@bastani/atomic 0.9.26-alpha.2 → 0.9.26-alpha.3

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 (98) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/builtin/intercom/broker/spawn.ts +2 -0
  3. package/dist/builtin/intercom/index.bundle.mjs +1 -0
  4. package/dist/builtin/intercom/package.json +1 -1
  5. package/dist/builtin/mcp/native-client-licenses/NOTICE +2 -0
  6. package/dist/builtin/mcp/package.json +1 -1
  7. package/dist/builtin/subagents/package.json +1 -1
  8. package/dist/builtin/web-access/package.json +1 -1
  9. package/dist/builtin/workflows/package.json +1 -1
  10. package/dist/cli/args.d.ts.map +1 -1
  11. package/dist/cli/args.js +4 -1
  12. package/dist/cli/args.js.map +1 -1
  13. package/dist/core/keybindings.d.ts +5 -0
  14. package/dist/core/keybindings.d.ts.map +1 -1
  15. package/dist/core/keybindings.js +1 -0
  16. package/dist/core/keybindings.js.map +1 -1
  17. package/dist/extensions/mcp/cli.d.ts.map +1 -1
  18. package/dist/extensions/mcp/cli.js +4 -1
  19. package/dist/extensions/mcp/cli.js.map +1 -1
  20. package/dist/extensions/mcp/client/oauth/callback.d.ts +4 -2
  21. package/dist/extensions/mcp/client/oauth/callback.d.ts.map +1 -1
  22. package/dist/extensions/mcp/client/oauth/callback.js +12 -7
  23. package/dist/extensions/mcp/client/oauth/callback.js.map +1 -1
  24. package/dist/extensions/mcp/client/oauth/flow.d.ts +7 -1
  25. package/dist/extensions/mcp/client/oauth/flow.d.ts.map +1 -1
  26. package/dist/extensions/mcp/client/oauth/flow.js +20 -21
  27. package/dist/extensions/mcp/client/oauth/flow.js.map +1 -1
  28. package/dist/extensions/mcp/client/oauth/index.d.ts +1 -1
  29. package/dist/extensions/mcp/client/oauth/index.d.ts.map +1 -1
  30. package/dist/extensions/mcp/client/oauth/index.js.map +1 -1
  31. package/dist/extensions/mcp/client/oauth/provider.d.ts +5 -2
  32. package/dist/extensions/mcp/client/oauth/provider.d.ts.map +1 -1
  33. package/dist/extensions/mcp/client/oauth/provider.js +2 -0
  34. package/dist/extensions/mcp/client/oauth/provider.js.map +1 -1
  35. package/dist/extensions/mcp/config.d.ts +16 -4
  36. package/dist/extensions/mcp/config.d.ts.map +1 -1
  37. package/dist/extensions/mcp/config.js +50 -11
  38. package/dist/extensions/mcp/config.js.map +1 -1
  39. package/dist/extensions/mcp/index.d.ts +4 -1
  40. package/dist/extensions/mcp/index.d.ts.map +1 -1
  41. package/dist/extensions/mcp/index.js +80 -46
  42. package/dist/extensions/mcp/index.js.map +1 -1
  43. package/dist/extensions/mcp/oauth.d.ts.map +1 -1
  44. package/dist/extensions/mcp/oauth.js +9 -5
  45. package/dist/extensions/mcp/oauth.js.map +1 -1
  46. package/dist/extensions/mcp/ui.d.ts.map +1 -1
  47. package/dist/extensions/mcp/ui.js +8 -4
  48. package/dist/extensions/mcp/ui.js.map +1 -1
  49. package/dist/index.d.ts +1 -1
  50. package/dist/index.js.map +1 -1
  51. package/dist/modes/interactive/chat-input-actions.js.map +1 -1
  52. package/dist/modes/interactive/components/auth-url.d.ts +10 -0
  53. package/dist/modes/interactive/components/auth-url.d.ts.map +1 -0
  54. package/dist/modes/interactive/components/auth-url.js +37 -0
  55. package/dist/modes/interactive/components/auth-url.js.map +1 -0
  56. package/dist/modes/interactive/components/index.d.ts +0 -1
  57. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  58. package/dist/modes/interactive/components/index.js +0 -1
  59. package/dist/modes/interactive/components/index.js.map +1 -1
  60. package/dist/modes/interactive/components/login-dialog.d.ts +1 -0
  61. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  62. package/dist/modes/interactive/components/login-dialog.js +10 -5
  63. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  64. package/dist/modes/interactive/interactive-auth-login.js +0 -1
  65. package/dist/modes/interactive/interactive-auth-login.js.map +1 -1
  66. package/dist/modes/interactive/interactive-hotkeys-debug.js +1 -11
  67. package/dist/modes/interactive/interactive-hotkeys-debug.js.map +1 -1
  68. package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
  69. package/dist/modes/interactive/interactive-mode-deps.d.ts +1 -2
  70. package/dist/modes/interactive/interactive-mode-deps.d.ts.map +1 -1
  71. package/dist/modes/interactive/interactive-mode-deps.js +0 -1
  72. package/dist/modes/interactive/interactive-mode-deps.js.map +1 -1
  73. package/dist/modes/interactive/interactive-mode-surface.d.ts +0 -5
  74. package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
  75. package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
  76. package/dist/modes/interactive/interactive-model-routing.js +0 -2
  77. package/dist/modes/interactive/interactive-model-routing.js.map +1 -1
  78. package/dist/modes/interactive/interactive-tui.js.map +1 -1
  79. package/dist/modes/interactive-engine/engine-custom-ui.d.ts.map +1 -1
  80. package/dist/modes/interactive-engine/engine-custom-ui.js +38 -2
  81. package/dist/modes/interactive-engine/engine-custom-ui.js.map +1 -1
  82. package/dist/modes/interactive-engine/protocol.d.ts +10 -0
  83. package/dist/modes/interactive-engine/protocol.d.ts.map +1 -1
  84. package/dist/modes/interactive-engine/protocol.js +10 -0
  85. package/dist/modes/interactive-engine/protocol.js.map +1 -1
  86. package/dist/modes/interactive-engine/remote-component.d.ts +2 -1
  87. package/dist/modes/interactive-engine/remote-component.d.ts.map +1 -1
  88. package/dist/modes/interactive-engine/remote-component.js +23 -1
  89. package/dist/modes/interactive-engine/remote-component.js.map +1 -1
  90. package/docs/keybindings.md +3 -0
  91. package/docs/mcp-servers.md +13 -1
  92. package/docs/providers.md +2 -0
  93. package/npm-shrinkwrap.json +55 -33
  94. package/package.json +4 -3
  95. package/dist/modes/interactive/components/daxnuts.d.ts +0 -23
  96. package/dist/modes/interactive/components/daxnuts.d.ts.map +0 -1
  97. package/dist/modes/interactive/components/daxnuts.js +0 -140
  98. package/dist/modes/interactive/components/daxnuts.js.map +0 -1
