@bradygaster/squad-cli 0.8.17-preview → 0.8.18

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/dist/cli-entry.js CHANGED
@@ -1,14 +1,4 @@
1
1
  #!/usr/bin/env node
2
- process.env.NODE_NO_WARNINGS = '1';
3
- // Suppress Node.js experimental feature warnings (e.g., SQLite) from end users
4
- const originalEmitWarning = process.emitWarning;
5
- process.emitWarning = (warning, ...args) => {
6
- if (typeof warning === 'string' && warning.includes('ExperimentalWarning'))
7
- return;
8
- if (warning?.name === 'ExperimentalWarning')
9
- return;
10
- return originalEmitWarning.call(process, warning, ...args);
11
- };
12
2
  /**
13
3
  * Squad CLI — entry point for command-line invocation.
14
4
  * Separated from src/index.ts so library consumers can import
@@ -16,25 +6,6 @@ process.emitWarning = (warning, ...args) => {
16
6
  *
17
7
  * SDK library exports live in src/index.ts (dist/index.js).
18
8
  */
19
- // Load .env file if present (dev mode)
20
- import { existsSync, readFileSync } from 'node:fs';
21
- import { resolve } from 'node:path';
22
- import { fileURLToPath } from 'node:url';
23
- const envPath = resolve(process.cwd(), '.env');
24
- if (existsSync(envPath)) {
25
- for (const line of readFileSync(envPath, 'utf8').split('\n')) {
26
- const trimmed = line.trim();
27
- if (!trimmed || trimmed.startsWith('#'))
28
- continue;
29
- const eq = trimmed.indexOf('=');
30
- if (eq > 0) {
31
- const key = trimmed.slice(0, eq).trim();
32
- const val = trimmed.slice(eq + 1).trim();
33
- if (!process.env[key])
34
- process.env[key] = val;
35
- }
36
- }
37
- }
38
9
  import fs from 'node:fs';
39
10
  import path from 'node:path';
40
11
  import { fatal, SquadError } from './cli/core/errors.js';
@@ -42,676 +13,83 @@ import { BOLD, RESET, DIM, RED } from './cli/core/output.js';
42
13
  import { runInit } from './cli/core/init.js';
43
14
  import { resolveSquad, resolveGlobalSquadPath } from '@bradygaster/squad-sdk';
44
15
  import { runShell } from './cli/shell/index.js';
45
- import { loadWelcomeData } from './cli/shell/lifecycle.js';
46
16
  // Keep VERSION in index.ts (public API); import it here via re-export
47
17
  import { VERSION } from '@bradygaster/squad-sdk';
