@agentbridge1/cli 0.0.6 → 0.0.7
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/build-info.json +4 -4
- package/dist/commands/connect.js +3 -0
- package/dist/commands/doctor.js +139 -50
- package/dist/commands/install-rules.js +64 -0
- package/dist/commands/recover.js +13 -2
- package/dist/commands/room.js +82 -0
- package/dist/config.js +31 -1
- package/dist/error-catalog.js +1 -1
- package/dist/index.js +32 -0
- package/dist/init.js +10 -0
- package/dist/rules-sync.js +138 -0
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"builtAt": "2026-06-
|
|
3
|
-
"gitHead": "
|
|
4
|
-
"sourceLatestMtime": "2026-06-
|
|
5
|
-
"sourceLatestFile": "src/commands/
|
|
2
|
+
"builtAt": "2026-06-07T04:20:05.843Z",
|
|
3
|
+
"gitHead": "aa4b2fb",
|
|
4
|
+
"sourceLatestMtime": "2026-06-07T04:16:53.273Z",
|
|
5
|
+
"sourceLatestFile": "src/commands/room.ts"
|
|
6
6
|
}
|
package/dist/commands/connect.js
CHANGED
|
@@ -293,6 +293,9 @@ async function runConnect(options = {}) {
|
|
|
293
293
|
diagnostics.bootstrapStatus = "skipped";
|
|
294
294
|
diagnostics.rotateStatus = "skipped";
|
|
295
295
|
}
|
|
296
|
+
if (connectionUpgraded) {
|
|
297
|
+
await verifyCredentials(projectId, effectiveApiKey, apiBaseUrl);
|
|
298
|
+
}
|
|
296
299
|
if (!executionSurfaceId) {
|
|
297
300
|
(0, config_1.updateConfig)({
|
|
298
301
|
projectId,
|
package/dist/commands/doctor.js
CHANGED
|
@@ -11,9 +11,15 @@ const dist_freshness_1 = require("./dist-freshness");
|
|
|
11
11
|
const session_state_1 = require("../session-state");
|
|
12
12
|
const server_sync_1 = require("../server-sync");
|
|
13
13
|
const recovery_reconcile_1 = require("../recovery-reconcile");
|
|
14
|
+
const rules_sync_1 = require("../rules-sync");
|
|
14
15
|
function cliRootFromCommandDir() {
|
|
15
16
|
return (0, node_path_1.resolve)(__dirname, "..", "..");
|
|
16
17
|
}
|
|
18
|
+
function isRecoveryBaselinePendingHello(hello) {
|
|
19
|
+
return (hello.setup_required === true ||
|
|
20
|
+
hello.agentbridge_status === "recovery_baseline_pending" ||
|
|
21
|
+
hello.instruction?.recovery_status === "baseline_required");
|
|
22
|
+
}
|
|
17
23
|
function normalizeReasonCandidate(value) {
|
|
18
24
|
if (!value)
|
|
19
25
|
return undefined;
|
|
@@ -87,11 +93,9 @@ function extractHttpReason(error) {
|
|
|
87
93
|
}
|
|
88
94
|
return `http_${error.status}`;
|
|
89
95
|
}
|
|
90
|
-
function detectGovernanceSignals(workspaceRoot) {
|
|
91
|
-
const
|
|
92
|
-
const rulesMarkdownPath = (0, node_path_1.resolve)(workspaceRoot, "AGENTBRIDGE.md");
|
|
96
|
+
function detectGovernanceSignals(workspaceRoot, expectedProjectId) {
|
|
97
|
+
const analysis = (0, rules_sync_1.analyzeRulesArtifacts)(workspaceRoot, expectedProjectId);
|
|
93
98
|
const mcpConfigPath = (0, node_path_1.resolve)(workspaceRoot, ".cursor", "mcp.json");
|
|
94
|
-
const rulesInstalled = (0, node_fs_1.existsSync)(rulesMdcPath) && (0, node_fs_1.existsSync)(rulesMarkdownPath);
|
|
95
99
|
let mcpConfigured = false;
|
|
96
100
|
if ((0, node_fs_1.existsSync)(mcpConfigPath)) {
|
|
97
101
|
try {
|
|
@@ -104,9 +108,54 @@ function detectGovernanceSignals(workspaceRoot) {
|
|
|
104
108
|
}
|
|
105
109
|
}
|
|
106
110
|
return {
|
|
107
|
-
|
|
111
|
+
rulesFilesPresent: analysis.filesPresent,
|
|
112
|
+
rulesFormat: analysis.format,
|
|
113
|
+
rulesProjectIdInRepo: analysis.projectIdInRepo,
|
|
114
|
+
rulesProjectIdMatches: analysis.projectIdMatchesConfig,
|
|
115
|
+
rulesInstalled: analysis.protocolRulesValid,
|
|
116
|
+
rulesIssues: analysis.issues,
|
|
108
117
|
mcpConfigured,
|
|
109
|
-
governanceStatus:
|
|
118
|
+
governanceStatus: analysis.protocolRulesValid && mcpConfigured ? "active" : "inactive",
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function isRouteMissingBody(body) {
|
|
122
|
+
return body.toLowerCase().includes("route not found");
|
|
123
|
+
}
|
|
124
|
+
function mapProjectAccessFailure(error) {
|
|
125
|
+
if (error instanceof http_1.CliHttpError) {
|
|
126
|
+
const reason = extractHttpReason(error);
|
|
127
|
+
if (reason === "not_project_member") {
|
|
128
|
+
return {
|
|
129
|
+
status: "failed",
|
|
130
|
+
reason,
|
|
131
|
+
suggestedNextAction: "use an API key/agent that is a member of this project, or add this agent to the project.",
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (reason === "unauthorized") {
|
|
135
|
+
return {
|
|
136
|
+
status: "failed",
|
|
137
|
+
reason,
|
|
138
|
+
suggestedNextAction: "verify AGENTBRIDGE_API_KEY is valid for this environment and has project read access.",
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
if (reason === "project_not_found") {
|
|
142
|
+
return {
|
|
143
|
+
status: "failed",
|
|
144
|
+
reason,
|
|
145
|
+
suggestedNextAction: "verify AGENTBRIDGE_PROJECT_ID points to an existing project in this API environment.",
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
status: "failed",
|
|
150
|
+
reason,
|
|
151
|
+
suggestedNextAction: "confirm project ID, API key, and base URL are correct and that this key has project read access.",
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
const messageReason = error instanceof Error ? normalizeReasonCandidate(error.message) : undefined;
|
|
155
|
+
return {
|
|
156
|
+
status: "failed",
|
|
157
|
+
reason: messageReason ?? "network_error",
|
|
158
|
+
suggestedNextAction: "check network reachability to the API base URL and retry doctor.",
|
|
110
159
|
};
|
|
111
160
|
}
|
|
112
161
|
async function checkProjectAccess(ctx) {
|
|
@@ -119,46 +168,33 @@ async function checkProjectAccess(ctx) {
|
|
|
119
168
|
const projectId = ctx.projectId;
|
|
120
169
|
const apiKey = ctx.apiKey;
|
|
121
170
|
const apiBaseUrl = ctx.apiBaseUrl;
|
|
171
|
+
const syncCtx = { projectId, apiKey, apiBaseUrl, apiKeySource: "config" };
|
|
172
|
+
const summaryPath = `/v1/dev/projects/${encodeURIComponent(projectId)}/summary`;
|
|
173
|
+
const packetPath = `/v1/dev/projects/${encodeURIComponent(projectId)}/packet`;
|
|
122
174
|
try {
|
|
123
|
-
|
|
175
|
+
await (0, http_1.getJson)(syncCtx, summaryPath);
|
|
176
|
+
let packet;
|
|
177
|
+
try {
|
|
178
|
+
packet = await (0, http_1.getJson)(syncCtx, packetPath);
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
// Summary proves access; packet is optional (recovery metadata only).
|
|
182
|
+
}
|
|
124
183
|
return { status: "ok", packet };
|
|
125
184
|
}
|
|
126
185
|
catch (error) {
|
|
127
|
-
if (error instanceof http_1.CliHttpError
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
suggestedNextAction: "use an API key/agent that is a member of this project, or add this agent to the project.",
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
if (reason === "unauthorized") {
|
|
137
|
-
return {
|
|
138
|
-
status: "failed",
|
|
139
|
-
reason,
|
|
140
|
-
suggestedNextAction: "verify AGENTBRIDGE_API_KEY is valid for this environment and has project read access.",
|
|
141
|
-
};
|
|
186
|
+
if (error instanceof http_1.CliHttpError &&
|
|
187
|
+
error.status === 404 &&
|
|
188
|
+
isRouteMissingBody(error.body ?? "")) {
|
|
189
|
+
try {
|
|
190
|
+
const packet = await (0, http_1.getJson)(syncCtx, packetPath);
|
|
191
|
+
return { status: "ok", packet };
|
|
142
192
|
}
|
|
143
|
-
|
|
144
|
-
return
|
|
145
|
-
status: "failed",
|
|
146
|
-
reason,
|
|
147
|
-
suggestedNextAction: "verify AGENTBRIDGE_PROJECT_ID points to an existing project in this API environment.",
|
|
148
|
-
};
|
|
193
|
+
catch (packetError) {
|
|
194
|
+
return mapProjectAccessFailure(packetError);
|
|
149
195
|
}
|
|
150
|
-
return {
|
|
151
|
-
status: "failed",
|
|
152
|
-
reason,
|
|
153
|
-
suggestedNextAction: "confirm project ID, API key, and base URL are correct and that this key has project read access.",
|
|
154
|
-
};
|
|
155
196
|
}
|
|
156
|
-
|
|
157
|
-
return {
|
|
158
|
-
status: "failed",
|
|
159
|
-
reason: messageReason ?? "network_error",
|
|
160
|
-
suggestedNextAction: "check network reachability to the API base URL and retry doctor.",
|
|
161
|
-
};
|
|
197
|
+
return mapProjectAccessFailure(error);
|
|
162
198
|
}
|
|
163
199
|
}
|
|
164
200
|
async function checkIdentityAccess(ctx) {
|
|
@@ -173,7 +209,8 @@ async function checkIdentityAccess(ctx) {
|
|
|
173
209
|
}
|
|
174
210
|
const helloUrl = `${ctx.apiBaseUrl}/v1/dev/projects/${ctx.projectId}/hello`;
|
|
175
211
|
let identityModel = "unknown";
|
|
176
|
-
let
|
|
212
|
+
let helloExecutionSurfaceId = null;
|
|
213
|
+
let baselinePending = false;
|
|
177
214
|
try {
|
|
178
215
|
const helloRes = await fetch(helloUrl, {
|
|
179
216
|
method: "POST",
|
|
@@ -185,18 +222,22 @@ async function checkIdentityAccess(ctx) {
|
|
|
185
222
|
});
|
|
186
223
|
if (helloRes.ok) {
|
|
187
224
|
const hello = (await helloRes.json());
|
|
225
|
+
baselinePending = isRecoveryBaselinePendingHello(hello);
|
|
188
226
|
if (hello.identity_model === "work_identity") {
|
|
189
227
|
identityModel = "work_identity";
|
|
190
228
|
}
|
|
191
229
|
else if (hello.identity_model === "legacy") {
|
|
192
230
|
identityModel = "legacy";
|
|
193
231
|
}
|
|
194
|
-
|
|
232
|
+
helloExecutionSurfaceId = hello.execution_surface?.id?.trim() || null;
|
|
195
233
|
}
|
|
196
234
|
}
|
|
197
235
|
catch {
|
|
198
236
|
// Keep default unknown/null and continue with identity resolution checks.
|
|
199
237
|
}
|
|
238
|
+
const configSurfaceId = ctx.configExecutionSurfaceId?.trim() || null;
|
|
239
|
+
const executionSurfaceId = helloExecutionSurfaceId || configSurfaceId;
|
|
240
|
+
const executionSurfaceSource = helloExecutionSurfaceId ? "hello" : configSurfaceId ? "config" : undefined;
|
|
200
241
|
try {
|
|
201
242
|
const syncCtx = {
|
|
202
243
|
projectId: ctx.projectId,
|
|
@@ -209,18 +250,28 @@ async function checkIdentityAccess(ctx) {
|
|
|
209
250
|
(0, server_sync_1.listWorkIdentities)(syncCtx),
|
|
210
251
|
]);
|
|
211
252
|
const callerId = callerPacket?.work_identity?.id ?? null;
|
|
212
|
-
const
|
|
213
|
-
if (
|
|
214
|
-
|
|
253
|
+
const callerResolvable = Boolean(callerId) || identities.length === 1;
|
|
254
|
+
if (identityModel === "unknown" &&
|
|
255
|
+
callerResolvable &&
|
|
256
|
+
(baselinePending || Boolean(configSurfaceId))) {
|
|
257
|
+
identityModel = "work_identity";
|
|
215
258
|
}
|
|
216
|
-
|
|
217
|
-
|
|
259
|
+
const startCapable = Boolean(executionSurfaceId);
|
|
260
|
+
if (callerResolvable) {
|
|
261
|
+
return {
|
|
262
|
+
status: "ok",
|
|
263
|
+
identityModel,
|
|
264
|
+
executionSurfaceId,
|
|
265
|
+
executionSurfaceSource,
|
|
266
|
+
startCapable,
|
|
267
|
+
};
|
|
218
268
|
}
|
|
219
269
|
return {
|
|
220
270
|
status: "failed",
|
|
221
271
|
reason: "caller_identity_unresolved",
|
|
222
272
|
identityModel,
|
|
223
273
|
executionSurfaceId,
|
|
274
|
+
executionSurfaceSource,
|
|
224
275
|
startCapable,
|
|
225
276
|
};
|
|
226
277
|
}
|
|
@@ -230,6 +281,7 @@ async function checkIdentityAccess(ctx) {
|
|
|
230
281
|
reason: "caller_identity_unresolved",
|
|
231
282
|
identityModel,
|
|
232
283
|
executionSurfaceId,
|
|
284
|
+
executionSurfaceSource,
|
|
233
285
|
startCapable: Boolean(executionSurfaceId),
|
|
234
286
|
};
|
|
235
287
|
}
|
|
@@ -256,7 +308,7 @@ async function runDoctor(cliRootOverride) {
|
|
|
256
308
|
const projectConfigPresent = projectIdPresent && apiKeyPresent && baseUrlPresent;
|
|
257
309
|
const activeSession = (0, session_state_1.readSessionState)();
|
|
258
310
|
const hasActiveWork = Boolean(activeSession?.id || activeSession?.serverSessionId);
|
|
259
|
-
const governance = detectGovernanceSignals(workspaceRoot);
|
|
311
|
+
const governance = detectGovernanceSignals(workspaceRoot, resolvedProjectId ?? undefined);
|
|
260
312
|
const envContributed = Boolean(process.env.AGENTBRIDGE_PROJECT_ID) ||
|
|
261
313
|
Boolean(process.env.AGENTBRIDGE_API_KEY) ||
|
|
262
314
|
Boolean(process.env.AGENTBRIDGE_BASE_URL) ||
|
|
@@ -290,15 +342,35 @@ async function runDoctor(cliRootOverride) {
|
|
|
290
342
|
if (projectAccess.reason) {
|
|
291
343
|
lines.push(`Project access reason: ${projectAccess.reason}`);
|
|
292
344
|
}
|
|
345
|
+
lines.push(`API key accepted by server: ${projectAccess.status === "ok"
|
|
346
|
+
? "yes"
|
|
347
|
+
: projectAccess.status === "skipped"
|
|
348
|
+
? "skipped"
|
|
349
|
+
: "no"}`);
|
|
293
350
|
const identityAccess = await checkIdentityAccess({
|
|
294
351
|
projectId: resolvedProjectId,
|
|
295
352
|
apiKey: resolvedApiKey,
|
|
296
353
|
apiBaseUrl: resolvedBaseUrl,
|
|
297
|
-
configComplete: projectConfigPresent
|
|
354
|
+
configComplete: projectConfigPresent,
|
|
355
|
+
configExecutionSurfaceId: cfg.executionSurfaceId,
|
|
298
356
|
});
|
|
357
|
+
lines.push(`Rules files present: ${governance.rulesFilesPresent ? "yes" : "no"}`);
|
|
358
|
+
lines.push(`Rules format: ${governance.rulesFormat}`);
|
|
359
|
+
if (governance.rulesProjectIdInRepo) {
|
|
360
|
+
lines.push(`Rules project ID: ${governance.rulesProjectIdInRepo}`);
|
|
361
|
+
}
|
|
362
|
+
if (governance.rulesProjectIdMatches !== null) {
|
|
363
|
+
lines.push(`Rules project ID match: ${governance.rulesProjectIdMatches ? "yes" : "no"}`);
|
|
364
|
+
}
|
|
299
365
|
lines.push(`Rules installed: ${governance.rulesInstalled ? "yes" : "no"}`);
|
|
300
366
|
lines.push(`MCP configured: ${governance.mcpConfigured ? "yes" : "no"}`);
|
|
301
367
|
lines.push(`Agent governance: ${governance.governanceStatus}`);
|
|
368
|
+
if (governance.rulesIssues.length > 0) {
|
|
369
|
+
for (const issue of governance.rulesIssues) {
|
|
370
|
+
lines.push(`Rules issue: ${issue}`);
|
|
371
|
+
}
|
|
372
|
+
lines.push("Rules fix: run `agentbridge install-rules`");
|
|
373
|
+
}
|
|
302
374
|
lines.push(`Start capable: ${identityAccess.startCapable ? "yes" : "no"}`);
|
|
303
375
|
if (projectAccess.status === "failed" && projectAccess.reason) {
|
|
304
376
|
const view = (0, error_catalog_1.catalogViewForDoctorReason)(projectAccess.reason, projectAccess.suggestedNextAction);
|
|
@@ -367,7 +439,7 @@ async function runDoctor(cliRootOverride) {
|
|
|
367
439
|
lines.push(`- Rules: ${governance.rulesInstalled ? "installed" : "missing"}`);
|
|
368
440
|
lines.push(`- MCP: ${governance.mcpConfigured ? "configured" : "missing"}`);
|
|
369
441
|
lines.push("- Note: Project context exists, but domains are not fully mapped yet.");
|
|
370
|
-
lines.push("- Next: run `
|
|
442
|
+
lines.push("- Next: run `agentbridge recover --force` for a full domain rebuild, or `agentbridge watch` for live supervision");
|
|
371
443
|
}
|
|
372
444
|
else if (productStatus === "active_work_found") {
|
|
373
445
|
const recoveryBasic = (0, recovery_reconcile_1.recoveryIsBasicPacket)(projectAccess.packet);
|
|
@@ -399,6 +471,16 @@ async function runDoctor(cliRootOverride) {
|
|
|
399
471
|
if (!projectConfigPresent) {
|
|
400
472
|
lines.push("- Note: Connection details are missing. Add credentials, then rerun agentbridge doctor.");
|
|
401
473
|
}
|
|
474
|
+
else if (projectAccess.status === "failed" &&
|
|
475
|
+
(projectAccess.reason === "unauthorized" ||
|
|
476
|
+
projectAccess.reason === "forbidden" ||
|
|
477
|
+
projectAccess.reason === "not_project_member")) {
|
|
478
|
+
lines.push("- Reason: API key rejected by server.");
|
|
479
|
+
lines.push("- Next: run `agentbridge room exit`, get a fresh key from the dashboard, then `agentbridge connect`.");
|
|
480
|
+
if (identityAccess.executionSurfaceId) {
|
|
481
|
+
lines.push("- Note: Config still has a saved execution surface, but the server rejected the stored API key.");
|
|
482
|
+
}
|
|
483
|
+
}
|
|
402
484
|
else if (!identityAccess.startCapable) {
|
|
403
485
|
lines.push("- Reason: execution surface missing.");
|
|
404
486
|
lines.push("- Next: agentbridge connect");
|
|
@@ -406,7 +488,11 @@ async function runDoctor(cliRootOverride) {
|
|
|
406
488
|
else {
|
|
407
489
|
lines.push("- Note: Connection check failed. Verify credentials/network, then rerun agentbridge doctor.");
|
|
408
490
|
}
|
|
409
|
-
if (identityAccess.startCapable
|
|
491
|
+
if (identityAccess.startCapable &&
|
|
492
|
+
projectAccess.status === "failed" &&
|
|
493
|
+
projectAccess.reason !== "unauthorized" &&
|
|
494
|
+
projectAccess.reason !== "forbidden" &&
|
|
495
|
+
projectAccess.reason !== "not_project_member") {
|
|
410
496
|
lines.push("- Next: agentbridge doctor");
|
|
411
497
|
}
|
|
412
498
|
}
|
|
@@ -418,6 +504,9 @@ async function runDoctor(cliRootOverride) {
|
|
|
418
504
|
}
|
|
419
505
|
lines.push(`- Caller identity model: ${identityAccess.identityModel}`);
|
|
420
506
|
lines.push(`- Execution surface present: ${identityAccess.executionSurfaceId ? "yes" : "no"}`);
|
|
507
|
+
if (identityAccess.executionSurfaceSource) {
|
|
508
|
+
lines.push(`- Execution surface source: ${identityAccess.executionSurfaceSource}`);
|
|
509
|
+
}
|
|
421
510
|
lines.push(`- Start capable (strict/session modes): ${identityAccess.startCapable ? "yes" : "no"}`);
|
|
422
511
|
if (identityAccess.status !== "ok") {
|
|
423
512
|
lines.push("- Note: internal identity mismatch affects strict/resume flows, not room-level inferred watch startup.");
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runInstallRules = runInstallRules;
|
|
4
|
+
const node_path_1 = require("node:path");
|
|
5
|
+
const node_process_1 = require("node:process");
|
|
6
|
+
const config_1 = require("../config");
|
|
7
|
+
const errors_1 = require("../errors");
|
|
8
|
+
const http_1 = require("../http");
|
|
9
|
+
const rules_sync_1 = require("../rules-sync");
|
|
10
|
+
function resolveNetworkContext(overrides) {
|
|
11
|
+
const cfg = (0, config_1.readConfig)();
|
|
12
|
+
const projectId = overrides.projectId ?? process.env.AGENTBRIDGE_PROJECT_ID ?? cfg.projectId ?? "";
|
|
13
|
+
const apiKey = overrides.apiKey ?? process.env.AGENTBRIDGE_API_KEY ?? cfg.apiKey ?? "";
|
|
14
|
+
const apiBaseUrl = overrides.apiBaseUrl ??
|
|
15
|
+
process.env.AGENTBRIDGE_BASE_URL ??
|
|
16
|
+
cfg.apiBaseUrl ??
|
|
17
|
+
"https://agentauth-api-production.up.railway.app";
|
|
18
|
+
if (!projectId || !apiKey) {
|
|
19
|
+
throw (0, errors_1.catalogCliError)("CONFIG_INCOMPLETE");
|
|
20
|
+
}
|
|
21
|
+
return { projectId, apiKey, apiBaseUrl };
|
|
22
|
+
}
|
|
23
|
+
async function runInstallRules(opts = {}) {
|
|
24
|
+
const ctx = resolveNetworkContext(opts);
|
|
25
|
+
const workspaceRoot = (0, node_process_1.cwd)();
|
|
26
|
+
let rules;
|
|
27
|
+
try {
|
|
28
|
+
rules = await (0, rules_sync_1.fetchProjectRules)(ctx);
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if ((0, http_1.isCliHttpError)(error)) {
|
|
32
|
+
throw new errors_1.SafeCliError(`Could not fetch AgentBridge rules for project ${ctx.projectId} (${error.status}).`);
|
|
33
|
+
}
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
if (!Array.isArray(rules.files) || rules.files.length === 0) {
|
|
37
|
+
throw new errors_1.SafeCliError("AgentBridge rules response did not include any files to install.");
|
|
38
|
+
}
|
|
39
|
+
(0, rules_sync_1.writeRulesFiles)(workspaceRoot, rules.files);
|
|
40
|
+
let installedAt = null;
|
|
41
|
+
try {
|
|
42
|
+
const marked = await (0, rules_sync_1.markProjectRulesInstalled)(ctx);
|
|
43
|
+
installedAt = marked.rules_installed_at ?? null;
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if ((0, http_1.isCliHttpError)(error)) {
|
|
47
|
+
throw new errors_1.SafeCliError(`Wrote rules files locally, but could not mark them installed on the server (${error.status}).`);
|
|
48
|
+
}
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
const projectName = (0, rules_sync_1.extractProjectNameFromRulesResponse)(rules.files);
|
|
52
|
+
const lines = [
|
|
53
|
+
"Installed AgentBridge protocol rules:",
|
|
54
|
+
` Project: ${projectName ?? "(unknown name)"} (${ctx.projectId})`,
|
|
55
|
+
` Wrote: ${(0, node_path_1.resolve)(workspaceRoot, "AGENTBRIDGE.md")}`,
|
|
56
|
+
` Wrote: ${(0, node_path_1.resolve)(workspaceRoot, ".cursor", "rules", "agentbridge.mdc")}`,
|
|
57
|
+
];
|
|
58
|
+
if (installedAt) {
|
|
59
|
+
lines.push(` Server marked installed at: ${installedAt}`);
|
|
60
|
+
}
|
|
61
|
+
lines.push("");
|
|
62
|
+
lines.push("Next: run `agentbridge doctor` to verify rules and MCP configuration.");
|
|
63
|
+
process.stdout.write(`${lines.join("\n")}\n`);
|
|
64
|
+
}
|
package/dist/commands/recover.js
CHANGED
|
@@ -110,7 +110,7 @@ function renderBasicHint() {
|
|
|
110
110
|
"",
|
|
111
111
|
"Recovery: basic",
|
|
112
112
|
"AgentBridge found only a generic project area.",
|
|
113
|
-
"Run `
|
|
113
|
+
"Run `agentbridge recover --force` to rebuild the domain map.",
|
|
114
114
|
"",
|
|
115
115
|
].join("\n");
|
|
116
116
|
}
|
|
@@ -146,7 +146,18 @@ function normalizeBootstrapError(error) {
|
|
|
146
146
|
return (0, errors_1.catalogCliError)("CONFIG_INCOMPLETE", {
|
|
147
147
|
what: `Recovery bootstrap payload was rejected by server validation${detailText}.`,
|
|
148
148
|
why: "The server cannot activate recovery until required bootstrap fields pass validation.",
|
|
149
|
-
next: "Retry `agentbridge recover`. If it fails again, run with
|
|
149
|
+
next: "Retry `agentbridge recover`. If it fails again, run with AGENTBRIDGE_DEBUG_HTTP=1 and share output.",
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
if ((0, http_1.isCliHttpError)(error) && error.status >= 500) {
|
|
153
|
+
const parsed = (0, http_1.parseCliHttpErrorBody)(error);
|
|
154
|
+
const serverDetail = (0, http_1.extractHttpErrorCode)(parsed) ||
|
|
155
|
+
error.body.trim().slice(0, 240) ||
|
|
156
|
+
"no response body";
|
|
157
|
+
return (0, errors_1.catalogCliError)("SERVER_ERROR", {
|
|
158
|
+
what: `Recovery bootstrap failed with HTTP ${error.status}.`,
|
|
159
|
+
why: "The server errored while applying recovery baseline at POST /v1/dev/projects/{id}/bootstrap.",
|
|
160
|
+
next: `Retry agentbridge recover. For full request/response traces, run with AGENTBRIDGE_DEBUG_HTTP=1. Server detail: ${serverDetail}`,
|
|
150
161
|
});
|
|
151
162
|
}
|
|
152
163
|
if (error instanceof Error) {
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runRoomExit = runRoomExit;
|
|
4
|
+
const config_1 = require("../config");
|
|
5
|
+
const session_state_1 = require("../session-state");
|
|
6
|
+
function envOverridesConfig() {
|
|
7
|
+
const overrides = [];
|
|
8
|
+
if (process.env.AGENTBRIDGE_PROJECT_ID?.trim()) {
|
|
9
|
+
overrides.push("AGENTBRIDGE_PROJECT_ID");
|
|
10
|
+
}
|
|
11
|
+
if (process.env.AGENTBRIDGE_API_KEY?.trim()) {
|
|
12
|
+
overrides.push("AGENTBRIDGE_API_KEY");
|
|
13
|
+
}
|
|
14
|
+
if (process.env.AGENTBRIDGE_BASE_URL?.trim()) {
|
|
15
|
+
overrides.push("AGENTBRIDGE_BASE_URL");
|
|
16
|
+
}
|
|
17
|
+
return overrides;
|
|
18
|
+
}
|
|
19
|
+
function shouldClearLocalSession(keepSession) {
|
|
20
|
+
if (keepSession)
|
|
21
|
+
return false;
|
|
22
|
+
const state = (0, session_state_1.readSessionState)();
|
|
23
|
+
if (!state)
|
|
24
|
+
return false;
|
|
25
|
+
return state.status === "active" || state.status === "blocked";
|
|
26
|
+
}
|
|
27
|
+
function runRoomExit(options = {}) {
|
|
28
|
+
const before = (0, config_1.readConfig)();
|
|
29
|
+
const hadConnection = Boolean(before.projectId || before.apiKey || before.executionSurfaceId || before.activeAgentId);
|
|
30
|
+
const result = (0, config_1.clearRoomConnection)();
|
|
31
|
+
const clearedSession = shouldClearLocalSession(Boolean(options.keepSession));
|
|
32
|
+
if (clearedSession) {
|
|
33
|
+
(0, session_state_1.clearSessionState)();
|
|
34
|
+
}
|
|
35
|
+
const envOverrides = envOverridesConfig();
|
|
36
|
+
if (options.json) {
|
|
37
|
+
process.stdout.write(`${JSON.stringify({
|
|
38
|
+
action: "room_exit",
|
|
39
|
+
config_path: config_1.CONFIG_PATH,
|
|
40
|
+
had_connection: hadConnection,
|
|
41
|
+
cleared_keys: result.clearedKeys,
|
|
42
|
+
previous_project_id: result.previousProjectId ?? null,
|
|
43
|
+
had_api_key: result.hadApiKey,
|
|
44
|
+
cleared_local_session: clearedSession,
|
|
45
|
+
env_overrides: envOverrides,
|
|
46
|
+
preserved: ["domains", "apiBaseUrl", "precommitHookInstalled", "cliPath"],
|
|
47
|
+
}, null, 2)}\n`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
process.stdout.write("AgentBridge room exit\n");
|
|
51
|
+
process.stdout.write("─────────────────────────────────────────\n");
|
|
52
|
+
if (!hadConnection && result.clearedKeys.length === 0) {
|
|
53
|
+
process.stdout.write("No saved room connection in .agentbridge/config.json.\n");
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
process.stdout.write("Cleared saved room connection from .agentbridge/config.json.\n");
|
|
57
|
+
if (result.previousProjectId) {
|
|
58
|
+
process.stdout.write(` Project: ${result.previousProjectId}\n`);
|
|
59
|
+
}
|
|
60
|
+
if (result.hadApiKey) {
|
|
61
|
+
process.stdout.write(" API key: removed from config\n");
|
|
62
|
+
}
|
|
63
|
+
if (result.clearedKeys.includes("executionSurfaceId")) {
|
|
64
|
+
process.stdout.write(" Execution surface: removed from config\n");
|
|
65
|
+
}
|
|
66
|
+
if (result.clearedKeys.includes("activeAgentId")) {
|
|
67
|
+
process.stdout.write(" Active agent: removed from config\n");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (clearedSession) {
|
|
71
|
+
process.stdout.write("Cleared active local supervision session (.agentbridge/session.json).\n");
|
|
72
|
+
}
|
|
73
|
+
process.stdout.write("\nPreserved: domain map and other recovery data from init/recover.\n");
|
|
74
|
+
process.stdout.write("Server rules and MCP config on disk are unchanged.\n");
|
|
75
|
+
if (envOverrides.length > 0) {
|
|
76
|
+
process.stdout.write(`\nNote: ${envOverrides.join(", ")} still set in your shell — they override config until unset.\n`);
|
|
77
|
+
}
|
|
78
|
+
process.stdout.write("\nNext:\n");
|
|
79
|
+
process.stdout.write(" 1. Get a fresh API key at https://agentbridge.dev/dashboard\n");
|
|
80
|
+
process.stdout.write(" 2. agentbridge connect --project <id> --api-key <key>\n");
|
|
81
|
+
process.stdout.write(" 3. agentbridge doctor\n");
|
|
82
|
+
}
|
package/dist/config.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DEFAULT_API_BASE_URL = exports.CONFIG_PATH = exports.CONFIG_DIR = void 0;
|
|
3
|
+
exports.ROOM_CONNECTION_KEYS = exports.DEFAULT_API_BASE_URL = exports.CONFIG_PATH = exports.CONFIG_DIR = void 0;
|
|
4
4
|
exports.readConfig = readConfig;
|
|
5
5
|
exports.writeConfig = writeConfig;
|
|
6
6
|
exports.updateConfig = updateConfig;
|
|
7
|
+
exports.clearRoomConnection = clearRoomConnection;
|
|
7
8
|
exports.contextFromConfig = contextFromConfig;
|
|
8
9
|
const node_fs_1 = require("node:fs");
|
|
9
10
|
const node_path_1 = require("node:path");
|
|
@@ -30,6 +31,35 @@ function updateConfig(partial) {
|
|
|
30
31
|
writeConfig(next);
|
|
31
32
|
return next;
|
|
32
33
|
}
|
|
34
|
+
/** Config keys that bind this repo to a cloud room / execution surface. */
|
|
35
|
+
exports.ROOM_CONNECTION_KEYS = [
|
|
36
|
+
"projectId",
|
|
37
|
+
"apiKey",
|
|
38
|
+
"activeAgentId",
|
|
39
|
+
"executionSurfaceId",
|
|
40
|
+
"activeChangeRequestId",
|
|
41
|
+
"lastAcceptedSessionId",
|
|
42
|
+
"lastAcceptedAt",
|
|
43
|
+
];
|
|
44
|
+
function clearRoomConnection() {
|
|
45
|
+
const current = readConfig();
|
|
46
|
+
const clearedKeys = [];
|
|
47
|
+
const next = { ...current };
|
|
48
|
+
for (const key of exports.ROOM_CONNECTION_KEYS) {
|
|
49
|
+
if (next[key] !== undefined) {
|
|
50
|
+
clearedKeys.push(key);
|
|
51
|
+
delete next[key];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (clearedKeys.length > 0) {
|
|
55
|
+
writeConfig(next);
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
clearedKeys,
|
|
59
|
+
previousProjectId: current.projectId,
|
|
60
|
+
hadApiKey: Boolean(current.apiKey),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
33
63
|
function contextFromConfig() {
|
|
34
64
|
const cfg = readConfig();
|
|
35
65
|
const apiBaseUrl = process.env.AGENTBRIDGE_BASE_URL ??
|
package/dist/error-catalog.js
CHANGED
|
@@ -20,7 +20,7 @@ const CATALOG = {
|
|
|
20
20
|
title: "Authentication failed.",
|
|
21
21
|
what: "The API key was missing, invalid, or rejected by the server.",
|
|
22
22
|
why: "AgentBridge cannot reach project APIs without valid credentials.",
|
|
23
|
-
next: "
|
|
23
|
+
next: "Run `agentbridge room exit`, set a fresh AGENTBRIDGE_API_KEY, then `agentbridge connect`.",
|
|
24
24
|
},
|
|
25
25
|
PROJECT_ACCESS_NOT_FOUND: {
|
|
26
26
|
code: "PROJECT_ACCESS_NOT_FOUND",
|
package/dist/index.js
CHANGED
|
@@ -25,11 +25,13 @@ const memory_1 = require("./commands/memory");
|
|
|
25
25
|
const session_1 = require("./commands/session");
|
|
26
26
|
const handshake_1 = require("./commands/handshake");
|
|
27
27
|
const connect_1 = require("./commands/connect");
|
|
28
|
+
const room_1 = require("./commands/room");
|
|
28
29
|
const setup_mcp_1 = require("./commands/setup-mcp");
|
|
29
30
|
const bug_1 = require("./commands/bug");
|
|
30
31
|
const autopilot_1 = require("./commands/autopilot");
|
|
31
32
|
const attention_1 = require("./commands/attention");
|
|
32
33
|
const recover_1 = require("./commands/recover");
|
|
34
|
+
const install_rules_1 = require("./commands/install-rules");
|
|
33
35
|
const proof_guidance_1 = require("./commands/proof-guidance");
|
|
34
36
|
const node_path_1 = require("node:path");
|
|
35
37
|
const node_child_process_1 = require("node:child_process");
|
|
@@ -41,6 +43,10 @@ const COMMAND_HELP = {
|
|
|
41
43
|
connect: " agentbridge connect [--project <id>] [--api-key <key>] [--api-base-url <url>] [--agent <id>]\n" +
|
|
42
44
|
" Save credentials and verify connection to your AgentBridge project.\n" +
|
|
43
45
|
" Credentials can also come from AGENTBRIDGE_PROJECT_ID / AGENTBRIDGE_API_KEY env vars.\n",
|
|
46
|
+
room: " agentbridge room exit [--keep-session] [--json]\n" +
|
|
47
|
+
" Clear saved room credentials from .agentbridge/config.json (no server call).\n" +
|
|
48
|
+
" Use when API keys are rejected or you need to connect to a different project.\n" +
|
|
49
|
+
" Preserves domain recovery data from init/recover.\n",
|
|
44
50
|
recover: " agentbridge recover [--force]\n" +
|
|
45
51
|
" Recover project context so AI agents do not start blind.\n" +
|
|
46
52
|
" Shows project name, domains, and known context.\n",
|
|
@@ -89,6 +95,9 @@ const COMMAND_HELP = {
|
|
|
89
95
|
" Manage domain memory packets.\n",
|
|
90
96
|
version: " agentbridge version\n Print CLI version.\n",
|
|
91
97
|
doctor: " agentbridge doctor\n Run config and connectivity diagnostics.\n",
|
|
98
|
+
"install-rules": " agentbridge install-rules [--project <id>] [--api-key <key>] [--api-base-url <url>]\n" +
|
|
99
|
+
" Fetch server protocol rules for the connected project, write AGENTBRIDGE.md and\n" +
|
|
100
|
+
" .cursor/rules/agentbridge.mdc, and mark rules installed on the server.\n",
|
|
92
101
|
"precommit-check": " agentbridge precommit-check\n Git pre-commit hook: block commits that violate domain boundaries.\n",
|
|
93
102
|
bug: " agentbridge bug report --title \"<text>\" --severity P0|P1|P2|P3 --category <CATEGORY> \\\n" +
|
|
94
103
|
" [--found-in dogfood,production,...] --observed \"<text>\" --expected \"<text>\" \\\n" +
|
|
@@ -118,6 +127,8 @@ function usage(command, opts) {
|
|
|
118
127
|
"Setup:",
|
|
119
128
|
" agentbridge doctor Check connection and repo readiness",
|
|
120
129
|
" agentbridge connect Save credentials (cloud projects)",
|
|
130
|
+
" agentbridge room exit Clear stale room credentials (no server call)",
|
|
131
|
+
" agentbridge install-rules Install server protocol rules in this repo",
|
|
121
132
|
"",
|
|
122
133
|
"Advanced commands:",
|
|
123
134
|
" start --task ... --scope ... Strict scoped session (both flags required)",
|
|
@@ -263,6 +274,27 @@ async function main() {
|
|
|
263
274
|
});
|
|
264
275
|
return;
|
|
265
276
|
}
|
|
277
|
+
if (command === "room") {
|
|
278
|
+
const sub = positional[0] ?? "";
|
|
279
|
+
if (sub === "exit") {
|
|
280
|
+
(0, room_1.runRoomExit)({
|
|
281
|
+
keepSession: flags["--keep-session"] === "true",
|
|
282
|
+
json: flags["--json"] === "true",
|
|
283
|
+
});
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
process.stderr.write("Usage: agentbridge room exit [--keep-session] [--json]\n");
|
|
287
|
+
process.exit(3);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (command === "install-rules") {
|
|
291
|
+
await (0, install_rules_1.runInstallRules)({
|
|
292
|
+
projectId,
|
|
293
|
+
apiKey,
|
|
294
|
+
apiBaseUrl,
|
|
295
|
+
});
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
266
298
|
if (command === "recover") {
|
|
267
299
|
await (0, recover_1.runRecover)({ force: Boolean(flags["--force"]) });
|
|
268
300
|
return;
|
package/dist/init.js
CHANGED
|
@@ -597,6 +597,16 @@ async function runBootstrapRecovery(ctx, opts = {}) {
|
|
|
597
597
|
const existingBootstrap = refsToBootstrapDomains(opts.reconcilePlan.existingActive);
|
|
598
598
|
bootstrapPayload.domains = (0, recovery_reconcile_1.mergeBootstrapDomainEntries)(opts.reconcilePlan, candidateBootstrap, existingBootstrap);
|
|
599
599
|
}
|
|
600
|
+
if (process.env.AGENTBRIDGE_DEBUG_HTTP === "1") {
|
|
601
|
+
const payloadDomains = Array.isArray(bootstrapPayload.domains)
|
|
602
|
+
? bootstrapPayload.domains
|
|
603
|
+
: [];
|
|
604
|
+
const mappedPathCount = payloadDomains.reduce((count, domain) => {
|
|
605
|
+
const paths = domain.files && domain.files.length > 0 ? domain.files : domain.pathPatterns;
|
|
606
|
+
return count + (Array.isArray(paths) ? paths.length : 0);
|
|
607
|
+
}, 0);
|
|
608
|
+
process.stderr.write(`[agentbridge:recover] bootstrap POST project=${ctx.projectId} domains=${payloadDomains.length} mapped_paths=${mappedPathCount} product_summary_len=${String(bootstrapPayload.product_summary ?? "").length}\n`);
|
|
609
|
+
}
|
|
600
610
|
await (0, http_1.postJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/bootstrap`, bootstrapPayload);
|
|
601
611
|
const payloadDomains = Array.isArray(bootstrapPayload.domains)
|
|
602
612
|
? bootstrapPayload.domains
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PROTOCOL_RULES_MARKER = exports.LOCAL_RULES_HEADER = void 0;
|
|
4
|
+
exports.rulesArtifactPaths = rulesArtifactPaths;
|
|
5
|
+
exports.extractProjectIdFromRulesText = extractProjectIdFromRulesText;
|
|
6
|
+
exports.classifyRulesFormat = classifyRulesFormat;
|
|
7
|
+
exports.analyzeRulesArtifacts = analyzeRulesArtifacts;
|
|
8
|
+
exports.fetchProjectRules = fetchProjectRules;
|
|
9
|
+
exports.writeRulesFiles = writeRulesFiles;
|
|
10
|
+
exports.markProjectRulesInstalled = markProjectRulesInstalled;
|
|
11
|
+
exports.extractProjectNameFromRulesResponse = extractProjectNameFromRulesResponse;
|
|
12
|
+
const node_fs_1 = require("node:fs");
|
|
13
|
+
const node_path_1 = require("node:path");
|
|
14
|
+
const http_1 = require("./http");
|
|
15
|
+
exports.LOCAL_RULES_HEADER = "# AgentBridge Local Rules";
|
|
16
|
+
exports.PROTOCOL_RULES_MARKER = "This repo is coordinated through AgentBridge";
|
|
17
|
+
function rulesArtifactPaths(workspaceRoot) {
|
|
18
|
+
return {
|
|
19
|
+
rulesMarkdownPath: (0, node_path_1.resolve)(workspaceRoot, "AGENTBRIDGE.md"),
|
|
20
|
+
rulesMdcPath: (0, node_path_1.resolve)(workspaceRoot, ".cursor", "rules", "agentbridge.mdc"),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function extractProjectIdFromRulesText(content) {
|
|
24
|
+
const markdownMatch = content.match(/\*\*Project ID:\*\*\s*`([^`]+)`/);
|
|
25
|
+
if (markdownMatch?.[1]) {
|
|
26
|
+
return markdownMatch[1].trim();
|
|
27
|
+
}
|
|
28
|
+
const inlineMatch = content.match(/project ID:\s*`([^`]+)`/i);
|
|
29
|
+
if (inlineMatch?.[1]) {
|
|
30
|
+
return inlineMatch[1].trim();
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
function classifyRulesFormat(markdownContent, mdcContent) {
|
|
35
|
+
const md = markdownContent ?? "";
|
|
36
|
+
const mdc = mdcContent ?? "";
|
|
37
|
+
if (!md.trim() && !mdc.trim()) {
|
|
38
|
+
return "missing";
|
|
39
|
+
}
|
|
40
|
+
if (md.startsWith(exports.LOCAL_RULES_HEADER) || mdc.includes("Recovered domains:")) {
|
|
41
|
+
return "local";
|
|
42
|
+
}
|
|
43
|
+
if (md.includes(exports.PROTOCOL_RULES_MARKER) ||
|
|
44
|
+
mdc.includes("AgentBridge Protocol Rules") ||
|
|
45
|
+
mdc.includes("AgentBridge protocol rules")) {
|
|
46
|
+
return "protocol";
|
|
47
|
+
}
|
|
48
|
+
return "unknown";
|
|
49
|
+
}
|
|
50
|
+
function readOptionalFile(path) {
|
|
51
|
+
if (!(0, node_fs_1.existsSync)(path))
|
|
52
|
+
return null;
|
|
53
|
+
return (0, node_fs_1.readFileSync)(path, "utf8");
|
|
54
|
+
}
|
|
55
|
+
function analyzeRulesArtifacts(workspaceRoot, expectedProjectId) {
|
|
56
|
+
const paths = rulesArtifactPaths(workspaceRoot);
|
|
57
|
+
const markdownContent = readOptionalFile(paths.rulesMarkdownPath);
|
|
58
|
+
const mdcContent = readOptionalFile(paths.rulesMdcPath);
|
|
59
|
+
const filesPresent = markdownContent !== null && mdcContent !== null;
|
|
60
|
+
const format = classifyRulesFormat(markdownContent, mdcContent);
|
|
61
|
+
const projectIds = new Set();
|
|
62
|
+
if (markdownContent) {
|
|
63
|
+
const id = extractProjectIdFromRulesText(markdownContent);
|
|
64
|
+
if (id)
|
|
65
|
+
projectIds.add(id);
|
|
66
|
+
}
|
|
67
|
+
if (mdcContent) {
|
|
68
|
+
const id = extractProjectIdFromRulesText(mdcContent);
|
|
69
|
+
if (id)
|
|
70
|
+
projectIds.add(id);
|
|
71
|
+
}
|
|
72
|
+
const issues = [];
|
|
73
|
+
let projectIdInRepo = null;
|
|
74
|
+
if (projectIds.size === 1) {
|
|
75
|
+
projectIdInRepo = [...projectIds][0] ?? null;
|
|
76
|
+
}
|
|
77
|
+
else if (projectIds.size > 1) {
|
|
78
|
+
issues.push("AGENTBRIDGE.md and .cursor/rules/agentbridge.mdc reference different project IDs.");
|
|
79
|
+
projectIdInRepo = [...projectIds][0] ?? null;
|
|
80
|
+
}
|
|
81
|
+
else if (filesPresent) {
|
|
82
|
+
issues.push("Rules files are present but no project ID could be parsed.");
|
|
83
|
+
}
|
|
84
|
+
let projectIdMatchesConfig = null;
|
|
85
|
+
if (expectedProjectId) {
|
|
86
|
+
if (projectIdInRepo) {
|
|
87
|
+
projectIdMatchesConfig = projectIdInRepo === expectedProjectId;
|
|
88
|
+
if (!projectIdMatchesConfig) {
|
|
89
|
+
issues.push(`Rules reference project ${projectIdInRepo}, but configured project is ${expectedProjectId}.`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else if (filesPresent) {
|
|
93
|
+
projectIdMatchesConfig = false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (!filesPresent) {
|
|
97
|
+
issues.push("Missing AGENTBRIDGE.md or .cursor/rules/agentbridge.mdc.");
|
|
98
|
+
}
|
|
99
|
+
else if (format === "local") {
|
|
100
|
+
issues.push("Rules are in local watch format (from agentbridge init), not server protocol rules.");
|
|
101
|
+
}
|
|
102
|
+
else if (format === "unknown") {
|
|
103
|
+
issues.push("Rules files exist but do not match the expected AgentBridge protocol format.");
|
|
104
|
+
}
|
|
105
|
+
const protocolRulesValid = filesPresent &&
|
|
106
|
+
format === "protocol" &&
|
|
107
|
+
(projectIdMatchesConfig ?? true) &&
|
|
108
|
+
issues.length === 0;
|
|
109
|
+
return {
|
|
110
|
+
paths,
|
|
111
|
+
filesPresent,
|
|
112
|
+
format,
|
|
113
|
+
projectIdInRepo,
|
|
114
|
+
projectIdMatchesConfig,
|
|
115
|
+
protocolRulesValid,
|
|
116
|
+
issues,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
async function fetchProjectRules(ctx) {
|
|
120
|
+
return (0, http_1.getJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/rules`);
|
|
121
|
+
}
|
|
122
|
+
function writeRulesFiles(workspaceRoot, files) {
|
|
123
|
+
for (const file of files) {
|
|
124
|
+
const fullPath = (0, node_path_1.resolve)(workspaceRoot, file.path);
|
|
125
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(fullPath), { recursive: true });
|
|
126
|
+
(0, node_fs_1.writeFileSync)(fullPath, file.content, "utf8");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async function markProjectRulesInstalled(ctx) {
|
|
130
|
+
return (0, http_1.postJson)(ctx, `/v1/dev/projects/${encodeURIComponent(ctx.projectId)}/rules/mark-installed`, {});
|
|
131
|
+
}
|
|
132
|
+
function extractProjectNameFromRulesResponse(files) {
|
|
133
|
+
const markdown = files.find((file) => file.path === "AGENTBRIDGE.md")?.content;
|
|
134
|
+
if (!markdown)
|
|
135
|
+
return null;
|
|
136
|
+
const match = markdown.match(/^# AgentBridge — (.+)$/m);
|
|
137
|
+
return match?.[1]?.trim() ?? null;
|
|
138
|
+
}
|