feat: add onboarding doc links

This commit is contained in:
Peter Steinberger
2026-01-07 01:19:31 +01:00
parent 19c95d0ff7
commit 62112d9978
7 changed files with 110 additions and 21 deletions

View File

@@ -1194,6 +1194,12 @@ Control UI base path:
- Examples: `"/ui"`, `"/clawdbot"`, `"/apps/clawdbot"`. - Examples: `"/ui"`, `"/clawdbot"`, `"/apps/clawdbot"`.
- Default: root (`/`) (unchanged). - Default: root (`/`) (unchanged).
Related docs:
- [Control UI](/web/control-ui)
- [Web overview](/web)
- [Tailscale](/gateway/tailscale)
- [Remote access](/gateway/remote)
Notes: Notes:
- `clawdbot gateway` refuses to start unless `gateway.mode` is set to `local` (or you pass the override flag). - `clawdbot gateway` refuses to start unless `gateway.mode` is set to `local` (or you pass the override flag).
- `gateway.port` controls the single multiplexed port used for WebSocket + HTTP (control UI, hooks, A2UI). - `gateway.port` controls the single multiplexed port used for WebSocket + HTTP (control UI, hooks, A2UI).

View File

@@ -150,6 +150,14 @@ async function promptGatewayConfig(
let tailscaleResetOnExit = false; let tailscaleResetOnExit = false;
if (tailscaleMode !== "off") { if (tailscaleMode !== "off") {
note(
[
"Docs:",
"https://docs.clawd.bot/gateway/tailscale",
"https://docs.clawd.bot/web",
].join("\n"),
"Tailscale",
);
tailscaleResetOnExit = Boolean( tailscaleResetOnExit = Boolean(
guardCancel( guardCancel(
await confirm({ await confirm({
@@ -300,6 +308,7 @@ async function promptAuthConfig(
} catch (err) { } catch (err) {
spin.stop("OAuth failed"); spin.stop("OAuth failed");
runtime.error(String(err)); runtime.error(String(err));
note("Trouble with OAuth? See https://docs.clawd.bot/start/faq", "OAuth");
} }
} else if (authChoice === "openai-codex") { } else if (authChoice === "openai-codex") {
const isRemote = isRemoteEnvironment(); const isRemote = isRemoteEnvironment();
@@ -373,6 +382,7 @@ async function promptAuthConfig(
} catch (err) { } catch (err) {
spin.stop("OpenAI OAuth failed"); spin.stop("OpenAI OAuth failed");
runtime.error(String(err)); runtime.error(String(err));
note("Trouble with OAuth? See https://docs.clawd.bot/start/faq", "OAuth");
} }
} else if (authChoice === "antigravity") { } else if (authChoice === "antigravity") {
const isRemote = isRemoteEnvironment(); const isRemote = isRemoteEnvironment();
@@ -447,6 +457,7 @@ async function promptAuthConfig(
} catch (err) { } catch (err) {
spin.stop("Antigravity OAuth failed"); spin.stop("Antigravity OAuth failed");
runtime.error(String(err)); runtime.error(String(err));
note("Trouble with OAuth? See https://docs.clawd.bot/start/faq", "OAuth");
} }
} else if (authChoice === "apiKey") { } else if (authChoice === "apiKey") {
const key = guardCancel( const key = guardCancel(
@@ -608,9 +619,11 @@ export async function runConfigureWizard(
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}`) ...snapshot.issues.map((iss) => `- ${iss.path}: ${iss.message}`),
.join("\n"), "",
"Docs: https://docs.clawd.bot/gateway/configuration",
].join("\n"),
"Config issues", "Config issues",
); );
} }
@@ -789,6 +802,14 @@ export async function runConfigureWizard(
await healthCommand({ json: false, timeoutMs: 10_000 }, runtime); await healthCommand({ json: false, timeoutMs: 10_000 }, runtime);
} catch (err) { } catch (err) {
runtime.error(`Health check failed: ${String(err)}`); runtime.error(`Health check failed: ${String(err)}`);
note(
[
"Docs:",
"https://docs.clawd.bot/gateway/health",
"https://docs.clawd.bot/gateway/troubleshooting",
].join("\n"),
"Health check help",
);
} }
} }
@@ -797,20 +818,22 @@ export async function runConfigureWizard(
runtime.error(controlUiAssets.message); runtime.error(controlUiAssets.message);
} }
note( note(
(() => { (() => {
const bind = nextConfig.gateway?.bind ?? "loopback"; const bind = nextConfig.gateway?.bind ?? "loopback";
const links = resolveControlUiLinks({ const links = resolveControlUiLinks({
bind, bind,
port: gatewayPort, port: gatewayPort,
basePath: nextConfig.gateway?.controlUi?.basePath, basePath: nextConfig.gateway?.controlUi?.basePath,
}); });
return [`Web UI: ${links.httpUrl}`, `Gateway WS: ${links.wsUrl}`].join( return [
"\n", `Web UI: ${links.httpUrl}`,
); `Gateway WS: ${links.wsUrl}`,
})(), "Docs: https://docs.clawd.bot/web/control-ui",
"Control UI", ].join("\n");
); })(),
"Control UI",
);
const browserSupport = await detectBrowserOpenSupport(); const browserSupport = await detectBrowserOpenSupport();
if (!browserSupport.ok) { if (!browserSupport.ok) {

View File

@@ -195,6 +195,9 @@ export function formatControlUiSshHint(params: {
"Then open:", "Then open:",
localUrl, localUrl,
authedUrl, authedUrl,
"Docs:",
"https://docs.clawd.bot/gateway/remote",
"https://docs.clawd.bot/web/control-ui",
] ]
.filter(Boolean) .filter(Boolean)
.join("\n"); .join("\n");

View File

@@ -39,6 +39,7 @@ async function noteProviderPrimer(prompter: WizardPrompter): Promise<void> {
"DM security: default is pairing; unknown DMs get a pairing code.", "DM security: default is pairing; unknown DMs get a pairing code.",
"Approve with: clawdbot pairing approve --provider <provider> <code>", "Approve with: clawdbot pairing approve --provider <provider> <code>",
'Public DMs require dmPolicy="open" + allowFrom=["*"].', 'Public DMs require dmPolicy="open" + allowFrom=["*"].',
"Docs: https://docs.clawd.bot/start/pairing",
"", "",
"WhatsApp: links via WhatsApp Web (scan QR), stores creds for future sends.", "WhatsApp: links via WhatsApp Web (scan QR), stores creds for future sends.",
"WhatsApp: dedicated second number recommended; primary number OK (self-chat).", "WhatsApp: dedicated second number recommended; primary number OK (self-chat).",
@@ -59,6 +60,7 @@ async function noteTelegramTokenHelp(prompter: WizardPrompter): Promise<void> {
"2) Run /newbot (or /mybots)", "2) Run /newbot (or /mybots)",
"3) Copy the token (looks like 123456:ABC...)", "3) Copy the token (looks like 123456:ABC...)",
"Tip: you can also set TELEGRAM_BOT_TOKEN in your env.", "Tip: you can also set TELEGRAM_BOT_TOKEN in your env.",
"Docs: https://docs.clawd.bot/telegram",
].join("\n"), ].join("\n"),
"Telegram bot token", "Telegram bot token",
); );
@@ -71,6 +73,7 @@ async function noteDiscordTokenHelp(prompter: WizardPrompter): Promise<void> {
"2) Bot → Add Bot → Reset Token → copy token", "2) Bot → Add Bot → Reset Token → copy token",
"3) OAuth2 → URL Generator → scope 'bot' → invite to your server", "3) OAuth2 → URL Generator → scope 'bot' → invite to your server",
"Tip: enable Message Content Intent if you need message text.", "Tip: enable Message Content Intent if you need message text.",
"Docs: https://docs.clawd.bot/discord",
].join("\n"), ].join("\n"),
"Discord bot token", "Discord bot token",
); );
@@ -158,6 +161,7 @@ async function noteSlackTokenHelp(
"4) Enable Event Subscriptions (socket) for message events", "4) Enable Event Subscriptions (socket) for message events",
"5) App Home → enable the Messages tab for DMs", "5) App Home → enable the Messages tab for DMs",
"Tip: set SLACK_BOT_TOKEN + SLACK_APP_TOKEN in your env.", "Tip: set SLACK_BOT_TOKEN + SLACK_APP_TOKEN in your env.",
"Docs: https://docs.clawd.bot/slack",
"", "",
"Manifest (JSON):", "Manifest (JSON):",
manifest, manifest,
@@ -298,6 +302,7 @@ async function maybeConfigureDmPolicies(params: {
"Default: pairing (unknown DMs get a pairing code).", "Default: pairing (unknown DMs get a pairing code).",
`Approve: clawdbot pairing approve --provider ${params.provider} <code>`, `Approve: clawdbot pairing approve --provider ${params.provider} <code>`,
`Public DMs: ${params.policyKey}="open" + ${params.allowFromKey} includes "*".`, `Public DMs: ${params.policyKey}="open" + ${params.allowFromKey} includes "*".`,
"Docs: https://docs.clawd.bot/start/pairing",
].join("\n"), ].join("\n"),
`${params.label} DM access`, `${params.label} DM access`,
); );
@@ -383,6 +388,7 @@ async function promptWhatsAppAllowFrom(
"- disabled: ignore WhatsApp DMs", "- disabled: ignore WhatsApp DMs",
"", "",
`Current: dmPolicy=${existingPolicy}, allowFrom=${existingLabel}`, `Current: dmPolicy=${existingPolicy}, allowFrom=${existingLabel}`,
"Docs: https://docs.clawd.bot/whatsapp",
].join("\n"), ].join("\n"),
"WhatsApp DM access", "WhatsApp DM access",
); );
@@ -551,6 +557,7 @@ export async function setupProviders(
[ [
"Scan the QR with WhatsApp on your phone.", "Scan the QR with WhatsApp on your phone.",
`Credentials are stored under ${WA_WEB_AUTH_DIR}/ for future runs.`, `Credentials are stored under ${WA_WEB_AUTH_DIR}/ for future runs.`,
"Docs: https://docs.clawd.bot/whatsapp",
].join("\n"), ].join("\n"),
"WhatsApp linking", "WhatsApp linking",
); );
@@ -566,6 +573,10 @@ export async function setupProviders(
await loginWeb(false, "web"); await loginWeb(false, "web");
} catch (err) { } catch (err) {
runtime.error(`WhatsApp login failed: ${String(err)}`); runtime.error(`WhatsApp login failed: ${String(err)}`);
await prompter.note(
"Docs: https://docs.clawd.bot/whatsapp",
"WhatsApp help",
);
} }
} else if (!whatsappLinked) { } else if (!whatsappLinked) {
await prompter.note( await prompter.note(
@@ -864,6 +875,7 @@ export async function setupProviders(
'Link device with: signal-cli link -n "Clawdbot"', 'Link device with: signal-cli link -n "Clawdbot"',
"Scan QR in Signal → Linked Devices", "Scan QR in Signal → Linked Devices",
"Then run: clawdbot gateway call providers.status --params '{\"probe\":true}'", "Then run: clawdbot gateway call providers.status --params '{\"probe\":true}'",
"Docs: https://docs.clawd.bot/signal",
].join("\n"), ].join("\n"),
"Signal next steps", "Signal next steps",
); );
@@ -902,6 +914,7 @@ export async function setupProviders(
"Ensure Clawdbot has Full Disk Access to Messages DB.", "Ensure Clawdbot has Full Disk Access to Messages DB.",
"Grant Automation permission for Messages when prompted.", "Grant Automation permission for Messages when prompted.",
"List chats with: imsg chats --limit 20", "List chats with: imsg chats --limit 20",
"Docs: https://docs.clawd.bot/imessage",
].join("\n"), ].join("\n"),
"iMessage next steps", "iMessage next steps",
); );

View File

@@ -42,7 +42,10 @@ export async function promptRemoteGatewayConfig(
if (!hasBonjourTool) { if (!hasBonjourTool) {
await prompter.note( await prompter.note(
"Bonjour discovery requires dns-sd (macOS) or avahi-browse (Linux).", [
"Bonjour discovery requires dns-sd (macOS) or avahi-browse (Linux).",
"Docs: https://docs.clawd.bot/gateway/discovery",
].join("\n"),
"Discovery", "Discovery",
); );
} }
@@ -98,6 +101,7 @@ export async function promptRemoteGatewayConfig(
`ssh -N -L 18789:127.0.0.1:18789 <user>@${host}${ `ssh -N -L 18789:127.0.0.1:18789 <user>@${host}${
selectedBeacon.sshPort ? ` -p ${selectedBeacon.sshPort}` : "" selectedBeacon.sshPort ? ` -p ${selectedBeacon.sshPort}` : ""
}`, }`,
"Docs: https://docs.clawd.bot/gateway/remote",
].join("\n"), ].join("\n"),
"SSH tunnel", "SSH tunnel",
); );

View File

@@ -168,6 +168,7 @@ export async function setupSkills(
runtime.log( runtime.log(
"Tip: run `clawdbot doctor` to review skills + requirements.", "Tip: run `clawdbot doctor` to review skills + requirements.",
); );
runtime.log("Docs: https://docs.clawd.bot/skills");
} }
} }
} }

View File

@@ -142,9 +142,11 @@ export async function runOnboardingWizard(
await prompter.note(summarizeExistingConfig(baseConfig), title); await prompter.note(summarizeExistingConfig(baseConfig), title);
if (!snapshot.valid && snapshot.issues.length > 0) { if (!snapshot.valid && snapshot.issues.length > 0) {
await prompter.note( await prompter.note(
snapshot.issues [
.map((iss) => `- ${iss.path}: ${iss.message}`) ...snapshot.issues.map((iss) => `- ${iss.path}: ${iss.message}`),
.join("\n"), "",
"Docs: https://docs.clawd.bot/gateway/configuration",
].join("\n"),
"Config issues", "Config issues",
); );
} }
@@ -302,6 +304,10 @@ export async function runOnboardingWizard(
} catch (err) { } catch (err) {
spin.stop("OAuth failed"); spin.stop("OAuth failed");
runtime.error(String(err)); runtime.error(String(err));
await prompter.note(
"Trouble with OAuth? See https://docs.clawd.bot/start/faq",
"OAuth help",
);
} }
} else if (authChoice === "openai-codex") { } else if (authChoice === "openai-codex") {
const isRemote = isRemoteEnvironment(); const isRemote = isRemoteEnvironment();
@@ -375,6 +381,10 @@ export async function runOnboardingWizard(
} catch (err) { } catch (err) {
spin.stop("OpenAI OAuth failed"); spin.stop("OpenAI OAuth failed");
runtime.error(String(err)); runtime.error(String(err));
await prompter.note(
"Trouble with OAuth? See https://docs.clawd.bot/start/faq",
"OAuth help",
);
} }
} else if (authChoice === "antigravity") { } else if (authChoice === "antigravity") {
const isRemote = isRemoteEnvironment(); const isRemote = isRemoteEnvironment();
@@ -448,6 +458,10 @@ export async function runOnboardingWizard(
} catch (err) { } catch (err) {
spin.stop("Antigravity OAuth failed"); spin.stop("Antigravity OAuth failed");
runtime.error(String(err)); runtime.error(String(err));
await prompter.note(
"Trouble with OAuth? See https://docs.clawd.bot/start/faq",
"OAuth help",
);
} }
} else if (authChoice === "apiKey") { } else if (authChoice === "apiKey") {
const key = await prompter.text({ const key = await prompter.text({
@@ -520,6 +534,14 @@ export async function runOnboardingWizard(
let tailscaleResetOnExit = false; let tailscaleResetOnExit = false;
if (tailscaleMode !== "off") { if (tailscaleMode !== "off") {
await prompter.note(
[
"Docs:",
"https://docs.clawd.bot/gateway/tailscale",
"https://docs.clawd.bot/web",
].join("\n"),
"Tailscale",
);
tailscaleResetOnExit = Boolean( tailscaleResetOnExit = Boolean(
await prompter.confirm({ await prompter.confirm({
message: "Reset Tailscale serve/funnel on exit?", message: "Reset Tailscale serve/funnel on exit?",
@@ -693,6 +715,14 @@ export async function runOnboardingWizard(
await healthCommand({ json: false, timeoutMs: 10_000 }, runtime); await healthCommand({ json: false, timeoutMs: 10_000 }, runtime);
} catch (err) { } catch (err) {
runtime.error(`Health check failed: ${String(err)}`); runtime.error(`Health check failed: ${String(err)}`);
await prompter.note(
[
"Docs:",
"https://docs.clawd.bot/gateway/health",
"https://docs.clawd.bot/gateway/troubleshooting",
].join("\n"),
"Health check help",
);
} }
const controlUiAssets = await ensureControlUiAssetsBuilt(runtime); const controlUiAssets = await ensureControlUiAssetsBuilt(runtime);
@@ -726,6 +756,7 @@ export async function runOnboardingWizard(
`Web UI: ${links.httpUrl}`, `Web UI: ${links.httpUrl}`,
tokenParam ? `Web UI (with token): ${authedUrl}` : undefined, tokenParam ? `Web UI (with token): ${authedUrl}` : undefined,
`Gateway WS: ${links.wsUrl}`, `Gateway WS: ${links.wsUrl}`,
"Docs: https://docs.clawd.bot/web/control-ui",
] ]
.filter(Boolean) .filter(Boolean)
.join("\n"); .join("\n");
@@ -772,5 +803,13 @@ export async function runOnboardingWizard(
} }
} }
await prompter.note(
[
"Back up your agent workspace.",
"Docs: https://docs.clawd.bot/concepts/agent-workspace",
].join("\n"),
"Workspace backup",
);
await prompter.outro("Onboarding complete."); await prompter.outro("Onboarding complete.");
} }