@brainmcp/brainmcp 0.1.12 → 0.1.14

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.
Files changed (108) hide show
  1. package/README.md +79 -5
  2. package/dist/agent-config.js +155 -24
  3. package/dist/commands/auth.d.ts +47 -0
  4. package/dist/commands/auth.js +392 -0
  5. package/dist/commands/auth.js.map +1 -0
  6. package/dist/commands/capture.d.ts +34 -0
  7. package/dist/commands/capture.js +349 -0
  8. package/dist/commands/capture.js.map +1 -0
  9. package/dist/commands/dispatch.d.ts +43 -0
  10. package/dist/commands/dispatch.js +287 -0
  11. package/dist/commands/dispatch.js.map +1 -0
  12. package/dist/commands/index.d.ts +4 -0
  13. package/dist/commands/index.js +198 -36
  14. package/dist/commands/index.js.map +1 -1
  15. package/dist/hooks/adapters.d.ts +14 -0
  16. package/dist/hooks/adapters.js +41 -0
  17. package/dist/hooks/adapters.js.map +1 -0
  18. package/dist/hooks/claude.d.ts +24 -0
  19. package/dist/hooks/claude.js +36 -0
  20. package/dist/hooks/claude.js.map +1 -0
  21. package/dist/hooks/codex.d.ts +18 -0
  22. package/dist/hooks/codex.js +36 -0
  23. package/dist/hooks/codex.js.map +1 -0
  24. package/dist/hooks/cursor.d.ts +22 -0
  25. package/dist/hooks/cursor.js +26 -0
  26. package/dist/hooks/cursor.js.map +1 -0
  27. package/dist/hooks/managed-hooks.d.ts +30 -0
  28. package/dist/hooks/managed-hooks.js +189 -0
  29. package/dist/hooks/managed-hooks.js.map +1 -0
  30. package/dist/hooks/vscode.d.ts +22 -0
  31. package/dist/hooks/vscode.js +27 -0
  32. package/dist/hooks/vscode.js.map +1 -0
  33. package/dist/index.js +7 -102
  34. package/dist/index.js.map +1 -1
  35. package/dist/lib/api-client.d.ts +32 -0
  36. package/dist/lib/api-client.js +207 -0
  37. package/dist/lib/api-client.js.map +1 -0
  38. package/dist/lib/callback-server.d.ts +17 -0
  39. package/dist/lib/callback-server.js +145 -0
  40. package/dist/lib/callback-server.js.map +1 -0
  41. package/dist/lib/cli-contracts.d.ts +193 -0
  42. package/dist/lib/cli-contracts.js +242 -0
  43. package/dist/lib/cli-contracts.js.map +1 -0
  44. package/dist/lib/config.d.ts +2 -0
  45. package/dist/lib/config.js +11 -0
  46. package/dist/lib/config.js.map +1 -1
  47. package/dist/lib/credentials.d.ts +89 -0
  48. package/dist/lib/credentials.js +304 -0
  49. package/dist/lib/credentials.js.map +1 -0
  50. package/dist/lib/endpoints.d.ts +5 -0
  51. package/dist/lib/endpoints.js +31 -0
  52. package/dist/lib/endpoints.js.map +1 -0
  53. package/dist/lib/http.d.ts +3 -0
  54. package/dist/lib/http.js +51 -0
  55. package/dist/lib/http.js.map +1 -0
  56. package/dist/lib/managed.d.ts +8 -0
  57. package/dist/lib/managed.js +72 -6
  58. package/dist/lib/managed.js.map +1 -1
  59. package/dist/lib/oauth-client.d.ts +55 -0
  60. package/dist/lib/oauth-client.js +155 -0
  61. package/dist/lib/oauth-client.js.map +1 -0
  62. package/dist/lib/pkce.d.ts +14 -0
  63. package/dist/lib/pkce.js +33 -0
  64. package/dist/lib/pkce.js.map +1 -0
  65. package/dist/lib/vault.d.ts +26 -0
  66. package/dist/lib/vault.js +221 -0
  67. package/dist/lib/vault.js.map +1 -0
  68. package/dist/runtime/brainmcp-hook-runtime.mjs +3243 -0
  69. package/dist/runtime/capture.d.ts +83 -0
  70. package/dist/runtime/capture.js +332 -0
  71. package/dist/runtime/capture.js.map +1 -0
  72. package/dist/runtime/diagnostics.d.ts +7 -0
  73. package/dist/runtime/diagnostics.js +75 -0
  74. package/dist/runtime/diagnostics.js.map +1 -0
  75. package/dist/runtime/doctor-checks.d.ts +15 -0
  76. package/dist/runtime/doctor-checks.js +233 -0
  77. package/dist/runtime/doctor-checks.js.map +1 -0
  78. package/dist/runtime/flush.d.ts +30 -0
  79. package/dist/runtime/flush.js +193 -0
  80. package/dist/runtime/flush.js.map +1 -0
  81. package/dist/runtime/hook-dispatcher.d.ts +31 -0
  82. package/dist/runtime/hook-dispatcher.js +381 -0
  83. package/dist/runtime/hook-dispatcher.js.map +1 -0
  84. package/dist/runtime/index.d.ts +12 -0
  85. package/dist/runtime/index.js +69 -0
  86. package/dist/runtime/index.js.map +1 -0
  87. package/dist/runtime/installer.d.ts +26 -0
  88. package/dist/runtime/installer.js +134 -0
  89. package/dist/runtime/installer.js.map +1 -0
  90. package/dist/runtime/lock.d.ts +19 -0
  91. package/dist/runtime/lock.js +116 -0
  92. package/dist/runtime/lock.js.map +1 -0
  93. package/dist/runtime/orient.d.ts +55 -0
  94. package/dist/runtime/orient.js +256 -0
  95. package/dist/runtime/orient.js.map +1 -0
  96. package/dist/runtime/queue.d.ts +7 -0
  97. package/dist/runtime/queue.js +80 -0
  98. package/dist/runtime/queue.js.map +1 -0
  99. package/dist/runtime/retention.d.ts +26 -0
  100. package/dist/runtime/retention.js +175 -0
  101. package/dist/runtime/retention.js.map +1 -0
  102. package/dist/runtime/session.d.ts +68 -0
  103. package/dist/runtime/session.js +283 -0
  104. package/dist/runtime/session.js.map +1 -0
  105. package/dist/runtime/storage.d.ts +32 -0
  106. package/dist/runtime/storage.js +236 -0
  107. package/dist/runtime/storage.js.map +1 -0
  108. package/package.json +3 -2
