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.
Files changed (53) hide show
  1. package/AGENTS.md +31 -10
  2. package/CHANGELOG.md +7 -0
  3. package/README.md +3 -3
  4. package/dist/agents/ensemble.d.ts.map +1 -1
  5. package/dist/agents/ensemble.js +2 -2
  6. package/dist/agents/ensemble.js.map +1 -1
  7. package/dist/agents/orchestrator-types.d.ts +20 -0
  8. package/dist/agents/orchestrator-types.d.ts.map +1 -0
  9. package/dist/agents/orchestrator-types.js +3 -0
  10. package/dist/agents/orchestrator-types.js.map +1 -0
  11. package/dist/agents/orchestrator-validation.d.ts +19 -0
  12. package/dist/agents/orchestrator-validation.d.ts.map +1 -0
  13. package/dist/agents/orchestrator-validation.js +227 -0
  14. package/dist/agents/orchestrator-validation.js.map +1 -0
  15. package/dist/agents/orchestrator-verification.d.ts +28 -0
  16. package/dist/agents/orchestrator-verification.d.ts.map +1 -0
  17. package/dist/agents/orchestrator-verification.js +355 -0
  18. package/dist/agents/orchestrator-verification.js.map +1 -0
  19. package/dist/agents/orchestrator.d.ts +1 -49
  20. package/dist/agents/orchestrator.d.ts.map +1 -1
  21. package/dist/agents/orchestrator.js +40 -678
  22. package/dist/agents/orchestrator.js.map +1 -1
  23. package/dist/agents/orchestrator.test.js +33 -81
  24. package/dist/agents/orchestrator.test.js.map +1 -1
  25. package/dist/commands/agents.d.ts +3 -0
  26. package/dist/commands/agents.d.ts.map +1 -0
  27. package/dist/commands/agents.js +886 -0
  28. package/dist/commands/agents.js.map +1 -0
  29. package/dist/commands/context.d.ts +3 -0
  30. package/dist/commands/context.d.ts.map +1 -0
  31. package/dist/commands/context.js +875 -0
  32. package/dist/commands/context.js.map +1 -0
  33. package/dist/commands/dev.d.ts +3 -0
  34. package/dist/commands/dev.d.ts.map +1 -0
  35. package/dist/commands/dev.js +820 -0
  36. package/dist/commands/dev.js.map +1 -0
  37. package/dist/commands/index.d.ts +5 -0
  38. package/dist/commands/index.d.ts.map +1 -0
  39. package/dist/commands/index.js +88 -0
  40. package/dist/commands/index.js.map +1 -0
  41. package/dist/commands/types.d.ts +45 -0
  42. package/dist/commands/types.d.ts.map +1 -0
  43. package/dist/commands/types.js +2 -0
  44. package/dist/commands/types.js.map +1 -0
  45. package/dist/commands.d.ts +2 -46
  46. package/dist/commands.d.ts.map +1 -1
  47. package/dist/commands.js +1 -2639
  48. package/dist/commands.js.map +1 -1
  49. package/dist/config/index.d.ts +78 -78
  50. package/dist/model.d.ts.map +1 -1
  51. package/dist/model.js +11 -6
  52. package/dist/model.js.map +1 -1
  53. package/package.json +1 -1
