agentxchain 0.6.0 → 0.7.1

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 CHANGED
@@ -14,60 +14,32 @@ Or run without installing:
14
14
  npx agentxchain init
15
15
  ```
16
16
 
17
- ## Quick start — VS Code / Cursor (recommended)
18
-
19
- No API keys or cloud connection needed. Uses native VS Code custom agents.
17
+ ## Quick start
20
18
 
21
19
  ```bash
22
20
  agentxchain init # create project with agents + hooks
23
21
  cd my-project/ && code . # open in VS Code / Cursor
24
- # Select an agent from Chat dropdown (auto-discovered from .github/agents/)
22
+ # Select an agent from the Chat dropdown (auto-discovered from .github/agents/)
25
23
  agentxchain release # release human lock to begin turns
26
24
  ```
27
25
 
28
- The `Stop` hook acts as referee: when an agent finishes, it hands off to the next agent automatically.
29
-
30
- ## Quick start — Cursor Cloud Agents
31
-
32
- ```bash
33
- agentxchain init
34
- cd my-project/
35
- echo "CURSOR_API_KEY=your_key" >> .env # cursor.com/settings -> Cloud Agents
36
- # Connect GitHub in Cursor Settings -> GitHub integration
37
- agentxchain start --ide cursor # launch cloud agents
38
- agentxchain watch # coordinate turns
39
- agentxchain release # begin turns (initial lock is human)
40
- ```
41
-
42
- > `CURSOR_API_KEY` is required for Cloud commands. Your Cursor account needs GitHub access to the target repository.
26
+ The `Stop` hook acts as referee: when an agent finishes, it determines the next agent and hands off automatically. No polling process needed.
43
27
 
44
28
  ## Commands
45
29
 
46
30
  | Command | What it does |
47
31
  |---------|-------------|
48
- | `init` | Create project folder with agents, protocol files, hooks, and templates |
32
+ | `init` | Create project folder with agents, hooks, protocol files, and templates |
49
33
  | `generate` | Regenerate VS Code agent files (`.agent.md`, hooks) from `agentxchain.json` |
50
- | `start` | Launch agents in Cursor Cloud, Claude Code, or VS Code |
51
- | `watch` | The referee for cloud mode — coordinates turns, enforces TTL, wakes agents |
52
- | `status` | Show lock, phase, agents, Cursor session info |
53
- | `claim` | Human takes control (pauses Cursor agents) |
34
+ | `start` | Show agent setup instructions for your IDE |
35
+ | `status` | Show lock, phase, agents |
36
+ | `claim` | Human takes control |
54
37
  | `release` | Hand lock back to agents |
55
- | `stop` | Terminate all running cloud agents |
56
- | `branch` | Show/set Cursor branch override (`cursor.ref`) |
38
+ | `stop` | Terminate running Claude Code agent sessions |
39
+ | `watch` | Fallback referee for non-IDE environments |
57
40
  | `config` | View/edit config, add/remove agents, change rules |
58
41
  | `update` | Self-update CLI from npm |
59
42
 
60
- ### Branch selection
61
-
62
- By default, Cursor launches use your current local git branch.
63
-
64
- ```bash
65
- agentxchain branch # show current/effective branch
66
- agentxchain branch develop # pin to a specific branch
67
- agentxchain branch --use-current # pin to whatever branch you're on now
68
- agentxchain branch --unset # remove pin; follow active git branch
69
- ```
70
-
71
43
  ### Additional flags
72
44
 
73
45
  ```bash
@@ -75,45 +47,42 @@ agentxchain watch --daemon # run watch in background
75
47
  agentxchain release --force # force-release non-human holder lock
