caplets 0.15.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.
Files changed (3) hide show
  1. package/README.md +110 -14
  2. package/dist/index.js +2890 -775
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -9,441 +9,25 @@ 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";
15
16
  import { Readable } from "stream";
16
17
  import crypto$1 from "crypto";
17
- //#region ../core/dist/generated-tool-input-schema.js
18
- const operations = [
19
- "get_caplet",
20
- "check_backend",
21
- "list_tools",
22
- "search_tools",
23
- "get_tool",
24
- "call_tool"
25
- ];
26
- const generatedToolInputDescriptions = {
27
- operation: "Wrapper operation: get_caplet, check_backend, list_tools, search_tools, get_tool, or call_tool.",
28
- query: "Required for search_tools only.",
29
- limit: "Optional search_tools result limit.",
30
- tool: "Exact downstream tool name for get_tool or call_tool.",
31
- arguments: "Required JSON object for call_tool arguments/downstream inputs.",
32
- fields: "Optional call_tool structured output paths when outputSchema allows it."
18
+ //#region \0rolldown/runtime.js
19
+ var __defProp$1 = Object.defineProperty;
20
+ var __exportAll$1 = (all, no_symbols) => {
21
+ let target = {};
22
+ for (var name in all) __defProp$1(target, name, {
23
+ get: all[name],
24
+ enumerable: true
25
+ });
26
+ if (!no_symbols) __defProp$1(target, Symbol.toStringTag, { value: "Module" });
27
+ return target;
33
28
  };
34
- function generatedToolInputJsonSchema() {
35
- return {
36
- type: "object",
37
- properties: {
38
- operation: {
39
- type: "string",
40
- enum: operations,
41
- description: generatedToolInputDescriptions.operation
42
- },
43
- query: {
44
- type: "string",
45
- description: generatedToolInputDescriptions.query
46
- },
47
- limit: {
48
- type: "integer",
49
- minimum: 1,
50
- description: generatedToolInputDescriptions.limit
51
- },
52
- tool: {
53
- type: "string",
54
- description: generatedToolInputDescriptions.tool
55
- },
56
- arguments: {
57
- type: "object",
58
- description: generatedToolInputDescriptions.arguments
59
- },
60
- fields: {
61
- type: "array",
62
- items: {
63
- type: "string",
64
- minLength: 1
65
- },
66
- minItems: 1,
67
- description: generatedToolInputDescriptions.fields
68
- }
69
- },
70
- required: ["operation"],
71
- additionalProperties: false
72
- };
73
- }
74
29
  //#endregion
75
- //#region ../core/dist/engine-Brwid_mq.js
76
- var __create = Object.create;
77
- var __defProp = Object.defineProperty;
78
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
79
- var __getOwnPropNames = Object.getOwnPropertyNames;
80
- var __getProtoOf = Object.getPrototypeOf;
81
- var __hasOwnProp = Object.prototype.hasOwnProperty;
82
- var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
83
- var __copyProps = (to, from, except, desc) => {
84
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
85
- key = keys[i];
86
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
87
- get: ((k) => from[k]).bind(null, key),
88
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
89
- });
90
- }
91
- return to;
92
- };
93
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
94
- value: mod,
95
- enumerable: true
96
- }) : target, mod));
97
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
98
- var CapletsError = class extends Error {
99
- code;
100
- details;
101
- constructor(code, message, details) {
102
- super(message);
103
- this.name = "CapletsError";
104
- this.code = code;
105
- this.details = details;
106
- }
107
- };
108
- const SECRET_KEY_PATTERN = /(token|secret|authorization|auth|api[-_]?key|password|credential|clientsecret|client_secret|code|refresh)/i;
109
- const SECRET_VALUE_PATTERN = /(bearer\s+)[a-z0-9._~+/=-]+|([?&](?:access_token|refresh_token|token|code)=)[^&\s]+/gi;
110
- function redactSecrets(value) {
111
- if (typeof value === "string") return value.replace(SECRET_VALUE_PATTERN, "$1$2[REDACTED]");
112
- if (Array.isArray(value)) return value.map((item) => redactSecrets(item));
113
- if (value && typeof value === "object") {
114
- const redacted = {};
115
- for (const [key, nested] of Object.entries(value)) redacted[key] = SECRET_KEY_PATTERN.test(key) ? "[REDACTED]" : redactSecrets(nested);
116
- return redacted;
117
- }
118
- return value;
119
- }
120
- function toSafeError(error, fallback = "INTERNAL_ERROR") {
121
- if (error instanceof CapletsError) return {
122
- code: error.code,
123
- message: String(redactSecrets(error.message)),
124
- ...error.details === void 0 ? {} : { details: redactSecrets(error.details) }
125
- };
126
- if (error instanceof Error) return {
127
- code: fallback,
128
- message: String(redactSecrets(error.message))
129
- };
130
- return {
131
- code: fallback,
132
- message: String(redactSecrets(error))
133
- };
134
- }
135
- function errorResult(error, fallback) {
136
- const safe = toSafeError(error, fallback);
137
- return {
138
- isError: true,
139
- content: [{
140
- type: "text",
141
- text: `${safe.code}: ${safe.message}`
142
- }],
143
- structuredContent: { error: safe }
144
- };
145
- }
146
- function textContent(text) {
147
- return text ? [{
148
- type: "text",
149
- text
150
- }] : [];
151
- }
152
- function compactJsonText(value, maxLength = 600) {
153
- return compactText(JSON.stringify(value) ?? String(value), maxLength);
154
- }
155
- function compactText(value, maxLength = 600) {
156
- const collapsed = value.replace(/\s+/gu, " ").trim();
157
- return collapsed.length > maxLength ? `${collapsed.slice(0, maxLength - 1).trimEnd()}…` : collapsed;
158
- }
159
- function resultKeys(value) {
160
- if (!value || typeof value !== "object" || Array.isArray(value)) return "scalar result";
161
- const keys = Object.keys(value).filter((key) => key !== "elapsedMs");
162
- return keys.length > 0 ? `structured keys: ${keys.join(", ")}` : "empty structured result";
163
- }
164
- function statusSummary(value) {
165
- if (!value || typeof value !== "object" || Array.isArray(value)) return compactJsonText(value);
166
- const record = value;
167
- return [
168
- typeof record.status === "number" ? `status ${record.status}` : void 0,
169
- typeof record.statusText === "string" && record.statusText ? record.statusText : void 0,
170
- typeof record.exitCode === "number" ? `exit ${record.exitCode}` : void 0,
171
- "body" in record ? "body" : void 0,
172
- "json" in record ? "json" : void 0,
173
- typeof record.stdout === "string" && record.stdout ? "stdout" : void 0,
174
- typeof record.stderr === "string" && record.stderr ? "stderr" : void 0
175
- ].filter((part) => Boolean(part)).join("; ") || resultKeys(record);
176
- }
177
- function compactStructuredContent(value) {
178
- return textContent(statusSummary(value));
179
- }
180
- function searchToolList(tools, query, limit, compact) {
181
- const tokens = query.toLocaleLowerCase().split(/\s+/).filter(Boolean);
182
- return tools.filter((tool) => {
183
- const haystack = `${tool.name}\n${tool.description ?? ""}`.toLocaleLowerCase();
184
- return tokens.some((token) => haystack.includes(token));
185
- }).sort((left, right) => left.name.localeCompare(right.name)).slice(0, limit).map(compact);
186
- }
187
- const DEFAULT_INPUT_SCHEMA$1 = {
188
- type: "object",
189
- additionalProperties: true
190
- };
191
- var CliToolsManager = class {
192
- registry;
193
- constructor(registry) {
194
- this.registry = registry;
195
- }
196
- updateRegistry(registry) {
197
- this.registry = registry;
198
- }
199
- invalidate(_serverId) {}
200
- async checkTools(config) {
201
- const startedAt = Date.now();
202
- try {
203
- for (const action of actionsFor(config)) {
204
- const cwdTemplate = action.cwd ?? config.cwd;
205
- if (cwdTemplate && !cwdTemplate.includes("$input")) {
206
- if (!existsSync(interpolateRequiredString(cwdTemplate, {}, "cwd"))) throw new CapletsError("CONFIG_INVALID", `CLI cwd does not exist for ${config.server}/${action.name}`);
207
- }
208
- if (!action.command.includes("$input")) resolveCommandPath(action.command);
209
- }
210
- this.registry.setStatus(config.server, "available");
211
- return {
212
- id: config.server,
213
- status: "available",
214
- toolCount: Object.keys(config.actions).length,
215
- elapsedMs: Date.now() - startedAt
216
- };
217
- } catch (error) {
218
- const safe = toSafeError(error, "SERVER_UNAVAILABLE");
219
- this.registry.setStatus(config.server, "unavailable", safe);
220
- return {
221
- id: config.server,
222
- status: "unavailable",
223
- elapsedMs: Date.now() - startedAt,
224
- error: safe
225
- };
226
- }
227
- }
228
- async listTools(config) {
229
- return actionsFor(config).map((action) => this.toTool(action));
230
- }
231
- async getTool(config, toolName) {
232
- return this.toTool(getAction(config, toolName));
233
- }
234
- async callTool(config, toolName, args) {
235
- const action = getAction(config, toolName);
236
- validateInput(action, args);
237
- const execution = resolveExecution(config, action, args);
238
- const startedAt = Date.now();
239
- const controller = new AbortController();
240
- const timeout = setTimeout(() => controller.abort(), execution.timeoutMs);
241
- try {
242
- const result = await spawnCommand(execution, controller.signal, () => Date.now() - startedAt);
243
- const structured = parseStructuredResult(action, result, result.exitCode !== 0);
244
- return {
245
- content: compactStructuredContent(structured),
246
- structuredContent: structured,
247
- isError: result.exitCode !== 0
248
- };
249
- } catch (error) {
250
- if (isAbortError$1(error)) throw new CapletsError("TOOL_CALL_TIMEOUT", `CLI tool timed out for ${config.server}/${toolName}`);
251
- if (error instanceof CapletsError) throw error;
252
- throw new CapletsError("DOWNSTREAM_TOOL_ERROR", `CLI tool failed for ${config.server}/${toolName}`, toSafeError(error));
253
- } finally {
254
- clearTimeout(timeout);
255
- }
256
- }
257
- compact(config, tool) {
258
- return {
259
- id: config.server,
260
- tool: tool.name,
261
- ...tool.description ? { description: tool.description } : {},
262
- hasInputSchema: Boolean(tool.inputSchema),
263
- hasOutputSchema: Boolean(tool.outputSchema)
264
- };
265
- }
266
- search(config, tools, query, limit) {
267
- return searchToolList(tools, query, limit, (tool) => this.compact(config, tool));
268
- }
269
- toTool(action) {
270
- return {
271
- name: action.name,
272
- ...action.description ? { description: action.description } : {},
273
- inputSchema: action.inputSchema ?? DEFAULT_INPUT_SCHEMA$1,
274
- ...action.outputSchema ? { outputSchema: action.outputSchema } : {},
275
- ...action.annotations ? { annotations: action.annotations } : {}
276
- };
277
- }
278
- };
279
- function actionsFor(config) {
280
- return Object.entries(config.actions).map(([name, action]) => ({
281
- name,
282
- ...action
283
- })).sort((left, right) => left.name.localeCompare(right.name));
284
- }
285
- function getAction(config, toolName) {
286
- const actions = actionsFor(config);
287
- const action = actions.find((candidate) => candidate.name === toolName);
288
- if (!action) throw new CapletsError("TOOL_NOT_FOUND", `Tool ${toolName} was not found on ${config.server}`, {
289
- server: config.server,
290
- tool: toolName,
291
- suggestions: actions.map((candidate) => candidate.name).filter((name) => name.toLocaleLowerCase().includes(toolName.toLocaleLowerCase()[0] ?? "")).slice(0, 5)
292
- });
293
- return action;
294
- }
295
- function resolveExecution(config, action, input) {
296
- const cwd = interpolateString(action.cwd ?? config.cwd, input, "cwd");
297
- if (cwd && !existsSync(cwd)) throw new CapletsError("CONFIG_INVALID", `CLI cwd does not exist for ${config.server}/${action.name}`);
298
- const env = {
299
- ...process.env,
300
- ...resolveEnv(config.env, input),
301
- ...resolveEnv(action.env, input)
302
- };
303
- return {
304
- command: interpolateString(action.command, input, "command") ?? action.command,
305
- args: (action.args ?? []).map((arg, index) => interpolateRequiredString(arg, input, `args.${index}`)),
306
- ...cwd ? { cwd } : {},
307
- env,
308
- timeoutMs: action.timeoutMs ?? config.timeoutMs,
309
- maxOutputBytes: action.maxOutputBytes ?? config.maxOutputBytes
310
- };
311
- }
312
- function resolveEnv(env, input) {
313
- if (!env) return {};
314
- return Object.fromEntries(Object.entries(env).map(([key, value]) => [key, interpolateRequiredString(value, input, `env.${key}`)]));
315
- }
316
- function interpolateString(value, input, field) {
317
- return value === void 0 ? void 0 : interpolateRequiredString(value, input, field);
318
- }
319
- function interpolateRequiredString(value, input, field) {
320
- return value.replace(/\$input(?:\.([A-Za-z0-9_.-]+))?/g, (_match, path) => {
321
- if (!path) throw new CapletsError("REQUEST_INVALID", `CLI ${field} cannot interpolate $input directly`);
322
- const selected = valueAtPath$1(input, path);
323
- if (selected === void 0 || selected === null) throw new CapletsError("REQUEST_INVALID", `CLI ${field} references missing input ${path}`);
324
- if (typeof selected !== "string" && typeof selected !== "number" && typeof selected !== "boolean") throw new CapletsError("REQUEST_INVALID", `CLI ${field} input ${path} must be a string, number, or boolean`);
325
- return String(selected);
326
- });
327
- }
328
- function valueAtPath$1(input, path) {
329
- let current = input;
330
- for (const segment of path.split(".")) {
331
- if (!current || typeof current !== "object" || Array.isArray(current)) return;
332
- current = current[segment];
333
- }
334
- return current;
335
- }
336
- function validateInput(action, input) {
337
- const schema = action.inputSchema;
338
- if (!schema) return;
339
- const required = Array.isArray(schema.required) ? schema.required : [];
340
- for (const key of required) if (typeof key === "string" && (input[key] === void 0 || input[key] === null)) throw new CapletsError("REQUEST_INVALID", `CLI tool ${action.name} requires input ${key}`);
341
- const properties = isPlainObject$7(schema.properties) ? schema.properties : {};
342
- for (const [key, property] of Object.entries(properties)) {
343
- if (input[key] === void 0 || !isPlainObject$7(property) || typeof property.type !== "string") continue;
344
- if (!matchesJsonType(input[key], property.type)) throw new CapletsError("REQUEST_INVALID", `CLI tool ${action.name} input ${key} must be ${property.type}`);
345
- }
346
- }
347
- function matchesJsonType(value, type) {
348
- switch (type) {
349
- case "string": return typeof value === "string";
350
- case "number":
351
- case "integer": return typeof value === "number" && (type === "number" || Number.isInteger(value));
352
- case "boolean": return typeof value === "boolean";
353
- case "object": return isPlainObject$7(value);
354
- case "array": return Array.isArray(value);
355
- case "null": return value === null;
356
- default: return true;
357
- }
358
- }
359
- function spawnCommand(execution, signal, elapsedMs) {
360
- return new Promise((resolve, reject) => {
361
- let stdout = "";
362
- let stderr = "";
363
- let outputBytes = 0;
364
- const child = spawn(execution.command, execution.args, {
365
- cwd: execution.cwd,
366
- env: execution.env,
367
- shell: false,
368
- signal,
369
- windowsHide: true
370
- });
371
- child.on("error", reject);
372
- const append = (stream, chunk) => {
373
- outputBytes += chunk.byteLength;
374
- if (outputBytes > execution.maxOutputBytes) {
375
- child.kill();
376
- reject(new CapletsError("DOWNSTREAM_TOOL_ERROR", "CLI tool output exceeded byte limit"));
377
- return;
378
- }
379
- if (stream === "stdout") stdout += chunk.toString("utf8");
380
- else stderr += chunk.toString("utf8");
381
- };
382
- child.stdout?.on("data", (chunk) => append("stdout", chunk));
383
- child.stderr?.on("data", (chunk) => append("stderr", chunk));
384
- child.on("close", (exitCode, childSignal) => {
385
- resolve({
386
- exitCode,
387
- signal: childSignal,
388
- stdout,
389
- stderr,
390
- elapsedMs: elapsedMs()
391
- });
392
- });
393
- });
394
- }
395
- function parseStructuredResult(action, result, tolerateInvalidJson = false) {
396
- const structured = {
397
- exitCode: result.exitCode,
398
- stdout: result.stdout,
399
- stderr: result.stderr,
400
- elapsedMs: result.elapsedMs,
401
- ...result.signal ? { signal: result.signal } : {}
402
- };
403
- if (action.output?.type === "json" && result.stdout.trim()) try {
404
- structured.json = JSON.parse(result.stdout);
405
- } catch (error) {
406
- if (tolerateInvalidJson) {
407
- structured.jsonParseError = toSafeError(error);
408
- return structured;
409
- }
410
- throw new CapletsError("DOWNSTREAM_PROTOCOL_ERROR", `CLI tool ${action.name} stdout was not valid JSON`, toSafeError(error));
411
- }
412
- return structured;
413
- }
414
- function resolveCommandPath(command) {
415
- if (isAbsolute(command) || /[\\/]/.test(command)) {
416
- assertExecutable(command);
417
- return command;
418
- }
419
- for (const directory of (process.env.PATH ?? "").split(delimiter)) {
420
- if (!directory) continue;
421
- const candidate = join(directory, command);
422
- if (isExecutable(candidate)) return candidate;
423
- if (process.platform === "win32") for (const ext of (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";")) {
424
- const windowsCandidate = join(directory, `${command}${ext.toLowerCase()}`);
425
- if (isExecutable(windowsCandidate)) return windowsCandidate;
426
- }
427
- }
428
- throw new CapletsError("SERVER_UNAVAILABLE", `CLI command ${command} was not found on PATH`);
429
- }
430
- function assertExecutable(path) {
431
- if (!isExecutable(path)) throw new CapletsError("SERVER_UNAVAILABLE", `CLI command ${path} is not executable`);
432
- }
433
- function isExecutable(path) {
434
- try {
435
- accessSync(path, constants.X_OK);
436
- return true;
437
- } catch {
438
- return false;
439
- }
440
- }
441
- function isAbortError$1(error) {
442
- return error instanceof Error && error.name === "AbortError";
443
- }
444
- function isPlainObject$7(value) {
445
- return value !== null && typeof value === "object" && !Array.isArray(value);
446
- }
30
+ //#region ../core/dist/generated-tool-input-schema-B6rce396.js
447
31
  var _a$1;
448
32
  /** A special constant with type `never` */
449
33
  const NEVER = /* @__PURE__ */ Object.freeze({ status: "aborted" });
@@ -587,7 +171,7 @@ const allowsEval = /* @__PURE__ */ cached(() => {
587
171
  return false;
588
172
  }
589
173
  });
590
- function isPlainObject$6(o) {
174
+ function isPlainObject$8(o) {
591
175
  if (isObject(o) === false) return false;
592
176
  const ctor = o.constructor;
593
177
  if (ctor === void 0) return true;
@@ -598,7 +182,7 @@ function isPlainObject$6(o) {
598
182
  return true;
599
183
  }
600
184
  function shallowClone(o) {
601
- if (isPlainObject$6(o)) return { ...o };
185
+ if (isPlainObject$8(o)) return { ...o };
602
186
  if (Array.isArray(o)) return [...o];
603
187
  if (o instanceof Map) return new Map(o);
604
188
  if (o instanceof Set) return new Set(o);
@@ -681,7 +265,7 @@ function omit(schema, mask) {
681
265
  }));
682
266
  }
683
267
  function extend(schema, shape) {
684
- if (!isPlainObject$6(shape)) throw new Error("Invalid input to extend: expected a plain object");
268
+ if (!isPlainObject$8(shape)) throw new Error("Invalid input to extend: expected a plain object");
685
269
  const checks = schema._zod.def.checks;
686
270
  if (checks && checks.length > 0) {
687
271
  const existingShape = schema._zod.def.shape;
@@ -697,7 +281,7 @@ function extend(schema, shape) {
697
281
  } }));
698
282
  }
