docs(frontend): clarify Pi management workflow

This commit is contained in:
User
2026-08-22 21:02:10 +02:00
parent 120816d81c
commit 80b3575b2e
2 changed files with 108 additions and 90 deletions
+46 -61
View File
@@ -238,30 +238,35 @@ test("shows a seven-step host-terminal workflow in scrollable platform tabs", as
"Open the project root",
"Edit the provider catalog",
"Enable the model",
"Check the provider credential",
"Complete the provider setup",
"Reload Pi configuration",
"Update the Pi version",
"Recover a failed update",
"Diagnose and recover a failed operation",
]);
expect(linux).toHaveTextContent("The deploy directory is beside compose.yaml");
expect(linux).toHaveTextContent("All commands below start in this project root");
expect(linux).toHaveTextContent("mkdir -p bin");
expect(linux).toHaveTextContent("go -C tools/thothctl build -o ../../bin/thothctl ./cmd/thothctl");
expect(linux).toHaveTextContent("cd /absolute/path/to/ThothII");
expect(linux).toHaveTextContent("tht pi status");
expect(linux).toHaveTextContent("tht pi doctor");
expect(linux).toHaveTextContent("Invalid JSON");
expect(linux).toHaveTextContent("Provider connectivity");
expect(linux).toHaveTextContent("If maintenance is inactive");
expect(linux).toHaveTextContent("active after an update");
expect(linux).toHaveTextContent("active after a restart");
expect(linux).toHaveTextContent("run every command in this list from that directory");
expect(linux).toHaveTextContent("deploy/pi/models.json");
expect(linux).toHaveTextContent("deploy/pi/settings.json");
expect(linux).toHaveTextContent("baseUrl is the provider API endpoint");
expect(linux).toHaveTextContent("enabledModels uses provider/model identifiers");
expect(linux).toHaveTextContent("Pi reads the provider API key from a protected file on the host");
expect(linux).toHaveTextContent("Do not put the key in models.json or settings.json");
expect(linux).toHaveTextContent("./bin/thothctl pi restart --yes --drain");
expect(linux).toHaveTextContent("./bin/thothctl pi update");
expect(linux).toHaveTextContent("./bin/thothctl pi update --version <VERSION> --source pull --image <IMAGE>@sha256:<DIGEST> --yes --drain");
expect(linux).toHaveTextContent("The command installs the Pi version pinned in docker/core.Dockerfile");
expect(linux).toHaveTextContent("./bin/thothctl pi maintenance status");
expect(linux).toHaveTextContent("./bin/thothctl pi logs");
expect(linux).toHaveTextContent("./bin/thothctl pi rollback --yes");
expect(linux).toHaveTextContent("./bin/thothctl pi maintenance recover --yes");
expect(linux).not.toHaveTextContent("~/bin/");
expect(linux).toHaveTextContent("During the initial installation");
expect(linux).toHaveTextContent("Pi credentials file location");
expect(linux).toHaveTextContent("tht pi restart --yes --drain");
expect(linux).toHaveTextContent("tht pi update");
expect(linux).toHaveTextContent("tht pi update --version <VERSION> --source pull --image <IMAGE>@sha256:<DIGEST> --yes --drain");
expect(linux).toHaveTextContent("tht pi maintenance status");
expect(linux).toHaveTextContent("tht pi logs");
expect(linux).toHaveTextContent("tht pi rollback --yes");
expect(linux).toHaveTextContent("tht pi maintenance recover --yes");
expect(linux).not.toHaveTextContent("PI_AUTH_FILE");
expect(linux).not.toHaveTextContent("thothctl");
expect(linux).not.toHaveTextContent("~/.pi/agent/");
await user.click(macosTab);
@@ -271,24 +276,22 @@ test("shows a seven-step host-terminal workflow in scrollable platform tabs", as
"Open the project root",
"Edit the provider catalog",
"Enable the model",
"Check the provider credential",
"Complete the provider setup",
"Reload Pi configuration",
"Update the Pi version",
"Recover a failed update",
"Diagnose and recover a failed operation",
]);
expect(macos).toHaveTextContent("The deploy directory is beside compose.yaml");
expect(macos).toHaveTextContent("All commands below start in this project root");
expect(macos).toHaveTextContent("go -C tools/thothctl build -o ../../bin/thothctl ./cmd/thothctl");
expect(macos).toHaveTextContent("cd /absolute/path/to/ThothII");
expect(macos).toHaveTextContent("run every command in this list from that directory");
expect(macos).toHaveTextContent("deploy/pi/models.json");
expect(macos).toHaveTextContent("deploy/pi/settings.json");
expect(macos).toHaveTextContent("./bin/thothctl pi restart --yes --drain");
expect(macos).toHaveTextContent("./bin/thothctl pi update");
expect(macos).toHaveTextContent("./bin/thothctl pi update --version <VERSION> --source pull --image <IMAGE>@sha256:<DIGEST> --yes --drain");
expect(macos).toHaveTextContent("The command installs the Pi version pinned in docker/core.Dockerfile");
expect(macos).toHaveTextContent("./bin/thothctl pi maintenance status");
expect(macos).toHaveTextContent("./bin/thothctl pi logs");
expect(macos).toHaveTextContent("./bin/thothctl pi rollback --yes");
expect(macos).toHaveTextContent("./bin/thothctl pi maintenance recover --yes");
expect(macos).toHaveTextContent("tht pi restart --yes --drain");
expect(macos).toHaveTextContent("tht pi update");
expect(macos).toHaveTextContent("tht pi update --version <VERSION> --source pull --image <IMAGE>@sha256:<DIGEST> --yes --drain");
expect(macos).toHaveTextContent("tht pi maintenance status");
expect(macos).toHaveTextContent("tht pi logs");
expect(macos).toHaveTextContent("tht pi rollback --yes");
expect(macos).toHaveTextContent("tht pi maintenance recover --yes");
await user.click(windowsTab);
const windows = screen.getByRole("tabpanel", { name: "Windows" });
@@ -297,44 +300,26 @@ test("shows a seven-step host-terminal workflow in scrollable platform tabs", as
"Open the project root",
"Edit the provider catalog",
"Enable the model",
"Check the provider credential",
"Complete the provider setup",
"Reload Pi configuration",
"Update the Pi version",
"Recover a failed update",
"Diagnose and recover a failed operation",
]);
expect(windows).toHaveTextContent("The deploy directory is beside compose.yaml");
expect(windows).toHaveTextContent("All commands below start in this project root");
expect(windows).toHaveTextContent("Set-Location C:\\absolute\\path\\to\\ThothII");
expect(windows).toHaveTextContent("run every command in this list from that directory");
expect(windows).toHaveTextContent("deploy\\pi\\models.json");
expect(windows).toHaveTextContent("deploy\\pi\\settings.json");
expect(windows).toHaveTextContent("New-Item -ItemType Directory -Force bin");
expect(windows).toHaveTextContent("go -C tools/thothctl build -o ../../bin/thothctl.exe ./cmd/thothctl");
expect(windows).toHaveTextContent('.\\bin\\thothctl.exe pi restart --yes --drain');
expect(windows).toHaveTextContent('.\\bin\\thothctl.exe pi update');
expect(windows).toHaveTextContent('.\\bin\\thothctl.exe pi update --version <VERSION> --source pull --image <IMAGE>@sha256:<DIGEST> --yes --drain');
expect(windows).toHaveTextContent("The command installs the Pi version pinned in docker/core.Dockerfile");
expect(windows).toHaveTextContent('.\\bin\\thothctl.exe pi maintenance status');
expect(windows).toHaveTextContent('.\\bin\\thothctl.exe pi logs');
expect(windows).toHaveTextContent('.\\bin\\thothctl.exe pi rollback --yes');
expect(windows).toHaveTextContent('.\\bin\\thothctl.exe pi maintenance recover --yes');
expect(windows).not.toHaveTextContent("~\\bin\\");
expect(windows).toHaveTextContent('tht pi restart --yes --drain');
expect(windows).toHaveTextContent('tht pi update');
expect(windows).toHaveTextContent('tht pi update --version <VERSION> --source pull --image <IMAGE>@sha256:<DIGEST> --yes --drain');
expect(windows).toHaveTextContent('tht pi maintenance status');
expect(windows).toHaveTextContent('tht pi logs');
expect(windows).toHaveTextContent('tht pi rollback --yes');
expect(windows).toHaveTextContent('tht pi maintenance recover --yes');
expect(windows).not.toHaveTextContent("PI_AUTH_FILE");
expect(windows).not.toHaveTextContent("thothctl");
});
test("keeps the required PI_AUTH_FILE guidance in one static text node", async () => {
const user = userEvent.setup();
renderManagement();
const tablist = await screen.findByRole("tablist", { name: "Pi host platform" });
await user.click(within(tablist).getByRole("tab", { name: "Linux" }));
const credentialStep = screen.getByRole("heading", { name: "Check the provider credential" }).closest("li");
const guidance = credentialStep?.querySelector("p");
expect(
Array.from(guidance?.childNodes ?? []).some(
(node) => node.nodeType === Node.TEXT_NODE
&& node.textContent?.includes("Pi reads the provider API key from a protected file on the host"),
),
).toBe(true);
});
test("reloads installation defaults when the panel is reopened", async () => {
let statusCalls = 0;
+62 -29
View File
@@ -107,12 +107,15 @@ type PiPlatformDetails = {
modelsPath: string;
settingsPath: string;
terminal: string;
changeDirectoryCommand: string;
credentialProtection: string;
buildCommand: string;
restartCommand: string;
updateCommand: string;
pullCommand: string;
recoveryCommands: string;
diagnosticCommands: string;
rollbackCommand: string;
recoverCommand: string;
verificationCommands: string;
};
const piPlatforms: Array<{ id: PiPlatform; label: string; details: PiPlatformDetails }> = [
@@ -123,12 +126,15 @@ const piPlatforms: Array<{ id: PiPlatform; label: string; details: PiPlatformDet
modelsPath: "deploy/pi/models.json",
settingsPath: "deploy/pi/settings.json",
terminal: "a terminal",
changeDirectoryCommand: "cd /absolute/path/to/ThothII",
credentialProtection: "a protected host file with mode 0600",
buildCommand: "mkdir -p bin\ngo -C tools/thothctl build -o ../../bin/thothctl ./cmd/thothctl",
restartCommand: "./bin/thothctl pi restart --yes --drain",
updateCommand: "./bin/thothctl pi update",
pullCommand: "./bin/thothctl pi update --version <VERSION> --source pull --image <IMAGE>@sha256:<DIGEST> --yes --drain",
recoveryCommands: "./bin/thothctl pi maintenance status\n./bin/thothctl pi logs\n./bin/thothctl pi rollback --yes\n./bin/thothctl pi maintenance recover --yes",
restartCommand: "tht pi restart --yes --drain",
updateCommand: "tht pi update",
pullCommand: "tht pi update --version <VERSION> --source pull --image <IMAGE>@sha256:<DIGEST> --yes --drain",
diagnosticCommands: "tht pi maintenance status\ntht pi status\ntht pi doctor\ntht pi logs",
rollbackCommand: "tht pi rollback --yes",
recoverCommand: "tht pi maintenance recover --yes",
verificationCommands: "tht pi maintenance status\ntht pi doctor\ntht pi test",
},
},
{
@@ -138,12 +144,15 @@ const piPlatforms: Array<{ id: PiPlatform; label: string; details: PiPlatformDet
modelsPath: "deploy/pi/models.json",
settingsPath: "deploy/pi/settings.json",
terminal: "Terminal",
changeDirectoryCommand: "cd /absolute/path/to/ThothII",
credentialProtection: "a protected host file with mode 0600",
buildCommand: "mkdir -p bin\ngo -C tools/thothctl build -o ../../bin/thothctl ./cmd/thothctl",
restartCommand: "./bin/thothctl pi restart --yes --drain",
updateCommand: "./bin/thothctl pi update",
pullCommand: "./bin/thothctl pi update --version <VERSION> --source pull --image <IMAGE>@sha256:<DIGEST> --yes --drain",
recoveryCommands: "./bin/thothctl pi maintenance status\n./bin/thothctl pi logs\n./bin/thothctl pi rollback --yes\n./bin/thothctl pi maintenance recover --yes",
restartCommand: "tht pi restart --yes --drain",
updateCommand: "tht pi update",
pullCommand: "tht pi update --version <VERSION> --source pull --image <IMAGE>@sha256:<DIGEST> --yes --drain",
diagnosticCommands: "tht pi maintenance status\ntht pi status\ntht pi doctor\ntht pi logs",
rollbackCommand: "tht pi rollback --yes",
recoverCommand: "tht pi maintenance recover --yes",
verificationCommands: "tht pi maintenance status\ntht pi doctor\ntht pi test",
},
},
{
@@ -153,12 +162,15 @@ const piPlatforms: Array<{ id: PiPlatform; label: string; details: PiPlatformDet
modelsPath: "deploy\\pi\\models.json",
settingsPath: "deploy\\pi\\settings.json",
terminal: "PowerShell",
changeDirectoryCommand: "Set-Location C:\\absolute\\path\\to\\ThothII",
credentialProtection: "a protected host file with a user-only ACL",
buildCommand: "New-Item -ItemType Directory -Force bin | Out-Null\ngo -C tools/thothctl build -o ../../bin/thothctl.exe ./cmd/thothctl",
restartCommand: ".\\bin\\thothctl.exe pi restart --yes --drain",
updateCommand: ".\\bin\\thothctl.exe pi update",
pullCommand: ".\\bin\\thothctl.exe pi update --version <VERSION> --source pull --image <IMAGE>@sha256:<DIGEST> --yes --drain",
recoveryCommands: ".\\bin\\thothctl.exe pi maintenance status\n.\\bin\\thothctl.exe pi logs\n.\\bin\\thothctl.exe pi rollback --yes\n.\\bin\\thothctl.exe pi maintenance recover --yes",
restartCommand: "tht pi restart --yes --drain",
updateCommand: "tht pi update",
pullCommand: "tht pi update --version <VERSION> --source pull --image <IMAGE>@sha256:<DIGEST> --yes --drain",
diagnosticCommands: "tht pi maintenance status\ntht pi status\ntht pi doctor\ntht pi logs",
rollbackCommand: "tht pi rollback --yes",
recoverCommand: "tht pi maintenance recover --yes",
verificationCommands: "tht pi maintenance status\ntht pi doctor\ntht pi test",
},
},
];
@@ -171,13 +183,21 @@ function PiInstructionSteps({ details }: { details: PiPlatformDetails }) {
return <ol className="grid gap-4 pl-5 marker:font-semibold marker:text-muted-foreground">
<li>
<h4 className="font-semibold text-foreground">Open the project root</h4>
<p className="mt-1 text-muted-foreground">Using {details.terminal}, open the current ThothII checkout or worktree root. All commands below start in this project root. The deploy directory is beside <code>compose.yaml</code>. If this checkout has no <code>bin</code> directory, create it and build the local CLI once:</p>
<PiCodeBlock className="mt-2">{details.buildCommand}</PiCodeBlock>
<p className="mt-2 text-muted-foreground">The commands below use the executable built in this checkout, so they cannot accidentally target another worktree.</p>
<p className="mt-1 text-muted-foreground">Using {details.terminal}, before editing files or running any lifecycle command, enter the exact checkout or worktree root. Run the platform command below, then run every command in this list from that directory: <code>tht</code> resolves the installation descriptor and configuration from the selected project. A worktree is an independent checkout, so this prevents changing a different copy by mistake.</p>
<PiCodeBlock className="mt-2">{details.changeDirectoryCommand}</PiCodeBlock>
<p className="mt-2 text-muted-foreground">The relevant layout is:</p>
<PiCodeBlock className="mt-1">project-root/
├── compose.yaml
├── deploy/
│ └── pi/
│ ├── models.json
│ └── settings.json
└── docker/</PiCodeBlock>
<p className="mt-2 text-muted-foreground">The <code>deploy/</code> directory contains the selected installation descriptor and profile files. You do not need to create or manage a <code>bin/</code> directory: <code>tht</code> is the installed host CLI. If discovery finds multiple descriptors, pass the intended one explicitly with <code>--installation &lt;absolute-path&gt;/thothii-installation.yaml</code>. If <code>tht</code> is not on your <code>PATH</code>, install it using the installation guide.</p>
</li>
<li>
<h4 className="font-semibold text-foreground">Edit the provider catalog</h4>
<p className="mt-1 text-muted-foreground">Edit <code>{details.modelsPath}</code>. It is the provider catalog.</p>
<p className="mt-1 text-muted-foreground">Edit <code>{details.modelsPath}</code> only when adding or correcting a provider/model definition. The provider catalog is an address book/map of the services Pi can call: each entry supplies the API endpoint and format, and lists the model identifiers offered there. It does not enable a model and never contains credentials.</p>
<dl className="mt-2 grid grid-cols-[auto_1fr] gap-x-2 gap-y-1 text-muted-foreground">
<dt className="font-mono text-foreground">baseUrl </dt><dd>is the provider API endpoint.</dd>
<dt className="font-mono text-foreground">api </dt><dd>selects the provider API format.</dd>
@@ -188,31 +208,44 @@ function PiInstructionSteps({ details }: { details: PiPlatformDetails }) {
</li>
<li>
<h4 className="font-semibold text-foreground">Enable the model</h4>
<p className="mt-1 text-muted-foreground">Edit <code>{details.settingsPath}</code>. It is the enabled-model policy, not the provider catalog.</p>
<p className="mt-1 text-muted-foreground">Enable a model after the provider setup is ready. For a custom provider, first define the provider and model ID in <code>{details.modelsPath}</code>; built-in Pi models may already be known without a local catalog entry. Then edit <code>{details.settingsPath}</code> in the project root, add the exact <code>provider/model</code> identifier to <code>enabledModels</code>, and reload Pi. Do not edit it merely to choose the default provider/model: use this page or <code>tht pi configure</code>.</p>
<dl className="mt-2 grid grid-cols-[auto_1fr] gap-x-2 gap-y-1 text-muted-foreground">
<dt className="font-mono text-foreground">enabledModels </dt><dd>uses provider/model identifiers to choose the models available for new Pi work.</dd>
</dl>
</li>
<li>
<h4 className="font-semibold text-foreground">Check the provider credential</h4>
<p className="mt-1 text-muted-foreground">Pi reads the provider API key from a protected file on the host. <code>PI_AUTH_FILE</code> tells this installation which file to use; Docker mounts it read-only into the core container. Do not put the key in <code>models.json</code> or <code>settings.json</code>. Keep {details.credentialProtection}.</p>
<h4 className="font-semibold text-foreground">Complete the provider setup</h4>
<p className="mt-1 text-muted-foreground">During the initial installation, run <code>tht setup</code> and complete the prompt named “Pi credentials file location”; it can create the empty protected template. On an existing installation with a missing credential, correct the protected credential file selected during setup by following the installation guide. Keep {details.credentialProtection}, then restart and run <code>tht pi doctor</code> and <code>tht pi test</code>. Never paste credentials into either JSON file, the page, a command, or a log.</p>
</li>
<li>
<h4 className="font-semibold text-foreground">Reload Pi configuration</h4>
<p className="mt-1 text-muted-foreground">After changing the catalog, policy, or selected credential file, reload Pi configuration.</p>
<p className="mt-1 text-muted-foreground">After changing <code>models.json</code>, <code>settings.json</code>, or the provider credential, reload the running <code>core</code> service so it reads the new files. This is a configuration reload, not a Pi version update; it keeps the current image.</p>
<PiCodeBlock className="mt-2">{details.restartCommand}</PiCodeBlock>
</li>
<li>
<h4 className="font-semibold text-foreground">Update the Pi version</h4>
<p className="mt-1 text-muted-foreground">The command installs the Pi version pinned in <code>docker/core.Dockerfile</code>. Use <code>--version &lt;VERSION&gt;</code> only when you deliberately want another version.</p>
<p className="mt-1 text-muted-foreground">Use <code>tht pi update</code> for the routine build: it resolves the latest stable Pi release from the registry, builds it locally, drains active sessions, and recreates only <code>core</code>. Add <code>--version &lt;VERSION&gt;</code> when an explicitly reviewed version is required. The digest-pinned <code>--source pull</code> form is for an already-built, reviewed registry image; its declared version must match the digest-pinned image. Configuration changes use Reload above, not update.</p>
<PiCodeBlock className="mt-2">{details.updateCommand}</PiCodeBlock>
<p className="mt-2 text-[11px] text-muted-foreground">Advanced: pull an immutable, digest-pinned image.</p>
<PiCodeBlock className="mt-1 text-[11px] text-muted-foreground">{details.pullCommand}</PiCodeBlock>
</li>
<li>
<h4 className="font-semibold text-foreground">Recover a failed update</h4>
<p className="mt-1 text-muted-foreground">Check maintenance status and bounded sanitized Pi logs first. For a failed update, use rollback. For restart or update maintenance recovery after repairing the reported problem, use maintenance recovery.</p>
<PiCodeBlock className="mt-2 text-[11px] text-muted-foreground">{details.recoveryCommands}</PiCodeBlock>
<h4 className="font-semibold text-foreground">Diagnose and recover a failed operation</h4>
<p className="mt-1 text-muted-foreground">Start with these diagnostics, in order:</p>
<PiCodeBlock className="mt-2 text-[11px] text-muted-foreground">{details.diagnosticCommands}</PiCodeBlock>
<dl className="mt-2 grid gap-2 text-muted-foreground">
<dt className="font-semibold text-foreground">Configuration</dt><dd>Invalid JSON: fix the reported file and validate it. A provider/model ID mismatch or a model missing from <code>enabledModels</code>: correct the IDs or policy, then reload.</dd>
<dt className="font-semibold text-foreground">Credentials</dt><dd>Missing or unreadable credential: correct the protected credential file selected during setup and its permissions, without printing the file.</dd>
<dt className="font-semibold text-foreground">Provider connectivity</dt><dd>Wrong <code>baseUrl</code>, network, or provider error: correct the endpoint or network, then retry.</dd>
<dt className="font-semibold text-foreground">Docker and disk</dt><dd>Unhealthy Docker or insufficient disk: restore Docker health or free space before retrying.</dd>
</dl>
<p className="mt-2 text-muted-foreground">If maintenance is inactive, no recovery is needed: correct the cause and retry the original command.</p>
<p className="mt-2 text-muted-foreground">If maintenance is active after an update, rollback the previous image:</p>
<PiCodeBlock className="mt-1 text-[11px] text-muted-foreground">{details.rollbackCommand}</PiCodeBlock>
<p className="mt-2 text-muted-foreground">If maintenance is active after a restart, fix the cause first, then recover the captured lifecycle state:</p>
<PiCodeBlock className="mt-1 text-[11px] text-muted-foreground">{details.recoverCommand}</PiCodeBlock>
<p className="mt-2 text-muted-foreground">After the selected remedy, verify the installation:</p>
<PiCodeBlock className="mt-1 text-[11px] text-muted-foreground">{details.verificationCommands}</PiCodeBlock>
</li>
</ol>;
}