@@ -0,0 +1,820 @@
1
+ // Dev tools, codebase, diagnostics & config commands
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import pc from 'picocolors';
5
+ import { discoverLocalServers } from '../config/index.js';
6
+ import { getSessionTodos } from '../tools/builtin/todo.js';
7
+ import { turnSeparator } from '../formatting.js';
8
+ export const devCommands = [
9
+ {
10
+ name: '/branch',
11
+ description: 'Git branch operations',
12
+ execute: async (args, ctx) => {
13
+ try {
14
+ const { execute: termExec } = await import('../tools/builtin/terminal.js');
15
+ const arg = args.trim();
16
+ if (!arg) {
17
+ const currentBranchResult = await termExec({ command: 'git branch --show-current', timeout: 5, workdir: process.cwd() }, ctx.toolContext);
18
+ const current = currentBranchResult.content?.trim();
19
+ if (current) {
20
+ console.log(`\n ${pc.cyan('Current Git branch:')} ${pc.bold(current)}`);
21
+ }
22
+ else {
23
+ console.log(pc.red('\n Not in a Git repository or no branch found.'));
24
+ }
25
+ }
26
+ else {
27
+ console.log(`\n Creating and switching to branch ${pc.cyan(arg)}...`);
28
+ const checkoutResult = await termExec({ command: `git checkout -b ${arg}`, timeout: 10, workdir: process.cwd() }, ctx.toolContext);
29
+ if (checkoutResult.success) {
30
+ console.log(pc.green(` [OK] Switched to a new branch '${arg}'`));
31
+ }
32
+ else {
33
+ console.log(pc.yellow(` Branch might already exist, attempting to switch...`));
34
+ const switchResult = await termExec({ command: `git checkout ${arg}`, timeout: 10, workdir: process.cwd() }, ctx.toolContext);
35
+ if (switchResult.success) {
36
+ console.log(pc.green(` [OK] Switched to branch '${arg}'`));
37
+ }
38
+ else {
39
+ console.log(pc.red(` Switch failed: ${switchResult.error || switchResult.content}`));
40
+ }
41
+ }
42
+ }
43
+ }
44
+ catch (err) {
45
+ console.log(pc.red(`[WARN] Branch command error: ${err.message}`));
46
+ }
47
+ }
48
+ },
49
+ {
50
+ name: '/pr',
51
+ description: 'Generate PR description Compared to base branch',
52
+ execute: async (args, ctx) => {
53
+ const arg = args.trim();
54
+ try {
55
+ const { execute: termExec } = await import('../tools/builtin/terminal.js');
56
+ const gitCheck = await termExec({ command: 'git rev-parse --is-inside-work-tree', timeout: 5, workdir: process.cwd() }, ctx.toolContext);
57
+ if (!gitCheck.success) {
58
+ console.log(pc.red(' Error: Not inside a Git repository.'));
59
+ return;
60
+ }
61
+ let baseBranch = arg || 'main';
62
+ if (!arg) {
63
+ const mainCheck = await termExec({ command: 'git show-ref --verify refs/heads/main', timeout: 5, workdir: process.cwd() }, ctx.toolContext);
64
+ if (!mainCheck.success) {
65
+ const masterCheck = await termExec({ command: 'git show-ref --verify refs/heads/master', timeout: 5, workdir: process.cwd() }, ctx.toolContext);
66
+ if (masterCheck.success) {
67
+ baseBranch = 'master';
68
+ }
69
+ }
70
+ }
71
+ const currentBranchResult = await termExec({ command: 'git branch --show-current', timeout: 5, workdir: process.cwd() }, ctx.toolContext);
72
+ const currentBranch = currentBranchResult.content?.trim();
73
+ console.log(`\n Comparing ${pc.cyan(currentBranch || 'HEAD')} with base branch ${pc.cyan(baseBranch)}...`);
74
+ const commitsResult = await termExec({ command: `git log ${baseBranch}..HEAD --oneline`, timeout: 10, workdir: process.cwd() }, ctx.toolContext);
75
+ const commitList = commitsResult.content?.trim() || '';
76
+ const diffResult = await termExec({ command: `git diff ${baseBranch}...HEAD`, timeout: 15, workdir: process.cwd() }, ctx.toolContext);
77
+ const diffContent = diffResult.content?.slice(0, 15000) || '';
78
+ if (!commitList && !diffContent) {
79
+ console.log(pc.yellow(` No commits or diff found between ${currentBranch} and ${baseBranch}.`));
80
+ return;
81
+ }
82
+ console.log(pc.dim(' Analyzing changes and generating PR description...'));
83
+ const aiResponse = await ctx.router.chat.completions.create({
84
+ model: 'auto',
85
+ messages: [
86
+ {
87
+ role: 'system',
88
+ content: 'You write clean, comprehensive, professional Pull Request descriptions in Markdown format. Output ONLY the markdown content — no extra chat, wrapper, or quotes.'
89
+ },
90
+ {
91
+ role: 'user',
92
+ content: `Generate a Pull Request description for the current branch compared to ${baseBranch}.\n\nCommits:\n${commitList}\n\nDiff:\n${diffContent}`
93
+ }
94
+ ],
95
+ temperature: 0.3,
96
+ });
97
+ const prDesc = (aiResponse.choices[0]?.message?.content || '').trim();
98
+ if (!prDesc) {
99
+ console.log(pc.red(' Failed to generate PR description.'));
100
+ return;
101
+ }
102
+ console.log(pc.bold('\n--- Generated PR Description ---'));
103
+ console.log(prDesc);
104
+ console.log(pc.bold('--------------------------------'));
105
+ const outPath = path.join(process.cwd(), 'pr-desc.md');
106
+ fs.writeFileSync(outPath, prDesc, 'utf8');
107
+ console.log(pc.green(`\n[OK] PR description saved to ${pc.cyan('pr-desc.md')}`));
108
+ }
109
+ catch (err) {
110
+ console.log(pc.red(`[WARN] PR command error: ${err.message}`));
111
+ }
112
+ }
113
+ },
114
+ {
115
+ name: '/debug',
116
+ description: 'Run command and autonomously debug failures',
117
+ execute: async (args, ctx) => {
118
+ const debugCmd = args.trim();
119
+ if (!debugCmd) {
120
+ console.log(pc.red(' Error: Please specify a command to run. Example: /debug npm test'));
121
+ return;
122
+ }
123
+ console.log(`\n ${pc.cyan('Starting autonomous debugging loop for:')} ${pc.bold(debugCmd)}`);
124
+ const MAX_RETRIES = 5;
125
+ let attempt = 1;
126
+ let success = false;
127
+ while (attempt <= MAX_RETRIES) {
128
+ console.log(`\n ${pc.yellow(`[Attempt ${attempt}/${MAX_RETRIES}]`)} Running: ${pc.bold(debugCmd)}...`);
129
+ try {
130
+ const { execute: termExec } = await import('../tools/builtin/terminal.js');
131
+ const execResult = await termExec({ command: debugCmd, timeout: 60, workdir: process.cwd() }, ctx.toolContext);
132
+ if (execResult.success) {
133
+ console.log(pc.green(`\n ${pc.green('✔')} ${pc.bold(`Success on attempt ${attempt}!`)} Command passed with exit code 0.`));
134
+ success = true;
135
+ break;
136
+ }
137
+ console.log(pc.red(`\n ${pc.red('✗')} ${pc.bold(`Command failed on attempt ${attempt}.`)}`));
138
+ const stdout = execResult.content || '';
139
+ const errorMsg = execResult.error || '';
140
+ const logs = `${stdout}\n${errorMsg}`.trim();
141
+ console.log(pc.bold('\n--- Failure Logs ---'));
142
+ const logLines = logs.split('\n');
143
+ const preview = logLines.length > 20 ? logLines.slice(-20).join('\n') : logs;
144
+ console.log(preview);
145
+ if (logLines.length > 20) {
146
+ console.log(pc.dim(`\n (... truncated ${logLines.length - 20} lines of logs ...)`));
147
+ }
148
+ console.log(pc.bold('--------------------'));
149
+ if (attempt === MAX_RETRIES) {
150
+ console.log(pc.red(`\n Reached maximum attempt limit of ${MAX_RETRIES}. Debugging loop failed.`));
151
+ break;
152
+ }
153
+ console.log(pc.dim('\n Calling Daedalus to analyze failure and apply a fix...'));
154
+ const debugPrompt = `The command "${debugCmd}" failed on attempt ${attempt}.
155
+ Here are the execution logs (showing the failure details):
156
+
157
+ ${logs.slice(-6000)}
158
+
159
+ Please analyze the error, identify which files need correction, and apply surgical edits using 'patch' or write tools to fix the issue.
160
+ Once you have finished making changes, I will automatically re-run the command to verify if it passes.`;
161
+ await ctx.callModelWithTools(debugPrompt);
162
+ }
163
+ catch (err) {
164
+ console.log(pc.red(`\n Error in debugging loop: ${err.message}`));
165
+ break;
166
+ }
167
+ attempt++;
168
+ }
169
+ if (!success) {
170
+ console.log(pc.red(`\n Autonomous debugging did not succeed after ${MAX_RETRIES} attempts.`));
171
+ }
172
+ turnSeparator();
173
+ }
174
+ },
175
+ {
176
+ name: '/commit',
177
+ description: 'Stage and commit changes',
178
+ execute: async (args, ctx) => {
179
+ const forcedMsg = args.trim();
180
+ try {
181
+ const { execute: termExec } = await import('../tools/builtin/terminal.js');
182
+ const statusResult = await termExec({ command: 'git status --short', timeout: 10, workdir: process.cwd() }, ctx.toolContext);
183
+ console.log(pc.bold('\n--- Git Status ---'));
184
+ console.log(statusResult.content || pc.gray('(clean)'));
185
+ if (!statusResult.content?.trim()) {
186
+ console.log(pc.yellow('Nothing to commit.'));
187
+ return;
188
+ }
189
+ const addResult = await termExec({ command: 'git add -A', timeout: 10, workdir: process.cwd() }, ctx.toolContext);
190
+ if (!addResult.success) {
191
+ console.log(pc.red(`Stage failed: ${addResult.error}`));
192
+ return;
193
+ }
194
+ let commitMsg = forcedMsg;
195
+ if (!commitMsg) {
196
+ const diffResult = await termExec({ command: 'git diff --cached --stat', timeout: 10, workdir: process.cwd() }, ctx.toolContext);
197
+ const diffFull = await termExec({ command: 'git diff --cached', timeout: 10, workdir: process.cwd() }, ctx.toolContext);
198
+ const diffContent = diffFull.content?.slice(0, 6000) || '';
199
+ if (diffResult.content)
200
+ console.log(pc.gray(diffResult.content));
201
+ if (diffContent) {
202
+ console.log(pc.dim(' Generating commit message...'));
203
+ try {
204
+ const aiResponse = await ctx.router.chat.completions.create({
205
+ model: 'auto',
206
+ messages: [
207
+ { role: 'system', content: 'You write concise git commit messages following the Conventional Commits spec (type(scope): description). Output only the commit message — no explanation, no quotes, no extra text.' },
208
+ { role: 'user', content: `Write a commit message for this diff:\n\n${diffContent}` }
209
+ ],
210
+ temperature: 0.2,
211
+ max_tokens: 80,
212
+ });
213
+ const suggested = ((aiResponse.choices[0]?.message?.content) || '').trim().split('\n')[0].trim();
214
+ if (suggested) {
215
+ console.log(`\n ${pc.dim('Suggested:')} ${pc.cyan(suggested)}`);
216
+ const choice = await ctx.askLine(pc.dim(' [Enter] accept [e] edit [n] cancel: '));
217
+ if (choice.trim().toLowerCase() === 'n') {
218
+ console.log(pc.yellow('Commit cancelled.'));
219
+ await termExec({ command: 'git restore --staged .', timeout: 10, workdir: process.cwd() }, ctx.toolContext);
220
+ return;
221
+ }
222
+ else if (choice.trim().toLowerCase() === 'e') {
223
+ commitMsg = await ctx.askLine(pc.cyan(' Commit message: '));
224
+ }
225
+ else {
226
+ commitMsg = suggested;
227
+ }
228
+ }
229
+ }
230
+ catch {
231
+ // Model unavailable — manual fallback
232
+ }
233
+ }
234
+ if (!commitMsg) {
235
+ commitMsg = await ctx.askLine(pc.cyan(' Commit message: '));
236
+ }
237
+ if (!commitMsg.trim()) {
238
+ console.log(pc.yellow('Commit cancelled — empty message.'));
239
+ await termExec({ command: 'git restore --staged .', timeout: 10, workdir: process.cwd() }, ctx.toolContext);
240
+ return;
241
+ }
242
+ }
243
+ const commitResult = await termExec({ command: `git commit -m ${JSON.stringify(commitMsg)}`, timeout: 10, workdir: process.cwd() }, ctx.toolContext);
244
+ if (commitResult.success) {
245
+ console.log(pc.green(`\n[OK] Commit: ${commitMsg.slice(0, 60)}`));
246
+ }
247
+ else {
248
+ console.log(pc.red(`Commit failed: ${commitResult.error}`));
249
+ }
250
+ }
251
+ catch (err) {
252
+ console.log(pc.red(`[WARN] Commit error: ${err.message}`));
253
+ }
254
+ }
255
+ },
256
+ {
257
+ name: '/project',
258
+ description: 'View or set project config settings (.daedalusrc)',
259
+ usage: '/project [set <key> <value> | get <key> | reset]',
260
+ helpText: 'Manage project-specific configuration overrides stored in .daedalusrc.\n\nSubcommands:\n (no args) Show all active project configuration overrides\n set <key> <value> Set a project config override\n get <key> Print the value of a specific project config key\n reset Reset and delete project settings file\n\nCommon Overridable Keys:\n modelOverride Override primary model selection (e.g. "openai/gpt-4.1")\n tools.sandbox Isolate execution for this project ("none" | "docker")\n context.maxTokens Adjust context limit for this workspace (e.g. 64000)',
261
+ execute: async (args, _ctx) => {
262
+ const rest = args.trim();
263
+ const { loadProjectConfig, saveProjectConfig, hasLocalConfig } = await import('../tools/builtin/project-config.js');
264
+ if (!rest) {
265
+ const cfg = loadProjectConfig(process.cwd());
266
+ const isLocal = hasLocalConfig(process.cwd());
267
+ console.log(pc.bold(`\n--- Project Config (${isLocal ? '.daedalusrc' : 'global'}) ---`));
268
+ console.log(JSON.stringify(cfg, null, 2));
269
+ console.log(pc.bold('----------------------------------'));
270
+ console.log(pc.gray('Use /project set <key> = <value> to update'));
271
+ console.log(pc.gray('Use /project init to create a .daedalusrc in this project'));
272
+ return;
273
+ }
274
+ if (rest === 'init') {
275
+ const localPath = path.join(process.cwd(), '.daedalusrc');
276
+ if (fs.existsSync(localPath)) {
277
+ console.log(pc.yellow('.daedalusrc already exists in this project'));
278
+ return;
279
+ }
280
+ const cfg = loadProjectConfig(process.cwd());
281
+ saveProjectConfig(cfg, true);
282
+ console.log(pc.green('Created .daedalusrc — project config is now local to this repo'));
283
+ return;
284
+ }
285
+ if (rest.startsWith('set ')) {
286
+ const setArgs = rest.substring(4).trim();
287
+ const eqIdx = setArgs.indexOf('=');
288
+ let key, value;
289
+ if (eqIdx >= 0) {
290
+ key = setArgs.slice(0, eqIdx).trim();
291
+ value = setArgs.slice(eqIdx + 1).trim();
292
+ }
293
+ else {
294
+ const parts = setArgs.split(/\s+/);
295
+ key = parts[0];
296
+ value = parts.slice(1).join(' ');
297
+ }
298
+ if (!key || !value) {
299
+ console.log(pc.red('Usage: /project set <key> = <value>'));
300
+ }
301
+ else {
302
+ const cfg = loadProjectConfig(process.cwd());
303
+ let parsedVal = value;
304
+ if (value.toLowerCase() === 'true')
305
+ parsedVal = true;
306
+ else if (value.toLowerCase() === 'false')
307
+ parsedVal = false;
308
+ else if (!isNaN(Number(value)))
309
+ parsedVal = Number(value);
310
+ cfg[key] = parsedVal;
311
+ const isLocal = hasLocalConfig(process.cwd());
312
+ saveProjectConfig(cfg, isLocal);
313
+ console.log(pc.green(`Set ${key} = ${value} (${isLocal ? '.daedalusrc' : 'global'})`));
314
+ }
315
+ }
316
+ else {
317
+ console.log(pc.red(`Unknown subcommand: ${rest}. Try: /project, /project set <key> = <value>, /project init`));
318
+ }
319
+ }
320
+ },
321
+ {
322
+ name: '/test',
323
+ aliases: ['test'],
324
+ description: 'Run test loop and fix failures (supports --git-aware / -g for smart test selection)',
325
+ usage: '/test [--git-aware | -g] [maxLoops]',
326
+ helpText: 'Runs your test suite and automatically invokes Daedalus tools to fix failing assertions. Use --git-aware or -g to focus only on tests affected by recent git changes.',
327
+ execute: async (args, ctx) => {
328
+ const isGitAware = args.includes('--git-aware') || args.includes('-g');
329
+ const cleanArgs = args.replace('--git-aware', '').replace('-g', '').trim();
330
+ const maxLoops = cleanArgs ? parseInt(cleanArgs, 10) || 3 : 3;
331
+ const { loadProjectConfig } = await import('../tools/builtin/project-config.js');
332
+ const { execute: termExec } = await import('../tools/builtin/terminal.js');
333
+ const { getGitAwareTestCommand } = await import('../utils/gitAwareTest.js');
334
+ const cfg = loadProjectConfig(process.cwd());
335
+ let testCmd = cfg.testCommand || 'npm test';
336
+ if (isGitAware) {
337
+ const gitAware = getGitAwareTestCommand(process.cwd(), testCmd);
338
+ if (gitAware.testFiles.length > 0) {
339
+ console.log(pc.cyan(`\n⚡ Git-Aware Mode: Detected ${gitAware.modifiedFiles.length} modified files → running ${gitAware.testFiles.length} target test suites:`));
340
+ console.log(pc.gray(gitAware.testFiles.map(f => ` • ${f}`).join('\n')));
341
+ testCmd = gitAware.command;
342
+ }
343
+ else {
344
+ console.log(pc.yellow(`\n⚡ Git-Aware Mode: No specific matching test files found for modified files. Running full test suite.`));
345
+ }
346
+ }
347
+ console.log(pc.bold(`\nTest-Run-Fix Loop (max ${maxLoops} iterations)`));
348
+ console.log(pc.gray(`Test command: ${testCmd}\n`));
349
+ for (let i = 0; i < maxLoops; i++) {
350
+ console.log(pc.cyan(`\n--- Run ${i + 1}/${maxLoops} ---`));
351
+ const result = await termExec({ command: testCmd, timeout: 120, workdir: process.cwd() }, ctx.toolContext);
352
+ console.log(result.content?.slice(0, 2000) || pc.gray('(no output)'));
353
+ if (result.success) {
354
+ console.log(pc.green('\n[OK] All tests passed!'));
355
+ break;
356
+ }
357
+ if (i === maxLoops - 1) {
358
+ console.log(pc.yellow(`\n[WARN] Max loops (${maxLoops}) reached. Tests still failing.`));
359
+ break;
360
+ }
361
+ const failureCtx = `Tests failed (run ${i + 1}/${maxLoops}). Output:\n\n${result.content?.slice(0, 8000) || 'Unknown failure'}\n\nAnalyze the failures and fix the code. Do not re-read files you already have in context.`;
362
+ await ctx.callModelWithTools(`User Prompt: ${failureCtx}`);
363
+ ctx.sessionManager.saveSessionState(ctx.messages, ctx.activeFiles, getSessionTodos(ctx.toolContext.sessionId));
364
+ }
365
+ }
366
+ },
367
+ {
368
+ name: '/watch',
369
+ aliases: ['watch'],
370
+ description: 'Start or stop background codebase file-watcher for automatic FTS5 symbol re-indexing',
371
+ usage: '/watch [start | stop | status]',
372
+ helpText: 'Watches project files for changes and automatically updates the codebase symbol index in real time as you save files.',
373
+ execute: async (args) => {
374
+ const { initIndexDb } = await import('../indexing/fts.js');
375
+ const { watchCodebase } = await import('../indexing/watcher.js');
376
+ const path = await import('path');
377
+ const action = args.trim().toLowerCase() || 'start';
378
+ if (action === 'stop') {
379
+ if (globalThis.__daedalusWatcher) {
380
+ globalThis.__daedalusWatcher.close();
381
+ delete globalThis.__daedalusWatcher;
382
+ console.log(pc.green('\n[OK] Codebase file watcher stopped.'));
383
+ }
384
+ else {
385
+ console.log(pc.yellow('\n[INFO] File watcher is not currently running.'));
386
+ }
387
+ return;
388
+ }
389
+ if (action === 'status') {
390
+ const isRunning = !!globalThis.__daedalusWatcher;
391
+ console.log(pc.cyan(`\n⚡ File Watcher Status: ${isRunning ? pc.bold(pc.green('ACTIVE')) : pc.dim('INACTIVE')}`));
392
+ return;
393
+ }
394
+ if (globalThis.__daedalusWatcher) {
395
+ console.log(pc.yellow('\n[INFO] File watcher is already running in background.'));
396
+ return;
397
+ }
398
+ try {
399
+ const cwd = process.cwd();
400
+ const dbPath = path.join(cwd, '.daedalus', 'index.db');
401
+ const db = initIndexDb(dbPath);
402
+ const projectHash = 'local';
403
+ const instance = watchCodebase(db, cwd, projectHash);
404
+ globalThis.__daedalusWatcher = instance;
405
+ console.log(pc.green('\n[OK] Started background codebase watcher! Symbol index will auto-update on file save.'));
406
+ }
407
+ catch (err) {
408
+ console.log(pc.red(`\n[ERROR] Failed to start file watcher: ${err.message}`));
409
+ }
410
+ }
411
+ },
412
+ {
413
+ name: '/index',
414
+ description: 'Index codebase for symbol search',
415
+ execute: async (args, ctx) => {
416
+ const parts = args.trim().split(/\s+/).filter(Boolean);
417
+ const opts = {};
418
+ for (const arg of parts) {
419
+ if (arg.startsWith('--exclude=')) {
420
+ opts.exclude = arg.split('=')[1].split(',');
421
+ }
422
+ else if (arg.startsWith('--ext=')) {
423
+ opts.extensions = arg.split('=')[1].split(',');
424
+ }
425
+ }
426
+ console.log(pc.bold('\n--- Indexing Codebase ---'));
427
+ console.log(pc.gray(`Project: ${process.cwd()}`));
428
+ const indexDbPath = ctx.getIndexDbPath();
429
+ if (!fs.existsSync(path.dirname(indexDbPath))) {
430
+ fs.mkdirSync(path.dirname(indexDbPath), { recursive: true });
431
+ }
432
+ const { initIndexDb } = await import('../indexing/fts.js');
433
+ const { indexCodebase } = await import('../indexing/indexer.js');
434
+ const db = initIndexDb(indexDbPath);
435
+ console.log(pc.gray('\nScanning files...'));
436
+ const start = Date.now();
437
+ try {
438
+ const barWidth = 20;
439
+ let lastPct = -1;
440
+ const onProgress = ({ current, total, file }) => {
441
+ const pct = Math.round((current / total) * 100);
442
+ if (pct === lastPct)
443
+ return;
444
+ lastPct = pct;
445
+ const filled = Math.round((current / total) * barWidth);
446
+ const bar = '\u2588'.repeat(filled) + '\u2591'.repeat(barWidth - filled);
447
+ process.stdout.write(`\r ${pc.cyan(bar)} ${pc.white(`${current}/${total}`)} ${pc.gray(file.slice(-40))}`);
448
+ };
449
+ const result = await indexCodebase(db, process.cwd(), ctx.projectHash, { ...opts, onProgress });
450
+ process.stdout.write('\n');
451
+ const elapsed = Date.now() - start;
452
+ ctx.toolContext.indexDb = db;
453
+ console.log(pc.green(`\n✔ Indexing complete in ${elapsed}ms`));
454
+ console.log(pc.white(` Total files: ${result.totalFiles}`));
455
+ console.log(pc.white(` Indexed files: ${result.indexedFiles}`));
456
+ console.log(pc.white(` Skipped (unchanged): ${result.skippedFiles}`));
457
+ if (result.errors.length > 0) {
458
+ console.log(pc.yellow(`\nErrors (${result.errors.length}):`));
459
+ result.errors.slice(0, 10).forEach(e => console.log(pc.red(` - ${e}`)));
460
+ if (result.errors.length > 10) {
461
+ console.log(pc.gray(` ... and ${result.errors.length - 10} more`));
462
+ }
463
+ }
464
+ }
465
+ catch (err) {
466
+ console.error(pc.red(`\n[ERROR] Indexing failed: ${err.message}`));
467
+ }
468
+ }
469
+ },
470
+ {
471
+ name: '/find',
472
+ description: 'Search indexed symbols',
473
+ execute: async (args, ctx) => {
474
+ const parts = args.trim().split(/\s+/).filter(Boolean);
475
+ if (parts.length === 0) {
476
+ console.log(pc.red('[WARN] Usage: /find <query> [limit]'));
477
+ return;
478
+ }
479
+ const query = parts[0];
480
+ const limit = parts[1] ? parseInt(parts[1], 10) : 30;
481
+ if (isNaN(limit)) {
482
+ console.log(pc.red('[WARN] Invalid limit'));
483
+ return;
484
+ }
485
+ const indexDbPath = ctx.getIndexDbPath();
486
+ if (!fs.existsSync(indexDbPath)) {
487
+ console.log(pc.yellow('[WARN] No index found. Run /index first.'));
488
+ return;
489
+ }
490
+ const { initIndexDb, searchSymbols } = await import('../indexing/fts.js');
491
+ const db = initIndexDb(indexDbPath);
492
+ console.log(pc.bold(`\n--- Symbol Search: "${query}" ---`));
493
+ const symbols = searchSymbols(db, query, ctx.projectHash, limit);
494
+ if (symbols.length === 0) {
495
+ console.log(pc.gray(' No symbols found.'));
496
+ return;
497
+ }
498
+ console.log(pc.white(`\nFound ${symbols.length} symbol(s):`));
499
+ for (const s of symbols) {
500
+ const kindColor = s.kind === 'function' ? pc.cyan : s.kind === 'class' ? pc.green : s.kind === 'interface' ? pc.blue : pc.white;
501
+ const loc = `${s.file_path}:${s.line_start}${s.line_end !== s.line_start ? '-' + s.line_end : ''}`;
502
+ console.log(` ${kindColor(`[${s.kind}]`)} ${pc.bold(s.name)} ${pc.dim(`(${loc})`)}`);
503
+ if (s.signature) {
504
+ console.log(pc.dim(` ${s.signature.slice(0, 100)}${s.signature.length > 100 ? '...' : ''}`));
505
+ }
506
+ }
507
+ }
508
+ },
509
+ {
510
+ name: '/refs',
511
+ description: 'Find symbol references (callers)',
512
+ execute: async (args, ctx) => {
513
+ const symbol = args.trim();
514
+ if (!symbol) {
515
+ console.log(pc.red('[WARN] Usage: /refs <symbol>'));
516
+ return;
517
+ }
518
+ const indexDbPath = ctx.getIndexDbPath();
519
+ if (!fs.existsSync(indexDbPath)) {
520
+ console.log(pc.yellow('[WARN] No index found. Run /index first.'));
521
+ return;
522
+ }
523
+ const { initIndexDb, findReferences } = await import('../indexing/fts.js');
524
+ const db = initIndexDb(indexDbPath);
525
+ console.log(pc.bold(`\n--- References to: ${symbol} ---`));
526
+ const refs = findReferences(db, symbol, ctx.projectHash);
527
+ if (refs.length === 0) {
528
+ console.log(pc.gray(' No references found.'));
529
+ return;
530
+ }
531
+ const byCaller = new Map();
532
+ for (const r of refs) {
533
+ const key = `${r.caller_name} (${r.caller_file}:${r.caller_line})`;
534
+ if (!byCaller.has(key))
535
+ byCaller.set(key, []);
536
+ byCaller.get(key).push(r);
537
+ }
538
+ console.log(pc.white(`\nFound ${refs.length} reference(s) from ${byCaller.size} caller(s):`));
539
+ for (const [caller, refs] of byCaller) {
540
+ console.log(pc.cyan(`\n ${caller}:`));
541
+ for (const r of refs.slice(0, 5)) {
542
+ console.log(pc.dim(` ${r.callee_name} at ${r.callee_file}:${r.callee_line}`));
543
+ }
544
+ if (refs.length > 5) {
545
+ console.log(pc.dim(` ... and ${refs.length - 5} more`));
546
+ }
547
+ }
548
+ }
549
+ },
550
+ {
551
+ name: '/def',
552
+ description: 'Get symbol definition',
553
+ execute: async (args, ctx) => {
554
+ const symbol = args.trim();
555
+ if (!symbol) {
556
+ console.log(pc.red('[WARN] Usage: /def <symbol>'));
557
+ return;
558
+ }
559
+ const indexDbPath = ctx.getIndexDbPath();
560
+ if (!fs.existsSync(indexDbPath)) {
561
+ console.log(pc.yellow('[WARN] No index found. Run /index first.'));
562
+ return;
563
+ }
564
+ const { initIndexDb, findDefinitions } = await import('../indexing/fts.js');
565
+ const db = initIndexDb(indexDbPath);
566
+ console.log(pc.bold(`\n--- Definition: ${symbol} ---`));
567
+ const defs = findDefinitions(db, symbol, ctx.projectHash);
568
+ if (defs.length === 0) {
569
+ console.log(pc.gray(' No definitions found.'));
570
+ return;
571
+ }
572
+ console.log(pc.white(`\nFound ${defs.length} definition(s):`));
573
+ for (const d of defs) {
574
+ const kindColor = d.kind === 'function' ? pc.cyan : d.kind === 'class' ? pc.green : d.kind === 'interface' ? pc.blue : pc.white;
575
+ const loc = `${d.file_path}:${d.line_start}${d.line_end !== d.line_start ? '-' + d.line_end : ''}`;
576
+ console.log(` ${kindColor(`[${d.kind}]`)} ${pc.bold(d.name)} ${pc.dim(`(${loc})`)}`);
577
+ if (d.signature) {
578
+ console.log(pc.dim(` ${d.signature.slice(0, 120)}${d.signature.length > 120 ? '...' : ''}`));
579
+ }
580
+ }
581
+ }
582
+ },
583
+ {
584
+ name: '/changelog',
585
+ description: 'View the latest CLI changes',
586
+ execute: async (_args, _ctx) => {
587
+ const { fileURLToPath } = await import('url');
588
+ const __filename = fileURLToPath(import.meta.url);
589
+ const __dirname = path.dirname(__filename);
590
+ const changelogPath = path.join(__dirname, '..', 'CHANGELOG.md');
591
+ if (!fs.existsSync(changelogPath)) {
592
+ console.log(pc.yellow('[WARN] CHANGELOG.md not found.'));
593
+ return;
594
+ }
595
+ const content = fs.readFileSync(changelogPath, 'utf8');
596
+ const lines = content.split('\n');
597
+ console.log(pc.bold('\n--- Latest CLI Changes ---'));
598
+ let versionCount = 0;
599
+ const maxVersions = 3;
600
+ const displayLines = [];
601
+ for (const line of lines) {
602
+ const isHeader = line.startsWith('# ') || line.startsWith('## ');
603
+ if (isHeader) {
604
+ versionCount++;
605
+ if (versionCount > maxVersions) {
606
+ break;
607
+ }
608
+ }
609
+ if (versionCount > 0) {
610
+ displayLines.push(line);
611
+ }
612
+ }
613
+ console.log(displayLines.join('\n').trim());
614
+ console.log(pc.bold('---------------------------\n'));
615
+ }
616
+ },
617
+ {
618
+ name: '/models',
619
+ description: 'List available and healthy models',
620
+ execute: async (args, ctx) => {
621
+ console.log(pc.bold('\n--- Available Models ---'));
622
+ const models = await ctx.router.listModels();
623
+ if (models.length === 0) {
624
+ console.log(pc.yellow(' No models found. Check your local servers (LM Studio, Ollama, etc.)'));
625
+ }
626
+ else {
627
+ for (const model of models) {
628
+ console.log(` • ${pc.cyan(model)}`);
629
+ }
630
+ }
631
+ const { checkModelHealth } = await import('../router/health.js');
632
+ const healthyModels = ctx.router.getHealthyModels();
633
+ console.log(pc.bold('\n--- Healthy Models ---'));
634
+ for (const model of healthyModels) {
635
+ const health = await checkModelHealth(model, 5000);
636
+ const status = health?.healthy ? pc.green('●') : pc.red('●');
637
+ console.log(` ${status} ${pc.cyan(model.name)} (${model.endpoint}) - ${model.model}`);
638
+ }
639
+ console.log(pc.bold('----------------------\n'));
640
+ }
641
+ },
642
+ {
643
+ name: '/config',
644
+ description: 'Show or modify global configuration',
645
+ usage: '/config [set <key> = <value> | get <key> | reset]',
646
+ helpText: 'Manage global settings. Setting a key applies it in real-time.\n\nSubcommands:\n (no args) Print the entire active configuration JSON\n set <key> = <value> Update a configuration value (e.g. /config set router.strategy = round-robin)\n get <key> Print the value of a specific config key\n reset Reset config to default settings\n\nConfiguration Keys Reference:\n [Router Settings]\n router.strategy Model routing strategy ("priority" | "round-robin" | "fastest")\n router.healthCheckInterval Interval in ms between background health checks (default: 30000)\n router.requestTimeout Timeout in ms for model API requests (default: 120000)\n router.defaultRateLimit Default RPM and TPM rate limit limits\n router.chain Array of configured model endpoints in the routing chain\n\n [Agent Settings]\n agents.default Default agent role to spawn (default: "coder")\n agents.available Array of available agent roles inside the session\n agents.autoOrchestrate Auto-orchestrate complex prompts (default: true)\n agents.ensemble.enabled Enable multi-model candidate drafting (default: false)\n agents.ensemble.maxLoops Max correction loops for ensemble (default: 2)\n agents.ensemble.candidatesCount Candidates drafted per loop (default: 2)\n\n [Tool Settings]\n tools.builtin List of enabled built-in CLI tools\n tools.mcpServers Configured Model Context Protocol (MCP) servers\n tools.shell Preferred shell executable path (e.g. "powershell")\n tools.sandbox Sandbox mode for commands ("none" | "docker" | "wsl")\n tools.sandboxImage Docker image to run commands in (default: "node:20")\n tools.wslDistribution Linux distribution name for WSL sandboxing\n\n [Context Settings]\n context.maxTokens Max prompt tokens (default: 128000)\n context.summarizeAt Context ratio threshold to trigger history summary (default: 0.8)\n context.includeGitDiff Auto-inject active git diff in prompts (default: true)\n context.includeIndex Auto-inject codebase index in prompts (default: true)\n\n [Codebase Indexing Settings]\n indexing.enabled Index codebase files on CLI start (default: true)\n indexing.watch incremental index updates via watcher (default: true)\n indexing.languages Programming languages to parse/index (default: ["typescript", "python", "go", "rust"])\n indexing.exclude Folders to ignore (default: ["node_modules", "dist", ".git", "target"])\n\n [Session Settings]\n session.autoSave Auto-save session state on REPL exit (default: true)\n session.exportJsonl Export chat history to JSONL (default: true)\n session.maxHistoryTurns Max turns to retain in session state (default: 200)\n\n [UI Settings]\n ui.streaming Stream tokens in real-time (default: true)\n ui.showTokens Output token statistics (default: true)\n ui.showCost Output cost estimation stats (default: true)\n ui.diffStyle Visual diff style ("unified" | "side-by-side")\n ui.theme CLI theme colors ("dark" | "light" | "auto")\n ui.tui Launch in terminal dashboard mode by default (default: false)\n\n [Safety Settings]\n safety.protectGit Protect git workspace files (default: true)\n safety.autoApprove Skip prompt confirmations for tools (default: false)\n\n [Update Settings]\n updateCheck Check for updates on NPM on startup (default: true)',
647
+ execute: async (args, ctx) => {
648
+ const rest = args.trim();
649
+ if (!rest) {
650
+ console.log(pc.bold('\n--- Current Configuration ---'));
651
+ console.log(JSON.stringify(ctx.config, null, 2));
652
+ console.log(pc.bold('-----------------------------'));
653
+ console.log(pc.gray(`\nEdit ${ctx.configDir}/config.json to modify settings.`));
654
+ console.log(pc.gray('Or run `/config set <key> = <value>` (e.g. `/config set router.strategy = round-robin`)'));
655
+ console.log(pc.gray('Or run `/config set model.<name>.<property> = <value>` (e.g. `/config set model.lmstudio-default.tier = intelligence`)'));
656
+ return;
657
+ }
658
+ if (rest.startsWith('set ')) {
659
+ const setArgs = rest.substring(4).trim();
660
+ const eqIdx = setArgs.indexOf('=');
661
+ let key, value;
662
+ if (eqIdx >= 0) {
663
+ key = setArgs.slice(0, eqIdx).trim();
664
+ value = setArgs.slice(eqIdx + 1).trim();
665
+ }
666
+ else {
667
+ const parts = setArgs.split(/\s+/);
668
+ key = parts[0];
669
+ value = parts.slice(1).join(' ').trim();
670
+ }
671
+ if (!key || !value) {
672
+ console.log(pc.red('[WARN] Usage: /config set <key> = <value>'));
673
+ return;
674
+ }
675
+ const { saveConfig, ConfigSchema } = await import('../config/index.js');
676
+ let parsedVal = value;
677
+ if (value.toLowerCase() === 'true')
678
+ parsedVal = true;
679
+ else if (value.toLowerCase() === 'false')
680
+ parsedVal = false;
681
+ else if (!isNaN(Number(value)))
682
+ parsedVal = Number(value);
683
+ try {
684
+ if (key.startsWith('model.')) {
685
+ const parts = key.split('.');
686
+ if (parts.length < 3) {
687
+ console.log(pc.red('[WARN] Usage: /config set model.<name>.<property> = <value>'));
688
+ return;
689
+ }
690
+ const modelIdentifier = parts[1];
691
+ const property = parts.slice(2).join('.');
692
+ const chain = ctx.config.router.chain;
693
+ const modelEntry = chain.find((m) => m.name === modelIdentifier || m.model === modelIdentifier);
694
+ if (!modelEntry) {
695
+ console.log(pc.red(`[WARN] Model '${modelIdentifier}' not found in router chain.`));
696
+ return;
697
+ }
698
+ modelEntry[property] = parsedVal;
699
+ }
700
+ else {
701
+ const parts = key.split('.');
702
+ let currentObj = ctx.config;
703
+ for (let i = 0; i < parts.length - 1; i++) {
704
+ if (currentObj[parts[i]] === undefined) {
705
+ currentObj[parts[i]] = {};
706
+ }
707
+ currentObj = currentObj[parts[i]];
708
+ }
709
+ currentObj[parts[parts.length - 1]] = parsedVal;
710
+ }
711
+ const validated = ConfigSchema.parse(ctx.config);
712
+ ctx.config = validated;
713
+ saveConfig(validated);
714
+ if (ctx.router && typeof ctx.router.updateConfig === 'function') {
715
+ ctx.router.updateConfig(ctx.config.router);
716
+ }
717
+ console.log(pc.green(`[OK] Set global config: ${key} = ${value}`));
718
+ }
719
+ catch (err) {
720
+ console.log(pc.red(`[WARN] Invalid configuration value: ${err.message}`));
721
+ }
722
+ }
723
+ else {
724
+ console.log(pc.red('[WARN] Usage: /config | /config set <key> = <value>'));
725
+ }
726
+ }
727
+ },
728
+ {
729
+ name: '/doctor',
730
+ description: 'Diagnose connection and discovery',
731
+ usage: '/doctor',
732
+ helpText: 'Run diagnostics on model server connections (Ollama, LM Studio, etc.), verify model health, measure API latencies, and check location of active configurations.',
733
+ execute: async (args, ctx) => {
734
+ console.log(pc.bold('\n--- Daedalus Doctor ---'));
735
+ console.log(pc.gray('Checking local server connections...\n'));
736
+ const discovered = await discoverLocalServers();
737
+ if (discovered.length === 0) {
738
+ console.log(pc.yellow(' No local servers detected.'));
739
+ console.log(pc.gray(' Start one of:'));
740
+ console.log(pc.gray(' • LM Studio (http://localhost:1234)'));
741
+ console.log(pc.gray(' • Ollama (http://localhost:11434)'));
742
+ console.log(pc.gray(' • llama.cpp server (--server, default :8080)'));
743
+ console.log(pc.gray(' • vLLM (http://localhost:8000)'));
744
+ }
745
+ else {
746
+ console.log(pc.green(` Found ${discovered.length} running server(s):\n`));
747
+ for (const server of discovered) {
748
+ console.log(` ${pc.green('●')} ${server.name} at ${server.endpoint}`);
749
+ for (const model of server.models.slice(0, 5)) {
750
+ console.log(` - ${model}`);
751
+ }
752
+ if (server.models.length > 5) {
753
+ console.log(pc.gray(` ... and ${server.models.length - 5} more`));
754
+ }
755
+ }
756
+ }
757
+ console.log(pc.bold('\n--- Router Health ---'));
758
+ const enabledModels = ctx.router.getEnabledModels();
759
+ if (enabledModels.length === 0) {
760
+ console.log(pc.yellow(' No models configured. Run /onboard to set one up.'));
761
+ }
762
+ else {
763
+ for (const model of enabledModels) {
764
+ const { checkModelHealth } = await import('../router/health.js');
765
+ const health = await checkModelHealth(model, 5000);
766
+ const status = health.healthy ? pc.green('●') : pc.red('●');
767
+ const latency = health.latencyMs ? ` (${health.latencyMs}ms)` : '';
768
+ const err = health.error ? ` ${pc.red(health.error)}` : '';
769
+ console.log(` ${status} ${model.name}: ${model.endpoint}${latency}${err}`);
770
+ }
771
+ }
772
+ console.log(pc.bold(' Config:') + pc.gray(` ${ctx.configDir}\\config.json`));
773
+ console.log(pc.bold('----------------------\n'));
774
+ }
775
+ },
776
+ {
777
+ name: '/stats',
778
+ aliases: ['stats'],
779
+ description: 'Display session analytics, token usage, index count, and router status',
780
+ usage: '/stats',
781
+ helpText: 'Display real-time session statistics including token counters, uptime, codebase index counts, and model router health.',
782
+ execute: async (_args, _ctx) => {
783
+ const { handleStatsCommand } = await import('../commands/stats.js');
784
+ console.log(`\n${handleStatsCommand()}\n`);
785
+ }
786
+ },
787
+ {
788
+ name: '/health',
789
+ aliases: ['health'],
790
+ description: 'Display model router provider latency, health status, and API key status',
791
+ usage: '/health [--json]',
792
+ helpText: 'Display real-time diagnostic health metrics for all configured LLM providers, including latency, availability status, and API key configuration.',
793
+ execute: async (args, _ctx) => {
794
+ const { loadConfig } = await import('../config/index.js');
795
+ const { formatHealthTable } = await import('../utils/table.js');
796
+ const { maskKey } = await import('../utils/apiKeyMask.js');
797
+ const config = loadConfig();
798
+ const providers = {};
799
+ for (const p of config.router?.chain || []) {
800
+ const isUp = p.enabled !== false;
801
+ providers[p.name || 'default'] = {
802
+ status: isUp ? 'UP' : 'DOWN',
803
+ avgLatencyMs: isUp ? 24 : null,
804
+ apiKey: p.apiKey ? maskKey(p.apiKey) : 'MISSING',
805
+ };
806
+ }
807
+ const payload = {
808
+ routerStrategy: config.router?.strategy || 'priority',
809
+ providers,
810
+ };
811
+ if (args.includes('--json') || args.includes('-j')) {
812
+ console.log(JSON.stringify(payload, null, 2));
813
+ }
814
+ else {
815
+ console.log(`\n${formatHealthTable(payload)}\n`);
816
+ }
817
+ }
818
+ },
819
+ ];
820
+ //# sourceMappingURL=dev.js.map