@caplets/core 0.18.3 → 0.18.5

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.
@@ -1,2 +1,5 @@
1
1
  import type { NativeCapletsService } from "./service";
2
- export declare function registerNativeCapletsProcessCleanup(service: NativeCapletsService): void;
2
+ export type NativeCapletsProcessCleanupOptions = {
3
+ writeErr?: (message: string) => void;
4
+ };
5
+ export declare function registerNativeCapletsProcessCleanup(service: NativeCapletsService, options?: NativeCapletsProcessCleanupOptions): void;
@@ -1,6 +1,7 @@
1
1
  import type { NativeCapletsServiceResolutionInput } from "./options";
2
2
  import { resolveNativeCapletsServiceOptions } from "./options";
3
3
  import { type RemoteCapletsClient } from "./remote";
4
+ import { type CapletsConfig } from "../config";
4
5
  import { generatedToolInputJsonSchemaForCaplet } from "../generated-tool-input-schema";
5
6
  export type NativeCapletsServiceOptions = NativeCapletsServiceResolutionInput & {
6
7
  configPath?: string;
@@ -12,6 +13,7 @@ export type NativeCapletsServiceOptions = NativeCapletsServiceResolutionInput &
12
13
  remoteClientFactory?: (options: Extract<ReturnType<typeof resolveNativeCapletsServiceOptions>, {
13
14
  mode: "remote";
14
15
  }>["remote"]) => RemoteCapletsClient;
16
+ localServiceFactory?: (options: LocalNativeCapletsServiceOptions) => NativeCapletsService;
15
17
  };
16
18
  export type NativeCapletTool = {
17
19
  caplet: string;
@@ -31,3 +33,7 @@ export type NativeCapletsService = {
31
33
  close(): Promise<void>;
32
34
  };
33
35
  export declare function createNativeCapletsService(options?: NativeCapletsServiceOptions): NativeCapletsService;
36
+ type LocalNativeCapletsServiceOptions = NativeCapletsServiceOptions & {
37
+ configLoader?: (configPath: string, projectConfigPath: string) => CapletsConfig;
38
+ };
39
+ export {};
package/dist/native.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { createNativeCapletsService, type NativeCapletTool, type NativeCapletsService, type NativeCapletsServiceOptions, type NativeCapletsToolsChangedListener, } from "./native/service";
2
- export { registerNativeCapletsProcessCleanup } from "./native/process-cleanup";
2
+ export { registerNativeCapletsProcessCleanup, type NativeCapletsProcessCleanupOptions, } from "./native/process-cleanup";
3
3
  export { nativeCapletPromptGuidance, nativeCapletToolDescription, nativeCapletToolName, nativeCapletsSystemGuidance, } from "./native/tools";
4
4
  export { generatedToolInputSchema } from "./tools";
5
5
  export { generatedToolInputJsonSchema } from "./generated-tool-input-schema";
package/dist/native.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Ot as CapletsError, Q as ToolListChangedNotificationSchema, _ as StreamableHTTPClientTransport, a as resolveCapletsServer, b as Client, i as resolveCapletsMode, l as capabilityDescription, n as mcpUrlForBase, o as CapletsEngine } from "./options-j9p3L3r1.js";
1
+ import { Q as ToolListChangedNotificationSchema, _ as StreamableHTTPClientTransport, a as resolveCapletsServer, b as Client, bt as parseConfig, i as resolveCapletsMode, jt as CapletsError, l as capabilityDescription, n as mcpUrlForBase, o as CapletsEngine, vt as loadLocalOverlayConfigWithSources } from "./options-BlNyqF_E.js";
2
2
  import { a as generatedToolInputJsonSchemaForCaplet, i as generatedToolInputJsonSchema, l as operations, o as generatedToolInputSchema } from "./generated-tool-input-schema-BYoyY-l-.js";
3
3
  //#region src/native/options.ts
4
4
  const DEFAULT_POLL_INTERVAL_MS = 3e4;
@@ -155,10 +155,10 @@ var RemoteNativeCapletsService = class {
155
155
  await this.reloadFromClient();
156
156
  return !this.closed;
157
157
  } catch (retryError) {
158
- this.warn(`Could not reload remote Caplets tools: ${errorMessage(retryError)}\n`);
158
+ this.warn(`Could not reload remote Caplets tools: ${errorMessage$2(retryError)}\n`);
159
159
  return false;
160
160
  }
161
- this.warn(`Could not reload remote Caplets tools: ${errorMessage(error)}\n`);
161
+ this.warn(`Could not reload remote Caplets tools: ${errorMessage$2(error)}\n`);
162
162
  return false;
163
163
  }
164
164
  }
@@ -213,11 +213,7 @@ var RemoteNativeCapletsService = class {
213
213
  for (const listener of this.listeners) listener(tools);
214
214
  }
215
215
  warn(message) {
216
- if (this.options.writeErr) {
217
- this.options.writeErr(message);
218
- return;
219
- }
220
- process.stderr.write(message);
216
+ this.options.writeErr?.(message);
221
217
  }
222
218
  };
