@arka-labs/nemesis 1.2.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 (100) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +668 -0
  3. package/lib/core/agent-launcher.js +193 -0
  4. package/lib/core/audit.js +210 -0
  5. package/lib/core/connexions.js +80 -0
  6. package/lib/core/flowmap/api.js +111 -0
  7. package/lib/core/flowmap/cli-helpers.js +80 -0
  8. package/lib/core/flowmap/machine.js +281 -0
  9. package/lib/core/flowmap/persistence.js +83 -0
  10. package/lib/core/generators.js +183 -0
  11. package/lib/core/inbox.js +275 -0
  12. package/lib/core/logger.js +20 -0
  13. package/lib/core/mission.js +109 -0
  14. package/lib/core/notewriter/config.js +36 -0
  15. package/lib/core/notewriter/cr.js +237 -0
  16. package/lib/core/notewriter/log.js +112 -0
  17. package/lib/core/notewriter/notes.js +168 -0
  18. package/lib/core/notewriter/paths.js +45 -0
  19. package/lib/core/notewriter/reader.js +121 -0
  20. package/lib/core/notewriter/registry.js +80 -0
  21. package/lib/core/odm.js +191 -0
  22. package/lib/core/profile-picker.js +323 -0
  23. package/lib/core/project.js +287 -0
  24. package/lib/core/registry.js +129 -0
  25. package/lib/core/secrets.js +137 -0
  26. package/lib/core/services.js +45 -0
  27. package/lib/core/team.js +287 -0
  28. package/lib/core/templates.js +80 -0
  29. package/lib/kairos/agent-runner.js +261 -0
  30. package/lib/kairos/claude-invoker.js +90 -0
  31. package/lib/kairos/context-injector.js +331 -0
  32. package/lib/kairos/context-loader.js +108 -0
  33. package/lib/kairos/context-writer.js +45 -0
  34. package/lib/kairos/dispatcher-router.js +173 -0
  35. package/lib/kairos/dispatcher.js +139 -0
  36. package/lib/kairos/event-bus.js +287 -0
  37. package/lib/kairos/event-router.js +131 -0
  38. package/lib/kairos/flowmap-bridge.js +120 -0
  39. package/lib/kairos/hook-handlers.js +351 -0
  40. package/lib/kairos/hook-installer.js +207 -0
  41. package/lib/kairos/hook-prompts.js +54 -0
  42. package/lib/kairos/leader-rules.js +94 -0
  43. package/lib/kairos/pid-checker.js +108 -0
  44. package/lib/kairos/situation-detector.js +123 -0
  45. package/lib/sync/fallback-engine.js +97 -0
  46. package/lib/sync/hcm-client.js +170 -0
  47. package/lib/sync/health.js +47 -0
  48. package/lib/sync/llm-client.js +387 -0
  49. package/lib/sync/nemesis-client.js +379 -0
  50. package/lib/sync/service-session.js +74 -0
  51. package/lib/sync/sync-engine.js +178 -0
  52. package/lib/ui/box.js +104 -0
  53. package/lib/ui/brand.js +42 -0
  54. package/lib/ui/colors.js +57 -0
  55. package/lib/ui/dashboard.js +580 -0
  56. package/lib/ui/error-hints.js +49 -0
  57. package/lib/ui/format.js +61 -0
  58. package/lib/ui/menu.js +306 -0
  59. package/lib/ui/note-card.js +198 -0
  60. package/lib/ui/note-colors.js +26 -0
  61. package/lib/ui/note-detail.js +297 -0
  62. package/lib/ui/note-filters.js +252 -0
  63. package/lib/ui/note-views.js +283 -0
  64. package/lib/ui/prompt.js +81 -0
  65. package/lib/ui/spinner.js +139 -0
  66. package/lib/ui/streambox.js +46 -0
  67. package/lib/ui/table.js +42 -0
  68. package/lib/ui/tree.js +33 -0
  69. package/package.json +53 -0
  70. package/src/cli.js +457 -0
  71. package/src/commands/_helpers.js +119 -0
  72. package/src/commands/audit.js +187 -0
  73. package/src/commands/auth.js +316 -0
  74. package/src/commands/doctor.js +243 -0
  75. package/src/commands/hcm.js +147 -0
  76. package/src/commands/inbox.js +333 -0
  77. package/src/commands/init.js +160 -0
  78. package/src/commands/kairos.js +216 -0
  79. package/src/commands/kars.js +134 -0
  80. package/src/commands/mission.js +275 -0
  81. package/src/commands/notes.js +316 -0
  82. package/src/commands/notewriter.js +296 -0
  83. package/src/commands/odm.js +329 -0
  84. package/src/commands/orch.js +68 -0
  85. package/src/commands/project.js +123 -0
  86. package/src/commands/run.js +123 -0
  87. package/src/commands/services.js +705 -0
  88. package/src/commands/status.js +231 -0
  89. package/src/commands/team.js +572 -0
  90. package/src/config.js +84 -0
  91. package/src/index.js +5 -0
  92. package/templates/project-context.json +10 -0
  93. package/templates/template_CONTRIB-NAME.json +22 -0
  94. package/templates/template_CR-ODM-NAME-000.exemple.json +32 -0
  95. package/templates/template_DEC-NAME-000.json +18 -0
  96. package/templates/template_INTV-NAME-000.json +15 -0
  97. package/templates/template_MISSION_CONTRACT.json +46 -0
  98. package/templates/template_ODM-NAME-000.json +89 -0
  99. package/templates/template_REGISTRY-PROJECT.json +26 -0
  100. package/templates/template_TXN-NAME-000.json +24 -0
