@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/README.md +6 -6
- package/dist/args.cjs +35 -5
- package/dist/args.cjs.map +1 -1
- package/dist/args.js +35 -5
- package/dist/args.js.map +1 -1
- package/dist/index.cjs +1938 -1690
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1888 -1645
- package/dist/index.js.map +1 -1
- package/dist/init.cjs +1380 -57
- package/dist/init.cjs.map +1 -1
- package/dist/init.js +1384 -57
- package/dist/init.js.map +1 -1
- package/dist/logger.cjs +98 -22
- package/dist/logger.cjs.map +1 -1
- package/dist/logger.js +97 -21
- package/dist/logger.js.map +1 -1
- package/dist/params.cjs +90 -20
- package/dist/params.cjs.map +1 -1
- package/dist/params.js +90 -18
- package/dist/params.js.map +1 -1
- package/dist/screen.cjs +2 -2
- package/package.json +1 -1
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 =
|
|
28
|
-
this.overrides =
|
|
29
|
-
this.defaults =
|
|
30
|
-
this.prefixes =
|
|
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
|
-
|
|
560
|
-
|
|
561
|
-
|
|
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
|
|
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
|
-
|
|
664
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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.
|
|
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 =
|
|
2272
|
+
const args = Args.init({
|
|
985
2273
|
overrides: opts.overrides || {},
|
|
986
2274
|
defaults: opts.defaults || {}
|
|
987
2275
|
});
|
|
988
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
2374
|
+
logError(`[params]: ${error.message} (${errorLocation})`);
|
|
1048
2375
|
process.exitCode = 3;
|
|
1049
2376
|
} else if (error instanceof InitError) {
|
|
1050
|
-
|
|
2377
|
+
logError(`[init]: ${error.message} (${errorLocation})`);
|
|
1051
2378
|
process.exitCode = 4;
|
|
1052
2379
|
} else {
|
|
1053
|
-
|
|
2380
|
+
logError(`[other] error:`, error, errorLocation);
|
|
1054
2381
|
process.exitCode = 5;
|
|
1055
2382
|
}
|
|
1056
2383
|
} finally {
|