caplets 0.9.0 → 0.11.0

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/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from "node:module";
3
3
  import minproc, { stdin, stdout, default as process$1 } from "node:process";
4
- import { execFileSync } from "node:child_process";
5
- import minpath, { basename, dirname, extname, isAbsolute, join, parse, posix, relative, resolve, win32 } from "node:path";
4
+ import { execFileSync, spawn } from "node:child_process";
5
+ import minpath, { basename, delimiter, dirname, extname, isAbsolute, join, parse, posix, relative, resolve, win32 } from "node:path";
6
6
  import { accessSync, chmodSync, constants, cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, statSync, watch, writeFileSync } from "node:fs";
7
7
  import { createInterface } from "node:readline/promises";
8
8
  import { createServer } from "node:http";
@@ -180,7 +180,7 @@ const allowsEval = /* @__PURE__ */ cached(() => {
180
180
  return false;
181
181
  }
182
182
  });
183
- function isPlainObject$5(o) {
183
+ function isPlainObject$7(o) {
184
184
  if (isObject(o) === false) return false;
185
185
  const ctor = o.constructor;
186
186
  if (ctor === void 0) return true;
@@ -191,7 +191,7 @@ function isPlainObject$5(o) {
191
191
  return true;
192
192
  }
193
193
  function shallowClone(o) {
194
- if (isPlainObject$5(o)) return { ...o };
194
+ if (isPlainObject$7(o)) return { ...o };
195
195
  if (Array.isArray(o)) return [...o];
196
196
  if (o instanceof Map) return new Map(o);
197
197
  if (o instanceof Set) return new Set(o);
@@ -274,7 +274,7 @@ function omit(schema, mask) {
274
274
  }));
275
275
  }
276
276
  function extend(schema, shape) {
277
- if (!isPlainObject$5(shape)) throw new Error("Invalid input to extend: expected a plain object");
277
+ if (!isPlainObject$7(shape)) throw new Error("Invalid input to extend: expected a plain object");
278
278
  const checks = schema._zod.def.checks;
279
279
  if (checks && checks.length > 0) {
280
280
  const existingShape = schema._zod.def.shape;
@@ -290,7 +290,7 @@ function extend(schema, shape) {
290
290
  } }));
291
291
  }
