@vincemakes/kiso-tui 0.1.35 → 0.1.37
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/components.d.ts +26 -3
- package/dist/components.js +396 -44
- package/dist/compositor.d.ts +24 -7
- package/dist/compositor.js +111 -39
- package/dist/editor.d.ts +2 -6
- package/dist/editor.js +11 -53
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/render.d.ts +35 -4
- package/dist/render.js +115 -29
- package/dist/width.d.ts +15 -0
- package/dist/width.js +59 -0
- package/package.json +1 -1
package/dist/render.js
CHANGED
|
@@ -7,8 +7,9 @@
|
|
|
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
|
-
|
|
11
|
-
export const
|
|
10
|
+
import { charWidth, displayWidth } from "./width.js";
|
|
11
|
+
export const COLOR_ON = { bold: "\x1b[1m", dim: "\x1b[2m", red: "\x1b[31m", green: "\x1b[32m", code: "\x1b[38;5;110m", rv: "\x1b[7m", rvEnd: "\x1b[27m", reset: "\x1b[0m" };
|
|
12
|
+
export const COLOR_OFF = { bold: "", dim: "", red: "", green: "", code: "", rv: "", rvEnd: "", reset: "" };
|
|
12
13
|
export function palette() {
|
|
13
14
|
return process.env.NO_COLOR === undefined && process.stdout.isTTY ? COLOR_ON : COLOR_OFF;
|
|
14
15
|
}
|
|
@@ -293,43 +294,81 @@ export function renderTerminalGap(statusLine) {
|
|
|
293
294
|
* Pure.
|
|
294
295
|
*/
|
|
295
296
|
export const TAGLINE = "the coding agent that survives kill -9";
|
|
297
|
+
/** v6's existing logo — W1's COMPACT tier, byte-identical, no redraw. */
|
|
296
298
|
const LOGO_ROWS = ["█ █ ▀█▀ █▀▀ █▀█", "█▀▄ █ ▀▀█ █ █", "▀ ▀ ▀▀▀ ▀▀▀ ▀▀▀"];
|
|
297
|
-
/**
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
299
|
+
/** W1's BIG tier (36x6, `█` and space only — no half-blocks, so there is
|
|
300
|
+
* no tile seam to lose in a font that renders ▀ ▄ at the wrong height).
|
|
301
|
+
* Each pixel is two cells wide on purpose (a terminal cell is ~1:2);
|
|
302
|
+
* the render indents two — 38 columns total, clears 40. */
|
|
303
|
+
const BIG_LOGO_ROWS = [
|
|
304
|
+
"██ ██ ██████ ████████ ████████",
|
|
305
|
+
"██ ██ ██ ██ ██ ██",
|
|
306
|
+
"████ ██ ████████ ██ ██",
|
|
307
|
+
"████ ██ ██ ██ ██",
|
|
308
|
+
"██ ██ ██ ██ ██ ██",
|
|
309
|
+
"██ ██ ██████ ████████ ████████",
|
|
310
|
+
];
|
|
311
|
+
/** v3 §01 (W1): truncate a row at `width`, marking the hidden span
|
|
312
|
+
* " (+N)". W1: the width math is the charWidth authority (the banner's
|
|
313
|
+
* brick glyphs are 1 cell — the art's 38 columns clear 40), and the
|
|
314
|
+
* marker's own cells are part of the row — the visible cut leaves room
|
|
315
|
+
* for it, so a truncated row never exceeds W (a cut row carries the
|
|
316
|
+
* marker INSIDE the width; a row that fits is returned untouched). */
|
|
307
317
|
export function truncateRow(row, width) {
|
|
308
|
-
|
|
318
|
+
const total = displayWidth(row);
|
|
319
|
+
if (total <= width)
|
|
309
320
|
return row;
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
let
|
|
313
|
-
for (
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
321
|
+
// iterate the marker to a fixpoint: the marker's width changes the
|
|
322
|
+
// cut, the cut changes the hidden count the marker reports
|
|
323
|
+
let marker = " (+0)";
|
|
324
|
+
for (;;) {
|
|
325
|
+
const cut = Math.max(0, width - displayWidth(marker));
|
|
326
|
+
let w = 0;
|
|
327
|
+
let i = 0;
|
|
328
|
+
while (i < row.length) {
|
|
329
|
+
const cp = row.codePointAt(i);
|
|
330
|
+
const cw = charWidth(cp);
|
|
331
|
+
if (w + cw > cut)
|
|
332
|
+
break;
|
|
333
|
+
w += cw;
|
|
334
|
+
i += cp > 0xffff ? 2 : 1; // code-point stepping — never split a pair
|
|
335
|
+
}
|
|
336
|
+
const next = ` (+${total - w})`;
|
|
337
|
+
if (next === marker)
|
|
338
|
+
return `${row.slice(0, i)}${next}`;
|
|
339
|
+
marker = next;
|
|
318
340
|
}
|
|
319
|
-
return `${row.slice(0, i)} (+${displayW(row) - w})`;
|
|
320
341
|
}
|
|
321
|
-
/** v3 §01 (V6-2): the banner lines for a width W
|
|
322
|
-
*
|
|
323
|
-
|
|
342
|
+
/** v3 §01 (V6-2) + W1: the banner lines for a width W and height H —
|
|
343
|
+
* the tier table (extends the existing "under 40 columns, skip the
|
|
344
|
+
* logo" rule with a HEIGHT input):
|
|
345
|
+
* W ≥ 40 and H ≥ 20 → BIG (the 36x6 wordmark, 2-column indent)
|
|
346
|
+
* W ≥ 40 and 14–19 rows → COMPACT (v6's LOGO_ROWS, byte-identical)
|
|
347
|
+
* anything smaller → text rows only
|
|
348
|
+
* then the blank, then "vX — tagline" — the art IS the wordmark, so the
|
|
349
|
+
* text row does not repeat the name — then extensions — then the W5
|
|
350
|
+
* resume list (BIG only, W5). Every row truncates at the terminal width
|
|
351
|
+
* with a " (+N)" marker. Pure. */
|
|
352
|
+
export function bannerLines(W, H, version, extensionsText, resume = [], now = Date.now()) {
|
|
324
353
|
const rows = [];
|
|
325
354
|
if (W >= 40) {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
355
|
+
if (H >= 20) {
|
|
356
|
+
for (const r of BIG_LOGO_ROWS)
|
|
357
|
+
rows.push(truncateRow(` ${r}`, W));
|
|
358
|
+
}
|
|
359
|
+
else if (H >= 14) {
|
|
360
|
+
for (const r of LOGO_ROWS)
|
|
361
|
+
rows.push(truncateRow(r, W));
|
|
362
|
+
}
|
|
329
363
|
}
|
|
330
|
-
rows.
|
|
364
|
+
if (rows.length > 0)
|
|
365
|
+
rows.push("");
|
|
366
|
+
rows.push(truncateRow(`v${version} — ${TAGLINE}`, W));
|
|
331
367
|
if (extensionsText !== "")
|
|
332
368
|
rows.push(truncateRow(extensionsText, W));
|
|
369
|
+
if (W >= 40 && H >= 20 && resume.length > 0) {
|
|
370
|
+
rows.push("", ...renderResumeList(resume, W, now));
|
|
371
|
+
}
|
|
333
372
|
return rows;
|
|
334
373
|
}
|
|
335
374
|
export function renderRecap(s) {
|
|
@@ -353,3 +392,50 @@ export function renderSessionLine(meta) {
|
|
|
353
392
|
// round 8: the title is the user's first prompt — model/user text, escaped.
|
|
354
393
|
return `${meta.id.padEnd(24)} ${meta.runs} runs ${String(meta.events).padStart(5)} events ${when} ${escapeTerminal(meta.title)}`;
|
|
355
394
|
}
|
|
395
|
+
export function relativeTime(updatedAt, now) {
|
|
396
|
+
const s = Math.max(0, now - updatedAt) / 1000;
|
|
397
|
+
if (s < 60)
|
|
398
|
+
return "now";
|
|
399
|
+
const m = Math.floor(s / 60);
|
|
400
|
+
if (m < 60)
|
|
401
|
+
return `${m}m ago`;
|
|
402
|
+
const h = Math.floor(m / 60);
|
|
403
|
+
if (h < 24)
|
|
404
|
+
return `${h}h ago`;
|
|
405
|
+
const d = Math.floor(h / 24);
|
|
406
|
+
if (d < 7)
|
|
407
|
+
return `${d}d ago`;
|
|
408
|
+
return `${Math.floor(d / 7)}w ago`;
|
|
409
|
+
}
|
|
410
|
+
function titleCut(text, max) {
|
|
411
|
+
if (displayWidth(text) <= max)
|
|
412
|
+
return text;
|
|
413
|
+
const room = max - displayWidth("…");
|
|
414
|
+
let w = 0;
|
|
415
|
+
let i = 0;
|
|
416
|
+
while (i < text.length) {
|
|
417
|
+
const cp = text.codePointAt(i);
|
|
418
|
+
const cw = charWidth(cp);
|
|
419
|
+
if (w + cw > room)
|
|
420
|
+
break;
|
|
421
|
+
w += cw;
|
|
422
|
+
i += cp > 0xffff ? 2 : 1;
|
|
423
|
+
}
|
|
424
|
+
return text.slice(0, i) + "…";
|
|
425
|
+
}
|
|
426
|
+
export function renderResumeList(metas, W, now) {
|
|
427
|
+
if (metas.length === 0)
|
|
428
|
+
return [];
|
|
429
|
+
const rows = [" ▞ resume"];
|
|
430
|
+
const whens = metas.map((m) => relativeTime(m.updatedAt, now));
|
|
431
|
+
const metaTexts = metas.map((m) => `${m.events} events · ${m.runs} runs`);
|
|
432
|
+
const metaW = Math.max(...metaTexts.map((t) => t.length));
|
|
433
|
+
const titleW = Math.max(1, W - 13 - metaW);
|
|
434
|
+
for (let i = 0; i < metas.length; i += 1) {
|
|
435
|
+
const title = escapeTerminal(metas[i].title);
|
|
436
|
+
const shown = titleCut(title, titleW);
|
|
437
|
+
const pad = titleW - displayWidth(shown);
|
|
438
|
+
rows.push(` ${whens[i].padEnd(7)} ${shown}${" ".repeat(pad)} ${metaTexts[i].padStart(metaW)}`);
|
|
439
|
+
}
|
|
440
|
+
return rows;
|
|
441
|
+
}
|
package/dist/width.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The display-width primitives — the SINGLE width authority (TUI v5
|
|
3
|
+
* #16e: "charWidth is the width authority"). The eastAsianWidth table
|
|
4
|
+
* is a ~40-line subset (CJK ideographs/kana/hangul/fullwidth/common
|
|
5
|
+
* wide symbols = 2, everything else = 1 — the box-drawing/brick glyphs
|
|
6
|
+
* █▀▄▞▸ are narrow). Known limitation, documented in the README: emoji
|
|
7
|
+
* ZWJ clusters are not guaranteed perfect — each code point counts as
|
|
8
|
+
* its width. Zero dependencies (importable from any module).
|
|
9
|
+
*/
|
|
10
|
+
/** A code point's display width: 2 for the wide ranges, 1 otherwise. */
|
|
11
|
+
export declare function charWidth(cp: number): number;
|
|
12
|
+
/** Display width of a code-point array (cursor math, scrolling). */
|
|
13
|
+
export declare function widthOf(chars: readonly number[]): number;
|
|
14
|
+
/** Display width of a string. */
|
|
15
|
+
export declare function displayWidth(text: string): number;
|
package/dist/width.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The display-width primitives — the SINGLE width authority (TUI v5
|
|
3
|
+
* #16e: "charWidth is the width authority"). The eastAsianWidth table
|
|
4
|
+
* is a ~40-line subset (CJK ideographs/kana/hangul/fullwidth/common
|
|
5
|
+
* wide symbols = 2, everything else = 1 — the box-drawing/brick glyphs
|
|
6
|
+
* █▀▄▞▸ are narrow). Known limitation, documented in the README: emoji
|
|
7
|
+
* ZWJ clusters are not guaranteed perfect — each code point counts as
|
|
8
|
+
* its width. Zero dependencies (importable from any module).
|
|
9
|
+
*/
|
|
10
|
+
/** A code point's display width: 2 for the wide ranges, 1 otherwise. */
|
|
11
|
+
export function charWidth(cp) {
|
|
12
|
+
if (cp >= 0x1100 && cp <= 0x115f)
|
|
13
|
+
return 2; // hangul jamo
|
|
14
|
+
if (cp >= 0x2e80 && cp <= 0x303e)
|
|
15
|
+
return 2; // radicals .. CJK punctuation
|
|
16
|
+
if (cp >= 0x3041 && cp <= 0x33ff)
|
|
17
|
+
return 2; // kana, CJK compat
|
|
18
|
+
if (cp >= 0x3400 && cp <= 0x4dbf)
|
|
19
|
+
return 2; // CJK ext A
|
|
20
|
+
if (cp >= 0x4e00 && cp <= 0x9fff)
|
|
21
|
+
return 2; // CJK unified
|
|
22
|
+
if (cp >= 0xa000 && cp <= 0xa4cf)
|
|
23
|
+
return 2; // yi
|
|
24
|
+
if (cp >= 0xa960 && cp <= 0xa97f)
|
|
25
|
+
return 2; // hangul jamo ext
|
|
26
|
+
if (cp >= 0xac00 && cp <= 0xd7a3)
|
|
27
|
+
return 2; // hangul syllables
|
|
28
|
+
if (cp >= 0xf900 && cp <= 0xfaff)
|
|
29
|
+
return 2; // CJK compat ideographs
|
|
30
|
+
if (cp >= 0xfe10 && cp <= 0xfe19)
|
|
31
|
+
return 2; // vertical forms
|
|
32
|
+
if (cp >= 0xfe30 && cp <= 0xfe6f)
|
|
33
|
+
return 2; // CJK compat forms
|
|
34
|
+
if (cp >= 0xff00 && cp <= 0xff60)
|
|
35
|
+
return 2; // fullwidth forms
|
|
36
|
+
if (cp >= 0xffe0 && cp <= 0xffe6)
|
|
37
|
+
return 2; // fullwidth signs
|
|
38
|
+
if (cp >= 0x1f300 && cp <= 0x1f64f)
|
|
39
|
+
return 2; // emoji (misc + emoticons)
|
|
40
|
+
if (cp >= 0x1f900 && cp <= 0x1f9ff)
|
|
41
|
+
return 2; // supplemental emoji
|
|
42
|
+
if (cp >= 0x20000 && cp <= 0x3fffd)
|
|
43
|
+
return 2; // CJK ext B..G
|
|
44
|
+
return 1;
|
|
45
|
+
}
|
|
46
|
+
/** Display width of a code-point array (cursor math, scrolling). */
|
|
47
|
+
export function widthOf(chars) {
|
|
48
|
+
let w = 0;
|
|
49
|
+
for (const cp of chars)
|
|
50
|
+
w += charWidth(cp);
|
|
51
|
+
return w;
|
|
52
|
+
}
|
|
53
|
+
/** Display width of a string. */
|
|
54
|
+
export function displayWidth(text) {
|
|
55
|
+
let w = 0;
|
|
56
|
+
for (const ch of text)
|
|
57
|
+
w += charWidth(ch.codePointAt(0));
|
|
58
|
+
return w;
|
|
59
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-tui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.37",
|
|
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",
|