fix(onboarding): daemon progress + web search setup
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
|||||||
} from "../daemon/runtime-paths.js";
|
} from "../daemon/runtime-paths.js";
|
||||||
import { resolveGatewayService } from "../daemon/service.js";
|
import { resolveGatewayService } from "../daemon/service.js";
|
||||||
import { buildServiceEnvironment } from "../daemon/service-env.js";
|
import { buildServiceEnvironment } from "../daemon/service-env.js";
|
||||||
|
import { withProgress } from "../cli/progress.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 { confirm, select } from "./configure.shared.js";
|
import { confirm, select } from "./configure.shared.js";
|
||||||
@@ -43,63 +44,89 @@ export async function maybeInstallDaemon(params: {
|
|||||||
params.runtime,
|
params.runtime,
|
||||||
);
|
);
|
||||||
if (action === "restart") {
|
if (action === "restart") {
|
||||||
await service.restart({
|
await withProgress(
|
||||||
profile: process.env.CLAWDBOT_PROFILE,
|
{ label: "Gateway daemon", indeterminate: true, delayMs: 0 },
|
||||||
stdout: process.stdout,
|
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;
|
shouldCheckLinger = true;
|
||||||
shouldInstall = false;
|
shouldInstall = false;
|
||||||
}
|
}
|
||||||
if (action === "skip") return;
|
if (action === "skip") return;
|
||||||
if (action === "reinstall") {
|
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 (shouldInstall) {
|
||||||
if (!params.daemonRuntime) {
|
await withProgress(
|
||||||
daemonRuntime = guardCancel(
|
{ label: "Gateway daemon", indeterminate: true, delayMs: 0 },
|
||||||
await select({
|
async (progress) => {
|
||||||
message: "Gateway daemon runtime",
|
if (!params.daemonRuntime) {
|
||||||
options: GATEWAY_DAEMON_RUNTIME_OPTIONS,
|
daemonRuntime = guardCancel(
|
||||||
initialValue: DEFAULT_GATEWAY_DAEMON_RUNTIME,
|
await select({
|
||||||
}),
|
message: "Gateway daemon runtime",
|
||||||
params.runtime,
|
options: GATEWAY_DAEMON_RUNTIME_OPTIONS,
|
||||||
) as GatewayDaemonRuntime;
|
initialValue: DEFAULT_GATEWAY_DAEMON_RUNTIME,
|
||||||
}
|
}),
|
||||||
const devMode =
|
params.runtime,
|
||||||
process.argv[1]?.includes(`${path.sep}src${path.sep}`) && process.argv[1]?.endsWith(".ts");
|
) as GatewayDaemonRuntime;
|
||||||
const nodePath = await resolvePreferredNodePath({
|
}
|
||||||
env: process.env,
|
|
||||||
runtime: daemonRuntime,
|
progress.setLabel("Preparing Gateway daemon…");
|
||||||
});
|
|
||||||
const { programArguments, workingDirectory } = await resolveGatewayProgramArguments({
|
const devMode =
|
||||||
port: params.port,
|
process.argv[1]?.includes(`${path.sep}src${path.sep}`) &&
|
||||||
dev: devMode,
|
process.argv[1]?.endsWith(".ts");
|
||||||
runtime: daemonRuntime,
|
const nodePath = await resolvePreferredNodePath({
|
||||||
nodePath,
|
env: process.env,
|
||||||
});
|
runtime: daemonRuntime,
|
||||||
if (daemonRuntime === "node") {
|
});
|
||||||
const systemNode = await resolveSystemNodeInfo({ env: process.env });
|
const { programArguments, workingDirectory } = await resolveGatewayProgramArguments({
|
||||||
const warning = renderSystemNodeWarning(systemNode, programArguments[0]);
|
port: params.port,
|
||||||
if (warning) note(warning, "Gateway runtime");
|
dev: devMode,
|
||||||
}
|
runtime: daemonRuntime,
|
||||||
const environment = buildServiceEnvironment({
|
nodePath,
|
||||||
env: process.env,
|
});
|
||||||
port: params.port,
|
if (daemonRuntime === "node") {
|
||||||
token: params.gatewayToken,
|
const systemNode = await resolveSystemNodeInfo({ env: process.env });
|
||||||
launchdLabel:
|
const warning = renderSystemNodeWarning(systemNode, programArguments[0]);
|
||||||
process.platform === "darwin"
|
if (warning) note(warning, "Gateway runtime");
|
||||||
? resolveGatewayLaunchAgentLabel(process.env.CLAWDBOT_PROFILE)
|
}
|
||||||
: undefined,
|
const environment = buildServiceEnvironment({
|
||||||
});
|
env: process.env,
|
||||||
await service.install({
|
port: params.port,
|
||||||
env: process.env,
|
token: params.gatewayToken,
|
||||||
stdout: process.stdout,
|
launchdLabel:
|
||||||
programArguments,
|
process.platform === "darwin"
|
||||||
workingDirectory,
|
? resolveGatewayLaunchAgentLabel(process.env.CLAWDBOT_PROFILE)
|
||||||
environment,
|
: 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;
|
shouldCheckLinger = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,9 +98,18 @@ async function promptWebToolsConfig(
|
|||||||
const existingFetch = nextConfig.tools?.web?.fetch;
|
const existingFetch = nextConfig.tools?.web?.fetch;
|
||||||
const hasSearchKey = Boolean(existingSearch?.apiKey);
|
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(
|
const enableSearch = guardCancel(
|
||||||
await confirm({
|
await confirm({
|
||||||
message: "Enable web_search (Brave Search API)?",
|
message: "Enable web_search (Brave Search)?",
|
||||||
initialValue: existingSearch?.enabled ?? hasSearchKey,
|
initialValue: existingSearch?.enabled ?? hasSearchKey,
|
||||||
}),
|
}),
|
||||||
runtime,
|
runtime,
|
||||||
@@ -116,7 +125,7 @@ async function promptWebToolsConfig(
|
|||||||
await text({
|
await text({
|
||||||
message: hasSearchKey
|
message: hasSearchKey
|
||||||
? "Brave Search API key (leave blank to keep current or use BRAVE_API_KEY)"
|
? "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...",
|
placeholder: hasSearchKey ? "Leave blank to keep current" : "BSA...",
|
||||||
}),
|
}),
|
||||||
runtime,
|
runtime,
|
||||||
@@ -127,7 +136,8 @@ async function promptWebToolsConfig(
|
|||||||
} else if (!hasSearchKey) {
|
} else if (!hasSearchKey) {
|
||||||
note(
|
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",
|
"Docs: https://docs.clawd.bot/tools/web",
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
"Web search",
|
"Web search",
|
||||||
|
|||||||
@@ -49,6 +49,19 @@ type FinalizeOnboardingOptions = {
|
|||||||
export async function finalizeOnboardingWizard(options: FinalizeOnboardingOptions) {
|
export async function finalizeOnboardingWizard(options: FinalizeOnboardingOptions) {
|
||||||
const { flow, opts, baseConfig, nextConfig, settings, prompter, runtime } = options;
|
const { flow, opts, baseConfig, nextConfig, settings, prompter, runtime } = options;
|
||||||
|
|
||||||
|
const withWizardProgress = async <T>(
|
||||||
|
label: string,
|
||||||
|
options: { doneMessage?: string },
|
||||||
|
work: (progress: { update: (message: string) => void }) => Promise<T>,
|
||||||
|
): Promise<T> => {
|
||||||
|
const progress = prompter.progress(label);
|
||||||
|
try {
|
||||||
|
return await work(progress);
|
||||||
|
} finally {
|
||||||
|
progress.stop(options.doneMessage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const systemdAvailable =
|
const systemdAvailable =
|
||||||
process.platform === "linux" ? await isSystemdUserServiceAvailable() : true;
|
process.platform === "linux" ? await isSystemdUserServiceAvailable() : true;
|
||||||
if (process.platform === "linux" && !systemdAvailable) {
|
if (process.platform === "linux" && !systemdAvailable) {
|
||||||
@@ -123,12 +136,26 @@ export async function finalizeOnboardingWizard(options: FinalizeOnboardingOption
|
|||||||
],
|
],
|
||||||
})) as "restart" | "reinstall" | "skip";
|
})) as "restart" | "reinstall" | "skip";
|
||||||
if (action === "restart") {
|
if (action === "restart") {
|
||||||
await service.restart({
|
await withWizardProgress(
|
||||||
profile: process.env.CLAWDBOT_PROFILE,
|
"Gateway daemon",
|
||||||
stdout: process.stdout,
|
{ 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") {
|
} 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 =
|
const devMode =
|
||||||
process.argv[1]?.includes(`${path.sep}src${path.sep}`) && process.argv[1]?.endsWith(".ts");
|
process.argv[1]?.includes(`${path.sep}src${path.sep}`) && process.argv[1]?.endsWith(".ts");
|
||||||
const nodePath = await resolvePreferredNodePath({
|
await withWizardProgress(
|
||||||
env: process.env,
|
"Gateway daemon",
|
||||||
runtime: daemonRuntime,
|
{ doneMessage: "Gateway daemon installed." },
|
||||||
});
|
async (progress) => {
|
||||||
const { programArguments, workingDirectory } = await resolveGatewayProgramArguments({
|
progress.update("Preparing Gateway daemon…");
|
||||||
port: settings.port,
|
const nodePath = await resolvePreferredNodePath({
|
||||||
dev: devMode,
|
env: process.env,
|
||||||
runtime: daemonRuntime,
|
runtime: daemonRuntime,
|
||||||
nodePath,
|
});
|
||||||
});
|
const { programArguments, workingDirectory } = await resolveGatewayProgramArguments({
|
||||||
if (daemonRuntime === "node") {
|
port: settings.port,
|
||||||
const systemNode = await resolveSystemNodeInfo({ env: process.env });
|
dev: devMode,
|
||||||
const warning = renderSystemNodeWarning(systemNode, programArguments[0]);
|
runtime: daemonRuntime,
|
||||||
if (warning) await prompter.note(warning, "Gateway runtime");
|
nodePath,
|
||||||
}
|
});
|
||||||
const environment = buildServiceEnvironment({
|
if (daemonRuntime === "node") {
|
||||||
env: process.env,
|
const systemNode = await resolveSystemNodeInfo({ env: process.env });
|
||||||
port: settings.port,
|
const warning = renderSystemNodeWarning(systemNode, programArguments[0]);
|
||||||
token: settings.gatewayToken,
|
if (warning) await prompter.note(warning, "Gateway runtime");
|
||||||
launchdLabel:
|
}
|
||||||
process.platform === "darwin"
|
const environment = buildServiceEnvironment({
|
||||||
? resolveGatewayLaunchAgentLabel(process.env.CLAWDBOT_PROFILE)
|
env: process.env,
|
||||||
: undefined,
|
port: settings.port,
|
||||||
});
|
token: settings.gatewayToken,
|
||||||
await service.install({
|
launchdLabel:
|
||||||
env: process.env,
|
process.platform === "darwin"
|
||||||
stdout: process.stdout,
|
? resolveGatewayLaunchAgentLabel(process.env.CLAWDBOT_PROFILE)
|
||||||
programArguments,
|
: undefined,
|
||||||
workingDirectory,
|
});
|
||||||
environment,
|
|
||||||
});
|
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(
|
await prompter.note(
|
||||||
hasWebSearchKey
|
hasWebSearchKey
|
||||||
? [
|
? [
|
||||||
"Web search is ready.",
|
"Web search is enabled, so your agent can look things up online when needed.",
|
||||||
|
"",
|
||||||
webSearchKey
|
webSearchKey
|
||||||
? "Brave API key: stored in config (tools.web.search.apiKey)."
|
? "API key: stored in config (tools.web.search.apiKey)."
|
||||||
: "Brave API key: provided via BRAVE_API_KEY env var.",
|
: "API key: provided via BRAVE_API_KEY env var (Gateway environment).",
|
||||||
"Docs: https://docs.clawd.bot/tools/web",
|
"Docs: https://docs.clawd.bot/tools/web",
|
||||||
].join("\n")
|
].join("\n")
|
||||||
: [
|
: [
|
||||||
"Recommended: set up a Brave Search API key for web_search.",
|
"If you want your agent to be able to search the web, you’ll need an API key.",
|
||||||
"Easiest: clawdbot configure --section web (stores tools.web.search.apiKey).",
|
"",
|
||||||
"Env alternative: BRAVE_API_KEY (gateway environment).",
|
"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",
|
"Docs: https://docs.clawd.bot/tools/web",
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
"Web search (optional)",
|
"Web search (optional)",
|
||||||
|
|||||||
Reference in New Issue
Block a user