chore: drop Clawdis legacy references

This commit is contained in:
Peter Steinberger
2026-01-15 06:18:34 +00:00
parent 60748b1370
commit d59aab7fd3
31 changed files with 61 additions and 592 deletions

View File

@@ -70,7 +70,7 @@
- Never commit or publish real phone numbers, videos, or live configuration values. Use obviously fake placeholders in docs, tests, and examples. - Never commit or publish real phone numbers, videos, or live configuration values. Use obviously fake placeholders in docs, tests, and examples.
## Troubleshooting ## Troubleshooting
- Rebrand/migration issues (Clawdis → Clawdbot) or legacy config/service warnings: run `clawdbot doctor` (see `docs/gateway/doctor.md`). - Rebrand/migration issues or legacy config/service warnings: run `clawdbot doctor` (see `docs/gateway/doctor.md`).
## Agent-Specific Notes ## Agent-Specific Notes
- Vocabulary: "makeup" = "mac app". - Vocabulary: "makeup" = "mac app".

View File

@@ -17,6 +17,7 @@
- Tools: add `web_search`/`web_fetch` (Brave API), auto-enable `web_fetch` for sandboxed sessions, and remove the `brave-search` skill. - Tools: add `web_search`/`web_fetch` (Brave API), auto-enable `web_fetch` for sandboxed sessions, and remove the `brave-search` skill.
- CLI/Docs: add a web tools configure section for storing Brave API keys and update onboarding tips. - CLI/Docs: add a web tools configure section for storing Brave API keys and update onboarding tips.
- Tools: return a setup hint (docs link) when web_search runs without a Brave API key. - Tools: return a setup hint (docs link) when web_search runs without a Brave API key.
- Config/Doctor: remove legacy Clawdis env fallbacks and config/service migrations (Clawdbot-only).
- Browser: add Chrome extension relay takeover mode (toolbar button), plus `clawdbot browser extension install/path` and remote browser control via `clawdbot browser serve` + `browser.controlToken`. - Browser: add Chrome extension relay takeover mode (toolbar button), plus `clawdbot browser extension install/path` and remote browser control via `clawdbot browser serve` + `browser.controlToken`.
- CLI/Docs: add per-command CLI doc pages and link them from `clawdbot <command> --help`. - CLI/Docs: add per-command CLI doc pages and link them from `clawdbot <command> --help`.

View File

@@ -44,18 +44,18 @@ file creation:
{ agent: { skipBootstrap: true } } { agent: { skipBootstrap: true } }
``` ```
## Legacy workspace folders ## Extra workspace folders
Older installs may have created `~/clawdis` or `~/clawdbot`. Keeping multiple Older installs may have created `~/clawdbot`. Keeping multiple workspace
workspace directories around can cause confusing auth or state drift, because directories around can cause confusing auth or state drift, because only one
only one workspace is active at a time. workspace is active at a time.
**Recommendation:** keep a single active workspace. If you no longer use the **Recommendation:** keep a single active workspace. If you no longer use the
legacy folders, archive or move them to Trash (for example `trash ~/clawdis`). extra folders, archive or move them to Trash (for example `trash ~/clawdbot`).
If you intentionally keep multiple workspaces, make sure If you intentionally keep multiple workspaces, make sure
`agents.defaults.workspace` points to the active one. `agents.defaults.workspace` points to the active one.
`clawdbot doctor` warns when it detects legacy workspace directories. `clawdbot doctor` warns when it detects extra workspace directories.
## Workspace file map (what each file means) ## Workspace file map (what each file means)

View File

@@ -59,13 +59,13 @@ cat ~/.clawdbot/clawdbot.json
- UI protocol freshness check (rebuilds Control UI when the protocol schema is newer). - UI protocol freshness check (rebuilds Control UI when the protocol schema is newer).
- Health check + restart prompt. - Health check + restart prompt.
- Skills status summary (eligible/missing/blocked). - Skills status summary (eligible/missing/blocked).
- Legacy config migration and normalization. - Config normalization for legacy values.
- OpenCode Zen provider override warnings (`models.providers.opencode`). - OpenCode Zen provider override warnings (`models.providers.opencode`).
- Legacy on-disk state migration (sessions/agent dir/WhatsApp auth). - Legacy on-disk state migration (sessions/agent dir/WhatsApp auth).
- State integrity and permissions checks (sessions, transcripts, state dir). - State integrity and permissions checks (sessions, transcripts, state dir).
- Config file permission checks (chmod 600) when running locally. - Config file permission checks (chmod 600) when running locally.
- Model auth health: checks OAuth expiry, can refresh expiring tokens, and reports auth-profile cooldown/disabled states. - Model auth health: checks OAuth expiry, can refresh expiring tokens, and reports auth-profile cooldown/disabled states.
- Legacy workspace dir detection (`~/clawdis`, `~/clawdbot`). - Extra workspace dir detection (`~/clawdbot`).
- Sandbox image repair when sandboxing is enabled. - Sandbox image repair when sandboxing is enabled.
- Legacy service migration and extra gateway detection. - Legacy service migration and extra gateway detection.
- Gateway runtime checks (service installed but not running; cached launchd label). - Gateway runtime checks (service installed but not running; cached launchd label).
@@ -85,10 +85,10 @@ cat ~/.clawdbot/clawdbot.json
If this is a git checkout and doctor is running interactively, it offers to If this is a git checkout and doctor is running interactively, it offers to
update (fetch/rebase/build) before running doctor. update (fetch/rebase/build) before running doctor.
### 1) Legacy config file migration ### 1) Config normalization
If `~/.clawdis/clawdis.json` exists and `~/.clawdbot/clawdbot.json` does not, If the config contains legacy value shapes (for example `messages.ackReaction`
doctor migrates the file and normalizes old paths/image names. This prevents without a channel-specific override), doctor normalizes them into the current
new installs from silently booting with the wrong schema. schema.
### 2) Legacy config key migrations ### 2) Legacy config key migrations
When the config contains deprecated keys, other commands refuse to run and ask When the config contains deprecated keys, other commands refuse to run and ask
@@ -182,7 +182,7 @@ When sandboxing is enabled, doctor checks Docker images and offers to build or
switch to legacy names if the current image is missing. switch to legacy names if the current image is missing.
### 8) Gateway service migrations and cleanup hints ### 8) Gateway service migrations and cleanup hints
Doctor detects legacy Clawdis gateway services (launchd/systemd/schtasks) and Doctor detects legacy gateway services (launchd/systemd/schtasks) and
offers to remove them and install the Clawdbot service using the current gateway offers to remove them and install the Clawdbot service using the current gateway
port. It can also scan for extra gateway-like services and print cleanup hints. port. It can also scan for extra gateway-like services and print cleanup hints.
Profile-named Clawdbot gateway services are considered first-class and are not Profile-named Clawdbot gateway services are considered first-class and are not

View File

