sinapse-ai 7.3.3 → 7.4.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.
- package/.sinapse-ai/data/entity-registry.yaml +763 -765
- package/.sinapse-ai/development/templates/chrome-brain/knowledge-base/chrome-brain.md +161 -0
- package/.sinapse-ai/development/templates/chrome-brain/rules/chrome-brain-autoload.md +56 -0
- package/.sinapse-ai/development/templates/chrome-brain/scripts/chrome-brain-log.sh +67 -0
- package/.sinapse-ai/development/templates/chrome-brain/scripts/chrome-debug.sh +232 -0
- package/.sinapse-ai/development/templates/chrome-brain/scripts/chrome-ensure.sh +210 -0
- package/.sinapse-ai/development/templates/chrome-brain/squad-integrations/squad-animations.md +50 -0
- package/.sinapse-ai/development/templates/chrome-brain/squad-integrations/squad-brand.md +42 -0
- package/.sinapse-ai/development/templates/chrome-brain/squad-integrations/squad-claude.md +49 -0
- package/.sinapse-ai/development/templates/chrome-brain/squad-integrations/squad-cloning.md +50 -0
- package/.sinapse-ai/development/templates/chrome-brain/squad-integrations/squad-commercial.md +41 -0
- package/.sinapse-ai/development/templates/chrome-brain/squad-integrations/squad-content.md +45 -0
- package/.sinapse-ai/development/templates/chrome-brain/squad-integrations/squad-copy.md +44 -0
- package/.sinapse-ai/development/templates/chrome-brain/squad-integrations/squad-cybersecurity.md +42 -0
- package/.sinapse-ai/development/templates/chrome-brain/squad-integrations/squad-design.md +50 -0
- package/.sinapse-ai/development/templates/chrome-brain/squad-integrations/squad-growth.md +45 -0
- package/.sinapse-ai/development/templates/chrome-brain/squad-integrations/squad-paidmedia.md +47 -0
- package/.sinapse-ai/development/templates/chrome-brain/squad-integrations/squad-product.md +49 -0
- package/.sinapse-ai/development/templates/chrome-brain/squad-integrations/squad-research.md +41 -0
- package/.sinapse-ai/development/templates/chrome-brain/squad-integrations/squad-storytelling.md +41 -0
- package/.sinapse-ai/install-manifest.yaml +81 -5
- package/CHROME-BRAIN-INSTALL.md +93 -0
- package/README.md +28 -1
- package/bin/modules/chrome-brain-installer.js +757 -0
- package/bin/sinapse.js +18 -0
- package/install-chrome-brain.sh +1328 -0
- package/package.json +3 -1
- package/packages/sinapse-install/src/capabilities/chrome-brain.js +962 -0
- package/packages/sinapse-install/src/installer.js +60 -2
- package/sinapse/agents/sinapse-orqx.md +27 -0
|
@@ -0,0 +1,962 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chrome Brain — Cross-platform Chrome Browser Automation Installer
|
|
3
|
+
*
|
|
4
|
+
* Installs the Chrome Brain capability for SINAPSE:
|
|
5
|
+
* - Detects Chrome binary across macOS, Linux, Windows, WSL
|
|
6
|
+
* - Creates launcher/logger scripts in user bin directory
|
|
7
|
+
* - Merges PreToolUse/PostToolUse hooks into ~/.claude/settings.json
|
|
8
|
+
* - Adds Chrome DevTools MCP to ~/.claude.json
|
|
9
|
+
* - Copies KB and rule templates to ~/.sinapse/ structure
|
|
10
|
+
* - Validates the full installation
|
|
11
|
+
*
|
|
12
|
+
* @module capabilities/chrome-brain
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const os = require('os');
|
|
20
|
+
const { execSync } = require('child_process');
|
|
21
|
+
const { detectOS, OS_TYPE } = require('../os-detector');
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Constants
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
const DEBUG_PORT = 9222;
|
|
28
|
+
const DEBUG_PROFILE = path.join(os.homedir(), '.chrome-debug-profile');
|
|
29
|
+
const SINAPSE_DIR = path.join(os.homedir(), '.sinapse');
|
|
30
|
+
const CLAUDE_DIR = path.join(os.homedir(), '.claude');
|
|
31
|
+
const CLAUDE_JSON = path.join(os.homedir(), '.claude.json');
|
|
32
|
+
const CLAUDE_SETTINGS = path.join(CLAUDE_DIR, 'settings.json');
|
|
33
|
+
|
|
34
|
+
const SQUAD_NAMES = [
|
|
35
|
+
'squad-animations',
|
|
36
|
+
'squad-design',
|
|
37
|
+
'squad-cloning',
|
|
38
|
+
'squad-claude',
|
|
39
|
+
'squad-paidmedia',
|
|
40
|
+
'squad-growth',
|
|
41
|
+
'squad-content',
|
|
42
|
+
'squad-copy',
|
|
43
|
+
'squad-research',
|
|
44
|
+
'squad-cybersecurity',
|
|
45
|
+
'squad-commercial',
|
|
46
|
+
'squad-brand',
|
|
47
|
+
'squad-storytelling',
|
|
48
|
+
'squad-product',
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Logging helpers
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
let _quiet = false;
|
|
56
|
+
const LOG = {
|
|
57
|
+
ok: (msg) => { if (!_quiet) console.log(` \x1b[32m[OK]\x1b[0m ${msg}`); },
|
|
58
|
+
fail: (msg) => console.log(` \x1b[31m[FAIL]\x1b[0m ${msg}`),
|
|
59
|
+
warn: (msg) => console.log(` \x1b[33m[WARN]\x1b[0m ${msg}`),
|
|
60
|
+
info: (msg) => { if (!_quiet) console.log(` \x1b[36m[INFO]\x1b[0m ${msg}`); },
|
|
61
|
+
step: (msg) => { if (!_quiet) console.log(`\n\x1b[1m${msg}\x1b[0m`); },
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// Chrome Detection
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Detects the Chrome (or Chromium) binary path for the current platform.
|
|
70
|
+
* @returns {{ found: boolean, path: string|null, variant: string|null }}
|
|
71
|
+
*/
|
|
72
|
+
function detectChrome() {
|
|
73
|
+
const platform = process.platform;
|
|
74
|
+
const candidates = [];
|
|
75
|
+
|
|
76
|
+
if (platform === 'darwin') {
|
|
77
|
+
candidates.push({
|
|
78
|
+
path: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
79
|
+
variant: 'Google Chrome',
|
|
80
|
+
});
|
|
81
|
+
candidates.push({
|
|
82
|
+
path: '/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
83
|
+
variant: 'Chromium',
|
|
84
|
+
});
|
|
85
|
+
} else if (platform === 'win32') {
|
|
86
|
+
const pf = process.env.PROGRAMFILES || 'C:\\Program Files';
|
|
87
|
+
const pfx86 = process.env['PROGRAMFILES(X86)'] || 'C:\\Program Files (x86)';
|
|
88
|
+
const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
|
|
89
|
+
candidates.push({
|
|
90
|
+
path: path.join(pf, 'Google', 'Chrome', 'Application', 'chrome.exe'),
|
|
91
|
+
variant: 'Google Chrome',
|
|
92
|
+
});
|
|
93
|
+
candidates.push({
|
|
94
|
+
path: path.join(pfx86, 'Google', 'Chrome', 'Application', 'chrome.exe'),
|
|
95
|
+
variant: 'Google Chrome (x86)',
|
|
96
|
+
});
|
|
97
|
+
candidates.push({
|
|
98
|
+
path: path.join(localAppData, 'Google', 'Chrome', 'Application', 'chrome.exe'),
|
|
99
|
+
variant: 'Google Chrome (User)',
|
|
100
|
+
});
|
|
101
|
+
// Try 'where' command as fallback
|
|
102
|
+
try {
|
|
103
|
+
const found = execSync('where chrome.exe 2>NUL', { encoding: 'utf8' }).trim().split(/\r?\n/)[0];
|
|
104
|
+
if (found) {
|
|
105
|
+
candidates.push({ path: found, variant: 'Google Chrome (PATH)' });
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
// not in PATH
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
// Linux / WSL
|
|
112
|
+
candidates.push({ path: '/usr/bin/google-chrome', variant: 'Google Chrome' });
|
|
113
|
+
candidates.push({ path: '/usr/bin/google-chrome-stable', variant: 'Google Chrome Stable' });
|
|
114
|
+
candidates.push({ path: '/usr/bin/chromium-browser', variant: 'Chromium' });
|
|
115
|
+
candidates.push({ path: '/usr/bin/chromium', variant: 'Chromium' });
|
|
116
|
+
candidates.push({ path: '/snap/bin/chromium', variant: 'Chromium (Snap)' });
|
|
117
|
+
// Try 'which' as fallback
|
|
118
|
+
for (const bin of ['google-chrome', 'google-chrome-stable', 'chromium-browser', 'chromium']) {
|
|
119
|
+
try {
|
|
120
|
+
const found = execSync(`which ${bin} 2>/dev/null`, { encoding: 'utf8' }).trim();
|
|
121
|
+
if (found && !candidates.some((c) => c.path === found)) {
|
|
122
|
+
candidates.push({ path: found, variant: bin });
|
|
123
|
+
}
|
|
124
|
+
} catch {
|
|
125
|
+
// not found
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for (const candidate of candidates) {
|
|
131
|
+
try {
|
|
132
|
+
if (fs.existsSync(candidate.path)) {
|
|
133
|
+
return { found: true, path: candidate.path, variant: candidate.variant };
|
|
134
|
+
}
|
|
135
|
+
} catch {
|
|
136
|
+
// permission error — skip
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return { found: false, path: null, variant: null };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
// Script Generators
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Returns the bin directory for scripts based on OS.
|
|
149
|
+
* Unix: ~/.local/bin Windows: ~/.sinapse/bin
|
|
150
|
+
*/
|
|
151
|
+
function getBinDir(osInfo) {
|
|
152
|
+
if (osInfo.type === OS_TYPE.WINDOWS) {
|
|
153
|
+
return path.join(os.homedir(), '.sinapse', 'bin');
|
|
154
|
+
}
|
|
155
|
+
return path.join(os.homedir(), '.local', 'bin');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Generates the chrome-ensure script content.
|
|
160
|
+
*/
|
|
161
|
+
function generateChromeEnsure(chromePath, osInfo) {
|
|
162
|
+
if (osInfo.type === OS_TYPE.WINDOWS) {
|
|
163
|
+
// PowerShell script wrapped in a .cmd launcher
|
|
164
|
+
return `@echo off
|
|
165
|
+
powershell -NoProfile -ExecutionPolicy Bypass -Command ^
|
|
166
|
+
"$port = if ($args[0]) { $args[0] } else { ${DEBUG_PORT} };" ^
|
|
167
|
+
"$profile = '%USERPROFILE%\\.chrome-debug-profile';" ^
|
|
168
|
+
"$cdp = 'http://127.0.0.1:' + $port + '/json/version';" ^
|
|
169
|
+
"try { $r = Invoke-WebRequest -Uri $cdp -TimeoutSec 1 -UseBasicParsing -ErrorAction Stop; exit 0 } catch {};" ^
|
|
170
|
+
"$running = Get-NetTCPConnection -LocalPort $port -ErrorAction SilentlyContinue;" ^
|
|
171
|
+
"if (-not $running) {" ^
|
|
172
|
+
" Get-Process chrome -ErrorAction SilentlyContinue | Where-Object { $_.CommandLine -like '*chrome-debug-profile*' } | Stop-Process -Force -ErrorAction SilentlyContinue;" ^
|
|
173
|
+
" Start-Sleep -Seconds 1;" ^
|
|
174
|
+
"};" ^
|
|
175
|
+
"Start-Process '${chromePath.replace(/\\/g, '\\\\')}' -ArgumentList '--remote-debugging-port=' + $port, '--user-data-dir=' + $profile, '--no-first-run' -WindowStyle Hidden;" ^
|
|
176
|
+
"for ($i = 1; $i -le 20; $i++) {" ^
|
|
177
|
+
" Start-Sleep -Milliseconds 500;" ^
|
|
178
|
+
" try { $r = Invoke-WebRequest -Uri $cdp -TimeoutSec 1 -UseBasicParsing -ErrorAction Stop; exit 0 } catch {};" ^
|
|
179
|
+
"};" ^
|
|
180
|
+
"Write-Error 'BLOCKED: Chrome debug failed to start on port ' + $port + '. Run chrome-debug manually.';" ^
|
|
181
|
+
"exit 1"
|
|
182
|
+
`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Unix (macOS / Linux / WSL)
|
|
186
|
+
const portCheck = osInfo.type === OS_TYPE.MACOS
|
|
187
|
+
? 'lsof -iTCP:$PORT -sTCP:LISTEN'
|
|
188
|
+
: 'ss -tlnp | grep -q ":$PORT " || lsof -iTCP:$PORT -sTCP:LISTEN 2>/dev/null';
|
|
189
|
+
|
|
190
|
+
return `#!/bin/bash
|
|
191
|
+
PORT="\${1:-${DEBUG_PORT}}"
|
|
192
|
+
PROFILE="${DEBUG_PROFILE}"
|
|
193
|
+
CDP="http://127.0.0.1:$PORT/json/version"
|
|
194
|
+
if curl -sf "$CDP" -o /dev/null --max-time 1; then
|
|
195
|
+
exit 0
|
|
196
|
+
fi
|
|
197
|
+
if ! ${portCheck} &>/dev/null; then
|
|
198
|
+
pgrep -f "user-data-dir=$PROFILE" | xargs kill 2>/dev/null
|
|
199
|
+
sleep 1
|
|
200
|
+
fi
|
|
201
|
+
"${chromePath}" \\
|
|
202
|
+
--remote-debugging-port="$PORT" \\
|
|
203
|
+
--user-data-dir="$PROFILE" \\
|
|
204
|
+
--no-first-run \\
|
|
205
|
+
&>/dev/null &
|
|
206
|
+
for i in {1..20}; do
|
|
207
|
+
if curl -sf "$CDP" -o /dev/null --max-time 1; then
|
|
208
|
+
exit 0
|
|
209
|
+
fi
|
|
210
|
+
sleep 0.5
|
|
211
|
+
done
|
|
212
|
+
echo "BLOCKED: Chrome debug failed to start on port $PORT. Run 'chrome-debug' manually." >&2
|
|
213
|
+
exit 1
|
|
214
|
+
`;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Generates the chrome-debug script content.
|
|
219
|
+
*/
|
|
220
|
+
function generateChromeDebug(chromePath, osInfo) {
|
|
221
|
+
if (osInfo.type === OS_TYPE.WINDOWS) {
|
|
222
|
+
return `@echo off
|
|
223
|
+
powershell -NoProfile -ExecutionPolicy Bypass -Command ^
|
|
224
|
+
"$port = if ($args[0]) { $args[0] } else { ${DEBUG_PORT} };" ^
|
|
225
|
+
"$profile = '%USERPROFILE%\\.chrome-debug-profile';" ^
|
|
226
|
+
"try { $r = Invoke-WebRequest -Uri ('http://127.0.0.1:' + $port + '/json/version') -TimeoutSec 1 -UseBasicParsing -ErrorAction Stop; Write-Host 'Chrome debug already running on port' $port; exit 0 } catch {};" ^
|
|
227
|
+
"Write-Host 'Killing existing debug Chrome instances...';" ^
|
|
228
|
+
"Get-Process chrome -ErrorAction SilentlyContinue | Where-Object { $_.CommandLine -like '*chrome-debug-profile*' } | Stop-Process -Force -ErrorAction SilentlyContinue;" ^
|
|
229
|
+
"Start-Sleep -Seconds 2;" ^
|
|
230
|
+
"Write-Host 'Launching Chrome with remote debugging on port' $port '...';" ^
|
|
231
|
+
"Start-Process '${chromePath.replace(/\\/g, '\\\\')}' -ArgumentList '--remote-debugging-port=' + $port, '--user-data-dir=' + $profile, '--no-first-run' -WindowStyle Hidden;" ^
|
|
232
|
+
"for ($i = 1; $i -le 15; $i++) {" ^
|
|
233
|
+
" Start-Sleep -Seconds 1;" ^
|
|
234
|
+
" try { $r = Invoke-WebRequest -Uri ('http://127.0.0.1:' + $port + '/json/version') -TimeoutSec 1 -UseBasicParsing -ErrorAction Stop; Write-Host 'Chrome ready on port' $port; exit 0 } catch {};" ^
|
|
235
|
+
"};" ^
|
|
236
|
+
"Write-Error 'ERROR: Chrome failed to start with debugging';" ^
|
|
237
|
+
"exit 1"
|
|
238
|
+
`;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return `#!/bin/bash
|
|
242
|
+
PORT="\${1:-${DEBUG_PORT}}"
|
|
243
|
+
PROFILE="${DEBUG_PROFILE}"
|
|
244
|
+
if curl -s "http://127.0.0.1:$PORT/json/version" &>/dev/null; then
|
|
245
|
+
echo "Chrome debug already running on port $PORT"
|
|
246
|
+
exit 0
|
|
247
|
+
fi
|
|
248
|
+
echo "Killing existing debug Chrome instances..."
|
|
249
|
+
pgrep -f "user-data-dir=$PROFILE" | xargs kill 2>/dev/null
|
|
250
|
+
sleep 2
|
|
251
|
+
echo "Launching Chrome with remote debugging on port $PORT..."
|
|
252
|
+
"${chromePath}" \\
|
|
253
|
+
--remote-debugging-port="$PORT" \\
|
|
254
|
+
--user-data-dir="$PROFILE" \\
|
|
255
|
+
--no-first-run \\
|
|
256
|
+
&>/dev/null &
|
|
257
|
+
for i in {1..15}; do
|
|
258
|
+
if curl -s "http://127.0.0.1:$PORT/json/version" &>/dev/null; then
|
|
259
|
+
echo "Chrome ready on port $PORT"
|
|
260
|
+
exit 0
|
|
261
|
+
fi
|
|
262
|
+
sleep 1
|
|
263
|
+
done
|
|
264
|
+
echo "ERROR: Chrome failed to start with debugging"
|
|
265
|
+
exit 1
|
|
266
|
+
`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Generates the chrome-brain-log script content.
|
|
271
|
+
*/
|
|
272
|
+
function generateChromeBrainLog(osInfo) {
|
|
273
|
+
if (osInfo.type === OS_TYPE.WINDOWS) {
|
|
274
|
+
return `@echo off
|
|
275
|
+
powershell -NoProfile -ExecutionPolicy Bypass -Command ^
|
|
276
|
+
"$logDir = '%USERPROFILE%\\.chrome-brain';" ^
|
|
277
|
+
"$logFile = Join-Path $logDir ('session-' + (Get-Date -Format 'yyyyMMdd') + '.log');" ^
|
|
278
|
+
"$counterFile = Join-Path $logDir '.screenshot-count';" ^
|
|
279
|
+
"New-Item -ItemType Directory -Path $logDir -Force | Out-Null;" ^
|
|
280
|
+
"$toolName = if ($env:HOOK_TOOL_NAME) { $env:HOOK_TOOL_NAME } else { 'unknown' };" ^
|
|
281
|
+
"$ts = Get-Date -Format 'HH:mm:ss';" ^
|
|
282
|
+
"Add-Content -Path $logFile -Value ('$ts $toolName');" ^
|
|
283
|
+
"if ($toolName -match 'take_screenshot|take_snapshot') {" ^
|
|
284
|
+
" $count = 0;" ^
|
|
285
|
+
" if (Test-Path $counterFile) { $count = [int](Get-Content $counterFile) };" ^
|
|
286
|
+
" $count++;" ^
|
|
287
|
+
" Set-Content -Path $counterFile -Value $count;" ^
|
|
288
|
+
" if ($count -eq 12) { Write-Warning 'WARNING: 12 screenshots in this session. Consider saving state and rotating.' };" ^
|
|
289
|
+
" if ($count -ge 15) { Write-Error 'CRITICAL: 15+ screenshots. Session at risk of exceeding 20MB API limit. Save state NOW.' };" ^
|
|
290
|
+
"};" ^
|
|
291
|
+
"exit 0"
|
|
292
|
+
`;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return `#!/bin/bash
|
|
296
|
+
LOG_DIR="$HOME/.chrome-brain"
|
|
297
|
+
LOG_FILE="$LOG_DIR/session-$(date +%Y%m%d).log"
|
|
298
|
+
COUNTER_FILE="$LOG_DIR/.screenshot-count"
|
|
299
|
+
mkdir -p "$LOG_DIR"
|
|
300
|
+
TOOL_NAME="\${HOOK_TOOL_NAME:-unknown}"
|
|
301
|
+
TIMESTAMP=$(date +%H:%M:%S)
|
|
302
|
+
echo "$TIMESTAMP $TOOL_NAME" >> "$LOG_FILE"
|
|
303
|
+
if echo "$TOOL_NAME" | grep -qE "take_screenshot|take_snapshot"; then
|
|
304
|
+
COUNT=$(cat "$COUNTER_FILE" 2>/dev/null || echo 0)
|
|
305
|
+
COUNT=$((COUNT + 1))
|
|
306
|
+
echo "$COUNT" > "$COUNTER_FILE"
|
|
307
|
+
if [ "$COUNT" -eq 12 ]; then
|
|
308
|
+
echo "WARNING: 12 screenshots in this session. Consider saving state and rotating." >&2
|
|
309
|
+
elif [ "$COUNT" -ge 15 ]; then
|
|
310
|
+
echo "CRITICAL: 15+ screenshots. Session at risk of exceeding 20MB API limit. Save state NOW." >&2
|
|
311
|
+
fi
|
|
312
|
+
fi
|
|
313
|
+
exit 0
|
|
314
|
+
`;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ---------------------------------------------------------------------------
|
|
318
|
+
// JSON Merge Helpers
|
|
319
|
+
// ---------------------------------------------------------------------------
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Safely reads a JSON file. Returns empty object on failure.
|
|
323
|
+
*/
|
|
324
|
+
function readJsonSafe(filePath) {
|
|
325
|
+
try {
|
|
326
|
+
if (fs.existsSync(filePath)) {
|
|
327
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
328
|
+
return JSON.parse(raw);
|
|
329
|
+
}
|
|
330
|
+
} catch (err) {
|
|
331
|
+
LOG.warn(`Could not parse ${filePath}: ${err.message}`);
|
|
332
|
+
}
|
|
333
|
+
return {};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Writes a JSON file with pretty-printing.
|
|
338
|
+
*/
|
|
339
|
+
function writeJson(filePath, data) {
|
|
340
|
+
const dir = path.dirname(filePath);
|
|
341
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
342
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Merges Chrome Brain hooks into ~/.claude/settings.json.
|
|
347
|
+
* Preserves all existing hooks — only replaces entries with matching matchers.
|
|
348
|
+
*/
|
|
349
|
+
function mergeHooks() {
|
|
350
|
+
const newHooks = {
|
|
351
|
+
PreToolUse: [
|
|
352
|
+
{ matcher: 'mcp__chrome-devtools__*', hooks: [{ type: 'command', command: 'chrome-ensure' }] },
|
|
353
|
+
{ matcher: 'mcp__claude-in-chrome__*', hooks: [{ type: 'command', command: 'chrome-ensure' }] },
|
|
354
|
+
],
|
|
355
|
+
PostToolUse: [
|
|
356
|
+
{ matcher: 'mcp__chrome-devtools__*', hooks: [{ type: 'command', command: 'chrome-brain-log' }] },
|
|
357
|
+
{ matcher: 'mcp__claude-in-chrome__*', hooks: [{ type: 'command', command: 'chrome-brain-log' }] },
|
|
358
|
+
],
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const settings = readJsonSafe(CLAUDE_SETTINGS);
|
|
362
|
+
if (!settings.hooks) {
|
|
363
|
+
settings.hooks = {};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
for (const [hookType, hookList] of Object.entries(newHooks)) {
|
|
367
|
+
const existing = settings.hooks[hookType] || [];
|
|
368
|
+
const newMatchers = new Set(hookList.map((h) => h.matcher));
|
|
369
|
+
const filtered = existing.filter((e) => !newMatchers.has(e.matcher));
|
|
370
|
+
filtered.push(...hookList);
|
|
371
|
+
settings.hooks[hookType] = filtered;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
writeJson(CLAUDE_SETTINGS, settings);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Adds Chrome DevTools MCP server to ~/.claude.json.
|
|
379
|
+
*/
|
|
380
|
+
function mergeMcpConfig(osInfo) {
|
|
381
|
+
const config = readJsonSafe(CLAUDE_JSON);
|
|
382
|
+
if (!config.mcpServers) {
|
|
383
|
+
config.mcpServers = {};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (osInfo.type === OS_TYPE.WINDOWS) {
|
|
387
|
+
config.mcpServers['chrome-devtools'] = {
|
|
388
|
+
command: 'cmd',
|
|
389
|
+
args: ['/c', 'npx', '-y', 'chrome-devtools-mcp@latest', '--browser-url=http://127.0.0.1:9222'],
|
|
390
|
+
};
|
|
391
|
+
} else {
|
|
392
|
+
config.mcpServers['chrome-devtools'] = {
|
|
393
|
+
command: 'npx',
|
|
394
|
+
args: ['chrome-devtools-mcp@latest', '--browser-url=http://127.0.0.1:9222'],
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
writeJson(CLAUDE_JSON, config);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// ---------------------------------------------------------------------------
|
|
402
|
+
// Template / KB Content
|
|
403
|
+
// ---------------------------------------------------------------------------
|
|
404
|
+
|
|
405
|
+
function getAutoloadRuleContent() {
|
|
406
|
+
return `# Chrome Brain — Auto-Activation & Auto-Learning Rule
|
|
407
|
+
|
|
408
|
+
> CRITICAL: This capability auto-activates and auto-learns. No command needed.
|
|
409
|
+
|
|
410
|
+
## Auto-Activation
|
|
411
|
+
|
|
412
|
+
When the user's prompt matches ANY of these patterns, Chrome Brain is active:
|
|
413
|
+
|
|
414
|
+
- **Browser**: abrir/navegar/acessar site, URL, pagina, chrome, aba
|
|
415
|
+
- **Cloning**: clonar/replicar/copiar site, pagina, hero, layout, animacao
|
|
416
|
+
- **Forms**: preencher/cadastrar/signup/login formulario, form, conta
|
|
417
|
+
- **Audit**: auditar/analisar/inspecionar site, performance, Lighthouse
|
|
418
|
+
- **Scraping**: extrair/scrape/coletar dados, conteudo, HTML, CSS
|
|
419
|
+
- **Animation**: animacao 3D, Three.js, WebGL, shader, canvas
|
|
420
|
+
|
|
421
|
+
## Protocol
|
|
422
|
+
|
|
423
|
+
1. Chrome connection is guaranteed by PreToolUse hook (chrome-ensure)
|
|
424
|
+
2. Select tooling: CDP > dev-browser > claude-in-chrome
|
|
425
|
+
3. Execute with NSN Mode enabled (never say "I can't" — try 3+ alternatives)
|
|
426
|
+
4. Track screenshot count — max 15 per session
|
|
427
|
+
5. Handoff results to domain squad when applicable
|
|
428
|
+
|
|
429
|
+
## Auto-Learning — MANDATORY
|
|
430
|
+
|
|
431
|
+
After completing ANY browser automation task, evaluate:
|
|
432
|
+
|
|
433
|
+
1. **Did something unexpected happen?** (error, workaround, new pattern)
|
|
434
|
+
2. **Did NSN Mode activate?** (barrier encountered and resolved)
|
|
435
|
+
3. **Is the solution generalizable?** (useful for future sessions)
|
|
436
|
+
|
|
437
|
+
If YES to any:
|
|
438
|
+
- Append the learning to the Learnings Log section in:
|
|
439
|
+
~/.sinapse/sinapse/knowledge-base/chrome-brain.md
|
|
440
|
+
|
|
441
|
+
## Session Management
|
|
442
|
+
|
|
443
|
+
Track screenshots mentally. When approaching 12:
|
|
444
|
+
1. Save current state to handoff file
|
|
445
|
+
2. Suggest session rotation to user
|
|
446
|
+
3. Never exceed 15 screenshots in a single session
|
|
447
|
+
|
|
448
|
+
## Knowledge Base
|
|
449
|
+
|
|
450
|
+
Full reference: ~/.sinapse/sinapse/knowledge-base/chrome-brain.md
|
|
451
|
+
`;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function getMasterKbContent() {
|
|
455
|
+
return `# Chrome Brain — Browser Automation Capability
|
|
456
|
+
|
|
457
|
+
> Cross-squad capability que da a TODOS os agents do SINAPSE o poder de
|
|
458
|
+
> navegar, clonar, preencher, auditar e scrape qualquer site via Chrome real.
|
|
459
|
+
> Auto-ativado. Sem comando manual. NSN Mode sempre ligado.
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
## Arquitetura
|
|
464
|
+
|
|
465
|
+
\`\`\`
|
|
466
|
+
Chrome (porta 9222, perfil ~/.chrome-debug-profile)
|
|
467
|
+
+-- Chrome DevTools MCP (29 tools) — acoes rapidas, screenshots, Lighthouse
|
|
468
|
+
+-- dev-browser (Playwright) — scraping complexo, batch, headless
|
|
469
|
+
+-- claude-in-chrome (Extension) — fallback visual, coordenadas de tela
|
|
470
|
+
\`\`\`
|
|
471
|
+
|
|
472
|
+
**Prioridade de tooling:** CDP > dev-browser > claude-in-chrome
|
|
473
|
+
|
|
474
|
+
**Auto-launch:** Hook PreToolUse roda chrome-ensure antes de qualquer tool chrome.
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
## Decisao de Tooling
|
|
479
|
+
|
|
480
|
+
| Cenario | Ferramenta |
|
|
481
|
+
|---------|-----------|
|
|
482
|
+
| Click, fill, navegar | Chrome DevTools MCP |
|
|
483
|
+
| Screenshot/snapshot | Chrome DevTools MCP |
|
|
484
|
+
| Performance/Lighthouse | Chrome DevTools MCP |
|
|
485
|
+
| Network/Console | Chrome DevTools MCP |
|
|
486
|
+
| Scraping com logica JS | dev-browser evaluate() |
|
|
487
|
+
| Batch/loops | dev-browser |
|
|
488
|
+
| Headless | dev-browser --headless |
|
|
489
|
+
| Iframe cross-origin | claude-in-chrome ou CDP Input.dispatchMouseEvent |
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
## NSN Mode — Nunca Diga Nunca
|
|
494
|
+
|
|
495
|
+
Quando encontrar barreira:
|
|
496
|
+
1. Classificar — permission? technical? knowledge? external?
|
|
497
|
+
2. Buscar workaround via web search
|
|
498
|
+
3. Tentar alternativas — minimo 3 abordagens diferentes
|
|
499
|
+
4. Configurar e testar
|
|
500
|
+
5. Repetir (max 5 ciclos)
|
|
501
|
+
6. Escalar ao usuario com detalhes
|
|
502
|
+
|
|
503
|
+
---
|
|
504
|
+
|
|
505
|
+
## Session Management
|
|
506
|
+
|
|
507
|
+
- Max 15 screenshots por sessao
|
|
508
|
+
- Max 10 snapshots por sessao
|
|
509
|
+
- Preferir evaluate_script sobre take_snapshot
|
|
510
|
+
- Rotacionar sessao a cada ~12 screenshots
|
|
511
|
+
|
|
512
|
+
---
|
|
513
|
+
|
|
514
|
+
## Learnings Log
|
|
515
|
+
|
|
516
|
+
> Secao atualizada automaticamente quando NSN Mode resolve problemas novos.
|
|
517
|
+
|
|
518
|
+
`;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function getSquadIntegrationContent(squadName) {
|
|
522
|
+
const footer = `## Tools Disponiveis
|
|
523
|
+
|
|
524
|
+
1. **Chrome DevTools MCP** (29 tools) — acoes rapidas, screenshots, audit
|
|
525
|
+
2. **dev-browser** — scraping complexo, batch operations
|
|
526
|
+
3. **claude-in-chrome** — fallback visual
|
|
527
|
+
|
|
528
|
+
## Session Management
|
|
529
|
+
|
|
530
|
+
- Max 15 screenshots por sessao
|
|
531
|
+
- Salvar outputs em arquivo antes de acumular
|
|
532
|
+
- Rotacionar sessao a cada ~12 screenshots
|
|
533
|
+
|
|
534
|
+
## NSN Mode
|
|
535
|
+
|
|
536
|
+
Ativo. Se uma acao de browser falhar, tentar alternativas automaticamente.
|
|
537
|
+
|
|
538
|
+
## Referencia Completa
|
|
539
|
+
|
|
540
|
+
~/.sinapse/sinapse/knowledge-base/chrome-brain.md
|
|
541
|
+
`;
|
|
542
|
+
|
|
543
|
+
return `# Chrome Brain Integration — ${squadName}
|
|
544
|
+
|
|
545
|
+
> This squad has access to Chrome Brain for browser automation tasks.
|
|
546
|
+
> Auto-activated when browser interaction is needed.
|
|
547
|
+
|
|
548
|
+
## Tier: 2
|
|
549
|
+
|
|
550
|
+
## When to Activate
|
|
551
|
+
|
|
552
|
+
- User task requires browser interaction relevant to this squad's domain
|
|
553
|
+
- Data extraction from web sources
|
|
554
|
+
- Visual validation of deliverables in browser
|
|
555
|
+
- Automated form filling or navigation
|
|
556
|
+
|
|
557
|
+
## What This Squad Receives from Chrome Brain
|
|
558
|
+
|
|
559
|
+
- DOM snapshots and screenshots
|
|
560
|
+
- Extracted data (text, styles, assets)
|
|
561
|
+
- Performance and accessibility audits
|
|
562
|
+
- Network and console monitoring data
|
|
563
|
+
|
|
564
|
+
## What This Squad Sends to Chrome Brain
|
|
565
|
+
|
|
566
|
+
- URLs and targets for capture
|
|
567
|
+
- Custom extraction scripts
|
|
568
|
+
- Deliverables for visual validation
|
|
569
|
+
|
|
570
|
+
${footer}`;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// ---------------------------------------------------------------------------
|
|
574
|
+
// Core Install / Uninstall
|
|
575
|
+
// ---------------------------------------------------------------------------
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Full Chrome Brain installation.
|
|
579
|
+
*
|
|
580
|
+
* @param {Object} [options={}]
|
|
581
|
+
* @param {boolean} [options.dryRun=false] Log actions without writing files
|
|
582
|
+
* @param {boolean} [options.skipMcp=false] Skip MCP config merge
|
|
583
|
+
* @param {boolean} [options.skipHooks=false] Skip hook merge
|
|
584
|
+
* @param {boolean} [options.skipKb=false] Skip knowledge base copy
|
|
585
|
+
* @param {boolean} [options.skipScripts=false] Skip script creation
|
|
586
|
+
* @returns {{ success: boolean, chrome: object, errors: string[] }}
|
|
587
|
+
*/
|
|
588
|
+
function installChromeBrain(options = {}) {
|
|
589
|
+
const { dryRun = false, skipMcp = false, skipHooks = false, skipKb = false, skipScripts = false, quiet = false } = options;
|
|
590
|
+
_quiet = quiet;
|
|
591
|
+
const errors = [];
|
|
592
|
+
const osInfo = detectOS();
|
|
593
|
+
|
|
594
|
+
LOG.step('Chrome Brain Installer');
|
|
595
|
+
LOG.info(`OS: ${osInfo.type} | Platform: ${osInfo.platform} | Arch: ${osInfo.arch}`);
|
|
596
|
+
|
|
597
|
+
// --- Step 1: Detect Chrome ---
|
|
598
|
+
LOG.step('Step 1 — Detecting Chrome...');
|
|
599
|
+
const chrome = detectChrome();
|
|
600
|
+
if (!chrome.found) {
|
|
601
|
+
LOG.fail('Google Chrome not found. Install Chrome and retry.');
|
|
602
|
+
return { success: false, chrome, errors: ['Chrome not found'] };
|
|
603
|
+
}
|
|
604
|
+
LOG.ok(`${chrome.variant} at ${chrome.path}`);
|
|
605
|
+
|
|
606
|
+
// --- Step 2: Create scripts ---
|
|
607
|
+
if (!skipScripts) {
|
|
608
|
+
LOG.step('Step 2 — Creating launcher scripts...');
|
|
609
|
+
const binDir = getBinDir(osInfo);
|
|
610
|
+
const ext = osInfo.type === OS_TYPE.WINDOWS ? '.cmd' : '';
|
|
611
|
+
const scripts = {
|
|
612
|
+
[`chrome-ensure${ext}`]: generateChromeEnsure(chrome.path, osInfo),
|
|
613
|
+
[`chrome-debug${ext}`]: generateChromeDebug(chrome.path, osInfo),
|
|
614
|
+
[`chrome-brain-log${ext}`]: generateChromeBrainLog(osInfo),
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
try {
|
|
618
|
+
if (!dryRun) {
|
|
619
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
620
|
+
}
|
|
621
|
+
for (const [name, content] of Object.entries(scripts)) {
|
|
622
|
+
const filePath = path.join(binDir, name);
|
|
623
|
+
if (dryRun) {
|
|
624
|
+
LOG.info(`[DRY-RUN] Would create ${filePath}`);
|
|
625
|
+
} else {
|
|
626
|
+
fs.writeFileSync(filePath, content, 'utf8');
|
|
627
|
+
// Make executable on Unix
|
|
628
|
+
if (osInfo.type !== OS_TYPE.WINDOWS) {
|
|
629
|
+
fs.chmodSync(filePath, 0o755);
|
|
630
|
+
}
|
|
631
|
+
LOG.ok(`${name} created`);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Warn if binDir not in PATH
|
|
636
|
+
const pathDirs = (process.env.PATH || '').split(path.delimiter);
|
|
637
|
+
if (!pathDirs.some((d) => d === binDir || d === binDir + path.sep)) {
|
|
638
|
+
LOG.warn(`${binDir} may not be in your PATH. Add it to your shell profile.`);
|
|
639
|
+
}
|
|
640
|
+
} catch (err) {
|
|
641
|
+
const msg = `Failed to create scripts: ${err.message}`;
|
|
642
|
+
LOG.fail(msg);
|
|
643
|
+
errors.push(msg);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// --- Step 3: Merge hooks ---
|
|
648
|
+
if (!skipHooks) {
|
|
649
|
+
LOG.step('Step 3 — Merging hooks into ~/.claude/settings.json...');
|
|
650
|
+
try {
|
|
651
|
+
if (dryRun) {
|
|
652
|
+
LOG.info('[DRY-RUN] Would merge hooks into settings.json');
|
|
653
|
+
} else {
|
|
654
|
+
fs.mkdirSync(CLAUDE_DIR, { recursive: true });
|
|
655
|
+
mergeHooks();
|
|
656
|
+
LOG.ok('Hooks merged into ~/.claude/settings.json');
|
|
657
|
+
}
|
|
658
|
+
} catch (err) {
|
|
659
|
+
const msg = `Failed to merge hooks: ${err.message}`;
|
|
660
|
+
LOG.fail(msg);
|
|
661
|
+
errors.push(msg);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// --- Step 4: MCP config ---
|
|
666
|
+
if (!skipMcp) {
|
|
667
|
+
LOG.step('Step 4 — Adding Chrome DevTools MCP to ~/.claude.json...');
|
|
668
|
+
try {
|
|
669
|
+
if (dryRun) {
|
|
670
|
+
LOG.info('[DRY-RUN] Would merge MCP config');
|
|
671
|
+
} else {
|
|
672
|
+
mergeMcpConfig(osInfo);
|
|
673
|
+
LOG.ok('Chrome DevTools MCP added to ~/.claude.json');
|
|
674
|
+
}
|
|
675
|
+
} catch (err) {
|
|
676
|
+
const msg = `Failed to merge MCP config: ${err.message}`;
|
|
677
|
+
LOG.fail(msg);
|
|
678
|
+
errors.push(msg);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// --- Step 5: KB and rules ---
|
|
683
|
+
if (!skipKb) {
|
|
684
|
+
LOG.step('Step 5 — Installing knowledge base and rules...');
|
|
685
|
+
const kbFiles = [
|
|
686
|
+
{
|
|
687
|
+
path: path.join(SINAPSE_DIR, '.claude', 'rules', 'chrome-brain-autoload.md'),
|
|
688
|
+
content: getAutoloadRuleContent(),
|
|
689
|
+
label: 'chrome-brain-autoload.md rule',
|
|
690
|
+
},
|
|
691
|
+
{
|
|
692
|
+
path: path.join(SINAPSE_DIR, 'sinapse', 'knowledge-base', 'chrome-brain.md'),
|
|
693
|
+
content: getMasterKbContent(),
|
|
694
|
+
label: 'master KB chrome-brain.md',
|
|
695
|
+
},
|
|
696
|
+
];
|
|
697
|
+
|
|
698
|
+
// Squad integration files
|
|
699
|
+
for (const squad of SQUAD_NAMES) {
|
|
700
|
+
kbFiles.push({
|
|
701
|
+
path: path.join(SINAPSE_DIR, squad, 'knowledge-base', 'chrome-brain-integration.md'),
|
|
702
|
+
content: getSquadIntegrationContent(squad),
|
|
703
|
+
label: `${squad}/chrome-brain-integration.md`,
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
for (const file of kbFiles) {
|
|
708
|
+
try {
|
|
709
|
+
if (dryRun) {
|
|
710
|
+
LOG.info(`[DRY-RUN] Would create ${file.label}`);
|
|
711
|
+
} else {
|
|
712
|
+
fs.mkdirSync(path.dirname(file.path), { recursive: true });
|
|
713
|
+
fs.writeFileSync(file.path, file.content, 'utf8');
|
|
714
|
+
LOG.ok(file.label);
|
|
715
|
+
}
|
|
716
|
+
} catch (err) {
|
|
717
|
+
const msg = `Failed to create ${file.label}: ${err.message}`;
|
|
718
|
+
LOG.fail(msg);
|
|
719
|
+
errors.push(msg);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// --- Step 6: Validate ---
|
|
725
|
+
LOG.step('Step 6 — Validating installation...');
|
|
726
|
+
if (!dryRun) {
|
|
727
|
+
const status = getChromeBrainStatus();
|
|
728
|
+
const total = Object.values(status.components).filter((v) => v === true).length;
|
|
729
|
+
const count = Object.keys(status.components).length;
|
|
730
|
+
if (total === count) {
|
|
731
|
+
LOG.ok(`All ${count} components validated successfully`);
|
|
732
|
+
} else {
|
|
733
|
+
LOG.warn(`${total}/${count} components validated — check warnings above`);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
const success = errors.length === 0;
|
|
738
|
+
if (success) {
|
|
739
|
+
LOG.step('Chrome Brain installed successfully!');
|
|
740
|
+
} else {
|
|
741
|
+
LOG.step(`Chrome Brain installed with ${errors.length} error(s)`);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
return { success, chrome, errors };
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Removes the Chrome Brain capability.
|
|
749
|
+
*
|
|
750
|
+
* @param {Object} [options={}]
|
|
751
|
+
* @param {boolean} [options.dryRun=false] Log actions without deleting files
|
|
752
|
+
* @param {boolean} [options.keepKb=false] Preserve knowledge base files
|
|
753
|
+
* @returns {{ success: boolean, removed: string[], errors: string[] }}
|
|
754
|
+
*/
|
|
755
|
+
function uninstallChromeBrain(options = {}) {
|
|
756
|
+
const { dryRun = false, keepKb = false } = options;
|
|
757
|
+
const osInfo = detectOS();
|
|
758
|
+
const removed = [];
|
|
759
|
+
const errors = [];
|
|
760
|
+
|
|
761
|
+
LOG.step('Chrome Brain Uninstaller');
|
|
762
|
+
|
|
763
|
+
// --- Remove scripts ---
|
|
764
|
+
const binDir = getBinDir(osInfo);
|
|
765
|
+
const ext = osInfo.type === OS_TYPE.WINDOWS ? '.cmd' : '';
|
|
766
|
+
const scriptNames = [`chrome-ensure${ext}`, `chrome-debug${ext}`, `chrome-brain-log${ext}`];
|
|
767
|
+
|
|
768
|
+
for (const name of scriptNames) {
|
|
769
|
+
const filePath = path.join(binDir, name);
|
|
770
|
+
try {
|
|
771
|
+
if (fs.existsSync(filePath)) {
|
|
772
|
+
if (dryRun) {
|
|
773
|
+
LOG.info(`[DRY-RUN] Would remove ${filePath}`);
|
|
774
|
+
} else {
|
|
775
|
+
fs.unlinkSync(filePath);
|
|
776
|
+
LOG.ok(`Removed ${name}`);
|
|
777
|
+
}
|
|
778
|
+
removed.push(filePath);
|
|
779
|
+
}
|
|
780
|
+
} catch (err) {
|
|
781
|
+
errors.push(`Failed to remove ${name}: ${err.message}`);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// --- Remove hooks from settings.json ---
|
|
786
|
+
try {
|
|
787
|
+
const settings = readJsonSafe(CLAUDE_SETTINGS);
|
|
788
|
+
if (settings.hooks) {
|
|
789
|
+
const chromeMatchers = new Set([
|
|
790
|
+
'mcp__chrome-devtools__*',
|
|
791
|
+
'mcp__claude-in-chrome__*',
|
|
792
|
+
]);
|
|
793
|
+
let changed = false;
|
|
794
|
+
for (const hookType of ['PreToolUse', 'PostToolUse']) {
|
|
795
|
+
if (Array.isArray(settings.hooks[hookType])) {
|
|
796
|
+
const before = settings.hooks[hookType].length;
|
|
797
|
+
settings.hooks[hookType] = settings.hooks[hookType].filter(
|
|
798
|
+
(e) => !chromeMatchers.has(e.matcher),
|
|
799
|
+
);
|
|
800
|
+
if (settings.hooks[hookType].length < before) {
|
|
801
|
+
changed = true;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
if (changed) {
|
|
806
|
+
if (dryRun) {
|
|
807
|
+
LOG.info('[DRY-RUN] Would remove Chrome hooks from settings.json');
|
|
808
|
+
} else {
|
|
809
|
+
writeJson(CLAUDE_SETTINGS, settings);
|
|
810
|
+
LOG.ok('Removed Chrome hooks from settings.json');
|
|
811
|
+
}
|
|
812
|
+
removed.push(CLAUDE_SETTINGS);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
} catch (err) {
|
|
816
|
+
errors.push(`Failed to clean hooks: ${err.message}`);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// --- Remove MCP from claude.json ---
|
|
820
|
+
try {
|
|
821
|
+
const config = readJsonSafe(CLAUDE_JSON);
|
|
822
|
+
if (config.mcpServers && config.mcpServers['chrome-devtools']) {
|
|
823
|
+
if (dryRun) {
|
|
824
|
+
LOG.info('[DRY-RUN] Would remove chrome-devtools from ~/.claude.json');
|
|
825
|
+
} else {
|
|
826
|
+
delete config.mcpServers['chrome-devtools'];
|
|
827
|
+
writeJson(CLAUDE_JSON, config);
|
|
828
|
+
LOG.ok('Removed chrome-devtools MCP from ~/.claude.json');
|
|
829
|
+
}
|
|
830
|
+
removed.push(CLAUDE_JSON);
|
|
831
|
+
}
|
|
832
|
+
} catch (err) {
|
|
833
|
+
errors.push(`Failed to clean MCP config: ${err.message}`);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// --- Remove KB files ---
|
|
837
|
+
if (!keepKb) {
|
|
838
|
+
const kbPaths = [
|
|
839
|
+
path.join(SINAPSE_DIR, '.claude', 'rules', 'chrome-brain-autoload.md'),
|
|
840
|
+
path.join(SINAPSE_DIR, 'sinapse', 'knowledge-base', 'chrome-brain.md'),
|
|
841
|
+
];
|
|
842
|
+
for (const squad of SQUAD_NAMES) {
|
|
843
|
+
kbPaths.push(path.join(SINAPSE_DIR, squad, 'knowledge-base', 'chrome-brain-integration.md'));
|
|
844
|
+
}
|
|
845
|
+
for (const kbPath of kbPaths) {
|
|
846
|
+
try {
|
|
847
|
+
if (fs.existsSync(kbPath)) {
|
|
848
|
+
if (dryRun) {
|
|
849
|
+
LOG.info(`[DRY-RUN] Would remove ${path.basename(kbPath)}`);
|
|
850
|
+
} else {
|
|
851
|
+
fs.unlinkSync(kbPath);
|
|
852
|
+
LOG.ok(`Removed ${path.basename(kbPath)}`);
|
|
853
|
+
}
|
|
854
|
+
removed.push(kbPath);
|
|
855
|
+
}
|
|
856
|
+
} catch (err) {
|
|
857
|
+
errors.push(`Failed to remove ${kbPath}: ${err.message}`);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
const success = errors.length === 0;
|
|
863
|
+
LOG.step(success ? 'Chrome Brain uninstalled.' : `Uninstall completed with ${errors.length} error(s)`);
|
|
864
|
+
return { success, removed, errors };
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// ---------------------------------------------------------------------------
|
|
868
|
+
// Status
|
|
869
|
+
// ---------------------------------------------------------------------------
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Checks installation status of all Chrome Brain components.
|
|
873
|
+
*
|
|
874
|
+
* @returns {{
|
|
875
|
+
* installed: boolean,
|
|
876
|
+
* chrome: { found: boolean, path: string|null, variant: string|null },
|
|
877
|
+
* components: {
|
|
878
|
+
* chromeDetected: boolean,
|
|
879
|
+
* scriptsInstalled: boolean,
|
|
880
|
+
* hooksConfigured: boolean,
|
|
881
|
+
* mcpConfigured: boolean,
|
|
882
|
+
* ruleInstalled: boolean,
|
|
883
|
+
* masterKbInstalled: boolean,
|
|
884
|
+
* squadKbCount: number
|
|
885
|
+
* }
|
|
886
|
+
* }}
|
|
887
|
+
*/
|
|
888
|
+
function getChromeBrainStatus() {
|
|
889
|
+
const osInfo = detectOS();
|
|
890
|
+
const chrome = detectChrome();
|
|
891
|
+
const binDir = getBinDir(osInfo);
|
|
892
|
+
const ext = osInfo.type === OS_TYPE.WINDOWS ? '.cmd' : '';
|
|
893
|
+
|
|
894
|
+
// Check scripts
|
|
895
|
+
const scriptsInstalled = ['chrome-ensure', 'chrome-debug', 'chrome-brain-log']
|
|
896
|
+
.every((name) => fs.existsSync(path.join(binDir, `${name}${ext}`)));
|
|
897
|
+
|
|
898
|
+
// Check hooks
|
|
899
|
+
let hooksConfigured = false;
|
|
900
|
+
try {
|
|
901
|
+
const settings = readJsonSafe(CLAUDE_SETTINGS);
|
|
902
|
+
const pre = settings.hooks?.PreToolUse || [];
|
|
903
|
+
const post = settings.hooks?.PostToolUse || [];
|
|
904
|
+
const hasPre = pre.some((h) => h.matcher === 'mcp__chrome-devtools__*');
|
|
905
|
+
const hasPost = post.some((h) => h.matcher === 'mcp__chrome-devtools__*');
|
|
906
|
+
hooksConfigured = hasPre && hasPost;
|
|
907
|
+
} catch {
|
|
908
|
+
// not configured
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// Check MCP
|
|
912
|
+
let mcpConfigured = false;
|
|
913
|
+
try {
|
|
914
|
+
const config = readJsonSafe(CLAUDE_JSON);
|
|
915
|
+
mcpConfigured = !!config.mcpServers?.['chrome-devtools'];
|
|
916
|
+
} catch {
|
|
917
|
+
// not configured
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// Check rule
|
|
921
|
+
const ruleInstalled = fs.existsSync(
|
|
922
|
+
path.join(SINAPSE_DIR, '.claude', 'rules', 'chrome-brain-autoload.md'),
|
|
923
|
+
);
|
|
924
|
+
|
|
925
|
+
// Check master KB
|
|
926
|
+
const masterKbInstalled = fs.existsSync(
|
|
927
|
+
path.join(SINAPSE_DIR, 'sinapse', 'knowledge-base', 'chrome-brain.md'),
|
|
928
|
+
);
|
|
929
|
+
|
|
930
|
+
// Count squad KBs
|
|
931
|
+
let squadKbCount = 0;
|
|
932
|
+
for (const squad of SQUAD_NAMES) {
|
|
933
|
+
if (fs.existsSync(path.join(SINAPSE_DIR, squad, 'knowledge-base', 'chrome-brain-integration.md'))) {
|
|
934
|
+
squadKbCount++;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
const components = {
|
|
939
|
+
chromeDetected: chrome.found,
|
|
940
|
+
scriptsInstalled,
|
|
941
|
+
hooksConfigured,
|
|
942
|
+
mcpConfigured,
|
|
943
|
+
ruleInstalled,
|
|
944
|
+
masterKbInstalled,
|
|
945
|
+
squadKbComplete: squadKbCount === SQUAD_NAMES.length,
|
|
946
|
+
};
|
|
947
|
+
|
|
948
|
+
const installed = Object.values(components).every((v) => v === true);
|
|
949
|
+
|
|
950
|
+
return { installed, chrome, components, squadKbCount };
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// ---------------------------------------------------------------------------
|
|
954
|
+
// Exports
|
|
955
|
+
// ---------------------------------------------------------------------------
|
|
956
|
+
|
|
957
|
+
module.exports = {
|
|
958
|
+
installChromeBrain,
|
|
959
|
+
uninstallChromeBrain,
|
|
960
|
+
getChromeBrainStatus,
|
|
961
|
+
detectChrome,
|
|
962
|
+
};
|