@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.
- package/LICENSE +201 -0
- package/README.md +668 -0
- package/lib/core/agent-launcher.js +193 -0
- package/lib/core/audit.js +210 -0
- package/lib/core/connexions.js +80 -0
- package/lib/core/flowmap/api.js +111 -0
- package/lib/core/flowmap/cli-helpers.js +80 -0
- package/lib/core/flowmap/machine.js +281 -0
- package/lib/core/flowmap/persistence.js +83 -0
- package/lib/core/generators.js +183 -0
- package/lib/core/inbox.js +275 -0
- package/lib/core/logger.js +20 -0
- package/lib/core/mission.js +109 -0
- package/lib/core/notewriter/config.js +36 -0
- package/lib/core/notewriter/cr.js +237 -0
- package/lib/core/notewriter/log.js +112 -0
- package/lib/core/notewriter/notes.js +168 -0
- package/lib/core/notewriter/paths.js +45 -0
- package/lib/core/notewriter/reader.js +121 -0
- package/lib/core/notewriter/registry.js +80 -0
- package/lib/core/odm.js +191 -0
- package/lib/core/profile-picker.js +323 -0
- package/lib/core/project.js +287 -0
- package/lib/core/registry.js +129 -0
- package/lib/core/secrets.js +137 -0
- package/lib/core/services.js +45 -0
- package/lib/core/team.js +287 -0
- package/lib/core/templates.js +80 -0
- package/lib/kairos/agent-runner.js +261 -0
- package/lib/kairos/claude-invoker.js +90 -0
- package/lib/kairos/context-injector.js +331 -0
- package/lib/kairos/context-loader.js +108 -0
- package/lib/kairos/context-writer.js +45 -0
- package/lib/kairos/dispatcher-router.js +173 -0
- package/lib/kairos/dispatcher.js +139 -0
- package/lib/kairos/event-bus.js +287 -0
- package/lib/kairos/event-router.js +131 -0
- package/lib/kairos/flowmap-bridge.js +120 -0
- package/lib/kairos/hook-handlers.js +351 -0
- package/lib/kairos/hook-installer.js +207 -0
- package/lib/kairos/hook-prompts.js +54 -0
- package/lib/kairos/leader-rules.js +94 -0
- package/lib/kairos/pid-checker.js +108 -0
- package/lib/kairos/situation-detector.js +123 -0
- package/lib/sync/fallback-engine.js +97 -0
- package/lib/sync/hcm-client.js +170 -0
- package/lib/sync/health.js +47 -0
- package/lib/sync/llm-client.js +387 -0
- package/lib/sync/nemesis-client.js +379 -0
- package/lib/sync/service-session.js +74 -0
- package/lib/sync/sync-engine.js +178 -0
- package/lib/ui/box.js +104 -0
- package/lib/ui/brand.js +42 -0
- package/lib/ui/colors.js +57 -0
- package/lib/ui/dashboard.js +580 -0
- package/lib/ui/error-hints.js +49 -0
- package/lib/ui/format.js +61 -0
- package/lib/ui/menu.js +306 -0
- package/lib/ui/note-card.js +198 -0
- package/lib/ui/note-colors.js +26 -0
- package/lib/ui/note-detail.js +297 -0
- package/lib/ui/note-filters.js +252 -0
- package/lib/ui/note-views.js +283 -0
- package/lib/ui/prompt.js +81 -0
- package/lib/ui/spinner.js +139 -0
- package/lib/ui/streambox.js +46 -0
- package/lib/ui/table.js +42 -0
- package/lib/ui/tree.js +33 -0
- package/package.json +53 -0
- package/src/cli.js +457 -0
- package/src/commands/_helpers.js +119 -0
- package/src/commands/audit.js +187 -0
- package/src/commands/auth.js +316 -0
- package/src/commands/doctor.js +243 -0
- package/src/commands/hcm.js +147 -0
- package/src/commands/inbox.js +333 -0
- package/src/commands/init.js +160 -0
- package/src/commands/kairos.js +216 -0
- package/src/commands/kars.js +134 -0
- package/src/commands/mission.js +275 -0
- package/src/commands/notes.js +316 -0
- package/src/commands/notewriter.js +296 -0
- package/src/commands/odm.js +329 -0
- package/src/commands/orch.js +68 -0
- package/src/commands/project.js +123 -0
- package/src/commands/run.js +123 -0
- package/src/commands/services.js +705 -0
- package/src/commands/status.js +231 -0
- package/src/commands/team.js +572 -0
- package/src/config.js +84 -0
- package/src/index.js +5 -0
- package/templates/project-context.json +10 -0
- package/templates/template_CONTRIB-NAME.json +22 -0
- package/templates/template_CR-ODM-NAME-000.exemple.json +32 -0
- package/templates/template_DEC-NAME-000.json +18 -0
- package/templates/template_INTV-NAME-000.json +15 -0
- package/templates/template_MISSION_CONTRACT.json +46 -0
- package/templates/template_ODM-NAME-000.json +89 -0
- package/templates/template_REGISTRY-PROJECT.json +26 -0
- 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
|
+
}
|