toolcraft 0.0.65 → 0.0.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/renderer.js +37 -2
- package/node_modules/toolcraft-design/dist/components/inspector-card.d.ts +21 -0
- package/node_modules/toolcraft-design/dist/components/inspector-card.js +42 -0
- package/node_modules/toolcraft-design/dist/components/resource-browser.d.ts +21 -0
- package/node_modules/toolcraft-design/dist/components/resource-browser.js +98 -0
- package/node_modules/toolcraft-design/dist/explorer/index.d.ts +2 -0
- package/node_modules/toolcraft-design/dist/explorer/index.js +1 -0
- package/node_modules/toolcraft-design/dist/explorer/two-pane.d.ts +94 -0
- package/node_modules/toolcraft-design/dist/explorer/two-pane.js +446 -0
- package/node_modules/toolcraft-design/dist/index.d.ts +6 -2
- package/node_modules/toolcraft-design/dist/index.js +3 -1
- package/package.json +2 -2
package/dist/renderer.js
CHANGED
|
@@ -46,6 +46,9 @@ function unwrapMcpEnvelope(result) {
|
|
|
46
46
|
function isArrayOfObjects(value) {
|
|
47
47
|
return Array.isArray(value) && value.every((entry) => isObject(entry));
|
|
48
48
|
}
|
|
49
|
+
function isNonEmptyArrayOfObjects(value) {
|
|
50
|
+
return Array.isArray(value) && value.length > 0 && value.every((entry) => isObject(entry));
|
|
51
|
+
}
|
|
49
52
|
function stringifyValue(value) {
|
|
50
53
|
if (value === undefined) {
|
|
51
54
|
return "";
|
|
@@ -97,10 +100,26 @@ function detailRows(result, depth = 0) {
|
|
|
97
100
|
rows.push(...detailRows(value, depth + 1));
|
|
98
101
|
continue;
|
|
99
102
|
}
|
|
103
|
+
if (isNonEmptyArrayOfObjects(value)) {
|
|
104
|
+
rows.push({ label, value: "" });
|
|
105
|
+
rows.push(...arrayObjectDetailRows(value, depth + 1));
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
100
108
|
rows.push({ label, value: displayScalar(value) });
|
|
101
109
|
}
|
|
102
110
|
return rows;
|
|
103
111
|
}
|
|
112
|
+
function arrayObjectDetailRows(value, depth) {
|
|
113
|
+
return value.flatMap((entry, index) => {
|
|
114
|
+
if (value.length === 1) {
|
|
115
|
+
return detailRows(entry, depth);
|
|
116
|
+
}
|
|
117
|
+
return [
|
|
118
|
+
{ label: `${" ".repeat(depth)}${index + 1}`, value: "" },
|
|
119
|
+
...detailRows(entry, depth + 1)
|
|
120
|
+
];
|
|
121
|
+
});
|
|
122
|
+
}
|
|
104
123
|
function displayScalar(value) {
|
|
105
124
|
if (typeof value === "boolean") {
|
|
106
125
|
return value ? "Yes" : "No";
|
|
@@ -137,11 +156,26 @@ function directObjectSections(result) {
|
|
|
137
156
|
.map(([key, value]) => ({ title: humanizeKey(key), rows: detailRows(value) }))
|
|
138
157
|
.filter((section) => section.rows.length > 0);
|
|
139
158
|
}
|
|
159
|
+
function directArrayObjectSections(result) {
|
|
160
|
+
return Object.entries(result).flatMap(([key, value]) => {
|
|
161
|
+
if (!isNonEmptyArrayOfObjects(value)) {
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
const title = humanizeKey(key);
|
|
165
|
+
return value
|
|
166
|
+
.map((entry, index) => ({
|
|
167
|
+
title: value.length === 1 ? title : `${title} ${index + 1}`,
|
|
168
|
+
rows: detailRows(entry)
|
|
169
|
+
}))
|
|
170
|
+
.filter((section) => section.rows.length > 0);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
140
173
|
function renderObjectCard(result, primitives, title) {
|
|
141
174
|
const scalarRows = directScalarRows(result);
|
|
142
175
|
const nestedSections = directObjectSections(result);
|
|
176
|
+
const arrayObjectSections = directArrayObjectSections(result);
|
|
143
177
|
const listRows = Object.entries(result)
|
|
144
|
-
.filter(([, value]) => Array.isArray(value))
|
|
178
|
+
.filter(([, value]) => Array.isArray(value) && !isNonEmptyArrayOfObjects(value))
|
|
145
179
|
.map(([key, value]) => ({ label: humanizeKey(key), value: displayScalar(value) }));
|
|
146
180
|
return renderDetailCard({
|
|
147
181
|
theme: primitives.getTheme(),
|
|
@@ -149,7 +183,8 @@ function renderObjectCard(result, primitives, title) {
|
|
|
149
183
|
sections: [
|
|
150
184
|
{ rows: scalarRows },
|
|
151
185
|
...nestedSections,
|
|
152
|
-
{ title: "Lists", rows: listRows }
|
|
186
|
+
{ title: "Lists", rows: listRows },
|
|
187
|
+
...arrayObjectSections
|
|
153
188
|
]
|
|
154
189
|
});
|
|
155
190
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ThemePalette } from "../tokens/colors.js";
|
|
2
|
+
export interface InspectorField {
|
|
3
|
+
label: string;
|
|
4
|
+
value: string;
|
|
5
|
+
}
|
|
6
|
+
export interface InspectorSection {
|
|
7
|
+
title?: string;
|
|
8
|
+
fields: InspectorField[];
|
|
9
|
+
}
|
|
10
|
+
export interface RenderInspectorCardOptions {
|
|
11
|
+
theme: ThemePalette;
|
|
12
|
+
title: string;
|
|
13
|
+
subtitle?: string;
|
|
14
|
+
badges?: string[];
|
|
15
|
+
preview?: string;
|
|
16
|
+
previewTitle?: string;
|
|
17
|
+
sections?: InspectorSection[];
|
|
18
|
+
width?: number;
|
|
19
|
+
maxPreviewLines?: number;
|
|
20
|
+
}
|
|
21
|
+
export declare function renderInspectorCard(options: RenderInspectorCardOptions): string;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { renderDetailCard } from "./detail-card.js";
|
|
2
|
+
export function renderInspectorCard(options) {
|
|
3
|
+
const preview = truncatePreview(options.preview, options.maxPreviewLines ?? 8);
|
|
4
|
+
const sections = (options.sections ?? [])
|
|
5
|
+
.filter((section) => section.fields.length > 0)
|
|
6
|
+
.map((section) => ({
|
|
7
|
+
title: section.title,
|
|
8
|
+
rows: section.fields.map((field) => ({ label: field.label, value: field.value }))
|
|
9
|
+
}));
|
|
10
|
+
return renderDetailCard({
|
|
11
|
+
theme: options.theme,
|
|
12
|
+
title: options.title,
|
|
13
|
+
subtitle: options.subtitle,
|
|
14
|
+
badges: options.badges,
|
|
15
|
+
prose: preview === undefined
|
|
16
|
+
? undefined
|
|
17
|
+
: [{
|
|
18
|
+
title: options.previewTitle ?? "Preview",
|
|
19
|
+
value: preview
|
|
20
|
+
}],
|
|
21
|
+
sections,
|
|
22
|
+
width: options.width
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function truncatePreview(value, maxLines) {
|
|
26
|
+
if (value === undefined) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
const lines = splitLines(value).map((line) => line.trimEnd());
|
|
30
|
+
const firstContent = lines.findIndex((line) => line.trim().length > 0);
|
|
31
|
+
const contentLines = firstContent === -1 ? [] : lines.slice(firstContent);
|
|
32
|
+
if (contentLines.length === 0) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
if (contentLines.length <= maxLines) {
|
|
36
|
+
return contentLines.join("\n");
|
|
37
|
+
}
|
|
38
|
+
return [...contentLines.slice(0, maxLines), `... ${contentLines.length - maxLines} more line(s)`].join("\n");
|
|
39
|
+
}
|
|
40
|
+
function splitLines(content) {
|
|
41
|
+
return content.split("\n").map((line) => line.endsWith("\r") ? line.slice(0, -1) : line);
|
|
42
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ThemePalette } from "../tokens/colors.js";
|
|
2
|
+
export interface ResourceBrowserItem {
|
|
3
|
+
label: string;
|
|
4
|
+
meta?: string[];
|
|
5
|
+
preview?: string;
|
|
6
|
+
badge?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ResourceBrowserGroup {
|
|
9
|
+
title: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
emptyHint?: string;
|
|
12
|
+
items: ResourceBrowserItem[];
|
|
13
|
+
}
|
|
14
|
+
export interface RenderResourceBrowserOptions {
|
|
15
|
+
theme: ThemePalette;
|
|
16
|
+
title: string;
|
|
17
|
+
subtitle?: string;
|
|
18
|
+
groups: ResourceBrowserGroup[];
|
|
19
|
+
footer?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function renderResourceBrowser(options: RenderResourceBrowserOptions): string;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { resolveOutputFormat } from "../internal/output-format.js";
|
|
2
|
+
import { stripAnsi } from "../internal/strip-ansi.js";
|
|
3
|
+
export function renderResourceBrowser(options) {
|
|
4
|
+
switch (resolveOutputFormat()) {
|
|
5
|
+
case "markdown":
|
|
6
|
+
return renderMarkdown(options);
|
|
7
|
+
case "json":
|
|
8
|
+
return renderJson(options);
|
|
9
|
+
default:
|
|
10
|
+
return renderTerminal(options);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function renderTerminal(options) {
|
|
14
|
+
const blocks = [renderTitle(options)];
|
|
15
|
+
for (const group of options.groups) {
|
|
16
|
+
blocks.push(renderTerminalGroup(options.theme, group));
|
|
17
|
+
}
|
|
18
|
+
if (options.footer !== undefined) {
|
|
19
|
+
blocks.push(options.theme.muted(options.footer));
|
|
20
|
+
}
|
|
21
|
+
return blocks.join("\n\n");
|
|
22
|
+
}
|
|
23
|
+
function renderTitle(options) {
|
|
24
|
+
return [
|
|
25
|
+
options.theme.header(options.title),
|
|
26
|
+
options.subtitle === undefined ? undefined : options.theme.muted(options.subtitle)
|
|
27
|
+
].filter((value) => value !== undefined).join(" ");
|
|
28
|
+
}
|
|
29
|
+
function renderTerminalGroup(theme, group) {
|
|
30
|
+
const lines = [
|
|
31
|
+
`${theme.header(group.title)} ${theme.muted(String(group.items.length))}`,
|
|
32
|
+
...(group.description === undefined ? [] : [theme.muted(group.description)])
|
|
33
|
+
];
|
|
34
|
+
if (group.items.length === 0) {
|
|
35
|
+
lines.push(theme.muted(group.emptyHint ?? "No items"));
|
|
36
|
+
return lines.join("\n");
|
|
37
|
+
}
|
|
38
|
+
for (const [index, item] of group.items.entries()) {
|
|
39
|
+
lines.push(...renderTerminalItem(theme, item));
|
|
40
|
+
if (index < group.items.length - 1) {
|
|
41
|
+
lines.push("");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return lines.join("\n");
|
|
45
|
+
}
|
|
46
|
+
function renderTerminalItem(theme, item) {
|
|
47
|
+
return [
|
|
48
|
+
`${theme.accent(">")} ${theme.header(item.label)}${item.badge === undefined ? "" : ` ${theme.info(item.badge)}`}`,
|
|
49
|
+
...(item.meta === undefined || item.meta.length === 0 ? [] : [` ${theme.muted(item.meta.join(" · "))}`]),
|
|
50
|
+
...(item.preview === undefined || item.preview.length === 0 ? [] : [` ${theme.muted(item.preview)}`])
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
function renderMarkdown(options) {
|
|
54
|
+
const blocks = [`# ${stripAnsi(options.title)}`];
|
|
55
|
+
if (options.subtitle !== undefined) {
|
|
56
|
+
blocks.push(stripAnsi(options.subtitle));
|
|
57
|
+
}
|
|
58
|
+
for (const group of options.groups) {
|
|
59
|
+
const lines = [`## ${stripAnsi(group.title)} (${group.items.length})`];
|
|
60
|
+
if (group.description !== undefined) {
|
|
61
|
+
lines.push(stripAnsi(group.description));
|
|
62
|
+
}
|
|
63
|
+
if (group.items.length === 0) {
|
|
64
|
+
lines.push(stripAnsi(group.emptyHint ?? "No items"));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
lines.push(...group.items.map((item) => renderMarkdownItem(item)));
|
|
68
|
+
}
|
|
69
|
+
blocks.push(lines.join("\n\n"));
|
|
70
|
+
}
|
|
71
|
+
if (options.footer !== undefined) {
|
|
72
|
+
blocks.push(stripAnsi(options.footer));
|
|
73
|
+
}
|
|
74
|
+
return blocks.join("\n\n");
|
|
75
|
+
}
|
|
76
|
+
function renderMarkdownItem(item) {
|
|
77
|
+
const meta = item.meta === undefined || item.meta.length === 0 ? "" : ` — ${item.meta.map(stripAnsi).join(" · ")}`;
|
|
78
|
+
const preview = item.preview === undefined || item.preview.length === 0 ? "" : `: ${stripAnsi(item.preview)}`;
|
|
79
|
+
return `- **${stripAnsi(item.label)}**${meta}${preview}`;
|
|
80
|
+
}
|
|
81
|
+
function renderJson(options) {
|
|
82
|
+
return JSON.stringify({
|
|
83
|
+
title: stripAnsi(options.title),
|
|
84
|
+
...(options.subtitle === undefined ? {} : { subtitle: stripAnsi(options.subtitle) }),
|
|
85
|
+
groups: options.groups.map((group) => ({
|
|
86
|
+
title: stripAnsi(group.title),
|
|
87
|
+
...(group.description === undefined ? {} : { description: stripAnsi(group.description) }),
|
|
88
|
+
...(group.emptyHint === undefined ? {} : { emptyHint: stripAnsi(group.emptyHint) }),
|
|
89
|
+
items: group.items.map((item) => ({
|
|
90
|
+
label: stripAnsi(item.label),
|
|
91
|
+
...(item.meta === undefined ? {} : { meta: item.meta.map(stripAnsi) }),
|
|
92
|
+
...(item.preview === undefined ? {} : { preview: stripAnsi(item.preview) }),
|
|
93
|
+
...(item.badge === undefined ? {} : { badge: stripAnsi(item.badge) })
|
|
94
|
+
}))
|
|
95
|
+
})),
|
|
96
|
+
...(options.footer === undefined ? {} : { footer: stripAnsi(options.footer) })
|
|
97
|
+
}, null, 2);
|
|
98
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { Detail, DetailCtx, Row } from "./state.js";
|
|
2
2
|
export { runExplorer } from "./runtime.js";
|
|
3
|
+
export { runTwoPaneExplorer, renderTwoPaneExplorer, TwoPaneExplorerRuntime } from "./two-pane.js";
|
|
3
4
|
export { createInitialState } from "./state.js";
|
|
4
5
|
export { resolveBindings } from "./keymap.js";
|
|
5
6
|
export type { Effect, ExplorerEvent } from "./events.js";
|
|
6
7
|
export type { BindingTarget, ExplorerBindingDefaults, ExplorerBuiltinCommand, ResolvedBindings } from "./keymap.js";
|
|
7
8
|
export type { Action, ActionContext, Detail, DetailCtx, DetailItem, Dirty, ExplorerConfig, ExplorerLayoutMode, ExplorerSize, ExplorerState, ReorderContext, Row, Tone } from "./state.js";
|
|
9
|
+
export type { TwoPaneAction, TwoPaneActionContext, TwoPaneDefinition, TwoPaneExplorerConfig, TwoPaneExplorerState, TwoPanePaneState, TwoPaneRow } from "./two-pane.js";
|
|
8
10
|
export declare function singleDetail<R>(fn: (row: Row, ctx: DetailCtx) => string | Promise<string>): Detail<R>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { runExplorer } from "./runtime.js";
|
|
2
|
+
export { runTwoPaneExplorer, renderTwoPaneExplorer, TwoPaneExplorerRuntime } from "./two-pane.js";
|
|
2
3
|
export { createInitialState } from "./state.js";
|
|
3
4
|
export { resolveBindings } from "./keymap.js";
|
|
4
5
|
export function singleDetail(fn) {
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { ScreenBuffer } from "../dashboard/buffer.js";
|
|
2
|
+
import { type TerminalDriver } from "../dashboard/terminal.js";
|
|
3
|
+
import type { Tone } from "./state.js";
|
|
4
|
+
export interface TwoPaneRow {
|
|
5
|
+
id: string;
|
|
6
|
+
title: string;
|
|
7
|
+
subtitle?: string;
|
|
8
|
+
badge?: {
|
|
9
|
+
text: string;
|
|
10
|
+
tone?: Tone;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export interface TwoPaneDefinition {
|
|
14
|
+
id: string;
|
|
15
|
+
title: string;
|
|
16
|
+
rows: () => Promise<TwoPaneRow[]>;
|
|
17
|
+
emptyHint?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface TwoPaneActionContext<R> {
|
|
20
|
+
activePane: TwoPanePaneState;
|
|
21
|
+
inactivePane: TwoPanePaneState;
|
|
22
|
+
row: TwoPaneRow;
|
|
23
|
+
rows: TwoPaneRow[];
|
|
24
|
+
refresh: () => Promise<void>;
|
|
25
|
+
suspendAnd: <T>(fn: () => Promise<T>) => Promise<T>;
|
|
26
|
+
toast: (message: string, tone?: Tone) => void;
|
|
27
|
+
exit: (result?: R | null) => void;
|
|
28
|
+
}
|
|
29
|
+
export interface TwoPaneAction<R> {
|
|
30
|
+
id: string;
|
|
31
|
+
label: string;
|
|
32
|
+
key: string | string[];
|
|
33
|
+
handler: (ctx: TwoPaneActionContext<R>) => void | Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
export interface TwoPaneExplorerConfig<R> {
|
|
36
|
+
title: string;
|
|
37
|
+
panes: [TwoPaneDefinition, TwoPaneDefinition];
|
|
38
|
+
actions: TwoPaneAction<R>[];
|
|
39
|
+
refresh?: () => void | Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
export interface TwoPanePaneState {
|
|
42
|
+
id: string;
|
|
43
|
+
title: string;
|
|
44
|
+
rows: TwoPaneRow[];
|
|
45
|
+
cursor: number;
|
|
46
|
+
selected: Set<string>;
|
|
47
|
+
filter: string;
|
|
48
|
+
emptyHint: string;
|
|
49
|
+
}
|
|
50
|
+
export interface TwoPaneExplorerState {
|
|
51
|
+
title: string;
|
|
52
|
+
panes: [TwoPanePaneState, TwoPanePaneState];
|
|
53
|
+
activePaneIndex: 0 | 1;
|
|
54
|
+
filterFocused: boolean;
|
|
55
|
+
toast: {
|
|
56
|
+
message: string;
|
|
57
|
+
tone: Tone;
|
|
58
|
+
} | null;
|
|
59
|
+
size: {
|
|
60
|
+
cols: number;
|
|
61
|
+
rows: number;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export declare function runTwoPaneExplorer<R = void>(config: TwoPaneExplorerConfig<R>): Promise<R | null>;
|
|
65
|
+
export declare class TwoPaneExplorerRuntime<R> {
|
|
66
|
+
private readonly config;
|
|
67
|
+
private readonly driver;
|
|
68
|
+
private state;
|
|
69
|
+
private unsubscribeKeypress;
|
|
70
|
+
private unsubscribeResize;
|
|
71
|
+
private toastTimer;
|
|
72
|
+
private stopped;
|
|
73
|
+
private rowsRequestToken;
|
|
74
|
+
private settle;
|
|
75
|
+
constructor(config: TwoPaneExplorerConfig<R>, driver: TerminalDriver);
|
|
76
|
+
run(): Promise<R | null>;
|
|
77
|
+
private startTerminal;
|
|
78
|
+
private loadRows;
|
|
79
|
+
private refresh;
|
|
80
|
+
private dispatchKey;
|
|
81
|
+
private dispatchFilterKey;
|
|
82
|
+
private moveCursor;
|
|
83
|
+
private setCursor;
|
|
84
|
+
private toggleSelection;
|
|
85
|
+
private runAction;
|
|
86
|
+
private suspendAnd;
|
|
87
|
+
private showToast;
|
|
88
|
+
private activePane;
|
|
89
|
+
private inactivePane;
|
|
90
|
+
private render;
|
|
91
|
+
private exit;
|
|
92
|
+
private fail;
|
|
93
|
+
}
|
|
94
|
+
export declare function renderTwoPaneExplorer<R>(state: TwoPaneExplorerState, actions: TwoPaneAction<R>[], screen: ScreenBuffer): void;
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
import { ScreenBuffer, cellToAnsi } from "../dashboard/buffer.js";
|
|
2
|
+
import { createTerminalDriver } from "../dashboard/terminal.js";
|
|
3
|
+
const TOAST_MS = 2500;
|
|
4
|
+
export async function runTwoPaneExplorer(config) {
|
|
5
|
+
if (process.stdout.isTTY !== true) {
|
|
6
|
+
throw new Error("two-pane explorer requires a TTY");
|
|
7
|
+
}
|
|
8
|
+
return new TwoPaneExplorerRuntime(config, createTerminalDriver()).run();
|
|
9
|
+
}
|
|
10
|
+
export class TwoPaneExplorerRuntime {
|
|
11
|
+
config;
|
|
12
|
+
driver;
|
|
13
|
+
state;
|
|
14
|
+
unsubscribeKeypress;
|
|
15
|
+
unsubscribeResize;
|
|
16
|
+
toastTimer;
|
|
17
|
+
stopped = false;
|
|
18
|
+
rowsRequestToken = 0;
|
|
19
|
+
settle;
|
|
20
|
+
constructor(config, driver) {
|
|
21
|
+
this.config = config;
|
|
22
|
+
this.driver = driver;
|
|
23
|
+
const size = normalizeSize(driver.getSize());
|
|
24
|
+
this.state = {
|
|
25
|
+
title: config.title,
|
|
26
|
+
panes: [
|
|
27
|
+
initialPaneState(config.panes[0]),
|
|
28
|
+
initialPaneState(config.panes[1])
|
|
29
|
+
],
|
|
30
|
+
activePaneIndex: 0,
|
|
31
|
+
filterFocused: false,
|
|
32
|
+
toast: null,
|
|
33
|
+
size
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
run() {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
this.settle = { resolve, reject };
|
|
39
|
+
try {
|
|
40
|
+
this.startTerminal();
|
|
41
|
+
this.render();
|
|
42
|
+
this.loadRows().catch((error) => this.fail(error));
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
this.fail(error);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
startTerminal() {
|
|
50
|
+
this.driver.enterRawMode();
|
|
51
|
+
this.driver.enterAltScreen();
|
|
52
|
+
this.driver.disableLineWrap();
|
|
53
|
+
this.driver.hideCursor();
|
|
54
|
+
this.unsubscribeKeypress = this.driver.onKeypress((key) => {
|
|
55
|
+
this.dispatchKey(key);
|
|
56
|
+
});
|
|
57
|
+
this.unsubscribeResize = this.driver.onResize(() => {
|
|
58
|
+
this.state = { ...this.state, size: normalizeSize(this.driver.getSize()) };
|
|
59
|
+
this.render();
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
async loadRows(requestToken = ++this.rowsRequestToken) {
|
|
63
|
+
const [leftRows, rightRows] = await Promise.all([
|
|
64
|
+
this.config.panes[0].rows(),
|
|
65
|
+
this.config.panes[1].rows()
|
|
66
|
+
]);
|
|
67
|
+
if (requestToken !== this.rowsRequestToken) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
this.state = {
|
|
71
|
+
...this.state,
|
|
72
|
+
panes: [
|
|
73
|
+
rowsLoaded(this.state.panes[0], leftRows),
|
|
74
|
+
rowsLoaded(this.state.panes[1], rightRows)
|
|
75
|
+
]
|
|
76
|
+
};
|
|
77
|
+
this.render();
|
|
78
|
+
}
|
|
79
|
+
async refresh() {
|
|
80
|
+
await this.config.refresh?.();
|
|
81
|
+
await this.loadRows(++this.rowsRequestToken);
|
|
82
|
+
}
|
|
83
|
+
dispatchKey(key) {
|
|
84
|
+
if (this.stopped) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (this.state.filterFocused) {
|
|
88
|
+
this.dispatchFilterKey(key);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (isQuitKey(key)) {
|
|
92
|
+
this.exit(null);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (key.name === "tab") {
|
|
96
|
+
this.state = {
|
|
97
|
+
...this.state,
|
|
98
|
+
activePaneIndex: this.state.activePaneIndex === 0 ? 1 : 0
|
|
99
|
+
};
|
|
100
|
+
this.render();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (key.name === "up" || key.name === "down") {
|
|
104
|
+
this.moveCursor(key.name === "up" ? -1 : 1);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (key.name === "home") {
|
|
108
|
+
this.setCursor(0);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (key.name === "end") {
|
|
112
|
+
this.setCursor(filteredRows(this.activePane()).length - 1);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (key.ch === " ") {
|
|
116
|
+
this.toggleSelection();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (key.ch === "/") {
|
|
120
|
+
this.state = {
|
|
121
|
+
...this.state,
|
|
122
|
+
filterFocused: true,
|
|
123
|
+
panes: updateActivePane(this.state, (pane) => ({ ...pane, filter: "", cursor: 0 }))
|
|
124
|
+
};
|
|
125
|
+
this.render();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const action = this.config.actions.find((candidate) => actionMatchesKey(candidate, key));
|
|
129
|
+
if (action !== undefined) {
|
|
130
|
+
this.runAction(action);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
dispatchFilterKey(key) {
|
|
134
|
+
if (key.name === "escape" || key.name === "return") {
|
|
135
|
+
this.state = { ...this.state, filterFocused: false };
|
|
136
|
+
this.render();
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (key.name === "backspace") {
|
|
140
|
+
this.state = {
|
|
141
|
+
...this.state,
|
|
142
|
+
panes: updateActivePane(this.state, (pane) => ({
|
|
143
|
+
...pane,
|
|
144
|
+
filter: Array.from(pane.filter).slice(0, -1).join(""),
|
|
145
|
+
cursor: 0
|
|
146
|
+
}))
|
|
147
|
+
};
|
|
148
|
+
this.render();
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (key.ch !== undefined && key.ch.length > 0 && key.ch !== "/") {
|
|
152
|
+
this.state = {
|
|
153
|
+
...this.state,
|
|
154
|
+
panes: updateActivePane(this.state, (pane) => ({
|
|
155
|
+
...pane,
|
|
156
|
+
filter: `${pane.filter}${key.ch}`,
|
|
157
|
+
cursor: 0
|
|
158
|
+
}))
|
|
159
|
+
};
|
|
160
|
+
this.render();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
moveCursor(delta) {
|
|
164
|
+
const pane = this.activePane();
|
|
165
|
+
const rows = filteredRows(pane);
|
|
166
|
+
if (rows.length === 0) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
this.setCursor(Math.max(0, Math.min(rows.length - 1, pane.cursor + delta)));
|
|
170
|
+
}
|
|
171
|
+
setCursor(cursor) {
|
|
172
|
+
this.state = {
|
|
173
|
+
...this.state,
|
|
174
|
+
panes: updateActivePane(this.state, (pane) => ({
|
|
175
|
+
...pane,
|
|
176
|
+
cursor: Math.max(0, cursor)
|
|
177
|
+
}))
|
|
178
|
+
};
|
|
179
|
+
this.render();
|
|
180
|
+
}
|
|
181
|
+
toggleSelection() {
|
|
182
|
+
const pane = this.activePane();
|
|
183
|
+
const row = currentRow(pane);
|
|
184
|
+
if (row === undefined) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const selected = new Set(pane.selected);
|
|
188
|
+
if (selected.has(row.id)) {
|
|
189
|
+
selected.delete(row.id);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
selected.add(row.id);
|
|
193
|
+
}
|
|
194
|
+
this.state = {
|
|
195
|
+
...this.state,
|
|
196
|
+
panes: updateActivePane(this.state, (candidate) => ({ ...candidate, selected }))
|
|
197
|
+
};
|
|
198
|
+
this.render();
|
|
199
|
+
}
|
|
200
|
+
runAction(action) {
|
|
201
|
+
const activePane = this.activePane();
|
|
202
|
+
const inactivePane = this.inactivePane();
|
|
203
|
+
const row = currentRow(activePane);
|
|
204
|
+
if (row === undefined) {
|
|
205
|
+
this.showToast("Select an item first", "warning");
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const selectedRows = activePane.rows.filter((candidate) => activePane.selected.has(candidate.id));
|
|
209
|
+
const context = {
|
|
210
|
+
activePane,
|
|
211
|
+
inactivePane,
|
|
212
|
+
row,
|
|
213
|
+
rows: selectedRows.length > 0 ? selectedRows : [row],
|
|
214
|
+
refresh: async () => this.refresh(),
|
|
215
|
+
suspendAnd: async (fn) => this.suspendAnd(fn),
|
|
216
|
+
toast: (message, tone) => this.showToast(message, tone),
|
|
217
|
+
exit: (result) => this.exit(result ?? null)
|
|
218
|
+
};
|
|
219
|
+
Promise.resolve()
|
|
220
|
+
.then(() => action.handler(context))
|
|
221
|
+
.catch((error) => {
|
|
222
|
+
this.showToast(error instanceof Error ? error.message : "Action failed", "error");
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
async suspendAnd(fn) {
|
|
226
|
+
this.driver.exitAltScreen();
|
|
227
|
+
this.driver.enableLineWrap();
|
|
228
|
+
this.driver.showCursor();
|
|
229
|
+
this.driver.exitRawMode();
|
|
230
|
+
try {
|
|
231
|
+
return await fn();
|
|
232
|
+
}
|
|
233
|
+
finally {
|
|
234
|
+
if (!this.stopped) {
|
|
235
|
+
this.driver.enterRawMode();
|
|
236
|
+
this.driver.enterAltScreen();
|
|
237
|
+
this.driver.disableLineWrap();
|
|
238
|
+
this.driver.hideCursor();
|
|
239
|
+
this.state = { ...this.state, size: normalizeSize(this.driver.getSize()) };
|
|
240
|
+
this.render();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
showToast(message, tone = "info") {
|
|
245
|
+
if (this.toastTimer !== undefined) {
|
|
246
|
+
clearTimeout(this.toastTimer);
|
|
247
|
+
}
|
|
248
|
+
this.state = { ...this.state, toast: { message, tone } };
|
|
249
|
+
this.render();
|
|
250
|
+
this.toastTimer = setTimeout(() => {
|
|
251
|
+
this.state = { ...this.state, toast: null };
|
|
252
|
+
this.render();
|
|
253
|
+
}, TOAST_MS);
|
|
254
|
+
}
|
|
255
|
+
activePane() {
|
|
256
|
+
return this.state.panes[this.state.activePaneIndex];
|
|
257
|
+
}
|
|
258
|
+
inactivePane() {
|
|
259
|
+
return this.state.panes[this.state.activePaneIndex === 0 ? 1 : 0];
|
|
260
|
+
}
|
|
261
|
+
render() {
|
|
262
|
+
if (this.stopped) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const size = normalizeSize(this.driver.getSize());
|
|
266
|
+
if (size.cols !== this.state.size.cols || size.rows !== this.state.size.rows) {
|
|
267
|
+
this.state = { ...this.state, size };
|
|
268
|
+
}
|
|
269
|
+
const next = new ScreenBuffer(this.state.size.cols, this.state.size.rows);
|
|
270
|
+
renderTwoPaneExplorer(this.state, this.config.actions, next);
|
|
271
|
+
this.driver.write(screenToAnsi(next));
|
|
272
|
+
}
|
|
273
|
+
exit(result) {
|
|
274
|
+
if (this.stopped) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
this.stopped = true;
|
|
278
|
+
this.unsubscribeKeypress?.();
|
|
279
|
+
this.unsubscribeResize?.();
|
|
280
|
+
if (this.toastTimer !== undefined) {
|
|
281
|
+
clearTimeout(this.toastTimer);
|
|
282
|
+
}
|
|
283
|
+
this.driver.destroy();
|
|
284
|
+
this.settle?.resolve(result);
|
|
285
|
+
}
|
|
286
|
+
fail(error) {
|
|
287
|
+
if (!this.stopped) {
|
|
288
|
+
this.stopped = true;
|
|
289
|
+
this.driver.destroy();
|
|
290
|
+
}
|
|
291
|
+
this.settle?.reject(error);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
export function renderTwoPaneExplorer(state, actions, screen) {
|
|
295
|
+
screen.clear();
|
|
296
|
+
if (state.size.cols < 60 || state.size.rows < 8) {
|
|
297
|
+
screen.put(0, 0, fit(` ${state.title} `, state.size.cols), { bold: true });
|
|
298
|
+
screen.put(0, 2, fit("Terminal is too small for two-pane view.", state.size.cols));
|
|
299
|
+
screen.put(0, state.size.rows - 1, fit("q quit", state.size.cols), { dim: true });
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const gap = 1;
|
|
303
|
+
const paneWidth = Math.floor((state.size.cols - gap) / 2);
|
|
304
|
+
const rightWidth = state.size.cols - paneWidth - gap;
|
|
305
|
+
const paneHeight = Math.max(5, state.size.rows - 2);
|
|
306
|
+
renderPane(screen, state.panes[0], {
|
|
307
|
+
x: 0,
|
|
308
|
+
y: 0,
|
|
309
|
+
width: paneWidth,
|
|
310
|
+
height: paneHeight,
|
|
311
|
+
active: state.activePaneIndex === 0
|
|
312
|
+
});
|
|
313
|
+
renderPane(screen, state.panes[1], {
|
|
314
|
+
x: paneWidth + gap,
|
|
315
|
+
y: 0,
|
|
316
|
+
width: rightWidth,
|
|
317
|
+
height: paneHeight,
|
|
318
|
+
active: state.activePaneIndex === 1
|
|
319
|
+
});
|
|
320
|
+
renderFooter(screen, state, actions, state.size.rows - 2);
|
|
321
|
+
if (state.toast !== null) {
|
|
322
|
+
screen.put(0, state.size.rows - 1, fit(` ${state.toast.message} `, state.size.cols), toneStyle(state.toast.tone));
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function renderPane(screen, pane, layout) {
|
|
326
|
+
const borderStyle = layout.active ? { bold: true, fg: "cyan" } : { dim: true };
|
|
327
|
+
const title = `${layout.active ? ">" : " "} ${pane.title}`;
|
|
328
|
+
screen.put(layout.x, layout.y, `+${fit(title, layout.width - 2, "-")}+`, borderStyle);
|
|
329
|
+
for (let y = 1; y < layout.height - 1; y += 1) {
|
|
330
|
+
screen.put(layout.x, layout.y + y, "|", borderStyle);
|
|
331
|
+
screen.put(layout.x + layout.width - 1, layout.y + y, "|", borderStyle);
|
|
332
|
+
}
|
|
333
|
+
const filterLabel = pane.filter.length > 0 ? ` /${pane.filter}` : "";
|
|
334
|
+
screen.put(layout.x, layout.y + layout.height - 1, `+${fit(`${pane.selected.size} selected${filterLabel}`, layout.width - 2, "-")}+`, borderStyle);
|
|
335
|
+
const visibleRows = filteredRows(pane);
|
|
336
|
+
const bodyHeight = layout.height - 2;
|
|
337
|
+
if (visibleRows.length === 0) {
|
|
338
|
+
screen.put(layout.x + 2, layout.y + 2, fit(pane.emptyHint, Math.max(0, layout.width - 4)), { dim: true });
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
const start = Math.max(0, Math.min(pane.cursor - Math.floor(bodyHeight / 2), visibleRows.length - bodyHeight));
|
|
342
|
+
for (let index = 0; index < bodyHeight; index += 1) {
|
|
343
|
+
const row = visibleRows[start + index];
|
|
344
|
+
if (row === undefined) {
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
const selected = pane.selected.has(row.id);
|
|
348
|
+
const cursor = start + index === pane.cursor;
|
|
349
|
+
const marker = selected ? "*" : " ";
|
|
350
|
+
const pointer = cursor && layout.active ? ">" : " ";
|
|
351
|
+
const badge = row.badge ? ` [${row.badge.text}]` : "";
|
|
352
|
+
const subtitle = row.subtitle ? ` ${row.subtitle}` : "";
|
|
353
|
+
const style = cursor && layout.active ? { inverse: true } : {};
|
|
354
|
+
screen.put(layout.x + 1, layout.y + 1 + index, fit(`${pointer}${marker} ${row.title}${badge}${subtitle}`, layout.width - 2), style);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function renderFooter(screen, state, actions, y) {
|
|
358
|
+
const actionHelp = actions.map((action) => `${firstKey(action.key)} ${action.label}`).join(" ");
|
|
359
|
+
const filter = state.filterFocused ? "search: typing, enter done" : "/ search";
|
|
360
|
+
const text = `tab pane ${filter} space select ${actionHelp} q quit`;
|
|
361
|
+
screen.put(0, y, fit(text, state.size.cols), { dim: true });
|
|
362
|
+
}
|
|
363
|
+
function initialPaneState(definition) {
|
|
364
|
+
return {
|
|
365
|
+
id: definition.id,
|
|
366
|
+
title: definition.title,
|
|
367
|
+
rows: [],
|
|
368
|
+
cursor: 0,
|
|
369
|
+
selected: new Set(),
|
|
370
|
+
filter: "",
|
|
371
|
+
emptyHint: definition.emptyHint ?? "No items"
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
function rowsLoaded(pane, rows) {
|
|
375
|
+
const ids = new Set(rows.map((row) => row.id));
|
|
376
|
+
return {
|
|
377
|
+
...pane,
|
|
378
|
+
rows,
|
|
379
|
+
selected: new Set([...pane.selected].filter((id) => ids.has(id))),
|
|
380
|
+
cursor: Math.max(0, Math.min(pane.cursor, Math.max(0, filteredRows({ ...pane, rows }).length - 1)))
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
function filteredRows(pane) {
|
|
384
|
+
const query = pane.filter.trim().toLowerCase();
|
|
385
|
+
if (query.length === 0) {
|
|
386
|
+
return pane.rows;
|
|
387
|
+
}
|
|
388
|
+
return pane.rows.filter((row) => {
|
|
389
|
+
const haystack = `${row.title} ${row.subtitle ?? ""} ${row.badge?.text ?? ""}`.toLowerCase();
|
|
390
|
+
return haystack.includes(query);
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
function currentRow(pane) {
|
|
394
|
+
return filteredRows(pane)[pane.cursor];
|
|
395
|
+
}
|
|
396
|
+
function updateActivePane(state, update) {
|
|
397
|
+
return state.activePaneIndex === 0
|
|
398
|
+
? [update(state.panes[0]), state.panes[1]]
|
|
399
|
+
: [state.panes[0], update(state.panes[1])];
|
|
400
|
+
}
|
|
401
|
+
function actionMatchesKey(action, key) {
|
|
402
|
+
const keys = Array.isArray(action.key) ? action.key : [action.key];
|
|
403
|
+
return keys.some((candidate) => key.ch === candidate || key.name === candidate);
|
|
404
|
+
}
|
|
405
|
+
function firstKey(key) {
|
|
406
|
+
return Array.isArray(key) ? key[0] ?? "" : key;
|
|
407
|
+
}
|
|
408
|
+
function isQuitKey(key) {
|
|
409
|
+
return key.ch === "q" || (key.name === "c" && key.ctrl);
|
|
410
|
+
}
|
|
411
|
+
function normalizeSize(size) {
|
|
412
|
+
return {
|
|
413
|
+
cols: Math.max(0, Math.floor(size.cols)),
|
|
414
|
+
rows: Math.max(0, Math.floor(size.rows))
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
function fit(text, width, pad = " ") {
|
|
418
|
+
if (width <= 0) {
|
|
419
|
+
return "";
|
|
420
|
+
}
|
|
421
|
+
const normalized = text.length > width ? text.slice(0, width) : text;
|
|
422
|
+
return normalized.padEnd(width, pad);
|
|
423
|
+
}
|
|
424
|
+
function toneStyle(tone) {
|
|
425
|
+
if (tone === "success") {
|
|
426
|
+
return { fg: "green", bold: true };
|
|
427
|
+
}
|
|
428
|
+
if (tone === "warning") {
|
|
429
|
+
return { fg: "yellow", bold: true };
|
|
430
|
+
}
|
|
431
|
+
if (tone === "error") {
|
|
432
|
+
return { fg: "red", bold: true };
|
|
433
|
+
}
|
|
434
|
+
return tone === "muted" ? { dim: true } : { fg: "cyan", bold: true };
|
|
435
|
+
}
|
|
436
|
+
function screenToAnsi(screen) {
|
|
437
|
+
let output = "";
|
|
438
|
+
for (let y = 0; y < screen.height; y += 1) {
|
|
439
|
+
output += `\u001b[${y + 1};1H`;
|
|
440
|
+
for (let x = 0; x < screen.width; x += 1) {
|
|
441
|
+
const cell = screen.get(x, y);
|
|
442
|
+
output += cellToAnsi(cell);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return output;
|
|
446
|
+
}
|
|
@@ -23,6 +23,10 @@ export { renderCatalog } from "./components/catalog.js";
|
|
|
23
23
|
export type { CatalogGroup, CatalogItem, CatalogMetric, CatalogTone, RenderCatalogOptions } from "./components/catalog.js";
|
|
24
24
|
export { renderDetailCard } from "./components/detail-card.js";
|
|
25
25
|
export type { DetailCardRow, DetailCardSection, RenderDetailCardOptions } from "./components/detail-card.js";
|
|
26
|
+
export { renderInspectorCard } from "./components/inspector-card.js";
|
|
27
|
+
export type { InspectorField, InspectorSection, RenderInspectorCardOptions } from "./components/inspector-card.js";
|
|
28
|
+
export { renderResourceBrowser } from "./components/resource-browser.js";
|
|
29
|
+
export type { RenderResourceBrowserOptions, ResourceBrowserGroup, ResourceBrowserItem } from "./components/resource-browser.js";
|
|
26
30
|
export { getTemplatePartialNames, renderTemplate, resolveTemplatePartials } from "./components/template.js";
|
|
27
31
|
export type { RenderTemplateOptions, TemplateEscape } from "./components/template.js";
|
|
28
32
|
export { openExternal } from "./components/browser.js";
|
|
@@ -31,8 +35,8 @@ export * as dashboard from "./dashboard/index.js";
|
|
|
31
35
|
export { createDashboard, shouldUseInteractiveDashboard } from "./dashboard/index.js";
|
|
32
36
|
export type { Dashboard, DashboardOptions } from "./dashboard/index.js";
|
|
33
37
|
export * as explorer from "./explorer/index.js";
|
|
34
|
-
export { runExplorer, singleDetail } from "./explorer/index.js";
|
|
35
|
-
export type { Row, DetailItem, Detail, DetailCtx, Action, ActionContext, ExplorerConfig, ReorderContext, Tone } from "./explorer/index.js";
|
|
38
|
+
export { runExplorer, runTwoPaneExplorer, singleDetail } from "./explorer/index.js";
|
|
39
|
+
export type { Row, DetailItem, Detail, DetailCtx, Action, ActionContext, TwoPaneAction, TwoPaneActionContext, TwoPaneDefinition, TwoPaneExplorerConfig, TwoPanePaneState, TwoPaneRow, ExplorerConfig, ReorderContext, Tone } from "./explorer/index.js";
|
|
36
40
|
export * as prompts from "./prompts/index.js";
|
|
37
41
|
export { intro, introPlain, outro, note, select, multiselect, text as promptText, confirm, confirmOrCancel, password, spinner, withSpinner, isCancel, cancel, log, PromptCancelledError } from "./prompts/index.js";
|
|
38
42
|
export type { SelectOptions, MultiselectOptions, TextOptions, ConfirmOptions, PasswordOptions, SpinnerOptions, WithSpinnerOptions } from "./prompts/index.js";
|
|
@@ -17,6 +17,8 @@ export { formatCommandNotFoundPanel } from "./components/command-errors.js";
|
|
|
17
17
|
export { renderTable } from "./components/table.js";
|
|
18
18
|
export { renderCatalog } from "./components/catalog.js";
|
|
19
19
|
export { renderDetailCard } from "./components/detail-card.js";
|
|
20
|
+
export { renderInspectorCard } from "./components/inspector-card.js";
|
|
21
|
+
export { renderResourceBrowser } from "./components/resource-browser.js";
|
|
20
22
|
export { getTemplatePartialNames, renderTemplate, resolveTemplatePartials } from "./components/template.js";
|
|
21
23
|
export { openExternal } from "./components/browser.js";
|
|
22
24
|
// ACP rendering
|
|
@@ -26,7 +28,7 @@ export * as dashboard from "./dashboard/index.js";
|
|
|
26
28
|
export { createDashboard, shouldUseInteractiveDashboard } from "./dashboard/index.js";
|
|
27
29
|
// Explorer
|
|
28
30
|
export * as explorer from "./explorer/index.js";
|
|
29
|
-
export { runExplorer, singleDetail } from "./explorer/index.js";
|
|
31
|
+
export { runExplorer, runTwoPaneExplorer, singleDetail } from "./explorer/index.js";
|
|
30
32
|
// Prompts
|
|
31
33
|
export * as prompts from "./prompts/index.js";
|
|
32
34
|
export { intro, introPlain, outro, note, select, multiselect, text as promptText, confirm, confirmOrCancel, password, spinner, withSpinner, isCancel, cancel, log, PromptCancelledError } from "./prompts/index.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolcraft",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.67",
|
|
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.67",
|
|
51
51
|
"commander": "^14.0.3",
|
|
52
52
|
"fast-string-width": "^3.0.2",
|
|
53
53
|
"fast-wrap-ansi": "^0.2.0",
|