@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,27 @@
1
+ export function parseVsCodeEvent(raw, eventName = 'sessionStart') {
2
+ const input = (raw && typeof raw === 'object' ? raw : {});
3
+ return {
4
+ client: 'vscode',
5
+ event: eventName,
6
+ sessionKey: input.session_id || input.sessionId || 'default_session',
7
+ cwd: input.cwd || input.workspace,
8
+ source: input.source,
9
+ prompt: input.prompt,
10
+ toolName: input.tool_name,
11
+ toolInput: input.tool_input,
12
+ toolResult: input.tool_result ?? input.tool_response,
13
+ model: input.model,
14
+ };
15
+ }
16
+ export function emitVsCodeOutput(event, additionalContext) {
17
+ if ((event.event === 'SessionStart' || event.event === 'sessionStart') && additionalContext) {
18
+ return {
19
+ hookSpecificOutput: {
20
+ hookEventName: 'SessionStart',
21
+ additionalContext,
22
+ },
23
+ };
24
+ }
25
+ return {};
26
+ }
27
+ //# sourceMappingURL=vscode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vscode.js","sourceRoot":"","sources":["../../src/hooks/vscode.ts"],"names":[],"mappings":"AAuBA,MAAM,UAAU,gBAAgB,CAAC,GAAY,EAAE,SAAS,GAAG,cAAc;IACvE,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAoB,CAAC;IAC7E,OAAO;QACL,MAAM,EAAE,QAAQ;QAChB,KAAK,EAAE,SAAS;QAChB,UAAU,EAAE,KAAK,CAAC,UAAU,IAAI,KAAK,CAAC,SAAS,IAAI,iBAAiB;QACpE,GAAG,EAAE,KAAK,CAAC,GAAG,IAAI,KAAK,CAAC,SAAS;QACjC,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,QAAQ,EAAE,KAAK,CAAC,SAAS;QACzB,SAAS,EAAE,KAAK,CAAC,UAAU;QAC3B,UAAU,EAAE,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,aAAa;QACpD,KAAK,EAAE,KAAK,CAAC,KAAK;KACnB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAgB,EAAE,iBAA0B;IAC3E,IAAI,CAAC,KAAK,CAAC,KAAK,KAAK,cAAc,IAAI,KAAK,CAAC,KAAK,KAAK,cAAc,CAAC,IAAI,iBAAiB,EAAE,CAAC;QAC5F,OAAO;YACL,kBAAkB,EAAE;gBAClB,aAAa,EAAE,cAAc;gBAC7B,iBAAiB;aAClB;SACF,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,CAAC;AACZ,CAAC"}
package/dist/index.js CHANGED
@@ -1,106 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { argv, cwd, stdin, stdout } from 'node:process';
3
- import { createInterface } from 'node:readline/promises';
4
- import { BRAINMCP_CLI_PACKAGE_SPEC, BRAINMCP_GLOBAL_CLIENTS } from './agent-config.js';
5
- import { doctor, init, initAll, printConfig, remove } from './commands/index.js';
6
- import { assertClientId, CLIENT_IDS, clientLabel } from './lib/config.js';
7
- const HELP = `brainmcp — configure BrainMCP for agent projects
8
-
9
- Recommended setup (supported agents, all projects):
10
- npx --yes ${BRAINMCP_CLI_PACKAGE_SPEC} init --all --scope user
11
-
12
- Usage:
13
- brainmcp init [--client <client>] [--workspace <id>] [--scope project|user] [--yes]
14
- brainmcp init --all [--workspace <id>] [--scope project|user] [--yes]
15
- brainmcp doctor [--client <client>] [--workspace <id>] [--scope project|user]
16
- brainmcp print [--client <client>] [--workspace <id>] [--scope project|user]
17
- brainmcp remove [--client <client>] [--scope project|user] [--yes]
18
-
19
- Clients: ${CLIENT_IDS.join(', ')}
20
- --all configures ${BRAINMCP_GLOBAL_CLIENTS.map(clientLabel).join(', ')} (generic is client-specific).
21
- Project scope is the default. User scope writes to each host's supported per-user locations.
22
- Every mutation previews a diff and needs confirmation unless a trailing CLI --yes is supplied.
23
- The leading npx --yes above accepts npm's package download; it does not skip the file preview.`;
24
- async function main() {
25
- const [command, ...rawOptions] = argv.slice(2);
26
- if (!command || command === '--help' || command === '-h' || command === 'help') {
27
- console.log(HELP);
28
- return;
29
- }
30
- const values = parseOptions(rawOptions);
31
- if (values.all && values.client)
32
- throw new Error('Use either --all or --client, not both.');
33
- if (values.all && command !== 'init')
34
- throw new Error('--all is currently supported only by init.');
35
- const options = {
36
- root: values.root ?? cwd(),
37
- client: values.client,
38
- workspace: values.workspace,
39
- scope: values.scope ?? 'project',
40
- yes: values.yes,
41
- output: console.log,
42
- confirm: confirm,
43
- };
44
- switch (command) {
45
- case 'init':
46
- await (values.all ? initAll(options) : init(options));
47
- return;
48
- case 'doctor':
49
- await doctor(options);
50
- return;
51
- case 'print':
52
- await printConfig(options);
53
- return;
54
- case 'remove':
55
- await remove(options);
56
- return;
57
- default:
58
- throw new Error(`Unknown command: ${command}\n\n${HELP}`);
59
- }
60
- }
61
- function parseOptions(args) {
62
- const result = { yes: false, all: false };
63
- for (let index = 0; index < args.length; index += 1) {
64
- const option = args[index];
65
- if (option === '--yes' || option === '-y')
66
- result.yes = true;
67
- else if (option === '--all')
68
- result.all = true;
69
- else if (option === '--client')
70
- result.client = assertClientId(readValue(args, ++index, option));
71
- else if (option === '--workspace')
72
- result.workspace = readValue(args, ++index, option);
73
- else if (option === '--scope') {
74
- const scope = readValue(args, ++index, option);
75
- if (scope !== 'project' && scope !== 'user')
76
- throw new Error('--scope must be project or user.');
77
- result.scope = scope;
78
- }
79
- else if (option === '--root')
80
- result.root = readValue(args, ++index, option);
81
- else
82
- throw new Error(`Unknown option: ${option}`);
83
- }
84
- return result;
85
- }
86
- function readValue(args, index, option) {
87
- const value = args[index];
88
- if (!value || value.startsWith('-'))
89
- throw new Error(`${option} requires a value.`);
90
- return value;
91
- }
92
- async function confirm(message) {
93
- if (!stdin.isTTY || !stdout.isTTY)
94
- return false;
95
- const readline = createInterface({ input: stdin, output: stdout });
96
- try {
97
- return (await readline.question(`${message} [y/N] `)).trim().toLowerCase() === 'y';
98
- }
99
- finally {
100
- readline.close();
101
- }
102
- }
103
- main().catch((error) => {
2
+ import { argv } from 'node:process';
3
+ import { dispatch } from './commands/dispatch.js';
4
+ dispatch(argv.slice(2))
5
+ .then((code) => {
6
+ process.exitCode = code;
7
+ })
8
+ .catch((error) => {
104
9
  console.error(error instanceof Error ? error.message : String(error));
105
10
  process.exitCode = 1;
106
11
  });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,IAAI,EAAY,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAElD,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;KACpB,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;IACb,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;AAC1B,CAAC,CAAC;KACD,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IACxB,OAAO,CAAC,KAAK,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACtE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,32 @@
1
+ import type { CredentialStore, StoredCredentials } from './credentials.js';
2
+ import { OAuthClient } from './oauth-client.js';
3
+ export interface ApiClientOptions {
4
+ apiUrl?: string;
5
+ store: CredentialStore;
6
+ oauthClient?: OAuthClient;
7
+ fetchFn?: typeof fetch;
8
+ serviceTokenEnv?: string;
9
+ timeoutMs?: number;
10
+ }
11
+ export interface ApiRequestOptions {
12
+ method?: string;
13
+ headers?: Record<string, string>;
14
+ body?: unknown;
15
+ timeoutMs?: number;
16
+ skipAuth?: boolean;
17
+ signal?: AbortSignal;
18
+ expectedAuthorizationId?: string;
19
+ beforeSend?: () => boolean;
20
+ }
21
+ export declare class ApiClient {
22
+ private apiUrl;
23
+ private store;
24
+ private oauthClient;
25
+ private fetchFn;
26
+ private serviceToken?;
27
+ private defaultTimeoutMs;
28
+ constructor(options: ApiClientOptions);
29
+ getEffectiveCredentials(): Promise<StoredCredentials | null>;
30
+ request<T>(path: string, options?: ApiRequestOptions): Promise<T>;
31
+ private refreshTokensWithLock;
32
+ }
@@ -0,0 +1,207 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { describeMisdirectedCompanionApi, resolveCompanionApiUrl } from './endpoints.js';
3
+ import { grantedCliScopes, OAuthClient } from './oauth-client.js';
4
+ import { validateHttpUrl, withRequestDeadline } from './http.js';
5
+ export class ApiClient {
6
+ apiUrl;
7
+ store;
8
+ oauthClient;
9
+ fetchFn;
10
+ serviceToken;
11
+ defaultTimeoutMs;
12
+ constructor(options) {
13
+ this.apiUrl = (options.apiUrl ?? resolveCompanionApiUrl()).replace(/\/+$/, '');
14
+ this.store = options.store;
15
+ this.fetchFn = options.fetchFn ?? fetch;
16
+ this.oauthClient = options.oauthClient ?? new OAuthClient({ fetchFn: this.fetchFn });
17
+ this.serviceToken = options.serviceTokenEnv ?? process.env.BRAINMCP_SERVICE_TOKEN;
18
+ this.defaultTimeoutMs = options.timeoutMs ?? 15_000;
19
+ validateHttpUrl(this.apiUrl);
20
+ }
21
+ async getEffectiveCredentials() {
22
+ const stored = await this.store.get();
23
+ if (stored?.authType === 'oauth' && stored.accessToken) {
24
+ return stored;
25
+ }
26
+ if (this.serviceToken) {
27
+ return {
28
+ version: 1,
29
+ authType: 'service',
30
+ serviceToken: this.serviceToken,
31
+ updatedAt: new Date().toISOString(),
32
+ };
33
+ }
34
+ return stored;
35
+ }
36
+ async request(path, options = {}) {
37
+ return withRequestDeadline(options.timeoutMs ?? this.defaultTimeoutMs, options.signal, async (signal) => {
38
+ const explicitAuthorization = new Headers(options.headers).has('Authorization');
39
+ const useStoredAuth = !options.skipAuth && !explicitAuthorization;
40
+ const creds = useStoredAuth ? await this.getEffectiveCredentials() : null;
41
+ signal.throwIfAborted();
42
+ if (options.expectedAuthorizationId && creds?.authorizationId !== options.expectedAuthorizationId) {
43
+ throw new Error('Companion authorization changed before the request.');
44
+ }
45
+ const url = new URL(path, `${this.apiUrl}/`);
46
+ if (url.origin !== new URL(this.apiUrl).origin || url.username || url.password) {
47
+ throw new Error('API requests must use the configured API origin.');
48
+ }
49
+ const isOrientEndpoint = url.pathname.endsWith('/orient');
50
+ if (creds?.authType === 'service' && isOrientEndpoint) {
51
+ throw new Error('Service tokens cannot access /orient. Interactive companion OAuth credentials are required for session orientation.');
52
+ }
53
+ const requestId = randomUUID();
54
+ const execute = async (token) => {
55
+ const headers = new Headers(options.headers);
56
+ headers.set('x-request-id', requestId);
57
+ headers.set('Accept', 'application/json');
58
+ if (options.body && !headers.has('Content-Type')) {
59
+ headers.set('Content-Type', 'application/json');
60
+ }
61
+ if (token && useStoredAuth) {
62
+ headers.set('Authorization', `Bearer ${token}`);
63
+ }
64
+ signal.throwIfAborted();
65
+ if (options.beforeSend && !options.beforeSend())
66
+ throw new Error('Request cancelled because capture consent changed.');
67
+ return await this.fetchFn(url.toString(), {
68
+ method: options.method ?? (options.body ? 'POST' : 'GET'),
69
+ headers,
70
+ body: options.body ? JSON.stringify(options.body) : undefined,
71
+ signal,
72
+ redirect: 'error',
73
+ });
74
+ };
75
+ let token = creds?.authType === 'oauth' ? creds.accessToken ?? null : creds?.serviceToken ?? null;
76
+ let res = await execute(token);
77
+ // Handle 401 with refresh-once policy if using OAuth credentials
78
+ if (res.status === 401 && creds?.authType === 'oauth' && creds.refreshToken) {
79
+ await res.body?.cancel();
80
+ const refreshed = await this.refreshTokensWithLock(token, creds, signal);
81
+ if (refreshed) {
82
+ token = refreshed.accessToken ?? null;
83
+ res = await execute(token);
84
+ }
85
+ }
86
+ if (!res.ok) {
87
+ // Response bodies can echo credentials or captured content. Keep them out
88
+ // of terminal output, persisted queue errors, and hook diagnostics.
89
+ const misdirected = res.status === 404 ? describeMisdirectedCompanionApi(this.apiUrl) : null;
90
+ const error = new Error(misdirected
91
+ ? `API request failed (status 404) for ${url.pathname}. ${misdirected}`
92
+ : `API request failed (status ${res.status})`);
93
+ error.status = res.status;
94
+ throw error;
95
+ }
96
+ if (res.status === 204) {
97
+ return undefined;
98
+ }
99
+ return (await res.json());
100
+ });
101
+ }
102
+ async refreshTokensWithLock(failedAccessToken, original, signal) {
103
+ const leaseHolder = randomUUID();
104
+ const leaseDurationMs = 15_000;
105
+ let refreshToken = null;
106
+ // Claim a short fenced lease under the credential lock, then release the
107
+ // filesystem lock before doing OAuth network I/O. A concurrent process
108
+ // either reuses the rotated credential or waits for the lease owner.
109
+ for (let attempt = 0; attempt < 50; attempt += 1) {
110
+ signal.throwIfAborted();
111
+ const claim = await this.store.withRefreshLock(async (current) => {
112
+ signal.throwIfAborted();
113
+ if (current?.authorizationId !== original.authorizationId || current?.userId !== original.userId) {
114
+ return { kind: 'missing', current: null };
115
+ }
116
+ if (!current || current.authType !== 'oauth' || !current.refreshToken) {
117
+ return { kind: 'missing', current };
118
+ }
119
+ if (current.accessToken && current.accessToken !== failedAccessToken) {
120
+ return { kind: 'reuse', current };
121
+ }
122
+ const leaseExpiry = current.refreshLease
123
+ ? Date.parse(current.refreshLease.expiresAt)
124
+ : Number.NaN;
125
+ if (current.refreshLease &&
126
+ current.refreshLease.leaseHolder !== leaseHolder &&
127
+ Number.isFinite(leaseExpiry) &&
128
+ leaseExpiry > Date.now()) {
129
+ return { kind: 'wait', current };
130
+ }
131
+ const updated = {
132
+ ...current,
133
+ refreshLease: {
134
+ leaseHolder,
135
+ expiresAt: new Date(Date.now() + leaseDurationMs).toISOString(),
136
+ revision: (current.refreshLease?.revision ?? 0) + 1,
137
+ },
138
+ updatedAt: new Date().toISOString(),
139
+ };
140
+ await this.store.set(updated);
141
+ return { kind: 'claimed', current: updated };
142
+ });
143
+ if (claim.kind === 'missing')
144
+ return claim.current;
145
+ if (claim.kind === 'reuse')
146
+ return claim.current;
147
+ if (claim.kind === 'claimed') {
148
+ refreshToken = claim.current.refreshToken ?? null;
149
+ break;
150
+ }
151
+ await new Promise((resolve) => setTimeout(resolve, 80 + Math.floor(Math.random() * 40)));
152
+ }
153
+ if (!refreshToken)
154
+ return null;
155
+ try {
156
+ const endpoints = await this.oauthClient.discoverEndpoints(signal);
157
+ const tokenResponse = await this.oauthClient.refreshAccessToken({
158
+ tokenEndpoint: endpoints.tokenEndpoint,
159
+ refreshToken,
160
+ signal,
161
+ });
162
+ return await this.store.withRefreshLock(async (current) => {
163
+ signal.throwIfAborted();
164
+ if (!current || current.authType !== 'oauth' || current.authorizationId !== original.authorizationId || current.userId !== original.userId)
165
+ return null;
166
+ if (current.refreshLease?.leaseHolder !== leaseHolder) {
167
+ return current.accessToken !== failedAccessToken ? current : null;
168
+ }
169
+ if (current.refreshToken && current.refreshToken !== refreshToken) {
170
+ const reused = { ...current, refreshLease: undefined, updatedAt: new Date().toISOString() };
171
+ await this.store.set(reused);
172
+ return reused;
173
+ }
174
+ const now = new Date();
175
+ const scopes = tokenResponse.scope === undefined
176
+ ? current.scopes
177
+ : grantedCliScopes(current.scopes ?? [], tokenResponse.scope);
178
+ const updated = {
179
+ ...current,
180
+ accessToken: tokenResponse.access_token,
181
+ refreshToken: tokenResponse.refresh_token ?? current.refreshToken,
182
+ scopes,
183
+ expiresAt: tokenResponse.expires_in
184
+ ? new Date(now.getTime() + tokenResponse.expires_in * 1000).toISOString()
185
+ : current.expiresAt,
186
+ refreshLease: undefined,
187
+ updatedAt: now.toISOString(),
188
+ };
189
+ await this.store.set(updated);
190
+ return updated;
191
+ });
192
+ }
193
+ catch (error) {
194
+ await this.store.withRefreshLock(async (current) => {
195
+ if (current?.authType === 'oauth' && current.refreshLease?.leaseHolder === leaseHolder) {
196
+ await this.store.set({
197
+ ...current,
198
+ refreshLease: undefined,
199
+ updatedAt: new Date().toISOString(),
200
+ });
201
+ }
202
+ });
203
+ throw error;
204
+ }
205
+ }
206
+ }
207
+ //# sourceMappingURL=api-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-client.js","sourceRoot":"","sources":["../../src/lib/api-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ export interface CallbackResult {
2
+ code?: string;
3
+ error?: string;
4
+ errorDescription?: string;
5
+ }
6
+ export interface CallbackServerOptions {
7
+ expectedState: string;
8
+ timeoutMs?: number;
9
+ pathname?: string;
10
+ }
11
+ export interface ActiveCallbackServer {
12
+ port: number;
13
+ redirectUri: string;
14
+ waitForCallback(): Promise<CallbackResult>;
15
+ close(): Promise<void>;
16
+ }
17
+ export declare function startCallbackServer(options: CallbackServerOptions): Promise<ActiveCallbackServer>;
@@ -0,0 +1,145 @@
1
+ import { createServer } from 'node:http';
2
+ import { URL } from 'node:url';
3
+ const SUCCESS_HTML = `<!DOCTYPE html>
4
+ <html>
5
+ <head><title>BrainMCP Authentication Successful</title><style>
6
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; background: #0f172a; color: #f8fafc; }
7
+ .card { background: #1e293b; padding: 2rem 3rem; border-radius: 12px; border: 1px solid #334155; text-align: center; max-width: 480px; box-shadow: 0 10px 25px rgba(0,0,0,0.5); }
8
+ h1 { font-size: 1.5rem; margin-bottom: 0.5rem; color: #38bdf8; }
9
+ p { color: #94a3b8; font-size: 0.95rem; line-height: 1.5; }
10
+ </style></head>
11
+ <body><div class="card"><h1>Authentication Successful</h1><p>You may close this tab and return to your terminal.</p></div></body>
12
+ </html>`;
13
+ const ERROR_HTML = (msg) => `<!DOCTYPE html>
14
+ <html>
15
+ <head><title>BrainMCP Authentication Failed</title><style>
16
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; background: #0f172a; color: #f8fafc; }
17
+ .card { background: #1e293b; padding: 2rem 3rem; border-radius: 12px; border: 1px solid #dc2626; text-align: center; max-width: 480px; }
18
+ h1 { font-size: 1.5rem; margin-bottom: 0.5rem; color: #f87171; }
19
+ p { color: #94a3b8; font-size: 0.95rem; line-height: 1.5; }
20
+ </style></head>
21
+ <body><div class="card"><h1>Authentication Failed</h1><p>${escapeHtml(msg)}</p></div></body>
22
+ </html>`;
23
+ function escapeHtml(value) {
24
+ return value
25
+ .replace(/&/g, '&amp;')
26
+ .replace(/</g, '&lt;')
27
+ .replace(/>/g, '&gt;')
28
+ .replace(/"/g, '&quot;')
29
+ .replace(/'/g, '&#39;');
30
+ }
31
+ export async function startCallbackServer(options) {
32
+ const timeoutMs = options.timeoutMs ?? 300_000;
33
+ const expectedPathname = options.pathname ?? '/callback';
34
+ let hasFinished = false;
35
+ let isConsumed = false;
36
+ let server = null;
37
+ let timeoutHandle = null;
38
+ let resolveResult;
39
+ let rejectResult;
40
+ const resultPromise = new Promise((resolve, reject) => {
41
+ resolveResult = resolve;
42
+ rejectResult = reject;
43
+ });
44
+ // Prevent unhandledRejection if close() is invoked before consumer calls waitForCallback()
45
+ resultPromise.catch(() => { });
46
+ const closeServer = async () => {
47
+ if (timeoutHandle) {
48
+ clearTimeout(timeoutHandle);
49
+ timeoutHandle = null;
50
+ }
51
+ if (server) {
52
+ const s = server;
53
+ server = null;
54
+ await new Promise((resolve) => s.close(() => resolve()));
55
+ }
56
+ };
57
+ server = createServer((req, res) => {
58
+ if (!req.url) {
59
+ res.writeHead(400, { 'Content-Type': 'text/plain' });
60
+ res.end('Bad Request');
61
+ return;
62
+ }
63
+ const hostHeader = req.headers.host;
64
+ // Strict host check: must be 127.0.0.1 or localhost with optional port
65
+ if (!hostHeader || !/^(127\.0\.0\.1|localhost)(:\d+)?$/.test(hostHeader)) {
66
+ res.writeHead(400, { 'Content-Type': 'text/plain' });
67
+ res.end('Invalid host header');
68
+ return;
69
+ }
70
+ const parsedUrl = new URL(req.url, `http://${hostHeader}`);
71
+ // Path verification
72
+ if (parsedUrl.pathname !== expectedPathname) {
73
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
74
+ res.end('Not Found');
75
+ return;
76
+ }
77
+ // One-shot check
78
+ if (isConsumed) {
79
+ res.writeHead(409, { 'Content-Type': 'text/html' });
80
+ res.end(ERROR_HTML('Callback already consumed or timed out.'));
81
+ return;
82
+ }
83
+ // State verification
84
+ const state = parsedUrl.searchParams.get('state');
85
+ if (!state || state !== options.expectedState) {
86
+ res.writeHead(400, { 'Content-Type': 'text/html' });
87
+ res.end(ERROR_HTML('State parameter mismatch or missing.'));
88
+ return;
89
+ }
90
+ // Mark as consumed
91
+ isConsumed = true;
92
+ hasFinished = true;
93
+ const error = parsedUrl.searchParams.get('error');
94
+ const errorDescription = parsedUrl.searchParams.get('error_description') ?? undefined;
95
+ const code = parsedUrl.searchParams.get('code') ?? undefined;
96
+ if (error) {
97
+ res.writeHead(200, { 'Content-Type': 'text/html' });
98
+ res.end(ERROR_HTML(errorDescription || `Error: ${error}`));
99
+ resolveResult({ error, errorDescription });
100
+ setTimeout(() => void closeServer(), 500);
101
+ return;
102
+ }
103
+ if (!code) {
104
+ res.writeHead(400, { 'Content-Type': 'text/html' });
105
+ res.end(ERROR_HTML('Missing authorization code in callback.'));
106
+ resolveResult({ error: 'missing_code', errorDescription: 'Authorization code was not provided.' });
107
+ setTimeout(() => void closeServer(), 500);
108
+ return;
109
+ }
110
+ res.writeHead(200, { 'Content-Type': 'text/html' });
111
+ res.end(SUCCESS_HTML);
112
+ resolveResult({ code });
113
+ setTimeout(() => void closeServer(), 500);
114
+ });
115
+ // Bind exclusively to 127.0.0.1 port 0
116
+ await new Promise((resolve, reject) => {
117
+ server.listen(0, '127.0.0.1', () => resolve());
118
+ server.on('error', reject);
119
+ });
120
+ const address = server.address();
121
+ const port = address.port;
122
+ const redirectUri = `http://127.0.0.1:${port}${expectedPathname}`;
123
+ timeoutHandle = setTimeout(() => {
124
+ if (!hasFinished) {
125
+ hasFinished = true;
126
+ isConsumed = true;
127
+ rejectResult(new Error(`Authentication timed out after ${Math.round(timeoutMs / 1000)} seconds.`));
128
+ void closeServer();
129
+ }
130
+ }, timeoutMs);
131
+ return {
132
+ port,
133
+ redirectUri,
134
+ waitForCallback: () => resultPromise,
135
+ close: async () => {
136
+ if (!hasFinished) {
137
+ hasFinished = true;
138
+ isConsumed = true;
139
+ rejectResult(new Error('Authentication cancelled by user.'));
140
+ }
141
+ await closeServer();
142
+ },
143
+ };
144
+ }
145
+ //# sourceMappingURL=callback-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"callback-server.js","sourceRoot":"","sources":["../../src/lib/callback-server.ts"],"names":[],"mappings":"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"}