@caplets/core 0.16.0 → 0.17.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.d.ts +2 -0
- package/dist/index.js +746 -77
- package/dist/native/options.d.ts +4 -5
- package/dist/native.js +15 -58
- package/dist/{engine-Brwid_mq.js → options-CJEOqS87.js} +238 -20
- 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/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { A as CompleteRequestSchema, At as string, B as InitializedNotificationSchema, C as assertToolsCallTaskCapability, Ct as resolveProjectConfigPath, D as toJsonSchemaCompat, Dt as ZodOptional, E as mergeCapabilities, Et as SERVER_ID_PATTERN, F as ElicitResultSchema, Ft as toSafeError, G as ListResourcesRequestSchema, H as LATEST_PROTOCOL_VERSION, I as EmptyResultSchema, It as __commonJSMin, J as LoggingLevelSchema, K as ListRootsResultSchema, L as ErrorCode, Lt as __require, M as CreateMessageResultWithToolsSchema, Mt as CAPLETS_ERROR_CODES, N as CreateTaskResultSchema, Nt as CapletsError, O as CallToolRequestSchema, Ot as literal, Pt as redactSecrets, Q as SetLevelRequestSchema, R as GetPromptRequestSchema, Rt as __toESM, S as assertClientRequestTaskCapability, St as resolveProjectCapletsRoot, T as Protocol, Tt as validateCapletFile, U as ListPromptsRequestSchema, V as JSONRPCMessageSchema, W as ListResourceTemplatesRequestSchema, X as ReadResourceRequestSchema, Y as McpError, Z as SUPPORTED_PROTOCOL_VERSIONS, _ as readTokenBundle, _t as loadConfigWithSources, a as resolveCapletsServer, at as isJSONRPCResultResponse, b as serializeMessage, bt as resolveCapletsRoot, c as handleServerTool, ct as getParseErrorMessage, d as runGenericOAuthFlow, dt as isZ4Schema, et as assertCompleteRequestPrompt, f as runOAuthFlow, ft as normalizeObjectSchema, g as isTokenBundleExpired, gt as loadConfig, h as deleteTokenBundle, ht as safeParseAsync, i as resolveCapletsMode, it as isJSONRPCRequest, j as CreateMessageResultSchema, jt as url, k as CallToolResultSchema, kt as object, l as ServerRegistry, lt as getSchemaDescription, m as startOAuthFlow, mt as safeParse, nt as isInitializeRequest, o as CapletsEngine, ot as getLiteralValue, p as startGenericOAuthFlow, pt as objectFromShape, q as ListToolsRequestSchema, r as parseServerBaseUrl, rt as isJSONRPCErrorResponse, s as generatedToolInputSchema, st as getObjectShape, t as controlUrlForBase, tt as assertCompleteRequestResourceTemplate, u as capabilityDescription, ut as isSchemaOptional, vt as parseConfig, w as AjvJsonSchemaValidator, wt as discoverCapletFiles, xt as resolveConfigPath, y as ReadBuffer, yt as DEFAULT_AUTH_DIR, z as InitializeRequestSchema } from "./options-CJEOqS87.js";
|
|
2
2
|
import { accessSync, chmodSync, closeSync, constants, cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, rmSync, statSync, writeFileSync, writeSync } from "node:fs";
|
|
3
3
|
import { basename, dirname, join, parse, relative, resolve } from "node:path";
|
|
4
4
|
import { execFileSync } from "node:child_process";
|
|
@@ -1319,7 +1319,7 @@ const EMPTY_COMPLETION_RESULT = { completion: {
|
|
|
1319
1319
|
} };
|
|
1320
1320
|
//#endregion
|
|
1321
1321
|
//#region package.json
|
|
1322
|
-
var version = "0.
|
|
1322
|
+
var version = "0.17.0";
|
|
1323
1323
|
//#endregion
|
|
1324
1324
|
//#region src/serve/session.ts
|
|
1325
1325
|
var CapletsMcpSession = class {
|
|
@@ -4921,49 +4921,56 @@ async function loginAuth(serverId, options) {
|
|
|
4921
4921
|
}
|
|
4922
4922
|
}
|
|
4923
4923
|
function logoutAuth(serverId, options) {
|
|
4924
|
-
|
|
4925
|
-
if (deleteTokenBundle(serverId, options.authDir)) options.writeOut(`Deleted OAuth credentials for \`${serverId}\`.\n`);
|
|
4924
|
+
if (logoutAuthResult(serverId, options).deleted) options.writeOut(`Deleted OAuth credentials for \`${serverId}\`.\n`);
|
|
4926
4925
|
else options.writeOut(`No OAuth credentials found for \`${serverId}\`.\n`);
|
|
4927
4926
|
}
|
|
4927
|
+
function logoutAuthResult(serverId, options) {
|
|
4928
|
+
assertLoginTarget(findAuthTarget(serverId, loadConfig(options.configPath)), serverId);
|
|
4929
|
+
return {
|
|
4930
|
+
server: serverId,
|
|
4931
|
+
deleted: deleteTokenBundle(serverId, options.authDir)
|
|
4932
|
+
};
|
|
4933
|
+
}
|
|
4928
4934
|
function listAuth(options) {
|
|
4929
|
-
const
|
|
4935
|
+
const rows = listAuthRows(options);
|
|
4930
4936
|
const format = options.format ?? "plain";
|
|
4931
4937
|
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
4938
|
options.writeOut(`${JSON.stringify(rows, null, 2)}\n`);
|
|
4943
4939
|
return;
|
|
4944
4940
|
}
|
|
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) {
|
|
4941
|
+
options.writeOut(formatAuthRows(rows, format));
|
|
4942
|
+
}
|
|
4943
|
+
function listAuthRows(options) {
|
|
4944
|
+
return authTargets(loadConfig(options.configPath)).sort((left, right) => left.server.localeCompare(right.server)).map((server) => {
|
|
4952
4945
|
const bundle = readTokenBundle(server.server, options.authDir);
|
|
4953
4946
|
const status = !bundle ? "missing" : isTokenBundleExpired(bundle) ? "expired" : "authenticated";
|
|
4954
|
-
|
|
4947
|
+
return {
|
|
4948
|
+
server: server.server,
|
|
4949
|
+
status,
|
|
4950
|
+
...bundle?.expiresAt ? { expiresAt: bundle.expiresAt } : {},
|
|
4951
|
+
...bundle?.scope ? { scope: bundle.scope } : {}
|
|
4952
|
+
};
|
|
4953
|
+
});
|
|
4954
|
+
}
|
|
4955
|
+
function formatAuthRows(rows, format) {
|
|
4956
|
+
if (rows.length === 0) return format === "markdown" ? "## OAuth credentials\n\nNo configured remote OAuth servers found.\n" : "No configured remote OAuth servers found.\n";
|
|
4957
|
+
let output = "";
|
|
4958
|
+
if (format === "markdown") output += "## OAuth credentials\n\n";
|
|
4959
|
+
else output += "OAuth credentials\n\n";
|
|
4960
|
+
for (const row of rows) {
|
|
4961
|
+
const details = [row.expiresAt ? `expires ${row.expiresAt}` : void 0, row.scope ? `scope ${row.scope}` : void 0].filter(Boolean).join("; ");
|
|
4955
4962
|
if (format === "markdown") {
|
|
4956
|
-
|
|
4963
|
+
output += `- \`${row.server}\` — ${row.status}${details ? ` (${details})` : ""}\n`;
|
|
4957
4964
|
continue;
|
|
4958
4965
|
}
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
` Status: ${status}`,
|
|
4962
|
-
...
|
|
4963
|
-
...
|
|
4964
|
-
].join("\n")
|
|
4965
|
-
options.writeOut("\n\n");
|
|
4966
|
+
output += [
|
|
4967
|
+
row.server,
|
|
4968
|
+
` Status: ${row.status}`,
|
|
4969
|
+
...row.expiresAt ? [` Expires: ${row.expiresAt}`] : [],
|
|
4970
|
+
...row.scope ? [` Scope: ${row.scope}`] : []
|
|
4971
|
+
].join("\n") + "\n\n";
|
|
4966
4972
|
}
|
|
4973
|
+
return output;
|
|
4967
4974
|
}
|
|
4968
4975
|
function findAuthTarget(serverId, config = loadConfig()) {
|
|
4969
4976
|
return authTargets(config).find((server) => server.server === serverId);
|
|
@@ -5390,6 +5397,96 @@ function nearestExistingParent(path) {
|
|
|
5390
5397
|
return nearestExistingParent(parent);
|
|
5391
5398
|
}
|
|
5392
5399
|
//#endregion
|
|
5400
|
+
//#region src/remote-control/client.ts
|
|
5401
|
+
var RemoteControlClient = class {
|
|
5402
|
+
#baseUrl;
|
|
5403
|
+
#requestInit;
|
|
5404
|
+
#fetch;
|
|
5405
|
+
constructor(options) {
|
|
5406
|
+
this.#baseUrl = options.baseUrl;
|
|
5407
|
+
this.#requestInit = options.requestInit;
|
|
5408
|
+
this.#fetch = options.fetch ?? fetch;
|
|
5409
|
+
}
|
|
5410
|
+
async request(command, args) {
|
|
5411
|
+
const controlUrl = controlUrlForBase(this.#baseUrl);
|
|
5412
|
+
let response;
|
|
5413
|
+
try {
|
|
5414
|
+
response = await this.#fetch(controlUrl, {
|
|
5415
|
+
...this.#requestInit,
|
|
5416
|
+
method: "POST",
|
|
5417
|
+
headers: mergeJsonHeaders(this.#requestInit.headers),
|
|
5418
|
+
body: JSON.stringify({
|
|
5419
|
+
command,
|
|
5420
|
+
arguments: args
|
|
5421
|
+
})
|
|
5422
|
+
});
|
|
5423
|
+
} catch (error) {
|
|
5424
|
+
throw new CapletsError("SERVER_UNAVAILABLE", `Could not connect to Caplets server at ${safeBaseUrl(this.#baseUrl)}.`, toSafeError(error, "SERVER_UNAVAILABLE"));
|
|
5425
|
+
}
|
|
5426
|
+
if (response.status === 401 || response.status === 403) throw new CapletsError("AUTH_FAILED", "Caplets server authentication failed. Check CAPLETS_SERVER_USER and CAPLETS_SERVER_PASSWORD.");
|
|
5427
|
+
if (!response.ok) throw new CapletsError("SERVER_UNAVAILABLE", `Caplets server at ${safeBaseUrl(this.#baseUrl)} returned HTTP ${response.status}.`);
|
|
5428
|
+
const payload = await parseRemoteCliResponse(response);
|
|
5429
|
+
if (!payload.ok) throw new CapletsError(payload.error.code, redactRemoteMessage(payload.error.message), payload.error.nextAction === void 0 ? void 0 : { nextAction: payload.error.nextAction });
|
|
5430
|
+
return payload.result;
|
|
5431
|
+
}
|
|
5432
|
+
};
|
|
5433
|
+
function mergeJsonHeaders(headers) {
|
|
5434
|
+
const merged = new Headers(headers);
|
|
5435
|
+
merged.set("content-type", "application/json");
|
|
5436
|
+
return merged;
|
|
5437
|
+
}
|
|
5438
|
+
function safeBaseUrl(baseUrl) {
|
|
5439
|
+
const safe = new URL(baseUrl.href);
|
|
5440
|
+
safe.username = "";
|
|
5441
|
+
safe.password = "";
|
|
5442
|
+
safe.search = "";
|
|
5443
|
+
safe.hash = "";
|
|
5444
|
+
return safe.toString();
|
|
5445
|
+
}
|
|
5446
|
+
async function parseRemoteCliResponse(response) {
|
|
5447
|
+
let payload;
|
|
5448
|
+
try {
|
|
5449
|
+
payload = await response.json();
|
|
5450
|
+
} catch (error) {
|
|
5451
|
+
throw invalidRemoteControlResponse(error);
|
|
5452
|
+
}
|
|
5453
|
+
if (!isRecord(payload)) throw invalidRemoteControlResponse();
|
|
5454
|
+
if (payload.ok === true) {
|
|
5455
|
+
if (!("result" in payload)) throw invalidRemoteControlResponse();
|
|
5456
|
+
return {
|
|
5457
|
+
ok: true,
|
|
5458
|
+
result: payload.result
|
|
5459
|
+
};
|
|
5460
|
+
}
|
|
5461
|
+
if (payload.ok === false) {
|
|
5462
|
+
const error = payload.error;
|
|
5463
|
+
if (!isRecord(error) || typeof error.code !== "string" || typeof error.message !== "string") throw invalidRemoteControlResponse();
|
|
5464
|
+
if ("nextAction" in error && error.nextAction !== void 0 && typeof error.nextAction !== "string") throw invalidRemoteControlResponse();
|
|
5465
|
+
const errorResponse = {
|
|
5466
|
+
ok: false,
|
|
5467
|
+
error: {
|
|
5468
|
+
code: isCapletsErrorCode(error.code) ? error.code : "DOWNSTREAM_TOOL_ERROR",
|
|
5469
|
+
message: error.message
|
|
5470
|
+
}
|
|
5471
|
+
};
|
|
5472
|
+
if (typeof error.nextAction === "string") errorResponse.error.nextAction = error.nextAction;
|
|
5473
|
+
return errorResponse;
|
|
5474
|
+
}
|
|
5475
|
+
throw invalidRemoteControlResponse();
|
|
5476
|
+
}
|
|
5477
|
+
function invalidRemoteControlResponse(cause) {
|
|
5478
|
+
return new CapletsError("DOWNSTREAM_PROTOCOL_ERROR", "Caplets server returned an invalid remote control response.", cause === void 0 ? void 0 : toSafeError(cause, "DOWNSTREAM_PROTOCOL_ERROR"));
|
|
5479
|
+
}
|
|
5480
|
+
function isRecord(value) {
|
|
5481
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5482
|
+
}
|
|
5483
|
+
function isCapletsErrorCode(value) {
|
|
5484
|
+
return CAPLETS_ERROR_CODES.includes(value);
|
|
5485
|
+
}
|
|
5486
|
+
function redactRemoteMessage(message) {
|
|
5487
|
+
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]");
|
|
5488
|
+
}
|
|
5489
|
+
//#endregion
|
|
5393
5490
|
//#region ../../node_modules/.pnpm/hono@4.12.19/node_modules/hono/dist/compose.js
|
|
5394
5491
|
var compose = (middleware, onError, onNotFound) => {
|
|
5395
5492
|
return (context, next) => {
|
|
@@ -8309,30 +8406,348 @@ var logger = (fn = console.log) => {
|
|
|
8309
8406
|
};
|
|
8310
8407
|
};
|
|
8311
8408
|
//#endregion
|
|
8409
|
+
//#region src/remote-control/dispatch.ts
|
|
8410
|
+
const ENGINE_COMMANDS = new Set([
|
|
8411
|
+
"get_caplet",
|
|
8412
|
+
"check_backend",
|
|
8413
|
+
"list_tools",
|
|
8414
|
+
"search_tools",
|
|
8415
|
+
"get_tool",
|
|
8416
|
+
"call_tool"
|
|
8417
|
+
]);
|
|
8418
|
+
async function dispatchRemoteCliRequest(request, context) {
|
|
8419
|
+
try {
|
|
8420
|
+
return {
|
|
8421
|
+
ok: true,
|
|
8422
|
+
result: await dispatch(request, context)
|
|
8423
|
+
};
|
|
8424
|
+
} catch (error) {
|
|
8425
|
+
const safe = toSafeError(error);
|
|
8426
|
+
const action = nextAction(safe.details);
|
|
8427
|
+
return {
|
|
8428
|
+
ok: false,
|
|
8429
|
+
error: {
|
|
8430
|
+
code: safe.code,
|
|
8431
|
+
message: redactControlErrorMessage(safe.message),
|
|
8432
|
+
...action ? { nextAction: action } : {}
|
|
8433
|
+
}
|
|
8434
|
+
};
|
|
8435
|
+
}
|
|
8436
|
+
}
|
|
8437
|
+
async function dispatch(request, context) {
|
|
8438
|
+
assertObject(request, "remote control request");
|
|
8439
|
+
assertObject(request.arguments, "remote control request arguments");
|
|
8440
|
+
if (request.command === "list") return listCaplets(loadConfigWithSources(context.configPath, context.projectConfigPath), { includeDisabled: optionalBoolean(request.arguments, "includeDisabled") ?? false });
|
|
8441
|
+
if (ENGINE_COMMANDS.has(request.command)) {
|
|
8442
|
+
const caplet = requiredString(request.arguments, "caplet");
|
|
8443
|
+
const toolRequest = requiredEngineRequest(request.arguments, request.command);
|
|
8444
|
+
const engine = new CapletsEngine(context);
|
|
8445
|
+
try {
|
|
8446
|
+
return await engine.execute(caplet, toolRequest);
|
|
8447
|
+
} finally {
|
|
8448
|
+
await engine.close();
|
|
8449
|
+
}
|
|
8450
|
+
}
|
|
8451
|
+
if (request.command === "init") return {
|
|
8452
|
+
remote: true,
|
|
8453
|
+
path: initConfig({
|
|
8454
|
+
...optionalProp("path", context.configPath),
|
|
8455
|
+
...optionalProp("force", optionalBoolean(request.arguments, "force"))
|
|
8456
|
+
})
|
|
8457
|
+
};
|
|
8458
|
+
if (request.command === "add") return dispatchAdd(request.arguments, context);
|
|
8459
|
+
if (request.command === "install") return {
|
|
8460
|
+
remote: true,
|
|
8461
|
+
...installCaplets(requiredString(request.arguments, "repo"), {
|
|
8462
|
+
...optionalProp("capletIds", optionalStringArray(request.arguments, "capletIds")),
|
|
8463
|
+
destinationRoot: context.projectCapletsRoot,
|
|
8464
|
+
...optionalProp("force", optionalBoolean(request.arguments, "force"))
|
|
8465
|
+
})
|
|
8466
|
+
};
|
|
8467
|
+
if (request.command === "auth_list") return listAuthRows({
|
|
8468
|
+
...optionalProp("configPath", context.configPath),
|
|
8469
|
+
...optionalProp("authDir", context.authDir)
|
|
8470
|
+
});
|
|
8471
|
+
if (request.command === "auth_logout") return logoutAuthResult(requiredString(request.arguments, "server"), {
|
|
8472
|
+
...optionalProp("configPath", context.configPath),
|
|
8473
|
+
...optionalProp("authDir", context.authDir)
|
|
8474
|
+
});
|
|
8475
|
+
if (request.command === "auth_login_start") return startRemoteAuthLogin(requiredString(request.arguments, "server"), context);
|
|
8476
|
+
if (request.command === "auth_login_complete") return completeRemoteAuthLogin(requiredString(request.arguments, "flowId"), requiredString(request.arguments, "callbackUrl"), context);
|
|
8477
|
+
throw new CapletsError("UNKNOWN_OPERATION", `Unsupported remote control command ${request.command}`);
|
|
8478
|
+
}
|
|
8479
|
+
async function startRemoteAuthLogin(serverId, context) {
|
|
8480
|
+
if (!context.authFlowStore || !context.controlCallbackBaseUrl) throw new CapletsError("REQUEST_INVALID", "Remote auth login is not available on this server");
|
|
8481
|
+
const config = loadConfigWithSources(context.configPath, context.projectConfigPath).config;
|
|
8482
|
+
const target = findAuthTarget(serverId, config);
|
|
8483
|
+
assertLoginTarget(target, serverId);
|
|
8484
|
+
const flowId = randomUUID();
|
|
8485
|
+
const baseUrl = context.controlCallbackBaseUrl.endsWith("/") ? context.controlCallbackBaseUrl : `${context.controlCallbackBaseUrl}/`;
|
|
8486
|
+
const redirectUri = new URL(`auth/callback/${flowId}`, baseUrl).toString();
|
|
8487
|
+
const started = target.backend === "mcp" ? await startOAuthFlow(target, {
|
|
8488
|
+
redirectUri,
|
|
8489
|
+
...optionalProp("authDir", context.authDir)
|
|
8490
|
+
}) : await startGenericOAuthFlow(target, {
|
|
8491
|
+
redirectUri,
|
|
8492
|
+
...optionalProp("authDir", context.authDir)
|
|
8493
|
+
});
|
|
8494
|
+
if (!started.authorizationUrl) return {
|
|
8495
|
+
server: serverId,
|
|
8496
|
+
authenticated: true
|
|
8497
|
+
};
|
|
8498
|
+
const flow = context.authFlowStore.create({
|
|
8499
|
+
server: serverId,
|
|
8500
|
+
authorizationUrl: started.authorizationUrl,
|
|
8501
|
+
complete: started.complete
|
|
8502
|
+
}, flowId);
|
|
8503
|
+
return {
|
|
8504
|
+
server: serverId,
|
|
8505
|
+
flowId: flow.id,
|
|
8506
|
+
authorizationUrl: flow.authorizationUrl
|
|
8507
|
+
};
|
|
8508
|
+
}
|
|
8509
|
+
async function completeRemoteAuthLogin(flowId, callbackUrl, context) {
|
|
8510
|
+
const flow = context.authFlowStore?.get(flowId);
|
|
8511
|
+
if (!flow) throw new CapletsError("REQUEST_INVALID", `Unknown auth flow ${flowId}`);
|
|
8512
|
+
context.authFlowStore?.delete(flowId);
|
|
8513
|
+
await flow.complete(callbackUrl);
|
|
8514
|
+
return {
|
|
8515
|
+
server: flow.server,
|
|
8516
|
+
authenticated: true
|
|
8517
|
+
};
|
|
8518
|
+
}
|
|
8519
|
+
function dispatchAdd(args, context) {
|
|
8520
|
+
const kind = requiredString(args, "kind");
|
|
8521
|
+
const id = requiredString(args, "id");
|
|
8522
|
+
const options = remoteAddOptions$1(kind, optionalObject(args, "options"));
|
|
8523
|
+
switch (kind) {
|
|
8524
|
+
case "cli": return {
|
|
8525
|
+
remote: true,
|
|
8526
|
+
label: "CLI",
|
|
8527
|
+
...addCliCaplet(id, {
|
|
8528
|
+
...options,
|
|
8529
|
+
destinationRoot: context.projectCapletsRoot,
|
|
8530
|
+
print: false
|
|
8531
|
+
})
|
|
8532
|
+
};
|
|
8533
|
+
case "mcp": return {
|
|
8534
|
+
remote: true,
|
|
8535
|
+
label: "MCP",
|
|
8536
|
+
...addMcpCaplet(id, {
|
|
8537
|
+
...options,
|
|
8538
|
+
destinationRoot: context.projectCapletsRoot,
|
|
8539
|
+
print: false
|
|
8540
|
+
})
|
|
8541
|
+
};
|
|
8542
|
+
case "openapi": return {
|
|
8543
|
+
remote: true,
|
|
8544
|
+
label: "OpenAPI",
|
|
8545
|
+
...addOpenApiCaplet(id, {
|
|
8546
|
+
...options,
|
|
8547
|
+
destinationRoot: context.projectCapletsRoot,
|
|
8548
|
+
print: false
|
|
8549
|
+
})
|
|
8550
|
+
};
|
|
8551
|
+
case "graphql": return {
|
|
8552
|
+
remote: true,
|
|
8553
|
+
label: "GraphQL",
|
|
8554
|
+
...addGraphqlCaplet(id, {
|
|
8555
|
+
...options,
|
|
8556
|
+
destinationRoot: context.projectCapletsRoot,
|
|
8557
|
+
print: false
|
|
8558
|
+
})
|
|
8559
|
+
};
|
|
8560
|
+
case "http": return {
|
|
8561
|
+
remote: true,
|
|
8562
|
+
label: "HTTP",
|
|
8563
|
+
...addHttpCaplet(id, {
|
|
8564
|
+
...options,
|
|
8565
|
+
destinationRoot: context.projectCapletsRoot,
|
|
8566
|
+
print: false
|
|
8567
|
+
})
|
|
8568
|
+
};
|
|
8569
|
+
default: throw new CapletsError("REQUEST_INVALID", "add.kind must be cli, mcp, openapi, graphql, or http");
|
|
8570
|
+
}
|
|
8571
|
+
}
|
|
8572
|
+
function optionalProp(key, value) {
|
|
8573
|
+
return value === void 0 ? {} : { [key]: value };
|
|
8574
|
+
}
|
|
8575
|
+
function assertObject(value, label) {
|
|
8576
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new CapletsError("REQUEST_INVALID", `${label} must be an object`);
|
|
8577
|
+
}
|
|
8578
|
+
function requiredString(args, key) {
|
|
8579
|
+
const value = args[key];
|
|
8580
|
+
if (typeof value !== "string" || value.length === 0) throw new CapletsError("REQUEST_INVALID", `${key} must be a non-empty string`);
|
|
8581
|
+
return value;
|
|
8582
|
+
}
|
|
8583
|
+
function optionalObject(args, key) {
|
|
8584
|
+
const value = args[key];
|
|
8585
|
+
if (value === void 0) return {};
|
|
8586
|
+
assertObject(value, key);
|
|
8587
|
+
return value;
|
|
8588
|
+
}
|
|
8589
|
+
function requiredEngineRequest(args, command) {
|
|
8590
|
+
const toolRequest = optionalObject(args, "request");
|
|
8591
|
+
if (typeof toolRequest.operation !== "string") throw new CapletsError("REQUEST_INVALID", "request.operation must be a string");
|
|
8592
|
+
if (toolRequest.operation !== command) throw new CapletsError("REQUEST_INVALID", `request.operation must match remote command ${command}`);
|
|
8593
|
+
return toolRequest;
|
|
8594
|
+
}
|
|
8595
|
+
function remoteAddOptions$1(kind, options) {
|
|
8596
|
+
rejectServerOwnedAddOptions(options);
|
|
8597
|
+
switch (kind) {
|
|
8598
|
+
case "cli": return pickOptions(options, {
|
|
8599
|
+
repo: "string",
|
|
8600
|
+
include: "string",
|
|
8601
|
+
command: "string",
|
|
8602
|
+
force: "boolean"
|
|
8603
|
+
});
|
|
8604
|
+
case "mcp": return pickOptions(options, {
|
|
8605
|
+
command: "string",
|
|
8606
|
+
arg: "string-array",
|
|
8607
|
+
cwd: "string",
|
|
8608
|
+
env: "string-array",
|
|
8609
|
+
url: "string",
|
|
8610
|
+
transport: "string",
|
|
8611
|
+
tokenEnv: "string",
|
|
8612
|
+
force: "boolean"
|
|
8613
|
+
});
|
|
8614
|
+
case "openapi": return pickOptions(options, {
|
|
8615
|
+
spec: "string",
|
|
8616
|
+
baseUrl: "string",
|
|
8617
|
+
tokenEnv: "string",
|
|
8618
|
+
force: "boolean"
|
|
8619
|
+
});
|
|
8620
|
+
case "graphql": return pickOptions(options, {
|
|
8621
|
+
endpointUrl: "string",
|
|
8622
|
+
schema: "string",
|
|
8623
|
+
introspection: "boolean",
|
|
8624
|
+
tokenEnv: "string",
|
|
8625
|
+
force: "boolean"
|
|
8626
|
+
});
|
|
8627
|
+
case "http": return pickOptions(options, {
|
|
8628
|
+
baseUrl: "string",
|
|
8629
|
+
action: "string-array",
|
|
8630
|
+
tokenEnv: "string",
|
|
8631
|
+
force: "boolean"
|
|
8632
|
+
});
|
|
8633
|
+
default: return options;
|
|
8634
|
+
}
|
|
8635
|
+
}
|
|
8636
|
+
function pickOptions(options, schema) {
|
|
8637
|
+
const next = {};
|
|
8638
|
+
for (const [key, type] of Object.entries(schema)) {
|
|
8639
|
+
const value = options[key];
|
|
8640
|
+
if (value === void 0) continue;
|
|
8641
|
+
validateOptionType(key, value, type);
|
|
8642
|
+
next[key] = value;
|
|
8643
|
+
}
|
|
8644
|
+
return next;
|
|
8645
|
+
}
|
|
8646
|
+
function rejectServerOwnedAddOptions(options) {
|
|
8647
|
+
if ("output" in options) throw new CapletsError("REQUEST_INVALID", "Remote add output is not supported remotely; the server owns destinationRoot and output path selection");
|
|
8648
|
+
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`);
|
|
8649
|
+
}
|
|
8650
|
+
function validateOptionType(key, value, type) {
|
|
8651
|
+
if (type === "string" && typeof value !== "string") throw new CapletsError("REQUEST_INVALID", `add.options.${key} must be a string`);
|
|
8652
|
+
if (type === "boolean" && typeof value !== "boolean") throw new CapletsError("REQUEST_INVALID", `add.options.${key} must be a boolean`);
|
|
8653
|
+
if (type === "string-array") {
|
|
8654
|
+
if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) throw new CapletsError("REQUEST_INVALID", `add.options.${key} must be an array of strings`);
|
|
8655
|
+
}
|
|
8656
|
+
}
|
|
8657
|
+
function optionalBoolean(args, key) {
|
|
8658
|
+
const value = args[key];
|
|
8659
|
+
if (value === void 0) return;
|
|
8660
|
+
if (typeof value !== "boolean") throw new CapletsError("REQUEST_INVALID", `${key} must be a boolean`);
|
|
8661
|
+
return value;
|
|
8662
|
+
}
|
|
8663
|
+
function optionalStringArray(args, key) {
|
|
8664
|
+
const value = args[key];
|
|
8665
|
+
if (value === void 0) return;
|
|
8666
|
+
if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) throw new CapletsError("REQUEST_INVALID", `${key} must be an array of strings`);
|
|
8667
|
+
return value;
|
|
8668
|
+
}
|
|
8669
|
+
function nextAction(details) {
|
|
8670
|
+
if (details && typeof details === "object" && "nextAction" in details) {
|
|
8671
|
+
const value = details.nextAction;
|
|
8672
|
+
return typeof value === "string" ? value : void 0;
|
|
8673
|
+
}
|
|
8674
|
+
}
|
|
8675
|
+
function redactControlErrorMessage(message) {
|
|
8676
|
+
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]");
|
|
8677
|
+
}
|
|
8678
|
+
//#endregion
|
|
8679
|
+
//#region src/remote-control/auth-flow.ts
|
|
8680
|
+
const DEFAULT_AUTH_FLOW_TTL_MS = 600 * 1e3;
|
|
8681
|
+
var RemoteAuthFlowStore = class {
|
|
8682
|
+
options;
|
|
8683
|
+
flows = /* @__PURE__ */ new Map();
|
|
8684
|
+
constructor(options = {}) {
|
|
8685
|
+
this.options = options;
|
|
8686
|
+
}
|
|
8687
|
+
create(flow, id = randomUUID()) {
|
|
8688
|
+
this.pruneExpired();
|
|
8689
|
+
const created = {
|
|
8690
|
+
id,
|
|
8691
|
+
createdAt: this.now(),
|
|
8692
|
+
...flow
|
|
8693
|
+
};
|
|
8694
|
+
this.flows.set(created.id, created);
|
|
8695
|
+
return { ...created };
|
|
8696
|
+
}
|
|
8697
|
+
get(id) {
|
|
8698
|
+
this.pruneExpired();
|
|
8699
|
+
const flow = this.flows.get(id);
|
|
8700
|
+
if (flow && this.isExpired(flow)) {
|
|
8701
|
+
this.flows.delete(id);
|
|
8702
|
+
return;
|
|
8703
|
+
}
|
|
8704
|
+
return flow;
|
|
8705
|
+
}
|
|
8706
|
+
delete(id) {
|
|
8707
|
+
this.flows.delete(id);
|
|
8708
|
+
}
|
|
8709
|
+
pruneExpired() {
|
|
8710
|
+
for (const [id, flow] of this.flows) if (this.isExpired(flow)) this.flows.delete(id);
|
|
8711
|
+
}
|
|
8712
|
+
isExpired(flow) {
|
|
8713
|
+
return this.now() - flow.createdAt > (this.options.ttlMs ?? DEFAULT_AUTH_FLOW_TTL_MS);
|
|
8714
|
+
}
|
|
8715
|
+
now() {
|
|
8716
|
+
return this.options.now?.() ?? Date.now();
|
|
8717
|
+
}
|
|
8718
|
+
};
|
|
8719
|
+
//#endregion
|
|
8312
8720
|
//#region src/serve/http.ts
|
|
8313
8721
|
function createHttpServeApp(options, engine, io = {}) {
|
|
8314
8722
|
const app = new Hono();
|
|
8315
8723
|
const sessions = /* @__PURE__ */ new Map();
|
|
8316
8724
|
const writeErr = io.writeErr ?? process.stderr.write.bind(process.stderr);
|
|
8725
|
+
const paths = servicePaths(options.path);
|
|
8726
|
+
const authFlowStore = io.authFlowStore ?? new RemoteAuthFlowStore();
|
|
8317
8727
|
app.use("*", logger((message, ...rest) => {
|
|
8318
8728
|
writeErr(`${[message, ...rest].join(" ")}\n`);
|
|
8319
8729
|
}));
|
|
8320
|
-
app.get(
|
|
8730
|
+
app.get(paths.base, (c) => c.json({
|
|
8321
8731
|
name: "caplets",
|
|
8322
8732
|
transport: "http",
|
|
8323
|
-
|
|
8324
|
-
|
|
8733
|
+
base: paths.base,
|
|
8734
|
+
mcp: paths.mcp,
|
|
8735
|
+
control: paths.control,
|
|
8736
|
+
health: paths.health,
|
|
8325
8737
|
auth: {
|
|
8326
8738
|
type: "basic",
|
|
8327
8739
|
enabled: options.auth.enabled
|
|
8328
8740
|
}
|
|
8329
8741
|
}));
|
|
8330
|
-
app.get(
|
|
8742
|
+
app.get(paths.health, (c) => c.json({
|
|
8331
8743
|
status: "ok",
|
|
8332
8744
|
transport: "http",
|
|
8333
|
-
|
|
8745
|
+
base: paths.base,
|
|
8746
|
+
mcpPath: paths.mcp,
|
|
8747
|
+
controlPath: paths.control,
|
|
8748
|
+
healthPath: paths.health
|
|
8334
8749
|
}));
|
|
8335
|
-
app.all(
|
|
8750
|
+
app.all(paths.mcp, basicAuth(options.auth), async (c) => {
|
|
8336
8751
|
const sessionId = c.req.header("mcp-session-id");
|
|
8337
8752
|
if (sessionId) {
|
|
8338
8753
|
const existing = sessions.get(sessionId);
|
|
@@ -8363,6 +8778,36 @@ function createHttpServeApp(options, engine, io = {}) {
|
|
|
8363
8778
|
sessions.set(nextSessionId, session);
|
|
8364
8779
|
return session.transport.handleRequest(c);
|
|
8365
8780
|
});
|
|
8781
|
+
app.post(paths.control, basicAuth(options.auth), async (c) => {
|
|
8782
|
+
let request;
|
|
8783
|
+
try {
|
|
8784
|
+
const parsed = await c.req.json();
|
|
8785
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new CapletsError("REQUEST_INVALID", "Control request JSON must be an object");
|
|
8786
|
+
request = parsed;
|
|
8787
|
+
} catch (error) {
|
|
8788
|
+
const safe = toSafeError(error instanceof CapletsError ? error : new CapletsError("REQUEST_INVALID", "Control request body must be valid JSON", error), "REQUEST_INVALID");
|
|
8789
|
+
return c.json({
|
|
8790
|
+
ok: false,
|
|
8791
|
+
error: {
|
|
8792
|
+
code: safe.code,
|
|
8793
|
+
message: safe.message
|
|
8794
|
+
}
|
|
8795
|
+
});
|
|
8796
|
+
}
|
|
8797
|
+
return c.json(await dispatchRemoteCliRequest(request, controlContext(io, writeErr, authFlowStore, c.req.url, paths.control, options.trustProxy, (name) => c.req.header(name))));
|
|
8798
|
+
});
|
|
8799
|
+
app.get(routePath(paths.control, "auth/callback/:flowId"), async (c) => {
|
|
8800
|
+
const flowId = c.req.param("flowId");
|
|
8801
|
+
const result = await dispatchRemoteCliRequest({
|
|
8802
|
+
command: "auth_login_complete",
|
|
8803
|
+
arguments: {
|
|
8804
|
+
flowId,
|
|
8805
|
+
callbackUrl: c.req.url
|
|
8806
|
+
}
|
|
8807
|
+
}, controlContext(io, writeErr, authFlowStore, c.req.url, paths.control, options.trustProxy, (name) => c.req.header(name)));
|
|
8808
|
+
if (!result.ok) writeErr(`Caplets authentication failed for flow ${flowId}: ${result.error.message}\n`);
|
|
8809
|
+
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);
|
|
8810
|
+
});
|
|
8366
8811
|
app.notFound((c) => c.json({ error: "not_found" }, 404));
|
|
8367
8812
|
app.closeCapletsSessions = async () => {
|
|
8368
8813
|
await Promise.allSettled([...sessions.values()].map(async (session) => {
|
|
@@ -8373,19 +8818,66 @@ function createHttpServeApp(options, engine, io = {}) {
|
|
|
8373
8818
|
if (options.warnUnauthenticatedNetwork) writeErr(`Warning: Caplets MCP HTTP server is listening on ${options.host} without authentication.\n`);
|
|
8374
8819
|
return app;
|
|
8375
8820
|
}
|
|
8821
|
+
function controlContext(io, writeErr, authFlowStore, requestUrl, controlPath, trustProxy, header) {
|
|
8822
|
+
return {
|
|
8823
|
+
...io.control,
|
|
8824
|
+
projectCapletsRoot: io.control?.projectCapletsRoot ?? resolveProjectCapletsRoot(),
|
|
8825
|
+
authFlowStore,
|
|
8826
|
+
controlCallbackBaseUrl: new URL(controlPath, publicRequestOrigin(requestUrl, trustProxy, header)).toString(),
|
|
8827
|
+
writeErr
|
|
8828
|
+
};
|
|
8829
|
+
}
|
|
8830
|
+
function publicRequestOrigin(requestUrl, trustProxy, header) {
|
|
8831
|
+
const url = new URL(requestUrl);
|
|
8832
|
+
if (!trustProxy) return `${url.protocol.slice(0, -1)}://${header("host") ?? url.host}`;
|
|
8833
|
+
const forwardedProto = firstForwardedValue(header("x-forwarded-proto"));
|
|
8834
|
+
const forwardedHost = firstForwardedValue(header("x-forwarded-host"));
|
|
8835
|
+
return `${forwardedProto === "http" || forwardedProto === "https" ? forwardedProto : url.protocol.slice(0, -1)}://${forwardedHost ?? header("host") ?? url.host}`;
|
|
8836
|
+
}
|
|
8837
|
+
function firstForwardedValue(value) {
|
|
8838
|
+
return value?.split(",", 1)[0]?.trim() || void 0;
|
|
8839
|
+
}
|
|
8376
8840
|
async function serveHttp(options, engineOptions = {}, writeErr = (value) => process.stderr.write(value)) {
|
|
8377
8841
|
const engine = new CapletsEngine(engineOptions);
|
|
8378
|
-
const app = createHttpServeApp(options, engine, {
|
|
8842
|
+
const app = createHttpServeApp(options, engine, {
|
|
8843
|
+
writeErr,
|
|
8844
|
+
control: {
|
|
8845
|
+
...engineOptions,
|
|
8846
|
+
projectCapletsRoot: projectCapletsRootForEngineOptions(engineOptions)
|
|
8847
|
+
}
|
|
8848
|
+
});
|
|
8849
|
+
const paths = servicePaths(options.path);
|
|
8850
|
+
const origin = `http://${formatHost(options.host)}:${options.port}`;
|
|
8851
|
+
const baseUrl = `${origin}${paths.base === "/" ? "" : paths.base}`;
|
|
8379
8852
|
installHttpSignalHandlers(serve({
|
|
8380
8853
|
fetch: app.fetch,
|
|
8381
8854
|
hostname: options.host,
|
|
8382
8855
|
port: options.port
|
|
8383
8856
|
}, () => {
|
|
8384
|
-
writeErr(`Caplets
|
|
8385
|
-
writeErr(`
|
|
8857
|
+
writeErr(`Caplets HTTP service listening on ${baseUrl}\n`);
|
|
8858
|
+
writeErr(`MCP endpoint: ${origin}${paths.mcp}\n`);
|
|
8859
|
+
writeErr(`Control endpoint: ${origin}${paths.control}\n`);
|
|
8860
|
+
writeErr(`Health check: ${origin}${paths.health}\n`);
|
|
8386
8861
|
writeErr(`Basic Auth: ${options.auth.enabled ? `enabled (user: ${options.auth.user})` : "disabled"}\n`);
|
|
8387
8862
|
}), app, engine, writeErr);
|
|
8388
8863
|
}
|
|
8864
|
+
function projectCapletsRootForEngineOptions(engineOptions) {
|
|
8865
|
+
return engineOptions.projectConfigPath ? resolveProjectCapletsRootForConfigPath(engineOptions.projectConfigPath) : resolveProjectCapletsRoot();
|
|
8866
|
+
}
|
|
8867
|
+
function resolveProjectCapletsRootForConfigPath(projectConfigPath) {
|
|
8868
|
+
return dirname(projectConfigPath);
|
|
8869
|
+
}
|
|
8870
|
+
function routePath(base, path) {
|
|
8871
|
+
return base === "/" ? `/${path}` : `${base}/${path}`;
|
|
8872
|
+
}
|
|
8873
|
+
function servicePaths(base) {
|
|
8874
|
+
return {
|
|
8875
|
+
base,
|
|
8876
|
+
mcp: routePath(base, "mcp"),
|
|
8877
|
+
control: routePath(base, "control"),
|
|
8878
|
+
health: routePath(base, "healthz")
|
|
8879
|
+
};
|
|
8880
|
+
}
|
|
8389
8881
|
async function createHttpSession(engine, sessionId, options, onClose) {
|
|
8390
8882
|
const transport = new StreamableHTTPTransport({
|
|
8391
8883
|
sessionIdGenerator: () => sessionId,
|
|
@@ -8465,7 +8957,8 @@ const HTTP_ONLY_OPTIONS = [
|
|
|
8465
8957
|
"path",
|
|
8466
8958
|
"user",
|
|
8467
8959
|
"password",
|
|
8468
|
-
"allowUnauthenticatedHttp"
|
|
8960
|
+
"allowUnauthenticatedHttp",
|
|
8961
|
+
"trustProxy"
|
|
8469
8962
|
];
|
|
8470
8963
|
function resolveServeOptions(raw, env = process.env) {
|
|
8471
8964
|
const transport = parseTransport(raw.transport ?? "stdio");
|
|
@@ -8474,9 +8967,10 @@ function resolveServeOptions(raw, env = process.env) {
|
|
|
8474
8967
|
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
8968
|
return { transport };
|
|
8476
8969
|
}
|
|
8477
|
-
const
|
|
8478
|
-
const
|
|
8479
|
-
const
|
|
8970
|
+
const serverUrl = env.CAPLETS_SERVER_URL ? parseServeServerUrl(nonEmpty(env.CAPLETS_SERVER_URL, "CAPLETS_SERVER_URL")) : void 0;
|
|
8971
|
+
const host = nonEmpty(raw.host, "--host") ?? serverUrlHost(serverUrl) ?? "127.0.0.1";
|
|
8972
|
+
const port = parsePort(raw.port ?? (serverUrl?.port ? Number(serverUrl.port) : 5387));
|
|
8973
|
+
const path = normalizeHttpPath(raw.path ?? serverUrl?.pathname ?? "/");
|
|
8480
8974
|
const userWasExplicit = raw.user !== void 0 || hasEnv(env.CAPLETS_SERVER_USER);
|
|
8481
8975
|
const user = nonEmpty(raw.user, "--user") ?? nonEmpty(env.CAPLETS_SERVER_USER, "CAPLETS_SERVER_USER") ?? "caplets";
|
|
8482
8976
|
const password = nonEmpty(raw.password, "--password") ?? nonEmpty(env.CAPLETS_SERVER_PASSWORD, "CAPLETS_SERVER_PASSWORD");
|
|
@@ -8498,13 +8992,22 @@ function resolveServeOptions(raw, env = process.env) {
|
|
|
8498
8992
|
path,
|
|
8499
8993
|
auth,
|
|
8500
8994
|
warnUnauthenticatedNetwork: !loopback && !auth.enabled,
|
|
8501
|
-
loopback
|
|
8995
|
+
loopback,
|
|
8996
|
+
trustProxy: raw.trustProxy === true
|
|
8502
8997
|
};
|
|
8503
8998
|
}
|
|
8504
8999
|
function isLoopbackHost(host) {
|
|
8505
9000
|
const normalized = host.toLocaleLowerCase();
|
|
8506
9001
|
return normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1";
|
|
8507
9002
|
}
|
|
9003
|
+
function parseServeServerUrl(value) {
|
|
9004
|
+
try {
|
|
9005
|
+
return parseServerBaseUrl(value);
|
|
9006
|
+
} catch (error) {
|
|
9007
|
+
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.");
|
|
9008
|
+
throw error;
|
|
9009
|
+
}
|
|
9010
|
+
}
|
|
8508
9011
|
function parseTransport(value) {
|
|
8509
9012
|
if (value === "stdio" || value === "http") return value;
|
|
8510
9013
|
throw new CapletsError("REQUEST_INVALID", `Expected --transport to be stdio or http, got ${value}`);
|
|
@@ -8519,6 +9022,9 @@ function normalizeHttpPath(value) {
|
|
|
8519
9022
|
if (value.includes("?") || value.includes("#")) throw new CapletsError("REQUEST_INVALID", "HTTP --path must not include a query string or fragment");
|
|
8520
9023
|
return value === "/" ? value : value.replace(/\/+$/u, "");
|
|
8521
9024
|
}
|
|
9025
|
+
function serverUrlHost(url) {
|
|
9026
|
+
return url?.hostname.replace(/^\[(.*)\]$/u, "$1");
|
|
9027
|
+
}
|
|
8522
9028
|
function nonEmpty(value, label) {
|
|
8523
9029
|
if (value === void 0) return;
|
|
8524
9030
|
const trimmed = value.trim();
|
|
@@ -8656,6 +9162,8 @@ async function runCli(args, io = {}) {
|
|
|
8656
9162
|
function createProgram(io = {}) {
|
|
8657
9163
|
const writeOut = io.writeOut ?? ((value) => process.stdout.write(value));
|
|
8658
9164
|
const writeErr = io.writeErr ?? ((value) => process.stderr.write(value));
|
|
9165
|
+
const env = io.env ?? process.env;
|
|
9166
|
+
const currentConfigPath = () => envConfigPath(env);
|
|
8659
9167
|
const setExitCode = io.setExitCode ?? ((code) => {
|
|
8660
9168
|
process.exitCode = code;
|
|
8661
9169
|
});
|
|
@@ -8665,42 +9173,78 @@ function createProgram(io = {}) {
|
|
|
8665
9173
|
writeErr,
|
|
8666
9174
|
outputError: (value, write) => write(value)
|
|
8667
9175
|
});
|
|
8668
|
-
program.command("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
|
|
9176
|
+
program.command("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
9177
|
const resolved = resolveServeOptions(options);
|
|
8670
|
-
const configPath =
|
|
9178
|
+
const configPath = currentConfigPath();
|
|
8671
9179
|
await (io.serve ?? ((serveOptions) => serveResolvedCaplets(serveOptions, {
|
|
8672
9180
|
...configPath ? { configPath } : {},
|
|
8673
9181
|
...io.authDir ? { authDir: io.authDir } : {}
|
|
8674
9182
|
}, writeErr)))(resolved);
|
|
8675
9183
|
});
|
|
8676
|
-
program.command("init").description("Create a starter Caplets config file.").option("--force", "overwrite an existing config file").action((options) => {
|
|
8677
|
-
const
|
|
9184
|
+
program.command("init").description("Create a starter Caplets config file.").option("--force", "overwrite an existing config file").action(async (options) => {
|
|
9185
|
+
const remote = remoteClientForCli(io);
|
|
9186
|
+
if (remote) {
|
|
9187
|
+
writeOut(`Created remote Caplets config at ${(await remote.request("init", { force: Boolean(options.force) })).path}\n`);
|
|
9188
|
+
return;
|
|
9189
|
+
}
|
|
9190
|
+
const configPath = currentConfigPath();
|
|
8678
9191
|
writeOut(`Created Caplets config at ${initConfig({
|
|
8679
9192
|
...configPath ? { path: configPath } : {},
|
|
8680
9193
|
force: Boolean(options.force)
|
|
8681
9194
|
})}\n`);
|
|
8682
9195
|
});
|
|
8683
|
-
program.command("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((options) => {
|
|
8684
|
-
const
|
|
9196
|
+
program.command("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) => {
|
|
9197
|
+
const includeDisabled = Boolean(options.all);
|
|
9198
|
+
const remote = remoteClientForCli(io);
|
|
9199
|
+
if (remote) {
|
|
9200
|
+
const rows = await remote.request("list", { includeDisabled });
|
|
9201
|
+
if (options.json || options.format === "json") {
|
|
9202
|
+
writeOut(`${JSON.stringify(rows, null, 2)}\n`);
|
|
9203
|
+
return;
|
|
9204
|
+
}
|
|
9205
|
+
writeOut(formatCapletList(rows, options.format ?? "plain"));
|
|
9206
|
+
return;
|
|
9207
|
+
}
|
|
9208
|
+
const rows = listCaplets(loadConfigWithSources(currentConfigPath()), { includeDisabled });
|
|
8685
9209
|
if (options.json || options.format === "json") {
|
|
8686
9210
|
writeOut(`${JSON.stringify(rows, null, 2)}\n`);
|
|
8687
9211
|
return;
|
|
8688
9212
|
}
|
|
8689
9213
|
writeOut(formatCapletList(rows, options.format ?? "plain"));
|
|
8690
9214
|
});
|
|
8691
|
-
program.command("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((repo, capletIds, options) => {
|
|
9215
|
+
program.command("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) => {
|
|
9216
|
+
const remote = remoteClientForCli(io);
|
|
9217
|
+
if (remote) {
|
|
9218
|
+
if (options.global) writeErr("Warning: --global is not supported in remote mode; the server controls the installation destination.\n");
|
|
9219
|
+
const result = await remote.request("install", {
|
|
9220
|
+
repo,
|
|
9221
|
+
capletIds,
|
|
9222
|
+
force: Boolean(options.force)
|
|
9223
|
+
});
|
|
9224
|
+
for (const caplet of result.installed) writeOut(`Installed ${caplet.id} to remote ${caplet.destination}\n`);
|
|
9225
|
+
return;
|
|
9226
|
+
}
|
|
8692
9227
|
const result = installCaplets(repo, {
|
|
8693
9228
|
capletIds,
|
|
8694
9229
|
force: Boolean(options.force),
|
|
8695
|
-
destinationRoot: options.global ? resolveCapletsRoot(resolveConfigPath(
|
|
9230
|
+
destinationRoot: options.global ? resolveCapletsRoot(resolveConfigPath(currentConfigPath())) : resolveProjectCapletsRoot()
|
|
8696
9231
|
});
|
|
8697
9232
|
for (const caplet of result.installed) writeOut(`Installed ${caplet.id} to ${caplet.destination}\n`);
|
|
8698
9233
|
});
|
|
8699
9234
|
const add = program.command("add").description("Add generated Caplet files.");
|
|
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) => {
|
|
9235
|
+
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) => {
|
|
9236
|
+
const remote = remoteClientForCli(io);
|
|
9237
|
+
if (remote) {
|
|
9238
|
+
writeAddResult(writeOut, "CLI", await remote.request("add", {
|
|
9239
|
+
kind: "cli",
|
|
9240
|
+
id,
|
|
9241
|
+
options: remoteAddOptions(options)
|
|
9242
|
+
}));
|
|
9243
|
+
return;
|
|
9244
|
+
}
|
|
8701
9245
|
const result = addCliCaplet(id, {
|
|
8702
9246
|
...options,
|
|
8703
|
-
destinationRoot: options.global ? resolveCapletsRoot(resolveConfigPath(
|
|
9247
|
+
destinationRoot: options.global ? resolveCapletsRoot(resolveConfigPath(currentConfigPath())) : resolveProjectCapletsRoot()
|
|
8704
9248
|
});
|
|
8705
9249
|
if (result.path) {
|
|
8706
9250
|
writeOut(`Wrote CLI Caplet to ${result.path}\n`);
|
|
@@ -8708,28 +9252,64 @@ function createProgram(io = {}) {
|
|
|
8708
9252
|
}
|
|
8709
9253
|
writeOut(result.text);
|
|
8710
9254
|
});
|
|
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) => {
|
|
9255
|
+
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) => {
|
|
9256
|
+
const remote = remoteClientForCli(io);
|
|
9257
|
+
if (remote) {
|
|
9258
|
+
writeAddResult(writeOut, "MCP", await remote.request("add", {
|
|
9259
|
+
kind: "mcp",
|
|
9260
|
+
id,
|
|
9261
|
+
options: remoteAddOptions(options)
|
|
9262
|
+
}));
|
|
9263
|
+
return;
|
|
9264
|
+
}
|
|
8712
9265
|
writeAddResult(writeOut, "MCP", addMcpCaplet(id, {
|
|
8713
9266
|
...options,
|
|
8714
|
-
destinationRoot: addDestinationRoot(options)
|
|
9267
|
+
destinationRoot: addDestinationRoot(options, currentConfigPath())
|
|
8715
9268
|
}));
|
|
8716
9269
|
});
|
|
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) => {
|
|
9270
|
+
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) => {
|
|
9271
|
+
const remote = remoteClientForCli(io);
|
|
9272
|
+
if (remote) {
|
|
9273
|
+
writeAddResult(writeOut, "OpenAPI", await remote.request("add", {
|
|
9274
|
+
kind: "openapi",
|
|
9275
|
+
id,
|
|
9276
|
+
options: remoteAddOptions(options)
|
|
9277
|
+
}));
|
|
9278
|
+
return;
|
|
9279
|
+
}
|
|
8718
9280
|
writeAddResult(writeOut, "OpenAPI", addOpenApiCaplet(id, {
|
|
8719
9281
|
...options,
|
|
8720
|
-
destinationRoot: addDestinationRoot(options)
|
|
9282
|
+
destinationRoot: addDestinationRoot(options, currentConfigPath())
|
|
8721
9283
|
}));
|
|
8722
9284
|
});
|
|
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) => {
|
|
9285
|
+
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) => {
|
|
9286
|
+
const remote = remoteClientForCli(io);
|
|
9287
|
+
if (remote) {
|
|
9288
|
+
writeAddResult(writeOut, "GraphQL", await remote.request("add", {
|
|
9289
|
+
kind: "graphql",
|
|
9290
|
+
id,
|
|
9291
|
+
options: remoteAddOptions(options)
|
|
9292
|
+
}));
|
|
9293
|
+
return;
|
|
9294
|
+
}
|
|
8724
9295
|
writeAddResult(writeOut, "GraphQL", addGraphqlCaplet(id, {
|
|
8725
9296
|
...options,
|
|
8726
|
-
destinationRoot: addDestinationRoot(options)
|
|
9297
|
+
destinationRoot: addDestinationRoot(options, currentConfigPath())
|
|
8727
9298
|
}));
|
|
8728
9299
|
});
|
|
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) => {
|
|
9300
|
+
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) => {
|
|
9301
|
+
const remote = remoteClientForCli(io);
|
|
9302
|
+
if (remote) {
|
|
9303
|
+
writeAddResult(writeOut, "HTTP", await remote.request("add", {
|
|
9304
|
+
kind: "http",
|
|
9305
|
+
id,
|
|
9306
|
+
options: remoteAddOptions(options)
|
|
9307
|
+
}));
|
|
9308
|
+
return;
|
|
9309
|
+
}
|
|
8730
9310
|
writeAddResult(writeOut, "HTTP", addHttpCaplet(id, {
|
|
8731
9311
|
...options,
|
|
8732
|
-
destinationRoot: addDestinationRoot(options)
|
|
9312
|
+
destinationRoot: addDestinationRoot(options, currentConfigPath())
|
|
8733
9313
|
}));
|
|
8734
9314
|
});
|
|
8735
9315
|
program.command("get-caplet").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) => {
|
|
@@ -8738,6 +9318,8 @@ function createProgram(io = {}) {
|
|
|
8738
9318
|
writeErr,
|
|
8739
9319
|
setExitCode,
|
|
8740
9320
|
authDir: io.authDir,
|
|
9321
|
+
env,
|
|
9322
|
+
remote: remoteClientForCli(io),
|
|
8741
9323
|
format: options.format
|
|
8742
9324
|
});
|
|
8743
9325
|
});
|
|
@@ -8747,6 +9329,8 @@ function createProgram(io = {}) {
|
|
|
8747
9329
|
writeErr,
|
|
8748
9330
|
setExitCode,
|
|
8749
9331
|
authDir: io.authDir,
|
|
9332
|
+
env,
|
|
9333
|
+
remote: remoteClientForCli(io),
|
|
8750
9334
|
format: options.format
|
|
8751
9335
|
});
|
|
8752
9336
|
});
|
|
@@ -8756,6 +9340,8 @@ function createProgram(io = {}) {
|
|
|
8756
9340
|
writeErr,
|
|
8757
9341
|
setExitCode,
|
|
8758
9342
|
authDir: io.authDir,
|
|
9343
|
+
env,
|
|
9344
|
+
remote: remoteClientForCli(io),
|
|
8759
9345
|
format: options.format
|
|
8760
9346
|
});
|
|
8761
9347
|
});
|
|
@@ -8772,6 +9358,8 @@ function createProgram(io = {}) {
|
|
|
8772
9358
|
writeErr,
|
|
8773
9359
|
setExitCode,
|
|
8774
9360
|
authDir: io.authDir,
|
|
9361
|
+
env,
|
|
9362
|
+
remote: remoteClientForCli(io),
|
|
8775
9363
|
format: options.format
|
|
8776
9364
|
});
|
|
8777
9365
|
});
|
|
@@ -8785,6 +9373,8 @@ function createProgram(io = {}) {
|
|
|
8785
9373
|
writeErr,
|
|
8786
9374
|
setExitCode,
|
|
8787
9375
|
authDir: io.authDir,
|
|
9376
|
+
env,
|
|
9377
|
+
remote: remoteClientForCli(io),
|
|
8788
9378
|
format: options.format
|
|
8789
9379
|
});
|
|
8790
9380
|
});
|
|
@@ -8800,15 +9390,17 @@ function createProgram(io = {}) {
|
|
|
8800
9390
|
writeErr,
|
|
8801
9391
|
setExitCode,
|
|
8802
9392
|
authDir: io.authDir,
|
|
9393
|
+
env,
|
|
9394
|
+
remote: remoteClientForCli(io),
|
|
8803
9395
|
format: options.format
|
|
8804
9396
|
});
|
|
8805
9397
|
});
|
|
8806
9398
|
const config = program.command("config").description("Inspect Caplets config locations.");
|
|
8807
9399
|
config.command("path").description("Print the effective user config path.").action(() => {
|
|
8808
|
-
writeOut(`${resolveConfigPath(
|
|
9400
|
+
writeOut(`${resolveConfigPath(currentConfigPath())}\n`);
|
|
8809
9401
|
});
|
|
8810
9402
|
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(
|
|
9403
|
+
const paths = resolveCliConfigPaths(currentConfigPath(), io.authDir);
|
|
8812
9404
|
if (options.json || options.format === "json") {
|
|
8813
9405
|
writeOut(`${JSON.stringify(paths, null, 2)}\n`);
|
|
8814
9406
|
return;
|
|
@@ -8817,7 +9409,19 @@ function createProgram(io = {}) {
|
|
|
8817
9409
|
});
|
|
8818
9410
|
const auth = program.command("auth").description("Manage OAuth credentials for remote servers.");
|
|
8819
9411
|
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
|
|
9412
|
+
const remote = remoteClientForCli(io);
|
|
9413
|
+
if (remote) {
|
|
9414
|
+
const started = await remote.request("auth_login_start", { server: serverId });
|
|
9415
|
+
if (started.authorizationUrl) {
|
|
9416
|
+
writeOut(`Open this URL to authorize ${serverId}:\n${started.authorizationUrl}\n`);
|
|
9417
|
+
if (options.open !== false) await openBrowser(started.authorizationUrl);
|
|
9418
|
+
writeOut("Complete authentication in your browser. The server callback will store credentials.\n");
|
|
9419
|
+
return;
|
|
9420
|
+
}
|
|
9421
|
+
if (started.authenticated) writeOut(`Authenticated \`${serverId}\`.\n`);
|
|
9422
|
+
return;
|
|
9423
|
+
}
|
|
9424
|
+
const configPath = currentConfigPath();
|
|
8821
9425
|
await loginAuth(serverId, {
|
|
8822
9426
|
noOpen: options.open === false,
|
|
8823
9427
|
writeOut,
|
|
@@ -8826,27 +9430,78 @@ function createProgram(io = {}) {
|
|
|
8826
9430
|
...io.authDir ? { authDir: io.authDir } : {}
|
|
8827
9431
|
});
|
|
8828
9432
|
});
|
|
8829
|
-
auth.command("logout").description("Delete stored OAuth credentials for a server.").argument("<server>", "configured server ID").action((serverId) => {
|
|
8830
|
-
const
|
|
9433
|
+
auth.command("logout").description("Delete stored OAuth credentials for a server.").argument("<server>", "configured server ID").action(async (serverId) => {
|
|
9434
|
+
const remote = remoteClientForCli(io);
|
|
9435
|
+
if (remote) {
|
|
9436
|
+
writeOut((await remote.request("auth_logout", { server: serverId })).deleted ? `Deleted remote OAuth credentials for \`${serverId}\`.\n` : `No remote OAuth credentials found for \`${serverId}\`.\n`);
|
|
9437
|
+
return;
|
|
9438
|
+
}
|
|
9439
|
+
const configPath = currentConfigPath();
|
|
8831
9440
|
logoutAuth(serverId, {
|
|
8832
9441
|
writeOut,
|
|
8833
9442
|
...configPath ? { configPath } : {},
|
|
8834
9443
|
...io.authDir ? { authDir: io.authDir } : {}
|
|
8835
9444
|
});
|
|
8836
9445
|
});
|
|
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 =
|
|
9446
|
+
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) => {
|
|
9447
|
+
const configPath = currentConfigPath();
|
|
9448
|
+
const format = options.json || options.format === "json" ? "json" : options.format ?? "plain";
|
|
9449
|
+
const remote = remoteClientForCli(io);
|
|
9450
|
+
if (remote) {
|
|
9451
|
+
const rows = await remote.request("auth_list", {});
|
|
9452
|
+
if (format === "json") {
|
|
9453
|
+
writeOut(`${JSON.stringify(rows, null, 2)}\n`);
|
|
9454
|
+
return;
|
|
9455
|
+
}
|
|
9456
|
+
writeOut(formatAuthRows(rows, format));
|
|
9457
|
+
return;
|
|
9458
|
+
}
|
|
8839
9459
|
listAuth({
|
|
8840
9460
|
writeOut,
|
|
8841
|
-
format
|
|
9461
|
+
format,
|
|
8842
9462
|
...configPath ? { configPath } : {},
|
|
8843
9463
|
...io.authDir ? { authDir: io.authDir } : {}
|
|
8844
9464
|
});
|
|
8845
9465
|
});
|
|
8846
9466
|
return program;
|
|
8847
9467
|
}
|
|
8848
|
-
function envConfigPath() {
|
|
8849
|
-
return
|
|
9468
|
+
function envConfigPath(env) {
|
|
9469
|
+
return env.CAPLETS_CONFIG?.trim() || void 0;
|
|
9470
|
+
}
|
|
9471
|
+
function remoteClientForCli(io) {
|
|
9472
|
+
const env = io.env ?? process.env;
|
|
9473
|
+
if (resolveCapletsMode({}, env).mode !== "remote") return;
|
|
9474
|
+
return new RemoteControlClient(resolveCapletsServer(io.fetch ? { fetch: io.fetch } : {}, env));
|
|
9475
|
+
}
|
|
9476
|
+
async function openBrowser(url) {
|
|
9477
|
+
const { spawn } = await import("node:child_process");
|
|
9478
|
+
spawn(process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open", process.platform === "win32" ? [
|
|
9479
|
+
"/c",
|
|
9480
|
+
"start",
|
|
9481
|
+
"",
|
|
9482
|
+
url
|
|
9483
|
+
] : [url], {
|
|
9484
|
+
stdio: "ignore",
|
|
9485
|
+
detached: true
|
|
9486
|
+
}).unref();
|
|
9487
|
+
}
|
|
9488
|
+
function remoteCommandForOperation(operation) {
|
|
9489
|
+
switch (operation) {
|
|
9490
|
+
case "get_caplet":
|
|
9491
|
+
case "check_backend":
|
|
9492
|
+
case "list_tools":
|
|
9493
|
+
case "search_tools":
|
|
9494
|
+
case "get_tool":
|
|
9495
|
+
case "call_tool": return operation;
|
|
9496
|
+
default: return;
|
|
9497
|
+
}
|
|
9498
|
+
}
|
|
9499
|
+
function remoteAddOptions(options) {
|
|
9500
|
+
const { output, print, global, destinationRoot, ...remoteOptions } = options;
|
|
9501
|
+
if (global) throw new CapletsError("REQUEST_INVALID", "--global is not supported in remote mode; the server controls the add destination.");
|
|
9502
|
+
if (print) throw new CapletsError("REQUEST_INVALID", "--print is not supported in remote mode; the server controls add output.");
|
|
9503
|
+
if (output !== void 0) throw new CapletsError("REQUEST_INVALID", "--output is not supported in remote mode; the server controls the add destination.");
|
|
9504
|
+
return remoteOptions;
|
|
8850
9505
|
}
|
|
8851
9506
|
function collect(value, previous) {
|
|
8852
9507
|
previous.push(value);
|
|
@@ -8889,7 +9544,21 @@ function isPlainObject(value) {
|
|
|
8889
9544
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
8890
9545
|
}
|
|
8891
9546
|
async function executeOperation(caplet, request, io) {
|
|
8892
|
-
const
|
|
9547
|
+
const command = remoteCommandForOperation(request.operation);
|
|
9548
|
+
if (io.remote && command) {
|
|
9549
|
+
const result = await io.remote.request(command, {
|
|
9550
|
+
caplet,
|
|
9551
|
+
request
|
|
9552
|
+
});
|
|
9553
|
+
const output = cliOutputForOperation(result, {
|
|
9554
|
+
...request,
|
|
9555
|
+
caplet
|
|
9556
|
+
}, io.format ?? "markdown");
|
|
9557
|
+
io.writeOut(typeof output === "string" ? `${output}\n` : `${JSON.stringify(output, null, 2)}\n`);
|
|
9558
|
+
if (isPlainObject(result) && result.isError === true) io.setExitCode(1);
|
|
9559
|
+
return;
|
|
9560
|
+
}
|
|
9561
|
+
const configPath = envConfigPath(io.env ?? process.env);
|
|
8893
9562
|
const engine = new CapletsEngine({
|
|
8894
9563
|
...configPath ? { configPath } : {},
|
|
8895
9564
|
...io.authDir ? { authDir: io.authDir } : {},
|
|
@@ -9135,12 +9804,12 @@ function schemaSummary(schema) {
|
|
|
9135
9804
|
required.length > 0 ? `required ${required.join(", ")}` : "no required fields"
|
|
9136
9805
|
].filter((part) => Boolean(part)).join("; ");
|
|
9137
9806
|
}
|
|
9138
|
-
function addDestinationRoot(options) {
|
|
9139
|
-
return options.global ? resolveCapletsRoot(resolveConfigPath(
|
|
9807
|
+
function addDestinationRoot(options, configPath) {
|
|
9808
|
+
return options.global ? resolveCapletsRoot(resolveConfigPath(configPath)) : resolveProjectCapletsRoot();
|
|
9140
9809
|
}
|
|
9141
9810
|
function writeAddResult(writeOut, label, result) {
|
|
9142
9811
|
if (result.path) {
|
|
9143
|
-
writeOut(`Wrote ${label} Caplet to ${result.path}\n`);
|
|
9812
|
+
writeOut(`Wrote ${result.remote ? "remote " : ""}${label} Caplet to ${result.path}\n`);
|
|
9144
9813
|
return;
|
|
9145
9814
|
}
|
|
9146
9815
|
writeOut(result.text);
|