diff --git a/src/cli/program.ts b/src/cli/program.ts index e74851b73..cb67d9421 100644 --- a/src/cli/program.ts +++ b/src/cli/program.ts @@ -134,7 +134,9 @@ export function buildProgram() { program .command("onboard") - .description("Interactive wizard to set up the gateway, workspace, and skills") + .description( + "Interactive wizard to set up the gateway, workspace, and skills", + ) .option("--workspace ", "Agent workspace directory (default: ~/clawd)") .option("--non-interactive", "Run without prompts", false) .option("--mode ", "Wizard mode: local|remote") @@ -166,7 +168,10 @@ export function buildProgram() { | "skip" | undefined, anthropicApiKey: opts.anthropicApiKey as string | undefined, - gatewayPort: Number.parseInt(String(opts.gatewayPort ?? "18789"), 10), + gatewayPort: Number.parseInt( + String(opts.gatewayPort ?? "18789"), + 10, + ), gatewayBind: opts.gatewayBind as | "loopback" | "lan" diff --git a/src/commands/onboard.ts b/src/commands/onboard.ts index 8dcc6ffad..aa07284d2 100644 --- a/src/commands/onboard.ts +++ b/src/commands/onboard.ts @@ -7,23 +7,22 @@ import { confirm, intro, isCancel, + multiselect, note, outro, select, spinner, text, - multiselect, } from "@clack/prompts"; import { loginAnthropic, type OAuthCredentials } from "@mariozechner/pi-ai"; import { discoverAuthStorage } from "@mariozechner/pi-coding-agent"; - +import { resolveClawdisAgentDir } from "../agents/agent-paths.js"; +import { installSkill } from "../agents/skills-install.js"; +import { buildWorkspaceSkillStatus } from "../agents/skills-status.js"; import { DEFAULT_AGENT_WORKSPACE_DIR, ensureAgentWorkspace, } from "../agents/workspace.js"; -import { resolveClawdisAgentDir } from "../agents/agent-paths.js"; -import { installSkill } from "../agents/skills-install.js"; -import { buildWorkspaceSkillStatus } from "../agents/skills-status.js"; import type { ClawdisConfig } from "../config/config.js"; import { CONFIG_PATH_CLAWDIS, @@ -207,7 +206,10 @@ function upsertSkillEntry( }; } -function resolveNodeManagerOptions(): Array<{ value: "npm" | "pnpm" | "bun"; label: string }> { +function resolveNodeManagerOptions(): Array<{ + value: "npm" | "pnpm" | "bun"; + label: string; +}> { return [ { value: "npm", label: "npm" }, { value: "pnpm", label: "pnpm" }, @@ -215,7 +217,10 @@ function resolveNodeManagerOptions(): Array<{ value: "npm" | "pnpm" | "bun"; lab ]; } -async function moveToTrash(pathname: string, runtime: RuntimeEnv): Promise { +async function moveToTrash( + pathname: string, + runtime: RuntimeEnv, +): Promise { if (!pathname) return; try { await fs.access(pathname); @@ -382,8 +387,11 @@ export async function onboardCommand( } const workspaceDir = resolveUserPath( - (opts.workspace ?? baseConfig.agent?.workspace ?? DEFAULT_AGENT_WORKSPACE_DIR) - .trim(), + ( + opts.workspace ?? + baseConfig.agent?.workspace ?? + DEFAULT_AGENT_WORKSPACE_DIR + ).trim(), ); let nextConfig: ClawdisConfig = { @@ -513,7 +521,9 @@ export async function onboardCommand( PATH: process.env.PATH, CLAWDIS_GATEWAY_TOKEN: gatewayToken, CLAWDIS_LAUNCHD_LABEL: - process.platform === "darwin" ? GATEWAY_LAUNCH_AGENT_LABEL : undefined, + process.platform === "darwin" + ? GATEWAY_LAUNCH_AGENT_LABEL + : undefined, }; await service.install({ env: process.env, @@ -555,11 +565,15 @@ export async function onboardCommand( let baseConfig: ClawdisConfig = snapshot.valid ? snapshot.config : {}; if (snapshot.exists) { - const title = snapshot.valid ? "Existing config detected" : "Invalid config"; + const title = snapshot.valid + ? "Existing config detected" + : "Invalid config"; note(summarizeExistingConfig(baseConfig), title); if (!snapshot.valid && snapshot.issues.length > 0) { note( - snapshot.issues.map((iss) => `- ${iss.path}: ${iss.message}`).join("\n"), + snapshot.issues + .map((iss) => `- ${iss.path}: ${iss.message}`) + .join("\n"), "Config issues", ); } @@ -584,8 +598,14 @@ export async function onboardCommand( message: "Reset scope", options: [ { value: "config", label: "Config only" }, - { value: "config+creds+sessions", label: "Config + creds + sessions" }, - { value: "full", label: "Full reset (config + creds + sessions + workspace)" }, + { + value: "config+creds+sessions", + label: "Config + creds + sessions", + }, + { + value: "full", + label: "Full reset (config + creds + sessions + workspace)", + }, ], }), runtime, @@ -636,7 +656,9 @@ export async function onboardCommand( runtime, ) as string); - const workspaceDir = resolveUserPath(workspaceInput.trim() || DEFAULT_AGENT_WORKSPACE_DIR); + const workspaceDir = resolveUserPath( + workspaceInput.trim() || DEFAULT_AGENT_WORKSPACE_DIR, + ); let nextConfig: ClawdisConfig = { ...baseConfig, @@ -767,7 +789,10 @@ export async function onboardCommand( } if (tailscaleMode !== "off" && bind !== "loopback") { - note("Tailscale requires bind=loopback. Adjusting bind to loopback.", "Note"); + note( + "Tailscale requires bind=loopback. Adjusting bind to loopback.", + "Note", + ); bind = "loopback"; } @@ -872,7 +897,10 @@ export async function onboardCommand( } } - if (!loaded || (loaded && (await service.isLoaded({ env: process.env })) === false)) { + if ( + !loaded || + (loaded && (await service.isLoaded({ env: process.env })) === false) + ) { const devMode = process.argv[1]?.includes(`${path.sep}src${path.sep}`) && process.argv[1]?.endsWith(".ts"); @@ -882,7 +910,9 @@ export async function onboardCommand( PATH: process.env.PATH, CLAWDIS_GATEWAY_TOKEN: gatewayToken, CLAWDIS_LAUNCHD_LABEL: - process.platform === "darwin" ? GATEWAY_LAUNCH_AGENT_LABEL : undefined, + process.platform === "darwin" + ? GATEWAY_LAUNCH_AGENT_LABEL + : undefined, }; await service.install({ env: process.env, diff --git a/src/daemon/launchd.ts b/src/daemon/launchd.ts index 173dde899..257fa7310 100644 --- a/src/daemon/launchd.ts +++ b/src/daemon/launchd.ts @@ -25,7 +25,9 @@ export function resolveLaunchAgentPlistPath( ); } -export function resolveGatewayLogPaths(env: Record): { +export function resolveGatewayLogPaths( + env: Record, +): { logDir: string; stdoutPath: string; stderrPath: string; @@ -160,7 +162,11 @@ async function execLaunchctl( const { stdout, stderr } = await execFileAsync("launchctl", args, { encoding: "utf8", }); - return { stdout: String(stdout ?? ""), stderr: String(stderr ?? ""), code: 0 }; + return { + stdout: String(stdout ?? ""), + stderr: String(stderr ?? ""), + code: 0, + }; } catch (error) { const e = error as { stdout?: unknown; @@ -257,10 +263,16 @@ export async function installLaunchAgent({ await execLaunchctl(["unload", plistPath]); const boot = await execLaunchctl(["bootstrap", domain, plistPath]); if (boot.code !== 0) { - throw new Error(`launchctl bootstrap failed: ${boot.stderr || boot.stdout}`.trim()); + throw new Error( + `launchctl bootstrap failed: ${boot.stderr || boot.stdout}`.trim(), + ); } await execLaunchctl(["enable", `${domain}/${GATEWAY_LAUNCH_AGENT_LABEL}`]); - await execLaunchctl(["kickstart", "-k", `${domain}/${GATEWAY_LAUNCH_AGENT_LABEL}`]); + await execLaunchctl([ + "kickstart", + "-k", + `${domain}/${GATEWAY_LAUNCH_AGENT_LABEL}`, + ]); stdout.write(`Installed LaunchAgent: ${plistPath}\n`); stdout.write(`Logs: ${stdoutPath}\n`); @@ -276,7 +288,9 @@ export async function restartLaunchAgent({ const label = GATEWAY_LAUNCH_AGENT_LABEL; const res = await execLaunchctl(["kickstart", "-k", `${domain}/${label}`]); if (res.code !== 0) { - throw new Error(`launchctl kickstart failed: ${res.stderr || res.stdout}`.trim()); + throw new Error( + `launchctl kickstart failed: ${res.stderr || res.stdout}`.trim(), + ); } stdout.write(`Restarted LaunchAgent: ${domain}/${label}\n`); } diff --git a/src/daemon/program-args.ts b/src/daemon/program-args.ts index 875601ab1..8e1fcb785 100644 --- a/src/daemon/program-args.ts +++ b/src/daemon/program-args.ts @@ -56,7 +56,13 @@ function resolveRepoRootForDev(): string { } async function resolveTsxCliPath(repoRoot: string): Promise { - const candidate = path.join(repoRoot, "node_modules", "tsx", "dist", "cli.mjs"); + const candidate = path.join( + repoRoot, + "node_modules", + "tsx", + "dist", + "cli.mjs", + ); await fs.access(candidate); return candidate; } diff --git a/src/daemon/schtasks.ts b/src/daemon/schtasks.ts index a19454dbc..5e1e5da56 100644 --- a/src/daemon/schtasks.ts +++ b/src/daemon/schtasks.ts @@ -13,7 +13,9 @@ function resolveHomeDir(env: Record): string { return home; } -function resolveTaskScriptPath(env: Record): string { +function resolveTaskScriptPath( + env: Record, +): string { const home = resolveHomeDir(env); return path.join(home, ".clawdis", "gateway.cmd"); } @@ -71,7 +73,10 @@ export async function readScheduledTaskCommand( if (line.toLowerCase().startsWith("rem ")) continue; if (line.toLowerCase().startsWith("set ")) continue; if (line.toLowerCase().startsWith("cd /d ")) { - workingDirectory = line.slice("cd /d ".length).trim().replace(/^"|"$/g, ""); + workingDirectory = line + .slice("cd /d ".length) + .trim() + .replace(/^"|"$/g, ""); continue; } commandLine = line; @@ -119,7 +124,11 @@ async function execSchtasks( encoding: "utf8", windowsHide: true, }); - return { stdout: String(stdout ?? ""), stderr: String(stderr ?? ""), code: 0 }; + return { + stdout: String(stdout ?? ""), + stderr: String(stderr ?? ""), + code: 0, + }; } catch (error) { const e = error as { stdout?: unknown; @@ -184,7 +193,9 @@ export async function installScheduledTask({ quotedScript, ]); if (create.code !== 0) { - throw new Error(`schtasks create failed: ${create.stderr || create.stdout}`.trim()); + throw new Error( + `schtasks create failed: ${create.stderr || create.stdout}`.trim(), + ); } await execSchtasks(["/Run", "/TN", GATEWAY_WINDOWS_TASK_NAME]); diff --git a/src/daemon/service.ts b/src/daemon/service.ts index 2ca0eb567..0ce53469e 100644 --- a/src/daemon/service.ts +++ b/src/daemon/service.ts @@ -38,10 +38,13 @@ export type GatewayService = { stdout: NodeJS.WritableStream; }) => Promise; restart: (args: { stdout: NodeJS.WritableStream }) => Promise; - isLoaded: (args: { env: Record }) => Promise; - readCommand: ( - env: Record, - ) => Promise<{ programArguments: string[]; workingDirectory?: string } | null>; + isLoaded: (args: { + env: Record; + }) => Promise; + readCommand: (env: Record) => Promise<{ + programArguments: string[]; + workingDirectory?: string; + } | null>; }; export function resolveGatewayService(): GatewayService { @@ -102,5 +105,7 @@ export function resolveGatewayService(): GatewayService { }; } - throw new Error(`Gateway service install not supported on ${process.platform}`); + throw new Error( + `Gateway service install not supported on ${process.platform}`, + ); } diff --git a/src/daemon/systemd.ts b/src/daemon/systemd.ts index e70871f02..d3fb59cee 100644 --- a/src/daemon/systemd.ts +++ b/src/daemon/systemd.ts @@ -13,7 +13,9 @@ function resolveHomeDir(env: Record): string { return home; } -function resolveSystemdUnitPath(env: Record): string { +function resolveSystemdUnitPath( + env: Record, +): string { const home = resolveHomeDir(env); return path.join( home, @@ -143,7 +145,11 @@ async function execSystemctl( const { stdout, stderr } = await execFileAsync("systemctl", args, { encoding: "utf8", }); - return { stdout: String(stdout ?? ""), stderr: String(stderr ?? ""), code: 0 }; + return { + stdout: String(stdout ?? ""), + stderr: String(stderr ?? ""), + code: 0, + }; } catch (error) { const e = error as { stdout?: unknown; @@ -169,9 +175,13 @@ async function assertSystemdAvailable() { if (res.code === 0) return; const detail = res.stderr || res.stdout; if (detail.toLowerCase().includes("not found")) { - throw new Error("systemctl not available; systemd user services are required on Linux."); + throw new Error( + "systemctl not available; systemd user services are required on Linux.", + ); } - throw new Error(`systemctl --user unavailable: ${detail || "unknown error"}`.trim()); + throw new Error( + `systemctl --user unavailable: ${detail || "unknown error"}`.trim(), + ); } export async function installSystemdService({ @@ -191,23 +201,33 @@ export async function installSystemdService({ const unitPath = resolveSystemdUnitPath(env); await fs.mkdir(path.dirname(unitPath), { recursive: true }); - const unit = buildSystemdUnit({ programArguments, workingDirectory, environment }); + const unit = buildSystemdUnit({ + programArguments, + workingDirectory, + environment, + }); await fs.writeFile(unitPath, unit, "utf8"); const unitName = `${GATEWAY_SYSTEMD_SERVICE_NAME}.service`; const reload = await execSystemctl(["--user", "daemon-reload"]); if (reload.code !== 0) { - throw new Error(`systemctl daemon-reload failed: ${reload.stderr || reload.stdout}`.trim()); + throw new Error( + `systemctl daemon-reload failed: ${reload.stderr || reload.stdout}`.trim(), + ); } const enable = await execSystemctl(["--user", "enable", unitName]); if (enable.code !== 0) { - throw new Error(`systemctl enable failed: ${enable.stderr || enable.stdout}`.trim()); + throw new Error( + `systemctl enable failed: ${enable.stderr || enable.stdout}`.trim(), + ); } const restart = await execSystemctl(["--user", "restart", unitName]); if (restart.code !== 0) { - throw new Error(`systemctl restart failed: ${restart.stderr || restart.stdout}`.trim()); + throw new Error( + `systemctl restart failed: ${restart.stderr || restart.stdout}`.trim(), + ); } stdout.write(`Installed systemd service: ${unitPath}\n`); @@ -243,7 +263,9 @@ export async function restartSystemdService({ const unitName = `${GATEWAY_SYSTEMD_SERVICE_NAME}.service`; const res = await execSystemctl(["--user", "restart", unitName]); if (res.code !== 0) { - throw new Error(`systemctl restart failed: ${res.stderr || res.stdout}`.trim()); + throw new Error( + `systemctl restart failed: ${res.stderr || res.stdout}`.trim(), + ); } stdout.write(`Restarted systemd service: ${unitName}\n`); }