feat: add TUI bootstrap start
This commit is contained in:
@@ -101,6 +101,7 @@
|
|||||||
- CLI: centralize lobster palette + apply it to onboarding/config prompts. — thanks @steipete
|
- 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
|
- 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
|
- 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
|
- 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
|
- 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
|
- Dev templates: ship C-3PO dev workspace defaults as docs templates and use them for dev bootstrap. — thanks @steipete
|
||||||
|
|||||||
@@ -660,5 +660,6 @@ Options:
|
|||||||
- `--session <key>`
|
- `--session <key>`
|
||||||
- `--deliver`
|
- `--deliver`
|
||||||
- `--thinking <level>`
|
- `--thinking <level>`
|
||||||
|
- `--message <text>`
|
||||||
- `--timeout-ms <ms>`
|
- `--timeout-ms <ms>`
|
||||||
- `--history-limit <n>`
|
- `--history-limit <n>`
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export function registerTuiCli(program: Command) {
|
|||||||
)
|
)
|
||||||
.option("--deliver", "Deliver assistant replies", false)
|
.option("--deliver", "Deliver assistant replies", false)
|
||||||
.option("--thinking <level>", "Thinking level override")
|
.option("--thinking <level>", "Thinking level override")
|
||||||
|
.option("--message <text>", "Send an initial message after connecting")
|
||||||
.option("--timeout-ms <ms>", "Agent timeout in ms", "30000")
|
.option("--timeout-ms <ms>", "Agent timeout in ms", "30000")
|
||||||
.option("--history-limit <n>", "History entries to load", "200")
|
.option("--history-limit <n>", "History entries to load", "200")
|
||||||
.action(async (opts) => {
|
.action(async (opts) => {
|
||||||
@@ -37,6 +38,7 @@ export function registerTuiCli(program: Command) {
|
|||||||
session: opts.session as string | undefined,
|
session: opts.session as string | undefined,
|
||||||
deliver: Boolean(opts.deliver),
|
deliver: Boolean(opts.deliver),
|
||||||
thinking: opts.thinking as string | undefined,
|
thinking: opts.thinking as string | undefined,
|
||||||
|
message: opts.message as string | undefined,
|
||||||
timeoutMs: Number.isNaN(timeoutMs) ? undefined : timeoutMs,
|
timeoutMs: Number.isNaN(timeoutMs) ? undefined : timeoutMs,
|
||||||
historyLimit: Number.isNaN(historyLimit) ? undefined : historyLimit,
|
historyLimit: Number.isNaN(historyLimit) ? undefined : historyLimit,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export type TuiOptions = {
|
|||||||
thinking?: string;
|
thinking?: string;
|
||||||
timeoutMs?: number;
|
timeoutMs?: number;
|
||||||
historyLimit?: number;
|
historyLimit?: number;
|
||||||
|
message?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ChatEvent = {
|
type ChatEvent = {
|
||||||
@@ -146,6 +147,8 @@ export async function runTui(opts: TuiOptions) {
|
|||||||
let toolsExpanded = false;
|
let toolsExpanded = false;
|
||||||
let showThinking = false;
|
let showThinking = false;
|
||||||
let deliverDefault = Boolean(opts.deliver);
|
let deliverDefault = Boolean(opts.deliver);
|
||||||
|
const autoMessage = opts.message?.trim();
|
||||||
|
let autoMessageSent = false;
|
||||||
let sessionInfo: SessionInfo = {};
|
let sessionInfo: SessionInfo = {};
|
||||||
let lastCtrlCAt = 0;
|
let lastCtrlCAt = 0;
|
||||||
|
|
||||||
@@ -976,6 +979,10 @@ export async function runTui(opts: TuiOptions) {
|
|||||||
await loadHistory();
|
await loadHistory();
|
||||||
chatLog.addSystem("gateway connected");
|
chatLog.addSystem("gateway connected");
|
||||||
tui.requestRender();
|
tui.requestRender();
|
||||||
|
if (!autoMessageSent && autoMessage) {
|
||||||
|
autoMessageSent = true;
|
||||||
|
await sendMessage(autoMessage);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
chatLog.addSystem("gateway reconnected");
|
chatLog.addSystem("gateway reconnected");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import fs from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { ensureAuthProfileStore } from "../agents/auth-profiles.js";
|
import { ensureAuthProfileStore } from "../agents/auth-profiles.js";
|
||||||
|
import { DEFAULT_BOOTSTRAP_FILENAME } from "../agents/workspace.js";
|
||||||
import {
|
import {
|
||||||
applyAuthChoice,
|
applyAuthChoice,
|
||||||
warnIfModelConfigLooksOff,
|
warnIfModelConfigLooksOff,
|
||||||
@@ -52,6 +54,7 @@ import { buildServiceEnvironment } from "../daemon/service-env.js";
|
|||||||
import { ensureControlUiAssetsBuilt } from "../infra/control-ui-assets.js";
|
import { ensureControlUiAssetsBuilt } from "../infra/control-ui-assets.js";
|
||||||
import type { RuntimeEnv } from "../runtime.js";
|
import type { RuntimeEnv } from "../runtime.js";
|
||||||
import { defaultRuntime } from "../runtime.js";
|
import { defaultRuntime } from "../runtime.js";
|
||||||
|
import { runTui } from "../tui/tui.js";
|
||||||
import { resolveUserPath, sleep } from "../utils.js";
|
import { resolveUserPath, sleep } from "../utils.js";
|
||||||
import type { WizardPrompter } from "./prompts.js";
|
import type { WizardPrompter } from "./prompts.js";
|
||||||
|
|
||||||
@@ -654,6 +657,11 @@ export async function runOnboardingWizard(
|
|||||||
const gatewayStatusLine = gatewayProbe.ok
|
const gatewayStatusLine = gatewayProbe.ok
|
||||||
? "Gateway: reachable"
|
? "Gateway: reachable"
|
||||||
: `Gateway: not detected${gatewayProbe.detail ? ` (${gatewayProbe.detail})` : ""}`;
|
: `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(
|
await prompter.note(
|
||||||
[
|
[
|
||||||
@@ -668,33 +676,58 @@ export async function runOnboardingWizard(
|
|||||||
"Control UI",
|
"Control UI",
|
||||||
);
|
);
|
||||||
|
|
||||||
const browserSupport = await detectBrowserOpenSupport();
|
|
||||||
if (gatewayProbe.ok) {
|
if (gatewayProbe.ok) {
|
||||||
if (!browserSupport.ok) {
|
if (hasBootstrap) {
|
||||||
await prompter.note(
|
await prompter.note(
|
||||||
formatControlUiSshHint({
|
[
|
||||||
port,
|
"This is the defining action that makes your agent you.",
|
||||||
basePath: baseConfig.gateway?.controlUi?.basePath,
|
"Please take your time.",
|
||||||
token: authMode === "token" ? gatewayToken : undefined,
|
"The more you tell it, the better the experience will be.",
|
||||||
}),
|
'We will send: "Wake up, my friend!"',
|
||||||
"Open Control UI",
|
].join("\n"),
|
||||||
|
"Start TUI (best option!)",
|
||||||
);
|
);
|
||||||
} else {
|
const wantsTui = await prompter.confirm({
|
||||||
const wantsOpen = await prompter.confirm({
|
message: "Start TUI now? (best option!)",
|
||||||
message: "Open Control UI now?",
|
|
||||||
initialValue: true,
|
initialValue: true,
|
||||||
});
|
});
|
||||||
if (wantsOpen) {
|
if (wantsTui) {
|
||||||
const opened = await openUrl(`${links.httpUrl}${tokenParam}`);
|
await runTui({
|
||||||
if (!opened) {
|
url: links.wsUrl,
|
||||||
await prompter.note(
|
token: authMode === "token" ? gatewayToken : undefined,
|
||||||
formatControlUiSshHint({
|
password:
|
||||||
port,
|
authMode === "password" ? baseConfig.gateway?.auth?.password : "",
|
||||||
basePath: baseConfig.gateway?.controlUi?.basePath,
|
message: "Wake up, my friend!",
|
||||||
token: authMode === "token" ? gatewayToken : undefined,
|
});
|
||||||
}),
|
}
|
||||||
"Open Control UI",
|
} 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",
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user