toolcraft-openapi 0.0.17 → 0.0.19
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/bin/generate.js +7 -0
- package/dist/define-client.js +2 -2
- package/dist/generate.js +2 -2
- package/dist/http.d.ts +21 -2
- package/dist/http.js +147 -22
- package/dist/index.d.ts +1 -1
- package/dist/lock.d.ts +1 -1
- package/dist/lock.js +109 -5
- package/dist/mock/fetch.js +1 -1
- package/dist/network-error.d.ts +2 -0
- package/dist/network-error.js +83 -0
- package/dist/spec-source.js +103 -3
- package/node_modules/@poe-code/design-system/dist/acp/components.js +15 -13
- package/node_modules/@poe-code/design-system/dist/components/color.d.ts +31 -0
- package/node_modules/@poe-code/design-system/dist/components/color.js +101 -0
- package/node_modules/@poe-code/design-system/dist/components/help-formatter-plain.d.ts +1 -0
- package/node_modules/@poe-code/design-system/dist/components/help-formatter-plain.js +1 -1
- package/node_modules/@poe-code/design-system/dist/components/index.d.ts +4 -0
- package/node_modules/@poe-code/design-system/dist/components/index.js +2 -0
- package/node_modules/@poe-code/design-system/dist/components/logger.js +2 -2
- package/node_modules/@poe-code/design-system/dist/components/symbols.js +3 -3
- package/node_modules/@poe-code/design-system/dist/components/table.js +191 -40
- package/node_modules/@poe-code/design-system/dist/components/template.d.ts +6 -0
- package/node_modules/@poe-code/design-system/dist/components/template.js +271 -0
- package/node_modules/@poe-code/design-system/dist/components/text.d.ts +1 -0
- package/node_modules/@poe-code/design-system/dist/components/text.js +11 -3
- package/node_modules/@poe-code/design-system/dist/dashboard/buffer.js +20 -13
- package/node_modules/@poe-code/design-system/dist/dashboard/keymap.d.ts +5 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/keymap.js +146 -12
- package/node_modules/@poe-code/design-system/dist/dashboard/terminal.js +31 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/types.d.ts +1 -0
- package/node_modules/@poe-code/design-system/dist/explorer/actions.d.ts +16 -0
- package/node_modules/@poe-code/design-system/dist/explorer/actions.js +39 -0
- package/node_modules/@poe-code/design-system/dist/explorer/demo.d.ts +13 -0
- package/node_modules/@poe-code/design-system/dist/explorer/demo.js +297 -0
- package/node_modules/@poe-code/design-system/dist/explorer/events.d.ts +61 -0
- package/node_modules/@poe-code/design-system/dist/explorer/events.js +1 -0
- package/node_modules/@poe-code/design-system/dist/explorer/filter.d.ts +10 -0
- package/node_modules/@poe-code/design-system/dist/explorer/filter.js +95 -0
- package/node_modules/@poe-code/design-system/dist/explorer/index.d.ts +8 -0
- package/node_modules/@poe-code/design-system/dist/explorer/index.js +8 -0
- package/node_modules/@poe-code/design-system/dist/explorer/jobs.d.ts +7 -0
- package/node_modules/@poe-code/design-system/dist/explorer/jobs.js +59 -0
- package/node_modules/@poe-code/design-system/dist/explorer/keymap.d.ts +21 -0
- package/node_modules/@poe-code/design-system/dist/explorer/keymap.js +363 -0
- package/node_modules/@poe-code/design-system/dist/explorer/layout.d.ts +20 -0
- package/node_modules/@poe-code/design-system/dist/explorer/layout.js +73 -0
- package/node_modules/@poe-code/design-system/dist/explorer/reducer.d.ts +9 -0
- package/node_modules/@poe-code/design-system/dist/explorer/reducer.js +704 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/detail.d.ts +4 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/detail.js +96 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/footer.d.ts +4 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/footer.js +49 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/header.d.ts +4 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/header.js +56 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/index.d.ts +8 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/index.js +61 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/list.d.ts +4 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/list.js +106 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/modal.d.ts +3 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/modal.js +91 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/test-fixtures.d.ts +8 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/test-fixtures.js +156 -0
- package/node_modules/@poe-code/design-system/dist/explorer/runtime.d.ts +2 -0
- package/node_modules/@poe-code/design-system/dist/explorer/runtime.js +282 -0
- package/node_modules/@poe-code/design-system/dist/explorer/runtime.test-helpers.d.ts +50 -0
- package/node_modules/@poe-code/design-system/dist/explorer/runtime.test-helpers.js +101 -0
- package/node_modules/@poe-code/design-system/dist/explorer/state.d.ts +130 -0
- package/node_modules/@poe-code/design-system/dist/explorer/state.js +87 -0
- package/node_modules/@poe-code/design-system/dist/explorer/theme.d.ts +27 -0
- package/node_modules/@poe-code/design-system/dist/explorer/theme.js +97 -0
- package/node_modules/@poe-code/design-system/dist/index.d.ts +7 -0
- package/node_modules/@poe-code/design-system/dist/index.js +5 -0
- package/node_modules/@poe-code/design-system/dist/internal/color-support.d.ts +9 -0
- package/node_modules/@poe-code/design-system/dist/internal/color-support.js +12 -0
- package/node_modules/@poe-code/design-system/dist/prompts/index.js +2 -2
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/cancel.js +2 -2
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/intro.js +2 -2
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/log.js +4 -4
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/note.js +5 -5
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/outro.js +2 -2
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/spinner.js +3 -3
- package/node_modules/@poe-code/design-system/dist/static/menu.js +5 -5
- package/node_modules/@poe-code/design-system/dist/static/spinner.js +8 -8
- package/node_modules/@poe-code/design-system/dist/tokens/colors.js +29 -29
- package/node_modules/@poe-code/design-system/dist/tokens/typography.js +6 -6
- package/node_modules/@poe-code/design-system/package.json +6 -3
- package/package.json +2 -4
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export function resolveAction(state, keyEvent) {
|
|
2
|
+
const target = state.bindings.resolve(keyEvent);
|
|
3
|
+
if (target?.type !== "action") {
|
|
4
|
+
return null;
|
|
5
|
+
}
|
|
6
|
+
const actionState = state.actionState.get(target.id);
|
|
7
|
+
if (actionState?.available !== true ||
|
|
8
|
+
actionState.running === true ||
|
|
9
|
+
actionState.action === undefined) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
return actionState.action;
|
|
13
|
+
}
|
|
14
|
+
export function buildActionContext(state, _action, source, runtimeHandles, rowsOverride) {
|
|
15
|
+
const row = rowsOverride?.[0] ?? currentRow(state) ?? { id: "", title: "" };
|
|
16
|
+
return {
|
|
17
|
+
row,
|
|
18
|
+
rows: rowsOverride ?? selectedRows(state, row),
|
|
19
|
+
item: source === "detail" ? currentDetailItem(state) : undefined,
|
|
20
|
+
filter: state.filter,
|
|
21
|
+
refresh: runtimeHandles.refresh,
|
|
22
|
+
suspendAnd: runtimeHandles.suspendAnd,
|
|
23
|
+
toast: runtimeHandles.toast,
|
|
24
|
+
confirm: runtimeHandles.confirm,
|
|
25
|
+
exit: runtimeHandles.exit
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function currentRow(state) {
|
|
29
|
+
return state.rows[state.filtered[state.cursor] ?? -1];
|
|
30
|
+
}
|
|
31
|
+
function currentDetailItem(state) {
|
|
32
|
+
return state.detail.items?.[state.detail.cursor];
|
|
33
|
+
}
|
|
34
|
+
function selectedRows(state, fallback) {
|
|
35
|
+
if (state.selected.size === 0) {
|
|
36
|
+
return fallback.id === "" ? [] : [fallback];
|
|
37
|
+
}
|
|
38
|
+
return state.rows.filter((row) => state.selected.has(row.id));
|
|
39
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ExplorerConfig } from "./state.js";
|
|
2
|
+
type ExplorerDemoMode = "single-detail-mode" | "list-detail-mode";
|
|
3
|
+
export interface ExplorerDemoOptions {
|
|
4
|
+
mode: ExplorerDemoMode;
|
|
5
|
+
slowDetail: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface BuildExplorerDemoConfigOptions extends ExplorerDemoOptions {
|
|
8
|
+
onReorder?: (orderedIds: string[]) => void | Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
export declare function parseExplorerDemoOptions(argv?: string[], env?: NodeJS.ProcessEnv): ExplorerDemoOptions;
|
|
11
|
+
export declare function buildExplorerDemoConfig(options: BuildExplorerDemoConfigOptions): ExplorerConfig<void>;
|
|
12
|
+
export declare function main(): Promise<void>;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { runExplorer } from "./index.js";
|
|
5
|
+
const detailDelayMs = 500;
|
|
6
|
+
const truthyEnvValues = new Set(["1", "true", "yes", "on"]);
|
|
7
|
+
const singleDetailRows = [
|
|
8
|
+
{
|
|
9
|
+
id: "configure-commands",
|
|
10
|
+
title: "Configure commands",
|
|
11
|
+
subtitle: "Provider config mutation flow",
|
|
12
|
+
badge: { text: "ready", tone: "success" },
|
|
13
|
+
group: "Planning"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: "provider-boilerplate",
|
|
17
|
+
title: "Provider boilerplate audit",
|
|
18
|
+
subtitle: "Keep providers declarative and minimal",
|
|
19
|
+
badge: { text: "review", tone: "warning" },
|
|
20
|
+
group: "Planning"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: "markdown-reader",
|
|
24
|
+
title: "Markdown reader",
|
|
25
|
+
subtitle: "Terminal-rendered plan preview",
|
|
26
|
+
badge: { text: "done", tone: "muted" },
|
|
27
|
+
group: "Docs"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "design-system-prompts",
|
|
31
|
+
title: "Design system prompts",
|
|
32
|
+
subtitle: "Prompt primitives owned by the package",
|
|
33
|
+
badge: { text: "active", tone: "info" },
|
|
34
|
+
group: "UI"
|
|
35
|
+
}
|
|
36
|
+
];
|
|
37
|
+
const singleDetailMarkdown = {
|
|
38
|
+
"configure-commands": [
|
|
39
|
+
"# Configure commands",
|
|
40
|
+
"",
|
|
41
|
+
"Provider configuration should be derived from declarative provider config.",
|
|
42
|
+
"",
|
|
43
|
+
"- Parse existing files with structured parsers.",
|
|
44
|
+
"- Deep merge edits instead of replacing user-owned configuration.",
|
|
45
|
+
"- Keep CLI and SDK arguments in parity."
|
|
46
|
+
].join("\n"),
|
|
47
|
+
"provider-boilerplate": [
|
|
48
|
+
"# Provider boilerplate audit",
|
|
49
|
+
"",
|
|
50
|
+
"Adding a provider should mean adding one provider file. Everything else should come from the provider config.",
|
|
51
|
+
"",
|
|
52
|
+
"Avoid provider-specific branches in shared code paths."
|
|
53
|
+
].join("\n"),
|
|
54
|
+
"markdown-reader": [
|
|
55
|
+
"# Markdown reader",
|
|
56
|
+
"",
|
|
57
|
+
"Render markdown plans with stable wrapping and predictable terminal styling.",
|
|
58
|
+
"",
|
|
59
|
+
"The reader is a display surface, not a planning store."
|
|
60
|
+
].join("\n"),
|
|
61
|
+
"design-system-prompts": [
|
|
62
|
+
"# Design system prompts",
|
|
63
|
+
"",
|
|
64
|
+
"Prompt primitives belong in the design-system package so CLI surfaces share one style.",
|
|
65
|
+
"",
|
|
66
|
+
"Direct use of unrelated prompt libraries should stay out of consumers."
|
|
67
|
+
].join("\n")
|
|
68
|
+
};
|
|
69
|
+
const reviewRows = [
|
|
70
|
+
{
|
|
71
|
+
id: "pr-1842",
|
|
72
|
+
title: "PR #1842 provider config parser",
|
|
73
|
+
subtitle: "3 unresolved comments by reviewbot",
|
|
74
|
+
badge: { text: "changes", tone: "warning" },
|
|
75
|
+
group: "Review queue"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: "pr-1847",
|
|
79
|
+
title: "PR #1847 explorer TUI library",
|
|
80
|
+
subtitle: "2 comments, one destructive flow question",
|
|
81
|
+
badge: { text: "ready", tone: "success" },
|
|
82
|
+
group: "Review queue"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: "pr-1851",
|
|
86
|
+
title: "PR #1851 markdown QA checklist",
|
|
87
|
+
subtitle: "1 docs-only note",
|
|
88
|
+
badge: { text: "docs", tone: "info" },
|
|
89
|
+
group: "Review queue"
|
|
90
|
+
}
|
|
91
|
+
];
|
|
92
|
+
const reviewComments = {
|
|
93
|
+
"pr-1842": [
|
|
94
|
+
{
|
|
95
|
+
id: "pr-1842-comment-1",
|
|
96
|
+
title: "Review: provider registry",
|
|
97
|
+
subtitle: "packages/providers/src/registry.ts:42",
|
|
98
|
+
body: "The provider data is already present in config. Derive the registry entry instead of repeating provider names here.",
|
|
99
|
+
tone: "warning"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: "pr-1842-comment-2",
|
|
103
|
+
title: "Review: config merge",
|
|
104
|
+
subtitle: "packages/config-mutations/src/apply.ts:88",
|
|
105
|
+
body: "This should deep merge the parsed structure so user-owned fields remain intact.",
|
|
106
|
+
tone: "error"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: "pr-1842-comment-3",
|
|
110
|
+
title: "Review: parser coverage",
|
|
111
|
+
subtitle: "packages/config-mutations/src/apply.test.ts:131",
|
|
112
|
+
body: "Add a fixture that proves comments and unknown keys survive the update path.",
|
|
113
|
+
tone: "info"
|
|
114
|
+
}
|
|
115
|
+
],
|
|
116
|
+
"pr-1847": [
|
|
117
|
+
{
|
|
118
|
+
id: "pr-1847-comment-1",
|
|
119
|
+
title: "Review: detail loading",
|
|
120
|
+
subtitle: "packages/design-system/src/explorer/jobs.ts:19",
|
|
121
|
+
body: "The 150 ms loading threshold is the right behavior. This demo should make that visible with --slow-detail.",
|
|
122
|
+
tone: "success"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: "pr-1847-comment-2",
|
|
126
|
+
title: "Review: destructive confirm",
|
|
127
|
+
subtitle: "packages/design-system/src/explorer/reducer.ts:435",
|
|
128
|
+
body: "Confirm modal behavior should be reachable from manual QA with a simple keybinding.",
|
|
129
|
+
tone: "warning"
|
|
130
|
+
}
|
|
131
|
+
],
|
|
132
|
+
"pr-1851": [
|
|
133
|
+
{
|
|
134
|
+
id: "pr-1851-comment-1",
|
|
135
|
+
title: "Review: QA format",
|
|
136
|
+
subtitle: "docs/qa/explorer-tui-library.md",
|
|
137
|
+
body: "Keep this as a markdown checklist. Do not convert manual QA into a script.",
|
|
138
|
+
tone: "info"
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
};
|
|
142
|
+
export function parseExplorerDemoOptions(argv = process.argv.slice(2), env = process.env) {
|
|
143
|
+
let mode = parseMode(env.EXPLORER_DEMO_MODE) ?? "single-detail-mode";
|
|
144
|
+
let slowDetail = isTruthy(env.EXPLORER_DEMO_SLOW_DETAIL);
|
|
145
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
146
|
+
const arg = argv[index];
|
|
147
|
+
if (arg === "--slow-detail") {
|
|
148
|
+
slowDetail = true;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (arg === "--single-detail-mode") {
|
|
152
|
+
mode = "single-detail-mode";
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (arg === "--list-detail-mode") {
|
|
156
|
+
mode = "list-detail-mode";
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (arg === "--mode") {
|
|
160
|
+
const next = argv[index + 1];
|
|
161
|
+
const parsed = parseMode(next);
|
|
162
|
+
if (parsed === undefined) {
|
|
163
|
+
throw new Error(`Unsupported explorer demo mode: ${next ?? ""}`);
|
|
164
|
+
}
|
|
165
|
+
mode = parsed;
|
|
166
|
+
index += 1;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (arg.startsWith("--mode=")) {
|
|
170
|
+
const parsed = parseMode(arg.slice("--mode=".length));
|
|
171
|
+
if (parsed === undefined) {
|
|
172
|
+
throw new Error(`Unsupported explorer demo mode: ${arg.slice("--mode=".length)}`);
|
|
173
|
+
}
|
|
174
|
+
mode = parsed;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return { mode, slowDetail };
|
|
178
|
+
}
|
|
179
|
+
export function buildExplorerDemoConfig(options) {
|
|
180
|
+
const rows = options.mode === "single-detail-mode" ? singleDetailRows : reviewRows;
|
|
181
|
+
const detail = options.mode === "single-detail-mode"
|
|
182
|
+
? buildSingleDetail(options.slowDetail)
|
|
183
|
+
: buildReviewDetail(options.slowDetail);
|
|
184
|
+
return {
|
|
185
|
+
title: `Explorer Demo - ${options.mode}`,
|
|
186
|
+
rows: async () => rows,
|
|
187
|
+
detail,
|
|
188
|
+
actions: demoActions(),
|
|
189
|
+
reorder: {
|
|
190
|
+
onReorder: options.onReorder ?? (() => undefined)
|
|
191
|
+
},
|
|
192
|
+
multiSelect: true,
|
|
193
|
+
emptyHint: "No rows match the current filter"
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
export async function main() {
|
|
197
|
+
const options = parseExplorerDemoOptions();
|
|
198
|
+
await runExplorer(buildExplorerDemoConfig(options));
|
|
199
|
+
}
|
|
200
|
+
function buildSingleDetail(slowDetail) {
|
|
201
|
+
return {
|
|
202
|
+
items: async (row, ctx) => {
|
|
203
|
+
await delayDetailIfNeeded(slowDetail, ctx);
|
|
204
|
+
const markdown = singleDetailMarkdown[row.id] ?? `# ${row.title}\n\nNo demo detail is available.`;
|
|
205
|
+
return [{ id: row.id, render: () => markdown }];
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function buildReviewDetail(slowDetail) {
|
|
210
|
+
return {
|
|
211
|
+
items: async (row, ctx) => {
|
|
212
|
+
await delayDetailIfNeeded(slowDetail, ctx);
|
|
213
|
+
const comments = reviewComments[row.id] ?? [];
|
|
214
|
+
return comments.map((comment) => ({
|
|
215
|
+
id: comment.id,
|
|
216
|
+
title: comment.title,
|
|
217
|
+
subtitle: comment.subtitle,
|
|
218
|
+
badge: { text: "comment", tone: comment.tone },
|
|
219
|
+
render: () => comment.body
|
|
220
|
+
}));
|
|
221
|
+
},
|
|
222
|
+
actions: [
|
|
223
|
+
{
|
|
224
|
+
id: "resolve-comment",
|
|
225
|
+
label: "Resolve comment",
|
|
226
|
+
key: "x",
|
|
227
|
+
showInFooter: true,
|
|
228
|
+
handler: (ctx) => {
|
|
229
|
+
ctx.toast(`Resolved ${ctx.item?.title ?? ctx.row.title}`, "success");
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
]
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function demoActions() {
|
|
236
|
+
return [
|
|
237
|
+
{
|
|
238
|
+
id: "open",
|
|
239
|
+
label: "Open",
|
|
240
|
+
primary: true,
|
|
241
|
+
showInFooter: true,
|
|
242
|
+
handler: (ctx) => {
|
|
243
|
+
ctx.toast(`Opened ${ctx.row.title}`, "info");
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
id: "refresh",
|
|
248
|
+
label: "Refresh",
|
|
249
|
+
key: "r",
|
|
250
|
+
showInFooter: true,
|
|
251
|
+
handler: async (ctx) => {
|
|
252
|
+
await ctx.refresh();
|
|
253
|
+
ctx.toast("Rows refreshed", "success");
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
id: "archive",
|
|
258
|
+
label: () => "Archive selected",
|
|
259
|
+
key: "a",
|
|
260
|
+
destructive: true,
|
|
261
|
+
showInFooter: true,
|
|
262
|
+
handler: (ctx) => {
|
|
263
|
+
ctx.toast(`Archived ${ctx.rows.length} row${ctx.rows.length === 1 ? "" : "s"}`, "warning");
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
];
|
|
267
|
+
}
|
|
268
|
+
async function delayDetailIfNeeded(slowDetail, ctx) {
|
|
269
|
+
if (!slowDetail) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
await new Promise((resolve) => {
|
|
273
|
+
const timeout = setTimeout(resolve, detailDelayMs);
|
|
274
|
+
ctx.signal.addEventListener("abort", () => {
|
|
275
|
+
clearTimeout(timeout);
|
|
276
|
+
resolve();
|
|
277
|
+
}, { once: true });
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
function parseMode(value) {
|
|
281
|
+
if (value === "single-detail-mode" || value === "list-detail-mode") {
|
|
282
|
+
return value;
|
|
283
|
+
}
|
|
284
|
+
return undefined;
|
|
285
|
+
}
|
|
286
|
+
function isTruthy(value) {
|
|
287
|
+
return value === undefined ? false : truthyEnvValues.has(value.toLowerCase());
|
|
288
|
+
}
|
|
289
|
+
const entry = process.argv[1];
|
|
290
|
+
const isMain = typeof entry === "string" && path.resolve(entry) === fileURLToPath(import.meta.url);
|
|
291
|
+
if (isMain) {
|
|
292
|
+
main().catch((error) => {
|
|
293
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
294
|
+
process.stderr.write(`${message}\n`);
|
|
295
|
+
process.exitCode = 1;
|
|
296
|
+
});
|
|
297
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { KeypressEvent } from "../dashboard/terminal.js";
|
|
2
|
+
import type { Action, DetailItem, Row } from "./state.js";
|
|
3
|
+
export type Effect = {
|
|
4
|
+
type: "renderDetail";
|
|
5
|
+
rowId: string;
|
|
6
|
+
token: number;
|
|
7
|
+
} | {
|
|
8
|
+
type: "exit";
|
|
9
|
+
result: unknown;
|
|
10
|
+
after?: () => Promise<void>;
|
|
11
|
+
} | {
|
|
12
|
+
type: "suspend";
|
|
13
|
+
fn: () => Promise<unknown>;
|
|
14
|
+
resumeWith: (value: unknown) => ExplorerEvent;
|
|
15
|
+
} | {
|
|
16
|
+
type: "persistOrder";
|
|
17
|
+
orderedIds: string[];
|
|
18
|
+
};
|
|
19
|
+
export type ExplorerEvent = {
|
|
20
|
+
type: "key";
|
|
21
|
+
key: KeypressEvent;
|
|
22
|
+
} | {
|
|
23
|
+
type: "resize";
|
|
24
|
+
cols: number;
|
|
25
|
+
rows: number;
|
|
26
|
+
} | {
|
|
27
|
+
type: "rowsLoaded";
|
|
28
|
+
rows: Row[];
|
|
29
|
+
} | {
|
|
30
|
+
type: "detailLoading";
|
|
31
|
+
rowId: string;
|
|
32
|
+
token: number;
|
|
33
|
+
} | {
|
|
34
|
+
type: "detailLoaded";
|
|
35
|
+
rowId: string;
|
|
36
|
+
token: number;
|
|
37
|
+
items: DetailItem[];
|
|
38
|
+
} | {
|
|
39
|
+
type: "detailError";
|
|
40
|
+
rowId: string;
|
|
41
|
+
token: number;
|
|
42
|
+
error: Error;
|
|
43
|
+
} | {
|
|
44
|
+
type: "actionResolved";
|
|
45
|
+
actionId: string;
|
|
46
|
+
} | {
|
|
47
|
+
type: "toastExpired";
|
|
48
|
+
} | {
|
|
49
|
+
type: "suspendResumed";
|
|
50
|
+
value: unknown;
|
|
51
|
+
emit: ExplorerEvent;
|
|
52
|
+
} | {
|
|
53
|
+
type: "modalDismissed";
|
|
54
|
+
result: unknown;
|
|
55
|
+
};
|
|
56
|
+
export type ConfirmModal = {
|
|
57
|
+
kind: "confirm";
|
|
58
|
+
action: Action<unknown>;
|
|
59
|
+
rows: Row[];
|
|
60
|
+
resolver: (ok: boolean) => void;
|
|
61
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Row } from "./state.js";
|
|
2
|
+
export interface FilterMatch {
|
|
3
|
+
index: number;
|
|
4
|
+
score: number;
|
|
5
|
+
positions: number[];
|
|
6
|
+
}
|
|
7
|
+
export interface FilterRowsOptions {
|
|
8
|
+
caseSensitive?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function filterRows(query: string, rows: readonly Row[], opts?: FilterRowsOptions): FilterMatch[];
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
const MATCH_SCORE = 10;
|
|
2
|
+
const CONSECUTIVE_BONUS = 14;
|
|
3
|
+
const WORD_START_BONUS = 10;
|
|
4
|
+
const EARLY_MATCH_BONUS = 3;
|
|
5
|
+
export function filterRows(query, rows, opts = {}) {
|
|
6
|
+
if (query.length === 0) {
|
|
7
|
+
return rows.map((_, index) => ({ index, score: 0, positions: [] }));
|
|
8
|
+
}
|
|
9
|
+
const preparedQuery = opts.caseSensitive === true ? query : query.toLocaleLowerCase();
|
|
10
|
+
const matches = [];
|
|
11
|
+
rows.forEach((row, index) => {
|
|
12
|
+
const text = searchableText(row);
|
|
13
|
+
const preparedText = opts.caseSensitive === true ? text : text.toLocaleLowerCase();
|
|
14
|
+
const match = matchSubsequence(preparedQuery, preparedText);
|
|
15
|
+
if (match !== undefined) {
|
|
16
|
+
matches.push({ index, ...match });
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
return matches.sort((left, right) => right.score - left.score || left.index - right.index);
|
|
20
|
+
}
|
|
21
|
+
function searchableText(row) {
|
|
22
|
+
return [row.title, row.subtitle]
|
|
23
|
+
.filter((value) => value !== undefined)
|
|
24
|
+
.map(stripAnsi)
|
|
25
|
+
.join(" ");
|
|
26
|
+
}
|
|
27
|
+
function stripAnsi(value) {
|
|
28
|
+
return value.replace(/\u001b\[[0-9;]*m/g, "");
|
|
29
|
+
}
|
|
30
|
+
function matchSubsequence(query, text) {
|
|
31
|
+
let previousStates = [];
|
|
32
|
+
for (let queryIndex = 0; queryIndex < query.length; queryIndex += 1) {
|
|
33
|
+
const states = [];
|
|
34
|
+
for (let textIndex = 0; textIndex < text.length; textIndex += 1) {
|
|
35
|
+
if (text[textIndex] !== query[queryIndex]) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (queryIndex === 0) {
|
|
39
|
+
states[textIndex] = {
|
|
40
|
+
score: characterScore(text, textIndex, undefined) + Math.max(0, EARLY_MATCH_BONUS - textIndex),
|
|
41
|
+
positions: [textIndex]
|
|
42
|
+
};
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
for (let previousIndex = 0; previousIndex < textIndex; previousIndex += 1) {
|
|
46
|
+
const previous = previousStates[previousIndex];
|
|
47
|
+
if (previous === undefined) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const next = {
|
|
51
|
+
score: previous.score + characterScore(text, textIndex, previousIndex),
|
|
52
|
+
positions: [...previous.positions, textIndex]
|
|
53
|
+
};
|
|
54
|
+
if (isBetterMatch(next, states[textIndex])) {
|
|
55
|
+
states[textIndex] = next;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
previousStates = states;
|
|
60
|
+
if (!previousStates.some((state) => state !== undefined)) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return previousStates.reduce((best, state) => (state !== undefined && isBetterMatch(state, best) ? state : best), undefined);
|
|
65
|
+
}
|
|
66
|
+
function characterScore(text, index, previousIndex) {
|
|
67
|
+
let score = MATCH_SCORE;
|
|
68
|
+
if (previousIndex !== undefined && index === previousIndex + 1) {
|
|
69
|
+
score += CONSECUTIVE_BONUS;
|
|
70
|
+
}
|
|
71
|
+
if (isWordStart(text, index)) {
|
|
72
|
+
score += WORD_START_BONUS;
|
|
73
|
+
}
|
|
74
|
+
return score;
|
|
75
|
+
}
|
|
76
|
+
function isBetterMatch(candidate, current) {
|
|
77
|
+
if (current === undefined || candidate.score !== current.score) {
|
|
78
|
+
return current === undefined || candidate.score > current.score;
|
|
79
|
+
}
|
|
80
|
+
for (let index = 0; index < candidate.positions.length; index += 1) {
|
|
81
|
+
const position = candidate.positions[index];
|
|
82
|
+
const currentPosition = current.positions[index] ?? Number.POSITIVE_INFINITY;
|
|
83
|
+
if (position !== currentPosition) {
|
|
84
|
+
return position < currentPosition;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
function isWordStart(text, index) {
|
|
90
|
+
if (index === 0) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
const previous = text[index - 1];
|
|
94
|
+
return (previous === " " || previous === "-" || previous === "_" || previous === "/" || previous === ".");
|
|
95
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Detail, DetailCtx, Row } from "./state.js";
|
|
2
|
+
export { runExplorer } from "./runtime.js";
|
|
3
|
+
export { createInitialState } from "./state.js";
|
|
4
|
+
export { resolveBindings } from "./keymap.js";
|
|
5
|
+
export type { Effect, ExplorerEvent } from "./events.js";
|
|
6
|
+
export type { BindingTarget, ExplorerBindingDefaults, ExplorerBuiltinCommand, ResolvedBindings } from "./keymap.js";
|
|
7
|
+
export type { Action, ActionContext, Detail, DetailCtx, DetailItem, Dirty, ExplorerConfig, ExplorerLayoutMode, ExplorerSize, ExplorerState, Row, Tone } from "./state.js";
|
|
8
|
+
export declare function singleDetail<R>(fn: (row: Row, ctx: DetailCtx) => string | Promise<string>): Detail<R>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { runExplorer } from "./runtime.js";
|
|
2
|
+
export { createInitialState } from "./state.js";
|
|
3
|
+
export { resolveBindings } from "./keymap.js";
|
|
4
|
+
export function singleDetail(fn) {
|
|
5
|
+
return {
|
|
6
|
+
items: async (row) => [{ id: row.id, render: ctx => fn(row, ctx) }]
|
|
7
|
+
};
|
|
8
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ExplorerEvent } from "./events.js";
|
|
2
|
+
import type { DetailCtx, DetailItem } from "./state.js";
|
|
3
|
+
export declare const LOADING_INDICATOR_MS = 150;
|
|
4
|
+
export declare function createDetailJobs(emit: (event: ExplorerEvent) => void): {
|
|
5
|
+
schedule: (rowId: string, items: (ctx: DetailCtx) => Promise<DetailItem[]>, ctx: DetailCtx) => Promise<void>;
|
|
6
|
+
abort: () => void;
|
|
7
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export const LOADING_INDICATOR_MS = 150;
|
|
2
|
+
export function createDetailJobs(emit) {
|
|
3
|
+
let token = 0;
|
|
4
|
+
let current = null;
|
|
5
|
+
const abortedTokens = new Set();
|
|
6
|
+
return {
|
|
7
|
+
async schedule(rowId, items, ctx) {
|
|
8
|
+
if (current !== null) {
|
|
9
|
+
current.controller.abort();
|
|
10
|
+
clearTimeout(current.loadingTimer);
|
|
11
|
+
}
|
|
12
|
+
const nextToken = ++token;
|
|
13
|
+
const controller = new AbortController();
|
|
14
|
+
let finished = false;
|
|
15
|
+
const loadingTimer = setTimeout(() => {
|
|
16
|
+
if (!finished && !abortedTokens.has(nextToken)) {
|
|
17
|
+
emit({ type: "detailLoading", rowId, token: nextToken });
|
|
18
|
+
}
|
|
19
|
+
}, LOADING_INDICATOR_MS);
|
|
20
|
+
current = { controller, loadingTimer, token: nextToken };
|
|
21
|
+
try {
|
|
22
|
+
const loadedItems = await items({ ...ctx, signal: controller.signal });
|
|
23
|
+
finished = true;
|
|
24
|
+
if (!abortedTokens.has(nextToken)) {
|
|
25
|
+
emit({ type: "detailLoaded", rowId, token: nextToken, items: loadedItems });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
finished = true;
|
|
30
|
+
if (!abortedTokens.has(nextToken)) {
|
|
31
|
+
emit({ type: "detailError", rowId, token: nextToken, error: toError(error) });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
finished = true;
|
|
36
|
+
clearTimeout(loadingTimer);
|
|
37
|
+
abortedTokens.delete(nextToken);
|
|
38
|
+
if (current?.controller === controller) {
|
|
39
|
+
current = null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
abort() {
|
|
44
|
+
if (current === null) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
current.controller.abort();
|
|
48
|
+
clearTimeout(current.loadingTimer);
|
|
49
|
+
abortedTokens.add(current.token);
|
|
50
|
+
current = null;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function toError(error) {
|
|
55
|
+
if (error instanceof Error) {
|
|
56
|
+
return error;
|
|
57
|
+
}
|
|
58
|
+
return new Error(String(error));
|
|
59
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ExplorerEvent } from "./events.js";
|
|
2
|
+
import type { ExplorerConfig } from "./state.js";
|
|
3
|
+
export type ExplorerBuiltinCommand = "quit" | "filter" | "help" | "palette" | "cursorUp" | "cursorDown" | "top" | "bottom" | "pageUp" | "pageDown" | "focusNext" | "escape" | "confirm" | "toggleSelect" | "selectAll" | "clearSelection" | "detailScrollDown" | "detailScrollUp" | "extendSelectionUp" | "extendSelectionDown" | "reorderUp" | "reorderDown";
|
|
4
|
+
export type BindingTarget = {
|
|
5
|
+
type: "builtin";
|
|
6
|
+
id: ExplorerBuiltinCommand;
|
|
7
|
+
} | {
|
|
8
|
+
type: "action";
|
|
9
|
+
id: string;
|
|
10
|
+
};
|
|
11
|
+
type ExplorerKeypressEvent = Extract<ExplorerEvent, {
|
|
12
|
+
type: "key";
|
|
13
|
+
}>["key"];
|
|
14
|
+
export interface ResolvedBindings {
|
|
15
|
+
bindings: ReadonlyMap<string, BindingTarget>;
|
|
16
|
+
keysByTarget: ReadonlyMap<string, readonly string[]>;
|
|
17
|
+
resolve: (event: ExplorerKeypressEvent) => BindingTarget | undefined;
|
|
18
|
+
}
|
|
19
|
+
export type ExplorerBindingDefaults = Partial<Record<ExplorerBuiltinCommand, string[]>>;
|
|
20
|
+
export declare function resolveBindings<R>(config: ExplorerConfig<R>, defaults?: ExplorerBindingDefaults): ResolvedBindings;
|
|
21
|
+
export {};
|