sinapse-ai 1.14.0 → 1.16.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 +66 -16
- package/.sinapse-ai/development/tasks/spec-analyze.md +460 -0
- package/.sinapse-ai/development/tasks/spec-clarify.md +374 -0
- 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/development/workflows/spec-pipeline.yaml +121 -7
- package/.sinapse-ai/install-manifest.yaml +34 -14
- package/.sinapse-ai/product/templates/spec-tmpl.md +18 -0
- 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/AGENTS.md +1 -1
- package/CHANGELOG.md +2 -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
|
@@ -75,9 +75,16 @@ function buildStatusLine(data) {
|
|
|
75
75
|
|
|
76
76
|
// 5 & 6. Agent & Squad (from session cache + API fallback)
|
|
77
77
|
const cache = readSessionCache();
|
|
78
|
+
const badges = loadBadges();
|
|
79
|
+
const agentBadge = resolveBadge(cache, badges);
|
|
78
80
|
const activeAgent = cache.agent || data.agent?.name || '';
|
|
79
81
|
const activeSquad = cache.squad || '';
|
|
80
|
-
if (
|
|
82
|
+
if (agentBadge) {
|
|
83
|
+
// Public badge: {emoji} {Name} (e.g. "\ud83d\udcbb Pixel") instead of the raw id.
|
|
84
|
+
parts.push(`${cyan}${agentBadge.emoji} ${agentBadge.name}${reset}`);
|
|
85
|
+
} else if (activeAgent) {
|
|
86
|
+
parts.push(`${cyan}\ud83e\udd16 ${activeAgent}${reset}`);
|
|
87
|
+
}
|
|
81
88
|
if (activeSquad) parts.push(`${orange}\ud83c\udfaf ${activeSquad}${reset}`);
|
|
82
89
|
|
|
83
90
|
// 6.5 Orchestration \u2014 Imperator + active specialists (TTL 6h)
|
|
@@ -90,7 +97,7 @@ function buildStatusLine(data) {
|
|
|
90
97
|
}
|
|
91
98
|
const ativos = (cache.specialists || []).filter(s => nowSec - s.ts < TTL);
|
|
92
99
|
if (ativos.length > 0) {
|
|
93
|
-
parts.push(`${cyan}\ud83e\udded ${ativos.length} especialistas: ${ativos.map(s => s.id).join(', ')}${reset}`);
|
|
100
|
+
parts.push(`${cyan}\ud83e\udded ${ativos.length} especialistas: ${ativos.map(s => (badges && badges[s.id] ? badges[s.id].name : s.id)).join(', ')}${reset}`);
|
|
94
101
|
}
|
|
95
102
|
|
|
96
103
|
// 7. Project:Branch
|
|
@@ -169,6 +176,33 @@ function readSessionCache() {
|
|
|
169
176
|
return { agent: '', squad: '', role: null, imperator: null, specialists: [] };
|
|
170
177
|
}
|
|
171
178
|
|
|
179
|
+
function loadBadges() {
|
|
180
|
+
// id -> {emoji, area, name}. Keys are bare ids or "squad/id"; index by the last
|
|
181
|
+
// segment to match what the session-cache stores. Best-effort — null on absence.
|
|
182
|
+
try {
|
|
183
|
+
const p = path.join(os.homedir(), '.claude', 'agent-badges.json');
|
|
184
|
+
const raw = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
185
|
+
if (!raw || typeof raw !== 'object') return null;
|
|
186
|
+
const byId = {};
|
|
187
|
+
for (const key of Object.keys(raw)) {
|
|
188
|
+
const v = raw[key];
|
|
189
|
+
if (v && v.emoji && v.name) byId[key.split('/').pop()] = v;
|
|
190
|
+
}
|
|
191
|
+
return Object.keys(byId).length ? byId : null;
|
|
192
|
+
} catch {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function resolveBadge(cache, badges) {
|
|
198
|
+
if (!badges) return null;
|
|
199
|
+
if (cache.role === 'orqx' && cache.agent) {
|
|
200
|
+
return badges[`${cache.agent}-orqx`] || badges[cache.agent] || null;
|
|
201
|
+
}
|
|
202
|
+
if (cache.agent) return badges[cache.agent] || null;
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
|
|
172
206
|
function simpleHash(str) {
|
|
173
207
|
let hash = 0;
|
|
174
208
|
for (let i = 0; i < str.length; i++) {
|
|
@@ -33,6 +33,8 @@ const os = require('os');
|
|
|
33
33
|
|
|
34
34
|
const MASTERS = new Set(['sinapse', 'snps', 'sinapse-orqx', 'snps-orqx']);
|
|
35
35
|
const SPECIALIST_CAP = 6;
|
|
36
|
+
const BADGE_TTL = 21600; // 6h — mirrors the statusline freshness window.
|
|
37
|
+
const IMPERATOR_KEY = 'snps-orqx'; // badge key for the master / Imperator voice.
|
|
36
38
|
|
|
37
39
|
// Identical to track-agent.sh and statusline-script.js.
|
|
38
40
|
function simpleHash(str) {
|
|
@@ -132,6 +134,111 @@ function applyName(cache, name, ts) {
|
|
|
132
134
|
}
|
|
133
135
|
}
|
|
134
136
|
|
|
137
|
+
// ── Badge injection ──────────────────────────────────────────────────────────
|
|
138
|
+
// The detector also surfaces the speaking agent's public badge to the model via
|
|
139
|
+
// the UserPromptSubmit `additionalContext` channel, so every response can open
|
|
140
|
+
// with the right seal. Pure best-effort: any failure → no injection, never block.
|
|
141
|
+
|
|
142
|
+
// Load ~/.claude/agent-badges.json and index by last path segment. Keys are
|
|
143
|
+
// either bare ids ("developer") or "squad/id" ("squad-brand/brand-orqx"); the
|
|
144
|
+
// session-cache only ever stores the bare id, so we index on the last segment.
|
|
145
|
+
function loadBadges() {
|
|
146
|
+
try {
|
|
147
|
+
const p = path.join(os.homedir(), '.claude', 'agent-badges.json');
|
|
148
|
+
const raw = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
149
|
+
if (!raw || typeof raw !== 'object') return null;
|
|
150
|
+
const byId = {};
|
|
151
|
+
for (const key of Object.keys(raw)) {
|
|
152
|
+
const v = raw[key];
|
|
153
|
+
if (!v || typeof v !== 'object' || !v.emoji || !v.name) continue;
|
|
154
|
+
byId[key.split('/').pop()] = v;
|
|
155
|
+
}
|
|
156
|
+
return Object.keys(byId).length ? byId : null;
|
|
157
|
+
} catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function fullBadge(b) {
|
|
163
|
+
return `▌ ${b.emoji} · SNPS · ${b.area || ''} · ${b.name}`.replace(/ · · /, ' · ');
|
|
164
|
+
}
|
|
165
|
+
function shortBadge(b) {
|
|
166
|
+
return `▌ ${b.emoji} ${b.name}`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Resolve the primary speaking voice. cache.role reflects the LAST classified
|
|
170
|
+
// mention, so it doubles as the recency signal (imperator vs orqx vs specialist).
|
|
171
|
+
function resolvePrimary(cache, byId) {
|
|
172
|
+
if (cache.role === 'imperator') return byId[IMPERATOR_KEY] || null;
|
|
173
|
+
if (cache.role === 'orqx' && cache.agent) {
|
|
174
|
+
return byId[`${cache.agent}-orqx`] || byId[cache.agent] || null;
|
|
175
|
+
}
|
|
176
|
+
if (cache.agent) return byId[cache.agent] || null;
|
|
177
|
+
if (cache.imperator && cache.imperator.active) return byId[IMPERATOR_KEY] || null;
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Build the additionalContext string, or null when no SINAPSE voice is active.
|
|
182
|
+
function buildBadgeContext(cache) {
|
|
183
|
+
const byId = loadBadges();
|
|
184
|
+
if (!byId) return null;
|
|
185
|
+
|
|
186
|
+
const now = nowSec();
|
|
187
|
+
const primary = resolvePrimary(cache, byId);
|
|
188
|
+
|
|
189
|
+
// The specialist roster is only meaningful while the Imperator is coordinating
|
|
190
|
+
// a multi-agent orchestration; a plain specialist↔specialist switch shows just
|
|
191
|
+
// the active voice (no roster noise).
|
|
192
|
+
const imperatorActive =
|
|
193
|
+
cache.imperator && cache.imperator.active && now - (cache.imperator.ts || 0) < BADGE_TTL;
|
|
194
|
+
const specs = imperatorActive
|
|
195
|
+
? (cache.specialists || [])
|
|
196
|
+
.filter((s) => s && s.id && s.id !== cache.agent && now - (s.ts || 0) < BADGE_TTL)
|
|
197
|
+
.map((s) => byId[s.id])
|
|
198
|
+
.filter(Boolean)
|
|
199
|
+
: [];
|
|
200
|
+
|
|
201
|
+
if (!primary && specs.length === 0) return null;
|
|
202
|
+
|
|
203
|
+
const lines = [];
|
|
204
|
+
lines.push('<sinapse-selo>');
|
|
205
|
+
lines.push('Identidade pública dos agentes SINAPSE. Aplique o protocolo de selo na sua resposta.');
|
|
206
|
+
lines.push('');
|
|
207
|
+
if (primary) {
|
|
208
|
+
lines.push(`Voz ativa → cheio: ${fullBadge(primary)} curto: ${shortBadge(primary)}`);
|
|
209
|
+
}
|
|
210
|
+
if (specs.length) {
|
|
211
|
+
lines.push('Especialistas em jogo nesta orquestração:');
|
|
212
|
+
for (const b of specs) lines.push(` • ${fullBadge(b)} (curto: ${shortBadge(b)})`);
|
|
213
|
+
}
|
|
214
|
+
lines.push('');
|
|
215
|
+
lines.push('Protocolo (NON-NEGOTIABLE):');
|
|
216
|
+
lines.push('1. Abra a resposta com o selo do agente que está falando — 1ª linha, SÓ na troca de voz. Mesma voz em blocos seguidos não repete o selo.');
|
|
217
|
+
lines.push('2. Estreia da voz nesta conversa = selo CHEIO. Volta de uma voz já apresentada = selo CURTO.');
|
|
218
|
+
lines.push('3. Orquestrador (Imperator / -orqx): selo → apresenta o PLANO → ESPERA o "pode ir" do usuário → executa os handoffs (cada agente entra com seu selo) → consolida no fim.');
|
|
219
|
+
lines.push('4. O selo (emoji · SNPS · área · nome) É a identidade pública — nunca exponha @ids técnicos nem estrutura interna ao usuário.');
|
|
220
|
+
lines.push('</sinapse-selo>');
|
|
221
|
+
|
|
222
|
+
return lines.join('\n');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function emitBadge(cache) {
|
|
226
|
+
try {
|
|
227
|
+
const ctx = buildBadgeContext(cache);
|
|
228
|
+
if (!ctx) return;
|
|
229
|
+
process.stdout.write(
|
|
230
|
+
JSON.stringify({
|
|
231
|
+
hookSpecificOutput: {
|
|
232
|
+
hookEventName: 'UserPromptSubmit',
|
|
233
|
+
additionalContext: ctx,
|
|
234
|
+
},
|
|
235
|
+
})
|
|
236
|
+
);
|
|
237
|
+
} catch {
|
|
238
|
+
/* fail-soft: a badge is never worth blocking a prompt. */
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
135
242
|
function main() {
|
|
136
243
|
const input = readStdin();
|
|
137
244
|
if (!input) process.exit(0);
|
|
@@ -197,17 +304,20 @@ function main() {
|
|
|
197
304
|
}
|
|
198
305
|
}
|
|
199
306
|
|
|
200
|
-
if (
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
process.exit(0);
|
|
307
|
+
if (changed) {
|
|
308
|
+
cache.version = 2;
|
|
309
|
+
cache.updated = new Date().toISOString();
|
|
310
|
+
try {
|
|
311
|
+
fs.writeFileSync(cacheFile, JSON.stringify(cache));
|
|
312
|
+
} catch {
|
|
313
|
+
/* cache write is best-effort — still try to inject the badge below. */
|
|
314
|
+
}
|
|
209
315
|
}
|
|
210
316
|
|
|
317
|
+
// Inject the active agent's badge even on continuation turns (no new mention
|
|
318
|
+
// keeps the same voice active). Fail-soft and silent when no voice is active.
|
|
319
|
+
emitBadge(cache);
|
|
320
|
+
|
|
211
321
|
process.exit(0);
|
|
212
322
|
}
|
|
213
323
|
|
package/AGENTS.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# AGENTS.md
|
|
2
2
|
|
|
3
3
|
> SINAPSE AI -- AI-Orchestrated System for Full Stack Development
|
|
4
|
-
> 17 squads · 172 agents (all `@`-resolvable in Codex) · 1,
|
|
4
|
+
> 17 squads · 172 agents (all `@`-resolvable in Codex) · 1,412 task files, 1,348 resolvable via the parametric activator
|
|
5
5
|
>
|
|
6
6
|
> Codex resolves every agent and its real tasks at runtime from source (no frozen
|
|
7
7
|
> snapshot): `node .codex/scripts/resolve-codex-agent.js <agent> [command]`.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
## [1.
|
|
1
|
+
## [1.16.0](https://github.com/caioimori/sinapse-ai/compare/1.15.0...1.16.0) (2026-06-25)
|
|
2
2
|
|
|
3
3
|
### Features
|
|
4
4
|
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
### Maintenance
|
|
8
|
-
|
|
9
|
-
* **release:** sincroniza main com a v1.13.0 publicada (package.json + CHANGELOG) ([#277](https://github.com/caioimori/sinapse-ai/issues/277)) ([0c64e27](https://github.com/caioimori/sinapse-ai/commit/0c64e273dfe31ca06c4c72c91064f2a0e87b59ad))
|
|
5
|
+
* add clarify and analyze gates to Spec Pipeline ([#280](https://github.com/caioimori/sinapse-ai/issues/280)) ([18b643f](https://github.com/caioimori/sinapse-ai/commit/18b643fe6d4038bb5a9967dccdd7b34bf3b9f6cc))
|
|
10
6
|
|
|
11
7
|
# Changelog
|
|
12
8
|
|
package/bin/commands/install.js
CHANGED
|
@@ -48,7 +48,7 @@ const {
|
|
|
48
48
|
|
|
49
49
|
async function cmdInstallGlobal(opts = {}) {
|
|
50
50
|
const logger = getLogger();
|
|
51
|
-
header();
|
|
51
|
+
header({ force: true });
|
|
52
52
|
|
|
53
53
|
// Story 10.20 — Upsert detection
|
|
54
54
|
const force = Boolean(opts.force);
|
|
@@ -82,41 +82,12 @@ async function cmdInstallGlobal(opts = {}) {
|
|
|
82
82
|
logger.always(`${DIM} Language: ${label} (from saved config; pass --reconfigure to change)${NC}`);
|
|
83
83
|
}
|
|
84
84
|
if (!language) {
|
|
85
|
+
// Approved install redesign: Portuguese is the default and is NO longer
|
|
86
|
+
// prompted ("Idioma: Portugues (padrao)"). The resolved language is still
|
|
87
|
+
// saved to settings.language below and shown in the install preview. English
|
|
88
|
+
// stays available for power users via a pre-set settings.language=english.
|
|
85
89
|
language = 'pt';
|
|
86
|
-
|
|
87
|
-
// check that silently defaulted to `pt` in Git Bash + Windows.
|
|
88
|
-
if (detectInteractiveMode()) {
|
|
89
|
-
try {
|
|
90
|
-
const inquirer = require('inquirer');
|
|
91
|
-
const langAnswer = await inquirer.prompt([{
|
|
92
|
-
type: 'list',
|
|
93
|
-
name: 'language',
|
|
94
|
-
message: 'Language / Idioma:',
|
|
95
|
-
choices: [
|
|
96
|
-
{ name: 'Portugues', value: 'pt' },
|
|
97
|
-
{ name: 'English', value: 'en' },
|
|
98
|
-
],
|
|
99
|
-
default: 'pt',
|
|
100
|
-
}]);
|
|
101
|
-
language = langAnswer.language;
|
|
102
|
-
} catch {
|
|
103
|
-
// Fallback: readline
|
|
104
|
-
const readline = require('readline');
|
|
105
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
106
|
-
language = await new Promise((resolve) => {
|
|
107
|
-
logger.always(` ${CYAN}Language / Idioma:${NC}`);
|
|
108
|
-
logger.always(` ${GREEN}1${NC}) Portugues`);
|
|
109
|
-
logger.always(` ${GREEN}2${NC}) English`);
|
|
110
|
-
rl.question(` ${BOLD}[1/2]:${NC} `, (answer) => {
|
|
111
|
-
rl.close();
|
|
112
|
-
resolve((answer || '1').trim() === '2' ? 'en' : 'pt');
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
} else {
|
|
117
|
-
// Story 10.46 — surface the silent default so users on CI / pipes know.
|
|
118
|
-
warnNonInteractive();
|
|
119
|
-
}
|
|
90
|
+
if (!detectInteractiveMode()) warnNonInteractive();
|
|
120
91
|
}
|
|
121
92
|
|
|
122
93
|
// Save language to ~/.claude/settings.json
|
|
@@ -168,16 +139,23 @@ async function cmdInstallGlobal(opts = {}) {
|
|
|
168
139
|
hookCount = fs.readdirSync(path.join(ROOT, '.claude', 'hooks')).filter(f => /\.(cjs|js|py|sh)$/.test(f)).length;
|
|
169
140
|
} catch { /* hooks dir optional */ }
|
|
170
141
|
|
|
142
|
+
// Framed "o que será instalado" box (B&W). Rows are plain text so the width
|
|
143
|
+
// math stays correct — inline ANSI codes would break padEnd alignment.
|
|
144
|
+
const boxRows = [
|
|
145
|
+
`${squads.length} squads · ${agentTotal} agentes especializados`,
|
|
146
|
+
'Orquestrador master — @sinapse / @snps',
|
|
147
|
+
];
|
|
148
|
+
if (hookCount) boxRows.push(`${hookCount} hooks de proteção + regras do framework`);
|
|
149
|
+
boxRows.push(`Editor(es): ${llmLabel}`);
|
|
150
|
+
boxRows.push(`Idioma: ${langLabelMap[language] || language}`);
|
|
151
|
+
boxRows.push(`Modo: ${isUpsert ? 'atualizar instalação existente' : 'instalação nova'}`);
|
|
152
|
+
boxRows.push('Destino: ~/.sinapse (núcleo) + ~/.claude/agents (agentes)');
|
|
153
|
+
const boxTitle = ' o que será instalado ';
|
|
154
|
+
const innerW = Math.max(boxTitle.length + 1, ...boxRows.map((r) => r.length + 1)) + 1;
|
|
171
155
|
logger.always('');
|
|
172
|
-
logger.always(`${
|
|
173
|
-
logger.always(
|
|
174
|
-
logger.always(
|
|
175
|
-
if (hookCount) logger.always(` ${hookCount} hooks de proteção + regras do framework`);
|
|
176
|
-
logger.always(` Editor(es): ${BOLD}${llmLabel}${NC}`);
|
|
177
|
-
logger.always(` Idioma: ${langLabelMap[language] || language}`);
|
|
178
|
-
logger.always(` Modo: ${isUpsert ? 'atualizar instalação existente' : 'instalação nova'}`);
|
|
179
|
-
logger.always(` Destino: ${DIM}~/.sinapse/${NC} (núcleo) + ${DIM}~/.claude/agents/${NC} (agentes)`);
|
|
180
|
-
logger.always(`${BOLD}──────────────────────────────${NC}`);
|
|
156
|
+
logger.always(`${DIM} ┌─${boxTitle}${'─'.repeat(innerW - boxTitle.length - 1)}┐${NC}`);
|
|
157
|
+
for (const r of boxRows) logger.always(`${DIM} │${NC} ${r.padEnd(innerW - 1)}${DIM}│${NC}`);
|
|
158
|
+
logger.always(`${DIM} └${'─'.repeat(innerW)}┘${NC}`);
|
|
181
159
|
|
|
182
160
|
if (detectInteractiveMode()) {
|
|
183
161
|
const inquirer = require('inquirer');
|
package/bin/lib/header.js
CHANGED
|
@@ -8,11 +8,15 @@ const {
|
|
|
8
8
|
} = require('../../.sinapse-ai/core/logger');
|
|
9
9
|
const { VERSION, WHITE, BOLD, DIM, NC } = require('./constants');
|
|
10
10
|
|
|
11
|
-
function header() {
|
|
11
|
+
function header(opts = {}) {
|
|
12
12
|
const logger = getLogger();
|
|
13
|
-
|
|
14
|
-
//
|
|
15
|
-
if (!
|
|
13
|
+
const force = !!opts.force;
|
|
14
|
+
// --json (machine output) and --quiet always suppress the banner.
|
|
15
|
+
if (!logger || logger.json || logger.threshold === 0 /* LEVELS.error */) return;
|
|
16
|
+
// Outside json/quiet: show on --verbose / --debug, on first run, OR when the
|
|
17
|
+
// caller forces it. The `install` command forces it so the brand banner is
|
|
18
|
+
// always visible at install time (Story A.2 AC 7 kept for non-forced paths).
|
|
19
|
+
if (!force && !shouldShowHeader(logger)) return;
|
|
16
20
|
const W = `${WHITE}${BOLD}`;
|
|
17
21
|
const lines = [
|
|
18
22
|
'',
|
|
@@ -23,17 +27,17 @@ function header() {
|
|
|
23
27
|
`${W} ███████║██║ ╚████║██║ ███████║ ██║ ██║██║${NC}`,
|
|
24
28
|
`${W} ╚══════╝╚═╝ ╚═══╝╚═╝ ╚══════╝ ╚═╝ ╚═╝╚═╝${NC}`,
|
|
25
29
|
'',
|
|
26
|
-
`${DIM}
|
|
27
|
-
|
|
30
|
+
`${DIM} Orquestracao de inteligencia artificial · v${VERSION}${NC}`,
|
|
31
|
+
'',
|
|
32
|
+
`${DIM} ┼${'─'.repeat(50)}┼${NC}`,
|
|
28
33
|
'',
|
|
29
34
|
];
|
|
30
|
-
// Write directly to stdout so the header still shows
|
|
31
|
-
//
|
|
32
|
-
// gate that decides whether we get here at all.
|
|
35
|
+
// Write directly to stdout so the header still shows even when the logger
|
|
36
|
+
// level is `warn` (default). The gates above decide whether we get here.
|
|
33
37
|
for (const line of lines) {
|
|
34
38
|
try { process.stdout.write(`${line}\n`); } catch { /* ignore */ }
|
|
35
39
|
}
|
|
36
|
-
// Mark first-run done so subsequent runs stay clean
|
|
40
|
+
// Mark first-run done so subsequent (non-forced) runs stay clean.
|
|
37
41
|
markFirstRunDone();
|
|
38
42
|
}
|
|
39
43
|
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
*
|
|
11
11
|
* What it installs:
|
|
12
12
|
* - ~/.claude/statusline-script.js (renders the statusline)
|
|
13
|
-
* - ~/.claude/hooks/track-agent.cjs (UserPromptSubmit detector — writes session-cache)
|
|
13
|
+
* - ~/.claude/hooks/track-agent.cjs (UserPromptSubmit detector — writes session-cache + injects the agent badge)
|
|
14
14
|
* - ~/.claude/hooks/track-agent-clear.cjs (Stop hook — clears session-cache on session end)
|
|
15
|
+
* - ~/.claude/agent-badges.json (id -> {emoji, area, name} — read by the detector + statusline)
|
|
15
16
|
* - ~/.claude/session-cache/ (per-cwd cache directory)
|
|
16
17
|
* - settings.statusLine (graceful skip if already set)
|
|
17
18
|
* - settings.hooks.UserPromptSubmit → track-agent.cjs (idempotent guard)
|
|
@@ -41,6 +42,7 @@ function resolveSources(sourceCoreDir) {
|
|
|
41
42
|
script: path.join(templatesDir, 'statusline-script.js'),
|
|
42
43
|
detector: path.join(templatesDir, 'track-agent.cjs'),
|
|
43
44
|
clear: path.join(templatesDir, 'track-agent-clear.cjs'),
|
|
45
|
+
badges: path.join(templatesDir, 'agent-badges.json'),
|
|
44
46
|
};
|
|
45
47
|
}
|
|
46
48
|
|
|
@@ -122,6 +124,15 @@ async function setupStatusline(opts = {}) {
|
|
|
122
124
|
result.files.push(clearTarget);
|
|
123
125
|
}
|
|
124
126
|
|
|
127
|
+
// Agent badge map (id -> {emoji, area, name}). Read by the detector hook to
|
|
128
|
+
// inject the speaking agent's badge, and by the statusline to render it.
|
|
129
|
+
// Best-effort: a missing source never blocks the rest of the install.
|
|
130
|
+
if (await fse.pathExists(sources.badges)) {
|
|
131
|
+
const badgesTarget = path.join(claudeDir, 'agent-badges.json');
|
|
132
|
+
await fse.copy(sources.badges, badgesTarget);
|
|
133
|
+
result.files.push(badgesTarget);
|
|
134
|
+
}
|
|
135
|
+
|
|
125
136
|
// Read existing global settings (back-compat with any prior config).
|
|
126
137
|
let settings = {};
|
|
127
138
|
try {
|