brainclaw 0.19.12 → 0.19.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.
@@ -13,21 +13,21 @@ This project uses brainclaw for shared coordination between humans and agents.
13
13
 
14
14
  1. Run \`brainclaw context\` to load shared state (constraints, decisions, traps, plans, handoffs)
15
15
  2. Check **Your open work** for active claims and in-progress plans assigned to you
16
- 3. Respect active claims from other agents — check \`brainclaw claim list\` before editing a claimed scope
16
+ 3. Respect active claims from other agents — check \`brainclaw claim list\` before editing a claimed scope
17
17
 
18
18
  ### Before finishing (required)
19
19
 
20
- 1. Release claims you opened: \`brainclaw claim release <id>\` — or \`brainclaw session-end --auto-release\`
21
- 2. Update completed plan items: \`brainclaw plan update <id> --status done\`
20
+ 1. Release claims you opened: \`brainclaw claim release <id>\` — or \`brainclaw session-end --auto-release\`
21
+ 2. Update completed plan items: \`brainclaw plan update <id> --status done\`
22
22
 
23
23
  ### Recording work
24
24
 
25
25
  \`\`\`bash
26
- brainclaw memory create decision "<text>" # record a decision
27
- brainclaw memory create constraint "<text>" # record an active constraint
28
- brainclaw memory create trap "<text>" # record a known trap
29
- brainclaw claim create "<text>" --scope <path> # claim a scope before editing
30
- brainclaw plan create "<text>" # add a shared work item
26
+ brainclaw memory create decision "<text>" # record a decision
27
+ brainclaw memory create constraint "<text>" # record an active constraint
28
+ brainclaw memory create trap "<text>" # record a known trap
29
+ brainclaw claim create "<text>" --scope <path> # claim a scope before editing
30
+ brainclaw plan create "<text>" # add a shared work item
31
31
  \`\`\`
32
32
 
33
33
  Memory is stored in \`${storageDir}/\`. Run \`brainclaw doctor\` to verify health.
@@ -38,9 +38,9 @@ export function buildHygieneSection() {
38
38
 
39
39
  Before starting work:
40
40
  1. Run \`brainclaw context\` (or \`brainclaw context --json\`) to load shared memory
41
- 2. Run \`brainclaw claim list\` — do not edit a file claimed by another agent
42
- 3. Create a plan for significant work: \`brainclaw plan create "<description>"\`
43
- 4. Claim files you will modify: \`brainclaw claim create "<description>" --scope <path>\`
41
+ 2. Run \`brainclaw claim list\` — do not edit a file claimed by another agent
42
+ 3. Create a plan for significant work: \`brainclaw plan create "<description>"\`
43
+ 4. Claim files you will modify: \`brainclaw claim create "<description>" --scope <path>\`
44
44
 
45
45
  Before finishing:
46
46
  1. Run \`brainclaw session-end --auto-release\` — releases claims and updates plans
@@ -327,13 +327,13 @@ export function describeAutoConfigWrite(result) {
327
327
  return `✔ ${verb} ${result.label} at ${displayPath}`;
328
328
  }
329
329
  export function buildClaudeCodeCommandText() {
330
- return `Load brainclaw project memory and prepare for coordinated work.
331
-
332
- Steps:
333
- 1. Run \`brainclaw context --json\` — load constraints, decisions, traps, plans, handoffs
334
- 2. Run \`brainclaw claim list\` — check what files other agents have claimed
335
- 3. Before editing any file, run \`brainclaw claim create "<description>" --scope <path>\`
336
- 4. Before finishing, run \`brainclaw session-end --auto-release\`
330
+ return `Load brainclaw project memory and prepare for coordinated work.
331
+
332
+ Steps:
333
+ 1. Run \`brainclaw context --json\` — load constraints, decisions, traps, plans, handoffs
334
+ 2. Run \`brainclaw claim list\` — check what files other agents have claimed
335
+ 3. Before editing any file, run \`brainclaw claim create "<description>" --scope <path>\`
336
+ 4. Before finishing, run \`brainclaw session-end --auto-release\`
337
337
  `;
338
338
  }
