diff --git a/CHANGELOG.md b/CHANGELOG.md index 68bc1f6f2..e3cc4a8e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -101,6 +101,7 @@ - CLI: centralize lobster palette + apply it to onboarding/config prompts. — thanks @steipete - Gateway/CLI: add `clawdbot gateway --dev/--reset` to auto-create a dev config/workspace with a robot identity (no BOOTSTRAP.md), and reset wipes config/creds/sessions/workspace (subcommand --dev no longer collides with global --dev profile). — thanks @steipete - Configure: stop prompting to open the Control UI (still shown in onboarding). — thanks @steipete +- Onboarding/TUI: prompt to start TUI (best option) when BOOTSTRAP.md exists and add `tui --message` to auto-send the first prompt. — thanks @steipete - Telegram: suppress grammY getUpdates stack traces; log concise retry message instead. — thanks @steipete - Gateway/CLI: allow dev profile (`clawdbot --dev`) to auto-create the dev config + workspace. — thanks @steipete - Dev templates: ship C-3PO dev workspace defaults as docs templates and use them for dev bootstrap. — thanks @steipete diff --git a/docs/cli/index.md b/docs/cli/index.md index d53131670..730ad9397 100644 --- a/docs/cli/index.md +++ b/docs/cli/index.md @@ -660,5 +660,6 @@ Options: - `--session ` - `--deliver` - `--thinking ` +- `--message ` - `--timeout-ms ` - `--history-limit ` diff --git a/src/cli/tui-cli.ts b/src/cli/tui-cli.ts index beb4f84f1..86ce7ca1d 100644 --- a/src/cli/tui-cli.ts +++ b/src/cli/tui-cli.ts @@ -18,6 +18,7 @@ export function registerTuiCli(program: Command) { ) .option("--deliver", "Deliver assistant replies", false) .option("--thinking ", "Thinking level override") + .option("--message ", "Send an initial message after connecting") .option("--timeout-ms ", "Agent timeout in ms", "30000") .option("--history-limit ", "History entries to load", "200") .action(async (opts) => { @@ -37,6 +38,7 @@ export function registerTuiCli(program: Command) { session: opts.session as string | undefined, deliver: Boolean(opts.deliver), thinking: opts.thinking as string | undefined, + message: opts.message as string | undefined, timeoutMs: Number.isNaN(timeoutMs) ? undefined : timeoutMs, historyLimit: Number.isNaN(historyLimit) ? undefined : historyLimit, }); diff --git a/src/tui/tui.ts b/src/tui/tui.ts index 204156beb..bda0d8bd5 100644 --- a/src/tui/tui.ts +++ b/src/tui/tui.ts @@ -34,6 +34,7 @@ export type TuiOptions = { thinking?: string; timeoutMs?: number; historyLimit?: number; + message?: string; }; type ChatEvent = { @@ -146,6 +147,8 @@ export async function runTui(opts: TuiOptions) { let toolsExpanded = false; let showThinking = false; let deliverDefault = Boolean(opts.deliver); + const autoMessage = opts.message?.trim(); + let autoMessageSent = false; let sessionInfo: SessionInfo = {}; let lastCtrlCAt = 0; @@ -976,6 +979,10 @@ export async function runTui(opts: TuiOptions) { await loadHistory(); chatLog.addSystem("gateway connected"); tui.requestRender(); + if (!autoMessageSent && autoMessage) { + autoMessageSent = true; + await sendMessage(autoMessage); + } } else { chatLog.addSystem("gateway reconnected"); } diff --git a/src/wizard/onboarding.ts b/src/wizard/onboarding.ts index cb8459437..baaff9e58 100644 --- a/src/wizard/onboarding.ts +++ b/src/wizard/onboarding.ts @@ -1,5 +1,7 @@ +import fs from "node:fs/promises"; import path from "node:path"; import { ensureAuthProfileStore } from "../agents/auth-profiles.js"; +import { DEFAULT_BOOTSTRAP_FILENAME } from "../agents/workspace.js"; import { applyAuthChoice, warnIfModelConfigLooksOff, @@ -52,6 +54,7 @@ import { buildServiceEnvironment } from "../daemon/service-env.js"; import { ensureControlUiAssetsBuilt } from "../infra/control-ui-assets.js"; import type { RuntimeEnv } from "../runtime.js"; import { defaultRuntime } from "../runtime.js"; +import { runTui } from "../tui/tui.js"; import { resolveUserPath, sleep } from "../utils.js"; import type { WizardPrompter } from "./prompts.js"; @@ -654,6 +657,11 @@ export async function runOnboardingWizard( const gatewayStatusLine = gatewayProbe.ok ? "Gateway: reachable" : `Gateway: not detected${gatewayProbe.detail ? ` (${gatewayProbe.detail})` : ""}`; + const bootstrapPath = path.join(workspaceDir, DEFAULT_BOOTSTRAP_FILENAME); + const hasBootstrap = await fs + .access(bootstrapPath) + .then(() => true) + .catch(() => false); await prompter.note( [ @@ -668,33 +676,58 @@ export async function runOnboardingWizard( "Control UI", ); - const browserSupport = await detectBrowserOpenSupport(); if (gatewayProbe.ok) { - if (!browserSupport.ok) { + if (hasBootstrap) { await prompter.note( - formatControlUiSshHint({ - port, - basePath: baseConfig.gateway?.controlUi?.basePath, - token: authMode === "token" ? gatewayToken : undefined, - }), - "Open Control UI", + [ + "This is the defining action that makes your agent you.", + "Please take your time.", + "The more you tell it, the better the experience will be.", + 'We will send: "Wake up, my friend!"', + ].join("\n"), + "Start TUI (best option!)", ); - } else { - const wantsOpen = await prompter.confirm({ - message: "Open Control UI now?", + const wantsTui = await prompter.confirm({ + message: "Start TUI now? (best option!)", initialValue: true, }); - if (wantsOpen) { - const opened = await openUrl(`${links.httpUrl}${tokenParam}`); - if (!opened) { - await prompter.note( - formatControlUiSshHint({ - port, - basePath: baseConfig.gateway?.controlUi?.basePath, - token: authMode === "token" ? gatewayToken : undefined, - }), - "Open Control UI", - ); + if (wantsTui) { + await runTui({ + url: links.wsUrl, + token: authMode === "token" ? gatewayToken : undefined, + password: + authMode === "password" ? baseConfig.gateway?.auth?.password : "", + message: "Wake up, my friend!", + }); + } + } else { + const browserSupport = await detectBrowserOpenSupport(); + if (!browserSupport.ok) { + await prompter.note( + formatControlUiSshHint({ + port, + basePath: baseConfig.gateway?.controlUi?.basePath, + token: authMode === "token" ? gatewayToken : undefined, + }), + "Open Control UI", + ); + } else { + const wantsOpen = await prompter.confirm({ + message: "Open Control UI now?", + initialValue: true, + }); + if (wantsOpen) { + const opened = await openUrl(`${links.httpUrl}${tokenParam}`); + if (!opened) { + await prompter.note( + formatControlUiSshHint({ + port, + basePath: baseConfig.gateway?.controlUi?.basePath, + token: authMode === "token" ? gatewayToken : undefined, + }), + "Open Control UI", + ); + } } } }