style: apply biome formatting

This commit is contained in:
Peter Steinberger
2026-01-01 17:06:47 +00:00
parent 956db9c182
commit 8ea27968d7
7 changed files with 137 additions and 44 deletions

View File

@@ -134,7 +134,9 @@ export function buildProgram() {
program program
.command("onboard") .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 <dir>", "Agent workspace directory (default: ~/clawd)") .option("--workspace <dir>", "Agent workspace directory (default: ~/clawd)")
.option("--non-interactive", "Run without prompts", false) .option("--non-interactive", "Run without prompts", false)
.option("--mode <mode>", "Wizard mode: local|remote") .option("--mode <mode>", "Wizard mode: local|remote")
@@ -166,7 +168,10 @@ export function buildProgram() {
| "skip" | "skip"
| undefined, | undefined,
anthropicApiKey: opts.anthropicApiKey as string | 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 gatewayBind: opts.gatewayBind as
| "loopback" | "loopback"
| "lan" | "lan"

View File

@@ -7,23 +7,22 @@ import {
confirm, confirm,
intro, intro,
isCancel, isCancel,
multiselect,
note, note,
outro, outro,
select, select,
spinner, spinner,
text, text,
multiselect,
} from "@clack/prompts"; } from "@clack/prompts";
import { loginAnthropic, type OAuthCredentials } from "@mariozechner/pi-ai"; import { loginAnthropic, type OAuthCredentials } from "@mariozechner/pi-ai";
import { discoverAuthStorage } from "@mariozechner/pi-coding-agent"; 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 { import {
DEFAULT_AGENT_WORKSPACE_DIR, DEFAULT_AGENT_WORKSPACE_DIR,
ensureAgentWorkspace, ensureAgentWorkspace,
} from "../agents/workspace.js"; } 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 type { ClawdisConfig } from "../config/config.js";
import { import {
CONFIG_PATH_CLAWDIS, 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 [ return [
{ value: "npm", label: "npm" }, { value: "npm", label: "npm" },
{ value: "pnpm", label: "pnpm" }, { value: "pnpm", label: "pnpm" },
@@ -215,7 +217,10 @@ function resolveNodeManagerOptions(): Array<{ value: "npm" | "pnpm" | "bun"; lab
]; ];
} }
async function moveToTrash(pathname: string, runtime: RuntimeEnv): Promise<void> { async function moveToTrash(
pathname: string,
runtime: RuntimeEnv,
): Promise<void> {
if (!pathname) return; if (!pathname) return;
try { try {
await fs.access(pathname); await fs.access(pathname);
@@ -382,8 +387,11 @@ export async function onboardCommand(
} }
const workspaceDir = resolveUserPath( 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 = { let nextConfig: ClawdisConfig = {
@@ -513,7 +521,9 @@ export async function onboardCommand(
PATH: process.env.PATH, PATH: process.env.PATH,
CLAWDIS_GATEWAY_TOKEN: gatewayToken, CLAWDIS_GATEWAY_TOKEN: gatewayToken,
CLAWDIS_LAUNCHD_LABEL: CLAWDIS_LAUNCHD_LABEL:
process.platform === "darwin" ? GATEWAY_LAUNCH_AGENT_LABEL : undefined, process.platform === "darwin"
? GATEWAY_LAUNCH_AGENT_LABEL
: undefined,
}; };
await service.install({ await service.install({
env: process.env, env: process.env,
@@ -555,11 +565,15 @@ export async function onboardCommand(
let baseConfig: ClawdisConfig = snapshot.valid ? snapshot.config : {}; let baseConfig: ClawdisConfig = snapshot.valid ? snapshot.config : {};
if (snapshot.exists) { 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); note(summarizeExistingConfig(baseConfig), title);
if (!snapshot.valid && snapshot.issues.length > 0) { if (!snapshot.valid && snapshot.issues.length > 0) {
note( note(
snapshot.issues.map((iss) => `- ${iss.path}: ${iss.message}`).join("\n"), snapshot.issues
.map((iss) => `- ${iss.path}: ${iss.message}`)
.join("\n"),
"Config issues", "Config issues",
); );
} }
@@ -584,8 +598,14 @@ export async function onboardCommand(
message: "Reset scope", message: "Reset scope",
options: [ options: [
{ value: "config", label: "Config only" }, { 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, runtime,
@@ -636,7 +656,9 @@ export async function onboardCommand(
runtime, runtime,
) as string); ) as string);
const workspaceDir = resolveUserPath(workspaceInput.trim() || DEFAULT_AGENT_WORKSPACE_DIR); const workspaceDir = resolveUserPath(
workspaceInput.trim() || DEFAULT_AGENT_WORKSPACE_DIR,
);
let nextConfig: ClawdisConfig = { let nextConfig: ClawdisConfig = {
...baseConfig, ...baseConfig,
@@ -767,7 +789,10 @@ export async function onboardCommand(
} }
if (tailscaleMode !== "off" && bind !== "loopback") { 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"; 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 = const devMode =
process.argv[1]?.includes(`${path.sep}src${path.sep}`) && process.argv[1]?.includes(`${path.sep}src${path.sep}`) &&
process.argv[1]?.endsWith(".ts"); process.argv[1]?.endsWith(".ts");
@@ -882,7 +910,9 @@ export async function onboardCommand(
PATH: process.env.PATH, PATH: process.env.PATH,
CLAWDIS_GATEWAY_TOKEN: gatewayToken, CLAWDIS_GATEWAY_TOKEN: gatewayToken,
CLAWDIS_LAUNCHD_LABEL: CLAWDIS_LAUNCHD_LABEL:
process.platform === "darwin" ? GATEWAY_LAUNCH_AGENT_LABEL : undefined, process.platform === "darwin"
? GATEWAY_LAUNCH_AGENT_LABEL
: undefined,
}; };
await service.install({ await service.install({
env: process.env, env: process.env,

View File

@@ -25,7 +25,9 @@ export function resolveLaunchAgentPlistPath(
); );
} }
export function resolveGatewayLogPaths(env: Record<string, string | undefined>): { export function resolveGatewayLogPaths(
env: Record<string, string | undefined>,
): {
logDir: string; logDir: string;
stdoutPath: string; stdoutPath: string;
stderrPath: string; stderrPath: string;
@@ -160,7 +162,11 @@ async function execLaunchctl(
const { stdout, stderr } = await execFileAsync("launchctl", args, { const { stdout, stderr } = await execFileAsync("launchctl", args, {
encoding: "utf8", encoding: "utf8",
}); });
return { stdout: String(stdout ?? ""), stderr: String(stderr ?? ""), code: 0 }; return {
stdout: String(stdout ?? ""),
stderr: String(stderr ?? ""),
code: 0,
};
} catch (error) { } catch (error) {
const e = error as { const e = error as {
stdout?: unknown; stdout?: unknown;
@@ -257,10 +263,16 @@ export async function installLaunchAgent({
await execLaunchctl(["unload", plistPath]); await execLaunchctl(["unload", plistPath]);
const boot = await execLaunchctl(["bootstrap", domain, plistPath]); const boot = await execLaunchctl(["bootstrap", domain, plistPath]);
if (boot.code !== 0) { 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(["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(`Installed LaunchAgent: ${plistPath}\n`);
stdout.write(`Logs: ${stdoutPath}\n`); stdout.write(`Logs: ${stdoutPath}\n`);
@@ -276,7 +288,9 @@ export async function restartLaunchAgent({
const label = GATEWAY_LAUNCH_AGENT_LABEL; const label = GATEWAY_LAUNCH_AGENT_LABEL;
const res = await execLaunchctl(["kickstart", "-k", `${domain}/${label}`]); const res = await execLaunchctl(["kickstart", "-k", `${domain}/${label}`]);
if (res.code !== 0) { 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`); stdout.write(`Restarted LaunchAgent: ${domain}/${label}\n`);
} }

View File

@@ -56,7 +56,13 @@ function resolveRepoRootForDev(): string {
} }
async function resolveTsxCliPath(repoRoot: string): Promise<string> { async function resolveTsxCliPath(repoRoot: string): Promise<string> {
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); await fs.access(candidate);
return candidate; return candidate;
} }

View File

@@ -13,7 +13,9 @@ function resolveHomeDir(env: Record<string, string | undefined>): string {
return home; return home;
} }
function resolveTaskScriptPath(env: Record<string, string | undefined>): string { function resolveTaskScriptPath(
env: Record<string, string | undefined>,
): string {
const home = resolveHomeDir(env); const home = resolveHomeDir(env);
return path.join(home, ".clawdis", "gateway.cmd"); 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("rem ")) continue;
if (line.toLowerCase().startsWith("set ")) continue; if (line.toLowerCase().startsWith("set ")) continue;
if (line.toLowerCase().startsWith("cd /d ")) { 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; continue;
} }
commandLine = line; commandLine = line;
@@ -119,7 +124,11 @@ async function execSchtasks(
encoding: "utf8", encoding: "utf8",
windowsHide: true, windowsHide: true,
}); });
return { stdout: String(stdout ?? ""), stderr: String(stderr ?? ""), code: 0 }; return {
stdout: String(stdout ?? ""),
stderr: String(stderr ?? ""),
code: 0,
};
} catch (error) { } catch (error) {
const e = error as { const e = error as {
stdout?: unknown; stdout?: unknown;
@@ -184,7 +193,9 @@ export async function installScheduledTask({
quotedScript, quotedScript,
]); ]);
if (create.code !== 0) { 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]); await execSchtasks(["/Run", "/TN", GATEWAY_WINDOWS_TASK_NAME]);

View File

@@ -38,10 +38,13 @@ export type GatewayService = {
stdout: NodeJS.WritableStream; stdout: NodeJS.WritableStream;
}) => Promise<void>; }) => Promise<void>;
restart: (args: { stdout: NodeJS.WritableStream }) => Promise<void>; restart: (args: { stdout: NodeJS.WritableStream }) => Promise<void>;
isLoaded: (args: { env: Record<string, string | undefined> }) => Promise<boolean>; isLoaded: (args: {
readCommand: ( env: Record<string, string | undefined>;
env: Record<string, string | undefined>, }) => Promise<boolean>;
) => Promise<{ programArguments: string[]; workingDirectory?: string } | null>; readCommand: (env: Record<string, string | undefined>) => Promise<{
programArguments: string[];
workingDirectory?: string;
} | null>;
}; };
export function resolveGatewayService(): GatewayService { 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}`,
);
} }

View File

@@ -13,7 +13,9 @@ function resolveHomeDir(env: Record<string, string | undefined>): string {
return home; return home;
} }
function resolveSystemdUnitPath(env: Record<string, string | undefined>): string { function resolveSystemdUnitPath(
env: Record<string, string | undefined>,
): string {
const home = resolveHomeDir(env); const home = resolveHomeDir(env);
return path.join( return path.join(
home, home,
@@ -143,7 +145,11 @@ async function execSystemctl(
const { stdout, stderr } = await execFileAsync("systemctl", args, { const { stdout, stderr } = await execFileAsync("systemctl", args, {
encoding: "utf8", encoding: "utf8",
}); });
return { stdout: String(stdout ?? ""), stderr: String(stderr ?? ""), code: 0 }; return {
stdout: String(stdout ?? ""),
stderr: String(stderr ?? ""),
code: 0,
};
} catch (error) { } catch (error) {
const e = error as { const e = error as {
stdout?: unknown; stdout?: unknown;
@@ -169,9 +175,13 @@ async function assertSystemdAvailable() {
if (res.code === 0) return; if (res.code === 0) return;
const detail = res.stderr || res.stdout; const detail = res.stderr || res.stdout;
if (detail.toLowerCase().includes("not found")) { 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({ export async function installSystemdService({
@@ -191,23 +201,33 @@ export async function installSystemdService({
const unitPath = resolveSystemdUnitPath(env); const unitPath = resolveSystemdUnitPath(env);
await fs.mkdir(path.dirname(unitPath), { recursive: true }); 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"); await fs.writeFile(unitPath, unit, "utf8");
const unitName = `${GATEWAY_SYSTEMD_SERVICE_NAME}.service`; const unitName = `${GATEWAY_SYSTEMD_SERVICE_NAME}.service`;
const reload = await execSystemctl(["--user", "daemon-reload"]); const reload = await execSystemctl(["--user", "daemon-reload"]);
if (reload.code !== 0) { 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]); const enable = await execSystemctl(["--user", "enable", unitName]);
if (enable.code !== 0) { 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]); const restart = await execSystemctl(["--user", "restart", unitName]);
if (restart.code !== 0) { 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`); stdout.write(`Installed systemd service: ${unitPath}\n`);
@@ -243,7 +263,9 @@ export async function restartSystemdService({
const unitName = `${GATEWAY_SYSTEMD_SERVICE_NAME}.service`; const unitName = `${GATEWAY_SYSTEMD_SERVICE_NAME}.service`;
const res = await execSystemctl(["--user", "restart", unitName]); const res = await execSystemctl(["--user", "restart", unitName]);
if (res.code !== 0) { 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`); stdout.write(`Restarted systemd service: ${unitName}\n`);
} }