@vincemakes/kiso-tui 0.1.24 → 0.1.26
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/body.js +19 -15
- package/dist/dock.d.ts +3 -2
- package/dist/dock.js +22 -5
- package/dist/editor.js +1 -1
- package/dist/render.d.ts +21 -11
- package/dist/render.js +22 -8
- package/package.json +1 -1
package/dist/body.js
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import { truncateDiff } from "./diff.js";
|
|
27
27
|
import { displayWidth } from "./editor.js";
|
|
28
|
-
import { escapeTerminal, foldResult, foldThinking, palette, renderTerminalGap, renderToolSummary, } from "./render.js";
|
|
28
|
+
import { colorInlineCode, escapeTerminal, foldResult, foldThinking, palette, renderTerminalGap, renderToolSummary, } from "./render.js";
|
|
29
29
|
/** The spinner glyphs, cycled by the heartbeat (v3 §05 — the working family). */
|
|
30
30
|
const SPINNER = ["▖", "▘", "▝", "▗"];
|
|
31
31
|
const TOOL_SUMMARY_MAX = 60; // the tool line's parameter summary, chars
|
|
@@ -136,7 +136,7 @@ export class Body {
|
|
|
136
136
|
this.#closeOpenThinking();
|
|
137
137
|
this.#closeOpenText();
|
|
138
138
|
const p = palette();
|
|
139
|
-
this.#write(`${p.
|
|
139
|
+
this.#write(`${p.bold}you> ${escapeTerminal(text)}${p.reset}\n`);
|
|
140
140
|
return;
|
|
141
141
|
}
|
|
142
142
|
this.#closeOpenThinking();
|
|
@@ -405,13 +405,12 @@ export class Body {
|
|
|
405
405
|
const p = palette();
|
|
406
406
|
switch (cell.kind) {
|
|
407
407
|
case "user":
|
|
408
|
-
// v3 §02: the user message is a
|
|
409
|
-
// line
|
|
410
|
-
//
|
|
411
|
-
//
|
|
412
|
-
//
|
|
413
|
-
|
|
414
|
-
return cell.text.split("\n").map((l) => `${p.rev}${escapeTerminal(l)}${p.reset}`);
|
|
408
|
+
// v3 §02, TUI v5 #16f (v4.1 design): the user message is a left
|
|
409
|
+
// rail — a bright-white BOLD ▍ per line, then the text (the
|
|
410
|
+
// reverse-video block is RETIRED: it washed out on light
|
|
411
|
+
// themes). Multi-line whole: every line carries the rail
|
|
412
|
+
// (多行连贯); resize-safe; NO_COLOR → the rail renders plain.
|
|
413
|
+
return cell.text.split("\n").map((l) => `${p.bold}▍${p.reset} ${escapeTerminal(l)}`);
|
|
415
414
|
case "thinking": {
|
|
416
415
|
const block = cell.text;
|
|
417
416
|
const trimmed = escapeTerminal(block.trim());
|
|
@@ -429,11 +428,11 @@ export class Body {
|
|
|
429
428
|
return [`${p.red}✗ ${name} (${err}, ${elapsed}s)${p.reset}`];
|
|
430
429
|
}
|
|
431
430
|
const delta = cell.added + cell.removed > 0 ? `, +${cell.added} -${cell.removed}` : "";
|
|
432
|
-
return [`${p.
|
|
431
|
+
return [`${p.bold}✓ ${name}${p.reset} (${summary}${delta}, ${elapsed}s)`];
|
|
433
432
|
}
|
|
434
433
|
if (cell.state === "approval") {
|
|
435
|
-
const lines = [`→ ${name} ${summary} ${p.
|
|
436
|
-
// v2e: the mini-diff — ▎
|
|
434
|
+
const lines = [`→ ${name} ${summary} ${p.bold}⏸${p.reset}`];
|
|
435
|
+
// v2e: the mini-diff — ▎ bold edge (the brick motif), - red /
|
|
437
436
|
// + green / context dim; NO_COLOR keeps the ± prefixes plain.
|
|
438
437
|
if (cell.diff !== null) {
|
|
439
438
|
for (const d of cell.diff) {
|
|
@@ -442,21 +441,26 @@ export class Body {
|
|
|
442
441
|
: d.kind === "+"
|
|
443
442
|
? `${p.green}+ ${escapeTerminal(d.text)}${p.reset}`
|
|
444
443
|
: `${p.dim} ${escapeTerminal(d.text)}${p.reset}`;
|
|
445
|
-
lines.push(`${p.
|
|
444
|
+
lines.push(`${p.bold}▎${p.reset}${body}`);
|
|
446
445
|
}
|
|
447
446
|
}
|
|
448
447
|
return lines;
|
|
449
448
|
}
|
|
450
449
|
if (cell.state === "running") {
|
|
451
450
|
const elapsed = cell.startedAt !== null ? Math.max(1, Math.round((Date.now() - cell.startedAt) / 1000)) : 1;
|
|
452
|
-
return [`→ ${name} ${summary} ${p.
|
|
451
|
+
return [`→ ${name} ${summary} ${p.bold}${SPINNER[this.#spinnerI % SPINNER.length]}${p.reset} ${elapsed}s`];
|
|
453
452
|
}
|
|
454
453
|
return [`→ ${name} ${summary}`];
|
|
455
454
|
}
|
|
456
455
|
case "text": {
|
|
457
456
|
const text = escapeTerminal(cell.text);
|
|
458
457
|
const wrapped = this.#wrap(text, W);
|
|
459
|
-
|
|
458
|
+
// TUI v5 #16e: the inline-code tint — backtick spans in
|
|
459
|
+
// assistant body text, matched PER LINE after the wrap (a
|
|
460
|
+
// span opened on one line and closed on another does NOT
|
|
461
|
+
// match — 跨行不匹配). NO_COLOR → the codes are empty →
|
|
462
|
+
// byte-identical.
|
|
463
|
+
return wrapped.length > 0 ? wrapped.map((l) => colorInlineCode(l)) : [""];
|
|
460
464
|
}
|
|
461
465
|
case "notice":
|
|
462
466
|
return [escapeTerminal(cell.text)];
|
package/dist/dock.d.ts
CHANGED
|
@@ -69,7 +69,8 @@ export declare class Dock {
|
|
|
69
69
|
/** The bottom four rows, wrapped in CSI 2026 (synchronized output —
|
|
70
70
|
* the pi trick against flicker). The cursor ends at the input line's
|
|
71
71
|
* edit position. v3 §03: the upper ╌ row, the input row, the lower
|
|
72
|
-
* ╌ row, the status row — the status is dim (
|
|
73
|
-
* come from the CLI's composition).
|
|
72
|
+
* ╌ row, the status row — the status is dim (bold accents inside
|
|
73
|
+
* come from the CLI's composition). TUI v5 #16g: the idle status
|
|
74
|
+
* row carries the right-aligned "/ commands · ↑ history" hint. */
|
|
74
75
|
redraw(): void;
|
|
75
76
|
}
|
package/dist/dock.js
CHANGED
|
@@ -52,7 +52,7 @@ export class Dock {
|
|
|
52
52
|
* mode — the bottom three rows need room to exist. */
|
|
53
53
|
enter() {
|
|
54
54
|
const rows = process.stdout.rows ?? 0;
|
|
55
|
-
if (process.stdout.isTTY !== true || palette().
|
|
55
|
+
if (process.stdout.isTTY !== true || palette().bold === "" || rows < 4)
|
|
56
56
|
return;
|
|
57
57
|
this.#active = true;
|
|
58
58
|
this.#height = rows;
|
|
@@ -134,8 +134,9 @@ export class Dock {
|
|
|
134
134
|
/** The bottom four rows, wrapped in CSI 2026 (synchronized output —
|
|
135
135
|
* the pi trick against flicker). The cursor ends at the input line's
|
|
136
136
|
* edit position. v3 §03: the upper ╌ row, the input row, the lower
|
|
137
|
-
* ╌ row, the status row — the status is dim (
|
|
138
|
-
* come from the CLI's composition).
|
|
137
|
+
* ╌ row, the status row — the status is dim (bold accents inside
|
|
138
|
+
* come from the CLI's composition). TUI v5 #16g: the idle status
|
|
139
|
+
* row carries the right-aligned "/ commands · ↑ history" hint. */
|
|
139
140
|
redraw() {
|
|
140
141
|
if (!this.#active)
|
|
141
142
|
return;
|
|
@@ -144,7 +145,7 @@ export class Dock {
|
|
|
144
145
|
const W = this.#width;
|
|
145
146
|
const sep = `${p.dim}${"╌".repeat(W)}${p.reset}`;
|
|
146
147
|
const status = `${this.#status}${this.#tail === "" ? "" : ` · ${this.#tail}`}`;
|
|
147
|
-
const statusLine = this.#question ??
|
|
148
|
+
const statusLine = this.#question ?? this.#statusRow(status, p, W);
|
|
148
149
|
const inp = this.#inputState();
|
|
149
150
|
const out = [];
|
|
150
151
|
// P3 (审查): the DEC private-mode SET/RESET needs the "?" prefix —
|
|
@@ -160,7 +161,7 @@ export class Dock {
|
|
|
160
161
|
const item = menu.items[i];
|
|
161
162
|
const row = H - 4 - (menu.items.length - 1 - i);
|
|
162
163
|
const text = i === menu.selected
|
|
163
|
-
? `${p.
|
|
164
|
+
? `${p.bold}▸ ${item.name}${p.reset} ${item.desc}`
|
|
164
165
|
: `${p.dim} ${item.name} ${item.desc}${p.reset}`;
|
|
165
166
|
out.push(`\x1b[${row};1H\x1b[0K${text}`);
|
|
166
167
|
}
|
|
@@ -173,4 +174,20 @@ export class Dock {
|
|
|
173
174
|
out.push("\x1b[?2026l"); // synchronized output OFF
|
|
174
175
|
process.stdout.write(out.join(""));
|
|
175
176
|
}
|
|
177
|
+
/** TUI v5 #16g: the status row — the base status left-aligned, the
|
|
178
|
+
* "/ commands · ↑ history" hint right-aligned in the idle state
|
|
179
|
+
* (tail empty, no takeover question). The hint is CUT FIRST when
|
|
180
|
+
* the width is short — the status itself is never truncated for it;
|
|
181
|
+
* the running state carries its own esc hint in the status text, so
|
|
182
|
+
* the non-empty tail suppresses this one. */
|
|
183
|
+
#statusRow(status, p, W) {
|
|
184
|
+
const hint = this.#tail === "" && this.#question === null ? " / commands · ↑ history" : "";
|
|
185
|
+
if (hint === "")
|
|
186
|
+
return `${p.dim}${status}${p.reset}`;
|
|
187
|
+
const statusW = displayWidth(status.replace(/\x1b\[[0-9;]*m/g, ""));
|
|
188
|
+
const hintW = displayWidth(hint);
|
|
189
|
+
if (statusW + hintW > W)
|
|
190
|
+
return `${p.dim}${status}${p.reset}`;
|
|
191
|
+
return `${p.dim}${status}${" ".repeat(W - statusW - hintW)}${hint}${p.reset}`;
|
|
192
|
+
}
|
|
176
193
|
}
|
package/dist/editor.js
CHANGED
|
@@ -212,7 +212,7 @@ export class Editor {
|
|
|
212
212
|
const st = this.dockState();
|
|
213
213
|
const W = (process.stdout.columns ?? 0) || 80; // a degenerate 0 size (no TIOCSWINSZ) falls back
|
|
214
214
|
const cursorCol = Math.min(1 + PROMPT_WIDTH + st.cursor, W);
|
|
215
|
-
process.stdout.write(`\r\x1b[0K${p.
|
|
215
|
+
process.stdout.write(`\r\x1b[0K${p.bold}${PROMPT}${p.reset}${st.line}\x1b[${cursorCol}G`);
|
|
216
216
|
}
|
|
217
217
|
// ---- input ----
|
|
218
218
|
/** Feed raw stdin bytes — the parser. Public for unit tests. */
|
package/dist/render.d.ts
CHANGED
|
@@ -8,22 +8,23 @@
|
|
|
8
8
|
* package has ZERO kiso-core imports: input is data, output is bytes.
|
|
9
9
|
*/
|
|
10
10
|
/**
|
|
11
|
-
* v2a — the palette, centralized (no hard-coded codes elsewhere)
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
11
|
+
* v2a — the palette, centralized (no hard-coded codes elsewhere); v5
|
|
12
|
+
* (TUI v5 #16e, the v4.1 design): the decorative blue (38;5;75) is
|
|
13
|
+
* RETIRED — the identity accents (the you> prompt, the banner tagline,
|
|
14
|
+
* ✓ marks, slash-command names, the ▍ user rail, the input brick) are
|
|
15
|
+
* bright-white BOLD (SGR 1); `code` is the content semantic tint for
|
|
16
|
+
* inline code spans in assistant text (256-color 110 — the cube color
|
|
17
|
+
* nearest the design's #8fb4d8); red for errors, dim for metadata,
|
|
18
|
+
* green for the diff additions. NO_COLOR set, or a non-TTY output →
|
|
19
|
+
* every code is empty, so pipes and CI carry ZERO ANSI (the existing
|
|
20
|
+
* byte-level e2e assertions guard it). Everything not listed is plain.
|
|
20
21
|
*/
|
|
21
22
|
export interface Palette {
|
|
22
|
-
readonly
|
|
23
|
+
readonly bold: string;
|
|
23
24
|
readonly dim: string;
|
|
24
25
|
readonly red: string;
|
|
25
26
|
readonly green: string;
|
|
26
|
-
readonly
|
|
27
|
+
readonly code: string;
|
|
27
28
|
readonly reset: string;
|
|
28
29
|
}
|
|
29
30
|
export declare const COLOR_ON: Palette;
|
|
@@ -36,6 +37,15 @@ export declare function palette(): Palette;
|
|
|
36
37
|
* EVERY externally-sourced string must pass through this before any output.
|
|
37
38
|
*/
|
|
38
39
|
export declare function escapeTerminal(text: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* TUI v5 #16e: the inline-code tint — backtick spans in ONE line of
|
|
42
|
+
* assistant body text get the `code` color. Deliberately NOT a markdown
|
|
43
|
+
* engine: single level only (`[^`]*` cannot nest), a span never matches
|
|
44
|
+
* across lines (the caller passes one line; an opener without a closer
|
|
45
|
+
* on the same line stays plain). NO_COLOR / pipes → the codes are empty
|
|
46
|
+
* strings → the line passes through byte-identical.
|
|
47
|
+
*/
|
|
48
|
+
export declare function colorInlineCode(line: string): string;
|
|
39
49
|
/** The canonical-path resolver for the approval detail — injected by the
|
|
40
50
|
* caller (the CLI passes the tools' own resolution). The tui package is
|
|
41
51
|
* pure terminal: input is data, output is bytes, zero runtime deps. */
|
package/dist/render.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* kiso-core's Event — the CLI translates Event → RenderInput. The tui
|
|
8
8
|
* package has ZERO kiso-core imports: input is data, output is bytes.
|
|
9
9
|
*/
|
|
10
|
-
export const COLOR_ON = {
|
|
11
|
-
export const COLOR_OFF = {
|
|
10
|
+
export const COLOR_ON = { bold: "\x1b[1m", dim: "\x1b[2m", red: "\x1b[31m", green: "\x1b[32m", code: "\x1b[38;5;110m", reset: "\x1b[0m" };
|
|
11
|
+
export const COLOR_OFF = { bold: "", dim: "", red: "", green: "", code: "", reset: "" };
|
|
12
12
|
export function palette() {
|
|
13
13
|
return process.env.NO_COLOR === undefined && process.stdout.isTTY ? COLOR_ON : COLOR_OFF;
|
|
14
14
|
}
|
|
@@ -26,6 +26,20 @@ export function escapeTerminal(text) {
|
|
|
26
26
|
.replace(/[\u0080-\u009f]/g, "") // C1
|
|
27
27
|
.replace(/[\u202a-\u202e\u2066-\u2069]/g, ""); // bidi
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* TUI v5 #16e: the inline-code tint — backtick spans in ONE line of
|
|
31
|
+
* assistant body text get the `code` color. Deliberately NOT a markdown
|
|
32
|
+
* engine: single level only (`[^`]*` cannot nest), a span never matches
|
|
33
|
+
* across lines (the caller passes one line; an opener without a closer
|
|
34
|
+
* on the same line stays plain). NO_COLOR / pipes → the codes are empty
|
|
35
|
+
* strings → the line passes through byte-identical.
|
|
36
|
+
*/
|
|
37
|
+
export function colorInlineCode(line) {
|
|
38
|
+
const p = palette();
|
|
39
|
+
if (p.code === "" || p.reset === "")
|
|
40
|
+
return line;
|
|
41
|
+
return line.replace(/`([^`]*)`/g, `${p.code}\`$1\`${p.reset}`);
|
|
42
|
+
}
|
|
29
43
|
/**
|
|
30
44
|
* Render one event. `text` may be a continuation (text_delta appends to the
|
|
31
45
|
* current line); `newline` says whether the line is complete.
|
|
@@ -58,9 +72,9 @@ export function renderEvent(ev, prevThinking = false, resolvePath = (p) => p) {
|
|
|
58
72
|
const p = palette();
|
|
59
73
|
switch (ev.type) {
|
|
60
74
|
case "user_input":
|
|
61
|
-
// v2a:
|
|
62
|
-
// itself; this render is the REPLAY path).
|
|
63
|
-
return { text: `${p.
|
|
75
|
+
// v2a/v5: bold (the identity accent — the interactive prompt
|
|
76
|
+
// echoes itself; this render is the REPLAY path).
|
|
77
|
+
return { text: `${p.bold}you> ${escapeTerminal(typeof ev.content === "string" ? ev.content : "(content)")}${p.reset}\n`, newline: true, prompt: false };
|
|
64
78
|
case "text_delta":
|
|
65
79
|
return { text: escapeTerminal(ev.text), newline: false, prompt: false };
|
|
66
80
|
case "text_end":
|
|
@@ -164,9 +178,9 @@ function approvalDetail(name, input, resolvePath) {
|
|
|
164
178
|
* code; failures (isError) are ✗. Pure and deterministic.
|
|
165
179
|
*/
|
|
166
180
|
export function renderToolSummary(name, input, result) {
|
|
167
|
-
// v2a: ✓ is a
|
|
181
|
+
// v2a/v5: ✓ is a bold identity accent; ✗ stays red.
|
|
168
182
|
const p = palette();
|
|
169
|
-
const mark = result.isError ? `${p.red}✗${p.reset}` : `${p.
|
|
183
|
+
const mark = result.isError ? `${p.red}✗${p.reset}` : `${p.bold}✓${p.reset}`;
|
|
170
184
|
const shortName = name.replace("_file", "");
|
|
171
185
|
const detail = toolSummaryDetail(name, input, result);
|
|
172
186
|
return `${mark} ${escapeTerminal(`${shortName} ${detail}`)}`;
|
|
@@ -315,7 +329,7 @@ export function renderRecap(s) {
|
|
|
315
329
|
}
|
|
316
330
|
if (s.ctxLeftPct !== null)
|
|
317
331
|
parts.push(`ctx left ~${Math.round(s.ctxLeftPct)}%`);
|
|
318
|
-
return `${p.
|
|
332
|
+
return `${p.bold}▞${p.reset} ${parts.join(" · ")}\n`;
|
|
319
333
|
}
|
|
320
334
|
/** One-line summary of a session, for `kiso sessions`. */
|
|
321
335
|
export function renderSessionLine(meta) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-tui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.26",
|
|
4
4
|
"description": "kiso tui \u2014 the pure terminal layer (cell renderer, dock, raw editor, diff, palette). Zero runtime dependencies: input is data, output is bytes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|