overmind-mcp 3.4.2 → 3.5.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.
@@ -13,8 +13,18 @@
13
13
  */
14
14
 
15
15
  import { execSync, spawn } from 'child_process';
16
- import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
17
- import { join } from 'path';
16
+ import {
17
+ existsSync,
18
+ mkdirSync,
19
+ writeFileSync,
20
+ readFileSync,
21
+ lstatSync,
22
+ renameSync,
23
+ symlinkSync,
24
+ unlinkSync,
25
+ cpSync,
26
+ } from 'fs';
27
+ import { join, basename } from 'path';
18
28
  import { fileURLToPath } from 'url';
19
29
  import { dirname } from 'path';
20
30
  import { randomBytes } from 'crypto';
@@ -294,6 +304,108 @@ async function installPostgresMCP() {
294
304
  }
295
305
  }
296
306
 
307
+ function copyIfMissing(src, dest) {
308
+ try {
309
+ if (!existsSync(src) || existsSync(dest)) return false;
310
+ cpSync(src, dest, { recursive: true, preserveTimestamps: true });
311
+ return true;
312
+ } catch {
313
+ return false;
314
+ }
315
+ }
316
+
317
+ function alignHermesHome() {
318
+ logSection('ALIGNEMENT HERMES_HOME');
319
+
320
+ // Windows Desktop utilise %LOCALAPPDATA%\\hermes. Ne pas symlinker ~/.hermes sur Windows.
321
+ if (process.platform === 'win32') {
322
+ log(COLORS.cyan, 'Windows détecté: Hermes Desktop utilise %LOCALAPPDATA%\\hermes. Symlink ~/.hermes ignoré.');
323
+ return true;
324
+ }
325
+
326
+ const home = process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH;
327
+ const overmindHermes = join(INSTALL_DIR, 'hermes');
328
+ const nativeHermes = join(home, '.hermes');
329
+ const profilesDir = join(overmindHermes, 'profiles');
330
+
331
+ mkdirSync(profilesDir, { recursive: true });
332
+ mkdirSync(join(overmindHermes, 'distributions'), { recursive: true });
333
+
334
+ try {
335
+ if (!existsSync(nativeHermes)) {
336
+ symlinkSync(overmindHermes, nativeHermes, 'dir');
337
+ log(COLORS.green, `✅ Symlink créé: ~/.hermes → ${overmindHermes}`);
338
+ return true;
339
+ }
340
+
341
+ const stat = lstatSync(nativeHermes);
342
+
343
+ if (stat.isSymbolicLink()) {
344
+ const target = runCommand(`readlink "${nativeHermes}"`)?.trim();
345
+ if (target === overmindHermes) {
346
+ log(COLORS.green, '✅ ~/.hermes pointe déjà vers ~/.overmind/hermes');
347
+ return true;
348
+ }
349
+ log(COLORS.yellow, `⚠️ ~/.hermes pointe vers ${target || '(inconnu)'} — correction`);
350
+ unlinkSync(nativeHermes);
351
+ symlinkSync(overmindHermes, nativeHermes, 'dir');
352
+ log(COLORS.green, '✅ Symlink ~/.hermes corrigé');
353
+ return true;
354
+ }
355
+
356
+ if (!stat.isDirectory()) {
357
+ log(COLORS.yellow, '⚠️ ~/.hermes existe mais n’est pas un dossier — backup + symlink');
358
+ } else {
359
+ log(COLORS.yellow, '⚠️ Dual-path détecté: ~/.hermes est un vrai dossier — migration non destructive');
360
+ }
361
+
362
+ // Copier les profils de ~/.hermes/profiles vers ~/.overmind/hermes/profiles sans écraser.
363
+ const nativeProfiles = join(nativeHermes, 'profiles');
364
+ if (existsSync(nativeProfiles)) {
365
+ for (const entry of runCommand(`find "${nativeProfiles}" -mindepth 1 -maxdepth 1 -type d 2>/dev/null`)?.split('\n') || []) {
366
+ if (!entry.trim()) continue;
367
+ const name = basename(entry.trim());
368
+ const destProfile = join(profilesDir, name);
369
+ mkdirSync(destProfile, { recursive: true });
370
+ for (const file of ['config.yaml', 'SOUL.md', '.env', '.mcp.json', 'profile.yaml', 'workspace.yaml', 'auth.json', 'state.db']) {
371
+ if (copyIfMissing(join(entry.trim(), file), join(destProfile, file))) {
372
+ log(COLORS.cyan, ` Copie profil ${name}/${file}`);
373
+ }
374
+ }
375
+ for (const dir of ['skills', 'memories', 'cron', 'hooks', 'sessions']) {
376
+ if (copyIfMissing(join(entry.trim(), dir), join(destProfile, dir))) {
377
+ log(COLORS.cyan, ` Copie profil ${name}/${dir}/`);
378
+ }
379
+ }
380
+ }
381
+ }
382
+
383
+ // Copier les fichiers globaux sans écraser.
384
+ for (const file of ['config.yaml', 'auth.json', '.env', '.mcp.json', 'SOUL.md', 'state.db']) {
385
+ if (copyIfMissing(join(nativeHermes, file), join(overmindHermes, file))) {
386
+ log(COLORS.cyan, ` Copie global ${file}`);
387
+ }
388
+ }
389
+ for (const dir of ['skills', 'memories', 'cron', 'hooks', 'sessions']) {
390
+ if (copyIfMissing(join(nativeHermes, dir), join(overmindHermes, dir))) {
391
+ log(COLORS.cyan, ` Copie global ${dir}/`);
392
+ }
393
+ }
394
+
395
+ const backup = `${nativeHermes}.backup.${new Date().toISOString().replace(/[-:T.Z]/g, '').slice(0, 14)}`;
396
+ renameSync(nativeHermes, backup);
397
+ symlinkSync(overmindHermes, nativeHermes, 'dir');
398
+ log(COLORS.green, `✅ Symlink créé: ~/.hermes → ${overmindHermes}`);
399
+ log(COLORS.green, `✅ Backup conservé: ${backup}`);
400
+ log(COLORS.green, '✅ Aucune donnée effacée');
401
+ return true;
402
+ } catch (error) {
403
+ log(COLORS.yellow, `⚠️ Alignement ~/.hermes échoué: ${error.message}`);
404
+ log(COLORS.cyan, ' Fix manuel: mv ~/.hermes ~/.hermes.backup.$(date +%Y%m%d%H%M%S) && ln -s ~/.overmind/hermes ~/.hermes');
405
+ return false;
406
+ }
407
+ }
408
+
297
409
  function createEnvConfig() {
298
410
  logSection('CRÉATION CONFIGURATION');
299
411
 
@@ -531,6 +643,7 @@ async function main() {
531
643
  console.log(' ✓ Démarrer PostgreSQL + pgvector');
532
644
  console.log(' ✓ Copier .env.example → .env');
533
645
  console.log(' ✓ Copier .mcp.json.example → .mcp.json');
646
+ console.log(' ✓ Aligner Hermes: ~/.hermes → ~/.overmind/hermes (Linux/macOS)');
534
647
  console.log(' ✓ Valider PostgreSQL');
535
648
  console.log('');
536
649
 
@@ -553,6 +666,9 @@ async function main() {
553
666
  // Step 2: Setup .env and .mcp.json (BEFORE Docker so password is shared)
554
667
  createEnvConfig();
555
668
 
669
+ // Step 2b: Align Hermes roots (Linux/macOS npm -g path hygiene)
670
+ alignHermesHome();
671
+
556
672
  // Step 3: Setup PostgreSQL (uses PG_PASSWORD)
557
673
  await setupPostgreSQL();
558
674
 
@@ -234,7 +234,7 @@ async function main() {
234
234
  🌐 CONNEXION:
235
235
  🗄️ PostgreSQL: localhost:5432
236
236
  👤 User: postgres
237
- 🔑 Password: overmind_temp_password_change_me (À CHANGER !)
237
+ 🔑 Password: (généré automatiquement — voir ~/.overmind/.env)
238
238
 
239
239
  🚀 POUR DÉMARRER OVERMIND:
240
240
  pnpm run dev # Mode développement
package/scripts/setup.mjs CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  /**
3
3
  * ═══════════════════════════════════════════════════════════════════════════════
4
4
  * SETUP SCRIPT - Installation Automatique OverMind
@@ -18,6 +18,7 @@
18
18
  import { execSync, spawn } from 'child_process';
19
19
  import { copyFileSync, existsSync, mkdirSync, writeFileSync } from 'fs';
20
20
  import { join } from 'path';
21
+ import { randomBytes } from 'crypto';
21
22
  import { fileURLToPath } from 'url';
22
23
  import { dirname } from 'path';
23
24
  import { createInterface } from 'readline';
@@ -175,10 +176,12 @@ async function installPostgreSQL() {
175
176
  execSync('docker pull pgvector/pgvector:pg16', { stdio: 'inherit' });
176
177
 
177
178
  console.log('🚀 Démarrage container PostgreSQL...');
179
+ const pgPwd = randomBytes(18).toString('base64url');
178
180
  const runCommand = `docker run -d --name overmind-postgres-pgvector \\
179
181
  -p 5432:5432 \\
180
- -e POSTGRES_PASSWORD=overmind_temp_password_change_me \\
182
+ -e POSTGRES_PASSWORD=${pgPwd} \\
181
183
  -e POSTGRES_USER=postgres \\
184
+ -e POSTGRES_DB=overmind_memory \\
182
185
  -v overmind_postgres_data:/var/lib/postgresql/data \\
183
186
  --restart unless-stopped \\
184
187
  pgvector/pgvector:pg16`;
@@ -39,6 +39,8 @@ const C = {
39
39
 
40
40
  const HOME = process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH || '';
41
41
  const OVERMIND_DIR = join(HOME, '.overmind');
42
+ const OVERMIND_HERMES_DIR = join(OVERMIND_DIR, 'hermes');
43
+ const NATIVE_HERMES_DIR = join(HOME, '.hermes');
42
44
  const ENV_FILE = join(OVERMIND_DIR, '.env');
43
45
 
44
46
  let passed = 0;
@@ -142,8 +144,31 @@ if (portCheck) {
142
144
  }
143
145
  }
144
146
 
145
- // 6. Hermes (optionnel)
146
- console.log(`\n${C.bold}6. Hermes CLI (optionnel)${C.reset}`);
147
+ // 6. Hermes root alignment
148
+ console.log(`\n${C.bold}6. Hermes root alignment${C.reset}`);
149
+ if (process.platform === 'win32') {
150
+ ok('Windows Desktop: racine attendue = %LOCALAPPDATA%\\hermes (pas de symlink ~/.hermes)');
151
+ } else if (!existsSync(OVERMIND_HERMES_DIR)) {
152
+ warn('~/.overmind/hermes absent — sera créé par overmind-install-native ou postinstall');
153
+ } else if (!existsSync(NATIVE_HERMES_DIR)) {
154
+ warn('~/.hermes absent — postinstall devrait créer le symlink vers ~/.overmind/hermes');
155
+ } else {
156
+ const linkTarget = runCmd(`readlink "${NATIVE_HERMES_DIR}" 2>/dev/null`);
157
+ if (linkTarget === OVERMIND_HERMES_DIR) {
158
+ ok('~/.hermes → ~/.overmind/hermes (symlink complet OK)');
159
+ } else {
160
+ const nativeProfilesTarget = runCmd(`readlink "${NATIVE_HERMES_DIR}/profiles" 2>/dev/null`);
161
+ const nativeConfigTarget = runCmd(`readlink "${NATIVE_HERMES_DIR}/config.yaml" 2>/dev/null`);
162
+ if (nativeProfilesTarget && nativeConfigTarget) {
163
+ warn('Mode hybride détecté: ~/.hermes est un vrai dossier mais profiles/config.yaml sont symlinkés. Rejeu recommandé: npm i -g overmind-mcp@latest ou overmind-install-native');
164
+ } else {
165
+ warn('Dual-path détecté: ~/.hermes n’est pas symlinké vers ~/.overmind/hermes. Risque de config divergente.');
166
+ }
167
+ }
168
+ }
169
+
170
+ // 7. Hermes (optionnel)
171
+ console.log(`\n${C.bold}7. Hermes CLI (optionnel)${C.reset}`);
147
172
  const hermes = runCmd('which hermes 2>/dev/null');
148
173
  if (hermes) {
149
174
  const hermesVersion = runCmd('hermes version 2>/dev/null');
@@ -1,133 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * migrate-hermes-home.js — One-shot migration for HERMES_HOME path change.
4
- *
5
- * Background: HERMES_HOME was previously computed from process.cwd() which
6
- * produced different paths depending on where the runner was launched from.
7
- * Starting with Overmind 2.8.27, HERMES_HOME is resolved from
8
- * OVERMIND_AGENT_HOME or $HOME/.overmind/hermes/agent_<name>/.hermes.
9
- *
10
- * This script migrates any existing state from the legacy workspace-relative
11
- * paths (e.g. <workflow>/.overmind/hermes/agent_<name>/) to the new canonical
12
- * HOME-based path. Safe to run multiple times.
13
- *
14
- * Usage: node scripts/migrate-hermes-home.js [--dry-run]
15
- */
16
-
17
- import fs from 'fs';
18
- import path from 'path';
19
- import os from 'os';
20
-
21
- const args = process.argv.slice(2);
22
- const dryRun = args.includes('--dry-run');
23
-
24
- function log(msg) {
25
- process.stdout.write(`[migrate-hermes-home] ${msg}\n`);
26
- }
27
-
28
- function findLegacyBases(workspaceDir) {
29
- const legacy = [];
30
- for (const sub of ['.overmind/hermes', '.overmind/hermes/agent_*']) {
31
- const full = path.join(workspaceDir, sub);
32
- if (fs.existsSync(full)) {
33
- try {
34
- const entries = fs.readdirSync(full, { withFileTypes: true });
35
- for (const e of entries) {
36
- if (e.isDirectory() && e.name.startsWith('agent_')) {
37
- legacy.push(path.join(full, e.name));
38
- } else if (e.isDirectory() && e.name === 'central') {
39
- legacy.push(path.join(full, e.name));
40
- }
41
- }
42
- } catch { /* ignore */ }
43
- }
44
- }
45
- return legacy;
46
- }
47
-
48
- function getCanonicalBase(platform) {
49
- const homeBase = process.env.LOCALAPPDATA
50
- || process.env.USERPROFILE
51
- || os.homedir();
52
- return platform === 'win32'
53
- ? path.join(homeBase, 'overmind', 'hermes')
54
- : path.join(homeBase, '.overmind', 'hermes');
55
- }
56
-
57
- function migrateOne(src, dst) {
58
- // src = legacy agent dir (e.g. <workspace>/.overmind/hermes/agent_foo)
59
- // dst = canonical agent dir (e.g. ~/.overmind/hermes/agent_foo)
60
- if (fs.existsSync(dst)) {
61
- log(` SKIP: destination already exists: ${dst}`);
62
- return false;
63
- }
64
- if (dryRun) {
65
- log(` DRY-RUN: would move ${src} -> ${dst}`);
66
- return true;
67
- }
68
- // Make parent if missing
69
- fs.mkdirSync(path.dirname(dst), { recursive: true });
70
- // Use rename for atomicity (works on same-filesystem; cross-fs would need copy+delete)
71
- try {
72
- fs.renameSync(src, dst);
73
- log(` MOVED: ${src} -> ${dst}`);
74
- return true;
75
- } catch (e) {
76
- if (e.code === 'EXDEV') {
77
- // Cross-device: copy + delete
78
- log(` CROSS-FS: copying ${src} -> ${dst}`);
79
- copyDirSync(src, dst);
80
- fs.rmSync(src, { recursive: true, force: true });
81
- return true;
82
- }
83
- throw e;
84
- }
85
- }
86
-
87
- function copyDirSync(src, dst) {
88
- fs.mkdirSync(dst, { recursive: true });
89
- for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
90
- const s = path.join(src, entry.name);
91
- const d = path.join(dst, entry.name);
92
- if (entry.isDirectory()) {
93
- copyDirSync(s, d);
94
- } else if (entry.isFile()) {
95
- fs.copyFileSync(s, d);
96
- }
97
- }
98
- }
99
-
100
- function main() {
101
- const workspaceDir = process.env.OVERMIND_WORKSPACE
102
- || process.cwd();
103
- const platform = process.platform;
104
- const canonicalBase = getCanonicalBase(platform);
105
-
106
- log(`workspace: ${workspaceDir}`);
107
- log(`platform: ${platform}`);
108
- log(`canonical: ${canonicalBase}`);
109
- log(`mode: ${dryRun ? 'DRY-RUN' : 'LIVE'}`);
110
- log('');
111
-
112
- const legacy = findLegacyBases(workspaceDir);
113
- if (legacy.length === 0) {
114
- log('No legacy .overmind directories found. Nothing to migrate.');
115
- return;
116
- }
117
-
118
- let moved = 0;
119
- for (const src of legacy) {
120
- const name = path.basename(src);
121
- const dst = path.join(canonicalBase, name);
122
- log(`Migrating ${name}:`);
123
- if (migrateOne(src, dst)) moved++;
124
- }
125
-
126
- log('');
127
- log(`Done. ${moved}/${legacy.length} agent directories migrated.`);
128
- if (dryRun) {
129
- log('Re-run without --dry-run to actually perform the migration.');
130
- }
131
- }
132
-
133
- main();
@@ -1,205 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * migrate-to-profiles.mjs — Migrate old Hermes agents to native profiles.
4
- *
5
- * Scans the old Overmind Hermes layout (<HERMES_HOME>/agents/<name>/settings.json)
6
- * and creates a native Hermes profile for each, preserving:
7
- * - SOUL.md (system prompt)
8
- * - credentials (.env)
9
- * - model + provider (config.yaml)
10
- * - MCP servers
11
- *
12
- * Usage:
13
- * node scripts/migrate-to-profiles.mjs [--dry-run]
14
- */
15
-
16
- import { exec } from 'child_process';
17
- import { promisify } from 'util';
18
- import fs from 'fs';
19
- import path from 'path';
20
- import os from 'os';
21
-
22
- const execAsync = promisify(exec);
23
-
24
- // ─── Resolve old HERMES_HOME ──────────────────────────────────────────────────
25
-
26
- function resolveOldHermesHome() {
27
- if (process.env.OVERMIND_HERMES_HOME) return process.env.OVERMIND_HERMES_HOME;
28
-
29
- // Workspace fallback
30
- const wsHome = path.join(process.cwd(), '.overmind', 'hermes');
31
- if (fs.existsSync(wsHome)) return wsHome;
32
-
33
- // HOME-based
34
- const homeBase = process.env.LOCALAPPDATA || os.homedir();
35
- return process.platform === 'win32'
36
- ? path.join(homeBase, 'overmind', 'hermes')
37
- : path.join(homeBase, '.overmind', 'hermes');
38
- }
39
-
40
- function resolveOldAgentsDir() {
41
- const home = resolveOldHermesHome();
42
- const agentsDir = path.join(home, 'agents');
43
- if (fs.existsSync(agentsDir)) return agentsDir;
44
- return null;
45
- }
46
-
47
- // ─── Detect provider from credentials ─────────────────────────────────────────
48
-
49
- function detectProvider(token, baseUrl) {
50
- if (!token) return 'openrouter';
51
- const url = (baseUrl || '').toLowerCase();
52
- const t = token.toLowerCase();
53
-
54
- if (url.includes('minimaxi') || url.includes('minimax') || t.startsWith('sk-cp-') || t.startsWith('sk-mm-')) {
55
- return url.includes('minimaxi') ? 'minimax-cn' : 'minimax';
56
- }
57
- if (url.includes('z.ai') || url.includes('bigmodel') || /^[0-9a-f]{32}/i.test(token)) {
58
- return 'zai';
59
- }
60
- if (url.includes('anthropic') || t.startsWith('sk-ant-')) {
61
- return 'anthropic';
62
- }
63
- if (url.includes('openai') || t.startsWith('sk-')) {
64
- return 'openai';
65
- }
66
- return 'openrouter';
67
- }
68
-
69
- function buildCredentialsMap(provider, authToken) {
70
- const creds = {};
71
- if (!authToken) return creds;
72
-
73
- switch (provider) {
74
- case 'minimax-cn':
75
- creds['MINIMAX_CN_API_KEY'] = authToken;
76
- break;
77
- case 'minimax':
78
- creds['MINIMAX_API_KEY'] = authToken;
79
- break;
80
- case 'zai':
81
- creds['GLM_API_KEY'] = authToken;
82
- creds['ZAI_ANTHROPIC_FALLBACK_KEY'] = authToken;
83
- break;
84
- case 'anthropic':
85
- creds['ANTHROPIC_API_KEY'] = authToken;
86
- break;
87
- case 'openai':
88
- creds['OPENAI_API_KEY'] = authToken;
89
- break;
90
- default:
91
- creds['OPENROUTER_API_KEY'] = authToken;
92
- }
93
- return creds;
94
- }
95
-
96
- // ─── Migration ────────────────────────────────────────────────────────────────
97
-
98
- async function migrate(dryRun = false) {
99
- const agentsDir = resolveOldAgentsDir();
100
- if (!agentsDir) {
101
- console.log('ℹ️ No old Hermes agents directory found. Nothing to migrate.');
102
- return;
103
- }
104
-
105
- const agentDirs = fs.readdirSync(agentsDir)
106
- .filter(d => fs.statSync(path.join(agentsDir, d)).isDirectory());
107
-
108
- if (agentDirs.length === 0) {
109
- console.log('ℹ️ No old Hermes agents found. Nothing to migrate.');
110
- return;
111
- }
112
-
113
- console.log(`Found ${agentDirs.length} old Hermes agent(s) to migrate:\n`);
114
-
115
- let migrated = 0;
116
- let skipped = 0;
117
- let failed = 0;
118
-
119
- for (const agentName of agentDirs) {
120
- const agentDir = path.join(agentsDir, agentName);
121
- const settingsPath = path.join(agentDir, 'settings.json');
122
- const soulPath = path.join(agentDir, 'SOUL.md');
123
-
124
- console.log(` 📦 ${agentName}`);
125
-
126
- // Read settings.json
127
- if (!fs.existsSync(settingsPath)) {
128
- console.log(` ⚠️ No settings.json found — skipping.`);
129
- skipped++;
130
- continue;
131
- }
132
-
133
- let settings;
134
- try {
135
- settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
136
- } catch (e) {
137
- console.log(` ❌ Failed to parse settings.json: ${e.message}`);
138
- failed++;
139
- continue;
140
- }
141
-
142
- const env = settings.env || {};
143
- const authToken = env.ANTHROPIC_AUTH_TOKEN || env.ANTHROPIC_API_KEY || '';
144
- const baseUrl = env.ANTHROPIC_BASE_URL || '';
145
- const model = env.ANTHROPIC_MODEL || env.MODEL || 'MiniMax-M3';
146
- const provider = env.ANTHROPIC_PROVIDER || detectProvider(authToken, baseUrl);
147
- const mcpServers = settings.enabledMcpjsonServers || [];
148
-
149
- // Read SOUL.md
150
- let soulContent = '';
151
- if (fs.existsSync(soulPath)) {
152
- soulContent = fs.readFileSync(soulPath, 'utf-8');
153
- }
154
-
155
- console.log(` Model: ${model} (${provider})`);
156
- console.log(` MCPs: ${mcpServers.length} server(s)`);
157
- console.log(` Auth: ${authToken ? `***${authToken.slice(-4)}` : '(none)'}`);
158
-
159
- if (dryRun) {
160
- console.log(` 🔄 [DRY-RUN] Would create profile: hermes profile create ${agentName}`);
161
- migrated++;
162
- continue;
163
- }
164
-
165
- try {
166
- // 1. Create profile
167
- const { HermesProfileManager } = await import('../dist/services/HermesProfileManager.js');
168
- const credentials = buildCredentialsMap(provider, authToken);
169
-
170
- const { profilePath } = await HermesProfileManager.create({
171
- name: agentName,
172
- prompt: soulContent || `You are ${agentName}, an AI assistant.`,
173
- model,
174
- provider,
175
- credentials,
176
- mcpServers,
177
- description: `Migrated agent: ${agentName}`,
178
- });
179
-
180
- console.log(` ✅ Profile created: ${profilePath}`);
181
- migrated++;
182
- } catch (e) {
183
- console.log(` ❌ Migration failed: ${e.message}`);
184
- failed++;
185
- }
186
- }
187
-
188
- console.log(`\n📊 Migration complete: ${migrated} migrated, ${skipped} skipped, ${failed} failed.`);
189
-
190
- if (!dryRun && migrated > 0) {
191
- console.log(`\n💡 Next steps:`);
192
- console.log(` • Verify profiles: hermes profile list`);
193
- console.log(` • Test an agent: hermes -p <name> chat "test"`);
194
- console.log(` • Old layout preserved at: ${agentsDir}`);
195
- console.log(` • Delete old layout when ready: rm -rf ${agentsDir}`);
196
- }
197
- }
198
-
199
- // ─── CLI ──────────────────────────────────────────────────────────────────────
200
-
201
- const dryRun = process.argv.includes('--dry-run');
202
- migrate(dryRun).catch(e => {
203
- console.error('Migration error:', e);
204
- process.exit(1);
205
- });