borgmcp 2.9.0 → 2.10.0
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/assimilate-cmd.d.ts +13 -0
- package/dist/assimilate-cmd.d.ts.map +1 -1
- package/dist/assimilate-cmd.js +169 -10
- package/dist/assimilate-cmd.js.map +1 -1
- package/dist/assimilate-deps.d.ts.map +1 -1
- package/dist/assimilate-deps.js +26 -1
- package/dist/assimilate-deps.js.map +1 -1
- package/dist/assimilate-welcome.d.ts.map +1 -1
- package/dist/assimilate-welcome.js +1 -0
- package/dist/assimilate-welcome.js.map +1 -1
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +27 -0
- package/dist/claude.js.map +1 -1
- package/dist/cli-help.d.ts +1 -0
- package/dist/cli-help.d.ts.map +1 -1
- package/dist/cli-help.js +11 -0
- package/dist/cli-help.js.map +1 -1
- package/dist/config.d.ts +11 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +169 -13
- package/dist/config.js.map +1 -1
- package/dist/invitation-artifact.d.ts +33 -0
- package/dist/invitation-artifact.d.ts.map +1 -0
- package/dist/invitation-artifact.js +89 -0
- package/dist/invitation-artifact.js.map +1 -0
- package/dist/launch-gate.d.ts +5 -4
- package/dist/launch-gate.d.ts.map +1 -1
- package/dist/launch-gate.js +7 -8
- package/dist/launch-gate.js.map +1 -1
- package/dist/parse-assimilate-args.d.ts.map +1 -1
- package/dist/parse-assimilate-args.js +9 -3
- package/dist/parse-assimilate-args.js.map +1 -1
- package/dist/recover-enrollment-cmd.d.ts +17 -0
- package/dist/recover-enrollment-cmd.d.ts.map +1 -0
- package/dist/recover-enrollment-cmd.js +62 -0
- package/dist/recover-enrollment-cmd.js.map +1 -0
- package/dist/regen-format.d.ts +9 -0
- package/dist/regen-format.d.ts.map +1 -1
- package/dist/regen-format.js +15 -0
- package/dist/regen-format.js.map +1 -1
- package/dist/regen.js +27 -3
- package/dist/regen.js.map +1 -1
- package/dist/seat-store.d.ts.map +1 -1
- package/dist/seat-store.js +7 -0
- package/dist/seat-store.js.map +1 -1
- package/dist/server-facade.d.ts +1 -1
- package/dist/server-facade.d.ts.map +1 -1
- package/dist/server-facade.js +2 -2
- package/dist/server-facade.js.map +1 -1
- package/dist/server-handshake.d.ts +19 -3
- package/dist/server-handshake.d.ts.map +1 -1
- package/dist/server-handshake.js +86 -9
- package/dist/server-handshake.js.map +1 -1
- package/dist/server-trust.d.ts +8 -0
- package/dist/server-trust.d.ts.map +1 -1
- package/dist/server-trust.js +165 -5
- package/dist/server-trust.js.map +1 -1
- package/dist/token-store.d.ts +3 -0
- package/dist/token-store.d.ts.map +1 -1
- package/dist/token-store.js +2 -0
- package/dist/token-store.js.map +1 -1
- package/dist/unknown-subcommand.d.ts +1 -1
- package/dist/unknown-subcommand.d.ts.map +1 -1
- package/dist/unknown-subcommand.js +1 -0
- package/dist/unknown-subcommand.js.map +1 -1
- package/docs/EXTRACTION_PROVENANCE.md +11 -7
- package/docs/LOCAL_SERVER.md +12 -3
- package/docs/RELEASING.md +12 -6
- package/package.json +2 -2
- package/src/assimilate-cmd.ts +219 -24
- package/src/assimilate-deps.ts +26 -0
- package/src/assimilate-welcome.ts +1 -0
- package/src/claude.ts +22 -0
- package/src/cli-help.ts +14 -0
- package/src/config.ts +165 -10
- package/src/invitation-artifact.ts +116 -0
- package/src/launch-gate.ts +7 -8
- package/src/parse-assimilate-args.ts +11 -4
- package/src/recover-enrollment-cmd.ts +63 -0
- package/src/regen-format.ts +25 -0
- package/src/regen.ts +30 -2
- package/src/seat-store.ts +6 -0
- package/src/server-facade.ts +2 -2
- package/src/server-handshake.ts +113 -6
- package/src/server-trust.ts +180 -5
- package/src/token-store.ts +5 -0
- package/src/unknown-subcommand.ts +1 -0
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* (`--cli`, `--remote`, agent passthrough, …) still fall through to launch.
|
|
12
12
|
*/
|
|
13
13
|
/** The subcommands the claude.ts router dispatches on (lines 107-176). */
|
|
14
|
-
export declare const KNOWN_SUBCOMMANDS: readonly ["setup", "update", "assimilate", "reset-local-seat", "spawn", "sync", "cleanup", "launch-all", "server"];
|
|
14
|
+
export declare const KNOWN_SUBCOMMANDS: readonly ["setup", "update", "assimilate", "reset-local-seat", "recover-enrollment", "spawn", "sync", "cleanup", "launch-all", "server"];
|
|
15
15
|
/**
|
|
16
16
|
* Returns the offending command string when argv[2] is an unknown non-flag
|
|
17
17
|
* positional (caller should error + exit 1), or null when it's bare `borg`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unknown-subcommand.d.ts","sourceRoot":"","sources":["../src/unknown-subcommand.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,0EAA0E;AAC1E,eAAO,MAAM,iBAAiB,
|
|
1
|
+
{"version":3,"file":"unknown-subcommand.d.ts","sourceRoot":"","sources":["../src/unknown-subcommand.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,0EAA0E;AAC1E,eAAO,MAAM,iBAAiB,0IAWpB,CAAC;AAEX;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAK1E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unknown-subcommand.js","sourceRoot":"","sources":["../src/unknown-subcommand.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,0EAA0E;AAC1E,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,OAAO;IACP,QAAQ;IACR,YAAY;IACZ,kBAAkB;IAClB,OAAO;IACP,MAAM;IACN,SAAS;IACT,YAAY;IACZ,QAAQ;CACA,CAAC;AAEX;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAyB;IACzD,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC,CAAC,cAAc;IACpD,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,8BAA8B;IACtE,IAAK,iBAAuC,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1E,OAAO,KAAK,CAAC,CAAC,wCAAwC;AACxD,CAAC"}
|
|
1
|
+
{"version":3,"file":"unknown-subcommand.js","sourceRoot":"","sources":["../src/unknown-subcommand.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,0EAA0E;AAC1E,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,OAAO;IACP,QAAQ;IACR,YAAY;IACZ,kBAAkB;IAClB,oBAAoB;IACpB,OAAO;IACP,MAAM;IACN,SAAS;IACT,YAAY;IACZ,QAAQ;CACA,CAAC;AAEX;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAyB;IACzD,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC,CAAC,cAAc;IACpD,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,8BAA8B;IACtE,IAAK,iBAAuC,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1E,OAAO,KAAK,CAAC,CAAC,wCAAwC;AACxD,CAAC"}
|
|
@@ -22,7 +22,7 @@ The extraction copied the monorepo's `client/src/` production boundary and top-l
|
|
|
22
22
|
recovery, `2.0.2`, `2.0.3`, `2.0.4`, `2.0.5`, and `2.0.6` successors were
|
|
23
23
|
published and registry-verified. The immutable `2.0.7` workflow failed before
|
|
24
24
|
package creation or npm publication. The `2.0.8`, `2.0.9`, `2.0.10`, `2.0.11`,
|
|
25
|
-
`2.1.0`, and `2.1.1` successors were published, and `2.2.0` was subsequently published, and `2.3.0` was subsequently published, and `2.4.0` was subsequently published, and `2.4.1` was subsequently published, and `2.5.0` was subsequently published, and `2.6.0` was subsequently published, and `2.6.1` was subsequently published, and `2.7.0` was subsequently published, and `2.7.1` was subsequently published, and `2.7.2` was subsequently published, and `2.7.3` was subsequently published, and `2.8.0` was subsequently published, so the next candidate identity is `2.
|
|
25
|
+
`2.1.0`, and `2.1.1` successors were published, and `2.2.0` was subsequently published, and `2.3.0` was subsequently published, and `2.4.0` was subsequently published, and `2.4.1` was subsequently published, and `2.5.0` was subsequently published, and `2.6.0` was subsequently published, and `2.6.1` was subsequently published, and `2.7.0` was subsequently published, and `2.7.1` was subsequently published, and `2.7.2` was subsequently published, and `2.7.3` was subsequently published, and `2.8.0` was subsequently published, and `2.9.0` was subsequently published, so the next candidate identity is `2.10.0`. Extraction and
|
|
26
26
|
versioning do not authorize publication.
|
|
27
27
|
|
|
28
28
|
## Review Holds
|
|
@@ -38,11 +38,15 @@ artifact reaches no hosted authority.
|
|
|
38
38
|
Local enrollment now uses the reviewed client-generated credential/retry
|
|
39
39
|
contract, with a pre-request `PENDING` record in the local 0600-permission seat
|
|
40
40
|
store, exact-tuple ambiguous retry, and verified activation. The contract now
|
|
41
|
-
resolves to the audited registry `borgmcp-shared@0.
|
|
42
|
-
`sha512-
|
|
43
|
-
The
|
|
44
|
-
`
|
|
45
|
-
|
|
41
|
+
resolves to the audited registry `borgmcp-shared@0.8.0` with integrity
|
|
42
|
+
`sha512-0p/ZC03Y6G4N6bfBjMD5b+WcQXcLalhU/3QqGjSWYCFKa3q2/183O53w6Tu6y8h2DkhTw5prnamsOozmVpXZew==`.
|
|
43
|
+
The coupled `borgmcp-server@0.9.0` release was published on 2026-08-02 for the
|
|
44
|
+
client `borgmcp@2.10.0` line. Its annotated tag object
|
|
45
|
+
`7021a2e1f551aa0c1716c9e3f29f7b8bc4b0ccb0` peels to protected-main commit
|
|
46
|
+
`734051375d77013f8fd5b396af12feb53d5af96d`; the same-run artifact integrity is
|
|
47
|
+
`sha512-lOxIPg3WcjSBte46iTM7SAFVN6Y0b2oizOKAJ9Q1EijBdOlmsh/cQlpUIaa8F2UJPaTDFsXy3M64gpGs6XgKzA==`
|
|
48
|
+
in the `borgmcp-server-0.9.0-release` registry decision. Both releases pin the
|
|
49
|
+
audited `borgmcp-shared@0.8.0` dependency above. Client `borgmcp@2.9.0` is published. The immutable `v2.0.7`
|
|
46
50
|
attempt failed before publication and remains preserved. Publication of the next
|
|
47
|
-
candidate remains gated by reviewed `v2.
|
|
51
|
+
candidate remains gated by reviewed `v2.10.0` source, a fresh annotated tag, and
|
|
48
52
|
the exact-artifact and protected-publication gates.
|
package/docs/LOCAL_SERVER.md
CHANGED
|
@@ -140,6 +140,14 @@ create distinct bounded cubes. An ordinary enrolled client is denied before a
|
|
|
140
140
|
create request is sent. Cloud-only capabilities fail explicitly rather than
|
|
141
141
|
being redirected.
|
|
142
142
|
|
|
143
|
+
## Agent Launch State
|
|
144
|
+
|
|
145
|
+
Borg tools are inactive unless the agent session was launched with `borg`.
|
|
146
|
+
For Claude Code and Codex, `BORG_DISABLE_LAUNCH_REMINDER` is a presence-based
|
|
147
|
+
local opt-out for launch orientation messaging; setting it does not activate
|
|
148
|
+
Borg tools or change server authorization. OpenCode has no launch-reminder
|
|
149
|
+
surface.
|
|
150
|
+
|
|
143
151
|
## Updating Borg
|
|
144
152
|
|
|
145
153
|
For an npm-global installation, use the whole-product command:
|
|
@@ -227,9 +235,10 @@ The default discovery endpoint is `https://127.0.0.1:7091`. Explicit `--host` va
|
|
|
227
235
|
|
|
228
236
|
## Release status
|
|
229
237
|
|
|
230
|
-
This
|
|
231
|
-
matching
|
|
232
|
-
association, and cube deletion. The server
|
|
238
|
+
This client candidate pins published `borgmcp-shared@0.8.0` and requires the
|
|
239
|
+
matching coupled client/server release for protocol v7 repository resolve,
|
|
240
|
+
association, and cube deletion. The already-published server 0.8.0 retains its
|
|
241
|
+
immutable older shared dependency pin and is not that coupled artifact. The server owner-enrollment, repository resolve/association,
|
|
233
242
|
cube-create, attach, restart, log, and SSE implementation must also pass the full
|
|
234
243
|
process-level local dogfood gate. Until that gate opens the self-hosted path remains preview-only, and the client
|
|
235
244
|
publish is deferred accordingly.
|
package/docs/RELEASING.md
CHANGED
|
@@ -178,8 +178,13 @@ Never move, replace, reuse, or rerun that tag or workflow. The annotated `v2.8.0
|
|
|
178
178
|
`ad40e93ee9b1ff2801119c1c763ad526974ae2a6`. Workflow run `30639790368`, attempt 1,
|
|
179
179
|
successfully published that exact source as `borgmcp@2.8.0`; the same-run artifact report records integrity
|
|
180
180
|
`sha512-hKNyWwVg/8WJ/qcqaNjZVylMw2UzgFEhE7sAJmWy/BjNU4lEa6ox8N7SdbEt8hhKsySMxt+YTFbNFitRtf9u3g==`.
|
|
181
|
+
Never move, replace, reuse, or rerun that tag or workflow. The annotated `v2.9.0` tag object
|
|
182
|
+
`26df119da33e914f857f820e68df6e887afed949` peels to protected-main commit
|
|
183
|
+
`895f12065c09ec5c0a35f770f26ec89f36f2feaa`. Workflow run `30695501563`, attempt 1,
|
|
184
|
+
successfully published that exact source as `borgmcp@2.9.0`; the same-run artifact report records integrity
|
|
185
|
+
`sha512-e10KK//zAP6xRDtUjCrLqzVyeX5zd/39JD4LksBhNVL0ZIGthu5ZLahPMrcCjGl6lZQHNTCo8Tez5c1jmThgGQ==`.
|
|
181
186
|
Never move, replace, reuse, or rerun that tag or workflow. The next candidate
|
|
182
|
-
uses the unused `v2.
|
|
187
|
+
uses the unused `v2.10.0` identity from a fresh reviewed protected-main commit
|
|
183
188
|
and requires the complete release gate again.
|
|
184
189
|
|
|
185
190
|
## Release Prerequisites
|
|
@@ -191,12 +196,13 @@ Before creating the release tag, independently verify all of these conditions:
|
|
|
191
196
|
- the extraction review confirms no private backend secrets, deployment
|
|
192
197
|
configuration, customer data, local state, or duplicated shared contracts
|
|
193
198
|
entered the public package;
|
|
194
|
-
- the exact audited registry dependency `borgmcp-shared@0.
|
|
199
|
+
- the exact audited registry dependency `borgmcp-shared@0.8.0` remains locked to
|
|
195
200
|
its canonical tarball and integrity
|
|
196
|
-
`sha512-
|
|
197
|
-
- the client
|
|
198
|
-
`
|
|
199
|
-
|
|
201
|
+
`sha512-0p/ZC03Y6G4N6bfBjMD5b+WcQXcLalhU/3QqGjSWYCFKa3q2/183O53w6Tu6y8h2DkhTw5prnamsOozmVpXZew==`;
|
|
202
|
+
- the coupled client/server release is published only after the server artifact
|
|
203
|
+
is rebuilt against `borgmcp-shared@0.8.0` and both pass the complete local
|
|
204
|
+
dogfood gate; the already-published server 0.8.0 remains immutable at its
|
|
205
|
+
older immutable shared dependency pin;
|
|
200
206
|
- the selected stable client version is unused and the exact release commit is
|
|
201
207
|
on protected `main`;
|
|
202
208
|
- the repository and protected npm environment settings pass an operator audit;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "borgmcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.0",
|
|
4
4
|
"description": "Coordinate AI coding agents in shared cubes. Works with Claude Code, Codex, and OpenCode.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
73
|
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
74
|
-
"borgmcp-shared": "0.
|
|
74
|
+
"borgmcp-shared": "0.8.0",
|
|
75
75
|
"chalk": "^5.3.0",
|
|
76
76
|
"prompts": "^2.4.2",
|
|
77
77
|
"which": "^4.0.0"
|
package/src/assimilate-cmd.ts
CHANGED
|
@@ -56,6 +56,17 @@ import {
|
|
|
56
56
|
RepositoryAssociationOutcomeUnknownError,
|
|
57
57
|
RepositoryAssociationResolutionError,
|
|
58
58
|
} from './server-errors.js';
|
|
59
|
+
import {
|
|
60
|
+
decodeAndVerifyInvitationArtifact,
|
|
61
|
+
InvitationArtifactCompatibilityError,
|
|
62
|
+
InvitationArtifactEndpointMismatchError,
|
|
63
|
+
InvitationArtifactFormatError,
|
|
64
|
+
InvitationArtifactLegacyError,
|
|
65
|
+
InvitationArtifactRecoveryError,
|
|
66
|
+
InvitationArtifactStorageError,
|
|
67
|
+
InvitationArtifactTransportError,
|
|
68
|
+
InvitationArtifactTrustError,
|
|
69
|
+
} from './invitation-artifact.js';
|
|
59
70
|
import type { SeatStatus } from './seat-probe.js';
|
|
60
71
|
import type { ServerSessionOperation } from './config.js';
|
|
61
72
|
import type { ExpectedBinding, FinalizeServerSeatOutcome, PersistedLocalSeat } from './cubes.js';
|
|
@@ -67,6 +78,7 @@ import type {
|
|
|
67
78
|
AssociateRepositoryCubeResponse,
|
|
68
79
|
CreateCubeRepository,
|
|
69
80
|
CubeTemplate,
|
|
81
|
+
InvitationArtifact,
|
|
70
82
|
} from 'borgmcp-shared/protocol';
|
|
71
83
|
import {
|
|
72
84
|
initializeRepositoryCube,
|
|
@@ -277,7 +289,11 @@ export interface AssimilateDeps {
|
|
|
277
289
|
detectLocalServer: () => Promise<string | null>;
|
|
278
290
|
connectServer: (
|
|
279
291
|
apiUrl: string,
|
|
280
|
-
enrollment?: {
|
|
292
|
+
enrollment?: {
|
|
293
|
+
invitation: string;
|
|
294
|
+
artifact?: import('borgmcp-shared/protocol').InvitationArtifact;
|
|
295
|
+
confirmReplacement?: () => Promise<boolean>;
|
|
296
|
+
},
|
|
281
297
|
) => Promise<{
|
|
282
298
|
token: string;
|
|
283
299
|
trustIdentity: string;
|
|
@@ -291,6 +307,17 @@ export interface AssimilateDeps {
|
|
|
291
307
|
trustIdentity: string;
|
|
292
308
|
serverCapabilities?: readonly string[];
|
|
293
309
|
} | null>;
|
|
310
|
+
/** Read-only pending-artifact peek used to validate an explicit host before
|
|
311
|
+
* the normal resume path performs trust/credential work. */
|
|
312
|
+
peekPendingServerEnrollment?: () => Promise<{ origin: string; invitation: string } | null>;
|
|
313
|
+
resumePendingServerEnrollment?: (
|
|
314
|
+
onPending?: () => void,
|
|
315
|
+
) => Promise<{
|
|
316
|
+
token: string;
|
|
317
|
+
apiUrl?: string;
|
|
318
|
+
trustIdentity: string;
|
|
319
|
+
serverCapabilities?: readonly string[];
|
|
320
|
+
} | null>;
|
|
294
321
|
|
|
295
322
|
resolveRepositoryCube: (
|
|
296
323
|
apiUrl: string,
|
|
@@ -382,6 +409,11 @@ async function selectAssimilationAuthority(
|
|
|
382
409
|
|
|
383
410
|
// Only a local self-hosted server authority exists. Non-TTY and --yes must
|
|
384
411
|
// NOT infer an authority — fail closed with actionable guidance.
|
|
412
|
+
if (flags.enroll && deps.isTTY()) {
|
|
413
|
+
// The v2 enrollment artifact is itself the authority selector. Use the
|
|
414
|
+
// local origin only as a placeholder until the hidden artifact is decoded.
|
|
415
|
+
return { kind: 'server', apiUrl: DEFAULT_LOCAL_SERVER_ORIGIN };
|
|
416
|
+
}
|
|
385
417
|
if (!deps.isTTY() || flags.yes) {
|
|
386
418
|
if (deps.defaultAuthority) return deps.defaultAuthority;
|
|
387
419
|
const command = mode === 'cube-init'
|
|
@@ -521,6 +553,38 @@ function reportServerFailure(
|
|
|
521
553
|
);
|
|
522
554
|
return 1;
|
|
523
555
|
}
|
|
556
|
+
if (error instanceof InvitationArtifactCompatibilityError) {
|
|
557
|
+
deps.stderr(`${error.message}\n`);
|
|
558
|
+
return 1;
|
|
559
|
+
}
|
|
560
|
+
if (error instanceof InvitationArtifactLegacyError) {
|
|
561
|
+
deps.stderr(`${error.message}\n`);
|
|
562
|
+
return 1;
|
|
563
|
+
}
|
|
564
|
+
if (error instanceof InvitationArtifactFormatError) {
|
|
565
|
+
deps.stderr(`${error.message}\n`);
|
|
566
|
+
return 1;
|
|
567
|
+
}
|
|
568
|
+
if (error instanceof InvitationArtifactEndpointMismatchError) {
|
|
569
|
+
deps.stderr(`${error.message}\n`);
|
|
570
|
+
return 1;
|
|
571
|
+
}
|
|
572
|
+
if (error instanceof InvitationArtifactTransportError) {
|
|
573
|
+
deps.stderr(`${error.message}\n`);
|
|
574
|
+
return 1;
|
|
575
|
+
}
|
|
576
|
+
if (error instanceof InvitationArtifactStorageError) {
|
|
577
|
+
deps.stderr(`${error.message}\n`);
|
|
578
|
+
return 1;
|
|
579
|
+
}
|
|
580
|
+
if (error instanceof InvitationArtifactRecoveryError) {
|
|
581
|
+
deps.stderr(`${error.message}\n`);
|
|
582
|
+
return 1;
|
|
583
|
+
}
|
|
584
|
+
if (error instanceof InvitationArtifactTrustError) {
|
|
585
|
+
deps.stderr(`${error.message}\n`);
|
|
586
|
+
return 1;
|
|
587
|
+
}
|
|
524
588
|
if (/HTTP 40[13]|auth(?:entication|orization)|credential.*(?:invalid|rejected)/i.test(message)) {
|
|
525
589
|
deps.stderr(
|
|
526
590
|
`The saved enrollment for ${apiUrl} was rejected. Re-run ` +
|
|
@@ -662,7 +726,117 @@ export async function runAssimilate(
|
|
|
662
726
|
return 1;
|
|
663
727
|
}
|
|
664
728
|
|
|
665
|
-
|
|
729
|
+
const artifactOnlyEnrollment = args.flags.enroll === true &&
|
|
730
|
+
args.flags.server === undefined && deps.defaultAuthority === undefined && deps.isTTY();
|
|
731
|
+
let preResumeAttempted = false;
|
|
732
|
+
let preResumedEnrollment: {
|
|
733
|
+
token: string;
|
|
734
|
+
trustIdentity: string;
|
|
735
|
+
serverCapabilities?: readonly string[];
|
|
736
|
+
} | null = null;
|
|
737
|
+
|
|
738
|
+
let prefetchedInvitation: string | undefined;
|
|
739
|
+
let prefetchedArtifact: InvitationArtifact | undefined;
|
|
740
|
+
|
|
741
|
+
// An explicit --host plus a new invitation must be rejected on the pure
|
|
742
|
+
// input path. Decode and compare the operator-presented artifact before any
|
|
743
|
+
// pending-enrollment lookup: the lookup enumerates the credential backend,
|
|
744
|
+
// so it must not precede this contradiction check. A matching artifact may
|
|
745
|
+
// then take the published pending-resume path until client#267 lands.
|
|
746
|
+
if (args.flags.enroll && args.flags.server !== undefined && deps.isTTY()) {
|
|
747
|
+
let preResumeOrigin: string;
|
|
748
|
+
try {
|
|
749
|
+
preResumeOrigin = normalizeServerEndpoint(args.flags.server);
|
|
750
|
+
} catch (error) {
|
|
751
|
+
deps.stderr(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
752
|
+
return 1;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
prefetchedInvitation = await deps.promptSecret('Enrollment invitation (single-use; hidden input):');
|
|
756
|
+
if (!prefetchedInvitation) {
|
|
757
|
+
deps.stderr('No enrollment invitation was entered. Ask the server operator for one, then retry.\n');
|
|
758
|
+
return 1;
|
|
759
|
+
}
|
|
760
|
+
try {
|
|
761
|
+
prefetchedArtifact = decodeAndVerifyInvitationArtifact(prefetchedInvitation);
|
|
762
|
+
if (prefetchedArtifact.endpoint !== preResumeOrigin) {
|
|
763
|
+
throw new InvitationArtifactEndpointMismatchError(
|
|
764
|
+
preResumeOrigin,
|
|
765
|
+
prefetchedArtifact.endpoint,
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
} catch (error) {
|
|
769
|
+
deps.stderr(`${error instanceof Error ? error.message : 'The enrollment invitation is invalid.'}\n`);
|
|
770
|
+
prefetchedInvitation = undefined;
|
|
771
|
+
prefetchedArtifact = undefined;
|
|
772
|
+
return 1;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
let pendingForHost = false;
|
|
776
|
+
if (deps.peekPendingServerEnrollment !== undefined) {
|
|
777
|
+
let pending: { origin: string; invitation: string } | null = null;
|
|
778
|
+
try {
|
|
779
|
+
pending = await deps.peekPendingServerEnrollment();
|
|
780
|
+
} catch {
|
|
781
|
+
pending = null;
|
|
782
|
+
}
|
|
783
|
+
if (pending?.origin === preResumeOrigin) {
|
|
784
|
+
try {
|
|
785
|
+
const pendingArtifact = decodeAndVerifyInvitationArtifact(pending.invitation);
|
|
786
|
+
if (pendingArtifact.endpoint !== preResumeOrigin) {
|
|
787
|
+
throw new InvitationArtifactEndpointMismatchError(
|
|
788
|
+
preResumeOrigin,
|
|
789
|
+
pendingArtifact.endpoint,
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
pendingForHost = true;
|
|
793
|
+
} catch (error) {
|
|
794
|
+
deps.stderr(`${error instanceof Error ? error.message : 'The enrollment invitation is invalid.'}\n`);
|
|
795
|
+
return 1;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
if (pendingForHost) {
|
|
801
|
+
try {
|
|
802
|
+
preResumeAttempted = true;
|
|
803
|
+
preResumedEnrollment = await deps.resumeServerEnrollment(preResumeOrigin, () => {
|
|
804
|
+
deps.stderr(
|
|
805
|
+
`Resuming the pending enrollment for \`${preResumeOrigin}\`; ` +
|
|
806
|
+
'do not enter another invitation unless the server certificate was reissued; ' +
|
|
807
|
+
'if it was, request a current invitation and rerun this command.\n',
|
|
808
|
+
);
|
|
809
|
+
});
|
|
810
|
+
} catch {
|
|
811
|
+
preResumeAttempted = false;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
if (artifactOnlyEnrollment ||
|
|
816
|
+
preResumeAttempted && preResumedEnrollment === null) {
|
|
817
|
+
if (artifactOnlyEnrollment && deps.resumePendingServerEnrollment) {
|
|
818
|
+
preResumedEnrollment = await deps.resumePendingServerEnrollment(() => {
|
|
819
|
+
deps.stderr('Resuming the pending enrollment; no new invitation is required.\n');
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
if (artifactOnlyEnrollment && preResumedEnrollment) {
|
|
823
|
+
// The exact pending tuple was already redeemed or is being resumed.
|
|
824
|
+
} else {
|
|
825
|
+
prefetchedInvitation = await deps.promptSecret('Enrollment invitation (single-use; hidden input):');
|
|
826
|
+
if (!prefetchedInvitation) {
|
|
827
|
+
deps.stderr('No enrollment invitation was entered. Ask the server operator for one, then retry.\n');
|
|
828
|
+
return 1;
|
|
829
|
+
}
|
|
830
|
+
try {
|
|
831
|
+
prefetchedArtifact = decodeAndVerifyInvitationArtifact(prefetchedInvitation);
|
|
832
|
+
} catch (error) {
|
|
833
|
+
deps.stderr(`${error instanceof Error ? error.message : 'The enrollment invitation is invalid.'}\n`);
|
|
834
|
+
prefetchedInvitation = undefined;
|
|
835
|
+
return 1;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
if (!artifactOnlyEnrollment && args.flags.server === undefined && deps.defaultAuthority === undefined) {
|
|
666
840
|
const connectCommand = mode === 'cube-init'
|
|
667
841
|
? 'borg server cube init --host <host>'
|
|
668
842
|
: 'borg assimilate --host <host>';
|
|
@@ -699,8 +873,9 @@ export async function runAssimilate(
|
|
|
699
873
|
}
|
|
700
874
|
|
|
701
875
|
// ----- Step 1: Select and authenticate the local server -----
|
|
702
|
-
const
|
|
703
|
-
if (!
|
|
876
|
+
const selectedAuthority = await selectAssimilationAuthority(args.flags, deps, mode);
|
|
877
|
+
if (!selectedAuthority) return 1;
|
|
878
|
+
let authority: AssimilationAuthority = selectedAuthority;
|
|
704
879
|
if (localSeatReadError !== undefined) {
|
|
705
880
|
return reportServerFailure(deps, authority.apiUrl, localSeatReadError, false, mode);
|
|
706
881
|
}
|
|
@@ -723,33 +898,48 @@ export async function runAssimilate(
|
|
|
723
898
|
);
|
|
724
899
|
return 1;
|
|
725
900
|
}
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
901
|
+
let resumed = preResumedEnrollment;
|
|
902
|
+
if (!resumed && prefetchedArtifact === undefined && !preResumeAttempted && !artifactOnlyEnrollment) {
|
|
903
|
+
resumed = await deps.resumeServerEnrollment(authority.apiUrl, () => {
|
|
904
|
+
deps.stderr(
|
|
905
|
+
`Resuming the pending enrollment for \`${authority.apiUrl}\`; ` +
|
|
906
|
+
'do not enter another invitation unless the server certificate was reissued; ' +
|
|
907
|
+
'if it was, request a current invitation and rerun this command.\n',
|
|
908
|
+
);
|
|
909
|
+
});
|
|
910
|
+
}
|
|
732
911
|
if (resumed) {
|
|
912
|
+
if ('apiUrl' in resumed && typeof resumed.apiUrl === 'string') {
|
|
913
|
+
authority = { kind: 'server', apiUrl: resumed.apiUrl };
|
|
914
|
+
}
|
|
733
915
|
serverAuth = resumed;
|
|
734
916
|
} else {
|
|
735
|
-
let invitation = await deps.promptSecret(
|
|
736
|
-
|
|
917
|
+
let invitation = prefetchedInvitation ?? await deps.promptSecret(
|
|
918
|
+
artifactOnlyEnrollment
|
|
919
|
+
? 'Enrollment invitation (single-use; hidden input):'
|
|
920
|
+
: `Enrollment invitation for \`${authority.apiUrl}\` (single-use; hidden input):`,
|
|
737
921
|
);
|
|
738
922
|
if (!invitation) {
|
|
739
|
-
deps.stderr(
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
923
|
+
deps.stderr(artifactOnlyEnrollment
|
|
924
|
+
? 'No enrollment invitation was entered. Ask the server operator for one, then rerun `borg assimilate --enroll`.\n'
|
|
925
|
+
: `No enrollment invitation was entered for ${authority.apiUrl}. ` +
|
|
926
|
+
`Ask the server operator for one, then rerun ${localAssimilateCommand(authority.apiUrl, true, mode)}.\n`);
|
|
743
927
|
return 1;
|
|
744
928
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
929
|
+
try {
|
|
930
|
+
const artifact = prefetchedArtifact ?? decodeAndVerifyInvitationArtifact(invitation);
|
|
931
|
+
if (args.flags.server !== undefined && authority.apiUrl !== artifact.endpoint) {
|
|
932
|
+
throw new InvitationArtifactEndpointMismatchError(authority.apiUrl, artifact.endpoint);
|
|
933
|
+
}
|
|
934
|
+
authority = { kind: 'server', apiUrl: artifact.endpoint };
|
|
935
|
+
serverAuth = await deps.connectServer(authority.apiUrl, {
|
|
936
|
+
invitation,
|
|
937
|
+
artifact,
|
|
938
|
+
confirmReplacement: async () => strictAffirmative(await deps.prompt(
|
|
939
|
+
`A local enrollment for ${authority.apiUrl} already exists. Replacing it will orphan ` +
|
|
940
|
+
'the first enrolled client. Replace it? [y/N]: ',
|
|
941
|
+
)),
|
|
942
|
+
});
|
|
753
943
|
} finally {
|
|
754
944
|
// Strings cannot be zeroized in JavaScript, but drop this command's
|
|
755
945
|
// reference immediately after the exchange instead of retaining the
|
|
@@ -777,6 +967,11 @@ export async function runAssimilate(
|
|
|
777
967
|
serverTrustIdentity: serverAuth.trustIdentity,
|
|
778
968
|
serverCapabilities: serverAuth.serverCapabilities ?? [],
|
|
779
969
|
};
|
|
970
|
+
if (args.flags.enroll) {
|
|
971
|
+
deps.stderr(
|
|
972
|
+
`This machine (${deps.getHostname()}) is enrolled with Borg server \`${authority.apiUrl}\`.\n`,
|
|
973
|
+
);
|
|
974
|
+
}
|
|
780
975
|
} catch (error) {
|
|
781
976
|
return reportServerFailure(deps, authority.apiUrl, error, args.flags.enroll === true, mode);
|
|
782
977
|
}
|
package/src/assimilate-deps.ts
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
connectLocalBorgServer,
|
|
35
35
|
createLocalBorgServerCube,
|
|
36
36
|
enrollLocalBorgServer,
|
|
37
|
+
enrollLocalBorgServerArtifact,
|
|
37
38
|
probeLocalBorgServer,
|
|
38
39
|
resolveLocalBorgServerRepositoryCube,
|
|
39
40
|
resumeLocalBorgServerEnrollment,
|
|
@@ -62,6 +63,7 @@ import {
|
|
|
62
63
|
setCodexWakeTarget,
|
|
63
64
|
} from './cubes.js';
|
|
64
65
|
import { addProjectSessionStartHook } from './config-utils.js';
|
|
66
|
+
import { findPendingServerEnrollment } from './config.js';
|
|
65
67
|
import { setTerminalTitle as setTitle } from './terminal-title.js';
|
|
66
68
|
import { defaultCliChoiceDeps, resolveCliChoice } from './cli-platform.js';
|
|
67
69
|
import { prepareCodexRemoteLaunch, defaultCodexRemoteDeps } from './codex-remote.js';
|
|
@@ -240,6 +242,15 @@ export function buildDefaultAssimilateDeps(
|
|
|
240
242
|
: null,
|
|
241
243
|
connectServer: async (apiUrl, enrollment) => {
|
|
242
244
|
if (enrollment) {
|
|
245
|
+
if (enrollment.artifact) {
|
|
246
|
+
return enrollLocalBorgServerArtifact(enrollment.artifact, {
|
|
247
|
+
invitation: enrollment.invitation,
|
|
248
|
+
...(enrollment.confirmReplacement === undefined
|
|
249
|
+
? {}
|
|
250
|
+
: { confirmReplacement: enrollment.confirmReplacement }),
|
|
251
|
+
clientName: osHostname(),
|
|
252
|
+
});
|
|
253
|
+
}
|
|
243
254
|
return enrollLocalBorgServer(apiUrl, enrollment.invitation, {
|
|
244
255
|
...(enrollment.confirmReplacement === undefined
|
|
245
256
|
? {}
|
|
@@ -253,6 +264,21 @@ export function buildDefaultAssimilateDeps(
|
|
|
253
264
|
resumeLocalBorgServerEnrollment(apiUrl, {
|
|
254
265
|
...(onPending === undefined ? {} : { onPending }),
|
|
255
266
|
}),
|
|
267
|
+
peekPendingServerEnrollment: async () => {
|
|
268
|
+
const pending = await findPendingServerEnrollment();
|
|
269
|
+
return pending === null
|
|
270
|
+
? null
|
|
271
|
+
: { origin: pending.origin, invitation: pending.invitation };
|
|
272
|
+
},
|
|
273
|
+
resumePendingServerEnrollment: async (onPending) =>
|
|
274
|
+
(async () => {
|
|
275
|
+
const pending = await findPendingServerEnrollment();
|
|
276
|
+
if (!pending) return null;
|
|
277
|
+
const result = await resumeLocalBorgServerEnrollment(pending.origin, {
|
|
278
|
+
...(onPending === undefined ? {} : { onPending }),
|
|
279
|
+
});
|
|
280
|
+
return result ? { ...result, apiUrl: pending.origin } : null;
|
|
281
|
+
})(),
|
|
256
282
|
|
|
257
283
|
resolveRepositoryCube: async (apiUrl, token, input, serverTrustIdentity) => {
|
|
258
284
|
if (serverTrustIdentity === undefined) {
|
|
@@ -33,6 +33,7 @@ export function renderAssimilationWelcome(
|
|
|
33
33
|
``,
|
|
34
34
|
`In the launched agent, run \`borg_whoami\` and \`borg_roster\` to verify the seat`,
|
|
35
35
|
`and begin coordinating.`,
|
|
36
|
+
`Launch future sessions in this repository with \`borg\`.`,
|
|
36
37
|
...teammateLines,
|
|
37
38
|
``,
|
|
38
39
|
].join('\n');
|
package/src/claude.ts
CHANGED
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
} from './reset-local-seat-cmd.js';
|
|
44
44
|
import { parseLaunchAllArgs } from './parse-launch-all-args.js';
|
|
45
45
|
import { unknownSubcommand } from './unknown-subcommand.js';
|
|
46
|
+
import { parseRecoverEnrollmentArgs, runRecoverEnrollment } from './recover-enrollment-cmd.js';
|
|
46
47
|
import { runLaunchAll } from './launch-all-cmd.js';
|
|
47
48
|
import { buildDefaultLaunchAllDeps } from './launch-all-deps.js';
|
|
48
49
|
import { discoverDroneCandidates } from './launch-all-discovery.js';
|
|
@@ -173,6 +174,27 @@ async function main() {
|
|
|
173
174
|
const code = await runResetLocalSeat(parsed.flags, buildDefaultResetLocalSeatDeps());
|
|
174
175
|
process.exit(code);
|
|
175
176
|
}
|
|
177
|
+
if (process.argv[2] === 'recover-enrollment') {
|
|
178
|
+
const parsed = parseRecoverEnrollmentArgs(process.argv.slice(3));
|
|
179
|
+
if (!parsed.ok) {
|
|
180
|
+
if (parsed.error === 'help') {
|
|
181
|
+
process.stdout.write(clientSubcommandHelpText('recover-enrollment', [], getPackageVersion()) ?? '');
|
|
182
|
+
process.exit(0);
|
|
183
|
+
}
|
|
184
|
+
process.stderr.write(chalk.red(`${consolePrefix()}◼ borg recover-enrollment: ${parsed.error}\n`));
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
const prompt = async (message: string): Promise<string> => {
|
|
188
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
189
|
+
try { return await rl.question(message); } finally { rl.close(); }
|
|
190
|
+
};
|
|
191
|
+
const code = await runRecoverEnrollment(parsed.flags, {
|
|
192
|
+
prompt,
|
|
193
|
+
stderr: (line) => process.stderr.write(line),
|
|
194
|
+
stdout: (line) => process.stdout.write(line),
|
|
195
|
+
});
|
|
196
|
+
process.exit(code);
|
|
197
|
+
}
|
|
176
198
|
if (process.argv[2] === 'spawn') {
|
|
177
199
|
// Deprecated; the stub prints a redirect message and exits 2.
|
|
178
200
|
const code = await runSpawn();
|
package/src/cli-help.ts
CHANGED
|
@@ -64,6 +64,7 @@ export function clientSubcommandHelpText(
|
|
|
64
64
|
case 'setup': return setupHelpText(version);
|
|
65
65
|
case 'assimilate': return assimilateHelpText(version);
|
|
66
66
|
case 'reset-local-seat': return resetLocalSeatHelpText(version);
|
|
67
|
+
case 'recover-enrollment': return recoverEnrollmentHelpText(version);
|
|
67
68
|
case 'sync': return syncHelpText(version);
|
|
68
69
|
case 'cleanup': return cleanupHelpText(version);
|
|
69
70
|
case 'launch-all': return launchAllHelpText(version);
|
|
@@ -101,6 +102,7 @@ export function topLevelHelpText(version: string): string {
|
|
|
101
102
|
` borg assimilate --worktree <name> Spawn a worktree drone (in ~/.borg/worktrees/<repo>/<name>)\n` +
|
|
102
103
|
` borg server cube init Initialize this repository's cube without creating a drone\n` +
|
|
103
104
|
` borg reset-local-seat Clear ONLY this worktree's saved local seat (offline; after a rejection)\n` +
|
|
105
|
+
` borg recover-enrollment Clear ONLY one failed server enrollment transaction\n` +
|
|
104
106
|
` borg sync [--prune] Sync this worktree's branch to origin/main\n` +
|
|
105
107
|
` borg cleanup [--prune] Report (or --prune) worktrees orphaned by evicted drones\n` +
|
|
106
108
|
` borg launch-all [cube] Launch all drone worktrees of a cube (default: active cube)\n` +
|
|
@@ -113,6 +115,17 @@ export function topLevelHelpText(version: string): string {
|
|
|
113
115
|
);
|
|
114
116
|
}
|
|
115
117
|
|
|
118
|
+
export function recoverEnrollmentHelpText(version: string): string {
|
|
119
|
+
return (
|
|
120
|
+
`borg recover-enrollment (borgmcp ${version}) — clear one failed server enrollment\n\n` +
|
|
121
|
+
`Usage:\n` +
|
|
122
|
+
` borg recover-enrollment [--host <host>] [--yes]\n` +
|
|
123
|
+
` borg recover-enrollment --help\n\n` +
|
|
124
|
+
`This operation clears only the failed enrollment transaction for one server.\n` +
|
|
125
|
+
`It does not touch other server enrollments or accounts. It never requires manual file editing.\n`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
116
129
|
/** Help for the whole-product, npm-owned update journey. */
|
|
117
130
|
export function updateHelpText(version: string): string {
|
|
118
131
|
return (
|
|
@@ -143,6 +156,7 @@ export function serverHelpText(): string {
|
|
|
143
156
|
` status Report verified runtime evidence.\n` +
|
|
144
157
|
` update Verify and activate a local server artifact.\n` +
|
|
145
158
|
` invite Create a single-use invitation in an interactive terminal.\n` +
|
|
159
|
+
` cert-reissue Widen the server certificate to cover another address without replacing the CA.\n` +
|
|
146
160
|
` dashboard View the running local server dashboard.\n` +
|
|
147
161
|
` cube init Initialize this Git repository's cube; does not create a drone.\n\n` +
|
|
148
162
|
`Run borg server <command> --help for server command options.\n`
|