toolcraft 0.0.17 → 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 (81) 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/package.json +6 -2
package/dist/sdk.js CHANGED
@@ -1,11 +1,25 @@
1
1
  import { access, readFile, writeFile } from "node:fs/promises";
2
- import { UserError, assertCommandRequirements, resolveCommandSecrets } from "./index.js";
2
+ import { ToolcraftBugError, assertCommandRequirements, resolveCommandSecrets } from "./index.js";
3
+ import { writeErrorReport } from "./error-report.js";
3
4
  import { mergeApprovalsGroup } from "./human-in-loop/approvals-commands.js";
4
5
  import { invokeWithHumanInLoop } from "./human-in-loop/index.js";
5
6
  import { hasMcpProxyGroups, resolveMcpProxies } from "./mcp-proxy.js";
6
7
  import { getExpectedNumberDescription, isValidNumberSchemaValue } from "./number-schema.js";
7
8
  import { filterSchemaForScope } from "./schema-scope.js";
8
- const RESERVED_SERVICE_NAMES = new Set(["params", "secrets", "fetch", "fs", "env", "progress"]);
9
+ import { enableSourceMaps } from "./stack-trim.js";
10
+ import { suggest } from "./suggest.js";
11
+ import { throwValidationErrors } from "./validation-errors.js";
12
+ const RESERVED_SERVICE_NAMES = new Set([
13
+ "params",
14
+ "secrets",
15
+ "fetch",
16
+ "fs",
17
+ "env",
18
+ "progress",
19
+ "runtimeOptions",
20
+ "root"
21
+ ]);
22
+ const RESERVED_SERVICE_NAMES_MESSAGE = "Available reserved names: params, secrets, fetch, fs, env, progress, runtimeOptions, root.";
9
23
  function splitWords(value) {
10
24
  const words = [];
11
25
  let current = "";
@@ -24,7 +38,9 @@ function splitWords(value) {
24
38
  const isUppercase = char !== lower && char === upper;
25
39
  const previous = value[index - 1];
26
40
  const next = value[index + 1];
27
- const previousIsLowercase = previous !== undefined && previous === previous.toLowerCase() && previous !== previous.toUpperCase();
41
+ const previousIsLowercase = previous !== undefined &&
42
+ previous === previous.toLowerCase() &&
43
+ previous !== previous.toUpperCase();
28
44
  const nextIsLowercase = next !== undefined && next === next.toLowerCase() && next !== next.toUpperCase();
29
45
  if (isUppercase && current.length > 0 && (previousIsLowercase || nextIsLowercase)) {
30
46
  words.push(current.toLowerCase());
@@ -69,30 +85,52 @@ function createFs() {
69
85
  catch {
70
86
  return false;
71
87
  }
72
- },
88
+ }
73
89
  };
74
90
  }
75
91
  function createEnv(values = process.env) {
76
92
  return {
77
93
  get(key) {
78
94
  return values[key];
79
- },
95
+ }
80
96
  };
81
97
  }
82
98
  function validateServices(services) {
83
99
  for (const name of Object.keys(services)) {
84
100
  if (RESERVED_SERVICE_NAMES.has(name)) {
85
- throw new Error(`Service name "${name}" is reserved. Choose a different name.`);
101
+ throw new Error(`Service name "${name}" is reserved. Choose a different name. ${RESERVED_SERVICE_NAMES_MESSAGE}`);
86
102
  }
87
103
  }
88
104
  }
