terminal-pilot 0.0.49 → 0.0.51

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 (108) hide show
  1. package/dist/assets/jetbrains-mono-400-italic.ttf +0 -0
  2. package/dist/assets/jetbrains-mono-400-normal.ttf +0 -0
  3. package/dist/assets/jetbrains-mono-700-italic.ttf +0 -0
  4. package/dist/assets/jetbrains-mono-700-normal.ttf +0 -0
  5. package/dist/cli.js +64 -32
  6. package/dist/cli.js.map +4 -4
  7. package/dist/commands/close-session.js +26 -0
  8. package/dist/commands/close-session.js.map +2 -2
  9. package/dist/commands/create-session.js +26 -0
  10. package/dist/commands/create-session.js.map +2 -2
  11. package/dist/commands/daemon-runtime.js.map +1 -1
  12. package/dist/commands/fill.js +26 -0
  13. package/dist/commands/fill.js.map +2 -2
  14. package/dist/commands/get-session.js +26 -0
  15. package/dist/commands/get-session.js.map +2 -2
  16. package/dist/commands/index.js +39 -6
  17. package/dist/commands/index.js.map +4 -4
  18. package/dist/commands/install.js +42 -3
  19. package/dist/commands/install.js.map +4 -4
  20. package/dist/commands/installer.js +16 -3
  21. package/dist/commands/installer.js.map +4 -4
  22. package/dist/commands/list-sessions.js +26 -0
  23. package/dist/commands/list-sessions.js.map +2 -2
  24. package/dist/commands/press-key.js +26 -0
  25. package/dist/commands/press-key.js.map +2 -2
  26. package/dist/commands/read-history.js +26 -0
  27. package/dist/commands/read-history.js.map +2 -2
  28. package/dist/commands/read-screen.js +26 -0
  29. package/dist/commands/read-screen.js.map +2 -2
  30. package/dist/commands/resize.js +26 -0
  31. package/dist/commands/resize.js.map +2 -2
  32. package/dist/commands/runtime.js.map +1 -1
  33. package/dist/commands/screenshot.js +26 -0
  34. package/dist/commands/screenshot.js.map +2 -2
  35. package/dist/commands/send-signal.js +26 -0
  36. package/dist/commands/send-signal.js.map +2 -2
  37. package/dist/commands/type.js +26 -0
  38. package/dist/commands/type.js.map +2 -2
  39. package/dist/commands/uninstall.js +42 -3
  40. package/dist/commands/uninstall.js.map +4 -4
  41. package/dist/commands/wait-for-exit.js +26 -0
  42. package/dist/commands/wait-for-exit.js.map +2 -2
  43. package/dist/commands/wait-for.js +26 -0
  44. package/dist/commands/wait-for.js.map +2 -2
  45. package/dist/composition.json +1 -1
  46. package/dist/testing/cli-repl.js +64 -32
  47. package/dist/testing/cli-repl.js.map +4 -4
  48. package/dist/testing/qa-cli.js +64 -32
  49. package/dist/testing/qa-cli.js.map +4 -4
  50. package/node_modules/@poe-code/user-error/README.md +56 -0
  51. package/node_modules/@poe-code/user-error/dist/index.d.ts +17 -0
  52. package/node_modules/@poe-code/user-error/dist/index.js +21 -0
  53. package/node_modules/@poe-code/user-error/package.json +24 -0
  54. package/node_modules/toolcraft-design/README.md +30 -0
  55. package/node_modules/toolcraft-design/dist/dashboard/terminal-width.d.ts +1 -0
  56. package/node_modules/toolcraft-design/dist/dashboard/terminal-width.js +18 -1
  57. package/node_modules/toolcraft-design/dist/explorer/actions.d.ts +3 -2
  58. package/node_modules/toolcraft-design/dist/explorer/actions.js +50 -4
  59. package/node_modules/toolcraft-design/dist/explorer/events.d.ts +7 -2
  60. package/node_modules/toolcraft-design/dist/explorer/filter.js +1 -1
  61. package/node_modules/toolcraft-design/dist/explorer/index.d.ts +2 -4
  62. package/node_modules/toolcraft-design/dist/explorer/index.js +1 -2
  63. package/node_modules/toolcraft-design/dist/explorer/jobs.d.ts +2 -2
  64. package/node_modules/toolcraft-design/dist/explorer/jobs.js +3 -5
  65. package/node_modules/toolcraft-design/dist/explorer/keymap.d.ts +13 -3
  66. package/node_modules/toolcraft-design/dist/explorer/keymap.js +89 -348
  67. package/node_modules/toolcraft-design/dist/explorer/layout.d.ts +1 -0
  68. package/node_modules/toolcraft-design/dist/explorer/layout.js +14 -4
  69. package/node_modules/toolcraft-design/dist/explorer/reducer.js +116 -16
  70. package/node_modules/toolcraft-design/dist/explorer/render/detail.d.ts +1 -1
  71. package/node_modules/toolcraft-design/dist/explorer/render/detail.js +45 -7
  72. package/node_modules/toolcraft-design/dist/explorer/render/footer.d.ts +1 -1
  73. package/node_modules/toolcraft-design/dist/explorer/render/footer.js +18 -3
  74. package/node_modules/toolcraft-design/dist/explorer/render/header.d.ts +1 -1
  75. package/node_modules/toolcraft-design/dist/explorer/render/header.js +6 -2
  76. package/node_modules/toolcraft-design/dist/explorer/render/index.d.ts +1 -1
  77. package/node_modules/toolcraft-design/dist/explorer/render/index.js +2 -1
  78. package/node_modules/toolcraft-design/dist/explorer/render/list.d.ts +16 -2
  79. package/node_modules/toolcraft-design/dist/explorer/render/list.js +13 -34
  80. package/node_modules/toolcraft-design/dist/explorer/render/modal.d.ts +1 -1
  81. package/node_modules/toolcraft-design/dist/explorer/render/modal.js +11 -11
  82. package/node_modules/toolcraft-design/dist/explorer/render/pane.d.ts +5 -2
  83. package/node_modules/toolcraft-design/dist/explorer/render/pane.js +9 -6
  84. package/node_modules/toolcraft-design/dist/explorer/render/test-fixtures.js +2 -3
  85. package/node_modules/toolcraft-design/dist/explorer/runtime.js +115 -73
  86. package/node_modules/toolcraft-design/dist/explorer/runtime.test-helpers.d.ts +23 -37
  87. package/node_modules/toolcraft-design/dist/explorer/runtime.test-helpers.js +32 -83
  88. package/node_modules/toolcraft-design/dist/explorer/state.d.ts +79 -8
  89. package/node_modules/toolcraft-design/dist/explorer/state.js +70 -18
  90. package/node_modules/toolcraft-design/dist/index.d.ts +8 -2
  91. package/node_modules/toolcraft-design/dist/index.js +5 -1
  92. package/node_modules/toolcraft-design/dist/screen/ansi-text.d.ts +2 -0
  93. package/node_modules/toolcraft-design/dist/screen/ansi-text.js +35 -0
  94. package/node_modules/toolcraft-design/dist/screen/screen.d.ts +40 -0
  95. package/node_modules/toolcraft-design/dist/screen/screen.js +122 -0
  96. package/node_modules/toolcraft-design/dist/screen/style.d.ts +16 -0
  97. package/node_modules/toolcraft-design/dist/screen/style.js +55 -0
  98. package/node_modules/toolcraft-design/dist/terminal/driver.d.ts +34 -0
  99. package/node_modules/toolcraft-design/dist/terminal/driver.js +65 -0
  100. package/node_modules/toolcraft-design/dist/terminal/input.d.ts +25 -0
  101. package/node_modules/toolcraft-design/dist/terminal/input.js +181 -0
  102. package/node_modules/toolcraft-design/dist/terminal/output.d.ts +10 -0
  103. package/node_modules/toolcraft-design/dist/terminal/output.js +44 -0
  104. package/package.json +2 -1
  105. package/node_modules/toolcraft-design/dist/explorer/two-pane.d.ts +0 -102
  106. package/node_modules/toolcraft-design/dist/explorer/two-pane.js +0 -510
  107. package/node_modules/toolcraft-design/dist/run-two-pane-explorer.d.ts +0 -2
  108. package/node_modules/toolcraft-design/dist/run-two-pane-explorer.js +0 -1
