@p4code/cli 0.1.13 → 0.1.14
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.
- package/dist/bin.mjs +605 -45
- package/dist/client/assets/{DiffPanel-Bpvhj9Zk.js → DiffPanel-BdNsdwM5.js} +2 -2
- package/dist/client/assets/{FilePreviewPanel-C2_gi2li.js → FilePreviewPanel-CWcDqLso.js} +2 -2
- package/dist/client/assets/{PreviewPanel-CXrvKPAn.js → PreviewPanel-2u47bK5v.js} +2 -2
- package/dist/client/assets/{angular-html-tWdnCR5u.js → angular-html-BLv19dHn.js} +2 -2
- package/dist/client/assets/{angular-ts-DHHrhqEn.js → angular-ts-D41w0Au3.js} +2 -2
- package/dist/client/assets/{apl-Dttzd1My.js → apl-BEl8UFu4.js} +2 -2
- package/dist/client/assets/arrow-right-Tef5pBUM.js +2 -0
- package/dist/client/assets/{astro-BhhLcbHg.js → astro-U1Y_O85L.js} +2 -2
- package/dist/client/assets/{blade-D1XdhGNZ.js → blade-CZ232EWr.js} +2 -2
- package/dist/client/assets/{c-DU3so7RH.js → c-DirgNKaz.js} +2 -2
- package/dist/client/assets/{cobol-C5SwCVtJ.js → cobol-B7WyPi1F.js} +2 -2
- package/dist/client/assets/{coffee-D8JHirJ4.js → coffee-C_qHkMIP.js} +2 -2
- package/dist/client/assets/{cpp-BrxgqiPv.js → cpp-SPUhkLTs.js} +2 -2
- package/dist/client/assets/{crystal-LnjbHrAj.js → crystal-vq6jdakY.js} +2 -2
- package/dist/client/assets/{css-BhvHnfwC.js → css-BAnKum7K.js} +2 -2
- package/dist/client/assets/{edge-BIAZ2O1m.js → edge-BuMKesaI.js} +2 -2
- package/dist/client/assets/{elixir-C7fzptdf.js → elixir-BL-W6BH4.js} +2 -2
- package/dist/client/assets/{elm-24neygAB.js → elm-DiUCNL3v.js} +2 -2
- package/dist/client/assets/{erb-DpLDFI3P.js → erb-skcSurJb.js} +2 -2
- package/dist/client/assets/{fileCommentAnnotations-D9LoOqKz.js → fileCommentAnnotations-BRTew3I1.js} +2 -2
- package/dist/client/assets/{git-rebase-nk76LFrn.js → git-rebase-dO-xI28x.js} +2 -2
- package/dist/client/assets/{glimmer-js-D4faXOGy.js → glimmer-js-CECukRm7.js} +2 -2
- package/dist/client/assets/{glimmer-ts-CUIVPmuw.js → glimmer-ts-CIaRC8zm.js} +2 -2
- package/dist/client/assets/{glsl-CHEysBk5.js → glsl-CKSbugKg.js} +2 -2
- package/dist/client/assets/{graphql-DxDXWgtu.js → graphql-BiZ7twV7.js} +2 -2
- package/dist/client/assets/{hack-SE0rPM7F.js → hack-BdAxCPD7.js} +2 -2
- package/dist/client/assets/{haml-CZNVJ1t_.js → haml-SZR9Ndrp.js} +2 -2
- package/dist/client/assets/{handlebars-D3H0z0i1.js → handlebars-BExVQKqz.js} +2 -2
- package/dist/client/assets/{html-BXh14Kiu.js → html-CkYFYJn1.js} +2 -2
- package/dist/client/assets/{html-derivative-Dcx75xJi.js → html-derivative-Cop3EQeZ.js} +2 -2
- package/dist/client/assets/{http-CHN_K5kg.js → http-DQ8c4u3s.js} +2 -2
- package/dist/client/assets/{hurl-DcyaHxXE.js → hurl-BKyyIuNR.js} +2 -2
- package/dist/client/assets/{index-DesIBOH0.js → index-ClVe76A5.js} +191 -191
- package/dist/client/assets/index-DGu8ejUP.css +1 -0
- package/dist/client/assets/{java-CibfJb97.js → java-CedFcWif.js} +2 -2
- package/dist/client/assets/{javascript-CGKYJrgO.js → javascript-BdXDoWFM.js} +2 -2
- package/dist/client/assets/{jinja-hMi6h2V1.js → jinja-CK8YWL7a.js} +2 -2
- package/dist/client/assets/{jison-D5hwL1Vj.js → jison-C-mnFz-X.js} +2 -2
- package/dist/client/assets/{json-DBBzorBt.js → json-Dxix4Qbo.js} +2 -2
- package/dist/client/assets/{jsx-CfjpnlWf.js → jsx-6M0gL61I.js} +2 -2
- package/dist/client/assets/{julia-DY-wGzwr.js → julia-SXfQGJ8P.js} +2 -2
- package/dist/client/assets/{just-Aiaxo0L3.js → just-O0TR05uE.js} +2 -2
- package/dist/client/assets/{latex-Cl7y6v9p.js → latex-w7R_uAyc.js} +2 -2
- package/dist/client/assets/{liquid-tjIYQaDR.js → liquid-hPzEzmzZ.js} +2 -2
- package/dist/client/assets/{lua-DeDHmNSA.js → lua-D24pQ94w.js} +2 -2
- package/dist/client/assets/{marko-BLEE3RS_.js → marko-DsaGFeHY.js} +2 -2
- package/dist/client/assets/{mdc-DBLy0ju2.js → mdc-C4vifn2U.js} +2 -2
- package/dist/client/assets/{nginx-BpS56zJM.js → nginx-KnKOgHWr.js} +2 -2
- package/dist/client/assets/{nim-BP_5ZWs4.js → nim-CXVNWegc.js} +2 -2
- package/dist/client/assets/{perl-BNoghH8w.js → perl-_PU7gLJn.js} +2 -2
- package/dist/client/assets/{php-fikhFdsH.js → php-B--0cDl3.js} +2 -2
- package/dist/client/assets/{pug-BmML_p4L.js → pug-DELbYjHv.js} +2 -2
- package/dist/client/assets/{qml-WOQTdjfD.js → qml-CyGKPCuH.js} +2 -2
- package/dist/client/assets/{r-BuLVFJ9C.js → r-CSjDVJJY.js} +2 -2
- package/dist/client/assets/{razor-CsZ5mXvp.js → razor-ppZnQuQy.js} +2 -2
- package/dist/client/assets/{regexp-BbB_Z0k0.js → regexp-DAYlObpi.js} +2 -2
- package/dist/client/assets/{rst-CGogIoWE.js → rst-DV8sAt14.js} +2 -2
- package/dist/client/assets/{ruby-Drt9dpIY.js → ruby-DXISWktP.js} +2 -2
- package/dist/client/assets/{sas-mvidrzzg.js → sas-CJl6Tu3p.js} +2 -2
- package/dist/client/assets/{scss-DcqXsy4k.js → scss-DmbKMDuD.js} +2 -2
- package/dist/client/assets/{shellscript-C6nla6y6.js → shellscript-BFH8QS9G.js} +2 -2
- package/dist/client/assets/{shellsession-Dhkmvk-h.js → shellsession-CIHFsdwm.js} +2 -2
- package/dist/client/assets/{soy-CA1GOdLw.js → soy--q_a3wNH.js} +2 -2
- package/dist/client/assets/{sql-CaDtCgQd.js → sql-DE3xygzU.js} +2 -2
- package/dist/client/assets/{stata-r5dYDO1a.js → stata-BZORI8K-.js} +2 -2
- package/dist/client/assets/{surrealql-CreiDVJS.js → surrealql-DQwYGqPv.js} +2 -2
- package/dist/client/assets/{svelte-LQf52vP7.js → svelte-C8rcZCbS.js} +2 -2
- package/dist/client/assets/{templ-BAiCCrhW.js → templ-YxwRimHC.js} +2 -2
- package/dist/client/assets/{tex-Bi4veNnI.js → tex-CreWrrVU.js} +2 -2
- package/dist/client/assets/textarea-DogWRke3.js +46 -0
- package/dist/client/assets/{ts-tags-CmONaW11.js → ts-tags-BGYLwTar.js} +2 -2
- package/dist/client/assets/{tsx-DEGaq1SP.js → tsx-dvRLM93g.js} +2 -2
- package/dist/client/assets/{twig-DR3myS6w.js → twig-BwKK1q-j.js} +2 -2
- package/dist/client/assets/{typescript-pTX3WTUF.js → typescript-CIA9s0mZ.js} +2 -2
- package/dist/client/assets/{vue-_Gh228sM.js → vue-RR_2K8-s.js} +2 -2
- package/dist/client/assets/{vue-html-DqI2SuCG.js → vue-html-SobvEO2e.js} +2 -2
- package/dist/client/assets/{vue-vine-DJ2u5TuR.js → vue-vine-BQgxB5Yf.js} +2 -2
- package/dist/client/assets/{xml-DtWHD1Mk.js → xml-qR9OaTYe.js} +2 -2
- package/dist/client/assets/{xsl--uIbb82K.js → xsl-CNAIK0Wp.js} +2 -2
- package/dist/client/assets/{yaml-BYZt09An.js → yaml-CnmdSZli.js} +2 -2
- package/dist/client/index.html +3 -3
- package/package.json +1 -1
- package/dist/client/assets/arrow-right-DL8xTI3l.js +0 -2
- package/dist/client/assets/index-D7hXE3Wh.css +0 -1
- 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$
|
|
124
|
+
const make$74 = () => {
|
|
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$
|
|
233
|
+
const layer$70 = Layer.sync(NetService, make$74);
|
|
234
234
|
//#endregion
|
|
235
235
|
//#region package.json
|
|
236
|
-
var version = "0.1.
|
|
236
|
+
var version = "0.1.14";
|
|
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$
|
|
258
|
-
const layer$
|
|
257
|
+
const make$73 = (config) => ServerConfig$1.of(config);
|
|
258
|
+
const layer$69 = (config) => Layer.succeed(ServerConfig$1, make$73(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
|
|
@@ -8198,7 +8221,9 @@ const McpServerStatus = Schema$1.Struct({
|
|
|
8198
8221
|
registration: McpServerRegistration,
|
|
8199
8222
|
missingSecrets: Schema$1.Array(McpSecretSlot),
|
|
8200
8223
|
/** True when this machine holds everything needed to start it. */
|
|
8201
|
-
ready: Schema$1.Boolean
|
|
8224
|
+
ready: Schema$1.Boolean,
|
|
8225
|
+
/** Absent for servers that never challenged and hold no token here. */
|
|
8226
|
+
oauth: Schema$1.optional(McpServerOAuthStatus)
|
|
8202
8227
|
});
|
|
8203
8228
|
const McpServerList = Schema$1.Struct({ servers: Schema$1.Array(McpServerStatus) });
|
|
8204
8229
|
const McpServerSaveInput = Schema$1.Struct({ registration: McpServerRegistration });
|
|
@@ -8216,6 +8241,12 @@ const McpServerSecretInput = Schema$1.Struct({
|
|
|
8216
8241
|
slot: McpSecretSlot,
|
|
8217
8242
|
value: Schema$1.String
|
|
8218
8243
|
});
|
|
8244
|
+
/**
|
|
8245
|
+
* The one thing the panel needs to continue a sign-in: where to send the
|
|
8246
|
+
* browser. Everything else the flow holds — verifier, client id, endpoints —
|
|
8247
|
+
* stays on the server, because the callback lands there, not on the panel.
|
|
8248
|
+
*/
|
|
8249
|
+
const McpOAuthBeginResult = Schema$1.Struct({ authorizeUrl: Schema$1.String });
|
|
8219
8250
|
var McpRegistryError = class extends Schema$1.TaggedErrorClass()("McpRegistryError", { detail: Schema$1.String }) {
|
|
8220
8251
|
get message() {
|
|
8221
8252
|
return this.detail;
|
|
@@ -8565,6 +8596,8 @@ const WS_METHODS = {
|
|
|
8565
8596
|
mcpSave: "mcp.save",
|
|
8566
8597
|
mcpRemove: "mcp.remove",
|
|
8567
8598
|
mcpSetSecret: "mcp.setSecret",
|
|
8599
|
+
mcpOAuthBegin: "mcp.oauthBegin",
|
|
8600
|
+
mcpOAuthDisconnect: "mcp.oauthDisconnect",
|
|
8568
8601
|
sourceControlLookupRepository: "sourceControl.lookupRepository",
|
|
8569
8602
|
sourceControlCloneRepository: "sourceControl.cloneRepository",
|
|
8570
8603
|
sourceControlPublishRepository: "sourceControl.publishRepository",
|
|
@@ -8834,6 +8867,21 @@ const WsMcpSetSecretRpc = Rpc.make(WS_METHODS.mcpSetSecret, {
|
|
|
8834
8867
|
error: Schema$1.Union([McpRegistryError, EnvironmentAuthorizationError])
|
|
8835
8868
|
});
|
|
8836
8869
|
/**
|
|
8870
|
+
* Starts an OAuth sign-in for a remote server and answers with the authorize
|
|
8871
|
+
* URL. The panel opens it in a browser; the redirect lands on this server's
|
|
8872
|
+
* own `/oauth/callback`, so nothing else crosses the wire to the client.
|
|
8873
|
+
*/
|
|
8874
|
+
const WsMcpOAuthBeginRpc = Rpc.make(WS_METHODS.mcpOAuthBegin, {
|
|
8875
|
+
payload: McpServerNameInput,
|
|
8876
|
+
success: McpOAuthBeginResult,
|
|
8877
|
+
error: Schema$1.Union([McpRegistryError, EnvironmentAuthorizationError])
|
|
8878
|
+
});
|
|
8879
|
+
const WsMcpOAuthDisconnectRpc = Rpc.make(WS_METHODS.mcpOAuthDisconnect, {
|
|
8880
|
+
payload: McpServerNameInput,
|
|
8881
|
+
success: McpServerList,
|
|
8882
|
+
error: Schema$1.Union([McpRegistryError, EnvironmentAuthorizationError])
|
|
8883
|
+
});
|
|
8884
|
+
/**
|
|
8837
8885
|
* The board, then every change to it, for as long as the client stays
|
|
8838
8886
|
* subscribed. The leading snapshot is what makes this sufficient on its own —
|
|
8839
8887
|
* a client does not list first, so there is no window between the two in which
|
|
@@ -9211,7 +9259,7 @@ const WsSubscribeAuthAccessRpc = Rpc.make(WS_METHODS.subscribeAuthAccess, {
|
|
|
9211
9259
|
error: Schema$1.Union([AuthAccessStreamError, EnvironmentAuthorizationError]),
|
|
9212
9260
|
stream: true
|
|
9213
9261
|
});
|
|
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);
|
|
9262
|
+
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
9263
|
//#endregion
|
|
9216
9264
|
//#region ../../packages/shared/src/oauthScope.ts
|
|
9217
9265
|
const OAUTH_SCOPE_TOKEN = /^[\u0021\u0023-\u005b\u005d-\u007e]+$/u;
|
|
@@ -9357,7 +9405,7 @@ function deriveAuthClientMetadata(input) {
|
|
|
9357
9405
|
//#endregion
|
|
9358
9406
|
//#region src/auth/EnvironmentAuthPolicy.ts
|
|
9359
9407
|
var EnvironmentAuthPolicy = class extends Context.Service()("@p4code/cli/auth/EnvironmentAuthPolicy") {};
|
|
9360
|
-
const make$
|
|
9408
|
+
const make$72 = Effect.gen(function* () {
|
|
9361
9409
|
const config = yield* ServerConfig$1;
|
|
9362
9410
|
const isRemoteReachable = isRemoteReachableHost(config.host);
|
|
9363
9411
|
const policy = config.mode === "desktop" ? isRemoteReachable ? "remote-reachable" : "desktop-managed-local" : isRemoteReachable ? "remote-reachable" : "loopback-browser";
|
|
@@ -9375,7 +9423,7 @@ const make$71 = Effect.gen(function* () {
|
|
|
9375
9423
|
};
|
|
9376
9424
|
return EnvironmentAuthPolicy.of({ getDescriptor: () => Effect.succeed(descriptor).pipe(Effect.withSpan("EnvironmentAuthPolicy.getDescriptor")) });
|
|
9377
9425
|
});
|
|
9378
|
-
const layer$
|
|
9426
|
+
const layer$68 = Layer.effect(EnvironmentAuthPolicy, make$72);
|
|
9379
9427
|
//#endregion
|
|
9380
9428
|
//#region src/persistence/Errors.ts
|
|
9381
9429
|
function summarizeSchemaIssue(issue) {
|
|
@@ -9556,7 +9604,7 @@ function toPersistenceSqlOrDecodeError$6(sqlOperation, decodeOperation, correlat
|
|
|
9556
9604
|
cause
|
|
9557
9605
|
});
|
|
9558
9606
|
}
|
|
9559
|
-
const make$
|
|
9607
|
+
const make$71 = Effect.gen(function* () {
|
|
9560
9608
|
const sql = yield* SqlClient.SqlClient;
|
|
9561
9609
|
const createSessionRow = SqlSchema.void({
|
|
9562
9610
|
Request: CreateAuthSessionInput,
|
|
@@ -9690,7 +9738,7 @@ const make$70 = Effect.gen(function* () {
|
|
|
9690
9738
|
setLastConnectedAt
|
|
9691
9739
|
};
|
|
9692
9740
|
});
|
|
9693
|
-
const layer$
|
|
9741
|
+
const layer$67 = Layer.effect(AuthSessionRepository, make$71);
|
|
9694
9742
|
//#endregion
|
|
9695
9743
|
//#region src/auth/ServerSecretStore.ts
|
|
9696
9744
|
const secretStoreErrorContext = {
|
|
@@ -9757,7 +9805,7 @@ const isSecretStoreError = Schema$1.is(SecretStoreError);
|
|
|
9757
9805
|
const isPlatformError = (value) => Predicate.isTagged(value, "PlatformError");
|
|
9758
9806
|
const isSecretAlreadyExistsError = (error) => "cause" in error && isPlatformError(error.cause) && error.cause.reason._tag === "AlreadyExists";
|
|
9759
9807
|
var ServerSecretStore = class extends Context.Service()("@p4code/cli/auth/ServerSecretStore") {};
|
|
9760
|
-
const make$
|
|
9808
|
+
const make$70 = Effect.gen(function* () {
|
|
9761
9809
|
const crypto = yield* Crypto.Crypto;
|
|
9762
9810
|
const fileSystem = yield* FileSystem.FileSystem;
|
|
9763
9811
|
const path = yield* Path.Path;
|
|
@@ -9827,7 +9875,7 @@ const make$69 = Effect.gen(function* () {
|
|
|
9827
9875
|
remove
|
|
9828
9876
|
});
|
|
9829
9877
|
});
|
|
9830
|
-
const layer$
|
|
9878
|
+
const layer$66 = Layer.effect(ServerSecretStore, make$70);
|
|
9831
9879
|
//#endregion
|
|
9832
9880
|
//#region src/auth/SessionStore.ts
|
|
9833
9881
|
var MalformedSessionTokenError = class extends Schema$1.TaggedErrorClass()("MalformedSessionTokenError", {}) {
|
|
@@ -10065,7 +10113,7 @@ function toAuthClientSession(input) {
|
|
|
10065
10113
|
current: false
|
|
10066
10114
|
};
|
|
10067
10115
|
}
|
|
10068
|
-
const make$
|
|
10116
|
+
const make$69 = Effect.gen(function* () {
|
|
10069
10117
|
const crypto = yield* Crypto.Crypto;
|
|
10070
10118
|
const serverConfig = yield* ServerConfig$1;
|
|
10071
10119
|
const secretStore = yield* ServerSecretStore;
|
|
@@ -10379,7 +10427,7 @@ const make$68 = Effect.gen(function* () {
|
|
|
10379
10427
|
markDisconnected
|
|
10380
10428
|
});
|
|
10381
10429
|
});
|
|
10382
|
-
const layer$
|
|
10430
|
+
const layer$65 = Layer.effect(SessionStore, make$69).pipe(Layer.provideMerge(layer$67));
|
|
10383
10431
|
//#endregion
|
|
10384
10432
|
//#region src/persistence/AuthPairingLinks.ts
|
|
10385
10433
|
const AuthPairingLinkRecord = Schema$1.Struct({
|
|
@@ -10440,7 +10488,7 @@ function toPersistenceSqlOrDecodeError$5(sqlOperation, decodeOperation, correlat
|
|
|
10440
10488
|
cause
|
|
10441
10489
|
});
|
|
10442
10490
|
}
|
|
10443
|
-
const make$
|
|
10491
|
+
const make$68 = Effect.gen(function* () {
|
|
10444
10492
|
const sql = yield* SqlClient.SqlClient;
|
|
10445
10493
|
const createPairingLinkRow = SqlSchema.void({
|
|
10446
10494
|
Request: CreateAuthPairingLinkInput,
|
|
@@ -10575,7 +10623,7 @@ const make$67 = Effect.gen(function* () {
|
|
|
10575
10623
|
getByCredential
|
|
10576
10624
|
};
|
|
10577
10625
|
});
|
|
10578
|
-
const layer$
|
|
10626
|
+
const layer$64 = Layer.effect(AuthPairingLinkRepository, make$68);
|
|
10579
10627
|
//#endregion
|
|
10580
10628
|
//#region src/auth/PairingGrantStore.ts
|
|
10581
10629
|
var UnknownBootstrapCredentialError = class extends Schema$1.TaggedErrorClass()("UnknownBootstrapCredentialError", {}) {
|
|
@@ -10670,7 +10718,7 @@ const DEV_STARTUP_TTL_HOURS = Duration.hours(24);
|
|
|
10670
10718
|
const PAIRING_TOKEN_ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
|
|
10671
10719
|
const PAIRING_TOKEN_LENGTH = 12;
|
|
10672
10720
|
const PAIRING_TOKEN_REJECTION_LIMIT = Math.floor(256 / 32) * 32;
|
|
10673
|
-
const make$
|
|
10721
|
+
const make$67 = Effect.gen(function* () {
|
|
10674
10722
|
const crypto = yield* Crypto.Crypto;
|
|
10675
10723
|
const config = yield* ServerConfig$1;
|
|
10676
10724
|
const pairingLinks = yield* AuthPairingLinkRepository;
|
|
@@ -10868,7 +10916,7 @@ const make$66 = Effect.gen(function* () {
|
|
|
10868
10916
|
consume
|
|
10869
10917
|
});
|
|
10870
10918
|
});
|
|
10871
|
-
const layer$
|
|
10919
|
+
const layer$63 = Layer.effect(PairingGrantStore, make$67).pipe(Layer.provideMerge(layer$64));
|
|
10872
10920
|
//#endregion
|
|
10873
10921
|
//#region src/persistence/Migrations/001_OrchestrationEvents.ts
|
|
10874
10922
|
var _001_OrchestrationEvents_default = Effect.gen(function* () {
|
|
@@ -12723,7 +12771,7 @@ function parseBearerToken(request) {
|
|
|
12723
12771
|
const token = header.slice(7).trim();
|
|
12724
12772
|
return token.length > 0 ? token : null;
|
|
12725
12773
|
}
|
|
12726
|
-
const make$
|
|
12774
|
+
const make$66 = Effect.gen(function* () {
|
|
12727
12775
|
const policy = yield* EnvironmentAuthPolicy;
|
|
12728
12776
|
const bootstrapCredentials = yield* PairingGrantStore;
|
|
12729
12777
|
const sessions = yield* SessionStore;
|
|
@@ -12918,9 +12966,9 @@ const make$65 = Effect.gen(function* () {
|
|
|
12918
12966
|
issueStartupPairingUrl
|
|
12919
12967
|
});
|
|
12920
12968
|
});
|
|
12921
|
-
const layer$
|
|
12922
|
-
const storageLayer = Layer.mergeAll(layer$
|
|
12923
|
-
const runtimeLayer = layer$
|
|
12969
|
+
const layer$62 = Layer.effect(EnvironmentAuth, make$66).pipe(Layer.provideMerge(layer$63), Layer.provideMerge(layer$65), Layer.provideMerge(layer$68));
|
|
12970
|
+
const storageLayer = Layer.mergeAll(layer$66, layerConfig);
|
|
12971
|
+
const runtimeLayer = layer$62.pipe(Layer.provideMerge(storageLayer));
|
|
12924
12972
|
//#endregion
|
|
12925
12973
|
//#region src/cliAuthFormat.ts
|
|
12926
12974
|
const newline = "\n";
|
|
@@ -13675,7 +13723,7 @@ const runWithEnvironmentAuth = (flags, run, options) => Effect.gen(function* ()
|
|
|
13675
13723
|
}
|
|
13676
13724
|
return yield* Effect.gen(function* () {
|
|
13677
13725
|
return yield* run(yield* EnvironmentAuth);
|
|
13678
|
-
}).pipe(Effect.provide(Layer.mergeAll(runtimeLayer).pipe(Layer.provide(layer$
|
|
13726
|
+
}).pipe(Effect.provide(Layer.mergeAll(runtimeLayer).pipe(Layer.provide(layer$69(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)))));
|
|
13679
13727
|
});
|
|
13680
13728
|
const ttlFlag = Flag.string("ttl").pipe(Flag.withSchema(DurationFromString), Flag.withDescription("TTL, for example `5m`, `1h`, `30d`, or `15 minutes`."), Flag.optional);
|
|
13681
13729
|
const jsonFlag = Flag.boolean("json").pipe(Flag.withDescription("Emit JSON instead of human-readable output."), Flag.withDefault(false));
|
|
@@ -13815,7 +13863,7 @@ const DEFAULT_LIMITS = {
|
|
|
13815
13863
|
windowMillis: FAILURE_WINDOW_MS,
|
|
13816
13864
|
blockMillis: BLOCK_DURATION_MS
|
|
13817
13865
|
};
|
|
13818
|
-
const make$
|
|
13866
|
+
const make$65 = Effect.fn("HubAuthThrottle.make")(function* (limits = DEFAULT_LIMITS) {
|
|
13819
13867
|
const state = yield* Ref.make(initialThrottleState);
|
|
13820
13868
|
return HubAuthThrottle.of({
|
|
13821
13869
|
shouldRefuse: Effect.gen(function* () {
|
|
@@ -13829,7 +13877,7 @@ const make$64 = Effect.fn("HubAuthThrottle.make")(function* (limits = DEFAULT_LI
|
|
|
13829
13877
|
})
|
|
13830
13878
|
});
|
|
13831
13879
|
});
|
|
13832
|
-
const layer$
|
|
13880
|
+
const layer$61 = Layer.effect(HubAuthThrottle, make$65());
|
|
13833
13881
|
//#endregion
|
|
13834
13882
|
//#region src/hub/HubAuth.ts
|
|
13835
13883
|
/**
|
|
@@ -14628,7 +14676,7 @@ const deleteAssetRoute = HttpRouter.add("DELETE", "/assets/:kind/:name", respond
|
|
|
14628
14676
|
const outcome = yield* (yield* AgentAssetRepository).deleteByName(requested);
|
|
14629
14677
|
return outcome._tag === "conflict" ? assetConflict(Option.getOrNull(outcome.current)) : HttpServerResponse.empty({ status: 204 });
|
|
14630
14678
|
})));
|
|
14631
|
-
const layer$
|
|
14679
|
+
const layer$60 = Layer.mergeAll(healthRoute, listTasksRoute, getTaskRoute, createTaskRoute, updateTaskRoute, deleteTaskRoute, listAssetsRoute, getAssetRoute, putAssetRoute, deleteAssetRoute);
|
|
14632
14680
|
//#endregion
|
|
14633
14681
|
//#region src/hub/Migrations/001_Tasks.ts
|
|
14634
14682
|
/**
|
|
@@ -14907,7 +14955,7 @@ const announce = Layer.effectDiscard(Effect.gen(function* () {
|
|
|
14907
14955
|
});
|
|
14908
14956
|
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
14957
|
}));
|
|
14910
|
-
const makeHubLayer = HttpRouter.serve(layer$
|
|
14958
|
+
const makeHubLayer = HttpRouter.serve(layer$60).pipe(Layer.provideMerge(announce), Layer.provideMerge(HubHttpServerLive), Layer.provideMerge(layer$61), Layer.provideMerge(TaskRepositoryLive), Layer.provideMerge(AgentAssetRepositoryLive), Layer.provideMerge(HubPersistenceLive));
|
|
14911
14959
|
const runHub = Layer.launch(makeHubLayer);
|
|
14912
14960
|
//#endregion
|
|
14913
14961
|
//#region src/atomicWrite.ts
|
|
@@ -15031,7 +15079,7 @@ function stripDefaultServerSettings(current, defaults) {
|
|
|
15031
15079
|
}
|
|
15032
15080
|
return Object.is(current, defaults) ? void 0 : current;
|
|
15033
15081
|
}
|
|
15034
|
-
const make$
|
|
15082
|
+
const make$64 = Effect.gen(function* () {
|
|
15035
15083
|
const { settingsPath } = yield* ServerConfig$1;
|
|
15036
15084
|
const fs = yield* FileSystem.FileSystem;
|
|
15037
15085
|
const pathService = yield* Path.Path;
|
|
@@ -15252,7 +15300,7 @@ const make$63 = Effect.gen(function* () {
|
|
|
15252
15300
|
}
|
|
15253
15301
|
};
|
|
15254
15302
|
});
|
|
15255
|
-
const layer$
|
|
15303
|
+
const layer$59 = Layer.effect(ServerSettingsService, make$64);
|
|
15256
15304
|
//#endregion
|
|
15257
15305
|
//#region src/pathExpansion.ts
|
|
15258
15306
|
/**
|
|
@@ -15461,6 +15509,464 @@ const discoverClaudeSkills = Effect.fn("discoverClaudeSkills")(function* (config
|
|
|
15461
15509
|
}
|
|
15462
15510
|
return [...skillsByName.values()].sort((left, right) => left.name.localeCompare(right.name));
|
|
15463
15511
|
});
|
|
15512
|
+
//#endregion
|
|
15513
|
+
//#region src/mcp/McpOAuth.ts
|
|
15514
|
+
/**
|
|
15515
|
+
* OAuth sign-in for remote MCP servers, per the MCP authorization spec.
|
|
15516
|
+
*
|
|
15517
|
+
* The whole flow runs on this server: RFC 9728 protected-resource discovery,
|
|
15518
|
+
* RFC 8414 authorization-server metadata, RFC 7591 dynamic client
|
|
15519
|
+
* registration, PKCE, and the token exchange when the browser lands on
|
|
15520
|
+
* `/oauth/callback`. The panel only ever sees one string — the authorize URL —
|
|
15521
|
+
* because everything else in the flow is either a secret or only meaningful to
|
|
15522
|
+
* the machine the callback returns to.
|
|
15523
|
+
*
|
|
15524
|
+
* **Everything learned per server is one JSON secret** in `ServerSecretStore`
|
|
15525
|
+
* (`mcp.<server>.oauth`): client id, endpoints, tokens. Registrations sync
|
|
15526
|
+
* between machines; this record deliberately does not, which is what makes
|
|
15527
|
+
* "each machine signs in for itself" true by construction rather than policy.
|
|
15528
|
+
*
|
|
15529
|
+
* @module mcp/McpOAuth
|
|
15530
|
+
*/
|
|
15531
|
+
/** How long a probe's verdict is trusted before the server is asked again. */
|
|
15532
|
+
const PROBE_CACHE_TTL_MS = 300 * 1e3;
|
|
15533
|
+
/** An authorize URL nobody returned from within this window is abandoned. */
|
|
15534
|
+
const PENDING_FLOW_TTL_MS = 600 * 1e3;
|
|
15535
|
+
/** A token this close to expiry is refreshed rather than handed to a session. */
|
|
15536
|
+
const TOKEN_EXPIRY_MARGIN_MS = 60 * 1e3;
|
|
15537
|
+
/** Outbound discovery, registration and token requests all use this cap. */
|
|
15538
|
+
const OAUTH_REQUEST_TIMEOUT_MS = 10 * 1e3;
|
|
15539
|
+
const CODE_VERIFIER_BYTES = 32;
|
|
15540
|
+
const STATE_BYTES = 16;
|
|
15541
|
+
/** Sent in the probe's initialize request; any spec revision the server accepts will do. */
|
|
15542
|
+
const MCP_PROBE_PROTOCOL_VERSION = "2025-06-18";
|
|
15543
|
+
/** What the authorization server's consent screen shows. See `registerClient`. */
|
|
15544
|
+
const OAUTH_CLIENT_NAME = "Claude Code (p4code)";
|
|
15545
|
+
/** The path the environment server answers on; also sent as the redirect URI. */
|
|
15546
|
+
const OAUTH_CALLBACK_PATH = "/oauth/callback";
|
|
15547
|
+
const StoredTokens = Schema$1.Struct({
|
|
15548
|
+
accessToken: Schema$1.String,
|
|
15549
|
+
refreshToken: Schema$1.optional(Schema$1.String),
|
|
15550
|
+
/** Epoch milliseconds. Absent when the server did not say. */
|
|
15551
|
+
expiresAt: Schema$1.optional(Schema$1.Number)
|
|
15552
|
+
});
|
|
15553
|
+
/**
|
|
15554
|
+
* Everything this machine holds about one server's OAuth relationship.
|
|
15555
|
+
*
|
|
15556
|
+
* Endpoints and client id live beside the tokens so that disconnect can drop
|
|
15557
|
+
* the tokens and a later reconnect can skip re-registration — a client id is
|
|
15558
|
+
* not a credential, but re-registering on every sign-in litters the
|
|
15559
|
+
* authorization server with one-use clients.
|
|
15560
|
+
*/
|
|
15561
|
+
const StoredOAuth = Schema$1.Struct({
|
|
15562
|
+
clientId: Schema$1.String,
|
|
15563
|
+
clientSecret: Schema$1.optional(Schema$1.String),
|
|
15564
|
+
authorizationEndpoint: Schema$1.String,
|
|
15565
|
+
tokenEndpoint: Schema$1.String,
|
|
15566
|
+
/** RFC 8707 resource indicator: the canonical MCP server URL. */
|
|
15567
|
+
resource: Schema$1.optional(Schema$1.String),
|
|
15568
|
+
scope: Schema$1.optional(Schema$1.String),
|
|
15569
|
+
tokens: Schema$1.optional(StoredTokens)
|
|
15570
|
+
});
|
|
15571
|
+
const StoredOAuthFromJson = Schema$1.fromJsonString(StoredOAuth);
|
|
15572
|
+
const decodeStored = Schema$1.decodeUnknownExit(StoredOAuthFromJson);
|
|
15573
|
+
const encodeStored = Schema$1.encodeSync(StoredOAuthFromJson);
|
|
15574
|
+
/**
|
|
15575
|
+
* Parse a `WWW-Authenticate` challenge into its scheme and parameters.
|
|
15576
|
+
*
|
|
15577
|
+
* Handles the shape MCP servers actually send — `Bearer` followed by
|
|
15578
|
+
* comma-separated `key="value"` or bare `key=value` pairs. Exported for tests;
|
|
15579
|
+
* a wrong parse here turns "needs sign-in" into "broken server".
|
|
15580
|
+
*/
|
|
15581
|
+
function parseWwwAuthenticate(header) {
|
|
15582
|
+
const trimmed = header.trim();
|
|
15583
|
+
const spaceIndex = trimmed.indexOf(" ");
|
|
15584
|
+
const scheme = (spaceIndex === -1 ? trimmed : trimmed.slice(0, spaceIndex)).toLowerCase();
|
|
15585
|
+
const rest = spaceIndex === -1 ? "" : trimmed.slice(spaceIndex + 1);
|
|
15586
|
+
const params = {};
|
|
15587
|
+
for (const match of rest.matchAll(/([a-zA-Z0-9_-]+)\s*=\s*(?:"([^"]*)"|([^\s,]+))/gu)) {
|
|
15588
|
+
const key = match[1];
|
|
15589
|
+
const value = match[2] ?? match[3];
|
|
15590
|
+
if (key !== void 0 && value !== void 0) params[key.toLowerCase()] = value;
|
|
15591
|
+
}
|
|
15592
|
+
return {
|
|
15593
|
+
scheme,
|
|
15594
|
+
params
|
|
15595
|
+
};
|
|
15596
|
+
}
|
|
15597
|
+
/**
|
|
15598
|
+
* The RFC 8414 lookup locations for an issuer, in the order worth trying.
|
|
15599
|
+
*
|
|
15600
|
+
* Path insertion first — `https://a/.well-known/oauth-authorization-server/x`
|
|
15601
|
+
* for issuer `https://a/x` — then the OIDC variants, because real servers are
|
|
15602
|
+
* split between the two specs and a miss costs one request.
|
|
15603
|
+
*/
|
|
15604
|
+
function authServerMetadataUrls(issuer) {
|
|
15605
|
+
const url = new URL(issuer);
|
|
15606
|
+
const path = url.pathname === "/" ? "" : url.pathname.replace(/\/$/u, "");
|
|
15607
|
+
return [
|
|
15608
|
+
`${url.origin}/.well-known/oauth-authorization-server${path}`,
|
|
15609
|
+
`${url.origin}/.well-known/openid-configuration${path}`,
|
|
15610
|
+
...path === "" ? [] : [`${url.origin}${path}/.well-known/openid-configuration`]
|
|
15611
|
+
];
|
|
15612
|
+
}
|
|
15613
|
+
const AuthServerMetadata = Schema$1.Struct({
|
|
15614
|
+
authorization_endpoint: Schema$1.String,
|
|
15615
|
+
token_endpoint: Schema$1.String,
|
|
15616
|
+
registration_endpoint: Schema$1.optional(Schema$1.String),
|
|
15617
|
+
scopes_supported: Schema$1.optional(Schema$1.Array(Schema$1.String))
|
|
15618
|
+
});
|
|
15619
|
+
const ProtectedResourceMetadata = Schema$1.Struct({
|
|
15620
|
+
authorization_servers: Schema$1.optional(Schema$1.Array(Schema$1.String)),
|
|
15621
|
+
scopes_supported: Schema$1.optional(Schema$1.Array(Schema$1.String))
|
|
15622
|
+
});
|
|
15623
|
+
const ClientRegistrationResponse = Schema$1.Struct({
|
|
15624
|
+
client_id: Schema$1.String,
|
|
15625
|
+
client_secret: Schema$1.optional(Schema$1.String)
|
|
15626
|
+
});
|
|
15627
|
+
const TokenResponse = Schema$1.Struct({
|
|
15628
|
+
access_token: Schema$1.String,
|
|
15629
|
+
refresh_token: Schema$1.optional(Schema$1.String),
|
|
15630
|
+
expires_in: Schema$1.optional(Schema$1.Number)
|
|
15631
|
+
});
|
|
15632
|
+
const decodeJsonBody = Schema$1.decodeUnknownExit(Schema$1.UnknownFromJsonString);
|
|
15633
|
+
const decodeAuthServerMetadata = Schema$1.decodeUnknownExit(AuthServerMetadata);
|
|
15634
|
+
const decodeProtectedResourceMetadata = Schema$1.decodeUnknownExit(ProtectedResourceMetadata);
|
|
15635
|
+
const decodeClientRegistration = Schema$1.decodeUnknownExit(ClientRegistrationResponse);
|
|
15636
|
+
const decodeTokenResponse = Schema$1.decodeUnknownExit(TokenResponse);
|
|
15637
|
+
var McpOAuth = class extends Context.Service()("@p4code/cli/mcp/McpOAuth") {};
|
|
15638
|
+
const registryError = (detail) => new McpRegistryError({ detail });
|
|
15639
|
+
const make$63 = Effect.gen(function* () {
|
|
15640
|
+
const config = yield* ServerConfig$1;
|
|
15641
|
+
const secrets = yield* ServerSecretStore;
|
|
15642
|
+
const http = yield* HttpClient.HttpClient;
|
|
15643
|
+
const crypto = yield* Crypto.Crypto;
|
|
15644
|
+
const pendingFlows = yield* Ref.make(/* @__PURE__ */ new Map());
|
|
15645
|
+
const probeCache = yield* Ref.make(/* @__PURE__ */ new Map());
|
|
15646
|
+
const redirectUri = `http://127.0.0.1:${config.port}${OAUTH_CALLBACK_PATH}`;
|
|
15647
|
+
const nowMillis = Effect.map(DateTime.now, DateTime.toEpochMillis);
|
|
15648
|
+
const readRecord = Effect.fn("McpOAuth.readRecord")(function* (server) {
|
|
15649
|
+
const stored = yield* secrets.get(mcpOAuthStorageName(server)).pipe(Effect.orElseSucceed(() => Option.none()));
|
|
15650
|
+
if (Option.isNone(stored)) return Option.none();
|
|
15651
|
+
const decoded = decodeStored(new TextDecoder().decode(stored.value));
|
|
15652
|
+
return decoded._tag === "Success" ? Option.some(decoded.value) : Option.none();
|
|
15653
|
+
});
|
|
15654
|
+
const writeRecord = Effect.fn("McpOAuth.writeRecord")(function* (server, record) {
|
|
15655
|
+
yield* secrets.set(mcpOAuthStorageName(server), new TextEncoder().encode(encodeStored(record))).pipe(Effect.catchCause((cause) => Effect.logWarning("mcp oauth record write failed", {
|
|
15656
|
+
cause,
|
|
15657
|
+
server
|
|
15658
|
+
})));
|
|
15659
|
+
});
|
|
15660
|
+
const fetchJson = Effect.fn("McpOAuth.fetchJson")(function* (request) {
|
|
15661
|
+
const response = yield* http.execute(HttpClientRequest.setHeader(request, "accept", "application/json")).pipe(Effect.timeout(OAUTH_REQUEST_TIMEOUT_MS));
|
|
15662
|
+
const text = yield* response.text;
|
|
15663
|
+
const parsed = decodeJsonBody(text);
|
|
15664
|
+
return {
|
|
15665
|
+
status: response.status,
|
|
15666
|
+
json: parsed._tag === "Success" ? parsed.value : void 0
|
|
15667
|
+
};
|
|
15668
|
+
});
|
|
15669
|
+
/**
|
|
15670
|
+
* Ask the server, without credentials, whether it wants any.
|
|
15671
|
+
*
|
|
15672
|
+
* A real MCP initialize request rather than a bare GET, because the GET is
|
|
15673
|
+
* not part of the streamable-http contract and real servers 405 it — Figma
|
|
15674
|
+
* does — while answering the initialize POST with the 401 challenge this
|
|
15675
|
+
* probe exists to see.
|
|
15676
|
+
*/
|
|
15677
|
+
const probe = Effect.fn("McpOAuth.probe")(function* (registration) {
|
|
15678
|
+
const now = yield* nowMillis;
|
|
15679
|
+
const cached = (yield* Ref.get(probeCache)).get(registration.url);
|
|
15680
|
+
if (cached !== void 0 && now - cached.at < PROBE_CACHE_TTL_MS) return cached;
|
|
15681
|
+
const verdict = yield* http.execute(HttpClientRequest.bodyJsonUnsafe(HttpClientRequest.setHeaders(HttpClientRequest.post(registration.url), {
|
|
15682
|
+
...registration.headers,
|
|
15683
|
+
accept: "application/json, text/event-stream"
|
|
15684
|
+
}), {
|
|
15685
|
+
jsonrpc: "2.0",
|
|
15686
|
+
id: 0,
|
|
15687
|
+
method: "initialize",
|
|
15688
|
+
params: {
|
|
15689
|
+
protocolVersion: MCP_PROBE_PROTOCOL_VERSION,
|
|
15690
|
+
capabilities: {},
|
|
15691
|
+
clientInfo: {
|
|
15692
|
+
name: "p4code",
|
|
15693
|
+
version: "0.0.0"
|
|
15694
|
+
}
|
|
15695
|
+
}
|
|
15696
|
+
})).pipe(Effect.timeout(OAUTH_REQUEST_TIMEOUT_MS), Effect.map((response) => {
|
|
15697
|
+
const challengeHeader = response.headers["www-authenticate"];
|
|
15698
|
+
const challenge = response.status === 401 && challengeHeader !== void 0 ? parseWwwAuthenticate(challengeHeader) : void 0;
|
|
15699
|
+
return {
|
|
15700
|
+
at: now,
|
|
15701
|
+
requiresAuth: response.status === 401,
|
|
15702
|
+
resourceMetadataUrl: challenge?.params["resource_metadata"],
|
|
15703
|
+
scope: challenge?.params["scope"]
|
|
15704
|
+
};
|
|
15705
|
+
}), Effect.orElseSucceed(() => ({
|
|
15706
|
+
at: now,
|
|
15707
|
+
requiresAuth: false,
|
|
15708
|
+
resourceMetadataUrl: void 0,
|
|
15709
|
+
scope: void 0
|
|
15710
|
+
})));
|
|
15711
|
+
yield* Ref.update(probeCache, (cache) => {
|
|
15712
|
+
const next = new Map(cache);
|
|
15713
|
+
next.set(registration.url, verdict);
|
|
15714
|
+
return next;
|
|
15715
|
+
});
|
|
15716
|
+
return verdict;
|
|
15717
|
+
});
|
|
15718
|
+
const forgetProbe = (url) => Ref.update(probeCache, (cache) => {
|
|
15719
|
+
const next = new Map(cache);
|
|
15720
|
+
next.delete(url);
|
|
15721
|
+
return next;
|
|
15722
|
+
});
|
|
15723
|
+
const statusFor = (registration) => Effect.gen(function* () {
|
|
15724
|
+
const record = yield* readRecord(registration.name);
|
|
15725
|
+
if (Option.isSome(record) && record.value.tokens !== void 0) return Option.some({
|
|
15726
|
+
required: true,
|
|
15727
|
+
connected: true
|
|
15728
|
+
});
|
|
15729
|
+
if ((yield* probe(registration)).requiresAuth || Option.isSome(record)) return Option.some({
|
|
15730
|
+
required: true,
|
|
15731
|
+
connected: false
|
|
15732
|
+
});
|
|
15733
|
+
return Option.none();
|
|
15734
|
+
}).pipe(Effect.catchCause((cause) => Effect.logWarning("mcp oauth status failed", {
|
|
15735
|
+
cause,
|
|
15736
|
+
server: registration.name
|
|
15737
|
+
}).pipe(Effect.as(Option.none()))));
|
|
15738
|
+
/**
|
|
15739
|
+
* Find the authorization server for an MCP endpoint.
|
|
15740
|
+
*
|
|
15741
|
+
* RFC 9728 first: the 401 challenge names a protected-resource metadata URL,
|
|
15742
|
+
* which names the authorization servers. When the challenge does not say —
|
|
15743
|
+
* the spec's fallback — the MCP origin itself is tried as the issuer.
|
|
15744
|
+
*/
|
|
15745
|
+
const discover = Effect.fn("McpOAuth.discover")(function* (registration) {
|
|
15746
|
+
yield* forgetProbe(registration.url);
|
|
15747
|
+
const verdict = yield* probe(registration);
|
|
15748
|
+
let issuers = [new URL(registration.url).origin];
|
|
15749
|
+
let resourceScopes;
|
|
15750
|
+
const metadataUrl = verdict.resourceMetadataUrl ?? `${new URL(registration.url).origin}/.well-known/oauth-protected-resource${new URL(registration.url).pathname.replace(/\/$/u, "")}`;
|
|
15751
|
+
const resourceMetadata = yield* fetchJson(HttpClientRequest.get(metadataUrl)).pipe(Effect.map(({ status, json }) => {
|
|
15752
|
+
if (status !== 200) return void 0;
|
|
15753
|
+
const decoded = decodeProtectedResourceMetadata(json);
|
|
15754
|
+
return decoded._tag === "Success" ? decoded.value : void 0;
|
|
15755
|
+
}), Effect.orElseSucceed(() => void 0));
|
|
15756
|
+
if (resourceMetadata !== void 0) {
|
|
15757
|
+
if ((resourceMetadata.authorization_servers?.length ?? 0) > 0) issuers = resourceMetadata.authorization_servers ?? issuers;
|
|
15758
|
+
resourceScopes = resourceMetadata.scopes_supported;
|
|
15759
|
+
}
|
|
15760
|
+
for (const issuer of issuers) for (const candidate of authServerMetadataUrls(issuer)) {
|
|
15761
|
+
const metadata = yield* fetchJson(HttpClientRequest.get(candidate)).pipe(Effect.map(({ status, json }) => {
|
|
15762
|
+
if (status !== 200) return void 0;
|
|
15763
|
+
const decoded = decodeAuthServerMetadata(json);
|
|
15764
|
+
return decoded._tag === "Success" ? decoded.value : void 0;
|
|
15765
|
+
}), Effect.orElseSucceed(() => void 0));
|
|
15766
|
+
if (metadata !== void 0) return {
|
|
15767
|
+
metadata,
|
|
15768
|
+
scope: verdict.scope ?? (resourceScopes !== void 0 && resourceScopes.length > 0 ? resourceScopes.join(" ") : void 0)
|
|
15769
|
+
};
|
|
15770
|
+
}
|
|
15771
|
+
return yield* registryError(`Could not discover an OAuth authorization server for ${registration.url}.`);
|
|
15772
|
+
});
|
|
15773
|
+
const registerClient = Effect.fn("McpOAuth.registerClient")(function* (metadata) {
|
|
15774
|
+
if (metadata.registration_endpoint === void 0) return yield* registryError("The authorization server does not support dynamic client registration.");
|
|
15775
|
+
const { status, json } = yield* fetchJson(HttpClientRequest.bodyJsonUnsafe(HttpClientRequest.post(metadata.registration_endpoint), {
|
|
15776
|
+
client_name: OAUTH_CLIENT_NAME,
|
|
15777
|
+
redirect_uris: [redirectUri],
|
|
15778
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
15779
|
+
response_types: ["code"],
|
|
15780
|
+
token_endpoint_auth_method: "none"
|
|
15781
|
+
})).pipe(Effect.mapError(() => registryError("Client registration request failed.")));
|
|
15782
|
+
if (status !== 200 && status !== 201) return yield* registryError(`Client registration was rejected with status ${status}.`);
|
|
15783
|
+
const decoded = decodeClientRegistration(json);
|
|
15784
|
+
if (decoded._tag !== "Success") return yield* registryError("Client registration answered an unexpected shape.");
|
|
15785
|
+
return decoded.value;
|
|
15786
|
+
});
|
|
15787
|
+
const randomUrlSafe = (bytes) => crypto.randomBytes(bytes).pipe(Effect.map(Encoding.encodeBase64Url));
|
|
15788
|
+
const begin = (registration) => Effect.gen(function* () {
|
|
15789
|
+
const { metadata, scope } = yield* discover(registration);
|
|
15790
|
+
const existing = yield* readRecord(registration.name);
|
|
15791
|
+
const reusable = Option.filter(existing, (record) => record.tokenEndpoint === metadata.token_endpoint && record.authorizationEndpoint === metadata.authorization_endpoint);
|
|
15792
|
+
const client = Option.isSome(reusable) ? {
|
|
15793
|
+
client_id: reusable.value.clientId,
|
|
15794
|
+
client_secret: reusable.value.clientSecret
|
|
15795
|
+
} : yield* registerClient(metadata);
|
|
15796
|
+
const codeVerifier = yield* randomUrlSafe(CODE_VERIFIER_BYTES).pipe(Effect.mapError(() => registryError("Could not generate a PKCE verifier.")));
|
|
15797
|
+
const challengeBytes = yield* crypto.digest("SHA-256", new TextEncoder().encode(codeVerifier)).pipe(Effect.mapError(() => registryError("Could not hash the PKCE verifier.")));
|
|
15798
|
+
const state = yield* randomUrlSafe(STATE_BYTES).pipe(Effect.mapError(() => registryError("Could not generate a state value.")));
|
|
15799
|
+
const record = {
|
|
15800
|
+
clientId: client.client_id,
|
|
15801
|
+
...client.client_secret === void 0 ? {} : { clientSecret: client.client_secret },
|
|
15802
|
+
authorizationEndpoint: metadata.authorization_endpoint,
|
|
15803
|
+
tokenEndpoint: metadata.token_endpoint,
|
|
15804
|
+
resource: registration.url,
|
|
15805
|
+
...scope === void 0 ? {} : { scope }
|
|
15806
|
+
};
|
|
15807
|
+
yield* writeRecord(registration.name, record);
|
|
15808
|
+
const startedAt = yield* nowMillis;
|
|
15809
|
+
yield* Ref.update(pendingFlows, (flows) => {
|
|
15810
|
+
const next = new Map([...flows].filter(([, flow]) => startedAt - flow.startedAt < PENDING_FLOW_TTL_MS));
|
|
15811
|
+
next.set(state, {
|
|
15812
|
+
server: registration.name,
|
|
15813
|
+
codeVerifier,
|
|
15814
|
+
redirectUri,
|
|
15815
|
+
record,
|
|
15816
|
+
startedAt
|
|
15817
|
+
});
|
|
15818
|
+
return next;
|
|
15819
|
+
});
|
|
15820
|
+
const authorizeUrl = new URL(metadata.authorization_endpoint);
|
|
15821
|
+
authorizeUrl.searchParams.set("response_type", "code");
|
|
15822
|
+
authorizeUrl.searchParams.set("client_id", client.client_id);
|
|
15823
|
+
authorizeUrl.searchParams.set("redirect_uri", redirectUri);
|
|
15824
|
+
authorizeUrl.searchParams.set("state", state);
|
|
15825
|
+
authorizeUrl.searchParams.set("code_challenge", Encoding.encodeBase64Url(challengeBytes));
|
|
15826
|
+
authorizeUrl.searchParams.set("code_challenge_method", "S256");
|
|
15827
|
+
authorizeUrl.searchParams.set("resource", registration.url);
|
|
15828
|
+
if (scope !== void 0) authorizeUrl.searchParams.set("scope", scope);
|
|
15829
|
+
return { authorizeUrl: authorizeUrl.toString() };
|
|
15830
|
+
});
|
|
15831
|
+
const exchangeToken = Effect.fn("McpOAuth.exchangeToken")(function* (flow, code) {
|
|
15832
|
+
const { status, json } = yield* fetchJson(HttpClientRequest.bodyUrlParams(HttpClientRequest.post(flow.record.tokenEndpoint), {
|
|
15833
|
+
grant_type: "authorization_code",
|
|
15834
|
+
code,
|
|
15835
|
+
redirect_uri: flow.redirectUri,
|
|
15836
|
+
client_id: flow.record.clientId,
|
|
15837
|
+
code_verifier: flow.codeVerifier,
|
|
15838
|
+
...flow.record.clientSecret === void 0 ? {} : { client_secret: flow.record.clientSecret },
|
|
15839
|
+
...flow.record.resource === void 0 ? {} : { resource: flow.record.resource }
|
|
15840
|
+
}));
|
|
15841
|
+
if (status !== 200) return yield* Effect.fail(`token endpoint answered status ${status}`);
|
|
15842
|
+
const decoded = decodeTokenResponse(json);
|
|
15843
|
+
if (decoded._tag !== "Success") return yield* Effect.fail("token endpoint answered an unexpected shape");
|
|
15844
|
+
return decoded.value;
|
|
15845
|
+
});
|
|
15846
|
+
const handleCallback = (params) => Effect.gen(function* () {
|
|
15847
|
+
if (params.state === void 0) return {
|
|
15848
|
+
ok: false,
|
|
15849
|
+
detail: "The redirect carried no state value."
|
|
15850
|
+
};
|
|
15851
|
+
const flow = (yield* Ref.get(pendingFlows)).get(params.state);
|
|
15852
|
+
if (flow === void 0) return {
|
|
15853
|
+
ok: false,
|
|
15854
|
+
detail: "This sign-in is unknown or has expired. Start it again from settings."
|
|
15855
|
+
};
|
|
15856
|
+
yield* Ref.update(pendingFlows, (current) => {
|
|
15857
|
+
const next = new Map(current);
|
|
15858
|
+
next.delete(params.state);
|
|
15859
|
+
return next;
|
|
15860
|
+
});
|
|
15861
|
+
if (params.error !== void 0) return {
|
|
15862
|
+
ok: false,
|
|
15863
|
+
detail: params.errorDescription ?? `The authorization server answered: ${params.error}.`
|
|
15864
|
+
};
|
|
15865
|
+
if (params.code === void 0) return {
|
|
15866
|
+
ok: false,
|
|
15867
|
+
detail: "The redirect carried no authorization code."
|
|
15868
|
+
};
|
|
15869
|
+
const now = yield* nowMillis;
|
|
15870
|
+
const exchanged = yield* exchangeToken(flow, params.code).pipe(Effect.map((response) => ({
|
|
15871
|
+
failed: false,
|
|
15872
|
+
response
|
|
15873
|
+
})), Effect.catch((detail) => Effect.succeed({
|
|
15874
|
+
failed: true,
|
|
15875
|
+
detail: typeof detail === "string" ? detail : "the token request failed"
|
|
15876
|
+
})));
|
|
15877
|
+
if (exchanged.failed) {
|
|
15878
|
+
yield* Effect.logWarning("mcp oauth token exchange failed", {
|
|
15879
|
+
server: flow.server,
|
|
15880
|
+
detail: exchanged.detail
|
|
15881
|
+
});
|
|
15882
|
+
return {
|
|
15883
|
+
ok: false,
|
|
15884
|
+
detail: `Token exchange failed: ${exchanged.detail}.`
|
|
15885
|
+
};
|
|
15886
|
+
}
|
|
15887
|
+
yield* writeRecord(flow.server, {
|
|
15888
|
+
...flow.record,
|
|
15889
|
+
tokens: {
|
|
15890
|
+
accessToken: exchanged.response.access_token,
|
|
15891
|
+
...exchanged.response.refresh_token === void 0 ? {} : { refreshToken: exchanged.response.refresh_token },
|
|
15892
|
+
...exchanged.response.expires_in === void 0 ? {} : { expiresAt: now + exchanged.response.expires_in * 1e3 }
|
|
15893
|
+
}
|
|
15894
|
+
});
|
|
15895
|
+
return {
|
|
15896
|
+
ok: true,
|
|
15897
|
+
server: flow.server
|
|
15898
|
+
};
|
|
15899
|
+
}).pipe(Effect.catchCause((cause) => Effect.logWarning("mcp oauth callback failed", { cause }).pipe(Effect.as({
|
|
15900
|
+
ok: false,
|
|
15901
|
+
detail: "The sign-in failed. See the server log."
|
|
15902
|
+
}))));
|
|
15903
|
+
const disconnect = (name) => Effect.gen(function* () {
|
|
15904
|
+
const record = yield* readRecord(name);
|
|
15905
|
+
if (Option.isNone(record)) return;
|
|
15906
|
+
const { tokens: _dropped, ...rest } = record.value;
|
|
15907
|
+
yield* writeRecord(name, rest);
|
|
15908
|
+
}).pipe(Effect.catchCause((cause) => Effect.logWarning("mcp oauth disconnect failed", {
|
|
15909
|
+
cause,
|
|
15910
|
+
server: name
|
|
15911
|
+
}).pipe(Effect.asVoid)));
|
|
15912
|
+
const refreshTokens = Effect.fn("McpOAuth.refreshTokens")(function* (server, record, refreshToken) {
|
|
15913
|
+
const { status, json } = yield* fetchJson(HttpClientRequest.bodyUrlParams(HttpClientRequest.post(record.tokenEndpoint), {
|
|
15914
|
+
grant_type: "refresh_token",
|
|
15915
|
+
refresh_token: refreshToken,
|
|
15916
|
+
client_id: record.clientId,
|
|
15917
|
+
...record.clientSecret === void 0 ? {} : { client_secret: record.clientSecret },
|
|
15918
|
+
...record.resource === void 0 ? {} : { resource: record.resource }
|
|
15919
|
+
}));
|
|
15920
|
+
if (status !== 200) return yield* Effect.fail(`status ${status}`);
|
|
15921
|
+
const decoded = decodeTokenResponse(json);
|
|
15922
|
+
if (decoded._tag !== "Success") return yield* Effect.fail("unexpected shape");
|
|
15923
|
+
const response = decoded.value;
|
|
15924
|
+
const now = yield* nowMillis;
|
|
15925
|
+
const tokens = {
|
|
15926
|
+
accessToken: response.access_token,
|
|
15927
|
+
refreshToken: response.refresh_token ?? refreshToken,
|
|
15928
|
+
...response.expires_in === void 0 ? {} : { expiresAt: now + response.expires_in * 1e3 }
|
|
15929
|
+
};
|
|
15930
|
+
yield* writeRecord(server, {
|
|
15931
|
+
...record,
|
|
15932
|
+
tokens
|
|
15933
|
+
});
|
|
15934
|
+
return tokens.accessToken;
|
|
15935
|
+
});
|
|
15936
|
+
const accessTokenFor = (registration) => Effect.gen(function* () {
|
|
15937
|
+
const record = yield* readRecord(registration.name);
|
|
15938
|
+
if (Option.isNone(record) || record.value.tokens === void 0) return Option.none();
|
|
15939
|
+
const tokens = record.value.tokens;
|
|
15940
|
+
const now = yield* nowMillis;
|
|
15941
|
+
if (!(tokens.expiresAt !== void 0 && now >= tokens.expiresAt - TOKEN_EXPIRY_MARGIN_MS)) return Option.some(tokens.accessToken);
|
|
15942
|
+
if (tokens.refreshToken === void 0) return Option.none();
|
|
15943
|
+
return yield* refreshTokens(registration.name, record.value, tokens.refreshToken).pipe(Effect.map(Option.some), Effect.catchCause((cause) => Effect.logWarning("mcp oauth refresh failed", {
|
|
15944
|
+
cause,
|
|
15945
|
+
server: registration.name
|
|
15946
|
+
}).pipe(Effect.as(Option.none()))));
|
|
15947
|
+
}).pipe(Effect.catchCause((cause) => Effect.logWarning("mcp oauth token lookup failed", {
|
|
15948
|
+
cause,
|
|
15949
|
+
server: registration.name
|
|
15950
|
+
}).pipe(Effect.as(Option.none()))));
|
|
15951
|
+
return {
|
|
15952
|
+
statusFor,
|
|
15953
|
+
begin,
|
|
15954
|
+
handleCallback,
|
|
15955
|
+
disconnect,
|
|
15956
|
+
accessTokenFor
|
|
15957
|
+
};
|
|
15958
|
+
});
|
|
15959
|
+
const layer$58 = Layer.effect(McpOAuth, make$63);
|
|
15960
|
+
Layer.succeed(McpOAuth, {
|
|
15961
|
+
statusFor: () => Effect.succeed(Option.none()),
|
|
15962
|
+
begin: () => Effect.fail(new McpRegistryError({ detail: "OAuth sign-in is not available." })),
|
|
15963
|
+
handleCallback: () => Effect.succeed({
|
|
15964
|
+
ok: false,
|
|
15965
|
+
detail: "OAuth sign-in is not available."
|
|
15966
|
+
}),
|
|
15967
|
+
disconnect: () => Effect.void,
|
|
15968
|
+
accessTokenFor: () => Effect.succeed(Option.none())
|
|
15969
|
+
});
|
|
15464
15970
|
/** Extension for a registration file, so the sync's asset name is `<server>.json`. */
|
|
15465
15971
|
const MCP_REGISTRATION_SUFFIX = ".json";
|
|
15466
15972
|
const RegistrationFromJson$1 = Schema$1.fromJsonString(McpServerRegistration);
|
|
@@ -15471,6 +15977,7 @@ const slotsOf = (registration) => registration.secrets ?? [];
|
|
|
15471
15977
|
const make$62 = Effect.gen(function* () {
|
|
15472
15978
|
const config = yield* ServerConfig$1;
|
|
15473
15979
|
const secrets = yield* ServerSecretStore;
|
|
15980
|
+
const oauth = yield* McpOAuth;
|
|
15474
15981
|
const fileSystem = yield* FileSystem.FileSystem;
|
|
15475
15982
|
const path = yield* Path.Path;
|
|
15476
15983
|
const services = yield* Effect.context();
|
|
@@ -15513,10 +16020,13 @@ const make$62 = Effect.gen(function* () {
|
|
|
15513
16020
|
const value = yield* readSecret(registration.name, slot);
|
|
15514
16021
|
if (Option.isNone(value)) missingSecrets.push(slot);
|
|
15515
16022
|
}
|
|
16023
|
+
const oauthStatus = registration.transport === "stdio" || !registration.enabled ? Option.none() : yield* oauth.statusFor(registration);
|
|
16024
|
+
const needsSignIn = Option.isSome(oauthStatus) && !oauthStatus.value.connected;
|
|
15516
16025
|
statuses.push({
|
|
15517
16026
|
registration,
|
|
15518
16027
|
missingSecrets,
|
|
15519
|
-
ready: registration.enabled && missingSecrets.length === 0
|
|
16028
|
+
ready: registration.enabled && missingSecrets.length === 0 && !needsSignIn,
|
|
16029
|
+
...Option.isSome(oauthStatus) ? { oauth: oauthStatus.value } : {}
|
|
15520
16030
|
});
|
|
15521
16031
|
}
|
|
15522
16032
|
return statuses;
|
|
@@ -15536,6 +16046,7 @@ const make$62 = Effect.gen(function* () {
|
|
|
15536
16046
|
target: slot.target,
|
|
15537
16047
|
key: slot.key
|
|
15538
16048
|
})).pipe(Effect.ignore);
|
|
16049
|
+
yield* secrets.remove(mcpOAuthStorageName(name)).pipe(Effect.ignore);
|
|
15539
16050
|
}).pipe(Effect.provide(services), Effect.catchCause((cause) => Effect.logWarning("mcp registry remove failed", { cause }).pipe(Effect.asVoid)));
|
|
15540
16051
|
const setSecret = ({ name, slot, value }) => secrets.set(mcpSecretStorageName({
|
|
15541
16052
|
server: name,
|
|
@@ -15565,6 +16076,11 @@ const make$62 = Effect.gen(function* () {
|
|
|
15565
16076
|
else env[slot.key] = value.value;
|
|
15566
16077
|
}
|
|
15567
16078
|
if (!complete) continue;
|
|
16079
|
+
const hasExplicitAuthorization = Object.keys(headers).some((key) => key.toLowerCase() === "authorization");
|
|
16080
|
+
if (registration.transport !== "stdio" && !hasExplicitAuthorization) {
|
|
16081
|
+
const token = yield* oauth.accessTokenFor(registration);
|
|
16082
|
+
if (Option.isSome(token)) headers["Authorization"] = `Bearer ${token.value}`;
|
|
16083
|
+
}
|
|
15568
16084
|
resolved[registration.name] = registration.transport === "stdio" ? {
|
|
15569
16085
|
type: "stdio",
|
|
15570
16086
|
command: registration.command,
|
|
@@ -17297,7 +17813,7 @@ const make$59 = Effect.gen(function* () {
|
|
|
17297
17813
|
const layer$54 = Layer.effect(AssetSync, make$59);
|
|
17298
17814
|
//#endregion
|
|
17299
17815
|
//#region src/cli/assets.ts
|
|
17300
|
-
const SyncServicesLive = layer$54.pipe(Layer.provideMerge(layer$55), Layer.provideMerge(layer$56), Layer.provideMerge(layer$
|
|
17816
|
+
const SyncServicesLive = layer$54.pipe(Layer.provideMerge(layer$55), Layer.provideMerge(layer$56), Layer.provideMerge(layer$59.pipe(Layer.provide(layer$66))), Layer.provideMerge(layer$66), Layer.provideMerge(FetchHttpClient.layer));
|
|
17301
17817
|
const runWithSync = (flags, run) => Effect.gen(function* () {
|
|
17302
17818
|
const config = yield* resolveCliAuthConfig(flags, yield* GlobalFlag.LogLevel);
|
|
17303
17819
|
return yield* Effect.gen(function* () {
|
|
@@ -17305,7 +17821,7 @@ const runWithSync = (flags, run) => Effect.gen(function* () {
|
|
|
17305
17821
|
sync: yield* AssetSync,
|
|
17306
17822
|
link: yield* HubLink
|
|
17307
17823
|
});
|
|
17308
|
-
}).pipe(Effect.provide(SyncServicesLive.pipe(Layer.provide(layer$
|
|
17824
|
+
}).pipe(Effect.provide(SyncServicesLive.pipe(Layer.provide(layer$69(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, "Error")))));
|
|
17309
17825
|
});
|
|
17310
17826
|
const STATE_LABEL = {
|
|
17311
17827
|
synced: "synced",
|
|
@@ -27891,7 +28407,7 @@ const runProjectMutation = Effect.fn("runProjectMutation")(function* (flags, run
|
|
|
27891
28407
|
});
|
|
27892
28408
|
yield* Console.log(output);
|
|
27893
28409
|
}));
|
|
27894
|
-
const offlineRuntimeLayer = ProjectCliRuntimeLive.pipe(Layer.provide(layer$
|
|
28410
|
+
const offlineRuntimeLayer = ProjectCliRuntimeLive.pipe(Layer.provide(layer$69(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)));
|
|
27895
28411
|
return yield* Effect.gen(function* () {
|
|
27896
28412
|
const snapshot = yield* getOfflineSnapshot();
|
|
27897
28413
|
const orchestrationEngine = yield* OrchestrationEngineService;
|
|
@@ -27902,7 +28418,7 @@ const runProjectMutation = Effect.fn("runProjectMutation")(function* (flags, run
|
|
|
27902
28418
|
});
|
|
27903
28419
|
yield* Console.log(output);
|
|
27904
28420
|
}).pipe(Effect.provide(offlineRuntimeLayer));
|
|
27905
|
-
}).pipe(Effect.provide(Layer.mergeAll(runtimeLayer, layer$43).pipe(Layer.provideMerge(FetchHttpClient.layer), Layer.provide(layer$
|
|
28421
|
+
}).pipe(Effect.provide(Layer.mergeAll(runtimeLayer, layer$43).pipe(Layer.provideMerge(FetchHttpClient.layer), Layer.provide(layer$69(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)))));
|
|
27906
28422
|
});
|
|
27907
28423
|
const projectAddCommand = Command.make("add", {
|
|
27908
28424
|
...projectLocationFlags,
|
|
@@ -29222,6 +29738,40 @@ const otlpTracesProxyRouteLayer = HttpRouter.add("POST", OTLP_TRACES_PROXY_PATH,
|
|
|
29222
29738
|
EnvironmentInternalError: HttpServerRespondable.toResponse,
|
|
29223
29739
|
EnvironmentScopeRequiredError: HttpServerRespondable.toResponse
|
|
29224
29740
|
})));
|
|
29741
|
+
const escapeHtml = (input) => input.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """);
|
|
29742
|
+
const oauthResultPage = (input) => HttpServerResponse.text([
|
|
29743
|
+
"<!doctype html>",
|
|
29744
|
+
`<html><head><meta charset="utf-8"><title>${escapeHtml(input.title)}</title></head>`,
|
|
29745
|
+
"<body style=\"font-family: system-ui, sans-serif; max-width: 32rem; margin: 4rem auto;\">",
|
|
29746
|
+
`<h1 style="font-size: 1.25rem;">${escapeHtml(input.title)}</h1>`,
|
|
29747
|
+
`<p>${escapeHtml(input.detail)}</p>`,
|
|
29748
|
+
"</body></html>"
|
|
29749
|
+
].join(""), { contentType: "text/html; charset=utf-8" });
|
|
29750
|
+
/**
|
|
29751
|
+
* Where the browser lands after an MCP server sign-in.
|
|
29752
|
+
*
|
|
29753
|
+
* Deliberately unauthenticated: the redirect comes from an authorization
|
|
29754
|
+
* server that holds no p4code credential. The `state` value — minted by
|
|
29755
|
+
* `McpOAuth.begin`, single-use, expiring — is what ties the request to a flow
|
|
29756
|
+
* this server started; a request without a live one changes nothing.
|
|
29757
|
+
*/
|
|
29758
|
+
const mcpOAuthCallbackRouteLayer = HttpRouter.add("GET", OAUTH_CALLBACK_PATH, Effect.gen(function* () {
|
|
29759
|
+
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
29760
|
+
const url = new URL(request.url, "http://localhost.invalid");
|
|
29761
|
+
const outcome = yield* (yield* McpOAuth).handleCallback({
|
|
29762
|
+
state: url.searchParams.get("state") ?? void 0,
|
|
29763
|
+
code: url.searchParams.get("code") ?? void 0,
|
|
29764
|
+
error: url.searchParams.get("error") ?? void 0,
|
|
29765
|
+
errorDescription: url.searchParams.get("error_description") ?? void 0
|
|
29766
|
+
});
|
|
29767
|
+
return outcome.ok ? oauthResultPage({
|
|
29768
|
+
title: "Signed in",
|
|
29769
|
+
detail: `${outcome.server} is connected. You can close this tab and return to p4code.`
|
|
29770
|
+
}) : oauthResultPage({
|
|
29771
|
+
title: "Sign-in failed",
|
|
29772
|
+
detail: outcome.detail
|
|
29773
|
+
});
|
|
29774
|
+
}));
|
|
29225
29775
|
const assetRouteLayer = HttpRouter.add("GET", `${ASSET_ROUTE_PREFIX}/*`, Effect.gen(function* () {
|
|
29226
29776
|
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
29227
29777
|
const url = HttpServerRequest.toURL(request);
|
|
@@ -33376,15 +33926,15 @@ const toTaskStoreError = (operation) => (cause) => Effect.logWarning("task store
|
|
|
33376
33926
|
*/
|
|
33377
33927
|
const buildAutoPrompt = (task) => {
|
|
33378
33928
|
const reference = task.readableId ?? task.taskId;
|
|
33379
|
-
return `
|
|
33929
|
+
return `Picking up board task \`${reference}\`: ${task.title}
|
|
33380
33930
|
|
|
33381
|
-
|
|
33931
|
+
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
33932
|
|
|
33383
|
-
This thread is already linked to the task,
|
|
33933
|
+
This thread is already linked to the task, status In progress. Do the work.
|
|
33384
33934
|
|
|
33385
|
-
Record
|
|
33935
|
+
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
33936
|
|
|
33387
|
-
After reading the task, read the code it concerns. If it
|
|
33937
|
+
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
33938
|
};
|
|
33389
33939
|
/**
|
|
33390
33940
|
* Render a task into the prompt that seeds its thread.
|
|
@@ -43408,6 +43958,8 @@ const RPC_REQUIRED_SCOPE = /* @__PURE__ */ new Map([
|
|
|
43408
43958
|
[WS_METHODS.mcpSave, AuthOrchestrationOperateScope],
|
|
43409
43959
|
[WS_METHODS.mcpRemove, AuthOrchestrationOperateScope],
|
|
43410
43960
|
[WS_METHODS.mcpSetSecret, AuthOrchestrationOperateScope],
|
|
43961
|
+
[WS_METHODS.mcpOAuthBegin, AuthOrchestrationOperateScope],
|
|
43962
|
+
[WS_METHODS.mcpOAuthDisconnect, AuthOrchestrationOperateScope],
|
|
43411
43963
|
[WS_METHODS.serverGetSettings, AuthOrchestrationReadScope],
|
|
43412
43964
|
[WS_METHODS.serverUpdateSettings, AuthOrchestrationOperateScope],
|
|
43413
43965
|
[WS_METHODS.serverDiscoverSourceControl, AuthOrchestrationReadScope],
|
|
@@ -43521,6 +44073,7 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
|
|
|
43521
44073
|
const hubLink = yield* HubLink;
|
|
43522
44074
|
const assetSync = yield* AssetSync;
|
|
43523
44075
|
const mcpRegistry = yield* McpRegistry;
|
|
44076
|
+
const mcpOAuth = yield* McpOAuth;
|
|
43524
44077
|
const skillRegistry = yield* SkillRegistry;
|
|
43525
44078
|
const startup = yield* ServerRuntimeStartup;
|
|
43526
44079
|
const workspaceEntries = yield* WorkspaceEntries;
|
|
@@ -44122,6 +44675,13 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
|
|
|
44122
44675
|
slot,
|
|
44123
44676
|
value
|
|
44124
44677
|
}).pipe(Effect.andThen(mcpRegistry.list.pipe(Effect.map((servers) => ({ servers }))))), { "rpc.aggregate": "mcp" }),
|
|
44678
|
+
[WS_METHODS.mcpOAuthBegin]: ({ name }) => observeRpcEffect$1(WS_METHODS.mcpOAuthBegin, Effect.gen(function* () {
|
|
44679
|
+
const found = (yield* mcpRegistry.list).find((status) => status.registration.name === name);
|
|
44680
|
+
if (found === void 0) return yield* new McpRegistryError({ detail: `No server is named ${name}.` });
|
|
44681
|
+
if (found.registration.transport === "stdio") return yield* new McpRegistryError({ detail: "A stdio server is launched locally and has no sign-in." });
|
|
44682
|
+
return yield* mcpOAuth.begin(found.registration);
|
|
44683
|
+
}), { "rpc.aggregate": "mcp" }),
|
|
44684
|
+
[WS_METHODS.mcpOAuthDisconnect]: ({ name }) => observeRpcEffect$1(WS_METHODS.mcpOAuthDisconnect, mcpOAuth.disconnect(name).pipe(Effect.andThen(mcpRegistry.list.pipe(Effect.map((servers) => ({ servers }))))), { "rpc.aggregate": "mcp" }),
|
|
44125
44685
|
[WS_METHODS.serverGetSettings]: (_input) => observeRpcEffect$1(WS_METHODS.serverGetSettings, serverSettings.getSettings.pipe(Effect.map(redactServerSettingsForClient)), { "rpc.aggregate": "server" }),
|
|
44126
44686
|
[WS_METHODS.serverUpdateSettings]: ({ patch }) => observeRpcEffect$1(WS_METHODS.serverUpdateSettings, serverSettings.updateSettings(patch).pipe(Effect.map(redactServerSettingsForClient)), { "rpc.aggregate": "server" }),
|
|
44127
44687
|
[WS_METHODS.serverDiscoverSourceControl]: (_input) => observeRpcEffect$1(WS_METHODS.serverDiscoverSourceControl, sourceControlDiscovery.discover, { "rpc.aggregate": "server" }),
|
|
@@ -46619,7 +47179,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () {
|
|
|
46619
47179
|
};
|
|
46620
47180
|
});
|
|
46621
47181
|
var OpenCodeRuntime = class extends Context.Service()("@p4code/cli/provider/opencodeRuntime") {};
|
|
46622
|
-
const OpenCodeRuntimeLive = Layer.effect(OpenCodeRuntime, makeOpenCodeRuntime).pipe(Layer.provide(layer$
|
|
47182
|
+
const OpenCodeRuntimeLive = Layer.effect(OpenCodeRuntime, makeOpenCodeRuntime).pipe(Layer.provide(layer$70));
|
|
46623
47183
|
//#endregion
|
|
46624
47184
|
//#region src/textGeneration/TextGenerationUtils.ts
|
|
46625
47185
|
const isTextGenerationError$2 = Schema$1.is(TextGenerationError);
|
|
@@ -85904,9 +86464,9 @@ const WorkspaceEntriesLayerLive = layer$25.pipe(Layer.provide(layer$43));
|
|
|
85904
86464
|
const WorkspaceFileSystemLayerLive = layer$24.pipe(Layer.provide(layer$43), Layer.provide(WorkspaceEntriesLayerLive));
|
|
85905
86465
|
const WorkspaceLayerLive = Layer.mergeAll(layer$43, WorkspaceEntriesLayerLive, WorkspaceFileSystemLayerLive);
|
|
85906
86466
|
const ProjectFaviconResolverLayerLive = layer$41.pipe(Layer.provide(layer$43), Layer.provide(layer$42));
|
|
85907
|
-
const AuthLayerLive = layer$
|
|
86467
|
+
const AuthLayerLive = layer$62.pipe(Layer.provideMerge(PersistenceLayerLive), Layer.provide(layer$66));
|
|
85908
86468
|
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$
|
|
86469
|
+
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$59.pipe(Layer.provide(layer$66))), 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$66)).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$70));
|
|
85910
86470
|
/**
|
|
85911
86471
|
* Hub asset sync.
|
|
85912
86472
|
*
|
|
@@ -85916,7 +86476,7 @@ const RuntimeDependenciesLive = ReactorLayerLive.pipe(Layer.provideMerge(Checkpo
|
|
|
85916
86476
|
*/
|
|
85917
86477
|
const AssetSyncLive = layer$54.pipe(Layer.provideMerge(layer$55), Layer.provideMerge(layer$56));
|
|
85918
86478
|
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));
|
|
86479
|
+
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
86480
|
const makeServerLayer = Layer.unwrap(Effect.gen(function* () {
|
|
85921
86481
|
const config = yield* ServerConfig$1;
|
|
85922
86482
|
yield* fixPath();
|
|
@@ -86326,7 +86886,7 @@ const serviceCommand = Command.make("service").pipe(Command.withDescription("Man
|
|
|
86326
86886
|
]));
|
|
86327
86887
|
//#endregion
|
|
86328
86888
|
//#region src/bin.ts
|
|
86329
|
-
const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, layer$
|
|
86889
|
+
const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, layer$70, FetchHttpClient.layer);
|
|
86330
86890
|
/**
|
|
86331
86891
|
* Prints an update reminder before dispatching, at most once a day.
|
|
86332
86892
|
*
|