caplets 0.17.2 → 0.17.4

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 +47 -14
  2. package/dist/index.js +730 -138
  3. package/package.json +6 -5
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from "node:module";
3
- import { accessSync, chmodSync, closeSync, constants, cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, renameSync, rmSync, statSync, watch, writeFileSync, writeSync } from "node:fs";
4
- import minpath, { basename, delimiter, dirname, extname, isAbsolute, join, parse, posix, relative, resolve, win32 } from "node:path";
3
+ import { accessSync, chmodSync, closeSync, constants, copyFileSync, cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, watch, writeFileSync, writeSync } from "node:fs";
4
+ import minpath, { basename, delimiter, dirname, extname, isAbsolute, join, parse, posix, relative, resolve, sep, win32 } from "node:path";
5
5
  import { execFileSync, spawn } from "node:child_process";
6
6
  import process$1, { stdin, stdout } from "node:process";
7
7
  import { fileURLToPath } from "node:url";
@@ -4709,7 +4709,7 @@ function generatedToolInputJsonSchema() {
4709
4709
  return generatedToolInputJsonSchemaForCaplet({ backend: "tool" });
4710
4710
  }
4711
4711
  //#endregion
4712
- //#region ../core/dist/options-bnsSREid.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(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(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;
@@ -56439,8 +56702,21 @@ async function loadOpenApiSource(endpoint, authDir) {
56439
56702
  if (endpoint.specPath) return endpoint.specPath;
56440
56703
  if (!endpoint.specUrl) throw new CapletsError("CONFIG_INVALID", `${endpoint.server} is missing OpenAPI spec source`);
56441
56704
  if (!endpoint.baseUrl) throw new CapletsError("CONFIG_INVALID", `${endpoint.server} must configure baseUrl when using remote specUrl`);
56442
- const response = await fetchWithLimit(endpoint.specUrl, endpoint.requestTimeoutMs, shouldSendSpecAuth(endpoint) ? authHeaders(endpoint, authDir) : {});
56443
- return JSON.parse(response);
56705
+ return parseOpenApiSourceText(await fetchWithLimit(endpoint.specUrl, endpoint.requestTimeoutMs, shouldSendSpecAuth(endpoint) ? authHeaders(endpoint, authDir) : {}));
56706
+ }
56707
+ function parseOpenApiSourceText(source) {
56708
+ let parsed;
56709
+ try {
56710
+ parsed = JSON.parse(source);
56711
+ } catch (jsonError) {
56712
+ try {
56713
+ parsed = (0, import_dist$1.parse)(source);
56714
+ } catch {
56715
+ throw jsonError instanceof Error ? jsonError : new Error(String(jsonError));
56716
+ }
56717
+ }
56718
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("OpenAPI source must parse to an object");
56719
+ return parsed;
56444
56720
  }
56445
56721
  function extractOperations(endpoint, document) {
56446
56722
  const operations = [];
@@ -56458,6 +56734,7 @@ function extractOperations(endpoint, document) {
56458
56734
  const requestBody = requestBodyFor(operation);
56459
56735
  const outputSchema = outputSchemaFor(operation);
56460
56736
  const baseUrl = endpoint.baseUrl ?? firstServerUrl(document);
56737
+ const staticHeaders = staticHeaderDefaultsFor(endpoint, parameters);
56461
56738
  validateOperationBaseUrl(endpoint, baseUrl);
56462
56739
  operations.push({
56463
56740
  name,
@@ -56465,15 +56742,32 @@ function extractOperations(endpoint, document) {
56465
56742
  path,
56466
56743
  ...typeof operation.summary === "string" ? { summary: operation.summary } : {},
56467
56744
  ...typeof operation.description === "string" ? { description: operation.description } : {},
56468
- inputSchema: inputSchemaFor(parameters, requestBody),
56745
+ inputSchema: inputSchemaFor(parameters, requestBody, staticHeaders),
56469
56746
  ...outputSchema ? { outputSchema } : {},
56470
56747
  ...requestBody?.contentType ? { requestBodyContentType: requestBody.contentType } : {},
56471
- ...baseUrl ? { baseUrl } : {}
56748
+ ...baseUrl ? { baseUrl } : {},
56749
+ ...Object.keys(staticHeaders).length ? { staticHeaders } : {}
56472
56750
  });
56473
56751
  }
56474
56752
  }
56475
56753
  return operations.sort((left, right) => left.name.localeCompare(right.name));
56476
56754
  }
