daedalus-cli 3.63.2 → 3.65.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 (87) hide show
  1. package/CHANGELOG.md +22 -3
  2. package/dist/agents/orchestrator.d.ts +3 -6
  3. package/dist/agents/orchestrator.d.ts.map +1 -1
  4. package/dist/agents/orchestrator.js +39 -843
  5. package/dist/agents/orchestrator.js.map +1 -1
  6. package/dist/agents/subagent-runner.d.ts +18 -0
  7. package/dist/agents/subagent-runner.d.ts.map +1 -0
  8. package/dist/agents/subagent-runner.js +231 -0
  9. package/dist/agents/subagent-runner.js.map +1 -0
  10. package/dist/agents/subagent-runner.test.d.ts +2 -0
  11. package/dist/agents/subagent-runner.test.d.ts.map +1 -0
  12. package/dist/agents/subagent-runner.test.js +74 -0
  13. package/dist/agents/subagent-runner.test.js.map +1 -0
  14. package/dist/agents/task-delegator.d.ts +28 -0
  15. package/dist/agents/task-delegator.d.ts.map +1 -0
  16. package/dist/agents/task-delegator.js +542 -0
  17. package/dist/agents/task-delegator.js.map +1 -0
  18. package/dist/agents/task-delegator.test.d.ts +2 -0
  19. package/dist/agents/task-delegator.test.d.ts.map +1 -0
  20. package/dist/agents/task-delegator.test.js +83 -0
  21. package/dist/agents/task-delegator.test.js.map +1 -0
  22. package/dist/agents/turn-guards.d.ts +41 -0
  23. package/dist/agents/turn-guards.d.ts.map +1 -0
  24. package/dist/agents/turn-guards.js +325 -0
  25. package/dist/agents/turn-guards.js.map +1 -0
  26. package/dist/agents/turn-guards.test.d.ts +2 -0
  27. package/dist/agents/turn-guards.test.d.ts.map +1 -0
  28. package/dist/agents/turn-guards.test.js +123 -0
  29. package/dist/agents/turn-guards.test.js.map +1 -0
  30. package/dist/commands/agents.d.ts +5 -28
  31. package/dist/commands/agents.d.ts.map +1 -1
  32. package/dist/commands/agents.js +11 -1021
  33. package/dist/commands/agents.js.map +1 -1
  34. package/dist/commands/autopilot.d.ts +30 -0
  35. package/dist/commands/autopilot.d.ts.map +1 -0
  36. package/dist/commands/autopilot.js +372 -0
  37. package/dist/commands/autopilot.js.map +1 -0
  38. package/dist/commands/hunt.d.ts +3 -0
  39. package/dist/commands/hunt.d.ts.map +1 -0
  40. package/dist/commands/hunt.js +181 -0
  41. package/dist/commands/hunt.js.map +1 -0
  42. package/dist/commands/mcp.d.ts +3 -0
  43. package/dist/commands/mcp.d.ts.map +1 -0
  44. package/dist/commands/mcp.js +339 -0
  45. package/dist/commands/mcp.js.map +1 -0
  46. package/dist/commands/onboard.d.ts +3 -0
  47. package/dist/commands/onboard.d.ts.map +1 -0
  48. package/dist/commands/onboard.js +109 -0
  49. package/dist/commands/onboard.js.map +1 -0
  50. package/dist/config/index.d.ts.map +1 -1
  51. package/dist/config/index.js +2 -7
  52. package/dist/config/index.js.map +1 -1
  53. package/dist/indexing/indexer.d.ts +1 -1
  54. package/dist/indexing/indexer.d.ts.map +1 -1
  55. package/dist/indexing/indexer.js +112 -89
  56. package/dist/indexing/indexer.js.map +1 -1
  57. package/dist/indexing/indexer.test.js +58 -2
  58. package/dist/indexing/indexer.test.js.map +1 -1
  59. package/dist/indexing/watcher.js +1 -1
  60. package/dist/indexing/watcher.js.map +1 -1
  61. package/dist/indexing/watcher.test.js +53 -16
  62. package/dist/indexing/watcher.test.js.map +1 -1
  63. package/dist/model.d.ts.map +1 -1
  64. package/dist/model.js +32 -426
  65. package/dist/model.js.map +1 -1
  66. package/dist/router/fallback.test.js +67 -0
  67. package/dist/router/fallback.test.js.map +1 -1
  68. package/dist/router/index.d.ts.map +1 -1
  69. package/dist/router/index.js +23 -2
  70. package/dist/router/index.js.map +1 -1
  71. package/dist/session/manager.d.ts +2 -2
  72. package/dist/session/manager.d.ts.map +1 -1
  73. package/dist/session/manager.js +9 -6
  74. package/dist/session/manager.js.map +1 -1
  75. package/dist/session/manager.test.js +5 -0
  76. package/dist/session/manager.test.js.map +1 -1
  77. package/dist/session/sqlite.d.ts +4 -0
  78. package/dist/session/sqlite.d.ts.map +1 -1
  79. package/dist/session/sqlite.js +17 -0
  80. package/dist/session/sqlite.js.map +1 -1
  81. package/dist/session/sqlite.test.js +10 -1
  82. package/dist/session/sqlite.test.js.map +1 -1
  83. package/dist/types.d.ts +17 -5
  84. package/dist/types.d.ts.map +1 -1
  85. package/dist/types.test.js +19 -0
  86. package/dist/types.test.js.map +1 -1
  87. package/package.json +2 -2
@@ -4,158 +4,16 @@ import path from 'path';
4
4
  import pc from 'picocolors';
5
5
  import { executeToolCalls } from '../tools/executor.js';
6
6
  import { spawnBackgroundAgent } from '../agents/background.js';
7
- import { handleSpecCommand, getGitRepoInfo } from '../agents/loop.js';
7
+ import { handleSpecCommand } from '../agents/loop.js';
8
8
  import { generateSpecContract, loadSpecContract, formatSpecForPrompt } from '../agents/spec.js';
9
9
  import { roleLabel } from '../agents/roles.js';
10
10
  import { turnSeparator } from '../formatting.js';
11
- import { execSync } from 'child_process';
12
- import { loadConfig } from '../config/index.js';
13
- import { scanStagedDiffForSecrets } from '../security/secret-detector.js';
14
11
  import { errMessage } from '../utils/errors.js';
