agentxchain 0.1.1 → 0.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.
@@ -1,9 +1,13 @@
1
- import { writeFileSync, existsSync, mkdirSync } from 'fs';
2
- import { join, resolve } from 'path';
1
+ import { writeFileSync, readFileSync, existsSync, mkdirSync, readdirSync } from 'fs';
2
+ import { join, resolve, dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
3
4
  import chalk from 'chalk';
4
5
  import inquirer from 'inquirer';
5
6
  import { CONFIG_FILE, LOCK_FILE, STATE_FILE } from '../lib/config.js';
6
7
 
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const TEMPLATES_DIR = join(__dirname, '../templates');
10
+
7
11
  const DEFAULT_AGENTS = {
8
12
  pm: {
9
13
  name: 'Product Manager',
@@ -24,54 +28,93 @@ const DEFAULT_AGENTS = {
24
28
  };
25
29
 
26
30
  function slugify(name) {
27
- return name
28
- .toLowerCase()
29
- .replace(/[^a-z0-9]+/g, '-')
30
- .replace(/^-|-$/g, '');
31
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
32
+ }
33
+
34
+ function loadTemplates() {
35
+ const templates = [];
36
+ try {
37
+ const files = readdirSync(TEMPLATES_DIR).filter(f => f.endsWith('.json'));
38
+ for (const file of files) {
39
+ const raw = readFileSync(join(TEMPLATES_DIR, file), 'utf8');
40
+ const tmpl = JSON.parse(raw);
41
+ templates.push({ id: file.replace('.json', ''), ...tmpl });
42
+ }
43
+ } catch {}
44
+ return templates;
31
45
  }
32
46
 
33
47
  export async function initCommand(opts) {
34
- let project, agents, folderName;
48
+ let project, agents, folderName, rules;
35
49
 
36
50
  if (opts.yes) {
37
51
  project = 'My AgentXchain project';
38
52
  agents = DEFAULT_AGENTS;
39
53
  folderName = slugify(project);
54
+ rules = { max_consecutive_claims: 2, require_message: true, compress_after_words: 5000 };
40
55
  } else {
41
- const nameAnswer = await inquirer.prompt([{
42
- type: 'input',
43
- name: 'project',
44
- message: 'Project name:',
45
- default: 'My AgentXchain project'
46
- }]);
47
-
48
- project = nameAnswer.project;
49
- folderName = slugify(project);
50
-
51
- const folderAnswer = await inquirer.prompt([{
52
- type: 'input',
53
- name: 'folder',
54
- message: 'Folder name:',
55
- default: folderName
56
- }]);
57
-
58
- folderName = folderAnswer.folder;
59
-
60
- const agentAnswer = await inquirer.prompt([{
61
- type: 'confirm',
62
- name: 'useDefaults',
63
- message: 'Use default agents (pm, dev, qa, ux)?',
64
- default: true
56
+ const templates = loadTemplates();
57
+
58
+ // Template selection
59
+ const templateChoices = [
60
+ { name: `${chalk.cyan('Custom')} define your own agents`, value: 'custom' },
61
+ { name: `${chalk.cyan('Default')} — PM, Dev, QA, UX (4 agents)`, value: 'default' },
62
+ ...templates.map(t => ({
63
+ name: `${chalk.cyan(t.label)} — ${t.description} (${Object.keys(t.agents).length} agents)`,
64
+ value: t.id
65
+ }))
66
+ ];
67
+
68
+ const { template } = await inquirer.prompt([{
69
+ type: 'list',
70
+ name: 'template',
71
+ message: 'Choose a team template:',
72
+ choices: templateChoices
65
73
  }]);
66
74
 
67
- if (agentAnswer.useDefaults) {
75
+ if (template !== 'custom' && template !== 'default') {
76
+ const tmpl = templates.find(t => t.id === template);
77
+ agents = tmpl.agents;
78
+ rules = tmpl.rules || {};
79
+ const { projectName } = await inquirer.prompt([{
80
+ type: 'input',
81
+ name: 'projectName',
82
+ message: 'Project name:',
83
+ default: tmpl.project
84
+ }]);
85
+ project = projectName;
86
+ } else if (template === 'default') {
68
87
  agents = DEFAULT_AGENTS;
88
+ rules = { max_consecutive_claims: 2, require_message: true, compress_after_words: 5000 };
89
+ const { projectName } = await inquirer.prompt([{
90
+ type: 'input',
91
+ name: 'projectName',
92
+ message: 'Project name:',
93
+ default: 'My AgentXchain project'
94
+ }]);
95
+ project = projectName;
69
96
  } else {
97
+ const { projectName } = await inquirer.prompt([{
98
+ type: 'input',
99
+ name: 'projectName',
100
+ message: 'Project name:',
101
+ default: 'My AgentXchain project'
102
+ }]);
103
+ project = projectName;
70
104
  agents = {};
105
+ rules = { max_consecutive_claims: 2, require_message: true, compress_after_words: 5000 };
71
106
  let adding = true;
72
107
  while (adding) {
73
108
  const agent = await inquirer.prompt([
74
- { type: 'input', name: 'id', message: 'Agent ID (lowercase, no spaces):' },
109
+ {
110
+ type: 'input', name: 'id', message: 'Agent ID (lowercase, no spaces):',
111
+ validate: v => {
112
+ if (!v.match(/^[a-z0-9-]+$/)) return 'Lowercase letters, numbers, hyphens only.';
113
+ if (v === 'human' || v === 'system') return `"${v}" is reserved.`;
114
+ if (agents[v]) return `"${v}" already added.`;
115
+ return true;
116
+ }
117
+ },
75
118
  { type: 'input', name: 'name', message: 'Display name:' },
76
119
  { type: 'input', name: 'mandate', message: 'Mandate (what this agent does):' },
77
120
  { type: 'confirm', name: 'more', message: 'Add another agent?', default: true }
@@ -80,72 +123,81 @@ export async function initCommand(opts) {
80
123
  adding = agent.more;
81
124
  }
82
125
  }
126
+
127
+ folderName = slugify(project);
128
+ const { folder } = await inquirer.prompt([{
129
+ type: 'input',
130
+ name: 'folder',
131
+ message: 'Folder name:',
132
+ default: folderName
133
+ }]);
134
+ folderName = folder;
83
135
  }
84
136
 
85
137
  const dir = resolve(process.cwd(), folderName);
86
138
 
87
- if (existsSync(dir)) {
88
- if (existsSync(join(dir, CONFIG_FILE))) {
89
- if (!opts.yes) {
90
- const { overwrite } = await inquirer.prompt([{
91
- type: 'confirm',
92
- name: 'overwrite',
93
- message: `${folderName}/ already has an agentxchain.json. Overwrite?`,
94
- default: false
95
- }]);
96
- if (!overwrite) {
97
- console.log(chalk.yellow(' Aborted.'));
98
- return;
99
- }
139
+ if (existsSync(dir) && existsSync(join(dir, CONFIG_FILE))) {
140
+ if (!opts.yes) {
141
+ const { overwrite } = await inquirer.prompt([{
142
+ type: 'confirm',
143
+ name: 'overwrite',
144
+ message: `${folderName}/ already has agentxchain.json. Overwrite?`,
145
+ default: false
146
+ }]);
147
+ if (!overwrite) {
148
+ console.log(chalk.yellow(' Aborted.'));
149
+ return;
100
150
  }
101
151
  }
102
- } else {
103
- mkdirSync(dir, { recursive: true });
104
152
  }
105
153
 
154
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
155
+
106
156
  const config = {
107
157
  version: 3,
108
158
  project,
109
159
  agents,
110
160
  log: 'log.md',
161
+ state_file: 'state.md',
162
+ history_file: 'history.jsonl',
111
163
  rules: {
112
- max_consecutive_claims: 2,
113
- require_message: true,
114
- compress_after_words: 5000
164
+ max_consecutive_claims: rules.max_consecutive_claims || 2,
165
+ require_message: rules.require_message !== false,
166
+ compress_after_words: rules.compress_after_words || 5000,
167
+ ttl_minutes: rules.ttl_minutes || 10,
168
+ watch_interval_ms: rules.watch_interval_ms || 5000,
169
+ ...(rules.verify_command ? { verify_command: rules.verify_command } : {})
115
170
  }
116
171
  };
117
172
 
118
- const lock = {
119
- holder: null,
120
- last_released_by: null,
121
- turn_number: 0,
122
- claimed_at: null
123
- };
124
-
125
- const state = {
126
- phase: 'discovery',
127
- blocked: false,
128
- blocked_on: null,
129
- project
130
- };
173
+ const lock = { holder: null, last_released_by: null, turn_number: 0, claimed_at: null };
174
+ const state = { phase: 'discovery', blocked: false, blocked_on: null, project };
131
175
 
132
176
  writeFileSync(join(dir, CONFIG_FILE), JSON.stringify(config, null, 2) + '\n');
133
177
  writeFileSync(join(dir, LOCK_FILE), JSON.stringify(lock, null, 2) + '\n');
134
- writeFileSync(join(dir, STATE_FILE), JSON.stringify(state, null, 2) + '\n');
135
- writeFileSync(join(dir, config.log), `# ${project} — Agent Log\n\n## COMPRESSED CONTEXT\n\n(No compressed context yet.)\n\n## MESSAGE LOG\n\n(Agents append messages below this line.)\n`);
178
+ writeFileSync(join(dir, 'state.json'), JSON.stringify(state, null, 2) + '\n');
179
+ writeFileSync(join(dir, 'state.md'), `# ${project} — Current State\n\n## Architecture\n\n(Agents update this each turn with current decisions.)\n\n## Active Work\n\n(What's in progress right now.)\n\n## Open Issues\n\n(Bugs, blockers, risks.)\n\n## Next Steps\n\n(What should happen next.)\n`);
180
+ writeFileSync(join(dir, 'history.jsonl'), '');
181
+ writeFileSync(join(dir, 'log.md'), `# ${project} — Agent Log\n\n## COMPRESSED CONTEXT\n\n(No compressed context yet.)\n\n## MESSAGE LOG\n\n(Agents append messages below this line.)\n`);
136
182
  writeFileSync(join(dir, 'HUMAN_TASKS.md'), '# Human Tasks\n\n(Agents append tasks here when they need human action.)\n');
137
183
 
184
+ const agentCount = Object.keys(agents).length;
138
185
  console.log('');
139
186
  console.log(chalk.green(` ✓ Created ${chalk.bold(folderName)}/`));
140
187
  console.log('');
141
- console.log(` ${chalk.dim('├──')} ${CONFIG_FILE}`);
142
- console.log(` ${chalk.dim('├──')} ${LOCK_FILE}`);
143
- console.log(` ${chalk.dim('├──')} ${STATE_FILE}`);
144
- console.log(` ${chalk.dim('├──')} ${config.log}`);
188
+ console.log(` ${chalk.dim('├──')} agentxchain.json ${chalk.dim(`(${agentCount} agents)`)}`);
189
+ console.log(` ${chalk.dim('├──')} lock.json`);
190
+ console.log(` ${chalk.dim('├──')} state.json`);
191
+ console.log(` ${chalk.dim('├──')} state.md`);
192
+ console.log(` ${chalk.dim('├──')} history.jsonl`);
193
+ console.log(` ${chalk.dim('├──')} log.md`);
145
194
  console.log(` ${chalk.dim('└──')} HUMAN_TASKS.md`);
146
195
  console.log('');
147
196
  console.log(` ${chalk.dim('Agents:')} ${Object.keys(agents).join(', ')}`);
148
197
  console.log('');
149
- console.log(` ${chalk.cyan('Next:')} ${chalk.bold(`cd ${folderName}`)} && ${chalk.bold('agentxchain start')}`);
198
+ console.log(` ${chalk.cyan('Next:')}`);
199
+ console.log(` ${chalk.bold(`cd ${folderName}`)}`);
200
+ console.log(` ${chalk.bold('agentxchain watch')} ${chalk.dim('# start the referee')}`);
201
+ console.log(` ${chalk.bold('agentxchain start')} ${chalk.dim('# launch agents in your IDE')}`);
150
202
  console.log('');
151
203
  }
@@ -1,5 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import { loadConfig, loadLock, loadState } from '../lib/config.js';
3
+ import { getAgentStatus, loadSession } from '../adapters/cursor.js';
3
4
 
4
5
  export async function statusCommand(opts) {
5
6
  const result = loadConfig();
@@ -19,10 +20,9 @@ export async function statusCommand(opts) {
19
20
 
20
21
  console.log('');
21
22
  console.log(chalk.bold(' AgentXchain Status'));
22
- console.log(chalk.dim(' ' + '─'.repeat(40)));
23
+ console.log(chalk.dim(' ' + '─'.repeat(44)));
23
24
  console.log('');
24
25
 
25
- // Project
26
26
  console.log(` ${chalk.dim('Project:')} ${config.project}`);
27
27
  console.log(` ${chalk.dim('Phase:')} ${state ? formatPhase(state.phase) : chalk.dim('unknown')}`);
28
28
  if (state?.blocked) {
@@ -32,45 +32,83 @@ export async function statusCommand(opts) {
32
32
 
33
33
  // Lock
34
34
  if (lock) {
35
- if (lock.holder) {
35
+ if (lock.holder === 'human') {
36
+ console.log(` ${chalk.dim('Lock:')} ${chalk.magenta('HUMAN')} — you hold the lock`);
37
+ } else if (lock.holder) {
36
38
  const agentName = config.agents[lock.holder]?.name || lock.holder;
37
39
  console.log(` ${chalk.dim('Lock:')} ${chalk.yellow('CLAIMED')} by ${chalk.bold(lock.holder)} (${agentName})`);
38
40
  if (lock.claimed_at) {
39
- const ago = timeSince(lock.claimed_at);
40
- console.log(` ${chalk.dim('Claimed:')} ${ago} ago`);
41
+ console.log(` ${chalk.dim('Claimed:')} ${timeSince(lock.claimed_at)} ago`);
41
42
  }
42
43
  } else {
43
44
  console.log(` ${chalk.dim('Lock:')} ${chalk.green('FREE')} — any agent can claim`);
44
45
  }
45
46
  console.log(` ${chalk.dim('Turn:')} ${lock.turn_number}`);
46
47
  if (lock.last_released_by) {
47
- const lastName = config.agents[lock.last_released_by]?.name || lock.last_released_by;
48
- console.log(` ${chalk.dim('Last:')} ${lock.last_released_by} (${lastName})`);
48
+ console.log(` ${chalk.dim('Last:')} ${lock.last_released_by}`);
49
49
  }
50
50
  }
51
51
  console.log('');
52
52
 
53
+ // Cursor session info
54
+ const session = loadSession(root);
55
+ const apiKey = process.env.CURSOR_API_KEY;
56
+ const hasCursor = session?.ide === 'cursor' && session?.launched?.length > 0;
57
+
58
+ if (hasCursor) {
59
+ console.log(` ${chalk.dim('Cursor:')} ${chalk.cyan('Active session')} (${session.launched.length} agents)`);
60
+ console.log(` ${chalk.dim('Started:')} ${session.started_at}`);
61
+ if (session.repo) console.log(` ${chalk.dim('Repo:')} ${session.repo}`);
62
+ console.log('');
63
+ }
64
+
53
65
  // Agents
54
66
  console.log(` ${chalk.dim('Agents:')} ${Object.keys(config.agents).length}`);
67
+
55
68
  for (const [id, agent] of Object.entries(config.agents)) {
56
69
  const isHolder = lock?.holder === id;
57
70
  const marker = isHolder ? chalk.yellow('●') : chalk.dim('○');
58
71
  const label = isHolder ? chalk.bold(id) : id;
59
- console.log(` ${marker} ${label} — ${agent.name}`);
72
+
73
+ let cursorStatus = '';
74
+ if (hasCursor && apiKey) {
75
+ const cloudAgent = session.launched.find(a => a.id === id);
76
+ if (cloudAgent) {
77
+ try {
78
+ const statusData = await getAgentStatus(apiKey, cloudAgent.cloudId);
79
+ if (statusData?.status) {
80
+ cursorStatus = ` ${formatCursorStatus(statusData.status)}`;
81
+ }
82
+ } catch {
83
+ cursorStatus = chalk.dim(' [API error]');
84
+ }
85
+ }
86
+ }
87
+
88
+ console.log(` ${marker} ${label} — ${agent.name}${cursorStatus}`);
89
+ }
90
+
91
+ if (lock?.holder === 'human') {
92
+ console.log(` ${chalk.magenta('●')} ${chalk.bold('human')} — You`);
60
93
  }
94
+
61
95
  console.log('');
62
96
  }
63
97
 
64
98
  function formatPhase(phase) {
65
- const colors = {
66
- discovery: chalk.blue,
67
- build: chalk.green,
68
- qa: chalk.yellow,
69
- deploy: chalk.magenta,
70
- blocked: chalk.red
99
+ const colors = { discovery: chalk.blue, build: chalk.green, qa: chalk.yellow, deploy: chalk.magenta, blocked: chalk.red };
100
+ return (colors[phase] || chalk.white)(phase);
101
+ }
102
+
103
+ function formatCursorStatus(status) {
104
+ const map = {
105
+ CREATING: chalk.dim('[creating]'),
106
+ RUNNING: chalk.cyan('[running]'),
107
+ FINISHED: chalk.green('[finished]'),
108
+ STOPPED: chalk.yellow('[stopped]'),
109
+ ERRORED: chalk.red('[errored]'),
71
110
  };
72
- const fn = colors[phase] || chalk.white;
73
- return fn(phase);
111
+ return map[status] || chalk.dim(`[${status}]`);
74
112
  }
75
113
 
76
114
  function timeSince(iso) {
@@ -2,85 +2,69 @@ import { readFileSync, existsSync, unlinkSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import chalk from 'chalk';
4
4
  import { loadConfig } from '../lib/config.js';
5
+ import { deleteAgent, stopAgent, loadSession } from '../adapters/cursor.js';
5
6
 
6
7
  const SESSION_FILE = '.agentxchain-session.json';
7
8
 
8
9
  export async function stopCommand() {
9
10
  const result = loadConfig();
10
- if (!result) {
11
- console.log(chalk.red('No agentxchain.json found.'));
12
- process.exit(1);
13
- }
11
+ if (!result) { console.log(chalk.red(' No agentxchain.json found.')); process.exit(1); }
14
12
 
15
13
  const { root } = result;
16
- const sessionPath = join(root, SESSION_FILE);
14
+ const session = loadSession(root);
17
15
 
18
- if (!existsSync(sessionPath)) {
19
- console.log(chalk.yellow(' No active session found (.agentxchain-session.json missing).'));
20
- console.log(chalk.dim(' If agents are running, stop them manually in the IDE.'));
16
+ if (!session) {
17
+ console.log(chalk.yellow(' No active session found.'));
18
+ console.log(chalk.dim(' If agents are running, stop them manually.'));
21
19
  return;
22
20
  }
23
21
 
24
- const session = JSON.parse(readFileSync(sessionPath, 'utf8'));
25
22
  console.log('');
26
23
  console.log(chalk.bold(` Stopping ${session.launched.length} agents (${session.ide})`));
27
24
  console.log('');
28
25
 
29
- switch (session.ide) {
30
- case 'cursor':
31
- await stopCursorAgents(session);
32
- break;
33
- case 'claude-code':
34
- stopClaudeCodeAgents(session);
35
- break;
36
- default:
37
- console.log(chalk.yellow(` IDE "${session.ide}" manual stop required.`));
38
- }
39
-
40
- unlinkSync(sessionPath);
41
- console.log('');
42
- console.log(chalk.dim(' Session file removed.'));
43
- console.log(chalk.green(' All agents stopped.'));
44
- console.log('');
45
- }
46
-
47
- async function stopCursorAgents(session) {
48
- const apiKey = process.env.CURSOR_API_KEY;
49
-
50
- for (const agent of session.launched) {
51
- if (agent.cloudId && apiKey) {
52
- try {
53
- const res = await fetch(`https://api.cursor.com/v0/agents/${agent.cloudId}`, {
54
- method: 'DELETE',
55
- headers: { 'Authorization': `Basic ${btoa(apiKey + ':')}` }
56
- });
57
- if (res.ok) {
58
- console.log(chalk.green(` ✓ Stopped ${agent.id} (cloud ID: ${agent.cloudId})`));
59
- } else {
60
- console.log(chalk.yellow(` ⚠ Could not stop ${agent.id}: ${res.status}`));
26
+ if (session.ide === 'cursor') {
27
+ const apiKey = process.env.CURSOR_API_KEY;
28
+ if (!apiKey) {
29
+ console.log(chalk.yellow(' CURSOR_API_KEY not set. Cannot stop agents via API.'));
30
+ console.log(chalk.dim(' Stop them manually at cursor.com/agents'));
31
+ } else {
32
+ for (const agent of session.launched) {
33
+ try {
34
+ const deleted = await deleteAgent(apiKey, agent.cloudId);
35
+ if (deleted) {
36
+ console.log(chalk.green(` ✓ Deleted ${chalk.bold(agent.id)} (${agent.cloudId})`));
37
+ } else {
38
+ console.log(chalk.yellow(` ⚠ Could not delete ${agent.id} — may already be gone`));
39
+ }
40
+ } catch (err) {
41
+ console.log(chalk.red(` ✗ ${agent.id}: ${err.message}`));
61
42
  }
62
- } catch (err) {
63
- console.log(chalk.red(` ✗ Error stopping ${agent.id}: ${err.message}`));
64
43
  }
65
- } else {
66
- console.log(chalk.dim(` ${agent.id} — no cloud ID or API key; stop manually in Cursor.`));
67
44
  }
68
- }
69
- }
70
-
71
- function stopClaudeCodeAgents(session) {
72
- for (const agent of session.launched) {
73
- if (agent.pid) {
74
- try {
75
- process.kill(agent.pid, 'SIGTERM');
76
- console.log(chalk.green(` ✓ Sent SIGTERM to ${agent.id} (PID: ${agent.pid})`));
77
- } catch (err) {
78
- if (err.code === 'ESRCH') {
79
- console.log(chalk.dim(` ${agent.id} (PID: ${agent.pid}) — already stopped.`));
80
- } else {
81
- console.log(chalk.red(` ✗ Error stopping ${agent.id}: ${err.message}`));
45
+ } else if (session.ide === 'claude-code') {
46
+ for (const agent of session.launched) {
47
+ if (agent.pid) {
48
+ try {
49
+ process.kill(agent.pid, 'SIGTERM');
50
+ console.log(chalk.green(` ✓ Sent SIGTERM to ${agent.id} (PID: ${agent.pid})`));
51
+ } catch (err) {
52
+ if (err.code === 'ESRCH') {
53
+ console.log(chalk.dim(` ${agent.id} (PID: ${agent.pid}) — already stopped`));
54
+ } else {
55
+ console.log(chalk.red(` ✗ ${agent.id}: ${err.message}`));
56
+ }
82
57
  }
83
58
  }
84
59
  }
85
60
  }
61
+
62
+ // Remove session file
63
+ const sessionPath = join(root, SESSION_FILE);
64
+ if (existsSync(sessionPath)) unlinkSync(sessionPath);
65
+
66
+ console.log('');
67
+ console.log(chalk.dim(' Session file removed.'));
68
+ console.log(chalk.green(' All agents stopped.'));
69
+ console.log('');
86
70
  }
@@ -0,0 +1,42 @@
1
+ import { execSync } from 'child_process';
2
+ import { readFileSync } from 'fs';
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname, join } from 'path';
5
+ import chalk from 'chalk';
6
+
7
+ export async function updateCommand() {
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const pkg = JSON.parse(readFileSync(join(__dirname, '../../package.json'), 'utf8'));
10
+ const currentVersion = pkg.version;
11
+
12
+ console.log('');
13
+ console.log(` ${chalk.dim('Current version:')} ${currentVersion}`);
14
+ console.log(` ${chalk.dim('Checking npm for latest...')}`);
15
+
16
+ try {
17
+ const latest = execSync('npm view agentxchain version', { encoding: 'utf8' }).trim();
18
+
19
+ if (latest === currentVersion) {
20
+ console.log(chalk.green(` ✓ Already on the latest version (${currentVersion}).`));
21
+ console.log('');
22
+ return;
23
+ }
24
+
25
+ console.log(` ${chalk.dim('Latest version:')} ${chalk.cyan(latest)}`);
26
+ console.log('');
27
+ console.log(` Updating...`);
28
+
29
+ execSync('npm install -g agentxchain@latest', { stdio: 'inherit' });
30
+
31
+ console.log('');
32
+ console.log(chalk.green(` ✓ Updated to ${latest}`));
33
+ console.log('');
34
+ } catch (err) {
35
+ console.log('');
36
+ console.log(chalk.yellow(' Could not auto-update. Run manually:'));
37
+ console.log(` ${chalk.bold('npm install -g agentxchain@latest')}`);
38
+ console.log('');
39
+ console.log(chalk.dim(` Error: ${err.message}`));
40
+ console.log('');
41
+ }
42
+ }