borgmcp 3.2.0 → 3.4.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/README.md +18 -3
- package/dist/agent-integration-health.d.ts +44 -0
- package/dist/agent-integration-health.d.ts.map +1 -0
- package/dist/agent-integration-health.js +284 -0
- package/dist/agent-integration-health.js.map +1 -0
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +13 -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 -3
- package/dist/cli-help.js.map +1 -1
- package/dist/config-utils.d.ts +21 -0
- package/dist/config-utils.d.ts.map +1 -1
- package/dist/config-utils.js +186 -20
- package/dist/config-utils.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -6
- package/dist/index.js.map +1 -1
- package/dist/log-audit-core.d.ts +7 -0
- package/dist/log-audit-core.d.ts.map +1 -0
- package/dist/log-audit-core.js +80 -0
- package/dist/log-audit-core.js.map +1 -0
- package/dist/log-audit.d.ts +3 -3
- package/dist/log-audit.js +7 -110
- package/dist/log-audit.js.map +1 -1
- package/dist/opencode-drone.d.ts.map +1 -1
- package/dist/opencode-drone.js +6 -1
- package/dist/opencode-drone.js.map +1 -1
- package/dist/opencode-plugin.d.ts +47 -7
- package/dist/opencode-plugin.d.ts.map +1 -1
- package/dist/opencode-plugin.js +187 -23
- package/dist/opencode-plugin.js.map +1 -1
- package/dist/regen-format.d.ts.map +1 -1
- package/dist/regen-format.js +5 -6
- package/dist/regen-format.js.map +1 -1
- package/dist/resolved-cli-config.d.ts +1 -0
- package/dist/resolved-cli-config.d.ts.map +1 -1
- package/dist/resolved-cli-config.js +1 -0
- package/dist/resolved-cli-config.js.map +1 -1
- package/dist/startup-services.d.ts +0 -1
- package/dist/startup-services.d.ts.map +1 -1
- package/dist/startup-services.js +0 -2
- package/dist/startup-services.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/dist/update-cmd.d.ts +1 -1
- package/dist/update-cmd.d.ts.map +1 -1
- package/dist/update-cmd.js +15 -8
- package/dist/update-cmd.js.map +1 -1
- package/docs/LOCAL_SERVER.md +28 -7
- package/package.json +1 -1
- package/src/agent-integration-health.ts +347 -0
- package/src/claude.ts +14 -0
- package/src/cli-help.ts +14 -3
- package/src/config-utils.ts +185 -20
- package/src/index.ts +1 -7
- package/src/log-audit-core.ts +81 -0
- package/src/log-audit.ts +6 -117
- package/src/opencode-drone.ts +6 -1
- package/src/opencode-plugin.ts +216 -22
- package/src/regen-format.ts +5 -6
- package/src/resolved-cli-config.ts +2 -0
- package/src/startup-services.ts +0 -3
- package/src/unknown-subcommand.ts +1 -0
- package/src/update-cmd.ts +18 -11
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import which from 'which';
|
|
4
|
+
import {
|
|
5
|
+
inspectManagedAgentHookConfigs,
|
|
6
|
+
isOpenCodeMcpServerConfigured,
|
|
7
|
+
type ManagedAgentHookConfigHealth,
|
|
8
|
+
refreshManagedAgentHookConfigs,
|
|
9
|
+
refreshManagedAgentMcpConfigs,
|
|
10
|
+
} from './config-utils.js';
|
|
11
|
+
import { borgHomeRoot, isCanonicalPath } from './private-root.js';
|
|
12
|
+
import { getPackageVersion } from './version.js';
|
|
13
|
+
import {
|
|
14
|
+
buildBorgPluginSource,
|
|
15
|
+
installBorgPlugin,
|
|
16
|
+
openCodePluginPath,
|
|
17
|
+
} from './opencode-plugin.js';
|
|
18
|
+
|
|
19
|
+
export const AGENT_HOOK_BINS = [
|
|
20
|
+
'borg-regen',
|
|
21
|
+
'borg-clear-rewake',
|
|
22
|
+
'borg-log-audit',
|
|
23
|
+
'borg-foreign-path-reminder',
|
|
24
|
+
'borg-inbox-monitor',
|
|
25
|
+
] as const;
|
|
26
|
+
|
|
27
|
+
export type AgentHookBinName = typeof AGENT_HOOK_BINS[number];
|
|
28
|
+
|
|
29
|
+
export interface AgentHookBinHealth {
|
|
30
|
+
name: AgentHookBinName;
|
|
31
|
+
status: 'ok' | 'missing' | 'wrong-owner' | 'version-skew' | 'unreadable';
|
|
32
|
+
resolvedPath?: string;
|
|
33
|
+
owner?: string;
|
|
34
|
+
version?: string;
|
|
35
|
+
detail?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface AgentIntegrationHealth {
|
|
39
|
+
expectedVersion: string;
|
|
40
|
+
bins: AgentHookBinHealth[];
|
|
41
|
+
issues: Array<AgentHookBinHealth | ManagedAgentHookConfigHealth | OpenCodePluginHealth>;
|
|
42
|
+
hookConfigs: ManagedAgentHookConfigHealth[];
|
|
43
|
+
openCodePlugin: OpenCodePluginHealth;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface OpenCodePluginHealth {
|
|
47
|
+
path: string;
|
|
48
|
+
configured: boolean;
|
|
49
|
+
status: 'ok' | 'present' | 'absent' | 'missing' | 'outdated' | 'unreadable' | 'refused';
|
|
50
|
+
version?: string;
|
|
51
|
+
detail?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface InspectAgentIntegrationHealthOptions {
|
|
55
|
+
expectedVersion?: string;
|
|
56
|
+
path?: string;
|
|
57
|
+
homeDir?: string;
|
|
58
|
+
resolveBin?: (name: AgentHookBinName, searchPath: string | undefined) => string | null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function findOwningPackage(startPath: string): { name: string; version: string } | null {
|
|
62
|
+
let directory = path.dirname(startPath);
|
|
63
|
+
const root = path.parse(directory).root;
|
|
64
|
+
while (true) {
|
|
65
|
+
const packagePath = path.join(directory, 'package.json');
|
|
66
|
+
try {
|
|
67
|
+
const parsed = JSON.parse(fs.readFileSync(packagePath, 'utf8')) as Record<string, unknown>;
|
|
68
|
+
if (typeof parsed.name === 'string' && typeof parsed.version === 'string') {
|
|
69
|
+
return { name: parsed.name, version: parsed.version };
|
|
70
|
+
}
|
|
71
|
+
} catch {
|
|
72
|
+
// Keep walking. npm bin links resolve into a package's dist directory.
|
|
73
|
+
}
|
|
74
|
+
if (directory === root) return null;
|
|
75
|
+
directory = path.dirname(directory);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function defaultResolveBin(name: AgentHookBinName, searchPath: string | undefined): string | null {
|
|
80
|
+
try {
|
|
81
|
+
return which.sync(name, searchPath === undefined ? undefined : { path: searchPath });
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function inspectOpenCodePlugin(homeDir: string, expectedVersion: string): OpenCodePluginHealth {
|
|
88
|
+
const pluginPath = openCodePluginPath(homeDir);
|
|
89
|
+
const configured = isOpenCodeMcpServerConfigured(
|
|
90
|
+
path.join(homeDir, '.config', 'opencode', 'opencode.json'),
|
|
91
|
+
);
|
|
92
|
+
try {
|
|
93
|
+
let metadata: fs.Stats;
|
|
94
|
+
try {
|
|
95
|
+
metadata = fs.lstatSync(pluginPath);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
98
|
+
if (!isCanonicalPath(pluginPath)) {
|
|
99
|
+
return {
|
|
100
|
+
path: pluginPath,
|
|
101
|
+
configured,
|
|
102
|
+
status: 'refused',
|
|
103
|
+
detail: 'plugin path contains a symlink',
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return { path: pluginPath, configured, status: configured ? 'missing' : 'absent' };
|
|
107
|
+
}
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
if (metadata.isSymbolicLink()) {
|
|
111
|
+
return {
|
|
112
|
+
path: pluginPath,
|
|
113
|
+
configured,
|
|
114
|
+
status: 'refused',
|
|
115
|
+
detail: 'plugin path is a symlink',
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
if (!isCanonicalPath(pluginPath)) {
|
|
119
|
+
return {
|
|
120
|
+
path: pluginPath,
|
|
121
|
+
configured,
|
|
122
|
+
status: 'refused',
|
|
123
|
+
detail: 'plugin path contains a symlink',
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (!metadata.isFile()) {
|
|
127
|
+
return {
|
|
128
|
+
path: pluginPath,
|
|
129
|
+
configured,
|
|
130
|
+
status: 'refused',
|
|
131
|
+
detail: 'plugin path is not a regular file',
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const source = fs.readFileSync(pluginPath, 'utf8');
|
|
135
|
+
const marker = source.match(/borgmcp-opencode-plugin:([^;\s]+);opencode=/)?.[1];
|
|
136
|
+
if (!configured) {
|
|
137
|
+
return { path: pluginPath, configured, status: 'present', version: marker ?? 'unknown' };
|
|
138
|
+
}
|
|
139
|
+
if (source === buildBorgPluginSource(expectedVersion)) {
|
|
140
|
+
return { path: pluginPath, configured, status: 'ok', version: expectedVersion };
|
|
141
|
+
}
|
|
142
|
+
return { path: pluginPath, configured, status: 'outdated', version: marker ?? 'unknown' };
|
|
143
|
+
} catch (error) {
|
|
144
|
+
return {
|
|
145
|
+
path: pluginPath,
|
|
146
|
+
configured,
|
|
147
|
+
status: 'unreadable',
|
|
148
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function inspectAgentIntegrationHealth(
|
|
154
|
+
options: InspectAgentIntegrationHealthOptions = {},
|
|
155
|
+
): AgentIntegrationHealth {
|
|
156
|
+
const expectedVersion = options.expectedVersion ?? getPackageVersion();
|
|
157
|
+
const homeDir = options.homeDir ?? borgHomeRoot();
|
|
158
|
+
const resolveBin = options.resolveBin ?? defaultResolveBin;
|
|
159
|
+
const bins = AGENT_HOOK_BINS.map((name): AgentHookBinHealth => {
|
|
160
|
+
const found = resolveBin(name, options.path);
|
|
161
|
+
if (!found) return { name, status: 'missing' };
|
|
162
|
+
let resolvedPath: string;
|
|
163
|
+
try {
|
|
164
|
+
resolvedPath = fs.realpathSync(found);
|
|
165
|
+
} catch (error) {
|
|
166
|
+
return {
|
|
167
|
+
name,
|
|
168
|
+
status: 'unreadable',
|
|
169
|
+
resolvedPath: found,
|
|
170
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
const owner = findOwningPackage(resolvedPath);
|
|
174
|
+
if (!owner || owner.name !== 'borgmcp') {
|
|
175
|
+
return {
|
|
176
|
+
name,
|
|
177
|
+
status: 'wrong-owner',
|
|
178
|
+
resolvedPath,
|
|
179
|
+
owner: owner?.name ?? 'unknown',
|
|
180
|
+
version: owner?.version,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
if (owner.version !== expectedVersion) {
|
|
184
|
+
return { name, status: 'version-skew', resolvedPath, owner: owner.name, version: owner.version };
|
|
185
|
+
}
|
|
186
|
+
return { name, status: 'ok', resolvedPath, owner: owner.name, version: owner.version };
|
|
187
|
+
});
|
|
188
|
+
const hookConfigs = inspectManagedAgentHookConfigs(homeDir);
|
|
189
|
+
const openCodePlugin = inspectOpenCodePlugin(homeDir, expectedVersion);
|
|
190
|
+
return {
|
|
191
|
+
expectedVersion,
|
|
192
|
+
bins,
|
|
193
|
+
issues: [
|
|
194
|
+
...bins.filter((bin) => bin.status !== 'ok'),
|
|
195
|
+
...hookConfigs.filter((config) => config.status === 'stale' || config.status === 'invalid'),
|
|
196
|
+
...(openCodePlugin.status === 'unreadable' ||
|
|
197
|
+
(openCodePlugin.status === 'refused' && openCodePlugin.configured) ||
|
|
198
|
+
(openCodePlugin.configured && openCodePlugin.status !== 'ok')
|
|
199
|
+
? [openCodePlugin]
|
|
200
|
+
: []),
|
|
201
|
+
],
|
|
202
|
+
hookConfigs,
|
|
203
|
+
openCodePlugin,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function renderBin(bin: AgentHookBinHealth, expectedVersion: string): string {
|
|
208
|
+
switch (bin.status) {
|
|
209
|
+
case 'ok': return `${bin.name}: ok (${bin.version})`;
|
|
210
|
+
case 'missing': return `${bin.name}: missing`;
|
|
211
|
+
case 'wrong-owner': return `${bin.name}: wrong owner ${bin.owner}${bin.version ? `@${bin.version}` : ''}${bin.resolvedPath ? ` at ${bin.resolvedPath}` : ''}`;
|
|
212
|
+
case 'version-skew': return `${bin.name}: version ${bin.version}, expected ${expectedVersion}${bin.resolvedPath ? ` at ${bin.resolvedPath}` : ''}`;
|
|
213
|
+
case 'unreadable': return `${bin.name}: unreadable${bin.resolvedPath ? ` at ${bin.resolvedPath}` : ''}${bin.detail ? ` (${bin.detail})` : ''}`;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function renderOpenCodePlugin(
|
|
218
|
+
plugin: OpenCodePluginHealth,
|
|
219
|
+
expectedVersion: string,
|
|
220
|
+
): string {
|
|
221
|
+
const prefix = 'OpenCode borg-orient.js plugin:';
|
|
222
|
+
switch (plugin.status) {
|
|
223
|
+
case 'ok': return `${prefix} ok (${plugin.version})`;
|
|
224
|
+
case 'present': return `${prefix} present`;
|
|
225
|
+
case 'absent': return `${prefix} absent`;
|
|
226
|
+
case 'missing': return `${prefix} missing at ${plugin.path}`;
|
|
227
|
+
case 'outdated': return `${prefix} version ${plugin.version}, expected ${expectedVersion} at ${plugin.path}`;
|
|
228
|
+
case 'unreadable': return `${prefix} unreadable at ${plugin.path}${plugin.detail ? ` (${plugin.detail})` : ''}`;
|
|
229
|
+
case 'refused': return `${prefix} refused at ${plugin.path}${plugin.detail ? ` (${plugin.detail})` : ''}`;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function renderAgentIntegrationHealth(report: AgentIntegrationHealth): string {
|
|
234
|
+
const lines = [
|
|
235
|
+
`Borg agent integration (borgmcp ${report.expectedVersion})`,
|
|
236
|
+
...report.bins.map((bin) => ` ${renderBin(bin, report.expectedVersion)}`),
|
|
237
|
+
'Hook configuration:',
|
|
238
|
+
...report.hookConfigs.map((item) => (
|
|
239
|
+
` ${item.status}: ${item.path}${item.detail ? ` (${item.detail})` : ''}`
|
|
240
|
+
)),
|
|
241
|
+
renderOpenCodePlugin(report.openCodePlugin, report.expectedVersion),
|
|
242
|
+
];
|
|
243
|
+
for (const bin of report.bins) {
|
|
244
|
+
switch (bin.status) {
|
|
245
|
+
case 'missing':
|
|
246
|
+
lines.push(
|
|
247
|
+
`Repair missing ${bin.name}: npm install --global borgmcp@${report.expectedVersion} --ignore-scripts`,
|
|
248
|
+
);
|
|
249
|
+
break;
|
|
250
|
+
case 'wrong-owner':
|
|
251
|
+
case 'version-skew':
|
|
252
|
+
lines.push(
|
|
253
|
+
`Fix PATH so ${bin.name} resolves to borgmcp@${report.expectedVersion}, then run: borg doctor`,
|
|
254
|
+
);
|
|
255
|
+
break;
|
|
256
|
+
case 'unreadable':
|
|
257
|
+
lines.push(`Fix or replace unreadable ${bin.name}, then run: borg doctor`);
|
|
258
|
+
break;
|
|
259
|
+
case 'ok':
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
for (const config of report.hookConfigs) {
|
|
264
|
+
if (config.status === 'stale') {
|
|
265
|
+
lines.push('Repair stale managed hooks: borg update --yes');
|
|
266
|
+
} else if (config.status === 'invalid') {
|
|
267
|
+
lines.push(config.detail?.startsWith('inventory failed:')
|
|
268
|
+
? `Restore readable, non-symlinked hook inventory path ${config.path}, then run: borg doctor`
|
|
269
|
+
: `Repair invalid managed hook config ${config.path}, then run: borg update --yes`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (report.openCodePlugin.status === 'missing') {
|
|
273
|
+
lines.push('Repair missing OpenCode plugin: borg update --yes');
|
|
274
|
+
} else if (report.openCodePlugin.status === 'outdated' && report.openCodePlugin.configured) {
|
|
275
|
+
lines.push('Repair OpenCode plugin: borg update --yes');
|
|
276
|
+
} else if (report.openCodePlugin.status === 'unreadable') {
|
|
277
|
+
lines.push(report.openCodePlugin.configured
|
|
278
|
+
? `Fix or replace unreadable OpenCode plugin ${report.openCodePlugin.path}, then run: borg update --yes`
|
|
279
|
+
: `Fix or remove unreadable OpenCode plugin ${report.openCodePlugin.path}`);
|
|
280
|
+
} else if (report.openCodePlugin.status === 'refused' && report.openCodePlugin.configured) {
|
|
281
|
+
lines.push(
|
|
282
|
+
`Remove or replace the OpenCode plugin path ${report.openCodePlugin.path}, then run: borg update --yes`,
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
return `${lines.join('\n')}\n`;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function assertAgentIntegrationHealthy(report: AgentIntegrationHealth): void {
|
|
289
|
+
if (report.issues.length === 0) return;
|
|
290
|
+
throw new Error(renderAgentIntegrationHealth(report).trim());
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export function runDoctor(options: InspectAgentIntegrationHealthOptions & {
|
|
294
|
+
stdout?: (text: string) => void;
|
|
295
|
+
} = {}): number {
|
|
296
|
+
const report = inspectAgentIntegrationHealth(options);
|
|
297
|
+
(options.stdout ?? ((text) => process.stdout.write(text)))(renderAgentIntegrationHealth(report));
|
|
298
|
+
return report.issues.length === 0 ? 0 : 1;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export function warnIfAgentIntegrationUnhealthy(
|
|
302
|
+
options: InspectAgentIntegrationHealthOptions & { stderr?: (text: string) => void } = {},
|
|
303
|
+
): boolean {
|
|
304
|
+
const stderr = options.stderr ?? ((text) => process.stderr.write(text));
|
|
305
|
+
try {
|
|
306
|
+
const report = inspectAgentIntegrationHealth(options);
|
|
307
|
+
if (report.issues.length === 0) return true;
|
|
308
|
+
stderr(
|
|
309
|
+
`Warning: Borg agent integration is incomplete or version-skewed.\n${renderAgentIntegrationHealth(report)}`,
|
|
310
|
+
);
|
|
311
|
+
return false;
|
|
312
|
+
} catch (error) {
|
|
313
|
+
stderr(
|
|
314
|
+
`Warning: Borg agent integration health check failed; launch continues: ${error instanceof Error ? error.message : String(error)}\n`,
|
|
315
|
+
);
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** Update-time whole-integration refresh. Attempt each independent surface so
|
|
321
|
+
* one malformed config does not hide later repairs, then aggregate failures. */
|
|
322
|
+
export function refreshAndVerifyManagedAgentIntegrations(): void {
|
|
323
|
+
const failures: string[] = [];
|
|
324
|
+
try {
|
|
325
|
+
refreshManagedAgentMcpConfigs();
|
|
326
|
+
} catch (error) {
|
|
327
|
+
failures.push(
|
|
328
|
+
`${error instanceof Error ? error.message : String(error)}. ` +
|
|
329
|
+
'Repair the named agent MCP config, then rerun borg update --yes',
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
try {
|
|
333
|
+
refreshManagedAgentHookConfigs();
|
|
334
|
+
} catch (error) {
|
|
335
|
+
failures.push(
|
|
336
|
+
`${error instanceof Error ? error.message : String(error)}. ` +
|
|
337
|
+
'Repair the named managed hook config, then rerun borg update --yes',
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
if (isOpenCodeMcpServerConfigured()) installBorgPlugin();
|
|
341
|
+
try {
|
|
342
|
+
assertAgentIntegrationHealthy(inspectAgentIntegrationHealth());
|
|
343
|
+
} catch (error) {
|
|
344
|
+
failures.push(error instanceof Error ? error.message : String(error));
|
|
345
|
+
}
|
|
346
|
+
if (failures.length > 0) throw new Error(failures.join('; '));
|
|
347
|
+
}
|
package/src/claude.ts
CHANGED
|
@@ -86,6 +86,7 @@ import { allocateOpenCodePort, connectOpenCodeDrone, createOpenCodeLaunchKickoff
|
|
|
86
86
|
import { buildOpenCodeLaunchArgs, defaultApprovalIo, resolveLaunchBorgApprovals } from './cli-tool-approval.js';
|
|
87
87
|
import { isClientOwnedCubeInitArgv, runEarlyServerFacade } from './server-facade.js';
|
|
88
88
|
import { runEarlyUpdate } from './update-cmd.js';
|
|
89
|
+
import { runDoctor, warnIfAgentIntegrationUnhealthy } from './agent-integration-health.js';
|
|
89
90
|
|
|
90
91
|
export type AssimilateDepsBuilder = typeof buildDefaultAssimilateDeps;
|
|
91
92
|
|
|
@@ -209,6 +210,9 @@ async function main() {
|
|
|
209
210
|
await import('./setup.js');
|
|
210
211
|
return;
|
|
211
212
|
}
|
|
213
|
+
if (process.argv[2] === 'doctor') {
|
|
214
|
+
process.exit(runDoctor());
|
|
215
|
+
}
|
|
212
216
|
if (process.argv[2] === 'assimilate') {
|
|
213
217
|
const code = await runAssimilateEntry(process.argv.slice(3));
|
|
214
218
|
process.exit(code);
|
|
@@ -626,10 +630,20 @@ function ensureResolvedCliConfigured(cli: BorgCli): void {
|
|
|
626
630
|
addClaudeUserPromptSubmitHook: addUserPromptSubmitHook,
|
|
627
631
|
addCodexSessionStartHook,
|
|
628
632
|
addCodexUserPromptSubmitHook,
|
|
633
|
+
installOpenCodePlugin: installBorgPlugin,
|
|
629
634
|
});
|
|
630
635
|
} catch (err: any) {
|
|
631
636
|
console.error(`${consolePrefix()}${chalk.yellow(`warning: ${label} integration check failed: ${err?.message ?? err}`)}`);
|
|
632
637
|
}
|
|
638
|
+
try {
|
|
639
|
+
warnIfAgentIntegrationUnhealthy({
|
|
640
|
+
stderr: (text) => console.error(`${consolePrefix()}${chalk.yellow(text.trimEnd())}`),
|
|
641
|
+
});
|
|
642
|
+
} catch (err: any) {
|
|
643
|
+
console.error(`${consolePrefix()}${chalk.yellow(
|
|
644
|
+
`warning: agent integration health check failed; launch continues: ${err?.message ?? err}`,
|
|
645
|
+
)}`);
|
|
646
|
+
}
|
|
633
647
|
}
|
|
634
648
|
|
|
635
649
|
function isEntryInvocation(): boolean {
|
package/src/cli-help.ts
CHANGED
|
@@ -44,6 +44,15 @@ export function launchAllHelpText(version: string): string {
|
|
|
44
44
|
);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
export function doctorHelpText(version: string): string {
|
|
48
|
+
return (
|
|
49
|
+
`borg doctor (borgmcp ${version}) — inspect Borg agent integrations without changing them\n\n` +
|
|
50
|
+
`Usage:\n` +
|
|
51
|
+
` borg doctor Run read-only checks for hook commands, hook configuration, and the OpenCode plugin\n` +
|
|
52
|
+
` borg doctor --help Show this help\n`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
47
56
|
export function clientSubcommandHelpText(
|
|
48
57
|
command: string | undefined,
|
|
49
58
|
args: readonly string[],
|
|
@@ -57,6 +66,7 @@ export function clientSubcommandHelpText(
|
|
|
57
66
|
case 'recover-enrollment': return recoverEnrollmentHelpText(version);
|
|
58
67
|
case 'cleanup': return cleanupHelpText(version);
|
|
59
68
|
case 'launch-all': return launchAllHelpText(version);
|
|
69
|
+
case 'doctor': return doctorHelpText(version);
|
|
60
70
|
default: return null;
|
|
61
71
|
}
|
|
62
72
|
}
|
|
@@ -86,6 +96,7 @@ export function topLevelHelpText(version: string): string {
|
|
|
86
96
|
` borg Launch your agent CLI; in a TTY, bare borg may show the launch menu\n` +
|
|
87
97
|
` borg setup Set up borg MCP server + agent CLI integration\n` +
|
|
88
98
|
` borg update Update the client and installed local server together\n` +
|
|
99
|
+
` borg doctor Check agent hook commands, versions, configs, and the OpenCode plugin\n` +
|
|
89
100
|
` borg assimilate [role] Join or create a cube\n` +
|
|
90
101
|
` borg assimilate --host <host> Join or create on an explicit server\n` +
|
|
91
102
|
` borg assimilate --worktree <name> Spawn a worktree drone (in ~/.borg/worktrees/<repo>/<name>)\n` +
|
|
@@ -128,9 +139,9 @@ export function updateHelpText(version: string): string {
|
|
|
128
139
|
`and runtime are updated. Alternate registries and unsupported or ambiguous package-manager\n` +
|
|
129
140
|
`provenance fail closed with manual-update guidance.\n\n` +
|
|
130
141
|
`If no local server is installed, the server phase is skipped. A failure after the client\n` +
|
|
131
|
-
`succeeds is reported as partial completion with
|
|
132
|
-
`stopped server. After package verification, Borg replaces stale borgmcp package launch paths\n` +
|
|
133
|
-
`in
|
|
142
|
+
`succeeds is reported as partial completion with status-specific recovery. Borg never starts\n` +
|
|
143
|
+
`a stopped server. After package verification, Borg replaces stale borgmcp package launch paths\n` +
|
|
144
|
+
`in MCP registrations and managed agent hooks while preserving their other settings. It\n` +
|
|
134
145
|
`leaves absent registrations and borg entries that point to another command unchanged. Borg\n` +
|
|
135
146
|
`never restarts agent processes;\n` +
|
|
136
147
|
`restart active agent sessions yourself.\n`
|