89
- function validateEnum(value, schema, label) {
90
- if (!schema.values.includes(value)) {
91
- throw new UserError(`Invalid value for "${label}". Expected one of: ${schema.values.map((candidate) => String(candidate)).join(", ")}.`);
105
+ function formatAvailableList(values) {
106
+ return `Available: ${[...values].sort().join(", ")}.`;
107
+ }
108
+ function formatEnumError(value, schema, label) {
109
+ const suggestionLine = typeof value === "string"
110
+ ? formatEnumSuggestionLine(value, schema.values.map((candidate) => String(candidate)))
111
+ : " ";
112
+ return `Invalid value for "${label}".${suggestionLine}Expected one of: ${schema.values.map((candidate) => String(candidate)).join(", ")}, got ${describeReceived(value)}.`;
113
+ }
114
+ function formatEnumSuggestionLine(value, values) {
115
+ const suggestions = suggest(value, values);
116
+ return suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}?\n` : " ";
117
+ }
118
+ function describeReceived(value) {
119
+ if (value === null)
120
+ return "null";
121
+ if (value === undefined)
122
+ return "missing";
123
+ if (Array.isArray(value))
124
+ return `array(${value.length})`;
125
+ if (typeof value === "object")
126
+ return "object";
127
+ if (typeof value === "string") {
128
+ const s = value.length > 40 ? `${value.slice(0, 40)}…` : value;
129
+ return `${JSON.stringify(s)}`;
92
130
  }
93
- return value;
131
+ return JSON.stringify(value);
94
132
  }
95
- function validateSchemaValue(schema, value, label) {
133
+ function validateSchemaValue(schema, value, label, errors) {
96
134
  const unwrappedSchema = unwrapOptional(schema);
97
135
  if (value === null && unwrappedSchema.nullable === true) {
98
136
  return null;
@@ -100,33 +138,53 @@ function validateSchemaValue(schema, value, label) {
100
138
  switch (unwrappedSchema.kind) {
101
139
  case "string":
102
140
  if (typeof value !== "string") {
103
- throw new UserError(`Invalid value for "${label}". Expected a string.`);
141
+ errors.push({
142
+ path: label,
143
+ message: `Invalid value for "${label}". Expected a string, got ${describeReceived(value)}.`
144
+ });
104
145
  }
105
146
  return value;
106
147
  case "number":
107
148
  if (!isValidNumberSchemaValue(value, unwrappedSchema)) {
108
- throw new UserError(`Invalid value for "${label}". Expected ${getExpectedNumberDescription(unwrappedSchema)}.`);
149
+ errors.push({
150
+ path: label,
151
+ message: `Invalid value for "${label}". Expected ${getExpectedNumberDescription(unwrappedSchema)}, got ${describeReceived(value)}.`
152
+ });
109
153
  }
110
154
  return value;
111
155
  case "boolean":
112
156
  if (typeof value !== "boolean") {
113
- throw new UserError(`Invalid value for "${label}". Expected a boolean.`);
157
+ errors.push({
158
+ path: label,
159
+ message: `Invalid value for "${label}". Expected a boolean, got ${describeReceived(value)}.`
160
+ });
114
161
  }
115
162
  return value;
116
163
  case "enum":
117
- return validateEnum(value, unwrappedSchema, label);
164
+ if (!unwrappedSchema.values.includes(value)) {
165
+ errors.push({ path: label, message: formatEnumError(value, unwrappedSchema, label) });
166
+ }
167
+ return value;
118
168
  case "array":
119
169
  if (!Array.isArray(value)) {
120
- throw new UserError(`Invalid value for "${label}". Expected an array.`);
170
+ errors.push({
171
+ path: label,
172
+ message: `Invalid value for "${label}". Expected an array, got ${describeReceived(value)}.`
173
+ });
174
+ return value;
121
175
  }
122
- return value.map((item, index) => validateSchemaValue(unwrappedSchema.item, item, `${label}[${index}]`));
176
+ return value.map((item, index) => validateSchemaValue(unwrappedSchema.item, item, `${label}[${index}]`, errors));
123
177
  case "object":
124
- return validateObjectSchema(unwrappedSchema, value, label);
178
+ return validateObjectSchema(unwrappedSchema, value, label, errors);
125
179
  }
126
180
  }
127
- function validateObjectSchema(schema, value, label) {
181
+ function validateObjectSchema(schema, value, label, errors) {
128
182
  if (!isPlainObject(value)) {
129
- throw new UserError(`Invalid value for "${label}". Expected an object.`);
183
+ errors.push({
184
+ path: label,
185
+ message: `Invalid value for "${label}". Expected an object, got ${describeReceived(value)}.`
186
+ });
187
+ return {};
130
188
  }
131
189
  const result = {};
132
190
  const expectedKeys = new Map();
@@ -136,7 +194,10 @@ function validateObjectSchema(schema, value, label) {
136
194
  for (const key of Object.keys(value)) {
137
195
  if (!expectedKeys.has(key)) {
138
196
  const fieldLabel = label.length === 0 ? key : `${label}.${key}`;
139
- throw new UserError(`Unexpected parameter "${fieldLabel}".`);
197
+ errors.push({
198
+ path: fieldLabel,
199
+ message: `Unexpected parameter "${fieldLabel}". ${formatAvailableList([...expectedKeys.keys()].map((expectedKey) => label.length === 0 ? expectedKey : `${label}.${expectedKey}`))}`
200
+ });
140
201
  }
141
202
  }
142
203
  for (const [inputKey, [outputKey, rawChildSchema]] of expectedKeys.entries()) {
@@ -151,14 +212,18 @@ function validateObjectSchema(schema, value, label) {
151
212
  if (isOptional(rawChildSchema)) {
152
213
  continue;
153
214
  }
154
- throw new UserError(`Missing required parameter "${fieldLabel}".`);
215
+ errors.push({ path: fieldLabel, message: `Missing required parameter "${fieldLabel}".` });
216
+ continue;
155
217
  }
156
- result[outputKey] = validateSchemaValue(rawChildSchema, value[inputKey], fieldLabel);
218
+ result[outputKey] = validateSchemaValue(rawChildSchema, value[inputKey], fieldLabel, errors);
157
219
  }
158
220
  return result;
159
221
  }
160
222
  function validateSDKArguments(schema, argumentsValue) {
161
- return validateObjectSchema(schema, argumentsValue ?? {}, "");
223
+ const errors = [];
224
+ const result = validateObjectSchema(schema, argumentsValue ?? {}, "", errors);
225
+ throwValidationErrors(errors);
226
+ return result;
162
227
  }
163
228
  function defineMember(target, key, value) {
164
229
  if (Object.prototype.hasOwnProperty.call(target, key)) {
@@ -168,10 +233,11 @@ function defineMember(target, key, value) {
168
233
  value,
169
234
  enumerable: true,
170
235
  configurable: false,
171
- writable: false,
236
+ writable: false
172
237
  });
173
238
  }
174
239
  export function createSDK(root, options = {}) {
240
+ enableSourceMaps();
175
241
  const mergedRoot = mergeApprovalsGroup(root);
176
242
  if (!hasMcpProxyGroups(mergedRoot)) {
177
243
  return createResolvedSDK(mergedRoot, options);
@@ -186,29 +252,47 @@ function createResolvedSDK(root, options = {}) {
186
252
  function build(node, path) {
187
253
  if (node.kind === "command") {
188
254
  return async (params) => {
189
- const secrets = resolveCommandSecrets(node);
190
- const baseContext = {
191
- ...services,
192
- runtimeOptions,
193
- root,
194
- secrets,
195
- fetch: globalThis.fetch,
196
- fs: createFs(),
197
- env: createEnv(),
198
- progress() {
199
- return undefined;
200
- },
201
- };
202
- await assertCommandRequirements(node, { ...baseContext, params: undefined });
203
- const paramsSchema = filterSchemaForScope(node.params, "sdk");
204
- if (paramsSchema === undefined || paramsSchema.kind !== "object") {
205
- throw new Error(`Bug: command "${node.name}" must define an object params schema for SDK.`);
255
+ const commandPath = [...path, node.name].join(".");
256
+ let secrets;
257
+ let validatedParams;
258
+ try {
259
+ secrets = resolveCommandSecrets(node);
260
+ const baseContext = {
261
+ ...services,
262
+ runtimeOptions,
263
+ root,
264
+ secrets,
265
+ fetch: globalThis.fetch,
266
+ fs: createFs(),
267
+ env: createEnv(),
268
+ progress() {
269
+ return undefined;
270
+ }
271
+ };
272
+ await assertCommandRequirements(node, { ...baseContext, params: undefined });
273
+ const paramsSchema = filterSchemaForScope(node.params, "sdk");
274
+ if (paramsSchema === undefined || paramsSchema.kind !== "object") {
275
+ throw new ToolcraftBugError(`command "${node.name}" must define an object params schema for SDK.`);
276
+ }
277
+ validatedParams = validateSDKArguments(paramsSchema, params);
278
+ return await invokeWithHumanInLoop(node, {
279
+ ...baseContext,
280
+ params: validatedParams
281
+ }, runtimeOptions, commandPath);
282
+ }
283
+ catch (error) {
284
+ await writeErrorReport({
285
+ command: node,
286
+ commandPath,
287
+ env: process.env,
288
+ error,
289
+ errorReports: options.errorReports,
290
+ params: validatedParams,
291
+ projectRoot: options.projectRoot,
292
+ secrets
293
+ });
294
+ throw error;
206
295
  }
207
- const validatedParams = validateSDKArguments(paramsSchema, params);
208
- return invokeWithHumanInLoop(node, {
209
- ...baseContext,
210
- params: validatedParams,
211
- }, runtimeOptions, [...path, node.name].join("."));
212
296
  };
213
297
  }
214
298
  const output = {};
@@ -263,7 +347,7 @@ function createDeferredSDK(root, options) {
263
347
  return path.length === 0 ? resolveSDK().then.bind(resolveSDK()) : undefined;
264
348
  }
265
349
  return createPathProxy([...path, property]);
266
- },
350
+ }
267
351
  });
268
352
  return createPathProxy([]);
269
353
  }
@@ -0,0 +1,8 @@
1
+ export interface SourceSnippetOptions {
2
+ source: string;
3
+ line: number;
4
+ column?: number;
5
+ context?: number;
6
+ filePath?: string;
7
+ }
8
+ export declare function renderSourceSnippet(opts: SourceSnippetOptions): string;
@@ -0,0 +1,42 @@
1
+ import { text } from "@poe-code/design-system";
2
+ export function renderSourceSnippet(opts) {
3
+ const lines = opts.source.replaceAll("\r\n", "\n").replaceAll("\r", "\n").split("\n");
4
+ const line = clampInteger(opts.line, 1, Math.max(lines.length, 1));
5
+ const context = Math.max(0, Math.floor(opts.context ?? 2));
6
+ const startLine = Math.max(1, line - context);
7
+ const endLine = Math.min(lines.length, line + context);
8
+ const gutterWidth = String(endLine).length;
9
+ const output = [];
10
+ if (opts.filePath !== undefined) {
11
+ output.push(muted(`--> ${opts.filePath}:${line}${opts.column === undefined ? "" : `:${Math.max(1, opts.column)}`}`));
12
+ }
13
+ output.push(renderDivider(gutterWidth));
14
+ for (let currentLine = startLine; currentLine <= endLine; currentLine += 1) {
15
+ const sourceLine = lines[currentLine - 1] ?? "";
16
+ output.push(`${muted(String(currentLine).padStart(gutterWidth, " "))} | ${sourceLine}`);
17
+ if (currentLine === line && opts.column !== undefined) {
18
+ const column = Math.max(1, Math.floor(opts.column));
19
+ output.push(`${muted(" ".repeat(gutterWidth))} | ${" ".repeat(column - 1)}${error("^")}`);
20
+ }
21
+ }
22
+ output.push(renderDivider(gutterWidth));
23
+ return output.join("\n");
24
+ }
25
+ function renderDivider(gutterWidth) {
26
+ return `${muted(" ".repeat(gutterWidth))} |`;
27
+ }
28
+ function clampInteger(value, min, max) {
29
+ if (!Number.isFinite(value)) {
30
+ return min;
31
+ }
32
+ return Math.min(max, Math.max(min, Math.floor(value)));
33
+ }
34
+ function muted(value) {
35
+ return shouldStyleStderr() ? text.muted(value) : value;
36
+ }
37
+ function error(value) {
38
+ return shouldStyleStderr() ? text.error(value) : value;
39
+ }
40
+ function shouldStyleStderr() {
41
+ return process.stderr.isTTY === true;
42
+ }
@@ -0,0 +1,4 @@
1
+ export type DebugStackMode = "trim" | "raw";
2
+ export declare function enableSourceMaps(): void;
3
+ export declare function formatDebugStack(stack: string, mode: DebugStackMode): string;
4
+ export declare function trimStack(stack: string): string;
@@ -0,0 +1,70 @@
1
+ const HIDDEN_FRAME_SUMMARY_PREFIX = " … (";
2
+ const HIDDEN_FRAME_SUMMARY_SUFFIX = " hidden — pass --debug=raw to show)";
3
+ export function enableSourceMaps() {
4
+ process.setSourceMapsEnabled?.(true);
5
+ }
6
+ export function formatDebugStack(stack, mode) {
7
+ return mode === "raw" ? stack : trimStack(stack);
8
+ }
9
+ export function trimStack(stack) {
10
+ const sections = splitStackSections(stack);
11
+ const trimmed = sections.map((section) => trimStackSection(section));
12
+ const hiddenFrameCount = trimmed.reduce((count, section) => count + section.hiddenFrameCount, 0);
13
+ if (hiddenFrameCount === 0) {
14
+ return stack;
15
+ }
16
+ return trimmed.flatMap((section) => section.lines).join("\n");
17
+ }
18
+ function splitStackSections(stack) {
19
+ const lines = stack.split("\n");
20
+ const firstLine = lines[0];
21
+ if (firstLine === undefined) {
22
+ return [];
23
+ }
24
+ const sections = [{ header: firstLine, lines: [] }];
25
+ for (const line of lines.slice(1)) {
26
+ if (isCauseHeader(line)) {
27
+ sections.push({ header: line, lines: [] });
28
+ continue;
29
+ }
30
+ sections[sections.length - 1]?.lines.push(line);
31
+ }
32
+ return sections;
33
+ }
34
+ function trimStackSection(section) {
35
+ const userFrames = [];
36
+ const skippedFrames = [];
37
+ for (const line of section.lines) {
38
+ if (isFrameworkOrRuntimeFrame(line)) {
39
+ skippedFrames.push(line);
40
+ continue;
41
+ }
42
+ userFrames.push(line);
43
+ }
44
+ if (skippedFrames.length === 0) {
45
+ return {
46
+ lines: [section.header, ...section.lines],
47
+ hiddenFrameCount: 0
48
+ };
49
+ }
50
+ return {
51
+ lines: [section.header, ...userFrames, formatHiddenFrameSummary(skippedFrames.length)],
52
+ hiddenFrameCount: skippedFrames.length
53
+ };
54
+ }
55
+ function isCauseHeader(line) {
56
+ return line.trimStart().startsWith("[cause]:");
57
+ }
58
+ function isFrameworkOrRuntimeFrame(line) {
59
+ const normalized = line.replaceAll("\\", "/");
60
+ return (normalized.includes("node_modules/toolcraft/") ||
61
+ normalized.includes("node_modules/toolcraft-openapi/") ||
62
+ normalized.includes("node_modules/toolcraft-schema/") ||
63
+ normalized.includes("node_modules/commander/") ||
64
+ normalized.includes("node:internal/") ||
65
+ normalized.includes("/packages/toolcraft/src/"));
66
+ }
67
+ function formatHiddenFrameSummary(count) {
68
+ const plural = count === 1 ? "" : "s";
69
+ return `${HIDDEN_FRAME_SUMMARY_PREFIX}${count} framework / runtime frame${plural}${HIDDEN_FRAME_SUMMARY_SUFFIX}`;
70
+ }
@@ -0,0 +1,4 @@
1
+ export declare function suggest(input: string, candidates: readonly string[], opts?: {
2
+ max?: number;
3
+ threshold?: number;
4
+ }): string[];
@@ -0,0 +1,46 @@
1
+ export function suggest(input, candidates, opts = {}) {
2
+ if (input.length === 0) {
3
+ return [];
4
+ }
5
+ const max = opts.max ?? 3;
6
+ const threshold = opts.threshold ?? Math.max(1, Math.floor(input.length / 4));
7
+ return candidates
8
+ .map((candidate) => ({
9
+ candidate,
10
+ distance: damerauLevenshtein(input, candidate)
11
+ }))
12
+ .filter(({ distance }) => distance <= threshold)
13
+ .sort((left, right) => {
14
+ if (left.distance !== right.distance) {
15
+ return left.distance - right.distance;
16
+ }
17
+ return left.candidate.localeCompare(right.candidate);
18
+ })
19
+ .slice(0, max)
20
+ .map(({ candidate }) => candidate);
21
+ }
22
+ function damerauLevenshtein(left, right) {
23
+ const distances = Array.from({ length: left.length + 1 }, () => Array.from({ length: right.length + 1 }, () => 0));
24
+ for (let row = 0; row <= left.length; row += 1) {
25
+ distances[row][0] = row;
26
+ }
27
+ for (let column = 0; column <= right.length; column += 1) {
28
+ distances[0][column] = column;
29
+ }
30
+ for (let row = 1; row <= left.length; row += 1) {
31
+ for (let column = 1; column <= right.length; column += 1) {
32
+ const substitutionCost = left[row - 1] === right[column - 1] ? 0 : 1;
33
+ const deletion = distances[row - 1][column] + 1;
34
+ const insertion = distances[row][column - 1] + 1;
35
+ const substitution = distances[row - 1][column - 1] + substitutionCost;
36
+ distances[row][column] = Math.min(deletion, insertion, substitution);
37
+ if (row > 1 &&
38
+ column > 1 &&
39
+ left[row - 1] === right[column - 2] &&
40
+ left[row - 2] === right[column - 1]) {
41
+ distances[row][column] = Math.min(distances[row][column], distances[row - 2][column - 2] + 1);
42
+ }
43
+ }
44
+ }
45
+ return distances[left.length][right.length];
46
+ }
@@ -1,3 +1,6 @@
1
1
  export declare class UserError extends Error {
2
+ constructor(message: string, options?: ErrorOptions);
3
+ }
4
+ export declare class ToolcraftBugError extends Error {
2
5
  constructor(message: string);
3
6
  }
@@ -1,6 +1,12 @@
1
1
  export class UserError extends Error {
2
+ constructor(message, options) {
3
+ super(message, options);
4
+ this.name = "UserError";
5
+ }
6
+ }
7
+ export class ToolcraftBugError extends Error {
2
8
  constructor(message) {
3
9
  super(message);
4
- this.name = "UserError";
10
+ this.name = "ToolcraftBugError";
5
11
  }
6
12
  }
@@ -0,0 +1,5 @@
1
+ export type ValidationError = {
2
+ path: string;
3
+ message: string;
4
+ };
5
+ export declare function throwValidationErrors(errors: readonly ValidationError[]): void;
@@ -0,0 +1,18 @@
1
+ import { UserError } from "./user-error.js";
2
+ const MAX_RENDERED_VALIDATION_ERRORS = 10;
3
+ export function throwValidationErrors(errors) {
4
+ if (errors.length === 0) {
5
+ return;
6
+ }
7
+ if (errors.length === 1) {
8
+ throw new UserError(errors[0]?.message ?? "Invalid parameters.");
9
+ }
10
+ const rendered = errors
11
+ .slice(0, MAX_RENDERED_VALIDATION_ERRORS)
12
+ .map((error) => ` - ${error.path}: ${error.message}`);
13
+ const remaining = errors.length - rendered.length;
14
+ if (remaining > 0) {
15
+ rendered.push(` … and ${remaining} more`);
16
+ }
17
+ throw new UserError(`${errors.length} parameter errors:\n${rendered.join("\n")}`);
18
+ }
@@ -1,4 +1,5 @@
1
1
  import type { CommandInfo, FormatColumnsOptions, OptionInfo } from "./help-formatter.js";
2
+ export declare function stripAnsi(value: string): string;
2
3
  export declare function formatColumns(opts: FormatColumnsOptions): string;
3
4
  export declare function formatCommandList(commands: CommandInfo[]): string;
4
5
  export declare function formatOptionList(options: OptionInfo[]): string;
@@ -1,4 +1,4 @@
1
- function stripAnsi(value) {
1
+ export function stripAnsi(value) {
2
2
  let output = "";
3
3
  for (let index = 0; index < value.length; index += 1) {
4
4
  if (value[index] === "\u001b") {
@@ -10,6 +10,7 @@ export declare const text: {
10
10
  readonly usageCommand: (content: string) => string;
11
11
  readonly link: (content: string) => string;
12
12
  readonly muted: (content: string) => string;
13
+ readonly error: (content: string) => string;
13
14
  readonly badge: (content: string) => string;
14
15
  readonly selectLabel: (label: string, detail?: string) => string;
15
16
  };
@@ -91,6 +91,14 @@ export const text = {
91
91
  return `*${content}*`;
92
92
  return getTheme().muted(content);
93
93
  },
94
+ error(content) {
95
+ const format = resolveOutputFormat();
96
+ if (format === "json")
97
+ return content;
98
+ if (format === "markdown")
99
+ return `**${content}**`;
100
+ return getTheme().error(content);
101
+ },
94
102
  badge(content) {
95
103
  const format = resolveOutputFormat();
96
104
  if (format === "json")
@@ -126,6 +126,9 @@ export function cellToAnsi(cell) {
126
126
  if (style.dim) {
127
127
  painter = painter.dim;
128
128
  }
129
+ if (style.underline) {
130
+ painter = painter.underline;
131
+ }
129
132
  if (style.fg) {
130
133
  painter = applyForegroundColor(painter, style.fg);
131
134
  }
@@ -158,6 +161,9 @@ function normalizeStyle(style) {
158
161
  if (style?.dim !== undefined) {
159
162
  next.dim = style.dim;
160
163
  }
164
+ if (style?.underline !== undefined) {
165
+ next.underline = style.underline;
166
+ }
161
167
  return next;
162
168
  }
163
169
  function normalizeSize(value) {
@@ -168,7 +174,8 @@ function cellsEqual(left, right) {
168
174
  && left.style.fg === right.style.fg
169
175
  && left.style.bg === right.style.bg
170
176
  && left.style.bold === right.style.bold
171
- && left.style.dim === right.style.dim;
177
+ && left.style.dim === right.style.dim
178
+ && left.style.underline === right.style.underline;
172
179
  }
173
180
  function applyForegroundColor(instance, color) {
174
181
  if (color.startsWith("#")) {
@@ -1,3 +1,8 @@
1
1
  import type { KeypressEvent } from "./terminal.js";
2
2
  import type { Command } from "./types.js";
3
3
  export declare function createKeymap(overrides?: Partial<Record<Command, string[]>>): (event: KeypressEvent) => Command | undefined;
4
+ export declare function createKeymap<TCommand extends string>(overrides: Partial<Record<TCommand, string[]>> | undefined, options: {
5
+ commands: readonly TCommand[];
6
+ defaultBindings: Record<TCommand, readonly string[]>;
7
+ }): (event: KeypressEvent) => TCommand | undefined;
8
+ export declare function canonicalizeBinding(binding: string): string | undefined;