overmind-mcp 2.8.3 → 2.8.7

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 (98) hide show
  1. package/.mcp.json.example +20 -20
  2. package/README.md +143 -143
  3. package/bin/launch.bat +40 -0
  4. package/bin/launch.js +78 -0
  5. package/bin/launch.sh +46 -0
  6. package/bin/overmind-pool.mjs +248 -248
  7. package/bin/restart_mcp.bat +3 -0
  8. package/bin/start_server.bat +3 -0
  9. package/bin/test_mcp.bat +4 -0
  10. package/dist/bin/cli.js +13 -0
  11. package/dist/bin/cli.js.map +1 -1
  12. package/dist/bin/launch.js +78 -0
  13. package/dist/bridge/BridgeProxy.d.ts +52 -0
  14. package/dist/bridge/BridgeProxy.d.ts.map +1 -0
  15. package/dist/bridge/BridgeProxy.js +265 -0
  16. package/dist/bridge/BridgeProxy.js.map +1 -0
  17. package/dist/bridge/OverBridgeService.d.ts +96 -0
  18. package/dist/bridge/OverBridgeService.d.ts.map +1 -0
  19. package/dist/bridge/OverBridgeService.js +334 -0
  20. package/dist/bridge/OverBridgeService.js.map +1 -0
  21. package/dist/bridge/index.d.ts +11 -0
  22. package/dist/bridge/index.d.ts.map +1 -0
  23. package/dist/bridge/index.js +11 -0
  24. package/dist/bridge/index.js.map +1 -0
  25. package/dist/bridge/types.d.ts +132 -0
  26. package/dist/bridge/types.d.ts.map +1 -0
  27. package/dist/bridge/types.js +19 -0
  28. package/dist/bridge/types.js.map +1 -0
  29. package/dist/bridge/utils.d.ts +48 -0
  30. package/dist/bridge/utils.d.ts.map +1 -0
  31. package/dist/bridge/utils.js +128 -0
  32. package/dist/bridge/utils.js.map +1 -0
  33. package/dist/lib/config.d.ts +1 -0
  34. package/dist/lib/config.d.ts.map +1 -1
  35. package/dist/lib/config.js +13 -5
  36. package/dist/lib/config.js.map +1 -1
  37. package/dist/lib/envUtils.d.ts +2 -0
  38. package/dist/lib/envUtils.d.ts.map +1 -1
  39. package/dist/lib/envUtils.js +13 -2
  40. package/dist/lib/envUtils.js.map +1 -1
  41. package/dist/lib/logger.js +1 -1
  42. package/dist/lib/orchestration/dispatcher.d.ts.map +1 -1
  43. package/dist/lib/orchestration/dispatcher.js +0 -1
  44. package/dist/lib/orchestration/dispatcher.js.map +1 -1
  45. package/dist/lib/processRegistry.d.ts.map +1 -1
  46. package/dist/lib/processRegistry.js +34 -21
  47. package/dist/lib/processRegistry.js.map +1 -1
  48. package/dist/memory/PostgresMemoryProvider.d.ts.map +1 -1
  49. package/dist/memory/PostgresMemoryProvider.js +14 -3
  50. package/dist/memory/PostgresMemoryProvider.js.map +1 -1
  51. package/dist/server.d.ts.map +1 -1
  52. package/dist/server.js +2 -1
  53. package/dist/server.js.map +1 -1
  54. package/dist/services/AgentManager.d.ts.map +1 -1
  55. package/dist/services/AgentManager.js +473 -97
  56. package/dist/services/AgentManager.js.map +1 -1
  57. package/dist/services/ClaudeRunner.d.ts.map +1 -1
  58. package/dist/services/ClaudeRunner.js.map +1 -1
  59. package/dist/services/GeminiRunner.d.ts +26 -2
  60. package/dist/services/GeminiRunner.d.ts.map +1 -1
  61. package/dist/services/GeminiRunner.js +146 -136
  62. package/dist/services/GeminiRunner.js.map +1 -1
  63. package/dist/services/KiloRunner.d.ts.map +1 -1
  64. package/dist/services/KiloRunner.js +6 -1
  65. package/dist/services/KiloRunner.js.map +1 -1
  66. package/dist/services/NousHermesRunner.d.ts +1 -0
  67. package/dist/services/NousHermesRunner.d.ts.map +1 -1
  68. package/dist/services/NousHermesRunner.js +476 -549
  69. package/dist/services/NousHermesRunner.js.map +1 -1
  70. package/dist/tools/agent_control.d.ts +1 -1
  71. package/dist/tools/agent_control.d.ts.map +1 -1
  72. package/dist/tools/agent_control.js +4 -3
  73. package/dist/tools/agent_control.js.map +1 -1
  74. package/dist/tools/config_example.d.ts +1 -0
  75. package/dist/tools/config_example.d.ts.map +1 -1
  76. package/dist/tools/config_example.js +45 -2
  77. package/dist/tools/config_example.js.map +1 -1
  78. package/dist/tools/create_agent.d.ts +1 -1
  79. package/dist/tools/manage_agents.d.ts +2 -2
  80. package/dist/tools/run_agent.d.ts +2 -1
  81. package/dist/tools/run_agent.d.ts.map +1 -1
  82. package/dist/tools/run_agent.js +30 -3
  83. package/dist/tools/run_agent.js.map +1 -1
  84. package/dist/tools/run_agents_parallel.d.ts +2 -1
  85. package/dist/tools/run_agents_parallel.d.ts.map +1 -1
  86. package/dist/tools/run_gemini.d.ts +13 -0
  87. package/dist/tools/run_gemini.d.ts.map +1 -1
  88. package/dist/tools/run_gemini.js +6 -2
  89. package/dist/tools/run_gemini.js.map +1 -1
  90. package/dist/tools/run_hermes.d.ts +1 -0
  91. package/dist/tools/run_hermes.d.ts.map +1 -1
  92. package/dist/tools/run_hermes.js +22 -15
  93. package/dist/tools/run_hermes.js.map +1 -1
  94. package/docs/agent-http-tutorial.md +524 -524
  95. package/docs/provider-config-map.md +444 -0
  96. package/package.json +8 -10
  97. package/scripts/_db_check.py +10 -0
  98. package/scripts/status_check.py +20 -0