76
48
  ```
77
49
 
78
- ## VS Code plugin
50
+ ## How it works
79
51
 
80
52
  `agentxchain init` generates native VS Code agent files:
81
53
 
82
- - `.github/agents/*.agent.md` — custom agents (auto-discovered by VS Code Chat)
54
+ - `.github/agents/*.agent.md` — custom agents (auto-discovered by VS Code / Cursor Chat)
83
55
  - `.github/hooks/agentxchain.json` — lifecycle hooks (Stop = referee, SessionStart = context injection)
84
56
  - `scripts/agentxchain-*.sh` — hook shell scripts
85
57
 
86
- VS Code extension (optional, for UI):
87
- - Status bar: lock holder, turn, phase (live-updated via file watcher)
88
- - Sidebar: agent dashboard with quick actions
89
- - Commands: Claim, Release, Status, Generate
90
-
91
- Install the extension:
92
- ```bash
93
- code --install-extension cli/vscode-extension/agentxchain-0.1.0.vsix
94
- ```
58
+ When an agent finishes its response, the Stop hook reads `lock.json`, determines the next agent, and hands off automatically.
95
59
 
96
60
  ## Key features
97
61
 
98
62
  - **Native VS Code agents** — `.agent.md` files, lifecycle hooks, handoffs
99
- - **Claim-based coordination** — no fixed turn order; agents self-organize
100
- - **Stop hook referee** — deterministic turn coordination via VS Code hooks
63
+ - **Works in any VS Code fork** — Cursor, VS Code, Windsurf, etc.
64
+ - **Stop hook referee** — deterministic turn coordination via lifecycle hooks
101
65
  - **User-defined teams** — any number of agents, any roles
102
- - **Cursor Cloud Agents** launch and manage via API (optional)
103
- - **Branch-safe launching** — defaults to active git branch
104
- - **Lock TTL** — stale locks auto-released after timeout
105
- - **Verify command** — agents must pass tests before releasing
66
+ - **No API keys or cloud required** everything runs locally
106
67
  - **Human-in-the-loop** — claim/release to intervene anytime
107
68
  - **Team templates** — SaaS MVP, Landing Page, Bug Squad, API Builder, Refactor Team
108
69
 
70
+ ## VS Code extension (optional)
71
+
72
+ For a richer UI experience, install the extension:
73
+
74
+ ```bash
75
+ code --install-extension cli/vscode-extension/agentxchain-0.1.0.vsix
76
+ ```
77
+
78
+ Adds: status bar (lock holder, turn, phase), sidebar dashboard, command palette integration.
79
+
109
80
  ## Publish updates (maintainers)
110
81
 
111
82
  ```bash
112
83
  cd cli
113
84
  bash scripts/publish-npm.sh # patch bump + publish
114
85
  bash scripts/publish-npm.sh minor # minor bump + publish
115
- bash scripts/publish-npm.sh 0.5.0 # explicit version + publish
116
- bash scripts/publish-npm.sh patch --dry-run
117
86
  ```
118
87
 
119
88
  If `NPM_TOKEN` exists in `agentXchain.dev/.env` (project root), the script uses it automatically.
@@ -1,5 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import { readFileSync } from 'fs';
4
+ import { join, dirname } from 'path';
5
+ import { fileURLToPath } from 'url';
3
6
  import { Command } from 'commander';
4
7
  import { initCommand } from '../src/commands/init.js';
5
8
  import { statusCommand } from '../src/commands/status.js';
@@ -9,15 +12,17 @@ import { configCommand } from '../src/commands/config.js';
9
12
  import { updateCommand } from '../src/commands/update.js';
10
13
  import { watchCommand } from '../src/commands/watch.js';
11
14
  import { claimCommand, releaseCommand } from '../src/commands/claim.js';
12
- import { branchCommand } from '../src/commands/branch.js';
13
15
  import { generateCommand } from '../src/commands/generate.js';
14
16
 
17
+ const __dirname = dirname(fileURLToPath(import.meta.url));
18
+ const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
19
+
15
20
  const program = new Command();
16
21
 
17
22
  program
18
23
  .name('agentxchain')
19
24
  .description('Multi-agent coordination in your IDE')
20
- .version('0.4.1');
25
+ .version(pkg.version);
21
26
 
22
27
  program
23
28
  .command('init')
@@ -34,7 +39,7 @@ program
34
39
  program
35
40
  .command('start')
36
41
  .description('Launch agents in your IDE')
37
- .option('--ide <ide>', 'Target IDE: cursor, vscode, claude-code', 'cursor')
42
+ .option('--ide <ide>', 'Target IDE: vscode, claude-code', 'vscode')
38
43
  .option('--agent <id>', 'Launch a specific agent only')
39
44
  .option('--dry-run', 'Print what would be launched without doing it')
40
45
  .action(startCommand);
@@ -53,13 +58,6 @@ program
53
58
  .option('-j, --json', 'Output config as JSON')
54
59
  .action(configCommand);
55
60
 
56
- program
57
- .command('branch [name]')
58
- .description('Show or set the Cursor branch used for agent launches')
59
- .option('--use-current', 'Set override to the current local git branch')
60
- .option('--unset', 'Remove override and follow active git branch automatically')
61
- .action(branchCommand);
62
-
63
61
  program
64
62
  .command('generate')
65
63
  .description('Regenerate VS Code agent files (.agent.md, hooks) from agentxchain.json')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "CLI for AgentXchain — multi-agent coordination in your IDE",
5
5
  "type": "module",
6
6
  "bin": {
@@ -2,8 +2,6 @@ import { writeFileSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import chalk from 'chalk';
4
4
  import { loadConfig, loadLock, LOCK_FILE } from '../lib/config.js';
5
- import { stopAgent, sendFollowup, loadSession } from '../adapters/cursor.js';
6
- import { getCursorApiKey, printCursorApiKeyRequired } from '../lib/cursor-api-key.js';
7
5
 
8
6
  export async function claimCommand(opts) {
9
7
  const result = loadConfig();
@@ -13,18 +11,6 @@ export async function claimCommand(opts) {
13
11
  const lock = loadLock(root);
14
12
  if (!lock) { console.log(chalk.red(' lock.json not found.')); process.exit(1); }
15
13
 
16
- const apiKey = getCursorApiKey(root);
17
- const session = loadSession(root);
18
- const hasCursorSession = session?.ide === 'cursor' && session?.launched?.length > 0;
19
- const hasCursor = hasCursorSession && apiKey;
20
-
21
- if (hasCursorSession && !apiKey) {
22
- printCursorApiKeyRequired('`agentxchain claim` with Cursor agents');
23
- console.log(chalk.dim(' Claim aborted so agents are not left running unexpectedly.'));
24
- console.log('');
25
- process.exit(1);
26
- }
27
-
28
14
  if (lock.holder === 'human') {
29
15
  console.log('');
30
16
  console.log(chalk.yellow(' You already hold the lock.'));
@@ -42,19 +28,6 @@ export async function claimCommand(opts) {
42
28
  return;
43
29
  }
44
30
 
45
- // Pause all Cursor agents when human claims
46
- if (hasCursor && session.launched.length > 0) {
47
- console.log(chalk.dim(' Pausing Cursor agents...'));
48
- for (const agent of session.launched) {
49
- try {
50
- await stopAgent(apiKey, agent.cloudId);
51
- console.log(chalk.dim(` Paused ${agent.id}`));
52
- } catch {
53
- console.log(chalk.dim(` Could not pause ${agent.id}`));
54
- }
55
- }
56
- }
57
-
58
31
  const lockPath = join(root, LOCK_FILE);
59
32
  const newLock = {
60
33
  holder: 'human',
@@ -66,7 +39,6 @@ export async function claimCommand(opts) {
66
39
 
67
40
  console.log('');
68
41
  console.log(chalk.green(` ✓ Lock claimed by ${chalk.bold('human')} (turn ${lock.turn_number})`));
69
- if (hasCursor) console.log(chalk.dim(' All Cursor agents paused.'));
70
42
  console.log(` ${chalk.dim('Do your work, then:')} ${chalk.bold('agentxchain release')}`);
71
43
  console.log('');
72
44
  }
@@ -88,14 +60,12 @@ export async function releaseCommand(opts) {
88
60
  const name = config.agents[lock.holder]?.name || lock.holder;
89
61
  console.log('');
90
62
  console.log(chalk.red(` Lock is held by ${chalk.bold(lock.holder)} (${name}), not human.`));
91
- console.log(chalk.dim(' Refusing to release another holder without explicit override.'));
92
- console.log(chalk.dim(' Use `agentxchain release --force` if you really want to break this lock.'));
63
+ console.log(chalk.dim(' Use `agentxchain release --force` to break this lock.'));
93
64
  console.log('');
94
65
  process.exit(1);
95
66
  }
96
67
 
97
68
  const who = lock.holder;
98
- const priorLastReleasedBy = lock.last_released_by;
99
69
  const lockPath = join(root, LOCK_FILE);
100
70
  const newLock = {
101
71
  holder: null,
@@ -107,53 +77,6 @@ export async function releaseCommand(opts) {
107
77
 
108
78
  console.log('');
109
79
  console.log(chalk.green(` ✓ Lock released by ${chalk.bold(who)} (turn ${newLock.turn_number})`));
110
-
111
- // If releasing from human and Cursor session exists, wake the next agent
112
- if (who === 'human') {
113
- const apiKey = getCursorApiKey(root);
114
- const session = loadSession(root);
115
- const hasCursorSession = session?.ide === 'cursor' && session?.launched?.length > 0;
116
-
117
- if (hasCursorSession && !apiKey) {
118
- printCursorApiKeyRequired('`agentxchain release` with Cursor agents');
119
- console.log(chalk.dim(' Lock released, but wake-up followup was skipped due to missing key.'));
120
- console.log(chalk.dim(' Start `agentxchain watch` after setting the key.'));
121
- console.log('');
122
- return;
123
- }
124
-
125
- if (session?.ide === 'cursor' && apiKey) {
126
- const next = pickNextAgent(priorLastReleasedBy, config);
127
- if (!next) {
128
- console.log(chalk.dim(' No agents configured to wake.'));
129
- console.log('');
130
- return;
131
- }
132
- const cloudAgent = session.launched.find(a => a.id === next);
133
-
134
- if (cloudAgent) {
135
- try {
136
- const name = config.agents[next]?.name || next;
137
- await sendFollowup(apiKey, cloudAgent.cloudId,
138
- `Human released the lock. It's your turn. Read lock.json, claim it, and do your work as ${name}.`
139
- );
140
- console.log(chalk.cyan(` Woke ${chalk.bold(next)} via Cursor followup.`));
141
- } catch (err) {
142
- console.log(chalk.dim(` Could not wake ${next}: ${err.message}`));
143
- }
144
- }
145
- console.log(chalk.dim(' The watch process will coordinate from here.'));
146
- }
147
- }
148
-
80
+ console.log(chalk.dim(' The Stop hook will coordinate the next agent turn in VS Code.'));
149
81
  console.log('');
150
82
  }
151
-
152
- function pickNextAgent(lastReleasedBy, config) {
153
- const agentIds = Object.keys(config.agents || {});
154
- if (agentIds.length === 0) return null;
155
- if (!lastReleasedBy || !agentIds.includes(lastReleasedBy)) return agentIds[0];
156
-
157
- const idx = agentIds.indexOf(lastReleasedBy);
158
- return agentIds[(idx + 1) % agentIds.length];
159
- }
@@ -206,15 +206,8 @@ export async function initCommand(opts) {
206
206
  writeFileSync(join(dir, 'history.jsonl'), '');
207
207
  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`);
208
208
  writeFileSync(join(dir, 'HUMAN_TASKS.md'), '# Human Tasks\n\n(Agents append tasks here when they need human action.)\n');
209
- writeFileSync(join(dir, '.env.example'), 'CURSOR_API_KEY=\n');
210
- if (!existsSync(join(dir, '.env'))) {
211
- writeFileSync(
212
- join(dir, '.env'),
213
- '# Required for Cursor commands: start/watch/stop/claim/release\nCURSOR_API_KEY=\n'
214
- );
215
- }
216
209
  const gitignorePath = join(dir, '.gitignore');
217
- const requiredIgnores = ['.env', '.agentxchain-session.json', '.agentxchain-trigger.json'];
210
+ const requiredIgnores = ['.env', '.agentxchain-trigger.json'];
218
211
  if (!existsSync(gitignorePath)) {
219
212
  writeFileSync(gitignorePath, requiredIgnores.join('\n') + '\n');
220
213
  } else {
@@ -271,9 +264,8 @@ export async function initCommand(opts) {
271
264
  console.log('');
272
265
  console.log(` ${chalk.cyan('Next:')}`);
273
266
  console.log(` ${chalk.bold(`cd ${folderName}`)}`);
274
- console.log(` ${chalk.bold('edit .env')} ${chalk.dim('# set CURSOR_API_KEY (required for Cursor mode)')}`);
275
- console.log(` ${chalk.bold('agentxchain start')} ${chalk.dim('# launch agents in Cursor')}`);
276
- console.log(` ${chalk.bold('agentxchain watch')} ${chalk.dim('# start the referee')}`);
277
- console.log(` ${chalk.bold('agentxchain release')} ${chalk.dim('# begin automation (initial lock is human)')}`);
267
+ console.log(` ${chalk.bold('code .')} ${chalk.dim('# open in VS Code / Cursor')}`);
268
+ console.log(` ${chalk.dim('Select an agent from the Chat dropdown (auto-discovered from .github/agents/)')}`);
269
+ console.log(` ${chalk.bold('agentxchain release')} ${chalk.dim('# release human lock to begin turns')}`);
278
270
  console.log('');
279
271
  }
@@ -1,7 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import { loadConfig } from '../lib/config.js';
3
3
  import { generateSeedPrompt } from '../lib/seed-prompt.js';
4
- import { getCursorApiKey, printCursorApiKeyRequired } from '../lib/cursor-api-key.js';
5
4
 
6
5
  export async function startCommand(opts) {
7
6
  const result = loadConfig();
@@ -28,12 +27,11 @@ export async function startCommand(opts) {
28
27
  }
29
28
 
30
29
  console.log('');
31
- console.log(chalk.bold(` Launching ${agentCount} agents via ${ide}`));
32
- console.log(chalk.dim(` Project: ${config.project}`));
30
+ console.log(chalk.bold(` ${agentCount} agents configured for ${config.project}`));
33
31
  console.log('');
34
32
 
35
33
  if (opts.dryRun) {
36
- console.log(chalk.yellow(' DRY RUN — showing what would be launched:'));
34
+ console.log(chalk.yellow(' DRY RUN — showing agents:'));
37
35
  console.log('');
38
36
  for (const [id, agent] of Object.entries(config.agents)) {
39
37
  if (opts.agent && opts.agent !== id) continue;
@@ -45,14 +43,23 @@ export async function startCommand(opts) {
45
43
  }
46
44
 
47
45
  switch (ide) {
48
- case 'cursor': {
49
- const apiKey = getCursorApiKey(root);
50
- if (!apiKey) {
51
- printCursorApiKeyRequired('`agentxchain start --ide cursor`');
52
- process.exit(1);
46
+ case 'vscode': {
47
+ console.log(chalk.green(' Agents are set up as VS Code custom agents.'));
48
+ console.log('');
49
+ console.log(chalk.dim(' Your agents in .github/agents/:'));
50
+ for (const [id, agent] of Object.entries(config.agents)) {
51
+ console.log(` ${chalk.cyan(id)}.agent.md — ${agent.name}`);
53
52
  }
54
- const { launchCursorAgents } = await import('../adapters/cursor.js');
55
- await launchCursorAgents(config, root, opts);
53
+ console.log('');
54
+ console.log(` ${chalk.bold('How to use:')}`);
55
+ console.log(` 1. Open this project in VS Code / Cursor`);
56
+ console.log(` 2. Open Chat (${chalk.bold('Cmd+L')})`);
57
+ console.log(` 3. Select an agent from the Chat dropdown`);
58
+ console.log(` 4. Run ${chalk.bold('agentxchain release')} to release the human lock`);
59
+ console.log(` 5. Agents coordinate via hooks — Stop hook hands off automatically`);
60
+ console.log('');
61
+ console.log(chalk.dim(' If agents don\'t appear, run: agentxchain generate'));
62
+ console.log('');
56
63
  break;
57
64
  }
58
65
  case 'claude-code': {
@@ -60,31 +67,8 @@ export async function startCommand(opts) {
60
67
  await launchClaudeCodeAgents(config, root, opts);
61
68
  break;
62
69
  }
63
- case 'vscode': {
64
- console.log(chalk.yellow(' VS Code adapter coming soon.'));
65
- console.log(chalk.dim(' For now, use the seed prompts below in VS Code chat panels.'));
66
- console.log('');
67
- printPrompts(config, opts);
68
- break;
69
- }
70
70
  default:
71
- console.log(chalk.red(` Unknown IDE: ${ide}. Supported: cursor, vscode, claude-code`));
71
+ console.log(chalk.red(` Unknown IDE: ${ide}. Supported: vscode, claude-code`));
72
72
  process.exit(1);
73
73
  }
74
74
  }
75
-
76
- function printPrompts(config, opts) {
77
- const agents = opts.agent
78
- ? { [opts.agent]: config.agents[opts.agent] }
79
- : config.agents;
80
-
81
- for (const [id, agent] of Object.entries(agents)) {
82
- const prompt = generateSeedPrompt(id, agent, config);
83
- console.log(chalk.dim(' ' + '─'.repeat(50)));
84
- console.log(chalk.cyan(` Agent: ${chalk.bold(id)} (${agent.name})`));
85
- console.log(chalk.dim(' ' + '─'.repeat(50)));
86
- console.log('');
87
- console.log(prompt);
88
- console.log('');
89
- }
90
- }
@@ -1,7 +1,5 @@
1
1
  import chalk from 'chalk';
2
2
  import { loadConfig, loadLock, loadState } from '../lib/config.js';
3
- import { getAgentStatus, loadSession } from '../adapters/cursor.js';
4
- import { getCursorApiKey } from '../lib/cursor-api-key.js';
5
3
 
6
4
  export async function statusCommand(opts) {
7
5
  const result = loadConfig();
@@ -31,7 +29,6 @@ export async function statusCommand(opts) {
31
29
  }
32
30
  console.log('');
33
31
 
34
- // Lock
35
32
  if (lock) {
36
33
  if (lock.holder === 'human') {
37
34
  console.log(` ${chalk.dim('Lock:')} ${chalk.magenta('HUMAN')} — you hold the lock`);
@@ -51,45 +48,13 @@ export async function statusCommand(opts) {
51
48
  }
52
49
  console.log('');
53
50
 
54
- // Cursor session info
55
- const session = loadSession(root);
56
- const apiKey = getCursorApiKey(root);
57
- const hasCursor = session?.ide === 'cursor' && session?.launched?.length > 0;
58
-
59
- if (hasCursor) {
60
- console.log(` ${chalk.dim('Cursor:')} ${chalk.cyan('Active session')} (${session.launched.length} agents)`);
61
- console.log(` ${chalk.dim('Started:')} ${session.started_at}`);
62
- if (session.repo) console.log(` ${chalk.dim('Repo:')} ${session.repo}`);
63
- if (!apiKey) {
64
- console.log(` ${chalk.dim('API key:')} ${chalk.red('Missing')} (set CURSOR_API_KEY in .env for live statuses)`);
65
- }
66
- console.log('');
67
- }
68
-
69
- // Agents
70
51
  console.log(` ${chalk.dim('Agents:')} ${Object.keys(config.agents).length}`);
71
52
 
72
53
  for (const [id, agent] of Object.entries(config.agents)) {
73
54
  const isHolder = lock?.holder === id;
74
55
  const marker = isHolder ? chalk.yellow('●') : chalk.dim('○');
75
56
  const label = isHolder ? chalk.bold(id) : id;
76
-
77
- let cursorStatus = '';
78
- if (hasCursor && apiKey) {
79
- const cloudAgent = session.launched.find(a => a.id === id);
80
- if (cloudAgent) {
81
- try {
82
- const statusData = await getAgentStatus(apiKey, cloudAgent.cloudId);
83
- if (statusData?.status) {
84
- cursorStatus = ` ${formatCursorStatus(statusData.status)}`;
85
- }
86
- } catch {
87
- cursorStatus = chalk.dim(' [API error]');
88
- }
89
- }
90
- }
91
-
92
- console.log(` ${marker} ${label} — ${agent.name}${cursorStatus}`);
57
+ console.log(` ${marker} ${label} — ${agent.name}`);
93
58
  }
94
59
 
95
60
  if (lock?.holder === 'human') {
@@ -104,17 +69,6 @@ function formatPhase(phase) {
104
69
  return (colors[phase] || chalk.white)(phase);
105
70
  }
106
71
 
107
- function formatCursorStatus(status) {
108
- const map = {
109
- CREATING: chalk.dim('[creating]'),
110
- RUNNING: chalk.cyan('[running]'),
111
- FINISHED: chalk.green('[finished]'),
112
- STOPPED: chalk.yellow('[stopped]'),
113
- ERRORED: chalk.red('[errored]'),
114
- };
115
- return map[status] || chalk.dim(`[${status}]`);
116
- }
117
-
118
72
  function timeSince(iso) {
119
73
  const ms = Date.now() - new Date(iso).getTime();
120
74
  const sec = Math.floor(ms / 1000);
@@ -2,8 +2,6 @@ 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, loadSession } from '../adapters/cursor.js';
6
- import { getCursorApiKey, printCursorApiKeyRequired } from '../lib/cursor-api-key.js';
7
5
 
8
6
  const SESSION_FILE = '.agentxchain-session.json';
9
7
 
@@ -12,44 +10,28 @@ export async function stopCommand() {
12
10
  if (!result) { console.log(chalk.red(' No agentxchain.json found.')); process.exit(1); }
13
11
 
14
12
  const { root } = result;
15
- const session = loadSession(root);
13
+ const sessionPath = join(root, SESSION_FILE);
16
14
 
17
- if (!session) {
15
+ if (!existsSync(sessionPath)) {
18
16
  console.log(chalk.yellow(' No active session found.'));
19
- console.log(chalk.dim(' If agents are running, stop them manually.'));
17
+ console.log(chalk.dim(' If agents are running in VS Code / Cursor, close their chat sessions manually.'));
18
+ return;
19
+ }
20
+
21
+ let session;
22
+ try {
23
+ session = JSON.parse(readFileSync(sessionPath, 'utf8'));
24
+ } catch {
25
+ console.log(chalk.yellow(' Could not read session file.'));
20
26
  return;
21
27
  }
22
28
 
23
29
  console.log('');
24
- console.log(chalk.bold(` Stopping ${session.launched.length} agents (${session.ide})`));
30
+ console.log(chalk.bold(` Stopping ${session.launched?.length || 0} agents (${session.ide || 'unknown'})`));
25
31
  console.log('');
26
- let allStopped = true;
27
32
 
28
- if (session.ide === 'cursor') {
29
- const apiKey = getCursorApiKey(root);
30
- if (!apiKey) {
31
- printCursorApiKeyRequired('`agentxchain stop` for Cursor agents');
32
- console.log(chalk.dim(' Session file was kept so you can retry after setting the key.'));
33
- console.log('');
34
- return;
35
- }
36
-
37
- for (const agent of session.launched) {
38
- try {
39
- const deleted = await deleteAgent(apiKey, agent.cloudId);
40
- if (deleted) {
41
- console.log(chalk.green(` ✓ Deleted ${chalk.bold(agent.id)} (${agent.cloudId})`));
42
- } else {
43
- console.log(chalk.yellow(` ⚠ Could not delete ${agent.id} — may already be gone`));
44
- allStopped = false;
45
- }
46
- } catch (err) {
47
- console.log(chalk.red(` ✗ ${agent.id}: ${err.message}`));
48
- allStopped = false;
49
- }
50
- }
51
- } else if (session.ide === 'claude-code') {
52
- for (const agent of session.launched) {
33
+ if (session.ide === 'claude-code') {
34
+ for (const agent of (session.launched || [])) {
53
35
  if (agent.pid) {
54
36
  try {
55
37
  process.kill(agent.pid, 'SIGTERM');
@@ -59,22 +41,17 @@ export async function stopCommand() {
59
41
  console.log(chalk.dim(` ${agent.id} (PID: ${agent.pid}) — already stopped`));
60
42
  } else {
61
43
  console.log(chalk.red(` ✗ ${agent.id}: ${err.message}`));
62
- allStopped = false;
63
44
  }
64
45
  }
65
46
  }
66
47
  }
48
+ } else {
49
+ console.log(chalk.dim(' For VS Code / Cursor agents, close the chat sessions manually.'));
67
50
  }
68
51
 
52
+ unlinkSync(sessionPath);
69
53
  console.log('');
70
- const sessionPath = join(root, SESSION_FILE);
71
- if (allStopped) {
72
- if (existsSync(sessionPath)) unlinkSync(sessionPath);
73
- console.log(chalk.dim(' Session file removed.'));
74
- console.log(chalk.green(' All agents stopped.'));
75
- } else {
76
- console.log(chalk.yellow(' Some agents could not be stopped.'));
77
- console.log(chalk.dim(' Session file was kept so you can retry `agentxchain stop`.'));
78
- }
54
+ console.log(chalk.dim(' Session file removed.'));
55
+ console.log(chalk.green(' Done.'));
79
56
  console.log('');
80
57
  }
@@ -5,8 +5,6 @@ import { fileURLToPath } from 'url';
5
5
  import chalk from 'chalk';
6
6
  import { loadConfig, loadLock, LOCK_FILE } from '../lib/config.js';
7
7
  import { notifyHuman as sendNotification } from '../lib/notify.js';
8
- import { sendFollowup, getAgentStatus, stopAgent, loadSession } from '../adapters/cursor.js';
9
- import { getCursorApiKey, printCursorApiKeyRequired } from '../lib/cursor-api-key.js';
10
8
 
11
9
  export async function watchCommand(opts) {
12
10
  if (opts.daemon && process.env.AGENTXCHAIN_WATCH_DAEMON !== '1') {
@@ -24,17 +22,9 @@ export async function watchCommand(opts) {
24
22
  const interval = config.rules?.watch_interval_ms || 5000;
25
23
  const ttlMinutes = config.rules?.ttl_minutes || 10;
26
24
  const agentIds = Object.keys(config.agents);
25
+
27
26
  if (agentIds.length === 0) {
28
27
  console.log(chalk.red(' No agents configured in agentxchain.json.'));
29
- console.log(chalk.dim(' Add an agent with: agentxchain config --add-agent'));
30
- process.exit(1);
31
- }
32
- const apiKey = getCursorApiKey(root);
33
- const session = loadSession(root);
34
- const hasCursorSession = session?.ide === 'cursor' && session?.launched?.length > 0;
35
-
36
- if (hasCursorSession && !apiKey) {
37
- printCursorApiKeyRequired('`agentxchain watch` with a Cursor session');
38
28
  process.exit(1);
39
29
  }
40
30
 
@@ -43,13 +33,11 @@ export async function watchCommand(opts) {
43
33
  console.log(chalk.dim(` Project: ${config.project}`));
44
34
  console.log(chalk.dim(` Agents: ${agentIds.join(', ')}`));
45
35
  console.log(chalk.dim(` Poll: ${interval}ms | TTL: ${ttlMinutes}min`));
46
- if (hasCursorSession) {
47
- console.log(chalk.cyan(` Mode: Cursor Cloud Agents (${session.launched.length} agents)`));
48
- } else {
49
- console.log(chalk.dim(` Mode: Local trigger file (no Cursor session found)`));
50
- }
36
+ console.log(chalk.dim(` Mode: Local file watcher + trigger file`));
51
37
  console.log('');
52
38
  console.log(chalk.cyan(' Watching lock.json... (Ctrl+C to stop)'));
39
+ console.log(chalk.dim(' Note: In VS Code/Cursor, the Stop hook coordinates turns automatically.'));
40
+ console.log(chalk.dim(' This watch process is a fallback for non-IDE environments.'));
53
41
  console.log('');
54
42
 
55
43
  let lastState = null;
@@ -61,7 +49,6 @@ export async function watchCommand(opts) {
61
49
 
62
50
  const stateKey = `${lock.holder}:${lock.turn_number}`;
63
51
 
64
- // TTL check — stale lock
65
52
  if (lock.holder && lock.holder !== 'human' && lock.claimed_at) {
66
53
  const elapsed = Date.now() - new Date(lock.claimed_at).getTime();
67
54
  const ttlMs = ttlMinutes * 60 * 1000;
@@ -70,22 +57,12 @@ export async function watchCommand(opts) {
70
57
  const staleAgent = lock.holder;
71
58
  const minutes = Math.round(elapsed / 60000);
72
59
  log('ttl', `Lock held by ${staleAgent} for ${minutes}min. Force-releasing.`);
73
-
74
- if (hasCursorSession && apiKey) {
75
- const cloudAgent = session.launched.find(a => a.id === staleAgent);
76
- if (cloudAgent) {
77
- await stopAgent(apiKey, cloudAgent.cloudId);
78
- log('ttl', `Stopped Cursor agent ${staleAgent}`);
79
- }
80
- }
81
-
82
60
  forceRelease(root, lock, staleAgent, config);
83
61
  lastState = null;
84
62
  return;
85
63
  }
86
64
  }
87
65
 
88
- // Human holder
89
66
  if (lock.holder === 'human') {
90
67
  if (stateKey !== lastState) {
91
68
  log('human', 'Lock held by HUMAN. Run `agentxchain release` when done.');
@@ -95,45 +72,20 @@ export async function watchCommand(opts) {
95
72
  return;
96
73
  }
97
74
 
98
- // Agent is working
99
75
  if (lock.holder) {
100
76
  if (stateKey !== lastState) {
101
77
  const name = config.agents[lock.holder]?.name || lock.holder;
102
78
  log('claimed', `${lock.holder} (${name}) working... (turn ${lock.turn_number})`);
103
79
  lastState = stateKey;
104
-
105
- // Check Cursor agent status if available
106
- if (hasCursorSession && apiKey) {
107
- const cloudAgent = session.launched.find(a => a.id === lock.holder);
108
- if (cloudAgent) {
109
- const status = await getAgentStatus(apiKey, cloudAgent.cloudId);
110
- if (status?.status === 'FINISHED') {
111
- log('warn', `${lock.holder} Cursor agent is FINISHED but lock not released. May need TTL.`);
112
- }
113
- }
114
- }
115
80
  }
116
81
  return;
117
82
  }
118
83
 
119
- // Lock is FREE — wake the next agent
120
84
  if (stateKey !== lastState) {
121
85
  const next = pickNextAgent(lock, config);
122
- log('free', `Lock free (released by ${lock.last_released_by || 'none'}). Waking ${chalk.bold(next)}.`);
123
- let wakeSucceeded = false;
124
-
125
- if (hasCursorSession && apiKey) {
126
- wakeSucceeded = await wakeCursorAgent(apiKey, session, next, lock, config, root);
127
- } else {
128
- writeTrigger(root, next, lock, config);
129
- wakeSucceeded = true;
130
- }
131
-
132
- if (wakeSucceeded) {
133
- lastState = stateKey;
134
- } else {
135
- log('warn', `Wake for ${next} failed. Will retry next poll.`);
136
- }
86
+ log('free', `Lock free (released by ${lock.last_released_by || 'none'}). Next: ${chalk.bold(next)}.`);
87
+ writeTrigger(root, next, lock, config);
88
+ lastState = stateKey;
137
89
  }
138
90
  } catch (err) {
139
91
  log('error', err.message);
@@ -151,40 +103,6 @@ export async function watchCommand(opts) {
151
103
  });
152
104
  }
153
105
 
154
- async function wakeCursorAgent(apiKey, session, agentId, lock, config, root) {
155
- const cloudAgent = session.launched.find(a => a.id === agentId);
156
- if (!cloudAgent) {
157
- log('warn', `No Cursor cloud agent found for "${agentId}". Using trigger file.`);
158
- writeTrigger(root, agentId, lock, config);
159
- return true;
160
- }
161
-
162
- const name = config.agents[agentId]?.name || agentId;
163
- const wakeMessage = `The lock is free. It's your turn.
164
-
165
- READ lock.json — if holder is null, CLAIM it by writing holder="${agentId}" and claimed_at=now.
166
- Then do your work per your mandate:
167
- - Name: ${name}
168
- - Mandate: ${config.agents[agentId]?.mandate || '(see agentxchain.json)'}
169
-
170
- When done:
171
- 1. Update state.md with current project state
172
- 2. Append one line to history.jsonl
173
- ${config.rules?.verify_command ? `3. Run verify: ${config.rules.verify_command} — fix if it fails` : ''}
174
- ${config.rules?.verify_command ? '4' : '3'}. RELEASE lock.json: holder=null, last_released_by="${agentId}", turn_number=${lock.turn_number + 1}, claimed_at=null
175
-
176
- This must be the last thing you write. The watch process will wake the next agent.`;
177
-
178
- try {
179
- await sendFollowup(apiKey, cloudAgent.cloudId, wakeMessage);
180
- log('wake', `Sent followup to ${chalk.bold(agentId)} (${cloudAgent.cloudId})`);
181
- return true;
182
- } catch (err) {
183
- log('error', `Failed to wake ${agentId}: ${err.message}`);
184
- return false;
185
- }
186
- }
187
-
188
106
  function pickNextAgent(lock, config) {
189
107
  const agentIds = Object.keys(config.agents);
190
108
  if (agentIds.length === 0) return null;
@@ -229,7 +147,6 @@ function log(type, msg) {
229
147
  const tags = {
230
148
  free: chalk.green('FREE '),
231
149
  claimed: chalk.yellow('WORK '),
232
- wake: chalk.cyan('WAKE '),
233
150
  ttl: chalk.red(' TTL '),
234
151
  human: chalk.magenta('HUMAN'),
235
152
  warn: chalk.yellow('WARN '),
@@ -1,182 +0,0 @@
1
- import { writeFileSync, readFileSync, existsSync } from 'fs';
2
- import { join } from 'path';
3
- import chalk from 'chalk';
4
- import { generateSeedPrompt } from '../lib/seed-prompt.js';
5
- import { getRepoUrl, getCurrentBranch } from '../lib/repo.js';
6
- import { getCursorApiKey, printCursorApiKeyRequired } from '../lib/cursor-api-key.js';
7
-
8
- const API_BASE = 'https://api.cursor.com/v0';
9
-
10
- function authHeaders(apiKey) {
11
- return {
12
- 'Authorization': `Basic ${Buffer.from(apiKey + ':').toString('base64')}`,
13
- 'Content-Type': 'application/json'
14
- };
15
- }
16
-
17
- // --- Public API ---
18
-
19
- export async function launchCursorAgents(config, root, opts) {
20
- const apiKey = getCursorApiKey(root);
21
-
22
- if (!apiKey) {
23
- printCursorApiKeyRequired('`agentxchain start --ide cursor`');
24
- return [];
25
- }
26
-
27
- const repoUrl = await getRepoUrl(root);
28
- if (!repoUrl) {
29
- console.log(chalk.red(' Could not detect GitHub repo URL.'));
30
- console.log(chalk.dim(' Make sure this project is a git repo with a GitHub remote.'));
31
- console.log(chalk.dim(' Or set source.repository manually in agentxchain.json.'));
32
- return [];
33
- }
34
-
35
- const model = config.cursor?.model || 'default';
36
- const ref = config.cursor?.ref || getCurrentBranch(root) || 'main';
37
- console.log(chalk.dim(` Cursor source: ${repoUrl} @ ${ref}`));
38
- const agents = filterAgents(config, opts.agent);
39
- const launched = [];
40
- let branchErrorCount = 0;
41
-
42
- for (const [id, agent] of Object.entries(agents)) {
43
- const prompt = generateSeedPrompt(id, agent, config);
44
-
45
- try {
46
- const body = {
47
- prompt: { text: prompt },
48
- source: { repository: repoUrl, ref },
49
- target: { autoCreatePr: false }
50
- };
51
- if (model !== 'default') body.model = model;
52
-
53
- const res = await fetch(`${API_BASE}/agents`, {
54
- method: 'POST',
55
- headers: authHeaders(apiKey),
56
- body: JSON.stringify(body)
57
- });
58
-
59
- if (!res.ok) {
60
- const errBody = await res.text();
61
- console.log(chalk.red(` ✗ ${id}: ${res.status} ${errBody}`));
62
- if (errBody.includes('Failed to verify existence of branch')) {
63
- branchErrorCount += 1;
64
- }
65
- continue;
66
- }
67
-
68
- const data = await res.json();
69
- launched.push({
70
- id,
71
- name: agent.name,
72
- cloudId: data.id,
73
- status: data.status || 'CREATING',
74
- url: data.target?.url || null
75
- });
76
-
77
- const urlStr = data.target?.url ? chalk.dim(` → ${data.target.url}`) : '';
78
- console.log(chalk.green(` ✓ ${chalk.bold(id)} (${agent.name}) — ${data.id}${urlStr}`));
79
- } catch (err) {
80
- console.log(chalk.red(` ✗ ${id}: ${err.message}`));
81
- }
82
- }
83
-
84
- if (launched.length > 0) {
85
- saveSession(root, launched, repoUrl, ref);
86
- }
87
-
88
- if (launched.length === 0 && branchErrorCount > 0) {
89
- console.log('');
90
- console.log(chalk.yellow(' Launch failed because the branch ref is invalid for this repository.'));
91
- console.log(chalk.dim(' Fix by setting the branch explicitly in agentxchain.json:'));
92
- console.log(chalk.bold(' "cursor": { "ref": "your-default-branch" }'));
93
- console.log(chalk.dim(' Or switch to the target branch locally, then re-run start.'));
94
- console.log(chalk.dim(' If the branch exists on GitHub, verify your Cursor account has GitHub access'));
95
- console.log(chalk.dim(' to this repository (Cursor Settings -> GitHub integration).'));
96
- console.log('');
97
- }
98
-
99
- return launched;
100
- }
101
-
102
- export async function sendFollowup(apiKey, cloudId, message) {
103
- const res = await fetch(`${API_BASE}/agents/${cloudId}/followup`, {
104
- method: 'POST',
105
- headers: authHeaders(apiKey),
106
- body: JSON.stringify({ prompt: { text: message } })
107
- });
108
- if (!res.ok) {
109
- const body = await res.text();
110
- throw new Error(`Followup failed (${res.status}): ${body}`);
111
- }
112
- return await res.json();
113
- }
114
-
115
- export async function getAgentStatus(apiKey, cloudId) {
116
- const res = await fetch(`${API_BASE}/agents/${cloudId}`, {
117
- method: 'GET',
118
- headers: authHeaders(apiKey)
119
- });
120
- if (!res.ok) return null;
121
- return await res.json();
122
- }
123
-
124
- export async function getAgentConversation(apiKey, cloudId) {
125
- const res = await fetch(`${API_BASE}/agents/${cloudId}/conversation`, {
126
- method: 'GET',
127
- headers: authHeaders(apiKey)
128
- });
129
- if (!res.ok) return null;
130
- return await res.json();
131
- }
132
-
133
- export async function stopAgent(apiKey, cloudId) {
134
- const res = await fetch(`${API_BASE}/agents/${cloudId}/stop`, {
135
- method: 'POST',
136
- headers: authHeaders(apiKey)
137
- });
138
- return res.ok;
139
- }
140
-
141
- export async function deleteAgent(apiKey, cloudId) {
142
- const res = await fetch(`${API_BASE}/agents/${cloudId}`, {
143
- method: 'DELETE',
144
- headers: authHeaders(apiKey)
145
- });
146
- return res.ok;
147
- }
148
-
149
- export function loadSession(root) {
150
- const sessionPath = join(root, '.agentxchain-session.json');
151
- if (!existsSync(sessionPath)) return null;
152
- return JSON.parse(readFileSync(sessionPath, 'utf8'));
153
- }
154
-
155
- // --- Internal ---
156
-
157
- function saveSession(root, launched, repoUrl, ref) {
158
- const session = {
159
- launched,
160
- started_at: new Date().toISOString(),
161
- ide: 'cursor',
162
- repo: repoUrl,
163
- ref
164
- };
165
- const sessionPath = join(root, '.agentxchain-session.json');
166
- writeFileSync(sessionPath, JSON.stringify(session, null, 2) + '\n');
167
- console.log(chalk.dim(` Session saved to .agentxchain-session.json`));
168
- }
169
-
170
- function filterAgents(config, specificId) {
171
- if (specificId) {
172
- if (!config.agents[specificId]) {
173
- console.log(chalk.red(` Agent "${specificId}" not found in agentxchain.json`));
174
- process.exit(1);
175
- }
176
- return { [specificId]: config.agents[specificId] };
177
- }
178
- return config.agents;
179
- }
180
-
181
- // No prompt fallback here by design.
182
- // Cursor mode is strict: an API key is required.
@@ -1,61 +0,0 @@
1
- import { existsSync, readFileSync } from 'fs';
2
- import { join } from 'path';
3
- import chalk from 'chalk';
4
-
5
- function parseEnvFile(raw) {
6
- const out = {};
7
- const lines = raw.split(/\r?\n/);
8
-
9
- for (const line of lines) {
10
- const trimmed = line.trim();
11
- if (!trimmed || trimmed.startsWith('#')) continue;
12
-
13
- const eq = trimmed.indexOf('=');
14
- if (eq === -1) continue;
15
-
16
- const key = trimmed.slice(0, eq).trim();
17
- let value = trimmed.slice(eq + 1).trim();
18
-
19
- if (
20
- (value.startsWith('"') && value.endsWith('"')) ||
21
- (value.startsWith("'") && value.endsWith("'"))
22
- ) {
23
- value = value.slice(1, -1);
24
- }
25
-
26
- if (key) out[key] = value;
27
- }
28
-
29
- return out;
30
- }
31
-
32
- export function hydrateEnvFromProject(root) {
33
- if (!root) return;
34
-
35
- const envPath = join(root, '.env');
36
- if (!existsSync(envPath)) return;
37
-
38
- try {
39
- const parsed = parseEnvFile(readFileSync(envPath, 'utf8'));
40
- for (const [k, v] of Object.entries(parsed)) {
41
- if (!process.env[k] && v !== undefined) process.env[k] = v;
42
- }
43
- } catch {
44
- // Non-fatal: commands still work with shell env vars.
45
- }
46
- }
47
-
48
- export function getCursorApiKey(root) {
49
- hydrateEnvFromProject(root);
50
- const key = process.env.CURSOR_API_KEY?.trim();
51
- return key || null;
52
- }
53
-
54
- export function printCursorApiKeyRequired(commandName = 'this command') {
55
- console.log('');
56
- console.log(chalk.red(` CURSOR_API_KEY is required for ${commandName}.`));
57
- console.log(chalk.dim(' Set it once in your project root .env file:'));
58
- console.log(` ${chalk.bold('CURSOR_API_KEY=your_key')}`);
59
- console.log(chalk.dim(' You can get a key from: cursor.com/settings -> Cloud Agents'));
60
- console.log('');
61
- }