agentxchain 0.4.0 → 0.4.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 +30 -2
- package/bin/agentxchain.js +9 -1
- package/package.json +3 -2
- package/src/adapters/cursor.js +26 -34
- package/src/commands/branch.js +98 -0
- package/src/commands/claim.js +21 -3
- package/src/commands/init.js +21 -1
- package/src/commands/start.js +6 -1
- package/src/commands/status.js +5 -1
- package/src/commands/stop.js +18 -15
- package/src/commands/watch.js +7 -1
- package/src/lib/cursor-api-key.js +61 -0
- package/src/lib/repo.js +9 -1
package/README.md
CHANGED
|
@@ -19,35 +19,63 @@ npx agentxchain init
|
|
|
19
19
|
```bash
|
|
20
20
|
agentxchain init # create a project (template selection)
|
|
21
21
|
cd my-project/
|
|
22
|
-
|
|
22
|
+
echo "CURSOR_API_KEY=your_key" >> .env # from cursor.com/settings -> Cloud Agents
|
|
23
23
|
agentxchain start --ide cursor # launch agents
|
|
24
24
|
agentxchain watch # coordinate turns automatically
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
> `CURSOR_API_KEY` is required for Cursor commands (`start/watch/stop/claim/release` when using Cursor sessions).
|
|
28
|
+
|
|
27
29
|
## Commands
|
|
28
30
|
|
|
29
31
|
| Command | What it does |
|
|
30
32
|
|---------|-------------|
|
|
31
33
|
| `init` | Create project folder with agents, protocol files, and templates |
|
|
32
|
-
| `start` | Launch agents in Cursor, Claude Code, or VS Code |
|
|
34
|
+
| `start` | Launch agents in Cursor, Claude Code, or VS Code (`CURSOR_API_KEY` required for Cursor) |
|
|
33
35
|
| `watch` | The referee — coordinates turns, enforces TTL, wakes agents |
|
|
34
36
|
| `status` | Show lock, phase, agents, Cursor session info |
|
|
35
37
|
| `claim` | Human takes control (pauses Cursor agents) |
|
|
36
38
|
| `release` | Hand lock back to agents |
|
|
37
39
|
| `stop` | Terminate all running agents |
|
|
40
|
+
| `branch` | Show/set Cursor branch override (`cursor.ref`) |
|
|
38
41
|
| `config` | View/edit config, add/remove agents, change rules |
|
|
39
42
|
| `update` | Self-update CLI from npm |
|
|
40
43
|
|
|
44
|
+
### Branch selection
|
|
45
|
+
|
|
46
|
+
By default, Cursor launches use your current local git branch. You can override this when needed.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
agentxchain branch # show current/effective branch
|
|
50
|
+
agentxchain branch develop # pin to a specific branch
|
|
51
|
+
agentxchain branch --use-current # pin to whatever branch you're on now
|
|
52
|
+
agentxchain branch --unset # remove pin; follow active git branch
|
|
53
|
+
```
|
|
54
|
+
|
|
41
55
|
## Key features
|
|
42
56
|
|
|
43
57
|
- **Claim-based coordination** — no fixed turn order; agents self-organize
|
|
44
58
|
- **User-defined teams** — any number of agents, any roles
|
|
45
59
|
- **Cursor Cloud Agents** — launch and manage agents via API
|
|
60
|
+
- **Branch-safe launching** — defaults to active git branch; optional `branch` override
|
|
61
|
+
- **Project `.env` loading** — CLI auto-reads `CURSOR_API_KEY` from project root `.env`
|
|
46
62
|
- **Lock TTL** — stale locks auto-released after timeout
|
|
47
63
|
- **Verify command** — agents must pass tests before releasing
|
|
48
64
|
- **Human-in-the-loop** — claim/release to intervene anytime
|
|
49
65
|
- **Team templates** — SaaS MVP, Landing Page, Bug Squad, API Builder, Refactor Team
|
|
50
66
|
|
|
67
|
+
## Publish updates (maintainers)
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
cd cli
|
|
71
|
+
bash scripts/publish-npm.sh # patch bump + publish
|
|
72
|
+
bash scripts/publish-npm.sh minor # minor bump + publish
|
|
73
|
+
bash scripts/publish-npm.sh 0.5.0 # explicit version + publish
|
|
74
|
+
bash scripts/publish-npm.sh patch --dry-run
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
If `NPM_TOKEN` exists in `cli/.env`, the script uses it automatically.
|
|
78
|
+
|
|
51
79
|
## Links
|
|
52
80
|
|
|
53
81
|
- [agentxchain.dev](https://agentxchain.dev)
|
package/bin/agentxchain.js
CHANGED
|
@@ -9,13 +9,14 @@ import { configCommand } from '../src/commands/config.js';
|
|
|
9
9
|
import { updateCommand } from '../src/commands/update.js';
|
|
10
10
|
import { watchCommand } from '../src/commands/watch.js';
|
|
11
11
|
import { claimCommand, releaseCommand } from '../src/commands/claim.js';
|
|
12
|
+
import { branchCommand } from '../src/commands/branch.js';
|
|
12
13
|
|
|
13
14
|
const program = new Command();
|
|
14
15
|
|
|
15
16
|
program
|
|
16
17
|
.name('agentxchain')
|
|
17
18
|
.description('Multi-agent coordination in your IDE')
|
|
18
|
-
.version('0.
|
|
19
|
+
.version('0.4.0');
|
|
19
20
|
|
|
20
21
|
program
|
|
21
22
|
.command('init')
|
|
@@ -51,6 +52,13 @@ program
|
|
|
51
52
|
.option('-j, --json', 'Output config as JSON')
|
|
52
53
|
.action(configCommand);
|
|
53
54
|
|
|
55
|
+
program
|
|
56
|
+
.command('branch [name]')
|
|
57
|
+
.description('Show or set the Cursor branch used for agent launches')
|
|
58
|
+
.option('--use-current', 'Set override to the current local git branch')
|
|
59
|
+
.option('--unset', 'Remove override and follow active git branch automatically')
|
|
60
|
+
.action(branchCommand);
|
|
61
|
+
|
|
54
62
|
program
|
|
55
63
|
.command('watch')
|
|
56
64
|
.description('Watch lock.json and coordinate agent turns (the referee)')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentxchain",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "CLI for AgentXchain — multi-agent coordination in your IDE",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"scripts": {
|
|
15
15
|
"dev": "node bin/agentxchain.js",
|
|
16
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"
|
|
17
|
+
"build:linux": "bun build bin/agentxchain.js --compile --target=bun-linux-x64 --outfile=dist/agentxchain-linux-x64",
|
|
18
|
+
"publish:npm": "bash scripts/publish-npm.sh"
|
|
18
19
|
},
|
|
19
20
|
"keywords": [
|
|
20
21
|
"ai",
|
package/src/adapters/cursor.js
CHANGED
|
@@ -2,7 +2,8 @@ import { writeFileSync, readFileSync, existsSync } from 'fs';
|
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { generateSeedPrompt } from '../lib/seed-prompt.js';
|
|
5
|
-
import { getRepoUrl } from '../lib/repo.js';
|
|
5
|
+
import { getRepoUrl, getCurrentBranch } from '../lib/repo.js';
|
|
6
|
+
import { getCursorApiKey, printCursorApiKeyRequired } from '../lib/cursor-api-key.js';
|
|
6
7
|
|
|
7
8
|
const API_BASE = 'https://api.cursor.com/v0';
|
|
8
9
|
|
|
@@ -16,11 +17,11 @@ function authHeaders(apiKey) {
|
|
|
16
17
|
// --- Public API ---
|
|
17
18
|
|
|
18
19
|
export async function launchCursorAgents(config, root, opts) {
|
|
19
|
-
const apiKey =
|
|
20
|
+
const apiKey = getCursorApiKey(root);
|
|
20
21
|
|
|
21
22
|
if (!apiKey) {
|
|
22
|
-
|
|
23
|
-
return
|
|
23
|
+
printCursorApiKeyRequired('`agentxchain start --ide cursor`');
|
|
24
|
+
return [];
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
const repoUrl = await getRepoUrl(root);
|
|
@@ -32,9 +33,11 @@ export async function launchCursorAgents(config, root, opts) {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
const model = config.cursor?.model || 'default';
|
|
35
|
-
const ref = config.cursor?.ref || 'main';
|
|
36
|
+
const ref = config.cursor?.ref || getCurrentBranch(root) || 'main';
|
|
37
|
+
console.log(chalk.dim(` Cursor source: ${repoUrl} @ ${ref}`));
|
|
36
38
|
const agents = filterAgents(config, opts.agent);
|
|
37
39
|
const launched = [];
|
|
40
|
+
let branchErrorCount = 0;
|
|
38
41
|
|
|
39
42
|
for (const [id, agent] of Object.entries(agents)) {
|
|
40
43
|
const prompt = generateSeedPrompt(id, agent, config);
|
|
@@ -56,6 +59,9 @@ export async function launchCursorAgents(config, root, opts) {
|
|
|
56
59
|
if (!res.ok) {
|
|
57
60
|
const errBody = await res.text();
|
|
58
61
|
console.log(chalk.red(` ✗ ${id}: ${res.status} ${errBody}`));
|
|
62
|
+
if (errBody.includes('Failed to verify existence of branch')) {
|
|
63
|
+
branchErrorCount += 1;
|
|
64
|
+
}
|
|
59
65
|
continue;
|
|
60
66
|
}
|
|
61
67
|
|
|
@@ -76,7 +82,16 @@ export async function launchCursorAgents(config, root, opts) {
|
|
|
76
82
|
}
|
|
77
83
|
|
|
78
84
|
if (launched.length > 0) {
|
|
79
|
-
saveSession(root, launched, repoUrl);
|
|
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('');
|
|
80
95
|
}
|
|
81
96
|
|
|
82
97
|
return launched;
|
|
@@ -137,12 +152,13 @@ export function loadSession(root) {
|
|
|
137
152
|
|
|
138
153
|
// --- Internal ---
|
|
139
154
|
|
|
140
|
-
function saveSession(root, launched, repoUrl) {
|
|
155
|
+
function saveSession(root, launched, repoUrl, ref) {
|
|
141
156
|
const session = {
|
|
142
157
|
launched,
|
|
143
158
|
started_at: new Date().toISOString(),
|
|
144
159
|
ide: 'cursor',
|
|
145
|
-
repo: repoUrl
|
|
160
|
+
repo: repoUrl,
|
|
161
|
+
ref
|
|
146
162
|
};
|
|
147
163
|
const sessionPath = join(root, '.agentxchain-session.json');
|
|
148
164
|
writeFileSync(sessionPath, JSON.stringify(session, null, 2) + '\n');
|
|
@@ -160,29 +176,5 @@ function filterAgents(config, specificId) {
|
|
|
160
176
|
return config.agents;
|
|
161
177
|
}
|
|
162
178
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
console.log(chalk.bold(' No API key. Printing seed prompts for manual use:'));
|
|
166
|
-
console.log('');
|
|
167
|
-
for (const [id, agent] of Object.entries(agents)) {
|
|
168
|
-
const prompt = generateSeedPrompt(id, agent, config);
|
|
169
|
-
console.log(chalk.dim(' ' + '─'.repeat(50)));
|
|
170
|
-
console.log(chalk.cyan(` Agent: ${chalk.bold(id)} (${agent.name})`));
|
|
171
|
-
console.log(chalk.dim(' ' + '─'.repeat(50)));
|
|
172
|
-
console.log('');
|
|
173
|
-
console.log(prompt);
|
|
174
|
-
console.log('');
|
|
175
|
-
}
|
|
176
|
-
return [];
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function printApiKeyHelp() {
|
|
180
|
-
console.log('');
|
|
181
|
-
console.log(chalk.yellow(' CURSOR_API_KEY not found.'));
|
|
182
|
-
console.log('');
|
|
183
|
-
console.log(` 1. Go to ${chalk.cyan('cursor.com/settings')} → Cloud Agents`);
|
|
184
|
-
console.log(' 2. Create an API key');
|
|
185
|
-
console.log(` 3. Add to .env: ${chalk.bold('CURSOR_API_KEY=your_key')}`);
|
|
186
|
-
console.log(` 4. Run: ${chalk.bold('source .env && agentxchain start')}`);
|
|
187
|
-
console.log('');
|
|
188
|
-
}
|
|
179
|
+
// No prompt fallback here by design.
|
|
180
|
+
// Cursor mode is strict: an API key is required.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { writeFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { loadConfig, CONFIG_FILE } from '../lib/config.js';
|
|
5
|
+
import { getCurrentBranch } from '../lib/repo.js';
|
|
6
|
+
|
|
7
|
+
export async function branchCommand(name, opts) {
|
|
8
|
+
const result = loadConfig();
|
|
9
|
+
if (!result) {
|
|
10
|
+
console.log(chalk.red(' No agentxchain.json found. Run `agentxchain init` first.'));
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { root, config } = result;
|
|
15
|
+
const configPath = join(root, CONFIG_FILE);
|
|
16
|
+
const currentGitBranch = getCurrentBranch(root) || 'main';
|
|
17
|
+
const hasOverride = !!config.cursor?.ref;
|
|
18
|
+
const overrideBranch = hasOverride ? config.cursor.ref : null;
|
|
19
|
+
|
|
20
|
+
if (opts.unset && (name || opts.useCurrent)) {
|
|
21
|
+
console.log(chalk.red(' Use either --unset OR a branch value, not both.'));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (name && opts.useCurrent) {
|
|
26
|
+
console.log(chalk.red(' Use either --use-current OR a branch value, not both.'));
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (opts.unset) {
|
|
31
|
+
if (config.cursor?.ref) {
|
|
32
|
+
delete config.cursor.ref;
|
|
33
|
+
if (config.cursor && Object.keys(config.cursor).length === 0) delete config.cursor;
|
|
34
|
+
saveConfig(configPath, config);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log('');
|
|
38
|
+
console.log(chalk.green(' ✓ Cleared branch override.'));
|
|
39
|
+
console.log(chalk.dim(` Effective branch now follows git: ${currentGitBranch}`));
|
|
40
|
+
console.log('');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (opts.useCurrent) {
|
|
45
|
+
setBranchOverride(config, configPath, currentGitBranch);
|
|
46
|
+
console.log('');
|
|
47
|
+
console.log(chalk.green(` ✓ Set Cursor branch override to current git branch: ${chalk.bold(currentGitBranch)}`));
|
|
48
|
+
console.log('');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (name) {
|
|
53
|
+
const branch = String(name).trim();
|
|
54
|
+
if (!branch) {
|
|
55
|
+
console.log(chalk.red(' Branch cannot be empty.'));
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
if (!/^[A-Za-z0-9._/-]+$/.test(branch)) {
|
|
59
|
+
console.log(chalk.red(' Invalid branch name. Use letters, numbers, ., _, -, /.'));
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
setBranchOverride(config, configPath, branch);
|
|
64
|
+
console.log('');
|
|
65
|
+
console.log(chalk.green(` ✓ Set Cursor branch override: ${chalk.bold(branch)}`));
|
|
66
|
+
if (branch !== currentGitBranch) {
|
|
67
|
+
console.log(chalk.dim(` (current git branch is ${currentGitBranch})`));
|
|
68
|
+
}
|
|
69
|
+
console.log('');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const effective = overrideBranch || currentGitBranch;
|
|
74
|
+
console.log('');
|
|
75
|
+
console.log(chalk.bold(' AgentXchain Branch'));
|
|
76
|
+
console.log(chalk.dim(' ' + '─'.repeat(36)));
|
|
77
|
+
console.log('');
|
|
78
|
+
console.log(` ${chalk.dim('Current git branch:')} ${currentGitBranch}`);
|
|
79
|
+
console.log(` ${chalk.dim('Cursor override:')} ${overrideBranch || chalk.dim('none')}`);
|
|
80
|
+
console.log(` ${chalk.dim('Effective branch:')} ${chalk.bold(effective)}`);
|
|
81
|
+
console.log('');
|
|
82
|
+
console.log(chalk.dim(' Commands:'));
|
|
83
|
+
console.log(` ${chalk.bold('agentxchain branch')} Show effective branch`);
|
|
84
|
+
console.log(` ${chalk.bold('agentxchain branch <name>')} Set branch override`);
|
|
85
|
+
console.log(` ${chalk.bold('agentxchain branch --use-current')} Set override to current git branch`);
|
|
86
|
+
console.log(` ${chalk.bold('agentxchain branch --unset')} Follow git branch automatically`);
|
|
87
|
+
console.log('');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function setBranchOverride(config, configPath, branch) {
|
|
91
|
+
if (!config.cursor) config.cursor = {};
|
|
92
|
+
config.cursor.ref = branch;
|
|
93
|
+
saveConfig(configPath, config);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function saveConfig(configPath, config) {
|
|
97
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
98
|
+
}
|
package/src/commands/claim.js
CHANGED
|
@@ -3,6 +3,7 @@ import { join } from 'path';
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { loadConfig, loadLock, LOCK_FILE } from '../lib/config.js';
|
|
5
5
|
import { stopAgent, sendFollowup, loadSession } from '../adapters/cursor.js';
|
|
6
|
+
import { getCursorApiKey, printCursorApiKeyRequired } from '../lib/cursor-api-key.js';
|
|
6
7
|
|
|
7
8
|
export async function claimCommand(opts) {
|
|
8
9
|
const result = loadConfig();
|
|
@@ -12,9 +13,17 @@ export async function claimCommand(opts) {
|
|
|
12
13
|
const lock = loadLock(root);
|
|
13
14
|
if (!lock) { console.log(chalk.red(' lock.json not found.')); process.exit(1); }
|
|
14
15
|
|
|
15
|
-
const apiKey =
|
|
16
|
+
const apiKey = getCursorApiKey(root);
|
|
16
17
|
const session = loadSession(root);
|
|
17
|
-
const
|
|
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
|
+
}
|
|
18
27
|
|
|
19
28
|
if (lock.holder === 'human') {
|
|
20
29
|
console.log('');
|
|
@@ -90,8 +99,17 @@ export async function releaseCommand() {
|
|
|
90
99
|
|
|
91
100
|
// If releasing from human and Cursor session exists, wake the next agent
|
|
92
101
|
if (who === 'human') {
|
|
93
|
-
const apiKey =
|
|
102
|
+
const apiKey = getCursorApiKey(root);
|
|
94
103
|
const session = loadSession(root);
|
|
104
|
+
const hasCursorSession = session?.ide === 'cursor' && session?.launched?.length > 0;
|
|
105
|
+
|
|
106
|
+
if (hasCursorSession && !apiKey) {
|
|
107
|
+
printCursorApiKeyRequired('`agentxchain release` with Cursor agents');
|
|
108
|
+
console.log(chalk.dim(' Lock released, but wake-up followup was skipped due to missing key.'));
|
|
109
|
+
console.log(chalk.dim(' Start `agentxchain watch` after setting the key.'));
|
|
110
|
+
console.log('');
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
95
113
|
|
|
96
114
|
if (session?.ide === 'cursor' && apiKey) {
|
|
97
115
|
const agentIds = Object.keys(config.agents);
|
package/src/commands/init.js
CHANGED
|
@@ -205,6 +205,25 @@ export async function initCommand(opts) {
|
|
|
205
205
|
writeFileSync(join(dir, 'history.jsonl'), '');
|
|
206
206
|
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`);
|
|
207
207
|
writeFileSync(join(dir, 'HUMAN_TASKS.md'), '# Human Tasks\n\n(Agents append tasks here when they need human action.)\n');
|
|
208
|
+
writeFileSync(join(dir, '.env.example'), 'CURSOR_API_KEY=\n');
|
|
209
|
+
if (!existsSync(join(dir, '.env'))) {
|
|
210
|
+
writeFileSync(
|
|
211
|
+
join(dir, '.env'),
|
|
212
|
+
'# Required for Cursor commands: start/watch/stop/claim/release\nCURSOR_API_KEY=\n'
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
const gitignorePath = join(dir, '.gitignore');
|
|
216
|
+
const requiredIgnores = ['.env', '.agentxchain-session.json', '.agentxchain-trigger.json'];
|
|
217
|
+
if (!existsSync(gitignorePath)) {
|
|
218
|
+
writeFileSync(gitignorePath, requiredIgnores.join('\n') + '\n');
|
|
219
|
+
} else {
|
|
220
|
+
const existingIgnore = readFileSync(gitignorePath, 'utf8');
|
|
221
|
+
const missing = requiredIgnores.filter(entry => !existingIgnore.split(/\r?\n/).includes(entry));
|
|
222
|
+
if (missing.length > 0) {
|
|
223
|
+
const prefix = existingIgnore.endsWith('\n') ? '' : '\n';
|
|
224
|
+
writeFileSync(gitignorePath, existingIgnore + prefix + missing.join('\n') + '\n');
|
|
225
|
+
}
|
|
226
|
+
}
|
|
208
227
|
|
|
209
228
|
// .planning/ structure
|
|
210
229
|
mkdirSync(join(dir, '.planning', 'research'), { recursive: true });
|
|
@@ -245,7 +264,8 @@ export async function initCommand(opts) {
|
|
|
245
264
|
console.log('');
|
|
246
265
|
console.log(` ${chalk.cyan('Next:')}`);
|
|
247
266
|
console.log(` ${chalk.bold(`cd ${folderName}`)}`);
|
|
267
|
+
console.log(` ${chalk.bold('edit .env')} ${chalk.dim('# set CURSOR_API_KEY (required for Cursor mode)')}`);
|
|
268
|
+
console.log(` ${chalk.bold('agentxchain start')} ${chalk.dim('# launch agents in Cursor')}`);
|
|
248
269
|
console.log(` ${chalk.bold('agentxchain watch')} ${chalk.dim('# start the referee')}`);
|
|
249
|
-
console.log(` ${chalk.bold('agentxchain start')} ${chalk.dim('# launch agents in your IDE')}`);
|
|
250
270
|
console.log('');
|
|
251
271
|
}
|
package/src/commands/start.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import ora from 'ora';
|
|
3
2
|
import { loadConfig } from '../lib/config.js';
|
|
4
3
|
import { generateSeedPrompt } from '../lib/seed-prompt.js';
|
|
4
|
+
import { getCursorApiKey, printCursorApiKeyRequired } from '../lib/cursor-api-key.js';
|
|
5
5
|
|
|
6
6
|
export async function startCommand(opts) {
|
|
7
7
|
const result = loadConfig();
|
|
@@ -33,6 +33,11 @@ export async function startCommand(opts) {
|
|
|
33
33
|
|
|
34
34
|
switch (ide) {
|
|
35
35
|
case 'cursor': {
|
|
36
|
+
const apiKey = getCursorApiKey(root);
|
|
37
|
+
if (!apiKey) {
|
|
38
|
+
printCursorApiKeyRequired('`agentxchain start --ide cursor`');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
36
41
|
const { launchCursorAgents } = await import('../adapters/cursor.js');
|
|
37
42
|
await launchCursorAgents(config, root, opts);
|
|
38
43
|
break;
|
package/src/commands/status.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { loadConfig, loadLock, loadState } from '../lib/config.js';
|
|
3
3
|
import { getAgentStatus, loadSession } from '../adapters/cursor.js';
|
|
4
|
+
import { getCursorApiKey } from '../lib/cursor-api-key.js';
|
|
4
5
|
|
|
5
6
|
export async function statusCommand(opts) {
|
|
6
7
|
const result = loadConfig();
|
|
@@ -52,13 +53,16 @@ export async function statusCommand(opts) {
|
|
|
52
53
|
|
|
53
54
|
// Cursor session info
|
|
54
55
|
const session = loadSession(root);
|
|
55
|
-
const apiKey =
|
|
56
|
+
const apiKey = getCursorApiKey(root);
|
|
56
57
|
const hasCursor = session?.ide === 'cursor' && session?.launched?.length > 0;
|
|
57
58
|
|
|
58
59
|
if (hasCursor) {
|
|
59
60
|
console.log(` ${chalk.dim('Cursor:')} ${chalk.cyan('Active session')} (${session.launched.length} agents)`);
|
|
60
61
|
console.log(` ${chalk.dim('Started:')} ${session.started_at}`);
|
|
61
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
|
+
}
|
|
62
66
|
console.log('');
|
|
63
67
|
}
|
|
64
68
|
|
package/src/commands/stop.js
CHANGED
|
@@ -2,7 +2,8 @@ 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,
|
|
5
|
+
import { deleteAgent, loadSession } from '../adapters/cursor.js';
|
|
6
|
+
import { getCursorApiKey, printCursorApiKeyRequired } from '../lib/cursor-api-key.js';
|
|
6
7
|
|
|
7
8
|
const SESSION_FILE = '.agentxchain-session.json';
|
|
8
9
|
|
|
@@ -24,22 +25,24 @@ export async function stopCommand() {
|
|
|
24
25
|
console.log('');
|
|
25
26
|
|
|
26
27
|
if (session.ide === 'cursor') {
|
|
27
|
-
const apiKey =
|
|
28
|
+
const apiKey = getCursorApiKey(root);
|
|
28
29
|
if (!apiKey) {
|
|
29
|
-
|
|
30
|
-
console.log(chalk.dim('
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
console.log(chalk.
|
|
30
|
+
printCursorApiKeyRequired('`agentxchain stop` for Cursor agents');
|
|
31
|
+
console.log(chalk.dim(' Session file was kept so you can retry after setting the key.'));
|
|
32
|
+
console.log('');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const agent of session.launched) {
|
|
37
|
+
try {
|
|
38
|
+
const deleted = await deleteAgent(apiKey, agent.cloudId);
|
|
39
|
+
if (deleted) {
|
|
40
|
+
console.log(chalk.green(` ✓ Deleted ${chalk.bold(agent.id)} (${agent.cloudId})`));
|
|
41
|
+
} else {
|
|
42
|
+
console.log(chalk.yellow(` ⚠ Could not delete ${agent.id} — may already be gone`));
|
|
42
43
|
}
|
|
44
|
+
} catch (err) {
|
|
45
|
+
console.log(chalk.red(` ✗ ${agent.id}: ${err.message}`));
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
48
|
} else if (session.ide === 'claude-code') {
|
package/src/commands/watch.js
CHANGED
|
@@ -4,6 +4,7 @@ import chalk from 'chalk';
|
|
|
4
4
|
import { loadConfig, loadLock, LOCK_FILE } from '../lib/config.js';
|
|
5
5
|
import { notifyHuman as sendNotification } from '../lib/notify.js';
|
|
6
6
|
import { sendFollowup, getAgentStatus, stopAgent, loadSession } from '../adapters/cursor.js';
|
|
7
|
+
import { getCursorApiKey, printCursorApiKeyRequired } from '../lib/cursor-api-key.js';
|
|
7
8
|
|
|
8
9
|
export async function watchCommand(opts) {
|
|
9
10
|
const result = loadConfig();
|
|
@@ -16,10 +17,15 @@ export async function watchCommand(opts) {
|
|
|
16
17
|
const interval = config.rules?.watch_interval_ms || 5000;
|
|
17
18
|
const ttlMinutes = config.rules?.ttl_minutes || 10;
|
|
18
19
|
const agentIds = Object.keys(config.agents);
|
|
19
|
-
const apiKey =
|
|
20
|
+
const apiKey = getCursorApiKey(root);
|
|
20
21
|
const session = loadSession(root);
|
|
21
22
|
const hasCursorSession = session?.ide === 'cursor' && session?.launched?.length > 0;
|
|
22
23
|
|
|
24
|
+
if (hasCursorSession && !apiKey) {
|
|
25
|
+
printCursorApiKeyRequired('`agentxchain watch` with a Cursor session');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
23
29
|
console.log('');
|
|
24
30
|
console.log(chalk.bold(' AgentXchain Watch'));
|
|
25
31
|
console.log(chalk.dim(` Project: ${config.project}`));
|
|
@@ -0,0 +1,61 @@
|
|
|
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
|
+
}
|
package/src/lib/repo.js
CHANGED
|
@@ -30,8 +30,16 @@ export async function getRepoUrl(root) {
|
|
|
30
30
|
|
|
31
31
|
export function getCurrentBranch(root) {
|
|
32
32
|
try {
|
|
33
|
-
|
|
33
|
+
const current = execSync('git rev-parse --abbrev-ref HEAD', { cwd: root, encoding: 'utf8' }).trim();
|
|
34
|
+
if (current && current !== 'HEAD') return current;
|
|
35
|
+
} catch {}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const remoteHead = execSync('git symbolic-ref --short refs/remotes/origin/HEAD', { cwd: root, encoding: 'utf8' }).trim();
|
|
39
|
+
if (remoteHead.includes('/')) return remoteHead.split('/').pop();
|
|
34
40
|
} catch {
|
|
35
41
|
return 'main';
|
|
36
42
|
}
|
|
43
|
+
|
|
44
|
+
return 'main';
|
|
37
45
|
}
|