699
283
  function safeExtend(schema, shape) {
700
- if (!isPlainObject$6(shape)) throw new Error("Invalid input to safeExtend: expected a plain object");
284
+ if (!isPlainObject$8(shape)) throw new Error("Invalid input to safeExtend: expected a plain object");
701
285
  return clone(schema, mergeDefs(schema._zod.def, { get shape() {
702
286
  const _shape = {
703
287
  ...schema._zod.def.shape,
@@ -888,7 +472,7 @@ const _parse = (_Err) => (schema, value, _ctx, _params) => {
888
472
  }
889
473
  return result.value;
890
474
  };
891
- const parse$3 = /* @__PURE__ */ _parse($ZodRealError);
475
+ const parse$1 = /* @__PURE__ */ _parse($ZodRealError);
892
476
  const _parseAsync = (_Err) => async (schema, value, _ctx, params) => {
893
477
  const ctx = _ctx ? {
894
478
  ..._ctx,
@@ -925,7 +509,7 @@ const _safeParse = (_Err) => (schema, value, _ctx) => {
925
509
  data: result.value
926
510
  };
927
511
  };
928
- const safeParse$2 = /* @__PURE__ */ _safeParse($ZodRealError);
512
+ const safeParse$1 = /* @__PURE__ */ _safeParse($ZodRealError);
929
513
  const _safeParseAsync = (_Err) => async (schema, value, _ctx) => {
930
514
  const ctx = _ctx ? {
931
515
  ..._ctx,
@@ -944,7 +528,7 @@ const _safeParseAsync = (_Err) => async (schema, value, _ctx) => {
944
528
  data: result.value
945
529
  };
946
530
  };
947
- const safeParseAsync$2 = /* @__PURE__ */ _safeParseAsync($ZodRealError);
531
+ const safeParseAsync$1 = /* @__PURE__ */ _safeParseAsync($ZodRealError);
948
532
  const _encode = (_Err) => (schema, value, _ctx) => {
949
533
  const ctx = _ctx ? {
950
534
  ..._ctx,
@@ -1043,7 +627,7 @@ const string$1 = (params) => {
1043
627
  return new RegExp(`^${regex}$`);
1044
628
  };
1045
629
  const integer = /^-?\d+$/;
1046
- const number$2 = /^-?\d+(?:\.\d+)?$/;
630
+ const number$1 = /^-?\d+(?:\.\d+)?$/;
1047
631
  const boolean$1 = /^(?:true|false)$/i;
1048
632
  const _null$2 = /^null$/i;
1049
633
  const lowercase = /^[^A-Z]*$/;
@@ -1517,10 +1101,10 @@ const $ZodType = /* @__PURE__ */ $constructor("$ZodType", (inst, def) => {
1517
1101
  defineLazy(inst, "~standard", () => ({
1518
1102
  validate: (value) => {
1519
1103
  try {
1520
- const r = safeParse$2(inst, value);
1104
+ const r = safeParse$1(inst, value);
1521
1105
  return r.success ? { value: r.data } : { issues: r.error?.issues };
1522
1106
  } catch (_) {
1523
- return safeParseAsync$2(inst, value).then((r) => r.success ? { value: r.data } : { issues: r.error?.issues });
1107
+ return safeParseAsync$1(inst, value).then((r) => r.success ? { value: r.data } : { issues: r.error?.issues });
1524
1108
  }
1525
1109
  },
1526
1110
  vendor: "zod",
@@ -1810,7 +1394,7 @@ const $ZodJWT = /* @__PURE__ */ $constructor("$ZodJWT", (inst, def) => {
1810
1394
  });
1811
1395
  const $ZodNumber = /* @__PURE__ */ $constructor("$ZodNumber", (inst, def) => {
1812
1396
  $ZodType.init(inst, def);
1813
- inst._zod.pattern = inst._zod.bag.pattern ?? number$2;
1397
+ inst._zod.pattern = inst._zod.bag.pattern ?? number$1;
1814
1398
  inst._zod.parse = (payload, _ctx) => {
1815
1399
  if (def.coerce) try {
1816
1400
  payload.value = Number(payload.value);
@@ -2297,7 +1881,7 @@ function mergeValues$1(a, b) {
2297
1881
  valid: true,
2298
1882
  data: a
2299
1883
  };
2300
- if (isPlainObject$6(a) && isPlainObject$6(b)) {
1884
+ if (isPlainObject$8(a) && isPlainObject$8(b)) {
2301
1885
  const bKeys = Object.keys(b);
2302
1886
  const sharedKeys = Object.keys(a).filter((key) => bKeys.indexOf(key) !== -1);
2303
1887
  const newObj = {
@@ -2373,7 +1957,7 @@ const $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
2373
1957
  $ZodType.init(inst, def);
2374
1958
  inst._zod.parse = (payload, ctx) => {
2375
1959
  const input = payload.value;
2376
- if (!isPlainObject$6(input)) {
1960
+ if (!isPlainObject$8(input)) {
2377
1961
  payload.issues.push({
2378
1962
  expected: "record",
2379
1963
  code: "invalid_type",
@@ -2440,7 +2024,7 @@ const $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
2440
2024
  issues: []
2441
2025
  }, ctx);
2442
2026
  if (keyResult instanceof Promise) throw new Error("Async schemas not supported in object keys currently");
2443
- if (typeof key === "string" && number$2.test(key) && keyResult.issues.length) {
2027
+ if (typeof key === "string" && number$1.test(key) && keyResult.issues.length) {
2444
2028
  const retryResult = def.keyType._zod.run({
2445
2029
  value: Number(key),
2446
2030
  issues: []
@@ -4115,8 +3699,8 @@ const initializer = (inst, issues) => {
4115
3699
  const ZodRealError = /* @__PURE__ */ $constructor("ZodError", initializer, { Parent: Error });
4116
3700
  const parse$2 = /* @__PURE__ */ _parse(ZodRealError);
4117
3701
  const parseAsync = /* @__PURE__ */ _parseAsync(ZodRealError);
4118
- const safeParse$1 = /* @__PURE__ */ _safeParse(ZodRealError);
4119
- const safeParseAsync$1 = /* @__PURE__ */ _safeParseAsync(ZodRealError);
3702
+ const safeParse$2 = /* @__PURE__ */ _safeParse(ZodRealError);
3703
+ const safeParseAsync$2 = /* @__PURE__ */ _safeParseAsync(ZodRealError);
4120
3704
  const encode = /* @__PURE__ */ _encode(ZodRealError);
4121
3705
  const decode = /* @__PURE__ */ _decode(ZodRealError);
4122
3706
  const encodeAsync = /* @__PURE__ */ _encodeAsync(ZodRealError);
@@ -4172,9 +3756,9 @@ const ZodType$1 = /* @__PURE__ */ $constructor("ZodType", (inst, def) => {
4172
3756
  inst.type = def.type;
4173
3757
  Object.defineProperty(inst, "_def", { value: def });
4174
3758
  inst.parse = (data, params) => parse$2(inst, data, params, { callee: inst.parse });
4175
- inst.safeParse = (data, params) => safeParse$1(inst, data, params);
3759
+ inst.safeParse = (data, params) => safeParse$2(inst, data, params);
4176
3760
  inst.parseAsync = async (data, params) => parseAsync(inst, data, params, { callee: inst.parseAsync });
4177
- inst.safeParseAsync = async (data, params) => safeParseAsync$1(inst, data, params);
3761
+ inst.safeParseAsync = async (data, params) => safeParseAsync$2(inst, data, params);
4178
3762
  inst.spa = inst.safeParseAsync;
4179
3763
  inst.encode = (data, params) => encode(inst, data, params);
4180
3764
  inst.decode = (data, params) => decode(inst, data, params);
@@ -4523,7 +4107,7 @@ const ZodNumber$1 = /* @__PURE__ */ $constructor("ZodNumber", (inst, def) => {
4523
4107
  inst.isFinite = true;
4524
4108
  inst.format = bag.format ?? null;
4525
4109
  });
4526
- function number$1(params) {
4110
+ function number$2(params) {
4527
4111
  return /* @__PURE__ */ _number(ZodNumber$1, params);
4528
4112
  }
4529
4113
  const ZodNumberFormat = /* @__PURE__ */ $constructor("ZodNumberFormat", (inst, def) => {
@@ -4969,6 +4553,571 @@ function preprocess(fn, schema) {
4969
4553
  out: schema
4970
4554
  });
4971
4555
  }
4556
+ const operations = [
4557
+ "get_caplet",
4558
+ "check_backend",
4559
+ "list_tools",
4560
+ "search_tools",
4561
+ "get_tool",
4562
+ "call_tool"
4563
+ ];
4564
+ const mcpOperations = [
4565
+ ...operations,
4566
+ "list_resources",
4567
+ "search_resources",
4568
+ "list_resource_templates",
4569
+ "read_resource",
4570
+ "list_prompts",
4571
+ "search_prompts",
4572
+ "get_prompt",
4573
+ "complete"
4574
+ ];
4575
+ const generatedToolInputDescriptions = {
4576
+ operation: "Wrapper operation: get_caplet, check_backend, list_tools, search_tools, get_tool, call_tool. MCP Caplets also expose resources, prompts, and completions.",
4577
+ query: "Required for search operations only.",
4578
+ limit: "Optional list/search result limit.",
4579
+ tool: "Exact downstream tool name for get_tool or call_tool.",
4580
+ arguments: "JSON object for call_tool arguments/downstream inputs or get_prompt arguments.",
4581
+ fields: "Optional call_tool structured output paths when outputSchema allows it.",
4582
+ uri: "Exact downstream resource URI for read_resource.",
4583
+ prompt: "Exact downstream prompt name for get_prompt.",
4584
+ ref: "Completion target reference for complete.",
4585
+ argument: "Completion argument object for complete."
4586
+ };
4587
+ const completionRefSchema = union([object$1({
4588
+ type: literal("prompt"),
4589
+ name: string().min(1)
4590
+ }).strict(), object$1({
4591
+ type: literal("resourceTemplate"),
4592
+ uri: string().min(1)
4593
+ }).strict()]);
4594
+ const completionArgumentSchema = object$1({
4595
+ name: string().min(1),
4596
+ value: string()
4597
+ }).strict();
4598
+ const baseShape = {
4599
+ query: string().optional().describe(generatedToolInputDescriptions.query),
4600
+ limit: number$2().int().positive().optional().describe(generatedToolInputDescriptions.limit),
4601
+ tool: string().optional().describe(generatedToolInputDescriptions.tool),
4602
+ arguments: object$1({}).catchall(any()).optional().describe(generatedToolInputDescriptions.arguments),
4603
+ fields: array(string().min(1)).min(1).optional().describe(generatedToolInputDescriptions.fields)
4604
+ };
4605
+ function generatedToolInputSchemaForCaplet(caplet) {
4606
+ return object$1({
4607
+ operation: (caplet.backend === "mcp" ? _enum(mcpOperations) : _enum(operations)).describe(generatedToolInputDescriptions.operation),
4608
+ ...baseShape,
4609
+ ...caplet.backend === "mcp" ? {
4610
+ uri: string().optional().describe(generatedToolInputDescriptions.uri),
4611
+ prompt: string().optional().describe(generatedToolInputDescriptions.prompt),
4612
+ ref: completionRefSchema.optional().describe(generatedToolInputDescriptions.ref),
4613
+ argument: completionArgumentSchema.optional().describe(generatedToolInputDescriptions.argument)
4614
+ } : {}
4615
+ }).strict();
4616
+ }
4617
+ object$1({
4618
+ operation: _enum(operations).describe(generatedToolInputDescriptions.operation),
4619
+ ...baseShape
4620
+ }).strict();
4621
+ function generatedToolInputJsonSchemaForCaplet(caplet) {
4622
+ const mcp = caplet.backend === "mcp";
4623
+ return {
4624
+ type: "object",
4625
+ properties: {
4626
+ operation: {
4627
+ type: "string",
4628
+ enum: mcp ? mcpOperations : operations,
4629
+ description: generatedToolInputDescriptions.operation
4630
+ },
4631
+ query: {
4632
+ type: "string",
4633
+ description: generatedToolInputDescriptions.query
4634
+ },
4635
+ limit: {
4636
+ type: "integer",
4637
+ minimum: 1,
4638
+ description: generatedToolInputDescriptions.limit
4639
+ },
4640
+ tool: {
4641
+ type: "string",
4642
+ description: generatedToolInputDescriptions.tool
4643
+ },
4644
+ arguments: {
4645
+ type: "object",
4646
+ description: generatedToolInputDescriptions.arguments
4647
+ },
4648
+ fields: {
4649
+ type: "array",
4650
+ items: {
4651
+ type: "string",
4652
+ minLength: 1
4653
+ },
4654
+ minItems: 1,
4655
+ description: generatedToolInputDescriptions.fields
4656
+ },
4657
+ ...mcp ? {
4658
+ uri: {
4659
+ type: "string",
4660
+ description: generatedToolInputDescriptions.uri
4661
+ },
4662
+ prompt: {
4663
+ type: "string",
4664
+ description: generatedToolInputDescriptions.prompt
4665
+ },
4666
+ ref: {
4667
+ oneOf: [{
4668
+ type: "object",
4669
+ properties: {
4670
+ type: { const: "prompt" },
4671
+ name: {
4672
+ type: "string",
4673
+ minLength: 1
4674
+ }
4675
+ },
4676
+ required: ["type", "name"],
4677
+ additionalProperties: false
4678
+ }, {
4679
+ type: "object",
4680
+ properties: {
4681
+ type: { const: "resourceTemplate" },
4682
+ uri: {
4683
+ type: "string",
4684
+ minLength: 1
4685
+ }
4686
+ },
4687
+ required: ["type", "uri"],
4688
+ additionalProperties: false
4689
+ }],
4690
+ description: generatedToolInputDescriptions.ref
4691
+ },
4692
+ argument: {
4693
+ type: "object",
4694
+ properties: {
4695
+ name: {
4696
+ type: "string",
4697
+ minLength: 1
4698
+ },
4699
+ value: { type: "string" }
4700
+ },
4701
+ required: ["name", "value"],
4702
+ additionalProperties: false,
4703
+ description: generatedToolInputDescriptions.argument
4704
+ }
4705
+ } : {}
4706
+ },
4707
+ required: ["operation"],
4708
+ additionalProperties: false
4709
+ };
4710
+ }
4711
+ function generatedToolInputJsonSchema() {
4712
+ return generatedToolInputJsonSchemaForCaplet({ backend: "tool" });
4713
+ }
4714
+ //#endregion
4715
+ //#region ../core/dist/options-DM1cMRcp.js
4716
+ var __create = Object.create;
4717
+ var __defProp = Object.defineProperty;
4718
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4719
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4720
+ var __getProtoOf = Object.getPrototypeOf;
4721
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
4722
+ var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
4723
+ var __exportAll = (all, no_symbols) => {
4724
+ let target = {};
4725
+ for (var name in all) __defProp(target, name, {
4726
+ get: all[name],
4727
+ enumerable: true
4728
+ });
4729
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
4730
+ return target;
4731
+ };
4732
+ var __copyProps = (to, from, except, desc) => {
4733
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
4734
+ key = keys[i];
4735
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
4736
+ get: ((k) => from[k]).bind(null, key),
4737
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
4738
+ });
4739
+ }
4740
+ return to;
4741
+ };
4742
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
4743
+ value: mod,
4744
+ enumerable: true
4745
+ }) : target, mod));
4746
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
4747
+ const CAPLETS_ERROR_CODES = [
4748
+ "CONFIG_NOT_FOUND",
4749
+ "CONFIG_EXISTS",
4750
+ "CONFIG_INVALID",
4751
+ "REQUEST_INVALID",
4752
+ "SERVER_NOT_FOUND",
4753
+ "SERVER_UNAVAILABLE",
4754
+ "SERVER_START_TIMEOUT",
4755
+ "UNKNOWN_OPERATION",
4756
+ "TOOL_NOT_FOUND",
4757
+ "TOOL_CALL_TIMEOUT",
4758
+ "AUTH_REQUIRED",
4759
+ "AUTH_FAILED",
4760
+ "AUTH_REFRESH_FAILED",
4761
+ "DOWNSTREAM_PROTOCOL_ERROR",
4762
+ "DOWNSTREAM_TOOL_ERROR",
4763
+ "UNSUPPORTED_OPERATION",
4764
+ "UNSUPPORTED_CAPABILITY",
4765
+ "PROMPT_NOT_FOUND",
4766
+ "DOWNSTREAM_RESOURCE_ERROR",
4767
+ "DOWNSTREAM_PROMPT_ERROR",
4768
+ "DOWNSTREAM_COMPLETION_ERROR",
4769
+ "UNSUPPORTED_TRANSPORT",
4770
+ "INTERNAL_ERROR"
4771
+ ];
4772
+ var CapletsError = class extends Error {
4773
+ code;
4774
+ details;
4775
+ constructor(code, message, details) {
4776
+ super(message);
4777
+ this.name = "CapletsError";
4778
+ this.code = code;
4779
+ this.details = details;
4780
+ }
4781
+ };
4782
+ const SECRET_KEY_PATTERN = /(token|secret|authorization|auth|api[-_]?key|password|credential|clientsecret|client_secret|code|refresh)/i;
4783
+ const SECRET_VALUE_PATTERN = /(bearer\s+)[a-z0-9._~+/=-]+|([?&](?:access_token|refresh_token|token|code)=)[^&\s]+/gi;
4784
+ function redactSecrets(value) {
4785
+ if (typeof value === "string") return value.replace(SECRET_VALUE_PATTERN, "$1$2[REDACTED]");
4786
+ if (Array.isArray(value)) return value.map((item) => redactSecrets(item));
4787
+ if (value && typeof value === "object") {
4788
+ const redacted = {};
4789
+ for (const [key, nested] of Object.entries(value)) redacted[key] = SECRET_KEY_PATTERN.test(key) ? "[REDACTED]" : redactSecrets(nested);
4790
+ return redacted;
4791
+ }
4792
+ return value;
4793
+ }
4794
+ function toSafeError(error, fallback = "INTERNAL_ERROR") {
4795
+ if (error instanceof CapletsError) return {
4796
+ code: error.code,
4797
+ message: String(redactSecrets(error.message)),
4798
+ ...error.details === void 0 ? {} : { details: redactSecrets(error.details) }
4799
+ };
4800
+ if (error instanceof Error) return {
4801
+ code: fallback,
4802
+ message: String(redactSecrets(error.message))
4803
+ };
4804
+ return {
4805
+ code: fallback,
4806
+ message: String(redactSecrets(error))
4807
+ };
4808
+ }
4809
+ function errorResult(error, fallback) {
4810
+ const safe = toSafeError(error, fallback);
4811
+ return {
4812
+ isError: true,
4813
+ content: [{
4814
+ type: "text",
4815
+ text: `${safe.code}: ${safe.message}`
4816
+ }],
4817
+ structuredContent: { error: safe }
4818
+ };
4819
+ }
4820
+ function textContent(text) {
4821
+ return text ? [{
4822
+ type: "text",
4823
+ text
4824
+ }] : [];
4825
+ }
4826
+ function compactJsonText(value, maxLength = 600) {
4827
+ return compactText(JSON.stringify(value) ?? String(value), maxLength);
4828
+ }
4829
+ function compactText(value, maxLength = 600) {
4830
+ const collapsed = value.replace(/\s+/gu, " ").trim();
4831
+ return collapsed.length > maxLength ? `${collapsed.slice(0, maxLength - 1).trimEnd()}…` : collapsed;
4832
+ }
4833
+ function resultKeys(value) {
4834
+ if (!value || typeof value !== "object" || Array.isArray(value)) return "scalar result";
4835
+ const keys = Object.keys(value).filter((key) => key !== "elapsedMs");
4836
+ return keys.length > 0 ? `structured keys: ${keys.join(", ")}` : "empty structured result";
4837
+ }
4838
+ function statusSummary(value) {
4839
+ if (!value || typeof value !== "object" || Array.isArray(value)) return compactJsonText(value);
4840
+ const record = value;
4841
+ return [
4842
+ typeof record.status === "number" ? `status ${record.status}` : void 0,
4843
+ typeof record.statusText === "string" && record.statusText ? record.statusText : void 0,
4844
+ typeof record.exitCode === "number" ? `exit ${record.exitCode}` : void 0,
4845
+ "body" in record ? "body" : void 0,
4846
+ "json" in record ? "json" : void 0,
4847
+ typeof record.stdout === "string" && record.stdout ? "stdout" : void 0,
4848
+ typeof record.stderr === "string" && record.stderr ? "stderr" : void 0
4849
+ ].filter((part) => Boolean(part)).join("; ") || resultKeys(record);
4850
+ }
4851
+ function compactStructuredContent(value) {
4852
+ return textContent(statusSummary(value));
4853
+ }
4854
+ function searchToolList(tools, query, limit, compact) {
4855
+ const tokens = query.toLocaleLowerCase().split(/\s+/).filter(Boolean);
4856
+ return tools.filter((tool) => {
4857
+ const haystack = `${tool.name}\n${tool.description ?? ""}`.toLocaleLowerCase();
4858
+ return tokens.some((token) => haystack.includes(token));
4859
+ }).sort((left, right) => left.name.localeCompare(right.name)).slice(0, limit).map(compact);
4860
+ }
4861
+ const DEFAULT_INPUT_SCHEMA$1 = {
4862
+ type: "object",
4863
+ additionalProperties: true
4864
+ };
4865
+ var CliToolsManager = class {
4866
+ registry;
4867
+ constructor(registry) {
4868
+ this.registry = registry;
4869
+ }
4870
+ updateRegistry(registry) {
4871
+ this.registry = registry;
4872
+ }
4873
+ invalidate(_serverId) {}
4874
+ async checkTools(config) {
4875
+ const startedAt = Date.now();
4876
+ try {
4877
+ for (const action of actionsFor(config)) {
4878
+ const cwdTemplate = action.cwd ?? config.cwd;
4879
+ if (cwdTemplate && !cwdTemplate.includes("$input")) {
4880
+ if (!existsSync(interpolateRequiredString(cwdTemplate, {}, "cwd"))) throw new CapletsError("CONFIG_INVALID", `CLI cwd does not exist for ${config.server}/${action.name}`);
4881
+ }
4882
+ if (!action.command.includes("$input")) resolveCommandPath(action.command);
4883
+ }
4884
+ this.registry.setStatus(config.server, "available");
4885
+ return {
4886
+ id: config.server,
4887
+ status: "available",
4888
+ toolCount: Object.keys(config.actions).length,
4889
+ elapsedMs: Date.now() - startedAt
4890
+ };
4891
+ } catch (error) {
4892
+ const safe = toSafeError(error, "SERVER_UNAVAILABLE");
4893
+ this.registry.setStatus(config.server, "unavailable", safe);
4894
+ return {
4895
+ id: config.server,
4896
+ status: "unavailable",
4897
+ elapsedMs: Date.now() - startedAt,
4898
+ error: safe
4899
+ };
4900
+ }
4901
+ }
4902
+ async listTools(config) {
4903
+ return actionsFor(config).map((action) => this.toTool(action));
4904
+ }
4905
+ async getTool(config, toolName) {
4906
+ return this.toTool(getAction(config, toolName));
4907
+ }
4908
+ async callTool(config, toolName, args) {
4909
+ const action = getAction(config, toolName);
4910
+ validateInput(action, args);
4911
+ const execution = resolveExecution(config, action, args);
4912
+ const startedAt = Date.now();
4913
+ const controller = new AbortController();
4914
+ const timeout = setTimeout(() => controller.abort(), execution.timeoutMs);
4915
+ try {
4916
+ const result = await spawnCommand(execution, controller.signal, () => Date.now() - startedAt);
4917
+ const structured = parseStructuredResult(action, result, result.exitCode !== 0);
4918
+ return {
4919
+ content: compactStructuredContent(structured),
4920
+ structuredContent: structured,
4921
+ isError: result.exitCode !== 0
4922
+ };
4923
+ } catch (error) {
4924
+ if (isAbortError$1(error)) throw new CapletsError("TOOL_CALL_TIMEOUT", `CLI tool timed out for ${config.server}/${toolName}`);
4925
+ if (error instanceof CapletsError) throw error;
4926
+ throw new CapletsError("DOWNSTREAM_TOOL_ERROR", `CLI tool failed for ${config.server}/${toolName}`, toSafeError(error));
4927
+ } finally {
4928
+ clearTimeout(timeout);
4929
+ }
4930
+ }
4931
+ compact(config, tool) {
4932
+ return {
4933
+ id: config.server,
4934
+ tool: tool.name,
4935
+ ...tool.description ? { description: tool.description } : {},
4936
+ hasInputSchema: Boolean(tool.inputSchema),
4937
+ hasOutputSchema: Boolean(tool.outputSchema)
4938
+ };
4939
+ }
4940
+ search(config, tools, query, limit) {
4941
+ return searchToolList(tools, query, limit, (tool) => this.compact(config, tool));
4942
+ }
4943
+ toTool(action) {
4944
+ return {
4945
+ name: action.name,
4946
+ ...action.description ? { description: action.description } : {},
4947
+ inputSchema: action.inputSchema ?? DEFAULT_INPUT_SCHEMA$1,
4948
+ ...action.outputSchema ? { outputSchema: action.outputSchema } : {},
4949
+ ...action.annotations ? { annotations: action.annotations } : {}
4950
+ };
4951
+ }
4952
+ };
4953
+ function actionsFor(config) {
4954
+ return Object.entries(config.actions).map(([name, action]) => ({
4955
+ name,
4956
+ ...action
4957
+ })).sort((left, right) => left.name.localeCompare(right.name));
4958
+ }
4959
+ function getAction(config, toolName) {
4960
+ const actions = actionsFor(config);
4961
+ const action = actions.find((candidate) => candidate.name === toolName);
4962
+ if (!action) throw new CapletsError("TOOL_NOT_FOUND", `Tool ${toolName} was not found on ${config.server}`, {
4963
+ server: config.server,
4964
+ tool: toolName,
4965
+ suggestions: actions.map((candidate) => candidate.name).filter((name) => name.toLocaleLowerCase().includes(toolName.toLocaleLowerCase()[0] ?? "")).slice(0, 5)
4966
+ });
4967
+ return action;
4968
+ }
4969
+ function resolveExecution(config, action, input) {
4970
+ const cwd = interpolateString(action.cwd ?? config.cwd, input, "cwd");
4971
+ if (cwd && !existsSync(cwd)) throw new CapletsError("CONFIG_INVALID", `CLI cwd does not exist for ${config.server}/${action.name}`);
4972
+ const env = {
4973
+ ...process.env,
4974
+ ...resolveEnv(config.env, input),
4975
+ ...resolveEnv(action.env, input)
4976
+ };
4977
+ return {
4978
+ command: interpolateString(action.command, input, "command") ?? action.command,
4979
+ args: (action.args ?? []).map((arg, index) => interpolateRequiredString(arg, input, `args.${index}`)),
4980
+ ...cwd ? { cwd } : {},
4981
+ env,
4982
+ timeoutMs: action.timeoutMs ?? config.timeoutMs,
4983
+ maxOutputBytes: action.maxOutputBytes ?? config.maxOutputBytes
4984
+ };
4985
+ }
4986
+ function resolveEnv(env, input) {
4987
+ if (!env) return {};
4988
+ return Object.fromEntries(Object.entries(env).map(([key, value]) => [key, interpolateRequiredString(value, input, `env.${key}`)]));
4989
+ }
4990
+ function interpolateString(value, input, field) {
4991
+ return value === void 0 ? void 0 : interpolateRequiredString(value, input, field);
4992
+ }
4993
+ function interpolateRequiredString(value, input, field) {
4994
+ return value.replace(/\$input(?:\.([A-Za-z0-9_.-]+))?/g, (_match, path) => {
4995
+ if (!path) throw new CapletsError("REQUEST_INVALID", `CLI ${field} cannot interpolate $input directly`);
4996
+ const selected = valueAtPath$1(input, path);
4997
+ if (selected === void 0 || selected === null) throw new CapletsError("REQUEST_INVALID", `CLI ${field} references missing input ${path}`);
4998
+ if (typeof selected !== "string" && typeof selected !== "number" && typeof selected !== "boolean") throw new CapletsError("REQUEST_INVALID", `CLI ${field} input ${path} must be a string, number, or boolean`);
4999
+ return String(selected);
5000
+ });
5001
+ }
5002
+ function valueAtPath$1(input, path) {
5003
+ let current = input;
5004
+ for (const segment of path.split(".")) {
5005
+ if (!current || typeof current !== "object" || Array.isArray(current)) return;
5006
+ current = current[segment];
5007
+ }
5008
+ return current;
5009
+ }
5010
+ function validateInput(action, input) {
5011
+ const schema = action.inputSchema;
5012
+ if (!schema) return;
5013
+ const required = Array.isArray(schema.required) ? schema.required : [];
5014
+ for (const key of required) if (typeof key === "string" && (input[key] === void 0 || input[key] === null)) throw new CapletsError("REQUEST_INVALID", `CLI tool ${action.name} requires input ${key}`);
5015
+ const properties = isPlainObject$6(schema.properties) ? schema.properties : {};
5016
+ for (const [key, property] of Object.entries(properties)) {
5017
+ if (input[key] === void 0 || !isPlainObject$6(property) || typeof property.type !== "string") continue;
5018
+ if (!matchesJsonType(input[key], property.type)) throw new CapletsError("REQUEST_INVALID", `CLI tool ${action.name} input ${key} must be ${property.type}`);
5019
+ }
5020
+ }
5021
+ function matchesJsonType(value, type) {
5022
+ switch (type) {
5023
+ case "string": return typeof value === "string";
5024
+ case "number":
5025
+ case "integer": return typeof value === "number" && (type === "number" || Number.isInteger(value));
5026
+ case "boolean": return typeof value === "boolean";
5027
+ case "object": return isPlainObject$6(value);
5028
+ case "array": return Array.isArray(value);
5029
+ case "null": return value === null;
5030
+ default: return true;
5031
+ }
5032
+ }
5033
+ function spawnCommand(execution, signal, elapsedMs) {
5034
+ return new Promise((resolve, reject) => {
5035
+ let stdout = "";
5036
+ let stderr = "";
5037
+ let outputBytes = 0;
5038
+ const child = spawn(execution.command, execution.args, {
5039
+ cwd: execution.cwd,
5040
+ env: execution.env,
5041
+ shell: false,
5042
+ signal,
5043
+ windowsHide: true
5044
+ });
5045
+ child.on("error", reject);
5046
+ const append = (stream, chunk) => {
5047
+ outputBytes += chunk.byteLength;
5048
+ if (outputBytes > execution.maxOutputBytes) {
5049
+ child.kill();
5050
+ reject(new CapletsError("DOWNSTREAM_TOOL_ERROR", "CLI tool output exceeded byte limit"));
5051
+ return;
5052
+ }
5053
+ if (stream === "stdout") stdout += chunk.toString("utf8");
5054
+ else stderr += chunk.toString("utf8");
5055
+ };
5056
+ child.stdout?.on("data", (chunk) => append("stdout", chunk));
5057
+ child.stderr?.on("data", (chunk) => append("stderr", chunk));
5058
+ child.on("close", (exitCode, childSignal) => {
5059
+ resolve({
5060
+ exitCode,
5061
+ signal: childSignal,
5062
+ stdout,
5063
+ stderr,
5064
+ elapsedMs: elapsedMs()
5065
+ });
5066
+ });
5067
+ });
5068
+ }
5069
+ function parseStructuredResult(action, result, tolerateInvalidJson = false) {
5070
+ const structured = {
5071
+ exitCode: result.exitCode,
5072
+ stdout: result.stdout,
5073
+ stderr: result.stderr,
5074
+ elapsedMs: result.elapsedMs,
5075
+ ...result.signal ? { signal: result.signal } : {}
5076
+ };
5077
+ if (action.output?.type === "json" && result.stdout.trim()) try {
5078
+ structured.json = JSON.parse(result.stdout);
5079
+ } catch (error) {
5080
+ if (tolerateInvalidJson) {
5081
+ structured.jsonParseError = toSafeError(error);
5082
+ return structured;
5083
+ }
5084
+ throw new CapletsError("DOWNSTREAM_PROTOCOL_ERROR", `CLI tool ${action.name} stdout was not valid JSON`, toSafeError(error));
5085
+ }
5086
+ return structured;
5087
+ }
5088
+ function resolveCommandPath(command) {
5089
+ if (isAbsolute(command) || /[\\/]/.test(command)) {
5090
+ assertExecutable(command);
5091
+ return command;
5092
+ }
5093
+ for (const directory of (process.env.PATH ?? "").split(delimiter)) {
5094
+ if (!directory) continue;
5095
+ const candidate = join(directory, command);
5096
+ if (isExecutable(candidate)) return candidate;
5097
+ if (process.platform === "win32") for (const ext of (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";")) {
5098
+ const windowsCandidate = join(directory, `${command}${ext.toLowerCase()}`);
5099
+ if (isExecutable(windowsCandidate)) return windowsCandidate;
5100
+ }
5101
+ }
5102
+ throw new CapletsError("SERVER_UNAVAILABLE", `CLI command ${command} was not found on PATH`);
5103
+ }
5104
+ function assertExecutable(path) {
5105
+ if (!isExecutable(path)) throw new CapletsError("SERVER_UNAVAILABLE", `CLI command ${path} is not executable`);
5106
+ }
5107
+ function isExecutable(path) {
5108
+ try {
5109
+ accessSync(path, constants.X_OK);
5110
+ return true;
5111
+ } catch {
5112
+ return false;
5113
+ }
5114
+ }
5115
+ function isAbortError$1(error) {
5116
+ return error instanceof Error && error.name === "AbortError";
5117
+ }
5118
+ function isPlainObject$6(value) {
5119
+ return value !== null && typeof value === "object" && !Array.isArray(value);
5120
+ }
4972
5121
  /** @deprecated Use the raw string literal codes instead, e.g. "invalid_type". */
4973
5122
  const ZodIssueCode$1 = {
4974
5123
  invalid_type: "invalid_type",
@@ -12489,9 +12638,9 @@ const capletMcpServerSchema = object$1({
12489
12638
  cwd: string().min(1).optional().describe("Working directory for stdio servers."),
12490
12639
  url: string().min(1).optional().describe("Remote MCP server URL for http or sse transport."),
12491
12640
  auth: capletRemoteAuthSchema.optional(),
12492
- startupTimeoutMs: number$1().int().positive().optional().describe("Timeout in milliseconds for starting or checking a downstream server."),
12493
- callTimeoutMs: number$1().int().positive().optional().describe("Timeout in milliseconds for downstream tool calls."),
12494
- toolCacheTtlMs: number$1().int().nonnegative().optional().describe("Milliseconds downstream tool metadata stays fresh. Set 0 to refresh every time."),
12641
+ startupTimeoutMs: number$2().int().positive().optional().describe("Timeout in milliseconds for starting or checking a downstream server."),
12642
+ callTimeoutMs: number$2().int().positive().optional().describe("Timeout in milliseconds for downstream tool calls."),
12643
+ toolCacheTtlMs: number$2().int().nonnegative().optional().describe("Milliseconds downstream tool metadata stays fresh. Set 0 to refresh every time."),
12495
12644
  disabled: boolean().optional().describe("When true, omit this Caplet from discovery and do not start its MCP server.")
12496
12645
  }).strict().superRefine((server, ctx) => {
12497
12646
  const effectiveTransport = server.transport ?? (server.command ? "stdio" : void 0);
@@ -12546,8 +12695,8 @@ const capletOpenApiEndpointSchema = object$1({
12546
12695
  specUrl: string().min(1).optional().describe("Remote OpenAPI specification URL."),
12547
12696
  baseUrl: string().min(1).optional().describe("Override base URL for OpenAPI requests."),
12548
12697
  auth: capletEndpointAuthSchema.describe("Explicit OpenAPI request auth config. Use {\"type\":\"none\"} for public APIs."),
12549
- requestTimeoutMs: number$1().int().positive().optional().describe("Timeout in milliseconds for OpenAPI HTTP requests."),
12550
- operationCacheTtlMs: number$1().int().nonnegative().optional().describe("Milliseconds OpenAPI operation metadata stays fresh. Set 0 to refresh every time."),
12698
+ requestTimeoutMs: number$2().int().positive().optional().describe("Timeout in milliseconds for OpenAPI HTTP requests."),
12699
+ operationCacheTtlMs: number$2().int().nonnegative().optional().describe("Milliseconds OpenAPI operation metadata stays fresh. Set 0 to refresh every time."),
12551
12700
  disabled: boolean().optional().describe("When true, omit this Caplet from discovery.")
12552
12701
  }).strict().superRefine((endpoint, ctx) => {
12553
12702
  if (Boolean(endpoint.specPath) === Boolean(endpoint.specUrl)) ctx.addIssue({
@@ -12584,9 +12733,9 @@ const capletGraphQlEndpointSchema = object$1({
12584
12733
  introspection: literal(true).optional().describe("Load schema through endpoint introspection."),
12585
12734
  operations: record(string().regex(SERVER_ID_PATTERN), capletGraphQlOperationSchema).optional().describe("Configured GraphQL operations keyed by stable tool name."),
12586
12735
  auth: capletEndpointAuthSchema.describe("Explicit GraphQL request auth config. Use {\"type\":\"none\"} for public APIs."),
12587
- requestTimeoutMs: number$1().int().positive().optional().describe("Timeout in milliseconds for GraphQL HTTP requests."),
12588
- operationCacheTtlMs: number$1().int().nonnegative().optional().describe("Milliseconds GraphQL operation metadata stays fresh. Set 0 to refresh every time."),
12589
- selectionDepth: number$1().int().positive().max(5).optional().describe("Maximum depth for auto-generated GraphQL selection sets."),
12736
+ requestTimeoutMs: number$2().int().positive().optional().describe("Timeout in milliseconds for GraphQL HTTP requests."),
12737
+ operationCacheTtlMs: number$2().int().nonnegative().optional().describe("Milliseconds GraphQL operation metadata stays fresh. Set 0 to refresh every time."),
12738
+ selectionDepth: number$2().int().positive().max(5).optional().describe("Maximum depth for auto-generated GraphQL selection sets."),
12590
12739
  disabled: boolean().optional().describe("When true, omit this Caplet from discovery.")
12591
12740
  }).strict().superRefine((endpoint, ctx) => {
12592
12741
  if (Number(Boolean(endpoint.schemaPath)) + Number(Boolean(endpoint.schemaUrl)) + Number(endpoint.introspection === true) !== 1) ctx.addIssue({
@@ -12607,7 +12756,7 @@ const capletGraphQlEndpointSchema = object$1({
12607
12756
  });
12608
12757
  const httpScalarMappingSchema$1 = record(string(), union([
12609
12758
  string(),
12610
- number$1(),
12759
+ number$2(),
12611
12760
  boolean()
12612
12761
  ]));
12613
12762
  const capletHttpActionSchema = object$1({
@@ -12635,8 +12784,8 @@ const capletHttpApiSchema = object$1({
12635
12784
  baseUrl: string().min(1).regex(HTTP_BASE_URL_PATTERN, "HTTP API baseUrl must not include credentials, query, or fragment").describe("Base URL for HTTP action requests."),
12636
12785
  auth: capletEndpointAuthSchema.describe("Explicit HTTP API request auth config. Use {\"type\":\"none\"} for public APIs."),
12637
12786
  actions: record(string().regex(SERVER_ID_PATTERN), capletHttpActionSchema).refine((actions) => Object.keys(actions).length > 0, "HTTP API must define at least one action").describe("Configured HTTP actions keyed by stable tool name."),
12638
- requestTimeoutMs: number$1().int().positive().optional().describe("Timeout in milliseconds for HTTP action requests."),
12639
- maxResponseBytes: number$1().int().positive().optional().describe("Maximum HTTP action response body bytes to read."),
12787
+ requestTimeoutMs: number$2().int().positive().optional().describe("Timeout in milliseconds for HTTP action requests."),
12788
+ maxResponseBytes: number$2().int().positive().optional().describe("Maximum HTTP action response body bytes to read."),
12640
12789
  disabled: boolean().optional().describe("When true, omit this Caplet from discovery.")
12641
12790
  }).strict().superRefine((api, ctx) => {
12642
12791
  if (api.baseUrl && !hasEnvReference$1(api.baseUrl) && !isAllowedHttpBaseUrl(api.baseUrl)) ctx.addIssue({
@@ -12666,8 +12815,8 @@ const capletCliToolActionSchema = object$1({
12666
12815
  args: array(string()).optional().describe("Arguments passed to the command."),
12667
12816
  env: record(string(), string()).optional().describe("Additional environment variables."),
12668
12817
  cwd: string().min(1).optional().describe("Working directory for this action."),
12669
- timeoutMs: number$1().int().positive().optional(),
12670
- maxOutputBytes: number$1().int().positive().optional(),
12818
+ timeoutMs: number$2().int().positive().optional(),
12819
+ maxOutputBytes: number$2().int().positive().optional(),
12671
12820
  output: capletCliToolOutputSchema.optional(),
12672
12821
  annotations: capletCliToolAnnotationsSchema.optional()
12673
12822
  }).strict();
@@ -12675,16 +12824,16 @@ const capletCliToolsSchema = object$1({
12675
12824
  actions: record(string().regex(SERVER_ID_PATTERN), capletCliToolActionSchema).refine((actions) => Object.keys(actions).length > 0, "CLI tools backend must define at least one action").describe("Configured CLI actions keyed by stable tool name."),
12676
12825
  cwd: string().min(1).optional().describe("Default working directory for CLI actions."),
12677
12826
  env: record(string(), string()).optional().describe("Default environment variables."),
12678
- timeoutMs: number$1().int().positive().optional(),
12679
- maxOutputBytes: number$1().int().positive().optional(),
12827
+ timeoutMs: number$2().int().positive().optional(),
12828
+ maxOutputBytes: number$2().int().positive().optional(),
12680
12829
  disabled: boolean().optional().describe("When true, omit this Caplet from discovery.")
12681
12830
  }).strict();
12682
12831
  const capletSetSchema = object$1({
12683
12832
  configPath: string().min(1).optional().describe("Child Caplets config.json path."),
12684
12833
  capletsRoot: string().min(1).optional().describe("Child Markdown Caplets root directory."),
12685
- defaultSearchLimit: number$1().int().positive().optional(),
12686
- maxSearchLimit: number$1().int().positive().max(50).optional(),
12687
- toolCacheTtlMs: number$1().int().nonnegative().optional(),
12834
+ defaultSearchLimit: number$2().int().positive().optional(),
12835
+ maxSearchLimit: number$2().int().positive().max(50).optional(),
12836
+ toolCacheTtlMs: number$2().int().nonnegative().optional(),
12688
12837
  disabled: boolean().optional().describe("When true, omit this Caplet from discovery.")
12689
12838
  }).strict().superRefine((set, ctx) => {
12690
12839
  if (!set.configPath && !set.capletsRoot) ctx.addIssue({
@@ -12922,14 +13071,24 @@ function defaultStateBaseDir(env = process.env, home = homedir(), platform = pro
12922
13071
  if (platform === "win32") return env.LOCALAPPDATA && win32.isAbsolute(env.LOCALAPPDATA) ? env.LOCALAPPDATA : win32.join(home, "AppData", "Local");
12923
13072
  return env.XDG_STATE_HOME && posix.isAbsolute(env.XDG_STATE_HOME) ? env.XDG_STATE_HOME : posix.join(home, ".local", "state");
12924
13073
  }
13074
+ function defaultCacheBaseDir(env = process.env, home = homedir(), platform = process.platform) {
13075
+ if (platform === "win32") return env.LOCALAPPDATA && win32.isAbsolute(env.LOCALAPPDATA) ? env.LOCALAPPDATA : win32.join(home, "AppData", "Local");
13076
+ if (platform === "darwin") return posix.join(home, "Library", "Caches");
13077
+ return env.XDG_CACHE_HOME && posix.isAbsolute(env.XDG_CACHE_HOME) ? env.XDG_CACHE_HOME : posix.join(home, ".cache");
13078
+ }
12925
13079
  function defaultConfigPath(env = process.env, home = homedir(), platform = process.platform) {
12926
13080
  return (platform === "win32" ? win32.join : posix.join)(defaultConfigBaseDir(env, home, platform), "caplets", "config.json");
12927
13081
  }
12928
13082
  function defaultAuthDir(env = process.env, home = homedir(), platform = process.platform) {
12929
13083
  return (platform === "win32" ? win32.join : posix.join)(defaultStateBaseDir(env, home, platform), "caplets", "auth");
12930
13084
  }
13085
+ function defaultCompletionCacheDir(env = process.env, home = homedir(), platform = process.platform) {
13086
+ const pathJoin = platform === "win32" ? win32.join : posix.join;
13087
+ return platform === "win32" ? pathJoin(defaultCacheBaseDir(env, home, platform), "caplets", "cache", "completions") : pathJoin(defaultCacheBaseDir(env, home, platform), "caplets", "completions");
13088
+ }
12931
13089
  const DEFAULT_CONFIG_PATH = defaultConfigPath();
12932
13090
  const DEFAULT_AUTH_DIR = defaultAuthDir();
13091
+ const DEFAULT_COMPLETION_CACHE_DIR = defaultCompletionCacheDir();
12933
13092
  const PROJECT_CONFIG_FILE = join(".caplets", "config.json");
12934
13093
  function resolveConfigPath(path) {
12935
13094
  return path ?? DEFAULT_CONFIG_PATH;
@@ -13042,9 +13201,9 @@ const publicServerSchema = object$1({
13042
13201
  url: string().url().optional().describe("Remote MCP server URL for http or sse transport."),
13043
13202
  auth: remoteAuthSchema.optional(),
13044
13203
  tags: array(string().trim().min(1).max(80)).optional(),
13045
- startupTimeoutMs: number$1().int().positive().default(1e4).describe("Timeout in milliseconds for starting or checking a downstream server."),
13046
- callTimeoutMs: number$1().int().positive().default(6e4).describe("Timeout in milliseconds for downstream tool calls."),
13047
- toolCacheTtlMs: number$1().int().nonnegative().default(3e4).describe("Milliseconds downstream tool metadata stays fresh. Set 0 to refresh every time."),
13204
+ startupTimeoutMs: number$2().int().positive().default(1e4).describe("Timeout in milliseconds for starting or checking a downstream server."),
13205
+ callTimeoutMs: number$2().int().positive().default(6e4).describe("Timeout in milliseconds for downstream tool calls."),
13206
+ toolCacheTtlMs: number$2().int().nonnegative().default(3e4).describe("Milliseconds downstream tool metadata stays fresh. Set 0 to refresh every time."),
13048
13207
  disabled: boolean().default(false).describe("When true, omit this server from Caplets discovery and do not start it.")
13049
13208
  }).strict();
13050
13209
  const normalizedServerSchema = publicServerSchema.extend({ body: string().optional() });
@@ -13056,8 +13215,8 @@ const publicOpenApiEndpointSchema = object$1({
13056
13215
  baseUrl: string().url().optional().describe("Override base URL for OpenAPI requests."),
13057
13216
  auth: openApiAuthSchema.describe("Explicit OpenAPI request auth config. Use {\"type\":\"none\"} for public APIs."),
13058
13217
  tags: array(string().trim().min(1).max(80)).optional(),
13059
- requestTimeoutMs: number$1().int().positive().default(6e4).describe("Timeout in milliseconds for OpenAPI HTTP requests."),
13060
- operationCacheTtlMs: number$1().int().nonnegative().default(3e4).describe("Milliseconds OpenAPI operation metadata stays fresh. Set 0 to refresh every time."),
13218
+ requestTimeoutMs: number$2().int().positive().default(6e4).describe("Timeout in milliseconds for OpenAPI HTTP requests."),
13219
+ operationCacheTtlMs: number$2().int().nonnegative().default(3e4).describe("Milliseconds OpenAPI operation metadata stays fresh. Set 0 to refresh every time."),
13061
13220
  disabled: boolean().default(false).describe("When true, omit this OpenAPI Caplet from discovery.")
13062
13221
  }).strict();
13063
13222
  const normalizedOpenApiEndpointSchema = publicOpenApiEndpointSchema.extend({ body: string().optional() });
@@ -13082,9 +13241,9 @@ const publicGraphQlEndpointSchema = object$1({
13082
13241
  operations: record(string().regex(SERVER_ID_PATTERN), graphQlOperationSchema).optional().describe("Configured GraphQL operations keyed by stable tool name."),
13083
13242
  auth: openApiAuthSchema.describe("Explicit GraphQL request auth config. Use {\"type\":\"none\"} for public APIs."),
13084
13243
  tags: array(string().trim().min(1).max(80)).optional(),
13085
- requestTimeoutMs: number$1().int().positive().default(6e4).describe("Timeout in milliseconds for GraphQL HTTP requests."),
13086
- operationCacheTtlMs: number$1().int().nonnegative().default(3e4).describe("Milliseconds GraphQL operation metadata stays fresh. Set 0 to refresh every time."),
13087
- selectionDepth: number$1().int().positive().max(5).default(2).describe("Maximum depth for auto-generated GraphQL selection sets."),
13244
+ requestTimeoutMs: number$2().int().positive().default(6e4).describe("Timeout in milliseconds for GraphQL HTTP requests."),
13245
+ operationCacheTtlMs: number$2().int().nonnegative().default(3e4).describe("Milliseconds GraphQL operation metadata stays fresh. Set 0 to refresh every time."),
13246
+ selectionDepth: number$2().int().positive().max(5).default(2).describe("Maximum depth for auto-generated GraphQL selection sets."),
13088
13247
  disabled: boolean().default(false).describe("When true, omit this GraphQL Caplet.")
13089
13248
  }).strict().superRefine((endpoint, ctx) => {
13090
13249
  if (Number(Boolean(endpoint.schemaPath)) + Number(Boolean(endpoint.schemaUrl)) + Number(endpoint.introspection === true) !== 1) ctx.addIssue({
@@ -13095,7 +13254,7 @@ const publicGraphQlEndpointSchema = object$1({
13095
13254
  const normalizedGraphQlEndpointSchema = publicGraphQlEndpointSchema.extend({ body: string().optional() });
13096
13255
  const httpScalarMappingSchema = record(string(), union([
13097
13256
  string(),
13098
- number$1(),
13257
+ number$2(),
13099
13258
  boolean()
13100
13259
  ]));
13101
13260
  const httpActionSchema = object$1({
@@ -13127,8 +13286,8 @@ const publicHttpApiSchema = object$1({
13127
13286
  auth: openApiAuthSchema.describe("Explicit HTTP API request auth config. Use {\"type\":\"none\"} for public APIs."),
13128
13287
  actions: record(string().regex(SERVER_ID_PATTERN), httpActionSchema).refine((actions) => Object.keys(actions).length > 0, "HTTP API must define at least one action").describe("Configured HTTP actions keyed by stable tool name."),
13129
13288
  tags: array(string().trim().min(1).max(80)).optional(),
13130
- requestTimeoutMs: number$1().int().positive().default(6e4).describe("Timeout in milliseconds for HTTP action requests."),
13131
- maxResponseBytes: number$1().int().positive().default(2e5).describe("Maximum HTTP action response body bytes to read."),
13289
+ requestTimeoutMs: number$2().int().positive().default(6e4).describe("Timeout in milliseconds for HTTP action requests."),
13290
+ maxResponseBytes: number$2().int().positive().default(2e5).describe("Maximum HTTP action response body bytes to read."),
13132
13291
  disabled: boolean().default(false).describe("When true, omit this HTTP API Caplet.")
13133
13292
  }).strict();
13134
13293
  const normalizedHttpApiSchema = publicHttpApiSchema.extend({ body: string().optional() });
@@ -13147,8 +13306,8 @@ const cliToolActionSchema = object$1({
13147
13306
  args: array(string()).optional().describe("Arguments passed to the command."),
13148
13307
  env: record(string(), string()).optional().describe("Additional environment variables for the command."),
13149
13308
  cwd: string().min(1).optional().describe("Working directory for this action."),
13150
- timeoutMs: number$1().int().positive().optional().describe("Command timeout in milliseconds."),
13151
- maxOutputBytes: number$1().int().positive().optional().describe("Maximum combined stdout and stderr bytes to keep."),
13309
+ timeoutMs: number$2().int().positive().optional().describe("Command timeout in milliseconds."),
13310
+ maxOutputBytes: number$2().int().positive().optional().describe("Maximum combined stdout and stderr bytes to keep."),
13152
13311
  output: cliToolOutputSchema.optional(),
13153
13312
  annotations: cliToolAnnotationsSchema.optional()
13154
13313
  }).strict();
@@ -13159,8 +13318,8 @@ const publicCliToolsSchema = object$1({
13159
13318
  cwd: string().min(1).optional().describe("Default working directory for CLI actions."),
13160
13319
  env: record(string(), string()).optional().describe("Default environment variables for CLI actions."),
13161
13320
  tags: array(string().trim().min(1).max(80)).optional(),
13162
- timeoutMs: number$1().int().positive().default(6e4).describe("Default timeout in milliseconds for CLI actions."),
13163
- maxOutputBytes: number$1().int().positive().default(2e5).describe("Default maximum combined stdout and stderr bytes to keep."),
13321
+ timeoutMs: number$2().int().positive().default(6e4).describe("Default timeout in milliseconds for CLI actions."),
13322
+ maxOutputBytes: number$2().int().positive().default(2e5).describe("Default maximum combined stdout and stderr bytes to keep."),
13164
13323
  disabled: boolean().default(false).describe("When true, omit this CLI tools Caplet.")
13165
13324
  }).strict();
13166
13325
  const normalizedCliToolsSchema = publicCliToolsSchema.extend({ body: string().optional() });
@@ -13169,9 +13328,9 @@ const publicCapletSetSchema = object$1({
13169
13328
  description: string().describe("Capability description shown before child Caplets are disclosed.").refine((value) => value.trim().length >= 10, "description must contain at least 10 non-whitespace characters").refine((value) => value.length <= 1500, "description must be at most 1500 characters"),
13170
13329
  configPath: string().min(1).optional().describe("Child Caplets config.json path."),
13171
13330
  capletsRoot: string().min(1).optional().describe("Child Markdown Caplets root directory."),
13172
- defaultSearchLimit: number$1().int().positive().default(20).describe("Default maximum number of child Caplet search results."),
13173
- maxSearchLimit: number$1().int().positive().max(50).default(50).describe("Maximum accepted child Caplet search result limit."),
13174
- toolCacheTtlMs: number$1().int().nonnegative().default(3e4).describe("Milliseconds child Caplet metadata stays fresh. Set 0 to refresh every time."),
13331
+ defaultSearchLimit: number$2().int().positive().default(20).describe("Default maximum number of child Caplet search results."),
13332
+ maxSearchLimit: number$2().int().positive().max(50).default(50).describe("Maximum accepted child Caplet search result limit."),
13333
+ toolCacheTtlMs: number$2().int().nonnegative().default(3e4).describe("Milliseconds child Caplet metadata stays fresh. Set 0 to refresh every time."),
13175
13334
  tags: array(string().trim().min(1).max(80)).optional(),
13176
13335
  disabled: boolean().default(false).describe("When true, omit this Caplet set.")
13177
13336
  }).strict().superRefine((set, ctx) => {
@@ -13190,8 +13349,19 @@ function configSchemaFor(serverValueSchema, openApiEndpointValueSchema, graphQlE
13190
13349
  return object$1({
13191
13350
  $schema: string().url().optional().describe("Optional JSON Schema URL for editor validation."),
13192
13351
  version: literal(1).default(1).describe("Caplets config schema version."),
13193
- defaultSearchLimit: number$1().int().positive().default(20).describe("Default maximum number of same-server search results."),
13194
- maxSearchLimit: number$1().int().positive().max(50).default(50).describe("Maximum accepted search_tools limit."),
13352
+ defaultSearchLimit: number$2().int().positive().default(20).describe("Default maximum number of same-server search results."),
13353
+ maxSearchLimit: number$2().int().positive().max(50).default(50).describe("Maximum accepted search_tools limit."),
13354
+ completion: object$1({
13355
+ discoveryTimeoutMs: number$2().int().positive().default(750),
13356
+ overallTimeoutMs: number$2().int().positive().default(1500),
13357
+ cacheTtlMs: number$2().int().nonnegative().default(3e5),
13358
+ negativeCacheTtlMs: number$2().int().nonnegative().default(3e4)
13359
+ }).strict().default({
13360
+ discoveryTimeoutMs: 750,
13361
+ overallTimeoutMs: 1500,
13362
+ cacheTtlMs: 3e5,
13363
+ negativeCacheTtlMs: 3e4
13364
+ }).describe("Shell completion discovery timeout and cache settings."),
13195
13365
  mcpServers: record(string().regex(SERVER_ID_PATTERN), serverValueSchema).default({}).describe("Downstream MCP servers keyed by stable server ID."),
13196
13366
  openapiEndpoints: record(string().regex(SERVER_ID_PATTERN), openApiEndpointValueSchema).default({}).describe("OpenAPI endpoints keyed by stable Caplet ID."),
13197
13367
  graphqlEndpoints: record(string().regex(SERVER_ID_PATTERN), graphQlEndpointValueSchema).default({}).describe("GraphQL endpoints keyed by stable Caplet ID."),
@@ -13442,7 +13612,7 @@ function loadConfigWithSources(path = resolveConfigPath(), projectPath = resolve
13442
13612
  const userConfig = hasUserConfig ? readPublicConfigInput(path) : void 0;
13443
13613
  const userCaplets = loadCapletFilesWithPaths(resolveCapletsRoot(path));
13444
13614
  const projectConfig = hasProjectConfig ? rejectProjectConfigExecutableBackendMaps(readPublicConfigInput(projectPath), projectPath) : void 0;
13445
- const projectCapletsRoot = resolveProjectCapletsRootForConfigPath(projectPath);
13615
+ const projectCapletsRoot = resolveProjectCapletsRootForConfigPath$1(projectPath);
13446
13616
  const projectCaplets = projectCapletsRoot ? loadCapletFilesWithPaths(projectCapletsRoot) : void 0;
13447
13617
  if (!hasUserConfig && !hasProjectConfig && !userCaplets && !projectCaplets) throw new CapletsError("CONFIG_NOT_FOUND", `Caplets config not found at ${path} or ${projectPath}`);
13448
13618
  try {
@@ -13496,7 +13666,7 @@ function loadIsolatedConfig(options) {
13496
13666
  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
13667
  return config;
13498
13668
  }
13499
- function resolveProjectCapletsRootForConfigPath(projectPath) {
13669
+ function resolveProjectCapletsRootForConfigPath$1(projectPath) {
13500
13670
  const root = dirname(projectPath);
13501
13671
  return basename(root) === ".caplets" && basename(projectPath) === "config.json" ? root : void 0;
13502
13672
  }
@@ -13706,7 +13876,8 @@ function parseConfig(input) {
13706
13876
  version: parsed.data.version,
13707
13877
  options: {
13708
13878
  defaultSearchLimit: parsed.data.defaultSearchLimit,
13709
- maxSearchLimit: parsed.data.maxSearchLimit
13879
+ maxSearchLimit: parsed.data.maxSearchLimit,
13880
+ completion: parsed.data.completion
13710
13881
  },
13711
13882
  mcpServers: servers,
13712
13883
  openapiEndpoints,
@@ -17286,10 +17457,10 @@ const ZodMiniType = /* @__PURE__ */ $constructor("ZodMiniType", (inst, def) => {
17286
17457
  $ZodType.init(inst, def);
17287
17458
  inst.def = def;
17288
17459
  inst.type = def.type;
17289
- inst.parse = (data, params) => parse$3(inst, data, params, { callee: inst.parse });
17290
- inst.safeParse = (data, params) => safeParse$2(inst, data, params);
17460
+ inst.parse = (data, params) => parse$1(inst, data, params, { callee: inst.parse });
17461
+ inst.safeParse = (data, params) => safeParse$1(inst, data, params);
17291
17462
  inst.parseAsync = async (data, params) => parseAsync$1(inst, data, params, { callee: inst.parseAsync });
17292
- inst.safeParseAsync = async (data, params) => safeParseAsync$2(inst, data, params);
17463
+ inst.safeParseAsync = async (data, params) => safeParseAsync$1(inst, data, params);
17293
17464
  inst.check = (...checks) => {
17294
17465
  return inst.clone({
17295
17466
  ...def,
@@ -17335,11 +17506,11 @@ function objectFromShape(shape) {
17335
17506
  throw new Error("Mixed Zod versions detected in object shape.");
17336
17507
  }
17337
17508
  function safeParse(schema, data) {
17338
- if (isZ4Schema(schema)) return safeParse$2(schema, data);
17509
+ if (isZ4Schema(schema)) return safeParse$1(schema, data);
17339
17510
  return schema.safeParse(data);
17340
17511
  }
17341
17512
  async function safeParseAsync(schema, data) {
17342
- if (isZ4Schema(schema)) return await safeParseAsync$2(schema, data);
17513
+ if (isZ4Schema(schema)) return await safeParseAsync$1(schema, data);
17343
17514
  return await schema.safeParseAsync(data);
17344
17515
  }
17345
17516
  function getObjectShape(schema) {
@@ -17453,7 +17624,7 @@ const AssertObjectSchema = custom((v) => v !== null && (typeof v === "object" ||
17453
17624
  /**
17454
17625
  * A progress token, used to associate progress notifications with the original request.
17455
17626
  */
17456
- const ProgressTokenSchema = union([string(), number$1().int()]);
17627
+ const ProgressTokenSchema = union([string(), number$2().int()]);
17457
17628
  /**
17458
17629
  * An opaque token used to represent a cursor for pagination.
17459
17630
  */
@@ -17462,13 +17633,13 @@ looseObject({
17462
17633
  /**
17463
17634
  * Requested duration in milliseconds to retain task from creation.
17464
17635
  */
17465
- ttl: number$1().optional(),
17636
+ ttl: number$2().optional(),
17466
17637
  /**
17467
17638
  * Time in milliseconds to wait between task status requests.
17468
17639
  */
17469
- pollInterval: number$1().optional()
17640
+ pollInterval: number$2().optional()
17470
17641
  });
17471
- const TaskMetadataSchema = object$1({ ttl: number$1().optional() });
17642
+ const TaskMetadataSchema = object$1({ ttl: number$2().optional() });
17472
17643
  /**
17473
17644
  * Metadata for associating messages with a task.
17474
17645
  * Include this in the `_meta` field under the key `io.modelcontextprotocol/related-task`.
@@ -17535,7 +17706,7 @@ _meta: RequestMetaSchema.optional() });
17535
17706
  /**
17536
17707
  * A uniquely identifying ID for a request in JSON-RPC.
17537
17708
  */
17538
- const RequestIdSchema = union([string(), number$1().int()]);
17709
+ const RequestIdSchema = union([string(), number$2().int()]);
17539
17710
  /**
17540
17711
  * A request that expects a response.
17541
17712
  */
@@ -17592,7 +17763,7 @@ const JSONRPCErrorResponseSchema = object$1({
17592
17763
  /**
17593
17764
  * The error type that occurred.
17594
17765
  */
17595
- code: number$1().int(),
17766
+ code: number$2().int(),
17596
17767
  /**
17597
17768
  * A short description of the error. The message SHOULD be limited to a concise single sentence.
17598
17769
  */
@@ -17928,11 +18099,11 @@ const ProgressSchema = object$1({
17928
18099
  /**
17929
18100
  * The progress thus far. This should increase every time progress is made, even if the total is unknown.
17930
18101
  */
17931
- progress: number$1(),
18102
+ progress: number$2(),
17932
18103
  /**
17933
18104
  * Total number of items to process (or total progress required), if known.
17934
18105
  */
17935
- total: optional(number$1()),
18106
+ total: optional(number$2()),
17936
18107
  /**
17937
18108
  * An optional message describing the current progress.
17938
18109
  */
@@ -17988,7 +18159,7 @@ const TaskSchema = object$1({
17988
18159
  * Time in milliseconds to keep task results available after completion.
17989
18160
  * If null, the task has unlimited lifetime until manually cleaned up.
17990
18161
  */
17991
- ttl: union([number$1(), _null()]),
18162
+ ttl: union([number$2(), _null()]),
17992
18163
  /**
17993
18164
  * ISO 8601 timestamp when the task was created.
17994
18165
  */
@@ -17997,7 +18168,7 @@ const TaskSchema = object$1({
17997
18168
  * ISO 8601 timestamp when the task was last updated.
17998
18169
  */
17999
18170
  lastUpdatedAt: string(),
18000
- pollInterval: optional(number$1()),
18171
+ pollInterval: optional(number$2()),
18001
18172
  /**
18002
18173
  * Optional diagnostic message for failed tasks or other status information.
18003
18174
  */
@@ -18112,7 +18283,7 @@ const AnnotationsSchema = object$1({
18112
18283
  /**
18113
18284
  * Importance hint for the resource, from 0 (least) to 1 (most).
18114
18285
  */
18115
- priority: number$1().min(0).max(1).optional(),
18286
+ priority: number$2().min(0).max(1).optional(),
18116
18287
  /**
18117
18288
  * ISO 8601 timestamp for the most recent modification.
18118
18289
  */
@@ -18143,7 +18314,7 @@ const ResourceSchema = object$1({
18143
18314
  *
18144
18315
  * This can be used by Hosts to display file sizes and estimate context window usage.
18145
18316
  */
18146
- size: optional(number$1()),
18317
+ size: optional(number$2()),
18147
18318
  /**
18148
18319
  * Optional annotations for the client.
18149
18320
  */
@@ -18670,7 +18841,7 @@ const ListChangedOptionsBaseSchema = object$1({
18670
18841
  *
18671
18842
  * @default 300
18672
18843
  */
18673
- debounceMs: number$1().int().nonnegative().default(300)
18844
+ debounceMs: number$2().int().nonnegative().default(300)
18674
18845
  });
18675
18846
  /**
18676
18847
  * The severity of a log message.
@@ -18739,15 +18910,15 @@ name: string().optional() })).optional(),
18739
18910
  /**
18740
18911
  * How much to prioritize cost when selecting a model.
18741
18912
  */
18742
- costPriority: number$1().min(0).max(1).optional(),
18913
+ costPriority: number$2().min(0).max(1).optional(),
18743
18914
  /**
18744
18915
  * How much to prioritize sampling speed (latency) when selecting a model.
18745
18916
  */
18746
- speedPriority: number$1().min(0).max(1).optional(),
18917
+ speedPriority: number$2().min(0).max(1).optional(),
18747
18918
  /**
18748
18919
  * How much to prioritize intelligence and capabilities when selecting a model.
18749
18920
  */
18750
- intelligencePriority: number$1().min(0).max(1).optional()
18921
+ intelligencePriority: number$2().min(0).max(1).optional()
18751
18922
  });
18752
18923
  /**
18753
18924
  * Controls tool usage behavior in sampling requests.
@@ -18837,13 +19008,13 @@ const CreateMessageRequestParamsSchema = TaskAugmentedRequestParamsSchema.extend
18837
19008
  "thisServer",
18838
19009
  "allServers"
18839
19010
  ]).optional(),
18840
- temperature: number$1().optional(),
19011
+ temperature: number$2().optional(),
18841
19012
  /**
18842
19013
  * The requested maximum number of tokens to sample (to prevent runaway completions).
18843
19014
  *
18844
19015
  * The client MAY choose to sample fewer tokens than the requested maximum.
18845
19016
  */
18846
- maxTokens: number$1().int(),
19017
+ maxTokens: number$2().int(),
18847
19018
  stopSequences: array(string()).optional(),
18848
19019
  /**
18849
19020
  * Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific.
@@ -18947,8 +19118,8 @@ const StringSchemaSchema = object$1({
18947
19118
  type: literal("string"),
18948
19119
  title: string().optional(),
18949
19120
  description: string().optional(),
18950
- minLength: number$1().optional(),
18951
- maxLength: number$1().optional(),
19121
+ minLength: number$2().optional(),
19122
+ maxLength: number$2().optional(),
18952
19123
  format: _enum([
18953
19124
  "email",
18954
19125
  "uri",
@@ -18964,9 +19135,9 @@ const NumberSchemaSchema = object$1({
18964
19135
  type: _enum(["number", "integer"]),
18965
19136
  title: string().optional(),
18966
19137
  description: string().optional(),
18967
- minimum: number$1().optional(),
18968
- maximum: number$1().optional(),
18969
- default: number$1().optional()
19138
+ minimum: number$2().optional(),
19139
+ maximum: number$2().optional(),
19140
+ default: number$2().optional()
18970
19141
  });
18971
19142
  /**
18972
19143
  * Schema for single-selection enumeration without display titles for options.
@@ -19009,8 +19180,8 @@ const PrimitiveSchemaDefinitionSchema = union([
19009
19180
  type: literal("array"),
19010
19181
  title: string().optional(),
19011
19182
  description: string().optional(),
19012
- minItems: number$1().optional(),
19013
- maxItems: number$1().optional(),
19183
+ minItems: number$2().optional(),
19184
+ maxItems: number$2().optional(),
19014
19185
  items: object$1({
19015
19186
  type: literal("string"),
19016
19187
  enum: array(string())
@@ -19020,8 +19191,8 @@ const PrimitiveSchemaDefinitionSchema = union([
19020
19191
  type: literal("array"),
19021
19192
  title: string().optional(),
19022
19193
  description: string().optional(),
19023
- minItems: number$1().optional(),
19024
- maxItems: number$1().optional(),
19194
+ minItems: number$2().optional(),
19195
+ maxItems: number$2().optional(),
19025
19196
  items: object$1({ anyOf: array(object$1({
19026
19197
  const: string(),
19027
19198
  title: string()
@@ -19126,7 +19297,7 @@ const ElicitResultSchema = ResultSchema.extend({
19126
19297
  */
19127
19298
  content: preprocess((val) => val === null ? void 0 : val, record(string(), union([
19128
19299
  string(),
19129
- number$1(),
19300
+ number$2(),
19130
19301
  boolean(),
19131
19302
  array(string())
19132
19303
  ])).optional())
@@ -19199,7 +19370,7 @@ const CompleteResultSchema = ResultSchema.extend({ completion: looseObject({
19199
19370
  /**
19200
19371
  * The total number of completion options available. This can exceed the number of values actually sent in the response.
19201
19372
  */
19202
- total: optional(number$1().int()),
19373
+ total: optional(number$2().int()),
19203
19374
  /**
19204
19375
  * Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown.
19205
19376
  */
@@ -28871,8 +29042,8 @@ const OAuthClientMetadataSchema = object$1({
28871
29042
  const OAuthClientInformationSchema = object$1({
28872
29043
  client_id: string(),
28873
29044
  client_secret: string().optional(),
28874
- client_id_issued_at: number$1().optional(),
28875
- client_secret_expires_at: number$1().optional()
29045
+ client_id_issued_at: number$2().optional(),
29046
+ client_secret_expires_at: number$2().optional()
28876
29047
  }).strip();
28877
29048
  /**
28878
29049
  * RFC 7591 OAuth 2.0 Dynamic Client Registration full response (client information plus metadata)
@@ -30743,8 +30914,45 @@ var FileOAuthProvider = class {
30743
30914
  headers.set("content-type", "application/x-www-form-urlencoded");
30744
30915
  };
30745
30916
  };
30746
- async function runOAuthFlow(server, options = {}) {
30917
+ async function startOAuthFlow(server, options) {
30747
30918
  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`);
30919
+ let redirectUrl;
30920
+ const provider = new FileOAuthProvider(server, options.redirectUri, (url) => {
30921
+ redirectUrl = url;
30922
+ options.print?.(`Open this URL to authorize ${server.server}:\n${url.toString()}`);
30923
+ }, options.authDir);
30924
+ const scope = scopesFor(server.auth);
30925
+ try {
30926
+ if (await auth(provider, {
30927
+ serverUrl: server.url,
30928
+ ...scope ? { scope } : {}
30929
+ }) === "AUTHORIZED") return {
30930
+ authorizationUrl: "",
30931
+ complete: async () => {}
30932
+ };
30933
+ } catch (error) {
30934
+ throw normalizeMcpOAuthError(server, error);
30935
+ }
30936
+ if (!redirectUrl) throw new CapletsError("AUTH_FAILED", "OAuth authorization URL was not provided");
30937
+ return {
30938
+ authorizationUrl: redirectUrl.toString(),
30939
+ complete: async (callbackUrl) => {
30940
+ assertNoOAuthCallbackError(server, callbackUrl);
30941
+ const completion = extractCompletion(callbackUrl);
30942
+ if (completion.state !== provider.state()) throw new CapletsError("AUTH_FAILED", "OAuth callback state did not match");
30943
+ try {
30944
+ await auth(provider, {
30945
+ serverUrl: server.url,
30946
+ authorizationCode: completion.code,
30947
+ ...scope ? { scope } : {}
30948
+ });
30949
+ } catch (error) {
30950
+ throw normalizeMcpOAuthError(server, error);
30951
+ }
30952
+ }
30953
+ };
30954
+ }
30955
+ async function runOAuthFlow(server, options = {}) {
30748
30956
  let callbackCode;
30749
30957
  let callbackState;
30750
30958
  const callback = await createLoopbackCallback((url) => {
@@ -30752,37 +30960,43 @@ async function runOAuthFlow(server, options = {}) {
30752
30960
  callbackCode = url.searchParams.get("code") ?? void 0;
30753
30961
  callbackState = url.searchParams.get("state") ?? void 0;
30754
30962
  });
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
30963
  try {
30761
- const scope = scopesFor(server.auth);
30762
- const first = await auth(provider, {
30763
- serverUrl: server.url,
30764
- ...scope ? { scope } : {}
30964
+ const started = await startOAuthFlow(server, {
30965
+ redirectUri: callback.redirectUri,
30966
+ ...options.authDir ? { authDir: options.authDir } : {},
30967
+ ...options.print ? { print: options.print } : {}
30765
30968
  });
30766
- if (first === "AUTHORIZED") return first;
30767
- if (!options.noOpen && redirectUrl) await (options.open ? options.open(redirectUrl.toString()) : openBrowser(redirectUrl.toString()));
30969
+ if (!started.authorizationUrl) return "AUTHORIZED";
30970
+ if (!options.noOpen) await (options.open ? options.open(started.authorizationUrl) : openBrowser$1(started.authorizationUrl));
30768
30971
  const manualInput = options.manualInput ?? (options.noOpen ? await options.readManualInput?.() : void 0);
30769
30972
  const completion = manualInput ? extractCompletion(manualInput) : await callback.waitForCode(() => callbackCode ? {
30770
30973
  code: callbackCode,
30771
30974
  ...callbackState ? { state: callbackState } : {}
30772
30975
  } : void 0);
30773
- const expectedState = provider.state();
30774
- if (completion.state !== expectedState) throw new CapletsError("AUTH_FAILED", "OAuth callback state did not match");
30775
- return await auth(provider, {
30776
- serverUrl: server.url,
30777
- authorizationCode: completion.code,
30778
- ...scope ? { scope } : {}
30779
- });
30976
+ await started.complete(completion.state ? `${callback.redirectUri}?code=${encodeURIComponent(completion.code)}&state=${encodeURIComponent(completion.state)}` : `${callback.redirectUri}?code=${encodeURIComponent(completion.code)}`);
30977
+ return "AUTHORIZED";
30780
30978
  } catch (error) {
30781
30979
  throw normalizeMcpOAuthError(server, error);
30782
30980
  } finally {
30783
30981
  await callback.close();
30784
30982
  }
30785
30983
  }
30984
+ function assertNoOAuthCallbackError(target, callbackUrl) {
30985
+ let url;
30986
+ try {
30987
+ url = new URL(callbackUrl);
30988
+ } catch {
30989
+ return;
30990
+ }
30991
+ const error = url.searchParams.get("error");
30992
+ if (!error) return;
30993
+ const description = url.searchParams.get("error_description");
30994
+ throw new CapletsError("AUTH_FAILED", description ? `OAuth provider returned an error: ${description}` : "OAuth provider returned an error", redactSecrets({
30995
+ server: target.server,
30996
+ error,
30997
+ error_description: description ?? void 0
30998
+ }));
30999
+ }
30786
31000
  function normalizeMcpOAuthError(server, error) {
30787
31001
  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
31002
  server: server.server,
@@ -30790,6 +31004,75 @@ function normalizeMcpOAuthError(server, error) {
30790
31004
  });
30791
31005
  return error;
30792
31006
  }
31007
+ async function startGenericOAuthFlow(target, options) {
31008
+ if (target.auth?.type !== "oauth2" && target.auth?.type !== "oidc") throw new CapletsError("REQUEST_INVALID", `${target.server} is not configured for OAuth`);
31009
+ const authConfig = target.auth;
31010
+ const redirectUri = authConfig.redirectUri ?? options.redirectUri;
31011
+ const verifier = base64url(randomBytes(32));
31012
+ const state = base64url(randomBytes(24));
31013
+ const allowLoopbackHttp = isLoopbackDevelopmentTarget(target, authConfig);
31014
+ const metadata = await discoverAuthorizationServer(target, authConfig, allowLoopbackHttp);
31015
+ const authorizationEndpoint = authConfig.authorizationUrl ?? metadata.authorization_endpoint;
31016
+ const tokenEndpoint = authConfig.tokenUrl ?? metadata.token_endpoint;
31017
+ if (!authorizationEndpoint || !tokenEndpoint) throw new CapletsError("AUTH_FAILED", "OAuth metadata is missing endpoints", { server: target.server });
31018
+ assertAllowedAuthUrl(authorizationEndpoint, "authorization endpoint", allowLoopbackHttp);
31019
+ assertAllowedAuthUrl(tokenEndpoint, "token endpoint", allowLoopbackHttp);
31020
+ const client = await resolveGenericClient(target, authConfig, metadata, redirectUri, allowLoopbackHttp);
31021
+ const scope = scopesFor(authConfig);
31022
+ const authorizationUrl = new URL(authorizationEndpoint);
31023
+ authorizationUrl.searchParams.set("response_type", "code");
31024
+ authorizationUrl.searchParams.set("client_id", client.clientId);
31025
+ authorizationUrl.searchParams.set("redirect_uri", redirectUri);
31026
+ authorizationUrl.searchParams.set("code_challenge", pkceChallenge(verifier));
31027
+ authorizationUrl.searchParams.set("code_challenge_method", "S256");
31028
+ authorizationUrl.searchParams.set("state", state);
31029
+ if (scope) authorizationUrl.searchParams.set("scope", scope);
31030
+ options.print?.(`Open this URL to authorize ${target.server}:\n${authorizationUrl.toString()}`);
31031
+ return {
31032
+ authorizationUrl: authorizationUrl.toString(),
31033
+ complete: async (callbackUrl) => {
31034
+ assertNoOAuthCallbackError(target, callbackUrl);
31035
+ const completion = extractCompletion(callbackUrl);
31036
+ if (completion.state !== state) throw new CapletsError("AUTH_FAILED", "OAuth callback state did not match");
31037
+ const params = new URLSearchParams({
31038
+ grant_type: "authorization_code",
31039
+ code: completion.code,
31040
+ redirect_uri: redirectUri,
31041
+ client_id: client.clientId,
31042
+ code_verifier: verifier
31043
+ });
31044
+ if (client.clientSecret) params.set("client_secret", client.clientSecret);
31045
+ const tokenResponse = await fetchJson(tokenEndpoint, target.requestTimeoutMs, {
31046
+ method: "POST",
31047
+ headers: { "content-type": "application/x-www-form-urlencoded" },
31048
+ body: params.toString()
31049
+ }, allowLoopbackHttp);
31050
+ const idToken = asString(tokenResponse.id_token);
31051
+ const idClaims = parseJwtPayload(idToken);
31052
+ validateOidcToken(authConfig, metadata, idToken, idClaims, client.clientId);
31053
+ writeTokenBundle(stripUndefined({
31054
+ server: target.server,
31055
+ authType: authConfig.type,
31056
+ accessToken: requireString(tokenResponse.access_token, "access_token"),
31057
+ refreshToken: asString(tokenResponse.refresh_token),
31058
+ tokenType: asString(tokenResponse.token_type),
31059
+ expiresAt: typeof tokenResponse.expires_in === "number" ? new Date(Date.now() + tokenResponse.expires_in * 1e3).toISOString() : void 0,
31060
+ scope: asString(tokenResponse.scope) ?? scope,
31061
+ idToken,
31062
+ issuer: asString(idClaims?.iss) ?? metadata.issuer ?? authConfig.issuer,
31063
+ subject: asString(idClaims?.sub),
31064
+ clientId: client.clientId,
31065
+ clientSecret: client.clientSecret,
31066
+ protectedResourceOrigin: protectedResourceOrigin(target, authConfig),
31067
+ metadata: redactSecrets({
31068
+ protectedResource: target.url ?? target.baseUrl ?? target.specUrl,
31069
+ authorizationServer: metadata,
31070
+ dynamicClient: client.dynamic ? { client_id: client.clientId } : void 0
31071
+ })
31072
+ }), options.authDir);
31073
+ }
31074
+ };
31075
+ }
30793
31076
  async function runGenericOAuthFlow(target, options = {}) {
30794
31077
  if (target.auth?.type !== "oauth2" && target.auth?.type !== "oidc") throw new CapletsError("REQUEST_INVALID", `${target.server} is not configured for OAuth`);
30795
31078
  const authConfig = target.auth;
@@ -30822,7 +31105,7 @@ async function runGenericOAuthFlow(target, options = {}) {
30822
31105
  authorizationUrl.searchParams.set("state", state);
30823
31106
  if (scope) authorizationUrl.searchParams.set("scope", scope);
30824
31107
  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()));
31108
+ if (!options.noOpen) await (options.open ? options.open(authorizationUrl.toString()) : openBrowser$1(authorizationUrl.toString()));
30826
31109
  const manualInput = options.manualInput ?? (options.noOpen ? await options.readManualInput?.() : void 0);
30827
31110
  const completion = manualInput ? extractCompletion(manualInput) : await callback.waitForCode(() => callbackCode ? {
30828
31111
  code: callbackCode,
@@ -30925,7 +31208,7 @@ async function createLoopbackCallback(onCallback) {
30925
31208
  close: () => new Promise((resolve) => server.close(() => resolve()))
30926
31209
  };
30927
31210
  }
30928
- async function openBrowser(url) {
31211
+ async function openBrowser$1(url) {
30929
31212
  const { spawn } = await import("node:child_process");
30930
31213
  spawn(process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open", process.platform === "win32" ? [
30931
31214
  "/c",
@@ -31135,14 +31418,28 @@ var DownstreamManager = class {
31135
31418
  async checkServer(server) {
31136
31419
  const startedAt = Date.now();
31137
31420
  try {
31421
+ const capabilities = (await this.connect(server)).client.getServerCapabilities() ?? {};
31138
31422
  const tools = await this.refreshTools(server, true);
31139
31423
  this.registry.setStatus(server.server, "available");
31140
- return {
31424
+ const result = {
31141
31425
  id: server.server,
31142
31426
  status: "available",
31427
+ capabilities: {
31428
+ tools: Boolean(capabilities.tools),
31429
+ resources: Boolean(capabilities.resources),
31430
+ resourceTemplates: Boolean(capabilities.resources),
31431
+ prompts: Boolean(capabilities.prompts),
31432
+ completions: Boolean(capabilities.completions)
31433
+ },
31143
31434
  toolCount: tools.length,
31144
31435
  elapsedMs: Date.now() - startedAt
31145
31436
  };
31437
+ if (capabilities.resources) Object.assign(result, {
31438
+ resourceCount: (await this.listResources(server, true)).length,
31439
+ resourceTemplateCount: (await this.listResourceTemplates(server, true)).length
31440
+ });
31441
+ if (capabilities.prompts) Object.assign(result, { promptCount: (await this.listPrompts(server, true)).length });
31442
+ return result;
31146
31443
  } catch (error) {
31147
31444
  const safe = toSafeError(error, "SERVER_UNAVAILABLE");
31148
31445
  this.registry.setStatus(server.server, "unavailable", safe);
@@ -31184,6 +31481,86 @@ var DownstreamManager = class {
31184
31481
  throw new CapletsError("DOWNSTREAM_TOOL_ERROR", `Downstream tool failed for ${server.server}/${toolName}`, toSafeError(error));
31185
31482
  }
31186
31483
  }
31484
+ async listResources(server, force = false) {
31485
+ const connection = await this.assertCapability(server, "resources");
31486
+ if (!force && connection.resources && this.isCacheFresh(connection.resourcesFetchedAt, server.toolCacheTtlMs)) return connection.resources;
31487
+ const resources = [];
31488
+ let cursor;
31489
+ do {
31490
+ const result = await connection.client.listResources(cursor ? { cursor } : void 0, { timeout: server.startupTimeoutMs });
31491
+ resources.push(...result.resources ?? []);
31492
+ cursor = result.nextCursor;
31493
+ } while (cursor);
31494
+ connection.resources = resources;
31495
+ connection.resourcesFetchedAt = Date.now();
31496
+ return resources;
31497
+ }
31498
+ async listResourceTemplates(server, force = false) {
31499
+ const connection = await this.assertCapability(server, "resources");
31500
+ if (!force && connection.resourceTemplates && this.isCacheFresh(connection.resourceTemplatesFetchedAt, server.toolCacheTtlMs)) return connection.resourceTemplates;
31501
+ const resourceTemplates = [];
31502
+ let cursor;
31503
+ do {
31504
+ const result = await connection.client.listResourceTemplates(cursor ? { cursor } : void 0, { timeout: server.startupTimeoutMs });
31505
+ resourceTemplates.push(...result.resourceTemplates ?? []);
31506
+ cursor = result.nextCursor;
31507
+ } while (cursor);
31508
+ connection.resourceTemplates = resourceTemplates;
31509
+ connection.resourceTemplatesFetchedAt = Date.now();
31510
+ return resourceTemplates;
31511
+ }
31512
+ async readResource(server, uri) {
31513
+ const connection = await this.assertCapability(server, "resources");
31514
+ try {
31515
+ return await connection.client.readResource({ uri }, { timeout: server.callTimeoutMs });
31516
+ } catch (error) {
31517
+ throw new CapletsError("DOWNSTREAM_RESOURCE_ERROR", `Downstream resource read failed for ${server.server}/${uri}`, toSafeError(error));
31518
+ }
31519
+ }
31520
+ async listPrompts(server, force = false) {
31521
+ const connection = await this.assertCapability(server, "prompts");
31522
+ if (!force && connection.prompts && this.isCacheFresh(connection.promptsFetchedAt, server.toolCacheTtlMs)) return connection.prompts;
31523
+ const prompts = [];
31524
+ let cursor;
31525
+ do {
31526
+ const result = await connection.client.listPrompts(cursor ? { cursor } : void 0, { timeout: server.startupTimeoutMs });
31527
+ prompts.push(...result.prompts ?? []);
31528
+ cursor = result.nextCursor;
31529
+ } while (cursor);
31530
+ connection.prompts = prompts;
31531
+ connection.promptsFetchedAt = Date.now();
31532
+ return prompts;
31533
+ }
31534
+ async getPrompt(server, promptName, args) {
31535
+ if (!(await this.listPrompts(server)).some((prompt) => prompt.name === promptName)) throw new CapletsError("PROMPT_NOT_FOUND", `Prompt ${promptName} was not found on ${server.server}`);
31536
+ const connection = await this.connect(server);
31537
+ try {
31538
+ return await connection.client.getPrompt({
31539
+ name: promptName,
31540
+ arguments: stringifyPromptArgs(args)
31541
+ }, { timeout: server.callTimeoutMs });
31542
+ } catch (error) {
31543
+ throw new CapletsError("DOWNSTREAM_PROMPT_ERROR", `Downstream prompt failed for ${server.server}/${promptName}`, toSafeError(error));
31544
+ }
31545
+ }
31546
+ async complete(server, request) {
31547
+ const connection = await this.assertCapability(server, "completions");
31548
+ const params = {
31549
+ ref: request.ref.type === "prompt" ? {
31550
+ type: "ref/prompt",
31551
+ name: request.ref.name
31552
+ } : {
31553
+ type: "ref/resource",
31554
+ uri: request.ref.uri
31555
+ },
31556
+ argument: request.argument
31557
+ };
31558
+ try {
31559
+ return await connection.client.complete(params, { timeout: server.callTimeoutMs });
31560
+ } catch (error) {
31561
+ throw new CapletsError("DOWNSTREAM_COMPLETION_ERROR", `Downstream completion failed for ${server.server}`, toSafeError(error));
31562
+ }
31563
+ }
31187
31564
  compact(server, tool) {
31188
31565
  return {
31189
31566
  id: server.server,
@@ -31193,9 +31570,75 @@ var DownstreamManager = class {
31193
31570
  hasOutputSchema: Boolean(tool.outputSchema)
31194
31571
  };
31195
31572
  }
31573
+ compactResource(server, resource) {
31574
+ return {
31575
+ id: server.server,
31576
+ kind: "resource",
31577
+ uri: resource.uri,
31578
+ ...resource.name ? { name: resource.name } : {},
31579
+ ...resource.description ? { description: resource.description } : {},
31580
+ ...resource.mimeType ? { mimeType: resource.mimeType } : {},
31581
+ ...typeof resource.size === "number" ? { size: resource.size } : {}
31582
+ };
31583
+ }
31584
+ compactResourceTemplate(server, template) {
31585
+ return {
31586
+ id: server.server,
31587
+ kind: "resourceTemplate",
31588
+ uriTemplate: template.uriTemplate,
31589
+ ...template.name ? { name: template.name } : {},
31590
+ ...template.description ? { description: template.description } : {},
31591
+ ...template.mimeType ? { mimeType: template.mimeType } : {}
31592
+ };
31593
+ }
31594
+ compactPrompt(server, prompt) {
31595
+ return {
31596
+ id: server.server,
31597
+ prompt: prompt.name,
31598
+ ...prompt.description ? { description: prompt.description } : {},
31599
+ ...prompt.arguments ? { arguments: prompt.arguments } : {}
31600
+ };
31601
+ }
31602
+ searchResources(server, resources, query, limit) {
31603
+ const lower = query.toLocaleLowerCase();
31604
+ return resources.map((resource) => this.compactResource(server, resource)).filter((resource) => [
31605
+ resource.uri,
31606
+ resource.name,
31607
+ resource.description,
31608
+ resource.mimeType
31609
+ ].some((value) => value?.toLocaleLowerCase().includes(lower))).slice(0, limit);
31610
+ }
31611
+ searchResourceTemplates(server, templates, query, limit) {
31612
+ const lower = query.toLocaleLowerCase();
31613
+ return templates.map((template) => this.compactResourceTemplate(server, template)).filter((template) => [
31614
+ template.uriTemplate,
31615
+ template.name,
31616
+ template.description,
31617
+ template.mimeType
31618
+ ].some((value) => value?.toLocaleLowerCase().includes(lower))).slice(0, limit);
31619
+ }
31620
+ searchPrompts(server, prompts, query, limit) {
31621
+ const lower = query.toLocaleLowerCase();
31622
+ return prompts.map((prompt) => this.compactPrompt(server, prompt)).filter((prompt) => [
31623
+ prompt.prompt,
31624
+ prompt.description,
31625
+ ...(prompt.arguments ?? []).flatMap((arg) => [arg.name, arg.description])
31626
+ ].some((value) => value?.toLocaleLowerCase().includes(lower))).slice(0, limit);
31627
+ }
31196
31628
  search(server, tools, query, limit) {
31197
31629
  return searchToolList(tools, query, limit, (tool) => this.compact(server, tool));
31198
31630
  }
31631
+ async assertCapability(server, capability) {
31632
+ const connection = await this.connect(server);
31633
+ if (!connection.client.getServerCapabilities()?.[capability]) throw new CapletsError("UNSUPPORTED_CAPABILITY", `${server.server} does not advertise MCP ${capability}`, {
31634
+ server: server.server,
31635
+ capability
31636
+ });
31637
+ return connection;
31638
+ }
31639
+ isCacheFresh(fetchedAt, ttlMs) {
31640
+ return fetchedAt !== void 0 && ttlMs > 0 && Date.now() - fetchedAt <= ttlMs;
31641
+ }
31199
31642
  async refreshTools(server, force) {
31200
31643
  const connection = await this.connect(server);
31201
31644
  const now = Date.now();
@@ -31239,6 +31682,20 @@ var DownstreamManager = class {
31239
31682
  transport,
31240
31683
  configFingerprint: expectedFingerprint
31241
31684
  };
31685
+ client.setNotificationHandler(ToolListChangedNotificationSchema, () => {
31686
+ connection.tools = void 0;
31687
+ connection.toolsFetchedAt = void 0;
31688
+ });
31689
+ client.setNotificationHandler(ResourceListChangedNotificationSchema, () => {
31690
+ connection.resources = void 0;
31691
+ connection.resourcesFetchedAt = void 0;
31692
+ connection.resourceTemplates = void 0;
31693
+ connection.resourceTemplatesFetchedAt = void 0;
31694
+ });
31695
+ client.setNotificationHandler(PromptListChangedNotificationSchema, () => {
31696
+ connection.prompts = void 0;
31697
+ connection.promptsFetchedAt = void 0;
31698
+ });
31242
31699
  pendingConnection = connection;
31243
31700
  this.connecting.set(server.server, connection);
31244
31701
  transport.onclose = () => {
@@ -31369,6 +31826,18 @@ function nearbyToolNames(tools, needle) {
31369
31826
  function isTimeoutLike(error) {
31370
31827
  return error instanceof Error && /timeout|timed out|aborted/i.test(error.message);
31371
31828
  }
31829
+ function stringifyPromptArgs(args) {
31830
+ const stringified = {};
31831
+ for (const [key, value] of Object.entries(args)) {
31832
+ if (typeof value === "string") {
31833
+ stringified[key] = value;
31834
+ continue;
31835
+ }
31836
+ const serialized = JSON.stringify(value);
31837
+ if (typeof serialized === "string") stringified[key] = serialized;
31838
+ }
31839
+ return stringified;
31840
+ }
31372
31841
  function isAuthRemediationError(error) {
31373
31842
  return error instanceof CapletsError && (error.code === "AUTH_REQUIRED" || error.code === "AUTH_FAILED");
31374
31843
  }
@@ -56230,7 +56699,7 @@ function capabilityDescription(server) {
56230
56699
  return [
56231
56700
  `${server.name} Caplet.`,
56232
56701
  server.description,
56233
- "Use get_caplet for details when needed; use search_tools or list_tools to discover downstream operations."
56702
+ server.backend === "mcp" ? "Use get_caplet for details when needed; use tools for actions, resources for readable context, prompts for reusable workflows, and complete for prompt/resource-template arguments." : "Use get_caplet for details when needed; use search_tools or list_tools to discover downstream operations."
56234
56703
  ].filter(Boolean).join(" ");
56235
56704
  }
56236
56705
  var ServerRegistry = class {
@@ -56446,17 +56915,9 @@ function cloneJsonValue(value) {
56446
56915
  function throwInvalid(message) {
56447
56916
  throw new CapletsError("REQUEST_INVALID", message);
56448
56917
  }
56449
- const generatedToolInputSchema = object$1({
56450
- operation: _enum(operations).describe(generatedToolInputDescriptions.operation),
56451
- query: string().optional().describe(generatedToolInputDescriptions.query),
56452
- limit: number$1().int().positive().optional().describe(generatedToolInputDescriptions.limit),
56453
- tool: string().optional().describe(generatedToolInputDescriptions.tool),
56454
- arguments: record(string(), unknown()).optional().describe(generatedToolInputDescriptions.arguments),
56455
- fields: array(string().min(1)).min(1).optional().describe(generatedToolInputDescriptions.fields)
56456
- }).strict();
56457
56918
  async function handleServerTool(server, request, registry, downstream, openapi, graphql, http, cli, caplets) {
56458
56919
  const startedAt = Date.now();
56459
- const parsed = validateOperationRequest(request, registry.config.options.maxSearchLimit);
56920
+ const parsed = validateOperationRequest(request, registry.config.options.maxSearchLimit, server.backend);
56460
56921
  switch (parsed.operation) {
56461
56922
  case "get_caplet": return jsonResult(registry.detail(server), metadataFor(server, "get_caplet", void 0, startedAt));
56462
56923
  case "check_backend": return jsonResult(await backendFor(server, downstream, openapi, graphql, http, cli, caplets).check(server), metadataFor(server, "check_backend", void 0, startedAt));
@@ -56497,11 +56958,75 @@ async function handleServerTool(server, request, registry, downstream, openapi,
56497
56958
  validateFieldSelection(tool.outputSchema, parsed.fields);
56498
56959
  return annotateCallToolResult(projectCallToolResult(await backend.callTool(server, parsed.tool, parsed.arguments), tool.outputSchema, parsed.fields), metadataFor(server, "call_tool", parsed.tool, startedAt));
56499
56960
  }
56961
+ case "list_resources": {
56962
+ const backend = mcpBackendFor(server, downstream);
56963
+ const resources = await backend.listResources(server);
56964
+ const templates = await backend.listResourceTemplates(server);
56965
+ const limit = parsed.limit ?? resources.length + templates.length;
56966
+ return jsonResult({
56967
+ id: server.server,
56968
+ name: server.name,
56969
+ resources: resources.slice(0, limit).map((resource) => backend.compactResource(server, resource)),
56970
+ resourceTemplates: templates.slice(0, Math.max(0, limit - resources.length)).map((template) => backend.compactResourceTemplate(server, template))
56971
+ }, metadataFor(server, "list_resources", void 0, startedAt));
56972
+ }
56973
+ case "search_resources": {
56974
+ const backend = mcpBackendFor(server, downstream);
56975
+ const resources = await backend.listResources(server);
56976
+ const templates = await backend.listResourceTemplates(server);
56977
+ const limit = parsed.limit ?? registry.config.options.defaultSearchLimit;
56978
+ const resourceMatches = backend.searchResources(server, resources, parsed.query, limit);
56979
+ const templateMatches = backend.searchResourceTemplates(server, templates, parsed.query, Math.max(0, limit - resourceMatches.length));
56980
+ return jsonResult({
56981
+ id: server.server,
56982
+ name: server.name,
56983
+ query: parsed.query,
56984
+ matches: [...resourceMatches, ...templateMatches]
56985
+ }, metadataFor(server, "search_resources", void 0, startedAt));
56986
+ }
56987
+ case "list_resource_templates": {
56988
+ const backend = mcpBackendFor(server, downstream);
56989
+ const templates = await backend.listResourceTemplates(server);
56990
+ const limit = parsed.limit ?? templates.length;
56991
+ return jsonResult({
56992
+ id: server.server,
56993
+ name: server.name,
56994
+ resourceTemplates: templates.slice(0, limit).map((template) => backend.compactResourceTemplate(server, template))
56995
+ }, metadataFor(server, "list_resource_templates", void 0, startedAt));
56996
+ }
56997
+ case "read_resource": return annotateMcpResult(await mcpBackendFor(server, downstream).readResource(server, parsed.uri), metadataFor(server, "read_resource", { uri: parsed.uri }, startedAt));
56998
+ case "list_prompts": {
56999
+ const backend = mcpBackendFor(server, downstream);
57000
+ const prompts = await backend.listPrompts(server);
57001
+ const limit = parsed.limit ?? prompts.length;
57002
+ return jsonResult({
57003
+ id: server.server,
57004
+ name: server.name,
57005
+ prompts: prompts.slice(0, limit).map((prompt) => backend.compactPrompt(server, prompt))
57006
+ }, metadataFor(server, "list_prompts", void 0, startedAt));
57007
+ }
57008
+ case "search_prompts": {
57009
+ const backend = mcpBackendFor(server, downstream);
57010
+ const prompts = await backend.listPrompts(server);
57011
+ const limit = parsed.limit ?? registry.config.options.defaultSearchLimit;
57012
+ return jsonResult({
57013
+ id: server.server,
57014
+ name: server.name,
57015
+ query: parsed.query,
57016
+ prompts: backend.searchPrompts(server, prompts, parsed.query, limit)
57017
+ }, metadataFor(server, "search_prompts", void 0, startedAt));
57018
+ }
57019
+ case "get_prompt": return annotateMcpResult(await mcpBackendFor(server, downstream).getPrompt(server, parsed.prompt, parsed.arguments), metadataFor(server, "get_prompt", { prompt: parsed.prompt }, startedAt));
57020
+ case "complete": return annotateMcpResult(await mcpBackendFor(server, downstream).complete(server, {
57021
+ ref: parsed.ref,
57022
+ argument: parsed.argument
57023
+ }), metadataFor(server, "complete", void 0, startedAt));
56500
57024
  }
56501
57025
  }
56502
- function validateOperationRequest(request, maxSearchLimit) {
56503
- if (request && typeof request === "object" && "operation" in request && typeof request.operation === "string" && !operations.includes(request.operation)) throw new CapletsError("UNKNOWN_OPERATION", `Unknown operation: ${request.operation}`);
56504
- const result = generatedToolInputSchema.safeParse(request);
57026
+ function validateOperationRequest(request, maxSearchLimit, backend = "tool") {
57027
+ const result = generatedToolInputSchemaForCaplet({ backend }).safeParse(request);
57028
+ if (request && typeof request === "object" && "operation" in request && typeof request.operation === "string" && !mcpOperations.includes(request.operation)) throw new CapletsError("UNKNOWN_OPERATION", `Unknown operation: ${request.operation}`);
57029
+ if (request && typeof request === "object" && "operation" in request && typeof request.operation === "string" && backend !== "mcp" && mcpOperations.includes(request.operation) && !operations.includes(request.operation)) throw new CapletsError("UNSUPPORTED_OPERATION", `${request.operation} is only available for MCP-backed Caplets`);
56505
57030
  if (!result.success) throw new CapletsError("REQUEST_INVALID", "Generated server tool request is invalid", result.error.issues);
56506
57031
  const value = result.data;
56507
57032
  const keys = Object.keys(value).sort();
@@ -56548,7 +57073,7 @@ function validateOperationRequest(request, maxSearchLimit) {
56548
57073
  "fields"
56549
57074
  ]);
56550
57075
  if (!value.tool) throw new CapletsError("REQUEST_INVALID", "call_tool requires tool");
56551
- if (!isPlainObject$8(value.arguments)) throw new CapletsError("REQUEST_INVALID", "call_tool.arguments must be a JSON object");
57076
+ if (!isPlainObject$7(value.arguments)) throw new CapletsError("REQUEST_INVALID", "call_tool.arguments must be a JSON object");
56552
57077
  return value.fields === void 0 ? {
56553
57078
  operation: "call_tool",
56554
57079
  tool: value.tool,
@@ -56559,23 +57084,82 @@ function validateOperationRequest(request, maxSearchLimit) {
56559
57084
  arguments: value.arguments,
56560
57085
  fields: value.fields
56561
57086
  };
57087
+ case "list_resources":
57088
+ case "list_resource_templates":
57089
+ case "list_prompts":
57090
+ allowed(["limit"]);
57091
+ if (value.limit !== void 0 && value.limit > maxSearchLimit) throw new CapletsError("REQUEST_INVALID", `${value.operation} limit must be <= ${maxSearchLimit}`);
57092
+ return value.limit === void 0 ? { operation: value.operation } : {
57093
+ operation: value.operation,
57094
+ limit: value.limit
57095
+ };
57096
+ case "search_resources":
57097
+ case "search_prompts":
57098
+ allowed(["query", "limit"]);
57099
+ if (!value.query) throw new CapletsError("REQUEST_INVALID", `${value.operation} requires query`);
57100
+ if (value.limit !== void 0 && value.limit > maxSearchLimit) throw new CapletsError("REQUEST_INVALID", `${value.operation} limit must be <= ${maxSearchLimit}`);
57101
+ return value.limit === void 0 ? {
57102
+ operation: value.operation,
57103
+ query: value.query
57104
+ } : {
57105
+ operation: value.operation,
57106
+ query: value.query,
57107
+ limit: value.limit
57108
+ };
57109
+ case "read_resource":
57110
+ allowed(["uri"]);
57111
+ if (!value.uri) throw new CapletsError("REQUEST_INVALID", "read_resource requires uri");
57112
+ return {
57113
+ operation: "read_resource",
57114
+ uri: value.uri
57115
+ };
57116
+ case "get_prompt":
57117
+ allowed(["prompt", "arguments"]);
57118
+ if (!value.prompt) throw new CapletsError("REQUEST_INVALID", "get_prompt requires prompt");
57119
+ if (value.arguments !== void 0 && !isPlainObject$7(value.arguments)) throw new CapletsError("REQUEST_INVALID", "get_prompt.arguments must be a JSON object");
57120
+ return {
57121
+ operation: "get_prompt",
57122
+ prompt: value.prompt,
57123
+ arguments: value.arguments ?? {}
57124
+ };
57125
+ case "complete":
57126
+ allowed(["ref", "argument"]);
57127
+ if (!value.ref) throw new CapletsError("REQUEST_INVALID", "complete requires ref");
57128
+ if (!value.argument) throw new CapletsError("REQUEST_INVALID", "complete requires argument");
57129
+ return {
57130
+ operation: "complete",
57131
+ ref: value.ref,
57132
+ argument: value.argument
57133
+ };
56562
57134
  }
56563
- return assertNever(value.operation);
57135
+ throw new CapletsError("INTERNAL_ERROR", "Unhandled operation");
56564
57136
  }
56565
- function assertNever(value) {
56566
- throw new CapletsError("INTERNAL_ERROR", `Unhandled operation: ${String(value)}`);
57137
+ function mcpBackendFor(server, downstream) {
57138
+ if (server.backend !== "mcp") throw new CapletsError("UNSUPPORTED_OPERATION", "MCP resource, prompt, and completion operations require an MCP-backed Caplet");
57139
+ return downstream;
56567
57140
  }
56568
- function metadataFor(server, operation, tool, startedAt) {
57141
+ function metadataFor(server, operation, target, startedAt) {
57142
+ const targetFields = typeof target === "string" ? { tool: target } : target ?? {};
56569
57143
  return {
56570
57144
  id: server.server,
56571
57145
  name: server.name,
56572
57146
  backend: server.backend,
56573
57147
  operation,
56574
- ...tool === void 0 ? {} : { tool },
57148
+ ...targetFields,
56575
57149
  status: "ok",
56576
57150
  ...startedAt === void 0 ? {} : { elapsedMs: Date.now() - startedAt }
56577
57151
  };
56578
57152
  }
57153
+ function annotateMcpResult(result, metadata) {
57154
+ const existingMeta = result._meta;
57155
+ return {
57156
+ ...result,
57157
+ _meta: {
57158
+ ...isPlainObject$7(existingMeta) ? existingMeta : {},
57159
+ caplets: metadata
57160
+ }
57161
+ };
57162
+ }
56579
57163
  function jsonResult(value, metadata) {
56580
57164
  return {
56581
57165
  content: [{
@@ -56599,7 +57183,7 @@ function annotateCallToolResult(result, metadata) {
56599
57183
  return {
56600
57184
  ...result,
56601
57185
  _meta: {
56602
- ...isPlainObject$8(existingMeta) ? existingMeta : {},
57186
+ ...isPlainObject$7(existingMeta) ? existingMeta : {},
56603
57187
  caplets: annotatedMetadata
56604
57188
  }
56605
57189
  };
@@ -56607,7 +57191,7 @@ function annotateCallToolResult(result, metadata) {
56607
57191
  function projectCallToolResult(result, outputSchema, fields) {
56608
57192
  if (result.isError === true) return result;
56609
57193
  const structuredContent = result.structuredContent;
56610
- if (!isPlainObject$8(structuredContent)) throw new CapletsError("DOWNSTREAM_PROTOCOL_ERROR", "Field selection requires the downstream tool to return object structuredContent");
57194
+ if (!isPlainObject$7(structuredContent)) throw new CapletsError("DOWNSTREAM_PROTOCOL_ERROR", "Field selection requires the downstream tool to return object structuredContent");
56611
57195
  const projected = projectStructuredContent(structuredContent, outputSchema, fields);
56612
57196
  return {
56613
57197
  ...result,
@@ -56616,11 +57200,11 @@ function projectCallToolResult(result, outputSchema, fields) {
56616
57200
  };
56617
57201
  }
56618
57202
  function extractArtifacts(result) {
56619
- if (!isPlainObject$8(result) || !Array.isArray(result.content)) return [];
57203
+ if (!isPlainObject$7(result) || !Array.isArray(result.content)) return [];
56620
57204
  const artifacts = [];
56621
57205
  const seen = /* @__PURE__ */ new Set();
56622
57206
  for (const item of result.content) {
56623
- if (!isPlainObject$8(item) || item.type !== "text" || typeof item.text !== "string") continue;
57207
+ if (!isPlainObject$7(item) || item.type !== "text" || typeof item.text !== "string") continue;
56624
57208
  const text = item.text;
56625
57209
  for (const link of parseMarkdownLinks(text)) {
56626
57210
  const label = link.label;
@@ -56735,7 +57319,7 @@ function artifactKindFromText(text) {
56735
57319
  if (/network[-_ ]?(?:log)?|har\b/.test(text)) return "network-log";
56736
57320
  return "file";
56737
57321
  }
56738
- function isPlainObject$8(value) {
57322
+ function isPlainObject$7(value) {
56739
57323
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
56740
57324
  }
56741
57325
  function backendFor(server, downstream, openapi, graphql, http, cli, caplets) {
@@ -57043,6 +57627,38 @@ var CapletsEngine = class {
57043
57627
  return errorResult(error);
57044
57628
  }
57045
57629
  }
57630
+ async completeCliWords(words) {
57631
+ const { completeCliWords } = await Promise.resolve().then(() => completion_CxGG6ae3_exports).then((n) => n.r);
57632
+ return await completeCliWords(words, {
57633
+ config: this.registry.config,
57634
+ managers: {
57635
+ listTools: async (server) => this.listCompletionTools(server),
57636
+ listPrompts: async (server) => {
57637
+ if (server.backend !== "mcp") return [];
57638
+ return (await this.downstream.listPrompts(server)).map((prompt) => ({
57639
+ name: prompt.name,
57640
+ ...prompt.description ? { description: prompt.description } : {}
57641
+ }));
57642
+ },
57643
+ listResources: async (server) => {
57644
+ if (server.backend !== "mcp") return [];
57645
+ return (await this.downstream.listResources(server)).map((resource) => ({
57646
+ uri: resource.uri,
57647
+ ...resource.name ? { name: resource.name } : {},
57648
+ ...resource.description ? { description: resource.description } : {}
57649
+ }));
57650
+ },
57651
+ listResourceTemplates: async (server) => {
57652
+ if (server.backend !== "mcp") return [];
57653
+ return (await this.downstream.listResourceTemplates(server)).map((template) => ({
57654
+ uriTemplate: template.uriTemplate,
57655
+ ...template.name ? { name: template.name } : {},
57656
+ ...template.description ? { description: template.description } : {}
57657
+ }));
57658
+ }
57659
+ }
57660
+ });
57661
+ }
57046
57662
  async close() {
57047
57663
  this.closed = true;
57048
57664
  try {
@@ -57062,6 +57678,12 @@ var CapletsEngine = class {
57062
57678
  this.reloadListeners.clear();
57063
57679
  }
57064
57680
  }
57681
+ async listCompletionTools(server) {
57682
+ return (server.backend === "mcp" ? await this.downstream.listTools(server) : server.backend === "openapi" ? await this.openapi.listTools(server) : server.backend === "graphql" ? await this.graphql.listTools(server) : server.backend === "http" ? await this.http.listTools(server) : server.backend === "cli" ? await this.cli.listTools(server) : await this.capletSets.listTools(server)).map((tool) => ({
57683
+ name: tool.name,
57684
+ ...tool.description ? { description: tool.description } : {}
57685
+ }));
57686
+ }
57065
57687
  async reloadOnce() {
57066
57688
  if (this.closed) return false;
57067
57689
  let nextConfig;
@@ -57259,6 +57881,649 @@ function isDirectory(path) {
57259
57881
  return false;
57260
57882
  }
57261
57883
  }
57884
+ const DEFAULT_SERVER_USER = "caplets";
57885
+ function resolveCapletsMode(input = {}, env = process.env) {
57886
+ const mode = parseCapletsMode(input.mode ?? env.CAPLETS_MODE ?? "auto");
57887
+ if (mode === "local") return { mode: "local" };
57888
+ const rawUrl = nonEmpty$1(input.serverUrl, "serverUrl") ?? nonEmpty$1(env.CAPLETS_SERVER_URL, "CAPLETS_SERVER_URL");
57889
+ if (mode === "remote") {
57890
+ if (rawUrl === void 0) throw new CapletsError("REQUEST_INVALID", "CAPLETS_MODE=remote requires CAPLETS_SERVER_URL or serverUrl.");
57891
+ return { mode: "remote" };
57892
+ }
57893
+ return rawUrl === void 0 ? { mode: "local" } : { mode: "remote" };
57894
+ }
57895
+ function resolveCapletsServer(input = {}, env = process.env) {
57896
+ const rawUrl = nonEmpty$1(input.url, "url") ?? nonEmpty$1(env.CAPLETS_SERVER_URL, "CAPLETS_SERVER_URL");
57897
+ if (rawUrl === void 0) throw new CapletsError("REQUEST_INVALID", "CAPLETS_SERVER_URL or url is required.");
57898
+ const baseUrl = parseServerBaseUrl(rawUrl);
57899
+ const userWasExplicit = input.user !== void 0 || hasEnv$1(env.CAPLETS_SERVER_USER);
57900
+ const user = nonEmpty$1(input.user, "user") ?? nonEmpty$1(env.CAPLETS_SERVER_USER, "CAPLETS_SERVER_USER") ?? DEFAULT_SERVER_USER;
57901
+ const password = nonEmpty$1(input.password, "password") ?? nonEmpty$1(env.CAPLETS_SERVER_PASSWORD, "CAPLETS_SERVER_PASSWORD");
57902
+ if (userWasExplicit && password === void 0) throw new CapletsError("REQUEST_INVALID", "Caplets server Basic Auth requires a password; set CAPLETS_SERVER_PASSWORD or password.");
57903
+ const auth = password === void 0 ? {
57904
+ enabled: false,
57905
+ user
57906
+ } : {
57907
+ enabled: true,
57908
+ user,
57909
+ password
57910
+ };
57911
+ const requestInit = auth.enabled ? { headers: { Authorization: basicAuthHeader(auth.user, auth.password) } } : {};
57912
+ return {
57913
+ baseUrl,
57914
+ mcpUrl: mcpUrlForBase(baseUrl),
57915
+ controlUrl: controlUrlForBase(baseUrl),
57916
+ healthUrl: healthUrlForBase(baseUrl),
57917
+ auth,
57918
+ requestInit,
57919
+ ...input.fetch ? { fetch: input.fetch } : {}
57920
+ };
57921
+ }
57922
+ function mcpUrlForBase(baseUrl) {
57923
+ return appendBasePath(baseUrl, "mcp");
57924
+ }
57925
+ function controlUrlForBase(baseUrl) {
57926
+ return appendBasePath(baseUrl, "control");
57927
+ }
57928
+ function healthUrlForBase(baseUrl) {
57929
+ return appendBasePath(baseUrl, "healthz");
57930
+ }
57931
+ function appendBasePath(baseUrl, path) {
57932
+ const url = new URL(baseUrl.href);
57933
+ url.pathname = `${url.pathname === "/" ? "" : url.pathname}/${path}`;
57934
+ return url;
57935
+ }
57936
+ function parseServerBaseUrl(value) {
57937
+ let url;
57938
+ try {
57939
+ url = new URL(value);
57940
+ } catch {
57941
+ throw new CapletsError("REQUEST_INVALID", "Invalid Caplets server URL.");
57942
+ }
57943
+ 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.");
57944
+ 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.");
57945
+ url.pathname = url.pathname === "/" ? "/" : url.pathname.replace(/\/+$/u, "");
57946
+ return url;
57947
+ }
57948
+ function isLoopbackHost$1(host) {
57949
+ const normalized = host.toLocaleLowerCase();
57950
+ return normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1" || normalized === "[::1]";
57951
+ }
57952
+ function parseCapletsMode(value) {
57953
+ if (value === "auto" || value === "local" || value === "remote") return value;
57954
+ throw new CapletsError("REQUEST_INVALID", `Expected CAPLETS_MODE to be auto, local, or remote, got ${value}`);
57955
+ }
57956
+ function basicAuthHeader(user, password) {
57957
+ return `Basic ${Buffer$1.from(`${user}:${password}`).toString("base64")}`;
57958
+ }
57959
+ function nonEmpty$1(value, label) {
57960
+ if (value === void 0) return;
57961
+ const trimmed = value.trim();
57962
+ if (!trimmed) throw new CapletsError("REQUEST_INVALID", `${label} must not be empty`);
57963
+ return trimmed;
57964
+ }
57965
+ function hasEnv$1(value) {
57966
+ return value !== void 0 && value.trim() !== "";
57967
+ }
57968
+ //#endregion
57969
+ //#region ../core/dist/completion-CxGG6ae3.js
57970
+ var completion_CxGG6ae3_exports = /* @__PURE__ */ __exportAll$1({
57971
+ a: () => formatCapletList,
57972
+ c: () => resolveCliConfigPaths,
57973
+ i: () => trailingSpaceCompletionToken,
57974
+ l: () => cliCommands,
57975
+ n: () => completionScript,
57976
+ o: () => formatConfigPaths,
57977
+ r: () => completion_exports,
57978
+ s: () => listCaplets,
57979
+ t: () => completeCliWords,
57980
+ u: () => completionShells
57981
+ });
57982
+ const completionShells = [
57983
+ "bash",
57984
+ "zsh",
57985
+ "fish",
57986
+ "powershell",
57987
+ "cmd"
57988
+ ];
57989
+ const cliCommands = {
57990
+ completion: "completion",
57991
+ completeHidden: "__complete",
57992
+ serve: "serve",
57993
+ init: "init",
57994
+ list: "list",
57995
+ install: "install",
57996
+ add: "add",
57997
+ getCaplet: "get-caplet",
57998
+ checkBackend: "check-backend",
57999
+ listTools: "list-tools",
58000
+ searchTools: "search-tools",
58001
+ getTool: "get-tool",
58002
+ callTool: "call-tool",
58003
+ listResources: "list-resources",
58004
+ searchResources: "search-resources",
58005
+ listResourceTemplates: "list-resource-templates",
58006
+ readResource: "read-resource",
58007
+ listPrompts: "list-prompts",
58008
+ searchPrompts: "search-prompts",
58009
+ getPrompt: "get-prompt",
58010
+ complete: "complete",
58011
+ config: "config",
58012
+ auth: "auth"
58013
+ };
58014
+ const topLevelCommandNames = [
58015
+ cliCommands.serve,
58016
+ cliCommands.init,
58017
+ cliCommands.list,
58018
+ cliCommands.install,
58019
+ cliCommands.add,
58020
+ cliCommands.getCaplet,
58021
+ cliCommands.checkBackend,
58022
+ cliCommands.listTools,
58023
+ cliCommands.searchTools,
58024
+ cliCommands.getTool,
58025
+ cliCommands.callTool,
58026
+ cliCommands.listResources,
58027
+ cliCommands.searchResources,
58028
+ cliCommands.listResourceTemplates,
58029
+ cliCommands.readResource,
58030
+ cliCommands.listPrompts,
58031
+ cliCommands.searchPrompts,
58032
+ cliCommands.getPrompt,
58033
+ cliCommands.complete,
58034
+ cliCommands.config,
58035
+ cliCommands.auth,
58036
+ cliCommands.completion
58037
+ ];
58038
+ const cliSubcommands = {
58039
+ [cliCommands.add]: [
58040
+ "cli",
58041
+ "mcp",
58042
+ "openapi",
58043
+ "graphql",
58044
+ "http"
58045
+ ],
58046
+ [cliCommands.auth]: [
58047
+ "login",
58048
+ "logout",
58049
+ "list"
58050
+ ],
58051
+ [cliCommands.completion]: [...completionShells],
58052
+ [cliCommands.config]: ["path", "paths"]
58053
+ };
58054
+ const capletIdCommands = new Set([
58055
+ cliCommands.getCaplet,
58056
+ cliCommands.checkBackend,
58057
+ cliCommands.listTools,
58058
+ cliCommands.searchTools,
58059
+ cliCommands.listResources,
58060
+ cliCommands.searchResources,
58061
+ cliCommands.listResourceTemplates,
58062
+ cliCommands.readResource,
58063
+ cliCommands.listPrompts,
58064
+ cliCommands.searchPrompts,
58065
+ cliCommands.complete
58066
+ ]);
58067
+ const qualifiedToolCommands = new Set([cliCommands.getTool, cliCommands.callTool]);
58068
+ const qualifiedPromptCommands = new Set([cliCommands.getPrompt]);
58069
+ function listCaplets(configWithSources, options) {
58070
+ const { config, sources, shadows } = configWithSources;
58071
+ return allCaplets(config).filter((server) => options.includeDisabled || !server.disabled).map((server) => ({
58072
+ server: server.server,
58073
+ backend: server.backend,
58074
+ name: server.name,
58075
+ description: server.description,
58076
+ disabled: server.disabled,
58077
+ status: initialServerStatus(server),
58078
+ source: sources[server.server]?.kind ?? "unknown",
58079
+ path: sources[server.server]?.path ?? null,
58080
+ shadows: shadows[server.server] ?? []
58081
+ })).sort((left, right) => left.server.localeCompare(right.server));
58082
+ }
58083
+ function initialServerStatus(server) {
58084
+ return server.disabled ? "disabled" : "not_started";
58085
+ }
58086
+ function allCaplets(config) {
58087
+ return [
58088
+ ...Object.values(config.mcpServers),
58089
+ ...Object.values(config.openapiEndpoints),
58090
+ ...Object.values(config.graphqlEndpoints),
58091
+ ...Object.values(config.httpApis),
58092
+ ...Object.values(config.cliTools)
58093
+ ];
58094
+ }
58095
+ function formatCapletList(rows, format = "plain") {
58096
+ return format === "markdown" ? formatCapletListMarkdown(rows) : formatCapletListPlain(rows);
58097
+ }
58098
+ function formatCapletListMarkdown(rows) {
58099
+ if (rows.length === 0) return "## Configured Caplets\n\nNo configured Caplets found.\n";
58100
+ const heading = [
58101
+ "## Configured Caplets",
58102
+ "",
58103
+ `${rows.length} ${rows.length === 1 ? "Caplet" : "Caplets"} shown.`,
58104
+ ""
58105
+ ];
58106
+ const entries = rows.flatMap((row) => [
58107
+ `- \`${row.server}\` — ${row.name}`,
58108
+ ` - Backend: ${row.backend}`,
58109
+ ` - Status: ${row.status}`,
58110
+ ` - Source: ${row.source}`,
58111
+ ...row.disabled ? [" - Disabled: true"] : [],
58112
+ ...row.path ? [` - Path: ${row.path}`] : []
58113
+ ]);
58114
+ const warnings = rows.flatMap((row) => row.shadows.map((shadow) => `Warning: ${formatSourceKind(row.source)} Caplet ${row.server} shadows ${formatSourceKind(shadow.kind)} Caplet at ${shadow.path}`));
58115
+ if (warnings.length === 0) return `${[...heading, ...entries].join("\n")}\n`;
58116
+ return `${[
58117
+ ...heading,
58118
+ ...entries,
58119
+ "",
58120
+ "Warnings:",
58121
+ ...warnings.map((warning) => `- ${warning}`)
58122
+ ].join("\n")}\n`;
58123
+ }
58124
+ function formatCapletListPlain(rows) {
58125
+ if (rows.length === 0) return "No configured Caplets found.\n";
58126
+ const entries = rows.map((row) => [
58127
+ row.server,
58128
+ ` Name: ${row.name}`,
58129
+ ` Backend: ${row.backend}`,
58130
+ ` Status: ${row.status}`,
58131
+ ` Source: ${row.source}`,
58132
+ ...row.disabled ? [" Disabled: true"] : [],
58133
+ ...row.path ? [` Path: ${row.path}`] : []
58134
+ ].join("\n")).join("\n\n");
58135
+ const warnings = rows.flatMap((row) => row.shadows.map((shadow) => `Warning: ${formatSourceKind(row.source)} Caplet ${row.server} shadows ${formatSourceKind(shadow.kind)} Caplet at ${shadow.path}`));
58136
+ if (warnings.length === 0) return `Configured Caplets (${rows.length})\n\n${entries}\n`;
58137
+ return `Configured Caplets (${rows.length})\n\n${entries}\n\n${warnings.join("\n")}\n`;
58138
+ }
58139
+ function formatSourceKind(kind) {
58140
+ if (kind.startsWith("project")) return "project";
58141
+ if (kind.startsWith("global")) return "global";
58142
+ return kind;
58143
+ }
58144
+ function resolveCliConfigPaths(envConfigPath, authDir) {
58145
+ const configPath = resolveConfigPath(envConfigPath);
58146
+ const effectiveAuthDir = authDir ?? DEFAULT_AUTH_DIR;
58147
+ return {
58148
+ userConfig: configPath,
58149
+ projectConfig: resolveProjectConfigPath(),
58150
+ userRoot: resolveCapletsRoot(configPath),
58151
+ stateRoot: dirname(effectiveAuthDir),
58152
+ projectRoot: resolveProjectCapletsRoot(),
58153
+ authDir: effectiveAuthDir,
58154
+ envConfig: envConfigPath ?? null
58155
+ };
58156
+ }
58157
+ function formatConfigPaths(paths, format = "plain") {
58158
+ if (format === "markdown") return formatConfigPathsMarkdown(paths);
58159
+ return formatConfigPathsPlain(paths);
58160
+ }
58161
+ function formatConfigPathsMarkdown(paths) {
58162
+ return [
58163
+ "## Caplets paths",
58164
+ "",
58165
+ `- User config: ${paths.userConfig}`,
58166
+ `- Project config: ${paths.projectConfig}`,
58167
+ `- User Caplets root: ${paths.userRoot}`,
58168
+ `- State root: ${paths.stateRoot}`,
58169
+ `- Project Caplets root: ${paths.projectRoot}`,
58170
+ `- Auth directory: ${paths.authDir}`,
58171
+ `- CAPLETS_CONFIG: ${paths.envConfig ?? "unset"}`
58172
+ ].join("\n") + "\n";
58173
+ }
58174
+ function formatConfigPathsPlain(paths) {
58175
+ return [
58176
+ "Caplets paths",
58177
+ "",
58178
+ `User config: ${paths.userConfig}`,
58179
+ `Project config: ${paths.projectConfig}`,
58180
+ `User root: ${paths.userRoot}`,
58181
+ `State root: ${paths.stateRoot}`,
58182
+ `Project root: ${paths.projectRoot}`,
58183
+ `Auth directory: ${paths.authDir}`,
58184
+ `CAPLETS_CONFIG: ${paths.envConfig ?? "unset"}`
58185
+ ].join("\n") + "\n";
58186
+ }
58187
+ function completionCacheKey(input) {
58188
+ return createHash("sha256").update(JSON.stringify(input)).digest("hex");
58189
+ }
58190
+ function readCompletionCacheEntry(cacheDir, key, now = Date.now()) {
58191
+ try {
58192
+ const parsed = JSON.parse(readFileSync(cachePath(cacheDir, key), "utf8"));
58193
+ if (parsed.status === "positive" && Array.isArray(parsed.candidates)) return {
58194
+ ...parsed,
58195
+ fresh: now <= parsed.expiresAt
58196
+ };
58197
+ if (parsed.status === "negative" && typeof parsed.reason === "string") return {
58198
+ ...parsed,
58199
+ fresh: now <= parsed.expiresAt
58200
+ };
58201
+ } catch {
58202
+ return;
58203
+ }
58204
+ }
58205
+ function writeCompletionCacheEntry(cacheDir, key, entry) {
58206
+ mkdirSync(cacheDir, { recursive: true });
58207
+ const path = cachePath(cacheDir, key);
58208
+ const tempPath = `${path}.${process.pid}.tmp`;
58209
+ writeFileSync(tempPath, JSON.stringify(entry), { mode: 384 });
58210
+ renameSync(tempPath, path);
58211
+ }
58212
+ function cachePath(cacheDir, key) {
58213
+ return join(cacheDir, `${key}.json`);
58214
+ }
58215
+ async function discoverCompletionCandidates(serverId, kind, options) {
58216
+ const server = enabledServer(serverId, options.config);
58217
+ if (!server) return [];
58218
+ const completion = options.completion ?? options.config.options.completion;
58219
+ const now = options.now ?? Date.now();
58220
+ const configCandidates = configDefinedCandidates(serverId, kind, options.config);
58221
+ const cacheDir = options.cacheDir ?? DEFAULT_COMPLETION_CACHE_DIR;
58222
+ const key = completionCacheKey({
58223
+ server: server.server,
58224
+ backend: server.backend,
58225
+ kind,
58226
+ fingerprint: completionFingerprint(server, kind, completion)
58227
+ });
58228
+ const cached = readCompletionCacheEntry(cacheDir, key, now);
58229
+ if (cached?.status === "positive" && cached.fresh) return cached.candidates;
58230
+ if (cached?.status === "negative" && cached.fresh) return cached.candidates ?? configCandidates;
58231
+ try {
58232
+ const live = await withTimeout(liveCandidates(server, kind, options.managers), Math.min(completion.discoveryTimeoutMs, completion.overallTimeoutMs));
58233
+ const candidates = dedupeCandidates([...configCandidates, ...live]);
58234
+ writeCompletionCacheEntry(cacheDir, key, {
58235
+ status: "positive",
58236
+ fetchedAt: now,
58237
+ expiresAt: now + completion.cacheTtlMs,
58238
+ candidates
58239
+ });
58240
+ return candidates;
58241
+ } catch (error) {
58242
+ writeCompletionCacheEntry(cacheDir, key, {
58243
+ status: "negative",
58244
+ fetchedAt: now,
58245
+ expiresAt: now + completion.negativeCacheTtlMs,
58246
+ reason: negativeReason(error),
58247
+ ...cached?.status === "positive" ? { candidates: cached.candidates } : {}
58248
+ });
58249
+ if (cached?.status === "positive") return cached.candidates;
58250
+ return configCandidates;
58251
+ }
58252
+ }
58253
+ function configDefinedCandidates(serverId, kind, config) {
58254
+ if (kind !== "tools") return [];
58255
+ const cli = config.cliTools[serverId];
58256
+ if (cli && !cli.disabled) return Object.keys(cli.actions).map((name) => ({ value: `${serverId}.${name}` }));
58257
+ const http = config.httpApis[serverId];
58258
+ if (http && !http.disabled) return Object.keys(http.actions).map((name) => ({ value: `${serverId}.${name}` }));
58259
+ const graphql = config.graphqlEndpoints[serverId];
58260
+ if (graphql && !graphql.disabled && graphql.operations) return Object.keys(graphql.operations).map((name) => ({ value: `${serverId}.${name}` }));
58261
+ return [];
58262
+ }
58263
+ async function liveCandidates(server, kind, managers) {
58264
+ if (kind === "tools" && managers?.listTools) return (await managers.listTools(server)).map((tool) => ({
58265
+ value: `${server.server}.${tool.name}`,
58266
+ description: tool.description
58267
+ }));
58268
+ if (kind === "tools") return [];
58269
+ if (server.backend !== "mcp") return [];
58270
+ if (kind === "prompts" && managers?.listPrompts) return (await managers.listPrompts(server)).map((prompt) => ({
58271
+ value: `${server.server}.${prompt.name}`,
58272
+ description: prompt.description
58273
+ }));
58274
+ if (kind === "resources" && managers?.listResources) return (await managers.listResources(server)).map((resource) => ({
58275
+ value: resource.uri,
58276
+ label: resource.name,
58277
+ description: resource.description
58278
+ }));
58279
+ if (kind === "resourceTemplates" && managers?.listResourceTemplates) return (await managers.listResourceTemplates(server)).map((template) => ({
58280
+ value: template.uriTemplate,
58281
+ label: template.name,
58282
+ description: template.description
58283
+ }));
58284
+ throw new CapletsError("UNSUPPORTED_CAPABILITY", `Completion discovery is unsupported for ${kind}`);
58285
+ }
58286
+ function completionFingerprint(server, kind, completion) {
58287
+ return JSON.stringify({
58288
+ kind,
58289
+ completion: {
58290
+ discoveryTimeoutMs: completion.discoveryTimeoutMs,
58291
+ cacheTtlMs: completion.cacheTtlMs,
58292
+ negativeCacheTtlMs: completion.negativeCacheTtlMs
58293
+ },
58294
+ server: secretFreeServerShape(server)
58295
+ });
58296
+ }
58297
+ function secretFreeServerShape(server) {
58298
+ const base = {
58299
+ server: server.server,
58300
+ backend: server.backend,
58301
+ name: server.name,
58302
+ description: server.description,
58303
+ tags: server.tags,
58304
+ disabled: server.disabled
58305
+ };
58306
+ switch (server.backend) {
58307
+ case "mcp": return {
58308
+ ...base,
58309
+ transport: server.transport,
58310
+ command: server.command,
58311
+ args: server.args,
58312
+ cwd: server.cwd,
58313
+ url: server.url,
58314
+ authType: server.auth?.type,
58315
+ startupTimeoutMs: server.startupTimeoutMs,
58316
+ callTimeoutMs: server.callTimeoutMs
58317
+ };
58318
+ case "openapi": return {
58319
+ ...base,
58320
+ specPath: server.specPath,
58321
+ specUrl: server.specUrl,
58322
+ baseUrl: server.baseUrl,
58323
+ authType: server.auth.type,
58324
+ requestTimeoutMs: server.requestTimeoutMs
58325
+ };
58326
+ case "graphql": return {
58327
+ ...base,
58328
+ endpointUrl: server.endpointUrl,
58329
+ schemaPath: server.schemaPath,
58330
+ schemaUrl: server.schemaUrl,
58331
+ authType: server.auth.type,
58332
+ operationNames: server.operations ? Object.keys(server.operations) : void 0
58333
+ };
58334
+ case "http": return {
58335
+ ...base,
58336
+ baseUrl: server.baseUrl,
58337
+ authType: server.auth.type,
58338
+ actions: Object.fromEntries(Object.entries(server.actions).map(([name, action]) => [name, {
58339
+ method: action.method,
58340
+ path: action.path
58341
+ }])),
58342
+ requestTimeoutMs: server.requestTimeoutMs
58343
+ };
58344
+ case "cli": return {
58345
+ ...base,
58346
+ cwd: server.cwd,
58347
+ actions: Object.fromEntries(Object.entries(server.actions).map(([name, action]) => [name, {
58348
+ command: action.command,
58349
+ args: action.args,
58350
+ cwd: action.cwd
58351
+ }])),
58352
+ timeoutMs: server.timeoutMs,
58353
+ maxOutputBytes: server.maxOutputBytes
58354
+ };
58355
+ case "caplets": return {
58356
+ ...base,
58357
+ configPath: server.configPath,
58358
+ capletsRoot: server.capletsRoot,
58359
+ defaultSearchLimit: server.defaultSearchLimit,
58360
+ maxSearchLimit: server.maxSearchLimit
58361
+ };
58362
+ }
58363
+ }
58364
+ function negativeReason(error) {
58365
+ if (error instanceof CapletsError) {
58366
+ if (error.code === "AUTH_REQUIRED" || error.code === "AUTH_FAILED" || error.code === "AUTH_REFRESH_FAILED") return "auth_required";
58367
+ if (error.code === "SERVER_UNAVAILABLE" || error.code === "SERVER_START_TIMEOUT") return "unavailable";
58368
+ if (error.code === "UNSUPPORTED_CAPABILITY" || error.code === "UNSUPPORTED_OPERATION") return "unsupported";
58369
+ if (error.code === "TOOL_CALL_TIMEOUT" || error.code === "DOWNSTREAM_COMPLETION_ERROR") return "timeout";
58370
+ }
58371
+ return error instanceof Error && error.message.includes("timeout") ? "timeout" : "error";
58372
+ }
58373
+ async function withTimeout(promise, timeoutMs) {
58374
+ let timeout;
58375
+ try {
58376
+ return await Promise.race([promise, new Promise((_, reject) => {
58377
+ timeout = setTimeout(() => reject(/* @__PURE__ */ new Error("completion discovery timeout")), timeoutMs);
58378
+ })]);
58379
+ } finally {
58380
+ if (timeout) clearTimeout(timeout);
58381
+ }
58382
+ }
58383
+ function enabledServer(serverId, config) {
58384
+ const server = config.mcpServers[serverId] ?? config.openapiEndpoints[serverId] ?? config.graphqlEndpoints[serverId] ?? config.httpApis[serverId] ?? config.cliTools[serverId] ?? config.capletSets[serverId];
58385
+ return server && !server.disabled ? server : void 0;
58386
+ }
58387
+ function dedupeCandidates(candidates) {
58388
+ const seen = /* @__PURE__ */ new Set();
58389
+ return candidates.filter((candidate) => {
58390
+ if (seen.has(candidate.value)) return false;
58391
+ seen.add(candidate.value);
58392
+ return true;
58393
+ });
58394
+ }
58395
+ var completion_exports = /* @__PURE__ */ __exportAll({
58396
+ completeCliWords: () => completeCliWords,
58397
+ completionScript: () => completionScript,
58398
+ trailingSpaceCompletionToken: () => trailingSpaceCompletionToken
58399
+ });
58400
+ const trailingSpaceCompletionToken = "__CAPLETS_TRAILING_SPACE__";
58401
+ const optionValueSuggestions = {
58402
+ "*": { "--format": [
58403
+ "markdown",
58404
+ "md",
58405
+ "plain",
58406
+ "json"
58407
+ ] },
58408
+ serve: { "--transport": ["stdio", "http"] },
58409
+ "add:mcp": { "--transport": ["http", "sse"] },
58410
+ "add:cli": { "--include": [
58411
+ "git",
58412
+ "gh",
58413
+ "package"
58414
+ ] }
58415
+ };
58416
+ function completionScript(shell) {
58417
+ switch (shell) {
58418
+ case "bash": return bashCompletionScript();
58419
+ case "zsh": return zshCompletionScript();
58420
+ case "fish": return fishCompletionScript();
58421
+ case "powershell": return powershellCompletionScript();
58422
+ case "cmd": return cmdCompletionScript();
58423
+ default: throw new CapletsError("REQUEST_INVALID", "completion shell must be bash, zsh, fish, powershell, or cmd");
58424
+ }
58425
+ }
58426
+ async function completeCliWords(words, options = {}) {
58427
+ try {
58428
+ const normalized = words.length === 0 ? [""] : words;
58429
+ const current = normalized.at(-1) ?? "";
58430
+ const previous = normalized.at(-2);
58431
+ const command = normalized[0] ?? "";
58432
+ const subcommand = normalized[1] ?? "";
58433
+ if (command === cliCommands.complete && previous === "--prompt" && subcommand) return prefixFilter((await discoverCompletionCandidates(subcommand, "prompts", discoveryOptions(options))).map((candidate) => candidate.value.replace(`${subcommand}.`, "")), current);
58434
+ if (command === cliCommands.complete && previous === "--resource-template" && subcommand) return prefixFilter((await discoverCompletionCandidates(subcommand, "resourceTemplates", discoveryOptions(options))).map((candidate) => candidate.value), current);
58435
+ const optionValues = suggestionsForOptionValue(command, subcommand, previous);
58436
+ if (optionValues) return prefixFilter(optionValues, current);
58437
+ if (normalized.length === 1) return prefixFilter([...topLevelCommandNames], current);
58438
+ if (normalized.length === 2 && command in cliSubcommands) return prefixFilter(cliSubcommands[command], current);
58439
+ if (normalized.length === 2 && capletIdCommands.has(command)) return prefixFilter(promptResourceCommands.has(command) ? configuredCapletIds(options, { backend: "mcp" }) : configuredCapletIds(options), current);
58440
+ if (normalized.length === 2 && (qualifiedToolCommands.has(command) || qualifiedPromptCommands.has(command))) {
58441
+ if (current.includes(".")) return prefixFilter((await discoverCompletionCandidates(current.slice(0, current.indexOf(".")), qualifiedToolCommands.has(command) ? "tools" : "prompts", discoveryOptions(options))).map((candidate) => candidate.value), current);
58442
+ return prefixFilter(configuredCapletIds(options, qualifiedPromptCommands.has(command) ? { backend: "mcp" } : void 0).map((id) => `${id}.`), current);
58443
+ }
58444
+ if (command === cliCommands.readResource && normalized.length === 3) return prefixFilter((await discoverCompletionCandidates(subcommand, "resources", discoveryOptions(options))).map((candidate) => candidate.value), current);
58445
+ if (command === cliCommands.auth && ["login", "logout"].includes(subcommand) && normalized.length === 3) return prefixFilter(configuredCapletIds(options), current);
58446
+ return [];
58447
+ } catch {
58448
+ return [];
58449
+ }
58450
+ }
58451
+ function suggestionsForOptionValue(command, subcommand, previous) {
58452
+ if (!previous) return void 0;
58453
+ return optionValueSuggestions[`${command}:${subcommand}`]?.[previous] ?? optionValueSuggestions[command]?.[previous] ?? optionValueSuggestions["*"]?.[previous];
58454
+ }
58455
+ const promptResourceCommands = new Set([
58456
+ cliCommands.getPrompt,
58457
+ cliCommands.readResource,
58458
+ cliCommands.complete
58459
+ ]);
58460
+ function configuredCapletIds(options, filter = {}) {
58461
+ return listCaplets(options.config ? {
58462
+ config: options.config,
58463
+ sources: {},
58464
+ shadows: {}
58465
+ } : loadConfigWithSources(options.configPath, options.projectConfigPath), { includeDisabled: false }).filter((row) => !filter.backend || row.backend === filter.backend).map((row) => row.server);
58466
+ }
58467
+ function discoveryOptions(options) {
58468
+ return {
58469
+ config: options.config ?? loadConfigWithSources(options.configPath, options.projectConfigPath).config,
58470
+ completion: options.completion,
58471
+ cacheDir: options.cacheDir,
58472
+ managers: options.managers
58473
+ };
58474
+ }
58475
+ function prefixFilter(values, prefix) {
58476
+ return values.filter((value) => value.startsWith(prefix));
58477
+ }
58478
+ function bashCompletionScript() {
58479
+ return `# caplets bash completion
58480
+ _caplets_completions() {
58481
+ local IFS=$'\n'
58482
+ COMPREPLY=( $(caplets __complete --shell bash -- "\${COMP_WORDS[@]:1}" 2>/dev/null) )
58483
+ }
58484
+ complete -o default -F _caplets_completions caplets
58485
+ `;
58486
+ }
58487
+ function zshCompletionScript() {
58488
+ return `#compdef caplets
58489
+ _caplets() {
58490
+ local -a suggestions
58491
+ suggestions=("\${(@f)$(caplets __complete --shell zsh -- "\${words[@]:1}" 2>/dev/null)}")
58492
+ compadd -- $suggestions
58493
+ }
58494
+ _caplets "$@"
58495
+ `;
58496
+ }
58497
+ function fishCompletionScript() {
58498
+ return `# caplets fish completion
58499
+ function __caplets_complete
58500
+ set -l tokens (commandline -opc)
58501
+ set -l current (commandline -ct)
58502
+ caplets __complete --shell fish -- $tokens[2..-1] $current 2>/dev/null
58503
+ end
58504
+ complete -c caplets -f -a '(__caplets_complete)'
58505
+ `;
58506
+ }
58507
+ function powershellCompletionScript() {
58508
+ return `# caplets PowerShell completion
58509
+ Register-ArgumentCompleter -Native -CommandName caplets -ScriptBlock {
58510
+ param($wordToComplete, $commandAst, $cursorPosition)
58511
+ $tokens = @($commandAst.CommandElements | Select-Object -Skip 1 | ForEach-Object { $_.ToString() })
58512
+ if ($tokens.Count -eq 0 -or $commandAst.Extent.Text.EndsWith(' ')) { $tokens += '${trailingSpaceCompletionToken}' }
58513
+ caplets __complete --shell powershell -- @tokens 2>$null | ForEach-Object {
58514
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
58515
+ }
58516
+ }
58517
+ `;
58518
+ }
58519
+ function cmdCompletionScript() {
58520
+ return `@echo off
58521
+ REM caplets cmd completion helper
58522
+ REM cmd.exe has no native programmable completion API. This doskey macro prints suggestions for the current words.
58523
+ doskey caplets-complete=caplets __complete --shell cmd -- $* 2^>nul
58524
+ REM Usage: caplets-complete get-caplet
58525
+ `;
58526
+ }
57262
58527
  //#endregion
57263
58528
  //#region ../core/dist/index.js
57264
58529
  /**
@@ -58557,7 +59822,7 @@ const EMPTY_COMPLETION_RESULT = { completion: {
58557
59822
  values: [],
58558
59823
  hasMore: false
58559
59824
  } };
58560
- var version$1 = "0.16.0";
59825
+ var version$1 = "0.18.0";
58561
59826
  var CapletsMcpSession = class {
58562
59827
  engine;
58563
59828
  server;
@@ -58599,6 +59864,7 @@ var CapletsMcpSession = class {
58599
59864
  if (!previousCaplet || serializeCaplet(previousCaplet) !== serializeCaplet(caplet)) tool.update({
58600
59865
  title: caplet.name,
58601
59866
  description: capabilityDescription(caplet),
59867
+ paramsSchema: generatedToolInputSchemaForCaplet(caplet).shape,
58602
59868
  callback: async (request) => this.handleTool(serverId, request),
58603
59869
  enabled: true
58604
59870
  });
@@ -58612,7 +59878,7 @@ var CapletsMcpSession = class {
58612
59878
  return this.server.registerTool(caplet.server, {
58613
59879
  title: caplet.name,
58614
59880
  description: capabilityDescription(caplet),
58615
- inputSchema: generatedToolInputSchema
59881
+ inputSchema: generatedToolInputSchemaForCaplet(caplet).shape
58616
59882
  }, async (request) => this.handleTool(caplet.server, request));
58617
59883
  }
58618
59884
  async handleTool(serverId, request) {
@@ -62090,49 +63356,56 @@ async function loginAuth(serverId, options) {
62090
63356
  }
62091
63357
  }
62092
63358
  function logoutAuth(serverId, options) {
62093
- assertLoginTarget(findAuthTarget(serverId, loadConfig(options.configPath)), serverId);
62094
- if (deleteTokenBundle(serverId, options.authDir)) options.writeOut(`Deleted OAuth credentials for \`${serverId}\`.\n`);
63359
+ if (logoutAuthResult(serverId, options).deleted) options.writeOut(`Deleted OAuth credentials for \`${serverId}\`.\n`);
62095
63360
  else options.writeOut(`No OAuth credentials found for \`${serverId}\`.\n`);
62096
63361
  }
63362
+ function logoutAuthResult(serverId, options) {
63363
+ assertLoginTarget(findAuthTarget(serverId, loadConfig(options.configPath)), serverId);
63364
+ return {
63365
+ server: serverId,
63366
+ deleted: deleteTokenBundle(serverId, options.authDir)
63367
+ };
63368
+ }
62097
63369
  function listAuth(options) {
62098
- const servers = authTargets(loadConfig(options.configPath)).sort((left, right) => left.server.localeCompare(right.server));
63370
+ const rows = listAuthRows(options);
62099
63371
  const format = options.format ?? "plain";
62100
63372
  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
63373
  options.writeOut(`${JSON.stringify(rows, null, 2)}\n`);
62112
63374
  return;
62113
63375
  }
62114
- if (servers.length === 0) {
62115
- options.writeOut(format === "markdown" ? "## OAuth credentials\n\nNo configured remote OAuth servers found.\n" : "No configured remote OAuth servers found.\n");
62116
- return;
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) {
63376
+ options.writeOut(formatAuthRows(rows, format));
63377
+ }
63378
+ function listAuthRows(options) {
63379
+ return authTargets(loadConfig(options.configPath)).sort((left, right) => left.server.localeCompare(right.server)).map((server) => {
62121
63380
  const bundle = readTokenBundle(server.server, options.authDir);
62122
63381
  const status = !bundle ? "missing" : isTokenBundleExpired(bundle) ? "expired" : "authenticated";
62123
- const details = [bundle?.expiresAt ? `expires ${bundle.expiresAt}` : void 0, bundle?.scope ? `scope ${bundle.scope}` : void 0].filter(Boolean).join("; ");
63382
+ return {
63383
+ server: server.server,
63384
+ status,
63385
+ ...bundle?.expiresAt ? { expiresAt: bundle.expiresAt } : {},
63386
+ ...bundle?.scope ? { scope: bundle.scope } : {}
63387
+ };
63388
+ });
63389
+ }
63390
+ function formatAuthRows(rows, format) {
63391
+ if (rows.length === 0) return format === "markdown" ? "## OAuth credentials\n\nNo configured remote OAuth servers found.\n" : "No configured remote OAuth servers found.\n";
63392
+ let output = "";
63393
+ if (format === "markdown") output += "## OAuth credentials\n\n";
63394
+ else output += "OAuth credentials\n\n";
63395
+ for (const row of rows) {
63396
+ const details = [row.expiresAt ? `expires ${row.expiresAt}` : void 0, row.scope ? `scope ${row.scope}` : void 0].filter(Boolean).join("; ");
62124
63397
  if (format === "markdown") {
62125
- options.writeOut(`- \`${server.server}\` — ${status}${details ? ` (${details})` : ""}\n`);
63398
+ output += `- \`${row.server}\` — ${row.status}${details ? ` (${details})` : ""}\n`;
62126
63399
  continue;
62127
63400
  }
62128
- options.writeOut([
62129
- server.server,
62130
- ` Status: ${status}`,
62131
- ...bundle?.expiresAt ? [` Expires: ${bundle.expiresAt}`] : [],
62132
- ...bundle?.scope ? [` Scope: ${bundle.scope}`] : []
62133
- ].join("\n"));
62134
- options.writeOut("\n\n");
63401
+ output += [
63402
+ row.server,
63403
+ ` Status: ${row.status}`,
63404
+ ...row.expiresAt ? [` Expires: ${row.expiresAt}`] : [],
63405
+ ...row.scope ? [` Scope: ${row.scope}`] : []
63406
+ ].join("\n") + "\n\n";
62135
63407
  }
63408
+ return output;
62136
63409
  }
62137
63410
  function findAuthTarget(serverId, config = loadConfig()) {
62138
63411
  return authTargets(config).find((server) => server.server === serverId);
@@ -62201,124 +63474,6 @@ function starterConfig() {
62201
63474
  } }
62202
63475
  }, null, 2);
62203
63476
  }
62204
- function listCaplets(configWithSources, options) {
62205
- const { config, sources, shadows } = configWithSources;
62206
- return allCaplets(config).filter((server) => options.includeDisabled || !server.disabled).map((server) => ({
62207
- server: server.server,
62208
- backend: server.backend,
62209
- name: server.name,
62210
- description: server.description,
62211
- disabled: server.disabled,
62212
- status: initialServerStatus(server),
62213
- source: sources[server.server]?.kind ?? "unknown",
62214
- path: sources[server.server]?.path ?? null,
62215
- shadows: shadows[server.server] ?? []
62216
- })).sort((left, right) => left.server.localeCompare(right.server));
62217
- }
62218
- function initialServerStatus(server) {
62219
- return server.disabled ? "disabled" : "not_started";
62220
- }
62221
- function allCaplets(config) {
62222
- return [
62223
- ...Object.values(config.mcpServers),
62224
- ...Object.values(config.openapiEndpoints),
62225
- ...Object.values(config.graphqlEndpoints),
62226
- ...Object.values(config.httpApis),
62227
- ...Object.values(config.cliTools)
62228
- ];
62229
- }
62230
- function formatCapletList(rows, format = "plain") {
62231
- return format === "markdown" ? formatCapletListMarkdown(rows) : formatCapletListPlain(rows);
62232
- }
62233
- function formatCapletListMarkdown(rows) {
62234
- if (rows.length === 0) return "## Configured Caplets\n\nNo configured Caplets found.\n";
62235
- const heading = [
62236
- "## Configured Caplets",
62237
- "",
62238
- `${rows.length} ${rows.length === 1 ? "Caplet" : "Caplets"} shown.`,
62239
- ""
62240
- ];
62241
- const entries = rows.flatMap((row) => [
62242
- `- \`${row.server}\` — ${row.name}`,
62243
- ` - Backend: ${row.backend}`,
62244
- ` - Status: ${row.status}`,
62245
- ` - Source: ${row.source}`,
62246
- ...row.disabled ? [" - Disabled: true"] : [],
62247
- ...row.path ? [` - Path: ${row.path}`] : []
62248
- ]);
62249
- const warnings = rows.flatMap((row) => row.shadows.map((shadow) => `Warning: ${formatSourceKind(row.source)} Caplet ${row.server} shadows ${formatSourceKind(shadow.kind)} Caplet at ${shadow.path}`));
62250
- if (warnings.length === 0) return `${[...heading, ...entries].join("\n")}\n`;
62251
- return `${[
62252
- ...heading,
62253
- ...entries,
62254
- "",
62255
- "Warnings:",
62256
- ...warnings.map((warning) => `- ${warning}`)
62257
- ].join("\n")}\n`;
62258
- }
62259
- function formatCapletListPlain(rows) {
62260
- if (rows.length === 0) return "No configured Caplets found.\n";
62261
- const entries = rows.map((row) => [
62262
- row.server,
62263
- ` Name: ${row.name}`,
62264
- ` Backend: ${row.backend}`,
62265
- ` Status: ${row.status}`,
62266
- ` Source: ${row.source}`,
62267
- ...row.disabled ? [" Disabled: true"] : [],
62268
- ...row.path ? [` Path: ${row.path}`] : []
62269
- ].join("\n")).join("\n\n");
62270
- const warnings = rows.flatMap((row) => row.shadows.map((shadow) => `Warning: ${formatSourceKind(row.source)} Caplet ${row.server} shadows ${formatSourceKind(shadow.kind)} Caplet at ${shadow.path}`));
62271
- if (warnings.length === 0) return `Configured Caplets (${rows.length})\n\n${entries}\n`;
62272
- return `Configured Caplets (${rows.length})\n\n${entries}\n\n${warnings.join("\n")}\n`;
62273
- }
62274
- function formatSourceKind(kind) {
62275
- if (kind.startsWith("project")) return "project";
62276
- if (kind.startsWith("global")) return "global";
62277
- return kind;
62278
- }
62279
- function resolveCliConfigPaths(envConfigPath, authDir) {
62280
- const configPath = resolveConfigPath(envConfigPath);
62281
- const effectiveAuthDir = authDir ?? DEFAULT_AUTH_DIR;
62282
- return {
62283
- userConfig: configPath,
62284
- projectConfig: resolveProjectConfigPath(),
62285
- userRoot: resolveCapletsRoot(configPath),
62286
- stateRoot: dirname(effectiveAuthDir),
62287
- projectRoot: resolveProjectCapletsRoot(),
62288
- authDir: effectiveAuthDir,
62289
- envConfig: envConfigPath ?? null
62290
- };
62291
- }
62292
- function formatConfigPaths(paths, format = "plain") {
62293
- if (format === "markdown") return formatConfigPathsMarkdown(paths);
62294
- return formatConfigPathsPlain(paths);
62295
- }
62296
- function formatConfigPathsMarkdown(paths) {
62297
- return [
62298
- "## Caplets paths",
62299
- "",
62300
- `- User config: ${paths.userConfig}`,
62301
- `- Project config: ${paths.projectConfig}`,
62302
- `- User Caplets root: ${paths.userRoot}`,
62303
- `- State root: ${paths.stateRoot}`,
62304
- `- Project Caplets root: ${paths.projectRoot}`,
62305
- `- Auth directory: ${paths.authDir}`,
62306
- `- CAPLETS_CONFIG: ${paths.envConfig ?? "unset"}`
62307
- ].join("\n") + "\n";
62308
- }
62309
- function formatConfigPathsPlain(paths) {
62310
- return [
62311
- "Caplets paths",
62312
- "",
62313
- `User config: ${paths.userConfig}`,
62314
- `Project config: ${paths.projectConfig}`,
62315
- `User root: ${paths.userRoot}`,
62316
- `State root: ${paths.stateRoot}`,
62317
- `Project root: ${paths.projectRoot}`,
62318
- `Auth directory: ${paths.authDir}`,
62319
- `CAPLETS_CONFIG: ${paths.envConfig ?? "unset"}`
62320
- ].join("\n") + "\n";
62321
- }
62322
63477
  function installCaplets(repo, options = {}) {
62323
63478
  const source = resolveInstallSource(repo);
62324
63479
  try {
@@ -62552,6 +63707,94 @@ function nearestExistingParent(path) {
62552
63707
  if (parent === path) return parent;
62553
63708
  return nearestExistingParent(parent);
62554
63709
  }
63710
+ var RemoteControlClient = class {
63711
+ #baseUrl;
63712
+ #requestInit;
63713
+ #fetch;
63714
+ constructor(options) {
63715
+ this.#baseUrl = options.baseUrl;
63716
+ this.#requestInit = options.requestInit;
63717
+ this.#fetch = options.fetch ?? fetch;
63718
+ }
63719
+ async request(command, args) {
63720
+ const controlUrl = controlUrlForBase(this.#baseUrl);
63721
+ let response;
63722
+ try {
63723
+ response = await this.#fetch(controlUrl, {
63724
+ ...this.#requestInit,
63725
+ method: "POST",
63726
+ headers: mergeJsonHeaders(this.#requestInit.headers),
63727
+ body: JSON.stringify({
63728
+ command,
63729
+ arguments: args
63730
+ })
63731
+ });
63732
+ } catch (error) {
63733
+ throw new CapletsError("SERVER_UNAVAILABLE", `Could not connect to Caplets server at ${safeBaseUrl(this.#baseUrl)}.`, toSafeError(error, "SERVER_UNAVAILABLE"));
63734
+ }
63735
+ if (response.status === 401 || response.status === 403) throw new CapletsError("AUTH_FAILED", "Caplets server authentication failed. Check CAPLETS_SERVER_USER and CAPLETS_SERVER_PASSWORD.");
63736
+ if (!response.ok) throw new CapletsError("SERVER_UNAVAILABLE", `Caplets server at ${safeBaseUrl(this.#baseUrl)} returned HTTP ${response.status}.`);
63737
+ const payload = await parseRemoteCliResponse(response);
63738
+ if (!payload.ok) throw new CapletsError(payload.error.code, redactRemoteMessage(payload.error.message), payload.error.nextAction === void 0 ? void 0 : { nextAction: payload.error.nextAction });
63739
+ return payload.result;
63740
+ }
63741
+ };
63742
+ function mergeJsonHeaders(headers) {
63743
+ const merged = new Headers(headers);
63744
+ merged.set("content-type", "application/json");
63745
+ return merged;
63746
+ }
63747
+ function safeBaseUrl(baseUrl) {
63748
+ const safe = new URL(baseUrl.href);
63749
+ safe.username = "";
63750
+ safe.password = "";
63751
+ safe.search = "";
63752
+ safe.hash = "";
63753
+ return safe.toString();
63754
+ }
63755
+ async function parseRemoteCliResponse(response) {
63756
+ let payload;
63757
+ try {
63758
+ payload = await response.json();
63759
+ } catch (error) {
63760
+ throw invalidRemoteControlResponse(error);
63761
+ }
63762
+ if (!isRecord(payload)) throw invalidRemoteControlResponse();
63763
+ if (payload.ok === true) {
63764
+ if (!("result" in payload)) throw invalidRemoteControlResponse();
63765
+ return {
63766
+ ok: true,
63767
+ result: payload.result
63768
+ };
63769
+ }
63770
+ if (payload.ok === false) {
63771
+ const error = payload.error;
63772
+ if (!isRecord(error) || typeof error.code !== "string" || typeof error.message !== "string") throw invalidRemoteControlResponse();
63773
+ if ("nextAction" in error && error.nextAction !== void 0 && typeof error.nextAction !== "string") throw invalidRemoteControlResponse();
63774
+ const errorResponse = {
63775
+ ok: false,
63776
+ error: {
63777
+ code: isCapletsErrorCode(error.code) ? error.code : "DOWNSTREAM_TOOL_ERROR",
63778
+ message: error.message
63779
+ }
63780
+ };
63781
+ if (typeof error.nextAction === "string") errorResponse.error.nextAction = error.nextAction;
63782
+ return errorResponse;
63783
+ }
63784
+ throw invalidRemoteControlResponse();
63785
+ }
63786
+ function invalidRemoteControlResponse(cause) {
63787
+ return new CapletsError("DOWNSTREAM_PROTOCOL_ERROR", "Caplets server returned an invalid remote control response.", cause === void 0 ? void 0 : toSafeError(cause, "DOWNSTREAM_PROTOCOL_ERROR"));
63788
+ }
63789
+ function isRecord(value) {
63790
+ return value !== null && typeof value === "object" && !Array.isArray(value);
63791
+ }
63792
+ function isCapletsErrorCode(value) {
63793
+ return CAPLETS_ERROR_CODES.includes(value);
63794
+ }
63795
+ function redactRemoteMessage(message) {
63796
+ 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]");
63797
+ }
62555
63798
  var compose = (middleware, onError, onNotFound) => {
62556
63799
  return (context, next) => {
62557
63800
  let index = -1;
@@ -65421,29 +66664,367 @@ var logger = (fn = console.log) => {
65421
66664
  await log(fn, "-->", method, path, c.res.status, time(start));
65422
66665
  };
65423
66666
  };
66667
+ const ENGINE_COMMANDS = new Set([
66668
+ "get_caplet",
66669
+ "check_backend",
66670
+ "list_tools",
66671
+ "search_tools",
66672
+ "get_tool",
66673
+ "call_tool",
66674
+ "list_resources",
66675
+ "search_resources",
66676
+ "list_resource_templates",
66677
+ "read_resource",
66678
+ "list_prompts",
66679
+ "search_prompts",
66680
+ "get_prompt",
66681
+ "complete"
66682
+ ]);
66683
+ async function dispatchRemoteCliRequest(request, context) {
66684
+ try {
66685
+ return {
66686
+ ok: true,
66687
+ result: await dispatch(request, context)
66688
+ };
66689
+ } catch (error) {
66690
+ const safe = toSafeError(error);
66691
+ const action = nextAction(safe.details);
66692
+ return {
66693
+ ok: false,
66694
+ error: {
66695
+ code: safe.code,
66696
+ message: redactControlErrorMessage(safe.message),
66697
+ ...action ? { nextAction: action } : {}
66698
+ }
66699
+ };
66700
+ }
66701
+ }
66702
+ async function dispatch(request, context) {
66703
+ assertObject(request, "remote control request");
66704
+ assertObject(request.arguments, "remote control request arguments");
66705
+ if (request.command === "list") return listCaplets(loadConfigWithSources(context.configPath, context.projectConfigPath), { includeDisabled: optionalBoolean(request.arguments, "includeDisabled") ?? false });
66706
+ if (ENGINE_COMMANDS.has(request.command)) {
66707
+ const caplet = requiredString(request.arguments, "caplet");
66708
+ const toolRequest = requiredEngineRequest(request.arguments, request.command);
66709
+ const engine = new CapletsEngine(context);
66710
+ try {
66711
+ return await engine.execute(caplet, toolRequest);
66712
+ } finally {
66713
+ await engine.close();
66714
+ }
66715
+ }
66716
+ if (request.command === "init") return {
66717
+ remote: true,
66718
+ path: initConfig({
66719
+ ...optionalProp("path", context.configPath),
66720
+ ...optionalProp("force", optionalBoolean(request.arguments, "force"))
66721
+ })
66722
+ };
66723
+ if (request.command === "add") return dispatchAdd(request.arguments, context);
66724
+ if (request.command === "install") return {
66725
+ remote: true,
66726
+ ...installCaplets(requiredString(request.arguments, "repo"), {
66727
+ ...optionalProp("capletIds", optionalStringArray(request.arguments, "capletIds")),
66728
+ destinationRoot: context.projectCapletsRoot,
66729
+ ...optionalProp("force", optionalBoolean(request.arguments, "force"))
66730
+ })
66731
+ };
66732
+ if (request.command === "complete_cli") {
66733
+ const shell = optionalString(request.arguments, "shell") ?? "bash";
66734
+ if (!completionShells.includes(shell)) return [];
66735
+ const engine = new CapletsEngine(context);
66736
+ try {
66737
+ return await engine.completeCliWords(optionalStringArray(request.arguments, "words") ?? [""]);
66738
+ } finally {
66739
+ await engine.close();
66740
+ }
66741
+ }
66742
+ if (request.command === "auth_list") return listAuthRows({
66743
+ ...optionalProp("configPath", context.configPath),
66744
+ ...optionalProp("authDir", context.authDir)
66745
+ });
66746
+ if (request.command === "auth_logout") return logoutAuthResult(requiredString(request.arguments, "server"), {
66747
+ ...optionalProp("configPath", context.configPath),
66748
+ ...optionalProp("authDir", context.authDir)
66749
+ });
66750
+ if (request.command === "auth_login_start") return startRemoteAuthLogin(requiredString(request.arguments, "server"), context);
66751
+ if (request.command === "auth_login_complete") return completeRemoteAuthLogin(requiredString(request.arguments, "flowId"), requiredString(request.arguments, "callbackUrl"), context);
66752
+ throw new CapletsError("UNKNOWN_OPERATION", `Unsupported remote control command ${request.command}`);
66753
+ }
66754
+ async function startRemoteAuthLogin(serverId, context) {
66755
+ if (!context.authFlowStore || !context.controlCallbackBaseUrl) throw new CapletsError("REQUEST_INVALID", "Remote auth login is not available on this server");
66756
+ const config = loadConfigWithSources(context.configPath, context.projectConfigPath).config;
66757
+ const target = findAuthTarget(serverId, config);
66758
+ assertLoginTarget(target, serverId);
66759
+ const flowId = randomUUID();
66760
+ const baseUrl = context.controlCallbackBaseUrl.endsWith("/") ? context.controlCallbackBaseUrl : `${context.controlCallbackBaseUrl}/`;
66761
+ const redirectUri = new URL(`auth/callback/${flowId}`, baseUrl).toString();
66762
+ const started = target.backend === "mcp" ? await startOAuthFlow(target, {
66763
+ redirectUri,
66764
+ ...optionalProp("authDir", context.authDir)
66765
+ }) : await startGenericOAuthFlow(target, {
66766
+ redirectUri,
66767
+ ...optionalProp("authDir", context.authDir)
66768
+ });
66769
+ if (!started.authorizationUrl) return {
66770
+ server: serverId,
66771
+ authenticated: true
66772
+ };
66773
+ const flow = context.authFlowStore.create({
66774
+ server: serverId,
66775
+ authorizationUrl: started.authorizationUrl,
66776
+ complete: started.complete
66777
+ }, flowId);
66778
+ return {
66779
+ server: serverId,
66780
+ flowId: flow.id,
66781
+ authorizationUrl: flow.authorizationUrl
66782
+ };
66783
+ }
66784
+ async function completeRemoteAuthLogin(flowId, callbackUrl, context) {
66785
+ const flow = context.authFlowStore?.get(flowId);
66786
+ if (!flow) throw new CapletsError("REQUEST_INVALID", `Unknown auth flow ${flowId}`);
66787
+ context.authFlowStore?.delete(flowId);
66788
+ await flow.complete(callbackUrl);
66789
+ return {
66790
+ server: flow.server,
66791
+ authenticated: true
66792
+ };
66793
+ }
66794
+ function dispatchAdd(args, context) {
66795
+ const kind = requiredString(args, "kind");
66796
+ const id = requiredString(args, "id");
66797
+ const options = remoteAddOptions$1(kind, optionalObject(args, "options"));
66798
+ switch (kind) {
66799
+ case "cli": return {
66800
+ remote: true,
66801
+ label: "CLI",
66802
+ ...addCliCaplet(id, {
66803
+ ...options,
66804
+ destinationRoot: context.projectCapletsRoot,
66805
+ print: false
66806
+ })
66807
+ };
66808
+ case "mcp": return {
66809
+ remote: true,
66810
+ label: "MCP",
66811
+ ...addMcpCaplet(id, {
66812
+ ...options,
66813
+ destinationRoot: context.projectCapletsRoot,
66814
+ print: false
66815
+ })
66816
+ };
66817
+ case "openapi": return {
66818
+ remote: true,
66819
+ label: "OpenAPI",
66820
+ ...addOpenApiCaplet(id, {
66821
+ ...options,
66822
+ destinationRoot: context.projectCapletsRoot,
66823
+ print: false
66824
+ })
66825
+ };
66826
+ case "graphql": return {
66827
+ remote: true,
66828
+ label: "GraphQL",
66829
+ ...addGraphqlCaplet(id, {
66830
+ ...options,
66831
+ destinationRoot: context.projectCapletsRoot,
66832
+ print: false
66833
+ })
66834
+ };
66835
+ case "http": return {
66836
+ remote: true,
66837
+ label: "HTTP",
66838
+ ...addHttpCaplet(id, {
66839
+ ...options,
66840
+ destinationRoot: context.projectCapletsRoot,
66841
+ print: false
66842
+ })
66843
+ };
66844
+ default: throw new CapletsError("REQUEST_INVALID", "add.kind must be cli, mcp, openapi, graphql, or http");
66845
+ }
66846
+ }
66847
+ function optionalProp(key, value) {
66848
+ return value === void 0 ? {} : { [key]: value };
66849
+ }
66850
+ function assertObject(value, label) {
66851
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new CapletsError("REQUEST_INVALID", `${label} must be an object`);
66852
+ }
66853
+ function requiredString(args, key) {
66854
+ const value = args[key];
66855
+ if (typeof value !== "string" || value.length === 0) throw new CapletsError("REQUEST_INVALID", `${key} must be a non-empty string`);
66856
+ return value;
66857
+ }
66858
+ function optionalString(args, key) {
66859
+ const value = args[key];
66860
+ if (value === void 0) return;
66861
+ if (typeof value !== "string") throw new CapletsError("REQUEST_INVALID", `${key} must be a string`);
66862
+ return value;
66863
+ }
66864
+ function optionalObject(args, key) {
66865
+ const value = args[key];
66866
+ if (value === void 0) return {};
66867
+ assertObject(value, key);
66868
+ return value;
66869
+ }
66870
+ function requiredEngineRequest(args, command) {
66871
+ const toolRequest = optionalObject(args, "request");
66872
+ if (typeof toolRequest.operation !== "string") throw new CapletsError("REQUEST_INVALID", "request.operation must be a string");
66873
+ if (toolRequest.operation !== command) throw new CapletsError("REQUEST_INVALID", `request.operation must match remote command ${command}`);
66874
+ return toolRequest;
66875
+ }
66876
+ function remoteAddOptions$1(kind, options) {
66877
+ rejectServerOwnedAddOptions(options);
66878
+ switch (kind) {
66879
+ case "cli": return pickOptions(options, {
66880
+ repo: "string",
66881
+ include: "string",
66882
+ command: "string",
66883
+ force: "boolean"
66884
+ });
66885
+ case "mcp": return pickOptions(options, {
66886
+ command: "string",
66887
+ arg: "string-array",
66888
+ cwd: "string",
66889
+ env: "string-array",
66890
+ url: "string",
66891
+ transport: "string",
66892
+ tokenEnv: "string",
66893
+ force: "boolean"
66894
+ });
66895
+ case "openapi": return pickOptions(options, {
66896
+ spec: "string",
66897
+ baseUrl: "string",
66898
+ tokenEnv: "string",
66899
+ force: "boolean"
66900
+ });
66901
+ case "graphql": return pickOptions(options, {
66902
+ endpointUrl: "string",
66903
+ schema: "string",
66904
+ introspection: "boolean",
66905
+ tokenEnv: "string",
66906
+ force: "boolean"
66907
+ });
66908
+ case "http": return pickOptions(options, {
66909
+ baseUrl: "string",
66910
+ action: "string-array",
66911
+ tokenEnv: "string",
66912
+ force: "boolean"
66913
+ });
66914
+ default: return options;
66915
+ }
66916
+ }
66917
+ function pickOptions(options, schema) {
66918
+ const next = {};
66919
+ for (const [key, type] of Object.entries(schema)) {
66920
+ const value = options[key];
66921
+ if (value === void 0) continue;
66922
+ validateOptionType(key, value, type);
66923
+ next[key] = value;
66924
+ }
66925
+ return next;
66926
+ }
66927
+ function rejectServerOwnedAddOptions(options) {
66928
+ if ("output" in options) throw new CapletsError("REQUEST_INVALID", "Remote add output is not supported remotely; the server owns destinationRoot and output path selection");
66929
+ 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`);
66930
+ }
66931
+ function validateOptionType(key, value, type) {
66932
+ if (type === "string" && typeof value !== "string") throw new CapletsError("REQUEST_INVALID", `add.options.${key} must be a string`);
66933
+ if (type === "boolean" && typeof value !== "boolean") throw new CapletsError("REQUEST_INVALID", `add.options.${key} must be a boolean`);
66934
+ if (type === "string-array") {
66935
+ if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) throw new CapletsError("REQUEST_INVALID", `add.options.${key} must be an array of strings`);
66936
+ }
66937
+ }
66938
+ function optionalBoolean(args, key) {
66939
+ const value = args[key];
66940
+ if (value === void 0) return;
66941
+ if (typeof value !== "boolean") throw new CapletsError("REQUEST_INVALID", `${key} must be a boolean`);
66942
+ return value;
66943
+ }
66944
+ function optionalStringArray(args, key) {
66945
+ const value = args[key];
66946
+ if (value === void 0) return;
66947
+ if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) throw new CapletsError("REQUEST_INVALID", `${key} must be an array of strings`);
66948
+ return value;
66949
+ }
66950
+ function nextAction(details) {
66951
+ if (details && typeof details === "object" && "nextAction" in details) {
66952
+ const value = details.nextAction;
66953
+ return typeof value === "string" ? value : void 0;
66954
+ }
66955
+ }
66956
+ function redactControlErrorMessage(message) {
66957
+ 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]");
66958
+ }
66959
+ const DEFAULT_AUTH_FLOW_TTL_MS = 600 * 1e3;
66960
+ var RemoteAuthFlowStore = class {
66961
+ options;
66962
+ flows = /* @__PURE__ */ new Map();
66963
+ constructor(options = {}) {
66964
+ this.options = options;
66965
+ }
66966
+ create(flow, id = randomUUID()) {
66967
+ this.pruneExpired();
66968
+ const created = {
66969
+ id,
66970
+ createdAt: this.now(),
66971
+ ...flow
66972
+ };
66973
+ this.flows.set(created.id, created);
66974
+ return { ...created };
66975
+ }
66976
+ get(id) {
66977
+ this.pruneExpired();
66978
+ const flow = this.flows.get(id);
66979
+ if (flow && this.isExpired(flow)) {
66980
+ this.flows.delete(id);
66981
+ return;
66982
+ }
66983
+ return flow;
66984
+ }
66985
+ delete(id) {
66986
+ this.flows.delete(id);
66987
+ }
66988
+ pruneExpired() {
66989
+ for (const [id, flow] of this.flows) if (this.isExpired(flow)) this.flows.delete(id);
66990
+ }
66991
+ isExpired(flow) {
66992
+ return this.now() - flow.createdAt > (this.options.ttlMs ?? DEFAULT_AUTH_FLOW_TTL_MS);
66993
+ }
66994
+ now() {
66995
+ return this.options.now?.() ?? Date.now();
66996
+ }
66997
+ };
65424
66998
  function createHttpServeApp(options, engine, io = {}) {
65425
66999
  const app = new Hono();
65426
67000
  const sessions = /* @__PURE__ */ new Map();
65427
67001
  const writeErr = io.writeErr ?? process.stderr.write.bind(process.stderr);
67002
+ const paths = servicePaths(options.path);
67003
+ const authFlowStore = io.authFlowStore ?? new RemoteAuthFlowStore();
65428
67004
  app.use("*", logger((message, ...rest) => {
65429
67005
  writeErr(`${[message, ...rest].join(" ")}\n`);
65430
67006
  }));
65431
- app.get("/", (c) => c.json({
67007
+ app.get(paths.base, (c) => c.json({
65432
67008
  name: "caplets",
65433
67009
  transport: "http",
65434
- mcp: options.path,
65435
- health: "/healthz",
67010
+ base: paths.base,
67011
+ mcp: paths.mcp,
67012
+ control: paths.control,
67013
+ health: paths.health,
65436
67014
  auth: {
65437
67015
  type: "basic",
65438
67016
  enabled: options.auth.enabled
65439
67017
  }
65440
67018
  }));
65441
- app.get("/healthz", (c) => c.json({
67019
+ app.get(paths.health, (c) => c.json({
65442
67020
  status: "ok",
65443
67021
  transport: "http",
65444
- mcpPath: options.path
67022
+ base: paths.base,
67023
+ mcpPath: paths.mcp,
67024
+ controlPath: paths.control,
67025
+ healthPath: paths.health
65445
67026
  }));
65446
- app.all(options.path, basicAuth(options.auth), async (c) => {
67027
+ app.all(paths.mcp, basicAuth(options.auth), async (c) => {
65447
67028
  const sessionId = c.req.header("mcp-session-id");
65448
67029
  if (sessionId) {
65449
67030
  const existing = sessions.get(sessionId);
@@ -65474,6 +67055,36 @@ function createHttpServeApp(options, engine, io = {}) {
65474
67055
  sessions.set(nextSessionId, session);
65475
67056
  return session.transport.handleRequest(c);
65476
67057
  });
67058
+ app.post(paths.control, basicAuth(options.auth), async (c) => {
67059
+ let request;
67060
+ try {
67061
+ const parsed = await c.req.json();
67062
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new CapletsError("REQUEST_INVALID", "Control request JSON must be an object");
67063
+ request = parsed;
67064
+ } catch (error) {
67065
+ const safe = toSafeError(error instanceof CapletsError ? error : new CapletsError("REQUEST_INVALID", "Control request body must be valid JSON", error), "REQUEST_INVALID");
67066
+ return c.json({
67067
+ ok: false,
67068
+ error: {
67069
+ code: safe.code,
67070
+ message: safe.message
67071
+ }
67072
+ });
67073
+ }
67074
+ return c.json(await dispatchRemoteCliRequest(request, controlContext(io, writeErr, authFlowStore, c.req.url, paths.control, options.trustProxy, (name) => c.req.header(name))));
67075
+ });
67076
+ app.get(routePath(paths.control, "auth/callback/:flowId"), async (c) => {
67077
+ const flowId = c.req.param("flowId");
67078
+ const result = await dispatchRemoteCliRequest({
67079
+ command: "auth_login_complete",
67080
+ arguments: {
67081
+ flowId,
67082
+ callbackUrl: c.req.url
67083
+ }
67084
+ }, controlContext(io, writeErr, authFlowStore, c.req.url, paths.control, options.trustProxy, (name) => c.req.header(name)));
67085
+ if (!result.ok) writeErr(`Caplets authentication failed for flow ${flowId}: ${result.error.message}\n`);
67086
+ 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);
67087
+ });
65477
67088
  app.notFound((c) => c.json({ error: "not_found" }, 404));
65478
67089
  app.closeCapletsSessions = async () => {
65479
67090
  await Promise.allSettled([...sessions.values()].map(async (session) => {
@@ -65484,19 +67095,66 @@ function createHttpServeApp(options, engine, io = {}) {
65484
67095
  if (options.warnUnauthenticatedNetwork) writeErr(`Warning: Caplets MCP HTTP server is listening on ${options.host} without authentication.\n`);
65485
67096
  return app;
65486
67097
  }
67098
+ function controlContext(io, writeErr, authFlowStore, requestUrl, controlPath, trustProxy, header) {
67099
+ return {
67100
+ ...io.control,
67101
+ projectCapletsRoot: io.control?.projectCapletsRoot ?? resolveProjectCapletsRoot(),
67102
+ authFlowStore,
67103
+ controlCallbackBaseUrl: new URL(controlPath, publicRequestOrigin(requestUrl, trustProxy, header)).toString(),
67104
+ writeErr
67105
+ };
67106
+ }
67107
+ function publicRequestOrigin(requestUrl, trustProxy, header) {
67108
+ const url = new URL(requestUrl);
67109
+ if (!trustProxy) return `${url.protocol.slice(0, -1)}://${header("host") ?? url.host}`;
67110
+ const forwardedProto = firstForwardedValue(header("x-forwarded-proto"));
67111
+ const forwardedHost = firstForwardedValue(header("x-forwarded-host"));
67112
+ return `${forwardedProto === "http" || forwardedProto === "https" ? forwardedProto : url.protocol.slice(0, -1)}://${forwardedHost ?? header("host") ?? url.host}`;
67113
+ }
67114
+ function firstForwardedValue(value) {
67115
+ return value?.split(",", 1)[0]?.trim() || void 0;
67116
+ }
65487
67117
  async function serveHttp(options, engineOptions = {}, writeErr = (value) => process.stderr.write(value)) {
65488
67118
  const engine = new CapletsEngine(engineOptions);
65489
- const app = createHttpServeApp(options, engine, { writeErr });
67119
+ const app = createHttpServeApp(options, engine, {
67120
+ writeErr,
67121
+ control: {
67122
+ ...engineOptions,
67123
+ projectCapletsRoot: projectCapletsRootForEngineOptions(engineOptions)
67124
+ }
67125
+ });
67126
+ const paths = servicePaths(options.path);
67127
+ const origin = `http://${formatHost(options.host)}:${options.port}`;
67128
+ const baseUrl = `${origin}${paths.base === "/" ? "" : paths.base}`;
65490
67129
  installHttpSignalHandlers(serve({
65491
67130
  fetch: app.fetch,
65492
67131
  hostname: options.host,
65493
67132
  port: options.port
65494
67133
  }, () => {
65495
- writeErr(`Caplets MCP HTTP server listening on http://${formatHost(options.host)}:${options.port}${options.path}\n`);
65496
- writeErr(`Health check: http://${formatHost(options.host)}:${options.port}/healthz\n`);
67134
+ writeErr(`Caplets HTTP service listening on ${baseUrl}\n`);
67135
+ writeErr(`MCP endpoint: ${origin}${paths.mcp}\n`);
67136
+ writeErr(`Control endpoint: ${origin}${paths.control}\n`);
67137
+ writeErr(`Health check: ${origin}${paths.health}\n`);
65497
67138
  writeErr(`Basic Auth: ${options.auth.enabled ? `enabled (user: ${options.auth.user})` : "disabled"}\n`);
65498
67139
  }), app, engine, writeErr);
65499
67140
  }
67141
+ function projectCapletsRootForEngineOptions(engineOptions) {
67142
+ return engineOptions.projectConfigPath ? resolveProjectCapletsRootForConfigPath(engineOptions.projectConfigPath) : resolveProjectCapletsRoot();
67143
+ }
67144
+ function resolveProjectCapletsRootForConfigPath(projectConfigPath) {
67145
+ return dirname(projectConfigPath);
67146
+ }
67147
+ function routePath(base, path) {
67148
+ return base === "/" ? `/${path}` : `${base}/${path}`;
67149
+ }
67150
+ function servicePaths(base) {
67151
+ return {
67152
+ base,
67153
+ mcp: routePath(base, "mcp"),
67154
+ control: routePath(base, "control"),
67155
+ health: routePath(base, "healthz")
67156
+ };
67157
+ }
65500
67158
  async function createHttpSession(engine, sessionId, options, onClose) {
65501
67159
  const transport = new StreamableHTTPTransport({
65502
67160
  sessionIdGenerator: () => sessionId,
@@ -65574,7 +67232,8 @@ const HTTP_ONLY_OPTIONS = [
65574
67232
  "path",
65575
67233
  "user",
65576
67234
  "password",
65577
- "allowUnauthenticatedHttp"
67235
+ "allowUnauthenticatedHttp",
67236
+ "trustProxy"
65578
67237
  ];
65579
67238
  function resolveServeOptions(raw, env = process.env) {
65580
67239
  const transport = parseTransport(raw.transport ?? "stdio");
@@ -65583,9 +67242,10 @@ function resolveServeOptions(raw, env = process.env) {
65583
67242
  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
67243
  return { transport };
65585
67244
  }
65586
- const host = nonEmpty(raw.host, "--host") ?? "127.0.0.1";
65587
- const port = parsePort(raw.port ?? 5387);
65588
- const path = normalizeHttpPath(raw.path ?? "/mcp");
67245
+ const serverUrl = env.CAPLETS_SERVER_URL ? parseServeServerUrl(nonEmpty(env.CAPLETS_SERVER_URL, "CAPLETS_SERVER_URL")) : void 0;
67246
+ const host = nonEmpty(raw.host, "--host") ?? serverUrlHost(serverUrl) ?? "127.0.0.1";
67247
+ const port = parsePort(raw.port ?? (serverUrl?.port ? Number(serverUrl.port) : 5387));
67248
+ const path = normalizeHttpPath(raw.path ?? serverUrl?.pathname ?? "/");
65589
67249
  const userWasExplicit = raw.user !== void 0 || hasEnv(env.CAPLETS_SERVER_USER);
65590
67250
  const user = nonEmpty(raw.user, "--user") ?? nonEmpty(env.CAPLETS_SERVER_USER, "CAPLETS_SERVER_USER") ?? "caplets";
65591
67251
  const password = nonEmpty(raw.password, "--password") ?? nonEmpty(env.CAPLETS_SERVER_PASSWORD, "CAPLETS_SERVER_PASSWORD");
@@ -65607,13 +67267,22 @@ function resolveServeOptions(raw, env = process.env) {
65607
67267
  path,
65608
67268
  auth,
65609
67269
  warnUnauthenticatedNetwork: !loopback && !auth.enabled,
65610
- loopback
67270
+ loopback,
67271
+ trustProxy: raw.trustProxy === true
65611
67272
  };
65612
67273
  }
65613
67274
  function isLoopbackHost(host) {
65614
67275
  const normalized = host.toLocaleLowerCase();
65615
67276
  return normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1";
65616
67277
  }
67278
+ function parseServeServerUrl(value) {
67279
+ try {
67280
+ return parseServerBaseUrl(value);
67281
+ } catch (error) {
67282
+ 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.");
67283
+ throw error;
67284
+ }
67285
+ }
65617
67286
  function parseTransport(value) {
65618
67287
  if (value === "stdio" || value === "http") return value;
65619
67288
  throw new CapletsError("REQUEST_INVALID", `Expected --transport to be stdio or http, got ${value}`);
@@ -65628,6 +67297,9 @@ function normalizeHttpPath(value) {
65628
67297
  if (value.includes("?") || value.includes("#")) throw new CapletsError("REQUEST_INVALID", "HTTP --path must not include a query string or fragment");
65629
67298
  return value === "/" ? value : value.replace(/\/+$/u, "");
65630
67299
  }
67300
+ function serverUrlHost(url) {
67301
+ return url?.hostname.replace(/^\[(.*)\]$/u, "$1");
67302
+ }
65631
67303
  function nonEmpty(value, label) {
65632
67304
  if (value === void 0) return;
65633
67305
  const trimmed = value.trim();
@@ -65751,9 +67423,14 @@ async function runCli(args, io = {}) {
65751
67423
  throw error;
65752
67424
  }
65753
67425
  }
67426
+ function normalizeCompletionWords(words) {
67427
+ return words.map((word) => word === "__CAPLETS_TRAILING_SPACE__" ? "" : word);
67428
+ }
65754
67429
  function createProgram(io = {}) {
65755
67430
  const writeOut = io.writeOut ?? ((value) => process.stdout.write(value));
65756
67431
  const writeErr = io.writeErr ?? ((value) => process.stderr.write(value));
67432
+ const env = io.env ?? process.env;
67433
+ const currentConfigPath = () => envConfigPath(env);
65757
67434
  const setExitCode = io.setExitCode ?? ((code) => {
65758
67435
  process.exitCode = code;
65759
67436
  });
@@ -65763,42 +67440,98 @@ function createProgram(io = {}) {
65763
67440
  writeErr,
65764
67441
  outputError: (value, write) => write(value)
65765
67442
  });
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 MCP endpoint 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").action(async (options) => {
67443
+ program.command(cliCommands.completion).description("Print a shell completion script.").argument("<shell>", "completion shell: bash, zsh, fish, powershell, or cmd").action((shell) => {
67444
+ if (!completionShells.includes(shell)) throw new CapletsError("REQUEST_INVALID", "completion shell must be bash, zsh, fish, powershell, or cmd");
67445
+ writeOut(completionScript(shell));
67446
+ });
67447
+ program.command(cliCommands.completeHidden, { hidden: true }).description("Internal shell completion endpoint.").option("--shell <shell>", "completion shell").allowUnknownOption(true).argument("[words...]", "words to complete").action(async (words, options) => {
67448
+ const shell = completionShells.includes(options.shell) ? options.shell : "bash";
67449
+ const remote = remoteClientForCli(io);
67450
+ const configPath = currentConfigPath();
67451
+ const completionWords = normalizeCompletionWords(words);
67452
+ let suggestions = [];
67453
+ try {
67454
+ suggestions = remote ? await remote.request("complete_cli", {
67455
+ shell,
67456
+ words: completionWords
67457
+ }) : await completeCliWords(completionWords, configPath ? { configPath } : {});
67458
+ } catch {
67459
+ suggestions = [];
67460
+ }
67461
+ if (suggestions.length > 0) writeOut(`${suggestions.join("\n")}\n`);
67462
+ });
67463
+ program.command(cliCommands.serve).description("Serve configured Caplets as an MCP server.").option("--transport <transport>", "server transport: stdio or http").option("--host <host>", "HTTP bind host").option("--port <port>", "HTTP bind port").option("--path <path>", "HTTP service base path").option("--user <user>", "HTTP Basic Auth username").option("--password <password>", "HTTP Basic Auth password").option("--allow-unauthenticated-http", "allow unauthenticated HTTP serving on non-loopback hosts").option("--trust-proxy", "trust X-Forwarded-* headers from a reverse proxy").action(async (options) => {
65767
67464
  const resolved = resolveServeOptions(options);
65768
- const configPath = envConfigPath();
67465
+ const configPath = currentConfigPath();
65769
67466
  await (io.serve ?? ((serveOptions) => serveResolvedCaplets(serveOptions, {
65770
67467
  ...configPath ? { configPath } : {},
65771
67468
  ...io.authDir ? { authDir: io.authDir } : {}
65772
67469
  }, writeErr)))(resolved);
65773
67470
  });
65774
- program.command("init").description("Create a starter Caplets config file.").option("--force", "overwrite an existing config file").action((options) => {
65775
- const configPath = envConfigPath();
67471
+ program.command(cliCommands.init).description("Create a starter Caplets config file.").option("--force", "overwrite an existing config file").action(async (options) => {
67472
+ const remote = remoteClientForCli(io);
67473
+ if (remote) {
67474
+ writeOut(`Created remote Caplets config at ${(await remote.request("init", { force: Boolean(options.force) })).path}\n`);
67475
+ return;
67476
+ }
67477
+ const configPath = currentConfigPath();
65776
67478
  writeOut(`Created Caplets config at ${initConfig({
65777
67479
  ...configPath ? { path: configPath } : {},
65778
67480
  force: Boolean(options.force)
65779
67481
  })}\n`);
65780
67482
  });
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 rows = listCaplets(loadConfigWithSources(envConfigPath()), { includeDisabled: Boolean(options.all) });
67483
+ program.command(cliCommands.list).description("List configured Caplets.").option("--all", "include disabled Caplets").option("--json", "print JSON output").option("--format <format>", "output format: plain, markdown, md, or json", parseOutputFormat).action(async (options) => {
67484
+ const includeDisabled = Boolean(options.all);
67485
+ const remote = remoteClientForCli(io);
67486
+ if (remote) {
67487
+ const rows = await remote.request("list", { includeDisabled });
67488
+ if (options.json || options.format === "json") {
67489
+ writeOut(`${JSON.stringify(rows, null, 2)}\n`);
67490
+ return;
67491
+ }
67492
+ writeOut(formatCapletList(rows, options.format ?? "plain"));
67493
+ return;
67494
+ }
67495
+ const rows = listCaplets(loadConfigWithSources(currentConfigPath()), { includeDisabled });
65783
67496
  if (options.json || options.format === "json") {
65784
67497
  writeOut(`${JSON.stringify(rows, null, 2)}\n`);
65785
67498
  return;
65786
67499
  }
65787
67500
  writeOut(formatCapletList(rows, options.format ?? "plain"));
65788
67501
  });
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) => {
67502
+ program.command(cliCommands.install).description("Install Caplets from a repo's caplets directory.").argument("<repo>", "local repo path, Git URL, or GitHub owner/repo").argument("[caplets...]", "optional Caplet IDs to install").option("-g, --global", "install to the user Caplets root").option("--force", "overwrite installed Caplets").action(async (repo, capletIds, options) => {
67503
+ const remote = remoteClientForCli(io);
67504
+ if (remote) {
67505
+ if (options.global) writeErr("Warning: --global is not supported in remote mode; the server controls the installation destination.\n");
67506
+ const result = await remote.request("install", {
67507
+ repo,
67508
+ capletIds,
67509
+ force: Boolean(options.force)
67510
+ });
67511
+ for (const caplet of result.installed) writeOut(`Installed ${caplet.id} to remote ${caplet.destination}\n`);
67512
+ return;
67513
+ }
65790
67514
  const result = installCaplets(repo, {
65791
67515
  capletIds,
65792
67516
  force: Boolean(options.force),
65793
- destinationRoot: options.global ? resolveCapletsRoot(resolveConfigPath(envConfigPath())) : resolveProjectCapletsRoot()
67517
+ destinationRoot: options.global ? resolveCapletsRoot(resolveConfigPath(currentConfigPath())) : resolveProjectCapletsRoot()
65794
67518
  });
65795
67519
  for (const caplet of result.installed) writeOut(`Installed ${caplet.id} to ${caplet.destination}\n`);
65796
67520
  });
65797
- 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) => {
67521
+ const add = program.command(cliCommands.add).description("Add generated Caplet files.");
67522
+ 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) => {
67523
+ const remote = remoteClientForCli(io);
67524
+ if (remote) {
67525
+ writeAddResult(writeOut, "CLI", await remote.request("add", {
67526
+ kind: "cli",
67527
+ id,
67528
+ options: remoteAddOptions(options)
67529
+ }));
67530
+ return;
67531
+ }
65799
67532
  const result = addCliCaplet(id, {
65800
67533
  ...options,
65801
- destinationRoot: options.global ? resolveCapletsRoot(resolveConfigPath(envConfigPath())) : resolveProjectCapletsRoot()
67534
+ destinationRoot: options.global ? resolveCapletsRoot(resolveConfigPath(currentConfigPath())) : resolveProjectCapletsRoot()
65802
67535
  });
65803
67536
  if (result.path) {
65804
67537
  writeOut(`Wrote CLI Caplet to ${result.path}\n`);
@@ -65806,58 +67539,100 @@ function createProgram(io = {}) {
65806
67539
  }
65807
67540
  writeOut(result.text);
65808
67541
  });
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) => {
67542
+ 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) => {
67543
+ const remote = remoteClientForCli(io);
67544
+ if (remote) {
67545
+ writeAddResult(writeOut, "MCP", await remote.request("add", {
67546
+ kind: "mcp",
67547
+ id,
67548
+ options: remoteAddOptions(options)
67549
+ }));
67550
+ return;
67551
+ }
65810
67552
  writeAddResult(writeOut, "MCP", addMcpCaplet(id, {
65811
67553
  ...options,
65812
- destinationRoot: addDestinationRoot(options)
67554
+ destinationRoot: addDestinationRoot(options, currentConfigPath())
65813
67555
  }));
65814
67556
  });
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) => {
67557
+ 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) => {
67558
+ const remote = remoteClientForCli(io);
67559
+ if (remote) {
67560
+ writeAddResult(writeOut, "OpenAPI", await remote.request("add", {
67561
+ kind: "openapi",
67562
+ id,
67563
+ options: remoteAddOptions(options)
67564
+ }));
67565
+ return;
67566
+ }
65816
67567
  writeAddResult(writeOut, "OpenAPI", addOpenApiCaplet(id, {
65817
67568
  ...options,
65818
- destinationRoot: addDestinationRoot(options)
67569
+ destinationRoot: addDestinationRoot(options, currentConfigPath())
65819
67570
  }));
65820
67571
  });
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) => {
67572
+ 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) => {
67573
+ const remote = remoteClientForCli(io);
67574
+ if (remote) {
67575
+ writeAddResult(writeOut, "GraphQL", await remote.request("add", {
67576
+ kind: "graphql",
67577
+ id,
67578
+ options: remoteAddOptions(options)
67579
+ }));
67580
+ return;
67581
+ }
65822
67582
  writeAddResult(writeOut, "GraphQL", addGraphqlCaplet(id, {
65823
67583
  ...options,
65824
- destinationRoot: addDestinationRoot(options)
67584
+ destinationRoot: addDestinationRoot(options, currentConfigPath())
65825
67585
  }));
65826
67586
  });
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) => {
67587
+ 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) => {
67588
+ const remote = remoteClientForCli(io);
67589
+ if (remote) {
67590
+ writeAddResult(writeOut, "HTTP", await remote.request("add", {
67591
+ kind: "http",
67592
+ id,
67593
+ options: remoteAddOptions(options)
67594
+ }));
67595
+ return;
67596
+ }
65828
67597
  writeAddResult(writeOut, "HTTP", addHttpCaplet(id, {
65829
67598
  ...options,
65830
- destinationRoot: addDestinationRoot(options)
67599
+ destinationRoot: addDestinationRoot(options, currentConfigPath())
65831
67600
  }));
65832
67601
  });
65833
- 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) => {
67602
+ program.command(cliCommands.getCaplet).description("Print a configured Caplet card.").argument("<caplet>", "configured Caplet ID").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => {
65834
67603
  await executeOperation(caplet, { operation: "get_caplet" }, {
65835
67604
  writeOut,
65836
67605
  writeErr,
65837
67606
  setExitCode,
65838
67607
  authDir: io.authDir,
67608
+ env,
67609
+ remote: remoteClientForCli(io),
65839
67610
  format: options.format
65840
67611
  });
65841
67612
  });
65842
- program.command("check-backend").description("Check backend availability for a configured Caplet.").argument("<caplet>", "configured Caplet ID").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => {
67613
+ program.command(cliCommands.checkBackend).description("Check backend availability for a configured Caplet.").argument("<caplet>", "configured Caplet ID").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => {
65843
67614
  await executeOperation(caplet, { operation: "check_backend" }, {
65844
67615
  writeOut,
65845
67616
  writeErr,
65846
67617
  setExitCode,
65847
67618
  authDir: io.authDir,
67619
+ env,
67620
+ remote: remoteClientForCli(io),
65848
67621
  format: options.format
65849
67622
  });
65850
67623
  });
65851
- program.command("list-tools").description("List downstream tools for a configured Caplet.").argument("<caplet>", "configured Caplet ID").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => {
67624
+ program.command(cliCommands.listTools).description("List downstream tools for a configured Caplet.").argument("<caplet>", "configured Caplet ID").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => {
65852
67625
  await executeOperation(caplet, { operation: "list_tools" }, {
65853
67626
  writeOut,
65854
67627
  writeErr,
65855
67628
  setExitCode,
65856
67629
  authDir: io.authDir,
67630
+ env,
67631
+ remote: remoteClientForCli(io),
65857
67632
  format: options.format
65858
67633
  });
65859
67634
  });
65860
- program.command("search-tools").description("Search downstream tools for a configured Caplet.").argument("<caplet>", "configured Caplet ID").argument("<query>", "search query").option("--limit <n>", "maximum number of tools to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, query, options) => {
67635
+ program.command(cliCommands.searchTools).description("Search downstream tools for a configured Caplet.").argument("<caplet>", "configured Caplet ID").argument("<query>", "search query").option("--limit <n>", "maximum number of tools to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, query, options) => {
65861
67636
  await executeOperation(caplet, options.limit === void 0 ? {
65862
67637
  operation: "search_tools",
65863
67638
  query
@@ -65870,10 +67645,12 @@ function createProgram(io = {}) {
65870
67645
  writeErr,
65871
67646
  setExitCode,
65872
67647
  authDir: io.authDir,
67648
+ env,
67649
+ remote: remoteClientForCli(io),
65873
67650
  format: options.format
65874
67651
  });
65875
67652
  });
65876
- program.command("get-tool").description("Print one downstream tool schema.").argument("<caplet.tool>", "qualified target, split on the first dot").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (target, options) => {
67653
+ program.command(cliCommands.getTool).description("Print one downstream tool schema.").argument("<caplet.tool>", "qualified target, split on the first dot").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (target, options) => {
65877
67654
  const { caplet, tool } = parseQualifiedTarget(target);
65878
67655
  await executeOperation(caplet, {
65879
67656
  operation: "get_tool",
@@ -65883,10 +67660,12 @@ function createProgram(io = {}) {
65883
67660
  writeErr,
65884
67661
  setExitCode,
65885
67662
  authDir: io.authDir,
67663
+ env,
67664
+ remote: remoteClientForCli(io),
65886
67665
  format: options.format
65887
67666
  });
65888
67667
  });
65889
- program.command("call-tool").description("Call one downstream tool.").argument("<caplet.tool>", "qualified target, split on the first dot").option("--args <json-object>", "JSON object of downstream tool arguments").option("--field <path>", "project a field from structured output", collect, []).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (target, options) => {
67668
+ program.command(cliCommands.callTool).description("Call one downstream tool.").argument("<caplet.tool>", "qualified target, split on the first dot").option("--args <json-object>", "JSON object of downstream tool arguments").option("--field <path>", "project a field from structured output", collect, []).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (target, options) => {
65890
67669
  const { caplet, tool } = parseQualifiedTarget(target);
65891
67670
  await executeOperation(caplet, {
65892
67671
  operation: "call_tool",
@@ -65898,24 +67677,150 @@ function createProgram(io = {}) {
65898
67677
  writeErr,
65899
67678
  setExitCode,
65900
67679
  authDir: io.authDir,
67680
+ env,
67681
+ remote: remoteClientForCli(io),
65901
67682
  format: options.format
65902
67683
  });
65903
67684
  });
65904
- const config = program.command("config").description("Inspect Caplets config locations.");
67685
+ program.command(cliCommands.listResources).description("List MCP resources for a configured MCP Caplet.").argument("<caplet>").option("--limit <n>", "maximum number of resources to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => executeOperation(caplet, options.limit === void 0 ? { operation: "list_resources" } : {
67686
+ operation: "list_resources",
67687
+ limit: options.limit
67688
+ }, {
67689
+ writeOut,
67690
+ writeErr,
67691
+ setExitCode,
67692
+ authDir: io.authDir,
67693
+ env,
67694
+ remote: remoteClientForCli(io),
67695
+ format: options.format
67696
+ }));
67697
+ program.command(cliCommands.searchResources).description("Search MCP resources and resource templates for a configured MCP Caplet.").argument("<caplet>").argument("<query>").option("--limit <n>", "maximum number of matches to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, query, options) => executeOperation(caplet, options.limit === void 0 ? {
67698
+ operation: "search_resources",
67699
+ query
67700
+ } : {
67701
+ operation: "search_resources",
67702
+ query,
67703
+ limit: options.limit
67704
+ }, {
67705
+ writeOut,
67706
+ writeErr,
67707
+ setExitCode,
67708
+ authDir: io.authDir,
67709
+ env,
67710
+ remote: remoteClientForCli(io),
67711
+ format: options.format
67712
+ }));
67713
+ program.command(cliCommands.listResourceTemplates).description("List MCP resource templates for a configured MCP Caplet.").argument("<caplet>").option("--limit <n>", "maximum number of templates to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => executeOperation(caplet, options.limit === void 0 ? { operation: "list_resource_templates" } : {
67714
+ operation: "list_resource_templates",
67715
+ limit: options.limit
67716
+ }, {
67717
+ writeOut,
67718
+ writeErr,
67719
+ setExitCode,
67720
+ authDir: io.authDir,
67721
+ env,
67722
+ remote: remoteClientForCli(io),
67723
+ format: options.format
67724
+ }));
67725
+ program.command(cliCommands.readResource).description("Read one MCP resource by URI.").argument("<caplet>").argument("<uri>").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, uri, options) => executeOperation(caplet, {
67726
+ operation: "read_resource",
67727
+ uri
67728
+ }, {
67729
+ writeOut,
67730
+ writeErr,
67731
+ setExitCode,
67732
+ authDir: io.authDir,
67733
+ env,
67734
+ remote: remoteClientForCli(io),
67735
+ format: options.format
67736
+ }));
67737
+ program.command(cliCommands.listPrompts).description("List MCP prompts for a configured MCP Caplet.").argument("<caplet>").option("--limit <n>", "maximum number of prompts to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => executeOperation(caplet, options.limit === void 0 ? { operation: "list_prompts" } : {
67738
+ operation: "list_prompts",
67739
+ limit: options.limit
67740
+ }, {
67741
+ writeOut,
67742
+ writeErr,
67743
+ setExitCode,
67744
+ authDir: io.authDir,
67745
+ env,
67746
+ remote: remoteClientForCli(io),
67747
+ format: options.format
67748
+ }));
67749
+ program.command(cliCommands.searchPrompts).description("Search MCP prompts for a configured MCP Caplet.").argument("<caplet>").argument("<query>").option("--limit <n>", "maximum number of prompts to return", parsePositiveInteger).option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, query, options) => executeOperation(caplet, options.limit === void 0 ? {
67750
+ operation: "search_prompts",
67751
+ query
67752
+ } : {
67753
+ operation: "search_prompts",
67754
+ query,
67755
+ limit: options.limit
67756
+ }, {
67757
+ writeOut,
67758
+ writeErr,
67759
+ setExitCode,
67760
+ authDir: io.authDir,
67761
+ env,
67762
+ remote: remoteClientForCli(io),
67763
+ format: options.format
67764
+ }));
67765
+ program.command(cliCommands.getPrompt).description("Get one MCP prompt by name.").argument("<caplet.prompt>", "qualified target, split on the first dot").option("--args <json-object>", "JSON object of prompt arguments").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (target, options) => {
67766
+ const { caplet, tool: prompt } = parseQualifiedTarget(target);
67767
+ await executeOperation(caplet, {
67768
+ operation: "get_prompt",
67769
+ prompt,
67770
+ arguments: parseJsonObjectOption(options.args, "get-prompt --args")
67771
+ }, {
67772
+ writeOut,
67773
+ writeErr,
67774
+ setExitCode,
67775
+ authDir: io.authDir,
67776
+ env,
67777
+ remote: remoteClientForCli(io),
67778
+ format: options.format
67779
+ });
67780
+ });
67781
+ program.command(cliCommands.complete).description("Complete an MCP prompt or resource-template argument.").argument("<caplet>").requiredOption("--argument <name>", "argument name").option("--value <value>", "argument prefix", "").option("--prompt <name>", "prompt name to complete").option("--resource-template <uri-template>", "resource template URI to complete").option("--format <format>", "output format: markdown, md, plain, or json", parseOutputFormat).action(async (caplet, options) => executeOperation(caplet, {
67782
+ operation: "complete",
67783
+ ref: completionRefFromOptions(options),
67784
+ argument: {
67785
+ name: options.argument,
67786
+ value: options.value
67787
+ }
67788
+ }, {
67789
+ writeOut,
67790
+ writeErr,
67791
+ setExitCode,
67792
+ authDir: io.authDir,
67793
+ env,
67794
+ remote: remoteClientForCli(io),
67795
+ format: options.format
67796
+ }));
67797
+ const config = program.command(cliCommands.config).description("Inspect Caplets config locations.");
65905
67798
  config.command("path").description("Print the effective user config path.").action(() => {
65906
- writeOut(`${resolveConfigPath(envConfigPath())}\n`);
67799
+ writeOut(`${resolveConfigPath(currentConfigPath())}\n`);
65907
67800
  });
65908
67801
  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(envConfigPath(), io.authDir);
67802
+ const paths = resolveCliConfigPaths(currentConfigPath(), io.authDir);
65910
67803
  if (options.json || options.format === "json") {
65911
67804
  writeOut(`${JSON.stringify(paths, null, 2)}\n`);
65912
67805
  return;
65913
67806
  }
65914
67807
  writeOut(formatConfigPaths(paths, options.format ?? "plain"));
65915
67808
  });
65916
- const auth = program.command("auth").description("Manage OAuth credentials for remote servers.");
67809
+ const auth = program.command(cliCommands.auth).description("Manage OAuth credentials for remote servers.");
65917
67810
  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 configPath = envConfigPath();
67811
+ const remote = remoteClientForCli(io);
67812
+ if (remote) {
67813
+ const started = await remote.request("auth_login_start", { server: serverId });
67814
+ if (started.authorizationUrl) {
67815
+ writeOut(`Open this URL to authorize ${serverId}:\n${started.authorizationUrl}\n`);
67816
+ if (options.open !== false) await openBrowser(started.authorizationUrl);
67817
+ writeOut("Complete authentication in your browser. The server callback will store credentials.\n");
67818
+ return;
67819
+ }
67820
+ if (started.authenticated) writeOut(`Authenticated \`${serverId}\`.\n`);
67821
+ return;
67822
+ }
67823
+ const configPath = currentConfigPath();
65919
67824
  await loginAuth(serverId, {
65920
67825
  noOpen: options.open === false,
65921
67826
  writeOut,
@@ -65924,27 +67829,86 @@ function createProgram(io = {}) {
65924
67829
  ...io.authDir ? { authDir: io.authDir } : {}
65925
67830
  });
65926
67831
  });
65927
- auth.command("logout").description("Delete stored OAuth credentials for a server.").argument("<server>", "configured server ID").action((serverId) => {
65928
- const configPath = envConfigPath();
67832
+ auth.command("logout").description("Delete stored OAuth credentials for a server.").argument("<server>", "configured server ID").action(async (serverId) => {
67833
+ const remote = remoteClientForCli(io);
67834
+ if (remote) {
67835
+ writeOut((await remote.request("auth_logout", { server: serverId })).deleted ? `Deleted remote OAuth credentials for \`${serverId}\`.\n` : `No remote OAuth credentials found for \`${serverId}\`.\n`);
67836
+ return;
67837
+ }
67838
+ const configPath = currentConfigPath();
65929
67839
  logoutAuth(serverId, {
65930
67840
  writeOut,
65931
67841
  ...configPath ? { configPath } : {},
65932
67842
  ...io.authDir ? { authDir: io.authDir } : {}
65933
67843
  });
65934
67844
  });
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 = envConfigPath();
67845
+ 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) => {
67846
+ const configPath = currentConfigPath();
67847
+ const format = options.json || options.format === "json" ? "json" : options.format ?? "plain";
67848
+ const remote = remoteClientForCli(io);
67849
+ if (remote) {
67850
+ const rows = await remote.request("auth_list", {});
67851
+ if (format === "json") {
67852
+ writeOut(`${JSON.stringify(rows, null, 2)}\n`);
67853
+ return;
67854
+ }
67855
+ writeOut(formatAuthRows(rows, format));
67856
+ return;
67857
+ }
65937
67858
  listAuth({
65938
67859
  writeOut,
65939
- format: options.json || options.format === "json" ? "json" : options.format ?? "plain",
67860
+ format,
65940
67861
  ...configPath ? { configPath } : {},
65941
67862
  ...io.authDir ? { authDir: io.authDir } : {}
65942
67863
  });
65943
67864
  });
65944
67865
  return program;
65945
67866
  }
65946
- function envConfigPath() {
65947
- return process.env.CAPLETS_CONFIG?.trim() || void 0;
67867
+ function envConfigPath(env) {
67868
+ return env.CAPLETS_CONFIG?.trim() || void 0;
67869
+ }
67870
+ function remoteClientForCli(io) {
67871
+ const env = io.env ?? process.env;
67872
+ if (resolveCapletsMode({}, env).mode !== "remote") return;
67873
+ return new RemoteControlClient(resolveCapletsServer(io.fetch ? { fetch: io.fetch } : {}, env));
67874
+ }
67875
+ async function openBrowser(url) {
67876
+ const { spawn } = await import("node:child_process");
67877
+ spawn(process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open", process.platform === "win32" ? [
67878
+ "/c",
67879
+ "start",
67880
+ "",
67881
+ url
67882
+ ] : [url], {
67883
+ stdio: "ignore",
67884
+ detached: true
67885
+ }).unref();
67886
+ }
67887
+ function remoteCommandForOperation(operation) {
67888
+ switch (operation) {
67889
+ case "get_caplet":
67890
+ case "check_backend":
67891
+ case "list_tools":
67892
+ case "search_tools":
67893
+ case "get_tool":
67894
+ case "call_tool":
67895
+ case "list_resources":
67896
+ case "search_resources":
67897
+ case "list_resource_templates":
67898
+ case "read_resource":
67899
+ case "list_prompts":
67900
+ case "search_prompts":
67901
+ case "get_prompt":
67902
+ case "complete": return operation;
67903
+ default: return;
67904
+ }
67905
+ }
67906
+ function remoteAddOptions(options) {
67907
+ const { output, print, global, destinationRoot, ...remoteOptions } = options;
67908
+ if (global) throw new CapletsError("REQUEST_INVALID", "--global is not supported in remote mode; the server controls the add destination.");
67909
+ if (print) throw new CapletsError("REQUEST_INVALID", "--print is not supported in remote mode; the server controls add output.");
67910
+ if (output !== void 0) throw new CapletsError("REQUEST_INVALID", "--output is not supported in remote mode; the server controls the add destination.");
67911
+ return remoteOptions;
65948
67912
  }
65949
67913
  function collect(value, previous) {
65950
67914
  previous.push(value);
@@ -65983,11 +67947,48 @@ function parseCallToolArgs(value) {
65983
67947
  if (!isPlainObject(parsed)) throw new CapletsError("REQUEST_INVALID", "call-tool --args must be a JSON object");
65984
67948
  return parsed;
65985
67949
  }
67950
+ function parseJsonObjectOption(value, label) {
67951
+ if (value === void 0) return {};
67952
+ let parsed;
67953
+ try {
67954
+ parsed = JSON.parse(value);
67955
+ } catch (error) {
67956
+ throw new CapletsError("REQUEST_INVALID", `${label} must be valid JSON`, error);
67957
+ }
67958
+ if (!isPlainObject(parsed)) throw new CapletsError("REQUEST_INVALID", `${label} must be a JSON object`);
67959
+ return parsed;
67960
+ }
67961
+ function completionRefFromOptions(options) {
67962
+ if (options.prompt && options.resourceTemplate) throw new CapletsError("REQUEST_INVALID", "complete accepts either --prompt or --resource-template, not both");
67963
+ if (options.prompt) return {
67964
+ type: "prompt",
67965
+ name: options.prompt
67966
+ };
67967
+ if (options.resourceTemplate) return {
67968
+ type: "resourceTemplate",
67969
+ uri: options.resourceTemplate
67970
+ };
67971
+ throw new CapletsError("REQUEST_INVALID", "complete requires --prompt or --resource-template");
67972
+ }
65986
67973
  function isPlainObject(value) {
65987
67974
  return value !== null && typeof value === "object" && !Array.isArray(value);
65988
67975
  }
65989
67976
  async function executeOperation(caplet, request, io) {
65990
- const configPath = envConfigPath();
67977
+ const command = remoteCommandForOperation(request.operation);
67978
+ if (io.remote && command) {
67979
+ const result = await io.remote.request(command, {
67980
+ caplet,
67981
+ request
67982
+ });
67983
+ const output = cliOutputForOperation(result, {
67984
+ ...request,
67985
+ caplet
67986
+ }, io.format ?? "markdown");
67987
+ io.writeOut(typeof output === "string" ? `${output}\n` : `${JSON.stringify(output, null, 2)}\n`);
67988
+ if (isPlainObject(result) && result.isError === true) io.setExitCode(1);
67989
+ return;
67990
+ }
67991
+ const configPath = envConfigPath(io.env ?? process.env);
65991
67992
  const engine = new CapletsEngine({
65992
67993
  ...configPath ? { configPath } : {},
65993
67994
  ...io.authDir ? { authDir: io.authDir } : {},
@@ -66099,6 +68100,55 @@ function markdownSummaryForOperation(result, request) {
66099
68100
  "",
66100
68101
  "Use `--format json` to inspect the full structured result."
66101
68102
  ].filter((line) => line !== void 0).join("\n");
68103
+ case "list_resources":
68104
+ case "search_resources": {
68105
+ const resources = Array.isArray(payload.resources) ? payload.resources : [];
68106
+ const templates = Array.isArray(payload.resourceTemplates) ? payload.resourceTemplates : [];
68107
+ const matches = Array.isArray(payload.matches) ? payload.matches : [...resources, ...templates];
68108
+ return [
68109
+ `## MCP resources for \`${id}\``,
68110
+ "",
68111
+ `${matches.length} item${matches.length === 1 ? "" : "s"} found.`,
68112
+ "",
68113
+ ...formatResourceLines(matches, "markdown")
68114
+ ].join("\n");
68115
+ }
68116
+ case "list_resource_templates": {
68117
+ const templates = Array.isArray(payload.resourceTemplates) ? payload.resourceTemplates : [];
68118
+ return [
68119
+ `## MCP resource templates for \`${id}\``,
68120
+ "",
68121
+ ...formatResourceLines(templates, "markdown")
68122
+ ].join("\n");
68123
+ }
68124
+ case "read_resource": return [
68125
+ `## Resource \`${String(request.uri ?? "")}\``,
68126
+ "",
68127
+ summarizeResourceRead(payload),
68128
+ "",
68129
+ "Use `--format json` to inspect all contents."
68130
+ ].join("\n");
68131
+ case "list_prompts":
68132
+ case "search_prompts": {
68133
+ const prompts = Array.isArray(payload.prompts) ? payload.prompts : [];
68134
+ return [
68135
+ `## MCP prompts for \`${id}\``,
68136
+ "",
68137
+ ...formatPromptLines(prompts, "markdown")
68138
+ ].join("\n");
68139
+ }
68140
+ case "get_prompt": return [
68141
+ `## Prompt \`${String(request.caplet)}.${String(request.prompt)}\``,
68142
+ "",
68143
+ summarizePromptResult(payload),
68144
+ "",
68145
+ "Use `--format json` to inspect all messages."
68146
+ ].join("\n");
68147
+ case "complete": return [
68148
+ `## Completion for \`${id}\``,
68149
+ "",
68150
+ summarizeCompletionResult(payload)
68151
+ ].join("\n");
66102
68152
  default: return JSON.stringify(payload, null, 2);
66103
68153
  }
66104
68154
  }
@@ -66155,6 +68205,33 @@ function plainSummaryForOperation(result, request) {
66155
68205
  `Result: ${summarizeCallResult(payload)}`,
66156
68206
  "Use --format json to inspect the full structured result."
66157
68207
  ].filter((line) => Boolean(line)).join("\n");
68208
+ case "list_resources":
68209
+ case "search_resources": {
68210
+ const resources = Array.isArray(payload.resources) ? payload.resources : [];
68211
+ const templates = Array.isArray(payload.resourceTemplates) ? payload.resourceTemplates : [];
68212
+ const matches = Array.isArray(payload.matches) ? payload.matches : [...resources, ...templates];
68213
+ return [`MCP resources for ${id} (${matches.length}):`, ...formatResourceLines(matches, "plain")].join("\n");
68214
+ }
68215
+ case "list_resource_templates": {
68216
+ const templates = Array.isArray(payload.resourceTemplates) ? payload.resourceTemplates : [];
68217
+ return [`MCP resource templates for ${id}:`, ...formatResourceLines(templates, "plain")].join("\n");
68218
+ }
68219
+ case "read_resource": return [
68220
+ `Resource ${String(request.uri ?? "")}`,
68221
+ summarizeResourceRead(payload),
68222
+ "Use --format json to inspect all contents."
68223
+ ].join("\n");
68224
+ case "list_prompts":
68225
+ case "search_prompts": {
68226
+ const prompts = Array.isArray(payload.prompts) ? payload.prompts : [];
68227
+ return [`MCP prompts for ${id}:`, ...formatPromptLines(prompts, "plain")].join("\n");
68228
+ }
68229
+ case "get_prompt": return [
68230
+ `Prompt ${String(request.caplet)}.${String(request.prompt)}`,
68231
+ summarizePromptResult(payload),
68232
+ "Use --format json to inspect all messages."
68233
+ ].join("\n");
68234
+ case "complete": return [`Completion for ${id}`, summarizeCompletionResult(payload)].join("\n");
66158
68235
  default: return JSON.stringify(payload, null, 2);
66159
68236
  }
66160
68237
  }
@@ -66171,6 +68248,44 @@ function formatToolLines(tools, format) {
66171
68248
  return `- ${displayName}${flags ? ` (${flags})` : ""}${tool.description ? ` — ${compactDescription(String(tool.description))}` : ""}`;
66172
68249
  });
66173
68250
  }
68251
+ function formatResourceLines(resources, format) {
68252
+ if (resources.length === 0) return ["- none"];
68253
+ return resources.map((resource) => {
68254
+ if (!isPlainObject(resource)) return `- ${String(resource)}`;
68255
+ const name = String(resource.uri ?? resource.uriTemplate ?? "unknown");
68256
+ const displayName = format === "markdown" ? `\`${name}\`` : name;
68257
+ const label = typeof resource.name === "string" ? ` (${resource.name})` : "";
68258
+ return `- ${typeof resource.kind === "string" ? `${resource.kind}: ` : ""}${displayName}${label}${resource.description ? ` — ${compactDescription(String(resource.description))}` : ""}`;
68259
+ });
68260
+ }
68261
+ function formatPromptLines(prompts, format) {
68262
+ if (prompts.length === 0) return ["- none"];
68263
+ return prompts.map((prompt) => {
68264
+ if (!isPlainObject(prompt)) return `- ${String(prompt)}`;
68265
+ const name = String(prompt.prompt ?? prompt.name ?? "unknown");
68266
+ return `- ${format === "markdown" ? `\`${name}\`` : name}${Array.isArray(prompt.arguments) ? ` (${prompt.arguments.length} args)` : ""}${prompt.description ? ` — ${compactDescription(String(prompt.description))}` : ""}`;
68267
+ });
68268
+ }
68269
+ function summarizeResourceRead(payload) {
68270
+ const contents = Array.isArray(payload.contents) ? payload.contents : [];
68271
+ if (contents.length === 0) return "No contents returned.";
68272
+ const first = contents.find(isPlainObject);
68273
+ if (!first) return `${contents.length} content item${contents.length === 1 ? "" : "s"} returned.`;
68274
+ return previewValue(typeof first.text === "string" ? first.text : first.blob) ?? `${contents.length} content item${contents.length === 1 ? "" : "s"} returned.`;
68275
+ }
68276
+ function summarizePromptResult(payload) {
68277
+ const messages = Array.isArray(payload.messages) ? payload.messages : [];
68278
+ if (messages.length === 0) return "No messages returned.";
68279
+ const first = messages.find(isPlainObject);
68280
+ if (!first) return `${messages.length} message${messages.length === 1 ? "" : "s"} returned.`;
68281
+ return previewValue((isPlainObject(first.content) ? first.content : void 0)?.text ?? first.content) ?? `${messages.length} message${messages.length === 1 ? "" : "s"} returned.`;
68282
+ }
68283
+ function summarizeCompletionResult(payload) {
68284
+ const completion = isPlainObject(payload.completion) ? payload.completion : void 0;
68285
+ const values = Array.isArray(completion?.values) ? completion.values : [];
68286
+ if (values.length > 0) return values.map((value) => `- ${String(value)}`).join("\n");
68287
+ return previewValue(payload) ?? "No completions returned.";
68288
+ }
66174
68289
  function compactDescription(value) {
66175
68290
  const firstParagraph = value.trim().split(/\n\s*\n/u)[0] ?? "";
66176
68291
  const collapsed = (firstParagraph.match(/^.*?(?:[.!?](?=\s|$)|$)/u)?.[0] ?? firstParagraph).replace(/\s+/gu, " ").trim();
@@ -66233,19 +68348,19 @@ function schemaSummary(schema) {
66233
68348
  required.length > 0 ? `required ${required.join(", ")}` : "no required fields"
66234
68349
  ].filter((part) => Boolean(part)).join("; ");
66235
68350
  }
66236
- function addDestinationRoot(options) {
66237
- return options.global ? resolveCapletsRoot(resolveConfigPath(envConfigPath())) : resolveProjectCapletsRoot();
68351
+ function addDestinationRoot(options, configPath) {
68352
+ return options.global ? resolveCapletsRoot(resolveConfigPath(configPath)) : resolveProjectCapletsRoot();
66238
68353
  }
66239
68354
  function writeAddResult(writeOut, label, result) {
66240
68355
  if (result.path) {
66241
- writeOut(`Wrote ${label} Caplet to ${result.path}\n`);
68356
+ writeOut(`Wrote ${result.remote ? "remote " : ""}${label} Caplet to ${result.path}\n`);
66242
68357
  return;
66243
68358
  }
66244
68359
  writeOut(result.text);
66245
68360
  }
66246
68361
  //#endregion
66247
68362
  //#region package.json
66248
- var version = "0.15.0";
68363
+ var version = "0.17.0";
66249
68364
  //#endregion
66250
68365
  //#region src/index.ts
66251
68366
  async function main() {