@nmakarov/cli-toolkit 0.7.1 → 0.8.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/dist/init.js CHANGED
@@ -1,15 +1,1107 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
1
3
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
4
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
5
  }) : x)(function(x) {
4
6
  if (typeof require !== "undefined") return require.apply(this, arguments);
5
7
  throw Error('Dynamic require of "' + x + '" is not supported');
6
8
  });
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
16
+
17
+ // src/screen/components.ts
18
+ import { createElement as h } from "react";
19
+ import { Box, Text } from "ink";
20
+ function getScreenWidth(maxWidth = null) {
21
+ const terminalWidth = process.stdout.columns || 80;
22
+ const availableWidth = Math.max(20, terminalWidth - 4);
23
+ return maxWidth ? Math.min(availableWidth, maxWidth) : availableWidth;
24
+ }
25
+ function ScreenContainer({ children }) {
26
+ const width = getScreenWidth();
27
+ return h(Box, {
28
+ flexDirection: "column",
29
+ marginTop: 1,
30
+ borderStyle: "single",
31
+ borderColor: "cyan",
32
+ paddingX: 1,
33
+ width
34
+ // Use the calculated width directly
35
+ }, children);
36
+ }
37
+ function ScreenRow({ children }) {
38
+ return h(Box, { flexDirection: "column" }, children);
39
+ }
40
+ function ScreenTitle({ text }) {
41
+ return h(
42
+ ScreenRow,
43
+ {},
44
+ h(Text, { bold: true, color: "cyan" }, text)
45
+ );
46
+ }
47
+ function ScreenDivider({ width }) {
48
+ const dividerWidth = width || getScreenWidth() - 4;
49
+ return h(Text, { color: "cyan", dimColor: true }, "\u2500".repeat(dividerWidth));
50
+ }
51
+ function ScreenBody({ children, alignItems = "flex-start" }) {
52
+ return h(Box, { flexDirection: "column", alignItems }, children);
53
+ }
54
+ function ScreenFooter({ lines, textStyle }) {
55
+ const defaultTextStyle = {
56
+ dimColor: true,
57
+ color: "white"
58
+ };
59
+ const finalTextStyle = { ...defaultTextStyle, ...textStyle };
60
+ const flattenAndWrap = (items, keyPrefix = "") => {
61
+ const result = [];
62
+ let keyIndex = 0;
63
+ items.forEach((item, index) => {
64
+ if (Array.isArray(item)) {
65
+ const nested = flattenAndWrap(item, `${keyPrefix}-${index}`);
66
+ result.push(...nested);
67
+ } else if (typeof item === "string") {
68
+ result.push(
69
+ h(Text, { key: `${keyPrefix}-${keyIndex++}`, ...finalTextStyle }, item)
70
+ );
71
+ } else {
72
+ const element = item;
73
+ if (element.key === null || element.key === void 0) {
74
+ result.push(
75
+ h(Text, { key: `${keyPrefix}-${keyIndex++}`, ...finalTextStyle }, element)
76
+ );
77
+ } else {
78
+ result.push(element);
79
+ }
80
+ }
81
+ });
82
+ return result;
83
+ };
84
+ const wrappedItems = flattenAndWrap(lines);
85
+ return h(
86
+ Box,
87
+ { flexDirection: "column" },
88
+ h(Box, { flexDirection: "row" }, ...wrappedItems)
89
+ );
90
+ }
91
+ var init_components = __esm({
92
+ "src/screen/components.ts"() {
93
+ "use strict";
94
+ }
95
+ });
96
+
97
+ // src/screen/list-components.ts
98
+ import React2, { useState, useEffect, useRef, createElement } from "react";
99
+ import { Box as Box2, Text as Text2 } from "ink";
100
+ function MultiColumnListComponent({ items, ctx, selectedIndexRef }) {
101
+ const [, forceUpdate] = useState({});
102
+ const termWidth = (process.stdout.columns || 80) - 8;
103
+ const maxItemLength = Math.max(...items.map((w) => w.length));
104
+ const columnWidth = maxItemLength + 3;
105
+ const columns = Math.max(1, Math.floor(termWidth / columnWidth));
106
+ const itemsPerColumn = Math.ceil(items.length / columns);
107
+ useEffect(() => {
108
+ ctx.setAction("moveUp", () => {
109
+ selectedIndexRef.current = Math.max(0, selectedIndexRef.current - 1);
110
+ forceUpdate({});
111
+ });
112
+ ctx.setAction("moveDown", () => {
113
+ selectedIndexRef.current = Math.min(items.length - 1, selectedIndexRef.current + 1);
114
+ forceUpdate({});
115
+ });
116
+ ctx.setAction("moveLeft", () => {
117
+ if (selectedIndexRef.current === 0) {
118
+ ctx.goBack();
119
+ } else {
120
+ selectedIndexRef.current = Math.max(0, selectedIndexRef.current - itemsPerColumn);
121
+ forceUpdate({});
122
+ }
123
+ });
124
+ ctx.setAction("moveRight", () => {
125
+ selectedIndexRef.current = Math.min(items.length - 1, selectedIndexRef.current + itemsPerColumn);
126
+ forceUpdate({});
127
+ });
128
+ ctx.setKeyBinding([
129
+ { key: "leftArrow", caption: "navigate", action: "moveLeft", order: 0 },
130
+ { key: "rightArrow", caption: "navigate", action: "moveRight", order: 0 },
131
+ { key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
132
+ { key: "downArrow", caption: "navigate", action: "moveDown", order: 0 }
133
+ ]);
134
+ ctx.addFooter(`Total: ${items.length} items`);
135
+ }, []);
136
+ const selectedIndex = selectedIndexRef.current;
137
+ const rows = [];
138
+ for (let row = 0; row < itemsPerColumn; row++) {
139
+ const cols = [];
140
+ for (let col = 0; col < columns; col++) {
141
+ const index = col * itemsPerColumn + row;
142
+ if (index < items.length) {
143
+ const isSelected = index === selectedIndex;
144
+ cols.push(
145
+ h2(
146
+ Box2,
147
+ { key: index, width: columnWidth },
148
+ h2(Text2, {
149
+ color: isSelected ? "black" : "white",
150
+ backgroundColor: isSelected ? "cyan" : void 0,
151
+ bold: isSelected
152
+ }, items[index].padEnd(maxItemLength))
153
+ )
154
+ );
155
+ }
156
+ }
157
+ rows.push(
158
+ h2(ScreenRow, { key: row, children: h2(Box2, { flexDirection: "row" }, ...cols) })
159
+ );
160
+ }
161
+ return h2(Box2, { flexDirection: "column" }, ...rows);
162
+ }
163
+ function MultiColumnListWithPreviewComponent({
164
+ items,
165
+ getPreviewContent,
166
+ ctx,
167
+ selectedIndexRef
168
+ }) {
169
+ const [, forceUpdate] = useState({});
170
+ const termWidth = (process.stdout.columns || 80) - 8;
171
+ const maxItemLength = Math.max(...items.map((w) => w.length));
172
+ const columnWidth = maxItemLength + 3;
173
+ const columns = Math.max(1, Math.floor(termWidth / columnWidth));
174
+ const itemsPerColumn = Math.ceil(items.length / columns);
175
+ useEffect(() => {
176
+ ctx.setAction("moveUp", () => {
177
+ selectedIndexRef.current = Math.max(0, selectedIndexRef.current - 1);
178
+ forceUpdate({});
179
+ });
180
+ ctx.setAction("moveDown", () => {
181
+ selectedIndexRef.current = Math.min(items.length - 1, selectedIndexRef.current + 1);
182
+ forceUpdate({});
183
+ });
184
+ ctx.setAction("moveLeft", () => {
185
+ if (selectedIndexRef.current === 0) {
186
+ ctx.goBack();
187
+ } else {
188
+ selectedIndexRef.current = Math.max(0, selectedIndexRef.current - itemsPerColumn);
189
+ forceUpdate({});
190
+ }
191
+ });
192
+ ctx.setAction("moveRight", () => {
193
+ selectedIndexRef.current = Math.min(items.length - 1, selectedIndexRef.current + itemsPerColumn);
194
+ forceUpdate({});
195
+ });
196
+ ctx.setKeyBinding([
197
+ { key: "leftArrow", caption: "navigate", action: "moveLeft", order: 0 },
198
+ { key: "rightArrow", caption: "navigate", action: "moveRight", order: 0 },
199
+ { key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
200
+ { key: "downArrow", caption: "navigate", action: "moveDown", order: 0 }
201
+ ]);
202
+ ctx.addFooter(`Total: ${items.length} items`);
203
+ }, []);
204
+ const selectedIndex = selectedIndexRef.current;
205
+ const selectedItem = items[selectedIndex];
206
+ const rows = [];
207
+ for (let row = 0; row < itemsPerColumn; row++) {
208
+ const cols = [];
209
+ for (let col = 0; col < columns; col++) {
210
+ const index = col * itemsPerColumn + row;
211
+ if (index < items.length) {
212
+ const isSelected = index === selectedIndex;
213
+ cols.push(
214
+ h2(
215
+ Box2,
216
+ { key: index, width: columnWidth },
217
+ h2(Text2, {
218
+ color: isSelected ? "black" : "white",
219
+ backgroundColor: isSelected ? "cyan" : void 0,
220
+ bold: isSelected
221
+ }, items[index].padEnd(maxItemLength))
222
+ )
223
+ );
224
+ }
225
+ }
226
+ rows.push(
227
+ h2(ScreenRow, { key: row, children: h2(Box2, { flexDirection: "row" }, ...cols) })
228
+ );
229
+ }
230
+ const previewContent = getPreviewContent ? getPreviewContent(selectedItem) : selectedItem;
231
+ const previewRows = [];
232
+ if (typeof previewContent === "string") {
233
+ previewRows.push(h2(ScreenRow, { key: "preview-string", children: h2(Text2, { bold: true }, previewContent) }));
234
+ } else if (typeof previewContent === "object" && !React2.isValidElement(previewContent) && previewContent !== null) {
235
+ Object.entries(previewContent).forEach(([key, value], idx) => {
236
+ previewRows.push(h2(ScreenRow, { key: `preview-${key}-${idx}`, children: h2(Text2, {}, `${key}: ${value}`) }));
237
+ });
238
+ } else if (React2.isValidElement(previewContent)) {
239
+ previewRows.push(h2(ScreenRow, { key: "preview-element", children: previewContent }));
240
+ }
241
+ return h2(
242
+ Box2,
243
+ { flexDirection: "column" },
244
+ ...rows,
245
+ h2(ScreenRow, { key: "spacer-1", children: h2(Text2, {}, " ") }),
246
+ h2(ScreenDivider, { key: "divider" }),
247
+ h2(ScreenRow, { key: "spacer-2", children: h2(Text2, {}, " ") }),
248
+ ...previewRows
249
+ );
250
+ }
251
+ function ListComponent({ items, ctx, selectedIndexRef, renderItem, getTitle, sortable = false, maxHeight, sortHighlightStyle, selectionMarker = " " }) {
252
+ const [, forceUpdate] = useState({});
253
+ const [sortOrder, setSortOrder] = useState("none");
254
+ const [scrollOffset, setScrollOffset] = useState(0);
255
+ const scrollStateRef = useRef({ scrollOffset: 0, maxHeight: 0, totalItems: 0 });
256
+ const defaultGetTitle = (item) => {
257
+ return getTitle ? getTitle(item) : typeof item.value === "string" ? item.value : item.value?.title || item.name;
258
+ };
259
+ const titleGetter = getTitle || defaultGetTitle;
260
+ const displayItems = sortable && sortOrder !== "none" ? [...items].sort((a, b) => {
261
+ const titleA = titleGetter(a).toLowerCase();
262
+ const titleB = titleGetter(b).toLowerCase();
263
+ if (sortOrder === "asc") {
264
+ return titleA < titleB ? -1 : titleA > titleB ? 1 : 0;
265
+ } else {
266
+ return titleA > titleB ? -1 : titleA < titleB ? 1 : 0;
267
+ }
268
+ }) : items;
269
+ const effectiveMaxHeight = maxHeight || displayItems.length;
270
+ const canScroll = displayItems.length > effectiveMaxHeight;
271
+ const maxScrollOffset = Math.max(0, displayItems.length - effectiveMaxHeight);
272
+ const clampedScrollOffset = Math.min(Math.max(0, scrollOffset), maxScrollOffset);
273
+ const visibleItems = displayItems.slice(clampedScrollOffset, clampedScrollOffset + effectiveMaxHeight);
274
+ const canScrollUp = clampedScrollOffset > 0;
275
+ const canScrollDown = clampedScrollOffset < maxScrollOffset;
276
+ scrollStateRef.current = { scrollOffset, maxHeight: effectiveMaxHeight, totalItems: displayItems.length };
277
+ useEffect(() => {
278
+ ctx.setAction("moveUp", () => {
279
+ const newIndex = Math.max(0, selectedIndexRef.current - 1);
280
+ selectedIndexRef.current = newIndex;
281
+ const { scrollOffset: currentScrollOffset, maxHeight: currentMaxHeight, totalItems } = scrollStateRef.current;
282
+ const currentMaxScrollOffset = Math.max(0, totalItems - currentMaxHeight);
283
+ const currentClampedScrollOffset = Math.min(Math.max(0, currentScrollOffset), currentMaxScrollOffset);
284
+ if (newIndex < currentClampedScrollOffset) {
285
+ setScrollOffset(newIndex);
286
+ }
287
+ forceUpdate({});
288
+ });
289
+ ctx.setAction("moveDown", () => {
290
+ const currentItems = sortable && sortOrder !== "none" ? [...items].sort((a, b) => {
291
+ const titleA = titleGetter(a).toLowerCase();
292
+ const titleB = titleGetter(b).toLowerCase();
293
+ if (sortOrder === "asc") {
294
+ return titleA < titleB ? -1 : titleA > titleB ? 1 : 0;
295
+ } else {
296
+ return titleA > titleB ? -1 : titleA < titleB ? 1 : 0;
297
+ }
298
+ }) : items;
299
+ const maxIndex = currentItems.length - 1;
300
+ const newIndex = Math.min(maxIndex, selectedIndexRef.current + 1);
301
+ selectedIndexRef.current = newIndex;
302
+ const { scrollOffset: currentScrollOffset, maxHeight: currentMaxHeight, totalItems } = scrollStateRef.current;
303
+ const currentMaxScrollOffset = Math.max(0, totalItems - currentMaxHeight);
304
+ const currentClampedScrollOffset = Math.min(Math.max(0, currentScrollOffset), currentMaxScrollOffset);
305
+ if (newIndex >= currentClampedScrollOffset + currentMaxHeight) {
306
+ setScrollOffset(newIndex - currentMaxHeight + 1);
307
+ }
308
+ forceUpdate({});
309
+ });
310
+ ctx.setAction("scrollUp", () => {
311
+ const { scrollOffset: currentScrollOffset, maxHeight: currentMaxHeight, totalItems } = scrollStateRef.current;
312
+ const currentMaxScrollOffset = Math.max(0, totalItems - currentMaxHeight);
313
+ const newScrollOffset = Math.max(0, currentScrollOffset - 1);
314
+ setScrollOffset(newScrollOffset);
315
+ forceUpdate({});
316
+ });
317
+ ctx.setAction("scrollDown", () => {
318
+ const { scrollOffset: currentScrollOffset, maxHeight: currentMaxHeight, totalItems } = scrollStateRef.current;
319
+ const currentMaxScrollOffset = Math.max(0, totalItems - currentMaxHeight);
320
+ const newScrollOffset = Math.min(currentMaxScrollOffset, currentScrollOffset + 1);
321
+ setScrollOffset(newScrollOffset);
322
+ forceUpdate({});
323
+ });
324
+ if (sortable) {
325
+ ctx.setAction("toggleSort", () => {
326
+ const nextSort = sortOrder === "none" ? "asc" : sortOrder === "asc" ? "desc" : "none";
327
+ const currentSelectedItem = displayItems[selectedIndexRef.current];
328
+ setSortOrder(nextSort);
329
+ const newSortedItems = nextSort !== "none" ? [...items].sort((a, b) => {
330
+ const titleA = titleGetter(a).toLowerCase();
331
+ const titleB = titleGetter(b).toLowerCase();
332
+ if (nextSort === "asc") {
333
+ return titleA < titleB ? -1 : titleA > titleB ? 1 : 0;
334
+ } else {
335
+ return titleA > titleB ? -1 : titleA < titleB ? 1 : 0;
336
+ }
337
+ }) : items;
338
+ const newIndex = newSortedItems.findIndex((item) => item === currentSelectedItem);
339
+ if (newIndex !== -1) {
340
+ selectedIndexRef.current = newIndex;
341
+ setScrollOffset(newIndex);
342
+ } else {
343
+ selectedIndexRef.current = 0;
344
+ setScrollOffset(0);
345
+ }
346
+ forceUpdate({});
347
+ });
348
+ const defaultHighlightStyle = {
349
+ color: "black",
350
+ backgroundColor: "green",
351
+ bold: true
352
+ };
353
+ const highlightStyle = { ...defaultHighlightStyle, ...sortHighlightStyle };
354
+ const sortCaption = () => {
355
+ if (sortOrder === "none") {
356
+ return h2(Text2, {}, "s to toggle sort");
357
+ } else {
358
+ const sortLabel = sortOrder === "asc" ? "ASC" : "DESC";
359
+ return h2(
360
+ Text2,
361
+ {},
362
+ "s to toggle ",
363
+ h2(Text2, { color: "white", bold: true }, "sort"),
364
+ " ",
365
+ h2(Text2, highlightStyle, ` ${sortLabel} `)
366
+ );
367
+ }
368
+ };
369
+ ctx.setKeyBinding([
370
+ { key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
371
+ { key: "downArrow", caption: "navigate", action: "moveDown", order: 0 },
372
+ {
373
+ key: "s",
374
+ caption: sortCaption,
375
+ action: "toggleSort",
376
+ order: 5
377
+ }
378
+ ]);
379
+ ctx.update();
380
+ } else {
381
+ ctx.setKeyBinding([
382
+ { key: "upArrow", caption: "navigate", action: "moveUp", order: 0 },
383
+ { key: "downArrow", caption: "navigate", action: "moveDown", order: 0 }
384
+ ]);
385
+ }
386
+ }, [sortOrder, sortable]);
387
+ const selectedIndex = selectedIndexRef.current;
388
+ const defaultRenderItem = (item, isSelected, displayIndex, actualIndex) => {
389
+ const isFirstVisible = displayIndex === 0;
390
+ const isLastVisible = displayIndex === visibleItems.length - 1;
391
+ let arrowPrefix = "";
392
+ let selectionPrefix = "";
393
+ if (isFirstVisible && canScrollUp) {
394
+ arrowPrefix = "\u2191 ";
395
+ } else if (isLastVisible && canScrollDown) {
396
+ arrowPrefix = "\u2193 ";
397
+ } else {
398
+ arrowPrefix = " ";
399
+ }
400
+ if (isSelected) {
401
+ selectionPrefix = selectionMarker;
402
+ } else {
403
+ selectionPrefix = " ".repeat(selectionMarker.length);
404
+ }
405
+ return h2(
406
+ Box2,
407
+ { flexDirection: "row" },
408
+ // Arrow (clickable if functional, not highlighted)
409
+ h2(Text2, {
410
+ key: `arrow-${actualIndex}`,
411
+ color: "white"
412
+ }, arrowPrefix),
413
+ // Selection marker space (always same width, not highlighted)
414
+ h2(Text2, { key: `marker-${actualIndex}`, color: "white" }, selectionPrefix),
415
+ // Item name (highlighted if selected)
416
+ h2(Text2, {
417
+ key: `name-${actualIndex}`,
418
+ color: isSelected ? "black" : "white",
419
+ backgroundColor: isSelected ? "cyan" : void 0,
420
+ bold: isSelected
421
+ }, item.name)
422
+ );
423
+ };
424
+ const itemRenderer = renderItem || defaultRenderItem;
425
+ return h2(
426
+ Box2,
427
+ { flexDirection: "column" },
428
+ ...visibleItems.map((item, displayIndex) => {
429
+ const actualIndex = clampedScrollOffset + displayIndex;
430
+ const isSelected = actualIndex === selectedIndex;
431
+ if (renderItem) {
432
+ const isFirstVisible = displayIndex === 0;
433
+ const isLastVisible = displayIndex === visibleItems.length - 1;
434
+ let arrowPrefix = "";
435
+ let selectionPrefix = "";
436
+ if (isFirstVisible && canScrollUp) {
437
+ arrowPrefix = "\u2191 ";
438
+ } else if (isLastVisible && canScrollDown) {
439
+ arrowPrefix = "\u2193 ";
440
+ } else {
441
+ arrowPrefix = " ";
442
+ }
443
+ if (isSelected) {
444
+ selectionPrefix = selectionMarker;
445
+ } else {
446
+ selectionPrefix = " ".repeat(selectionMarker.length);
447
+ }
448
+ return h2(ScreenRow, {
449
+ key: `item-${actualIndex}`,
450
+ children: h2(
451
+ Box2,
452
+ { flexDirection: "row" },
453
+ // Arrow (clickable if functional, not highlighted)
454
+ h2(Text2, {
455
+ key: `arrow-${actualIndex}`,
456
+ color: "white"
457
+ }, arrowPrefix),
458
+ // Selection marker space (always same width, not highlighted)
459
+ h2(Text2, { key: `marker-${actualIndex}`, color: "white" }, selectionPrefix),
460
+ // Custom rendered content
461
+ renderItem(item, isSelected, displayIndex)
462
+ )
463
+ });
464
+ } else {
465
+ return h2(ScreenRow, {
466
+ key: `item-${actualIndex}`,
467
+ children: itemRenderer(item, isSelected, displayIndex, actualIndex)
468
+ });
469
+ }
470
+ })
471
+ );
472
+ }
473
+ var h2;
474
+ var init_list_components = __esm({
475
+ "src/screen/list-components.ts"() {
476
+ "use strict";
477
+ init_components();
478
+ h2 = createElement;
479
+ }
480
+ });
481
+
482
+ // src/screen/screens.ts
483
+ import { useState as useState2, createElement as h3 } from "react";
484
+ import { render, useInput, Text as Text3 } from "ink";
485
+ function groupKeyBindings(bindings) {
486
+ const groups = {};
487
+ const enabledBindings = bindings.filter((b) => b.enabled !== false);
488
+ enabledBindings.forEach((binding) => {
489
+ const caption = typeof binding.caption === "string" ? binding.caption : "";
490
+ if (!groups[caption]) {
491
+ groups[caption] = {
492
+ keys: [],
493
+ caption,
494
+ order: binding.order || 999
495
+ };
496
+ }
497
+ groups[caption].keys.push(binding.key);
498
+ });
499
+ return Object.values(groups);
500
+ }
501
+ function formatKeyBindings(bindings, mode = "long") {
502
+ const resolvedBindings = bindings.map((binding) => {
503
+ let resolvedCaption = binding.caption;
504
+ if (typeof binding.caption === "function") {
505
+ resolvedCaption = binding.caption();
506
+ }
507
+ return {
508
+ ...binding,
509
+ resolvedCaption
510
+ };
511
+ });
512
+ const groups = groupKeyBindings(resolvedBindings.map((b) => ({
513
+ ...b,
514
+ caption: typeof b.resolvedCaption === "string" ? b.resolvedCaption : ""
515
+ })));
516
+ groups.sort((a, b) => a.order - b.order);
517
+ const items = [];
518
+ groups.forEach((group) => {
519
+ const bindingWithCustom = resolvedBindings.find(
520
+ (b) => group.keys.includes(b.key) && typeof b.resolvedCaption !== "string"
521
+ );
522
+ if (bindingWithCustom && bindingWithCustom.resolvedCaption) {
523
+ items.push(bindingWithCustom.resolvedCaption);
524
+ } else {
525
+ const keyStr = formatKeys(group.keys);
526
+ if (mode === "long") {
527
+ items.push(`${keyStr} to ${group.caption}`);
528
+ } else {
529
+ items.push(keyStr);
530
+ }
531
+ }
532
+ });
533
+ return items;
534
+ }
535
+ function formatKeys(keys) {
536
+ const keyMap = {
537
+ "escape": "esc",
538
+ "leftArrow": "\u2190",
539
+ "rightArrow": "\u2192",
540
+ "upArrow": "\u2191",
541
+ "downArrow": "\u2193",
542
+ "return": "enter"
543
+ };
544
+ return keys.map((k) => keyMap[k] || k).join("/");
545
+ }
546
+ async function showScreen(config2) {
547
+ const {
548
+ title,
549
+ onRender,
550
+ parentData = {}
551
+ } = config2;
552
+ return new Promise((resolve2) => {
553
+ let instance;
554
+ const keyBindings = [];
555
+ const actions = {};
556
+ const customFooterItems = [];
557
+ let renderResult = null;
558
+ let initialized = false;
559
+ const Screen = () => {
560
+ const [updateCounter, setUpdateCounter] = useState2(0);
561
+ if (!initialized) {
562
+ const defaultBindings = [
563
+ { key: "escape", caption: "go back", action: "back", protected: true, order: 1 },
564
+ { key: "leftArrow", caption: "go back", action: "back", protected: false, order: 1 }
565
+ // Note: 'select' is not a default - components add it if needed
566
+ ];
567
+ defaultBindings.forEach((binding) => {
568
+ keyBindings.push(binding);
569
+ });
570
+ actions.back = () => {
571
+ cleanup(null);
572
+ };
573
+ initialized = true;
574
+ }
575
+ const context = {
576
+ setAction: (actionName, handlerFn) => {
577
+ actions[actionName] = handlerFn;
578
+ },
579
+ setKeyBinding: (bindingOrBindings) => {
580
+ const bindingsToSet = Array.isArray(bindingOrBindings) ? bindingOrBindings : [bindingOrBindings];
581
+ bindingsToSet.forEach((binding) => {
582
+ const existingIndex = keyBindings.findIndex((b) => b.key === binding.key);
583
+ if (existingIndex >= 0) {
584
+ const existing = keyBindings[existingIndex];
585
+ if (existing.protected) {
586
+ console.warn(`Cannot override protected key: ${binding.key}`);
587
+ return;
588
+ }
589
+ keyBindings[existingIndex] = {
590
+ ...existing,
591
+ ...binding,
592
+ order: binding.order !== void 0 ? binding.order : existing.order,
593
+ enabled: binding.enabled !== void 0 ? binding.enabled : existing.enabled !== void 0 ? existing.enabled : true
594
+ };
595
+ } else {
596
+ keyBindings.push({
597
+ protected: false,
598
+ order: 999,
599
+ enabled: true,
600
+ ...binding
601
+ });
602
+ }
603
+ });
604
+ },
605
+ updateKeyBinding: (keyName, updates) => {
606
+ const index = keyBindings.findIndex((b) => b.key === keyName);
607
+ if (index >= 0) {
608
+ keyBindings[index] = {
609
+ ...keyBindings[index],
610
+ ...updates
611
+ };
612
+ }
613
+ },
614
+ removeKeyBinding: (keyName) => {
615
+ const index = keyBindings.findIndex((b) => b.key === keyName);
616
+ if (index >= 0) {
617
+ if (keyBindings[index].protected) {
618
+ console.warn(`Cannot remove protected key: ${keyName}`);
619
+ return;
620
+ }
621
+ keyBindings.splice(index, 1);
622
+ }
623
+ },
624
+ addFooter: (item) => {
625
+ customFooterItems.push(item);
626
+ },
627
+ clearFooter: () => {
628
+ customFooterItems.length = 0;
629
+ },
630
+ setFooter: (items) => {
631
+ customFooterItems.length = 0;
632
+ const itemsArray = Array.isArray(items) ? items : [items];
633
+ customFooterItems.push(...itemsArray);
634
+ },
635
+ update: () => {
636
+ setUpdateCounter((c) => c + 1);
637
+ },
638
+ goBack: () => {
639
+ if (actions.back) {
640
+ actions.back();
641
+ }
642
+ },
643
+ close: (result) => {
644
+ cleanup(result);
645
+ },
646
+ parentData
647
+ };
648
+ if (!renderResult) {
649
+ renderResult = onRender(context);
650
+ }
651
+ useInput((input, key) => {
652
+ if (key.ctrl && input === "c") {
653
+ cleanup(null);
654
+ process.exit(0);
655
+ return;
656
+ }
657
+ let matchedBinding = null;
658
+ for (const binding of keyBindings) {
659
+ let keyMatches = false;
660
+ if (key[binding.key]) {
661
+ keyMatches = true;
662
+ } else if (input === binding.key) {
663
+ keyMatches = true;
664
+ }
665
+ if (keyMatches) {
666
+ if (binding.enabled === false) {
667
+ continue;
668
+ }
669
+ if (binding.condition && !binding.condition(context)) {
670
+ continue;
671
+ }
672
+ matchedBinding = binding;
673
+ break;
674
+ }
675
+ }
676
+ if (matchedBinding && actions[matchedBinding.action]) {
677
+ const actionResult = actions[matchedBinding.action]({
678
+ input,
679
+ key,
680
+ binding: matchedBinding
681
+ });
682
+ }
683
+ });
684
+ const footerLines = [];
685
+ const bindingItems = formatKeyBindings(keyBindings, "long");
686
+ if (bindingItems.length > 0) {
687
+ const bindingsLine = [];
688
+ bindingItems.forEach((item, idx) => {
689
+ if (idx > 0) {
690
+ bindingsLine.push(", ");
691
+ }
692
+ bindingsLine.push(item);
693
+ });
694
+ const allStrings = bindingItems.every((item) => typeof item === "string");
695
+ if (allStrings) {
696
+ footerLines.push(bindingsLine.join(""));
697
+ } else {
698
+ const wrappedBindingsLine = bindingsLine.map(
699
+ (item) => typeof item === "string" ? h3(Text3, {}, item) : item
700
+ );
701
+ footerLines.push(wrappedBindingsLine);
702
+ }
703
+ }
704
+ customFooterItems.forEach((item) => {
705
+ if (typeof item === "string") {
706
+ footerLines.push(item);
707
+ } else {
708
+ footerLines.push(item);
709
+ }
710
+ });
711
+ return h3(
712
+ ScreenContainer,
713
+ {},
714
+ h3(ScreenTitle, { text: title }),
715
+ h3(ScreenDivider),
716
+ h3(ScreenRow, {}, h3(Text3, {}, " ")),
717
+ renderResult,
718
+ h3(ScreenRow, {}, h3(Text3, {}, " ")),
719
+ h3(ScreenDivider),
720
+ h3(ScreenFooter, { lines: footerLines })
721
+ );
722
+ };
723
+ const cleanup = (result) => {
724
+ if (instance) instance.unmount();
725
+ setTimeout(() => resolve2(result), 50);
726
+ };
727
+ instance = render(h3(Screen));
728
+ });
729
+ }
730
+ async function showListScreen(config2) {
731
+ const { title, items, onSelect, onEscape, parentData, initialSelectedIndex = 0, renderItem, getTitle, sortable, maxHeight, sortHighlightStyle, selectionMarker } = config2;
732
+ return showScreen({
733
+ title,
734
+ parentData,
735
+ onRender: (ctx) => {
736
+ const selectedIndexRef = { current: initialSelectedIndex };
737
+ ctx.setAction("select", () => {
738
+ const selected = items[selectedIndexRef.current];
739
+ if (onSelect) {
740
+ const result = onSelect(selected.value, selectedIndexRef.current);
741
+ ctx.close(result);
742
+ }
743
+ });
744
+ if (onEscape) {
745
+ ctx.setAction("back", () => {
746
+ const result = onEscape(selectedIndexRef.current);
747
+ ctx.close(result);
748
+ });
749
+ }
750
+ ctx.setKeyBinding({ key: "return", caption: "select", action: "select", order: 2 });
751
+ return h3(ListComponent, { items, ctx, selectedIndexRef, renderItem, getTitle, sortable, maxHeight, sortHighlightStyle, selectionMarker });
752
+ }
753
+ });
754
+ }
755
+ async function showMultiColumnListScreen(config2) {
756
+ const { title, items, onSelect, onEscape, parentData, initialSelectedIndex = 0 } = config2;
757
+ return showScreen({
758
+ title,
759
+ parentData,
760
+ onRender: (ctx) => {
761
+ const selectedIndexRef = { current: initialSelectedIndex };
762
+ ctx.setAction("select", () => {
763
+ const selected = items[selectedIndexRef.current];
764
+ if (onSelect) {
765
+ const result = onSelect(selected, selectedIndexRef.current);
766
+ ctx.close(result);
767
+ }
768
+ });
769
+ if (onEscape) {
770
+ ctx.setAction("back", () => {
771
+ const result = onEscape(selectedIndexRef.current);
772
+ ctx.close(result);
773
+ });
774
+ }
775
+ ctx.setKeyBinding({ key: "return", caption: "select", action: "select", order: 2 });
776
+ return h3(MultiColumnListComponent, { items, ctx, selectedIndexRef });
777
+ }
778
+ });
779
+ }
780
+ async function showMultiColumnListWithPreviewScreen(config2) {
781
+ const { title, items, getPreviewContent, onSelect, onEscape, parentData, initialSelectedIndex = 0 } = config2;
782
+ return showScreen({
783
+ title,
784
+ parentData,
785
+ onRender: (ctx) => {
786
+ const selectedIndexRef = { current: initialSelectedIndex };
787
+ ctx.setAction("select", () => {
788
+ const selected = items[selectedIndexRef.current];
789
+ if (onSelect) {
790
+ const result = onSelect(selected, selectedIndexRef.current);
791
+ ctx.close(result);
792
+ }
793
+ });
794
+ if (onEscape) {
795
+ ctx.setAction("back", () => {
796
+ const result = onEscape(selectedIndexRef.current);
797
+ ctx.close(result);
798
+ });
799
+ }
800
+ ctx.setKeyBinding({ key: "return", caption: "select", action: "select", order: 2 });
801
+ return h3(MultiColumnListWithPreviewComponent, { items, getPreviewContent, ctx, selectedIndexRef });
802
+ }
803
+ });
804
+ }
805
+ var showMenuScreen, showWordGridScreen;
806
+ var init_screens = __esm({
807
+ "src/screen/screens.ts"() {
808
+ "use strict";
809
+ init_components();
810
+ init_list_components();
811
+ showMenuScreen = showListScreen;
812
+ showWordGridScreen = showMultiColumnListScreen;
813
+ }
814
+ });
815
+
816
+ // src/screen/ui-elements.ts
817
+ import { createElement as h4 } from "react";
818
+ import { Box as Box4, Text as Text4 } from "ink";
819
+ function ListItem({
820
+ children,
821
+ isSelected = false,
822
+ color = "white",
823
+ backgroundColor,
824
+ bold = false,
825
+ dimColor = false
826
+ }) {
827
+ return h4(
828
+ Box4,
829
+ {},
830
+ h4(Text4, {
831
+ color: isSelected ? backgroundColor || "green" : color,
832
+ backgroundColor: isSelected ? color : backgroundColor,
833
+ bold: isSelected || bold,
834
+ dimColor: !isSelected && dimColor
835
+ }, children)
836
+ );
837
+ }
838
+ function TextBlock({
839
+ text,
840
+ color = "white",
841
+ dimmed = false,
842
+ bold = false,
843
+ maxWidth
844
+ }) {
845
+ return h4(
846
+ Box4,
847
+ {},
848
+ h4(Text4, {
849
+ color,
850
+ dimColor: dimmed,
851
+ bold
852
+ }, text)
853
+ );
854
+ }
855
+ function Divider({ character = "\u2500", width = 80 }) {
856
+ return h4(
857
+ Box4,
858
+ { marginY: 1 },
859
+ h4(Text4, { dimColor: true }, character.repeat(width))
860
+ );
861
+ }
862
+ function GridCell({
863
+ children,
864
+ width,
865
+ color = "white",
866
+ backgroundColor,
867
+ bold = false,
868
+ dimColor = false,
869
+ align = "left"
870
+ }) {
871
+ return h4(
872
+ Box4,
873
+ { width },
874
+ h4(Text4, {
875
+ color,
876
+ backgroundColor,
877
+ bold,
878
+ dimColor,
879
+ textAlign: align
880
+ }, children)
881
+ );
882
+ }
883
+ function InputField({ prompt, value, onChange, onSubmit }) {
884
+ return h4(
885
+ Box4,
886
+ { flexDirection: "column" },
887
+ h4(Text4, {}, prompt),
888
+ h4(
889
+ Box4,
890
+ { marginTop: 1 },
891
+ h4(Text4, { color: "cyan" }, " > ", value, "_")
892
+ )
893
+ );
894
+ }
895
+ var init_ui_elements = __esm({
896
+ "src/screen/ui-elements.ts"() {
897
+ "use strict";
898
+ }
899
+ });
900
+
901
+ // src/screen/utils.ts
902
+ function buildBreadcrumb(parts) {
903
+ if (parts.length === 0) return "";
904
+ if (parts.length === 1) return parts[0];
905
+ return parts.slice(1).map((part) => `\u2190 ${part}`).join(" ");
906
+ }
907
+ function buildDetailBreadcrumb(path, suffix = "") {
908
+ if (path.length <= 1) {
909
+ return suffix ? `\u2190 ${suffix}` : path[0] || "";
910
+ }
911
+ const breadcrumb = buildBreadcrumb(path);
912
+ return suffix ? `${breadcrumb} ${suffix}` : breadcrumb;
913
+ }
914
+ var init_utils = __esm({
915
+ "src/screen/utils.ts"() {
916
+ "use strict";
917
+ }
918
+ });
919
+
920
+ // src/screen/footer-builder.ts
921
+ function buildFooter(config2 = {}) {
922
+ const {
923
+ navigation = null,
924
+ actions = null,
925
+ info = null,
926
+ escape = "Esc to go back",
927
+ custom = null
928
+ } = config2;
929
+ const lines = [];
930
+ const mainParts = [];
931
+ if (navigation) {
932
+ mainParts.push(navigation);
933
+ }
934
+ if (actions) {
935
+ mainParts.push(actions);
936
+ }
937
+ if (escape) {
938
+ mainParts.push(escape);
939
+ }
940
+ if (mainParts.length > 0) {
941
+ lines.push(mainParts.join(", "));
942
+ }
943
+ if (info) {
944
+ const infoLines = Array.isArray(info) ? info : [info];
945
+ lines.push(...infoLines);
946
+ }
947
+ if (custom) {
948
+ const customLines = Array.isArray(custom) ? custom : [custom];
949
+ lines.push(...customLines);
950
+ }
951
+ return lines;
952
+ }
953
+ function organizeFooterMessages(messages) {
954
+ if (!messages || messages.length === 0) {
955
+ return ["Esc to go back"];
956
+ }
957
+ const navigation = messages.filter((m) => m.includes("\u2191") || m.includes("\u2193") || m.includes("\u2190") || m.includes("\u2192"));
958
+ const actions = messages.filter((m) => m.includes("Enter") || m.includes("select") || m.includes("submit"));
959
+ const escape = messages.filter((m) => m.includes("Esc"));
960
+ const others = messages.filter(
961
+ (m) => !navigation.includes(m) && !actions.includes(m) && !escape.includes(m)
962
+ );
963
+ const lines = [];
964
+ const mainLine = [...navigation, ...actions, ...escape].join(", ");
965
+ if (mainLine) lines.push(mainLine);
966
+ lines.push(...others);
967
+ return lines;
968
+ }
969
+ var FooterPresets;
970
+ var init_footer_builder = __esm({
971
+ "src/screen/footer-builder.ts"() {
972
+ "use strict";
973
+ FooterPresets = {
974
+ /**
975
+ * Menu screen footer
976
+ */
977
+ menu: (customInfo = null) => buildFooter({
978
+ navigation: "\u2191/\u2193 to navigate",
979
+ actions: "Enter to select",
980
+ escape: "Esc to go back",
981
+ info: customInfo
982
+ }),
983
+ /**
984
+ * Word grid footer
985
+ */
986
+ wordGrid: (totalWords) => buildFooter({
987
+ navigation: "\u2191\u2193\u2190\u2192 to navigate",
988
+ actions: "Enter to select",
989
+ escape: "Esc to go back",
990
+ info: `Total: ${totalWords} words`
991
+ }),
992
+ /**
993
+ * Text input footer
994
+ */
995
+ textInput: () => buildFooter({
996
+ actions: "Type and press Enter to submit",
997
+ escape: "Esc to cancel"
998
+ }),
999
+ /**
1000
+ * Info/static screen footer
1001
+ */
1002
+ info: () => buildFooter({
1003
+ escape: "Esc to continue"
1004
+ }),
1005
+ /**
1006
+ * Main menu footer (escape exits)
1007
+ */
1008
+ mainMenu: () => buildFooter({
1009
+ navigation: "\u2191/\u2193 to navigate",
1010
+ actions: "Enter to select",
1011
+ escape: "Esc to exit"
1012
+ }),
1013
+ /**
1014
+ * Action menu footer (for word cards, etc.)
1015
+ */
1016
+ actionMenu: (hasAudio = false) => {
1017
+ const parts = buildFooter({
1018
+ navigation: "\u2191/\u2193 to navigate",
1019
+ actions: "Enter to select",
1020
+ escape: "Esc to go back"
1021
+ });
1022
+ if (hasAudio) {
1023
+ parts.push("Audio available");
1024
+ }
1025
+ return parts;
1026
+ }
1027
+ };
1028
+ }
1029
+ });
1030
+
1031
+ // src/screen/index.ts
1032
+ var screen_exports = {};
1033
+ __export(screen_exports, {
1034
+ Box: () => Box5,
1035
+ Divider: () => Divider,
1036
+ FooterPresets: () => FooterPresets,
1037
+ GridCell: () => GridCell,
1038
+ InputField: () => InputField,
1039
+ ListComponent: () => ListComponent,
1040
+ ListItem: () => ListItem,
1041
+ MultiColumnListComponent: () => MultiColumnListComponent,
1042
+ MultiColumnListWithPreviewComponent: () => MultiColumnListWithPreviewComponent,
1043
+ React: () => React5,
1044
+ ScreenBody: () => ScreenBody,
1045
+ ScreenContainer: () => ScreenContainer,
1046
+ ScreenDivider: () => ScreenDivider,
1047
+ ScreenFooter: () => ScreenFooter,
1048
+ ScreenRow: () => ScreenRow,
1049
+ ScreenTitle: () => ScreenTitle,
1050
+ Text: () => Text5,
1051
+ TextBlock: () => TextBlock,
1052
+ buildBreadcrumb: () => buildBreadcrumb,
1053
+ buildDetailBreadcrumb: () => buildDetailBreadcrumb,
1054
+ buildFooter: () => buildFooter,
1055
+ h: () => createElement2,
1056
+ load: () => load,
1057
+ organizeFooterMessages: () => organizeFooterMessages,
1058
+ showListScreen: () => showListScreen,
1059
+ showMenuScreen: () => showMenuScreen,
1060
+ showMultiColumnListScreen: () => showMultiColumnListScreen,
1061
+ showMultiColumnListWithPreviewScreen: () => showMultiColumnListWithPreviewScreen,
1062
+ showScreen: () => showScreen,
1063
+ showWordGridScreen: () => showWordGridScreen,
1064
+ useCallback: () => useCallback,
1065
+ useEffect: () => useEffect3,
1066
+ useInput: () => useInput2,
1067
+ useMemo: () => useMemo,
1068
+ useRef: () => useRef3,
1069
+ useState: () => useState3
1070
+ });
1071
+ import React5, { useState as useState3, useEffect as useEffect3, useRef as useRef3, useMemo, useCallback, createElement as createElement2 } from "react";
1072
+ import { Box as Box5, Text as Text5, useInput as useInput2 } from "ink";
1073
+ async function load() {
1074
+ if (loadPromise) return loadPromise;
1075
+ loadPromise = Promise.all([
1076
+ import("react"),
1077
+ import("ink")
1078
+ ]).then(() => {
1079
+ });
1080
+ return loadPromise;
1081
+ }
1082
+ var loadPromise;
1083
+ var init_screen = __esm({
1084
+ "src/screen/index.ts"() {
1085
+ "use strict";
1086
+ init_screens();
1087
+ init_list_components();
1088
+ init_components();
1089
+ init_ui_elements();
1090
+ init_utils();
1091
+ init_footer_builder();
1092
+ loadPromise = null;
1093
+ if (typeof window === "undefined") {
1094
+ load().catch(() => {
1095
+ });
1096
+ }
1097
+ }
1098
+ });
7
1099
 
8
1100
  // src/args/index.ts
9
1101
  import { readFileSync, existsSync } from "fs";
10
1102
  import { resolve, dirname, basename, extname, join, isAbsolute } from "path";
11
1103
  import { config } from "dotenv";
12
- var Args = class {
1104
+ var Args = class _Args {
13
1105
  args = {};
14
1106
  flags = {};
15
1107
  options = {};
@@ -24,10 +1116,13 @@ var Args = class {
24
1116
  configsLoaded = [];
25
1117
  env = "local";
26
1118
  constructor(config2 = {}) {
27
- this.aliases = config2.aliases || {};
28
- this.overrides = config2.overrides || {};
29
- this.defaults = config2.defaults || {};
30
- this.prefixes = config2.prefixes || ["not", "no"];
1119
+ this.aliases = {};
1120
+ this.overrides = {};
1121
+ this.defaults = {};
1122
+ this.prefixes = ["not", "no"];
1123
+ if (Object.keys(config2).length > 0) {
1124
+ this.configure(config2);
1125
+ }
31
1126
  const args = config2.args || process.argv.slice(2);
32
1127
  this.parseArgs(args);
33
1128
  this.env = this.get("env")?.toLowerCase() || "local";
@@ -35,6 +1130,33 @@ var Args = class {
35
1130
  this.loadConfigFiles();
36
1131
  this.checkConflicts();
37
1132
  }
1133
+ /**
1134
+ * Configure Args options
1135
+ * Only parameters present in config are updated
1136
+ * Note: Args is special - it's initialized first, so it can't take context
1137
+ */
1138
+ configure(config2) {
1139
+ if (config2.aliases !== void 0) {
1140
+ this.aliases = config2.aliases;
1141
+ }
1142
+ if (config2.overrides !== void 0) {
1143
+ this.overrides = config2.overrides;
1144
+ }
1145
+ if (config2.defaults !== void 0) {
1146
+ this.defaults = config2.defaults;
1147
+ }
1148
+ if (config2.prefixes !== void 0) {
1149
+ this.prefixes = config2.prefixes;
1150
+ }
1151
+ }
1152
+ /**
1153
+ * Initialize Args instance
1154
+ * Note: Args is special - it's initialized first, so it can't take context
1155
+ * This static method is for consistency with other components
1156
+ */
1157
+ static init(config2 = {}) {
1158
+ return new _Args(config2);
1159
+ }
38
1160
  /**
39
1161
  * Parse command line arguments
40
1162
  */
@@ -550,18 +1672,76 @@ var joiStringArrayType = (type) => (value, helpers) => {
550
1672
  };
551
1673
 
552
1674
  // src/params/index.ts
553
- var Params = class {
1675
+ var Params = class _Params {
1676
+ context;
1677
+ // Partial context during initialization
554
1678
  params = {};
555
1679
  definitions = {};
556
1680
  args;
557
1681
  paramSetters = [];
558
1682
  paramGetters = [];
559
- constructor({ args }, opts = {}) {
560
- this.args = args;
561
- for (const [k, v] of Object.entries(opts)) {
1683
+ trackedParams = [];
1684
+ constructor(context, options = {}) {
1685
+ this.context = context;
1686
+ this.args = context.args;
1687
+ if (Object.keys(options).length > 0) {
1688
+ this.configure(options);
1689
+ }
1690
+ }
1691
+ /**
1692
+ * Configure parameters
1693
+ * Only parameters present in options are updated
1694
+ */
1695
+ configure(options) {
1696
+ for (const [k, v] of Object.entries(options)) {
562
1697
  this.params[k] = v;
563
1698
  }
564
1699
  }
1700
+ /**
1701
+ * Initialize Params from context and CLI parameters
1702
+ * Note: Params is special - it's initialized early with partial context
1703
+ */
1704
+ static init(context, options) {
1705
+ return new _Params(context, options || {});
1706
+ }
1707
+ /**
1708
+ * Track a parameter request for --stopAfter=init feature
1709
+ */
1710
+ trackParam(key, definition, value, source) {
1711
+ this.trackedParams.push({
1712
+ key,
1713
+ definition,
1714
+ value,
1715
+ source
1716
+ });
1717
+ }
1718
+ /**
1719
+ * Get all tracked parameters (for --stopAfter=init)
1720
+ */
1721
+ getTrackedParams() {
1722
+ return [...this.trackedParams];
1723
+ }
1724
+ /**
1725
+ * Get all figured parameters as a record
1726
+ * Returns all parameters that were collected during initialization,
1727
+ * whether from CLI args, options, or defaults
1728
+ */
1729
+ getAllFigured() {
1730
+ const result = {};
1731
+ for (const param of this.trackedParams) {
1732
+ result[param.key] = {
1733
+ value: param.value,
1734
+ source: param.source
1735
+ };
1736
+ }
1737
+ return result;
1738
+ }
1739
+ /**
1740
+ * Clear tracked parameters
1741
+ */
1742
+ clearTrackedParams() {
1743
+ this.trackedParams = [];
1744
+ }
565
1745
  /**
566
1746
  * Assign a parameter definition
567
1747
  */
@@ -635,6 +1815,8 @@ var Params = class {
635
1815
  type = type.default(defValObj.value);
636
1816
  } else if (str.match(/required/)) {
637
1817
  type = type.required();
1818
+ } else {
1819
+ type = type.optional();
638
1820
  }
639
1821
  return type;
640
1822
  }
@@ -642,7 +1824,12 @@ var Params = class {
642
1824
  * Validate a value against a definition
643
1825
  */
644
1826
  validate(key, val, def) {
645
- const { value, error } = def.type.validate(val, { context: { params: this.params } });
1827
+ const normalizedVal = val === null ? void 0 : val;
1828
+ const { value, error } = def.type.validate(normalizedVal, {
1829
+ context: { params: this.params },
1830
+ abortEarly: false,
1831
+ allowUnknown: false
1832
+ });
646
1833
  if (error) {
647
1834
  const errs = error.details.map((el) => el.message).join(", ");
648
1835
  throw new ParamError(`"${key}" validation error: ${errs}`);
@@ -660,11 +1847,26 @@ var Params = class {
660
1847
  }
661
1848
  const valFromArgs = this.args.get(key);
662
1849
  const valFromParams = this.params[key];
663
- const res = valFromGetters ? this.validate(key, valFromGetters, def) : valFromArgs ? this.validate(key, valFromArgs, def) : this.validate(key, valFromParams, def);
664
- if (res !== void 0 && def.values && !def.values.includes(res)) {
1850
+ let source = "default";
1851
+ let value;
1852
+ if (valFromGetters !== void 0 && valFromGetters !== null) {
1853
+ value = this.validate(key, valFromGetters, def);
1854
+ source = "options";
1855
+ } else if (valFromArgs !== void 0 && valFromArgs !== null) {
1856
+ value = this.validate(key, valFromArgs, def);
1857
+ source = "cli";
1858
+ } else if (valFromParams !== void 0 && valFromParams !== null) {
1859
+ value = this.validate(key, valFromParams, def);
1860
+ source = "options";
1861
+ } else {
1862
+ value = this.validate(key, void 0, def);
1863
+ source = "default";
1864
+ }
1865
+ this.trackParam(key, definition || "string", value, source);
1866
+ if (value !== void 0 && def.values && !def.values.includes(value)) {
665
1867
  throw new ParamError(`key ${key} should be one of ${def.values}`);
666
1868
  }
667
- return res;
1869
+ return value;
668
1870
  }
669
1871
  /**
670
1872
  * Set a parameter value with validation
@@ -698,10 +1900,10 @@ var Params = class {
698
1900
  * Run all registered getters for a key
699
1901
  */
700
1902
  runAllRegisteredGetters(key) {
701
- let val = null;
1903
+ let val = void 0;
702
1904
  for (const getter of this.paramGetters) {
703
1905
  val = getter(key, this.definitions[key]);
704
- if (val !== void 0) {
1906
+ if (val !== void 0 && val !== null) {
705
1907
  break;
706
1908
  }
707
1909
  }
@@ -772,6 +1974,7 @@ var ALL_LEVELS = [
772
1974
  "response",
773
1975
  "progress"
774
1976
  ];
1977
+ var MAX_LEVEL_LENGTH = Math.max(...ALL_LEVELS.map((level) => level.toUpperCase().length));
775
1978
  var LEVEL_COLORS = {
776
1979
  error: chalk.red.bold,
777
1980
  warn: chalk.rgb(255, 165, 0),
@@ -785,13 +1988,104 @@ var LEVEL_COLORS = {
785
1988
  progress: chalk.green,
786
1989
  results: chalk.magenta
787
1990
  };
788
- var CliToolkitLogger = class {
1991
+ var Logger = class _Logger {
1992
+ context;
1993
+ // Partial context during initialization
789
1994
  options;
790
1995
  transport;
791
1996
  startTimes = {};
792
1997
  lastProgressTimes = {};
793
- constructor(options = {}) {
794
- this.options = this.normalizeOptions(options);
1998
+ constructor(context, options = {}) {
1999
+ this.context = context;
2000
+ this.options = this.getDefaultOptions();
2001
+ if (options) {
2002
+ this.configure(options);
2003
+ }
2004
+ this.updateTransport();
2005
+ }
2006
+ /**
2007
+ * Configure logger options
2008
+ * Only parameters present in options are updated
2009
+ */
2010
+ configure(options) {
2011
+ if (options.mode !== void 0) {
2012
+ this.options.mode = this.isValidMode(options.mode) ? options.mode : "text";
2013
+ }
2014
+ if (options.route !== void 0) {
2015
+ this.options.route = options.route;
2016
+ this.updateTransport();
2017
+ }
2018
+ if (options.prefix !== void 0) {
2019
+ this.options.prefix = options.prefix;
2020
+ }
2021
+ if (options.silent !== void 0) {
2022
+ this.options.silent = options.silent;
2023
+ }
2024
+ if (options.showLevel !== void 0) {
2025
+ this.options.showLevel = options.showLevel;
2026
+ }
2027
+ if (options.timestamp !== void 0) {
2028
+ this.options.timestamp = options.timestamp;
2029
+ }
2030
+ if (options.levels !== void 0) {
2031
+ this.options.levels = this.normalizeLevels(options.levels);
2032
+ }
2033
+ if (options.progress !== void 0) {
2034
+ if (options.progress.withTimes !== void 0) {
2035
+ this.options.progressTimes = options.progress.withTimes;
2036
+ }
2037
+ if (options.progress.throttleMs !== void 0) {
2038
+ this.options.progressThrottle = options.progress.throttleMs;
2039
+ }
2040
+ }
2041
+ }
2042
+ /**
2043
+ * Initialize logger from context and CLI parameters
2044
+ */
2045
+ static init(context, options) {
2046
+ const paramDefs = {
2047
+ mode: "string default text",
2048
+ route: "string default console",
2049
+ prefix: "string",
2050
+ silent: "boolean default false",
2051
+ showLevel: "boolean default true",
2052
+ timestamp: "boolean default false",
2053
+ levels: "string",
2054
+ progressWithTimes: "boolean default false",
2055
+ progressThrottleMs: "number"
2056
+ };
2057
+ const cliParams = context.params.getAll(paramDefs);
2058
+ const config2 = {
2059
+ mode: options?.mode ?? cliParams.mode,
2060
+ route: options?.route ?? cliParams.route,
2061
+ prefix: options?.prefix ?? cliParams.prefix,
2062
+ silent: options?.silent ?? cliParams.silent,
2063
+ showLevel: options?.showLevel ?? cliParams.showLevel,
2064
+ timestamp: options?.timestamp ?? cliParams.timestamp,
2065
+ levels: options?.levels ?? (cliParams.levels ? cliParams.levels.split(",") : void 0),
2066
+ progress: options?.progress ?? {
2067
+ withTimes: cliParams.progressWithTimes,
2068
+ throttleMs: cliParams.progressThrottleMs
2069
+ }
2070
+ };
2071
+ const logger = new _Logger(context, config2);
2072
+ context.logger = logger;
2073
+ return logger;
2074
+ }
2075
+ getDefaultOptions() {
2076
+ return {
2077
+ mode: "text",
2078
+ route: this.shouldUseIpcRoute() ? "ipc" : "console",
2079
+ prefix: void 0,
2080
+ silent: false,
2081
+ showLevel: true,
2082
+ timestamp: false,
2083
+ levels: ALL_LEVELS,
2084
+ progressTimes: false,
2085
+ progressThrottle: void 0
2086
+ };
2087
+ }
2088
+ updateTransport() {
795
2089
  this.transport = this.options.route === "ipc" ? new ParentProcessTransport() : new ConsoleTransport();
796
2090
  }
797
2091
  setMode(mode) {
@@ -900,7 +2194,7 @@ var CliToolkitLogger = class {
900
2194
  parts.push(now.toISOString());
901
2195
  }
902
2196
  if (this.options.showLevel) {
903
- parts.push(struct.level.toUpperCase());
2197
+ parts.push(struct.level.toUpperCase().padEnd(MAX_LEVEL_LENGTH));
904
2198
  }
905
2199
  if (struct.level === "progress") {
906
2200
  if (struct.prefix) {
@@ -934,22 +2228,6 @@ var CliToolkitLogger = class {
934
2228
  inspectChunks(chunks) {
935
2229
  return chunks.map((chunk) => util.inspect(chunk, { colors: true, depth: null })).join(" ");
936
2230
  }
937
- normalizeOptions(options) {
938
- const { route, mode, prefix, silent, showLevel, timestamp, levels, progress } = options;
939
- const shouldUseIpc = this.shouldUseIpcRoute();
940
- const normalized = {
941
- mode: this.isValidMode(mode) ? mode : "text",
942
- route: route ?? (shouldUseIpc ? "ipc" : "console"),
943
- prefix,
944
- silent: silent ?? false,
945
- showLevel: showLevel ?? true,
946
- timestamp: timestamp ?? false,
947
- levels: this.normalizeLevels(levels),
948
- progressTimes: progress?.withTimes ?? false,
949
- progressThrottle: progress?.throttleMs
950
- };
951
- return normalized;
952
- }
953
2231
  shouldUseIpcRoute() {
954
2232
  if (process.env.VITEST || process.env.NODE_ENV === "test") {
955
2233
  return false;
@@ -980,35 +2258,44 @@ var CliToolkitLogger = class {
980
2258
 
981
2259
  // src/init/index.ts
982
2260
  import { EventEmitter } from "events";
2261
+ function extractComponentOptions(opts, componentName) {
2262
+ const reservedKeys = ["overrides", "defaults", "modules"];
2263
+ const componentOptions = {};
2264
+ for (const [key, value] of Object.entries(opts)) {
2265
+ if (!reservedKeys.includes(key)) {
2266
+ componentOptions[key] = value;
2267
+ }
2268
+ }
2269
+ return componentOptions;
2270
+ }
983
2271
  function setup(opts = {}) {
984
- const args = new Args({
2272
+ const args = Args.init({
985
2273
  overrides: opts.overrides || {},
986
2274
  defaults: opts.defaults || {}
987
2275
  });
988
- const params = new Params({ args }, opts.overrides || {});
989
- const loggerOptions = opts.logger || {};
990
- const logger = new CliToolkitLogger({
991
- mode: loggerOptions.mode || "text",
992
- route: loggerOptions.route || "console",
993
- prefix: loggerOptions.prefix,
994
- silent: loggerOptions.silent,
995
- showLevel: loggerOptions.showLevel,
996
- timestamp: loggerOptions.timestamp,
997
- levels: loggerOptions.levels
998
- });
999
- const cleanupFunctions = [];
1000
- const context = {
2276
+ const partialContext = {
1001
2277
  args,
1002
- params,
1003
- logger,
1004
2278
  emitter: new EventEmitter(),
1005
2279
  isStop: () => false,
1006
- // Will be set in init function
1007
- cleanupFunctions,
2280
+ cleanupFunctions: [],
1008
2281
  registerCleanup: (fn) => {
1009
- cleanupFunctions.push(fn);
2282
+ partialContext.cleanupFunctions.push(fn);
1010
2283
  }
1011
2284
  };
2285
+ const params = Params.init(partialContext, opts.overrides || {});
2286
+ partialContext.params = params;
2287
+ const loggerOptions = extractComponentOptions(opts, "logger");
2288
+ const logger = Logger.init(partialContext, loggerOptions);
2289
+ partialContext.logger = logger;
2290
+ const context = {
2291
+ args,
2292
+ params,
2293
+ logger,
2294
+ emitter: partialContext.emitter,
2295
+ isStop: partialContext.isStop,
2296
+ cleanupFunctions: partialContext.cleanupFunctions,
2297
+ registerCleanup: partialContext.registerCleanup
2298
+ };
1012
2299
  logger.debug("[setup] completed successfully");
1013
2300
  return context;
1014
2301
  }
@@ -1019,13 +2306,46 @@ async function setupModules(context, opts = {}) {
1019
2306
  context.logger.debug("[setupModules] completed successfully");
1020
2307
  return context;
1021
2308
  }
2309
+ function printAllParameters(context) {
2310
+ const trackedParams = context.params.getTrackedParams();
2311
+ console.log("\n=== All Figured Parameters ===");
2312
+ console.log("\nComponent: Logger");
2313
+ const loggerParams = trackedParams.filter(
2314
+ (p) => ["mode", "route", "prefix", "silent", "showLevel", "timestamp", "levels"].includes(p.key)
2315
+ );
2316
+ if (loggerParams.length > 0) {
2317
+ loggerParams.forEach((p) => {
2318
+ console.log(` ${p.key}: ${JSON.stringify(p.value)} (from ${p.source})`);
2319
+ });
2320
+ } else {
2321
+ console.log(" (no parameters requested)");
2322
+ }
2323
+ console.log("\n=== End Parameters ===\n");
2324
+ }
1022
2325
  async function init(flow, opts = {}) {
1023
2326
  let stop = false;
1024
2327
  let context = null;
1025
2328
  try {
2329
+ try {
2330
+ const screenModule = await Promise.resolve().then(() => (init_screen(), screen_exports));
2331
+ if (screenModule && typeof screenModule.load === "function") {
2332
+ await screenModule.load();
2333
+ }
2334
+ } catch {
2335
+ if (typeof __require !== "undefined") {
2336
+ try {
2337
+ } catch {
2338
+ }
2339
+ }
2340
+ }
1026
2341
  context = setup(opts);
1027
2342
  context.isStop = () => stop;
1028
2343
  context = await setupModules(context, opts);
2344
+ const stopAfter = context.args.get("stopAfter");
2345
+ if (stopAfter === "init") {
2346
+ printAllParameters(context);
2347
+ process.exit(0);
2348
+ }
1029
2349
  process.on("SIGINT", async () => {
1030
2350
  if (stop) {
1031
2351
  context.logger.warn("[process] killed");
@@ -1043,14 +2363,21 @@ async function init(flow, opts = {}) {
1043
2363
  await flow(context);
1044
2364
  } catch (error) {
1045
2365
  const errorLocation = error instanceof Error && error.stack ? error.stack.split("\n")[1]?.trim() || "Unknown location" : "Unknown location";
2366
+ const logError = (msg, ...args) => {
2367
+ if (context?.logger) {
2368
+ context.logger.error(msg, ...args);
2369
+ } else {
2370
+ console.error(msg, ...args);
2371
+ }
2372
+ };
1046
2373
  if (error instanceof ParamError) {
1047
- context?.logger.error(`[params]: ${error.message} (${errorLocation})`);
2374
+ logError(`[params]: ${error.message} (${errorLocation})`);
1048
2375
  process.exitCode = 3;
1049
2376
  } else if (error instanceof InitError) {
1050
- context?.logger.error(`[init]: ${error.message} (${errorLocation})`);
2377
+ logError(`[init]: ${error.message} (${errorLocation})`);
1051
2378
  process.exitCode = 4;
1052
2379
  } else {
1053
- context?.logger.error(`[other] error:`, error, errorLocation);
2380
+ logError(`[other] error:`, error, errorLocation);
1054
2381
  process.exitCode = 5;
1055
2382
  }
1056
2383
  } finally {