caplets 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -16
- package/dist/index.js +980 -101
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import { homedir, tmpdir } from "node:os";
|
|
|
9
9
|
import { PassThrough } from "node:stream";
|
|
10
10
|
import { createServer } from "node:http";
|
|
11
11
|
import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
|
|
12
|
+
import { Buffer as Buffer$1 } from "node:buffer";
|
|
12
13
|
import { createInterface } from "node:readline/promises";
|
|
13
14
|
import { createServer as createServer$1 } from "http";
|
|
14
15
|
import { Http2ServerRequest, constants as constants$1 } from "http2";
|
|
@@ -72,7 +73,7 @@ function generatedToolInputJsonSchema() {
|
|
|
72
73
|
};
|
|
73
74
|
}
|
|
74
75
|
//#endregion
|
|
75
|
-
//#region ../core/dist/
|
|
76
|
+
//#region ../core/dist/options-CJEOqS87.js
|
|
76
77
|
var __create = Object.create;
|
|
77
78
|
var __defProp = Object.defineProperty;
|
|
78
79
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -95,6 +96,25 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
95
96
|
enumerable: true
|
|
96
97
|
}) : target, mod));
|
|
97
98
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
99
|
+
const CAPLETS_ERROR_CODES = [
|
|
100
|
+
"CONFIG_NOT_FOUND",
|
|
101
|
+
"CONFIG_EXISTS",
|
|
102
|
+
"CONFIG_INVALID",
|
|
103
|
+
"REQUEST_INVALID",
|
|
104
|
+
"SERVER_NOT_FOUND",
|
|
105
|
+
"SERVER_UNAVAILABLE",
|
|
106
|
+
"SERVER_START_TIMEOUT",
|
|
107
|
+
"UNKNOWN_OPERATION",
|
|
108
|
+
"TOOL_NOT_FOUND",
|
|
109
|
+
"TOOL_CALL_TIMEOUT",
|
|
110
|
+
"AUTH_REQUIRED",
|
|
111
|
+
"AUTH_FAILED",
|
|
112
|
+
"AUTH_REFRESH_FAILED",
|
|
113
|
+
"DOWNSTREAM_PROTOCOL_ERROR",
|
|
114
|
+
"DOWNSTREAM_TOOL_ERROR",
|
|
115
|
+
"UNSUPPORTED_TRANSPORT",
|
|
116
|
+
"INTERNAL_ERROR"
|
|
117
|
+
];
|
|
98
118
|
var CapletsError = class extends Error {
|
|
99
119
|
code;
|
|
100
120
|
details;
|
|
@@ -13442,7 +13462,7 @@ function loadConfigWithSources(path = resolveConfigPath(), projectPath = resolve
|
|
|
13442
13462
|
const userConfig = hasUserConfig ? readPublicConfigInput(path) : void 0;
|
|
13443
13463
|
const userCaplets = loadCapletFilesWithPaths(resolveCapletsRoot(path));
|
|
13444
13464
|
const projectConfig = hasProjectConfig ? rejectProjectConfigExecutableBackendMaps(readPublicConfigInput(projectPath), projectPath) : void 0;
|
|
13445
|
-
const projectCapletsRoot = resolveProjectCapletsRootForConfigPath(projectPath);
|
|
13465
|
+
const projectCapletsRoot = resolveProjectCapletsRootForConfigPath$1(projectPath);
|
|
13446
13466
|
const projectCaplets = projectCapletsRoot ? loadCapletFilesWithPaths(projectCapletsRoot) : void 0;
|
|
13447
13467
|
if (!hasUserConfig && !hasProjectConfig && !userCaplets && !projectCaplets) throw new CapletsError("CONFIG_NOT_FOUND", `Caplets config not found at ${path} or ${projectPath}`);
|
|
13448
13468
|
try {
|
|
@@ -13496,7 +13516,7 @@ function loadIsolatedConfig(options) {
|
|
|
13496
13516
|
if (Object.keys(config.mcpServers).length === 0 && Object.keys(config.openapiEndpoints).length === 0 && Object.keys(config.graphqlEndpoints).length === 0 && Object.keys(config.httpApis).length === 0 && Object.keys(config.cliTools).length === 0 && Object.keys(config.capletSets).length === 0) throw new CapletsError("CONFIG_INVALID", "Nested Caplet set must define at least one Caplet");
|
|
13497
13517
|
return config;
|
|
13498
13518
|
}
|
|
13499
|
-
function resolveProjectCapletsRootForConfigPath(projectPath) {
|
|
13519
|
+
function resolveProjectCapletsRootForConfigPath$1(projectPath) {
|
|
13500
13520
|
const root = dirname(projectPath);
|
|
13501
13521
|
return basename(root) === ".caplets" && basename(projectPath) === "config.json" ? root : void 0;
|
|
13502
13522
|
}
|
|
@@ -30743,8 +30763,45 @@ var FileOAuthProvider = class {
|
|
|
30743
30763
|
headers.set("content-type", "application/x-www-form-urlencoded");
|
|
30744
30764
|
};
|
|
30745
30765
|
};
|
|
30746
|
-
async function
|
|
30766
|
+
async function startOAuthFlow(server, options) {
|
|
30747
30767
|
if (server.transport === "stdio" || !server.url || server.auth?.type !== "oauth2" && server.auth?.type !== "oidc") throw new CapletsError("REQUEST_INVALID", `${server.server} is not a configured OAuth remote server`);
|
|
30768
|
+
let redirectUrl;
|
|
30769
|
+
const provider = new FileOAuthProvider(server, options.redirectUri, (url) => {
|
|
30770
|
+
redirectUrl = url;
|
|
30771
|
+
options.print?.(`Open this URL to authorize ${server.server}:\n${url.toString()}`);
|
|
30772
|
+
}, options.authDir);
|
|
30773
|
+
const scope = scopesFor(server.auth);
|
|
30774
|
+
try {
|
|
30775
|
+
if (await auth(provider, {
|
|
30776
|
+
serverUrl: server.url,
|
|
30777
|
+
...scope ? { scope } : {}
|
|
30778
|
+
}) === "AUTHORIZED") return {
|
|
30779
|
+
authorizationUrl: "",
|
|
30780
|
+
complete: async () => {}
|
|
30781
|
+
};
|
|
30782
|
+
} catch (error) {
|
|
30783
|
+
throw normalizeMcpOAuthError(server, error);
|
|
30784
|
+
}
|
|
30785
|
+
if (!redirectUrl) throw new CapletsError("AUTH_FAILED", "OAuth authorization URL was not provided");
|
|
30786
|
+
return {
|
|
30787
|
+
authorizationUrl: redirectUrl.toString(),
|
|
30788
|
+
complete: async (callbackUrl) => {
|
|
30789
|
+
assertNoOAuthCallbackError(server, callbackUrl);
|
|
30790
|
+
const completion = extractCompletion(callbackUrl);
|
|
30791
|
+
if (completion.state !== provider.state()) throw new CapletsError("AUTH_FAILED", "OAuth callback state did not match");
|
|
30792
|
+
try {
|
|
30793
|
+
await auth(provider, {
|
|
30794
|
+
serverUrl: server.url,
|
|
30795
|
+
authorizationCode: completion.code,
|
|
30796
|
+
...scope ? { scope } : {}
|
|
30797
|
+
});
|
|
30798
|
+
} catch (error) {
|
|
30799
|
+
throw normalizeMcpOAuthError(server, error);
|
|
30800
|
+
}
|
|
30801
|
+
}
|
|
30802
|
+
};
|
|
30803
|
+
}
|
|
30804
|
+
async function runOAuthFlow(server, options = {}) {
|
|
30748
30805
|
let callbackCode;
|
|
30749
30806
|
let callbackState;
|
|
30750
30807
|
const callback = await createLoopbackCallback((url) => {
|
|
@@ -30752,37 +30809,43 @@ async function runOAuthFlow(server, options = {}) {
|
|
|
30752
30809
|
callbackCode = url.searchParams.get("code") ?? void 0;
|
|
30753
30810
|
callbackState = url.searchParams.get("state") ?? void 0;
|
|
30754
30811
|
});
|
|
30755
|
-
let redirectUrl;
|
|
30756
|
-
const provider = new FileOAuthProvider(server, callback.redirectUri, (url) => {
|
|
30757
|
-
redirectUrl = url;
|
|
30758
|
-
options.print?.(`Open this URL to authorize ${server.server}:\n${url.toString()}`);
|
|
30759
|
-
}, options.authDir);
|
|
30760
30812
|
try {
|
|
30761
|
-
const
|
|
30762
|
-
|
|
30763
|
-
|
|
30764
|
-
...
|
|
30813
|
+
const started = await startOAuthFlow(server, {
|
|
30814
|
+
redirectUri: callback.redirectUri,
|
|
30815
|
+
...options.authDir ? { authDir: options.authDir } : {},
|
|
30816
|
+
...options.print ? { print: options.print } : {}
|
|
30765
30817
|
});
|
|
30766
|
-
if (
|
|
30767
|
-
if (!options.noOpen
|
|
30818
|
+
if (!started.authorizationUrl) return "AUTHORIZED";
|
|
30819
|
+
if (!options.noOpen) await (options.open ? options.open(started.authorizationUrl) : openBrowser$1(started.authorizationUrl));
|
|
30768
30820
|
const manualInput = options.manualInput ?? (options.noOpen ? await options.readManualInput?.() : void 0);
|
|
30769
30821
|
const completion = manualInput ? extractCompletion(manualInput) : await callback.waitForCode(() => callbackCode ? {
|
|
30770
30822
|
code: callbackCode,
|
|
30771
30823
|
...callbackState ? { state: callbackState } : {}
|
|
30772
30824
|
} : void 0);
|
|
30773
|
-
|
|
30774
|
-
|
|
30775
|
-
return await auth(provider, {
|
|
30776
|
-
serverUrl: server.url,
|
|
30777
|
-
authorizationCode: completion.code,
|
|
30778
|
-
...scope ? { scope } : {}
|
|
30779
|
-
});
|
|
30825
|
+
await started.complete(completion.state ? `${callback.redirectUri}?code=${encodeURIComponent(completion.code)}&state=${encodeURIComponent(completion.state)}` : `${callback.redirectUri}?code=${encodeURIComponent(completion.code)}`);
|
|
30826
|
+
return "AUTHORIZED";
|
|
30780
30827
|
} catch (error) {
|
|
30781
30828
|
throw normalizeMcpOAuthError(server, error);
|
|
30782
30829
|
} finally {
|
|
30783
30830
|
await callback.close();
|
|
30784
30831
|
}
|
|
30785
30832
|
}
|
|
30833
|
+
function assertNoOAuthCallbackError(target, callbackUrl) {
|
|
30834
|
+
let url;
|
|
30835
|
+
try {
|
|
30836
|
+
url = new URL(callbackUrl);
|
|
30837
|
+
} catch {
|
|
30838
|
+
return;
|
|
30839
|
+
}
|
|
30840
|
+
const error = url.searchParams.get("error");
|
|
30841
|
+
if (!error) return;
|
|
30842
|
+
const description = url.searchParams.get("error_description");
|
|
30843
|
+
throw new CapletsError("AUTH_FAILED", description ? `OAuth provider returned an error: ${description}` : "OAuth provider returned an error", redactSecrets({
|
|
30844
|
+
server: target.server,
|
|
30845
|
+
error,
|
|
30846
|
+
error_description: description ?? void 0
|
|
30847
|
+
}));
|
|
30848
|
+
}
|
|
30786
30849
|
function normalizeMcpOAuthError(server, error) {
|
|
30787
30850
|
if ((server.auth?.type === "oauth2" || server.auth?.type === "oidc") && !server.auth.clientId && !server.auth.clientMetadataUrl && error instanceof Error && error.message.includes("does not support dynamic client registration")) return new CapletsError("AUTH_FAILED", "OAuth is not available for this server without a host-specific OAuth app or PAT auth", {
|
|
30788
30851
|
server: server.server,
|
|
@@ -30790,6 +30853,75 @@ function normalizeMcpOAuthError(server, error) {
|
|
|
30790
30853
|
});
|
|
30791
30854
|
return error;
|
|
30792
30855
|
}
|
|
30856
|
+
async function startGenericOAuthFlow(target, options) {
|
|
30857
|
+
if (target.auth?.type !== "oauth2" && target.auth?.type !== "oidc") throw new CapletsError("REQUEST_INVALID", `${target.server} is not configured for OAuth`);
|
|
30858
|
+
const authConfig = target.auth;
|
|
30859
|
+
const redirectUri = authConfig.redirectUri ?? options.redirectUri;
|
|
30860
|
+
const verifier = base64url(randomBytes(32));
|
|
30861
|
+
const state = base64url(randomBytes(24));
|
|
30862
|
+
const allowLoopbackHttp = isLoopbackDevelopmentTarget(target, authConfig);
|
|
30863
|
+
const metadata = await discoverAuthorizationServer(target, authConfig, allowLoopbackHttp);
|
|
30864
|
+
const authorizationEndpoint = authConfig.authorizationUrl ?? metadata.authorization_endpoint;
|
|
30865
|
+
const tokenEndpoint = authConfig.tokenUrl ?? metadata.token_endpoint;
|
|
30866
|
+
if (!authorizationEndpoint || !tokenEndpoint) throw new CapletsError("AUTH_FAILED", "OAuth metadata is missing endpoints", { server: target.server });
|
|
30867
|
+
assertAllowedAuthUrl(authorizationEndpoint, "authorization endpoint", allowLoopbackHttp);
|
|
30868
|
+
assertAllowedAuthUrl(tokenEndpoint, "token endpoint", allowLoopbackHttp);
|
|
30869
|
+
const client = await resolveGenericClient(target, authConfig, metadata, redirectUri, allowLoopbackHttp);
|
|
30870
|
+
const scope = scopesFor(authConfig);
|
|
30871
|
+
const authorizationUrl = new URL(authorizationEndpoint);
|
|
30872
|
+
authorizationUrl.searchParams.set("response_type", "code");
|
|
30873
|
+
authorizationUrl.searchParams.set("client_id", client.clientId);
|
|
30874
|
+
authorizationUrl.searchParams.set("redirect_uri", redirectUri);
|
|
30875
|
+
authorizationUrl.searchParams.set("code_challenge", pkceChallenge(verifier));
|
|
30876
|
+
authorizationUrl.searchParams.set("code_challenge_method", "S256");
|
|
30877
|
+
authorizationUrl.searchParams.set("state", state);
|
|
30878
|
+
if (scope) authorizationUrl.searchParams.set("scope", scope);
|
|
30879
|
+
options.print?.(`Open this URL to authorize ${target.server}:\n${authorizationUrl.toString()}`);
|
|
30880
|
+
return {
|
|
30881
|
+
authorizationUrl: authorizationUrl.toString(),
|
|
30882
|
+
complete: async (callbackUrl) => {
|
|
30883
|
+
assertNoOAuthCallbackError(target, callbackUrl);
|
|
30884
|
+
const completion = extractCompletion(callbackUrl);
|
|
30885
|
+
if (completion.state !== state) throw new CapletsError("AUTH_FAILED", "OAuth callback state did not match");
|
|
30886
|
+
const params = new URLSearchParams({
|
|
30887
|
+
grant_type: "authorization_code",
|
|
30888
|
+
code: completion.code,
|
|
30889
|
+
redirect_uri: redirectUri,
|
|
30890
|
+
client_id: client.clientId,
|
|
30891
|
+
code_verifier: verifier
|
|
30892
|
+
});
|
|
30893
|
+
if (client.clientSecret) params.set("client_secret", client.clientSecret);
|
|
30894
|
+
const tokenResponse = await fetchJson(tokenEndpoint, target.requestTimeoutMs, {
|
|
30895
|
+
method: "POST",
|
|
30896
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
30897
|
+
body: params.toString()
|
|
30898
|
+
}, allowLoopbackHttp);
|
|
30899
|
+
const idToken = asString(tokenResponse.id_token);
|
|
30900
|
+
const idClaims = parseJwtPayload(idToken);
|
|
30901
|
+
validateOidcToken(authConfig, metadata, idToken, idClaims, client.clientId);
|
|
30902
|
+
writeTokenBundle(stripUndefined({
|
|
30903
|
+
server: target.server,
|
|
30904
|
+
authType: authConfig.type,
|
|
30905
|
+
accessToken: requireString(tokenResponse.access_token, "access_token"),
|
|
30906
|
+
refreshToken: asString(tokenResponse.refresh_token),
|
|
30907
|
+
tokenType: asString(tokenResponse.token_type),
|
|
30908
|
+
expiresAt: typeof tokenResponse.expires_in === "number" ? new Date(Date.now() + tokenResponse.expires_in * 1e3).toISOString() : void 0,
|
|
30909
|
+
scope: asString(tokenResponse.scope) ?? scope,
|
|
30910
|
+
idToken,
|
|
30911
|
+
issuer: asString(idClaims?.iss) ?? metadata.issuer ?? authConfig.issuer,
|
|
30912
|
+
subject: asString(idClaims?.sub),
|
|
30913
|
+
clientId: client.clientId,
|
|
30914
|
+
clientSecret: client.clientSecret,
|
|
30915
|
+
protectedResourceOrigin: protectedResourceOrigin(target, authConfig),
|
|
30916
|
+
metadata: redactSecrets({
|
|
30917
|
+
protectedResource: target.url ?? target.baseUrl ?? target.specUrl,
|
|
30918
|
+
authorizationServer: metadata,
|
|
30919
|
+
dynamicClient: client.dynamic ? { client_id: client.clientId } : void 0
|
|
30920
|
+
})
|
|
30921
|
+
}), options.authDir);
|
|
30922
|
+
}
|
|
30923
|
+
};
|
|
30924
|
+
}
|
|
30793
30925
|
async function runGenericOAuthFlow(target, options = {}) {
|
|
30794
30926
|
if (target.auth?.type !== "oauth2" && target.auth?.type !== "oidc") throw new CapletsError("REQUEST_INVALID", `${target.server} is not configured for OAuth`);
|
|
30795
30927
|
const authConfig = target.auth;
|
|
@@ -30822,7 +30954,7 @@ async function runGenericOAuthFlow(target, options = {}) {
|
|
|
30822
30954
|
authorizationUrl.searchParams.set("state", state);
|
|
30823
30955
|
if (scope) authorizationUrl.searchParams.set("scope", scope);
|
|
30824
30956
|
options.print?.(`Open this URL to authorize ${target.server}:\n${authorizationUrl.toString()}`);
|
|
30825
|
-
if (!options.noOpen) await (options.open ? options.open(authorizationUrl.toString()) : openBrowser(authorizationUrl.toString()));
|
|
30957
|
+
if (!options.noOpen) await (options.open ? options.open(authorizationUrl.toString()) : openBrowser$1(authorizationUrl.toString()));
|
|
30826
30958
|
const manualInput = options.manualInput ?? (options.noOpen ? await options.readManualInput?.() : void 0);
|
|
30827
30959
|
const completion = manualInput ? extractCompletion(manualInput) : await callback.waitForCode(() => callbackCode ? {
|
|
30828
30960
|
code: callbackCode,
|
|
@@ -30925,7 +31057,7 @@ async function createLoopbackCallback(onCallback) {
|
|
|
30925
31057
|
close: () => new Promise((resolve) => server.close(() => resolve()))
|
|
30926
31058
|
};
|
|
30927
31059
|
}
|
|
30928
|
-
async function openBrowser(url) {
|
|
31060
|
+
async function openBrowser$1(url) {
|
|
30929
31061
|
const { spawn } = await import("node:child_process");
|
|
30930
31062
|
spawn(process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open", process.platform === "win32" ? [
|
|
30931
31063
|
"/c",
|
|
@@ -57259,6 +57391,90 @@ function isDirectory(path) {
|
|
|
57259
57391
|
return false;
|
|
57260
57392
|
}
|
|
57261
57393
|
}
|
|
57394
|
+
const DEFAULT_SERVER_USER = "caplets";
|
|
57395
|
+
function resolveCapletsMode(input = {}, env = process.env) {
|
|
57396
|
+
const mode = parseCapletsMode(input.mode ?? env.CAPLETS_MODE ?? "auto");
|
|
57397
|
+
if (mode === "local") return { mode: "local" };
|
|
57398
|
+
const rawUrl = nonEmpty$1(input.serverUrl, "serverUrl") ?? nonEmpty$1(env.CAPLETS_SERVER_URL, "CAPLETS_SERVER_URL");
|
|
57399
|
+
if (mode === "remote") {
|
|
57400
|
+
if (rawUrl === void 0) throw new CapletsError("REQUEST_INVALID", "CAPLETS_MODE=remote requires CAPLETS_SERVER_URL or serverUrl.");
|
|
57401
|
+
return { mode: "remote" };
|
|
57402
|
+
}
|
|
57403
|
+
return rawUrl === void 0 ? { mode: "local" } : { mode: "remote" };
|
|
57404
|
+
}
|
|
57405
|
+
function resolveCapletsServer(input = {}, env = process.env) {
|
|
57406
|
+
const rawUrl = nonEmpty$1(input.url, "url") ?? nonEmpty$1(env.CAPLETS_SERVER_URL, "CAPLETS_SERVER_URL");
|
|
57407
|
+
if (rawUrl === void 0) throw new CapletsError("REQUEST_INVALID", "CAPLETS_SERVER_URL or url is required.");
|
|
57408
|
+
const baseUrl = parseServerBaseUrl(rawUrl);
|
|
57409
|
+
const userWasExplicit = input.user !== void 0 || hasEnv$1(env.CAPLETS_SERVER_USER);
|
|
57410
|
+
const user = nonEmpty$1(input.user, "user") ?? nonEmpty$1(env.CAPLETS_SERVER_USER, "CAPLETS_SERVER_USER") ?? DEFAULT_SERVER_USER;
|
|
57411
|
+
const password = nonEmpty$1(input.password, "password") ?? nonEmpty$1(env.CAPLETS_SERVER_PASSWORD, "CAPLETS_SERVER_PASSWORD");
|
|
57412
|
+
if (userWasExplicit && password === void 0) throw new CapletsError("REQUEST_INVALID", "Caplets server Basic Auth requires a password; set CAPLETS_SERVER_PASSWORD or password.");
|
|
57413
|
+
const auth = password === void 0 ? {
|
|
57414
|
+
enabled: false,
|
|
57415
|
+
user
|
|
57416
|
+
} : {
|
|
57417
|
+
enabled: true,
|
|
57418
|
+
user,
|
|
57419
|
+
password
|
|
57420
|
+
};
|
|
57421
|
+
const requestInit = auth.enabled ? { headers: { Authorization: basicAuthHeader(auth.user, auth.password) } } : {};
|
|
57422
|
+
return {
|
|
57423
|
+
baseUrl,
|
|
57424
|
+
mcpUrl: mcpUrlForBase(baseUrl),
|
|
57425
|
+
controlUrl: controlUrlForBase(baseUrl),
|
|
57426
|
+
healthUrl: healthUrlForBase(baseUrl),
|
|
57427
|
+
auth,
|
|
57428
|
+
requestInit,
|
|
57429
|
+
...input.fetch ? { fetch: input.fetch } : {}
|
|
57430
|
+
};
|
|
57431
|
+
}
|
|
57432
|
+
function mcpUrlForBase(baseUrl) {
|
|
57433
|
+
return appendBasePath(baseUrl, "mcp");
|
|
57434
|
+
}
|
|
57435
|
+
function controlUrlForBase(baseUrl) {
|
|
57436
|
+
return appendBasePath(baseUrl, "control");
|
|
57437
|
+
}
|
|
57438
|
+
function healthUrlForBase(baseUrl) {
|
|
57439
|
+
return appendBasePath(baseUrl, "healthz");
|
|
57440
|
+
}
|
|
57441
|
+
function appendBasePath(baseUrl, path) {
|
|
57442
|
+
const url = new URL(baseUrl.href);
|
|
57443
|
+
url.pathname = `${url.pathname === "/" ? "" : url.pathname}/${path}`;
|
|
57444
|
+
return url;
|
|
57445
|
+
}
|
|
57446
|
+
function parseServerBaseUrl(value) {
|
|
57447
|
+
let url;
|
|
57448
|
+
try {
|
|
57449
|
+
url = new URL(value);
|
|
57450
|
+
} catch {
|
|
57451
|
+
throw new CapletsError("REQUEST_INVALID", "Invalid Caplets server URL.");
|
|
57452
|
+
}
|
|
57453
|
+
if (url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "") throw new CapletsError("REQUEST_INVALID", "Caplets server URL must not include username, password, query string, or fragment.");
|
|
57454
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && isLoopbackHost$1(url.hostname))) throw new CapletsError("REQUEST_INVALID", "Caplets server URL must use https except loopback development URLs.");
|
|
57455
|
+
url.pathname = url.pathname === "/" ? "/" : url.pathname.replace(/\/+$/u, "");
|
|
57456
|
+
return url;
|
|
57457
|
+
}
|
|
57458
|
+
function isLoopbackHost$1(host) {
|
|
57459
|
+
const normalized = host.toLocaleLowerCase();
|
|
57460
|
+
return normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1" || normalized === "[::1]";
|
|
57461
|
+
}
|
|
57462
|
+
function parseCapletsMode(value) {
|
|
57463
|
+
if (value === "auto" || value === "local" || value === "remote") return value;
|
|
57464
|
+
throw new CapletsError("REQUEST_INVALID", `Expected CAPLETS_MODE to be auto, local, or remote, got ${value}`);
|
|
57465
|
+
}
|
|
57466
|
+
function basicAuthHeader(user, password) {
|
|
57467
|
+
return `Basic ${Buffer$1.from(`${user}:${password}`).toString("base64")}`;
|
|
57468
|
+
}
|
|
57469
|
+
function nonEmpty$1(value, label) {
|
|
57470
|
+
if (value === void 0) return;
|
|
57471
|
+
const trimmed = value.trim();
|
|
57472
|
+
if (!trimmed) throw new CapletsError("REQUEST_INVALID", `${label} must not be empty`);
|
|
57473
|
+
return trimmed;
|
|
57474
|
+
}
|
|
57475
|
+
function hasEnv$1(value) {
|
|
57476
|
+
return value !== void 0 && value.trim() !== "";
|
|
57477
|
+
}
|
|
57262
57478
|
//#endregion
|
|
57263
57479
|
//#region ../core/dist/index.js
|
|
57264
57480
|
/**
|
|
@@ -58557,7 +58773,7 @@ const EMPTY_COMPLETION_RESULT = { completion: {
|
|
|
58557
58773
|
values: [],
|
|
58558
58774
|
hasMore: false
|
|
58559
58775
|
} };
|
|
58560
|
-
var version$1 = "0.
|
|
58776
|
+
var version$1 = "0.17.0";
|
|
58561
58777
|
var CapletsMcpSession = class {
|
|
58562
58778
|
engine;
|
|
58563
58779
|
server;
|
|
@@ -62090,49 +62306,56 @@ async function loginAuth(serverId, options) {
|
|
|
62090
62306
|
}
|
|
62091
62307
|
}
|
|
62092
62308
|
function logoutAuth(serverId, options) {
|
|
62093
|
-
|
|
62094
|
-
if (deleteTokenBundle(serverId, options.authDir)) options.writeOut(`Deleted OAuth credentials for \`${serverId}\`.\n`);
|
|
62309
|
+
if (logoutAuthResult(serverId, options).deleted) options.writeOut(`Deleted OAuth credentials for \`${serverId}\`.\n`);
|
|
62095
62310
|
else options.writeOut(`No OAuth credentials found for \`${serverId}\`.\n`);
|
|
62096
62311
|
}
|
|
62312
|
+
function logoutAuthResult(serverId, options) {
|
|
62313
|
+
assertLoginTarget(findAuthTarget(serverId, loadConfig(options.configPath)), serverId);
|
|
62314
|
+
return {
|
|
62315
|
+
server: serverId,
|
|
62316
|
+
deleted: deleteTokenBundle(serverId, options.authDir)
|
|
62317
|
+
};
|
|
62318
|
+
}
|
|
62097
62319
|
function listAuth(options) {
|
|
62098
|
-
const
|
|
62320
|
+
const rows = listAuthRows(options);
|
|
62099
62321
|
const format = options.format ?? "plain";
|
|
62100
62322
|
if (format === "json") {
|
|
62101
|
-
const rows = servers.map((server) => {
|
|
62102
|
-
const bundle = readTokenBundle(server.server, options.authDir);
|
|
62103
|
-
const status = !bundle ? "missing" : isTokenBundleExpired(bundle) ? "expired" : "authenticated";
|
|
62104
|
-
return {
|
|
62105
|
-
server: server.server,
|
|
62106
|
-
status,
|
|
62107
|
-
...bundle?.expiresAt ? { expiresAt: bundle.expiresAt } : {},
|
|
62108
|
-
...bundle?.scope ? { scope: bundle.scope } : {}
|
|
62109
|
-
};
|
|
62110
|
-
});
|
|
62111
62323
|
options.writeOut(`${JSON.stringify(rows, null, 2)}\n`);
|
|
62112
62324
|
return;
|
|
62113
62325
|
}
|
|
62114
|
-
|
|
62115
|
-
|
|
62116
|
-
|
|
62117
|
-
|
|
62118
|
-
if (format === "markdown") options.writeOut("## OAuth credentials\n\n");
|
|
62119
|
-
else options.writeOut("OAuth credentials\n\n");
|
|
62120
|
-
for (const server of servers) {
|
|
62326
|
+
options.writeOut(formatAuthRows(rows, format));
|
|
62327
|
+
}
|
|
62328
|
+
function listAuthRows(options) {
|
|
62329
|
+
return authTargets(loadConfig(options.configPath)).sort((left, right) => left.server.localeCompare(right.server)).map((server) => {
|
|
62121
62330
|
const bundle = readTokenBundle(server.server, options.authDir);
|
|
62122
62331
|
const status = !bundle ? "missing" : isTokenBundleExpired(bundle) ? "expired" : "authenticated";
|
|
62123
|
-
|
|
62332
|
+
return {
|
|
62333
|
+
server: server.server,
|
|
62334
|
+
status,
|
|
62335
|
+
...bundle?.expiresAt ? { expiresAt: bundle.expiresAt } : {},
|
|
62336
|
+
...bundle?.scope ? { scope: bundle.scope } : {}
|
|
62337
|
+
};
|
|
62338
|
+
});
|
|
62339
|
+
}
|
|
62340
|
+
function formatAuthRows(rows, format) {
|
|
62341
|
+
if (rows.length === 0) return format === "markdown" ? "## OAuth credentials\n\nNo configured remote OAuth servers found.\n" : "No configured remote OAuth servers found.\n";
|
|
62342
|
+
let output = "";
|
|
62343
|
+
if (format === "markdown") output += "## OAuth credentials\n\n";
|
|
62344
|
+
else output += "OAuth credentials\n\n";
|
|
62345
|
+
for (const row of rows) {
|
|
62346
|
+
const details = [row.expiresAt ? `expires ${row.expiresAt}` : void 0, row.scope ? `scope ${row.scope}` : void 0].filter(Boolean).join("; ");
|
|
62124
62347
|
if (format === "markdown") {
|
|
62125
|
-
|
|
62348
|
+
output += `- \`${row.server}\` — ${row.status}${details ? ` (${details})` : ""}\n`;
|
|
62126
62349
|
continue;
|
|
62127
62350
|
}
|
|
62128
|
-
|
|
62129
|
-
|
|
62130
|
-
` Status: ${status}`,
|
|
62131
|
-
...
|
|
62132
|
-
...
|
|
62133
|
-
].join("\n")
|
|
62134
|
-
options.writeOut("\n\n");
|
|
62351
|
+
output += [
|
|
62352
|
+
row.server,
|
|
62353
|
+
` Status: ${row.status}`,
|
|
62354
|
+
...row.expiresAt ? [` Expires: ${row.expiresAt}`] : [],
|
|
62355
|
+
...row.scope ? [` Scope: ${row.scope}`] : []
|
|
62356
|
+
].join("\n") + "\n\n";
|
|
62135
62357
|
}
|
|
62358
|
+
return output;
|
|
62136
62359
|
}
|
|
62137
62360
|
function findAuthTarget(serverId, config = loadConfig()) {
|
|
62138
62361
|
return authTargets(config).find((server) => server.server === serverId);
|
|
@@ -62552,6 +62775,94 @@ function nearestExistingParent(path) {
|
|
|
62552
62775
|
if (parent === path) return parent;
|
|
62553
62776
|
return nearestExistingParent(parent);
|
|
62554
62777
|
}
|
|
62778
|
+
var RemoteControlClient = class {
|
|
62779
|
+
#baseUrl;
|
|
62780
|
+
#requestInit;
|
|
62781
|
+
#fetch;
|
|
62782
|
+
constructor(options) {
|
|
62783
|
+
this.#baseUrl = options.baseUrl;
|
|
62784
|
+
this.#requestInit = options.requestInit;
|
|
62785
|
+
this.#fetch = options.fetch ?? fetch;
|
|
62786
|
+
}
|
|
62787
|
+
async request(command, args) {
|
|
62788
|
+
const controlUrl = controlUrlForBase(this.#baseUrl);
|
|
62789
|
+
let response;
|
|
62790
|
+
try {
|
|
62791
|
+
response = await this.#fetch(controlUrl, {
|
|
62792
|
+
...this.#requestInit,
|
|
62793
|
+
method: "POST",
|
|
62794
|
+
headers: mergeJsonHeaders(this.#requestInit.headers),
|
|
62795
|
+
body: JSON.stringify({
|
|
62796
|
+
command,
|
|
62797
|
+
arguments: args
|
|
62798
|
+
})
|
|
62799
|
+
});
|
|
62800
|
+
} catch (error) {
|
|
62801
|
+
throw new CapletsError("SERVER_UNAVAILABLE", `Could not connect to Caplets server at ${safeBaseUrl(this.#baseUrl)}.`, toSafeError(error, "SERVER_UNAVAILABLE"));
|
|
62802
|
+
}
|
|
62803
|
+
if (response.status === 401 || response.status === 403) throw new CapletsError("AUTH_FAILED", "Caplets server authentication failed. Check CAPLETS_SERVER_USER and CAPLETS_SERVER_PASSWORD.");
|
|
62804
|
+
if (!response.ok) throw new CapletsError("SERVER_UNAVAILABLE", `Caplets server at ${safeBaseUrl(this.#baseUrl)} returned HTTP ${response.status}.`);
|
|
62805
|
+
const payload = await parseRemoteCliResponse(response);
|
|
62806
|
+
if (!payload.ok) throw new CapletsError(payload.error.code, redactRemoteMessage(payload.error.message), payload.error.nextAction === void 0 ? void 0 : { nextAction: payload.error.nextAction });
|
|
62807
|
+
return payload.result;
|
|
62808
|
+
}
|
|
62809
|
+
};
|
|
62810
|
+
function mergeJsonHeaders(headers) {
|
|
62811
|
+
const merged = new Headers(headers);
|
|
62812
|
+
merged.set("content-type", "application/json");
|
|
62813
|
+
return merged;
|
|
62814
|
+
}
|
|
62815
|
+
function safeBaseUrl(baseUrl) {
|
|
62816
|
+
const safe = new URL(baseUrl.href);
|
|
62817
|
+
safe.username = "";
|
|
62818
|
+
safe.password = "";
|
|
62819
|
+
safe.search = "";
|
|
62820
|
+
safe.hash = "";
|
|
62821
|
+
return safe.toString();
|
|
62822
|
+
}
|
|
62823
|
+
async function parseRemoteCliResponse(response) {
|
|
62824
|
+
let payload;
|
|
62825
|
+
try {
|
|
62826
|
+
payload = await response.json();
|
|
62827
|
+
} catch (error) {
|
|
62828
|
+
throw invalidRemoteControlResponse(error);
|
|
62829
|
+
}
|
|
62830
|
+
if (!isRecord(payload)) throw invalidRemoteControlResponse();
|
|
62831
|
+
if (payload.ok === true) {
|
|
62832
|
+
if (!("result" in payload)) throw invalidRemoteControlResponse();
|
|
62833
|
+
return {
|
|
62834
|
+
ok: true,
|
|
62835
|
+
result: payload.result
|
|
62836
|
+
};
|
|
62837
|
+
}
|
|
62838
|
+
if (payload.ok === false) {
|
|
62839
|
+
const error = payload.error;
|
|
62840
|
+
if (!isRecord(error) || typeof error.code !== "string" || typeof error.message !== "string") throw invalidRemoteControlResponse();
|
|
62841
|
+
if ("nextAction" in error && error.nextAction !== void 0 && typeof error.nextAction !== "string") throw invalidRemoteControlResponse();
|
|
62842
|
+
const errorResponse = {
|
|
62843
|
+
ok: false,
|
|
62844
|
+
error: {
|
|
62845
|
+
code: isCapletsErrorCode(error.code) ? error.code : "DOWNSTREAM_TOOL_ERROR",
|
|
62846
|
+
message: error.message
|
|
62847
|
+
}
|
|
62848
|
+
};
|
|
62849
|
+
if (typeof error.nextAction === "string") errorResponse.error.nextAction = error.nextAction;
|
|
62850
|
+
return errorResponse;
|
|
62851
|
+
}
|
|
62852
|
+
throw invalidRemoteControlResponse();
|
|
62853
|
+
}
|
|
62854
|
+
function invalidRemoteControlResponse(cause) {
|
|
62855
|
+
return new CapletsError("DOWNSTREAM_PROTOCOL_ERROR", "Caplets server returned an invalid remote control response.", cause === void 0 ? void 0 : toSafeError(cause, "DOWNSTREAM_PROTOCOL_ERROR"));
|
|
62856
|
+
}
|
|
62857
|
+
function isRecord(value) {
|
|
62858
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
62859
|
+
}
|
|
62860
|
+
function isCapletsErrorCode(value) {
|
|
62861
|
+
return CAPLETS_ERROR_CODES.includes(value);
|
|
62862
|
+
}
|
|
62863
|
+
function redactRemoteMessage(message) {
|
|
62864
|
+
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]");
|
|
62865
|
+
}
|
|
62555
62866
|
var compose = (middleware, onError, onNotFound) => {
|
|
62556
62867
|
return (context, next) => {
|
|
62557
62868
|
let index = -1;
|
|
@@ -65421,29 +65732,343 @@ var logger = (fn = console.log) => {
|
|
|
65421
65732
|
await log(fn, "-->", method, path, c.res.status, time(start));
|
|
65422
65733
|
};
|
|
65423
65734
|
};
|
|
65735
|
+
const ENGINE_COMMANDS = new Set([
|
|
65736
|
+
"get_caplet",
|
|
65737
|
+
"check_backend",
|
|
65738
|
+
"list_tools",
|
|
65739
|
+
"search_tools",
|
|
65740
|
+
"get_tool",
|
|
65741
|
+
"call_tool"
|
|
65742
|
+
]);
|
|
65743
|
+
async function dispatchRemoteCliRequest(request, context) {
|
|
65744
|
+
try {
|
|
65745
|
+
return {
|
|
65746
|
+
ok: true,
|
|
65747
|
+
result: await dispatch(request, context)
|
|
65748
|
+
};
|
|
65749
|
+
} catch (error) {
|
|
65750
|
+
const safe = toSafeError(error);
|
|
65751
|
+
const action = nextAction(safe.details);
|
|
65752
|
+
return {
|
|
65753
|
+
ok: false,
|
|
65754
|
+
error: {
|
|
65755
|
+
code: safe.code,
|
|
65756
|
+
message: redactControlErrorMessage(safe.message),
|
|
65757
|
+
...action ? { nextAction: action } : {}
|
|
65758
|
+
}
|
|
65759
|
+
};
|
|
65760
|
+
}
|
|
65761
|
+
}
|
|
65762
|
+
async function dispatch(request, context) {
|
|
65763
|
+
assertObject(request, "remote control request");
|
|
65764
|
+
assertObject(request.arguments, "remote control request arguments");
|
|
65765
|
+
if (request.command === "list") return listCaplets(loadConfigWithSources(context.configPath, context.projectConfigPath), { includeDisabled: optionalBoolean(request.arguments, "includeDisabled") ?? false });
|
|
65766
|
+
if (ENGINE_COMMANDS.has(request.command)) {
|
|
65767
|
+
const caplet = requiredString(request.arguments, "caplet");
|
|
65768
|
+
const toolRequest = requiredEngineRequest(request.arguments, request.command);
|
|
65769
|
+
const engine = new CapletsEngine(context);
|
|
65770
|
+
try {
|
|
65771
|
+
return await engine.execute(caplet, toolRequest);
|
|
65772
|
+
} finally {
|
|
65773
|
+
await engine.close();
|
|
65774
|
+
}
|
|
65775
|
+
}
|
|
65776
|
+
if (request.command === "init") return {
|
|
65777
|
+
remote: true,
|
|
65778
|
+
path: initConfig({
|
|
65779
|
+
...optionalProp("path", context.configPath),
|
|
65780
|
+
...optionalProp("force", optionalBoolean(request.arguments, "force"))
|
|
65781
|
+
})
|
|
65782
|
+
};
|
|
65783
|
+
if (request.command === "add") return dispatchAdd(request.arguments, context);
|
|
65784
|
+
if (request.command === "install") return {
|
|
65785
|
+
remote: true,
|
|
65786
|
+
...installCaplets(requiredString(request.arguments, "repo"), {
|
|
65787
|
+
...optionalProp("capletIds", optionalStringArray(request.arguments, "capletIds")),
|
|
65788
|
+
destinationRoot: context.projectCapletsRoot,
|
|
65789
|
+
...optionalProp("force", optionalBoolean(request.arguments, "force"))
|
|
65790
|
+
})
|
|
65791
|
+
};
|
|
65792
|
+
if (request.command === "auth_list") return listAuthRows({
|
|
65793
|
+
...optionalProp("configPath", context.configPath),
|
|
65794
|
+
...optionalProp("authDir", context.authDir)
|
|
65795
|
+
});
|
|
65796
|
+
if (request.command === "auth_logout") return logoutAuthResult(requiredString(request.arguments, "server"), {
|
|
65797
|
+
...optionalProp("configPath", context.configPath),
|
|
65798
|
+
...optionalProp("authDir", context.authDir)
|
|
65799
|
+
});
|
|
65800
|
+
if (request.command === "auth_login_start") return startRemoteAuthLogin(requiredString(request.arguments, "server"), context);
|
|
65801
|
+
if (request.command === "auth_login_complete") return completeRemoteAuthLogin(requiredString(request.arguments, "flowId"), requiredString(request.arguments, "callbackUrl"), context);
|
|
65802
|
+
throw new CapletsError("UNKNOWN_OPERATION", `Unsupported remote control command ${request.command}`);
|
|
65803
|
+
}
|
|
65804
|
+
async function startRemoteAuthLogin(serverId, context) {
|
|
65805
|
+
if (!context.authFlowStore || !context.controlCallbackBaseUrl) throw new CapletsError("REQUEST_INVALID", "Remote auth login is not available on this server");
|
|
65806
|
+
const config = loadConfigWithSources(context.configPath, context.projectConfigPath).config;
|
|
65807
|
+
const target = findAuthTarget(serverId, config);
|
|
65808
|
+
assertLoginTarget(target, serverId);
|
|
65809
|
+
const flowId = randomUUID();
|
|
65810
|
+
const baseUrl = context.controlCallbackBaseUrl.endsWith("/") ? context.controlCallbackBaseUrl : `${context.controlCallbackBaseUrl}/`;
|
|
65811
|
+
const redirectUri = new URL(`auth/callback/${flowId}`, baseUrl).toString();
|
|
65812
|
+
const started = target.backend === "mcp" ? await startOAuthFlow(target, {
|
|
65813
|
+
redirectUri,
|
|
65814
|
+
...optionalProp("authDir", context.authDir)
|
|
65815
|
+
}) : await startGenericOAuthFlow(target, {
|
|
65816
|
+
redirectUri,
|
|
65817
|
+
...optionalProp("authDir", context.authDir)
|
|
65818
|
+
});
|
|
65819
|
+
if (!started.authorizationUrl) return {
|
|
65820
|
+
server: serverId,
|
|
65821
|
+
authenticated: true
|
|
65822
|
+
};
|
|
65823
|
+
const flow = context.authFlowStore.create({
|
|
65824
|
+
server: serverId,
|
|
65825
|
+
authorizationUrl: started.authorizationUrl,
|
|
65826
|
+
complete: started.complete
|
|
65827
|
+
}, flowId);
|
|
65828
|
+
return {
|
|
65829
|
+
server: serverId,
|
|
65830
|
+
flowId: flow.id,
|
|
65831
|
+
authorizationUrl: flow.authorizationUrl
|
|
65832
|
+
};
|
|
65833
|
+
}
|
|
65834
|
+
async function completeRemoteAuthLogin(flowId, callbackUrl, context) {
|
|
65835
|
+
const flow = context.authFlowStore?.get(flowId);
|
|
65836
|
+
if (!flow) throw new CapletsError("REQUEST_INVALID", `Unknown auth flow ${flowId}`);
|
|
65837
|
+
context.authFlowStore?.delete(flowId);
|
|
65838
|
+
await flow.complete(callbackUrl);
|
|
65839
|
+
return {
|
|
65840
|
+
server: flow.server,
|
|
65841
|
+
authenticated: true
|
|
65842
|
+
};
|
|
65843
|
+
}
|
|
65844
|
+
function dispatchAdd(args, context) {
|
|
65845
|
+
const kind = requiredString(args, "kind");
|
|
65846
|
+
const id = requiredString(args, "id");
|
|
65847
|
+
const options = remoteAddOptions$1(kind, optionalObject(args, "options"));
|
|
65848
|
+
switch (kind) {
|
|
65849
|
+
case "cli": return {
|
|
65850
|
+
remote: true,
|
|
65851
|
+
label: "CLI",
|
|
65852
|
+
...addCliCaplet(id, {
|
|
65853
|
+
...options,
|
|
65854
|
+
destinationRoot: context.projectCapletsRoot,
|
|
65855
|
+
print: false
|
|
65856
|
+
})
|
|
65857
|
+
};
|
|
65858
|
+
case "mcp": return {
|
|
65859
|
+
remote: true,
|
|
65860
|
+
label: "MCP",
|
|
65861
|
+
...addMcpCaplet(id, {
|
|
65862
|
+
...options,
|
|
65863
|
+
destinationRoot: context.projectCapletsRoot,
|
|
65864
|
+
print: false
|
|
65865
|
+
})
|
|
65866
|
+
};
|
|
65867
|
+
case "openapi": return {
|
|
65868
|
+
remote: true,
|
|
65869
|
+
label: "OpenAPI",
|
|
65870
|
+
...addOpenApiCaplet(id, {
|
|
65871
|
+
...options,
|
|
65872
|
+
destinationRoot: context.projectCapletsRoot,
|
|
65873
|
+
print: false
|
|
65874
|
+
})
|
|
65875
|
+
};
|
|
65876
|
+
case "graphql": return {
|
|
65877
|
+
remote: true,
|
|
65878
|
+
label: "GraphQL",
|
|
65879
|
+
...addGraphqlCaplet(id, {
|
|
65880
|
+
...options,
|
|
65881
|
+
destinationRoot: context.projectCapletsRoot,
|
|
65882
|
+
print: false
|
|
65883
|
+
})
|
|
65884
|
+
};
|
|
65885
|
+
case "http": return {
|
|
65886
|
+
remote: true,
|
|
65887
|
+
label: "HTTP",
|
|
65888
|
+
...addHttpCaplet(id, {
|
|
65889
|
+
...options,
|
|
65890
|
+
destinationRoot: context.projectCapletsRoot,
|
|
65891
|
+
print: false
|
|
65892
|
+
})
|
|
65893
|
+
};
|
|
65894
|
+
default: throw new CapletsError("REQUEST_INVALID", "add.kind must be cli, mcp, openapi, graphql, or http");
|
|
65895
|
+
}
|
|
65896
|
+
}
|
|
65897
|
+
function optionalProp(key, value) {
|
|
65898
|
+
return value === void 0 ? {} : { [key]: value };
|
|
65899
|
+
}
|
|
65900
|
+
function assertObject(value, label) {
|
|
65901
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new CapletsError("REQUEST_INVALID", `${label} must be an object`);
|
|
65902
|
+
}
|
|
65903
|
+
function requiredString(args, key) {
|
|
65904
|
+
const value = args[key];
|
|
65905
|
+
if (typeof value !== "string" || value.length === 0) throw new CapletsError("REQUEST_INVALID", `${key} must be a non-empty string`);
|
|
65906
|
+
return value;
|
|
65907
|
+
}
|
|
65908
|
+
function optionalObject(args, key) {
|
|
65909
|
+
const value = args[key];
|
|
65910
|
+
if (value === void 0) return {};
|
|
65911
|
+
assertObject(value, key);
|
|
65912
|
+
return value;
|
|
65913
|
+
}
|
|
65914
|
+
function requiredEngineRequest(args, command) {
|
|
65915
|
+
const toolRequest = optionalObject(args, "request");
|
|
65916
|
+
if (typeof toolRequest.operation !== "string") throw new CapletsError("REQUEST_INVALID", "request.operation must be a string");
|
|
65917
|
+
if (toolRequest.operation !== command) throw new CapletsError("REQUEST_INVALID", `request.operation must match remote command ${command}`);
|
|
65918
|
+
return toolRequest;
|
|
65919
|
+
}
|
|
65920
|
+
function remoteAddOptions$1(kind, options) {
|
|
65921
|
+
rejectServerOwnedAddOptions(options);
|
|
65922
|
+
switch (kind) {
|
|
65923
|
+
case "cli": return pickOptions(options, {
|
|
65924
|
+
repo: "string",
|
|
65925
|
+
include: "string",
|
|
65926
|
+
command: "string",
|
|
65927
|
+
force: "boolean"
|
|
65928
|
+
});
|
|
65929
|
+
case "mcp": return pickOptions(options, {
|
|
65930
|
+
command: "string",
|
|
65931
|
+
arg: "string-array",
|
|
65932
|
+
cwd: "string",
|
|
65933
|
+
env: "string-array",
|
|
65934
|
+
url: "string",
|
|
65935
|
+
transport: "string",
|
|
65936
|
+
tokenEnv: "string",
|
|
65937
|
+
force: "boolean"
|
|
65938
|
+
});
|
|
65939
|
+
case "openapi": return pickOptions(options, {
|
|
65940
|
+
spec: "string",
|
|
65941
|
+
baseUrl: "string",
|
|
65942
|
+
tokenEnv: "string",
|
|
65943
|
+
force: "boolean"
|
|
65944
|
+
});
|
|
65945
|
+
case "graphql": return pickOptions(options, {
|
|
65946
|
+
endpointUrl: "string",
|
|
65947
|
+
schema: "string",
|
|
65948
|
+
introspection: "boolean",
|
|
65949
|
+
tokenEnv: "string",
|
|
65950
|
+
force: "boolean"
|
|
65951
|
+
});
|
|
65952
|
+
case "http": return pickOptions(options, {
|
|
65953
|
+
baseUrl: "string",
|
|
65954
|
+
action: "string-array",
|
|
65955
|
+
tokenEnv: "string",
|
|
65956
|
+
force: "boolean"
|
|
65957
|
+
});
|
|
65958
|
+
default: return options;
|
|
65959
|
+
}
|
|
65960
|
+
}
|
|
65961
|
+
function pickOptions(options, schema) {
|
|
65962
|
+
const next = {};
|
|
65963
|
+
for (const [key, type] of Object.entries(schema)) {
|
|
65964
|
+
const value = options[key];
|
|
65965
|
+
if (value === void 0) continue;
|
|
65966
|
+
validateOptionType(key, value, type);
|
|
65967
|
+
next[key] = value;
|
|
65968
|
+
}
|
|
65969
|
+
return next;
|
|
65970
|
+
}
|
|
65971
|
+
function rejectServerOwnedAddOptions(options) {
|
|
65972
|
+
if ("output" in options) throw new CapletsError("REQUEST_INVALID", "Remote add output is not supported remotely; the server owns destinationRoot and output path selection");
|
|
65973
|
+
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`);
|
|
65974
|
+
}
|
|
65975
|
+
function validateOptionType(key, value, type) {
|
|
65976
|
+
if (type === "string" && typeof value !== "string") throw new CapletsError("REQUEST_INVALID", `add.options.${key} must be a string`);
|
|
65977
|
+
if (type === "boolean" && typeof value !== "boolean") throw new CapletsError("REQUEST_INVALID", `add.options.${key} must be a boolean`);
|
|
65978
|
+
if (type === "string-array") {
|
|
65979
|
+
if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) throw new CapletsError("REQUEST_INVALID", `add.options.${key} must be an array of strings`);
|
|
65980
|
+
}
|
|
65981
|
+
}
|
|
65982
|
+
function optionalBoolean(args, key) {
|
|
65983
|
+
const value = args[key];
|
|
65984
|
+
if (value === void 0) return;
|
|
65985
|
+
if (typeof value !== "boolean") throw new CapletsError("REQUEST_INVALID", `${key} must be a boolean`);
|
|
65986
|
+
return value;
|
|
65987
|
+
}
|
|
65988
|
+
function optionalStringArray(args, key) {
|
|
65989
|
+
const value = args[key];
|
|
65990
|
+
if (value === void 0) return;
|
|
65991
|
+
if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) throw new CapletsError("REQUEST_INVALID", `${key} must be an array of strings`);
|
|
65992
|
+
return value;
|
|
65993
|
+
}
|
|
65994
|
+
function nextAction(details) {
|
|
65995
|
+
if (details && typeof details === "object" && "nextAction" in details) {
|
|
65996
|
+
const value = details.nextAction;
|
|
65997
|
+
return typeof value === "string" ? value : void 0;
|
|
65998
|
+
}
|
|
65999
|
+
}
|
|
66000
|
+
function redactControlErrorMessage(message) {
|
|
66001
|
+
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]");
|
|
66002
|
+
}
|
|
66003
|
+
const DEFAULT_AUTH_FLOW_TTL_MS = 600 * 1e3;
|
|
66004
|
+
var RemoteAuthFlowStore = class {
|
|
66005
|
+
options;
|
|
66006
|
+
flows = /* @__PURE__ */ new Map();
|
|
66007
|
+
constructor(options = {}) {
|
|
66008
|
+
this.options = options;
|
|
66009
|
+
}
|
|
66010
|
+
create(flow, id = randomUUID()) {
|
|
66011
|
+
this.pruneExpired();
|
|
66012
|
+
const created = {
|
|
66013
|
+
id,
|
|
66014
|
+
createdAt: this.now(),
|
|
66015
|
+
...flow
|
|
66016
|
+
};
|
|
66017
|
+
this.flows.set(created.id, created);
|
|
66018
|
+
return { ...created };
|
|
66019
|
+
}
|
|
66020
|
+
get(id) {
|
|
66021
|
+
this.pruneExpired();
|
|
66022
|
+
const flow = this.flows.get(id);
|
|
66023
|
+
if (flow && this.isExpired(flow)) {
|
|
66024
|
+
this.flows.delete(id);
|
|
66025
|
+
return;
|
|
66026
|
+
}
|
|
66027
|
+
return flow;
|
|
66028
|
+
}
|
|
66029
|
+
delete(id) {
|
|
66030
|
+
this.flows.delete(id);
|
|
66031
|
+
}
|
|
66032
|
+
pruneExpired() {
|
|
66033
|
+
for (const [id, flow] of this.flows) if (this.isExpired(flow)) this.flows.delete(id);
|
|
66034
|
+
}
|
|
66035
|
+
isExpired(flow) {
|
|
66036
|
+
return this.now() - flow.createdAt > (this.options.ttlMs ?? DEFAULT_AUTH_FLOW_TTL_MS);
|
|
66037
|
+
}
|
|
66038
|
+
now() {
|
|
66039
|
+
return this.options.now?.() ?? Date.now();
|
|
66040
|
+
}
|
|
66041
|
+
};
|
|
65424
66042
|
function createHttpServeApp(options, engine, io = {}) {
|
|
65425
66043
|
const app = new Hono();
|
|
65426
66044
|
const sessions = /* @__PURE__ */ new Map();
|
|
65427
66045
|
const writeErr = io.writeErr ?? process.stderr.write.bind(process.stderr);
|
|
66046
|
+
const paths = servicePaths(options.path);
|
|
66047
|
+
const authFlowStore = io.authFlowStore ?? new RemoteAuthFlowStore();
|
|
65428
66048
|
app.use("*", logger((message, ...rest) => {
|
|
65429
66049
|
writeErr(`${[message, ...rest].join(" ")}\n`);
|
|
65430
66050
|
}));
|
|
65431
|
-
app.get(
|
|
66051
|
+
app.get(paths.base, (c) => c.json({
|
|
65432
66052
|
name: "caplets",
|
|
65433
66053
|
transport: "http",
|
|
65434
|
-
|
|
65435
|
-
|
|
66054
|
+
base: paths.base,
|
|
66055
|
+
mcp: paths.mcp,
|
|
66056
|
+
control: paths.control,
|
|
66057
|
+
health: paths.health,
|
|
65436
66058
|
auth: {
|
|
65437
66059
|
type: "basic",
|
|
65438
66060
|
enabled: options.auth.enabled
|
|
65439
66061
|
}
|
|
65440
66062
|
}));
|
|
65441
|
-
app.get(
|
|
66063
|
+
app.get(paths.health, (c) => c.json({
|
|
65442
66064
|
status: "ok",
|
|
65443
66065
|
transport: "http",
|
|
65444
|
-
|
|
66066
|
+
base: paths.base,
|
|
66067
|
+
mcpPath: paths.mcp,
|
|
66068
|
+
controlPath: paths.control,
|
|
66069
|
+
healthPath: paths.health
|
|
65445
66070
|
}));
|
|
65446
|
-
app.all(
|
|
66071
|
+
app.all(paths.mcp, basicAuth(options.auth), async (c) => {
|
|
65447
66072
|
const sessionId = c.req.header("mcp-session-id");
|
|
65448
66073
|
if (sessionId) {
|
|
65449
66074
|
const existing = sessions.get(sessionId);
|
|
@@ -65474,6 +66099,36 @@ function createHttpServeApp(options, engine, io = {}) {
|
|
|
65474
66099
|
sessions.set(nextSessionId, session);
|
|
65475
66100
|
return session.transport.handleRequest(c);
|
|
65476
66101
|
});
|
|
66102
|
+
app.post(paths.control, basicAuth(options.auth), async (c) => {
|
|
66103
|
+
let request;
|
|
66104
|
+
try {
|
|
66105
|
+
const parsed = await c.req.json();
|
|
66106
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new CapletsError("REQUEST_INVALID", "Control request JSON must be an object");
|
|
66107
|
+
request = parsed;
|
|
66108
|
+
} catch (error) {
|
|
66109
|
+
const safe = toSafeError(error instanceof CapletsError ? error : new CapletsError("REQUEST_INVALID", "Control request body must be valid JSON", error), "REQUEST_INVALID");
|
|
66110
|
+
return c.json({
|
|
66111
|
+
ok: false,
|
|
66112
|
+
error: {
|
|
66113
|
+
code: safe.code,
|
|
66114
|
+
message: safe.message
|
|
66115
|
+
}
|
|
66116
|
+
});
|
|
66117
|
+
}
|
|
66118
|
+
return c.json(await dispatchRemoteCliRequest(request, controlContext(io, writeErr, authFlowStore, c.req.url, paths.control, options.trustProxy, (name) => c.req.header(name))));
|
|
66119
|
+
});
|
|
66120
|
+
app.get(routePath(paths.control, "auth/callback/:flowId"), async (c) => {
|
|
66121
|
+
const flowId = c.req.param("flowId");
|
|
66122
|
+
const result = await dispatchRemoteCliRequest({
|
|
66123
|
+
command: "auth_login_complete",
|
|
66124
|
+
arguments: {
|
|
66125
|
+
flowId,
|
|
66126
|
+
callbackUrl: c.req.url
|
|
66127
|
+
}
|
|
66128
|
+
}, controlContext(io, writeErr, authFlowStore, c.req.url, paths.control, options.trustProxy, (name) => c.req.header(name)));
|
|
66129
|
+
if (!result.ok) writeErr(`Caplets authentication failed for flow ${flowId}: ${result.error.message}\n`);
|
|
66130
|
+
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);
|
|
66131
|
+
});
|
|
65477
66132
|
app.notFound((c) => c.json({ error: "not_found" }, 404));
|
|
65478
66133
|
app.closeCapletsSessions = async () => {
|
|
65479
66134
|
await Promise.allSettled([...sessions.values()].map(async (session) => {
|
|
@@ -65484,19 +66139,66 @@ function createHttpServeApp(options, engine, io = {}) {
|
|
|
65484
66139
|
if (options.warnUnauthenticatedNetwork) writeErr(`Warning: Caplets MCP HTTP server is listening on ${options.host} without authentication.\n`);
|
|
65485
66140
|
return app;
|
|
65486
66141
|
}
|
|
66142
|
+
function controlContext(io, writeErr, authFlowStore, requestUrl, controlPath, trustProxy, header) {
|
|
66143
|
+
return {
|
|
66144
|
+
...io.control,
|
|
66145
|
+
projectCapletsRoot: io.control?.projectCapletsRoot ?? resolveProjectCapletsRoot(),
|
|
66146
|
+
authFlowStore,
|
|
66147
|
+
controlCallbackBaseUrl: new URL(controlPath, publicRequestOrigin(requestUrl, trustProxy, header)).toString(),
|
|
66148
|
+
writeErr
|
|
66149
|
+
};
|
|
66150
|
+
}
|
|
66151
|
+
function publicRequestOrigin(requestUrl, trustProxy, header) {
|
|
66152
|
+
const url = new URL(requestUrl);
|
|
66153
|
+
if (!trustProxy) return `${url.protocol.slice(0, -1)}://${header("host") ?? url.host}`;
|
|
66154
|
+
const forwardedProto = firstForwardedValue(header("x-forwarded-proto"));
|
|
66155
|
+
const forwardedHost = firstForwardedValue(header("x-forwarded-host"));
|
|
66156
|
+
return `${forwardedProto === "http" || forwardedProto === "https" ? forwardedProto : url.protocol.slice(0, -1)}://${forwardedHost ?? header("host") ?? url.host}`;
|
|
66157
|
+
}
|
|
66158
|
+
function firstForwardedValue(value) {
|
|
66159
|
+
return value?.split(",", 1)[0]?.trim() || void 0;
|
|
66160
|
+
}
|
|
65487
66161
|
async function serveHttp(options, engineOptions = {}, writeErr = (value) => process.stderr.write(value)) {
|
|
65488
66162
|
const engine = new CapletsEngine(engineOptions);
|
|
65489
|
-
const app = createHttpServeApp(options, engine, {
|
|
66163
|
+
const app = createHttpServeApp(options, engine, {
|
|
66164
|
+
writeErr,
|
|
66165
|
+
control: {
|
|
66166
|
+
...engineOptions,
|
|
66167
|
+
projectCapletsRoot: projectCapletsRootForEngineOptions(engineOptions)
|
|
66168
|
+
}
|
|
66169
|
+
});
|
|
66170
|
+
const paths = servicePaths(options.path);
|
|
66171
|
+
const origin = `http://${formatHost(options.host)}:${options.port}`;
|
|
66172
|
+
const baseUrl = `${origin}${paths.base === "/" ? "" : paths.base}`;
|
|
65490
66173
|
installHttpSignalHandlers(serve({
|
|
65491
66174
|
fetch: app.fetch,
|
|
65492
66175
|
hostname: options.host,
|
|
65493
66176
|
port: options.port
|
|
65494
66177
|
}, () => {
|
|
65495
|
-
writeErr(`Caplets
|
|
65496
|
-
writeErr(`
|
|
66178
|
+
writeErr(`Caplets HTTP service listening on ${baseUrl}\n`);
|
|
66179
|
+
writeErr(`MCP endpoint: ${origin}${paths.mcp}\n`);
|
|
66180
|
+
writeErr(`Control endpoint: ${origin}${paths.control}\n`);
|
|
66181
|
+
writeErr(`Health check: ${origin}${paths.health}\n`);
|
|
65497
66182
|
writeErr(`Basic Auth: ${options.auth.enabled ? `enabled (user: ${options.auth.user})` : "disabled"}\n`);
|
|
65498
66183
|
}), app, engine, writeErr);
|
|
65499
66184
|
}
|
|
66185
|
+
function projectCapletsRootForEngineOptions(engineOptions) {
|
|
66186
|
+
return engineOptions.projectConfigPath ? resolveProjectCapletsRootForConfigPath(engineOptions.projectConfigPath) : resolveProjectCapletsRoot();
|
|
66187
|
+
}
|
|
66188
|
+
function resolveProjectCapletsRootForConfigPath(projectConfigPath) {
|
|
66189
|
+
return dirname(projectConfigPath);
|
|
66190
|
+
}
|
|
66191
|
+
function routePath(base, path) {
|
|
66192
|
+
return base === "/" ? `/${path}` : `${base}/${path}`;
|
|
66193
|
+
}
|
|
66194
|
+
function servicePaths(base) {
|
|
66195
|
+
return {
|
|
66196
|
+
base,
|
|
66197
|
+
mcp: routePath(base, "mcp"),
|
|
66198
|
+
control: routePath(base, "control"),
|
|
66199
|
+
health: routePath(base, "healthz")
|
|
66200
|
+
};
|
|
66201
|
+
}
|
|
65500
66202
|
async function createHttpSession(engine, sessionId, options, onClose) {
|
|
65501
66203
|
const transport = new StreamableHTTPTransport({
|
|
65502
66204
|
sessionIdGenerator: () => sessionId,
|
|
@@ -65574,7 +66276,8 @@ const HTTP_ONLY_OPTIONS = [
|
|
|
65574
66276
|
"path",
|
|
65575
66277
|
"user",
|
|
65576
66278
|
"password",
|
|
65577
|
-
"allowUnauthenticatedHttp"
|
|
66279
|
+
"allowUnauthenticatedHttp",
|
|
66280
|
+
"trustProxy"
|
|
65578
66281
|
];
|
|
65579
66282
|
function resolveServeOptions(raw, env = process.env) {
|
|
65580
66283
|
const transport = parseTransport(raw.transport ?? "stdio");
|
|
@@ -65583,9 +66286,10 @@ function resolveServeOptions(raw, env = process.env) {
|
|
|
65583
66286
|
if (invalid.length > 0) throw new CapletsError("REQUEST_INVALID", `${invalid.map((key) => `--${key}`).join(", ")} ${invalid.length === 1 ? "is" : "are"} only valid with --transport http`);
|
|
65584
66287
|
return { transport };
|
|
65585
66288
|
}
|
|
65586
|
-
const
|
|
65587
|
-
const
|
|
65588
|
-
const
|
|
66289
|
+
const serverUrl = env.CAPLETS_SERVER_URL ? parseServeServerUrl(nonEmpty(env.CAPLETS_SERVER_URL, "CAPLETS_SERVER_URL")) : void 0;
|
|
66290
|
+
const host = nonEmpty(raw.host, "--host") ?? serverUrlHost(serverUrl) ?? "127.0.0.1";
|
|
66291
|
+
const port = parsePort(raw.port ?? (serverUrl?.port ? Number(serverUrl.port) : 5387));
|
|
66292
|
+
const path = normalizeHttpPath(raw.path ?? serverUrl?.pathname ?? "/");
|
|
65589
66293
|
const userWasExplicit = raw.user !== void 0 || hasEnv(env.CAPLETS_SERVER_USER);
|
|
65590
66294
|
const user = nonEmpty(raw.user, "--user") ?? nonEmpty(env.CAPLETS_SERVER_USER, "CAPLETS_SERVER_USER") ?? "caplets";
|
|
65591
66295
|
const password = nonEmpty(raw.password, "--password") ?? nonEmpty(env.CAPLETS_SERVER_PASSWORD, "CAPLETS_SERVER_PASSWORD");
|
|
@@ -65607,13 +66311,22 @@ function resolveServeOptions(raw, env = process.env) {
|
|
|
65607
66311
|
path,
|
|
65608
66312
|
auth,
|
|
65609
66313
|
warnUnauthenticatedNetwork: !loopback && !auth.enabled,
|
|
65610
|
-
loopback
|
|
66314
|
+
loopback,
|
|
66315
|
+
trustProxy: raw.trustProxy === true
|
|
65611
66316
|
};
|
|
65612
66317
|
}
|
|
65613
66318
|
function isLoopbackHost(host) {
|
|
65614
66319
|
const normalized = host.toLocaleLowerCase();
|
|
65615
66320
|
return normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1";
|
|
65616
66321
|
}
|
|
66322
|
+
function parseServeServerUrl(value) {
|
|
66323
|
+
try {
|
|
66324
|
+
return parseServerBaseUrl(value);
|
|
66325
|
+
} catch (error) {
|
|
66326
|
+
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.");
|
|
66327
|
+
throw error;
|
|
66328
|
+
}
|
|
66329
|
+
}
|
|
65617
66330
|
function parseTransport(value) {
|
|
65618
66331
|
if (value === "stdio" || value === "http") return value;
|
|
65619
66332
|
throw new CapletsError("REQUEST_INVALID", `Expected --transport to be stdio or http, got ${value}`);
|
|
@@ -65628,6 +66341,9 @@ function normalizeHttpPath(value) {
|
|
|
65628
66341
|
if (value.includes("?") || value.includes("#")) throw new CapletsError("REQUEST_INVALID", "HTTP --path must not include a query string or fragment");
|
|
65629
66342
|
return value === "/" ? value : value.replace(/\/+$/u, "");
|
|
65630
66343
|
}
|
|
66344
|
+
function serverUrlHost(url) {
|
|
66345
|
+
return url?.hostname.replace(/^\[(.*)\]$/u, "$1");
|
|
66346
|
+
}
|
|
65631
66347
|
function nonEmpty(value, label) {
|
|
65632
66348
|
if (value === void 0) return;
|
|
65633
66349
|
const trimmed = value.trim();
|
|
@@ -65754,6 +66470,8 @@ async function runCli(args, io = {}) {
|
|
|
65754
66470
|
function createProgram(io = {}) {
|
|
65755
66471
|
const writeOut = io.writeOut ?? ((value) => process.stdout.write(value));
|
|
65756
66472
|
const writeErr = io.writeErr ?? ((value) => process.stderr.write(value));
|
|
66473
|
+
const env = io.env ?? process.env;
|
|
66474
|
+
const currentConfigPath = () => envConfigPath(env);
|
|
65757
66475
|
const setExitCode = io.setExitCode ?? ((code) => {
|
|
65758
66476
|
process.exitCode = code;
|
|
65759
66477
|
});
|
|
@@ -65763,42 +66481,78 @@ function createProgram(io = {}) {
|
|
|
65763
66481
|
writeErr,
|
|
65764
66482
|
outputError: (value, write) => write(value)
|
|
65765
66483
|
});
|
|
65766
|
-
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
|
|
66484
|
+
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) => {
|
|
65767
66485
|
const resolved = resolveServeOptions(options);
|
|
65768
|
-
const configPath =
|
|
66486
|
+
const configPath = currentConfigPath();
|
|
65769
66487
|
await (io.serve ?? ((serveOptions) => serveResolvedCaplets(serveOptions, {
|
|
65770
66488
|
...configPath ? { configPath } : {},
|
|
65771
66489
|
...io.authDir ? { authDir: io.authDir } : {}
|
|
65772
66490
|
}, writeErr)))(resolved);
|
|
65773
66491
|
});
|
|
65774
|
-
program.command("init").description("Create a starter Caplets config file.").option("--force", "overwrite an existing config file").action((options) => {
|
|
65775
|
-
const
|
|
66492
|
+
program.command("init").description("Create a starter Caplets config file.").option("--force", "overwrite an existing config file").action(async (options) => {
|
|
66493
|
+
const remote = remoteClientForCli(io);
|
|
66494
|
+
if (remote) {
|
|
66495
|
+
writeOut(`Created remote Caplets config at ${(await remote.request("init", { force: Boolean(options.force) })).path}\n`);
|
|
66496
|
+
return;
|
|
66497
|
+
}
|
|
66498
|
+
const configPath = currentConfigPath();
|
|
65776
66499
|
writeOut(`Created Caplets config at ${initConfig({
|
|
65777
66500
|
...configPath ? { path: configPath } : {},
|
|
65778
66501
|
force: Boolean(options.force)
|
|
65779
66502
|
})}\n`);
|
|
65780
66503
|
});
|
|
65781
|
-
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) => {
|
|
65782
|
-
const
|
|
66504
|
+
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) => {
|
|
66505
|
+
const includeDisabled = Boolean(options.all);
|
|
66506
|
+
const remote = remoteClientForCli(io);
|
|
66507
|
+
if (remote) {
|
|
66508
|
+
const rows = await remote.request("list", { includeDisabled });
|
|
66509
|
+
if (options.json || options.format === "json") {
|
|
66510
|
+
writeOut(`${JSON.stringify(rows, null, 2)}\n`);
|
|
66511
|
+
return;
|
|
66512
|
+
}
|
|
66513
|
+
writeOut(formatCapletList(rows, options.format ?? "plain"));
|
|
66514
|
+
return;
|
|
66515
|
+
}
|
|
66516
|
+
const rows = listCaplets(loadConfigWithSources(currentConfigPath()), { includeDisabled });
|
|
65783
66517
|
if (options.json || options.format === "json") {
|
|
65784
66518
|
writeOut(`${JSON.stringify(rows, null, 2)}\n`);
|
|
65785
66519
|
return;
|
|
65786
66520
|
}
|
|
65787
66521
|
writeOut(formatCapletList(rows, options.format ?? "plain"));
|
|
65788
66522
|
});
|
|
65789
|
-
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) => {
|
|
66523
|
+
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) => {
|
|
66524
|
+
const remote = remoteClientForCli(io);
|
|
66525
|
+
if (remote) {
|
|
66526
|
+
if (options.global) writeErr("Warning: --global is not supported in remote mode; the server controls the installation destination.\n");
|
|
66527
|
+
const result = await remote.request("install", {
|
|
66528
|
+
repo,
|
|
66529
|
+
capletIds,
|
|
66530
|
+
force: Boolean(options.force)
|
|
66531
|
+
});
|
|
66532
|
+
for (const caplet of result.installed) writeOut(`Installed ${caplet.id} to remote ${caplet.destination}\n`);
|
|
66533
|
+
return;
|
|
66534
|
+
}
|
|
65790
66535
|
const result = installCaplets(repo, {
|
|
65791
66536
|
capletIds,
|
|
65792
66537
|
force: Boolean(options.force),
|
|
65793
|
-
destinationRoot: options.global ? resolveCapletsRoot(resolveConfigPath(
|
|
66538
|
+
destinationRoot: options.global ? resolveCapletsRoot(resolveConfigPath(currentConfigPath())) : resolveProjectCapletsRoot()
|
|
65794
66539
|
});
|
|
65795
66540
|
for (const caplet of result.installed) writeOut(`Installed ${caplet.id} to ${caplet.destination}\n`);
|
|
65796
66541
|
});
|
|
65797
66542
|
const add = program.command("add").description("Add generated Caplet files.");
|
|
65798
|
-
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) => {
|
|
66543
|
+
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) => {
|
|
66544
|
+
const remote = remoteClientForCli(io);
|
|
66545
|
+
if (remote) {
|
|
66546
|
+
writeAddResult(writeOut, "CLI", await remote.request("add", {
|
|
66547
|
+
kind: "cli",
|
|
66548
|
+
id,
|
|
66549
|
+
options: remoteAddOptions(options)
|
|
66550
|
+
}));
|
|
66551
|
+
return;
|
|
66552
|
+
}
|
|
65799
66553
|
const result = addCliCaplet(id, {
|
|
65800
66554
|
...options,
|
|
65801
|
-
destinationRoot: options.global ? resolveCapletsRoot(resolveConfigPath(
|
|
66555
|
+
destinationRoot: options.global ? resolveCapletsRoot(resolveConfigPath(currentConfigPath())) : resolveProjectCapletsRoot()
|
|
65802
66556
|
});
|
|
65803
66557
|
if (result.path) {
|
|
65804
66558
|
writeOut(`Wrote CLI Caplet to ${result.path}\n`);
|
|
@@ -65806,28 +66560,64 @@ function createProgram(io = {}) {
|
|
|
65806
66560
|
}
|
|
65807
66561
|
writeOut(result.text);
|
|
65808
66562
|
});
|
|
65809
|
-
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) => {
|
|
66563
|
+
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) => {
|
|
66564
|
+
const remote = remoteClientForCli(io);
|
|
66565
|
+
if (remote) {
|
|
66566
|
+
writeAddResult(writeOut, "MCP", await remote.request("add", {
|
|
66567
|
+
kind: "mcp",
|
|
66568
|
+
id,
|
|
66569
|
+
options: remoteAddOptions(options)
|
|
66570
|
+
}));
|
|
66571
|
+
return;
|
|
66572
|
+
}
|
|
65810
66573
|
writeAddResult(writeOut, "MCP", addMcpCaplet(id, {
|
|
65811
66574
|
...options,
|
|
65812
|
-
destinationRoot: addDestinationRoot(options)
|
|
66575
|
+
destinationRoot: addDestinationRoot(options, currentConfigPath())
|
|
65813
66576
|
}));
|
|
65814
66577
|
});
|
|
65815
|
-
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) => {
|
|
66578
|
+
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) => {
|
|
66579
|
+
const remote = remoteClientForCli(io);
|
|
66580
|
+
if (remote) {
|
|
66581
|
+
writeAddResult(writeOut, "OpenAPI", await remote.request("add", {
|
|
66582
|
+
kind: "openapi",
|
|
66583
|
+
id,
|
|
66584
|
+
options: remoteAddOptions(options)
|
|
66585
|
+
}));
|
|
66586
|
+
return;
|
|
66587
|
+
}
|
|
65816
66588
|
writeAddResult(writeOut, "OpenAPI", addOpenApiCaplet(id, {
|
|
65817
66589
|
...options,
|
|
65818
|
-
destinationRoot: addDestinationRoot(options)
|
|
66590
|
+
destinationRoot: addDestinationRoot(options, currentConfigPath())
|
|
65819
66591
|
}));
|
|
65820
66592
|
});
|
|
65821
|
-
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) => {
|
|
66593
|
+
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) => {
|
|
66594
|
+
const remote = remoteClientForCli(io);
|
|
66595
|
+
if (remote) {
|
|
66596
|
+
writeAddResult(writeOut, "GraphQL", await remote.request("add", {
|
|
66597
|
+
kind: "graphql",
|
|
66598
|
+
id,
|
|
66599
|
+
options: remoteAddOptions(options)
|
|
66600
|
+
}));
|
|
66601
|
+
return;
|
|
66602
|
+
}
|
|
65822
66603
|
writeAddResult(writeOut, "GraphQL", addGraphqlCaplet(id, {
|
|
65823
66604
|
...options,
|
|
65824
|
-
destinationRoot: addDestinationRoot(options)
|
|
66605
|
+
destinationRoot: addDestinationRoot(options, currentConfigPath())
|
|
65825
66606
|
}));
|
|
65826
66607
|
});
|
|
65827
|
-
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) => {
|
|
66608
|
+
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) => {
|
|
66609
|
+
const remote = remoteClientForCli(io);
|
|
66610
|
+
if (remote) {
|
|
66611
|
+
writeAddResult(writeOut, "HTTP", await remote.request("add", {
|
|
66612
|
+
kind: "http",
|
|
66613
|
+
id,
|
|
66614
|
+
options: remoteAddOptions(options)
|
|
66615
|
+
}));
|
|
66616
|
+
return;
|
|
66617
|
+
}
|
|
65828
66618
|
writeAddResult(writeOut, "HTTP", addHttpCaplet(id, {
|
|
65829
66619
|
...options,
|
|
65830
|
-
destinationRoot: addDestinationRoot(options)
|
|
66620
|
+
destinationRoot: addDestinationRoot(options, currentConfigPath())
|
|
65831
66621
|
}));
|
|
65832
66622
|
});
|
|
65833
66623
|
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) => {
|
|
@@ -65836,6 +66626,8 @@ function createProgram(io = {}) {
|
|
|
65836
66626
|
writeErr,
|
|
65837
66627
|
setExitCode,
|
|
65838
66628
|
authDir: io.authDir,
|
|
66629
|
+
env,
|
|
66630
|
+
remote: remoteClientForCli(io),
|
|
65839
66631
|
format: options.format
|
|
65840
66632
|
});
|
|
65841
66633
|
});
|
|
@@ -65845,6 +66637,8 @@ function createProgram(io = {}) {
|
|
|
65845
66637
|
writeErr,
|
|
65846
66638
|
setExitCode,
|
|
65847
66639
|
authDir: io.authDir,
|
|
66640
|
+
env,
|
|
66641
|
+
remote: remoteClientForCli(io),
|
|
65848
66642
|
format: options.format
|
|
65849
66643
|
});
|
|
65850
66644
|
});
|
|
@@ -65854,6 +66648,8 @@ function createProgram(io = {}) {
|
|
|
65854
66648
|
writeErr,
|
|
65855
66649
|
setExitCode,
|
|
65856
66650
|
authDir: io.authDir,
|
|
66651
|
+
env,
|
|
66652
|
+
remote: remoteClientForCli(io),
|
|
65857
66653
|
format: options.format
|
|
65858
66654
|
});
|
|
65859
66655
|
});
|
|
@@ -65870,6 +66666,8 @@ function createProgram(io = {}) {
|
|
|
65870
66666
|
writeErr,
|
|
65871
66667
|
setExitCode,
|
|
65872
66668
|
authDir: io.authDir,
|
|
66669
|
+
env,
|
|
66670
|
+
remote: remoteClientForCli(io),
|
|
65873
66671
|
format: options.format
|
|
65874
66672
|
});
|
|
65875
66673
|
});
|
|
@@ -65883,6 +66681,8 @@ function createProgram(io = {}) {
|
|
|
65883
66681
|
writeErr,
|
|
65884
66682
|
setExitCode,
|
|
65885
66683
|
authDir: io.authDir,
|
|
66684
|
+
env,
|
|
66685
|
+
remote: remoteClientForCli(io),
|
|
65886
66686
|
format: options.format
|
|
65887
66687
|
});
|
|
65888
66688
|
});
|
|
@@ -65898,15 +66698,17 @@ function createProgram(io = {}) {
|
|
|
65898
66698
|
writeErr,
|
|
65899
66699
|
setExitCode,
|
|
65900
66700
|
authDir: io.authDir,
|
|
66701
|
+
env,
|
|
66702
|
+
remote: remoteClientForCli(io),
|
|
65901
66703
|
format: options.format
|
|
65902
66704
|
});
|
|
65903
66705
|
});
|
|
65904
66706
|
const config = program.command("config").description("Inspect Caplets config locations.");
|
|
65905
66707
|
config.command("path").description("Print the effective user config path.").action(() => {
|
|
65906
|
-
writeOut(`${resolveConfigPath(
|
|
66708
|
+
writeOut(`${resolveConfigPath(currentConfigPath())}\n`);
|
|
65907
66709
|
});
|
|
65908
66710
|
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) => {
|
|
65909
|
-
const paths = resolveCliConfigPaths(
|
|
66711
|
+
const paths = resolveCliConfigPaths(currentConfigPath(), io.authDir);
|
|
65910
66712
|
if (options.json || options.format === "json") {
|
|
65911
66713
|
writeOut(`${JSON.stringify(paths, null, 2)}\n`);
|
|
65912
66714
|
return;
|
|
@@ -65915,7 +66717,19 @@ function createProgram(io = {}) {
|
|
|
65915
66717
|
});
|
|
65916
66718
|
const auth = program.command("auth").description("Manage OAuth credentials for remote servers.");
|
|
65917
66719
|
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) => {
|
|
65918
|
-
const
|
|
66720
|
+
const remote = remoteClientForCli(io);
|
|
66721
|
+
if (remote) {
|
|
66722
|
+
const started = await remote.request("auth_login_start", { server: serverId });
|
|
66723
|
+
if (started.authorizationUrl) {
|
|
66724
|
+
writeOut(`Open this URL to authorize ${serverId}:\n${started.authorizationUrl}\n`);
|
|
66725
|
+
if (options.open !== false) await openBrowser(started.authorizationUrl);
|
|
66726
|
+
writeOut("Complete authentication in your browser. The server callback will store credentials.\n");
|
|
66727
|
+
return;
|
|
66728
|
+
}
|
|
66729
|
+
if (started.authenticated) writeOut(`Authenticated \`${serverId}\`.\n`);
|
|
66730
|
+
return;
|
|
66731
|
+
}
|
|
66732
|
+
const configPath = currentConfigPath();
|
|
65919
66733
|
await loginAuth(serverId, {
|
|
65920
66734
|
noOpen: options.open === false,
|
|
65921
66735
|
writeOut,
|
|
@@ -65924,27 +66738,78 @@ function createProgram(io = {}) {
|
|
|
65924
66738
|
...io.authDir ? { authDir: io.authDir } : {}
|
|
65925
66739
|
});
|
|
65926
66740
|
});
|
|
65927
|
-
auth.command("logout").description("Delete stored OAuth credentials for a server.").argument("<server>", "configured server ID").action((serverId) => {
|
|
65928
|
-
const
|
|
66741
|
+
auth.command("logout").description("Delete stored OAuth credentials for a server.").argument("<server>", "configured server ID").action(async (serverId) => {
|
|
66742
|
+
const remote = remoteClientForCli(io);
|
|
66743
|
+
if (remote) {
|
|
66744
|
+
writeOut((await remote.request("auth_logout", { server: serverId })).deleted ? `Deleted remote OAuth credentials for \`${serverId}\`.\n` : `No remote OAuth credentials found for \`${serverId}\`.\n`);
|
|
66745
|
+
return;
|
|
66746
|
+
}
|
|
66747
|
+
const configPath = currentConfigPath();
|
|
65929
66748
|
logoutAuth(serverId, {
|
|
65930
66749
|
writeOut,
|
|
65931
66750
|
...configPath ? { configPath } : {},
|
|
65932
66751
|
...io.authDir ? { authDir: io.authDir } : {}
|
|
65933
66752
|
});
|
|
65934
66753
|
});
|
|
65935
|
-
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) => {
|
|
65936
|
-
const configPath =
|
|
66754
|
+
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) => {
|
|
66755
|
+
const configPath = currentConfigPath();
|
|
66756
|
+
const format = options.json || options.format === "json" ? "json" : options.format ?? "plain";
|
|
66757
|
+
const remote = remoteClientForCli(io);
|
|
66758
|
+
if (remote) {
|
|
66759
|
+
const rows = await remote.request("auth_list", {});
|
|
66760
|
+
if (format === "json") {
|
|
66761
|
+
writeOut(`${JSON.stringify(rows, null, 2)}\n`);
|
|
66762
|
+
return;
|
|
66763
|
+
}
|
|
66764
|
+
writeOut(formatAuthRows(rows, format));
|
|
66765
|
+
return;
|
|
66766
|
+
}
|
|
65937
66767
|
listAuth({
|
|
65938
66768
|
writeOut,
|
|
65939
|
-
format
|
|
66769
|
+
format,
|
|
65940
66770
|
...configPath ? { configPath } : {},
|
|
65941
66771
|
...io.authDir ? { authDir: io.authDir } : {}
|
|
65942
66772
|
});
|
|
65943
66773
|
});
|
|
65944
66774
|
return program;
|
|
65945
66775
|
}
|
|
65946
|
-
function envConfigPath() {
|
|
65947
|
-
return
|
|
66776
|
+
function envConfigPath(env) {
|
|
66777
|
+
return env.CAPLETS_CONFIG?.trim() || void 0;
|
|
66778
|
+
}
|
|
66779
|
+
function remoteClientForCli(io) {
|
|
66780
|
+
const env = io.env ?? process.env;
|
|
66781
|
+
if (resolveCapletsMode({}, env).mode !== "remote") return;
|
|
66782
|
+
return new RemoteControlClient(resolveCapletsServer(io.fetch ? { fetch: io.fetch } : {}, env));
|
|
66783
|
+
}
|
|
66784
|
+
async function openBrowser(url) {
|
|
66785
|
+
const { spawn } = await import("node:child_process");
|
|
66786
|
+
spawn(process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open", process.platform === "win32" ? [
|
|
66787
|
+
"/c",
|
|
66788
|
+
"start",
|
|
66789
|
+
"",
|
|
66790
|
+
url
|
|
66791
|
+
] : [url], {
|
|
66792
|
+
stdio: "ignore",
|
|
66793
|
+
detached: true
|
|
66794
|
+
}).unref();
|
|
66795
|
+
}
|
|
66796
|
+
function remoteCommandForOperation(operation) {
|
|
66797
|
+
switch (operation) {
|
|
66798
|
+
case "get_caplet":
|
|
66799
|
+
case "check_backend":
|
|
66800
|
+
case "list_tools":
|
|
66801
|
+
case "search_tools":
|
|
66802
|
+
case "get_tool":
|
|
66803
|
+
case "call_tool": return operation;
|
|
66804
|
+
default: return;
|
|
66805
|
+
}
|
|
66806
|
+
}
|
|
66807
|
+
function remoteAddOptions(options) {
|
|
66808
|
+
const { output, print, global, destinationRoot, ...remoteOptions } = options;
|
|
66809
|
+
if (global) throw new CapletsError("REQUEST_INVALID", "--global is not supported in remote mode; the server controls the add destination.");
|
|
66810
|
+
if (print) throw new CapletsError("REQUEST_INVALID", "--print is not supported in remote mode; the server controls add output.");
|
|
66811
|
+
if (output !== void 0) throw new CapletsError("REQUEST_INVALID", "--output is not supported in remote mode; the server controls the add destination.");
|
|
66812
|
+
return remoteOptions;
|
|
65948
66813
|
}
|
|
65949
66814
|
function collect(value, previous) {
|
|
65950
66815
|
previous.push(value);
|
|
@@ -65987,7 +66852,21 @@ function isPlainObject(value) {
|
|
|
65987
66852
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
65988
66853
|
}
|
|
65989
66854
|
async function executeOperation(caplet, request, io) {
|
|
65990
|
-
const
|
|
66855
|
+
const command = remoteCommandForOperation(request.operation);
|
|
66856
|
+
if (io.remote && command) {
|
|
66857
|
+
const result = await io.remote.request(command, {
|
|
66858
|
+
caplet,
|
|
66859
|
+
request
|
|
66860
|
+
});
|
|
66861
|
+
const output = cliOutputForOperation(result, {
|
|
66862
|
+
...request,
|
|
66863
|
+
caplet
|
|
66864
|
+
}, io.format ?? "markdown");
|
|
66865
|
+
io.writeOut(typeof output === "string" ? `${output}\n` : `${JSON.stringify(output, null, 2)}\n`);
|
|
66866
|
+
if (isPlainObject(result) && result.isError === true) io.setExitCode(1);
|
|
66867
|
+
return;
|
|
66868
|
+
}
|
|
66869
|
+
const configPath = envConfigPath(io.env ?? process.env);
|
|
65991
66870
|
const engine = new CapletsEngine({
|
|
65992
66871
|
...configPath ? { configPath } : {},
|
|
65993
66872
|
...io.authDir ? { authDir: io.authDir } : {},
|
|
@@ -66233,19 +67112,19 @@ function schemaSummary(schema) {
|
|
|
66233
67112
|
required.length > 0 ? `required ${required.join(", ")}` : "no required fields"
|
|
66234
67113
|
].filter((part) => Boolean(part)).join("; ");
|
|
66235
67114
|
}
|
|
66236
|
-
function addDestinationRoot(options) {
|
|
66237
|
-
return options.global ? resolveCapletsRoot(resolveConfigPath(
|
|
67115
|
+
function addDestinationRoot(options, configPath) {
|
|
67116
|
+
return options.global ? resolveCapletsRoot(resolveConfigPath(configPath)) : resolveProjectCapletsRoot();
|
|
66238
67117
|
}
|
|
66239
67118
|
function writeAddResult(writeOut, label, result) {
|
|
66240
67119
|
if (result.path) {
|
|
66241
|
-
writeOut(`Wrote ${label} Caplet to ${result.path}\n`);
|
|
67120
|
+
writeOut(`Wrote ${result.remote ? "remote " : ""}${label} Caplet to ${result.path}\n`);
|
|
66242
67121
|
return;
|
|
66243
67122
|
}
|
|
66244
67123
|
writeOut(result.text);
|
|
66245
67124
|
}
|
|
66246
67125
|
//#endregion
|
|
66247
67126
|
//#region package.json
|
|
66248
|
-
var version = "0.
|
|
67127
|
+
var version = "0.16.0";
|
|
66249
67128
|
//#endregion
|
|
66250
67129
|
//#region src/index.ts
|
|
66251
67130
|
async function main() {
|