@vincemakes/kiso-tui 0.1.20 → 0.1.21
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/editor.js +80 -10
- package/dist/index.d.ts +1 -1
- package/dist/render.d.ts +91 -4
- package/dist/render.js +7 -2
- package/package.json +1 -1
package/dist/editor.js
CHANGED
|
@@ -98,6 +98,12 @@ export class Editor {
|
|
|
98
98
|
#onRender;
|
|
99
99
|
#menuOpen = false; // v3 §04: the slash-command menu
|
|
100
100
|
#menuSel = 0;
|
|
101
|
+
// A2 (手感): the session-scoped input history — every submitted TURN
|
|
102
|
+
// line (never a question answer), capped at 100, never persisted. ↑↓
|
|
103
|
+
// navigate it ONLY from an empty input or while already browsing.
|
|
104
|
+
#history = [];
|
|
105
|
+
#historyIdx = null;
|
|
106
|
+
#preBrowse = [];
|
|
101
107
|
#pending = ""; // an incomplete ESC/CSI prefix across chunks
|
|
102
108
|
#decoder = new TextDecoder();
|
|
103
109
|
#entered = false;
|
|
@@ -233,6 +239,15 @@ export class Editor {
|
|
|
233
239
|
this.#scroll = 0;
|
|
234
240
|
this.#refreshMenu();
|
|
235
241
|
}
|
|
242
|
+
else if (this.#historyIdx !== null) {
|
|
243
|
+
// A2: Esc exits the history browse — the pre-browse
|
|
244
|
+
// (empty) input returns.
|
|
245
|
+
this.#historyIdx = null;
|
|
246
|
+
this.#chars = [...this.#preBrowse];
|
|
247
|
+
this.#cursor = this.#chars.length;
|
|
248
|
+
this.#reflow();
|
|
249
|
+
this.#onRender();
|
|
250
|
+
}
|
|
236
251
|
else {
|
|
237
252
|
this.#escapeCb?.();
|
|
238
253
|
i += 1;
|
|
@@ -316,13 +331,20 @@ export class Editor {
|
|
|
316
331
|
this.#onRender();
|
|
317
332
|
}
|
|
318
333
|
}
|
|
319
|
-
else if (final === "A"
|
|
320
|
-
// v3 §04: ↑↓
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
334
|
+
else if (final === "A" || final === "B") {
|
|
335
|
+
// v3 §04: the menu owns ↑↓ while open (the selection, never the
|
|
336
|
+
// cursor). A2 (手感): otherwise ↑↓ navigate the session history
|
|
337
|
+
// — ONLY from an empty input or while already browsing; mid-edit
|
|
338
|
+
// the cursor semantics are unchanged (↑↓ do nothing).
|
|
339
|
+
if (this.#menuOpen) {
|
|
340
|
+
if (final === "A")
|
|
341
|
+
this.#menuSel = Math.max(0, this.#menuSel - 1);
|
|
342
|
+
else
|
|
343
|
+
this.#menuSel = Math.min(this.#menuFiltered().length - 1, this.#menuSel + 1);
|
|
344
|
+
}
|
|
345
|
+
else if (this.#historyIdx !== null || this.line() === "") {
|
|
346
|
+
this.#historyMove(final === "A" ? -1 : 1);
|
|
347
|
+
}
|
|
326
348
|
this.#onRender();
|
|
327
349
|
}
|
|
328
350
|
else if (final === "D") {
|
|
@@ -342,6 +364,8 @@ export class Editor {
|
|
|
342
364
|
}
|
|
343
365
|
// ---- editing ----
|
|
344
366
|
#insert(cp) {
|
|
367
|
+
if (this.#historyIdx !== null)
|
|
368
|
+
this.#historyIdx = null; // editing leaves the browse
|
|
345
369
|
this.#chars.splice(this.#cursor, 0, cp);
|
|
346
370
|
this.#cursor += 1;
|
|
347
371
|
this.#reflow();
|
|
@@ -351,6 +375,8 @@ export class Editor {
|
|
|
351
375
|
#backspace() {
|
|
352
376
|
if (this.#cursor === 0)
|
|
353
377
|
return;
|
|
378
|
+
if (this.#historyIdx !== null)
|
|
379
|
+
this.#historyIdx = null; // editing leaves the browse
|
|
354
380
|
this.#chars.splice(this.#cursor - 1, 1);
|
|
355
381
|
this.#cursor -= 1;
|
|
356
382
|
this.#reflow();
|
|
@@ -397,10 +423,20 @@ export class Editor {
|
|
|
397
423
|
#submit() {
|
|
398
424
|
let line = String.fromCodePoint(...this.#chars);
|
|
399
425
|
if (this.#menuOpen) {
|
|
400
|
-
//
|
|
426
|
+
// A1 (手感): Enter submits the EXACT selection directly; a
|
|
427
|
+
// PARTIAL selection COMPLETES the buffer (the Tab semantics)
|
|
428
|
+
// without submitting — the user reviews and presses Enter
|
|
429
|
+
// again. The old behavior executed the completed command on
|
|
430
|
+
// the first Enter, before the user had seen the completion.
|
|
401
431
|
const m = this.#menuFiltered()[this.#menuSel];
|
|
402
|
-
if (m !== undefined)
|
|
403
|
-
|
|
432
|
+
if (m !== undefined && m.name !== line) {
|
|
433
|
+
this.#chars = [...m.name].map((ch) => ch.codePointAt(0));
|
|
434
|
+
this.#cursor = this.#chars.length;
|
|
435
|
+
this.#reflow();
|
|
436
|
+
this.#refreshMenu();
|
|
437
|
+
this.#onRender();
|
|
438
|
+
return; // completed, not executed
|
|
439
|
+
}
|
|
404
440
|
}
|
|
405
441
|
this.#chars = [];
|
|
406
442
|
this.#cursor = 0;
|
|
@@ -418,8 +454,42 @@ export class Editor {
|
|
|
418
454
|
else {
|
|
419
455
|
this.#pendingLines.push(line); // nobody wired yet — hold it
|
|
420
456
|
}
|
|
457
|
+
// A2: the history remembers submitted TURN lines — never question
|
|
458
|
+
// answers, never empties; adjacent duplicates collapse.
|
|
459
|
+
if (cb === null && line !== "") {
|
|
460
|
+
if (this.#history[this.#history.length - 1] !== line)
|
|
461
|
+
this.#history.push(line);
|
|
462
|
+
if (this.#history.length > 100)
|
|
463
|
+
this.#history.shift();
|
|
464
|
+
}
|
|
421
465
|
this.#onRender();
|
|
422
466
|
}
|
|
467
|
+
/** A2: step the history browse; a delta past the newest exits back to
|
|
468
|
+
* the pre-browse input. */
|
|
469
|
+
#historyMove(delta) {
|
|
470
|
+
if (this.#history.length === 0)
|
|
471
|
+
return;
|
|
472
|
+
if (this.#historyIdx === null) {
|
|
473
|
+
this.#preBrowse = this.#chars; // entering from an empty input
|
|
474
|
+
this.#historyIdx = this.#history.length - 1;
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
const next = this.#historyIdx + delta;
|
|
478
|
+
if (next < 0)
|
|
479
|
+
return; // the oldest entry — stay
|
|
480
|
+
this.#historyIdx = next;
|
|
481
|
+
if (next >= this.#history.length) {
|
|
482
|
+
this.#historyIdx = null; // past the newest — exit the browse
|
|
483
|
+
this.#chars = [...this.#preBrowse];
|
|
484
|
+
this.#cursor = this.#chars.length;
|
|
485
|
+
this.#reflow();
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
this.#chars = [...this.#history[this.#historyIdx]].map((ch) => ch.codePointAt(0));
|
|
490
|
+
this.#cursor = this.#chars.length;
|
|
491
|
+
this.#reflow();
|
|
492
|
+
}
|
|
423
493
|
// ---- width-based horizontal scroll ----
|
|
424
494
|
#reflow() {
|
|
425
495
|
const W = (process.stdout.columns ?? 0) || 80; // degenerate 0 falls back to 80
|
package/dist/index.d.ts
CHANGED
|
@@ -7,5 +7,5 @@
|
|
|
7
7
|
export { Body, type BodyOptions } from "./body.js";
|
|
8
8
|
export { Dock } from "./dock.js";
|
|
9
9
|
export { Editor, MENU_ITEMS, PROMPT, PROMPT_WIDTH, displayWidth, charWidth, widthOf, type MenuItem, } from "./editor.js";
|
|
10
|
-
export { bannerLines, COLOR_OFF, COLOR_ON, escapeTerminal, foldResult, foldThinking, kUnit, palette, renderEvent, renderRecap, renderSessionLine, renderStatusLine, renderTerminalGap, renderToolSummary, TAGLINE, truncateRow, type Palette, type PathResolver, type RecapStats, type RenderResult, type RunUsage, } from "./render.js";
|
|
10
|
+
export { bannerLines, COLOR_OFF, COLOR_ON, escapeTerminal, foldResult, foldThinking, kUnit, palette, renderEvent, renderRecap, renderSessionLine, renderStatusLine, renderTerminalGap, renderToolSummary, TAGLINE, truncateRow, type Palette, type PathResolver, type RecapStats, type RenderInput, type RenderResult, type RunUsage, } from "./render.js";
|
|
11
11
|
export { editFileDiff, truncateDiff, writeFileDiff, type DiffLine, type DiffResult } from "./diff.js";
|
package/dist/render.d.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Event rendering for the terminal. Pure (testable): given
|
|
3
|
-
* the lines a human sees. Colors are raw ANSI — no
|
|
2
|
+
* Event rendering for the terminal. Pure (testable): given the render
|
|
3
|
+
* input, produce the lines a human sees. Colors are raw ANSI — no
|
|
4
|
+
* dependencies.
|
|
5
|
+
*
|
|
6
|
+
* 手感批 C5: the input is the tui's OWN data shape (RenderInput), never
|
|
7
|
+
* kiso-core's Event — the CLI translates Event → RenderInput. The tui
|
|
8
|
+
* package has ZERO kiso-core imports: input is data, output is bytes.
|
|
4
9
|
*/
|
|
5
|
-
import type { Event } from "@vincemakes/kiso-core";
|
|
6
10
|
/**
|
|
7
11
|
* v2a — the palette, centralized (no hard-coded codes elsewhere): ONE
|
|
8
12
|
* accent — blue, ANSI 256 color 75 — for the identity accents (the you>
|
|
@@ -39,6 +43,89 @@ export interface RenderResult {
|
|
|
39
43
|
readonly newline: boolean;
|
|
40
44
|
readonly prompt: boolean;
|
|
41
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* 手感批 C5 — the render input, the tui's OWN data shape: the subset of
|
|
48
|
+
* an event stream the renderer reads, keyed by type. The CLI translates
|
|
49
|
+
* its Event stream into this (Event → RenderInput) before rendering —
|
|
50
|
+
* the tui package never imports kiso-core. Field names mirror the
|
|
51
|
+
* rendered Event members so the render body stays byte-identical.
|
|
52
|
+
*/
|
|
53
|
+
export type RenderInput = {
|
|
54
|
+
readonly type: "user_input";
|
|
55
|
+
readonly content: string | readonly {
|
|
56
|
+
readonly type?: string;
|
|
57
|
+
readonly text?: string;
|
|
58
|
+
}[];
|
|
59
|
+
} | {
|
|
60
|
+
readonly type: "text_delta";
|
|
61
|
+
readonly text: string;
|
|
62
|
+
} | {
|
|
63
|
+
readonly type: "text_end";
|
|
64
|
+
} | {
|
|
65
|
+
readonly type: "thinking";
|
|
66
|
+
readonly text: string;
|
|
67
|
+
} | {
|
|
68
|
+
readonly type: "tool_call_end";
|
|
69
|
+
readonly name: string;
|
|
70
|
+
readonly input: Readonly<Record<string, unknown>> | null;
|
|
71
|
+
} | {
|
|
72
|
+
readonly type: "tool_execution_started";
|
|
73
|
+
} | {
|
|
74
|
+
readonly type: "tool_execution_succeeded";
|
|
75
|
+
} | {
|
|
76
|
+
readonly type: "tool_execution_failed";
|
|
77
|
+
readonly error: string;
|
|
78
|
+
} | {
|
|
79
|
+
readonly type: "tool_result";
|
|
80
|
+
readonly content: string | readonly {
|
|
81
|
+
readonly type?: string;
|
|
82
|
+
readonly text?: string;
|
|
83
|
+
}[];
|
|
84
|
+
readonly isError: boolean;
|
|
85
|
+
} | {
|
|
86
|
+
readonly type: "permission_requested";
|
|
87
|
+
readonly name: string;
|
|
88
|
+
readonly input: Readonly<Record<string, unknown>>;
|
|
89
|
+
} | {
|
|
90
|
+
readonly type: "permission_decided";
|
|
91
|
+
readonly decision: "approved" | "denied";
|
|
92
|
+
readonly reason?: string;
|
|
93
|
+
} | {
|
|
94
|
+
readonly type: "terminal";
|
|
95
|
+
readonly outcome: {
|
|
96
|
+
readonly kind: "completed";
|
|
97
|
+
} | {
|
|
98
|
+
readonly kind: "max_tokens";
|
|
99
|
+
} | {
|
|
100
|
+
readonly kind: "max_turns";
|
|
101
|
+
readonly turns: number;
|
|
102
|
+
} | {
|
|
103
|
+
readonly kind: "error";
|
|
104
|
+
readonly error: {
|
|
105
|
+
readonly message: string;
|
|
106
|
+
};
|
|
107
|
+
} | {
|
|
108
|
+
readonly kind: "aborted";
|
|
109
|
+
readonly by: string;
|
|
110
|
+
} | {
|
|
111
|
+
readonly kind: "hook_stopped";
|
|
112
|
+
readonly hook: string;
|
|
113
|
+
};
|
|
114
|
+
} | {
|
|
115
|
+
readonly type: "compacted";
|
|
116
|
+
readonly cleared: readonly {
|
|
117
|
+
readonly eventSeq?: number;
|
|
118
|
+
readonly callId: string;
|
|
119
|
+
}[];
|
|
120
|
+
} | {
|
|
121
|
+
readonly type: "summarized";
|
|
122
|
+
readonly coversToSeq: number;
|
|
123
|
+
} | {
|
|
124
|
+
readonly type: "uncertain_pending";
|
|
125
|
+
readonly name: string;
|
|
126
|
+
readonly executionId: string;
|
|
127
|
+
readonly error: string;
|
|
128
|
+
};
|
|
42
129
|
/**
|
|
43
130
|
* Render one event. `text` may be a continuation (text_delta appends to the
|
|
44
131
|
* current line); `newline` says whether the line is complete.
|
|
@@ -58,7 +145,7 @@ export interface RenderResult {
|
|
|
58
145
|
export declare function foldThinking(block: string): string;
|
|
59
146
|
/** v2b — the [result] echo truncates at 160 chars + a /last hint. */
|
|
60
147
|
export declare function foldResult(content: string): string;
|
|
61
|
-
export declare function renderEvent(ev:
|
|
148
|
+
export declare function renderEvent(ev: RenderInput, prevThinking?: boolean, resolvePath?: PathResolver): RenderResult;
|
|
62
149
|
/**
|
|
63
150
|
* B 区: one-line summary of a completed tool call, e.g.
|
|
64
151
|
* ✓ edit src/foo.ts (+12 -3) ✓ read src/bar.ts (140 lines)
|
package/dist/render.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Event rendering for the terminal. Pure (testable): given
|
|
3
|
-
* the lines a human sees. Colors are raw ANSI — no
|
|
2
|
+
* Event rendering for the terminal. Pure (testable): given the render
|
|
3
|
+
* input, produce the lines a human sees. Colors are raw ANSI — no
|
|
4
|
+
* dependencies.
|
|
5
|
+
*
|
|
6
|
+
* 手感批 C5: the input is the tui's OWN data shape (RenderInput), never
|
|
7
|
+
* kiso-core's Event — the CLI translates Event → RenderInput. The tui
|
|
8
|
+
* package has ZERO kiso-core imports: input is data, output is bytes.
|
|
4
9
|
*/
|
|
5
10
|
export const COLOR_ON = { blue: "\x1b[38;5;75m", dim: "\x1b[2m", red: "\x1b[31m", green: "\x1b[32m", bg: "\x1b[48;5;237m", reset: "\x1b[0m" };
|
|
6
11
|
export const COLOR_OFF = { blue: "", dim: "", red: "", green: "", bg: "", reset: "" };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-tui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
4
4
|
"description": "kiso tui — 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",
|