@p4code/cli 0.0.41 → 0.0.43
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
CHANGED
|
@@ -7,6 +7,7 @@ import * as Console from "effect/Console";
|
|
|
7
7
|
import * as Effect from "effect/Effect";
|
|
8
8
|
import * as Layer from "effect/Layer";
|
|
9
9
|
import { Argument, Command, Flag, GlobalFlag, Prompt } from "effect/unstable/cli";
|
|
10
|
+
import * as CliError from "effect/unstable/cli/CliError";
|
|
10
11
|
import { FetchHttpClient, Headers, HttpBody, HttpClient, HttpClientError, HttpClientRequest, HttpClientResponse, HttpRouter, HttpServer, HttpServerRequest, HttpServerRespondable, HttpServerResponse } from "effect/unstable/http";
|
|
11
12
|
import * as Context from "effect/Context";
|
|
12
13
|
import * as NodeOS from "node:os";
|
|
@@ -54,13 +55,11 @@ import * as Exit from "effect/Exit";
|
|
|
54
55
|
import * as SchemaGetter from "effect/SchemaGetter";
|
|
55
56
|
import * as Config from "effect/Config";
|
|
56
57
|
import * as NodeReadline from "node:readline";
|
|
57
|
-
import * as Scope from "effect/Scope";
|
|
58
|
-
import * as ChildProcess$1 from "effect/unstable/process/ChildProcess";
|
|
59
|
-
import * as ChildProcessSpawner$1 from "effect/unstable/process/ChildProcessSpawner";
|
|
60
58
|
import * as Cache from "effect/Cache";
|
|
61
59
|
import * as Deferred from "effect/Deferred";
|
|
62
60
|
import * as Equal from "effect/Equal";
|
|
63
61
|
import * as Semaphore from "effect/Semaphore";
|
|
62
|
+
import * as Scope from "effect/Scope";
|
|
64
63
|
import { parse } from "yaml";
|
|
65
64
|
import * as HttpApiClient from "effect/unstable/httpapi/HttpApiClient";
|
|
66
65
|
import * as Clock from "effect/Clock";
|
|
@@ -69,6 +68,8 @@ import * as Queue from "effect/Queue";
|
|
|
69
68
|
import { cast, dual } from "effect/Function";
|
|
70
69
|
import Mime from "@effect/platform-node/Mime";
|
|
71
70
|
import * as Arr from "effect/Array";
|
|
71
|
+
import * as ChildProcess$1 from "effect/unstable/process/ChildProcess";
|
|
72
|
+
import * as ChildProcessSpawner$1 from "effect/unstable/process/ChildProcessSpawner";
|
|
72
73
|
import * as Schedule from "effect/Schedule";
|
|
73
74
|
import * as HttpClient$1 from "effect/unstable/http/HttpClient";
|
|
74
75
|
import * as HttpClientRequest$1 from "effect/unstable/http/HttpClientRequest";
|
|
@@ -120,7 +121,7 @@ const closeServer = (server) => {
|
|
|
120
121
|
* NetService - Service tag for startup networking helpers.
|
|
121
122
|
*/
|
|
122
123
|
var NetService = class extends Context.Service()("@p4code/shared/Net/NetService") {};
|
|
123
|
-
const make$
|
|
124
|
+
const make$71 = () => {
|
|
124
125
|
/**
|
|
125
126
|
* Returns true when a TCP server can bind to {host, port}.
|
|
126
127
|
* `EADDRNOTAVAIL` is treated as available so IPv6-absent hosts don't fail
|
|
@@ -229,10 +230,10 @@ const make$70 = () => {
|
|
|
229
230
|
})
|
|
230
231
|
};
|
|
231
232
|
};
|
|
232
|
-
const layer$
|
|
233
|
+
const layer$67 = Layer.sync(NetService, make$71);
|
|
233
234
|
//#endregion
|
|
234
235
|
//#region package.json
|
|
235
|
-
var version = "0.0.
|
|
236
|
+
var version = "0.0.43";
|
|
236
237
|
//#endregion
|
|
237
238
|
//#region src/config.ts
|
|
238
239
|
/**
|
|
@@ -253,8 +254,8 @@ var ServerConfig$1 = class extends Context.Service()("@p4code/cli/config/ServerC
|
|
|
253
254
|
/** @deprecated Import and use `layerTest` from this module. */
|
|
254
255
|
static layerTest = (cwd, baseDirOrPrefix) => layerTest$2(cwd, baseDirOrPrefix);
|
|
255
256
|
};
|
|
256
|
-
const make$
|
|
257
|
-
const layer$
|
|
257
|
+
const make$70 = (config) => ServerConfig$1.of(config);
|
|
258
|
+
const layer$66 = (config) => Layer.succeed(ServerConfig$1, make$70(config));
|
|
258
259
|
const deriveServerPaths = Effect.fn(function* (baseDir, devUrl, options = {}) {
|
|
259
260
|
const { join } = yield* Path.Path;
|
|
260
261
|
const stateDir = join(baseDir, devUrl !== void 0 && !options.baseDirIsExplicit ? "dev" : "userdata");
|
|
@@ -922,7 +923,7 @@ const resolveLatestVersion = Effect.fn("cli.updateCheck.resolve")(function* (sta
|
|
|
922
923
|
* `service update` restarts it onto the new one.
|
|
923
924
|
*/
|
|
924
925
|
function formatUpdateReminder(input) {
|
|
925
|
-
return [`Update available: ${PACKAGE_NAME}@${input.latestVersion} (running ${input.currentVersion}).`, ` npm i -g ${PACKAGE_NAME}@latest &&
|
|
926
|
+
return [`Update available: ${PACKAGE_NAME}@${input.latestVersion} (running ${input.currentVersion}).`, ` npm i -g ${PACKAGE_NAME}@latest && p4c service update`].join("\n");
|
|
926
927
|
}
|
|
927
928
|
/**
|
|
928
929
|
* The reminder line, or null when there is nothing to say. Callers print it;
|
|
@@ -6177,7 +6178,7 @@ var ServerProviderUpdateError = class extends Schema$1.TaggedErrorClass()("Serve
|
|
|
6177
6178
|
}
|
|
6178
6179
|
};
|
|
6179
6180
|
const ServerSelfUpdateInput = Schema$1.Struct({
|
|
6180
|
-
/** Exact npm version of the `
|
|
6181
|
+
/** Exact npm version of the `p4c` package to install (never a dist-tag, so
|
|
6181
6182
|
the server and the acknowledging client agree on what was requested). */
|
|
6182
6183
|
targetVersion: TrimmedNonEmptyString });
|
|
6183
6184
|
/** Acknowledgement that the update artifact is installed and the server is
|
|
@@ -7312,10 +7313,26 @@ const AGENT_ASSET_TOTAL_MAX_BYTES = 4 * 1024 * 1024;
|
|
|
7312
7313
|
* What kind of thing this is.
|
|
7313
7314
|
*
|
|
7314
7315
|
* A literal union rather than free text because the kind decides where the
|
|
7315
|
-
* asset lands on disk, and an unknown kind has no answer to that.
|
|
7316
|
-
*
|
|
7316
|
+
* asset lands on disk, and an unknown kind has no answer to that. Each kind
|
|
7317
|
+
* needs a materializer, which is what stops this from being a place to add a
|
|
7318
|
+
* string.
|
|
7319
|
+
*
|
|
7320
|
+
* - `skill` — a directory under `<configDir>/skills`, named by the asset.
|
|
7321
|
+
* - `memory` — a single file directly in `<configDir>`, named by the asset.
|
|
7322
|
+
* The name is the filename, and only the allowlisted ones exist: that
|
|
7323
|
+
* directory also holds `settings.json` and `.credentials.json`, so "write the
|
|
7324
|
+
* file this asset names" has to be bounded by something narrower than the
|
|
7325
|
+
* path schema.
|
|
7326
|
+
* - `mcp` — an external MCP server registration, in p4code's own state
|
|
7327
|
+
* directory rather than the provider's. The definition travels; the bearer
|
|
7328
|
+
* tokens and API keys it references do not, and stay in each machine's own
|
|
7329
|
+
* secret store.
|
|
7317
7330
|
*/
|
|
7318
|
-
const AgentAssetKind = Schema$1.Literals([
|
|
7331
|
+
const AgentAssetKind = Schema$1.Literals([
|
|
7332
|
+
"skill",
|
|
7333
|
+
"memory",
|
|
7334
|
+
"mcp"
|
|
7335
|
+
]);
|
|
7319
7336
|
/**
|
|
7320
7337
|
* An asset's name, which for a skill is also its directory name.
|
|
7321
7338
|
*
|
|
@@ -7331,6 +7348,18 @@ const AgentAssetName = TrimmedNonEmptyString.check(Schema$1.isMaxLength(AGENT_AS
|
|
|
7331
7348
|
description: "Letters, digits, dot, dash and underscore; must not start with a separator."
|
|
7332
7349
|
}));
|
|
7333
7350
|
/**
|
|
7351
|
+
* Which asset, across every kind.
|
|
7352
|
+
*
|
|
7353
|
+
* Kind and name together, because name alone stopped being unique the moment a
|
|
7354
|
+
* second kind existed: `AgentAssetName` permits dots, so a skill directory may
|
|
7355
|
+
* legitimately be called `CLAUDE.md`. A report saying only "published
|
|
7356
|
+
* CLAUDE.md" would be ambiguous about what had just been sent where.
|
|
7357
|
+
*/
|
|
7358
|
+
const AgentAssetRef = Schema$1.Struct({
|
|
7359
|
+
kind: AgentAssetKind,
|
|
7360
|
+
name: AgentAssetName
|
|
7361
|
+
});
|
|
7362
|
+
/**
|
|
7334
7363
|
* A path inside the asset, relative to the asset's own directory.
|
|
7335
7364
|
*
|
|
7336
7365
|
* Same reasoning as the name and the same threat, in a more tempting form:
|
|
@@ -7428,56 +7457,77 @@ const HubLinkSource = Schema$1.Literals([
|
|
|
7428
7457
|
"none"
|
|
7429
7458
|
]);
|
|
7430
7459
|
/**
|
|
7431
|
-
* What
|
|
7460
|
+
* What an asset's three views add up to.
|
|
7432
7461
|
*
|
|
7433
7462
|
* `pending` means the next sync will act; `local-edit` means it will publish
|
|
7434
7463
|
* this machine's copy; `conflict` means it will do nothing and wants a person.
|
|
7435
7464
|
* `unmanaged` is the important one — p4code did not write it, so it is neither
|
|
7436
7465
|
* uploaded nor written over.
|
|
7466
|
+
*
|
|
7467
|
+
* `hub-only` is the one that has no local half: the hub holds it and this
|
|
7468
|
+
* machine does not materialize it. That is normal for a memory file belonging
|
|
7469
|
+
* to a provider this machine does not run, and it is the state that makes
|
|
7470
|
+
* hub-side editing worth having — otherwise the only way to reach such an asset
|
|
7471
|
+
* would be to install the provider first.
|
|
7437
7472
|
*/
|
|
7438
|
-
const
|
|
7473
|
+
const AgentSyncEntryState = Schema$1.Literals([
|
|
7439
7474
|
"synced",
|
|
7440
7475
|
"pending",
|
|
7441
7476
|
"local-edit",
|
|
7442
7477
|
"conflict",
|
|
7443
|
-
"unmanaged"
|
|
7478
|
+
"unmanaged",
|
|
7479
|
+
"hub-only"
|
|
7444
7480
|
]);
|
|
7445
|
-
const
|
|
7481
|
+
const AgentSyncEntry = Schema$1.Struct({
|
|
7482
|
+
kind: AgentAssetKind,
|
|
7446
7483
|
name: Schema$1.String,
|
|
7447
|
-
state:
|
|
7484
|
+
state: AgentSyncEntryState,
|
|
7448
7485
|
version: Schema$1.NullOr(Schema$1.Int),
|
|
7449
7486
|
/** Conflict reason or pending action, for a caller that wants to explain. */
|
|
7450
7487
|
detail: Schema$1.NullOr(Schema$1.String)
|
|
7451
7488
|
});
|
|
7452
|
-
const
|
|
7489
|
+
const AgentSyncRefusal = Schema$1.Struct({
|
|
7490
|
+
kind: AgentAssetKind,
|
|
7453
7491
|
name: Schema$1.String,
|
|
7454
7492
|
reason: Schema$1.String
|
|
7455
7493
|
});
|
|
7456
|
-
const
|
|
7494
|
+
const AgentSyncConflictReport = Schema$1.Struct({
|
|
7495
|
+
kind: AgentAssetKind,
|
|
7457
7496
|
name: Schema$1.String,
|
|
7458
7497
|
reason: Schema$1.String
|
|
7459
7498
|
});
|
|
7460
|
-
|
|
7499
|
+
/**
|
|
7500
|
+
* Where one kind materializes on this machine.
|
|
7501
|
+
*
|
|
7502
|
+
* A list rather than a field per kind, so adding the third kind is data rather
|
|
7503
|
+
* than another optional string every reader has to learn about. Null when the
|
|
7504
|
+
* root cannot be resolved — a provider that is not configured here.
|
|
7505
|
+
*/
|
|
7506
|
+
const AgentSyncRoot = Schema$1.Struct({
|
|
7507
|
+
kind: AgentAssetKind,
|
|
7508
|
+
path: Schema$1.NullOr(Schema$1.String)
|
|
7509
|
+
});
|
|
7510
|
+
const AgentSyncStatus = Schema$1.Struct({
|
|
7461
7511
|
configured: Schema$1.Boolean,
|
|
7462
7512
|
hubUrl: Schema$1.NullOr(Schema$1.String),
|
|
7463
7513
|
syncMode: HubSyncMode,
|
|
7464
7514
|
shareMode: HubShareMode,
|
|
7465
7515
|
source: HubLinkSource,
|
|
7466
|
-
|
|
7467
|
-
entries: Schema$1.Array(
|
|
7468
|
-
refused: Schema$1.Array(
|
|
7516
|
+
roots: Schema$1.Array(AgentSyncRoot),
|
|
7517
|
+
entries: Schema$1.Array(AgentSyncEntry),
|
|
7518
|
+
refused: Schema$1.Array(AgentSyncRefusal),
|
|
7469
7519
|
/** Set when the hub could not be reached. Local state is still reported. */
|
|
7470
7520
|
unavailable: Schema$1.NullOr(Schema$1.String)
|
|
7471
7521
|
});
|
|
7472
|
-
const
|
|
7522
|
+
const AgentSyncReport = Schema$1.Struct({
|
|
7473
7523
|
ran: Schema$1.Boolean,
|
|
7474
|
-
|
|
7475
|
-
materialized: Schema$1.Array(
|
|
7476
|
-
published: Schema$1.Array(
|
|
7477
|
-
removed: Schema$1.Array(
|
|
7478
|
-
conflicts: Schema$1.Array(
|
|
7479
|
-
unmanaged: Schema$1.Array(
|
|
7480
|
-
refused: Schema$1.Array(
|
|
7524
|
+
roots: Schema$1.Array(AgentSyncRoot),
|
|
7525
|
+
materialized: Schema$1.Array(AgentAssetRef),
|
|
7526
|
+
published: Schema$1.Array(AgentAssetRef),
|
|
7527
|
+
removed: Schema$1.Array(AgentAssetRef),
|
|
7528
|
+
conflicts: Schema$1.Array(AgentSyncConflictReport),
|
|
7529
|
+
unmanaged: Schema$1.Array(AgentAssetRef),
|
|
7530
|
+
refused: Schema$1.Array(AgentSyncRefusal),
|
|
7481
7531
|
unavailable: Schema$1.NullOr(Schema$1.String)
|
|
7482
7532
|
});
|
|
7483
7533
|
const HubConnectInput = Schema$1.Struct({
|
|
@@ -7497,7 +7547,57 @@ const HubMintedToken = Schema$1.Struct({
|
|
|
7497
7547
|
token: Schema$1.String,
|
|
7498
7548
|
digest: Schema$1.String
|
|
7499
7549
|
});
|
|
7500
|
-
const
|
|
7550
|
+
const AgentAssetRefInput = AgentAssetRef;
|
|
7551
|
+
/**
|
|
7552
|
+
* One asset as the hub holds it, for editing.
|
|
7553
|
+
*
|
|
7554
|
+
* Answered as `found: false` rather than as an error, because "the hub does not
|
|
7555
|
+
* have this" is a normal answer to a panel that is showing a row another
|
|
7556
|
+
* machine may have deleted a moment ago. `version` is carried back out so the
|
|
7557
|
+
* save that follows can be a compare-and-set rather than a blind overwrite.
|
|
7558
|
+
*/
|
|
7559
|
+
const AgentAssetContent = Schema$1.Struct({
|
|
7560
|
+
found: Schema$1.Boolean,
|
|
7561
|
+
kind: AgentAssetKind,
|
|
7562
|
+
name: AgentAssetName,
|
|
7563
|
+
files: Schema$1.Array(AgentAssetFile),
|
|
7564
|
+
version: Schema$1.NullOr(Schema$1.Int),
|
|
7565
|
+
updatedAt: Schema$1.NullOr(IsoDateTime),
|
|
7566
|
+
/** Set when the hub could not be reached, so a caller can say why it is blank. */
|
|
7567
|
+
unavailable: Schema$1.NullOr(Schema$1.String)
|
|
7568
|
+
});
|
|
7569
|
+
/**
|
|
7570
|
+
* Edit an asset in the hub, rather than on disk and upward.
|
|
7571
|
+
*
|
|
7572
|
+
* The hub is the shared copy, so this is the edit that reaches every machine
|
|
7573
|
+
* including ones that are asleep — and the only way to reach an asset this
|
|
7574
|
+
* machine does not materialize. `expectedVersion` is required, not optional:
|
|
7575
|
+
* an unconditional overwrite of shared state is exactly the operation that
|
|
7576
|
+
* loses another machine's edit without telling anyone.
|
|
7577
|
+
*/
|
|
7578
|
+
const AgentAssetSaveInput = Schema$1.Struct({
|
|
7579
|
+
kind: AgentAssetKind,
|
|
7580
|
+
name: AgentAssetName,
|
|
7581
|
+
files: AgentAssetFiles,
|
|
7582
|
+
expectedVersion: PositiveInt
|
|
7583
|
+
});
|
|
7584
|
+
/**
|
|
7585
|
+
* Delete an asset from the hub and so from every machine that syncs it.
|
|
7586
|
+
*
|
|
7587
|
+
* Distinct from unpublishing, which only ends *this* machine's share and keeps
|
|
7588
|
+
* the local copy. This one is the destructive one, and it does not require the
|
|
7589
|
+
* asset to be managed here — that restriction is what made a stale row from a
|
|
7590
|
+
* decommissioned machine impossible to clear.
|
|
7591
|
+
*
|
|
7592
|
+
* `expectedVersion` is optional here alone: someone deleting a row they can see
|
|
7593
|
+
* in a list has usually never held a version for it, and refusing on that basis
|
|
7594
|
+
* would make the operation unreachable in exactly the case it exists for.
|
|
7595
|
+
*/
|
|
7596
|
+
const AgentAssetDeleteInput = Schema$1.Struct({
|
|
7597
|
+
kind: AgentAssetKind,
|
|
7598
|
+
name: AgentAssetName,
|
|
7599
|
+
expectedVersion: Schema$1.optional(PositiveInt)
|
|
7600
|
+
});
|
|
7501
7601
|
/**
|
|
7502
7602
|
* Changing the hub link failed.
|
|
7503
7603
|
*
|
|
@@ -7522,6 +7622,143 @@ const encoder$3 = new TextEncoder();
|
|
|
7522
7622
|
*/
|
|
7523
7623
|
const agentAssetTotalBytes = (files) => files.reduce((total, file) => total + encoder$3.encode(file.content).length + encoder$3.encode(file.path).length, 0);
|
|
7524
7624
|
//#endregion
|
|
7625
|
+
//#region ../../packages/contracts/src/mcpServer.ts
|
|
7626
|
+
/**
|
|
7627
|
+
* External MCP server registrations.
|
|
7628
|
+
*
|
|
7629
|
+
* The servers a person adds themselves — a ticket tracker, a search index, a
|
|
7630
|
+
* build system — as opposed to p4code's own toolkit, which the adapters inject
|
|
7631
|
+
* on every session and which is not registered anywhere.
|
|
7632
|
+
*
|
|
7633
|
+
* **These are p4code's own data, not the provider's.** The obvious place to put
|
|
7634
|
+
* them is `~/.claude.json` under `mcpServers`, which is where the Claude CLI
|
|
7635
|
+
* keeps its own. p4code does not write that file: it also holds `oauthAccount`,
|
|
7636
|
+
* `machineID` and a `projects` map of per-path trust decisions, so owning one
|
|
7637
|
+
* key inside it means round-tripping all of that. The SDK takes `mcpServers` as
|
|
7638
|
+
* a session option and the adapter already passes it, so a registration p4code
|
|
7639
|
+
* stores itself reaches the agent by the same door its own toolkit uses —
|
|
7640
|
+
* without touching the provider's file at all.
|
|
7641
|
+
*
|
|
7642
|
+
* **Secret values are not in here, and that is the point.** A registration
|
|
7643
|
+
* routinely needs a bearer token in a header or an API key in the environment.
|
|
7644
|
+
* Those are named here and stored per machine in `ServerSecretStore`; the
|
|
7645
|
+
* registration itself carries only the name of the slot. That is what lets the
|
|
7646
|
+
* whole registration sync through the hub — which stores asset files verbatim —
|
|
7647
|
+
* without a working credential ever leaving the machine that holds it.
|
|
7648
|
+
*
|
|
7649
|
+
* @module contracts/mcpServer
|
|
7650
|
+
*/
|
|
7651
|
+
/**
|
|
7652
|
+
* A server's name, reused from asset names rather than defined again.
|
|
7653
|
+
*
|
|
7654
|
+
* It is the key in the SDK's `mcpServers` record *and* a path segment in
|
|
7655
|
+
* p4code's own store, so it needs exactly the constraint an asset name already
|
|
7656
|
+
* has. Two definitions of the same rule is how one of them ends up laxer.
|
|
7657
|
+
*/
|
|
7658
|
+
const McpServerName = AgentAssetName;
|
|
7659
|
+
/**
|
|
7660
|
+
* Where a secret goes when it is resolved.
|
|
7661
|
+
*
|
|
7662
|
+
* The slot, not the value. `header` fills one entry of an http server's
|
|
7663
|
+
* `headers`; `env` fills one entry of a stdio server's environment.
|
|
7664
|
+
*/
|
|
7665
|
+
const McpSecretTarget = Schema$1.Literals(["header", "env"]);
|
|
7666
|
+
/**
|
|
7667
|
+
* One secret this server needs, named but not carried.
|
|
7668
|
+
*
|
|
7669
|
+
* There is no `secretName` field: the storage name is derived from the server,
|
|
7670
|
+
* the target and the key. A caller-supplied name would be one more thing to get
|
|
7671
|
+
* wrong, and two servers could name the same slot differently or — worse — the
|
|
7672
|
+
* same name for different slots.
|
|
7673
|
+
*/
|
|
7674
|
+
const McpSecretSlot = Schema$1.Struct({
|
|
7675
|
+
target: McpSecretTarget,
|
|
7676
|
+
key: TrimmedNonEmptyString
|
|
7677
|
+
});
|
|
7678
|
+
/**
|
|
7679
|
+
* The storage name for one slot's value.
|
|
7680
|
+
*
|
|
7681
|
+
* Derived in the contract so both ends agree without either importing the
|
|
7682
|
+
* other's helper: the server resolving a session and the panel writing a value
|
|
7683
|
+
* have to compute the same string or the value is written where nothing looks.
|
|
7684
|
+
*/
|
|
7685
|
+
const mcpSecretStorageName = (input) => `mcp.${input.server}.${input.target}.${input.key}`;
|
|
7686
|
+
const commonFields = {
|
|
7687
|
+
name: McpServerName,
|
|
7688
|
+
/**
|
|
7689
|
+
* Off means registered and not injected.
|
|
7690
|
+
*
|
|
7691
|
+
* Kept rather than deleted because turning a server off to test something is
|
|
7692
|
+
* the common case, and re-entering a command line and four headers to turn it
|
|
7693
|
+
* back on is how people stop turning it off.
|
|
7694
|
+
*/
|
|
7695
|
+
enabled: Schema$1.Boolean,
|
|
7696
|
+
secrets: Schema$1.optional(Schema$1.Array(McpSecretSlot))
|
|
7697
|
+
};
|
|
7698
|
+
/**
|
|
7699
|
+
* A server p4code launches as a child process.
|
|
7700
|
+
*
|
|
7701
|
+
* `env` holds only values that are not secret. Anything that is goes in
|
|
7702
|
+
* `secrets` and is resolved from this machine's own store at session start.
|
|
7703
|
+
*/
|
|
7704
|
+
const McpStdioServer = Schema$1.Struct({
|
|
7705
|
+
...commonFields,
|
|
7706
|
+
transport: Schema$1.Literal("stdio"),
|
|
7707
|
+
command: TrimmedNonEmptyString,
|
|
7708
|
+
args: Schema$1.optional(Schema$1.Array(Schema$1.String)),
|
|
7709
|
+
env: Schema$1.optional(Schema$1.Record(Schema$1.String, Schema$1.String))
|
|
7710
|
+
});
|
|
7711
|
+
/** A server reached over HTTP. `headers` holds only non-secret values. */
|
|
7712
|
+
const McpRemoteServer = Schema$1.Struct({
|
|
7713
|
+
...commonFields,
|
|
7714
|
+
transport: Schema$1.Literals(["http", "sse"]),
|
|
7715
|
+
url: TrimmedNonEmptyString,
|
|
7716
|
+
headers: Schema$1.optional(Schema$1.Record(Schema$1.String, Schema$1.String))
|
|
7717
|
+
});
|
|
7718
|
+
/**
|
|
7719
|
+
* A registration, as a union rather than one struct of optional fields.
|
|
7720
|
+
*
|
|
7721
|
+
* A stdio server with a `url` and no `command` is not a thing, and a struct
|
|
7722
|
+
* with both optional would let one be stored and then fail at session start —
|
|
7723
|
+
* on a machine that may not be the one where it was typed.
|
|
7724
|
+
*/
|
|
7725
|
+
const McpServerRegistration = Schema$1.Union([McpStdioServer, McpRemoteServer]);
|
|
7726
|
+
/**
|
|
7727
|
+
* What a panel needs to show, including what this machine cannot run.
|
|
7728
|
+
*
|
|
7729
|
+
* `missingSecrets` is the field that earns this type. A registration syncs
|
|
7730
|
+
* everywhere; its secrets do not. So a server can be perfectly valid and still
|
|
7731
|
+
* unusable *here*, and saying which slot is empty is the difference between a
|
|
7732
|
+
* fixable state and a mystery.
|
|
7733
|
+
*/
|
|
7734
|
+
const McpServerStatus = Schema$1.Struct({
|
|
7735
|
+
registration: McpServerRegistration,
|
|
7736
|
+
missingSecrets: Schema$1.Array(McpSecretSlot),
|
|
7737
|
+
/** True when this machine holds everything needed to start it. */
|
|
7738
|
+
ready: Schema$1.Boolean
|
|
7739
|
+
});
|
|
7740
|
+
const McpServerList = Schema$1.Struct({ servers: Schema$1.Array(McpServerStatus) });
|
|
7741
|
+
const McpServerSaveInput = Schema$1.Struct({ registration: McpServerRegistration });
|
|
7742
|
+
const McpServerNameInput = Schema$1.Struct({ name: McpServerName });
|
|
7743
|
+
/**
|
|
7744
|
+
* Setting one secret value, on this machine only.
|
|
7745
|
+
*
|
|
7746
|
+
* Separate from saving the registration because they go to different places and
|
|
7747
|
+
* only one of them travels. Bundling them would make the panel's save button
|
|
7748
|
+
* the thing that decides whether a credential syncs, which is not a decision to
|
|
7749
|
+
* leave to a button.
|
|
7750
|
+
*/
|
|
7751
|
+
const McpServerSecretInput = Schema$1.Struct({
|
|
7752
|
+
name: McpServerName,
|
|
7753
|
+
slot: McpSecretSlot,
|
|
7754
|
+
value: Schema$1.String
|
|
7755
|
+
});
|
|
7756
|
+
var McpRegistryError = class extends Schema$1.TaggedErrorClass()("McpRegistryError", { detail: Schema$1.String }) {
|
|
7757
|
+
get message() {
|
|
7758
|
+
return this.detail;
|
|
7759
|
+
}
|
|
7760
|
+
};
|
|
7761
|
+
//#endregion
|
|
7525
7762
|
//#region ../../packages/contracts/src/filesystem.ts
|
|
7526
7763
|
const FILESYSTEM_PATH_MAX_LENGTH = 512;
|
|
7527
7764
|
const FilesystemBrowseInput = Schema$1.Struct({
|
|
@@ -7777,6 +8014,13 @@ const WS_METHODS = {
|
|
|
7777
8014
|
skillsPublish: "skills.publish",
|
|
7778
8015
|
skillsPublishAll: "skills.publishAll",
|
|
7779
8016
|
skillsUnpublish: "skills.unpublish",
|
|
8017
|
+
assetsRead: "assets.read",
|
|
8018
|
+
assetsSave: "assets.save",
|
|
8019
|
+
assetsDelete: "assets.delete",
|
|
8020
|
+
mcpList: "mcp.list",
|
|
8021
|
+
mcpSave: "mcp.save",
|
|
8022
|
+
mcpRemove: "mcp.remove",
|
|
8023
|
+
mcpSetSecret: "mcp.setSecret",
|
|
7780
8024
|
sourceControlLookupRepository: "sourceControl.lookupRepository",
|
|
7781
8025
|
sourceControlCloneRepository: "sourceControl.cloneRepository",
|
|
7782
8026
|
sourceControlPublishRepository: "sourceControl.publishRepository",
|
|
@@ -7846,36 +8090,36 @@ const WsTasksDeleteRpc = Rpc.make(WS_METHODS.tasksDelete, {
|
|
|
7846
8090
|
const HubRpcError = Schema$1.Union([HubLinkError, EnvironmentAuthorizationError]);
|
|
7847
8091
|
const WsHubGetSyncStatusRpc = Rpc.make(WS_METHODS.hubGetSyncStatus, {
|
|
7848
8092
|
payload: Schema$1.Struct({}),
|
|
7849
|
-
success:
|
|
8093
|
+
success: AgentSyncStatus,
|
|
7850
8094
|
error: Schema$1.Union([EnvironmentAuthorizationError])
|
|
7851
8095
|
});
|
|
7852
8096
|
const WsHubConnectRpc = Rpc.make(WS_METHODS.hubConnect, {
|
|
7853
8097
|
payload: HubConnectInput,
|
|
7854
|
-
success:
|
|
8098
|
+
success: AgentSyncStatus,
|
|
7855
8099
|
error: HubRpcError
|
|
7856
8100
|
});
|
|
7857
8101
|
const WsHubDisconnectRpc = Rpc.make(WS_METHODS.hubDisconnect, {
|
|
7858
8102
|
payload: Schema$1.Struct({}),
|
|
7859
|
-
success:
|
|
8103
|
+
success: AgentSyncStatus,
|
|
7860
8104
|
error: HubRpcError
|
|
7861
8105
|
});
|
|
7862
8106
|
const WsHubSetSyncModeRpc = Rpc.make(WS_METHODS.hubSetSyncMode, {
|
|
7863
8107
|
payload: HubSyncModeInput,
|
|
7864
|
-
success:
|
|
8108
|
+
success: AgentSyncStatus,
|
|
7865
8109
|
error: HubRpcError
|
|
7866
8110
|
});
|
|
7867
8111
|
/**
|
|
7868
8112
|
* Mint a bearer token for a hub, without contacting one.
|
|
7869
8113
|
*
|
|
7870
8114
|
* A hub issues nothing — it only ever holds digests — so this is local
|
|
7871
|
-
* generation, the same thing `
|
|
8115
|
+
* generation, the same thing `p4c hub token add` does. It exists as an RPC
|
|
7872
8116
|
* rather than in the browser because a client reached over plain HTTP on a LAN
|
|
7873
8117
|
* has no `crypto.subtle`, and a token generator that silently degrades is not
|
|
7874
8118
|
* a thing to ship.
|
|
7875
8119
|
*/
|
|
7876
8120
|
const WsHubSetShareModeRpc = Rpc.make(WS_METHODS.hubSetShareMode, {
|
|
7877
8121
|
payload: HubShareModeInput,
|
|
7878
|
-
success:
|
|
8122
|
+
success: AgentSyncStatus,
|
|
7879
8123
|
error: HubRpcError
|
|
7880
8124
|
});
|
|
7881
8125
|
const WsHubMintTokenRpc = Rpc.make(WS_METHODS.hubMintToken, {
|
|
@@ -7886,36 +8130,99 @@ const WsHubMintTokenRpc = Rpc.make(WS_METHODS.hubMintToken, {
|
|
|
7886
8130
|
const WsSkillsSyncRpc = Rpc.make(WS_METHODS.skillsSync, {
|
|
7887
8131
|
payload: Schema$1.Struct({}),
|
|
7888
8132
|
success: Schema$1.Struct({
|
|
7889
|
-
report:
|
|
7890
|
-
status:
|
|
8133
|
+
report: AgentSyncReport,
|
|
8134
|
+
status: AgentSyncStatus
|
|
7891
8135
|
}),
|
|
7892
8136
|
error: Schema$1.Union([EnvironmentAuthorizationError])
|
|
7893
8137
|
});
|
|
7894
8138
|
const WsSkillsPublishRpc = Rpc.make(WS_METHODS.skillsPublish, {
|
|
7895
|
-
payload:
|
|
8139
|
+
payload: AgentAssetRefInput,
|
|
7896
8140
|
success: Schema$1.Struct({
|
|
7897
|
-
report:
|
|
7898
|
-
status:
|
|
8141
|
+
report: AgentSyncReport,
|
|
8142
|
+
status: AgentSyncStatus
|
|
7899
8143
|
}),
|
|
7900
8144
|
error: Schema$1.Union([EnvironmentAuthorizationError])
|
|
7901
8145
|
});
|
|
7902
8146
|
const WsSkillsPublishAllRpc = Rpc.make(WS_METHODS.skillsPublishAll, {
|
|
7903
8147
|
payload: Schema$1.Struct({}),
|
|
7904
8148
|
success: Schema$1.Struct({
|
|
7905
|
-
report:
|
|
7906
|
-
status:
|
|
8149
|
+
report: AgentSyncReport,
|
|
8150
|
+
status: AgentSyncStatus
|
|
7907
8151
|
}),
|
|
7908
8152
|
error: Schema$1.Union([EnvironmentAuthorizationError])
|
|
7909
8153
|
});
|
|
7910
8154
|
const WsSkillsUnpublishRpc = Rpc.make(WS_METHODS.skillsUnpublish, {
|
|
7911
|
-
payload:
|
|
8155
|
+
payload: AgentAssetRefInput,
|
|
7912
8156
|
success: Schema$1.Struct({
|
|
7913
|
-
report:
|
|
7914
|
-
status:
|
|
8157
|
+
report: AgentSyncReport,
|
|
8158
|
+
status: AgentSyncStatus
|
|
7915
8159
|
}),
|
|
7916
8160
|
error: Schema$1.Union([EnvironmentAuthorizationError])
|
|
7917
8161
|
});
|
|
7918
8162
|
/**
|
|
8163
|
+
* Reading, editing and deleting an asset in the hub itself.
|
|
8164
|
+
*
|
|
8165
|
+
* The hub is the shared copy, so these reach every machine — including ones
|
|
8166
|
+
* that are asleep, and including assets this machine does not materialize at
|
|
8167
|
+
* all. Without them the only way to change a shared asset was to edit the local
|
|
8168
|
+
* file and wait for it to propagate, which is no way at all when the asset
|
|
8169
|
+
* belongs to a provider this machine does not run or to a machine you no longer
|
|
8170
|
+
* have.
|
|
8171
|
+
*/
|
|
8172
|
+
const WsAssetsReadRpc = Rpc.make(WS_METHODS.assetsRead, {
|
|
8173
|
+
payload: AgentAssetRefInput,
|
|
8174
|
+
success: AgentAssetContent,
|
|
8175
|
+
error: Schema$1.Union([EnvironmentAuthorizationError])
|
|
8176
|
+
});
|
|
8177
|
+
const WsAssetsSaveRpc = Rpc.make(WS_METHODS.assetsSave, {
|
|
8178
|
+
payload: AgentAssetSaveInput,
|
|
8179
|
+
success: Schema$1.Struct({
|
|
8180
|
+
report: AgentSyncReport,
|
|
8181
|
+
status: AgentSyncStatus
|
|
8182
|
+
}),
|
|
8183
|
+
error: Schema$1.Union([EnvironmentAuthorizationError])
|
|
8184
|
+
});
|
|
8185
|
+
const WsAssetsDeleteRpc = Rpc.make(WS_METHODS.assetsDelete, {
|
|
8186
|
+
payload: AgentAssetDeleteInput,
|
|
8187
|
+
success: Schema$1.Struct({
|
|
8188
|
+
report: AgentSyncReport,
|
|
8189
|
+
status: AgentSyncStatus
|
|
8190
|
+
}),
|
|
8191
|
+
error: Schema$1.Union([EnvironmentAuthorizationError])
|
|
8192
|
+
});
|
|
8193
|
+
/**
|
|
8194
|
+
* Registering the user's own MCP servers.
|
|
8195
|
+
*
|
|
8196
|
+
* Every one of these answers with the whole list, for the same reason the sync
|
|
8197
|
+
* calls answer with the whole status: a panel that had to re-read after each
|
|
8198
|
+
* write would show a stale row in between.
|
|
8199
|
+
*
|
|
8200
|
+
* `setSecret` is deliberately its own call rather than a field on `save`. The
|
|
8201
|
+
* two go to different places — one to a file that syncs between machines, one
|
|
8202
|
+
* to a store that never leaves this one — and folding them together would make
|
|
8203
|
+
* a save button the thing that decides whether a credential travels.
|
|
8204
|
+
*/
|
|
8205
|
+
const WsMcpListRpc = Rpc.make(WS_METHODS.mcpList, {
|
|
8206
|
+
payload: Schema$1.Struct({}),
|
|
8207
|
+
success: McpServerList,
|
|
8208
|
+
error: Schema$1.Union([EnvironmentAuthorizationError])
|
|
8209
|
+
});
|
|
8210
|
+
const WsMcpSaveRpc = Rpc.make(WS_METHODS.mcpSave, {
|
|
8211
|
+
payload: McpServerSaveInput,
|
|
8212
|
+
success: McpServerList,
|
|
8213
|
+
error: Schema$1.Union([McpRegistryError, EnvironmentAuthorizationError])
|
|
8214
|
+
});
|
|
8215
|
+
const WsMcpRemoveRpc = Rpc.make(WS_METHODS.mcpRemove, {
|
|
8216
|
+
payload: McpServerNameInput,
|
|
8217
|
+
success: McpServerList,
|
|
8218
|
+
error: Schema$1.Union([McpRegistryError, EnvironmentAuthorizationError])
|
|
8219
|
+
});
|
|
8220
|
+
const WsMcpSetSecretRpc = Rpc.make(WS_METHODS.mcpSetSecret, {
|
|
8221
|
+
payload: McpServerSecretInput,
|
|
8222
|
+
success: McpServerList,
|
|
8223
|
+
error: Schema$1.Union([McpRegistryError, EnvironmentAuthorizationError])
|
|
8224
|
+
});
|
|
8225
|
+
/**
|
|
7919
8226
|
* The board, then every change to it, for as long as the client stays
|
|
7920
8227
|
* subscribed. The leading snapshot is what makes this sufficient on its own —
|
|
7921
8228
|
* a client does not list first, so there is no window between the two in which
|
|
@@ -8283,7 +8590,7 @@ const WsSubscribeAuthAccessRpc = Rpc.make(WS_METHODS.subscribeAuthAccess, {
|
|
|
8283
8590
|
error: Schema$1.Union([AuthAccessStreamError, EnvironmentAuthorizationError]),
|
|
8284
8591
|
stream: true
|
|
8285
8592
|
});
|
|
8286
|
-
const WsRpcGroup = RpcGroup.make(WsServerProbeRpc, WsServerGetConfigRpc, WsServerRefreshProvidersRpc, WsServerUpdateProviderRpc, WsServerUpdateServerRpc, WsServerUpsertKeybindingRpc, WsServerRemoveKeybindingRpc, WsServerGetSettingsRpc, WsServerUpdateSettingsRpc, WsServerDiscoverSourceControlRpc, 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, WsSubscribeTasksRpc, WsHubGetSyncStatusRpc, WsHubConnectRpc, WsHubDisconnectRpc, WsHubSetSyncModeRpc, WsHubSetShareModeRpc, WsHubMintTokenRpc, WsSkillsSyncRpc, WsSkillsPublishRpc, WsSkillsPublishAllRpc, WsSkillsUnpublishRpc, WsOrchestrationDispatchCommandRpc, WsOrchestrationGetTurnDiffRpc, WsOrchestrationGetFullThreadDiffRpc, WsOrchestrationGetArchivedShellSnapshotRpc, WsOrchestrationSubscribeShellRpc, WsOrchestrationSubscribeThreadRpc);
|
|
8593
|
+
const WsRpcGroup = RpcGroup.make(WsServerProbeRpc, WsServerGetConfigRpc, WsServerRefreshProvidersRpc, WsServerUpdateProviderRpc, WsServerUpdateServerRpc, WsServerUpsertKeybindingRpc, WsServerRemoveKeybindingRpc, WsServerGetSettingsRpc, WsServerUpdateSettingsRpc, WsServerDiscoverSourceControlRpc, 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, WsSubscribeTasksRpc, WsHubGetSyncStatusRpc, WsHubConnectRpc, WsHubDisconnectRpc, WsHubSetSyncModeRpc, WsHubSetShareModeRpc, WsHubMintTokenRpc, WsSkillsSyncRpc, WsSkillsPublishRpc, WsSkillsPublishAllRpc, WsSkillsUnpublishRpc, WsAssetsReadRpc, WsAssetsSaveRpc, WsAssetsDeleteRpc, WsMcpListRpc, WsMcpSaveRpc, WsMcpRemoveRpc, WsMcpSetSecretRpc, WsOrchestrationDispatchCommandRpc, WsOrchestrationGetTurnDiffRpc, WsOrchestrationGetFullThreadDiffRpc, WsOrchestrationGetArchivedShellSnapshotRpc, WsOrchestrationSubscribeShellRpc, WsOrchestrationSubscribeThreadRpc);
|
|
8287
8594
|
//#endregion
|
|
8288
8595
|
//#region ../../packages/shared/src/oauthScope.ts
|
|
8289
8596
|
const OAUTH_SCOPE_TOKEN = /^[\u0021\u0023-\u005b\u005d-\u007e]+$/u;
|
|
@@ -8429,7 +8736,7 @@ function deriveAuthClientMetadata(input) {
|
|
|
8429
8736
|
//#endregion
|
|
8430
8737
|
//#region src/auth/EnvironmentAuthPolicy.ts
|
|
8431
8738
|
var EnvironmentAuthPolicy = class extends Context.Service()("@p4code/cli/auth/EnvironmentAuthPolicy") {};
|
|
8432
|
-
const make$
|
|
8739
|
+
const make$69 = Effect.gen(function* () {
|
|
8433
8740
|
const config = yield* ServerConfig$1;
|
|
8434
8741
|
const isRemoteReachable = isRemoteReachableHost(config.host);
|
|
8435
8742
|
const policy = config.mode === "desktop" ? isRemoteReachable ? "remote-reachable" : "desktop-managed-local" : isRemoteReachable ? "remote-reachable" : "loopback-browser";
|
|
@@ -8447,7 +8754,7 @@ const make$68 = Effect.gen(function* () {
|
|
|
8447
8754
|
};
|
|
8448
8755
|
return EnvironmentAuthPolicy.of({ getDescriptor: () => Effect.succeed(descriptor).pipe(Effect.withSpan("EnvironmentAuthPolicy.getDescriptor")) });
|
|
8449
8756
|
});
|
|
8450
|
-
const layer$
|
|
8757
|
+
const layer$65 = Layer.effect(EnvironmentAuthPolicy, make$69);
|
|
8451
8758
|
//#endregion
|
|
8452
8759
|
//#region src/persistence/Errors.ts
|
|
8453
8760
|
function summarizeSchemaIssue(issue) {
|
|
@@ -8628,7 +8935,7 @@ function toPersistenceSqlOrDecodeError$6(sqlOperation, decodeOperation, correlat
|
|
|
8628
8935
|
cause
|
|
8629
8936
|
});
|
|
8630
8937
|
}
|
|
8631
|
-
const make$
|
|
8938
|
+
const make$68 = Effect.gen(function* () {
|
|
8632
8939
|
const sql = yield* SqlClient.SqlClient;
|
|
8633
8940
|
const createSessionRow = SqlSchema.void({
|
|
8634
8941
|
Request: CreateAuthSessionInput,
|
|
@@ -8762,7 +9069,7 @@ const make$67 = Effect.gen(function* () {
|
|
|
8762
9069
|
setLastConnectedAt
|
|
8763
9070
|
};
|
|
8764
9071
|
});
|
|
8765
|
-
const layer$
|
|
9072
|
+
const layer$64 = Layer.effect(AuthSessionRepository, make$68);
|
|
8766
9073
|
//#endregion
|
|
8767
9074
|
//#region src/auth/ServerSecretStore.ts
|
|
8768
9075
|
const secretStoreErrorContext = {
|
|
@@ -8829,7 +9136,7 @@ const isSecretStoreError = Schema$1.is(SecretStoreError);
|
|
|
8829
9136
|
const isPlatformError = (value) => Predicate.isTagged(value, "PlatformError");
|
|
8830
9137
|
const isSecretAlreadyExistsError = (error) => "cause" in error && isPlatformError(error.cause) && error.cause.reason._tag === "AlreadyExists";
|
|
8831
9138
|
var ServerSecretStore = class extends Context.Service()("@p4code/cli/auth/ServerSecretStore") {};
|
|
8832
|
-
const make$
|
|
9139
|
+
const make$67 = Effect.gen(function* () {
|
|
8833
9140
|
const crypto = yield* Crypto.Crypto;
|
|
8834
9141
|
const fileSystem = yield* FileSystem.FileSystem;
|
|
8835
9142
|
const path = yield* Path.Path;
|
|
@@ -8899,7 +9206,7 @@ const make$66 = Effect.gen(function* () {
|
|
|
8899
9206
|
remove
|
|
8900
9207
|
});
|
|
8901
9208
|
});
|
|
8902
|
-
const layer$
|
|
9209
|
+
const layer$63 = Layer.effect(ServerSecretStore, make$67);
|
|
8903
9210
|
//#endregion
|
|
8904
9211
|
//#region src/auth/SessionStore.ts
|
|
8905
9212
|
var MalformedSessionTokenError = class extends Schema$1.TaggedErrorClass()("MalformedSessionTokenError", {}) {
|
|
@@ -9137,7 +9444,7 @@ function toAuthClientSession(input) {
|
|
|
9137
9444
|
current: false
|
|
9138
9445
|
};
|
|
9139
9446
|
}
|
|
9140
|
-
const make$
|
|
9447
|
+
const make$66 = Effect.gen(function* () {
|
|
9141
9448
|
const crypto = yield* Crypto.Crypto;
|
|
9142
9449
|
const serverConfig = yield* ServerConfig$1;
|
|
9143
9450
|
const secretStore = yield* ServerSecretStore;
|
|
@@ -9451,7 +9758,7 @@ const make$65 = Effect.gen(function* () {
|
|
|
9451
9758
|
markDisconnected
|
|
9452
9759
|
});
|
|
9453
9760
|
});
|
|
9454
|
-
const layer$
|
|
9761
|
+
const layer$62 = Layer.effect(SessionStore, make$66).pipe(Layer.provideMerge(layer$64));
|
|
9455
9762
|
//#endregion
|
|
9456
9763
|
//#region src/persistence/AuthPairingLinks.ts
|
|
9457
9764
|
const AuthPairingLinkRecord = Schema$1.Struct({
|
|
@@ -9512,7 +9819,7 @@ function toPersistenceSqlOrDecodeError$5(sqlOperation, decodeOperation, correlat
|
|
|
9512
9819
|
cause
|
|
9513
9820
|
});
|
|
9514
9821
|
}
|
|
9515
|
-
const make$
|
|
9822
|
+
const make$65 = Effect.gen(function* () {
|
|
9516
9823
|
const sql = yield* SqlClient.SqlClient;
|
|
9517
9824
|
const createPairingLinkRow = SqlSchema.void({
|
|
9518
9825
|
Request: CreateAuthPairingLinkInput,
|
|
@@ -9647,7 +9954,7 @@ const make$64 = Effect.gen(function* () {
|
|
|
9647
9954
|
getByCredential
|
|
9648
9955
|
};
|
|
9649
9956
|
});
|
|
9650
|
-
const layer$
|
|
9957
|
+
const layer$61 = Layer.effect(AuthPairingLinkRepository, make$65);
|
|
9651
9958
|
//#endregion
|
|
9652
9959
|
//#region src/auth/PairingGrantStore.ts
|
|
9653
9960
|
var UnknownBootstrapCredentialError = class extends Schema$1.TaggedErrorClass()("UnknownBootstrapCredentialError", {}) {
|
|
@@ -9742,7 +10049,7 @@ const DEV_STARTUP_TTL_HOURS = Duration.hours(24);
|
|
|
9742
10049
|
const PAIRING_TOKEN_ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
|
|
9743
10050
|
const PAIRING_TOKEN_LENGTH = 12;
|
|
9744
10051
|
const PAIRING_TOKEN_REJECTION_LIMIT = Math.floor(256 / 32) * 32;
|
|
9745
|
-
const make$
|
|
10052
|
+
const make$64 = Effect.gen(function* () {
|
|
9746
10053
|
const crypto = yield* Crypto.Crypto;
|
|
9747
10054
|
const config = yield* ServerConfig$1;
|
|
9748
10055
|
const pairingLinks = yield* AuthPairingLinkRepository;
|
|
@@ -9940,7 +10247,7 @@ const make$63 = Effect.gen(function* () {
|
|
|
9940
10247
|
consume
|
|
9941
10248
|
});
|
|
9942
10249
|
});
|
|
9943
|
-
const layer$
|
|
10250
|
+
const layer$60 = Layer.effect(PairingGrantStore, make$64).pipe(Layer.provideMerge(layer$61));
|
|
9944
10251
|
//#endregion
|
|
9945
10252
|
//#region src/persistence/Migrations/001_OrchestrationEvents.ts
|
|
9946
10253
|
var _001_OrchestrationEvents_default = Effect.gen(function* () {
|
|
@@ -11555,7 +11862,7 @@ Layer.effectDiscard(runMigrations());
|
|
|
11555
11862
|
//#region src/persistence/Layers/Sqlite.ts
|
|
11556
11863
|
const defaultSqliteClientLoaders = {
|
|
11557
11864
|
bun: () => import("@effect/sql-sqlite-bun/SqliteClient"),
|
|
11558
|
-
node: () => import("./NodeSqliteClient-
|
|
11865
|
+
node: () => import("./NodeSqliteClient-CRS7rg24.mjs")
|
|
11559
11866
|
};
|
|
11560
11867
|
const makeRuntimeSqliteLayer = Effect.fn("makeRuntimeSqliteLayer")(function* (config) {
|
|
11561
11868
|
const runtime = process.versions.bun !== void 0 ? "bun" : "node";
|
|
@@ -11713,7 +12020,7 @@ function parseBearerToken(request) {
|
|
|
11713
12020
|
const token = header.slice(7).trim();
|
|
11714
12021
|
return token.length > 0 ? token : null;
|
|
11715
12022
|
}
|
|
11716
|
-
const make$
|
|
12023
|
+
const make$63 = Effect.gen(function* () {
|
|
11717
12024
|
const policy = yield* EnvironmentAuthPolicy;
|
|
11718
12025
|
const bootstrapCredentials = yield* PairingGrantStore;
|
|
11719
12026
|
const sessions = yield* SessionStore;
|
|
@@ -11908,9 +12215,9 @@ const make$62 = Effect.gen(function* () {
|
|
|
11908
12215
|
issueStartupPairingUrl
|
|
11909
12216
|
});
|
|
11910
12217
|
});
|
|
11911
|
-
const layer$
|
|
11912
|
-
const storageLayer = Layer.mergeAll(layer$
|
|
11913
|
-
const runtimeLayer = layer$
|
|
12218
|
+
const layer$59 = Layer.effect(EnvironmentAuth, make$63).pipe(Layer.provideMerge(layer$60), Layer.provideMerge(layer$62), Layer.provideMerge(layer$65));
|
|
12219
|
+
const storageLayer = Layer.mergeAll(layer$63, layerConfig);
|
|
12220
|
+
const runtimeLayer = layer$59.pipe(Layer.provideMerge(storageLayer));
|
|
11914
12221
|
//#endregion
|
|
11915
12222
|
//#region src/cliAuthFormat.ts
|
|
11916
12223
|
const newline = "\n";
|
|
@@ -12665,7 +12972,7 @@ const runWithEnvironmentAuth = (flags, run, options) => Effect.gen(function* ()
|
|
|
12665
12972
|
}
|
|
12666
12973
|
return yield* Effect.gen(function* () {
|
|
12667
12974
|
return yield* run(yield* EnvironmentAuth);
|
|
12668
|
-
}).pipe(Effect.provide(Layer.mergeAll(runtimeLayer).pipe(Layer.provide(layer$
|
|
12975
|
+
}).pipe(Effect.provide(Layer.mergeAll(runtimeLayer).pipe(Layer.provide(layer$66(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)))));
|
|
12669
12976
|
});
|
|
12670
12977
|
const ttlFlag = Flag.string("ttl").pipe(Flag.withSchema(DurationFromString), Flag.withDescription("TTL, for example `5m`, `1h`, `30d`, or `15 minutes`."), Flag.optional);
|
|
12671
12978
|
const jsonFlag = Flag.boolean("json").pipe(Flag.withDescription("Emit JSON instead of human-readable output."), Flag.withDefault(false));
|
|
@@ -12750,422 +13057,6 @@ const sessionCommand = Command.make("session").pipe(Command.withDescription("Man
|
|
|
12750
13057
|
]));
|
|
12751
13058
|
const authCommand = Command.make("auth").pipe(Command.withDescription("Manage the local auth control plane for headless deployments."), Command.withSubcommands([pairingCommand, sessionCommand]));
|
|
12752
13059
|
//#endregion
|
|
12753
|
-
//#region src/stream/collectUint8StreamText.ts
|
|
12754
|
-
const collectUint8StreamText = (input) => {
|
|
12755
|
-
const maxBytes = input.maxBytes ?? Number.POSITIVE_INFINITY;
|
|
12756
|
-
const truncatedMarker = input.truncatedMarker ?? "";
|
|
12757
|
-
return input.stream.pipe(Stream.runFold(() => ({
|
|
12758
|
-
chunks: [],
|
|
12759
|
-
bytes: 0,
|
|
12760
|
-
truncated: false
|
|
12761
|
-
}), (state, chunk) => {
|
|
12762
|
-
if (state.truncated) return state;
|
|
12763
|
-
const remainingBytes = maxBytes - state.bytes;
|
|
12764
|
-
if (remainingBytes <= 0) return {
|
|
12765
|
-
...state,
|
|
12766
|
-
truncated: true
|
|
12767
|
-
};
|
|
12768
|
-
const nextChunk = chunk.byteLength > remainingBytes ? chunk.slice(0, remainingBytes) : chunk;
|
|
12769
|
-
state.chunks.push(nextChunk);
|
|
12770
|
-
const bytes = state.bytes + nextChunk.byteLength;
|
|
12771
|
-
const truncated = chunk.byteLength > remainingBytes;
|
|
12772
|
-
return {
|
|
12773
|
-
chunks: state.chunks,
|
|
12774
|
-
bytes,
|
|
12775
|
-
truncated
|
|
12776
|
-
};
|
|
12777
|
-
}), Effect.map((state) => {
|
|
12778
|
-
const text = Buffer.concat(state.chunks, state.bytes).toString("utf8");
|
|
12779
|
-
return {
|
|
12780
|
-
text: state.truncated && truncatedMarker.length > 0 ? `${text}${truncatedMarker}` : text,
|
|
12781
|
-
bytes: state.bytes,
|
|
12782
|
-
truncated: state.truncated
|
|
12783
|
-
};
|
|
12784
|
-
}));
|
|
12785
|
-
};
|
|
12786
|
-
//#endregion
|
|
12787
|
-
//#region src/processRunner.ts
|
|
12788
|
-
const ProcessInvocationFields = {
|
|
12789
|
-
command: Schema$1.String,
|
|
12790
|
-
argumentCount: Schema$1.Number,
|
|
12791
|
-
cwd: Schema$1.optional(Schema$1.String),
|
|
12792
|
-
spawnCwd: Schema$1.optional(Schema$1.String)
|
|
12793
|
-
};
|
|
12794
|
-
const formatProcessInvocation = (input) => {
|
|
12795
|
-
const executionCwd = input.spawnCwd ?? input.cwd;
|
|
12796
|
-
return executionCwd === void 0 ? `'${input.command}'` : `'${input.command}' in '${executionCwd}'`;
|
|
12797
|
-
};
|
|
12798
|
-
var ProcessSpawnError = class extends Schema$1.TaggedErrorClass()("ProcessSpawnError", {
|
|
12799
|
-
...ProcessInvocationFields,
|
|
12800
|
-
resolvedCommand: Schema$1.optional(Schema$1.String),
|
|
12801
|
-
resolvedArgumentCount: Schema$1.optional(Schema$1.Number),
|
|
12802
|
-
shell: Schema$1.optional(Schema$1.Boolean),
|
|
12803
|
-
cause: Schema$1.Defect()
|
|
12804
|
-
}) {
|
|
12805
|
-
get message() {
|
|
12806
|
-
return `Failed to spawn process ${formatProcessInvocation(this)}`;
|
|
12807
|
-
}
|
|
12808
|
-
};
|
|
12809
|
-
var ProcessStdinError = class extends Schema$1.TaggedErrorClass()("ProcessStdinError", {
|
|
12810
|
-
...ProcessInvocationFields,
|
|
12811
|
-
stdinBytes: Schema$1.Number,
|
|
12812
|
-
cause: Schema$1.Defect()
|
|
12813
|
-
}) {
|
|
12814
|
-
get message() {
|
|
12815
|
-
return `Failed to write stdin for process ${formatProcessInvocation(this)}`;
|
|
12816
|
-
}
|
|
12817
|
-
};
|
|
12818
|
-
var ProcessOutputLimitError = class extends Schema$1.TaggedErrorClass()("ProcessOutputLimitError", {
|
|
12819
|
-
...ProcessInvocationFields,
|
|
12820
|
-
stream: Schema$1.Literals(["stdout", "stderr"]),
|
|
12821
|
-
maxBytes: Schema$1.Number,
|
|
12822
|
-
observedBytes: Schema$1.Number
|
|
12823
|
-
}) {
|
|
12824
|
-
get message() {
|
|
12825
|
-
return `Process ${formatProcessInvocation(this)} ${this.stream} produced ${this.observedBytes} bytes, exceeding the ${this.maxBytes} byte limit`;
|
|
12826
|
-
}
|
|
12827
|
-
};
|
|
12828
|
-
var ProcessReadError = class extends Schema$1.TaggedErrorClass()("ProcessReadError", {
|
|
12829
|
-
...ProcessInvocationFields,
|
|
12830
|
-
stream: Schema$1.Literals([
|
|
12831
|
-
"stdout",
|
|
12832
|
-
"stderr",
|
|
12833
|
-
"exitCode"
|
|
12834
|
-
]),
|
|
12835
|
-
cause: Schema$1.Defect()
|
|
12836
|
-
}) {
|
|
12837
|
-
get message() {
|
|
12838
|
-
return `Failed to read ${this.stream} for process ${formatProcessInvocation(this)}`;
|
|
12839
|
-
}
|
|
12840
|
-
};
|
|
12841
|
-
var ProcessTimeoutError = class extends Schema$1.TaggedErrorClass()("ProcessTimeoutError", {
|
|
12842
|
-
...ProcessInvocationFields,
|
|
12843
|
-
timeoutMs: Schema$1.Number
|
|
12844
|
-
}) {
|
|
12845
|
-
get message() {
|
|
12846
|
-
return `Process ${formatProcessInvocation(this)} timed out after ${this.timeoutMs}ms`;
|
|
12847
|
-
}
|
|
12848
|
-
};
|
|
12849
|
-
Schema$1.Union([
|
|
12850
|
-
ProcessSpawnError,
|
|
12851
|
-
ProcessStdinError,
|
|
12852
|
-
ProcessOutputLimitError,
|
|
12853
|
-
ProcessReadError,
|
|
12854
|
-
ProcessTimeoutError
|
|
12855
|
-
]);
|
|
12856
|
-
var ProcessRunner = class extends Context.Service()("@p4code/cli/processRunner") {};
|
|
12857
|
-
const DEFAULT_TIMEOUT = "60 seconds";
|
|
12858
|
-
const DEFAULT_MAX_OUTPUT_BYTES$2 = 8 * 1024 * 1024;
|
|
12859
|
-
const WINDOWS_COMMAND_NOT_FOUND_PATTERNS = [
|
|
12860
|
-
/is not recognized as an internal or external command/i,
|
|
12861
|
-
/n.o . reconhecido como um comando interno/i,
|
|
12862
|
-
/non . riconosciuto come comando interno o esterno/i,
|
|
12863
|
-
/n.est pas reconnu en tant que commande interne/i,
|
|
12864
|
-
/no se reconoce como un comando interno o externo/i,
|
|
12865
|
-
/wird nicht als interner oder externer befehl/i
|
|
12866
|
-
];
|
|
12867
|
-
function hasWindowsCommandNotFoundMessage(output) {
|
|
12868
|
-
return WINDOWS_COMMAND_NOT_FOUND_PATTERNS.some((pattern) => pattern.test(output));
|
|
12869
|
-
}
|
|
12870
|
-
const isWindowsCommandNotFound = Effect.fn("processRunner.isWindowsCommandNotFound")(function* (code, stderr) {
|
|
12871
|
-
if ((yield* HostProcessPlatform) !== "win32") return false;
|
|
12872
|
-
if (code === 9009) return true;
|
|
12873
|
-
return hasWindowsCommandNotFoundMessage(stderr);
|
|
12874
|
-
});
|
|
12875
|
-
const collectText = Effect.fn("processRunner.collectText")(function* (input) {
|
|
12876
|
-
const stream = input.stream.pipe(Stream.mapError((cause) => new ProcessReadError({
|
|
12877
|
-
command: input.command,
|
|
12878
|
-
argumentCount: input.args.length,
|
|
12879
|
-
cwd: input.cwd,
|
|
12880
|
-
spawnCwd: input.spawnCwd,
|
|
12881
|
-
stream: input.streamName,
|
|
12882
|
-
cause
|
|
12883
|
-
})));
|
|
12884
|
-
if (input.outputMode === "truncate") return yield* collectUint8StreamText({
|
|
12885
|
-
stream,
|
|
12886
|
-
maxBytes: input.maxOutputBytes,
|
|
12887
|
-
truncatedMarker: input.truncatedMarker
|
|
12888
|
-
});
|
|
12889
|
-
return yield* stream.pipe(Stream.runFoldEffect(() => ({
|
|
12890
|
-
chunks: [],
|
|
12891
|
-
bytes: 0
|
|
12892
|
-
}), (state, chunk) => {
|
|
12893
|
-
const remainingBytes = input.maxOutputBytes - state.bytes;
|
|
12894
|
-
if (chunk.byteLength > remainingBytes) return Effect.fail(new ProcessOutputLimitError({
|
|
12895
|
-
command: input.command,
|
|
12896
|
-
argumentCount: input.args.length,
|
|
12897
|
-
cwd: input.cwd,
|
|
12898
|
-
spawnCwd: input.spawnCwd,
|
|
12899
|
-
stream: input.streamName,
|
|
12900
|
-
maxBytes: input.maxOutputBytes,
|
|
12901
|
-
observedBytes: state.bytes + chunk.byteLength
|
|
12902
|
-
}));
|
|
12903
|
-
state.chunks.push(chunk);
|
|
12904
|
-
return Effect.succeed({
|
|
12905
|
-
chunks: state.chunks,
|
|
12906
|
-
bytes: state.bytes + chunk.byteLength
|
|
12907
|
-
});
|
|
12908
|
-
}), Effect.map((state) => ({
|
|
12909
|
-
text: Buffer.concat(state.chunks, state.bytes).toString("utf8"),
|
|
12910
|
-
bytes: state.bytes,
|
|
12911
|
-
truncated: false
|
|
12912
|
-
})));
|
|
12913
|
-
});
|
|
12914
|
-
function finalizeRunProcess(effect, input) {
|
|
12915
|
-
const timeout = Duration.fromInputUnsafe(input.timeout ?? DEFAULT_TIMEOUT);
|
|
12916
|
-
const timeoutBehavior = input.timeoutBehavior ?? "error";
|
|
12917
|
-
return effect.pipe(Effect.scoped, Effect.timeoutOption(timeout), Effect.flatMap((result) => {
|
|
12918
|
-
if (Option.isSome(result)) return Effect.succeed(result.value);
|
|
12919
|
-
if (timeoutBehavior === "timedOutResult") return Effect.succeed({
|
|
12920
|
-
stdout: "",
|
|
12921
|
-
stderr: "",
|
|
12922
|
-
code: null,
|
|
12923
|
-
timedOut: true,
|
|
12924
|
-
stdoutTruncated: false,
|
|
12925
|
-
stderrTruncated: false
|
|
12926
|
-
});
|
|
12927
|
-
return Effect.fail(new ProcessTimeoutError({
|
|
12928
|
-
command: input.command,
|
|
12929
|
-
argumentCount: input.args.length,
|
|
12930
|
-
cwd: input.cwd,
|
|
12931
|
-
spawnCwd: input.spawnCwd,
|
|
12932
|
-
timeoutMs: Duration.toMillis(timeout)
|
|
12933
|
-
}));
|
|
12934
|
-
}));
|
|
12935
|
-
}
|
|
12936
|
-
const runProcessCore = Effect.fn("processRunner.runProcessCore")(function* (spawner, input) {
|
|
12937
|
-
const maxOutputBytes = input.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES$2;
|
|
12938
|
-
const outputMode = input.outputMode ?? "error";
|
|
12939
|
-
const truncatedMarker = input.truncatedMarker ?? "";
|
|
12940
|
-
const extendEnv = input.env !== void 0;
|
|
12941
|
-
const spawnCommand = yield* resolveSpawnCommand(input.command, input.args, input.env === void 0 ? {} : {
|
|
12942
|
-
env: input.env,
|
|
12943
|
-
extendEnv
|
|
12944
|
-
});
|
|
12945
|
-
const child = yield* spawner.spawn(ChildProcess$1.make(spawnCommand.command, spawnCommand.args, {
|
|
12946
|
-
...input.spawnCwd ?? input.cwd ? { cwd: input.spawnCwd ?? input.cwd } : {},
|
|
12947
|
-
...input.env !== void 0 ? {
|
|
12948
|
-
env: input.env,
|
|
12949
|
-
extendEnv
|
|
12950
|
-
} : {},
|
|
12951
|
-
shell: spawnCommand.shell
|
|
12952
|
-
})).pipe(Effect.mapError((cause) => new ProcessSpawnError({
|
|
12953
|
-
command: input.command,
|
|
12954
|
-
argumentCount: input.args.length,
|
|
12955
|
-
cwd: input.cwd,
|
|
12956
|
-
spawnCwd: input.spawnCwd,
|
|
12957
|
-
resolvedCommand: spawnCommand.command,
|
|
12958
|
-
resolvedArgumentCount: spawnCommand.args.length,
|
|
12959
|
-
shell: spawnCommand.shell,
|
|
12960
|
-
cause
|
|
12961
|
-
})));
|
|
12962
|
-
const stdin = input.stdin;
|
|
12963
|
-
const writeStdin = stdin === void 0 ? Effect.void : Stream.run(Stream.encodeText(Stream.make(stdin)), child.stdin).pipe(Effect.mapError((cause) => new ProcessStdinError({
|
|
12964
|
-
command: input.command,
|
|
12965
|
-
argumentCount: input.args.length,
|
|
12966
|
-
cwd: input.cwd,
|
|
12967
|
-
spawnCwd: input.spawnCwd,
|
|
12968
|
-
stdinBytes: Buffer.byteLength(stdin),
|
|
12969
|
-
cause
|
|
12970
|
-
})));
|
|
12971
|
-
const [stdout, stderr] = yield* Effect.all([
|
|
12972
|
-
collectText({
|
|
12973
|
-
command: input.command,
|
|
12974
|
-
args: input.args,
|
|
12975
|
-
cwd: input.cwd,
|
|
12976
|
-
spawnCwd: input.spawnCwd,
|
|
12977
|
-
streamName: "stdout",
|
|
12978
|
-
stream: child.stdout,
|
|
12979
|
-
maxOutputBytes,
|
|
12980
|
-
outputMode,
|
|
12981
|
-
truncatedMarker
|
|
12982
|
-
}),
|
|
12983
|
-
collectText({
|
|
12984
|
-
command: input.command,
|
|
12985
|
-
args: input.args,
|
|
12986
|
-
cwd: input.cwd,
|
|
12987
|
-
spawnCwd: input.spawnCwd,
|
|
12988
|
-
streamName: "stderr",
|
|
12989
|
-
stream: child.stderr,
|
|
12990
|
-
maxOutputBytes,
|
|
12991
|
-
outputMode,
|
|
12992
|
-
truncatedMarker
|
|
12993
|
-
}),
|
|
12994
|
-
writeStdin
|
|
12995
|
-
], { concurrency: "unbounded" });
|
|
12996
|
-
const exitCode = yield* child.exitCode.pipe(Effect.mapError((cause) => new ProcessReadError({
|
|
12997
|
-
command: input.command,
|
|
12998
|
-
argumentCount: input.args.length,
|
|
12999
|
-
cwd: input.cwd,
|
|
13000
|
-
spawnCwd: input.spawnCwd,
|
|
13001
|
-
stream: "exitCode",
|
|
13002
|
-
cause
|
|
13003
|
-
})));
|
|
13004
|
-
return {
|
|
13005
|
-
stdout: stdout.text,
|
|
13006
|
-
stderr: stderr.text,
|
|
13007
|
-
code: exitCode,
|
|
13008
|
-
timedOut: false,
|
|
13009
|
-
stdoutTruncated: stdout.truncated,
|
|
13010
|
-
stderrTruncated: stderr.truncated
|
|
13011
|
-
};
|
|
13012
|
-
});
|
|
13013
|
-
const make$61 = Effect.fn("ProcessRunner.make")(function* () {
|
|
13014
|
-
const spawner = yield* ChildProcessSpawner$1.ChildProcessSpawner;
|
|
13015
|
-
const run = (input) => finalizeRunProcess(runProcessCore(spawner, input), input);
|
|
13016
|
-
return ProcessRunner.of({ run });
|
|
13017
|
-
});
|
|
13018
|
-
const layer$57 = Layer.effect(ProcessRunner, make$61());
|
|
13019
|
-
//#endregion
|
|
13020
|
-
//#region src/cli/completions.ts
|
|
13021
|
-
/**
|
|
13022
|
-
* Installs the zsh completion script for the `p4` command.
|
|
13023
|
-
*
|
|
13024
|
-
* zsh ships `_perforce` with `#compdef p4 p4d`, so out of the box tabbing
|
|
13025
|
-
* through our subcommands prints "unhandled perforce command: …" from its
|
|
13026
|
-
* fallback message. `compinit` resolves duplicate `#compdef` claims by fpath
|
|
13027
|
-
* order — first match wins — and `_perforce` lives in the *last* fpath entry
|
|
13028
|
-
* (`/usr/share/zsh/<ver>/functions`). Writing our own `_p4` into any earlier
|
|
13029
|
-
* directory therefore replaces it outright, with no shell-config edit.
|
|
13030
|
-
*/
|
|
13031
|
-
const COMPLETION_FILE = "_p4";
|
|
13032
|
-
const GENERATE_TIMEOUT_MS = 1e4;
|
|
13033
|
-
/**
|
|
13034
|
-
* Ordered by how reliably zsh actually scans them.
|
|
13035
|
-
*
|
|
13036
|
-
* oh-my-zsh comes first and is created on demand: it adds
|
|
13037
|
-
* `custom/completions` to fpath unconditionally, it is user-owned so
|
|
13038
|
-
* `compinit -i` never skips it as insecure, and it does not depend on a package
|
|
13039
|
-
* manager's prefix. The Homebrew and /usr/local directories are only used when
|
|
13040
|
-
* they already exist, and even then are not guaranteed to be on fpath —
|
|
13041
|
-
* `brew shellenv` contributes Homebrew's, and a plugin that rebuilds fpath can
|
|
13042
|
-
* drop it, which is exactly how a completion file lands somewhere zsh never
|
|
13043
|
-
* reads.
|
|
13044
|
-
*/
|
|
13045
|
-
function candidateDirectories(input) {
|
|
13046
|
-
return [
|
|
13047
|
-
{
|
|
13048
|
-
dir: input.join(input.homeDir, ".oh-my-zsh", "custom", "completions"),
|
|
13049
|
-
createWhenPresent: input.join(input.homeDir, ".oh-my-zsh")
|
|
13050
|
-
},
|
|
13051
|
-
{ dir: "/opt/homebrew/share/zsh/site-functions" },
|
|
13052
|
-
{ dir: "/usr/local/share/zsh/site-functions" },
|
|
13053
|
-
{
|
|
13054
|
-
dir: input.join(input.homeDir, ".zfunc"),
|
|
13055
|
-
requiresFpathEntry: true
|
|
13056
|
-
}
|
|
13057
|
-
];
|
|
13058
|
-
}
|
|
13059
|
-
/**
|
|
13060
|
-
* Asks the running CLI for its own completion script rather than rendering one
|
|
13061
|
-
* here, so the output cannot drift from what `--completions` produces.
|
|
13062
|
-
*/
|
|
13063
|
-
const generateZshCompletionScript = Effect.fn("cli.completions.generate")(function* () {
|
|
13064
|
-
const runner = yield* ProcessRunner;
|
|
13065
|
-
const execPath = yield* HostProcessExecutablePath;
|
|
13066
|
-
const entryPath = (yield* HostProcessArguments)[1] ?? "";
|
|
13067
|
-
if (entryPath === "") return null;
|
|
13068
|
-
const result = yield* runner.run({
|
|
13069
|
-
command: execPath,
|
|
13070
|
-
args: [
|
|
13071
|
-
entryPath,
|
|
13072
|
-
"--completions",
|
|
13073
|
-
"zsh"
|
|
13074
|
-
],
|
|
13075
|
-
timeout: GENERATE_TIMEOUT_MS
|
|
13076
|
-
});
|
|
13077
|
-
if (result.code !== 0) return null;
|
|
13078
|
-
const script = result.stdout.trim();
|
|
13079
|
-
return script.length === 0 ? null : `${script}\n`;
|
|
13080
|
-
});
|
|
13081
|
-
/**
|
|
13082
|
-
* Best-effort: returns null when completions are not applicable (Windows, no
|
|
13083
|
-
* writable directory) rather than failing the command that asked for them.
|
|
13084
|
-
* Installing completions is never the reason an install should fail.
|
|
13085
|
-
*/
|
|
13086
|
-
const installZshCompletion = Effect.fn("cli.completions.install")(function* () {
|
|
13087
|
-
if ((yield* HostProcessPlatform) === "win32") return null;
|
|
13088
|
-
const fs = yield* FileSystem.FileSystem;
|
|
13089
|
-
const path = yield* Path.Path;
|
|
13090
|
-
const homeDir = (yield* HostProcessEnvironment).HOME ?? "";
|
|
13091
|
-
if (homeDir === "") return null;
|
|
13092
|
-
const script = yield* generateZshCompletionScript();
|
|
13093
|
-
if (script === null) return null;
|
|
13094
|
-
for (const candidate of candidateDirectories({
|
|
13095
|
-
homeDir,
|
|
13096
|
-
join: path.join
|
|
13097
|
-
})) {
|
|
13098
|
-
if (candidate.requiresFpathEntry === true || candidate.createWhenPresent !== void 0 && (yield* fs.exists(candidate.createWhenPresent).pipe(Effect.orElseSucceed(() => false)))) {
|
|
13099
|
-
if (!(yield* fs.makeDirectory(candidate.dir, { recursive: true }).pipe(Effect.as(true), Effect.orElseSucceed(() => false)))) continue;
|
|
13100
|
-
} else if (!(yield* fs.exists(candidate.dir).pipe(Effect.orElseSucceed(() => false)))) continue;
|
|
13101
|
-
const target = path.join(candidate.dir, COMPLETION_FILE);
|
|
13102
|
-
if (!(yield* fs.writeFileString(target, script).pipe(Effect.as(true), Effect.orElseSucceed(() => false)))) continue;
|
|
13103
|
-
return {
|
|
13104
|
-
path: target,
|
|
13105
|
-
requiresFpathEntry: candidate.requiresFpathEntry === true
|
|
13106
|
-
};
|
|
13107
|
-
}
|
|
13108
|
-
return null;
|
|
13109
|
-
});
|
|
13110
|
-
const HINT_MARKER_FILE = "completions-hint";
|
|
13111
|
-
const HINT_INTERVAL_MS = 1440 * 60 * 1e3;
|
|
13112
|
-
const COMPLETIONS_HINT = "Shell completions are not installed, so tab completion shows Perforce's. Run `p4 completions install`.";
|
|
13113
|
-
/**
|
|
13114
|
-
* A hint when no `_p4` is on the fpath, at most once a day.
|
|
13115
|
-
*
|
|
13116
|
-
* A stat of a few directories, deliberately not the install itself: generating
|
|
13117
|
-
* the script spawns a subprocess, which is far too much to do on every command
|
|
13118
|
-
* just in case. Throttled through a marker file so an unactioned hint does not
|
|
13119
|
-
* become noise on every invocation.
|
|
13120
|
-
*/
|
|
13121
|
-
const resolveMissingCompletionsHint = Effect.fn("cli.completions.hint")(function* (input) {
|
|
13122
|
-
if ((yield* HostProcessPlatform) === "win32") return null;
|
|
13123
|
-
const fs = yield* FileSystem.FileSystem;
|
|
13124
|
-
const path = yield* Path.Path;
|
|
13125
|
-
const homeDir = (yield* HostProcessEnvironment).HOME ?? "";
|
|
13126
|
-
if (homeDir === "") return null;
|
|
13127
|
-
for (const candidate of candidateDirectories({
|
|
13128
|
-
homeDir,
|
|
13129
|
-
join: path.join
|
|
13130
|
-
})) if (yield* fs.exists(path.join(candidate.dir, COMPLETION_FILE)).pipe(Effect.orElseSucceed(() => false))) return null;
|
|
13131
|
-
const markerPath = path.join(input.stateDir, HINT_MARKER_FILE);
|
|
13132
|
-
const marker = yield* fs.stat(markerPath).pipe(Effect.asSome, Effect.orElseSucceed(() => null));
|
|
13133
|
-
const nowMs = DateTime.toEpochMillis(yield* DateTime.now);
|
|
13134
|
-
if (marker !== null && Option.isSome(marker)) {
|
|
13135
|
-
const mtime = marker.value.mtime;
|
|
13136
|
-
if (Option.isSome(mtime) && nowMs - mtime.value.getTime() < HINT_INTERVAL_MS) return null;
|
|
13137
|
-
}
|
|
13138
|
-
yield* fs.makeDirectory(input.stateDir, { recursive: true }).pipe(Effect.ignore);
|
|
13139
|
-
yield* fs.writeFileString(markerPath, "").pipe(Effect.ignore);
|
|
13140
|
-
return COMPLETIONS_HINT;
|
|
13141
|
-
});
|
|
13142
|
-
/**
|
|
13143
|
-
* Standalone so completions never depend on the service. A laptop that only
|
|
13144
|
-
* runs clients has no service to install, and a host whose `service install`
|
|
13145
|
-
* fails still deserves working completions.
|
|
13146
|
-
*/
|
|
13147
|
-
const completionsCommand = Command.make("completions").pipe(Command.withDescription("Manage shell completions for the p4 command."), Command.withSubcommands([Command.make("install").pipe(Command.withDescription("Install zsh completions, replacing the Perforce completion that claims `p4`."), Command.withHandler(() => Effect.gen(function* () {
|
|
13148
|
-
const install = yield* installZshCompletion();
|
|
13149
|
-
if (install === null) {
|
|
13150
|
-
yield* Console.error([
|
|
13151
|
-
"Could not install completions automatically.",
|
|
13152
|
-
"Write them yourself with:",
|
|
13153
|
-
" p4 --completions zsh > <a directory on your fpath>/_p4"
|
|
13154
|
-
].join("\n"));
|
|
13155
|
-
return;
|
|
13156
|
-
}
|
|
13157
|
-
yield* Console.log(formatZshCompletionInstall(install));
|
|
13158
|
-
}).pipe(Effect.provide(layer$57))))]));
|
|
13159
|
-
function formatZshCompletionInstall(install) {
|
|
13160
|
-
const installed = `Installed zsh completions at ${install.path}.`;
|
|
13161
|
-
if (!install.requiresFpathEntry) return `${installed} Open a new shell to use them.`;
|
|
13162
|
-
return [
|
|
13163
|
-
installed,
|
|
13164
|
-
`Add this to ~/.zshrc before compinit runs, then open a new shell:`,
|
|
13165
|
-
` fpath=(${install.path.replace(/\/[^/]+$/u, "")} $fpath)`
|
|
13166
|
-
].join("\n");
|
|
13167
|
-
}
|
|
13168
|
-
//#endregion
|
|
13169
13060
|
//#region src/hub/HubConfig.ts
|
|
13170
13061
|
/**
|
|
13171
13062
|
* Configuration for the hub process.
|
|
@@ -13427,7 +13318,7 @@ const makeAgentAssetRepository = Effect.gen(function* () {
|
|
|
13427
13318
|
};
|
|
13428
13319
|
const existing = yield* findByKey(key);
|
|
13429
13320
|
if (Option.isNone(existing)) return { _tag: "deleted" };
|
|
13430
|
-
if (existing.value.version !== request.expectedVersion) return {
|
|
13321
|
+
if (request.expectedVersion !== void 0 && existing.value.version !== request.expectedVersion) return {
|
|
13431
13322
|
_tag: "conflict",
|
|
13432
13323
|
current: Option.some(existing.value)
|
|
13433
13324
|
};
|
|
@@ -13809,29 +13700,34 @@ const putAssetRoute = HttpRouter.add("PUT", "/assets", respondToHubFailures(Effe
|
|
|
13809
13700
|
return HttpServerResponse.jsonUnsafe(outcome.asset, { status: outcome.created ? 201 : 200 });
|
|
13810
13701
|
})));
|
|
13811
13702
|
/**
|
|
13812
|
-
* Deletion is conditional
|
|
13813
|
-
* skill and both decide to remove it,
|
|
13814
|
-
* predates the other's edit —
|
|
13815
|
-
* nothing to notice it by.
|
|
13703
|
+
* Deletion is conditional when `expectedVersion` is supplied, which the sync
|
|
13704
|
+
* always does. Two machines can both hold a skill and both decide to remove it,
|
|
13705
|
+
* and one may be acting on a copy that predates the other's edit — deleting on
|
|
13706
|
+
* that basis discards the edit with nothing to notice it by.
|
|
13707
|
+
*
|
|
13708
|
+
* Omitting it deletes unconditionally, and that is deliberate: a person
|
|
13709
|
+
* clearing a row they can only see in a listing has never held a version for
|
|
13710
|
+
* it. Requiring one would leave an asset published by a decommissioned machine
|
|
13711
|
+
* permanently stuck, which is a worse failure than the race it would prevent.
|
|
13816
13712
|
*/
|
|
13817
13713
|
const deleteAssetRoute = HttpRouter.add("DELETE", "/assets/:kind/:name", respondToHubFailures(Effect.gen(function* () {
|
|
13818
13714
|
yield* authenticateHubRequest();
|
|
13819
13715
|
const params = yield* HttpRouter.params;
|
|
13820
13716
|
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
13821
|
-
const
|
|
13717
|
+
const rawExpectedVersion = new URL(request.url, "http://hub.invalid").searchParams.get("expectedVersion");
|
|
13822
13718
|
const requested = yield* decodeOrInvalid(Schema$1.Struct({
|
|
13823
13719
|
kind: AgentAssetKind,
|
|
13824
13720
|
name: AgentAssetName,
|
|
13825
|
-
expectedVersion: Schema$1.FiniteFromString.check(Schema$1.isGreaterThanOrEqualTo(1))
|
|
13721
|
+
expectedVersion: Schema$1.optional(Schema$1.FiniteFromString.check(Schema$1.isGreaterThanOrEqualTo(1)))
|
|
13826
13722
|
}), "Path or expectedVersion query parameter is not valid.")({
|
|
13827
13723
|
kind: params.kind,
|
|
13828
13724
|
name: params.name,
|
|
13829
|
-
expectedVersion:
|
|
13725
|
+
...rawExpectedVersion === null ? {} : { expectedVersion: rawExpectedVersion }
|
|
13830
13726
|
});
|
|
13831
13727
|
const outcome = yield* (yield* AgentAssetRepository).deleteByName(requested);
|
|
13832
13728
|
return outcome._tag === "conflict" ? assetConflict(Option.getOrNull(outcome.current)) : HttpServerResponse.empty({ status: 204 });
|
|
13833
13729
|
})));
|
|
13834
|
-
const layer$
|
|
13730
|
+
const layer$58 = Layer.mergeAll(healthRoute, listTasksRoute, getTaskRoute, createTaskRoute, updateTaskRoute, deleteTaskRoute, listAssetsRoute, getAssetRoute, putAssetRoute, deleteAssetRoute);
|
|
13835
13731
|
//#endregion
|
|
13836
13732
|
//#region src/hub/Migrations/001_Tasks.ts
|
|
13837
13733
|
/**
|
|
@@ -13978,7 +13874,7 @@ const runHubMigrations = Effect.fn("runHubMigrations")(function* () {
|
|
|
13978
13874
|
//#region src/hub/Sqlite.ts
|
|
13979
13875
|
const sqliteClientLoaders = {
|
|
13980
13876
|
bun: () => import("@effect/sql-sqlite-bun/SqliteClient"),
|
|
13981
|
-
node: () => import("./NodeSqliteClient-
|
|
13877
|
+
node: () => import("./NodeSqliteClient-CRS7rg24.mjs")
|
|
13982
13878
|
};
|
|
13983
13879
|
const makeHubSqliteLayer = Effect.fn("makeHubSqliteLayer")(function* (config) {
|
|
13984
13880
|
const runtime = process.versions.bun !== void 0 ? "bun" : "node";
|
|
@@ -14045,7 +13941,7 @@ const announce = Layer.effectDiscard(Effect.gen(function* () {
|
|
|
14045
13941
|
});
|
|
14046
13942
|
if (isRemoteReachableHost(config.host)) yield* Effect.logWarning("Hub is bound beyond loopback; the bearer token and your tailnet ACL are the only controls.", { host: config.host });
|
|
14047
13943
|
}));
|
|
14048
|
-
const makeHubLayer = HttpRouter.serve(layer$
|
|
13944
|
+
const makeHubLayer = HttpRouter.serve(layer$58).pipe(Layer.provideMerge(announce), Layer.provideMerge(HubHttpServerLive), Layer.provideMerge(TaskRepositoryLive), Layer.provideMerge(AgentAssetRepositoryLive), Layer.provideMerge(HubPersistenceLive));
|
|
14049
13945
|
const runHub = Layer.launch(makeHubLayer);
|
|
14050
13946
|
//#endregion
|
|
14051
13947
|
//#region src/atomicWrite.ts
|
|
@@ -14169,7 +14065,7 @@ function stripDefaultServerSettings(current, defaults) {
|
|
|
14169
14065
|
}
|
|
14170
14066
|
return Object.is(current, defaults) ? void 0 : current;
|
|
14171
14067
|
}
|
|
14172
|
-
const make$
|
|
14068
|
+
const make$62 = Effect.gen(function* () {
|
|
14173
14069
|
const { settingsPath } = yield* ServerConfig$1;
|
|
14174
14070
|
const fs = yield* FileSystem.FileSystem;
|
|
14175
14071
|
const pathService = yield* Path.Path;
|
|
@@ -14390,7 +14286,252 @@ const make$60 = Effect.gen(function* () {
|
|
|
14390
14286
|
}
|
|
14391
14287
|
};
|
|
14392
14288
|
});
|
|
14393
|
-
const layer$
|
|
14289
|
+
const layer$57 = Layer.effect(ServerSettingsService, make$62);
|
|
14290
|
+
//#endregion
|
|
14291
|
+
//#region src/pathExpansion.ts
|
|
14292
|
+
/**
|
|
14293
|
+
* Expand a leading `~` (or `~/…`, `~\…`) in a user-supplied path to the
|
|
14294
|
+
* current user's home directory. Spawned processes don't get shell
|
|
14295
|
+
* expansion, so env vars like `CODEX_HOME=~/.codex-work` would be passed
|
|
14296
|
+
* verbatim and treated as relative paths by the receiver.
|
|
14297
|
+
*
|
|
14298
|
+
* Matches the behavior of the other `expandHomePath` helpers in the
|
|
14299
|
+
* workspace layers and CLI bootstrap: `~` alone and both `~/` and `~\`
|
|
14300
|
+
* separators are handled. Returns the input unchanged if it doesn't
|
|
14301
|
+
* start with `~` or is empty. Does not handle `~user` (other-user)
|
|
14302
|
+
* expansion.
|
|
14303
|
+
*/
|
|
14304
|
+
function expandHomePath$3(value) {
|
|
14305
|
+
if (!value) return value;
|
|
14306
|
+
if (value === "~") return NodeOS.homedir();
|
|
14307
|
+
if (value.startsWith("~/") || value.startsWith("~\\")) return NodePath.join(NodeOS.homedir(), value.slice(2));
|
|
14308
|
+
return value;
|
|
14309
|
+
}
|
|
14310
|
+
//#endregion
|
|
14311
|
+
//#region src/provider/Drivers/ClaudeSkills.ts
|
|
14312
|
+
/**
|
|
14313
|
+
* ClaudeSkills — filesystem discovery of Claude Code skills for the `$` picker.
|
|
14314
|
+
*
|
|
14315
|
+
* Claude Code loads skills from `<config dir>/skills` (user scope) and
|
|
14316
|
+
* `<cwd>/.claude/skills` (project scope), one directory per skill with a
|
|
14317
|
+
* `SKILL.md` carrying YAML frontmatter. The Agent SDK init handshake surfaces
|
|
14318
|
+
* skills only as slash commands without their filesystem paths, so the
|
|
14319
|
+
* provider snapshot scans the same locations directly, mirroring how the
|
|
14320
|
+
* Codex app-server reports its skills.
|
|
14321
|
+
*
|
|
14322
|
+
* @module provider/Drivers/ClaudeSkills
|
|
14323
|
+
*/
|
|
14324
|
+
const FRONTMATTER_PATTERN = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
|
|
14325
|
+
function parseSkillFrontmatter(contents) {
|
|
14326
|
+
const match = FRONTMATTER_PATTERN.exec(contents);
|
|
14327
|
+
if (!match) return { kind: "missing" };
|
|
14328
|
+
let parsed;
|
|
14329
|
+
try {
|
|
14330
|
+
parsed = parse(match[1] ?? "");
|
|
14331
|
+
} catch {
|
|
14332
|
+
return { kind: "malformed" };
|
|
14333
|
+
}
|
|
14334
|
+
if (typeof parsed !== "object" || parsed === null) return { kind: "malformed" };
|
|
14335
|
+
const record = parsed;
|
|
14336
|
+
const name = typeof record.name === "string" ? record.name.trim() : "";
|
|
14337
|
+
const description = typeof record.description === "string" ? record.description.trim() : "";
|
|
14338
|
+
return {
|
|
14339
|
+
kind: "parsed",
|
|
14340
|
+
...name ? { name } : {},
|
|
14341
|
+
...description ? { description } : {}
|
|
14342
|
+
};
|
|
14343
|
+
}
|
|
14344
|
+
/**
|
|
14345
|
+
* Resolve the Claude config directory the CLI would use, matching the
|
|
14346
|
+
* precedence the spawned CLI sees: the instance's `homePath` (exported as
|
|
14347
|
+
* `CLAUDE_CONFIG_DIR` by `makeClaudeEnvironment`), then a `CLAUDE_CONFIG_DIR`
|
|
14348
|
+
* already present in the process environment, then `~/.claude`.
|
|
14349
|
+
*
|
|
14350
|
+
* Exported for the memory sync, which materializes `CLAUDE.md` directly into
|
|
14351
|
+
* this directory. Same reasoning as the skills root below: resolving it a
|
|
14352
|
+
* second time somewhere else is how the sync and the runtime come to disagree
|
|
14353
|
+
* about which config directory they are talking about.
|
|
14354
|
+
*/
|
|
14355
|
+
const resolveClaudeConfigDirPath = Effect.fn("resolveClaudeConfigDirPath")(function* (config, environment, cwd) {
|
|
14356
|
+
const path = yield* Path.Path;
|
|
14357
|
+
const homePath = config.homePath.trim();
|
|
14358
|
+
if (homePath.length > 0) return path.resolve(expandHomePath$3(homePath));
|
|
14359
|
+
const environmentConfigDir = environment.CLAUDE_CONFIG_DIR?.trim() ?? "";
|
|
14360
|
+
if (environmentConfigDir.length > 0) return cwd ? path.resolve(cwd, environmentConfigDir) : path.resolve(environmentConfigDir);
|
|
14361
|
+
return path.join(NodeOS.homedir(), ".claude");
|
|
14362
|
+
});
|
|
14363
|
+
/**
|
|
14364
|
+
* The user-scope skills directory the CLI would read.
|
|
14365
|
+
*
|
|
14366
|
+
* Exported for the hub sync, which materializes into exactly this directory —
|
|
14367
|
+
* the same resolution the discovery below uses, so a synced skill is one the
|
|
14368
|
+
* `$` picker can see. Deriving it a second time is how the two would drift.
|
|
14369
|
+
*/
|
|
14370
|
+
const resolveClaudeUserSkillsDir = Effect.fn("resolveClaudeUserSkillsDir")(function* (config, environment, cwd) {
|
|
14371
|
+
const path = yield* Path.Path;
|
|
14372
|
+
const configDirPath = yield* resolveClaudeConfigDirPath(config, environment ?? process.env, cwd);
|
|
14373
|
+
return path.join(configDirPath, "skills");
|
|
14374
|
+
});
|
|
14375
|
+
/**
|
|
14376
|
+
* Enumerate Claude Code skills from the user config dir and the workspace.
|
|
14377
|
+
* Discovery is best-effort: unreadable roots and malformed skill entries are
|
|
14378
|
+
* skipped so a broken skill never degrades the provider snapshot. On name
|
|
14379
|
+
* collisions the project-scoped skill wins, matching Claude Code's
|
|
14380
|
+
* most-specific-wins resolution.
|
|
14381
|
+
*/
|
|
14382
|
+
const discoverClaudeSkills = Effect.fn("discoverClaudeSkills")(function* (config, cwd, environment) {
|
|
14383
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
14384
|
+
const path = yield* Path.Path;
|
|
14385
|
+
const configDirPath = yield* resolveClaudeConfigDirPath(config, environment ?? process.env, cwd);
|
|
14386
|
+
const roots = [{
|
|
14387
|
+
directory: path.join(configDirPath, "skills"),
|
|
14388
|
+
scope: "user"
|
|
14389
|
+
}, ...cwd ? [{
|
|
14390
|
+
directory: path.join(cwd, ".claude", "skills"),
|
|
14391
|
+
scope: "project"
|
|
14392
|
+
}] : []];
|
|
14393
|
+
const skillsByName = /* @__PURE__ */ new Map();
|
|
14394
|
+
for (const root of roots) {
|
|
14395
|
+
const entries = yield* fileSystem.readDirectory(root.directory).pipe(Effect.orElseSucceed(() => []));
|
|
14396
|
+
for (const entry of [...entries].sort()) {
|
|
14397
|
+
const skillPath = path.join(root.directory, entry, "SKILL.md");
|
|
14398
|
+
const contents = yield* fileSystem.readFileString(skillPath).pipe(Effect.orElseSucceed(() => void 0));
|
|
14399
|
+
if (contents === void 0) continue;
|
|
14400
|
+
const frontmatter = parseSkillFrontmatter(contents);
|
|
14401
|
+
if (frontmatter.kind === "malformed") continue;
|
|
14402
|
+
const name = (frontmatter.kind === "parsed" ? frontmatter.name : void 0) ?? entry.trim();
|
|
14403
|
+
if (!name) continue;
|
|
14404
|
+
skillsByName.set(name, {
|
|
14405
|
+
name,
|
|
14406
|
+
path: skillPath,
|
|
14407
|
+
enabled: true,
|
|
14408
|
+
scope: root.scope,
|
|
14409
|
+
...frontmatter.kind === "parsed" && frontmatter.description ? { description: frontmatter.description } : {}
|
|
14410
|
+
});
|
|
14411
|
+
}
|
|
14412
|
+
}
|
|
14413
|
+
return [...skillsByName.values()].sort((left, right) => left.name.localeCompare(right.name));
|
|
14414
|
+
});
|
|
14415
|
+
/** Extension for a registration file, so the sync's asset name is `<server>.json`. */
|
|
14416
|
+
const MCP_REGISTRATION_SUFFIX = ".json";
|
|
14417
|
+
const RegistrationFromJson$1 = Schema$1.fromJsonString(McpServerRegistration);
|
|
14418
|
+
const decodeRegistration$1 = Schema$1.decodeUnknownExit(RegistrationFromJson$1);
|
|
14419
|
+
const encodeRegistration = Schema$1.encodeSync(RegistrationFromJson$1);
|
|
14420
|
+
var McpRegistry = class extends Context.Service()("@p4code/cli/mcp/McpRegistry") {};
|
|
14421
|
+
const slotsOf = (registration) => registration.secrets ?? [];
|
|
14422
|
+
const make$61 = Effect.gen(function* () {
|
|
14423
|
+
const config = yield* ServerConfig$1;
|
|
14424
|
+
const secrets = yield* ServerSecretStore;
|
|
14425
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
14426
|
+
const path = yield* Path.Path;
|
|
14427
|
+
const services = yield* Effect.context();
|
|
14428
|
+
const root = path.join(config.stateDir, "mcp");
|
|
14429
|
+
const fileFor = (name) => path.join(root, `${name}${MCP_REGISTRATION_SUFFIX}`);
|
|
14430
|
+
const readSecret = Effect.fn("McpRegistry.readSecret")(function* (server, slot) {
|
|
14431
|
+
const stored = yield* secrets.get(mcpSecretStorageName({
|
|
14432
|
+
server,
|
|
14433
|
+
target: slot.target,
|
|
14434
|
+
key: slot.key
|
|
14435
|
+
})).pipe(Effect.orElseSucceed(() => Option.none()));
|
|
14436
|
+
return Option.map(stored, (bytes) => new TextDecoder().decode(bytes));
|
|
14437
|
+
});
|
|
14438
|
+
/**
|
|
14439
|
+
* Every readable registration.
|
|
14440
|
+
*
|
|
14441
|
+
* A file that will not parse is skipped rather than failing the read. One
|
|
14442
|
+
* corrupt registration should cost that one server, not every server and the
|
|
14443
|
+
* panel that lists them.
|
|
14444
|
+
*/
|
|
14445
|
+
const readAll = Effect.fn("McpRegistry.readAll")(function* () {
|
|
14446
|
+
const entries = yield* fileSystem.readDirectory(root).pipe(Effect.orElseSucceed(() => []));
|
|
14447
|
+
const found = [];
|
|
14448
|
+
for (const entry of [...entries].sort()) {
|
|
14449
|
+
if (!entry.endsWith(".json")) continue;
|
|
14450
|
+
const raw = yield* fileSystem.readFileString(path.join(root, entry)).pipe(Effect.orElseSucceed(() => void 0));
|
|
14451
|
+
if (raw === void 0) continue;
|
|
14452
|
+
const decoded = decodeRegistration$1(raw);
|
|
14453
|
+
if (decoded._tag !== "Success") continue;
|
|
14454
|
+
found.push(decoded.value);
|
|
14455
|
+
}
|
|
14456
|
+
return found;
|
|
14457
|
+
});
|
|
14458
|
+
const list = Effect.gen(function* () {
|
|
14459
|
+
const registrations = yield* readAll();
|
|
14460
|
+
const statuses = [];
|
|
14461
|
+
for (const registration of registrations) {
|
|
14462
|
+
const missingSecrets = [];
|
|
14463
|
+
for (const slot of slotsOf(registration)) {
|
|
14464
|
+
const value = yield* readSecret(registration.name, slot);
|
|
14465
|
+
if (Option.isNone(value)) missingSecrets.push(slot);
|
|
14466
|
+
}
|
|
14467
|
+
statuses.push({
|
|
14468
|
+
registration,
|
|
14469
|
+
missingSecrets,
|
|
14470
|
+
ready: registration.enabled && missingSecrets.length === 0
|
|
14471
|
+
});
|
|
14472
|
+
}
|
|
14473
|
+
return statuses;
|
|
14474
|
+
}).pipe(Effect.provide(services), Effect.catchCause((cause) => Effect.logWarning("mcp registry list failed", { cause }).pipe(Effect.as([]))));
|
|
14475
|
+
const save = (registration) => Effect.gen(function* () {
|
|
14476
|
+
yield* fileSystem.makeDirectory(root, { recursive: true }).pipe(Effect.ignore);
|
|
14477
|
+
yield* writeFileStringAtomically({
|
|
14478
|
+
filePath: fileFor(registration.name),
|
|
14479
|
+
contents: `${encodeRegistration(registration)}\n`
|
|
14480
|
+
});
|
|
14481
|
+
}).pipe(Effect.provide(services), Effect.catchCause((cause) => Effect.logWarning("mcp registry save failed", { cause }).pipe(Effect.asVoid)));
|
|
14482
|
+
const remove = (name) => Effect.gen(function* () {
|
|
14483
|
+
const removed = (yield* readAll()).find((entry) => entry.name === name);
|
|
14484
|
+
yield* fileSystem.remove(fileFor(name), { force: true });
|
|
14485
|
+
for (const slot of removed === void 0 ? [] : slotsOf(removed)) yield* secrets.remove(mcpSecretStorageName({
|
|
14486
|
+
server: name,
|
|
14487
|
+
target: slot.target,
|
|
14488
|
+
key: slot.key
|
|
14489
|
+
})).pipe(Effect.ignore);
|
|
14490
|
+
}).pipe(Effect.provide(services), Effect.catchCause((cause) => Effect.logWarning("mcp registry remove failed", { cause }).pipe(Effect.asVoid)));
|
|
14491
|
+
const setSecret = ({ name, slot, value }) => secrets.set(mcpSecretStorageName({
|
|
14492
|
+
server: name,
|
|
14493
|
+
target: slot.target,
|
|
14494
|
+
key: slot.key
|
|
14495
|
+
}), new TextEncoder().encode(value)).pipe(Effect.catchCause((cause) => Effect.logWarning("mcp secret write failed", { cause }).pipe(Effect.asVoid)));
|
|
14496
|
+
return {
|
|
14497
|
+
list,
|
|
14498
|
+
save,
|
|
14499
|
+
remove,
|
|
14500
|
+
setSecret,
|
|
14501
|
+
resolveForSession: Effect.gen(function* () {
|
|
14502
|
+
const registrations = yield* readAll();
|
|
14503
|
+
const resolved = {};
|
|
14504
|
+
for (const registration of registrations) {
|
|
14505
|
+
if (!registration.enabled) continue;
|
|
14506
|
+
const headers = { ...registration.transport === "stdio" ? {} : registration.headers ?? {} };
|
|
14507
|
+
const env = { ...registration.transport === "stdio" ? registration.env ?? {} : {} };
|
|
14508
|
+
let complete = true;
|
|
14509
|
+
for (const slot of slotsOf(registration)) {
|
|
14510
|
+
const value = yield* readSecret(registration.name, slot);
|
|
14511
|
+
if (Option.isNone(value)) {
|
|
14512
|
+
complete = false;
|
|
14513
|
+
break;
|
|
14514
|
+
}
|
|
14515
|
+
if (slot.target === "header") headers[slot.key] = value.value;
|
|
14516
|
+
else env[slot.key] = value.value;
|
|
14517
|
+
}
|
|
14518
|
+
if (!complete) continue;
|
|
14519
|
+
resolved[registration.name] = registration.transport === "stdio" ? {
|
|
14520
|
+
type: "stdio",
|
|
14521
|
+
command: registration.command,
|
|
14522
|
+
args: registration.args ?? [],
|
|
14523
|
+
env
|
|
14524
|
+
} : {
|
|
14525
|
+
type: registration.transport,
|
|
14526
|
+
url: registration.url,
|
|
14527
|
+
headers
|
|
14528
|
+
};
|
|
14529
|
+
}
|
|
14530
|
+
return resolved;
|
|
14531
|
+
}).pipe(Effect.provide(services), Effect.catchCause((cause) => Effect.logWarning("mcp registry resolve failed", { cause }).pipe(Effect.as({}))))
|
|
14532
|
+
};
|
|
14533
|
+
});
|
|
14534
|
+
const layer$56 = Layer.effect(McpRegistry, make$61);
|
|
14394
14535
|
//#endregion
|
|
14395
14536
|
//#region src/sync/HubLink.ts
|
|
14396
14537
|
/**
|
|
@@ -14487,7 +14628,7 @@ const formatHubLink = (input) => encodeStoredHubLink({
|
|
|
14487
14628
|
shareMode: input.shareMode
|
|
14488
14629
|
});
|
|
14489
14630
|
const fromEnvironment = (environment) => validateHubLink(environment.P4CODE_HUB_URL ?? "", environment.P4CODE_HUB_TOKEN ?? "");
|
|
14490
|
-
const make$
|
|
14631
|
+
const make$60 = Effect.fn("HubLink.make")(function* (environment) {
|
|
14491
14632
|
const secrets = yield* ServerSecretStore;
|
|
14492
14633
|
const env = environment ?? process.env;
|
|
14493
14634
|
const fromEnv = fromEnvironment(env);
|
|
@@ -14553,7 +14694,7 @@ const make$59 = Effect.fn("HubLink.make")(function* (environment) {
|
|
|
14553
14694
|
})
|
|
14554
14695
|
};
|
|
14555
14696
|
});
|
|
14556
|
-
const layer$
|
|
14697
|
+
const layer$55 = Layer.effect(HubLink, make$60());
|
|
14557
14698
|
//#endregion
|
|
14558
14699
|
//#region src/sync/HubAssetClient.ts
|
|
14559
14700
|
/**
|
|
@@ -14586,7 +14727,7 @@ const decodeAssetListPage = Schema$1.decodeUnknownEffect(AssetListPage);
|
|
|
14586
14727
|
const decodeConflictBody = Schema$1.decodeUnknownEffect(ConflictBody);
|
|
14587
14728
|
const decodeAsset = Schema$1.decodeUnknownEffect(AgentAsset);
|
|
14588
14729
|
var HubAssetClient = class extends Context.Service()("@p4code/cli/sync/HubAssetClient") {};
|
|
14589
|
-
const make$
|
|
14730
|
+
const make$59 = Effect.gen(function* () {
|
|
14590
14731
|
const http = yield* HttpClient.HttpClient;
|
|
14591
14732
|
const link = yield* HubLink;
|
|
14592
14733
|
const requireSettings = Effect.gen(function* () {
|
|
@@ -14623,13 +14764,24 @@ const make$58 = Effect.gen(function* () {
|
|
|
14623
14764
|
});
|
|
14624
14765
|
const list = (kind) => Effect.gen(function* () {
|
|
14625
14766
|
const settings = yield* requireSettings;
|
|
14626
|
-
const
|
|
14767
|
+
const query = kind === void 0 ? "" : `?kind=${encodeURIComponent(kind)}`;
|
|
14768
|
+
const response = yield* send("list", settings, HttpClientRequest.get(`${settings.baseUrl}/assets${query}`));
|
|
14627
14769
|
if (response.status !== 200) return yield* new HubUnavailable({
|
|
14628
14770
|
operation: "list",
|
|
14629
14771
|
detail: `status ${response.status}`
|
|
14630
14772
|
});
|
|
14631
14773
|
return (yield* decodeBody("list", decodeAssetListPage, response)).assets;
|
|
14632
14774
|
});
|
|
14775
|
+
const get = ({ kind, name }) => Effect.gen(function* () {
|
|
14776
|
+
const settings = yield* requireSettings;
|
|
14777
|
+
const response = yield* send("get", settings, HttpClientRequest.get(`${settings.baseUrl}/assets/${encodeURIComponent(kind)}/${encodeURIComponent(name)}`));
|
|
14778
|
+
if (response.status === 404) return Option.none();
|
|
14779
|
+
if (response.status !== 200) return yield* new HubUnavailable({
|
|
14780
|
+
operation: "get",
|
|
14781
|
+
detail: `status ${response.status}`
|
|
14782
|
+
});
|
|
14783
|
+
return Option.some(yield* decodeBody("get", decodeAsset, response));
|
|
14784
|
+
});
|
|
14633
14785
|
const write = (input) => Effect.gen(function* () {
|
|
14634
14786
|
const settings = yield* requireSettings;
|
|
14635
14787
|
const response = yield* send("write", settings, HttpClientRequest.bodyJsonUnsafe(HttpClientRequest.put(`${settings.baseUrl}/assets`), input));
|
|
@@ -14642,7 +14794,8 @@ const make$58 = Effect.gen(function* () {
|
|
|
14642
14794
|
});
|
|
14643
14795
|
const remove = ({ kind, name, expectedVersion }) => Effect.gen(function* () {
|
|
14644
14796
|
const settings = yield* requireSettings;
|
|
14645
|
-
const
|
|
14797
|
+
const query = expectedVersion === void 0 ? "" : `?expectedVersion=${expectedVersion}`;
|
|
14798
|
+
const response = yield* send("remove", settings, HttpClientRequest.delete(`${settings.baseUrl}/assets/${encodeURIComponent(kind)}/${encodeURIComponent(name)}${query}`));
|
|
14646
14799
|
if (response.status === 409) return yield* yield* conflictFrom(name, response);
|
|
14647
14800
|
if (response.status !== 204 && response.status !== 404) return yield* new HubUnavailable({
|
|
14648
14801
|
operation: "remove",
|
|
@@ -14651,130 +14804,248 @@ const make$58 = Effect.gen(function* () {
|
|
|
14651
14804
|
});
|
|
14652
14805
|
return {
|
|
14653
14806
|
list,
|
|
14807
|
+
get,
|
|
14654
14808
|
write,
|
|
14655
14809
|
remove
|
|
14656
14810
|
};
|
|
14657
14811
|
});
|
|
14658
|
-
const layer$
|
|
14812
|
+
const layer$54 = Layer.effect(HubAssetClient, make$59);
|
|
14659
14813
|
//#endregion
|
|
14660
|
-
//#region src/
|
|
14814
|
+
//#region src/sync/mcpRegistrationFiles.ts
|
|
14661
14815
|
/**
|
|
14662
|
-
*
|
|
14663
|
-
* current user's home directory. Spawned processes don't get shell
|
|
14664
|
-
* expansion, so env vars like `CODEX_HOME=~/.codex-work` would be passed
|
|
14665
|
-
* verbatim and treated as relative paths by the receiver.
|
|
14816
|
+
* Reading and writing MCP registration files for the sync.
|
|
14666
14817
|
*
|
|
14667
|
-
*
|
|
14668
|
-
*
|
|
14669
|
-
*
|
|
14670
|
-
*
|
|
14671
|
-
*
|
|
14818
|
+
* The third asset driver's filesystem half. Unlike memory, the directory is
|
|
14819
|
+
* p4code's own — nothing else lives in `<stateDir>/mcp` — so there is no
|
|
14820
|
+
* allowlist to police and the rules are about shape rather than about which
|
|
14821
|
+
* neighbours must not be touched.
|
|
14822
|
+
*
|
|
14823
|
+
* **What is not here is the point.** A registration references its bearer
|
|
14824
|
+
* tokens and API keys by slot name; the values live in `ServerSecretStore` and
|
|
14825
|
+
* are never written into these files. So a registration can travel through a
|
|
14826
|
+
* hub that stores asset files verbatim without a working credential leaving the
|
|
14827
|
+
* machine that holds it. This module enforces the file shape; `McpRegistry`
|
|
14828
|
+
* enforces the split.
|
|
14829
|
+
*
|
|
14830
|
+
* @module sync/mcpRegistrationFiles
|
|
14672
14831
|
*/
|
|
14673
|
-
|
|
14674
|
-
|
|
14675
|
-
if (value === "~") return NodeOS.homedir();
|
|
14676
|
-
if (value.startsWith("~/") || value.startsWith("~\\")) return NodePath.join(NodeOS.homedir(), value.slice(2));
|
|
14677
|
-
return value;
|
|
14678
|
-
}
|
|
14679
|
-
//#endregion
|
|
14680
|
-
//#region src/provider/Drivers/ClaudeSkills.ts
|
|
14832
|
+
const RegistrationFromJson = Schema$1.fromJsonString(McpServerRegistration);
|
|
14833
|
+
const decodeRegistration = Schema$1.decodeUnknownExit(RegistrationFromJson);
|
|
14681
14834
|
/**
|
|
14682
|
-
*
|
|
14835
|
+
* Every registration in the directory, as syncable assets.
|
|
14683
14836
|
*
|
|
14684
|
-
*
|
|
14685
|
-
*
|
|
14686
|
-
*
|
|
14687
|
-
* skills only as slash commands without their filesystem paths, so the
|
|
14688
|
-
* provider snapshot scans the same locations directly, mirroring how the
|
|
14689
|
-
* Codex app-server reports its skills.
|
|
14837
|
+
* The asset name is the filename — `linear.json` — for the same reason memory's
|
|
14838
|
+
* is: one string is both the hub key and the path, so there is no mapping to
|
|
14839
|
+
* get wrong in one direction and not the other.
|
|
14690
14840
|
*
|
|
14691
|
-
*
|
|
14841
|
+
* A file that will not parse is refused rather than skipped. Unlike a stray
|
|
14842
|
+
* `.DS_Store`, a malformed `.json` in this directory is something p4code wrote
|
|
14843
|
+
* or something the sync brought in, so it is a fault worth reporting rather
|
|
14844
|
+
* than noise to ignore.
|
|
14692
14845
|
*/
|
|
14693
|
-
const
|
|
14694
|
-
|
|
14695
|
-
const
|
|
14696
|
-
|
|
14697
|
-
|
|
14698
|
-
|
|
14699
|
-
|
|
14700
|
-
|
|
14701
|
-
|
|
14846
|
+
const readMcpRegistrations = Effect.fn("mcpRegistrationFiles.read")(function* (registrationsRoot) {
|
|
14847
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
14848
|
+
const path = yield* Path.Path;
|
|
14849
|
+
const entries = yield* fileSystem.readDirectory(registrationsRoot).pipe(Effect.orElseSucceed(() => []));
|
|
14850
|
+
const skills = [];
|
|
14851
|
+
const refused = [];
|
|
14852
|
+
for (const entry of [...entries].sort()) {
|
|
14853
|
+
if (!entry.endsWith(".json") || entry.startsWith(".")) continue;
|
|
14854
|
+
const content = yield* fileSystem.readFileString(path.join(registrationsRoot, entry)).pipe(Effect.orElseSucceed(() => void 0));
|
|
14855
|
+
if (content === void 0) {
|
|
14856
|
+
refused.push({
|
|
14857
|
+
name: entry,
|
|
14858
|
+
reason: "unreadable"
|
|
14859
|
+
});
|
|
14860
|
+
continue;
|
|
14861
|
+
}
|
|
14862
|
+
if (decodeRegistration(content)._tag !== "Success") {
|
|
14863
|
+
refused.push({
|
|
14864
|
+
name: entry,
|
|
14865
|
+
reason: "unreadable"
|
|
14866
|
+
});
|
|
14867
|
+
continue;
|
|
14868
|
+
}
|
|
14869
|
+
skills.push({
|
|
14870
|
+
name: entry,
|
|
14871
|
+
files: [{
|
|
14872
|
+
path: entry,
|
|
14873
|
+
content
|
|
14874
|
+
}]
|
|
14875
|
+
});
|
|
14702
14876
|
}
|
|
14703
|
-
if (typeof parsed !== "object" || parsed === null) return { kind: "malformed" };
|
|
14704
|
-
const record = parsed;
|
|
14705
|
-
const name = typeof record.name === "string" ? record.name.trim() : "";
|
|
14706
|
-
const description = typeof record.description === "string" ? record.description.trim() : "";
|
|
14707
14877
|
return {
|
|
14708
|
-
|
|
14709
|
-
|
|
14710
|
-
...description ? { description } : {}
|
|
14878
|
+
skills,
|
|
14879
|
+
refused
|
|
14711
14880
|
};
|
|
14712
|
-
}
|
|
14881
|
+
});
|
|
14713
14882
|
/**
|
|
14714
|
-
*
|
|
14715
|
-
*
|
|
14716
|
-
*
|
|
14717
|
-
*
|
|
14883
|
+
* Write a registration that arrived from the hub.
|
|
14884
|
+
*
|
|
14885
|
+
* Refused unless it is exactly one file whose path is the asset's own name and
|
|
14886
|
+
* whose content decodes as a registration. The last check is what stops another
|
|
14887
|
+
* machine — or a hub row edited by hand — from installing something this server
|
|
14888
|
+
* would then feed to an agent as a tool source.
|
|
14718
14889
|
*/
|
|
14719
|
-
const
|
|
14890
|
+
const writeMcpRegistration = Effect.fn("mcpRegistrationFiles.write")(function* (input) {
|
|
14891
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
14720
14892
|
const path = yield* Path.Path;
|
|
14721
|
-
const
|
|
14722
|
-
if (
|
|
14723
|
-
|
|
14724
|
-
|
|
14725
|
-
return
|
|
14893
|
+
const file = input.files[0];
|
|
14894
|
+
if (!input.name.endsWith(".json") || input.files.length !== 1 || file === void 0 || file.path !== input.name) return "refused";
|
|
14895
|
+
if (decodeRegistration(file.content)._tag !== "Success") return "refused";
|
|
14896
|
+
yield* fileSystem.makeDirectory(input.registrationsRoot, { recursive: true }).pipe(Effect.ignore);
|
|
14897
|
+
return yield* writeFileStringAtomically({
|
|
14898
|
+
filePath: path.join(input.registrationsRoot, input.name),
|
|
14899
|
+
contents: file.content
|
|
14900
|
+
}).pipe(Effect.as("written"), Effect.orElseSucceed(() => "failed"));
|
|
14901
|
+
});
|
|
14902
|
+
const removeMcpRegistration = Effect.fn("mcpRegistrationFiles.remove")(function* (input) {
|
|
14903
|
+
if (!input.name.endsWith(".json")) return;
|
|
14904
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
14905
|
+
const path = yield* Path.Path;
|
|
14906
|
+
yield* fileSystem.remove(path.join(input.registrationsRoot, input.name), { force: true });
|
|
14726
14907
|
});
|
|
14908
|
+
//#endregion
|
|
14909
|
+
//#region src/sync/memoryFile.ts
|
|
14727
14910
|
/**
|
|
14728
|
-
*
|
|
14911
|
+
* Reading and writing user-scope memory files.
|
|
14729
14912
|
*
|
|
14730
|
-
*
|
|
14731
|
-
*
|
|
14732
|
-
*
|
|
14913
|
+
* Memory is the instruction file an agent reads before anything else, so a
|
|
14914
|
+
* machine whose `CLAUDE.md` differs is a machine whose agent behaves
|
|
14915
|
+
* differently — which is the whole reason the hub exists. Unlike a skill it is
|
|
14916
|
+
* a single file, and it sits *directly* in the provider's config directory
|
|
14917
|
+
* rather than in a subdirectory the sync could own outright.
|
|
14918
|
+
*
|
|
14919
|
+
* That last part is the entire design constraint. `<configDir>` also holds
|
|
14920
|
+
* `settings.json`, `.claude.json` and `.credentials.json`. "Write the file this
|
|
14921
|
+
* asset names" is therefore not safe on its own: an authorized machine — or a
|
|
14922
|
+
* hub row someone wrote by hand — could name `settings.json` and have every
|
|
14923
|
+
* other machine overwrite its own configuration on the next sync. The path
|
|
14924
|
+
* schema does not save us here, because `settings.json` is a perfectly valid
|
|
14925
|
+
* asset path.
|
|
14926
|
+
*
|
|
14927
|
+
* So the allowlist below is the boundary, and it is checked on the way in as
|
|
14928
|
+
* well as on the way out: a file this module will not write is also one it will
|
|
14929
|
+
* not read, so nothing outside it can reach the hub in the first place.
|
|
14930
|
+
*
|
|
14931
|
+
* @module sync/memoryFile
|
|
14733
14932
|
*/
|
|
14734
|
-
const resolveClaudeUserSkillsDir = Effect.fn("resolveClaudeUserSkillsDir")(function* (config, environment, cwd) {
|
|
14735
|
-
const path = yield* Path.Path;
|
|
14736
|
-
const configDirPath = yield* resolveClaudeConfigDirPath(config, environment ?? process.env, cwd);
|
|
14737
|
-
return path.join(configDirPath, "skills");
|
|
14738
|
-
});
|
|
14739
14933
|
/**
|
|
14740
|
-
*
|
|
14741
|
-
*
|
|
14742
|
-
*
|
|
14743
|
-
*
|
|
14744
|
-
*
|
|
14934
|
+
* The only files in the provider config directory this sync will ever touch.
|
|
14935
|
+
*
|
|
14936
|
+
* Both names, not one: Claude Code reads `CLAUDE.md`, Codex reads `AGENTS.md`,
|
|
14937
|
+
* and a machine that syncs both is the ordinary case rather than a special one.
|
|
14938
|
+
* Nothing else in that directory is memory, and everything else in it is either
|
|
14939
|
+
* a credential or a per-machine setting.
|
|
14940
|
+
*
|
|
14941
|
+
* The asset's name *is* the filename. That keeps the hub key and the on-disk
|
|
14942
|
+
* path the same string, so there is no mapping table to get wrong, and it makes
|
|
14943
|
+
* the allowlist checkable at the boundary rather than after a join.
|
|
14745
14944
|
*/
|
|
14746
|
-
const
|
|
14945
|
+
const MEMORY_FILE_NAMES = ["CLAUDE.md", "AGENTS.md"];
|
|
14946
|
+
/**
|
|
14947
|
+
* Whether this name is one of the memory files.
|
|
14948
|
+
*
|
|
14949
|
+
* Exact match, deliberately. Not a suffix test and not case-insensitive: a
|
|
14950
|
+
* check that accepted `claude.md` on a case-insensitive filesystem and rejected
|
|
14951
|
+
* it on a case-sensitive one would make the same hub row materialize on one
|
|
14952
|
+
* machine and refuse on another.
|
|
14953
|
+
*/
|
|
14954
|
+
const isMemoryAssetName = (name) => MEMORY_FILE_NAMES.includes(name);
|
|
14955
|
+
/** The same test the skill reader uses: a NUL is what breaks a JSON text column. */
|
|
14956
|
+
const looksBinary$1 = (content) => content.includes("\0");
|
|
14957
|
+
/**
|
|
14958
|
+
* Read whichever memory files exist in this machine's config directory.
|
|
14959
|
+
*
|
|
14960
|
+
* Only the allowlisted names are even stat'd. A missing file is not a refusal:
|
|
14961
|
+
* most machines have a `CLAUDE.md` and no `AGENTS.md`, and reporting the second
|
|
14962
|
+
* as a problem would be reporting the normal case.
|
|
14963
|
+
*/
|
|
14964
|
+
const readMemoryFiles = Effect.fn("memoryFile.readMemoryFiles")(function* (memoryRoot) {
|
|
14747
14965
|
const fileSystem = yield* FileSystem.FileSystem;
|
|
14748
14966
|
const path = yield* Path.Path;
|
|
14749
|
-
const
|
|
14750
|
-
const
|
|
14751
|
-
|
|
14752
|
-
|
|
14753
|
-
|
|
14754
|
-
|
|
14755
|
-
|
|
14756
|
-
|
|
14757
|
-
const skillsByName = /* @__PURE__ */ new Map();
|
|
14758
|
-
for (const root of roots) {
|
|
14759
|
-
const entries = yield* fileSystem.readDirectory(root.directory).pipe(Effect.orElseSucceed(() => []));
|
|
14760
|
-
for (const entry of [...entries].sort()) {
|
|
14761
|
-
const skillPath = path.join(root.directory, entry, "SKILL.md");
|
|
14762
|
-
const contents = yield* fileSystem.readFileString(skillPath).pipe(Effect.orElseSucceed(() => void 0));
|
|
14763
|
-
if (contents === void 0) continue;
|
|
14764
|
-
const frontmatter = parseSkillFrontmatter(contents);
|
|
14765
|
-
if (frontmatter.kind === "malformed") continue;
|
|
14766
|
-
const name = (frontmatter.kind === "parsed" ? frontmatter.name : void 0) ?? entry.trim();
|
|
14767
|
-
if (!name) continue;
|
|
14768
|
-
skillsByName.set(name, {
|
|
14967
|
+
const skills = [];
|
|
14968
|
+
const refused = [];
|
|
14969
|
+
for (const name of MEMORY_FILE_NAMES) {
|
|
14970
|
+
const absolute = path.join(memoryRoot, name);
|
|
14971
|
+
if ((yield* fileSystem.stat(absolute).pipe(Effect.orElseSucceed(() => void 0)))?.type !== "File") continue;
|
|
14972
|
+
const content = yield* fileSystem.readFileString(absolute).pipe(Effect.orElseSucceed(() => void 0));
|
|
14973
|
+
if (content === void 0) {
|
|
14974
|
+
refused.push({
|
|
14769
14975
|
name,
|
|
14770
|
-
|
|
14771
|
-
|
|
14772
|
-
|
|
14773
|
-
|
|
14976
|
+
reason: "unreadable"
|
|
14977
|
+
});
|
|
14978
|
+
continue;
|
|
14979
|
+
}
|
|
14980
|
+
if (looksBinary$1(content)) {
|
|
14981
|
+
refused.push({
|
|
14982
|
+
name,
|
|
14983
|
+
reason: "binary-file"
|
|
14984
|
+
});
|
|
14985
|
+
continue;
|
|
14986
|
+
}
|
|
14987
|
+
if (content.length > 524288) {
|
|
14988
|
+
refused.push({
|
|
14989
|
+
name,
|
|
14990
|
+
reason: "file-too-large"
|
|
14991
|
+
});
|
|
14992
|
+
continue;
|
|
14993
|
+
}
|
|
14994
|
+
const files = [{
|
|
14995
|
+
path: name,
|
|
14996
|
+
content
|
|
14997
|
+
}];
|
|
14998
|
+
if (agentAssetTotalBytes(files) > 4194304) {
|
|
14999
|
+
refused.push({
|
|
15000
|
+
name,
|
|
15001
|
+
reason: "asset-too-large"
|
|
14774
15002
|
});
|
|
15003
|
+
continue;
|
|
14775
15004
|
}
|
|
15005
|
+
skills.push({
|
|
15006
|
+
name,
|
|
15007
|
+
files
|
|
15008
|
+
});
|
|
14776
15009
|
}
|
|
14777
|
-
return
|
|
15010
|
+
return {
|
|
15011
|
+
skills,
|
|
15012
|
+
refused
|
|
15013
|
+
};
|
|
15014
|
+
});
|
|
15015
|
+
/**
|
|
15016
|
+
* Whether an asset from the hub is a memory file this machine will write.
|
|
15017
|
+
*
|
|
15018
|
+
* Both halves are checked. The name has to be allowlisted, and the files have
|
|
15019
|
+
* to be exactly the one file that name refers to — an asset named `CLAUDE.md`
|
|
15020
|
+
* carrying a second file called `settings.json` would otherwise write it, which
|
|
15021
|
+
* is the same escape by a longer route.
|
|
15022
|
+
*/
|
|
15023
|
+
const isWritableMemoryAsset = (input) => isMemoryAssetName(input.name) && input.files.length === 1 && input.files[0]?.path === input.name;
|
|
15024
|
+
/**
|
|
15025
|
+
* Write a memory file, or refuse.
|
|
15026
|
+
*
|
|
15027
|
+
* Answers rather than failing, because the caller's response to "this asset is
|
|
15028
|
+
* not writable here" is to report it alongside the other refusals, not to
|
|
15029
|
+
* abandon the sync. Written atomically: memory is read at the start of every
|
|
15030
|
+
* agent turn, and a half-written instruction file is instructions with the end
|
|
15031
|
+
* missing, which an agent will follow without noticing.
|
|
15032
|
+
*/
|
|
15033
|
+
const writeMemoryFile = Effect.fn("memoryFile.writeMemoryFile")(function* (input) {
|
|
15034
|
+
if (!isWritableMemoryAsset(input)) return "refused";
|
|
15035
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
15036
|
+
const path = yield* Path.Path;
|
|
15037
|
+
const contents = input.files[0]?.content ?? "";
|
|
15038
|
+
yield* fileSystem.makeDirectory(input.memoryRoot, { recursive: true }).pipe(Effect.ignore);
|
|
15039
|
+
return yield* writeFileStringAtomically({
|
|
15040
|
+
filePath: path.join(input.memoryRoot, input.name),
|
|
15041
|
+
contents
|
|
15042
|
+
}).pipe(Effect.as("written"), Effect.orElseSucceed(() => "failed"));
|
|
15043
|
+
});
|
|
15044
|
+
const removeMemoryFile = Effect.fn("memoryFile.removeMemoryFile")(function* (input) {
|
|
15045
|
+
if (!isMemoryAssetName(input.name)) return;
|
|
15046
|
+
const fileSystem = yield* FileSystem.FileSystem;
|
|
15047
|
+
const path = yield* Path.Path;
|
|
15048
|
+
yield* fileSystem.remove(path.join(input.memoryRoot, input.name), { force: true });
|
|
14778
15049
|
});
|
|
14779
15050
|
//#endregion
|
|
14780
15051
|
//#region src/sync/skillManifest.ts
|
|
@@ -15151,10 +15422,10 @@ function planSkillSync(input) {
|
|
|
15151
15422
|
};
|
|
15152
15423
|
}
|
|
15153
15424
|
//#endregion
|
|
15154
|
-
//#region src/sync/
|
|
15425
|
+
//#region src/sync/AssetSync.ts
|
|
15155
15426
|
const EMPTY_REPORT = {
|
|
15156
15427
|
ran: false,
|
|
15157
|
-
|
|
15428
|
+
roots: [],
|
|
15158
15429
|
materialized: [],
|
|
15159
15430
|
published: [],
|
|
15160
15431
|
removed: [],
|
|
@@ -15163,13 +15434,12 @@ const EMPTY_REPORT = {
|
|
|
15163
15434
|
refused: [],
|
|
15164
15435
|
unavailable: null
|
|
15165
15436
|
};
|
|
15166
|
-
var
|
|
15167
|
-
const make$
|
|
15437
|
+
var AssetSync = class extends Context.Service()("@p4code/cli/sync/AssetSync") {};
|
|
15438
|
+
const make$58 = Effect.gen(function* () {
|
|
15168
15439
|
const client = yield* HubAssetClient;
|
|
15169
15440
|
const link = yield* HubLink;
|
|
15170
15441
|
const settingsStore = yield* ServerSettingsService;
|
|
15171
15442
|
const config = yield* ServerConfig$1;
|
|
15172
|
-
const fileSystem = yield* FileSystem.FileSystem;
|
|
15173
15443
|
const path = yield* Path.Path;
|
|
15174
15444
|
/**
|
|
15175
15445
|
* Captured here rather than left in each method's requirements, so the
|
|
@@ -15178,267 +15448,446 @@ const make$57 = Effect.gen(function* () {
|
|
|
15178
15448
|
* neither should have to carry a filesystem to ask for a sync.
|
|
15179
15449
|
*/
|
|
15180
15450
|
const services = yield* Effect.context();
|
|
15451
|
+
/**
|
|
15452
|
+
* Still `skill-sync-manifest.json`.
|
|
15453
|
+
*
|
|
15454
|
+
* Renaming it would be tidier and would also declare every skill this machine
|
|
15455
|
+
* manages to be unmanaged, which on the next sync means re-publishing all of
|
|
15456
|
+
* them into collisions. The manifest is keyed by root directory, and the two
|
|
15457
|
+
* kinds have different roots, so one file holds both without ambiguity.
|
|
15458
|
+
*/
|
|
15181
15459
|
const manifestPath = path.join(config.stateDir, "skill-sync-manifest.json");
|
|
15182
|
-
const
|
|
15183
|
-
return
|
|
15460
|
+
const claudeHome = Effect.fn("AssetSync.claudeHome")(function* () {
|
|
15461
|
+
return { homePath: (yield* settingsStore.getSettings.pipe(Effect.orElseSucceed(() => void 0)))?.providers.claudeAgent.homePath ?? "" };
|
|
15184
15462
|
});
|
|
15463
|
+
const drivers = [
|
|
15464
|
+
{
|
|
15465
|
+
kind: "skill",
|
|
15466
|
+
resolveRoot: Effect.gen(function* () {
|
|
15467
|
+
return yield* resolveClaudeUserSkillsDir(yield* claudeHome());
|
|
15468
|
+
}),
|
|
15469
|
+
read: (root) => readSkillDirectory(root),
|
|
15470
|
+
write: ({ root, name, files }) => writeSkillDirectory({
|
|
15471
|
+
skillsRoot: root,
|
|
15472
|
+
name,
|
|
15473
|
+
files
|
|
15474
|
+
}).pipe(Effect.as("written"), Effect.orElseSucceed(() => "failed")),
|
|
15475
|
+
remove: ({ root, name }) => removeSkillDirectory({
|
|
15476
|
+
skillsRoot: root,
|
|
15477
|
+
name
|
|
15478
|
+
}).pipe(Effect.ignore)
|
|
15479
|
+
},
|
|
15480
|
+
{
|
|
15481
|
+
kind: "memory",
|
|
15482
|
+
resolveRoot: Effect.gen(function* () {
|
|
15483
|
+
return yield* resolveClaudeConfigDirPath(yield* claudeHome(), process.env);
|
|
15484
|
+
}),
|
|
15485
|
+
read: (root) => readMemoryFiles(root),
|
|
15486
|
+
write: ({ root, name, files }) => writeMemoryFile({
|
|
15487
|
+
memoryRoot: root,
|
|
15488
|
+
name,
|
|
15489
|
+
files
|
|
15490
|
+
}).pipe(Effect.map((outcome) => outcome)),
|
|
15491
|
+
remove: ({ root, name }) => removeMemoryFile({
|
|
15492
|
+
memoryRoot: root,
|
|
15493
|
+
name
|
|
15494
|
+
}).pipe(Effect.ignore)
|
|
15495
|
+
},
|
|
15496
|
+
{
|
|
15497
|
+
kind: "mcp",
|
|
15498
|
+
resolveRoot: Effect.succeed(path.join(config.stateDir, "mcp")),
|
|
15499
|
+
read: (root) => readMcpRegistrations(root),
|
|
15500
|
+
write: ({ root, name, files }) => writeMcpRegistration({
|
|
15501
|
+
registrationsRoot: root,
|
|
15502
|
+
name,
|
|
15503
|
+
files
|
|
15504
|
+
}),
|
|
15505
|
+
remove: ({ root, name }) => removeMcpRegistration({
|
|
15506
|
+
registrationsRoot: root,
|
|
15507
|
+
name
|
|
15508
|
+
}).pipe(Effect.ignore)
|
|
15509
|
+
}
|
|
15510
|
+
];
|
|
15511
|
+
const driverFor = (kind) => drivers.find((driver) => driver.kind === kind);
|
|
15185
15512
|
const isConfigured = link.current.pipe(Effect.map((state) => Option.isSome(state.settings)));
|
|
15513
|
+
/**
|
|
15514
|
+
* The three views for one kind, gathered once.
|
|
15515
|
+
*
|
|
15516
|
+
* `status` and `run` both need exactly this and disagreeing about it is how a
|
|
15517
|
+
* panel comes to show something the next sync does not do.
|
|
15518
|
+
*/
|
|
15519
|
+
const viewsFor = Effect.fn("AssetSync.viewsFor")(function* (driver, hubAssets) {
|
|
15520
|
+
const root = yield* driver.resolveRoot;
|
|
15521
|
+
const hub = hubAssets.filter((asset) => asset.kind === driver.kind);
|
|
15522
|
+
if (root === null) return {
|
|
15523
|
+
root,
|
|
15524
|
+
hub,
|
|
15525
|
+
local: [],
|
|
15526
|
+
refused: [],
|
|
15527
|
+
managed: []
|
|
15528
|
+
};
|
|
15529
|
+
const local = yield* driver.read(root);
|
|
15530
|
+
const manifest = yield* readSkillManifest(manifestPath);
|
|
15531
|
+
return {
|
|
15532
|
+
root,
|
|
15533
|
+
hub,
|
|
15534
|
+
local: local.skills,
|
|
15535
|
+
refused: local.refused,
|
|
15536
|
+
managed: entriesFor(manifest, root)
|
|
15537
|
+
};
|
|
15538
|
+
});
|
|
15539
|
+
const rootsOf = Effect.fn("AssetSync.rootsOf")(function* () {
|
|
15540
|
+
const roots = [];
|
|
15541
|
+
for (const driver of drivers) roots.push({
|
|
15542
|
+
kind: driver.kind,
|
|
15543
|
+
path: yield* driver.resolveRoot
|
|
15544
|
+
});
|
|
15545
|
+
return roots;
|
|
15546
|
+
});
|
|
15186
15547
|
const run = Effect.gen(function* () {
|
|
15187
15548
|
if (!(yield* isConfigured)) return EMPTY_REPORT;
|
|
15188
|
-
const
|
|
15189
|
-
const
|
|
15190
|
-
if (Option.isNone(
|
|
15549
|
+
const roots = yield* rootsOf();
|
|
15550
|
+
const hubAssets = yield* client.list().pipe(Effect.option);
|
|
15551
|
+
if (Option.isNone(hubAssets)) return {
|
|
15191
15552
|
...EMPTY_REPORT,
|
|
15192
15553
|
ran: true,
|
|
15193
|
-
|
|
15554
|
+
roots,
|
|
15194
15555
|
unavailable: "hub is unreachable"
|
|
15195
15556
|
};
|
|
15196
|
-
const local = yield* readSkillDirectory(skillsRoot);
|
|
15197
|
-
const manifest = yield* readSkillManifest(manifestPath);
|
|
15198
|
-
const plan = planSkillSync({
|
|
15199
|
-
hub: hub.value,
|
|
15200
|
-
local: local.skills,
|
|
15201
|
-
manifest: entriesFor(manifest, skillsRoot)
|
|
15202
|
-
});
|
|
15203
|
-
const entries = new Map(entriesFor(manifest, skillsRoot).map((entry) => [entry.name, entry]));
|
|
15204
15557
|
const materialized = [];
|
|
15205
15558
|
const published = [];
|
|
15206
15559
|
const removed = [];
|
|
15207
|
-
const
|
|
15208
|
-
|
|
15209
|
-
|
|
15210
|
-
|
|
15211
|
-
|
|
15212
|
-
|
|
15213
|
-
|
|
15214
|
-
|
|
15215
|
-
|
|
15216
|
-
|
|
15217
|
-
|
|
15218
|
-
|
|
15219
|
-
|
|
15220
|
-
|
|
15221
|
-
|
|
15222
|
-
|
|
15223
|
-
|
|
15224
|
-
|
|
15225
|
-
|
|
15226
|
-
|
|
15227
|
-
|
|
15228
|
-
|
|
15229
|
-
|
|
15230
|
-
|
|
15560
|
+
const unmanaged = [];
|
|
15561
|
+
const conflicts = [];
|
|
15562
|
+
const refused = [];
|
|
15563
|
+
const shareMode = (yield* link.current).shareMode;
|
|
15564
|
+
for (const driver of drivers) {
|
|
15565
|
+
const views = yield* viewsFor(driver, hubAssets.value);
|
|
15566
|
+
const kind = driver.kind;
|
|
15567
|
+
for (const entry of views.refused) refused.push({
|
|
15568
|
+
kind,
|
|
15569
|
+
name: entry.name,
|
|
15570
|
+
reason: entry.reason
|
|
15571
|
+
});
|
|
15572
|
+
if (views.root === null) continue;
|
|
15573
|
+
const root = views.root;
|
|
15574
|
+
const plan = planSkillSync({
|
|
15575
|
+
hub: views.hub,
|
|
15576
|
+
local: views.local,
|
|
15577
|
+
manifest: views.managed
|
|
15578
|
+
});
|
|
15579
|
+
const entries = new Map(views.managed.map((entry) => [entry.name, entry]));
|
|
15580
|
+
/** One manifest for the whole run, re-read per kind so roots do not clobber. */
|
|
15581
|
+
const commit = Effect.fn("AssetSync.commit")(function* () {
|
|
15582
|
+
const manifest = yield* readSkillManifest(manifestPath);
|
|
15583
|
+
yield* writeSkillManifest(manifestPath, withEntriesFor(manifest, root, [...entries.values()]));
|
|
15584
|
+
});
|
|
15585
|
+
for (const action of plan.actions) switch (action._tag) {
|
|
15586
|
+
case "materialize": {
|
|
15587
|
+
const outcome = yield* driver.write({
|
|
15588
|
+
root,
|
|
15589
|
+
name: action.asset.name,
|
|
15590
|
+
files: action.asset.files
|
|
15591
|
+
});
|
|
15592
|
+
if (outcome !== "written") {
|
|
15593
|
+
refused.push({
|
|
15594
|
+
kind,
|
|
15595
|
+
name: action.asset.name,
|
|
15596
|
+
reason: outcome === "refused" ? "not-writable-here" : "write-failed"
|
|
15597
|
+
});
|
|
15598
|
+
break;
|
|
15599
|
+
}
|
|
15600
|
+
entries.set(action.asset.name, {
|
|
15601
|
+
name: action.asset.name,
|
|
15602
|
+
version: action.asset.version,
|
|
15603
|
+
digest: digestSkillFiles(action.asset.files)
|
|
15604
|
+
});
|
|
15605
|
+
materialized.push({
|
|
15606
|
+
kind,
|
|
15607
|
+
name: action.asset.name
|
|
15608
|
+
});
|
|
15609
|
+
break;
|
|
15610
|
+
}
|
|
15611
|
+
case "publish": {
|
|
15612
|
+
const written = yield* client.write({
|
|
15613
|
+
kind,
|
|
15614
|
+
name: action.name,
|
|
15615
|
+
files: action.files,
|
|
15616
|
+
expectedVersion: action.expectedVersion
|
|
15617
|
+
}).pipe(Effect.option);
|
|
15618
|
+
if (Option.isNone(written)) {
|
|
15619
|
+
conflicts.push({
|
|
15620
|
+
kind,
|
|
15621
|
+
name: action.name,
|
|
15622
|
+
reason: "edited-both-sides"
|
|
15623
|
+
});
|
|
15624
|
+
break;
|
|
15625
|
+
}
|
|
15626
|
+
entries.set(action.name, {
|
|
15231
15627
|
name: action.name,
|
|
15232
|
-
|
|
15628
|
+
version: written.value.version,
|
|
15629
|
+
digest: digestSkillFiles(written.value.files)
|
|
15630
|
+
});
|
|
15631
|
+
published.push({
|
|
15632
|
+
kind,
|
|
15633
|
+
name: action.name
|
|
15233
15634
|
});
|
|
15234
15635
|
break;
|
|
15235
15636
|
}
|
|
15236
|
-
|
|
15237
|
-
|
|
15238
|
-
|
|
15239
|
-
|
|
15240
|
-
|
|
15241
|
-
|
|
15242
|
-
|
|
15637
|
+
case "removeLocal":
|
|
15638
|
+
yield* driver.remove({
|
|
15639
|
+
root,
|
|
15640
|
+
name: action.name
|
|
15641
|
+
});
|
|
15642
|
+
entries.delete(action.name);
|
|
15643
|
+
removed.push({
|
|
15644
|
+
kind,
|
|
15645
|
+
name: action.name
|
|
15646
|
+
});
|
|
15647
|
+
break;
|
|
15648
|
+
case "forget":
|
|
15649
|
+
entries.delete(action.name);
|
|
15650
|
+
break;
|
|
15243
15651
|
}
|
|
15244
|
-
|
|
15245
|
-
|
|
15246
|
-
|
|
15247
|
-
|
|
15248
|
-
|
|
15249
|
-
|
|
15250
|
-
|
|
15251
|
-
|
|
15252
|
-
|
|
15253
|
-
|
|
15254
|
-
|
|
15255
|
-
|
|
15256
|
-
|
|
15257
|
-
const shareMode = (yield* link.current).shareMode;
|
|
15258
|
-
const autoShared = [];
|
|
15259
|
-
if (shareMode === "all") {
|
|
15260
|
-
for (const name of plan.unmanaged) {
|
|
15261
|
-
const skill = local.skills.find((entry) => entry.name === name);
|
|
15262
|
-
if (skill === void 0) continue;
|
|
15263
|
-
const written = yield* client.write({
|
|
15264
|
-
kind: "skill",
|
|
15265
|
-
name,
|
|
15266
|
-
files: skill.files
|
|
15267
|
-
}).pipe(Effect.option);
|
|
15268
|
-
if (Option.isNone(written)) {
|
|
15269
|
-
conflicts.push({
|
|
15652
|
+
for (const conflict of plan.conflicts) conflicts.push({
|
|
15653
|
+
kind,
|
|
15654
|
+
name: conflict.name,
|
|
15655
|
+
reason: conflict.reason
|
|
15656
|
+
});
|
|
15657
|
+
yield* commit();
|
|
15658
|
+
const autoShared = /* @__PURE__ */ new Set();
|
|
15659
|
+
if (shareMode === "all") {
|
|
15660
|
+
for (const name of plan.unmanaged) {
|
|
15661
|
+
const local = views.local.find((entry) => entry.name === name);
|
|
15662
|
+
if (local === void 0) continue;
|
|
15663
|
+
const written = yield* client.write({
|
|
15664
|
+
kind,
|
|
15270
15665
|
name,
|
|
15271
|
-
|
|
15666
|
+
files: local.files
|
|
15667
|
+
}).pipe(Effect.option);
|
|
15668
|
+
if (Option.isNone(written)) {
|
|
15669
|
+
conflicts.push({
|
|
15670
|
+
kind,
|
|
15671
|
+
name,
|
|
15672
|
+
reason: "unmanaged-local-collision"
|
|
15673
|
+
});
|
|
15674
|
+
continue;
|
|
15675
|
+
}
|
|
15676
|
+
entries.set(name, {
|
|
15677
|
+
name,
|
|
15678
|
+
version: written.value.version,
|
|
15679
|
+
digest: digestSkillFiles(written.value.files)
|
|
15272
15680
|
});
|
|
15273
|
-
|
|
15681
|
+
published.push({
|
|
15682
|
+
kind,
|
|
15683
|
+
name
|
|
15684
|
+
});
|
|
15685
|
+
autoShared.add(name);
|
|
15274
15686
|
}
|
|
15275
|
-
|
|
15276
|
-
name,
|
|
15277
|
-
version: written.value.version,
|
|
15278
|
-
digest: digestSkillFiles(written.value.files)
|
|
15279
|
-
});
|
|
15280
|
-
autoShared.push(name);
|
|
15687
|
+
yield* commit();
|
|
15281
15688
|
}
|
|
15282
|
-
|
|
15689
|
+
for (const name of plan.unmanaged) if (!autoShared.has(name)) unmanaged.push({
|
|
15690
|
+
kind,
|
|
15691
|
+
name
|
|
15692
|
+
});
|
|
15283
15693
|
}
|
|
15284
15694
|
return {
|
|
15285
15695
|
ran: true,
|
|
15286
|
-
|
|
15696
|
+
roots,
|
|
15287
15697
|
materialized,
|
|
15288
|
-
published
|
|
15698
|
+
published,
|
|
15289
15699
|
removed,
|
|
15290
15700
|
conflicts,
|
|
15291
|
-
unmanaged
|
|
15292
|
-
refused
|
|
15701
|
+
unmanaged,
|
|
15702
|
+
refused,
|
|
15293
15703
|
unavailable: null
|
|
15294
15704
|
};
|
|
15295
|
-
}).pipe(Effect.provide(services), Effect.catchCause((cause) => Effect.logWarning("
|
|
15705
|
+
}).pipe(Effect.provide(services), Effect.catchCause((cause) => Effect.logWarning("asset sync failed", { cause }).pipe(Effect.as(EMPTY_REPORT))));
|
|
15296
15706
|
/**
|
|
15297
|
-
* The same
|
|
15707
|
+
* The same views the sync reads, rendered rather than acted on.
|
|
15298
15708
|
*
|
|
15299
15709
|
* Deliberately its own pass instead of a dry-run flag on `run`: a dry run
|
|
15300
15710
|
* sharing the executing code path is one `if` away from writing during a
|
|
15301
15711
|
* preview, and this is the call a panel makes every time it opens.
|
|
15302
15712
|
*/
|
|
15303
|
-
const
|
|
15713
|
+
const assetStatus = Effect.gen(function* () {
|
|
15304
15714
|
const state = yield* link.current;
|
|
15305
|
-
const
|
|
15306
|
-
const local = yield* readSkillDirectory(skillsRoot);
|
|
15307
|
-
const managed = entriesFor(yield* readSkillManifest(manifestPath), skillsRoot);
|
|
15715
|
+
const roots = yield* rootsOf();
|
|
15308
15716
|
const base = {
|
|
15309
15717
|
syncMode: state.syncMode,
|
|
15310
15718
|
shareMode: state.shareMode,
|
|
15311
15719
|
source: state.source,
|
|
15312
|
-
|
|
15313
|
-
refused: local.refused
|
|
15720
|
+
roots
|
|
15314
15721
|
};
|
|
15315
|
-
|
|
15316
|
-
|
|
15317
|
-
|
|
15318
|
-
|
|
15319
|
-
|
|
15320
|
-
|
|
15321
|
-
|
|
15322
|
-
|
|
15323
|
-
detail: null
|
|
15324
|
-
})),
|
|
15325
|
-
unavailable: null
|
|
15326
|
-
};
|
|
15327
|
-
const hubUrl = state.settings.value.baseUrl;
|
|
15328
|
-
const hub = yield* client.list("skill").pipe(Effect.option);
|
|
15329
|
-
if (Option.isNone(hub)) return {
|
|
15330
|
-
...base,
|
|
15331
|
-
configured: true,
|
|
15332
|
-
hubUrl,
|
|
15333
|
-
entries: managed.map((entry) => ({
|
|
15722
|
+
const hubAssets = Option.isNone(state.settings) ? Option.none() : yield* client.list().pipe(Effect.option);
|
|
15723
|
+
const entries = [];
|
|
15724
|
+
const refused = [];
|
|
15725
|
+
for (const driver of drivers) {
|
|
15726
|
+
const kind = driver.kind;
|
|
15727
|
+
const views = yield* viewsFor(driver, Option.getOrElse(hubAssets, () => []));
|
|
15728
|
+
for (const entry of views.refused) refused.push({
|
|
15729
|
+
kind,
|
|
15334
15730
|
name: entry.name,
|
|
15335
|
-
|
|
15336
|
-
|
|
15337
|
-
|
|
15338
|
-
|
|
15339
|
-
|
|
15340
|
-
|
|
15341
|
-
|
|
15342
|
-
|
|
15343
|
-
|
|
15344
|
-
|
|
15345
|
-
|
|
15346
|
-
|
|
15347
|
-
|
|
15348
|
-
|
|
15349
|
-
|
|
15350
|
-
|
|
15351
|
-
|
|
15352
|
-
|
|
15353
|
-
|
|
15354
|
-
|
|
15355
|
-
|
|
15356
|
-
|
|
15357
|
-
|
|
15358
|
-
|
|
15359
|
-
|
|
15360
|
-
|
|
15361
|
-
|
|
15362
|
-
|
|
15363
|
-
const
|
|
15364
|
-
|
|
15365
|
-
|
|
15366
|
-
|
|
15367
|
-
|
|
15368
|
-
|
|
15369
|
-
|
|
15370
|
-
|
|
15371
|
-
|
|
15372
|
-
|
|
15373
|
-
version
|
|
15374
|
-
|
|
15375
|
-
|
|
15376
|
-
|
|
15377
|
-
|
|
15378
|
-
|
|
15379
|
-
|
|
15380
|
-
|
|
15381
|
-
|
|
15382
|
-
|
|
15383
|
-
|
|
15731
|
+
reason: entry.reason
|
|
15732
|
+
});
|
|
15733
|
+
if (Option.isNone(hubAssets)) {
|
|
15734
|
+
for (const entry of views.managed) entries.push({
|
|
15735
|
+
kind,
|
|
15736
|
+
name: entry.name,
|
|
15737
|
+
state: "synced",
|
|
15738
|
+
version: entry.version,
|
|
15739
|
+
detail: null
|
|
15740
|
+
});
|
|
15741
|
+
for (const local of views.local) {
|
|
15742
|
+
if (views.managed.some((entry) => entry.name === local.name)) continue;
|
|
15743
|
+
entries.push({
|
|
15744
|
+
kind,
|
|
15745
|
+
name: local.name,
|
|
15746
|
+
state: "unmanaged",
|
|
15747
|
+
version: null,
|
|
15748
|
+
detail: null
|
|
15749
|
+
});
|
|
15750
|
+
}
|
|
15751
|
+
continue;
|
|
15752
|
+
}
|
|
15753
|
+
const plan = planSkillSync({
|
|
15754
|
+
hub: views.hub,
|
|
15755
|
+
local: views.local,
|
|
15756
|
+
manifest: views.managed
|
|
15757
|
+
});
|
|
15758
|
+
const conflictByName = new Map(plan.conflicts.map((entry) => [entry.name, entry.reason]));
|
|
15759
|
+
const pendingByName = new Map(plan.actions.map((action) => [action._tag === "materialize" ? action.asset.name : action.name, action._tag]));
|
|
15760
|
+
const managedByName = new Map(views.managed.map((entry) => [entry.name, entry]));
|
|
15761
|
+
const hubByName = new Map(views.hub.map((asset) => [asset.name, asset]));
|
|
15762
|
+
const localNames = new Set(views.local.map((entry) => entry.name));
|
|
15763
|
+
const names = [.../* @__PURE__ */ new Set([
|
|
15764
|
+
...hubByName.keys(),
|
|
15765
|
+
...localNames,
|
|
15766
|
+
...managedByName.keys()
|
|
15767
|
+
])].sort();
|
|
15768
|
+
for (const name of names) {
|
|
15769
|
+
const version = hubByName.get(name)?.version ?? managedByName.get(name)?.version ?? null;
|
|
15770
|
+
const conflict = conflictByName.get(name);
|
|
15771
|
+
if (conflict !== void 0) {
|
|
15772
|
+
entries.push({
|
|
15773
|
+
kind,
|
|
15774
|
+
name,
|
|
15775
|
+
state: "conflict",
|
|
15776
|
+
version,
|
|
15777
|
+
detail: conflict
|
|
15778
|
+
});
|
|
15779
|
+
continue;
|
|
15780
|
+
}
|
|
15781
|
+
if (views.root === null) {
|
|
15782
|
+
entries.push({
|
|
15783
|
+
kind,
|
|
15784
|
+
name,
|
|
15785
|
+
state: "hub-only",
|
|
15786
|
+
version,
|
|
15787
|
+
detail: null
|
|
15788
|
+
});
|
|
15789
|
+
continue;
|
|
15790
|
+
}
|
|
15791
|
+
const action = pendingByName.get(name);
|
|
15792
|
+
if (action === "publish") {
|
|
15793
|
+
entries.push({
|
|
15794
|
+
kind,
|
|
15795
|
+
name,
|
|
15796
|
+
state: "local-edit",
|
|
15797
|
+
version,
|
|
15798
|
+
detail: null
|
|
15799
|
+
});
|
|
15800
|
+
continue;
|
|
15801
|
+
}
|
|
15802
|
+
if (action === "materialize" || action === "removeLocal") {
|
|
15803
|
+
entries.push({
|
|
15804
|
+
kind,
|
|
15805
|
+
name,
|
|
15806
|
+
state: "pending",
|
|
15807
|
+
version,
|
|
15808
|
+
detail: action
|
|
15809
|
+
});
|
|
15810
|
+
continue;
|
|
15811
|
+
}
|
|
15812
|
+
const isManaged = managedByName.has(name);
|
|
15813
|
+
entries.push({
|
|
15814
|
+
kind,
|
|
15815
|
+
name,
|
|
15816
|
+
state: isManaged ? "synced" : "unmanaged",
|
|
15817
|
+
version: isManaged ? version : null,
|
|
15818
|
+
detail: null
|
|
15819
|
+
});
|
|
15820
|
+
}
|
|
15821
|
+
}
|
|
15384
15822
|
return {
|
|
15385
15823
|
...base,
|
|
15386
|
-
configured:
|
|
15387
|
-
hubUrl,
|
|
15824
|
+
configured: Option.isSome(state.settings),
|
|
15825
|
+
hubUrl: Option.match(state.settings, {
|
|
15826
|
+
onNone: () => null,
|
|
15827
|
+
onSome: (settings) => settings.baseUrl
|
|
15828
|
+
}),
|
|
15388
15829
|
entries,
|
|
15389
|
-
|
|
15830
|
+
refused,
|
|
15831
|
+
unavailable: Option.isSome(state.settings) && Option.isNone(hubAssets) ? "hub is unreachable" : null
|
|
15390
15832
|
};
|
|
15391
|
-
}).pipe(Effect.provide(services), Effect.catchCause((cause) => Effect.logWarning("
|
|
15833
|
+
}).pipe(Effect.provide(services), Effect.catchCause((cause) => Effect.logWarning("asset sync status failed", { cause }).pipe(Effect.as({
|
|
15392
15834
|
configured: false,
|
|
15393
15835
|
hubUrl: null,
|
|
15394
15836
|
syncMode: "auto",
|
|
15395
15837
|
shareMode: "all",
|
|
15396
15838
|
source: "none",
|
|
15397
|
-
|
|
15839
|
+
roots: [],
|
|
15398
15840
|
entries: [],
|
|
15399
15841
|
refused: [],
|
|
15400
|
-
unavailable: "could not read local
|
|
15842
|
+
unavailable: "could not read local assets"
|
|
15401
15843
|
}))));
|
|
15402
|
-
const publish = (name) => Effect.gen(function* () {
|
|
15844
|
+
const publish = ({ kind, name }) => Effect.gen(function* () {
|
|
15403
15845
|
if (!(yield* isConfigured)) return EMPTY_REPORT;
|
|
15404
|
-
const
|
|
15405
|
-
|
|
15406
|
-
|
|
15846
|
+
const driver = driverFor(kind);
|
|
15847
|
+
if (driver === void 0) return EMPTY_REPORT;
|
|
15848
|
+
const root = yield* driver.resolveRoot;
|
|
15849
|
+
const roots = yield* rootsOf();
|
|
15850
|
+
if (root === null) return {
|
|
15407
15851
|
...EMPTY_REPORT,
|
|
15408
15852
|
ran: true,
|
|
15409
|
-
|
|
15853
|
+
roots
|
|
15410
15854
|
};
|
|
15411
|
-
const local = yield*
|
|
15412
|
-
const refused = local.refused.filter((entry) => entry.name === name)
|
|
15855
|
+
const local = yield* driver.read(root);
|
|
15856
|
+
const refused = local.refused.filter((entry) => entry.name === name).map((entry) => ({
|
|
15857
|
+
kind,
|
|
15858
|
+
name: entry.name,
|
|
15859
|
+
reason: entry.reason
|
|
15860
|
+
}));
|
|
15413
15861
|
if (refused.length > 0) return {
|
|
15414
15862
|
...EMPTY_REPORT,
|
|
15415
15863
|
ran: true,
|
|
15416
|
-
|
|
15864
|
+
roots,
|
|
15417
15865
|
refused
|
|
15418
15866
|
};
|
|
15419
|
-
const
|
|
15420
|
-
if (
|
|
15867
|
+
const asset = local.skills.find((entry) => entry.name === name);
|
|
15868
|
+
if (asset === void 0) return {
|
|
15421
15869
|
...EMPTY_REPORT,
|
|
15422
15870
|
ran: true,
|
|
15423
|
-
|
|
15871
|
+
roots
|
|
15424
15872
|
};
|
|
15425
15873
|
const written = yield* client.write({
|
|
15426
|
-
kind
|
|
15874
|
+
kind,
|
|
15427
15875
|
name,
|
|
15428
|
-
files:
|
|
15876
|
+
files: asset.files
|
|
15429
15877
|
}).pipe(Effect.option);
|
|
15430
15878
|
if (Option.isNone(written)) return {
|
|
15431
15879
|
...EMPTY_REPORT,
|
|
15432
15880
|
ran: true,
|
|
15433
|
-
|
|
15881
|
+
roots,
|
|
15434
15882
|
conflicts: [{
|
|
15883
|
+
kind,
|
|
15435
15884
|
name,
|
|
15436
15885
|
reason: "unmanaged-local-collision"
|
|
15437
15886
|
}]
|
|
15438
15887
|
};
|
|
15439
15888
|
const manifest = yield* readSkillManifest(manifestPath);
|
|
15440
|
-
const entries = entriesFor(manifest,
|
|
15441
|
-
yield* writeSkillManifest(manifestPath, withEntriesFor(manifest,
|
|
15889
|
+
const entries = entriesFor(manifest, root).filter((entry) => entry.name !== name);
|
|
15890
|
+
yield* writeSkillManifest(manifestPath, withEntriesFor(manifest, root, [...entries, {
|
|
15442
15891
|
name,
|
|
15443
15892
|
version: written.value.version,
|
|
15444
15893
|
digest: digestSkillFiles(written.value.files)
|
|
@@ -15446,134 +15895,261 @@ const make$57 = Effect.gen(function* () {
|
|
|
15446
15895
|
return {
|
|
15447
15896
|
...EMPTY_REPORT,
|
|
15448
15897
|
ran: true,
|
|
15449
|
-
|
|
15450
|
-
published: [
|
|
15898
|
+
roots,
|
|
15899
|
+
published: [{
|
|
15900
|
+
kind,
|
|
15901
|
+
name
|
|
15902
|
+
}]
|
|
15451
15903
|
};
|
|
15452
|
-
}).pipe(Effect.provide(services), Effect.catchCause((cause) => Effect.logWarning("
|
|
15904
|
+
}).pipe(Effect.provide(services), Effect.catchCause((cause) => Effect.logWarning("asset publish failed", { cause }).pipe(Effect.as(EMPTY_REPORT))));
|
|
15453
15905
|
const publishAll = Effect.gen(function* () {
|
|
15454
15906
|
if (!(yield* isConfigured)) return EMPTY_REPORT;
|
|
15455
|
-
const
|
|
15456
|
-
const
|
|
15457
|
-
const candidates = status.entries.filter((entry) => entry.state === "unmanaged").map((entry) => entry.name);
|
|
15907
|
+
const status = yield* assetStatus;
|
|
15908
|
+
const candidates = status.entries.filter((entry) => entry.state === "unmanaged");
|
|
15458
15909
|
const published = [];
|
|
15459
15910
|
const conflicts = [];
|
|
15460
|
-
for (const
|
|
15461
|
-
const report = yield* publish(
|
|
15911
|
+
for (const candidate of candidates) {
|
|
15912
|
+
const report = yield* publish({
|
|
15913
|
+
kind: candidate.kind,
|
|
15914
|
+
name: candidate.name
|
|
15915
|
+
});
|
|
15462
15916
|
published.push(...report.published);
|
|
15463
15917
|
conflicts.push(...report.conflicts);
|
|
15464
15918
|
}
|
|
15465
15919
|
return {
|
|
15466
15920
|
...EMPTY_REPORT,
|
|
15467
15921
|
ran: true,
|
|
15468
|
-
|
|
15922
|
+
roots: status.roots,
|
|
15469
15923
|
published,
|
|
15470
15924
|
conflicts,
|
|
15471
15925
|
refused: status.refused
|
|
15472
15926
|
};
|
|
15473
|
-
}).pipe(Effect.provide(services), Effect.catchCause((cause) => Effect.logWarning("
|
|
15474
|
-
const unpublish = (name) => Effect.gen(function* () {
|
|
15927
|
+
}).pipe(Effect.provide(services), Effect.catchCause((cause) => Effect.logWarning("asset publish-all failed", { cause }).pipe(Effect.as(EMPTY_REPORT))));
|
|
15928
|
+
const unpublish = ({ kind, name }) => Effect.gen(function* () {
|
|
15475
15929
|
if (!(yield* isConfigured)) return EMPTY_REPORT;
|
|
15476
|
-
const
|
|
15930
|
+
const driver = driverFor(kind);
|
|
15931
|
+
if (driver === void 0) return EMPTY_REPORT;
|
|
15932
|
+
const roots = yield* rootsOf();
|
|
15933
|
+
const root = yield* driver.resolveRoot;
|
|
15934
|
+
if (root === null) return {
|
|
15935
|
+
...EMPTY_REPORT,
|
|
15936
|
+
ran: true,
|
|
15937
|
+
roots
|
|
15938
|
+
};
|
|
15477
15939
|
const manifest = yield* readSkillManifest(manifestPath);
|
|
15478
|
-
const managed = entriesFor(manifest,
|
|
15940
|
+
const managed = entriesFor(manifest, root);
|
|
15479
15941
|
const entry = managed.find((candidate) => candidate.name === name);
|
|
15480
15942
|
if (entry === void 0) return {
|
|
15481
15943
|
...EMPTY_REPORT,
|
|
15482
15944
|
ran: true,
|
|
15483
|
-
|
|
15945
|
+
roots
|
|
15484
15946
|
};
|
|
15485
15947
|
const removedFromHub = yield* client.remove({
|
|
15486
|
-
kind
|
|
15948
|
+
kind,
|
|
15487
15949
|
name,
|
|
15488
15950
|
expectedVersion: entry.version
|
|
15489
15951
|
}).pipe(Effect.option);
|
|
15490
15952
|
if (Option.isNone(removedFromHub)) return {
|
|
15491
15953
|
...EMPTY_REPORT,
|
|
15492
15954
|
ran: true,
|
|
15493
|
-
|
|
15955
|
+
roots,
|
|
15494
15956
|
conflicts: [{
|
|
15957
|
+
kind,
|
|
15495
15958
|
name,
|
|
15496
15959
|
reason: "edited-both-sides"
|
|
15497
15960
|
}]
|
|
15498
15961
|
};
|
|
15499
|
-
yield* writeSkillManifest(manifestPath, withEntriesFor(manifest,
|
|
15962
|
+
yield* writeSkillManifest(manifestPath, withEntriesFor(manifest, root, managed.filter((candidate) => candidate.name !== name)));
|
|
15500
15963
|
return {
|
|
15501
15964
|
...EMPTY_REPORT,
|
|
15502
15965
|
ran: true,
|
|
15503
|
-
|
|
15504
|
-
removed: [
|
|
15966
|
+
roots,
|
|
15967
|
+
removed: [{
|
|
15968
|
+
kind,
|
|
15969
|
+
name
|
|
15970
|
+
}]
|
|
15971
|
+
};
|
|
15972
|
+
}).pipe(Effect.provide(services), Effect.catchCause((cause) => Effect.logWarning("asset unpublish failed", { cause }).pipe(Effect.as(EMPTY_REPORT))));
|
|
15973
|
+
const read = ({ kind, name }) => client.get({
|
|
15974
|
+
kind,
|
|
15975
|
+
name
|
|
15976
|
+
}).pipe(Effect.map((asset) => Option.match(asset, {
|
|
15977
|
+
onNone: () => ({
|
|
15978
|
+
found: false,
|
|
15979
|
+
kind,
|
|
15980
|
+
name,
|
|
15981
|
+
files: [],
|
|
15982
|
+
version: null,
|
|
15983
|
+
updatedAt: null,
|
|
15984
|
+
unavailable: null
|
|
15985
|
+
}),
|
|
15986
|
+
onSome: (found) => ({
|
|
15987
|
+
found: true,
|
|
15988
|
+
kind,
|
|
15989
|
+
name,
|
|
15990
|
+
files: found.files,
|
|
15991
|
+
version: found.version,
|
|
15992
|
+
updatedAt: found.updatedAt,
|
|
15993
|
+
unavailable: null
|
|
15994
|
+
})
|
|
15995
|
+
})), Effect.catchCause((cause) => Effect.logWarning("asset read failed", { cause }).pipe(Effect.as({
|
|
15996
|
+
found: false,
|
|
15997
|
+
kind,
|
|
15998
|
+
name,
|
|
15999
|
+
files: [],
|
|
16000
|
+
version: null,
|
|
16001
|
+
updatedAt: null,
|
|
16002
|
+
unavailable: "hub is unreachable"
|
|
16003
|
+
}))));
|
|
16004
|
+
const save = ({ kind, name, files, expectedVersion }) => Effect.gen(function* () {
|
|
16005
|
+
if (!(yield* isConfigured)) return EMPTY_REPORT;
|
|
16006
|
+
const written = yield* client.write({
|
|
16007
|
+
kind,
|
|
16008
|
+
name,
|
|
16009
|
+
files,
|
|
16010
|
+
expectedVersion
|
|
16011
|
+
}).pipe(Effect.option);
|
|
16012
|
+
if (Option.isNone(written)) return {
|
|
16013
|
+
...EMPTY_REPORT,
|
|
16014
|
+
ran: true,
|
|
16015
|
+
roots: yield* rootsOf(),
|
|
16016
|
+
conflicts: [{
|
|
16017
|
+
kind,
|
|
16018
|
+
name,
|
|
16019
|
+
reason: "edited-both-sides"
|
|
16020
|
+
}]
|
|
16021
|
+
};
|
|
16022
|
+
return yield* run;
|
|
16023
|
+
}).pipe(Effect.provide(services), Effect.catchCause((cause) => Effect.logWarning("asset save failed", { cause }).pipe(Effect.as(EMPTY_REPORT))));
|
|
16024
|
+
const deleteFromHub = ({ kind, name, expectedVersion }) => Effect.gen(function* () {
|
|
16025
|
+
if (!(yield* isConfigured)) return EMPTY_REPORT;
|
|
16026
|
+
const removed = yield* client.remove({
|
|
16027
|
+
kind,
|
|
16028
|
+
name,
|
|
16029
|
+
...expectedVersion === void 0 ? {} : { expectedVersion }
|
|
16030
|
+
}).pipe(Effect.option);
|
|
16031
|
+
if (Option.isNone(removed)) return {
|
|
16032
|
+
...EMPTY_REPORT,
|
|
16033
|
+
ran: true,
|
|
16034
|
+
roots: yield* rootsOf(),
|
|
16035
|
+
conflicts: [{
|
|
16036
|
+
kind,
|
|
16037
|
+
name,
|
|
16038
|
+
reason: "edited-both-sides"
|
|
16039
|
+
}]
|
|
15505
16040
|
};
|
|
15506
|
-
|
|
16041
|
+
return yield* run;
|
|
16042
|
+
}).pipe(Effect.provide(services), Effect.catchCause((cause) => Effect.logWarning("asset delete failed", { cause }).pipe(Effect.as(EMPTY_REPORT))));
|
|
15507
16043
|
return {
|
|
15508
16044
|
run,
|
|
15509
|
-
status:
|
|
16045
|
+
status: assetStatus,
|
|
15510
16046
|
publish,
|
|
15511
16047
|
publishAll,
|
|
15512
|
-
unpublish
|
|
16048
|
+
unpublish,
|
|
16049
|
+
read,
|
|
16050
|
+
save,
|
|
16051
|
+
deleteFromHub
|
|
15513
16052
|
};
|
|
15514
16053
|
});
|
|
15515
|
-
const layer$
|
|
16054
|
+
const layer$53 = Layer.effect(AssetSync, make$58);
|
|
15516
16055
|
//#endregion
|
|
15517
|
-
//#region src/cli/
|
|
15518
|
-
const SyncServicesLive = layer$
|
|
16056
|
+
//#region src/cli/assets.ts
|
|
16057
|
+
const SyncServicesLive = layer$53.pipe(Layer.provideMerge(layer$54), Layer.provideMerge(layer$55), Layer.provideMerge(layer$57.pipe(Layer.provide(layer$63))), Layer.provideMerge(layer$63), Layer.provideMerge(FetchHttpClient.layer));
|
|
15519
16058
|
const runWithSync = (flags, run) => Effect.gen(function* () {
|
|
15520
16059
|
const config = yield* resolveCliAuthConfig(flags, yield* GlobalFlag.LogLevel);
|
|
15521
16060
|
return yield* Effect.gen(function* () {
|
|
15522
16061
|
return yield* run({
|
|
15523
|
-
sync: yield*
|
|
16062
|
+
sync: yield* AssetSync,
|
|
15524
16063
|
link: yield* HubLink
|
|
15525
16064
|
});
|
|
15526
|
-
}).pipe(Effect.provide(SyncServicesLive.pipe(Layer.provide(layer$
|
|
16065
|
+
}).pipe(Effect.provide(SyncServicesLive.pipe(Layer.provide(layer$66(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, "Error")))));
|
|
15527
16066
|
});
|
|
15528
16067
|
const STATE_LABEL = {
|
|
15529
16068
|
synced: "synced",
|
|
15530
16069
|
pending: "will update",
|
|
15531
16070
|
"local-edit": "will publish",
|
|
15532
16071
|
conflict: "CONFLICT",
|
|
15533
|
-
unmanaged: "not shared"
|
|
16072
|
+
unmanaged: "not shared",
|
|
16073
|
+
"hub-only": "on the hub only"
|
|
16074
|
+
};
|
|
16075
|
+
const SKILL_WORDS = {
|
|
16076
|
+
kind: "skill",
|
|
16077
|
+
command: "skills",
|
|
16078
|
+
singular: "skill",
|
|
16079
|
+
plural: "skills",
|
|
16080
|
+
rootLabel: "Skills",
|
|
16081
|
+
nameDescription: "Name of the skill directory."
|
|
16082
|
+
};
|
|
16083
|
+
const MEMORY_WORDS = {
|
|
16084
|
+
kind: "memory",
|
|
16085
|
+
command: "memory",
|
|
16086
|
+
singular: "memory file",
|
|
16087
|
+
plural: "memory files",
|
|
16088
|
+
rootLabel: "Memory",
|
|
16089
|
+
nameDescription: "Memory filename, for example CLAUDE.md."
|
|
15534
16090
|
};
|
|
16091
|
+
const MCP_WORDS = {
|
|
16092
|
+
kind: "mcp",
|
|
16093
|
+
command: "mcp",
|
|
16094
|
+
singular: "MCP server",
|
|
16095
|
+
plural: "MCP servers",
|
|
16096
|
+
rootLabel: "MCP",
|
|
16097
|
+
nameDescription: "Registration filename, for example linear.json."
|
|
16098
|
+
};
|
|
16099
|
+
const rootFor = (status, kind) => status.roots.find((root) => root.kind === kind)?.path ?? null;
|
|
15535
16100
|
/**
|
|
15536
16101
|
* Exported for tests. These two are the only place a wrong report becomes a
|
|
15537
16102
|
* confident sentence, so they are worth pinning: "Already up to date" and
|
|
15538
16103
|
* "nothing changed" must never be printed for a run that failed.
|
|
15539
16104
|
*/
|
|
15540
|
-
const formatStatus = (status) => {
|
|
16105
|
+
const formatStatus = (status, words = SKILL_WORDS) => {
|
|
15541
16106
|
const lines = [];
|
|
15542
|
-
if (!status.configured) lines.push("No hub is configured. Connect one with:
|
|
16107
|
+
if (!status.configured) lines.push("No hub is configured. Connect one with: p4c hub connect --url <url> --token <token>");
|
|
15543
16108
|
else {
|
|
15544
16109
|
lines.push(`Hub: ${status.hubUrl ?? "unknown"}${status.source === "environment" ? " (from the environment)" : ""}`);
|
|
15545
16110
|
lines.push(`Sync: ${status.syncMode}`);
|
|
15546
|
-
lines.push(`Share: ${status.shareMode === "all" ?
|
|
16111
|
+
lines.push(`Share: ${status.shareMode === "all" ? `all ${words.plural} (new ones share automatically)` : "only the ones you choose"}`);
|
|
15547
16112
|
}
|
|
15548
|
-
|
|
16113
|
+
const root = rootFor(status, words.kind);
|
|
16114
|
+
if (root !== null) lines.push(`${words.rootLabel}: ${root}`);
|
|
15549
16115
|
if (status.unavailable !== null) lines.push(`Warning: ${status.unavailable}`);
|
|
15550
16116
|
lines.push("");
|
|
15551
|
-
|
|
15552
|
-
|
|
16117
|
+
const entries = status.entries.filter((entry) => entry.kind === words.kind);
|
|
16118
|
+
if (entries.length === 0) lines.push(`No ${words.plural} found.`);
|
|
16119
|
+
else for (const entry of entries) {
|
|
15553
16120
|
const version = entry.version === null ? "" : ` v${entry.version}`;
|
|
15554
16121
|
const detail = entry.detail === null ? "" : ` (${entry.detail})`;
|
|
15555
16122
|
lines.push(` ${entry.name.padEnd(28)} ${STATE_LABEL[entry.state] ?? entry.state}${version}${detail}`);
|
|
15556
16123
|
}
|
|
15557
|
-
for (const refusal of status.refused) lines.push(` ${refusal.name.padEnd(28)} cannot sync (${refusal.reason})`);
|
|
16124
|
+
for (const refusal of status.refused.filter((entry) => entry.kind === words.kind)) lines.push(` ${refusal.name.padEnd(28)} cannot sync (${refusal.reason})`);
|
|
15558
16125
|
return lines.join("\n");
|
|
15559
16126
|
};
|
|
16127
|
+
/**
|
|
16128
|
+
* A report covers every kind at once, so names are qualified as soon as more
|
|
16129
|
+
* than one kind appears in it. `published CLAUDE.md` is ambiguous in a way
|
|
16130
|
+
* `published memory CLAUDE.md` is not — a skill directory may legitimately be
|
|
16131
|
+
* called `CLAUDE.md`, since the name schema permits dots.
|
|
16132
|
+
*/
|
|
16133
|
+
const describeRefs = (refs) => {
|
|
16134
|
+
const kinds = new Set(refs.map((ref) => ref.kind));
|
|
16135
|
+
return refs.map((ref) => kinds.size > 1 ? `${ref.kind} ${ref.name}` : ref.name).join(", ");
|
|
16136
|
+
};
|
|
15560
16137
|
const formatReport = (report) => {
|
|
15561
16138
|
if (!report.ran) return "No hub is configured; nothing to do.";
|
|
15562
16139
|
if (report.unavailable !== null) return `Hub is unreachable (${report.unavailable}); nothing changed.`;
|
|
15563
16140
|
const parts = [];
|
|
15564
|
-
if (report.materialized.length > 0) parts.push(`updated ${report.materialized
|
|
15565
|
-
if (report.published.length > 0) parts.push(`published ${report.published
|
|
15566
|
-
if (report.removed.length > 0) parts.push(`removed ${report.removed
|
|
16141
|
+
if (report.materialized.length > 0) parts.push(`updated ${describeRefs(report.materialized)}`);
|
|
16142
|
+
if (report.published.length > 0) parts.push(`published ${describeRefs(report.published)}`);
|
|
16143
|
+
if (report.removed.length > 0) parts.push(`removed ${describeRefs(report.removed)}`);
|
|
15567
16144
|
for (const conflict of report.conflicts) parts.push(`CONFLICT on ${conflict.name} (${conflict.reason})`);
|
|
15568
16145
|
for (const refusal of report.refused) parts.push(`cannot sync ${refusal.name} (${refusal.reason})`);
|
|
15569
16146
|
const summary = parts.length === 0 ? "Already up to date." : parts.join("\n");
|
|
15570
16147
|
if (report.unmanaged.length === 0) return summary;
|
|
15571
16148
|
const count = report.unmanaged.length;
|
|
15572
|
-
return `${summary}\n\n${count} local
|
|
16149
|
+
return `${summary}\n\n${count} local item${count === 1 ? " is" : "s are"} not shared. Share ${count === 1 ? "it" : "them"} with: p4c skills publish --all`;
|
|
15573
16150
|
};
|
|
15574
16151
|
const urlFlag = Flag.string("url").pipe(Flag.withDescription("Base URL of the hub, for example https://hub.example.com."));
|
|
15575
|
-
const tokenFlag = Flag.string("token").pipe(Flag.withDescription("Bearer token for this server, from `
|
|
15576
|
-
const skillNameArgument = Argument.string("name").pipe(Argument.withDescription("Name of the skill directory."));
|
|
16152
|
+
const tokenFlag = Flag.string("token").pipe(Flag.withDescription("Bearer token for this server, from `p4c hub token add`."));
|
|
15577
16153
|
const hubConnectCommand = Command.make("connect", {
|
|
15578
16154
|
...authLocationFlags,
|
|
15579
16155
|
url: urlFlag,
|
|
@@ -15589,32 +16165,74 @@ const hubDisconnectCommand = Command.make("disconnect", { ...authLocationFlags }
|
|
|
15589
16165
|
yield* link.disconnect;
|
|
15590
16166
|
yield* Console.log(formatStatus(yield* sync.status));
|
|
15591
16167
|
}))));
|
|
15592
|
-
const
|
|
15593
|
-
const
|
|
15594
|
-
|
|
15595
|
-
|
|
15596
|
-
|
|
15597
|
-
|
|
15598
|
-
|
|
15599
|
-
|
|
15600
|
-
|
|
15601
|
-
|
|
15602
|
-
|
|
15603
|
-
|
|
15604
|
-
})
|
|
15605
|
-
|
|
15606
|
-
|
|
15607
|
-
|
|
15608
|
-
}).pipe(
|
|
15609
|
-
|
|
16168
|
+
const makeAssetCommands = (words) => {
|
|
16169
|
+
const nameArgument = Argument.string("name").pipe(Argument.withDescription(words.nameDescription));
|
|
16170
|
+
return {
|
|
16171
|
+
statusCommand: Command.make("status", { ...authLocationFlags }).pipe(Command.withDescription(`Show which ${words.plural} are shared, changed, or in conflict.`), Command.withHandler((flags) => runWithSync(flags, ({ sync }) => sync.status.pipe(Effect.flatMap((status) => Console.log(formatStatus(status, words))))))),
|
|
16172
|
+
syncCommand: Command.make("sync", { ...authLocationFlags }).pipe(Command.withDescription("Reconcile everything this machine shares with the hub now."), Command.withHandler((flags) => runWithSync(flags, ({ sync }) => sync.run.pipe(Effect.flatMap((report) => Console.log(formatReport(report))))))),
|
|
16173
|
+
publishCommand: Command.make("publish", {
|
|
16174
|
+
...authLocationFlags,
|
|
16175
|
+
name: nameArgument.pipe(Argument.optional),
|
|
16176
|
+
all: Flag.boolean("all").pipe(Flag.withDescription("Share everything local this machine does not already share."))
|
|
16177
|
+
}).pipe(Command.withDescription(`Share local ${words.plural} through the hub, so every machine gets them.`), Command.withHandler((flags) => runWithSync(flags, ({ sync }) => Effect.gen(function* () {
|
|
16178
|
+
if (flags.all) return yield* sync.publishAll.pipe(Effect.flatMap((report) => Console.log(formatReport(report))));
|
|
16179
|
+
const name = Option.getOrUndefined(flags.name);
|
|
16180
|
+
if (name === void 0) return yield* Console.error(`Name a ${words.singular}, or pass --all to share every unshared one.`);
|
|
16181
|
+
return yield* sync.publish({
|
|
16182
|
+
kind: words.kind,
|
|
16183
|
+
name
|
|
16184
|
+
}).pipe(Effect.flatMap((report) => Console.log(formatReport(report))));
|
|
16185
|
+
})))),
|
|
16186
|
+
unpublishCommand: Command.make("unpublish", {
|
|
16187
|
+
...authLocationFlags,
|
|
16188
|
+
name: nameArgument
|
|
16189
|
+
}).pipe(Command.withDescription(`Stop sharing a ${words.singular}. It stays on this machine and leaves the others.`), Command.withHandler((flags) => runWithSync(flags, ({ sync }) => sync.unpublish({
|
|
16190
|
+
kind: words.kind,
|
|
16191
|
+
name: flags.name
|
|
16192
|
+
}).pipe(Effect.flatMap((report) => Console.log(formatReport(report))))))),
|
|
16193
|
+
showCommand: Command.make("show", {
|
|
16194
|
+
...authLocationFlags,
|
|
16195
|
+
name: nameArgument
|
|
16196
|
+
}).pipe(Command.withDescription(`Print the hub's copy of a ${words.singular}, and where it lives here.`), Command.withHandler((flags) => runWithSync(flags, ({ sync }) => Effect.gen(function* () {
|
|
16197
|
+
const name = flags.name;
|
|
16198
|
+
const content = yield* sync.read({
|
|
16199
|
+
kind: words.kind,
|
|
16200
|
+
name
|
|
16201
|
+
});
|
|
16202
|
+
if (content.unavailable !== null) return yield* Console.error(`Hub is unreachable (${content.unavailable}).`);
|
|
16203
|
+
if (!content.found) return yield* Console.error(`The hub has no ${words.singular} named ${name}.`);
|
|
16204
|
+
const lines = [`${name} v${content.version ?? "?"} updated ${content.updatedAt ?? "?"}`];
|
|
16205
|
+
for (const file of content.files) lines.push("", `--- ${file.path} ---`, file.content.trimEnd());
|
|
16206
|
+
const root = rootFor(yield* sync.status, words.kind);
|
|
16207
|
+
if (root !== null) lines.push("", `Edit it here and the next sync publishes it: ${root}/${name}`);
|
|
16208
|
+
return yield* Console.log(lines.join("\n"));
|
|
16209
|
+
})))),
|
|
16210
|
+
removeCommand: Command.make("rm", {
|
|
16211
|
+
...authLocationFlags,
|
|
16212
|
+
name: nameArgument,
|
|
16213
|
+
yes: Flag.boolean("yes").pipe(Flag.withDescription("Confirm: this removes it from the hub and from every machine."))
|
|
16214
|
+
}).pipe(Command.withDescription(`Delete a ${words.singular} from the hub and from every machine.`), Command.withHandler((flags) => runWithSync(flags, ({ sync }) => Effect.gen(function* () {
|
|
16215
|
+
if (!flags.yes) return yield* Console.error(`This deletes ${flags.name} from the hub and from every machine that syncs it.\nRe-run with --yes if that is what you want.\nTo stop sharing it from here only: p4 ${words.command} unpublish ${flags.name}`);
|
|
16216
|
+
const report = yield* sync.deleteFromHub({
|
|
16217
|
+
kind: words.kind,
|
|
16218
|
+
name: flags.name
|
|
16219
|
+
});
|
|
16220
|
+
return yield* Console.log(formatReport(report));
|
|
16221
|
+
}))))
|
|
16222
|
+
};
|
|
16223
|
+
};
|
|
16224
|
+
const skillCommands = makeAssetCommands(SKILL_WORDS);
|
|
16225
|
+
const memoryCommands = makeAssetCommands(MEMORY_WORDS);
|
|
16226
|
+
const mcpCommands = makeAssetCommands(MCP_WORDS);
|
|
16227
|
+
const shareAllCommand = Command.make("share-all", { ...authLocationFlags }).pipe(Command.withDescription("Share everything, including new items, automatically. The default."), Command.withHandler((flags) => runWithSync(flags, ({ sync, link }) => Effect.gen(function* () {
|
|
15610
16228
|
yield* link.setShareMode("all");
|
|
15611
16229
|
yield* Console.log(formatStatus(yield* sync.status));
|
|
15612
16230
|
}))));
|
|
15613
|
-
const shareSelectedCommand = Command.make("share-selected", { ...authLocationFlags }).pipe(Command.withDescription("Share only
|
|
16231
|
+
const shareSelectedCommand = Command.make("share-selected", { ...authLocationFlags }).pipe(Command.withDescription("Share only what you name with `publish`. Nothing shares on its own."), Command.withHandler((flags) => runWithSync(flags, ({ sync, link }) => Effect.gen(function* () {
|
|
15614
16232
|
yield* link.setShareMode("selected");
|
|
15615
16233
|
yield* Console.log(formatStatus(yield* sync.status));
|
|
15616
16234
|
}))));
|
|
15617
|
-
const pauseCommand = Command.make("pause", { ...authLocationFlags }).pipe(Command.withDescription("Stop syncing automatically. `
|
|
16235
|
+
const pauseCommand = Command.make("pause", { ...authLocationFlags }).pipe(Command.withDescription("Stop syncing automatically. `p4c skills sync` still works."), Command.withHandler((flags) => runWithSync(flags, ({ sync, link }) => Effect.gen(function* () {
|
|
15618
16236
|
yield* link.setSyncMode("paused");
|
|
15619
16237
|
yield* Console.log(formatStatus(yield* sync.status));
|
|
15620
16238
|
}))));
|
|
@@ -15623,15 +16241,41 @@ const resumeCommand = Command.make("resume", { ...authLocationFlags }).pipe(Comm
|
|
|
15623
16241
|
yield* Console.log(formatStatus(yield* sync.status));
|
|
15624
16242
|
}))));
|
|
15625
16243
|
const skillsCommand = Command.make("skills", {}).pipe(Command.withDescription("Share agent skills across your machines through the hub."), Command.withSubcommands([
|
|
15626
|
-
statusCommand,
|
|
15627
|
-
syncCommand,
|
|
15628
|
-
publishCommand,
|
|
15629
|
-
unpublishCommand,
|
|
16244
|
+
skillCommands.statusCommand,
|
|
16245
|
+
skillCommands.syncCommand,
|
|
16246
|
+
skillCommands.publishCommand,
|
|
16247
|
+
skillCommands.unpublishCommand,
|
|
16248
|
+
skillCommands.showCommand,
|
|
16249
|
+
skillCommands.removeCommand,
|
|
15630
16250
|
pauseCommand,
|
|
15631
16251
|
resumeCommand,
|
|
15632
16252
|
shareAllCommand,
|
|
15633
16253
|
shareSelectedCommand
|
|
15634
16254
|
]));
|
|
16255
|
+
const memoryCommand = Command.make("memory", {}).pipe(Command.withDescription("Share user memory — CLAUDE.md, AGENTS.md — across your machines."), Command.withSubcommands([
|
|
16256
|
+
memoryCommands.statusCommand,
|
|
16257
|
+
memoryCommands.syncCommand,
|
|
16258
|
+
memoryCommands.publishCommand,
|
|
16259
|
+
memoryCommands.unpublishCommand,
|
|
16260
|
+
memoryCommands.showCommand,
|
|
16261
|
+
memoryCommands.removeCommand
|
|
16262
|
+
]));
|
|
16263
|
+
/**
|
|
16264
|
+
* The sharing half of `p4c mcp`.
|
|
16265
|
+
*
|
|
16266
|
+
* Registering a server — its command, its URL, its tokens — belongs in the
|
|
16267
|
+
* panel, where a form can ask for a secret and put it somewhere that does not
|
|
16268
|
+
* sync. These subcommands are the same six every kind gets, and what they move
|
|
16269
|
+
* is the definition only.
|
|
16270
|
+
*/
|
|
16271
|
+
const mcpCommand = Command.make("mcp", {}).pipe(Command.withDescription("Share MCP server registrations across your machines."), Command.withSubcommands([
|
|
16272
|
+
mcpCommands.statusCommand,
|
|
16273
|
+
mcpCommands.syncCommand,
|
|
16274
|
+
mcpCommands.publishCommand,
|
|
16275
|
+
mcpCommands.unpublishCommand,
|
|
16276
|
+
mcpCommands.showCommand,
|
|
16277
|
+
mcpCommands.removeCommand
|
|
16278
|
+
]));
|
|
15635
16279
|
/** Exported for the hub command, which owns `connect`/`disconnect`. */
|
|
15636
16280
|
const hubLinkCommands = [hubConnectCommand, hubDisconnectCommand];
|
|
15637
16281
|
//#endregion
|
|
@@ -15729,7 +16373,7 @@ const serveHubCommand = Command.make("serve", {
|
|
|
15729
16373
|
fileTokenHashes: hubTokenHashes(tokenFile)
|
|
15730
16374
|
});
|
|
15731
16375
|
if (rejectedTokenHashes.length > 0) yield* Console.error(`Ignoring ${rejectedTokenHashes.length} malformed entr${rejectedTokenHashes.length === 1 ? "y" : "ies"} in P4CODE_HUB_TOKEN_HASHES; each must be a 64-character SHA-256 hex digest.`);
|
|
15732
|
-
if (config.tokenHashes.size === 0) yield* Console.error("No server tokens are registered, so every request will be rejected.\nMint one with:
|
|
16376
|
+
if (config.tokenHashes.size === 0) yield* Console.error("No server tokens are registered, so every request will be rejected.\nMint one with: p4c hub token add --label studio");
|
|
15733
16377
|
return yield* runHub.pipe(Effect.provideService(HubConfig, HubConfig.of({
|
|
15734
16378
|
dbPath: config.dbPath,
|
|
15735
16379
|
host: config.host,
|
|
@@ -20307,6 +20951,273 @@ function mergeGitStatusParts(local, remote) {
|
|
|
20307
20951
|
};
|
|
20308
20952
|
}
|
|
20309
20953
|
//#endregion
|
|
20954
|
+
//#region src/stream/collectUint8StreamText.ts
|
|
20955
|
+
const collectUint8StreamText = (input) => {
|
|
20956
|
+
const maxBytes = input.maxBytes ?? Number.POSITIVE_INFINITY;
|
|
20957
|
+
const truncatedMarker = input.truncatedMarker ?? "";
|
|
20958
|
+
return input.stream.pipe(Stream.runFold(() => ({
|
|
20959
|
+
chunks: [],
|
|
20960
|
+
bytes: 0,
|
|
20961
|
+
truncated: false
|
|
20962
|
+
}), (state, chunk) => {
|
|
20963
|
+
if (state.truncated) return state;
|
|
20964
|
+
const remainingBytes = maxBytes - state.bytes;
|
|
20965
|
+
if (remainingBytes <= 0) return {
|
|
20966
|
+
...state,
|
|
20967
|
+
truncated: true
|
|
20968
|
+
};
|
|
20969
|
+
const nextChunk = chunk.byteLength > remainingBytes ? chunk.slice(0, remainingBytes) : chunk;
|
|
20970
|
+
state.chunks.push(nextChunk);
|
|
20971
|
+
const bytes = state.bytes + nextChunk.byteLength;
|
|
20972
|
+
const truncated = chunk.byteLength > remainingBytes;
|
|
20973
|
+
return {
|
|
20974
|
+
chunks: state.chunks,
|
|
20975
|
+
bytes,
|
|
20976
|
+
truncated
|
|
20977
|
+
};
|
|
20978
|
+
}), Effect.map((state) => {
|
|
20979
|
+
const text = Buffer.concat(state.chunks, state.bytes).toString("utf8");
|
|
20980
|
+
return {
|
|
20981
|
+
text: state.truncated && truncatedMarker.length > 0 ? `${text}${truncatedMarker}` : text,
|
|
20982
|
+
bytes: state.bytes,
|
|
20983
|
+
truncated: state.truncated
|
|
20984
|
+
};
|
|
20985
|
+
}));
|
|
20986
|
+
};
|
|
20987
|
+
//#endregion
|
|
20988
|
+
//#region src/processRunner.ts
|
|
20989
|
+
const ProcessInvocationFields = {
|
|
20990
|
+
command: Schema$1.String,
|
|
20991
|
+
argumentCount: Schema$1.Number,
|
|
20992
|
+
cwd: Schema$1.optional(Schema$1.String),
|
|
20993
|
+
spawnCwd: Schema$1.optional(Schema$1.String)
|
|
20994
|
+
};
|
|
20995
|
+
const formatProcessInvocation = (input) => {
|
|
20996
|
+
const executionCwd = input.spawnCwd ?? input.cwd;
|
|
20997
|
+
return executionCwd === void 0 ? `'${input.command}'` : `'${input.command}' in '${executionCwd}'`;
|
|
20998
|
+
};
|
|
20999
|
+
var ProcessSpawnError = class extends Schema$1.TaggedErrorClass()("ProcessSpawnError", {
|
|
21000
|
+
...ProcessInvocationFields,
|
|
21001
|
+
resolvedCommand: Schema$1.optional(Schema$1.String),
|
|
21002
|
+
resolvedArgumentCount: Schema$1.optional(Schema$1.Number),
|
|
21003
|
+
shell: Schema$1.optional(Schema$1.Boolean),
|
|
21004
|
+
cause: Schema$1.Defect()
|
|
21005
|
+
}) {
|
|
21006
|
+
get message() {
|
|
21007
|
+
return `Failed to spawn process ${formatProcessInvocation(this)}`;
|
|
21008
|
+
}
|
|
21009
|
+
};
|
|
21010
|
+
var ProcessStdinError = class extends Schema$1.TaggedErrorClass()("ProcessStdinError", {
|
|
21011
|
+
...ProcessInvocationFields,
|
|
21012
|
+
stdinBytes: Schema$1.Number,
|
|
21013
|
+
cause: Schema$1.Defect()
|
|
21014
|
+
}) {
|
|
21015
|
+
get message() {
|
|
21016
|
+
return `Failed to write stdin for process ${formatProcessInvocation(this)}`;
|
|
21017
|
+
}
|
|
21018
|
+
};
|
|
21019
|
+
var ProcessOutputLimitError = class extends Schema$1.TaggedErrorClass()("ProcessOutputLimitError", {
|
|
21020
|
+
...ProcessInvocationFields,
|
|
21021
|
+
stream: Schema$1.Literals(["stdout", "stderr"]),
|
|
21022
|
+
maxBytes: Schema$1.Number,
|
|
21023
|
+
observedBytes: Schema$1.Number
|
|
21024
|
+
}) {
|
|
21025
|
+
get message() {
|
|
21026
|
+
return `Process ${formatProcessInvocation(this)} ${this.stream} produced ${this.observedBytes} bytes, exceeding the ${this.maxBytes} byte limit`;
|
|
21027
|
+
}
|
|
21028
|
+
};
|
|
21029
|
+
var ProcessReadError = class extends Schema$1.TaggedErrorClass()("ProcessReadError", {
|
|
21030
|
+
...ProcessInvocationFields,
|
|
21031
|
+
stream: Schema$1.Literals([
|
|
21032
|
+
"stdout",
|
|
21033
|
+
"stderr",
|
|
21034
|
+
"exitCode"
|
|
21035
|
+
]),
|
|
21036
|
+
cause: Schema$1.Defect()
|
|
21037
|
+
}) {
|
|
21038
|
+
get message() {
|
|
21039
|
+
return `Failed to read ${this.stream} for process ${formatProcessInvocation(this)}`;
|
|
21040
|
+
}
|
|
21041
|
+
};
|
|
21042
|
+
var ProcessTimeoutError = class extends Schema$1.TaggedErrorClass()("ProcessTimeoutError", {
|
|
21043
|
+
...ProcessInvocationFields,
|
|
21044
|
+
timeoutMs: Schema$1.Number
|
|
21045
|
+
}) {
|
|
21046
|
+
get message() {
|
|
21047
|
+
return `Process ${formatProcessInvocation(this)} timed out after ${this.timeoutMs}ms`;
|
|
21048
|
+
}
|
|
21049
|
+
};
|
|
21050
|
+
Schema$1.Union([
|
|
21051
|
+
ProcessSpawnError,
|
|
21052
|
+
ProcessStdinError,
|
|
21053
|
+
ProcessOutputLimitError,
|
|
21054
|
+
ProcessReadError,
|
|
21055
|
+
ProcessTimeoutError
|
|
21056
|
+
]);
|
|
21057
|
+
var ProcessRunner = class extends Context.Service()("@p4code/cli/processRunner") {};
|
|
21058
|
+
const DEFAULT_TIMEOUT = "60 seconds";
|
|
21059
|
+
const DEFAULT_MAX_OUTPUT_BYTES$2 = 8 * 1024 * 1024;
|
|
21060
|
+
const WINDOWS_COMMAND_NOT_FOUND_PATTERNS = [
|
|
21061
|
+
/is not recognized as an internal or external command/i,
|
|
21062
|
+
/n.o . reconhecido como um comando interno/i,
|
|
21063
|
+
/non . riconosciuto come comando interno o esterno/i,
|
|
21064
|
+
/n.est pas reconnu en tant que commande interne/i,
|
|
21065
|
+
/no se reconoce como un comando interno o externo/i,
|
|
21066
|
+
/wird nicht als interner oder externer befehl/i
|
|
21067
|
+
];
|
|
21068
|
+
function hasWindowsCommandNotFoundMessage(output) {
|
|
21069
|
+
return WINDOWS_COMMAND_NOT_FOUND_PATTERNS.some((pattern) => pattern.test(output));
|
|
21070
|
+
}
|
|
21071
|
+
const isWindowsCommandNotFound = Effect.fn("processRunner.isWindowsCommandNotFound")(function* (code, stderr) {
|
|
21072
|
+
if ((yield* HostProcessPlatform) !== "win32") return false;
|
|
21073
|
+
if (code === 9009) return true;
|
|
21074
|
+
return hasWindowsCommandNotFoundMessage(stderr);
|
|
21075
|
+
});
|
|
21076
|
+
const collectText = Effect.fn("processRunner.collectText")(function* (input) {
|
|
21077
|
+
const stream = input.stream.pipe(Stream.mapError((cause) => new ProcessReadError({
|
|
21078
|
+
command: input.command,
|
|
21079
|
+
argumentCount: input.args.length,
|
|
21080
|
+
cwd: input.cwd,
|
|
21081
|
+
spawnCwd: input.spawnCwd,
|
|
21082
|
+
stream: input.streamName,
|
|
21083
|
+
cause
|
|
21084
|
+
})));
|
|
21085
|
+
if (input.outputMode === "truncate") return yield* collectUint8StreamText({
|
|
21086
|
+
stream,
|
|
21087
|
+
maxBytes: input.maxOutputBytes,
|
|
21088
|
+
truncatedMarker: input.truncatedMarker
|
|
21089
|
+
});
|
|
21090
|
+
return yield* stream.pipe(Stream.runFoldEffect(() => ({
|
|
21091
|
+
chunks: [],
|
|
21092
|
+
bytes: 0
|
|
21093
|
+
}), (state, chunk) => {
|
|
21094
|
+
const remainingBytes = input.maxOutputBytes - state.bytes;
|
|
21095
|
+
if (chunk.byteLength > remainingBytes) return Effect.fail(new ProcessOutputLimitError({
|
|
21096
|
+
command: input.command,
|
|
21097
|
+
argumentCount: input.args.length,
|
|
21098
|
+
cwd: input.cwd,
|
|
21099
|
+
spawnCwd: input.spawnCwd,
|
|
21100
|
+
stream: input.streamName,
|
|
21101
|
+
maxBytes: input.maxOutputBytes,
|
|
21102
|
+
observedBytes: state.bytes + chunk.byteLength
|
|
21103
|
+
}));
|
|
21104
|
+
state.chunks.push(chunk);
|
|
21105
|
+
return Effect.succeed({
|
|
21106
|
+
chunks: state.chunks,
|
|
21107
|
+
bytes: state.bytes + chunk.byteLength
|
|
21108
|
+
});
|
|
21109
|
+
}), Effect.map((state) => ({
|
|
21110
|
+
text: Buffer.concat(state.chunks, state.bytes).toString("utf8"),
|
|
21111
|
+
bytes: state.bytes,
|
|
21112
|
+
truncated: false
|
|
21113
|
+
})));
|
|
21114
|
+
});
|
|
21115
|
+
function finalizeRunProcess(effect, input) {
|
|
21116
|
+
const timeout = Duration.fromInputUnsafe(input.timeout ?? DEFAULT_TIMEOUT);
|
|
21117
|
+
const timeoutBehavior = input.timeoutBehavior ?? "error";
|
|
21118
|
+
return effect.pipe(Effect.scoped, Effect.timeoutOption(timeout), Effect.flatMap((result) => {
|
|
21119
|
+
if (Option.isSome(result)) return Effect.succeed(result.value);
|
|
21120
|
+
if (timeoutBehavior === "timedOutResult") return Effect.succeed({
|
|
21121
|
+
stdout: "",
|
|
21122
|
+
stderr: "",
|
|
21123
|
+
code: null,
|
|
21124
|
+
timedOut: true,
|
|
21125
|
+
stdoutTruncated: false,
|
|
21126
|
+
stderrTruncated: false
|
|
21127
|
+
});
|
|
21128
|
+
return Effect.fail(new ProcessTimeoutError({
|
|
21129
|
+
command: input.command,
|
|
21130
|
+
argumentCount: input.args.length,
|
|
21131
|
+
cwd: input.cwd,
|
|
21132
|
+
spawnCwd: input.spawnCwd,
|
|
21133
|
+
timeoutMs: Duration.toMillis(timeout)
|
|
21134
|
+
}));
|
|
21135
|
+
}));
|
|
21136
|
+
}
|
|
21137
|
+
const runProcessCore = Effect.fn("processRunner.runProcessCore")(function* (spawner, input) {
|
|
21138
|
+
const maxOutputBytes = input.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES$2;
|
|
21139
|
+
const outputMode = input.outputMode ?? "error";
|
|
21140
|
+
const truncatedMarker = input.truncatedMarker ?? "";
|
|
21141
|
+
const extendEnv = input.env !== void 0;
|
|
21142
|
+
const spawnCommand = yield* resolveSpawnCommand(input.command, input.args, input.env === void 0 ? {} : {
|
|
21143
|
+
env: input.env,
|
|
21144
|
+
extendEnv
|
|
21145
|
+
});
|
|
21146
|
+
const child = yield* spawner.spawn(ChildProcess$1.make(spawnCommand.command, spawnCommand.args, {
|
|
21147
|
+
...input.spawnCwd ?? input.cwd ? { cwd: input.spawnCwd ?? input.cwd } : {},
|
|
21148
|
+
...input.env !== void 0 ? {
|
|
21149
|
+
env: input.env,
|
|
21150
|
+
extendEnv
|
|
21151
|
+
} : {},
|
|
21152
|
+
shell: spawnCommand.shell
|
|
21153
|
+
})).pipe(Effect.mapError((cause) => new ProcessSpawnError({
|
|
21154
|
+
command: input.command,
|
|
21155
|
+
argumentCount: input.args.length,
|
|
21156
|
+
cwd: input.cwd,
|
|
21157
|
+
spawnCwd: input.spawnCwd,
|
|
21158
|
+
resolvedCommand: spawnCommand.command,
|
|
21159
|
+
resolvedArgumentCount: spawnCommand.args.length,
|
|
21160
|
+
shell: spawnCommand.shell,
|
|
21161
|
+
cause
|
|
21162
|
+
})));
|
|
21163
|
+
const stdin = input.stdin;
|
|
21164
|
+
const writeStdin = stdin === void 0 ? Effect.void : Stream.run(Stream.encodeText(Stream.make(stdin)), child.stdin).pipe(Effect.mapError((cause) => new ProcessStdinError({
|
|
21165
|
+
command: input.command,
|
|
21166
|
+
argumentCount: input.args.length,
|
|
21167
|
+
cwd: input.cwd,
|
|
21168
|
+
spawnCwd: input.spawnCwd,
|
|
21169
|
+
stdinBytes: Buffer.byteLength(stdin),
|
|
21170
|
+
cause
|
|
21171
|
+
})));
|
|
21172
|
+
const [stdout, stderr] = yield* Effect.all([
|
|
21173
|
+
collectText({
|
|
21174
|
+
command: input.command,
|
|
21175
|
+
args: input.args,
|
|
21176
|
+
cwd: input.cwd,
|
|
21177
|
+
spawnCwd: input.spawnCwd,
|
|
21178
|
+
streamName: "stdout",
|
|
21179
|
+
stream: child.stdout,
|
|
21180
|
+
maxOutputBytes,
|
|
21181
|
+
outputMode,
|
|
21182
|
+
truncatedMarker
|
|
21183
|
+
}),
|
|
21184
|
+
collectText({
|
|
21185
|
+
command: input.command,
|
|
21186
|
+
args: input.args,
|
|
21187
|
+
cwd: input.cwd,
|
|
21188
|
+
spawnCwd: input.spawnCwd,
|
|
21189
|
+
streamName: "stderr",
|
|
21190
|
+
stream: child.stderr,
|
|
21191
|
+
maxOutputBytes,
|
|
21192
|
+
outputMode,
|
|
21193
|
+
truncatedMarker
|
|
21194
|
+
}),
|
|
21195
|
+
writeStdin
|
|
21196
|
+
], { concurrency: "unbounded" });
|
|
21197
|
+
const exitCode = yield* child.exitCode.pipe(Effect.mapError((cause) => new ProcessReadError({
|
|
21198
|
+
command: input.command,
|
|
21199
|
+
argumentCount: input.args.length,
|
|
21200
|
+
cwd: input.cwd,
|
|
21201
|
+
spawnCwd: input.spawnCwd,
|
|
21202
|
+
stream: "exitCode",
|
|
21203
|
+
cause
|
|
21204
|
+
})));
|
|
21205
|
+
return {
|
|
21206
|
+
stdout: stdout.text,
|
|
21207
|
+
stderr: stderr.text,
|
|
21208
|
+
code: exitCode,
|
|
21209
|
+
timedOut: false,
|
|
21210
|
+
stdoutTruncated: stdout.truncated,
|
|
21211
|
+
stderrTruncated: stderr.truncated
|
|
21212
|
+
};
|
|
21213
|
+
});
|
|
21214
|
+
const make$57 = Effect.fn("ProcessRunner.make")(function* () {
|
|
21215
|
+
const spawner = yield* ChildProcessSpawner$1.ChildProcessSpawner;
|
|
21216
|
+
const run = (input) => finalizeRunProcess(runProcessCore(spawner, input), input);
|
|
21217
|
+
return ProcessRunner.of({ run });
|
|
21218
|
+
});
|
|
21219
|
+
const layer$52 = Layer.effect(ProcessRunner, make$57());
|
|
21220
|
+
//#endregion
|
|
20310
21221
|
//#region src/project/RepositoryIdentityResolver.ts
|
|
20311
21222
|
const DEFAULT_REPOSITORY_IDENTITY_CACHE_CAPACITY = 512;
|
|
20312
21223
|
const DEFAULT_POSITIVE_CACHE_TTL = Duration.minutes(1);
|
|
@@ -20411,7 +21322,7 @@ const make$56 = Effect.fn("RepositoryIdentityResolver.make")(function* (options
|
|
|
20411
21322
|
});
|
|
20412
21323
|
return RepositoryIdentityResolver.of({ resolve });
|
|
20413
21324
|
});
|
|
20414
|
-
const layer$51 = Layer.effect(RepositoryIdentityResolver, make$56()).pipe(Layer.provide(layer$
|
|
21325
|
+
const layer$51 = Layer.effect(RepositoryIdentityResolver, make$56()).pipe(Layer.provide(layer$52));
|
|
20415
21326
|
//#endregion
|
|
20416
21327
|
//#region src/orchestration/Layers/ProjectionSnapshotQuery.ts
|
|
20417
21328
|
const decodeReadModel = Schema$1.decodeUnknownEffect(OrchestrationReadModel);
|
|
@@ -23316,6 +24227,7 @@ const make$51 = Effect.fn("cloud.boot_service.make")(function* (input) {
|
|
|
23316
24227
|
});
|
|
23317
24228
|
const activateLaunchAgent = Effect.gen(function* () {
|
|
23318
24229
|
yield* runStep("unloading the previous service", "launchctl", ["bootout", serviceTarget]).pipe(Effect.ignore);
|
|
24230
|
+
yield* runStep("enabling the service", "launchctl", ["enable", serviceTarget]).pipe(Effect.ignore);
|
|
23319
24231
|
yield* runStep("loading the service", "launchctl", [
|
|
23320
24232
|
"bootstrap",
|
|
23321
24233
|
guiDomain,
|
|
@@ -23429,7 +24341,7 @@ const layer$46 = (input) => Layer.effect(BootService, make$51(input));
|
|
|
23429
24341
|
//#endregion
|
|
23430
24342
|
//#region src/service/selfUpdate.ts
|
|
23431
24343
|
/**
|
|
23432
|
-
* Lets a connected client replace this server with another published `
|
|
24344
|
+
* Lets a connected client replace this server with another published `p4c`
|
|
23433
24345
|
* version over RPC — the only update path that works when the user is not at
|
|
23434
24346
|
* the machine (phone against a home server, relay-managed box). The target
|
|
23435
24347
|
* version is npm-installed into the pinned runtime and verified before
|
|
@@ -23648,7 +24560,7 @@ const make$50 = Effect.fn("cloud.server_self_update.make")(function* (options) {
|
|
|
23648
24560
|
});
|
|
23649
24561
|
return ServerSelfUpdate.of({ update });
|
|
23650
24562
|
});
|
|
23651
|
-
const layer$45 = Layer.effect(ServerSelfUpdate, make$50()).pipe(Layer.provide(layer$
|
|
24563
|
+
const layer$45 = Layer.effect(ServerSelfUpdate, make$50()).pipe(Layer.provide(layer$52));
|
|
23652
24564
|
//#endregion
|
|
23653
24565
|
//#region src/environment/ServerEnvironmentLabel.ts
|
|
23654
24566
|
const ServerEnvironmentLabelCommandProbe = Schema$1.Literals(["macos-computer-name", "linux-pretty-hostname"]);
|
|
@@ -23843,7 +24755,7 @@ const make$49 = Effect.gen(function* () {
|
|
|
23843
24755
|
* state. It intentionally has no fallback Layer.succeed value: callers must
|
|
23844
24756
|
* provide the external platform services and a ServerConfig.
|
|
23845
24757
|
*/
|
|
23846
|
-
const layer$44 = Layer.effect(ServerEnvironment, make$49).pipe(Layer.provide(layer$
|
|
24758
|
+
const layer$44 = Layer.effect(ServerEnvironment, make$49).pipe(Layer.provide(layer$52));
|
|
23847
24759
|
//#endregion
|
|
23848
24760
|
//#region src/provider/Services/ProviderSessionReaper.ts
|
|
23849
24761
|
var ProviderSessionReaper = class extends Context.Service()("@p4code/cli/provider/Services/ProviderSessionReaper") {};
|
|
@@ -24927,7 +25839,7 @@ const make$48 = Effect.gen(function* () {
|
|
|
24927
25839
|
const lifecycleEvents = yield* ServerLifecycleEvents;
|
|
24928
25840
|
const serverSettings = yield* ServerSettingsService;
|
|
24929
25841
|
const serverEnvironment = yield* ServerEnvironment;
|
|
24930
|
-
const
|
|
25842
|
+
const assetSync = yield* AssetSync;
|
|
24931
25843
|
const hubLink = yield* HubLink;
|
|
24932
25844
|
const crypto = yield* Crypto.Crypto;
|
|
24933
25845
|
const commandGate = yield* makeCommandGate;
|
|
@@ -24949,13 +25861,13 @@ const make$48 = Effect.gen(function* () {
|
|
|
24949
25861
|
environmentVariable: error.environmentVariable,
|
|
24950
25862
|
cause: error.cause
|
|
24951
25863
|
})), Effect.forkScoped));
|
|
24952
|
-
yield* Effect.logDebug("startup phase: syncing user-scope
|
|
25864
|
+
yield* Effect.logDebug("startup phase: syncing user-scope agent assets");
|
|
24953
25865
|
yield* runStartupPhase("skills.sync", Effect.gen(function* () {
|
|
24954
|
-
if ((yield* hubLink.current).syncMode !== "auto") return yield* Effect.logDebug("
|
|
24955
|
-
const report = yield*
|
|
25866
|
+
if ((yield* hubLink.current).syncMode !== "auto") return yield* Effect.logDebug("asset sync is paused; skipping the automatic pass");
|
|
25867
|
+
const report = yield* assetSync.run;
|
|
24956
25868
|
if (!report.ran) return;
|
|
24957
|
-
yield* Effect.log("
|
|
24958
|
-
|
|
25869
|
+
yield* Effect.log("asset sync", {
|
|
25870
|
+
roots: report.roots,
|
|
24959
25871
|
materialized: report.materialized,
|
|
24960
25872
|
published: report.published,
|
|
24961
25873
|
removed: report.removed,
|
|
@@ -25375,7 +26287,7 @@ const ProjectCliRuntimeLive = Layer.mergeAll(layer$42, OrchestrationLayerLive.pi
|
|
|
25375
26287
|
const PROJECT_CLI_LIVE_SERVER_TIMEOUT = Duration.seconds(1);
|
|
25376
26288
|
const withProjectCliSessionToken = (environmentAuth, run) => Effect.acquireUseRelease(environmentAuth.issueSession({
|
|
25377
26289
|
scopes: AuthAdministrativeScopes,
|
|
25378
|
-
label: "
|
|
26290
|
+
label: "p4c project cli"
|
|
25379
26291
|
}), (issued) => run(issued.token), (issued) => environmentAuth.revokeSession(issued.sessionId).pipe(Effect.ignore({ log: true })));
|
|
25380
26292
|
const withProjectCliLiveServerTimeout = (effect) => effect.pipe(Effect.timeout(PROJECT_CLI_LIVE_SERVER_TIMEOUT));
|
|
25381
26293
|
const makeLiveServerClient = (origin) => HttpApiClient.make(EnvironmentHttpApi, { baseUrl: origin });
|
|
@@ -25462,7 +26374,7 @@ const runProjectMutation = Effect.fn("runProjectMutation")(function* (flags, run
|
|
|
25462
26374
|
});
|
|
25463
26375
|
yield* Console.log(output);
|
|
25464
26376
|
}));
|
|
25465
|
-
const offlineRuntimeLayer = ProjectCliRuntimeLive.pipe(Layer.provide(layer$
|
|
26377
|
+
const offlineRuntimeLayer = ProjectCliRuntimeLive.pipe(Layer.provide(layer$66(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)));
|
|
25466
26378
|
return yield* Effect.gen(function* () {
|
|
25467
26379
|
const snapshot = yield* getOfflineSnapshot();
|
|
25468
26380
|
const orchestrationEngine = yield* OrchestrationEngineService;
|
|
@@ -25473,7 +26385,7 @@ const runProjectMutation = Effect.fn("runProjectMutation")(function* (flags, run
|
|
|
25473
26385
|
});
|
|
25474
26386
|
yield* Console.log(output);
|
|
25475
26387
|
}).pipe(Effect.provide(offlineRuntimeLayer));
|
|
25476
|
-
}).pipe(Effect.provide(Layer.mergeAll(runtimeLayer, layer$42).pipe(Layer.provideMerge(FetchHttpClient.layer), Layer.provide(layer$
|
|
26388
|
+
}).pipe(Effect.provide(Layer.mergeAll(runtimeLayer, layer$42).pipe(Layer.provideMerge(FetchHttpClient.layer), Layer.provide(layer$66(config)), Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)))));
|
|
25477
26389
|
});
|
|
25478
26390
|
const projectAddCommand = Command.make("add", {
|
|
25479
26391
|
...projectLocationFlags,
|
|
@@ -28768,7 +29680,7 @@ const make$43 = Effect.gen(function* () {
|
|
|
28768
29680
|
});
|
|
28769
29681
|
return VcsProcess.of({ run });
|
|
28770
29682
|
});
|
|
28771
|
-
const layer$38 = Layer.effect(VcsProcess, make$43).pipe(Layer.provide(layer$
|
|
29683
|
+
const layer$38 = Layer.effect(VcsProcess, make$43).pipe(Layer.provide(layer$52));
|
|
28772
29684
|
//#endregion
|
|
28773
29685
|
//#region src/vcs/VcsDriver.ts
|
|
28774
29686
|
var VcsDriver = class extends Context.Service()("@p4code/cli/vcs/VcsDriver") {};
|
|
@@ -32549,7 +33461,7 @@ const makeWithOptions$1 = Effect.fn("TerminalManager.makeWithOptions")(function*
|
|
|
32549
33461
|
subscribeMetadata
|
|
32550
33462
|
});
|
|
32551
33463
|
});
|
|
32552
|
-
const layer$30 = Layer.effect(TerminalManager, make$35()).pipe(Layer.provide(layer$
|
|
33464
|
+
const layer$30 = Layer.effect(TerminalManager, make$35()).pipe(Layer.provide(layer$52));
|
|
32553
33465
|
//#endregion
|
|
32554
33466
|
//#region src/mcp/McpInvocationContext.ts
|
|
32555
33467
|
var McpInvocationContext = class extends Context.Service()("@p4code/cli/mcp/McpInvocationContext") {};
|
|
@@ -39500,7 +40412,7 @@ const make$9 = Effect.gen(function* () {
|
|
|
39500
40412
|
const layer$4 = Layer.effect(SourceControlRepositoryService, make$9);
|
|
39501
40413
|
//#endregion
|
|
39502
40414
|
//#region src/ws.ts
|
|
39503
|
-
/** Matches `
|
|
40415
|
+
/** Matches `p4c hub token add`, so a token minted here and one minted there are the same thing. */
|
|
39504
40416
|
const HUB_TOKEN_BYTES = 32;
|
|
39505
40417
|
const isOrchestrationDispatchCommandError = Schema$1.is(OrchestrationDispatchCommandError);
|
|
39506
40418
|
const nowIso$5 = Effect.map(DateTime.now, DateTime.formatIso);
|
|
@@ -39632,6 +40544,13 @@ const RPC_REQUIRED_SCOPE = /* @__PURE__ */ new Map([
|
|
|
39632
40544
|
[WS_METHODS.skillsPublish, AuthOrchestrationOperateScope],
|
|
39633
40545
|
[WS_METHODS.skillsPublishAll, AuthOrchestrationOperateScope],
|
|
39634
40546
|
[WS_METHODS.skillsUnpublish, AuthOrchestrationOperateScope],
|
|
40547
|
+
[WS_METHODS.assetsRead, AuthOrchestrationReadScope],
|
|
40548
|
+
[WS_METHODS.assetsSave, AuthOrchestrationOperateScope],
|
|
40549
|
+
[WS_METHODS.assetsDelete, AuthOrchestrationOperateScope],
|
|
40550
|
+
[WS_METHODS.mcpList, AuthOrchestrationReadScope],
|
|
40551
|
+
[WS_METHODS.mcpSave, AuthOrchestrationOperateScope],
|
|
40552
|
+
[WS_METHODS.mcpRemove, AuthOrchestrationOperateScope],
|
|
40553
|
+
[WS_METHODS.mcpSetSecret, AuthOrchestrationOperateScope],
|
|
39635
40554
|
[WS_METHODS.serverGetSettings, AuthOrchestrationReadScope],
|
|
39636
40555
|
[WS_METHODS.serverUpdateSettings, AuthOrchestrationOperateScope],
|
|
39637
40556
|
[WS_METHODS.serverDiscoverSourceControl, AuthOrchestrationReadScope],
|
|
@@ -39742,7 +40661,8 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
|
|
|
39742
40661
|
const lifecycleEvents = yield* ServerLifecycleEvents;
|
|
39743
40662
|
const serverSettings = yield* ServerSettingsService;
|
|
39744
40663
|
const hubLink = yield* HubLink;
|
|
39745
|
-
const
|
|
40664
|
+
const assetSync = yield* AssetSync;
|
|
40665
|
+
const mcpRegistry = yield* McpRegistry;
|
|
39746
40666
|
const startup = yield* ServerRuntimeStartup;
|
|
39747
40667
|
const workspaceEntries = yield* WorkspaceEntries;
|
|
39748
40668
|
const workspaceFileSystem = yield* WorkspaceFileSystem;
|
|
@@ -40204,11 +41124,11 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
|
|
|
40204
41124
|
}), { "rpc.aggregate": "tasks" }),
|
|
40205
41125
|
[WS_METHODS.tasksDelete]: ({ taskId }) => observeRpcEffect$1(WS_METHODS.tasksDelete, taskRepository.deleteById({ taskId }).pipe(Effect.catch(toTaskStoreError("tasks.delete")), Effect.as({})), { "rpc.aggregate": "tasks" }),
|
|
40206
41126
|
[WS_METHODS.subscribeTasks]: (_input) => observeRpcStreamEffect$1(WS_METHODS.subscribeTasks, Effect.succeed(taskRepository.streamWithSnapshot.pipe(Stream.catch((cause) => Stream.fromEffect(toTaskStoreError("tasks.subscribe")(cause))))), { "rpc.aggregate": "tasks" }),
|
|
40207
|
-
[WS_METHODS.hubGetSyncStatus]: (_input) => observeRpcEffect$1(WS_METHODS.hubGetSyncStatus,
|
|
40208
|
-
[WS_METHODS.hubConnect]: (input) => observeRpcEffect$1(WS_METHODS.hubConnect, hubLink.connect(input).pipe(Effect.mapError((cause) => new HubLinkError({ detail: cause.message })), Effect.andThen(
|
|
40209
|
-
[WS_METHODS.hubDisconnect]: (_input) => observeRpcEffect$1(WS_METHODS.hubDisconnect, hubLink.disconnect.pipe(Effect.mapError((cause) => new HubLinkError({ detail: cause.message })), Effect.andThen(
|
|
40210
|
-
[WS_METHODS.hubSetSyncMode]: ({ mode }) => observeRpcEffect$1(WS_METHODS.hubSetSyncMode, hubLink.setSyncMode(mode).pipe(Effect.mapError((cause) => new HubLinkError({ detail: cause.message })), Effect.andThen(
|
|
40211
|
-
[WS_METHODS.hubSetShareMode]: ({ mode }) => observeRpcEffect$1(WS_METHODS.hubSetShareMode, hubLink.setShareMode(mode).pipe(Effect.mapError((cause) => new HubLinkError({ detail: cause.message })), Effect.andThen(
|
|
41127
|
+
[WS_METHODS.hubGetSyncStatus]: (_input) => observeRpcEffect$1(WS_METHODS.hubGetSyncStatus, assetSync.status, { "rpc.aggregate": "hub" }),
|
|
41128
|
+
[WS_METHODS.hubConnect]: (input) => observeRpcEffect$1(WS_METHODS.hubConnect, hubLink.connect(input).pipe(Effect.mapError((cause) => new HubLinkError({ detail: cause.message })), Effect.andThen(assetSync.status)), { "rpc.aggregate": "hub" }),
|
|
41129
|
+
[WS_METHODS.hubDisconnect]: (_input) => observeRpcEffect$1(WS_METHODS.hubDisconnect, hubLink.disconnect.pipe(Effect.mapError((cause) => new HubLinkError({ detail: cause.message })), Effect.andThen(assetSync.status)), { "rpc.aggregate": "hub" }),
|
|
41130
|
+
[WS_METHODS.hubSetSyncMode]: ({ mode }) => observeRpcEffect$1(WS_METHODS.hubSetSyncMode, hubLink.setSyncMode(mode).pipe(Effect.mapError((cause) => new HubLinkError({ detail: cause.message })), Effect.andThen(assetSync.status)), { "rpc.aggregate": "hub" }),
|
|
41131
|
+
[WS_METHODS.hubSetShareMode]: ({ mode }) => observeRpcEffect$1(WS_METHODS.hubSetShareMode, hubLink.setShareMode(mode).pipe(Effect.mapError((cause) => new HubLinkError({ detail: cause.message })), Effect.andThen(assetSync.status)), { "rpc.aggregate": "hub" }),
|
|
40212
41132
|
[WS_METHODS.hubMintToken]: (_input) => observeRpcEffect$1(WS_METHODS.hubMintToken, Effect.gen(function* () {
|
|
40213
41133
|
const token = yield* crypto.randomBytes(HUB_TOKEN_BYTES).pipe(Effect.map((bytes) => Buffer.from(bytes).toString("base64url")), Effect.orDie);
|
|
40214
41134
|
return {
|
|
@@ -40218,28 +41138,49 @@ const makeWsRpcLayer = (currentSession, previewAutomationBroker) => WsRpcGroup.t
|
|
|
40218
41138
|
}), { "rpc.aggregate": "hub" }),
|
|
40219
41139
|
[WS_METHODS.skillsSync]: (_input) => observeRpcEffect$1(WS_METHODS.skillsSync, Effect.gen(function* () {
|
|
40220
41140
|
return {
|
|
40221
|
-
report: yield*
|
|
40222
|
-
status: yield*
|
|
41141
|
+
report: yield* assetSync.run,
|
|
41142
|
+
status: yield* assetSync.status
|
|
40223
41143
|
};
|
|
40224
41144
|
}), { "rpc.aggregate": "skills" }),
|
|
40225
|
-
[WS_METHODS.skillsPublish]: (
|
|
41145
|
+
[WS_METHODS.skillsPublish]: (ref) => observeRpcEffect$1(WS_METHODS.skillsPublish, Effect.gen(function* () {
|
|
40226
41146
|
return {
|
|
40227
|
-
report: yield*
|
|
40228
|
-
status: yield*
|
|
41147
|
+
report: yield* assetSync.publish(ref),
|
|
41148
|
+
status: yield* assetSync.status
|
|
40229
41149
|
};
|
|
40230
41150
|
}), { "rpc.aggregate": "skills" }),
|
|
40231
41151
|
[WS_METHODS.skillsPublishAll]: (_input) => observeRpcEffect$1(WS_METHODS.skillsPublishAll, Effect.gen(function* () {
|
|
40232
41152
|
return {
|
|
40233
|
-
report: yield*
|
|
40234
|
-
status: yield*
|
|
41153
|
+
report: yield* assetSync.publishAll,
|
|
41154
|
+
status: yield* assetSync.status
|
|
40235
41155
|
};
|
|
40236
41156
|
}), { "rpc.aggregate": "skills" }),
|
|
40237
|
-
[WS_METHODS.skillsUnpublish]: (
|
|
41157
|
+
[WS_METHODS.skillsUnpublish]: (ref) => observeRpcEffect$1(WS_METHODS.skillsUnpublish, Effect.gen(function* () {
|
|
40238
41158
|
return {
|
|
40239
|
-
report: yield*
|
|
40240
|
-
status: yield*
|
|
41159
|
+
report: yield* assetSync.unpublish(ref),
|
|
41160
|
+
status: yield* assetSync.status
|
|
40241
41161
|
};
|
|
40242
41162
|
}), { "rpc.aggregate": "skills" }),
|
|
41163
|
+
[WS_METHODS.assetsRead]: (ref) => observeRpcEffect$1(WS_METHODS.assetsRead, assetSync.read(ref), { "rpc.aggregate": "skills" }),
|
|
41164
|
+
[WS_METHODS.assetsSave]: (input) => observeRpcEffect$1(WS_METHODS.assetsSave, Effect.gen(function* () {
|
|
41165
|
+
return {
|
|
41166
|
+
report: yield* assetSync.save(input),
|
|
41167
|
+
status: yield* assetSync.status
|
|
41168
|
+
};
|
|
41169
|
+
}), { "rpc.aggregate": "skills" }),
|
|
41170
|
+
[WS_METHODS.assetsDelete]: (input) => observeRpcEffect$1(WS_METHODS.assetsDelete, Effect.gen(function* () {
|
|
41171
|
+
return {
|
|
41172
|
+
report: yield* assetSync.deleteFromHub(input),
|
|
41173
|
+
status: yield* assetSync.status
|
|
41174
|
+
};
|
|
41175
|
+
}), { "rpc.aggregate": "skills" }),
|
|
41176
|
+
[WS_METHODS.mcpList]: (_input) => observeRpcEffect$1(WS_METHODS.mcpList, mcpRegistry.list.pipe(Effect.map((servers) => ({ servers }))), { "rpc.aggregate": "mcp" }),
|
|
41177
|
+
[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" }),
|
|
41178
|
+
[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" }),
|
|
41179
|
+
[WS_METHODS.mcpSetSecret]: ({ name, slot, value }) => observeRpcEffect$1(WS_METHODS.mcpSetSecret, mcpRegistry.setSecret({
|
|
41180
|
+
name,
|
|
41181
|
+
slot,
|
|
41182
|
+
value
|
|
41183
|
+
}).pipe(Effect.andThen(mcpRegistry.list.pipe(Effect.map((servers) => ({ servers }))))), { "rpc.aggregate": "mcp" }),
|
|
40243
41184
|
[WS_METHODS.serverGetSettings]: (_input) => observeRpcEffect$1(WS_METHODS.serverGetSettings, serverSettings.getSettings.pipe(Effect.map(redactServerSettingsForClient)), { "rpc.aggregate": "server" }),
|
|
40244
41185
|
[WS_METHODS.serverUpdateSettings]: ({ patch }) => observeRpcEffect$1(WS_METHODS.serverUpdateSettings, serverSettings.updateSettings(patch).pipe(Effect.map(redactServerSettingsForClient)), { "rpc.aggregate": "server" }),
|
|
40245
41186
|
[WS_METHODS.serverDiscoverSourceControl]: (_input) => observeRpcEffect$1(WS_METHODS.serverDiscoverSourceControl, sourceControlDiscovery.discover, { "rpc.aggregate": "server" }),
|
|
@@ -42440,7 +43381,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () {
|
|
|
42440
43381
|
};
|
|
42441
43382
|
});
|
|
42442
43383
|
var OpenCodeRuntime = class extends Context.Service()("@p4code/cli/provider/opencodeRuntime") {};
|
|
42443
|
-
const OpenCodeRuntimeLive = Layer.effect(OpenCodeRuntime, makeOpenCodeRuntime).pipe(Layer.provide(layer$
|
|
43384
|
+
const OpenCodeRuntimeLive = Layer.effect(OpenCodeRuntime, makeOpenCodeRuntime).pipe(Layer.provide(layer$67));
|
|
42444
43385
|
//#endregion
|
|
42445
43386
|
//#region src/textGeneration/TextGenerationUtils.ts
|
|
42446
43387
|
const isTextGenerationError$2 = Schema$1.is(TextGenerationError);
|
|
@@ -43432,14 +44373,15 @@ const checkClaudeProviderStatus = Effect.fn("checkClaudeProviderStatus")(functio
|
|
|
43432
44373
|
const versionProbe = yield* runClaudeCommand(claudeSettings, ["--version"], resolvedEnvironment).pipe(Effect.timeoutOption(DEFAULT_TIMEOUT_MS), Effect.result);
|
|
43433
44374
|
if (Result.isFailure(versionProbe)) {
|
|
43434
44375
|
const error = versionProbe.failure;
|
|
43435
|
-
|
|
44376
|
+
const missing = isCommandMissingCause(error);
|
|
44377
|
+
yield* (missing ? Effect.logDebug : Effect.logWarning)("Claude Agent CLI health check failed.", { errorTag: error._tag });
|
|
43436
44378
|
return buildServerProvider({
|
|
43437
44379
|
presentation: CLAUDE_PRESENTATION,
|
|
43438
44380
|
enabled: claudeSettings.enabled,
|
|
43439
44381
|
checkedAt,
|
|
43440
44382
|
models: allModels,
|
|
43441
44383
|
probe: {
|
|
43442
|
-
installed: !
|
|
44384
|
+
installed: !missing,
|
|
43443
44385
|
version: null,
|
|
43444
44386
|
status: "error",
|
|
43445
44387
|
auth: { status: "unknown" },
|
|
@@ -45861,6 +46803,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
|
|
|
45861
46803
|
...ultracode ? { ultracode: true } : {}
|
|
45862
46804
|
};
|
|
45863
46805
|
const mcpSession = readMcpProviderSession(input.threadId);
|
|
46806
|
+
const externalMcpServers = options?.resolveMcpServers === void 0 ? {} : yield* options.resolveMcpServers;
|
|
45864
46807
|
const queryOptions = {
|
|
45865
46808
|
...input.cwd ? { cwd: input.cwd } : {},
|
|
45866
46809
|
...apiModelId ? { model: apiModelId } : {},
|
|
@@ -45881,11 +46824,14 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
|
|
|
45881
46824
|
env: claudeEnvironment,
|
|
45882
46825
|
...input.cwd ? { additionalDirectories: [input.cwd] } : {},
|
|
45883
46826
|
...Object.keys(extraArgs).length > 0 ? { extraArgs } : {},
|
|
45884
|
-
...mcpSession ? { mcpServers: {
|
|
45885
|
-
|
|
45886
|
-
|
|
45887
|
-
|
|
45888
|
-
|
|
46827
|
+
...mcpSession || Object.keys(externalMcpServers).length > 0 ? { mcpServers: {
|
|
46828
|
+
...externalMcpServers,
|
|
46829
|
+
...mcpSession ? { "p4-code": {
|
|
46830
|
+
type: "http",
|
|
46831
|
+
url: mcpSession.endpoint,
|
|
46832
|
+
headers: { Authorization: mcpSession.authorizationHeader }
|
|
46833
|
+
} } : {}
|
|
46834
|
+
} } : {}
|
|
45889
46835
|
};
|
|
45890
46836
|
yield* Effect.annotateCurrentSpan({
|
|
45891
46837
|
"provider.kind": PROVIDER$5,
|
|
@@ -46345,6 +47291,7 @@ const ClaudeDriver = {
|
|
|
46345
47291
|
const adapter = yield* makeClaudeAdapter(effectiveConfig, {
|
|
46346
47292
|
instanceId,
|
|
46347
47293
|
environment: processEnv,
|
|
47294
|
+
resolveMcpServers: (yield* McpRegistry).resolveForSession,
|
|
46348
47295
|
...eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {}
|
|
46349
47296
|
});
|
|
46350
47297
|
const textGeneration = yield* makeClaudeTextGeneration(effectiveConfig, processEnv);
|
|
@@ -72617,14 +73564,15 @@ const checkCursorProviderStatus = Effect.fn("checkCursorProviderStatus")(functio
|
|
|
72617
73564
|
const aboutProbe = yield* runCursorAboutCommand(cursorSettings, environment).pipe(Effect.timeoutOption(ABOUT_TIMEOUT_MS), Effect.result);
|
|
72618
73565
|
if (Result.isFailure(aboutProbe)) {
|
|
72619
73566
|
const error = aboutProbe.failure;
|
|
72620
|
-
|
|
73567
|
+
const missing = isCommandMissingCause(error);
|
|
73568
|
+
yield* (missing ? Effect.logDebug : Effect.logWarning)("Cursor Agent CLI health check failed.", { errorTag: error._tag });
|
|
72621
73569
|
return buildServerProvider({
|
|
72622
73570
|
presentation: CURSOR_PRESENTATION,
|
|
72623
73571
|
enabled: cursorSettings.enabled,
|
|
72624
73572
|
checkedAt,
|
|
72625
73573
|
models: fallbackModels,
|
|
72626
73574
|
probe: {
|
|
72627
|
-
installed: !
|
|
73575
|
+
installed: !missing,
|
|
72628
73576
|
version: null,
|
|
72629
73577
|
status: "error",
|
|
72630
73578
|
auth: { status: "unknown" },
|
|
@@ -75261,14 +76209,15 @@ const checkGrokProviderStatus = Effect.fn("checkGrokProviderStatus")(function* (
|
|
|
75261
76209
|
const versionResult = yield* runGrokVersionCommand(grokSettings, environment).pipe(Effect.timeoutOption(VERSION_PROBE_TIMEOUT_MS), Effect.result);
|
|
75262
76210
|
if (Result.isFailure(versionResult)) {
|
|
75263
76211
|
const error = versionResult.failure;
|
|
75264
|
-
|
|
76212
|
+
const missing = isCommandMissingCause(error);
|
|
76213
|
+
yield* (missing ? Effect.logDebug : Effect.logWarning)("Grok CLI health check failed.", { errorTag: error._tag });
|
|
75265
76214
|
return buildServerProvider({
|
|
75266
76215
|
presentation: GROK_PRESENTATION,
|
|
75267
76216
|
enabled: grokSettings.enabled,
|
|
75268
76217
|
checkedAt,
|
|
75269
76218
|
models: fallbackModels,
|
|
75270
76219
|
probe: {
|
|
75271
|
-
installed: !
|
|
76220
|
+
installed: !missing,
|
|
75272
76221
|
version: null,
|
|
75273
76222
|
status: "error",
|
|
75274
76223
|
auth: { status: "unknown" },
|
|
@@ -81518,16 +82467,16 @@ const SourceControlRepositoryServiceLayerLive = layer$4.pipe(Layer.provideMerge(
|
|
|
81518
82467
|
const ReviewLayerLive = layer$9.pipe(Layer.provideMerge(layer$37), Layer.provideMerge(VcsDriverRegistryLayerLive));
|
|
81519
82468
|
const VcsLayerLive = Layer.empty.pipe(Layer.provideMerge(layer$36), Layer.provideMerge(VcsDriverRegistryLayerLive), Layer.provideMerge(layer$10.pipe(Layer.provide(VcsDriverRegistryLayerLive))), Layer.provideMerge(GitWorkflowLayerLive), Layer.provideMerge(ReviewLayerLive), Layer.provideMerge(SourceControlRepositoryServiceLayerLive), Layer.provideMerge(layer$11.pipe(Layer.provide(GitWorkflowLayerLive))));
|
|
81520
82469
|
const CheckpointingLayerLive = Layer.empty.pipe(Layer.provideMerge(layer$33), Layer.provideMerge(layer$34.pipe(Layer.provide(VcsDriverRegistryLayerLive))));
|
|
81521
|
-
const PortScannerLayerLive = layer$31.pipe(Layer.provide(layer$
|
|
82470
|
+
const PortScannerLayerLive = layer$31.pipe(Layer.provide(layer$52));
|
|
81522
82471
|
const TerminalLayerLive = layer$30.pipe(Layer.provide(PtyAdapterLive), Layer.provide(PortScannerLayerLive));
|
|
81523
82472
|
const PreviewLayerLive = Layer.empty.pipe(Layer.provideMerge(layer$28), Layer.provideMerge(PortScannerLayerLive));
|
|
81524
82473
|
const WorkspaceEntriesLayerLive = layer$26.pipe(Layer.provide(layer$42));
|
|
81525
82474
|
const WorkspaceFileSystemLayerLive = layer$25.pipe(Layer.provide(layer$42), Layer.provide(WorkspaceEntriesLayerLive));
|
|
81526
82475
|
const WorkspaceLayerLive = Layer.mergeAll(layer$42, WorkspaceEntriesLayerLive, WorkspaceFileSystemLayerLive);
|
|
81527
82476
|
const ProjectFaviconResolverLayerLive = layer$40.pipe(Layer.provide(layer$42), Layer.provide(layer$41));
|
|
81528
|
-
const AuthLayerLive = layer$
|
|
82477
|
+
const AuthLayerLive = layer$59.pipe(Layer.provideMerge(PersistenceLayerLive), Layer.provide(layer$63));
|
|
81529
82478
|
const ProviderRuntimeLayerLive = ProviderSessionReaperLive.pipe(Layer.provideMerge(ProviderLayerLive), Layer.provideMerge(OrchestrationLayerLive));
|
|
81530
|
-
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$50), Layer.provideMerge(ProviderRegistryLive), Layer.provideMerge(ProviderInstanceRegistryHydrationLive), Layer.provideMerge(ProviderEventLoggersLive), Layer.provideMerge(OpenCodeRuntimeLive), Layer.provideMerge(layer$
|
|
82479
|
+
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$50), Layer.provideMerge(ProviderRegistryLive), Layer.provideMerge(ProviderInstanceRegistryHydrationLive), Layer.provideMerge(ProviderEventLoggersLive), Layer.provideMerge(OpenCodeRuntimeLive), Layer.provideMerge(layer$57.pipe(Layer.provide(layer$63))), Layer.provideMerge(WorkspaceLayerLive), Layer.provideMerge(ProjectFaviconResolverLayerLive), Layer.provideMerge(layer$51), Layer.provideMerge(layer$44), Layer.provideMerge(AuthLayerLive), Layer.provideMerge(layer$56), Layer.provideMerge(layer$63)).pipe(Layer.provideMerge(layer$8), Layer.provideMerge(layer$7), Layer.provideMerge(layer$6), Layer.provideMerge(layer$47), Layer.provideMerge(layer$49), Layer.provideMerge(layer$48), Layer.provide(layer$67));
|
|
81531
82480
|
/**
|
|
81532
82481
|
* Hub asset sync.
|
|
81533
82482
|
*
|
|
@@ -81535,8 +82484,8 @@ const RuntimeDependenciesLive = ReactorLayerLive.pipe(Layer.provideMerge(Checkpo
|
|
|
81535
82484
|
* not-configured shape, and every call reports that rather than failing — so
|
|
81536
82485
|
* this costs a server with no hub nothing but a layer.
|
|
81537
82486
|
*/
|
|
81538
|
-
const
|
|
81539
|
-
const RuntimeServicesLive = layer$43.pipe(Layer.provideMerge(
|
|
82487
|
+
const AssetSyncLive = layer$53.pipe(Layer.provideMerge(layer$54), Layer.provideMerge(layer$55));
|
|
82488
|
+
const RuntimeServicesLive = layer$43.pipe(Layer.provideMerge(AssetSyncLive), Layer.provideMerge(RuntimeDependenciesLive));
|
|
81540
82489
|
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(TaskRepositoryLive.pipe(Layer.provide(PersistenceLayerLive))), Layer.provide(ProjectionProjectRepositoryLive.pipe(Layer.provide(PersistenceLayerLive))), Layer.provide(layer$29), Layer.provide(layer$45), Layer.provide(browserApiCorsLayer), Layer.provide(httpCompressionLayer));
|
|
81541
82490
|
const makeServerLayer = Layer.unwrap(Effect.gen(function* () {
|
|
81542
82491
|
const config = yield* ServerConfig$1;
|
|
@@ -81601,7 +82550,7 @@ const bootServiceLayer = (config) => layer$46({
|
|
|
81601
82550
|
baseDir: config.baseDir,
|
|
81602
82551
|
logsDir: config.logsDir,
|
|
81603
82552
|
cliVersion: version
|
|
81604
|
-
}).pipe(Layer.provide(layer$
|
|
82553
|
+
}).pipe(Layer.provide(layer$52));
|
|
81605
82554
|
/** Install, update, or repair the service using the CLI version running this command. */
|
|
81606
82555
|
const reconcileService = Effect.fn("cli.service.reconcile")(function* () {
|
|
81607
82556
|
const service = yield* BootService;
|
|
@@ -81633,37 +82582,26 @@ function formatReconcileSuccess(input) {
|
|
|
81633
82582
|
}
|
|
81634
82583
|
function formatServiceStatus(status, cliVersion) {
|
|
81635
82584
|
if (!status.supported) return "P4Code service\n Status: unavailable on this machine\n Supported on: Linux with systemd, macOS with launchd";
|
|
81636
|
-
if (!status.installed) return "P4Code service\n Status: not installed\n Next: Run `
|
|
82585
|
+
if (!status.installed) return "P4Code service\n Status: not installed\n Next: Run `p4c service install`.";
|
|
81637
82586
|
return [
|
|
81638
82587
|
"P4Code service",
|
|
81639
82588
|
` Status: ${status.current ? `installed · @p4code/cli@${cliVersion}` : "needs an update or repair"}`,
|
|
81640
82589
|
` Unit: ${status.unitPath}`,
|
|
81641
82590
|
` Data: ${status.baseDir}`,
|
|
81642
82591
|
` Logs: ${status.logPath}`,
|
|
81643
|
-
...status.current ? [] : [" Next: Run `
|
|
82592
|
+
...status.current ? [] : [" Next: Run `p4c service update`."]
|
|
81644
82593
|
].join("\n");
|
|
81645
82594
|
}
|
|
81646
82595
|
const runServiceCommand = Effect.fn("cli.service.run")(function* (flags, run) {
|
|
81647
82596
|
const config = yield* resolveCliAuthConfig(flags, yield* GlobalFlag.LogLevel);
|
|
81648
82597
|
const baseDirNotice = yield* describeNonDefaultBaseDir(config.baseDir);
|
|
81649
82598
|
if (baseDirNotice !== null) yield* Console.error(baseDirNotice);
|
|
81650
|
-
return yield* run.pipe(Effect.tapError((error) => isBootServiceCommandError(error) ? BootService.pipe(Effect.flatMap((service) => Console.error(`Details: ${service.logPath}`))) : Effect.void), Effect.provide(Layer.mergeAll(bootServiceLayer(config), layer$
|
|
81651
|
-
});
|
|
81652
|
-
/**
|
|
81653
|
-
* Runs on every `service install` and `service update`, including the
|
|
81654
|
-
* already-current paths. Gating it on "the unit changed" meant a host whose
|
|
81655
|
-
* service was already installed — the common case after the first setup — never
|
|
81656
|
-
* got completions at all.
|
|
81657
|
-
*/
|
|
81658
|
-
const reportZshCompletionInstall = Effect.gen(function* () {
|
|
81659
|
-
const completion = yield* installZshCompletion().pipe(Effect.orElseSucceed(() => null));
|
|
81660
|
-
if (completion !== null) yield* Console.log(formatZshCompletionInstall(completion));
|
|
82599
|
+
return yield* run.pipe(Effect.tapError((error) => isBootServiceCommandError(error) ? BootService.pipe(Effect.flatMap((service) => Console.error(`Details: ${service.logPath}`))) : Effect.void), Effect.provide(Layer.mergeAll(bootServiceLayer(config), layer$52)));
|
|
81661
82600
|
});
|
|
81662
82601
|
const serviceInstallCommand = Command.make("install", projectLocationFlags).pipe(Command.withDescription("Install P4Code as a background service for this user."), Command.withHandler((flags) => runServiceCommand(flags, Effect.gen(function* () {
|
|
81663
82602
|
const result = yield* reconcileService();
|
|
81664
82603
|
if (!result.changed) {
|
|
81665
82604
|
yield* Console.log(`P4Code service is already installed with @p4code/cli@${version}.`);
|
|
81666
|
-
yield* reportZshCompletionInstall;
|
|
81667
82605
|
return;
|
|
81668
82606
|
}
|
|
81669
82607
|
yield* Console.log(formatReconcileSuccess({
|
|
@@ -81672,14 +82610,12 @@ const serviceInstallCommand = Command.make("install", projectLocationFlags).pipe
|
|
|
81672
82610
|
cliVersion: version,
|
|
81673
82611
|
platform: yield* HostProcessPlatform
|
|
81674
82612
|
}));
|
|
81675
|
-
yield* reportZshCompletionInstall;
|
|
81676
82613
|
}))));
|
|
81677
82614
|
const serviceUpdateCommand = Command.make("update", projectLocationFlags).pipe(Command.withDescription("Update or repair the background service so it runs this CLI build, then restart it."), Command.withHandler((flags) => runServiceCommand(flags, Effect.gen(function* () {
|
|
81678
82615
|
const result = yield* reconcileService();
|
|
81679
82616
|
if (!result.changed) {
|
|
81680
82617
|
yield* (yield* BootService).restart;
|
|
81681
82618
|
yield* Console.log(`Restarted the P4Code service on @p4code/cli@${version}.`);
|
|
81682
|
-
yield* reportZshCompletionInstall;
|
|
81683
82619
|
return;
|
|
81684
82620
|
}
|
|
81685
82621
|
yield* Console.log(formatReconcileSuccess({
|
|
@@ -81688,7 +82624,6 @@ const serviceUpdateCommand = Command.make("update", projectLocationFlags).pipe(C
|
|
|
81688
82624
|
cliVersion: version,
|
|
81689
82625
|
platform: yield* HostProcessPlatform
|
|
81690
82626
|
}));
|
|
81691
|
-
yield* reportZshCompletionInstall;
|
|
81692
82627
|
}))));
|
|
81693
82628
|
const serviceUninstallCommand = Command.make("uninstall", projectLocationFlags).pipe(Command.withDescription("Stop and remove the P4Code background service."), Command.withHandler((flags) => runServiceCommand(flags, Effect.gen(function* () {
|
|
81694
82629
|
const removed = yield* (yield* BootService).uninstall;
|
|
@@ -81721,7 +82656,7 @@ const serviceCommand = Command.make("service").pipe(Command.withDescription("Man
|
|
|
81721
82656
|
]));
|
|
81722
82657
|
//#endregion
|
|
81723
82658
|
//#region src/bin.ts
|
|
81724
|
-
const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, layer$
|
|
82659
|
+
const CliRuntimeLayer = Layer.mergeAll(NodeServices.layer, layer$67, FetchHttpClient.layer);
|
|
81725
82660
|
/**
|
|
81726
82661
|
* Prints an update reminder before dispatching, at most once a day.
|
|
81727
82662
|
*
|
|
@@ -81737,21 +82672,60 @@ const reportUpdateAvailable = Effect.gen(function* () {
|
|
|
81737
82672
|
stateDir: paths.stateDir
|
|
81738
82673
|
});
|
|
81739
82674
|
if (reminder !== null) yield* Console.error(reminder);
|
|
81740
|
-
const completionsHint = yield* resolveMissingCompletionsHint({ stateDir: paths.stateDir });
|
|
81741
|
-
if (completionsHint !== null) yield* Console.error(completionsHint);
|
|
81742
82675
|
}).pipe(Effect.ignore);
|
|
81743
|
-
|
|
82676
|
+
/**
|
|
82677
|
+
* `p4c`, not `p4`.
|
|
82678
|
+
*
|
|
82679
|
+
* Perforce owns `p4`, and zsh ships `_perforce` claiming it with
|
|
82680
|
+
* `#compdef p4 p4d`. Fighting for the name cost a completion installer, a
|
|
82681
|
+
* per-run "your completions are missing" hint, and an argument about which
|
|
82682
|
+
* fpath directory `compinit` would prefer — all of it maintenance for a
|
|
82683
|
+
* conflict we chose. Taking a name nothing else claims deletes the problem
|
|
82684
|
+
* rather than managing it.
|
|
82685
|
+
*/
|
|
82686
|
+
const makeCli = () => Command.make("p4c", { ...sharedServerCommandFlags }).pipe(Command.withDescription("Run the P4Code server."), Command.withHandler((flags) => runServerCommand(flags)), Command.withSubcommands([
|
|
81744
82687
|
startCommand,
|
|
81745
82688
|
serveCommand,
|
|
81746
82689
|
authCommand,
|
|
81747
|
-
completionsCommand,
|
|
81748
82690
|
hubCommand,
|
|
82691
|
+
mcpCommand,
|
|
82692
|
+
memoryCommand,
|
|
81749
82693
|
projectCommand,
|
|
81750
82694
|
serviceCommand,
|
|
81751
82695
|
skillsCommand
|
|
81752
82696
|
]));
|
|
81753
82697
|
const cli = makeCli();
|
|
81754
|
-
|
|
82698
|
+
/**
|
|
82699
|
+
* A one-line message for an expected failure, without the stack.
|
|
82700
|
+
*
|
|
82701
|
+
* Every domain error here is a `Schema.TaggedErrorClass` with a `message`
|
|
82702
|
+
* getter written for a person — "Background setup failed while loading the
|
|
82703
|
+
* service (exit code 5)" — and the surrounding command has usually already
|
|
82704
|
+
* printed the log path that holds the detail. Appending twenty frames of
|
|
82705
|
+
* bundled `dist/bin.mjs` to that adds nothing anybody can act on.
|
|
82706
|
+
*/
|
|
82707
|
+
const describeExpectedFailure = (error) => {
|
|
82708
|
+
const message = error?.message;
|
|
82709
|
+
if (typeof message === "string" && message.trim().length > 0) return message;
|
|
82710
|
+
return String(error);
|
|
82711
|
+
};
|
|
82712
|
+
/**
|
|
82713
|
+
* Expected failures print their message; defects keep their stack.
|
|
82714
|
+
*
|
|
82715
|
+
* The distinction is Effect's own, and it is the right one: a typed failure is
|
|
82716
|
+
* a condition the code anticipated and described, so a trace through a
|
|
82717
|
+
* bundled file is noise. A defect is a bug, and there the trace is the only
|
|
82718
|
+
* thing worth having.
|
|
82719
|
+
*
|
|
82720
|
+
* `CliError` is re-raised rather than handled, because `runMain` already
|
|
82721
|
+
* renders usage and validation failures better than a bare message would —
|
|
82722
|
+
* catching them here would replace "unknown flag --foo, did you mean --force"
|
|
82723
|
+
* with one flat line.
|
|
82724
|
+
*/
|
|
82725
|
+
const reportFailuresWithoutStacks = (effect) => effect.pipe(Effect.catchIf((error) => !CliError.isCliError(error), (error) => Console.error(describeExpectedFailure(error)).pipe(Effect.andThen(Effect.sync(() => {
|
|
82726
|
+
process.exitCode = 1;
|
|
82727
|
+
})))));
|
|
82728
|
+
if (import.meta.main) reportUpdateAvailable.pipe(Effect.andThen(Command.run(cli, { version })), reportFailuresWithoutStacks, Effect.scoped, Effect.provide(CliRuntimeLayer), NodeRuntime.runMain);
|
|
81755
82729
|
//#endregion
|
|
81756
82730
|
export { cli, makeCli };
|
|
81757
82731
|
|