/** * Status-bar composition for the TUI footer. Codex/Claude-Code-style split * line: identity facts and session figures flow from the left, while the * permission badge (the Codex "autonomous selection" anchor, with its * shift+tab cycle hint) pins to the right edge. Every segment carries a tone * the footer maps to a theme color, and layoutStatusBar degrades the line * item by item so it always fits one physical row — truncation with an * ellipsis happens only after every lesser group has already dropped out. * * @module @deepseek-ai/dsh-tui/render/status */ import type { TranscriptStats } from './projection.ts'; import { formatTokens } from './text.ts'; export { formatTokens }; /** * Compact duration: 45.2s under a minute, 2m42s from there on. * @param ms - duration in milliseconds. * @returns display string. */ export declare function formatDuration(ms: number): string; /** * Cache-hit share of billed prompt-side input. The denominator is the same * billed total the /usage panel shows (uncached input plus both cache * buckets), so the two readouts can never disagree. * @param usage - cumulative token totals. * @returns percent rounded to one decimal place, or null when no input was billed. */ export declare function cacheHitPercent(usage: TranscriptStats['usage']): number | null; /** * Presentation tones for status spans; the footer maps each to a theme color * (Codex status-line accents: model/path/branch/state/usage categories). */ export type StatusTone = 'model' | 'live' | 'path' | 'branch' | 'value' | 'label' | 'meta' | 'accent' | 'success' | 'plan' | 'warn' | 'error' | 'ctxFill'; /** One colored run inside the status bar. */ export interface StatusSpan { text: string; tone: StatusTone; } /** * One pipe-separated cluster on the leading side of the bar. Spans are the * full visual sequence: junction separators ride along as their own dim * 'label'-tone spans, so joining is a flat concat with no implicit glue. */ export interface StatusGroup { spans: readonly StatusSpan[]; } /** One physical row of the footer: leading clusters and trailing badges. */ export interface StatusRow { /** Leading clusters, pipe-separated in display order; index 0 is identity. */ left: readonly StatusGroup[]; /** Trailing spans pinned to the right edge, dot-separated in display order. */ right: readonly StatusSpan[]; /** Whether the shift+tab cycle hint rides after the permission badge. */ hint: boolean; } /** * The footer layout: two stacked physical rows. Row 1 keeps the primary * controls in model, cwd, mode, branch, context, permission order. Row 2 * carries every secondary session/run figure and degrades independently. */ export interface StatusLayout { row1: StatusRow; row2: StatusRow; } /** Separator between leading clusters. */ export declare const STATUS_GROUP_SEPARATOR = " | "; /** Separator between trailing state spans. */ export declare const STATUS_ITEM_SEPARATOR = " \u00B7 "; /** The Codex-style mode cycle hint appended to the permission badge. */ /** English compatibility value for callers that only measure the default layout. */ export declare const STATUS_CYCLE_HINT = " (shift+tab to cycle)"; /** Localized mode-cycle hint used by the live layout. */ export declare function statusCycleHint(): string; /** * Interior columns of the context bar. The layout starts every bar at this * width so the drop ladder can pre-measure the group, then degrades the * readout and shrinks the bar inside a tighter budget before dropping the * group (see CONTEXT_MIN_WIDTH) rather than asking the layout for more room. */ export declare const CONTEXT_BAR_WIDTH = 24; /** * Render context occupancy as ONE stepless proportional bar: a solid * DeepSeek-blue fill run tracking the occupancy and a dim dotted free * track for the rest. Nothing else lives inside the bar — the usage * readout rides outside it (see contextGroupSpans) — so the geometry * always reads as the true remaining share. A given occupancy always * renders the identical bar. * @param usedTokens - reported used tokens. * @param contextWindow - route capacity. * @param width - total bar columns. * @returns tone-split spans for the footer to paint. */ export declare function contextBar(usedTokens: number, contextWindow: number, width: number): readonly StatusSpan[]; /** How much usage detail the context group's readout carries. */ export type ContextReadoutMode = 'full' | 'percent' | 'none'; /** * Compose the context group: the proportional bar plus the usage readout * OUTSIDE the bar, so the dotted track keeps its proportional meaning no * matter how wide the readout is. `full` reads `12.3K/1.0M 25%`; `percent` * drops the absolute pair; `none` is the bare bar. The readout turns amber * once occupancy reaches the warning threshold. */ export declare function contextGroupSpans(usedTokens: number, contextWindow: number, barWidth: number, readout: ContextReadoutMode): readonly StatusSpan[]; /** * One customizable status item (the Codex /statusline picker contract). * 'left' items render as pipe-separated clusters after the identity dot; * 'right' items pin to the right edge as dot-separated state badges. */ export type StatusItemId = 'model' | 'cwd' | 'branch' | 'plan' | 'mode' | 'turns' | 'durations' | 'cache' | 'context' | 'tokens' | 'title' | 'goal' | 'sandbox' | 'permission'; /** Picker-facing metadata for one customizable item. */ export interface StatusItemInfo { id: StatusItemId; /** Short picker label. */ label: string; /** One-line picker description of what the item shows. */ description: string; /** Which side of the split row the item renders on. */ side: 'left' | 'right'; } /** The full item catalog in canonical order (the /statusline default). */ export declare const STATUS_ITEMS: readonly StatusItemInfo[]; /** Picker rows with labels/descriptions in the active interface language. */ export declare function localizedStatusItems(): readonly StatusItemInfo[]; /** * Default order: the whole catalog (matches the pre-customization bar). * The busy dot is not an item — it always leads the identity cluster. */ export declare const DEFAULT_STATUSLINE_ITEMS: readonly StatusItemId[]; /** * Parse a persisted statusline item list. The stored value is the ordered * set of ENABLED items (the Codex /statusline contract): unknown ids and * duplicates drop out, and a non-array value (missing or corrupt file) * falls back to the full default set. An explicitly empty array is valid — * the bar degrades to its busy dot alone. * @param value - the raw parsed JSON value (expected string[]). * @returns the normalized ordered item list. */ export declare function parseStatuslineItems(value: unknown): readonly StatusItemId[]; /** * Extra left padding on the secondary row so its content aligns with the * model name's left edge on the primary row (padding 2 + busy dot 2). The * layout subtracts it from row 2's budget so the indent can never wrap it. */ export declare const STATUS_ROW2_INDENT = 2; /** Identity facts the runner resolves once at mount; empty strings drop out. */ export interface StatusFacts { /** 'provider/model' selection serving this session. */ model: string; /** Agent preset composing this session. */ mode?: string; /** Working-directory basename the session serves. */ cwd: string; /** Git branch name, empty outside a repository or on a detached HEAD file. */ branch: string; /** Short session identifier (last dash-separated segment or tail). */ sessionId: string; /** Latest session title (folded from 'session/title'); shown in place of the id. */ title: string; /** Sandbox-mode override (folded from 'sandbox/mode'), empty when never switched. */ sandbox: string; /** Live goal summary (folded from 'goal/change'), undefined when none. */ goal: { phase: string; rounds: number; max: number; } | undefined; /** Whether plan mode is active (folded from 'plan/mode'). */ plan: boolean; /** Active or pending permission preset; empty only when the service is unavailable. */ permission: string; } /** * Traffic-light tone for a permission preset: read-only stays success green, * full access reads error red, and every workspace-scoped middle ground * (including unknown presets) reads warning amber. * @param permission - active permission preset label. * @returns tone for the badge span. */ export declare function permissionTone(permission: string): StatusTone; /** * Compose the two-row footer layout under a column budget. Row 1 keeps model, * cwd, mode, branch, context, then the right-pinned permission badge and cycle * hint. It shrinks the context bar, drops the hint, and peels trailing * identity facts before removing the context group or the permission badge. * Row 2 fits all secondary figures and state within its own budget. * @param facts - identity facts resolved by the runner. * @param stats - session figures folded from the durable log. * @param columns - usable columns for each row (before their left padding). * @param options - 'busy' hides the cycle hint while a turn runs (Codex * keeps mode hints idle-only); 'items' is the ordered enabled-item config * from /statusline (defaults to the full catalog). Display order follows the * config per side while the drop ladder keeps its fixed ranks. * @returns the two rows to render; row1.left is never empty. */ export declare function layoutStatusBar(facts: StatusFacts, stats: TranscriptStats, columns: number, options?: { busy?: boolean; items?: readonly string[]; contextWidth?: number; }): StatusLayout;