15
- import { discoverLocalServers, saveConfig } from '../config/index.js';
16
- import { messageText } from '../types.js';
17
- import { safeGitResetHard, safeBranchDelete, safeBranchSwitch, allowDestroyFromArgs, detectBaseBranch, safeMergeToBase } from '../git/safe-git.js';
18
- /**
19
- * Normalize a raw /autopilot argument into a clean feature description.
20
- * Strips a leading slash-command token (e.g. if the user re-pastes
21
- * "/autopilot ...") so it never leaks into the orchestration goal, branch
22
- * slug, or delegated sub-task text. Returns '' for empty/whitespace input.
23
- */
24
- export function normalizeAutopilotIdea(raw) {
25
- return raw.trim().replace(/^\/\S+\s*/, '').trim();
26
- }
27
- // Secret / credential filename patterns that must never be committed by an
28
- // autonomous run, even if a sub-agent stages them.
29
- const SECRET_FILE_PATTERN = /(\.env(\..*)?|.*\.key|.*\.pem|.*\.pfx|credentials.*|secrets?.*|.*id_rsa.*)$/i;
30
- function isGitIgnored(cwd, file) {
31
- try {
32
- execSync(`git check-ignore --quiet ${JSON.stringify(file)}`, { cwd, stdio: 'ignore', windowsHide: true });
33
- return true;
34
- }
35
- catch {
36
- return false;
37
- }
38
- }
39
- // Stage all changes but always unstage secret-looking or gitignored files so an
40
- // autonomous run can never commit a .env / credential / build artifact the repo
41
- // intended to keep untracked.
42
- function safeGitAdd(cwd) {
43
- try {
44
- execSync('git add -A', { cwd, stdio: 'ignore', windowsHide: true });
45
- }
46
- catch {
47
- return;
48
- }
49
- try {
50
- const out = execSync('git diff --cached --name-only', { cwd, encoding: 'utf8', windowsHide: true });
51
- const staged = out.split('\n').map((s) => s.trim()).filter(Boolean);
52
- const exclude = staged.filter((f) => SECRET_FILE_PATTERN.test(f) || isGitIgnored(cwd, f));
53
- if (exclude.length > 0) {
54
- execSync(`git reset -q -- ${exclude.map((f) => JSON.stringify(f)).join(' ')}`, { cwd, stdio: 'ignore', windowsHide: true });
55
- console.log(pc.dim(`[CHECK] Excluded ${exclude.length} secret/ignored file(s) from commit (e.g. .env) — not staged.`));
56
- }
57
- }
58
- catch {
59
- // best-effort
60
- }
61
- // Pre-commit guard: refuse to proceed if the remaining staged diff still
62
- // contains a credential in an added line. The autopilot would otherwise
63
- // commit a leaked key. We throw so the caller's catch logs and stops.
64
- try {
65
- const enabled = loadConfig().security?.preCommitGuard !== false;
66
- if (enabled) {
67
- const hits = scanStagedDiffForSecrets(cwd);
68
- if (hits.length > 0) {
69
- throw new Error(`pre-commit guard: staged diff contains ${hits.length} credential line(s). Unstage and remove them (rotate the secret) before committing.`);
70
- }
71
- }
72
- }
73
- catch (e) {
74
- if (e instanceof Error && e.message.startsWith('pre-commit guard:'))
75
- throw e;
76
- // loadConfig/path failures are best-effort: don't block the commit on them
77
- }
78
- }
79
- // Gate the autopilot commit on the target project's own build + test scripts.
80
- // Using the project's scripts (not a hand-rolled tsc invocation) keeps
81
- // tsconfig/module-resolution correct and catches broken or empty test files
82
- // that a sub-agent may have left behind. If the project declares no
83
- // build/test scripts (e.g. a bare repo), the gate is skipped — the
84
- // orchestrator already verified during the run.
85
- export async function runAutopilotVerify(cwd) {
86
- // Locate the project(s) to verify. A greenfield scaffold may live in a subdirectory
87
- // (e.g. ./daedalus-scan), not at the repo root — so if there's no package.json at
88
- // cwd, scan immediate subdirectories for one. Checking only the repo root is a false
89
- // negative: it returns "ok" with nothing verified, which let autopilot merge broken
90
- // code (see sandbox run where the project lived in ./daedalus-scan).
91
- const projects = [];
92
- const rootPkg = path.join(cwd, 'package.json');
93
- if (fs.existsSync(rootPkg)) {
94
- projects.push(cwd);
95
- }
96
- else {
97
- let entries = [];
98
- try {
99
- entries = fs.readdirSync(cwd, { withFileTypes: true });
100
- }
101
- catch { /* ignore */ }
102
- for (const e of entries) {
103
- if (!e.isDirectory() || e.name === 'node_modules' || e.name.startsWith('.'))
104
- continue;
105
- if (fs.existsSync(path.join(cwd, e.name, 'package.json')))
106
- projects.push(path.join(cwd, e.name));
107
- }
108
- }
109
- if (projects.length === 0) {
110
- // No package.json anywhere — nothing to verify against.
111
- return { ok: true, detail: '' };
112
- }
113
- for (const proj of projects) {
114
- let scripts = {};
115
- try {
116
- scripts = JSON.parse(fs.readFileSync(path.join(proj, 'package.json'), 'utf8')).scripts ?? {};
117
- }
118
- catch {
119
- continue;
120
- }
121
- // Ensure deps are installed so build/test tooling (tsc, vitest) exists. Skipping this
122
- // means even a correct project fails verification because the binaries aren't on disk.
123
- if (!fs.existsSync(path.join(proj, 'node_modules'))) {
124
- try {
125
- execSync('npm install', { cwd: proj, stdio: 'ignore', windowsHide: true });
126
- }
127
- catch (e) {
128
- const msg = e instanceof Error ? errMessage(e) : String(e);
129
- return { ok: false, detail: `npm install failed in ${path.relative(cwd, proj) || '.'}: ${msg.split('\n')[0]}` };
130
- }
131
- }
132
- for (const script of ['build', 'test']) {
133
- if (!scripts[script])
134
- continue;
135
- try {
136
- execSync(`npm run ${script}`, { cwd: proj, stdio: 'ignore', windowsHide: true });
137
- }
138
- catch (e) {
139
- const msg = e instanceof Error ? errMessage(e) : String(e);
140
- return { ok: false, detail: `npm run ${script} failed in ${path.relative(cwd, proj) || '.'}: ${msg.split('\n')[0]}` };
141
- }
142
- }
143
- }
144
- return { ok: true, detail: '' };
145
- }
146
- function writeAutopilotManifest(m) {
147
- try {
148
- const dir = path.join(process.cwd(), '.daedalus');
149
- fs.mkdirSync(dir, { recursive: true });
150
- const file = path.join(dir, `run-${Date.now()}.json`);
151
- fs.writeFileSync(file, JSON.stringify(m, null, 2), 'utf8');
152
- console.log(pc.dim(`[INFO] Run manifest written to ${file}`));
153
- }
154
- catch {
155
- // best-effort — manifest is a convenience, never block the session on it
156
- }
157
- }
158
- export { writeAutopilotManifest };
12
+ import { autopilotCommand, normalizeAutopilotIdea, runAutopilotVerify, writeAutopilotManifest, } from './autopilot.js';
13
+ import { mcpCommand } from './mcp.js';
14
+ import { huntCommand } from './hunt.js';
15
+ import { onboardCommand } from './onboard.js';
16
+ export { normalizeAutopilotIdea, runAutopilotVerify, writeAutopilotManifest, autopilotCommand, mcpCommand, huntCommand, onboardCommand, };
159
17
  export const agentCommands = [
160
18
  {
161
19
  name: '/spawn',
@@ -428,461 +286,18 @@ export const agentCommands = [
428
286
  console.log(pc.gray(` Interfaces: ${spec.interfaces.length} | Functions: ${spec.functions.length} | Test Cases: ${spec.testCases.length}`));
429
287
  console.log(pc.gray(` Saved to .daedalus/spec.md & .daedalus/spec.json`));
430
288
  if (!trimmed.toLowerCase().startsWith('generate ')) {
431
- // Also run GitHub issue creation if desired
432
289
  await handleSpecCommand(goal, ctx);
433
290
  }
434
291
  }
435
292
  },
436
- {
437
- name: '/mcp',
438
- description: 'Manage MCP servers: explore, search, install, list, remove, info',
439
- usage: '/mcp <subcommand> [args]',
440
- helpText: 'Configure and interact with Model Context Protocol (MCP) servers.\n\nSubcommands:\n explore, ex Browse curated featured community MCP servers\n list, l List all installed MCP servers and their active state\n search, s <query> Search the public MCP Registry for available servers\n install, i <name> Install an MCP server from the registry\n remove, rm <name> Uninstall an MCP server\n info <name> Display metadata and information for a registry server\n enable <name> Enable a configured server\n disable <name> Disable a configured server without removing it',
441
- execute: async (args, _ctx) => {
442
- const parts = args.trim().split(/\s+/);
443
- const sub = parts[0]?.toLowerCase();
444
- const rest = parts.slice(1).join(' ').trim();
445
- const { searchRegistry, fetchServerByName, fetchAllServers, registryEntryToConfig, addServerToConfig, removeServerFromConfig, listInstalledServers, toggleServer } = await import('../tools/mcp/manager.js');
446
- const { mcpRegistry } = await import('../tools/mcp/registry.js');
447
- switch (sub) {
448
- case 'bundle':
449
- case 'b': {
450
- const { listMcpBundles, getMcpBundle } = await import('../tools/mcp/bundles.js');
451
- const bundleSub = parts[1]?.toLowerCase();
452
- const bundleTarget = parts[2];
453
- if (bundleSub === 'list' || !bundleSub) {
454
- console.log(pc.cyan('\n=== One-Click MCP Tool Bundles ===\n'));
455
- for (const b of listMcpBundles()) {
456
- console.log(` ${pc.bold(b.name.padEnd(16))} ${pc.dim(b.description)}`);
457
- for (const s of b.servers) {
458
- console.log(` ${pc.dim('•')} ${pc.cyan(s.name)}: ${s.description}`);
459
- }
460
- console.log();
461
- }
462
- console.log(pc.dim(' Install a bundle: /mcp bundle install <name>\n'));
463
- return;
464
- }
465
- if (bundleSub === 'install') {
466
- if (!bundleTarget) {
467
- console.log(pc.yellow(' Usage: /mcp bundle install <name>'));
468
- return;
469
- }
470
- const bundle = getMcpBundle(bundleTarget);
471
- if (!bundle) {
472
- console.log(pc.red(` Bundle "${bundleTarget}" not found. Run /mcp bundle list to see presets.`));
473
- return;
474
- }
475
- console.log(pc.cyan(`\n [MCP BUNDLE] Installing preset bundle: ${pc.bold(bundle.name)}...`));
476
- for (const s of bundle.servers) {
477
- addServerToConfig({ name: s.name, transport: 'stdio', command: s.command, args: s.args, enabled: true });
478
- console.log(pc.green(` [OK] Installed MCP server "${s.name}"`));
479
- }
480
- console.log(pc.dim('\n Run /mcp reconnect to activate new servers.\n'));
481
- return;
482
- }
483
- console.log(pc.yellow(' Usage: /mcp bundle [list | install <name>]'));
484
- return;
485
- }
486
- case 'search':
487
- case 's': {
488
- if (!rest) {
489
- console.log(pc.yellow(' Usage: /mcp search <query>'));
490
- return;
491
- }
492
- console.log(pc.dim(` Searching registry for "${rest}"...`));
493
- try {
494
- const results = await searchRegistry(rest, 15);
495
- if (results.length === 0) {
496
- console.log(pc.yellow(' No servers found. Try a broader search.'));
497
- return;
498
- }
499
- console.log(`\n ${pc.bold(`Found ${results.length} server(s):`)}`);
500
- for (const s of results) {
501
- const label = s.title || s.name;
502
- const desc = s.description.length > 80 ? s.description.slice(0, 80) + '…' : s.description;
503
- const remote = s.remotes?.[0]?.url || '';
504
- const pkg = s.packages?.[0]?.identifier || '';
505
- const source = remote || pkg || '(no install info)';
506
- const installType = s.packages ? 'stdio' : s.remotes ? 'http' : '?';
507
- console.log(` ${pc.cyan(label)}`);
508
- console.log(` ${pc.dim(desc)}`);
509
- console.log(` ${pc.gray('Install:')} ${pc.dim(source)} (${installType})`);
510
- console.log();
511
- }
512
- }
513
- catch (err) {
514
- console.log(pc.red(` Search failed: ${errMessage(err)}`));
515
- }
516
- return;
517
- }
518
- case 'install':
519
- case 'i': {
520
- if (!rest) {
521
- console.log(pc.yellow(' Usage: /mcp install <server-name>'));
522
- console.log(pc.dim(' First search for a server with: /mcp search <query>'));
523
- return;
524
- }
525
- console.log(pc.dim(` Fetching "${rest}" from registry...`));
526
- try {
527
- const entry = await fetchServerByName(rest);
528
- if (!entry) {
529
- console.log(pc.yellow(` Server "${rest}" not found in registry. Try /mcp search first.`));
530
- return;
531
- }
532
- const config = registryEntryToConfig(entry);
533
- if (!config) {
534
- console.log(pc.yellow(` Cannot install "${rest}": no stdio package or remote URL found.`));
535
- return;
536
- }
537
- const result = addServerToConfig(config);
538
- if (result.success) {
539
- console.log(pc.green(` ${result.message}`));
540
- console.log(pc.dim(' Restart Daedalus or reconnect to load the new server.'));
541
- }
542
- else {
543
- console.log(pc.yellow(` ${result.message}`));
544
- }
545
- }
546
- catch (err) {
547
- console.log(pc.red(` Install failed: ${errMessage(err)}`));
548
- }
549
- return;
550
- }
551
- case 'explore':
552
- case 'ex': {
553
- console.log(pc.dim(' Browsing the MCP registry...\n'));
554
- try {
555
- const all = await fetchAllServers(100);
556
- const local = all.filter(s => s.packages && s.packages.length > 0);
557
- const remote = all.filter(s => s.remotes && s.remotes.length > 0);
558
- console.log(` ${pc.bold(`Found ${all.length} servers in registry`)}`);
559
- const showSample = (list, label, max = 5) => {
560
- if (list.length === 0)
561
- return;
562
- console.log(`\n ${pc.underline(label)} (${list.length} available)`);
563
- for (const s of list.slice(0, max)) {
564
- const pkg = s.packages?.[0]?.identifier || '';
565
- const url = s.remotes?.[0]?.url || '';
566
- const source = pkg || url;
567
- const info = s.description.length > 55 ? s.description.slice(0, 53) + '…' : s.description;
568
- const showName = s.name.length > 28 ? s.name.slice(0, 26) + '…' : s.name;
569
- console.log(` ${pc.cyan(showName.padEnd(30))} ${pc.dim(info)}`);
570
- console.log(` ${' '.repeat(30)} ${pc.gray('→')} ${pc.dim(source)}`);
571
- }
572
- if (list.length > max) {
573
- console.log(` ${' '.repeat(30)} ${pc.dim(`… and ${list.length - max} more`)}`);
574
- }
575
- };
576
- showSample(local, 'Local (stdio — install & run)', 6);
577
- showSample(remote, 'Remote (HTTP — cloud API)', 6);
578
- console.log(`\n ${pc.dim('Tip: /mcp search <query> to find specific servers')}`);
579
- }
580
- catch (err) {
581
- console.log(pc.red(` Explore failed: ${errMessage(err)}`));
582
- }
583
- return;
584
- }
585
- case 'list':
586
- case 'ls':
587
- case 'l': {
588
- const servers = listInstalledServers();
589
- if (servers.length === 0) {
590
- console.log(pc.yellow(' No MCP servers installed.'));
591
- console.log(pc.dim(' Try /mcp explore to see what\'s available.'));
592
- return;
593
- }
594
- const connected = mcpRegistry.getConnectedServers();
595
- console.log(`\n ${pc.bold('Installed MCP Servers:')}`);
596
- for (const s of servers) {
597
- const status = connected.includes(s.name) ? pc.green('●') : s.enabled ? pc.yellow('○') : pc.red('○');
598
- const state = connected.includes(s.name) ? pc.green('connected')
599
- : s.enabled ? pc.yellow('pending')
600
- : pc.red('disabled');
601
- console.log(` ${status} ${pc.cyan(s.name.padEnd(20))} ${pc.dim(s.transport.padEnd(6))} ${state}`);
602
- }
603
- console.log();
604
- return;
605
- }
606
- case 'remove':
607
- case 'rm':
608
- case 'r': {
609
- if (!rest) {
610
- console.log(pc.yellow(' Usage: /mcp remove <server-name>'));
611
- return;
612
- }
613
- const result = removeServerFromConfig(rest);
614
- if (result.success) {
615
- console.log(pc.green(` ${result.message}`));
616
- }
617
- else {
618
- console.log(pc.yellow(` ${result.message}`));
619
- }
620
- return;
621
- }
622
- case 'info': {
623
- if (!rest) {
624
- console.log(pc.yellow(' Usage: /mcp info <server-name>'));
625
- return;
626
- }
627
- try {
628
- console.log(pc.dim(` Fetching "${rest}" from registry...`));
629
- const entry = await fetchServerByName(rest);
630
- if (!entry) {
631
- console.log(pc.yellow(` Server "${rest}" not found.`));
632
- return;
633
- }
634
- console.log(`\n ${pc.bold(entry.title || entry.name)}`);
635
- console.log(` ${pc.dim(entry.description)}`);
636
- console.log(` ${pc.gray('Name:')} ${entry.name}`);
637
- console.log(` ${pc.gray('Version:')} ${entry.version}`);
638
- if (entry.websiteUrl)
639
- console.log(` ${pc.gray('Website:')} ${entry.websiteUrl}`);
640
- if (entry.repository?.url)
641
- console.log(` ${pc.gray('Source:')} ${entry.repository.url}`);
642
- if (entry.remotes && entry.remotes.length > 0) {
643
- console.log(`\n ${pc.bold('Remote endpoints:')}`);
644
- for (const r of entry.remotes) {
645
- console.log(` ${pc.cyan(r.type)} ${pc.dim(r.url)}`);
646
- if (r.headers) {
647
- for (const h of r.headers) {
648
- const req = h.isRequired ? pc.yellow(' (required)') : '';
649
- const secret = h.isSecret ? pc.dim(' [secret]') : '';
650
- console.log(` ${pc.gray('Header:')} ${h.name}${req}${secret}`);
651
- }
652
- }
653
- }
654
- }
655
- if (entry.packages && entry.packages.length > 0) {
656
- console.log(`\n ${pc.bold('Packages:')}`);
657
- for (const p of entry.packages) {
658
- const [cmd, ...args] = p.registryType === 'npm' ? ['npx', '-y', p.identifier]
659
- : p.registryType === 'pypi' ? ['uvx', p.identifier]
660
- : [p.identifier];
661
- console.log(` ${pc.cyan(p.registryType)} ${pc.dim(`${cmd} ${args.join(' ')}`)}`);
662
- if (p.environmentVariables) {
663
- for (const env of p.environmentVariables) {
664
- const req = env.isRequired ? pc.yellow(' (required)') : '';
665
- const secret = env.isSecret ? pc.dim(' [secret]') : '';
666
- console.log(` ${pc.gray('Env:')} ${env.name}${req}${secret}`);
667
- if (env.description)
668
- console.log(` ${pc.dim(env.description)}`);
669
- }
670
- }
671
- }
672
- }
673
- console.log();
674
- }
675
- catch (err) {
676
- console.log(pc.red(` Info fetch failed: ${errMessage(err)}`));
677
- }
678
- return;
679
- }
680
- case 'reconnect':
681
- case 'rc': {
682
- const { loadConfig } = await import('../config/index.js');
683
- const config = loadConfig();
684
- const mcpConfigs = Object.entries(config.tools.mcpServers)
685
- .filter(([_, s]) => s.enabled)
686
- .map(([name, s]) => ({
687
- name,
688
- transport: s.transport,
689
- command: s.command,
690
- args: s.args,
691
- url: s.url,
692
- headers: s.headers,
693
- enabled: s.enabled,
694
- }));
695
- const already = mcpRegistry.getConnectedServers();
696
- const newServers = mcpConfigs.filter(c => !already.includes(c.name));
697
- if (newServers.length === 0) {
698
- if (mcpConfigs.length === 0) {
699
- console.log(pc.yellow(' No enabled MCP servers configured. Install one with /mcp install'));
700
- }
701
- else {
702
- console.log(pc.dim(' All enabled MCP servers are already connected.'));
703
- }
704
- return;
705
- }
706
- mcpRegistry.setConfigs(mcpConfigs);
707
- const connected = [];
708
- const failed = [];
709
- for (const s of newServers) {
710
- try {
711
- await mcpRegistry.connectServer(s);
712
- connected.push(s.name);
713
- }
714
- catch (err) {
715
- failed.push(`${s.name} (${errMessage(err)})`);
716
- }
717
- }
718
- if (connected.length > 0) {
719
- const totalTools = mcpRegistry.getToolDefinitions().length;
720
- console.log(pc.green(` Connected: ${connected.join(', ')} (${totalTools} MCP tools total)`));
721
- }
722
- if (failed.length > 0) {
723
- console.log(pc.yellow(` Failed: ${failed.join(', ')}`));
724
- }
725
- return;
726
- }
727
- case 'enable':
728
- case 'e': {
729
- if (!rest) {
730
- console.log(pc.yellow(' Usage: /mcp enable <server-name>'));
731
- return;
732
- }
733
- const enableResult = toggleServer(rest, true);
734
- console.log(enableResult.success ? pc.green(` ${enableResult.message}`) : pc.yellow(` ${enableResult.message}`));
735
- return;
736
- }
737
- case 'disable':
738
- case 'd': {
739
- if (!rest) {
740
- console.log(pc.yellow(' Usage: /mcp disable <server-name>'));
741
- return;
742
- }
743
- const disableResult = toggleServer(rest, false);
744
- console.log(disableResult.success ? pc.green(` ${disableResult.message}`) : pc.yellow(` ${disableResult.message}`));
745
- return;
746
- }
747
- default:
748
- console.log(pc.bold('\n MCP Server Manager'));
749
- console.log(` ${pc.cyan('/mcp explore')} ${pc.dim('Browse available servers in the registry')}`);
750
- console.log(` ${pc.cyan('/mcp search <query>')} ${pc.dim('Search MCP registry + Smithery')}`);
751
- console.log(` ${pc.cyan('/mcp install <name>')} ${pc.dim('Install a server from the registry')}`);
752
- console.log(` ${pc.cyan('/mcp list')} ${pc.dim('List installed servers')}`);
753
- console.log(` ${pc.cyan('/mcp remove <name>')} ${pc.dim('Remove an installed server')}`);
754
- console.log(` ${pc.cyan('/mcp info <name>')} ${pc.dim('Show server details')}`);
755
- console.log(` ${pc.cyan('/mcp reconnect')} ${pc.dim('Reconnect all enabled servers')}`);
756
- console.log(` ${pc.cyan('/mcp enable <name>')} ${pc.dim('Enable a disabled server')}`);
757
- console.log(` ${pc.cyan('/mcp disable <name>')} ${pc.dim('Disable a server without removing it')}`);
758
- console.log(`\n ${pc.bold('Popular npm MCP servers (add to ~/.daedalus/config.json):')}`);
759
- console.log(` ${pc.dim(' "server-name": { "transport": "stdio", "command": "npx", "args": ["-y", "@npm/package"], "enabled": true }')}`);
760
- console.log(` ${pc.gray('→')} ${pc.cyan('filesystem')} ${pc.dim('npx -y @modelcontextprotocol/server-filesystem <allowed-dir>')}`);
761
- console.log(` ${pc.gray('→')} ${pc.cyan('puppeteer')} ${pc.dim('npx -y @modelcontextprotocol/server-puppeteer')}`);
762
- console.log(` ${pc.gray('→')} ${pc.cyan('memory')} ${pc.dim('npx -y @modelcontextprotocol/server-memory')}`);
763
- console.log(` ${pc.gray('→')} ${pc.cyan('fetch')} ${pc.dim('npx -y @modelcontextprotocol/server-fetch')}`);
764
- console.log(` ${pc.gray('→')} ${pc.cyan('sequential-thinking')} ${pc.dim('npx -y @modelcontextprotocol/server-sequential-thinking')}`);
765
- console.log(` ${pc.gray('→')} ${pc.cyan('github')} ${pc.dim('npx -y @github/github-mcp-server')}`);
766
- console.log(` ${pc.gray('→')} ${pc.cyan('sqlite')} ${pc.dim('npx -y @modelcontextprotocol/server-sqlite <db-path>')}`);
767
- console.log(` ${pc.dim(' Then run /mcp reconnect to load them.')}`);
768
- console.log();
769
- }
770
- }
771
- },
772
- {
773
- name: '/onboard',
774
- description: 'First-time setup — discover local models, configure, and test',
775
- usage: '/onboard',
776
- helpText: 'Run the interactive setup wizard to scan your local network/environment for model servers, select a primary model tier, and test its output/diagnostics.',
777
- execute: async (_args, ctx) => {
778
- const config = ctx.config;
779
- console.log(pc.bold(pc.cyan('\n╔══════════════════════════════════════╗')));
780
- console.log(pc.bold(pc.cyan('║ Daedalus Onboarding ║')));
781
- console.log(pc.bold(pc.cyan('╚══════════════════════════════════════╝')));
782
- console.log();
783
- console.log('Daedalus runs AI models locally on your machine.');
784
- console.log('First, I need to know which model server to use.');
785
- console.log();
786
- // Step 1: Discover local model servers
787
- console.log(pc.bold('🔍 Scanning for local model servers...'));
788
- const discovered = await discoverLocalServers();
789
- let chosenEndpoint = '';
790
- let chosenModel = '';
791
- if (discovered.length > 0) {
792
- console.log(pc.green(`\n Found ${discovered.length} running server(s):\n`));
793
- for (let i = 0; i < discovered.length; i++) {
794
- const s = discovered[i];
795
- console.log(` ${i + 1}. ${pc.cyan(s.name)} at ${s.endpoint}`);
796
- for (const m of s.models.slice(0, 3)) {
797
- console.log(` - ${m}`);
798
- }
799
- if (s.models.length > 3) {
800
- console.log(pc.gray(` ... and ${s.models.length - 3} more`));
801
- }
802
- }
803
- console.log();
804
- const serverChoice = await ctx.askLine(`Select a server (1-${discovered.length}) or press Enter to add manually: `);
805
- const idx = parseInt(serverChoice) - 1;
806
- if (idx >= 0 && idx < discovered.length) {
807
- const server = discovered[idx];
808
- chosenEndpoint = server.endpoint;
809
- if (server.models.length === 1) {
810
- chosenModel = server.models[0];
811
- }
812
- else {
813
- console.log(`\nModels on ${pc.cyan(server.name)}:`);
814
- for (let i = 0; i < server.models.length; i++) {
815
- console.log(` ${i + 1}. ${server.models[i]}`);
816
- }
817
- const modelChoice = await ctx.askLine(`Select a model (1-${server.models.length}): `);
818
- const midx = parseInt(modelChoice) - 1;
819
- if (midx >= 0 && midx < server.models.length) {
820
- chosenModel = server.models[midx];
821
- }
822
- }
823
- }
824
- }
825
- if (!chosenEndpoint) {
826
- console.log(`\nEnter your model server details manually.`);
827
- chosenEndpoint = await ctx.askLine('API endpoint (e.g. http://localhost:1234/v1): ');
828
- if (!chosenEndpoint)
829
- chosenEndpoint = 'http://localhost:1234/v1';
830
- chosenModel = await ctx.askLine('Model name (e.g. qwen2.5-coder-7b-instruct): ');
831
- if (!chosenModel)
832
- chosenModel = 'auto';
833
- }
834
- if (!chosenModel)
835
- chosenModel = 'auto';
836
- // Step 2: Add to config
837
- const entry = {
838
- name: chosenModel,
839
- endpoint: chosenEndpoint,
840
- model: chosenModel,
841
- priority: 1,
842
- enabled: true,
843
- };
844
- // Replace any existing chain or add to it
845
- config.router.chain = [entry, ...config.router.chain.filter((e) => e.endpoint !== chosenEndpoint)];
846
- saveConfig(config);
847
- console.log(pc.green(`\n✓ Added model "${pc.bold(chosenModel)}" at ${chosenEndpoint}`));
848
- // Step 3: Test the model
849
- const testPrompt = await ctx.askLine('\nRun a quick test? (Y/n): ');
850
- if (testPrompt.toLowerCase() !== 'n') {
851
- console.log(pc.dim('\nSending test request...'));
852
- try {
853
- const start = Date.now();
854
- const testMessages = [
855
- { role: 'system', content: 'You are a helpful assistant. Respond in 1-2 sentences.' },
856
- { role: 'user', content: 'Say hello and confirm you are working.' },
857
- ];
858
- const testRouter = ctx.router;
859
- const completion = await testRouter.chat.completions.create({
860
- model: chosenModel,
861
- messages: testMessages,
862
- temperature: 0.1,
863
- });
864
- const elapsed = Date.now() - start;
865
- const text = messageText(completion.choices?.[0]?.message?.content ?? '') || '(no response)';
866
- console.log(pc.green(`\n✓ Response received in ${elapsed}ms:`));
867
- console.log(` ${pc.white(text)}`);
868
- }
869
- catch (err) {
870
- console.log(pc.yellow(`\n⚠ Test failed: ${errMessage(err)}`));
871
- console.log(' The model is configured but may need troubleshooting.');
872
- console.log(` Check ${pc.cyan(ctx.configDir + '/config.json')} and verify the endpoint.`);
873
- }
874
- }
875
- console.log(pc.green(`\n✓ Onboarding complete! Configuration saved to:`));
876
- console.log(` ${pc.cyan(ctx.configDir + '/config.json')}`);
877
- console.log(`\nType ${pc.cyan('?')} to see all available commands, or just start typing.`);
878
- }
879
- },
293
+ mcpCommand,
294
+ onboardCommand,
880
295
  {
881
296
  name: '/tui',
882
297
  description: 'Toggle the Terminal User Interface (TUI) dashboard',
883
298
  usage: '/tui',
884
299
  helpText: 'Switch between standard REPL chat mode and the side-by-side Terminal dashboard mode (which includes resource charts, model settings, and context monitors).',
885
- execute: async (args, ctx) => {
300
+ execute: async (_args, ctx) => {
886
301
  if (!ctx.rl) {
887
302
  throw new Error('SWITCH_MODE_CLI');
888
303
  }
@@ -937,433 +352,8 @@ export const agentCommands = [
937
352
  }
938
353
  }
939
354
  },
940
- {
941
- name: '/autopilot',
942
- description: 'Autonomously implement a feature: branch, code, test, commit, and PR',
943
- usage: '/autopilot <feature description> [--allow-destroy]',
944
- helpText: 'End-to-end autonomous feature development. Creates a branch, plans and implements the feature, runs verification, commits, pushes, and opens a pull request.\\n\\nFlow:\\n 1. Interactive Q&A to refine the feature spec\\n 2. Creates a git branch (daedalus-autopilot-<slug>)\\n 3. Runs the multi-agent orchestrator to implement it\\n 4. Verifies with build/lint/tests\\n 5. Commits and pushes to GitHub\\n 6. Opens a Pull Request against main\\n\\nRequires a GitHub repository with a configured remote origin.\\n\\nSafety: in local-only mode (no remote), the working tree and branch are NEVER destroyed on failure — changes are kept for inspection. Pass --allow-destroy to override this and discard the branch on failure (only for throwaway repos).',
945
- execute: async (args, ctx) => {
946
- // Strip a leading slash-command token (e.g. if the user re-pasted
947
- // "/autopilot ...") so it never leaks into the goal, branch slug, or
948
- // delegated sub-tasks.
949
- const idea = normalizeAutopilotIdea(args);
950
- if (!idea) {
951
- console.log(pc.yellow('[WARN] Usage: /autopilot <feature description>'));
952
- return;
953
- }
954
- const manifest = {
955
- feature: idea,
956
- branch: '',
957
- remote: null,
958
- mode: 'non-git',
959
- outcome: 'stopped-error',
960
- tasksPlanned: 0,
961
- tasksDone: 0,
962
- filesChanged: [],
963
- testResult: null,
964
- finishedAt: '',
965
- };
966
- const emitManifest = () => {
967
- manifest.finishedAt = new Date().toISOString();
968
- writeAutopilotManifest(manifest);
969
- };
970
- try {
971
- let isGitRepo = true;
972
- try {
973
- execSync('git rev-parse --is-inside-work-tree', { cwd: ctx.toolContext.projectRoot, stdio: 'ignore', windowsHide: true });
974
- }
975
- catch {
976
- isGitRepo = false;
977
- }
978
- if (!isGitRepo) {
979
- console.log(pc.cyan('[INFO] Non-git directory detected. Auto-initializing Git repository for autonomous branch safety...'));
980
- try {
981
- const cwd = ctx.toolContext.projectRoot || process.cwd();
982
- execSync('git init', { cwd, windowsHide: true });
983
- const gitIgnorePath = path.join(cwd, '.gitignore');
984
- if (!fs.existsSync(gitIgnorePath)) {
985
- fs.writeFileSync(gitIgnorePath, "node_modules/\ndist/\n.daedalus/\n", 'utf8');
986
- }
987
- safeGitAdd(cwd);
988
- execSync('git commit -m "initial clean setup"', { cwd, windowsHide: true });
989
- isGitRepo = true;
990
- console.log(pc.green('[OK] Git repository initialized with tracking branch support.'));
991
- }
992
- catch {
993
- console.log(pc.yellow('[WARNING] Working directory is not a git repository. Autonomous changes will NOT be tracked in a git branch.'));
994
- }
995
- }
996
- const repoInfo = isGitRepo ? getGitRepoInfo(ctx.toolContext.projectRoot) : null;
997
- if (!repoInfo) {
998
- console.log(pc.yellow('[INFO] No GitHub remote found. Running in local-only mode (no PR will be created).'));
999
- }
1000
- const slug = idea.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 40);
1001
- const branchName = `daedalus-autopilot-${slug}`;
1002
- manifest.remote = repoInfo ? `${repoInfo.owner}/${repoInfo.repo}` : null;
1003
- manifest.mode = !isGitRepo ? 'non-git' : repoInfo ? 'git' : 'local-only';
1004
- manifest.branch = branchName;
1005
- if (isGitRepo) {
1006
- try {
1007
- // Always branch from the repo's stable base (main/master) — not from
1008
- // whatever branch the user happens to be on. This prevents run-to-run
1009
- // cruft chaining: a prior autopilot branch would otherwise leak into
1010
- // the next run. Detect and switch to base first.
1011
- const baseBranch = detectBaseBranch(ctx.toolContext.projectRoot);
1012
- execSync(`git checkout ${baseBranch}`, { cwd: ctx.toolContext.projectRoot, stdio: 'ignore', windowsHide: true });
1013
- // Re-entering a run must not discard uncommitted work already on the
1014
- // branch. safeBranchSwitch switches without -B unless --allow-destroy.
1015
- const allowDestroy = allowDestroyFromArgs(idea) || !!repoInfo;
1016
- safeBranchSwitch(branchName, { cwd: ctx.toolContext.projectRoot, allowDestroy, branch: branchName });
1017
- console.log(pc.green(`[OK] Switched to branch: ${branchName} (from ${baseBranch})`));
1018
- }
1019
- catch (err) {
1020
- const msg = err instanceof Error ? errMessage(err) : String(err);
1021
- console.log(pc.red(`[ERROR] Failed to create branch: ${msg}`));
1022
- return;
1023
- }
1024
- }
1025
- const goal = `Implement the following feature: ${idea}`;
1026
- console.log(pc.cyan(`\n[AUTOPILOT] Starting autonomous implementation...`));
1027
- process.env.DAEDALUS_AUTO_APPROVE = 'true';
1028
- process.env.DAEDALUS_ALLOW_INSTALL = 'true';
1029
- try {
1030
- const { Orchestrator } = await import('../agents/orchestrator.js');
1031
- const orchestrator = new Orchestrator(ctx.router, ctx.messages, ctx.toolContext, ctx.sessionManager, ctx.config?.modelOverride);
1032
- const result = await orchestrator.run(goal);
1033
- console.log(pc.white(`\n${result}`));
1034
- const orchestrationFailed = result.startsWith('Orchestration failed') || result.includes('## Orchestration Hit Verification Failures');
1035
- const wasAborted = result.includes('## Orchestration Paused');
1036
- if (orchestrationFailed || wasAborted) {
1037
- // Print Self-Evaluating Autopilot Post-Mortem Report before rolling back
1038
- const cols = process.stdout.columns || 80;
1039
- const lineLen = Math.max(20, Math.min(70, cols - 6));
1040
- console.log(`\n ${pc.bold(pc.red('─ Autopilot Post-Mortem ─'))} ${pc.dim('─'.repeat(Math.max(10, lineLen - 25)))}`);
1041
- const failed = orchestrator.results?.filter((r) => !r.success) || [];
1042
- if (failed.length > 0) {
1043
- failed.forEach((f, idx) => {
1044
- console.log(` ${pc.bold(pc.red(`❌ Failed Step ${idx + 1}:`))} ${pc.bold(`[${f.role}]`)} ${f.goal}`);
1045
- console.log(` ${pc.yellow(`📌 Diagnostic:`)} ${f.summary.split('\n')[0]}`);
1046
- });
1047
- }
1048
- else {
1049
- console.log(` ${pc.yellow('❌ Verification check failed — required files failed artifact or build checks.')}`);
1050
- }
1051
- console.log(`\n ${pc.cyan('Recommendations:')}`);
1052
- console.log(` - Target missing file: ${pc.bold(`/task create <file>`)}`);
1053
- console.log(` - Re-run autopilot: ${pc.bold(`/autopilot ${idea}`)}`);
1054
- console.log(` ${pc.dim('─'.repeat(lineLen + 2))}\n`);
1055
- throw new Error(orchestrationFailed ? 'Orchestration reported failure' : 'Orchestration was paused/aborted');
1056
- }
1057
- }
1058
- catch (err) {
1059
- const msg = err instanceof Error ? errMessage(err) : String(err);
1060
- console.log(pc.red(`\n[ERROR] Run stopped: ${msg}`));
1061
- manifest.outcome = 'stopped-error';
1062
- if (isGitRepo) {
1063
- console.log(pc.dim('[CHECK] Verification did not pass — keeping the implemented changes on the branch for review.'));
1064
- // Never destroy the working tree or branch in local-only mode. Only a
1065
- // remote-backed repo (throwaway branch) or an explicit --allow-destroy
1066
- // opt-in may discard. Otherwise we keep the branch for inspection.
1067
- const allowDestroy = allowDestroyFromArgs(idea) || !!repoInfo;
1068
- if (allowDestroy) {
1069
- try {
1070
- safeGitResetHard({ cwd: ctx.toolContext.projectRoot, allowDestroy });
1071
- execSync('git checkout main', { cwd: ctx.toolContext.projectRoot, stdio: 'ignore', windowsHide: true });
1072
- safeBranchDelete(branchName, { cwd: ctx.toolContext.projectRoot, allowDestroy });
1073
- console.log(pc.green('[OK] Branch cleaned up; main is untouched.'));
1074
- }
1075
- catch (rollbackErr) {
1076
- const rbMsg = rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr);
1077
- console.log(pc.red(`[ERROR] Cleanup failed: ${rbMsg}. Manual cleanup may be needed.`));
1078
- }
1079
- }
1080
- else {
1081
- console.log(pc.cyan(`[INFO] Local-only mode: keeping branch '${branchName}' with the implemented changes for inspection. Fix and commit manually.`));
1082
- }
1083
- }
1084
- return;
1085
- }
1086
- if (isGitRepo) {
1087
- console.log(pc.cyan('\n[AUTOPILOT] Verifying build & tests before commit...'));
1088
- const verify = await runAutopilotVerify(ctx.toolContext.projectRoot);
1089
- if (!verify.ok) {
1090
- console.log(pc.red(`\n[ERROR] Verification did not pass — holding the changes on the branch instead of committing. ${verify.detail}`));
1091
- console.log(pc.cyan(`[INFO] Branch '${branchName}' is kept with the implemented changes for inspection.`));
1092
- manifest.outcome = 'stopped-verify';
1093
- manifest.testResult = verify;
1094
- return;
1095
- }
1096
- console.log(pc.green('[OK] Build & tests passed.'));
1097
- console.log(pc.cyan('\n[AUTOPILOT] Committing changes...'));
1098
- try {
1099
- safeGitAdd(ctx.toolContext.projectRoot);
1100
- const cleanTitle = idea.replace(/[^a-zA-Z0-9 ]/g, '').trim();
1101
- execSync(`git commit -m "feat: ${cleanTitle}"`, { cwd: ctx.toolContext.projectRoot, windowsHide: true });
1102
- console.log(pc.green('[OK] Changes committed.'));
1103
- try {
1104
- const diff = execSync(`git diff --name-only HEAD~1 HEAD`, { cwd: ctx.toolContext.projectRoot, encoding: 'utf8', windowsHide: true });
1105
- manifest.filesChanged = diff.split('\n').map((s) => s.trim()).filter(Boolean);
1106
- }
1107
- catch { /* best-effort */ }
1108
- }
1109
- catch (err) {
1110
- const msg = err instanceof Error ? errMessage(err) : String(err);
1111
- if (msg.includes('nothing to commit')) {
1112
- console.log(pc.yellow('[INFO] No changes to commit.'));
1113
- }
1114
- else {
1115
- console.log(pc.red(`[ERROR] Failed to commit: ${msg}`));
1116
- return;
1117
- }
1118
- }
1119
- }
1120
- else {
1121
- console.log(pc.yellow('\n[INFO] Non-git working directory. Autonomous implementation completed directly on files.'));
1122
- }
1123
- if (repoInfo) {
1124
- console.log(pc.cyan('\n[AUTOPILOT] Pushing branch and creating PR...'));
1125
- let token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
1126
- if (!token) {
1127
- try {
1128
- token = execSync('gh auth token', { encoding: 'utf8', windowsHide: true }).trim();
1129
- }
1130
- catch {
1131
- console.log(pc.yellow('[INFO] No GitHub token found. Run `gh auth login` or set GITHUB_TOKEN.'));
1132
- console.log(pc.yellow(`[INFO] Branch ${branchName} is ready locally. Push manually.`));
1133
- return;
1134
- }
1135
- }
1136
- try {
1137
- execSync(`git push -u origin ${branchName} --force`, { cwd: ctx.toolContext.projectRoot, windowsHide: true });
1138
- const prResponse = await fetch(`https://api.github.com/repos/${repoInfo.owner}/${repoInfo.repo}/pulls`, {
1139
- method: 'POST',
1140
- headers: {
1141
- 'Authorization': `Bearer ${token}`,
1142
- 'Content-Type': 'application/json',
1143
- },
1144
- body: JSON.stringify({
1145
- title: `[Autopilot] ${idea}`,
1146
- head: branchName,
1147
- base: 'main',
1148
- body: `## Description\n\nAutonomously implemented by Daedalus Autopilot.\n\n**Feature:** ${idea}\n\n---\n_Generated by \`/autopilot\`_`,
1149
- }),
1150
- });
1151
- if (prResponse.ok) {
1152
- const pr = await prResponse.json();
1153
- console.log(pc.green(`\n[OK] Pull Request created: ${pr.html_url}`));
1154
- }
1155
- else {
1156
- const errText = await prResponse.text();
1157
- console.log(pc.red(`[ERROR] Failed to create PR: ${prResponse.status} ${errText}`));
1158
- console.log(pc.yellow(`[INFO] Branch ${branchName} is pushed. Create PR manually.`));
1159
- }
1160
- }
1161
- catch (err) {
1162
- const msg = err instanceof Error ? errMessage(err) : String(err);
1163
- console.log(pc.red(`[ERROR] Push/PR failed: ${msg}`));
1164
- console.log(pc.yellow(`[INFO] Branch ${branchName} is ready locally.`));
1165
- }
1166
- }
1167
- else {
1168
- console.log(pc.yellow('\n[INFO] No GitHub remote configured. Implementation is committed locally.'));
1169
- console.log(pc.yellow(`[INFO] Branch: ${branchName}`));
1170
- // Local-only mode: merge the verified-green feature branch back into the
1171
- // base branch so the working tree stays current and the user never has to
1172
- // manually switch back. Never force-pushes; fast-forwards when possible.
1173
- const baseBranch = detectBaseBranch(ctx.toolContext.projectRoot);
1174
- if (baseBranch !== branchName && safeMergeToBase(branchName, baseBranch, ctx.toolContext.projectRoot)) {
1175
- console.log(pc.green(`[OK] Merged ${branchName} into ${baseBranch}. You are now on ${baseBranch}.`));
1176
- }
1177
- }
1178
- console.log(pc.cyan(`\n[AUTOPILOT] Done! The feature branch was merged into the base branch — no manual switching needed.`));
1179
- manifest.outcome = repoInfo ? 'pr-opened' : 'committed-local';
1180
- }
1181
- finally {
1182
- emitManifest();
1183
- }
1184
- }
1185
- },
1186
- {
1187
- name: '/hunt',
1188
- aliases: ['/bug'],
1189
- description: 'Autonomously hunt down and fix a bug: reproduce, locate root cause, fix, verify',
1190
- usage: '/hunt <failing-test-filepath> or <bug description>',
1191
- helpText: 'End-to-end autonomous bug fixing. If given a test file path, runs it to capture the failure, searches the codebase for root cause, implements a fix, verifies the test passes, commits, pushes, and opens a pull request.\n\nFlow:\n 1. (Optional) Runs the failing test to capture error output\n 2. Creates a git branch (daedalus-hunt-<slug>)\n 3. Runs the multi-agent orchestrator to find and fix the bug\n 4. Re-runs the test to confirm the fix\n 5. Commits and pushes to GitHub\n 6. Opens a Pull Request against main\n\nProvide a test file path to enable automated reproduction and verification.',
1192
- execute: async (args, ctx) => {
1193
- const input = args.trim();
1194
- if (!input) {
1195
- console.log(pc.yellow('[WARN] Usage: /hunt <failing-test-filepath> or <bug description>'));
1196
- return;
1197
- }
1198
- const repoInfo = getGitRepoInfo(ctx.toolContext.projectRoot);
1199
- if (!repoInfo) {
1200
- console.log(pc.yellow('[INFO] No GitHub remote found. Running in local-only mode (no PR will be created).'));
1201
- }
1202
- const slug = input.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 40);
1203
- const branchName = `daedalus-hunt-${slug}`;
1204
- const testFilePattern = /\.(test|spec)\.(ts|tsx|js|jsx|mjs)$/i;
1205
- const isTestFile = testFilePattern.test(input.trim());
1206
- // Step 1: If input is a test file, run it to capture failure output
1207
- let testFailureOutput = '';
1208
- if (isTestFile) {
1209
- const testPath = input.trim();
1210
- console.log(pc.cyan(`\n[HUNT] Reproducing failure from: ${testPath}`));
1211
- try {
1212
- const { execute: termExec } = await import('../tools/builtin/terminal.js');
1213
- const testResult = await termExec({ command: `npx vitest run ${testPath} --reporter=verbose`, timeout: 120, workdir: process.cwd() }, ctx.toolContext);
1214
- if (testResult.success) {
1215
- console.log(pc.yellow(`\n[HUNT] Test passed — no bug to fix. Running on main branch only.`));
1216
- return;
1217
- }
1218
- testFailureOutput = testResult.content || '';
1219
- const errorLines = testFailureOutput.split('\n').filter(l => l.includes('FAIL') || l.includes('AssertionError') || l.includes('Error:') || l.includes('×')).slice(0, 20).join('\n');
1220
- console.log(pc.red(`\n[HUNT] Failure reproduced:\n${errorLines.slice(0, 1000)}`));
1221
- }
1222
- catch (err) {
1223
- const msg = err instanceof Error ? errMessage(err) : String(err);
1224
- console.log(pc.yellow(`[HUNT] Could not run test: ${msg}. Continuing with description only.`));
1225
- }
1226
- }
1227
- // Step 2: Create branch
1228
- try {
1229
- const allowDestroy = allowDestroyFromArgs(input) || !!repoInfo;
1230
- safeBranchSwitch(branchName, { cwd: ctx.toolContext.projectRoot, allowDestroy, branch: branchName });
1231
- console.log(pc.green(`[OK] Switched to branch: ${branchName}`));
1232
- }
1233
- catch (err) {
1234
- const msg = err instanceof Error ? errMessage(err) : String(err);
1235
- console.log(pc.red(`[ERROR] Failed to create branch: ${msg}`));
1236
- return;
1237
- }
1238
- // Step 3: Build goal for orchestrator
1239
- const testContext = testFailureOutput ? `\n\nTest failure output:\n\`\`\`\n${testFailureOutput.slice(0, 4000)}\n\`\`\`` : '';
1240
- const goal = `Fix the following bug:\n${input}${testContext}\n\nFind the root cause, fix it, and ensure existing tests still pass.`;
1241
- console.log(pc.cyan(`\n[HUNT] Starting autonomous bug hunt...`));
1242
- process.env.DAEDALUS_AUTO_APPROVE = 'true';
1243
- let orchestratorResult = '';
1244
- try {
1245
- const { Orchestrator } = await import('../agents/orchestrator.js');
1246
- const orchestrator = new Orchestrator(ctx.router, ctx.messages, ctx.toolContext, ctx.sessionManager, ctx.config?.modelOverride);
1247
- orchestratorResult = await orchestrator.run(goal);
1248
- console.log(pc.white(`\n${orchestratorResult}`));
1249
- const orchestrationFailed = orchestratorResult.startsWith('Orchestration failed') || orchestratorResult.includes('## Orchestration Hit Verification Failures');
1250
- const wasAborted = orchestratorResult.includes('## Orchestration Paused');
1251
- if (orchestrationFailed || wasAborted) {
1252
- throw new Error(orchestrationFailed ? 'Orchestration reported failure' : 'Orchestration was paused/aborted');
1253
- }
1254
- }
1255
- catch (err) {
1256
- const msg = err instanceof Error ? errMessage(err) : String(err);
1257
- console.log(pc.red(`\n[ERROR] Bug hunt stopped: ${msg}`));
1258
- console.log(pc.dim('[CHECK] Fix did not verify — discarding the attempt to keep main clean.'));
1259
- const allowDestroy = allowDestroyFromArgs(input) || !!repoInfo;
1260
- if (allowDestroy) {
1261
- try {
1262
- safeGitResetHard({ cwd: ctx.toolContext.projectRoot, allowDestroy });
1263
- execSync('git checkout main', { cwd: ctx.toolContext.projectRoot, stdio: 'ignore', windowsHide: true });
1264
- safeBranchDelete(branchName, { cwd: ctx.toolContext.projectRoot, allowDestroy });
1265
- console.log(pc.green('[OK] Branch cleaned up; main is untouched.'));
1266
- }
1267
- catch (rollbackErr) {
1268
- const rbMsg = rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr);
1269
- console.log(pc.red(`[ERROR] Cleanup failed: ${rbMsg}. Manual cleanup may be needed.`));
1270
- }
1271
- }
1272
- else {
1273
- console.log(pc.cyan(`[INFO] Local-only mode: keeping branch '${branchName}' with the attempted fix for inspection. Fix and commit manually.`));
1274
- }
1275
- return;
1276
- }
1277
- // Step 4: Verify fix by re-running the test
1278
- if (isTestFile && testFailureOutput) {
1279
- console.log(pc.cyan('\n[HUNT] Verifying fix...'));
1280
- try {
1281
- const { execute: termExec } = await import('../tools/builtin/terminal.js');
1282
- const verifyResult = await termExec({ command: `npx vitest run ${input.trim()} --reporter=verbose`, timeout: 120, workdir: process.cwd() }, ctx.toolContext);
1283
- if (verifyResult.success) {
1284
- console.log(pc.green(`\n[HUNT] ✓ Fix verified — test passes.`));
1285
- }
1286
- else {
1287
- console.log(pc.yellow(`\n[HUNT] ⚠ Fix verification failed. Test still failing. Continuing to commit partial fix.`));
1288
- }
1289
- }
1290
- catch (err) {
1291
- const msg = err instanceof Error ? errMessage(err) : String(err);
1292
- console.log(pc.yellow(`[HUNT] Verification skipped: ${msg}`));
1293
- }
1294
- }
1295
- // Step 5: Commit
1296
- console.log(pc.cyan('\n[HUNT] Committing changes...'));
1297
- try {
1298
- execSync('git add .', { cwd: ctx.toolContext.projectRoot, windowsHide: true });
1299
- const cleanTitle = input.replace(/[^a-zA-Z0-9 ]/g, '').trim();
1300
- execSync(`git commit -m "fix: ${cleanTitle}"`, { cwd: ctx.toolContext.projectRoot, windowsHide: true });
1301
- console.log(pc.green('[OK] Changes committed.'));
1302
- }
1303
- catch (err) {
1304
- const msg = err instanceof Error ? errMessage(err) : String(err);
1305
- if (msg.includes('nothing to commit')) {
1306
- console.log(pc.yellow('[INFO] No changes to commit.'));
1307
- }
1308
- else {
1309
- console.log(pc.red(`[ERROR] Failed to commit: ${msg}`));
1310
- return;
1311
- }
1312
- }
1313
- // Step 6: Push and PR
1314
- if (repoInfo) {
1315
- console.log(pc.cyan('\n[HUNT] Pushing branch and creating PR...'));
1316
- let token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
1317
- if (!token) {
1318
- try {
1319
- token = execSync('gh auth token', { encoding: 'utf8', windowsHide: true }).trim();
1320
- }
1321
- catch {
1322
- console.log(pc.yellow('[INFO] No GitHub token found. Run `gh auth login` or set GITHUB_TOKEN.'));
1323
- console.log(pc.yellow(`[INFO] Branch ${branchName} is ready locally. Push manually.`));
1324
- return;
1325
- }
1326
- }
1327
- try {
1328
- execSync(`git push -u origin ${branchName} --force`, { cwd: ctx.toolContext.projectRoot, windowsHide: true });
1329
- const hasSummary = orchestratorResult && !orchestratorResult.startsWith('Orchestration failed');
1330
- const prBody = `## Description\n\nAutonomously fixed by Daedalus Hunt.\n\n**Bug:** ${input}\n${testFailureOutput ? `\n**Failure reproduced:**\n\`\`\`\n${testFailureOutput.slice(0, 1500)}\n\`\`\`\n` : ''}${hasSummary ? `\n**Summary:**\n${orchestratorResult.slice(0, 2000)}` : ''}\n\n---\n_Generated by \`/hunt\`_`;
1331
- const prResponse = await fetch(`https://api.github.com/repos/${repoInfo.owner}/${repoInfo.repo}/pulls`, {
1332
- method: 'POST',
1333
- headers: {
1334
- 'Authorization': `Bearer ${token}`,
1335
- 'Content-Type': 'application/json',
1336
- },
1337
- body: JSON.stringify({
1338
- title: `[Hunt] ${input}`,
1339
- head: branchName,
1340
- base: 'main',
1341
- body: prBody,
1342
- }),
1343
- });
1344
- if (prResponse.ok) {
1345
- const pr = await prResponse.json();
1346
- console.log(pc.green(`\n[OK] Pull Request created: ${pr.html_url}`));
1347
- }
1348
- else {
1349
- const errText = await prResponse.text();
1350
- console.log(pc.red(`[ERROR] Failed to create PR: ${prResponse.status} ${errText}`));
1351
- console.log(pc.yellow(`[INFO] Branch ${branchName} is pushed. Create PR manually.`));
1352
- }
1353
- }
1354
- catch (err) {
1355
- const msg = err instanceof Error ? errMessage(err) : String(err);
1356
- console.log(pc.red(`[ERROR] Push/PR failed: ${msg}`));
1357
- console.log(pc.yellow(`[INFO] Branch ${branchName} is ready locally.`));
1358
- }
1359
- }
1360
- else {
1361
- console.log(pc.yellow('\n[INFO] No GitHub remote configured. Fix is committed locally.'));
1362
- console.log(pc.yellow(`[INFO] Branch: ${branchName}`));
1363
- }
1364
- console.log(pc.cyan(`\n[HUNT] Done! Run 'git checkout main' to return to main branch.`));
1365
- }
1366
- },
355
+ autopilotCommand,
356
+ huntCommand,
1367
357
  {
1368
358
  name: '/preview',
1369
359
  description: 'Screenshot a local HTML file or URL and save the image',