@p4code/cli 0.1.13 → 0.1.15

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 (86) hide show
  1. package/dist/bin.mjs +921 -52
  2. package/dist/client/assets/{DiffPanel-Bpvhj9Zk.js → DiffPanel-Btlwh6tZ.js} +2 -2
  3. package/dist/client/assets/{FilePreviewPanel-C2_gi2li.js → FilePreviewPanel-CJRGrUq9.js} +2 -2
  4. package/dist/client/assets/{PreviewPanel-CXrvKPAn.js → PreviewPanel-CM8chpxZ.js} +2 -2
  5. package/dist/client/assets/{angular-html-tWdnCR5u.js → angular-html-BjmAeE7T.js} +2 -2
  6. package/dist/client/assets/{angular-ts-DHHrhqEn.js → angular-ts-BBpxlOlP.js} +2 -2
  7. package/dist/client/assets/{apl-Dttzd1My.js → apl-BbgMrCuX.js} +2 -2
  8. package/dist/client/assets/arrow-right-DYd55OqT.js +2 -0
  9. package/dist/client/assets/{astro-BhhLcbHg.js → astro-mUvWYN-n.js} +2 -2
  10. package/dist/client/assets/{blade-D1XdhGNZ.js → blade-B5QWyKCP.js} +2 -2
  11. package/dist/client/assets/{c-DU3so7RH.js → c-BfHpmPOy.js} +2 -2
  12. package/dist/client/assets/{cobol-C5SwCVtJ.js → cobol-DLZOS_vG.js} +2 -2
  13. package/dist/client/assets/{coffee-D8JHirJ4.js → coffee-9TkBNWbZ.js} +2 -2
  14. package/dist/client/assets/{cpp-BrxgqiPv.js → cpp-qjIc-2IN.js} +2 -2
  15. package/dist/client/assets/{crystal-LnjbHrAj.js → crystal-DCOpXRVe.js} +2 -2
  16. package/dist/client/assets/{css-BhvHnfwC.js → css-Xrxx3evf.js} +2 -2
  17. package/dist/client/assets/{edge-BIAZ2O1m.js → edge-BJXKMMao.js} +2 -2
  18. package/dist/client/assets/{elixir-C7fzptdf.js → elixir-_uRFN4Aw.js} +2 -2
  19. package/dist/client/assets/{elm-24neygAB.js → elm-fwfp6NdI.js} +2 -2
  20. package/dist/client/assets/{erb-DpLDFI3P.js → erb-DfSw8n3G.js} +2 -2
  21. package/dist/client/assets/{fileCommentAnnotations-D9LoOqKz.js → fileCommentAnnotations-BCnpdEVp.js} +2 -2
  22. package/dist/client/assets/{git-rebase-nk76LFrn.js → git-rebase-CwGd8ZXk.js} +2 -2
  23. package/dist/client/assets/{glimmer-js-D4faXOGy.js → glimmer-js-DIl7yS9j.js} +2 -2
  24. package/dist/client/assets/{glimmer-ts-CUIVPmuw.js → glimmer-ts-ClbRblrS.js} +2 -2
  25. package/dist/client/assets/{glsl-CHEysBk5.js → glsl-DPbTpA_H.js} +2 -2
  26. package/dist/client/assets/{graphql-DxDXWgtu.js → graphql-Ugw-cmPb.js} +2 -2
  27. package/dist/client/assets/{hack-SE0rPM7F.js → hack-BNaGlFKY.js} +2 -2
  28. package/dist/client/assets/{haml-CZNVJ1t_.js → haml-CADPiQL8.js} +2 -2
  29. package/dist/client/assets/{handlebars-D3H0z0i1.js → handlebars-lMMkQPVI.js} +2 -2
  30. package/dist/client/assets/{html-BXh14Kiu.js → html-dV4yJf1E.js} +2 -2
  31. package/dist/client/assets/{html-derivative-Dcx75xJi.js → html-derivative-DC417Zuq.js} +2 -2
  32. package/dist/client/assets/{http-CHN_K5kg.js → http-DHNl1S4G.js} +2 -2
  33. package/dist/client/assets/{hurl-DcyaHxXE.js → hurl-MeTzE8YN.js} +2 -2
  34. package/dist/client/assets/{index-DesIBOH0.js → index-BmkbvCRH.js} +200 -200
  35. package/dist/client/assets/index-DvMVFBig.css +1 -0
  36. package/dist/client/assets/{java-CibfJb97.js → java-_z6VtRIC.js} +2 -2
  37. package/dist/client/assets/{javascript-CGKYJrgO.js → javascript-mzH0UoeT.js} +2 -2
  38. package/dist/client/assets/{jinja-hMi6h2V1.js → jinja-CcSfwgQN.js} +2 -2
  39. package/dist/client/assets/{jison-D5hwL1Vj.js → jison-Dnr6DQYK.js} +2 -2
  40. package/dist/client/assets/{json-DBBzorBt.js → json-xiKHc2eN.js} +2 -2
  41. package/dist/client/assets/{jsx-CfjpnlWf.js → jsx-B0uBKQ5P.js} +2 -2
  42. package/dist/client/assets/{julia-DY-wGzwr.js → julia-Du1DH_tJ.js} +2 -2
  43. package/dist/client/assets/{just-Aiaxo0L3.js → just-DlyxJRKQ.js} +2 -2
  44. package/dist/client/assets/{latex-Cl7y6v9p.js → latex-FzyaW41z.js} +2 -2
  45. package/dist/client/assets/{liquid-tjIYQaDR.js → liquid-n6xLDAue.js} +2 -2
  46. package/dist/client/assets/{lua-DeDHmNSA.js → lua-DL20UMyO.js} +2 -2
  47. package/dist/client/assets/{marko-BLEE3RS_.js → marko-OhxSvhVO.js} +2 -2
  48. package/dist/client/assets/{mdc-DBLy0ju2.js → mdc-JFr_O-f6.js} +2 -2
  49. package/dist/client/assets/{nginx-BpS56zJM.js → nginx-DO3MRFFZ.js} +2 -2
  50. package/dist/client/assets/{nim-BP_5ZWs4.js → nim-gxiT3Pjh.js} +2 -2
  51. package/dist/client/assets/{perl-BNoghH8w.js → perl-uO1ltvnm.js} +2 -2
  52. package/dist/client/assets/{php-fikhFdsH.js → php-DkSF6XhT.js} +2 -2
  53. package/dist/client/assets/{pug-BmML_p4L.js → pug-D6ks186t.js} +2 -2
  54. package/dist/client/assets/{qml-WOQTdjfD.js → qml-CsabseTB.js} +2 -2
  55. package/dist/client/assets/{r-BuLVFJ9C.js → r-UjYqKhF6.js} +2 -2
  56. package/dist/client/assets/{razor-CsZ5mXvp.js → razor-Dr34iB-_.js} +2 -2
  57. package/dist/client/assets/{regexp-BbB_Z0k0.js → regexp-BG9MLSbj.js} +2 -2
  58. package/dist/client/assets/{rst-CGogIoWE.js → rst-I9GYrav4.js} +2 -2
  59. package/dist/client/assets/{ruby-Drt9dpIY.js → ruby-DQZcmBTL.js} +2 -2
  60. package/dist/client/assets/{sas-mvidrzzg.js → sas-To8Ja79N.js} +2 -2
  61. package/dist/client/assets/{scss-DcqXsy4k.js → scss-KhIYRETT.js} +2 -2
  62. package/dist/client/assets/{shellscript-C6nla6y6.js → shellscript-B3M9HD4S.js} +2 -2
  63. package/dist/client/assets/{shellsession-Dhkmvk-h.js → shellsession-DCI3i_rX.js} +2 -2
  64. package/dist/client/assets/{soy-CA1GOdLw.js → soy-11gkdBZx.js} +2 -2
  65. package/dist/client/assets/{sql-CaDtCgQd.js → sql-Dc2GulG4.js} +2 -2
  66. package/dist/client/assets/{stata-r5dYDO1a.js → stata-Br5SUE30.js} +2 -2
  67. package/dist/client/assets/{surrealql-CreiDVJS.js → surrealql-Rg04dQNn.js} +2 -2
  68. package/dist/client/assets/{svelte-LQf52vP7.js → svelte-CtlB9-BL.js} +2 -2
  69. package/dist/client/assets/{templ-BAiCCrhW.js → templ-Du0y714B.js} +2 -2
  70. package/dist/client/assets/{tex-Bi4veNnI.js → tex-DujbYFg6.js} +2 -2
  71. package/dist/client/assets/textarea-C3IX8FEW.js +46 -0
  72. package/dist/client/assets/{ts-tags-CmONaW11.js → ts-tags-D1giNT28.js} +2 -2
  73. package/dist/client/assets/{tsx-DEGaq1SP.js → tsx-6OJBHQve.js} +2 -2
  74. package/dist/client/assets/{twig-DR3myS6w.js → twig-CsGDI-1m.js} +2 -2
  75. package/dist/client/assets/{typescript-pTX3WTUF.js → typescript-BK1jdq6h.js} +2 -2
  76. package/dist/client/assets/{vue-_Gh228sM.js → vue-CcdGezrU.js} +2 -2
  77. package/dist/client/assets/{vue-html-DqI2SuCG.js → vue-html-Cki3bF1b.js} +2 -2
  78. package/dist/client/assets/{vue-vine-DJ2u5TuR.js → vue-vine-CqNi4Hn_.js} +2 -2
  79. package/dist/client/assets/{xml-DtWHD1Mk.js → xml-By8mAu2E.js} +2 -2
  80. package/dist/client/assets/{xsl--uIbb82K.js → xsl-DWgCtTr-.js} +2 -2
  81. package/dist/client/assets/{yaml-BYZt09An.js → yaml-DZTshk4U.js} +2 -2
  82. package/dist/client/index.html +3 -3
  83. package/package.json +1 -1
  84. package/dist/client/assets/arrow-right-DL8xTI3l.js +0 -2
  85. package/dist/client/assets/index-D7hXE3Wh.css +0 -1
  86. package/dist/client/assets/textarea-8UeQ85tZ.js +0 -46