@@ -0,0 +1,287 @@
1
+ import { cwd, stdin, stdout } from 'node:process';
2
+ import { createInterface } from 'node:readline/promises';
3
+ import { BRAINMCP_CLI_PACKAGE_SPEC, BRAINMCP_GLOBAL_CLIENTS } from '../agent-config.js';
4
+ import { CLI_EXIT_CODES } from '../lib/cli-contracts.js';
5
+ import { assertClientId, CLIENT_IDS, clientLabel } from '../lib/config.js';
6
+ import { doctor, init, initAll, printConfig, remove, } from './index.js';
7
+ import { login, logout, whoami } from './auth.js';
8
+ import { runHookRuntimeCli } from '../runtime/index.js';
9
+ import { captureStatus, captureEnable, captureDisable, captureNow, capturePurgeLocal, capturePurgePreview, } from './capture.js';
10
+ export const HELP = `brainmcp — configure BrainMCP for agent projects and authenticate companion CLI
11
+
12
+ Recommended setup (supported agents, all projects):
13
+ npx --yes ${BRAINMCP_CLI_PACKAGE_SPEC} init --all --scope user
14
+
15
+ Usage:
16
+ brainmcp init [--client <client>] [--workspace <id>] [--scope project|user] [--no-hooks] [--write-back-nudge] [--yes]
17
+ brainmcp init --all [--workspace <id>] [--scope project|user] [--no-hooks] [--write-back-nudge] [--yes]
18
+ brainmcp doctor [--client <client>] [--workspace <id>] [--scope project|user]
19
+ brainmcp print [--client <client>] [--workspace <id>] [--scope project|user]
20
+ brainmcp remove [--client <client>] [--scope project|user] [--yes]
21
+ brainmcp login [--capture-digests] [--allow-file-credentials] [--service-token-stdin] [--store-service-token] [--force] [--json]
22
+ brainmcp logout [--json]
23
+ brainmcp whoami [--json]
24
+ brainmcp hook <client> <event>
25
+ brainmcp capture [enable|disable|status|now|purge-local] [--dry-run] [--workspace <id>] [--include-prompts] [--yes] [--json]
26
+
27
+ Clients: ${CLIENT_IDS.join(', ')}
28
+ --all configures ${BRAINMCP_GLOBAL_CLIENTS.map(clientLabel).join(', ')} (generic is client-specific).
29
+ Project scope is the default. User scope writes to each host's supported per-user locations.
30
+ Every mutation previews a diff and needs confirmation unless a trailing CLI --yes is supplied.
31
+ The leading npx --yes above accepts npm's package download; it does not skip the file preview.`;
32
+ export function parseCliArgs(args) {
33
+ const [command, ...rawOptions] = args;
34
+ const result = {
35
+ command,
36
+ yes: false,
37
+ all: false,
38
+ captureDigests: false,
39
+ force: false,
40
+ json: false,
41
+ noHooks: false,
42
+ writeBackNudge: false,
43
+ enable: false,
44
+ disable: false,
45
+ status: false,
46
+ dryRun: false,
47
+ purge: false,
48
+ includePrompts: false,
49
+ now: false,
50
+ allowFileCredentials: false,
51
+ serviceTokenStdin: false,
52
+ storeServiceToken: false,
53
+ };
54
+ for (let index = 0; index < rawOptions.length; index += 1) {
55
+ const option = rawOptions[index];
56
+ if (option === '--yes' || option === '-y')
57
+ result.yes = true;
58
+ else if (option === '--all')
59
+ result.all = true;
60
+ else if (option === '--capture-digests')
61
+ result.captureDigests = true;
62
+ else if (option === '--force')
63
+ result.force = true;
64
+ else if (option === '--json')
65
+ result.json = true;
66
+ else if (option === '--enable' || option === 'enable')
67
+ result.enable = true;
68
+ else if (option === '--disable' || option === 'disable')
69
+ result.disable = true;
70
+ else if (option === '--status' || option === 'status')
71
+ result.status = true;
72
+ else if (option === '--dry-run')
73
+ result.dryRun = true;
74
+ else if (option === '--purge' || option === 'purge-local')
75
+ result.purge = true;
76
+ else if (option === '--include-prompts')
77
+ result.includePrompts = true;
78
+ else if (option === '--now' || option === 'now')
79
+ result.now = true;
80
+ else if (option === '--allow-file-credentials')
81
+ result.allowFileCredentials = true;
82
+ else if (option === '--service-token-stdin')
83
+ result.serviceTokenStdin = true;
84
+ else if (option === '--store-service-token')
85
+ result.storeServiceToken = true;
86
+ else if (option === '--no-hooks')
87
+ result.noHooks = true;
88
+ else if (option === '--write-back-nudge')
89
+ result.writeBackNudge = true;
90
+ else if (option === '--client')
91
+ result.client = assertClientId(readValue(rawOptions, ++index, option));
92
+ else if (option === '--workspace')
93
+ result.workspace = readValue(rawOptions, ++index, option);
94
+ else if (option === '--scope') {
95
+ const scope = readValue(rawOptions, ++index, option);
96
+ if (scope !== 'project' && scope !== 'user')
97
+ throw new Error('--scope must be project or user.');
98
+ result.scope = scope;
99
+ }
100
+ else if (option === '--root')
101
+ result.root = readValue(rawOptions, ++index, option);
102
+ else
103
+ throw new Error(`Unknown option: ${option}`);
104
+ }
105
+ if (command === 'capture') {
106
+ const actions = [result.enable, result.disable, result.status, result.now, result.purge].filter(Boolean);
107
+ if (actions.length > 1) {
108
+ throw new Error('Choose exactly one capture action: enable, disable, status, now, or purge-local.');
109
+ }
110
+ const effectiveNow = result.now || (actions.length === 0 && result.dryRun);
111
+ if (result.dryRun && !effectiveNow) {
112
+ throw new Error('--dry-run is valid only with `capture now`.');
113
+ }
114
+ if (result.includePrompts && !result.enable) {
115
+ throw new Error('--include-prompts is valid only with `capture enable`.');
116
+ }
117
+ if (result.workspace && !result.enable && !effectiveNow) {
118
+ throw new Error('--workspace is valid only with `capture enable` or `capture now`.');
119
+ }
120
+ }
121
+ return result;
122
+ }
123
+ function readValue(args, index, option) {
124
+ const value = args[index];
125
+ if (!value || value.startsWith('-'))
126
+ throw new Error(`${option} requires a value.`);
127
+ return value;
128
+ }
129
+ export async function defaultConfirm(message) {
130
+ if (!stdin.isTTY || !stdout.isTTY)
131
+ return false;
132
+ const readline = createInterface({ input: stdin, output: stdout });
133
+ try {
134
+ return (await readline.question(`${message} [y/N] `)).trim().toLowerCase() === 'y';
135
+ }
136
+ finally {
137
+ readline.close();
138
+ }
139
+ }
140
+ export async function dispatch(rawArgs, context = {}) {
141
+ const output = context.output ?? console.log;
142
+ const confirmFn = context.confirm ?? defaultConfirm;
143
+ const currentRoot = context.cwd ?? cwd();
144
+ const [command] = rawArgs;
145
+ if (!command || command === '--help' || command === '-h' || command === 'help') {
146
+ output(HELP);
147
+ return CLI_EXIT_CODES.SUCCESS;
148
+ }
149
+ if (command === 'hook') {
150
+ await runHookRuntimeCli(rawArgs.slice(1));
151
+ return CLI_EXIT_CODES.SUCCESS;
152
+ }
153
+ let parsed;
154
+ try {
155
+ parsed = parseCliArgs(rawArgs);
156
+ }
157
+ catch (err) {
158
+ console.error(err instanceof Error ? err.message : String(err));
159
+ return CLI_EXIT_CODES.USAGE_ERROR;
160
+ }
161
+ if (parsed.all && parsed.client) {
162
+ console.error('Use either --all or --client, not both.');
163
+ return CLI_EXIT_CODES.USAGE_ERROR;
164
+ }
165
+ if (parsed.all && parsed.command !== 'init') {
166
+ console.error('--all is currently supported only by init.');
167
+ return CLI_EXIT_CODES.USAGE_ERROR;
168
+ }
169
+ const legacyOptions = {
170
+ root: parsed.root ?? currentRoot,
171
+ client: parsed.client,
172
+ workspace: parsed.workspace,
173
+ scope: parsed.scope ?? 'project',
174
+ yes: parsed.yes,
175
+ noHooks: parsed.noHooks,
176
+ writeBackNudge: parsed.writeBackNudge,
177
+ output,
178
+ confirm: confirmFn,
179
+ };
180
+ try {
181
+ switch (parsed.command) {
182
+ case 'init':
183
+ await (parsed.all ? initAll(legacyOptions) : init(legacyOptions));
184
+ return CLI_EXIT_CODES.SUCCESS;
185
+ case 'doctor':
186
+ await doctor(legacyOptions);
187
+ return CLI_EXIT_CODES.SUCCESS;
188
+ case 'print':
189
+ await printConfig(legacyOptions);
190
+ return CLI_EXIT_CODES.SUCCESS;
191
+ case 'remove':
192
+ await remove(legacyOptions);
193
+ return CLI_EXIT_CODES.SUCCESS;
194
+ case 'login':
195
+ if (parsed.serviceTokenStdin && parsed.captureDigests) {
196
+ console.error('Service credential login cannot be mixed with --capture-digests.');
197
+ return CLI_EXIT_CODES.USAGE_ERROR;
198
+ }
199
+ await login({
200
+ captureDigests: parsed.captureDigests,
201
+ force: parsed.force,
202
+ json: parsed.json,
203
+ allowFileCredentials: parsed.allowFileCredentials,
204
+ serviceTokenStdin: parsed.serviceTokenStdin,
205
+ storeServiceToken: parsed.storeServiceToken,
206
+ output,
207
+ openBrowser: context.openBrowser,
208
+ store: context.store,
209
+ oauthClient: context.oauthClient,
210
+ });
211
+ return CLI_EXIT_CODES.SUCCESS;
212
+ case 'logout':
213
+ await logout({
214
+ json: parsed.json,
215
+ output,
216
+ store: context.store,
217
+ oauthClient: context.oauthClient,
218
+ });
219
+ return CLI_EXIT_CODES.SUCCESS;
220
+ case 'whoami':
221
+ await whoami({
222
+ json: parsed.json,
223
+ output,
224
+ store: context.store,
225
+ apiClient: context.apiClient,
226
+ });
227
+ return CLI_EXIT_CODES.SUCCESS;
228
+ case 'capture': {
229
+ let result;
230
+ if (parsed.enable) {
231
+ result = await captureEnable({
232
+ baseDir: context.baseDir,
233
+ includePrompts: parsed.includePrompts,
234
+ workspace: parsed.workspace,
235
+ apiClient: context.apiClient,
236
+ store: context.store,
237
+ });
238
+ }
239
+ else if (parsed.disable) {
240
+ result = captureDisable({ baseDir: context.baseDir });
241
+ }
242
+ else if (parsed.purge) {
243
+ const preview = capturePurgePreview({ baseDir: context.baseDir });
244
+ const previewText = `${preview.queueItems} queue item(s), ${preview.sessionLedgers} session ledger(s), and ${preview.orientationCacheFiles} orientation cache file(s)`;
245
+ const confirmed = parsed.yes || await confirmFn(`Delete ${previewText}? Credentials, preferences, and host configuration will be preserved.`);
246
+ result = confirmed
247
+ ? capturePurgeLocal({ baseDir: context.baseDir })
248
+ : {
249
+ exitCode: CLI_EXIT_CODES.SUCCESS,
250
+ data: { purged: false, preview, message: 'Local capture purge cancelled; no files were deleted.' },
251
+ };
252
+ }
253
+ else if (parsed.now || parsed.dryRun) {
254
+ result = await captureNow({
255
+ dryRun: parsed.dryRun,
256
+ baseDir: context.baseDir,
257
+ cwd: currentRoot,
258
+ workspace: parsed.workspace,
259
+ apiClient: context.apiClient,
260
+ store: context.store,
261
+ });
262
+ }
263
+ else {
264
+ result = captureStatus({ baseDir: context.baseDir, json: parsed.json });
265
+ }
266
+ if (parsed.json) {
267
+ output(JSON.stringify(result.data, null, 2));
268
+ }
269
+ else if (typeof result.data === 'object' && result.data !== null && 'message' in result.data) {
270
+ output(String(result.data.message));
271
+ }
272
+ else {
273
+ output(JSON.stringify(result.data, null, 2));
274
+ }
275
+ return result.exitCode;
276
+ }
277
+ default:
278
+ console.error(`Unknown command: ${parsed.command}\n\n${HELP}`);
279
+ return CLI_EXIT_CODES.USAGE_ERROR;
280
+ }
281
+ }
282
+ catch (error) {
283
+ console.error(error instanceof Error ? error.message : String(error));
284
+ return CLI_EXIT_CODES.GENERAL_ERROR;
285
+ }
286
+ }
287
+ //# sourceMappingURL=dispatch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatch.js","sourceRoot":"","sources":["../../src/commands/dispatch.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,8 @@ export type CommandOptions = {
5
5
  workspace?: string;
