@gleapai/kai-bridge 0.10.1 → 0.11.0

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/src/tui.mjs ADDED
@@ -0,0 +1,76 @@
1
+ // Terminal drawing for the wizard and status screens: ANSI colours that
2
+ // switch off on pipes / NO_COLOR, and box-drawn panels laid out side by
3
+ // side when the terminal is wide enough. No dependency on purpose.
4
+
5
+ const ANSI_RE = /\x1b\[[0-9;]*m/g;
6
+
7
+ export function colorsEnabled({ stream = process.stdout, env = process.env } = {}) {
8
+ if (env.NO_COLOR) return false;
9
+ if (env.FORCE_COLOR) return true;
10
+ return !!stream.isTTY && env.TERM !== "dumb";
11
+ }
12
+
13
+ /** `palette().green("ok")` — every style is an identity function when colours are off. */
14
+ export function palette(on = colorsEnabled()) {
15
+ const wrap = (open, close) => (s) => (on ? `\x1b[${open}m${s}\x1b[${close}m` : String(s));
16
+ return {
17
+ on,
18
+ bold: wrap(1, 22),
19
+ dim: wrap(2, 22),
20
+ red: wrap(31, 39),
21
+ green: wrap(32, 39),
22
+ yellow: wrap(33, 39),
23
+ blue: wrap(34, 39),
24
+ magenta: wrap(35, 39),
25
+ cyan: wrap(36, 39),
26
+ };
27
+ }
28
+
29
+ export const strip = (s) => String(s).replace(ANSI_RE, "");
30
+ /** Printed width of a string — styling stripped. Box glyphs count as one cell. */
31
+ export const width = (s) => [...strip(s)].length;
32
+ export const padRight = (s, n) => s + " ".repeat(Math.max(0, n - width(s)));
33
+
34
+ /**
35
+ * Rounded banner: title left, `right` flush right.
36
+ * ╭──────────────────────╮
37
+ * │ TITLE v1 ● ok │
38
+ * ╰──────────────────────╯
39
+ */
40
+ export function banner({ left, right = "", cols = 64, paint = palette() }) {
41
+ const inner = Math.max(cols - 2, width(left) + width(right) + 6);
42
+ const gap = inner - 2 - width(left) - width(right) - 2;
43
+ const line = `│ ${left}${" ".repeat(Math.max(1, gap))}${right} │`;
44
+ return [paint.magenta(`╭${"─".repeat(inner)}╮`), paint.magenta("│") + line.slice(1, -1) + paint.magenta("│"), paint.magenta(`╰${"─".repeat(inner)}╯`)];
45
+ }
46
+
47
+ /**
48
+ * One box with a title in its top edge. `inner` widens it beyond its content.
49
+ * ┌ title ───────┐
50
+ * │ line │
51
+ * └──────────────┘
52
+ */
53
+ export function panel(title, lines, { inner = 0, paint = palette() } = {}) {
54
+ const w = Math.max(inner, width(title) + 3, ...lines.map((l) => width(l) + 2));
55
+ const top = paint.dim(`┌ ${title} ${"─".repeat(Math.max(0, w - width(title) - 2))}┐`);
56
+ const body = lines.map((l) => `${paint.dim("│")} ${padRight(l, w - 2)} ${paint.dim("│")}`);
57
+ return [top, ...body, paint.dim(`└${"─".repeat(w)}┘`)];
58
+ }
59
+
60
+ /** Blocks (arrays of lines) next to each other when they fit in `cols`, else stacked. */
61
+ export function layout(blocks, { cols = 80, gap = 1 } = {}) {
62
+ const widths = blocks.map((b) => Math.max(...b.map(width)));
63
+ const total = widths.reduce((a, b) => a + b, 0) + gap * (blocks.length - 1);
64
+ if (total > cols) return blocks.flatMap((b, i) => (i < blocks.length - 1 ? [...b, ""] : b));
65
+ const rows = Math.max(...blocks.map((b) => b.length));
66
+ const lines = [];
67
+ for (let r = 0; r < rows; r += 1) {
68
+ lines.push(blocks.map((b, i) => padRight(b[r] ?? "", widths[i])).join(" ".repeat(gap)).replace(/\s+$/, ""));
69
+ }
70
+ return lines;
71
+ }
72
+
73
+ /** The terminal width to draw for — capped so wide monitors keep a readable block. */
74
+ export function terminalColumns({ stream = process.stdout, max = 72, min = 40 } = {}) {
75
+ return Math.max(min, Math.min(max, stream.columns || 80));
76
+ }