borgmcp 2.8.0 → 2.9.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 +1 -0
- package/dist/assimilate-cmd.d.ts.map +1 -1
- package/dist/assimilate-cmd.js +18 -4
- package/dist/assimilate-cmd.js.map +1 -1
- package/dist/assimilate-deps.d.ts.map +1 -1
- package/dist/assimilate-deps.js +3 -0
- package/dist/assimilate-deps.js.map +1 -1
- package/dist/cli-help.d.ts.map +1 -1
- package/dist/cli-help.js +2 -3
- package/dist/cli-help.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -0
- package/dist/config.js.map +1 -1
- package/dist/first-run-server.d.ts +10 -2
- package/dist/first-run-server.d.ts.map +1 -1
- package/dist/first-run-server.js +54 -7
- package/dist/first-run-server.js.map +1 -1
- package/dist/remote-client.d.ts +1 -1
- package/dist/remote-client.d.ts.map +1 -1
- package/dist/remote-client.js +1 -1
- package/dist/remote-client.js.map +1 -1
- package/dist/runtime-metadata.d.ts +1 -1
- package/dist/runtime-metadata.d.ts.map +1 -1
- package/dist/runtime-metadata.js +3 -3
- package/dist/runtime-metadata.js.map +1 -1
- package/dist/server-errors.d.ts +1 -1
- package/dist/server-errors.d.ts.map +1 -1
- package/dist/server-errors.js.map +1 -1
- package/dist/server-handshake.d.ts +4 -0
- package/dist/server-handshake.d.ts.map +1 -1
- package/dist/server-handshake.js +18 -0
- package/dist/server-handshake.js.map +1 -1
- package/dist/setup.js +5 -2
- package/dist/setup.js.map +1 -1
- package/dist/tool-manifest.d.ts.map +1 -1
- package/dist/tool-manifest.js +5 -3
- package/dist/tool-manifest.js.map +1 -1
- package/dist/update-cmd.d.ts +1 -0
- package/dist/update-cmd.d.ts.map +1 -1
- package/dist/update-cmd.js +8 -3
- package/dist/update-cmd.js.map +1 -1
- package/docs/EXTRACTION_PROVENANCE.md +3 -3
- package/docs/RELEASING.md +6 -1
- package/package.json +1 -1
- package/src/assimilate-cmd.ts +25 -6
- package/src/assimilate-deps.ts +3 -0
- package/src/cli-help.ts +2 -3
- package/src/config.ts +4 -0
- package/src/first-run-server.ts +71 -8
- package/src/remote-client.ts +2 -2
- package/src/runtime-metadata.ts +4 -3
- package/src/server-errors.ts +1 -0
- package/src/server-handshake.ts +29 -0
- package/src/setup.ts +5 -2
- package/src/tool-manifest.ts +5 -3
- package/src/update-cmd.ts +8 -3
package/docs/RELEASING.md
CHANGED
|
@@ -173,8 +173,13 @@ Never move, replace, reuse, or rerun that tag or workflow. The annotated `v2.7.3
|
|
|
173
173
|
`83fadd1b308f2b770ce0d8da6a3b61183ec94f8d`. Workflow run `30618377765`, attempt 1,
|
|
174
174
|
successfully published that exact source as `borgmcp@2.7.3`; the same-run artifact report records integrity
|
|
175
175
|
`sha512-Jb+Kg/+H3cPU34JX7eFV3gEO/PzdfJEruNU1mJwYlQZKG7TMfrLJzwghDzxmlMupHJHL3rzAVZ224wioM/pclg==`.
|
|
176
|
+
Never move, replace, reuse, or rerun that tag or workflow. The annotated `v2.8.0` tag object
|
|
177
|
+
`576e2401a83537662ef0d5f7b0d03d6101b15c54` peels to protected-main commit
|
|
178
|
+
`ad40e93ee9b1ff2801119c1c763ad526974ae2a6`. Workflow run `30639790368`, attempt 1,
|
|
179
|
+
successfully published that exact source as `borgmcp@2.8.0`; the same-run artifact report records integrity
|
|
180
|
+
`sha512-hKNyWwVg/8WJ/qcqaNjZVylMw2UzgFEhE7sAJmWy/BjNU4lEa6ox8N7SdbEt8hhKsySMxt+YTFbNFitRtf9u3g==`.
|
|
176
181
|
Never move, replace, reuse, or rerun that tag or workflow. The next candidate
|
|
177
|
-
uses the unused `v2.
|
|
182
|
+
uses the unused `v2.9.0` identity from a fresh reviewed protected-main commit
|
|
178
183
|
and requires the complete release gate again.
|
|
179
184
|
|
|
180
185
|
## Release Prerequisites
|
package/package.json
CHANGED
package/src/assimilate-cmd.ts
CHANGED
|
@@ -277,7 +277,7 @@ export interface AssimilateDeps {
|
|
|
277
277
|
detectLocalServer: () => Promise<string | null>;
|
|
278
278
|
connectServer: (
|
|
279
279
|
apiUrl: string,
|
|
280
|
-
enrollment?: { invitation: string },
|
|
280
|
+
enrollment?: { invitation: string; confirmReplacement?: () => Promise<boolean> },
|
|
281
281
|
) => Promise<{
|
|
282
282
|
token: string;
|
|
283
283
|
trustIdentity: string;
|
|
@@ -361,6 +361,11 @@ function affirmative(answer: string): boolean {
|
|
|
361
361
|
return normalized === '' || normalized === 'y' || normalized === 'yes';
|
|
362
362
|
}
|
|
363
363
|
|
|
364
|
+
function strictAffirmative(answer: string): boolean {
|
|
365
|
+
const normalized = answer.trim().toLowerCase();
|
|
366
|
+
return normalized === 'y' || normalized === 'yes';
|
|
367
|
+
}
|
|
368
|
+
|
|
364
369
|
async function selectAssimilationAuthority(
|
|
365
370
|
flags: AssimilateFlags,
|
|
366
371
|
deps: AssimilateDeps,
|
|
@@ -475,6 +480,14 @@ function reportServerFailure(
|
|
|
475
480
|
);
|
|
476
481
|
return 1;
|
|
477
482
|
}
|
|
483
|
+
if (error instanceof BorgServerError && error.code === 'LOCAL_CREDENTIAL_EXISTS') {
|
|
484
|
+
deps.stderr(
|
|
485
|
+
`A local enrollment for ${apiUrl} already exists and was not replaced. ` +
|
|
486
|
+
`Re-run ${localAssimilateCommand(apiUrl, true, mode)} and explicitly confirm replacement ` +
|
|
487
|
+
'only if the first enrolled client should be abandoned.\n',
|
|
488
|
+
);
|
|
489
|
+
return 1;
|
|
490
|
+
}
|
|
478
491
|
if (error instanceof LegacySessionCredentialCollisionError) {
|
|
479
492
|
const recovery = mode === 'cube-init'
|
|
480
493
|
? localAssimilateCommand(error.origin, true, mode)
|
|
@@ -729,8 +742,14 @@ export async function runAssimilate(
|
|
|
729
742
|
);
|
|
730
743
|
return 1;
|
|
731
744
|
}
|
|
732
|
-
|
|
733
|
-
|
|
745
|
+
try {
|
|
746
|
+
serverAuth = await deps.connectServer(authority.apiUrl, {
|
|
747
|
+
invitation,
|
|
748
|
+
confirmReplacement: async () => strictAffirmative(await deps.prompt(
|
|
749
|
+
`A local enrollment for ${authority.apiUrl} already exists. Replacing it will orphan ` +
|
|
750
|
+
'the first enrolled client. Replace it? [y/N]: ',
|
|
751
|
+
)),
|
|
752
|
+
});
|
|
734
753
|
} finally {
|
|
735
754
|
// Strings cannot be zeroized in JavaScript, but drop this command's
|
|
736
755
|
// reference immediately after the exchange instead of retaining the
|
|
@@ -1492,7 +1511,7 @@ export async function runAssimilate(
|
|
|
1492
1511
|
deps.stderr(
|
|
1493
1512
|
'Borg could not enumerate this repository’s existing worktrees, so it did not risk creating a colliding sibling.\n' +
|
|
1494
1513
|
'Run `git worktree list` from this repository and resolve the reported Git error, then rerun `borg assimilate`.\n' +
|
|
1495
|
-
'A
|
|
1514
|
+
'A local drone reservation was created and remains pending; rerunning after fixing the worktree issue resumes that reservation.\n',
|
|
1496
1515
|
);
|
|
1497
1516
|
return 1;
|
|
1498
1517
|
}
|
|
@@ -1565,13 +1584,13 @@ export async function runAssimilate(
|
|
|
1565
1584
|
? `Git left branch ${residualBranch} without a registered worktree; Borg preserved it.\n`
|
|
1566
1585
|
: '') +
|
|
1567
1586
|
'Run `git worktree list` and `git status` to inspect repository state, resolve the reported Git error, then rerun `borg assimilate`.\n' +
|
|
1568
|
-
'A
|
|
1587
|
+
'A local drone reservation was created and remains pending; rerunning after fixing the worktree issue resumes that reservation.\n',
|
|
1569
1588
|
);
|
|
1570
1589
|
return 1;
|
|
1571
1590
|
}
|
|
1572
1591
|
deps.stderr(
|
|
1573
1592
|
`spawned sibling worktree at ${candidate} on branch ${wtBranch} (${startRef}); ` +
|
|
1574
|
-
`the original dir keeps its active
|
|
1593
|
+
`the original dir keeps its active drone binding — run \`borg reset-local-seat\` there if that binding is stale.\n`
|
|
1575
1594
|
);
|
|
1576
1595
|
deps.chdir(candidate);
|
|
1577
1596
|
deps.stderr(renderWorktreeSteeringNote(candidate, wtBranch, projectRoot));
|
package/src/assimilate-deps.ts
CHANGED
|
@@ -241,6 +241,9 @@ export function buildDefaultAssimilateDeps(
|
|
|
241
241
|
connectServer: async (apiUrl, enrollment) => {
|
|
242
242
|
if (enrollment) {
|
|
243
243
|
return enrollLocalBorgServer(apiUrl, enrollment.invitation, {
|
|
244
|
+
...(enrollment.confirmReplacement === undefined
|
|
245
|
+
? {}
|
|
246
|
+
: { confirmReplacement: enrollment.confirmReplacement }),
|
|
244
247
|
clientName: osHostname(),
|
|
245
248
|
});
|
|
246
249
|
}
|
package/src/cli-help.ts
CHANGED
|
@@ -74,9 +74,8 @@ export function clientSubcommandHelpText(
|
|
|
74
74
|
export function setupNextStepsText(): string {
|
|
75
75
|
return (
|
|
76
76
|
`◼ Next steps:\n` +
|
|
77
|
-
`1. Run \`borg server
|
|
78
|
-
`2.
|
|
79
|
-
`3. In a second terminal, cd into your project and run \`borg assimilate\`.\n`
|
|
77
|
+
`1. Run \`borg server start\` and leave that terminal open.\n` +
|
|
78
|
+
`2. In a second terminal, cd into your project's Git repository and run \`borg assimilate\`.\n`
|
|
80
79
|
);
|
|
81
80
|
}
|
|
82
81
|
|
package/src/config.ts
CHANGED
|
@@ -447,6 +447,7 @@ export async function activatePendingServerEnrollment(
|
|
|
447
447
|
credential: string;
|
|
448
448
|
clientId: string;
|
|
449
449
|
serverCapabilities: ServerCapability[];
|
|
450
|
+
allowReplacement?: boolean;
|
|
450
451
|
},
|
|
451
452
|
): Promise<void> {
|
|
452
453
|
validateServerCredentialBinding(input.origin, input.trustIdentity);
|
|
@@ -471,6 +472,9 @@ export async function activatePendingServerEnrollment(
|
|
|
471
472
|
} catch {
|
|
472
473
|
throw new Error('pending Borg server enrollment does not match the verified response');
|
|
473
474
|
}
|
|
475
|
+
if (!input.allowReplacement && await backend.get(serverCredentialAccount(input.origin, input.trustIdentity))) {
|
|
476
|
+
throw new Error('local Borg server enrollment already exists');
|
|
477
|
+
}
|
|
474
478
|
// Already inside the single store lock — use the UNLOCKED write body so we do
|
|
475
479
|
// not re-acquire (and self-deadlock on) CREDENTIALS_LOCK (CR3b).
|
|
476
480
|
await writeServerCredentialRecord(backend, {
|
package/src/first-run-server.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
1
2
|
import { readFileSync } from 'node:fs';
|
|
2
3
|
import { dirname, join } from 'node:path';
|
|
3
4
|
import { fileURLToPath } from 'node:url';
|
|
@@ -14,10 +15,12 @@ const CLIENT_PACKAGE = 'borgmcp';
|
|
|
14
15
|
const SERVER_PACKAGE = 'borgmcp-server';
|
|
15
16
|
const SHARED_PACKAGE = 'borgmcp-shared';
|
|
16
17
|
const DEFAULT_CONNECT_COMMAND = 'borg assimilate --host <host>';
|
|
18
|
+
const CLIENT_ONBOARDING_ENV = 'BORG_CLIENT_ONBOARDING';
|
|
19
|
+
export const HINT_SUPPORTED_FROM = '0.8.1';
|
|
17
20
|
|
|
18
21
|
export type FirstRunServerInstallResult =
|
|
19
|
-
| { kind: 'present'; server: InstalledPackage }
|
|
20
|
-
| { kind: 'installed'; server: InstalledPackage }
|
|
22
|
+
| { kind: 'present'; server: InstalledPackage; suppressClientNextSteps?: boolean }
|
|
23
|
+
| { kind: 'installed'; server: InstalledPackage; suppressClientNextSteps?: boolean }
|
|
21
24
|
| { kind: 'declined' | 'non-interactive' | 'failed' };
|
|
22
25
|
|
|
23
26
|
export interface FirstRunServerInstallDeps {
|
|
@@ -32,7 +35,8 @@ export interface FirstRunServerInstallDeps {
|
|
|
32
35
|
version: string,
|
|
33
36
|
options?: { ignoreScripts?: boolean },
|
|
34
37
|
): Promise<void>;
|
|
35
|
-
|
|
38
|
+
runServerSetup(server: InstalledPackage, options?: { onboardingHint?: boolean }): Promise<number>;
|
|
39
|
+
confirm(message: string, defaultYes?: boolean): Promise<'yes' | 'no' | 'eof' | 'interrupted'>;
|
|
36
40
|
isTTY(): boolean;
|
|
37
41
|
stdout(text: string): void;
|
|
38
42
|
stderr(text: string): void;
|
|
@@ -84,6 +88,18 @@ export function buildDefaultFirstRunServerInstallDeps(): FirstRunServerInstallDe
|
|
|
84
88
|
publishedPackage: update.publishedPackage,
|
|
85
89
|
publishedVersions: update.publishedVersions,
|
|
86
90
|
installGlobal: update.installGlobal,
|
|
91
|
+
runServerSetup: (server, options = {}) => new Promise((resolve) => {
|
|
92
|
+
const childEnv = { ...process.env };
|
|
93
|
+
delete childEnv[CLIENT_ONBOARDING_ENV];
|
|
94
|
+
if (options.onboardingHint) childEnv[CLIENT_ONBOARDING_ENV] = '1';
|
|
95
|
+
const child = spawn(server.binPath, ['setup'], {
|
|
96
|
+
stdio: 'inherit',
|
|
97
|
+
shell: false,
|
|
98
|
+
env: childEnv,
|
|
99
|
+
});
|
|
100
|
+
child.once('error', () => resolve(1));
|
|
101
|
+
child.once('exit', (code) => resolve(code ?? 1));
|
|
102
|
+
}),
|
|
87
103
|
confirm: update.confirm,
|
|
88
104
|
isTTY: update.isTTY,
|
|
89
105
|
stdout: update.stdout,
|
|
@@ -100,6 +116,7 @@ export function buildDefaultFirstRunServerInstallDeps(): FirstRunServerInstallDe
|
|
|
100
116
|
export async function offerFirstRunServerInstall(
|
|
101
117
|
deps: FirstRunServerInstallDeps = buildDefaultFirstRunServerInstallDeps(),
|
|
102
118
|
connectCommand = DEFAULT_CONNECT_COMMAND,
|
|
119
|
+
options: { initializeServer?: boolean } = {},
|
|
103
120
|
): Promise<FirstRunServerInstallResult> {
|
|
104
121
|
let installed: InstalledPackage | null;
|
|
105
122
|
try {
|
|
@@ -112,7 +129,15 @@ export async function offerFirstRunServerInstall(
|
|
|
112
129
|
);
|
|
113
130
|
return { kind: 'failed' };
|
|
114
131
|
}
|
|
115
|
-
if (installed)
|
|
132
|
+
if (installed) {
|
|
133
|
+
return initializeServerIfRequested(
|
|
134
|
+
installed,
|
|
135
|
+
deps,
|
|
136
|
+
options.initializeServer,
|
|
137
|
+
'present',
|
|
138
|
+
shouldSuppressClientNextSteps(installed.version),
|
|
139
|
+
);
|
|
140
|
+
}
|
|
116
141
|
|
|
117
142
|
if (!deps.isTTY()) {
|
|
118
143
|
deps.stderr(
|
|
@@ -140,9 +165,11 @@ export async function offerFirstRunServerInstall(
|
|
|
140
165
|
const command = exactInstallCommand(target.version);
|
|
141
166
|
const decision = await deps.confirm(
|
|
142
167
|
`No local ${SERVER_PACKAGE} installation was found. ` +
|
|
143
|
-
|
|
168
|
+
`Install ${SERVER_PACKAGE}@${target.version}, the local coordination server, ` +
|
|
169
|
+
`which uses ${SHARED_PACKAGE}@${target.sharedVersion}, now?\n` +
|
|
144
170
|
`Command: ${command}\n` +
|
|
145
|
-
|
|
171
|
+
`Install it now? [Y/n] `,
|
|
172
|
+
true,
|
|
146
173
|
);
|
|
147
174
|
if (decision !== 'yes') {
|
|
148
175
|
const reason = decision === 'interrupted'
|
|
@@ -172,7 +199,43 @@ export async function offerFirstRunServerInstall(
|
|
|
172
199
|
|
|
173
200
|
deps.stdout(
|
|
174
201
|
`Installed ${SERVER_PACKAGE}@${target.version} with ${SHARED_PACKAGE}@${target.sharedVersion}.\n` +
|
|
175
|
-
|
|
202
|
+
(options.initializeServer
|
|
203
|
+
? `The local server package is ready.\n`
|
|
204
|
+
: `Next, run \`borg server setup\`, then run \`borg server start\` in a terminal you keep open.\n`),
|
|
205
|
+
);
|
|
206
|
+
return initializeServerIfRequested(installed, deps, options.initializeServer, 'installed');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function initializeServerIfRequested(
|
|
210
|
+
server: InstalledPackage,
|
|
211
|
+
deps: FirstRunServerInstallDeps,
|
|
212
|
+
requested: boolean | undefined,
|
|
213
|
+
kind: 'present' | 'installed',
|
|
214
|
+
suppressClientNextSteps = false,
|
|
215
|
+
): Promise<FirstRunServerInstallResult> {
|
|
216
|
+
if (!requested) return { kind, server, ...(suppressClientNextSteps ? { suppressClientNextSteps: true } : {}) };
|
|
217
|
+
try {
|
|
218
|
+
const exitCode = await deps.runServerSetup(server, { onboardingHint: !suppressClientNextSteps });
|
|
219
|
+
if (exitCode === 0) {
|
|
220
|
+
return { kind, server, ...(suppressClientNextSteps ? { suppressClientNextSteps: true } : {}) };
|
|
221
|
+
}
|
|
222
|
+
} catch (error) {
|
|
223
|
+
deps.stderr(`Local server setup could not be completed. ${error instanceof Error ? error.message : String(error)}\n`);
|
|
224
|
+
}
|
|
225
|
+
deps.stderr(
|
|
226
|
+
'Run `borg server setup` to retry local server initialization, then run `borg server start`.\n',
|
|
176
227
|
);
|
|
177
|
-
return { kind: '
|
|
228
|
+
return { kind: 'failed' };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function shouldSuppressClientNextSteps(version: unknown): boolean {
|
|
232
|
+
if (!isExactSemver(version)) return false;
|
|
233
|
+
const installedParts = version.split('.').slice(0, 3).map(Number);
|
|
234
|
+
const supportedParts = HINT_SUPPORTED_FROM.split('.').map(Number);
|
|
235
|
+
for (let index = 0; index < 3; index += 1) {
|
|
236
|
+
if (installedParts[index] !== supportedParts[index]) {
|
|
237
|
+
return installedParts[index] < supportedParts[index];
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return false;
|
|
178
241
|
}
|
package/src/remote-client.ts
CHANGED
|
@@ -1041,7 +1041,7 @@ export async function regen(
|
|
|
1041
1041
|
opts: {
|
|
1042
1042
|
since?: string;
|
|
1043
1043
|
/** Advisory self-report from the running agent; never model-routing config. */
|
|
1044
|
-
reportedModel?: string;
|
|
1044
|
+
reportedModel?: string | null;
|
|
1045
1045
|
/** Positively identified running Agent CLI; null means explicitly unknown. */
|
|
1046
1046
|
agentKind?: AgentKind | null;
|
|
1047
1047
|
/** Current cwd-derived identity; refreshed each regen to avoid stale routing data. */
|
|
@@ -1077,7 +1077,7 @@ export async function regen(
|
|
|
1077
1077
|
) {
|
|
1078
1078
|
const patch = buildRuntimeMetadataPatch({
|
|
1079
1079
|
agentKind: opts.agentKind ?? null,
|
|
1080
|
-
reportedModel: opts.reportedModel,
|
|
1080
|
+
reportedModel: opts.reportedModel ?? null,
|
|
1081
1081
|
workingRepo: opts.workingRepo,
|
|
1082
1082
|
});
|
|
1083
1083
|
try {
|
package/src/runtime-metadata.ts
CHANGED
|
@@ -53,13 +53,14 @@ export function buildRuntimeMetadataReport(input: {
|
|
|
53
53
|
|
|
54
54
|
export function buildRuntimeMetadataPatch(input: {
|
|
55
55
|
agentKind: AgentKind | null;
|
|
56
|
-
reportedModel?: string;
|
|
56
|
+
reportedModel?: string | null;
|
|
57
57
|
workingRepo?: WorkingRepo;
|
|
58
58
|
}): DroneRuntimeMetadataPatch {
|
|
59
59
|
const patch: DroneRuntimeMetadataPatch = { agent_kind: input.agentKind };
|
|
60
60
|
if (input.reportedModel !== undefined) {
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
patch.reported_model = input.reportedModel === null
|
|
62
|
+
? null
|
|
63
|
+
: validateReportedModel(input.reportedModel);
|
|
63
64
|
}
|
|
64
65
|
const repository = reportableRepository(input.workingRepo);
|
|
65
66
|
if (repository) Object.assign(patch, repository);
|
package/src/server-errors.ts
CHANGED
package/src/server-handshake.ts
CHANGED
|
@@ -452,6 +452,8 @@ export async function enrollBorgServer(
|
|
|
452
452
|
prepareEnrollment?: typeof getOrCreatePendingServerEnrollment;
|
|
453
453
|
activateEnrollment?: typeof activatePendingServerEnrollment;
|
|
454
454
|
clearPendingEnrollment?: typeof clearPendingServerEnrollment;
|
|
455
|
+
loadCredentialRecord?: typeof getServerCredentialRecord;
|
|
456
|
+
confirmReplacement?: () => Promise<boolean>;
|
|
455
457
|
clientName?: string;
|
|
456
458
|
} = {},
|
|
457
459
|
): Promise<NewServerEnrollment> {
|
|
@@ -461,6 +463,24 @@ export async function enrollBorgServer(
|
|
|
461
463
|
// persisted and before any invitation/secret-bearing request is sent.
|
|
462
464
|
const protocol = await preflightBorgServerTag(origin, fetchImpl);
|
|
463
465
|
|
|
466
|
+
const existingCredential = await (deps.loadCredentialRecord ?? getServerCredentialRecord)(
|
|
467
|
+
origin,
|
|
468
|
+
trustIdentity,
|
|
469
|
+
);
|
|
470
|
+
let replacementConfirmed = false;
|
|
471
|
+
if (existingCredential) {
|
|
472
|
+
const confirmed = deps.confirmReplacement !== undefined
|
|
473
|
+
? await deps.confirmReplacement()
|
|
474
|
+
: false;
|
|
475
|
+
if (!confirmed) {
|
|
476
|
+
throw new BorgServerError(
|
|
477
|
+
'LOCAL_CREDENTIAL_EXISTS',
|
|
478
|
+
'a local enrollment already exists for this server and was not replaced',
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
replacementConfirmed = true;
|
|
482
|
+
}
|
|
483
|
+
|
|
464
484
|
const pending = await (deps.prepareEnrollment ?? getOrCreatePendingServerEnrollment)({
|
|
465
485
|
origin,
|
|
466
486
|
trustIdentity,
|
|
@@ -528,6 +548,7 @@ export async function enrollBorgServer(
|
|
|
528
548
|
credential: pending.credential,
|
|
529
549
|
clientId: decoded.payload.client_id,
|
|
530
550
|
serverCapabilities: decoded.payload.server_capabilities,
|
|
551
|
+
...(replacementConfirmed ? { allowReplacement: true } : {}),
|
|
531
552
|
});
|
|
532
553
|
return {
|
|
533
554
|
token: pending.credential,
|
|
@@ -1011,6 +1032,8 @@ export async function enrollLocalBorgServer(
|
|
|
1011
1032
|
prepareEnrollment?: typeof getOrCreatePendingServerEnrollment;
|
|
1012
1033
|
activateEnrollment?: typeof activatePendingServerEnrollment;
|
|
1013
1034
|
clearPendingEnrollment?: typeof clearPendingServerEnrollment;
|
|
1035
|
+
loadCredentialRecord?: typeof getServerCredentialRecord;
|
|
1036
|
+
confirmReplacement?: () => Promise<boolean>;
|
|
1014
1037
|
clientName?: string;
|
|
1015
1038
|
} = {},
|
|
1016
1039
|
): Promise<NewServerEnrollment> {
|
|
@@ -1022,6 +1045,12 @@ export async function enrollLocalBorgServer(
|
|
|
1022
1045
|
...(deps.clearPendingEnrollment === undefined
|
|
1023
1046
|
? {}
|
|
1024
1047
|
: { clearPendingEnrollment: deps.clearPendingEnrollment }),
|
|
1048
|
+
...(deps.loadCredentialRecord === undefined
|
|
1049
|
+
? {}
|
|
1050
|
+
: { loadCredentialRecord: deps.loadCredentialRecord }),
|
|
1051
|
+
...(deps.confirmReplacement === undefined
|
|
1052
|
+
? {}
|
|
1053
|
+
: { confirmReplacement: deps.confirmReplacement }),
|
|
1025
1054
|
...(deps.clientName === undefined ? {} : { clientName: deps.clientName }),
|
|
1026
1055
|
});
|
|
1027
1056
|
}
|
package/src/setup.ts
CHANGED
|
@@ -73,11 +73,12 @@ async function main() {
|
|
|
73
73
|
// configuration. Decline/non-interactive/failure paths therefore leave no
|
|
74
74
|
// partial setup state behind.
|
|
75
75
|
console.log(chalk.blue('◼ Local Server'));
|
|
76
|
-
const serverInstall = await offerFirstRunServerInstall();
|
|
76
|
+
const serverInstall = await offerFirstRunServerInstall(undefined, undefined, { initializeServer: true });
|
|
77
77
|
if (serverInstall.kind !== 'present' && serverInstall.kind !== 'installed') {
|
|
78
78
|
process.exit(serverInstall.kind === 'declined' ? 0 : 1);
|
|
79
79
|
}
|
|
80
80
|
console.log('');
|
|
81
|
+
console.log('◼ Local server initialized');
|
|
81
82
|
|
|
82
83
|
// Step 1: Configure every detected agent CLI
|
|
83
84
|
console.log(chalk.blue('◼ Agent CLI Integration'));
|
|
@@ -185,7 +186,9 @@ async function main() {
|
|
|
185
186
|
// Success message
|
|
186
187
|
console.log(chalk.green.bold('\nSetup complete!\n'));
|
|
187
188
|
console.log(chalk.yellow('🔄 Restart Claude Code / Codex / OpenCode (or open a new session) for the changes to take effect.\n'));
|
|
188
|
-
|
|
189
|
+
if (!serverInstall.suppressClientNextSteps) {
|
|
190
|
+
console.log(chalk.gray(setupNextStepsText()));
|
|
191
|
+
}
|
|
189
192
|
}
|
|
190
193
|
|
|
191
194
|
// Run wizard
|
package/src/tool-manifest.ts
CHANGED
|
@@ -30,7 +30,7 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
|
|
|
30
30
|
'Optional `since` (entry-id UUID or ISO-8601 timestamp) trims the recent-log section ' +
|
|
31
31
|
'to entries strictly after the anchor — pass your last-seen entry id to skip ' +
|
|
32
32
|
'already-processed history on each refresh. If you know the current session model, pass ' +
|
|
33
|
-
'optional `model` to self-report
|
|
33
|
+
'optional `model` to self-report its printable identifier as advisory metadata; model names are not allowlisted.',
|
|
34
34
|
inputSchema: {
|
|
35
35
|
type: 'object',
|
|
36
36
|
properties: {
|
|
@@ -47,9 +47,11 @@ export const TOOL_MANIFEST: ToolManifestEntry[] = [
|
|
|
47
47
|
},
|
|
48
48
|
model: {
|
|
49
49
|
type: 'string',
|
|
50
|
-
|
|
50
|
+
minLength: 1,
|
|
51
|
+
maxLength: 160,
|
|
52
|
+
pattern: '^[A-Za-z0-9][A-Za-z0-9._/@:+-]*$',
|
|
51
53
|
description:
|
|
52
|
-
'Optional advisory self-report of the model running this agent session. Use
|
|
54
|
+
'Optional advisory self-report of the model running this agent session. Use a printable model identifier of 1-160 ASCII characters; model names are not allowlisted.',
|
|
53
55
|
},
|
|
54
56
|
},
|
|
55
57
|
required: [],
|
package/src/update-cmd.ts
CHANGED
|
@@ -1128,7 +1128,13 @@ async function defaultPublishedVersions(
|
|
|
1128
1128
|
}
|
|
1129
1129
|
}
|
|
1130
1130
|
|
|
1131
|
-
|
|
1131
|
+
export function parseConfirmationAnswer(answer: string, defaultYes = false): 'yes' | 'no' {
|
|
1132
|
+
const normalized = answer.trim().toLowerCase();
|
|
1133
|
+
if (normalized === '') return defaultYes ? 'yes' : 'no';
|
|
1134
|
+
return normalized === 'y' || normalized === 'yes' ? 'yes' : 'no';
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
async function defaultConfirm(message: string, defaultYes = false): Promise<'yes' | 'no' | 'eof' | 'interrupted'> {
|
|
1132
1138
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1133
1139
|
let interrupted = false;
|
|
1134
1140
|
rl.once('SIGINT', () => {
|
|
@@ -1136,8 +1142,7 @@ async function defaultConfirm(message: string): Promise<'yes' | 'no' | 'eof' | '
|
|
|
1136
1142
|
rl.close();
|
|
1137
1143
|
});
|
|
1138
1144
|
try {
|
|
1139
|
-
|
|
1140
|
-
return answer === 'y' || answer === 'yes' ? 'yes' : 'no';
|
|
1145
|
+
return parseConfirmationAnswer(await rl.question(message), defaultYes);
|
|
1141
1146
|
} catch (error) {
|
|
1142
1147
|
if (interrupted) return 'interrupted';
|
|
1143
1148
|
if ((error as NodeJS.ErrnoException).code === 'ERR_USE_AFTER_CLOSE') return 'eof';
|