toolcraft 0.0.69 → 0.0.71
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/node_modules/toolcraft-design/dist/explorer/layout.js +5 -3
- package/node_modules/toolcraft-design/dist/explorer/reducer.js +22 -6
- package/node_modules/toolcraft-design/dist/explorer/render/detail.js +19 -6
- package/node_modules/toolcraft-design/dist/explorer/render/list.js +20 -9
- package/node_modules/toolcraft-design/dist/explorer/render/pane.d.ts +5 -0
- package/node_modules/toolcraft-design/dist/explorer/render/pane.js +30 -0
- package/node_modules/toolcraft-design/dist/explorer/two-pane.d.ts +8 -0
- package/node_modules/toolcraft-design/dist/explorer/two-pane.js +70 -6
- package/node_modules/toolcraft-design/dist/prompts/interactive/glyphs.d.ts +3 -3
- package/node_modules/toolcraft-design/dist/prompts/interactive/glyphs.js +3 -3
- package/node_modules/toolcraft-design/dist/prompts/interactive/keys.js +3 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/multiselect.js +8 -4
- package/package.json +4 -4
|
@@ -40,13 +40,15 @@ export function computeExplorerLayout(opts) {
|
|
|
40
40
|
footer
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
|
-
const
|
|
44
|
-
const
|
|
43
|
+
const gutterWidth = 1;
|
|
44
|
+
const availableWidth = Math.max(0, cols - gutterWidth);
|
|
45
|
+
const listWidth = mode === "wide" ? Math.floor((availableWidth * 5) / 12) : Math.floor((availableWidth * 2) / 5);
|
|
46
|
+
const detailWidth = availableWidth - listWidth;
|
|
45
47
|
return {
|
|
46
48
|
mode,
|
|
47
49
|
header,
|
|
48
50
|
list: { x: 0, y: contentY, width: listWidth, height: contentHeight },
|
|
49
|
-
detail: { x: listWidth, y: contentY, width: detailWidth, height: contentHeight },
|
|
51
|
+
detail: { x: listWidth + gutterWidth, y: contentY, width: detailWidth, height: contentHeight },
|
|
50
52
|
footer
|
|
51
53
|
};
|
|
52
54
|
}
|
|
@@ -238,7 +238,9 @@ function detailLoaded(state, rowId, token, items) {
|
|
|
238
238
|
};
|
|
239
239
|
}
|
|
240
240
|
function detailItemRendered(state, rowId, token, itemIndex, content) {
|
|
241
|
-
if (state.detail.rowId !== rowId ||
|
|
241
|
+
if (state.detail.rowId !== rowId ||
|
|
242
|
+
state.detail.token !== token ||
|
|
243
|
+
state.detail.items?.[itemIndex] === undefined) {
|
|
242
244
|
return mark(state, 0);
|
|
243
245
|
}
|
|
244
246
|
const items = state.detail.items.map((item, index) => index === itemIndex ? { ...item, renderedContent: content } : item);
|
|
@@ -354,7 +356,14 @@ function updateFilter(state, filter) {
|
|
|
354
356
|
matchPositions,
|
|
355
357
|
cursor,
|
|
356
358
|
detail,
|
|
357
|
-
actionState: recomputeActionState({
|
|
359
|
+
actionState: recomputeActionState({
|
|
360
|
+
...state,
|
|
361
|
+
filter,
|
|
362
|
+
filtered,
|
|
363
|
+
matchPositions,
|
|
364
|
+
cursor,
|
|
365
|
+
detail
|
|
366
|
+
}),
|
|
358
367
|
dirty: REGION_HEADER | REGION_LIST | REGION_DETAIL | REGION_FOOTER
|
|
359
368
|
};
|
|
360
369
|
const effect = detailEffect(next);
|
|
@@ -385,7 +394,11 @@ function escape(state, runtimeHandles) {
|
|
|
385
394
|
if (state.filterFocused || state.filter.length > 0) {
|
|
386
395
|
const cleared = updateFilter({ ...state, filterFocused: false }, "");
|
|
387
396
|
return {
|
|
388
|
-
state: {
|
|
397
|
+
state: {
|
|
398
|
+
...cleared.state,
|
|
399
|
+
filterFocused: false,
|
|
400
|
+
dirty: cleared.state.dirty | REGION_HEADER | REGION_FOOTER
|
|
401
|
+
},
|
|
389
402
|
effects: cleared.effects
|
|
390
403
|
};
|
|
391
404
|
}
|
|
@@ -500,9 +513,9 @@ function detailBodyHeight(state) {
|
|
|
500
513
|
if (state.layout === "narrow-vertical") {
|
|
501
514
|
const listHeight = Math.ceil(contentHeight / 2);
|
|
502
515
|
const detailHeight = contentHeight - listHeight;
|
|
503
|
-
return Math.max(0, detailHeight -
|
|
516
|
+
return Math.max(0, detailHeight - 2);
|
|
504
517
|
}
|
|
505
|
-
return contentHeight;
|
|
518
|
+
return Math.max(0, contentHeight - 2);
|
|
506
519
|
}
|
|
507
520
|
function extendSelection(state, delta) {
|
|
508
521
|
if (!state.multiSelect) {
|
|
@@ -557,7 +570,10 @@ function reorder(state, delta) {
|
|
|
557
570
|
actionState: recomputeActionState({ ...state, rows, filtered, matchPositions, cursor }),
|
|
558
571
|
dirty: REGION_LIST | REGION_FOOTER
|
|
559
572
|
};
|
|
560
|
-
return {
|
|
573
|
+
return {
|
|
574
|
+
state: next,
|
|
575
|
+
effects: [{ type: "persistOrder", orderedIds: rows.map((row) => row.id) }]
|
|
576
|
+
};
|
|
561
577
|
}
|
|
562
578
|
function paletteInput(state, key) {
|
|
563
579
|
if (state.modal?.kind !== "palette") {
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { stripAnsi } from "../../internal/strip-ansi.js";
|
|
2
|
+
import { renderMarkdown } from "../../terminal-markdown/index.js";
|
|
1
3
|
import { getExplorerStyles } from "../theme.js";
|
|
4
|
+
import { drawPaneFrame, paneBodyRect } from "./pane.js";
|
|
2
5
|
import { fitToWidth } from "./text.js";
|
|
3
6
|
export function renderDetail(state, screen, layout) {
|
|
4
7
|
const rect = layout.detail;
|
|
@@ -9,16 +12,19 @@ export function renderDetail(state, screen, layout) {
|
|
|
9
12
|
}
|
|
10
13
|
const row = state.rows.find((r) => r.id === state.detail.rowId) ?? null;
|
|
11
14
|
if (layout.mode === "narrow-vertical") {
|
|
12
|
-
screen
|
|
13
|
-
renderDetailBody(state, screen,
|
|
15
|
+
drawPaneFrame(screen, rect, "Preview", state.focused === "detail" ? styles.borderFocused : styles.border);
|
|
16
|
+
renderDetailBody(state, screen, paneBodyRect(rect), row);
|
|
14
17
|
return;
|
|
15
18
|
}
|
|
16
|
-
screen
|
|
17
|
-
renderDetailBody(state, screen,
|
|
19
|
+
drawPaneFrame(screen, rect, "Preview", state.focused === "detail" ? styles.borderFocused : styles.border);
|
|
20
|
+
renderDetailBody(state, screen, paneBodyRect(rect), row);
|
|
18
21
|
}
|
|
19
22
|
function renderDetailBody(state, screen, rect, row) {
|
|
20
23
|
const styles = getExplorerStyles();
|
|
21
24
|
const items = state.detail.items;
|
|
25
|
+
if (rect.width <= 0 || rect.height <= 0) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
22
28
|
if (items === null) {
|
|
23
29
|
writeLine(screen, rect, 0, state.detail.loading ? "Loading detail..." : state.emptyHint, styles.muted);
|
|
24
30
|
return;
|
|
@@ -28,7 +34,7 @@ function renderDetailBody(state, screen, rect, row) {
|
|
|
28
34
|
return;
|
|
29
35
|
}
|
|
30
36
|
if (items.length === 1 && items[0]?.title === undefined) {
|
|
31
|
-
renderBlob(screen, rect,
|
|
37
|
+
renderBlob(screen, rect, renderItemMarkdown(items[0], rect, row), state.detail.scroll);
|
|
32
38
|
return;
|
|
33
39
|
}
|
|
34
40
|
renderListMode(state, screen, rect, items, row);
|
|
@@ -49,7 +55,7 @@ function renderListMode(state, screen, rect, items, row) {
|
|
|
49
55
|
writeLine(screen, rect, y, ` ${item.subtitle}`, styles.muted);
|
|
50
56
|
y += 1;
|
|
51
57
|
}
|
|
52
|
-
for (const line of
|
|
58
|
+
for (const line of renderItemMarkdown(item, rect, row).split("\n")) {
|
|
53
59
|
if (y >= rect.height) {
|
|
54
60
|
break;
|
|
55
61
|
}
|
|
@@ -69,6 +75,13 @@ function renderBlob(screen, rect, text, scroll) {
|
|
|
69
75
|
writeLine(screen, rect, row, lines[row] ?? "");
|
|
70
76
|
}
|
|
71
77
|
}
|
|
78
|
+
function renderItemMarkdown(item, rect, row) {
|
|
79
|
+
const content = renderItem(item, rect, row);
|
|
80
|
+
if (content.trim().length === 0) {
|
|
81
|
+
return "";
|
|
82
|
+
}
|
|
83
|
+
return stripAnsi(renderMarkdown(content, { width: Math.max(1, rect.width) }).trimEnd());
|
|
84
|
+
}
|
|
72
85
|
function renderItem(item, rect, row) {
|
|
73
86
|
if (item.renderedContent !== undefined) {
|
|
74
87
|
return item.renderedContent;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getExplorerStyles } from "../theme.js";
|
|
2
|
+
import { drawPaneFrame, paneBodyRect } from "./pane.js";
|
|
2
3
|
import { cellWidth, centerCells, fitToWidth, splitGraphemeCells, stripAnsi } from "./text.js";
|
|
3
4
|
const listLineCache = new WeakMap();
|
|
4
5
|
export function renderList(state, screen, layout) {
|
|
@@ -12,36 +13,41 @@ export function renderList(state, screen, layout) {
|
|
|
12
13
|
writeLine(screen, rect, 0, "Terminal too narrow", styles.muted);
|
|
13
14
|
return;
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
+
drawPaneFrame(screen, rect, "Plans", state.focused === "list" ? styles.borderFocused : styles.border);
|
|
17
|
+
const bodyRect = paneBodyRect(rect);
|
|
18
|
+
if (bodyRect.width <= 0 || bodyRect.height <= 0) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const rectKey = `${bodyRect.x}:${bodyRect.y}:${bodyRect.width}:${bodyRect.height}`;
|
|
16
22
|
const cached = listLineCache.get(screen);
|
|
17
23
|
const cache = cached?.rectKey === rectKey ? cached.lines : new Map();
|
|
18
24
|
listLineCache.set(screen, { rectKey, lines: cache });
|
|
19
25
|
if (state.filtered.length === 0) {
|
|
20
26
|
const hint = state.emptyHint;
|
|
21
|
-
writeLine(screen,
|
|
27
|
+
writeLine(screen, bodyRect, Math.floor(bodyRect.height / 2), centerCells(hint, bodyRect.width, bodyRect.x), styles.muted);
|
|
22
28
|
cache.clear();
|
|
23
29
|
return;
|
|
24
30
|
}
|
|
25
31
|
let lastGroup;
|
|
26
32
|
let y = 0;
|
|
27
33
|
for (const rowIndex of state.filtered) {
|
|
28
|
-
if (y >=
|
|
34
|
+
if (y >= bodyRect.height) {
|
|
29
35
|
break;
|
|
30
36
|
}
|
|
31
37
|
const row = state.rows[rowIndex];
|
|
32
38
|
if (!row) {
|
|
33
39
|
continue;
|
|
34
40
|
}
|
|
35
|
-
if (row.group && row.group !== lastGroup && y <
|
|
41
|
+
if (row.group && row.group !== lastGroup && y < bodyRect.height) {
|
|
36
42
|
const hash = `group:${row.group}`;
|
|
37
43
|
if (cache.get(y) !== hash) {
|
|
38
|
-
writeLine(screen,
|
|
44
|
+
writeLine(screen, bodyRect, y, row.group, styles.muted);
|
|
39
45
|
cache.set(y, hash);
|
|
40
46
|
}
|
|
41
47
|
y += 1;
|
|
42
48
|
lastGroup = row.group;
|
|
43
49
|
}
|
|
44
|
-
if (y >=
|
|
50
|
+
if (y >= bodyRect.height) {
|
|
45
51
|
break;
|
|
46
52
|
}
|
|
47
53
|
const selected = state.multiSelect && state.selected.has(row.id);
|
|
@@ -49,14 +55,19 @@ export function renderList(state, screen, layout) {
|
|
|
49
55
|
const positions = state.matchPositions.get(rowIndex) ?? [];
|
|
50
56
|
const hash = lineHash(row, selected, cursor, positions);
|
|
51
57
|
if (cache.get(y) !== hash) {
|
|
52
|
-
renderRow(screen,
|
|
58
|
+
renderRow(screen, bodyRect, y, row, {
|
|
59
|
+
selected,
|
|
60
|
+
cursor,
|
|
61
|
+
focused: state.focused === "list",
|
|
62
|
+
positions
|
|
63
|
+
});
|
|
53
64
|
cache.set(y, hash);
|
|
54
65
|
}
|
|
55
66
|
y += 1;
|
|
56
|
-
if (row.subtitle && y <
|
|
67
|
+
if (row.subtitle && y < bodyRect.height) {
|
|
57
68
|
const subtitleHash = `${hash}:subtitle:${row.subtitle}`;
|
|
58
69
|
if (cache.get(y) !== subtitleHash) {
|
|
59
|
-
writeLine(screen,
|
|
70
|
+
writeLine(screen, bodyRect, y, ` ${row.subtitle}`, styles.muted);
|
|
60
71
|
cache.set(y, subtitleHash);
|
|
61
72
|
}
|
|
62
73
|
y += 1;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { ScreenBuffer } from "../../dashboard/buffer.js";
|
|
2
|
+
import type { CellStyle } from "../../dashboard/types.js";
|
|
3
|
+
import type { Rect } from "../layout.js";
|
|
4
|
+
export declare function drawPaneFrame(screen: ScreenBuffer, rect: Rect, title: string, style?: CellStyle): void;
|
|
5
|
+
export declare function paneBodyRect(rect: Rect): Rect;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { fitToWidth, padEndCells } from "./text.js";
|
|
2
|
+
export function drawPaneFrame(screen, rect, title, style = {}) {
|
|
3
|
+
if (rect.width <= 0 || rect.height <= 0) {
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
if (rect.width === 1) {
|
|
7
|
+
for (let y = 0; y < rect.height; y += 1) {
|
|
8
|
+
screen.put(rect.x, rect.y + y, "│", style);
|
|
9
|
+
}
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const innerWidth = Math.max(0, rect.width - 2);
|
|
13
|
+
const titleSegment = padEndCells(fitToWidth(`─ ${title} `, innerWidth, rect.x + 1), innerWidth, "─", rect.x + 1);
|
|
14
|
+
screen.put(rect.x, rect.y, `┌${titleSegment}┐`, style);
|
|
15
|
+
for (let y = 1; y < rect.height - 1; y += 1) {
|
|
16
|
+
screen.put(rect.x, rect.y + y, "│", style);
|
|
17
|
+
screen.put(rect.x + rect.width - 1, rect.y + y, "│", style);
|
|
18
|
+
}
|
|
19
|
+
if (rect.height > 1) {
|
|
20
|
+
screen.put(rect.x, rect.y + rect.height - 1, `└${"─".repeat(innerWidth)}┘`, style);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function paneBodyRect(rect) {
|
|
24
|
+
return {
|
|
25
|
+
x: rect.x + 2,
|
|
26
|
+
y: rect.y + 1,
|
|
27
|
+
width: Math.max(0, rect.width - 4),
|
|
28
|
+
height: Math.max(0, rect.height - 2)
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -37,6 +37,11 @@ export interface TwoPaneExplorerConfig<R> {
|
|
|
37
37
|
panes: [TwoPaneDefinition, TwoPaneDefinition];
|
|
38
38
|
actions: TwoPaneAction<R>[];
|
|
39
39
|
refresh?: () => void | Promise<void>;
|
|
40
|
+
trace?: (record: TwoPaneTraceRecord) => void | Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
export interface TwoPaneTraceRecord {
|
|
43
|
+
event: string;
|
|
44
|
+
[key: string]: unknown;
|
|
40
45
|
}
|
|
41
46
|
export interface TwoPanePaneState {
|
|
42
47
|
id: string;
|
|
@@ -75,6 +80,8 @@ export declare class TwoPaneExplorerRuntime<R> {
|
|
|
75
80
|
constructor(config: TwoPaneExplorerConfig<R>, driver: TerminalDriver);
|
|
76
81
|
run(): Promise<R | null>;
|
|
77
82
|
private startTerminal;
|
|
83
|
+
private subscribeKeypress;
|
|
84
|
+
private pauseKeypress;
|
|
78
85
|
private loadRows;
|
|
79
86
|
private refresh;
|
|
80
87
|
private dispatchKey;
|
|
@@ -90,5 +97,6 @@ export declare class TwoPaneExplorerRuntime<R> {
|
|
|
90
97
|
private render;
|
|
91
98
|
private exit;
|
|
92
99
|
private fail;
|
|
100
|
+
private trace;
|
|
93
101
|
}
|
|
94
102
|
export declare function renderTwoPaneExplorer<R>(state: TwoPaneExplorerState, actions: TwoPaneAction<R>[], screen: ScreenBuffer): void;
|
|
@@ -51,14 +51,21 @@ export class TwoPaneExplorerRuntime {
|
|
|
51
51
|
this.driver.enterAltScreen();
|
|
52
52
|
this.driver.disableLineWrap();
|
|
53
53
|
this.driver.hideCursor();
|
|
54
|
-
this.
|
|
55
|
-
this.dispatchKey(key);
|
|
56
|
-
});
|
|
54
|
+
this.subscribeKeypress();
|
|
57
55
|
this.unsubscribeResize = this.driver.onResize(() => {
|
|
58
56
|
this.state = { ...this.state, size: normalizeSize(this.driver.getSize()) };
|
|
59
57
|
this.render();
|
|
60
58
|
});
|
|
61
59
|
}
|
|
60
|
+
subscribeKeypress() {
|
|
61
|
+
this.unsubscribeKeypress = this.driver.onKeypress((key) => {
|
|
62
|
+
this.dispatchKey(key);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
pauseKeypress() {
|
|
66
|
+
this.unsubscribeKeypress?.();
|
|
67
|
+
this.unsubscribeKeypress = undefined;
|
|
68
|
+
}
|
|
62
69
|
async loadRows(requestToken = ++this.rowsRequestToken) {
|
|
63
70
|
const [leftRows, rightRows] = await Promise.all([
|
|
64
71
|
this.config.panes[0].rows(),
|
|
@@ -84,6 +91,14 @@ export class TwoPaneExplorerRuntime {
|
|
|
84
91
|
if (this.stopped) {
|
|
85
92
|
return;
|
|
86
93
|
}
|
|
94
|
+
this.trace("key", {
|
|
95
|
+
key: traceKey(key),
|
|
96
|
+
filterFocused: this.state.filterFocused,
|
|
97
|
+
pane: this.activePane().id,
|
|
98
|
+
cursor: this.activePane().cursor,
|
|
99
|
+
selected: this.activePane().selected.size,
|
|
100
|
+
filter: this.activePane().filter
|
|
101
|
+
});
|
|
87
102
|
if (this.state.filterFocused) {
|
|
88
103
|
this.dispatchFilterKey(key);
|
|
89
104
|
return;
|
|
@@ -92,7 +107,7 @@ export class TwoPaneExplorerRuntime {
|
|
|
92
107
|
this.exit(null);
|
|
93
108
|
return;
|
|
94
109
|
}
|
|
95
|
-
if (key
|
|
110
|
+
if (isTabKey(key)) {
|
|
96
111
|
this.state = {
|
|
97
112
|
...this.state,
|
|
98
113
|
activePaneIndex: this.state.activePaneIndex === 0 ? 1 : 0
|
|
@@ -112,7 +127,7 @@ export class TwoPaneExplorerRuntime {
|
|
|
112
127
|
this.setCursor(filteredRows(this.activePane()).length - 1);
|
|
113
128
|
return;
|
|
114
129
|
}
|
|
115
|
-
if (key.ch === " ") {
|
|
130
|
+
if (key.ch === " " || key.name === "space") {
|
|
116
131
|
this.toggleSelection();
|
|
117
132
|
return;
|
|
118
133
|
}
|
|
@@ -131,8 +146,14 @@ export class TwoPaneExplorerRuntime {
|
|
|
131
146
|
}
|
|
132
147
|
}
|
|
133
148
|
dispatchFilterKey(key) {
|
|
134
|
-
if (key.name === "escape" || key.name === "return") {
|
|
149
|
+
if (key.name === "escape" || key.name === "return" || key.name === "enter") {
|
|
135
150
|
this.state = { ...this.state, filterFocused: false };
|
|
151
|
+
this.trace("filter.submit", {
|
|
152
|
+
key: traceKey(key),
|
|
153
|
+
pane: this.activePane().id,
|
|
154
|
+
filter: this.activePane().filter,
|
|
155
|
+
rows: filteredRows(this.activePane()).length
|
|
156
|
+
});
|
|
136
157
|
this.render();
|
|
137
158
|
return;
|
|
138
159
|
}
|
|
@@ -145,6 +166,12 @@ export class TwoPaneExplorerRuntime {
|
|
|
145
166
|
cursor: 0
|
|
146
167
|
}))
|
|
147
168
|
};
|
|
169
|
+
this.trace("filter.update", {
|
|
170
|
+
key: traceKey(key),
|
|
171
|
+
pane: this.activePane().id,
|
|
172
|
+
filter: this.activePane().filter,
|
|
173
|
+
rows: filteredRows(this.activePane()).length
|
|
174
|
+
});
|
|
148
175
|
this.render();
|
|
149
176
|
return;
|
|
150
177
|
}
|
|
@@ -157,6 +184,12 @@ export class TwoPaneExplorerRuntime {
|
|
|
157
184
|
cursor: 0
|
|
158
185
|
}))
|
|
159
186
|
};
|
|
187
|
+
this.trace("filter.update", {
|
|
188
|
+
key: traceKey(key),
|
|
189
|
+
pane: this.activePane().id,
|
|
190
|
+
filter: this.activePane().filter,
|
|
191
|
+
rows: filteredRows(this.activePane()).length
|
|
192
|
+
});
|
|
160
193
|
this.render();
|
|
161
194
|
}
|
|
162
195
|
}
|
|
@@ -195,6 +228,14 @@ export class TwoPaneExplorerRuntime {
|
|
|
195
228
|
...this.state,
|
|
196
229
|
panes: updateActivePane(this.state, (candidate) => ({ ...candidate, selected }))
|
|
197
230
|
};
|
|
231
|
+
this.trace("selection.toggle", {
|
|
232
|
+
pane: pane.id,
|
|
233
|
+
row: row.id,
|
|
234
|
+
selected: selected.size,
|
|
235
|
+
checked: selected.has(row.id),
|
|
236
|
+
filter: pane.filter,
|
|
237
|
+
cursor: pane.cursor
|
|
238
|
+
});
|
|
198
239
|
this.render();
|
|
199
240
|
}
|
|
200
241
|
runAction(action) {
|
|
@@ -223,6 +264,7 @@ export class TwoPaneExplorerRuntime {
|
|
|
223
264
|
});
|
|
224
265
|
}
|
|
225
266
|
async suspendAnd(fn) {
|
|
267
|
+
this.pauseKeypress();
|
|
226
268
|
this.driver.exitAltScreen();
|
|
227
269
|
this.driver.enableLineWrap();
|
|
228
270
|
this.driver.showCursor();
|
|
@@ -236,6 +278,7 @@ export class TwoPaneExplorerRuntime {
|
|
|
236
278
|
this.driver.enterAltScreen();
|
|
237
279
|
this.driver.disableLineWrap();
|
|
238
280
|
this.driver.hideCursor();
|
|
281
|
+
this.subscribeKeypress();
|
|
239
282
|
this.state = { ...this.state, size: normalizeSize(this.driver.getSize()) };
|
|
240
283
|
this.render();
|
|
241
284
|
}
|
|
@@ -290,6 +333,15 @@ export class TwoPaneExplorerRuntime {
|
|
|
290
333
|
}
|
|
291
334
|
this.settle?.reject(error);
|
|
292
335
|
}
|
|
336
|
+
trace(event, fields = {}) {
|
|
337
|
+
const trace = this.config.trace;
|
|
338
|
+
if (trace === undefined) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
void Promise.resolve(trace({ event, ...fields })).catch(() => {
|
|
342
|
+
// Diagnostic tracing must not interfere with TUI input handling.
|
|
343
|
+
});
|
|
344
|
+
}
|
|
293
345
|
}
|
|
294
346
|
export function renderTwoPaneExplorer(state, actions, screen) {
|
|
295
347
|
screen.clear();
|
|
@@ -402,9 +454,21 @@ function actionMatchesKey(action, key) {
|
|
|
402
454
|
const keys = Array.isArray(action.key) ? action.key : [action.key];
|
|
403
455
|
return keys.some((candidate) => key.ch === candidate || key.name === candidate);
|
|
404
456
|
}
|
|
457
|
+
function isTabKey(key) {
|
|
458
|
+
return key.name === "tab" || key.ch === "\t" || (key.name === "i" && key.ctrl);
|
|
459
|
+
}
|
|
405
460
|
function firstKey(key) {
|
|
406
461
|
return Array.isArray(key) ? key[0] ?? "" : key;
|
|
407
462
|
}
|
|
463
|
+
function traceKey(key) {
|
|
464
|
+
return {
|
|
465
|
+
name: key.name,
|
|
466
|
+
ch: key.ch,
|
|
467
|
+
ctrl: key.ctrl,
|
|
468
|
+
meta: key.meta,
|
|
469
|
+
shift: key.shift
|
|
470
|
+
};
|
|
471
|
+
}
|
|
408
472
|
function isQuitKey(key) {
|
|
409
473
|
return key.ch === "q" || (key.name === "c" && key.ctrl);
|
|
410
474
|
}
|
|
@@ -10,9 +10,9 @@ export declare const GLYPHS: {
|
|
|
10
10
|
readonly barEnd: string;
|
|
11
11
|
readonly radioActive: string;
|
|
12
12
|
readonly radioInactive: string;
|
|
13
|
-
readonly checkboxActive:
|
|
14
|
-
readonly checkboxSelected:
|
|
15
|
-
readonly checkboxInactive:
|
|
13
|
+
readonly checkboxActive: "[ ]";
|
|
14
|
+
readonly checkboxSelected: "[x]";
|
|
15
|
+
readonly checkboxInactive: "[ ]";
|
|
16
16
|
readonly passwordMask: string;
|
|
17
17
|
readonly ellipsis: "...";
|
|
18
18
|
};
|
|
@@ -27,9 +27,9 @@ export const GLYPHS = {
|
|
|
27
27
|
barEnd: glyph("└", "-"),
|
|
28
28
|
radioActive: glyph("●", ">"),
|
|
29
29
|
radioInactive: glyph("○", " "),
|
|
30
|
-
checkboxActive:
|
|
31
|
-
checkboxSelected:
|
|
32
|
-
checkboxInactive:
|
|
30
|
+
checkboxActive: "[ ]",
|
|
31
|
+
checkboxSelected: "[x]",
|
|
32
|
+
checkboxInactive: "[ ]",
|
|
33
33
|
passwordMask: glyph("•", "*"),
|
|
34
34
|
ellipsis: "..."
|
|
35
35
|
};
|
|
@@ -102,10 +102,14 @@ function renderOption(option, values, active, submitted, cancelled) {
|
|
|
102
102
|
}
|
|
103
103
|
function renderMultiselectPrompt(prompt, opts) {
|
|
104
104
|
if (prompt.state === "submit" || prompt.state === "cancel") {
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
const selectedOptions = prompt.visibleOptions.filter((option) => hasValue(prompt.value, option.value));
|
|
106
|
+
const labels = selectedOptions.length > 3
|
|
107
|
+
? prompt.state === "submit"
|
|
108
|
+
? color.dim(`${selectedOptions.length} selected`)
|
|
109
|
+
: color.dim.strikethrough(`${selectedOptions.length} selected`)
|
|
110
|
+
: selectedOptions
|
|
111
|
+
.map((option) => prompt.state === "submit" ? color.dim(option.label) : color.dim.strikethrough(option.label))
|
|
112
|
+
.join(", ");
|
|
109
113
|
const end = prompt.state === "submit" ? color.green(GLYPHS.barEnd) : color.red(GLYPHS.barEnd);
|
|
110
114
|
return `${color.gray(GLYPHS.barStart)} ${symbol(prompt.state)} ${opts.message}\n${color.gray(GLYPHS.bar)} ${labels}\n${end}`;
|
|
111
115
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolcraft",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.71",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
"postpack": "node ../../scripts/manage-bundled-workspace-deps.mjs cleanup . toolcraft-design @poe-code/frontmatter @poe-code/agent-mcp-config @poe-code/agent-human-in-loop @poe-code/task-list @poe-code/agent-defs @poe-code/config-mutations @poe-code/process-runner tiny-mcp-client mcp-oauth auth-store"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"toolcraft-schema": "0.0.
|
|
51
|
-
"commander": "^
|
|
50
|
+
"toolcraft-schema": "0.0.71",
|
|
51
|
+
"commander": "^13.1.0",
|
|
52
52
|
"fast-string-width": "^3.0.2",
|
|
53
53
|
"fast-wrap-ansi": "^0.2.0",
|
|
54
54
|
"ignore": "^5.3.2",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"dist"
|
|
64
64
|
],
|
|
65
65
|
"engines": {
|
|
66
|
-
"node": ">=
|
|
66
|
+
"node": ">=18.18"
|
|
67
67
|
},
|
|
68
68
|
"repository": {
|
|
69
69
|
"type": "git",
|