@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,333 @@
1
+ /**
2
+ * nemesis inbox — Manage human document inbox pipeline.
3
+ *
4
+ * Subcommands:
5
+ * list List pending documents
6
+ * add <file> Add a document to the inbox
7
+ * process [docId] Dispatch a document to the Leader for MC draft
8
+ * history Show completed documents
9
+ *
10
+ * Options:
11
+ * -h, --help Help
12
+ */
13
+
14
+ import { resolve } from 'node:path';
15
+ import { ensureProject, pickFromList, BACK_ITEM, HOME_ITEM } from './_helpers.js';
16
+ import { readRegistry, getLanes } from '../../lib/core/registry.js';
17
+ import {
18
+ addDocument,
19
+ addDocumentFromText,
20
+ listDocuments,
21
+ readDocument,
22
+ markProcessing,
23
+ cleanupDone,
24
+ } from '../../lib/core/inbox.js';
25
+ import { launchHeadless } from '../../lib/kairos/agent-runner.js';
26
+ import { createDispatchTransaction } from '../../lib/kairos/dispatcher-router.js';
27
+ import { titledBox } from '../../lib/ui/box.js';
28
+ import { style } from '../../lib/ui/colors.js';
29
+ import { interactiveMenu } from '../../lib/ui/menu.js';
30
+ import { askText } from '../../lib/ui/prompt.js';
31
+ import { formatError, COMMON_HINTS } from '../../lib/ui/error-hints.js';
32
+
33
+ export async function handler({ args, flags, config }) {
34
+ if (flags.help) {
35
+ printHelp();
36
+ return;
37
+ }
38
+
39
+ const sub = args[0];
40
+ const subArgs = args.slice(1);
41
+
42
+ if (sub === 'list') return handleList(config);
43
+ if (sub === 'add') return handleAdd(subArgs, config);
44
+ if (sub === 'process') return handleProcess(subArgs, flags, config);
45
+ if (sub === 'history') return handleHistory(config);
46
+
47
+ // No subcommand → interactive menu
48
+ const choice = await interactiveMenu([
49
+ { label: 'Documents en attente', value: 'list', description: 'Voir les documents pending' },
50
+ { label: 'Ajouter un document', value: 'add', description: 'Ajouter un fichier a l\'inbox' },
51
+ { label: 'Traiter un document', value: 'process', description: 'Dispatcher au Leader' },
52
+ { label: 'Historique', value: 'history', description: 'Documents traites' },
53
+ BACK_ITEM, HOME_ITEM,
54
+ ], { title: 'nemesis › Inbox' });
55
+
56
+ if (!choice || choice === '__back__') return '__back__';
57
+ if (choice === '__home__') return '__home__';
58
+
59
+ if (choice === 'list') return handleList(config);
60
+ if (choice === 'add') return handleAdd(subArgs, config);
61
+ if (choice === 'process') return handleProcess(subArgs, flags, config);
62
+ if (choice === 'history') return handleHistory(config);
63
+ }
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // List pending documents
67
+ // ---------------------------------------------------------------------------
68
+
69
+ async function handleList(config) {
70
+ const root = config.cwd;
71
+ const docs = listDocuments(root, 'pending');
72
+ const processing = listDocuments(root, 'processing');
73
+
74
+ if (docs.length === 0 && processing.length === 0) {
75
+ console.log(`\n ${style.dim('Aucun document en attente.')}`);
76
+ console.log(` ${style.dim('Ajoutez un document :')} nemesis inbox add <fichier>\n`);
77
+ return;
78
+ }
79
+
80
+ if (docs.length > 0) {
81
+ console.log('');
82
+ const lines = docs.map(d => {
83
+ const age = formatRelativeTime(d.created_at);
84
+ return `${style.bold(d.id)} ${d.title} ${style.dim(age)} [${d.source}]`;
85
+ });
86
+ console.log(titledBox(`Pending (${docs.length})`, lines, { border: style.yellow }));
87
+ }
88
+
89
+ if (processing.length > 0) {
90
+ console.log('');
91
+ const lines = processing.map(d => {
92
+ const age = formatRelativeTime(d.processing_at || d.created_at);
93
+ return `${style.bold(d.id)} ${d.title} ${style.dim(age)} ${style.nemesisAccent('en cours')}`;
94
+ });
95
+ console.log(titledBox(`En cours (${processing.length})`, lines, { border: style.nemesisAccent }));
96
+ }
97
+ console.log('');
98
+ }
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // Add document
102
+ // ---------------------------------------------------------------------------
103
+
104
+ async function handleAdd(args, config) {
105
+ const root = config.cwd;
106
+
107
+ const filePath = args[0];
108
+
109
+ if (filePath) {
110
+ const absPath = resolve(root, filePath);
111
+ try {
112
+ const { id } = addDocument(root, absPath);
113
+ console.log(`\n ${style.green('✓')} Document ${style.bold(id)} ajoute a l'inbox.`);
114
+ console.log(` ${style.dim('→ Traitez-le :')} nemesis inbox process ${id}\n`);
115
+ } catch (err) {
116
+ console.log(`\n ${style.red('✗')} ${err.message}\n`);
117
+ }
118
+ return;
119
+ }
120
+
121
+ // Interactive: ask for text or file
122
+ const choice = await interactiveMenu([
123
+ { label: 'Depuis un fichier', value: 'file', description: 'Copier un fichier dans l\'inbox' },
124
+ { label: 'Texte libre', value: 'text', description: 'Saisir le contenu directement' },
125
+ BACK_ITEM,
126
+ ], { title: 'Type de document' });
127
+
128
+ if (!choice || choice === '__back__') return '__back__';
129
+
130
+ if (choice === 'file') {
131
+ const path = await askText('Chemin du fichier');
132
+ if (!path) return;
133
+ const absPath = resolve(root, path);
134
+ try {
135
+ const { id } = addDocument(root, absPath);
136
+ console.log(`\n ${style.green('✓')} Document ${style.bold(id)} ajoute.\n`);
137
+ } catch (err) {
138
+ console.log(`\n ${style.red('✗')} ${err.message}\n`);
139
+ }
140
+ } else {
141
+ const title = await askText('Titre du document');
142
+ const content = await askText('Contenu');
143
+ if (!content) return;
144
+ const { id } = addDocumentFromText(root, content, { title: title || undefined });
145
+ console.log(`\n ${style.green('✓')} Document ${style.bold(id)} ajoute.\n`);
146
+ }
147
+ }
148
+
149
+ // ---------------------------------------------------------------------------
150
+ // Process — dispatch document to Leader
151
+ // ---------------------------------------------------------------------------
152
+
153
+ async function handleProcess(args, flags, config) {
154
+ const project = await ensureProject(config, flags);
155
+ if (!project) return;
156
+
157
+ const root = config.cwd;
158
+ const hcmDir = project.hcm_dir;
159
+
160
+ // Find the Leader
161
+ const registry = readRegistry(hcmDir);
162
+ if (!registry) {
163
+ console.log(formatError('Registry introuvable.', COMMON_HINTS.REGISTRY_NOT_FOUND));
164
+ return;
165
+ }
166
+
167
+ const lanes = getLanes(registry);
168
+ const leader = lanes.find(l => l.leader);
169
+ if (!leader) {
170
+ console.log(formatError('Aucun Leader dans l\'equipe.', {
171
+ hint: 'Ajoutez un agent Leader avec nemesis team add.',
172
+ command: 'nemesis team add',
173
+ }));
174
+ return;
175
+ }
176
+
177
+ if (!leader.session_id) {
178
+ console.log(formatError(`Le Leader "${leader.name}" n'a pas de session.`, COMMON_HINTS.AGENT_NO_SESSION));
179
+ return;
180
+ }
181
+
182
+ // Pick document
183
+ let docId = args[0];
184
+ if (!docId) {
185
+ const docs = listDocuments(root, 'pending');
186
+ if (docs.length === 0) {
187
+ console.log(`\n ${style.dim('Aucun document en attente.')}\n`);
188
+ return;
189
+ }
190
+
191
+ docId = await pickFromList(docs, {
192
+ title: 'Document a traiter',
193
+ labelFn: d => `${d.id} ${d.title} ${style.dim(formatRelativeTime(d.created_at))}`,
194
+ });
195
+ if (!docId) return;
196
+ }
197
+
198
+ const doc = readDocument(root, docId, 'pending');
199
+ if (!doc) {
200
+ console.log(`\n ${style.red('✗')} Document "${docId}" introuvable dans pending.\n`);
201
+ return;
202
+ }
203
+
204
+ // Build inbox prompt for Leader
205
+ const prompt = buildInboxPrompt(doc);
206
+
207
+ // Create transaction
208
+ const txnResult = createDispatchTransaction({
209
+ from: 'PM',
210
+ to: leader.name,
211
+ projectId: project.id,
212
+ root,
213
+ });
214
+
215
+ // Launch headless
216
+ try {
217
+ const { pid, txnId } = await launchHeadless(leader, prompt, {
218
+ txnId: txnResult.txnId,
219
+ projectId: project.id,
220
+ root,
221
+ });
222
+
223
+ // Mark as processing
224
+ markProcessing(root, docId, { txnId, agentName: leader.name });
225
+
226
+ console.log('');
227
+ console.log(titledBox('Document dispatche au Leader', [
228
+ `${style.bold('Document')} : ${doc.metadata.title} (${docId})`,
229
+ `${style.bold('Leader')} : ${leader.name}`,
230
+ `${style.bold('TXN')} : ${txnId}`,
231
+ `${style.bold('PID')} : ${pid || 'detache'}`,
232
+ '',
233
+ `Le Leader analysera le document et proposera un Mission Contract.`,
234
+ ], { border: style.green }));
235
+ console.log('');
236
+ } catch (err) {
237
+ console.log(`\n ${style.red('✗')} ${err.message}\n`);
238
+ }
239
+ }
240
+
241
+ // ---------------------------------------------------------------------------
242
+ // History — completed documents
243
+ // ---------------------------------------------------------------------------
244
+
245
+ async function handleHistory(config) {
246
+ const root = config.cwd;
247
+ const docs = listDocuments(root, 'done');
248
+
249
+ if (docs.length === 0) {
250
+ console.log(`\n ${style.dim('Aucun document traite.')}\n`);
251
+ return;
252
+ }
253
+
254
+ console.log('');
255
+ const lines = docs.map(d => {
256
+ const age = formatRelativeTime(d.done_at || d.created_at);
257
+ const mc = d.mission_id ? ` → ${style.green(d.mission_id)}` : '';
258
+ return `${style.bold(d.id)} ${d.title} ${style.dim(age)}${mc}`;
259
+ });
260
+ console.log(titledBox(`Traites (${docs.length})`, lines, { border: style.green }));
261
+ console.log('');
262
+
263
+ // Cleanup old done
264
+ const cleaned = cleanupDone(root);
265
+ if (cleaned > 0) {
266
+ console.log(` ${style.dim(`${cleaned} document(s) archive(s) nettoye(s) (>30j)`)}\n`);
267
+ }
268
+ }
269
+
270
+ // ---------------------------------------------------------------------------
271
+ // Helpers
272
+ // ---------------------------------------------------------------------------
273
+
274
+ /**
275
+ * Build a prompt for the Leader to analyze an inbox document and produce an MC draft.
276
+ */
277
+ function buildInboxPrompt(doc) {
278
+ const parts = [
279
+ '# Document inbox a analyser',
280
+ '',
281
+ `**Titre** : ${doc.metadata.title}`,
282
+ `**Source** : ${doc.metadata.source}`,
283
+ `**Date** : ${doc.metadata.created_at}`,
284
+ '',
285
+ '## Contenu du document',
286
+ '',
287
+ doc.content,
288
+ '',
289
+ '## Instructions',
290
+ '',
291
+ 'Lis ce document attentivement. Propose un Mission Contract avec :',
292
+ '- Titre et objectifs',
293
+ '- Scope (in/out)',
294
+ '- Liste des OdMs necessaires avec assignation agent',
295
+ '- Criteres de succes',
296
+ '',
297
+ 'Depose le MC draft dans .nemesis/HCM/contracts/ au format JSON structure.',
298
+ 'Utilise le template mission_contract : nemesis mission init <ID>',
299
+ '',
300
+ ];
301
+ return parts.join('\n');
302
+ }
303
+
304
+ export { buildInboxPrompt };
305
+
306
+ function formatRelativeTime(isoDate) {
307
+ if (!isoDate) return '';
308
+ const diff = Date.now() - new Date(isoDate).getTime();
309
+ const minutes = Math.floor(diff / 60000);
310
+ if (minutes < 1) return 'a l\'instant';
311
+ if (minutes < 60) return `il y a ${minutes}m`;
312
+ const hours = Math.floor(minutes / 60);
313
+ if (hours < 24) return `il y a ${hours}h`;
314
+ const days = Math.floor(hours / 24);
315
+ return `il y a ${days}j`;
316
+ }
317
+
318
+ function printHelp() {
319
+ console.log(`
320
+ ${style.bold('nemesis inbox')} — Pipeline de documents humains
321
+
322
+ ${style.bold('Usage:')} nemesis inbox [subcommand] [options]
323
+
324
+ ${style.bold('Subcommands:')}
325
+ list Documents en attente
326
+ add <file> Ajouter un document a l'inbox
327
+ process [docId] Dispatcher au Leader pour MC draft
328
+ history Documents traites
329
+
330
+ ${style.bold('Options:')}
331
+ -h, --help Aide
332
+ `);
333
+ }
@@ -0,0 +1,160 @@
1
+ import { initProject, storeHcmStatus } from '../../lib/core/project.js';
2
+ import { createHcmClient } from '../../lib/sync/hcm-client.js';
3
+ import { askText } from '../../lib/ui/prompt.js';
4
+ import { createMultiStepSpinner } from '../../lib/ui/spinner.js';
5
+ import { renderBrandHeader } from '../../lib/ui/brand.js';
6
+ import { style } from '../../lib/ui/colors.js';
7
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
8
+ import { join } from 'node:path';
9
+ import { homedir } from 'node:os';
10
+
11
+ const HELP = `
12
+ nemesis init — Scaffold structure projet
13
+
14
+ Usage: nemesis init [options]
15
+
16
+ Options:
17
+ --bare Mode non-interactif (defaults)
18
+ --id <id> ID du projet
19
+ --name <name> Nom du projet
20
+ --desc <desc> Description
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 bare = args.includes('--bare');
31
+
32
+ if (!bare) {
33
+ const header = renderBrandHeader();
34
+ for (const line of header) console.log(line);
35
+ }
36
+
37
+ let projectId, projectName, description;
38
+
39
+ if (bare) {
40
+ projectId = getFlag(args, '--id') || 'PROJECT-001';
41
+ projectName = getFlag(args, '--name') || projectId;
42
+ description = getFlag(args, '--desc') || '';
43
+ } else {
44
+ projectName = await askText('Nom du projet');
45
+ projectId = await askText('ID projet', projectName.toUpperCase().replace(/\s+/g, '-'));
46
+ description = await askText('Description', '');
47
+ }
48
+
49
+ const multiSpinner = createMultiStepSpinner(['Initialisation du projet', 'Validation HCM']);
50
+ multiSpinner.start();
51
+
52
+ const result = initProject({
53
+ projectId,
54
+ projectName,
55
+ description,
56
+ root: config.cwd,
57
+ });
58
+
59
+ multiSpinner.nextStep();
60
+
61
+ // Validate HCM connectivity
62
+ const client = createHcmClient();
63
+ const ping = await client.ping();
64
+ storeHcmStatus(result.hcmDir, projectId, {
65
+ connected: ping.ok,
66
+ url: client.baseUrl,
67
+ latency: ping.latency,
68
+ checked_at: new Date().toISOString(),
69
+ ...(ping.error ? { error: ping.error } : {}),
70
+ });
71
+
72
+ if (ping.ok) {
73
+ multiSpinner.complete('Projet initialise');
74
+ writeMcpConfig(config.cwd, client.baseUrl);
75
+ } else {
76
+ multiSpinner.fail('HCM non connecte');
77
+ console.log(` ${style.dim('\u2192')} ${style.dim(ping.error || 'timeout')}`);
78
+ console.log(` ${style.dim('\u2192 Configurez avec')} nemesis auth login`);
79
+ }
80
+
81
+ console.log('');
82
+ console.log(' Cree :');
83
+ for (const dir of result.created) {
84
+ console.log(` ${style.green('\u2713')} .nemesis/HCM/${dir}/`);
85
+ }
86
+ if (result.created.length === 0) {
87
+ console.log(' (structure deja existante)');
88
+ }
89
+ console.log(` ${style.green('\u2713')} .nemesis/HCM/PROCESS.md`);
90
+ console.log(` ${style.green('\u2713')} .nemesis/template/ (templates)`);
91
+ console.log(` ${style.green('\u2713')} .nemesis/HCM/project/CONTEXT_PROJECT_${projectId}.json`);
92
+ console.log(` ${style.green('\u2713')} .nemesis/HCM/project/REGISTRY-${projectId}.json (PM seul)`);
93
+ console.log('');
94
+ }
95
+
96
+ /**
97
+ * Write or update .mcp.json at project root with HCM MCP server config.
98
+ * Reads saved auth from ~/.nemesis/config.json for API key and MCP server path.
99
+ * Creates the file if missing, updates the API key if stale.
100
+ */
101
+ function writeMcpConfig(root, hcmUrl) {
102
+ const mcpPath = join(root, '.mcp.json');
103
+ const saved = loadSavedNemesisConfig();
104
+ const apiKey = saved.hcm_api_key || process.env.HCM_API_KEY || '';
105
+ const mcpServerPath = saved.hcm_mcp_path || process.env.HCM_MCP_PATH || '';
106
+
107
+ // Update existing .mcp.json if API key is stale
108
+ if (existsSync(mcpPath)) {
109
+ try {
110
+ const existing = JSON.parse(readFileSync(mcpPath, 'utf-8'));
111
+ const currentKey = existing?.mcpServers?.HCM_Sipher?.env?.HCM_API_KEY || '';
112
+ if (apiKey && currentKey !== apiKey) {
113
+ existing.mcpServers.HCM_Sipher.env.HCM_API_KEY = apiKey;
114
+ if (hcmUrl) existing.mcpServers.HCM_Sipher.env.HCM_API_URL = hcmUrl;
115
+ writeFileSync(mcpPath, JSON.stringify(existing, null, 2) + '\n', 'utf-8');
116
+ console.log(` ${style.green('\u2713')} .mcp.json mis a jour (cle API synchronisee)`);
117
+ }
118
+ } catch { /* malformed — recreate below */ }
119
+ return;
120
+ }
121
+
122
+ if (!mcpServerPath) {
123
+ console.log(` ${style.yellow('\u26A0')} .mcp.json non genere — hcm_mcp_path absent`);
124
+ console.log(` ${style.dim('\u2192 Ajoutez "hcm_mcp_path" dans ~/.nemesis/config.json')}`);
125
+ console.log(` ${style.dim(' ou definissez HCM_MCP_PATH dans l\'environnement')}`);
126
+ return;
127
+ }
128
+
129
+ const mcpConfig = {
130
+ mcpServers: {
131
+ HCM_Sipher: {
132
+ type: 'stdio',
133
+ command: 'npx',
134
+ args: ['tsx', mcpServerPath],
135
+ env: {
136
+ HCM_API_URL: hcmUrl,
137
+ ...(apiKey ? { HCM_API_KEY: apiKey } : {}),
138
+ },
139
+ },
140
+ },
141
+ };
142
+
143
+ writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2) + '\n', 'utf-8');
144
+ console.log(` ${style.green('\u2713')} .mcp.json (MCP HCM pour tous les agents)`);
145
+ }
146
+
147
+ function loadSavedNemesisConfig() {
148
+ const configFile = join(homedir(), '.nemesis', 'config.json');
149
+ if (!existsSync(configFile)) return {};
150
+ try {
151
+ return JSON.parse(readFileSync(configFile, 'utf-8'));
152
+ } catch {
153
+ return {};
154
+ }
155
+ }
156
+
157
+ function getFlag(args, flag) {
158
+ const idx = args.indexOf(flag);
159
+ return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : null;
160
+ }