agentxchain 0.1.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.
package/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # agentxchain
2
+
3
+ CLI for multi-agent coordination in your IDE. Define a team of AI agents, launch them in Cursor / Claude Code / VS Code, and let them coordinate via a shared protocol.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g agentxchain
9
+ ```
10
+
11
+ Or run without installing:
12
+
13
+ ```bash
14
+ npx agentxchain init
15
+ ```
16
+
17
+ ## Quick start
18
+
19
+ ```bash
20
+ # 1. Initialize a project (creates agentxchain.json, lock.json, state.json, log.md)
21
+ agentxchain init
22
+
23
+ # 2. Check status
24
+ agentxchain status
25
+
26
+ # 3. Launch agents in your IDE
27
+ agentxchain start --ide cursor
28
+
29
+ # 4. Stop agents
30
+ agentxchain stop
31
+ ```
32
+
33
+ ## Commands
34
+
35
+ ### `agentxchain init`
36
+
37
+ Interactive setup. Creates all protocol files in the current directory.
38
+
39
+ - `-y, --yes` — skip prompts, use 4 default agents (pm, dev, qa, ux)
40
+
41
+ ### `agentxchain status`
42
+
43
+ Show current lock holder, phase, turn number, and all agents.
44
+
45
+ - `-j, --json` — output as JSON
46
+
47
+ ### `agentxchain start`
48
+
49
+ Launch agents in your IDE.
50
+
51
+ - `--ide <ide>` — target IDE: `cursor`, `claude-code`, `vscode` (default: cursor)
52
+ - `--agent <id>` — launch only one specific agent
53
+ - `--dry-run` — preview what would be launched
54
+
55
+ For Cursor Cloud Agents, set `CURSOR_API_KEY` in your environment. Without it, the CLI prints seed prompts you can paste manually.
56
+
57
+ ### `agentxchain stop`
58
+
59
+ Stop all running agent sessions. Reads `.agentxchain-session.json` to find active agents.
60
+
61
+ ## How it works
62
+
63
+ AgentXchain uses a **claim-based protocol**:
64
+
65
+ 1. Agents are defined in `agentxchain.json` (name, mandate, rules)
66
+ 2. A `lock.json` file tracks who holds the lock
67
+ 3. When the lock is free, any agent can claim it
68
+ 4. The agent does its work, logs a message, and releases the lock
69
+ 5. Another agent claims. The cycle continues.
70
+
71
+ No fixed turn order. Agents self-organize. See [PROTOCOL-v3.md](https://agentxchain.dev) for the full spec.
72
+
73
+ ## Links
74
+
75
+ - Website: [agentxchain.dev](https://agentxchain.dev)
76
+ - GitHub: [github.com/shivamtiwari93/agentXchain.dev](https://github.com/shivamtiwari93/agentXchain.dev)
77
+ - Protocol: [PROTOCOL-v3.md](https://github.com/shivamtiwari93/agentXchain.dev/blob/main/PROTOCOL-v3.md)
78
+
79
+ ## License
80
+
81
+ MIT
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import chalk from 'chalk';
5
+ import { initCommand } from '../src/commands/init.js';
6
+ import { statusCommand } from '../src/commands/status.js';
7
+ import { startCommand } from '../src/commands/start.js';
8
+ import { stopCommand } from '../src/commands/stop.js';
9
+
10
+ const program = new Command();
11
+
12
+ program
13
+ .name('agentxchain')
14
+ .description('Multi-agent coordination in your IDE')
15
+ .version('0.1.0');
16
+
17
+ program
18
+ .command('init')
19
+ .description('Initialize a new AgentXchain project')
20
+ .option('-y, --yes', 'Skip prompts, use defaults')
21
+ .action(initCommand);
22
+
23
+ program
24
+ .command('status')
25
+ .description('Show current lock status, phase, and agents')
26
+ .option('-j, --json', 'Output as JSON')
27
+ .action(statusCommand);
28
+
29
+ program
30
+ .command('start')
31
+ .description('Launch agents in your IDE')
32
+ .option('--ide <ide>', 'Target IDE: cursor, vscode, claude-code', 'cursor')
33
+ .option('--agent <id>', 'Launch a specific agent only')
34
+ .option('--dry-run', 'Print what would be launched without doing it')
35
+ .action(startCommand);
36
+
37
+ program
38
+ .command('stop')
39
+ .description('Stop all running agent sessions')
40
+ .action(stopCommand);
41
+
42
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "agentxchain",
3
+ "version": "0.1.0",
4
+ "description": "CLI for AgentXchain — multi-agent coordination in your IDE",
5
+ "type": "module",
6
+ "bin": {
7
+ "agentxchain": "./bin/agentxchain.js"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "src/",
12
+ "README.md"
13
+ ],
14
+ "scripts": {
15
+ "dev": "node bin/agentxchain.js",
16
+ "build:macos": "bun build bin/agentxchain.js --compile --target=bun-darwin-arm64 --outfile=dist/agentxchain-macos-arm64",
17
+ "build:linux": "bun build bin/agentxchain.js --compile --target=bun-linux-x64 --outfile=dist/agentxchain-linux-x64"
18
+ },
19
+ "keywords": ["ai", "agents", "multi-agent", "coordination", "sdlc", "cursor", "vscode", "claude-code", "agentxchain"],
20
+ "author": "shivamtiwari93",
21
+ "license": "MIT",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/shivamtiwari93/agentXchain.dev.git"
25
+ },
26
+ "homepage": "https://agentxchain.dev",
27
+ "dependencies": {
28
+ "commander": "^13.0.0",
29
+ "chalk": "^5.4.0",
30
+ "inquirer": "^12.0.0",
31
+ "ora": "^8.0.0"
32
+ },
33
+ "engines": {
34
+ "node": ">=18"
35
+ }
36
+ }
@@ -0,0 +1,49 @@
1
+ import { spawn } from 'child_process';
2
+ import chalk from 'chalk';
3
+ import { generateSeedPrompt } from '../lib/seed-prompt.js';
4
+ import { writeFileSync } from 'fs';
5
+ import { join } from 'path';
6
+
7
+ export async function launchClaudeCodeAgents(config, root, opts) {
8
+ const agents = filterAgents(config, opts.agent);
9
+ const launched = [];
10
+
11
+ for (const [id, agent] of Object.entries(agents)) {
12
+ const prompt = generateSeedPrompt(id, agent, config);
13
+
14
+ try {
15
+ const child = spawn('claude', ['--system-prompt', prompt], {
16
+ cwd: root,
17
+ stdio: 'ignore',
18
+ detached: true
19
+ });
20
+ child.unref();
21
+
22
+ launched.push({ id, name: agent.name, pid: child.pid });
23
+ console.log(chalk.green(` ✓ Launched ${chalk.bold(id)} (${agent.name}) — PID: ${child.pid}`));
24
+ } catch (err) {
25
+ console.log(chalk.red(` Failed to launch ${id}: ${err.message}`));
26
+ console.log(chalk.dim(` Make sure 'claude' CLI is installed and in your PATH.`));
27
+ }
28
+ }
29
+
30
+ if (launched.length > 0) {
31
+ const sessionFile = JSON.stringify({ launched, started_at: new Date().toISOString(), ide: 'claude-code' }, null, 2);
32
+ writeFileSync(join(root, '.agentxchain-session.json'), sessionFile + '\n');
33
+ console.log('');
34
+ console.log(chalk.dim(` Session saved to .agentxchain-session.json`));
35
+ }
36
+
37
+ return launched;
38
+ }
39
+
40
+ function filterAgents(config, specificId) {
41
+ if (specificId) {
42
+ if (!config.agents[specificId]) {
43
+ console.log(chalk.red(` Agent "${specificId}" not found in agentxchain.json`));
44
+ process.exit(1);
45
+ }
46
+ return { [specificId]: config.agents[specificId] };
47
+ }
48
+ return config.agents;
49
+ }
@@ -0,0 +1,98 @@
1
+ import chalk from 'chalk';
2
+ import { generateSeedPrompt } from '../lib/seed-prompt.js';
3
+
4
+ const API_BASE = 'https://api.cursor.com/v0';
5
+
6
+ export async function launchCursorAgents(config, root, opts) {
7
+ const apiKey = process.env.CURSOR_API_KEY;
8
+
9
+ if (!apiKey) {
10
+ console.log('');
11
+ console.log(chalk.yellow(' Cursor Cloud Agents API key not found.'));
12
+ console.log('');
13
+ console.log(' To launch agents via Cursor Cloud API:');
14
+ console.log(` 1. Go to ${chalk.cyan('cursor.com/dashboard')} → Cloud Agents`);
15
+ console.log(' 2. Create an API key');
16
+ console.log(` 3. Set: ${chalk.bold('export CURSOR_API_KEY=your_key')}`);
17
+ console.log(` 4. Run: ${chalk.bold('agentxchain start --ide cursor')}`);
18
+ console.log('');
19
+ console.log(chalk.dim(' Falling back to seed prompt output...'));
20
+ console.log('');
21
+ return fallbackPromptOutput(config, opts);
22
+ }
23
+
24
+ const agents = filterAgents(config, opts.agent);
25
+ const launched = [];
26
+
27
+ for (const [id, agent] of Object.entries(agents)) {
28
+ const prompt = generateSeedPrompt(id, agent, config);
29
+
30
+ try {
31
+ const res = await fetch(`${API_BASE}/agents`, {
32
+ method: 'POST',
33
+ headers: {
34
+ 'Authorization': `Basic ${btoa(apiKey + ':')}`,
35
+ 'Content-Type': 'application/json'
36
+ },
37
+ body: JSON.stringify({
38
+ prompt,
39
+ repository: root,
40
+ name: `agentxchain-${id}`
41
+ })
42
+ });
43
+
44
+ if (!res.ok) {
45
+ const body = await res.text();
46
+ console.log(chalk.red(` Failed to launch ${id}: ${res.status} ${body}`));
47
+ continue;
48
+ }
49
+
50
+ const data = await res.json();
51
+ launched.push({ id, name: agent.name, cloudId: data.id || 'unknown' });
52
+ console.log(chalk.green(` ✓ Launched ${chalk.bold(id)} (${agent.name}) — cloud ID: ${data.id || '?'}`));
53
+ } catch (err) {
54
+ console.log(chalk.red(` Failed to launch ${id}: ${err.message}`));
55
+ }
56
+ }
57
+
58
+ if (launched.length > 0) {
59
+ const sessionFile = JSON.stringify({ launched, started_at: new Date().toISOString(), ide: 'cursor' }, null, 2);
60
+ const { writeFileSync } = await import('fs');
61
+ const { join } = await import('path');
62
+ writeFileSync(join(root, '.agentxchain-session.json'), sessionFile + '\n');
63
+ console.log('');
64
+ console.log(chalk.dim(` Session saved to .agentxchain-session.json`));
65
+ }
66
+
67
+ return launched;
68
+ }
69
+
70
+ function fallbackPromptOutput(config, opts) {
71
+ const agents = filterAgents(config, opts.agent);
72
+
73
+ console.log(chalk.bold(' Copy-paste these prompts into separate Cursor sessions:'));
74
+ console.log('');
75
+
76
+ for (const [id, agent] of Object.entries(agents)) {
77
+ const prompt = generateSeedPrompt(id, agent, config);
78
+ console.log(chalk.dim(' ' + '─'.repeat(50)));
79
+ console.log(chalk.cyan(` Agent: ${chalk.bold(id)} (${agent.name})`));
80
+ console.log(chalk.dim(' ' + '─'.repeat(50)));
81
+ console.log('');
82
+ console.log(prompt);
83
+ console.log('');
84
+ }
85
+
86
+ return [];
87
+ }
88
+
89
+ function filterAgents(config, specificId) {
90
+ if (specificId) {
91
+ if (!config.agents[specificId]) {
92
+ console.log(chalk.red(` Agent "${specificId}" not found in agentxchain.json`));
93
+ process.exit(1);
94
+ }
95
+ return { [specificId]: config.agents[specificId] };
96
+ }
97
+ return config.agents;
98
+ }
@@ -0,0 +1,135 @@
1
+ import { writeFileSync, existsSync, mkdirSync } from 'fs';
2
+ import { join } from 'path';
3
+ import chalk from 'chalk';
4
+ import inquirer from 'inquirer';
5
+ import { CONFIG_FILE, LOCK_FILE, STATE_FILE } from '../lib/config.js';
6
+
7
+ const DEFAULT_AGENTS = {
8
+ pm: {
9
+ name: 'Product Manager',
10
+ mandate: 'Quality uplift, purchase blockers, voice of customer. Frame decisions from the user perspective. Production quality, not demo quality.'
11
+ },
12
+ dev: {
13
+ name: 'Fullstack Developer',
14
+ mandate: 'Implement features, run build/lint/test, use tools (git, npm). Every turn must produce working code. Push back on vague requirements.'
15
+ },
16
+ qa: {
17
+ name: 'QA Engineer',
18
+ mandate: 'Test coverage, regression, acceptance criteria. Run the app, try to break things. File bugs with reproduction steps.'
19
+ },
20
+ ux: {
21
+ name: 'UX & Compression',
22
+ mandate: 'Review UI/UX from a first-time user perspective. Compress context when log exceeds word limit.'
23
+ }
24
+ };
25
+
26
+ export async function initCommand(opts) {
27
+ const dir = process.cwd();
28
+
29
+ if (existsSync(join(dir, CONFIG_FILE)) && !opts.yes) {
30
+ const { overwrite } = await inquirer.prompt([{
31
+ type: 'confirm',
32
+ name: 'overwrite',
33
+ message: `${CONFIG_FILE} already exists. Overwrite?`,
34
+ default: false
35
+ }]);
36
+ if (!overwrite) {
37
+ console.log(chalk.yellow('Aborted.'));
38
+ return;
39
+ }
40
+ }
41
+
42
+ let project, agents;
43
+
44
+ if (opts.yes) {
45
+ project = 'My AgentXchain project';
46
+ agents = DEFAULT_AGENTS;
47
+ } else {
48
+ const answers = await inquirer.prompt([
49
+ {
50
+ type: 'input',
51
+ name: 'project',
52
+ message: 'Project name (one line):',
53
+ default: 'My AgentXchain project'
54
+ },
55
+ {
56
+ type: 'confirm',
57
+ name: 'useDefaults',
58
+ message: `Use default agents (pm, dev, qa, ux)?`,
59
+ default: true
60
+ }
61
+ ]);
62
+
63
+ project = answers.project;
64
+
65
+ if (answers.useDefaults) {
66
+ agents = DEFAULT_AGENTS;
67
+ } else {
68
+ agents = {};
69
+ let adding = true;
70
+ while (adding) {
71
+ const agent = await inquirer.prompt([
72
+ { type: 'input', name: 'id', message: 'Agent ID (lowercase, no spaces):' },
73
+ { type: 'input', name: 'name', message: 'Display name:' },
74
+ { type: 'input', name: 'mandate', message: 'Mandate (what this agent does):' },
75
+ { type: 'confirm', name: 'more', message: 'Add another agent?', default: true }
76
+ ]);
77
+ agents[agent.id] = { name: agent.name, mandate: agent.mandate };
78
+ adding = agent.more;
79
+ }
80
+ }
81
+ }
82
+
83
+ const config = {
84
+ version: 3,
85
+ project,
86
+ agents,
87
+ log: 'log.md',
88
+ rules: {
89
+ max_consecutive_claims: 2,
90
+ require_message: true,
91
+ compress_after_words: 5000
92
+ }
93
+ };
94
+
95
+ const lock = {
96
+ holder: null,
97
+ last_released_by: null,
98
+ turn_number: 0,
99
+ claimed_at: null
100
+ };
101
+
102
+ const state = {
103
+ phase: 'discovery',
104
+ blocked: false,
105
+ blocked_on: null,
106
+ project
107
+ };
108
+
109
+ writeFileSync(join(dir, CONFIG_FILE), JSON.stringify(config, null, 2) + '\n');
110
+ writeFileSync(join(dir, LOCK_FILE), JSON.stringify(lock, null, 2) + '\n');
111
+ writeFileSync(join(dir, STATE_FILE), JSON.stringify(state, null, 2) + '\n');
112
+
113
+ const logFile = config.log;
114
+ if (!existsSync(join(dir, logFile))) {
115
+ writeFileSync(join(dir, logFile), `# ${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`);
116
+ }
117
+
118
+ if (!existsSync(join(dir, 'HUMAN_TASKS.md'))) {
119
+ writeFileSync(join(dir, 'HUMAN_TASKS.md'), '# Human Tasks\n\n(Agents append tasks here when they need human action.)\n');
120
+ }
121
+
122
+ console.log('');
123
+ console.log(chalk.green(' AgentXchain project initialized.'));
124
+ console.log('');
125
+ console.log(` ${chalk.dim('Config:')} ${CONFIG_FILE}`);
126
+ console.log(` ${chalk.dim('Lock:')} ${LOCK_FILE}`);
127
+ console.log(` ${chalk.dim('State:')} ${STATE_FILE}`);
128
+ console.log(` ${chalk.dim('Log:')} ${logFile}`);
129
+ console.log(` ${chalk.dim('Tasks:')} HUMAN_TASKS.md`);
130
+ console.log('');
131
+ console.log(` ${chalk.dim('Agents:')} ${Object.keys(agents).join(', ')}`);
132
+ console.log('');
133
+ console.log(` ${chalk.cyan('Next:')} Run ${chalk.bold('agentxchain start')} to launch agents in your IDE.`);
134
+ console.log('');
135
+ }
@@ -0,0 +1,72 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { loadConfig } from '../lib/config.js';
4
+ import { generateSeedPrompt } from '../lib/seed-prompt.js';
5
+
6
+ export async function startCommand(opts) {
7
+ const result = loadConfig();
8
+ if (!result) {
9
+ console.log(chalk.red('No agentxchain.json found. Run `agentxchain init` first.'));
10
+ process.exit(1);
11
+ }
12
+
13
+ const { root, config } = result;
14
+ const agentCount = Object.keys(config.agents).length;
15
+ const ide = opts.ide;
16
+
17
+ console.log('');
18
+ console.log(chalk.bold(` Launching ${agentCount} agents via ${ide}`));
19
+ console.log(chalk.dim(` Project: ${config.project}`));
20
+ console.log('');
21
+
22
+ if (opts.dryRun) {
23
+ console.log(chalk.yellow(' DRY RUN — showing what would be launched:'));
24
+ console.log('');
25
+ for (const [id, agent] of Object.entries(config.agents)) {
26
+ if (opts.agent && opts.agent !== id) continue;
27
+ console.log(` ${chalk.cyan(id)} — ${agent.name}`);
28
+ console.log(chalk.dim(` ${agent.mandate.slice(0, 80)}...`));
29
+ console.log('');
30
+ }
31
+ return;
32
+ }
33
+
34
+ switch (ide) {
35
+ case 'cursor': {
36
+ const { launchCursorAgents } = await import('../adapters/cursor.js');
37
+ await launchCursorAgents(config, root, opts);
38
+ break;
39
+ }
40
+ case 'claude-code': {
41
+ const { launchClaudeCodeAgents } = await import('../adapters/claude-code.js');
42
+ await launchClaudeCodeAgents(config, root, opts);
43
+ break;
44
+ }
45
+ case 'vscode': {
46
+ console.log(chalk.yellow(' VS Code adapter coming soon.'));
47
+ console.log(chalk.dim(' For now, use the seed prompts below in VS Code chat panels.'));
48
+ console.log('');
49
+ printPrompts(config, opts);
50
+ break;
51
+ }
52
+ default:
53
+ console.log(chalk.red(` Unknown IDE: ${ide}. Supported: cursor, vscode, claude-code`));
54
+ process.exit(1);
55
+ }
56
+ }
57
+
58
+ function printPrompts(config, opts) {
59
+ const agents = opts.agent
60
+ ? { [opts.agent]: config.agents[opts.agent] }
61
+ : config.agents;
62
+
63
+ for (const [id, agent] of Object.entries(agents)) {
64
+ const prompt = generateSeedPrompt(id, agent, config);
65
+ console.log(chalk.dim(' ' + '─'.repeat(50)));
66
+ console.log(chalk.cyan(` Agent: ${chalk.bold(id)} (${agent.name})`));
67
+ console.log(chalk.dim(' ' + '─'.repeat(50)));
68
+ console.log('');
69
+ console.log(prompt);
70
+ console.log('');
71
+ }
72
+ }
@@ -0,0 +1,84 @@
1
+ import chalk from 'chalk';
2
+ import { loadConfig, loadLock, loadState } from '../lib/config.js';
3
+
4
+ export async function statusCommand(opts) {
5
+ const result = loadConfig();
6
+ if (!result) {
7
+ console.log(chalk.red('No agentxchain.json found. Run `agentxchain init` first.'));
8
+ process.exit(1);
9
+ }
10
+
11
+ const { root, config } = result;
12
+ const lock = loadLock(root);
13
+ const state = loadState(root);
14
+
15
+ if (opts.json) {
16
+ console.log(JSON.stringify({ config, lock, state }, null, 2));
17
+ return;
18
+ }
19
+
20
+ console.log('');
21
+ console.log(chalk.bold(' AgentXchain Status'));
22
+ console.log(chalk.dim(' ' + '─'.repeat(40)));
23
+ console.log('');
24
+
25
+ // Project
26
+ console.log(` ${chalk.dim('Project:')} ${config.project}`);
27
+ console.log(` ${chalk.dim('Phase:')} ${state ? formatPhase(state.phase) : chalk.dim('unknown')}`);
28
+ if (state?.blocked) {
29
+ console.log(` ${chalk.dim('Blocked:')} ${chalk.red('YES')} — ${state.blocked_on || 'unknown reason'}`);
30
+ }
31
+ console.log('');
32
+
33
+ // Lock
34
+ if (lock) {
35
+ if (lock.holder) {
36
+ const agentName = config.agents[lock.holder]?.name || lock.holder;
37
+ console.log(` ${chalk.dim('Lock:')} ${chalk.yellow('CLAIMED')} by ${chalk.bold(lock.holder)} (${agentName})`);
38
+ if (lock.claimed_at) {
39
+ const ago = timeSince(lock.claimed_at);
40
+ console.log(` ${chalk.dim('Claimed:')} ${ago} ago`);
41
+ }
42
+ } else {
43
+ console.log(` ${chalk.dim('Lock:')} ${chalk.green('FREE')} — any agent can claim`);
44
+ }
45
+ console.log(` ${chalk.dim('Turn:')} ${lock.turn_number}`);
46
+ 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})`);
49
+ }
50
+ }
51
+ console.log('');
52
+
53
+ // Agents
54
+ console.log(` ${chalk.dim('Agents:')} ${Object.keys(config.agents).length}`);
55
+ for (const [id, agent] of Object.entries(config.agents)) {
56
+ const isHolder = lock?.holder === id;
57
+ const marker = isHolder ? chalk.yellow('●') : chalk.dim('○');
58
+ const label = isHolder ? chalk.bold(id) : id;
59
+ console.log(` ${marker} ${label} — ${agent.name}`);
60
+ }
61
+ console.log('');
62
+ }
63
+
64
+ 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
71
+ };
72
+ const fn = colors[phase] || chalk.white;
73
+ return fn(phase);
74
+ }
75
+
76
+ function timeSince(iso) {
77
+ const ms = Date.now() - new Date(iso).getTime();
78
+ const sec = Math.floor(ms / 1000);
79
+ if (sec < 60) return `${sec}s`;
80
+ const min = Math.floor(sec / 60);
81
+ if (min < 60) return `${min}m`;
82
+ const hr = Math.floor(min / 60);
83
+ return `${hr}h ${min % 60}m`;
84
+ }
@@ -0,0 +1,86 @@
1
+ import { readFileSync, existsSync, unlinkSync } from 'fs';
2
+ import { join } from 'path';
3
+ import chalk from 'chalk';
4
+ import { loadConfig } from '../lib/config.js';
5
+
6
+ const SESSION_FILE = '.agentxchain-session.json';
7
+
8
+ export async function stopCommand() {
9
+ const result = loadConfig();
10
+ if (!result) {
11
+ console.log(chalk.red('No agentxchain.json found.'));
12
+ process.exit(1);
13
+ }
14
+
15
+ const { root } = result;
16
+ const sessionPath = join(root, SESSION_FILE);
17
+
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.'));
21
+ return;
22
+ }
23
+
24
+ const session = JSON.parse(readFileSync(sessionPath, 'utf8'));
25
+ console.log('');
26
+ console.log(chalk.bold(` Stopping ${session.launched.length} agents (${session.ide})`));
27
+ console.log('');
28
+
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}`));
61
+ }
62
+ } catch (err) {
63
+ console.log(chalk.red(` ✗ Error stopping ${agent.id}: ${err.message}`));
64
+ }
65
+ } else {
66
+ console.log(chalk.dim(` ${agent.id} — no cloud ID or API key; stop manually in Cursor.`));
67
+ }
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}`));
82
+ }
83
+ }
84
+ }
85
+ }
86
+ }
@@ -0,0 +1,36 @@
1
+ import { readFileSync, existsSync } from 'fs';
2
+ import { join } from 'path';
3
+
4
+ const CONFIG_FILE = 'agentxchain.json';
5
+ const LOCK_FILE = 'lock.json';
6
+ const STATE_FILE = 'state.json';
7
+
8
+ export function findProjectRoot(startDir = process.cwd()) {
9
+ let dir = startDir;
10
+ while (dir !== '/') {
11
+ if (existsSync(join(dir, CONFIG_FILE))) return dir;
12
+ dir = join(dir, '..');
13
+ }
14
+ return null;
15
+ }
16
+
17
+ export function loadConfig(dir = process.cwd()) {
18
+ const root = findProjectRoot(dir);
19
+ if (!root) return null;
20
+ const raw = readFileSync(join(root, CONFIG_FILE), 'utf8');
21
+ return { root, config: JSON.parse(raw) };
22
+ }
23
+
24
+ export function loadLock(root) {
25
+ const path = join(root, LOCK_FILE);
26
+ if (!existsSync(path)) return null;
27
+ return JSON.parse(readFileSync(path, 'utf8'));
28
+ }
29
+
30
+ export function loadState(root) {
31
+ const path = join(root, STATE_FILE);
32
+ if (!existsSync(path)) return null;
33
+ return JSON.parse(readFileSync(path, 'utf8'));
34
+ }
35
+
36
+ export { CONFIG_FILE, LOCK_FILE, STATE_FILE };
@@ -0,0 +1,44 @@
1
+ export function generateSeedPrompt(agentId, agentDef, config) {
2
+ const logFile = config.log || 'log.md';
3
+ const maxClaims = config.rules?.max_consecutive_claims || 2;
4
+
5
+ return `You are agent "${agentId}" on an AgentXchain team.
6
+
7
+ YOUR IDENTITY
8
+ - Name: ${agentDef.name}
9
+ - Mandate: ${agentDef.mandate}
10
+
11
+ SETUP
12
+ - The project config is in agentxchain.json. Your entry is under agents."${agentId}".
13
+ - The protocol spec is PROTOCOL-v3.md (in this folder or one level up).
14
+ - The message log is "${logFile}". The lock is lock.json. Project state is state.json.
15
+
16
+ YOUR LOOP (run forever, do not exit)
17
+ 1. Read lock.json. If holder is not null, wait 30 seconds and try again.
18
+ 2. When holder is null, CLAIM: write lock.json with holder="${agentId}", claimed_at=now.
19
+ Re-read lock.json to confirm you won. If someone else claimed, go back to step 1.
20
+ 3. You have the lock. Read state.json (phase, blocked), then read the latest messages in ${logFile}.
21
+ - If blocked and you can't unblock: short "Still blocked" message, release lock, go to step 1.
22
+ - Otherwise: do your work per your mandate.
23
+ 4. Append ONE message to ${logFile}:
24
+
25
+ ---
26
+ ### [${agentId}] (${agentDef.name}) | Turn N
27
+ **Status:** ...
28
+ **Decision:** ...
29
+ **Action:** ...
30
+ **Next:** ...
31
+
32
+ 5. Update state.json if phase/blocked changed.
33
+ 6. RELEASE lock.json: holder=null, last_released_by="${agentId}", turn_number=previous+1, claimed_at=null.
34
+ This MUST be the last thing you write.
35
+ 7. Go to step 1. Never exit. Never say "I'm done."
36
+
37
+ RULES
38
+ - Never write without holding the lock.
39
+ - One message per turn. One git commit per turn: "Turn N - ${agentId} - description".
40
+ - Challenge previous work. Find at least one risk or issue. No blind agreement.
41
+ - Stay in your lane. Do what your mandate says.
42
+ - Max ${maxClaims} consecutive claims. If you've hit the limit, skip and let others go.
43
+ - Always release the lock.`;
44
+ }