daedalus-cli 1.83.7 → 1.83.8
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 +31 -10
- package/CHANGELOG.md +7 -0
- package/README.md +3 -3
- package/dist/agents/ensemble.d.ts.map +1 -1
- package/dist/agents/ensemble.js +2 -2
- package/dist/agents/ensemble.js.map +1 -1
- package/dist/agents/orchestrator-types.d.ts +20 -0
- package/dist/agents/orchestrator-types.d.ts.map +1 -0
- package/dist/agents/orchestrator-types.js +3 -0
- package/dist/agents/orchestrator-types.js.map +1 -0
- package/dist/agents/orchestrator-validation.d.ts +19 -0
- package/dist/agents/orchestrator-validation.d.ts.map +1 -0
- package/dist/agents/orchestrator-validation.js +227 -0
- package/dist/agents/orchestrator-validation.js.map +1 -0
- package/dist/agents/orchestrator-verification.d.ts +28 -0
- package/dist/agents/orchestrator-verification.d.ts.map +1 -0
- package/dist/agents/orchestrator-verification.js +355 -0
- package/dist/agents/orchestrator-verification.js.map +1 -0
- package/dist/agents/orchestrator.d.ts +1 -49
- package/dist/agents/orchestrator.d.ts.map +1 -1
- package/dist/agents/orchestrator.js +40 -678
- package/dist/agents/orchestrator.js.map +1 -1
- package/dist/agents/orchestrator.test.js +33 -81
- package/dist/agents/orchestrator.test.js.map +1 -1
- package/dist/commands/agents.d.ts +3 -0
- package/dist/commands/agents.d.ts.map +1 -0
- package/dist/commands/agents.js +886 -0
- package/dist/commands/agents.js.map +1 -0
- package/dist/commands/context.d.ts +3 -0
- package/dist/commands/context.d.ts.map +1 -0
- package/dist/commands/context.js +875 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/dev.d.ts +3 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +820 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/index.d.ts +5 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +88 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/types.d.ts +45 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/types.js +2 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/commands.d.ts +2 -46
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +1 -2639
- package/dist/commands.js.map +1 -1
- package/dist/config/index.d.ts +78 -78
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +11 -6
- package/dist/model.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,886 @@
|
|
|
1
|
+
// Agent orchestration, MCP, setup & utility commands
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import pc from 'picocolors';
|
|
5
|
+
import { executeToolCalls } from '../tools/executor.js';
|
|
6
|
+
import { spawnBackgroundAgent } from '../agents/background.js';
|
|
7
|
+
import { handleSpecCommand, getGitRepoInfo } from '../agents/loop.js';
|
|
8
|
+
import { turnSeparator } from '../formatting.js';
|
|
9
|
+
import { execSync } from 'child_process';
|
|
10
|
+
import { discoverLocalServers, saveConfig } from '../config/index.js';
|
|
11
|
+
export const agentCommands = [
|
|
12
|
+
{
|
|
13
|
+
name: '/spawn',
|
|
14
|
+
aliases: ['/delegate'],
|
|
15
|
+
description: 'Spawn sub-agent: /spawn [--bg] <role> <task>',
|
|
16
|
+
usage: '/spawn [--bg] <role> <task> OR /delegate [--bg] <task> to <role>',
|
|
17
|
+
helpText: 'Spawns a specialized agent to execute a coding or research task.\n\nRoles:\n coder Implements, patches, and refactors code files\n reviewer Critically reviews changes and runs tests\n debugger Tackles compilation errors, runtime failures, and logs\n researcher Scans repository structure and reads doc resources\n planner Outlines architecture plans and coordinates execution\n\nOptions:\n --bg Runs the agent asynchronously in the background',
|
|
18
|
+
execute: async (args, ctx) => {
|
|
19
|
+
let role = '';
|
|
20
|
+
let task = '';
|
|
21
|
+
let isBackground = false;
|
|
22
|
+
let cleanedArgs = args.trim();
|
|
23
|
+
if (cleanedArgs.startsWith('--bg ')) {
|
|
24
|
+
isBackground = true;
|
|
25
|
+
cleanedArgs = cleanedArgs.substring(5).trim();
|
|
26
|
+
}
|
|
27
|
+
else if (cleanedArgs.endsWith(' --bg')) {
|
|
28
|
+
isBackground = true;
|
|
29
|
+
cleanedArgs = cleanedArgs.substring(0, cleanedArgs.length - 5).trim();
|
|
30
|
+
}
|
|
31
|
+
if (cleanedArgs.includes(' to ')) {
|
|
32
|
+
const match = cleanedArgs.match(/^(.+)\s+to\s+(\w+)$/i);
|
|
33
|
+
if (match) {
|
|
34
|
+
task = match[1].trim();
|
|
35
|
+
role = match[2].toLowerCase();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
const parts = cleanedArgs.split(/\s+/);
|
|
40
|
+
if (parts.length >= 2) {
|
|
41
|
+
role = parts[0].toLowerCase();
|
|
42
|
+
task = cleanedArgs.substring(parts[0].length).trim();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const validRoles = ['coder', 'reviewer', 'debugger', 'researcher', 'planner'];
|
|
46
|
+
if (!role || !task) {
|
|
47
|
+
console.log(pc.red('[WARN] Usage: /spawn [--bg] <role> <task> OR /delegate [--bg] <task> to <role>'));
|
|
48
|
+
console.log(pc.gray(` Roles: ${validRoles.join(', ')}`));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (!validRoles.includes(role)) {
|
|
52
|
+
console.log(pc.red(`[WARN] Unknown role: ${role}. Valid: ${validRoles.join(', ')}`));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const context = `Active files: ${Array.from(ctx.activeFiles.values()).join(', ') || 'none'}`;
|
|
56
|
+
if (isBackground) {
|
|
57
|
+
console.log(pc.cyan(`\n[SPAWN] Spawning ${role} agent in background for: ${task.slice(0, 80)}...`));
|
|
58
|
+
const id = spawnBackgroundAgent(role, task, context, ctx.toolContext);
|
|
59
|
+
console.log(pc.green(`[OK] Spawned background task #${id} (${role}) successfully.`));
|
|
60
|
+
console.log(pc.gray(` Check status via /tasks, view logs/results via /task ${id}, or cancel via /task kill ${id}`));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
console.log(pc.cyan(`\n[SPAWN] Spawning ${role} agent for: ${task.slice(0, 80)}...`));
|
|
64
|
+
const fakeToolCall = {
|
|
65
|
+
id: `call_${Date.now()}`,
|
|
66
|
+
type: 'function',
|
|
67
|
+
function: {
|
|
68
|
+
name: 'delegate_task',
|
|
69
|
+
arguments: JSON.stringify({ goal: task, context, role }),
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
const results = await executeToolCalls([fakeToolCall], ctx.toolContext);
|
|
73
|
+
for (const result of results) {
|
|
74
|
+
const status = result.success ? pc.green('✔') : pc.red('✗');
|
|
75
|
+
console.log(`\n${status} ${role} agent completed`);
|
|
76
|
+
console.log(pc.white(result.content));
|
|
77
|
+
if (!result.success && result.error) {
|
|
78
|
+
console.log(pc.red(`Error: ${result.error}`));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: '/tasks',
|
|
85
|
+
description: 'List background agent tasks',
|
|
86
|
+
usage: '/tasks',
|
|
87
|
+
helpText: 'Display a list of all active, completed, failed, or cancelled background agent tasks.',
|
|
88
|
+
execute: async (_args, _ctx) => {
|
|
89
|
+
const { backgroundJobs } = await import('../agents/background.js');
|
|
90
|
+
if (backgroundJobs.size === 0) {
|
|
91
|
+
console.log(pc.gray('No background tasks found.'));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
console.log(pc.cyan('\n--- Background Tasks ---'));
|
|
95
|
+
for (const job of backgroundJobs.values()) {
|
|
96
|
+
const duration = job.finishedAt
|
|
97
|
+
? `${Math.round((job.finishedAt - job.startedAt) / 1000)}s`
|
|
98
|
+
: `${Math.round((Date.now() - job.startedAt) / 1000)}s elapsed`;
|
|
99
|
+
let statusStr;
|
|
100
|
+
if (job.status === 'running') {
|
|
101
|
+
statusStr = pc.blue('RUNNING');
|
|
102
|
+
}
|
|
103
|
+
else if (job.status === 'completed') {
|
|
104
|
+
statusStr = pc.green('COMPLETED');
|
|
105
|
+
}
|
|
106
|
+
else if (job.status === 'failed') {
|
|
107
|
+
statusStr = pc.red('FAILED');
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
statusStr = pc.yellow('CANCELLED');
|
|
111
|
+
}
|
|
112
|
+
console.log(`[#${job.id}] ${pc.bold(job.role)} — ${statusStr} (${duration})`);
|
|
113
|
+
console.log(pc.gray(` Goal: ${job.goal.slice(0, 80)}`));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: '/task',
|
|
119
|
+
description: 'Manage background task: /task <id> | /task kill <id>',
|
|
120
|
+
usage: '/task <id> OR /task kill <id>',
|
|
121
|
+
helpText: 'Inspect or terminate background agent tasks.\n\nArguments:\n <id> Show detail info, logs, and output/result of a background task\n kill <id> Cancel and terminate a running background task',
|
|
122
|
+
execute: async (args, _ctx) => {
|
|
123
|
+
const { backgroundJobs, killBackgroundAgent } = await import('../agents/background.js');
|
|
124
|
+
const trimmed = args.trim();
|
|
125
|
+
if (!trimmed) {
|
|
126
|
+
console.log(pc.red('[WARN] Usage: /task <id> OR /task kill <id>'));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (trimmed.startsWith('kill ')) {
|
|
130
|
+
const idStr = trimmed.substring(5).trim();
|
|
131
|
+
const id = parseInt(idStr, 10);
|
|
132
|
+
if (isNaN(id)) {
|
|
133
|
+
console.log(pc.red(`[WARN] Invalid task ID: ${idStr}`));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const killed = killBackgroundAgent(id);
|
|
137
|
+
if (killed) {
|
|
138
|
+
console.log(pc.green(`[OK] Task #${id} cancelled.`));
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
console.log(pc.red(`[WARN] Task #${id} is not running or not found.`));
|
|
142
|
+
}
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const id = parseInt(trimmed, 10);
|
|
146
|
+
if (isNaN(id)) {
|
|
147
|
+
console.log(pc.red('[WARN] Usage: /task <id> OR /task kill <id>'));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const job = backgroundJobs.get(id);
|
|
151
|
+
if (!job) {
|
|
152
|
+
console.log(pc.red(`[WARN] Task #${id} not found.`));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
console.log(pc.cyan(`\n--- Task #${job.id} (${job.role}) ---`));
|
|
156
|
+
console.log(`Goal: ${job.goal}`);
|
|
157
|
+
console.log(`Status: ${job.status.toUpperCase()}`);
|
|
158
|
+
console.log(`Started: ${new Date(job.startedAt).toLocaleTimeString()}`);
|
|
159
|
+
if (job.finishedAt) {
|
|
160
|
+
console.log(`Finished: ${new Date(job.finishedAt).toLocaleTimeString()}`);
|
|
161
|
+
console.log(`Duration: ${Math.round((job.finishedAt - job.startedAt) / 1000)}s`);
|
|
162
|
+
}
|
|
163
|
+
if (job.status === 'completed' && job.result) {
|
|
164
|
+
console.log(pc.white('\n--- Result ---'));
|
|
165
|
+
console.log(job.result);
|
|
166
|
+
}
|
|
167
|
+
else if (job.status === 'failed' && job.error) {
|
|
168
|
+
console.log(pc.red(`\n--- Error ---`));
|
|
169
|
+
console.log(job.error);
|
|
170
|
+
}
|
|
171
|
+
else if (job.status === 'running') {
|
|
172
|
+
console.log(pc.gray('\nThis task is still running. Check again later.'));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: '/orchestrate',
|
|
178
|
+
aliases: ['/orc', '/run', '/o'],
|
|
179
|
+
description: 'Orchestrate agents for a goal',
|
|
180
|
+
usage: '/orchestrate <goal>',
|
|
181
|
+
helpText: 'Spawns the Orchestration system to plan, execute, and verify a high-level coding goal.\nOrchestrate generates a task.md checklist, coordinates specialized sub-agents, runs verification commands, and handles self-repair loops automatically.',
|
|
182
|
+
execute: async (args, ctx) => {
|
|
183
|
+
const pendingPlan = ctx.sessionManager.getState('orchestrate_plan');
|
|
184
|
+
const pendingGoal = ctx.sessionManager.getState('orchestrate_goal');
|
|
185
|
+
if (pendingPlan && pendingGoal) {
|
|
186
|
+
const goal = args.trim();
|
|
187
|
+
const shouldResume = !goal || goal.toLowerCase() === pendingGoal.toLowerCase();
|
|
188
|
+
let proceed = false;
|
|
189
|
+
if (shouldResume && process.env.DAEDALUS_AUTO_APPROVE === 'true') {
|
|
190
|
+
proceed = true;
|
|
191
|
+
}
|
|
192
|
+
else if (shouldResume) {
|
|
193
|
+
console.log(pc.yellow(`\n[INFO] Found a pending orchestration plan for: "${pendingGoal}"`));
|
|
194
|
+
const answer = await ctx.askLine(`Would you like to resume it? [y]es / [n]o: `);
|
|
195
|
+
const char = answer.trim().toLowerCase().slice(0, 1);
|
|
196
|
+
if (char === 'y' || answer.trim() === '') {
|
|
197
|
+
proceed = true;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (proceed) {
|
|
201
|
+
console.log(pc.cyan(`\n[ORCHESTRATE] Resuming orchestration for: ${pendingGoal}`));
|
|
202
|
+
const { Orchestrator } = await import('../agents/orchestrator.js');
|
|
203
|
+
const orchestrator = new Orchestrator(ctx.router, ctx.messages, ctx.toolContext, ctx.sessionManager);
|
|
204
|
+
const planText = ctx.sessionManager.getState('orchestrate_plan_text') || '';
|
|
205
|
+
const taskIndex = ctx.sessionManager.getState('orchestrate_task_index') || 0;
|
|
206
|
+
const prevResults = ctx.sessionManager.getState('orchestrate_results') || [];
|
|
207
|
+
const result = await orchestrator.resume(pendingGoal, planText, pendingPlan, taskIndex, prevResults);
|
|
208
|
+
console.log(pc.white(`\n${result}`));
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
ctx.sessionManager.saveState('orchestrate_plan', null);
|
|
213
|
+
ctx.sessionManager.saveState('orchestrate_goal', null);
|
|
214
|
+
ctx.sessionManager.saveState('orchestrate_task_index', null);
|
|
215
|
+
ctx.sessionManager.saveState('orchestrate_results', null);
|
|
216
|
+
ctx.sessionManager.saveState('orchestrate_plan_text', null);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const goal = args.trim();
|
|
220
|
+
if (!goal) {
|
|
221
|
+
console.log(pc.red('[WARN] Usage: /orchestrate <goal>'));
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
console.log(pc.cyan(`\n[ORCHESTRATE] Starting orchestration for: ${goal}`));
|
|
225
|
+
const { Orchestrator } = await import('../agents/orchestrator.js');
|
|
226
|
+
const orchestrator = new Orchestrator(ctx.router, ctx.messages, ctx.toolContext, ctx.sessionManager);
|
|
227
|
+
const result = await orchestrator.run(goal);
|
|
228
|
+
console.log(pc.white(`\n${result}`));
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
name: '/ensemble',
|
|
233
|
+
description: 'Ensemble model drafting pipeline',
|
|
234
|
+
execute: async (args, ctx) => {
|
|
235
|
+
const ensembleGoal = args.trim();
|
|
236
|
+
if (!ensembleGoal) {
|
|
237
|
+
console.log(pc.red(' Error: Please specify a goal for the ensemble draft. Example: /ensemble Implement feature X'));
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
const { runEnsembleWorkflow } = await import('../agents/ensemble.js');
|
|
242
|
+
await runEnsembleWorkflow(ensembleGoal, ctx.toolContext, ctx.config, ctx.router);
|
|
243
|
+
}
|
|
244
|
+
catch (err) {
|
|
245
|
+
console.log(pc.red(`\n Error in ensemble drafting: ${err.message}`));
|
|
246
|
+
}
|
|
247
|
+
turnSeparator();
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
name: '/spec',
|
|
252
|
+
description: 'Flesh out a feature idea into a GitHub Issue spec (Finn Loop)',
|
|
253
|
+
usage: '/spec <goal>',
|
|
254
|
+
helpText: 'Generate detailed specifications and requirements for a coding goal.\nRuns an interactive interview query chain to clarify goals, then saves the resulting specification format.',
|
|
255
|
+
execute: async (args, ctx) => {
|
|
256
|
+
await handleSpecCommand(args, ctx);
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: '/mcp',
|
|
261
|
+
description: 'Manage MCP servers: explore, search, install, list, remove, info',
|
|
262
|
+
usage: '/mcp <subcommand> [args]',
|
|
263
|
+
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',
|
|
264
|
+
execute: async (args, _ctx) => {
|
|
265
|
+
const parts = args.trim().split(/\s+/);
|
|
266
|
+
const sub = parts[0]?.toLowerCase();
|
|
267
|
+
const rest = parts.slice(1).join(' ').trim();
|
|
268
|
+
const { searchRegistry, fetchServerByName, fetchAllServers, registryEntryToConfig, addServerToConfig, removeServerFromConfig, listInstalledServers, toggleServer } = await import('../tools/mcp/manager.js');
|
|
269
|
+
const { mcpRegistry } = await import('../tools/mcp/registry.js');
|
|
270
|
+
switch (sub) {
|
|
271
|
+
case 'search':
|
|
272
|
+
case 's': {
|
|
273
|
+
if (!rest) {
|
|
274
|
+
console.log(pc.yellow(' Usage: /mcp search <query>'));
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
console.log(pc.dim(` Searching registry for "${rest}"...`));
|
|
278
|
+
try {
|
|
279
|
+
const results = await searchRegistry(rest, 15);
|
|
280
|
+
if (results.length === 0) {
|
|
281
|
+
console.log(pc.yellow(' No servers found. Try a broader search.'));
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
console.log(`\n ${pc.bold(`Found ${results.length} server(s):`)}`);
|
|
285
|
+
for (const s of results) {
|
|
286
|
+
const label = s.title || s.name;
|
|
287
|
+
const desc = s.description.length > 80 ? s.description.slice(0, 80) + '…' : s.description;
|
|
288
|
+
const remote = s.remotes?.[0]?.url || '';
|
|
289
|
+
const pkg = s.packages?.[0]?.identifier || '';
|
|
290
|
+
const source = remote || pkg || '(no install info)';
|
|
291
|
+
const installType = s.packages ? 'stdio' : s.remotes ? 'http' : '?';
|
|
292
|
+
console.log(` ${pc.cyan(label)}`);
|
|
293
|
+
console.log(` ${pc.dim(desc)}`);
|
|
294
|
+
console.log(` ${pc.gray('Install:')} ${pc.dim(source)} (${installType})`);
|
|
295
|
+
console.log();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
catch (err) {
|
|
299
|
+
console.log(pc.red(` Search failed: ${err.message}`));
|
|
300
|
+
}
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
case 'install':
|
|
304
|
+
case 'i': {
|
|
305
|
+
if (!rest) {
|
|
306
|
+
console.log(pc.yellow(' Usage: /mcp install <server-name>'));
|
|
307
|
+
console.log(pc.dim(' First search for a server with: /mcp search <query>'));
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
console.log(pc.dim(` Fetching "${rest}" from registry...`));
|
|
311
|
+
try {
|
|
312
|
+
const entry = await fetchServerByName(rest);
|
|
313
|
+
if (!entry) {
|
|
314
|
+
console.log(pc.yellow(` Server "${rest}" not found in registry. Try /mcp search first.`));
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const config = registryEntryToConfig(entry);
|
|
318
|
+
if (!config) {
|
|
319
|
+
console.log(pc.yellow(` Cannot install "${rest}": no stdio package or remote URL found.`));
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const result = addServerToConfig(config);
|
|
323
|
+
if (result.success) {
|
|
324
|
+
console.log(pc.green(` ${result.message}`));
|
|
325
|
+
console.log(pc.dim(' Restart Daedalus or reconnect to load the new server.'));
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
console.log(pc.yellow(` ${result.message}`));
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
catch (err) {
|
|
332
|
+
console.log(pc.red(` Install failed: ${err.message}`));
|
|
333
|
+
}
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
case 'explore':
|
|
337
|
+
case 'ex': {
|
|
338
|
+
console.log(pc.dim(' Browsing the MCP registry...\n'));
|
|
339
|
+
try {
|
|
340
|
+
const all = await fetchAllServers(100);
|
|
341
|
+
const local = all.filter(s => s.packages && s.packages.length > 0);
|
|
342
|
+
const remote = all.filter(s => s.remotes && s.remotes.length > 0);
|
|
343
|
+
console.log(` ${pc.bold(`Found ${all.length} servers in registry`)}`);
|
|
344
|
+
const showSample = (list, label, max = 5) => {
|
|
345
|
+
if (list.length === 0)
|
|
346
|
+
return;
|
|
347
|
+
console.log(`\n ${pc.underline(label)} (${list.length} available)`);
|
|
348
|
+
for (const s of list.slice(0, max)) {
|
|
349
|
+
const pkg = s.packages?.[0]?.identifier || '';
|
|
350
|
+
const url = s.remotes?.[0]?.url || '';
|
|
351
|
+
const source = pkg || url;
|
|
352
|
+
const info = s.description.length > 55 ? s.description.slice(0, 53) + '…' : s.description;
|
|
353
|
+
const showName = s.name.length > 28 ? s.name.slice(0, 26) + '…' : s.name;
|
|
354
|
+
console.log(` ${pc.cyan(showName.padEnd(30))} ${pc.dim(info)}`);
|
|
355
|
+
console.log(` ${' '.repeat(30)} ${pc.gray('→')} ${pc.dim(source)}`);
|
|
356
|
+
}
|
|
357
|
+
if (list.length > max) {
|
|
358
|
+
console.log(` ${' '.repeat(30)} ${pc.dim(`… and ${list.length - max} more`)}`);
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
showSample(local, 'Local (stdio — install & run)', 6);
|
|
362
|
+
showSample(remote, 'Remote (HTTP — cloud API)', 6);
|
|
363
|
+
console.log(`\n ${pc.dim('Tip: /mcp search <query> to find specific servers')}`);
|
|
364
|
+
}
|
|
365
|
+
catch (err) {
|
|
366
|
+
console.log(pc.red(` Explore failed: ${err.message}`));
|
|
367
|
+
}
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
case 'list':
|
|
371
|
+
case 'ls':
|
|
372
|
+
case 'l': {
|
|
373
|
+
const servers = listInstalledServers();
|
|
374
|
+
if (servers.length === 0) {
|
|
375
|
+
console.log(pc.yellow(' No MCP servers installed.'));
|
|
376
|
+
console.log(pc.dim(' Try /mcp explore to see what\'s available.'));
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const connected = mcpRegistry.getConnectedServers();
|
|
380
|
+
console.log(`\n ${pc.bold('Installed MCP Servers:')}`);
|
|
381
|
+
for (const s of servers) {
|
|
382
|
+
const status = connected.includes(s.name) ? pc.green('●') : s.enabled ? pc.yellow('○') : pc.red('○');
|
|
383
|
+
const state = connected.includes(s.name) ? pc.green('connected')
|
|
384
|
+
: s.enabled ? pc.yellow('pending')
|
|
385
|
+
: pc.red('disabled');
|
|
386
|
+
console.log(` ${status} ${pc.cyan(s.name.padEnd(20))} ${pc.dim(s.transport.padEnd(6))} ${state}`);
|
|
387
|
+
}
|
|
388
|
+
console.log();
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
case 'remove':
|
|
392
|
+
case 'rm':
|
|
393
|
+
case 'r': {
|
|
394
|
+
if (!rest) {
|
|
395
|
+
console.log(pc.yellow(' Usage: /mcp remove <server-name>'));
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
const result = removeServerFromConfig(rest);
|
|
399
|
+
if (result.success) {
|
|
400
|
+
console.log(pc.green(` ${result.message}`));
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
console.log(pc.yellow(` ${result.message}`));
|
|
404
|
+
}
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
case 'info': {
|
|
408
|
+
if (!rest) {
|
|
409
|
+
console.log(pc.yellow(' Usage: /mcp info <server-name>'));
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
try {
|
|
413
|
+
console.log(pc.dim(` Fetching "${rest}" from registry...`));
|
|
414
|
+
const entry = await fetchServerByName(rest);
|
|
415
|
+
if (!entry) {
|
|
416
|
+
console.log(pc.yellow(` Server "${rest}" not found.`));
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
console.log(`\n ${pc.bold(entry.title || entry.name)}`);
|
|
420
|
+
console.log(` ${pc.dim(entry.description)}`);
|
|
421
|
+
console.log(` ${pc.gray('Name:')} ${entry.name}`);
|
|
422
|
+
console.log(` ${pc.gray('Version:')} ${entry.version}`);
|
|
423
|
+
if (entry.websiteUrl)
|
|
424
|
+
console.log(` ${pc.gray('Website:')} ${entry.websiteUrl}`);
|
|
425
|
+
if (entry.repository?.url)
|
|
426
|
+
console.log(` ${pc.gray('Source:')} ${entry.repository.url}`);
|
|
427
|
+
if (entry.remotes && entry.remotes.length > 0) {
|
|
428
|
+
console.log(`\n ${pc.bold('Remote endpoints:')}`);
|
|
429
|
+
for (const r of entry.remotes) {
|
|
430
|
+
console.log(` ${pc.cyan(r.type)} ${pc.dim(r.url)}`);
|
|
431
|
+
if (r.headers) {
|
|
432
|
+
for (const h of r.headers) {
|
|
433
|
+
const req = h.isRequired ? pc.yellow(' (required)') : '';
|
|
434
|
+
const secret = h.isSecret ? pc.dim(' [secret]') : '';
|
|
435
|
+
console.log(` ${pc.gray('Header:')} ${h.name}${req}${secret}`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (entry.packages && entry.packages.length > 0) {
|
|
441
|
+
console.log(`\n ${pc.bold('Packages:')}`);
|
|
442
|
+
for (const p of entry.packages) {
|
|
443
|
+
const [cmd, ...args] = p.registryType === 'npm' ? ['npx', '-y', p.identifier]
|
|
444
|
+
: p.registryType === 'pypi' ? ['uvx', p.identifier]
|
|
445
|
+
: [p.identifier];
|
|
446
|
+
console.log(` ${pc.cyan(p.registryType)} ${pc.dim(`${cmd} ${args.join(' ')}`)}`);
|
|
447
|
+
if (p.environmentVariables) {
|
|
448
|
+
for (const env of p.environmentVariables) {
|
|
449
|
+
const req = env.isRequired ? pc.yellow(' (required)') : '';
|
|
450
|
+
const secret = env.isSecret ? pc.dim(' [secret]') : '';
|
|
451
|
+
console.log(` ${pc.gray('Env:')} ${env.name}${req}${secret}`);
|
|
452
|
+
if (env.description)
|
|
453
|
+
console.log(` ${pc.dim(env.description)}`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
console.log();
|
|
459
|
+
}
|
|
460
|
+
catch (err) {
|
|
461
|
+
console.log(pc.red(` Info fetch failed: ${err.message}`));
|
|
462
|
+
}
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
case 'reconnect':
|
|
466
|
+
case 'rc': {
|
|
467
|
+
const { loadConfig } = await import('../config/index.js');
|
|
468
|
+
const config = loadConfig();
|
|
469
|
+
const mcpConfigs = Object.entries(config.tools.mcpServers)
|
|
470
|
+
.filter(([_, s]) => s.enabled)
|
|
471
|
+
.map(([name, s]) => ({
|
|
472
|
+
name,
|
|
473
|
+
transport: s.transport,
|
|
474
|
+
command: s.command,
|
|
475
|
+
args: s.args,
|
|
476
|
+
url: s.url,
|
|
477
|
+
headers: s.headers,
|
|
478
|
+
enabled: s.enabled,
|
|
479
|
+
}));
|
|
480
|
+
const already = mcpRegistry.getConnectedServers();
|
|
481
|
+
const newServers = mcpConfigs.filter(c => !already.includes(c.name));
|
|
482
|
+
if (newServers.length === 0) {
|
|
483
|
+
if (mcpConfigs.length === 0) {
|
|
484
|
+
console.log(pc.yellow(' No enabled MCP servers configured. Install one with /mcp install'));
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
console.log(pc.dim(' All enabled MCP servers are already connected.'));
|
|
488
|
+
}
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
mcpRegistry.setConfigs(mcpConfigs);
|
|
492
|
+
const connected = [];
|
|
493
|
+
const failed = [];
|
|
494
|
+
for (const s of newServers) {
|
|
495
|
+
try {
|
|
496
|
+
await mcpRegistry.connectServer(s);
|
|
497
|
+
connected.push(s.name);
|
|
498
|
+
}
|
|
499
|
+
catch (err) {
|
|
500
|
+
failed.push(`${s.name} (${err.message})`);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
if (connected.length > 0) {
|
|
504
|
+
const totalTools = mcpRegistry.getToolDefinitions().length;
|
|
505
|
+
console.log(pc.green(` Connected: ${connected.join(', ')} (${totalTools} MCP tools total)`));
|
|
506
|
+
}
|
|
507
|
+
if (failed.length > 0) {
|
|
508
|
+
console.log(pc.yellow(` Failed: ${failed.join(', ')}`));
|
|
509
|
+
}
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
case 'enable':
|
|
513
|
+
case 'e': {
|
|
514
|
+
if (!rest) {
|
|
515
|
+
console.log(pc.yellow(' Usage: /mcp enable <server-name>'));
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
const enableResult = toggleServer(rest, true);
|
|
519
|
+
console.log(enableResult.success ? pc.green(` ${enableResult.message}`) : pc.yellow(` ${enableResult.message}`));
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
case 'disable':
|
|
523
|
+
case 'd': {
|
|
524
|
+
if (!rest) {
|
|
525
|
+
console.log(pc.yellow(' Usage: /mcp disable <server-name>'));
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
const disableResult = toggleServer(rest, false);
|
|
529
|
+
console.log(disableResult.success ? pc.green(` ${disableResult.message}`) : pc.yellow(` ${disableResult.message}`));
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
default:
|
|
533
|
+
console.log(pc.bold('\n MCP Server Manager'));
|
|
534
|
+
console.log(` ${pc.cyan('/mcp explore')} ${pc.dim('Browse available servers in the registry')}`);
|
|
535
|
+
console.log(` ${pc.cyan('/mcp search <query>')} ${pc.dim('Search the official MCP registry')}`);
|
|
536
|
+
console.log(` ${pc.cyan('/mcp install <name>')} ${pc.dim('Install a server from the registry')}`);
|
|
537
|
+
console.log(` ${pc.cyan('/mcp list')} ${pc.dim('List installed servers')}`);
|
|
538
|
+
console.log(` ${pc.cyan('/mcp remove <name>')} ${pc.dim('Remove an installed server')}`);
|
|
539
|
+
console.log(` ${pc.cyan('/mcp info <name>')} ${pc.dim('Show server details')}`);
|
|
540
|
+
console.log(` ${pc.cyan('/mcp reconnect')} ${pc.dim('Reconnect all enabled servers')}`);
|
|
541
|
+
console.log(` ${pc.cyan('/mcp enable <name>')} ${pc.dim('Enable a disabled server')}`);
|
|
542
|
+
console.log(` ${pc.cyan('/mcp disable <name>')} ${pc.dim('Disable a server without removing it')}`);
|
|
543
|
+
console.log(`\n ${pc.bold('Zero-config starters (no API keys needed):')}`);
|
|
544
|
+
console.log(` ${pc.gray('→')} ${pc.cyan('io.github/modelcontextprotocol/sequential-thinking')} ${pc.dim('Step-by-step reasoning')}`);
|
|
545
|
+
console.log(` ${pc.gray('→')} ${pc.cyan('io.github/modelcontextprotocol/filesystem')} ${pc.dim('Read/write files in allowed dirs')}`);
|
|
546
|
+
console.log(` ${pc.gray('→')} ${pc.cyan('io.github/modelcontextprotocol/memory')} ${pc.dim('Persistent key-value store')}`);
|
|
547
|
+
console.log(` ${pc.gray('→')} ${pc.cyan('io.github/modelcontextprotocol/fetch')} ${pc.dim('Fetch URLs and extract content')}`);
|
|
548
|
+
console.log(` ${pc.gray('→')} ${pc.cyan('io.github/modelcontextprotocol/puppeteer')} ${pc.dim('Browser automation')}`);
|
|
549
|
+
console.log(` ${pc.gray('→')} ${pc.cyan('ai.ankimcp/anki-mcp-server')} ${pc.dim('Anki flashcard management')}`);
|
|
550
|
+
console.log(` ${pc.dim(' /mcp install <name> to install any of the above')}`);
|
|
551
|
+
console.log();
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
name: '/onboard',
|
|
557
|
+
description: 'First-time setup — discover local models, configure, and test',
|
|
558
|
+
usage: '/onboard',
|
|
559
|
+
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.',
|
|
560
|
+
execute: async (_args, ctx) => {
|
|
561
|
+
const config = ctx.config;
|
|
562
|
+
console.log(pc.bold(pc.cyan('\n╔══════════════════════════════════════╗')));
|
|
563
|
+
console.log(pc.bold(pc.cyan('║ Daedalus Onboarding ║')));
|
|
564
|
+
console.log(pc.bold(pc.cyan('╚══════════════════════════════════════╝')));
|
|
565
|
+
console.log();
|
|
566
|
+
console.log('Daedalus runs AI models locally on your machine.');
|
|
567
|
+
console.log('First, I need to know which model server to use.');
|
|
568
|
+
console.log();
|
|
569
|
+
// Step 1: Discover local model servers
|
|
570
|
+
console.log(pc.bold('🔍 Scanning for local model servers...'));
|
|
571
|
+
const discovered = await discoverLocalServers();
|
|
572
|
+
let chosenEndpoint = '';
|
|
573
|
+
let chosenModel = '';
|
|
574
|
+
if (discovered.length > 0) {
|
|
575
|
+
console.log(pc.green(`\n Found ${discovered.length} running server(s):\n`));
|
|
576
|
+
for (let i = 0; i < discovered.length; i++) {
|
|
577
|
+
const s = discovered[i];
|
|
578
|
+
console.log(` ${i + 1}. ${pc.cyan(s.name)} at ${s.endpoint}`);
|
|
579
|
+
for (const m of s.models.slice(0, 3)) {
|
|
580
|
+
console.log(` - ${m}`);
|
|
581
|
+
}
|
|
582
|
+
if (s.models.length > 3) {
|
|
583
|
+
console.log(pc.gray(` ... and ${s.models.length - 3} more`));
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
console.log();
|
|
587
|
+
const serverChoice = await ctx.askLine(`Select a server (1-${discovered.length}) or press Enter to add manually: `);
|
|
588
|
+
const idx = parseInt(serverChoice) - 1;
|
|
589
|
+
if (idx >= 0 && idx < discovered.length) {
|
|
590
|
+
const server = discovered[idx];
|
|
591
|
+
chosenEndpoint = server.endpoint;
|
|
592
|
+
if (server.models.length === 1) {
|
|
593
|
+
chosenModel = server.models[0];
|
|
594
|
+
}
|
|
595
|
+
else {
|
|
596
|
+
console.log(`\nModels on ${pc.cyan(server.name)}:`);
|
|
597
|
+
for (let i = 0; i < server.models.length; i++) {
|
|
598
|
+
console.log(` ${i + 1}. ${server.models[i]}`);
|
|
599
|
+
}
|
|
600
|
+
const modelChoice = await ctx.askLine(`Select a model (1-${server.models.length}): `);
|
|
601
|
+
const midx = parseInt(modelChoice) - 1;
|
|
602
|
+
if (midx >= 0 && midx < server.models.length) {
|
|
603
|
+
chosenModel = server.models[midx];
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
if (!chosenEndpoint) {
|
|
609
|
+
console.log(`\nEnter your model server details manually.`);
|
|
610
|
+
chosenEndpoint = await ctx.askLine('API endpoint (e.g. http://localhost:1234/v1): ');
|
|
611
|
+
if (!chosenEndpoint)
|
|
612
|
+
chosenEndpoint = 'http://localhost:1234/v1';
|
|
613
|
+
chosenModel = await ctx.askLine('Model name (e.g. qwen2.5-coder-7b-instruct): ');
|
|
614
|
+
if (!chosenModel)
|
|
615
|
+
chosenModel = 'auto';
|
|
616
|
+
}
|
|
617
|
+
if (!chosenModel)
|
|
618
|
+
chosenModel = 'auto';
|
|
619
|
+
// Step 2: Add to config
|
|
620
|
+
const entry = {
|
|
621
|
+
name: chosenModel,
|
|
622
|
+
endpoint: chosenEndpoint,
|
|
623
|
+
model: chosenModel,
|
|
624
|
+
priority: 1,
|
|
625
|
+
enabled: true,
|
|
626
|
+
};
|
|
627
|
+
// Replace any existing chain or add to it
|
|
628
|
+
config.router.chain = [entry, ...config.router.chain.filter((e) => e.endpoint !== chosenEndpoint)];
|
|
629
|
+
saveConfig(config);
|
|
630
|
+
console.log(pc.green(`\n✓ Added model "${pc.bold(chosenModel)}" at ${chosenEndpoint}`));
|
|
631
|
+
// Step 3: Test the model
|
|
632
|
+
const testPrompt = await ctx.askLine('\nRun a quick test? (Y/n): ');
|
|
633
|
+
if (testPrompt.toLowerCase() !== 'n') {
|
|
634
|
+
console.log(pc.dim('\nSending test request...'));
|
|
635
|
+
try {
|
|
636
|
+
const start = Date.now();
|
|
637
|
+
const testMessages = [
|
|
638
|
+
{ role: 'system', content: 'You are a helpful assistant. Respond in 1-2 sentences.' },
|
|
639
|
+
{ role: 'user', content: 'Say hello and confirm you are working.' },
|
|
640
|
+
];
|
|
641
|
+
const testRouter = ctx.router;
|
|
642
|
+
const completion = await testRouter.chat.completions.create({
|
|
643
|
+
model: chosenModel,
|
|
644
|
+
messages: testMessages,
|
|
645
|
+
temperature: 0.1,
|
|
646
|
+
});
|
|
647
|
+
const elapsed = Date.now() - start;
|
|
648
|
+
const text = completion.choices?.[0]?.message?.content || '(no response)';
|
|
649
|
+
console.log(pc.green(`\n✓ Response received in ${elapsed}ms:`));
|
|
650
|
+
console.log(` ${pc.white(text)}`);
|
|
651
|
+
}
|
|
652
|
+
catch (err) {
|
|
653
|
+
console.log(pc.yellow(`\n⚠ Test failed: ${err.message}`));
|
|
654
|
+
console.log(' The model is configured but may need troubleshooting.');
|
|
655
|
+
console.log(` Check ${pc.cyan(ctx.configDir + '/config.json')} and verify the endpoint.`);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
console.log(pc.green(`\n✓ Onboarding complete! Configuration saved to:`));
|
|
659
|
+
console.log(` ${pc.cyan(ctx.configDir + '/config.json')}`);
|
|
660
|
+
console.log(`\nType ${pc.cyan('?')} to see all available commands, or just start typing.`);
|
|
661
|
+
}
|
|
662
|
+
},
|
|
663
|
+
{
|
|
664
|
+
name: '/tui',
|
|
665
|
+
description: 'Toggle the Terminal User Interface (TUI) dashboard',
|
|
666
|
+
usage: '/tui',
|
|
667
|
+
helpText: 'Switch between standard REPL chat mode and the side-by-side Terminal dashboard mode (which includes resource charts, model settings, and context monitors).',
|
|
668
|
+
execute: async (args, ctx) => {
|
|
669
|
+
if (!ctx.rl) {
|
|
670
|
+
throw new Error('SWITCH_MODE_CLI');
|
|
671
|
+
}
|
|
672
|
+
else {
|
|
673
|
+
throw new Error('SWITCH_MODE_TUI');
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
},
|
|
677
|
+
{
|
|
678
|
+
name: '/image',
|
|
679
|
+
description: 'Generate an image using local Stable Diffusion WebUI or Pollinations AI',
|
|
680
|
+
usage: '/image <prompt> [--output path] [--provider auto|sd-webui|pollinations] [--width 512] [--height 512] [--steps 20]',
|
|
681
|
+
helpText: 'Generate an image using a local Stable Diffusion WebUI instance (http://127.0.0.1:7860) or free Pollinations AI.\n\nArguments:\n <prompt> Detailed description of the image to generate\n --provider <engine> Engine: auto (local SD with Pollinations fallback), sd-webui, or pollinations\n --output <path> Filepath to save PNG (default: ./assets/images/img_<timestamp>.png)\n --width <pixels> Image width (default: 512)\n --height <pixels> Image height (default: 512)\n --steps <count> Sampling steps for local SD (default: 20)',
|
|
682
|
+
execute: async (args, _ctx) => {
|
|
683
|
+
const promptText = args.trim();
|
|
684
|
+
if (!promptText) {
|
|
685
|
+
console.log(pc.yellow('Usage: /image <prompt> [--provider auto|sd-webui|pollinations] [--output path] [--width 512] [--height 512] [--steps 20]'));
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
console.log(pc.cyan(`\n Generating image...`));
|
|
689
|
+
const { generateImage } = await import('../tools/builtin/image.js');
|
|
690
|
+
let width;
|
|
691
|
+
let height;
|
|
692
|
+
let steps;
|
|
693
|
+
let provider;
|
|
694
|
+
let output_path;
|
|
695
|
+
const cleanedPrompt = promptText
|
|
696
|
+
.replace(/--provider\s+([^\s]+)/i, (_, pr) => {
|
|
697
|
+
if (['auto', 'sd-webui', 'pollinations'].includes(pr.toLowerCase())) {
|
|
698
|
+
provider = pr.toLowerCase();
|
|
699
|
+
}
|
|
700
|
+
return '';
|
|
701
|
+
})
|
|
702
|
+
.replace(/--output\s+([^\s]+)/i, (_, p) => { output_path = p; return ''; })
|
|
703
|
+
.replace(/--width\s+(\d+)/i, (_, w) => { width = parseInt(w, 10); return ''; })
|
|
704
|
+
.replace(/--height\s+(\d+)/i, (_, h) => { height = parseInt(h, 10); return ''; })
|
|
705
|
+
.replace(/--steps\s+(\d+)/i, (_, s) => { steps = parseInt(s, 10); return ''; })
|
|
706
|
+
.trim();
|
|
707
|
+
const res = await generateImage({
|
|
708
|
+
prompt: cleanedPrompt || promptText,
|
|
709
|
+
width,
|
|
710
|
+
height,
|
|
711
|
+
steps,
|
|
712
|
+
provider,
|
|
713
|
+
output_path,
|
|
714
|
+
});
|
|
715
|
+
if (res.success) {
|
|
716
|
+
console.log(pc.green(`\n✔ ${res.content}`));
|
|
717
|
+
}
|
|
718
|
+
else {
|
|
719
|
+
console.log(pc.red(`\n✗ Image generation failed: ${res.error}`));
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
},
|
|
723
|
+
{
|
|
724
|
+
name: '/autopilot',
|
|
725
|
+
description: 'Autonomously implement a feature: branch, code, test, commit, and PR',
|
|
726
|
+
usage: '/autopilot <feature description>',
|
|
727
|
+
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.',
|
|
728
|
+
execute: async (args, ctx) => {
|
|
729
|
+
const idea = args.trim();
|
|
730
|
+
if (!idea) {
|
|
731
|
+
console.log(pc.red('[WARN] Usage: /autopilot <feature description>'));
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
const repoInfo = getGitRepoInfo(ctx.toolContext.projectRoot);
|
|
735
|
+
if (!repoInfo) {
|
|
736
|
+
console.log(pc.yellow('[INFO] No GitHub remote found. Running in local-only mode (no PR will be created).'));
|
|
737
|
+
}
|
|
738
|
+
const slug = idea.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 40);
|
|
739
|
+
const branchName = `daedalus-autopilot-${slug}`;
|
|
740
|
+
try {
|
|
741
|
+
execSync(`git checkout -B ${branchName}`, { cwd: ctx.toolContext.projectRoot });
|
|
742
|
+
console.log(pc.green(`[OK] Created branch: ${branchName}`));
|
|
743
|
+
}
|
|
744
|
+
catch (err) {
|
|
745
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
746
|
+
console.log(pc.red(`[ERROR] Failed to create branch: ${msg}`));
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
const goal = `Implement the following feature: ${idea}`;
|
|
750
|
+
console.log(pc.cyan(`\n[AUTOPILOT] Starting autonomous implementation...`));
|
|
751
|
+
process.env.DAEDALUS_AUTO_APPROVE = 'true';
|
|
752
|
+
try {
|
|
753
|
+
const { Orchestrator } = await import('../agents/orchestrator.js');
|
|
754
|
+
const orchestrator = new Orchestrator(ctx.router, ctx.messages, ctx.toolContext, ctx.sessionManager);
|
|
755
|
+
const result = await orchestrator.run(goal);
|
|
756
|
+
console.log(pc.white(`\n${result}`));
|
|
757
|
+
const orchestrationFailed = result.startsWith('Orchestration failed') || result.includes('## Orchestration Hit Verification Failures');
|
|
758
|
+
const wasAborted = result.includes('## Orchestration Paused');
|
|
759
|
+
if (orchestrationFailed || wasAborted) {
|
|
760
|
+
throw new Error(orchestrationFailed ? 'Orchestration reported failure' : 'Orchestration was paused/aborted');
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
catch (err) {
|
|
764
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
765
|
+
console.log(pc.red(`\n[ERROR] Implementation failed: ${msg}`));
|
|
766
|
+
console.log(pc.yellow('[ROLLBACK] Rolling back to main branch...'));
|
|
767
|
+
try {
|
|
768
|
+
execSync('git reset --hard', { cwd: ctx.toolContext.projectRoot });
|
|
769
|
+
execSync('git checkout main', { cwd: ctx.toolContext.projectRoot });
|
|
770
|
+
execSync(`git branch -D ${branchName}`, { cwd: ctx.toolContext.projectRoot });
|
|
771
|
+
console.log(pc.green('[OK] Rolled back to main. Branch deleted.'));
|
|
772
|
+
}
|
|
773
|
+
catch (rollbackErr) {
|
|
774
|
+
const rbMsg = rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr);
|
|
775
|
+
console.log(pc.red(`[ERROR] Rollback failed: ${rbMsg}. Manual cleanup may be needed.`));
|
|
776
|
+
}
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
console.log(pc.cyan('\n[AUTOPILOT] Committing changes...'));
|
|
780
|
+
try {
|
|
781
|
+
execSync('git add .', { cwd: ctx.toolContext.projectRoot });
|
|
782
|
+
const cleanTitle = idea.replace(/[^a-zA-Z0-9 ]/g, '').trim();
|
|
783
|
+
execSync(`git commit -m "feat: ${cleanTitle}"`, { cwd: ctx.toolContext.projectRoot });
|
|
784
|
+
console.log(pc.green('[OK] Changes committed.'));
|
|
785
|
+
}
|
|
786
|
+
catch (err) {
|
|
787
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
788
|
+
if (msg.includes('nothing to commit')) {
|
|
789
|
+
console.log(pc.yellow('[INFO] No changes to commit.'));
|
|
790
|
+
}
|
|
791
|
+
else {
|
|
792
|
+
console.log(pc.red(`[ERROR] Failed to commit: ${msg}`));
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
if (repoInfo) {
|
|
797
|
+
console.log(pc.cyan('\n[AUTOPILOT] Pushing branch and creating PR...'));
|
|
798
|
+
let token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
|
|
799
|
+
if (!token) {
|
|
800
|
+
try {
|
|
801
|
+
token = execSync('gh auth token', { encoding: 'utf8' }).trim();
|
|
802
|
+
}
|
|
803
|
+
catch {
|
|
804
|
+
console.log(pc.yellow('[INFO] No GitHub token found. Run `gh auth login` or set GITHUB_TOKEN.'));
|
|
805
|
+
console.log(pc.yellow(`[INFO] Branch ${branchName} is ready locally. Push manually.`));
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
try {
|
|
810
|
+
execSync(`git push -u origin ${branchName} --force`, { cwd: ctx.toolContext.projectRoot });
|
|
811
|
+
const prResponse = await fetch(`https://api.github.com/repos/${repoInfo.owner}/${repoInfo.repo}/pulls`, {
|
|
812
|
+
method: 'POST',
|
|
813
|
+
headers: {
|
|
814
|
+
'Authorization': `Bearer ${token}`,
|
|
815
|
+
'Content-Type': 'application/json',
|
|
816
|
+
},
|
|
817
|
+
body: JSON.stringify({
|
|
818
|
+
title: `[Autopilot] ${idea}`,
|
|
819
|
+
head: branchName,
|
|
820
|
+
base: 'main',
|
|
821
|
+
body: `## Description\n\nAutonomously implemented by Daedalus Autopilot.\n\n**Feature:** ${idea}\n\n---\n_Generated by \`/autopilot\`_`,
|
|
822
|
+
}),
|
|
823
|
+
});
|
|
824
|
+
if (prResponse.ok) {
|
|
825
|
+
const pr = await prResponse.json();
|
|
826
|
+
console.log(pc.green(`\n[OK] Pull Request created: ${pr.html_url}`));
|
|
827
|
+
}
|
|
828
|
+
else {
|
|
829
|
+
const errText = await prResponse.text();
|
|
830
|
+
console.log(pc.red(`[ERROR] Failed to create PR: ${prResponse.status} ${errText}`));
|
|
831
|
+
console.log(pc.yellow(`[INFO] Branch ${branchName} is pushed. Create PR manually.`));
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
catch (err) {
|
|
835
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
836
|
+
console.log(pc.red(`[ERROR] Push/PR failed: ${msg}`));
|
|
837
|
+
console.log(pc.yellow(`[INFO] Branch ${branchName} is ready locally.`));
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
else {
|
|
841
|
+
console.log(pc.yellow('\n[INFO] No GitHub remote configured. Implementation is committed locally.'));
|
|
842
|
+
console.log(pc.yellow(`[INFO] Branch: ${branchName}`));
|
|
843
|
+
}
|
|
844
|
+
console.log(pc.cyan(`\n[AUTOPILOT] Done! Run 'git checkout main' to return to main branch.`));
|
|
845
|
+
}
|
|
846
|
+
},
|
|
847
|
+
{
|
|
848
|
+
name: '/preview',
|
|
849
|
+
description: 'Screenshot a local HTML file or URL and save the image',
|
|
850
|
+
usage: '/preview <filepath | url>',
|
|
851
|
+
helpText: 'Opens the given HTML file or URL in headless Chrome and saves a PNG screenshot.\n Examples:\n /preview preview.html\n /preview http://localhost:3000\n /preview ./src/components/output.html',
|
|
852
|
+
execute: async (args, ctx) => {
|
|
853
|
+
const target = args.trim();
|
|
854
|
+
if (!target) {
|
|
855
|
+
console.log(pc.red('[WARN] Usage: /preview <filepath or URL>'));
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
let url = target;
|
|
859
|
+
if (!/^https?:\/\//i.test(target) && !/^file:\/\//i.test(target)) {
|
|
860
|
+
const absPath = path.resolve(target);
|
|
861
|
+
if (!fs.existsSync(absPath)) {
|
|
862
|
+
console.log(pc.red(`[ERROR] File not found: ${absPath}`));
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
url = `file:///${absPath.replace(/\\/g, '/')}`;
|
|
866
|
+
}
|
|
867
|
+
console.log(pc.dim(`[PREVIEW] Screenshotting ${url}...`));
|
|
868
|
+
try {
|
|
869
|
+
const { screenshotPage } = await import('../tools/builtin/screenshot.js');
|
|
870
|
+
const result = await screenshotPage({ url }, ctx.toolContext);
|
|
871
|
+
if (!result.success) {
|
|
872
|
+
console.log(pc.red(`[ERROR] ${result.error || 'Screenshot failed'}`));
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
const data = JSON.parse(result.content);
|
|
876
|
+
console.log(pc.green(`[OK] Screenshot saved to: ${data.savedPath}`));
|
|
877
|
+
console.log(pc.dim(` URL: ${data.url}`));
|
|
878
|
+
}
|
|
879
|
+
catch (err) {
|
|
880
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
881
|
+
console.log(pc.red(`[ERROR] Preview failed: ${msg}`));
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
},
|
|
885
|
+
];
|
|
886
|
+
//# sourceMappingURL=agents.js.map
|