toolcraft 0.0.68 → 0.0.70

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/cli.js CHANGED
@@ -3328,11 +3328,21 @@ function findUnknownCommanderCommand(program, argv) {
3328
3328
  commandPath: pathSegments.join(" ")
3329
3329
  };
3330
3330
  }
3331
- if (current.commands.length === 0 || getDefaultCommanderCommandName(current) !== undefined) {
3331
+ if (current.commands.length === 0) {
3332
3332
  return undefined;
3333
3333
  }
3334
3334
  const child = current.commands.find((command) => command.name() === token || command.aliases().includes(token));
3335
3335
  if (child === undefined) {
3336
+ if (getDefaultCommanderCommandName(current) !== undefined) {
3337
+ if (shouldRejectDefaultCommandToken(current, token, pathSegments)) {
3338
+ return {
3339
+ input: token,
3340
+ currentCommand: current,
3341
+ commandPath: pathSegments.join(" ")
3342
+ };
3343
+ }
3344
+ return undefined;
3345
+ }
3336
3346
  return {
3337
3347
  input: token,
3338
3348
  currentCommand: current,
@@ -3344,6 +3354,38 @@ function findUnknownCommanderCommand(program, argv) {
3344
3354
  }
3345
3355
  return undefined;
3346
3356
  }
3357
+ function shouldRejectDefaultCommandToken(command, token, pathSegments) {
3358
+ return (pathSegments.length === 0 &&
3359
+ isBareCommandLikeToken(token) &&
3360
+ hasNonDefaultPublicChildCommand(command));
3361
+ }
3362
+ function hasNonDefaultPublicChildCommand(command) {
3363
+ const defaultName = getDefaultCommanderCommandName(command);
3364
+ return command.commands.some((child) => child.name() !== defaultName &&
3365
+ !isToolcraftHiddenCommander(child) &&
3366
+ !getToolcraftReservedChildNames(command).includes(child.name()));
3367
+ }
3368
+ function isBareCommandLikeToken(token) {
3369
+ if (token.length === 0) {
3370
+ return false;
3371
+ }
3372
+ for (const character of token) {
3373
+ if (!isCommandNameCharacter(character)) {
3374
+ return false;
3375
+ }
3376
+ }
3377
+ return true;
3378
+ }
3379
+ function isCommandNameCharacter(character) {
3380
+ const code = character.codePointAt(0);
3381
+ if (code === undefined) {
3382
+ return false;
3383
+ }
3384
+ const isLowercaseLetter = code >= 97 && code <= 122;
3385
+ const isUppercaseLetter = code >= 65 && code <= 90;
3386
+ const isDigit = code >= 48 && code <= 57;
3387
+ return isLowercaseLetter || isUppercaseLetter || isDigit || character === "-" || character === "_";
3388
+ }
3347
3389
  function getDefaultCommanderCommandName(command) {
3348
3390
  const candidate = command;
3349
3391
  return typeof candidate._defaultCommandName === "string"
@@ -37,6 +37,11 @@ export interface TwoPaneExplorerConfig<R> {
37
37
  panes: [TwoPaneDefinition, TwoPaneDefinition];
38
38
  actions: TwoPaneAction<R>[];
39
39
  refresh?: () => void | Promise<void>;
40
+ trace?: (record: TwoPaneTraceRecord) => void | Promise<void>;
41
+ }
42
+ export interface TwoPaneTraceRecord {
43
+ event: string;
44
+ [key: string]: unknown;
40
45
  }
41
46
  export interface TwoPanePaneState {
42
47
  id: string;
@@ -75,6 +80,8 @@ export declare class TwoPaneExplorerRuntime<R> {
75
80
  constructor(config: TwoPaneExplorerConfig<R>, driver: TerminalDriver);
76
81
  run(): Promise<R | null>;
77
82
  private startTerminal;
83
+ private subscribeKeypress;
84
+ private pauseKeypress;
78
85
  private loadRows;
79
86
  private refresh;
80
87
  private dispatchKey;
@@ -90,5 +97,6 @@ export declare class TwoPaneExplorerRuntime<R> {
90
97
  private render;
91
98
  private exit;
92
99
  private fail;
100
+ private trace;
93
101
  }
94
102
  export declare function renderTwoPaneExplorer<R>(state: TwoPaneExplorerState, actions: TwoPaneAction<R>[], screen: ScreenBuffer): void;
@@ -51,14 +51,21 @@ export class TwoPaneExplorerRuntime {
51
51
  this.driver.enterAltScreen();
52
52
  this.driver.disableLineWrap();
53
53
  this.driver.hideCursor();
54
- this.unsubscribeKeypress = this.driver.onKeypress((key) => {
55
- this.dispatchKey(key);
56
- });
54
+ this.subscribeKeypress();
57
55
  this.unsubscribeResize = this.driver.onResize(() => {
58
56
  this.state = { ...this.state, size: normalizeSize(this.driver.getSize()) };
59
57
  this.render();
60
58
  });
61
59
  }
60
+ subscribeKeypress() {
61
+ this.unsubscribeKeypress = this.driver.onKeypress((key) => {
62
+ this.dispatchKey(key);
63
+ });
64
+ }
65
+ pauseKeypress() {
66
+ this.unsubscribeKeypress?.();
67
+ this.unsubscribeKeypress = undefined;
68
+ }
62
69
  async loadRows(requestToken = ++this.rowsRequestToken) {
63
70
  const [leftRows, rightRows] = await Promise.all([
64
71
  this.config.panes[0].rows(),
@@ -84,6 +91,14 @@ export class TwoPaneExplorerRuntime {
84
91
  if (this.stopped) {
85
92
  return;
86
93
  }
94
+ this.trace("key", {
95
+ key: traceKey(key),
96
+ filterFocused: this.state.filterFocused,
97
+ pane: this.activePane().id,
98
+ cursor: this.activePane().cursor,
99
+ selected: this.activePane().selected.size,
100
+ filter: this.activePane().filter
101
+ });
87
102
  if (this.state.filterFocused) {
88
103
  this.dispatchFilterKey(key);
89
104
  return;
@@ -92,7 +107,7 @@ export class TwoPaneExplorerRuntime {
92
107
  this.exit(null);
93
108
  return;
94
109
  }
95
- if (key.name === "tab") {
110
+ if (isTabKey(key)) {
96
111
  this.state = {
97
112
  ...this.state,
98
113
  activePaneIndex: this.state.activePaneIndex === 0 ? 1 : 0
@@ -112,7 +127,7 @@ export class TwoPaneExplorerRuntime {
112
127
  this.setCursor(filteredRows(this.activePane()).length - 1);
113
128
  return;
114
129
  }
115
- if (key.ch === " ") {
130
+ if (key.ch === " " || key.name === "space") {
116
131
  this.toggleSelection();
117
132
  return;
118
133
  }
@@ -131,8 +146,14 @@ export class TwoPaneExplorerRuntime {
131
146
  }
132
147
  }
133
148
  dispatchFilterKey(key) {
134
- if (key.name === "escape" || key.name === "return") {
149
+ if (key.name === "escape" || key.name === "return" || key.name === "enter") {
135
150
  this.state = { ...this.state, filterFocused: false };
151
+ this.trace("filter.submit", {
152
+ key: traceKey(key),
153
+ pane: this.activePane().id,
154
+ filter: this.activePane().filter,
155
+ rows: filteredRows(this.activePane()).length
156
+ });
136
157
  this.render();
137
158
  return;
138
159
  }
@@ -145,6 +166,12 @@ export class TwoPaneExplorerRuntime {
145
166
  cursor: 0
146
167
  }))
147
168
  };
169
+ this.trace("filter.update", {
170
+ key: traceKey(key),
171
+ pane: this.activePane().id,
172
+ filter: this.activePane().filter,
173
+ rows: filteredRows(this.activePane()).length
174
+ });
148
175
  this.render();
149
176
  return;
150
177
  }
@@ -157,6 +184,12 @@ export class TwoPaneExplorerRuntime {
157
184
  cursor: 0
158
185
  }))
159
186
  };
187
+ this.trace("filter.update", {
188
+ key: traceKey(key),
189
+ pane: this.activePane().id,
190
+ filter: this.activePane().filter,
191
+ rows: filteredRows(this.activePane()).length
192
+ });
160
193
  this.render();
161
194
  }
162
195
  }
@@ -195,6 +228,14 @@ export class TwoPaneExplorerRuntime {
195
228
  ...this.state,
196
229
  panes: updateActivePane(this.state, (candidate) => ({ ...candidate, selected }))
197
230
  };
231
+ this.trace("selection.toggle", {
232
+ pane: pane.id,
233
+ row: row.id,
234
+ selected: selected.size,
235
+ checked: selected.has(row.id),
236
+ filter: pane.filter,
237
+ cursor: pane.cursor
238
+ });
198
239
  this.render();
199
240
  }
200
241
  runAction(action) {
@@ -223,6 +264,7 @@ export class TwoPaneExplorerRuntime {
223
264
  });
224
265
  }
225
266
  async suspendAnd(fn) {
267
+ this.pauseKeypress();
226
268
  this.driver.exitAltScreen();
227
269
  this.driver.enableLineWrap();
228
270
  this.driver.showCursor();
@@ -236,6 +278,7 @@ export class TwoPaneExplorerRuntime {
236
278
  this.driver.enterAltScreen();
237
279
  this.driver.disableLineWrap();
238
280
  this.driver.hideCursor();
281
+ this.subscribeKeypress();
239
282
  this.state = { ...this.state, size: normalizeSize(this.driver.getSize()) };
240
283
  this.render();
241
284
  }
@@ -290,6 +333,15 @@ export class TwoPaneExplorerRuntime {
290
333
  }
291
334
  this.settle?.reject(error);
292
335
  }
336
+ trace(event, fields = {}) {
337
+ const trace = this.config.trace;
338
+ if (trace === undefined) {
339
+ return;
340
+ }
341
+ void Promise.resolve(trace({ event, ...fields })).catch(() => {
342
+ // Diagnostic tracing must not interfere with TUI input handling.
343
+ });
344
+ }
293
345
  }
294
346
  export function renderTwoPaneExplorer(state, actions, screen) {
295
347
  screen.clear();
@@ -402,9 +454,21 @@ function actionMatchesKey(action, key) {
402
454
  const keys = Array.isArray(action.key) ? action.key : [action.key];
403
455
  return keys.some((candidate) => key.ch === candidate || key.name === candidate);
404
456
  }
457
+ function isTabKey(key) {
458
+ return key.name === "tab" || key.ch === "\t" || (key.name === "i" && key.ctrl);
459
+ }
405
460
  function firstKey(key) {
406
461
  return Array.isArray(key) ? key[0] ?? "" : key;
407
462
  }
463
+ function traceKey(key) {
464
+ return {
465
+ name: key.name,
466
+ ch: key.ch,
467
+ ctrl: key.ctrl,
468
+ meta: key.meta,
469
+ shift: key.shift
470
+ };
471
+ }
408
472
  function isQuitKey(key) {
409
473
  return key.ch === "q" || (key.name === "c" && key.ctrl);
410
474
  }
@@ -10,9 +10,9 @@ export declare const GLYPHS: {
10
10
  readonly barEnd: string;
11
11
  readonly radioActive: string;
12
12
  readonly radioInactive: string;
13
- readonly checkboxActive: string;
14
- readonly checkboxSelected: string;
15
- readonly checkboxInactive: string;
13
+ readonly checkboxActive: "[ ]";
14
+ readonly checkboxSelected: "[x]";
15
+ readonly checkboxInactive: "[ ]";
16
16
  readonly passwordMask: string;
17
17
  readonly ellipsis: "...";
18
18
  };
@@ -27,9 +27,9 @@ export const GLYPHS = {
27
27
  barEnd: glyph("└", "-"),
28
28
  radioActive: glyph("●", ">"),
29
29
  radioInactive: glyph("○", " "),
30
- checkboxActive: glyph("◻", "[ ]"),
31
- checkboxSelected: glyph("◼", "[+]"),
32
- checkboxInactive: glyph("◻", "[ ]"),
30
+ checkboxActive: "[ ]",
31
+ checkboxSelected: "[x]",
32
+ checkboxInactive: "[ ]",
33
33
  passwordMask: glyph("•", "*"),
34
34
  ellipsis: "..."
35
35
  };
@@ -21,6 +21,9 @@ export function mapKey(name, char) {
21
21
  if (char === " ") {
22
22
  return "space";
23
23
  }
24
+ if (char !== undefined && aliases[char] !== undefined) {
25
+ return aliases[char];
26
+ }
24
27
  if (!name) {
25
28
  return undefined;
26
29
  }
@@ -102,10 +102,14 @@ function renderOption(option, values, active, submitted, cancelled) {
102
102
  }
103
103
  function renderMultiselectPrompt(prompt, opts) {
104
104
  if (prompt.state === "submit" || prompt.state === "cancel") {
105
- const labels = prompt.visibleOptions
106
- .filter((option) => hasValue(prompt.value, option.value))
107
- .map((option) => prompt.state === "submit" ? color.dim(option.label) : color.dim.strikethrough(option.label))
108
- .join(", ");
105
+ const selectedOptions = prompt.visibleOptions.filter((option) => hasValue(prompt.value, option.value));
106
+ const labels = selectedOptions.length > 3
107
+ ? prompt.state === "submit"
108
+ ? color.dim(`${selectedOptions.length} selected`)
109
+ : color.dim.strikethrough(`${selectedOptions.length} selected`)
110
+ : selectedOptions
111
+ .map((option) => prompt.state === "submit" ? color.dim(option.label) : color.dim.strikethrough(option.label))
112
+ .join(", ");
109
113
  const end = prompt.state === "submit" ? color.green(GLYPHS.barEnd) : color.red(GLYPHS.barEnd);
110
114
  return `${color.gray(GLYPHS.barStart)} ${symbol(prompt.state)} ${opts.message}\n${color.gray(GLYPHS.bar)} ${labels}\n${end}`;
111
115
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft",
3
- "version": "0.0.68",
3
+ "version": "0.0.70",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -47,8 +47,8 @@
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.68",
51
- "commander": "^14.0.3",
50
+ "toolcraft-schema": "0.0.70",
51
+ "commander": "^13.1.0",
52
52
  "fast-string-width": "^3.0.2",
53
53
  "fast-wrap-ansi": "^0.2.0",
54
54
  "ignore": "^5.3.2",
@@ -63,7 +63,7 @@
63
63
  "dist"
64
64
  ],
65
65
  "engines": {
66
- "node": ">=20"
66
+ "node": ">=18.18"
67
67
  },
68
68
  "repository": {
69
69
  "type": "git",