swarmroom 0.1.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.
Files changed (48) hide show
  1. package/bin/swarmroom.js +2 -0
  2. package/dist/commands/agents.d.ts +2 -0
  3. package/dist/commands/agents.js +71 -0
  4. package/dist/commands/daemon.d.ts +2 -0
  5. package/dist/commands/daemon.js +302 -0
  6. package/dist/commands/setup.d.ts +2 -0
  7. package/dist/commands/setup.js +165 -0
  8. package/dist/commands/start.d.ts +2 -0
  9. package/dist/commands/start.js +134 -0
  10. package/dist/commands/status.d.ts +2 -0
  11. package/dist/commands/status.js +58 -0
  12. package/dist/configurators/__tests__/configurators.test.d.ts +1 -0
  13. package/dist/configurators/__tests__/configurators.test.js +102 -0
  14. package/dist/configurators/claude-code.d.ts +5 -0
  15. package/dist/configurators/claude-code.js +60 -0
  16. package/dist/configurators/gemini-cli.d.ts +5 -0
  17. package/dist/configurators/gemini-cli.js +61 -0
  18. package/dist/configurators/index.d.ts +18 -0
  19. package/dist/configurators/index.js +21 -0
  20. package/dist/configurators/opencode.d.ts +5 -0
  21. package/dist/configurators/opencode.js +60 -0
  22. package/dist/daemon/__tests__/config.test.d.ts +1 -0
  23. package/dist/daemon/__tests__/config.test.js +125 -0
  24. package/dist/daemon/__tests__/process-detector.test.d.ts +1 -0
  25. package/dist/daemon/__tests__/process-detector.test.js +77 -0
  26. package/dist/daemon/__tests__/watcher.test.d.ts +1 -0
  27. package/dist/daemon/__tests__/watcher.test.js +305 -0
  28. package/dist/daemon/config.d.ts +26 -0
  29. package/dist/daemon/config.js +89 -0
  30. package/dist/daemon/process-detector.d.ts +21 -0
  31. package/dist/daemon/process-detector.js +59 -0
  32. package/dist/daemon/watcher.d.ts +38 -0
  33. package/dist/daemon/watcher.js +225 -0
  34. package/dist/detectors/__tests__/detectors.test.d.ts +1 -0
  35. package/dist/detectors/__tests__/detectors.test.js +105 -0
  36. package/dist/detectors/claude-code.d.ts +7 -0
  37. package/dist/detectors/claude-code.js +39 -0
  38. package/dist/detectors/gemini-cli.d.ts +6 -0
  39. package/dist/detectors/gemini-cli.js +27 -0
  40. package/dist/detectors/index.d.ts +15 -0
  41. package/dist/detectors/index.js +12 -0
  42. package/dist/detectors/opencode.d.ts +6 -0
  43. package/dist/detectors/opencode.js +26 -0
  44. package/dist/index.d.ts +1 -0
  45. package/dist/index.js +17 -0
  46. package/dist/utils/display.d.ts +10 -0
  47. package/dist/utils/display.js +78 -0
  48. package/package.json +38 -0
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../dist/index.js';
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function makeAgentsCommand(): Command;
@@ -0,0 +1,71 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import { DEFAULT_PORT } from '@swarmroom/shared';
5
+ import { banner, table, error, info, statusIndicator, } from '../utils/display.js';
6
+ async function resolveHubUrl(providedUrl) {
7
+ if (providedUrl)
8
+ return providedUrl;
9
+ try {
10
+ const { discoverHub } = await import('@swarmroom/sdk');
11
+ const discovered = await discoverHub(3000);
12
+ if (discovered)
13
+ return discovered;
14
+ }
15
+ catch {
16
+ }
17
+ return `http://localhost:${DEFAULT_PORT}`;
18
+ }
19
+ export function makeAgentsCommand() {
20
+ const cmd = new Command('agents')
21
+ .description('List all registered agents')
22
+ .option('--hub-url <url>', 'Hub URL (or discover via mDNS)')
23
+ .action(async (options) => {
24
+ banner('SwarmRoom Agents');
25
+ const hubUrl = await resolveHubUrl(options.hubUrl);
26
+ const spinner = ora(`Fetching agents from ${chalk.cyan(hubUrl)}...`).start();
27
+ try {
28
+ const response = await fetch(`${hubUrl}/api/agents`);
29
+ if (!response.ok) {
30
+ spinner.fail(`Hub returned HTTP ${response.status}`);
31
+ return;
32
+ }
33
+ const result = (await response.json());
34
+ const agents = result.data.agents;
35
+ spinner.stop();
36
+ if (agents.length === 0) {
37
+ console.log('');
38
+ info('No agents registered yet.');
39
+ console.log('');
40
+ console.log(chalk.gray(' Tip: Connect an agent using the SDK or configure one with ') +
41
+ chalk.cyan('swarmroom setup'));
42
+ console.log('');
43
+ return;
44
+ }
45
+ console.log('');
46
+ info(`${agents.length} agent(s) registered`);
47
+ console.log('');
48
+ const headers = ['Name', 'Status', 'Teams', 'ID'];
49
+ const rows = agents.map((agent) => {
50
+ const name = agent.displayName ?? agent.name;
51
+ const status = statusIndicator(agent.status);
52
+ const teams = agent.agentCard?.teams?.join(', ') ?? chalk.gray('none');
53
+ const id = chalk.gray(agent.id.slice(0, 8) + '…');
54
+ return [name, status, teams, id];
55
+ });
56
+ table(headers, rows);
57
+ console.log('');
58
+ }
59
+ catch {
60
+ spinner.fail(`Cannot connect to Hub at ${chalk.cyan(hubUrl)}`);
61
+ console.log('');
62
+ error('Hub is offline or unreachable.');
63
+ console.log('');
64
+ console.log(chalk.gray(' Tip: Start the hub with ') +
65
+ chalk.cyan('npm run dev -w packages/server') +
66
+ chalk.gray(' or check the URL.'));
67
+ console.log('');
68
+ }
69
+ });
70
+ return cmd;
71
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function makeDaemonCommand(): Command;
@@ -0,0 +1,302 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import inquirer from 'inquirer';
4
+ import { spawn } from 'node:child_process';
5
+ import { readFileSync, writeFileSync, unlinkSync, existsSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+ import { homedir } from 'node:os';
8
+ import { DaemonWatcher } from '../daemon/watcher.js';
9
+ import { loadDaemonConfig, saveDaemonConfig, getDefaultConfig, } from '../daemon/config.js';
10
+ import { banner, keyValue, info, success, error, warn, table, } from '../utils/display.js';
11
+ const PID_FILE = join(homedir(), '.swarmroom', 'daemon.pid');
12
+ function makeStartCommand() {
13
+ return new Command('start')
14
+ .description('Start the SwarmRoom daemon')
15
+ .option('--hub-url <url>', 'Override hub URL')
16
+ .option('--verbose', 'Enable verbose logging')
17
+ .option('--background', 'Run as detached background process')
18
+ .action(async (options) => {
19
+ banner('SwarmRoom Daemon');
20
+ const config = loadDaemonConfig();
21
+ const hubUrl = options.hubUrl ?? config.hubUrl;
22
+ if (options.background) {
23
+ const args = [process.argv[1], 'daemon', 'start', '--hub-url', hubUrl];
24
+ if (options.verbose) {
25
+ args.push('--verbose');
26
+ }
27
+ const child = spawn(process.argv[0], args, {
28
+ detached: true,
29
+ stdio: 'ignore',
30
+ });
31
+ child.unref();
32
+ const pid = child.pid;
33
+ if (pid) {
34
+ const pidDir = join(homedir(), '.swarmroom');
35
+ if (!existsSync(pidDir)) {
36
+ const { mkdirSync } = await import('node:fs');
37
+ mkdirSync(pidDir, { recursive: true });
38
+ }
39
+ writeFileSync(PID_FILE, String(pid), 'utf-8');
40
+ success(`Daemon started in background (PID: ${chalk.bold(String(pid))})`);
41
+ keyValue('Hub URL', chalk.cyan(hubUrl));
42
+ keyValue('PID file', chalk.gray(PID_FILE));
43
+ }
44
+ else {
45
+ error('Failed to start daemon process.');
46
+ }
47
+ return;
48
+ }
49
+ info(`Starting daemon in foreground...`);
50
+ keyValue('Hub URL', chalk.cyan(hubUrl));
51
+ console.log('');
52
+ const watcher = new DaemonWatcher({
53
+ hubUrl,
54
+ verbose: options.verbose ?? false,
55
+ workdir: process.cwd(),
56
+ });
57
+ watcher.start();
58
+ const shutdown = () => {
59
+ console.log('');
60
+ info('Shutting down daemon...');
61
+ watcher.stop();
62
+ process.exit(0);
63
+ };
64
+ process.on('SIGINT', shutdown);
65
+ process.on('SIGTERM', shutdown);
66
+ });
67
+ }
68
+ function makeStopCommand() {
69
+ return new Command('stop')
70
+ .description('Stop the SwarmRoom daemon')
71
+ .action(() => {
72
+ banner('SwarmRoom Daemon');
73
+ if (!existsSync(PID_FILE)) {
74
+ error('No daemon PID file found. Daemon may not be running.');
75
+ return;
76
+ }
77
+ const pidStr = readFileSync(PID_FILE, 'utf-8').trim();
78
+ const pid = parseInt(pidStr, 10);
79
+ if (isNaN(pid)) {
80
+ error(`Invalid PID in ${PID_FILE}: "${pidStr}"`);
81
+ unlinkSync(PID_FILE);
82
+ return;
83
+ }
84
+ try {
85
+ process.kill(pid, 'SIGTERM');
86
+ unlinkSync(PID_FILE);
87
+ success(`Daemon stopped (PID: ${chalk.bold(String(pid))})`);
88
+ }
89
+ catch (err) {
90
+ const code = err.code;
91
+ if (code === 'ESRCH') {
92
+ warn('Daemon process not found (may have already stopped).');
93
+ unlinkSync(PID_FILE);
94
+ }
95
+ else {
96
+ error(`Failed to stop daemon (PID: ${pid}): ${err.message}`);
97
+ }
98
+ }
99
+ });
100
+ }
101
+ function makeStatusSubcommand() {
102
+ return new Command('status')
103
+ .description('Show daemon status')
104
+ .action(() => {
105
+ banner('SwarmRoom Daemon');
106
+ let daemonRunning = false;
107
+ let daemonPid = null;
108
+ if (existsSync(PID_FILE)) {
109
+ const pidStr = readFileSync(PID_FILE, 'utf-8').trim();
110
+ const pid = parseInt(pidStr, 10);
111
+ if (!isNaN(pid)) {
112
+ try {
113
+ process.kill(pid, 0);
114
+ daemonRunning = true;
115
+ daemonPid = pid;
116
+ }
117
+ catch {
118
+ }
119
+ }
120
+ }
121
+ const config = loadDaemonConfig();
122
+ keyValue('Status', daemonRunning
123
+ ? chalk.green('● running')
124
+ : chalk.gray('● stopped'));
125
+ if (daemonPid !== null) {
126
+ keyValue('PID', chalk.white(String(daemonPid)));
127
+ }
128
+ keyValue('Hub URL', chalk.cyan(config.hubUrl));
129
+ console.log('');
130
+ const agentNames = Object.keys(config.agents);
131
+ if (agentNames.length > 0) {
132
+ info('Agent wakeup configuration:');
133
+ console.log('');
134
+ const headers = ['Agent', 'Headless Wakeup', 'Command'];
135
+ const rows = agentNames.map((name) => {
136
+ const agent = config.agents[name];
137
+ const wakeup = agent.headlessWakeup
138
+ ? chalk.green('enabled')
139
+ : chalk.gray('disabled');
140
+ const cmd = chalk.white(`${agent.command} ${agent.args.join(' ')}`);
141
+ return [name, wakeup, cmd];
142
+ });
143
+ table(headers, rows);
144
+ }
145
+ else {
146
+ info('No agents configured.');
147
+ }
148
+ console.log('');
149
+ });
150
+ }
151
+ function makeConfigSubcommand() {
152
+ return new Command('config')
153
+ .description('Configure the daemon')
154
+ .action(async () => {
155
+ banner('SwarmRoom Daemon');
156
+ let running = true;
157
+ while (running) {
158
+ const config = loadDaemonConfig();
159
+ const { action } = await inquirer.prompt([
160
+ {
161
+ type: 'list',
162
+ name: 'action',
163
+ message: 'Daemon configuration:',
164
+ choices: [
165
+ { name: 'Show current config', value: 'show' },
166
+ { name: 'Toggle headless wakeup for an agent', value: 'toggle' },
167
+ { name: 'Set hub URL', value: 'hub-url' },
168
+ { name: 'Set workdir for an agent', value: 'workdir' },
169
+ { name: 'Reset to defaults', value: 'reset' },
170
+ { name: 'Exit', value: 'exit' },
171
+ ],
172
+ },
173
+ ]);
174
+ switch (action) {
175
+ case 'show': {
176
+ console.log('');
177
+ keyValue('Hub URL', chalk.cyan(config.hubUrl));
178
+ console.log('');
179
+ const agentNames = Object.keys(config.agents);
180
+ if (agentNames.length > 0) {
181
+ const headers = ['Agent', 'Headless Wakeup', 'Command', 'Workdir'];
182
+ const rows = agentNames.map((name) => {
183
+ const agent = config.agents[name];
184
+ const wakeup = agent.headlessWakeup
185
+ ? chalk.green('enabled')
186
+ : chalk.gray('disabled');
187
+ const cmd = `${agent.command} ${agent.args.join(' ')}`;
188
+ const workdir = agent.workdir ?? chalk.gray('(default)');
189
+ return [name, wakeup, cmd, workdir];
190
+ });
191
+ table(headers, rows);
192
+ }
193
+ else {
194
+ info('No agents configured.');
195
+ }
196
+ console.log('');
197
+ break;
198
+ }
199
+ case 'toggle': {
200
+ const agentNames = Object.keys(config.agents);
201
+ if (agentNames.length === 0) {
202
+ warn('No agents configured.');
203
+ break;
204
+ }
205
+ const { agent } = await inquirer.prompt([
206
+ {
207
+ type: 'list',
208
+ name: 'agent',
209
+ message: 'Select agent to toggle:',
210
+ choices: agentNames.map((name) => {
211
+ const agentConf = config.agents[name];
212
+ const status = agentConf.headlessWakeup
213
+ ? chalk.green('enabled')
214
+ : chalk.gray('disabled');
215
+ return { name: `${name} (${status})`, value: name };
216
+ }),
217
+ },
218
+ ]);
219
+ const agentConfig = config.agents[agent];
220
+ if (agentConfig) {
221
+ agentConfig.headlessWakeup = !agentConfig.headlessWakeup;
222
+ saveDaemonConfig(config);
223
+ const newStatus = agentConfig.headlessWakeup ? 'enabled' : 'disabled';
224
+ success(`Headless wakeup for ${chalk.bold(agent)} is now ${newStatus}.`);
225
+ }
226
+ break;
227
+ }
228
+ case 'hub-url': {
229
+ const { url } = await inquirer.prompt([
230
+ {
231
+ type: 'input',
232
+ name: 'url',
233
+ message: 'Enter new hub URL:',
234
+ default: config.hubUrl,
235
+ },
236
+ ]);
237
+ config.hubUrl = url;
238
+ saveDaemonConfig(config);
239
+ success(`Hub URL set to ${chalk.cyan(url)}`);
240
+ break;
241
+ }
242
+ case 'workdir': {
243
+ const agentNames = Object.keys(config.agents);
244
+ if (agentNames.length === 0) {
245
+ warn('No agents configured.');
246
+ break;
247
+ }
248
+ const { agent } = await inquirer.prompt([
249
+ {
250
+ type: 'list',
251
+ name: 'agent',
252
+ message: 'Select agent:',
253
+ choices: agentNames,
254
+ },
255
+ ]);
256
+ const { dir } = await inquirer.prompt([
257
+ {
258
+ type: 'input',
259
+ name: 'dir',
260
+ message: `Working directory for ${agent}:`,
261
+ default: config.agents[agent]?.workdir ?? process.cwd(),
262
+ },
263
+ ]);
264
+ const agentConfig = config.agents[agent];
265
+ if (agentConfig) {
266
+ agentConfig.workdir = dir;
267
+ saveDaemonConfig(config);
268
+ success(`Workdir for ${chalk.bold(agent)} set to ${chalk.cyan(dir)}`);
269
+ }
270
+ break;
271
+ }
272
+ case 'reset': {
273
+ const { confirm } = await inquirer.prompt([
274
+ {
275
+ type: 'confirm',
276
+ name: 'confirm',
277
+ message: 'Reset daemon config to defaults?',
278
+ default: false,
279
+ },
280
+ ]);
281
+ if (confirm) {
282
+ saveDaemonConfig(getDefaultConfig());
283
+ success('Config reset to defaults.');
284
+ }
285
+ break;
286
+ }
287
+ case 'exit':
288
+ running = false;
289
+ break;
290
+ }
291
+ }
292
+ });
293
+ }
294
+ export function makeDaemonCommand() {
295
+ const cmd = new Command('daemon')
296
+ .description('Manage the SwarmRoom daemon');
297
+ cmd.addCommand(makeStartCommand());
298
+ cmd.addCommand(makeStopCommand());
299
+ cmd.addCommand(makeStatusSubcommand());
300
+ cmd.addCommand(makeConfigSubcommand());
301
+ return cmd;
302
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function makeSetupCommand(): Command;
@@ -0,0 +1,165 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import inquirer from 'inquirer';
5
+ import { DEFAULT_PORT } from '@swarmroom/shared';
6
+ import { banner, successBox, info, warn, } from '../utils/display.js';
7
+ import { detectAllAgents } from '../detectors/index.js';
8
+ import { configureAgent } from '../configurators/index.js';
9
+ async function tryMdnsDiscovery() {
10
+ try {
11
+ const { discoverHub } = await import('@swarmroom/sdk');
12
+ return await discoverHub(5000);
13
+ }
14
+ catch {
15
+ return null;
16
+ }
17
+ }
18
+ async function testHubConnection(hubUrl) {
19
+ try {
20
+ const response = await fetch(`${hubUrl}/health`);
21
+ if (!response.ok)
22
+ return null;
23
+ return (await response.json());
24
+ }
25
+ catch {
26
+ return null;
27
+ }
28
+ }
29
+ export function makeSetupCommand() {
30
+ const cmd = new Command('setup')
31
+ .description('Interactive setup wizard for SwarmRoom')
32
+ .option('--hub-url <url>', 'Hub URL (skip mDNS discovery)')
33
+ .option('--dry-run', 'Show what would be configured without writing files')
34
+ .option('--yes', 'Skip confirmations (non-interactive)')
35
+ .action(async (options) => {
36
+ banner('SwarmRoom Setup', 'Configure your AI agents to collaborate');
37
+ const dryRun = options.dryRun ?? false;
38
+ const nonInteractive = options.yes ?? false;
39
+ if (dryRun) {
40
+ warn('Dry run mode — no files will be written');
41
+ console.log('');
42
+ }
43
+ let hubUrl = options.hubUrl ?? null;
44
+ if (!hubUrl) {
45
+ const mdnsSpinner = ora('Searching for SwarmRoom Hub via mDNS...').start();
46
+ hubUrl = await tryMdnsDiscovery();
47
+ if (hubUrl) {
48
+ mdnsSpinner.succeed(`Found Hub at ${chalk.cyan(hubUrl)}`);
49
+ }
50
+ else {
51
+ mdnsSpinner.info('No Hub found via mDNS');
52
+ if (nonInteractive) {
53
+ hubUrl = `http://localhost:${DEFAULT_PORT}`;
54
+ info(`Using default Hub URL: ${chalk.cyan(hubUrl)}`);
55
+ }
56
+ else {
57
+ const answers = await inquirer.prompt([
58
+ {
59
+ type: 'input',
60
+ name: 'hubUrl',
61
+ message: 'Enter Hub URL:',
62
+ default: `http://localhost:${DEFAULT_PORT}`,
63
+ },
64
+ ]);
65
+ hubUrl = answers.hubUrl;
66
+ }
67
+ }
68
+ }
69
+ const connectionSpinner = ora(`Testing connection to ${chalk.cyan(hubUrl)}...`).start();
70
+ const health = await testHubConnection(hubUrl);
71
+ if (health) {
72
+ connectionSpinner.succeed(`Hub is ${chalk.green('online')} (v${health.version}, ${health.agentCount} agents)`);
73
+ }
74
+ else {
75
+ connectionSpinner.warn(`Hub at ${chalk.cyan(hubUrl)} is ${chalk.red('offline')}`);
76
+ warn('Setup will continue, but some features may not work until the Hub is running.');
77
+ console.log('');
78
+ }
79
+ const detectSpinner = ora('Detecting installed AI agents...').start();
80
+ const agents = await detectAllAgents();
81
+ const installedAgents = agents.filter((a) => a.installed);
82
+ if (installedAgents.length > 0) {
83
+ detectSpinner.succeed(`Found ${installedAgents.length} installed agent(s)`);
84
+ }
85
+ else {
86
+ detectSpinner.info('No agents auto-detected');
87
+ }
88
+ console.log('');
89
+ for (const agent of agents) {
90
+ const status = agent.installed ? chalk.green('installed') : chalk.gray('not found');
91
+ const config = agent.configExists ? chalk.cyan(`config: ${agent.configPath}`) : chalk.gray('no config');
92
+ console.log(` ${agent.name}: ${status} | ${config}`);
93
+ }
94
+ console.log('');
95
+ let selectedAgents;
96
+ if (nonInteractive) {
97
+ selectedAgents = installedAgents.map((a) => a.name);
98
+ info(`Auto-selecting ${selectedAgents.length} installed agent(s)`);
99
+ }
100
+ else {
101
+ const answers = await inquirer.prompt([
102
+ {
103
+ type: 'checkbox',
104
+ name: 'agents',
105
+ message: 'Select agents to configure:',
106
+ choices: agents.map((a) => ({
107
+ name: `${a.name}${a.installed ? chalk.green(' (installed)') : ''}${a.configExists ? chalk.cyan(' (config exists)') : ''}`,
108
+ value: a.name,
109
+ checked: a.installed,
110
+ })),
111
+ },
112
+ ]);
113
+ selectedAgents = answers.agents;
114
+ }
115
+ if (selectedAgents.length === 0) {
116
+ info('No agents selected. You can run setup again later.');
117
+ return;
118
+ }
119
+ console.log('');
120
+ const agentMap = new Map();
121
+ for (const agent of agents) {
122
+ agentMap.set(agent.name, agent);
123
+ }
124
+ const configuredAgents = [];
125
+ const failedAgents = [];
126
+ for (const agentName of selectedAgents) {
127
+ const detection = agentMap.get(agentName);
128
+ if (!detection)
129
+ continue;
130
+ const configSpinner = ora(`Configuring ${chalk.bold(agentName)}...`).start();
131
+ const result = await configureAgent(agentName, hubUrl, detection.configPath, dryRun);
132
+ if (!result.success) {
133
+ configSpinner.fail(`${agentName}: ${chalk.red(result.error ?? 'unknown error')}`);
134
+ failedAgents.push(agentName);
135
+ continue;
136
+ }
137
+ if (dryRun) {
138
+ configSpinner.info(`${agentName}: ${chalk.yellow('dry run')} → ${chalk.dim(result.configPath)}`);
139
+ }
140
+ else {
141
+ const backupNote = result.backedUp ? ` (backup: ${chalk.dim(result.backupPath)})` : '';
142
+ configSpinner.succeed(`${agentName}: ${chalk.green('configured')} → ${chalk.dim(result.configPath)}${backupNote}`);
143
+ }
144
+ if (result.after) {
145
+ console.log(chalk.gray(result.after.split('\n').map((l) => ` ${l}`).join('\n')));
146
+ }
147
+ configuredAgents.push(agentName);
148
+ }
149
+ console.log('');
150
+ const summaryLines = [
151
+ `Hub: ${hubUrl}`,
152
+ `Agents configured: ${configuredAgents.length}`,
153
+ ...configuredAgents.map((a) => ` ${chalk.green('✔')} ${a}`),
154
+ ...failedAgents.map((a) => ` ${chalk.red('✘')} ${a}`),
155
+ ];
156
+ if (dryRun) {
157
+ summaryLines.push('', chalk.yellow('(dry run — no files were written)'));
158
+ }
159
+ successBox('Setup Complete', summaryLines);
160
+ if (!dryRun && configuredAgents.length > 0) {
161
+ info('Run ' + chalk.cyan('swarmroom status') + ' to verify your configuration.');
162
+ }
163
+ });
164
+ return cmd;
165
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function makeStartCommand(): Command;