toolcraft 0.0.16 → 0.0.18

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.
Files changed (91) hide show
  1. package/dist/cli.d.ts +2 -0
  2. package/dist/cli.js +833 -124
  3. package/dist/error-report.d.ts +39 -0
  4. package/dist/error-report.js +330 -0
  5. package/dist/human-in-loop/approval-tasks.js +11 -8
  6. package/dist/human-in-loop/approvals-commands.js +21 -20
  7. package/dist/human-in-loop/default-provider.js +5 -3
  8. package/dist/human-in-loop/runner.js +45 -4
  9. package/dist/index.d.ts +2 -2
  10. package/dist/index.js +55 -35
  11. package/dist/json-schema-converter.d.ts +1 -0
  12. package/dist/json-schema-converter.js +102 -52
  13. package/dist/mcp-proxy.d.ts +1 -0
  14. package/dist/mcp-proxy.js +13 -6
  15. package/dist/mcp.d.ts +2 -0
  16. package/dist/mcp.js +131 -55
  17. package/dist/sdk.d.ts +4 -2
  18. package/dist/sdk.js +132 -48
  19. package/dist/source-snippet.d.ts +8 -0
  20. package/dist/source-snippet.js +42 -0
  21. package/dist/stack-trim.d.ts +4 -0
  22. package/dist/stack-trim.js +70 -0
  23. package/dist/suggest.d.ts +4 -0
  24. package/dist/suggest.js +46 -0
  25. package/dist/user-error.d.ts +3 -0
  26. package/dist/user-error.js +7 -1
  27. package/dist/validation-errors.d.ts +5 -0
  28. package/dist/validation-errors.js +18 -0
  29. package/node_modules/@poe-code/design-system/dist/components/help-formatter-plain.d.ts +1 -0
  30. package/node_modules/@poe-code/design-system/dist/components/help-formatter-plain.js +1 -1
  31. package/node_modules/@poe-code/design-system/dist/components/text.d.ts +1 -0
  32. package/node_modules/@poe-code/design-system/dist/components/text.js +8 -0
  33. package/node_modules/@poe-code/design-system/dist/dashboard/buffer.js +8 -1
  34. package/node_modules/@poe-code/design-system/dist/dashboard/keymap.d.ts +5 -0
  35. package/node_modules/@poe-code/design-system/dist/dashboard/keymap.js +146 -12
  36. package/node_modules/@poe-code/design-system/dist/dashboard/terminal.js +31 -0
  37. package/node_modules/@poe-code/design-system/dist/dashboard/types.d.ts +1 -0
  38. package/node_modules/@poe-code/design-system/dist/explorer/actions.d.ts +16 -0
  39. package/node_modules/@poe-code/design-system/dist/explorer/actions.js +39 -0
  40. package/node_modules/@poe-code/design-system/dist/explorer/demo.d.ts +13 -0
  41. package/node_modules/@poe-code/design-system/dist/explorer/demo.js +297 -0
  42. package/node_modules/@poe-code/design-system/dist/explorer/events.d.ts +61 -0
  43. package/node_modules/@poe-code/design-system/dist/explorer/events.js +1 -0
  44. package/node_modules/@poe-code/design-system/dist/explorer/filter.d.ts +10 -0
  45. package/node_modules/@poe-code/design-system/dist/explorer/filter.js +95 -0
  46. package/node_modules/@poe-code/design-system/dist/explorer/index.d.ts +8 -0
  47. package/node_modules/@poe-code/design-system/dist/explorer/index.js +8 -0
  48. package/node_modules/@poe-code/design-system/dist/explorer/jobs.d.ts +7 -0
  49. package/node_modules/@poe-code/design-system/dist/explorer/jobs.js +59 -0
  50. package/node_modules/@poe-code/design-system/dist/explorer/keymap.d.ts +21 -0
  51. package/node_modules/@poe-code/design-system/dist/explorer/keymap.js +363 -0
  52. package/node_modules/@poe-code/design-system/dist/explorer/layout.d.ts +20 -0
  53. package/node_modules/@poe-code/design-system/dist/explorer/layout.js +73 -0
  54. package/node_modules/@poe-code/design-system/dist/explorer/reducer.d.ts +9 -0
  55. package/node_modules/@poe-code/design-system/dist/explorer/reducer.js +704 -0
  56. package/node_modules/@poe-code/design-system/dist/explorer/render/detail.d.ts +4 -0
  57. package/node_modules/@poe-code/design-system/dist/explorer/render/detail.js +96 -0
  58. package/node_modules/@poe-code/design-system/dist/explorer/render/footer.d.ts +4 -0
  59. package/node_modules/@poe-code/design-system/dist/explorer/render/footer.js +49 -0
  60. package/node_modules/@poe-code/design-system/dist/explorer/render/header.d.ts +4 -0
  61. package/node_modules/@poe-code/design-system/dist/explorer/render/header.js +56 -0
  62. package/node_modules/@poe-code/design-system/dist/explorer/render/index.d.ts +8 -0
  63. package/node_modules/@poe-code/design-system/dist/explorer/render/index.js +61 -0
  64. package/node_modules/@poe-code/design-system/dist/explorer/render/list.d.ts +4 -0
  65. package/node_modules/@poe-code/design-system/dist/explorer/render/list.js +106 -0
  66. package/node_modules/@poe-code/design-system/dist/explorer/render/modal.d.ts +3 -0
  67. package/node_modules/@poe-code/design-system/dist/explorer/render/modal.js +91 -0
  68. package/node_modules/@poe-code/design-system/dist/explorer/render/test-fixtures.d.ts +8 -0
  69. package/node_modules/@poe-code/design-system/dist/explorer/render/test-fixtures.js +156 -0
  70. package/node_modules/@poe-code/design-system/dist/explorer/runtime.d.ts +2 -0
  71. package/node_modules/@poe-code/design-system/dist/explorer/runtime.js +282 -0
  72. package/node_modules/@poe-code/design-system/dist/explorer/runtime.test-helpers.d.ts +50 -0
  73. package/node_modules/@poe-code/design-system/dist/explorer/runtime.test-helpers.js +101 -0
  74. package/node_modules/@poe-code/design-system/dist/explorer/state.d.ts +130 -0
  75. package/node_modules/@poe-code/design-system/dist/explorer/state.js +87 -0
  76. package/node_modules/@poe-code/design-system/dist/explorer/theme.d.ts +27 -0
  77. package/node_modules/@poe-code/design-system/dist/explorer/theme.js +97 -0
  78. package/node_modules/@poe-code/design-system/dist/index.d.ts +3 -0
  79. package/node_modules/@poe-code/design-system/dist/index.js +3 -0
  80. package/node_modules/@poe-code/design-system/package.json +1 -0
  81. package/node_modules/@poe-code/task-list/README.md +98 -0
  82. package/node_modules/@poe-code/task-list/dist/backends/gh-issues-sync.d.ts +46 -0
  83. package/node_modules/@poe-code/task-list/dist/backends/gh-issues-sync.js +309 -0
  84. package/node_modules/@poe-code/task-list/dist/backends/gh-issues.d.ts +2 -0
  85. package/node_modules/@poe-code/task-list/dist/backends/gh-issues.js +22 -2
  86. package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.js +266 -99
  87. package/node_modules/@poe-code/task-list/dist/index.d.ts +1 -0
  88. package/node_modules/@poe-code/task-list/dist/index.js +1 -0
  89. package/node_modules/@poe-code/task-list/dist/open.js +3 -0
  90. package/node_modules/@poe-code/task-list/dist/types.d.ts +4 -0
  91. package/package.json +6 -2
