@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 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.blue}you> ${escapeTerminal(text)}${p.reset}\n`);
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 SGR block, no prefix — every
409
- // line carries the block's highlight (multi-line whole;
410
- // resize-safe). TUI v4 #16c: the fixed dark background
411
- // (48;5;237) is GONE reverse video (7m) swaps foreground/
412
- // background, so the block follows the terminal theme (light
413
- // theme → dark text on light). Pipes stay plain.
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.blue}✓ ${name}${p.reset} (${summary}${delta}, ${elapsed}s)`];
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.blue}⏸${p.reset}`];
436
- // v2e: the mini-diff — ▎ blue edge (the brick motif), - red /
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.blue}▎${p.reset}${body}`);
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.blue}${SPINNER[this.#spinnerI % SPINNER.length]}${p.reset} ${elapsed}s`];
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
- return wrapped.length > 0 ? wrapped : [""];
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 (blue accents inside
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().blue === "" || rows < 4)
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 (blue accents inside
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 ?? `${p.dim}${status}${p.reset}`;
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.blue}▸ ${item.name}${p.reset} ${item.desc}`
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.blue}${PROMPT}${p.reset}${st.line}\x1b[${cursorCol}G`);
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): ONE
12
- * accent blue, ANSI 256 color 75 — for the identity accents (the you>
13
- * prompt, the banner tagline, marks, slash-command names); red for
14
- * errors; dim for metadata. NO_COLOR set, or a non-TTY output every
15
- * code is empty, so pipes and CI carry ZERO ANSI (the existing byte-level
16
- * e2e assertions guard it). Everything not listed here is plain.
17
- * v3: `rev` the user-message block highlight (SGR 7 reverse video,
18
- * TUI v4 #16c: the fixed dark background is GONE reverse follows the
19
- * terminal theme).
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 blue: string;
23
+ readonly bold: string;
23
24
  readonly dim: string;
24
25
  readonly red: string;
25
26
  readonly green: string;
26
- readonly rev: string;
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 = { blue: "\x1b[38;5;75m", dim: "\x1b[2m", red: "\x1b[31m", green: "\x1b[32m", rev: "\x1b[7m", reset: "\x1b[0m" };
11
- export const COLOR_OFF = { blue: "", dim: "", red: "", green: "", rev: "", reset: "" };
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: blue (the identity accent — the interactive prompt echoes
62
- // itself; this render is the REPLAY path).
63
- return { text: `${p.blue}you> ${escapeTerminal(typeof ev.content === "string" ? ev.content : "(content)")}${p.reset}\n`, newline: true, prompt: false };
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 blue identity accent; ✗ stays red.
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.blue}✓${p.reset}`;
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.blue}▞${p.reset} ${parts.join(" · ")}\n`;
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.24",
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",