@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,329 @@
1
+ import { initOdm, listOdm, inspectOdm, setOdmStatus, assignOdm } from '../../lib/core/odm.js';
2
+ import { readRegistry, getLanes } from '../../lib/core/registry.js';
3
+ import { askText, askConfirm } from '../../lib/ui/prompt.js';
4
+ import { interactiveMenu } from '../../lib/ui/menu.js';
5
+ import { renderTable } from '../../lib/ui/table.js';
6
+ import { formatOutput } from '../../lib/ui/format.js';
7
+ import { style } from '../../lib/ui/colors.js';
8
+ import { ensureProject, pickFromList, nextId, BACK_ITEM, HOME_ITEM } from './_helpers.js';
9
+ import { getOdmFlowmapPhase } from '../../lib/core/flowmap/cli-helpers.js';
10
+ import { getCurrentState } from '../../lib/core/flowmap/api.js';
11
+ import { STEP_METADATA } from '../../lib/core/flowmap/machine.js';
12
+
13
+ const HELP = `
14
+ nemesis odm — Ordres de Mission
15
+
16
+ Usage: nemesis odm <subcommand> [options]
17
+
18
+ Subcommands:
19
+ init [id] Cree un OdM
20
+ list Liste les OdMs
21
+ inspect [id] Affiche le contenu complet d'un OdM + CR associes
22
+ set-status <id> <status> Change le statut d'un OdM
23
+ assign <id> <agent> Assigne un OdM a un agent
24
+
25
+ Options:
26
+ -h, --help Aide
27
+ `;
28
+
29
+ export async function handler({ args, flags, config }) {
30
+ if (flags.help || args.includes('--help') || args.includes('-h')) {
31
+ console.log(HELP);
32
+ return;
33
+ }
34
+
35
+ let sub = args[0];
36
+ let subArgs = args.slice(1);
37
+
38
+ if (!sub) {
39
+ sub = await interactiveMenu([
40
+ { label: 'list', value: 'list', description: 'Liste les OdMs' },
41
+ { label: 'init', value: 'init', description: 'Cree un OdM' },
42
+ { label: 'inspect', value: 'inspect', description: 'Affiche le contenu d\'un OdM' },
43
+ { label: 'set-status', value: 'set-status', description: 'Change le statut d\'un OdM' },
44
+ { label: 'assign', value: 'assign', description: 'Assigne un OdM a un agent' },
45
+ BACK_ITEM,
46
+ HOME_ITEM,
47
+ ], { title: 'nemesis odm' });
48
+ if (!sub || sub === '__back__') return '__back__';
49
+ if (sub === '__home__') return '__home__';
50
+ subArgs = [];
51
+ }
52
+
53
+ switch (sub) {
54
+ case 'init':
55
+ return handleInit(subArgs, flags, config);
56
+ case 'list':
57
+ return handleList(flags, config);
58
+ case 'inspect':
59
+ return handleInspect(subArgs, flags, config);
60
+ case 'set-status':
61
+ return handleSetStatus(subArgs, flags, config);
62
+ case 'assign':
63
+ return handleAssign(subArgs, flags, config);
64
+ default:
65
+ console.error(`Sous-commande inconnue : ${sub}`);
66
+ console.log(HELP);
67
+ }
68
+ }
69
+
70
+ async function handleInit(args, flags, config) {
71
+ const project = await ensureProject(config, flags);
72
+ if (!project) return;
73
+
74
+ // Auto-generate ID if not provided
75
+ let id = args[0];
76
+ if (!id) {
77
+ const existing = listOdm(project.hcm_dir);
78
+ const prefix = `ODM-${project.id}`;
79
+ const suggested = nextId(prefix, existing.map(o => o.id));
80
+ id = await askText('ID de l\'OdM', suggested);
81
+ }
82
+ if (!id) return;
83
+
84
+ const title = await askText('Titre');
85
+
86
+ // Assignee from registry
87
+ let assignedTo = '';
88
+ const registry = readRegistry(project.hcm_dir);
89
+ const lanes = registry ? getLanes(registry) : [];
90
+
91
+ if (lanes.length > 0) {
92
+ const assigneeItems = [
93
+ ...lanes.map(l => ({ label: `${l.name || l.id} (${l.role})`, value: l.id })),
94
+ { label: style.dim('(non assigne)'), value: '' },
95
+ ];
96
+ assignedTo = await interactiveMenu(assigneeItems, {
97
+ title: 'Assigne a',
98
+ });
99
+ if (assignedTo === null) assignedTo = '';
100
+ } else {
101
+ console.log(`\n ${style.yellow('\u26A0')} Equipe vide — l'OdM sera non assigne.`);
102
+ console.log(` ${style.dim('Ajoutez un agent apres : nemesis team add')}\n`);
103
+ }
104
+
105
+ // Priority via interactive menu
106
+ const priority = await interactiveMenu([
107
+ { label: 'HAUTE', value: 'HAUTE' },
108
+ { label: 'MOYENNE', value: 'MOYENNE' },
109
+ { label: 'BASSE', value: 'BASSE' },
110
+ ], { title: 'Priorite' });
111
+ if (!priority) return;
112
+
113
+ const result = initOdm(id, {
114
+ title, assignedTo, priority, root: config.cwd, projectId: project.id,
115
+ });
116
+
117
+ console.log(`\n ${style.green('\u2713')} OdM cree : ${style.bold(id)}`);
118
+ console.log(` ${style.dim('\u2192')} ${result.filepath.replace(config.cwd + '/', '')}`);
119
+
120
+ // Next steps
121
+ console.log(`\n ${style.bold('Prochaines etapes')} :`);
122
+ console.log(' 1. Completez le cadrage (scope, steps, rejection_criteria)');
123
+ if (!assignedTo) {
124
+ console.log(` 2. Assignez a un agent : ${style.dim('nemesis team add')}`);
125
+ } else {
126
+ console.log(` 2. L'agent ${style.bold(assignedTo)} peut demarrer l'execution`);
127
+ }
128
+ console.log('');
129
+
130
+ // Loop — propose to create another
131
+ const another = await askConfirm('Creer un autre OdM ?', false);
132
+ if (another) return handleInit([], flags, config);
133
+ }
134
+
135
+ async function handleList(flags, config) {
136
+ const project = await ensureProject(config, flags);
137
+ if (!project) return;
138
+
139
+ const odms = listOdm(project.hcm_dir);
140
+
141
+ if (flags.format === 'json') {
142
+ console.log(formatOutput(odms, 'json'));
143
+ return;
144
+ }
145
+
146
+ if (odms.length === 0) {
147
+ console.log(`\n ${style.dim('Aucun OdM.')}`);
148
+ const doCreate = await askConfirm('Creer un OdM maintenant ?', true);
149
+ if (doCreate) {
150
+ return handleInit([], flags, config);
151
+ }
152
+ console.log('');
153
+ return;
154
+ }
155
+
156
+ // Enrich with FLOWMAP phase (mission_id from full OdM data)
157
+ const enriched = odms.map(o => {
158
+ let phase = '';
159
+ try {
160
+ const full = inspectOdm(o.id, project.hcm_dir);
161
+ phase = getOdmFlowmapPhase(full.odm.odm_meta, config.cwd) || '';
162
+ } catch { /* no-op */ }
163
+ return { ...o, flowmapPhase: phase };
164
+ });
165
+
166
+ console.log('');
167
+ console.log(renderTable(
168
+ [
169
+ { key: 'id', label: 'ID' },
170
+ { key: 'title', label: 'Titre' },
171
+ { key: 'assignee', label: 'Assigne' },
172
+ { key: 'priority', label: 'Priorite' },
173
+ { key: 'status', label: 'Status' },
174
+ { key: 'flowmapPhase', label: 'Phase' },
175
+ ],
176
+ enriched,
177
+ ));
178
+ console.log('');
179
+ }
180
+
181
+ async function handleInspect(args, flags, config) {
182
+ const project = await ensureProject(config, flags);
183
+ if (!project) return;
184
+
185
+ let id = args[0];
186
+
187
+ // If no ID → picker from existing OdMs
188
+ if (!id) {
189
+ const odms = listOdm(project.hcm_dir);
190
+ if (odms.length === 0) {
191
+ console.log(`\n ${style.dim('Aucun OdM a inspecter.')}`);
192
+ const doCreate = await askConfirm('Creer un OdM maintenant ?', true);
193
+ if (doCreate) return handleInit([], flags, config);
194
+ return;
195
+ }
196
+ id = await pickFromList(odms, {
197
+ title: 'Quel OdM inspecter ?',
198
+ labelFn: (o) => `${o.id} — ${o.title || style.dim('(sans titre)')} [${o.status}]`,
199
+ });
200
+ if (!id) return;
201
+ }
202
+
203
+ const result = inspectOdm(id, project.hcm_dir);
204
+
205
+ if (flags.format === 'json') {
206
+ console.log(formatOutput(result, 'json'));
207
+ return;
208
+ }
209
+
210
+ console.log(`\n ${style.bold('OdM')} : ${style.nemesisAccent(id)}`);
211
+ const meta = result.odm.odm_meta || {};
212
+ console.log(` Status : ${meta.status || '?'}`);
213
+ console.log(` Priorite : ${meta.priority || '?'}`);
214
+ console.log(` Assigne a : ${meta.assigned_to?.actor_id || style.dim('(non assigne)')}`);
215
+ console.log(` Titre : ${result.odm.odm_payload?.cadrage?.title || '?'}`);
216
+
217
+ // FLOWMAP position
218
+ if (meta.mission_id) {
219
+ const fmState = getCurrentState(meta.mission_id, { root: config.cwd });
220
+ if (fmState) {
221
+ const fmMeta = STEP_METADATA[fmState.value];
222
+ if (fmMeta) {
223
+ console.log(` FLOWMAP : ${fmState.value} [${fmMeta.phase}] ${fmMeta.label}`);
224
+ }
225
+ }
226
+ }
227
+
228
+ if (result.associatedCrs.length > 0) {
229
+ console.log('\n CR associes :');
230
+ console.log(renderTable(
231
+ [
232
+ { key: 'id', label: 'CR' },
233
+ { key: 'title', label: 'Titre' },
234
+ { key: 'status', label: 'Status' },
235
+ { key: 'date', label: 'Date' },
236
+ ],
237
+ result.associatedCrs,
238
+ ));
239
+ } else {
240
+ console.log(`\n CR associes : ${style.dim('(aucun)')}`);
241
+ }
242
+ console.log('');
243
+ }
244
+
245
+ async function handleSetStatus(args, flags, config) {
246
+ const project = await ensureProject(config, flags);
247
+ if (!project) return;
248
+
249
+ let id = args[0];
250
+ let newStatus = args[1];
251
+
252
+ // If no ID → picker from existing OdMs
253
+ if (!id) {
254
+ const odms = listOdm(project.hcm_dir);
255
+ if (odms.length === 0) {
256
+ console.log(`\n ${style.dim('Aucun OdM.')}\n`);
257
+ return;
258
+ }
259
+ id = await pickFromList(odms, {
260
+ title: 'Quel OdM ?',
261
+ labelFn: (o) => `${o.id} — ${o.title || style.dim('(sans titre)')} [${o.status}]`,
262
+ });
263
+ if (!id) return;
264
+ }
265
+
266
+ if (!newStatus) {
267
+ const statusChoices = [
268
+ { label: 'READY_TO_DISPATCH', value: 'READY_TO_DISPATCH', description: 'Pret pour l\'orchestrateur' },
269
+ { label: 'ASSIGNED', value: 'ASSIGNED', description: 'Assigne a un agent' },
270
+ { label: 'DISPATCHING', value: 'DISPATCHING', description: 'En cours de dispatch' },
271
+ ];
272
+ newStatus = await interactiveMenu(statusChoices, { title: 'Nouveau statut' });
273
+ if (!newStatus) return;
274
+ }
275
+
276
+ try {
277
+ const result = setOdmStatus(id, newStatus, project.hcm_dir);
278
+ console.log(`\n ${style.green('\u2713')} ${style.bold(id)} : ${result.oldStatus} → ${style.nemesisAccent(result.newStatus)}\n`);
279
+ } catch (err) {
280
+ console.log(`\n ${style.red('\u2717')} ${err.message}\n`);
281
+ }
282
+ }
283
+
284
+ async function handleAssign(args, flags, config) {
285
+ const project = await ensureProject(config, flags);
286
+ if (!project) return;
287
+
288
+ let id = args[0];
289
+ let agentId = args[1];
290
+
291
+ // If no ID → picker
292
+ if (!id) {
293
+ const odms = listOdm(project.hcm_dir);
294
+ if (odms.length === 0) {
295
+ console.log(`\n ${style.dim('Aucun OdM.')}\n`);
296
+ return;
297
+ }
298
+ id = await pickFromList(odms, {
299
+ title: 'Quel OdM assigner ?',
300
+ labelFn: (o) => `${o.id} — ${o.title || style.dim('(sans titre)')} [${o.status}]`,
301
+ });
302
+ if (!id) return;
303
+ }
304
+
305
+ // If no agent → picker from registry
306
+ if (!agentId) {
307
+ const registry = readRegistry(project.hcm_dir);
308
+ const lanes = registry ? getLanes(registry) : [];
309
+ if (lanes.length === 0) {
310
+ console.log(`\n ${style.dim('Aucun agent enregistre.')}\n`);
311
+ return;
312
+ }
313
+ agentId = await interactiveMenu(
314
+ lanes.map(l => ({
315
+ label: `${l.name || l.id} (${l.role})`,
316
+ value: l.id,
317
+ })),
318
+ { title: 'Assigner a' },
319
+ );
320
+ if (!agentId) return;
321
+ }
322
+
323
+ try {
324
+ const result = assignOdm(id, agentId, project.hcm_dir);
325
+ console.log(`\n ${style.green('\u2713')} ${style.bold(id)} assigne a ${style.nemesisAccent(result.assignedTo)}\n`);
326
+ } catch (err) {
327
+ console.log(`\n ${style.red('\u2717')} ${err.message}\n`);
328
+ }
329
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * nemesis orch — Orchestration dashboard.
3
+ * Monitors dispatches, escalations, and deliveries via filesystem polling.
4
+ * L1 only — zero agent modification.
5
+ */
6
+
7
+ import { style } from '../../lib/ui/colors.js';
8
+ import { startDashboard, renderDashboard } from '../../lib/ui/dashboard.js';
9
+
10
+ const HELP = `
11
+ nemesis orch — Dashboard orchestration
12
+
13
+ Usage: nemesis orch [options]
14
+
15
+ Affiche l'etat courant des dispatches, escalades et livraisons.
16
+ Rafraichissement automatique toutes les 2s.
17
+
18
+ Options:
19
+ --once Afficher une fois et quitter
20
+ --interval <ms> Intervalle de polling (default: 2000)
21
+ -h, --help Aide
22
+ `;
23
+
24
+ export async function handler({ args, flags, config }) {
25
+ if (flags.help || args.includes('--help') || args.includes('-h')) {
26
+ console.log(HELP);
27
+ return;
28
+ }
29
+
30
+ const root = config.cwd;
31
+ const once = args.includes('--once');
32
+
33
+ if (once || flags.format === 'json') {
34
+ // Single render
35
+ if (flags.format === 'json') {
36
+ const { detectState } = await import('../../lib/ui/dashboard.js');
37
+ const data = detectState(root);
38
+ console.log(JSON.stringify(data, null, 2));
39
+ } else {
40
+ console.log(renderDashboard(root));
41
+ }
42
+ return;
43
+ }
44
+
45
+ // Parse --interval
46
+ let interval = 2000;
47
+ const intervalIdx = args.indexOf('--interval');
48
+ if (intervalIdx !== -1 && args[intervalIdx + 1]) {
49
+ const parsed = parseInt(args[intervalIdx + 1], 10);
50
+ if (!isNaN(parsed) && parsed >= 500) interval = parsed;
51
+ }
52
+
53
+ console.log(` ${style.dim('Dashboard orchestration — q pour quitter')}\n`);
54
+
55
+ const _dashboard = startDashboard(root, { pollInterval: interval });
56
+
57
+ // Keep process alive until dashboard stops
58
+ await new Promise(resolve => {
59
+ const check = setInterval(() => {
60
+ // Dashboard will call process.exit on quit
61
+ }, 1000);
62
+
63
+ process.on('beforeExit', () => {
64
+ clearInterval(check);
65
+ resolve();
66
+ });
67
+ });
68
+ }
@@ -0,0 +1,123 @@
1
+ import { getProjectStatus } from '../../lib/core/project.js';
2
+ import { readRegistry, getLanes, getPM } from '../../lib/core/registry.js';
3
+ import { renderTable } from '../../lib/ui/table.js';
4
+ import { formatOutput } from '../../lib/ui/format.js';
5
+ import { style } from '../../lib/ui/colors.js';
6
+ import { ensureProject } from './_helpers.js';
7
+ import { getFlowmapSummary } from '../../lib/core/flowmap/cli-helpers.js';
8
+
9
+ const HELP = `
10
+ nemesis project — Vue projet
11
+
12
+ Usage: nemesis project <subcommand>
13
+
14
+ Subcommands:
15
+ status Vue synthetique de l'etat du projet
16
+ init Alias de "nemesis init"
17
+
18
+ Options:
19
+ -h, --help Aide
20
+ `;
21
+
22
+ export async function handler({ args, flags, config }) {
23
+ if (flags.help || args.includes('--help') || args.includes('-h')) {
24
+ console.log(HELP);
25
+ return;
26
+ }
27
+
28
+ const sub = args[0] || 'status';
29
+
30
+ switch (sub) {
31
+ case 'status':
32
+ return handleStatus(flags, config);
33
+ case 'init': {
34
+ const initMod = await import('./init.js');
35
+ return initMod.handler({ args: args.slice(1), flags, config });
36
+ }
37
+ default:
38
+ console.error(`Sous-commande inconnue : ${sub}`);
39
+ console.log(HELP);
40
+ }
41
+ }
42
+
43
+ async function handleStatus(flags, config) {
44
+ const project = await ensureProject(config, flags);
45
+ if (!project) return;
46
+
47
+ const status = getProjectStatus(project.hcm_dir);
48
+ const registry = readRegistry(project.hcm_dir);
49
+ const lanes = registry ? getLanes(registry) : [];
50
+ const pm = registry ? getPM(registry) : null;
51
+
52
+ const flowmapEntries = getFlowmapSummary(config.cwd);
53
+
54
+ if (flags.format === 'json') {
55
+ const flowmap = flowmapEntries.map(e => ({
56
+ missionId: e.missionId, state: e.state, phase: e.phase, who: e.who, step: e.state,
57
+ }));
58
+ console.log(formatOutput({ project, status, team: { pm, agents: lanes }, flowmap }, 'json'));
59
+ return;
60
+ }
61
+
62
+ console.log(`\n ${style.bold('Projet')} : ${style.nemesisAccent(project.id)}`);
63
+ console.log(` ${style.bold('Status')} : ${project.status || 'ACTIVE'}`);
64
+ console.log(` ${style.bold('Equipe')} : ${lanes.length + 1} membres (1 PM, ${lanes.length} agents)\n`);
65
+
66
+ // OdMs table
67
+ if (status.odms.length > 0) {
68
+ console.log(` ${style.bold('OdMs')} :`);
69
+ console.log(renderTable(
70
+ [
71
+ { key: 'id', label: 'OdM' },
72
+ { key: 'status', label: 'Status' },
73
+ { key: 'assignee', label: 'Assigne a' },
74
+ { key: 'priority', label: 'Priorite' },
75
+ ],
76
+ status.odms,
77
+ ));
78
+ } else {
79
+ console.log(` OdMs : ${style.dim('(aucun)')}`);
80
+ }
81
+
82
+ // FLOWMAP
83
+ if (flowmapEntries.length > 0) {
84
+ console.log(` ${style.bold('FLOWMAP')} :`);
85
+ console.log(renderTable(
86
+ [
87
+ { key: 'missionId', label: 'Mission' },
88
+ { key: 'phase', label: 'Phase' },
89
+ { key: 'state', label: 'Etape' },
90
+ { key: 'who', label: 'Qui' },
91
+ ],
92
+ flowmapEntries,
93
+ ));
94
+ } else {
95
+ console.log(` Missions actives : ${style.dim('(aucune)')}`);
96
+ }
97
+
98
+ console.log('');
99
+
100
+ // Decisions
101
+ if (status.decisions.length > 0) {
102
+ console.log(' Decisions recentes :');
103
+ for (const dec of status.decisions.slice(-5)) {
104
+ console.log(` - ${dec.id} : ${dec.title} (${dec.status})`);
105
+ }
106
+ } else {
107
+ console.log(` Decisions recentes : ${style.dim('(aucune)')}`);
108
+ }
109
+
110
+ console.log('');
111
+
112
+ // CRs
113
+ if (status.crs.length > 0) {
114
+ console.log(' Derniers depots :');
115
+ for (const cr of status.crs.slice(-5)) {
116
+ console.log(` - ${cr.id} (${cr.date || cr.status})`);
117
+ }
118
+ } else {
119
+ console.log(` Derniers depots : ${style.dim('(aucun)')}`);
120
+ }
121
+
122
+ console.log('');
123
+ }
@@ -0,0 +1,123 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { readRegistry, getLanes } from '../../lib/core/registry.js';
3
+ import { createStreamBox } from '../../lib/ui/streambox.js';
4
+ import { titledBox } from '../../lib/ui/box.js';
5
+ import { style } from '../../lib/ui/colors.js';
6
+ import { ensureProject, getFlag } from './_helpers.js';
7
+
8
+ const HELP = `
9
+ nemesis run — Reprendre une session agent
10
+
11
+ Usage: nemesis run agent:<NAME> [options]
12
+
13
+ Options:
14
+ --p <prompt> Envoyer un prompt direct (non-interactif)
15
+ -h, --help Aide
16
+ `;
17
+
18
+ export async function handler({ args, flags, config }) {
19
+ if (flags.help || args.includes('--help') || args.includes('-h')) {
20
+ console.log(HELP);
21
+ return;
22
+ }
23
+
24
+ const project = await ensureProject(config, flags);
25
+ if (!project) return;
26
+
27
+ // Parse agent:<NAME>
28
+ const agentArg = args[0];
29
+ if (!agentArg || !agentArg.startsWith('agent:')) {
30
+ console.error(`\n ${style.red('Usage')}: nemesis run agent:<NAME>\n`);
31
+ return;
32
+ }
33
+ const agentName = agentArg.slice('agent:'.length);
34
+ if (!agentName) {
35
+ console.error(`\n ${style.red('Nom d\'agent manquant')} apres agent:\n`);
36
+ return;
37
+ }
38
+
39
+ // Lookup registry
40
+ const registry = readRegistry(project.hcm_dir);
41
+ if (!registry) {
42
+ console.error(`\n ${style.red('Registry introuvable.')} Lancez ${style.bold('nemesis init')} d'abord.\n`);
43
+ return;
44
+ }
45
+
46
+ const lanes = getLanes(registry);
47
+ const lane = lanes.find(l => l.id === agentName || l.name === agentName);
48
+ if (!lane) {
49
+ console.error(`\n ${style.red('Agent inconnu')} : ${agentName}`);
50
+ console.error(` Agents disponibles : ${lanes.map(l => l.id).join(', ') || style.dim('(aucun)')}`);
51
+ console.error(` Utilisez ${style.bold('nemesis team add')} pour onboard un agent.\n`);
52
+ return;
53
+ }
54
+
55
+ if (!lane.session_id) {
56
+ console.error(`\n ${style.red('Pas de session')} pour ${agentName}.`);
57
+ console.error(` Lancez d'abord ${style.bold('nemesis team add')} pour onboarder l'agent.\n`);
58
+ return;
59
+ }
60
+
61
+ const prompt = getFlag(args, '--p');
62
+
63
+ if (prompt) {
64
+ // Prompt direct — stream-json output
65
+ console.log(`\n ${style.bold('Prompt direct')} → ${style.nemesisAccent(agentName)}\n`);
66
+ const box = createStreamBox(4);
67
+
68
+ try {
69
+ const child = spawn('claude', [
70
+ '--resume', lane.session_id,
71
+ '-p', prompt,
72
+ '--output-format', 'stream-json',
73
+ '--verbose',
74
+ '--dangerously-skip-permissions',
75
+ ], { stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env } });
76
+
77
+ let finalResult = '';
78
+
79
+ child.stdout.on('data', (chunk) => {
80
+ for (const raw of chunk.toString().split('\n')) {
81
+ if (!raw.trim()) continue;
82
+ try {
83
+ const evt = JSON.parse(raw);
84
+ if (evt.type === 'assistant' && evt.message?.content) {
85
+ for (const block of evt.message.content) {
86
+ if (block.type === 'text' && block.text) {
87
+ for (const tl of block.text.split('\n').filter(Boolean)) {
88
+ box.push(style.dim(tl));
89
+ }
90
+ }
91
+ }
92
+ }
93
+ if (evt.type === 'result') {
94
+ finalResult = evt.result || '';
95
+ }
96
+ } catch { /* ignore */ }
97
+ }
98
+ });
99
+
100
+ await new Promise((resolve) => child.on('close', resolve));
101
+ box.clear();
102
+
103
+ if (finalResult) {
104
+ const lines = finalResult.split('\n');
105
+ console.log(titledBox('Reponse', lines, { border: style.nemesisAccent }));
106
+ console.log('');
107
+ }
108
+ } catch (err) {
109
+ console.error(`\n ${style.red('Erreur')}: ${err.message}\n`);
110
+ }
111
+ } else {
112
+ // Interactive mode — inherit stdio
113
+ console.log(`\n ${style.bold('Session interactive')} → ${style.nemesisAccent(agentName)}`);
114
+ console.log(` ${style.dim('Session ID')}: ${lane.session_id}\n`);
115
+
116
+ const child = spawn('claude', ['--resume', lane.session_id, '--dangerously-skip-permissions'], {
117
+ stdio: 'inherit',
118
+ env: { ...process.env },
119
+ });
120
+
121
+ await new Promise((resolve) => child.on('close', resolve));
122
+ }
123
+ }