caplets 0.10.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/README.md +73 -7
- package/caplets/github-cli/CAPLET.md +41 -0
- package/caplets/repo-cli/CAPLET.md +37 -0
- package/dist/index.js +669 -48
- package/package.json +1 -1
- package/schemas/caplet.schema.json +139 -0
- package/schemas/caplets-config.schema.json +174 -0
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$
|
|
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$6(o) {
|
|
|
191
191
|
return true;
|
|
192
192
|
}
|
|
193
193
|
function shallowClone(o) {
|
|
194
|
-
if (isPlainObject$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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.
|
|
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
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
-
|
|
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$
|
|
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$
|
|
19263
|
+
function isPlainObject$6(value) {
|
|
19213
19264
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
19214
19265
|
}
|
|
19215
19266
|
function validateCapletId(id, path) {
|
|
@@ -19409,7 +19460,39 @@ const publicHttpApiSchema = object$1({
|
|
|
19409
19460
|
disabled: boolean().default(false).describe("When true, omit this HTTP API Caplet.")
|
|
19410
19461
|
}).strict();
|
|
19411
19462
|
const normalizedHttpApiSchema = publicHttpApiSchema.extend({ body: string().optional() });
|
|
19412
|
-
|
|
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) {
|
|
19413
19496
|
return object$1({
|
|
19414
19497
|
$schema: string().url().optional().describe("Optional JSON Schema URL for editor validation."),
|
|
19415
19498
|
version: literal(1).default(1).describe("Caplets config schema version."),
|
|
@@ -19418,7 +19501,8 @@ function configSchemaFor(serverValueSchema, openApiEndpointValueSchema, graphQlE
|
|
|
19418
19501
|
mcpServers: record(string().regex(SERVER_ID_PATTERN), serverValueSchema).default({}).describe("Downstream MCP servers keyed by stable server ID."),
|
|
19419
19502
|
openapiEndpoints: record(string().regex(SERVER_ID_PATTERN), openApiEndpointValueSchema).default({}).describe("OpenAPI endpoints keyed by stable Caplet ID."),
|
|
19420
19503
|
graphqlEndpoints: record(string().regex(SERVER_ID_PATTERN), graphQlEndpointValueSchema).default({}).describe("GraphQL endpoints keyed by stable Caplet ID."),
|
|
19421
|
-
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.")
|
|
19422
19506
|
}).strict().superRefine((config, ctx) => {
|
|
19423
19507
|
if (config.defaultSearchLimit > config.maxSearchLimit) ctx.addIssue({
|
|
19424
19508
|
code: "custom",
|
|
@@ -19607,10 +19691,34 @@ function configSchemaFor(serverValueSchema, openApiEndpointValueSchema, graphQlE
|
|
|
19607
19691
|
"headers"
|
|
19608
19692
|
]);
|
|
19609
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
|
+
}
|
|
19610
19718
|
});
|
|
19611
19719
|
}
|
|
19612
|
-
const configFileSchema = configSchemaFor(publicServerSchema, publicOpenApiEndpointSchema, publicGraphQlEndpointSchema, publicHttpApiSchema);
|
|
19613
|
-
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);
|
|
19614
19722
|
function loadConfig(path = resolveConfigPath(), projectPath = resolveProjectConfigPath()) {
|
|
19615
19723
|
const hasUserConfig = existsSync(path);
|
|
19616
19724
|
const hasProjectConfig = existsSync(projectPath);
|
|
@@ -19621,7 +19729,7 @@ function loadConfig(path = resolveConfigPath(), projectPath = resolveProjectConf
|
|
|
19621
19729
|
if (!hasUserConfig && !hasProjectConfig && !userCaplets && !projectCaplets) throw new CapletsError("CONFIG_NOT_FOUND", `Caplets config not found at ${path} or ${projectPath}`);
|
|
19622
19730
|
try {
|
|
19623
19731
|
const config = parseConfig(mergeConfigInputs(userConfig, userCaplets, projectConfig, projectCaplets));
|
|
19624
|
-
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
|
|
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");
|
|
19625
19733
|
return config;
|
|
19626
19734
|
} catch (error) {
|
|
19627
19735
|
if (error instanceof CapletsError) throw error;
|
|
@@ -19646,12 +19754,13 @@ function normalizeLocalPaths(input, baseDir) {
|
|
|
19646
19754
|
return stripUndefined({
|
|
19647
19755
|
...input,
|
|
19648
19756
|
openapiEndpoints: normalizeEndpointPaths(input.openapiEndpoints, baseDir, normalizeOpenApiPath),
|
|
19649
|
-
graphqlEndpoints: normalizeEndpointPaths(input.graphqlEndpoints, baseDir, normalizeGraphQlPath)
|
|
19757
|
+
graphqlEndpoints: normalizeEndpointPaths(input.graphqlEndpoints, baseDir, normalizeGraphQlPath),
|
|
19758
|
+
cliTools: normalizeEndpointPaths(input.cliTools, baseDir, normalizeCliToolsPaths)
|
|
19650
19759
|
});
|
|
19651
19760
|
}
|
|
19652
19761
|
function normalizeEndpointPaths(endpoints, baseDir, normalize) {
|
|
19653
19762
|
if (!endpoints) return;
|
|
19654
|
-
return Object.fromEntries(Object.entries(endpoints).map(([id, endpoint]) => [id, isPlainObject$
|
|
19763
|
+
return Object.fromEntries(Object.entries(endpoints).map(([id, endpoint]) => [id, isPlainObject$5(endpoint) ? normalize(endpoint, baseDir) : endpoint]));
|
|
19655
19764
|
}
|
|
19656
19765
|
function normalizeOpenApiPath(endpoint, baseDir) {
|
|
19657
19766
|
return {
|
|
@@ -19660,7 +19769,7 @@ function normalizeOpenApiPath(endpoint, baseDir) {
|
|
|
19660
19769
|
};
|
|
19661
19770
|
}
|
|
19662
19771
|
function normalizeGraphQlPath(endpoint, baseDir) {
|
|
19663
|
-
const operations = isPlainObject$
|
|
19772
|
+
const operations = isPlainObject$5(endpoint.operations) ? Object.fromEntries(Object.entries(endpoint.operations).map(([name, operation]) => [name, isPlainObject$5(operation) ? {
|
|
19664
19773
|
...operation,
|
|
19665
19774
|
documentPath: normalizeLocalPath(operation.documentPath, baseDir)
|
|
19666
19775
|
} : operation])) : endpoint.operations;
|
|
@@ -19670,6 +19779,17 @@ function normalizeGraphQlPath(endpoint, baseDir) {
|
|
|
19670
19779
|
operations
|
|
19671
19780
|
};
|
|
19672
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
|
+
}
|
|
19673
19793
|
function normalizeLocalPath(value, baseDir) {
|
|
19674
19794
|
if (typeof value !== "string" || !value || isAbsolute(value) || hasEnvReference(value)) return value;
|
|
19675
19795
|
return join(baseDir, value);
|
|
@@ -19678,6 +19798,7 @@ function rejectUntrustedProjectExecutableBackends(input, path) {
|
|
|
19678
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`);
|
|
19679
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`);
|
|
19680
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`);
|
|
19681
19802
|
return input;
|
|
19682
19803
|
}
|
|
19683
19804
|
function mergeConfigInputs(...inputs) {
|
|
@@ -19702,6 +19823,10 @@ function mergeConfigInputs(...inputs) {
|
|
|
19702
19823
|
httpApis: {
|
|
19703
19824
|
...merged?.httpApis,
|
|
19704
19825
|
...input.httpApis
|
|
19826
|
+
},
|
|
19827
|
+
cliTools: {
|
|
19828
|
+
...merged?.cliTools,
|
|
19829
|
+
...input.cliTools
|
|
19705
19830
|
}
|
|
19706
19831
|
};
|
|
19707
19832
|
}
|
|
@@ -19738,6 +19863,12 @@ function parseConfig(input) {
|
|
|
19738
19863
|
server,
|
|
19739
19864
|
backend: "http"
|
|
19740
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
|
+
});
|
|
19741
19872
|
return {
|
|
19742
19873
|
version: parsed.data.version,
|
|
19743
19874
|
options: {
|
|
@@ -19747,7 +19878,8 @@ function parseConfig(input) {
|
|
|
19747
19878
|
mcpServers: servers,
|
|
19748
19879
|
openapiEndpoints,
|
|
19749
19880
|
graphqlEndpoints,
|
|
19750
|
-
httpApis
|
|
19881
|
+
httpApis,
|
|
19882
|
+
cliTools
|
|
19751
19883
|
};
|
|
19752
19884
|
}
|
|
19753
19885
|
function validateEndpointAuthHeaders(auth, ctx, path) {
|
|
@@ -19776,10 +19908,10 @@ function interpolateConfig(value, path = []) {
|
|
|
19776
19908
|
return value;
|
|
19777
19909
|
}
|
|
19778
19910
|
function isPublicMetadataPath(path) {
|
|
19779
|
-
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;
|
|
19780
19912
|
return NON_INTERPOLATED_SERVER_FIELDS.has(path[2] ?? "");
|
|
19781
19913
|
}
|
|
19782
|
-
function isPlainObject$
|
|
19914
|
+
function isPlainObject$5(value) {
|
|
19783
19915
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
19784
19916
|
}
|
|
19785
19917
|
function hasEnvReference(value) {
|
|
@@ -19868,6 +20000,194 @@ async function maybeReadManualInput() {
|
|
|
19868
20000
|
}
|
|
19869
20001
|
}
|
|
19870
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
|
|
19871
20191
|
//#region src/cli/init.ts
|
|
19872
20192
|
function initConfig(options = {}) {
|
|
19873
20193
|
const path = resolveConfigPath(options.path);
|
|
@@ -19919,7 +20239,9 @@ function allCaplets$1(config) {
|
|
|
19919
20239
|
return [
|
|
19920
20240
|
...Object.values(config.mcpServers),
|
|
19921
20241
|
...Object.values(config.openapiEndpoints),
|
|
19922
|
-
...Object.values(config.graphqlEndpoints)
|
|
20242
|
+
...Object.values(config.graphqlEndpoints),
|
|
20243
|
+
...Object.values(config.httpApis),
|
|
20244
|
+
...Object.values(config.cliTools)
|
|
19923
20245
|
];
|
|
19924
20246
|
}
|
|
19925
20247
|
function formatCapletList(rows) {
|
|
@@ -20159,6 +20481,14 @@ function createProgram(io = {}) {
|
|
|
20159
20481
|
});
|
|
20160
20482
|
for (const caplet of result.installed) writeOut(`Installed ${caplet.id} to ${caplet.destination}\n`);
|
|
20161
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
|
+
});
|
|
20162
20492
|
const config = program.command("config").description("Inspect Caplets config locations.");
|
|
20163
20493
|
config.command("path").description("Print the effective user config path.").action(() => {
|
|
20164
20494
|
writeOut(`${resolveConfigPath(envConfigPath())}\n`);
|
|
@@ -25865,7 +26195,7 @@ var Protocol = class {
|
|
|
25865
26195
|
};
|
|
25866
26196
|
}
|
|
25867
26197
|
};
|
|
25868
|
-
function isPlainObject$
|
|
26198
|
+
function isPlainObject$4(value) {
|
|
25869
26199
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
25870
26200
|
}
|
|
25871
26201
|
function mergeCapabilities(base, additional) {
|
|
@@ -25875,7 +26205,7 @@ function mergeCapabilities(base, additional) {
|
|
|
25875
26205
|
const addValue = additional[k];
|
|
25876
26206
|
if (addValue === void 0) continue;
|
|
25877
26207
|
const baseValue = result[k];
|
|
25878
|
-
if (isPlainObject$
|
|
26208
|
+
if (isPlainObject$4(baseValue) && isPlainObject$4(addValue)) result[k] = {
|
|
25879
26209
|
...baseValue,
|
|
25880
26210
|
...addValue
|
|
25881
26211
|
};
|
|
@@ -33526,6 +33856,273 @@ const EMPTY_COMPLETION_RESULT = { completion: {
|
|
|
33526
33856
|
hasMore: false
|
|
33527
33857
|
} };
|
|
33528
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
|
|
33529
34126
|
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/client.js
|
|
33530
34127
|
/**
|
|
33531
34128
|
* Experimental client task features for MCP SDK.
|
|
@@ -61265,7 +61862,7 @@ function openApiCacheKey(endpoint) {
|
|
|
61265
61862
|
//#endregion
|
|
61266
61863
|
//#region src/capability-description.mjs
|
|
61267
61864
|
function capabilityDescription(server) {
|
|
61268
|
-
const backendName = server.backend === "mcp" ? "MCP server" : server.backend === "openapi" ? "OpenAPI endpoint" : server.backend === "graphql" ? "GraphQL endpoint" : "HTTP API";
|
|
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";
|
|
61269
61866
|
const checkOperation = server.backend === "mcp" ? "check_mcp_server" : "check_backend";
|
|
61270
61867
|
const hint = [
|
|
61271
61868
|
`Use this Caplet to inspect and call tools from its ${backendName} backend.`,
|
|
@@ -61295,7 +61892,7 @@ var ServerRegistry = class {
|
|
|
61295
61892
|
return this.allCaplets().filter((server) => !server.disabled);
|
|
61296
61893
|
}
|
|
61297
61894
|
get(serverId) {
|
|
61298
|
-
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];
|
|
61299
61896
|
return server?.disabled ? void 0 : server;
|
|
61300
61897
|
}
|
|
61301
61898
|
require(serverId) {
|
|
@@ -61346,7 +61943,8 @@ var ServerRegistry = class {
|
|
|
61346
61943
|
...Object.values(this.config.mcpServers),
|
|
61347
61944
|
...Object.values(this.config.openapiEndpoints),
|
|
61348
61945
|
...Object.values(this.config.graphqlEndpoints),
|
|
61349
|
-
...Object.values(this.config.httpApis)
|
|
61946
|
+
...Object.values(this.config.httpApis),
|
|
61947
|
+
...Object.values(this.config.cliTools)
|
|
61350
61948
|
];
|
|
61351
61949
|
}
|
|
61352
61950
|
};
|
|
@@ -61372,6 +61970,13 @@ function backendDetail(server) {
|
|
|
61372
61970
|
requestTimeoutMs: server.requestTimeoutMs,
|
|
61373
61971
|
configuredActions: Object.keys(server.actions).length
|
|
61374
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
|
+
};
|
|
61375
61980
|
return {
|
|
61376
61981
|
type: "mcp",
|
|
61377
61982
|
transport: server.transport,
|
|
@@ -61503,7 +62108,7 @@ const operations = [
|
|
|
61503
62108
|
const generatedToolInputDescriptions = {
|
|
61504
62109
|
operation: [
|
|
61505
62110
|
"Caplets wrapper operation to perform for this configured Caplet backend.",
|
|
61506
|
-
"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
|
|
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.",
|
|
61507
62112
|
"For call_tool, pass downstream inputs only inside the top-level \"arguments\" object."
|
|
61508
62113
|
].join(" "),
|
|
61509
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.",
|
|
@@ -61520,16 +62125,16 @@ const generatedToolInputSchema = object$1({
|
|
|
61520
62125
|
arguments: record(string(), unknown()).optional().describe(generatedToolInputDescriptions.arguments),
|
|
61521
62126
|
fields: array(string().min(1)).min(1).optional().describe(generatedToolInputDescriptions.fields)
|
|
61522
62127
|
}).strict();
|
|
61523
|
-
async function handleServerTool(server, request, registry, downstream, openapi, graphql, http) {
|
|
62128
|
+
async function handleServerTool(server, request, registry, downstream, openapi, graphql, http, cli) {
|
|
61524
62129
|
const parsed = validateOperationRequest(request, registry.config.options.maxSearchLimit);
|
|
61525
62130
|
switch (parsed.operation) {
|
|
61526
62131
|
case "get_caplet": return jsonResult(registry.detail(server));
|
|
61527
|
-
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));
|
|
61528
62133
|
case "check_mcp_server":
|
|
61529
62134
|
if (server.backend !== "mcp") throw new CapletsError("REQUEST_INVALID", "check_mcp_server is only valid for MCP-backed Caplets; use check_backend");
|
|
61530
62135
|
return jsonResult(await downstream.checkServer(server));
|
|
61531
62136
|
case "list_tools": {
|
|
61532
|
-
const backend = backendFor(server, downstream, openapi, graphql, http);
|
|
62137
|
+
const backend = backendFor(server, downstream, openapi, graphql, http, cli);
|
|
61533
62138
|
const tools = await backend.listTools(server);
|
|
61534
62139
|
return jsonResult({
|
|
61535
62140
|
server: server.server,
|
|
@@ -61537,7 +62142,7 @@ async function handleServerTool(server, request, registry, downstream, openapi,
|
|
|
61537
62142
|
});
|
|
61538
62143
|
}
|
|
61539
62144
|
case "search_tools": {
|
|
61540
|
-
const backend = backendFor(server, downstream, openapi, graphql, http);
|
|
62145
|
+
const backend = backendFor(server, downstream, openapi, graphql, http, cli);
|
|
61541
62146
|
const tools = await backend.listTools(server);
|
|
61542
62147
|
const limit = parsed.limit ?? registry.config.options.defaultSearchLimit;
|
|
61543
62148
|
return jsonResult({
|
|
@@ -61547,14 +62152,14 @@ async function handleServerTool(server, request, registry, downstream, openapi,
|
|
|
61547
62152
|
});
|
|
61548
62153
|
}
|
|
61549
62154
|
case "get_tool": {
|
|
61550
|
-
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);
|
|
61551
62156
|
return jsonResult({
|
|
61552
62157
|
server: server.server,
|
|
61553
62158
|
tool
|
|
61554
62159
|
});
|
|
61555
62160
|
}
|
|
61556
62161
|
case "call_tool": {
|
|
61557
|
-
const backend = backendFor(server, downstream, openapi, graphql, http);
|
|
62162
|
+
const backend = backendFor(server, downstream, openapi, graphql, http, cli);
|
|
61558
62163
|
if (parsed.fields === void 0) return backend.callTool(server, parsed.tool, parsed.arguments);
|
|
61559
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");
|
|
61560
62165
|
const tool = await backend.getTool(server, parsed.tool);
|
|
@@ -61647,7 +62252,7 @@ function projectCallToolResult(result, outputSchema, fields) {
|
|
|
61647
62252
|
function isPlainObject(value) {
|
|
61648
62253
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
61649
62254
|
}
|
|
61650
|
-
function backendFor(server, downstream, openapi, graphql, http) {
|
|
62255
|
+
function backendFor(server, downstream, openapi, graphql, http, cli) {
|
|
61651
62256
|
if (server.backend === "mcp") return {
|
|
61652
62257
|
check: (...args) => downstream.checkServer(...args),
|
|
61653
62258
|
listTools: (...args) => downstream.listTools(...args),
|
|
@@ -61678,6 +62283,17 @@ function backendFor(server, downstream, openapi, graphql, http) {
|
|
|
61678
62283
|
search: (...args) => http.search(...args)
|
|
61679
62284
|
};
|
|
61680
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
|
+
}
|
|
61681
62297
|
if (!openapi) throw new CapletsError("INTERNAL_ERROR", "OpenAPI manager is not configured");
|
|
61682
62298
|
return {
|
|
61683
62299
|
check: (...args) => openapi.checkEndpoint(...args),
|
|
@@ -61697,6 +62313,7 @@ var CapletsRuntime = class {
|
|
|
61697
62313
|
openapi;
|
|
61698
62314
|
graphql;
|
|
61699
62315
|
http;
|
|
62316
|
+
cli;
|
|
61700
62317
|
tools = /* @__PURE__ */ new Map();
|
|
61701
62318
|
paths;
|
|
61702
62319
|
watchDebounceMs;
|
|
@@ -61718,6 +62335,7 @@ var CapletsRuntime = class {
|
|
|
61718
62335
|
this.openapi = new OpenApiManager(this.registry, selectAuthOptions(options.authDir));
|
|
61719
62336
|
this.graphql = new GraphQLManager(this.registry, selectAuthOptions(options.authDir));
|
|
61720
62337
|
this.http = new HttpActionManager(this.registry, selectAuthOptions(options.authDir));
|
|
62338
|
+
this.cli = new CliToolsManager(this.registry);
|
|
61721
62339
|
this.server = options.server ?? new McpServer({
|
|
61722
62340
|
name: "caplets",
|
|
61723
62341
|
version
|
|
@@ -61794,6 +62412,7 @@ var CapletsRuntime = class {
|
|
|
61794
62412
|
this.openapi.updateRegistry(nextRegistry);
|
|
61795
62413
|
this.graphql.updateRegistry(nextRegistry);
|
|
61796
62414
|
this.http.updateRegistry(nextRegistry);
|
|
62415
|
+
this.cli.updateRegistry(nextRegistry);
|
|
61797
62416
|
let invalidated = true;
|
|
61798
62417
|
try {
|
|
61799
62418
|
await this.invalidateChangedBackends(previousConfig, nextConfig);
|
|
@@ -61852,7 +62471,7 @@ var CapletsRuntime = class {
|
|
|
61852
62471
|
}
|
|
61853
62472
|
async handleTool(serverId, request) {
|
|
61854
62473
|
try {
|
|
61855
|
-
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);
|
|
61856
62475
|
} catch (error) {
|
|
61857
62476
|
return errorResult(error);
|
|
61858
62477
|
}
|
|
@@ -61869,6 +62488,7 @@ var CapletsRuntime = class {
|
|
|
61869
62488
|
if (before?.backend === "openapi" || after?.backend === "openapi" || !after) this.openapi.invalidate(serverId);
|
|
61870
62489
|
if (before?.backend === "graphql" || after?.backend === "graphql" || !after) this.graphql.invalidate(serverId);
|
|
61871
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);
|
|
61872
62492
|
}
|
|
61873
62493
|
}
|
|
61874
62494
|
resetWatchers() {
|
|
@@ -61961,14 +62581,15 @@ function allCaplets(config) {
|
|
|
61961
62581
|
...Object.values(config.mcpServers),
|
|
61962
62582
|
...Object.values(config.openapiEndpoints),
|
|
61963
62583
|
...Object.values(config.graphqlEndpoints),
|
|
61964
|
-
...Object.values(config.httpApis)
|
|
62584
|
+
...Object.values(config.httpApis),
|
|
62585
|
+
...Object.values(config.cliTools)
|
|
61965
62586
|
];
|
|
61966
62587
|
}
|
|
61967
62588
|
function nextEnabledServers(config) {
|
|
61968
62589
|
return allCaplets(config).filter((server) => !server.disabled);
|
|
61969
62590
|
}
|
|
61970
62591
|
function capletById(config, serverId) {
|
|
61971
|
-
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];
|
|
61972
62593
|
}
|
|
61973
62594
|
function serializeCaplet(caplet) {
|
|
61974
62595
|
return JSON.stringify(caplet ?? null);
|