sinapse-ai 1.14.0 → 1.15.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 +9 -11
- package/.sinapse-ai/development/templates/chrome-brain/rules/chrome-brain-autoload.md +4 -0
- package/.sinapse-ai/development/templates/chrome-brain/scripts/chrome-brain-log.cjs +78 -0
- package/.sinapse-ai/development/templates/chrome-brain/scripts/chrome-ensure.cjs +108 -0
- package/.sinapse-ai/install-manifest.yaml +22 -10
- package/.sinapse-ai/product/templates/statusline/agent-badges.json +862 -0
- package/.sinapse-ai/product/templates/statusline/statusline-script.js +36 -2
- package/.sinapse-ai/product/templates/statusline/track-agent.cjs +119 -9
- package/CHANGELOG.md +3 -6
- package/bin/commands/install.js +22 -44
- package/bin/lib/header.js +14 -10
- package/bin/lib/setup-statusline.js +12 -1
- package/bin/modules/chrome-brain-installer.js +124 -199
- package/package.json +1 -1
- package/scripts/gen-agent-badges.js +105 -0
|
@@ -23,7 +23,16 @@ const SCRIPTS_DIR = path.join(HOME, '.local', 'bin');
|
|
|
23
23
|
const CLAUDE_SETTINGS = path.join(HOME, '.claude', 'settings.json');
|
|
24
24
|
const CLAUDE_JSON = path.join(HOME, '.claude.json');
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
// Node scripts shipped today — cross-platform, run hidden (no console window),
|
|
27
|
+
// and never kill a healthy Chrome.
|
|
28
|
+
const NODE_SCRIPTS = ['chrome-ensure.cjs', 'chrome-brain-log.cjs'];
|
|
29
|
+
// Legacy bash scripts from older installs — proactively removed on (re)install
|
|
30
|
+
// and uninstall so they stop popping console windows on Windows.
|
|
31
|
+
const LEGACY_SCRIPTS = ['chrome-ensure', 'chrome-debug', 'chrome-brain-log'];
|
|
32
|
+
// Pinned MCP version (no `@latest` drift).
|
|
33
|
+
const MCP_CHROME_DEVTOOLS_VERSION = '1.4.0';
|
|
34
|
+
// Runtime config the Node scripts read (chrome path, port, profile).
|
|
35
|
+
const CHROME_BRAIN_CONFIG = path.join(SINAPSE_DIR, 'chrome-brain.json');
|
|
27
36
|
|
|
28
37
|
const SQUAD_INTEGRATIONS = [
|
|
29
38
|
'squad-animations', 'squad-design', 'squad-cloning', 'squad-claude',
|
|
@@ -177,165 +186,6 @@ function removeHooks(settingsPath, matchers) {
|
|
|
177
186
|
writeJson(settingsPath, settings);
|
|
178
187
|
}
|
|
179
188
|
|
|
180
|
-
// ============================================================================
|
|
181
|
-
// Script Generation (cross-platform)
|
|
182
|
-
// ============================================================================
|
|
183
|
-
|
|
184
|
-
function sanitizeChromePath(chromePath) {
|
|
185
|
-
// Validate Chrome path contains only safe characters to prevent shell injection
|
|
186
|
-
const sanitized = chromePath.replace(/\\/g, '/');
|
|
187
|
-
if (!/^[a-zA-Z0-9\s/\-_.:()[\]]+$/.test(sanitized)) {
|
|
188
|
-
throw new Error(`Unsafe Chrome path detected: ${chromePath}`);
|
|
189
|
-
}
|
|
190
|
-
return sanitized;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function generateChromeEnsure(chromePath, platform) {
|
|
194
|
-
const safePath = sanitizeChromePath(chromePath);
|
|
195
|
-
const chromeCmd = `"${safePath}"`;
|
|
196
|
-
|
|
197
|
-
// Platform-aware kill command: Windows Git Bash lacks pgrep, use taskkill
|
|
198
|
-
const killCmd = platform === 'windows'
|
|
199
|
-
? `# Windows: use taskkill instead of pgrep (not available in Git Bash)
|
|
200
|
-
if command -v taskkill &>/dev/null; then
|
|
201
|
-
tasklist /FI "IMAGENAME eq chrome.exe" /NH 2>/dev/null | grep -i "chrome-debug-profile" | awk '{print $2}' | while read pid; do taskkill /PID "$pid" /F 2>/dev/null; done
|
|
202
|
-
elif command -v pgrep &>/dev/null; then
|
|
203
|
-
pgrep -f "user-data-dir=$CHROME_DEBUG_PROFILE" 2>/dev/null | xargs kill 2>/dev/null || true
|
|
204
|
-
fi`
|
|
205
|
-
: 'pgrep -f "user-data-dir=$CHROME_DEBUG_PROFILE" 2>/dev/null | xargs kill 2>/dev/null || true';
|
|
206
|
-
|
|
207
|
-
return `#!/bin/bash
|
|
208
|
-
# Chrome Brain — chrome-ensure (auto-launch)
|
|
209
|
-
# Called by PreToolUse hook before any chrome-devtools or claude-in-chrome tool
|
|
210
|
-
# Cross-platform: macOS, Linux, Windows Git Bash
|
|
211
|
-
|
|
212
|
-
PORT="\${1:-9222}"
|
|
213
|
-
CHROME_DEBUG_PROFILE="$HOME/.chrome-debug-profile"
|
|
214
|
-
CDP="http://127.0.0.1:$PORT/json/version"
|
|
215
|
-
|
|
216
|
-
# Fast path: check if already running (~50ms)
|
|
217
|
-
if curl -sf "$CDP" -o /dev/null --max-time 1 2>/dev/null; then
|
|
218
|
-
exit 0
|
|
219
|
-
fi
|
|
220
|
-
|
|
221
|
-
# Kill stale debug profile instances only (never normal Chrome)
|
|
222
|
-
${killCmd}
|
|
223
|
-
sleep 1
|
|
224
|
-
|
|
225
|
-
# Launch Chrome with debug flags
|
|
226
|
-
${chromeCmd} \\
|
|
227
|
-
--remote-debugging-port="$PORT" \\
|
|
228
|
-
--user-data-dir="$CHROME_DEBUG_PROFILE" \\
|
|
229
|
-
--no-first-run \\
|
|
230
|
-
&>/dev/null &
|
|
231
|
-
|
|
232
|
-
# Wait for startup (max 10 seconds)
|
|
233
|
-
for i in $(seq 1 20); do
|
|
234
|
-
if curl -sf "$CDP" -o /dev/null --max-time 1 2>/dev/null; then
|
|
235
|
-
exit 0
|
|
236
|
-
fi
|
|
237
|
-
sleep 0.5
|
|
238
|
-
done
|
|
239
|
-
|
|
240
|
-
echo "BLOCKED: Chrome debug failed to start on port $PORT. Run 'chrome-debug' manually." >&2
|
|
241
|
-
exit 1
|
|
242
|
-
`;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function generateChromeDebug(chromePath, platform) {
|
|
246
|
-
const safePath = sanitizeChromePath(chromePath);
|
|
247
|
-
const chromeCmd = `"${safePath}"`;
|
|
248
|
-
|
|
249
|
-
// Platform-aware kill command: Windows Git Bash lacks pgrep, use taskkill
|
|
250
|
-
const killCmd = platform === 'windows'
|
|
251
|
-
? `# Windows: use taskkill instead of pgrep (not available in Git Bash)
|
|
252
|
-
if command -v taskkill &>/dev/null; then
|
|
253
|
-
tasklist /FI "IMAGENAME eq chrome.exe" /NH 2>/dev/null | grep -i "chrome-debug-profile" | awk '{print $2}' | while read pid; do taskkill /PID "$pid" /F 2>/dev/null; done
|
|
254
|
-
elif command -v pgrep &>/dev/null; then
|
|
255
|
-
pgrep -f "user-data-dir=$CHROME_DEBUG_PROFILE" 2>/dev/null | xargs kill 2>/dev/null || true
|
|
256
|
-
fi`
|
|
257
|
-
: 'pgrep -f "user-data-dir=$CHROME_DEBUG_PROFILE" 2>/dev/null | xargs kill 2>/dev/null || true';
|
|
258
|
-
|
|
259
|
-
return `#!/bin/bash
|
|
260
|
-
# Chrome Brain — chrome-debug (manual launch)
|
|
261
|
-
# User runs this directly if auto-launch doesn't work
|
|
262
|
-
|
|
263
|
-
PORT="\${1:-9222}"
|
|
264
|
-
CHROME_DEBUG_PROFILE="$HOME/.chrome-debug-profile"
|
|
265
|
-
|
|
266
|
-
# Check if already running
|
|
267
|
-
if curl -s "http://127.0.0.1:$PORT/json/version" &>/dev/null; then
|
|
268
|
-
echo "Chrome debug already running on port $PORT"
|
|
269
|
-
exit 0
|
|
270
|
-
fi
|
|
271
|
-
|
|
272
|
-
# Kill only debug profile instances
|
|
273
|
-
${killCmd}
|
|
274
|
-
sleep 2
|
|
275
|
-
|
|
276
|
-
echo "Launching Chrome with remote debugging on port $PORT..."
|
|
277
|
-
|
|
278
|
-
${chromeCmd} \\
|
|
279
|
-
--remote-debugging-port="$PORT" \\
|
|
280
|
-
--user-data-dir="$CHROME_DEBUG_PROFILE" \\
|
|
281
|
-
--no-first-run \\
|
|
282
|
-
&>/dev/null &
|
|
283
|
-
|
|
284
|
-
# Wait for startup
|
|
285
|
-
for i in $(seq 1 15); do
|
|
286
|
-
if curl -s "http://127.0.0.1:$PORT/json/version" &>/dev/null; then
|
|
287
|
-
echo "Chrome ready on port $PORT"
|
|
288
|
-
exit 0
|
|
289
|
-
fi
|
|
290
|
-
sleep 1
|
|
291
|
-
done
|
|
292
|
-
|
|
293
|
-
echo "ERROR: Chrome failed to start with debugging"
|
|
294
|
-
exit 1
|
|
295
|
-
`;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
function generateChromeBrainLog() {
|
|
299
|
-
return `#!/bin/bash
|
|
300
|
-
# Chrome Brain — chrome-brain-log (session logger)
|
|
301
|
-
# Called by PostToolUse hook after every chrome tool call
|
|
302
|
-
# Tracks usage and warns about screenshot limits
|
|
303
|
-
|
|
304
|
-
LOG_DIR="$HOME/.chrome-brain"
|
|
305
|
-
TODAY=$(date +%Y%m%d)
|
|
306
|
-
LOG_FILE="$LOG_DIR/session-$TODAY.log"
|
|
307
|
-
COUNTER_FILE="$LOG_DIR/.screenshot-count"
|
|
308
|
-
COUNTER_DATE_FILE="$LOG_DIR/.screenshot-date"
|
|
309
|
-
mkdir -p "$LOG_DIR"
|
|
310
|
-
|
|
311
|
-
# Reset counter daily
|
|
312
|
-
LAST_DATE=$(cat "$COUNTER_DATE_FILE" 2>/dev/null || echo "")
|
|
313
|
-
if [ "$LAST_DATE" != "$TODAY" ]; then
|
|
314
|
-
echo "0" > "$COUNTER_FILE"
|
|
315
|
-
echo "$TODAY" > "$COUNTER_DATE_FILE"
|
|
316
|
-
fi
|
|
317
|
-
|
|
318
|
-
TOOL_NAME="\${HOOK_TOOL_NAME:-unknown}"
|
|
319
|
-
TIMESTAMP=$(date +%H:%M:%S)
|
|
320
|
-
|
|
321
|
-
echo "$TIMESTAMP $TOOL_NAME" >> "$LOG_FILE"
|
|
322
|
-
|
|
323
|
-
if echo "$TOOL_NAME" | grep -qE "take_screenshot|take_snapshot"; then
|
|
324
|
-
COUNT=$(cat "$COUNTER_FILE" 2>/dev/null || echo 0)
|
|
325
|
-
COUNT=$((COUNT + 1))
|
|
326
|
-
echo "$COUNT" > "$COUNTER_FILE"
|
|
327
|
-
|
|
328
|
-
if [ "$COUNT" -eq 12 ]; then
|
|
329
|
-
echo "WARNING: 12 screenshots in this session. Consider saving state and rotating." >&2
|
|
330
|
-
elif [ "$COUNT" -ge 15 ]; then
|
|
331
|
-
echo "CRITICAL: 15+ screenshots. Session at risk of exceeding 20MB API limit. Save state NOW." >&2
|
|
332
|
-
fi
|
|
333
|
-
fi
|
|
334
|
-
|
|
335
|
-
exit 0
|
|
336
|
-
`;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
189
|
// ============================================================================
|
|
340
190
|
// Install
|
|
341
191
|
// ============================================================================
|
|
@@ -349,57 +199,104 @@ function installScripts(chromePath, platform) {
|
|
|
349
199
|
|
|
350
200
|
fs.mkdirSync(scriptsDir, { recursive: true });
|
|
351
201
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
202
|
+
// Remove legacy extensionless bash scripts from older installs — on Windows
|
|
203
|
+
// they spawned a console window and could not run reliably.
|
|
204
|
+
for (const dir of [scriptsDir, SCRIPTS_DIR, path.join(SINAPSE_DIR, 'bin')]) {
|
|
205
|
+
for (const name of LEGACY_SCRIPTS) {
|
|
206
|
+
try {
|
|
207
|
+
const p = path.join(dir, name);
|
|
208
|
+
if (fs.existsSync(p)) fs.unlinkSync(p);
|
|
209
|
+
} catch { /* best-effort */ }
|
|
210
|
+
}
|
|
211
|
+
}
|
|
357
212
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
213
|
+
// Copy the cross-platform Node scripts from the template package.
|
|
214
|
+
const templateDir = findTemplateDir();
|
|
215
|
+
const srcDir = templateDir ? path.join(templateDir, 'scripts') : null;
|
|
216
|
+
for (const name of NODE_SCRIPTS) {
|
|
217
|
+
const dest = path.join(scriptsDir, name);
|
|
218
|
+
const src = srcDir ? path.join(srcDir, name) : null;
|
|
219
|
+
if (src && fs.existsSync(src)) {
|
|
220
|
+
fs.copyFileSync(src, dest);
|
|
221
|
+
try { fs.chmodSync(dest, 0o755); } catch { /* no-op on Windows */ }
|
|
222
|
+
ok(`${name} -> ${dest}`);
|
|
223
|
+
} else {
|
|
224
|
+
warn(`${name} template not found — skipped`);
|
|
225
|
+
}
|
|
365
226
|
}
|
|
366
227
|
|
|
367
|
-
//
|
|
228
|
+
// Write the runtime config the Node scripts read.
|
|
229
|
+
const config = {
|
|
230
|
+
chromePath,
|
|
231
|
+
port: 9222,
|
|
232
|
+
profile: path.join(HOME, '.chrome-debug-profile'),
|
|
233
|
+
};
|
|
234
|
+
writeJson(CHROME_BRAIN_CONFIG, config);
|
|
235
|
+
ok(`config -> ${CHROME_BRAIN_CONFIG}`);
|
|
368
236
|
|
|
369
237
|
return scriptsDir;
|
|
370
238
|
}
|
|
371
239
|
|
|
240
|
+
// Remove every legacy / duplicate Chrome Brain hook before re-adding the clean
|
|
241
|
+
// canonical set. Self-healing: fixes installs that accumulated a SessionStart
|
|
242
|
+
// auto-launch, extensionless bash commands, or duplicate regex matchers.
|
|
243
|
+
function cleanLegacyChromeHooks(settingsPath) {
|
|
244
|
+
const settings = readJson(settingsPath);
|
|
245
|
+
if (!settings.hooks) return;
|
|
246
|
+
const isChromeCmd = (cmd) => /chrome-ensure|chrome-brain-log/i.test(cmd || '');
|
|
247
|
+
const isChromeMatcher = (m) => /chrome-devtools|claude-in-chrome|dev-browser/i.test(m || '');
|
|
248
|
+
|
|
249
|
+
for (const type of Object.keys(settings.hooks)) {
|
|
250
|
+
let arr = settings.hooks[type] || [];
|
|
251
|
+
if (type === 'SessionStart') {
|
|
252
|
+
// Strip ONLY chrome commands from SessionStart; keep co-located hooks.
|
|
253
|
+
arr = arr
|
|
254
|
+
.map((e) => {
|
|
255
|
+
if (Array.isArray(e.hooks)) e.hooks = e.hooks.filter((h) => !isChromeCmd(h && h.command));
|
|
256
|
+
return e;
|
|
257
|
+
})
|
|
258
|
+
.filter((e) => Array.isArray(e.hooks) && e.hooks.length > 0);
|
|
259
|
+
} else {
|
|
260
|
+
arr = arr.filter((e) => {
|
|
261
|
+
const cmd = e && e.hooks && e.hooks[0] && e.hooks[0].command;
|
|
262
|
+
if (isChromeCmd(cmd)) return false;
|
|
263
|
+
if ((type === 'PreToolUse' || type === 'PostToolUse') && isChromeMatcher(e.matcher)) return false;
|
|
264
|
+
return true;
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
if (arr.length === 0) delete settings.hooks[type];
|
|
268
|
+
else settings.hooks[type] = arr;
|
|
269
|
+
}
|
|
270
|
+
if (settings.hooks && Object.keys(settings.hooks).length === 0) delete settings.hooks;
|
|
271
|
+
writeJson(settingsPath, settings);
|
|
272
|
+
}
|
|
273
|
+
|
|
372
274
|
function installHooks() {
|
|
373
275
|
step('Merging hooks into ~/.claude/settings.json...');
|
|
374
276
|
|
|
277
|
+
// 1) Clean any legacy / duplicate chrome hooks first (idempotent self-heal).
|
|
278
|
+
try { cleanLegacyChromeHooks(CLAUDE_SETTINGS); } catch { /* best-effort */ }
|
|
279
|
+
|
|
375
280
|
const binDir = detectPlatform() === 'windows'
|
|
376
281
|
? path.join(SINAPSE_DIR, 'bin')
|
|
377
282
|
: SCRIPTS_DIR;
|
|
378
|
-
const
|
|
379
|
-
const
|
|
283
|
+
const ensure = path.join(binDir, 'chrome-ensure.cjs').replace(/\\/g, '/');
|
|
284
|
+
const log = path.join(binDir, 'chrome-brain-log.cjs').replace(/\\/g, '/');
|
|
285
|
+
const nodeCmd = (p) => `node "${p}"`;
|
|
286
|
+
const matchers = ['mcp__chrome-devtools__*', 'mcp__claude-in-chrome__*', 'mcp__dev-browser__*'];
|
|
287
|
+
|
|
288
|
+
// NO SessionStart hook on purpose — launching Chrome at every session start
|
|
289
|
+
// pops a window unprompted. The lazy PreToolUse hook below runs chrome-ensure
|
|
290
|
+
// only right before a browser tool call, so Chrome comes up exactly when (and
|
|
291
|
+
// only when) it is needed.
|
|
380
292
|
const hookDefs = {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
// The lazy PreToolUse hook below already runs chrome-ensure right before any
|
|
384
|
-
// browser tool call, so Chrome is guaranteed up exactly when (and only when)
|
|
385
|
-
// it's needed — matching the documented design
|
|
386
|
-
// (templates/chrome-brain/rules/chrome-brain-autoload.md:
|
|
387
|
-
// "Chrome connection is guaranteed by PreToolUse hook").
|
|
388
|
-
PreToolUse: [
|
|
389
|
-
{ matcher: 'mcp__chrome-devtools__*', hooks: [{ type: 'command', command: ensureCmd }] },
|
|
390
|
-
{ matcher: 'mcp__claude-in-chrome__*', hooks: [{ type: 'command', command: ensureCmd }] },
|
|
391
|
-
{ matcher: 'mcp__dev-browser__*', hooks: [{ type: 'command', command: ensureCmd }] },
|
|
392
|
-
],
|
|
393
|
-
PostToolUse: [
|
|
394
|
-
{ matcher: 'mcp__chrome-devtools__*', hooks: [{ type: 'command', command: logCmd }] },
|
|
395
|
-
{ matcher: 'mcp__claude-in-chrome__*', hooks: [{ type: 'command', command: logCmd }] },
|
|
396
|
-
{ matcher: 'mcp__dev-browser__*', hooks: [{ type: 'command', command: logCmd }] },
|
|
397
|
-
],
|
|
293
|
+
PreToolUse: matchers.map((m) => ({ matcher: m, hooks: [{ type: 'command', command: nodeCmd(ensure) }] })),
|
|
294
|
+
PostToolUse: matchers.map((m) => ({ matcher: m, hooks: [{ type: 'command', command: nodeCmd(log) }] })),
|
|
398
295
|
};
|
|
399
296
|
|
|
400
297
|
try {
|
|
401
298
|
mergeHooks(CLAUDE_SETTINGS, hookDefs);
|
|
402
|
-
ok('Hooks merged
|
|
299
|
+
ok('Hooks merged (lazy PreToolUse + PostToolUse, no SessionStart)');
|
|
403
300
|
} catch (error) {
|
|
404
301
|
fail(`Failed to merge hooks: ${error.message}`);
|
|
405
302
|
}
|
|
@@ -411,16 +308,17 @@ function installMcp(platform) {
|
|
|
411
308
|
const config = readJson(CLAUDE_JSON);
|
|
412
309
|
if (!config.mcpServers) config.mcpServers = {};
|
|
413
310
|
|
|
414
|
-
// --- Chrome DevTools MCP ---
|
|
311
|
+
// --- Chrome DevTools MCP (pinned version — no @latest drift) ---
|
|
312
|
+
const cdpMcp = `chrome-devtools-mcp@${MCP_CHROME_DEVTOOLS_VERSION}`;
|
|
415
313
|
if (platform === 'windows') {
|
|
416
314
|
config.mcpServers['chrome-devtools'] = {
|
|
417
315
|
command: 'cmd',
|
|
418
|
-
args: ['/c', 'npx', '-y',
|
|
316
|
+
args: ['/c', 'npx', '-y', cdpMcp, '--browser-url=http://127.0.0.1:9222'],
|
|
419
317
|
};
|
|
420
318
|
} else {
|
|
421
319
|
config.mcpServers['chrome-devtools'] = {
|
|
422
320
|
command: 'npx',
|
|
423
|
-
args: ['
|
|
321
|
+
args: ['-y', cdpMcp, '--browser-url=http://127.0.0.1:9222'],
|
|
424
322
|
};
|
|
425
323
|
}
|
|
426
324
|
|
|
@@ -600,9 +498,9 @@ function uninstallChromeBrain() {
|
|
|
600
498
|
|
|
601
499
|
let removed = 0;
|
|
602
500
|
|
|
603
|
-
// Remove scripts
|
|
501
|
+
// Remove scripts (current Node + any legacy bash) + runtime config
|
|
604
502
|
step('Removing scripts...');
|
|
605
|
-
for (const name of
|
|
503
|
+
for (const name of [...NODE_SCRIPTS, ...LEGACY_SCRIPTS]) {
|
|
606
504
|
for (const dir of [SCRIPTS_DIR, path.join(SINAPSE_DIR, 'bin')]) {
|
|
607
505
|
const scriptPath = path.join(dir, name);
|
|
608
506
|
if (fs.existsSync(scriptPath)) {
|
|
@@ -612,6 +510,13 @@ function uninstallChromeBrain() {
|
|
|
612
510
|
}
|
|
613
511
|
}
|
|
614
512
|
}
|
|
513
|
+
try {
|
|
514
|
+
if (fs.existsSync(CHROME_BRAIN_CONFIG)) {
|
|
515
|
+
fs.unlinkSync(CHROME_BRAIN_CONFIG);
|
|
516
|
+
ok(`Removed ${CHROME_BRAIN_CONFIG}`);
|
|
517
|
+
removed++;
|
|
518
|
+
}
|
|
519
|
+
} catch { /* best-effort */ }
|
|
615
520
|
|
|
616
521
|
// Remove hooks
|
|
617
522
|
step('Removing hooks from settings...');
|
|
@@ -696,7 +601,7 @@ function getChromeBrainStatus() {
|
|
|
696
601
|
}
|
|
697
602
|
|
|
698
603
|
// Scripts
|
|
699
|
-
for (const name of
|
|
604
|
+
for (const name of NODE_SCRIPTS) {
|
|
700
605
|
total++;
|
|
701
606
|
const scriptPath = path.join(SCRIPTS_DIR, name);
|
|
702
607
|
const altPath = path.join(SINAPSE_DIR, 'bin', name);
|
|
@@ -886,12 +791,32 @@ async function runChromeBrain(subArgs) {
|
|
|
886
791
|
getChromeBrainStatus();
|
|
887
792
|
break;
|
|
888
793
|
|
|
794
|
+
case 'login': {
|
|
795
|
+
// Open the fixed-profile debug Chrome so the user logs into all accounts
|
|
796
|
+
// ONCE. The profile persists, so this never needs to be repeated.
|
|
797
|
+
const binDir = detectPlatform() === 'windows' ? path.join(SINAPSE_DIR, 'bin') : SCRIPTS_DIR;
|
|
798
|
+
const ensure = path.join(binDir, 'chrome-ensure.cjs');
|
|
799
|
+
console.log(`\n${bold(cyan('Chrome Brain — Login'))}`);
|
|
800
|
+
if (!fs.existsSync(ensure)) {
|
|
801
|
+
fail('chrome-ensure.cjs nao encontrado — rode `sinapse chrome-brain install` primeiro.');
|
|
802
|
+
break;
|
|
803
|
+
}
|
|
804
|
+
console.log(' Abrindo a janela fixa do Chrome Brain. Logue em todas as suas contas uma');
|
|
805
|
+
console.log(' vez — o perfil persiste, voce nao precisa logar de novo.\n');
|
|
806
|
+
const { spawn } = require('child_process');
|
|
807
|
+
const child = spawn(process.execPath, [ensure, '--visible'], { detached: true, stdio: 'ignore', windowsHide: true });
|
|
808
|
+
child.unref();
|
|
809
|
+
ok('Janela aberta (ou ja estava aberta).');
|
|
810
|
+
break;
|
|
811
|
+
}
|
|
812
|
+
|
|
889
813
|
default:
|
|
890
814
|
console.log(`
|
|
891
815
|
${bold('sinapse chrome-brain')} — Browser Automation Capability
|
|
892
816
|
|
|
893
817
|
${bold('USAGE:')}
|
|
894
818
|
sinapse chrome-brain install Install Chrome Brain (scripts, hooks, MCP, KB)
|
|
819
|
+
sinapse chrome-brain login Open the fixed window to log into your accounts once
|
|
895
820
|
sinapse chrome-brain uninstall Remove Chrome Brain completely
|
|
896
821
|
sinapse chrome-brain status Check installation status
|
|
897
822
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* gen-agent-badges — regenerates the agent badge map (id -> {emoji, area, name}).
|
|
6
|
+
*
|
|
7
|
+
* Output: .sinapse-ai/product/templates/statusline/agent-badges.json
|
|
8
|
+
* (installed to ~/.claude/agent-badges.json by bin/lib/setup-statusline.js;
|
|
9
|
+
* read by the track-agent.cjs hook to inject the badge and by the statusline
|
|
10
|
+
* to render it).
|
|
11
|
+
*
|
|
12
|
+
* Deterministic. No Invention — every field is read from a real definition:
|
|
13
|
+
* - Framework (núcleo) agents: .sinapse-ai/manifests/agents.csv (id, name, icon).
|
|
14
|
+
* - Squad agents: squads/<squad>/agents/<id>.md (4 supported formats below).
|
|
15
|
+
* Keys are the bare id for núcleo agents and "<squad>/<id>" for squad agents.
|
|
16
|
+
* The badge name is always the FIRST word of the agent's name.
|
|
17
|
+
*
|
|
18
|
+
* Re-run whenever agents are added / removed / renamed, then commit the JSON +
|
|
19
|
+
* `npm run generate:manifest` (the install manifest hashes the shipped file).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const fs = require('fs');
|
|
23
|
+
const path = require('path');
|
|
24
|
+
|
|
25
|
+
const REPO = path.resolve(__dirname, '..');
|
|
26
|
+
const SQUADS_DIR = path.join(REPO, 'squads');
|
|
27
|
+
const MANIFEST = path.join(REPO, '.sinapse-ai', 'manifests', 'agents.csv');
|
|
28
|
+
const OUT = path.join(REPO, '.sinapse-ai', 'product', 'templates', 'statusline', 'agent-badges.json');
|
|
29
|
+
|
|
30
|
+
const SQUAD_AREA = {
|
|
31
|
+
'squad-brand': 'BRAND', 'squad-copy': 'COPY', 'squad-design': 'DESIGN',
|
|
32
|
+
'squad-content': 'CONTENT', 'squad-growth': 'GROWTH', 'squad-paidmedia': 'ADS',
|
|
33
|
+
'squad-research': 'RESEARCH', 'squad-commercial': 'VENDAS', 'squad-finance': 'FINANCE',
|
|
34
|
+
'squad-cybersecurity': 'CYBER', 'squad-animations': 'MOTION', 'squad-storytelling': 'STORY',
|
|
35
|
+
'squad-cloning': 'CLONE', 'squad-council': 'COUNCIL', 'squad-courses': 'COURSES',
|
|
36
|
+
'squad-product': 'PRODUCT', 'claude-code-mastery': 'CLAUDE',
|
|
37
|
+
};
|
|
38
|
+
const FW_AREA = {
|
|
39
|
+
'snps-orqx': 'ORQX', 'developer': 'DEV', 'architect': 'ARQ', 'data-engineer': 'DATA',
|
|
40
|
+
'analyst': 'ANALISE', 'quality-gate': 'QA', 'devops': 'OPS', 'product-lead': 'PO',
|
|
41
|
+
'project-lead': 'PM', 'sprint-lead': 'SM', 'ux-design-expert': 'UX', 'squad-creator': 'CREATOR',
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const clean = (s) => s.replace(/^['"`|]+|['"`|]+$/g, '').trim();
|
|
45
|
+
const titleCase = (slug) => slug.split('-').filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
|
|
46
|
+
const firstEmoji = (line) => { const m = (line || '').match(/\p{Extended_Pictographic}️?/u); return m ? m[0] : null; };
|
|
47
|
+
|
|
48
|
+
// Tries 3 formats per label: YAML (key: v), MD-list (**Label:** v), MD-table (| **Label** | v |).
|
|
49
|
+
function extract(text, labels) {
|
|
50
|
+
for (const lbl of labels) {
|
|
51
|
+
let m = text.match(new RegExp('^\\s*' + lbl + ':\\s*(.+)$', 'im'));
|
|
52
|
+
if (m && clean(m[1])) return clean(m[1]);
|
|
53
|
+
m = text.match(new RegExp('\\*\\*' + lbl + ':\\*\\*\\s*(.+)$', 'im'));
|
|
54
|
+
if (m && clean(m[1])) return clean(m[1]);
|
|
55
|
+
m = text.match(new RegExp('\\|\\s*\\*\\*' + lbl + '\\*\\*\\s*\\|\\s*([^|]+?)\\s*\\|', 'i'));
|
|
56
|
+
if (m && clean(m[1])) return clean(m[1]);
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const badges = [];
|
|
62
|
+
|
|
63
|
+
for (const line of fs.readFileSync(MANIFEST, 'utf8').split('\n')) {
|
|
64
|
+
const cols = line.split(',');
|
|
65
|
+
const id = (cols[0] || '').trim();
|
|
66
|
+
if (!/^[a-z][a-z0-9-]*$/.test(id) || id === 'id') continue;
|
|
67
|
+
const name = (cols[1] || '').trim();
|
|
68
|
+
const icon = (cols[3] || '').trim();
|
|
69
|
+
if (!name || !icon) continue;
|
|
70
|
+
badges.push({ id, name, icon, area: FW_AREA[id] || id.toUpperCase(), squad: 'núcleo' });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let squadDirs = [];
|
|
74
|
+
try { squadDirs = fs.readdirSync(SQUADS_DIR).filter((d) => fs.statSync(path.join(SQUADS_DIR, d)).isDirectory()); } catch {}
|
|
75
|
+
for (const squad of squadDirs) {
|
|
76
|
+
const dir = path.join(SQUADS_DIR, squad, 'agents');
|
|
77
|
+
let files = [];
|
|
78
|
+
try { files = fs.readdirSync(dir).filter((f) => f.endsWith('.md')); } catch { continue; }
|
|
79
|
+
const area = SQUAD_AREA[squad] || squad.replace(/^squad-/, '').toUpperCase();
|
|
80
|
+
for (const file of files) {
|
|
81
|
+
const slug = file.replace(/\.md$/, '');
|
|
82
|
+
const content = fs.readFileSync(path.join(dir, file), 'utf8');
|
|
83
|
+
let name = extract(content, ['name', 'nome']);
|
|
84
|
+
if (!name || name === slug) name = titleCase(slug); // no codename -> presentable id
|
|
85
|
+
const icon = extract(content, ['icon', 'ícone', 'icone']) || firstEmoji(content.split('\n')[0]);
|
|
86
|
+
badges.push({ id: `${squad}/${slug}`, name, icon, area, squad });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Emoji fallback: an agent with no icon inherits its squad orqx's emoji.
|
|
91
|
+
const squadIcon = {};
|
|
92
|
+
for (const b of badges) if (/-orqx$/.test(b.id) && b.icon) squadIcon[b.squad] = b.icon;
|
|
93
|
+
let herdados = 0;
|
|
94
|
+
for (const b of badges) if (!b.icon) { b.icon = squadIcon[b.squad] || '◆'; b.herdado = true; herdados++; }
|
|
95
|
+
|
|
96
|
+
// The badge uses only the FIRST name (never the full name).
|
|
97
|
+
for (const b of badges) b.name = b.name.split(/\s+/)[0];
|
|
98
|
+
|
|
99
|
+
const out = {};
|
|
100
|
+
for (const b of badges) out[b.id] = { emoji: b.icon, area: b.area, name: b.name };
|
|
101
|
+
fs.writeFileSync(OUT, JSON.stringify(out, null, 2));
|
|
102
|
+
|
|
103
|
+
const generic = badges.filter((b) => b.icon === '◆').length;
|
|
104
|
+
console.log(`agent-badges.json written: ${badges.length} agents · emoji inherited: ${herdados} · generic ◆: ${generic}`);
|
|
105
|
+
console.log(`-> ${path.relative(REPO, OUT)}`);
|