@@ -1,376 +1,117 @@
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: ["pageup", "Ctrl+u"],
11
- pageDown: ["pagedown", "Ctrl+d"],
1
+ const BUILTINS = {
2
+ quit: ["Ctrl+c"],
3
+ filter: [],
4
+ help: [],
5
+ palette: ["Ctrl+p"],
6
+ cursorUp: ["up"],
7
+ cursorDown: ["down"],
8
+ top: ["home"],
9
+ bottom: ["end"],
10
+ pageUp: ["pageup"],
11
+ pageDown: ["pagedown"],
12
+ halfPageUp: ["Ctrl+u"],
13
+ halfPageDown: ["Ctrl+d"],
12
14
  focusNext: ["tab"],
13
15
  escape: ["escape"],
14
16
  confirm: ["return", "enter"],
15
17
  toggleSelect: ["space"],
16
18
  selectAll: ["Ctrl+a"],
17
19
  clearSelection: ["Ctrl+/"],
18
- detailScrollDown: ["Ctrl+f"],
19
- detailScrollUp: ["Ctrl+b"],
20
+ detailScrollDown: [],
21
+ detailScrollUp: [],
20
22
  extendSelectionUp: ["Shift+up"],
21
23
  extendSelectionDown: ["Shift+down"],
22
- reorderUp: ["Shift+up", "K"],
23
- reorderDown: ["Shift+down", "J"]
24
+ reorderUp: ["Shift+up"],
25
+ reorderDown: ["Shift+down"]
24
26
  };
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 selectionCommands = new Set([
49
- "toggleSelect",
50
- "selectAll",
51
- "clearSelection",
52
- "extendSelectionUp",
53
- "extendSelectionDown"
54
- ]);
55
- const reservedActionIds = new Set(["quit"]);
27
+ const CORE_ACCELERATORS = new Set(["c", "u", "d", "p"]);
56
28
  export function resolveBindings(config, defaults = {}) {
57
- const baseCommands = config.reorder === undefined
58
- ? baseBuiltinCommands
59
- : [
60
- ...baseBuiltinCommands.filter((command) => command !== "extendSelectionUp" && command !== "extendSelectionDown"),
61
- ...reorderCommands
62
- ];
63
- const commands = config.multiSelect === false
64
- ? baseCommands.filter((command) => !selectionCommands.has(command))
65
- : baseCommands;
66
- const commandBindings = new Map();
67
- const flatBindings = new Map();
68
- const targetKeys = new Map();
69
- const targetsByCommand = new Map();
70
- const claimed = new Map();
71
- const conflicts = new Map();
29
+ assertNoBareLetterBindings(config);
30
+ assertAcceleratorsFree(config);
31
+ const bindings = new Map();
32
+ const keysByTarget = new Map();
33
+ const commands = Object.keys(BUILTINS).filter(command => {
34
+ if (config.multiSelect === false && ["toggleSelect", "selectAll", "clearSelection", "extendSelectionUp", "extendSelectionDown"].includes(command))
35
+ return false;
36
+ if (config.reorder === undefined && (command === "reorderUp" || command === "reorderDown"))
37
+ return false;
38
+ if (config.reorder !== undefined && (command === "extendSelectionUp" || command === "extendSelectionDown"))
39
+ return false;
40
+ return true;
41
+ });
72
42
  for (const command of commands) {
73
- const target = { type: "builtin", id: command };
74
- const keys = command === "quit"
75
- ? builtinBindings.quit
76
- : defaults[command] ?? builtinBindings[command];
77
- addBindings({
78
- keys,
79
- owner: command,
80
- commandId: `builtin:${command}`,
81
- target,
82
- commandBindings,
83
- flatBindings,
84
- targetKeys,
85
- targetsByCommand,
86
- claimed,
87
- conflicts,
88
- warn: false
89
- });
43
+ const keys = command === "quit" ? BUILTINS.quit : (defaults[command] ?? BUILTINS[command]);
44
+ add(keys, { type: "builtin", id: command }, bindings, keysByTarget);
90
45
  }
91
- for (const action of [...config.actions, ...(config.detail.actions ?? [])]) {
92
- if (reservedActionIds.has(action.id)) {
93
- continue;
94
- }
95
- const keys = toBindingArray(config.keybindOverrides?.[action.id] ?? action.key);
96
- addBindings({
97
- keys,
98
- owner: action.id,
99
- commandId: `action:${action.id}`,
100
- target: { type: "action", id: action.id },
101
- commandBindings,
102
- flatBindings,
103
- targetKeys,
104
- targetsByCommand,
105
- claimed,
106
- conflicts,
107
- warn: true
108
- });
109
- }
110
- if (process.env.NODE_ENV !== "production") {
111
- for (const warning of conflicts.values()) {
112
- process.stderr.write(`${warning}\n`);
113
- }
46
+ for (const action of allActions(config)) {
47
+ if (action.accelerator !== undefined)
48
+ add([`Ctrl+${action.accelerator}`], { type: "action", id: action.id }, bindings, keysByTarget);
114
49
  }
115
- const commandIds = Array.from(commandBindings.keys());
116
- const defaultKeymapBindings = Object.fromEntries(Array.from(commandBindings.entries()));
117
- const resolveCommand = createKeymap(commandIds, defaultKeymapBindings);
118
50
  return {
119
- bindings: flatBindings,
120
- keysByTarget: targetKeys,
121
- resolve: (event) => {
122
- const command = resolveCommand(event);
123
- return command === undefined ? undefined : targetsByCommand.get(command);
124
- }
51
+ bindings,
52
+ keysByTarget,
53
+ resolve: event => bindings.get(eventKey(event))
125
54
  };
126
55
  }
127
- function addBindings(opts) {
128
- const accepted = [];
129
- for (const key of opts.keys) {
130
- const canonical = canonicalizeBinding(key);
131
- if (canonical === undefined) {
132
- continue;
133
- }
134
- const existing = opts.claimed.get(canonical);
135
- if (existing !== undefined) {
136
- if (existing !== opts.owner && opts.warn) {
137
- opts.conflicts.set(`${opts.owner}:${canonical}`, `Explorer key binding conflict: ${key} for ${opts.owner} is already bound to ${existing}`);
138
- }
139
- continue;
140
- }
141
- opts.claimed.set(canonical, opts.owner);
142
- opts.flatBindings.set(canonical, opts.target);
143
- accepted.push(key);
56
+ export function assertNoBareLetterBindings(config) {
57
+ for (const action of allActions(config)) {
58
+ const legacy = action.key === undefined ? [] : (Array.isArray(action.key) ? action.key : [action.key]);
59
+ if (legacy.length > 0)
60
+ throw new Error(`Explorer action ${action.id} uses a bare key; use accelerator instead`);
144
61
  }
145
- if (accepted.length > 0) {
146
- opts.commandBindings.set(opts.commandId, accepted);
147
- opts.targetKeys.set(targetKey(opts.target), accepted);
148
- opts.targetsByCommand.set(opts.commandId, opts.target);
62
+ if (config.keybindOverrides !== undefined && Object.keys(config.keybindOverrides).length > 0) {
63
+ throw new Error("Explorer keybind overrides are not supported because printable keys belong to filtering");
149
64
  }
150
- return accepted;
151
65
  }
152
- function targetKey(target) {
153
- return `${target.type}:${target.id}`;
154
- }
155
- function toBindingArray(value) {
156
- if (value === undefined) {
157
- return [];
158
- }
159
- return Array.isArray(value) ? value : [value];
160
- }
161
- function createKeymap(commands, defaultBindings) {
162
- const bindings = new Map();
163
- const sequences = new Set();
164
- let pendingSequence = "";
165
- for (const command of commands) {
166
- const commandBindings = defaultBindings[command]
167
- .map(parseBinding)
168
- .filter((binding) => binding !== undefined);
169
- for (const binding of commandBindings) {
170
- if (binding.sequence !== undefined) {
171
- sequences.add(binding.sequence);
172
- }
173
- }
174
- bindings.set(command, commandBindings);
175
- }
176
- return (event) => {
177
- for (const command of commands) {
178
- const commandBindings = bindings.get(command);
179
- if (commandBindings?.some((binding) => matchesSingleKey(binding, event))) {
180
- pendingSequence = "";
181
- return command;
182
- }
183
- }
184
- const token = eventToSequenceToken(event);
185
- if (token === undefined) {
186
- pendingSequence = "";
187
- return undefined;
188
- }
189
- pendingSequence = `${pendingSequence}${token}`;
190
- for (const command of commands) {
191
- const commandBindings = bindings.get(command);
192
- if (commandBindings?.some((binding) => binding.sequence === pendingSequence)) {
193
- pendingSequence = "";
194
- return command;
195
- }
196
- }
197
- if (hasSequencePrefix(sequences, pendingSequence)) {
198
- return undefined;
199
- }
200
- pendingSequence = token;
201
- if (hasSequencePrefix(sequences, pendingSequence)) {
202
- return undefined;
203
- }
204
- pendingSequence = "";
205
- return undefined;
206
- };
207
- }
208
- function canonicalizeBinding(binding) {
209
- const parsed = parseBinding(binding);
210
- if (parsed === undefined) {
211
- return undefined;
212
- }
213
- const modifiers = [
214
- parsed.ctrl ? "ctrl" : undefined,
215
- parsed.meta ? "meta" : undefined,
216
- parsed.shift ? "shift" : undefined
217
- ].filter((modifier) => modifier !== undefined);
218
- const key = parsed.name ?? parsed.ch;
219
- if (parsed.sequence !== undefined) {
220
- return parsed.sequence.toLowerCase();
221
- }
222
- return key === undefined ? undefined : [...modifiers, key.toLowerCase()].join("+");
223
- }
224
- function parseBinding(binding) {
225
- const value = binding.trim();
226
- if (value.length === 0) {
227
- return undefined;
228
- }
229
- const parts = value.split("+").map((part) => part.trim()).filter(Boolean);
230
- if (parts.length === 0) {
231
- return undefined;
232
- }
233
- let ctrl = false;
234
- let meta = false;
235
- let shift = false;
236
- const key = parts.at(-1);
237
- if (key === undefined) {
238
- return undefined;
239
- }
240
- for (const modifier of parts.slice(0, -1)) {
241
- const normalized = modifier.toLowerCase();
242
- if (normalized === "ctrl" || normalized === "control") {
243
- ctrl = true;
244
- continue;
245
- }
246
- if (normalized === "meta" || normalized === "alt") {
247
- meta = true;
66
+ export function assertAcceleratorsFree(config) {
67
+ const claimed = new Map();
68
+ for (const action of allActions(config)) {
69
+ if (action.accelerator === undefined)
248
70
  continue;
249
- }
250
- if (normalized === "shift") {
251
- shift = true;
252
- }
253
- }
254
- const normalizedKey = normalizeKeyName(key);
255
- if (parts.length === 1 && isShiftedCharacter(normalizedKey)) {
256
- shift = true;
257
- }
258
- if (normalizedKey.length === 1) {
259
- return {
260
- ch: normalizeBindingCharacter(normalizedKey, shift),
261
- ctrl,
262
- meta,
263
- shift
264
- };
71
+ const accelerator = action.accelerator.toLowerCase();
72
+ if (accelerator.length !== 1 || accelerator < "a" || accelerator > "z")
73
+ throw new Error(`Explorer action ${action.id} accelerator must be one letter`);
74
+ if (CORE_ACCELERATORS.has(accelerator))
75
+ throw new Error(`Explorer action ${action.id} accelerator Ctrl+${accelerator.toUpperCase()} collides with a core key`);
76
+ const owner = claimed.get(accelerator);
77
+ if (owner !== undefined)
78
+ throw new Error(`Explorer actions ${owner} and ${action.id} share Ctrl+${accelerator.toUpperCase()}`);
79
+ claimed.set(accelerator, action.id);
265
80
  }
266
- if (!ctrl &&
267
- !meta &&
268
- !shift &&
269
- !isNamedKey(normalizedKey) &&
270
- isPrintableSequence(normalizedKey)) {
271
- return {
272
- sequence: normalizedKey,
273
- ctrl,
274
- meta,
275
- shift
276
- };
277
- }
278
- return {
279
- name: normalizedKey.toLowerCase(),
280
- ctrl,
281
- meta,
282
- shift
283
- };
284
81
  }
285
- function matchesSingleKey(binding, event) {
286
- if (binding.sequence !== undefined) {
287
- return false;
288
- }
289
- if (binding.ctrl !== event.ctrl ||
290
- binding.meta !== event.meta ||
291
- binding.shift !== event.shift) {
292
- return false;
293
- }
294
- if (binding.ch !== undefined) {
295
- if (binding.ch === " " && event.name === "space") {
296
- return true;
297
- }
298
- return event.ch === binding.ch || event.name === binding.ch.toLowerCase();
299
- }
300
- if (binding.name !== undefined) {
301
- return event.name === binding.name;
302
- }
303
- return false;
82
+ export function keymapToHelp(config) {
83
+ return [
84
+ { title: "Navigation", entries: [
85
+ { key: "↑/↓", label: "move" }, { key: "PgUp/PgDn", label: "page" },
86
+ { key: "Home/End", label: "first/last" }, { key: "Tab", label: "focus" }
87
+ ] },
88
+ { title: "Actions", entries: [
89
+ { key: "Enter", label: "actions" }, { key: "Ctrl+P", label: "palette" },
90
+ ...allActions(config).filter(action => action.accelerator !== undefined).map(action => ({ key: `Ctrl+${action.accelerator.toUpperCase()}`, label: typeof action.label === "string" ? action.label : action.id }))
91
+ ] },
92
+ { title: "General", entries: [{ key: "Esc", label: "clear/quit" }, { key: "Ctrl+C", label: "quit" }] }
93
+ ];
304
94
  }
305
- function eventToSequenceToken(event) {
306
- if (event.ctrl || event.meta || event.ch === undefined) {
307
- return undefined;
308
- }
309
- return event.ch;
95
+ function allActions(config) {
96
+ const actions = [...config.actions, ...(config.detail?.actions ?? [])];
97
+ return [...new Map(actions.map(action => [action.id, action])).values()];
310
98
  }
311
- function hasSequencePrefix(sequences, prefix) {
312
- for (const sequence of sequences) {
313
- if (sequence.startsWith(prefix)) {
314
- return true;
99
+ function add(keys, target, bindings, keysByTarget) {
100
+ const accepted = [];
101
+ for (const key of keys) {
102
+ const normalized = canonical(key);
103
+ if (!bindings.has(normalized)) {
104
+ bindings.set(normalized, target);
105
+ accepted.push(key);
315
106
  }
316
107
  }
317
- return false;
318
- }
319
- function isShiftedCharacter(value) {
320
- return value.length === 1 && value.toLowerCase() !== value && value.toUpperCase() === value;
321
- }
322
- function normalizeBindingCharacter(value, shift) {
323
- if (!shift || value.toLowerCase() === value.toUpperCase()) {
324
- return value;
325
- }
326
- return value.toUpperCase();
327
- }
328
- function normalizeKeyName(value) {
329
- if (value.toLowerCase() === "space") {
330
- return " ";
331
- }
332
- if (value === "↑") {
333
- return "up";
334
- }
335
- if (value === "↓") {
336
- return "down";
337
- }
338
- if (value === "←") {
339
- return "left";
340
- }
341
- if (value === "→") {
342
- return "right";
343
- }
344
- return value;
345
- }
346
- function isNamedKey(value) {
347
- return namedKeys.has(value.toLowerCase());
108
+ if (accepted.length > 0)
109
+ keysByTarget.set(`${target.type}:${target.id}`, accepted);
348
110
  }
349
- function isPrintableSequence(value) {
350
- if (Array.from(value).length <= 1) {
351
- return false;
352
- }
353
- for (const char of value) {
354
- const codePoint = char.codePointAt(0);
355
- if (codePoint === undefined || codePoint < 0x20 || codePoint === 0x7f) {
356
- return false;
357
- }
358
- }
359
- return true;
111
+ function canonical(value) { return value.trim().toLowerCase().replace("control+", "ctrl+"); }
112
+ function eventKey(event) {
113
+ const name = event.name ?? event.ch ?? "";
114
+ const modifiers = [event.ctrl ? "ctrl" : "", event.meta ? "meta" : "", event.shift ? "shift" : ""].filter(Boolean);
115
+ const normalized = name === "return" ? "return" : name.toLowerCase();
116
+ return [...modifiers, normalized].join("+");
360
117
  }
361
- const namedKeys = new Set([
362
- "backspace",
363
- "delete",
364
- "down",
365
- "end",
366
- "enter",
367
- "escape",
368
- "home",
369
- "left",
370
- "pagedown",
371
- "pageup",
372
- "return",
373
- "right",
374
- "tab",
375
- "up"
376
- ]);
@@ -9,6 +9,7 @@ export interface ExplorerLayoutOptions {
9
9
  cols: number;
10
10
  rows: number;
11
11
  detailHidden?: boolean;
12
+ focused?: "list" | "detail";
12
13
  }
13
14
  export interface ExplorerLayout {
14
15
  mode: ExplorerLayoutMode;
@@ -3,7 +3,7 @@ const FOOTER_HEIGHT = 1;
3
3
  export function computeExplorerLayout(opts) {
4
4
  const cols = normalizeSize(opts.cols);
5
5
  const rows = normalizeSize(opts.rows);
6
- const mode = resolveMode(cols);
6
+ const mode = resolveMode(cols, rows);
7
7
  const footerHeight = rows > 0 ? Math.min(FOOTER_HEIGHT, rows) : 0;
8
8
  const headerHeight = Math.min(HEADER_HEIGHT, Math.max(0, rows - footerHeight));
9
9
  const contentY = headerHeight;
@@ -11,7 +11,7 @@ export function computeExplorerLayout(opts) {
11
11
  const footerY = headerHeight + contentHeight;
12
12
  const header = { x: 0, y: 0, width: cols, height: headerHeight };
13
13
  const footer = { x: 0, y: footerY, width: cols, height: footerHeight };
14
- if (mode === "too-narrow" || mode === "narrow-list-only") {
14
+ if (mode === "too-narrow") {
15
15
  return {
16
16
  mode,
17
17
  header,
@@ -20,6 +20,16 @@ export function computeExplorerLayout(opts) {
20
20
  footer
21
21
  };
22
22
  }
23
+ if (mode === "narrow-list-only") {
24
+ const detailFocused = opts.focused === "detail";
25
+ return {
26
+ mode,
27
+ header,
28
+ list: detailFocused ? { x: 0, y: contentY, width: 0, height: contentHeight } : { x: 0, y: contentY, width: cols, height: contentHeight },
29
+ detail: detailFocused ? { x: 0, y: contentY, width: cols, height: contentHeight } : { x: cols, y: contentY, width: 0, height: contentHeight },
30
+ footer
31
+ };
32
+ }
23
33
  if (opts.detailHidden === true) {
24
34
  return {
25
35
  mode,
@@ -52,8 +62,8 @@ export function computeExplorerLayout(opts) {
52
62
  footer
53
63
  };
54
64
  }
55
- function resolveMode(cols) {
56
- if (cols < 40) {
65
+ function resolveMode(cols, rows) {
66
+ if (cols < 60 || rows < 8) {
57
67
  return "too-narrow";
58
68
  }
59
69
  if (cols < 80) {