feat: apply lobster palette to prompts
This commit is contained in:
@@ -83,6 +83,7 @@
|
|||||||
- **Multi-agent safety:** do **not** switch branches / check out a different branch unless explicitly requested.
|
- **Multi-agent safety:** do **not** switch branches / check out a different branch unless explicitly requested.
|
||||||
- **Multi-agent safety:** running multiple agents is OK as long as each agent has its own session.
|
- **Multi-agent safety:** running multiple agents is OK as long as each agent has its own session.
|
||||||
- **Multi-agent safety:** when you see unrecognized files, keep going; focus on your changes and commit only those.
|
- **Multi-agent safety:** when you see unrecognized files, keep going; focus on your changes and commit only those.
|
||||||
|
- Lobster seam: use the shared CLI palette in `src/terminal/palette.ts` (no hardcoded colors); apply palette to onboarding/config prompts and other TTY UI output as needed.
|
||||||
- **Multi-agent safety:** focus reports on your edits; avoid guard-rail disclaimers unless truly blocked; when multiple agents touch the same file, continue if safe; end with a brief “other files present” note only if relevant.
|
- **Multi-agent safety:** focus reports on your edits; avoid guard-rail disclaimers unless truly blocked; when multiple agents touch the same file, continue if safe; end with a brief “other files present” note only if relevant.
|
||||||
- Bug investigations: read source code of relevant npm dependencies and all related local code before concluding; aim for high-confidence root cause.
|
- Bug investigations: read source code of relevant npm dependencies and all related local code before concluding; aim for high-confidence root cause.
|
||||||
- Code style: add brief comments for tricky logic; keep files under ~500 LOC when feasible (split/refactor as needed).
|
- Code style: add brief comments for tricky logic; keep files under ~500 LOC when feasible (split/refactor as needed).
|
||||||
|
|||||||
@@ -78,6 +78,7 @@
|
|||||||
- Agent: bypass Anthropic OAuth tool-name blocks by capitalizing built-ins and keeping pruning tool matching case-insensitive. (#553) — thanks @andrewting19
|
- Agent: bypass Anthropic OAuth tool-name blocks by capitalizing built-ins and keeping pruning tool matching case-insensitive. (#553) — thanks @andrewting19
|
||||||
- Commands/Tools: disable /restart and gateway restart tool by default (enable with commands.restart=true).
|
- Commands/Tools: disable /restart and gateway restart tool by default (enable with commands.restart=true).
|
||||||
- Gateway/CLI: add `clawdbot gateway discover` (Bonjour scan on `local.` + `clawdbot.internal.`) with `--timeout` and `--json`. — thanks @steipete
|
- Gateway/CLI: add `clawdbot gateway discover` (Bonjour scan on `local.` + `clawdbot.internal.`) with `--timeout` and `--json`. — thanks @steipete
|
||||||
|
- CLI: centralize lobster palette + apply it to onboarding/config prompts. — thanks @steipete
|
||||||
|
|
||||||
## 2026.1.8
|
## 2026.1.8
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ Clawdbot uses a lobster palette for CLI output.
|
|||||||
- `error` (#E23D2D): errors, failures.
|
- `error` (#E23D2D): errors, failures.
|
||||||
- `muted` (#8B7F77): de-emphasis, metadata.
|
- `muted` (#8B7F77): de-emphasis, metadata.
|
||||||
|
|
||||||
|
Palette source of truth: `src/terminal/palette.ts` (aka “lobster seam”).
|
||||||
|
|
||||||
## Command tree
|
## Command tree
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
confirm,
|
confirm as clackConfirm,
|
||||||
intro,
|
intro as clackIntro,
|
||||||
multiselect,
|
multiselect as clackMultiselect,
|
||||||
note,
|
note as clackNote,
|
||||||
outro,
|
outro as clackOutro,
|
||||||
select,
|
select as clackSelect,
|
||||||
spinner,
|
spinner,
|
||||||
text,
|
text as clackText,
|
||||||
} from "@clack/prompts";
|
} from "@clack/prompts";
|
||||||
import {
|
import {
|
||||||
loginOpenAICodex,
|
loginOpenAICodex,
|
||||||
@@ -39,6 +39,11 @@ 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 { theme } from "../terminal/theme.js";
|
import { theme } from "../terminal/theme.js";
|
||||||
|
import {
|
||||||
|
stylePromptHint,
|
||||||
|
stylePromptMessage,
|
||||||
|
stylePromptTitle,
|
||||||
|
} from "../terminal/prompt-style.js";
|
||||||
import { resolveUserPath, sleep } from "../utils.js";
|
import { resolveUserPath, sleep } from "../utils.js";
|
||||||
import { createClackPrompter } from "../wizard/clack-prompter.js";
|
import { createClackPrompter } from "../wizard/clack-prompter.js";
|
||||||
import {
|
import {
|
||||||
@@ -104,6 +109,43 @@ type ConfigureWizardParams = {
|
|||||||
sections?: WizardSection[];
|
sections?: WizardSection[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const intro = (message: string) =>
|
||||||
|
clackIntro(stylePromptTitle(message) ?? message);
|
||||||
|
const outro = (message: string) =>
|
||||||
|
clackOutro(stylePromptTitle(message) ?? message);
|
||||||
|
const note = (message: string, title?: string) =>
|
||||||
|
clackNote(message, stylePromptTitle(title));
|
||||||
|
const text = (params: Parameters<typeof clackText>[0]) =>
|
||||||
|
clackText({
|
||||||
|
...params,
|
||||||
|
message: stylePromptMessage(params.message),
|
||||||
|
});
|
||||||
|
const confirm = (params: Parameters<typeof clackConfirm>[0]) =>
|
||||||
|
clackConfirm({
|
||||||
|
...params,
|
||||||
|
message: stylePromptMessage(params.message),
|
||||||
|
});
|
||||||
|
const select = <T>(params: Parameters<typeof clackSelect<T>>[0]) =>
|
||||||
|
clackSelect({
|
||||||
|
...params,
|
||||||
|
message: stylePromptMessage(params.message),
|
||||||
|
options: params.options.map((opt) =>
|
||||||
|
opt.hint === undefined
|
||||||
|
? opt
|
||||||
|
: { ...opt, hint: stylePromptHint(opt.hint) },
|
||||||
|
),
|
||||||
|
});
|
||||||
|
const multiselect = <T>(params: Parameters<typeof clackMultiselect<T>>[0]) =>
|
||||||
|
clackMultiselect({
|
||||||
|
...params,
|
||||||
|
message: stylePromptMessage(params.message),
|
||||||
|
options: params.options.map((opt) =>
|
||||||
|
opt.hint === undefined
|
||||||
|
? opt
|
||||||
|
: { ...opt, hint: stylePromptHint(opt.hint) },
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
const startOscSpinner = (label: string) => {
|
const startOscSpinner = (label: string) => {
|
||||||
const spin = spinner();
|
const spin = spinner();
|
||||||
spin.start(theme.accent(label));
|
spin.start(theme.accent(label));
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { note } from "@clack/prompts";
|
import { note as clackNote } from "@clack/prompts";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
buildAuthHealthSummary,
|
buildAuthHealthSummary,
|
||||||
@@ -13,8 +13,12 @@ import {
|
|||||||
resolveApiKeyForProfile,
|
resolveApiKeyForProfile,
|
||||||
} from "../agents/auth-profiles.js";
|
} from "../agents/auth-profiles.js";
|
||||||
import type { ClawdbotConfig } from "../config/config.js";
|
import type { ClawdbotConfig } from "../config/config.js";
|
||||||
|
import { stylePromptTitle } from "../terminal/prompt-style.js";
|
||||||
import type { DoctorPrompter } from "./doctor-prompter.js";
|
import type { DoctorPrompter } from "./doctor-prompter.js";
|
||||||
|
|
||||||
|
const note = (message: string, title?: string) =>
|
||||||
|
clackNote(message, stylePromptTitle(title));
|
||||||
|
|
||||||
export async function maybeRepairAnthropicOAuthProfileId(
|
export async function maybeRepairAnthropicOAuthProfileId(
|
||||||
cfg: ClawdbotConfig,
|
cfg: ClawdbotConfig,
|
||||||
prompter: DoctorPrompter,
|
prompter: DoctorPrompter,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
import { note } from "@clack/prompts";
|
import { note as clackNote } from "@clack/prompts";
|
||||||
|
|
||||||
import type { ClawdbotConfig } from "../config/config.js";
|
import type { ClawdbotConfig } from "../config/config.js";
|
||||||
import { resolveGatewayPort, resolveIsNixMode } from "../config/paths.js";
|
import { resolveGatewayPort, resolveIsNixMode } from "../config/paths.js";
|
||||||
@@ -31,6 +31,10 @@ import {
|
|||||||
type GatewayDaemonRuntime,
|
type GatewayDaemonRuntime,
|
||||||
} from "./daemon-runtime.js";
|
} from "./daemon-runtime.js";
|
||||||
import type { DoctorOptions, DoctorPrompter } from "./doctor-prompter.js";
|
import type { DoctorOptions, DoctorPrompter } from "./doctor-prompter.js";
|
||||||
|
import { stylePromptTitle } from "../terminal/prompt-style.js";
|
||||||
|
|
||||||
|
const note = (message: string, title?: string) =>
|
||||||
|
clackNote(message, stylePromptTitle(title));
|
||||||
|
|
||||||
function detectGatewayRuntime(
|
function detectGatewayRuntime(
|
||||||
programArguments: string[] | undefined,
|
programArguments: string[] | undefined,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
import { note } from "@clack/prompts";
|
import { note as clackNote } from "@clack/prompts";
|
||||||
|
|
||||||
import type { ClawdbotConfig } from "../config/config.js";
|
import type { ClawdbotConfig } from "../config/config.js";
|
||||||
import {
|
import {
|
||||||
@@ -12,8 +12,12 @@ import {
|
|||||||
writeConfigFile,
|
writeConfigFile,
|
||||||
} from "../config/config.js";
|
} from "../config/config.js";
|
||||||
import type { RuntimeEnv } from "../runtime.js";
|
import type { RuntimeEnv } from "../runtime.js";
|
||||||
|
import { stylePromptTitle } from "../terminal/prompt-style.js";
|
||||||
import { resolveUserPath } from "../utils.js";
|
import { resolveUserPath } from "../utils.js";
|
||||||
|
|
||||||
|
const note = (message: string, title?: string) =>
|
||||||
|
clackNote(message, stylePromptTitle(title));
|
||||||
|
|
||||||
function resolveLegacyConfigPath(env: NodeJS.ProcessEnv): string {
|
function resolveLegacyConfigPath(env: NodeJS.ProcessEnv): string {
|
||||||
const override = env.CLAWDIS_CONFIG_PATH?.trim();
|
const override = env.CLAWDIS_CONFIG_PATH?.trim();
|
||||||
if (override) return override;
|
if (override) return override;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { confirm, select } from "@clack/prompts";
|
import { confirm, select } from "@clack/prompts";
|
||||||
|
|
||||||
import type { RuntimeEnv } from "../runtime.js";
|
import type { RuntimeEnv } from "../runtime.js";
|
||||||
|
import { stylePromptHint, stylePromptMessage } from "../terminal/prompt-style.js";
|
||||||
import { guardCancel } from "./onboard-helpers.js";
|
import { guardCancel } from "./onboard-helpers.js";
|
||||||
|
|
||||||
export type DoctorOptions = {
|
export type DoctorOptions = {
|
||||||
@@ -42,7 +43,15 @@ export function createDoctorPrompter(params: {
|
|||||||
if (nonInteractive) return false;
|
if (nonInteractive) return false;
|
||||||
if (shouldRepair) return true;
|
if (shouldRepair) return true;
|
||||||
if (!canPrompt) return Boolean(p.initialValue ?? false);
|
if (!canPrompt) return Boolean(p.initialValue ?? false);
|
||||||
return guardCancel(await confirm(p), params.runtime) === true;
|
return (
|
||||||
|
guardCancel(
|
||||||
|
await confirm({
|
||||||
|
...p,
|
||||||
|
message: stylePromptMessage(p.message),
|
||||||
|
}),
|
||||||
|
params.runtime,
|
||||||
|
) === true
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -56,7 +65,15 @@ export function createDoctorPrompter(params: {
|
|||||||
if (shouldRepair && shouldForce) return true;
|
if (shouldRepair && shouldForce) return true;
|
||||||
if (shouldRepair && !shouldForce) return false;
|
if (shouldRepair && !shouldForce) return false;
|
||||||
if (!canPrompt) return Boolean(p.initialValue ?? false);
|
if (!canPrompt) return Boolean(p.initialValue ?? false);
|
||||||
return guardCancel(await confirm(p), params.runtime) === true;
|
return (
|
||||||
|
guardCancel(
|
||||||
|
await confirm({
|
||||||
|
...p,
|
||||||
|
message: stylePromptMessage(p.message),
|
||||||
|
}),
|
||||||
|
params.runtime,
|
||||||
|
) === true
|
||||||
|
);
|
||||||
},
|
},
|
||||||
confirmSkipInNonInteractive: async (p) => {
|
confirmSkipInNonInteractive: async (p) => {
|
||||||
if (nonInteractive) return false;
|
if (nonInteractive) return false;
|
||||||
@@ -65,7 +82,18 @@ export function createDoctorPrompter(params: {
|
|||||||
},
|
},
|
||||||
select: async <T>(p: Parameters<typeof select>[0], fallback: T) => {
|
select: async <T>(p: Parameters<typeof select>[0], fallback: T) => {
|
||||||
if (!canPrompt || shouldRepair) return fallback;
|
if (!canPrompt || shouldRepair) return fallback;
|
||||||
return guardCancel(await select(p), params.runtime) as T;
|
return guardCancel(
|
||||||
|
await select({
|
||||||
|
...p,
|
||||||
|
message: stylePromptMessage(p.message),
|
||||||
|
options: p.options.map((opt) =>
|
||||||
|
opt.hint === undefined
|
||||||
|
? opt
|
||||||
|
: { ...opt, hint: stylePromptHint(opt.hint) },
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
params.runtime,
|
||||||
|
) as T;
|
||||||
},
|
},
|
||||||
shouldRepair,
|
shouldRepair,
|
||||||
shouldForce,
|
shouldForce,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
import { note } from "@clack/prompts";
|
import { note as clackNote } from "@clack/prompts";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_SANDBOX_BROWSER_IMAGE,
|
DEFAULT_SANDBOX_BROWSER_IMAGE,
|
||||||
@@ -12,9 +12,13 @@ import {
|
|||||||
import type { ClawdbotConfig } from "../config/config.js";
|
import type { ClawdbotConfig } from "../config/config.js";
|
||||||
import { runCommandWithTimeout, runExec } from "../process/exec.js";
|
import { runCommandWithTimeout, runExec } from "../process/exec.js";
|
||||||
import type { RuntimeEnv } from "../runtime.js";
|
import type { RuntimeEnv } from "../runtime.js";
|
||||||
|
import { stylePromptTitle } from "../terminal/prompt-style.js";
|
||||||
import { replaceModernName } from "./doctor-legacy-config.js";
|
import { replaceModernName } from "./doctor-legacy-config.js";
|
||||||
import type { DoctorPrompter } from "./doctor-prompter.js";
|
import type { DoctorPrompter } from "./doctor-prompter.js";
|
||||||
|
|
||||||
|
const note = (message: string, title?: string) =>
|
||||||
|
clackNote(message, stylePromptTitle(title));
|
||||||
|
|
||||||
type SandboxScriptInfo = {
|
type SandboxScriptInfo = {
|
||||||
scriptPath: string;
|
scriptPath: string;
|
||||||
cwd: string;
|
cwd: string;
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import { note } from "@clack/prompts";
|
import { note as clackNote } from "@clack/prompts";
|
||||||
|
|
||||||
import type { ClawdbotConfig } from "../config/config.js";
|
import type { ClawdbotConfig } from "../config/config.js";
|
||||||
|
import { stylePromptTitle } from "../terminal/prompt-style.js";
|
||||||
import { readProviderAllowFromStore } from "../pairing/pairing-store.js";
|
import { readProviderAllowFromStore } from "../pairing/pairing-store.js";
|
||||||
import { readTelegramAllowFromStore } from "../telegram/pairing-store.js";
|
import { readTelegramAllowFromStore } from "../telegram/pairing-store.js";
|
||||||
import { resolveTelegramToken } from "../telegram/token.js";
|
import { resolveTelegramToken } from "../telegram/token.js";
|
||||||
import { normalizeE164 } from "../utils.js";
|
import { normalizeE164 } from "../utils.js";
|
||||||
|
|
||||||
|
const note = (message: string, title?: string) =>
|
||||||
|
clackNote(message, stylePromptTitle(title));
|
||||||
|
|
||||||
export async function noteSecurityWarnings(cfg: ClawdbotConfig) {
|
export async function noteSecurityWarnings(cfg: ClawdbotConfig) {
|
||||||
const warnings: string[] = [];
|
const warnings: string[] = [];
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import fs from "node:fs";
|
|||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
|
||||||
import { note } from "@clack/prompts";
|
import { note as clackNote } from "@clack/prompts";
|
||||||
|
|
||||||
import type { ClawdbotConfig } from "../config/config.js";
|
import type { ClawdbotConfig } from "../config/config.js";
|
||||||
import { resolveOAuthDir, resolveStateDir } from "../config/paths.js";
|
import { resolveOAuthDir, resolveStateDir } from "../config/paths.js";
|
||||||
@@ -13,8 +13,12 @@ import {
|
|||||||
resolveSessionTranscriptsDirForAgent,
|
resolveSessionTranscriptsDirForAgent,
|
||||||
resolveStorePath,
|
resolveStorePath,
|
||||||
} from "../config/sessions.js";
|
} from "../config/sessions.js";
|
||||||
|
import { stylePromptTitle } from "../terminal/prompt-style.js";
|
||||||
import { DEFAULT_AGENT_ID, normalizeAgentId } from "../routing/session-key.js";
|
import { DEFAULT_AGENT_ID, normalizeAgentId } from "../routing/session-key.js";
|
||||||
|
|
||||||
|
const note = (message: string, title?: string) =>
|
||||||
|
clackNote(message, stylePromptTitle(title));
|
||||||
|
|
||||||
type DoctorPrompterLike = {
|
type DoctorPrompterLike = {
|
||||||
confirmSkipInNonInteractive: (params: {
|
confirmSkipInNonInteractive: (params: {
|
||||||
message: string;
|
message: string;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { intro, note, outro } from "@clack/prompts";
|
import { intro as clackIntro, note as clackNote, outro as clackOutro } from "@clack/prompts";
|
||||||
import { buildWorkspaceSkillStatus } from "../agents/skills-status.js";
|
import { buildWorkspaceSkillStatus } from "../agents/skills-status.js";
|
||||||
import type { ClawdbotConfig } from "../config/config.js";
|
import type { ClawdbotConfig } from "../config/config.js";
|
||||||
import {
|
import {
|
||||||
@@ -20,6 +20,7 @@ import { formatPortDiagnostics, inspectPortUsage } from "../infra/ports.js";
|
|||||||
import { collectProvidersStatusIssues } from "../infra/providers-status-issues.js";
|
import { collectProvidersStatusIssues } from "../infra/providers-status-issues.js";
|
||||||
import type { RuntimeEnv } from "../runtime.js";
|
import type { RuntimeEnv } from "../runtime.js";
|
||||||
import { defaultRuntime } from "../runtime.js";
|
import { defaultRuntime } from "../runtime.js";
|
||||||
|
import { stylePromptTitle } from "../terminal/prompt-style.js";
|
||||||
import { resolveUserPath, sleep } from "../utils.js";
|
import { resolveUserPath, sleep } from "../utils.js";
|
||||||
import {
|
import {
|
||||||
DEFAULT_GATEWAY_DAEMON_RUNTIME,
|
DEFAULT_GATEWAY_DAEMON_RUNTIME,
|
||||||
@@ -71,6 +72,13 @@ import {
|
|||||||
} from "./onboard-helpers.js";
|
} from "./onboard-helpers.js";
|
||||||
import { ensureSystemdUserLingerInteractive } from "./systemd-linger.js";
|
import { ensureSystemdUserLingerInteractive } from "./systemd-linger.js";
|
||||||
|
|
||||||
|
const intro = (message: string) =>
|
||||||
|
clackIntro(stylePromptTitle(message) ?? message);
|
||||||
|
const outro = (message: string) =>
|
||||||
|
clackOutro(stylePromptTitle(message) ?? message);
|
||||||
|
const note = (message: string, title?: string) =>
|
||||||
|
clackNote(message, stylePromptTitle(title));
|
||||||
|
|
||||||
function resolveMode(cfg: ClawdbotConfig): "local" | "remote" {
|
function resolveMode(cfg: ClawdbotConfig): "local" | "remote" {
|
||||||
return cfg.gateway?.mode === "remote" ? "remote" : "local";
|
return cfg.gateway?.mode === "remote" ? "remote" : "local";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { spawnSync } from "node:child_process";
|
import { spawnSync } from "node:child_process";
|
||||||
|
|
||||||
import { confirm, select, text } from "@clack/prompts";
|
import {
|
||||||
|
confirm as clackConfirm,
|
||||||
|
select as clackSelect,
|
||||||
|
text as clackText,
|
||||||
|
} from "@clack/prompts";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CLAUDE_CLI_PROFILE_ID,
|
CLAUDE_CLI_PROFILE_ID,
|
||||||
@@ -11,9 +15,34 @@ import { normalizeProviderId } from "../../agents/model-selection.js";
|
|||||||
import { parseDurationMs } from "../../cli/parse-duration.js";
|
import { parseDurationMs } from "../../cli/parse-duration.js";
|
||||||
import { CONFIG_PATH_CLAWDBOT } from "../../config/config.js";
|
import { CONFIG_PATH_CLAWDBOT } from "../../config/config.js";
|
||||||
import type { RuntimeEnv } from "../../runtime.js";
|
import type { RuntimeEnv } from "../../runtime.js";
|
||||||
|
import {
|
||||||
|
stylePromptHint,
|
||||||
|
stylePromptMessage,
|
||||||
|
} from "../../terminal/prompt-style.js";
|
||||||
import { applyAuthProfileConfig } from "../onboard-auth.js";
|
import { applyAuthProfileConfig } from "../onboard-auth.js";
|
||||||
import { updateConfig } from "./shared.js";
|
import { updateConfig } from "./shared.js";
|
||||||
|
|
||||||
|
const confirm = (params: Parameters<typeof clackConfirm>[0]) =>
|
||||||
|
clackConfirm({
|
||||||
|
...params,
|
||||||
|
message: stylePromptMessage(params.message),
|
||||||
|
});
|
||||||
|
const text = (params: Parameters<typeof clackText>[0]) =>
|
||||||
|
clackText({
|
||||||
|
...params,
|
||||||
|
message: stylePromptMessage(params.message),
|
||||||
|
});
|
||||||
|
const select = <T>(params: Parameters<typeof clackSelect<T>>[0]) =>
|
||||||
|
clackSelect({
|
||||||
|
...params,
|
||||||
|
message: stylePromptMessage(params.message),
|
||||||
|
options: params.options.map((opt) =>
|
||||||
|
opt.hint === undefined
|
||||||
|
? opt
|
||||||
|
: { ...opt, hint: stylePromptHint(opt.hint) },
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
type TokenProvider = "anthropic";
|
type TokenProvider = "anthropic";
|
||||||
|
|
||||||
function resolveTokenProvider(raw?: string): TokenProvider | "custom" | null {
|
function resolveTokenProvider(raw?: string): TokenProvider | "custom" | null {
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { cancel, isCancel, multiselect } from "@clack/prompts";
|
import {
|
||||||
|
cancel,
|
||||||
|
isCancel,
|
||||||
|
multiselect as clackMultiselect,
|
||||||
|
} from "@clack/prompts";
|
||||||
import { resolveApiKeyForProvider } from "../../agents/model-auth.js";
|
import { resolveApiKeyForProvider } from "../../agents/model-auth.js";
|
||||||
import {
|
import {
|
||||||
type ModelScanResult,
|
type ModelScanResult,
|
||||||
@@ -7,11 +11,27 @@ import {
|
|||||||
import { withProgressTotals } from "../../cli/progress.js";
|
import { withProgressTotals } from "../../cli/progress.js";
|
||||||
import { CONFIG_PATH_CLAWDBOT, loadConfig } from "../../config/config.js";
|
import { CONFIG_PATH_CLAWDBOT, loadConfig } from "../../config/config.js";
|
||||||
import type { RuntimeEnv } from "../../runtime.js";
|
import type { RuntimeEnv } from "../../runtime.js";
|
||||||
|
import {
|
||||||
|
stylePromptHint,
|
||||||
|
stylePromptMessage,
|
||||||
|
stylePromptTitle,
|
||||||
|
} from "../../terminal/prompt-style.js";
|
||||||
import { formatMs, formatTokenK, updateConfig } from "./shared.js";
|
import { formatMs, formatTokenK, updateConfig } from "./shared.js";
|
||||||
|
|
||||||
const MODEL_PAD = 42;
|
const MODEL_PAD = 42;
|
||||||
const CTX_PAD = 8;
|
const CTX_PAD = 8;
|
||||||
|
|
||||||
|
const multiselect = <T>(params: Parameters<typeof clackMultiselect<T>>[0]) =>
|
||||||
|
clackMultiselect({
|
||||||
|
...params,
|
||||||
|
message: stylePromptMessage(params.message),
|
||||||
|
options: params.options.map((opt) =>
|
||||||
|
opt.hint === undefined
|
||||||
|
? opt
|
||||||
|
: { ...opt, hint: stylePromptHint(opt.hint) },
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
const pad = (value: string, size: number) => value.padEnd(size);
|
const pad = (value: string, size: number) => value.padEnd(size);
|
||||||
|
|
||||||
const truncate = (value: string, max: number) => {
|
const truncate = (value: string, max: number) => {
|
||||||
@@ -268,7 +288,7 @@ export async function modelsScanCommand(
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (isCancel(selection)) {
|
if (isCancel(selection)) {
|
||||||
cancel("Model scan cancelled.");
|
cancel(stylePromptTitle("Model scan cancelled.") ?? "Model scan cancelled.");
|
||||||
runtime.exit(0);
|
runtime.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,7 +305,9 @@ export async function modelsScanCommand(
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (isCancel(imageSelection)) {
|
if (isCancel(imageSelection)) {
|
||||||
cancel("Model scan cancelled.");
|
cancel(
|
||||||
|
stylePromptTitle("Model scan cancelled.") ?? "Model scan cancelled.",
|
||||||
|
);
|
||||||
runtime.exit(0);
|
runtime.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { normalizeControlUiBasePath } from "../gateway/control-ui.js";
|
|||||||
import { pickPrimaryTailnetIPv4 } from "../infra/tailnet.js";
|
import { pickPrimaryTailnetIPv4 } from "../infra/tailnet.js";
|
||||||
import { runCommandWithTimeout } from "../process/exec.js";
|
import { runCommandWithTimeout } from "../process/exec.js";
|
||||||
import type { RuntimeEnv } from "../runtime.js";
|
import type { RuntimeEnv } from "../runtime.js";
|
||||||
|
import { stylePromptTitle } from "../terminal/prompt-style.js";
|
||||||
import { CONFIG_DIR, resolveUserPath } from "../utils.js";
|
import { CONFIG_DIR, resolveUserPath } from "../utils.js";
|
||||||
import { VERSION } from "../version.js";
|
import { VERSION } from "../version.js";
|
||||||
import type {
|
import type {
|
||||||
@@ -27,7 +28,7 @@ import type {
|
|||||||
|
|
||||||
export function guardCancel<T>(value: T, runtime: RuntimeEnv): T {
|
export function guardCancel<T>(value: T, runtime: RuntimeEnv): T {
|
||||||
if (isCancel(value)) {
|
if (isCancel(value)) {
|
||||||
cancel("Setup cancelled.");
|
cancel(stylePromptTitle("Setup cancelled.") ?? "Setup cancelled.");
|
||||||
runtime.exit(0);
|
runtime.exit(0);
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import { note } from "@clack/prompts";
|
import { note as clackNote } from "@clack/prompts";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
enableSystemdUserLinger,
|
enableSystemdUserLinger,
|
||||||
readSystemdUserLingerStatus,
|
readSystemdUserLingerStatus,
|
||||||
} from "../daemon/systemd.js";
|
} from "../daemon/systemd.js";
|
||||||
import type { RuntimeEnv } from "../runtime.js";
|
import type { RuntimeEnv } from "../runtime.js";
|
||||||
|
import { stylePromptTitle } from "../terminal/prompt-style.js";
|
||||||
|
|
||||||
|
const note = (message: string, title?: string) =>
|
||||||
|
clackNote(message, stylePromptTitle(title));
|
||||||
|
|
||||||
export type LingerPrompter = {
|
export type LingerPrompter = {
|
||||||
confirm?: (params: {
|
confirm?: (params: {
|
||||||
|
|||||||
12
src/terminal/palette.ts
Normal file
12
src/terminal/palette.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// Lobster palette tokens for CLI/UI theming. "lobster seam" == use this palette.
|
||||||
|
// Keep in sync with docs/cli/index.md (CLI palette section).
|
||||||
|
export const LOBSTER_PALETTE = {
|
||||||
|
accent: "#FF5A2D",
|
||||||
|
accentBright: "#FF7A3D",
|
||||||
|
accentDim: "#D14A22",
|
||||||
|
info: "#FF8A5B",
|
||||||
|
success: "#2FBF71",
|
||||||
|
warn: "#FFB020",
|
||||||
|
error: "#E23D2D",
|
||||||
|
muted: "#8B7F77",
|
||||||
|
} as const;
|
||||||
10
src/terminal/prompt-style.ts
Normal file
10
src/terminal/prompt-style.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { isRich, theme } from "./theme.js";
|
||||||
|
|
||||||
|
export const stylePromptMessage = (message: string): string =>
|
||||||
|
isRich() ? theme.accent(message) : message;
|
||||||
|
|
||||||
|
export const stylePromptTitle = (title?: string): string | undefined =>
|
||||||
|
title && isRich() ? theme.heading(title) : title;
|
||||||
|
|
||||||
|
export const stylePromptHint = (hint?: string): string | undefined =>
|
||||||
|
hint && isRich() ? theme.muted(hint) : hint;
|
||||||
@@ -1,16 +1,6 @@
|
|||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
|
|
||||||
// Semantic palette for CLI output. Keep in sync with docs/cli/index.md.
|
import { LOBSTER_PALETTE } from "./palette.js";
|
||||||
export const LOBSTER_PALETTE = {
|
|
||||||
accent: "#FF5A2D",
|
|
||||||
accentBright: "#FF7A3D",
|
|
||||||
accentDim: "#D14A22",
|
|
||||||
info: "#FF8A5B",
|
|
||||||
success: "#2FBF71",
|
|
||||||
warn: "#FFB020",
|
|
||||||
error: "#E23D2D",
|
|
||||||
muted: "#8B7F77",
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
const hex = (value: string) => chalk.hex(value);
|
const hex = (value: string) => chalk.hex(value);
|
||||||
|
|
||||||
|
|||||||
@@ -13,12 +13,17 @@ import {
|
|||||||
} from "@clack/prompts";
|
} from "@clack/prompts";
|
||||||
import { createCliProgress } from "../cli/progress.js";
|
import { createCliProgress } from "../cli/progress.js";
|
||||||
import { theme } from "../terminal/theme.js";
|
import { theme } from "../terminal/theme.js";
|
||||||
|
import {
|
||||||
|
stylePromptHint,
|
||||||
|
stylePromptMessage,
|
||||||
|
stylePromptTitle,
|
||||||
|
} from "../terminal/prompt-style.js";
|
||||||
import type { WizardProgress, WizardPrompter } from "./prompts.js";
|
import type { WizardProgress, WizardPrompter } from "./prompts.js";
|
||||||
import { WizardCancelledError } from "./prompts.js";
|
import { WizardCancelledError } from "./prompts.js";
|
||||||
|
|
||||||
function guardCancel<T>(value: T | symbol): T {
|
function guardCancel<T>(value: T | symbol): T {
|
||||||
if (isCancel(value)) {
|
if (isCancel(value)) {
|
||||||
cancel("Setup cancelled.");
|
cancel(stylePromptTitle("Setup cancelled.") ?? "Setup cancelled.");
|
||||||
throw new WizardCancelledError();
|
throw new WizardCancelledError();
|
||||||
}
|
}
|
||||||
return value as T;
|
return value as T;
|
||||||
@@ -27,21 +32,23 @@ function guardCancel<T>(value: T | symbol): T {
|
|||||||
export function createClackPrompter(): WizardPrompter {
|
export function createClackPrompter(): WizardPrompter {
|
||||||
return {
|
return {
|
||||||
intro: async (title) => {
|
intro: async (title) => {
|
||||||
intro(title);
|
intro(stylePromptTitle(title) ?? title);
|
||||||
},
|
},
|
||||||
outro: async (message) => {
|
outro: async (message) => {
|
||||||
outro(message);
|
outro(stylePromptTitle(message) ?? message);
|
||||||
},
|
},
|
||||||
note: async (message, title) => {
|
note: async (message, title) => {
|
||||||
note(message, title);
|
note(message, stylePromptTitle(title));
|
||||||
},
|
},
|
||||||
select: async (params) =>
|
select: async (params) =>
|
||||||
guardCancel(
|
guardCancel(
|
||||||
await select({
|
await select({
|
||||||
message: params.message,
|
message: stylePromptMessage(params.message),
|
||||||
options: params.options.map((opt) => {
|
options: params.options.map((opt) => {
|
||||||
const base = { value: opt.value, label: opt.label };
|
const base = { value: opt.value, label: opt.label };
|
||||||
return opt.hint === undefined ? base : { ...base, hint: opt.hint };
|
return opt.hint === undefined
|
||||||
|
? base
|
||||||
|
: { ...base, hint: stylePromptHint(opt.hint) };
|
||||||
}) as Option<(typeof params.options)[number]["value"]>[],
|
}) as Option<(typeof params.options)[number]["value"]>[],
|
||||||
initialValue: params.initialValue,
|
initialValue: params.initialValue,
|
||||||
}),
|
}),
|
||||||
@@ -49,10 +56,12 @@ export function createClackPrompter(): WizardPrompter {
|
|||||||
multiselect: async (params) =>
|
multiselect: async (params) =>
|
||||||
guardCancel(
|
guardCancel(
|
||||||
await multiselect({
|
await multiselect({
|
||||||
message: params.message,
|
message: stylePromptMessage(params.message),
|
||||||
options: params.options.map((opt) => {
|
options: params.options.map((opt) => {
|
||||||
const base = { value: opt.value, label: opt.label };
|
const base = { value: opt.value, label: opt.label };
|
||||||
return opt.hint === undefined ? base : { ...base, hint: opt.hint };
|
return opt.hint === undefined
|
||||||
|
? base
|
||||||
|
: { ...base, hint: stylePromptHint(opt.hint) };
|
||||||
}) as Option<(typeof params.options)[number]["value"]>[],
|
}) as Option<(typeof params.options)[number]["value"]>[],
|
||||||
initialValues: params.initialValues,
|
initialValues: params.initialValues,
|
||||||
}),
|
}),
|
||||||
@@ -60,7 +69,7 @@ export function createClackPrompter(): WizardPrompter {
|
|||||||
text: async (params) =>
|
text: async (params) =>
|
||||||
guardCancel(
|
guardCancel(
|
||||||
await text({
|
await text({
|
||||||
message: params.message,
|
message: stylePromptMessage(params.message),
|
||||||
initialValue: params.initialValue,
|
initialValue: params.initialValue,
|
||||||
placeholder: params.placeholder,
|
placeholder: params.placeholder,
|
||||||
validate: params.validate,
|
validate: params.validate,
|
||||||
@@ -69,7 +78,7 @@ export function createClackPrompter(): WizardPrompter {
|
|||||||
confirm: async (params) =>
|
confirm: async (params) =>
|
||||||
guardCancel(
|
guardCancel(
|
||||||
await confirm({
|
await confirm({
|
||||||
message: params.message,
|
message: stylePromptMessage(params.message),
|
||||||
initialValue: params.initialValue,
|
initialValue: params.initialValue,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user