diff --git a/src/commands/configure.daemon.ts b/src/commands/configure.daemon.ts index c05a60d30..a06e533c6 100644 --- a/src/commands/configure.daemon.ts +++ b/src/commands/configure.daemon.ts @@ -8,6 +8,7 @@ import { } from "../daemon/runtime-paths.js"; import { resolveGatewayService } from "../daemon/service.js"; import { buildServiceEnvironment } from "../daemon/service-env.js"; +import { withProgress } from "../cli/progress.js"; import type { RuntimeEnv } from "../runtime.js"; import { note } from "../terminal/note.js"; import { confirm, select } from "./configure.shared.js"; @@ -43,63 +44,89 @@ export async function maybeInstallDaemon(params: { params.runtime, ); if (action === "restart") { - await service.restart({ - profile: process.env.CLAWDBOT_PROFILE, - stdout: process.stdout, - }); + await withProgress( + { label: "Gateway daemon", indeterminate: true, delayMs: 0 }, + async (progress) => { + progress.setLabel("Restarting Gateway daemon…"); + await service.restart({ + profile: process.env.CLAWDBOT_PROFILE, + stdout: process.stdout, + }); + progress.setLabel("Gateway daemon restarted."); + }, + ); shouldCheckLinger = true; shouldInstall = false; } if (action === "skip") return; if (action === "reinstall") { - await service.uninstall({ env: process.env, stdout: process.stdout }); + await withProgress( + { label: "Gateway daemon", indeterminate: true, delayMs: 0 }, + async (progress) => { + progress.setLabel("Uninstalling Gateway daemon…"); + await service.uninstall({ env: process.env, stdout: process.stdout }); + progress.setLabel("Gateway daemon uninstalled."); + }, + ); } } if (shouldInstall) { - if (!params.daemonRuntime) { - daemonRuntime = guardCancel( - await select({ - message: "Gateway daemon runtime", - options: GATEWAY_DAEMON_RUNTIME_OPTIONS, - initialValue: DEFAULT_GATEWAY_DAEMON_RUNTIME, - }), - params.runtime, - ) as GatewayDaemonRuntime; - } - const devMode = - process.argv[1]?.includes(`${path.sep}src${path.sep}`) && process.argv[1]?.endsWith(".ts"); - const nodePath = await resolvePreferredNodePath({ - env: process.env, - runtime: daemonRuntime, - }); - const { programArguments, workingDirectory } = await resolveGatewayProgramArguments({ - port: params.port, - dev: devMode, - runtime: daemonRuntime, - nodePath, - }); - if (daemonRuntime === "node") { - const systemNode = await resolveSystemNodeInfo({ env: process.env }); - const warning = renderSystemNodeWarning(systemNode, programArguments[0]); - if (warning) note(warning, "Gateway runtime"); - } - const environment = buildServiceEnvironment({ - env: process.env, - port: params.port, - token: params.gatewayToken, - launchdLabel: - process.platform === "darwin" - ? resolveGatewayLaunchAgentLabel(process.env.CLAWDBOT_PROFILE) - : undefined, - }); - await service.install({ - env: process.env, - stdout: process.stdout, - programArguments, - workingDirectory, - environment, - }); + await withProgress( + { label: "Gateway daemon", indeterminate: true, delayMs: 0 }, + async (progress) => { + if (!params.daemonRuntime) { + daemonRuntime = guardCancel( + await select({ + message: "Gateway daemon runtime", + options: GATEWAY_DAEMON_RUNTIME_OPTIONS, + initialValue: DEFAULT_GATEWAY_DAEMON_RUNTIME, + }), + params.runtime, + ) as GatewayDaemonRuntime; + } + + progress.setLabel("Preparing Gateway daemon…"); + + const devMode = + process.argv[1]?.includes(`${path.sep}src${path.sep}`) && + process.argv[1]?.endsWith(".ts"); + const nodePath = await resolvePreferredNodePath({ + env: process.env, + runtime: daemonRuntime, + }); + const { programArguments, workingDirectory } = await resolveGatewayProgramArguments({ + port: params.port, + dev: devMode, + runtime: daemonRuntime, + nodePath, + }); + if (daemonRuntime === "node") { + const systemNode = await resolveSystemNodeInfo({ env: process.env }); + const warning = renderSystemNodeWarning(systemNode, programArguments[0]); + if (warning) note(warning, "Gateway runtime"); + } + const environment = buildServiceEnvironment({ + env: process.env, + port: params.port, + token: params.gatewayToken, + launchdLabel: + process.platform === "darwin" + ? resolveGatewayLaunchAgentLabel(process.env.CLAWDBOT_PROFILE) + : undefined, + }); + + progress.setLabel("Installing Gateway daemon…"); + await service.install({ + env: process.env, + stdout: process.stdout, + programArguments, + workingDirectory, + environment, + }); + progress.setLabel("Gateway daemon installed."); + }, + ); shouldCheckLinger = true; } diff --git a/src/commands/configure.wizard.ts b/src/commands/configure.wizard.ts index 2a38ba691..c09a6d8a8 100644 --- a/src/commands/configure.wizard.ts +++ b/src/commands/configure.wizard.ts @@ -98,9 +98,18 @@ async function promptWebToolsConfig( const existingFetch = nextConfig.tools?.web?.fetch; const hasSearchKey = Boolean(existingSearch?.apiKey); + note( + [ + "Web search lets your agent look things up online using the `web_search` tool.", + "It requires a Brave Search API key (you can store it in the config or set BRAVE_API_KEY in the Gateway environment).", + "Docs: https://docs.clawd.bot/tools/web", + ].join("\n"), + "Web search", + ); + const enableSearch = guardCancel( await confirm({ - message: "Enable web_search (Brave Search API)?", + message: "Enable web_search (Brave Search)?", initialValue: existingSearch?.enabled ?? hasSearchKey, }), runtime, @@ -116,7 +125,7 @@ async function promptWebToolsConfig( await text({ message: hasSearchKey ? "Brave Search API key (leave blank to keep current or use BRAVE_API_KEY)" - : "Brave Search API key (leave blank to use BRAVE_API_KEY)", + : "Brave Search API key (paste it here; leave blank to use BRAVE_API_KEY)", placeholder: hasSearchKey ? "Leave blank to keep current" : "BSA...", }), runtime, @@ -127,7 +136,8 @@ async function promptWebToolsConfig( } else if (!hasSearchKey) { note( [ - "No key stored. web_search needs BRAVE_API_KEY or tools.web.search.apiKey.", + "No key stored yet, so web_search will stay unavailable.", + "Store a key here or set BRAVE_API_KEY in the Gateway environment.", "Docs: https://docs.clawd.bot/tools/web", ].join("\n"), "Web search", diff --git a/src/wizard/onboarding.finalize.ts b/src/wizard/onboarding.finalize.ts index 62671ca54..29b91239d 100644 --- a/src/wizard/onboarding.finalize.ts +++ b/src/wizard/onboarding.finalize.ts @@ -49,6 +49,19 @@ type FinalizeOnboardingOptions = { export async function finalizeOnboardingWizard(options: FinalizeOnboardingOptions) { const { flow, opts, baseConfig, nextConfig, settings, prompter, runtime } = options; + const withWizardProgress = async ( + label: string, + options: { doneMessage?: string }, + work: (progress: { update: (message: string) => void }) => Promise, + ): Promise => { + const progress = prompter.progress(label); + try { + return await work(progress); + } finally { + progress.stop(options.doneMessage); + } + }; + const systemdAvailable = process.platform === "linux" ? await isSystemdUserServiceAvailable() : true; if (process.platform === "linux" && !systemdAvailable) { @@ -123,12 +136,26 @@ export async function finalizeOnboardingWizard(options: FinalizeOnboardingOption ], })) as "restart" | "reinstall" | "skip"; if (action === "restart") { - await service.restart({ - profile: process.env.CLAWDBOT_PROFILE, - stdout: process.stdout, - }); + await withWizardProgress( + "Gateway daemon", + { doneMessage: "Gateway daemon restarted." }, + async (progress) => { + progress.update("Restarting Gateway daemon…"); + await service.restart({ + profile: process.env.CLAWDBOT_PROFILE, + stdout: process.stdout, + }); + }, + ); } else if (action === "reinstall") { - await service.uninstall({ env: process.env, stdout: process.stdout }); + await withWizardProgress( + "Gateway daemon", + { doneMessage: "Gateway daemon uninstalled." }, + async (progress) => { + progress.update("Uninstalling Gateway daemon…"); + await service.uninstall({ env: process.env, stdout: process.stdout }); + }, + ); } } @@ -138,37 +165,46 @@ export async function finalizeOnboardingWizard(options: FinalizeOnboardingOption ) { const devMode = process.argv[1]?.includes(`${path.sep}src${path.sep}`) && process.argv[1]?.endsWith(".ts"); - const nodePath = await resolvePreferredNodePath({ - env: process.env, - runtime: daemonRuntime, - }); - const { programArguments, workingDirectory } = await resolveGatewayProgramArguments({ - port: settings.port, - dev: devMode, - runtime: daemonRuntime, - nodePath, - }); - if (daemonRuntime === "node") { - const systemNode = await resolveSystemNodeInfo({ env: process.env }); - const warning = renderSystemNodeWarning(systemNode, programArguments[0]); - if (warning) await prompter.note(warning, "Gateway runtime"); - } - const environment = buildServiceEnvironment({ - env: process.env, - port: settings.port, - token: settings.gatewayToken, - launchdLabel: - process.platform === "darwin" - ? resolveGatewayLaunchAgentLabel(process.env.CLAWDBOT_PROFILE) - : undefined, - }); - await service.install({ - env: process.env, - stdout: process.stdout, - programArguments, - workingDirectory, - environment, - }); + await withWizardProgress( + "Gateway daemon", + { doneMessage: "Gateway daemon installed." }, + async (progress) => { + progress.update("Preparing Gateway daemon…"); + const nodePath = await resolvePreferredNodePath({ + env: process.env, + runtime: daemonRuntime, + }); + const { programArguments, workingDirectory } = await resolveGatewayProgramArguments({ + port: settings.port, + dev: devMode, + runtime: daemonRuntime, + nodePath, + }); + if (daemonRuntime === "node") { + const systemNode = await resolveSystemNodeInfo({ env: process.env }); + const warning = renderSystemNodeWarning(systemNode, programArguments[0]); + if (warning) await prompter.note(warning, "Gateway runtime"); + } + const environment = buildServiceEnvironment({ + env: process.env, + port: settings.port, + token: settings.gatewayToken, + launchdLabel: + process.platform === "darwin" + ? resolveGatewayLaunchAgentLabel(process.env.CLAWDBOT_PROFILE) + : undefined, + }); + + progress.update("Installing Gateway daemon…"); + await service.install({ + env: process.env, + stdout: process.stdout, + programArguments, + workingDirectory, + environment, + }); + }, + ); } } @@ -353,16 +389,23 @@ export async function finalizeOnboardingWizard(options: FinalizeOnboardingOption await prompter.note( hasWebSearchKey ? [ - "Web search is ready.", + "Web search is enabled, so your agent can look things up online when needed.", + "", webSearchKey - ? "Brave API key: stored in config (tools.web.search.apiKey)." - : "Brave API key: provided via BRAVE_API_KEY env var.", + ? "API key: stored in config (tools.web.search.apiKey)." + : "API key: provided via BRAVE_API_KEY env var (Gateway environment).", "Docs: https://docs.clawd.bot/tools/web", ].join("\n") : [ - "Recommended: set up a Brave Search API key for web_search.", - "Easiest: clawdbot configure --section web (stores tools.web.search.apiKey).", - "Env alternative: BRAVE_API_KEY (gateway environment).", + "If you want your agent to be able to search the web, you’ll need an API key.", + "", + "Clawdbot uses Brave Search for the `web_search` tool. Without a Brave Search API key, web search won’t work.", + "", + "Set it up interactively:", + "- Run: clawdbot configure --section web", + "- Enable web_search and paste your Brave Search API key", + "", + "Alternative: set BRAVE_API_KEY in the Gateway environment (no config changes).", "Docs: https://docs.clawd.bot/tools/web", ].join("\n"), "Web search (optional)",