feat(doctor): offer update first
This commit is contained in:
@@ -30,6 +30,7 @@
|
|||||||
- CLI/Pairing: accept positional provider for `pairing list|approve` (npm-run compatible); update docs/bot hints.
|
- CLI/Pairing: accept positional provider for `pairing list|approve` (npm-run compatible); update docs/bot hints.
|
||||||
- Branding: normalize user-facing “ClawdBot”/“CLAWDBOT” → “Clawdbot” (CLI, status, docs).
|
- Branding: normalize user-facing “ClawdBot”/“CLAWDBOT” → “Clawdbot” (CLI, status, docs).
|
||||||
- Auto-reply: fix native `/model` not updating the actual chat session (Telegram/Slack/Discord). (#646)
|
- Auto-reply: fix native `/model` not updating the actual chat session (Telegram/Slack/Discord). (#646)
|
||||||
|
- Doctor: offer to run `clawdbot update` first on git installs (keeps doctor output aligned with latest).
|
||||||
- Doctor: avoid false legacy workspace warning when install dir is `~/clawdbot`. (#660)
|
- Doctor: avoid false legacy workspace warning when install dir is `~/clawdbot`. (#660)
|
||||||
- iMessage: fix reasoning persistence across DMs; avoid partial/duplicate replies when reasoning is enabled. (#655) — thanks @antons.
|
- iMessage: fix reasoning persistence across DMs; avoid partial/duplicate replies when reasoning is enabled. (#655) — thanks @antons.
|
||||||
- Models/Auth: allow MiniMax API configs without `models.providers.minimax.apiKey` (auth profiles / `MINIMAX_API_KEY`). (#656) — thanks @mneves75.
|
- Models/Auth: allow MiniMax API configs without `models.providers.minimax.apiKey` (auth profiles / `MINIMAX_API_KEY`). (#656) — thanks @mneves75.
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ read_when:
|
|||||||
|
|
||||||
Safely update a **source checkout** (git install) of Clawdbot.
|
Safely update a **source checkout** (git install) of Clawdbot.
|
||||||
|
|
||||||
If you installed via **npm/pnpm** (global install, no git metadata), use the package manager flow in [Updating](/install/updating).
|
If you installed via **npm/pnpm/bun** (global install, no git metadata), use the package manager flow in [Updating](/install/updating).
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@@ -42,6 +42,6 @@ High-level:
|
|||||||
|
|
||||||
## See also
|
## See also
|
||||||
|
|
||||||
|
- `clawdbot doctor` (offers to run update first on git checkouts)
|
||||||
- [Updating](/install/updating)
|
- [Updating](/install/updating)
|
||||||
- [CLI reference](/cli)
|
- [CLI reference](/cli)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
summary: "Updating Clawdbot safely (npm or source), plus rollback strategy"
|
summary: "Updating Clawdbot safely (global install or source), plus rollback strategy"
|
||||||
read_when:
|
read_when:
|
||||||
- Updating Clawdbot
|
- Updating Clawdbot
|
||||||
- Something breaks after an update
|
- Something breaks after an update
|
||||||
@@ -11,14 +11,14 @@ Clawdbot is moving fast (pre “1.0”). Treat updates like shipping infra: upda
|
|||||||
|
|
||||||
## Before you update
|
## Before you update
|
||||||
|
|
||||||
- Know how you installed: **npm** (global) vs **from source** (git clone).
|
- Know how you installed: **global** (npm/pnpm/bun) vs **from source** (git clone).
|
||||||
- Know how your Gateway is running: **foreground terminal** vs **supervised service** (launchd/systemd).
|
- Know how your Gateway is running: **foreground terminal** vs **supervised service** (launchd/systemd).
|
||||||
- Snapshot your tailoring:
|
- Snapshot your tailoring:
|
||||||
- Config: `~/.clawdbot/clawdbot.json`
|
- Config: `~/.clawdbot/clawdbot.json`
|
||||||
- Credentials: `~/.clawdbot/credentials/`
|
- Credentials: `~/.clawdbot/credentials/`
|
||||||
- Workspace: `~/clawd`
|
- Workspace: `~/clawd`
|
||||||
|
|
||||||
## Update (npm install)
|
## Update (global install)
|
||||||
|
|
||||||
Global install (pick one):
|
Global install (pick one):
|
||||||
|
|
||||||
@@ -30,6 +30,10 @@ npm i -g clawdbot@latest
|
|||||||
pnpm add -g clawdbot@latest
|
pnpm add -g clawdbot@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun add -g clawdbot@latest
|
||||||
|
```
|
||||||
|
|
||||||
Then:
|
Then:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -55,7 +59,7 @@ It runs a safe-ish update flow:
|
|||||||
- Fetches + rebases against the configured upstream.
|
- Fetches + rebases against the configured upstream.
|
||||||
- Installs deps, builds, builds the Control UI, and runs `clawdbot doctor`.
|
- Installs deps, builds, builds the Control UI, and runs `clawdbot doctor`.
|
||||||
|
|
||||||
If you installed via **npm/pnpm** (no git metadata), `clawdbot update` will skip. Use “Update (npm install)” instead.
|
If you installed via **npm/pnpm/bun** (no git metadata), `clawdbot update` will skip. Use “Update (global install)” instead.
|
||||||
|
|
||||||
## Update (Control UI / RPC)
|
## Update (Control UI / RPC)
|
||||||
|
|
||||||
@@ -90,12 +94,14 @@ pnpm clawdbot health
|
|||||||
Notes:
|
Notes:
|
||||||
- `pnpm build` matters when you run the packaged `clawdbot` binary ([`dist/entry.js`](https://github.com/clawdbot/clawdbot/blob/main/dist/entry.js)) or use Node to run `dist/`.
|
- `pnpm build` matters when you run the packaged `clawdbot` binary ([`dist/entry.js`](https://github.com/clawdbot/clawdbot/blob/main/dist/entry.js)) or use Node to run `dist/`.
|
||||||
- If you run directly from TypeScript (`pnpm clawdbot ...` / `bun run clawdbot ...`), a rebuild is usually unnecessary, but **config migrations still apply** → run doctor.
|
- If you run directly from TypeScript (`pnpm clawdbot ...` / `bun run clawdbot ...`), a rebuild is usually unnecessary, but **config migrations still apply** → run doctor.
|
||||||
- Switching between npm and git installs is easy: install the other flavor, then run `clawdbot doctor` so the gateway service entrypoint is rewritten to the current install.
|
- Switching between global and git installs is easy: install the other flavor, then run `clawdbot doctor` so the gateway service entrypoint is rewritten to the current install.
|
||||||
|
|
||||||
## Always run: `clawdbot doctor`
|
## Always run: `clawdbot doctor`
|
||||||
|
|
||||||
Doctor is the “safe update” command. It’s intentionally boring: repair + migrate + warn.
|
Doctor is the “safe update” command. It’s intentionally boring: repair + migrate + warn.
|
||||||
|
|
||||||
|
Note: if you’re on a **source install** (git checkout), `clawdbot doctor` will offer to run `clawdbot update` first.
|
||||||
|
|
||||||
Typical things it does:
|
Typical things it does:
|
||||||
- Migrate deprecated config keys / legacy config file locations.
|
- Migrate deprecated config keys / legacy config file locations.
|
||||||
- Audit DM policies and warn on risky “open” settings.
|
- Audit DM policies and warn on risky “open” settings.
|
||||||
@@ -127,7 +133,7 @@ Runbook + exact service labels: [Gateway runbook](/gateway)
|
|||||||
|
|
||||||
## Rollback / pinning (when something breaks)
|
## Rollback / pinning (when something breaks)
|
||||||
|
|
||||||
### Pin (npm)
|
### Pin (global install)
|
||||||
|
|
||||||
Install a known-good version:
|
Install a known-good version:
|
||||||
|
|
||||||
@@ -135,6 +141,14 @@ Install a known-good version:
|
|||||||
npm i -g clawdbot@2026.1.9
|
npm i -g clawdbot@2026.1.9
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm add -g clawdbot@2026.1.9
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun add -g clawdbot@2026.1.9
|
||||||
|
```
|
||||||
|
|
||||||
Then restart + re-run doctor:
|
Then restart + re-run doctor:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ function installFailingFetchCapture() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("openai-responses reasoning replay", () => {
|
describe("openai-responses reasoning replay", () => {
|
||||||
it("replays reasoning for tool-call-only turns", async () => {
|
it("does not replay reasoning for tool-call-only turns", async () => {
|
||||||
const cap = installFailingFetchCapture();
|
const cap = installFailingFetchCapture();
|
||||||
try {
|
try {
|
||||||
const model = buildModel();
|
const model = buildModel();
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise<void> {
|
|||||||
);
|
);
|
||||||
defaultRuntime.log(
|
defaultRuntime.log(
|
||||||
theme.muted(
|
theme.muted(
|
||||||
"Examples: `npm i -g clawdbot@latest` or `pnpm add -g clawdbot@latest`",
|
"Examples: `npm i -g clawdbot@latest`, `pnpm add -g clawdbot@latest`, or `bun add -g clawdbot@latest`",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -206,7 +206,7 @@ Examples:
|
|||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
- For git installs: fetches, rebases, installs deps, builds, and runs doctor
|
- For git installs: fetches, rebases, installs deps, builds, and runs doctor
|
||||||
- For npm installs: use npm/pnpm to reinstall (see docs/install/updating.md)
|
- For global installs: use npm/pnpm/bun to reinstall (see docs/install/updating.md)
|
||||||
- Skips update if the working directory has uncommitted changes
|
- Skips update if the working directory has uncommitted changes
|
||||||
|
|
||||||
${theme.muted("Docs:")} ${formatDocsLink("/updating", "docs.clawd.bot/updating")}`,
|
${theme.muted("Docs:")} ${formatDocsLink("/updating", "docs.clawd.bot/updating")}`,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|||||||
|
|
||||||
let originalIsTTY: boolean | undefined;
|
let originalIsTTY: boolean | undefined;
|
||||||
let originalStateDir: string | undefined;
|
let originalStateDir: string | undefined;
|
||||||
|
let originalUpdateInProgress: string | undefined;
|
||||||
let tempStateDir: string | undefined;
|
let tempStateDir: string | undefined;
|
||||||
|
|
||||||
function setStdinTty(value: boolean | undefined) {
|
function setStdinTty(value: boolean | undefined) {
|
||||||
@@ -19,9 +20,66 @@ function setStdinTty(value: boolean | undefined) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
confirm.mockReset().mockResolvedValue(true);
|
||||||
|
select.mockReset().mockResolvedValue("node");
|
||||||
|
note.mockClear();
|
||||||
|
|
||||||
|
readConfigFileSnapshot.mockReset();
|
||||||
|
writeConfigFile.mockReset().mockResolvedValue(undefined);
|
||||||
|
resolveClawdbotPackageRoot.mockReset().mockResolvedValue(null);
|
||||||
|
runGatewayUpdate.mockReset().mockResolvedValue({
|
||||||
|
status: "skipped",
|
||||||
|
mode: "unknown",
|
||||||
|
steps: [],
|
||||||
|
durationMs: 0,
|
||||||
|
});
|
||||||
|
legacyReadConfigFileSnapshot.mockReset().mockResolvedValue({
|
||||||
|
path: "/tmp/clawdis.json",
|
||||||
|
exists: false,
|
||||||
|
raw: null,
|
||||||
|
parsed: {},
|
||||||
|
valid: true,
|
||||||
|
config: {},
|
||||||
|
issues: [],
|
||||||
|
legacyIssues: [],
|
||||||
|
});
|
||||||
|
createConfigIO.mockReset().mockImplementation(() => ({
|
||||||
|
readConfigFileSnapshot: legacyReadConfigFileSnapshot,
|
||||||
|
}));
|
||||||
|
runExec.mockReset().mockResolvedValue({ stdout: "", stderr: "" });
|
||||||
|
runCommandWithTimeout.mockReset().mockResolvedValue({
|
||||||
|
stdout: "",
|
||||||
|
stderr: "",
|
||||||
|
code: 0,
|
||||||
|
signal: null,
|
||||||
|
killed: false,
|
||||||
|
});
|
||||||
|
ensureAuthProfileStore
|
||||||
|
.mockReset()
|
||||||
|
.mockReturnValue({ version: 1, profiles: {} });
|
||||||
|
migrateLegacyConfig.mockReset().mockImplementation((raw: unknown) => ({
|
||||||
|
config: raw as Record<string, unknown>,
|
||||||
|
changes: ["Moved routing.allowFrom → whatsapp.allowFrom."],
|
||||||
|
}));
|
||||||
|
findLegacyGatewayServices.mockReset().mockResolvedValue([]);
|
||||||
|
uninstallLegacyGatewayServices.mockReset().mockResolvedValue([]);
|
||||||
|
findExtraGatewayServices.mockReset().mockResolvedValue([]);
|
||||||
|
renderGatewayServiceCleanupHints.mockReset().mockReturnValue(["cleanup"]);
|
||||||
|
resolveGatewayProgramArguments.mockReset().mockResolvedValue({
|
||||||
|
programArguments: ["node", "cli", "gateway", "--port", "18789"],
|
||||||
|
});
|
||||||
|
serviceInstall.mockReset().mockResolvedValue(undefined);
|
||||||
|
serviceIsLoaded.mockReset().mockResolvedValue(false);
|
||||||
|
serviceStop.mockReset().mockResolvedValue(undefined);
|
||||||
|
serviceRestart.mockReset().mockResolvedValue(undefined);
|
||||||
|
serviceUninstall.mockReset().mockResolvedValue(undefined);
|
||||||
|
callGateway.mockReset().mockRejectedValue(new Error("gateway closed"));
|
||||||
|
|
||||||
originalIsTTY = process.stdin.isTTY;
|
originalIsTTY = process.stdin.isTTY;
|
||||||
setStdinTty(true);
|
setStdinTty(true);
|
||||||
originalStateDir = process.env.CLAWDBOT_STATE_DIR;
|
originalStateDir = process.env.CLAWDBOT_STATE_DIR;
|
||||||
|
originalUpdateInProgress = process.env.CLAWDBOT_UPDATE_IN_PROGRESS;
|
||||||
|
process.env.CLAWDBOT_UPDATE_IN_PROGRESS = "1";
|
||||||
tempStateDir = fs.mkdtempSync(
|
tempStateDir = fs.mkdtempSync(
|
||||||
path.join(os.tmpdir(), "clawdbot-doctor-state-"),
|
path.join(os.tmpdir(), "clawdbot-doctor-state-"),
|
||||||
);
|
);
|
||||||
@@ -39,6 +97,11 @@ afterEach(() => {
|
|||||||
} else {
|
} else {
|
||||||
process.env.CLAWDBOT_STATE_DIR = originalStateDir;
|
process.env.CLAWDBOT_STATE_DIR = originalStateDir;
|
||||||
}
|
}
|
||||||
|
if (originalUpdateInProgress === undefined) {
|
||||||
|
delete process.env.CLAWDBOT_UPDATE_IN_PROGRESS;
|
||||||
|
} else {
|
||||||
|
process.env.CLAWDBOT_UPDATE_IN_PROGRESS = originalUpdateInProgress;
|
||||||
|
}
|
||||||
if (tempStateDir) {
|
if (tempStateDir) {
|
||||||
fs.rmSync(tempStateDir, { recursive: true, force: true });
|
fs.rmSync(tempStateDir, { recursive: true, force: true });
|
||||||
tempStateDir = undefined;
|
tempStateDir = undefined;
|
||||||
@@ -50,6 +113,13 @@ const confirm = vi.fn().mockResolvedValue(true);
|
|||||||
const select = vi.fn().mockResolvedValue("node");
|
const select = vi.fn().mockResolvedValue("node");
|
||||||
const note = vi.fn();
|
const note = vi.fn();
|
||||||
const writeConfigFile = vi.fn().mockResolvedValue(undefined);
|
const writeConfigFile = vi.fn().mockResolvedValue(undefined);
|
||||||
|
const resolveClawdbotPackageRoot = vi.fn().mockResolvedValue(null);
|
||||||
|
const runGatewayUpdate = vi.fn().mockResolvedValue({
|
||||||
|
status: "skipped",
|
||||||
|
mode: "unknown",
|
||||||
|
steps: [],
|
||||||
|
durationMs: 0,
|
||||||
|
});
|
||||||
const migrateLegacyConfig = vi.fn((raw: unknown) => ({
|
const migrateLegacyConfig = vi.fn((raw: unknown) => ({
|
||||||
config: raw as Record<string, unknown>,
|
config: raw as Record<string, unknown>,
|
||||||
changes: ["Moved routing.allowFrom → whatsapp.allowFrom."],
|
changes: ["Moved routing.allowFrom → whatsapp.allowFrom."],
|
||||||
@@ -147,6 +217,14 @@ vi.mock("../process/exec.js", () => ({
|
|||||||
runCommandWithTimeout,
|
runCommandWithTimeout,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock("../infra/clawdbot-root.js", () => ({
|
||||||
|
resolveClawdbotPackageRoot,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("../infra/update-runner.js", () => ({
|
||||||
|
runGatewayUpdate,
|
||||||
|
}));
|
||||||
|
|
||||||
vi.mock("../agents/auth-profiles.js", async (importOriginal) => {
|
vi.mock("../agents/auth-profiles.js", async (importOriginal) => {
|
||||||
const actual = await importOriginal();
|
const actual = await importOriginal();
|
||||||
return {
|
return {
|
||||||
@@ -330,6 +408,57 @@ describe("doctor", () => {
|
|||||||
expect(serviceInstall).toHaveBeenCalledTimes(1);
|
expect(serviceInstall).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("offers to update first for git checkouts", async () => {
|
||||||
|
delete process.env.CLAWDBOT_UPDATE_IN_PROGRESS;
|
||||||
|
|
||||||
|
const root = "/tmp/clawdbot";
|
||||||
|
resolveClawdbotPackageRoot.mockResolvedValueOnce(root);
|
||||||
|
runCommandWithTimeout.mockResolvedValueOnce({
|
||||||
|
stdout: `${root}\n`,
|
||||||
|
stderr: "",
|
||||||
|
code: 0,
|
||||||
|
signal: null,
|
||||||
|
killed: false,
|
||||||
|
});
|
||||||
|
runGatewayUpdate.mockResolvedValueOnce({
|
||||||
|
status: "ok",
|
||||||
|
mode: "git",
|
||||||
|
root,
|
||||||
|
steps: [],
|
||||||
|
durationMs: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
readConfigFileSnapshot.mockResolvedValue({
|
||||||
|
path: "/tmp/clawdbot.json",
|
||||||
|
exists: true,
|
||||||
|
raw: "{}",
|
||||||
|
parsed: {},
|
||||||
|
valid: true,
|
||||||
|
config: {},
|
||||||
|
issues: [],
|
||||||
|
legacyIssues: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { doctorCommand } = await import("./doctor.js");
|
||||||
|
const runtime = {
|
||||||
|
log: vi.fn(),
|
||||||
|
error: vi.fn(),
|
||||||
|
exit: vi.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
await doctorCommand(runtime);
|
||||||
|
|
||||||
|
expect(runGatewayUpdate).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ cwd: root }),
|
||||||
|
);
|
||||||
|
expect(readConfigFileSnapshot).not.toHaveBeenCalled();
|
||||||
|
expect(
|
||||||
|
note.mock.calls.some(
|
||||||
|
([, title]) => typeof title === "string" && title === "Update result",
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it("migrates legacy config file", async () => {
|
it("migrates legacy config file", async () => {
|
||||||
readConfigFileSnapshot
|
readConfigFileSnapshot
|
||||||
.mockResolvedValueOnce({
|
.mockResolvedValueOnce({
|
||||||
|
|||||||
@@ -31,8 +31,11 @@ import { resolvePreferredNodePath } from "../daemon/runtime-paths.js";
|
|||||||
import { resolveGatewayService } from "../daemon/service.js";
|
import { resolveGatewayService } from "../daemon/service.js";
|
||||||
import { buildServiceEnvironment } from "../daemon/service-env.js";
|
import { buildServiceEnvironment } from "../daemon/service-env.js";
|
||||||
import { buildGatewayConnectionDetails, callGateway } from "../gateway/call.js";
|
import { buildGatewayConnectionDetails, callGateway } from "../gateway/call.js";
|
||||||
|
import { resolveClawdbotPackageRoot } from "../infra/clawdbot-root.js";
|
||||||
import { formatPortDiagnostics, inspectPortUsage } from "../infra/ports.js";
|
import { formatPortDiagnostics, inspectPortUsage } from "../infra/ports.js";
|
||||||
import { collectProvidersStatusIssues } from "../infra/providers-status-issues.js";
|
import { collectProvidersStatusIssues } from "../infra/providers-status-issues.js";
|
||||||
|
import { runGatewayUpdate } from "../infra/update-runner.js";
|
||||||
|
import { runCommandWithTimeout } from "../process/exec.js";
|
||||||
import type { RuntimeEnv } from "../runtime.js";
|
import type { RuntimeEnv } from "../runtime.js";
|
||||||
import { defaultRuntime } from "../runtime.js";
|
import { defaultRuntime } from "../runtime.js";
|
||||||
import { stylePromptTitle } from "../terminal/prompt-style.js";
|
import { stylePromptTitle } from "../terminal/prompt-style.js";
|
||||||
@@ -95,6 +98,25 @@ function resolveMode(cfg: ClawdbotConfig): "local" | "remote" {
|
|||||||
return cfg.gateway?.mode === "remote" ? "remote" : "local";
|
return cfg.gateway?.mode === "remote" ? "remote" : "local";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function detectClawdbotGitCheckout(
|
||||||
|
root: string,
|
||||||
|
): Promise<"git" | "not-git" | "unknown"> {
|
||||||
|
const res = await runCommandWithTimeout(
|
||||||
|
["git", "-C", root, "rev-parse", "--show-toplevel"],
|
||||||
|
{ timeoutMs: 5000 },
|
||||||
|
).catch(() => null);
|
||||||
|
if (!res) return "unknown";
|
||||||
|
if (res.code !== 0) {
|
||||||
|
// Avoid noisy "Update via package manager" notes when git is missing/broken,
|
||||||
|
// but do show it when this is clearly not a git checkout.
|
||||||
|
if (res.stderr.toLowerCase().includes("not a git repository")) {
|
||||||
|
return "not-git";
|
||||||
|
}
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
return res.stdout.trim() === root ? "git" : "not-git";
|
||||||
|
}
|
||||||
|
|
||||||
export async function doctorCommand(
|
export async function doctorCommand(
|
||||||
runtime: RuntimeEnv = defaultRuntime,
|
runtime: RuntimeEnv = defaultRuntime,
|
||||||
options: DoctorOptions = {},
|
options: DoctorOptions = {},
|
||||||
@@ -103,6 +125,68 @@ export async function doctorCommand(
|
|||||||
printWizardHeader(runtime);
|
printWizardHeader(runtime);
|
||||||
intro("Clawdbot doctor");
|
intro("Clawdbot doctor");
|
||||||
|
|
||||||
|
const updateInProgress = process.env.CLAWDBOT_UPDATE_IN_PROGRESS === "1";
|
||||||
|
const canOfferUpdate =
|
||||||
|
!updateInProgress &&
|
||||||
|
options.nonInteractive !== true &&
|
||||||
|
options.yes !== true &&
|
||||||
|
options.repair !== true &&
|
||||||
|
Boolean(process.stdin.isTTY);
|
||||||
|
if (canOfferUpdate) {
|
||||||
|
const root = await resolveClawdbotPackageRoot({
|
||||||
|
moduleUrl: import.meta.url,
|
||||||
|
argv1: process.argv[1],
|
||||||
|
cwd: process.cwd(),
|
||||||
|
});
|
||||||
|
if (root) {
|
||||||
|
const git = await detectClawdbotGitCheckout(root);
|
||||||
|
if (git === "git") {
|
||||||
|
const shouldUpdate = await prompter.confirm({
|
||||||
|
message: "Update Clawdbot from git before running doctor?",
|
||||||
|
initialValue: true,
|
||||||
|
});
|
||||||
|
if (shouldUpdate) {
|
||||||
|
note(
|
||||||
|
"Running update (fetch/rebase/build/ui:build/doctor)…",
|
||||||
|
"Update",
|
||||||
|
);
|
||||||
|
const result = await runGatewayUpdate({
|
||||||
|
cwd: root,
|
||||||
|
argv1: process.argv[1],
|
||||||
|
});
|
||||||
|
note(
|
||||||
|
[
|
||||||
|
`Status: ${result.status}`,
|
||||||
|
`Mode: ${result.mode}`,
|
||||||
|
result.root ? `Root: ${result.root}` : null,
|
||||||
|
result.reason ? `Reason: ${result.reason}` : null,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join("\n"),
|
||||||
|
"Update result",
|
||||||
|
);
|
||||||
|
if (result.status === "ok") {
|
||||||
|
outro(
|
||||||
|
"Update completed (doctor already ran as part of the update).",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (git === "not-git") {
|
||||||
|
note(
|
||||||
|
[
|
||||||
|
"This install is not a git checkout.",
|
||||||
|
"Update via your package manager, then rerun doctor:",
|
||||||
|
"- npm i -g clawdbot@latest",
|
||||||
|
"- pnpm add -g clawdbot@latest",
|
||||||
|
"- bun add -g clawdbot@latest",
|
||||||
|
].join("\n"),
|
||||||
|
"Update",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await maybeMigrateLegacyConfigFile(runtime);
|
await maybeMigrateLegacyConfigFile(runtime);
|
||||||
|
|
||||||
const snapshot = await readConfigFileSnapshot();
|
const snapshot = await readConfigFileSnapshot();
|
||||||
|
|||||||
@@ -148,9 +148,10 @@ async function runStep(
|
|||||||
argv: string[],
|
argv: string[],
|
||||||
cwd: string,
|
cwd: string,
|
||||||
timeoutMs: number,
|
timeoutMs: number,
|
||||||
|
env?: NodeJS.ProcessEnv,
|
||||||
): Promise<UpdateStepResult> {
|
): Promise<UpdateStepResult> {
|
||||||
const started = Date.now();
|
const started = Date.now();
|
||||||
const result = await runCommand(argv, { cwd, timeoutMs });
|
const result = await runCommand(argv, { cwd, timeoutMs, env });
|
||||||
const durationMs = Date.now() - started;
|
const durationMs = Date.now() - started;
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
@@ -346,6 +347,7 @@ export async function runGatewayUpdate(
|
|||||||
managerScriptArgs(manager, "clawdbot", ["doctor"]),
|
managerScriptArgs(manager, "clawdbot", ["doctor"]),
|
||||||
gitRoot,
|
gitRoot,
|
||||||
timeoutMs,
|
timeoutMs,
|
||||||
|
{ CLAWDBOT_UPDATE_IN_PROGRESS: "1" },
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user