agentxchain 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/bin/agentxchain.js +38 -4
- package/package.json +1 -1
- package/src/adapters/cursor.js +134 -44
- package/src/commands/claim.js +117 -0
- package/src/commands/config.js +149 -0
- package/src/commands/init.js +123 -71
- package/src/commands/status.js +54 -16
- package/src/commands/stop.js +43 -59
- package/src/commands/update.js +42 -0
- package/src/commands/watch.js +210 -0
- package/src/lib/notify.js +24 -0
- package/src/lib/repo.js +37 -0
- package/src/lib/seed-prompt.js +49 -24
- package/src/templates/api-builder.json +29 -0
- package/src/templates/bug-squad.json +25 -0
- package/src/templates/landing-page.json +36 -0
- package/src/templates/refactor-team.json +25 -0
- package/src/templates/saas-mvp.json +29 -0
package/README.md
CHANGED
|
@@ -58,6 +58,33 @@ For Cursor Cloud Agents, set `CURSOR_API_KEY` in your environment. Without it, t
|
|
|
58
58
|
|
|
59
59
|
Stop all running agent sessions. Reads `.agentxchain-session.json` to find active agents.
|
|
60
60
|
|
|
61
|
+
### `agentxchain config`
|
|
62
|
+
|
|
63
|
+
View or edit project configuration.
|
|
64
|
+
|
|
65
|
+
- `--add-agent` — interactively add a new agent
|
|
66
|
+
- `--remove-agent <id>` — remove an agent by ID
|
|
67
|
+
- `--set "<key> <value>"` — update a setting (e.g. `--set "rules.max_consecutive_claims 3"`)
|
|
68
|
+
- `-j, --json` — output config as JSON
|
|
69
|
+
|
|
70
|
+
Examples:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
agentxchain config # show current config
|
|
74
|
+
agentxchain config --add-agent # add a new agent
|
|
75
|
+
agentxchain config --remove-agent ux # remove the ux agent
|
|
76
|
+
agentxchain config --set "project My New Name" # change project name
|
|
77
|
+
agentxchain config --set "rules.compress_after_words 8000"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### `agentxchain update`
|
|
81
|
+
|
|
82
|
+
Update the CLI to the latest version from npm.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
agentxchain update
|
|
86
|
+
```
|
|
87
|
+
|
|
61
88
|
## How it works
|
|
62
89
|
|
|
63
90
|
AgentXchain uses a **claim-based protocol**:
|
package/bin/agentxchain.js
CHANGED
|
@@ -1,28 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { Command } from 'commander';
|
|
4
|
-
import chalk from 'chalk';
|
|
5
4
|
import { initCommand } from '../src/commands/init.js';
|
|
6
5
|
import { statusCommand } from '../src/commands/status.js';
|
|
7
6
|
import { startCommand } from '../src/commands/start.js';
|
|
8
7
|
import { stopCommand } from '../src/commands/stop.js';
|
|
8
|
+
import { configCommand } from '../src/commands/config.js';
|
|
9
|
+
import { updateCommand } from '../src/commands/update.js';
|
|
10
|
+
import { watchCommand } from '../src/commands/watch.js';
|
|
11
|
+
import { claimCommand, releaseCommand } from '../src/commands/claim.js';
|
|
9
12
|
|
|
10
13
|
const program = new Command();
|
|
11
14
|
|
|
12
15
|
program
|
|
13
16
|
.name('agentxchain')
|
|
14
17
|
.description('Multi-agent coordination in your IDE')
|
|
15
|
-
.version('0.1.
|
|
18
|
+
.version('0.1.1');
|
|
16
19
|
|
|
17
20
|
program
|
|
18
21
|
.command('init')
|
|
19
|
-
.description('
|
|
22
|
+
.description('Create a new AgentXchain project folder')
|
|
20
23
|
.option('-y, --yes', 'Skip prompts, use defaults')
|
|
21
24
|
.action(initCommand);
|
|
22
25
|
|
|
23
26
|
program
|
|
24
27
|
.command('status')
|
|
25
|
-
.description('Show
|
|
28
|
+
.description('Show lock status, phase, and agents')
|
|
26
29
|
.option('-j, --json', 'Output as JSON')
|
|
27
30
|
.action(statusCommand);
|
|
28
31
|
|
|
@@ -39,4 +42,35 @@ program
|
|
|
39
42
|
.description('Stop all running agent sessions')
|
|
40
43
|
.action(stopCommand);
|
|
41
44
|
|
|
45
|
+
program
|
|
46
|
+
.command('config')
|
|
47
|
+
.description('View or edit project configuration')
|
|
48
|
+
.option('--add-agent', 'Add a new agent interactively')
|
|
49
|
+
.option('--remove-agent <id>', 'Remove an agent by ID')
|
|
50
|
+
.option('--set <key_value>', 'Set a config value (e.g. --set "rules.max_consecutive_claims 3")')
|
|
51
|
+
.option('-j, --json', 'Output config as JSON')
|
|
52
|
+
.action(configCommand);
|
|
53
|
+
|
|
54
|
+
program
|
|
55
|
+
.command('watch')
|
|
56
|
+
.description('Watch lock.json and coordinate agent turns (the referee)')
|
|
57
|
+
.option('--daemon', 'Run in background mode')
|
|
58
|
+
.action(watchCommand);
|
|
59
|
+
|
|
60
|
+
program
|
|
61
|
+
.command('claim')
|
|
62
|
+
.description('Claim the lock as a human (take control)')
|
|
63
|
+
.option('--force', 'Force-claim even if an agent holds the lock')
|
|
64
|
+
.action(claimCommand);
|
|
65
|
+
|
|
66
|
+
program
|
|
67
|
+
.command('release')
|
|
68
|
+
.description('Release the lock (hand back to agents)')
|
|
69
|
+
.action(releaseCommand);
|
|
70
|
+
|
|
71
|
+
program
|
|
72
|
+
.command('update')
|
|
73
|
+
.description('Update agentxchain CLI to the latest version')
|
|
74
|
+
.action(updateCommand);
|
|
75
|
+
|
|
42
76
|
program.parse();
|
package/package.json
CHANGED
package/src/adapters/cursor.js
CHANGED
|
@@ -1,26 +1,38 @@
|
|
|
1
|
+
import { writeFileSync, readFileSync, existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
1
3
|
import chalk from 'chalk';
|
|
2
4
|
import { generateSeedPrompt } from '../lib/seed-prompt.js';
|
|
5
|
+
import { getRepoUrl } from '../lib/repo.js';
|
|
3
6
|
|
|
4
7
|
const API_BASE = 'https://api.cursor.com/v0';
|
|
5
8
|
|
|
9
|
+
function authHeaders(apiKey) {
|
|
10
|
+
return {
|
|
11
|
+
'Authorization': `Basic ${Buffer.from(apiKey + ':').toString('base64')}`,
|
|
12
|
+
'Content-Type': 'application/json'
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// --- Public API ---
|
|
17
|
+
|
|
6
18
|
export async function launchCursorAgents(config, root, opts) {
|
|
7
19
|
const apiKey = process.env.CURSOR_API_KEY;
|
|
8
20
|
|
|
9
21
|
if (!apiKey) {
|
|
10
|
-
|
|
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('');
|
|
22
|
+
printApiKeyHelp();
|
|
21
23
|
return fallbackPromptOutput(config, opts);
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
const repoUrl = await getRepoUrl(root);
|
|
27
|
+
if (!repoUrl) {
|
|
28
|
+
console.log(chalk.red(' Could not detect GitHub repo URL.'));
|
|
29
|
+
console.log(chalk.dim(' Make sure this project is a git repo with a GitHub remote.'));
|
|
30
|
+
console.log(chalk.dim(' Or set source.repository manually in agentxchain.json.'));
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const model = config.cursor?.model || 'default';
|
|
35
|
+
const ref = config.cursor?.ref || 'main';
|
|
24
36
|
const agents = filterAgents(config, opts.agent);
|
|
25
37
|
const launched = [];
|
|
26
38
|
|
|
@@ -28,51 +40,130 @@ export async function launchCursorAgents(config, root, opts) {
|
|
|
28
40
|
const prompt = generateSeedPrompt(id, agent, config);
|
|
29
41
|
|
|
30
42
|
try {
|
|
43
|
+
const body = {
|
|
44
|
+
prompt: { text: prompt },
|
|
45
|
+
source: { repository: repoUrl, ref },
|
|
46
|
+
target: { autoCreatePr: false }
|
|
47
|
+
};
|
|
48
|
+
if (model !== 'default') body.model = model;
|
|
49
|
+
|
|
31
50
|
const res = await fetch(`${API_BASE}/agents`, {
|
|
32
51
|
method: 'POST',
|
|
33
|
-
headers:
|
|
34
|
-
|
|
35
|
-
'Content-Type': 'application/json'
|
|
36
|
-
},
|
|
37
|
-
body: JSON.stringify({
|
|
38
|
-
prompt,
|
|
39
|
-
repository: root,
|
|
40
|
-
name: `agentxchain-${id}`
|
|
41
|
-
})
|
|
52
|
+
headers: authHeaders(apiKey),
|
|
53
|
+
body: JSON.stringify(body)
|
|
42
54
|
});
|
|
43
55
|
|
|
44
56
|
if (!res.ok) {
|
|
45
|
-
const
|
|
46
|
-
console.log(chalk.red(`
|
|
57
|
+
const errBody = await res.text();
|
|
58
|
+
console.log(chalk.red(` ✗ ${id}: ${res.status} ${errBody}`));
|
|
47
59
|
continue;
|
|
48
60
|
}
|
|
49
61
|
|
|
50
62
|
const data = await res.json();
|
|
51
|
-
launched.push({
|
|
52
|
-
|
|
63
|
+
launched.push({
|
|
64
|
+
id,
|
|
65
|
+
name: agent.name,
|
|
66
|
+
cloudId: data.id,
|
|
67
|
+
status: data.status || 'CREATING',
|
|
68
|
+
url: data.target?.url || null
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const urlStr = data.target?.url ? chalk.dim(` → ${data.target.url}`) : '';
|
|
72
|
+
console.log(chalk.green(` ✓ ${chalk.bold(id)} (${agent.name}) — ${data.id}${urlStr}`));
|
|
53
73
|
} catch (err) {
|
|
54
|
-
console.log(chalk.red(`
|
|
74
|
+
console.log(chalk.red(` ✗ ${id}: ${err.message}`));
|
|
55
75
|
}
|
|
56
76
|
}
|
|
57
77
|
|
|
58
78
|
if (launched.length > 0) {
|
|
59
|
-
|
|
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`));
|
|
79
|
+
saveSession(root, launched, repoUrl);
|
|
65
80
|
}
|
|
66
81
|
|
|
67
82
|
return launched;
|
|
68
83
|
}
|
|
69
84
|
|
|
85
|
+
export async function sendFollowup(apiKey, cloudId, message) {
|
|
86
|
+
const res = await fetch(`${API_BASE}/agents/${cloudId}/followup`, {
|
|
87
|
+
method: 'POST',
|
|
88
|
+
headers: authHeaders(apiKey),
|
|
89
|
+
body: JSON.stringify({ prompt: { text: message } })
|
|
90
|
+
});
|
|
91
|
+
if (!res.ok) {
|
|
92
|
+
const body = await res.text();
|
|
93
|
+
throw new Error(`Followup failed (${res.status}): ${body}`);
|
|
94
|
+
}
|
|
95
|
+
return await res.json();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function getAgentStatus(apiKey, cloudId) {
|
|
99
|
+
const res = await fetch(`${API_BASE}/agents/${cloudId}`, {
|
|
100
|
+
method: 'GET',
|
|
101
|
+
headers: authHeaders(apiKey)
|
|
102
|
+
});
|
|
103
|
+
if (!res.ok) return null;
|
|
104
|
+
return await res.json();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function getAgentConversation(apiKey, cloudId) {
|
|
108
|
+
const res = await fetch(`${API_BASE}/agents/${cloudId}/conversation`, {
|
|
109
|
+
method: 'GET',
|
|
110
|
+
headers: authHeaders(apiKey)
|
|
111
|
+
});
|
|
112
|
+
if (!res.ok) return null;
|
|
113
|
+
return await res.json();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export async function stopAgent(apiKey, cloudId) {
|
|
117
|
+
const res = await fetch(`${API_BASE}/agents/${cloudId}/stop`, {
|
|
118
|
+
method: 'POST',
|
|
119
|
+
headers: authHeaders(apiKey)
|
|
120
|
+
});
|
|
121
|
+
return res.ok;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function deleteAgent(apiKey, cloudId) {
|
|
125
|
+
const res = await fetch(`${API_BASE}/agents/${cloudId}`, {
|
|
126
|
+
method: 'DELETE',
|
|
127
|
+
headers: authHeaders(apiKey)
|
|
128
|
+
});
|
|
129
|
+
return res.ok;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function loadSession(root) {
|
|
133
|
+
const sessionPath = join(root, '.agentxchain-session.json');
|
|
134
|
+
if (!existsSync(sessionPath)) return null;
|
|
135
|
+
return JSON.parse(readFileSync(sessionPath, 'utf8'));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// --- Internal ---
|
|
139
|
+
|
|
140
|
+
function saveSession(root, launched, repoUrl) {
|
|
141
|
+
const session = {
|
|
142
|
+
launched,
|
|
143
|
+
started_at: new Date().toISOString(),
|
|
144
|
+
ide: 'cursor',
|
|
145
|
+
repo: repoUrl
|
|
146
|
+
};
|
|
147
|
+
const sessionPath = join(root, '.agentxchain-session.json');
|
|
148
|
+
writeFileSync(sessionPath, JSON.stringify(session, null, 2) + '\n');
|
|
149
|
+
console.log(chalk.dim(` Session saved to .agentxchain-session.json`));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function filterAgents(config, specificId) {
|
|
153
|
+
if (specificId) {
|
|
154
|
+
if (!config.agents[specificId]) {
|
|
155
|
+
console.log(chalk.red(` Agent "${specificId}" not found in agentxchain.json`));
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
return { [specificId]: config.agents[specificId] };
|
|
159
|
+
}
|
|
160
|
+
return config.agents;
|
|
161
|
+
}
|
|
162
|
+
|
|
70
163
|
function fallbackPromptOutput(config, opts) {
|
|
71
164
|
const agents = filterAgents(config, opts.agent);
|
|
72
|
-
|
|
73
|
-
console.log(chalk.bold(' Copy-paste these prompts into separate Cursor sessions:'));
|
|
165
|
+
console.log(chalk.bold(' No API key. Printing seed prompts for manual use:'));
|
|
74
166
|
console.log('');
|
|
75
|
-
|
|
76
167
|
for (const [id, agent] of Object.entries(agents)) {
|
|
77
168
|
const prompt = generateSeedPrompt(id, agent, config);
|
|
78
169
|
console.log(chalk.dim(' ' + '─'.repeat(50)));
|
|
@@ -82,17 +173,16 @@ function fallbackPromptOutput(config, opts) {
|
|
|
82
173
|
console.log(prompt);
|
|
83
174
|
console.log('');
|
|
84
175
|
}
|
|
85
|
-
|
|
86
176
|
return [];
|
|
87
177
|
}
|
|
88
178
|
|
|
89
|
-
function
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
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('');
|
|
98
188
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { writeFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { loadConfig, loadLock, LOCK_FILE } from '../lib/config.js';
|
|
5
|
+
import { stopAgent, sendFollowup, loadSession } from '../adapters/cursor.js';
|
|
6
|
+
|
|
7
|
+
export async function claimCommand(opts) {
|
|
8
|
+
const result = loadConfig();
|
|
9
|
+
if (!result) { console.log(chalk.red(' No agentxchain.json found.')); process.exit(1); }
|
|
10
|
+
|
|
11
|
+
const { root, config } = result;
|
|
12
|
+
const lock = loadLock(root);
|
|
13
|
+
if (!lock) { console.log(chalk.red(' lock.json not found.')); process.exit(1); }
|
|
14
|
+
|
|
15
|
+
const apiKey = process.env.CURSOR_API_KEY;
|
|
16
|
+
const session = loadSession(root);
|
|
17
|
+
const hasCursor = session?.ide === 'cursor' && apiKey;
|
|
18
|
+
|
|
19
|
+
if (lock.holder === 'human') {
|
|
20
|
+
console.log('');
|
|
21
|
+
console.log(chalk.yellow(' You already hold the lock.'));
|
|
22
|
+
console.log(` ${chalk.dim('Release with:')} ${chalk.bold('agentxchain release')}`);
|
|
23
|
+
console.log('');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (lock.holder && !opts.force) {
|
|
28
|
+
const name = config.agents[lock.holder]?.name || lock.holder;
|
|
29
|
+
console.log('');
|
|
30
|
+
console.log(chalk.yellow(` Lock held by ${chalk.bold(lock.holder)} (${name}).`));
|
|
31
|
+
console.log(chalk.dim(' Use --force to override.'));
|
|
32
|
+
console.log('');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Pause all Cursor agents when human claims
|
|
37
|
+
if (hasCursor && session.launched.length > 0) {
|
|
38
|
+
console.log(chalk.dim(' Pausing Cursor agents...'));
|
|
39
|
+
for (const agent of session.launched) {
|
|
40
|
+
try {
|
|
41
|
+
await stopAgent(apiKey, agent.cloudId);
|
|
42
|
+
console.log(chalk.dim(` Paused ${agent.id}`));
|
|
43
|
+
} catch {
|
|
44
|
+
console.log(chalk.dim(` Could not pause ${agent.id}`));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const lockPath = join(root, LOCK_FILE);
|
|
50
|
+
const newLock = {
|
|
51
|
+
holder: 'human',
|
|
52
|
+
last_released_by: lock.last_released_by,
|
|
53
|
+
turn_number: lock.turn_number,
|
|
54
|
+
claimed_at: new Date().toISOString()
|
|
55
|
+
};
|
|
56
|
+
writeFileSync(lockPath, JSON.stringify(newLock, null, 2) + '\n');
|
|
57
|
+
|
|
58
|
+
console.log('');
|
|
59
|
+
console.log(chalk.green(` ✓ Lock claimed by ${chalk.bold('human')} (turn ${lock.turn_number})`));
|
|
60
|
+
if (hasCursor) console.log(chalk.dim(' All Cursor agents paused.'));
|
|
61
|
+
console.log(` ${chalk.dim('Do your work, then:')} ${chalk.bold('agentxchain release')}`);
|
|
62
|
+
console.log('');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function releaseCommand() {
|
|
66
|
+
const result = loadConfig();
|
|
67
|
+
if (!result) { console.log(chalk.red(' No agentxchain.json found.')); process.exit(1); }
|
|
68
|
+
|
|
69
|
+
const { root, config } = result;
|
|
70
|
+
const lock = loadLock(root);
|
|
71
|
+
if (!lock) { console.log(chalk.red(' lock.json not found.')); process.exit(1); }
|
|
72
|
+
|
|
73
|
+
if (!lock.holder) {
|
|
74
|
+
console.log(chalk.yellow(' Lock is already free.'));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const who = lock.holder;
|
|
79
|
+
const lockPath = join(root, LOCK_FILE);
|
|
80
|
+
const newLock = {
|
|
81
|
+
holder: null,
|
|
82
|
+
last_released_by: who,
|
|
83
|
+
turn_number: who === 'human' ? lock.turn_number : lock.turn_number + 1,
|
|
84
|
+
claimed_at: null
|
|
85
|
+
};
|
|
86
|
+
writeFileSync(lockPath, JSON.stringify(newLock, null, 2) + '\n');
|
|
87
|
+
|
|
88
|
+
console.log('');
|
|
89
|
+
console.log(chalk.green(` ✓ Lock released by ${chalk.bold(who)} (turn ${newLock.turn_number})`));
|
|
90
|
+
|
|
91
|
+
// If releasing from human and Cursor session exists, wake the next agent
|
|
92
|
+
if (who === 'human') {
|
|
93
|
+
const apiKey = process.env.CURSOR_API_KEY;
|
|
94
|
+
const session = loadSession(root);
|
|
95
|
+
|
|
96
|
+
if (session?.ide === 'cursor' && apiKey) {
|
|
97
|
+
const agentIds = Object.keys(config.agents);
|
|
98
|
+
const next = agentIds[0];
|
|
99
|
+
const cloudAgent = session.launched.find(a => a.id === next);
|
|
100
|
+
|
|
101
|
+
if (cloudAgent) {
|
|
102
|
+
try {
|
|
103
|
+
const name = config.agents[next]?.name || next;
|
|
104
|
+
await sendFollowup(apiKey, cloudAgent.cloudId,
|
|
105
|
+
`Human released the lock. It's your turn. Read lock.json, claim it, and do your work as ${name}.`
|
|
106
|
+
);
|
|
107
|
+
console.log(chalk.cyan(` Woke ${chalk.bold(next)} via Cursor followup.`));
|
|
108
|
+
} catch (err) {
|
|
109
|
+
console.log(chalk.dim(` Could not wake ${next}: ${err.message}`));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
console.log(chalk.dim(' The watch process will coordinate from here.'));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
console.log('');
|
|
117
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import { loadConfig, CONFIG_FILE } from '../lib/config.js';
|
|
6
|
+
|
|
7
|
+
export async function configCommand(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
|
+
|
|
17
|
+
if (opts.addAgent) {
|
|
18
|
+
await addAgent(config, configPath);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (opts.removeAgent) {
|
|
23
|
+
removeAgent(config, configPath, opts.removeAgent);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (opts.set) {
|
|
28
|
+
setSetting(config, configPath, opts.set);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (opts.json) {
|
|
33
|
+
console.log(JSON.stringify(config, null, 2));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
printConfig(config);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function printConfig(config) {
|
|
41
|
+
console.log('');
|
|
42
|
+
console.log(chalk.bold(' AgentXchain Config'));
|
|
43
|
+
console.log(chalk.dim(' ' + '─'.repeat(40)));
|
|
44
|
+
console.log('');
|
|
45
|
+
console.log(` ${chalk.dim('Project:')} ${config.project}`);
|
|
46
|
+
console.log(` ${chalk.dim('Version:')} ${config.version}`);
|
|
47
|
+
console.log(` ${chalk.dim('Log:')} ${config.log}`);
|
|
48
|
+
console.log('');
|
|
49
|
+
|
|
50
|
+
console.log(` ${chalk.dim('Rules:')}`);
|
|
51
|
+
for (const [key, val] of Object.entries(config.rules || {})) {
|
|
52
|
+
console.log(` ${chalk.dim(key + ':')} ${val}`);
|
|
53
|
+
}
|
|
54
|
+
console.log('');
|
|
55
|
+
|
|
56
|
+
console.log(` ${chalk.dim('Agents:')} ${Object.keys(config.agents).length}`);
|
|
57
|
+
for (const [id, agent] of Object.entries(config.agents)) {
|
|
58
|
+
console.log(` ${chalk.cyan(id)} — ${agent.name}`);
|
|
59
|
+
console.log(` ${chalk.dim(agent.mandate.slice(0, 80))}${agent.mandate.length > 80 ? '...' : ''}`);
|
|
60
|
+
console.log('');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.log(chalk.dim(' Commands:'));
|
|
64
|
+
console.log(` ${chalk.bold('agentxchain config --add-agent')} Add a new agent`);
|
|
65
|
+
console.log(` ${chalk.bold('agentxchain config --remove-agent <id>')} Remove an agent`);
|
|
66
|
+
console.log(` ${chalk.bold('agentxchain config --set <key> <val>')} Update a setting`);
|
|
67
|
+
console.log(` ${chalk.bold('agentxchain config --json')} Output as JSON`);
|
|
68
|
+
console.log('');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function addAgent(config, configPath) {
|
|
72
|
+
const answers = await inquirer.prompt([
|
|
73
|
+
{
|
|
74
|
+
type: 'input',
|
|
75
|
+
name: 'id',
|
|
76
|
+
message: 'Agent ID (lowercase, no spaces):',
|
|
77
|
+
validate: (val) => {
|
|
78
|
+
if (!val.match(/^[a-z0-9-]+$/)) return 'Use lowercase letters, numbers, and hyphens only.';
|
|
79
|
+
if (val === 'human' || val === 'system') return `"${val}" is a reserved ID.`;
|
|
80
|
+
if (config.agents[val]) return `Agent "${val}" already exists.`;
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
{ type: 'input', name: 'name', message: 'Display name:' },
|
|
85
|
+
{ type: 'input', name: 'mandate', message: 'Mandate (what this agent does):' }
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
config.agents[answers.id] = { name: answers.name, mandate: answers.mandate };
|
|
89
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
90
|
+
|
|
91
|
+
console.log('');
|
|
92
|
+
console.log(chalk.green(` ✓ Added agent ${chalk.bold(answers.id)} (${answers.name})`));
|
|
93
|
+
console.log(` ${chalk.dim('Agents now:')} ${Object.keys(config.agents).join(', ')}`);
|
|
94
|
+
console.log('');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function removeAgent(config, configPath, id) {
|
|
98
|
+
if (!config.agents[id]) {
|
|
99
|
+
console.log(chalk.red(` Agent "${id}" not found.`));
|
|
100
|
+
console.log(` ${chalk.dim('Available:')} ${Object.keys(config.agents).join(', ')}`);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const name = config.agents[id].name;
|
|
105
|
+
delete config.agents[id];
|
|
106
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
107
|
+
|
|
108
|
+
console.log('');
|
|
109
|
+
console.log(chalk.green(` ✓ Removed agent ${chalk.bold(id)} (${name})`));
|
|
110
|
+
console.log(` ${chalk.dim('Agents now:')} ${Object.keys(config.agents).join(', ')}`);
|
|
111
|
+
console.log('');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function setSetting(config, configPath, keyValPair) {
|
|
115
|
+
const parts = keyValPair.split(/\s+/);
|
|
116
|
+
if (parts.length < 2) {
|
|
117
|
+
console.log(chalk.red(' Usage: agentxchain config --set <key> <value>'));
|
|
118
|
+
console.log(chalk.dim(' Example: agentxchain config --set rules.max_consecutive_claims 3'));
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const key = parts[0];
|
|
123
|
+
const rawVal = parts.slice(1).join(' ');
|
|
124
|
+
const segments = key.split('.');
|
|
125
|
+
|
|
126
|
+
let target = config;
|
|
127
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
128
|
+
if (target[segments[i]] === undefined) {
|
|
129
|
+
target[segments[i]] = {};
|
|
130
|
+
}
|
|
131
|
+
target = target[segments[i]];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const lastKey = segments[segments.length - 1];
|
|
135
|
+
const oldVal = target[lastKey];
|
|
136
|
+
|
|
137
|
+
let val = rawVal;
|
|
138
|
+
if (rawVal === 'true') val = true;
|
|
139
|
+
else if (rawVal === 'false') val = false;
|
|
140
|
+
else if (!isNaN(rawVal) && rawVal !== '') val = Number(rawVal);
|
|
141
|
+
|
|
142
|
+
target[lastKey] = val;
|
|
143
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
144
|
+
|
|
145
|
+
console.log('');
|
|
146
|
+
console.log(chalk.green(` ✓ Set ${chalk.bold(key)} = ${val}`));
|
|
147
|
+
if (oldVal !== undefined) console.log(chalk.dim(` (was: ${oldVal})`));
|
|
148
|
+
console.log('');
|
|
149
|
+
}
|