6
6
  scope: Scope;
7
7
  yes: boolean;
8
+ noHooks?: boolean;
9
+ writeBackNudge?: boolean;
8
10
  /** Test/embedding override. The CLI defaults to `os.homedir()`. */
9
11
  home?: string;
10
12
  /** Test/embedding override. The CLI defaults to `process.env`. */
@@ -22,3 +24,5 @@ export declare function initAll(options: CommandOptions): Promise<void>;
22
24
  export declare function remove(options: CommandOptions): Promise<void>;
23
25
  export declare function printConfig(options: CommandOptions): Promise<void>;
24
26
  export declare function doctor(options: CommandOptions): Promise<void>;
27
+ export * from './auth.js';
28
+ export * from './dispatch.js';
@@ -1,9 +1,14 @@
1
1
  import { homedir } from 'node:os';
2
2
  import { join, resolve } from 'node:path';
3
- import { BRAIN_BOOTSTRAP_PROMPT, BRAIN_DOCS_URL, BRAIN_MCP_ENDPOINT, BRAIN_MCP_INITIAL_SCOPES, BRAIN_OAUTH_AUTH_METADATA_CANONICAL_URL, BRAIN_OAUTH_AUTH_METADATA_URL, BRAIN_OAUTH_ISSUER, BRAIN_OAUTH_RESOURCE_METADATA_COMPAT_URL, BRAIN_OAUTH_RESOURCE_METADATA_URL, BRAINMCP_CLI_CLIENTS, BRAINMCP_GLOBAL_CLIENTS, MCP_OAUTH_CLIENT_IDS, buildBrainmcpDoctorCommand, } from '../agent-config.js';
3
+ import { BRAIN_BOOTSTRAP_PROMPT, BRAIN_DOCS_URL, BRAIN_MCP_ENDPOINT, BRAIN_MCP_INITIAL_SCOPES, BRAIN_OAUTH_AUTH_METADATA_CANONICAL_URL, BRAIN_OAUTH_AUTH_METADATA_URL, BRAIN_OAUTH_ISSUER, BRAIN_OAUTH_RESOURCE_METADATA_COMPAT_URL, BRAIN_OAUTH_RESOURCE_METADATA_URL, BRAINMCP_CLI_CLIENTS, BRAINMCP_GLOBAL_CLIENTS, MCP_OAUTH_CLIENT_IDS, buildBrainmcpDoctorCommand, buildBrainmcpInitCommand, buildBrainVerificationPrompt, GUIDANCE_VERSION, } from '../agent-config.js';
4
4
  import { clientLabel, guidancePreamble, renderGuidance, resolveWorkspace, targetFor, describeAntigravityServerConfig, } from '../lib/config.js';