@@ -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,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 {};
@@ -0,0 +1,363 @@
1
+ const builtinBindings = {
2
+ quit: ["q", "Ctrl+c"],
3
+ filter: ["/"],
4
+ help: ["?"],
5
+ palette: ["Ctrl+p", "Ctrl+k"],
6
+ cursorUp: ["up", "k"],
7
+ cursorDown: ["down", "j"],
8
+ top: ["home", "gg"],
9
+ bottom: ["end", "G"],
10
+ pageUp: ["Ctrl+u"],
11
+ pageDown: ["Ctrl+d"],
12
+ focusNext: ["tab"],
13
+ escape: ["escape"],
14
+ confirm: ["return", "enter"],
15
+ toggleSelect: ["space"],
16
+ selectAll: ["Ctrl+a"],
17
+ clearSelection: ["Ctrl+/"],
18
+ detailScrollDown: ["Ctrl+f"],
19
+ detailScrollUp: ["Ctrl+b"],
20
+ extendSelectionUp: ["Shift+up"],
21
+ extendSelectionDown: ["Shift+down"],
22
+ reorderUp: ["Ctrl+up", "K"],
23
+ reorderDown: ["Ctrl+down", "J"]
24
+ };
25
+ const baseBuiltinCommands = [
26
+ "quit",
27
+ "filter",
28
+ "help",
29
+ "palette",
30
+ "cursorUp",
31
+ "cursorDown",
32
+ "top",
33
+ "bottom",
34
+ "pageUp",
35
+ "pageDown",
36
+ "focusNext",
37
+ "escape",
38
+ "confirm",
39
+ "toggleSelect",
40
+ "selectAll",
41
+ "clearSelection",
42
+ "detailScrollDown",
43
+ "detailScrollUp",
44
+ "extendSelectionUp",
45
+ "extendSelectionDown"
46
+ ];
47
+ const reorderCommands = ["reorderUp", "reorderDown"];
48
+ const reservedActionIds = new Set(["quit"]);
49
+ export function resolveBindings(config, defaults = {}) {
50
+ const commands = config.reorder === undefined
51
+ ? baseBuiltinCommands
52
+ : [...baseBuiltinCommands, ...reorderCommands];
53
+ const commandBindings = new Map();
54
+ const flatBindings = new Map();
55
+ const targetKeys = new Map();
56
+ const targetsByCommand = new Map();
57
+ const claimed = new Map();
58
+ const conflicts = new Map();
59
+ for (const command of commands) {
60
+ const target = { type: "builtin", id: command };
61
+ const keys = command === "quit"
62
+ ? builtinBindings.quit
63
+ : defaults[command] ?? builtinBindings[command];
64
+ addBindings({
65
+ keys,
66
+ owner: command,
67
+ commandId: `builtin:${command}`,
68
+ target,
69
+ commandBindings,
70
+ flatBindings,
71
+ targetKeys,
72
+ targetsByCommand,
73
+ claimed,
74
+ conflicts,
75
+ warn: false
76
+ });
77
+ }
78
+ for (const action of [...config.actions, ...(config.detail.actions ?? [])]) {
79
+ if (reservedActionIds.has(action.id)) {
80
+ continue;
81
+ }
82
+ const keys = toBindingArray(config.keybindOverrides?.[action.id] ?? action.key);
83
+ addBindings({
84
+ keys,
85
+ owner: action.id,
86
+ commandId: `action:${action.id}`,
87
+ target: { type: "action", id: action.id },
88
+ commandBindings,
89
+ flatBindings,
90
+ targetKeys,
91
+ targetsByCommand,
92
+ claimed,
93
+ conflicts,
94
+ warn: true
95
+ });
96
+ }
97
+ if (process.env.NODE_ENV !== "production") {
98
+ for (const warning of conflicts.values()) {
99
+ process.stderr.write(`${warning}\n`);
100
+ }
101
+ }
102
+ const commandIds = Array.from(commandBindings.keys());
103
+ const defaultKeymapBindings = Object.fromEntries(Array.from(commandBindings.entries()));
104
+ const resolveCommand = createKeymap(commandIds, defaultKeymapBindings);
105
+ return {
106
+ bindings: flatBindings,
107
+ keysByTarget: targetKeys,
108
+ resolve: (event) => {
109
+ const command = resolveCommand(event);
110
+ return command === undefined ? undefined : targetsByCommand.get(command);
111
+ }
112
+ };
113
+ }
114
+ function addBindings(opts) {
115
+ const accepted = [];
116
+ for (const key of opts.keys) {
117
+ const canonical = canonicalizeBinding(key);
118
+ if (canonical === undefined) {
119
+ continue;
120
+ }
121
+ const existing = opts.claimed.get(canonical);
122
+ if (existing !== undefined) {
123
+ if (existing !== opts.owner && opts.warn) {
124
+ opts.conflicts.set(`${opts.owner}:${canonical}`, `Explorer key binding conflict: ${key} for ${opts.owner} is already bound to ${existing}`);
125
+ }
126
+ continue;
127
+ }
128
+ opts.claimed.set(canonical, opts.owner);
129
+ opts.flatBindings.set(canonical, opts.target);
130
+ accepted.push(key);
131
+ }
132
+ if (accepted.length > 0) {
133
+ opts.commandBindings.set(opts.commandId, accepted);
134
+ opts.targetKeys.set(targetKey(opts.target), accepted);
135
+ opts.targetsByCommand.set(opts.commandId, opts.target);
136
+ }
137
+ return accepted;
138
+ }
139
+ function targetKey(target) {
140
+ return `${target.type}:${target.id}`;
141
+ }
142
+ function toBindingArray(value) {
143
+ if (value === undefined) {
144
+ return [];
145
+ }
146
+ return Array.isArray(value) ? value : [value];
147
+ }
148
+ function createKeymap(commands, defaultBindings) {
149
+ const bindings = new Map();
150
+ const sequences = new Set();
151
+ let pendingSequence = "";
152
+ for (const command of commands) {
153
+ const commandBindings = defaultBindings[command]
154
+ .map(parseBinding)
155
+ .filter((binding) => binding !== undefined);
156
+ for (const binding of commandBindings) {
157
+ if (binding.sequence !== undefined) {
158
+ sequences.add(binding.sequence);
159
+ }
160
+ }
161
+ bindings.set(command, commandBindings);
162
+ }
163
+ return (event) => {
164
+ for (const command of commands) {
165
+ const commandBindings = bindings.get(command);
166
+ if (commandBindings?.some((binding) => matchesSingleKey(binding, event))) {
167
+ pendingSequence = "";
168
+ return command;
169
+ }
170
+ }
171
+ const token = eventToSequenceToken(event);
172
+ if (token === undefined) {
173
+ pendingSequence = "";
174
+ return undefined;
175
+ }
176
+ pendingSequence = `${pendingSequence}${token}`;
177
+ for (const command of commands) {
178
+ const commandBindings = bindings.get(command);
179
+ if (commandBindings?.some((binding) => binding.sequence === pendingSequence)) {
180
+ pendingSequence = "";
181
+ return command;
182
+ }
183
+ }
184
+ if (hasSequencePrefix(sequences, pendingSequence)) {
185
+ return undefined;
186
+ }
187
+ pendingSequence = token;
188
+ if (hasSequencePrefix(sequences, pendingSequence)) {
189
+ return undefined;
190
+ }
191
+ pendingSequence = "";
192
+ return undefined;
193
+ };
194
+ }
195
+ function canonicalizeBinding(binding) {
196
+ const parsed = parseBinding(binding);
197
+ if (parsed === undefined) {
198
+ return undefined;
199
+ }
200
+ const modifiers = [
201
+ parsed.ctrl ? "ctrl" : undefined,
202
+ parsed.meta ? "meta" : undefined,
203
+ parsed.shift ? "shift" : undefined
204
+ ].filter((modifier) => modifier !== undefined);
205
+ const key = parsed.name ?? parsed.ch;
206
+ if (parsed.sequence !== undefined) {
207
+ return parsed.sequence.toLowerCase();
208
+ }
209
+ return key === undefined ? undefined : [...modifiers, key.toLowerCase()].join("+");
210
+ }
211
+ function parseBinding(binding) {
212
+ const value = binding.trim();
213
+ if (value.length === 0) {
214
+ return undefined;
215
+ }
216
+ const parts = value.split("+").map((part) => part.trim()).filter(Boolean);
217
+ if (parts.length === 0) {
218
+ return undefined;
219
+ }
220
+ let ctrl = false;
221
+ let meta = false;
222
+ let shift = false;
223
+ const key = parts.at(-1);
224
+ if (key === undefined) {
225
+ return undefined;
226
+ }
227
+ for (const modifier of parts.slice(0, -1)) {
228
+ const normalized = modifier.toLowerCase();
229
+ if (normalized === "ctrl" || normalized === "control") {
230
+ ctrl = true;
231
+ continue;
232
+ }
233
+ if (normalized === "meta" || normalized === "alt") {
234
+ meta = true;
235
+ continue;
236
+ }
237
+ if (normalized === "shift") {
238
+ shift = true;
239
+ }
240
+ }
241
+ const normalizedKey = normalizeKeyName(key);
242
+ if (parts.length === 1 && isShiftedCharacter(normalizedKey)) {
243
+ shift = true;
244
+ }
245
+ if (normalizedKey.length === 1) {
246
+ return {
247
+ ch: normalizeBindingCharacter(normalizedKey, shift),
248
+ ctrl,
249
+ meta,
250
+ shift
251
+ };
252
+ }
253
+ if (!ctrl &&
254
+ !meta &&
255
+ !shift &&
256
+ !isNamedKey(normalizedKey) &&
257
+ isPrintableSequence(normalizedKey)) {
258
+ return {
259
+ sequence: normalizedKey,
260
+ ctrl,
261
+ meta,
262
+ shift
263
+ };
264
+ }
265
+ return {
266
+ name: normalizedKey.toLowerCase(),
267
+ ctrl,
268
+ meta,
269
+ shift
270
+ };
271
+ }
272
+ function matchesSingleKey(binding, event) {
273
+ if (binding.sequence !== undefined) {
274
+ return false;
275
+ }
276
+ if (binding.ctrl !== event.ctrl ||
277
+ binding.meta !== event.meta ||
278
+ binding.shift !== event.shift) {
279
+ return false;
280
+ }
281
+ if (binding.ch !== undefined) {
282
+ if (binding.ch === " " && event.name === "space") {
283
+ return true;
284
+ }
285
+ return event.ch === binding.ch || event.name === binding.ch.toLowerCase();
286
+ }
287
+ if (binding.name !== undefined) {
288
+ return event.name === binding.name;
289
+ }
290
+ return false;
291
+ }
292
+ function eventToSequenceToken(event) {
293
+ if (event.ctrl || event.meta || event.ch === undefined) {
294
+ return undefined;
295
+ }
296
+ return event.ch;
297
+ }
298
+ function hasSequencePrefix(sequences, prefix) {
299
+ for (const sequence of sequences) {
300
+ if (sequence.startsWith(prefix)) {
301
+ return true;
302
+ }
303
+ }
304
+ return false;
305
+ }
306
+ function isShiftedCharacter(value) {
307
+ return value.length === 1 && value.toLowerCase() !== value && value.toUpperCase() === value;
308
+ }
309
+ function normalizeBindingCharacter(value, shift) {
310
+ if (!shift || value.toLowerCase() === value.toUpperCase()) {
311
+ return value;
312
+ }
313
+ return value.toUpperCase();
314
+ }
315
+ function normalizeKeyName(value) {
316
+ if (value.toLowerCase() === "space") {
317
+ return " ";
318
+ }
319
+ if (value === "↑") {
320
+ return "up";
321
+ }
322
+ if (value === "↓") {
323
+ return "down";
324
+ }
325
+ if (value === "←") {
326
+ return "left";
327
+ }
328
+ if (value === "→") {
329
+ return "right";
330
+ }
331
+ return value;
332
+ }
333
+ function isNamedKey(value) {
334
+ return namedKeys.has(value.toLowerCase());
335
+ }
336
+ function isPrintableSequence(value) {
337
+ if (Array.from(value).length <= 1) {
338
+ return false;
339
+ }
340
+ for (const char of value) {
341
+ const codePoint = char.codePointAt(0);
342
+ if (codePoint === undefined || codePoint < 0x20 || codePoint === 0x7f) {
343
+ return false;
344
+ }
345
+ }
346
+ return true;
347
+ }
348
+ const namedKeys = new Set([
349
+ "backspace",
350
+ "delete",
351
+ "down",
352
+ "end",
353
+ "enter",
354
+ "escape",
355
+ "home",
356
+ "left",
357
+ "pagedown",
358
+ "pageup",
359
+ "return",
360
+ "right",
361
+ "tab",
362
+ "up"
363
+ ]);
@@ -0,0 +1,20 @@
1
+ export type ExplorerLayoutMode = "wide" | "medium" | "narrow-vertical" | "narrow-list-only" | "too-narrow";
2
+ export interface Rect {
3
+ x: number;
4
+ y: number;
5
+ width: number;
6
+ height: number;
7
+ }
8
+ export interface ExplorerLayoutOptions {
9
+ cols: number;
10
+ rows: number;
11
+ detailHidden?: boolean;
12
+ }
13
+ export interface ExplorerLayout {
14
+ mode: ExplorerLayoutMode;
15
+ header: Rect;
16
+ list: Rect;
17
+ detail: Rect;
18
+ footer: Rect;
19
+ }
20
+ export declare function computeExplorerLayout(opts: ExplorerLayoutOptions): ExplorerLayout;