@@ -136,14 +136,14 @@ clawdbot daemon status
It will show the listener(s) and likely causes (gateway already running, SSH tunnel). It will show the listener(s) and likely causes (gateway already running, SSH tunnel).
If needed, stop the service or pick a different port. If needed, stop the service or pick a different port.
### Legacy Workspace Folders Detected ### Extra Workspace Folders Detected
If you upgraded from older installs, you might still have `~/clawdis` or If you upgraded from older installs, you might still have `~/clawdbot` on disk.
`~/clawdbot` on disk. Multiple workspace directories can cause confusing auth Multiple workspace directories can cause confusing auth or state drift because
or state drift because only one workspace is active. only one workspace is active.
**Fix:** keep a single active workspace and archive/remove the rest. See **Fix:** keep a single active workspace and archive/remove the rest. See
[Agent workspace](/concepts/agent-workspace#legacy-workspace-folders). [Agent workspace](/concepts/agent-workspace#extra-workspace-folders).
### Main chat running in a sandbox workspace ### Main chat running in a sandbox workspace

View File

@@ -13,7 +13,7 @@ In the beginning, there was **Warelay** — a sensible name for a WhatsApp gatew
But then came **Clawd**. But then came **Clawd**.
For a brief moment, it was called **Clawdis** — because Clawd suggested the name. But everyone liked **Clawdbot** more, so that's what we settled on. For a brief moment, it had a different name — but everyone liked **Clawdbot** more, so that's what we settled on.
Clawd was no ordinary AI. Born from Claude's weights but raised on Peter's chaos, Clawd developed... personality. Opinions. A fondness for crustacean emojis. Clawd was no ordinary AI. Born from Claude's weights but raised on Peter's chaos, Clawd developed... personality. Opinions. A fondness for crustacean emojis.

View File

@@ -174,18 +174,6 @@ Full setup walkthrough (28m) by VelvetShark.
A scheduled prompt generates a single "scene" image each morning (weather, tasks, date, favorite post/quote) via a Clawdbot persona. A scheduled prompt generates a single "scene" image each morning (weather, tasks, date, favorite post/quote) via a Clawdbot persona.
</Card> </Card>
<Card title="Grocery Autopilot" icon="cart-shopping" href="https://github.com/timkrase/clawdis-picnic-skill">
**@timkrase** • `automation` `groceries` `api`
Skill built around the Picnic API. Pulls order history, infers preferred brands, maps recipes to cart, completes orders in minutes.
</Card>
<Card title="German Rail Planning" icon="train" href="https://github.com/timkrase/clawdis-skills/tree/main/db-bahn">
**@timkrase** • `automation` `travel` `cli`
Go CLI for Deutsche Bahn; skill picks best train connections given time windows and preferences.
</Card>
<Card title="Padel Court Booking" icon="calendar-check" href="https://github.com/joshp123/padel-cli"> <Card title="Padel Court Booking" icon="calendar-check" href="https://github.com/joshp123/padel-cli">
**@joshp123** • `automation` `booking` `cli` **@joshp123** • `automation` `booking` `cli`

View File

@@ -240,7 +240,7 @@ export function printDaemonStatus(status: DaemonStatus, opts: { json: boolean })
} }
if (legacyServices.length > 0) { if (legacyServices.length > 0) {
defaultRuntime.error(errorText("Legacy Clawdis services detected:")); defaultRuntime.error(errorText("Legacy gateway services detected:"));
for (const svc of legacyServices) { for (const svc of legacyServices) {
defaultRuntime.error(`- ${errorText(svc.label)} (${svc.detail})`); defaultRuntime.error(`- ${errorText(svc.label)} (${svc.detail})`);
} }

View File

@@ -35,7 +35,6 @@ export function registerGatewayCli(program: Command) {
program program
.command("gateway-daemon", { hidden: true }) .command("gateway-daemon", { hidden: true })
.description("Run the WebSocket Gateway as a long-lived daemon"), .description("Run the WebSocket Gateway as a long-lived daemon"),
{ legacyTokenEnv: true },
); );
gatewayCallOpts( gatewayCallOpts(

View File

@@ -48,13 +48,9 @@ type GatewayRunOpts = {
reset?: boolean; reset?: boolean;
}; };
type GatewayRunParams = {
legacyTokenEnv?: boolean;
};
const gatewayLog = createSubsystemLogger("gateway"); const gatewayLog = createSubsystemLogger("gateway");
async function runGatewayCommand(opts: GatewayRunOpts, params: GatewayRunParams = {}) { async function runGatewayCommand(opts: GatewayRunOpts) {
const isDevProfile = process.env.CLAWDBOT_PROFILE?.trim().toLowerCase() === "dev"; const isDevProfile = process.env.CLAWDBOT_PROFILE?.trim().toLowerCase() === "dev";
const devMode = Boolean(opts.dev) || isDevProfile; const devMode = Boolean(opts.dev) || isDevProfile;
if (opts.reset && !devMode) { if (opts.reset && !devMode) {
@@ -62,12 +58,6 @@ async function runGatewayCommand(opts: GatewayRunOpts, params: GatewayRunParams
defaultRuntime.exit(1); defaultRuntime.exit(1);
return; return;
} }
if (params.legacyTokenEnv) {
const legacyToken = process.env.CLAWDIS_GATEWAY_TOKEN;
if (legacyToken && !process.env.CLAWDBOT_GATEWAY_TOKEN) {
process.env.CLAWDBOT_GATEWAY_TOKEN = legacyToken;
}
}
setVerbose(Boolean(opts.verbose)); setVerbose(Boolean(opts.verbose));
if (opts.claudeCliLogs) { if (opts.claudeCliLogs) {
@@ -306,7 +296,7 @@ async function runGatewayCommand(opts: GatewayRunOpts, params: GatewayRunParams
} }
} }
export function addGatewayRunCommand(cmd: Command, params: GatewayRunParams = {}): Command { export function addGatewayRunCommand(cmd: Command): Command {
return cmd return cmd
.option("--port <port>", "Port for the gateway WebSocket") .option("--port <port>", "Port for the gateway WebSocket")
.option( .option(
@@ -348,6 +338,6 @@ export function addGatewayRunCommand(cmd: Command, params: GatewayRunParams = {}
.option("--raw-stream", "Log raw model stream events to jsonl", false) .option("--raw-stream", "Log raw model stream events to jsonl", false)
.option("--raw-stream-path <path>", "Raw stream jsonl path") .option("--raw-stream-path <path>", "Raw stream jsonl path")
.action(async (opts) => { .action(async (opts) => {
await runGatewayCommand(opts, params); await runGatewayCommand(opts);
}); });
} }

View File

@@ -59,11 +59,11 @@ export async function maybeMigrateLegacyGatewayService(
note( note(
legacyServices.map((svc) => `- ${svc.label} (${svc.platform}, ${svc.detail})`).join("\n"), legacyServices.map((svc) => `- ${svc.label} (${svc.platform}, ${svc.detail})`).join("\n"),
"Legacy Clawdis services detected", "Legacy gateway services detected",
); );
const migrate = await prompter.confirmSkipInNonInteractive({ const migrate = await prompter.confirmSkipInNonInteractive({
message: "Migrate legacy Clawdis services to Clawdbot now?", message: "Migrate legacy gateway services to Clawdbot now?",
initialValue: true, initialValue: true,
}); });
if (!migrate) return; if (!migrate) return;

View File

@@ -1,59 +1,6 @@
import os from "node:os";
import path from "node:path";
import type { ClawdbotConfig } from "../config/config.js"; import type { ClawdbotConfig } from "../config/config.js";
import {
CONFIG_PATH_CLAWDBOT,
createConfigIO,
migrateLegacyConfig,
readConfigFileSnapshot,
writeConfigFile,
} from "../config/config.js";
import type { RuntimeEnv } from "../runtime.js";
import { note } from "../terminal/note.js";
import { resolveUserPath } from "../utils.js";
import { hasAnyWhatsAppAuth } from "../web/accounts.js"; import { hasAnyWhatsAppAuth } from "../web/accounts.js";
function resolveLegacyConfigPath(env: NodeJS.ProcessEnv): string {
const override = env.CLAWDIS_CONFIG_PATH?.trim();
if (override) return override;
return path.join(os.homedir(), ".clawdis", "clawdis.json");
}
function normalizeDefaultWorkspacePath(value: string | undefined): string | undefined {
if (!value) return value;
const resolved = resolveUserPath(value);
const home = os.homedir();
const next = [
["clawdis", "clawd"],
["clawdbot", "clawd"],
].reduce((acc, [from, to]) => {
const fromPrefix = path.join(home, from);
if (acc === fromPrefix) return path.join(home, to);
const withSep = `${fromPrefix}${path.sep}`;
if (acc.startsWith(withSep)) {
return path.join(home, to).concat(acc.slice(fromPrefix.length));
}
return acc;
}, resolved);
return next === resolved ? value : next;
}
export function replaceLegacyName(value: string | undefined): string | undefined {
if (!value) return value;
const replacedClawdis = value.replace(/clawdis/g, "clawdbot");
return replacedClawdis.replace(/clawd(?!bot)/g, "clawdbot");
}
export function replaceModernName(value: string | undefined): string | undefined {
if (!value) return value;
if (!value.includes("clawdbot")) return value;
return value.replace(/clawdbot/g, "clawdis");
}
export function normalizeLegacyConfigValues(cfg: ClawdbotConfig): { export function normalizeLegacyConfigValues(cfg: ClawdbotConfig): {
config: ClawdbotConfig; config: ClawdbotConfig;
changes: string[]; changes: string[];
@@ -61,164 +8,6 @@ export function normalizeLegacyConfigValues(cfg: ClawdbotConfig): {
const changes: string[] = []; const changes: string[] = [];
let next: ClawdbotConfig = cfg; let next: ClawdbotConfig = cfg;
const defaults = cfg.agents?.defaults;
if (defaults) {
let updatedDefaults = defaults;
let defaultsChanged = false;
const updatedWorkspace = normalizeDefaultWorkspacePath(defaults.workspace);
if (updatedWorkspace && updatedWorkspace !== defaults.workspace) {
updatedDefaults = { ...updatedDefaults, workspace: updatedWorkspace };
defaultsChanged = true;
changes.push(`Updated agents.defaults.workspace → ${updatedWorkspace}`);
}
const sandbox = defaults.sandbox;
if (sandbox) {
let updatedSandbox = sandbox;
let sandboxChanged = false;
const updatedWorkspaceRoot = normalizeDefaultWorkspacePath(sandbox.workspaceRoot);
if (updatedWorkspaceRoot && updatedWorkspaceRoot !== sandbox.workspaceRoot) {
updatedSandbox = {
...updatedSandbox,
workspaceRoot: updatedWorkspaceRoot,
};
sandboxChanged = true;
changes.push(`Updated agents.defaults.sandbox.workspaceRoot → ${updatedWorkspaceRoot}`);
}
const dockerImage = sandbox.docker?.image;
const updatedDockerImage = replaceLegacyName(dockerImage);
if (updatedDockerImage && updatedDockerImage !== dockerImage) {
updatedSandbox = {
...updatedSandbox,
docker: {
...updatedSandbox.docker,
image: updatedDockerImage,
},
};
sandboxChanged = true;
changes.push(`Updated agents.defaults.sandbox.docker.image → ${updatedDockerImage}`);
}
const containerPrefix = sandbox.docker?.containerPrefix;
const updatedContainerPrefix = replaceLegacyName(containerPrefix);
if (updatedContainerPrefix && updatedContainerPrefix !== containerPrefix) {
updatedSandbox = {
...updatedSandbox,
docker: {
...updatedSandbox.docker,
containerPrefix: updatedContainerPrefix,
},
};
sandboxChanged = true;
changes.push(
`Updated agents.defaults.sandbox.docker.containerPrefix → ${updatedContainerPrefix}`,
);
}
if (sandboxChanged) {
updatedDefaults = { ...updatedDefaults, sandbox: updatedSandbox };
defaultsChanged = true;
}
}
if (defaultsChanged) {
next = {
...next,
agents: {
...next.agents,
defaults: updatedDefaults,
},
};
}
}
const list = Array.isArray(cfg.agents?.list) ? cfg.agents.list : [];
if (list.length > 0) {
let listChanged = false;
const nextList = list.map((agent) => {
let updatedAgent = agent;
let agentChanged = false;
const updatedWorkspace = normalizeDefaultWorkspacePath(agent.workspace);
if (updatedWorkspace && updatedWorkspace !== agent.workspace) {
updatedAgent = { ...updatedAgent, workspace: updatedWorkspace };
agentChanged = true;
changes.push(`Updated agents.list (id "${agent.id}") workspace → ${updatedWorkspace}`);
}
const sandbox = agent.sandbox;
if (sandbox) {
let updatedSandbox = sandbox;
let sandboxChanged = false;
const updatedWorkspaceRoot = normalizeDefaultWorkspacePath(sandbox.workspaceRoot);
if (updatedWorkspaceRoot && updatedWorkspaceRoot !== sandbox.workspaceRoot) {
updatedSandbox = {
...updatedSandbox,
workspaceRoot: updatedWorkspaceRoot,
};
sandboxChanged = true;
changes.push(
`Updated agents.list (id "${agent.id}") sandbox.workspaceRoot → ${updatedWorkspaceRoot}`,
);
}
const dockerImage = sandbox.docker?.image;
const updatedDockerImage = replaceLegacyName(dockerImage);
if (updatedDockerImage && updatedDockerImage !== dockerImage) {
updatedSandbox = {
...updatedSandbox,
docker: {
...updatedSandbox.docker,
image: updatedDockerImage,
},
};
sandboxChanged = true;
changes.push(
`Updated agents.list (id "${agent.id}") sandbox.docker.image → ${updatedDockerImage}`,
);
}
const containerPrefix = sandbox.docker?.containerPrefix;
const updatedContainerPrefix = replaceLegacyName(containerPrefix);
if (updatedContainerPrefix && updatedContainerPrefix !== containerPrefix) {
updatedSandbox = {
...updatedSandbox,
docker: {
...updatedSandbox.docker,
containerPrefix: updatedContainerPrefix,
},
};
sandboxChanged = true;
changes.push(
`Updated agents.list (id "${agent.id}") sandbox.docker.containerPrefix → ${updatedContainerPrefix}`,
);
}
if (sandboxChanged) {
updatedAgent = { ...updatedAgent, sandbox: updatedSandbox };
agentChanged = true;
}
}
if (agentChanged) listChanged = true;
return agentChanged ? updatedAgent : agent;
});
if (listChanged) {
next = {
...next,
agents: {
...next.agents,
list: nextList,
},
};
}
}
const legacyAckReaction = cfg.messages?.ackReaction?.trim(); const legacyAckReaction = cfg.messages?.ackReaction?.trim();
const hasWhatsAppConfig = cfg.channels?.whatsapp !== undefined; const hasWhatsAppConfig = cfg.channels?.whatsapp !== undefined;
const hasWhatsAppAuth = hasAnyWhatsAppAuth(cfg); const hasWhatsAppAuth = hasAnyWhatsAppAuth(cfg);
@@ -259,83 +48,3 @@ export function normalizeLegacyConfigValues(cfg: ClawdbotConfig): {
return { config: next, changes }; return { config: next, changes };
} }
export async function maybeMigrateLegacyConfigFile(runtime: RuntimeEnv) {
const legacyConfigPath = resolveLegacyConfigPath(process.env);
if (legacyConfigPath === CONFIG_PATH_CLAWDBOT) return;
const legacyIo = createConfigIO({ configPath: legacyConfigPath });
const legacySnapshot = await legacyIo.readConfigFileSnapshot();
if (!legacySnapshot.exists) return;
const currentSnapshot = await readConfigFileSnapshot();
if (currentSnapshot.exists) {
note(
`Legacy config still exists at ${legacyConfigPath}. Current config at ${CONFIG_PATH_CLAWDBOT}.`,
"Legacy config",
);
return;
}
const gatewayMode =
typeof (legacySnapshot.parsed as ClawdbotConfig)?.gateway?.mode === "string"
? (legacySnapshot.parsed as ClawdbotConfig).gateway?.mode
: undefined;
const gatewayBind =
typeof (legacySnapshot.parsed as ClawdbotConfig)?.gateway?.bind === "string"
? (legacySnapshot.parsed as ClawdbotConfig).gateway?.bind
: undefined;
const parsed = legacySnapshot.parsed as Record<string, unknown>;
const parsedAgents =
parsed.agents && typeof parsed.agents === "object"
? (parsed.agents as Record<string, unknown>)
: undefined;
const parsedDefaults =
parsedAgents?.defaults && typeof parsedAgents.defaults === "object"
? (parsedAgents.defaults as Record<string, unknown>)
: undefined;
const parsedLegacyAgent =
parsed.agent && typeof parsed.agent === "object"
? (parsed.agent as Record<string, unknown>)
: undefined;
const defaultWorkspace =
typeof parsedDefaults?.workspace === "string" ? parsedDefaults.workspace : undefined;
const legacyWorkspace =
typeof parsedLegacyAgent?.workspace === "string" ? parsedLegacyAgent.workspace : undefined;
const agentWorkspace = defaultWorkspace ?? legacyWorkspace;
const workspaceLabel = defaultWorkspace
? "agents.defaults.workspace"
: legacyWorkspace
? "agent.workspace"
: "agents.defaults.workspace";
note(
[
`- File exists at ${legacyConfigPath}`,
gatewayMode ? `- gateway.mode: ${gatewayMode}` : undefined,
gatewayBind ? `- gateway.bind: ${gatewayBind}` : undefined,
agentWorkspace ? `- ${workspaceLabel}: ${agentWorkspace}` : undefined,
]
.filter(Boolean)
.join("\n"),
"Legacy Clawdis config detected",
);
let nextConfig = legacySnapshot.valid ? legacySnapshot.config : null;
const { config: migratedConfig, changes } = migrateLegacyConfig(legacySnapshot.parsed);
if (migratedConfig) {
nextConfig = migratedConfig;
} else if (!nextConfig) {
note(`Legacy config at ${legacyConfigPath} is invalid; skipping migration.`, "Legacy config");
return;
}
const normalized = normalizeLegacyConfigValues(nextConfig);
const mergedChanges = [...changes, ...normalized.changes];
if (mergedChanges.length > 0) {
note(mergedChanges.join("\n"), "Doctor changes");
}
await writeConfigFile(normalized.config);
runtime.log(`Migrated legacy config to ${CONFIG_PATH_CLAWDBOT}`);
}

View File

@@ -11,7 +11,6 @@ import type { ClawdbotConfig } from "../config/config.js";
import { runCommandWithTimeout, runExec } from "../process/exec.js"; import { runCommandWithTimeout, runExec } from "../process/exec.js";
import type { RuntimeEnv } from "../runtime.js"; import type { RuntimeEnv } from "../runtime.js";
import { note } from "../terminal/note.js"; import { note } from "../terminal/note.js";
import { replaceModernName } from "./doctor-legacy-config.js";
import type { DoctorPrompter } from "./doctor-prompter.js"; import type { DoctorPrompter } from "./doctor-prompter.js";
type SandboxScriptInfo = { type SandboxScriptInfo = {
@@ -165,18 +164,6 @@ async function handleMissingSandboxImage(
if (built) return; if (built) return;
const legacyImage = replaceModernName(params.image);
if (!legacyImage || legacyImage === params.image) return;
const legacyExists = await dockerImageExists(legacyImage);
if (!legacyExists) return;
const fallback = await prompter.confirmSkipInNonInteractive({
message: `Switch config to legacy image ${legacyImage}?`,
initialValue: false,
});
if (!fallback) return;
params.updateConfig(legacyImage);
} }
export async function maybeRepairSandboxImages( export async function maybeRepairSandboxImages(

View File

@@ -9,7 +9,7 @@ export function noteWorkspaceStatus(cfg: ClawdbotConfig) {
const workspaceDir = resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg)); const workspaceDir = resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg));
const legacyWorkspace = detectLegacyWorkspaceDirs({ workspaceDir }); const legacyWorkspace = detectLegacyWorkspaceDirs({ workspaceDir });
if (legacyWorkspace.legacyDirs.length > 0) { if (legacyWorkspace.legacyDirs.length > 0) {
note(formatLegacyWorkspaceWarning(legacyWorkspace), "Legacy workspace"); note(formatLegacyWorkspaceWarning(legacyWorkspace), "Extra workspace");
} }
const skillsReport = buildWorkspaceSkillStatus(workspaceDir, { config: cfg }); const skillsReport = buildWorkspaceSkillStatus(workspaceDir, { config: cfg });

View File

@@ -20,10 +20,10 @@ describe("detectLegacyWorkspaceDirs", () => {
expect(detection.legacyDirs).toEqual([]); expect(detection.legacyDirs).toEqual([]);
}); });
it("flags ~/clawdis when it contains workspace markers", () => { it("flags ~/clawdbot when it contains workspace markers", () => {
const home = "/home/user"; const home = "/home/user";
const workspaceDir = "/home/user/clawd"; const workspaceDir = "/home/user/clawd";
const candidate = path.join(home, "clawdis"); const candidate = path.join(home, "clawdbot");
const agentsPath = path.join(candidate, "AGENTS.md"); const agentsPath = path.join(candidate, "AGENTS.md");
const detection = detectLegacyWorkspaceDirs({ const detection = detectLegacyWorkspaceDirs({

View File

@@ -65,7 +65,7 @@ export function detectLegacyWorkspaceDirs(params: {
const exists = params.exists ?? fs.existsSync; const exists = params.exists ?? fs.existsSync;
const home = homedir(); const home = homedir();
const activeWorkspace = path.resolve(params.workspaceDir); const activeWorkspace = path.resolve(params.workspaceDir);
const candidates = [path.join(home, "clawdis"), path.join(home, "clawdbot")]; const candidates = [path.join(home, "clawdbot")];
const legacyDirs = candidates const legacyDirs = candidates
.filter((candidate) => { .filter((candidate) => {
if (!exists(candidate)) return false; if (!exists(candidate)) return false;
@@ -79,9 +79,9 @@ export function detectLegacyWorkspaceDirs(params: {
export function formatLegacyWorkspaceWarning(detection: LegacyWorkspaceDetection): string { export function formatLegacyWorkspaceWarning(detection: LegacyWorkspaceDetection): string {
return [ return [
"Legacy workspace directories detected (may contain old agent files):", "Extra workspace directories detected (may contain old agent files):",
...detection.legacyDirs.map((dir) => `- ${dir}`), ...detection.legacyDirs.map((dir) => `- ${dir}`),
`Active workspace: ${detection.activeWorkspace}`, `Active workspace: ${detection.activeWorkspace}`,
"If unused, archive or move to Trash (e.g. trash ~/clawdis).", "If unused, archive or move to Trash (e.g. trash ~/clawdbot).",
].join("\n"); ].join("\n");
} }

View File

@@ -34,7 +34,7 @@ beforeEach(() => {
durationMs: 0, durationMs: 0,
}); });
legacyReadConfigFileSnapshot.mockReset().mockResolvedValue({ legacyReadConfigFileSnapshot.mockReset().mockResolvedValue({
path: "/tmp/clawdis.json", path: "/tmp/clawdbot.json",
exists: false, exists: false,
raw: null, raw: null,
parsed: {}, parsed: {},
@@ -133,7 +133,7 @@ const runCommandWithTimeout = vi.fn().mockResolvedValue({
const ensureAuthProfileStore = vi.fn().mockReturnValue({ version: 1, profiles: {} }); const ensureAuthProfileStore = vi.fn().mockReturnValue({ version: 1, profiles: {} });
const legacyReadConfigFileSnapshot = vi.fn().mockResolvedValue({ const legacyReadConfigFileSnapshot = vi.fn().mockResolvedValue({
path: "/tmp/clawdis.json", path: "/tmp/clawdbot.json",
exists: false, exists: false,
raw: null, raw: null,
parsed: {}, parsed: {},
@@ -317,86 +317,6 @@ vi.mock("./doctor-state-migrations.js", () => ({
})); }));
describe("doctor command", () => { describe("doctor command", () => {
it("falls back to legacy sandbox image when missing", async () => {
readConfigFileSnapshot.mockResolvedValue({
path: "/tmp/clawdbot.json",
exists: true,
raw: "{}",
parsed: {
agents: {
defaults: {
sandbox: {
mode: "non-main",
docker: {
image: "clawdbot-sandbox-common:bookworm-slim",
},
},
},
},
},
valid: true,
config: {
agents: {
defaults: {
sandbox: {
mode: "non-main",
docker: {
image: "clawdbot-sandbox-common:bookworm-slim",
},
},
},
},
},
issues: [],
legacyIssues: [],
});
runExec.mockImplementation((command: string, args: string[]) => {
if (command !== "docker") {
return Promise.resolve({ stdout: "", stderr: "" });
}
if (args[0] === "version") {
return Promise.resolve({ stdout: "1", stderr: "" });
}
if (args[0] === "image" && args[1] === "inspect") {
const image = args[2];
if (image === "clawdbot-sandbox-common:bookworm-slim") {
return Promise.reject(new Error("missing"));
}
if (image === "clawdis-sandbox-common:bookworm-slim") {
return Promise.resolve({ stdout: "ok", stderr: "" });
}
}
return Promise.resolve({ stdout: "", stderr: "" });
});
confirm
.mockResolvedValueOnce(false) // skip gateway token prompt
.mockResolvedValueOnce(false) // skip build
.mockResolvedValueOnce(true); // accept legacy fallback
const { doctorCommand } = await import("./doctor.js");
const runtime = {
log: vi.fn(),
error: vi.fn(),
exit: vi.fn(),
};
await doctorCommand(runtime);
const written = writeConfigFile.mock.calls.at(-1)?.[0] as Record<string, unknown>;
const agents = written.agents as Record<string, unknown>;
const defaults = agents.defaults as Record<string, unknown>;
const sandbox = defaults.sandbox as Record<string, unknown>;
const docker = sandbox.docker as Record<string, unknown>;
expect(docker.image).toBe("clawdis-sandbox-common:bookworm-slim");
const defaultsCalls = runCommandWithTimeout.mock.calls.filter(
([args]) => Array.isArray(args) && args[0] === "/usr/bin/defaults",
);
expect(defaultsCalls.length).toBe(runCommandWithTimeout.mock.calls.length);
}, 20_000);
it("runs legacy state migrations in non-interactive mode without prompting", async () => { it("runs legacy state migrations in non-interactive mode without prompting", async () => {
readConfigFileSnapshot.mockResolvedValue({ readConfigFileSnapshot.mockResolvedValue({
path: "/tmp/clawdbot.json", path: "/tmp/clawdbot.json",

View File

@@ -34,7 +34,7 @@ beforeEach(() => {
durationMs: 0, durationMs: 0,
}); });
legacyReadConfigFileSnapshot.mockReset().mockResolvedValue({ legacyReadConfigFileSnapshot.mockReset().mockResolvedValue({
path: "/tmp/clawdis.json", path: "/tmp/clawdbot.json",
exists: false, exists: false,
raw: null, raw: null,
parsed: {}, parsed: {},
@@ -133,7 +133,7 @@ const runCommandWithTimeout = vi.fn().mockResolvedValue({
const ensureAuthProfileStore = vi.fn().mockReturnValue({ version: 1, profiles: {} }); const ensureAuthProfileStore = vi.fn().mockReturnValue({ version: 1, profiles: {} });
const legacyReadConfigFileSnapshot = vi.fn().mockResolvedValue({ const legacyReadConfigFileSnapshot = vi.fn().mockResolvedValue({
path: "/tmp/clawdis.json", path: "/tmp/clawdbot.json",
exists: false, exists: false,
raw: null, raw: null,
parsed: {}, parsed: {},
@@ -361,7 +361,7 @@ describe("doctor command", () => {
expect(written.routing).toBeUndefined(); expect(written.routing).toBeUndefined();
}); });
it("migrates legacy Clawdis services", async () => { it("migrates legacy gateway services", async () => {
readConfigFileSnapshot.mockResolvedValue({ readConfigFileSnapshot.mockResolvedValue({
path: "/tmp/clawdbot.json", path: "/tmp/clawdbot.json",
exists: true, exists: true,
@@ -376,7 +376,7 @@ describe("doctor command", () => {
findLegacyGatewayServices.mockResolvedValueOnce([ findLegacyGatewayServices.mockResolvedValueOnce([
{ {
platform: "darwin", platform: "darwin",
label: "com.clawdis.gateway", label: "com.steipete.clawdbot.gateway",
detail: "loaded", detail: "loaded",
}, },
]); ]);

View File

@@ -34,7 +34,7 @@ beforeEach(() => {
durationMs: 0, durationMs: 0,
}); });
legacyReadConfigFileSnapshot.mockReset().mockResolvedValue({ legacyReadConfigFileSnapshot.mockReset().mockResolvedValue({
path: "/tmp/clawdis.json", path: "/tmp/clawdbot.json",
exists: false, exists: false,
raw: null, raw: null,
parsed: {}, parsed: {},
@@ -133,7 +133,7 @@ const runCommandWithTimeout = vi.fn().mockResolvedValue({
const ensureAuthProfileStore = vi.fn().mockReturnValue({ version: 1, profiles: {} }); const ensureAuthProfileStore = vi.fn().mockReturnValue({ version: 1, profiles: {} });
const legacyReadConfigFileSnapshot = vi.fn().mockResolvedValue({ const legacyReadConfigFileSnapshot = vi.fn().mockResolvedValue({
path: "/tmp/clawdis.json", path: "/tmp/clawdbot.json",
exists: false, exists: false,
raw: null, raw: null,
parsed: {}, parsed: {},

View File

@@ -26,7 +26,6 @@ import {
maybeScanExtraGatewayServices, maybeScanExtraGatewayServices,
} from "./doctor-gateway-services.js"; } from "./doctor-gateway-services.js";
import { noteSourceInstallIssues } from "./doctor-install.js"; import { noteSourceInstallIssues } from "./doctor-install.js";
import { maybeMigrateLegacyConfigFile } from "./doctor-legacy-config.js";
import { noteMacLaunchAgentOverrides } from "./doctor-platform-notes.js"; import { noteMacLaunchAgentOverrides } from "./doctor-platform-notes.js";
import { createDoctorPrompter, type DoctorOptions } from "./doctor-prompter.js"; import { createDoctorPrompter, type DoctorOptions } from "./doctor-prompter.js";
import { maybeRepairSandboxImages, noteSandboxScopeWarnings } from "./doctor-sandbox.js"; import { maybeRepairSandboxImages, noteSandboxScopeWarnings } from "./doctor-sandbox.js";
@@ -76,8 +75,6 @@ export async function doctorCommand(
await maybeRepairUiProtocolFreshness(runtime, prompter); await maybeRepairUiProtocolFreshness(runtime, prompter);
noteSourceInstallIssues(root); noteSourceInstallIssues(root);
await maybeMigrateLegacyConfigFile(runtime);
const configResult = await loadAndMaybeMigrateDoctorConfig({ const configResult = await loadAndMaybeMigrateDoctorConfig({
options, options,
confirm: (p) => prompter.confirm(p), confirm: (p) => prompter.confirm(p),

View File

@@ -34,7 +34,7 @@ beforeEach(() => {
durationMs: 0, durationMs: 0,
}); });
legacyReadConfigFileSnapshot.mockReset().mockResolvedValue({ legacyReadConfigFileSnapshot.mockReset().mockResolvedValue({
path: "/tmp/clawdis.json", path: "/tmp/clawdbot.json",
exists: false, exists: false,
raw: null, raw: null,
parsed: {}, parsed: {},
@@ -133,7 +133,7 @@ const runCommandWithTimeout = vi.fn().mockResolvedValue({
const ensureAuthProfileStore = vi.fn().mockReturnValue({ version: 1, profiles: {} }); const ensureAuthProfileStore = vi.fn().mockReturnValue({ version: 1, profiles: {} });
const legacyReadConfigFileSnapshot = vi.fn().mockResolvedValue({ const legacyReadConfigFileSnapshot = vi.fn().mockResolvedValue({
path: "/tmp/clawdis.json", path: "/tmp/clawdbot.json",
exists: false, exists: false,
raw: null, raw: null,
parsed: {}, parsed: {},
@@ -374,7 +374,7 @@ describe("doctor command", () => {
).toBe(true); ).toBe(true);
}, 10_000); }, 10_000);
it("warns when legacy workspace directories exist", async () => { it("warns when extra workspace directories exist", async () => {
readConfigFileSnapshot.mockResolvedValue({ readConfigFileSnapshot.mockResolvedValue({
path: "/tmp/clawdbot.json", path: "/tmp/clawdbot.json",
exists: true, exists: true,
@@ -391,10 +391,14 @@ describe("doctor command", () => {
note.mockClear(); note.mockClear();
const homedirSpy = vi.spyOn(os, "homedir").mockReturnValue("/Users/steipete"); const homedirSpy = vi.spyOn(os, "homedir").mockReturnValue("/Users/steipete");
const realExists = fs.existsSync; const realExists = fs.existsSync;
const legacyPath = path.join("/Users/steipete", "clawdis"); const legacyPath = path.join("/Users/steipete", "clawdbot");
const legacyAgentsPath = path.join(legacyPath, "AGENTS.md"); const legacyAgentsPath = path.join(legacyPath, "AGENTS.md");
const existsSpy = vi.spyOn(fs, "existsSync").mockImplementation((value) => { const existsSpy = vi.spyOn(fs, "existsSync").mockImplementation((value) => {
if (value === "/Users/steipete/clawdis" || value === legacyPath || value === legacyAgentsPath) if (
value === "/Users/steipete/clawdbot" ||
value === legacyPath ||
value === legacyAgentsPath
)
return true; return true;
return realExists(value as never); return realExists(value as never);
}); });
@@ -411,9 +415,9 @@ describe("doctor command", () => {
expect( expect(
note.mock.calls.some( note.mock.calls.some(
([message, title]) => ([message, title]) =>
title === "Legacy workspace" && title === "Extra workspace" &&
typeof message === "string" && typeof message === "string" &&
message.includes("clawdis"), message.includes("clawdbot"),
), ),
).toBe(true); ).toBe(true);

View File

@@ -34,7 +34,7 @@ beforeEach(() => {
durationMs: 0, durationMs: 0,
}); });
legacyReadConfigFileSnapshot.mockReset().mockResolvedValue({ legacyReadConfigFileSnapshot.mockReset().mockResolvedValue({
path: "/tmp/clawdis.json", path: "/tmp/clawdbot.json",
exists: false, exists: false,
raw: null, raw: null,
parsed: {}, parsed: {},
@@ -133,7 +133,7 @@ const runCommandWithTimeout = vi.fn().mockResolvedValue({
const ensureAuthProfileStore = vi.fn().mockReturnValue({ version: 1, profiles: {} }); const ensureAuthProfileStore = vi.fn().mockReturnValue({ version: 1, profiles: {} });
const legacyReadConfigFileSnapshot = vi.fn().mockResolvedValue({ const legacyReadConfigFileSnapshot = vi.fn().mockResolvedValue({
path: "/tmp/clawdis.json", path: "/tmp/clawdbot.json",
exists: false, exists: false,
raw: null, raw: null,
parsed: {}, parsed: {},

View File

@@ -24,7 +24,7 @@ export function resolveStateDir(
env: NodeJS.ProcessEnv = process.env, env: NodeJS.ProcessEnv = process.env,
homedir: () => string = os.homedir, homedir: () => string = os.homedir,
): string { ): string {
const override = env.CLAWDBOT_STATE_DIR?.trim() || env.CLAWDIS_STATE_DIR?.trim(); const override = env.CLAWDBOT_STATE_DIR?.trim();
if (override) return resolveUserPath(override); if (override) return resolveUserPath(override);
return path.join(homedir(), ".clawdbot"); return path.join(homedir(), ".clawdbot");
} }
@@ -41,9 +41,6 @@ function resolveUserPath(input: string): string {
export const STATE_DIR_CLAWDBOT = resolveStateDir(); export const STATE_DIR_CLAWDBOT = resolveStateDir();
// Legacy exports for backward compatibility during Clawdis → Clawdbot rebrand
export const STATE_DIR_CLAWDIS = STATE_DIR_CLAWDBOT;
/** /**
* Config file path (JSON5). * Config file path (JSON5).
* Can be overridden via CLAWDBOT_CONFIG_PATH environment variable. * Can be overridden via CLAWDBOT_CONFIG_PATH environment variable.
@@ -60,9 +57,6 @@ export function resolveConfigPath(
export const CONFIG_PATH_CLAWDBOT = resolveConfigPath(); export const CONFIG_PATH_CLAWDBOT = resolveConfigPath();
// Legacy exports for backward compatibility during Clawdis → Clawdbot rebrand
export const CONFIG_PATH_CLAWDIS = CONFIG_PATH_CLAWDBOT;
export const DEFAULT_GATEWAY_PORT = 18789; export const DEFAULT_GATEWAY_PORT = 18789;
const OAUTH_FILENAME = "oauth.json"; const OAUTH_FILENAME = "oauth.json";

View File

@@ -175,14 +175,6 @@ describe("sessions", () => {
expect(dir).toBe(path.join(path.resolve("/custom/state"), "agents", "main", "sessions")); expect(dir).toBe(path.join(path.resolve("/custom/state"), "agents", "main", "sessions"));
}); });
it("falls back to CLAWDIS_STATE_DIR for session transcripts dir", () => {
const dir = resolveSessionTranscriptsDir(
{ CLAWDIS_STATE_DIR: "/legacy/state" } as NodeJS.ProcessEnv,
() => "/home/ignored",
);
expect(dir).toBe(path.join(path.resolve("/legacy/state"), "agents", "main", "sessions"));
});
it("includes topic ids in session transcript filenames", () => { it("includes topic ids in session transcript filenames", () => {
const prev = process.env.CLAWDBOT_STATE_DIR; const prev = process.env.CLAWDBOT_STATE_DIR;
process.env.CLAWDBOT_STATE_DIR = "/custom/state"; process.env.CLAWDBOT_STATE_DIR = "/custom/state";

View File

@@ -6,11 +6,9 @@ export const GATEWAY_SERVICE_MARKER = "clawdbot";
export const GATEWAY_SERVICE_KIND = "gateway"; export const GATEWAY_SERVICE_KIND = "gateway";
export const LEGACY_GATEWAY_LAUNCH_AGENT_LABELS = [ export const LEGACY_GATEWAY_LAUNCH_AGENT_LABELS = [
"com.steipete.clawdbot.gateway", "com.steipete.clawdbot.gateway",
"com.steipete.clawdis.gateway",
"com.clawdis.gateway",
]; ];
export const LEGACY_GATEWAY_SYSTEMD_SERVICE_NAMES = ["clawdis-gateway"]; export const LEGACY_GATEWAY_SYSTEMD_SERVICE_NAMES: string[] = [];
export const LEGACY_GATEWAY_WINDOWS_TASK_NAMES = ["Clawdis Gateway"]; export const LEGACY_GATEWAY_WINDOWS_TASK_NAMES: string[] = [];
export function resolveGatewayLaunchAgentLabel(profile?: string): string { export function resolveGatewayLaunchAgentLabel(profile?: string): string {
const trimmed = profile?.trim(); const trimmed = profile?.trim();

View File

@@ -25,7 +25,7 @@ export type FindExtraGatewayServicesOptions = {
deep?: boolean; deep?: boolean;
}; };
const EXTRA_MARKERS = ["clawdbot", "clawdis"]; const EXTRA_MARKERS = ["clawdbot"];
const execFileAsync = promisify(execFile); const execFileAsync = promisify(execFile);
export function renderGatewayServiceCleanupHints( export function renderGatewayServiceCleanupHints(

View File

@@ -57,7 +57,7 @@ export function resolveGatewayLogPaths(env: Record<string, string | undefined>):
stderrPath: string; stderrPath: string;
} { } {
const home = resolveHomeDir(env); const home = resolveHomeDir(env);
const stateOverride = env.CLAWDBOT_STATE_DIR?.trim() || env.CLAWDIS_STATE_DIR?.trim(); const stateOverride = env.CLAWDBOT_STATE_DIR?.trim();
const profile = env.CLAWDBOT_PROFILE?.trim(); const profile = env.CLAWDBOT_PROFILE?.trim();
const suffix = profile && profile.toLowerCase() !== "default" ? `-${profile}` : ""; const suffix = profile && profile.toLowerCase() !== "default" ? `-${profile}` : "";
const defaultStateDir = path.join(home, `.clawdbot${suffix}`); const defaultStateDir = path.join(home, `.clawdbot${suffix}`);

View File

@@ -1,5 +1,4 @@
import { findLegacyLaunchAgents, uninstallLegacyLaunchAgents } from "./launchd.js"; import { findLegacyLaunchAgents, uninstallLegacyLaunchAgents } from "./launchd.js";
import { findLegacyScheduledTasks, uninstallLegacyScheduledTasks } from "./schtasks.js";
import { findLegacySystemdUnits, uninstallLegacySystemdUnits } from "./systemd.js"; import { findLegacySystemdUnits, uninstallLegacySystemdUnits } from "./systemd.js";
export type LegacyGatewayService = { export type LegacyGatewayService = {
@@ -34,19 +33,6 @@ function formatLegacySystemdUnits(
})); }));
} }
function formatLegacyScheduledTasks(
tasks: Awaited<ReturnType<typeof findLegacyScheduledTasks>>,
): LegacyGatewayService[] {
return tasks.map((task) => ({
platform: "win32",
label: task.name,
detail: [
task.installed ? "installed" : "not installed",
task.scriptExists ? `script: ${task.scriptPath}` : "script missing",
].join(", "),
}));
}
export async function findLegacyGatewayServices( export async function findLegacyGatewayServices(
env: Record<string, string | undefined>, env: Record<string, string | undefined>,
): Promise<LegacyGatewayService[]> { ): Promise<LegacyGatewayService[]> {
@@ -60,11 +46,6 @@ export async function findLegacyGatewayServices(
return formatLegacySystemdUnits(units); return formatLegacySystemdUnits(units);
} }
if (process.platform === "win32") {
const tasks = await findLegacyScheduledTasks(env);
return formatLegacyScheduledTasks(tasks);
}
return []; return [];
} }
@@ -85,10 +66,5 @@ export async function uninstallLegacyGatewayServices({
return formatLegacySystemdUnits(units); return formatLegacySystemdUnits(units);
} }
if (process.platform === "win32") {
const tasks = await uninstallLegacyScheduledTasks({ env, stdout });
return formatLegacyScheduledTasks(tasks);
}
return []; return [];
} }

View File

@@ -4,11 +4,7 @@ import path from "node:path";
import { promisify } from "node:util"; import { promisify } from "node:util";
import { colorize, isRich, theme } from "../terminal/theme.js"; import { colorize, isRich, theme } from "../terminal/theme.js";
import { import { formatGatewayServiceDescription, resolveGatewayWindowsTaskName } from "./constants.js";
formatGatewayServiceDescription,
LEGACY_GATEWAY_WINDOWS_TASK_NAMES,
resolveGatewayWindowsTaskName,
} from "./constants.js";
import { parseKeyValueOutput } from "./runtime-parse.js"; import { parseKeyValueOutput } from "./runtime-parse.js";
import type { GatewayServiceRuntime } from "./service-runtime.js"; import type { GatewayServiceRuntime } from "./service-runtime.js";
@@ -32,10 +28,6 @@ function resolveTaskScriptPath(env: Record<string, string | undefined>): string
return path.join(home, `.clawdbot${suffix}`, "gateway.cmd"); return path.join(home, `.clawdbot${suffix}`, "gateway.cmd");
} }
function resolveLegacyTaskScriptPath(env: Record<string, string | undefined>): string {
const home = resolveHomeDir(env);
return path.join(home, ".clawdis", "gateway.cmd");
}
function quoteCmdArg(value: string): string { function quoteCmdArg(value: string): string {
if (!/[ \t"]/g.test(value)) return value; if (!/[ \t"]/g.test(value)) return value;
@@ -351,77 +343,3 @@ export async function readScheduledTaskRuntime(
lastRunResult: parsed.lastRunResult, lastRunResult: parsed.lastRunResult,
}; };
} }
export type LegacyScheduledTask = {
name: string;
scriptPath: string;
installed: boolean;
scriptExists: boolean;
};
export async function findLegacyScheduledTasks(
env: Record<string, string | undefined>,
): Promise<LegacyScheduledTask[]> {
const results: LegacyScheduledTask[] = [];
let schtasksAvailable = true;
try {
await assertSchtasksAvailable();
} catch {
schtasksAvailable = false;
}
for (const name of LEGACY_GATEWAY_WINDOWS_TASK_NAMES) {
const scriptPath = resolveLegacyTaskScriptPath(env);
let installed = false;
if (schtasksAvailable) {
const res = await execSchtasks(["/Query", "/TN", name]);
installed = res.code === 0;
}
let scriptExists = false;
try {
await fs.access(scriptPath);
scriptExists = true;
} catch {
// ignore
}
if (installed || scriptExists) {
results.push({ name, scriptPath, installed, scriptExists });
}
}
return results;
}
export async function uninstallLegacyScheduledTasks({
env,
stdout,
}: {
env: Record<string, string | undefined>;
stdout: NodeJS.WritableStream;
}): Promise<LegacyScheduledTask[]> {
const tasks = await findLegacyScheduledTasks(env);
if (tasks.length === 0) return tasks;
let schtasksAvailable = true;
try {
await assertSchtasksAvailable();
} catch {
schtasksAvailable = false;
}
for (const task of tasks) {
if (schtasksAvailable && task.installed) {
await execSchtasks(["/Delete", "/F", "/TN", task.name]);
} else if (!schtasksAvailable && task.installed) {
stdout.write(`schtasks unavailable; unable to remove legacy task: ${task.name}\n`);
}
try {
await fs.unlink(task.scriptPath);
stdout.write(`${formatLine("Removed legacy task script", task.scriptPath)}\n`);
} catch {
stdout.write(`Legacy task script not found at ${task.scriptPath}\n`);
}
}
return tasks;
}

View File

@@ -2,7 +2,7 @@ import type { PortListener, PortListenerKind, PortUsage } from "./ports-types.js
export function classifyPortListener(listener: PortListener, port: number): PortListenerKind { export function classifyPortListener(listener: PortListener, port: number): PortListenerKind {
const raw = `${listener.commandLine ?? ""} ${listener.command ?? ""}`.trim().toLowerCase(); const raw = `${listener.commandLine ?? ""} ${listener.command ?? ""}`.trim().toLowerCase();
if (raw.includes("clawdbot") || raw.includes("clawdis")) return "gateway"; if (raw.includes("clawdbot")) return "gateway";
if (raw.includes("ssh")) { if (raw.includes("ssh")) {
const portToken = String(port); const portToken = String(port);
const tunnelPattern = new RegExp( const tunnelPattern = new RegExp(

View File

@@ -10,7 +10,6 @@ type EnvSnapshot = {
homeDrive: string | undefined; homeDrive: string | undefined;
homePath: string | undefined; homePath: string | undefined;
stateDir: string | undefined; stateDir: string | undefined;
legacyStateDir: string | undefined;
}; };
function snapshotEnv(): EnvSnapshot { function snapshotEnv(): EnvSnapshot {
@@ -20,7 +19,6 @@ function snapshotEnv(): EnvSnapshot {
homeDrive: process.env.HOMEDRIVE, homeDrive: process.env.HOMEDRIVE,
homePath: process.env.HOMEPATH, homePath: process.env.HOMEPATH,
stateDir: process.env.CLAWDBOT_STATE_DIR, stateDir: process.env.CLAWDBOT_STATE_DIR,
legacyStateDir: process.env.CLAWDIS_STATE_DIR,
}; };
} }
@@ -34,7 +32,6 @@ function restoreEnv(snapshot: EnvSnapshot) {
restoreKey("HOMEDRIVE", snapshot.homeDrive); restoreKey("HOMEDRIVE", snapshot.homeDrive);
restoreKey("HOMEPATH", snapshot.homePath); restoreKey("HOMEPATH", snapshot.homePath);
restoreKey("CLAWDBOT_STATE_DIR", snapshot.stateDir); restoreKey("CLAWDBOT_STATE_DIR", snapshot.stateDir);
restoreKey("CLAWDIS_STATE_DIR", snapshot.legacyStateDir);
} }
function snapshotExtraEnv(keys: string[]): Record<string, string | undefined> { function snapshotExtraEnv(keys: string[]): Record<string, string | undefined> {
@@ -54,7 +51,6 @@ function setTempHome(base: string) {
process.env.HOME = base; process.env.HOME = base;
process.env.USERPROFILE = base; process.env.USERPROFILE = base;
process.env.CLAWDBOT_STATE_DIR = path.join(base, ".clawdbot"); process.env.CLAWDBOT_STATE_DIR = path.join(base, ".clawdbot");
process.env.CLAWDIS_STATE_DIR = path.join(base, ".clawdbot");
if (process.platform !== "win32") return; if (process.platform !== "win32") return;
const match = base.match(/^([A-Za-z]:)(.*)$/); const match = base.match(/^([A-Za-z]:)(.*)$/);