@chantier/tui 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +488 -22
- package/dist/index.mjs +1291 -125
- package/package.json +3 -3
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
1
2
|
import { ApprovalDecision, ApprovalRequest } from "@chantier/permissions";
|
|
2
3
|
//#region ../../node_modules/cli-boxes/index.d.ts
|
|
3
4
|
/**
|
|
@@ -176,6 +177,66 @@ const petWithAutoComplete: Pet2 = '';
|
|
|
176
177
|
*/
|
|
177
178
|
type LiteralUnion<LiteralType, BaseType extends Primitive> = LiteralType | (BaseType & Record<never, never>);
|
|
178
179
|
//#endregion
|
|
180
|
+
//#region src/items.d.ts
|
|
181
|
+
/**
|
|
182
|
+
* Frozen v0.5 contract shared by both TUI build workers (spec
|
|
183
|
+
* local://chantier-tui-v05-spec.md §1). Types only — Worker A implements the
|
|
184
|
+
* runtime store against these; Worker B consumes them from widgets/input code.
|
|
185
|
+
*/
|
|
186
|
+
/** Finalized transcript item; rendered once via ink Static, never re-rendered. */
|
|
187
|
+
type TuiItem = {
|
|
188
|
+
kind: "markdown";
|
|
189
|
+
text: string;
|
|
190
|
+
} | {
|
|
191
|
+
kind: "tool";
|
|
192
|
+
toolName: string;
|
|
193
|
+
argsSummary: string;
|
|
194
|
+
outcome: "done" | "error";
|
|
195
|
+
durationMs?: number;
|
|
196
|
+
/** 1–2 line output preview (first non-empty line of the result content). */
|
|
197
|
+
detail?: string;
|
|
198
|
+
subagent?: {
|
|
199
|
+
sessionId: string;
|
|
200
|
+
summary: string;
|
|
201
|
+
};
|
|
202
|
+
} | {
|
|
203
|
+
kind: "divider";
|
|
204
|
+
text: string;
|
|
205
|
+
} | {
|
|
206
|
+
kind: "info";
|
|
207
|
+
text: string;
|
|
208
|
+
} | {
|
|
209
|
+
kind: "error";
|
|
210
|
+
text: string;
|
|
211
|
+
};
|
|
212
|
+
/** Status-widget state; null hides the widget. */
|
|
213
|
+
interface RunningState {
|
|
214
|
+
/** Date.now() when the run started (elapsed clock source). */
|
|
215
|
+
sinceMs: number;
|
|
216
|
+
detail?: string;
|
|
217
|
+
}
|
|
218
|
+
interface UsageTotals {
|
|
219
|
+
inputTokens: number;
|
|
220
|
+
outputTokens: number;
|
|
221
|
+
}
|
|
222
|
+
/** Additions to TuiStore for v0.5 (Worker A implements; TuiState gains the fields). */
|
|
223
|
+
interface TuiStoreV5 {
|
|
224
|
+
readonly items: readonly TuiItem[];
|
|
225
|
+
pushItem(item: TuiItem): void;
|
|
226
|
+
setRunning(running: RunningState | null): void;
|
|
227
|
+
readonly running: RunningState | null;
|
|
228
|
+
readonly queued: readonly string[];
|
|
229
|
+
pushQueued(text: string): void;
|
|
230
|
+
/** Replaces the LAST queued text (the ↑-edit path). */
|
|
231
|
+
editQueued(text: string): void;
|
|
232
|
+
dropQueued(): void;
|
|
233
|
+
readonly usage: UsageTotals | undefined;
|
|
234
|
+
setUsage(usage: UsageTotals | undefined): void;
|
|
235
|
+
readonly statusFlash: string;
|
|
236
|
+
/** 4–6s transient status flash that falls back to the persistent status (Hermes restoreStatusAfter). */
|
|
237
|
+
flashStatus(text: string): void;
|
|
238
|
+
}
|
|
239
|
+
//#endregion
|
|
179
240
|
//#region src/store.d.ts
|
|
180
241
|
type TuiMode = "input" | "running";
|
|
181
242
|
/** Optional attachment for an approval ask; pre-wired for diff previews. */
|
|
@@ -184,12 +245,20 @@ interface TuiPromptDetail {
|
|
|
184
245
|
}
|
|
185
246
|
interface TuiState {
|
|
186
247
|
readonly mode: TuiMode;
|
|
187
|
-
/** Finalized transcript
|
|
188
|
-
readonly
|
|
189
|
-
/** In-progress model text, replaced by
|
|
248
|
+
/** Finalized transcript items (rendered once via Static, never re-rendered). */
|
|
249
|
+
readonly items: readonly TuiItem[];
|
|
250
|
+
/** In-progress model text (the live region), replaced by items once finalized. */
|
|
190
251
|
readonly streamText: string;
|
|
191
252
|
/** Transient status line (e.g. "thinking…"); "" hides it. */
|
|
192
253
|
readonly status: string;
|
|
254
|
+
/** Status-widget state (spinner + elapsed); null hides the widget. */
|
|
255
|
+
readonly running: RunningState | null;
|
|
256
|
+
/** Tasks queued while a run is in flight; drained when the run settles. */
|
|
257
|
+
readonly queued: readonly string[];
|
|
258
|
+
/** Cumulative token usage for the footer; undefined until the first result. */
|
|
259
|
+
readonly usage: UsageTotals | undefined;
|
|
260
|
+
/** Transient flash message that falls back to the persistent status after a few seconds. */
|
|
261
|
+
readonly statusFlash: string;
|
|
193
262
|
/** Pending approval request; null while the model is streaming. */
|
|
194
263
|
readonly prompt: ApprovalRequest | null;
|
|
195
264
|
/** Optional diff attachment for the pending ask; null when absent. */
|
|
@@ -201,16 +270,41 @@ interface TuiState {
|
|
|
201
270
|
}
|
|
202
271
|
type TuiStore = {
|
|
203
272
|
readonly state: TuiState;
|
|
273
|
+
get items(): readonly TuiItem[];
|
|
274
|
+
get running(): RunningState | null;
|
|
275
|
+
get queued(): readonly string[];
|
|
276
|
+
get usage(): UsageTotals | undefined;
|
|
277
|
+
get statusFlash(): string;
|
|
204
278
|
/** React-side subscription; returns an unsubscribe function. */
|
|
205
279
|
subscribe(listener: () => void): () => void;
|
|
206
|
-
/** Appends one finalized transcript
|
|
207
|
-
|
|
280
|
+
/** Appends one finalized transcript item (rendered once, never re-rendered). */
|
|
281
|
+
pushItem(item: TuiItem): void;
|
|
208
282
|
/** Appends in-flight model text; flushStream() finalizes it. */
|
|
209
283
|
appendStream(text: string): void;
|
|
210
|
-
/**
|
|
211
|
-
|
|
284
|
+
/**
|
|
285
|
+
* Finalizes buffered stream text as a markdown item (spec §1). Without
|
|
286
|
+
* options, everything buffered becomes one item. With `{ safe: true }`, the
|
|
287
|
+
* buffer splits at its last safe paragraph boundary (takeSafeFlush): the
|
|
288
|
+
* flushed prefix becomes a markdown item and the remainder stays the live
|
|
289
|
+
* region — a half-open code fence is never finalized mid-stream.
|
|
290
|
+
*/
|
|
291
|
+
flushStream(options?: {
|
|
292
|
+
safe?: boolean;
|
|
293
|
+
}): void;
|
|
212
294
|
/** Shows a transient status (e.g. "thinking…"); "" hides it. */
|
|
213
295
|
setStatus(status: string): void;
|
|
296
|
+
/** Shows/clears the status-widget running state (spinner + elapsed). */
|
|
297
|
+
setRunning(running: RunningState | null): void;
|
|
298
|
+
/** Queues a task typed while a run is in flight. */
|
|
299
|
+
pushQueued(text: string): void;
|
|
300
|
+
/** Replaces the LAST queued text (the ↑-edit path); no-op when empty. */
|
|
301
|
+
editQueued(text: string): void;
|
|
302
|
+
/** Removes the LAST queued text; no-op when empty. */
|
|
303
|
+
dropQueued(): void;
|
|
304
|
+
/** Replaces the footer usage totals; undefined clears them. */
|
|
305
|
+
setUsage(usage: UsageTotals | undefined): void;
|
|
306
|
+
/** Transient flash message; falls back to the persistent status after ~5s. */
|
|
307
|
+
flashStatus(text: string): void;
|
|
214
308
|
/** Enters task-input mode; resolves the previous task signal if still open. */
|
|
215
309
|
awaitTask(defaultText?: string): Promise<string | null>;
|
|
216
310
|
/** Submits the typed task text; null = user quit. */
|
|
@@ -224,7 +318,7 @@ type TuiStore = {
|
|
|
224
318
|
decide(decision: ApprovalDecision): void;
|
|
225
319
|
/**
|
|
226
320
|
* Esc aborts the current work (deny pending prompt + notify); Ctrl-C quits
|
|
227
|
-
* the app with a nonzero exit signal.
|
|
321
|
+
* the app with a nonzero exit signal. The queue survives aborts.
|
|
228
322
|
*/
|
|
229
323
|
abort(kind: AbortKind): void;
|
|
230
324
|
/** Marks the store finished; App unmounts on the next render. */
|
|
@@ -235,9 +329,85 @@ export declare function createTuiStore(handlers: {
|
|
|
235
329
|
onAbort: (kind: AbortKind) => void;
|
|
236
330
|
}, isQuitCommand?: (text: string) => boolean): TuiStore;
|
|
237
331
|
//#endregion
|
|
332
|
+
//#region src/keys.d.ts
|
|
333
|
+
/**
|
|
334
|
+
* Namespaced action-id key map (spec §6a): every chord maps to a namespaced
|
|
335
|
+
* action id and components ask `matches(event, "app.interrupt")` instead of
|
|
336
|
+
* comparing raw key fields. This kills the BUG-3 class — any Ctrl-modified
|
|
337
|
+
* key quit (app.ts:23-25 in v0.4) — because ctrl-c is the ONLY quit chord,
|
|
338
|
+
* and it keeps dispatch as pure data + pure functions that unit-test without
|
|
339
|
+
* mounting ink (the Hermes approvalAction pattern, matching the existing
|
|
340
|
+
* keypressToDecision).
|
|
341
|
+
*/
|
|
342
|
+
/**
|
|
343
|
+
* Structural subset of ink's `useInput` Key that the matcher needs. Declared
|
|
344
|
+
* here (instead of importing ink) so keys.ts stays a pure, runtime-free
|
|
345
|
+
* module; ink's Key is structurally compatible by construction.
|
|
346
|
+
*/
|
|
347
|
+
interface Keychord {
|
|
348
|
+
/** Raw chunk bytes; a PTY can bundle the key with its Enter ("y\r"). */
|
|
349
|
+
readonly input?: string;
|
|
350
|
+
readonly ctrl?: boolean;
|
|
351
|
+
readonly escape?: boolean;
|
|
352
|
+
readonly upArrow?: boolean;
|
|
353
|
+
readonly downArrow?: boolean;
|
|
354
|
+
readonly return?: boolean;
|
|
355
|
+
readonly backspace?: boolean;
|
|
356
|
+
readonly delete?: boolean;
|
|
357
|
+
}
|
|
358
|
+
type ActionId =
|
|
359
|
+
/** Esc while a run streams: interrupt, keep completed work. */
|
|
360
|
+
"app.interrupt" |
|
|
361
|
+
/** Ctrl-C: the ONLY quit chord; the ×2 window lives in the caller. */
|
|
362
|
+
"app.quit" |
|
|
363
|
+
/** Approval card: y / a / n / esc. */
|
|
364
|
+
"app.decide.allow" | "app.decide.always" | "app.decide.deny" | "app.decide.abort" |
|
|
365
|
+
/** Enter at the task prompt (submit or queue while running). */
|
|
366
|
+
"app.submit" |
|
|
367
|
+
/** ↑ / ↓ history recall at an empty-ish editor. */
|
|
368
|
+
"app.history.prev" | "app.history.next" |
|
|
369
|
+
/** ↑ while queued rows exist: pop the last queued row into the editor. */
|
|
370
|
+
"app.queue.edit" |
|
|
371
|
+
/** Emacs set (§6f). */
|
|
372
|
+
"app.editor.home" | "app.editor.end" | "app.editor.char.back" | "app.editor.char.forward" | "app.editor.kill.to-end" | "app.editor.kill.line" | "app.editor.kill.word" | "app.editor.backspace" |
|
|
373
|
+
/** Ctrl-L clears + redraws; ink repaints from state, so this is a no-op
|
|
374
|
+
* hook point today — but it must stay OUT of app.quit's chord set. */
|
|
375
|
+
"app.redraw";
|
|
376
|
+
/**
|
|
377
|
+
* The chord table. "app.queue.edit" and "app.history.prev" share the
|
|
378
|
+
* up-arrow chord on purpose: callers disambiguate by state (queued rows
|
|
379
|
+
* exist vs history recall), which is how OMP treats Alt+Up vs plain Up.
|
|
380
|
+
*/
|
|
381
|
+
export declare const ACTION_CHORDS: Readonly<Record<ActionId, readonly Keychord[]>>;
|
|
382
|
+
/**
|
|
383
|
+
* Whether the keypress resolves to the action id. Input chunks are
|
|
384
|
+
* normalized by stripping CR/LF first: a PTY can deliver "y\r" as one chunk
|
|
385
|
+
* while ink sets key.return only for a lone CR.
|
|
386
|
+
*/
|
|
387
|
+
export declare function matches(event: Keychord, id: ActionId): boolean;
|
|
388
|
+
/** Hint line for the approval card, verbatim spec §7 mockup. */
|
|
389
|
+
export declare const APPROVAL_HINT_PARTS: readonly ["y allow", "a always", "n deny", "esc abort"];
|
|
390
|
+
/**
|
|
391
|
+
* Maps a prompt keypress to a decision; null = key not handled by the
|
|
392
|
+
* prompt. Moved here unchanged from app.ts (spec §7): this stays the pure
|
|
393
|
+
* dispatch surface, unit-tested without mounting ink.
|
|
394
|
+
*/
|
|
395
|
+
export declare function keypressToDecision(key: string): ApprovalDecision | null;
|
|
396
|
+
//#endregion
|
|
238
397
|
//#region src/app.d.ts
|
|
239
|
-
|
|
398
|
+
/** Footer data computed by the caller per render (context estimate + compact gate). */
|
|
399
|
+
interface FooterData {
|
|
400
|
+
readonly ctxFraction?: number;
|
|
401
|
+
readonly compactSoon?: boolean;
|
|
402
|
+
}
|
|
403
|
+
export declare function TuiApp({ store, footer }: {
|
|
240
404
|
store: TuiStore;
|
|
405
|
+
footer?: {
|
|
406
|
+
readonly model: string;
|
|
407
|
+
readonly sessionId: string;
|
|
408
|
+
readonly contextWindow?: number;
|
|
409
|
+
readonly data?: () => FooterData;
|
|
410
|
+
};
|
|
241
411
|
}): import("react").FunctionComponentElement<{
|
|
242
412
|
readonly position?: "absolute" | "relative" | "static" | undefined;
|
|
243
413
|
readonly top?: number | string | undefined;
|
|
@@ -322,16 +492,20 @@ export declare function TuiApp({ store }: {
|
|
|
322
492
|
} & import("react").RefAttributes<import("ink").DOMElement>>;
|
|
323
493
|
/** Screen-reader label for the approval card (ink serializes it as the button name). */
|
|
324
494
|
export declare function approvalLabel(tool: string, input: unknown): string;
|
|
325
|
-
/** Maps a prompt keypress to a decision; null = key not handled by the prompt. */
|
|
326
|
-
export declare function keypressToDecision(key: string): ApprovalDecision | null;
|
|
327
495
|
interface TuiOptions {
|
|
328
496
|
/** Opt-in screen-reader rendering (also honored: CHANTIER_SCREEN_READER=1). */
|
|
329
497
|
readonly screenReader?: boolean;
|
|
498
|
+
/** Footer segments; session id + model come from the CLI, contextWindow gates the ctx segment. */
|
|
499
|
+
readonly footer?: {
|
|
500
|
+
readonly model: string;
|
|
501
|
+
readonly sessionId: string;
|
|
502
|
+
readonly contextWindow?: number;
|
|
503
|
+
readonly data?: () => FooterData;
|
|
504
|
+
};
|
|
330
505
|
}
|
|
331
506
|
interface TuiInstance {
|
|
332
507
|
waitUntilExit(): Promise<void>;
|
|
333
508
|
}
|
|
334
|
-
/** Mounts the TUI. The store drives everything; the caller drives the agent. */
|
|
335
509
|
export declare function startTui(store: TuiStore, options?: TuiOptions): TuiInstance;
|
|
336
510
|
//#endregion
|
|
337
511
|
//#region src/diff.d.ts
|
|
@@ -355,15 +529,6 @@ interface DiffPreview {
|
|
|
355
529
|
/** Counts add/del lines across the whole diff and caps the displayed lines. */
|
|
356
530
|
export declare function summarizeUnifiedDiff(diff: string, maxLines?: number): DiffPreview;
|
|
357
531
|
//#endregion
|
|
358
|
-
//#region src/screen-reader.d.ts
|
|
359
|
-
/**
|
|
360
|
-
* Screen-reader mode: ink serializes the tree to plain linear text (no
|
|
361
|
-
* borders, no colors), keeps `<Static>` append-only, and rewrites the dynamic
|
|
362
|
-
* region only when it changes. Enabled by the `--screen-reader` flag or the
|
|
363
|
-
* CHANTIER_SCREEN_READER=1 environment alias.
|
|
364
|
-
*/
|
|
365
|
-
export declare function resolveScreenReader(flag: boolean | undefined, env: string | undefined): boolean;
|
|
366
|
-
//#endregion
|
|
367
532
|
//#region src/symbols.d.ts
|
|
368
533
|
/**
|
|
369
534
|
* Decorative characters and box borders used by the TUI. Screen readers skip
|
|
@@ -379,9 +544,310 @@ interface TuiSymbols {
|
|
|
379
544
|
readonly hintSeparator: string;
|
|
380
545
|
/** Truncation ellipsis: "…" normally, "..." in ASCII mode. */
|
|
381
546
|
readonly ellipsis: string;
|
|
547
|
+
/**
|
|
548
|
+
* Spinner animation frames (v0.5 status widget, spec §4a): the 10-frame
|
|
549
|
+
* braille cycle normally, the 4-glyph `|/-\` cycle in ASCII mode.
|
|
550
|
+
* `spinnerFrame` wraps per set, so no timing code depends on the count.
|
|
551
|
+
*/
|
|
552
|
+
readonly spinnerFrames: readonly string[];
|
|
553
|
+
/** Tool/task row bullet: "▸" normally, ">" in ASCII mode (§8 parity). */
|
|
554
|
+
readonly runGlyph: string;
|
|
555
|
+
/** Tree corner for detail and summary blocks: "└" normally, "+" in ASCII. */
|
|
556
|
+
readonly subGlyph: string;
|
|
557
|
+
/** Failed-tool glyph: "✗" normally, "x" in ASCII mode. */
|
|
558
|
+
readonly errorGlyph: string;
|
|
559
|
+
/** Divider rule cell: "─" normally, "-" in ASCII mode (§2d). */
|
|
560
|
+
readonly rule: string;
|
|
561
|
+
/** Context-bar cells: filled "▮" / empty "▯", ASCII "#" / "-" (§5). */
|
|
562
|
+
readonly barFilled: string;
|
|
563
|
+
readonly barEmpty: string;
|
|
564
|
+
/** Forward arrow: "→" normally, "->" in ASCII mode (divider text, §2d). */
|
|
565
|
+
readonly arrow: string;
|
|
566
|
+
/** Recall hint arrow: "↑" normally, "^" in ASCII mode (queue preview §6d). */
|
|
567
|
+
readonly arrowUp: string;
|
|
568
|
+
/** Minus sign in diff counts: "−" normally, "-" in ASCII mode (§7). */
|
|
569
|
+
readonly minus: string;
|
|
382
570
|
}
|
|
383
571
|
export declare function resolveSymbols(ascii: boolean): TuiSymbols;
|
|
384
572
|
/** CHANTIER_ASCII=1 requests the ASCII-safe rendering. */
|
|
385
573
|
export declare function isAsciiEnv(env: string | undefined): boolean;
|
|
386
574
|
//#endregion
|
|
387
|
-
|
|
575
|
+
//#region src/input.d.ts
|
|
576
|
+
/**
|
|
577
|
+
* v0.5 input surface (spec §6c-f): editor keys, paste, history. The emacs
|
|
578
|
+
* transforms and the paste thresholds are pure functions (testable without
|
|
579
|
+
* mounting ink); `TaskInput` is the props-driven ink component the app
|
|
580
|
+
* mounts at integration. Paste travels ink's separate usePaste channel —
|
|
581
|
+
* bracketed paste content never reaches useInput (verified in ink 7.1.1),
|
|
582
|
+
* which is exactly the fix for the v0.4 paste-destruction bug.
|
|
583
|
+
*/
|
|
584
|
+
interface EditorState {
|
|
585
|
+
readonly text: string;
|
|
586
|
+
/** Insertion point, 0..text.length. */
|
|
587
|
+
readonly cursor: number;
|
|
588
|
+
}
|
|
589
|
+
export declare function emptyEditor(): EditorState;
|
|
590
|
+
export declare function editorInsert(state: EditorState, insert: string): EditorState;
|
|
591
|
+
export declare function editorBackspace(state: EditorState): EditorState;
|
|
592
|
+
type EditorAction = "home" | "end" | "char.back" | "char.forward" | "kill.to-end" | "kill.line" | "kill.word";
|
|
593
|
+
/**
|
|
594
|
+
* Emacs set (§6f) as pure (text, cursor) transforms: ctrl+a/e home/end,
|
|
595
|
+
* ctrl+b/f char moves, ctrl+k kill to end, ctrl+u clear, ctrl+w kill word
|
|
596
|
+
* back. Single-line editor only; multiline ctrl+j is v0.6.
|
|
597
|
+
*/
|
|
598
|
+
export declare function applyEditorAction(state: EditorState, action: EditorAction): EditorState;
|
|
599
|
+
interface PasteResult {
|
|
600
|
+
/** Chip text when the paste exceeds the thresholds; null = insert verbatim. */
|
|
601
|
+
readonly chip: string | null;
|
|
602
|
+
readonly lines: number;
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Chip thresholds (§6e): >3 lines or >800 chars chips; under 12 terminal
|
|
606
|
+
* rows the CC narrow rule applies (1 line / 200 chars).
|
|
607
|
+
*/
|
|
608
|
+
export declare function pasteChip(text: string, rows?: number): PasteResult;
|
|
609
|
+
/** Restores full paste text from its chip markers before submit. */
|
|
610
|
+
export declare function expandPasteChips(text: string, chunks: ReadonlyMap<string, string>): string;
|
|
611
|
+
/** File-backed history; the path is injectable so tests never touch $HOME. */
|
|
612
|
+
export declare function historyPath(home?: string): string;
|
|
613
|
+
interface HistoryStore {
|
|
614
|
+
readonly entries: readonly string[];
|
|
615
|
+
/** Appends unless it duplicates the last entry; silent on file failure. */
|
|
616
|
+
record(text: string): Promise<void>;
|
|
617
|
+
/** ↑: empty editor → last → older. */
|
|
618
|
+
prev(): string | null;
|
|
619
|
+
/** ↓: forward; past the newest entry → null (empty editor). */
|
|
620
|
+
next(): string | null;
|
|
621
|
+
/** Typed input ends a browse sequence. */
|
|
622
|
+
reset(): void;
|
|
623
|
+
}
|
|
624
|
+
export declare function createHistoryStore(opts?: {
|
|
625
|
+
entries?: readonly string[];
|
|
626
|
+
file?: string;
|
|
627
|
+
}): HistoryStore;
|
|
628
|
+
/** Reads the JSONL history; silent failure yields an empty list. */
|
|
629
|
+
export declare function loadHistory(file: string): Promise<string[]>;
|
|
630
|
+
export declare const QUIT_HINT = "press ctrl-c again to quit";
|
|
631
|
+
interface TaskInputProps {
|
|
632
|
+
/** Controlled editor state; the host owns the single source of truth. */
|
|
633
|
+
readonly editor: EditorState;
|
|
634
|
+
readonly onEditorChange: (next: EditorState) => void;
|
|
635
|
+
/** Enter: the host routes to submitTask (idle) or pushQueued (running). */
|
|
636
|
+
readonly onSubmit: (text: string) => void;
|
|
637
|
+
/** True while a run streams: submit queues, ↑ edits the queue. */
|
|
638
|
+
readonly running: boolean;
|
|
639
|
+
readonly queuedCount: number;
|
|
640
|
+
/** Pops the last queued row back into the editor (§6d, OMP Alt+Up). */
|
|
641
|
+
readonly onQueueEdit: () => void;
|
|
642
|
+
/** Approval pending: every editor key is inert (input lock). */
|
|
643
|
+
readonly locked?: boolean;
|
|
644
|
+
/** Terminal rows for paste thresholds; undefined = standard thresholds. */
|
|
645
|
+
readonly rows?: number;
|
|
646
|
+
/** ↑/↓ recall + esc-saves-draft. */
|
|
647
|
+
readonly history?: HistoryStore;
|
|
648
|
+
/** Ctrl-C: host aborts the store (single-press idle, second stage running). */
|
|
649
|
+
readonly onQuit: () => void;
|
|
650
|
+
/** First ctrl-c while running: host flashes "press ctrl-c again to quit". */
|
|
651
|
+
readonly onQuitArm: () => void;
|
|
652
|
+
/** Two-stage window override for tests. */
|
|
653
|
+
readonly quitWindowMs?: number;
|
|
654
|
+
readonly symbols: TuiSymbols;
|
|
655
|
+
readonly screenReader?: boolean;
|
|
656
|
+
}
|
|
657
|
+
export declare function TaskInput({ editor, onEditorChange, onSubmit, running, queuedCount, onQueueEdit, locked, rows, history, onQuit, onQuitArm, quitWindowMs, symbols, screenReader }: TaskInputProps): ReactNode;
|
|
658
|
+
//#endregion
|
|
659
|
+
//#region src/markdown.d.ts
|
|
660
|
+
/**
|
|
661
|
+
* The v0.5 reading surface (spec local://chantier-tui-v05-spec.md §2): a
|
|
662
|
+
* dependency-free line-based markdown block parser plus the streaming flush
|
|
663
|
+
* splitter. Everything here is pure so the fence/flush rules can be tested
|
|
664
|
+
* without mounting ink.
|
|
665
|
+
*/
|
|
666
|
+
/** Result of splitting a stream buffer at its last safe paragraph boundary. */
|
|
667
|
+
interface SafeFlush {
|
|
668
|
+
/** Text that is safe to finalize (balanced blocks only); "" when nothing is. */
|
|
669
|
+
readonly flushed: string;
|
|
670
|
+
/** Text that must stay in the live region (may end inside an open fence). */
|
|
671
|
+
readonly rest: string;
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Splits a stream buffer at its last safe split point: the final `\n\n`
|
|
675
|
+
* outside an open fence. When the buffer ends inside an open fence and no
|
|
676
|
+
* outside boundary exists, it splits BEFORE the fence start instead, so a
|
|
677
|
+
* half-open code block is never emitted into the finalized transcript. With
|
|
678
|
+
* neither a boundary nor an open fence, nothing flushes.
|
|
679
|
+
*/
|
|
680
|
+
export declare function takeSafeFlush(buffer: string): SafeFlush;
|
|
681
|
+
export declare function hasMarkdownSyntax(text: string): boolean;
|
|
682
|
+
/** A dim rule wrapped around `text`; screen readers get the plain text only. */
|
|
683
|
+
export declare function markdownDivider(text: string, symbols: TuiSymbols, screenReader: boolean): ReactNode;
|
|
684
|
+
/**
|
|
685
|
+
* Renders markdown text into ink elements (spec §2b): headings bold, lists
|
|
686
|
+
* indented, fences dim + 2-space indented, rules as divider rows, tables as
|
|
687
|
+
* space-aligned rows. Screen-reader mode renders the same rows unstyled, with
|
|
688
|
+
* decorative rows replaced by their label, and plain input takes the fast
|
|
689
|
+
* path and skips parsing entirely. Tolerant streaming: an unclosed fence
|
|
690
|
+
* renders as plain text until closed, so the strict styling only ever
|
|
691
|
+
* applies to balanced blocks.
|
|
692
|
+
*/
|
|
693
|
+
export declare function markdownToElements(text: string, symbols: TuiSymbols, screenReader: boolean): ReactNode[];
|
|
694
|
+
//#endregion
|
|
695
|
+
//#region src/screen-reader.d.ts
|
|
696
|
+
/**
|
|
697
|
+
* Screen-reader mode: ink serializes the tree to plain linear text (no
|
|
698
|
+
* borders, no colors), keeps `<Static>` append-only, and rewrites the dynamic
|
|
699
|
+
* region only when it changes. Enabled by the `--screen-reader` flag or the
|
|
700
|
+
* CHANTIER_SCREEN_READER=1 environment alias.
|
|
701
|
+
*/
|
|
702
|
+
export declare function resolveScreenReader(flag: boolean | undefined, env: string | undefined): boolean;
|
|
703
|
+
//#endregion
|
|
704
|
+
//#region src/widgets.d.ts
|
|
705
|
+
/**
|
|
706
|
+
* v0.5 activity + status widgets (spec §2c/§2d/§4/§5/§7). These are
|
|
707
|
+
* standalone, props-driven components: the parallel worker's app.ts mounts
|
|
708
|
+
* them at integration, and tests feed plain data — nothing here imports the
|
|
709
|
+
* store runtime. Every glyph routes through the provided `symbols`, and
|
|
710
|
+
* screen-reader parity renders finalized labeled lines only (§8 matrix).
|
|
711
|
+
*/
|
|
712
|
+
interface RowSpec {
|
|
713
|
+
readonly text: string;
|
|
714
|
+
readonly color?: string;
|
|
715
|
+
readonly bold?: boolean;
|
|
716
|
+
readonly dim?: boolean;
|
|
717
|
+
}
|
|
718
|
+
/** First non-empty line of a tool result, capped for the row preview. */
|
|
719
|
+
export declare function previewLine(content: string, ellipsis: string, cap?: number): string;
|
|
720
|
+
export declare function formatDuration(ms: number | undefined): string;
|
|
721
|
+
/** "24k"-style token counts for the compaction divider (§2d mockup). */
|
|
722
|
+
export declare function formatTokens(tokens: number): string;
|
|
723
|
+
type ToolItem = Extract<TuiItem, {
|
|
724
|
+
kind: "tool";
|
|
725
|
+
}>;
|
|
726
|
+
interface ToolRowProps {
|
|
727
|
+
readonly item: ToolItem;
|
|
728
|
+
readonly symbols: TuiSymbols;
|
|
729
|
+
readonly screenReader?: boolean;
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Collapse ladder: task/read/edit render two lines (args + detail preview);
|
|
733
|
+
* everything else renders one line. Errors turn the glyph red (§2c). The
|
|
734
|
+
* full output stays in the transcript log; expand/collapse is v0.6.
|
|
735
|
+
*/
|
|
736
|
+
export declare function toolRowLines(item: ToolItem, symbols: TuiSymbols): Array<RowSpec>;
|
|
737
|
+
/** SR parity (§8): `tool: read(src/config.ts) done`. */
|
|
738
|
+
export declare function toolRowSrText(item: ToolItem): string;
|
|
739
|
+
export declare function ToolRow({ item, symbols, screenReader }: ToolRowProps): ReactNode;
|
|
740
|
+
interface SubagentCardProps {
|
|
741
|
+
readonly item: ToolItem & {
|
|
742
|
+
subagent: {
|
|
743
|
+
sessionId: string;
|
|
744
|
+
summary: string;
|
|
745
|
+
};
|
|
746
|
+
};
|
|
747
|
+
readonly symbols: TuiSymbols;
|
|
748
|
+
}
|
|
749
|
+
/** Summary block cap: 8 dim lines, then the tail pointer to the child session. */
|
|
750
|
+
export declare const SUBAGENT_SUMMARY_MAX_LINES = 8;
|
|
751
|
+
export declare function subagentLines(item: SubagentCardProps["item"], symbols: TuiSymbols): Array<RowSpec>;
|
|
752
|
+
export declare function SubagentCard({ item, symbols }: SubagentCardProps): ReactNode;
|
|
753
|
+
interface StatusWidgetProps {
|
|
754
|
+
readonly running: RunningState | null;
|
|
755
|
+
/** Persistent status text (e.g. "thinking…"); "" = none. */
|
|
756
|
+
readonly status: string;
|
|
757
|
+
readonly symbols: TuiSymbols;
|
|
758
|
+
/** Injectable clock source; defaults to Date.now (tests pass a stub). */
|
|
759
|
+
readonly now?: () => number;
|
|
760
|
+
readonly screenReader?: boolean;
|
|
761
|
+
}
|
|
762
|
+
export declare const STATUS_VERB_WIDTH: number;
|
|
763
|
+
/** Elapsed clock with a bounded tail: s → "1m 12s" → "1h 2m" → "99h+". */
|
|
764
|
+
export declare function formatElapsed(ms: number): string;
|
|
765
|
+
export declare function spinnerFrame(frame: number, symbols: TuiSymbols): string;
|
|
766
|
+
/**
|
|
767
|
+
* The one-line running status: spinner + anti-jitter-padded verb + elapsed +
|
|
768
|
+
* interrupt hint, with the detail line underneath. The verb padding is the
|
|
769
|
+
* Hermes trick: the tail never shifts while the spinner cycles because the
|
|
770
|
+
* verb column width is pinned to the widest verb the widget can show.
|
|
771
|
+
*/
|
|
772
|
+
export declare function statusLines(frame: number, running: RunningState, status: string, symbols: TuiSymbols, elapsedMs: number): Array<RowSpec>;
|
|
773
|
+
export declare function StatusWidget({ running, status, symbols, now, screenReader }: StatusWidgetProps): ReactNode;
|
|
774
|
+
interface FooterBarProps {
|
|
775
|
+
readonly model: string;
|
|
776
|
+
/** 0..1 context estimate; undefined (no declared window) hides the segment. */
|
|
777
|
+
readonly ctxFraction?: number;
|
|
778
|
+
/** Auto-compact reserve would trigger for this task (§5 "compaction soon"). */
|
|
779
|
+
readonly compactSoon?: boolean;
|
|
780
|
+
readonly usage?: UsageTotals;
|
|
781
|
+
readonly sessionId: string;
|
|
782
|
+
readonly columns: number;
|
|
783
|
+
readonly symbols: TuiSymbols;
|
|
784
|
+
/** Hidden entirely while an approval prompt is pending (§5). */
|
|
785
|
+
readonly hidden?: boolean;
|
|
786
|
+
}
|
|
787
|
+
export declare function formatTokenCount(tokens: number): string;
|
|
788
|
+
/** 8-cell context bar; filled cells round to nearest, clamped to [0, 8]. */
|
|
789
|
+
export declare function ctxBar(fraction: number, symbols: TuiSymbols): string;
|
|
790
|
+
/**
|
|
791
|
+
* Segment list under the width breakpoints: <64 → model + ctx%; <80 → +
|
|
792
|
+
* session; ≥80 → all four. Whole segments only — never mid-truncate.
|
|
793
|
+
*/
|
|
794
|
+
export declare function footerSegments(props: Omit<FooterBarProps, "hidden">): Array<RowSpec>;
|
|
795
|
+
export declare function FooterBar({ model, ctxFraction, compactSoon, usage, sessionId, columns, symbols, hidden }: FooterBarProps): ReactNode;
|
|
796
|
+
interface QueuePreviewProps {
|
|
797
|
+
readonly queued: readonly string[];
|
|
798
|
+
readonly symbols: TuiSymbols;
|
|
799
|
+
}
|
|
800
|
+
export declare const QUEUE_PREVIEW_MAX_ROWS = 2;
|
|
801
|
+
/** ≤2 dimmed rows + "+N more"; collapses runs of whitespace per row. */
|
|
802
|
+
export declare function queuePreviewLines(queued: readonly string[], symbols: TuiSymbols): Array<RowSpec>;
|
|
803
|
+
export declare function QueuePreview({ queued, symbols }: QueuePreviewProps): ReactNode;
|
|
804
|
+
interface ApprovalCardDetail {
|
|
805
|
+
readonly diff?: string;
|
|
806
|
+
}
|
|
807
|
+
interface ApprovalCardV2Props {
|
|
808
|
+
readonly request: ApprovalRequest;
|
|
809
|
+
readonly detail: ApprovalCardDetail | null;
|
|
810
|
+
readonly symbols: TuiSymbols;
|
|
811
|
+
/** cwd for the ~-shortened path display (CC label-shortening). */
|
|
812
|
+
readonly cwd?: string;
|
|
813
|
+
readonly screenReader?: boolean;
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Humanized subject per tool (§7): edit/write → ~-shortened path + ±counts
|
|
817
|
+
* from the diff detail; bash → command (80 cap); read/grep → the pattern,
|
|
818
|
+
* glob → its pattern; task → first line of the prompt.
|
|
819
|
+
*/
|
|
820
|
+
export declare function humanizeApproval(tool: string, input: unknown, detail: ApprovalCardDetail | null, symbols: TuiSymbols, cwd?: string): string;
|
|
821
|
+
/** SR parity (§8): `edit src/x.ts: +3 -1`. */
|
|
822
|
+
export declare function approvalSrLabel(tool: string, input: unknown, detail: ApprovalCardDetail | null, symbols: TuiSymbols, cwd?: string): string;
|
|
823
|
+
interface ApprovalCardV2Spec {
|
|
824
|
+
readonly title: string;
|
|
825
|
+
readonly subject: RowSpec | null;
|
|
826
|
+
readonly diffLines: Array<RowSpec>;
|
|
827
|
+
readonly hiddenLines: number;
|
|
828
|
+
readonly hints: string;
|
|
829
|
+
readonly srLabel: string;
|
|
830
|
+
}
|
|
831
|
+
/** Pure card spec so tests assert content without mounting ink. */
|
|
832
|
+
export declare function approvalCardSpec(props: {
|
|
833
|
+
request: ApprovalRequest;
|
|
834
|
+
detail: ApprovalCardDetail | null;
|
|
835
|
+
symbols: TuiSymbols;
|
|
836
|
+
cwd?: string;
|
|
837
|
+
}): ApprovalCardV2Spec;
|
|
838
|
+
export declare function ApprovalCardV2({ request, detail, symbols, cwd, screenReader }: ApprovalCardV2Props): ReactNode;
|
|
839
|
+
interface DividerProps {
|
|
840
|
+
readonly text: string;
|
|
841
|
+
readonly symbols: TuiSymbols;
|
|
842
|
+
readonly screenReader?: boolean;
|
|
843
|
+
}
|
|
844
|
+
/** `── {text} ──` with the ASCII `--` fallback. */
|
|
845
|
+
export declare function dividerLine(text: string, symbols: TuiSymbols): string;
|
|
846
|
+
export declare function Divider({ text, symbols, screenReader }: DividerProps): ReactNode;
|
|
847
|
+
/**
|
|
848
|
+
* Hermes mechanism: when no tool item survives a visibility filter, the last
|
|
849
|
+
* error item is forced back into view — quiet mode must never hide failures.
|
|
850
|
+
*/
|
|
851
|
+
export declare function withErrorBackstop(items: readonly TuiItem[], isVisible: (item: TuiItem) => boolean): readonly TuiItem[];
|
|
852
|
+
//#endregion
|
|
853
|
+
export type { AbortKind, ActionId, ApprovalCardDetail, ApprovalCardV2Props, ApprovalCardV2Spec, DiffLineKind, DiffPreview, DividerProps, EditorAction, EditorState, FooterBarProps, HistoryStore, Keychord, PasteResult, QueuePreviewProps, RowSpec, RunningState, SafeFlush, StatusWidgetProps, SubagentCardProps, TaskInputProps, ToolItem, ToolRowProps, TuiInstance, TuiItem, TuiOptions, TuiPromptDetail, TuiState, TuiStore, TuiStoreV5, TuiSymbols, UsageTotals };
|