@vincemakes/kiso-tui 0.1.20 → 0.1.23
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 +81 -10
- package/dist/index.d.ts +1 -1
- package/dist/render.d.ts +91 -4
- package/dist/render.js +7 -2
- package/package.json +2 -2
package/dist/editor.js
CHANGED
|
@@ -71,6 +71,7 @@ export const PROMPT = "▌you> "; // the kiso brick motif: one blue half-block,
|
|
|
71
71
|
export const PROMPT_WIDTH = displayWidth(PROMPT);
|
|
72
72
|
export const MENU_ITEMS = [
|
|
73
73
|
{ name: "/mode", desc: "switch the approval tier (manual/default/accept-edits/plan/bypass)" },
|
|
74
|
+
{ name: "/model", desc: "list model profiles; switch with /model <name|provider/model>" },
|
|
74
75
|
{ name: "/compact", desc: "summarize the older conversation to free context" },
|
|
75
76
|
{ name: "/think", desc: "show the last full thinking block" },
|
|
76
77
|
{ name: "/last", desc: "show the most recent tool call's input and output" },
|
|
@@ -98,6 +99,12 @@ export class Editor {
|
|
|
98
99
|
#onRender;
|
|
99
100
|
#menuOpen = false; // v3 §04: the slash-command menu
|
|
100
101
|
#menuSel = 0;
|
|
102
|
+
// A2 (手感): the session-scoped input history — every submitted TURN
|
|
103
|
+
// line (never a question answer), capped at 100, never persisted. ↑↓
|
|
104
|
+
// navigate it ONLY from an empty input or while already browsing.
|
|
105
|
+
#history = [];
|
|
106
|
+
#historyIdx = null;
|
|
107
|
+
#preBrowse = [];
|
|
101
108
|
#pending = ""; // an incomplete ESC/CSI prefix across chunks
|
|
102
109
|
#decoder = new TextDecoder();
|
|
103
110
|
#entered = false;
|
|
@@ -233,6 +240,15 @@ export class Editor {
|
|
|
233
240
|
this.#scroll = 0;
|
|
234
241
|
this.#refreshMenu();
|
|
235
242
|
}
|
|
243
|
+
else if (this.#historyIdx !== null) {
|
|
244
|
+
// A2: Esc exits the history browse — the pre-browse
|
|
245
|
+
// (empty) input returns.
|
|
246
|
+
this.#historyIdx = null;
|
|
247
|
+
this.#chars = [...this.#preBrowse];
|
|
248
|
+
this.#cursor = this.#chars.length;
|
|
249
|
+
this.#reflow();
|
|
250
|
+
this.#onRender();
|
|
251
|
+
}
|
|
236
252
|
else {
|
|
237
253
|
this.#escapeCb?.();
|
|
238
254
|
i += 1;
|
|
@@ -316,13 +332,20 @@ export class Editor {
|
|
|
316
332
|
this.#onRender();
|
|
317
333
|
}
|
|
318
334
|
}
|
|
319
|
-
else if (final === "A"
|
|
320
|
-
// v3 §04: ↑↓
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
335
|
+
else if (final === "A" || final === "B") {
|
|
336
|
+
// v3 §04: the menu owns ↑↓ while open (the selection, never the
|
|
337
|
+
// cursor). A2 (手感): otherwise ↑↓ navigate the session history
|
|
338
|
+
// — ONLY from an empty input or while already browsing; mid-edit
|
|
339
|
+
// the cursor semantics are unchanged (↑↓ do nothing).
|
|
340
|
+
if (this.#menuOpen) {
|
|
341
|
+
if (final === "A")
|
|
342
|
+
this.#menuSel = Math.max(0, this.#menuSel - 1);
|
|
343
|
+
else
|
|
344
|
+
this.#menuSel = Math.min(this.#menuFiltered().length - 1, this.#menuSel + 1);
|
|
345
|
+
}
|
|
346
|
+
else if (this.#historyIdx !== null || this.line() === "") {
|
|
347
|
+
this.#historyMove(final === "A" ? -1 : 1);
|
|
348
|
+
}
|
|
326
349
|
this.#onRender();
|
|
327
350
|
}
|
|
328
351
|
else if (final === "D") {
|
|
@@ -342,6 +365,8 @@ export class Editor {
|
|
|
342
365
|
}
|
|
343
366
|
// ---- editing ----
|
|
344
367
|
#insert(cp) {
|
|
368
|
+
if (this.#historyIdx !== null)
|
|
369
|
+
this.#historyIdx = null; // editing leaves the browse
|
|
345
370
|
this.#chars.splice(this.#cursor, 0, cp);
|
|
346
371
|
this.#cursor += 1;
|
|
347
372
|
this.#reflow();
|
|
@@ -351,6 +376,8 @@ export class Editor {
|
|
|
351
376
|
#backspace() {
|
|
352
377
|
if (this.#cursor === 0)
|
|
353
378
|
return;
|
|
379
|
+
if (this.#historyIdx !== null)
|
|
380
|
+
this.#historyIdx = null; // editing leaves the browse
|
|
354
381
|
this.#chars.splice(this.#cursor - 1, 1);
|
|
355
382
|
this.#cursor -= 1;
|
|
356
383
|
this.#reflow();
|
|
@@ -397,10 +424,20 @@ export class Editor {
|
|
|
397
424
|
#submit() {
|
|
398
425
|
let line = String.fromCodePoint(...this.#chars);
|
|
399
426
|
if (this.#menuOpen) {
|
|
400
|
-
//
|
|
427
|
+
// A1 (手感): Enter submits the EXACT selection directly; a
|
|
428
|
+
// PARTIAL selection COMPLETES the buffer (the Tab semantics)
|
|
429
|
+
// without submitting — the user reviews and presses Enter
|
|
430
|
+
// again. The old behavior executed the completed command on
|
|
431
|
+
// the first Enter, before the user had seen the completion.
|
|
401
432
|
const m = this.#menuFiltered()[this.#menuSel];
|
|
402
|
-
if (m !== undefined)
|
|
403
|
-
|
|
433
|
+
if (m !== undefined && m.name !== line) {
|
|
434
|
+
this.#chars = [...m.name].map((ch) => ch.codePointAt(0));
|
|
435
|
+
this.#cursor = this.#chars.length;
|
|
436
|
+
this.#reflow();
|
|
437
|
+
this.#refreshMenu();
|
|
438
|
+
this.#onRender();
|
|
439
|
+
return; // completed, not executed
|
|
440
|
+
}
|
|
404
441
|
}
|
|
405
442
|
this.#chars = [];
|
|
406
443
|
this.#cursor = 0;
|
|
@@ -418,8 +455,42 @@ export class Editor {
|
|
|
418
455
|
else {
|
|
419
456
|
this.#pendingLines.push(line); // nobody wired yet — hold it
|
|
420
457
|
}
|
|
458
|
+
// A2: the history remembers submitted TURN lines — never question
|
|
459
|
+
// answers, never empties; adjacent duplicates collapse.
|
|
460
|
+
if (cb === null && line !== "") {
|
|
461
|
+
if (this.#history[this.#history.length - 1] !== line)
|
|
462
|
+
this.#history.push(line);
|
|
463
|
+
if (this.#history.length > 100)
|
|
464
|
+
this.#history.shift();
|
|
465
|
+
}
|
|
421
466
|
this.#onRender();
|
|
422
467
|
}
|
|
468
|
+
/** A2: step the history browse; a delta past the newest exits back to
|
|
469
|
+
* the pre-browse input. */
|
|
470
|
+
#historyMove(delta) {
|
|
471
|
+
if (this.#history.length === 0)
|
|
472
|
+
return;
|
|
473
|
+
if (this.#historyIdx === null) {
|
|
474
|
+
this.#preBrowse = this.#chars; // entering from an empty input
|
|
475
|
+
this.#historyIdx = this.#history.length - 1;
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
const next = this.#historyIdx + delta;
|
|
479
|
+
if (next < 0)
|
|
480
|
+
return; // the oldest entry — stay
|
|
481
|
+
this.#historyIdx = next;
|
|
482
|
+
if (next >= this.#history.length) {
|
|
483
|
+
this.#historyIdx = null; // past the newest — exit the browse
|
|
484
|
+
this.#chars = [...this.#preBrowse];
|
|
485
|
+
this.#cursor = this.#chars.length;
|
|
486
|
+
this.#reflow();
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
this.#chars = [...this.#history[this.#historyIdx]].map((ch) => ch.codePointAt(0));
|
|
491
|
+
this.#cursor = this.#chars.length;
|
|
492
|
+
this.#reflow();
|
|
493
|
+
}
|
|
423
494
|
// ---- width-based horizontal scroll ----
|
|
424
495
|
#reflow() {
|
|
425
496
|
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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-tui",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "kiso tui
|
|
3
|
+
"version": "0.1.23",
|
|
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",
|
|
7
7
|
"exports": {
|