caplets 0.17.3 → 0.17.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.
Files changed (3) hide show
  1. package/README.md +35 -13
  2. package/dist/index.js +662 -129
  3. package/package.json +6 -5
package/README.md CHANGED
@@ -219,6 +219,12 @@ opencode
219
219
 
220
220
  For MCP-backed Codex or Claude Code configs, point the agent's MCP server entry at the derived `/mcp` URL using that agent's supported HTTP MCP configuration. If Basic Auth is needed, use the agent's secure secret or environment interpolation mechanism rather than hardcoding credentials.
221
221
 
222
+ In `CAPLETS_MODE=remote`, read and execute commands use a merged Caplets view. Remote server rows load first, user-global local Caplets overlay them, and project-local Caplets have the highest priority. A local Caplet with the same ID shadows the remote one, runs locally, and prints a warning such as `Warning: project Caplet shared shadows remote Caplet`; remote-only IDs continue through remote control.
223
+
224
+ Local overlays in remote mode are best-effort. Invalid local config sources warn and are ignored while remaining valid layers still load; invalid Caplet files warn and are skipped individually. For sibling file layouts, `foo/CAPLET.md` wins over `foo.md` and warns that `foo.md` was shadowed; if the winning `CAPLET.md` is invalid, that local ID is skipped instead of falling back to `foo.md`.
225
+
226
+ Mutating commands do not automatically write to the server just because remote mode is active. `caplets init`, `caplets add ...`, and `caplets install ...` write project-local by default, `--project` is the explicit project-local target, `--global` writes user-global local files, and `--remote` sends the mutation to the remote Caplets server. Target flags are mutually exclusive.
227
+
222
228
  ## Convert Existing Tooling
223
229
 
224
230
  Caplets is designed to convert what you already use into agent-friendly capability domains.
@@ -299,7 +305,7 @@ the agent chooses that server and asks to search, list, inspect, or call them.
299
305
 
300
306
  ## Capabilities
301
307
 
302
- - Reads downstream MCP server definitions, native OpenAPI endpoint definitions, native GraphQL endpoint definitions, explicit HTTP API action definitions, and curated CLI tool definitions from the user config file.
308
+ - Reads downstream MCP server definitions, native OpenAPI endpoint definitions, native GraphQL endpoint definitions, explicit HTTP API action definitions, and curated CLI tool definitions from user and project config sources.
303
309
  - Registers one generated MCP tool for each enabled MCP server, OpenAPI endpoint, GraphQL endpoint, HTTP API, or CLI tools backend.
304
310
  - Uses the configured server ID as the generated tool name.
305
311
  - Uses the configured `name` and `description` as the capability card shown to agents.
@@ -316,12 +322,20 @@ the agent chooses that server and asks to search, list, inspect, or call them.
316
322
 
317
323
  ## Configure
318
324
 
319
- Create a starter user config at `${XDG_CONFIG_HOME:-~/.config}/caplets/config.json` on Unix-like platforms or `%APPDATA%\caplets\config.json` on Windows:
325
+ Create a starter project config at `./.caplets/config.json`:
320
326
 
321
327
  ```sh
322
328
  caplets init
323
329
  ```
324
330
 
331
+ To create a starter user config instead, pass `--global`. The user config path is
332
+ `${XDG_CONFIG_HOME:-~/.config}/caplets/config.json` on Unix-like platforms and
333
+ `%APPDATA%\caplets\config.json` on Windows:
334
+
335
+ ```sh
336
+ caplets init --global
337
+ ```
338
+
325
339
  The generated config includes a disabled example server. Replace it with the MCP servers
326
340
  you want Caplets to expose:
327
341
 
@@ -406,10 +420,12 @@ you want Caplets to expose:
406
420
  }
407
421
  ```
408
422
 
409
- The default config path is `${XDG_CONFIG_HOME:-~/.config}/caplets/config.json` on Unix-like platforms and `%APPDATA%\caplets\config.json` on Windows. It can be overridden with `CAPLETS_CONFIG`:
423
+ The user config path can be overridden with `CAPLETS_CONFIG`; the project config path can be
424
+ overridden with `CAPLETS_PROJECT_CONFIG`:
410
425
 
411
426
  ```sh
412
- CAPLETS_CONFIG=/path/to/config.json caplets init
427
+ CAPLETS_PROJECT_CONFIG=/path/to/project/.caplets/config.json caplets init
428
+ CAPLETS_CONFIG=/path/to/user/config.json caplets init --global
413
429
  CAPLETS_CONFIG=/path/to/config.json caplets serve
414
430
  ```
415
431
 
@@ -422,10 +438,9 @@ caplets config paths
422
438
  caplets config paths --json
423
439
  ```
424
440
 
425
- Caplets validates this file at startup and hot reloads config changes while `caplets serve`
441
+ Caplets validates config files at startup and hot reloads config changes while `caplets serve`
426
442
  is running. Invalid edits are ignored until fixed, so the MCP server keeps serving the last
427
- known-good config instead of dropping every tool because of a transient JSON or validation
428
- error.
443
+ known-good config instead of dropping every tool because of a transient JSON or validation error.
429
444
 
430
445
  The optional `$schema` field points editors at the generated JSON Schema in
431
446
  [`schemas/caplets-config.schema.json`](schemas/caplets-config.schema.json). CI verifies that
@@ -591,12 +606,14 @@ That means a project-local Caplet can intentionally replace a user-level Caplet
591
606
  Use `caplets list` to see each Caplet's winning source; when a project Caplet shadows a user-level
592
607
  Caplet, the list output includes a warning naming the shadowed path.
593
608
 
594
- `caplets init` refuses to overwrite an existing config. To intentionally replace the file:
609
+ `caplets init` refuses to overwrite an existing project config. To intentionally replace the file:
595
610
 
596
611
  ```sh
597
612
  caplets init --force
598
613
  ```
599
614
 
615
+ Use `caplets init --global --force` to replace the user config instead.
616
+
600
617
  ### Caplet IDs
601
618
 
602
619
  Each key under `mcpServers`, `openapiEndpoints`, `graphqlEndpoints`, `httpApis`, `cliTools`, or `capletSets` is the
@@ -914,11 +931,16 @@ caplets auth login <server> --no-open
914
931
  In local mode, OAuth/OIDC tokens are stored under
915
932
  `${XDG_STATE_HOME:-~/.local/state}/caplets/auth/<server>.json` on Unix-like platforms and
916
933
  `%LOCALAPPDATA%\caplets\auth\<server>.json` on Windows. Token files use owner-only file
917
- permissions where the platform supports them. In `CAPLETS_MODE=remote`, `caplets auth list`,
918
- `caplets auth login <server>`, and `caplets auth logout <server>` operate on the configured Caplets
919
- server instead. Downstream OAuth/OIDC credentials are stored server-side and are not returned to the
920
- local client. Caplets supports well-known OAuth/OIDC discovery and dynamic client registration when
921
- advertised. When a token expires, run `caplets auth login <server>` again.
934
+ permissions where the platform supports them. In `CAPLETS_MODE=remote`, `caplets auth list` shows
935
+ local and remote auth targets with a `source` field. `caplets auth login <server>` and
936
+ `caplets auth logout <server>` use the matching scope when it is unambiguous; if the same ID exists
937
+ in multiple scopes, pass `--project`, `--global`, or `--remote`. Remote OAuth/OIDC credentials are
938
+ stored server-side and are not returned to the local client. Caplets supports well-known OAuth/OIDC
939
+ discovery and dynamic client registration when advertised. When a token expires, run
940
+ `caplets auth login <server>` again.
941
+
942
+ Auth target flags are mutually exclusive. `caplets auth list --project`,
943
+ `caplets auth list --global`, and `caplets auth list --remote` filter the listing to one scope.
922
944
 
923
945
  To inspect or remove stored OAuth credentials:
924
946
 
package/dist/index.js CHANGED
@@ -4709,7 +4709,7 @@ function generatedToolInputJsonSchema() {
4709
4709
  return generatedToolInputJsonSchemaForCaplet({ backend: "tool" });
4710
4710
  }
4711
4711
  //#endregion
4712
- //#region ../core/dist/options-j9p3L3r1.js
4712
+ //#region ../core/dist/options-BlNyqF_E.js
4713
4713
  var __create = Object.create;
4714
4714
  var __defProp = Object.defineProperty;
4715
4715
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -12911,6 +12911,82 @@ function loadCapletFilesWithPaths(root) {
12911
12911
  paths
12912
12912
  } : void 0;
12913
12913
  }
