diff --git a/packages/terminal-core/src/decorative-emoji.ts b/packages/terminal-core/src/decorative-emoji.ts index 946d09359e3..9dc29bf826b 100644 --- a/packages/terminal-core/src/decorative-emoji.ts +++ b/packages/terminal-core/src/decorative-emoji.ts @@ -1,5 +1,8 @@ import { splitGraphemes } from "./ansi.js"; +// Decorative emoji helpers that degrade cleanly on terminals without reliable emoji support. + +/** Environment and terminal facts used to decide decorative emoji support. */ export type DecorativeEmojiOptions = { env?: NodeJS.ProcessEnv; isTty?: boolean; @@ -9,6 +12,7 @@ export type DecorativeEmojiOptions = { const EMOJI_GRAPHEME_PATTERN = /[\p{Extended_Pictographic}\p{Regional_Indicator}\u20e3]/u; +/** Detect terminals with known emoji rendering support. */ function isKnownEmojiTerminal(env: NodeJS.ProcessEnv): boolean { const termProgram = (env.TERM_PROGRAM ?? "").toLowerCase(); const term = (env.TERM ?? "").toLowerCase(); @@ -24,6 +28,7 @@ function isKnownEmojiTerminal(env: NodeJS.ProcessEnv): boolean { ); } +/** Return true when locale variables indicate UTF-8 output support. */ function hasUtf8Locale(env: NodeJS.ProcessEnv): boolean { const locale = [env.LC_ALL, env.LC_CTYPE, env.LANG].find( (value) => typeof value === "string" && value.trim().length > 0, @@ -34,6 +39,7 @@ function hasUtf8Locale(env: NodeJS.ProcessEnv): boolean { return /utf-?8/i.test(locale); } +/** Return true when decorative emoji should be emitted for the target terminal. */ export function supportsDecorativeEmoji(options: DecorativeEmojiOptions = {}): boolean { const env = options.env ?? process.env; const platform = options.platform ?? process.platform; @@ -57,10 +63,12 @@ export function supportsDecorativeEmoji(options: DecorativeEmojiOptions = {}): b return false; } +/** Return the emoji only when decorative emoji output is supported. */ export function decorativeEmoji(emoji: string, options: DecorativeEmojiOptions = {}): string { return supportsDecorativeEmoji(options) ? emoji : ""; } +/** Prefix text with a decorative emoji when supported. */ export function decorativePrefix( emoji: string, text: string, @@ -70,6 +78,7 @@ export function decorativePrefix( return prefix ? `${prefix} ${text}` : text; } +/** Strip decorative emoji for terminals that should not receive them. */ export function stripDecorativeEmojiForTerminal( text: string, options: DecorativeEmojiOptions = {}, diff --git a/packages/terminal-core/src/display-string.ts b/packages/terminal-core/src/display-string.ts index a900492b422..7761470665a 100644 --- a/packages/terminal-core/src/display-string.ts +++ b/packages/terminal-core/src/display-string.ts @@ -1,11 +1,15 @@ import os from "node:os"; import path from "node:path"; +// Display-safe string helpers for shortening user home paths. + +/** Normalize env/home values and reject shell placeholder strings. */ function normalize(value: string | undefined): string | undefined { const trimmed = value?.trim(); return trimmed && trimmed !== "undefined" && trimmed !== "null" ? trimmed : undefined; } +/** Run a home resolver defensively because some runtimes throw for missing passwd data. */ function normalizeSafe(fn: () => string | undefined): string | undefined { try { return normalize(fn()); @@ -14,6 +18,7 @@ function normalizeSafe(fn: () => string | undefined): string | undefined { } } +/** Resolve Termux home from its Android prefix layout. */ function resolveTermuxHome(env: NodeJS.ProcessEnv): string | undefined { const prefix = normalize(env.PREFIX); if (!prefix || !normalize(env.ANDROID_DATA)) { @@ -25,6 +30,7 @@ function resolveTermuxHome(env: NodeJS.ProcessEnv): string | undefined { return path.resolve(prefix, "..", "home"); } +/** Resolve the underlying OS home before applying OpenClaw overrides. */ function resolveRawOsHomeDir(env: NodeJS.ProcessEnv, homedir: () => string): string | undefined { return ( normalize(env.HOME) ?? @@ -34,6 +40,7 @@ function resolveRawOsHomeDir(env: NodeJS.ProcessEnv, homedir: () => string): str ); } +/** Resolve raw home with OPENCLAW_HOME tilde expansion. */ function resolveRawHomeDir( env: NodeJS.ProcessEnv = process.env, homedir: () => string = os.homedir, @@ -46,6 +53,7 @@ function resolveRawHomeDir( return resolveRawOsHomeDir(env, homedir); } +/** Resolve the effective absolute home directory for display replacement. */ function resolveEffectiveHomeDir( env: NodeJS.ProcessEnv = process.env, homedir: () => string = os.homedir, @@ -54,6 +62,7 @@ function resolveEffectiveHomeDir( return raw ? path.resolve(raw) : undefined; } +/** Resolve the display prefix that should replace the effective home path. */ function resolveHomeDisplayPrefix(): { home: string; prefix: string } | undefined { const home = resolveEffectiveHomeDir(); if (!home) { @@ -63,6 +72,7 @@ function resolveHomeDisplayPrefix(): { home: string; prefix: string } | undefine return explicitHome ? { home, prefix: "$OPENCLAW_HOME" } : { home, prefix: "~" }; } +/** Replace the effective home path with "~" or "$OPENCLAW_HOME" for terminal display. */ export function displayString(input: string): string { if (!input) { return input; diff --git a/packages/terminal-core/src/health-style.ts b/packages/terminal-core/src/health-style.ts index 9c04b6ce0af..f710cc443e3 100644 --- a/packages/terminal-core/src/health-style.ts +++ b/packages/terminal-core/src/health-style.ts @@ -1,6 +1,9 @@ import { normalizeLowercaseStringOrEmpty } from "./string.js"; import { theme } from "./theme.js"; +// Styles the status word in health output lines. + +/** Highlight known health status prefixes in a "label: detail" line. */ export function styleHealthChannelLine(line: string, rich: boolean): string { if (!rich) { return line; diff --git a/packages/terminal-core/src/index.ts b/packages/terminal-core/src/index.ts index f9447bedf3b..bb2d773cdd2 100644 --- a/packages/terminal-core/src/index.ts +++ b/packages/terminal-core/src/index.ts @@ -1,3 +1,5 @@ +// Public barrel for shared terminal formatting helpers. + export * from "./ansi.js"; export * from "./decorative-emoji.js"; export * from "./health-style.js"; diff --git a/packages/terminal-core/src/osc-progress.ts b/packages/terminal-core/src/osc-progress.ts index 94b49c0346e..16ca777ab1d 100644 --- a/packages/terminal-core/src/osc-progress.ts +++ b/packages/terminal-core/src/osc-progress.ts @@ -1,14 +1,18 @@ +// OSC 9;4 progress reporting for terminals that support shell integration progress. + const OSC_PROGRESS_PREFIX = "\u001b]9;4;"; const OSC_PROGRESS_ST = "\u001b\\"; const OSC_PROGRESS_BEL = "\u0007"; const OSC_PROGRESS_C1_ST = "\u009c"; +/** Controller for terminal progress state. */ export type OscProgressController = { setIndeterminate: (label: string) => void; setPercent: (label: string, percent: number) => void; clear: () => void; }; +/** Return true when the terminal is known to support OSC progress messages. */ export function supportsOscProgress(env: NodeJS.ProcessEnv, isTty: boolean): boolean { if (!isTty) { return false; @@ -19,6 +23,7 @@ export function supportsOscProgress(env: NodeJS.ProcessEnv, isTty: boolean): boo ); } +/** Remove OSC terminators and escape introducers from progress labels. */ function sanitizeOscProgressLabel(label: string): string { return label .replaceAll(OSC_PROGRESS_ST, "") @@ -30,6 +35,7 @@ function sanitizeOscProgressLabel(label: string): string { .trim(); } +/** Format one OSC progress control sequence. */ function formatOscProgress(state: number, percent: number | null, label: string): string { const cleanLabel = sanitizeOscProgressLabel(label); if (percent === null) { @@ -39,6 +45,7 @@ function formatOscProgress(state: number, percent: number | null, label: string) return `${OSC_PROGRESS_PREFIX}${state};${normalizedPercent};${cleanLabel}${OSC_PROGRESS_ST}`; } +/** Create a progress controller, returning no-op methods on unsupported terminals. */ export function createOscProgressController(params: { env: NodeJS.ProcessEnv; isTty: boolean; diff --git a/packages/terminal-core/src/progress-line.ts b/packages/terminal-core/src/progress-line.ts index 818c6371476..a23a7021987 100644 --- a/packages/terminal-core/src/progress-line.ts +++ b/packages/terminal-core/src/progress-line.ts @@ -1,5 +1,8 @@ +// Tracks the active terminal progress line so callers can clear it before other output. + let activeStream: NodeJS.WriteStream | null = null; +/** Register the stream that currently owns an inline progress line. */ export function registerActiveProgressLine(stream: NodeJS.WriteStream): void { if (!stream.isTTY) { return; @@ -7,6 +10,7 @@ export function registerActiveProgressLine(stream: NodeJS.WriteStream): void { activeStream = stream; } +/** Clear the active progress line when it is attached to a TTY stream. */ export function clearActiveProgressLine(): void { if (!activeStream?.isTTY) { return; @@ -14,6 +18,7 @@ export function clearActiveProgressLine(): void { activeStream.write("\r\x1b[2K"); } +/** Unregister the active progress line, optionally only for a matching stream. */ export function unregisterActiveProgressLine(stream?: NodeJS.WriteStream): void { if (!activeStream) { return; diff --git a/packages/terminal-core/src/prompt-select-styled-params.ts b/packages/terminal-core/src/prompt-select-styled-params.ts index 34e01bc93b2..17b8733a817 100644 --- a/packages/terminal-core/src/prompt-select-styled-params.ts +++ b/packages/terminal-core/src/prompt-select-styled-params.ts @@ -1,20 +1,26 @@ import { stylePromptHint, stylePromptMessage } from "./prompt-style.js"; +// Pure prompt parameter styler used by interactive prompts and tests. + +/** Minimal select-like params accepted by the prompt styler. */ type SelectParamsLike = { message: string; options: readonly object[]; }; +/** Styling callbacks for prompt messages and hints. */ type PromptSelectStylers = { message: (value: string) => string; hint: (value: string) => string | undefined; }; +/** Default terminal stylers for select prompts. */ const defaultStylers: PromptSelectStylers = { message: stylePromptMessage, hint: stylePromptHint, }; +/** Return select params with styled prompt message and per-option hints. */ export function styleSelectParams( params: TParams, stylers: PromptSelectStylers = defaultStylers, diff --git a/packages/terminal-core/src/prompt-select-styled.ts b/packages/terminal-core/src/prompt-select-styled.ts index 7af07935f8f..0bdca8d1cf3 100644 --- a/packages/terminal-core/src/prompt-select-styled.ts +++ b/packages/terminal-core/src/prompt-select-styled.ts @@ -1,6 +1,9 @@ import { select } from "@clack/prompts"; import { styleSelectParams } from "./prompt-select-styled-params.js"; +// Clack select wrapper that applies OpenClaw prompt styling. + +/** Run a clack select prompt with styled message and hints. */ export function selectStyled(params: Parameters>[0]) { return select(styleSelectParams(params)); } diff --git a/packages/terminal-core/src/prompt-style.ts b/packages/terminal-core/src/prompt-style.ts index c33083c3157..ae82570030e 100644 --- a/packages/terminal-core/src/prompt-style.ts +++ b/packages/terminal-core/src/prompt-style.ts @@ -1,10 +1,15 @@ import { isRich, theme } from "./theme.js"; +// Shared styling helpers for interactive prompt copy. + +/** Style a prompt message when rich terminal output is active. */ export const stylePromptMessage = (message: string): string => isRich() ? theme.accent(message) : message; +/** Style a prompt title when rich terminal output is active. */ export const stylePromptTitle = (title?: string): string | undefined => title && isRich() ? theme.heading(title) : title; +/** Style a prompt hint when rich terminal output is active. */ export const stylePromptHint = (hint?: string): string | undefined => hint && isRich() ? theme.muted(hint) : hint; diff --git a/packages/terminal-core/src/stream-writer.ts b/packages/terminal-core/src/stream-writer.ts index 2c7ab21786c..e1d156b0427 100644 --- a/packages/terminal-core/src/stream-writer.ts +++ b/packages/terminal-core/src/stream-writer.ts @@ -1,8 +1,12 @@ +// Safe terminal stream writer that treats broken pipes as closed output. + +/** Hooks for safe stream writes. */ export type SafeStreamWriterOptions = { beforeWrite?: () => void; onBrokenPipe?: (err: NodeJS.ErrnoException, stream: NodeJS.WriteStream) => void; }; +/** Writer facade that tracks closed/broken-pipe state. */ export type SafeStreamWriter = { write: (stream: NodeJS.WriteStream, text: string) => boolean; writeLine: (stream: NodeJS.WriteStream, text: string) => boolean; @@ -10,11 +14,13 @@ export type SafeStreamWriter = { isClosed: () => boolean; }; +/** Detect broken pipe style stream errors. */ function isBrokenPipeError(err: unknown): err is NodeJS.ErrnoException { const code = (err as NodeJS.ErrnoException)?.code; return code === "EPIPE" || code === "EIO"; } +/** Create a stream writer that stops writing after EPIPE/EIO. */ export function createSafeStreamWriter(options: SafeStreamWriterOptions = {}): SafeStreamWriter { let closed = false; let notified = false; diff --git a/packages/terminal-core/src/string.ts b/packages/terminal-core/src/string.ts index ef5d2d75397..53d7f5ff646 100644 --- a/packages/terminal-core/src/string.ts +++ b/packages/terminal-core/src/string.ts @@ -1,3 +1,6 @@ +// Shared terminal string normalization helpers. + +/** Normalize string input to lowercase, returning empty string for non-strings. */ export function normalizeLowercaseStringOrEmpty(value: unknown): string { if (typeof value !== "string") { return ""; diff --git a/packages/terminal-core/src/terminal-link.ts b/packages/terminal-core/src/terminal-link.ts index 7ba1a970844..e42894385c3 100644 --- a/packages/terminal-core/src/terminal-link.ts +++ b/packages/terminal-core/src/terminal-link.ts @@ -1,3 +1,6 @@ +// OSC 8 terminal hyperlink formatting with plain-text fallback. + +/** Format a clickable terminal link when supported, otherwise return a readable fallback. */ export function formatTerminalLink( label: string, url: string, diff --git a/packages/terminal-core/src/theme.ts b/packages/terminal-core/src/theme.ts index da69db9e4d0..7b18f43a948 100644 --- a/packages/terminal-core/src/theme.ts +++ b/packages/terminal-core/src/theme.ts @@ -1,6 +1,8 @@ import chalk, { Chalk } from "chalk"; import { LOBSTER_PALETTE } from "./palette.js"; +// Shared terminal color theme that respects NO_COLOR and FORCE_COLOR. + const hasForceColor = typeof process.env.FORCE_COLOR === "string" && process.env.FORCE_COLOR.trim().length > 0 && @@ -10,6 +12,7 @@ const baseChalk = process.env.NO_COLOR && !hasForceColor ? new Chalk({ level: 0 const hex = (value: string) => baseChalk.hex(value); +/** Shared terminal theme color functions. */ export const theme = { accent: hex(LOBSTER_PALETTE.accent), accentBright: hex(LOBSTER_PALETTE.accentBright), @@ -24,7 +27,9 @@ export const theme = { option: hex(LOBSTER_PALETTE.warn), } as const; +/** Return true when color styling is active. */ export const isRich = () => baseChalk.level > 0; +/** Conditionally apply a color function based on caller rich-output state. */ export const colorize = (rich: boolean, color: (value: string) => string, value: string) => rich ? color(value) : value;