package/dist/bin.mjs CHANGED
@@ -121,7 +121,7 @@ const closeServer = (server) => {
121
121
  * NetService - Service tag for startup networking helpers.
122
122
  */
123
123
  var NetService = class extends Context.Service()("@p4code/shared/Net/NetService") {};
124
- const make$73 = () => {
124
+ const make$75 = () => {
125
125
  /**
126
126
  * Returns true when a TCP server can bind to {host, port}.
127
127
  * `EADDRNOTAVAIL` is treated as available so IPv6-absent hosts don't fail
@@ -230,10 +230,10 @@ const make$73 = () => {
230
230
  })
231
231
  };
232
232
  };
233
- const layer$69 = Layer.sync(NetService, make$73);
233
+ const layer$71 = Layer.sync(NetService, make$75);
234
234
  //#endregion
235
235
  //#region package.json
236
- var version = "0.1.13";
236
+ var version = "0.1.15";
237
237
  //#endregion
238
238
  //#region src/config.ts
239
239
  /**
@@ -254,8 +254,8 @@ var ServerConfig$1 = class extends Context.Service()("@p4code/cli/config/ServerC
254
254
  /** @deprecated Import and use `layerTest` from this module. */
255
255
  static layerTest = (cwd, baseDirOrPrefix) => layerTest$2(cwd, baseDirOrPrefix);
256
256
  };
257
- const make$72 = (config) => ServerConfig$1.of(config);
258
- const layer$68 = (config) => Layer.succeed(ServerConfig$1, make$72(config));
257
+ const make$74 = (config) => ServerConfig$1.of(config);
258
+ const layer$70 = (config) => Layer.succeed(ServerConfig$1, make$74(config));
259
259
  const deriveServerPaths = Effect.fn(function* (baseDir, devUrl, options = {}) {
260
260
  const { join } = yield* Path.Path;
261
261
  const stateDir = join(baseDir, devUrl !== void 0 && !options.baseDirIsExplicit ? "dev" : "userdata");
@@ -8146,6 +8146,15 @@ const McpSecretSlot = Schema$1.Struct({
8146
8146
  * have to compute the same string or the value is written where nothing looks.
8147
8147
  */
8148
8148
  const mcpSecretStorageName = (input) => `mcp.${input.server}.${input.target}.${input.key}`;
8149
+ /**
8150
+ * The storage name for a server's OAuth record — client id, endpoints, tokens.
8151
+ *
8152
+ * Beside `mcpSecretStorageName` for the same reason that one is here: the
8153
+ * server writing tokens and the registry cleaning up on remove must compute
8154
+ * the same string. Cannot collide with a slot name: slots always carry a
8155
+ * `header.` or `env.` segment where this has `oauth`.
8156
+ */
8157
+ const mcpOAuthStorageName = (server) => `mcp.${server}.oauth`;
8149
8158
  const commonFields = {
8150
8159
  name: McpServerName,
8151
8160
  /**
@@ -8187,6 +8196,20 @@ const McpRemoteServer = Schema$1.Struct({
8187
8196
  */
8188
8197
  const McpServerRegistration = Schema$1.Union([McpStdioServer, McpRemoteServer]);
8189
8198
  /**
8199
+ * OAuth state of a remote server, on this machine.
8200
+ *
8201
+ * Present only for a server that answered a readiness probe with a 401
8202
+ * challenge or that already holds a token here. Like `missingSecrets`, this is
8203
+ * per-machine truth about a registration that syncs everywhere: the token never
8204
+ * travels, so each machine reports its own `connected`.
8205
+ */
8206
+ const McpServerOAuthStatus = Schema$1.Struct({
8207
+ /** The server demanded a bearer token this machine does not send. */
8208
+ required: Schema$1.Boolean,
8209
+ /** This machine holds a token for it. */
8210
+ connected: Schema$1.Boolean
8211
+ });
8212
+ /**
8190
8213
  * What a panel needs to show, including what this machine cannot run.
8191
8214
  *
8192
8215
  * `missingSecrets` is the field that earns this type. A registration syncs
@@ -8194,15 +8217,62 @@ const McpServerRegistration = Schema$1.Union([McpStdioServer, McpRemoteServer]);
8194
8217
  * unusable *here*, and saying which slot is empty is the difference between a
8195
8218
  * fixable state and a mystery.
8196
8219
  */
8220
+ /**
8221
+ * Where a listed server lives, which is also where an edit goes.
8222
+ *
8223
+ * `claude` is the CLI's user scope (`~/.claude.json`) — the primary home,
8224
+ * because every Claude Code session reads it, inside p4code and out.
8225
+ * `project` is one workspace project's `.mcp.json`, committed with the repo.
8226
+ * `registry` is p4code's own store, the only one that syncs through the hub
8227
+ * and carries secret slots.
8228
+ */
8229
+ const McpServerSource = Schema$1.Literals([
8230
+ "registry",
8231
+ "claude",
8232
+ "project"
8233
+ ]);
8197
8234
  const McpServerStatus = Schema$1.Struct({
8198
8235
  registration: McpServerRegistration,
8199
8236
  missingSecrets: Schema$1.Array(McpSecretSlot),
8200
8237
  /** True when this machine holds everything needed to start it. */
8201
- ready: Schema$1.Boolean
8238
+ ready: Schema$1.Boolean,
8239
+ /** Absent for servers that never challenged and hold no token here. */
8240
+ oauth: Schema$1.optional(McpServerOAuthStatus),
8241
+ /** Absent means `registry`, which every row was before this field existed. */
8242
+ source: Schema$1.optional(McpServerSource),
8243
+ /** The workspace root whose `.mcp.json` holds this row. `project` rows only. */
8244
+ projectDir: Schema$1.optional(Schema$1.String)
8245
+ });
8246
+ /** A workspace project the panel can scope a server to. */
8247
+ const McpProjectRef = Schema$1.Struct({
8248
+ dir: Schema$1.String,
8249
+ title: Schema$1.String
8250
+ });
8251
+ const McpServerList = Schema$1.Struct({
8252
+ servers: Schema$1.Array(McpServerStatus),
8253
+ /**
8254
+ * The projects whose `.mcp.json` can be edited, for the add dialog's scope
8255
+ * picker. Carried with the list so the panel needs no second request.
8256
+ */
8257
+ projects: Schema$1.optional(Schema$1.Array(McpProjectRef))
8258
+ });
8259
+ /**
8260
+ * `source` routes the write: `claude` edits the `mcpServers` key of the
8261
+ * CLI's user-scope file, `registry` (or absent) writes p4code's own store.
8262
+ * The user-scope file is the primary home — it is what every Claude Code
8263
+ * session reads, in and out of p4code — so the panel defaults to it.
8264
+ */
8265
+ const McpServerSaveInput = Schema$1.Struct({
8266
+ registration: McpServerRegistration,
8267
+ source: Schema$1.optional(McpServerSource),
8268
+ /** Required when `source` is `project`: the workspace root to write into. */
8269
+ projectDir: Schema$1.optional(Schema$1.String)
8270
+ });
8271
+ const McpServerNameInput = Schema$1.Struct({
8272
+ name: McpServerName,
8273
+ source: Schema$1.optional(McpServerSource),
8274
+ projectDir: Schema$1.optional(Schema$1.String)
8202
8275
  });
8203
- const McpServerList = Schema$1.Struct({ servers: Schema$1.Array(McpServerStatus) });
8204
- const McpServerSaveInput = Schema$1.Struct({ registration: McpServerRegistration });
8205
- const McpServerNameInput = Schema$1.Struct({ name: McpServerName });
8206
8276
  /**
8207
8277
  * Setting one secret value, on this machine only.
8208
8278
  *
@@ -8216,6 +8286,12 @@ const McpServerSecretInput = Schema$1.Struct({
8216
8286
  slot: McpSecretSlot,
8217
8287
  value: Schema$1.String
8218
8288
  });
8289
+ /**
8290
+ * The one thing the panel needs to continue a sign-in: where to send the
8291
+ * browser. Everything else the flow holds — verifier, client id, endpoints —
8292
+ * stays on the server, because the callback lands there, not on the panel.
8293
+ */
8294
+ const McpOAuthBeginResult = Schema$1.Struct({ authorizeUrl: Schema$1.String });
8219
8295
  var McpRegistryError = class extends Schema$1.TaggedErrorClass()("McpRegistryError", { detail: Schema$1.String }) {
8220
8296
  get message() {
8221
8297
  return this.detail;
@@ -8565,6 +8641,8 @@ const WS_METHODS = {
8565
8641
  mcpSave: "mcp.save",
8566
8642
  mcpRemove: "mcp.remove",
8567
8643
  mcpSetSecret: "mcp.setSecret",
8644
+ mcpOAuthBegin: "mcp.oauthBegin",
8645
+ mcpOAuthDisconnect: "mcp.oauthDisconnect",
8568
8646
  sourceControlLookupRepository: "sourceControl.lookupRepository",
8569
8647
  sourceControlCloneRepository: "sourceControl.cloneRepository",
8570
8648
  sourceControlPublishRepository: "sourceControl.publishRepository",
@@ -8834,6 +8912,21 @@ const WsMcpSetSecretRpc = Rpc.make(WS_METHODS.mcpSetSecret, {
8834
8912
  error: Schema$1.Union([McpRegistryError, EnvironmentAuthorizationError])
8835
8913
  });
8836
8914
  /**
8915
+ * Starts an OAuth sign-in for a remote server and answers with the authorize
8916
+ * URL. The panel opens it in a browser; the redirect lands on this server's
8917
+ * own `/oauth/callback`, so nothing else crosses the wire to the client.
8918
+ */
8919
+ const WsMcpOAuthBeginRpc = Rpc.make(WS_METHODS.mcpOAuthBegin, {
8920
+ payload: McpServerNameInput,
8921
+ success: McpOAuthBeginResult,
8922
+ error: Schema$1.Union([McpRegistryError, EnvironmentAuthorizationError])
8923
+ });
8924
+ const WsMcpOAuthDisconnectRpc = Rpc.make(WS_METHODS.mcpOAuthDisconnect, {
8925
+ payload: McpServerNameInput,
8926
+ success: McpServerList,
8927
+ error: Schema$1.Union([McpRegistryError, EnvironmentAuthorizationError])
8928
+ });
8929
+ /**
8837
8930
  * The board, then every change to it, for as long as the client stays
8838
8931
  * subscribed. The leading snapshot is what makes this sufficient on its own —
8839
8932
  * a client does not list first, so there is no window between the two in which
@@ -9211,7 +9304,7 @@ const WsSubscribeAuthAccessRpc = Rpc.make(WS_METHODS.subscribeAuthAccess, {
9211
9304
  error: Schema$1.Union([AuthAccessStreamError, EnvironmentAuthorizationError]),
9212
9305
  stream: true
9213
9306
  });
9214
- const WsRpcGroup = RpcGroup.make(WsServerProbeRpc, WsServerGetConfigRpc, WsServerRefreshProvidersRpc, WsServerUpdateProviderRpc, WsServerUpdateServerRpc, WsServerUpsertKeybindingRpc, WsServerRemoveKeybindingRpc, WsServerGetSettingsRpc, WsServerUpdateSettingsRpc, WsServerDiscoverSourceControlRpc, WsServerGetProviderUsageRpc, WsServerGetTraceDiagnosticsRpc, WsServerGetProcessDiagnosticsRpc, WsServerGetProcessResourceHistoryRpc, WsServerSignalProcessRpc, WsSourceControlLookupRepositoryRpc, WsSourceControlCloneRepositoryRpc, WsSourceControlPublishRepositoryRpc, WsProjectsListEntriesRpc, WsProjectsReadFileRpc, WsProjectsSearchEntriesRpc, WsProjectsWriteFileRpc, WsShellOpenInEditorRpc, WsFilesystemBrowseRpc, WsAssetsCreateUrlRpc, WsSubscribeVcsStatusRpc, WsVcsPullRpc, WsVcsRefreshStatusRpc, WsGitRunStackedActionRpc, WsGitResolvePullRequestRpc, WsGitPreparePullRequestThreadRpc, WsVcsListRefsRpc, WsVcsCreateWorktreeRpc, WsVcsRemoveWorktreeRpc, WsVcsCreateRefRpc, WsVcsSwitchRefRpc, WsVcsInitRpc, WsReviewGetDiffPreviewRpc, WsTerminalOpenRpc, WsTerminalAttachRpc, WsTerminalWriteRpc, WsTerminalResizeRpc, WsTerminalClearRpc, WsTerminalRestartRpc, WsTerminalCloseRpc, WsSubscribeTerminalEventsRpc, WsSubscribeTerminalMetadataRpc, WsPreviewOpenRpc, WsPreviewNavigateRpc, WsPreviewResizeRpc, WsPreviewRefreshRpc, WsPreviewCloseRpc, WsPreviewListRpc, WsPreviewReportStatusRpc, WsPreviewAutomationConnectRpc, WsPreviewAutomationRespondRpc, WsPreviewAutomationFocusHostRpc, WsSubscribePreviewEventsRpc, WsSubscribeDiscoveredLocalServersRpc, WsSubscribeServerConfigRpc, WsSubscribeServerLifecycleRpc, WsSubscribeAuthAccessRpc, WsTasksListRpc, WsTasksGetRpc, WsTasksCreateRpc, WsTasksUpdateRpc, WsTasksDeleteRpc, WsTasksStartThreadRpc, WsSubscribeTasksRpc, WsHubGetSyncStatusRpc, WsHubConnectRpc, WsHubDisconnectRpc, WsHubSetSyncModeRpc, WsHubSetShareModeRpc, WsHubMintTokenRpc, WsSkillsSyncRpc, WsSkillsPublishRpc, WsSkillsPublishAllRpc, WsSkillsUnpublishRpc, WsAssetsReadRpc, WsAssetsSaveRpc, WsAssetsDeleteRpc, WsAssetsCreateLocalRpc, WsAssetsRemoveLocalRpc, WsSkillRegistrySearchRpc, WsSkillRegistryFetchRpc, WsMcpListRpc, WsMcpSaveRpc, WsMcpRemoveRpc, WsMcpSetSecretRpc, WsOrchestrationDispatchCommandRpc, WsOrchestrationGetTurnDiffRpc, WsOrchestrationGetFullThreadDiffRpc, WsOrchestrationGetArchivedShellSnapshotRpc, WsOrchestrationSubscribeShellRpc, WsOrchestrationSubscribeThreadRpc);
9307
+ const WsRpcGroup = RpcGroup.make(WsServerProbeRpc, WsServerGetConfigRpc, WsServerRefreshProvidersRpc, WsServerUpdateProviderRpc, WsServerUpdateServerRpc, WsServerUpsertKeybindingRpc, WsServerRemoveKeybindingRpc, WsServerGetSettingsRpc, WsServerUpdateSettingsRpc, WsServerDiscoverSourceControlRpc, WsServerGetProviderUsageRpc, WsServerGetTraceDiagnosticsRpc, WsServerGetProcessDiagnosticsRpc, WsServerGetProcessResourceHistoryRpc, WsServerSignalProcessRpc, WsSourceControlLookupRepositoryRpc, WsSourceControlCloneRepositoryRpc, WsSourceControlPublishRepositoryRpc, WsProjectsListEntriesRpc, WsProjectsReadFileRpc, WsProjectsSearchEntriesRpc, WsProjectsWriteFileRpc, WsShellOpenInEditorRpc, WsFilesystemBrowseRpc, WsAssetsCreateUrlRpc, WsSubscribeVcsStatusRpc, WsVcsPullRpc, WsVcsRefreshStatusRpc, WsGitRunStackedActionRpc, WsGitResolvePullRequestRpc, WsGitPreparePullRequestThreadRpc, WsVcsListRefsRpc, WsVcsCreateWorktreeRpc, WsVcsRemoveWorktreeRpc, WsVcsCreateRefRpc, WsVcsSwitchRefRpc, WsVcsInitRpc, WsReviewGetDiffPreviewRpc, WsTerminalOpenRpc, WsTerminalAttachRpc, WsTerminalWriteRpc, WsTerminalResizeRpc, WsTerminalClearRpc, WsTerminalRestartRpc, WsTerminalCloseRpc, WsSubscribeTerminalEventsRpc, WsSubscribeTerminalMetadataRpc, WsPreviewOpenRpc, WsPreviewNavigateRpc, WsPreviewResizeRpc, WsPreviewRefreshRpc, WsPreviewCloseRpc, WsPreviewListRpc, WsPreviewReportStatusRpc, WsPreviewAutomationConnectRpc, WsPreviewAutomationRespondRpc, WsPreviewAutomationFocusHostRpc, WsSubscribePreviewEventsRpc, WsSubscribeDiscoveredLocalServersRpc, WsSubscribeServerConfigRpc, WsSubscribeServerLifecycleRpc, WsSubscribeAuthAccessRpc, WsTasksListRpc, WsTasksGetRpc, WsTasksCreateRpc, WsTasksUpdateRpc, WsTasksDeleteRpc, WsTasksStartThreadRpc, WsSubscribeTasksRpc, WsHubGetSyncStatusRpc, WsHubConnectRpc, WsHubDisconnectRpc, WsHubSetSyncModeRpc, WsHubSetShareModeRpc, WsHubMintTokenRpc, WsSkillsSyncRpc, WsSkillsPublishRpc, WsSkillsPublishAllRpc, WsSkillsUnpublishRpc, WsAssetsReadRpc, WsAssetsSaveRpc, WsAssetsDeleteRpc, WsAssetsCreateLocalRpc, WsAssetsRemoveLocalRpc, WsSkillRegistrySearchRpc, WsSkillRegistryFetchRpc, WsMcpListRpc, WsMcpSaveRpc, WsMcpRemoveRpc, WsMcpSetSecretRpc, WsMcpOAuthBeginRpc, WsMcpOAuthDisconnectRpc, WsOrchestrationDispatchCommandRpc, WsOrchestrationGetTurnDiffRpc, WsOrchestrationGetFullThreadDiffRpc, WsOrchestrationGetArchivedShellSnapshotRpc, WsOrchestrationSubscribeShellRpc, WsOrchestrationSubscribeThreadRpc);
9215
9308
  //#endregion
9216
9309
  //#region ../../packages/shared/src/oauthScope.ts
9217
9310
  const OAUTH_SCOPE_TOKEN = /^[\u0021\u0023-\u005b\u005d-\u007e]+$/u;
@@ -9357,7 +9450,7 @@ function deriveAuthClientMetadata(input) {
9357
9450
  //#endregion
9358
9451
  //#region src/auth/EnvironmentAuthPolicy.ts
9359
9452
  var EnvironmentAuthPolicy = class extends Context.Service()("@p4code/cli/auth/EnvironmentAuthPolicy") {};
9360
- const make$71 = Effect.gen(function* () {
9453
+ const make$73 = Effect.gen(function* () {
9361
9454
  const config = yield* ServerConfig$1;
9362
9455
  const isRemoteReachable = isRemoteReachableHost(config.host);
9363
9456
  const policy = config.mode === "desktop" ? isRemoteReachable ? "remote-reachable" : "desktop-managed-local" : isRemoteReachable ? "remote-reachable" : "loopback-browser";
@@ -9375,7 +9468,7 @@ const make$71 = Effect.gen(function* () {
9375
9468
  };
9376
9469
  return EnvironmentAuthPolicy.of({ getDescriptor: () => Effect.succeed(descriptor).pipe(Effect.withSpan("EnvironmentAuthPolicy.getDescriptor")) });
9377
9470
  });
9378
- const layer$67 = Layer.effect(EnvironmentAuthPolicy, make$71);
9471
+ const layer$69 = Layer.effect(EnvironmentAuthPolicy, make$73);
9379
9472
  //#endregion
9380
9473
  //#region src/persistence/Errors.ts
9381
9474
  function summarizeSchemaIssue(issue) {
@@ -9556,7 +9649,7 @@ function toPersistenceSqlOrDecodeError$6(sqlOperation, decodeOperation, correlat
9556
9649
  cause
9557
9650
  });
9558
9651
  }
9559
- const make$70 = Effect.gen(function* () {
9652
+ const make$72 = Effect.gen(function* () {
9560
9653
  const sql = yield* SqlClient.SqlClient;
9561
9654
  const createSessionRow = SqlSchema.void({
9562
9655
  Request: CreateAuthSessionInput,
@@ -9690,7 +9783,7 @@ const make$70 = Effect.gen(function* () {
9690
9783
  setLastConnectedAt
9691
9784
  };
9692
9785
  });
9693
- const layer$66 = Layer.effect(AuthSessionRepository, make$70);
9786
+ const layer$68 = Layer.effect(AuthSessionRepository, make$72);
9694
9787
  //#endregion
9695
9788
  //#region src/auth/ServerSecretStore.ts
9696
9789
  const secretStoreErrorContext = {
@@ -9757,7 +9850,7 @@ const isSecretStoreError = Schema$1.is(SecretStoreError);
9757
9850
  const isPlatformError = (value) => Predicate.isTagged(value, "PlatformError");
9758
9851
  const isSecretAlreadyExistsError = (error) => "cause" in error && isPlatformError(error.cause) && error.cause.reason._tag === "AlreadyExists";
9759
9852
  var ServerSecretStore = class extends Context.Service()("@p4code/cli/auth/ServerSecretStore") {};
9760
- const make$69 = Effect.gen(function* () {
9853
+ const make$71 = Effect.gen(function* () {
9761
9854
  const crypto = yield* Crypto.Crypto;
9762
9855
  const fileSystem = yield* FileSystem.FileSystem;
9763
9856
  const path = yield* Path.Path;
@@ -9827,7 +9920,7 @@ const make$69 = Effect.gen(function* () {
9827
9920
  remove
9828
9921
  });
9829
9922
  });
9830
- const layer$65 = Layer.effect(ServerSecretStore, make$69);
9923
+ const layer$67 = Layer.effect(ServerSecretStore, make$71);
9831
9924
  //#endregion
9832
9925
  //#region src/auth/SessionStore.ts
9833
9926
  var MalformedSessionTokenError = class extends Schema$1.TaggedErrorClass()("MalformedSessionTokenError", {}) {
@@ -10065,7 +10158,7 @@ function toAuthClientSession(input) {
10065
10158
  current: false
10066
10159
  };
10067
10160
  }
10068
- const make$68 = Effect.gen(function* () {
10161
+ const make$70 = Effect.gen(function* () {
10069
10162
  const crypto = yield* Crypto.Crypto;
10070
10163
  const serverConfig = yield* ServerConfig$1;
10071
10164
  const secretStore = yield* ServerSecretStore;
@@ -10379,7 +10472,7 @@ const make$68 = Effect.gen(function* () {
10379
10472
  markDisconnected
10380
10473
  });
10381
10474
  });
10382
- const layer$64 = Layer.effect(SessionStore, make$68).pipe(Layer.provideMerge(layer$66));
10475
+ const layer$66 = Layer.effect(SessionStore, make$70).pipe(Layer.provideMerge(layer$68));
10383
10476
  //#endregion
10384
10477
  //#region src/persistence/AuthPairingLinks.ts
10385
10478
  const AuthPairingLinkRecord = Schema$1.Struct({
@@ -10440,7 +10533,7 @@ function toPersistenceSqlOrDecodeError$5(sqlOperation, decodeOperation, correlat
10440
10533
  cause
10441
10534
  });
10442
10535
  }
10443
- const make$67 = Effect.gen(function* () {
10536
+ const make$69 = Effect.gen(function* () {
10444
10537
  const sql = yield* SqlClient.SqlClient;
10445
10538
  const createPairingLinkRow = SqlSchema.void({
10446
10539
  Request: CreateAuthPairingLinkInput,
@@ -10575,7 +10668,7 @@ const make$67 = Effect.gen(function* () {
10575
10668
  getByCredential
10576
10669
  };
10577
10670
  });
10578
- const layer$63 = Layer.effect(AuthPairingLinkRepository, make$67);
10671
+ const layer$65 = Layer.effect(AuthPairingLinkRepository, make$69);
10579
10672
  //#endregion
10580
10673
  //#region src/auth/PairingGrantStore.ts
10581
10674
  var UnknownBootstrapCredentialError = class extends Schema$1.TaggedErrorClass()("UnknownBootstrapCredentialError", {}) {
@@ -10670,7 +10763,7 @@ const DEV_STARTUP_TTL_HOURS = Duration.hours(24);
10670
10763
  const PAIRING_TOKEN_ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
10671
10764
  const PAIRING_TOKEN_LENGTH = 12;
10672
10765
  const PAIRING_TOKEN_REJECTION_LIMIT = Math.floor(256 / 32) * 32;
10673
- const make$66 = Effect.gen(function* () {
10766
+ const make$68 = Effect.gen(function* () {
10674
10767
  const crypto = yield* Crypto.Crypto;
10675
10768
  const config = yield* ServerConfig$1;
10676
10769
  const pairingLinks = yield* AuthPairingLinkRepository;
@@ -10868,7 +10961,7 @@ const make$66 = Effect.gen(function* () {
10868
10961
  consume
10869
10962
  });
10870
10963
  });
10871
- const layer$62 = Layer.effect(PairingGrantStore, make$66).pipe(Layer.provideMerge(layer$63));
10964
+ const layer$64 = Layer.effect(PairingGrantStore, make$68).pipe(Layer.provideMerge(layer$65));
10872
10965
  //#endregion
10873
10966
  //#region src/persistence/Migrations/001_OrchestrationEvents.ts
10874
10967
  var _001_OrchestrationEvents_default = Effect.gen(function* () {
@@ -12723,7 +12816,7 @@ function parseBearerToken(request) {
12723
12816
  const token = header.slice(7).trim();
12724
12817
  return token.length > 0 ? token : null;
12725
12818
  }
12726
- const make$65 = Effect.gen(function* () {
12819
+ const make$67 = Effect.gen(function* () {
12727
12820
  const policy = yield* EnvironmentAuthPolicy;
12728
12821
  const bootstrapCredentials = yield* PairingGrantStore;
12729
12822
  const sessions = yield* SessionStore;
@@ -12918,9 +13011,9 @@ const make$65 = Effect.gen(function* () {
12918
13011
  issueStartupPairingUrl
12919
13012
  });
12920
13013
  });
12921
- const layer$61 = Layer.effect(EnvironmentAuth, make$65).pipe(Layer.provideMerge(layer$62), Layer.provideMerge(layer$64), Layer.provideMerge(layer$67));
12922
- const storageLayer = Layer.mergeAll(layer$65, layerConfig);
12923
- const runtimeLayer = layer$61.pipe(Layer.provideMerge(storageLayer));
13014
+ const layer$63 = Layer.effect(EnvironmentAuth, make$67).pipe(Layer.provideMerge(layer$64), Layer.provideMerge(layer$66), Layer.provideMerge(layer$69));
13015
+ const storageLayer = Layer.mergeAll(layer$67, layerConfig);
13016
+ const runtimeLayer = layer$63.pipe(Layer.provideMerge(storageLayer));
12924
13017
  //#endregion
12925
13018
  //#region src/cliAuthFormat.ts
12926
13019
  const newline = "\n";
@@ -13675,7 +13768,7 @@ const runWithEnvironmentAuth = (flags, run, options) => Effect.gen(function* ()
13675
13768
  }
13676
13769
  return yield* Effect.gen(function* () {
13677
13770
  return yield* run(yield* EnvironmentAuth);
13678
- }).pipe(Effect.provide(Layer.mergeAll(runtimeLayer).pipe(Layer.provide(layer$68(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)))));
13771
+ }).pipe(Effect.provide(Layer.mergeAll(runtimeLayer).pipe(Layer.provide(layer$70(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)))));
13679
13772
  });
13680
13773
  const ttlFlag = Flag.string("ttl").pipe(Flag.withSchema(DurationFromString), Flag.withDescription("TTL, for example `5m`, `1h`, `30d`, or `15 minutes`."), Flag.optional);
13681
13774
  const jsonFlag = Flag.boolean("json").pipe(Flag.withDescription("Emit JSON instead of human-readable output."), Flag.withDefault(false));
@@ -13815,7 +13908,7 @@ const DEFAULT_LIMITS = {
13815
13908
  windowMillis: FAILURE_WINDOW_MS,
13816
13909
  blockMillis: BLOCK_DURATION_MS
13817
13910
  };
13818
- const make$64 = Effect.fn("HubAuthThrottle.make")(function* (limits = DEFAULT_LIMITS) {
13911
+ const make$66 = Effect.fn("HubAuthThrottle.make")(function* (limits = DEFAULT_LIMITS) {
13819
13912
  const state = yield* Ref.make(initialThrottleState);
13820
13913
  return HubAuthThrottle.of({
13821
13914
  shouldRefuse: Effect.gen(function* () {
@@ -13829,7 +13922,7 @@ const make$64 = Effect.fn("HubAuthThrottle.make")(function* (limits = DEFAULT_LI
13829
13922
  })
13830
13923
  });
13831
13924
  });
13832
- const layer$60 = Layer.effect(HubAuthThrottle, make$64());
13925
+ const layer$62 = Layer.effect(HubAuthThrottle, make$66());
13833
13926
  //#endregion
13834
13927
  //#region src/hub/HubAuth.ts
13835
13928
  /**
@@ -14628,7 +14721,7 @@ const deleteAssetRoute = HttpRouter.add("DELETE", "/assets/:kind/:name", respond
14628
14721
  const outcome = yield* (yield* AgentAssetRepository).deleteByName(requested);
14629
14722
  return outcome._tag === "conflict" ? assetConflict(Option.getOrNull(outcome.current)) : HttpServerResponse.empty({ status: 204 });
14630
14723
  })));
14631
- const layer$59 = Layer.mergeAll(healthRoute, listTasksRoute, getTaskRoute, createTaskRoute, updateTaskRoute, deleteTaskRoute, listAssetsRoute, getAssetRoute, putAssetRoute, deleteAssetRoute);
14724
+ const layer$61 = Layer.mergeAll(healthRoute, listTasksRoute, getTaskRoute, createTaskRoute, updateTaskRoute, deleteTaskRoute, listAssetsRoute, getAssetRoute, putAssetRoute, deleteAssetRoute);
14632
14725
  //#endregion
14633
14726
  //#region src/hub/Migrations/001_Tasks.ts
14634
14727
  /**
@@ -14907,7 +15000,7 @@ const announce = Layer.effectDiscard(Effect.gen(function* () {
14907
15000
  });
14908
15001
  if (isRemoteReachableHost(config.host)) yield* Effect.logWarning("Hub is reachable beyond loopback. The bearer token is the only control — repeated auth failures are not rate limited.", { host: config.host });
14909
15002
  }));
14910
- const makeHubLayer = HttpRouter.serve(layer$59).pipe(Layer.provideMerge(announce), Layer.provideMerge(HubHttpServerLive), Layer.provideMerge(layer$60), Layer.provideMerge(TaskRepositoryLive), Layer.provideMerge(AgentAssetRepositoryLive), Layer.provideMerge(HubPersistenceLive));
15003
+ const makeHubLayer = HttpRouter.serve(layer$61).pipe(Layer.provideMerge(announce), Layer.provideMerge(HubHttpServerLive), Layer.provideMerge(layer$62), Layer.provideMerge(TaskRepositoryLive), Layer.provideMerge(AgentAssetRepositoryLive), Layer.provideMerge(HubPersistenceLive));
14911
15004
  const runHub = Layer.launch(makeHubLayer);
14912
15005
  //#endregion
14913
15006
  //#region src/atomicWrite.ts
@@ -15031,7 +15124,7 @@ function stripDefaultServerSettings(current, defaults) {
15031
15124
  }
15032
15125
  return Object.is(current, defaults) ? void 0 : current;
15033
15126
  }
15034
- const make$63 = Effect.gen(function* () {
15127
+ const make$65 = Effect.gen(function* () {
15035
15128
  const { settingsPath } = yield* ServerConfig$1;
15036
15129
  const fs = yield* FileSystem.FileSystem;
15037
15130
  const pathService = yield* Path.Path;
@@ -15252,7 +15345,7 @@ const make$63 = Effect.gen(function* () {
15252
15345
  }
15253
15346
  };
15254
15347
  });
15255
- const layer$58 = Layer.effect(ServerSettingsService, make$63);
15348
+ const layer$60 = Layer.effect(ServerSettingsService, make$65);
15256
15349
  //#endregion
15257
15350
  //#region src/pathExpansion.ts
15258
15351
  /**
@@ -15461,6 +15554,650 @@ const discoverClaudeSkills = Effect.fn("discoverClaudeSkills")(function* (config
15461
15554
  }
15462
15555
  return [...skillsByName.values()].sort((left, right) => left.name.localeCompare(right.name));
15463
15556
  });
15557
+ //#endregion
15558
+ //#region src/mcp/ClaudeMcpFiles.ts
15559
+ /**
15560
+ * The MCP servers in Claude Code's own configuration files.
15561
+ *
15562
+ * Two homes, both the CLI's and both primary over p4code's registry:
15563
+ *
15564
+ * - **User scope**: the `mcpServers` key of `~/.claude.json`. Every Claude
15565
+ * Code session reads it, inside p4code and out.
15566
+ * - **Project scope**: `<project>/.mcp.json`, committed with the repository
15567
+ * and read by every session working in it.
15568
+ *
15569
+ * The settings panel manages both directly — list, add, edit, remove — so
15570
+ * this service writes them. **A user-scope write touches only the
15571
+ * `mcpServers` key**; `~/.claude.json` also holds `oauthAccount`, `machineID`
15572
+ * and a `projects` map, and everything outside the one key is carried
15573
+ * through a write as parsed. This is the same read-modify-write the
15574
+ * `claude mcp add` command performs, with the same last-writer-wins caveat
15575
+ * when a CLI session saves concurrently.
15576
+ *
15577
+ * A malformed entry, a name that would not survive as a registry filename, or
15578
+ * a missing file all cost only what they describe: that entry, or that one
15579
+ * file's list, never the panel.
15580
+ *
15581
+ * @module mcp/ClaudeMcpFiles
15582
+ */
15583
+ const isServerName = Schema$1.is(McpServerName);
15584
+ /** The file a project's scope lives in, beside its lockfiles and dotfiles. */
15585
+ const PROJECT_MCP_FILENAME = ".mcp.json";
15586
+ /**
15587
+ * One entry as the CLI writes it. `type` is absent for the CLI's original
15588
+ * stdio-only shape, so it defaults rather than discriminates.
15589
+ */
15590
+ const ClaudeMcpEntry = Schema$1.Struct({
15591
+ type: Schema$1.optional(Schema$1.Literals([
15592
+ "stdio",
15593
+ "http",
15594
+ "sse"
15595
+ ])),
15596
+ command: Schema$1.optional(Schema$1.String),
15597
+ args: Schema$1.optional(Schema$1.Array(Schema$1.String)),
15598
+ env: Schema$1.optional(Schema$1.Record(Schema$1.String, Schema$1.String)),
15599
+ url: Schema$1.optional(Schema$1.String),
15600
+ headers: Schema$1.optional(Schema$1.Record(Schema$1.String, Schema$1.String))
15601
+ });
15602
+ /**
15603
+ * A config file as this service needs to see it: the one key it owns, with
15604
+ * every other key carried as parsed. Both `~/.claude.json` and `.mcp.json`
15605
+ * decode through this — the former has many other keys, the latter none.
15606
+ */
15607
+ const McpConfigFile = Schema$1.StructWithRest(Schema$1.Struct({ mcpServers: Schema$1.optional(Schema$1.Record(Schema$1.String, Schema$1.Unknown)) }), [Schema$1.Record(Schema$1.String, Schema$1.Unknown)]);
15608
+ const McpConfigFileFromJson = Schema$1.fromJsonString(McpConfigFile);
15609
+ const decodeConfigFile = Schema$1.decodeUnknownExit(McpConfigFileFromJson);
15610
+ const encodeConfigFile = Schema$1.encodeSync(McpConfigFileFromJson);
15611
+ const decodeEntry = Schema$1.decodeUnknownExit(ClaudeMcpEntry);
15612
+ /** Where the CLI keeps user scope: `$CLAUDE_CONFIG_DIR/.claude.json` when set, else `~/.claude.json`. */
15613
+ const claudeUserConfigPath = Effect.fn("claudeUserConfigPath")(function* () {
15614
+ const path = yield* Path.Path;
15615
+ const configDir = process.env["CLAUDE_CONFIG_DIR"]?.trim() ?? "";
15616
+ return configDir.length > 0 ? path.join(path.resolve(configDir), ".claude.json") : path.join(NodeOS.homedir(), ".claude.json");
15617
+ });
15618
+ /**
15619
+ * Map one CLI entry to a registration, or nothing when it cannot be one.
15620
+ *
15621
+ * `enabled: true` because presence in the CLI's file is what enabled means
15622
+ * there. Exported for tests.
15623
+ */
15624
+ function registrationFromClaudeEntry(name, entry) {
15625
+ if (!isServerName(name)) return void 0;
15626
+ const decoded = decodeEntry(entry);
15627
+ if (decoded._tag !== "Success") return void 0;
15628
+ const value = decoded.value;
15629
+ const type = value.type ?? "stdio";
15630
+ if (type === "stdio") {
15631
+ if (value.command === void 0 || value.command.trim().length === 0) return void 0;
15632
+ return {
15633
+ name,
15634
+ transport: "stdio",
15635
+ enabled: true,
15636
+ command: value.command,
15637
+ ...value.args !== void 0 && value.args.length > 0 ? { args: value.args } : {},
15638
+ ...value.env !== void 0 && Object.keys(value.env).length > 0 ? { env: value.env } : {}
15639
+ };
15640
+ }
15641
+ if (value.url === void 0 || value.url.trim().length === 0) return void 0;
15642
+ return {
15643
+ name,
15644
+ transport: type,
15645
+ enabled: true,
15646
+ url: value.url,
15647
+ ...value.headers !== void 0 && Object.keys(value.headers).length > 0 ? { headers: value.headers } : {}
15648
+ };
15649
+ }
15650
+ /**
15651
+ * The inverse mapping, for writes. `enabled` is dropped: the CLI has no such
15652
+ * flag, and presence in the file is what enabled means there. Exported for
15653
+ * tests, so the two mappings can be checked as a round trip.
15654
+ */
15655
+ function claudeEntryFromRegistration(registration) {
15656
+ if (registration.transport === "stdio") return {
15657
+ type: "stdio",
15658
+ command: registration.command,
15659
+ ...registration.args !== void 0 && registration.args.length > 0 ? { args: registration.args } : {},
15660
+ ...registration.env !== void 0 && Object.keys(registration.env).length > 0 ? { env: registration.env } : {}
15661
+ };
15662
+ return {
15663
+ type: registration.transport,
15664
+ url: registration.url,
15665
+ ...registration.headers !== void 0 && Object.keys(registration.headers).length > 0 ? { headers: registration.headers } : {}
15666
+ };
15667
+ }
15668
+ var ClaudeMcpFiles = class extends Context.Service()("@p4code/cli/mcp/ClaudeMcpFiles") {};
15669
+ const make$64 = Effect.gen(function* () {
15670
+ const fileSystem = yield* FileSystem.FileSystem;
15671
+ const path = yield* Path.Path;
15672
+ const services = yield* Effect.context();
15673
+ const readEntries = Effect.fn("ClaudeMcpFiles.readEntries")(function* (filePath) {
15674
+ const raw = yield* fileSystem.readFileString(filePath).pipe(Effect.orElseSucceed(() => void 0));
15675
+ if (raw === void 0) return [];
15676
+ const decoded = decodeConfigFile(raw);
15677
+ if (decoded._tag !== "Success") return [];
15678
+ const found = [];
15679
+ for (const [name, entry] of Object.entries(decoded.value.mcpServers ?? {})) {
15680
+ const registration = registrationFromClaudeEntry(name, entry);
15681
+ if (registration !== void 0) found.push(registration);
15682
+ }
15683
+ return found;
15684
+ });
15685
+ const readAt = (filePath) => filePath.pipe(Effect.flatMap((resolved) => readEntries(resolved)), Effect.provide(services), Effect.catchCause((cause) => Effect.logWarning("claude mcp config read failed", { cause }).pipe(Effect.as([]))));
15686
+ /**
15687
+ * Read, change the one key, write back atomically.
15688
+ *
15689
+ * A file that exists but will not parse fails the write loudly instead of
15690
+ * being replaced with `{ mcpServers }`: whatever is in it — including a
15691
+ * `~/.claude.json` full of the CLI's own state — is worth more than the
15692
+ * edit. A missing file starts from empty, which is how `.mcp.json` is born.
15693
+ */
15694
+ const changeFile = Effect.fn("ClaudeMcpFiles.changeFile")(function* (filePath, change) {
15695
+ const raw = yield* fileSystem.readFileString(filePath).pipe(Effect.orElseSucceed(() => void 0), Effect.provide(services));
15696
+ const decoded = raw === void 0 ? void 0 : decodeConfigFile(raw);
15697
+ if (decoded !== void 0 && decoded._tag !== "Success") return yield* new McpRegistryError({ detail: `${filePath} exists but could not be parsed, so it was left alone.` });
15698
+ const root = decoded === void 0 ? {} : decoded.value;
15699
+ const nextServers = change({ ...root.mcpServers });
15700
+ yield* fileSystem.makeDirectory(path.dirname(filePath), { recursive: true }).pipe(Effect.ignore, Effect.provide(services));
15701
+ yield* writeFileStringAtomically({
15702
+ filePath,
15703
+ contents: `${encodeConfigFile({
15704
+ ...root,
15705
+ mcpServers: nextServers
15706
+ })}\n`
15707
+ }).pipe(Effect.provide(services), Effect.mapError(() => new McpRegistryError({ detail: `Could not write ${filePath}.` })));
15708
+ });
15709
+ const projectFile = (projectDir) => path.join(path.resolve(projectDir), PROJECT_MCP_FILENAME);
15710
+ const upsertAt = (filePath) => Effect.fn("ClaudeMcpFiles.upsertAt")(function* (registration) {
15711
+ const resolved = yield* filePath.pipe(Effect.provide(services));
15712
+ yield* changeFile(resolved, (entries) => ({
15713
+ ...entries,
15714
+ [registration.name]: claudeEntryFromRegistration(registration)
15715
+ }));
15716
+ });
15717
+ const removeAt = (filePath) => Effect.fn("ClaudeMcpFiles.removeAt")(function* (name) {
15718
+ const resolved = yield* filePath.pipe(Effect.provide(services));
15719
+ yield* changeFile(resolved, (entries) => {
15720
+ const { [name]: _removed, ...rest } = entries;
15721
+ return rest;
15722
+ });
15723
+ });
15724
+ const userPath = claudeUserConfigPath();
15725
+ return {
15726
+ readUser: readAt(userPath),
15727
+ upsertUser: (registration) => upsertAt(userPath)(registration),
15728
+ removeUser: (name) => removeAt(userPath)(name),
15729
+ readProject: (projectDir) => readAt(Effect.succeed(projectFile(projectDir))),
15730
+ upsertProject: (projectDir, registration) => upsertAt(Effect.succeed(projectFile(projectDir)))(registration),
15731
+ removeProject: (projectDir, name) => removeAt(Effect.succeed(projectFile(projectDir)))(name)
15732
+ };
15733
+ });
15734
+ const layer$59 = Layer.effect(ClaudeMcpFiles, make$64);
15735
+ Layer.succeed(ClaudeMcpFiles, {
15736
+ readUser: Effect.succeed([]),
15737
+ upsertUser: () => Effect.fail(new McpRegistryError({ detail: "No Claude config in tests." })),
15738
+ removeUser: () => Effect.fail(new McpRegistryError({ detail: "No Claude config in tests." })),
15739
+ readProject: () => Effect.succeed([]),
15740
+ upsertProject: () => Effect.fail(new McpRegistryError({ detail: "No Claude config in tests." })),
15741
+ removeProject: () => Effect.fail(new McpRegistryError({ detail: "No Claude config in tests." }))
15742
+ });
15743
+ //#endregion
15744
+ //#region src/mcp/McpOAuth.ts
15745
+ /**
15746
+ * OAuth sign-in for remote MCP servers, per the MCP authorization spec.
15747
+ *
15748
+ * The whole flow runs on this server: RFC 9728 protected-resource discovery,
15749
+ * RFC 8414 authorization-server metadata, RFC 7591 dynamic client
15750
+ * registration, PKCE, and the token exchange when the browser lands on
15751
+ * `/oauth/callback`. The panel only ever sees one string — the authorize URL —
15752
+ * because everything else in the flow is either a secret or only meaningful to
15753
+ * the machine the callback returns to.
15754
+ *
15755
+ * **Everything learned per server is one JSON secret** in `ServerSecretStore`
15756
+ * (`mcp.<server>.oauth`): client id, endpoints, tokens. Registrations sync
15757
+ * between machines; this record deliberately does not, which is what makes
15758
+ * "each machine signs in for itself" true by construction rather than policy.
15759
+ *
15760
+ * @module mcp/McpOAuth
15761
+ */
15762
+ /** How long a probe's verdict is trusted before the server is asked again. */
15763
+ const PROBE_CACHE_TTL_MS = 300 * 1e3;
15764
+ /** An authorize URL nobody returned from within this window is abandoned. */
15765
+ const PENDING_FLOW_TTL_MS = 600 * 1e3;
15766
+ /** A token this close to expiry is refreshed rather than handed to a session. */
15767
+ const TOKEN_EXPIRY_MARGIN_MS = 60 * 1e3;
15768
+ /** Outbound discovery, registration and token requests all use this cap. */
15769
+ const OAUTH_REQUEST_TIMEOUT_MS = 10 * 1e3;
15770
+ const CODE_VERIFIER_BYTES = 32;
15771
+ const STATE_BYTES = 16;
15772
+ /** Sent in the probe's initialize request; any spec revision the server accepts will do. */
15773
+ const MCP_PROBE_PROTOCOL_VERSION = "2025-06-18";
15774
+ /** What the authorization server's consent screen shows. See `registerClient`. */
15775
+ const OAUTH_CLIENT_NAME = "Claude Code (p4code)";
15776
+ /** The path the environment server answers on; also sent as the redirect URI. */
15777
+ const OAUTH_CALLBACK_PATH = "/oauth/callback";
15778
+ const StoredTokens = Schema$1.Struct({
15779
+ accessToken: Schema$1.String,
15780
+ refreshToken: Schema$1.optional(Schema$1.String),
15781
+ /** Epoch milliseconds. Absent when the server did not say. */
15782
+ expiresAt: Schema$1.optional(Schema$1.Number)
15783
+ });
15784
+ /**
15785
+ * Everything this machine holds about one server's OAuth relationship.
15786
+ *
15787
+ * Endpoints and client id live beside the tokens so that disconnect can drop
15788
+ * the tokens and a later reconnect can skip re-registration — a client id is
15789
+ * not a credential, but re-registering on every sign-in litters the
15790
+ * authorization server with one-use clients.
15791
+ */
15792
+ const StoredOAuth = Schema$1.Struct({
15793
+ clientId: Schema$1.String,
15794
+ clientSecret: Schema$1.optional(Schema$1.String),
15795
+ authorizationEndpoint: Schema$1.String,
15796
+ tokenEndpoint: Schema$1.String,
15797
+ /** RFC 8707 resource indicator: the canonical MCP server URL. */
15798
+ resource: Schema$1.optional(Schema$1.String),
15799
+ scope: Schema$1.optional(Schema$1.String),
15800
+ tokens: Schema$1.optional(StoredTokens)
15801
+ });
15802
+ const StoredOAuthFromJson = Schema$1.fromJsonString(StoredOAuth);
15803
+ const decodeStored = Schema$1.decodeUnknownExit(StoredOAuthFromJson);
15804
+ const encodeStored = Schema$1.encodeSync(StoredOAuthFromJson);
15805
+ /**
15806
+ * Parse a `WWW-Authenticate` challenge into its scheme and parameters.
15807
+ *
15808
+ * Handles the shape MCP servers actually send — `Bearer` followed by
15809
+ * comma-separated `key="value"` or bare `key=value` pairs. Exported for tests;
15810
+ * a wrong parse here turns "needs sign-in" into "broken server".
15811
+ */
15812
+ function parseWwwAuthenticate(header) {
15813
+ const trimmed = header.trim();
15814
+ const spaceIndex = trimmed.indexOf(" ");
15815
+ const scheme = (spaceIndex === -1 ? trimmed : trimmed.slice(0, spaceIndex)).toLowerCase();
15816
+ const rest = spaceIndex === -1 ? "" : trimmed.slice(spaceIndex + 1);
15817
+ const params = {};
15818
+ for (const match of rest.matchAll(/([a-zA-Z0-9_-]+)\s*=\s*(?:"([^"]*)"|([^\s,]+))/gu)) {
15819
+ const key = match[1];
15820
+ const value = match[2] ?? match[3];
15821
+ if (key !== void 0 && value !== void 0) params[key.toLowerCase()] = value;
15822
+ }
15823
+ return {
15824
+ scheme,
15825
+ params
15826
+ };
15827
+ }
15828
+ /**
15829
+ * The RFC 8414 lookup locations for an issuer, in the order worth trying.
15830
+ *
15831
+ * Path insertion first — `https://a/.well-known/oauth-authorization-server/x`
15832
+ * for issuer `https://a/x` — then the OIDC variants, because real servers are
15833
+ * split between the two specs and a miss costs one request.
15834
+ */
15835
+ function authServerMetadataUrls(issuer) {
15836
+ const url = new URL(issuer);
15837
+ const path = url.pathname === "/" ? "" : url.pathname.replace(/\/$/u, "");
15838
+ return [
15839
+ `${url.origin}/.well-known/oauth-authorization-server${path}`,
15840
+ `${url.origin}/.well-known/openid-configuration${path}`,
15841
+ ...path === "" ? [] : [`${url.origin}${path}/.well-known/openid-configuration`]
15842
+ ];
15843
+ }
15844
+ const AuthServerMetadata = Schema$1.Struct({
15845
+ authorization_endpoint: Schema$1.String,
15846
+ token_endpoint: Schema$1.String,
15847
+ registration_endpoint: Schema$1.optional(Schema$1.String),
15848
+ scopes_supported: Schema$1.optional(Schema$1.Array(Schema$1.String))
15849
+ });
15850
+ const ProtectedResourceMetadata = Schema$1.Struct({
15851
+ authorization_servers: Schema$1.optional(Schema$1.Array(Schema$1.String)),
15852
+ scopes_supported: Schema$1.optional(Schema$1.Array(Schema$1.String))
15853
+ });
15854
+ const ClientRegistrationResponse = Schema$1.Struct({
15855
+ client_id: Schema$1.String,
15856
+ client_secret: Schema$1.optional(Schema$1.String)
15857
+ });
15858
+ const TokenResponse = Schema$1.Struct({
15859
+ access_token: Schema$1.String,
15860
+ refresh_token: Schema$1.optional(Schema$1.String),
15861
+ expires_in: Schema$1.optional(Schema$1.Number)
15862
+ });
15863
+ const decodeJsonBody = Schema$1.decodeUnknownExit(Schema$1.UnknownFromJsonString);
15864
+ const decodeAuthServerMetadata = Schema$1.decodeUnknownExit(AuthServerMetadata);
15865
+ const decodeProtectedResourceMetadata = Schema$1.decodeUnknownExit(ProtectedResourceMetadata);
15866
+ const decodeClientRegistration = Schema$1.decodeUnknownExit(ClientRegistrationResponse);
15867
+ const decodeTokenResponse = Schema$1.decodeUnknownExit(TokenResponse);
15868
+ var McpOAuth = class extends Context.Service()("@p4code/cli/mcp/McpOAuth") {};
15869
+ const registryError = (detail) => new McpRegistryError({ detail });
15870
+ const make$63 = Effect.gen(function* () {
15871
+ const config = yield* ServerConfig$1;
15872
+ const secrets = yield* ServerSecretStore;
15873
+ const http = yield* HttpClient.HttpClient;
15874
+ const crypto = yield* Crypto.Crypto;
15875
+ const pendingFlows = yield* Ref.make(/* @__PURE__ */ new Map());
15876
+ const probeCache = yield* Ref.make(/* @__PURE__ */ new Map());
15877
+ const redirectUri = `http://127.0.0.1:${config.port}${OAUTH_CALLBACK_PATH}`;
15878
+ const nowMillis = Effect.map(DateTime.now, DateTime.toEpochMillis);
15879
+ const readRecord = Effect.fn("McpOAuth.readRecord")(function* (server) {
15880
+ const stored = yield* secrets.get(mcpOAuthStorageName(server)).pipe(Effect.orElseSucceed(() => Option.none()));
15881
+ if (Option.isNone(stored)) return Option.none();
15882
+ const decoded = decodeStored(new TextDecoder().decode(stored.value));
15883
+ return decoded._tag === "Success" ? Option.some(decoded.value) : Option.none();
15884
+ });
15885
+ const writeRecord = Effect.fn("McpOAuth.writeRecord")(function* (server, record) {
15886
+ yield* secrets.set(mcpOAuthStorageName(server), new TextEncoder().encode(encodeStored(record))).pipe(Effect.catchCause((cause) => Effect.logWarning("mcp oauth record write failed", {
15887
+ cause,
15888
+ server
15889
+ })));
15890
+ });
15891
+ const fetchJson = Effect.fn("McpOAuth.fetchJson")(function* (request) {
15892
+ const response = yield* http.execute(HttpClientRequest.setHeader(request, "accept", "application/json")).pipe(Effect.timeout(OAUTH_REQUEST_TIMEOUT_MS));
15893
+ const text = yield* response.text;
15894
+ const parsed = decodeJsonBody(text);
15895
+ return {
15896
+ status: response.status,
15897
+ json: parsed._tag === "Success" ? parsed.value : void 0
15898
+ };
15899
+ });
15900
+ /**
15901
+ * Ask the server, without credentials, whether it wants any.
15902
+ *
15903
+ * A real MCP initialize request rather than a bare GET, because the GET is
15904
+ * not part of the streamable-http contract and real servers 405 it — Figma
15905
+ * does — while answering the initialize POST with the 401 challenge this
15906
+ * probe exists to see.
15907
+ */
15908
+ const probe = Effect.fn("McpOAuth.probe")(function* (registration) {
15909
+ const now = yield* nowMillis;
15910
+ const cached = (yield* Ref.get(probeCache)).get(registration.url);
15911
+ if (cached !== void 0 && now - cached.at < PROBE_CACHE_TTL_MS) return cached;
15912
+ const verdict = yield* http.execute(HttpClientRequest.bodyJsonUnsafe(HttpClientRequest.setHeaders(HttpClientRequest.post(registration.url), {
15913
+ ...registration.headers,
15914
+ accept: "application/json, text/event-stream"
15915
+ }), {
15916
+ jsonrpc: "2.0",
15917
+ id: 0,
15918
+ method: "initialize",
15919
+ params: {
15920
+ protocolVersion: MCP_PROBE_PROTOCOL_VERSION,
15921
+ capabilities: {},
15922
+ clientInfo: {
15923
+ name: "p4code",
15924
+ version: "0.0.0"
15925
+ }
15926
+ }
15927
+ })).pipe(Effect.timeout(OAUTH_REQUEST_TIMEOUT_MS), Effect.map((response) => {
15928
+ const challengeHeader = response.headers["www-authenticate"];
15929
+ const challenge = response.status === 401 && challengeHeader !== void 0 ? parseWwwAuthenticate(challengeHeader) : void 0;
15930
+ return {
15931
+ at: now,
15932
+ requiresAuth: response.status === 401,
15933
+ resourceMetadataUrl: challenge?.params["resource_metadata"],
15934
+ scope: challenge?.params["scope"]
15935
+ };
15936
+ }), Effect.orElseSucceed(() => ({
15937
+ at: now,
15938
+ requiresAuth: false,
15939
+ resourceMetadataUrl: void 0,
15940
+ scope: void 0
15941
+ })));
15942
+ yield* Ref.update(probeCache, (cache) => {
15943
+ const next = new Map(cache);
15944
+ next.set(registration.url, verdict);
15945
+ return next;
15946
+ });
15947
+ return verdict;
15948
+ });
15949
+ const forgetProbe = (url) => Ref.update(probeCache, (cache) => {
15950
+ const next = new Map(cache);
15951
+ next.delete(url);
15952
+ return next;
15953
+ });
15954
+ const statusFor = (registration) => Effect.gen(function* () {
15955
+ const record = yield* readRecord(registration.name);
15956
+ if (Option.isSome(record) && record.value.tokens !== void 0) return Option.some({
15957
+ required: true,
15958
+ connected: true
15959
+ });
15960
+ if ((yield* probe(registration)).requiresAuth || Option.isSome(record)) return Option.some({
15961
+ required: true,
15962
+ connected: false
15963
+ });
15964
+ return Option.none();
15965
+ }).pipe(Effect.catchCause((cause) => Effect.logWarning("mcp oauth status failed", {
15966
+ cause,
15967
+ server: registration.name
15968
+ }).pipe(Effect.as(Option.none()))));
15969
+ /**
15970
+ * Find the authorization server for an MCP endpoint.
15971
+ *
15972
+ * RFC 9728 first: the 401 challenge names a protected-resource metadata URL,
15973
+ * which names the authorization servers. When the challenge does not say —
15974
+ * the spec's fallback — the MCP origin itself is tried as the issuer.
15975
+ */
15976
+ const discover = Effect.fn("McpOAuth.discover")(function* (registration) {
15977
+ yield* forgetProbe(registration.url);
15978
+ const verdict = yield* probe(registration);
15979
+ let issuers = [new URL(registration.url).origin];
15980
+ let resourceScopes;
15981
+ const metadataUrl = verdict.resourceMetadataUrl ?? `${new URL(registration.url).origin}/.well-known/oauth-protected-resource${new URL(registration.url).pathname.replace(/\/$/u, "")}`;
15982
+ const resourceMetadata = yield* fetchJson(HttpClientRequest.get(metadataUrl)).pipe(Effect.map(({ status, json }) => {
15983
+ if (status !== 200) return void 0;
15984
+ const decoded = decodeProtectedResourceMetadata(json);
15985
+ return decoded._tag === "Success" ? decoded.value : void 0;
15986
+ }), Effect.orElseSucceed(() => void 0));
15987
+ if (resourceMetadata !== void 0) {
15988
+ if ((resourceMetadata.authorization_servers?.length ?? 0) > 0) issuers = resourceMetadata.authorization_servers ?? issuers;
15989
+ resourceScopes = resourceMetadata.scopes_supported;
15990
+ }
15991
+ for (const issuer of issuers) for (const candidate of authServerMetadataUrls(issuer)) {
15992
+ const metadata = yield* fetchJson(HttpClientRequest.get(candidate)).pipe(Effect.map(({ status, json }) => {
15993
+ if (status !== 200) return void 0;
15994
+ const decoded = decodeAuthServerMetadata(json);
15995
+ return decoded._tag === "Success" ? decoded.value : void 0;
15996
+ }), Effect.orElseSucceed(() => void 0));
15997
+ if (metadata !== void 0) return {
15998
+ metadata,
15999
+ scope: verdict.scope ?? (resourceScopes !== void 0 && resourceScopes.length > 0 ? resourceScopes.join(" ") : void 0)
16000
+ };
16001
+ }
16002
+ return yield* registryError(`Could not discover an OAuth authorization server for ${registration.url}.`);
16003
+ });
16004
+ const registerClient = Effect.fn("McpOAuth.registerClient")(function* (metadata) {
16005
+ if (metadata.registration_endpoint === void 0) return yield* registryError("The authorization server does not support dynamic client registration.");
16006
+ const { status, json } = yield* fetchJson(HttpClientRequest.bodyJsonUnsafe(HttpClientRequest.post(metadata.registration_endpoint), {
16007
+ client_name: OAUTH_CLIENT_NAME,
16008
+ redirect_uris: [redirectUri],
16009
+ grant_types: ["authorization_code", "refresh_token"],
16010
+ response_types: ["code"],
16011
+ token_endpoint_auth_method: "none"
16012
+ })).pipe(Effect.mapError(() => registryError("Client registration request failed.")));
16013
+ if (status !== 200 && status !== 201) return yield* registryError(`Client registration was rejected with status ${status}.`);
16014
+ const decoded = decodeClientRegistration(json);
16015
+ if (decoded._tag !== "Success") return yield* registryError("Client registration answered an unexpected shape.");
16016
+ return decoded.value;
16017
+ });
16018
+ const randomUrlSafe = (bytes) => crypto.randomBytes(bytes).pipe(Effect.map(Encoding.encodeBase64Url));
16019
+ const begin = (registration) => Effect.gen(function* () {
16020
+ const { metadata, scope } = yield* discover(registration);
16021
+ const existing = yield* readRecord(registration.name);
16022
+ const reusable = Option.filter(existing, (record) => record.tokenEndpoint === metadata.token_endpoint && record.authorizationEndpoint === metadata.authorization_endpoint);
16023
+ const client = Option.isSome(reusable) ? {
16024
+ client_id: reusable.value.clientId,
16025
+ client_secret: reusable.value.clientSecret
16026
+ } : yield* registerClient(metadata);
16027
+ const codeVerifier = yield* randomUrlSafe(CODE_VERIFIER_BYTES).pipe(Effect.mapError(() => registryError("Could not generate a PKCE verifier.")));
16028
+ const challengeBytes = yield* crypto.digest("SHA-256", new TextEncoder().encode(codeVerifier)).pipe(Effect.mapError(() => registryError("Could not hash the PKCE verifier.")));
16029
+ const state = yield* randomUrlSafe(STATE_BYTES).pipe(Effect.mapError(() => registryError("Could not generate a state value.")));
16030
+ const record = {
16031
+ clientId: client.client_id,
16032
+ ...client.client_secret === void 0 ? {} : { clientSecret: client.client_secret },
16033
+ authorizationEndpoint: metadata.authorization_endpoint,
16034
+ tokenEndpoint: metadata.token_endpoint,
16035
+ resource: registration.url,
16036
+ ...scope === void 0 ? {} : { scope }
16037
+ };
16038
+ yield* writeRecord(registration.name, record);
16039
+ const startedAt = yield* nowMillis;
16040
+ yield* Ref.update(pendingFlows, (flows) => {
16041
+ const next = new Map([...flows].filter(([, flow]) => startedAt - flow.startedAt < PENDING_FLOW_TTL_MS));
16042
+ next.set(state, {
16043
+ server: registration.name,
16044
+ codeVerifier,
16045
+ redirectUri,
16046
+ record,
16047
+ startedAt
16048
+ });
16049
+ return next;
16050
+ });
16051
+ const authorizeUrl = new URL(metadata.authorization_endpoint);
16052
+ authorizeUrl.searchParams.set("response_type", "code");
16053
+ authorizeUrl.searchParams.set("client_id", client.client_id);
16054
+ authorizeUrl.searchParams.set("redirect_uri", redirectUri);
16055
+ authorizeUrl.searchParams.set("state", state);
16056
+ authorizeUrl.searchParams.set("code_challenge", Encoding.encodeBase64Url(challengeBytes));
16057
+ authorizeUrl.searchParams.set("code_challenge_method", "S256");
16058
+ authorizeUrl.searchParams.set("resource", registration.url);
16059
+ if (scope !== void 0) authorizeUrl.searchParams.set("scope", scope);
16060
+ return { authorizeUrl: authorizeUrl.toString() };
16061
+ });
16062
+ const exchangeToken = Effect.fn("McpOAuth.exchangeToken")(function* (flow, code) {
16063
+ const { status, json } = yield* fetchJson(HttpClientRequest.bodyUrlParams(HttpClientRequest.post(flow.record.tokenEndpoint), {
16064
+ grant_type: "authorization_code",
16065
+ code,
16066
+ redirect_uri: flow.redirectUri,
16067
+ client_id: flow.record.clientId,
16068
+ code_verifier: flow.codeVerifier,
16069
+ ...flow.record.clientSecret === void 0 ? {} : { client_secret: flow.record.clientSecret },
16070
+ ...flow.record.resource === void 0 ? {} : { resource: flow.record.resource }
16071
+ }));
16072
+ if (status !== 200) return yield* Effect.fail(`token endpoint answered status ${status}`);
16073
+ const decoded = decodeTokenResponse(json);
16074
+ if (decoded._tag !== "Success") return yield* Effect.fail("token endpoint answered an unexpected shape");
16075
+ return decoded.value;
16076
+ });
16077
+ const handleCallback = (params) => Effect.gen(function* () {
16078
+ if (params.state === void 0) return {
16079
+ ok: false,
16080
+ detail: "The redirect carried no state value."
16081
+ };
16082
+ const flow = (yield* Ref.get(pendingFlows)).get(params.state);
16083
+ if (flow === void 0) return {
16084
+ ok: false,
16085
+ detail: "This sign-in is unknown or has expired. Start it again from settings."
16086
+ };
16087
+ yield* Ref.update(pendingFlows, (current) => {
16088
+ const next = new Map(current);
16089
+ next.delete(params.state);
16090
+ return next;
16091
+ });
16092
+ if (params.error !== void 0) return {
16093
+ ok: false,
16094
+ detail: params.errorDescription ?? `The authorization server answered: ${params.error}.`
16095
+ };
16096
+ if (params.code === void 0) return {
16097
+ ok: false,
16098
+ detail: "The redirect carried no authorization code."
16099
+ };
16100
+ const now = yield* nowMillis;
16101
+ const exchanged = yield* exchangeToken(flow, params.code).pipe(Effect.map((response) => ({
16102
+ failed: false,
16103
+ response
16104
+ })), Effect.catch((detail) => Effect.succeed({
16105
+ failed: true,
16106
+ detail: typeof detail === "string" ? detail : "the token request failed"
16107
+ })));
16108
+ if (exchanged.failed) {
16109
+ yield* Effect.logWarning("mcp oauth token exchange failed", {
16110
+ server: flow.server,
16111
+ detail: exchanged.detail
16112
+ });
16113
+ return {
16114
+ ok: false,
16115
+ detail: `Token exchange failed: ${exchanged.detail}.`
16116
+ };
16117
+ }
16118
+ yield* writeRecord(flow.server, {
16119
+ ...flow.record,
16120
+ tokens: {
16121
+ accessToken: exchanged.response.access_token,
16122
+ ...exchanged.response.refresh_token === void 0 ? {} : { refreshToken: exchanged.response.refresh_token },
16123
+ ...exchanged.response.expires_in === void 0 ? {} : { expiresAt: now + exchanged.response.expires_in * 1e3 }
16124
+ }
16125
+ });
16126
+ return {
16127
+ ok: true,
16128
+ server: flow.server
16129
+ };
16130
+ }).pipe(Effect.catchCause((cause) => Effect.logWarning("mcp oauth callback failed", { cause }).pipe(Effect.as({
16131
+ ok: false,
16132
+ detail: "The sign-in failed. See the server log."
16133
+ }))));
16134
+ const disconnect = (name) => Effect.gen(function* () {
16135
+ const record = yield* readRecord(name);
16136
+ if (Option.isNone(record)) return;
16137
+ const { tokens: _dropped, ...rest } = record.value;
16138
+ yield* writeRecord(name, rest);
16139
+ }).pipe(Effect.catchCause((cause) => Effect.logWarning("mcp oauth disconnect failed", {
16140
+ cause,
16141
+ server: name
16142
+ }).pipe(Effect.asVoid)));
16143
+ const refreshTokens = Effect.fn("McpOAuth.refreshTokens")(function* (server, record, refreshToken) {
16144
+ const { status, json } = yield* fetchJson(HttpClientRequest.bodyUrlParams(HttpClientRequest.post(record.tokenEndpoint), {
16145
+ grant_type: "refresh_token",
16146
+ refresh_token: refreshToken,
16147
+ client_id: record.clientId,
16148
+ ...record.clientSecret === void 0 ? {} : { client_secret: record.clientSecret },
16149
+ ...record.resource === void 0 ? {} : { resource: record.resource }
16150
+ }));
16151
+ if (status !== 200) return yield* Effect.fail(`status ${status}`);
16152
+ const decoded = decodeTokenResponse(json);
16153
+ if (decoded._tag !== "Success") return yield* Effect.fail("unexpected shape");
16154
+ const response = decoded.value;
16155
+ const now = yield* nowMillis;
16156
+ const tokens = {
16157
+ accessToken: response.access_token,
16158
+ refreshToken: response.refresh_token ?? refreshToken,
16159
+ ...response.expires_in === void 0 ? {} : { expiresAt: now + response.expires_in * 1e3 }
16160
+ };
16161
+ yield* writeRecord(server, {
16162
+ ...record,
16163
+ tokens
16164
+ });
16165
+ return tokens.accessToken;
16166
+ });
16167
+ const accessTokenFor = (registration) => Effect.gen(function* () {
16168
+ const record = yield* readRecord(registration.name);
16169
+ if (Option.isNone(record) || record.value.tokens === void 0) return Option.none();
16170
+ const tokens = record.value.tokens;
16171
+ const now = yield* nowMillis;
16172
+ if (!(tokens.expiresAt !== void 0 && now >= tokens.expiresAt - TOKEN_EXPIRY_MARGIN_MS)) return Option.some(tokens.accessToken);
16173
+ if (tokens.refreshToken === void 0) return Option.none();
16174
+ return yield* refreshTokens(registration.name, record.value, tokens.refreshToken).pipe(Effect.map(Option.some), Effect.catchCause((cause) => Effect.logWarning("mcp oauth refresh failed", {
16175
+ cause,
16176
+ server: registration.name
16177
+ }).pipe(Effect.as(Option.none()))));
16178
+ }).pipe(Effect.catchCause((cause) => Effect.logWarning("mcp oauth token lookup failed", {
16179
+ cause,
16180
+ server: registration.name
16181
+ }).pipe(Effect.as(Option.none()))));
16182
+ return {
16183
+ statusFor,
16184
+ begin,
16185
+ handleCallback,
16186
+ disconnect,
16187
+ accessTokenFor
16188
+ };
16189
+ });
16190
+ const layer$58 = Layer.effect(McpOAuth, make$63);
16191
+ Layer.succeed(McpOAuth, {
16192
+ statusFor: () => Effect.succeed(Option.none()),
16193
+ begin: () => Effect.fail(new McpRegistryError({ detail: "OAuth sign-in is not available." })),
16194
+ handleCallback: () => Effect.succeed({
16195
+ ok: false,
16196
+ detail: "OAuth sign-in is not available."
16197
+ }),
16198
+ disconnect: () => Effect.void,
16199
+ accessTokenFor: () => Effect.succeed(Option.none())
16200
+ });
15464
16201
  /** Extension for a registration file, so the sync's asset name is `<server>.json`. */
15465
16202
  const MCP_REGISTRATION_SUFFIX = ".json";
15466
16203
  const RegistrationFromJson$1 = Schema$1.fromJsonString(McpServerRegistration);
@@ -15471,6 +16208,8 @@ const slotsOf = (registration) => registration.secrets ?? [];
15471
16208
  const make$62 = Effect.gen(function* () {
15472
16209
  const config = yield* ServerConfig$1;
15473
16210
  const secrets = yield* ServerSecretStore;
16211
+ const oauth = yield* McpOAuth;
16212
+ const claudeFiles = yield* ClaudeMcpFiles;
15474
16213
  const fileSystem = yield* FileSystem.FileSystem;
15475
16214
  const path = yield* Path.Path;
15476
16215
  const services = yield* Effect.context();
@@ -15513,10 +16252,27 @@ const make$62 = Effect.gen(function* () {
15513
16252
  const value = yield* readSecret(registration.name, slot);
15514
16253
  if (Option.isNone(value)) missingSecrets.push(slot);
15515
16254
  }
16255
+ const oauthStatus = registration.transport === "stdio" || !registration.enabled ? Option.none() : yield* oauth.statusFor(registration);
16256
+ const needsSignIn = Option.isSome(oauthStatus) && !oauthStatus.value.connected;
15516
16257
  statuses.push({
15517
16258
  registration,
15518
16259
  missingSecrets,
15519
- ready: registration.enabled && missingSecrets.length === 0
16260
+ ready: registration.enabled && missingSecrets.length === 0 && !needsSignIn,
16261
+ ...Option.isSome(oauthStatus) ? { oauth: oauthStatus.value } : {},
16262
+ source: "registry"
16263
+ });
16264
+ }
16265
+ const registryNames = new Set(statuses.map((status) => status.registration.name));
16266
+ for (const userScope of yield* claudeFiles.readUser) {
16267
+ if (registryNames.has(userScope.name)) continue;
16268
+ const oauthStatus = userScope.transport === "stdio" ? Option.none() : yield* oauth.statusFor(userScope);
16269
+ const needsSignIn = Option.isSome(oauthStatus) && !oauthStatus.value.connected;
16270
+ statuses.push({
16271
+ registration: userScope,
16272
+ missingSecrets: [],
16273
+ ready: !needsSignIn,
16274
+ ...Option.isSome(oauthStatus) ? { oauth: oauthStatus.value } : {},
16275
+ source: "claude"
15520
16276
  });
15521
16277
  }
15522
16278
  return statuses;
@@ -15536,6 +16292,7 @@ const make$62 = Effect.gen(function* () {
15536
16292
  target: slot.target,
15537
16293
  key: slot.key
15538
16294
  })).pipe(Effect.ignore);
16295
+ yield* secrets.remove(mcpOAuthStorageName(name)).pipe(Effect.ignore);
15539
16296
  }).pipe(Effect.provide(services), Effect.catchCause((cause) => Effect.logWarning("mcp registry remove failed", { cause }).pipe(Effect.asVoid)));
15540
16297
  const setSecret = ({ name, slot, value }) => secrets.set(mcpSecretStorageName({
15541
16298
  server: name,
@@ -15565,6 +16322,11 @@ const make$62 = Effect.gen(function* () {
15565
16322
  else env[slot.key] = value.value;
15566
16323
  }
15567
16324
  if (!complete) continue;
16325
+ const hasExplicitAuthorization = Object.keys(headers).some((key) => key.toLowerCase() === "authorization");
16326
+ if (registration.transport !== "stdio" && !hasExplicitAuthorization) {
16327
+ const token = yield* oauth.accessTokenFor(registration);
16328
+ if (Option.isSome(token)) headers["Authorization"] = `Bearer ${token.value}`;
16329
+ }
15568
16330
  resolved[registration.name] = registration.transport === "stdio" ? {
15569
16331
  type: "stdio",
15570
16332
  command: registration.command,
@@ -15576,6 +16338,20 @@ const make$62 = Effect.gen(function* () {
15576
16338
  headers
15577
16339
  };
15578
16340
  }
16341
+ for (const userScope of yield* claudeFiles.readUser) {
16342
+ if (userScope.transport === "stdio") continue;
16343
+ if (userScope.name in resolved) continue;
16344
+ const token = yield* oauth.accessTokenFor(userScope);
16345
+ if (Option.isNone(token)) continue;
16346
+ resolved[userScope.name] = {
16347
+ type: userScope.transport,
16348
+ url: userScope.url,
16349
+ headers: {
16350
+ ...userScope.headers,
16351
+ Authorization: `Bearer ${token.value}`
16352
+ }
16353
+ };
16354
+ }
15579
16355
  return resolved;
15580
16356
  }).pipe(Effect.provide(services), Effect.catchCause((cause) => Effect.logWarning("mcp registry resolve failed", { cause }).pipe(Effect.as({}))))
15581
16357
  };
@@ -17297,7 +18073,7 @@ const make$59 = Effect.gen(function* () {
17297
18073
  const layer$54 = Layer.effect(AssetSync, make$59);
17298
18074
  //#endregion
17299
18075
  //#region src/cli/assets.ts
17300
- const SyncServicesLive = layer$54.pipe(Layer.provideMerge(layer$55), Layer.provideMerge(layer$56), Layer.provideMerge(layer$58.pipe(Layer.provide(layer$65))), Layer.provideMerge(layer$65), Layer.provideMerge(FetchHttpClient.layer));
18076
+ const SyncServicesLive = layer$54.pipe(Layer.provideMerge(layer$55), Layer.provideMerge(layer$56), Layer.provideMerge(layer$60.pipe(Layer.provide(layer$67))), Layer.provideMerge(layer$67), Layer.provideMerge(FetchHttpClient.layer));
17301
18077
  const runWithSync = (flags, run) => Effect.gen(function* () {
17302
18078
  const config = yield* resolveCliAuthConfig(flags, yield* GlobalFlag.LogLevel);
17303
18079
  return yield* Effect.gen(function* () {
@@ -17305,7 +18081,7 @@ const runWithSync = (flags, run) => Effect.gen(function* () {
17305
18081
  sync: yield* AssetSync,
17306
18082
  link: yield* HubLink
17307
18083
  });
17308
- }).pipe(Effect.provide(SyncServicesLive.pipe(Layer.provide(layer$68(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, "Error")))));
18084
+ }).pipe(Effect.provide(SyncServicesLive.pipe(Layer.provide(layer$70(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, "Error")))));
17309
18085
  });
17310
18086
  const STATE_LABEL = {
17311
18087
  synced: "synced",
@@ -27891,7 +28667,7 @@ const runProjectMutation = Effect.fn("runProjectMutation")(function* (flags, run
27891
28667
  });
27892
28668
  yield* Console.log(output);
27893
28669
  }));
27894
- const offlineRuntimeLayer = ProjectCliRuntimeLive.pipe(Layer.provide(layer$68(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)));
28670
+ const offlineRuntimeLayer = ProjectCliRuntimeLive.pipe(Layer.provide(layer$70(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)));
27895
28671
  return yield* Effect.gen(function* () {
27896
28672
  const snapshot = yield* getOfflineSnapshot();
27897
28673
  const orchestrationEngine = yield* OrchestrationEngineService;
@@ -27902,7 +28678,7 @@ const runProjectMutation = Effect.fn("runProjectMutation")(function* (flags, run
27902
28678
  });
27903
28679
  yield* Console.log(output);
27904
28680
  }).pipe(Effect.provide(offlineRuntimeLayer));
27905
- }).pipe(Effect.provide(Layer.mergeAll(runtimeLayer, layer$43).pipe(Layer.provideMerge(FetchHttpClient.layer), Layer.provide(layer$68(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)))));
28681
+ }).pipe(Effect.provide(Layer.mergeAll(runtimeLayer, layer$43).pipe(Layer.provideMerge(FetchHttpClient.layer), Layer.provide(layer$70(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)))));
27906
28682
  });
27907
28683
  const projectAddCommand = Command.make("add", {
27908
28684
  ...projectLocationFlags,
@@ -29222,6 +29998,40 @@ const otlpTracesProxyRouteLayer = HttpRouter.add("POST", OTLP_TRACES_PROXY_PATH,
29222
29998
  EnvironmentInternalError: HttpServerRespondable.toResponse,
29223
29999
  EnvironmentScopeRequiredError: HttpServerRespondable.toResponse
29224
30000
  })));
30001
+ const escapeHtml = (input) => input.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
30002
+ const oauthResultPage = (input) => HttpServerResponse.text([
30003
+ "<!doctype html>",
30004
+ `<html><head><meta charset="utf-8"><title>${escapeHtml(input.title)}</title></head>`,
30005
+ "<body style=\"font-family: system-ui, sans-serif; max-width: 32rem; margin: 4rem auto;\">",
30006
+ `<h1 style="font-size: 1.25rem;">${escapeHtml(input.title)}</h1>`,
30007
+ `<p>${escapeHtml(input.detail)}</p>`,
30008
+ "</body></html>"
30009
+ ].join(""), { contentType: "text/html; charset=utf-8" });
30010
+ /**
30011
+ * Where the browser lands after an MCP server sign-in.
30012
+ *
30013
+ * Deliberately unauthenticated: the redirect comes from an authorization
30014
+ * server that holds no p4code credential. The `state` value — minted by
30015
+ * `McpOAuth.begin`, single-use, expiring — is what ties the request to a flow
30016
+ * this server started; a request without a live one changes nothing.
30017
+ */
30018
+ const mcpOAuthCallbackRouteLayer = HttpRouter.add("GET", OAUTH_CALLBACK_PATH, Effect.gen(function* () {
30019
+ const request = yield* HttpServerRequest.HttpServerRequest;
30020
+ const url = new URL(request.url, "http://localhost.invalid");
30021
+ const outcome = yield* (yield* McpOAuth).handleCallback({
30022
+ state: url.searchParams.get("state") ?? void 0,
30023
+ code: url.searchParams.get("code") ?? void 0,
30024
+ error: url.searchParams.get("error") ?? void 0,
30025
+ errorDescription: url.searchParams.get("error_description") ?? void 0
30026
+ });
30027
+ return outcome.ok ? oauthResultPage({
30028
+ title: "Signed in",
30029
+ detail: `${outcome.server} is connected. You can close this tab and return to p4code.`
30030
+ }) : oauthResultPage({
30031
+ title: "Sign-in failed",
30032
+ detail: outcome.detail
30033
+ });
30034
+ }));
29225
30035
  const assetRouteLayer = HttpRouter.add("GET", `${ASSET_ROUTE_PREFIX}/*`, Effect.gen(function* () {
29226
30036
  const request = yield* HttpServerRequest.HttpServerRequest;
29227
30037
  const url = HttpServerRequest.toURL(request);
@@ -33376,15 +34186,15 @@ const toTaskStoreError = (operation) => (cause) => Effect.logWarning("task store
33376
34186
  */
33377
34187
  const buildAutoPrompt = (task) => {
33378
34188
  const reference = task.readableId ?? task.taskId;
33379
- return `You are picking up task \`${reference}\` from the board: ${task.title}
34189
+ return `Picking up board task \`${reference}\`: ${task.title}
33380
34190
 
33381
- Start by reading the task itself with \`task_current\` (or \`task_get\` with \`${reference}\`). The body, labels, project and priority live on the row, not in this message, and the row may have been edited since this thread started what it says now is the source of truth. Re-read it the same way whenever you need the fields again.
34191
+ Read the task first via \`task_current\` (or \`task_get\` with \`${reference}\`). Body, labels, project and priority live on the row, not in this message; the row may have changed since this thread started - the current row is the source of truth. Re-read it the same way whenever you need the fields again.
33382
34192
 
33383
- This thread is already linked to the task, which is now In progress. Do the work.
34193
+ This thread is already linked to the task, status In progress. Do the work.
33384
34194
 
33385
- Record what you find with \`task_update\` as you go a decision you made, a constraint you hit, something the task turned out to be wrong about. That is what makes the row worth reading afterwards. Leave \`status\` alone: moving work across the board is the user's call.
34195
+ Record findings via \`task_update\` as you go - decisions made, constraints hit, things the task turned out to be wrong about. That is what makes the row worth reading afterwards. Leave \`status\` alone: moving work across the board is the user's call.
33386
34196
 
33387
- After reading the task, read the code it concerns. If it turns out to be specified too poorly to act on, say so and stop rather than guessing at what was meant.`;
34197
+ After reading the task, read the code it concerns. If it is specified too poorly to act on, say so and stop rather than guessing.`;
33388
34198
  };
33389
34199
  /**
33390
34200
  * Render a task into the prompt that seeds its thread.
@@ -43408,6 +44218,8 @@ const RPC_REQUIRED_SCOPE = /* @__PURE__ */ new Map([
43408
44218
  [WS_METHODS.mcpSave, AuthOrchestrationOperateScope],
43409
44219
  [WS_METHODS.mcpRemove, AuthOrchestrationOperateScope],
43410
44220
  [WS_METHODS.mcpSetSecret, AuthOrchestrationOperateScope],
44221
+ [WS_METHODS.mcpOAuthBegin, AuthOrchestrationOperateScope],
44222
+ [WS_METHODS.mcpOAuthDisconnect, AuthOrchestrationOperateScope],
43411
44223
  [WS_METHODS.serverGetSettings, AuthOrchestrationReadScope],
43412
44224
  [WS_METHODS.serverUpdateSettings, AuthOrchestrationOperateScope],
43413
44225
  [WS_METHODS.serverDiscoverSourceControl, AuthOrchestrationReadScope],
@@ -43494,6 +44306,22 @@ function toAuthAccessStreamEvent(change, revision, currentSessionId) {
43494
44306
  };
43495
44307
  }
43496
44308
  }
44309
+ /**
44310
+ * Route one MCP write to the store its `source` names.
44311
+ *
44312
+ * `claude` and `project` write the CLI's own files; `registry` (or absent,
44313
+ * which every older client sends) writes p4code's store. A project write
44314
+ * without a directory is a caller bug worth a loud answer, not a fallback.
44315
+ */
44316
+ const routeMcpWrite = (input, handlers) => {
44317
+ if (input.source === "claude") return handlers.claude();
44318
+ if (input.source === "project") {
44319
+ const dir = input.projectDir?.trim() ?? "";
44320
+ if (dir.length === 0) return Effect.fail(new McpRegistryError({ detail: "A project-scope write needs its project directory." }));
44321
+ return handlers.project(dir);
44322
+ }
44323
+ return handlers.registry();
44324
+ };
43497
44325
  const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.toLayer(Effect.gen(function* () {
43498
44326
  const currentSessionId = currentSession.sessionId;
43499
44327
  const crypto = yield* Crypto.Crypto;
@@ -43521,7 +44349,27 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
43521
44349
  const hubLink = yield* HubLink;
43522
44350
  const assetSync = yield* AssetSync;
43523
44351
  const mcpRegistry = yield* McpRegistry;
44352
+ const mcpOAuth = yield* McpOAuth;
44353
+ const claudeMcpFiles = yield* ClaudeMcpFiles;
43524
44354
  const skillRegistry = yield* SkillRegistry;
44355
+ const listMcpServersEverywhere = Effect.gen(function* () {
44356
+ const servers = [...yield* mcpRegistry.list];
44357
+ const projectRows = yield* projectionProjects.listAll().pipe(Effect.map((rows) => rows.filter((row) => row.deletedAt === null)), Effect.orElseSucceed(() => []));
44358
+ for (const project of projectRows) for (const registration of yield* claudeMcpFiles.readProject(project.workspaceRoot)) servers.push({
44359
+ registration,
44360
+ missingSecrets: [],
44361
+ ready: true,
44362
+ source: "project",
44363
+ projectDir: project.workspaceRoot
44364
+ });
44365
+ return {
44366
+ servers,
44367
+ projects: projectRows.map((project) => ({
44368
+ dir: project.workspaceRoot,
44369
+ title: project.title
44370
+ }))
44371
+ };
44372
+ });
43525
44373
  const startup = yield* ServerRuntimeStartup;
43526
44374
  const workspaceEntries = yield* WorkspaceEntries;
43527
44375
  const workspaceFileSystem = yield* WorkspaceFileSystem;
@@ -44114,14 +44962,35 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
44114
44962
  }), { "rpc.aggregate": "skills" }),
44115
44963
  [WS_METHODS.skillRegistrySearch]: ({ query }) => observeRpcEffect$1(WS_METHODS.skillRegistrySearch, skillRegistry.search(query), { "rpc.aggregate": "skills" }),
44116
44964
  [WS_METHODS.skillRegistryFetch]: ({ id }) => observeRpcEffect$1(WS_METHODS.skillRegistryFetch, skillRegistry.fetch(id), { "rpc.aggregate": "skills" }),
44117
- [WS_METHODS.mcpList]: (_input) => observeRpcEffect$1(WS_METHODS.mcpList, mcpRegistry.list.pipe(Effect.map((servers) => ({ servers }))), { "rpc.aggregate": "mcp" }),
44118
- [WS_METHODS.mcpSave]: ({ registration }) => observeRpcEffect$1(WS_METHODS.mcpSave, mcpRegistry.save(registration).pipe(Effect.andThen(mcpRegistry.list.pipe(Effect.map((servers) => ({ servers }))))), { "rpc.aggregate": "mcp" }),
44119
- [WS_METHODS.mcpRemove]: ({ name }) => observeRpcEffect$1(WS_METHODS.mcpRemove, mcpRegistry.remove(name).pipe(Effect.andThen(mcpRegistry.list.pipe(Effect.map((servers) => ({ servers }))))), { "rpc.aggregate": "mcp" }),
44965
+ [WS_METHODS.mcpList]: (_input) => observeRpcEffect$1(WS_METHODS.mcpList, listMcpServersEverywhere, { "rpc.aggregate": "mcp" }),
44966
+ [WS_METHODS.mcpSave]: ({ registration, source, projectDir }) => observeRpcEffect$1(WS_METHODS.mcpSave, routeMcpWrite({
44967
+ source,
44968
+ projectDir
44969
+ }, {
44970
+ claude: () => claudeMcpFiles.upsertUser(registration),
44971
+ project: (dir) => claudeMcpFiles.upsertProject(dir, registration),
44972
+ registry: () => mcpRegistry.save(registration)
44973
+ }).pipe(Effect.andThen(listMcpServersEverywhere)), { "rpc.aggregate": "mcp" }),
44974
+ [WS_METHODS.mcpRemove]: ({ name, source, projectDir }) => observeRpcEffect$1(WS_METHODS.mcpRemove, routeMcpWrite({
44975
+ source,
44976
+ projectDir
44977
+ }, {
44978
+ claude: () => claudeMcpFiles.removeUser(name),
44979
+ project: (dir) => claudeMcpFiles.removeProject(dir, name),
44980
+ registry: () => mcpRegistry.remove(name)
44981
+ }).pipe(Effect.andThen(listMcpServersEverywhere)), { "rpc.aggregate": "mcp" }),
44120
44982
  [WS_METHODS.mcpSetSecret]: ({ name, slot, value }) => observeRpcEffect$1(WS_METHODS.mcpSetSecret, mcpRegistry.setSecret({
44121
44983
  name,
44122
44984
  slot,
44123
44985
  value
44124
- }).pipe(Effect.andThen(mcpRegistry.list.pipe(Effect.map((servers) => ({ servers }))))), { "rpc.aggregate": "mcp" }),
44986
+ }).pipe(Effect.andThen(listMcpServersEverywhere)), { "rpc.aggregate": "mcp" }),
44987
+ [WS_METHODS.mcpOAuthBegin]: ({ name }) => observeRpcEffect$1(WS_METHODS.mcpOAuthBegin, Effect.gen(function* () {
44988
+ const found = (yield* mcpRegistry.list).find((status) => status.registration.name === name);
44989
+ if (found === void 0) return yield* new McpRegistryError({ detail: `No server is named ${name}.` });
44990
+ if (found.registration.transport === "stdio") return yield* new McpRegistryError({ detail: "A stdio server is launched locally and has no sign-in." });
44991
+ return yield* mcpOAuth.begin(found.registration);
44992
+ }), { "rpc.aggregate": "mcp" }),
44993
+ [WS_METHODS.mcpOAuthDisconnect]: ({ name }) => observeRpcEffect$1(WS_METHODS.mcpOAuthDisconnect, mcpOAuth.disconnect(name).pipe(Effect.andThen(listMcpServersEverywhere)), { "rpc.aggregate": "mcp" }),
44125
44994
  [WS_METHODS.serverGetSettings]: (_input) => observeRpcEffect$1(WS_METHODS.serverGetSettings, serverSettings.getSettings.pipe(Effect.map(redactServerSettingsForClient)), { "rpc.aggregate": "server" }),
44126
44995
  [WS_METHODS.serverUpdateSettings]: ({ patch }) => observeRpcEffect$1(WS_METHODS.serverUpdateSettings, serverSettings.updateSettings(patch).pipe(Effect.map(redactServerSettingsForClient)), { "rpc.aggregate": "server" }),
44127
44996
  [WS_METHODS.serverDiscoverSourceControl]: (_input) => observeRpcEffect$1(WS_METHODS.serverDiscoverSourceControl, sourceControlDiscovery.discover, { "rpc.aggregate": "server" }),
@@ -46619,7 +47488,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () {
46619
47488
  };
46620
47489
  });
46621
47490
  var OpenCodeRuntime = class extends Context.Service()("@p4code/cli/provider/opencodeRuntime") {};
46622
- const OpenCodeRuntimeLive = Layer.effect(OpenCodeRuntime, makeOpenCodeRuntime).pipe(Layer.provide(layer$69));
47491
+ const OpenCodeRuntimeLive = Layer.effect(OpenCodeRuntime, makeOpenCodeRuntime).pipe(Layer.provide(layer$71));
46623
47492
  //#endregion
46624
47493
  //#region src/textGeneration/TextGenerationUtils.ts
46625
47494
  const isTextGenerationError$2 = Schema$1.is(TextGenerationError);
@@ -85904,9 +86773,9 @@ const WorkspaceEntriesLayerLive = layer$25.pipe(Layer.provide(layer$43));
85904
86773
  const WorkspaceFileSystemLayerLive = layer$24.pipe(Layer.provide(layer$43), Layer.provide(WorkspaceEntriesLayerLive));
85905
86774
  const WorkspaceLayerLive = Layer.mergeAll(layer$43, WorkspaceEntriesLayerLive, WorkspaceFileSystemLayerLive);
85906
86775
  const ProjectFaviconResolverLayerLive = layer$41.pipe(Layer.provide(layer$43), Layer.provide(layer$42));
85907
- const AuthLayerLive = layer$61.pipe(Layer.provideMerge(PersistenceLayerLive), Layer.provide(layer$65));
86776
+ const AuthLayerLive = layer$63.pipe(Layer.provideMerge(PersistenceLayerLive), Layer.provide(layer$67));
85908
86777
  const ProviderRuntimeLayerLive = ProviderSessionReaperLive.pipe(Layer.provideMerge(ProviderLayerLive), Layer.provideMerge(OrchestrationLayerLive));
85909
- const RuntimeDependenciesLive = ReactorLayerLive.pipe(Layer.provideMerge(CheckpointingLayerLive), Layer.provideMerge(SourceControlProviderRegistryLayerLive), Layer.provideMerge(GitLayerLive), Layer.provideMerge(VcsLayerLive), Layer.provideMerge(ProviderRuntimeLayerLive), Layer.provideMerge(Layer.mergeAll(TerminalLayerLive, PreviewLayerLive)), Layer.provideMerge(PersistenceLayerLive), Layer.provideMerge(layer$51), Layer.provideMerge(ProviderRegistryLive), Layer.provideMerge(ProviderInstanceRegistryHydrationLive), Layer.provideMerge(ProviderEventLoggersLive), Layer.provideMerge(OpenCodeRuntimeLive), Layer.provideMerge(layer$58.pipe(Layer.provide(layer$65))), Layer.provideMerge(WorkspaceLayerLive), Layer.provideMerge(ProjectFaviconResolverLayerLive), Layer.provideMerge(layer$52), Layer.provideMerge(layer$45), Layer.provideMerge(AuthLayerLive), Layer.provideMerge(Layer.mergeAll(layer$57, layer$31)), Layer.provideMerge(layer$65)).pipe(Layer.provideMerge(layer$8), Layer.provideMerge(layer$7), Layer.provideMerge(layer$6), Layer.provideMerge(layer$48), Layer.provideMerge(layer$50), Layer.provideMerge(layer$49), Layer.provide(layer$69));
86778
+ const RuntimeDependenciesLive = ReactorLayerLive.pipe(Layer.provideMerge(CheckpointingLayerLive), Layer.provideMerge(SourceControlProviderRegistryLayerLive), Layer.provideMerge(GitLayerLive), Layer.provideMerge(VcsLayerLive), Layer.provideMerge(ProviderRuntimeLayerLive), Layer.provideMerge(Layer.mergeAll(TerminalLayerLive, PreviewLayerLive)), Layer.provideMerge(PersistenceLayerLive), Layer.provideMerge(layer$51), Layer.provideMerge(ProviderRegistryLive), Layer.provideMerge(ProviderInstanceRegistryHydrationLive), Layer.provideMerge(ProviderEventLoggersLive), Layer.provideMerge(OpenCodeRuntimeLive), Layer.provideMerge(layer$60.pipe(Layer.provide(layer$67))), Layer.provideMerge(WorkspaceLayerLive), Layer.provideMerge(ProjectFaviconResolverLayerLive), Layer.provideMerge(layer$52), Layer.provideMerge(layer$45), Layer.provideMerge(AuthLayerLive), Layer.provideMerge(Layer.mergeAll(layer$57, layer$31).pipe(Layer.provideMerge(layer$58), Layer.provideMerge(layer$59))), Layer.provideMerge(layer$67)).pipe(Layer.provideMerge(layer$8), Layer.provideMerge(layer$7), Layer.provideMerge(layer$6), Layer.provideMerge(layer$48), Layer.provideMerge(layer$50), Layer.provideMerge(layer$49), Layer.provide(layer$71));
85910
86779
  /**
85911
86780
  * Hub asset sync.
85912
86781
  *
@@ -85916,7 +86785,7 @@ const RuntimeDependenciesLive = ReactorLayerLive.pipe(Layer.provideMerge(Checkpo
85916
86785
  */
85917
86786
  const AssetSyncLive = layer$54.pipe(Layer.provideMerge(layer$55), Layer.provideMerge(layer$56));
85918
86787
  const RuntimeServicesLive = layer$44.pipe(Layer.provideMerge(AssetSyncLive), Layer.provideMerge(RuntimeDependenciesLive));
85919
- const makeRoutesLayer = Layer.mergeAll(Layer.mergeAll(HttpApiBuilder.layer(EnvironmentHttpApi).pipe(Layer.provide(authHttpApiLayer), Layer.provide(orchestrationHttpApiLayer), Layer.provide(serverEnvironmentHttpApiLayer), Layer.provide(environmentAuthenticatedAuthLayer)), otlpTracesProxyRouteLayer, assetRouteLayer, staticAndDevRouteLayer, websocketRpcRouteLayer), layer.pipe(Layer.provide(layer$2))).pipe(Layer.provide(RoutedTaskRepositoryLive.pipe(Layer.provide(PersistenceLayerLive))), Layer.provide(ProjectionProjectRepositoryLive.pipe(Layer.provide(PersistenceLayerLive))), Layer.provide(layer$42), Layer.provide(layer$28), Layer.provide(layer$46), Layer.provide(browserApiCorsLayer), Layer.provide(httpCompressionLayer));
86788
+ const makeRoutesLayer = Layer.mergeAll(Layer.mergeAll(HttpApiBuilder.layer(EnvironmentHttpApi).pipe(Layer.provide(authHttpApiLayer), Layer.provide(orchestrationHttpApiLayer), Layer.provide(serverEnvironmentHttpApiLayer), Layer.provide(environmentAuthenticatedAuthLayer)), otlpTracesProxyRouteLayer, assetRouteLayer, mcpOAuthCallbackRouteLayer, staticAndDevRouteLayer, websocketRpcRouteLayer), layer.pipe(Layer.provide(layer$2))).pipe(Layer.provide(RoutedTaskRepositoryLive.pipe(Layer.provide(PersistenceLayerLive))), Layer.provide(ProjectionProjectRepositoryLive.pipe(Layer.provide(PersistenceLayerLive))), Layer.provide(layer$42), Layer.provide(layer$28), Layer.provide(layer$46), Layer.provide(browserApiCorsLayer), Layer.provide(httpCompressionLayer));
85920
86789
  const makeServerLayer = Layer.unwrap(Effect.gen(function* () {
85921
86790
  const config = yield* ServerConfig$1;
85922
86791
  yield* fixPath();
@@ -86326,7 +87195,7 @@ const serviceCommand = Command.make("service").pipe(Command.withDescription("Man
86326
87195
  ]));
86327
87196
  //#endregion
86328
87197
  //#region src/bin.ts
86329
- const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, layer$69, FetchHttpClient.layer);
87198
+ const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, layer$71, FetchHttpClient.layer);
86330
87199
  /**
86331
87200
  * Prints an update reminder before dispatching, at most once a day.
86332
87201
  *