56755
+ function staticHeaderDefaultsFor(endpoint, parameters) {
56756
+ const configuredHeaderNames = configuredAuthHeaderNames(endpoint);
56757
+ const headers = {};
56758
+ for (const parameter of parameters) {
56759
+ if (parameter?.in !== "header" || typeof parameter.name !== "string") continue;
56760
+ const normalized = parameter.name.toLowerCase();
56761
+ if (configuredHeaderNames.has(normalized) || FORBIDDEN_ARGUMENT_HEADERS.has(normalized) && normalized !== "accept") continue;
56762
+ const defaultValue = parameter.schema?.default;
56763
+ if ([
56764
+ "string",
56765
+ "number",
56766
+ "boolean"
56767
+ ].includes(typeof defaultValue)) headers[parameter.name] = String(defaultValue);
56768
+ }
56769
+ return headers;
56770
+ }
56477
56771
  function requestBodyFor(operation) {
56478
56772
  const requestBody = operation.requestBody;
56479
56773
  if (!requestBody || typeof requestBody !== "object") return;
@@ -56534,19 +56828,20 @@ function structuredOutputSchema(bodySchema) {
56534
56828
  }
56535
56829
  };
56536
56830
  }
56537
- function inputSchemaFor(parameters, requestBody) {
56831
+ function inputSchemaFor(parameters, requestBody, staticHeaders = {}) {
56538
56832
  const schema = {
56539
56833
  type: "object",
56540
56834
  additionalProperties: false,
56541
56835
  properties: {}
56542
56836
  };
56543
56837
  const required = [];
56838
+ const protectedStaticHeaders = new Set(Object.keys(staticHeaders).map((key) => key.toLowerCase()).filter((key) => FORBIDDEN_ARGUMENT_HEADERS.has(key)));
56544
56839
  for (const location of [
56545
56840
  "path",
56546
56841
  "query",
56547
56842
  "header"
56548
56843
  ]) {
56549
- const locationParameters = parameters.filter((parameter) => parameter?.in === location);
56844
+ const locationParameters = parameters.filter((parameter) => parameter?.in === location && !(location === "header" && protectedStaticHeaders.has(parameter.name?.toLowerCase())));
56550
56845
  if (locationParameters.length === 0) continue;
56551
56846
  const nestedRequired = locationParameters.filter((parameter) => parameter.required === true || location === "path").map((parameter) => parameter.name);
56552
56847
  schema.properties[location] = {
@@ -56585,7 +56880,8 @@ function buildRequest(endpoint, operation, args, authDir) {
56585
56880
  for (const [key, value] of Object.entries(asRecord(args.query))) if (value !== void 0 && value !== null) url.searchParams.append(key, serializeHttpValue("query", key, value));
56586
56881
  const headers = new Headers();
56587
56882
  applyAuth(headers, endpoint, authDir);
56588
- const configuredHeaderNames = endpoint.auth.type === "headers" ? new Set(Object.keys(endpoint.auth.headers).map((key) => key.toLowerCase())) : /* @__PURE__ */ new Set();
56883
+ const configuredHeaderNames = configuredAuthHeaderNames(endpoint);
56884
+ for (const [key, value] of Object.entries(operation.staticHeaders ?? {})) if (!headers.has(key) && !configuredHeaderNames.has(key.toLowerCase())) headers.set(key, value);
56589
56885
  for (const [key, value] of Object.entries(asRecord(args.header))) if (value !== void 0 && value !== null) {
56590
56886
  const normalized = key.toLowerCase();
56591
56887
  if (FORBIDDEN_ARGUMENT_HEADERS.has(normalized) || configuredHeaderNames.has(normalized)) throw new CapletsError("REQUEST_INVALID", `Header ${key} cannot be supplied by arguments`);
@@ -56631,6 +56927,9 @@ function asRecord(value) {
56631
56927
  function applyAuth(headers, endpoint, authDir) {
56632
56928
  for (const [key, value] of Object.entries(authHeaders(endpoint, authDir))) headers.set(key, value);
56633
56929
  }
56930
+ function configuredAuthHeaderNames(endpoint) {
56931
+ return endpoint.auth.type === "headers" ? new Set(Object.keys(endpoint.auth.headers).map((key) => key.toLowerCase())) : /* @__PURE__ */ new Set();
56932
+ }
56634
56933
  function authHeaders(endpoint, authDir) {
56635
56934
  switch (endpoint.auth.type) {
56636
56935
  case "none": return {};
@@ -57558,6 +57857,7 @@ var CapletsEngine = class {
57558
57857
  watchDebounceMs;
57559
57858
  watchEnabled;
57560
57859
  writeErr;
57860
+ configLoader;
57561
57861
  reloadListeners = /* @__PURE__ */ new Set();
57562
57862
  watchers = [];
57563
57863
  reloadTimer;
@@ -57570,7 +57870,8 @@ var CapletsEngine = class {
57570
57870
  configPath: resolveConfigPath(options.configPath),
57571
57871
  projectConfigPath: options.projectConfigPath ?? resolveProjectConfigPath()
57572
57872
  };
57573
- 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);
57574
57875
  this.registry = new ServerRegistry(config);
57575
57876
  this.downstream = new DownstreamManager(this.registry, selectAuthOptions(options.authDir));
57576
57877
  this.openapi = new OpenApiManager(this.registry, selectAuthOptions(options.authDir));
@@ -57625,7 +57926,7 @@ var CapletsEngine = class {
57625
57926
  }
57626
57927
  }
57627
57928
  async completeCliWords(words) {
57628
- const { completeCliWords } = await Promise.resolve().then(() => completion_L23s2FGB_exports).then((n) => n.r);
57929
+ const { completeCliWords } = await Promise.resolve().then(() => completion_DRPTunQd_exports).then((n) => n.r);
57629
57930
  return await completeCliWords(words, {
57630
57931
  config: this.registry.config,
57631
57932
  managers: {
@@ -57685,7 +57986,7 @@ var CapletsEngine = class {
57685
57986
  if (this.closed) return false;
57686
57987
  let nextConfig;
57687
57988
  try {
57688
- nextConfig = loadConfig(this.paths.configPath, this.paths.projectConfigPath);
57989
+ nextConfig = this.configLoader(this.paths.configPath, this.paths.projectConfigPath);
57689
57990
  } catch (error) {
57690
57991
  this.writeErr(`Caplets config reload failed; keeping last known-good config.\n`);
57691
57992
  this.writeErr(`${JSON.stringify(toSafeError(error, "CONFIG_INVALID"), null, 2)}\n`);
@@ -57963,8 +58264,8 @@ function hasEnv$1(value) {
57963
58264
  return value !== void 0 && value.trim() !== "";
57964
58265
  }
57965
58266
  //#endregion
57966
- //#region ../core/dist/completion-L23s2FGB.js
57967
- var completion_L23s2FGB_exports = /* @__PURE__ */ __exportAll$1({
58267
+ //#region ../core/dist/completion-DRPTunQd.js
58268
+ var completion_DRPTunQd_exports = /* @__PURE__ */ __exportAll$1({
57968
58269
  a: () => formatCapletList,
57969
58270
  c: () => resolveCliConfigPaths,
57970
58271
  i: () => trailingSpaceCompletionToken,
@@ -58086,7 +58387,8 @@ function allCaplets(config) {
58086
58387
  ...Object.values(config.openapiEndpoints),
58087
58388
  ...Object.values(config.graphqlEndpoints),
58088
58389
  ...Object.values(config.httpApis),
58089
- ...Object.values(config.cliTools)
58390
+ ...Object.values(config.cliTools),
58391
+ ...Object.values(config.capletSets)
58090
58392
  ];
58091
58393
  }
58092
58394
  function formatCapletList(rows, format = "plain") {
@@ -58138,15 +58440,15 @@ function formatSourceKind(kind) {
58138
58440
  if (kind.startsWith("global")) return "global";
58139
58441
  return kind;
58140
58442
  }
58141
- function resolveCliConfigPaths(envConfigPath, authDir) {
58443
+ function resolveCliConfigPaths(envConfigPath, projectConfigPath = resolveProjectConfigPath(), authDir) {
58142
58444
  const configPath = resolveConfigPath(envConfigPath);
58143
58445
  const effectiveAuthDir = authDir ?? DEFAULT_AUTH_DIR;
58144
58446
  return {
58145
58447
  userConfig: configPath,
58146
- projectConfig: resolveProjectConfigPath(),
58448
+ projectConfig: projectConfigPath,
58147
58449
  userRoot: resolveCapletsRoot(configPath),
58148
58450
  stateRoot: dirname(effectiveAuthDir),
58149
- projectRoot: resolveProjectCapletsRoot(),
58451
+ projectRoot: dirname(projectConfigPath),
58150
58452
  authDir: effectiveAuthDir,
58151
58453
  envConfig: envConfigPath ?? null
58152
58454
  };
@@ -59827,7 +60129,7 @@ const EMPTY_COMPLETION_RESULT = { completion: {
59827
60129
  values: [],
59828
60130
  hasMore: false
59829
60131
  } };
59830
- var version$1 = "0.18.2";
60132
+ var version$1 = "0.18.4";
59831
60133
  var CapletsMcpSession = class {
59832
60134
  engine;
59833
60135
  server;
@@ -63343,7 +63645,7 @@ function isUrlLike(value) {
63343
63645
  return /^https?:\/\//i.test(value);
63344
63646
  }
63345
63647
  async function loginAuth(serverId, options) {
63346
- const server = findAuthTarget(serverId, loadConfig(options.configPath));
63648
+ const server = findAuthTarget(serverId, options.config ?? loadConfig(options.configPath));
63347
63649
  assertLoginTarget(server, serverId);
63348
63650
  try {
63349
63651
  const flowOptions = {
@@ -63365,40 +63667,64 @@ function logoutAuth(serverId, options) {
63365
63667
  else options.writeOut(`No OAuth credentials found for \`${serverId}\`.\n`);
63366
63668
  }
63367
63669
  function logoutAuthResult(serverId, options) {
63368
- assertLoginTarget(findAuthTarget(serverId, loadConfig(options.configPath)), serverId);
63670
+ assertLoginTarget(findAuthTarget(serverId, options.config ?? loadConfig(options.configPath)), serverId);
63369
63671
  return {
63370
63672
  server: serverId,
63371
63673
  deleted: deleteTokenBundle(serverId, options.authDir)
63372
63674
  };
63373
63675
  }
63374
- function listAuth(options) {
63375
- const rows = listAuthRows(options);
63376
- const format = options.format ?? "plain";
63377
- if (format === "json") {
63378
- options.writeOut(`${JSON.stringify(rows, null, 2)}\n`);
63379
- 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;
63380
63698
  }
63381
- options.writeOut(formatAuthRows(rows, format));
63382
63699
  }
63383
- function listAuthRows(options) {
63384
- return authTargets(loadConfig(options.configPath)).sort((left, right) => left.server.localeCompare(right.server)).map((server) => {
63385
- 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);
63386
63707
  const status = !bundle ? "missing" : isTokenBundleExpired(bundle) ? "expired" : "authenticated";
63387
63708
  return {
63388
63709
  server: server.server,
63389
63710
  status,
63390
63711
  ...bundle?.expiresAt ? { expiresAt: bundle.expiresAt } : {},
63391
- ...bundle?.scope ? { scope: bundle.scope } : {}
63712
+ ...bundle?.scope ? { scope: bundle.scope } : {},
63713
+ ...server.source ? { source: server.source } : {}
63392
63714
  };
63393
63715
  });
63394
63716
  }
63395
63717
  function formatAuthRows(rows, format) {
63396
- 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";
63397
63719
  let output = "";
63398
63720
  if (format === "markdown") output += "## OAuth credentials\n\n";
63399
63721
  else output += "OAuth credentials\n\n";
63400
63722
  for (const row of rows) {
63401
- 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("; ");
63402
63728
  if (format === "markdown") {
63403
63729
  output += `- \`${row.server}\` — ${row.status}${details ? ` (${details})` : ""}\n`;
63404
63730
  continue;
@@ -63406,6 +63732,7 @@ function formatAuthRows(rows, format) {
63406
63732
  output += [
63407
63733
  row.server,
63408
63734
  ` Status: ${row.status}`,
63735
+ ...row.source ? [` Source: ${row.source}`] : [],
63409
63736
  ...row.expiresAt ? [` Expires: ${row.expiresAt}`] : [],
63410
63737
  ...row.scope ? [` Scope: ${row.scope}`] : []
63411
63738
  ].join("\n") + "\n\n";
@@ -63626,12 +63953,14 @@ function rejectCrossKindDestinationCollision(plan, destinationRoot) {
63626
63953
  function installPlan(caplet, options) {
63627
63954
  const isDirectory = basename(caplet.path) === "CAPLET.md";
63628
63955
  const sourcePath = isDirectory ? dirname(caplet.path) : caplet.path;
63956
+ const sourceBoundary = dirname(sourcePath);
63629
63957
  const sourcePathRelative = relative(options.repoRoot, sourcePath);
63630
63958
  const destination = isDirectory ? join(options.destinationRoot, caplet.id) : join(options.destinationRoot, `${caplet.id}.md`);
63631
63959
  return {
63632
63960
  id: caplet.id,
63633
63961
  source: `${options.sourceId}#${sourcePathRelative}`,
63634
63962
  sourcePath,
63963
+ sourceBoundary,
63635
63964
  destination,
63636
63965
  kind: isDirectory ? "directory" : "file"
63637
63966
  };
@@ -63693,16 +64022,40 @@ function removeInstallPath(path, label, force) {
63693
64022
  }
63694
64023
  function copyInstallPath(plan) {
63695
64024
  try {
64025
+ if (plan.kind === "directory") {
64026
+ copyDirectoryCaplet(plan.sourcePath, plan.destination, realpathSync(plan.sourceBoundary));
64027
+ return;
64028
+ }
63696
64029
  cpSync(plan.sourcePath, plan.destination, {
63697
- recursive: plan.kind === "directory",
64030
+ recursive: false,
63698
64031
  force: false,
63699
64032
  errorOnExist: true
63700
64033
  });
63701
64034
  } catch (error) {
64035
+ if (error instanceof CapletsError) throw error;
63702
64036
  if (isFsError(error, "EEXIST") || isFsError(error, "EISDIR")) throw new CapletsError("CONFIG_EXISTS", `Caplet ${plan.id} already exists at ${plan.destination}; pass --force to overwrite it`, toSafeError(error));
63703
64037
  throw new CapletsError("CONFIG_INVALID", `Could not install Caplet ${plan.id} to ${plan.destination}`, toSafeError(error));
63704
64038
  }
63705
64039
  }
64040
+ function copyDirectoryCaplet(source, destination, sourceBoundary, seenDirectories = /* @__PURE__ */ new Set()) {
64041
+ const resolvedSource = lstatSync(source).isSymbolicLink() ? resolveDirectoryCapletSymlink(source, sourceBoundary) : source;
64042
+ if (statSync(resolvedSource).isDirectory()) {
64043
+ const realDirectory = realpathSync(resolvedSource);
64044
+ if (seenDirectories.has(realDirectory)) throw new CapletsError("CONFIG_INVALID", `Directory Caplet symlink ${source} creates a copy cycle`);
64045
+ const childSeenDirectories = new Set(seenDirectories);
64046
+ childSeenDirectories.add(realDirectory);
64047
+ mkdirSync(destination);
64048
+ for (const entry of readdirSync(resolvedSource)) copyDirectoryCaplet(join(resolvedSource, entry), join(destination, entry), sourceBoundary, childSeenDirectories);
64049
+ return;
64050
+ }
64051
+ copyFileSync(resolvedSource, destination);
64052
+ }
64053
+ function resolveDirectoryCapletSymlink(source, sourceBoundary) {
64054
+ const target = readlinkSync(source);
64055
+ const resolvedTarget = realpathSync(isAbsolute(target) ? target : resolve(dirname(source), target));
64056
+ if (resolvedTarget !== sourceBoundary && !resolvedTarget.startsWith(`${sourceBoundary}${sep}`)) throw new CapletsError("CONFIG_INVALID", `Directory Caplet symlink ${source} resolves outside source Caplets boundary`);
64057
+ return resolvedTarget;
64058
+ }
63706
64059
  function isFsError(error, code) {
63707
64060
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
63708
64061
  }
@@ -67880,15 +68233,29 @@ function createProgram(io = {}) {
67880
68233
  const completionWords = normalizeCompletionWords(words);
67881
68234
  let suggestions = [];
67882
68235
  try {
67883
- suggestions = remote ? await remote.request("complete_cli", {
67884
- shell,
67885
- words: completionWords
67886
- }) : 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, {
67887
68250
  ...configPath ? { configPath } : {},
68251
+ projectConfigPath: envProjectConfigPath(env),
67888
68252
  ...io.authDir ? { authDir: io.authDir } : {}
67889
68253
  });
67890
68254
  } catch {
67891
- suggestions = remote ? [] : await completeCliWords(completionWords, configPath ? { configPath } : {});
68255
+ suggestions = remote ? [] : await completeCliWords(completionWords, {
68256
+ ...configPath ? { configPath } : {},
68257
+ projectConfigPath: envProjectConfigPath(env)
68258
+ });
67892
68259
  }
67893
68260
  if (suggestions.length > 0) writeOut(`${suggestions.join("\n")}\n`);
67894
68261
  });
@@ -67900,23 +68267,26 @@ function createProgram(io = {}) {
67900
68267
  ...io.authDir ? { authDir: io.authDir } : {}
67901
68268
  }, writeErr)))(resolved);
67902
68269
  });
67903
- program.command(cliCommands.init).description("Create a starter Caplets config file.").option("--force", "overwrite an existing config file").action(async (options) => {
67904
- const remote = remoteClientForCli(io);
67905
- if (remote) {
67906
- 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`);
67907
68274
  return;
67908
68275
  }
67909
- const configPath = currentConfigPath();
67910
- writeOut(`Created Caplets config at ${initConfig({
67911
- ...configPath ? { path: configPath } : {},
68276
+ const path = initConfig({
68277
+ path: target === "global" ? resolveConfigPath(currentConfigPath()) : envProjectConfigPath(env),
67912
68278
  force: Boolean(options.force)
67913
- })}\n`);
68279
+ });
68280
+ writeOut(`Created ${localMutationTargetLabel(target, io)}Caplets config at ${path}\n`);
67914
68281
  });
67915
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) => {
67916
68283
  const includeDisabled = Boolean(options.all);
67917
68284
  const remote = remoteClientForCli(io);
67918
68285
  if (remote) {
67919
- const rows = await remote.request("list", { includeDisabled });
68286
+ const rows = mergeRemoteAndLocalRows(await remote.request("list", { includeDisabled }), tryLoadLocalOverlayForCli(io, writeErr), {
68287
+ includeDisabled,
68288
+ writeErr
68289
+ });
67920
68290
  if (options.json || options.format === "json") {
67921
68291
  writeOut(`${JSON.stringify(rows, null, 2)}\n`);
67922
68292
  return;
@@ -67924,18 +68294,17 @@ function createProgram(io = {}) {
67924
68294
  writeOut(formatCapletList(rows, options.format ?? "plain"));
67925
68295
  return;
67926
68296
  }
67927
- const rows = listCaplets(loadConfigWithSources(currentConfigPath()), { includeDisabled });
68297
+ const rows = listCaplets(loadConfigWithSources(currentConfigPath(), envProjectConfigPath(env)), { includeDisabled });
67928
68298
  if (options.json || options.format === "json") {
67929
68299
  writeOut(`${JSON.stringify(rows, null, 2)}\n`);
67930
68300
  return;
67931
68301
  }
67932
68302
  writeOut(formatCapletList(rows, options.format ?? "plain"));
67933
68303
  });
67934
- 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) => {
67935
- const remote = remoteClientForCli(io);
67936
- if (remote) {
67937
- if (options.global) writeErr("Warning: --global is not supported in remote mode; the server controls the installation destination.\n");
67938
- 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", {
67939
68308
  repo,
67940
68309
  capletIds,
67941
68310
  force: Boolean(options.force)
@@ -67946,15 +68315,15 @@ function createProgram(io = {}) {
67946
68315
  const result = installCaplets(repo, {
67947
68316
  capletIds,
67948
68317
  force: Boolean(options.force),
67949
- destinationRoot: options.global ? resolveCapletsRoot(resolveConfigPath(currentConfigPath())) : resolveProjectCapletsRoot()
68318
+ destinationRoot: target === "global" ? resolveCapletsRoot(resolveConfigPath(currentConfigPath())) : envProjectCapletsRoot(env)
67950
68319
  });
67951
- 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`);
67952
68321
  });
67953
68322
  const add = program.command(cliCommands.add).description("Add generated Caplet files.");
67954
- 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) => {
67955
- const remote = remoteClientForCli(io);
67956
- if (remote) {
67957
- 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", {
67958
68327
  kind: "cli",
67959
68328
  id,
67960
68329
  options: remoteAddOptions(options)
@@ -67963,73 +68332,77 @@ function createProgram(io = {}) {
67963
68332
  }
67964
68333
  const result = addCliCaplet(id, {
67965
68334
  ...options,
67966
- destinationRoot: options.global ? resolveCapletsRoot(resolveConfigPath(currentConfigPath())) : resolveProjectCapletsRoot()
68335
+ destinationRoot: target === "global" ? resolveCapletsRoot(resolveConfigPath(currentConfigPath())) : envProjectCapletsRoot(env)
67967
68336
  });
67968
68337
  if (result.path) {
67969
- writeOut(`Wrote CLI Caplet to ${result.path}\n`);
68338
+ writeOut(`Wrote ${localMutationTargetLabel(target, io)}CLI Caplet to ${result.path}\n`);
67970
68339
  return;
67971
68340
  }
67972
68341
  writeOut(result.text);
67973
68342
  });
67974
- 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) => {
67975
- const remote = remoteClientForCli(io);
67976
- if (remote) {
67977
- 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", {
67978
68347
  kind: "mcp",
67979
68348
  id,
67980
68349
  options: remoteAddOptions(options)
67981
68350
  }));
67982
68351
  return;
67983
68352
  }
67984
- writeAddResult(writeOut, "MCP", addMcpCaplet(id, {
68353
+ const result = addMcpCaplet(id, {
67985
68354
  ...options,
67986
- destinationRoot: addDestinationRoot(options, currentConfigPath())
67987
- }));
68355
+ destinationRoot: addDestinationRoot(target, currentConfigPath(), env)
68356
+ });
68357
+ writeAddResult(writeOut, `${localMutationTargetLabel(target, io)}MCP`, result);
67988
68358
  });
67989
- 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) => {
67990
- const remote = remoteClientForCli(io);
67991
- if (remote) {
67992
- 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", {
67993
68363
  kind: "openapi",
67994
68364
  id,
67995
68365
  options: remoteAddOptions(options)
67996
68366
  }));
67997
68367
  return;
67998
68368
  }
67999
- writeAddResult(writeOut, "OpenAPI", addOpenApiCaplet(id, {
68369
+ const result = addOpenApiCaplet(id, {
68000
68370
  ...options,
68001
- destinationRoot: addDestinationRoot(options, currentConfigPath())
68002
- }));
68371
+ destinationRoot: addDestinationRoot(target, currentConfigPath(), env)
68372
+ });
68373
+ writeAddResult(writeOut, `${localMutationTargetLabel(target, io)}OpenAPI`, result);
68003
68374
  });
68004
- 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) => {
68005
- const remote = remoteClientForCli(io);
68006
- if (remote) {
68007
- 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", {
68008
68379
  kind: "graphql",
68009
68380
  id,
68010
68381
  options: remoteAddOptions(options)
68011
68382
  }));
68012
68383
  return;
68013
68384
  }
68014
- writeAddResult(writeOut, "GraphQL", addGraphqlCaplet(id, {
68385
+ const result = addGraphqlCaplet(id, {
68015
68386
  ...options,
68016
- destinationRoot: addDestinationRoot(options, currentConfigPath())
68017
- }));
68387
+ destinationRoot: addDestinationRoot(target, currentConfigPath(), env)
68388
+ });
68389
+ writeAddResult(writeOut, `${localMutationTargetLabel(target, io)}GraphQL`, result);
68018
68390
  });
68019
- 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) => {
68020
- const remote = remoteClientForCli(io);
68021
- if (remote) {
68022
- 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", {
68023
68395
  kind: "http",
68024
68396
  id,
68025
68397
  options: remoteAddOptions(options)
68026
68398
  }));
68027
68399
  return;
68028
68400
  }
68029
- writeAddResult(writeOut, "HTTP", addHttpCaplet(id, {
68401
+ const result = addHttpCaplet(id, {
68030
68402
  ...options,
68031
- destinationRoot: addDestinationRoot(options, currentConfigPath())
68032
- }));
68403
+ destinationRoot: addDestinationRoot(target, currentConfigPath(), env)
68404
+ });
68405
+ writeAddResult(writeOut, `${localMutationTargetLabel(target, io)}HTTP`, result);
68033
68406
  });
68034
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) => {
68035
68408
  await executeOperation(caplet, { operation: "get_caplet" }, {
@@ -68231,7 +68604,7 @@ function createProgram(io = {}) {
68231
68604
  writeOut(`${resolveConfigPath(currentConfigPath())}\n`);
68232
68605
  });
68233
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) => {
68234
- const paths = resolveCliConfigPaths(currentConfigPath(), io.authDir);
68607
+ const paths = resolveCliConfigPaths(currentConfigPath(), envProjectConfigPath(env), io.authDir);
68235
68608
  if (options.json || options.format === "json") {
68236
68609
  writeOut(`${JSON.stringify(paths, null, 2)}\n`);
68237
68610
  return;
@@ -68239,60 +68612,59 @@ function createProgram(io = {}) {
68239
68612
  writeOut(formatConfigPaths(paths, options.format ?? "plain"));
68240
68613
  });
68241
68614
  const auth = program.command(cliCommands.auth).description("Manage OAuth credentials for remote servers.");
68242
- 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) => {
68243
- const remote = remoteClientForCli(io);
68244
- if (remote) {
68245
- const started = await remote.request("auth_login_start", { server: serverId });
68246
- if (started.authorizationUrl) {
68247
- writeOut(`Open this URL to authorize ${serverId}:\n${started.authorizationUrl}\n`);
68248
- if (options.open !== false) await openBrowser(started.authorizationUrl);
68249
- writeOut("Complete authentication in your browser. The server callback will store credentials.\n");
68250
- return;
68251
- }
68252
- 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);
68253
68619
  return;
68254
68620
  }
68255
68621
  const configPath = currentConfigPath();
68622
+ const projectConfigPath = envProjectConfigPath(env);
68256
68623
  await loginAuth(serverId, {
68257
68624
  noOpen: options.open === false,
68258
68625
  writeOut,
68259
68626
  writeErr,
68260
68627
  ...configPath ? { configPath } : {},
68628
+ ...projectConfigPath ? { projectConfigPath } : {},
68629
+ config: localAuthConfigForTarget({
68630
+ serverId,
68631
+ ...configPath ? { configPath } : {},
68632
+ ...projectConfigPath ? { projectConfigPath } : {},
68633
+ source: target
68634
+ }),
68261
68635
  ...io.authDir ? { authDir: io.authDir } : {}
68262
68636
  });
68263
68637
  });
68264
- auth.command("logout").description("Delete stored OAuth credentials for a server.").argument("<server>", "configured server ID").action(async (serverId) => {
68265
- const remote = remoteClientForCli(io);
68266
- if (remote) {
68267
- 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`);
68268
68642
  return;
68269
68643
  }
68270
68644
  const configPath = currentConfigPath();
68645
+ const projectConfigPath = envProjectConfigPath(env);
68271
68646
  logoutAuth(serverId, {
68272
68647
  writeOut,
68273
68648
  ...configPath ? { configPath } : {},
68649
+ config: localAuthConfigForTarget({
68650
+ serverId,
68651
+ ...configPath ? { configPath } : {},
68652
+ ...projectConfigPath ? { projectConfigPath } : {},
68653
+ source: target
68654
+ }),
68274
68655
  ...io.authDir ? { authDir: io.authDir } : {}
68275
68656
  });
68276
68657
  });
68277
- 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) => {
68278
68659
  const configPath = currentConfigPath();
68660
+ const projectConfigPath = envProjectConfigPath(env);
68279
68661
  const format = options.json || options.format === "json" ? "json" : options.format ?? "plain";
68280
- const remote = remoteClientForCli(io);
68281
- if (remote) {
68282
- const rows = await remote.request("auth_list", {});
68283
- if (format === "json") {
68284
- writeOut(`${JSON.stringify(rows, null, 2)}\n`);
68285
- return;
68286
- }
68287
- 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`);
68288
68665
  return;
68289
68666
  }
68290
- listAuth({
68291
- writeOut,
68292
- format,
68293
- ...configPath ? { configPath } : {},
68294
- ...io.authDir ? { authDir: io.authDir } : {}
68295
- });
68667
+ writeOut(formatAuthRows(rows, format));
68296
68668
  });