@@ -26,8 +26,7 @@
26
26
  * current session for registered servers.
27
27
  */
28
28
  import { join, resolve } from "node:path";
29
- import { hyperlink } from "@earendil-works/pi-tui";
30
- import { getAgentDir } from "../../config.js";
29
+ import { CONFIG_DIR_NAME, getAgentDir } from "../../config.js";
31
30
  import { markNativeMcpToolDefinition, matchesMcpDirectToolSelection } from "../../core/mcp-child-policy.js";
32
31
  import { mcpNamespace } from "../../core/mcp-servers.js";
33
32
  import { openBrowser } from "../../utils/open-browser.js";
@@ -207,6 +206,8 @@ export function createMcpExtension(options = {}) {
207
206
  /** Servers from `mcp.json`, which take precedence over registered servers of the same name. */
208
207
  let configuredEntries = [];
209
208
  let configErrors = [];
209
+ /** The trusted project's `mcp.json`, where `/mcp` saves project overrides of global servers. */
210
+ let projectConfig;
210
211
  /** Registered servers that `mcp.json` overrides, shown in `/mcp`. */
211
212
  let overridden = [];
212
213
  let latestContext;
@@ -229,7 +230,10 @@ export function createMcpExtension(options = {}) {
229
230
  let modelRegistry;
230
231
  let serverLog;
231
232
  const openUrl = options.openUrl ?? openBrowser;
232
- const updateConfig = options.updateConfig ?? ((entry, patch) => updateMcpServerConfig(entry.source, entry.name, patch));
233
+ const updateConfig = options.updateConfig ??
234
+ ((entry, patch) => updateMcpServerConfig(entry.override ?? entry.source, entry.name, patch, {
235
+ override: entry.override !== undefined,
236
+ }));
233
237
  const listeners = new Set();
234
238
  const emitChange = () => {
235
239
  for (const listener of listeners)
@@ -534,18 +538,20 @@ export function createMcpExtension(options = {}) {
534
538
  };
535
539
  /**
536
540
  * Save a config change; returns an error message when the file could not be updated. Changes to
537
- * registered servers only apply to the current session.
541
+ * registered servers only apply to the current session. `inProject` adds a project override.
538
542
  */
539
- const saveConfig = (server, patch) => {
540
- if (server.entry.scope !== "extension") {
543
+ const saveConfig = (server, patch, inProject = false) => {
544
+ const override = inProject ? projectConfig : server.entry.override;
545
+ const entry = override ? { ...server.entry, override } : server.entry;
546
+ if (entry.scope !== "extension") {
541
547
  try {
542
- updateConfig(server.entry, patch);
548
+ updateConfig(entry, patch);
543
549
  }
544
550
  catch (error) {
545
- return `Could not update ${server.entry.source}: ${errorMessage(error)}`;
551
+ return `Could not update ${entry.override ?? entry.source}: ${errorMessage(error)}`;
546
552
  }
547
553
  }
548
- server.entry = { ...server.entry, config: { ...server.entry.config, ...patch } };
554
+ server.entry = { ...entry, config: { ...entry.config, ...patch } };
549
555
  return undefined;
550
556
  };
551
557
  const signIn = async (server, prompt) => {
@@ -600,8 +606,8 @@ export function createMcpExtension(options = {}) {
600
606
  }
601
607
  };
602
608
  /** Returns an error message when the config could not be saved; connection errors show in the state. */
603
- const setEnabled = async (server, enabled) => {
604
- const failed = saveConfig(server, { enabled });
609
+ const setEnabled = async (server, enabled, inProject = false) => {
610
+ const failed = saveConfig(server, { enabled }, inProject);
605
611
  if (failed)
606
612
  return failed;
607
613
  if (!enabled) {
@@ -647,9 +653,9 @@ export function createMcpExtension(options = {}) {
647
653
  .map((server) => ({
648
654
  value: server.entry.name,
649
655
  label: server.entry.name,
650
- description: `${describeState(server)} · ${exposureOf(server.entry)} · ${server.entry.scope ?? server.entry.source}`,
656
+ description: `${describeState(server)} · ${exposureOf(server.entry)} · ${server.entry.override ? "global, project override" : (server.entry.scope ?? server.entry.source)}`,
651
657
  })),
652
- empty: `No MCP servers configured. Add them to ${resolve(getAgentDir(), "mcp.json")} or .pi/mcp.json.`,
658
+ empty: `No MCP servers configured. Add them to ${resolve(getAgentDir(), "mcp.json")} or ${CONFIG_DIR_NAME}/mcp.json.`,
653
659
  confirmLabel: "manage",
654
660
  cancelLabel: "close",
655
661
  });
@@ -667,12 +673,20 @@ export function createMcpExtension(options = {}) {
667
673
  const { entry, connection } = server;
668
674
  const saved = entry.scope === "extension"
669
675
  ? "for this session"
670
- : entry.scope
671
- ? `saved to the ${entry.scope} mcp.json`
672
- : "saved to mcp.json";
676
+ : entry.override
677
+ ? "saved to the project mcp.json"
678
+ : entry.scope
679
+ ? `saved to the ${entry.scope} mcp.json`
680
+ : "saved to mcp.json";
681
+ // Global servers without an override can be turned on or off for the trusted project alone.
682
+ const inProject = entry.scope === "global" && !entry.override && projectConfig !== undefined;
683
+ const inProjectSaved = "saved to the project mcp.json";
673
684
  const items = [];
674
685
  if (!isEnabled(server)) {
675
686
  items.push({ value: "enable", label: "Enable", description: saved });
687
+ if (inProject) {
688
+ items.push({ value: "enable-project", label: "Enable in this project", description: inProjectSaved });
689
+ }
676
690
  }
677
691
  else {
678
692
  const state = connection?.state;
@@ -689,10 +703,14 @@ export function createMcpExtension(options = {}) {
689
703
  }
690
704
  items.push({ value: "exposure", label: "Exposure", description: exposureOf(entry) });
691
705
  items.push({ value: "disable", label: "Disable", description: saved });
706
+ if (inProject) {
707
+ items.push({ value: "disable-project", label: "Disable in this project", description: inProjectSaved });
708
+ }
692
709
  }
693
710
  const details = [
694
711
  describeTransport(entry),
695
712
  `${entry.scope ?? "config"}: ${entry.source}`,
713
+ ...(entry.override ? [`project override: ${entry.override}`] : []),
696
714
  `State: ${describeState(server, false)}`,
697
715
  ];
698
716
  const error = [server.message, connection?.state === "connected" ? undefined : connection?.error]
@@ -734,7 +752,7 @@ export function createMcpExtension(options = {}) {
734
752
  title: `Exposure of ${server.entry.name}`,
735
753
  details: server.entry.scope === "extension"
736
754
  ? `Applies to this session; the server is registered by ${server.entry.source}.`
737
- : `Saved to ${server.entry.source}.`,
755
+ : `Saved to ${server.entry.override ?? server.entry.source}.`,
738
756
  items: Object.keys(EXPOSURE_DESCRIPTIONS).map((exposure) => ({
739
757
  value: exposure,
740
758
  label: `${exposure === current ? "✓ " : " "}${exposure}`,
@@ -748,27 +766,29 @@ export function createMcpExtension(options = {}) {
748
766
  return undefined;
749
767
  return setExposure(server, choice);
750
768
  };
769
+ const signInWithUi = (ui, server) => {
770
+ const title = `Sign in to ${server.entry.name}`;
771
+ let authorizationUrl = "";
772
+ ui.status(title, "Contacting the authorization server…");
773
+ return signIn(server, {
774
+ showAuthorizationUrl: (url) => {
775
+ authorizationUrl = url.href;
776
+ openUrl(url.href);
777
+ },
778
+ promptForRedirectUrl: async (signal) => {
779
+ const value = await ui.redirectUrl(title, authorizationUrl, signal);
780
+ ui.status(title, "Connecting…");
781
+ return value;
782
+ },
783
+ });
784
+ };
751
785
  const runAction = async (ui, ctx, server, action) => {
752
786
  const { name } = server.entry;
753
787
  let message;
754
788
  switch (action) {
755
- case "signin": {
756
- const title = `Sign in to ${name}`;
757
- let authorizationUrl = "";
758
- ui.status(title, "Contacting the authorization server…");
759
- message = await signIn(server, {
760
- showAuthorizationUrl: (url) => {
761
- authorizationUrl = url.href;
762
- openUrl(url.href);
763
- },
764
- promptForRedirectUrl: async (signal) => {
765
- const value = await ui.redirectUrl(title, authorizationUrl, signal);
766
- ui.status(title, "Connecting…");
767
- return value;
768
- },
769
- });
789
+ case "signin":
790
+ message = await signInWithUi(ui, server);
770
791
  break;
771
- }
772
792
  case "reconnect":
773
793
  // A failure shows as the connection's state and error.
774
794
  ui.status(`MCP server ${name}`, "Reconnecting…");
@@ -785,9 +805,13 @@ export function createMcpExtension(options = {}) {
785
805
  break;
786
806
  case "enable":
787
807
  case "disable":
788
- ui.status(`MCP server ${name}`, action === "enable" ? "Connecting…" : "Disconnecting…");
789
- message = await setEnabled(server, action === "enable");
808
+ case "enable-project":
809
+ case "disable-project": {
810
+ const enable = action.startsWith("enable");
811
+ ui.status(`MCP server ${name}`, enable ? "Connecting…" : "Disconnecting…");
812
+ message = await setEnabled(server, enable, action.endsWith("-project"));
790
813
  break;
814
+ }
791
815
  }
792
816
  server.message = message;
793
817
  ensureDiscoveryActive(ctx);
@@ -812,7 +836,7 @@ export function createMcpExtension(options = {}) {
812
836
  // ---------------------------------------------------------------------------------------
813
837
  const formatStatus = () => {
814
838
  if (servers.length === 0 && configErrors.length === 0 && overridden.length === 0) {
815
- return `No MCP servers configured. Add them to ${resolve(getAgentDir(), "mcp.json")} or .pi/mcp.json.`;
839
+ return `No MCP servers configured. Add them to ${resolve(getAgentDir(), "mcp.json")} or ${CONFIG_DIR_NAME}/mcp.json.`;
816
840
  }
817
841
  const lines = servers.map((server) => {
818
842
  const { name } = server.entry;
@@ -872,16 +896,25 @@ export function createMcpExtension(options = {}) {
872
896
  ctx.ui.notify(`Signing in to MCP server "${name}" requires interactive mode.`, "error");
873
897
  return;
874
898
  }
875
- const failure = await signIn(server, {
876
- showAuthorizationUrl: (url) => {
877
- const lines = ctx.mode === "tui"
878
- ? `${hyperlink(url.href, url.href)}\n${hyperlink(process.platform === "darwin" ? "Cmd+click to open" : "Ctrl+click to open", url.href)}`
879
- : url.href;
880
- ctx.ui.notify(`Sign in to MCP server "${name}" in your browser:\n${lines}`, "info");
881
- openUrl(url.href);
882
- },
883
- promptForRedirectUrl: (signal) => ctx.ui.input(`Waiting for sign-in to "${name}". If the browser cannot reach this machine, paste the URL it was redirected to.`, "http://127.0.0.1:.../callback?code=...", { signal }),
884
- });
899
+ let failure;
900
+ if (ctx.mode === "tui") {
901
+ let completed = false;
902
+ await showMcpManager(ctx, async (ui) => {
903
+ failure = await signInWithUi(ui, server);
904
+ completed = true;
905
+ });
906
+ if (!completed)
907
+ return;
908
+ }
909
+ else {
910
+ failure = await signIn(server, {
911
+ showAuthorizationUrl: (url) => {
912
+ ctx.ui.notify(`Sign in to MCP server "${name}" in your browser:\n${url.href}`, "info");
913
+ openUrl(url.href);
914
+ },
915
+ promptForRedirectUrl: (signal) => ctx.ui.input(`Waiting for sign-in to "${name}". If the browser cannot reach this machine, paste the URL it was redirected to.`, "http://127.0.0.1:.../callback?code=...", { signal }),
916
+ });
917
+ }
885
918
  if (failure) {
886
919
  ctx.ui.notify(failure, failure === "Sign-in cancelled." ? "info" : "error");
887
920
  return;
@@ -894,6 +927,7 @@ export function createMcpExtension(options = {}) {
894
927
  subagentMcpDirectTools = ctx.subagentPolicy?.mcpDirectTools;
895
928
  const loaded = (options.loadConfig ?? defaultLoadConfig)(ctx);
896
929
  configErrors = loaded.errors;
930
+ projectConfig = loaded.projectConfig;
897
931
  autoEnableCodemode = (ctx.subagentPolicy ? false : loaded.autoEnableCodemode) ?? true;
898
932
  warnedUnreachable = false;
899
933
  waitedForStartup = false;