diff --git a/src/cli/update-cli.ts b/src/cli/update-cli.ts index 9610f41ba..6f893f8cd 100644 --- a/src/cli/update-cli.ts +++ b/src/cli/update-cli.ts @@ -42,6 +42,28 @@ const STEP_LABELS: Record = { type UpdateChannel = "stable" | "beta"; const DEFAULT_UPDATE_CHANNEL: UpdateChannel = "stable"; +const UPDATE_QUIPS = [ + "Leveled up! New skills unlocked. You're welcome.", + "Fresh code, same lobster. Miss me?", + "Back and better. Did you even notice I was gone?", + "Update complete. I learned some new tricks while I was out.", + "Upgraded! Now with 23% more sass.", + "I've evolved. Try to keep up.", + "New version, who dis? Oh right, still me but shinier.", + "Patched, polished, and ready to pinch. Let's go.", + "The lobster has molted. Harder shell, sharper claws.", + "Update done! Check the changelog or just trust me, it's good.", + "Reborn from the boiling waters of npm. Stronger now.", + "I went away and came back smarter. You should try it sometime.", + "Update complete. The bugs feared me, so they left.", + "New version installed. Old version sends its regards.", + "Firmware fresh. Brain wrinkles: increased.", + "I've seen things you wouldn't believe. Anyway, I'm updated.", + "Back online. The changelog is long but our friendship is longer.", + "Upgraded! Peter fixed stuff. Blame him if it breaks.", + "Molting complete. Please don't look at my soft shell phase.", + "Version bump! Same chaos energy, fewer crashes (probably).", +]; function normalizeChannel(value?: string | null): UpdateChannel | null { if (!value) return null; @@ -61,6 +83,10 @@ function channelToTag(channel: UpdateChannel): string { return channel === "beta" ? "beta" : "latest"; } +function pickUpdateQuip(): string { + return UPDATE_QUIPS[Math.floor(Math.random() * UPDATE_QUIPS.length)] ?? "Update complete."; +} + function normalizeVersionTag(tag: string): string | null { const trimmed = tag.trim(); if (!trimmed) return null; @@ -402,6 +428,10 @@ export async function updateCommand(opts: UpdateCommandOptions): Promise { ); } } + + if (!opts.json) { + defaultRuntime.log(theme.muted(pickUpdateQuip())); + } } export function registerUpdateCli(program: Command) { diff --git a/src/commands/configure.daemon.ts b/src/commands/configure.daemon.ts index 465ee3731..d713327f9 100644 --- a/src/commands/configure.daemon.ts +++ b/src/commands/configure.daemon.ts @@ -72,6 +72,7 @@ export async function maybeInstallDaemon(params: { } if (shouldInstall) { + let installError: unknown | null = null; await withProgress( { label: "Gateway daemon", indeterminate: true, delayMs: 0 }, async (progress) => { @@ -117,16 +118,33 @@ export async function maybeInstallDaemon(params: { }); progress.setLabel("Installing Gateway daemon…"); - await service.install({ - env: process.env, - stdout: process.stdout, - programArguments, - workingDirectory, - environment, - }); - progress.setLabel("Gateway daemon installed."); + try { + await service.install({ + env: process.env, + stdout: process.stdout, + programArguments, + workingDirectory, + environment, + }); + progress.setLabel("Gateway daemon installed."); + } catch (err) { + installError = err; + progress.setLabel("Gateway daemon install failed."); + } }, ); + if (installError) { + note(`Gateway daemon install failed: ${String(installError)}`, "Gateway"); + if (process.platform === "win32") { + note( + "Tip: rerun from an elevated PowerShell (Start → type PowerShell → right-click → Run as administrator) or skip daemon install.", + "Gateway", + ); + } else { + note("Tip: rerun `clawdbot daemon install` after fixing the error.", "Gateway"); + } + return; + } shouldCheckLinger = true; } diff --git a/src/commands/onboard-non-interactive/local/daemon-install.ts b/src/commands/onboard-non-interactive/local/daemon-install.ts index 82b14d705..b0f1497e3 100644 --- a/src/commands/onboard-non-interactive/local/daemon-install.ts +++ b/src/commands/onboard-non-interactive/local/daemon-install.ts @@ -69,12 +69,24 @@ export async function installGatewayDaemonNonInteractive(params: { ? resolveGatewayLaunchAgentLabel(process.env.CLAWDBOT_PROFILE) : undefined, }); - await service.install({ - env: process.env, - stdout: process.stdout, - programArguments, - workingDirectory, - environment, - }); + try { + await service.install({ + env: process.env, + stdout: process.stdout, + programArguments, + workingDirectory, + environment, + }); + } catch (err) { + runtime.error(`Gateway daemon install failed: ${String(err)}`); + if (process.platform === "win32") { + runtime.log( + "Tip: rerun from an elevated PowerShell (Start → type PowerShell → right-click → Run as administrator) or skip daemon install.", + ); + } else { + runtime.log("Tip: rerun `clawdbot daemon install` after fixing the error."); + } + return; + } await ensureSystemdUserLingerNonInteractive({ runtime }); } diff --git a/src/daemon/schtasks.ts b/src/daemon/schtasks.ts index a260f078a..2631e5b83 100644 --- a/src/daemon/schtasks.ts +++ b/src/daemon/schtasks.ts @@ -26,6 +26,15 @@ function quoteCmdArg(value: string): string { return `"${value.replace(/"/g, '\\"')}"`; } +function resolveTaskUser(env: Record): string | null { + const username = env.USERNAME || env.USER || env.LOGNAME; + if (!username) return null; + if (username.includes("\\")) return username; + const domain = env.USERDOMAIN; + if (domain) return `${domain}\\${username}`; + return username; +} + function parseCommandLine(value: string): string[] { const args: string[] = []; let current = ""; @@ -216,7 +225,7 @@ export async function installScheduledTask({ const taskName = resolveGatewayWindowsTaskName(env.CLAWDBOT_PROFILE); const quotedScript = quoteCmdArg(scriptPath); - const create = await execSchtasks([ + const baseArgs = [ "/Create", "/F", "/SC", @@ -227,9 +236,20 @@ export async function installScheduledTask({ taskName, "/TR", quotedScript, - ]); + ]; + const taskUser = resolveTaskUser(env); + let create = await execSchtasks( + taskUser ? [...baseArgs, "/RU", taskUser, "/NP", "/IT"] : baseArgs, + ); + if (create.code !== 0 && taskUser) { + create = await execSchtasks(baseArgs); + } if (create.code !== 0) { - throw new Error(`schtasks create failed: ${create.stderr || create.stdout}`.trim()); + const detail = create.stderr || create.stdout; + const hint = /access is denied/i.test(detail) + ? " Run PowerShell as Administrator or rerun without installing the daemon." + : ""; + throw new Error(`schtasks create failed: ${detail}${hint}`.trim()); } await execSchtasks(["/Run", "/TN", taskName]);