@caplets/core 0.16.0 → 0.18.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/auth.d.ts +14 -0
- package/dist/cli/auth.d.ts +24 -0
- package/dist/cli/commands.d.ts +37 -0
- package/dist/cli/completion-cache.d.ts +30 -0
- package/dist/cli/completion-discovery.d.ts +30 -0
- package/dist/cli/completion.d.ts +15 -0
- package/dist/cli.d.ts +2 -0
- package/dist/completion-CxGG6ae3.js +560 -0
- package/dist/config/paths.d.ts +3 -0
- package/dist/config.d.ts +14 -1
- package/dist/downstream.d.ts +169 -1
- package/dist/engine.d.ts +2 -0
- package/dist/errors.d.ts +1 -1
- package/dist/generated-tool-input-schema-B6rce396.js +4720 -0
- package/dist/generated-tool-input-schema.d.ts +222 -9
- package/dist/generated-tool-input-schema.js +2 -59
- package/dist/index.js +1063 -207
- package/dist/native/options.d.ts +4 -5
- package/dist/native/remote.d.ts +1 -0
- package/dist/native/service.d.ts +3 -0
- package/dist/native.js +38 -64
- package/dist/{engine-Brwid_mq.js → options-DM1cMRcp.js} +888 -4851
- package/dist/remote-control/auth-flow.d.ts +22 -0
- package/dist/remote-control/client.d.ts +11 -0
- package/dist/remote-control/dispatch.d.ts +9 -0
- package/dist/remote-control/types.d.ts +17 -0
- package/dist/serve/http.d.ts +11 -0
- package/dist/serve/options.d.ts +3 -1
- package/dist/server/options.d.ts +41 -0
- package/dist/tools.d.ts +38 -20
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as assertCompleteRequestPrompt, A as CreateMessageResultSchema, At as toSafeError, B as JSONRPCMessageSchema, C as AjvJsonSchemaValidator, D as CallToolRequestSchema, Dt as CAPLETS_ERROR_CODES, E as toJsonSchemaCompat, Et as SERVER_ID_PATTERN, F as EmptyResultSchema, G as ListRootsResultSchema, H as ListPromptsRequestSchema, I as ErrorCode, J as McpError, K as ListToolsRequestSchema, L as GetPromptRequestSchema, M as CreateTaskResultSchema, Nt as __require, O as CallToolResultSchema, Ot as CapletsError, P as ElicitResultSchema, Pt as __toESM, R as InitializeRequestSchema, S as assertToolsCallTaskCapability, St as resolveProjectCapletsRoot, T as mergeCapabilities, Tt as validateCapletFile, U as ListResourceTemplatesRequestSchema, V as LATEST_PROTOCOL_VERSION, W as ListResourcesRequestSchema, X as SUPPORTED_PROTOCOL_VERSIONS, Y as ReadResourceRequestSchema, Z as SetLevelRequestSchema, _t as parseConfig, a as resolveCapletsServer, at as getLiteralValue, bt as resolveCapletsRoot, c as ServerRegistry, ct as getSchemaDescription, d as runOAuthFlow, dt as normalizeObjectSchema, et as assertCompleteRequestResourceTemplate, f as startGenericOAuthFlow, ft as objectFromShape, g as readTokenBundle, gt as loadConfigWithSources, h as isTokenBundleExpired, ht as loadConfig, i as resolveCapletsMode, it as isJSONRPCResultResponse, j as CreateMessageResultWithToolsSchema, jt as __commonJSMin, k as CompleteRequestSchema, kt as redactSecrets, l as capabilityDescription, lt as isSchemaOptional, m as deleteTokenBundle, mt as safeParseAsync, nt as isJSONRPCErrorResponse, o as CapletsEngine, ot as getObjectShape, p as startOAuthFlow, pt as safeParse, q as LoggingLevelSchema, r as parseServerBaseUrl, rt as isJSONRPCRequest, s as handleServerTool, st as getParseErrorMessage, t as controlUrlForBase, tt as isInitializeRequest, u as runGenericOAuthFlow, ut as isZ4Schema, v as ReadBuffer, w as Protocol, wt as discoverCapletFiles, x as assertClientRequestTaskCapability, xt as resolveConfigPath, y as serializeMessage, z as InitializedNotificationSchema } from "./options-DM1cMRcp.js";
|
|
2
|
+
import { A as url, C as object, D as string, b as literal, d as ZodOptional, o as generatedToolInputSchema, s as generatedToolInputSchemaForCaplet } from "./generated-tool-input-schema-B6rce396.js";
|
|
3
|
+
import { a as formatCapletList, c as resolveCliConfigPaths, l as cliCommands, n as completionScript, o as formatConfigPaths, s as listCaplets, t as completeCliWords, u as completionShells } from "./completion-CxGG6ae3.js";
|
|
2
4
|
import { accessSync, chmodSync, closeSync, constants, cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, rmSync, statSync, writeFileSync, writeSync } from "node:fs";
|
|
3
5
|
import { basename, dirname, join, parse, relative, resolve } from "node:path";
|
|
4
6
|
import { execFileSync } from "node:child_process";
|
|
@@ -1319,7 +1321,7 @@ const EMPTY_COMPLETION_RESULT = { completion: {
|
|
|
1319
1321
|
} };
|
|
1320
1322
|
//#endregion
|
|
1321
1323
|
//#region package.json
|
|
1322
|
-
var version = "0.
|
|
1324
|
+
var version = "0.18.0";
|
|
1323
1325
|
//#endregion
|
|
1324
1326
|
//#region src/serve/session.ts
|
|
1325
1327
|
var CapletsMcpSession = class {
|
|
@@ -1363,6 +1365,7 @@ var CapletsMcpSession = class {
|
|
|
1363
1365
|
if (!previousCaplet || serializeCaplet(previousCaplet) !== serializeCaplet(caplet)) tool.update({
|
|
1364
1366
|
title: caplet.name,
|
|
1365
1367
|
description: capabilityDescription(caplet),
|
|
1368
|
+
paramsSchema: generatedToolInputSchemaForCaplet(caplet).shape,
|
|
1366
1369
|
callback: async (request) => this.handleTool(serverId, request),
|
|
1367
1370
|
enabled: true
|
|
1368
1371
|
});
|
|
@@ -1376,7 +1379,7 @@ var CapletsMcpSession = class {
|
|
|
1376
1379
|
return this.server.registerTool(caplet.server, {
|
|
1377
1380
|
title: caplet.name,
|
|
1378
1381
|
description: capabilityDescription(caplet),
|
|
1379
|
-
inputSchema:
|
|
1382
|
+
inputSchema: generatedToolInputSchemaForCaplet(caplet).shape
|
|
1380
1383
|
}, async (request) => this.handleTool(caplet.server, request));
|
|
1381
1384
|
}
|
|
1382
1385
|
async handleTool(serverId, request) {
|
|
@@ -4921,49 +4924,56 @@ async function loginAuth(serverId, options) {
|
|
|
4921
4924
|
}
|
|
4922
4925
|
}
|
|
4923
4926
|
function logoutAuth(serverId, options) {
|
|
4924
|
-
|
|
4925
|
-
if (deleteTokenBundle(serverId, options.authDir)) options.writeOut(`Deleted OAuth credentials for \`${serverId}\`.\n`);
|
|
4927
|
+
if (logoutAuthResult(serverId, options).deleted) options.writeOut(`Deleted OAuth credentials for \`${serverId}\`.\n`);
|
|
4926
4928
|
else options.writeOut(`No OAuth credentials found for \`${serverId}\`.\n`);
|
|
4927
4929
|
}
|
|
4930
|
+
function logoutAuthResult(serverId, options) {
|
|
4931
|
+
assertLoginTarget(findAuthTarget(serverId, loadConfig(options.configPath)), serverId);
|
|
4932
|
+
return {
|
|
4933
|
+
server: serverId,
|
|
4934
|
+
deleted: deleteTokenBundle(serverId, options.authDir)
|
|
4935
|
+
};
|
|
4936
|
+
}
|
|
4928
4937
|
function listAuth(options) {
|
|
4929
|
-
const
|
|
4938
|
+
const rows = listAuthRows(options);
|
|
4930
4939
|
const format = options.format ?? "plain";
|
|
4931
4940
|
if (format === "json") {
|
|
4932
|
-
const rows = servers.map((server) => {
|
|
4933
|
-
const bundle = readTokenBundle(server.server, options.authDir);
|
|
4934
|
-
const status = !bundle ? "missing" : isTokenBundleExpired(bundle) ? "expired" : "authenticated";
|
|
4935
|
-
return {
|
|
4936
|
-
server: server.server,
|
|
4937
|
-
status,
|
|
4938
|
-
...bundle?.expiresAt ? { expiresAt: bundle.expiresAt } : {},
|
|
4939
|
-
...bundle?.scope ? { scope: bundle.scope } : {}
|
|
4940
|
-
};
|
|
4941
|
-
});
|
|
4942
4941
|
options.writeOut(`${JSON.stringify(rows, null, 2)}\n`);
|
|
4943
4942
|
return;
|
|
4944
4943
|
}
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
if (format === "markdown") options.writeOut("## OAuth credentials\n\n");
|
|
4950
|
-
else options.writeOut("OAuth credentials\n\n");
|
|
4951
|
-
for (const server of servers) {
|
|
4944
|
+
options.writeOut(formatAuthRows(rows, format));
|
|
4945
|
+
}
|
|
4946
|
+
function listAuthRows(options) {
|
|
4947
|
+
return authTargets(loadConfig(options.configPath)).sort((left, right) => left.server.localeCompare(right.server)).map((server) => {
|
|
4952
4948
|
const bundle = readTokenBundle(server.server, options.authDir);
|
|
4953
4949
|
const status = !bundle ? "missing" : isTokenBundleExpired(bundle) ? "expired" : "authenticated";
|
|
4954
|
-
|
|
4950
|
+
return {
|
|
4951
|
+
server: server.server,
|
|
4952
|
+
status,
|
|
4953
|
+
...bundle?.expiresAt ? { expiresAt: bundle.expiresAt } : {},
|
|
4954
|
+
...bundle?.scope ? { scope: bundle.scope } : {}
|
|
4955
|
+
};
|
|
4956
|
+
});
|
|
4957
|
+
}
|
|
4958
|
+
function formatAuthRows(rows, format) {
|
|
4959
|
+
if (rows.length === 0) return format === "markdown" ? "## OAuth credentials\n\nNo configured remote OAuth servers found.\n" : "No configured remote OAuth servers found.\n";
|
|
4960
|
+
let output = "";
|
|
4961
|
+
if (format === "markdown") output += "## OAuth credentials\n\n";
|
|
4962
|
+
else output += "OAuth credentials\n\n";
|
|
4963
|
+
for (const row of rows) {
|
|
4964
|
+
const details = [row.expiresAt ? `expires ${row.expiresAt}` : void 0, row.scope ? `scope ${row.scope}` : void 0].filter(Boolean).join("; ");
|
|
4955
4965
|
if (format === "markdown") {
|
|
4956
|
-
|
|
4966
|
+
output += `- \`${row.server}\` — ${row.status}${details ? ` (${details})` : ""}\n`;
|
|
4957
4967
|
continue;
|
|
4958
4968
|
}
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
` Status: ${status}`,
|
|
4962
|
-
...
|
|
4963
|
-
...
|
|
4964
|
-
].join("\n")
|
|
4965
|
-
options.writeOut("\n\n");
|
|
4969
|
+
output += [
|
|
4970
|
+
row.server,
|
|
4971
|
+
` Status: ${row.status}`,
|
|
4972
|
+
...row.expiresAt ? [` Expires: ${row.expiresAt}`] : [],
|
|
4973
|
+
...row.scope ? [` Scope: ${row.scope}`] : []
|
|
4974
|
+
].join("\n") + "\n\n";
|
|
4966
4975
|
}
|
|
4976
|
+
return output;
|
|
4967
4977
|
}
|
|
4968
4978
|
function findAuthTarget(serverId, config = loadConfig()) {
|
|
4969
4979
|
return authTargets(config).find((server) => server.server === serverId);
|
|
@@ -5035,126 +5045,6 @@ function starterConfig() {
|
|
|
5035
5045
|
}, null, 2);
|
|
5036
5046
|
}
|
|
5037
5047
|
//#endregion
|
|
5038
|
-
//#region src/cli/inspection.ts
|
|
5039
|
-
function listCaplets(configWithSources, options) {
|
|
5040
|
-
const { config, sources, shadows } = configWithSources;
|
|
5041
|
-
return allCaplets(config).filter((server) => options.includeDisabled || !server.disabled).map((server) => ({
|
|
5042
|
-
server: server.server,
|
|
5043
|
-
backend: server.backend,
|
|
5044
|
-
name: server.name,
|
|
5045
|
-
description: server.description,
|
|
5046
|
-
disabled: server.disabled,
|
|
5047
|
-
status: initialServerStatus(server),
|
|
5048
|
-
source: sources[server.server]?.kind ?? "unknown",
|
|
5049
|
-
path: sources[server.server]?.path ?? null,
|
|
5050
|
-
shadows: shadows[server.server] ?? []
|
|
5051
|
-
})).sort((left, right) => left.server.localeCompare(right.server));
|
|
5052
|
-
}
|
|
5053
|
-
function initialServerStatus(server) {
|
|
5054
|
-
return server.disabled ? "disabled" : "not_started";
|
|
5055
|
-
}
|
|
5056
|
-
function allCaplets(config) {
|
|
5057
|
-
return [
|
|
5058
|
-
...Object.values(config.mcpServers),
|
|
5059
|
-
...Object.values(config.openapiEndpoints),
|
|
5060
|
-
...Object.values(config.graphqlEndpoints),
|
|
5061
|
-
...Object.values(config.httpApis),
|
|
5062
|
-
...Object.values(config.cliTools)
|
|
5063
|
-
];
|
|
5064
|
-
}
|
|
5065
|
-
function formatCapletList(rows, format = "plain") {
|
|
5066
|
-
return format === "markdown" ? formatCapletListMarkdown(rows) : formatCapletListPlain(rows);
|
|
5067
|
-
}
|
|
5068
|
-
function formatCapletListMarkdown(rows) {
|
|
5069
|
-
if (rows.length === 0) return "## Configured Caplets\n\nNo configured Caplets found.\n";
|
|
5070
|
-
const heading = [
|
|
5071
|
-
"## Configured Caplets",
|
|
5072
|
-
"",
|
|
5073
|
-
`${rows.length} ${rows.length === 1 ? "Caplet" : "Caplets"} shown.`,
|
|
5074
|
-
""
|
|
5075
|
-
];
|
|
5076
|
-
const entries = rows.flatMap((row) => [
|
|
5077
|
-
`- \`${row.server}\` — ${row.name}`,
|
|
5078
|
-
` - Backend: ${row.backend}`,
|
|
5079
|
-
` - Status: ${row.status}`,
|
|
5080
|
-
` - Source: ${row.source}`,
|
|
5081
|
-
...row.disabled ? [" - Disabled: true"] : [],
|
|
5082
|
-
...row.path ? [` - Path: ${row.path}`] : []
|
|
5083
|
-
]);
|
|
5084
|
-
const warnings = rows.flatMap((row) => row.shadows.map((shadow) => `Warning: ${formatSourceKind(row.source)} Caplet ${row.server} shadows ${formatSourceKind(shadow.kind)} Caplet at ${shadow.path}`));
|
|
5085
|
-
if (warnings.length === 0) return `${[...heading, ...entries].join("\n")}\n`;
|
|
5086
|
-
return `${[
|
|
5087
|
-
...heading,
|
|
5088
|
-
...entries,
|
|
5089
|
-
"",
|
|
5090
|
-
"Warnings:",
|
|
5091
|
-
...warnings.map((warning) => `- ${warning}`)
|
|
5092
|
-
].join("\n")}\n`;
|
|
5093
|
-
}
|
|
5094
|
-
function formatCapletListPlain(rows) {
|
|
5095
|
-
if (rows.length === 0) return "No configured Caplets found.\n";
|
|
5096
|
-
const entries = rows.map((row) => [
|
|
5097
|
-
row.server,
|
|
5098
|
-
` Name: ${row.name}`,
|
|
5099
|
-
` Backend: ${row.backend}`,
|
|
5100
|
-
` Status: ${row.status}`,
|
|
5101
|
-
` Source: ${row.source}`,
|
|
5102
|
-
...row.disabled ? [" Disabled: true"] : [],
|
|
5103
|
-
...row.path ? [` Path: ${row.path}`] : []
|
|
5104
|
-
].join("\n")).join("\n\n");
|
|
5105
|
-
const warnings = rows.flatMap((row) => row.shadows.map((shadow) => `Warning: ${formatSourceKind(row.source)} Caplet ${row.server} shadows ${formatSourceKind(shadow.kind)} Caplet at ${shadow.path}`));
|
|
5106
|
-
if (warnings.length === 0) return `Configured Caplets (${rows.length})\n\n${entries}\n`;
|
|
5107
|
-
return `Configured Caplets (${rows.length})\n\n${entries}\n\n${warnings.join("\n")}\n`;
|
|
5108
|
-
}
|
|
5109
|
-
function formatSourceKind(kind) {
|
|
5110
|
-
if (kind.startsWith("project")) return "project";
|
|
5111
|
-
if (kind.startsWith("global")) return "global";
|
|
5112
|
-
return kind;
|
|
5113
|
-
}
|
|
5114
|
-
function resolveCliConfigPaths(envConfigPath, authDir) {
|
|
5115
|
-
const configPath = resolveConfigPath(envConfigPath);
|
|
5116
|
-
const effectiveAuthDir = authDir ?? DEFAULT_AUTH_DIR;
|
|
5117
|
-
return {
|
|
5118
|
-
userConfig: configPath,
|
|
5119
|
-
projectConfig: resolveProjectConfigPath(),
|
|
5120
|
-
userRoot: resolveCapletsRoot(configPath),
|
|
5121
|
-
stateRoot: dirname(effectiveAuthDir),
|
|
5122
|
-
projectRoot: resolveProjectCapletsRoot(),
|
|
5123
|
-
authDir: effectiveAuthDir,
|
|
5124
|
-
envConfig: envConfigPath ?? null
|
|
5125
|
-
};
|
|
5126
|
-
}
|
|
5127
|
-
function formatConfigPaths(paths, format = "plain") {
|
|
5128
|
-
if (format === "markdown") return formatConfigPathsMarkdown(paths);
|
|
5129
|
-
return formatConfigPathsPlain(paths);
|
|
5130
|
-
}
|
|
5131
|
-
function formatConfigPathsMarkdown(paths) {
|
|
5132
|
-
return [
|
|
5133
|
-
"## Caplets paths",
|
|
5134
|
-
"",
|
|
5135
|
-
`- User config: ${paths.userConfig}`,
|
|
5136
|
-
`- Project config: ${paths.projectConfig}`,
|
|
5137
|
-
`- User Caplets root: ${paths.userRoot}`,
|
|
5138
|
-
`- State root: ${paths.stateRoot}`,
|
|
5139
|
-
`- Project Caplets root: ${paths.projectRoot}`,
|
|
5140
|
-
`- Auth directory: ${paths.authDir}`,
|
|
5141
|
-
`- CAPLETS_CONFIG: ${paths.envConfig ?? "unset"}`
|
|
5142
|
-
].join("\n") + "\n";
|
|
5143
|
-
}
|
|
5144
|
-
function formatConfigPathsPlain(paths) {
|
|
5145
|
-
return [
|
|
5146
|
-
"Caplets paths",
|
|
5147
|
-
"",
|
|
5148
|
-
`User config: ${paths.userConfig}`,
|
|
5149
|
-
`Project config: ${paths.projectConfig}`,
|
|
5150
|
-
`User root: ${paths.userRoot}`,
|
|
5151
|
-
`State root: ${paths.stateRoot}`,
|
|
5152
|
-
`Project root: ${paths.projectRoot}`,
|
|
5153
|
-
`Auth directory: ${paths.authDir}`,
|
|
5154
|
-
`CAPLETS_CONFIG: ${paths.envConfig ?? "unset"}`
|
|
5155
|
-
].join("\n") + "\n";
|
|
5156
|
-
}
|
|
5157
|
-
//#endregion
|
|
5158
5048
|
//#region src/cli/install.ts
|
|
5159
5049
|
function installCaplets(repo, options = {}) {
|
|
5160
5050
|
const source = resolveInstallSource(repo);
|
|
@@ -5390,6 +5280,96 @@ function nearestExistingParent(path) {
|
|
|
5390
5280
|
return nearestExistingParent(parent);
|
|
5391
5281
|
}
|
|
5392
5282
|
//#endregion
|
|
5283
|
+
//#region src/remote-control/client.ts
|
|
5284
|
+
var RemoteControlClient = class {
|
|
5285
|
+
#baseUrl;
|
|
5286
|
+
#requestInit;
|
|
5287
|
+
#fetch;
|
|
5288
|
+
constructor(options) {
|
|
5289
|
+
this.#baseUrl = options.baseUrl;
|
|
5290
|
+
this.#requestInit = options.requestInit;
|
|
5291
|
+
this.#fetch = options.fetch ?? fetch;
|
|
5292
|
+
}
|
|
5293
|
+
async request(command, args) {
|
|
5294
|
+
const controlUrl = controlUrlForBase(this.#baseUrl);
|
|
5295
|
+
let response;
|
|
5296
|
+
try {
|
|
5297
|
+
response = await this.#fetch(controlUrl, {
|
|
5298
|
+
...this.#requestInit,
|
|
5299
|
+
method: "POST",
|
|
5300
|
+
headers: mergeJsonHeaders(this.#requestInit.headers),
|
|
5301
|
+
body: JSON.stringify({
|
|
5302
|
+
command,
|
|
5303
|
+
arguments: args
|
|
5304
|
+
})
|
|
5305
|
+
});
|
|
5306
|
+
} catch (error) {
|
|
5307
|
+
throw new CapletsError("SERVER_UNAVAILABLE", `Could not connect to Caplets server at ${safeBaseUrl(this.#baseUrl)}.`, toSafeError(error, "SERVER_UNAVAILABLE"));
|
|
5308
|
+
}
|
|
5309
|
+
if (response.status === 401 || response.status === 403) throw new CapletsError("AUTH_FAILED", "Caplets server authentication failed. Check CAPLETS_SERVER_USER and CAPLETS_SERVER_PASSWORD.");
|
|
5310
|
+
if (!response.ok) throw new CapletsError("SERVER_UNAVAILABLE", `Caplets server at ${safeBaseUrl(this.#baseUrl)} returned HTTP ${response.status}.`);
|
|
5311
|
+
const payload = await parseRemoteCliResponse(response);
|
|
5312
|
+
if (!payload.ok) throw new CapletsError(payload.error.code, redactRemoteMessage(payload.error.message), payload.error.nextAction === void 0 ? void 0 : { nextAction: payload.error.nextAction });
|
|
5313
|
+
return payload.result;
|
|
5314
|
+
}
|
|
5315
|
+
};
|
|
5316
|
+
function mergeJsonHeaders(headers) {
|
|
5317
|
+
const merged = new Headers(headers);
|
|
5318
|
+
merged.set("content-type", "application/json");
|
|
5319
|
+
return merged;
|
|
5320
|
+
}
|
|
5321
|
+
function safeBaseUrl(baseUrl) {
|
|
5322
|
+
const safe = new URL(baseUrl.href);
|
|
5323
|
+
safe.username = "";
|
|
5324
|
+
safe.password = "";
|
|
5325
|
+
safe.search = "";
|
|
5326
|
+
safe.hash = "";
|
|
5327
|
+
return safe.toString();
|
|
5328
|
+
}
|
|
5329
|
+
async function parseRemoteCliResponse(response) {
|
|
5330
|
+
let payload;
|
|
5331
|
+
try {
|
|
5332
|
+
payload = await response.json();
|
|
5333
|
+
} catch (error) {
|
|
5334
|
+
throw invalidRemoteControlResponse(error);
|
|
5335
|
+
}
|
|
5336
|
+
if (!isRecord(payload)) throw invalidRemoteControlResponse();
|
|
5337
|
+
if (payload.ok === true) {
|
|
5338
|
+
if (!("result" in payload)) throw invalidRemoteControlResponse();
|
|
5339
|
+
return {
|
|
5340
|
+
ok: true,
|
|
5341
|
+
result: payload.result
|
|
5342
|
+
};
|
|
5343
|
+
}
|
|
5344
|
+
if (payload.ok === false) {
|
|
5345
|
+
const error = payload.error;
|
|
5346
|
+
if (!isRecord(error) || typeof error.code !== "string" || typeof error.message !== "string") throw invalidRemoteControlResponse();
|
|
5347
|
+
if ("nextAction" in error && error.nextAction !== void 0 && typeof error.nextAction !== "string") throw invalidRemoteControlResponse();
|
|
5348
|
+
const errorResponse = {
|
|
5349
|
+
ok: false,
|
|
5350
|
+
error: {
|
|
5351
|
+
code: isCapletsErrorCode(error.code) ? error.code : "DOWNSTREAM_TOOL_ERROR",
|
|
5352
|
+
message: error.message
|
|
5353
|
+
}
|
|
5354
|
+
};
|
|
5355
|
+
if (typeof error.nextAction === "string") errorResponse.error.nextAction = error.nextAction;
|
|
5356
|
+
return errorResponse;
|
|
5357
|
+
}
|
|
5358
|
+
throw invalidRemoteControlResponse();
|
|
5359
|
+
}
|
|
5360
|
+
function invalidRemoteControlResponse(cause) {
|
|
5361
|
+
return new CapletsError("DOWNSTREAM_PROTOCOL_ERROR", "Caplets server returned an invalid remote control response.", cause === void 0 ? void 0 : toSafeError(cause, "DOWNSTREAM_PROTOCOL_ERROR"));
|
|
5362
|
+
}
|
|
5363
|
+
function isRecord(value) {
|
|
5364
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5365
|
+
}
|
|
5366
|
+
function isCapletsErrorCode(value) {
|
|
5367
|
+
return CAPLETS_ERROR_CODES.includes(value);
|
|
5368
|
+
}
|
|
5369
|
+
function redactRemoteMessage(message) {
|
|
5370
|
+
return String(redactSecrets(message)).replace(/\b(authorization\s*:\s*(?:basic|bearer)\s+)[^\s,;]+/giu, "$1[REDACTED]").replace(/\b((?:access_)?token=)[^\s,;]+/giu, "$1[REDACTED]").replace(/\b((?:token|secret|authorization|auth|api[-_]?key|password|credential|clientsecret|client_secret|code|refresh(?:_token)?)\s*[=:]\s*)[^\s,;]+/giu, "$1[REDACTED]");
|
|
5371
|
+
}
|
|
5372
|
+
//#endregion
|
|
5393
5373
|
//#region ../../node_modules/.pnpm/hono@4.12.19/node_modules/hono/dist/compose.js
|
|
5394
5374
|
var compose = (middleware, onError, onNotFound) => {
|
|
5395
5375
|
return (context, next) => {
|
|
@@ -8309,30 +8289,372 @@ var logger = (fn = console.log) => {
|
|
|
8309
8289
|
};
|
|
8310
8290
|
};
|
|
8311
8291
|
//#endregion
|
|
8292
|
+
//#region src/remote-control/dispatch.ts
|
|
8293
|
+
const ENGINE_COMMANDS = new Set([
|
|
8294
|
+
"get_caplet",
|
|
8295
|
+
"check_backend",
|
|
8296
|
+
"list_tools",
|
|
8297
|
+
"search_tools",
|
|
8298
|
+
"get_tool",
|
|
8299
|
+
"call_tool",
|
|
8300
|
+
"list_resources",
|
|
8301
|
+
"search_resources",
|
|
8302
|
+
"list_resource_templates",
|
|
8303
|
+
"read_resource",
|
|
8304
|
+
"list_prompts",
|
|
8305
|
+
"search_prompts",
|
|
8306
|
+
"get_prompt",
|
|
8307
|
+
"complete"
|
|
8308
|
+
]);
|
|
8309
|
+
async function dispatchRemoteCliRequest(request, context) {
|
|
8310
|
+
try {
|
|
8311
|
+
return {
|
|
8312
|
+
ok: true,
|
|
8313
|
+
result: await dispatch(request, context)
|
|
8314
|
+
};
|
|
8315
|
+
} catch (error) {
|
|
8316
|
+
const safe = toSafeError(error);
|
|
8317
|
+
const action = nextAction(safe.details);
|
|
8318
|
+
return {
|
|
8319
|
+
ok: false,
|
|
8320
|
+
error: {
|
|
8321
|
+
code: safe.code,
|
|
8322
|
+
message: redactControlErrorMessage(safe.message),
|
|
8323
|
+
...action ? { nextAction: action } : {}
|
|
8324
|
+
}
|
|
8325
|
+
};
|
|
8326
|
+
}
|
|
8327
|
+
}
|
|
8328
|
+
async function dispatch(request, context) {
|
|
8329
|
+
assertObject(request, "remote control request");
|
|
8330
|
+
assertObject(request.arguments, "remote control request arguments");
|
|
8331
|
+
if (request.command === "list") return listCaplets(loadConfigWithSources(context.configPath, context.projectConfigPath), { includeDisabled: optionalBoolean(request.arguments, "includeDisabled") ?? false });
|
|
8332
|
+
if (ENGINE_COMMANDS.has(request.command)) {
|
|
8333
|
+
const caplet = requiredString(request.arguments, "caplet");
|
|
8334
|
+
const toolRequest = requiredEngineRequest(request.arguments, request.command);
|
|
8335
|
+
const engine = new CapletsEngine(context);
|
|
8336
|
+
try {
|
|
8337
|
+
return await engine.execute(caplet, toolRequest);
|
|
8338
|
+
} finally {
|
|
8339
|
+
await engine.close();
|
|
8340
|
+
}
|
|
8341
|
+
}
|
|
8342
|
+
if (request.command === "init") return {
|
|
8343
|
+
remote: true,
|
|
8344
|
+
path: initConfig({
|
|
8345
|
+
...optionalProp("path", context.configPath),
|
|
8346
|
+
...optionalProp("force", optionalBoolean(request.arguments, "force"))
|
|
8347
|
+
})
|
|
8348
|
+
};
|
|
8349
|
+
if (request.command === "add") return dispatchAdd(request.arguments, context);
|
|
8350
|
+
if (request.command === "install") return {
|
|
8351
|
+
remote: true,
|
|
8352
|
+
...installCaplets(requiredString(request.arguments, "repo"), {
|
|
8353
|
+
...optionalProp("capletIds", optionalStringArray(request.arguments, "capletIds")),
|
|
8354
|
+
destinationRoot: context.projectCapletsRoot,
|
|
8355
|
+
...optionalProp("force", optionalBoolean(request.arguments, "force"))
|
|
8356
|
+
})
|
|
8357
|
+
};
|
|
8358
|
+
if (request.command === "complete_cli") {
|
|
8359
|
+
const shell = optionalString(request.arguments, "shell") ?? "bash";
|
|
8360
|
+
if (!completionShells.includes(shell)) return [];
|
|
8361
|
+
const engine = new CapletsEngine(context);
|
|
8362
|
+
try {
|
|
8363
|
+
return await engine.completeCliWords(optionalStringArray(request.arguments, "words") ?? [""]);
|
|
8364
|
+
} finally {
|
|
8365
|
+
await engine.close();
|
|
8366
|
+
}
|
|
8367
|
+
}
|
|
8368
|
+
if (request.command === "auth_list") return listAuthRows({
|
|
8369
|
+
...optionalProp("configPath", context.configPath),
|
|
8370
|
+
...optionalProp("authDir", context.authDir)
|
|
8371
|
+
});
|
|
8372
|
+
if (request.command === "auth_logout") return logoutAuthResult(requiredString(request.arguments, "server"), {
|
|
8373
|
+
...optionalProp("configPath", context.configPath),
|
|
8374
|
+
...optionalProp("authDir", context.authDir)
|
|
8375
|
+
});
|
|
8376
|
+
if (request.command === "auth_login_start") return startRemoteAuthLogin(requiredString(request.arguments, "server"), context);
|
|
8377
|
+
if (request.command === "auth_login_complete") return completeRemoteAuthLogin(requiredString(request.arguments, "flowId"), requiredString(request.arguments, "callbackUrl"), context);
|
|
8378
|
+
throw new CapletsError("UNKNOWN_OPERATION", `Unsupported remote control command ${request.command}`);
|
|
8379
|
+
}
|
|
8380
|
+
async function startRemoteAuthLogin(serverId, context) {
|
|
8381
|
+
if (!context.authFlowStore || !context.controlCallbackBaseUrl) throw new CapletsError("REQUEST_INVALID", "Remote auth login is not available on this server");
|
|
8382
|
+
const config = loadConfigWithSources(context.configPath, context.projectConfigPath).config;
|
|
8383
|
+
const target = findAuthTarget(serverId, config);
|
|
8384
|
+
assertLoginTarget(target, serverId);
|
|
8385
|
+
const flowId = randomUUID();
|
|
8386
|
+
const baseUrl = context.controlCallbackBaseUrl.endsWith("/") ? context.controlCallbackBaseUrl : `${context.controlCallbackBaseUrl}/`;
|
|
8387
|
+
const redirectUri = new URL(`auth/callback/${flowId}`, baseUrl).toString();
|
|
8388
|
+
const started = target.backend === "mcp" ? await startOAuthFlow(target, {
|
|
8389
|
+
redirectUri,
|
|
8390
|
+
...optionalProp("authDir", context.authDir)
|
|
8391
|
+
}) : await startGenericOAuthFlow(target, {
|
|
8392
|
+
redirectUri,
|
|
8393
|
+
...optionalProp("authDir", context.authDir)
|
|
8394
|
+
});
|
|
8395
|
+
if (!started.authorizationUrl) return {
|
|
8396
|
+
server: serverId,
|
|
8397
|
+
authenticated: true
|
|
8398
|
+
};
|
|
8399
|
+
const flow = context.authFlowStore.create({
|
|
8400
|
+
server: serverId,
|
|
8401
|
+
authorizationUrl: started.authorizationUrl,
|
|
8402
|
+
complete: started.complete
|
|
8403
|
+
}, flowId);
|
|
8404
|
+
return {
|
|
8405
|
+
server: serverId,
|
|
8406
|
+
flowId: flow.id,
|
|
8407
|
+
authorizationUrl: flow.authorizationUrl
|
|
8408
|
+
};
|
|
8409
|
+
}
|
|
8410
|
+
async function completeRemoteAuthLogin(flowId, callbackUrl, context) {
|
|
8411
|
+
const flow = context.authFlowStore?.get(flowId);
|
|
8412
|
+
if (!flow) throw new CapletsError("REQUEST_INVALID", `Unknown auth flow ${flowId}`);
|
|
8413
|
+
context.authFlowStore?.delete(flowId);
|
|
8414
|
+
await flow.complete(callbackUrl);
|
|
8415
|
+
return {
|
|
8416
|
+
server: flow.server,
|
|
8417
|
+
authenticated: true
|
|
8418
|
+
};
|
|
8419
|
+
}
|
|
8420
|
+
function dispatchAdd(args, context) {
|
|
8421
|
+
const kind = requiredString(args, "kind");
|
|
8422
|
+
const id = requiredString(args, "id");
|
|
8423
|
+
const options = remoteAddOptions$1(kind, optionalObject(args, "options"));
|
|
8424
|
+
switch (kind) {
|
|
8425
|
+
case "cli": return {
|
|
8426
|
+
remote: true,
|
|
8427
|
+
label: "CLI",
|
|
8428
|
+
...addCliCaplet(id, {
|
|
8429
|
+
...options,
|
|
8430
|
+
destinationRoot: context.projectCapletsRoot,
|
|
8431
|
+
print: false
|
|
8432
|
+
})
|
|
8433
|
+
};
|
|
8434
|
+
case "mcp": return {
|
|
8435
|
+
remote: true,
|
|
8436
|
+
label: "MCP",
|
|
8437
|
+
...addMcpCaplet(id, {
|
|
8438
|
+
...options,
|
|
8439
|
+
destinationRoot: context.projectCapletsRoot,
|
|
8440
|
+
print: false
|
|
8441
|
+
})
|
|
8442
|
+
};
|
|
8443
|
+
case "openapi": return {
|
|
8444
|
+
remote: true,
|
|
8445
|
+
label: "OpenAPI",
|
|
8446
|
+
...addOpenApiCaplet(id, {
|
|
8447
|
+
...options,
|
|
8448
|
+
destinationRoot: context.projectCapletsRoot,
|
|
8449
|
+
print: false
|
|
8450
|
+
})
|
|
8451
|
+
};
|
|
8452
|
+
case "graphql": return {
|
|
8453
|
+
remote: true,
|
|
8454
|
+
label: "GraphQL",
|
|
8455
|
+
...addGraphqlCaplet(id, {
|
|
8456
|
+
...options,
|
|
8457
|
+
destinationRoot: context.projectCapletsRoot,
|
|
8458
|
+
print: false
|
|
8459
|
+
})
|
|
8460
|
+
};
|
|
8461
|
+
case "http": return {
|
|
8462
|
+
remote: true,
|
|
8463
|
+
label: "HTTP",
|
|
8464
|
+
...addHttpCaplet(id, {
|
|
8465
|
+
...options,
|
|
8466
|
+
destinationRoot: context.projectCapletsRoot,
|
|
8467
|
+
print: false
|
|
8468
|
+
})
|
|
8469
|
+
};
|
|
8470
|
+
default: throw new CapletsError("REQUEST_INVALID", "add.kind must be cli, mcp, openapi, graphql, or http");
|
|
8471
|
+
}
|
|
8472
|
+
}
|
|
8473
|
+
function optionalProp(key, value) {
|
|
8474
|
+
return value === void 0 ? {} : { [key]: value };
|
|
8475
|
+
}
|
|
8476
|
+
function assertObject(value, label) {
|
|
8477
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new CapletsError("REQUEST_INVALID", `${label} must be an object`);
|
|
8478
|
+
}
|
|
8479
|
+
function requiredString(args, key) {
|
|
8480
|
+
const value = args[key];
|
|
8481
|
+
if (typeof value !== "string" || value.length === 0) throw new CapletsError("REQUEST_INVALID", `${key} must be a non-empty string`);
|
|
8482
|
+
return value;
|
|
8483
|
+
}
|
|
8484
|
+
function optionalString(args, key) {
|
|
8485
|
+
const value = args[key];
|
|
8486
|
+
if (value === void 0) return;
|
|
8487
|
+
if (typeof value !== "string") throw new CapletsError("REQUEST_INVALID", `${key} must be a string`);
|
|
8488
|
+
return value;
|
|
8489
|
+
}
|
|
8490
|
+
function optionalObject(args, key) {
|
|
8491
|
+
const value = args[key];
|
|
8492
|
+
if (value === void 0) return {};
|
|
8493
|
+
assertObject(value, key);
|
|
8494
|
+
return value;
|
|
8495
|
+
}
|
|
8496
|
+
function requiredEngineRequest(args, command) {
|
|
8497
|
+
const toolRequest = optionalObject(args, "request");
|
|
8498
|
+
if (typeof toolRequest.operation !== "string") throw new CapletsError("REQUEST_INVALID", "request.operation must be a string");
|
|
8499
|
+
if (toolRequest.operation !== command) throw new CapletsError("REQUEST_INVALID", `request.operation must match remote command ${command}`);
|
|
8500
|
+
return toolRequest;
|
|
8501
|
+
}
|
|
8502
|
+
function remoteAddOptions$1(kind, options) {
|
|
8503
|
+
rejectServerOwnedAddOptions(options);
|
|
8504
|
+
switch (kind) {
|
|
8505
|
+
case "cli": return pickOptions(options, {
|
|
8506
|
+
repo: "string",
|
|
8507
|
+
include: "string",
|
|
8508
|
+
command: "string",
|
|
8509
|
+
force: "boolean"
|
|
8510
|
+
});
|
|
8511
|
+
case "mcp": return pickOptions(options, {
|
|
8512
|
+
command: "string",
|
|
8513
|
+
arg: "string-array",
|
|
8514
|
+
cwd: "string",
|
|
8515
|
+
env: "string-array",
|
|
8516
|
+
url: "string",
|
|
8517
|
+
transport: "string",
|
|
8518
|
+
tokenEnv: "string",
|
|
8519
|
+
force: "boolean"
|
|
8520
|
+
});
|
|
8521
|
+
case "openapi": return pickOptions(options, {
|
|
8522
|
+
spec: "string",
|
|
8523
|
+
baseUrl: "string",
|
|
8524
|
+
tokenEnv: "string",
|
|
8525
|
+
force: "boolean"
|
|
8526
|
+
});
|
|
8527
|
+
case "graphql": return pickOptions(options, {
|
|
8528
|
+
endpointUrl: "string",
|
|
8529
|
+
schema: "string",
|
|
8530
|
+
introspection: "boolean",
|
|
8531
|
+
tokenEnv: "string",
|
|
8532
|
+
force: "boolean"
|
|
8533
|
+
});
|
|
8534
|
+
case "http": return pickOptions(options, {
|
|
8535
|
+
baseUrl: "string",
|
|
8536
|
+
action: "string-array",
|
|
8537
|
+
tokenEnv: "string",
|
|
8538
|
+
force: "boolean"
|
|
8539
|
+
});
|
|
8540
|
+
default: return options;
|
|
8541
|
+
}
|
|
8542
|
+
}
|
|
8543
|
+
function pickOptions(options, schema) {
|
|
8544
|
+
const next = {};
|
|
8545
|
+
for (const [key, type] of Object.entries(schema)) {
|
|
8546
|
+
const value = options[key];
|
|
8547
|
+
if (value === void 0) continue;
|
|
8548
|
+
validateOptionType(key, value, type);
|
|
8549
|
+
next[key] = value;
|
|
8550
|
+
}
|
|
8551
|
+
return next;
|
|
8552
|
+
}
|
|
8553
|
+
function rejectServerOwnedAddOptions(options) {
|
|
8554
|
+
if ("output" in options) throw new CapletsError("REQUEST_INVALID", "Remote add output is not supported remotely; the server owns destinationRoot and output path selection");
|
|
8555
|
+
for (const key of ["destinationRoot", "print"]) if (key in options) throw new CapletsError("REQUEST_INVALID", `Remote add ${key} is not supported remotely; the server owns destinationRoot and print behavior`);
|
|
8556
|
+
}
|
|
8557
|
+
function validateOptionType(key, value, type) {
|
|
8558
|
+
if (type === "string" && typeof value !== "string") throw new CapletsError("REQUEST_INVALID", `add.options.${key} must be a string`);
|
|
8559
|
+
if (type === "boolean" && typeof value !== "boolean") throw new CapletsError("REQUEST_INVALID", `add.options.${key} must be a boolean`);
|
|
8560
|
+
if (type === "string-array") {
|
|
8561
|
+
if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) throw new CapletsError("REQUEST_INVALID", `add.options.${key} must be an array of strings`);
|
|
8562
|
+
}
|
|
8563
|
+
}
|
|
8564
|
+
function optionalBoolean(args, key) {
|
|
8565
|
+
const value = args[key];
|
|
8566
|
+
if (value === void 0) return;
|
|
8567
|
+
if (typeof value !== "boolean") throw new CapletsError("REQUEST_INVALID", `${key} must be a boolean`);
|
|
8568
|
+
return value;
|
|
8569
|
+
}
|
|
8570
|
+
function optionalStringArray(args, key) {
|
|
8571
|
+
const value = args[key];
|
|
8572
|
+
if (value === void 0) return;
|
|
8573
|
+
if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) throw new CapletsError("REQUEST_INVALID", `${key} must be an array of strings`);
|
|
8574
|
+
return value;
|
|
8575
|
+
}
|
|
8576
|
+
function nextAction(details) {
|
|
8577
|
+
if (details && typeof details === "object" && "nextAction" in details) {
|
|
8578
|
+
const value = details.nextAction;
|
|
8579
|
+
return typeof value === "string" ? value : void 0;
|
|
8580
|
+
}
|
|
8581
|
+
}
|
|
8582
|
+
function redactControlErrorMessage(message) {
|
|
8583
|
+
return message.replace(/(["'])(authorization|(?:access[_-]?)?token|refresh(?:[_-]?token)?|password|client[_-]?secret|clientsecret|api[-_]?key|apikey|secret|credential|code)\1\s*:\s*(["'])(?:\\.|[^\\])*?\3/giu, "$1$2$1:$3[REDACTED]$3").replace(/\b(authorization\s*:\s*(?:basic|bearer)\s+)[^\s,;]+/giu, "$1[REDACTED]").replace(/\b((?:access[_-]?)?token|refresh(?:[_-]?token)?|password|client[_-]?secret|clientsecret|api[-_]?key|apikey|secret|credential|code)(\s*[=:]\s*)[^\s,;]+/giu, "$1$2[REDACTED]");
|
|
8584
|
+
}
|
|
8585
|
+
//#endregion
|
|
8586
|
+
//#region src/remote-control/auth-flow.ts
|
|
8587
|
+
const DEFAULT_AUTH_FLOW_TTL_MS = 600 * 1e3;
|
|
8588
|
+
var RemoteAuthFlowStore = class {
|
|
8589
|
+
options;
|
|
8590
|
+
flows = /* @__PURE__ */ new Map();
|
|
8591
|
+
constructor(options = {}) {
|
|
8592
|
+
this.options = options;
|
|
8593
|
+
}
|
|
8594
|
+
create(flow, id = randomUUID()) {
|
|
8595
|
+
this.pruneExpired();
|
|
8596
|
+
const created = {
|
|
8597
|
+
id,
|
|
8598
|
+
createdAt: this.now(),
|
|
8599
|
+
...flow
|
|
8600
|
+
};
|
|
8601
|
+
this.flows.set(created.id, created);
|
|
8602
|
+
return { ...created };
|
|
8603
|
+
}
|
|
8604
|
+
get(id) {
|
|
8605
|
+
this.pruneExpired();
|
|
8606
|
+
const flow = this.flows.get(id);
|
|
8607
|
+
if (flow && this.isExpired(flow)) {
|
|
8608
|
+
this.flows.delete(id);
|
|
8609
|
+
return;
|
|
8610
|
+
}
|
|
8611
|
+
return flow;
|
|
8612
|
+
}
|
|
8613
|
+
delete(id) {
|
|
8614
|
+
this.flows.delete(id);
|
|
8615
|
+
}
|
|
8616
|
+
pruneExpired() {
|
|
8617
|
+
for (const [id, flow] of this.flows) if (this.isExpired(flow)) this.flows.delete(id);
|
|
8618
|
+
}
|
|
8619
|
+
isExpired(flow) {
|
|
8620
|
+
return this.now() - flow.createdAt > (this.options.ttlMs ?? DEFAULT_AUTH_FLOW_TTL_MS);
|
|
8621
|
+
}
|
|
8622
|
+
now() {
|
|
8623
|
+
return this.options.now?.() ?? Date.now();
|
|
8624
|
+
}
|
|
8625
|
+
};
|
|
8626
|
+
//#endregion
|
|
8312
8627
|
//#region src/serve/http.ts
|
|
8313
8628
|
function createHttpServeApp(options, engine, io = {}) {
|
|
8314
8629
|
const app = new Hono();
|
|
8315
8630
|
const sessions = /* @__PURE__ */ new Map();
|
|
8316
8631
|
const writeErr = io.writeErr ?? process.stderr.write.bind(process.stderr);
|
|
8632
|
+
const paths = servicePaths(options.path);
|
|
8633
|
+
const authFlowStore = io.authFlowStore ?? new RemoteAuthFlowStore();
|
|
8317
8634
|
app.use("*", logger((message, ...rest) => {
|
|
8318
8635
|
writeErr(`${[message, ...rest].join(" ")}\n`);
|
|
8319
8636
|
}));
|
|
8320
|
-
app.get(
|
|
8637
|
+
app.get(paths.base, (c) => c.json({
|
|
8321
8638
|
name: "caplets",
|
|
8322
8639
|
transport: "http",
|
|
8323
|
-
|
|
8324
|
-
|
|
8640
|
+
base: paths.base,
|
|
8641
|
+
mcp: paths.mcp,
|
|
8642
|
+
control: paths.control,
|
|
8643
|
+
health: paths.health,
|
|
8325
8644
|
auth: {
|
|
8326
8645
|
type: "basic",
|
|
8327
8646
|
enabled: options.auth.enabled
|
|
8328
8647
|
}
|
|
8329
8648
|
}));
|
|
8330
|
-
app.get(
|
|
8649
|
+
app.get(paths.health, (c) => c.json({
|
|
8331
8650
|
status: "ok",
|
|
8332
8651
|
transport: "http",
|
|
8333
|
-
|
|
8652
|
+
base: paths.base,
|
|
8653
|
+
mcpPath: paths.mcp,
|
|
8654
|
+
controlPath: paths.control,
|
|
8655
|
+
healthPath: paths.health
|
|
8334
8656
|
}));
|
|
8335
|
-
app.all(
|
|
8657
|
+
app.all(paths.mcp, basicAuth(options.auth), async (c) => {
|
|
8336
8658
|
const sessionId = c.req.header("mcp-session-id");
|
|
8337
8659
|
if (sessionId) {
|
|
8338
8660
|
const existing = sessions.get(sessionId);
|
|
@@ -8363,6 +8685,36 @@ function createHttpServeApp(options, engine, io = {}) {
|
|
|
8363
8685
|
sessions.set(nextSessionId, session);
|
|
8364
8686
|
return session.transport.handleRequest(c);
|
|
8365
8687
|
});
|
|
8688
|
+
app.post(paths.control, basicAuth(options.auth), async (c) => {
|
|
8689
|
+
let request;
|
|
8690
|
+
try {
|
|
8691
|
+
const parsed = await c.req.json();
|
|
8692
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new CapletsError("REQUEST_INVALID", "Control request JSON must be an object");
|
|
8693
|
+
request = parsed;
|
|
8694
|
+
} catch (error) {
|
|
8695
|
+
const safe = toSafeError(error instanceof CapletsError ? error : new CapletsError("REQUEST_INVALID", "Control request body must be valid JSON", error), "REQUEST_INVALID");
|
|
8696
|
+
return c.json({
|
|
8697
|
+
ok: false,
|
|
8698
|
+
error: {
|
|
8699
|
+
code: safe.code,
|
|
8700
|
+
message: safe.message
|
|
8701
|
+
}
|
|
8702
|
+
});
|
|
8703
|
+
}
|
|
8704
|
+
return c.json(await dispatchRemoteCliRequest(request, controlContext(io, writeErr, authFlowStore, c.req.url, paths.control, options.trustProxy, (name) => c.req.header(name))));
|
|
8705
|
+
});
|
|
8706
|
+
app.get(routePath(paths.control, "auth/callback/:flowId"), async (c) => {
|
|
8707
|
+
const flowId = c.req.param("flowId");
|
|
8708
|
+
const result = await dispatchRemoteCliRequest({
|
|
8709
|
+
command: "auth_login_complete",
|
|
8710
|
+
arguments: {
|
|
8711
|
+
flowId,
|
|
8712
|
+
callbackUrl: c.req.url
|
|
8713
|
+
}
|
|
8714
|
+
}, controlContext(io, writeErr, authFlowStore, c.req.url, paths.control, options.trustProxy, (name) => c.req.header(name)));
|
|
8715
|
+
if (!result.ok) writeErr(`Caplets authentication failed for flow ${flowId}: ${result.error.message}\n`);
|
|
8716
|
+
return result.ok ? c.text("Caplets authentication complete. You can return to your terminal.") : c.text("Caplets authentication failed. Check server logs for details.", 400);
|
|
8717
|
+
});
|
|
8366
8718
|
app.notFound((c) => c.json({ error: "not_found" }, 404));
|
|
8367
8719
|
app.closeCapletsSessions = async () => {
|
|
8368
8720
|
await Promise.allSettled([...sessions.values()].map(async (session) => {
|
|
@@ -8373,19 +8725,66 @@ function createHttpServeApp(options, engine, io = {}) {
|
|
|
8373
8725
|
if (options.warnUnauthenticatedNetwork) writeErr(`Warning: Caplets MCP HTTP server is listening on ${options.host} without authentication.\n`);
|
|
8374
8726
|
return app;
|
|
8375
8727
|
}
|
|
8728
|
+
function controlContext(io, writeErr, authFlowStore, requestUrl, controlPath, trustProxy, header) {
|
|
8729
|
+
return {
|
|
8730
|
+
...io.control,
|
|
8731
|
+
projectCapletsRoot: io.control?.projectCapletsRoot ?? resolveProjectCapletsRoot(),
|
|
8732
|
+
authFlowStore,
|
|
8733
|
+
controlCallbackBaseUrl: new URL(controlPath, publicRequestOrigin(requestUrl, trustProxy, header)).toString(),
|
|
8734
|
+
writeErr
|
|
8735
|
+
};
|
|
8736
|
+
}
|
|
8737
|
+
function publicRequestOrigin(requestUrl, trustProxy, header) {
|
|
8738
|
+
const url = new URL(requestUrl);
|
|
8739
|
+
if (!trustProxy) return `${url.protocol.slice(0, -1)}://${header("host") ?? url.host}`;
|
|
8740
|
+
const forwardedProto = firstForwardedValue(header("x-forwarded-proto"));
|
|
8741
|
+
const forwardedHost = firstForwardedValue(header("x-forwarded-host"));
|
|
8742
|
+
return `${forwardedProto === "http" || forwardedProto === "https" ? forwardedProto : url.protocol.slice(0, -1)}://${forwardedHost ?? header("host") ?? url.host}`;
|
|
8743
|
+
}
|
|
8744
|
+
function firstForwardedValue(value) {
|
|
8745
|
+
return value?.split(",", 1)[0]?.trim() || void 0;
|
|
8746
|
+
}
|
|
8376
8747
|
async function serveHttp(options, engineOptions = {}, writeErr = (value) => process.stderr.write(value)) {
|
|
8377
8748
|
const engine = new CapletsEngine(engineOptions);
|
|
8378
|
-
const app = createHttpServeApp(options, engine, {
|
|
8749
|
+
const app = createHttpServeApp(options, engine, {
|
|
8750
|
+
writeErr,
|
|
8751
|
+
control: {
|
|
8752
|
+
...engineOptions,
|
|
8753
|
+
projectCapletsRoot: projectCapletsRootForEngineOptions(engineOptions)
|
|
8754
|
+
}
|
|
8755
|
+
});
|
|
8756
|
+
const paths = servicePaths(options.path);
|
|
8757
|
+
const origin = `http://${formatHost(options.host)}:${options.port}`;
|
|
8758
|
+
const baseUrl = `${origin}${paths.base === "/" ? "" : paths.base}`;
|
|
8379
8759
|
installHttpSignalHandlers(serve({
|
|
8380
8760
|
fetch: app.fetch,
|
|
8381
8761
|
hostname: options.host,
|
|
8382
8762
|
port: options.port
|
|
8383
8763
|
}, () => {
|
|
8384
|
-
writeErr(`Caplets
|
|
8385
|
-
writeErr(`
|
|
8764
|
+
writeErr(`Caplets HTTP service listening on ${baseUrl}\n`);
|
|
8765
|
+
writeErr(`MCP endpoint: ${origin}${paths.mcp}\n`);
|
|
8766
|
+
writeErr(`Control endpoint: ${origin}${paths.control}\n`);
|
|
8767
|
+
writeErr(`Health check: ${origin}${paths.health}\n`);
|
|
8386
8768
|
writeErr(`Basic Auth: ${options.auth.enabled ? `enabled (user: ${options.auth.user})` : "disabled"}\n`);
|
|
8387
8769
|
}), app, engine, writeErr);
|
|
8388
8770
|
}
|
|
8771
|
+
function projectCapletsRootForEngineOptions(engineOptions) {
|
|
8772
|
+
return engineOptions.projectConfigPath ? resolveProjectCapletsRootForConfigPath(engineOptions.projectConfigPath) : resolveProjectCapletsRoot();
|
|
8773
|
+
}
|
|
8774
|
+
function resolveProjectCapletsRootForConfigPath(projectConfigPath) {
|
|
8775
|
+
return dirname(projectConfigPath);
|
|
8776
|
+
}
|
|
8777
|
+
function routePath(base, path) {
|
|
8778
|
+
return base === "/" ? `/${path}` : `${base}/${path}`;
|
|
8779
|
+
}
|
|
8780
|
+
function servicePaths(base) {
|
|
8781
|
+
return {
|
|
8782
|
+
base,
|
|
8783
|
+
mcp: routePath(base, "mcp"),
|
|
8784
|
+
control: routePath(base, "control"),
|
|
8785
|
+
health: routePath(base, "healthz")
|
|
8786
|
+
};
|
|
8787
|
+
}
|
|
8389
8788
|
async function createHttpSession(engine, sessionId, options, onClose) {
|
|
8390
8789
|
const transport = new StreamableHTTPTransport({
|
|
8391
8790
|
sessionIdGenerator: () => sessionId,
|
|
@@ -8465,7 +8864,8 @@ const HTTP_ONLY_OPTIONS = [
|
|
|
8465
8864
|
"path",
|
|
8466
8865
|
"user",
|
|
8467
8866
|
"password",
|
|
8468
|
-
"allowUnauthenticatedHttp"
|
|
8867
|
+
"allowUnauthenticatedHttp",
|
|
8868
|
+
"trustProxy"
|
|
8469
8869
|
];
|
|
8470
8870
|
function resolveServeOptions(raw, env = process.env) {
|
|
8471
8871
|
const transport = parseTransport(raw.transport ?? "stdio");
|
|
@@ -8474,9 +8874,10 @@ function resolveServeOptions(raw, env = process.env) {
|
|
|
8474
8874
|
if (invalid.length > 0) throw new CapletsError("REQUEST_INVALID", `${invalid.map((key) => `--${key}`).join(", ")} ${invalid.length === 1 ? "is" : "are"} only valid with --transport http`);
|
|
8475
8875
|
return { transport };
|
|
8476
8876
|
}
|
|
8477
|
-
const
|
|
8478
|
-
const
|
|
8479
|
-
const
|
|
8877
|
+
const serverUrl = env.CAPLETS_SERVER_URL ? parseServeServerUrl(nonEmpty(env.CAPLETS_SERVER_URL, "CAPLETS_SERVER_URL")) : void 0;
|
|
8878
|
+
const host = nonEmpty(raw.host, "--host") ?? serverUrlHost(serverUrl) ?? "127.0.0.1";
|
|
8879
|
+
const port = parsePort(raw.port ?? (serverUrl?.port ? Number(serverUrl.port) : 5387));
|
|
8880
|
+
const path = normalizeHttpPath(raw.path ?? serverUrl?.pathname ?? "/");
|
|
8480
8881
|
const userWasExplicit = raw.user !== void 0 || hasEnv(env.CAPLETS_SERVER_USER);
|
|
8481
8882
|
const user = nonEmpty(raw.user, "--user") ?? nonEmpty(env.CAPLETS_SERVER_USER, "CAPLETS_SERVER_USER") ?? "caplets";
|
|
8482
8883
|
const password = nonEmpty(raw.password, "--password") ?? nonEmpty(env.CAPLETS_SERVER_PASSWORD, "CAPLETS_SERVER_PASSWORD");
|
|
@@ -8498,13 +8899,22 @@ function resolveServeOptions(raw, env = process.env) {
|
|
|
8498
8899
|
path,
|
|
8499
8900
|
auth,
|
|
8500
8901
|
warnUnauthenticatedNetwork: !loopback && !auth.enabled,
|
|
8501
|
-
loopback
|
|
8902
|
+
loopback,
|
|
8903
|
+
trustProxy: raw.trustProxy === true
|
|
8502
8904
|
};
|
|
8503
8905
|
}
|
|
8504
8906
|
function isLoopbackHost(host) {
|
|
8505
8907
|
const normalized = host.toLocaleLowerCase();
|
|
8506
8908
|
return normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1";
|
|
8507
8909
|
}
|
|
8910
|
+
function parseServeServerUrl(value) {
|
|
8911
|
+
try {
|
|
8912
|
+
return parseServerBaseUrl(value);
|
|
8913
|
+
} catch (error) {
|
|
8914
|
+
if (error instanceof CapletsError && error.message.includes("must use https except loopback development URLs")) throw new CapletsError("REQUEST_INVALID", "CAPLETS_SERVER_URL must use https except loopback development URLs; use --host, --port, and --path separately for non-loopback HTTP bind addresses.");
|
|
8915
|
+
throw error;
|
|
8916
|
+
}
|
|
8917
|
+
}
|
|
8508
8918
|
function parseTransport(value) {
|
|
8509
8919
|
if (value === "stdio" || value === "http") return value;
|
|
8510
8920
|
throw new CapletsError("REQUEST_INVALID", `Expected --transport to be stdio or http, got ${value}`);
|
|
@@ -8519,6 +8929,9 @@ function normalizeHttpPath(value) {
|
|
|
8519
8929
|
if (value.includes("?") || value.includes("#")) throw new CapletsError("REQUEST_INVALID", "HTTP --path must not include a query string or fragment");
|
|
8520
8930
|
return value === "/" ? value : value.replace(/\/+$/u, "");
|
|
8521
8931
|
}
|
|
8932
|
+
function serverUrlHost(url) {
|
|
8933
|
+
return url?.hostname.replace(/^\[(.*)\]$/u, "$1");
|
|
8934
|
+
}
|
|
8522
8935
|
function nonEmpty(value, label) {
|
|
8523
8936
|
if (value === void 0) return;
|
|
8524
8937
|
const trimmed = value.trim();
|
|
@@ -8653,9 +9066,14 @@ async function runCli(args, io = {}) {
|
|
|
8653
9066
|
throw error;
|
|
8654
9067
|
}
|
|
8655
9068
|
}
|
|
9069
|
+
function normalizeCompletionWords(words) {
|
|
9070
|
+
return words.map((word) => word === "__CAPLETS_TRAILING_SPACE__" ? "" : word);
|
|
9071
|
+
}
|
|
8656
9072
|
function createProgram(io = {}) {
|
|
8657
9073
|
const writeOut = io.writeOut ?? ((value) => process.stdout.write(value));
|
|
8658
9074
|
const writeErr = io.writeErr ?? ((value) => process.stderr.write(value));
|
|
9075
|
+
const env = io.env ?? process.env;
|
|
9076
|
+
const currentConfigPath = () => envConfigPath(env);
|
|
8659
9077
|
const setExitCode = io.setExitCode ?? ((code) => {
|
|
8660
9078
|
process.exitCode = code;
|
|
8661
9079
|
});
|
|
@@ -8665,42 +9083,98 @@ function createProgram(io = {}) {
|
|
|
8665
9083
|
writeErr,
|
|
8666
9084
|
outputError: (value, write) => write(value)
|
|
8667
9085
|
});
|
|
8668
|
-
program.command(
|
|
9086
|
+
program.command(cliCommands.completion).description("Print a shell completion script.").argument("<shell>", "completion shell: bash, zsh, fish, powershell, or cmd").action((shell) => {
|
|
9087
|
+
if (!completionShells.includes(shell)) throw new CapletsError("REQUEST_INVALID", "completion shell must be bash, zsh, fish, powershell, or cmd");
|
|
9088
|
+
writeOut(completionScript(shell));
|
|
9089
|
+
});
|
|
9090
|
+
program.command(cliCommands.completeHidden, { hidden: true }).description("Internal shell completion endpoint.").option("--shell <shell>", "completion shell").allowUnknownOption(true).argument("[words...]", "words to complete").action(async (words, options) => {
|
|
9091
|
+
const shell = completionShells.includes(options.shell) ? options.shell : "bash";
|
|
9092
|
+
const remote = remoteClientForCli(io);
|
|
9093
|
+
const configPath = currentConfigPath();
|
|
9094
|
+
const completionWords = normalizeCompletionWords(words);
|
|
9095
|
+
let suggestions = [];
|
|
9096
|
+
try {
|
|
9097
|
+
suggestions = remote ? await remote.request("complete_cli", {
|
|
9098
|
+
shell,
|
|
9099
|
+
words: completionWords
|
|
9100
|
+
}) : await completeCliWords(completionWords, configPath ? { configPath } : {});
|
|
9101
|
+
} catch {
|
|
9102
|
+
suggestions = [];
|
|
9103
|
+
}
|
|
9104
|
+
if (suggestions.length > 0) writeOut(`${suggestions.join("\n")}\n`);
|
|
9105
|
+
});
|
|
9106
|
+
program.command(cliCommands.serve).description("Serve configured Caplets as an MCP server.").option("--transport <transport>", "server transport: stdio or http").option("--host <host>", "HTTP bind host").option("--port <port>", "HTTP bind port").option("--path <path>", "HTTP service base path").option("--user <user>", "HTTP Basic Auth username").option("--password <password>", "HTTP Basic Auth password").option("--allow-unauthenticated-http", "allow unauthenticated HTTP serving on non-loopback hosts").option("--trust-proxy", "trust X-Forwarded-* headers from a reverse proxy").action(async (options) => {
|
|
8669
9107
|
const resolved = resolveServeOptions(options);
|
|
8670
|
-
const configPath =
|
|
9108
|
+
const configPath = currentConfigPath();
|
|
8671
9109
|
await (io.serve ?? ((serveOptions) => serveResolvedCaplets(serveOptions, {
|
|
8672
9110
|
...configPath ? { configPath } : {},
|
|
8673
9111
|
...io.authDir ? { authDir: io.authDir } : {}
|
|
8674
9112
|
}, writeErr)))(resolved);
|
|
8675
9113
|
});
|
|
8676
|
-
program.command(
|
|
8677
|
-
const
|
|
9114
|
+
program.command(cliCommands.init).description("Create a starter Caplets config file.").option("--force", "overwrite an existing config file").action(async (options) => {
|
|
9115
|
+
const remote = remoteClientForCli(io);
|
|
9116
|
+
if (remote) {
|
|
9117
|
+
writeOut(`Created remote Caplets config at ${(await remote.request("init", { force: Boolean(options.force) })).path}\n`);
|
|
9118
|
+
return;
|
|
9119
|
+
}
|
|
9120
|
+
const configPath = currentConfigPath();
|
|
8678
9121
|
writeOut(`Created Caplets config at ${initConfig({
|
|
8679
9122
|
...configPath ? { path: configPath } : {},
|
|
8680
9123
|
force: Boolean(options.force)
|
|
8681
9124
|
})}\n`);
|
|
8682
9125
|
});
|
|
8683
|
-
program.command(
|
|
8684
|
-
const
|
|
9126
|
+
program.command(cliCommands.list).description("List configured Caplets.").option("--all", "include disabled Caplets").option("--json", "print JSON output").option("--format <format>", "output format: plain, markdown, md, or json", parseOutputFormat).action(async (options) => {
|
|
9127
|
+
const includeDisabled = Boolean(options.all);
|
|
9128
|
+
const remote = remoteClientForCli(io);
|
|
9129
|
+
if (remote) {
|
|
9130
|
+
const rows = await remote.request("list", { includeDisabled });
|
|
9131
|
+
if (options.json || options.format === "json") {
|
|
9132
|
+
writeOut(`${JSON.stringify(rows, null, 2)}\n`);
|
|
9133
|
+
return;
|
|
9134
|
+
}
|
|
9135
|
+
writeOut(formatCapletList(rows, options.format ?? "plain"));
|
|
9136
|
+
return;
|
|
9137
|
+
}
|
|
9138
|
+
const rows = listCaplets(loadConfigWithSources(currentConfigPath()), { includeDisabled });
|
|
8685
9139
|
if (options.json || options.format === "json") {
|
|
8686
9140
|
writeOut(`${JSON.stringify(rows, null, 2)}\n`);
|
|
8687
9141
|
return;
|
|
8688
9142
|
}
|
|
8689
9143
|
writeOut(formatCapletList(rows, options.format ?? "plain"));
|
|
8690
9144
|
});
|
|
8691
|
-
program.command(
|
|
9145
|
+
program.command(cliCommands.install).description("Install Caplets from a repo's caplets directory.").argument("<repo>", "local repo path, Git URL, or GitHub owner/repo").argument("[caplets...]", "optional Caplet IDs to install").option("-g, --global", "install to the user Caplets root").option("--force", "overwrite installed Caplets").action(async (repo, capletIds, options) => {
|
|
9146
|
+
const remote = remoteClientForCli(io);
|
|
9147
|
+
if (remote) {
|
|
9148
|
+
if (options.global) writeErr("Warning: --global is not supported in remote mode; the server controls the installation destination.\n");
|
|
9149
|
+
const result = await remote.request("install", {
|
|
9150
|
+
repo,
|
|
9151
|
+
capletIds,
|
|
9152
|
+
force: Boolean(options.force)
|
|
9153
|
+
});
|
|
9154
|
+
for (const caplet of result.installed) writeOut(`Installed ${caplet.id} to remote ${caplet.destination}\n`);
|
|
9155
|
+
return;
|
|
9156
|
+
}
|
|
8692
9157
|
const result = installCaplets(repo, {
|
|
8693
9158
|
capletIds,
|
|
8694
9159
|
force: Boolean(options.force),
|
|
8695
|
-
destinationRoot: options.global ? resolveCapletsRoot(resolveConfigPath(
|
|
9160
|
+
destinationRoot: options.global ? resolveCapletsRoot(resolveConfigPath(currentConfigPath())) : resolveProjectCapletsRoot()
|
|
8696
9161
|
});
|
|
8697
9162
|
for (const caplet of result.installed) writeOut(`Installed ${caplet.id} to ${caplet.destination}\n`);
|
|
8698
9163
|
});
|
|
8699
|
-
const add = program.command(
|
|
8700
|
-
add.command("cli").description("Add 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("-g, --global", "write to the user Caplets root").option("--print", "print generated Caplet text without writing a file").option("--output <path>", "output path").option("--force", "overwrite an existing destination file").action((id, options) => {
|
|
9164
|
+
const add = program.command(cliCommands.add).description("Add generated Caplet files.");
|
|
9165
|
+
add.command("cli").description("Add 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("-g, --global", "write to the user Caplets root").option("--print", "print generated Caplet text without writing a file").option("--output <path>", "output path").option("--force", "overwrite an existing destination file").action(async (id, options) => {
|
|
9166
|
+
const remote = remoteClientForCli(io);
|
|
9167
|
+
if (remote) {
|
|
9168
|
+
writeAddResult(writeOut, "CLI", await remote.request("add", {
|
|
9169
|
+
kind: "cli",
|
|
9170
|
+
id,
|
|
9171
|
+
options: remoteAddOptions(options)
|
|
9172
|
+
}));
|
|
9173
|
+
return;
|
|
9174
|
+
}
|
|
8701
9175
|
const result = addCliCaplet(id, {
|
|
8702
9176
|
...options,
|
|
8703
|
-
destinationRoot: options.global ? resolveCapletsRoot(resolveConfigPath(
|
|
9177
|
+
destinationRoot: options.global ? resolveCapletsRoot(resolveConfigPath(currentConfigPath())) : resolveProjectCapletsRoot()
|
|
8704
9178
|
});
|
|
8705
9179
|
if (result.path) {
|
|
8706
9180
|
writeOut(`Wrote CLI Caplet to ${result.path}\n`);
|
|
@@ -8708,58 +9182,100 @@ function createProgram(io = {}) {
|
|
|
8708
9182
|
}
|
|
8709
9183
|
writeOut(result.text);
|
|
8710
9184
|
});
|
|
8711
|
-
add.command("mcp").description("Add an MCP backend Caplet.").argument("<id>", "Caplet ID/display seed").option("--command <name>", "stdio command").option("--arg <value>", "stdio command argument", collect, []).option("--cwd <path>", "stdio working directory").option("--env <KEY=VALUE>", "stdio environment variable", collect, []).option("--url <url>", "remote MCP server URL").option("--transport <transport>", "remote transport: http or sse").option("--token-env <ENV>", "bearer token environment variable reference").option("-g, --global", "write to the user Caplets root").option("--print", "print generated Caplet text without writing a file").option("--output <path>", "output path").option("--force", "overwrite an existing destination file").action((id, options) => {
|
|
9185
|
+
add.command("mcp").description("Add an MCP backend Caplet.").argument("<id>", "Caplet ID/display seed").option("--command <name>", "stdio command").option("--arg <value>", "stdio command argument", collect, []).option("--cwd <path>", "stdio working directory").option("--env <KEY=VALUE>", "stdio environment variable", collect, []).option("--url <url>", "remote MCP server URL").option("--transport <transport>", "remote transport: http or sse").option("--token-env <ENV>", "bearer token environment variable reference").option("-g, --global", "write to the user Caplets root").option("--print", "print generated Caplet text without writing a file").option("--output <path>", "output path").option("--force", "overwrite an existing destination file").action(async (id, options) => {
|
|
9186
|
+
const remote = remoteClientForCli(io);
|
|
9187
|
+
if (remote) {
|
|
9188
|
+
writeAddResult(writeOut, "MCP", await remote.request("add", {
|
|
9189
|
+
kind: "mcp",
|
|
9190
|
+
id,
|
|
9191
|
+
options: remoteAddOptions(options)
|
|
9192
|
+
}));
|
|
9193
|
+
return;
|
|
9194
|
+
}
|
|
8712
9195
|
writeAddResult(writeOut, "MCP", addMcpCaplet(id, {
|
|
8713
9196
|
...options,
|
|
8714
|
-
destinationRoot: addDestinationRoot(options)
|
|
9197
|
+
destinationRoot: addDestinationRoot(options, currentConfigPath())
|
|
8715
9198
|
}));
|
|
8716
9199
|
});
|
|
8717
|
-
add.command("openapi").description("Add an OpenAPI backend Caplet.").argument("<id>", "Caplet ID/display seed").option("--spec <path-or-url>", "OpenAPI spec path or URL").option("--base-url <url>", "request base URL override").option("--token-env <ENV>", "bearer token environment variable reference").option("-g, --global", "write to the user Caplets root").option("--print", "print generated Caplet text without writing a file").option("--output <path>", "output path").option("--force", "overwrite an existing destination file").action((id, options) => {
|
|
9200
|
+
add.command("openapi").description("Add an OpenAPI backend Caplet.").argument("<id>", "Caplet ID/display seed").option("--spec <path-or-url>", "OpenAPI spec path or URL").option("--base-url <url>", "request base URL override").option("--token-env <ENV>", "bearer token environment variable reference").option("-g, --global", "write to the user Caplets root").option("--print", "print generated Caplet text without writing a file").option("--output <path>", "output path").option("--force", "overwrite an existing destination file").action(async (id, options) => {
|
|
9201
|
+
const remote = remoteClientForCli(io);
|
|
9202
|
+
if (remote) {
|
|
9203
|
+
writeAddResult(writeOut, "OpenAPI", await remote.request("add", {
|
|
9204
|
+
kind: "openapi",
|
|
9205
|
+
id,
|
|
9206
|
+
options: remoteAddOptions(options)
|
|
9207
|
+
}));
|
|
9208
|
+
return;
|
|
9209
|
+
}
|
|
8718
9210
|
writeAddResult(writeOut, "OpenAPI", addOpenApiCaplet(id, {
|
|
8719
9211
|
...options,
|
|
8720
|
-
destinationRoot: addDestinationRoot(options)
|
|
9212
|
+
destinationRoot: addDestinationRoot(options, currentConfigPath())
|
|
8721
9213
|
}));
|
|
8722
9214
|
});
|
|
8723
|
-
add.command("graphql").description("Add a GraphQL backend Caplet.").argument("<id>", "Caplet ID/display seed").option("--endpoint-url <url>", "GraphQL endpoint URL").option("--schema <path-or-url>", "GraphQL schema path or URL").option("--introspection", "load schema through endpoint introspection").option("--token-env <ENV>", "bearer token environment variable reference").option("-g, --global", "write to the user Caplets root").option("--print", "print generated Caplet text without writing a file").option("--output <path>", "output path").option("--force", "overwrite an existing destination file").action((id, options) => {
|
|
9215
|
+
add.command("graphql").description("Add a GraphQL backend Caplet.").argument("<id>", "Caplet ID/display seed").option("--endpoint-url <url>", "GraphQL endpoint URL").option("--schema <path-or-url>", "GraphQL schema path or URL").option("--introspection", "load schema through endpoint introspection").option("--token-env <ENV>", "bearer token environment variable reference").option("-g, --global", "write to the user Caplets root").option("--print", "print generated Caplet text without writing a file").option("--output <path>", "output path").option("--force", "overwrite an existing destination file").action(async (id, options) => {
|
|
9216
|
+
const remote = remoteClientForCli(io);
|
|
9217
|
+
if (remote) {
|
|
9218
|
+
writeAddResult(writeOut, "GraphQL", await remote.request("add", {
|
|
9219
|
+
kind: "graphql",
|
|
9220
|
+
id,
|
|
9221
|
+
options: remoteAddOptions(options)
|
|
9222
|
+
}));
|
|
9223
|
+
return;
|
|
9224
|
+
}
|
|
8724
9225
|
writeAddResult(writeOut, "GraphQL", addGraphqlCaplet(id, {
|
|
8725
9226
|
...options,
|
|
8726
|
-
destinationRoot: addDestinationRoot(options)
|
|
9227
|
+
destinationRoot: addDestinationRoot(options, currentConfigPath())
|
|
8727
9228
|
}));
|
|
8728
9229
|
});
|
|
8729
|
-
add.command("http").description("Add an HTTP actions backend Caplet.").argument("<id>", "Caplet ID/display seed").option("--base-url <url>", "HTTP API base URL").option("--action <name:METHOD:/path>", "HTTP action", collect, []).option("--token-env <ENV>", "bearer token environment variable reference").option("-g, --global", "write to the user Caplets root").option("--print", "print generated Caplet text without writing a file").option("--output <path>", "output path").option("--force", "overwrite an existing destination file").action((id, options) => {
|
|
9230
|
+
add.command("http").description("Add an HTTP actions backend Caplet.").argument("<id>", "Caplet ID/display seed").option("--base-url <url>", "HTTP API base URL").option("--action <name:METHOD:/path>", "HTTP action", collect, []).option("--token-env <ENV>", "bearer token environment variable reference").option("-g, --global", "write to the user Caplets root").option("--print", "print generated Caplet text without writing a file").option("--output <path>", "output path").option("--force", "overwrite an existing destination file").action(async (id, options) => {
|
|
9231
|
+
const remote = remoteClientForCli(io);
|
|
9232
|
+
if (remote) {
|
|
9233
|
+
writeAddResult(writeOut, "HTTP", await remote.request("add", {
|
|
9234
|
+
kind: "http",
|
|
9235
|
+
id,
|
|
9236
|
+
options: remoteAddOptions(options)
|
|
9237
|
+
}));
|
|
9238
|
+
return;
|
|
9239
|
+
}
|
|
8730
9240
|
writeAddResult(writeOut, "HTTP", addHttpCaplet(id, {
|
|
8731
9241
|
...options,
|
|
8732
|
-
destinationRoot: addDestinationRoot(options)
|
|
9242
|
+
destinationRoot: addDestinationRoot(options, currentConfigPath())
|
|
8733
9243
|
}));
|
|
8734
9244
|
});
|
|
8735
|
-
program.command(
|
|
9245
|
+
program.command(cliCommands.getCaplet).description("Print a configured Caplet card.").argument("<caplet>", "configured Caplet ID").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => {
|
|
8736
9246
|
await executeOperation(caplet, { operation: "get_caplet" }, {
|
|
8737
9247
|
writeOut,
|
|
8738
9248
|
writeErr,
|
|
8739
9249
|
setExitCode,
|
|
8740
9250
|
authDir: io.authDir,
|
|
9251
|
+
env,
|
|
9252
|
+
remote: remoteClientForCli(io),
|
|
8741
9253
|
format: options.format
|
|
8742
9254
|
});
|
|
8743
9255
|
});
|
|
8744
|
-
program.command(
|
|
9256
|
+
program.command(cliCommands.checkBackend).description("Check backend availability for a configured Caplet.").argument("<caplet>", "configured Caplet ID").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => {
|
|
8745
9257
|
await executeOperation(caplet, { operation: "check_backend" }, {
|
|
8746
9258
|
writeOut,
|
|
8747
9259
|
writeErr,
|
|
8748
9260
|
setExitCode,
|
|
8749
9261
|
authDir: io.authDir,
|
|
9262
|
+
env,
|
|
9263
|
+
remote: remoteClientForCli(io),
|
|
8750
9264
|
format: options.format
|
|
8751
9265
|
});
|
|
8752
9266
|
});
|
|
8753
|
-
program.command(
|
|
9267
|
+
program.command(cliCommands.listTools).description("List downstream tools for a configured Caplet.").argument("<caplet>", "configured Caplet ID").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => {
|
|
8754
9268
|
await executeOperation(caplet, { operation: "list_tools" }, {
|
|
8755
9269
|
writeOut,
|
|
8756
9270
|
writeErr,
|
|
8757
9271
|
setExitCode,
|
|
8758
9272
|
authDir: io.authDir,
|
|
9273
|
+
env,
|
|
9274
|
+
remote: remoteClientForCli(io),
|
|
8759
9275
|
format: options.format
|
|
8760
9276
|
});
|
|
8761
9277
|
});
|
|
8762
|
-
program.command(
|
|
9278
|
+
program.command(cliCommands.searchTools).description("Search downstream tools for a configured Caplet.").argument("<caplet>", "configured Caplet ID").argument("<query>", "search query").option("--limit <n>", "maximum number of tools to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, query, options) => {
|
|
8763
9279
|
await executeOperation(caplet, options.limit === void 0 ? {
|
|
8764
9280
|
operation: "search_tools",
|
|
8765
9281
|
query
|
|
@@ -8772,10 +9288,12 @@ function createProgram(io = {}) {
|
|
|
8772
9288
|
writeErr,
|
|
8773
9289
|
setExitCode,
|
|
8774
9290
|
authDir: io.authDir,
|
|
9291
|
+
env,
|
|
9292
|
+
remote: remoteClientForCli(io),
|
|
8775
9293
|
format: options.format
|
|
8776
9294
|
});
|
|
8777
9295
|
});
|
|
8778
|
-
program.command(
|
|
9296
|
+
program.command(cliCommands.getTool).description("Print one downstream tool schema.").argument("<caplet.tool>", "qualified target, split on the first dot").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (target, options) => {
|
|
8779
9297
|
const { caplet, tool } = parseQualifiedTarget(target);
|
|
8780
9298
|
await executeOperation(caplet, {
|
|
8781
9299
|
operation: "get_tool",
|
|
@@ -8785,10 +9303,12 @@ function createProgram(io = {}) {
|
|
|
8785
9303
|
writeErr,
|
|
8786
9304
|
setExitCode,
|
|
8787
9305
|
authDir: io.authDir,
|
|
9306
|
+
env,
|
|
9307
|
+
remote: remoteClientForCli(io),
|
|
8788
9308
|
format: options.format
|
|
8789
9309
|
});
|
|
8790
9310
|
});
|
|
8791
|
-
program.command(
|
|
9311
|
+
program.command(cliCommands.callTool).description("Call one downstream tool.").argument("<caplet.tool>", "qualified target, split on the first dot").option("--args <json-object>", "JSON object of downstream tool arguments").option("--field <path>", "project a field from structured output", collect, []).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (target, options) => {
|
|
8792
9312
|
const { caplet, tool } = parseQualifiedTarget(target);
|
|
8793
9313
|
await executeOperation(caplet, {
|
|
8794
9314
|
operation: "call_tool",
|
|
@@ -8800,24 +9320,150 @@ function createProgram(io = {}) {
|
|
|
8800
9320
|
writeErr,
|
|
8801
9321
|
setExitCode,
|
|
8802
9322
|
authDir: io.authDir,
|
|
9323
|
+
env,
|
|
9324
|
+
remote: remoteClientForCli(io),
|
|
9325
|
+
format: options.format
|
|
9326
|
+
});
|
|
9327
|
+
});
|
|
9328
|
+
program.command(cliCommands.listResources).description("List MCP resources for a configured MCP Caplet.").argument("<caplet>").option("--limit <n>", "maximum number of resources to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => executeOperation(caplet, options.limit === void 0 ? { operation: "list_resources" } : {
|
|
9329
|
+
operation: "list_resources",
|
|
9330
|
+
limit: options.limit
|
|
9331
|
+
}, {
|
|
9332
|
+
writeOut,
|
|
9333
|
+
writeErr,
|
|
9334
|
+
setExitCode,
|
|
9335
|
+
authDir: io.authDir,
|
|
9336
|
+
env,
|
|
9337
|
+
remote: remoteClientForCli(io),
|
|
9338
|
+
format: options.format
|
|
9339
|
+
}));
|
|
9340
|
+
program.command(cliCommands.searchResources).description("Search MCP resources and resource templates for a configured MCP Caplet.").argument("<caplet>").argument("<query>").option("--limit <n>", "maximum number of matches to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, query, options) => executeOperation(caplet, options.limit === void 0 ? {
|
|
9341
|
+
operation: "search_resources",
|
|
9342
|
+
query
|
|
9343
|
+
} : {
|
|
9344
|
+
operation: "search_resources",
|
|
9345
|
+
query,
|
|
9346
|
+
limit: options.limit
|
|
9347
|
+
}, {
|
|
9348
|
+
writeOut,
|
|
9349
|
+
writeErr,
|
|
9350
|
+
setExitCode,
|
|
9351
|
+
authDir: io.authDir,
|
|
9352
|
+
env,
|
|
9353
|
+
remote: remoteClientForCli(io),
|
|
9354
|
+
format: options.format
|
|
9355
|
+
}));
|
|
9356
|
+
program.command(cliCommands.listResourceTemplates).description("List MCP resource templates for a configured MCP Caplet.").argument("<caplet>").option("--limit <n>", "maximum number of templates to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => executeOperation(caplet, options.limit === void 0 ? { operation: "list_resource_templates" } : {
|
|
9357
|
+
operation: "list_resource_templates",
|
|
9358
|
+
limit: options.limit
|
|
9359
|
+
}, {
|
|
9360
|
+
writeOut,
|
|
9361
|
+
writeErr,
|
|
9362
|
+
setExitCode,
|
|
9363
|
+
authDir: io.authDir,
|
|
9364
|
+
env,
|
|
9365
|
+
remote: remoteClientForCli(io),
|
|
9366
|
+
format: options.format
|
|
9367
|
+
}));
|
|
9368
|
+
program.command(cliCommands.readResource).description("Read one MCP resource by URI.").argument("<caplet>").argument("<uri>").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, uri, options) => executeOperation(caplet, {
|
|
9369
|
+
operation: "read_resource",
|
|
9370
|
+
uri
|
|
9371
|
+
}, {
|
|
9372
|
+
writeOut,
|
|
9373
|
+
writeErr,
|
|
9374
|
+
setExitCode,
|
|
9375
|
+
authDir: io.authDir,
|
|
9376
|
+
env,
|
|
9377
|
+
remote: remoteClientForCli(io),
|
|
9378
|
+
format: options.format
|
|
9379
|
+
}));
|
|
9380
|
+
program.command(cliCommands.listPrompts).description("List MCP prompts for a configured MCP Caplet.").argument("<caplet>").option("--limit <n>", "maximum number of prompts to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => executeOperation(caplet, options.limit === void 0 ? { operation: "list_prompts" } : {
|
|
9381
|
+
operation: "list_prompts",
|
|
9382
|
+
limit: options.limit
|
|
9383
|
+
}, {
|
|
9384
|
+
writeOut,
|
|
9385
|
+
writeErr,
|
|
9386
|
+
setExitCode,
|
|
9387
|
+
authDir: io.authDir,
|
|
9388
|
+
env,
|
|
9389
|
+
remote: remoteClientForCli(io),
|
|
9390
|
+
format: options.format
|
|
9391
|
+
}));
|
|
9392
|
+
program.command(cliCommands.searchPrompts).description("Search MCP prompts for a configured MCP Caplet.").argument("<caplet>").argument("<query>").option("--limit <n>", "maximum number of prompts to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, query, options) => executeOperation(caplet, options.limit === void 0 ? {
|
|
9393
|
+
operation: "search_prompts",
|
|
9394
|
+
query
|
|
9395
|
+
} : {
|
|
9396
|
+
operation: "search_prompts",
|
|
9397
|
+
query,
|
|
9398
|
+
limit: options.limit
|
|
9399
|
+
}, {
|
|
9400
|
+
writeOut,
|
|
9401
|
+
writeErr,
|
|
9402
|
+
setExitCode,
|
|
9403
|
+
authDir: io.authDir,
|
|
9404
|
+
env,
|
|
9405
|
+
remote: remoteClientForCli(io),
|
|
9406
|
+
format: options.format
|
|
9407
|
+
}));
|
|
9408
|
+
program.command(cliCommands.getPrompt).description("Get one MCP prompt by name.").argument("<caplet.prompt>", "qualified target, split on the first dot").option("--args <json-object>", "JSON object of prompt arguments").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (target, options) => {
|
|
9409
|
+
const { caplet, tool: prompt } = parseQualifiedTarget(target);
|
|
9410
|
+
await executeOperation(caplet, {
|
|
9411
|
+
operation: "get_prompt",
|
|
9412
|
+
prompt,
|
|
9413
|
+
arguments: parseJsonObjectOption(options.args, "get-prompt --args")
|
|
9414
|
+
}, {
|
|
9415
|
+
writeOut,
|
|
9416
|
+
writeErr,
|
|
9417
|
+
setExitCode,
|
|
9418
|
+
authDir: io.authDir,
|
|
9419
|
+
env,
|
|
9420
|
+
remote: remoteClientForCli(io),
|
|
8803
9421
|
format: options.format
|
|
8804
9422
|
});
|
|
8805
9423
|
});
|
|
8806
|
-
|
|
9424
|
+
program.command(cliCommands.complete).description("Complete an MCP prompt or resource-template argument.").argument("<caplet>").requiredOption("--argument <name>", "argument name").option("--value <value>", "argument prefix", "").option("--prompt <name>", "prompt name to complete").option("--resource-template <uri-template>", "resource template URI to complete").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => executeOperation(caplet, {
|
|
9425
|
+
operation: "complete",
|
|
9426
|
+
ref: completionRefFromOptions(options),
|
|
9427
|
+
argument: {
|
|
9428
|
+
name: options.argument,
|
|
9429
|
+
value: options.value
|
|
9430
|
+
}
|
|
9431
|
+
}, {
|
|
9432
|
+
writeOut,
|
|
9433
|
+
writeErr,
|
|
9434
|
+
setExitCode,
|
|
9435
|
+
authDir: io.authDir,
|
|
9436
|
+
env,
|
|
9437
|
+
remote: remoteClientForCli(io),
|
|
9438
|
+
format: options.format
|
|
9439
|
+
}));
|
|
9440
|
+
const config = program.command(cliCommands.config).description("Inspect Caplets config locations.");
|
|
8807
9441
|
config.command("path").description("Print the effective user config path.").action(() => {
|
|
8808
|
-
writeOut(`${resolveConfigPath(
|
|
9442
|
+
writeOut(`${resolveConfigPath(currentConfigPath())}\n`);
|
|
8809
9443
|
});
|
|
8810
9444
|
config.command("paths").description("Print resolved Caplets config, root, and auth paths.").option("--json", "print JSON output").option("--format <format>", "output format: plain, markdown, md, or json", parseOutputFormat).action((options) => {
|
|
8811
|
-
const paths = resolveCliConfigPaths(
|
|
9445
|
+
const paths = resolveCliConfigPaths(currentConfigPath(), io.authDir);
|
|
8812
9446
|
if (options.json || options.format === "json") {
|
|
8813
9447
|
writeOut(`${JSON.stringify(paths, null, 2)}\n`);
|
|
8814
9448
|
return;
|
|
8815
9449
|
}
|
|
8816
9450
|
writeOut(formatConfigPaths(paths, options.format ?? "plain"));
|
|
8817
9451
|
});
|
|
8818
|
-
const auth = program.command(
|
|
9452
|
+
const auth = program.command(cliCommands.auth).description("Manage OAuth credentials for remote servers.");
|
|
8819
9453
|
auth.command("login").description("Authenticate a configured remote OAuth server.").argument("<server>", "configured server ID").option("--no-open", "print the authorization URL without opening a browser").action(async (serverId, options) => {
|
|
8820
|
-
const
|
|
9454
|
+
const remote = remoteClientForCli(io);
|
|
9455
|
+
if (remote) {
|
|
9456
|
+
const started = await remote.request("auth_login_start", { server: serverId });
|
|
9457
|
+
if (started.authorizationUrl) {
|
|
9458
|
+
writeOut(`Open this URL to authorize ${serverId}:\n${started.authorizationUrl}\n`);
|
|
9459
|
+
if (options.open !== false) await openBrowser(started.authorizationUrl);
|
|
9460
|
+
writeOut("Complete authentication in your browser. The server callback will store credentials.\n");
|
|
9461
|
+
return;
|
|
9462
|
+
}
|
|
9463
|
+
if (started.authenticated) writeOut(`Authenticated \`${serverId}\`.\n`);
|
|
9464
|
+
return;
|
|
9465
|
+
}
|
|
9466
|
+
const configPath = currentConfigPath();
|
|
8821
9467
|
await loginAuth(serverId, {
|
|
8822
9468
|
noOpen: options.open === false,
|
|
8823
9469
|
writeOut,
|
|
@@ -8826,27 +9472,86 @@ function createProgram(io = {}) {
|
|
|
8826
9472
|
...io.authDir ? { authDir: io.authDir } : {}
|
|
8827
9473
|
});
|
|
8828
9474
|
});
|
|
8829
|
-
auth.command("logout").description("Delete stored OAuth credentials for a server.").argument("<server>", "configured server ID").action((serverId) => {
|
|
8830
|
-
const
|
|
9475
|
+
auth.command("logout").description("Delete stored OAuth credentials for a server.").argument("<server>", "configured server ID").action(async (serverId) => {
|
|
9476
|
+
const remote = remoteClientForCli(io);
|
|
9477
|
+
if (remote) {
|
|
9478
|
+
writeOut((await remote.request("auth_logout", { server: serverId })).deleted ? `Deleted remote OAuth credentials for \`${serverId}\`.\n` : `No remote OAuth credentials found for \`${serverId}\`.\n`);
|
|
9479
|
+
return;
|
|
9480
|
+
}
|
|
9481
|
+
const configPath = currentConfigPath();
|
|
8831
9482
|
logoutAuth(serverId, {
|
|
8832
9483
|
writeOut,
|
|
8833
9484
|
...configPath ? { configPath } : {},
|
|
8834
9485
|
...io.authDir ? { authDir: io.authDir } : {}
|
|
8835
9486
|
});
|
|
8836
9487
|
});
|
|
8837
|
-
auth.command("list").description("List servers with stored OAuth credentials.").option("--json", "print JSON output").option("--format <format>", "output format: plain, markdown, md, or json", parseOutputFormat).action((options) => {
|
|
8838
|
-
const configPath =
|
|
9488
|
+
auth.command("list").description("List servers with stored OAuth credentials.").option("--json", "print JSON output").option("--format <format>", "output format: plain, markdown, md, or json", parseOutputFormat).action(async (options) => {
|
|
9489
|
+
const configPath = currentConfigPath();
|
|
9490
|
+
const format = options.json || options.format === "json" ? "json" : options.format ?? "plain";
|
|
9491
|
+
const remote = remoteClientForCli(io);
|
|
9492
|
+
if (remote) {
|
|
9493
|
+
const rows = await remote.request("auth_list", {});
|
|
9494
|
+
if (format === "json") {
|
|
9495
|
+
writeOut(`${JSON.stringify(rows, null, 2)}\n`);
|
|
9496
|
+
return;
|
|
9497
|
+
}
|
|
9498
|
+
writeOut(formatAuthRows(rows, format));
|
|
9499
|
+
return;
|
|
9500
|
+
}
|
|
8839
9501
|
listAuth({
|
|
8840
9502
|
writeOut,
|
|
8841
|
-
format
|
|
9503
|
+
format,
|
|
8842
9504
|
...configPath ? { configPath } : {},
|
|
8843
9505
|
...io.authDir ? { authDir: io.authDir } : {}
|
|
8844
9506
|
});
|
|
8845
9507
|
});
|
|
8846
9508
|
return program;
|
|
8847
9509
|
}
|
|
8848
|
-
function envConfigPath() {
|
|
8849
|
-
return
|
|
9510
|
+
function envConfigPath(env) {
|
|
9511
|
+
return env.CAPLETS_CONFIG?.trim() || void 0;
|
|
9512
|
+
}
|
|
9513
|
+
function remoteClientForCli(io) {
|
|
9514
|
+
const env = io.env ?? process.env;
|
|
9515
|
+
if (resolveCapletsMode({}, env).mode !== "remote") return;
|
|
9516
|
+
return new RemoteControlClient(resolveCapletsServer(io.fetch ? { fetch: io.fetch } : {}, env));
|
|
9517
|
+
}
|
|
9518
|
+
async function openBrowser(url) {
|
|
9519
|
+
const { spawn } = await import("node:child_process");
|
|
9520
|
+
spawn(process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open", process.platform === "win32" ? [
|
|
9521
|
+
"/c",
|
|
9522
|
+
"start",
|
|
9523
|
+
"",
|
|
9524
|
+
url
|
|
9525
|
+
] : [url], {
|
|
9526
|
+
stdio: "ignore",
|
|
9527
|
+
detached: true
|
|
9528
|
+
}).unref();
|
|
9529
|
+
}
|
|
9530
|
+
function remoteCommandForOperation(operation) {
|
|
9531
|
+
switch (operation) {
|
|
9532
|
+
case "get_caplet":
|
|
9533
|
+
case "check_backend":
|
|
9534
|
+
case "list_tools":
|
|
9535
|
+
case "search_tools":
|
|
9536
|
+
case "get_tool":
|
|
9537
|
+
case "call_tool":
|
|
9538
|
+
case "list_resources":
|
|
9539
|
+
case "search_resources":
|
|
9540
|
+
case "list_resource_templates":
|
|
9541
|
+
case "read_resource":
|
|
9542
|
+
case "list_prompts":
|
|
9543
|
+
case "search_prompts":
|
|
9544
|
+
case "get_prompt":
|
|
9545
|
+
case "complete": return operation;
|
|
9546
|
+
default: return;
|
|
9547
|
+
}
|
|
9548
|
+
}
|
|
9549
|
+
function remoteAddOptions(options) {
|
|
9550
|
+
const { output, print, global, destinationRoot, ...remoteOptions } = options;
|
|
9551
|
+
if (global) throw new CapletsError("REQUEST_INVALID", "--global is not supported in remote mode; the server controls the add destination.");
|
|
9552
|
+
if (print) throw new CapletsError("REQUEST_INVALID", "--print is not supported in remote mode; the server controls add output.");
|
|
9553
|
+
if (output !== void 0) throw new CapletsError("REQUEST_INVALID", "--output is not supported in remote mode; the server controls the add destination.");
|
|
9554
|
+
return remoteOptions;
|
|
8850
9555
|
}
|
|
8851
9556
|
function collect(value, previous) {
|
|
8852
9557
|
previous.push(value);
|
|
@@ -8885,11 +9590,48 @@ function parseCallToolArgs(value) {
|
|
|
8885
9590
|
if (!isPlainObject(parsed)) throw new CapletsError("REQUEST_INVALID", "call-tool --args must be a JSON object");
|
|
8886
9591
|
return parsed;
|
|
8887
9592
|
}
|
|
9593
|
+
function parseJsonObjectOption(value, label) {
|
|
9594
|
+
if (value === void 0) return {};
|
|
9595
|
+
let parsed;
|
|
9596
|
+
try {
|
|
9597
|
+
parsed = JSON.parse(value);
|
|
9598
|
+
} catch (error) {
|
|
9599
|
+
throw new CapletsError("REQUEST_INVALID", `${label} must be valid JSON`, error);
|
|
9600
|
+
}
|
|
9601
|
+
if (!isPlainObject(parsed)) throw new CapletsError("REQUEST_INVALID", `${label} must be a JSON object`);
|
|
9602
|
+
return parsed;
|
|
9603
|
+
}
|
|
9604
|
+
function completionRefFromOptions(options) {
|
|
9605
|
+
if (options.prompt && options.resourceTemplate) throw new CapletsError("REQUEST_INVALID", "complete accepts either --prompt or --resource-template, not both");
|
|
9606
|
+
if (options.prompt) return {
|
|
9607
|
+
type: "prompt",
|
|
9608
|
+
name: options.prompt
|
|
9609
|
+
};
|
|
9610
|
+
if (options.resourceTemplate) return {
|
|
9611
|
+
type: "resourceTemplate",
|
|
9612
|
+
uri: options.resourceTemplate
|
|
9613
|
+
};
|
|
9614
|
+
throw new CapletsError("REQUEST_INVALID", "complete requires --prompt or --resource-template");
|
|
9615
|
+
}
|
|
8888
9616
|
function isPlainObject(value) {
|
|
8889
9617
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
8890
9618
|
}
|
|
8891
9619
|
async function executeOperation(caplet, request, io) {
|
|
8892
|
-
const
|
|
9620
|
+
const command = remoteCommandForOperation(request.operation);
|
|
9621
|
+
if (io.remote && command) {
|
|
9622
|
+
const result = await io.remote.request(command, {
|
|
9623
|
+
caplet,
|
|
9624
|
+
request
|
|
9625
|
+
});
|
|
9626
|
+
const output = cliOutputForOperation(result, {
|
|
9627
|
+
...request,
|
|
9628
|
+
caplet
|
|
9629
|
+
}, io.format ?? "markdown");
|
|
9630
|
+
io.writeOut(typeof output === "string" ? `${output}\n` : `${JSON.stringify(output, null, 2)}\n`);
|
|
9631
|
+
if (isPlainObject(result) && result.isError === true) io.setExitCode(1);
|
|
9632
|
+
return;
|
|
9633
|
+
}
|
|
9634
|
+
const configPath = envConfigPath(io.env ?? process.env);
|
|
8893
9635
|
const engine = new CapletsEngine({
|
|
8894
9636
|
...configPath ? { configPath } : {},
|
|
8895
9637
|
...io.authDir ? { authDir: io.authDir } : {},
|
|
@@ -9001,6 +9743,55 @@ function markdownSummaryForOperation(result, request) {
|
|
|
9001
9743
|
"",
|
|
9002
9744
|
"Use `--format json` to inspect the full structured result."
|
|
9003
9745
|
].filter((line) => line !== void 0).join("\n");
|
|
9746
|
+
case "list_resources":
|
|
9747
|
+
case "search_resources": {
|
|
9748
|
+
const resources = Array.isArray(payload.resources) ? payload.resources : [];
|
|
9749
|
+
const templates = Array.isArray(payload.resourceTemplates) ? payload.resourceTemplates : [];
|
|
9750
|
+
const matches = Array.isArray(payload.matches) ? payload.matches : [...resources, ...templates];
|
|
9751
|
+
return [
|
|
9752
|
+
`## MCP resources for \`${id}\``,
|
|
9753
|
+
"",
|
|
9754
|
+
`${matches.length} item${matches.length === 1 ? "" : "s"} found.`,
|
|
9755
|
+
"",
|
|
9756
|
+
...formatResourceLines(matches, "markdown")
|
|
9757
|
+
].join("\n");
|
|
9758
|
+
}
|
|
9759
|
+
case "list_resource_templates": {
|
|
9760
|
+
const templates = Array.isArray(payload.resourceTemplates) ? payload.resourceTemplates : [];
|
|
9761
|
+
return [
|
|
9762
|
+
`## MCP resource templates for \`${id}\``,
|
|
9763
|
+
"",
|
|
9764
|
+
...formatResourceLines(templates, "markdown")
|
|
9765
|
+
].join("\n");
|
|
9766
|
+
}
|
|
9767
|
+
case "read_resource": return [
|
|
9768
|
+
`## Resource \`${String(request.uri ?? "")}\``,
|
|
9769
|
+
"",
|
|
9770
|
+
summarizeResourceRead(payload),
|
|
9771
|
+
"",
|
|
9772
|
+
"Use `--format json` to inspect all contents."
|
|
9773
|
+
].join("\n");
|
|
9774
|
+
case "list_prompts":
|
|
9775
|
+
case "search_prompts": {
|
|
9776
|
+
const prompts = Array.isArray(payload.prompts) ? payload.prompts : [];
|
|
9777
|
+
return [
|
|
9778
|
+
`## MCP prompts for \`${id}\``,
|
|
9779
|
+
"",
|
|
9780
|
+
...formatPromptLines(prompts, "markdown")
|
|
9781
|
+
].join("\n");
|
|
9782
|
+
}
|
|
9783
|
+
case "get_prompt": return [
|
|
9784
|
+
`## Prompt \`${String(request.caplet)}.${String(request.prompt)}\``,
|
|
9785
|
+
"",
|
|
9786
|
+
summarizePromptResult(payload),
|
|
9787
|
+
"",
|
|
9788
|
+
"Use `--format json` to inspect all messages."
|
|
9789
|
+
].join("\n");
|
|
9790
|
+
case "complete": return [
|
|
9791
|
+
`## Completion for \`${id}\``,
|
|
9792
|
+
"",
|
|
9793
|
+
summarizeCompletionResult(payload)
|
|
9794
|
+
].join("\n");
|
|
9004
9795
|
default: return JSON.stringify(payload, null, 2);
|
|
9005
9796
|
}
|
|
9006
9797
|
}
|
|
@@ -9057,6 +9848,33 @@ function plainSummaryForOperation(result, request) {
|
|
|
9057
9848
|
`Result: ${summarizeCallResult(payload)}`,
|
|
9058
9849
|
"Use --format json to inspect the full structured result."
|
|
9059
9850
|
].filter((line) => Boolean(line)).join("\n");
|
|
9851
|
+
case "list_resources":
|
|
9852
|
+
case "search_resources": {
|
|
9853
|
+
const resources = Array.isArray(payload.resources) ? payload.resources : [];
|
|
9854
|
+
const templates = Array.isArray(payload.resourceTemplates) ? payload.resourceTemplates : [];
|
|
9855
|
+
const matches = Array.isArray(payload.matches) ? payload.matches : [...resources, ...templates];
|
|
9856
|
+
return [`MCP resources for ${id} (${matches.length}):`, ...formatResourceLines(matches, "plain")].join("\n");
|
|
9857
|
+
}
|
|
9858
|
+
case "list_resource_templates": {
|
|
9859
|
+
const templates = Array.isArray(payload.resourceTemplates) ? payload.resourceTemplates : [];
|
|
9860
|
+
return [`MCP resource templates for ${id}:`, ...formatResourceLines(templates, "plain")].join("\n");
|
|
9861
|
+
}
|
|
9862
|
+
case "read_resource": return [
|
|
9863
|
+
`Resource ${String(request.uri ?? "")}`,
|
|
9864
|
+
summarizeResourceRead(payload),
|
|
9865
|
+
"Use --format json to inspect all contents."
|
|
9866
|
+
].join("\n");
|
|
9867
|
+
case "list_prompts":
|
|
9868
|
+
case "search_prompts": {
|
|
9869
|
+
const prompts = Array.isArray(payload.prompts) ? payload.prompts : [];
|
|
9870
|
+
return [`MCP prompts for ${id}:`, ...formatPromptLines(prompts, "plain")].join("\n");
|
|
9871
|
+
}
|
|
9872
|
+
case "get_prompt": return [
|
|
9873
|
+
`Prompt ${String(request.caplet)}.${String(request.prompt)}`,
|
|
9874
|
+
summarizePromptResult(payload),
|
|
9875
|
+
"Use --format json to inspect all messages."
|
|
9876
|
+
].join("\n");
|
|
9877
|
+
case "complete": return [`Completion for ${id}`, summarizeCompletionResult(payload)].join("\n");
|
|
9060
9878
|
default: return JSON.stringify(payload, null, 2);
|
|
9061
9879
|
}
|
|
9062
9880
|
}
|
|
@@ -9073,6 +9891,44 @@ function formatToolLines(tools, format) {
|
|
|
9073
9891
|
return `- ${displayName}${flags ? ` (${flags})` : ""}${tool.description ? ` — ${compactDescription(String(tool.description))}` : ""}`;
|
|
9074
9892
|
});
|
|
9075
9893
|
}
|
|
9894
|
+
function formatResourceLines(resources, format) {
|
|
9895
|
+
if (resources.length === 0) return ["- none"];
|
|
9896
|
+
return resources.map((resource) => {
|
|
9897
|
+
if (!isPlainObject(resource)) return `- ${String(resource)}`;
|
|
9898
|
+
const name = String(resource.uri ?? resource.uriTemplate ?? "unknown");
|
|
9899
|
+
const displayName = format === "markdown" ? `\`${name}\`` : name;
|
|
9900
|
+
const label = typeof resource.name === "string" ? ` (${resource.name})` : "";
|
|
9901
|
+
return `- ${typeof resource.kind === "string" ? `${resource.kind}: ` : ""}${displayName}${label}${resource.description ? ` — ${compactDescription(String(resource.description))}` : ""}`;
|
|
9902
|
+
});
|
|
9903
|
+
}
|
|
9904
|
+
function formatPromptLines(prompts, format) {
|
|
9905
|
+
if (prompts.length === 0) return ["- none"];
|
|
9906
|
+
return prompts.map((prompt) => {
|
|
9907
|
+
if (!isPlainObject(prompt)) return `- ${String(prompt)}`;
|
|
9908
|
+
const name = String(prompt.prompt ?? prompt.name ?? "unknown");
|
|
9909
|
+
return `- ${format === "markdown" ? `\`${name}\`` : name}${Array.isArray(prompt.arguments) ? ` (${prompt.arguments.length} args)` : ""}${prompt.description ? ` — ${compactDescription(String(prompt.description))}` : ""}`;
|
|
9910
|
+
});
|
|
9911
|
+
}
|
|
9912
|
+
function summarizeResourceRead(payload) {
|
|
9913
|
+
const contents = Array.isArray(payload.contents) ? payload.contents : [];
|
|
9914
|
+
if (contents.length === 0) return "No contents returned.";
|
|
9915
|
+
const first = contents.find(isPlainObject);
|
|
9916
|
+
if (!first) return `${contents.length} content item${contents.length === 1 ? "" : "s"} returned.`;
|
|
9917
|
+
return previewValue(typeof first.text === "string" ? first.text : first.blob) ?? `${contents.length} content item${contents.length === 1 ? "" : "s"} returned.`;
|
|
9918
|
+
}
|
|
9919
|
+
function summarizePromptResult(payload) {
|
|
9920
|
+
const messages = Array.isArray(payload.messages) ? payload.messages : [];
|
|
9921
|
+
if (messages.length === 0) return "No messages returned.";
|
|
9922
|
+
const first = messages.find(isPlainObject);
|
|
9923
|
+
if (!first) return `${messages.length} message${messages.length === 1 ? "" : "s"} returned.`;
|
|
9924
|
+
return previewValue((isPlainObject(first.content) ? first.content : void 0)?.text ?? first.content) ?? `${messages.length} message${messages.length === 1 ? "" : "s"} returned.`;
|
|
9925
|
+
}
|
|
9926
|
+
function summarizeCompletionResult(payload) {
|
|
9927
|
+
const completion = isPlainObject(payload.completion) ? payload.completion : void 0;
|
|
9928
|
+
const values = Array.isArray(completion?.values) ? completion.values : [];
|
|
9929
|
+
if (values.length > 0) return values.map((value) => `- ${String(value)}`).join("\n");
|
|
9930
|
+
return previewValue(payload) ?? "No completions returned.";
|
|
9931
|
+
}
|
|
9076
9932
|
function compactDescription(value) {
|
|
9077
9933
|
const firstParagraph = value.trim().split(/\n\s*\n/u)[0] ?? "";
|
|
9078
9934
|
const collapsed = (firstParagraph.match(/^.*?(?:[.!?](?=\s|$)|$)/u)?.[0] ?? firstParagraph).replace(/\s+/gu, " ").trim();
|
|
@@ -9135,12 +9991,12 @@ function schemaSummary(schema) {
|
|
|
9135
9991
|
required.length > 0 ? `required ${required.join(", ")}` : "no required fields"
|
|
9136
9992
|
].filter((part) => Boolean(part)).join("; ");
|
|
9137
9993
|
}
|
|
9138
|
-
function addDestinationRoot(options) {
|
|
9139
|
-
return options.global ? resolveCapletsRoot(resolveConfigPath(
|
|
9994
|
+
function addDestinationRoot(options, configPath) {
|
|
9995
|
+
return options.global ? resolveCapletsRoot(resolveConfigPath(configPath)) : resolveProjectCapletsRoot();
|
|
9140
9996
|
}
|
|
9141
9997
|
function writeAddResult(writeOut, label, result) {
|
|
9142
9998
|
if (result.path) {
|
|
9143
|
-
writeOut(`Wrote ${label} Caplet to ${result.path}\n`);
|
|
9999
|
+
writeOut(`Wrote ${result.remote ? "remote " : ""}${label} Caplet to ${result.path}\n`);
|
|
9144
10000
|
return;
|
|
9145
10001
|
}
|
|
9146
10002
|
writeOut(result.text);
|