toolcraft 0.0.17 → 0.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) 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/config-mutations/dist/execution/apply-mutation.js +3 -3
  30. package/node_modules/@poe-code/config-mutations/dist/mutations/template-mutation.d.ts +3 -3
  31. package/node_modules/@poe-code/config-mutations/dist/template/render.d.ts +0 -1
  32. package/node_modules/@poe-code/config-mutations/dist/template/render.js +2 -22
  33. package/node_modules/@poe-code/config-mutations/package.json +1 -4
  34. package/node_modules/@poe-code/design-system/dist/acp/components.js +15 -13
  35. package/node_modules/@poe-code/design-system/dist/components/color.d.ts +31 -0
  36. package/node_modules/@poe-code/design-system/dist/components/color.js +101 -0
  37. package/node_modules/@poe-code/design-system/dist/components/help-formatter-plain.d.ts +1 -0
  38. package/node_modules/@poe-code/design-system/dist/components/help-formatter-plain.js +1 -1
  39. package/node_modules/@poe-code/design-system/dist/components/index.d.ts +4 -0
  40. package/node_modules/@poe-code/design-system/dist/components/index.js +2 -0
  41. package/node_modules/@poe-code/design-system/dist/components/logger.js +2 -2
  42. package/node_modules/@poe-code/design-system/dist/components/symbols.js +3 -3
  43. package/node_modules/@poe-code/design-system/dist/components/table.js +191 -40
  44. package/node_modules/@poe-code/design-system/dist/components/template.d.ts +6 -0
  45. package/node_modules/@poe-code/design-system/dist/components/template.js +271 -0
  46. package/node_modules/@poe-code/design-system/dist/components/text.d.ts +1 -0
  47. package/node_modules/@poe-code/design-system/dist/components/text.js +11 -3
  48. package/node_modules/@poe-code/design-system/dist/dashboard/buffer.js +20 -13
  49. package/node_modules/@poe-code/design-system/dist/dashboard/keymap.d.ts +5 -0
  50. package/node_modules/@poe-code/design-system/dist/dashboard/keymap.js +146 -12
  51. package/node_modules/@poe-code/design-system/dist/dashboard/terminal.js +31 -0
  52. package/node_modules/@poe-code/design-system/dist/dashboard/types.d.ts +1 -0
  53. package/node_modules/@poe-code/design-system/dist/explorer/actions.d.ts +16 -0
  54. package/node_modules/@poe-code/design-system/dist/explorer/actions.js +39 -0
  55. package/node_modules/@poe-code/design-system/dist/explorer/demo.d.ts +13 -0
  56. package/node_modules/@poe-code/design-system/dist/explorer/demo.js +297 -0
  57. package/node_modules/@poe-code/design-system/dist/explorer/events.d.ts +61 -0
  58. package/node_modules/@poe-code/design-system/dist/explorer/events.js +1 -0
  59. package/node_modules/@poe-code/design-system/dist/explorer/filter.d.ts +10 -0
  60. package/node_modules/@poe-code/design-system/dist/explorer/filter.js +95 -0
  61. package/node_modules/@poe-code/design-system/dist/explorer/index.d.ts +8 -0
  62. package/node_modules/@poe-code/design-system/dist/explorer/index.js +8 -0
  63. package/node_modules/@poe-code/design-system/dist/explorer/jobs.d.ts +7 -0
  64. package/node_modules/@poe-code/design-system/dist/explorer/jobs.js +59 -0
  65. package/node_modules/@poe-code/design-system/dist/explorer/keymap.d.ts +21 -0
  66. package/node_modules/@poe-code/design-system/dist/explorer/keymap.js +363 -0
  67. package/node_modules/@poe-code/design-system/dist/explorer/layout.d.ts +20 -0
  68. package/node_modules/@poe-code/design-system/dist/explorer/layout.js +73 -0
  69. package/node_modules/@poe-code/design-system/dist/explorer/reducer.d.ts +9 -0
  70. package/node_modules/@poe-code/design-system/dist/explorer/reducer.js +704 -0
  71. package/node_modules/@poe-code/design-system/dist/explorer/render/detail.d.ts +4 -0
  72. package/node_modules/@poe-code/design-system/dist/explorer/render/detail.js +96 -0
  73. package/node_modules/@poe-code/design-system/dist/explorer/render/footer.d.ts +4 -0
  74. package/node_modules/@poe-code/design-system/dist/explorer/render/footer.js +49 -0
  75. package/node_modules/@poe-code/design-system/dist/explorer/render/header.d.ts +4 -0
  76. package/node_modules/@poe-code/design-system/dist/explorer/render/header.js +56 -0
  77. package/node_modules/@poe-code/design-system/dist/explorer/render/index.d.ts +8 -0
  78. package/node_modules/@poe-code/design-system/dist/explorer/render/index.js +61 -0
  79. package/node_modules/@poe-code/design-system/dist/explorer/render/list.d.ts +4 -0
  80. package/node_modules/@poe-code/design-system/dist/explorer/render/list.js +106 -0
  81. package/node_modules/@poe-code/design-system/dist/explorer/render/modal.d.ts +3 -0
  82. package/node_modules/@poe-code/design-system/dist/explorer/render/modal.js +91 -0
  83. package/node_modules/@poe-code/design-system/dist/explorer/render/test-fixtures.d.ts +8 -0
  84. package/node_modules/@poe-code/design-system/dist/explorer/render/test-fixtures.js +156 -0
  85. package/node_modules/@poe-code/design-system/dist/explorer/runtime.d.ts +2 -0
  86. package/node_modules/@poe-code/design-system/dist/explorer/runtime.js +282 -0
  87. package/node_modules/@poe-code/design-system/dist/explorer/runtime.test-helpers.d.ts +50 -0
  88. package/node_modules/@poe-code/design-system/dist/explorer/runtime.test-helpers.js +101 -0
  89. package/node_modules/@poe-code/design-system/dist/explorer/state.d.ts +130 -0
  90. package/node_modules/@poe-code/design-system/dist/explorer/state.js +87 -0
  91. package/node_modules/@poe-code/design-system/dist/explorer/theme.d.ts +27 -0
  92. package/node_modules/@poe-code/design-system/dist/explorer/theme.js +97 -0
  93. package/node_modules/@poe-code/design-system/dist/index.d.ts +7 -0
  94. package/node_modules/@poe-code/design-system/dist/index.js +5 -0
  95. package/node_modules/@poe-code/design-system/dist/internal/color-support.d.ts +9 -0
  96. package/node_modules/@poe-code/design-system/dist/internal/color-support.js +12 -0
  97. package/node_modules/@poe-code/design-system/dist/prompts/index.js +2 -2
  98. package/node_modules/@poe-code/design-system/dist/prompts/primitives/cancel.js +2 -2
  99. package/node_modules/@poe-code/design-system/dist/prompts/primitives/intro.js +2 -2
  100. package/node_modules/@poe-code/design-system/dist/prompts/primitives/log.js +4 -4
  101. package/node_modules/@poe-code/design-system/dist/prompts/primitives/note.js +5 -5
  102. package/node_modules/@poe-code/design-system/dist/prompts/primitives/outro.js +2 -2
  103. package/node_modules/@poe-code/design-system/dist/prompts/primitives/spinner.js +3 -3
  104. package/node_modules/@poe-code/design-system/dist/static/menu.js +5 -5
  105. package/node_modules/@poe-code/design-system/dist/static/spinner.js +8 -8
  106. package/node_modules/@poe-code/design-system/dist/tokens/colors.js +29 -29
  107. package/node_modules/@poe-code/design-system/dist/tokens/typography.js +6 -6
  108. package/node_modules/@poe-code/design-system/package.json +6 -3
  109. package/package.json +6 -5
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,4 @@
1
- import Mustache from "mustache";
1
+ import { renderTemplate } from "@poe-code/design-system";
2
2
  import { getConfigFormat, detectFormat } from "../formats/index.js";
