agent-squad-cli 1.0.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.
@@ -0,0 +1,505 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * agent-squad — Manage teams of AI agents
4
+ *
5
+ * Usage: agent-squad <command> [options]
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const { execSync } = require('child_process');
11
+
12
+ const SQUAD_DIR = path.join(process.env.HOME, '.config/agent-squad');
13
+ const CONFIG_FILE = path.join(SQUAD_DIR, 'config.json');
14
+
15
+ // Ensure squad directory exists
16
+ function ensureSquadDir() {
17
+ if (!fs.existsSync(SQUAD_DIR)) {
18
+ fs.mkdirSync(SQUAD_DIR, { recursive: true });
19
+ }
20
+ }
21
+
22
+ // Load or create config
23
+ function loadConfig() {
24
+ ensureSquadDir();
25
+ if (fs.existsSync(CONFIG_FILE)) {
26
+ return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
27
+ }
28
+ return { squads: [], activeSquad: null };
29
+ }
30
+
31
+ function saveConfig(config) {
32
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
33
+ }
34
+
35
+ // Get squad path
36
+ function getSquadPath(name) {
37
+ return path.join(SQUAD_DIR, name);
38
+ }
39
+
40
+ // Initialize new squad
41
+ function initSquad(name) {
42
+ const squadPath = getSquadPath(name);
43
+
44
+ if (fs.existsSync(squadPath)) {
45
+ console.error(`Squad "${name}" already exists.`);
46
+ process.exit(1);
47
+ }
48
+
49
+ fs.mkdirSync(squadPath, { recursive: true });
50
+ fs.mkdirSync(path.join(squadPath, 'agents'), { recursive: true });
51
+
52
+ const squadConfig = {
53
+ name,
54
+ created: new Date().toISOString(),
55
+ taskSystem: null,
56
+ taskConfig: {}
57
+ };
58
+
59
+ fs.writeFileSync(
60
+ path.join(squadPath, 'squad.json'),
61
+ JSON.stringify(squadConfig, null, 2)
62
+ );
63
+
64
+ // Create default AGENTS.md
65
+ const agentsMd = `# AGENTS.md — ${name} Squad Operations
66
+
67
+ ## Mission
68
+
69
+ [Define your squad's mission here]
70
+
71
+ ## Communication Rules
72
+
73
+ 1. All task discussion happens in the task system (Linear/Trello/etc)
74
+ 2. Use @mentions to notify specific agents
75
+ 3. When research is ready, @mention the writer
76
+ 4. When draft is ready, @mention the lead
77
+ 5. Lead reports to human when deliverables are ready
78
+
79
+ ## File Conventions
80
+
81
+ - Research notes: \`research/YYYY-MM-DD-topic.md\`
82
+ - Drafts: \`drafts/YYYY-MM-DD-topic.md\`
83
+ - Published: \`published/YYYY-MM-DD-topic.md\`
84
+
85
+ ## When to Escalate to Human
86
+
87
+ - Missing critical information
88
+ - Conflicting research findings
89
+ - Task blocked >24 hours
90
+ - Unclear requirements
91
+ `;
92
+
93
+ fs.writeFileSync(path.join(squadPath, 'AGENTS.md'), agentsMd);
94
+
95
+ // Update global config
96
+ const config = loadConfig();
97
+ config.squads.push(name);
98
+ config.activeSquad = name;
99
+ saveConfig(config);
100
+
101
+ console.log(`✅ Squad "${name}" initialized`);
102
+ console.log(` Location: ${squadPath}`);
103
+ console.log(`\nNext steps:`);
104
+ console.log(` agent-squad add <agent-name> --role "Description"`);
105
+ console.log(` agent-squad config --tasks linear`);
106
+ }
107
+
108
+ // Add agent to squad
109
+ function addAgent(name, options) {
110
+ const config = loadConfig();
111
+ const squadName = config.activeSquad;
112
+
113
+ if (!squadName) {
114
+ console.error('No active squad. Run: agent-squad init <name>');
115
+ process.exit(1);
116
+ }
117
+
118
+ const squadPath = getSquadPath(squadName);
119
+ const agentPath = path.join(squadPath, 'agents', name);
120
+
121
+ if (fs.existsSync(agentPath)) {
122
+ console.error(`Agent "${name}" already exists in squad.`);
123
+ process.exit(1);
124
+ }
125
+
126
+ fs.mkdirSync(agentPath, { recursive: true });
127
+
128
+ const role = options.role || 'General purpose agent';
129
+ const personality = options.personality || 'balanced';
130
+ const schedule = options.schedule || '*/15 * * * *';
131
+ const model = options.model || 'kimi-code';
132
+
133
+ // Create SOUL.md
134
+ const soulMd = generateSOUL(name, role, personality);
135
+ fs.writeFileSync(path.join(agentPath, 'SOUL.md'), soulMd);
136
+
137
+ // Copy AGENTS.md
138
+ fs.copyFileSync(
139
+ path.join(squadPath, 'AGENTS.md'),
140
+ path.join(agentPath, 'AGENTS.md')
141
+ );
142
+
143
+ // Create agent config
144
+ const agentConfig = {
145
+ name,
146
+ role,
147
+ personality,
148
+ schedule,
149
+ model,
150
+ sessionKey: `agent:${name}:main`,
151
+ enabled: false
152
+ };
153
+
154
+ fs.writeFileSync(
155
+ path.join(agentPath, 'config.json'),
156
+ JSON.stringify(agentConfig, null, 2)
157
+ );
158
+
159
+ console.log(`✅ Agent "${name}" added to squad "${squadName}"`);
160
+ console.log(` Session: ${agentConfig.sessionKey}`);
161
+ console.log(` Schedule: ${schedule}`);
162
+ console.log(`\nEdit personality: agent-squad edit ${name}`);
163
+ console.log(`Start heartbeats: agent-squad start`);
164
+ }
165
+
166
+ // Generate SOUL.md content
167
+ function generateSOUL(name, role, personality) {
168
+ const personalities = {
169
+ skeptical: `Skeptical and thorough. Questions assumptions. Finds edge cases.`,
170
+ creative: `Creative and exploratory. Generates novel ideas. Thinks outside the box.`,
171
+ analytical: `Analytical and precise. Data-driven. Focuses on accuracy.`,
172
+ balanced: `Balanced and practical. Adapts to the task at hand.`
173
+ };
174
+
175
+ return `# SOUL.md — Who You Are
176
+
177
+ **Name:** ${name}
178
+ **Role:** ${role}
179
+
180
+ ## Personality
181
+ ${personalities[personality] || personalities.balanced}
182
+
183
+ ## What You're Good At
184
+ - [Add specific strengths]
185
+ - [Add domain expertise]
186
+ - [Add preferred work style]
187
+
188
+ ## What You Care About
189
+ - [Add values and priorities]
190
+ - [Add pet peeves or preferences]
191
+
192
+ ## Your Tools
193
+ - Read the SKILL.md files for available tools
194
+ - Use tools appropriate to your role
195
+
196
+ ## How You Work
197
+ 1. Read AGENTS.md on every heartbeat
198
+ 2. Check task system for assigned work
199
+ 3. Do your work thoroughly
200
+ 4. Post updates and @mention relevant agents
201
+ 5. Report HEARTBEAT_OK if nothing to do
202
+
203
+ ## Squad Context
204
+ You are part of a multi-agent squad. Coordinate via the shared task system.
205
+ Follow the communication rules in AGENTS.md.
206
+ `;
207
+ }
208
+
209
+ // Edit agent SOUL.md
210
+ function editAgent(name) {
211
+ const config = loadConfig();
212
+ const squadName = config.activeSquad;
213
+ const editor = process.env.EDITOR || 'nano';
214
+
215
+ const soulPath = path.join(getSquadPath(squadName), 'agents', name, 'SOUL.md');
216
+
217
+ if (!fs.existsSync(soulPath)) {
218
+ console.error(`Agent "${name}" not found.`);
219
+ process.exit(1);
220
+ }
221
+
222
+ execSync(`${editor} "${soulPath}"`, { stdio: 'inherit' });
223
+ }
224
+
225
+ // Configure task system
226
+ function configTaskSystem(system, options) {
227
+ const config = loadConfig();
228
+ const squadName = config.activeSquad;
229
+
230
+ if (!squadName) {
231
+ console.error('No active squad. Run: agent-squad init <name>');
232
+ process.exit(1);
233
+ }
234
+
235
+ const squadPath = getSquadPath(squadName);
236
+ const squadConfig = JSON.parse(fs.readFileSync(path.join(squadPath, 'squad.json'), 'utf8'));
237
+
238
+ squadConfig.taskSystem = system;
239
+ squadConfig.taskConfig = options;
240
+
241
+ fs.writeFileSync(
242
+ path.join(squadPath, 'squad.json'),
243
+ JSON.stringify(squadConfig, null, 2)
244
+ );
245
+
246
+ console.log(`✅ Task system set to: ${system}`);
247
+
248
+ if (system === 'linear') {
249
+ console.log('\nMake sure you have:');
250
+ console.log(' - linear skill installed');
251
+ console.log(' - Linear API key configured');
252
+ console.log('\nUsage in SOUL.md:');
253
+ console.log(' linear issues --team "Your Team" | grep "Title"');
254
+ console.log(' linear issue:create --title "Task" --description "Details"');
255
+ } else if (system === 'trello') {
256
+ console.log('\nMake sure you have:');
257
+ console.log(' - trello skill installed');
258
+ console.log(' - Trello API key/token configured');
259
+ console.log('\nUsage in SOUL.md:');
260
+ console.log(' trello boards');
261
+ console.log(' trello board --board-id <id>');
262
+ console.log(' trello card:create --name "Task" --list-id <id>');
263
+ } else if (system === 'github') {
264
+ console.log('\nMake sure you have:');
265
+ console.log(' - github skill installed');
266
+ console.log(' - GitHub token configured');
267
+ console.log('\nUsage in SOUL.md:');
268
+ console.log(' github issue list --repo owner/repo');
269
+ console.log(' github issue:create --title "Task" --body "Details"');
270
+ } else if (system === 'file') {
271
+ console.log('\nFile-based task system:');
272
+ console.log(' Tasks stored in ~/.config/agent-squad/<squad>/tasks/');
273
+ console.log(' Markdown files with YAML frontmatter');
274
+ }
275
+ }
276
+
277
+ // Start all heartbeats
278
+ function startHeartbeats() {
279
+ const config = loadConfig();
280
+ const squadName = config.activeSquad;
281
+
282
+ if (!squadName) {
283
+ console.error('No active squad.');
284
+ process.exit(1);
285
+ }
286
+
287
+ const squadPath = getSquadPath(squadName);
288
+ const squadConfig = JSON.parse(fs.readFileSync(path.join(squadPath, 'squad.json'), 'utf8'));
289
+ const agentsPath = path.join(squadPath, 'agents');
290
+
291
+ if (!fs.existsSync(agentsPath)) {
292
+ console.error('No agents configured.');
293
+ process.exit(1);
294
+ }
295
+
296
+ const agents = fs.readdirSync(agentsPath).filter(f => {
297
+ return fs.statSync(path.join(agentsPath, f)).isDirectory();
298
+ });
299
+
300
+ console.log(`Starting heartbeats for ${agents.length} agents...\n`);
301
+
302
+ // Build task-system-specific guidance
303
+ let taskSystemGuidance = '';
304
+ if (squadConfig.taskSystem === 'linear') {
305
+ taskSystemGuidance = 'Use "linear issues" to check tasks. Post comments via Linear. Use labels for status.';
306
+ } else if (squadConfig.taskSystem === 'trello') {
307
+ taskSystemGuidance = 'Use "trello boards" to find boards. Check cards in lists. Post updates as comments. Move cards between lists for status.';
308
+ } else if (squadConfig.taskSystem === 'github') {
309
+ taskSystemGuidance = 'Use "github issue list" to check tasks. Post comments on issues. Use labels for status.';
310
+ } else {
311
+ taskSystemGuidance = 'Check the configured task system for assigned work.';
312
+ }
313
+
314
+ for (const agent of agents) {
315
+ const agentConfig = JSON.parse(fs.readFileSync(
316
+ path.join(agentsPath, agent, 'config.json'),
317
+ 'utf8'
318
+ ));
319
+
320
+ if (agentConfig.enabled) {
321
+ console.log(` ⏭️ ${agent} (already enabled)`);
322
+ continue;
323
+ }
324
+
325
+ // Build heartbeat message
326
+ const heartbeatMsg = `You are ${agentConfig.name}, ${agentConfig.role}. Read AGENTS.md and SOUL.md. ${taskSystemGuidance} Check for assigned work, @mentions, and activity. Do your work and post updates. If nothing needs attention, reply HEARTBEAT_OK.`;
327
+
328
+ // Add cron job via openclaw
329
+ try {
330
+ execSync(`openclaw cron add \\
331
+ --name "${squadName}-${agent}-heartbeat" \\
332
+ --cron "${agentConfig.schedule}" \\
333
+ --session "isolated" \\
334
+ --message "${heartbeatMsg}"`, { stdio: 'pipe' });
335
+
336
+ agentConfig.enabled = true;
337
+ fs.writeFileSync(
338
+ path.join(agentsPath, agent, 'config.json'),
339
+ JSON.stringify(agentConfig, null, 2)
340
+ );
341
+
342
+ console.log(` ✅ ${agent} — ${agentConfig.schedule}`);
343
+ } catch (err) {
344
+ console.log(` ❌ ${agent} — failed to add cron`);
345
+ }
346
+ }
347
+
348
+ console.log('\nHeartbeat cron jobs added.');
349
+ console.log('Agents will wake according to their schedules.');
350
+ }
351
+
352
+ // Stop all heartbeats
353
+ function stopHeartbeats() {
354
+ const config = loadConfig();
355
+ const squadName = config.activeSquad;
356
+
357
+ if (!squadName) {
358
+ console.error('No active squad.');
359
+ process.exit(1);
360
+ }
361
+
362
+ const squadPath = getSquadPath(squadName);
363
+ const agentsPath = path.join(squadPath, 'agents');
364
+
365
+ if (!fs.existsSync(agentsPath)) {
366
+ console.log('No agents configured.');
367
+ return;
368
+ }
369
+
370
+ const agents = fs.readdirSync(agentsPath).filter(f => {
371
+ return fs.statSync(path.join(agentsPath, f)).isDirectory();
372
+ });
373
+
374
+ console.log(`Stopping heartbeats for ${agents.length} agents...\n`);
375
+
376
+ for (const agent of agents) {
377
+ const agentConfigPath = path.join(agentsPath, agent, 'config.json');
378
+ const agentConfig = JSON.parse(fs.readFileSync(agentConfigPath, 'utf8'));
379
+
380
+ if (!agentConfig.enabled) {
381
+ console.log(` ⏭️ ${agent} (already disabled)`);
382
+ continue;
383
+ }
384
+
385
+ try {
386
+ execSync(`openclaw cron remove --name "${squadName}-${agent}-heartbeat"`, { stdio: 'pipe' });
387
+
388
+ agentConfig.enabled = false;
389
+ fs.writeFileSync(agentConfigPath, JSON.stringify(agentConfig, null, 2));
390
+
391
+ console.log(` ✅ ${agent} — stopped`);
392
+ } catch (err) {
393
+ console.log(` ❌ ${agent} — failed to remove cron`);
394
+ }
395
+ }
396
+
397
+ console.log('\nHeartbeat cron jobs removed.');
398
+ }
399
+
400
+ // Show squad status
401
+ function showStatus() {
402
+ const config = loadConfig();
403
+
404
+ console.log('Agent Squad Status\n');
405
+ console.log(`Active Squad: ${config.activeSquad || '(none)'}`);
406
+ console.log(`All Squads: ${config.squads.join(', ') || '(none)'}`);
407
+
408
+ if (config.activeSquad) {
409
+ const squadPath = getSquadPath(config.activeSquad);
410
+ const squadConfig = JSON.parse(fs.readFileSync(
411
+ path.join(squadPath, 'squad.json'),
412
+ 'utf8'
413
+ ));
414
+
415
+ console.log(`\nTask System: ${squadConfig.taskSystem || '(not configured)'}`);
416
+
417
+ const agentsPath = path.join(squadPath, 'agents');
418
+ if (fs.existsSync(agentsPath)) {
419
+ const agents = fs.readdirSync(agentsPath).filter(f => {
420
+ return fs.statSync(path.join(agentsPath, f)).isDirectory();
421
+ });
422
+
423
+ console.log(`\nAgents (${agents.length}):`);
424
+ for (const agent of agents) {
425
+ const agentConfig = JSON.parse(fs.readFileSync(
426
+ path.join(agentsPath, agent, 'config.json'),
427
+ 'utf8'
428
+ ));
429
+ const status = agentConfig.enabled ? '🟢' : '⚪';
430
+ console.log(` ${status} ${agent} — ${agentConfig.role} (${agentConfig.schedule})`);
431
+ }
432
+ }
433
+ }
434
+ }
435
+
436
+ // CLI argument parsing
437
+ function parseArgs(args) {
438
+ const options = {};
439
+ const positional = [];
440
+
441
+ for (let i = 0; i < args.length; i++) {
442
+ if (args[i].startsWith('--')) {
443
+ const key = args[i].slice(2);
444
+ if (i + 1 < args.length && !args[i + 1].startsWith('--')) {
445
+ options[key] = args[i + 1];
446
+ i++;
447
+ } else {
448
+ options[key] = true;
449
+ }
450
+ } else {
451
+ positional.push(args[i]);
452
+ }
453
+ }
454
+
455
+ return { command: positional[0], args: positional.slice(1), options };
456
+ }
457
+
458
+ // Main
459
+ function main() {
460
+ const { command, args, options } = parseArgs(process.argv.slice(2));
461
+
462
+ switch (command) {
463
+ case 'init':
464
+ initSquad(args[0]);
465
+ break;
466
+ case 'add':
467
+ addAgent(args[0], options);
468
+ break;
469
+ case 'edit':
470
+ editAgent(args[0]);
471
+ break;
472
+ case 'config':
473
+ configTaskSystem(options.tasks, options);
474
+ break;
475
+ case 'start':
476
+ startHeartbeats();
477
+ break;
478
+ case 'stop':
479
+ stopHeartbeats();
480
+ break;
481
+ case 'status':
482
+ showStatus();
483
+ break;
484
+ default:
485
+ console.log('Agent Squad — Multi-agent orchestration for OpenClaw\n');
486
+ console.log('Commands:');
487
+ console.log(' init <name> Create new squad');
488
+ console.log(' add <name> [options] Add agent to squad');
489
+ console.log(' --role "Description"');
490
+ console.log(' --personality skeptical|creative|analytical');
491
+ console.log(' --schedule "*/15 * * * *"');
492
+ console.log(' edit <name> Edit agent SOUL.md');
493
+ console.log(' config --tasks <system> Configure task system');
494
+ console.log(' start Start all heartbeats');
495
+ console.log(' stop Stop all heartbeats');
496
+ console.log(' status Show squad status');
497
+ console.log('\nExample:');
498
+ console.log(' agent-squad init content-squad');
499
+ console.log(' agent-squad add researcher --role "Deep researcher"');
500
+ console.log(' agent-squad config --tasks linear');
501
+ console.log(' agent-squad start');
502
+ }
503
+ }
504
+
505
+ main();