toolcraft 0.0.70 → 0.0.72
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 +28 -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/package.json +2 -2
|
@@ -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, formatGroupHeader(row.group, bodyRect), 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,20 +55,33 @@ 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;
|
|
63
74
|
}
|
|
64
75
|
}
|
|
65
76
|
}
|
|
77
|
+
function formatGroupHeader(label, rect) {
|
|
78
|
+
const text = ` ${label} `;
|
|
79
|
+
const labelWidth = cellWidth(text, rect.x);
|
|
80
|
+
if (labelWidth >= rect.width) {
|
|
81
|
+
return label;
|
|
82
|
+
}
|
|
83
|
+
return `${text}${"─".repeat(rect.width - labelWidth)}`;
|
|
84
|
+
}
|
|
66
85
|
function renderRow(screen, rect, rowY, row, opts) {
|
|
67
86
|
const styles = getExplorerStyles();
|
|
68
87
|
const marker = opts.selected ? "┃" : " ";
|
|
@@ -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
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolcraft",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.72",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -47,7 +47,7 @@
|
|
|
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.
|
|
50
|
+
"toolcraft-schema": "0.0.72",
|
|
51
51
|
"commander": "^13.1.0",
|
|
52
52
|
"fast-string-width": "^3.0.2",
|
|
53
53
|
"fast-wrap-ansi": "^0.2.0",
|