@@ -35,30 +35,36 @@ export class AgentManager {
35
35
  }
36
36
  }
37
37
  async listAgents(details = false) {
38
- const agentsDir = path.join(this.claudeDir, 'agents');
39
- await fs.mkdir(agentsDir, { recursive: true });
40
- const files = await fs.readdir(agentsDir);
41
- const agentFiles = files.filter((f) => f.endsWith('.md'));
42
- const agentsList = [];
38
+ const workspaceDir = getWorkspaceDir();
39
+ const claudeAgentsDir = path.join(this.claudeDir, 'agents');
40
+ await fs.mkdir(claudeAgentsDir, { recursive: true });
41
+ // 1. Scan .claude/agents/*.md
42
+ const claudeFiles = await fs.readdir(claudeAgentsDir).catch(() => []);
43
+ const claudeAgentNames = claudeFiles.filter((f) => f.endsWith('.md')).map((f) => f.replace('.md', ''));
44
+ // 2. Scan .overmind/hermes/agent_*
45
+ const hermesDir = path.join(workspaceDir, '.overmind', 'hermes');
46
+ const hermesDirs = await fs.readdir(hermesDir).catch(() => []);
47
+ const hermesAgentNames = hermesDirs
48
+ .filter((d) => d.startsWith('agent_'))
49
+ .map((d) => d.replace('agent_', ''));
50
+ const agentsMap = new Map();
43
51
  const availableServers = await this.getAvailableMcpServers();
44
- for (const file of agentFiles) {
45
- const agentName = file.replace('.md', '');
46
- if (!details) {
47
- agentsList.push(`- ${agentName}`);
48
- continue;
49
- }
50
- let info = `🤖 **${agentName}**`;
51
- const settingsPath = path.join(this.claudeDir, `settings_${agentName}.json`);
52
+ // Process Claude agents
53
+ for (const name of claudeAgentNames) {
54
+ let runner = 'claude';
55
+ let model = 'settings-default';
56
+ let provider = 'auto';
57
+ let mcpServers = [];
58
+ let missingConfig = false;
59
+ const settingsPath = path.join(this.claudeDir, `settings_${name}.json`);
52
60
  try {
53
61
  const settingsContent = await fs.readFile(settingsPath, 'utf-8');
54
62
  let settings = JSON.parse(settingsContent);
55
- // --- New interpolation logic ---
56
63
  settings = interpolateEnvVars(settings);
57
- const model = settings.env?.ANTHROPIC_MODEL || settings.model || 'settings-default';
58
- const runner = settings.runner || 'claude';
59
- const servers = settings.enabledMcpjsonServers || [];
60
- // Inférence du provider
61
- let provider = settings.provider || settings.env?.PROVIDER || 'auto';
64
+ model = settings.env?.ANTHROPIC_MODEL || settings.model || 'settings-default';
65
+ runner = settings.runner || 'claude';
66
+ mcpServers = settings.enabledMcpjsonServers || [];
67
+ provider = settings.provider || settings.env?.PROVIDER || 'auto';
62
68
  if (provider === 'auto') {
63
69
  if (settings.env?.MISTRAL_API_KEY)
64
70
  provider = 'mistral';
@@ -68,9 +74,7 @@ export class AgentManager {
68
74
  provider = 'nvidia';
69
75
  else if (settings.env?.GEMINI_API_KEY)
70
76
  provider = 'google';
71
- else if (model.includes('mistral') ||
72
- model.includes('codestral') ||
73
- model.includes('devstral'))
77
+ else if (model.includes('mistral') || model.includes('codestral') || model.includes('devstral'))
74
78
  provider = 'mistral';
75
79
  else if (model.includes('gpt-'))
76
80
  provider = 'openai';
@@ -79,44 +83,189 @@ export class AgentManager {
79
83
  else if (model.includes('claude'))
80
84
  provider = 'anthropic';
81
85
  }
82
- const serverStatus = servers.map((s) => {
83
- if (availableServers.includes(s))
84
- return s;
85
- // Recherche de suggestions (typo)
86
- const suggestion = availableServers.find((v) => v.toLowerCase().includes(s.toLowerCase()) ||
87
- s.toLowerCase().includes(v.toLowerCase()));
88
- return suggestion
89
- ? `${s} (⚠️ Nom incorrect ? Suggestion: **${suggestion}**)`
90
- : `${s} (⚠️ Absent de mcp.json)`;
86
+ }
87
+ catch {
88
+ missingConfig = true;
89
+ }
90
+ let promptSize = 0;
91
+ try {
92
+ const stat = await fs.stat(path.join(claudeAgentsDir, `${name}.md`));
93
+ promptSize = stat.size;
94
+ }
95
+ catch {
96
+ // Ignored
97
+ }
98
+ agentsMap.set(name, {
99
+ name,
100
+ runner,
101
+ model,
102
+ provider,
103
+ mcpServers,
104
+ origin: 'claude',
105
+ missingConfig,
106
+ promptSize,
107
+ });
108
+ }
109
+ // Process Hermes agents
110
+ for (const name of hermesAgentNames) {
111
+ let model = 'MiniMax-M2.7';
112
+ let provider = 'minimax-cn';
113
+ let mcpServers = ['memory-server', 'postgresql-server', 'twilio-mcp'];
114
+ let promptSize = 0;
115
+ const agentHermesDir = path.join(hermesDir, `agent_${name}`, '.hermes');
116
+ try {
117
+ const soulStat = await fs.stat(path.join(agentHermesDir, 'SOUL.md'));
118
+ promptSize = soulStat.size;
119
+ }
120
+ catch {
121
+ // Ignored
122
+ }
123
+ // Try reading .env to get actual model and provider
124
+ const envPath = path.join(agentHermesDir, '.env');
125
+ try {
126
+ const content = await fs.readFile(envPath, 'utf-8');
127
+ content.split('\n').forEach((line) => {
128
+ const trimmed = line.trim();
129
+ if (!trimmed || trimmed.startsWith('#'))
130
+ return;
131
+ const eqIdx = trimmed.indexOf('=');
132
+ if (eqIdx === -1)
133
+ return;
134
+ const k = trimmed.slice(0, eqIdx).trim();
135
+ let v = trimmed.slice(eqIdx + 1).trim();
136
+ if (v.startsWith('"') && v.endsWith('"'))
137
+ v = v.slice(1, -1);
138
+ else if (v.startsWith("'") && v.endsWith("'"))
139
+ v = v.slice(1, -1);
140
+ if (k === 'MODEL' || k === 'ANTHROPIC_MODEL') {
141
+ model = v;
142
+ }
143
+ else if (k === 'PROVIDER' || k === 'ANTHROPIC_PROVIDER') {
144
+ provider = v;
145
+ }
91
146
  });
92
- info += `\n - Runner : ${runner}`;
93
- info += `\n - Model : ${model}`;
94
- info += `\n - Provider: ${provider}`;
95
- info += `\n - Serveurs MCP : ${servers.length > 0 ? serverStatus.join(', ') : 'Aucun'}`;
96
147
  }
97
- catch (_e) {
98
- info += `\n - Config : ⚠️ Manquante ou invalide (${settingsPath})`;
148
+ catch {
149
+ // Ignored
99
150
  }
151
+ // Try reading config.yaml to get actual MCP servers
152
+ const yamlPath = path.join(agentHermesDir, 'config.yaml');
100
153
  try {
101
- const promptPath = path.join(agentsDir, file);
102
- const promptStat = await fs.stat(promptPath);
103
- info += `\n - Prompt Size : ${promptStat.size} bytes`;
154
+ const yamlContent = await fs.readFile(yamlPath, 'utf-8');
155
+ const parsedMcp = [];
156
+ const serverBlocks = yamlContent.split('\n ');
157
+ serverBlocks.forEach((block) => {
158
+ const lines = block.split('\n');
159
+ const header = lines[0].trim();
160
+ if (header && header.endsWith(':') && header !== 'mcp_servers:') {
161
+ parsedMcp.push(header.slice(0, -1).trim());
162
+ }
163
+ });
164
+ if (parsedMcp.length > 0)
165
+ mcpServers = parsedMcp;
104
166
  }
105
- catch (_e) {
106
- // Ignore missing or inaccessible prompt file
167
+ catch {
168
+ // Ignored
107
169
  }
108
- agentsList.push(info);
170
+ if (!agentsMap.has(name)) {
171
+ agentsMap.set(name, {
172
+ name,
173
+ runner: 'hermes',
174
+ model,
175
+ provider,
176
+ mcpServers,
177
+ origin: 'hermes',
178
+ missingConfig: false,
179
+ promptSize,
180
+ });
181
+ }
182
+ else {
183
+ const existing = agentsMap.get(name);
184
+ if (existing.runner === 'claude' && existing.missingConfig) {
185
+ existing.runner = 'hermes';
186
+ existing.missingConfig = false;
187
+ }
188
+ }
189
+ } // Group by runner
190
+ const runners = ['hermes', 'claude', 'kilo', 'gemini', 'qwencli', 'openclaw', 'cline', 'opencode'];
191
+ const grouped = new Map();
192
+ for (const r of runners) {
193
+ grouped.set(r, []);
109
194
  }
110
- return agentsList;
195
+ grouped.set('unknown/other', []);
196
+ for (const agent of agentsMap.values()) {
197
+ const r = agent.runner.toLowerCase();
198
+ if (grouped.has(r)) {
199
+ grouped.get(r).push(agent);
200
+ }
201
+ else {
202
+ grouped.get('unknown/other').push(agent);
203
+ }
204
+ }
205
+ const outputLines = [];
206
+ for (const [runName, list] of grouped.entries()) {
207
+ if (list.length === 0)
208
+ continue;
209
+ let sectionText = `### 🏃 Runner/Harnais : **${runName.toUpperCase()}** (${list.length} agent(s))\n`;
210
+ const items = [];
211
+ for (const agent of list) {
212
+ if (!details) {
213
+ const warnText = agent.missingConfig ? ' ⚠️ (Config settings manquante)' : '';
214
+ items.push(` - **${agent.name}**${warnText}`);
215
+ }
216
+ else {
217
+ let agentDesc = ` - **${agent.name}**`;
218
+ if (agent.missingConfig) {
219
+ agentDesc += `\n ⚠️ Config settings manquante (settings_${agent.name}.json)`;
220
+ }
221
+ else {
222
+ agentDesc += `\n - Modèle : ${agent.model}`;
223
+ agentDesc += `\n - Provider : ${agent.provider}`;
224
+ const serverStatus = agent.mcpServers.map((s) => {
225
+ if (availableServers.includes(s))
226
+ return s;
227
+ const sugg = availableServers.find(v => v.toLowerCase().includes(s.toLowerCase()) || s.toLowerCase().includes(v.toLowerCase()));
228
+ return sugg ? `${s} (⚠️ Suggestion: ${sugg})` : `${s} (⚠️ Absent)`;
229
+ });
230
+ agentDesc += `\n - MCPs : ${serverStatus.length > 0 ? serverStatus.join(', ') : 'Aucun'}`;
231
+ }
232
+ agentDesc += `\n - Prompt : ${agent.promptSize} bytes (${agent.origin === 'hermes' ? 'SOUL.md' : 'agents/' + agent.name + '.md'})`;
233
+ items.push(agentDesc);
234
+ }
235
+ }
236
+ sectionText += items.join('\n');
237
+ outputLines.push(sectionText);
238
+ }
239
+ return outputLines;
111
240
  }
112
241
  async deleteAgent(name) {
113
242
  validateAgentName(name);
243
+ const workspaceDir = getWorkspaceDir();
244
+ const hermesAgentDir = path.join(workspaceDir, '.overmind', 'hermes', `agent_${name}`);
245
+ let isHermes = false;
246
+ try {
247
+ const stat = await fs.stat(hermesAgentDir);
248
+ isHermes = stat.isDirectory();
249
+ }
250
+ catch {
251
+ // Ignored
252
+ }
253
+ const deletedFiles = [];
254
+ const errors = [];
255
+ if (isHermes) {
256
+ try {
257
+ await fs.rm(hermesAgentDir, { recursive: true, force: true });
258
+ deletedFiles.push(hermesAgentDir);
259
+ }
260
+ catch (e) {
261
+ errors.push(`hermes_dir: ${e instanceof Error ? e.message : String(e)}`);
262
+ }
263
+ return { deletedFiles, errors };
264
+ }
114
265
  const agentsDir = path.join(this.claudeDir, 'agents');
115
266
  const promptPath = path.join(agentsDir, `${name}.md`);
116
267
  const settingsPath = path.join(this.claudeDir, `settings_${name}.json`);
117
268
  const tmpMcpPath = path.join(this.claudeDir, `mcp_${name}_tmp.json`);
118
- const deletedFiles = [];
119
- const errors = [];
120
269
  for (const file of [promptPath, settingsPath, tmpMcpPath]) {
121
270
  try {
122
271
  await fs.unlink(file);
@@ -134,33 +283,154 @@ export class AgentManager {
134
283
  validateAgentName(name);
135
284
  const changes = [];
136
285
  const claudeDir = this.claudeDir;
286
+ const workspaceDir = getWorkspaceDir();
287
+ const hermesAgentDir = path.join(workspaceDir, '.overmind', 'hermes', `agent_${name}`);
288
+ const hermesSubDir = path.join(hermesAgentDir, '.hermes');
289
+ let isHermes = false;
290
+ try {
291
+ const stat = await fs.stat(hermesAgentDir);
292
+ isHermes = stat.isDirectory();
293
+ }
294
+ catch {
295
+ // Ignored
296
+ }
137
297
  // --- MODE RÉÉCRITURE DE FICHIER COMPLET ---
138
298
  if (updates.file && updates.content) {
139
- let filePath;
140
- switch (updates.file) {
141
- case 'prompt.md':
142
- filePath = path.join(claudeDir, 'agents', `${name}.md`);
143
- break;
144
- case 'settings.json':
145
- filePath = path.join(claudeDir, `settings_${name}.json`);
146
- break;
147
- case '.mcp.json':
148
- filePath = path.join(claudeDir, `.mcp.${name}.json`);
149
- break;
150
- case 'skill.md':
151
- filePath = path.join(claudeDir, 'agents', `${name}_skill.md`);
152
- await fs.mkdir(path.dirname(filePath), { recursive: true });
153
- break;
154
- default:
155
- throw new Error(`Fichier non supporté: ${updates.file}`);
156
- }
157
- await fs.writeFile(filePath, updates.content, 'utf-8');
158
- changes.push(`✅ Fichier **${updates.file}** réécrit pour l'agent **${name}**.`);
159
- // Si on réécrit settings.json, on ne fait pas les updates unitaires (déjà écrasé)
160
- if (updates.file === 'settings.json')
299
+ if (isHermes) {
300
+ let filePath;
301
+ switch (updates.file) {
302
+ case 'prompt.md':
303
+ filePath = path.join(hermesSubDir, 'SOUL.md');
304
+ await fs.writeFile(filePath, updates.content, 'utf-8');
305
+ changes.push(`✅ Fichier **SOUL.md** réécrit pour l'agent Hermes **${name}**.`);
306
+ break;
307
+ case 'settings.json': {
308
+ let envStr = '';
309
+ try {
310
+ const s = JSON.parse(updates.content);
311
+ if (s.env) {
312
+ for (const [k, v] of Object.entries(s.env)) {
313
+ if (typeof v === 'string')
314
+ envStr += `${k}=${v}\n`;
315
+ }
316
+ }
317
+ if (s.model && !envStr.includes('MODEL='))
318
+ envStr += `MODEL=${s.model}\n`;
319
+ if (s.runner && !envStr.includes('RUNNER='))
320
+ envStr += `RUNNER=${s.runner}\n`;
321
+ }
322
+ catch {
323
+ envStr = updates.content;
324
+ }
325
+ filePath = path.join(hermesSubDir, '.env');
326
+ await fs.writeFile(filePath, envStr, 'utf-8');
327
+ changes.push(`✅ Fichier **.env** réécrit pour l'agent Hermes **${name}**.`);
328
+ break;
329
+ }
330
+ case '.mcp.json': {
331
+ let yamlContent = 'mcp_servers:\n';
332
+ try {
333
+ const mc = JSON.parse(updates.content);
334
+ for (const [sName, srv] of Object.entries(mc.mcpServers || {})) {
335
+ const s = srv;
336
+ yamlContent += ` ${sName}:\n`;
337
+ if (s.url)
338
+ yamlContent += ` url: "${s.url}"\n`;
339
+ if (s.command)
340
+ yamlContent += ` command: "${s.command}"\n`;
341
+ }
342
+ }
343
+ catch {
344
+ yamlContent = updates.content;
345
+ }
346
+ filePath = path.join(hermesSubDir, 'config.yaml');
347
+ await fs.writeFile(filePath, yamlContent, 'utf-8');
348
+ changes.push(`✅ Fichier **config.yaml** réécrit pour l'agent Hermes **${name}**.`);
349
+ break;
350
+ }
351
+ case 'skill.md':
352
+ filePath = path.join(hermesSubDir, 'skills', 'skill.md');
353
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
354
+ await fs.writeFile(filePath, updates.content, 'utf-8');
355
+ changes.push(`✅ Fichier **skill.md** réécrit pour l'agent Hermes **${name}**.`);
356
+ break;
357
+ default:
358
+ throw new Error(`Fichier non supporté: ${updates.file}`);
359
+ }
161
360
  return changes;
361
+ }
362
+ else {
363
+ let filePath;
364
+ switch (updates.file) {
365
+ case 'prompt.md':
366
+ filePath = path.join(claudeDir, 'agents', `${name}.md`);
367
+ break;
368
+ case 'settings.json':
369
+ filePath = path.join(claudeDir, `settings_${name}.json`);
370
+ break;
371
+ case '.mcp.json':
372
+ filePath = path.join(claudeDir, `.mcp.${name}.json`);
373
+ break;
374
+ case 'skill.md':
375
+ filePath = path.join(claudeDir, 'agents', `${name}_skill.md`);
376
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
377
+ break;
378
+ default:
379
+ throw new Error(`Fichier non supporté: ${updates.file}`);
380
+ }
381
+ await fs.writeFile(filePath, updates.content, 'utf-8');
382
+ changes.push(`✅ Fichier **${updates.file}** réécrit pour l'agent **${name}**.`);
383
+ if (updates.file === 'settings.json')
384
+ return changes;
385
+ }
162
386
  }
163
- // --- MODE MISE À JOUR UNITAIRE (settings.json) ---
387
+ if (isHermes) {
388
+ const envVars = {};
389
+ const envPath = path.join(hermesSubDir, '.env');
390
+ try {
391
+ const content = await fs.readFile(envPath, 'utf-8');
392
+ content.split('\n').forEach((line) => {
393
+ const trimmed = line.trim();
394
+ if (!trimmed || trimmed.startsWith('#'))
395
+ return;
396
+ const eqIdx = trimmed.indexOf('=');
397
+ if (eqIdx === -1)
398
+ return;
399
+ const k = trimmed.slice(0, eqIdx).trim();
400
+ let v = trimmed.slice(eqIdx + 1).trim();
401
+ if (v.startsWith('"') && v.endsWith('"'))
402
+ v = v.slice(1, -1);
403
+ else if (v.startsWith("'") && v.endsWith("'"))
404
+ v = v.slice(1, -1);
405
+ if (k)
406
+ envVars[k] = v;
407
+ });
408
+ }
409
+ catch {
410
+ // Ignored
411
+ }
412
+ if (updates.model) {
413
+ const oldModel = envVars.MODEL || envVars.ANTHROPIC_MODEL;
414
+ envVars.MODEL = updates.model;
415
+ changes.push(`- Modèle : ${oldModel} -> ${updates.model}`);
416
+ }
417
+ if (updates.env) {
418
+ for (const [key, value] of Object.entries(updates.env)) {
419
+ const oldVal = envVars[key] ? '***' : '(undefined)';
420
+ envVars[key] = value;
421
+ changes.push(`- Env Var '${key}' : ${oldVal} -> ${value ? '***' : '(vide)'}`);
422
+ }
423
+ }
424
+ if (changes.length > 0) {
425
+ let newEnvContent = '';
426
+ for (const [k, v] of Object.entries(envVars)) {
427
+ newEnvContent += `${k}=${v}\n`;
428
+ }
429
+ await fs.writeFile(envPath, newEnvContent, 'utf-8');
430
+ }
431
+ return changes;
432
+ }
433
+ // --- MODE MISE À JOUR UNITAIRE (settings.json) pour Claude/Kilo/etc. ---
164
434
  const settingsPath = path.join(claudeDir, `settings_${name}.json`);
165
435
  let settings;
166
436
  try {
@@ -168,7 +438,6 @@ export class AgentManager {
168
438
  settings = JSON.parse(content);
169
439
  }
170
440
  catch (_e) {
171
- // Si on veut faire des updates unitaires, le settings doit exister
172
441
  if (updates.model ||
173
442
  updates.mcpServers ||
174
443
  updates.env ||
@@ -177,7 +446,7 @@ export class AgentManager {
177
446
  updates.cliPath) {
178
447
  throw new Error(`Impossible de modifier les paramètres unitaires : settings_${name}.json est introuvable.`, { cause: _e });
179
448
  }
180
- return changes; // Rien à faire
449
+ return changes;
181
450
  }
182
451
  if (updates.model) {
183
452
  settings.env = settings.env || {};
@@ -194,7 +463,6 @@ export class AgentManager {
194
463
  }
195
464
  settings.enabledMcpjsonServers = updates.mcpServers;
196
465
  changes.push(`- Serveurs MCP : [${oldServers.join(', ')}] -> [${updates.mcpServers.join(', ')}]`);
197
- // 📂 UPDATE .mcp.<name>.json
198
466
  try {
199
467
  const globalMcpPath = resolveConfigPath(CONFIG.CLAUDE.PATHS.MCP);
200
468
  const globalMcpContent = await fs.readFile(globalMcpPath, 'utf-8');
@@ -245,9 +513,6 @@ export class AgentManager {
245
513
  if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
246
514
  return { promptPath: '', settingsPath: '', error: 'INVALID_NAME' };
247
515
  }
248
- const agentsDir = path.join(this.claudeDir, 'agents');
249
- await fs.mkdir(this.claudeDir, { recursive: true });
250
- await fs.mkdir(agentsDir, { recursive: true });
251
516
  const memoryInstructions = `
252
517
 
253
518
  ---
@@ -272,12 +537,9 @@ Tu es jugé sur ta capacité à transmettre ton savoir. FOUILLIE ta mémoire et
272
537
  Tu es conçu pour être exécuté par différents runners (Claude, Kilo, Gemini, Hermes, etc.).
273
538
  - **Compatibilité** : Ton prompt et tes compétences sont agnostiques au runner.
274
539
  - **Orchestration** : Tu peux être sollicité dans des workflows parallèles (\`run_agents_parallel\`) ou séquentiels.
275
- - **Auto-Adaptation** : Adapte tes réponses au format attendu par le runner actuel (JSON pour Claude Code, texte brut pour Kilo, etc.).`;
540
+ - **Auto-Évaluation** : Adapte tes réponses au format attendu par le runner actuel (JSON pour Claude Code, texte brut pour Kilo, etc.).`;
276
541
  const finalPrompt = prompt + memoryInstructions;
277
- const promptPath = path.join(agentsDir, `${name}.md`);
278
- await fs.writeFile(promptPath, finalPrompt, 'utf-8');
279
542
  // Default mandatory environment variables according to user request
280
- // SECURITY: Never default to placeholder tokens — require real env vars
281
543
  const authToken = process.env.ANTHROPIC_AUTH_TOKEN;
282
544
  if (!authToken) {
283
545
  return {
@@ -316,7 +578,6 @@ Tu es conçu pour être exécuté par différents runners (Claude, Kilo, Gemini,
316
578
  if (copyEnvFrom && projectRoot) {
317
579
  try {
318
580
  const sourceSettingsPath = path.resolve(projectRoot, copyEnvFrom);
319
- // Prevent path traversal: source must be inside projectRoot
320
581
  if (!sourceSettingsPath.startsWith(path.resolve(projectRoot))) {
321
582
  throw new Error(`copyEnvFrom path escapes project root: ${copyEnvFrom}`);
322
583
  }
@@ -332,6 +593,63 @@ Tu es conçu pour être exécuté par différents runners (Claude, Kilo, Gemini,
332
593
  }
333
594
  }
334
595
  envVars.ANTHROPIC_MODEL = model; // Ensure model is correctly set
596
+ if (runner === 'hermes') {
597
+ const workspaceDir = getWorkspaceDir();
598
+ const hermesAgentDir = path.join(workspaceDir, '.overmind', 'hermes', `agent_${name}`);
599
+ const hermesSubDir = path.join(hermesAgentDir, '.hermes');
600
+ const hermesSkillsDir = path.join(hermesSubDir, 'skills');
601
+ await fs.mkdir(hermesSubDir, { recursive: true });
602
+ await fs.mkdir(hermesSkillsDir, { recursive: true });
603
+ const promptPath = path.join(hermesSubDir, 'SOUL.md');
604
+ await fs.writeFile(promptPath, finalPrompt, 'utf-8');
605
+ // Write .env
606
+ const envPath = path.join(hermesSubDir, '.env');
607
+ let envContent = '';
608
+ for (const [k, v] of Object.entries(envVars)) {
609
+ envContent += `${k}=${v}\n`;
610
+ }
611
+ await fs.writeFile(envPath, envContent, 'utf-8');
612
+ // Write config.yaml (MCP config)
613
+ const yamlPath = path.join(hermesSubDir, 'config.yaml');
614
+ let yamlContent = 'mcp_servers:\n';
615
+ const globalMcpPath = resolveConfigPath(CONFIG.CLAUDE.PATHS.MCP);
616
+ try {
617
+ const globalMcpContent = await fs.readFile(globalMcpPath, 'utf-8');
618
+ const globalMcp = JSON.parse(globalMcpContent);
619
+ mcpServers.forEach((serverName) => {
620
+ if (globalMcp.mcpServers && globalMcp.mcpServers[serverName]) {
621
+ const s = globalMcp.mcpServers[serverName];
622
+ yamlContent += ` ${serverName}:\n`;
623
+ if (s.url)
624
+ yamlContent += ` url: "${s.url}"\n`;
625
+ if (s.command)
626
+ yamlContent += ` command: "${s.command}"\n`;
627
+ }
628
+ });
629
+ }
630
+ catch {
631
+ // Ignored
632
+ }
633
+ await fs.writeFile(yamlPath, yamlContent, 'utf-8');
634
+ // Mémoriser la création de l'agent
635
+ try {
636
+ const memory = getMemoryProvider();
637
+ await memory.storeKnowledge({
638
+ text: `Nouvel agent IA créé : '${name}'. Runner : hermes.`,
639
+ source: 'agent',
640
+ agentName: name,
641
+ });
642
+ }
643
+ catch {
644
+ // Ignored
645
+ }
646
+ return { promptPath, settingsPath: envPath };
647
+ }
648
+ const agentsDir = path.join(this.claudeDir, 'agents');
649
+ await fs.mkdir(this.claudeDir, { recursive: true });
650
+ await fs.mkdir(agentsDir, { recursive: true });
651
+ const promptPath = path.join(agentsDir, `${name}.md`);
652
+ await fs.writeFile(promptPath, finalPrompt, 'utf-8');
335
653
  const settings = {
336
654
  env: envVars,
337
655
  enableAllProjectMcpServers: false,
@@ -339,7 +657,6 @@ Tu es conçu pour être exécuté par différents runners (Claude, Kilo, Gemini,
339
657
  agent: name,
340
658
  runner: runner || 'claude',
341
659
  };
342
- // Add runner specific metadata
343
660
  if (mode)
344
661
  settings.mode = mode;
345
662
  if (cliPath)
@@ -347,7 +664,6 @@ Tu es conçu pour être exécuté par différents runners (Claude, Kilo, Gemini,
347
664
  const settingsFileName = `settings_${name}.json`;
348
665
  const settingsPath = path.join(this.claudeDir, settingsFileName);
349
666
  await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2), 'utf-8');
350
- // 📂 ENSURE .mcp.<agent>.json CREATION
351
667
  try {
352
668
  const globalMcpPath = resolveConfigPath(CONFIG.CLAUDE.PATHS.MCP);
353
669
  const globalMcpContent = await fs.readFile(globalMcpPath, 'utf-8');
@@ -360,37 +676,36 @@ Tu es conçu pour être exécuté par différents runners (Claude, Kilo, Gemini,
360
676
  });
361
677
  const agentMcpPath = path.join(this.claudeDir, `.mcp.${name}.json`);
362
678
  await fs.writeFile(agentMcpPath, JSON.stringify({ mcpServers: agentMcpServers }, null, 2), 'utf-8');
363
- console.error(`[AgentManager] ✅ .mcp.${name}.json created with ${Object.keys(agentMcpServers).length} servers.`);
364
679
  }
365
680
  catch (e) {
366
681
  console.error(`[AgentManager] ⚠️ Failed to create .mcp.${name}.json:`, e.message);
367
682
  }
368
- // Mémoriser la création de l'agent dans Overmind
369
683
  try {
370
684
  const memory = getMemoryProvider();
371
685
  await memory.storeKnowledge({
372
- text: `Nouvel agent IA créé : '${name}'.
373
- Runner : ${runner || 'claude'}.
374
- Modèle : ${model}.
375
- Capacités définies : ${prompt.slice(0, 300)}...
376
- Serveurs MCP activés : ${mcpServers.join(', ')}.`,
686
+ text: `Nouvel agent IA créé : '${name}'. Runner : ${runner || 'claude'}.`,
377
687
  source: 'agent',
378
688
  agentName: name,
379
689
  });
380
690
  }
381
691
  catch (_e) {
382
- // Ignorer si la mémoire échoue
692
+ // Ignored
383
693
  }
384
694
  return { promptPath, settingsPath };
385
695
  }
386
696
  async getDetailedConfigs(name) {
387
697
  validateAgentName(name);
388
- const agentsDir = path.join(this.claudeDir, 'agents');
389
- const promptPath = path.join(agentsDir, `${name}.md`);
390
- const settingsPath = path.join(this.claudeDir, `settings_${name}.json`);
391
- const mcpPath = path.join(this.claudeDir, `.mcp.${name}.json`);
392
- const skillPath = path.join(this.claudeDir, `agents/${name}_skill.md`);
393
- const alternativeSkillPath = path.join(this.claudeDir, `skills/${name}.md`);
698
+ const workspaceDir = getWorkspaceDir();
699
+ const hermesAgentDir = path.join(workspaceDir, '.overmind', 'hermes', `agent_${name}`);
700
+ const hermesSubDir = path.join(hermesAgentDir, '.hermes');
701
+ let isHermes = false;
702
+ try {
703
+ const stat = await fs.stat(hermesAgentDir);
704
+ isHermes = stat.isDirectory();
705
+ }
706
+ catch {
707
+ // Ignored
708
+ }
394
709
  const result = {};
395
710
  const readFileSafe = async (filePath) => {
396
711
  try {
@@ -400,8 +715,69 @@ Serveurs MCP activés : ${mcpServers.join(', ')}.`,
400
715
  return 'MISSING';
401
716
  }
402
717
  };
718
+ if (isHermes) {
719
+ result['prompt.md'] = await readFileSafe(path.join(hermesSubDir, 'SOUL.md'));
720
+ const envVars = {};
721
+ const envContent = await readFileSafe(path.join(hermesSubDir, '.env'));
722
+ if (envContent !== 'MISSING') {
723
+ envContent.split('\n').forEach((line) => {
724
+ const trimmed = line.trim();
725
+ if (!trimmed || trimmed.startsWith('#'))
726
+ return;
727
+ const eqIdx = trimmed.indexOf('=');
728
+ if (eqIdx === -1)
729
+ return;
730
+ const k = trimmed.slice(0, eqIdx).trim();
731
+ let v = trimmed.slice(eqIdx + 1).trim();
732
+ if (v.startsWith('"') && v.endsWith('"'))
733
+ v = v.slice(1, -1);
734
+ else if (v.startsWith("'") && v.endsWith("'"))
735
+ v = v.slice(1, -1);
736
+ if (k)
737
+ envVars[k] = v;
738
+ });
739
+ }
740
+ result['settings.json'] = JSON.stringify({
741
+ env: envVars,
742
+ model: envVars.MODEL || envVars.ANTHROPIC_MODEL || 'MiniMax-M2.7',
743
+ runner: 'hermes'
744
+ }, null, 2);
745
+ const mcpServers = {};
746
+ const yamlContent = await readFileSafe(path.join(hermesSubDir, 'config.yaml'));
747
+ if (yamlContent !== 'MISSING') {
748
+ const serverBlocks = yamlContent.split('\n ');
749
+ serverBlocks.forEach((block) => {
750
+ const lines = block.split('\n');
751
+ const header = lines[0].trim();
752
+ if (header && header.endsWith(':') && header !== 'mcp_servers:') {
753
+ const srvName = header.slice(0, -1).trim();
754
+ const srvObj = {};
755
+ lines.slice(1).forEach((l) => {
756
+ const parts = l.trim().split(':');
757
+ if (parts.length >= 2) {
758
+ const key = parts[0].trim();
759
+ let val = parts.slice(1).join(':').trim();
760
+ if (val.startsWith('"') && val.endsWith('"'))
761
+ val = val.slice(1, -1);
762
+ srvObj[key] = val;
763
+ }
764
+ });
765
+ mcpServers[srvName] = srvObj;
766
+ }
767
+ });
768
+ }
769
+ result['.mcp.json'] = JSON.stringify({ mcpServers }, null, 2);
770
+ const skillPath = path.join(hermesSubDir, 'skills', 'skill.md');
771
+ result['skill.md'] = await readFileSafe(skillPath);
772
+ return result;
773
+ }
774
+ const agentsDir = path.join(this.claudeDir, 'agents');
775
+ const promptPath = path.join(agentsDir, `${name}.md`);
776
+ const settingsPath = path.join(this.claudeDir, `settings_${name}.json`);
777
+ const mcpPath = path.join(this.claudeDir, `.mcp.${name}.json`);
778
+ const skillPath = path.join(this.claudeDir, `agents/${name}_skill.md`);
779
+ const alternativeSkillPath = path.join(this.claudeDir, `skills/${name}.md`);
403
780
  // Fallback paths using workspace from OVERMIND_WORKSPACE env var
404
- const workspaceDir = getWorkspaceDir();
405
781
  const fallbackAgentsDir = path.join(workspaceDir, '.overmind', 'agents');
406
782
  const fallbackPromptPath = path.join(fallbackAgentsDir, `${name}.md`);
407
783
  const fallbackSettingsDir = path.join(workspaceDir, '.overmind', 'agents', name);