292
292
  function safeExtend(schema, shape) {
293
- if (!isPlainObject$5(shape)) throw new Error("Invalid input to safeExtend: expected a plain object");
293
+ if (!isPlainObject$7(shape)) throw new Error("Invalid input to safeExtend: expected a plain object");
294
294
  return clone(schema, mergeDefs(schema._zod.def, { get shape() {
295
295
  const _shape = {
296
296
  ...schema._zod.def.shape,
@@ -1904,7 +1904,7 @@ function mergeValues$1(a, b) {
1904
1904
  valid: true,
1905
1905
  data: a
1906
1906
  };
1907
- if (isPlainObject$5(a) && isPlainObject$5(b)) {
1907
+ if (isPlainObject$7(a) && isPlainObject$7(b)) {
1908
1908
  const bKeys = Object.keys(b);
1909
1909
  const sharedKeys = Object.keys(a).filter((key) => bKeys.indexOf(key) !== -1);
1910
1910
  const newObj = {
@@ -1980,7 +1980,7 @@ const $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
1980
1980
  $ZodType.init(inst, def);
1981
1981
  inst._zod.parse = (payload, ctx) => {
1982
1982
  const input = payload.value;
1983
- if (!isPlainObject$5(input)) {
1983
+ if (!isPlainObject$7(input)) {
1984
1984
  payload.issues.push({
1985
1985
  expected: "record",
1986
1986
  code: "invalid_type",
@@ -9619,7 +9619,7 @@ const { program, createCommand, createArgument, createOption, CommanderError, In
9619
9619
  })))(), 1)).default;
9620
9620
  //#endregion
9621
9621
  //#region package.json
9622
- var version = "0.9.0";
9622
+ var version = "0.11.0";
9623
9623
  //#endregion
9624
9624
  //#region node_modules/.pnpm/pkce-challenge@5.0.1/node_modules/pkce-challenge/dist/index.node.js
9625
9625
  let crypto;
@@ -19049,6 +19049,34 @@ const capletHttpApiSchema = object$1({
19049
19049
  "headers"
19050
19050
  ]);
19051
19051
  });
19052
+ const capletCliToolOutputSchema = object$1({ type: _enum(["text", "json"]).optional() }).strict();
19053
+ const capletCliToolAnnotationsSchema = object$1({
19054
+ readOnlyHint: boolean().optional(),
19055
+ destructiveHint: boolean().optional(),
19056
+ idempotentHint: boolean().optional(),
19057
+ openWorldHint: boolean().optional()
19058
+ }).strict();
19059
+ const capletCliToolActionSchema = object$1({
19060
+ description: string().min(1).optional().describe("Action capability description."),
19061
+ inputSchema: record(string(), unknown()).optional().describe("JSON Schema for call_tool arguments."),
19062
+ outputSchema: record(string(), unknown()).optional().describe("JSON Schema for structuredContent returned by this action."),
19063
+ command: string().min(1).describe("Executable command to spawn without a shell."),
19064
+ args: array(string()).optional().describe("Arguments passed to the command."),
19065
+ env: record(string(), string()).optional().describe("Additional environment variables."),
19066
+ cwd: string().min(1).optional().describe("Working directory for this action."),
19067
+ timeoutMs: number$1().int().positive().optional(),
19068
+ maxOutputBytes: number$1().int().positive().optional(),
19069
+ output: capletCliToolOutputSchema.optional(),
19070
+ annotations: capletCliToolAnnotationsSchema.optional()
19071
+ }).strict();
19072
+ const capletCliToolsSchema = object$1({
19073
+ actions: record(string().regex(SERVER_ID_PATTERN), capletCliToolActionSchema).refine((actions) => Object.keys(actions).length > 0, "CLI tools backend must define at least one action").describe("Configured CLI actions keyed by stable tool name."),
19074
+ cwd: string().min(1).optional().describe("Default working directory for CLI actions."),
19075
+ env: record(string(), string()).optional().describe("Default environment variables."),
19076
+ timeoutMs: number$1().int().positive().optional(),
19077
+ maxOutputBytes: number$1().int().positive().optional(),
19078
+ disabled: boolean().optional().describe("When true, omit this Caplet from discovery.")
19079
+ }).strict();
19052
19080
  const capletFileSchema = object$1({
19053
19081
  $schema: string().url().optional().describe("Optional JSON Schema URL for editor validation."),
19054
19082
  name: string().trim().min(1).max(80).describe("Human-readable Caplet display name."),
@@ -19057,11 +19085,12 @@ const capletFileSchema = object$1({
19057
19085
  mcpServer: capletMcpServerSchema.describe("MCP server backend configuration for this Caplet.").optional(),
19058
19086
  openapiEndpoint: capletOpenApiEndpointSchema.describe("OpenAPI endpoint backend configuration for this Caplet.").optional(),
19059
19087
  graphqlEndpoint: capletGraphQlEndpointSchema.describe("GraphQL endpoint backend configuration for this Caplet.").optional(),
19060
- httpApi: capletHttpApiSchema.describe("HTTP API backend configuration for this Caplet.").optional()
19088
+ httpApi: capletHttpApiSchema.describe("HTTP API backend configuration for this Caplet.").optional(),
19089
+ cliTools: capletCliToolsSchema.describe("CLI tools backend configuration for this Caplet.").optional()
19061
19090
  }).strict().superRefine((frontmatter, ctx) => {
19062
- if (Number(Boolean(frontmatter.mcpServer)) + Number(Boolean(frontmatter.openapiEndpoint)) + Number(Boolean(frontmatter.graphqlEndpoint)) + Number(Boolean(frontmatter.httpApi)) !== 1) ctx.addIssue({
19091
+ if (Number(Boolean(frontmatter.mcpServer)) + Number(Boolean(frontmatter.openapiEndpoint)) + Number(Boolean(frontmatter.graphqlEndpoint)) + Number(Boolean(frontmatter.httpApi)) + Number(Boolean(frontmatter.cliTools)) !== 1) ctx.addIssue({
19063
19092
  code: "custom",
19064
- message: "Caplet file must define exactly one backend: mcpServer, openapiEndpoint, graphqlEndpoint, or httpApi"
19093
+ message: "Caplet file must define exactly one backend: mcpServer, openapiEndpoint, graphqlEndpoint, httpApi, or cliTools"
19065
19094
  });
19066
19095
  });
19067
19096
  function loadCapletFiles(root) {
@@ -19070,29 +19099,35 @@ function loadCapletFiles(root) {
19070
19099
  const openapiEndpoints = {};
19071
19100
  const graphqlEndpoints = {};
19072
19101
  const httpApis = {};
19102
+ const cliTools = {};
19073
19103
  for (const candidate of discoverCapletFiles(root)) {
19074
- if (servers[candidate.id] || openapiEndpoints[candidate.id] || graphqlEndpoints[candidate.id] || httpApis[candidate.id]) throw new CapletsError("CONFIG_INVALID", `Duplicate Caplet ID ${candidate.id} under ${root}`);
19104
+ if (servers[candidate.id] || openapiEndpoints[candidate.id] || graphqlEndpoints[candidate.id] || httpApis[candidate.id] || cliTools[candidate.id]) throw new CapletsError("CONFIG_INVALID", `Duplicate Caplet ID ${candidate.id} under ${root}`);
19075
19105
  const config = readCapletFile(candidate.path);
19076
- if (isPlainObject$4(config) && config.backend === "openapi") {
19106
+ if (isPlainObject$6(config) && config.backend === "openapi") {
19077
19107
  const { backend: _backend, ...endpoint } = config;
19078
19108
  openapiEndpoints[candidate.id] = endpoint;
19079
- } else if (isPlainObject$4(config) && config.backend === "graphql") {
19109
+ } else if (isPlainObject$6(config) && config.backend === "graphql") {
19080
19110
  const { backend: _backend, ...endpoint } = config;
19081
19111
  graphqlEndpoints[candidate.id] = endpoint;
19082
- } else if (isPlainObject$4(config) && config.backend === "http") {
19112
+ } else if (isPlainObject$6(config) && config.backend === "http") {
19083
19113
  const { backend: _backend, ...endpoint } = config;
19084
19114
  httpApis[candidate.id] = endpoint;
19115
+ } else if (isPlainObject$6(config) && config.backend === "cli") {
19116
+ const { backend: _backend, ...endpoint } = config;
19117
+ cliTools[candidate.id] = endpoint;
19085
19118
  } else servers[candidate.id] = config;
19086
19119
  }
19087
19120
  const hasServers = Object.keys(servers).length > 0;
19088
19121
  const hasOpenApi = Object.keys(openapiEndpoints).length > 0;
19089
19122
  const hasGraphQl = Object.keys(graphqlEndpoints).length > 0;
19090
19123
  const hasHttpApis = Object.keys(httpApis).length > 0;
19091
- return hasServers || hasOpenApi || hasGraphQl || hasHttpApis ? {
19124
+ const hasCliTools = Object.keys(cliTools).length > 0;
19125
+ return hasServers || hasOpenApi || hasGraphQl || hasHttpApis || hasCliTools ? {
19092
19126
  ...hasServers ? { mcpServers: servers } : {},
19093
19127
  ...hasOpenApi ? { openapiEndpoints } : {},
19094
19128
  ...hasGraphQl ? { graphqlEndpoints } : {},
19095
- ...hasHttpApis ? { httpApis } : {}
19129
+ ...hasHttpApis ? { httpApis } : {},
19130
+ ...hasCliTools ? { cliTools } : {}
19096
19131
  } : void 0;
19097
19132
  }
19098
19133
  function discoverCapletFiles(root) {
@@ -19158,6 +19193,16 @@ function capletToServerConfig(frontmatter, body, baseDir) {
19158
19193
  ...frontmatter.tags ? { tags: frontmatter.tags } : {},
19159
19194
  body
19160
19195
  };
19196
+ if (frontmatter.cliTools) return {
19197
+ ...frontmatter.cliTools,
19198
+ cwd: normalizeLocalPath$1(frontmatter.cliTools.cwd, baseDir),
19199
+ actions: normalizeCliToolActions(frontmatter.cliTools.actions, baseDir),
19200
+ backend: "cli",
19201
+ name: frontmatter.name,
19202
+ description: frontmatter.description,
19203
+ ...frontmatter.tags ? { tags: frontmatter.tags } : {},
19204
+ body
19205
+ };
19161
19206
  return {
19162
19207
  ...frontmatter.mcpServer,
19163
19208
  name: frontmatter.name,
@@ -19166,6 +19211,12 @@ function capletToServerConfig(frontmatter, body, baseDir) {
19166
19211
  body
19167
19212
  };
19168
19213
  }
19214
+ function normalizeCliToolActions(actions, baseDir) {
19215
+ return Object.fromEntries(Object.entries(actions).map(([name, action]) => [name, {
19216
+ ...action,
19217
+ cwd: normalizeLocalPath$1(action.cwd, baseDir)
19218
+ }]));
19219
+ }
19169
19220
  function normalizeGraphQlOperations(operations, baseDir) {
19170
19221
  if (!operations) return;
19171
19222
  return Object.fromEntries(Object.entries(operations).map(([name, operation]) => [name, {
@@ -19200,7 +19251,7 @@ function parseFrontmatter(text, path) {
19200
19251
  value: text
19201
19252
  });
19202
19253
  matter(file, { strip: true });
19203
- if (!isPlainObject$4(file.data.matter) || Object.keys(file.data.matter).length === 0) throw new Error("empty frontmatter");
19254
+ if (!isPlainObject$6(file.data.matter) || Object.keys(file.data.matter).length === 0) throw new Error("empty frontmatter");
19204
19255
  return {
19205
19256
  frontmatter: file.data.matter,
19206
19257
  body: String(file)
@@ -19209,7 +19260,7 @@ function parseFrontmatter(text, path) {
19209
19260
  throw new CapletsError("CONFIG_INVALID", `Caplet file at ${path} has invalid YAML frontmatter`, redactSecrets(error));
19210
19261
  }
19211
19262
  }
19212
- function isPlainObject$4(value) {
19263
+ function isPlainObject$6(value) {
19213
19264
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
19214
19265
  }
19215
19266
  function validateCapletId(id, path) {
@@ -19386,6 +19437,7 @@ const httpActionSchema = object$1({
19386
19437
  path: string().min(1).regex(/^\//, "HTTP action path must start with /").describe("URL path appended to the HTTP API baseUrl.").refine((value) => !value.startsWith("//"), "HTTP action path must not start with //").refine((value) => !isUrl(value), "HTTP action path must be a URL path, not a URL"),
19387
19438
  description: string().min(1).optional().describe("Action capability description."),
19388
19439
  inputSchema: record(string(), unknown()).optional().describe("JSON Schema for call_tool arguments."),
19440
+ outputSchema: record(string(), unknown()).optional().describe("JSON Schema for structuredContent returned by this action."),
19389
19441
  query: httpScalarMappingSchema.optional().describe("Query parameter mapping."),
19390
19442
  headers: httpScalarMappingSchema.optional().describe("Request header mapping."),
19391
19443
  jsonBody: unknown().optional().describe("JSON request body mapping.")
@@ -19408,7 +19460,39 @@ const publicHttpApiSchema = object$1({
19408
19460
  disabled: boolean().default(false).describe("When true, omit this HTTP API Caplet.")
19409
19461
  }).strict();
19410
19462
  const normalizedHttpApiSchema = publicHttpApiSchema.extend({ body: string().optional() });
19411
- function configSchemaFor(serverValueSchema, openApiEndpointValueSchema, graphQlEndpointValueSchema, httpApiValueSchema) {
19463
+ const cliToolOutputSchema = object$1({ type: _enum(["text", "json"]).default("text").describe("How stdout should be represented in structuredContent.") }).strict();
19464
+ const cliToolAnnotationsSchema = object$1({
19465
+ readOnlyHint: boolean().optional(),
19466
+ destructiveHint: boolean().optional(),
19467
+ idempotentHint: boolean().optional(),
19468
+ openWorldHint: boolean().optional()
19469
+ }).strict();
19470
+ const cliToolActionSchema = object$1({
19471
+ description: string().min(1).optional().describe("Action capability description."),
19472
+ inputSchema: record(string(), unknown()).optional().describe("JSON Schema for call_tool arguments."),
19473
+ outputSchema: record(string(), unknown()).optional().describe("JSON Schema for structuredContent returned by this action."),
19474
+ command: string().min(1).describe("Executable command to spawn without a shell."),
19475
+ args: array(string()).optional().describe("Arguments passed to the command."),
19476
+ env: record(string(), string()).optional().describe("Additional environment variables for the command."),
19477
+ cwd: string().min(1).optional().describe("Working directory for this action."),
19478
+ timeoutMs: number$1().int().positive().optional().describe("Command timeout in milliseconds."),
19479
+ maxOutputBytes: number$1().int().positive().optional().describe("Maximum combined stdout and stderr bytes to keep."),
19480
+ output: cliToolOutputSchema.optional(),
19481
+ annotations: cliToolAnnotationsSchema.optional()
19482
+ }).strict();
19483
+ const publicCliToolsSchema = object$1({
19484
+ name: string().trim().min(1).max(80).describe("Human-readable CLI tools display name."),
19485
+ description: string().describe("Capability description shown to agents before CLI actions are disclosed.").refine((value) => value.trim().length >= 10, "description must contain at least 10 non-whitespace characters").refine((value) => value.length <= 1500, "description must be at most 1500 characters"),
19486
+ actions: record(string().regex(SERVER_ID_PATTERN), cliToolActionSchema).refine((actions) => Object.keys(actions).length > 0, "CLI tools backend must define at least one action").describe("Configured CLI actions keyed by stable tool name."),
19487
+ cwd: string().min(1).optional().describe("Default working directory for CLI actions."),
19488
+ env: record(string(), string()).optional().describe("Default environment variables for CLI actions."),
19489
+ tags: array(string().trim().min(1).max(80)).optional(),
19490
+ timeoutMs: number$1().int().positive().default(6e4).describe("Default timeout in milliseconds for CLI actions."),
19491
+ maxOutputBytes: number$1().int().positive().default(1e6).describe("Default maximum combined stdout and stderr bytes to keep."),
19492
+ disabled: boolean().default(false).describe("When true, omit this CLI tools Caplet.")
19493
+ }).strict();
19494
+ const normalizedCliToolsSchema = publicCliToolsSchema.extend({ body: string().optional() });
19495
+ function configSchemaFor(serverValueSchema, openApiEndpointValueSchema, graphQlEndpointValueSchema, httpApiValueSchema, cliToolsValueSchema) {
19412
19496
  return object$1({
19413
19497
  $schema: string().url().optional().describe("Optional JSON Schema URL for editor validation."),
19414
19498
  version: literal(1).default(1).describe("Caplets config schema version."),
@@ -19417,7 +19501,8 @@ function configSchemaFor(serverValueSchema, openApiEndpointValueSchema, graphQlE
19417
19501
  mcpServers: record(string().regex(SERVER_ID_PATTERN), serverValueSchema).default({}).describe("Downstream MCP servers keyed by stable server ID."),
19418
19502
  openapiEndpoints: record(string().regex(SERVER_ID_PATTERN), openApiEndpointValueSchema).default({}).describe("OpenAPI endpoints keyed by stable Caplet ID."),
19419
19503
  graphqlEndpoints: record(string().regex(SERVER_ID_PATTERN), graphQlEndpointValueSchema).default({}).describe("GraphQL endpoints keyed by stable Caplet ID."),
19420
- httpApis: record(string().regex(SERVER_ID_PATTERN), httpApiValueSchema).default({}).describe("HTTP APIs keyed by stable Caplet ID.")
19504
+ httpApis: record(string().regex(SERVER_ID_PATTERN), httpApiValueSchema).default({}).describe("HTTP APIs keyed by stable Caplet ID."),
19505
+ cliTools: record(string().regex(SERVER_ID_PATTERN), cliToolsValueSchema).default({}).describe("CLI tools keyed by stable Caplet ID.")
19421
19506
  }).strict().superRefine((config, ctx) => {
19422
19507
  if (config.defaultSearchLimit > config.maxSearchLimit) ctx.addIssue({
19423
19508
  code: "custom",
@@ -19606,10 +19691,34 @@ function configSchemaFor(serverValueSchema, openApiEndpointValueSchema, graphQlE
19606
19691
  "headers"
19607
19692
  ]);
19608
19693
  }
19694
+ for (const [server, rawValue] of Object.entries(config.cliTools)) {
19695
+ const raw = rawValue;
19696
+ const duplicateBackend = config.mcpServers[server] ? "mcpServers" : config.openapiEndpoints[server] ? "openapiEndpoints" : config.graphqlEndpoints[server] ? "graphqlEndpoints" : config.httpApis[server] ? "httpApis" : void 0;
19697
+ if (duplicateBackend) ctx.addIssue({
19698
+ code: "custom",
19699
+ path: ["cliTools", server],
19700
+ message: `Caplet ID ${server} is already used by ${duplicateBackend}`
19701
+ });
19702
+ if (!SERVER_ID_PATTERN.test(server)) ctx.addIssue({
19703
+ code: "custom",
19704
+ path: ["cliTools", server],
19705
+ message: "CLI tools ID must match ^[a-zA-Z0-9_-]{1,64}$"
19706
+ });
19707
+ for (const actionName of Object.keys(raw.actions)) if (!SERVER_ID_PATTERN.test(actionName)) ctx.addIssue({
19708
+ code: "custom",
19709
+ path: [
19710
+ "cliTools",
19711
+ server,
19712
+ "actions",
19713
+ actionName
19714
+ ],
19715
+ message: "CLI action ID must match ^[a-zA-Z0-9_-]{1,64}$"
19716
+ });
19717
+ }
19609
19718
  });
19610
19719
  }
19611
- const configFileSchema = configSchemaFor(publicServerSchema, publicOpenApiEndpointSchema, publicGraphQlEndpointSchema, publicHttpApiSchema);
19612
- const normalizedConfigFileSchema = configSchemaFor(normalizedServerSchema, normalizedOpenApiEndpointSchema, normalizedGraphQlEndpointSchema, normalizedHttpApiSchema);
19720
+ const configFileSchema = configSchemaFor(publicServerSchema, publicOpenApiEndpointSchema, publicGraphQlEndpointSchema, publicHttpApiSchema, publicCliToolsSchema);
19721
+ const normalizedConfigFileSchema = configSchemaFor(normalizedServerSchema, normalizedOpenApiEndpointSchema, normalizedGraphQlEndpointSchema, normalizedHttpApiSchema, normalizedCliToolsSchema);
19613
19722
  function loadConfig(path = resolveConfigPath(), projectPath = resolveProjectConfigPath()) {
19614
19723
  const hasUserConfig = existsSync(path);
19615
19724
  const hasProjectConfig = existsSync(projectPath);
@@ -19620,7 +19729,7 @@ function loadConfig(path = resolveConfigPath(), projectPath = resolveProjectConf
19620
19729
  if (!hasUserConfig && !hasProjectConfig && !userCaplets && !projectCaplets) throw new CapletsError("CONFIG_NOT_FOUND", `Caplets config not found at ${path} or ${projectPath}`);
19621
19730
  try {
19622
19731
  const config = parseConfig(mergeConfigInputs(userConfig, userCaplets, projectConfig, projectCaplets));
19623
- if (Object.keys(config.mcpServers).length === 0 && Object.keys(config.openapiEndpoints).length === 0 && Object.keys(config.graphqlEndpoints).length === 0 && Object.keys(config.httpApis).length === 0) throw new CapletsError("CONFIG_INVALID", "Caplets config must define at least one MCP server, OpenAPI endpoint, GraphQL endpoint, or HTTP API");
19732
+ if (Object.keys(config.mcpServers).length === 0 && Object.keys(config.openapiEndpoints).length === 0 && Object.keys(config.graphqlEndpoints).length === 0 && Object.keys(config.httpApis).length === 0 && Object.keys(config.cliTools).length === 0) throw new CapletsError("CONFIG_INVALID", "Caplets config must define at least one MCP server, OpenAPI endpoint, GraphQL endpoint, HTTP API, or CLI tools backend");
19624
19733
  return config;
19625
19734
  } catch (error) {
19626
19735
  if (error instanceof CapletsError) throw error;
@@ -19645,12 +19754,13 @@ function normalizeLocalPaths(input, baseDir) {
19645
19754
  return stripUndefined({
19646
19755
  ...input,
19647
19756
  openapiEndpoints: normalizeEndpointPaths(input.openapiEndpoints, baseDir, normalizeOpenApiPath),
19648
- graphqlEndpoints: normalizeEndpointPaths(input.graphqlEndpoints, baseDir, normalizeGraphQlPath)
19757
+ graphqlEndpoints: normalizeEndpointPaths(input.graphqlEndpoints, baseDir, normalizeGraphQlPath),
19758
+ cliTools: normalizeEndpointPaths(input.cliTools, baseDir, normalizeCliToolsPaths)
19649
19759
  });
19650
19760
  }
19651
19761
  function normalizeEndpointPaths(endpoints, baseDir, normalize) {
19652
19762
  if (!endpoints) return;
19653
- return Object.fromEntries(Object.entries(endpoints).map(([id, endpoint]) => [id, isPlainObject$3(endpoint) ? normalize(endpoint, baseDir) : endpoint]));
19763
+ return Object.fromEntries(Object.entries(endpoints).map(([id, endpoint]) => [id, isPlainObject$5(endpoint) ? normalize(endpoint, baseDir) : endpoint]));
19654
19764
  }
19655
19765
  function normalizeOpenApiPath(endpoint, baseDir) {
19656
19766
  return {
@@ -19659,7 +19769,7 @@ function normalizeOpenApiPath(endpoint, baseDir) {
19659
19769
  };
19660
19770
  }
19661
19771
  function normalizeGraphQlPath(endpoint, baseDir) {
19662
- const operations = isPlainObject$3(endpoint.operations) ? Object.fromEntries(Object.entries(endpoint.operations).map(([name, operation]) => [name, isPlainObject$3(operation) ? {
19772
+ const operations = isPlainObject$5(endpoint.operations) ? Object.fromEntries(Object.entries(endpoint.operations).map(([name, operation]) => [name, isPlainObject$5(operation) ? {
19663
19773
  ...operation,
19664
19774
  documentPath: normalizeLocalPath(operation.documentPath, baseDir)
19665
19775
  } : operation])) : endpoint.operations;
@@ -19669,6 +19779,17 @@ function normalizeGraphQlPath(endpoint, baseDir) {
19669
19779
  operations
19670
19780
  };
19671
19781
  }
19782
+ function normalizeCliToolsPaths(endpoint, baseDir) {
19783
+ const actions = isPlainObject$5(endpoint.actions) ? Object.fromEntries(Object.entries(endpoint.actions).map(([name, action]) => [name, isPlainObject$5(action) ? {
19784
+ ...action,
19785
+ cwd: normalizeLocalPath(action.cwd, baseDir)
19786
+ } : action])) : endpoint.actions;
19787
+ return {
19788
+ ...endpoint,
19789
+ cwd: normalizeLocalPath(endpoint.cwd, baseDir),
19790
+ actions
19791
+ };
19792
+ }
19672
19793
  function normalizeLocalPath(value, baseDir) {
19673
19794
  if (typeof value !== "string" || !value || isAbsolute(value) || hasEnvReference(value)) return value;
19674
19795
  return join(baseDir, value);
@@ -19677,6 +19798,7 @@ function rejectUntrustedProjectExecutableBackends(input, path) {
19677
19798
  if (input.openapiEndpoints && Object.keys(input.openapiEndpoints).length > 0) throw new CapletsError("CONFIG_INVALID", `Project config at ${path} cannot define openapiEndpoints; use trusted project Caplet files or user config`);
19678
19799
  if (input.graphqlEndpoints && Object.keys(input.graphqlEndpoints).length > 0) throw new CapletsError("CONFIG_INVALID", `Project config at ${path} cannot define graphqlEndpoints; use trusted project Caplet files or user config`);
19679
19800
  if (input.httpApis && Object.keys(input.httpApis).length > 0) throw new CapletsError("CONFIG_INVALID", `Project config at ${path} cannot define httpApis; use trusted project Caplet files or user config`);
19801
+ if (input.cliTools && Object.keys(input.cliTools).length > 0) throw new CapletsError("CONFIG_INVALID", `Project config at ${path} cannot define cliTools; use trusted project Caplet files or user config`);
19680
19802
  return input;
19681
19803
  }
19682
19804
  function mergeConfigInputs(...inputs) {
@@ -19701,6 +19823,10 @@ function mergeConfigInputs(...inputs) {
19701
19823
  httpApis: {
19702
19824
  ...merged?.httpApis,
19703
19825
  ...input.httpApis
19826
+ },
19827
+ cliTools: {
19828
+ ...merged?.cliTools,
19829
+ ...input.cliTools
19704
19830
  }
19705
19831
  };
19706
19832
  }
@@ -19737,6 +19863,12 @@ function parseConfig(input) {
19737
19863
  server,
19738
19864
  backend: "http"
19739
19865
  });
19866
+ const cliTools = {};
19867
+ for (const [server, raw] of Object.entries(parsed.data.cliTools)) cliTools[server] = stripUndefined({
19868
+ ...raw,
19869
+ server,
19870
+ backend: "cli"
19871
+ });
19740
19872
  return {
19741
19873
  version: parsed.data.version,
19742
19874
  options: {
@@ -19746,7 +19878,8 @@ function parseConfig(input) {
19746
19878
  mcpServers: servers,
19747
19879
  openapiEndpoints,
19748
19880
  graphqlEndpoints,
19749
- httpApis
19881
+ httpApis,
19882
+ cliTools
19750
19883
  };
19751
19884
  }
19752
19885
  function validateEndpointAuthHeaders(auth, ctx, path) {
@@ -19775,10 +19908,10 @@ function interpolateConfig(value, path = []) {
19775
19908
  return value;
19776
19909
  }
19777
19910
  function isPublicMetadataPath(path) {
19778
- if (path.length < 3 || path[0] !== "mcpServers" && path[0] !== "openapiEndpoints" && path[0] !== "graphqlEndpoints" && path[0] !== "httpApis") return false;
19911
+ if (path.length < 3 || path[0] !== "mcpServers" && path[0] !== "openapiEndpoints" && path[0] !== "graphqlEndpoints" && path[0] !== "httpApis" && path[0] !== "cliTools") return false;
19779
19912
  return NON_INTERPOLATED_SERVER_FIELDS.has(path[2] ?? "");
19780
19913
  }
19781
- function isPlainObject$3(value) {
19914
+ function isPlainObject$5(value) {
19782
19915
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
19783
19916
  }
19784
19917
  function hasEnvReference(value) {
@@ -19867,6 +20000,194 @@ async function maybeReadManualInput() {
19867
20000
  }
19868
20001
  }
19869
20002
  //#endregion
20003
+ //#region src/cli/author.ts
20004
+ function authorCliCaplet(id, options = {}) {
20005
+ const repo = resolve(options.repo ?? process.cwd());
20006
+ const include = options.include !== void 0 ? parseInclude(options.include) : options.command ? /* @__PURE__ */ new Set() : new Set([
20007
+ "git",
20008
+ "gh",
20009
+ "package"
20010
+ ]);
20011
+ const actions = {};
20012
+ if (include.has("git") || options.command === "git") Object.assign(actions, gitActions(repo));
20013
+ if (include.has("gh") || options.command === "gh") Object.assign(actions, ghActions(repo));
20014
+ if (include.has("package") || isPackageCommand(options.command)) Object.assign(actions, packageActions(repo, options.command));
20015
+ if (options.command && Object.keys(actions).length === 0) actions[`${sanitizeId(options.command)}_version`] = {
20016
+ description: `Print ${options.command} version information.`,
20017
+ command: options.command,
20018
+ args: ["--version"],
20019
+ annotations: { readOnlyHint: true }
20020
+ };
20021
+ if (Object.keys(actions).length === 0) throw new Error("No CLI actions could be generated for the requested options");
20022
+ const text = renderCaplet({
20023
+ name: titleize(id),
20024
+ description: `Curated CLI tools for ${basename(repo)} workflows.`,
20025
+ cwd: repo,
20026
+ actions
20027
+ });
20028
+ const output = options.output ?? "-";
20029
+ if (output !== "-") {
20030
+ writeFileSync(output, text);
20031
+ return {
20032
+ path: output,
20033
+ text
20034
+ };
20035
+ }
20036
+ return { text };
20037
+ }
20038
+ function parseInclude(value) {
20039
+ if (!value) return /* @__PURE__ */ new Set();
20040
+ return new Set(value.split(",").map((entry) => entry.trim()).filter(Boolean));
20041
+ }
20042
+ function gitActions(repo) {
20043
+ return {
20044
+ git_status: {
20045
+ description: "Show concise Git working tree status.",
20046
+ command: "git",
20047
+ args: ["status", "--short"],
20048
+ cwd: repo,
20049
+ annotations: { readOnlyHint: true }
20050
+ },
20051
+ git_current_branch: {
20052
+ description: "Print the current Git branch name.",
20053
+ command: "git",
20054
+ args: ["branch", "--show-current"],
20055
+ cwd: repo,
20056
+ annotations: { readOnlyHint: true }
20057
+ },
20058
+ git_changed_files: {
20059
+ description: "List changed tracked and untracked files.",
20060
+ command: "git",
20061
+ args: [
20062
+ "status",
20063
+ "--porcelain=v1",
20064
+ "--untracked-files=all"
20065
+ ],
20066
+ cwd: repo,
20067
+ annotations: { readOnlyHint: true }
20068
+ }
20069
+ };
20070
+ }
20071
+ function ghActions(repo) {
20072
+ return {
20073
+ gh_pr_status: {
20074
+ description: "Show pull request status for the current branch as JSON.",
20075
+ command: "gh",
20076
+ args: [
20077
+ "pr",
20078
+ "status",
20079
+ "--json",
20080
+ "currentBranch"
20081
+ ],
20082
+ cwd: repo,
20083
+ output: { type: "json" },
20084
+ annotations: {
20085
+ readOnlyHint: true,
20086
+ openWorldHint: true
20087
+ }
20088
+ },
20089
+ gh_issue_list: {
20090
+ description: "List open GitHub issues as JSON.",
20091
+ command: "gh",
20092
+ args: [
20093
+ "issue",
20094
+ "list",
20095
+ "--json",
20096
+ "number,title,state,url"
20097
+ ],
20098
+ cwd: repo,
20099
+ output: { type: "json" },
20100
+ annotations: {
20101
+ readOnlyHint: true,
20102
+ openWorldHint: true
20103
+ }
20104
+ }
20105
+ };
20106
+ }
20107
+ function packageActions(repo, explicitCommand) {
20108
+ const packageJsonPath = join(repo, "package.json");
20109
+ if (!existsSync(packageJsonPath)) return {};
20110
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
20111
+ const manager = explicitCommand && isPackageCommand(explicitCommand) ? explicitCommand : detectPackageManager(repo, packageJson);
20112
+ const scripts = packageJson.scripts ?? {};
20113
+ const actions = {};
20114
+ for (const script of [
20115
+ "test",
20116
+ "lint",
20117
+ "typecheck",
20118
+ "build",
20119
+ "verify"
20120
+ ]) {
20121
+ if (!scripts[script]) continue;
20122
+ actions[`package_${script}`] = {
20123
+ description: `Run the package ${script} script.`,
20124
+ command: manager,
20125
+ args: ["run", script],
20126
+ cwd: repo,
20127
+ annotations: { readOnlyHint: false },
20128
+ ...script === "test" || script === "verify" ? { timeoutMs: 12e4 } : {}
20129
+ };
20130
+ }
20131
+ return actions;
20132
+ }
20133
+ function detectPackageManager(repo, packageJson) {
20134
+ if (packageJson.packageManager?.startsWith("pnpm@")) return "pnpm";
20135
+ if (packageJson.packageManager?.startsWith("bun@")) return "bun";
20136
+ if (packageJson.packageManager?.startsWith("yarn@")) return "yarn";
20137
+ if (existsSync(join(repo, "pnpm-lock.yaml"))) return "pnpm";
20138
+ if (existsSync(join(repo, "bun.lockb")) || existsSync(join(repo, "bun.lock"))) return "bun";
20139
+ if (existsSync(join(repo, "yarn.lock"))) return "yarn";
20140
+ return "npm";
20141
+ }
20142
+ function isPackageCommand(command) {
20143
+ return command === "pnpm" || command === "npm" || command === "bun" || command === "yarn";
20144
+ }
20145
+ function renderCaplet(input) {
20146
+ const lines = [
20147
+ "---",
20148
+ "$schema: https://raw.githubusercontent.com/spiritledsoftware/caplets/main/schemas/caplet.schema.json",
20149
+ `name: ${yamlString(input.name)}`,
20150
+ `description: ${yamlString(input.description)}`,
20151
+ "tags:",
20152
+ " - cli",
20153
+ " - code",
20154
+ "cliTools:",
20155
+ ` cwd: ${yamlString(input.cwd)}`,
20156
+ " actions:"
20157
+ ];
20158
+ for (const [name, action] of Object.entries(input.actions).sort(([left], [right]) => left.localeCompare(right))) {
20159
+ lines.push(` ${name}:`);
20160
+ if (action.description) lines.push(` description: ${yamlString(action.description)}`);
20161
+ lines.push(` command: ${yamlString(action.command)}`);
20162
+ if (action.args?.length) {
20163
+ lines.push(" args:");
20164
+ for (const arg of action.args) lines.push(` - ${yamlString(arg)}`);
20165
+ }
20166
+ if (action.cwd && action.cwd !== input.cwd) lines.push(` cwd: ${yamlString(action.cwd)}`);
20167
+ if (action.timeoutMs) lines.push(` timeoutMs: ${action.timeoutMs}`);
20168
+ if (action.maxOutputBytes) lines.push(` maxOutputBytes: ${action.maxOutputBytes}`);
20169
+ if (action.output) {
20170
+ lines.push(" output:");
20171
+ lines.push(` type: ${action.output.type}`);
20172
+ }
20173
+ if (action.annotations) {
20174
+ lines.push(" annotations:");
20175
+ for (const [key, value] of Object.entries(action.annotations)) lines.push(` ${key}: ${value ? "true" : "false"}`);
20176
+ }
20177
+ }
20178
+ lines.push("---", "", `# ${input.name}`, "", input.description, "");
20179
+ return `${lines.join("\n")}`;
20180
+ }
20181
+ function yamlString(value) {
20182
+ return JSON.stringify(value);
20183
+ }
20184
+ function titleize(id) {
20185
+ return id.split(/[-_]/).filter(Boolean).map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`).join(" ");
20186
+ }
20187
+ function sanitizeId(value) {
20188
+ return value.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 48) || "cli";
20189
+ }
20190
+ //#endregion
19870
20191
  //#region src/cli/init.ts
19871
20192
  function initConfig(options = {}) {
19872
20193
  const path = resolveConfigPath(options.path);
@@ -19918,7 +20239,9 @@ function allCaplets$1(config) {
19918
20239
  return [
19919
20240
  ...Object.values(config.mcpServers),
19920
20241
  ...Object.values(config.openapiEndpoints),
19921
- ...Object.values(config.graphqlEndpoints)
20242
+ ...Object.values(config.graphqlEndpoints),
20243
+ ...Object.values(config.httpApis),
20244
+ ...Object.values(config.cliTools)
19922
20245
  ];
19923
20246
  }
19924
20247
  function formatCapletList(rows) {
@@ -20158,6 +20481,14 @@ function createProgram(io = {}) {
20158
20481
  });
20159
20482
  for (const caplet of result.installed) writeOut(`Installed ${caplet.id} to ${caplet.destination}\n`);
20160
20483
  });
20484
+ program.command("author").description("Generate reviewable Caplet files.").command("cli").description("Generate a CLI tools Caplet.").argument("<id>", "Caplet ID/display seed").option("--repo <path>", "repository path to inspect").option("--include <items>", "comma-separated generators to include: git,gh,package").option("--command <name>", "single CLI command template to generate").option("--output <path>", "output path, or - for stdout", "-").action((id, options) => {
20485
+ const result = authorCliCaplet(id, options);
20486
+ if (result.path) {
20487
+ writeOut(`Wrote CLI Caplet to ${result.path}\n`);
20488
+ return;
20489
+ }
20490
+ writeOut(result.text);
20491
+ });
20161
20492
  const config = program.command("config").description("Inspect Caplets config locations.");
20162
20493
  config.command("path").description("Print the effective user config path.").action(() => {
20163
20494
  writeOut(`${resolveConfigPath(envConfigPath())}\n`);
@@ -25864,7 +26195,7 @@ var Protocol = class {
25864
26195
  };
25865
26196
  }
25866
26197
  };
25867
- function isPlainObject$2(value) {
26198
+ function isPlainObject$4(value) {
25868
26199
  return value !== null && typeof value === "object" && !Array.isArray(value);
25869
26200
  }
25870
26201
  function mergeCapabilities(base, additional) {
@@ -25874,7 +26205,7 @@ function mergeCapabilities(base, additional) {
25874
26205
  const addValue = additional[k];
25875
26206
  if (addValue === void 0) continue;
25876
26207
  const baseValue = result[k];
25877
- if (isPlainObject$2(baseValue) && isPlainObject$2(addValue)) result[k] = {
26208
+ if (isPlainObject$4(baseValue) && isPlainObject$4(addValue)) result[k] = {
25878
26209
  ...baseValue,
25879
26210
  ...addValue
25880
26211
  };
@@ -33525,6 +33856,273 @@ const EMPTY_COMPLETION_RESULT = { completion: {
33525
33856
  hasMore: false
33526
33857
  } };
33527
33858
  //#endregion
33859
+ //#region src/cli-tools.ts
33860
+ const DEFAULT_INPUT_SCHEMA$1 = {
33861
+ type: "object",
33862
+ additionalProperties: true
33863
+ };
33864
+ var CliToolsManager = class {
33865
+ registry;
33866
+ constructor(registry) {
33867
+ this.registry = registry;
33868
+ }
33869
+ updateRegistry(registry) {
33870
+ this.registry = registry;
33871
+ }
33872
+ invalidate(_serverId) {}
33873
+ async checkTools(config) {
33874
+ const startedAt = Date.now();
33875
+ try {
33876
+ for (const action of actionsFor(config)) {
33877
+ const cwdTemplate = action.cwd ?? config.cwd;
33878
+ if (cwdTemplate && !cwdTemplate.includes("$input")) {
33879
+ if (!existsSync(interpolateRequiredString(cwdTemplate, {}, "cwd"))) throw new CapletsError("CONFIG_INVALID", `CLI cwd does not exist for ${config.server}/${action.name}`);
33880
+ }
33881
+ if (!action.command.includes("$input")) resolveCommandPath(action.command);
33882
+ }
33883
+ this.registry.setStatus(config.server, "available");
33884
+ return {
33885
+ server: config.server,
33886
+ status: "available",
33887
+ toolCount: Object.keys(config.actions).length,
33888
+ elapsedMs: Date.now() - startedAt
33889
+ };
33890
+ } catch (error) {
33891
+ const safe = toSafeError(error, "SERVER_UNAVAILABLE");
33892
+ this.registry.setStatus(config.server, "unavailable", safe);
33893
+ return {
33894
+ server: config.server,
33895
+ status: "unavailable",
33896
+ elapsedMs: Date.now() - startedAt,
33897
+ error: safe
33898
+ };
33899
+ }
33900
+ }
33901
+ async listTools(config) {
33902
+ return actionsFor(config).map((action) => this.toTool(action));
33903
+ }
33904
+ async getTool(config, toolName) {
33905
+ return this.toTool(getAction(config, toolName));
33906
+ }
33907
+ async callTool(config, toolName, args) {
33908
+ const action = getAction(config, toolName);
33909
+ validateInput(action, args);
33910
+ const execution = resolveExecution(config, action, args);
33911
+ const startedAt = Date.now();
33912
+ const controller = new AbortController();
33913
+ const timeout = setTimeout(() => controller.abort(), execution.timeoutMs);
33914
+ try {
33915
+ const result = await spawnCommand(execution, controller.signal, () => Date.now() - startedAt);
33916
+ const structured = parseStructuredResult(action, result, result.exitCode !== 0);
33917
+ return {
33918
+ content: [{
33919
+ type: "text",
33920
+ text: JSON.stringify(structured, null, 2)
33921
+ }],
33922
+ structuredContent: structured,
33923
+ isError: result.exitCode !== 0
33924
+ };
33925
+ } catch (error) {
33926
+ if (isAbortError$1(error)) throw new CapletsError("TOOL_CALL_TIMEOUT", `CLI tool timed out for ${config.server}/${toolName}`);
33927
+ if (error instanceof CapletsError) throw error;
33928
+ throw new CapletsError("DOWNSTREAM_TOOL_ERROR", `CLI tool failed for ${config.server}/${toolName}`, toSafeError(error));
33929
+ } finally {
33930
+ clearTimeout(timeout);
33931
+ }
33932
+ }
33933
+ compact(config, tool) {
33934
+ return {
33935
+ server: config.server,
33936
+ tool: tool.name,
33937
+ ...tool.description ? { description: tool.description } : {},
33938
+ ...tool.annotations ? { annotations: tool.annotations } : {},
33939
+ hasInputSchema: Boolean(tool.inputSchema),
33940
+ hasOutputSchema: Boolean(tool.outputSchema)
33941
+ };
33942
+ }
33943
+ search(config, tools, query, limit) {
33944
+ const needle = query.toLocaleLowerCase();
33945
+ return tools.filter((tool) => `${tool.name}\n${tool.description ?? ""}`.toLocaleLowerCase().includes(needle)).sort((left, right) => left.name.localeCompare(right.name)).slice(0, limit).map((tool) => this.compact(config, tool));
33946
+ }
33947
+ toTool(action) {
33948
+ return {
33949
+ name: action.name,
33950
+ ...action.description ? { description: action.description } : {},
33951
+ inputSchema: action.inputSchema ?? DEFAULT_INPUT_SCHEMA$1,
33952
+ ...action.outputSchema ? { outputSchema: action.outputSchema } : {},
33953
+ ...action.annotations ? { annotations: action.annotations } : {}
33954
+ };
33955
+ }
33956
+ };
33957
+ function actionsFor(config) {
33958
+ return Object.entries(config.actions).map(([name, action]) => ({
33959
+ name,
33960
+ ...action
33961
+ })).sort((left, right) => left.name.localeCompare(right.name));
33962
+ }
33963
+ function getAction(config, toolName) {
33964
+ const actions = actionsFor(config);
33965
+ const action = actions.find((candidate) => candidate.name === toolName);
33966
+ if (!action) throw new CapletsError("TOOL_NOT_FOUND", `Tool ${toolName} was not found on ${config.server}`, {
33967
+ server: config.server,
33968
+ tool: toolName,
33969
+ suggestions: actions.map((candidate) => candidate.name).filter((name) => name.toLocaleLowerCase().includes(toolName.toLocaleLowerCase()[0] ?? "")).slice(0, 5)
33970
+ });
33971
+ return action;
33972
+ }
33973
+ function resolveExecution(config, action, input) {
33974
+ const cwd = interpolateString(action.cwd ?? config.cwd, input, "cwd");
33975
+ if (cwd && !existsSync(cwd)) throw new CapletsError("CONFIG_INVALID", `CLI cwd does not exist for ${config.server}/${action.name}`);
33976
+ const env = {
33977
+ ...process.env,
33978
+ ...resolveEnv(config.env, input),
33979
+ ...resolveEnv(action.env, input)
33980
+ };
33981
+ return {
33982
+ command: interpolateString(action.command, input, "command") ?? action.command,
33983
+ args: (action.args ?? []).map((arg, index) => interpolateRequiredString(arg, input, `args.${index}`)),
33984
+ ...cwd ? { cwd } : {},
33985
+ env,
33986
+ timeoutMs: action.timeoutMs ?? config.timeoutMs,
33987
+ maxOutputBytes: action.maxOutputBytes ?? config.maxOutputBytes
33988
+ };
33989
+ }
33990
+ function resolveEnv(env, input) {
33991
+ if (!env) return {};
33992
+ return Object.fromEntries(Object.entries(env).map(([key, value]) => [key, interpolateRequiredString(value, input, `env.${key}`)]));
33993
+ }
33994
+ function interpolateString(value, input, field) {
33995
+ return value === void 0 ? void 0 : interpolateRequiredString(value, input, field);
33996
+ }
33997
+ function interpolateRequiredString(value, input, field) {
33998
+ return value.replace(/\$input(?:\.([A-Za-z0-9_.-]+))?/g, (_match, path) => {
33999
+ if (!path) throw new CapletsError("REQUEST_INVALID", `CLI ${field} cannot interpolate $input directly`);
34000
+ const selected = valueAtPath$1(input, path);
34001
+ if (selected === void 0 || selected === null) throw new CapletsError("REQUEST_INVALID", `CLI ${field} references missing input ${path}`);
34002
+ if (typeof selected !== "string" && typeof selected !== "number" && typeof selected !== "boolean") throw new CapletsError("REQUEST_INVALID", `CLI ${field} input ${path} must be a string, number, or boolean`);
34003
+ return String(selected);
34004
+ });
34005
+ }
34006
+ function valueAtPath$1(input, path) {
34007
+ let current = input;
34008
+ for (const segment of path.split(".")) {
34009
+ if (!current || typeof current !== "object" || Array.isArray(current)) return;
34010
+ current = current[segment];
34011
+ }
34012
+ return current;
34013
+ }
34014
+ function validateInput(action, input) {
34015
+ const schema = action.inputSchema;
34016
+ if (!schema) return;
34017
+ const required = Array.isArray(schema.required) ? schema.required : [];
34018
+ for (const key of required) if (typeof key === "string" && (input[key] === void 0 || input[key] === null)) throw new CapletsError("REQUEST_INVALID", `CLI tool ${action.name} requires input ${key}`);
34019
+ const properties = isPlainObject$3(schema.properties) ? schema.properties : {};
34020
+ for (const [key, property] of Object.entries(properties)) {
34021
+ if (input[key] === void 0 || !isPlainObject$3(property) || typeof property.type !== "string") continue;
34022
+ if (!matchesJsonType(input[key], property.type)) throw new CapletsError("REQUEST_INVALID", `CLI tool ${action.name} input ${key} must be ${property.type}`);
34023
+ }
34024
+ }
34025
+ function matchesJsonType(value, type) {
34026
+ switch (type) {
34027
+ case "string": return typeof value === "string";
34028
+ case "number":
34029
+ case "integer": return typeof value === "number" && (type === "number" || Number.isInteger(value));
34030
+ case "boolean": return typeof value === "boolean";
34031
+ case "object": return isPlainObject$3(value);
34032
+ case "array": return Array.isArray(value);
34033
+ case "null": return value === null;
34034
+ default: return true;
34035
+ }
34036
+ }
34037
+ function spawnCommand(execution, signal, elapsedMs) {
34038
+ return new Promise((resolve, reject) => {
34039
+ let stdout = "";
34040
+ let stderr = "";
34041
+ let outputBytes = 0;
34042
+ const child = spawn(execution.command, execution.args, {
34043
+ cwd: execution.cwd,
34044
+ env: execution.env,
34045
+ shell: false,
34046
+ signal,
34047
+ windowsHide: true
34048
+ });
34049
+ child.on("error", reject);
34050
+ const append = (stream, chunk) => {
34051
+ outputBytes += chunk.byteLength;
34052
+ if (outputBytes > execution.maxOutputBytes) {
34053
+ child.kill();
34054
+ reject(new CapletsError("DOWNSTREAM_TOOL_ERROR", "CLI tool output exceeded byte limit"));
34055
+ return;
34056
+ }
34057
+ if (stream === "stdout") stdout += chunk.toString("utf8");
34058
+ else stderr += chunk.toString("utf8");
34059
+ };
34060
+ child.stdout?.on("data", (chunk) => append("stdout", chunk));
34061
+ child.stderr?.on("data", (chunk) => append("stderr", chunk));
34062
+ child.on("close", (exitCode, childSignal) => {
34063
+ resolve({
34064
+ exitCode,
34065
+ signal: childSignal,
34066
+ stdout,
34067
+ stderr,
34068
+ elapsedMs: elapsedMs()
34069
+ });
34070
+ });
34071
+ });
34072
+ }
34073
+ function parseStructuredResult(action, result, tolerateInvalidJson = false) {
34074
+ const structured = {
34075
+ exitCode: result.exitCode,
34076
+ stdout: result.stdout,
34077
+ stderr: result.stderr,
34078
+ elapsedMs: result.elapsedMs,
34079
+ ...result.signal ? { signal: result.signal } : {}
34080
+ };
34081
+ if (action.output?.type === "json" && result.stdout.trim()) try {
34082
+ structured.json = JSON.parse(result.stdout);
34083
+ } catch (error) {
34084
+ if (tolerateInvalidJson) {
34085
+ structured.jsonParseError = toSafeError(error);
34086
+ return structured;
34087
+ }
34088
+ throw new CapletsError("DOWNSTREAM_PROTOCOL_ERROR", `CLI tool ${action.name} stdout was not valid JSON`, toSafeError(error));
34089
+ }
34090
+ return structured;
34091
+ }
34092
+ function resolveCommandPath(command) {
34093
+ if (isAbsolute(command) || /[\\/]/.test(command)) {
34094
+ assertExecutable(command);
34095
+ return command;
34096
+ }
34097
+ for (const directory of (process.env.PATH ?? "").split(delimiter)) {
34098
+ if (!directory) continue;
34099
+ const candidate = join(directory, command);
34100
+ if (isExecutable(candidate)) return candidate;
34101
+ if (process.platform === "win32") for (const ext of (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";")) {
34102
+ const windowsCandidate = join(directory, `${command}${ext.toLowerCase()}`);
34103
+ if (isExecutable(windowsCandidate)) return windowsCandidate;
34104
+ }
34105
+ }
34106
+ throw new CapletsError("SERVER_UNAVAILABLE", `CLI command ${command} was not found on PATH`);
34107
+ }
34108
+ function assertExecutable(path) {
34109
+ if (!isExecutable(path)) throw new CapletsError("SERVER_UNAVAILABLE", `CLI command ${path} is not executable`);
34110
+ }
34111
+ function isExecutable(path) {
34112
+ try {
34113
+ accessSync(path, constants.X_OK);
34114
+ return true;
34115
+ } catch {
34116
+ return false;
34117
+ }
34118
+ }
34119
+ function isAbortError$1(error) {
34120
+ return error instanceof Error && error.name === "AbortError";
34121
+ }
34122
+ function isPlainObject$3(value) {
34123
+ return value !== null && typeof value === "object" && !Array.isArray(value);
34124
+ }
34125
+ //#endregion
33528
34126
  //#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/client.js
33529
34127
  /**
33530
34128
  * Experimental client task features for MCP SDK.
@@ -35770,7 +36368,8 @@ var DownstreamManager = class {
35770
36368
  tool: tool.name,
35771
36369
  ...tool.description ? { description: tool.description } : {},
35772
36370
  ...tool.annotations ? { annotations: tool.annotations } : {},
35773
- hasInputSchema: Boolean(tool.inputSchema)
36371
+ hasInputSchema: Boolean(tool.inputSchema),
36372
+ hasOutputSchema: Boolean(tool.outputSchema)
35774
36373
  };
35775
36374
  }
35776
36375
  search(server, tools, query, limit) {
@@ -50749,7 +51348,8 @@ var GraphQLManager = class {
50749
51348
  tool: tool.name,
50750
51349
  ...tool.description ? { description: tool.description } : {},
50751
51350
  ...tool.annotations ? { annotations: tool.annotations } : {},
50752
- hasInputSchema: Boolean(tool.inputSchema)
51351
+ hasInputSchema: Boolean(tool.inputSchema),
51352
+ hasOutputSchema: Boolean(tool.outputSchema)
50753
51353
  };
50754
51354
  }
50755
51355
  search(endpoint, tools, query, limit) {
@@ -51147,7 +51747,8 @@ var HttpActionManager = class {
51147
51747
  tool: tool.name,
51148
51748
  ...tool.description ? { description: tool.description } : {},
51149
51749
  ...tool.annotations ? { annotations: tool.annotations } : {},
51150
- hasInputSchema: Boolean(tool.inputSchema)
51750
+ hasInputSchema: Boolean(tool.inputSchema),
51751
+ hasOutputSchema: Boolean(tool.outputSchema)
51151
51752
  };
51152
51753
  }
51153
51754
  search(api, tools, query, limit) {
@@ -51159,6 +51760,7 @@ var HttpActionManager = class {
51159
51760
  name: operation.name,
51160
51761
  ...operation.description ? { description: operation.description } : {},
51161
51762
  inputSchema: operation.inputSchema ?? DEFAULT_INPUT_SCHEMA,
51763
+ ...operation.outputSchema ? { outputSchema: operation.outputSchema } : {},
51162
51764
  annotations: {
51163
51765
  readOnlyHint: operation.method === "GET",
51164
51766
  destructiveHint: operation.method === "DELETE"
@@ -51230,7 +51832,7 @@ function resolveMapping(mapping, input) {
51230
51832
  function resolveMappingToRecord(mapping, input, name) {
51231
51833
  if (mapping === void 0) return {};
51232
51834
  const resolved = resolveMapping(mapping, input);
51233
- if (!isPlainObject$1(resolved)) throw new CapletsError("REQUEST_INVALID", `HTTP action ${name} mapping must resolve to an object`);
51835
+ if (!isPlainObject$2(resolved)) throw new CapletsError("REQUEST_INVALID", `HTTP action ${name} mapping must resolve to an object`);
51234
51836
  return resolved;
51235
51837
  }
51236
51838
  function valueAtPath(input, path) {
@@ -51325,9 +51927,9 @@ function buildActionUrl(base, actionPath, options = {}) {
51325
51927
  return baseUrl;
51326
51928
  }
51327
51929
  function asRecord$1(value) {
51328
- return isPlainObject$1(value) ? value : {};
51930
+ return isPlainObject$2(value) ? value : {};
51329
51931
  }
51330
- function isPlainObject$1(value) {
51932
+ function isPlainObject$2(value) {
51331
51933
  return value !== null && typeof value === "object" && !Array.isArray(value);
51332
51934
  }
51333
51935
  //#endregion
@@ -60941,7 +61543,8 @@ var OpenApiManager = class {
60941
61543
  tool: tool.name,
60942
61544
  ...tool.description ? { description: tool.description } : {},
60943
61545
  ...tool.annotations ? { annotations: tool.annotations } : {},
60944
- hasInputSchema: Boolean(tool.inputSchema)
61546
+ hasInputSchema: Boolean(tool.inputSchema),
61547
+ hasOutputSchema: Boolean(tool.outputSchema)
60945
61548
  };
60946
61549
  }
60947
61550
  search(endpoint, tools, query, limit) {
@@ -60984,6 +61587,7 @@ var OpenApiManager = class {
60984
61587
  name: operation.name,
60985
61588
  ...operation.summary || operation.description ? { description: operation.summary ?? operation.description } : {},
60986
61589
  inputSchema: operation.inputSchema,
61590
+ ...operation.outputSchema ? { outputSchema: operation.outputSchema } : {},
60987
61591
  annotations: {
60988
61592
  readOnlyHint: operation.method === "get" || operation.method === "head",
60989
61593
  destructiveHint: operation.method === "delete"
@@ -61019,6 +61623,7 @@ function extractOperations(endpoint, document) {
61019
61623
  seen.add(name);
61020
61624
  const parameters = [...inheritedParameters, ...Array.isArray(operation.parameters) ? operation.parameters : []];
61021
61625
  const requestBody = requestBodyFor(operation);
61626
+ const outputSchema = outputSchemaFor(operation);
61022
61627
  const baseUrl = endpoint.baseUrl ?? firstServerUrl(document);
61023
61628
  validateOperationBaseUrl(endpoint, baseUrl);
61024
61629
  operations.push({
@@ -61028,6 +61633,7 @@ function extractOperations(endpoint, document) {
61028
61633
  ...typeof operation.summary === "string" ? { summary: operation.summary } : {},
61029
61634
  ...typeof operation.description === "string" ? { description: operation.description } : {},
61030
61635
  inputSchema: inputSchemaFor(parameters, requestBody),
61636
+ ...outputSchema ? { outputSchema } : {},
61031
61637
  ...requestBody?.contentType ? { requestBodyContentType: requestBody.contentType } : {},
61032
61638
  ...baseUrl ? { baseUrl } : {}
61033
61639
  });
@@ -61048,6 +61654,53 @@ function requestBodyFor(operation) {
61048
61654
  contentType
61049
61655
  };
61050
61656
  }
61657
+ function outputSchemaFor(operation) {
61658
+ const responses = operation.responses;
61659
+ if (!responses || typeof responses !== "object") return;
61660
+ const schemas = [];
61661
+ for (const [status, response] of Object.entries(responses)) {
61662
+ if (!/^2\d\d$/.test(status) || !response || typeof response !== "object") continue;
61663
+ const content = response.content;
61664
+ if (!content || typeof content !== "object") continue;
61665
+ const contentType = JSON_CONTENT_TYPES.find((candidate) => content[candidate]);
61666
+ if (!contentType) continue;
61667
+ const schema = actualSchema(content[contentType]?.schema);
61668
+ if (!schema) continue;
61669
+ schemas.push(schema);
61670
+ }
61671
+ if (schemas.length === 0) return;
61672
+ const firstSchema = schemas[0];
61673
+ if (schemas.slice(1).some((schema) => JSON.stringify(schema) !== JSON.stringify(firstSchema))) return;
61674
+ return structuredOutputSchema(firstSchema);
61675
+ }
61676
+ function actualSchema(value) {
61677
+ rejectExternalRefs(value);
61678
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
61679
+ const schema = value;
61680
+ return typeof schema.$ref === "string" ? void 0 : schema;
61681
+ }
61682
+ function structuredOutputSchema(bodySchema) {
61683
+ return {
61684
+ type: "object",
61685
+ additionalProperties: false,
61686
+ required: [
61687
+ "status",
61688
+ "statusText",
61689
+ "headers"
61690
+ ],
61691
+ properties: {
61692
+ status: { type: "number" },
61693
+ statusText: { type: "string" },
61694
+ headers: {
61695
+ type: "object",
61696
+ additionalProperties: false,
61697
+ required: ["content-type"],
61698
+ properties: { "content-type": { type: "string" } }
61699
+ },
61700
+ body: bodySchema
61701
+ }
61702
+ };
61703
+ }
61051
61704
  function inputSchemaFor(parameters, requestBody) {
61052
61705
  const schema = {
61053
61706
  type: "object",
@@ -61207,6 +61860,26 @@ function openApiCacheKey(endpoint) {
61207
61860
  });
61208
61861
  }
61209
61862
  //#endregion
61863
+ //#region src/capability-description.mjs
61864
+ function capabilityDescription(server) {
61865
+ const backendName = server.backend === "mcp" ? "MCP server" : server.backend === "openapi" ? "OpenAPI endpoint" : server.backend === "graphql" ? "GraphQL endpoint" : server.backend === "http" ? "HTTP API" : "CLI tools";
61866
+ const checkOperation = server.backend === "mcp" ? "check_mcp_server" : "check_backend";
61867
+ const hint = [
61868
+ `Use this Caplet to inspect and call tools from its ${backendName} backend.`,
61869
+ "",
61870
+ "Recommended flow:",
61871
+ "- Read the full Caplet card: {\"operation\":\"get_caplet\"}",
61872
+ `- Check the backend: {"operation":"${checkOperation}"}`,
61873
+ "- Discover tools: {\"operation\":\"list_tools\"} or {\"operation\":\"search_tools\",\"query\":\"<what you need>\"}",
61874
+ "- Read one tool schema: {\"operation\":\"get_tool\",\"tool\":\"<tool name>\"}",
61875
+ "- Invoke one downstream tool: {\"operation\":\"call_tool\",\"tool\":\"<tool name>\",\"arguments\":{...}}",
61876
+ "",
61877
+ "Important: Do not put downstream arguments at the top level; put them inside \"arguments\".",
61878
+ "After get_tool shows outputSchema (non-GraphQL), call_tool may use fields: [\"path.to.field\"]."
61879
+ ].join("\n");
61880
+ return `${server.name}\n\n${server.description}\n\n${hint}`;
61881
+ }
61882
+ //#endregion
61210
61883
  //#region src/registry.ts
61211
61884
  var ServerRegistry = class {
61212
61885
  config;
@@ -61219,7 +61892,7 @@ var ServerRegistry = class {
61219
61892
  return this.allCaplets().filter((server) => !server.disabled);
61220
61893
  }
61221
61894
  get(serverId) {
61222
- const server = this.config.mcpServers[serverId] ?? this.config.openapiEndpoints[serverId] ?? this.config.graphqlEndpoints[serverId] ?? this.config.httpApis[serverId];
61895
+ const server = this.config.mcpServers[serverId] ?? this.config.openapiEndpoints[serverId] ?? this.config.graphqlEndpoints[serverId] ?? this.config.httpApis[serverId] ?? this.config.cliTools[serverId];
61223
61896
  return server?.disabled ? void 0 : server;
61224
61897
  }
61225
61898
  require(serverId) {
@@ -61270,27 +61943,11 @@ var ServerRegistry = class {
61270
61943
  ...Object.values(this.config.mcpServers),
61271
61944
  ...Object.values(this.config.openapiEndpoints),
61272
61945
  ...Object.values(this.config.graphqlEndpoints),
61273
- ...Object.values(this.config.httpApis)
61946
+ ...Object.values(this.config.httpApis),
61947
+ ...Object.values(this.config.cliTools)
61274
61948
  ];
61275
61949
  }
61276
61950
  };
61277
- function capabilityDescription(server) {
61278
- const backendName = server.backend === "mcp" ? "MCP server" : server.backend === "openapi" ? "OpenAPI endpoint" : server.backend === "graphql" ? "GraphQL endpoint" : "HTTP API";
61279
- const checkOperation = server.backend === "mcp" ? "check_mcp_server" : "check_backend";
61280
- const hint = [
61281
- `Use this Caplet to inspect and call tools from its ${backendName} backend.`,
61282
- "",
61283
- "Recommended flow:",
61284
- "- Read the full Caplet card: {\"operation\":\"get_caplet\"}",
61285
- `- Check the backend: {"operation":"${checkOperation}"}`,
61286
- "- Discover tools: {\"operation\":\"list_tools\"} or {\"operation\":\"search_tools\",\"query\":\"<what you need>\"}",
61287
- "- Read one tool schema: {\"operation\":\"get_tool\",\"tool\":\"<tool name>\"}",
61288
- "- Invoke one downstream tool: {\"operation\":\"call_tool\",\"tool\":\"<tool name>\",\"arguments\":{...}}",
61289
- "",
61290
- "Important: call_tool requires a top-level \"arguments\" JSON object containing the downstream tool inputs. Do not put downstream arguments at the top level of this wrapper request."
61291
- ].join("\n");
61292
- return `${server.name}\n\n${server.description}\n\n${hint}`;
61293
- }
61294
61951
  function backendDetail(server) {
61295
61952
  if (server.backend === "openapi") return {
61296
61953
  type: "openapi",
@@ -61313,6 +61970,13 @@ function backendDetail(server) {
61313
61970
  requestTimeoutMs: server.requestTimeoutMs,
61314
61971
  configuredActions: Object.keys(server.actions).length
61315
61972
  };
61973
+ if (server.backend === "cli") return {
61974
+ type: "cli",
61975
+ disabled: server.disabled,
61976
+ timeoutMs: server.timeoutMs,
61977
+ maxOutputBytes: server.maxOutputBytes,
61978
+ configuredActions: Object.keys(server.actions).length
61979
+ };
61316
61980
  return {
61317
61981
  type: "mcp",
61318
61982
  transport: server.transport,
@@ -61328,7 +61992,110 @@ function graphQlSource(server) {
61328
61992
  return "introspection";
61329
61993
  }
61330
61994
  //#endregion
61331
- //#region src/tools.ts
61995
+ //#region src/field-selection.ts
61996
+ function projectStructuredContent(value, outputSchema, fields) {
61997
+ validateFieldSelection(outputSchema, fields);
61998
+ if (!isPlainObject$1(value)) throwInvalid("Field selection requires object structured content");
61999
+ const result = createJsonObject();
62000
+ for (const field of fields) {
62001
+ const projected = projectPath(value, outputSchema, field.split("."));
62002
+ if (projected !== void 0) mergeValue(result, projected);
62003
+ }
62004
+ return result;
62005
+ }
62006
+ function validateFieldSelection(outputSchema, fields) {
62007
+ if (!isPlainObject$1(outputSchema)) throwInvalid("Field selection requires an output schema");
62008
+ if (!Array.isArray(fields) || fields.some((field) => typeof field !== "string")) throwInvalid("Field selection requires an array of field paths");
62009
+ for (const field of fields) validateSchemaPath(outputSchema, field.split("."), field);
62010
+ }
62011
+ function validateSchemaPath(schema, path, field) {
62012
+ let current = schema;
62013
+ for (const segment of path) {
62014
+ if (!isSupportedSegment(segment)) throwInvalid(`Unsupported field selection path: ${field}`);
62015
+ if (current?.type === "array") current = Array.isArray(current.items) ? void 0 : current.items;
62016
+ current = getOwnSchemaProperty(current?.properties, segment);
62017
+ if (!current) throwInvalid(`Field is not allowed by output schema: ${field}`);
62018
+ }
62019
+ }
62020
+ function getOwnSchemaProperty(properties, segment) {
62021
+ if (!properties || !Object.prototype.hasOwnProperty.call(properties, segment)) return;
62022
+ return properties[segment];
62023
+ }
62024
+ function projectPath(value, schema, path) {
62025
+ if (path.length === 0) return pruneToSchema(value, schema);
62026
+ if (Array.isArray(value)) {
62027
+ const itemSchema = arrayItemSchema(schema);
62028
+ return value.map((item) => projectPath(item, itemSchema, path) ?? {});
62029
+ }
62030
+ const segment = path[0];
62031
+ if (!isPlainObject$1(value) || !Object.prototype.hasOwnProperty.call(value, segment)) return;
62032
+ const rest = path.slice(1);
62033
+ const propertySchema = getSchemaProperty(schema, segment);
62034
+ const projected = projectPath(value[segment], propertySchema, rest);
62035
+ if (projected === void 0) return;
62036
+ return { [segment]: projected };
62037
+ }
62038
+ function pruneToSchema(value, schema) {
62039
+ if (Array.isArray(value)) {
62040
+ const itemSchema = arrayItemSchema(schema);
62041
+ return value.map((item) => pruneToSchema(item, itemSchema));
62042
+ }
62043
+ if (!isPlainObject$1(value)) return cloneJsonValue(value);
62044
+ const properties = isPlainObject$1(schema) ? schema.properties : void 0;
62045
+ if (!isPlainObject$1(properties)) return cloneJsonValue(value);
62046
+ const result = createJsonObject();
62047
+ for (const [key, nestedSchema] of Object.entries(properties)) if (isSupportedSegment(key) && Object.prototype.hasOwnProperty.call(value, key)) result[key] = pruneToSchema(value[key], nestedSchema);
62048
+ return result;
62049
+ }
62050
+ function getSchemaProperty(schema, segment) {
62051
+ const properties = isPlainObject$1(schema) ? schema.properties : void 0;
62052
+ if (!properties || !Object.prototype.hasOwnProperty.call(properties, segment)) return;
62053
+ return properties[segment];
62054
+ }
62055
+ function arrayItemSchema(schema) {
62056
+ if (!isPlainObject$1(schema) || Array.isArray(schema.items)) return;
62057
+ return schema.items;
62058
+ }
62059
+ function mergeValue(target, value) {
62060
+ if (!isPlainObject$1(value)) return;
62061
+ for (const [key, nested] of Object.entries(value)) {
62062
+ if (!isSupportedSegment(key)) continue;
62063
+ target[key] = mergeNested(target[key], nested);
62064
+ }
62065
+ }
62066
+ function mergeNested(existing, next) {
62067
+ if (next === void 0) return existing;
62068
+ if (Array.isArray(existing) && Array.isArray(next)) return Array.from({ length: Math.max(existing.length, next.length) }, (_, index) => mergeNested(existing[index], next[index]));
62069
+ if (isPlainObject$1(existing) && isPlainObject$1(next)) {
62070
+ const merged = Object.assign(createJsonObject(), existing);
62071
+ mergeValue(merged, next);
62072
+ return merged;
62073
+ }
62074
+ return next;
62075
+ }
62076
+ function isSupportedSegment(segment) {
62077
+ return segment !== "" && segment !== "*" && segment !== "__proto__" && segment !== "prototype" && segment !== "constructor" && !/^\d+$/.test(segment);
62078
+ }
62079
+ function isPlainObject$1(value) {
62080
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
62081
+ }
62082
+ function createJsonObject() {
62083
+ return Object.create(null);
62084
+ }
62085
+ function cloneJsonValue(value) {
62086
+ if (Array.isArray(value)) return value.map(cloneJsonValue);
62087
+ if (isPlainObject$1(value)) {
62088
+ const result = createJsonObject();
62089
+ for (const [key, nested] of Object.entries(value)) if (isSupportedSegment(key)) result[key] = cloneJsonValue(nested);
62090
+ return result;
62091
+ }
62092
+ return value;
62093
+ }
62094
+ function throwInvalid(message) {
62095
+ throw new CapletsError("REQUEST_INVALID", message);
62096
+ }
62097
+ //#endregion
62098
+ //#region src/generated-tool-input-schema.mjs
61332
62099
  const operations = [
61333
62100
  "get_caplet",
61334
62101
  "check_backend",
@@ -61338,27 +62105,36 @@ const operations = [
61338
62105
  "get_tool",
61339
62106
  "call_tool"
61340
62107
  ];
61341
- const generatedToolInputSchema = object$1({
61342
- operation: _enum(operations).describe([
62108
+ const generatedToolInputDescriptions = {
62109
+ operation: [
61343
62110
  "Caplets wrapper operation to perform for this configured Caplet backend.",
61344
- "Use get_caplet to read the full Caplet card, check_backend to check any backend, check_mcp_server to check an MCP backend, list_tools or search_tools to discover downstream tools, get_tool to read a downstream input schema, and call_tool to run one downstream tool or OpenAPI operation.",
62111
+ "Use get_caplet to read the full Caplet card, check_backend to check any backend, check_mcp_server to check an MCP backend, list_tools or search_tools to discover downstream tools, get_tool to read a downstream input schema, and call_tool to run one downstream tool, operation, action, or CLI command.",
61345
62112
  "For call_tool, pass downstream inputs only inside the top-level \"arguments\" object."
61346
- ].join(" ")),
61347
- query: string().optional().describe("Required only for search_tools. Example: {\"operation\":\"search_tools\",\"query\":\"web search\",\"limit\":5}. Do not use query for call_tool; put downstream query values under arguments.query."),
61348
- limit: number$1().int().positive().optional().describe("Optional only for search_tools; defaults to the configured search limit. For downstream result limits, use call_tool.arguments with the downstream schema field name."),
61349
- tool: string().optional().describe("Exact downstream tool name for get_tool or call_tool. Example: {\"operation\":\"get_tool\",\"tool\":\"web_search_exa\"} before calling it."),
61350
- arguments: record(string(), unknown()).optional().describe("Required JSON object only for call_tool. Put every downstream tool input inside this object. Example: {\"operation\":\"call_tool\",\"tool\":\"web_search_exa\",\"arguments\":{\"query\":\"latest MCP docs\",\"numResults\":3}}. Do not send downstream inputs as top-level query, limit, url, path, or other fields.")
62113
+ ].join(" "),
62114
+ query: "Required only for search_tools. Example: {\"operation\":\"search_tools\",\"query\":\"web search\",\"limit\":5}. Do not use query for call_tool; put downstream query values under arguments.query.",
62115
+ limit: "Optional only for search_tools; defaults to the configured search limit. For downstream result limits, use call_tool.arguments with the downstream schema field name.",
62116
+ tool: "Exact downstream tool name for get_tool or call_tool. Example: {\"operation\":\"get_tool\",\"tool\":\"web_search_exa\"} before calling it.",
62117
+ arguments: "Required JSON object only for call_tool. Put every downstream tool input inside this object. Example: {\"operation\":\"call_tool\",\"tool\":\"web_search_exa\",\"arguments\":{\"query\":\"latest MCP docs\",\"numResults\":3}}. Do not send downstream inputs as top-level query, limit, url, path, or other fields.",
62118
+ fields: "Optional for call_tool after get_tool shows outputSchema on a non-GraphQL tool. Example: fields: [\"path.to.field\"]."
62119
+ };
62120
+ const generatedToolInputSchema = object$1({
62121
+ operation: _enum(operations).describe(generatedToolInputDescriptions.operation),
62122
+ query: string().optional().describe(generatedToolInputDescriptions.query),
62123
+ limit: number$1().int().positive().optional().describe(generatedToolInputDescriptions.limit),
62124
+ tool: string().optional().describe(generatedToolInputDescriptions.tool),
62125
+ arguments: record(string(), unknown()).optional().describe(generatedToolInputDescriptions.arguments),
62126
+ fields: array(string().min(1)).min(1).optional().describe(generatedToolInputDescriptions.fields)
61351
62127
  }).strict();
61352
- async function handleServerTool(server, request, registry, downstream, openapi, graphql, http) {
62128
+ async function handleServerTool(server, request, registry, downstream, openapi, graphql, http, cli) {
61353
62129
  const parsed = validateOperationRequest(request, registry.config.options.maxSearchLimit);
61354
62130
  switch (parsed.operation) {
61355
62131
  case "get_caplet": return jsonResult(registry.detail(server));
61356
- case "check_backend": return jsonResult(await backendFor(server, downstream, openapi, graphql, http).check(server));
62132
+ case "check_backend": return jsonResult(await backendFor(server, downstream, openapi, graphql, http, cli).check(server));
61357
62133
  case "check_mcp_server":
61358
62134
  if (server.backend !== "mcp") throw new CapletsError("REQUEST_INVALID", "check_mcp_server is only valid for MCP-backed Caplets; use check_backend");
61359
62135
  return jsonResult(await downstream.checkServer(server));
61360
62136
  case "list_tools": {
61361
- const backend = backendFor(server, downstream, openapi, graphql, http);
62137
+ const backend = backendFor(server, downstream, openapi, graphql, http, cli);
61362
62138
  const tools = await backend.listTools(server);
61363
62139
  return jsonResult({
61364
62140
  server: server.server,
@@ -61366,7 +62142,7 @@ async function handleServerTool(server, request, registry, downstream, openapi,
61366
62142
  });
61367
62143
  }
61368
62144
  case "search_tools": {
61369
- const backend = backendFor(server, downstream, openapi, graphql, http);
62145
+ const backend = backendFor(server, downstream, openapi, graphql, http, cli);
61370
62146
  const tools = await backend.listTools(server);
61371
62147
  const limit = parsed.limit ?? registry.config.options.defaultSearchLimit;
61372
62148
  return jsonResult({
@@ -61376,13 +62152,21 @@ async function handleServerTool(server, request, registry, downstream, openapi,
61376
62152
  });
61377
62153
  }
61378
62154
  case "get_tool": {
61379
- const tool = await backendFor(server, downstream, openapi, graphql, http).getTool(server, parsed.tool);
62155
+ const tool = await backendFor(server, downstream, openapi, graphql, http, cli).getTool(server, parsed.tool);
61380
62156
  return jsonResult({
61381
62157
  server: server.server,
61382
62158
  tool
61383
62159
  });
61384
62160
  }
61385
- case "call_tool": return backendFor(server, downstream, openapi, graphql, http).callTool(server, parsed.tool, parsed.arguments);
62161
+ case "call_tool": {
62162
+ const backend = backendFor(server, downstream, openapi, graphql, http, cli);
62163
+ if (parsed.fields === void 0) return backend.callTool(server, parsed.tool, parsed.arguments);
62164
+ if (server.backend === "graphql") throw new CapletsError("REQUEST_INVALID", "call_tool.fields is not supported for GraphQL-backed Caplets; select fields in the GraphQL operation document instead");
62165
+ const tool = await backend.getTool(server, parsed.tool);
62166
+ if (!tool.outputSchema) throw new CapletsError("REQUEST_INVALID", "Field selection requires an output schema");
62167
+ validateFieldSelection(tool.outputSchema, parsed.fields);
62168
+ return projectCallToolResult(await backend.callTool(server, parsed.tool, parsed.arguments), tool.outputSchema, parsed.fields);
62169
+ }
61386
62170
  }
61387
62171
  }
61388
62172
  function validateOperationRequest(request, maxSearchLimit) {
@@ -61423,13 +62207,22 @@ function validateOperationRequest(request, maxSearchLimit) {
61423
62207
  tool: value.tool
61424
62208
  };
61425
62209
  case "call_tool":
61426
- allowed(["tool", "arguments"]);
62210
+ allowed([
62211
+ "tool",
62212
+ "arguments",
62213
+ "fields"
62214
+ ]);
61427
62215
  if (!value.tool) throw new CapletsError("REQUEST_INVALID", "call_tool requires tool");
61428
62216
  if (!isPlainObject(value.arguments)) throw new CapletsError("REQUEST_INVALID", "call_tool.arguments must be a JSON object");
61429
- return {
62217
+ return value.fields === void 0 ? {
61430
62218
  operation: "call_tool",
61431
62219
  tool: value.tool,
61432
62220
  arguments: value.arguments
62221
+ } : {
62222
+ operation: "call_tool",
62223
+ tool: value.tool,
62224
+ arguments: value.arguments,
62225
+ fields: value.fields
61433
62226
  };
61434
62227
  }
61435
62228
  }
@@ -61442,10 +62235,24 @@ function jsonResult(value) {
61442
62235
  structuredContent: { result: value }
61443
62236
  };
61444
62237
  }
62238
+ function projectCallToolResult(result, outputSchema, fields) {
62239
+ if (result.isError === true) return result;
62240
+ const structuredContent = result.structuredContent;
62241
+ if (!isPlainObject(structuredContent)) throw new CapletsError("DOWNSTREAM_PROTOCOL_ERROR", "Field selection requires the downstream tool to return object structuredContent");
62242
+ const projected = projectStructuredContent(structuredContent, outputSchema, fields);
62243
+ return {
62244
+ ...result,
62245
+ content: [{
62246
+ type: "text",
62247
+ text: JSON.stringify(projected, null, 2)
62248
+ }],
62249
+ structuredContent: projected
62250
+ };
62251
+ }
61445
62252
  function isPlainObject(value) {
61446
62253
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
61447
62254
  }
61448
- function backendFor(server, downstream, openapi, graphql, http) {
62255
+ function backendFor(server, downstream, openapi, graphql, http, cli) {
61449
62256
  if (server.backend === "mcp") return {
61450
62257
  check: (...args) => downstream.checkServer(...args),
61451
62258
  listTools: (...args) => downstream.listTools(...args),
@@ -61476,6 +62283,17 @@ function backendFor(server, downstream, openapi, graphql, http) {
61476
62283
  search: (...args) => http.search(...args)
61477
62284
  };
61478
62285
  }
62286
+ if (server.backend === "cli") {
62287
+ if (!cli) throw new CapletsError("INTERNAL_ERROR", "CLI tools manager is not configured");
62288
+ return {
62289
+ check: (...args) => cli.checkTools(...args),
62290
+ listTools: (...args) => cli.listTools(...args),
62291
+ getTool: (...args) => cli.getTool(...args),
62292
+ callTool: (...args) => cli.callTool(...args),
62293
+ compact: (...args) => cli.compact(...args),
62294
+ search: (...args) => cli.search(...args)
62295
+ };
62296
+ }
61479
62297
  if (!openapi) throw new CapletsError("INTERNAL_ERROR", "OpenAPI manager is not configured");
61480
62298
  return {
61481
62299
  check: (...args) => openapi.checkEndpoint(...args),
@@ -61495,6 +62313,7 @@ var CapletsRuntime = class {
61495
62313
  openapi;
61496
62314
  graphql;
61497
62315
  http;
62316
+ cli;
61498
62317
  tools = /* @__PURE__ */ new Map();
61499
62318
  paths;
61500
62319
  watchDebounceMs;
@@ -61516,6 +62335,7 @@ var CapletsRuntime = class {
61516
62335
  this.openapi = new OpenApiManager(this.registry, selectAuthOptions(options.authDir));
61517
62336
  this.graphql = new GraphQLManager(this.registry, selectAuthOptions(options.authDir));
61518
62337
  this.http = new HttpActionManager(this.registry, selectAuthOptions(options.authDir));
62338
+ this.cli = new CliToolsManager(this.registry);
61519
62339
  this.server = options.server ?? new McpServer({
61520
62340
  name: "caplets",
61521
62341
  version
@@ -61592,6 +62412,7 @@ var CapletsRuntime = class {
61592
62412
  this.openapi.updateRegistry(nextRegistry);
61593
62413
  this.graphql.updateRegistry(nextRegistry);
61594
62414
  this.http.updateRegistry(nextRegistry);
62415
+ this.cli.updateRegistry(nextRegistry);
61595
62416
  let invalidated = true;
61596
62417
  try {
61597
62418
  await this.invalidateChangedBackends(previousConfig, nextConfig);
@@ -61650,7 +62471,7 @@ var CapletsRuntime = class {
61650
62471
  }
61651
62472
  async handleTool(serverId, request) {
61652
62473
  try {
61653
- return await handleServerTool(this.registry.require(serverId), request, this.registry, this.downstream, this.openapi, this.graphql, this.http);
62474
+ return await handleServerTool(this.registry.require(serverId), request, this.registry, this.downstream, this.openapi, this.graphql, this.http, this.cli);
61654
62475
  } catch (error) {
61655
62476
  return errorResult(error);
61656
62477
  }
@@ -61667,6 +62488,7 @@ var CapletsRuntime = class {
61667
62488
  if (before?.backend === "openapi" || after?.backend === "openapi" || !after) this.openapi.invalidate(serverId);
61668
62489
  if (before?.backend === "graphql" || after?.backend === "graphql" || !after) this.graphql.invalidate(serverId);
61669
62490
  if (before?.backend === "http" || after?.backend === "http" || !after) this.http.invalidate(serverId);
62491
+ if (before?.backend === "cli" || after?.backend === "cli" || !after) this.cli.invalidate(serverId);
61670
62492
  }
61671
62493
  }
61672
62494
  resetWatchers() {
@@ -61759,14 +62581,15 @@ function allCaplets(config) {
61759
62581
  ...Object.values(config.mcpServers),
61760
62582
  ...Object.values(config.openapiEndpoints),
61761
62583
  ...Object.values(config.graphqlEndpoints),
61762
- ...Object.values(config.httpApis)
62584
+ ...Object.values(config.httpApis),
62585
+ ...Object.values(config.cliTools)
61763
62586
  ];
61764
62587
  }
61765
62588
  function nextEnabledServers(config) {
61766
62589
  return allCaplets(config).filter((server) => !server.disabled);
61767
62590
  }
61768
62591
  function capletById(config, serverId) {
61769
- return config.mcpServers[serverId] ?? config.openapiEndpoints[serverId] ?? config.graphqlEndpoints[serverId] ?? config.httpApis[serverId];
62592
+ return config.mcpServers[serverId] ?? config.openapiEndpoints[serverId] ?? config.graphqlEndpoints[serverId] ?? config.httpApis[serverId] ?? config.cliTools[serverId];
61770
62593
  }
61771
62594
  function serializeCaplet(caplet) {
61772
62595
  return JSON.stringify(caplet ?? null);