daedalus-cli 3.64.0 → 3.65.1
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/AGENTS.md +2 -1
- package/CHANGELOG.md +14 -0
- package/dist/agents/loop.d.ts.map +1 -1
- package/dist/agents/loop.js +4 -1
- package/dist/agents/loop.js.map +1 -1
- package/dist/agents/turn-guards.d.ts +41 -0
- package/dist/agents/turn-guards.d.ts.map +1 -0
- package/dist/agents/turn-guards.js +325 -0
- package/dist/agents/turn-guards.js.map +1 -0
- package/dist/agents/turn-guards.test.d.ts +2 -0
- package/dist/agents/turn-guards.test.d.ts.map +1 -0
- package/dist/agents/turn-guards.test.js +123 -0
- package/dist/agents/turn-guards.test.js.map +1 -0
- package/dist/commands/agents.d.ts +5 -28
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +11 -1021
- package/dist/commands/agents.js.map +1 -1
- package/dist/commands/autopilot.d.ts +30 -0
- package/dist/commands/autopilot.d.ts.map +1 -0
- package/dist/commands/autopilot.js +372 -0
- package/dist/commands/autopilot.js.map +1 -0
- package/dist/commands/context.d.ts.map +1 -1
- package/dist/commands/context.js +3 -1
- package/dist/commands/context.js.map +1 -1
- package/dist/commands/hunt.d.ts +3 -0
- package/dist/commands/hunt.d.ts.map +1 -0
- package/dist/commands/hunt.js +181 -0
- package/dist/commands/hunt.js.map +1 -0
- package/dist/commands/mcp.d.ts +3 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +339 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/onboard.d.ts +3 -0
- package/dist/commands/onboard.d.ts.map +1 -0
- package/dist/commands/onboard.js +109 -0
- package/dist/commands/onboard.js.map +1 -0
- package/dist/indexing/watcher.js +1 -1
- package/dist/indexing/watcher.js.map +1 -1
- package/dist/indexing/watcher.test.js +53 -16
- package/dist/indexing/watcher.test.js.map +1 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +32 -426
- package/dist/model.js.map +1 -1
- package/dist/session/manager.d.ts +2 -2
- package/dist/session/manager.d.ts.map +1 -1
- package/dist/session/manager.js +9 -6
- package/dist/session/manager.js.map +1 -1
- package/dist/session/manager.test.js +5 -0
- package/dist/session/manager.test.js.map +1 -1
- package/dist/session/sqlite.d.ts +4 -0
- package/dist/session/sqlite.d.ts.map +1 -1
- package/dist/session/sqlite.js +17 -0
- package/dist/session/sqlite.js.map +1 -1
- package/dist/session/sqlite.test.js +10 -1
- package/dist/session/sqlite.test.js.map +1 -1
- package/dist/tools/repl.d.ts.map +1 -1
- package/dist/tools/repl.js +3 -1
- package/dist/tools/repl.js.map +1 -1
- package/package.json +1 -1
package/dist/commands/agents.js
CHANGED
|
@@ -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
|
|
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 {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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',
|