3
3
  import { resolvePath } from "./path-utils.js";
4
4
  import { isNotFound, readFileIfExists, pathExists, createTimestamp } from "../fs-utils.js";
@@ -481,7 +481,7 @@ async function applyTemplateWrite(mutation, context, options) {
481
481
  const templateContext = mutation.context
482
482
  ? resolveValue(mutation.context, options)
483
483
  : {};
484
- const rendered = Mustache.render(template, templateContext);
484
+ const rendered = renderTemplate(template, templateContext);
485
485
  const existed = await pathExists(context.fs, targetPath);
486
486
  if (!context.dryRun) {
487
487
  await context.fs.writeFile(targetPath, rendered, { encoding: "utf8" });
@@ -513,7 +513,7 @@ async function applyTemplateMerge(mutation, context, options, formatName) {
513
513
  const templateContext = mutation.context
514
514
  ? resolveValue(mutation.context, options)
515
515
  : {};
516
- const rendered = Mustache.render(template, templateContext);
516
+ const rendered = renderTemplate(template, templateContext);
517
517
  // Parse rendered template
518
518
  let templateDoc;
519
519
  try {
@@ -4,7 +4,7 @@ export interface WriteOptions {
4
4
  target: ValueResolver<string>;
5
5
  /** Template ID to load via template loader */
6
6
  templateId: string;
7
- /** Context to pass to Mustache.render() */
7
+ /** Context to pass to renderTemplate() */
8
8
  context?: ValueResolver<ConfigObject>;
9
9
  /** Optional human-readable label for logging */
10
10
  label?: string;
@@ -14,7 +14,7 @@ export interface MergeTomlOptions {
14
14
  target: ValueResolver<string>;
15
15
  /** Template ID to load via template loader */
16
16
  templateId: string;
17
- /** Context to pass to Mustache.render() */
17
+ /** Context to pass to renderTemplate() */
18
18
  context?: ValueResolver<ConfigObject>;
19
19
  /** Optional human-readable label for logging */
20
20
  label?: string;
@@ -24,7 +24,7 @@ export interface MergeJsonOptions {
24
24
  target: ValueResolver<string>;
25
25
  /** Template ID to load via template loader */
26
26
  templateId: string;
27
- /** Context to pass to Mustache.render() */
27
+ /** Context to pass to renderTemplate() */
28
28
  context?: ValueResolver<ConfigObject>;
29
29
  /** Optional human-readable label for logging */
30
30
  label?: string;
@@ -1,7 +1,6 @@
1
1
  export type TemplateVariables = Record<string, string | number | boolean | string[]>;
2
2
  /**
3
3
  * Render a mustache template with the given variables.
4
- * Arrays are automatically joined with newlines.
5
4
  * HTML escaping is disabled.
6
5
  */
7
6
  export declare function renderTemplate(template: string, variables: TemplateVariables): string;
@@ -1,28 +1,8 @@
1
- import Mustache from "mustache";
2
- // Disable HTML escaping - we're rendering prompts, not HTML
3
- const originalEscape = Mustache.escape;
1
+ import { renderTemplate as renderDesignTemplate } from "@poe-code/design-system";
4
2
  /**
5
3
  * Render a mustache template with the given variables.
6
- * Arrays are automatically joined with newlines.
7
4
  * HTML escaping is disabled.
8
5
  */
9
6
  export function renderTemplate(template, variables) {
10
- // Pre-process variables to handle arrays
11
- const processed = {};
12
- for (const [key, value] of Object.entries(variables)) {
13
- if (Array.isArray(value)) {
14
- processed[key] = value.join("\n");
15
- }
16
- else {
17
- processed[key] = value;
18
- }
19
- }
20
- // Temporarily disable HTML escaping
21
- Mustache.escape = (text) => text;
22
- try {
23
- return Mustache.render(template, processed);
24
- }
25
- finally {
26
- Mustache.escape = originalEscape;
27
- }
7
+ return renderDesignTemplate(template, variables, { escape: "none" });
28
8
  }
@@ -22,12 +22,9 @@
22
22
  "dist"
23
23
  ],
24
24
  "dependencies": {
25
+ "@poe-code/design-system": "*",
25
26
  "jsonc-parser": "^3.3.1",
26
- "mustache": "^4.2.0",
27
27
  "smol-toml": "^1.3.0",
28
28
  "yaml": "^2.8.1"
29
- },
30
- "devDependencies": {
31
- "@types/mustache": "^4.2.6"
32
29
  }
33
30
  }