toolcraft 0.0.40 → 0.0.41

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.
@@ -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
  }
@@ -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.41",
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.41",
51
51
  "commander": "^14.0.3",
52
52
  "fast-string-width": "^3.0.2",
53
53
  "fast-wrap-ansi": "^0.2.0",