toolcraft 0.0.40 → 0.0.42

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
@@ -402,10 +402,13 @@ function parseEnumValue(value, values, label) {
402
402
  return match;
403
403
  }
404
404
  function validateStringPattern(value, schema, label) {
405
- if (schema.pattern === undefined) {
406
- return value;
405
+ if (schema.minLength !== undefined && value.length < schema.minLength) {
406
+ throw new UserError(`Invalid value for "${label}". Expected a string with length at least ${schema.minLength}, got string with length ${value.length}.`);
407
+ }
408
+ if (schema.maxLength !== undefined && value.length > schema.maxLength) {
409
+ throw new UserError(`Invalid value for "${label}". Expected a string with length at most ${schema.maxLength}, got string with length ${value.length}.`);
407
410
  }
408
- if (!matchesStringPattern(value, schema.pattern)) {
411
+ if (schema.pattern !== undefined && !matchesStringPattern(value, schema.pattern)) {
409
412
  throw new UserError(`Invalid value for "${label}": "${value}" does not match pattern "${schema.pattern}".`);
410
413
  }
411
414
  return value;
@@ -585,6 +588,14 @@ function parseArrayValue(value, schema, label) {
585
588
  }
586
589
  return splitArrayInput(value).map((item) => parseScalarValue(item, itemSchema, label));
587
590
  }
591
+ function validateArrayBounds(value, schema, label) {
592
+ if (schema.minItems !== undefined && value.length < schema.minItems) {
593
+ throw new UserError(`Invalid value for "${label}". Expected an array with at least ${schema.minItems} items, got array(${value.length}).`);
594
+ }
595
+ if (schema.maxItems !== undefined && value.length > schema.maxItems) {
596
+ throw new UserError(`Invalid value for "${label}". Expected an array with at most ${schema.maxItems} items, got array(${value.length}).`);
597
+ }
598
+ }
588
599
  function createOption(field, globalLongOptionFlags) {
589
600
  const flags = formatOptionFlags(field, globalLongOptionFlags);
590
601
  const collidesWithGlobalFlag = globalLongOptionFlags.has(field.optionFlag);
@@ -979,7 +990,11 @@ function formatExampleCommand(breadcrumb, rootUsageName, params) {
979
990
  const commandPath = buildUsageLine(breadcrumb, rootUsageName, "");
980
991
  const flags = Object.entries(params).map(([key, value]) => {
981
992
  const flag = `--${key}`;
982
- return typeof value === "boolean" ? (value ? flag : `--no-${key}`) : `${flag} ${formatExampleValue(value)}`;
993
+ return typeof value === "boolean"
994
+ ? value
995
+ ? flag
996
+ : `--no-${key}`
997
+ : `${flag} ${formatExampleValue(value)}`;
983
998
  });
984
999
  return [commandPath, ...flags].filter((token) => token.length > 0).join(" ");
985
1000
  }
@@ -1493,6 +1508,9 @@ function describeExpectedPresetValue(schema) {
1493
1508
  if (schema.kind === "array") {
1494
1509
  return "an array";
1495
1510
  }
1511
+ if (schema.kind === "number") {
1512
+ return getExpectedNumberDescription(schema);
1513
+ }
1496
1514
  if (schema.kind === "json") {
1497
1515
  return "valid JSON";
1498
1516
  }
@@ -1510,6 +1528,12 @@ function validatePresetScalarValue(value, schema, fieldPath, presetPath) {
1510
1528
  if (typeof value !== "string") {
1511
1529
  break;
1512
1530
  }
1531
+ if (schema.minLength !== undefined && value.length < schema.minLength) {
1532
+ throw new UserError(`Preset file "${presetPath}" has an invalid value for "${fieldPath}". Expected a string with length at least ${schema.minLength}, got string with length ${value.length}.`);
1533
+ }
1534
+ if (schema.maxLength !== undefined && value.length > schema.maxLength) {
1535
+ throw new UserError(`Preset file "${presetPath}" has an invalid value for "${fieldPath}". Expected a string with length at most ${schema.maxLength}, got string with length ${value.length}.`);
1536
+ }
1513
1537
  if (schema.pattern !== undefined && !matchesStringPattern(value, schema.pattern)) {
1514
1538
  throw new UserError(`Preset file "${presetPath}" has an invalid value for "${fieldPath}": "${value}" does not match pattern "${schema.pattern}".`);
1515
1539
  }
@@ -1548,6 +1572,12 @@ function validatePresetFieldValue(value, field, presetPath) {
1548
1572
  if (!Array.isArray(value)) {
1549
1573
  throw new UserError(`Preset file "${presetPath}" has an invalid value for "${field.displayPath}". Expected an array, got ${describeReceived(value)}.`);
1550
1574
  }
1575
+ if (field.schema.minItems !== undefined && value.length < field.schema.minItems) {
1576
+ throw new UserError(`Preset file "${presetPath}" has an invalid value for "${field.displayPath}". Expected an array with at least ${field.schema.minItems} items, got array(${value.length}).`);
1577
+ }
1578
+ if (field.schema.maxItems !== undefined && value.length > field.schema.maxItems) {
1579
+ throw new UserError(`Preset file "${presetPath}" has an invalid value for "${field.displayPath}". Expected an array with at most ${field.schema.maxItems} items, got array(${value.length}).`);
1580
+ }
1551
1581
  return value.map((item) => validatePresetScalarValue(item, itemSchema, field.displayPath, presetPath));
1552
1582
  }
1553
1583
  async function loadPresetValues(fields, presetPath) {
@@ -1959,12 +1989,17 @@ function parseOptionFieldValue(field, value, errors) {
1959
1989
  }
1960
1990
  parsedValues.push(...parsed);
1961
1991
  }
1992
+ validateArrayBounds(parsedValues, field.schema, field.displayPath);
1962
1993
  return { ok: true, value: parsedValues };
1963
1994
  }
1964
1995
  if (typeof value !== "string") {
1965
1996
  return { ok: true, value };
1966
1997
  }
1967
- return { ok: true, value: parseFieldInputValue(value, field.schema, field.displayPath) };
1998
+ const parsedValue = parseFieldInputValue(value, field.schema, field.displayPath);
1999
+ if (field.schema.kind === "array" && Array.isArray(parsedValue)) {
2000
+ validateArrayBounds(parsedValue, field.schema, field.displayPath);
2001
+ }
2002
+ return { ok: true, value: parsedValue };
1968
2003
  }
1969
2004
  catch (error) {
1970
2005
  if (error instanceof UserError || error instanceof InvalidArgumentError) {
@@ -2026,6 +2061,7 @@ function consumeFieldValue(args, index, schema, label, inlineValue) {
2026
2061
  if (values.length === 0) {
2027
2062
  throw new InvalidArgumentError(`option '${label}' argument missing`);
2028
2063
  }
2064
+ validateArrayBounds(values, schema, label);
2029
2065
  return {
2030
2066
  nextIndex,
2031
2067
  value: values
package/dist/mcp.js CHANGED
@@ -360,6 +360,9 @@ function validateSchemaValue(schema, value, casing, label, errors) {
360
360
  message: `Invalid value for "${label}". Expected a string, got ${describeReceived(value)}.`
361
361
  });
362
362
  }
363
+ else {
364
+ validateStringConstraints(unwrappedSchema, value, label, errors);
365
+ }
363
366
  return value;
364
367
  case "number":
365
368
  if (!isValidNumberSchemaValue(value, unwrappedSchema)) {
@@ -390,6 +393,7 @@ function validateSchemaValue(schema, value, casing, label, errors) {
390
393
  });
391
394
  return value;
392
395
  }
396
+ validateArrayConstraints(unwrappedSchema, value, label, errors);
393
397
  return value.map((item, index) => validateSchemaValue(unwrappedSchema.item, item, casing, `${label}[${index}]`, errors));
394
398
  case "object":
395
399
  return validateObjectSchema(unwrappedSchema, value, casing, label, errors);
@@ -437,6 +441,40 @@ function validateSchemaValue(schema, value, casing, label, errors) {
437
441
  }
438
442
  }
439
443
  }
444
+ function validateStringConstraints(schema, value, label, errors) {
445
+ if (schema.minLength !== undefined && value.length < schema.minLength) {
446
+ errors.push({
447
+ path: label,
448
+ message: `Invalid value for "${label}". Expected a string with length at least ${schema.minLength}, got string with length ${value.length}.`
449
+ });
450
+ }
451
+ if (schema.maxLength !== undefined && value.length > schema.maxLength) {
452
+ errors.push({
453
+ path: label,
454
+ message: `Invalid value for "${label}". Expected a string with length at most ${schema.maxLength}, got string with length ${value.length}.`
455
+ });
456
+ }
457
+ if (schema.pattern !== undefined && !new RegExp(schema.pattern).test(value)) {
458
+ errors.push({
459
+ path: label,
460
+ message: `Invalid value for "${label}": "${value}" does not match pattern "${schema.pattern}".`
461
+ });
462
+ }
463
+ }
464
+ function validateArrayConstraints(schema, value, label, errors) {
465
+ if (schema.minItems !== undefined && value.length < schema.minItems) {
466
+ errors.push({
467
+ path: label,
468
+ message: `Invalid value for "${label}". Expected an array with at least ${schema.minItems} items, got array(${value.length}).`
469
+ });
470
+ }
471
+ if (schema.maxItems !== undefined && value.length > schema.maxItems) {
472
+ errors.push({
473
+ path: label,
474
+ message: `Invalid value for "${label}". Expected an array with at most ${schema.maxItems} items, got array(${value.length}).`
475
+ });
476
+ }
477
+ }
440
478
  function validateObjectSchema(schema, value, casing, label, errors) {
441
479
  if (!isPlainObject(value)) {
442
480
  errors.push({
@@ -545,7 +583,9 @@ function serializeResultValue(schema, value, casing, label, errors) {
545
583
  if (branch === undefined) {
546
584
  const branchNames = Object.keys(unwrappedSchema.branches);
547
585
  errors.push({
548
- path: label.length === 0 ? unwrappedSchema.discriminator : `${label}.${unwrappedSchema.discriminator}`,
586
+ path: label.length === 0
587
+ ? unwrappedSchema.discriminator
588
+ : `${label}.${unwrappedSchema.discriminator}`,
549
589
  message: `Invalid value for "${label.length === 0 ? unwrappedSchema.discriminator : `${label}.${unwrappedSchema.discriminator}`}". Expected one of: ${branchNames.join(", ")}, got ${describeReceived(discriminator)}.`
550
590
  });
551
591
  return value;
@@ -654,9 +694,7 @@ function throwResultValidationErrors(errors) {
654
694
  if (errors.length === 1) {
655
695
  throw new ToolError(JSON_RPC_ERROR_CODES.INTERNAL_ERROR, errors[0]?.message ?? "Invalid command result.");
656
696
  }
657
- const rendered = errors
658
- .slice(0, 10)
659
- .map((error) => ` - ${error.path}: ${error.message}`);
697
+ const rendered = errors.slice(0, 10).map((error) => ` - ${error.path}: ${error.message}`);
660
698
  const remaining = errors.length - rendered.length;
661
699
  if (remaining > 0) {
662
700
  rendered.push(` ... and ${remaining} more`);
@@ -1,8 +1,15 @@
1
1
  export function isValidNumberSchemaValue(value, schema) {
2
2
  return (typeof value === "number" &&
3
3
  Number.isFinite(value) &&
4
- (schema.jsonType !== "integer" || Number.isInteger(value)));
4
+ (schema.jsonType !== "integer" || Number.isInteger(value)) &&
5
+ (schema.minimum === undefined || value >= schema.minimum) &&
6
+ (schema.maximum === undefined || value <= schema.maximum));
5
7
  }
6
8
  export function getExpectedNumberDescription(schema) {
7
- return schema.jsonType === "integer" ? "an integer" : "a number";
9
+ const type = schema.jsonType === "integer" ? "an integer" : "a number";
10
+ const bounds = [
11
+ schema.minimum === undefined ? undefined : `greater than or equal to ${schema.minimum}`,
12
+ schema.maximum === undefined ? undefined : `less than or equal to ${schema.maximum}`
13
+ ].filter((bound) => bound !== undefined);
14
+ return bounds.length === 0 ? type : `${type} ${bounds.join(" and ")}`;
8
15
  }
package/dist/sdk.js CHANGED
@@ -146,6 +146,9 @@ function validateSchemaValue(schema, value, label, errors) {
146
146
  message: `Invalid value for "${label}". Expected a string, got ${describeReceived(value)}.`
147
147
  });
148
148
  }
149
+ else {
150
+ validateStringConstraints(unwrappedSchema, value, label, errors);
151
+ }
149
152
  return value;
150
153
  case "number":
151
154
  if (!isValidNumberSchemaValue(value, unwrappedSchema)) {
@@ -176,6 +179,7 @@ function validateSchemaValue(schema, value, label, errors) {
176
179
  });
177
180
  return value;
178
181
  }
182
+ validateArrayConstraints(unwrappedSchema, value, label, errors);
179
183
  return value.map((item, index) => validateSchemaValue(unwrappedSchema.item, item, `${label}[${index}]`, errors));
180
184
  case "object":
181
185
  return validateObjectSchema(unwrappedSchema, value, label, errors);
@@ -220,6 +224,40 @@ function validateSchemaValue(schema, value, label, errors) {
220
224
  }
221
225
  }
222
226
  }
227
+ function validateStringConstraints(schema, value, label, errors) {
228
+ if (schema.minLength !== undefined && value.length < schema.minLength) {
229
+ errors.push({
230
+ path: label,
231
+ message: `Invalid value for "${label}". Expected a string with length at least ${schema.minLength}, got string with length ${value.length}.`
232
+ });
233
+ }
234
+ if (schema.maxLength !== undefined && value.length > schema.maxLength) {
235
+ errors.push({
236
+ path: label,
237
+ message: `Invalid value for "${label}". Expected a string with length at most ${schema.maxLength}, got string with length ${value.length}.`
238
+ });
239
+ }
240
+ if (schema.pattern !== undefined && !new RegExp(schema.pattern).test(value)) {
241
+ errors.push({
242
+ path: label,
243
+ message: `Invalid value for "${label}": "${value}" does not match pattern "${schema.pattern}".`
244
+ });
245
+ }
246
+ }
247
+ function validateArrayConstraints(schema, value, label, errors) {
248
+ if (schema.minItems !== undefined && value.length < schema.minItems) {
249
+ errors.push({
250
+ path: label,
251
+ message: `Invalid value for "${label}". Expected an array with at least ${schema.minItems} items, got array(${value.length}).`
252
+ });
253
+ }
254
+ if (schema.maxItems !== undefined && value.length > schema.maxItems) {
255
+ errors.push({
256
+ path: label,
257
+ message: `Invalid value for "${label}". Expected an array with at most ${schema.maxItems} items, got array(${value.length}).`
258
+ });
259
+ }
260
+ }
223
261
  function validateObjectSchema(schema, value, label, errors) {
224
262
  if (!isPlainObject(value)) {
225
263
  errors.push({
@@ -11,7 +11,7 @@ export const claudeCodeAgent = {
11
11
  CLAUDE_CODE_ENABLE_TELEMETRY: "1"
12
12
  }
13
13
  },
14
- configPath: "~/.claude/settings.json",
14
+ configPath: "~/.claude.json",
15
15
  branding: {
16
16
  colors: {
17
17
  dark: "#C15F3C",
@@ -3,7 +3,12 @@ export const claudeDesktopAgent = {
3
3
  name: "claude-desktop",
4
4
  label: "Claude Desktop",
5
5
  summary: "Anthropic's official desktop application for Claude",
6
- configPath: "~/.claude/settings.json",
6
+ configPath: "~/.config/Claude/claude_desktop_config.json",
7
+ configPaths: {
8
+ darwin: "~/Library/Application Support/Claude/claude_desktop_config.json",
9
+ linux: "~/.config/Claude/claude_desktop_config.json",
10
+ win32: "~/AppData/Roaming/Claude/claude_desktop_config.json"
11
+ },
7
12
  branding: {
8
13
  colors: {
9
14
  dark: "#D97757",
@@ -5,7 +5,7 @@ export const cursorAgent = {
5
5
  label: "Cursor",
6
6
  summary: "Cursor's CLI coding agent.",
7
7
  binaryName: "cursor-agent",
8
- configPath: "~/.cursor/cli-config.json",
8
+ configPath: "~/.cursor/mcp.json",
9
9
  branding: {
10
10
  colors: {
11
11
  dark: "#FFFFFF",
@@ -6,7 +6,7 @@ export const kimiAgent = {
6
6
  aliases: ["kimi-cli"],
7
7
  binaryName: "kimi",
8
8
  apiShapes: ["openai-chat-completions"],
9
- configPath: "~/.kimi/config.toml",
9
+ configPath: "~/.kimi/mcp.json",
10
10
  branding: {
11
11
  colors: {
12
12
  dark: "#7B68EE",
@@ -10,7 +10,7 @@ export const openCodeAgent = {
10
10
  OPENCODE_CONFIG_CONTENT: '{"experimental":{"openTelemetry":true}}'
11
11
  }
12
12
  },
13
- configPath: "~/.config/opencode/config.json",
13
+ configPath: "~/.config/opencode/opencode.json",
14
14
  branding: {
15
15
  colors: {
16
16
  dark: "#4A4F55",
@@ -2,24 +2,33 @@ import { resolveAgentId } from "./registry.js";
2
2
  function getOwnModel(specifier) {
3
3
  return Object.prototype.hasOwnProperty.call(specifier, "model") ? specifier.model : undefined;
4
4
  }
5
+ function requireNonBlank(value, field) {
6
+ const trimmed = value.trim();
7
+ if (trimmed.length === 0) {
8
+ throw new TypeError(`${field} must not be empty`);
9
+ }
10
+ return trimmed;
11
+ }
5
12
  export function parseAgentSpecifier(input) {
6
13
  const colonIndex = input.indexOf(":");
7
14
  if (colonIndex === -1) {
8
- return { agent: input.trim() };
15
+ return { agent: requireNonBlank(input, "agent") };
9
16
  }
10
- const agent = input.slice(0, colonIndex).trim();
17
+ const agent = requireNonBlank(input.slice(0, colonIndex), "agent");
11
18
  const model = input.slice(colonIndex + 1).trim();
12
19
  return {
13
20
  agent,
14
- ...(model.length > 0 ? { model } : {}),
21
+ ...(model.length > 0 ? { model } : {})
15
22
  };
16
23
  }
17
24
  export function formatAgentSpecifier(specifier) {
25
+ const agent = requireNonBlank(specifier.agent, "agent");
18
26
  const model = getOwnModel(specifier);
19
- if (model) {
20
- return `${specifier.agent}:${model}`;
27
+ const normalizedModel = model?.trim();
28
+ if (normalizedModel) {
29
+ return `${agent}:${normalizedModel}`;
21
30
  }
22
- return specifier.agent;
31
+ return agent;
23
32
  }
24
33
  export function normalizeAgentId(input) {
25
34
  const specifier = parseAgentSpecifier(input.trim());
@@ -14,6 +14,11 @@ export interface AgentDefinition {
14
14
  readonly apiShapes?: readonly ApiShapeId[];
15
15
  readonly otelCapture?: OtelCaptureDefinition;
16
16
  configPath: string;
17
+ readonly configPaths?: {
18
+ readonly darwin: string;
19
+ readonly linux: string;
20
+ readonly win32: string;
21
+ };
17
22
  branding: {
18
23
  colors: {
19
24
  dark: string;
@@ -15,6 +15,32 @@ export class UnsupportedAgentError extends Error {
15
15
  function isConfigObject(value) {
16
16
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
17
17
  }
18
+ function assertNonEmptyString(value, message) {
19
+ if (typeof value !== "string" || value.trim().length === 0) {
20
+ throw new Error(message);
21
+ }
22
+ }
23
+ function assertHttpUrl(value) {
24
+ assertNonEmptyString(value, "MCP HTTP URL must be a valid http or https URL.");
25
+ let parsed;
26
+ try {
27
+ parsed = new URL(value);
28
+ }
29
+ catch {
30
+ throw new Error("MCP HTTP URL must be a valid http or https URL.");
31
+ }
32
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
33
+ throw new Error("MCP HTTP URL must be a valid http or https URL.");
34
+ }
35
+ }
36
+ function validateServerEntry(server) {
37
+ assertNonEmptyString(server.name, "MCP server name must be a non-empty string.");
38
+ if (server.config.transport === "stdio") {
39
+ assertNonEmptyString(server.config.command, "MCP stdio command must be a non-empty string.");
40
+ return;
41
+ }
42
+ assertHttpUrl(server.config.url);
43
+ }
18
44
  function resolveServerMap(document, configKey) {
19
45
  const value = document[configKey];
20
46
  if (value === undefined) {
@@ -32,12 +58,15 @@ export async function configure(agentId, server, options) {
32
58
  if (!isSupported(agentId)) {
33
59
  throw new UnsupportedAgentError(agentId);
34
60
  }
61
+ validateServerEntry(server);
35
62
  const config = getAgentConfig(agentId);
36
63
  const configPath = resolveConfigPath(config, options.platform);
37
64
  const shapeTransformer = getShapeTransformer(config.shape);
38
65
  const shaped = shapeTransformer(server);
66
+ const enabledServer = { ...server, enabled: true };
67
+ const enabledShaped = shapeTransformer(enabledServer);
39
68
  if (shaped === undefined) {
40
- await unconfigure(agentId, server.name, options);
69
+ await unconfigure(agentId, enabledServer, options);
41
70
  return;
42
71
  }
43
72
  const configDir = getConfigDirectory(configPath);
@@ -57,10 +86,13 @@ export async function configure(agentId, server, options) {
57
86
  ? servers[server.name]
58
87
  : undefined;
59
88
  const shapedServer = shaped;
89
+ const enabledShapedServer = enabledShaped;
60
90
  if (existingServer !== undefined && isDeepStrictEqual(existingServer, shapedServer)) {
61
91
  return { changed: false, content: document };
62
92
  }
63
- if (existingServer !== undefined && server.enabled !== false) {
93
+ if (existingServer !== undefined &&
94
+ (enabledShapedServer === undefined ||
95
+ !isDeepStrictEqual(existingServer, enabledShapedServer))) {
64
96
  throw new Error(`MCP server "${server.name}" already exists with different configuration in ${configPath}.`);
65
97
  }
66
98
  const newServers = {
@@ -98,7 +130,8 @@ export async function unconfigure(agentId, server, options) {
98
130
  if (!Object.hasOwn(servers, serverName)) {
99
131
  return { changed: false, content: document };
100
132
  }
101
- if (expectedServer !== undefined && !isDeepStrictEqual(servers[serverName], expectedServer)) {
133
+ if (expectedServer !== undefined &&
134
+ !isDeepStrictEqual(servers[serverName], expectedServer)) {
102
135
  return { changed: false, content: document };
103
136
  }
104
137
  const newServers = { ...servers };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft",
3
- "version": "0.0.40",
3
+ "version": "0.0.42",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -47,7 +47,7 @@
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.40",
50
+ "toolcraft-schema": "0.0.42",
51
51
  "commander": "^14.0.3",
52
52
  "fast-string-width": "^3.0.2",
53
53
  "fast-wrap-ansi": "^0.2.0",