@vertz/tui 0.2.3

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/index.js ADDED
@@ -0,0 +1,2374 @@
1
+ import {
2
+ BORDER_CHARS,
3
+ defaultLayoutProps
4
+ } from "./shared/chunk-5x9fb9b2.js";
5
+ import {
6
+ TerminalBuffer,
7
+ TestAdapter
8
+ } from "./shared/chunk-6y95cgnx.js";
9
+
10
+ // src/index.ts
11
+ import {
12
+ batch,
13
+ computed,
14
+ createContext as createContext2,
15
+ onMount,
16
+ signal as signal10,
17
+ useContext as useContext2
18
+ } from "@vertz/ui";
19
+
20
+ // src/app.ts
21
+ import { lifecycleEffect, popScope as popScope2, pushScope as pushScope2, runCleanups as runCleanups2 } from "@vertz/ui/internals";
22
+
23
+ // src/input/key-parser.ts
24
+ function parseKey(data) {
25
+ const str = data.toString("utf8");
26
+ const code = str.charCodeAt(0);
27
+ if (str.startsWith("\x1B")) {
28
+ return parseEscapeSequence(str);
29
+ }
30
+ if (str === "\r" || str === `
31
+ `) {
32
+ return { name: "return", char: "", ctrl: false, shift: false, meta: false };
33
+ }
34
+ if (str === "\t") {
35
+ return { name: "tab", char: "", ctrl: false, shift: false, meta: false };
36
+ }
37
+ if (code === 127 || code === 8) {
38
+ return { name: "backspace", char: "", ctrl: false, shift: false, meta: false };
39
+ }
40
+ if (str === " ") {
41
+ return { name: "space", char: " ", ctrl: false, shift: false, meta: false };
42
+ }
43
+ if (str.length === 1 && code >= 1 && code <= 26) {
44
+ const letter = String.fromCharCode(code + 96);
45
+ return { name: letter, char: "", ctrl: true, shift: false, meta: false };
46
+ }
47
+ if (str.length === 1) {
48
+ return { name: str, char: str, ctrl: false, shift: false, meta: false };
49
+ }
50
+ return { name: str, char: str, ctrl: false, shift: false, meta: false };
51
+ }
52
+ function parseEscapeSequence(str) {
53
+ if (str === "\x1B") {
54
+ return { name: "escape", char: "", ctrl: false, shift: false, meta: false };
55
+ }
56
+ if (str.startsWith("\x1B[")) {
57
+ const seq = str.slice(2);
58
+ switch (seq) {
59
+ case "A":
60
+ return { name: "up", char: "", ctrl: false, shift: false, meta: false };
61
+ case "B":
62
+ return { name: "down", char: "", ctrl: false, shift: false, meta: false };
63
+ case "C":
64
+ return { name: "right", char: "", ctrl: false, shift: false, meta: false };
65
+ case "D":
66
+ return { name: "left", char: "", ctrl: false, shift: false, meta: false };
67
+ case "H":
68
+ return { name: "home", char: "", ctrl: false, shift: false, meta: false };
69
+ case "F":
70
+ return { name: "end", char: "", ctrl: false, shift: false, meta: false };
71
+ case "3~":
72
+ return { name: "delete", char: "", ctrl: false, shift: false, meta: false };
73
+ case "5~":
74
+ return { name: "pageup", char: "", ctrl: false, shift: false, meta: false };
75
+ case "6~":
76
+ return { name: "pagedown", char: "", ctrl: false, shift: false, meta: false };
77
+ case "Z":
78
+ return { name: "tab", char: "", ctrl: false, shift: true, meta: false };
79
+ case "1;5A":
80
+ return { name: "up", char: "", ctrl: true, shift: false, meta: false };
81
+ case "1;5B":
82
+ return { name: "down", char: "", ctrl: true, shift: false, meta: false };
83
+ case "1;5C":
84
+ return { name: "right", char: "", ctrl: true, shift: false, meta: false };
85
+ case "1;5D":
86
+ return { name: "left", char: "", ctrl: true, shift: false, meta: false };
87
+ }
88
+ }
89
+ if (str.length === 2 && str[1]) {
90
+ return { name: str[1], char: str[1], ctrl: false, shift: false, meta: true };
91
+ }
92
+ return { name: str, char: "", ctrl: false, shift: false, meta: false };
93
+ }
94
+
95
+ // src/input/stdin-reader.ts
96
+ class StdinReader {
97
+ _stdin;
98
+ _listeners = [];
99
+ _onData = null;
100
+ _wasRaw;
101
+ constructor(stdin) {
102
+ this._stdin = stdin ?? process.stdin;
103
+ this._wasRaw = this._stdin.isRaw ?? false;
104
+ }
105
+ start() {
106
+ if (this._onData)
107
+ return;
108
+ if (typeof this._stdin.setRawMode === "function") {
109
+ this._stdin.setRawMode(true);
110
+ }
111
+ this._stdin.resume();
112
+ this._onData = (data) => {
113
+ const key = parseKey(data);
114
+ for (const listener of this._listeners) {
115
+ listener(key);
116
+ }
117
+ if (key.ctrl && key.name === "c") {
118
+ process.exit(130);
119
+ }
120
+ };
121
+ this._stdin.on("data", this._onData);
122
+ }
123
+ stop() {
124
+ if (this._onData) {
125
+ this._stdin.off("data", this._onData);
126
+ this._onData = null;
127
+ }
128
+ if (typeof this._stdin.setRawMode === "function") {
129
+ this._stdin.setRawMode(this._wasRaw);
130
+ }
131
+ this._stdin.pause();
132
+ }
133
+ onKey(listener) {
134
+ this._listeners.push(listener);
135
+ return () => {
136
+ const idx = this._listeners.indexOf(listener);
137
+ if (idx !== -1)
138
+ this._listeners.splice(idx, 1);
139
+ };
140
+ }
141
+ dispose() {
142
+ this.stop();
143
+ this._listeners = [];
144
+ }
145
+ }
146
+
147
+ // src/internals.ts
148
+ import { _tryOnCleanup, domEffect, popScope, pushScope, runCleanups } from "@vertz/ui/internals";
149
+
150
+ // src/tui-element.ts
151
+ function isTuiElement(value) {
152
+ return typeof value === "object" && value !== null && "_tuiElement" in value;
153
+ }
154
+ function isTuiTextNode(value) {
155
+ return typeof value === "object" && value !== null && "_tuiText" in value;
156
+ }
157
+ function isTuiConditionalNode(value) {
158
+ return typeof value === "object" && value !== null && "_tuiConditional" in value;
159
+ }
160
+ function isTuiListNode(value) {
161
+ return typeof value === "object" && value !== null && "_tuiList" in value;
162
+ }
163
+
164
+ // src/internals.ts
165
+ var renderCallback = null;
166
+ var renderScheduled = false;
167
+ var syncRender = false;
168
+ function setRenderCallback(cb) {
169
+ renderCallback = cb;
170
+ }
171
+ function setSyncRender(sync) {
172
+ syncRender = sync;
173
+ }
174
+ function scheduleRender() {
175
+ if (!renderCallback)
176
+ return;
177
+ if (syncRender) {
178
+ renderCallback();
179
+ return;
180
+ }
181
+ if (renderScheduled)
182
+ return;
183
+ renderScheduled = true;
184
+ queueMicrotask(() => {
185
+ renderScheduled = false;
186
+ renderCallback?.();
187
+ });
188
+ }
189
+ function applyProp(el, key, value) {
190
+ el.props[key] = value;
191
+ switch (key) {
192
+ case "direction":
193
+ if (value === "row" || value === "column")
194
+ el.layoutProps.direction = value;
195
+ break;
196
+ case "padding":
197
+ if (typeof value === "number")
198
+ el.layoutProps.padding = value;
199
+ break;
200
+ case "paddingX":
201
+ if (typeof value === "number")
202
+ el.layoutProps.paddingX = value;
203
+ break;
204
+ case "paddingY":
205
+ if (typeof value === "number")
206
+ el.layoutProps.paddingY = value;
207
+ break;
208
+ case "gap":
209
+ if (typeof value === "number")
210
+ el.layoutProps.gap = value;
211
+ break;
212
+ case "width":
213
+ if (typeof value === "number" || value === "full")
214
+ el.layoutProps.width = value;
215
+ break;
216
+ case "height":
217
+ if (typeof value === "number")
218
+ el.layoutProps.height = value;
219
+ break;
220
+ case "grow":
221
+ if (typeof value === "number")
222
+ el.layoutProps.grow = value;
223
+ break;
224
+ case "align":
225
+ if (value === "start" || value === "center" || value === "end")
226
+ el.layoutProps.align = value;
227
+ break;
228
+ case "justify":
229
+ if (value === "start" || value === "center" || value === "end" || value === "between") {
230
+ el.layoutProps.justify = value;
231
+ }
232
+ break;
233
+ case "border":
234
+ if (value === "single" || value === "double" || value === "round" || value === "bold" || value === "none") {
235
+ el.layoutProps.border = value;
236
+ }
237
+ break;
238
+ case "color":
239
+ if (typeof value === "string")
240
+ el.style.color = value;
241
+ break;
242
+ case "bgColor":
243
+ case "borderColor":
244
+ if (typeof value === "string")
245
+ el.style.bgColor = value;
246
+ break;
247
+ case "bold":
248
+ el.style.bold = value === true ? true : undefined;
249
+ break;
250
+ case "dim":
251
+ el.style.dim = value === true ? true : undefined;
252
+ break;
253
+ case "italic":
254
+ el.style.italic = value === true ? true : undefined;
255
+ break;
256
+ case "underline":
257
+ el.style.underline = value === true ? true : undefined;
258
+ break;
259
+ case "strikethrough":
260
+ el.style.strikethrough = value === true ? true : undefined;
261
+ break;
262
+ }
263
+ }
264
+ function __element(tag, ...staticAttrs) {
265
+ const el = {
266
+ _tuiElement: true,
267
+ tag,
268
+ props: {},
269
+ style: {},
270
+ layoutProps: defaultLayoutProps(),
271
+ children: [],
272
+ parent: null,
273
+ dirty: false,
274
+ box: { x: 0, y: 0, width: 0, height: 0 }
275
+ };
276
+ for (let i = 0;i < staticAttrs.length; i += 2) {
277
+ const key = staticAttrs[i];
278
+ const value = staticAttrs[i + 1];
279
+ applyProp(el, key, value);
280
+ }
281
+ return el;
282
+ }
283
+ function __staticText(text) {
284
+ return {
285
+ _tuiText: true,
286
+ text,
287
+ style: {},
288
+ dirty: false,
289
+ box: { x: 0, y: 0, width: 0, height: 0 }
290
+ };
291
+ }
292
+ function __child(fn) {
293
+ const node = {
294
+ _tuiText: true,
295
+ text: "",
296
+ style: {},
297
+ dirty: false,
298
+ box: { x: 0, y: 0, width: 0, height: 0 }
299
+ };
300
+ domEffect(() => {
301
+ const value = fn();
302
+ if (value == null || typeof value === "boolean") {
303
+ node.text = "";
304
+ } else {
305
+ node.text = String(value);
306
+ }
307
+ node.dirty = true;
308
+ scheduleRender();
309
+ });
310
+ return node;
311
+ }
312
+ function __append(parent, child) {
313
+ parent.children.push(child);
314
+ if (isTuiElement(child)) {
315
+ child.parent = parent;
316
+ }
317
+ }
318
+
319
+ // src/renderer/ansi.ts
320
+ var FG_COLORS = {
321
+ black: "30",
322
+ red: "31",
323
+ green: "32",
324
+ yellow: "33",
325
+ blue: "34",
326
+ magenta: "35",
327
+ cyan: "36",
328
+ white: "37",
329
+ gray: "90",
330
+ redBright: "91",
331
+ greenBright: "92",
332
+ yellowBright: "93",
333
+ blueBright: "94",
334
+ magentaBright: "95",
335
+ cyanBright: "96",
336
+ whiteBright: "97"
337
+ };
338
+ var BG_COLORS = {
339
+ black: "40",
340
+ red: "41",
341
+ green: "42",
342
+ yellow: "43",
343
+ blue: "44",
344
+ magenta: "45",
345
+ cyan: "46",
346
+ white: "47",
347
+ gray: "100",
348
+ redBright: "101",
349
+ greenBright: "102",
350
+ yellowBright: "103",
351
+ blueBright: "104",
352
+ magentaBright: "105",
353
+ cyanBright: "106",
354
+ whiteBright: "107"
355
+ };
356
+ function hexToAnsi(hex, isBg) {
357
+ const clean = hex.replace("#", "");
358
+ if (!/^[0-9a-fA-F]{6}$/.test(clean))
359
+ return "";
360
+ const r = Number.parseInt(clean.slice(0, 2), 16);
361
+ const g = Number.parseInt(clean.slice(2, 4), 16);
362
+ const b = Number.parseInt(clean.slice(4, 6), 16);
363
+ return `${isBg ? "48" : "38"};2;${r};${g};${b}`;
364
+ }
365
+ function styleToSGR(style) {
366
+ const codes = [];
367
+ if (style.bold)
368
+ codes.push("1");
369
+ if (style.dim)
370
+ codes.push("2");
371
+ if (style.italic)
372
+ codes.push("3");
373
+ if (style.underline)
374
+ codes.push("4");
375
+ if (style.strikethrough)
376
+ codes.push("9");
377
+ if (style.color) {
378
+ if (style.color.startsWith("#")) {
379
+ const hex = hexToAnsi(style.color, false);
380
+ if (hex)
381
+ codes.push(hex);
382
+ } else {
383
+ const code = FG_COLORS[style.color];
384
+ if (code)
385
+ codes.push(code);
386
+ }
387
+ }
388
+ if (style.bgColor) {
389
+ if (style.bgColor.startsWith("#")) {
390
+ const hex = hexToAnsi(style.bgColor, true);
391
+ if (hex)
392
+ codes.push(hex);
393
+ } else {
394
+ const code = BG_COLORS[style.bgColor];
395
+ if (code)
396
+ codes.push(code);
397
+ }
398
+ }
399
+ if (codes.length === 0)
400
+ return "";
401
+ return `\x1B[${codes.join(";")}m`;
402
+ }
403
+ function cursorTo(row, col) {
404
+ return `\x1B[${row + 1};${col + 1}H`;
405
+ }
406
+ var RESET = "\x1B[0m";
407
+ var HIDE_CURSOR = "\x1B[?25l";
408
+ var SHOW_CURSOR = "\x1B[?25h";
409
+ var CLEAR_SCREEN = "\x1B[2J";
410
+ var ALT_BUFFER_ON = "\x1B[?1049h";
411
+ var ALT_BUFFER_OFF = "\x1B[?1049l";
412
+ var CURSOR_HOME = "\x1B[H";
413
+ function renderCell(cell) {
414
+ const sgr = styleToSGR(cell.style);
415
+ if (sgr) {
416
+ return `${sgr}${cell.char}${RESET}`;
417
+ }
418
+ return cell.char;
419
+ }
420
+ function renderRegions(regions) {
421
+ let output = "";
422
+ for (const region of regions) {
423
+ output += cursorTo(region.row, region.col);
424
+ for (const cell of region.cells) {
425
+ output += renderCell(cell);
426
+ }
427
+ }
428
+ return output;
429
+ }
430
+
431
+ // src/renderer/output-adapter.ts
432
+ class StdoutAdapter {
433
+ _stdout;
434
+ constructor(stdout) {
435
+ this._stdout = stdout ?? process.stdout;
436
+ }
437
+ write(data) {
438
+ this._stdout.write(data);
439
+ }
440
+ get columns() {
441
+ return this._stdout.columns ?? 80;
442
+ }
443
+ get rows() {
444
+ return this._stdout.rows ?? 24;
445
+ }
446
+ }
447
+
448
+ // src/layout/measure.ts
449
+ function measureTextWidth(text) {
450
+ const stripped = stripAnsi(text);
451
+ let width = 0;
452
+ for (const char of stripped) {
453
+ width += charWidth(char);
454
+ }
455
+ return width;
456
+ }
457
+ function stripAnsi(text) {
458
+ return text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "");
459
+ }
460
+ function charWidth(char) {
461
+ const code = char.codePointAt(0);
462
+ if (code === undefined)
463
+ return 0;
464
+ if (code >= 4352 && code <= 4447 || code >= 11904 && code <= 12350 || code >= 12352 && code <= 13247 || code >= 13312 && code <= 19903 || code >= 19968 && code <= 40959 || code >= 43360 && code <= 43391 || code >= 44032 && code <= 55215 || code >= 63744 && code <= 64255 || code >= 65072 && code <= 65135 || code >= 65281 && code <= 65376 || code >= 131072 && code <= 196605 || code >= 196608 && code <= 262141) {
465
+ return 2;
466
+ }
467
+ return 1;
468
+ }
469
+ function splitTextLines(text, maxWidth) {
470
+ if (maxWidth <= 0)
471
+ return [""];
472
+ const lines = text.split(`
473
+ `);
474
+ const result = [];
475
+ for (const line of lines) {
476
+ if (measureTextWidth(line) <= maxWidth) {
477
+ result.push(line);
478
+ } else {
479
+ let current = "";
480
+ let currentWidth = 0;
481
+ for (const char of line) {
482
+ const w = charWidth(char);
483
+ if (currentWidth + w > maxWidth)
484
+ break;
485
+ current += char;
486
+ currentWidth += w;
487
+ }
488
+ result.push(current);
489
+ }
490
+ }
491
+ return result;
492
+ }
493
+
494
+ // src/layout/compute.ts
495
+ function computeLayout(root, constraints) {
496
+ computeSize(root, constraints);
497
+ assignPosition(root, 0, 0);
498
+ }
499
+ function computeSize(node, constraints) {
500
+ const { props } = node;
501
+ const hasBorder = props.border !== "none";
502
+ const borderInset = hasBorder ? 2 : 0;
503
+ const padX = (props.paddingX || props.padding) * 2;
504
+ const padY = (props.paddingY || props.padding) * 2;
505
+ let resolvedWidth;
506
+ if (props.width === "full") {
507
+ resolvedWidth = constraints.maxWidth;
508
+ } else if (typeof props.width === "number") {
509
+ resolvedWidth = Math.min(props.width, constraints.maxWidth);
510
+ } else {
511
+ resolvedWidth = constraints.maxWidth;
512
+ }
513
+ const innerMaxWidth = Math.max(0, resolvedWidth - padX - borderInset);
514
+ const innerMaxHeight = Math.max(0, constraints.maxHeight - padY - borderInset);
515
+ if (node.type === "text") {
516
+ const text = node.text ?? "";
517
+ const lines = splitTextLines(text, innerMaxWidth);
518
+ const textWidth = lines.reduce((max, line) => Math.max(max, measureTextWidth(line)), 0);
519
+ const textHeight = lines.length;
520
+ if (props.width === "full" || typeof props.width === "number") {
521
+ node.box.width = resolvedWidth;
522
+ } else {
523
+ node.box.width = Math.min(textWidth + padX + borderInset, constraints.maxWidth);
524
+ }
525
+ node.box.height = Math.min(typeof props.height === "number" ? props.height : textHeight + padY + borderInset, constraints.maxHeight);
526
+ return;
527
+ }
528
+ const childConstraints = {
529
+ maxWidth: innerMaxWidth,
530
+ maxHeight: innerMaxHeight
531
+ };
532
+ for (const child of node.children) {
533
+ computeSize(child, childConstraints);
534
+ }
535
+ let contentWidth = 0;
536
+ let contentHeight = 0;
537
+ const visibleChildren = node.children.filter((c) => c.box.width > 0 || c.box.height > 0);
538
+ const gapTotal = Math.max(0, visibleChildren.length - 1) * props.gap;
539
+ if (props.direction === "row") {
540
+ for (const child of visibleChildren) {
541
+ contentWidth += child.box.width;
542
+ contentHeight = Math.max(contentHeight, child.box.height);
543
+ }
544
+ contentWidth += gapTotal;
545
+ } else {
546
+ for (const child of visibleChildren) {
547
+ contentWidth = Math.max(contentWidth, child.box.width);
548
+ contentHeight += child.box.height;
549
+ }
550
+ contentHeight += gapTotal;
551
+ }
552
+ const growChildren = visibleChildren.filter((c) => c.props.grow > 0);
553
+ if (growChildren.length > 0) {
554
+ if (props.direction === "row") {
555
+ const nonGrowWidth = visibleChildren.filter((c) => c.props.grow === 0).reduce((sum, c) => sum + c.box.width, 0);
556
+ const remaining = Math.max(0, innerMaxWidth - nonGrowWidth - gapTotal);
557
+ const totalGrow = growChildren.reduce((sum, c) => sum + c.props.grow, 0);
558
+ for (const child of growChildren) {
559
+ child.box.width = Math.floor(remaining * child.props.grow / totalGrow);
560
+ }
561
+ contentWidth = innerMaxWidth;
562
+ } else {
563
+ const nonGrowHeight = visibleChildren.filter((c) => c.props.grow === 0).reduce((sum, c) => sum + c.box.height, 0);
564
+ const remaining = Math.max(0, innerMaxHeight - nonGrowHeight - gapTotal);
565
+ const totalGrow = growChildren.reduce((sum, c) => sum + c.props.grow, 0);
566
+ for (const child of growChildren) {
567
+ child.box.height = Math.floor(remaining * child.props.grow / totalGrow);
568
+ }
569
+ contentHeight = innerMaxHeight;
570
+ }
571
+ }
572
+ if (props.width === "full") {
573
+ node.box.width = constraints.maxWidth;
574
+ } else if (typeof props.width === "number") {
575
+ node.box.width = Math.min(props.width, constraints.maxWidth);
576
+ } else {
577
+ node.box.width = Math.min(contentWidth + padX + borderInset, constraints.maxWidth);
578
+ }
579
+ node.box.height = Math.min(typeof props.height === "number" ? props.height : contentHeight + padY + borderInset, constraints.maxHeight);
580
+ }
581
+ function assignPosition(node, x, y) {
582
+ node.box.x = x;
583
+ node.box.y = y;
584
+ if (node.type === "text" || node.children.length === 0)
585
+ return;
586
+ const { props } = node;
587
+ const hasBorder = props.border !== "none";
588
+ const borderOffset = hasBorder ? 1 : 0;
589
+ const padLeft = (props.paddingX || props.padding) + borderOffset;
590
+ const padTop = (props.paddingY || props.padding) + borderOffset;
591
+ const padX = (props.paddingX || props.padding) * 2 + borderOffset * 2;
592
+ const padY = (props.paddingY || props.padding) * 2 + borderOffset * 2;
593
+ const innerWidth = Math.max(0, node.box.width - padX);
594
+ const innerHeight = Math.max(0, node.box.height - padY);
595
+ const visibleChildren = node.children.filter((c) => c.box.width > 0 || c.box.height > 0);
596
+ const gapTotal = Math.max(0, visibleChildren.length - 1) * props.gap;
597
+ if (props.direction === "row") {
598
+ const totalChildWidth = visibleChildren.reduce((sum, c) => sum + c.box.width, 0) + gapTotal;
599
+ let offsetX = computeJustifyOffset(props.justify, innerWidth, totalChildWidth, visibleChildren.length);
600
+ for (const child of visibleChildren) {
601
+ const offsetY = computeAlignOffset(props.align, innerHeight, child.box.height);
602
+ assignPosition(child, x + padLeft + offsetX, y + padTop + offsetY);
603
+ offsetX += child.box.width + props.gap;
604
+ }
605
+ } else {
606
+ const totalChildHeight = visibleChildren.reduce((sum, c) => sum + c.box.height, 0) + gapTotal;
607
+ let offsetY = computeJustifyOffset(props.justify, innerHeight, totalChildHeight, visibleChildren.length);
608
+ for (const child of visibleChildren) {
609
+ const offsetX = computeAlignOffset(props.align, innerWidth, child.box.width);
610
+ assignPosition(child, x + padLeft + offsetX, y + padTop + offsetY);
611
+ offsetY += child.box.height + props.gap;
612
+ }
613
+ }
614
+ }
615
+ function computeJustifyOffset(justify, totalSpace, contentSize, _childCount) {
616
+ switch (justify) {
617
+ case "start":
618
+ return 0;
619
+ case "center":
620
+ return Math.max(0, Math.floor((totalSpace - contentSize) / 2));
621
+ case "end":
622
+ return Math.max(0, totalSpace - contentSize);
623
+ case "between":
624
+ return 0;
625
+ }
626
+ }
627
+ function computeAlignOffset(align, totalSpace, childSize) {
628
+ switch (align) {
629
+ case "start":
630
+ return 0;
631
+ case "center":
632
+ return Math.max(0, Math.floor((totalSpace - childSize) / 2));
633
+ case "end":
634
+ return Math.max(0, totalSpace - childSize);
635
+ }
636
+ }
637
+
638
+ // src/nodes/types.ts
639
+ function isTuiElement2(value) {
640
+ return typeof value === "object" && value !== null && "_tuiElement" in value;
641
+ }
642
+ function isTuiTextNode2(value) {
643
+ return typeof value === "object" && value !== null && "_tuiText" in value;
644
+ }
645
+
646
+ // src/renderer/paint.ts
647
+ function toLayoutTree(node) {
648
+ if (node == null || node === false) {
649
+ return emptyLayoutNode();
650
+ }
651
+ if (Array.isArray(node)) {
652
+ return {
653
+ type: "box",
654
+ props: defaultLayoutProps(),
655
+ children: node.map((n) => toLayoutTree(n)),
656
+ box: { x: 0, y: 0, width: 0, height: 0 }
657
+ };
658
+ }
659
+ if (isTuiTextNode2(node)) {
660
+ return textToLayout(node);
661
+ }
662
+ if (isTuiConditionalNode(node)) {
663
+ if (node.current) {
664
+ return toLayoutTree(node.current);
665
+ }
666
+ return emptyLayoutNode();
667
+ }
668
+ if (isTuiListNode(node)) {
669
+ return {
670
+ type: "box",
671
+ props: defaultLayoutProps(),
672
+ children: node.items.map((item) => toLayoutTree(item)),
673
+ box: { x: 0, y: 0, width: 0, height: 0 }
674
+ };
675
+ }
676
+ if (isTuiElement2(node)) {
677
+ return elementToLayout(node);
678
+ }
679
+ return {
680
+ type: "text",
681
+ props: defaultLayoutProps(),
682
+ text: String(node),
683
+ children: [],
684
+ box: { x: 0, y: 0, width: 0, height: 0 }
685
+ };
686
+ }
687
+ function textToLayout(node) {
688
+ return {
689
+ type: "text",
690
+ props: defaultLayoutProps(),
691
+ text: node.text,
692
+ children: [],
693
+ box: { x: 0, y: 0, width: 0, height: 0 }
694
+ };
695
+ }
696
+ function elementToLayout(node) {
697
+ const { tag } = node;
698
+ if (tag === "Spacer") {
699
+ return {
700
+ type: "text",
701
+ props: { ...defaultLayoutProps(), grow: 1 },
702
+ text: "",
703
+ children: [],
704
+ box: { x: 0, y: 0, width: 0, height: 0 }
705
+ };
706
+ }
707
+ if (tag === "Text") {
708
+ const text = collectText(node.children);
709
+ return {
710
+ type: "text",
711
+ props: { ...defaultLayoutProps(), ...node.layoutProps },
712
+ style: node.style,
713
+ text,
714
+ children: [],
715
+ box: { x: 0, y: 0, width: 0, height: 0 }
716
+ };
717
+ }
718
+ const layoutChildren = node.children.map((child) => toLayoutTree(child));
719
+ return {
720
+ type: "box",
721
+ props: { ...defaultLayoutProps(), ...node.layoutProps },
722
+ children: layoutChildren,
723
+ box: { x: 0, y: 0, width: 0, height: 0 }
724
+ };
725
+ }
726
+ function collectText(children) {
727
+ let text = "";
728
+ for (const child of children) {
729
+ if (child == null || child === false)
730
+ continue;
731
+ if (Array.isArray(child)) {
732
+ text += collectText(child);
733
+ } else if (isTuiTextNode2(child) || isTuiTextNode(child)) {
734
+ text += child.text;
735
+ } else if (isTuiConditionalNode(child)) {
736
+ if (child.current) {
737
+ text += collectText([child.current]);
738
+ }
739
+ } else if (isTuiListNode(child)) {
740
+ text += collectText(child.items);
741
+ } else if (isTuiElement2(child)) {
742
+ text += collectText(child.children);
743
+ } else {
744
+ text += String(child);
745
+ }
746
+ }
747
+ return text;
748
+ }
749
+ function emptyLayoutNode() {
750
+ return {
751
+ type: "text",
752
+ props: defaultLayoutProps(),
753
+ text: "",
754
+ children: [],
755
+ box: { x: 0, y: 0, width: 0, height: 0 }
756
+ };
757
+ }
758
+ function paintTree(buffer, node, inheritedStyle, maxRows) {
759
+ if (node.type === "text") {
760
+ paintText(buffer, node, node.style ?? inheritedStyle, maxRows);
761
+ return;
762
+ }
763
+ if (node.props.border !== "none") {
764
+ paintBorder(buffer, node);
765
+ }
766
+ for (const child of node.children) {
767
+ paintTree(buffer, child, inheritedStyle, maxRows);
768
+ }
769
+ }
770
+ function paintText(buffer, node, style, maxRows) {
771
+ const text = node.text ?? "";
772
+ if (!text)
773
+ return;
774
+ const lines = splitTextLines(text, node.box.width);
775
+ for (let i = 0;i < lines.length; i++) {
776
+ const line = lines[i];
777
+ if (line === undefined)
778
+ continue;
779
+ const row = node.box.y + i;
780
+ if (row >= maxRows)
781
+ break;
782
+ buffer.writeString(row, node.box.x, line, style);
783
+ }
784
+ }
785
+ function paintBorder(buffer, node) {
786
+ const borderStyle = node.props.border;
787
+ if (borderStyle === "none")
788
+ return;
789
+ const chars = BORDER_CHARS[borderStyle];
790
+ if (!chars)
791
+ return;
792
+ const { x, y, width, height } = node.box;
793
+ const style = {};
794
+ buffer.set(y, x, chars.topLeft, style);
795
+ for (let c = 1;c < width - 1; c++) {
796
+ buffer.set(y, x + c, chars.horizontal, style);
797
+ }
798
+ if (width > 1)
799
+ buffer.set(y, x + width - 1, chars.topRight, style);
800
+ if (height > 1) {
801
+ buffer.set(y + height - 1, x, chars.bottomLeft, style);
802
+ for (let c = 1;c < width - 1; c++) {
803
+ buffer.set(y + height - 1, x + c, chars.horizontal, style);
804
+ }
805
+ if (width > 1) {
806
+ buffer.set(y + height - 1, x + width - 1, chars.bottomRight, style);
807
+ }
808
+ }
809
+ for (let r = 1;r < height - 1; r++) {
810
+ buffer.set(y + r, x, chars.vertical, style);
811
+ if (width > 1)
812
+ buffer.set(y + r, x + width - 1, chars.vertical, style);
813
+ }
814
+ }
815
+
816
+ // src/renderer/renderer.ts
817
+ class TuiRenderer {
818
+ _adapter;
819
+ _current;
820
+ _previous;
821
+ constructor(adapter) {
822
+ this._adapter = adapter;
823
+ this._current = new TerminalBuffer(adapter.columns, adapter.rows);
824
+ this._previous = new TerminalBuffer(adapter.columns, adapter.rows);
825
+ }
826
+ render(rootNode) {
827
+ this._current.clear();
828
+ const layoutRoot = toLayoutTree(rootNode);
829
+ computeLayout(layoutRoot, {
830
+ maxWidth: this._adapter.columns,
831
+ maxHeight: this._adapter.rows
832
+ });
833
+ paintTree(this._current, layoutRoot, {}, this._adapter.rows);
834
+ const regions = this._current.diff(this._previous);
835
+ if (regions.length > 0) {
836
+ const ansi = renderRegions(regions);
837
+ this._adapter.write(ansi);
838
+ }
839
+ this._previous = this._current.clone();
840
+ }
841
+ getBuffer() {
842
+ return this._current;
843
+ }
844
+ }
845
+
846
+ // src/app.ts
847
+ var currentApp = null;
848
+ function mount(app, options = {}) {
849
+ const mode = options.mode ?? "inline";
850
+ let adapter;
851
+ if (options.adapter) {
852
+ adapter = options.adapter;
853
+ } else {
854
+ adapter = new StdoutAdapter(options.stdout);
855
+ }
856
+ const renderer = new TuiRenderer(adapter);
857
+ if (mode === "fullscreen") {
858
+ adapter.write(HIDE_CURSOR + CLEAR_SCREEN + CURSOR_HOME);
859
+ } else if (mode === "alternate") {
860
+ adapter.write(ALT_BUFFER_ON + HIDE_CURSOR);
861
+ }
862
+ let exitResolve = null;
863
+ const exitPromise = new Promise((resolve) => {
864
+ exitResolve = resolve;
865
+ });
866
+ const ctx = {
867
+ renderer,
868
+ adapter,
869
+ mode,
870
+ disposed: false,
871
+ exitResolve,
872
+ testStdin: options.testStdin ?? null,
873
+ stdinReader: null,
874
+ stdinOptions: options.stdin,
875
+ scope: null,
876
+ effectCleanup: null,
877
+ rerenderFn: null,
878
+ componentStates: new Map,
879
+ stateIndex: 0
880
+ };
881
+ currentApp = ctx;
882
+ const isTestMode = adapter instanceof TestAdapter;
883
+ if (!options.testStdin) {
884
+ const stdinStream = options.stdin ?? (isTestMode ? undefined : process.stdin);
885
+ if (stdinStream) {
886
+ const reader = new StdinReader(stdinStream);
887
+ reader.start();
888
+ ctx.stdinReader = reader;
889
+ }
890
+ }
891
+ setSyncRender(isTestMode);
892
+ let rendering = false;
893
+ let dirty = false;
894
+ let lastTree = null;
895
+ const doRender = () => {
896
+ if (ctx.disposed)
897
+ return;
898
+ if (rendering) {
899
+ dirty = true;
900
+ return;
901
+ }
902
+ rendering = true;
903
+ try {
904
+ do {
905
+ dirty = false;
906
+ ctx.stateIndex = 0;
907
+ lastTree = app();
908
+ renderer.render(lastTree);
909
+ if (adapter instanceof TestAdapter) {
910
+ adapter.buffer = renderer.getBuffer();
911
+ }
912
+ } while (dirty);
913
+ } finally {
914
+ rendering = false;
915
+ }
916
+ };
917
+ ctx.rerenderFn = doRender;
918
+ const scope = pushScope2();
919
+ ctx.effectCleanup = lifecycleEffect(doRender);
920
+ popScope2();
921
+ ctx.scope = scope;
922
+ setRenderCallback(() => {
923
+ if (ctx.disposed)
924
+ return;
925
+ if (lastTree) {
926
+ renderer.render(lastTree);
927
+ if (adapter instanceof TestAdapter) {
928
+ adapter.buffer = renderer.getBuffer();
929
+ }
930
+ }
931
+ });
932
+ const handle = {
933
+ unmount() {
934
+ cleanup(ctx);
935
+ },
936
+ async waitUntilExit() {
937
+ await exitPromise;
938
+ },
939
+ output: adapter
940
+ };
941
+ return handle;
942
+ }
943
+ function cleanup(ctx) {
944
+ if (ctx.disposed)
945
+ return;
946
+ ctx.disposed = true;
947
+ setRenderCallback(null);
948
+ if (ctx.scope) {
949
+ runCleanups2(ctx.scope);
950
+ ctx.scope = null;
951
+ }
952
+ if (ctx.effectCleanup) {
953
+ ctx.effectCleanup();
954
+ ctx.effectCleanup = null;
955
+ }
956
+ if (ctx.stdinReader) {
957
+ ctx.stdinReader.dispose();
958
+ ctx.stdinReader = null;
959
+ }
960
+ if (ctx.mode === "fullscreen") {
961
+ ctx.adapter.write(SHOW_CURSOR);
962
+ } else if (ctx.mode === "alternate") {
963
+ ctx.adapter.write(ALT_BUFFER_OFF + SHOW_CURSOR);
964
+ }
965
+ if (ctx.exitResolve) {
966
+ ctx.exitResolve();
967
+ ctx.exitResolve = null;
968
+ }
969
+ if (currentApp === ctx) {
970
+ currentApp = null;
971
+ }
972
+ }
973
+ function exit() {
974
+ if (currentApp) {
975
+ cleanup(currentApp);
976
+ }
977
+ }
978
+ function getCurrentApp() {
979
+ return currentApp;
980
+ }
981
+ var tui = {
982
+ mount,
983
+ exit
984
+ };
985
+ // src/auth/device-code-auth.ts
986
+ import { execFile } from "node:child_process";
987
+ import { signal as signal2 } from "@vertz/ui";
988
+ import { _tryOnCleanup as _tryOnCleanup4 } from "@vertz/ui/internals";
989
+
990
+ // src/interactive.ts
991
+ var CI_ENV_VARS = [
992
+ "CI",
993
+ "GITHUB_ACTIONS",
994
+ "GITLAB_CI",
995
+ "CIRCLECI",
996
+ "TRAVIS",
997
+ "JENKINS_URL",
998
+ "BUILD_NUMBER",
999
+ "BUILDKITE",
1000
+ "CODEBUILD_BUILD_ID",
1001
+ "TF_BUILD"
1002
+ ];
1003
+ function isInteractive() {
1004
+ for (const envVar of CI_ENV_VARS) {
1005
+ if (process.env[envVar])
1006
+ return false;
1007
+ }
1008
+ if (!process.stdin.isTTY || !process.stdout.isTTY)
1009
+ return false;
1010
+ return true;
1011
+ }
1012
+
1013
+ class NonInteractiveError extends Error {
1014
+ constructor(promptType) {
1015
+ super(`Cannot run interactive ${promptType} prompt in non-interactive environment. ` + "Provide a default value or run in an interactive terminal.");
1016
+ this.name = "NonInteractiveError";
1017
+ }
1018
+ }
1019
+
1020
+ // src/theme.ts
1021
+ var symbols = Object.freeze({
1022
+ success: "✓",
1023
+ error: "✗",
1024
+ warning: "⚠",
1025
+ info: "ℹ",
1026
+ arrow: "➜",
1027
+ pointer: "❯",
1028
+ bullet: "●",
1029
+ dash: "─"
1030
+ });
1031
+ var colors = Object.freeze({
1032
+ success: "greenBright",
1033
+ error: "redBright",
1034
+ warning: "yellowBright",
1035
+ info: "cyanBright",
1036
+ dim: "gray",
1037
+ method: Object.freeze({
1038
+ GET: "greenBright",
1039
+ POST: "blueBright",
1040
+ PUT: "yellowBright",
1041
+ PATCH: "cyanBright",
1042
+ DELETE: "redBright"
1043
+ })
1044
+ });
1045
+
1046
+ // src/components/Spinner.ts
1047
+ import { signal } from "@vertz/ui";
1048
+ import { _tryOnCleanup as _tryOnCleanup2 } from "@vertz/ui/internals";
1049
+ var SPINNER_FRAMES = [
1050
+ "⠋",
1051
+ "⠙",
1052
+ "⠹",
1053
+ "⠸",
1054
+ "⠼",
1055
+ "⠴",
1056
+ "⠦",
1057
+ "⠧",
1058
+ "⠇",
1059
+ "⠏"
1060
+ ];
1061
+ function Spinner(props) {
1062
+ const frameIndex = signal(0);
1063
+ const timer = setInterval(() => {
1064
+ frameIndex.value = (frameIndex.value + 1) % SPINNER_FRAMES.length;
1065
+ }, 80);
1066
+ _tryOnCleanup2(() => clearInterval(timer));
1067
+ if (props.label) {
1068
+ const box = __element("Box", "direction", "row", "gap", 1);
1069
+ const spinnerText = __element("Text", "color", "cyan");
1070
+ __append(spinnerText, __child(() => SPINNER_FRAMES[frameIndex.value]));
1071
+ const labelText = __element("Text");
1072
+ __append(labelText, __staticText(props.label));
1073
+ __append(box, spinnerText);
1074
+ __append(box, labelText);
1075
+ return box;
1076
+ }
1077
+ const el = __element("Text", "color", "cyan");
1078
+ __append(el, __child(() => SPINNER_FRAMES[frameIndex.value]));
1079
+ return el;
1080
+ }
1081
+
1082
+ // src/input/hooks.ts
1083
+ import { _tryOnCleanup as _tryOnCleanup3 } from "@vertz/ui/internals";
1084
+ var registeredCallbacks = new WeakSet;
1085
+ function useKeyboard(callback) {
1086
+ if (registeredCallbacks.has(callback))
1087
+ return;
1088
+ registeredCallbacks.add(callback);
1089
+ const app = getCurrentApp();
1090
+ if (!app)
1091
+ return;
1092
+ let removeListener;
1093
+ if (app.testStdin) {
1094
+ removeListener = app.testStdin.onKey(callback);
1095
+ } else if (app.stdinReader) {
1096
+ removeListener = app.stdinReader.onKey(callback);
1097
+ }
1098
+ _tryOnCleanup3(() => {
1099
+ registeredCallbacks.delete(callback);
1100
+ removeListener?.();
1101
+ });
1102
+ }
1103
+
1104
+ // src/auth/device-code-display.ts
1105
+ function formatCountdown(seconds) {
1106
+ const m = Math.floor(seconds / 60);
1107
+ const s = seconds % 60;
1108
+ if (m > 0)
1109
+ return `${m}m ${s}s`;
1110
+ return `${s}s`;
1111
+ }
1112
+ function DeviceCodeDisplay({
1113
+ title = "Authenticate",
1114
+ userCode,
1115
+ verificationUri,
1116
+ secondsRemaining,
1117
+ status,
1118
+ onCancel,
1119
+ onOpenBrowser
1120
+ }) {
1121
+ useKeyboard((key) => {
1122
+ if (key.name === "escape" && onCancel) {
1123
+ onCancel();
1124
+ } else if (key.name === "return" && onOpenBrowser) {
1125
+ onOpenBrowser();
1126
+ }
1127
+ });
1128
+ const outer = __element("Box", "border", "round", "paddingX", 2, "paddingY", 1, "direction", "column");
1129
+ const titleEl = __element("Text", "bold", true);
1130
+ __append(titleEl, __staticText(title));
1131
+ __append(outer, titleEl);
1132
+ const spacer1 = __element("Text");
1133
+ __append(spacer1, __staticText(""));
1134
+ __append(outer, spacer1);
1135
+ const urlRow = __element("Box", "direction", "row", "gap", 1);
1136
+ const urlLabel = __element("Text", "dim", true);
1137
+ __append(urlLabel, __staticText("Visit:"));
1138
+ const urlValue = __element("Text");
1139
+ __append(urlValue, __child(() => verificationUri.value));
1140
+ __append(urlRow, urlLabel);
1141
+ __append(urlRow, urlValue);
1142
+ __append(outer, urlRow);
1143
+ const codeRow = __element("Box", "direction", "row", "gap", 1);
1144
+ const codeLabel = __element("Text", "dim", true);
1145
+ __append(codeLabel, __staticText("Code:"));
1146
+ const codeValue = __element("Text", "bold", true);
1147
+ __append(codeValue, __child(() => userCode.value));
1148
+ __append(codeRow, codeLabel);
1149
+ __append(codeRow, codeValue);
1150
+ __append(outer, codeRow);
1151
+ const spacer2 = __element("Text");
1152
+ __append(spacer2, __staticText(""));
1153
+ __append(outer, spacer2);
1154
+ const statusText = __element("Text");
1155
+ __append(statusText, __child(() => {
1156
+ const s = status.value;
1157
+ const remaining = formatCountdown(secondsRemaining.value);
1158
+ switch (s) {
1159
+ case "awaiting-approval":
1160
+ case "polling":
1161
+ return `Waiting for approval... (${remaining})`;
1162
+ case "success":
1163
+ return `${symbols.success} Authenticated!`;
1164
+ case "expired":
1165
+ return `${symbols.warning} Code expired`;
1166
+ case "denied":
1167
+ return `${symbols.error} Authorization denied`;
1168
+ case "error":
1169
+ return `${symbols.error} Authentication failed`;
1170
+ default:
1171
+ return "";
1172
+ }
1173
+ }));
1174
+ const spinnerContainer = __element("Box", "direction", "row", "gap", 1);
1175
+ const spinnerEl = Spinner({});
1176
+ __append(spinnerContainer, spinnerEl);
1177
+ __append(spinnerContainer, statusText);
1178
+ __append(outer, spinnerContainer);
1179
+ const spacer3 = __element("Text");
1180
+ __append(spacer3, __staticText(""));
1181
+ __append(outer, spacer3);
1182
+ const hint1 = __element("Text", "dim", true);
1183
+ __append(hint1, __staticText("Press Enter to open browser"));
1184
+ __append(outer, hint1);
1185
+ const hint2 = __element("Text", "dim", true);
1186
+ __append(hint2, __staticText("Press Esc to cancel"));
1187
+ __append(outer, hint2);
1188
+ return outer;
1189
+ }
1190
+
1191
+ // src/auth/types.ts
1192
+ class AuthDeniedError extends Error {
1193
+ constructor() {
1194
+ super("Authorization request was denied by the user.");
1195
+ this.name = "AuthDeniedError";
1196
+ }
1197
+ }
1198
+
1199
+ class AuthExpiredError extends Error {
1200
+ constructor() {
1201
+ super("Device code expired before authorization was completed.");
1202
+ this.name = "AuthExpiredError";
1203
+ }
1204
+ }
1205
+
1206
+ class AuthCancelledError extends Error {
1207
+ constructor() {
1208
+ super("Authentication was cancelled.");
1209
+ this.name = "AuthCancelledError";
1210
+ }
1211
+ }
1212
+
1213
+ // src/auth/device-code-flow.ts
1214
+ async function requestDeviceCode(config) {
1215
+ const fetcher = config.fetcher ?? globalThis.fetch;
1216
+ const body = { client_id: config.clientId };
1217
+ if (config.scopes?.length) {
1218
+ body.scope = config.scopes.join(" ");
1219
+ }
1220
+ const response = await fetcher(config.deviceCodeUrl, {
1221
+ method: "POST",
1222
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1223
+ body: new URLSearchParams(body),
1224
+ signal: AbortSignal.timeout(30000)
1225
+ });
1226
+ if (!response.ok) {
1227
+ throw new Error(`Device code request failed: ${response.status}`);
1228
+ }
1229
+ return response.json();
1230
+ }
1231
+ function sleep(ms) {
1232
+ return new Promise((resolve) => setTimeout(resolve, ms));
1233
+ }
1234
+ async function pollTokenUntilComplete(config, deviceCode, signal2) {
1235
+ const fetcher = config.fetcher ?? globalThis.fetch;
1236
+ let interval = deviceCode.interval * 1000;
1237
+ const deadline = Date.now() + deviceCode.expires_in * 1000;
1238
+ while (Date.now() < deadline) {
1239
+ if (signal2?.aborted) {
1240
+ throw new AuthExpiredError;
1241
+ }
1242
+ await sleep(interval);
1243
+ if (signal2?.aborted) {
1244
+ throw new AuthExpiredError;
1245
+ }
1246
+ const response = await fetcher(config.tokenUrl, {
1247
+ method: "POST",
1248
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1249
+ body: new URLSearchParams({
1250
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
1251
+ device_code: deviceCode.device_code,
1252
+ client_id: config.clientId
1253
+ }),
1254
+ signal: signal2 ? AbortSignal.any([signal2, AbortSignal.timeout(30000)]) : AbortSignal.timeout(30000)
1255
+ });
1256
+ if (response.ok) {
1257
+ const tokenData = await response.json();
1258
+ const tokens = {
1259
+ accessToken: tokenData.access_token,
1260
+ refreshToken: tokenData.refresh_token,
1261
+ expiresIn: tokenData.expires_in
1262
+ };
1263
+ if (config.onTokens) {
1264
+ await config.onTokens(tokens);
1265
+ }
1266
+ return tokens;
1267
+ }
1268
+ const errorBody = await response.json();
1269
+ switch (errorBody.error) {
1270
+ case "authorization_pending":
1271
+ break;
1272
+ case "slow_down":
1273
+ interval += 5000;
1274
+ break;
1275
+ case "access_denied":
1276
+ throw new AuthDeniedError;
1277
+ case "expired_token":
1278
+ throw new AuthExpiredError;
1279
+ default:
1280
+ throw new Error(`Token request failed: ${errorBody.error}${errorBody.error_description ? ` — ${errorBody.error_description}` : ""}`);
1281
+ }
1282
+ }
1283
+ throw new AuthExpiredError;
1284
+ }
1285
+
1286
+ // src/auth/device-code-auth.ts
1287
+ function write(text) {
1288
+ process.stdout.write(`${text}
1289
+ `);
1290
+ }
1291
+ async function DeviceCodeAuth(config) {
1292
+ if (!config._mountOptions && !isInteractive()) {
1293
+ return runNonInteractive(config);
1294
+ }
1295
+ return runInteractive(config);
1296
+ }
1297
+ async function runNonInteractive(config) {
1298
+ write(`${symbols.info} Starting device code authentication...`);
1299
+ const deviceCode = await requestDeviceCode(config);
1300
+ write(`${symbols.arrow} Visit: ${deviceCode.verification_uri}`);
1301
+ write(`${symbols.arrow} Code: ${deviceCode.user_code}`);
1302
+ write(`${symbols.info} Waiting for approval...`);
1303
+ const tokens = await pollTokenUntilComplete(config, deviceCode);
1304
+ write(`${symbols.success} Authenticated successfully.`);
1305
+ return tokens;
1306
+ }
1307
+ async function runInteractive(config) {
1308
+ return new Promise((resolve, reject) => {
1309
+ let handle = null;
1310
+ let abortController = null;
1311
+ function App() {
1312
+ const userCode = signal2("");
1313
+ const verificationUri = signal2("");
1314
+ const secondsRemaining = signal2(0);
1315
+ const status = signal2("requesting-code");
1316
+ abortController = new AbortController;
1317
+ requestDeviceCode(config).then((deviceCode) => {
1318
+ userCode.value = deviceCode.user_code;
1319
+ verificationUri.value = deviceCode.verification_uri;
1320
+ secondsRemaining.value = deviceCode.expires_in;
1321
+ status.value = "awaiting-approval";
1322
+ const countdownTimer = setInterval(() => {
1323
+ if (secondsRemaining.value > 0) {
1324
+ secondsRemaining.value--;
1325
+ }
1326
+ }, 1000);
1327
+ _tryOnCleanup4(() => clearInterval(countdownTimer));
1328
+ return pollTokenUntilComplete(config, deviceCode, abortController?.signal);
1329
+ }).then((tokens) => {
1330
+ status.value = "success";
1331
+ handle?.unmount();
1332
+ resolve(tokens);
1333
+ }).catch((err) => {
1334
+ if (err instanceof AuthCancelledError) {
1335
+ status.value = "cancelled";
1336
+ } else {
1337
+ status.value = "error";
1338
+ }
1339
+ handle?.unmount();
1340
+ reject(err);
1341
+ });
1342
+ return DeviceCodeDisplay({
1343
+ title: config.title,
1344
+ userCode,
1345
+ verificationUri,
1346
+ secondsRemaining,
1347
+ status,
1348
+ onCancel: () => {
1349
+ abortController?.abort();
1350
+ handle?.unmount();
1351
+ reject(new AuthCancelledError);
1352
+ },
1353
+ onOpenBrowser: () => {
1354
+ const uri = verificationUri.value;
1355
+ if (uri) {
1356
+ try {
1357
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
1358
+ execFile(cmd, [uri]);
1359
+ } catch {}
1360
+ }
1361
+ }
1362
+ });
1363
+ }
1364
+ handle = tui.mount(App, config._mountOptions);
1365
+ });
1366
+ }
1367
+ // src/components/Banner.ts
1368
+ function Banner({
1369
+ title,
1370
+ subtitle,
1371
+ border = "round",
1372
+ titleColor = "cyanBright"
1373
+ }) {
1374
+ const box = __element("Box", "border", border, "paddingX", 2, "paddingY", 1, "direction", "column");
1375
+ const titleEl = __element("Text", "bold", true, "color", titleColor);
1376
+ __append(titleEl, __staticText(title));
1377
+ __append(box, titleEl);
1378
+ if (subtitle) {
1379
+ const subtitleEl = __element("Text", "dim", true);
1380
+ __append(subtitleEl, __staticText(subtitle));
1381
+ __append(box, subtitleEl);
1382
+ }
1383
+ return box;
1384
+ }
1385
+ // src/components/Box.ts
1386
+ function Box(_props) {
1387
+ return null;
1388
+ }
1389
+ // src/components/Confirm.ts
1390
+ import { signal as signal3 } from "@vertz/ui";
1391
+ function Confirm(props) {
1392
+ const value = signal3(true);
1393
+ useKeyboard((key) => {
1394
+ if (key.name === "left" || key.name === "right") {
1395
+ value.value = !value.value;
1396
+ } else if (key.name === "y") {
1397
+ value.value = true;
1398
+ } else if (key.name === "n") {
1399
+ value.value = false;
1400
+ } else if (key.name === "return") {
1401
+ if (props.onSubmit)
1402
+ props.onSubmit(value.value);
1403
+ }
1404
+ });
1405
+ const box = __element("Box", "direction", "row", "gap", 1);
1406
+ const msgEl = __element("Text", "bold", true);
1407
+ __append(msgEl, __staticText(props.message));
1408
+ __append(box, msgEl);
1409
+ const yesEl = __element("Text");
1410
+ __append(yesEl, __child(() => value.value ? "[Yes]" : " Yes "));
1411
+ __append(box, yesEl);
1412
+ const slashEl = __element("Text");
1413
+ __append(slashEl, __staticText("/"));
1414
+ __append(box, slashEl);
1415
+ const noEl = __element("Text");
1416
+ __append(noEl, __child(() => value.value ? " No " : "[No]"));
1417
+ __append(box, noEl);
1418
+ return box;
1419
+ }
1420
+ // src/components/Dashboard.ts
1421
+ function Dashboard({ header, footer, children }) {
1422
+ const outer = __element("Box", "direction", "column", "width", "full");
1423
+ if (header) {
1424
+ __append(outer, header);
1425
+ }
1426
+ const contentBox = __element("Box", "direction", "column", "grow", 1);
1427
+ if (children) {
1428
+ __append(contentBox, children);
1429
+ }
1430
+ __append(outer, contentBox);
1431
+ if (footer) {
1432
+ __append(outer, footer);
1433
+ }
1434
+ return outer;
1435
+ }
1436
+ // src/components/DiagnosticView.ts
1437
+ function DiagnosticView({
1438
+ diagnostics,
1439
+ showSource = true,
1440
+ showSuggestions = true
1441
+ }) {
1442
+ const box = __element("Box", "direction", "column", "gap", 1);
1443
+ for (const diag of diagnostics) {
1444
+ const section = __element("Box", "direction", "column");
1445
+ const isError = diag.severity === "error";
1446
+ const isWarning = diag.severity === "warning";
1447
+ const icon = isError ? symbols.error : isWarning ? symbols.warning : symbols.info;
1448
+ const color = isError ? colors.error : isWarning ? colors.warning : colors.info;
1449
+ const headerEl = __element("Text", "color", color);
1450
+ __append(headerEl, __staticText(`${icon} ${diag.code}`));
1451
+ __append(section, headerEl);
1452
+ const messageEl = __element("Text");
1453
+ __append(messageEl, __staticText(` ${diag.message}`));
1454
+ __append(section, messageEl);
1455
+ if (diag.file) {
1456
+ const location = `${diag.file}:${diag.line ?? 0}:${diag.column ?? 0}`;
1457
+ const locEl = __element("Text", "dim", true);
1458
+ __append(locEl, __staticText(` at ${location}`));
1459
+ __append(section, locEl);
1460
+ }
1461
+ if (showSource && diag.sourceLines && diag.sourceLines.length > 0) {
1462
+ const maxLineNum = Math.max(...diag.sourceLines.map((l) => l.number));
1463
+ const gutterWidth = String(maxLineNum).length;
1464
+ for (const line of diag.sourceLines) {
1465
+ const lineNum = String(line.number).padStart(gutterWidth);
1466
+ const srcEl = __element("Text");
1467
+ __append(srcEl, __staticText(` ${lineNum} ${line.text}`));
1468
+ __append(section, srcEl);
1469
+ }
1470
+ if (diag.highlightStart !== undefined && diag.highlightLength !== undefined && diag.highlightLength > 0) {
1471
+ const padding = " ".repeat(diag.highlightStart + gutterWidth + 3);
1472
+ const underline = "^".repeat(diag.highlightLength);
1473
+ const underlineEl = __element("Text", "color", color);
1474
+ __append(underlineEl, __staticText(padding + underline));
1475
+ __append(section, underlineEl);
1476
+ }
1477
+ }
1478
+ if (showSuggestions && diag.suggestion) {
1479
+ const sugEl = __element("Text", "color", colors.info);
1480
+ __append(sugEl, __staticText(` ${symbols.info} ${diag.suggestion}`));
1481
+ __append(section, sugEl);
1482
+ }
1483
+ __append(box, section);
1484
+ }
1485
+ return box;
1486
+ }
1487
+ // src/components/Divider.ts
1488
+ function Divider({
1489
+ label,
1490
+ char = symbols.dash,
1491
+ width = 80,
1492
+ color
1493
+ }) {
1494
+ let text;
1495
+ if (label) {
1496
+ const labelText = ` ${label} `;
1497
+ const remaining = Math.max(0, width - labelText.length);
1498
+ const left = Math.floor(remaining / 2);
1499
+ const right = remaining - left;
1500
+ text = char.repeat(left) + labelText + char.repeat(right);
1501
+ } else {
1502
+ text = char.repeat(width);
1503
+ }
1504
+ const attrs = [];
1505
+ if (color) {
1506
+ attrs.push("color", color);
1507
+ } else {
1508
+ attrs.push("dim", true);
1509
+ }
1510
+ const el = __element("Text", ...attrs);
1511
+ __append(el, __staticText(text));
1512
+ return el;
1513
+ }
1514
+ // src/components/KeyValue.ts
1515
+ function KeyValue({ entries, separator = ": ", keyBold = true }) {
1516
+ const maxKeyLen = entries.reduce((max, e) => Math.max(max, e.key.length), 0);
1517
+ const box = __element("Box", "direction", "column");
1518
+ for (const entry of entries) {
1519
+ const paddedKey = entry.key.padStart(maxKeyLen);
1520
+ const row = __element("Box", "direction", "row");
1521
+ const keyAttrs = [];
1522
+ if (keyBold)
1523
+ keyAttrs.push("bold", true);
1524
+ const keyEl = __element("Text", ...keyAttrs);
1525
+ __append(keyEl, __staticText(paddedKey));
1526
+ const sepEl = __element("Text", "dim", true);
1527
+ __append(sepEl, __staticText(separator));
1528
+ const valEl = __element("Text");
1529
+ __append(valEl, __staticText(entry.value));
1530
+ __append(row, keyEl);
1531
+ __append(row, sepEl);
1532
+ __append(row, valEl);
1533
+ __append(box, row);
1534
+ }
1535
+ return box;
1536
+ }
1537
+ // src/components/Log.ts
1538
+ function Log(props) {
1539
+ const { items, children: renderItem } = props;
1540
+ const box = __element("Box", "direction", "column");
1541
+ for (const item of items) {
1542
+ __append(box, renderItem(item));
1543
+ }
1544
+ return box;
1545
+ }
1546
+ // src/components/LogStream.ts
1547
+ function LogStream({
1548
+ entries,
1549
+ children: renderEntry,
1550
+ maxLines
1551
+ }) {
1552
+ const box = __element("Box", "direction", "column");
1553
+ const visible = maxLines ? entries.slice(-maxLines) : entries;
1554
+ for (const entry of visible) {
1555
+ __append(box, renderEntry(entry));
1556
+ }
1557
+ return box;
1558
+ }
1559
+ // src/components/MultiSelect.ts
1560
+ import { signal as signal4 } from "@vertz/ui";
1561
+ function MultiSelect(props) {
1562
+ const box = __element("Box", "direction", "column");
1563
+ const header = __element("Text", "bold", true);
1564
+ __append(header, __staticText(props.message));
1565
+ __append(box, header);
1566
+ if (props.options.length === 0)
1567
+ return box;
1568
+ if (props.defaultValue) {
1569
+ const optionValues = new Set(props.options.map((o) => o.value));
1570
+ for (const val of props.defaultValue) {
1571
+ if (!optionValues.has(val)) {
1572
+ console.warn(`MultiSelect: defaultValue "${String(val)}" not found in options`);
1573
+ }
1574
+ }
1575
+ }
1576
+ const initialChecked = new Set(props.defaultValue ? props.options.map((o, i) => props.defaultValue?.includes(o.value) ? i : -1).filter((i) => i !== -1) : []);
1577
+ const selectedIndex = signal4(0);
1578
+ const checked = signal4(initialChecked);
1579
+ useKeyboard((key) => {
1580
+ if (key.name === "up") {
1581
+ selectedIndex.value = Math.max(0, selectedIndex.value - 1);
1582
+ } else if (key.name === "down") {
1583
+ selectedIndex.value = Math.min(props.options.length - 1, selectedIndex.value + 1);
1584
+ } else if (key.name === "space") {
1585
+ const newChecked = new Set(checked.value);
1586
+ if (newChecked.has(selectedIndex.value)) {
1587
+ newChecked.delete(selectedIndex.value);
1588
+ } else {
1589
+ newChecked.add(selectedIndex.value);
1590
+ }
1591
+ checked.value = newChecked;
1592
+ } else if (key.name === "return") {
1593
+ if (props.onSubmit) {
1594
+ const selected = props.options.filter((_, i) => checked.value.has(i)).map((o) => o.value);
1595
+ props.onSubmit(selected);
1596
+ }
1597
+ }
1598
+ });
1599
+ for (let i = 0;i < props.options.length; i++) {
1600
+ const option = props.options[i];
1601
+ if (!option)
1602
+ continue;
1603
+ const idx = i;
1604
+ const optionEl = __element("Text");
1605
+ __append(optionEl, __child(() => {
1606
+ const isSelected = idx === selectedIndex.value;
1607
+ const isChecked = checked.value.has(idx);
1608
+ const checkbox = isChecked ? symbols.success : symbols.bullet;
1609
+ const prefix = isSelected ? symbols.pointer : " ";
1610
+ return `${prefix} ${checkbox} ${option.label}`;
1611
+ }));
1612
+ __append(box, optionEl);
1613
+ }
1614
+ return box;
1615
+ }
1616
+ // src/components/PasswordInput.ts
1617
+ import { signal as signal5 } from "@vertz/ui";
1618
+ function PasswordInput(props) {
1619
+ const text = signal5(props.value ?? "");
1620
+ useKeyboard((key) => {
1621
+ if (key.name === "return") {
1622
+ if (props.onSubmit)
1623
+ props.onSubmit(text.value);
1624
+ } else if (key.name === "backspace") {
1625
+ text.value = text.value.slice(0, -1);
1626
+ if (props.onChange)
1627
+ props.onChange(text.value);
1628
+ } else if (key.char && key.char.length === 1 && key.name !== "space") {
1629
+ text.value += key.char;
1630
+ if (props.onChange)
1631
+ props.onChange(text.value);
1632
+ } else if (key.name === "space") {
1633
+ text.value += " ";
1634
+ if (props.onChange)
1635
+ props.onChange(text.value);
1636
+ }
1637
+ });
1638
+ const el = __element("Text");
1639
+ __append(el, __child(() => {
1640
+ const showPlaceholder = !text.value && props.placeholder;
1641
+ if (showPlaceholder)
1642
+ return props.placeholder ?? "";
1643
+ return text.value ? "•".repeat(text.value.length) : "";
1644
+ }));
1645
+ return el;
1646
+ }
1647
+ // src/components/ProgressBar.ts
1648
+ var FILLED = "█";
1649
+ var EMPTY = "░";
1650
+ function ProgressBar(props) {
1651
+ const { value, max, label, width = 20 } = props;
1652
+ const ratio = max > 0 ? Math.min(1, Math.max(0, value / max)) : 0;
1653
+ const filled = Math.round(ratio * width);
1654
+ const empty = width - filled;
1655
+ const percent = Math.round(ratio * 100);
1656
+ const bar = FILLED.repeat(filled) + EMPTY.repeat(empty);
1657
+ const text = label ? `${label} ${bar} ${percent}%` : `${bar} ${percent}%`;
1658
+ const el = __element("Text");
1659
+ __append(el, __staticText(text));
1660
+ return el;
1661
+ }
1662
+ // src/components/Select.ts
1663
+ import { signal as signal6 } from "@vertz/ui";
1664
+ function Select(props) {
1665
+ const box = __element("Box", "direction", "column");
1666
+ const header = __element("Text", "bold", true);
1667
+ __append(header, __staticText(props.message));
1668
+ __append(box, header);
1669
+ if (props.options.length === 0)
1670
+ return box;
1671
+ const selectedIndex = signal6(0);
1672
+ useKeyboard((key) => {
1673
+ if (key.name === "up") {
1674
+ const next = Math.max(0, selectedIndex.value - 1);
1675
+ if (next !== selectedIndex.value)
1676
+ selectedIndex.value = next;
1677
+ } else if (key.name === "down") {
1678
+ const next = Math.min(props.options.length - 1, selectedIndex.value + 1);
1679
+ if (next !== selectedIndex.value)
1680
+ selectedIndex.value = next;
1681
+ } else if (key.name === "return") {
1682
+ const option = props.options[selectedIndex.value];
1683
+ if (option && props.onSubmit) {
1684
+ props.onSubmit(option.value);
1685
+ }
1686
+ }
1687
+ });
1688
+ for (let i = 0;i < props.options.length; i++) {
1689
+ const option = props.options[i];
1690
+ if (!option)
1691
+ continue;
1692
+ const idx = i;
1693
+ const optionEl = __element("Text");
1694
+ __append(optionEl, __child(() => {
1695
+ const isSelected = idx === selectedIndex.value;
1696
+ const prefix = isSelected ? symbols.pointer : " ";
1697
+ const hint = option.hint ? ` (${option.hint})` : "";
1698
+ return `${prefix} ${option.label}${hint}`;
1699
+ }));
1700
+ __append(box, optionEl);
1701
+ }
1702
+ return box;
1703
+ }
1704
+ // src/components/Spacer.ts
1705
+ function Spacer(_props) {
1706
+ return null;
1707
+ }
1708
+ // src/components/Table.ts
1709
+ function padCell(text, width, align = "left") {
1710
+ if (text.length >= width)
1711
+ return text.slice(0, width);
1712
+ const padding = width - text.length;
1713
+ if (align === "right")
1714
+ return " ".repeat(padding) + text;
1715
+ if (align === "center") {
1716
+ const left = Math.floor(padding / 2);
1717
+ return " ".repeat(left) + text + " ".repeat(padding - left);
1718
+ }
1719
+ return text + " ".repeat(padding);
1720
+ }
1721
+ function Table(props) {
1722
+ const { data, columns } = props;
1723
+ const widths = columns.map((col) => {
1724
+ if (col.width)
1725
+ return col.width;
1726
+ let max = col.header.length;
1727
+ for (const row of data) {
1728
+ const val = String(row[col.key] ?? "");
1729
+ if (val.length > max)
1730
+ max = val.length;
1731
+ }
1732
+ return max;
1733
+ });
1734
+ const box = __element("Box", "direction", "column");
1735
+ const headerCells = columns.map((col, i) => padCell(col.header, widths[i] ?? col.header.length, col.align));
1736
+ const headerEl = __element("Text", "bold", true);
1737
+ __append(headerEl, __staticText(headerCells.join(" ")));
1738
+ __append(box, headerEl);
1739
+ const separator = widths.map((w) => "─".repeat(w)).join("──");
1740
+ const sepEl = __element("Text", "dim", true);
1741
+ __append(sepEl, __staticText(separator));
1742
+ __append(box, sepEl);
1743
+ for (const row of data) {
1744
+ const cells = columns.map((col, i) => {
1745
+ const val = String(row[col.key] ?? "");
1746
+ return padCell(val, widths[i] ?? val.length, col.align);
1747
+ });
1748
+ const rowEl = __element("Text");
1749
+ __append(rowEl, __staticText(cells.join(" ")));
1750
+ __append(box, rowEl);
1751
+ }
1752
+ return box;
1753
+ }
1754
+ // src/components/TaskRunner.ts
1755
+ import { signal as signal7 } from "@vertz/ui";
1756
+ function formatDuration(ms) {
1757
+ if (ms < 1000)
1758
+ return `${Math.round(ms)}ms`;
1759
+ return `${(ms / 1000).toFixed(1)}s`;
1760
+ }
1761
+ function write2(text) {
1762
+ process.stdout.write(`${text}
1763
+ `);
1764
+ }
1765
+ function TaskRunner(config) {
1766
+ const taskStates = config.tasks.map(() => signal7({
1767
+ status: "pending",
1768
+ duration: 0
1769
+ }));
1770
+ async function run() {
1771
+ const results = [];
1772
+ const ci = !isInteractive();
1773
+ let failed = false;
1774
+ for (const [i, task] of config.tasks.entries()) {
1775
+ const state = taskStates[i];
1776
+ if (!state)
1777
+ continue;
1778
+ if (failed) {
1779
+ state.value = { status: "skipped", duration: 0 };
1780
+ results.push({ label: task.label, status: "skipped", duration: 0 });
1781
+ if (ci)
1782
+ write2(`${symbols.dash} ${task.label} (skipped)`);
1783
+ continue;
1784
+ }
1785
+ state.value = { status: "running", duration: 0 };
1786
+ if (ci)
1787
+ write2(`${symbols.pointer} ${task.label}...`);
1788
+ const start = performance.now();
1789
+ try {
1790
+ const value = await task.run();
1791
+ const duration = performance.now() - start;
1792
+ state.value = { status: "success", duration };
1793
+ results.push({ label: task.label, status: "success", value, duration });
1794
+ if (ci)
1795
+ write2(`${symbols.success} ${task.label} (${formatDuration(duration)})`);
1796
+ } catch (err) {
1797
+ const duration = performance.now() - start;
1798
+ const error = err instanceof Error ? err : new Error(String(err));
1799
+ state.value = { status: "error", duration, error };
1800
+ results.push({ label: task.label, status: "error", error, duration });
1801
+ if (ci)
1802
+ write2(`${symbols.error} ${task.label} — failed`);
1803
+ failed = true;
1804
+ }
1805
+ }
1806
+ return results;
1807
+ }
1808
+ function component() {
1809
+ const box = __element("Box", "direction", "column");
1810
+ for (const [i, task] of config.tasks.entries()) {
1811
+ const state = taskStates[i];
1812
+ if (!state)
1813
+ continue;
1814
+ const row = __element("Box", "direction", "row", "gap", 1);
1815
+ const statusEl = __element("Text");
1816
+ __append(statusEl, __child(() => {
1817
+ const s = state.value;
1818
+ switch (s.status) {
1819
+ case "pending":
1820
+ return symbols.dash;
1821
+ case "success":
1822
+ return symbols.success;
1823
+ case "error":
1824
+ return symbols.error;
1825
+ case "skipped":
1826
+ return symbols.dash;
1827
+ default:
1828
+ return "";
1829
+ }
1830
+ }));
1831
+ const labelEl = __element("Text");
1832
+ __append(labelEl, __staticText(task.label));
1833
+ const durationEl = __element("Text", "color", "gray");
1834
+ __append(durationEl, __child(() => {
1835
+ const s = state.value;
1836
+ if (s.status === "success")
1837
+ return formatDuration(s.duration);
1838
+ if (s.status === "error")
1839
+ return s.error?.message ?? "failed";
1840
+ return "";
1841
+ }));
1842
+ __append(row, statusEl);
1843
+ __append(row, labelEl);
1844
+ __append(row, durationEl);
1845
+ __append(box, row);
1846
+ }
1847
+ return box;
1848
+ }
1849
+ return { run, component };
1850
+ }
1851
+ // src/components/Text.ts
1852
+ function Text(_props) {
1853
+ return null;
1854
+ }
1855
+ // src/components/TextInput.ts
1856
+ import { signal as signal8 } from "@vertz/ui";
1857
+ function TextInput(props) {
1858
+ const text = signal8(props.value ?? "");
1859
+ useKeyboard((key) => {
1860
+ if (key.name === "return") {
1861
+ if (props.onSubmit)
1862
+ props.onSubmit(text.value);
1863
+ } else if (key.name === "backspace") {
1864
+ text.value = text.value.slice(0, -1);
1865
+ if (props.onChange)
1866
+ props.onChange(text.value);
1867
+ } else if (key.char && key.char.length === 1 && key.name !== "space") {
1868
+ text.value += key.char;
1869
+ if (props.onChange)
1870
+ props.onChange(text.value);
1871
+ } else if (key.name === "space") {
1872
+ text.value += " ";
1873
+ if (props.onChange)
1874
+ props.onChange(text.value);
1875
+ }
1876
+ });
1877
+ const el = __element("Text");
1878
+ __append(el, __child(() => {
1879
+ const showPlaceholder = !text.value && props.placeholder;
1880
+ return showPlaceholder ? props.placeholder ?? "" : text.value;
1881
+ }));
1882
+ return el;
1883
+ }
1884
+ // src/focus/focus-manager.ts
1885
+ import { createContext, useContext } from "@vertz/ui";
1886
+ var FocusContext = createContext();
1887
+ function useFocus() {
1888
+ const ctx = useContext(FocusContext);
1889
+ return ctx ?? { focused: false };
1890
+ }
1891
+ // src/input/match.ts
1892
+ function parsePattern(pattern) {
1893
+ const parts = pattern.split("+");
1894
+ const name = parts[parts.length - 1] ?? pattern;
1895
+ let ctrl = false;
1896
+ let shift = false;
1897
+ let meta = false;
1898
+ for (let i = 0;i < parts.length - 1; i++) {
1899
+ const modifier = parts[i];
1900
+ if (modifier === undefined)
1901
+ continue;
1902
+ const lower = modifier.toLowerCase();
1903
+ if (lower === "ctrl")
1904
+ ctrl = true;
1905
+ else if (lower === "shift")
1906
+ shift = true;
1907
+ else if (lower === "meta")
1908
+ meta = true;
1909
+ }
1910
+ return { name, ctrl, shift, meta };
1911
+ }
1912
+ function match(keyMap) {
1913
+ const entries = [];
1914
+ for (const pattern in keyMap) {
1915
+ const handler = keyMap[pattern];
1916
+ if (handler) {
1917
+ entries.push({ parsed: parsePattern(pattern), handler });
1918
+ }
1919
+ }
1920
+ return (key) => {
1921
+ for (const { parsed, handler } of entries) {
1922
+ if (key.name === parsed.name && key.ctrl === parsed.ctrl && key.shift === parsed.shift && key.meta === parsed.meta) {
1923
+ handler(key);
1924
+ return;
1925
+ }
1926
+ }
1927
+ };
1928
+ }
1929
+ // src/legacy.ts
1930
+ var colorCodes = {
1931
+ green: (t) => `\x1B[32m${t}\x1B[0m`,
1932
+ red: (t) => `\x1B[31m${t}\x1B[0m`,
1933
+ yellow: (t) => `\x1B[33m${t}\x1B[0m`,
1934
+ cyan: (t) => `\x1B[36m${t}\x1B[0m`,
1935
+ gray: (t) => `\x1B[90m${t}\x1B[0m`
1936
+ };
1937
+ function applyColor(text, color) {
1938
+ const fn = colorCodes[color];
1939
+ return fn ? fn(text) : text;
1940
+ }
1941
+ var symbolMap = {
1942
+ info: symbols.info,
1943
+ error: symbols.error,
1944
+ warning: symbols.warning,
1945
+ success: symbols.success
1946
+ };
1947
+ var colorMap = {
1948
+ info: colors.info,
1949
+ error: colors.error,
1950
+ warning: colors.warning,
1951
+ success: colors.success
1952
+ };
1953
+ function Message({ type, children }) {
1954
+ return {
1955
+ render() {
1956
+ const symbol = symbolMap[type];
1957
+ const content = typeof children === "function" ? children() : String(children);
1958
+ return applyColor(`${symbol} ${content}`, colorMap[type]);
1959
+ }
1960
+ };
1961
+ }
1962
+ function SelectList({ title, choices, selectedIndex }) {
1963
+ return {
1964
+ render() {
1965
+ const titleText = typeof title === "function" ? title() : title;
1966
+ const choicesList = typeof choices === "function" ? choices() : choices;
1967
+ const selected = selectedIndex.value;
1968
+ const lines = [titleText];
1969
+ for (let i = 0;i < choicesList.length; i++) {
1970
+ const choice = choicesList[i];
1971
+ if (!choice)
1972
+ continue;
1973
+ const prefix = i === selected ? symbols.pointer : " ";
1974
+ lines.push(`${prefix} ${choice.label}`);
1975
+ }
1976
+ return lines.join(`
1977
+ `);
1978
+ }
1979
+ };
1980
+ }
1981
+ var iconMap = {
1982
+ pending: symbols.dash,
1983
+ running: symbols.pointer,
1984
+ done: symbols.success,
1985
+ error: symbols.error
1986
+ };
1987
+ function Task({ name, status, detail }) {
1988
+ return {
1989
+ render() {
1990
+ const nameText = typeof name === "function" ? name() : name;
1991
+ const currentStatus = status.value;
1992
+ const icon = iconMap[currentStatus];
1993
+ const detailText = detail?.value;
1994
+ let text = `${icon} ${nameText}`;
1995
+ if (detailText) {
1996
+ text += ` ${detailText}`;
1997
+ }
1998
+ if (currentStatus === "done") {
1999
+ return applyColor(text, colors.success);
2000
+ } else if (currentStatus === "error") {
2001
+ return applyColor(text, colors.error);
2002
+ } else if (currentStatus === "running") {
2003
+ return applyColor(text, colors.info);
2004
+ }
2005
+ return text;
2006
+ }
2007
+ };
2008
+ }
2009
+ function TaskList({ title, tasks }) {
2010
+ return {
2011
+ render() {
2012
+ const titleText = typeof title === "function" ? title() : title;
2013
+ const taskList = tasks.value;
2014
+ const lines = [`\x1B[1m${titleText}\x1B[0m`];
2015
+ for (const task of taskList) {
2016
+ const taskStatus = task.status;
2017
+ const icon = iconMap[taskStatus];
2018
+ let line = `${icon} ${task.name}`;
2019
+ if (task.detail) {
2020
+ line += ` ${task.detail}`;
2021
+ }
2022
+ if (taskStatus === "done") {
2023
+ line = applyColor(line, colors.success);
2024
+ } else if (taskStatus === "error") {
2025
+ line = applyColor(line, colors.error);
2026
+ } else if (taskStatus === "running") {
2027
+ line = applyColor(line, colors.info);
2028
+ }
2029
+ lines.push(line);
2030
+ }
2031
+ return lines.join(`
2032
+ `);
2033
+ }
2034
+ };
2035
+ }
2036
+ function createTaskRunner() {
2037
+ const messages = [];
2038
+ return {
2039
+ group(_name) {
2040
+ return {
2041
+ async task(_name2, fn) {
2042
+ const handle = {
2043
+ update() {},
2044
+ succeed() {},
2045
+ fail() {}
2046
+ };
2047
+ await fn(handle);
2048
+ },
2049
+ dismiss() {}
2050
+ };
2051
+ },
2052
+ info(message) {
2053
+ messages.push({ type: "info", message });
2054
+ },
2055
+ warn(message) {
2056
+ messages.push({ type: "warning", message });
2057
+ },
2058
+ error(message) {
2059
+ messages.push({ type: "error", message });
2060
+ },
2061
+ success(message) {
2062
+ messages.push({ type: "success", message });
2063
+ },
2064
+ cleanup() {}
2065
+ };
2066
+ }
2067
+ // src/prompt.ts
2068
+ import { signal as signal9 } from "@vertz/ui";
2069
+ function write3(text) {
2070
+ process.stdout.write(`${text}
2071
+ `);
2072
+ }
2073
+ var prompt = {
2074
+ text(config) {
2075
+ if (!config._mountOptions && !isInteractive()) {
2076
+ if (config.default !== undefined)
2077
+ return Promise.resolve(config.default);
2078
+ return Promise.reject(new NonInteractiveError("text"));
2079
+ }
2080
+ return new Promise((resolve) => {
2081
+ let handle = null;
2082
+ const errorMsg = signal9("");
2083
+ function App() {
2084
+ const box = __element("Box", "direction", "column");
2085
+ const header = __element("Text", "bold", true);
2086
+ __append(header, __staticText(config.message));
2087
+ __append(box, header);
2088
+ __append(box, TextInput({
2089
+ placeholder: config.placeholder,
2090
+ onSubmit: (value) => {
2091
+ if (config.validate) {
2092
+ const error = config.validate(value);
2093
+ if (error) {
2094
+ errorMsg.value = error;
2095
+ return;
2096
+ }
2097
+ }
2098
+ handle?.unmount();
2099
+ resolve(value);
2100
+ }
2101
+ }));
2102
+ const errorEl = __element("Text", "color", "red");
2103
+ __append(errorEl, __child(() => errorMsg.value));
2104
+ __append(box, errorEl);
2105
+ return box;
2106
+ }
2107
+ handle = tui.mount(App, config._mountOptions);
2108
+ });
2109
+ },
2110
+ select(config) {
2111
+ if (!isInteractive()) {
2112
+ if (config.default !== undefined)
2113
+ return Promise.resolve(config.default);
2114
+ return Promise.reject(new NonInteractiveError("select"));
2115
+ }
2116
+ return new Promise((resolve) => {
2117
+ let handle = null;
2118
+ function App() {
2119
+ return Select({
2120
+ message: config.message,
2121
+ options: config.options,
2122
+ onSubmit: (value) => {
2123
+ handle?.unmount();
2124
+ resolve(value);
2125
+ }
2126
+ });
2127
+ }
2128
+ handle = tui.mount(App);
2129
+ });
2130
+ },
2131
+ multiSelect(config) {
2132
+ if (!isInteractive()) {
2133
+ if (config.defaultValue !== undefined)
2134
+ return Promise.resolve(config.defaultValue);
2135
+ return Promise.reject(new NonInteractiveError("multiSelect"));
2136
+ }
2137
+ return new Promise((resolve) => {
2138
+ let handle = null;
2139
+ function App() {
2140
+ return MultiSelect({
2141
+ message: config.message,
2142
+ options: config.options,
2143
+ defaultValue: config.defaultValue,
2144
+ onSubmit: (values) => {
2145
+ handle?.unmount();
2146
+ resolve(values);
2147
+ }
2148
+ });
2149
+ }
2150
+ handle = tui.mount(App);
2151
+ });
2152
+ },
2153
+ confirm(config) {
2154
+ if (!isInteractive()) {
2155
+ if (config.default !== undefined)
2156
+ return Promise.resolve(config.default);
2157
+ return Promise.reject(new NonInteractiveError("confirm"));
2158
+ }
2159
+ return new Promise((resolve) => {
2160
+ let handle = null;
2161
+ function App() {
2162
+ return Confirm({
2163
+ message: config.message,
2164
+ onSubmit: (value) => {
2165
+ handle?.unmount();
2166
+ resolve(value);
2167
+ }
2168
+ });
2169
+ }
2170
+ handle = tui.mount(App);
2171
+ });
2172
+ },
2173
+ password(config) {
2174
+ if (!isInteractive()) {
2175
+ if (config.default !== undefined)
2176
+ return Promise.resolve(config.default);
2177
+ return Promise.reject(new NonInteractiveError("password"));
2178
+ }
2179
+ return new Promise((resolve) => {
2180
+ let handle = null;
2181
+ function App() {
2182
+ const box = __element("Box", "direction", "column");
2183
+ const header = __element("Text", "bold", true);
2184
+ __append(header, __staticText(config.message));
2185
+ __append(box, header);
2186
+ __append(box, PasswordInput({
2187
+ placeholder: config.placeholder,
2188
+ onSubmit: (value) => {
2189
+ handle?.unmount();
2190
+ resolve(value);
2191
+ }
2192
+ }));
2193
+ return box;
2194
+ }
2195
+ handle = tui.mount(App);
2196
+ });
2197
+ },
2198
+ spinner() {
2199
+ if (!isInteractive()) {
2200
+ return {
2201
+ start(message) {
2202
+ write3(`${symbols.info} ${message}`);
2203
+ },
2204
+ stop(message) {
2205
+ write3(`${symbols.success} ${message}`);
2206
+ }
2207
+ };
2208
+ }
2209
+ let handle = null;
2210
+ return {
2211
+ start(message) {
2212
+ function App() {
2213
+ return Spinner({ label: message });
2214
+ }
2215
+ handle = tui.mount(App);
2216
+ },
2217
+ stop(message) {
2218
+ handle?.unmount();
2219
+ handle = null;
2220
+ write3(message);
2221
+ }
2222
+ };
2223
+ },
2224
+ intro(title) {
2225
+ write3("");
2226
+ write3(` ${title}`);
2227
+ write3("");
2228
+ },
2229
+ outro(message) {
2230
+ write3("");
2231
+ write3(` ${message}`);
2232
+ write3("");
2233
+ },
2234
+ log: {
2235
+ info(message) {
2236
+ write3(`${symbols.info} ${message}`);
2237
+ },
2238
+ warn(message) {
2239
+ write3(`${symbols.warning} ${message}`);
2240
+ },
2241
+ error(message) {
2242
+ write3(`${symbols.error} ${message}`);
2243
+ },
2244
+ success(message) {
2245
+ write3(`${symbols.success} ${message}`);
2246
+ }
2247
+ }
2248
+ };
2249
+ // src/render-to-string.ts
2250
+ function renderToString(node, options = {}) {
2251
+ const width = options.width ?? 80;
2252
+ const height = options.height ?? 24;
2253
+ const layoutRoot = toLayoutTree(node);
2254
+ computeLayout(layoutRoot, { maxWidth: width, maxHeight: height });
2255
+ const buffer = new TerminalBuffer(width, height);
2256
+ paintTree(buffer, layoutRoot, {}, height);
2257
+ return bufferToString(buffer);
2258
+ }
2259
+ function bufferToString(buffer) {
2260
+ const lines = [];
2261
+ let lastNonEmptyRow = -1;
2262
+ for (let r = 0;r < buffer.height; r++) {
2263
+ const rowText = buffer.getRowText(r);
2264
+ if (rowText.trim()) {
2265
+ lastNonEmptyRow = r;
2266
+ }
2267
+ }
2268
+ for (let r = 0;r <= lastNonEmptyRow; r++) {
2269
+ lines.push(renderRow(buffer, r));
2270
+ }
2271
+ return lines.join(`
2272
+ `);
2273
+ }
2274
+ function renderRow(buffer, row) {
2275
+ let output = "";
2276
+ let lastNonSpace = -1;
2277
+ for (let c = buffer.width - 1;c >= 0; c--) {
2278
+ const cell = buffer.get(row, c);
2279
+ if (cell && cell.char !== " ") {
2280
+ lastNonSpace = c;
2281
+ break;
2282
+ }
2283
+ if (cell && hasStyle(cell.style)) {
2284
+ lastNonSpace = c;
2285
+ break;
2286
+ }
2287
+ }
2288
+ for (let c = 0;c <= lastNonSpace; c++) {
2289
+ const cell = buffer.get(row, c);
2290
+ if (!cell)
2291
+ continue;
2292
+ const sgr = styleToSGR(cell.style);
2293
+ if (sgr) {
2294
+ output += `${sgr}${cell.char}${RESET}`;
2295
+ } else {
2296
+ output += cell.char;
2297
+ }
2298
+ }
2299
+ return output;
2300
+ }
2301
+ function hasStyle(style) {
2302
+ return !!(style.color || style.bgColor || style.bold || style.dim || style.italic || style.underline || style.strikethrough);
2303
+ }
2304
+ // src/wizard.ts
2305
+ async function wizard(config) {
2306
+ const { steps, onStep } = config;
2307
+ const answers = {};
2308
+ const total = steps.length;
2309
+ for (let i = 0;i < steps.length; i++) {
2310
+ const step = steps[i];
2311
+ if (!step)
2312
+ continue;
2313
+ const current = i + 1;
2314
+ if (onStep) {
2315
+ onStep({ current, total, id: step.id });
2316
+ } else {
2317
+ prompt.log.info(`Step ${current}/${total} ${symbols.dash} ${step.id}`);
2318
+ }
2319
+ const ctx = { answers: { ...answers } };
2320
+ const value = await step.prompt(ctx);
2321
+ answers[step.id] = value;
2322
+ }
2323
+ return answers;
2324
+ }
2325
+ export {
2326
+ wizard,
2327
+ useKeyboard,
2328
+ useFocus,
2329
+ useContext2 as useContext,
2330
+ tui,
2331
+ symbols,
2332
+ signal10 as signal,
2333
+ requestDeviceCode,
2334
+ renderToString,
2335
+ prompt,
2336
+ pollTokenUntilComplete,
2337
+ onMount,
2338
+ match,
2339
+ isInteractive,
2340
+ createTaskRunner,
2341
+ createContext2 as createContext,
2342
+ computed,
2343
+ colors,
2344
+ batch,
2345
+ TextInput,
2346
+ Text,
2347
+ TaskRunner,
2348
+ TaskList,
2349
+ Task,
2350
+ Table,
2351
+ Spinner,
2352
+ Spacer,
2353
+ SelectList,
2354
+ Select,
2355
+ ProgressBar,
2356
+ PasswordInput,
2357
+ NonInteractiveError,
2358
+ MultiSelect,
2359
+ Message,
2360
+ LogStream,
2361
+ Log,
2362
+ KeyValue,
2363
+ FocusContext,
2364
+ Divider,
2365
+ DiagnosticView,
2366
+ DeviceCodeAuth,
2367
+ Dashboard,
2368
+ Confirm,
2369
+ Box,
2370
+ Banner,
2371
+ AuthExpiredError,
2372
+ AuthDeniedError,
2373
+ AuthCancelledError
2374
+ };