@humain/terminal 0.1.0 → 0.2.1

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 (220) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/bundle/chunks/{anthropic-messages-XULXUBIG.js → anthropic-messages-QFCEKGI3.js} +1 -1
  3. package/dist/bundle/chunks/{azure-openai-responses-JEG2XOYC.js → azure-openai-responses-G4ZMHV37.js} +1 -1
  4. package/dist/bundle/chunks/chunk-7T6XCLJF.js +22 -0
  5. package/dist/bundle/chunks/chunk-D6HHRSB5.js +103 -0
  6. package/dist/bundle/chunks/chunk-EY4DWHQU.js +2 -0
  7. package/dist/bundle/chunks/{chunk-RM336IEI.js → chunk-I47ZWR55.js} +486 -509
  8. package/dist/bundle/chunks/{chunk-JUPSJTFQ.js → chunk-K4JAZYQH.js} +1 -1
  9. package/dist/bundle/chunks/{chunk-HPPQHK3J.js → chunk-KKIB2TVR.js} +1 -1
  10. package/dist/bundle/chunks/chunk-LI2YVPWP.js +2 -0
  11. package/dist/bundle/chunks/{chunk-BZQ53VGE.js → chunk-XSR434CO.js} +1 -1
  12. package/dist/bundle/chunks/compat-QCV26HNS.js +2 -0
  13. package/dist/bundle/chunks/dist-PRPIUY3U.js +2 -0
  14. package/dist/bundle/chunks/esm-E7UVEYTO.js +4 -0
  15. package/dist/bundle/chunks/{extract-TRW5FBY6.js → extract-CTIFQZDJ.js} +15 -14
  16. package/dist/bundle/chunks/{google-generative-ai-OX6VSCA6.js → google-generative-ai-RSUU4MWO.js} +1 -1
  17. package/dist/bundle/chunks/{google-vertex-GW76GSS2.js → google-vertex-CKQPOITO.js} +1 -1
  18. package/dist/bundle/chunks/host-WP3MVKS2.js +2 -0
  19. package/dist/bundle/chunks/{node-KOIP55YZ.js → node-NF43OPII.js} +1 -1
  20. package/dist/bundle/chunks/{node-XY3CBY3C.js → node-UMFM7CDZ.js} +1 -1
  21. package/dist/bundle/chunks/{openai-codex-responses-NPLWP6ZC.js → openai-codex-responses-S34V5ZSD.js} +1 -1
  22. package/dist/bundle/chunks/{openai-completions-OT2GTJKC.js → openai-completions-T2IGVUEI.js} +1 -1
  23. package/dist/bundle/chunks/{openai-responses-5A6CR7GI.js → openai-responses-GYZODCT3.js} +1 -1
  24. package/dist/bundle/chunks/readability-LAGF34IB.js +3 -0
  25. package/dist/bundle/chunks/turndown.cjs-HRMZVRUL.js +2 -0
  26. package/dist/bundle/cli.js +1 -1
  27. package/dist/bundle/index.js +1 -1
  28. package/dist/bundle/rpc-entry.js +1 -1
  29. package/dist/cli.js +1 -5
  30. package/dist/core/agent-session.js +2 -2
  31. package/dist/core/agent-session.js.map +1 -1
  32. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
  33. package/dist/core/tools/file-mutation-queue.js +21 -8
  34. package/dist/core/tools/file-mutation-queue.js.map +1 -1
  35. package/dist/core/tools/subagent-env.d.ts +18 -0
  36. package/dist/core/tools/subagent-env.d.ts.map +1 -0
  37. package/dist/core/tools/subagent-env.js +43 -0
  38. package/dist/core/tools/subagent-env.js.map +1 -0
  39. package/dist/core/tools/subagent-scheduler.d.ts +31 -0
  40. package/dist/core/tools/subagent-scheduler.d.ts.map +1 -0
  41. package/dist/core/tools/subagent-scheduler.js +203 -0
  42. package/dist/core/tools/subagent-scheduler.js.map +1 -0
  43. package/dist/core/tools/subagent.d.ts +83 -3
  44. package/dist/core/tools/subagent.d.ts.map +1 -1
  45. package/dist/core/tools/subagent.js +214 -76
  46. package/dist/core/tools/subagent.js.map +1 -1
  47. package/dist/humain/index.d.ts.map +1 -1
  48. package/dist/humain/index.js +2 -0
  49. package/dist/humain/index.js.map +1 -1
  50. package/dist/humain/mcp-adapter/vendor/CHANGELOG.md +78 -0
  51. package/dist/humain/mcp-adapter/vendor/README.md +125 -19
  52. package/dist/humain/mcp-adapter/vendor/agent-plugin-loader.ts +69 -30
  53. package/dist/humain/mcp-adapter/vendor/agent-plugin-provenance.ts +36 -0
  54. package/dist/humain/mcp-adapter/vendor/claude-plugin-loader.ts +365 -0
  55. package/dist/humain/mcp-adapter/vendor/commands.ts +13 -15
  56. package/dist/humain/mcp-adapter/vendor/config.ts +198 -30
  57. package/dist/humain/mcp-adapter/vendor/consent-manager.ts +40 -9
  58. package/dist/humain/mcp-adapter/vendor/direct-tool-surface.ts +250 -0
  59. package/dist/humain/mcp-adapter/vendor/direct-tools.ts +9 -228
  60. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-loader.js +74 -32
  61. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-loader.js.map +1 -1
  62. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.d.ts +1 -0
  63. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.js +31 -0
  64. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.js.map +1 -0
  65. package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.d.ts +9 -0
  66. package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.js +362 -0
  67. package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.js.map +1 -0
  68. package/dist/humain/mcp-adapter/vendor/dist/config.d.ts +3 -1
  69. package/dist/humain/mcp-adapter/vendor/dist/config.js +189 -27
  70. package/dist/humain/mcp-adapter/vendor/dist/config.js.map +1 -1
  71. package/dist/humain/mcp-adapter/vendor/dist/metadata-cache.js +7 -16
  72. package/dist/humain/mcp-adapter/vendor/dist/metadata-cache.js.map +1 -1
  73. package/dist/humain/mcp-adapter/vendor/dist/package-mcp-loader.js +5 -17
  74. package/dist/humain/mcp-adapter/vendor/dist/package-mcp-loader.js.map +1 -1
  75. package/dist/humain/mcp-adapter/vendor/dist/types.d.ts +30 -4
  76. package/dist/humain/mcp-adapter/vendor/dist/types.js +26 -0
  77. package/dist/humain/mcp-adapter/vendor/dist/types.js.map +1 -1
  78. package/dist/humain/mcp-adapter/vendor/dist/utils.d.ts +6 -0
  79. package/dist/humain/mcp-adapter/vendor/dist/utils.js +65 -6
  80. package/dist/humain/mcp-adapter/vendor/dist/utils.js.map +1 -1
  81. package/dist/humain/mcp-adapter/vendor/host-html-template.ts +20 -19
  82. package/dist/humain/mcp-adapter/vendor/http-ca.ts +84 -0
  83. package/dist/humain/mcp-adapter/vendor/index.bundle.mjs +37102 -15103
  84. package/dist/humain/mcp-adapter/vendor/index.ts +828 -164
  85. package/dist/humain/mcp-adapter/vendor/init.ts +48 -10
  86. package/dist/humain/mcp-adapter/vendor/lazy-loader.ts +7 -0
  87. package/dist/humain/mcp-adapter/vendor/mcp-auth-fetch.ts +113 -0
  88. package/dist/humain/mcp-adapter/vendor/mcp-auth-flow.ts +256 -77
  89. package/dist/humain/mcp-adapter/vendor/mcp-auth.ts +316 -54
  90. package/dist/humain/mcp-adapter/vendor/mcp-code.ts +45 -18
  91. package/dist/humain/mcp-adapter/vendor/mcp-install.ts +60 -0
  92. package/dist/humain/mcp-adapter/vendor/mcp-oauth-provider.ts +70 -6
  93. package/dist/humain/mcp-adapter/vendor/mcp-output-guard.ts +53 -42
  94. package/dist/humain/mcp-adapter/vendor/mcp-panel-theme.ts +105 -0
  95. package/dist/humain/mcp-adapter/vendor/mcp-panel.ts +366 -331
  96. package/dist/humain/mcp-adapter/vendor/mcp-references.ts +9 -14
  97. package/dist/humain/mcp-adapter/vendor/mcp-script-worker.mjs +3 -1
  98. package/dist/humain/mcp-adapter/vendor/mcp-setup-panel.ts +383 -341
  99. package/dist/humain/mcp-adapter/vendor/mcp-status.ts +5 -0
  100. package/dist/humain/mcp-adapter/vendor/metadata-cache.ts +7 -16
  101. package/dist/humain/mcp-adapter/vendor/namespace-tools.ts +61 -3
  102. package/dist/humain/mcp-adapter/vendor/oauth.ts +6 -2
  103. package/dist/humain/mcp-adapter/vendor/package-mcp-loader.ts +5 -17
  104. package/dist/humain/mcp-adapter/vendor/package.json +13 -3
  105. package/dist/humain/mcp-adapter/vendor/prompts.ts +35 -3
  106. package/dist/humain/mcp-adapter/vendor/proxy-modes.ts +108 -7
  107. package/dist/humain/mcp-adapter/vendor/runtime-owner.ts +16 -1
  108. package/dist/humain/mcp-adapter/vendor/sampling-handler.ts +9 -32
  109. package/dist/humain/mcp-adapter/vendor/sandbox-proxy-template.ts +20 -93
  110. package/dist/humain/mcp-adapter/vendor/server-manager.ts +227 -40
  111. package/dist/humain/mcp-adapter/vendor/session-approvals.ts +187 -0
  112. package/dist/humain/mcp-adapter/vendor/skills/mcp-scripting/SKILL.md +9 -1
  113. package/dist/humain/mcp-adapter/vendor/state.ts +10 -1
  114. package/dist/humain/mcp-adapter/vendor/tool-approval.ts +10 -22
  115. package/dist/humain/mcp-adapter/vendor/tool-metadata.ts +14 -0
  116. package/dist/humain/mcp-adapter/vendor/types.ts +57 -4
  117. package/dist/humain/mcp-adapter/vendor/ui-server.ts +36 -28
  118. package/dist/humain/mcp-adapter/vendor/utils.ts +64 -6
  119. package/dist/humain/node-http.d.ts +3 -0
  120. package/dist/humain/node-http.d.ts.map +1 -0
  121. package/dist/humain/node-http.js +119 -0
  122. package/dist/humain/node-http.js.map +1 -0
  123. package/dist/humain/node-provider.d.ts +4 -0
  124. package/dist/humain/node-provider.d.ts.map +1 -0
  125. package/dist/humain/node-provider.js +359 -0
  126. package/dist/humain/node-provider.js.map +1 -0
  127. package/dist/humain/web-access/vendor/abortable.d.ts +3 -0
  128. package/dist/humain/web-access/vendor/abortable.d.ts.map +1 -0
  129. package/dist/humain/web-access/vendor/abortable.js +22 -0
  130. package/dist/humain/web-access/vendor/abortable.js.map +1 -0
  131. package/dist/humain/web-access/vendor/crawl4ai.d.ts +9 -0
  132. package/dist/humain/web-access/vendor/crawl4ai.d.ts.map +1 -0
  133. package/dist/humain/web-access/vendor/crawl4ai.js +187 -0
  134. package/dist/humain/web-access/vendor/crawl4ai.js.map +1 -0
  135. package/dist/humain/web-access/vendor/credential-source.d.ts.map +1 -1
  136. package/dist/humain/web-access/vendor/credential-source.js +1 -0
  137. package/dist/humain/web-access/vendor/credential-source.js.map +1 -1
  138. package/dist/humain/web-access/vendor/curator-page.d.ts.map +1 -1
  139. package/dist/humain/web-access/vendor/curator-page.js +20 -2
  140. package/dist/humain/web-access/vendor/curator-page.js.map +1 -1
  141. package/dist/humain/web-access/vendor/curator-run.d.ts +11 -0
  142. package/dist/humain/web-access/vendor/curator-run.d.ts.map +1 -0
  143. package/dist/humain/web-access/vendor/curator-run.js +38 -0
  144. package/dist/humain/web-access/vendor/curator-run.js.map +1 -0
  145. package/dist/humain/web-access/vendor/curator-server.d.ts +1 -0
  146. package/dist/humain/web-access/vendor/curator-server.d.ts.map +1 -1
  147. package/dist/humain/web-access/vendor/curator-server.js +2 -0
  148. package/dist/humain/web-access/vendor/curator-server.js.map +1 -1
  149. package/dist/humain/web-access/vendor/duckduckgo.d.ts.map +1 -1
  150. package/dist/humain/web-access/vendor/duckduckgo.js +1 -1
  151. package/dist/humain/web-access/vendor/duckduckgo.js.map +1 -1
  152. package/dist/humain/web-access/vendor/extract.d.ts +1 -1
  153. package/dist/humain/web-access/vendor/extract.d.ts.map +1 -1
  154. package/dist/humain/web-access/vendor/extract.js +58 -11
  155. package/dist/humain/web-access/vendor/extract.js.map +1 -1
  156. package/dist/humain/web-access/vendor/fetch-params.js +1 -1
  157. package/dist/humain/web-access/vendor/fetch-params.js.map +1 -1
  158. package/dist/humain/web-access/vendor/gemini-search.d.ts +2 -2
  159. package/dist/humain/web-access/vendor/gemini-search.d.ts.map +1 -1
  160. package/dist/humain/web-access/vendor/gemini-search.js +12 -5
  161. package/dist/humain/web-access/vendor/gemini-search.js.map +1 -1
  162. package/dist/humain/web-access/vendor/index.d.ts.map +1 -1
  163. package/dist/humain/web-access/vendor/index.js +23 -19
  164. package/dist/humain/web-access/vendor/index.js.map +1 -1
  165. package/dist/humain/web-access/vendor/openai-search.d.ts.map +1 -1
  166. package/dist/humain/web-access/vendor/openai-search.js +40 -14
  167. package/dist/humain/web-access/vendor/openai-search.js.map +1 -1
  168. package/dist/humain/web-access/vendor/page-query.d.ts.map +1 -1
  169. package/dist/humain/web-access/vendor/page-query.js +6 -2
  170. package/dist/humain/web-access/vendor/page-query.js.map +1 -1
  171. package/dist/humain/web-access/vendor/query-rewrite.d.ts.map +1 -1
  172. package/dist/humain/web-access/vendor/query-rewrite.js +6 -2
  173. package/dist/humain/web-access/vendor/query-rewrite.js.map +1 -1
  174. package/dist/humain/web-access/vendor/serpapi.d.ts +4 -0
  175. package/dist/humain/web-access/vendor/serpapi.d.ts.map +1 -0
  176. package/dist/humain/web-access/vendor/serpapi.js +215 -0
  177. package/dist/humain/web-access/vendor/serpapi.js.map +1 -0
  178. package/dist/humain/web-access/vendor/ssrf-protection.d.ts.map +1 -1
  179. package/dist/humain/web-access/vendor/ssrf-protection.js +5 -1
  180. package/dist/humain/web-access/vendor/ssrf-protection.js.map +1 -1
  181. package/dist/humain/web-access/vendor/storage.d.ts.map +1 -1
  182. package/dist/humain/web-access/vendor/storage.js +9 -0
  183. package/dist/humain/web-access/vendor/storage.js.map +1 -1
  184. package/dist/humain/web-access/vendor/summary-review.d.ts +1 -1
  185. package/dist/humain/web-access/vendor/summary-review.d.ts.map +1 -1
  186. package/dist/humain/web-access/vendor/summary-review.js +35 -24
  187. package/dist/humain/web-access/vendor/summary-review.js.map +1 -1
  188. package/dist/humain/web-access/vendor/utils.d.ts.map +1 -1
  189. package/dist/humain/web-access/vendor/utils.js +5 -3
  190. package/dist/humain/web-access/vendor/utils.js.map +1 -1
  191. package/dist/index.js +1 -51
  192. package/dist/main.d.ts.map +1 -1
  193. package/dist/main.js +16 -0
  194. package/dist/main.js.map +1 -1
  195. package/dist/modes/interactive/components/login-dialog.d.ts +7 -2
  196. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  197. package/dist/modes/interactive/components/login-dialog.js +46 -21
  198. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  199. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  200. package/dist/modes/interactive/interactive-mode.js +10 -13
  201. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  202. package/dist/modes/json-event.d.ts +8 -0
  203. package/dist/modes/json-event.d.ts.map +1 -1
  204. package/dist/modes/json-event.js +17 -0
  205. package/dist/modes/json-event.js.map +1 -1
  206. package/dist/modes/print-mode.d.ts.map +1 -1
  207. package/dist/modes/print-mode.js +3 -2
  208. package/dist/modes/print-mode.js.map +1 -1
  209. package/docs/environment-variables.md +8 -0
  210. package/docs/humain-terminal.md +6 -0
  211. package/docs/json.md +16 -0
  212. package/docs/providers.md +19 -1
  213. package/npm-shrinkwrap.json +6 -6
  214. package/package.json +2 -2
  215. package/src/humain/mcp-adapter/UPSTREAM.md +56 -7
  216. package/src/humain/mcp-adapter/upstream.json +6 -4
  217. package/src/humain/mcp-adapter/vendor/CHANGELOG.md +78 -0
  218. package/src/humain/web-access/UPSTREAM.md +18 -8
  219. package/src/humain/web-access/upstream.json +6 -5
  220. package/dist/bundle/chunks/chunk-2LC3S3VV.js +0 -105