@@ -0,0 +1,316 @@
1
+ import { loadAgentEntries, loadProjectTimeline, applyFilters, getAllTags, getAllSessions } from '../../lib/core/notewriter/reader.js';
2
+ import { readNote } from '../../lib/core/notewriter/notes.js';
3
+ import { readCr } from '../../lib/core/notewriter/cr.js';
4
+ import { detectProject } from '../../lib/core/project.js';
5
+ import { readRegistry, getLanes } from '../../lib/core/registry.js';
6
+ import { renderAgentView, renderTimelineView } from '../../lib/ui/note-views.js';
7
+ import { renderNoteDetail, renderCrDetail } from '../../lib/ui/note-detail.js';
8
+ import { renderFilterPanel, formatFilterSummary } from '../../lib/ui/note-filters.js';
9
+ import { renderNoteCard, renderCrCard } from '../../lib/ui/note-card.js';
10
+ import { interactiveMenu } from '../../lib/ui/menu.js';
11
+ import { style } from '../../lib/ui/colors.js';
12
+
13
+ const HELP = `
14
+ nemesis notes — Notes & CR — consultation memoire
15
+
16
+ Usage: nemesis notes <subcommand>
17
+
18
+ Subcommands:
19
+ agent <agent> Vue par agent
20
+ timeline Timeline projet (tous agents)
21
+ inspect <id> Afficher une note ou un CR par ID
22
+ (sans argument) Menu interactif
23
+ -h, --help Aide
24
+
25
+ Flags:
26
+ --cr Filtre CR uniquement
27
+ --level <L> Filtre par niveau (L1-L5)
28
+ `;
29
+
30
+ function getAgents(config) {
31
+ const project = detectProject(config.cwd);
32
+ if (!project) return [];
33
+ const reg = readRegistry(project.hcm_dir);
34
+ if (!reg) return [];
35
+ return getLanes(reg).map(l => l.name);
36
+ }
37
+
38
+ export async function handler({ args, flags, config }) {
39
+ if (flags.help || args.includes('--help') || args.includes('-h')) {
40
+ console.log(HELP);
41
+ return;
42
+ }
43
+
44
+ // Parse --cr from args
45
+ const crFlagIndex = args.indexOf('--cr');
46
+ if (crFlagIndex !== -1) {
47
+ flags.cr = true;
48
+ args.splice(crFlagIndex, 1);
49
+ }
50
+
51
+ // Parse --level from args
52
+ const levelIdx = args.indexOf('--level');
53
+ let levelFlag = null;
54
+ if (levelIdx >= 0 && args[levelIdx + 1]) {
55
+ levelFlag = args[levelIdx + 1];
56
+ }
57
+
58
+ const sub = args[0];
59
+ switch (sub) {
60
+ case 'agent': return handleAgent(args, flags, config, levelFlag);
61
+ case 'timeline': return handleTimeline(args, flags, config, levelFlag);
62
+ case 'inspect': return handleInspect(args, flags, config);
63
+ default: return handleMenu(args, flags, config, levelFlag);
64
+ }
65
+ }
66
+
67
+ async function handleMenu(args, flags, config, levelFlag) {
68
+ const agents = getAgents(config);
69
+
70
+ const items = [];
71
+ for (const agent of agents) {
72
+ const short = agent.replace(/^Agent_/i, '');
73
+ items.push({ label: `Notes \u2014 ${short}`, value: agent, description: `Notes et CR de ${short}` });
74
+ }
75
+ items.push({ label: 'Timeline projet', value: '__timeline__', description: 'Tous les agents' });
76
+
77
+ const choice = await interactiveMenu(items, { title: 'Notes & CR' });
78
+ if (!choice) return;
79
+
80
+ if (choice === '__timeline__') {
81
+ return handleTimeline(['timeline'], flags, config, levelFlag);
82
+ }
83
+
84
+ return handleAgent(['agent', choice], flags, config, levelFlag);
85
+ }
86
+
87
+ async function handleAgent(args, flags, config, levelFlag) {
88
+ const agentName = args[1];
89
+ if (!agentName) {
90
+ console.log(' Usage: nemesis notes agent <agent>');
91
+ return;
92
+ }
93
+
94
+ let allEntries = loadAgentEntries(config.cwd, agentName);
95
+ if (allEntries.length === 0) {
96
+ console.log(` Aucune note ni CR pour ${agentName.replace(/^Agent_/i, '')}.`);
97
+ return;
98
+ }
99
+
100
+ // Initial filters from flags
101
+ let filters = {};
102
+ if (flags.cr) filters.type = 'CR';
103
+ if (levelFlag) filters.levels = [levelFlag];
104
+
105
+ // Navigation loop
106
+ while (true) {
107
+ const filtered = applyFilters(allEntries, filters);
108
+ const filterSummary = formatFilterSummary(filters);
109
+
110
+ // Empty results after filtering — let user change filters instead of quitting
111
+ if (filtered.length === 0 && Object.keys(filters).length > 0) {
112
+ process.stdout.write('\x1b[2J\x1b[H');
113
+ const agentShort = agentName.replace(/^Agent_/i, '');
114
+ console.log(` ${style.bold(`Notes \u2014 ${agentShort}`)}`);
115
+ console.log(` 0 entrees \u00b7 filtre : ${filterSummary}`);
116
+ console.log('');
117
+ console.log(` ${style.dim('Aucune entree pour ces filtres')}`);
118
+ console.log('');
119
+
120
+ const action = await interactiveMenu([
121
+ { label: 'Modifier les filtres', value: 'filter', description: '' },
122
+ { label: 'Reinitialiser les filtres', value: 'reset', description: '' },
123
+ { label: 'Quitter', value: 'quit', description: '' },
124
+ ], { title: 'Que faire ?' });
125
+
126
+ if (!action || action === 'quit') break;
127
+ if (action === 'reset') { filters = {}; continue; }
128
+ if (action === 'filter') {
129
+ const tags = getAllTags(allEntries);
130
+ const sessions = getAllSessions(allEntries);
131
+ const filterResult = await renderFilterPanel(filters, tags, sessions);
132
+ if (filterResult.action === 'apply') filters = filterResult.filters;
133
+ else if (filterResult.action === 'reset') filters = {};
134
+ continue;
135
+ }
136
+ continue;
137
+ }
138
+
139
+ const result = await renderAgentView(filtered, {
140
+ agentName,
141
+ filterSummary,
142
+ });
143
+
144
+ if (result.action === 'quit') break;
145
+
146
+ if (result.action === 'filter') {
147
+ const tags = getAllTags(allEntries);
148
+ const sessions = getAllSessions(allEntries);
149
+ const filterResult = await renderFilterPanel(filters, tags, sessions);
150
+ if (filterResult.action === 'apply') filters = filterResult.filters;
151
+ else if (filterResult.action === 'reset') filters = {};
152
+ continue;
153
+ }
154
+
155
+ if (result.action === 'inspect' && result.entry) {
156
+ await navigateDetail(result.entry, filtered, config);
157
+ continue;
158
+ }
159
+ }
160
+ }
161
+
162
+ async function handleTimeline(args, flags, config, levelFlag) {
163
+ let allEntries = loadProjectTimeline(config.cwd);
164
+ if (allEntries.length === 0) {
165
+ console.log(' Aucune note ni CR sur le projet.');
166
+ return;
167
+ }
168
+
169
+ let filters = {};
170
+ if (flags.cr) filters.type = 'CR';
171
+ if (levelFlag) filters.levels = [levelFlag];
172
+
173
+ while (true) {
174
+ const filtered = applyFilters(allEntries, filters);
175
+ const filterSummary = formatFilterSummary(filters);
176
+
177
+ // Empty results after filtering
178
+ if (filtered.length === 0 && Object.keys(filters).length > 0) {
179
+ process.stdout.write('\x1b[2J\x1b[H');
180
+ console.log(` ${style.bold('Timeline projet')}`);
181
+ console.log(` 0 entrees \u00b7 filtre : ${filterSummary}`);
182
+ console.log('');
183
+ console.log(` ${style.dim('Aucune entree pour ces filtres')}`);
184
+ console.log('');
185
+
186
+ const action = await interactiveMenu([
187
+ { label: 'Modifier les filtres', value: 'filter', description: '' },
188
+ { label: 'Reinitialiser les filtres', value: 'reset', description: '' },
189
+ { label: 'Quitter', value: 'quit', description: '' },
190
+ ], { title: 'Que faire ?' });
191
+
192
+ if (!action || action === 'quit') break;
193
+ if (action === 'reset') { filters = {}; continue; }
194
+ if (action === 'filter') {
195
+ const tags = getAllTags(allEntries);
196
+ const sessions = getAllSessions(allEntries);
197
+ const filterResult = await renderFilterPanel(filters, tags, sessions);
198
+ if (filterResult.action === 'apply') filters = filterResult.filters;
199
+ else if (filterResult.action === 'reset') filters = {};
200
+ continue;
201
+ }
202
+ continue;
203
+ }
204
+
205
+ const result = await renderTimelineView(filtered, {
206
+ filterSummary,
207
+ });
208
+
209
+ if (result.action === 'quit') break;
210
+
211
+ if (result.action === 'filter') {
212
+ const tags = getAllTags(allEntries);
213
+ const sessions = getAllSessions(allEntries);
214
+ const filterResult = await renderFilterPanel(filters, tags, sessions);
215
+ if (filterResult.action === 'apply') filters = filterResult.filters;
216
+ else if (filterResult.action === 'reset') filters = {};
217
+ continue;
218
+ }
219
+
220
+ if (result.action === 'inspect' && result.entry) {
221
+ await navigateDetail(result.entry, filtered, config);
222
+ continue;
223
+ }
224
+ }
225
+ }
226
+
227
+ async function navigateDetail(entry, entries, _config) {
228
+ let current = entry;
229
+ let currentIndex = entries.findIndex(e => e.id === current.id);
230
+
231
+ while (true) {
232
+ const context = { allEntries: entries, currentIndex };
233
+
234
+ let result;
235
+ if (current.type === 'CR') {
236
+ result = await renderCrDetail(current.data, context);
237
+ } else {
238
+ result = await renderNoteDetail(current.data, context);
239
+ }
240
+
241
+ if (result.action === 'back') break;
242
+
243
+ if (result.action === 'prev' && currentIndex > 0) {
244
+ currentIndex--;
245
+ current = entries[currentIndex];
246
+ continue;
247
+ }
248
+
249
+ if (result.action === 'next' && currentIndex < entries.length - 1) {
250
+ currentIndex++;
251
+ current = entries[currentIndex];
252
+ continue;
253
+ }
254
+
255
+ if (result.action === 'crParent' && result.data) {
256
+ const crEntry = entries.find(e => e.id === result.data);
257
+ if (crEntry) {
258
+ current = crEntry;
259
+ currentIndex = entries.indexOf(crEntry);
260
+ }
261
+ continue;
262
+ }
263
+
264
+ if (result.action === 'notes' && result.data) {
265
+ break; // Return to list
266
+ }
267
+
268
+ if (result.action === 'prevCr' && result.data) {
269
+ const prevCr = entries.find(e => e.id === result.data);
270
+ if (prevCr) {
271
+ current = prevCr;
272
+ currentIndex = entries.indexOf(prevCr);
273
+ }
274
+ continue;
275
+ }
276
+
277
+ if (result.action === 'nextCr') {
278
+ const nextCrIdx = entries.findIndex((e, i) => i > currentIndex && e.type === 'CR');
279
+ if (nextCrIdx >= 0) {
280
+ current = entries[nextCrIdx];
281
+ currentIndex = nextCrIdx;
282
+ }
283
+ continue;
284
+ }
285
+ }
286
+ }
287
+
288
+ async function handleInspect(args, flags, config) {
289
+ const itemId = args[1];
290
+ if (!itemId) {
291
+ console.log(' Usage: nemesis notes inspect <id>');
292
+ return;
293
+ }
294
+
295
+ const agents = getAgents(config);
296
+
297
+ for (const agent of agents) {
298
+ if (itemId.startsWith('NOTE-')) {
299
+ const note = readNote(config.cwd, agent, itemId);
300
+ if (note) {
301
+ const card = renderNoteCard(note);
302
+ for (const line of card) console.log(line);
303
+ return;
304
+ }
305
+ } else if (itemId.startsWith('CR-')) {
306
+ const cr = readCr(config.cwd, agent, itemId);
307
+ if (cr) {
308
+ const card = renderCrCard(cr);
309
+ for (const line of card) console.log(line);
310
+ return;
311
+ }
312
+ }
313
+ }
314
+
315
+ console.log(` ID ${itemId} non trouve.`);
316
+ }
@@ -0,0 +1,296 @@
1
+ import { interactiveMenu } from '../../lib/ui/menu.js';
2
+ import { renderTable } from '../../lib/ui/table.js';
3
+ import { titledBox } from '../../lib/ui/box.js';
4
+ import { style } from '../../lib/ui/colors.js';
5
+ import { BACK_ITEM, HOME_ITEM } from './_helpers.js';
6
+ import { readRegistry, getLanes } from '../../lib/core/registry.js';
7
+ import { readNwRegistry, getNotesSinceCr, getLastCr } from '../../lib/core/notewriter/registry.js';
8
+ import { readNote, listNotes } from '../../lib/core/notewriter/notes.js';
9
+ import { readCr, listCrs, generateCR } from '../../lib/core/notewriter/cr.js';
10
+ import { replaySession, listLogFiles } from '../../lib/core/notewriter/log.js';
11
+ import { readNotewriterConfig } from '../../lib/core/notewriter/config.js';
12
+ import { agentShort } from '../../lib/core/notewriter/paths.js';
13
+
14
+ const HELP = `
15
+ nemesis notewriter — Memoire agent (notes, CR, replay)
16
+
17
+ Usage: nemesis notewriter [subcommand]
18
+
19
+ Subcommands:
20
+ status <agent> Etat NoteWriter d'un agent
21
+ cr <agent> Generer un CR manuel
22
+ list <agent> Lister notes et CR
23
+ inspect <agent> <id> Inspecter une note ou un CR
24
+ replay <agent> [session] Rejouer une session
25
+ -h, --help Aide
26
+ `;
27
+
28
+ export async function handler({ args, flags, config }) {
29
+ if (flags.help || args.includes('--help') || args.includes('-h')) {
30
+ console.log(HELP);
31
+ return;
32
+ }
33
+
34
+ const sub = args[0];
35
+ switch (sub) {
36
+ case 'status': return handleStatus(args, flags, config);
37
+ case 'cr': return handleCr(args, flags, config);
38
+ case 'list': return handleList(args, flags, config);
39
+ case 'inspect': return handleInspect(args, flags, config);
40
+ case 'replay': return handleReplay(args, flags, config);
41
+ default: return handleMenu(args, flags, config);
42
+ }
43
+ }
44
+
45
+ // --- Helpers ---
46
+
47
+ import { detectProject } from '../../lib/core/project.js';
48
+
49
+ async function pickAgent(config, prompt = 'Agent a inspecter') {
50
+ const project = detectProject(config.cwd);
51
+ if (!project) { console.log(' Aucun projet detecte.'); return null; }
52
+ const registry = readRegistry(project.hcm_dir);
53
+ if (!registry) { console.log(' Aucun registry.'); return null; }
54
+ const lanes = getLanes(registry);
55
+ if (lanes.length === 0) { console.log(' Aucun agent enregistre.'); return null; }
56
+
57
+ const items = lanes.map(l => ({
58
+ label: `${l.name} ${style.dim(l.role || '')}`,
59
+ value: l.name,
60
+ }));
61
+ items.push(BACK_ITEM, HOME_ITEM);
62
+
63
+ const choice = await interactiveMenu(items, { title: prompt });
64
+ if (!choice || choice === '__back__') return null;
65
+ if (choice === '__home__') return '__home__';
66
+ return choice;
67
+ }
68
+
69
+ // --- status ---
70
+
71
+ async function handleStatus(args, flags, config) {
72
+ let agentName = args[1];
73
+ if (!agentName) agentName = await pickAgent(config, 'Status — choisir un agent');
74
+ if (!agentName) return;
75
+ if (agentName === '__home__') return '__home__';
76
+
77
+ showStatus(config.cwd, agentName);
78
+ }
79
+
80
+ function showStatus(projectRoot, agentName) {
81
+ const nwReg = readNwRegistry(projectRoot, agentName);
82
+ const nwConfig = readNotewriterConfig(projectRoot);
83
+ const notesSinceCr = getNotesSinceCr(projectRoot, agentName);
84
+ const lastCr = getLastCr(projectRoot, agentName);
85
+ const logFiles = listLogFiles(projectRoot, agentName);
86
+
87
+ const lines = [
88
+ `Notes totales : ${style.bold(String(nwReg.notes.length))}`,
89
+ `CR totaux : ${style.bold(String(nwReg.crs.length))}`,
90
+ `Notes depuis CR: ${style.bold(String(notesSinceCr.length))}`,
91
+ `Dernier CR : ${lastCr ? `${lastCr.id} (${lastCr.timestamp})` : style.dim('Aucun')}`,
92
+ `Derniere note : ${nwReg.lastNoteId || style.dim('Aucune')}`,
93
+ `Sessions log : ${logFiles.length}`,
94
+ '',
95
+ style.dim('Config :'),
96
+ ` Notes actives : ${nwConfig.note_enabled ? style.green('Oui') : style.red('Non')}`,
97
+ ` CR actifs : ${nwConfig.cr_enabled ? style.green('Oui') : style.red('Non')}`,
98
+ ` Timer CR : ${nwConfig.cr_timer_minutes} min`,
99
+ ` Seuil CR : ${nwConfig.cr_min_level_for_inclusion}`,
100
+ ];
101
+
102
+ console.log(titledBox(`NoteWriter — ${agentShort(agentName)}`, lines, { border: style.arkaRed }));
103
+ }
104
+
105
+ // --- cr ---
106
+
107
+ async function handleCr(args, flags, config) {
108
+ let agentName = args[1];
109
+ if (!agentName) agentName = await pickAgent(config, 'CR manuel — choisir un agent');
110
+ if (!agentName) return;
111
+ if (agentName === '__home__') return '__home__';
112
+
113
+ const notesSinceCr = getNotesSinceCr(config.cwd, agentName);
114
+ if (notesSinceCr.length === 0) {
115
+ console.log(' Aucune note depuis le dernier CR. Rien a generer.');
116
+ return;
117
+ }
118
+
119
+ console.log(` Generation CR manuel pour ${agentShort(agentName)} (${notesSinceCr.length} notes)...`);
120
+
121
+ let callLlm;
122
+ try {
123
+ const mod = await import('../../lib/sync/service-session.js');
124
+ callLlm = mod.createServiceCaller(config.cwd, 'notes');
125
+ } catch {
126
+ console.log(' Service LLM notes non configure. CR impossible.');
127
+ return;
128
+ }
129
+
130
+ const cr = await generateCR(config.cwd, agentName, 'manual', callLlm);
131
+ if (!cr) {
132
+ console.log(' Generation CR echouee (LLM ou pas de notes).');
133
+ return;
134
+ }
135
+
136
+ console.log(titledBox(`CR genere — ${cr.id}`, [
137
+ `Titre : ${cr.title}`,
138
+ `Notes : ${cr.noteCount}`,
139
+ `Periode : ${cr.period.from} → ${cr.period.to}`,
140
+ '',
141
+ cr.summary,
142
+ ], { border: style.arkaRed }));
143
+ }
144
+
145
+ // --- list ---
146
+
147
+ async function handleList(args, flags, config) {
148
+ let agentName = args[1];
149
+ if (!agentName) agentName = await pickAgent(config, 'Lister — choisir un agent');
150
+ if (!agentName) return;
151
+ if (agentName === '__home__') return '__home__';
152
+
153
+ const notes = listNotes(config.cwd, agentName);
154
+ const crs = listCrs(config.cwd, agentName);
155
+
156
+ const items = [];
157
+ for (const n of notes) {
158
+ items.push({ type: 'NOTE', id: n.id, level: n.level || '-', timestamp: n.timestamp || '-', tags: (n.tags || []).join(', ') });
159
+ }
160
+ for (const c of crs) {
161
+ items.push({ type: 'CR', id: c.id, level: '-', timestamp: c.timestamp || '-', tags: c.trigger || '-' });
162
+ }
163
+ items.sort((a, b) => (b.timestamp || '').localeCompare(a.timestamp || ''));
164
+
165
+ if (items.length === 0) {
166
+ console.log(` Aucune note ni CR pour ${agentShort(agentName)}.`);
167
+ return;
168
+ }
169
+
170
+ renderTable(items, {
171
+ columns: ['type', 'id', 'level', 'timestamp', 'tags'],
172
+ headers: ['Type', 'ID', 'Niveau', 'Date', 'Tags'],
173
+ });
174
+ }
175
+
176
+ // --- inspect ---
177
+
178
+ async function handleInspect(args, flags, config) {
179
+ const agentName = args[1];
180
+ const itemId = args[2];
181
+ if (!agentName || !itemId) {
182
+ console.log(' Usage: nemesis notewriter inspect <agent> <id>');
183
+ return;
184
+ }
185
+
186
+ if (itemId.startsWith('NOTE-')) {
187
+ const note = readNote(config.cwd, agentName, itemId);
188
+ if (!note) { console.log(` Note ${itemId} non trouvee.`); return; }
189
+
190
+ console.log(titledBox(`NOTE — ${note.id} [${note.level}]`, [
191
+ `Agent : ${note.agentId}`,
192
+ `Session : ${note.sessionId}`,
193
+ `Tour : ${note.turn}`,
194
+ `Date : ${note.timestamp}`,
195
+ `CR ref : ${note.previousCrId || '-'}`,
196
+ '',
197
+ `Resume prompt : ${note.inputSummary}`,
198
+ '',
199
+ note.content,
200
+ '',
201
+ `Tags : ${(note.tags || []).join(', ') || '-'}`,
202
+ `Actions : ${(note.extractedActions || []).join(', ') || '-'}`,
203
+ ], { border: style.arkaRed }));
204
+ } else if (itemId.startsWith('CR-')) {
205
+ const cr = readCr(config.cwd, agentName, itemId);
206
+ if (!cr) { console.log(` CR ${itemId} non trouve.`); return; }
207
+
208
+ console.log(titledBox(`CR — ${cr.id}`, [
209
+ `Titre : ${cr.title}`,
210
+ `Trigger : ${cr.trigger}`,
211
+ `Periode : ${cr.period?.from || '-'} → ${cr.period?.to || '-'}`,
212
+ `CR prec. : ${cr.previousCrId || '-'}`,
213
+ `Notes : ${cr.noteCount} (${(cr.noteIds || []).join(', ')})`,
214
+ '',
215
+ '--- Resume ---',
216
+ cr.summary || '-',
217
+ '',
218
+ '--- Decisions ---',
219
+ ...(cr.decisions || []).map(d => ` - ${d}`),
220
+ '',
221
+ '--- Actions ---',
222
+ ...(cr.actions || []).map(a => ` - ${a}`),
223
+ '',
224
+ '--- Recontextualisation ---',
225
+ cr.recontextualization || '-',
226
+ ], { border: style.arkaRed }));
227
+ } else {
228
+ console.log(` ID ${itemId} non reconnu. Attendu : NOTE-xxx ou CR-xxx.`);
229
+ }
230
+ }
231
+
232
+ // --- replay ---
233
+
234
+ async function handleReplay(args, flags, config) {
235
+ let agentName = args[1];
236
+ if (!agentName) {
237
+ console.log(' Usage: nemesis notewriter replay <agent> [session]');
238
+ return;
239
+ }
240
+
241
+ const sessionId = args[2];
242
+ if (!sessionId) {
243
+ const files = listLogFiles(config.cwd, agentName);
244
+ if (files.length === 0) {
245
+ console.log(` Aucun log pour ${agentShort(agentName)}.`);
246
+ return;
247
+ }
248
+ console.log(`\n Sessions disponibles pour ${agentShort(agentName)} :\n`);
249
+ for (const f of files) {
250
+ console.log(` ${f.filename} ${style.dim(`(${f.month})`)}`);
251
+ }
252
+ return;
253
+ }
254
+
255
+ const messages = replaySession(config.cwd, agentName, sessionId);
256
+ if (messages.length === 0) {
257
+ console.log(` Session ${sessionId} vide ou introuvable.`);
258
+ return;
259
+ }
260
+
261
+ console.log(`\n Replay session ${sessionId} — ${messages.length} messages\n`);
262
+ for (const msg of messages) {
263
+ const role = msg.role === 'user' ? style.bold('► USER') : style.nemesisAccent('◄ AGENT');
264
+ const time = msg.timestamp ? style.dim(`[${msg.timestamp}]`) : '';
265
+ console.log(` ${role} ${time}`);
266
+ const content = msg.content?.length > 500 ? msg.content.slice(0, 500) + '...' : msg.content;
267
+ console.log(` ${content}`);
268
+ console.log('');
269
+ }
270
+ }
271
+
272
+ // --- menu ---
273
+
274
+ async function handleMenu(args, flags, config) {
275
+ const items = [
276
+ { label: 'Status agent', value: 'status', description: 'Etat NoteWriter d\'un agent' },
277
+ { label: 'Generer CR manuel', value: 'cr', description: 'Generer un compte-rendu' },
278
+ { label: 'Lister notes/CR', value: 'list', description: 'Toutes les notes et CR' },
279
+ { label: 'Inspecter note/CR', value: 'inspect', description: 'Voir le detail' },
280
+ { label: 'Rejouer session', value: 'replay', description: 'Replay depuis le log JSONL' },
281
+ BACK_ITEM,
282
+ HOME_ITEM,
283
+ ];
284
+
285
+ const choice = await interactiveMenu(items, { title: 'NoteWriter' });
286
+ if (!choice || choice === '__back__') return '__back__';
287
+ if (choice === '__home__') return '__home__';
288
+
289
+ switch (choice) {
290
+ case 'status': return handleStatus([choice], flags, config);
291
+ case 'cr': return handleCr([choice], flags, config);
292
+ case 'list': return handleList([choice], flags, config);
293
+ case 'inspect': return handleInspect([choice], flags, config);
294
+ case 'replay': return handleReplay([choice], flags, config);
295
+ }
296
+ }