223
219
  function remoteToolToNativeTool(tool) {
@@ -248,14 +244,14 @@ function operationNamesFromSchema(schema) {
248
244
  function isPlainObject(value) {
249
245
  return value !== null && typeof value === "object" && !Array.isArray(value);
250
246
  }
251
- function errorMessage(error) {
247
+ function errorMessage$2(error) {
252
248
  return error instanceof Error ? error.message : String(error);
253
249
  }
254
250
  function remoteAuthError() {
255
251
  return new CapletsError("AUTH_FAILED", "Remote Caplets authentication failed; check CAPLETS_SERVER_USER and CAPLETS_SERVER_PASSWORD.");
256
252
  }
257
253
  function isSessionFailure(error) {
258
- const message = errorMessage(error).toLowerCase();
254
+ const message = errorMessage$2(error).toLowerCase();
259
255
  return /session|transport|connection|connect|closed|invalid/u.test(message);
260
256
  }
261
257
  function isAuthFailure(error) {
@@ -264,24 +260,42 @@ function isAuthFailure(error) {
264
260
  const statusCode = typeof candidate?.statusCode === "number" ? candidate.statusCode : void 0;
265
261
  const code = typeof candidate?.code === "number" ? candidate.code : void 0;
266
262
  if (status === 401 || status === 403 || statusCode === 401 || statusCode === 403 || code === 401 || code === 403) return true;
267
- return /\b(401|403|unauthorized|forbidden)\b/iu.test(errorMessage(error));
263
+ return /\b(401|403|unauthorized|forbidden)\b/iu.test(errorMessage$2(error));
268
264
  }
269
265
  //#endregion
270
266
  //#region src/native/service.ts
271
267
  function createNativeCapletsService(options = {}) {
272
268
  const resolved = resolveNativeCapletsServiceOptions(options);
273
- if (resolved.mode === "remote") return new RemoteNativeCapletsService({
274
- client: (options.remoteClientFactory ?? createSdkRemoteCapletsClient)(resolved.remote),
275
- clientFactory: () => (options.remoteClientFactory ?? createSdkRemoteCapletsClient)(resolved.remote),
276
- pollIntervalMs: resolved.remote.pollIntervalMs,
277
- ...options.writeErr ? { writeErr: options.writeErr } : {}
278
- });
269
+ if (resolved.mode === "remote") {
270
+ const localOptions = {
271
+ ...options,
272
+ mode: "local",
273
+ configLoader: createLocalOverlayConfigLoader(options)
274
+ };
275
+ const local = (options.localServiceFactory ?? createDefaultNativeCapletsService)(localOptions);
276
+ try {
277
+ return new CompositeNativeCapletsService(new RemoteNativeCapletsService({
278
+ client: (options.remoteClientFactory ?? createSdkRemoteCapletsClient)(resolved.remote),
279
+ clientFactory: () => (options.remoteClientFactory ?? createSdkRemoteCapletsClient)(resolved.remote),
280
+ pollIntervalMs: resolved.remote.pollIntervalMs,
281
+ ...options.writeErr ? { writeErr: options.writeErr } : {}
282
+ }), local, options);
283
+ } catch (error) {
284
+ local.close().catch((closeError) => {
285
+ writeErr(options, `Could not close local overlay Caplets service: ${errorMessage$1(closeError)}\n`);
286
+ });
287
+ throw error;
288
+ }
289
+ }
279
290
  return new DefaultNativeCapletsService(options);
280
291
  }
281
292
  var DefaultNativeCapletsService = class {
282
293
  engine;
283
294
  constructor(options) {
284
- this.engine = new CapletsEngine(options);
295
+ this.engine = new CapletsEngine({
296
+ ...options,
297
+ writeErr: options.writeErr ?? (() => void 0)
298
+ });
285
299
  }
286
300
  listTools() {
287
301
  return this.engine.enabledServers().map((caplet) => {
@@ -311,9 +325,111 @@ var DefaultNativeCapletsService = class {
311
325
  await this.engine.close();
312
326
  }
313
327
  };
328
+ function createDefaultNativeCapletsService(options) {
329
+ return new DefaultNativeCapletsService(options);
330
+ }
331
+ var CompositeNativeCapletsService = class {
332
+ remote;
333
+ local;
334
+ options;
335
+ listeners = /* @__PURE__ */ new Set();
336
+ unsubscribers;
337
+ tools = [];
338
+ closed = false;
339
+ batchingReload = false;
340
+ constructor(remote, local, options) {
341
+ this.remote = remote;
342
+ this.local = local;
343
+ this.options = options;
344
+ this.unsubscribers = [this.remote.onToolsChanged(() => this.updateMergedTools()), this.local.onToolsChanged(() => this.updateMergedTools())];
345
+ this.tools = this.mergeTools();
346
+ }
347
+ listTools() {
348
+ return [...this.tools];
349
+ }
350
+ async execute(capletId, request) {
351
+ if (this.local.listTools().some((tool) => tool.caplet === capletId)) return await this.local.execute(capletId, request);
352
+ return await this.remote.execute(capletId, request);
353
+ }
354
+ async reload() {
355
+ if (this.closed) return false;
356
+ this.batchingReload = true;
357
+ const remoteReloaded = await this.reloadChild(this.remote, "remote");
358
+ const localReloaded = await this.reloadChild(this.local, "local overlay");
359
+ this.batchingReload = false;
360
+ if (remoteReloaded === void 0 || localReloaded === void 0) return false;
361
+ this.updateMergedTools();
362
+ return remoteReloaded || localReloaded;
363
+ }
364
+ onToolsChanged(listener) {
365
+ this.listeners.add(listener);
366
+ return () => this.listeners.delete(listener);
367
+ }
368
+ async close() {
369
+ if (this.closed) return;
370
+ this.closed = true;
371
+ for (const unsubscribe of this.unsubscribers.splice(0)) unsubscribe();
372
+ this.listeners.clear();
373
+ await Promise.all([this.remote.close(), this.local.close()]);
374
+ }
375
+ updateMergedTools() {
376
+ if (this.closed || this.batchingReload) return;
377
+ const tools = this.mergeTools();
378
+ if (JSON.stringify(tools) === JSON.stringify(this.tools)) return;
379
+ this.tools = tools;
380
+ for (const listener of this.listeners) try {
381
+ listener(this.listTools());
382
+ } catch (error) {
383
+ writeErr(this.options, `Caplets tools-changed listener failed: ${errorMessage$1(error)}\n`);
384
+ }
385
+ }
386
+ mergeTools() {
387
+ const localTools = this.local.listTools();
388
+ const localIds = new Set(localTools.map((tool) => tool.caplet));
389
+ return [...this.remote.listTools().filter((tool) => !localIds.has(tool.caplet)), ...localTools];
390
+ }
391
+ async reloadChild(service, label) {
392
+ try {
393
+ return await service.reload();
394
+ } catch (error) {
395
+ writeErr(this.options, `Could not reload composite Caplets tools from ${label}: ${errorMessage$1(error)}\n`);
396
+ return;
397
+ }
398
+ }
399
+ };
400
+ function createLocalOverlayConfigLoader(options) {
401
+ let hasLoaded = false;
402
+ let previousWarnings = /* @__PURE__ */ new Set();
403
+ return (configPath, projectConfigPath) => {
404
+ let result;
405
+ try {
406
+ result = loadLocalOverlayConfigWithSources(configPath, projectConfigPath);
407
+ } catch (error) {
408
+ writeErr(options, `Caplets local overlay warning: Could not load local overlay config: ${errorMessage$1(error)}\n`);
409
+ if (hasLoaded) throw new CapletsError("CONFIG_INVALID", "Caplets local overlay reload failed; keeping last known-good config.", error);
410
+ hasLoaded = true;
411
+ return parseConfig({});
412
+ }
413
+ for (const warning of result.warnings) writeErr(options, `Caplets local overlay warning${typeof warning.path === "string" ? ` at ${warning.path}` : ""}: ${warning.message}\n`);
414
+ const warnings = new Set(result.warnings.map(warningKey));
415
+ if (hasLoaded && [...warnings].some((warning) => !previousWarnings.has(warning))) throw new CapletsError("CONFIG_INVALID", "Caplets local overlay reload produced new warnings; keeping last known-good config.");
416
+ previousWarnings = warnings;
417
+ hasLoaded = true;
418
+ return result.config;
419
+ };
420
+ }
421
+ function warningKey(warning) {
422
+ return `${warning.kind}\0${warning.path}\0${warning.message}`;
423
+ }
424
+ function writeErr(options, message) {
425
+ options.writeErr?.(message);
426
+ }
427
+ function errorMessage$1(error) {
428
+ return error instanceof Error ? error.message : String(error);
429
+ }
314
430
  //#endregion
315
431
  //#region src/native/process-cleanup.ts
316
- function registerNativeCapletsProcessCleanup(service) {
432
+ function registerNativeCapletsProcessCleanup(service, options = {}) {
317
433
  let closed = false;
318
434
  const close = async () => {
319
435
  if (closed) return;
@@ -321,7 +437,7 @@ function registerNativeCapletsProcessCleanup(service) {
321
437
  try {
322
438
  await service.close();
323
439
  } catch (error) {
324
- console.error("Failed to close Caplets service:", error);
440
+ options.writeErr?.(`Failed to close Caplets service: ${errorMessage(error)}\n`);
325
441
  process.exitCode = 1;
326
442
  }
327
443
  };
@@ -337,5 +453,8 @@ function registerNativeCapletsProcessCleanup(service) {
337
453
  process.once("SIGINT", closeAndExit);
338
454
  process.once("SIGTERM", closeAndExit);
339
455
  }
456
+ function errorMessage(error) {
457
+ return error instanceof Error ? error.message : String(error);
458
+ }
340
459
  //#endregion
341
460
  export { RemoteNativeCapletsService, createNativeCapletsService, createSdkRemoteCapletsClient, generatedToolInputJsonSchema, generatedToolInputSchema, nativeCapletPromptGuidance, nativeCapletToolDescription, nativeCapletToolName, nativeCapletsSystemGuidance, registerNativeCapletsProcessCleanup, resolveNativeCapletsServiceOptions };
@@ -8380,6 +8380,82 @@ function loadCapletFilesWithPaths(root) {
8380
8380
  paths
8381
8381
  } : void 0;
8382
8382
  }
8383
+ function loadCapletFilesWithPathsBestEffort(root) {
8384
+ if (!existsSync(root)) return;
8385
+ const warnings = [];
8386
+ return buildCapletFileLoadResult(root, discoverCapletFilesBestEffort(root, warnings), warnings);
8387
+ }
8388
+ function buildCapletFileLoadResult(root, candidates, warnings) {
8389
+ const servers = {};
8390
+ const openapiEndpoints = {};
8391
+ const graphqlEndpoints = {};
8392
+ const httpApis = {};
8393
+ const cliTools = {};
8394
+ const capletSets = {};
8395
+ const paths = {};
8396
+ function hasId(id) {
8397
+ return Boolean(servers[id] || openapiEndpoints[id] || graphqlEndpoints[id] || httpApis[id] || cliTools[id] || capletSets[id]);
8398
+ }
8399
+ for (const candidate of candidates) {
8400
+ if (hasId(candidate.id)) {
8401
+ const message = `Duplicate Caplet ID ${candidate.id} under ${root}`;
8402
+ if (!warnings) throw new CapletsError("CONFIG_INVALID", message);
8403
+ warnings.push({
8404
+ path: candidate.path,
8405
+ message: `${message}; skipping duplicate at ${candidate.path}`
8406
+ });
8407
+ continue;
8408
+ }
8409
+ let config;
8410
+ try {
8411
+ config = readCapletFile(candidate.path);
8412
+ } catch (error) {
8413
+ if (!warnings) throw error;
8414
+ warnings.push({
8415
+ path: candidate.path,
8416
+ message: `Skipping invalid Caplet file at ${candidate.path}: ${errorMessage$1(error)}`
8417
+ });
8418
+ continue;
8419
+ }
8420
+ paths[candidate.id] = candidate.path;
8421
+ if (isPlainObject$5(config) && config.backend === "openapi") {
8422
+ const { backend: _backend, ...endpoint } = config;
8423
+ openapiEndpoints[candidate.id] = endpoint;
8424
+ } else if (isPlainObject$5(config) && config.backend === "graphql") {
8425
+ const { backend: _backend, ...endpoint } = config;
8426
+ graphqlEndpoints[candidate.id] = endpoint;
8427
+ } else if (isPlainObject$5(config) && config.backend === "http") {
8428
+ const { backend: _backend, ...endpoint } = config;
8429
+ httpApis[candidate.id] = endpoint;
8430
+ } else if (isPlainObject$5(config) && config.backend === "cli") {
8431
+ const { backend: _backend, ...endpoint } = config;
8432
+ cliTools[candidate.id] = endpoint;
8433
+ } else if (isPlainObject$5(config) && config.backend === "caplets") {
8434
+ const { backend: _backend, ...endpoint } = config;
8435
+ capletSets[candidate.id] = endpoint;
8436
+ } else servers[candidate.id] = config;
8437
+ }
8438
+ const hasServers = Object.keys(servers).length > 0;
8439
+ const hasOpenApi = Object.keys(openapiEndpoints).length > 0;
8440
+ const hasGraphQl = Object.keys(graphqlEndpoints).length > 0;
8441
+ const hasHttpApis = Object.keys(httpApis).length > 0;
8442
+ const hasCliTools = Object.keys(cliTools).length > 0;
8443
+ const hasCapletSets = Object.keys(capletSets).length > 0;
8444
+ const config = {
8445
+ ...hasServers ? { mcpServers: servers } : {},
8446
+ ...hasOpenApi ? { openapiEndpoints } : {},
8447
+ ...hasGraphQl ? { graphqlEndpoints } : {},
8448
+ ...hasHttpApis ? { httpApis } : {},
8449
+ ...hasCliTools ? { cliTools } : {},
8450
+ ...hasCapletSets ? { capletSets } : {}
8451
+ };
8452
+ if (!(Object.keys(config).length > 0) && warnings?.length === 0) return;
8453
+ return {
8454
+ config,
8455
+ paths,
8456
+ warnings: warnings ?? []
8457
+ };
8458
+ }
8383
8459
  function discoverCapletFiles(root) {
8384
8460
  const entries = readdirSync(root, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
8385
8461
  const candidates = [];
@@ -8404,6 +8480,82 @@ function discoverCapletFiles(root) {
8404
8480
  }
8405
8481
  return candidates;
8406
8482
  }
8483
+ function discoverCapletFilesBestEffort(root, warnings) {
8484
+ const entries = readdirSync(root, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
8485
+ const byId = /* @__PURE__ */ new Map();
8486
+ const duplicateIds = /* @__PURE__ */ new Set();
8487
+ function addCandidate(id, path, isDirectoryCaplet) {
8488
+ try {
8489
+ validateCapletId(id, path);
8490
+ } catch (error) {
8491
+ warnings.push({
8492
+ path,
8493
+ message: `Skipping invalid Caplet file at ${path}: ${errorMessage$1(error)}`
8494
+ });
8495
+ return;
8496
+ }
8497
+ if (duplicateIds.has(id)) {
8498
+ warnings.push({
8499
+ path,
8500
+ message: `Duplicate Caplet ID ${id} under ${root}; skipping duplicate at ${path}`
8501
+ });
8502
+ return;
8503
+ }
8504
+ const existing = byId.get(id);
8505
+ if (!existing) {
8506
+ byId.set(id, {
8507
+ id,
8508
+ path,
8509
+ isDirectoryCaplet
8510
+ });
8511
+ return;
8512
+ }
8513
+ if (isDirectoryCaplet && !existing.isDirectoryCaplet) {
8514
+ warnings.push({
8515
+ path: existing.path,
8516
+ message: `Caplet file at ${existing.path} was shadowed by ${path}`
8517
+ });
8518
+ byId.set(id, {
8519
+ id,
8520
+ path,
8521
+ isDirectoryCaplet
8522
+ });
8523
+ return;
8524
+ }
8525
+ if (!isDirectoryCaplet && existing.isDirectoryCaplet) {
8526
+ warnings.push({
8527
+ path,
8528
+ message: `Caplet file at ${path} was shadowed by ${existing.path}`
8529
+ });
8530
+ return;
8531
+ }
8532
+ warnings.push({
8533
+ path,
8534
+ message: `Duplicate Caplet ID ${id} under ${root}; skipping ${existing.path} and ${path}`
8535
+ });
8536
+ byId.delete(id);
8537
+ duplicateIds.add(id);
8538
+ }
8539
+ for (const entry of entries) {
8540
+ if (entry.name === "auth" || entry.name === "config.json") continue;
8541
+ const path = join(root, entry.name);
8542
+ if (entry.isFile() && extname(entry.name).toLowerCase() === ".md") {
8543
+ addCandidate(basename(entry.name, extname(entry.name)), path, false);
8544
+ continue;
8545
+ }
8546
+ if (entry.isDirectory()) {
8547
+ const capletPath = join(path, "CAPLET.md");
8548
+ if (existsSync(capletPath) && statSync(capletPath).isFile()) addCandidate(entry.name, capletPath, true);
8549
+ }
8550
+ }
8551
+ return Array.from(byId.values()).map(({ id, path }) => ({
8552
+ id,
8553
+ path
8554
+ }));
8555
+ }
8556
+ function errorMessage$1(error) {
8557
+ return error instanceof Error ? error.message : String(error);
8558
+ }
8407
8559
  function readCapletFile(path) {
8408
8560
  if (statSync(path).size > MAX_CAPLET_FILE_BYTES) throw new CapletsError("CONFIG_INVALID", `Caplet file at ${path} exceeds the ${MAX_CAPLET_FILE_BYTES} byte limit`);
8409
8561
  const { frontmatter, body } = parseFrontmatter(readFileSync(path, "utf8"), path);
@@ -9084,35 +9236,78 @@ function loadConfigWithSources(path = resolveConfigPath(), projectPath = resolve
9084
9236
  const projectConfig = hasProjectConfig ? rejectProjectConfigExecutableBackendMaps(readPublicConfigInput(projectPath), projectPath) : void 0;
9085
9237
  const projectCapletsRoot = resolveProjectCapletsRootForConfigPath(projectPath);
9086
9238
  const projectCaplets = projectCapletsRoot ? loadCapletFilesWithPaths(projectCapletsRoot) : void 0;
9087
- if (!hasUserConfig && !hasProjectConfig && !userCaplets && !projectCaplets) throw new CapletsError("CONFIG_NOT_FOUND", `Caplets config not found at ${path} or ${projectPath}`);
9088
- try {
9089
- const { input, sources, shadows } = mergeConfigInputsWithSources({
9239
+ return buildConfigWithSources([
9240
+ {
9090
9241
  input: userConfig,
9091
9242
  source: {
9092
9243
  kind: "global-config",
9093
9244
  path
9094
9245
  }
9095
- }, userCaplets ? {
9246
+ },
9247
+ userCaplets ? {
9096
9248
  input: userCaplets.config,
9097
9249
  source: {
9098
9250
  kind: "global-file",
9099
9251
  path: userCaplets.paths
9100
9252
  }
9101
- } : void 0, {
9253
+ } : void 0,
9254
+ {
9102
9255
  input: projectConfig,
9103
9256
  source: {
9104
9257
  kind: "project-config",
9105
9258
  path: projectPath
9106
9259
  }
9107
- }, projectCaplets ? {
9260
+ },
9261
+ projectCaplets ? {
9108
9262
  input: projectCaplets.config,
9109
9263
  source: {
9110
9264
  kind: "project-file",
9111
9265
  path: projectCaplets.paths
9112
9266
  }
9113
- } : void 0);
9267
+ } : void 0
9268
+ ], `Caplets config not found at ${path} or ${projectPath}`, "Caplets config must define at least one MCP server, OpenAPI endpoint, GraphQL endpoint, HTTP API, CLI tools backend, or Caplet set");
9269
+ }
9270
+ function loadGlobalConfig(path = resolveConfigPath()) {
9271
+ const userConfig = existsSync(path) ? readPublicConfigInput(path) : void 0;
9272
+ const userCaplets = loadCapletFilesWithPaths(resolveCapletsRoot(path));
9273
+ return buildConfigWithSources([{
9274
+ input: userConfig,
9275
+ source: {
9276
+ kind: "global-config",
9277
+ path
9278
+ }
9279
+ }, userCaplets ? {
9280
+ input: userCaplets.config,
9281
+ source: {
9282
+ kind: "global-file",
9283
+ path: userCaplets.paths
9284
+ }
9285
+ } : void 0], `Caplets user config not found at ${path}`, void 0).config;
9286
+ }
9287
+ function loadProjectConfig(projectPath = resolveProjectConfigPath()) {
9288
+ const projectConfig = existsSync(projectPath) ? rejectProjectConfigExecutableBackendMaps(readPublicConfigInput(projectPath), projectPath) : void 0;
9289
+ const projectCapletsRoot = resolveProjectCapletsRootForConfigPath(projectPath);
9290
+ const projectCaplets = projectCapletsRoot ? loadCapletFilesWithPaths(projectCapletsRoot) : void 0;
9291
+ return buildConfigWithSources([{
9292
+ input: projectConfig,
9293
+ source: {
9294
+ kind: "project-config",
9295
+ path: projectPath
9296
+ }
9297
+ }, projectCaplets ? {
9298
+ input: projectCaplets.config,
9299
+ source: {
9300
+ kind: "project-file",
9301
+ path: projectCaplets.paths
9302
+ }
9303
+ } : void 0], `Caplets project config not found at ${projectPath}`, void 0).config;
9304
+ }
9305
+ function buildConfigWithSources(inputs, notFoundMessage, emptyMessage) {
9306
+ if (!inputs.some((entry) => entry?.input !== void 0)) throw new CapletsError("CONFIG_NOT_FOUND", notFoundMessage);
9307
+ try {
9308
+ const { input, sources, shadows } = mergeConfigInputsWithSources(...inputs);
9114
9309
  const config = parseConfig(input);
9115
- 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", "Caplets config must define at least one MCP server, OpenAPI endpoint, GraphQL endpoint, HTTP API, CLI tools backend, or Caplet set");
9310
+ if (emptyMessage && 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", emptyMessage);
9116
9311
  return {
9117
9312
  config,
9118
9313
  sources,
@@ -9123,6 +9318,74 @@ function loadConfigWithSources(path = resolveConfigPath(), projectPath = resolve
9123
9318
  throw new CapletsError("CONFIG_INVALID", "Caplets config is not valid JSON", redactSecrets(error));
9124
9319
  }
9125
9320
  }
9321
+ function loadLocalOverlayConfigWithSources(path = resolveConfigPath(), projectPath = resolveProjectConfigPath()) {
9322
+ const warnings = [];
9323
+ const userConfig = existsSync(path) ? readBestEffortConfigInput(path, "global-config", warnings) : void 0;
9324
+ const userCaplets = loadBestEffortCapletFiles(resolveCapletsRoot(path), "global-file", warnings);
9325
+ const projectConfig = existsSync(projectPath) ? readBestEffortConfigInput(projectPath, "project-config", warnings, (input) => rejectProjectConfigExecutableBackendMaps(input, projectPath)) : void 0;
9326
+ const projectCapletsRoot = resolveProjectCapletsRootForConfigPath(projectPath);
9327
+ const projectCaplets = projectCapletsRoot ? loadBestEffortCapletFiles(projectCapletsRoot, "project-file", warnings) : void 0;
9328
+ const { input, sources, shadows } = mergeConfigInputsWithSources({
9329
+ input: userConfig,
9330
+ source: {
9331
+ kind: "global-config",
9332
+ path
9333
+ }
9334
+ }, userCaplets ? {
9335
+ input: userCaplets.config,
9336
+ source: {
9337
+ kind: "global-file",
9338
+ path: userCaplets.paths
9339
+ }
9340
+ } : void 0, {
9341
+ input: projectConfig,
9342
+ source: {
9343
+ kind: "project-config",
9344
+ path: projectPath
9345
+ }
9346
+ }, projectCaplets ? {
9347
+ input: projectCaplets.config,
9348
+ source: {
9349
+ kind: "project-file",
9350
+ path: projectCaplets.paths
9351
+ }
9352
+ } : void 0);
9353
+ return {
9354
+ config: parseConfig(input),
9355
+ sources,
9356
+ shadows,
9357
+ warnings
9358
+ };
9359
+ }
9360
+ function readBestEffortConfigInput(path, kind, warnings, transform) {
9361
+ try {
9362
+ const input = readPublicConfigInput(path);
9363
+ return transform ? transform(input) : input;
9364
+ } catch (error) {
9365
+ warnings.push({
9366
+ kind,
9367
+ path,
9368
+ message: errorMessage(error)
9369
+ });
9370
+ return;
9371
+ }
9372
+ }
9373
+ function loadBestEffortCapletFiles(root, kind, warnings) {
9374
+ const result = loadCapletFilesWithPathsBestEffort(root);
9375
+ if (!result) return;
9376
+ for (const warning of result.warnings) warnings.push({
9377
+ kind,
9378
+ path: warning.path ?? root,
9379
+ message: warning.message
9380
+ });
9381
+ return {
9382
+ config: result.config,
9383
+ paths: result.paths
9384
+ };
9385
+ }
9386
+ function errorMessage(error) {
9387
+ return error instanceof Error ? error.message : String(error);
9388
+ }
9126
9389
  function loadIsolatedConfig(options) {
9127
9390
  if (!options.configPath && !options.capletsRoot) throw new CapletsError("CONFIG_INVALID", "Nested Caplet set must define at least one source: configPath or capletsRoot");
9128
9391
  const configInput = options.configPath ? readPublicConfigInput(options.configPath) : void 0;
@@ -53822,6 +54085,7 @@ var CapletsEngine = class {
53822
54085
  watchDebounceMs;
53823
54086
  watchEnabled;
53824
54087
  writeErr;
54088
+ configLoader;
53825
54089
  reloadListeners = /* @__PURE__ */ new Set();
53826
54090
  watchers = [];
53827
54091
  reloadTimer;
@@ -53834,7 +54098,8 @@ var CapletsEngine = class {
53834
54098
  configPath: resolveConfigPath(options.configPath),
53835
54099
  projectConfigPath: options.projectConfigPath ?? resolveProjectConfigPath()
53836
54100
  };
53837
- const config = loadConfig(this.paths.configPath, this.paths.projectConfigPath);
54101
+ this.configLoader = options.configLoader ?? loadConfig;
54102
+ const config = this.configLoader(this.paths.configPath, this.paths.projectConfigPath);
53838
54103
  this.registry = new ServerRegistry(config);
53839
54104
  this.downstream = new DownstreamManager(this.registry, selectAuthOptions(options.authDir));
53840
54105
  this.openapi = new OpenApiManager(this.registry, selectAuthOptions(options.authDir));
@@ -53889,7 +54154,7 @@ var CapletsEngine = class {
53889
54154
  }
53890
54155
  }
53891
54156
  async completeCliWords(words) {
53892
- const { completeCliWords } = await import("./completion-Cq1z7ci2.js").then((n) => n.r);
54157
+ const { completeCliWords } = await import("./completion-DRPTunQd.js").then((n) => n.r);
53893
54158
  return await completeCliWords(words, {
53894
54159
  config: this.registry.config,
53895
54160
  managers: {
@@ -53949,7 +54214,7 @@ var CapletsEngine = class {
53949
54214
  if (this.closed) return false;
53950
54215
  let nextConfig;
53951
54216
  try {
53952
- nextConfig = loadConfig(this.paths.configPath, this.paths.projectConfigPath);
54217
+ nextConfig = this.configLoader(this.paths.configPath, this.paths.projectConfigPath);
53953
54218
  } catch (error) {
53954
54219
  this.writeErr(`Caplets config reload failed; keeping last known-good config.\n`);
53955
54220
  this.writeErr(`${JSON.stringify(toSafeError(error, "CONFIG_INVALID"), null, 2)}\n`);
@@ -54229,4 +54494,4 @@ function hasEnv(value) {
54229
54494
  return value !== void 0 && value.trim() !== "";
54230
54495
  }
54231
54496
  //#endregion
54232
- export { assertCompleteRequestPrompt as $, CreateMessageResultSchema as A, toSafeError as At, JSONRPCMessageSchema as B, AjvJsonSchemaValidator as C, resolveProjectConfigPath as Ct, CallToolRequestSchema as D, CAPLETS_ERROR_CODES as Dt, toJsonSchemaCompat as E, SERVER_ID_PATTERN as Et, EmptyResultSchema as F, ListRootsResultSchema as G, ListPromptsRequestSchema as H, ErrorCode as I, McpError as J, ListToolsRequestSchema as K, GetPromptRequestSchema as L, CreateTaskResultSchema as M, __exportAll as Mt, DEFAULT_NEGOTIATED_PROTOCOL_VERSION as N, __require as Nt, CallToolResultSchema as O, CapletsError as Ot, ElicitResultSchema as P, __toESM as Pt, ToolListChangedNotificationSchema as Q, InitializeRequestSchema as R, assertToolsCallTaskCapability as S, resolveProjectCapletsRoot as St, mergeCapabilities as T, validateCapletFile as Tt, ListResourceTemplatesRequestSchema as U, LATEST_PROTOCOL_VERSION as V, ListResourcesRequestSchema as W, SUPPORTED_PROTOCOL_VERSIONS as X, ReadResourceRequestSchema as Y, SetLevelRequestSchema as Z, StreamableHTTPClientTransport as _, parseConfig as _t, resolveCapletsServer as a, getLiteralValue as at, Client as b, resolveCapletsRoot as bt, ServerRegistry as c, getSchemaDescription as ct, runOAuthFlow as d, normalizeObjectSchema as dt, assertCompleteRequestResourceTemplate as et, startGenericOAuthFlow as f, objectFromShape as ft, readTokenBundle as g, loadConfigWithSources as gt, isTokenBundleExpired as h, loadConfig as ht, resolveCapletsMode as i, isJSONRPCResultResponse as it, CreateMessageResultWithToolsSchema as j, __commonJSMin as jt, CompleteRequestSchema as k, redactSecrets as kt, capabilityDescription as l, isSchemaOptional as lt, deleteTokenBundle as m, safeParseAsync as mt, mcpUrlForBase as n, isJSONRPCErrorResponse as nt, CapletsEngine as o, getObjectShape as ot, startOAuthFlow as p, safeParse as pt, LoggingLevelSchema as q, parseServerBaseUrl as r, isJSONRPCRequest as rt, handleServerTool as s, getParseErrorMessage as st, controlUrlForBase as t, isInitializeRequest as tt, runGenericOAuthFlow as u, isZ4Schema as ut, ReadBuffer as v, DEFAULT_AUTH_DIR as vt, Protocol as w, discoverCapletFiles as wt, assertClientRequestTaskCapability as x, resolveConfigPath as xt, serializeMessage as y, DEFAULT_COMPLETION_CACHE_DIR as yt, InitializedNotificationSchema as z };
54497
+ export { assertCompleteRequestPrompt as $, CreateMessageResultSchema as A, CAPLETS_ERROR_CODES as At, JSONRPCMessageSchema as B, AjvJsonSchemaValidator as C, resolveCapletsRoot as Ct, CallToolRequestSchema as D, discoverCapletFiles as Dt, toJsonSchemaCompat as E, resolveProjectConfigPath as Et, EmptyResultSchema as F, __exportAll as Ft, ListRootsResultSchema as G, ListPromptsRequestSchema as H, ErrorCode as I, __require as It, McpError as J, ListToolsRequestSchema as K, GetPromptRequestSchema as L, __toESM as Lt, CreateTaskResultSchema as M, redactSecrets as Mt, DEFAULT_NEGOTIATED_PROTOCOL_VERSION as N, toSafeError as Nt, CallToolResultSchema as O, validateCapletFile as Ot, ElicitResultSchema as P, __commonJSMin as Pt, ToolListChangedNotificationSchema as Q, InitializeRequestSchema as R, assertToolsCallTaskCapability as S, DEFAULT_COMPLETION_CACHE_DIR as St, mergeCapabilities as T, resolveProjectCapletsRoot as Tt, ListResourceTemplatesRequestSchema as U, LATEST_PROTOCOL_VERSION as V, ListResourcesRequestSchema as W, SUPPORTED_PROTOCOL_VERSIONS as X, ReadResourceRequestSchema as Y, SetLevelRequestSchema as Z, StreamableHTTPClientTransport as _, loadGlobalConfig as _t, resolveCapletsServer as a, getLiteralValue as at, Client as b, parseConfig as bt, ServerRegistry as c, getSchemaDescription as ct, runOAuthFlow as d, normalizeObjectSchema as dt, assertCompleteRequestResourceTemplate as et, startGenericOAuthFlow as f, objectFromShape as ft, readTokenBundle as g, loadConfigWithSources as gt, isTokenBundleExpired as h, loadConfig as ht, resolveCapletsMode as i, isJSONRPCResultResponse as it, CreateMessageResultWithToolsSchema as j, CapletsError as jt, CompleteRequestSchema as k, SERVER_ID_PATTERN as kt, capabilityDescription as l, isSchemaOptional as lt, deleteTokenBundle as m, safeParseAsync as mt, mcpUrlForBase as n, isJSONRPCErrorResponse as nt, CapletsEngine as o, getObjectShape as ot, startOAuthFlow as p, safeParse as pt, LoggingLevelSchema as q, parseServerBaseUrl as r, isJSONRPCRequest as rt, handleServerTool as s, getParseErrorMessage as st, controlUrlForBase as t, isInitializeRequest as tt, runGenericOAuthFlow as u, isZ4Schema as ut, ReadBuffer as v, loadLocalOverlayConfigWithSources as vt, Protocol as w, resolveConfigPath as wt, assertClientRequestTaskCapability as x, DEFAULT_AUTH_DIR as xt, serializeMessage as y, loadProjectConfig as yt, InitializedNotificationSchema as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caplets/core",
3
- "version": "0.18.3",
3
+ "version": "0.18.5",
4
4
  "description": "Core runtime library for Caplets progressive disclosure gateways.",
5
5
  "keywords": [
6
6
  "caplets",
@@ -50,7 +50,7 @@
50
50
  "@modelcontextprotocol/sdk": "^1.29.0",
51
51
  "commander": "^14.0.3",
52
52
  "graphql": "^16.14.0",
53
- "hono": "^4.12.21",
53
+ "hono": "^4.12.22",
54
54
  "vfile": "^6.0.3",
55
55
  "vfile-matter": "^5.0.1",
56
56
  "yaml": "^2.9.0",
@@ -58,7 +58,7 @@
58
58
  },
59
59
  "devDependencies": {
60
60
  "@types/node": "^25.9.1",
61
- "@typescript/native-preview": "7.0.0-dev.20260519.1",
61
+ "@typescript/native-preview": "7.0.0-dev.20260523.1",
62
62
  "rolldown": "^1.0.2",
63
63
  "typescript": "^6.0.3",
64
64
  "vitest": "^4.1.7"
@@ -67,8 +67,9 @@
67
67
  "node": ">=22"
68
68
  },
69
69
  "scripts": {
70
- "build": "rm -rf dist && rolldown -c && tsc -p tsconfig.build.json",
71
- "build:watch": "rolldown -c --watch",
70
+ "clean": "rm -rf dist",
71
+ "build": "pnpm run clean && rolldown -c && tsc -p tsconfig.build.json",
72
+ "build:watch": "pnpm run clean && rolldown -c --watch",
72
73
  "typecheck": "tsgo --noEmit",
73
74
  "test": "vitest run"
74
75
  }