48
- /** Debug logger — writes to stderr only when SQUAD_DEBUG=1. */
49
- function debugLog(...args) {
50
- if (process.env['SQUAD_DEBUG'] === '1') {
51
- console.error('[SQUAD_DEBUG]', ...args);
52
- }
53
- }
54
- /** Check if --help or -h appears in args after the subcommand. */
55
- function hasHelpFlag(args) {
56
- return args.slice(1).includes('--help') || args.slice(1).includes('-h');
57
- }
58
- /** Commands that skip the auto-link check (non-interactive or utility). */
59
- const SKIP_AUTO_LINK_CMDS = new Set([
60
- '--version', '-v', 'version',
61
- '--help', '-h', 'help',
62
- 'export', 'import', 'doctor', 'heartbeat',
63
- 'scrub-emails', '--preview', '--dry-run',
64
- ]);
65
- /**
66
- * Dev convenience: when running from source (-preview), check if the CLI
67
- * is globally linked and offer to link it if not.
68
- */
69
- async function checkAutoLink(cmd, args) {
70
- try {
71
- if (!VERSION.includes('-preview'))
72
- return;
73
- if (cmd && SKIP_AUTO_LINK_CMDS.has(cmd))
74
- return;
75
- if (args.includes('--preview') || args.includes('--dry-run'))
76
- return;
77
- if (args.includes('--help') || args.includes('-h'))
78
- return;
79
- if (!process.stdin.isTTY)
80
- return;
81
- const { homedir } = await import('node:os');
82
- const home = homedir();
83
- const squadDir = path.join(home, '.squad');
84
- const markerPath = path.join(squadDir, '.no-auto-link');
85
- if (fs.existsSync(markerPath))
86
- return;
87
- // Locate this package and the monorepo root
88
- const thisFile = fileURLToPath(import.meta.url);
89
- const packageDir = path.resolve(path.dirname(thisFile), '..');
90
- const repoRoot = path.resolve(packageDir, '..', '..');
91
- // Check if already globally linked
92
- const { execSync } = await import('node:child_process');
93
- let alreadyLinked = false;
94
- let output = '';
95
- try {
96
- output = execSync('npm ls -g @bradygaster/squad-cli --json', {
97
- encoding: 'utf8',
98
- stdio: ['pipe', 'pipe', 'pipe'],
99
- });
100
- }
101
- catch (e) {
102
- const err = e;
103
- output = err.stdout ?? '';
104
- }
105
- if (output) {
106
- try {
107
- const parsed = JSON.parse(output);
108
- const dep = parsed?.dependencies?.['@bradygaster/squad-cli'];
109
- if (dep?.link === true) {
110
- alreadyLinked = true;
111
- }
112
- else if (typeof dep?.resolved === 'string' && dep.resolved.startsWith('file:')) {
113
- try {
114
- const resolvedPath = fileURLToPath(new URL(dep.resolved));
115
- alreadyLinked = path.resolve(resolvedPath) === path.resolve(packageDir);
116
- }
117
- catch {
118
- // URL parsing failed — skip
119
- }
120
- }
121
- }
122
- catch {
123
- // JSON parse failed
124
- }
125
- }
126
- if (alreadyLinked)
127
- return;
128
- // Prompt the user
129
- console.log(`\n📦 You're running Squad from source (${VERSION}).`);
130
- console.log(` Run 'npm link -w packages/squad-cli' to make 'squad' use your local build?`);
131
- const { createInterface } = await import('node:readline');
132
- const rl = createInterface({ input: process.stdin, output: process.stdout });
133
- const answer = await new Promise((resolve) => {
134
- rl.question('\n[Y/n] ', (ans) => {
135
- rl.close();
136
- resolve(ans.trim().toLowerCase());
137
- });
138
- });
139
- if (answer === '' || answer === 'y' || answer === 'yes') {
140
- console.log('Linking...');
141
- try {
142
- execSync('npm link -w packages/squad-cli', {
143
- cwd: repoRoot,
144
- encoding: 'utf8',
145
- stdio: ['pipe', 'pipe', 'pipe'],
146
- });
147
- console.log('✓ Linked successfully. The "squad" command now uses your local build.\n');
148
- }
149
- catch (linkErr) {
150
- debugLog('npm link failed:', linkErr);
151
- console.log('Link failed — you can run it manually: npm link -w packages/squad-cli\n');
152
- }
153
- }
154
- else {
155
- if (!fs.existsSync(squadDir)) {
156
- fs.mkdirSync(squadDir, { recursive: true });
157
- }
158
- fs.writeFileSync(markerPath, '# Created by squad CLI. Delete this file to be prompted again.\n');
159
- console.log("Got it — won't ask again. Run 'npm link -w packages/squad-cli' manually anytime.\n");
160
- }
161
- }
162
- catch (err) {
163
- debugLog('Auto-link check failed, skipping:', err);
164
- }
165
- }
166
- /** Per-command help text. Returns undefined for unknown commands. */
167
- function getCommandHelp(cmd) {
168
- const help = {
169
- init: `
170
- ${BOLD}squad init${RESET} — Initialize Squad
171
-
172
- ${BOLD}USAGE${RESET}
173
- squad init [prompt] [--file <path>] [--global] [--mode remote <path>]
174
-
175
- ${BOLD}DESCRIPTION${RESET}
176
- Creates the .squad/ directory structure in your project or personal directory.
177
- Detects your project type and scaffolds appropriate workflows and templates.
178
- If a prompt is provided, stores it so the REPL can auto-cast your team.
179
-
180
- ${BOLD}OPTIONS${RESET}
181
- prompt A project description (quoted string) for team casting
182
- --file <path> Read the project description from a file
183
- --global Create personal squad at ~/.squad/
184
- --mode remote <path> Link to a remote team root (dual-root mode)
185
-
186
- ${BOLD}EXAMPLES${RESET}
187
- ${DIM}# Initialize and describe your project for auto-casting${RESET}
188
- squad init "Build a snake game in HTML and JavaScript"
189
-
190
- ${DIM}# Initialize with a spec file${RESET}
191
- squad init --file ./PROJECT.md
192
-
193
- ${DIM}# Initialize without a prompt (cast later interactively)${RESET}
194
- squad init
195
-
196
- ${DIM}# Create personal squad${RESET}
197
- squad init --global
198
-
199
- ${DIM}# Link to shared team in parent directory${RESET}
200
- squad init --mode remote ../team-repo
201
- `,
202
- upgrade: `
203
- ${BOLD}squad upgrade${RESET} — Update Squad Files
204
-
205
- ${BOLD}USAGE${RESET}
206
- squad upgrade [--global] [--migrate-directory]
207
-
208
- ${BOLD}DESCRIPTION${RESET}
209
- Updates Squad-owned files to the latest version. Your team state
210
- (team.md, decisions.md, agent history) is never modified.
211
-
212
- ${BOLD}OPTIONS${RESET}
213
- --global Upgrade personal squad at ~/.squad/
214
- --migrate-directory Rename .ai-team/ to .squad/ (legacy migration)
215
-
216
- ${BOLD}EXAMPLES${RESET}
217
- ${DIM}# Upgrade current repo's Squad files${RESET}
218
- squad upgrade
219
-
220
- ${DIM}# Upgrade personal squad${RESET}
221
- squad upgrade --global
222
-
223
- ${DIM}# Migrate from legacy .ai-team/ directory${RESET}
224
- squad upgrade --migrate-directory
225
- `,
226
- status: `
227
- ${BOLD}squad status${RESET} — Show Active Squad
228
-
229
- ${BOLD}USAGE${RESET}
230
- squad status
231
-
232
- ${BOLD}DESCRIPTION${RESET}
233
- Displays which squad is currently active and where it's located.
234
- Shows repo squad, personal squad path, and resolution order.
235
-
236
- ${BOLD}EXAMPLES${RESET}
237
- ${DIM}# Check active squad${RESET}
238
- squad status
239
- `,
240
- triage: `
241
- ${BOLD}squad triage${RESET} — Auto-Triage Issues
242
-
243
- ${BOLD}USAGE${RESET}
244
- squad triage [--interval <minutes>]
245
- squad watch [--interval <minutes>] ${DIM}(alias)${RESET}
246
-
247
- ${BOLD}DESCRIPTION${RESET}
248
- Ralph's work monitor. Continuously polls GitHub issues and automatically
249
- assigns them to the right team member based on content and expertise.
250
-
251
- ${BOLD}OPTIONS${RESET}
252
- --interval <minutes> Polling frequency (default: 10)
253
-
254
- ${BOLD}EXAMPLES${RESET}
255
- ${DIM}# Start triage with default 10-minute interval${RESET}
256
- squad triage
257
-
258
- ${DIM}# Poll every 5 minutes${RESET}
259
- squad triage --interval 5
260
- `,
261
- copilot: `
262
- ${BOLD}squad copilot${RESET} — Manage Copilot Coding Agent
263
-
264
- ${BOLD}USAGE${RESET}
265
- squad copilot [--off] [--auto-assign]
266
-
267
- ${BOLD}DESCRIPTION${RESET}
268
- Add or remove the GitHub Copilot coding agent (@copilot) from your team.
269
- When enabled, @copilot can pick up issues labeled 'squad:copilot'.
270
-
271
- ${BOLD}OPTIONS${RESET}
272
- --off Remove @copilot from the team
273
- --auto-assign Enable automatic issue assignment for @copilot
274
-
275
- ${BOLD}EXAMPLES${RESET}
276
- ${DIM}# Add @copilot to the team${RESET}
277
- squad copilot
278
-
279
- ${DIM}# Add @copilot with auto-assignment enabled${RESET}
280
- squad copilot --auto-assign
281
-
282
- ${DIM}# Remove @copilot from the team${RESET}
283
- squad copilot --off
284
- `,
285
- plugin: `
286
- ${BOLD}squad plugin${RESET} — Manage Plugin Marketplaces
287
-
288
- ${BOLD}USAGE${RESET}
289
- squad plugin marketplace add <owner/repo>
290
- squad plugin marketplace remove <name>
291
- squad plugin marketplace list
292
- squad plugin marketplace browse <name>
293
-
294
- ${BOLD}DESCRIPTION${RESET}
295
- Manage plugin marketplaces — registries of Squad extensions, skills,
296
- and agent templates.
297
-
298
- ${BOLD}EXAMPLES${RESET}
299
- ${DIM}# Add official Squad plugins marketplace${RESET}
300
- squad plugin marketplace add bradygaster/squad-plugins
301
-
302
- ${DIM}# List all registered marketplaces${RESET}
303
- squad plugin marketplace list
304
- `,
305
- export: `
306
- ${BOLD}squad export${RESET} — Export Squad to JSON
307
-
308
- ${BOLD}USAGE${RESET}
309
- squad export [--out <path>]
310
-
311
- ${BOLD}DESCRIPTION${RESET}
312
- Creates a portable JSON snapshot of your entire squad — casting state,
313
- agent charters, accumulated history, and skills.
314
-
315
- ${BOLD}OPTIONS${RESET}
316
- --out <path> Custom output path (default: squad-export.json)
317
-
318
- ${BOLD}EXAMPLES${RESET}
319
- ${DIM}# Export to default file${RESET}
320
- squad export
321
-
322
- ${DIM}# Export to custom location${RESET}
323
- squad export --out ./backups/team-backup.json
324
- `,
325
- import: `
326
- ${BOLD}squad import${RESET} — Import Squad from JSON
327
-
328
- ${BOLD}USAGE${RESET}
329
- squad import <file> [--force]
330
-
331
- ${BOLD}DESCRIPTION${RESET}
332
- Restores a squad from a JSON export file. Creates .squad/ directory
333
- and populates it with casting state, agents, skills, and history.
334
-
335
- ${BOLD}OPTIONS${RESET}
336
- --force Overwrite existing squad (archives old .squad/ first)
337
-
338
- ${BOLD}EXAMPLES${RESET}
339
- ${DIM}# Import into current directory${RESET}
340
- squad import squad-export.json
341
-
342
- ${DIM}# Import and overwrite existing squad${RESET}
343
- squad import squad-export.json --force
344
- `,
345
- 'scrub-emails': `
346
- ${BOLD}squad scrub-emails${RESET} — Remove Email Addresses
347
-
348
- ${BOLD}USAGE${RESET}
349
- squad scrub-emails [directory]
350
-
351
- ${BOLD}DESCRIPTION${RESET}
352
- Removes email addresses (PII) from Squad state files. Useful before
353
- committing to public repos or sharing exports.
354
-
355
- ${BOLD}OPTIONS${RESET}
356
- [directory] Target directory (default: .squad)
357
-
358
- ${BOLD}EXAMPLES${RESET}
359
- ${DIM}# Scrub current squad${RESET}
360
- squad scrub-emails .squad
361
-
362
- ${DIM}# Scrub legacy .ai-team directory${RESET}
363
- squad scrub-emails
364
- `,
365
- doctor: `
366
- ${BOLD}squad doctor${RESET} — Validate Squad Setup
367
-
368
- ${BOLD}USAGE${RESET}
369
- squad doctor
370
-
371
- ${BOLD}DESCRIPTION${RESET}
372
- Runs a 9-check diagnostic on your squad setup. Reports health of
373
- expected files, conventions, and directory structure.
374
-
375
- ${BOLD}EXAMPLES${RESET}
376
- ${DIM}# Check current squad setup${RESET}
377
- squad doctor
378
- `,
379
- link: `
380
- ${BOLD}squad link${RESET} — Link to Remote Team
381
-
382
- ${BOLD}USAGE${RESET}
383
- squad link <team-repo-path>
384
-
385
- ${BOLD}DESCRIPTION${RESET}
386
- Links the current project to a remote team root. Enables dual-root mode
387
- where project-specific state lives in .squad/ and team identity lives
388
- in a shared location.
389
-
390
- ${BOLD}EXAMPLES${RESET}
391
- ${DIM}# Link to parent directory's team${RESET}
392
- squad link ../team-repo
393
-
394
- ${DIM}# Link to absolute path${RESET}
395
- squad link /Users/org/shared-squad
396
- `,
397
- aspire: `
398
- ${BOLD}squad aspire${RESET} — Launch Aspire Dashboard
399
-
400
- ${BOLD}USAGE${RESET}
401
- squad aspire [--docker] [--port <number>]
402
-
403
- ${BOLD}DESCRIPTION${RESET}
404
- Launches the .NET Aspire dashboard for observability. Squad exports
405
- OpenTelemetry traces and metrics to the dashboard for real-time visibility.
406
-
407
- ${BOLD}OPTIONS${RESET}
408
- --docker Force Docker mode
409
- --port <number> OTLP port (default: 4317)
410
-
411
- ${BOLD}EXAMPLES${RESET}
412
- ${DIM}# Launch Aspire dashboard (auto-detect runtime)${RESET}
413
- squad aspire
414
-
415
- ${DIM}# Force Docker mode${RESET}
416
- squad aspire --docker
417
- `,
418
- upstream: `
419
- ${BOLD}squad upstream${RESET} — Manage Upstream Squad Sources
420
-
421
- ${BOLD}USAGE${RESET}
422
- squad upstream add <source> [--name <name>] [--ref <branch>]
423
- squad upstream remove <name>
424
- squad upstream list
425
- squad upstream sync [name]
426
-
427
- ${BOLD}DESCRIPTION${RESET}
428
- Manage upstream Squad sources — external configurations from local
429
- directories, git repositories, or export files that can be synced
430
- into your squad.
431
-
432
- ${BOLD}OPTIONS${RESET}
433
- add <source> Add an upstream (path, git URL, or .json export)
434
- --name <name> Custom name (auto-derived if omitted)
435
- --ref <branch> Git branch/tag (default: main)
436
- remove <name> Remove an upstream by name
437
- list Show all configured upstreams
438
- sync [name] Sync all or a specific upstream
439
-
440
- ${BOLD}EXAMPLES${RESET}
441
- ${DIM}# Add a git upstream${RESET}
442
- squad upstream add https://github.com/org/squad-config.git
443
-
444
- ${DIM}# Add a local upstream with custom name${RESET}
445
- squad upstream add ../shared-squad --name shared
446
-
447
- ${DIM}# Sync all upstreams${RESET}
448
- squad upstream sync
449
- `,
450
- nap: `
451
- ${BOLD}squad nap${RESET} — Context Hygiene
452
-
453
- ${BOLD}USAGE${RESET}
454
- squad nap [--deep] [--dry-run]
455
-
456
- ${BOLD}DESCRIPTION${RESET}
457
- Compresses agent histories, prunes old logs, archives stale decisions,
458
- and cleans up orphaned inbox files. Shows before/after stats with
459
- estimated token savings.
460
-
461
- ${BOLD}OPTIONS${RESET}
462
- --deep Aggressive mode — tighter history compression
463
- --dry-run Show what would change without modifying files
464
-
465
- ${BOLD}EXAMPLES${RESET}
466
- ${DIM}# Run a standard nap${RESET}
467
- squad nap
468
-
469
- ${DIM}# Preview changes without modifying files${RESET}
470
- squad nap --dry-run
471
-
472
- ${DIM}# Deep clean for maximum compression${RESET}
473
- squad nap --deep
474
- `,
475
- shell: `
476
- ${BOLD}squad shell${RESET} — Launch Interactive Shell
477
-
478
- ${BOLD}USAGE${RESET}
479
- squad shell [--preview] [--timeout <seconds>]
480
-
481
- ${BOLD}DESCRIPTION${RESET}
482
- Starts the interactive Squad REPL. Messages are routed to the
483
- right agent automatically based on content and team expertise.
484
-
485
- ${BOLD}OPTIONS${RESET}
486
- --preview Show team summary without launching shell
487
- --timeout <seconds> Set REPL inactivity timeout (default: 600)
488
-
489
- ${BOLD}EXAMPLES${RESET}
490
- ${DIM}# Start interactive shell${RESET}
491
- squad shell
492
-
493
- ${DIM}# Preview team before launching${RESET}
494
- squad shell --preview
495
- `,
496
- };
497
- // Handle aliases
498
- const aliases = {
499
- watch: 'triage',
500
- loop: 'triage',
501
- hire: 'init',
502
- heartbeat: 'doctor',
503
- };
504
- const key = aliases[cmd] ?? cmd;
505
- return help[key];
506
- }
507
18
  async function main() {
508
19
  const args = process.argv.slice(2);
509
20
  const hasGlobal = args.includes('--global');
510
- const rawCmd = args[0];
511
- const cmd = rawCmd?.trim() || undefined;
512
- // --timeout <seconds> set SQUAD_REPL_TIMEOUT env var for shell
513
- const timeoutIdx = args.indexOf('--timeout');
514
- if (timeoutIdx >= 0 && args[timeoutIdx + 1]) {
515
- process.env['SQUAD_REPL_TIMEOUT'] = args[timeoutIdx + 1];
516
- }
517
- // Dev convenience: offer to npm-link when running from source
518
- await checkAutoLink(cmd, args);
519
- // Empty or whitespace-only args should show help, not launch shell
520
- if (rawCmd !== undefined && !cmd) {
521
- console.log(`\n${BOLD}squad${RESET} v${VERSION}`);
522
- console.log(`Your AI agent team\n`);
523
- console.log(`Usage: squad [command] [options]`);
524
- console.log(`\nRun 'squad help' for full command list.\n`);
525
- return;
526
- }
527
- // --version / -v / version — canonical format: bare semver
528
- if (cmd === '--version' || cmd === '-v' || cmd === 'version') {
529
- console.log(VERSION);
21
+ const cmd = args[0];
22
+ // --version / -v
23
+ if (cmd === '--version' || cmd === '-v') {
24
+ console.log(`squad ${VERSION}`);
530
25
  return;
531
26
  }
532
27
  // --help / -h / help
533
28
  if (cmd === '--help' || cmd === '-h' || cmd === 'help') {
534
- console.log(`\n${BOLD}squad${RESET} v${VERSION}`);
535
- console.log(`Your AI agent team\n`);
536
- console.log(`${BOLD}Just type — squad routes your message to the right agent automatically${RESET}`);
537
- console.log(` squad Start interactive shell`);
538
- console.log(` squad --global Use your personal squad\n`);
29
+ console.log(`\n${BOLD}squad${RESET} v${VERSION} — Add an AI agent team to any project\n`);
539
30
  console.log(`Usage: squad [command] [options]\n`);
540
- console.log(`${BOLD}Getting Started${RESET}`);
541
- console.log(` ${BOLD}init${RESET} Create .squad/ in this repo ${DIM}(alias: hire)${RESET}`);
542
- console.log(` --global Create personal squad directory`);
543
- console.log(` --mode remote <path> Link to a remote team root`);
544
- console.log(` ${BOLD}upgrade${RESET} Update Squad files to latest`);
545
- console.log(` --global Upgrade personal squad`);
546
- console.log(` --migrate-directory Rename .ai-team/ → .squad/`);
547
- console.log(` ${BOLD}status${RESET} Show which squad is active`);
548
- console.log(` ${BOLD}doctor${RESET} Check your setup ${DIM}(alias: heartbeat)${RESET}`);
549
- console.log(`\n${BOLD}Development${RESET}`);
550
- console.log(` ${BOLD}shell${RESET} Launch interactive shell`);
551
- console.log(`\n${BOLD}Team Management${RESET}`);
552
- console.log(` ${BOLD}triage${RESET} Watch issues and auto-triage ${DIM}(alias: watch, loop)${RESET}`);
553
- console.log(` --interval <minutes> Polling frequency (default: 10)`);
554
- console.log(` ${BOLD}copilot${RESET} Add/remove GitHub Copilot agent`);
555
- console.log(` --off Remove @copilot`);
556
- console.log(` --auto-assign Enable auto-assignment`);
557
- console.log(` ${BOLD}link${RESET} Connect to a remote team`);
558
- console.log(` <team-repo-path>`);
559
- console.log(` ${BOLD}upstream${RESET} Manage upstream Squad sources`);
560
- console.log(` add|remove|list|sync`);
561
- console.log(`\n${BOLD}Utilities${RESET}`);
562
- console.log(` ${BOLD}nap${RESET} Context hygiene compress, prune, archive`);
563
- console.log(` --deep Aggressive compression`);
564
- console.log(` --dry-run Preview without changes`);
565
- console.log(` ${BOLD}export${RESET} Save squad to JSON`);
566
- console.log(` --out <path> Output path (default: squad-export.json)`);
567
- console.log(` ${BOLD}import${RESET} Load squad from JSON`);
568
- console.log(` <file> [--force]`);
569
- console.log(` ${BOLD}plugin${RESET} Manage plugins`);
570
- console.log(` marketplace add|remove|list|browse`);
571
- console.log(` ${BOLD}aspire${RESET} Open Aspire dashboard`);
572
- console.log(` --docker Force Docker mode`);
573
- console.log(` --port <number> OTLP port (default: 4317)`);
574
- console.log(` ${BOLD}scrub-emails${RESET} Remove email addresses from squad state`);
575
- console.log(` [directory] Target directory (default: .squad/)`);
576
- console.log(`\n${BOLD}Flags${RESET}`);
577
- console.log(` --version, -v Print version`);
578
- console.log(` --help, -h Show this help`);
579
- console.log(` --preview Show team summary without launching shell`);
580
- console.log(` --global Use personal squad path`);
581
- console.log(` --timeout <seconds> Set REPL inactivity timeout (default: 600)`);
582
- console.log(`\n${BOLD}Examples${RESET}`);
583
- console.log(` ${DIM}$ squad init${RESET} Set up a squad in this repo`);
584
- console.log(` ${DIM}$ squad triage --interval 5${RESET} Poll issues every 5 minutes`);
585
- console.log(` ${DIM}$ squad export --out ./backups/team.json${RESET}`);
586
- console.log(` Save squad snapshot`);
587
- console.log(` ${DIM}$ squad copilot --auto-assign${RESET} Add @copilot with auto-assignment`);
588
- console.log(` ${DIM}$ squad doctor${RESET} Diagnose setup issues`);
31
+ console.log(`Commands:`);
32
+ console.log(` ${BOLD}(default)${RESET} Launch interactive shell (no args)`);
33
+ console.log(` Flags: --global (init in personal squad directory)`);
34
+ console.log(` ${BOLD}init${RESET} Initialize Squad (skip files that already exist)`);
35
+ console.log(` Flags: --global (init in personal squad directory)`);
36
+ console.log(` ${BOLD}upgrade${RESET} Update Squad-owned files to latest version`);
37
+ console.log(` Overwrites: squad.agent.md, templates dir (.squad-templates/ or .ai-team-templates/)`);
38
+ console.log(` Never touches: .squad/ or .ai-team/ (your team state)`);
39
+ console.log(` Flags: --global (upgrade personal squad), --migrate-directory (rename .ai-team/ → .squad/)`);
40
+ console.log(` ${BOLD}status${RESET} Show which squad is active and why`);
41
+ console.log(` ${BOLD}triage${RESET} Scan for work and categorize issues`);
42
+ console.log(` Usage: triage [--interval <minutes>]`);
43
+ console.log(` Default: checks every 10 minutes (Ctrl+C to stop)`);
44
+ console.log(` ${BOLD}loop${RESET} Continuous work loop (Ralph mode)`);
45
+ console.log(` Usage: loop [--filter <label>] [--interval <minutes>]`);
46
+ console.log(` Default: checks every 10 minutes (Ctrl+C to stop)`);
47
+ console.log(` ${BOLD}hire${RESET} Team creation wizard`);
48
+ console.log(` Usage: hire [--name <name>] [--role <role>]`);
49
+ console.log(` ${BOLD}copilot${RESET} Add/remove the Copilot coding agent (@copilot)`);
50
+ console.log(` Usage: copilot [--off] [--auto-assign]`);
51
+ console.log(` ${BOLD}plugin${RESET} Manage plugin marketplaces`);
52
+ console.log(` Usage: plugin marketplace add|remove|list|browse`);
53
+ console.log(` ${BOLD}export${RESET} Export squad to a portable JSON snapshot`);
54
+ console.log(` Default: squad-export.json (use --out <path> to override)`);
55
+ console.log(` ${BOLD}import${RESET} Import squad from an export file`);
56
+ console.log(` Usage: import <file> [--force]`);
57
+ console.log(` ${BOLD}scrub-emails${RESET} Remove email addresses from Squad state files`);
58
+ console.log(` Usage: scrub-emails [directory] (default: .ai-team/)`);
59
+ console.log(` ${BOLD}start${RESET} Start Copilot with remote access from phone/browser`);
60
+ console.log(` Usage: start [--tunnel] [--port <n>] [--command <cmd>] [copilot flags...]`);
61
+ console.log(` Examples: start --tunnel --yolo`);
62
+ console.log(` start --tunnel --model claude-sonnet-4`);
63
+ console.log(` start --tunnel --command "agency copilot"`);
64
+ console.log(` ${BOLD}help${RESET} Show this help message`);
65
+ console.log(`\nFlags:`);
66
+ console.log(` ${BOLD}--version, -v${RESET} Print version`);
67
+ console.log(` ${BOLD}--help, -h${RESET} Show help`);
68
+ console.log(` ${BOLD}--global${RESET} Use personal (global) squad path (for init, upgrade)`);
589
69
  console.log(`\nInstallation:`);
590
- console.log(` npm i --save-dev @bradygaster/squad-cli`);
70
+ console.log(` npm install --save-dev @bradygaster/squad-cli`);
591
71
  console.log(`\nInsider channel:`);
592
- console.log(` npm i --save-dev @bradygaster/squad-cli@insider`);
593
- console.log(`\nRun 'squad <command> --help' for details.\n`);
594
- return;
595
- }
596
- // --preview / --dry-run — show team summary without launching the interactive shell
597
- if (cmd === '--preview' || cmd === '--dry-run' || (!cmd && args.includes('--preview')) || (cmd === 'shell' && args.includes('--preview'))) {
598
- const squadDir = resolveSquad(process.cwd());
599
- const globalPath = resolveGlobalSquadPath();
600
- const globalSquadDir = path.join(globalPath, '.squad');
601
- const teamRoot = squadDir ? path.dirname(squadDir) : fs.existsSync(globalSquadDir) ? globalPath : null;
602
- if (!teamRoot) {
603
- console.log(`${RED}✗${RESET} No squad found. Run 'squad init' first.`);
604
- process.exit(1);
605
- }
606
- const data = loadWelcomeData(teamRoot);
607
- if (!data) {
608
- console.log(`${RED}✗${RESET} Could not read team configuration.`);
609
- process.exit(1);
610
- }
611
- console.log(`\n${BOLD}Squad Preview${RESET}`);
612
- console.log(`${'─'.repeat(40)}`);
613
- console.log(` Team: ${data.projectName}`);
614
- console.log(` Agents: ${data.agents.length}`);
615
- if (data.description)
616
- console.log(` About: ${data.description}`);
617
- if (data.focus)
618
- console.log(` Focus: ${data.focus}`);
619
- console.log(`\n${BOLD}Agents${RESET}`);
620
- for (const a of data.agents) {
621
- console.log(` ${a.emoji} ${BOLD}${a.name}${RESET} — ${a.role}`);
622
- }
623
- console.log(`\n${BOLD}Shell Commands${RESET}`);
624
- console.log(` /status — Check your team & what's happening`);
625
- console.log(` /history — See recent messages`);
626
- console.log(` /agents — List all team members`);
627
- console.log(` /sessions — List saved sessions`);
628
- console.log(` /resume — Restore a past session`);
629
- console.log(` /clear — Clear the screen`);
630
- console.log(` /quit — Exit`);
631
- console.log(`\n${DIM}Run 'squad' to start the interactive shell.${RESET}\n`);
72
+ console.log(` npm install --save-dev @bradygaster/squad-cli@insider\n`);
632
73
  return;
633
74
  }
634
- // No args → check if .squad/ exists
75
+ // No args → launch interactive shell
635
76
  if (!cmd) {
636
- const squadPath = resolveSquad(process.cwd());
637
- const globalPath = resolveGlobalSquadPath();
638
- const globalSquadDir = path.join(globalPath, '.squad');
639
- const hasSquad = squadPath || fs.existsSync(globalSquadDir);
640
- if (hasSquad) {
641
- // Squad exists, launch shell
642
- await runShell();
643
- }
644
- else {
645
- // First run — no squad found. Welcome the user.
646
- console.log(`\n${BOLD}Welcome to Squad${RESET} v${VERSION}`);
647
- console.log(`Your AI agent team\n`);
648
- console.log(`Squad adds a team of AI agents to your project. Each agent`);
649
- console.log(`has a role — architect, tester, security reviewer — and they`);
650
- console.log(`collaborate to help you build, review, and ship code.\n`);
651
- console.log(`${BOLD}Get started:${RESET}`);
652
- console.log(` ${BOLD}squad init${RESET} Set up a squad in this repo`);
653
- console.log(` ${BOLD}squad init --global${RESET} Create a personal squad\n`);
654
- console.log(`${DIM}After init, just run ${BOLD}squad${RESET}${DIM} to start talking to your team.${RESET}`);
655
- console.log(`${DIM}Run ${BOLD}squad help${RESET}${DIM} for all commands.${RESET}\n`);
656
- }
77
+ await runShell();
657
78
  return;
658
79
  }
659
- // Per-command --help/-h: intercept before dispatching (fixes #511, #512)
660
- if (hasHelpFlag(args)) {
661
- const helpText = getCommandHelp(cmd);
662
- if (helpText) {
663
- console.log(helpText);
664
- return;
665
- }
666
- }
667
80
  // Route subcommands
668
- // hire alias for init (#501)
669
- if (cmd === 'init' || cmd === 'hire') {
670
- const modeIdx = args.indexOf('--mode');
671
- const mode = (modeIdx !== -1 && args[modeIdx + 1]) ? args[modeIdx + 1] : 'local';
81
+ if (cmd === 'init') {
672
82
  const dest = hasGlobal ? resolveGlobalSquadPath() : process.cwd();
673
- if (mode === 'remote') {
674
- const { writeRemoteConfig } = await import('./cli/commands/init-remote.js');
675
- // teamRoot can be provided as the next positional arg after --mode remote
676
- const teamRootArg = args.find((a, i) => i > 0 && a !== '--mode' && a !== 'remote' && a !== '--global' && a !== 'init');
677
- if (!teamRootArg) {
678
- fatal('squad init --mode remote <team-root-path>');
679
- }
680
- writeRemoteConfig(dest, teamRootArg);
681
- }
682
- // Extract project prompt: squad init "Build a snake game" or squad init --file ./spec.txt
683
- let initPrompt;
684
- const fileIdx = args.indexOf('--file');
685
- if (fileIdx !== -1 && args[fileIdx + 1]) {
686
- const filePath = resolve(args[fileIdx + 1]);
687
- if (!existsSync(filePath)) {
688
- fatal(`Prompt file not found: ${filePath}`);
689
- }
690
- initPrompt = readFileSync(filePath, 'utf-8').trim();
691
- }
692
- else {
693
- // Look for a positional string arg (not a flag, not 'init'/'hire')
694
- const skipSet = new Set(['init', 'hire', '--global', '--mode', mode]);
695
- const positional = args.find((a, i) => i > 0 && !a.startsWith('--') && !skipSet.has(a));
696
- if (positional)
697
- initPrompt = positional;
698
- }
699
- await runInit(dest, { prompt: initPrompt });
700
- return;
701
- }
702
- if (cmd === 'link') {
703
- const { runLink } = await import('./cli/commands/link.js');
704
- const linkTarget = args[1];
705
- if (!linkTarget) {
706
- fatal('Run: squad link <team-repo-path>');
707
- }
708
- runLink(process.cwd(), linkTarget);
83
+ runInit(dest).catch(err => {
84
+ fatal(err.message);
85
+ });
709
86
  return;
710
87
  }
711
88
  if (cmd === 'upgrade') {
712
89
  const { runUpgrade } = await import('./cli/core/upgrade.js');
713
90
  const { migrateDirectory } = await import('./cli/core/migrate-directory.js');
714
91
  const migrateDir = args.includes('--migrate-directory');
92
+ const selfUpgrade = args.includes('--self');
715
93
  const dest = hasGlobal ? resolveGlobalSquadPath() : process.cwd();
716
94
  // Handle --migrate-directory flag
717
95
  if (migrateDir) {
@@ -721,26 +99,40 @@ async function main() {
721
99
  // Run upgrade
722
100
  await runUpgrade(dest, {
723
101
  migrateDirectory: migrateDir,
102
+ self: selfUpgrade
724
103
  });
725
104
  return;
726
105
  }
727
- if (cmd === 'nap') {
728
- const { runNap, formatNapReport } = await import('./cli/core/nap.js');
729
- const squadDir = path.join(hasGlobal ? resolveGlobalSquadPath() : process.cwd(), '.squad');
730
- const deep = args.includes('--deep');
731
- const dryRun = args.includes('--dry-run');
732
- const result = await runNap({ squadDir, deep, dryRun });
733
- console.log(formatNapReport(result, !!process.env['NO_COLOR']));
106
+ if (cmd === 'triage' || cmd === 'watch') {
107
+ console.log('🕵️ Squad triage scanning for work... (full implementation pending)');
734
108
  return;
735
109
  }
736
- // loop alias for triage (#509)
737
- if (cmd === 'triage' || cmd === 'watch' || cmd === 'loop') {
738
- const { runWatch } = await import('./cli/commands/watch.js');
110
+ if (cmd === 'loop') {
111
+ const filterIdx = args.indexOf('--filter');
112
+ const filter = (filterIdx !== -1 && args[filterIdx + 1]) ? args[filterIdx + 1] : undefined;
739
113
  const intervalIdx = args.indexOf('--interval');
740
114
  const intervalMinutes = (intervalIdx !== -1 && args[intervalIdx + 1])
741
115
  ? parseInt(args[intervalIdx + 1], 10)
742
116
  : 10;
743
- await runWatch(process.cwd(), intervalMinutes);
117
+ console.log(`🔄 Squad loop starting... (full implementation pending)`);
118
+ if (filter) {
119
+ console.log(` Filter: ${filter}`);
120
+ }
121
+ console.log(` Interval: ${intervalMinutes} minutes`);
122
+ return;
123
+ }
124
+ if (cmd === 'hire') {
125
+ const nameIdx = args.indexOf('--name');
126
+ const name = (nameIdx !== -1 && args[nameIdx + 1]) ? args[nameIdx + 1] : undefined;
127
+ const roleIdx = args.indexOf('--role');
128
+ const role = (roleIdx !== -1 && args[roleIdx + 1]) ? args[roleIdx + 1] : undefined;
129
+ console.log('👋 Squad hire — team creation wizard starting... (full implementation pending)');
130
+ if (name) {
131
+ console.log(` Name: ${name}`);
132
+ }
133
+ if (role) {
134
+ console.log(` Role: ${role}`);
135
+ }
744
136
  return;
745
137
  }
746
138
  if (cmd === 'export') {
@@ -754,7 +146,7 @@ async function main() {
754
146
  const { runImport } = await import('./cli/commands/import.js');
755
147
  const importFile = args[1];
756
148
  if (!importFile) {
757
- fatal('Run: squad import <file> [--force]');
149
+ fatal('Usage: squad import <file> [--force]');
758
150
  }
759
151
  const hasForce = args.includes('--force');
760
152
  await runImport(process.cwd(), importFile, hasForce);
@@ -774,35 +166,16 @@ async function main() {
774
166
  }
775
167
  if (cmd === 'scrub-emails') {
776
168
  const { scrubEmails } = await import('./cli/core/email-scrub.js');
777
- const targetDir = args[1] || '.squad';
169
+ const targetDir = args[1] || '.ai-team';
778
170
  const count = await scrubEmails(targetDir);
779
171
  if (count > 0) {
780
- console.log(`Scrubbed ${count} email${count !== 1 ? 's' : ''}.`);
172
+ console.log(`Scrubbed ${count} email address(es).`);
781
173
  }
782
174
  else {
783
- console.log('No emails found.');
175
+ console.log('No email addresses found.');
784
176
  }
785
177
  return;
786
178
  }
787
- if (cmd === 'aspire') {
788
- const { runAspire } = await import('./cli/commands/aspire.js');
789
- const useDocker = args.includes('--docker');
790
- const portIdx = args.indexOf('--port');
791
- const port = (portIdx !== -1 && args[portIdx + 1]) ? parseInt(args[portIdx + 1], 10) : undefined;
792
- await runAspire({ docker: useDocker, port });
793
- return;
794
- }
795
- if (cmd === 'upstream') {
796
- const { upstreamCommand } = await import('./cli/commands/upstream.js');
797
- await upstreamCommand(args.slice(1));
798
- return;
799
- }
800
- // heartbeat → alias for doctor (#503)
801
- if (cmd === 'doctor' || cmd === 'heartbeat') {
802
- const { doctorCommand } = await import('./cli/commands/doctor.js');
803
- await doctorCommand(process.cwd());
804
- return;
805
- }
806
179
  if (cmd === 'status') {
807
180
  const repoSquad = resolveSquad(process.cwd());
808
181
  const globalPath = resolveGlobalSquadPath();
@@ -810,46 +183,49 @@ async function main() {
810
183
  const globalExists = fs.existsSync(globalSquadDir);
811
184
  console.log(`\n${BOLD}Squad Status${RESET}\n`);
812
185
  if (repoSquad) {
813
- console.log(` Here: ${BOLD}repo${RESET} (in .squad/)`);
814
- console.log(` Path: ${repoSquad}`);
186
+ console.log(` Active squad: ${BOLD}repo${RESET}`);
187
+ console.log(` Path: ${repoSquad}`);
188
+ console.log(` Reason: Found .squad/ in repository tree`);
815
189
  }
816
190
  else if (globalExists) {
817
- console.log(` Here: ${BOLD}personal${RESET} (global)`);
818
- console.log(` Path: ${globalSquadDir}`);
191
+ console.log(` Active squad: ${BOLD}personal (global)${RESET}`);
192
+ console.log(` Path: ${globalSquadDir}`);
193
+ console.log(` Reason: No repo .squad/ found; personal squad exists at global path`);
819
194
  }
820
195
  else {
821
- console.log(` Here: ${DIM}none${RESET}`);
822
- console.log(` Hint: Run 'squad init' to get started`);
196
+ console.log(` Active squad: ${DIM}none${RESET}`);
197
+ console.log(` Reason: No .squad/ found in repo tree or at global path`);
823
198
  }
824
199
  console.log();
825
- console.log(` ${DIM}Repo squad: ${repoSquad ?? 'not found'}${RESET}`);
826
- console.log(` ${DIM}Global: ${globalPath}${RESET}`);
200
+ console.log(` ${DIM}Repo resolution: ${repoSquad ?? 'not found'}${RESET}`);
201
+ console.log(` ${DIM}Global path: ${globalPath}${RESET}`);
202
+ console.log(` ${DIM}Global squad: ${globalExists ? globalSquadDir : 'not initialized'}${RESET}`);
827
203
  console.log();
828
204
  return;
829
205
  }
830
- // shell explicit REPL launch (#507)
831
- if (cmd === 'shell') {
832
- await runShell();
206
+ if (cmd === 'start') {
207
+ const { runStart } = await import('./cli/commands/start.js');
208
+ const hasTunnel = args.includes('--tunnel');
209
+ const portIdx = args.indexOf('--port');
210
+ const port = (portIdx !== -1 && args[portIdx + 1]) ? parseInt(args[portIdx + 1], 10) : 0;
211
+ // Collect all remaining args to pass through to copilot
212
+ const cmdIdx = args.indexOf('--command');
213
+ const customCmd = (cmdIdx !== -1 && args[cmdIdx + 1]) ? args[cmdIdx + 1] : undefined;
214
+ const squadFlags = ['start', '--tunnel', '--port', port.toString(), '--command', customCmd || ''].filter(Boolean);
215
+ const copilotArgs = args.slice(1).filter(a => !squadFlags.includes(a));
216
+ await runStart(process.cwd(), { tunnel: hasTunnel, port, copilotArgs, command: customCmd });
833
217
  return;
834
218
  }
835
219
  // Unknown command
836
- fatal(`Unknown command: ${cmd}. Run 'squad help' for commands.`);
220
+ fatal(`Unknown command: ${cmd}\n Run 'squad help' for usage information.`);
837
221
  }
838
222
  main().catch(err => {
839
- debugLog('Fatal CLI error:', err);
840
223
  if (err instanceof SquadError) {
841
224
  console.error(`${RED}✗${RESET} ${err.message}`);
842
225
  }
843
226
  else {
844
- const msg = err instanceof Error ? err.message : String(err);
845
- const friendly = msg.replace(/^Error:\s*/i, '');
846
- console.error(`${RED}✗${RESET} ${friendly}`);
847
- // Show stack trace only when SQUAD_DEBUG is enabled
848
- if (process.env['SQUAD_DEBUG'] === '1' && err instanceof Error && err.stack) {
849
- console.error(`\n${DIM}${err.stack}${RESET}`);
850
- }
227
+ console.error(err);
851
228
  }
852
- console.error(`\n${DIM}Tip: Run 'squad doctor' for help. Set SQUAD_DEBUG=1 for details.${RESET}`);
853
229
  process.exit(1);
854
230
  });
855
231
  //# sourceMappingURL=cli-entry.js.map