agentxchain 0.1.2 → 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/bin/agentxchain.js +19 -0
- package/package.json +1 -1
- package/src/adapters/cursor.js +134 -44
- package/src/commands/claim.js +117 -0
- package/src/commands/config.js +1 -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/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/bin/agentxchain.js
CHANGED
|
@@ -7,6 +7,8 @@ import { startCommand } from '../src/commands/start.js';
|
|
|
7
7
|
import { stopCommand } from '../src/commands/stop.js';
|
|
8
8
|
import { configCommand } from '../src/commands/config.js';
|
|
9
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';
|
|
10
12
|
|
|
11
13
|
const program = new Command();
|
|
12
14
|
|
|
@@ -49,6 +51,23 @@ program
|
|
|
49
51
|
.option('-j, --json', 'Output config as JSON')
|
|
50
52
|
.action(configCommand);
|
|
51
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
|
+
|
|
52
71
|
program
|
|
53
72
|
.command('update')
|
|
54
73
|
.description('Update agentxchain CLI to the latest version')
|
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
|
+
}
|
package/src/commands/config.js
CHANGED
|
@@ -76,6 +76,7 @@ async function addAgent(config, configPath) {
|
|
|
76
76
|
message: 'Agent ID (lowercase, no spaces):',
|
|
77
77
|
validate: (val) => {
|
|
78
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.`;
|
|
79
80
|
if (config.agents[val]) return `Agent "${val}" already exists.`;
|
|
80
81
|
return true;
|
|
81
82
|
}
|
package/src/commands/init.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import { writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
2
|
-
import { join, resolve } from 'path';
|
|
1
|
+
import { writeFileSync, readFileSync, existsSync, mkdirSync, readdirSync } from 'fs';
|
|
2
|
+
import { join, resolve, dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
3
4
|
import chalk from 'chalk';
|
|
4
5
|
import inquirer from 'inquirer';
|
|
5
6
|
import { CONFIG_FILE, LOCK_FILE, STATE_FILE } from '../lib/config.js';
|
|
6
7
|
|
|
8
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const TEMPLATES_DIR = join(__dirname, '../templates');
|
|
10
|
+
|
|
7
11
|
const DEFAULT_AGENTS = {
|
|
8
12
|
pm: {
|
|
9
13
|
name: 'Product Manager',
|
|
@@ -24,54 +28,93 @@ const DEFAULT_AGENTS = {
|
|
|
24
28
|
};
|
|
25
29
|
|
|
26
30
|
function slugify(name) {
|
|
27
|
-
return name
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function loadTemplates() {
|
|
35
|
+
const templates = [];
|
|
36
|
+
try {
|
|
37
|
+
const files = readdirSync(TEMPLATES_DIR).filter(f => f.endsWith('.json'));
|
|
38
|
+
for (const file of files) {
|
|
39
|
+
const raw = readFileSync(join(TEMPLATES_DIR, file), 'utf8');
|
|
40
|
+
const tmpl = JSON.parse(raw);
|
|
41
|
+
templates.push({ id: file.replace('.json', ''), ...tmpl });
|
|
42
|
+
}
|
|
43
|
+
} catch {}
|
|
44
|
+
return templates;
|
|
31
45
|
}
|
|
32
46
|
|
|
33
47
|
export async function initCommand(opts) {
|
|
34
|
-
let project, agents, folderName;
|
|
48
|
+
let project, agents, folderName, rules;
|
|
35
49
|
|
|
36
50
|
if (opts.yes) {
|
|
37
51
|
project = 'My AgentXchain project';
|
|
38
52
|
agents = DEFAULT_AGENTS;
|
|
39
53
|
folderName = slugify(project);
|
|
54
|
+
rules = { max_consecutive_claims: 2, require_message: true, compress_after_words: 5000 };
|
|
40
55
|
} else {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
folderName = folderAnswer.folder;
|
|
59
|
-
|
|
60
|
-
const agentAnswer = await inquirer.prompt([{
|
|
61
|
-
type: 'confirm',
|
|
62
|
-
name: 'useDefaults',
|
|
63
|
-
message: 'Use default agents (pm, dev, qa, ux)?',
|
|
64
|
-
default: true
|
|
56
|
+
const templates = loadTemplates();
|
|
57
|
+
|
|
58
|
+
// Template selection
|
|
59
|
+
const templateChoices = [
|
|
60
|
+
{ name: `${chalk.cyan('Custom')} — define your own agents`, value: 'custom' },
|
|
61
|
+
{ name: `${chalk.cyan('Default')} — PM, Dev, QA, UX (4 agents)`, value: 'default' },
|
|
62
|
+
...templates.map(t => ({
|
|
63
|
+
name: `${chalk.cyan(t.label)} — ${t.description} (${Object.keys(t.agents).length} agents)`,
|
|
64
|
+
value: t.id
|
|
65
|
+
}))
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
const { template } = await inquirer.prompt([{
|
|
69
|
+
type: 'list',
|
|
70
|
+
name: 'template',
|
|
71
|
+
message: 'Choose a team template:',
|
|
72
|
+
choices: templateChoices
|
|
65
73
|
}]);
|
|
66
74
|
|
|
67
|
-
if (
|
|
75
|
+
if (template !== 'custom' && template !== 'default') {
|
|
76
|
+
const tmpl = templates.find(t => t.id === template);
|
|
77
|
+
agents = tmpl.agents;
|
|
78
|
+
rules = tmpl.rules || {};
|
|
79
|
+
const { projectName } = await inquirer.prompt([{
|
|
80
|
+
type: 'input',
|
|
81
|
+
name: 'projectName',
|
|
82
|
+
message: 'Project name:',
|
|
83
|
+
default: tmpl.project
|
|
84
|
+
}]);
|
|
85
|
+
project = projectName;
|
|
86
|
+
} else if (template === 'default') {
|
|
68
87
|
agents = DEFAULT_AGENTS;
|
|
88
|
+
rules = { max_consecutive_claims: 2, require_message: true, compress_after_words: 5000 };
|
|
89
|
+
const { projectName } = await inquirer.prompt([{
|
|
90
|
+
type: 'input',
|
|
91
|
+
name: 'projectName',
|
|
92
|
+
message: 'Project name:',
|
|
93
|
+
default: 'My AgentXchain project'
|
|
94
|
+
}]);
|
|
95
|
+
project = projectName;
|
|
69
96
|
} else {
|
|
97
|
+
const { projectName } = await inquirer.prompt([{
|
|
98
|
+
type: 'input',
|
|
99
|
+
name: 'projectName',
|
|
100
|
+
message: 'Project name:',
|
|
101
|
+
default: 'My AgentXchain project'
|
|
102
|
+
}]);
|
|
103
|
+
project = projectName;
|
|
70
104
|
agents = {};
|
|
105
|
+
rules = { max_consecutive_claims: 2, require_message: true, compress_after_words: 5000 };
|
|
71
106
|
let adding = true;
|
|
72
107
|
while (adding) {
|
|
73
108
|
const agent = await inquirer.prompt([
|
|
74
|
-
{
|
|
109
|
+
{
|
|
110
|
+
type: 'input', name: 'id', message: 'Agent ID (lowercase, no spaces):',
|
|
111
|
+
validate: v => {
|
|
112
|
+
if (!v.match(/^[a-z0-9-]+$/)) return 'Lowercase letters, numbers, hyphens only.';
|
|
113
|
+
if (v === 'human' || v === 'system') return `"${v}" is reserved.`;
|
|
114
|
+
if (agents[v]) return `"${v}" already added.`;
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
},
|
|
75
118
|
{ type: 'input', name: 'name', message: 'Display name:' },
|
|
76
119
|
{ type: 'input', name: 'mandate', message: 'Mandate (what this agent does):' },
|
|
77
120
|
{ type: 'confirm', name: 'more', message: 'Add another agent?', default: true }
|
|
@@ -80,72 +123,81 @@ export async function initCommand(opts) {
|
|
|
80
123
|
adding = agent.more;
|
|
81
124
|
}
|
|
82
125
|
}
|
|
126
|
+
|
|
127
|
+
folderName = slugify(project);
|
|
128
|
+
const { folder } = await inquirer.prompt([{
|
|
129
|
+
type: 'input',
|
|
130
|
+
name: 'folder',
|
|
131
|
+
message: 'Folder name:',
|
|
132
|
+
default: folderName
|
|
133
|
+
}]);
|
|
134
|
+
folderName = folder;
|
|
83
135
|
}
|
|
84
136
|
|
|
85
137
|
const dir = resolve(process.cwd(), folderName);
|
|
86
138
|
|
|
87
|
-
if (existsSync(dir)) {
|
|
88
|
-
if (
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
139
|
+
if (existsSync(dir) && existsSync(join(dir, CONFIG_FILE))) {
|
|
140
|
+
if (!opts.yes) {
|
|
141
|
+
const { overwrite } = await inquirer.prompt([{
|
|
142
|
+
type: 'confirm',
|
|
143
|
+
name: 'overwrite',
|
|
144
|
+
message: `${folderName}/ already has agentxchain.json. Overwrite?`,
|
|
145
|
+
default: false
|
|
146
|
+
}]);
|
|
147
|
+
if (!overwrite) {
|
|
148
|
+
console.log(chalk.yellow(' Aborted.'));
|
|
149
|
+
return;
|
|
100
150
|
}
|
|
101
151
|
}
|
|
102
|
-
} else {
|
|
103
|
-
mkdirSync(dir, { recursive: true });
|
|
104
152
|
}
|
|
105
153
|
|
|
154
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
155
|
+
|
|
106
156
|
const config = {
|
|
107
157
|
version: 3,
|
|
108
158
|
project,
|
|
109
159
|
agents,
|
|
110
160
|
log: 'log.md',
|
|
161
|
+
state_file: 'state.md',
|
|
162
|
+
history_file: 'history.jsonl',
|
|
111
163
|
rules: {
|
|
112
|
-
max_consecutive_claims: 2,
|
|
113
|
-
require_message:
|
|
114
|
-
compress_after_words: 5000
|
|
164
|
+
max_consecutive_claims: rules.max_consecutive_claims || 2,
|
|
165
|
+
require_message: rules.require_message !== false,
|
|
166
|
+
compress_after_words: rules.compress_after_words || 5000,
|
|
167
|
+
ttl_minutes: rules.ttl_minutes || 10,
|
|
168
|
+
watch_interval_ms: rules.watch_interval_ms || 5000,
|
|
169
|
+
...(rules.verify_command ? { verify_command: rules.verify_command } : {})
|
|
115
170
|
}
|
|
116
171
|
};
|
|
117
172
|
|
|
118
|
-
const lock = {
|
|
119
|
-
|
|
120
|
-
last_released_by: null,
|
|
121
|
-
turn_number: 0,
|
|
122
|
-
claimed_at: null
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
const state = {
|
|
126
|
-
phase: 'discovery',
|
|
127
|
-
blocked: false,
|
|
128
|
-
blocked_on: null,
|
|
129
|
-
project
|
|
130
|
-
};
|
|
173
|
+
const lock = { holder: null, last_released_by: null, turn_number: 0, claimed_at: null };
|
|
174
|
+
const state = { phase: 'discovery', blocked: false, blocked_on: null, project };
|
|
131
175
|
|
|
132
176
|
writeFileSync(join(dir, CONFIG_FILE), JSON.stringify(config, null, 2) + '\n');
|
|
133
177
|
writeFileSync(join(dir, LOCK_FILE), JSON.stringify(lock, null, 2) + '\n');
|
|
134
|
-
writeFileSync(join(dir,
|
|
135
|
-
writeFileSync(join(dir,
|
|
178
|
+
writeFileSync(join(dir, 'state.json'), JSON.stringify(state, null, 2) + '\n');
|
|
179
|
+
writeFileSync(join(dir, 'state.md'), `# ${project} — Current State\n\n## Architecture\n\n(Agents update this each turn with current decisions.)\n\n## Active Work\n\n(What's in progress right now.)\n\n## Open Issues\n\n(Bugs, blockers, risks.)\n\n## Next Steps\n\n(What should happen next.)\n`);
|
|
180
|
+
writeFileSync(join(dir, 'history.jsonl'), '');
|
|
181
|
+
writeFileSync(join(dir, 'log.md'), `# ${project} — Agent Log\n\n## COMPRESSED CONTEXT\n\n(No compressed context yet.)\n\n## MESSAGE LOG\n\n(Agents append messages below this line.)\n`);
|
|
136
182
|
writeFileSync(join(dir, 'HUMAN_TASKS.md'), '# Human Tasks\n\n(Agents append tasks here when they need human action.)\n');
|
|
137
183
|
|
|
184
|
+
const agentCount = Object.keys(agents).length;
|
|
138
185
|
console.log('');
|
|
139
186
|
console.log(chalk.green(` ✓ Created ${chalk.bold(folderName)}/`));
|
|
140
187
|
console.log('');
|
|
141
|
-
console.log(` ${chalk.dim('├──')} ${
|
|
142
|
-
console.log(` ${chalk.dim('├──')}
|
|
143
|
-
console.log(` ${chalk.dim('├──')}
|
|
144
|
-
console.log(` ${chalk.dim('├──')}
|
|
188
|
+
console.log(` ${chalk.dim('├──')} agentxchain.json ${chalk.dim(`(${agentCount} agents)`)}`);
|
|
189
|
+
console.log(` ${chalk.dim('├──')} lock.json`);
|
|
190
|
+
console.log(` ${chalk.dim('├──')} state.json`);
|
|
191
|
+
console.log(` ${chalk.dim('├──')} state.md`);
|
|
192
|
+
console.log(` ${chalk.dim('├──')} history.jsonl`);
|
|
193
|
+
console.log(` ${chalk.dim('├──')} log.md`);
|
|
145
194
|
console.log(` ${chalk.dim('└──')} HUMAN_TASKS.md`);
|
|
146
195
|
console.log('');
|
|
147
196
|
console.log(` ${chalk.dim('Agents:')} ${Object.keys(agents).join(', ')}`);
|
|
148
197
|
console.log('');
|
|
149
|
-
console.log(` ${chalk.cyan('Next:')}
|
|
198
|
+
console.log(` ${chalk.cyan('Next:')}`);
|
|
199
|
+
console.log(` ${chalk.bold(`cd ${folderName}`)}`);
|
|
200
|
+
console.log(` ${chalk.bold('agentxchain watch')} ${chalk.dim('# start the referee')}`);
|
|
201
|
+
console.log(` ${chalk.bold('agentxchain start')} ${chalk.dim('# launch agents in your IDE')}`);
|
|
150
202
|
console.log('');
|
|
151
203
|
}
|