5
5
  import { applyChanges, exists, readText, renderDiff, toAbsolute } from '../lib/files.js';
6
- import { parseJsonc, removeJsonServer, removeMarkdownBlock, removeTomlBlock, upsertJsonServer, upsertMarkdownBlock, upsertTomlBlock } from '../lib/managed.js';
6
+ import { parseJsonc, removeJsoncPath, removeJsonServer, removeMarkdownBlock, removeTomlBlock, upsertJsoncPath, upsertJsonServer, upsertMarkdownBlock, upsertTomlBlock } from '../lib/managed.js';
7
+ import { checkCredentialHealth, checkRuntimeDrift, checkConsentPreferences, checkQueueStatus, checkCompanionApiTarget } from '../runtime/doctor-checks.js';
8
+ import { companionWorkspacesUrl } from '../lib/endpoints.js';
9
+ import { getBrainHome } from '../runtime/storage.js';
10
+ import { cliPackageVersion, getVersionedRuntimePath, loadPackagedRuntimeBundle, } from '../runtime/installer.js';
11
+ import { removeClaudeManagedHooks, removeCodexManagedHooks, removeCursorManagedHooks, removeVsCodeManagedHooks, upsertClaudeManagedHooks, upsertCodexManagedHooks, upsertCursorManagedHooks, upsertVsCodeManagedHooks, } from '../hooks/managed-hooks.js';
7
12
  export async function detectClients(root) {
8
13
  const candidates = [
9
14
  ['cursor', ['.cursor', '.cursor/mcp.json']],
@@ -32,6 +37,7 @@ export async function chooseClient(options) {
32
37
  }
33
38
  export async function init(options) {
34
39
  const client = await chooseClient(options);
40
+ await assertSingleMcpConfigScope(options, [client]);
35
41
  const workspace = resolveWorkspace(options.workspace);
36
42
  const changes = await plannedChanges(options, client, workspace, 'init');
37
43
  await previewAndApply(changes, options);
@@ -40,6 +46,7 @@ export async function init(options) {
40
46
  }
41
47
  export async function initAll(options) {
42
48
  const workspace = resolveWorkspace(options.workspace);
49
+ await assertSingleMcpConfigScope(options, BRAINMCP_GLOBAL_CLIENTS);
43
50
  const groups = await Promise.all(BRAINMCP_GLOBAL_CLIENTS.map((client) => plannedChanges(options, client, workspace, 'init')));
44
51
  await previewAndApply(mergeFileChanges(groups.flat()), options);
45
52
  options.output(`BrainMCP configured for ${BRAINMCP_GLOBAL_CLIENTS.map(clientLabel).join(', ')}.`);
@@ -74,7 +81,9 @@ function renderSetupNextSteps(clients, scope, workspace) {
74
81
  '3. Log in to brain from every client you plan to use:',
75
82
  ...authorizationSteps,
76
83
  '4. In browser consent, review the scopes, select the workspace(s) this client may reach, and choose its default workspace.',
77
- '5. In the project, ask: "Use brain to give me an overview of this workspace." Confirm the dashboard Connect view shows Agent seen.',
84
+ `5. Verify with a read-only agent prompt (guidance ${GUIDANCE_VERSION}):`,
85
+ ` ${buildBrainVerificationPrompt({ workspaceId: workspace })}`,
86
+ ' Confirm the dashboard Connect view shows Agent seen after a successful authenticated tool call.',
78
87
  '6. If the workspace is empty, copy and paste this bootstrap command into the connected agent:',
79
88
  ...BRAIN_BOOTSTRAP_PROMPT.split('\n').map((line) => ` ${line}`),
80
89
  '7. Follow the proposal status the agent reports. If it awaits Review, approve, edit, or reject it in the dashboard. The workspace is not populated until the proposal is applied.',
@@ -136,7 +145,20 @@ export async function doctor(options) {
136
145
  }
137
146
  if (guidance !== undefined) {
138
147
  const occurrences = (guidance.match(/brainmcp:managed:start/g) ?? []).length;
139
- checks.push(['managed guidance markers', occurrences === 1, occurrences === 1 ? 'one managed block' : `${occurrences} managed blocks`]);
148
+ const normalizedGuidance = guidance.replace(/\r\n/g, '\n');
149
+ const ends = (guidance.match(/brainmcp:managed:end/g) ?? []).length;
150
+ const blocks = normalizedGuidance.match(/<!-- brainmcp:managed:start[^\n]*-->[\s\S]*?<!-- brainmcp:managed:end -->/g) ?? [];
151
+ const validMarkers = occurrences === 1 && ends === 1 && blocks.length === 1;
152
+ checks.push(['managed guidance markers', validMarkers, validMarkers ? 'one complete managed block' : `${occurrences} starts, ${ends} ends, ${blocks.length} complete blocks`]);
153
+ const installedWorkspace = blocks[0]?.match(/^Workspace id: (.+)$/m)?.[1]?.trim();
154
+ const expectedWorkspace = resolveWorkspace(options.workspace) ?? installedWorkspace;
155
+ const expectedGuidance = renderGuidance(client, expectedWorkspace).trim();
156
+ const currentGuidance = validMarkers && blocks[0] === expectedGuidance;
157
+ checks.push([
158
+ 'managed guidance content', currentGuidance,
159
+ currentGuidance ? `matches bundled guidance ${GUIDANCE_VERSION}` :
160
+ `outdated or modified; preview repair with ${buildBrainmcpInitCommand({ client, scope: options.scope, workspaceId: expectedWorkspace })}`,
161
+ ]);
140
162
  checks.push(['workspace binding', !options.workspace || guidance.includes(`Workspace id: ${resolveWorkspace(options.workspace)}`), options.workspace ? 'requested workspace binding' : 'no workspace requested']);
141
163
  if ((client === 'vscode' || client === 'copilot') && options.scope === 'user') {
142
164
  checks.push([
@@ -157,38 +179,18 @@ export async function doctor(options) {
157
179
  ]);
158
180
  }
159
181
  }
160
- if (client === 'cursor' || client === 'vscode' || client === 'copilot' || client === 'antigravity') {
161
- const otherScope = options.scope === 'project' ? 'user' : 'project';
162
- const otherTarget = resolveTarget({ ...options, scope: otherScope }, targetFor(client, otherScope));
163
- if (otherTarget.configPath !== target.configPath) {
164
- const otherConfig = await readText(otherTarget.configPath);
165
- if (otherConfig === undefined) {
166
- checks.push([
167
- 'single MCP configuration scope',
168
- true,
169
- `no ${otherScope}-scope brain entry`,
170
- ]);
171
- }
172
- else {
173
- try {
174
- const duplicated = hasBrainServerConfig(otherConfig, otherTarget);
175
- checks.push([
176
- 'single MCP configuration scope',
177
- !duplicated,
178
- duplicated
179
- ? `brain is also configured at ${otherTarget.configPath}; keep it in only one scope`
180
- : `no ${otherScope}-scope brain entry`,
181
- ]);
182
- }
183
- catch (error) {
184
- checks.push([
185
- 'single MCP configuration scope',
186
- false,
187
- `cannot inspect ${otherTarget.configPath}: ${error instanceof Error ? error.message : String(error)}`,
188
- ]);
189
- }
190
- }
191
- }
182
+ if (requiresSingleMcpConfigScope(client)) {
183
+ const otherScope = oppositeScope(options.scope);
184
+ const duplicate = await inspectOtherMcpConfigScope(options, client);
185
+ checks.push([
186
+ 'single MCP configuration scope',
187
+ duplicate.status === 'absent',
188
+ duplicate.status === 'present'
189
+ ? `brain is also configured at ${duplicate.path}; keep it in only one scope`
190
+ : duplicate.status === 'invalid'
191
+ ? `cannot inspect ${duplicate.path}: ${duplicate.error}`
192
+ : `no ${otherScope}-scope brain entry`,
193
+ ]);
192
194
  }
193
195
  const fetchImpl = options.fetch ?? fetch;
194
196
  for (const [name, url] of [
@@ -253,11 +255,90 @@ export async function doctor(options) {
253
255
  catch (error) {
254
256
  checks.push(['endpoint challenge', false, error instanceof Error ? error.message : String(error)]);
255
257
  }
258
+ const apiOriginCheck = checkCompanionApiTarget();
259
+ checks.push([apiOriginCheck.name, apiOriginCheck.passed, apiOriginCheck.detail]);
260
+ try {
261
+ const workspacesUrl = companionWorkspacesUrl();
262
+ const workspaces = await fetchImpl(workspacesUrl, { headers: { accept: 'application/json' } });
263
+ const valid = workspaces.status === 401 || workspaces.status === 403;
264
+ checks.push([
265
+ 'companion workspaces endpoint',
266
+ valid,
267
+ valid
268
+ ? `HTTP ${workspaces.status} at ${workspacesUrl}`
269
+ : `HTTP ${workspaces.status} at ${workspacesUrl}; companion REST belongs on the API origin, not MCP`,
270
+ ]);
271
+ }
272
+ catch (error) {
273
+ checks.push(['companion workspaces endpoint', false, error instanceof Error ? error.message : String(error)]);
274
+ }
275
+ const brainHome = options.home ? join(options.home, '.brainmcp') : getBrainHome();
276
+ const credCheck = await checkCredentialHealth(brainHome, { probeVault: !options.home });
277
+ checks.push([credCheck.name, credCheck.passed, credCheck.detail]);
278
+ const driftCheck = checkRuntimeDrift(cliPackageVersion(), brainHome);
279
+ checks.push([driftCheck.name, driftCheck.passed, driftCheck.detail]);
280
+ const consentCheck = checkConsentPreferences(brainHome);
281
+ checks.push([consentCheck.name, consentCheck.passed, consentCheck.detail]);
282
+ const queueCheck = checkQueueStatus(brainHome);
283
+ checks.push([queueCheck.name, queueCheck.passed, queueCheck.detail]);
256
284
  for (const [name, passed, detail] of checks)
257
285
  options.output(`${passed ? '✓' : '✗'} ${name}: ${detail}`);
258
286
  if (checks.some(([, passed]) => !passed))
259
287
  throw new Error('BrainMCP doctor found configuration issues.');
260
288
  }
289
+ const SINGLE_SCOPE_MCP_CLIENTS = new Set([
290
+ 'antigravity',
291
+ 'codex',
292
+ 'copilot',
293
+ 'cursor',
294
+ 'vscode',
295
+ ]);
296
+ function requiresSingleMcpConfigScope(client) {
297
+ return SINGLE_SCOPE_MCP_CLIENTS.has(client);
298
+ }
299
+ function oppositeScope(scope) {
300
+ return scope === 'project' ? 'user' : 'project';
301
+ }
302
+ async function inspectOtherMcpConfigScope(options, client) {
303
+ const otherScope = oppositeScope(options.scope);
304
+ const currentTarget = resolveTarget(options, targetFor(client, options.scope));
305
+ const otherTarget = resolveTarget({ ...options, scope: otherScope }, targetFor(client, otherScope));
306
+ if (otherTarget.configPath === currentTarget.configPath) {
307
+ return { path: otherTarget.configPath, status: 'absent' };
308
+ }
309
+ const otherConfig = await readText(otherTarget.configPath);
310
+ if (otherConfig === undefined) {
311
+ return { path: otherTarget.configPath, status: 'absent' };
312
+ }
313
+ try {
314
+ return {
315
+ path: otherTarget.configPath,
316
+ status: hasBrainServerConfig(otherConfig, otherTarget) ? 'present' : 'absent',
317
+ };
318
+ }
319
+ catch (error) {
320
+ return {
321
+ error: error instanceof Error ? error.message : String(error),
322
+ path: otherTarget.configPath,
323
+ status: 'invalid',
324
+ };
325
+ }
326
+ }
327
+ async function assertSingleMcpConfigScope(options, clients) {
328
+ for (const client of clients) {
329
+ if (!requiresSingleMcpConfigScope(client))
330
+ continue;
331
+ const duplicate = await inspectOtherMcpConfigScope(options, client);
332
+ if (duplicate.status === 'absent')
333
+ continue;
334
+ const otherScope = oppositeScope(options.scope);
335
+ if (duplicate.status === 'invalid') {
336
+ throw new Error(`Cannot inspect the existing ${clientLabel(client)} ${otherScope}-scope configuration at ${duplicate.path}: ${duplicate.error}`);
337
+ }
338
+ throw new Error(`Brain is already configured for ${clientLabel(client)} at ${duplicate.path} (${otherScope} scope). ` +
339
+ `Keep it in exactly one scope: run \`brainmcp remove --client ${client} --scope ${otherScope} --yes\`, then retry.`);
340
+ }
341
+ }
261
342
  function hasBrainServerConfig(config, target) {
262
343
  if (target.format === 'toml') {
263
344
  return /\[mcp_servers\.brain\]/.test(config);
@@ -411,6 +492,84 @@ async function plannedChanges(options, client, workspace, mode) {
411
492
  }
412
493
  }
413
494
  }
495
+ const hookChanges = await plannedHookChanges(options, client, target, mode);
496
+ changes.push(...hookChanges);
497
+ return changes;
498
+ }
499
+ async function plannedHookChanges(options, client, target, mode) {
500
+ if (!target.supportsHooks || !target.hookPath)
501
+ return [];
502
+ if (mode === 'init' && options.noHooks)
503
+ return [];
504
+ const bundle = loadPackagedRuntimeBundle();
505
+ if (mode === 'init' && !bundle) {
506
+ throw new Error('The packaged BrainMCP hook runtime is missing. Reinstall or rebuild the CLI before enabling hooks.');
507
+ }
508
+ const home = options.home ?? homedir();
509
+ const brainHome = options.home ? join(options.home, '.brainmcp') : getBrainHome();
510
+ const version = cliPackageVersion();
511
+ const runtimePath = getVersionedRuntimePath(version, brainHome);
512
+ const hookFilePath = target.hookPath.startsWith('/')
513
+ ? target.hookPath
514
+ : options.scope === 'user'
515
+ ? join(home, target.hookPath)
516
+ : toAbsolute(options.root, target.hookPath);
517
+ const changes = [];
518
+ if (mode === 'init' && bundle) {
519
+ const existingRuntime = await readText(runtimePath);
520
+ if (existingRuntime !== bundle) {
521
+ changes.push({ path: runtimePath, before: existingRuntime, after: bundle });
522
+ }
523
+ }
524
+ const existingHook = await readText(hookFilePath);
525
+ let parsed = {};
526
+ if (existingHook?.trim()) {
527
+ parsed = parseJsonc(existingHook);
528
+ }
529
+ const next = mode === 'init'
530
+ ? client === 'claude-code'
531
+ ? upsertClaudeManagedHooks(parsed, runtimePath, { enableNudge: Boolean(options.writeBackNudge) })
532
+ : client === 'cursor'
533
+ ? upsertCursorManagedHooks(parsed, runtimePath)
534
+ : client === 'codex'
535
+ ? upsertCodexManagedHooks(parsed, runtimePath)
536
+ : upsertVsCodeManagedHooks(parsed, runtimePath)
537
+ : client === 'claude-code'
538
+ ? removeClaudeManagedHooks(parsed)
539
+ : client === 'cursor'
540
+ ? removeCursorManagedHooks(parsed)
541
+ : client === 'codex'
542
+ ? removeCodexManagedHooks(parsed)
543
+ : removeVsCodeManagedHooks(parsed);
544
+ const eventNames = client === 'claude-code'
545
+ ? ['SessionStart', 'UserPromptSubmit', 'PostToolUse', 'PreCompact', 'SessionEnd', 'Stop']
546
+ : client === 'cursor'
547
+ ? ['sessionStart', 'beforeSubmitPrompt', 'postToolUse', 'afterAgentResponse', 'preCompact', 'sessionEnd']
548
+ : client === 'codex'
549
+ ? ['SessionStart', 'UserPromptSubmit', 'PostToolUse', 'PreCompact', 'SessionEnd']
550
+ : ['SessionStart', 'UserPromptSubmit', 'PostToolUse', 'PreCompact'];
551
+ const nextRoot = next && typeof next === 'object' && !Array.isArray(next)
552
+ ? next
553
+ : {};
554
+ const nextHooks = nextRoot.hooks && typeof nextRoot.hooks === 'object' && !Array.isArray(nextRoot.hooks)
555
+ ? nextRoot.hooks
556
+ : {};
557
+ let after = existingHook ?? '{}\n';
558
+ if (client === 'cursor' && mode === 'init') {
559
+ after = upsertJsoncPath(after, ['version'], 1);
560
+ }
561
+ for (const eventName of eventNames) {
562
+ const value = nextHooks[eventName];
563
+ after = value === undefined
564
+ ? removeJsoncPath(after, ['hooks', eventName])
565
+ : upsertJsoncPath(after, ['hooks', eventName], value);
566
+ }
567
+ const meaningfulRootKeys = Object.keys(nextRoot).filter((key) => key !== 'version');
568
+ if (mode === 'remove' && meaningfulRootKeys.length === 0)
569
+ after = undefined;
570
+ if (existingHook !== after) {
571
+ changes.push({ path: hookFilePath, before: existingHook, after });
572
+ }
414
573
  return changes;
415
574
  }
416
575
  function resolveTarget(options, target) {
@@ -422,6 +581,7 @@ function resolveTarget(options, target) {
422
581
  ...target,
423
582
  configPath: join(codexHome, 'config.toml'),
424
583
  guidancePath: join(codexHome, 'AGENTS.md'),
584
+ hookPath: join(codexHome, 'hooks.json'),
425
585
  format: target.configFormat,
426
586
  rootKey: target.configRootKey,
427
587
  };
@@ -500,4 +660,6 @@ async function previewAndApply(changes, options) {
500
660
  ? `Applied changes with atomic per-file writes. Backups: ${backups.join(', ')}`
501
661
  : 'Applied changes with atomic per-file writes.');
502
662
  }
663
+ export * from './auth.js';
664
+ export * from './dispatch.js';
503
665
  //# sourceMappingURL=index.js.map