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