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.
- package/README.md +16 -0
- package/dist/cli.js +44 -1
- package/dist/commands/claude-desktop-extension.js +18 -0
- package/dist/commands/doctor.js +12 -5
- package/dist/commands/init.js +15 -0
- package/dist/commands/list-surface-tasks.js +39 -0
- package/dist/commands/reconcile.js +138 -0
- package/dist/commands/setup.js +19 -0
- package/dist/commands/status.js +17 -12
- package/dist/commands/surface-task-resource.js +35 -0
- package/dist/commands/surface-task.js +57 -0
- package/dist/commands/update-surface-task.js +30 -0
- package/dist/core/agent-files.js +18 -18
- package/dist/core/ai-surface-inventory.js +321 -0
- package/dist/core/ai-surface-tasks.js +40 -0
- package/dist/core/claude-desktop-extension.js +224 -0
- package/dist/core/ids.js +1 -0
- package/dist/core/io.js +1 -0
- package/dist/core/machine-profile.js +7 -1
- package/dist/core/migration.js +3 -1
- package/dist/core/schema.js +25 -0
- package/dist/core/workspace-projects.js +115 -0
- package/docs/cli.md +131 -0
- package/docs/quickstart.md +37 -0
- package/package.json +1 -1
package/dist/core/agent-files.js
CHANGED
|
@@ -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
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',
|