brainclaw 0.19.12 → 0.20.0

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 (40) hide show
  1. package/README.md +42 -11
  2. package/dist/cli.js +55 -1
  3. package/dist/commands/claude-desktop-extension.js +18 -0
  4. package/dist/commands/context.js +3 -1
  5. package/dist/commands/doctor.js +12 -5
  6. package/dist/commands/export.js +44 -0
  7. package/dist/commands/init.js +22 -6
  8. package/dist/commands/list-surface-tasks.js +39 -0
  9. package/dist/commands/mcp.js +86 -5
  10. package/dist/commands/reconcile.js +138 -0
  11. package/dist/commands/setup.js +19 -0
  12. package/dist/commands/status.js +17 -12
  13. package/dist/commands/surface-task-resource.js +35 -0
  14. package/dist/commands/surface-task.js +57 -0
  15. package/dist/commands/uninstall.js +145 -0
  16. package/dist/commands/update-surface-task.js +30 -0
  17. package/dist/core/agent-capability.js +184 -0
  18. package/dist/core/agent-context.js +24 -6
  19. package/dist/core/agent-files.js +18 -18
  20. package/dist/core/ai-surface-inventory.js +321 -0
  21. package/dist/core/ai-surface-tasks.js +40 -0
  22. package/dist/core/bootstrap.js +177 -0
  23. package/dist/core/claude-desktop-extension.js +224 -0
  24. package/dist/core/context.js +47 -24
  25. package/dist/core/ids.js +1 -0
  26. package/dist/core/instruction-templates.js +308 -0
  27. package/dist/core/io.js +1 -0
  28. package/dist/core/machine-profile.js +7 -1
  29. package/dist/core/migration.js +3 -1
  30. package/dist/core/schema.js +34 -0
  31. package/dist/core/setup-flow.js +191 -0
  32. package/dist/core/setup-state.js +30 -1
  33. package/dist/core/store-resolution.js +58 -0
  34. package/dist/core/workspace-projects.js +115 -0
  35. package/docs/architecture/project-refs.md +305 -0
  36. package/docs/cli.md +133 -1
  37. package/docs/integrations/agents.md +102 -150
  38. package/docs/integrations/overview.md +71 -45
  39. package/docs/quickstart.md +44 -111
  40. package/package.json +1 -1
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Agent capability profiles — describes what integration surfaces each
3
+ * agent supports so brainclaw can adapt its instruction file content,
4
+ * integration depth, and pressure level accordingly.
5
+ *
6
+ * Three profile tiers drive instruction file templates:
7
+ * A (full) — MCP + hooks + auto-approve → lightweight instructions
8
+ * B (standard) — MCP, no hooks → directive instructions with top traps
9
+ * C (limited) — no MCP → rich static content (plans, traps, decisions)
10
+ */
11
+ const PROFILES = {
12
+ 'claude-code': {
13
+ name: 'claude-code',
14
+ hasMcp: true,
15
+ hasHooks: true,
16
+ hasAutoApprove: true,
17
+ hasSkills: true,
18
+ hasRules: true,
19
+ instructionFile: 'CLAUDE.md',
20
+ sharedInstructionFile: true,
21
+ mcpConfigScope: 'both',
22
+ templateTier: 'A',
23
+ },
24
+ cursor: {
25
+ name: 'cursor',
26
+ hasMcp: true,
27
+ hasHooks: false,
28
+ hasAutoApprove: false,
29
+ hasSkills: false,
30
+ hasRules: true,
31
+ instructionFile: '.cursor/rules/brainclaw.md',
32
+ sharedInstructionFile: false,
33
+ mcpConfigScope: 'machine',
34
+ templateTier: 'B',
35
+ },
36
+ windsurf: {
37
+ name: 'windsurf',
38
+ hasMcp: true,
39
+ hasHooks: false,
40
+ hasAutoApprove: false,
41
+ hasSkills: false,
42
+ hasRules: true,
43
+ instructionFile: '.windsurfrules',
44
+ sharedInstructionFile: true,
45
+ mcpConfigScope: 'machine',
46
+ templateTier: 'B',
47
+ },
48
+ cline: {
49
+ name: 'cline',
50
+ hasMcp: true,
51
+ hasHooks: false,
52
+ hasAutoApprove: true,
53
+ hasSkills: false,
54
+ hasRules: true,
55
+ instructionFile: '.clinerules/brainclaw.md',
56
+ sharedInstructionFile: false,
57
+ mcpConfigScope: 'project',
58
+ templateTier: 'B',
59
+ },
60
+ roo: {
61
+ name: 'roo',
62
+ hasMcp: true,
63
+ hasHooks: false,
64
+ hasAutoApprove: true,
65
+ hasSkills: false,
66
+ hasRules: true,
67
+ instructionFile: '.roo/rules/brainclaw.md',
68
+ sharedInstructionFile: false,
69
+ mcpConfigScope: 'project',
70
+ templateTier: 'B',
71
+ },
72
+ continue: {
73
+ name: 'continue',
74
+ hasMcp: true,
75
+ hasHooks: false,
76
+ hasAutoApprove: false,
77
+ hasSkills: false,
78
+ hasRules: true,
79
+ instructionFile: '.continue/rules/brainclaw.md',
80
+ sharedInstructionFile: false,
81
+ mcpConfigScope: 'both',
82
+ templateTier: 'B',
83
+ },
84
+ opencode: {
85
+ name: 'opencode',
86
+ hasMcp: true,
87
+ hasHooks: false,
88
+ hasAutoApprove: false,
89
+ hasSkills: false,
90
+ hasRules: true,
91
+ instructionFile: 'AGENTS.md',
92
+ sharedInstructionFile: true,
93
+ mcpConfigScope: 'project',
94
+ templateTier: 'B',
95
+ },
96
+ codex: {
97
+ name: 'codex',
98
+ hasMcp: true,
99
+ hasHooks: false,
100
+ hasAutoApprove: false,
101
+ hasSkills: false,
102
+ hasRules: true,
103
+ instructionFile: 'AGENTS.md',
104
+ sharedInstructionFile: true,
105
+ mcpConfigScope: 'machine',
106
+ templateTier: 'B',
107
+ },
108
+ antigravity: {
109
+ name: 'antigravity',
110
+ hasMcp: true,
111
+ hasHooks: false,
112
+ hasAutoApprove: false,
113
+ hasSkills: false,
114
+ hasRules: true,
115
+ instructionFile: 'GEMINI.md',
116
+ sharedInstructionFile: true,
117
+ mcpConfigScope: 'machine',
118
+ templateTier: 'B',
119
+ },
120
+ 'github-copilot': {
121
+ name: 'github-copilot',
122
+ hasMcp: false,
123
+ hasHooks: false,
124
+ hasAutoApprove: false,
125
+ hasSkills: true,
126
+ hasRules: true,
127
+ instructionFile: '.github/copilot-instructions.md',
128
+ sharedInstructionFile: true,
129
+ mcpConfigScope: 'none',
130
+ templateTier: 'C',
131
+ },
132
+ };
133
+ /**
134
+ * Get the capability profile for a known agent.
135
+ * Returns undefined for unknown agent names.
136
+ */
137
+ export function getAgentCapabilityProfile(name) {
138
+ return PROFILES[name];
139
+ }
140
+ /**
141
+ * Get all known agent capability profiles.
142
+ */
143
+ export function getAllAgentCapabilityProfiles() {
144
+ return Object.values(PROFILES);
145
+ }
146
+ /**
147
+ * Get all agent names that match a given template tier.
148
+ */
149
+ export function getAgentsByTier(tier) {
150
+ return Object.values(PROFILES).filter((p) => p.templateTier === tier);
151
+ }
152
+ /**
153
+ * Check if an agent name is a known brainclaw-supported agent.
154
+ */
155
+ export function isKnownAgent(name) {
156
+ return name in PROFILES;
157
+ }
158
+ /**
159
+ * Summarize which integration surfaces are available for a given agent.
160
+ * Useful for setup UI to explain what brainclaw will configure.
161
+ */
162
+ export function describeAgentSurfaces(name) {
163
+ const profile = getAgentCapabilityProfile(name);
164
+ if (!profile)
165
+ return [];
166
+ const surfaces = [];
167
+ if (profile.hasMcp) {
168
+ surfaces.push(`MCP server (${profile.mcpConfigScope})`);
169
+ }
170
+ if (profile.hasRules) {
171
+ surfaces.push(`Instruction file (${profile.instructionFile})`);
172
+ }
173
+ if (profile.hasAutoApprove) {
174
+ surfaces.push('Auto-approve MCP tools');
175
+ }
176
+ if (profile.hasHooks) {
177
+ surfaces.push('Session hooks (pre-prompt + stop)');
178
+ }
179
+ if (profile.hasSkills) {
180
+ surfaces.push('Slash command / skill');
181
+ }
182
+ return surfaces;
183
+ }
184
+ //# sourceMappingURL=agent-capability.js.map
@@ -60,12 +60,30 @@ function readAgentsMarkdown(cwd) {
60
60
  const raw = fs.readFileSync(filepath, 'utf-8');
61
61
  const lines = raw.split(/\r?\n/);
62
62
  const title = lines.find((line) => line.trim().startsWith('#'))?.replace(/^#+\s*/, '').trim();
63
- const rules = lines
64
- .map((line) => line.trim())
65
- .filter((line) => /^([-*]|\d+\.)\s+/.test(line))
66
- .map((line) => line.replace(/^([-*]|\d+\.)\s+/, '').trim())
67
- .filter(Boolean)
68
- .slice(0, MAX_AGENT_RULES);
63
+ // Only extract rules from actionable sections, not from descriptive sections
64
+ // like "why this matters" which contain explanatory bullets, not instructions.
65
+ const SKIP_SECTIONS = /why this matters|what it provides|what brainclaw/i;
66
+ let currentSection = '';
67
+ let skipSection = false;
68
+ const rules = [];
69
+ for (const line of lines) {
70
+ const trimmed = line.trim();
71
+ if (trimmed.startsWith('#')) {
72
+ currentSection = trimmed.replace(/^#+\s*/, '');
73
+ skipSection = SKIP_SECTIONS.test(currentSection);
74
+ continue;
75
+ }
76
+ if (skipSection)
77
+ continue;
78
+ if (/^([-*]|\d+\.)\s+/.test(trimmed)) {
79
+ const text = trimmed.replace(/^([-*]|\d+\.)\s+/, '').trim();
80
+ if (text) {
81
+ rules.push(text);
82
+ if (rules.length >= MAX_AGENT_RULES)
83
+ break;
84
+ }
85
+ }
86
+ }
69
87
  return {
70
88
  present: true,
71
89
  title,
@@ -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