fix: wrap clack notes for cleaner boxes

This commit is contained in:
Peter Steinberger
2026-01-11 04:23:33 +01:00
parent 76c5bff7d6
commit 29884f8d6f
11 changed files with 105 additions and 50 deletions

View File

@@ -4,7 +4,6 @@ import {
confirm as clackConfirm, confirm as clackConfirm,
intro as clackIntro, intro as clackIntro,
multiselect as clackMultiselect, multiselect as clackMultiselect,
note as clackNote,
outro as clackOutro, outro as clackOutro,
select as clackSelect, select as clackSelect,
text as clackText, text as clackText,
@@ -26,6 +25,7 @@ import { ensureControlUiAssetsBuilt } from "../infra/control-ui-assets.js";
import { listChatProviders } from "../providers/registry.js"; import { listChatProviders } from "../providers/registry.js";
import type { RuntimeEnv } from "../runtime.js"; import type { RuntimeEnv } from "../runtime.js";
import { defaultRuntime } from "../runtime.js"; import { defaultRuntime } from "../runtime.js";
import { note } from "../terminal/note.js";
import { import {
stylePromptHint, stylePromptHint,
stylePromptMessage, stylePromptMessage,
@@ -90,8 +90,6 @@ const intro = (message: string) =>
clackIntro(stylePromptTitle(message) ?? message); clackIntro(stylePromptTitle(message) ?? message);
const outro = (message: string) => const outro = (message: string) =>
clackOutro(stylePromptTitle(message) ?? message); clackOutro(stylePromptTitle(message) ?? message);
const note = (message: string, title?: string) =>
clackNote(message, stylePromptTitle(title));
const text = (params: Parameters<typeof clackText>[0]) => const text = (params: Parameters<typeof clackText>[0]) =>
clackText({ clackText({
...params, ...params,

View File

@@ -1,5 +1,3 @@
import { note as clackNote } from "@clack/prompts";
import { import {
buildAuthHealthSummary, buildAuthHealthSummary,
DEFAULT_OAUTH_WARN_MS, DEFAULT_OAUTH_WARN_MS,
@@ -14,12 +12,9 @@ import {
resolveProfileUnusableUntilForDisplay, resolveProfileUnusableUntilForDisplay,
} 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 { note } from "../terminal/note.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,

View File

@@ -1,7 +1,5 @@
import path from "node:path"; import path from "node:path";
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";
import { resolveGatewayLaunchAgentLabel } from "../daemon/constants.js"; import { resolveGatewayLaunchAgentLabel } from "../daemon/constants.js";
@@ -26,7 +24,7 @@ import {
} from "../daemon/service-audit.js"; } from "../daemon/service-audit.js";
import { buildServiceEnvironment } from "../daemon/service-env.js"; import { buildServiceEnvironment } from "../daemon/service-env.js";
import type { RuntimeEnv } from "../runtime.js"; import type { RuntimeEnv } from "../runtime.js";
import { stylePromptTitle } from "../terminal/prompt-style.js"; import { note } from "../terminal/note.js";
import { import {
DEFAULT_GATEWAY_DAEMON_RUNTIME, DEFAULT_GATEWAY_DAEMON_RUNTIME,
GATEWAY_DAEMON_RUNTIME_OPTIONS, GATEWAY_DAEMON_RUNTIME_OPTIONS,
@@ -34,9 +32,6 @@ import {
} from "./daemon-runtime.js"; } from "./daemon-runtime.js";
import type { DoctorOptions, DoctorPrompter } from "./doctor-prompter.js"; import type { DoctorOptions, DoctorPrompter } from "./doctor-prompter.js";
const note = (message: string, title?: string) =>
clackNote(message, stylePromptTitle(title));
function detectGatewayRuntime( function detectGatewayRuntime(
programArguments: string[] | undefined, programArguments: string[] | undefined,
): GatewayDaemonRuntime { ): GatewayDaemonRuntime {

View File

@@ -1,8 +1,6 @@
import os from "node:os"; import os from "node:os";
import path from "node:path"; import path from "node:path";
import { note as clackNote } from "@clack/prompts";
import type { ClawdbotConfig } from "../config/config.js"; import type { ClawdbotConfig } from "../config/config.js";
import { import {
CONFIG_PATH_CLAWDBOT, CONFIG_PATH_CLAWDBOT,
@@ -12,12 +10,9 @@ 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 { note } from "../terminal/note.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;

View File

@@ -1,8 +1,6 @@
import fs from "node:fs"; import fs from "node:fs";
import path from "node:path"; import path from "node:path";
import { note as clackNote } from "@clack/prompts";
import { import {
DEFAULT_SANDBOX_BROWSER_IMAGE, DEFAULT_SANDBOX_BROWSER_IMAGE,
DEFAULT_SANDBOX_COMMON_IMAGE, DEFAULT_SANDBOX_COMMON_IMAGE,
@@ -12,13 +10,10 @@ 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 { note } from "../terminal/note.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;

View File

@@ -1,15 +1,10 @@
import { note as clackNote } from "@clack/prompts";
import type { ClawdbotConfig } from "../config/config.js"; import type { ClawdbotConfig } from "../config/config.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 { stylePromptTitle } from "../terminal/prompt-style.js"; import { note } from "../terminal/note.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[] = [];

View File

@@ -2,8 +2,6 @@ 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 as clackNote } from "@clack/prompts";
import { resolveDefaultAgentId } from "../agents/agent-scope.js"; import { resolveDefaultAgentId } from "../agents/agent-scope.js";
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";
@@ -14,10 +12,7 @@ import {
resolveSessionTranscriptsDirForAgent, resolveSessionTranscriptsDirForAgent,
resolveStorePath, resolveStorePath,
} from "../config/sessions.js"; } from "../config/sessions.js";
import { stylePromptTitle } from "../terminal/prompt-style.js"; import { note } from "../terminal/note.js";
const note = (message: string, title?: string) =>
clackNote(message, stylePromptTitle(title));
type DoctorPrompterLike = { type DoctorPrompterLike = {
confirmSkipInNonInteractive: (params: { confirmSkipInNonInteractive: (params: {

View File

@@ -3,7 +3,6 @@ import os from "node:os";
import path from "node:path"; import path from "node:path";
import { import {
intro as clackIntro, intro as clackIntro,
note as clackNote,
outro as clackOutro, outro as clackOutro,
} from "@clack/prompts"; } from "@clack/prompts";
import { import {
@@ -41,6 +40,7 @@ import { runCommandWithTimeout } from "../process/exec.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 { stylePromptTitle } from "../terminal/prompt-style.js";
import { note } from "../terminal/note.js";
import { sleep } from "../utils.js"; import { sleep } from "../utils.js";
import { import {
DEFAULT_GATEWAY_DAEMON_RUNTIME, DEFAULT_GATEWAY_DAEMON_RUNTIME,
@@ -97,8 +97,6 @@ const intro = (message: string) =>
clackIntro(stylePromptTitle(message) ?? message); clackIntro(stylePromptTitle(message) ?? message);
const outro = (message: string) => const outro = (message: string) =>
clackOutro(stylePromptTitle(message) ?? message); 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";

View File

@@ -1,15 +1,10 @@
import { note as clackNote } from "@clack/prompts";
import { import {
enableSystemdUserLinger, enableSystemdUserLinger,
isSystemdUserServiceAvailable, isSystemdUserServiceAvailable,
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"; import { note } from "../terminal/note.js";
const note = (message: string, title?: string) =>
clackNote(message, stylePromptTitle(title));
export type LingerPrompter = { export type LingerPrompter = {
confirm?: (params: { confirm?: (params: {

94
src/terminal/note.ts Normal file
View File

@@ -0,0 +1,94 @@
import { note as clackNote } from "@clack/prompts";
import { stylePromptTitle } from "./prompt-style.js";
const ANSI_ESCAPE = /\u001b\[[0-9;]*m/g;
function visibleLength(value: string): number {
return Array.from(value.replace(ANSI_ESCAPE, "")).length;
}
function splitLongWord(word: string, maxLen: number): string[] {
if (maxLen <= 0) return [word];
const chars = Array.from(word);
const parts: string[] = [];
for (let i = 0; i < chars.length; i += maxLen) {
parts.push(chars.slice(i, i + maxLen).join(""));
}
return parts.length > 0 ? parts : [word];
}
function wrapLine(line: string, maxWidth: number): string[] {
if (line.trim().length === 0) return [line];
const match = line.match(/^(\s*)([-*\u2022]\s+)?(.*)$/);
const indent = match?.[1] ?? "";
const bullet = match?.[2] ?? "";
const content = match?.[3] ?? "";
const firstPrefix = `${indent}${bullet}`;
const nextPrefix = `${indent}${bullet ? " ".repeat(bullet.length) : ""}`;
const firstWidth = Math.max(10, maxWidth - visibleLength(firstPrefix));
const nextWidth = Math.max(10, maxWidth - visibleLength(nextPrefix));
const words = content.split(/\s+/).filter(Boolean);
const lines: string[] = [];
let current = "";
let prefix = firstPrefix;
let available = firstWidth;
for (const word of words) {
if (!current) {
if (visibleLength(word) > available) {
const parts = splitLongWord(word, available);
const first = parts.shift() ?? "";
lines.push(prefix + first);
prefix = nextPrefix;
available = nextWidth;
for (const part of parts) lines.push(prefix + part);
continue;
}
current = word;
continue;
}
const candidate = `${current} ${word}`;
if (visibleLength(candidate) <= available) {
current = candidate;
continue;
}
lines.push(prefix + current);
prefix = nextPrefix;
available = nextWidth;
if (visibleLength(word) > available) {
const parts = splitLongWord(word, available);
const first = parts.shift() ?? "";
lines.push(prefix + first);
for (const part of parts) lines.push(prefix + part);
current = "";
continue;
}
current = word;
}
if (current || words.length === 0) {
lines.push(prefix + current);
}
return lines;
}
export function wrapNoteMessage(
message: string,
options: { maxWidth?: number; columns?: number } = {},
): string {
const columns = options.columns ?? process.stdout.columns ?? 80;
const maxWidth = options.maxWidth ?? Math.max(40, Math.min(88, columns - 10));
return message
.split("\n")
.flatMap((line) => wrapLine(line, maxWidth))
.join("\n");
}
export function note(message: string, title?: string) {
clackNote(wrapNoteMessage(message), stylePromptTitle(title));
}

View File

@@ -4,7 +4,6 @@ import {
intro, intro,
isCancel, isCancel,
multiselect, multiselect,
note,
type Option, type Option,
outro, outro,
select, select,
@@ -18,6 +17,7 @@ import {
stylePromptTitle, stylePromptTitle,
} from "../terminal/prompt-style.js"; } from "../terminal/prompt-style.js";
import { theme } from "../terminal/theme.js"; import { theme } from "../terminal/theme.js";
import { note as emitNote } from "../terminal/note.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";
@@ -38,7 +38,7 @@ export function createClackPrompter(): WizardPrompter {
outro(stylePromptTitle(message) ?? message); outro(stylePromptTitle(message) ?? message);
}, },
note: async (message, title) => { note: async (message, title) => {
note(message, stylePromptTitle(title)); emitNote(message, title);
}, },
select: async (params) => select: async (params) =>
guardCancel( guardCancel(