68297
68669
  return program;
68298
68670
  }
@@ -68336,12 +68708,90 @@ function remoteCommandForOperation(operation) {
68336
68708
  }
68337
68709
  }
68338
68710
  function remoteAddOptions(options) {
68339
- const { output, print, global, destinationRoot, ...remoteOptions } = options;
68340
- 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;
68341
68712
  if (print) throw new CapletsError("REQUEST_INVALID", "--print is not supported in remote mode; the server controls add output.");
68342
68713
  if (output !== void 0) throw new CapletsError("REQUEST_INVALID", "--output is not supported in remote mode; the server controls the add destination.");
68343
68714
  return remoteOptions;
68344
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
+ }
68345
68795
  function collect(value, previous) {
68346
68796
  previous.push(value);
68347
68797
  return previous;
@@ -68378,8 +68828,10 @@ function parseQualifiedTarget(capletOrTarget, toolArgument) {
68378
68828
  async function completeCliWordsLocally(words, options) {
68379
68829
  const engine = new CapletsEngine({
68380
68830
  ...options.configPath ? { configPath: options.configPath } : {},
68831
+ ...options.projectConfigPath ? { projectConfigPath: options.projectConfigPath } : {},
68381
68832
  ...options.authDir ? { authDir: options.authDir } : {},
68382
- watch: false
68833
+ watch: false,
68834
+ ...options.config ? { configLoader: () => options.config } : {}
68383
68835
  });
68384
68836
  try {
68385
68837
  return await engine.completeCliWords(words);
@@ -68387,6 +68839,34 @@ async function completeCliWordsLocally(words, options) {
68387
68839
  await engine.close();
68388
68840
  }
68389
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
+ }
68390
68870
  function parseCallToolArgs(value) {
68391
68871
  if (value === void 0) return {};
68392
68872
  let parsed;
@@ -68427,6 +68907,11 @@ function isPlainObject(value) {
68427
68907
  async function executeOperation(caplet, request, io) {
68428
68908
  const command = remoteCommandForOperation(request.operation);
68429
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
+ }
68430
68915
  const result = await io.remote.request(command, {
68431
68916
  caplet,
68432
68917
  request
@@ -68439,12 +68924,119 @@ async function executeOperation(caplet, request, io) {
68439
68924
  if (isPlainObject(result) && result.isError === true) io.setExitCode(1);
68440
68925
  return;
68441
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) {
68442
69032
  const configPath = envConfigPath(io.env ?? process.env);
68443
69033
  const engine = new CapletsEngine({
68444
69034
  ...configPath ? { configPath } : {},
69035
+ projectConfigPath: envProjectConfigPath(io.env ?? process.env),
68445
69036
  ...io.authDir ? { authDir: io.authDir } : {},
68446
69037
  watch: false,
68447
- writeErr: io.writeErr
69038
+ writeErr: io.writeErr,
69039
+ ...config ? { configLoader: () => config } : {}
68448
69040
  });
68449
69041
  try {
68450
69042
  const result = await engine.execute(caplet, request);
@@ -68799,8 +69391,8 @@ function schemaSummary(schema) {
68799
69391
  required.length > 0 ? `required ${required.join(", ")}` : "no required fields"
68800
69392
  ].filter((part) => Boolean(part)).join("; ");
68801
69393
  }
68802
- function addDestinationRoot(options, configPath) {
68803
- return options.global ? resolveCapletsRoot(resolveConfigPath(configPath)) : resolveProjectCapletsRoot();
69394
+ function addDestinationRoot(target, configPath, env) {
69395
+ return target === "global" ? resolveCapletsRoot(resolveConfigPath(configPath)) : envProjectCapletsRoot(env);
68804
69396
  }
68805
69397
  function writeAddResult(writeOut, label, result) {
68806
69398
  if (result.path) {
@@ -68811,7 +69403,7 @@ function writeAddResult(writeOut, label, result) {
68811
69403
  }
68812
69404
  //#endregion
68813
69405
  //#region package.json
68814
- var version = "0.17.2";
69406
+ var version = "0.17.4";
68815
69407
  //#endregion
68816
69408
  //#region src/index.ts
68817
69409
  async function main() {