12914
+ function loadCapletFilesWithPathsBestEffort(root) {
12915
+ if (!existsSync(root)) return;
12916
+ const warnings = [];
12917
+ return buildCapletFileLoadResult(root, discoverCapletFilesBestEffort(root, warnings), warnings);
12918
+ }
12919
+ function buildCapletFileLoadResult(root, candidates, warnings) {
12920
+ const servers = {};
12921
+ const openapiEndpoints = {};
12922
+ const graphqlEndpoints = {};
12923
+ const httpApis = {};
12924
+ const cliTools = {};
12925
+ const capletSets = {};
12926
+ const paths = {};
12927
+ function hasId(id) {
12928
+ return Boolean(servers[id] || openapiEndpoints[id] || graphqlEndpoints[id] || httpApis[id] || cliTools[id] || capletSets[id]);
12929
+ }
12930
+ for (const candidate of candidates) {
12931
+ if (hasId(candidate.id)) {
12932
+ const message = `Duplicate Caplet ID ${candidate.id} under ${root}`;
12933
+ if (!warnings) throw new CapletsError("CONFIG_INVALID", message);
12934
+ warnings.push({
12935
+ path: candidate.path,
12936
+ message: `${message}; skipping duplicate at ${candidate.path}`
12937
+ });
12938
+ continue;
12939
+ }
12940
+ let config;
12941
+ try {
12942
+ config = readCapletFile(candidate.path);
12943
+ } catch (error) {
12944
+ if (!warnings) throw error;
12945
+ warnings.push({
12946
+ path: candidate.path,
12947
+ message: `Skipping invalid Caplet file at ${candidate.path}: ${errorMessage$1(error)}`
12948
+ });
12949
+ continue;
12950
+ }
12951
+ paths[candidate.id] = candidate.path;
12952
+ if (isPlainObject$5(config) && config.backend === "openapi") {
12953
+ const { backend: _backend, ...endpoint } = config;
12954
+ openapiEndpoints[candidate.id] = endpoint;
12955
+ } else if (isPlainObject$5(config) && config.backend === "graphql") {
12956
+ const { backend: _backend, ...endpoint } = config;
12957
+ graphqlEndpoints[candidate.id] = endpoint;
12958
+ } else if (isPlainObject$5(config) && config.backend === "http") {
12959
+ const { backend: _backend, ...endpoint } = config;
12960
+ httpApis[candidate.id] = endpoint;
12961
+ } else if (isPlainObject$5(config) && config.backend === "cli") {
12962
+ const { backend: _backend, ...endpoint } = config;
12963
+ cliTools[candidate.id] = endpoint;
12964
+ } else if (isPlainObject$5(config) && config.backend === "caplets") {
12965
+ const { backend: _backend, ...endpoint } = config;
12966
+ capletSets[candidate.id] = endpoint;
12967
+ } else servers[candidate.id] = config;
12968
+ }
12969
+ const hasServers = Object.keys(servers).length > 0;
12970
+ const hasOpenApi = Object.keys(openapiEndpoints).length > 0;
12971
+ const hasGraphQl = Object.keys(graphqlEndpoints).length > 0;
12972
+ const hasHttpApis = Object.keys(httpApis).length > 0;
12973
+ const hasCliTools = Object.keys(cliTools).length > 0;
12974
+ const hasCapletSets = Object.keys(capletSets).length > 0;
12975
+ const config = {
12976
+ ...hasServers ? { mcpServers: servers } : {},
12977
+ ...hasOpenApi ? { openapiEndpoints } : {},
12978
+ ...hasGraphQl ? { graphqlEndpoints } : {},
12979
+ ...hasHttpApis ? { httpApis } : {},
12980
+ ...hasCliTools ? { cliTools } : {},
12981
+ ...hasCapletSets ? { capletSets } : {}
12982
+ };
12983
+ if (!(Object.keys(config).length > 0) && warnings?.length === 0) return;
12984
+ return {
12985
+ config,
12986
+ paths,
12987
+ warnings: warnings ?? []
12988
+ };
12989
+ }
12914
12990
  function discoverCapletFiles(root) {
12915
12991
  const entries = readdirSync(root, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
12916
12992
  const candidates = [];
@@ -12935,6 +13011,82 @@ function discoverCapletFiles(root) {
12935
13011
  }
12936
13012
  return candidates;
12937
13013
  }
13014
+ function discoverCapletFilesBestEffort(root, warnings) {
13015
+ const entries = readdirSync(root, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
13016
+ const byId = /* @__PURE__ */ new Map();
13017
+ const duplicateIds = /* @__PURE__ */ new Set();
13018
+ function addCandidate(id, path, isDirectoryCaplet) {
13019
+ try {
13020
+ validateCapletId(id, path);
13021
+ } catch (error) {
13022
+ warnings.push({
13023
+ path,
13024
+ message: `Skipping invalid Caplet file at ${path}: ${errorMessage$1(error)}`
13025
+ });
13026
+ return;
13027
+ }
13028
+ if (duplicateIds.has(id)) {
13029
+ warnings.push({
13030
+ path,
13031
+ message: `Duplicate Caplet ID ${id} under ${root}; skipping duplicate at ${path}`
13032
+ });
13033
+ return;
13034
+ }
13035
+ const existing = byId.get(id);
13036
+ if (!existing) {
13037
+ byId.set(id, {
13038
+ id,
13039
+ path,
13040
+ isDirectoryCaplet
13041
+ });
13042
+ return;
13043
+ }
13044
+ if (isDirectoryCaplet && !existing.isDirectoryCaplet) {
13045
+ warnings.push({
13046
+ path: existing.path,
13047
+ message: `Caplet file at ${existing.path} was shadowed by ${path}`
13048
+ });
13049
+ byId.set(id, {
13050
+ id,
13051
+ path,
13052
+ isDirectoryCaplet
13053
+ });
13054
+ return;
13055
+ }
13056
+ if (!isDirectoryCaplet && existing.isDirectoryCaplet) {
13057
+ warnings.push({
13058
+ path,
13059
+ message: `Caplet file at ${path} was shadowed by ${existing.path}`
13060
+ });
13061
+ return;
13062
+ }
13063
+ warnings.push({
13064
+ path,
13065
+ message: `Duplicate Caplet ID ${id} under ${root}; skipping ${existing.path} and ${path}`
13066
+ });
13067
+ byId.delete(id);
13068
+ duplicateIds.add(id);
13069
+ }
13070
+ for (const entry of entries) {
13071
+ if (entry.name === "auth" || entry.name === "config.json") continue;
13072
+ const path = join(root, entry.name);
13073
+ if (entry.isFile() && extname(entry.name).toLowerCase() === ".md") {
13074
+ addCandidate(basename(entry.name, extname(entry.name)), path, false);
13075
+ continue;
13076
+ }
13077
+ if (entry.isDirectory()) {
13078
+ const capletPath = join(path, "CAPLET.md");
13079
+ if (existsSync(capletPath) && statSync(capletPath).isFile()) addCandidate(entry.name, capletPath, true);
13080
+ }
13081
+ }
13082
+ return Array.from(byId.values()).map(({ id, path }) => ({
13083
+ id,
13084
+ path
13085
+ }));
13086
+ }
13087
+ function errorMessage$1(error) {
13088
+ return error instanceof Error ? error.message : String(error);
13089
+ }
12938
13090
  function readCapletFile(path) {
12939
13091
  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`);
12940
13092
  const { frontmatter, body } = parseFrontmatter(readFileSync(path, "utf8"), path);
@@ -13611,35 +13763,78 @@ function loadConfigWithSources(path = resolveConfigPath(), projectPath = resolve
13611
13763
  const projectConfig = hasProjectConfig ? rejectProjectConfigExecutableBackendMaps(readPublicConfigInput(projectPath), projectPath) : void 0;
13612
13764
  const projectCapletsRoot = resolveProjectCapletsRootForConfigPath$1(projectPath);
13613
13765
  const projectCaplets = projectCapletsRoot ? loadCapletFilesWithPaths(projectCapletsRoot) : void 0;
13614
- if (!hasUserConfig && !hasProjectConfig && !userCaplets && !projectCaplets) throw new CapletsError("CONFIG_NOT_FOUND", `Caplets config not found at ${path} or ${projectPath}`);
13615
- try {
13616
- const { input, sources, shadows } = mergeConfigInputsWithSources({
13766
+ return buildConfigWithSources([
13767
+ {
13617
13768
  input: userConfig,
13618
13769
  source: {
13619
13770
  kind: "global-config",
13620
13771
  path
13621
13772
  }
13622
- }, userCaplets ? {
13773
+ },
13774
+ userCaplets ? {
13623
13775
  input: userCaplets.config,
13624
13776
  source: {
13625
13777
  kind: "global-file",
13626
13778
  path: userCaplets.paths
13627
13779
  }
13628
- } : void 0, {
13780
+ } : void 0,
13781
+ {
13629
13782
  input: projectConfig,
13630
13783
  source: {
13631
13784
  kind: "project-config",
13632
13785
  path: projectPath
13633
13786
  }
13634
- }, projectCaplets ? {
13787
+ },
13788
+ projectCaplets ? {
13635
13789
  input: projectCaplets.config,
13636
13790
  source: {
13637
13791
  kind: "project-file",
13638
13792
  path: projectCaplets.paths
13639
13793
  }
13640
- } : void 0);
13794
+ } : void 0
13795
+ ], `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");
13796
+ }
13797
+ function loadGlobalConfig(path = resolveConfigPath()) {
13798
+ const userConfig = existsSync(path) ? readPublicConfigInput(path) : void 0;
13799
+ const userCaplets = loadCapletFilesWithPaths(resolveCapletsRoot(path));
13800
+ return buildConfigWithSources([{
13801
+ input: userConfig,
13802
+ source: {
13803
+ kind: "global-config",
13804
+ path
13805
+ }
13806
+ }, userCaplets ? {
13807
+ input: userCaplets.config,
13808
+ source: {
13809
+ kind: "global-file",
13810
+ path: userCaplets.paths
13811
+ }
13812
+ } : void 0], `Caplets user config not found at ${path}`, void 0).config;
13813
+ }
13814
+ function loadProjectConfig(projectPath = resolveProjectConfigPath()) {
13815
+ const projectConfig = existsSync(projectPath) ? rejectProjectConfigExecutableBackendMaps(readPublicConfigInput(projectPath), projectPath) : void 0;
13816
+ const projectCapletsRoot = resolveProjectCapletsRootForConfigPath$1(projectPath);
13817
+ const projectCaplets = projectCapletsRoot ? loadCapletFilesWithPaths(projectCapletsRoot) : void 0;
13818
+ return buildConfigWithSources([{
13819
+ input: projectConfig,
13820
+ source: {
13821
+ kind: "project-config",
13822
+ path: projectPath
13823
+ }
13824
+ }, projectCaplets ? {
13825
+ input: projectCaplets.config,
13826
+ source: {
13827
+ kind: "project-file",
13828
+ path: projectCaplets.paths
13829
+ }
13830
+ } : void 0], `Caplets project config not found at ${projectPath}`, void 0).config;
13831
+ }
13832
+ function buildConfigWithSources(inputs, notFoundMessage, emptyMessage) {
13833
+ if (!inputs.some((entry) => entry?.input !== void 0)) throw new CapletsError("CONFIG_NOT_FOUND", notFoundMessage);
13834
+ try {
13835
+ const { input, sources, shadows } = mergeConfigInputsWithSources(...inputs);
13641
13836
  const config = parseConfig(input);
13642
- 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");
13837
+ 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);
13643
13838
  return {
13644
13839
  config,
13645
13840
  sources,
@@ -13650,6 +13845,74 @@ function loadConfigWithSources(path = resolveConfigPath(), projectPath = resolve
13650
13845
  throw new CapletsError("CONFIG_INVALID", "Caplets config is not valid JSON", redactSecrets(error));
13651
13846
  }
13652
13847
  }
13848
+ function loadLocalOverlayConfigWithSources(path = resolveConfigPath(), projectPath = resolveProjectConfigPath()) {
13849
+ const warnings = [];
13850
+ const userConfig = existsSync(path) ? readBestEffortConfigInput(path, "global-config", warnings) : void 0;
13851
+ const userCaplets = loadBestEffortCapletFiles(resolveCapletsRoot(path), "global-file", warnings);
13852
+ const projectConfig = existsSync(projectPath) ? readBestEffortConfigInput(projectPath, "project-config", warnings, (input) => rejectProjectConfigExecutableBackendMaps(input, projectPath)) : void 0;
13853
+ const projectCapletsRoot = resolveProjectCapletsRootForConfigPath$1(projectPath);
13854
+ const projectCaplets = projectCapletsRoot ? loadBestEffortCapletFiles(projectCapletsRoot, "project-file", warnings) : void 0;
13855
+ const { input, sources, shadows } = mergeConfigInputsWithSources({
13856
+ input: userConfig,
13857
+ source: {
13858
+ kind: "global-config",
13859
+ path
13860
+ }
13861
+ }, userCaplets ? {
13862
+ input: userCaplets.config,
13863
+ source: {
13864
+ kind: "global-file",
13865
+ path: userCaplets.paths
13866
+ }
13867
+ } : void 0, {
13868
+ input: projectConfig,
13869
+ source: {
13870
+ kind: "project-config",
13871
+ path: projectPath
13872
+ }
13873
+ }, projectCaplets ? {
13874
+ input: projectCaplets.config,
13875
+ source: {
13876
+ kind: "project-file",
13877
+ path: projectCaplets.paths
13878
+ }
13879
+ } : void 0);
13880
+ return {
13881
+ config: parseConfig(input),
13882
+ sources,
13883
+ shadows,
13884
+ warnings
13885
+ };
13886
+ }
13887
+ function readBestEffortConfigInput(path, kind, warnings, transform) {
13888
+ try {
13889
+ const input = readPublicConfigInput(path);
13890
+ return transform ? transform(input) : input;
13891
+ } catch (error) {
13892
+ warnings.push({
13893
+ kind,
13894
+ path,
13895
+ message: errorMessage$2(error)
13896
+ });
13897
+ return;
13898
+ }
13899
+ }
13900
+ function loadBestEffortCapletFiles(root, kind, warnings) {
13901
+ const result = loadCapletFilesWithPathsBestEffort(root);
13902
+ if (!result) return;
13903
+ for (const warning of result.warnings) warnings.push({
13904
+ kind,
13905
+ path: warning.path ?? root,
13906
+ message: warning.message
13907
+ });
13908
+ return {
13909
+ config: result.config,
13910
+ paths: result.paths
13911
+ };
13912
+ }
13913
+ function errorMessage$2(error) {
13914
+ return error instanceof Error ? error.message : String(error);
13915
+ }
13653
13916
  function loadIsolatedConfig(options) {
13654
13917
  if (!options.configPath && !options.capletsRoot) throw new CapletsError("CONFIG_INVALID", "Nested Caplet set must define at least one source: configPath or capletsRoot");
13655
13918
  const configInput = options.configPath ? readPublicConfigInput(options.configPath) : void 0;
@@ -57594,6 +57857,7 @@ var CapletsEngine = class {
57594
57857
  watchDebounceMs;
57595
57858
  watchEnabled;
57596
57859
  writeErr;
57860
+ configLoader;
57597
57861
  reloadListeners = /* @__PURE__ */ new Set();
57598
57862
  watchers = [];
57599
57863
  reloadTimer;
@@ -57606,7 +57870,8 @@ var CapletsEngine = class {
57606
57870
  configPath: resolveConfigPath(options.configPath),
57607
57871
  projectConfigPath: options.projectConfigPath ?? resolveProjectConfigPath()
57608
57872
  };
57609
- const config = loadConfig(this.paths.configPath, this.paths.projectConfigPath);
57873
+ this.configLoader = options.configLoader ?? loadConfig;
57874
+ const config = this.configLoader(this.paths.configPath, this.paths.projectConfigPath);
57610
57875
  this.registry = new ServerRegistry(config);
57611
57876
  this.downstream = new DownstreamManager(this.registry, selectAuthOptions(options.authDir));
57612
57877
  this.openapi = new OpenApiManager(this.registry, selectAuthOptions(options.authDir));
@@ -57661,7 +57926,7 @@ var CapletsEngine = class {
57661
57926
  }
57662
57927
  }
57663
57928
  async completeCliWords(words) {
57664
- const { completeCliWords } = await Promise.resolve().then(() => completion_Cq1z7ci2_exports).then((n) => n.r);
57929
+ const { completeCliWords } = await Promise.resolve().then(() => completion_DRPTunQd_exports).then((n) => n.r);
57665
57930
  return await completeCliWords(words, {
57666
57931
  config: this.registry.config,
57667
57932
  managers: {
@@ -57721,7 +57986,7 @@ var CapletsEngine = class {
57721
57986
  if (this.closed) return false;
57722
57987
  let nextConfig;
57723
57988
  try {
57724
- nextConfig = loadConfig(this.paths.configPath, this.paths.projectConfigPath);
57989
+ nextConfig = this.configLoader(this.paths.configPath, this.paths.projectConfigPath);
57725
57990
  } catch (error) {
57726
57991
  this.writeErr(`Caplets config reload failed; keeping last known-good config.\n`);
57727
57992
  this.writeErr(`${JSON.stringify(toSafeError(error, "CONFIG_INVALID"), null, 2)}\n`);
@@ -57999,8 +58264,8 @@ function hasEnv$1(value) {
57999
58264
  return value !== void 0 && value.trim() !== "";
58000
58265
  }
58001
58266
  //#endregion
58002
- //#region ../core/dist/completion-Cq1z7ci2.js
58003
- var completion_Cq1z7ci2_exports = /* @__PURE__ */ __exportAll$1({
58267
+ //#region ../core/dist/completion-DRPTunQd.js
58268
+ var completion_DRPTunQd_exports = /* @__PURE__ */ __exportAll$1({
58004
58269
  a: () => formatCapletList,
58005
58270
  c: () => resolveCliConfigPaths,
58006
58271
  i: () => trailingSpaceCompletionToken,
@@ -58122,7 +58387,8 @@ function allCaplets(config) {
58122
58387
  ...Object.values(config.openapiEndpoints),
58123
58388
  ...Object.values(config.graphqlEndpoints),
58124
58389
  ...Object.values(config.httpApis),
58125
- ...Object.values(config.cliTools)
58390
+ ...Object.values(config.cliTools),
58391
+ ...Object.values(config.capletSets)
58126
58392
  ];
58127
58393
  }
58128
58394
  function formatCapletList(rows, format = "plain") {
@@ -58174,15 +58440,15 @@ function formatSourceKind(kind) {
58174
58440
  if (kind.startsWith("global")) return "global";
58175
58441
  return kind;
58176
58442
  }
58177
- function resolveCliConfigPaths(envConfigPath, authDir) {
58443
+ function resolveCliConfigPaths(envConfigPath, projectConfigPath = resolveProjectConfigPath(), authDir) {
58178
58444
  const configPath = resolveConfigPath(envConfigPath);
58179
58445
  const effectiveAuthDir = authDir ?? DEFAULT_AUTH_DIR;
58180
58446
  return {
58181
58447
  userConfig: configPath,
58182
- projectConfig: resolveProjectConfigPath(),
58448
+ projectConfig: projectConfigPath,
58183
58449
  userRoot: resolveCapletsRoot(configPath),
58184
58450
  stateRoot: dirname(effectiveAuthDir),
58185
- projectRoot: resolveProjectCapletsRoot(),
58451
+ projectRoot: dirname(projectConfigPath),
58186
58452
  authDir: effectiveAuthDir,
58187
58453
  envConfig: envConfigPath ?? null
58188
58454
  };
@@ -59863,7 +60129,7 @@ const EMPTY_COMPLETION_RESULT = { completion: {
59863
60129
  values: [],
59864
60130
  hasMore: false
59865
60131
  } };
59866
- var version$1 = "0.18.3";
60132
+ var version$1 = "0.18.5";
59867
60133
  var CapletsMcpSession = class {
59868
60134
  engine;
59869
60135
  server;
@@ -63379,7 +63645,7 @@ function isUrlLike(value) {
63379
63645
  return /^https?:\/\//i.test(value);
63380
63646
  }
63381
63647
  async function loginAuth(serverId, options) {
63382
- const server = findAuthTarget(serverId, loadConfig(options.configPath));
63648
+ const server = findAuthTarget(serverId, options.config ?? loadConfig(options.configPath));
63383
63649
  assertLoginTarget(server, serverId);
63384
63650
  try {
63385
63651
  const flowOptions = {
@@ -63401,40 +63667,64 @@ function logoutAuth(serverId, options) {
63401
63667
  else options.writeOut(`No OAuth credentials found for \`${serverId}\`.\n`);
63402
63668
  }
63403
63669
  function logoutAuthResult(serverId, options) {
63404
- assertLoginTarget(findAuthTarget(serverId, loadConfig(options.configPath)), serverId);
63670
+ assertLoginTarget(findAuthTarget(serverId, options.config ?? loadConfig(options.configPath)), serverId);
63405
63671
  return {
63406
63672
  server: serverId,
63407
63673
  deleted: deleteTokenBundle(serverId, options.authDir)
63408
63674
  };
63409
63675
  }
63410
- function listAuth(options) {
63411
- const rows = listAuthRows(options);
63412
- const format = options.format ?? "plain";
63413
- if (format === "json") {
63414
- options.writeOut(`${JSON.stringify(rows, null, 2)}\n`);
63415
- return;
63676
+ function listAuthRows(options) {
63677
+ return authRowsForTargets(authTargets(loadConfig(options.configPath)), options.authDir);
63678
+ }
63679
+ function listLocalAuthRows(options) {
63680
+ return authRowsForTargets(localAuthTargets(options), options.authDir);
63681
+ }
63682
+ function localAuthTargets(options) {
63683
+ return [...options.source === "project" ? [] : authTargetsForSource("global", options), ...options.source === "global" ? [] : authTargetsForSource("project", options)].filter((target) => !options.source || target.source === options.source);
63684
+ }
63685
+ function localAuthConfigForTarget(options) {
63686
+ assertLoginTarget(localAuthTargets(options).find((candidate) => candidate.server === options.serverId), options.serverId);
63687
+ return loadConfigForSource(options.source, options);
63688
+ }
63689
+ function authTargetsForSource(source, options) {
63690
+ try {
63691
+ return authTargets(loadConfigForSource(source, options)).map((target) => ({
63692
+ ...target,
63693
+ source
63694
+ }));
63695
+ } catch (error) {
63696
+ if (error instanceof CapletsError && error.code === "CONFIG_NOT_FOUND") return [];
63697
+ throw error;
63416
63698
  }
63417
- options.writeOut(formatAuthRows(rows, format));
63418
63699
  }
63419
- function listAuthRows(options) {
63420
- return authTargets(loadConfig(options.configPath)).sort((left, right) => left.server.localeCompare(right.server)).map((server) => {
63421
- const bundle = readTokenBundle(server.server, options.authDir);
63700
+ function loadConfigForSource(source, options) {
63701
+ if (source === "global") return loadGlobalConfig(options.configPath);
63702
+ return loadProjectConfig(options.projectConfigPath);
63703
+ }
63704
+ function authRowsForTargets(targets, authDir) {
63705
+ return targets.sort((left, right) => left.server.localeCompare(right.server)).map((server) => {
63706
+ const bundle = readTokenBundle(server.server, authDir);
63422
63707
  const status = !bundle ? "missing" : isTokenBundleExpired(bundle) ? "expired" : "authenticated";
63423
63708
  return {
63424
63709
  server: server.server,
63425
63710
  status,
63426
63711
  ...bundle?.expiresAt ? { expiresAt: bundle.expiresAt } : {},
63427
- ...bundle?.scope ? { scope: bundle.scope } : {}
63712
+ ...bundle?.scope ? { scope: bundle.scope } : {},
63713
+ ...server.source ? { source: server.source } : {}
63428
63714
  };
63429
63715
  });
63430
63716
  }
63431
63717
  function formatAuthRows(rows, format) {
63432
- if (rows.length === 0) return format === "markdown" ? "## OAuth credentials\n\nNo configured remote OAuth servers found.\n" : "No configured remote OAuth servers found.\n";
63718
+ if (rows.length === 0) return format === "markdown" ? "## OAuth credentials\n\nNo configured OAuth servers found.\n" : "No configured OAuth servers found.\n";
63433
63719
  let output = "";
63434
63720
  if (format === "markdown") output += "## OAuth credentials\n\n";
63435
63721
  else output += "OAuth credentials\n\n";
63436
63722
  for (const row of rows) {
63437
- const details = [row.expiresAt ? `expires ${row.expiresAt}` : void 0, row.scope ? `scope ${row.scope}` : void 0].filter(Boolean).join("; ");
63723
+ const details = [
63724
+ row.source ? `source ${row.source}` : void 0,
63725
+ row.expiresAt ? `expires ${row.expiresAt}` : void 0,
63726
+ row.scope ? `scope ${row.scope}` : void 0
63727
+ ].filter(Boolean).join("; ");
63438
63728
  if (format === "markdown") {
63439
63729
  output += `- \`${row.server}\` — ${row.status}${details ? ` (${details})` : ""}\n`;
63440
63730
  continue;
@@ -63442,6 +63732,7 @@ function formatAuthRows(rows, format) {
63442
63732
  output += [
63443
63733
  row.server,
63444
63734
  ` Status: ${row.status}`,
63735
+ ...row.source ? [` Source: ${row.source}`] : [],
63445
63736
  ...row.expiresAt ? [` Expires: ${row.expiresAt}`] : [],
63446
63737
  ...row.scope ? [` Scope: ${row.scope}`] : []
63447
63738
  ].join("\n") + "\n\n";
@@ -67942,15 +68233,29 @@ function createProgram(io = {}) {
67942
68233
  const completionWords = normalizeCompletionWords(words);
67943
68234
  let suggestions = [];
67944
68235
  try {
67945
- suggestions = remote ? await remote.request("complete_cli", {
67946
- shell,
67947
- words: completionWords
67948
- }) : await completeCliWordsLocally(completionWords, {
68236
+ if (remote) {
68237
+ const localOverlay = loadLocalOverlayForCli(io, () => {});
68238
+ const localSuggestions = await completeCliWordsLocally(completionWords, {
68239
+ ...configPath ? { configPath } : {},
68240
+ projectConfigPath: envProjectConfigPath(env),
68241
+ ...io.authDir ? { authDir: io.authDir } : {},
68242
+ config: localOverlay.config
68243
+ });
68244
+ if (localShadowedCompletionTarget(completionWords, localOverlay.config)) suggestions = localSuggestions;
68245
+ else suggestions = mergeCompletionSuggestions(localSuggestions, await remote.request("complete_cli", {
68246
+ shell,
68247
+ words: completionWords
68248
+ }));
68249
+ } else suggestions = await completeCliWordsLocally(completionWords, {
67949
68250
  ...configPath ? { configPath } : {},
68251
+ projectConfigPath: envProjectConfigPath(env),
67950
68252
  ...io.authDir ? { authDir: io.authDir } : {}
67951
68253
  });
67952
68254
  } catch {
67953
- suggestions = remote ? [] : await completeCliWords(completionWords, configPath ? { configPath } : {});
68255
+ suggestions = remote ? [] : await completeCliWords(completionWords, {
68256
+ ...configPath ? { configPath } : {},
68257
+ projectConfigPath: envProjectConfigPath(env)
68258
+ });
67954
68259
  }
67955
68260
  if (suggestions.length > 0) writeOut(`${suggestions.join("\n")}\n`);
67956
68261
  });
@@ -67962,23 +68267,26 @@ function createProgram(io = {}) {
67962
68267
  ...io.authDir ? { authDir: io.authDir } : {}
67963
68268
  }, writeErr)))(resolved);
67964
68269
  });
67965
- program.command(cliCommands.init).description("Create a starter Caplets config file.").option("--force", "overwrite an existing config file").action(async (options) => {
67966
- const remote = remoteClientForCli(io);
67967
- if (remote) {
67968
- writeOut(`Created remote Caplets config at ${(await remote.request("init", { force: Boolean(options.force) })).path}\n`);
68270
+ program.command(cliCommands.init).description("Create a starter Caplets config file.").option("--project", "create the project Caplets config").option("-g, --global", "create the user Caplets config").option("--remote", "create the remote Caplets config").option("--force", "overwrite an existing config file").action(async (options) => {
68271
+ const target = parseMutationTarget(options);
68272
+ if (target === "remote") {
68273
+ writeOut(`Created remote Caplets config at ${(await requireRemoteClientForTarget(io).request("init", { force: Boolean(options.force) })).path}\n`);
67969
68274
  return;
67970
68275
  }
67971
- const configPath = currentConfigPath();
67972
- writeOut(`Created Caplets config at ${initConfig({
67973
- ...configPath ? { path: configPath } : {},
68276
+ const path = initConfig({
68277
+ path: target === "global" ? resolveConfigPath(currentConfigPath()) : envProjectConfigPath(env),
67974
68278
  force: Boolean(options.force)
67975
- })}\n`);
68279
+ });
68280
+ writeOut(`Created ${localMutationTargetLabel(target, io)}Caplets config at ${path}\n`);
67976
68281
  });
67977
68282
  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) => {
67978
68283
  const includeDisabled = Boolean(options.all);
67979
68284
  const remote = remoteClientForCli(io);
67980
68285
  if (remote) {
67981
- const rows = await remote.request("list", { includeDisabled });
68286
+ const rows = mergeRemoteAndLocalRows(await remote.request("list", { includeDisabled }), tryLoadLocalOverlayForCli(io, writeErr), {
68287
+ includeDisabled,
68288
+ writeErr
68289
+ });
67982
68290
  if (options.json || options.format === "json") {
67983
68291
  writeOut(`${JSON.stringify(rows, null, 2)}\n`);
67984
68292
  return;
@@ -67986,18 +68294,17 @@ function createProgram(io = {}) {
67986
68294
  writeOut(formatCapletList(rows, options.format ?? "plain"));
67987
68295
  return;
67988
68296
  }
67989
- const rows = listCaplets(loadConfigWithSources(currentConfigPath()), { includeDisabled });
68297
+ const rows = listCaplets(loadConfigWithSources(currentConfigPath(), envProjectConfigPath(env)), { includeDisabled });
67990
68298
  if (options.json || options.format === "json") {
67991
68299
  writeOut(`${JSON.stringify(rows, null, 2)}\n`);
67992
68300
  return;
67993
68301
  }
67994
68302
  writeOut(formatCapletList(rows, options.format ?? "plain"));
67995
68303
  });
67996
- 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) => {
67997
- const remote = remoteClientForCli(io);
67998
- if (remote) {
67999
- if (options.global) writeErr("Warning: --global is not supported in remote mode; the server controls the installation destination.\n");
68000
- const result = await remote.request("install", {
68304
+ 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("--project", "install to the project Caplets root").option("-g, --global", "install to the user Caplets root").option("--remote", "install through remote control").option("--force", "overwrite installed Caplets").action(async (repo, capletIds, options) => {
68305
+ const target = parseMutationTarget(options);
68306
+ if (target === "remote") {
68307
+ const result = await requireRemoteClientForTarget(io).request("install", {
68001
68308
  repo,
68002
68309
  capletIds,
68003
68310
  force: Boolean(options.force)
@@ -68008,15 +68315,15 @@ function createProgram(io = {}) {
68008
68315
  const result = installCaplets(repo, {
68009
68316
  capletIds,
68010
68317
  force: Boolean(options.force),
68011
- destinationRoot: options.global ? resolveCapletsRoot(resolveConfigPath(currentConfigPath())) : resolveProjectCapletsRoot()
68318
+ destinationRoot: target === "global" ? resolveCapletsRoot(resolveConfigPath(currentConfigPath())) : envProjectCapletsRoot(env)
68012
68319
  });
68013
- for (const caplet of result.installed) writeOut(`Installed ${caplet.id} to ${caplet.destination}\n`);
68320
+ for (const caplet of result.installed) writeOut(`Installed ${caplet.id} to ${localMutationTargetLabel(target, io)}${caplet.destination}\n`);
68014
68321
  });
68015
68322
  const add = program.command(cliCommands.add).description("Add generated Caplet files.");
68016
- 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) => {
68017
- const remote = remoteClientForCli(io);
68018
- if (remote) {
68019
- writeAddResult(writeOut, "CLI", await remote.request("add", {
68323
+ 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("--project", "write to the project Caplets root").option("-g, --global", "write to the user Caplets root").option("--remote", "add through remote control").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) => {
68324
+ const target = parseMutationTarget(options);
68325
+ if (target === "remote") {
68326
+ writeAddResult(writeOut, "CLI", await requireRemoteClientForTarget(io).request("add", {
68020
68327
  kind: "cli",
68021
68328
  id,
68022
68329
  options: remoteAddOptions(options)
@@ -68025,73 +68332,77 @@ function createProgram(io = {}) {
68025
68332
  }
68026
68333
  const result = addCliCaplet(id, {
68027
68334
  ...options,
68028
- destinationRoot: options.global ? resolveCapletsRoot(resolveConfigPath(currentConfigPath())) : resolveProjectCapletsRoot()
68335
+ destinationRoot: target === "global" ? resolveCapletsRoot(resolveConfigPath(currentConfigPath())) : envProjectCapletsRoot(env)
68029
68336
  });
68030
68337
  if (result.path) {
68031
- writeOut(`Wrote CLI Caplet to ${result.path}\n`);
68338
+ writeOut(`Wrote ${localMutationTargetLabel(target, io)}CLI Caplet to ${result.path}\n`);
68032
68339
  return;
68033
68340
  }
68034
68341
  writeOut(result.text);
68035
68342
  });
68036
- 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) => {
68037
- const remote = remoteClientForCli(io);
68038
- if (remote) {
68039
- writeAddResult(writeOut, "MCP", await remote.request("add", {
68343
+ 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("--project", "write to the project Caplets root").option("-g, --global", "write to the user Caplets root").option("--remote", "add through remote control").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) => {
68344
+ const target = parseMutationTarget(options);
68345
+ if (target === "remote") {
68346
+ writeAddResult(writeOut, "MCP", await requireRemoteClientForTarget(io).request("add", {
68040
68347
  kind: "mcp",
68041
68348
  id,
68042
68349
  options: remoteAddOptions(options)
68043
68350
  }));
68044
68351
  return;
68045
68352
  }
68046
- writeAddResult(writeOut, "MCP", addMcpCaplet(id, {
68353
+ const result = addMcpCaplet(id, {
68047
68354
  ...options,
68048
- destinationRoot: addDestinationRoot(options, currentConfigPath())
68049
- }));
68355
+ destinationRoot: addDestinationRoot(target, currentConfigPath(), env)
68356
+ });
68357
+ writeAddResult(writeOut, `${localMutationTargetLabel(target, io)}MCP`, result);
68050
68358
  });
68051
- 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) => {
68052
- const remote = remoteClientForCli(io);
68053
- if (remote) {
68054
- writeAddResult(writeOut, "OpenAPI", await remote.request("add", {
68359
+ 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("--project", "write to the project Caplets root").option("-g, --global", "write to the user Caplets root").option("--remote", "add through remote control").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) => {
68360
+ const target = parseMutationTarget(options);
68361
+ if (target === "remote") {
68362
+ writeAddResult(writeOut, "OpenAPI", await requireRemoteClientForTarget(io).request("add", {
68055
68363
  kind: "openapi",
68056
68364
  id,
68057
68365
  options: remoteAddOptions(options)
68058
68366
  }));
68059
68367
  return;
68060
68368
  }
68061
- writeAddResult(writeOut, "OpenAPI", addOpenApiCaplet(id, {
68369
+ const result = addOpenApiCaplet(id, {
68062
68370
  ...options,
68063
- destinationRoot: addDestinationRoot(options, currentConfigPath())
68064
- }));
68371
+ destinationRoot: addDestinationRoot(target, currentConfigPath(), env)
68372
+ });
68373
+ writeAddResult(writeOut, `${localMutationTargetLabel(target, io)}OpenAPI`, result);
68065
68374
  });
68066
- 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) => {
68067
- const remote = remoteClientForCli(io);
68068
- if (remote) {
68069
- writeAddResult(writeOut, "GraphQL", await remote.request("add", {
68375
+ 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("--project", "write to the project Caplets root").option("-g, --global", "write to the user Caplets root").option("--remote", "add through remote control").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) => {
68376
+ const target = parseMutationTarget(options);
68377
+ if (target === "remote") {
68378
+ writeAddResult(writeOut, "GraphQL", await requireRemoteClientForTarget(io).request("add", {
68070
68379
  kind: "graphql",
68071
68380
  id,
68072
68381
  options: remoteAddOptions(options)
68073
68382
  }));
68074
68383
  return;
68075
68384
  }
68076
- writeAddResult(writeOut, "GraphQL", addGraphqlCaplet(id, {
68385
+ const result = addGraphqlCaplet(id, {
68077
68386
  ...options,
68078
- destinationRoot: addDestinationRoot(options, currentConfigPath())
68079
- }));
68387
+ destinationRoot: addDestinationRoot(target, currentConfigPath(), env)
68388
+ });
68389
+ writeAddResult(writeOut, `${localMutationTargetLabel(target, io)}GraphQL`, result);
68080
68390
  });
68081
- 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) => {
68082
- const remote = remoteClientForCli(io);
68083
- if (remote) {
68084
- writeAddResult(writeOut, "HTTP", await remote.request("add", {
68391
+ 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("--project", "write to the project Caplets root").option("-g, --global", "write to the user Caplets root").option("--remote", "add through remote control").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) => {
68392
+ const target = parseMutationTarget(options);
68393
+ if (target === "remote") {
68394
+ writeAddResult(writeOut, "HTTP", await requireRemoteClientForTarget(io).request("add", {
68085
68395
  kind: "http",
68086
68396
  id,
68087
68397
  options: remoteAddOptions(options)
68088
68398
  }));
68089
68399
  return;
68090
68400
  }
68091
- writeAddResult(writeOut, "HTTP", addHttpCaplet(id, {
68401
+ const result = addHttpCaplet(id, {
68092
68402
  ...options,
68093
- destinationRoot: addDestinationRoot(options, currentConfigPath())
68094
- }));
68403
+ destinationRoot: addDestinationRoot(target, currentConfigPath(), env)
68404
+ });
68405
+ writeAddResult(writeOut, `${localMutationTargetLabel(target, io)}HTTP`, result);
68095
68406
  });
68096
68407
  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) => {
68097
68408
  await executeOperation(caplet, { operation: "get_caplet" }, {
@@ -68293,7 +68604,7 @@ function createProgram(io = {}) {
68293
68604
  writeOut(`${resolveConfigPath(currentConfigPath())}\n`);
68294
68605
  });
68295
68606
  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) => {
68296
- const paths = resolveCliConfigPaths(currentConfigPath(), io.authDir);
68607
+ const paths = resolveCliConfigPaths(currentConfigPath(), envProjectConfigPath(env), io.authDir);
68297
68608
  if (options.json || options.format === "json") {
68298
68609
  writeOut(`${JSON.stringify(paths, null, 2)}\n`);
68299
68610
  return;
@@ -68301,60 +68612,59 @@ function createProgram(io = {}) {
68301
68612
  writeOut(formatConfigPaths(paths, options.format ?? "plain"));
68302
68613
  });
68303
68614
  const auth = program.command(cliCommands.auth).description("Manage OAuth credentials for remote servers.");
68304
- 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) => {
68305
- const remote = remoteClientForCli(io);
68306
- if (remote) {
68307
- const started = await remote.request("auth_login_start", { server: serverId });
68308
- if (started.authorizationUrl) {
68309
- writeOut(`Open this URL to authorize ${serverId}:\n${started.authorizationUrl}\n`);
68310
- if (options.open !== false) await openBrowser(started.authorizationUrl);
68311
- writeOut("Complete authentication in your browser. The server callback will store credentials.\n");
68312
- return;
68313
- }
68314
- if (started.authenticated) writeOut(`Authenticated \`${serverId}\`.\n`);
68615
+ auth.command("login").description("Authenticate a configured remote OAuth server.").argument("<server>", "configured server ID").option("--project", "authenticate using the project Caplets config").option("-g, --global", "authenticate using the user Caplets config").option("--remote", "authenticate using the remote server auth store").option("--no-open", "print the authorization URL without opening a browser").action(async (serverId, options) => {
68616
+ const target = await resolveAuthTarget(serverId, options, io);
68617
+ if (target === "remote") {
68618
+ await remoteAuthLogin(requireRemoteClientForTarget(io), serverId, options.open !== false, writeOut);
68315
68619
  return;
68316
68620
  }
68317
68621
  const configPath = currentConfigPath();
68622
+ const projectConfigPath = envProjectConfigPath(env);
68318
68623
  await loginAuth(serverId, {
68319
68624
  noOpen: options.open === false,
68320
68625
  writeOut,
68321
68626
  writeErr,
68322
68627
  ...configPath ? { configPath } : {},
68628
+ ...projectConfigPath ? { projectConfigPath } : {},
68629
+ config: localAuthConfigForTarget({
68630
+ serverId,
68631
+ ...configPath ? { configPath } : {},
68632
+ ...projectConfigPath ? { projectConfigPath } : {},
68633
+ source: target
68634
+ }),
68323
68635
  ...io.authDir ? { authDir: io.authDir } : {}
68324
68636
  });
68325
68637
  });
68326
- auth.command("logout").description("Delete stored OAuth credentials for a server.").argument("<server>", "configured server ID").action(async (serverId) => {
68327
- const remote = remoteClientForCli(io);
68328
- if (remote) {
68329
- writeOut((await remote.request("auth_logout", { server: serverId })).deleted ? `Deleted remote OAuth credentials for \`${serverId}\`.\n` : `No remote OAuth credentials found for \`${serverId}\`.\n`);
68638
+ auth.command("logout").description("Delete stored OAuth credentials for a server.").argument("<server>", "configured server ID").option("--project", "delete credentials for the project Caplets config target").option("-g, --global", "delete credentials for the user Caplets config target").option("--remote", "delete credentials from the remote server auth store").action(async (serverId, options) => {
68639
+ const target = await resolveAuthTarget(serverId, options, io);
68640
+ if (target === "remote") {
68641
+ writeOut((await requireRemoteClientForTarget(io).request("auth_logout", { server: serverId })).deleted ? `Deleted remote OAuth credentials for \`${serverId}\`.\n` : `No remote OAuth credentials found for \`${serverId}\`.\n`);
68330
68642
  return;
68331
68643
  }
68332
68644
  const configPath = currentConfigPath();
68645
+ const projectConfigPath = envProjectConfigPath(env);
68333
68646
  logoutAuth(serverId, {
68334
68647
  writeOut,
68335
68648
  ...configPath ? { configPath } : {},
68649
+ config: localAuthConfigForTarget({
68650
+ serverId,
68651
+ ...configPath ? { configPath } : {},
68652
+ ...projectConfigPath ? { projectConfigPath } : {},
68653
+ source: target
68654
+ }),
68336
68655
  ...io.authDir ? { authDir: io.authDir } : {}
68337
68656
  });
68338
68657
  });
68339
- 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) => {
68658
+ 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).option("--project", "list auth targets from the project Caplets config").option("-g, --global", "list auth targets from the user Caplets config").option("--remote", "list auth targets from the remote server auth store").action(async (options) => {
68340
68659
  const configPath = currentConfigPath();
68660
+ const projectConfigPath = envProjectConfigPath(env);
68341
68661
  const format = options.json || options.format === "json" ? "json" : options.format ?? "plain";
68342
- const remote = remoteClientForCli(io);
68343
- if (remote) {
68344
- const rows = await remote.request("auth_list", {});
68345
- if (format === "json") {
68346
- writeOut(`${JSON.stringify(rows, null, 2)}\n`);
68347
- return;
68348
- }
68349
- writeOut(formatAuthRows(rows, format));
68662
+ const rows = await authListRowsForCli(parseAuthFlagTarget(options), io, configPath, projectConfigPath);
68663
+ if (format === "json") {
68664
+ writeOut(`${JSON.stringify(rows, null, 2)}\n`);
68350
68665
  return;
68351
68666
  }
68352
- listAuth({
68353
- writeOut,
68354
- format,
68355
- ...configPath ? { configPath } : {},
68356
- ...io.authDir ? { authDir: io.authDir } : {}
68357
- });
68667
+ writeOut(formatAuthRows(rows, format));
68358
68668
  });
68359
68669
  return program;
68360
68670
  }
@@ -68398,12 +68708,90 @@ function remoteCommandForOperation(operation) {
68398
68708
  }
68399
68709
  }
68400
68710
  function remoteAddOptions(options) {
68401
- const { output, print, global, destinationRoot, ...remoteOptions } = options;
68402
- if (global) throw new CapletsError("REQUEST_INVALID", "--global is not supported in remote mode; the server controls the add destination.");
68711
+ const { output, print, global, project, remote, destinationRoot, ...remoteOptions } = options;
68403
68712
  if (print) throw new CapletsError("REQUEST_INVALID", "--print is not supported in remote mode; the server controls add output.");
68404
68713
  if (output !== void 0) throw new CapletsError("REQUEST_INVALID", "--output is not supported in remote mode; the server controls the add destination.");
68405
68714
  return remoteOptions;
68406
68715
  }
68716
+ function parseMutationTarget(options) {
68717
+ const selected = [
68718
+ options.project ? "--project" : void 0,
68719
+ options.global ? "--global" : void 0,
68720
+ options.remote ? "--remote" : void 0
68721
+ ].filter((value) => value !== void 0);
68722
+ if (selected.length > 1) throw new CapletsError("REQUEST_INVALID", `Cannot combine mutation target flags: ${selected.join(", ")}`);
68723
+ if (options.global) return "global";
68724
+ if (options.remote) return "remote";
68725
+ return "project";
68726
+ }
68727
+ function localMutationTargetLabel(target, io) {
68728
+ return remoteClientForCli(io) ? `${target} ` : "";
68729
+ }
68730
+ function parseAuthFlagTarget(options) {
68731
+ const selected = [
68732
+ options.project ? "--project" : void 0,
68733
+ options.global ? "--global" : void 0,
68734
+ options.remote ? "--remote" : void 0
68735
+ ].filter((value) => value !== void 0);
68736
+ if (selected.length > 1) throw new CapletsError("REQUEST_INVALID", `Cannot combine auth target flags: ${selected.join(", ")}`);
68737
+ if (options.project) return "project";
68738
+ if (options.global) return "global";
68739
+ if (options.remote) return "remote";
68740
+ }
68741
+ async function resolveAuthTarget(serverId, options, io) {
68742
+ const explicit = parseAuthFlagTarget(options);
68743
+ if (explicit) return explicit;
68744
+ const env = io.env ?? process.env;
68745
+ const configPath = envConfigPath(env);
68746
+ const projectConfigPath = envProjectConfigPath(env);
68747
+ const matches = localAuthTargets({
68748
+ ...configPath ? { configPath } : {},
68749
+ ...projectConfigPath ? { projectConfigPath } : {}
68750
+ }).filter((target) => target.server === serverId).map((target) => target.source);
68751
+ const remote = remoteClientForCli(io);
68752
+ if (remote) {
68753
+ if (matches.length === 0) matches.push("remote");
68754
+ else if ((await remoteAuthRows(remote)).some((row) => row.server === serverId)) matches.push("remote");
68755
+ }
68756
+ const unique = [...new Set(matches)];
68757
+ if (unique.length === 1) return unique[0];
68758
+ if (unique.length > 1) throw new CapletsError("REQUEST_INVALID", `Auth target \`${serverId}\` exists in multiple scopes. Pass --project, --global, or --remote.`);
68759
+ throw new CapletsError("SERVER_NOT_FOUND", `Server ${serverId} is not configured for OAuth`);
68760
+ }
68761
+ async function authListRowsForCli(target, io, configPath, projectConfigPath) {
68762
+ if (target === "remote") return remoteAuthRows(requireRemoteClientForTarget(io));
68763
+ const localRows = listLocalAuthRows({
68764
+ ...configPath ? { configPath } : {},
68765
+ ...projectConfigPath ? { projectConfigPath } : {},
68766
+ ...io.authDir ? { authDir: io.authDir } : {},
68767
+ ...target ? { source: target } : {}
68768
+ });
68769
+ if (target) return localRows;
68770
+ const remote = remoteClientForCli(io);
68771
+ if (!remote) return localRows;
68772
+ return [...localRows, ...await remoteAuthRows(remote)].sort((left, right) => left.server.localeCompare(right.server));
68773
+ }
68774
+ async function remoteAuthRows(remote) {
68775
+ return (await remote.request("auth_list", {})).map((row) => ({
68776
+ ...row,
68777
+ source: "remote"
68778
+ }));
68779
+ }
68780
+ async function remoteAuthLogin(remote, serverId, open, writeOut) {
68781
+ const started = await remote.request("auth_login_start", { server: serverId });
68782
+ if (started.authorizationUrl) {
68783
+ writeOut(`Open this URL to authorize ${serverId}:\n${started.authorizationUrl}\n`);
68784
+ if (open) await openBrowser(started.authorizationUrl);
68785
+ writeOut("Complete authentication in your browser. The server callback will store credentials.\n");
68786
+ return;
68787
+ }
68788
+ if (started.authenticated) writeOut(`Authenticated \`${serverId}\`.\n`);
68789
+ }
68790
+ function requireRemoteClientForTarget(io) {
68791
+ const remote = remoteClientForCli(io);
68792
+ if (!remote) throw new CapletsError("REQUEST_INVALID", "--remote requires CAPLETS_MODE=remote and CAPLETS_SERVER_URL");
68793
+ return remote;
68794
+ }
68407
68795
  function collect(value, previous) {
68408
68796
  previous.push(value);
68409
68797
  return previous;
@@ -68440,8 +68828,10 @@ function parseQualifiedTarget(capletOrTarget, toolArgument) {
68440
68828
  async function completeCliWordsLocally(words, options) {
68441
68829
  const engine = new CapletsEngine({
68442
68830
  ...options.configPath ? { configPath: options.configPath } : {},
68831
+ ...options.projectConfigPath ? { projectConfigPath: options.projectConfigPath } : {},
68443
68832
  ...options.authDir ? { authDir: options.authDir } : {},
68444
- watch: false
68833
+ watch: false,
68834
+ ...options.config ? { configLoader: () => options.config } : {}
68445
68835
  });
68446
68836
  try {
68447
68837
  return await engine.completeCliWords(words);
@@ -68449,6 +68839,34 @@ async function completeCliWordsLocally(words, options) {
68449
68839
  await engine.close();
68450
68840
  }
68451
68841
  }
68842
+ function mergeCompletionSuggestions(...groups) {
68843
+ return [...new Set(groups.flat())];
68844
+ }
68845
+ function localShadowedCompletionTarget(words, config) {
68846
+ const command = words[0];
68847
+ const target = words[1];
68848
+ if (!command || !target || target.startsWith("-")) return;
68849
+ const qualifiedCommands = new Set([
68850
+ cliCommands.getTool,
68851
+ cliCommands.callTool,
68852
+ cliCommands.getPrompt
68853
+ ]);
68854
+ const capletCommands = new Set([
68855
+ cliCommands.getCaplet,
68856
+ cliCommands.checkBackend,
68857
+ cliCommands.listTools,
68858
+ cliCommands.searchTools,
68859
+ cliCommands.listResources,
68860
+ cliCommands.searchResources,
68861
+ cliCommands.listResourceTemplates,
68862
+ cliCommands.readResource,
68863
+ cliCommands.listPrompts,
68864
+ cliCommands.searchPrompts,
68865
+ cliCommands.complete
68866
+ ]);
68867
+ const caplet = qualifiedCommands.has(command) ? target.slice(0, target.includes(".") ? target.indexOf(".") : target.length) : capletCommands.has(command) ? target : void 0;
68868
+ return caplet && hasEnabledCaplet(config, caplet) ? caplet : void 0;
68869
+ }
68452
68870
  function parseCallToolArgs(value) {
68453
68871
  if (value === void 0) return {};
68454
68872
  let parsed;
@@ -68489,6 +68907,11 @@ function isPlainObject(value) {
68489
68907
  async function executeOperation(caplet, request, io) {
68490
68908
  const command = remoteCommandForOperation(request.operation);
68491
68909
  if (io.remote && command) {
68910
+ const localOverlay = tryLoadLocalOverlayForCli(io, io.writeErr);
68911
+ if (localOverlay && hasEnabledCaplet(localOverlay.config, caplet)) {
68912
+ await executeLocalOperation(caplet, request, io, localOverlay.config);
68913
+ return;
68914
+ }
68492
68915
  const result = await io.remote.request(command, {
68493
68916
  caplet,
68494
68917
  request
@@ -68501,12 +68924,119 @@ async function executeOperation(caplet, request, io) {
68501
68924
  if (isPlainObject(result) && result.isError === true) io.setExitCode(1);
68502
68925
  return;
68503
68926
  }
68927
+ await executeLocalOperation(caplet, request, io);
68928
+ }
68929
+ function loadLocalOverlayForCli(io, writeErr) {
68930
+ const env = io.env ?? process.env;
68931
+ const overlay = loadLocalOverlayConfigWithSources(resolveConfigPath(envConfigPath(env)), envProjectConfigPath(env));
68932
+ for (const warning of overlay.warnings) writeErr(`Warning: ${warning.kind} at ${warning.path}: ${warning.message}\n`);
68933
+ return overlay;
68934
+ }
68935
+ function tryLoadLocalOverlayForCli(io, writeErr) {
68936
+ try {
68937
+ return loadLocalOverlayForCli(io, writeErr);
68938
+ } catch (error) {
68939
+ writeErr(`Warning: Could not load local Caplets overlay: ${formatErrorMessage(error)}\n`);
68940
+ return loadPartialLocalOverlayForCli(io, writeErr);
68941
+ }
68942
+ }
68943
+ function loadPartialLocalOverlayForCli(io, writeErr) {
68944
+ const env = io.env ?? process.env;
68945
+ const configPath = resolveConfigPath(envConfigPath(env));
68946
+ const projectConfigPath = envProjectConfigPath(env);
68947
+ const absentProjectPath = join(dirname(configPath), ".caplets-overlay-recovery", "config.json");
68948
+ const absentGlobalPath = join(dirname(projectConfigPath), ".caplets-overlay-recovery", "config.json");
68949
+ const globalOverlay = tryLoadPartialOverlayLayer("global", configPath, absentProjectPath, writeErr);
68950
+ const projectOverlay = tryLoadPartialOverlayLayer("project", absentGlobalPath, projectConfigPath, writeErr);
68951
+ if (!globalOverlay) return projectOverlay;
68952
+ if (!projectOverlay) return globalOverlay;
68953
+ return mergePartialLocalOverlays(globalOverlay, projectOverlay);
68954
+ }
68955
+ function tryLoadPartialOverlayLayer(label, configPath, projectConfigPath, writeErr) {
68956
+ try {
68957
+ const overlay = loadLocalOverlayConfigWithSources(configPath, projectConfigPath);
68958
+ for (const warning of overlay.warnings) writeErr(`Warning: ${warning.kind} at ${warning.path}: ${warning.message}\n`);
68959
+ return overlay;
68960
+ } catch (error) {
68961
+ writeErr(`Warning: Could not load ${label} Caplets overlay: ${formatErrorMessage(error)}\n`);
68962
+ return;
68963
+ }
68964
+ }
68965
+ function mergePartialLocalOverlays(globalOverlay, projectOverlay) {
68966
+ const config = { ...globalOverlay.config };
68967
+ const sources = { ...globalOverlay.sources };
68968
+ const shadows = { ...globalOverlay.shadows };
68969
+ for (const kind of capletConfigKinds) config[kind] = { ...globalOverlay.config[kind] };
68970
+ for (const kind of capletConfigKinds) for (const id of Object.keys(projectOverlay.config[kind])) {
68971
+ removeCapletFromPartialOverlay(config, sources, shadows, id);
68972
+ config[kind][id] = projectOverlay.config[kind][id];
68973
+ }
68974
+ for (const [id, source] of Object.entries(projectOverlay.sources)) sources[id] = source;
68975
+ for (const [id, shadowedSources] of Object.entries(projectOverlay.shadows)) shadows[id] = [...shadows[id] ?? [], ...shadowedSources];
68976
+ return {
68977
+ config,
68978
+ sources,
68979
+ shadows,
68980
+ warnings: [...globalOverlay.warnings, ...projectOverlay.warnings]
68981
+ };
68982
+ }
68983
+ const capletConfigKinds = [
68984
+ "mcpServers",
68985
+ "openapiEndpoints",
68986
+ "graphqlEndpoints",
68987
+ "httpApis",
68988
+ "cliTools",
68989
+ "capletSets"
68990
+ ];
68991
+ function removeCapletFromPartialOverlay(config, sources, shadows, id) {
68992
+ for (const kind of capletConfigKinds) delete config[kind][id];
68993
+ if (sources[id]) shadows[id] = [...shadows[id] ?? [], sources[id]];
68994
+ delete sources[id];
68995
+ }
68996
+ function formatErrorMessage(error) {
68997
+ return error instanceof Error ? error.message : String(error);
68998
+ }
68999
+ function envProjectConfigPath(env) {
69000
+ return env.CAPLETS_PROJECT_CONFIG?.trim() || resolveProjectConfigPath();
69001
+ }
69002
+ function envProjectCapletsRoot(env) {
69003
+ const projectConfigPath = env.CAPLETS_PROJECT_CONFIG?.trim();
69004
+ return projectConfigPath ? dirname(projectConfigPath) : resolveProjectCapletsRoot();
69005
+ }
69006
+ function mergeRemoteAndLocalRows(remoteRows, localOverlay, options) {
69007
+ const rows = /* @__PURE__ */ new Map();
69008
+ for (const row of remoteRows) rows.set(row.server, {
69009
+ ...row,
69010
+ source: "remote"
69011
+ });
69012
+ if (!localOverlay) return [...rows.values()].filter((row) => options.includeDisabled || !row.disabled).sort((left, right) => left.server.localeCompare(right.server));
69013
+ for (const row of listCaplets(localOverlay, { includeDisabled: true })) {
69014
+ if (rows.get(row.server)) {
69015
+ if (row.disabled) continue;
69016
+ options.writeErr(`Warning: ${formatOverlaySource(row.source)} Caplet ${row.server} shadows remote Caplet\n`);
69017
+ }
69018
+ rows.set(row.server, row);
69019
+ }
69020
+ return [...rows.values()].filter((row) => options.includeDisabled || !row.disabled).sort((left, right) => left.server.localeCompare(right.server));
69021
+ }
69022
+ function formatOverlaySource(kind) {
69023
+ if (kind.startsWith("project")) return "project";
69024
+ if (kind.startsWith("global")) return "global";
69025
+ return kind;
69026
+ }
69027
+ function hasEnabledCaplet(config, id) {
69028
+ const caplet = config.mcpServers[id] ?? config.openapiEndpoints[id] ?? config.graphqlEndpoints[id] ?? config.httpApis[id] ?? config.cliTools[id] ?? config.capletSets[id];
69029
+ return Boolean(caplet && !caplet.disabled);
69030
+ }
69031
+ async function executeLocalOperation(caplet, request, io, config) {
68504
69032
  const configPath = envConfigPath(io.env ?? process.env);
68505
69033
  const engine = new CapletsEngine({
68506
69034
  ...configPath ? { configPath } : {},
69035
+ projectConfigPath: envProjectConfigPath(io.env ?? process.env),
68507
69036
  ...io.authDir ? { authDir: io.authDir } : {},
68508
69037
  watch: false,
68509
- writeErr: io.writeErr
69038
+ writeErr: io.writeErr,
69039
+ ...config ? { configLoader: () => config } : {}
68510
69040
  });
68511
69041
  try {
68512
69042
  const result = await engine.execute(caplet, request);
@@ -68861,8 +69391,8 @@ function schemaSummary(schema) {
68861
69391
  required.length > 0 ? `required ${required.join(", ")}` : "no required fields"
68862
69392
  ].filter((part) => Boolean(part)).join("; ");
68863
69393
  }
68864
- function addDestinationRoot(options, configPath) {
68865
- return options.global ? resolveCapletsRoot(resolveConfigPath(configPath)) : resolveProjectCapletsRoot();
69394
+ function addDestinationRoot(target, configPath, env) {
69395
+ return target === "global" ? resolveCapletsRoot(resolveConfigPath(configPath)) : envProjectCapletsRoot(env);
68866
69396
  }
68867
69397
  function writeAddResult(writeOut, label, result) {
68868
69398
  if (result.path) {
@@ -68873,15 +69403,18 @@ function writeAddResult(writeOut, label, result) {
68873
69403
  }
68874
69404
  //#endregion
68875
69405
  //#region package.json
68876
- var version = "0.17.3";
69406
+ var version = "0.17.5";
68877
69407
  //#endregion
68878
69408
  //#region src/index.ts
68879
69409
  async function main() {
68880
69410
  await runCli(process.argv.slice(2), { version });
68881
69411
  }
68882
69412
  main().catch((error) => {
68883
- console.error(error);
69413
+ process.stderr.write(`${errorMessage(error)}\n`);
68884
69414
  process.exit(1);
68885
69415
  });
69416
+ function errorMessage(error) {
69417
+ return error instanceof Error ? error.message : String(error);
69418
+ }
68886
69419
  //#endregion
68887
69420
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "caplets",
3
- "version": "0.17.3",
3
+ "version": "0.17.5",
4
4
  "description": "Progressive disclosure gateway CLI for MCP servers and native Caplets adapters.",
5
5
  "keywords": [
6
6
  "caplets",
@@ -34,11 +34,11 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@modelcontextprotocol/sdk": "^1.29.0",
37
- "@caplets/core": "0.18.3"
37
+ "@caplets/core": "0.18.5"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/node": "^25.9.1",
41
- "@typescript/native-preview": "7.0.0-dev.20260519.1",
41
+ "@typescript/native-preview": "7.0.0-dev.20260523.1",
42
42
  "rolldown": "^1.0.2",
43
43
  "typescript": "^6.0.3",
44
44
  "vitest": "^4.1.7"
@@ -47,8 +47,9 @@
47
47
  "node": ">=22"
48
48
  },
49
49
  "scripts": {
50
- "build": "rm -rf dist && rolldown -c",
51
- "build:watch": "rm -rf dist && rolldown -c --watch",
50
+ "clean": "rm -rf dist",
51
+ "build": "pnpm run clean && rolldown -c",
52
+ "build:watch": "pnpm run clean && rolldown -c --watch",
52
53
  "typecheck": "tsgo --noEmit",
53
54
  "test": "vitest run"
54
55
  }