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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "CLI for AgentXchain — multi-agent coordination in your IDE",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
- console.log('');
11
- console.log(chalk.yellow(' Cursor Cloud Agents API key not found.'));
12
- console.log('');
13
- console.log(' To launch agents via Cursor Cloud API:');
14
- console.log(` 1. Go to ${chalk.cyan('cursor.com/dashboard')} → Cloud Agents`);
15
- console.log(' 2. Create an API key');
16
- console.log(` 3. Set: ${chalk.bold('export CURSOR_API_KEY=your_key')}`);
17
- console.log(` 4. Run: ${chalk.bold('agentxchain start --ide cursor')}`);
18
- console.log('');
19
- console.log(chalk.dim(' Falling back to seed prompt output...'));
20
- console.log('');
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
- 'Authorization': `Basic ${btoa(apiKey + ':')}`,
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 body = await res.text();
46
- console.log(chalk.red(` Failed to launch ${id}: ${res.status} ${body}`));
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({ id, name: agent.name, cloudId: data.id || 'unknown' });
52
- console.log(chalk.green(` ✓ Launched ${chalk.bold(id)} (${agent.name}) — cloud ID: ${data.id || '?'}`));
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(` Failed to launch ${id}: ${err.message}`));
74
+ console.log(chalk.red(` ${id}: ${err.message}`));
55
75
  }
56
76
  }
57
77
 
58
78
  if (launched.length > 0) {
59
- const sessionFile = JSON.stringify({ launched, started_at: new Date().toISOString(), ide: 'cursor' }, null, 2);
60
- const { writeFileSync } = await import('fs');
61
- const { join } = await import('path');
62
- writeFileSync(join(root, '.agentxchain-session.json'), sessionFile + '\n');
63
- console.log('');
64
- console.log(chalk.dim(` Session saved to .agentxchain-session.json`));
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 filterAgents(config, specificId) {
90
- if (specificId) {
91
- if (!config.agents[specificId]) {
92
- console.log(chalk.red(` Agent "${specificId}" not found in agentxchain.json`));
93
- process.exit(1);
94
- }
95
- return { [specificId]: config.agents[specificId] };
96
- }
97
- return config.agents;
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
+ }
@@ -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
  }
@@ -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
- .toLowerCase()
29
- .replace(/[^a-z0-9]+/g, '-')
30
- .replace(/^-|-$/g, '');
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 nameAnswer = await inquirer.prompt([{
42
- type: 'input',
43
- name: 'project',
44
- message: 'Project name:',
45
- default: 'My AgentXchain project'
46
- }]);
47
-
48
- project = nameAnswer.project;
49
- folderName = slugify(project);
50
-
51
- const folderAnswer = await inquirer.prompt([{
52
- type: 'input',
53
- name: 'folder',
54
- message: 'Folder name:',
55
- default: folderName
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 (agentAnswer.useDefaults) {
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
- { type: 'input', name: 'id', message: 'Agent ID (lowercase, no spaces):' },
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 (existsSync(join(dir, CONFIG_FILE))) {
89
- if (!opts.yes) {
90
- const { overwrite } = await inquirer.prompt([{
91
- type: 'confirm',
92
- name: 'overwrite',
93
- message: `${folderName}/ already has an agentxchain.json. Overwrite?`,
94
- default: false
95
- }]);
96
- if (!overwrite) {
97
- console.log(chalk.yellow(' Aborted.'));
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: true,
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
- holder: null,
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, STATE_FILE), JSON.stringify(state, null, 2) + '\n');
135
- writeFileSync(join(dir, config.log), `# ${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`);
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('├──')} ${CONFIG_FILE}`);
142
- console.log(` ${chalk.dim('├──')} ${LOCK_FILE}`);
143
- console.log(` ${chalk.dim('├──')} ${STATE_FILE}`);
144
- console.log(` ${chalk.dim('├──')} ${config.log}`);
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:')} ${chalk.bold(`cd ${folderName}`)} && ${chalk.bold('agentxchain start')}`);
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
  }