339
339
  export function ensureClineMcpConfig(cwd) {
@@ -0,0 +1,321 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { spawnSync } from 'node:child_process';
5
+ function run(command, args, timeout = 5000) {
6
+ try {
7
+ const result = spawnSync(command, args, { encoding: 'utf-8', timeout, windowsHide: true });
8
+ return { ok: result.status === 0, stdout: result.stdout ?? '', stderr: result.stderr ?? '' };
9
+ }
10
+ catch {
11
+ return { ok: false, stdout: '', stderr: '' };
12
+ }
13
+ }
14
+ function listRunningProcesses(platform) {
15
+ if (platform === 'win32') {
16
+ const result = run('tasklist', ['/FO', 'CSV', '/NH'], 8000);
17
+ if (!result.ok)
18
+ return [];
19
+ return result.stdout
20
+ .split(/\r?\n/)
21
+ .map((line) => line.trim())
22
+ .filter(Boolean)
23
+ .map((line) => line.replace(/^"|"$/g, '').split('","')[0] ?? '')
24
+ .map((name) => name.replace(/\.exe$/i, '').toLowerCase())
25
+ .filter(Boolean);
26
+ }
27
+ const result = run('ps', ['-A', '-o', 'comm='], 8000);
28
+ if (!result.ok)
29
+ return [];
30
+ return result.stdout
31
+ .split(/\r?\n/)
32
+ .map((line) => path.basename(line.trim()).toLowerCase())
33
+ .filter(Boolean);
34
+ }
35
+ function detectWindowsAppxPackages() {
36
+ if (process.platform !== 'win32')
37
+ return [];
38
+ const script = [
39
+ "$patterns = @('OpenAI.ChatGPT-Desktop', '*ChatGPT*', 'Claude', '*Claude*');",
40
+ '$packages = foreach ($pattern in $patterns) { Get-AppxPackage -Name $pattern -ErrorAction SilentlyContinue };',
41
+ '$packages | Sort-Object Name -Unique |',
42
+ 'ForEach-Object { "{0}`t{1}`t{2}" -f $_.Name, $_.Version.ToString(), $_.InstallLocation }',
43
+ ].join(' ');
44
+ const result = run('powershell', ['-NoProfile', '-Command', script], 15000);
45
+ if (!result.ok || !result.stdout.trim())
46
+ return [];
47
+ return result.stdout
48
+ .split(/\r?\n/)
49
+ .map((line) => line.trim())
50
+ .filter(Boolean)
51
+ .map((line) => {
52
+ const [name, version, installLocation] = line.split('\t');
53
+ return {
54
+ name: name ?? '',
55
+ version: version || undefined,
56
+ installLocation: installLocation || undefined,
57
+ };
58
+ })
59
+ .filter((row) => row.name);
60
+ }
61
+ function detectBrowsers(homeDir, platform) {
62
+ const browsers = new Set();
63
+ const commands = platform === 'win32'
64
+ ? ['msedge', 'chrome', 'firefox']
65
+ : platform === 'darwin'
66
+ ? ['open', 'google-chrome', 'firefox', 'safari']
67
+ : ['xdg-open', 'google-chrome', 'chromium-browser', 'chromium', 'firefox'];
68
+ for (const command of commands) {
69
+ const result = run(platform === 'win32' ? 'where' : 'which', [command], 3000);
70
+ if (result.ok)
71
+ browsers.add(command);
72
+ }
73
+ const footprintPaths = platform === 'win32'
74
+ ? [
75
+ path.join(process.env.LOCALAPPDATA ?? '', 'Microsoft', 'Edge', 'Application', 'msedge.exe'),
76
+ path.join(process.env.LOCALAPPDATA ?? '', 'Google', 'Chrome', 'Application', 'chrome.exe'),
77
+ path.join(process.env.PROGRAMFILES ?? 'C:\\Program Files', 'Mozilla Firefox', 'firefox.exe'),
78
+ ]
79
+ : platform === 'darwin'
80
+ ? [
81
+ '/Applications/Google Chrome.app',
82
+ '/Applications/Firefox.app',
83
+ '/Applications/Safari.app',
84
+ path.join(homeDir, 'Applications', 'Google Chrome.app'),
85
+ ]
86
+ : [
87
+ path.join(homeDir, '.local', 'share', 'applications'),
88
+ '/usr/share/applications',
89
+ ];
90
+ for (const footprint of footprintPaths) {
91
+ if (fs.existsSync(footprint))
92
+ browsers.add(path.basename(footprint).toLowerCase());
93
+ }
94
+ return [...browsers];
95
+ }
96
+ function matchProcess(processNames, pattern) {
97
+ return processNames.some((name) => pattern.test(name));
98
+ }
99
+ function findExistingPath(paths) {
100
+ return paths.find((candidate) => candidate && fs.existsSync(candidate));
101
+ }
102
+ function matchWindowsAppxPackage(packages, pattern) {
103
+ return packages.find((pkg) => pattern.test(pkg.name));
104
+ }
105
+ export function buildAiSurfaceInventory(options = {}) {
106
+ const platform = options.platform ?? process.platform;
107
+ const homeDir = options.homeDir ?? os.homedir();
108
+ const processNames = options.processNames ?? listRunningProcesses(platform);
109
+ const windowsAppxPackages = options.windowsAppxPackages ?? (platform === 'win32' ? detectWindowsAppxPackages() : []);
110
+ const browsers = options.browsers ?? detectBrowsers(homeDir, platform);
111
+ const surfaces = [];
112
+ const chatGptAppx = platform === 'win32'
113
+ ? matchWindowsAppxPackage(windowsAppxPackages, /OpenAI\.ChatGPT-Desktop|ChatGPT/i)
114
+ : undefined;
115
+ const chatGptRunning = matchProcess(processNames, /^chatgpt$/i);
116
+ const chatGptInstallLocation = platform === 'darwin'
117
+ ? findExistingPath([
118
+ '/Applications/ChatGPT.app',
119
+ path.join(homeDir, 'Applications', 'ChatGPT.app'),
120
+ ])
121
+ : platform === 'win32'
122
+ ? chatGptAppx?.installLocation
123
+ : undefined;
124
+ const chatGptDetected = Boolean(chatGptRunning || chatGptInstallLocation || chatGptAppx);
125
+ surfaces.push({
126
+ id: `surf_chatgpt_${platform}`,
127
+ product_name: 'chatgpt',
128
+ display_name: 'ChatGPT Desktop',
129
+ surface_kind: platform === 'linux' ? 'web_surface' : 'desktop_ai_app',
130
+ variant: platform === 'win32' ? 'windows_store' : platform === 'darwin' ? 'macos_app' : 'web',
131
+ status: chatGptRunning ? 'detected_running' : chatGptDetected ? 'detected_install' : (platform === 'linux' && browsers.length > 0 ? 'limited' : 'not_detected'),
132
+ running: chatGptRunning,
133
+ install_source: chatGptAppx ? 'appx' : chatGptInstallLocation ? 'bundle' : platform === 'linux' && browsers.length > 0 ? 'web' : undefined,
134
+ install_location: chatGptInstallLocation,
135
+ version: chatGptAppx?.version,
136
+ detection_sources: [
137
+ ...(chatGptAppx ? [`AppX package: ${chatGptAppx.name}`] : []),
138
+ ...(chatGptInstallLocation ? [`install path: ${chatGptInstallLocation}`] : []),
139
+ ...(chatGptRunning ? ['running process: ChatGPT'] : []),
140
+ ...(platform === 'linux' && browsers.length > 0 ? ['browser availability'] : []),
141
+ ],
142
+ supports_mcp: 'unknown',
143
+ supports_remote_connectors: 'unknown',
144
+ supports_local_config: 'limited',
145
+ supports_context_export: 'yes',
146
+ supports_prompt_bootstrap: 'yes',
147
+ supports_safe_write_actions: 'limited',
148
+ interactive_only: true,
149
+ can_edit_code: false,
150
+ recommended_uses: [
151
+ 'generate visual concepts and rough assets',
152
+ 'draft product copy and polished summaries',
153
+ 'prepare slide, email, or launch material from project context',
154
+ ],
155
+ });
156
+ const claudeRunning = matchProcess(processNames, /^claude$/i);
157
+ const claudeAppx = platform === 'win32'
158
+ ? matchWindowsAppxPackage(windowsAppxPackages, /^Claude$/i)
159
+ : undefined;
160
+ const claudeInstallLocation = platform === 'darwin'
161
+ ? findExistingPath([
162
+ '/Applications/Claude.app',
163
+ path.join(homeDir, 'Applications', 'Claude.app'),
164
+ ])
165
+ : platform === 'win32'
166
+ ? (claudeAppx?.installLocation ?? findExistingPath([
167
+ path.join(process.env.LOCALAPPDATA ?? '', 'Programs', 'Claude', 'Claude.exe'),
168
+ path.join(process.env.LOCALAPPDATA ?? '', 'AnthropicClaude', 'Claude.exe'),
169
+ path.join(process.env.LOCALAPPDATA ?? '', 'Claude'),
170
+ path.join(process.env.APPDATA ?? '', 'Claude'),
171
+ ]))
172
+ : undefined;
173
+ const claudeDetected = Boolean(claudeRunning || claudeInstallLocation);
174
+ surfaces.push({
175
+ id: `surf_claude_desktop_${platform}`,
176
+ product_name: 'claude',
177
+ display_name: 'Claude Desktop',
178
+ surface_kind: platform === 'linux' ? 'web_surface' : 'desktop_ai_app',
179
+ variant: platform === 'darwin' ? 'macos_app' : platform === 'win32' ? 'desktop' : 'web',
180
+ status: claudeRunning ? 'detected_running' : claudeDetected ? 'detected_install' : (platform === 'linux' && browsers.length > 0 ? 'limited' : 'not_detected'),
181
+ running: claudeRunning,
182
+ install_source: claudeAppx ? 'appx' : claudeInstallLocation ? 'bundle' : platform === 'linux' && browsers.length > 0 ? 'web' : undefined,
183
+ install_location: claudeInstallLocation,
184
+ version: claudeAppx?.version,
185
+ detection_sources: [
186
+ ...(claudeAppx ? [`AppX package: ${claudeAppx.name}`] : []),
187
+ ...(claudeInstallLocation ? [`install path: ${claudeInstallLocation}`] : []),
188
+ ...(claudeRunning ? ['running process: Claude'] : []),
189
+ ...(platform === 'linux' && browsers.length > 0 ? ['browser availability'] : []),
190
+ ],
191
+ supports_mcp: platform === 'linux' ? 'limited' : 'yes',
192
+ supports_remote_connectors: platform === 'linux' ? 'limited' : 'yes',
193
+ supports_local_config: platform === 'linux' ? 'limited' : 'yes',
194
+ supports_context_export: 'yes',
195
+ supports_prompt_bootstrap: 'yes',
196
+ supports_safe_write_actions: 'limited',
197
+ interactive_only: true,
198
+ can_edit_code: false,
199
+ recommended_uses: [
200
+ 'project synthesis and reasoning-heavy drafting',
201
+ 'doc and handoff preparation around a repo',
202
+ 'MCP-oriented project context consumption when supported',
203
+ ],
204
+ });
205
+ surfaces.push({
206
+ id: `surf_claude_cowork_${platform}`,
207
+ product_name: 'claude-cowork',
208
+ display_name: 'Claude Cowork',
209
+ surface_kind: 'desktop_embedded_capability',
210
+ variant: 'embedded',
211
+ parent_surface_id: `surf_claude_desktop_${platform}`,
212
+ status: claudeDetected || claudeRunning ? 'limited' : 'not_detected',
213
+ running: false,
214
+ detection_sources: claudeDetected || claudeRunning
215
+ ? ['parent capability: Claude Desktop']
216
+ : [],
217
+ supports_mcp: 'limited',
218
+ supports_remote_connectors: 'limited',
219
+ supports_local_config: 'limited',
220
+ supports_context_export: 'yes',
221
+ supports_prompt_bootstrap: 'yes',
222
+ supports_safe_write_actions: 'limited',
223
+ interactive_only: true,
224
+ can_edit_code: false,
225
+ recommended_uses: [
226
+ 'parallel collaboration on non-code deliverables',
227
+ 'task follow-up and structured handoff work',
228
+ 'operator-facing drafting without repo edits',
229
+ ],
230
+ });
231
+ const geminiCliPath = findExistingPath([
232
+ path.join(homeDir, '.gemini', 'antigravity'),
233
+ ]);
234
+ const geminiCliRunning = matchProcess(processNames, /gemini|antigravity/i);
235
+ const geminiCliVersion = run('gemini', ['--version'], 3000);
236
+ const geminiCliDetected = Boolean(geminiCliPath || geminiCliRunning || geminiCliVersion.ok);
237
+ surfaces.push({
238
+ id: `surf_gemini_cli_${platform}`,
239
+ product_name: 'gemini',
240
+ display_name: 'Gemini CLI / Antigravity',
241
+ surface_kind: 'cli_agent',
242
+ variant: 'antigravity',
243
+ status: geminiCliRunning ? 'detected_running' : geminiCliDetected ? 'brainclaw_ready' : 'not_detected',
244
+ running: geminiCliRunning,
245
+ install_source: geminiCliPath ? 'config_footprint' : geminiCliVersion.ok ? 'cli' : undefined,
246
+ install_location: geminiCliPath,
247
+ version: geminiCliVersion.ok ? geminiCliVersion.stdout.trim() : undefined,
248
+ detection_sources: [
249
+ ...(geminiCliPath ? [`config path: ${geminiCliPath}`] : []),
250
+ ...(geminiCliVersion.ok ? ['gemini --version'] : []),
251
+ ...(geminiCliRunning ? ['running process: gemini/antigravity'] : []),
252
+ ],
253
+ supports_mcp: 'yes',
254
+ supports_remote_connectors: 'unknown',
255
+ supports_local_config: 'yes',
256
+ supports_context_export: 'yes',
257
+ supports_prompt_bootstrap: 'yes',
258
+ supports_safe_write_actions: 'limited',
259
+ interactive_only: false,
260
+ can_edit_code: false,
261
+ recommended_uses: [
262
+ 'CLI-driven analysis and automation-adjacent tasks',
263
+ 'repo-adjacent reasoning without changing the editing agent',
264
+ 'structured use of local MCP and exported context',
265
+ ],
266
+ });
267
+ const geminiWebAvailable = browsers.length > 0;
268
+ surfaces.push({
269
+ id: `surf_gemini_web_${platform}`,
270
+ product_name: 'gemini',
271
+ display_name: 'Gemini Web',
272
+ surface_kind: 'web_surface',
273
+ variant: 'browser',
274
+ status: geminiWebAvailable ? 'limited' : 'not_detected',
275
+ running: matchProcess(processNames, /chrome|msedge|firefox|safari/i),
276
+ install_source: geminiWebAvailable ? 'web' : undefined,
277
+ detection_sources: geminiWebAvailable ? [`browser availability: ${browsers.join(', ')}`] : [],
278
+ supports_mcp: 'unknown',
279
+ supports_remote_connectors: 'unknown',
280
+ supports_local_config: 'limited',
281
+ supports_context_export: 'yes',
282
+ supports_prompt_bootstrap: 'yes',
283
+ supports_safe_write_actions: 'limited',
284
+ interactive_only: true,
285
+ can_edit_code: false,
286
+ recommended_uses: [
287
+ 'quick research and synthesis in a browser surface',
288
+ 'lightweight planning, summaries, and exploration',
289
+ 'prompt-bootstrap workflows when no native integration exists',
290
+ ],
291
+ });
292
+ return surfaces;
293
+ }
294
+ export function renderAiSurfaceSummary(surfaces) {
295
+ const detected = surfaces.filter((surface) => surface.status !== 'not_detected');
296
+ const lines = [];
297
+ lines.push(`AI surfaces: ${detected.length}/${surfaces.length} detected or available`);
298
+ for (const surface of detected) {
299
+ const details = [surface.surface_kind, surface.status];
300
+ if (surface.variant)
301
+ details.push(surface.variant);
302
+ if (surface.version)
303
+ details.push(surface.version);
304
+ lines.push(` - ${surface.display_name} (${details.join(', ')})`);
305
+ }
306
+ return lines;
307
+ }
308
+ export function renderAiSurfaceUsageHints(surfaces) {
309
+ const eligible = surfaces.filter((surface) => surface.status !== 'not_detected');
310
+ const lines = [];
311
+ for (const surface of eligible) {
312
+ if (surface.recommended_uses.length === 0)
313
+ continue;
314
+ lines.push(`${surface.display_name}:`);
315
+ for (const useCase of surface.recommended_uses.slice(0, 2)) {
316
+ lines.push(` - ${useCase}`);
317
+ }
318
+ }
319
+ return lines;
320
+ }
321
+ //# sourceMappingURL=ai-surface-inventory.js.map
@@ -0,0 +1,40 @@
1
+ import fs from 'node:fs';
2
+ import { JsonStore } from './json-store.js';
3
+ import { resolveEntityDir, withStoreLock } from './io.js';
4
+ import { AiSurfaceTaskRequestSchema } from './schema.js';
5
+ function surfaceTasksDir(cwd, mode = 'read') {
6
+ return resolveEntityDir('surface-tasks', cwd ?? process.cwd(), mode);
7
+ }
8
+ function surfaceTaskStore(cwd) {
9
+ return new JsonStore({
10
+ dirPath: surfaceTasksDir(cwd, 'read'),
11
+ documentType: 'ai_surface_task',
12
+ getId: (task) => task.id,
13
+ sort: (a, b) => a.created_at.localeCompare(b.created_at),
14
+ });
15
+ }
16
+ export function ensureAiSurfaceTasksDir(cwd) {
17
+ const dir = surfaceTasksDir(cwd, 'write');
18
+ if (!fs.existsSync(dir)) {
19
+ fs.mkdirSync(dir, { recursive: true });
20
+ }
21
+ }
22
+ export function saveAiSurfaceTask(task, cwd) {
23
+ withStoreLock(cwd, () => {
24
+ ensureAiSurfaceTasksDir(cwd);
25
+ const writeStore = new JsonStore({
26
+ dirPath: surfaceTasksDir(cwd, 'write'),
27
+ documentType: 'ai_surface_task',
28
+ getId: (entry) => entry.id,
29
+ sort: (a, b) => a.created_at.localeCompare(b.created_at),
30
+ });
31
+ writeStore.save(AiSurfaceTaskRequestSchema.parse(task));
32
+ });
33
+ }
34
+ export function loadAiSurfaceTask(id, cwd) {
35
+ return surfaceTaskStore(cwd).load(id);
36
+ }
37
+ export function listAiSurfaceTasks(cwd) {
38
+ return surfaceTaskStore(cwd).list();
39
+ }
40
+ //# sourceMappingURL=ai-surface-tasks.js.map
@@ -0,0 +1,224 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { spawnSync } from 'node:child_process';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { memoryExists } from './io.js';
6
+ const CLAUDE_DESKTOP_TOOLS = [
7
+ { name: 'bclaw_session_start', description: 'Open a Brainclaw session and surface Claude-targeted tasks plus compact execution context.' },
8
+ { name: 'bclaw_get_context', description: 'Retrieve ranked Brainclaw project context for the current task or path.' },
9
+ { name: 'bclaw_list_surface_tasks', description: 'List queued or completed Brainclaw tasks delegated to Claude Desktop.' },
10
+ { name: 'bclaw_update_surface_task', description: 'Mark a delegated Claude Desktop task as in progress or completed.' },
11
+ ];
12
+ export function buildClaudeDesktopExtension(options = {}) {
13
+ const cwd = path.resolve(options.cwd ?? process.cwd());
14
+ if (!memoryExists(cwd)) {
15
+ throw new Error('Project memory not initialized. Run `brainclaw init` first.');
16
+ }
17
+ const workspaceDir = path.resolve(options.workspaceDir ?? path.join(cwd, 'internal-docs', 'desktop-extensions', 'claude-desktop-brainclaw'));
18
+ const outputFile = path.resolve(options.outputFile ?? path.join(cwd, 'internal-docs', 'desktop-extensions', 'brainclaw-claude-desktop.mcpb'));
19
+ if (outputFile.startsWith(`${workspaceDir}${path.sep}`) || outputFile === workspaceDir) {
20
+ throw new Error('The .mcpb output file must live outside the extension workspace directory.');
21
+ }
22
+ const projectRoot = path.resolve(options.projectRoot ?? cwd);
23
+ const runtimeRoot = path.resolve(options.runtimeRootOverride ?? resolveRuntimeRoot());
24
+ const packageRoot = path.resolve(options.packageRootOverride ?? findPackageRoot(runtimeRoot));
25
+ const metadata = readPackageMetadata(packageRoot);
26
+ const copiedDependencies = options.dependenciesOverride ?? resolveRuntimeDependencies(packageRoot);
27
+ fs.rmSync(workspaceDir, { recursive: true, force: true });
28
+ fs.mkdirSync(workspaceDir, { recursive: true });
29
+ fs.cpSync(runtimeRoot, path.join(workspaceDir, 'runtime'), { recursive: true });
30
+ fs.mkdirSync(path.join(workspaceDir, 'server'), { recursive: true });
31
+ fs.mkdirSync(path.dirname(outputFile), { recursive: true });
32
+ for (const dep of copiedDependencies) {
33
+ const sourceDir = path.join(packageRoot, 'node_modules', dep);
34
+ if (!fs.existsSync(sourceDir)) {
35
+ throw new Error(`Missing runtime dependency for Claude Desktop extension: ${dep}`);
36
+ }
37
+ fs.cpSync(sourceDir, path.join(workspaceDir, 'node_modules', dep), { recursive: true });
38
+ }
39
+ const version = metadata.version ?? '0.0.0';
40
+ const manifestPath = path.join(workspaceDir, 'manifest.json');
41
+ const entryPointPath = path.join(workspaceDir, 'server', 'index.js');
42
+ const packageJsonPath = path.join(workspaceDir, 'package.json');
43
+ fs.writeFileSync(entryPointPath, buildServerEntryPoint(), 'utf-8');
44
+ fs.writeFileSync(packageJsonPath, `${JSON.stringify({
45
+ name: 'brainclaw-claude-desktop-extension',
46
+ private: true,
47
+ type: 'module',
48
+ version,
49
+ }, null, 2)}\n`, 'utf-8');
50
+ fs.writeFileSync(manifestPath, `${JSON.stringify(buildManifest(metadata, version, projectRoot), null, 2)}\n`, 'utf-8');
51
+ const packed = options.pack !== false ? packClaudeDesktopExtension(workspaceDir, outputFile) : false;
52
+ return {
53
+ workspaceDir,
54
+ outputFile,
55
+ packed,
56
+ manifestPath,
57
+ entryPointPath,
58
+ packageRoot,
59
+ runtimeRoot,
60
+ projectRoot,
61
+ copiedDependencies,
62
+ };
63
+ }
64
+ function buildServerEntryPoint() {
65
+ return `import process from 'node:process';
66
+
67
+ const projectRoot = process.env.BRAINCLAW_PROJECT_ROOT?.trim();
68
+ if (projectRoot) {
69
+ process.chdir(projectRoot);
70
+ }
71
+
72
+ const { runMcp } = await import('../runtime/commands/mcp.js');
73
+ runMcp();
74
+ `;
75
+ }
76
+ function buildManifest(metadata, version, projectRoot) {
77
+ return {
78
+ manifest_version: '0.3',
79
+ name: 'brainclaw-claude-desktop',
80
+ display_name: 'Brainclaw Project Memory',
81
+ version,
82
+ description: 'Brainclaw local project memory and delegated task inbox for Claude Desktop.',
83
+ author: {
84
+ name: 'Brainclaw',
85
+ ...(metadata.homepage ? { url: metadata.homepage } : {}),
86
+ },
87
+ ...(typeof metadata.repository === 'string'
88
+ ? { repository: { type: 'git', url: metadata.repository } }
89
+ : metadata.repository
90
+ ? { repository: metadata.repository }
91
+ : {}),
92
+ ...(metadata.homepage ? { homepage: metadata.homepage } : {}),
93
+ server: {
94
+ type: 'node',
95
+ entry_point: 'server/index.js',
96
+ mcp_config: {
97
+ command: 'node',
98
+ args: ['${__dirname}/server/index.js'],
99
+ env: {
100
+ BRAINCLAW_PROJECT_ROOT: '${user_config.project_root}',
101
+ BRAINCLAW_SKIP_SETUP_REQUIREMENT: '1',
102
+ },
103
+ },
104
+ },
105
+ tools: CLAUDE_DESKTOP_TOOLS,
106
+ compatibility: {
107
+ platforms: ['win32', 'darwin'],
108
+ runtimes: {
109
+ node: '>=20.0.0',
110
+ },
111
+ },
112
+ user_config: {
113
+ project_root: {
114
+ type: 'directory',
115
+ title: 'Project Root',
116
+ description: 'Brainclaw project root that Claude Desktop should operate on.',
117
+ required: true,
118
+ default: projectRoot,
119
+ },
120
+ },
121
+ };
122
+ }
123
+ function resolveRuntimeRoot() {
124
+ return path.resolve(fileURLToPath(new URL('..', import.meta.url)));
125
+ }
126
+ function findPackageRoot(startDir) {
127
+ let current = startDir;
128
+ while (true) {
129
+ const candidate = path.join(current, 'package.json');
130
+ if (fs.existsSync(candidate)) {
131
+ return current;
132
+ }
133
+ const parent = path.dirname(current);
134
+ if (parent === current) {
135
+ throw new Error(`Could not locate package.json from ${startDir}`);
136
+ }
137
+ current = parent;
138
+ }
139
+ }
140
+ function readPackageMetadata(packageRoot) {
141
+ const packageJsonPath = path.join(packageRoot, 'package.json');
142
+ return JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
143
+ }
144
+ function resolveRuntimeDependencies(packageRoot) {
145
+ const metadata = readPackageMetadata(packageRoot);
146
+ return Object.keys(metadata.dependencies ?? {});
147
+ }
148
+ function packClaudeDesktopExtension(workspaceDir, outputFile) {
149
+ fs.rmSync(outputFile, { force: true });
150
+ const pythonCommand = resolveAvailableCommand(process.platform === 'win32'
151
+ ? ['python', 'py', 'python3']
152
+ : ['python3', 'python', 'py']);
153
+ if (pythonCommand) {
154
+ const script = [
155
+ 'import os, sys, zipfile',
156
+ 'src, dest = sys.argv[1], sys.argv[2]',
157
+ 'with zipfile.ZipFile(dest, "w", zipfile.ZIP_DEFLATED) as zf:',
158
+ ' for root, _, files in os.walk(src):',
159
+ ' for name in files:',
160
+ ' full = os.path.join(root, name)',
161
+ ' rel = os.path.relpath(full, src)',
162
+ ' zf.write(full, rel)',
163
+ ].join('; ');
164
+ const result = spawnSync(pythonCommand, ['-c', script, workspaceDir, outputFile], {
165
+ encoding: 'utf-8',
166
+ });
167
+ if (result.status === 0) {
168
+ return true;
169
+ }
170
+ }
171
+ if (process.platform === 'win32') {
172
+ const shell = resolveAvailableCommand(['pwsh', 'powershell']);
173
+ if (!shell) {
174
+ throw new Error('Could not find Python or PowerShell to create the Claude Desktop .mcpb archive.');
175
+ }
176
+ const command = `Compress-Archive -Path (Join-Path '${escapePowerShellPath(workspaceDir)}' '*') -DestinationPath '${escapePowerShellPath(outputFile)}' -Force`;
177
+ const result = spawnSync(shell, ['-NoProfile', '-Command', command], {
178
+ encoding: 'utf-8',
179
+ });
180
+ if (result.status === 0) {
181
+ return true;
182
+ }
183
+ }
184
+ const zipCommand = resolveAvailableCommand(['zip']);
185
+ if (zipCommand) {
186
+ const result = spawnSync(zipCommand, ['-qr', outputFile, '.'], {
187
+ cwd: workspaceDir,
188
+ encoding: 'utf-8',
189
+ });
190
+ if (result.status === 0) {
191
+ return true;
192
+ }
193
+ }
194
+ throw new Error('Failed to create a .mcpb archive. Install Python 3, PowerShell, or zip.');
195
+ }
196
+ function resolveAvailableCommand(candidates) {
197
+ for (const candidate of candidates) {
198
+ const result = spawnSync(candidate, ['--version'], { encoding: 'utf-8' });
199
+ if (result.status === 0) {
200
+ return candidate;
201
+ }
202
+ }
203
+ return undefined;
204
+ }
205
+ function escapePowerShellPath(value) {
206
+ return value.replace(/'/g, "''");
207
+ }
208
+ export function renderClaudeDesktopExtensionSummary(result) {
209
+ const lines = [
210
+ 'Claude Desktop extension scaffold ready.',
211
+ `Workspace: ${path.relative(process.cwd(), result.workspaceDir) || result.workspaceDir}`,
212
+ `Manifest: ${path.relative(process.cwd(), result.manifestPath) || result.manifestPath}`,
213
+ `Server entry: ${path.relative(process.cwd(), result.entryPointPath) || result.entryPointPath}`,
214
+ ];
215
+ if (result.packed) {
216
+ lines.push(`Package: ${path.relative(process.cwd(), result.outputFile) || result.outputFile}`);
217
+ lines.push('Install in Claude Desktop via Developer -> Extensions -> Install Extension.');
218
+ }
219
+ else {
220
+ lines.push('Archive packing skipped (--no-pack).');
221
+ }
222
+ return lines.join('\n');
223
+ }
224
+ //# sourceMappingURL=claude-desktop-extension.js.map
package/dist/core/ids.js CHANGED
@@ -11,6 +11,7 @@ const PREFIXES = {
11
11
  plan_items: 'pln',
12
12
  plan_steps: 'stp',
13
13
  instruction_entries: 'ins',
14
+ ai_surface_tasks: 'ast',
14
15
  };
15
16
  const ID_COUNTER_FILE = '.id-counter.json';
16
17
  function counterPath(cwd) {
package/dist/core/io.js CHANGED
@@ -30,6 +30,7 @@ const ENTITY_DIR_MAP = {
30
30
  'runtime': 'coordination/runtime',
31
31
  'runtime-hosts': 'coordination/runtime-hosts',
32
32
  'runtime-private': 'coordination/runtime-private',
33
+ 'surface-tasks': 'coordination/surface-tasks',
33
34
  // discovery/ — Project entity: what's available
34
35
  'bootstrap': 'discovery/bootstrap',
35
36
  'bootstrap/seeds': 'discovery/bootstrap/seeds',