@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,572 @@
1
+ import { teamAdd, teamList, teamInspect, teamRemove } from '../../lib/core/team.js';
2
+ import { readRegistry, writeRegistry, getLanes, getPM, updateLaneField } from '../../lib/core/registry.js';
3
+ import { createHcmClient } from '../../lib/sync/hcm-client.js';
4
+ import { pickProfile } from '../../lib/core/profile-picker.js';
5
+ import { askText, askMultiChoice, askConfirm } from '../../lib/ui/prompt.js';
6
+ import { interactiveMenu } from '../../lib/ui/menu.js';
7
+ import { renderTree } from '../../lib/ui/tree.js';
8
+ import { renderTable } from '../../lib/ui/table.js';
9
+ import { titledBox } from '../../lib/ui/box.js';
10
+ import { style } from '../../lib/ui/colors.js';
11
+ import { isPidAlive } from '../../lib/kairos/pid-checker.js';
12
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
13
+ import { join } from 'node:path';
14
+ import { ensureProject, pickFromList, getFlag, sanitizeName, BACK_ITEM, HOME_ITEM } from './_helpers.js';
15
+
16
+ const HELP = `
17
+ nemesis team — Gestion equipe agents
18
+
19
+ Usage: nemesis team <subcommand> [options]
20
+
21
+ Subcommands:
22
+ add Onboard un nouvel agent
23
+ list Affiche l'organigramme
24
+ panneau [agent] Panneau agent (alias: inspect)
25
+ leader [agent] Active/desactive le flag leader (--on|--off)
26
+ remove [agent] Desactive un agent (archive)
27
+
28
+ Options (add):
29
+ --profile <id> Profil Kairos
30
+ --name <name> Nom de l'agent
31
+ --role <role> Role sur le projet
32
+ --reports-to <a> Hierarchie
33
+ --no-prompt Mode non-interactif
34
+ -h, --help Aide
35
+ `;
36
+
37
+ export async function handler({ args, flags, config }) {
38
+ if (flags.help || args.includes('--help') || args.includes('-h')) {
39
+ console.log(HELP);
40
+ return;
41
+ }
42
+
43
+ let sub = args[0];
44
+ let subArgs = args.slice(1);
45
+
46
+ if (!sub) {
47
+ sub = await interactiveMenu([
48
+ { label: 'list', value: 'list', description: 'Affiche l\'organigramme' },
49
+ { label: 'add', value: 'add', description: 'Onboard un nouvel agent' },
50
+ { label: 'panneau', value: 'panneau', description: 'Panneau agent' },
51
+ { label: 'leader', value: 'leader', description: 'Active/desactive le flag leader' },
52
+ { label: 'remove', value: 'remove', description: 'Desactive un agent' },
53
+ BACK_ITEM,
54
+ HOME_ITEM,
55
+ ], { title: 'nemesis team' });
56
+ if (!sub || sub === '__back__') return '__back__';
57
+ if (sub === '__home__') return '__home__';
58
+ subArgs = [];
59
+ }
60
+
61
+ switch (sub) {
62
+ case 'add':
63
+ return handleAdd(subArgs, flags, config);
64
+ case 'list':
65
+ return handleList(subArgs, flags, config);
66
+ case 'panneau':
67
+ case 'inspect':
68
+ return handlePanneau(subArgs, flags, config);
69
+ case 'leader':
70
+ return handleLeader(subArgs, flags, config);
71
+ case 'remove':
72
+ return handleRemove(subArgs, flags, config);
73
+ default:
74
+ console.error(`Sous-commande inconnue : ${sub}`);
75
+ console.log(HELP);
76
+ }
77
+ }
78
+
79
+ function getAgentLanes(project) {
80
+ const registry = readRegistry(project.hcm_dir);
81
+ return registry ? getLanes(registry) : [];
82
+ }
83
+
84
+ async function pickAgent(project, title = 'Choisir un agent') {
85
+ const lanes = getAgentLanes(project);
86
+ if (lanes.length === 0) {
87
+ console.log(`\n ${style.dim('Aucun agent enregistre.')}`);
88
+ return null;
89
+ }
90
+ return pickFromList(
91
+ lanes.map(l => ({ id: l.id || l.name, title: l.role, ...l })),
92
+ {
93
+ title,
94
+ labelFn: (l) => `${l.name || l.id} \u2014 ${l.role || style.dim('(pas de role)')}`,
95
+ },
96
+ );
97
+ }
98
+
99
+ async function handleAdd(args, flags, config) {
100
+ const project = await ensureProject(config, flags);
101
+ if (!project) return;
102
+
103
+ console.log(`\n ${style.bold('Projet')} : ${style.nemesisAccent(project.id)} ${style.dim('(detecte depuis .nemesis/)')}\n`);
104
+
105
+ const noPrompt = args.includes('--no-prompt');
106
+
107
+ // Profile Kairos — fetch from HCM or manual entry
108
+ let profileId = getFlag(args, '--profile');
109
+ let kairosData = null;
110
+ let hcmLive = !!project.hcm_status?.connected;
111
+ if (!profileId && !noPrompt) {
112
+ const client = createHcmClient();
113
+ const picked = await pickProfile(client, { write: console.log });
114
+ profileId = picked.id;
115
+ kairosData = picked.kairosData;
116
+ if (kairosData) hcmLive = true;
117
+ }
118
+
119
+ // Agent name
120
+ let agentName = getFlag(args, '--name');
121
+ if (!agentName && !noPrompt) {
122
+ agentName = await askText('Nom de l\'agent');
123
+ }
124
+ if (!agentName) {
125
+ console.log(`\n ${style.red('Nom de l\'agent requis.')}\n`);
126
+ return;
127
+ }
128
+ agentName = sanitizeName(agentName);
129
+
130
+ // Role
131
+ let role = getFlag(args, '--role');
132
+ if (!role && !noPrompt) {
133
+ role = await askText('Role sur le projet');
134
+ }
135
+ if (!role) {
136
+ console.log(`\n ${style.red('Role requis.')}\n`);
137
+ return;
138
+ }
139
+
140
+ // Reports to — interactive menu
141
+ let reportsTo = getFlag(args, '--reports-to');
142
+ if (!reportsTo && !noPrompt) {
143
+ const registry = readRegistry(project.hcm_dir);
144
+ const pm = getPM(registry);
145
+ const pmName = pm?.name || 'PM';
146
+ const reportsItems = [{ label: `${pmName} (PM)`, value: pmName }];
147
+ const lanes = getAgentLanes(project);
148
+ for (const lane of lanes) {
149
+ reportsItems.push({
150
+ label: `${lane.name || lane.id} (${lane.role})`,
151
+ value: (lane.name || lane.id).split(' (')[0],
152
+ });
153
+ }
154
+ reportsTo = await interactiveMenu(reportsItems, { title: 'Reporte a' });
155
+ if (!reportsTo) reportsTo = pmName;
156
+ }
157
+
158
+ // OdMs
159
+ let odmIds = [];
160
+ if (!noPrompt) {
161
+ const odmDir = join(project.hcm_dir, 'odm');
162
+ if (existsSync(odmDir)) {
163
+ const odmFiles = readdirSync(odmDir)
164
+ .filter(f => f.endsWith('.json') && !f.startsWith('legacy_'));
165
+ if (odmFiles.length > 0) {
166
+ const odmChoices = odmFiles.map(f => {
167
+ try {
168
+ const data = JSON.parse(readFileSync(join(odmDir, f), 'utf-8'));
169
+ return {
170
+ label: `${data.odm_meta?.odm_id || f} \u2014 ${data.odm_payload?.cadrage?.title || ''}`,
171
+ value: data.odm_meta?.odm_id || f,
172
+ };
173
+ } catch {
174
+ return { label: f, value: f };
175
+ }
176
+ });
177
+ odmIds = await askMultiChoice('OdMs a assigner (optionnel)', odmChoices);
178
+ }
179
+ }
180
+ }
181
+
182
+ // Execute
183
+ console.log('\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n');
184
+
185
+ const result = await teamAdd({
186
+ root: config.cwd,
187
+ agentName,
188
+ role,
189
+ reportsTo,
190
+ profileId: profileId || null,
191
+ kairosData,
192
+ odmIds,
193
+ environment: {
194
+ hcm: hcmLive,
195
+ files: [],
196
+ },
197
+ });
198
+
199
+ for (const step of result.results) {
200
+ console.log(` ${style.green('\u2713')} ${style.bold(step.label)}`);
201
+ console.log(` ${style.dim('\u2192')} ${step.detail}`);
202
+ console.log('');
203
+ }
204
+
205
+ console.log(' \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n');
206
+
207
+ const { runOnboarding } = await import('../../lib/core/agent-launcher.js');
208
+ const onboard = await runOnboarding(result.promptPath, result.agentName);
209
+ if (onboard.displayBox) {
210
+ console.log('');
211
+ console.log(onboard.displayBox);
212
+ console.log('');
213
+ }
214
+
215
+ // Always persist onboarding metadata
216
+ const reg = readRegistry(result.project.hcm_dir);
217
+ if (reg) {
218
+ updateLaneField(reg, result.agentName, 'last_onboarding', new Date().toISOString());
219
+ if (onboard.sessionId) {
220
+ updateLaneField(reg, result.agentName, 'session_id', onboard.sessionId);
221
+ }
222
+ if (onboard.pid) {
223
+ updateLaneField(reg, result.agentName, 'pid', onboard.pid);
224
+ }
225
+ writeRegistry(result.project.hcm_dir, reg);
226
+ }
227
+
228
+ // Post-onboarding menu — 2 choices
229
+ if (onboard.sessionId) {
230
+ console.log('');
231
+ console.log(' \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550');
232
+ console.log(` ${style.bold(result.agentName)} est pret.`);
233
+ console.log('');
234
+
235
+ const postAction = await interactiveMenu([
236
+ { label: 'Rejoindre la session interactive', value: 'join', description: '' },
237
+ { label: 'Retourner au menu principal', value: 'back', description: '' },
238
+ ], { title: 'Que souhaitez-vous faire ?' });
239
+
240
+ console.log(' \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550');
241
+
242
+ if (postAction === 'join') {
243
+ const { spawn: spawnChild } = await import('node:child_process');
244
+ console.log(`\n ${style.bold('Session interactive')} \u2192 ${style.nemesisAccent(result.agentName)}`);
245
+ console.log(` ${style.dim('Session ID')}: ${onboard.sessionId}\n`);
246
+
247
+ const child = spawnChild('claude', ['--resume', onboard.sessionId, '--dangerously-skip-permissions'], {
248
+ stdio: 'inherit',
249
+ env: { ...process.env },
250
+ });
251
+
252
+ await new Promise((resolve) => child.on('close', resolve));
253
+ process.exit(0);
254
+ }
255
+ }
256
+ }
257
+
258
+ async function handleList(args, flags, config) {
259
+ const project = await ensureProject(config, flags);
260
+ if (!project) return;
261
+
262
+ console.log(`\n ${style.bold('Projet')} : ${style.nemesisAccent(project.id)}\n`);
263
+
264
+ const result = teamList(project.hcm_dir);
265
+ if (!result || (result.tree && result.tree.children && result.tree.children.length === 0)) {
266
+ console.log(` ${style.dim('Equipe vide \u2014 seul le PM est enregistre.')}`);
267
+ const doAdd = await askConfirm('Onboard un agent maintenant ?', true);
268
+ if (doAdd) {
269
+ return handleAdd([], flags, config);
270
+ }
271
+ console.log('');
272
+ return;
273
+ }
274
+
275
+ console.log(renderTree(result.tree));
276
+ console.log('');
277
+ }
278
+
279
+ export { isPidAlive as checkAgentAlive } from '../../lib/kairos/pid-checker.js';
280
+
281
+ async function handlePanneau(args, flags, config) {
282
+ const project = await ensureProject(config, flags);
283
+ if (!project) return;
284
+
285
+ let agentId = args[0];
286
+
287
+ // If no agent specified → picker from registry
288
+ if (!agentId) {
289
+ agentId = await pickAgent(project, 'Quel agent inspecter ?');
290
+ if (!agentId) {
291
+ const doAdd = await askConfirm('Onboard un agent maintenant ?', false);
292
+ if (doAdd) return handleAdd([], flags, config);
293
+ return;
294
+ }
295
+ }
296
+
297
+ const result = teamInspect(agentId, config.cwd);
298
+
299
+ if (flags.format === 'json') {
300
+ const { formatOutput } = await import('../../lib/ui/format.js');
301
+ console.log(formatOutput(result, 'json'));
302
+ return;
303
+ }
304
+
305
+ const a = result.agent;
306
+ const alive = isPidAlive(a.pid);
307
+ const statusText = alive
308
+ ? `${style.green('\u25CF')} actif (PID ${a.pid})`
309
+ : a.session_id
310
+ ? `${style.yellow('\u25CF')} en veille (session disponible)`
311
+ : `${style.dim('\u25CB')} pas de session`;
312
+
313
+ // --- Zone 1 : Identite ---
314
+ const identityLines = [
315
+ `Role ${a.role}${a.leader ? ' ★ Leader' : ''}`,
316
+ ...(a.profiles && a.profiles.length > 0 ? [`Profil ${a.profiles[0]}`] : []),
317
+ `Reporte a ${a.reports_to || '?'}`,
318
+ `Statut ${statusText}`,
319
+ ];
320
+
321
+ if (result.memory) {
322
+ const memDate = result.memory.modified
323
+ ? result.memory.modified.split('-').reverse().join('/')
324
+ : '';
325
+ identityLines.push(`Memoire ${result.memory.path}`);
326
+ identityLines.push(` ${result.memory.path.split('/').pop()} \u2014 ${result.memory.size} \u2014 maj ${memDate}`);
327
+ } else {
328
+ identityLines.push(`Memoire ${style.dim('(introuvable)')}`);
329
+ }
330
+
331
+ console.log('');
332
+ console.log(titledBox(a.name || a.id, identityLines, { border: style.nemesisAccent }));
333
+
334
+ // --- Zone 2 : Commandes ---
335
+ const shortName = (a.name || a.id).replace(/^Agent_/, '');
336
+ const cmdLines = [
337
+ 'Session interactive',
338
+ ` ${style.bold(`nemesis run agent:${shortName}`)}`,
339
+ '',
340
+ 'Inject prompt',
341
+ ` ${style.bold(`nemesis run agent:${shortName} --p "<prompt>"`)}`,
342
+ ];
343
+ console.log(titledBox('Commandes', cmdLines, { border: style.nemesisAccent }));
344
+
345
+ // --- Zone 3 : Actions ---
346
+ let stayInPanel = true;
347
+ while (stayInPanel) {
348
+ const action = await interactiveMenu([
349
+ { label: 'Rejoindre la session', value: 'join', description: '' },
350
+ { label: 'Injecter un prompt', value: 'inject', description: '' },
351
+ { label: 'Voir les OdMs assignes', value: 'odms', description: '' },
352
+ { label: 'Voir les CRs recents', value: 'crs', description: '' },
353
+ { label: a.leader ? 'Desactiver Leader' : 'Activer Leader', value: 'leader', description: '' },
354
+ { label: 'Archiver l\'agent', value: 'archive', description: '' },
355
+ BACK_ITEM,
356
+ HOME_ITEM,
357
+ ], { title: 'Actions' });
358
+
359
+ if (!action || action === '__back__') {
360
+ break;
361
+ }
362
+ if (action === '__home__') return '__home__';
363
+
364
+ switch (action) {
365
+ case 'join':
366
+ await actionJoin(a, args, flags, config);
367
+ stayInPanel = false;
368
+ break;
369
+ case 'inject':
370
+ await actionInject(a);
371
+ break;
372
+ case 'odms':
373
+ actionShowOdms(result);
374
+ break;
375
+ case 'crs':
376
+ actionShowCrs(result);
377
+ break;
378
+ case 'leader': {
379
+ const reg = readRegistry(project.hcm_dir);
380
+ const newVal = !a.leader;
381
+ updateLaneField(reg, a.id, 'leader', newVal);
382
+ writeRegistry(project.hcm_dir, reg);
383
+ a.leader = newVal;
384
+ if (newVal) {
385
+ console.log(`\n ${style.green('★')} leader: ${style.bold('active')}\n`);
386
+ } else {
387
+ console.log(`\n ${style.dim('○')} leader: ${style.bold('desactive')}\n`);
388
+ }
389
+ break;
390
+ }
391
+ case 'archive':
392
+ await actionArchive(a, config);
393
+ stayInPanel = false;
394
+ break;
395
+ }
396
+ }
397
+ }
398
+
399
+ export async function actionJoin(agent, args, flags, config) {
400
+ if (!agent.session_id) {
401
+ console.log(`\n ${style.dim('Pas de session. Lancez l\'onboarding d\'abord.')}`);
402
+ const doAdd = await askConfirm('Lancer l\'onboarding ?', true);
403
+ if (doAdd) return handleAdd(args || [], flags || {}, config);
404
+ return;
405
+ }
406
+ // session_id exists → resume it (PID mort = normal, l'utilisateur n'etait pas connecte)
407
+ const { spawn: spawnChild } = await import('node:child_process');
408
+ console.log(`\n ${style.bold('Session interactive')} \u2192 ${style.nemesisAccent(agent.name || agent.id)}`);
409
+ console.log(` ${style.dim('Session ID')}: ${agent.session_id}\n`);
410
+ const child = spawnChild('claude', ['--resume', agent.session_id, '--dangerously-skip-permissions'], {
411
+ stdio: 'inherit',
412
+ env: { ...process.env },
413
+ });
414
+ await new Promise((resolve) => child.on('close', resolve));
415
+ process.exit(0);
416
+ }
417
+
418
+ export async function actionInject(agent) {
419
+ if (!agent.session_id) {
420
+ console.log(`\n ${style.dim('Pas de session active. Lancez l\'onboarding d\'abord.')}\n`);
421
+ return;
422
+ }
423
+ const prompt = await askText('Prompt');
424
+ if (!prompt) return;
425
+ const { spawn: spawnChild } = await import('node:child_process');
426
+ const { createStreamBox } = await import('../../lib/ui/streambox.js');
427
+ const box = createStreamBox(4);
428
+ const child = spawnChild('claude', [
429
+ '--resume', agent.session_id,
430
+ '-p', prompt,
431
+ '--output-format', 'stream-json',
432
+ '--verbose',
433
+ '--dangerously-skip-permissions',
434
+ ], { stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env } });
435
+
436
+ let finalResult = '';
437
+ child.stdout.on('data', (chunk) => {
438
+ for (const raw of chunk.toString().split('\n')) {
439
+ if (!raw.trim()) continue;
440
+ try {
441
+ const evt = JSON.parse(raw);
442
+ if (evt.type === 'assistant' && evt.message?.content) {
443
+ for (const block of evt.message.content) {
444
+ if (block.type === 'text' && block.text) {
445
+ for (const tl of block.text.split('\n').filter(Boolean)) {
446
+ box.push(style.dim(tl));
447
+ }
448
+ }
449
+ }
450
+ }
451
+ if (evt.type === 'result') finalResult = evt.result || '';
452
+ } catch { /* ignore */ }
453
+ }
454
+ });
455
+ await new Promise((resolve) => child.on('close', resolve));
456
+ box.clear();
457
+ if (finalResult) {
458
+ console.log(titledBox('Reponse', finalResult.split('\n'), { border: style.nemesisAccent }));
459
+ console.log('');
460
+ }
461
+ }
462
+
463
+ function actionShowOdms(inspectResult) {
464
+ if (inspectResult.odms.length === 0) {
465
+ console.log(`\n ${style.dim('Aucun OdM assigne.')}\n`);
466
+ return;
467
+ }
468
+ console.log('');
469
+ console.log(renderTable(
470
+ [
471
+ { key: 'id', label: 'OdM' },
472
+ { key: 'status', label: 'Status' },
473
+ { key: 'title', label: 'Titre' },
474
+ ],
475
+ inspectResult.odms,
476
+ ));
477
+ console.log('');
478
+ }
479
+
480
+ function actionShowCrs(inspectResult) {
481
+ if (inspectResult.crs.length === 0) {
482
+ console.log(`\n ${style.dim('Aucun CR recent.')}\n`);
483
+ return;
484
+ }
485
+ console.log('');
486
+ for (const cr of inspectResult.crs) {
487
+ console.log(` - ${cr.id} (${cr.date})`);
488
+ }
489
+ console.log('');
490
+ }
491
+
492
+ async function actionArchive(agent, config) {
493
+ const confirmed = await askConfirm(
494
+ `Confirmer la desinscription de ${agent.name || agent.id} ?`, false
495
+ );
496
+ if (!confirmed) {
497
+ console.log(' Annule.\n');
498
+ return;
499
+ }
500
+ teamRemove(agent.id, config.cwd);
501
+ console.log(`\n ${style.green('\u2713')} Agent archive.\n`);
502
+ }
503
+
504
+ async function handleLeader(args, flags, config) {
505
+ const project = await ensureProject(config, flags);
506
+ if (!project) return;
507
+
508
+ let agentId = args[0];
509
+
510
+ if (!agentId) {
511
+ agentId = await pickAgent(project, 'Quel agent ?');
512
+ if (!agentId) return;
513
+ }
514
+
515
+ const registry = readRegistry(project.hcm_dir);
516
+ if (!registry) {
517
+ console.log(`\n ${style.red('Registry introuvable.')}\n`);
518
+ return;
519
+ }
520
+
521
+ const lanes = getLanes(registry);
522
+ const lane = lanes.find(l => l.id === agentId || l.name === agentId);
523
+ if (!lane) {
524
+ console.log(`\n ${style.red(`Agent ${agentId} non trouve.`)}\n`);
525
+ return;
526
+ }
527
+
528
+ let newValue;
529
+ if (flags.on) {
530
+ newValue = true;
531
+ } else if (flags.off) {
532
+ newValue = false;
533
+ } else {
534
+ newValue = !lane.leader;
535
+ }
536
+
537
+ updateLaneField(registry, lane.id, 'leader', newValue);
538
+ writeRegistry(project.hcm_dir, registry);
539
+
540
+ if (newValue) {
541
+ console.log(`\n ${style.green('★')} leader: ${style.bold('active')} pour ${lane.name || lane.id}\n`);
542
+ } else {
543
+ console.log(`\n ${style.dim('○')} leader: ${style.bold('desactive')} pour ${lane.name || lane.id}\n`);
544
+ }
545
+ }
546
+
547
+ async function handleRemove(args, flags, config) {
548
+ const project = await ensureProject(config, flags);
549
+ if (!project) return;
550
+
551
+ let agentId = args[0];
552
+
553
+ // If no agent specified → picker from registry
554
+ if (!agentId) {
555
+ agentId = await pickAgent(project, 'Quel agent desactiver ?');
556
+ if (!agentId) return;
557
+ }
558
+
559
+ const confirmed = await askConfirm(`Confirmer la desinscription de ${agentId} ?`, false);
560
+ if (!confirmed) {
561
+ console.log(' Annule.\n');
562
+ return;
563
+ }
564
+
565
+ const _result = teamRemove(agentId, config.cwd);
566
+
567
+ console.log(`\n ${style.green('\u2713')} Registre mis a jour (lane retiree)`);
568
+ console.log(` ${style.green('\u2713')} CLAUDE.md mis a jour (ligne retiree)`);
569
+ console.log(` ${style.green('\u2713')} memory.md conserve (archive)`);
570
+ console.log(` ${style.green('\u2713')} Contributor card conservee (archive)`);
571
+ console.log(`\n L'agent est desactive. Ses fichiers sont conserves pour tracabilite.\n`);
572
+ }
package/src/config.js ADDED
@@ -0,0 +1,84 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
5
+
6
+ const CONFIG_DIR = join(homedir(), '.nemesis');
7
+ const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
8
+
9
+ const DEFAULT_CONFIG = {
10
+ version: '1.0.0',
11
+ defaults: {
12
+ hcm_url: 'https://hcm.arkalabs.app',
13
+ hcm_api_key_env: 'HCM_API_KEY',
14
+ claude_md_path: join(homedir(), '.claude', 'CLAUDE.md'),
15
+ onboarding_template: join(homedir(), '.claude', 'ONBOARDING_AGENT.md'),
16
+ hcm_protocol: join(homedir(), '.claude', 'HCM_PROTOCOL.md'),
17
+ },
18
+ current_project: null,
19
+ };
20
+
21
+ async function readConfigFile() {
22
+ try {
23
+ const raw = await readFile(CONFIG_FILE, 'utf-8');
24
+ return JSON.parse(raw);
25
+ } catch {
26
+ return {};
27
+ }
28
+ }
29
+
30
+ function detectProject(cwd) {
31
+ // Priority 1: .nemesis/HCM/project/
32
+ let baseDir = '.nemesis';
33
+ let projectDir = join(cwd, '.nemesis', 'HCM', 'project');
34
+
35
+ // Fallback: .owner/HCM/project/
36
+ if (!existsSync(projectDir)) {
37
+ const legacyDir = join(cwd, '.owner', 'HCM', 'project');
38
+ if (existsSync(legacyDir)) {
39
+ baseDir = '.owner';
40
+ projectDir = legacyDir;
41
+ } else {
42
+ return null;
43
+ }
44
+ }
45
+
46
+ try {
47
+ const files = readdirSync(projectDir);
48
+ const contextFile = files.find(f => f.startsWith('CONTEXT_PROJECT_') && f.endsWith('.json'));
49
+ if (!contextFile) return null;
50
+
51
+ const raw = readFileSync(join(projectDir, contextFile), 'utf-8');
52
+ const ctx = JSON.parse(raw);
53
+ return {
54
+ id: ctx.project_meta?.project_id || contextFile.replace('CONTEXT_PROJECT_', '').replace('.json', ''),
55
+ name: ctx.project_meta?.name || null,
56
+ context_file: join(projectDir, contextFile),
57
+ hcm_dir: join(cwd, baseDir, 'HCM'),
58
+ root: cwd,
59
+ legacy: baseDir === '.owner',
60
+ };
61
+ } catch {
62
+ return null;
63
+ }
64
+ }
65
+
66
+ export async function loadConfig(cwd = process.cwd()) {
67
+ const fileConfig = await readConfigFile();
68
+ const merged = {
69
+ ...DEFAULT_CONFIG,
70
+ ...fileConfig,
71
+ defaults: { ...DEFAULT_CONFIG.defaults, ...fileConfig.defaults },
72
+ };
73
+
74
+ const project = detectProject(cwd);
75
+ if (project) {
76
+ merged.current_project = project;
77
+ }
78
+
79
+ merged.cwd = cwd;
80
+ merged.config_dir = CONFIG_DIR;
81
+ merged.config_file = CONFIG_FILE;
82
+
83
+ return merged;
84
+ }
package/src/index.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { run } from './cli.js';
4
+
5
+ run(process.argv.slice(2));
@@ -0,0 +1,10 @@
1
+ {
2
+ "project_meta": {
3
+ "project_id": "",
4
+ "name": "",
5
+ "description": "",
6
+ "status": "ACTIVE",
7
+ "created_at": "",
8
+ "created_by": "PM"
9
+ }
10
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "schema": "arka.contributor_card.v1",
3
+ "contributor_meta": {
4
+ "contributor_id": "",
5
+ "kind": "",
6
+ "project_id": ""
7
+ },
8
+ "contributor_payload": {
9
+ "identity": {
10
+ "display_name": "",
11
+ "role": "",
12
+ "organization": "Arka Labs"
13
+ },
14
+ "expertise": {
15
+ "domains": [],
16
+ "description": ""
17
+ },
18
+ "runtime": null,
19
+ "capabilities": [],
20
+ "profiles": []
21
+ }
22
+ }