@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.
@@ -1,6 +1,6 @@
1
1
  {
2
- "builtAt": "2026-06-06T12:05:27.957Z",
3
- "gitHead": "ec17ba8",
4
- "sourceLatestMtime": "2026-06-06T11:59:41.649Z",
5
- "sourceLatestFile": "src/commands/watch.ts"
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
  }
@@ -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,
@@ -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 rulesMdcPath = (0, node_path_1.resolve)(workspaceRoot, ".cursor", "rules", "agentbridge.mdc");
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
- rulesInstalled,
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: rulesInstalled && mcpConfigured ? "active" : "inactive",
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
- const packet = await (0, http_1.getJson)({ projectId, apiKey, apiBaseUrl, apiKeySource: "config" }, `/v1/dev/projects/${encodeURIComponent(projectId)}/packet`);
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
- const reason = extractHttpReason(error);
129
- if (reason === "not_project_member") {
130
- return {
131
- status: "failed",
132
- reason,
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
- if (reason === "project_not_found") {
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
- const messageReason = error instanceof Error ? normalizeReasonCandidate(error.message) : undefined;
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 executionSurfaceId = null;
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
- executionSurfaceId = hello.execution_surface?.id?.trim() || null;
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 startCapable = Boolean(executionSurfaceId);
213
- if (callerId) {
214
- return { status: "ok", identityModel, executionSurfaceId, startCapable };
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
- if (identities.length === 1) {
217
- return { status: "ok", identityModel, executionSurfaceId, startCapable };
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 && projectAccess.status === "ok",
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 `agent recover --force` for a full domain rebuild, or `agentbridge watch` for live supervision");
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
+ }
@@ -110,7 +110,7 @@ function renderBasicHint() {
110
110
  "",
111
111
  "Recovery: basic",
112
112
  "AgentBridge found only a generic project area.",
113
- "Run `agent recover --force` to rebuild the domain map.",
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 AGENTBRIDGE_DEBUG=1 and share output.",
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 ??
@@ -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: "Verify AGENTBRIDGE_API_KEY and rerun `agentbridge doctor`.",
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentbridge1/cli",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "CLI for AgentBridge — structured AI agent work sessions with domain authority and approval gates",
5
5
  "type": "commonjs",
6
6
  "bin": {