@@ -311,9 +311,9 @@ export async function authenticateServer(
311
311
  }
312
312
 
313
313
  ui.setStatus("mcp-auth", `Authenticating ${serverName}...`);
314
- const authStorageOptions = getAuthStorageOptions(config.settings?.oauthDir, cwd);
314
+ const authStorageOptions = getAuthStorageOptions(config.settings?.oauthDir, cwd, config.settings?.oauthCredentialStore);
315
315
  const status = await authenticate(serverName, serverUrl, definition, {
316
- ...(authStorageOptions.baseDir ? { authStorageOptions } : {}),
316
+ ...(Object.keys(authStorageOptions).length > 0 ? { authStorageOptions } : {}),
317
317
  onAuthorizationUrl: () => {},
318
318
  onAuthorizationInput: async (authorizationUrl, inputSignal) => {
319
319
  if (inputSignal.aborted) return undefined;
@@ -364,27 +364,24 @@ export async function logoutServer(
364
364
 
365
365
  const signal = state.owner?.signal;
366
366
  try {
367
- await removeAuth(serverName, { authStorageOptions: state.authStorageOptions, signal, runtime: state.oauthRuntime });
367
+ await state.manager.close(serverName);
368
368
  } catch (error) {
369
369
  if (isAbortError(error, signal)) throw error;
370
370
  const message = error instanceof Error ? error.message : String(error);
371
371
  if (ui) {
372
- ui.notify(`Failed to clear OAuth credentials for "${serverName}": ${sanitizeTerminalText(message)}`, "error");
372
+ ui.notify(`Failed to close OAuth server "${serverName}"; credentials were not cleared: ${sanitizeTerminalText(message)}`, "error");
373
373
  }
374
374
  return { ok: false, message };
375
375
  }
376
376
 
377
377
  state.owner?.throwIfInactive();
378
378
  try {
379
- await state.manager.close(serverName);
379
+ await removeAuth(serverName, { authStorageOptions: state.authStorageOptions, signal, runtime: state.oauthRuntime });
380
380
  } catch (error) {
381
381
  if (isAbortError(error, signal)) throw error;
382
382
  const message = error instanceof Error ? error.message : String(error);
383
383
  if (ui) {
384
- ui.notify(
385
- `OAuth credentials were cleared for "${serverName}", but its connection could not be closed: ${sanitizeTerminalText(message)}`,
386
- "error",
387
- );
384
+ ui.notify(`Failed to clear OAuth credentials for "${serverName}": ${sanitizeTerminalText(message)}`, "error");
388
385
  }
389
386
  return { ok: false, message };
390
387
  }
@@ -474,7 +471,7 @@ function buildSharedConfigNoticeLines(configOverridePath: string | undefined, cw
474
471
  const discovery = getMcpStandardConfigSummary(configOverridePath, cwd);
475
472
  const onboardingState = loadOnboardingState();
476
473
  const sharedSources = discovery.sources.filter((source) =>
477
- (source.id === "shared-project" || source.id === "shared-global") && source.serverCount > 0,
474
+ (source.id === "shared-project" || source.id === "shared-project-ancestor" || source.id === "shared-global") && source.serverCount > 0,
478
475
  );
479
476
  if (sharedSources.length === 0 || onboardingState.sharedConfigHintShown) {
480
477
  return { lines: [], fingerprint: null };
@@ -556,8 +553,8 @@ export async function openMcpSetup(
556
553
 
557
554
  return new Promise<PanelFlowResult>((resolve) => {
558
555
  ctx.ui.custom(
559
- (tui, _theme, keybindings, done) => {
560
- return createMcpSetupPanel(discovery, callbacks, { mode, onboardingState, keybindings }, tui, () => {
556
+ (tui, theme, keybindings, done) => {
557
+ return createMcpSetupPanel(discovery, callbacks, { mode, onboardingState, keybindings, theme }, tui, () => {
561
558
  done(undefined);
562
559
  resolve({ configChanged });
563
560
  });
@@ -670,7 +667,7 @@ export async function openMcpPanel(
670
667
 
671
668
  await new Promise<void>((resolve) => {
672
669
  ctx.ui.custom(
673
- (tui, _theme, keybindings, done) => {
670
+ (tui, theme, keybindings, done) => {
674
671
  return createMcpPanel(config, cache, provenanceMap, callbacks, tui, (result: McpPanelResult) => {
675
672
  void (async () => {
676
673
  if (!result.cancelled && result.disabledChanges.size > 0) {
@@ -699,7 +696,7 @@ export async function openMcpPanel(
699
696
  done(undefined);
700
697
  resolve();
701
698
  });
702
- }, { noticeLines, keybindings });
699
+ }, { noticeLines, keybindings, theme });
703
700
  },
704
701
  {
705
702
  overlay: true,
@@ -750,13 +747,14 @@ export async function openMcpAuthPanel(
750
747
 
751
748
  await new Promise<void>((resolve) => {
752
749
  ctx.ui.custom(
753
- (tui, _theme, keybindings, done) => {
750
+ (tui, theme, keybindings, done) => {
754
751
  return createMcpPanel(config, cache, provenanceMap, callbacks, tui, () => {
755
752
  done(undefined);
756
753
  resolve();
757
754
  }, {
758
755
  authOnly: true,
759
756
  keybindings,
757
+ theme,
760
758
  noticeLines: ["Select an OAuth MCP server and press Enter or ctrl+a to authenticate."],
761
759
  });
762
760
  },
@@ -1,14 +1,16 @@
1
1
  // config.ts - Config loading with import support
2
- import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from "node:fs";
2
+ import { existsSync, readFileSync, realpathSync, statSync, writeFileSync, mkdirSync, renameSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
- import { dirname, join, resolve } from "node:path";
4
+ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
5
5
  import { parse as parseToml } from "smol-toml";
6
6
  import stripJsonComments from "strip-json-comments";
7
7
  import { getAgentPath, getConfigDirName } from "./agent-dir.ts";
8
8
  import { getAgentPluginSummaries, loadAgentPluginConfigs, type AgentPluginSummary } from "./agent-plugin-loader.ts";
9
+ import { cloneBuiltInAgentPluginEntry, isBuiltInAgentPlugin, mergeBuiltInAgentPluginEntries } from "./agent-plugin-provenance.ts";
10
+ import { loadClaudePluginBundles } from "./claude-plugin-loader.ts";
9
11
  import { loadPackageMcpConfigs } from "./package-mcp-loader.ts";
10
- import { isServerDisabled, type HostConfigDiscovery, type McpConfig, type ServerEntry, type McpSettings, type ImportKind, type ServerProvenance } from "./types.ts";
11
- import { toStringRecord } from "./utils.ts";
12
+ import { formatServerNamespace, isServerDisabled, type ClaudePluginConfig, type HostConfigDiscovery, type McpConfig, type ServerEntry, type McpSettings, type ImportKind, type ServerProvenance } from "./types.ts";
13
+ import { parseJsonWithComments, toStringRecord } from "./utils.ts";
12
14
 
13
15
  const GENERIC_GLOBAL_CONFIG_PATH = join(homedir(), ".config", "mcp", "mcp.json");
14
16
  const AGENTS_GLOBAL_CONFIG_PATHS = [
@@ -93,7 +95,7 @@ const IMPORT_PATHS: Record<ImportKind, string[]> = {
93
95
  };
94
96
 
95
97
  interface ConfigSourceSpec {
96
- id: "shared-global" | "agents-global" | "agents-nested-global" | "pi-global" | "shared-project" | "pi-project";
98
+ id: "shared-global" | "agents-global" | "agents-nested-global" | "pi-global" | "shared-project-ancestor" | "pi-project-ancestor" | "shared-project" | "pi-project";
97
99
  label: string;
98
100
  readPath: string;
99
101
  writePath: string;
@@ -253,7 +255,7 @@ export function getMcpDiscoverySummary(
253
255
  const includeHostConfigs = options.includeHostConfigs !== false;
254
256
 
255
257
  const importKinds = isExclusiveConfigMode()
256
- ? (readValidatedConfig(getEffectivePiGlobalConfigPath(overridePath), "MCP exclusive config")?.imports ?? [])
258
+ ? (readValidatedConfig(getPiGlobalConfigPath(overridePath), "MCP exclusive config")?.imports ?? [])
257
259
  : (Object.keys(IMPORT_PATHS) as ImportKind[]);
258
260
  const imports = includeHostConfigs
259
261
  ? importKinds
@@ -312,7 +314,12 @@ export function getMcpDiscoverySummary(
312
314
  }
313
315
 
314
316
  export function cloneMcpConfig(config: McpConfig): McpConfig {
315
- return structuredClone(config);
317
+ const cloned = structuredClone(config);
318
+ for (const [name, source] of Object.entries(config.mcpServers)) {
319
+ const builtInClone = cloneBuiltInAgentPluginEntry(source);
320
+ if (builtInClone) cloned.mcpServers[name] = builtInClone;
321
+ }
322
+ return cloned;
316
323
  }
317
324
 
318
325
  export function loadMcpConfig(overridePath?: string, cwd = process.cwd()): McpConfig {
@@ -331,14 +338,44 @@ export function loadMcpConfig(overridePath?: string, cwd = process.cwd()): McpCo
331
338
  config = mergeConfigs(config, expandImports(loaded, cwd));
332
339
  }
333
340
 
334
- if (isExclusiveConfigMode()) return config;
341
+ if (isExclusiveConfigMode()) return resolveConfiguredClaudePluginMcp(config, cwd);
335
342
 
336
343
  const packageConfig = loadPackageMcpConfigs(cwd);
337
344
  const pluginConfig = loadAgentPluginConfigs(config.settings?.agentPluginPaths, cwd);
338
345
  const packageServers = Object.fromEntries(
339
346
  Object.entries(packageConfig.mcpServers).filter(([name]) => !Object.hasOwn(pluginConfig.mcpServers, name)),
340
347
  );
341
- return mergeConfigs({ mcpServers: packageServers }, mergeConfigs(pluginConfig, config));
348
+ const higherPrecedenceConfig = mergeConfigs(
349
+ { mcpServers: packageServers },
350
+ mergeConfigs(pluginConfig, config),
351
+ );
352
+ return mergeClaudePluginMcpDefaults(config.claudePlugins, higherPrecedenceConfig, cwd);
353
+ }
354
+
355
+ export function resolveConfiguredClaudePluginMcp(config: McpConfig, cwd = process.cwd()): McpConfig {
356
+ return mergeClaudePluginMcpDefaults(config.claudePlugins, config, cwd);
357
+ }
358
+
359
+ export function discoverConfiguredClaudePluginSkills(config: McpConfig, cwd = process.cwd()): string[] {
360
+ return loadClaudePluginBundles(config.claudePlugins, cwd, validateConfig, { mcp: false, skills: true }).skillPaths;
361
+ }
362
+
363
+ function mergeClaudePluginMcpDefaults(
364
+ plugins: ClaudePluginConfig[] | undefined,
365
+ higherPrecedenceConfig: McpConfig,
366
+ cwd: string,
367
+ ): McpConfig {
368
+ const pluginServers = loadClaudePluginBundles(plugins, cwd, validateConfig, { mcp: true, skills: false }).mcpServers;
369
+ const higherNamesByNamespace = new Map(
370
+ Object.keys(higherPrecedenceConfig.mcpServers).map(name => [formatServerNamespace(name), name]),
371
+ );
372
+ const defaults = Object.fromEntries(Object.entries(pluginServers).filter(([name]) => {
373
+ const higherName = higherNamesByNamespace.get(formatServerNamespace(name));
374
+ if (!higherName || higherName === name) return true;
375
+ console.warn(`Claude plugin MCP server "${name}" is shadowed by higher-precedence server "${higherName}" because both normalize to the same namespace`);
376
+ return false;
377
+ }));
378
+ return mergeConfigs({ mcpServers: defaults }, higherPrecedenceConfig);
342
379
  }
343
380
 
344
381
  function getMergedSettings(overridePath?: string, cwd = process.cwd()): McpSettings | undefined {
@@ -418,7 +455,7 @@ function getConfigConflicts(
418
455
  }
419
456
 
420
457
  function getConfigSources(overridePath?: string, cwd = process.cwd()): ConfigSourceSpec[] {
421
- const userPath = getEffectivePiGlobalConfigPath(overridePath);
458
+ const userPath = getPiGlobalConfigPath(overridePath);
422
459
  const projectPath = getProjectConfigPath(cwd);
423
460
  const projectPiPath = getProjectPiConfigPath(cwd);
424
461
  const sources: ConfigSourceSpec[] = [];
@@ -472,6 +509,43 @@ function getConfigSources(overridePath?: string, cwd = process.cwd()): ConfigSou
472
509
  scope: "global",
473
510
  });
474
511
 
512
+ // Compare file identities so symlink aliases cannot reload a global source
513
+ // at ancestor precedence. Keep original paths for display and writes.
514
+ const reservedPaths = new Set([
515
+ ...sources.map((source) => getConfigPathIdentity(source.readPath)),
516
+ getConfigPathIdentity(projectPath),
517
+ getConfigPathIdentity(projectPiPath),
518
+ ]);
519
+ // Only user-global files (including an explicit override) may opt in to
520
+ // ancestor discovery. Project files cannot extend this trust boundary.
521
+ const ancestorSources = new Map<string, ConfigSourceSpec>();
522
+ const descriptors = [
523
+ { id: "shared-project-ancestor", label: "ancestor standard MCP", path: getProjectConfigPath, shared: true },
524
+ { id: "pi-project-ancestor", label: "ancestor Pi override", path: getProjectPiConfigPath, shared: false },
525
+ ] as const;
526
+ const ancestorRoot = getConfiguredAncestorRoot(sources, cwd);
527
+ if (ancestorRoot) {
528
+ for (const dir of getAncestorProjectDirs(cwd, ancestorRoot)) {
529
+ for (const descriptor of descriptors) {
530
+ const path = descriptor.path(dir);
531
+ const identity = getConfigPathIdentity(path);
532
+ if (reservedPaths.has(identity) || !existsSync(path)) continue;
533
+ // Reinsert aliases at their nearest precedence position.
534
+ ancestorSources.delete(identity);
535
+ ancestorSources.set(identity, {
536
+ id: descriptor.id,
537
+ label: descriptor.label,
538
+ readPath: path,
539
+ writePath: path,
540
+ kind: "project",
541
+ shared: descriptor.shared,
542
+ scope: "project",
543
+ });
544
+ }
545
+ }
546
+ }
547
+ sources.push(...ancestorSources.values());
548
+
475
549
  if (projectPath !== userPath) {
476
550
  sources.push({
477
551
  id: "shared-project",
@@ -499,21 +573,81 @@ function getConfigSources(overridePath?: string, cwd = process.cwd()): ConfigSou
499
573
  return sources;
500
574
  }
501
575
 
502
- function isExclusiveConfigMode(): boolean {
503
- return process.env.PI_MCP_CONFIG_MODE?.trim().toLowerCase() === "exclusive";
576
+ function getConfigPathIdentity(path: string): string {
577
+ try {
578
+ return realpathSync(path);
579
+ } catch {
580
+ // Missing or inaccessible paths still participate in lexical deduplication.
581
+ return resolve(path);
582
+ }
583
+ }
584
+
585
+ function isWithin(base: string, target: string): boolean {
586
+ const path = relative(base, target);
587
+ return path !== ".." && !path.startsWith(`..${sep}`) && !isAbsolute(path);
588
+ }
589
+
590
+ function getConfiguredAncestorRoot(globalSources: ConfigSourceSpec[], cwd: string): string | undefined {
591
+ let configured: unknown;
592
+ for (const source of globalSources) {
593
+ const roots = readValidatedConfig(source.readPath, `MCP config from ${source.readPath}`)?.settings?.ancestorConfigRoots;
594
+ if (roots !== undefined) configured = roots;
595
+ }
596
+ if (configured === undefined || (Array.isArray(configured) && configured.length === 0)) return undefined;
597
+ if (!Array.isArray(configured)) {
598
+ console.warn("Invalid settings.ancestorConfigRoots: expected an array of paths");
599
+ return undefined;
600
+ }
601
+
602
+ const home = getConfigPathIdentity(resolve(homedir()));
603
+ const canonicalCwd = getConfigPathIdentity(resolve(cwd));
604
+ const valid: string[] = [];
605
+ for (const entry of configured) {
606
+ const expanded = typeof entry === "string" && entry.startsWith("~/")
607
+ ? join(homedir(), entry.slice(2))
608
+ : entry;
609
+ if (typeof expanded !== "string" || !isAbsolute(expanded)) {
610
+ console.warn(`Invalid settings.ancestorConfigRoots entry ${JSON.stringify(entry)}: expected an absolute path or ~/...`);
611
+ continue;
612
+ }
613
+ try {
614
+ const root = realpathSync(expanded);
615
+ if (!statSync(root).isDirectory() || !isWithin(home, root) || !isWithin(root, canonicalCwd)) throw new Error();
616
+ valid.push(root);
617
+ } catch {
618
+ console.warn(`Invalid settings.ancestorConfigRoots entry ${JSON.stringify(entry)}: expected an existing directory under HOME containing cwd`);
619
+ }
620
+ }
621
+ return valid.sort((left, right) => right.length - left.length)[0];
622
+ }
623
+
624
+ function getAncestorProjectDirs(cwd: string, root: string): string[] {
625
+ const start = getConfigPathIdentity(resolve(cwd));
626
+ const dirs: string[] = [];
627
+ let current = dirname(start);
628
+ while (isWithin(root, current)) {
629
+ dirs.unshift(current);
630
+ if (current === root) break;
631
+ const parent = dirname(current);
632
+ if (parent === current) break;
633
+ current = parent;
634
+ }
635
+ return dirs;
504
636
  }
505
637
 
506
- function getEffectivePiGlobalConfigPath(overridePath?: string): string {
507
- return getPiGlobalConfigPath(isExclusiveConfigMode() ? undefined : overridePath);
638
+ function isExclusiveConfigMode(): boolean {
639
+ return process.env.PI_MCP_CONFIG_MODE?.trim().toLowerCase() === "exclusive";
508
640
  }
509
641
 
510
642
  function mergeConfigs(base: McpConfig, next: McpConfig): McpConfig {
511
643
  const imports = mergeImports(base.imports, next.imports);
512
644
  const settings = next.settings ? { ...base.settings, ...next.settings } : base.settings;
645
+ const claudePlugins = next.claudePlugins ?? base.claudePlugins;
513
646
  return {
514
647
  mcpServers: mergeServerMaps(base.mcpServers, next.mcpServers),
515
648
  ...(imports !== undefined ? { imports } : {}),
516
649
  ...(settings !== undefined ? { settings } : {}),
650
+ ...(claudePlugins !== undefined ? { claudePlugins } : {}),
517
651
  };
518
652
  }
519
653
 
@@ -522,7 +656,7 @@ function mergeConfigs(base: McpConfig, next: McpConfig): McpConfig {
522
656
  // different url, these MUST NOT be inherited from the lower-precedence entry —
523
657
  // otherwise the original endpoint's credentials would be shipped to the new
524
658
  // url. See the SECURITY note in mergeServerMaps.
525
- const URL_BOUND_AUTH_FIELDS = ["headers", "bearerToken", "bearerTokenEnv", "bearerTokenStore", "requestHeadersCommand"] as const;
659
+ const URL_BOUND_AUTH_FIELDS = ["headers", "bearerToken", "bearerTokenEnv", "bearerTokenStore", "requestHeadersCommand", "caFile"] as const;
526
660
 
527
661
  function mergeServerMaps(
528
662
  base: Record<string, ServerEntry>,
@@ -545,24 +679,24 @@ function mergeServerMaps(
545
679
  if (existing && typeof definition.command === "string") {
546
680
  baseEntry = { ...existing };
547
681
  for (const field of [
548
- "url", "headers", "requestHeadersCommand", "auth", "bearerToken",
549
- "bearerTokenEnv", "oauth", "httpTransport", "socket",
682
+ "url", "headers", "requestHeadersCommand", "caFile", "auth", "bearerToken",
683
+ "bearerTokenEnv", "bearerTokenStore", "oauth", "httpTransport", "socket",
550
684
  ] as const) {
551
685
  delete baseEntry[field];
552
686
  }
553
687
  } else if (existing && typeof definition.url === "string") {
554
688
  baseEntry = { ...existing };
555
689
  for (const field of [
556
- "command", "args", "env", "cwd", "pluginDataDir", "literalEnv", "socket",
690
+ "command", "args", "env", "cwd", "pluginDataDir", "literalEnv", "inheritEnv", "socket",
557
691
  ] as const) {
558
692
  delete baseEntry[field];
559
693
  }
560
694
  } else if (existing && typeof definition.socket === "string") {
561
695
  baseEntry = { ...existing };
562
696
  for (const field of [
563
- "command", "args", "env", "cwd", "pluginDataDir", "literalEnv", "url",
564
- "headers", "requestHeadersCommand", "auth", "bearerToken", "bearerTokenEnv",
565
- "oauth", "httpTransport",
697
+ "command", "args", "env", "cwd", "pluginDataDir", "literalEnv", "inheritEnv", "url",
698
+ "headers", "requestHeadersCommand", "caFile", "auth", "bearerToken", "bearerTokenEnv",
699
+ "bearerTokenStore", "oauth", "httpTransport",
566
700
  ] as const) {
567
701
  delete baseEntry[field];
568
702
  }
@@ -576,7 +710,11 @@ function mergeServerMaps(
576
710
  delete baseEntry.oauth;
577
711
  }
578
712
  }
579
- merged[name] = { ...baseEntry, ...definition };
713
+ if (existing && Object.hasOwn(definition, "env") && isBuiltInAgentPlugin(existing, "env") && !Object.hasOwn(definition, "literalEnv")) {
714
+ if (baseEntry === existing) baseEntry = { ...existing };
715
+ delete baseEntry.literalEnv;
716
+ }
717
+ merged[name] = mergeBuiltInAgentPluginEntries(baseEntry, definition);
580
718
  }
581
719
  return merged;
582
720
  }
@@ -606,6 +744,7 @@ function expandImports(config: McpConfig, cwd = process.cwd()): McpConfig {
606
744
  return {
607
745
  imports: config.imports,
608
746
  ...(config.settings !== undefined ? { settings: config.settings } : {}),
747
+ ...(config.claudePlugins !== undefined ? { claudePlugins: config.claudePlugins } : {}),
609
748
  mcpServers: mergeServerMaps(importedServers, config.mcpServers),
610
749
  };
611
750
  }
@@ -638,13 +777,9 @@ function resolveImportCandidates(importKind: ImportKind, cwd: string): string[]
638
777
  });
639
778
  }
640
779
 
641
- function parseJsonConfig(raw: string): unknown {
642
- return JSON.parse(stripJsonComments(raw, { trailingCommas: true }));
643
- }
644
-
645
780
  function readImportedConfig(path: string): unknown {
646
781
  const raw = readFileSync(path, "utf-8");
647
- return path.endsWith(".toml") ? parseToml(raw) : parseJsonConfig(raw);
782
+ return path.endsWith(".toml") ? parseToml(raw) : parseJsonWithComments(raw);
648
783
  }
649
784
 
650
785
  function loadImportedConfig(
@@ -694,7 +829,9 @@ function readValidatedConfig(path: string, label: string): McpConfig | null {
694
829
  if (!existsSync(path)) return null;
695
830
 
696
831
  try {
697
- return validateConfig(parseJsonConfig(readFileSync(path, "utf-8")));
832
+ const text = readFileSync(path, "utf-8");
833
+ if (stripJsonComments(text, { trailingCommas: true }).trim() === "") return null;
834
+ return validateConfig(parseJsonWithComments(text));
698
835
  } catch (error) {
699
836
  console.warn(`Failed to load ${label}:`, error);
700
837
  return null;
@@ -710,9 +847,39 @@ function validateConfig(raw: unknown): McpConfig {
710
847
  mcpServers: toServerEntries(raw.mcpServers ?? raw["mcp-servers"]),
711
848
  ...(Array.isArray(raw.imports) ? { imports: raw.imports as ImportKind[] } : {}),
712
849
  ...(raw.settings !== undefined ? { settings: raw.settings as McpSettings } : {}),
850
+ ...(raw.claudePlugins !== undefined ? { claudePlugins: parseClaudePlugins(raw.claudePlugins) } : {}),
713
851
  };
714
852
  }
715
853
 
854
+ function parseClaudePlugins(value: unknown): ClaudePluginConfig[] {
855
+ if (!Array.isArray(value)) {
856
+ console.warn("Invalid claudePlugins config: expected an array");
857
+ return [];
858
+ }
859
+
860
+ const plugins: ClaudePluginConfig[] = [];
861
+ for (const [index, entry] of value.entries()) {
862
+ if (!isRecord(entry) || typeof entry.path !== "string" || entry.path.trim().length === 0) {
863
+ console.warn(`Invalid claudePlugins[${index}]: expected an object with a non-empty path`);
864
+ continue;
865
+ }
866
+ if ((entry.mcp !== undefined && typeof entry.mcp !== "boolean") || (entry.skills !== undefined && typeof entry.skills !== "boolean")) {
867
+ console.warn(`Invalid claudePlugins[${index}] for ${entry.path}: mcp and skills must be booleans`);
868
+ continue;
869
+ }
870
+ if (entry.mcp !== true && entry.skills !== true) {
871
+ console.warn(`Invalid claudePlugins[${index}] for ${entry.path}: enable mcp, skills, or both`);
872
+ continue;
873
+ }
874
+ plugins.push({
875
+ path: entry.path,
876
+ ...(entry.mcp !== undefined ? { mcp: entry.mcp } : {}),
877
+ ...(entry.skills !== undefined ? { skills: entry.skills } : {}),
878
+ });
879
+ }
880
+ return plugins;
881
+ }
882
+
716
883
  function toServerEntries(servers: unknown): Record<string, ServerEntry> {
717
884
  if (!isRecord(servers)) return {};
718
885
  const entries: Record<string, ServerEntry> = {};
@@ -850,6 +1017,7 @@ function extractServers(config: unknown, kind: ImportKind): Record<string, Serve
850
1017
  mapped.oauth = {
851
1018
  ...(typeof oauth.clientId === "string" ? { clientId: oauth.clientId } : {}),
852
1019
  ...(typeof oauth.clientSecret === "string" ? { clientSecret: oauth.clientSecret } : {}),
1020
+ ...(typeof oauth.clientMetadataUrl === "string" ? { clientMetadataUrl: oauth.clientMetadataUrl } : {}),
853
1021
  ...(typeof oauth.scope === "string" ? { scope: oauth.scope } : {}),
854
1022
  ...(typeof oauth.authServerMetadataUrl === "string" ? { authServerMetadataUrl: oauth.authServerMetadataUrl } : {}),
855
1023
  ...(typeof oauth.skipIssuerMetadataValidation === "boolean"
@@ -964,7 +1132,7 @@ function readRawConfigObject(filePath: string): Record<string, unknown> {
964
1132
  if (!existsSync(filePath)) return {};
965
1133
 
966
1134
  try {
967
- const raw = parseJsonConfig(readFileSync(filePath, "utf-8"));
1135
+ const raw = parseJsonWithComments(readFileSync(filePath, "utf-8"));
968
1136
  return raw && typeof raw === "object" && !Array.isArray(raw) ? raw as Record<string, unknown> : {};
969
1137
  } catch {
970
1138
  return {};
@@ -1011,7 +1179,7 @@ export function writeProjectServerDisabledOverride(
1011
1179
  let raw: Record<string, unknown> = {};
1012
1180
  if (existsSync(filePath)) {
1013
1181
  try {
1014
- const parsed = parseJsonConfig(readFileSync(filePath, "utf-8"));
1182
+ const parsed = parseJsonWithComments(readFileSync(filePath, "utf-8"));
1015
1183
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1016
1184
  throw new Error("root value must be an object");
1017
1185
  }
@@ -1,5 +1,6 @@
1
1
  import { ConsentError } from "./errors.ts";
2
2
  import { logger } from "./logger.ts";
3
+ import type { SessionApprovalWriter } from "./session-approvals.ts";
3
4
 
4
5
  export type ToolConsentMode = "never" | "once-per-server" | "always";
5
6
 
@@ -8,7 +9,10 @@ export class ConsentManager {
8
9
  private deniedServers = new Set<string>();
9
10
  private log = logger.child({ component: "ConsentManager" });
10
11
 
11
- constructor(private mode: ToolConsentMode = "once-per-server") {
12
+ constructor(
13
+ private mode: ToolConsentMode = "once-per-server",
14
+ private persistDecision?: SessionApprovalWriter,
15
+ ) {
12
16
  this.log.debug("Initialized", { mode });
13
17
  }
14
18
 
@@ -24,17 +28,30 @@ export class ConsentManager {
24
28
  }
25
29
 
26
30
  registerDecision(serverName: string, approved: boolean): void {
27
- this.deniedServers.delete(serverName);
28
- this.approvedServers.delete(serverName);
31
+ this.applyDecision(serverName, approved);
32
+ try {
33
+ this.persistDecision?.({
34
+ version: 1,
35
+ kind: "iframe",
36
+ decision: approved ? "allow" : "deny",
37
+ serverName,
38
+ });
39
+ } catch (error) {
40
+ const detail = error instanceof Error ? error.message : String(error);
41
+ this.log.debug("Failed to persist consent decision", { server: serverName, error: detail });
42
+ }
43
+ }
29
44
 
30
- if (approved) {
31
- this.approvedServers.add(serverName);
32
- this.log.debug("Consent granted", { server: serverName });
45
+ /** Apply a persisted decision without writing it back to the session. */
46
+ restoreDecision(serverName: string, approved: boolean): void {
47
+ if (this.mode === "always" && approved) {
48
+ // Restored "always" grants cannot be reused. A later grant still
49
+ // overrides an earlier denial.
50
+ this.approvedServers.delete(serverName);
51
+ this.deniedServers.delete(serverName);
33
52
  return;
34
53
  }
35
-
36
- this.deniedServers.add(serverName);
37
- this.log.debug("Consent denied", { server: serverName });
54
+ this.applyDecision(serverName, approved);
38
55
  }
39
56
 
40
57
  ensureApproved(serverName: string): void {
@@ -61,4 +78,18 @@ export class ConsentManager {
61
78
  this.deniedServers.clear();
62
79
  this.log.debug("Cleared all consent records");
63
80
  }
81
+
82
+ private applyDecision(serverName: string, approved: boolean): void {
83
+ this.deniedServers.delete(serverName);
84
+ this.approvedServers.delete(serverName);
85
+
86
+ if (approved) {
87
+ this.approvedServers.add(serverName);
88
+ this.log.debug("Consent granted", { server: serverName });
89
+ return;
90
+ }
91
+
92
+ this.deniedServers.add(serverName);
93
+ this.log.debug("Consent denied", { server: serverName });
94
+ }
64
95
  }