daedalus-cli 0.5.25 → 1.0.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.
Files changed (225) hide show
  1. package/AGENTS.md +12 -2
  2. package/CHANGELOG.md +44 -0
  3. package/CONTRIBUTING.md +3 -2
  4. package/README.md +90 -215
  5. package/dist/agents/ensemble.js +1 -1
  6. package/dist/agents/ensemble.js.map +1 -1
  7. package/dist/agents/orchestrator.js +11 -9
  8. package/dist/agents/orchestrator.js.map +1 -1
  9. package/dist/agents/orchestrator.test.d.ts +2 -0
  10. package/dist/agents/orchestrator.test.d.ts.map +1 -0
  11. package/dist/agents/orchestrator.test.js +48 -0
  12. package/dist/agents/orchestrator.test.js.map +1 -0
  13. package/dist/agents/roles.test.d.ts +2 -0
  14. package/dist/agents/roles.test.d.ts.map +1 -0
  15. package/dist/agents/roles.test.js +108 -0
  16. package/dist/agents/roles.test.js.map +1 -0
  17. package/dist/banner.d.ts +3 -0
  18. package/dist/banner.d.ts.map +1 -0
  19. package/dist/banner.js +66 -0
  20. package/dist/banner.js.map +1 -0
  21. package/dist/clipboard.d.ts +3 -0
  22. package/dist/clipboard.d.ts.map +1 -0
  23. package/dist/clipboard.js +63 -0
  24. package/dist/clipboard.js.map +1 -0
  25. package/dist/commands.d.ts +44 -0
  26. package/dist/commands.d.ts.map +1 -0
  27. package/dist/commands.js +1044 -0
  28. package/dist/commands.js.map +1 -0
  29. package/dist/config/index.d.ts +47 -42
  30. package/dist/config/index.d.ts.map +1 -1
  31. package/dist/config/index.js +1 -0
  32. package/dist/config/index.js.map +1 -1
  33. package/dist/config/index.test.d.ts +2 -0
  34. package/dist/config/index.test.d.ts.map +1 -0
  35. package/dist/config/index.test.js +220 -0
  36. package/dist/config/index.test.js.map +1 -0
  37. package/dist/extraction.js +2 -2
  38. package/dist/extraction.js.map +1 -1
  39. package/dist/extraction.test.d.ts +2 -0
  40. package/dist/extraction.test.d.ts.map +1 -0
  41. package/dist/extraction.test.js +105 -0
  42. package/dist/extraction.test.js.map +1 -0
  43. package/dist/formatting.d.ts +13 -0
  44. package/dist/formatting.d.ts.map +1 -0
  45. package/dist/formatting.js +195 -0
  46. package/dist/formatting.js.map +1 -0
  47. package/dist/highlight.test.d.ts +2 -0
  48. package/dist/highlight.test.d.ts.map +1 -0
  49. package/dist/highlight.test.js +56 -0
  50. package/dist/highlight.test.js.map +1 -0
  51. package/dist/index.d.ts +1 -3
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +46 -1953
  54. package/dist/index.js.map +1 -1
  55. package/dist/index.test.js +1 -1
  56. package/dist/index.test.js.map +1 -1
  57. package/dist/indexing/fts.test.d.ts +2 -0
  58. package/dist/indexing/fts.test.d.ts.map +1 -0
  59. package/dist/indexing/fts.test.js +104 -0
  60. package/dist/indexing/fts.test.js.map +1 -0
  61. package/dist/indexing/indexer.d.ts +21 -0
  62. package/dist/indexing/indexer.d.ts.map +1 -1
  63. package/dist/indexing/indexer.js +4 -4
  64. package/dist/indexing/indexer.js.map +1 -1
  65. package/dist/indexing/indexer.test.d.ts +2 -0
  66. package/dist/indexing/indexer.test.d.ts.map +1 -0
  67. package/dist/indexing/indexer.test.js +192 -0
  68. package/dist/indexing/indexer.test.js.map +1 -0
  69. package/dist/model.d.ts +20 -0
  70. package/dist/model.d.ts.map +1 -0
  71. package/dist/model.js +278 -0
  72. package/dist/model.js.map +1 -0
  73. package/dist/onboarding/wizard.js +1 -1
  74. package/dist/onboarding/wizard.js.map +1 -1
  75. package/dist/profile.d.ts.map +1 -1
  76. package/dist/profile.js +12 -7
  77. package/dist/profile.js.map +1 -1
  78. package/dist/profile.test.d.ts +2 -0
  79. package/dist/profile.test.d.ts.map +1 -0
  80. package/dist/profile.test.js +70 -0
  81. package/dist/profile.test.js.map +1 -0
  82. package/dist/project-hash.d.ts +2 -0
  83. package/dist/project-hash.d.ts.map +1 -0
  84. package/dist/project-hash.js +6 -0
  85. package/dist/project-hash.js.map +1 -0
  86. package/dist/repl.d.ts +25 -0
  87. package/dist/repl.d.ts.map +1 -0
  88. package/dist/repl.js +203 -0
  89. package/dist/repl.js.map +1 -0
  90. package/dist/router/health.test.d.ts +2 -0
  91. package/dist/router/health.test.d.ts.map +1 -0
  92. package/dist/router/health.test.js +87 -0
  93. package/dist/router/health.test.js.map +1 -0
  94. package/dist/router/index.d.ts +1 -1
  95. package/dist/router/index.d.ts.map +1 -1
  96. package/dist/router/index.js +17 -6
  97. package/dist/router/index.js.map +1 -1
  98. package/dist/router/index.test.d.ts +2 -0
  99. package/dist/router/index.test.d.ts.map +1 -0
  100. package/dist/router/index.test.js +136 -0
  101. package/dist/router/index.test.js.map +1 -0
  102. package/dist/router/rate-limiter.test.d.ts +2 -0
  103. package/dist/router/rate-limiter.test.d.ts.map +1 -0
  104. package/dist/router/rate-limiter.test.js +90 -0
  105. package/dist/router/rate-limiter.test.js.map +1 -0
  106. package/dist/session/jsonl.d.ts +0 -2
  107. package/dist/session/jsonl.d.ts.map +1 -1
  108. package/dist/session/jsonl.js +3 -7
  109. package/dist/session/jsonl.js.map +1 -1
  110. package/dist/session/jsonl.test.d.ts +2 -0
  111. package/dist/session/jsonl.test.d.ts.map +1 -0
  112. package/dist/session/jsonl.test.js +88 -0
  113. package/dist/session/jsonl.test.js.map +1 -0
  114. package/dist/session/manager.d.ts +2 -0
  115. package/dist/session/manager.d.ts.map +1 -1
  116. package/dist/session/manager.js +20 -5
  117. package/dist/session/manager.js.map +1 -1
  118. package/dist/session/manager.test.d.ts +2 -0
  119. package/dist/session/manager.test.d.ts.map +1 -0
  120. package/dist/session/manager.test.js +120 -0
  121. package/dist/session/manager.test.js.map +1 -0
  122. package/dist/session/memory.test.d.ts +2 -0
  123. package/dist/session/memory.test.d.ts.map +1 -0
  124. package/dist/session/memory.test.js +100 -0
  125. package/dist/session/memory.test.js.map +1 -0
  126. package/dist/session/sqlite.test.d.ts +2 -0
  127. package/dist/session/sqlite.test.d.ts.map +1 -0
  128. package/dist/session/sqlite.test.js +151 -0
  129. package/dist/session/sqlite.test.js.map +1 -0
  130. package/dist/tools/builtin/delegation.test.d.ts +2 -0
  131. package/dist/tools/builtin/delegation.test.d.ts.map +1 -0
  132. package/dist/tools/builtin/delegation.test.js +87 -0
  133. package/dist/tools/builtin/delegation.test.js.map +1 -0
  134. package/dist/tools/builtin/eval.test.d.ts +2 -0
  135. package/dist/tools/builtin/eval.test.d.ts.map +1 -0
  136. package/dist/tools/builtin/eval.test.js +24 -0
  137. package/dist/tools/builtin/eval.test.js.map +1 -0
  138. package/dist/tools/builtin/files.d.ts.map +1 -1
  139. package/dist/tools/builtin/files.js +1 -3
  140. package/dist/tools/builtin/files.js.map +1 -1
  141. package/dist/tools/builtin/git.test.d.ts +2 -0
  142. package/dist/tools/builtin/git.test.d.ts.map +1 -0
  143. package/dist/tools/builtin/git.test.js +49 -0
  144. package/dist/tools/builtin/git.test.js.map +1 -0
  145. package/dist/tools/builtin/impact.test.d.ts +2 -0
  146. package/dist/tools/builtin/impact.test.d.ts.map +1 -0
  147. package/dist/tools/builtin/impact.test.js +50 -0
  148. package/dist/tools/builtin/impact.test.js.map +1 -0
  149. package/dist/tools/builtin/indexing.d.ts +0 -1
  150. package/dist/tools/builtin/indexing.d.ts.map +1 -1
  151. package/dist/tools/builtin/indexing.js +1 -5
  152. package/dist/tools/builtin/indexing.js.map +1 -1
  153. package/dist/tools/builtin/indexing.test.d.ts +2 -0
  154. package/dist/tools/builtin/indexing.test.d.ts.map +1 -0
  155. package/dist/tools/builtin/indexing.test.js +31 -0
  156. package/dist/tools/builtin/indexing.test.js.map +1 -0
  157. package/dist/tools/builtin/lsp.test.d.ts +2 -0
  158. package/dist/tools/builtin/lsp.test.d.ts.map +1 -0
  159. package/dist/tools/builtin/lsp.test.js +34 -0
  160. package/dist/tools/builtin/lsp.test.js.map +1 -0
  161. package/dist/tools/builtin/process-watcher.d.ts.map +1 -1
  162. package/dist/tools/builtin/process-watcher.js +11 -9
  163. package/dist/tools/builtin/process-watcher.js.map +1 -1
  164. package/dist/tools/builtin/process-watcher.test.d.ts +2 -0
  165. package/dist/tools/builtin/process-watcher.test.d.ts.map +1 -0
  166. package/dist/tools/builtin/process-watcher.test.js +53 -0
  167. package/dist/tools/builtin/process-watcher.test.js.map +1 -0
  168. package/dist/tools/builtin/project-config.js +3 -3
  169. package/dist/tools/builtin/project-config.js.map +1 -1
  170. package/dist/tools/builtin/project-config.test.d.ts +2 -0
  171. package/dist/tools/builtin/project-config.test.d.ts.map +1 -0
  172. package/dist/tools/builtin/project-config.test.js +81 -0
  173. package/dist/tools/builtin/project-config.test.js.map +1 -0
  174. package/dist/tools/builtin/screenshot.d.ts.map +1 -1
  175. package/dist/tools/builtin/screenshot.js +18 -12
  176. package/dist/tools/builtin/screenshot.js.map +1 -1
  177. package/dist/tools/builtin/terminal.d.ts +9 -0
  178. package/dist/tools/builtin/terminal.d.ts.map +1 -1
  179. package/dist/tools/builtin/terminal.js +74 -16
  180. package/dist/tools/builtin/terminal.js.map +1 -1
  181. package/dist/tools/builtin/terminal.test.d.ts +2 -0
  182. package/dist/tools/builtin/terminal.test.d.ts.map +1 -0
  183. package/dist/tools/builtin/terminal.test.js +161 -0
  184. package/dist/tools/builtin/terminal.test.js.map +1 -0
  185. package/dist/tools/builtin/todo.d.ts.map +1 -1
  186. package/dist/tools/builtin/todo.js +7 -5
  187. package/dist/tools/builtin/todo.js.map +1 -1
  188. package/dist/tools/builtin/todo.test.d.ts +2 -0
  189. package/dist/tools/builtin/todo.test.d.ts.map +1 -0
  190. package/dist/tools/builtin/todo.test.js +66 -0
  191. package/dist/tools/builtin/todo.test.js.map +1 -0
  192. package/dist/tools/builtin/web.test.d.ts +2 -0
  193. package/dist/tools/builtin/web.test.d.ts.map +1 -0
  194. package/dist/tools/builtin/web.test.js +78 -0
  195. package/dist/tools/builtin/web.test.js.map +1 -0
  196. package/dist/tools/executor.test.d.ts +2 -0
  197. package/dist/tools/executor.test.d.ts.map +1 -0
  198. package/dist/tools/executor.test.js +79 -0
  199. package/dist/tools/executor.test.js.map +1 -0
  200. package/dist/tools/mcp/http.test.d.ts +2 -0
  201. package/dist/tools/mcp/http.test.d.ts.map +1 -0
  202. package/dist/tools/mcp/http.test.js +128 -0
  203. package/dist/tools/mcp/http.test.js.map +1 -0
  204. package/dist/tools/mcp/registry.d.ts.map +1 -1
  205. package/dist/tools/mcp/registry.js +1 -6
  206. package/dist/tools/mcp/registry.js.map +1 -1
  207. package/dist/tools/mcp/registry.test.d.ts +2 -0
  208. package/dist/tools/mcp/registry.test.d.ts.map +1 -0
  209. package/dist/tools/mcp/registry.test.js +18 -0
  210. package/dist/tools/mcp/registry.test.js.map +1 -0
  211. package/dist/tools/mcp/stdio.d.ts.map +1 -1
  212. package/dist/tools/mcp/stdio.js +4 -1
  213. package/dist/tools/mcp/stdio.js.map +1 -1
  214. package/dist/tools/mcp/stdio.test.d.ts +2 -0
  215. package/dist/tools/mcp/stdio.test.d.ts.map +1 -0
  216. package/dist/tools/mcp/stdio.test.js +121 -0
  217. package/dist/tools/mcp/stdio.test.js.map +1 -0
  218. package/dist/update-check.d.ts +2 -0
  219. package/dist/update-check.d.ts.map +1 -0
  220. package/dist/update-check.js +48 -0
  221. package/dist/update-check.js.map +1 -0
  222. package/package.json +11 -1
  223. package/dist/manual-test.d.ts +0 -2
  224. package/dist/manual-test.d.ts.map +0 -1
  225. package/dist/manual-test.js.map +0 -1
@@ -0,0 +1,1044 @@
1
+ // Command Registry and Router for Daedalus CLI
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import pc from 'picocolors';
5
+ import { executeToolCalls } from './tools/executor.js';
6
+ import { discoverLocalServers } from './config/index.js';
7
+ import { getSessionTodos } from './tools/builtin/todo.js';
8
+ import { saveProfile } from './profile.js';
9
+ import { extractAndSave } from './extraction.js';
10
+ import { printUserTurn, turnSeparator } from './formatting.js';
11
+ import { getClipboardText, getClipboardImage } from './clipboard.js';
12
+ export const commandsList = [
13
+ {
14
+ name: '/add',
15
+ description: 'Add file to context',
16
+ execute: async (args, ctx) => {
17
+ const fileArg = args.trim();
18
+ if (!fileArg) {
19
+ const { runInteractiveFileSelector } = await import('./session/selector.js');
20
+ ctx.rl.pause();
21
+ const result = await runInteractiveFileSelector(process.cwd(), ctx.config.indexing.exclude, new Set(ctx.activeFiles.keys()));
22
+ ctx.rl.resume();
23
+ if (result !== null) {
24
+ ctx.activeFiles.clear();
25
+ for (const absPath of result) {
26
+ const rel = path.relative(process.cwd(), absPath);
27
+ ctx.activeFiles.set(absPath, rel);
28
+ }
29
+ ctx.toolContext.activeFiles = new Map(ctx.activeFiles);
30
+ console.log(pc.green(`\n[OK] Active context files updated: ${ctx.activeFiles.size} file(s)`));
31
+ }
32
+ }
33
+ else {
34
+ const absPath = path.resolve(fileArg);
35
+ ctx.activeFiles.set(absPath, fileArg);
36
+ ctx.toolContext.activeFiles = new Map(ctx.activeFiles);
37
+ console.log(pc.green(`[OK] Added file to context: ${pc.bold(fileArg)}`));
38
+ }
39
+ }
40
+ },
41
+ {
42
+ name: '/remove',
43
+ description: 'Remove file from context',
44
+ execute: async (args, ctx) => {
45
+ const fileArg = args.trim();
46
+ if (!fileArg) {
47
+ console.log(pc.red('[WARN] Please specify a file path. Example: /remove src/App.tsx'));
48
+ }
49
+ else {
50
+ const absPath = path.resolve(fileArg);
51
+ if (ctx.activeFiles.delete(absPath)) {
52
+ ctx.toolContext.activeFiles = new Map(ctx.activeFiles);
53
+ console.log(pc.green(`[OK] Removed file from context: ${pc.bold(fileArg)}`));
54
+ }
55
+ else {
56
+ console.log(pc.yellow(`[WARN] File was not in context: ${fileArg}`));
57
+ }
58
+ }
59
+ }
60
+ },
61
+ {
62
+ name: '/context',
63
+ description: 'Show active file context',
64
+ execute: async (args, ctx) => {
65
+ console.log(pc.bold('\n--- Monitored Files in Context ---'));
66
+ if (ctx.activeFiles.size === 0) {
67
+ console.log(pc.gray(' (No active files. Use "/add <filepath>" to add files)'));
68
+ }
69
+ else {
70
+ ctx.activeFiles.forEach((filename) => {
71
+ console.log(` • ${pc.cyan(filename)}`);
72
+ });
73
+ }
74
+ console.log(pc.bold('----------------------------------'));
75
+ }
76
+ },
77
+ {
78
+ name: '/paste',
79
+ description: 'Paste clipboard text/image as message',
80
+ execute: async (args, ctx) => {
81
+ const extra = args.trim();
82
+ if (extra && !extra.startsWith('http')) {
83
+ const filePath = path.resolve(extra);
84
+ if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
85
+ const ext = path.extname(filePath).toLowerCase();
86
+ if (['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp'].includes(ext)) {
87
+ const imgBuffer = fs.readFileSync(filePath);
88
+ const base64 = imgBuffer.toString('base64');
89
+ const message = 'What do you see in this image?';
90
+ printUserTurn(`${path.basename(filePath)} (image)`);
91
+ try {
92
+ const filesContext = ctx.buildFileContext();
93
+ const indexCtx = await ctx.buildIndexContext(message);
94
+ const userContent = `${indexCtx}${filesContext}User Prompt: ${message}`;
95
+ await ctx.callModelWithTools(userContent, base64);
96
+ ctx.sessionManager.saveSessionState(ctx.messages, ctx.activeFiles, getSessionTodos(ctx.toolContext.sessionId));
97
+ }
98
+ catch {
99
+ try {
100
+ const filesContext = ctx.buildFileContext();
101
+ const userContent = `${filesContext}User Prompt: ${message}`;
102
+ console.log(pc.yellow('\n [RETRY] Trying fallback mode...'));
103
+ await ctx.callModelWithFallback(userContent, base64);
104
+ ctx.sessionManager.saveSessionState(ctx.messages, ctx.activeFiles, getSessionTodos(ctx.toolContext.sessionId));
105
+ }
106
+ catch (fallbackErr) {
107
+ const firstLine = (fallbackErr.message || '').split('\n')[0];
108
+ console.log(pc.red(`\n ${pc.bold('[ERROR]')} Fallback also failed: ${firstLine}`));
109
+ }
110
+ }
111
+ turnSeparator();
112
+ return;
113
+ }
114
+ }
115
+ }
116
+ const imgPath = getClipboardImage(ctx.cliTempDir);
117
+ if (imgPath) {
118
+ const imgBuffer = fs.readFileSync(imgPath);
119
+ fs.unlinkSync(imgPath);
120
+ const base64 = imgBuffer.toString('base64');
121
+ const message = extra || 'What do you see in this image?';
122
+ printUserTurn(`${message} (image)`);
123
+ try {
124
+ const filesContext = ctx.buildFileContext();
125
+ const indexCtx = await ctx.buildIndexContext(message);
126
+ const userContent = `${indexCtx}${filesContext}User Prompt: ${message}`;
127
+ await ctx.callModelWithTools(userContent, base64);
128
+ ctx.sessionManager.saveSessionState(ctx.messages, ctx.activeFiles, getSessionTodos(ctx.toolContext.sessionId));
129
+ }
130
+ catch {
131
+ try {
132
+ const filesContext = ctx.buildFileContext();
133
+ const userContent = `${filesContext}User Prompt: ${message}`;
134
+ console.log(pc.yellow('\n [RETRY] Trying fallback mode...'));
135
+ await ctx.callModelWithFallback(userContent, base64);
136
+ ctx.sessionManager.saveSessionState(ctx.messages, ctx.activeFiles, getSessionTodos(ctx.toolContext.sessionId));
137
+ }
138
+ catch (fallbackErr) {
139
+ const firstLine = (fallbackErr.message || '').split('\n')[0];
140
+ console.log(pc.red(`\n ${pc.bold('[ERROR]')} Fallback also failed: ${firstLine}`));
141
+ }
142
+ }
143
+ turnSeparator();
144
+ return;
145
+ }
146
+ const clipboard = getClipboardText();
147
+ if (!clipboard) {
148
+ console.log(pc.red('[WARN] Clipboard is empty or inaccessible.'));
149
+ return;
150
+ }
151
+ const fullMessage = extra ? `${clipboard}\n\n${extra}` : clipboard;
152
+ printUserTurn(fullMessage);
153
+ try {
154
+ const filesContext = ctx.buildFileContext();
155
+ const indexCtx = await ctx.buildIndexContext(fullMessage);
156
+ const userContent = `${indexCtx}${filesContext}User Prompt: ${fullMessage}`;
157
+ await ctx.callModelWithTools(userContent);
158
+ ctx.sessionManager.saveSessionState(ctx.messages, ctx.activeFiles, getSessionTodos(ctx.toolContext.sessionId));
159
+ }
160
+ catch {
161
+ }
162
+ turnSeparator();
163
+ }
164
+ },
165
+ {
166
+ name: '/clear',
167
+ description: 'Clear conversation history',
168
+ execute: async (args, ctx) => {
169
+ ctx.messages.length = 0;
170
+ ctx.messages.push({ role: 'system', content: ctx.getSystemPromptWithMemory() });
171
+ console.log(pc.green('[OK] Conversation history cleared!'));
172
+ }
173
+ },
174
+ {
175
+ name: '/spawn',
176
+ aliases: ['/delegate'],
177
+ description: 'Spawn sub-agent: /spawn <role> <task>',
178
+ execute: async (args, ctx) => {
179
+ let role = '';
180
+ let task = '';
181
+ if (args.includes(' to ')) {
182
+ const match = args.match(/^(.+)\s+to\s+(\w+)$/i);
183
+ if (match) {
184
+ task = match[1].trim();
185
+ role = match[2].toLowerCase();
186
+ }
187
+ }
188
+ else {
189
+ const parts = args.trim().split(/\s+/);
190
+ if (parts.length >= 2) {
191
+ role = parts[0].toLowerCase();
192
+ task = args.substring(parts[0].length).trim();
193
+ }
194
+ }
195
+ const validRoles = ['coder', 'reviewer', 'debugger', 'researcher', 'planner'];
196
+ if (!role || !task) {
197
+ console.log(pc.red('[WARN] Usage: /spawn <role> <task> OR /delegate <task> to <role>'));
198
+ console.log(pc.gray(` Roles: ${validRoles.join(', ')}`));
199
+ return;
200
+ }
201
+ if (!validRoles.includes(role)) {
202
+ console.log(pc.red(`[WARN] Unknown role: ${role}. Valid: ${validRoles.join(', ')}`));
203
+ return;
204
+ }
205
+ console.log(pc.cyan(`\n[SPAWN] Spawning ${role} agent for: ${task.slice(0, 80)}...`));
206
+ const context = `Active files: ${Array.from(ctx.activeFiles.values()).join(', ') || 'none'}`;
207
+ const fakeToolCall = {
208
+ id: `call_${Date.now()}`,
209
+ type: 'function',
210
+ function: {
211
+ name: 'delegate_task',
212
+ arguments: JSON.stringify({ goal: task, context, role }),
213
+ },
214
+ };
215
+ const results = await executeToolCalls([fakeToolCall], ctx.toolContext);
216
+ for (const result of results) {
217
+ const status = result.success ? pc.green('✔') : pc.red('✗');
218
+ console.log(`\n${status} ${role} agent completed`);
219
+ console.log(pc.white(result.content));
220
+ if (!result.success && result.error) {
221
+ console.log(pc.red(`Error: ${result.error}`));
222
+ }
223
+ }
224
+ }
225
+ },
226
+ {
227
+ name: '/orchestrate',
228
+ description: 'Orchestrate agents for a goal',
229
+ execute: async (args, ctx) => {
230
+ const goal = args.trim();
231
+ if (!goal) {
232
+ console.log(pc.red('[WARN] Usage: /orchestrate <goal>'));
233
+ return;
234
+ }
235
+ console.log(pc.cyan(`\n[ORCHESTRATE] Starting orchestration for: ${goal}`));
236
+ const { Orchestrator } = await import('./agents/orchestrator.js');
237
+ const orchestrator = new Orchestrator(ctx.router, ctx.messages, ctx.toolContext);
238
+ const result = await orchestrator.run(goal);
239
+ console.log(pc.white(`\n${result}`));
240
+ }
241
+ },
242
+ {
243
+ name: '/memory',
244
+ description: 'View project memory (facts & conventions)',
245
+ execute: async (args, ctx) => {
246
+ const mem = ctx.sessionManager.loadMemory();
247
+ console.log(pc.bold('\n--- Project Facts & Conventions (Memory) ---'));
248
+ console.log(pc.bold('Conventions:'));
249
+ if (Object.keys(mem.conventions).length === 0) {
250
+ console.log(pc.gray(' No conventions saved.'));
251
+ }
252
+ else {
253
+ for (const [k, v] of Object.entries(mem.conventions)) {
254
+ console.log(` • ${pc.cyan(k)}: ${v}`);
255
+ }
256
+ }
257
+ console.log(pc.bold('\nFacts:'));
258
+ if (mem.facts.length === 0) {
259
+ console.log(pc.gray(' No facts saved.'));
260
+ }
261
+ else {
262
+ mem.facts.forEach(f => {
263
+ console.log(` • ${pc.cyan(f.key)}: ${f.value} (source: ${f.source})`);
264
+ });
265
+ }
266
+ console.log(pc.bold('------------------------------------------'));
267
+ }
268
+ },
269
+ {
270
+ name: '/fact',
271
+ description: 'Add a project fact to memory',
272
+ execute: async (args, ctx) => {
273
+ const eqIdx = args.indexOf('=');
274
+ if (eqIdx < 0) {
275
+ console.log(pc.red('[WARN] Usage: /fact <key> = <value>'));
276
+ }
277
+ else {
278
+ const key = args.slice(0, eqIdx).trim();
279
+ const value = args.slice(eqIdx + 1).trim();
280
+ ctx.sessionManager.addFact(key, value, 'user');
281
+ console.log(pc.green(`[OK] Saved fact: ${key} = ${value}`));
282
+ }
283
+ }
284
+ },
285
+ {
286
+ name: '/convention',
287
+ description: 'Add a project convention to memory',
288
+ execute: async (args, ctx) => {
289
+ const eqIdx = args.indexOf('=');
290
+ if (eqIdx < 0) {
291
+ console.log(pc.red('[WARN] Usage: /convention <key> = <value>'));
292
+ }
293
+ else {
294
+ const key = args.slice(0, eqIdx).trim();
295
+ const value = args.slice(eqIdx + 1).trim();
296
+ ctx.sessionManager.setConvention(key, value);
297
+ console.log(pc.green(`[OK] Saved convention: ${key} = ${value}`));
298
+ }
299
+ }
300
+ },
301
+ {
302
+ name: '/extract',
303
+ description: 'Manually extract facts from session',
304
+ execute: async (args, ctx) => {
305
+ console.log(pc.dim(' [EXTRACT] Extracting facts from conversation...'));
306
+ await extractAndSave(ctx.router, ctx.sessionManager, ctx.messages);
307
+ }
308
+ },
309
+ {
310
+ name: '/profile',
311
+ description: 'View or set user profile info',
312
+ execute: async (args, ctx) => {
313
+ const rest = args.trim();
314
+ if (!rest || rest === 'view') {
315
+ console.log(pc.bold('\n--- Your Profile ---'));
316
+ console.log(` ${pc.cyan('Name')}: ${ctx.userProfile.name || '(not set)'}`);
317
+ console.log(` ${pc.cyan('Bio')}: ${ctx.userProfile.bio || '(not set)'}`);
318
+ if (ctx.userProfile.updatedAt) {
319
+ console.log(pc.gray(` Last updated: ${new Date(ctx.userProfile.updatedAt).toLocaleString()}`));
320
+ }
321
+ console.log(pc.dim(' Set name: /profile name = Your Name'));
322
+ console.log(pc.dim(' Set bio: /profile bio = Tell me about yourself'));
323
+ return;
324
+ }
325
+ const eqIdx = rest.indexOf('=');
326
+ if (eqIdx < 0) {
327
+ if (rest.startsWith('name ')) {
328
+ ctx.userProfile.name = rest.substring(5).trim();
329
+ saveProfile(ctx.userProfile);
330
+ console.log(pc.green(`[OK] Profile name set: ${ctx.userProfile.name}`));
331
+ return;
332
+ }
333
+ if (rest.startsWith('bio ')) {
334
+ ctx.userProfile.bio = rest.substring(4).trim();
335
+ saveProfile(ctx.userProfile);
336
+ console.log(pc.green('[OK] Profile bio set.'));
337
+ return;
338
+ }
339
+ }
340
+ else {
341
+ const key = rest.slice(0, eqIdx).trim().toLowerCase();
342
+ const val = rest.slice(eqIdx + 1).trim();
343
+ if (key === 'name') {
344
+ ctx.userProfile.name = val;
345
+ saveProfile(ctx.userProfile);
346
+ console.log(pc.green(`[OK] Profile name set: ${ctx.userProfile.name}`));
347
+ return;
348
+ }
349
+ else if (key === 'bio') {
350
+ ctx.userProfile.bio = val;
351
+ saveProfile(ctx.userProfile);
352
+ console.log(pc.green('[OK] Profile bio set.'));
353
+ return;
354
+ }
355
+ }
356
+ console.log(pc.red('[WARN] Usage: /profile view | /profile name = <name> | /profile bio = <bio>'));
357
+ }
358
+ },
359
+ {
360
+ name: '/style',
361
+ description: 'Set your coding style preferences',
362
+ execute: async (args, ctx) => {
363
+ const rest = args.trim();
364
+ if (!rest || rest === 'view') {
365
+ console.log(pc.bold('\n--- Coding Style ---'));
366
+ console.log(` ${ctx.userProfile.style || '(not set)'}`);
367
+ console.log(pc.dim(' Set: /style <your coding preferences>'));
368
+ console.log(pc.dim(' Example: /style I prefer tabs, functional style, descriptive variable names'));
369
+ return;
370
+ }
371
+ ctx.userProfile.style = rest;
372
+ saveProfile(ctx.userProfile);
373
+ console.log(pc.green('[OK] Coding style saved. It will be injected into every session.'));
374
+ }
375
+ },
376
+ {
377
+ name: '/undo',
378
+ description: 'Undo last file patch',
379
+ execute: async (args, ctx) => {
380
+ const history = ctx.toolContext.patchHistory;
381
+ if (!history || history.length === 0) {
382
+ console.log(pc.yellow('[WARN] No patches to undo.'));
383
+ }
384
+ else {
385
+ const last = history[history.length - 1];
386
+ try {
387
+ const currentContent = fs.readFileSync(last.filePath, 'utf8');
388
+ if (currentContent === last.newContent) {
389
+ fs.writeFileSync(last.filePath, last.oldContent, 'utf8');
390
+ console.log(pc.green(`[OK] Undid patch to ${last.filePath} (${last.description})`));
391
+ }
392
+ else {
393
+ console.log(pc.yellow(`[WARN] File ${last.filePath} has been modified since last patch. Cannot auto-undo.`));
394
+ }
395
+ history.pop();
396
+ }
397
+ catch (err) {
398
+ console.log(pc.red(`[WARN] Failed to undo: ${err.message}`));
399
+ }
400
+ }
401
+ }
402
+ },
403
+ {
404
+ name: '/branch',
405
+ description: 'Git branch operations',
406
+ execute: async (args, ctx) => {
407
+ try {
408
+ const { execute: termExec } = await import('./tools/builtin/terminal.js');
409
+ const arg = args.trim();
410
+ if (!arg) {
411
+ const currentBranchResult = await termExec({ command: 'git branch --show-current', timeout: 5, workdir: process.cwd() }, ctx.toolContext);
412
+ const current = currentBranchResult.content?.trim();
413
+ if (current) {
414
+ console.log(`\n ${pc.cyan('Current Git branch:')} ${pc.bold(current)}`);
415
+ }
416
+ else {
417
+ console.log(pc.red('\n Not in a Git repository or no branch found.'));
418
+ }
419
+ }
420
+ else {
421
+ console.log(`\n Creating and switching to branch ${pc.cyan(arg)}...`);
422
+ const checkoutResult = await termExec({ command: `git checkout -b ${arg}`, timeout: 10, workdir: process.cwd() }, ctx.toolContext);
423
+ if (checkoutResult.success) {
424
+ console.log(pc.green(` [OK] Switched to a new branch '${arg}'`));
425
+ }
426
+ else {
427
+ console.log(pc.yellow(` Branch might already exist, attempting to switch...`));
428
+ const switchResult = await termExec({ command: `git checkout ${arg}`, timeout: 10, workdir: process.cwd() }, ctx.toolContext);
429
+ if (switchResult.success) {
430
+ console.log(pc.green(` [OK] Switched to branch '${arg}'`));
431
+ }
432
+ else {
433
+ console.log(pc.red(` Switch failed: ${switchResult.error || switchResult.content}`));
434
+ }
435
+ }
436
+ }
437
+ }
438
+ catch (err) {
439
+ console.log(pc.red(`[WARN] Branch command error: ${err.message}`));
440
+ }
441
+ }
442
+ },
443
+ {
444
+ name: '/pr',
445
+ description: 'Generate PR description Compared to base branch',
446
+ execute: async (args, ctx) => {
447
+ const arg = args.trim();
448
+ try {
449
+ const { execute: termExec } = await import('./tools/builtin/terminal.js');
450
+ const gitCheck = await termExec({ command: 'git rev-parse --is-inside-work-tree', timeout: 5, workdir: process.cwd() }, ctx.toolContext);
451
+ if (!gitCheck.success) {
452
+ console.log(pc.red(' Error: Not inside a Git repository.'));
453
+ return;
454
+ }
455
+ let baseBranch = arg || 'main';
456
+ if (!arg) {
457
+ const mainCheck = await termExec({ command: 'git show-ref --verify refs/heads/main', timeout: 5, workdir: process.cwd() }, ctx.toolContext);
458
+ if (!mainCheck.success) {
459
+ const masterCheck = await termExec({ command: 'git show-ref --verify refs/heads/master', timeout: 5, workdir: process.cwd() }, ctx.toolContext);
460
+ if (masterCheck.success) {
461
+ baseBranch = 'master';
462
+ }
463
+ }
464
+ }
465
+ const currentBranchResult = await termExec({ command: 'git branch --show-current', timeout: 5, workdir: process.cwd() }, ctx.toolContext);
466
+ const currentBranch = currentBranchResult.content?.trim();
467
+ console.log(`\n Comparing ${pc.cyan(currentBranch || 'HEAD')} with base branch ${pc.cyan(baseBranch)}...`);
468
+ const commitsResult = await termExec({ command: `git log ${baseBranch}..HEAD --oneline`, timeout: 10, workdir: process.cwd() }, ctx.toolContext);
469
+ const commitList = commitsResult.content?.trim() || '';
470
+ const diffResult = await termExec({ command: `git diff ${baseBranch}...HEAD`, timeout: 15, workdir: process.cwd() }, ctx.toolContext);
471
+ const diffContent = diffResult.content?.slice(0, 15000) || '';
472
+ if (!commitList && !diffContent) {
473
+ console.log(pc.yellow(` No commits or diff found between ${currentBranch} and ${baseBranch}.`));
474
+ return;
475
+ }
476
+ console.log(pc.dim(' Analyzing changes and generating PR description...'));
477
+ const aiResponse = await ctx.router.chat.completions.create({
478
+ model: 'auto',
479
+ messages: [
480
+ {
481
+ role: 'system',
482
+ content: 'You write clean, comprehensive, professional Pull Request descriptions in Markdown format. Output ONLY the markdown content — no extra chat, wrapper, or quotes.'
483
+ },
484
+ {
485
+ role: 'user',
486
+ content: `Generate a Pull Request description for the current branch compared to ${baseBranch}.\n\nCommits:\n${commitList}\n\nDiff:\n${diffContent}`
487
+ }
488
+ ],
489
+ temperature: 0.3,
490
+ });
491
+ const prDesc = (aiResponse.choices[0]?.message?.content || '').trim();
492
+ if (!prDesc) {
493
+ console.log(pc.red(' Failed to generate PR description.'));
494
+ return;
495
+ }
496
+ console.log(pc.bold('\n--- Generated PR Description ---'));
497
+ console.log(prDesc);
498
+ console.log(pc.bold('--------------------------------'));
499
+ const outPath = path.join(process.cwd(), 'pr-desc.md');
500
+ fs.writeFileSync(outPath, prDesc, 'utf8');
501
+ console.log(pc.green(`\n[OK] PR description saved to ${pc.cyan('pr-desc.md')}`));
502
+ }
503
+ catch (err) {
504
+ console.log(pc.red(`[WARN] PR command error: ${err.message}`));
505
+ }
506
+ }
507
+ },
508
+ {
509
+ name: '/debug',
510
+ description: 'Run command and autonomously debug failures',
511
+ execute: async (args, ctx) => {
512
+ const debugCmd = args.trim();
513
+ if (!debugCmd) {
514
+ console.log(pc.red(' Error: Please specify a command to run. Example: /debug npm test'));
515
+ return;
516
+ }
517
+ console.log(`\n ${pc.cyan('Starting autonomous debugging loop for:')} ${pc.bold(debugCmd)}`);
518
+ const MAX_RETRIES = 5;
519
+ let attempt = 1;
520
+ let success = false;
521
+ while (attempt <= MAX_RETRIES) {
522
+ console.log(`\n ${pc.yellow(`[Attempt ${attempt}/${MAX_RETRIES}]`)} Running: ${pc.bold(debugCmd)}...`);
523
+ try {
524
+ const { execute: termExec } = await import('./tools/builtin/terminal.js');
525
+ const execResult = await termExec({ command: debugCmd, timeout: 60, workdir: process.cwd() }, ctx.toolContext);
526
+ if (execResult.success) {
527
+ console.log(pc.green(`\n ${pc.green('✔')} ${pc.bold(`Success on attempt ${attempt}!`)} Command passed with exit code 0.`));
528
+ success = true;
529
+ break;
530
+ }
531
+ console.log(pc.red(`\n ${pc.red('✗')} ${pc.bold(`Command failed on attempt ${attempt}.`)}`));
532
+ const stdout = execResult.content || '';
533
+ const errorMsg = execResult.error || '';
534
+ const logs = `${stdout}\n${errorMsg}`.trim();
535
+ console.log(pc.bold('\n--- Failure Logs ---'));
536
+ const logLines = logs.split('\n');
537
+ const preview = logLines.length > 20 ? logLines.slice(-20).join('\n') : logs;
538
+ console.log(preview);
539
+ if (logLines.length > 20) {
540
+ console.log(pc.dim(`\n (... truncated ${logLines.length - 20} lines of logs ...)`));
541
+ }
542
+ console.log(pc.bold('--------------------'));
543
+ if (attempt === MAX_RETRIES) {
544
+ console.log(pc.red(`\n Reached maximum attempt limit of ${MAX_RETRIES}. Debugging loop failed.`));
545
+ break;
546
+ }
547
+ console.log(pc.dim('\n Calling Daedalus to analyze failure and apply a fix...'));
548
+ const debugPrompt = `The command "${debugCmd}" failed on attempt ${attempt}.
549
+ Here are the execution logs (showing the failure details):
550
+
551
+ ${logs.slice(-6000)}
552
+
553
+ Please analyze the error, identify which files need correction, and apply surgical edits using 'patch' or write tools to fix the issue.
554
+ Once you have finished making changes, I will automatically re-run the command to verify if it passes.`;
555
+ await ctx.callModelWithTools(debugPrompt);
556
+ }
557
+ catch (err) {
558
+ console.log(pc.red(`\n Error in debugging loop: ${err.message}`));
559
+ break;
560
+ }
561
+ attempt++;
562
+ }
563
+ if (!success) {
564
+ console.log(pc.red(`\n Autonomous debugging did not succeed after ${MAX_RETRIES} attempts.`));
565
+ }
566
+ turnSeparator();
567
+ }
568
+ },
569
+ {
570
+ name: '/ensemble',
571
+ description: 'Ensemble model drafting pipeline',
572
+ execute: async (args, ctx) => {
573
+ const ensembleGoal = args.trim();
574
+ if (!ensembleGoal) {
575
+ console.log(pc.red(' Error: Please specify a goal for the ensemble draft. Example: /ensemble Implement feature X'));
576
+ return;
577
+ }
578
+ try {
579
+ const { runEnsembleWorkflow } = await import('./agents/ensemble.js');
580
+ await runEnsembleWorkflow(ensembleGoal, ctx.toolContext, ctx.config, ctx.router);
581
+ }
582
+ catch (err) {
583
+ console.log(pc.red(`\n Error in ensemble drafting: ${err.message}`));
584
+ }
585
+ turnSeparator();
586
+ }
587
+ },
588
+ {
589
+ name: '/commit',
590
+ description: 'Stage and commit changes',
591
+ execute: async (args, ctx) => {
592
+ const forcedMsg = args.trim();
593
+ try {
594
+ const { execute: termExec } = await import('./tools/builtin/terminal.js');
595
+ const statusResult = await termExec({ command: 'git status --short', timeout: 10, workdir: process.cwd() }, ctx.toolContext);
596
+ console.log(pc.bold('\n--- Git Status ---'));
597
+ console.log(statusResult.content || pc.gray('(clean)'));
598
+ if (!statusResult.content?.trim()) {
599
+ console.log(pc.yellow('Nothing to commit.'));
600
+ return;
601
+ }
602
+ const addResult = await termExec({ command: 'git add -A', timeout: 10, workdir: process.cwd() }, ctx.toolContext);
603
+ if (!addResult.success) {
604
+ console.log(pc.red(`Stage failed: ${addResult.error}`));
605
+ return;
606
+ }
607
+ let commitMsg = forcedMsg;
608
+ if (!commitMsg) {
609
+ const diffResult = await termExec({ command: 'git diff --cached --stat', timeout: 10, workdir: process.cwd() }, ctx.toolContext);
610
+ const diffFull = await termExec({ command: 'git diff --cached', timeout: 10, workdir: process.cwd() }, ctx.toolContext);
611
+ const diffContent = diffFull.content?.slice(0, 6000) || '';
612
+ if (diffResult.content)
613
+ console.log(pc.gray(diffResult.content));
614
+ if (diffContent) {
615
+ console.log(pc.dim(' Generating commit message...'));
616
+ try {
617
+ const aiResponse = await ctx.router.chat.completions.create({
618
+ model: 'auto',
619
+ messages: [
620
+ { 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.' },
621
+ { role: 'user', content: `Write a commit message for this diff:\n\n${diffContent}` }
622
+ ],
623
+ temperature: 0.2,
624
+ max_tokens: 80,
625
+ });
626
+ const suggested = ((aiResponse.choices[0]?.message?.content) || '').trim().split('\n')[0].trim();
627
+ if (suggested) {
628
+ console.log(`\n ${pc.dim('Suggested:')} ${pc.cyan(suggested)}`);
629
+ const choice = await ctx.askLine(pc.dim(' [Enter] accept [e] edit [n] cancel: '));
630
+ if (choice.trim().toLowerCase() === 'n') {
631
+ console.log(pc.yellow('Commit cancelled.'));
632
+ await termExec({ command: 'git restore --staged .', timeout: 10, workdir: process.cwd() }, ctx.toolContext);
633
+ return;
634
+ }
635
+ else if (choice.trim().toLowerCase() === 'e') {
636
+ commitMsg = await ctx.askLine(pc.cyan(' Commit message: '));
637
+ }
638
+ else {
639
+ commitMsg = suggested;
640
+ }
641
+ }
642
+ }
643
+ catch {
644
+ // Model unavailable — manual fallback
645
+ }
646
+ }
647
+ if (!commitMsg) {
648
+ commitMsg = await ctx.askLine(pc.cyan(' Commit message: '));
649
+ }
650
+ if (!commitMsg.trim()) {
651
+ console.log(pc.yellow('Commit cancelled — empty message.'));
652
+ await termExec({ command: 'git restore --staged .', timeout: 10, workdir: process.cwd() }, ctx.toolContext);
653
+ return;
654
+ }
655
+ }
656
+ const commitResult = await termExec({ command: `git commit -m ${JSON.stringify(commitMsg)}`, timeout: 10, workdir: process.cwd() }, ctx.toolContext);
657
+ if (commitResult.success) {
658
+ console.log(pc.green(`\n[OK] Commit: ${commitMsg.slice(0, 60)}`));
659
+ }
660
+ else {
661
+ console.log(pc.red(`Commit failed: ${commitResult.error}`));
662
+ }
663
+ }
664
+ catch (err) {
665
+ console.log(pc.red(`[WARN] Commit error: ${err.message}`));
666
+ }
667
+ }
668
+ },
669
+ {
670
+ name: '/project',
671
+ description: 'View or set project config settings',
672
+ execute: async (args, ctx) => {
673
+ const rest = args.trim();
674
+ const { loadProjectConfig, saveProjectConfig } = await import('./tools/builtin/project-config.js');
675
+ if (!rest) {
676
+ const cfg = loadProjectConfig(process.cwd());
677
+ console.log(pc.bold('\n--- Project Config (.daedalus) ---'));
678
+ console.log(JSON.stringify(cfg, null, 2));
679
+ console.log(pc.bold('----------------------------------'));
680
+ console.log(pc.gray('Use /project set <key> = <value> to update'));
681
+ return;
682
+ }
683
+ if (rest.startsWith('set ')) {
684
+ const setArgs = rest.substring(4).trim();
685
+ const eqIdx = setArgs.indexOf('=');
686
+ let key, value;
687
+ if (eqIdx >= 0) {
688
+ key = setArgs.slice(0, eqIdx).trim();
689
+ value = setArgs.slice(eqIdx + 1).trim();
690
+ }
691
+ else {
692
+ const parts = setArgs.split(/\s+/);
693
+ key = parts[0];
694
+ value = parts.slice(1).join(' ');
695
+ }
696
+ if (!key || !value) {
697
+ console.log(pc.red('[WARN] Usage: /project set <key> = <value>'));
698
+ }
699
+ else {
700
+ const cfg = loadProjectConfig(process.cwd());
701
+ let parsedVal = value;
702
+ if (value.toLowerCase() === 'true')
703
+ parsedVal = true;
704
+ else if (value.toLowerCase() === 'false')
705
+ parsedVal = false;
706
+ else if (!isNaN(Number(value)))
707
+ parsedVal = Number(value);
708
+ cfg[key] = parsedVal;
709
+ saveProjectConfig(cfg);
710
+ console.log(pc.green(`[OK] Set ${key} = ${value}`));
711
+ }
712
+ }
713
+ else {
714
+ console.log(pc.red('[WARN] Usage: /project | /project set <key> = <value>'));
715
+ }
716
+ }
717
+ },
718
+ {
719
+ name: '/test',
720
+ description: 'Run test loop and fix failures',
721
+ execute: async (args, ctx) => {
722
+ const maxLoops = args.trim() ? parseInt(args.trim(), 10) || 3 : 3;
723
+ const { loadProjectConfig } = await import('./tools/builtin/project-config.js');
724
+ const { execute: termExec } = await import('./tools/builtin/terminal.js');
725
+ const cfg = loadProjectConfig(process.cwd());
726
+ const testCmd = cfg.testCommand || 'npm test';
727
+ console.log(pc.bold(`\nTest-Run-Fix Loop (max ${maxLoops} iterations)`));
728
+ console.log(pc.gray(`Test command: ${testCmd}\n`));
729
+ for (let i = 0; i < maxLoops; i++) {
730
+ console.log(pc.cyan(`\n--- Run ${i + 1}/${maxLoops} ---`));
731
+ const result = await termExec({ command: testCmd, timeout: 120, workdir: process.cwd() }, ctx.toolContext);
732
+ console.log(result.content?.slice(0, 2000) || pc.gray('(no output)'));
733
+ if (result.success) {
734
+ console.log(pc.green('\n[OK] All tests passed!'));
735
+ break;
736
+ }
737
+ if (i === maxLoops - 1) {
738
+ console.log(pc.yellow(`\n[WARN] Max loops (${maxLoops}) reached. Tests still failing.`));
739
+ break;
740
+ }
741
+ 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.`;
742
+ await ctx.callModelWithTools(`User Prompt: ${failureCtx}`);
743
+ ctx.sessionManager.saveSessionState(ctx.messages, ctx.activeFiles, getSessionTodos(ctx.toolContext.sessionId));
744
+ }
745
+ }
746
+ },
747
+ {
748
+ name: '/index',
749
+ description: 'Index codebase for symbol search',
750
+ execute: async (args, ctx) => {
751
+ const parts = args.trim().split(/\s+/).filter(Boolean);
752
+ const opts = {};
753
+ for (const arg of parts) {
754
+ if (arg.startsWith('--exclude=')) {
755
+ opts.exclude = arg.split('=')[1].split(',');
756
+ }
757
+ else if (arg.startsWith('--ext=')) {
758
+ opts.extensions = arg.split('=')[1].split(',');
759
+ }
760
+ }
761
+ console.log(pc.bold('\n--- Indexing Codebase ---'));
762
+ console.log(pc.gray(`Project: ${process.cwd()}`));
763
+ const indexDbPath = ctx.getIndexDbPath();
764
+ if (!fs.existsSync(path.dirname(indexDbPath))) {
765
+ fs.mkdirSync(path.dirname(indexDbPath), { recursive: true });
766
+ }
767
+ const { initIndexDb } = await import('./indexing/fts.js');
768
+ const { indexCodebase } = await import('./indexing/indexer.js');
769
+ const db = initIndexDb(indexDbPath);
770
+ console.log(pc.gray('\nScanning files...'));
771
+ const start = Date.now();
772
+ try {
773
+ const barWidth = 20;
774
+ let lastPct = -1;
775
+ const onProgress = ({ current, total, file }) => {
776
+ const pct = Math.round((current / total) * 100);
777
+ if (pct === lastPct)
778
+ return;
779
+ lastPct = pct;
780
+ const filled = Math.round((current / total) * barWidth);
781
+ const bar = '\u2588'.repeat(filled) + '\u2591'.repeat(barWidth - filled);
782
+ process.stdout.write(`\r ${pc.cyan(bar)} ${pc.white(`${current}/${total}`)} ${pc.gray(file.slice(-40))}`);
783
+ };
784
+ const result = await indexCodebase(db, process.cwd(), ctx.projectHash, { ...opts, onProgress });
785
+ process.stdout.write('\n');
786
+ const elapsed = Date.now() - start;
787
+ ctx.toolContext.indexDb = db;
788
+ console.log(pc.green(`\n✔ Indexing complete in ${elapsed}ms`));
789
+ console.log(pc.white(` Total files: ${result.totalFiles}`));
790
+ console.log(pc.white(` Indexed files: ${result.indexedFiles}`));
791
+ console.log(pc.white(` Skipped (unchanged): ${result.skippedFiles}`));
792
+ if (result.errors.length > 0) {
793
+ console.log(pc.yellow(`\nErrors (${result.errors.length}):`));
794
+ result.errors.slice(0, 10).forEach(e => console.log(pc.red(` - ${e}`)));
795
+ if (result.errors.length > 10) {
796
+ console.log(pc.gray(` ... and ${result.errors.length - 10} more`));
797
+ }
798
+ }
799
+ }
800
+ catch (err) {
801
+ console.error(pc.red(`\n[ERROR] Indexing failed: ${err.message}`));
802
+ }
803
+ }
804
+ },
805
+ {
806
+ name: '/find',
807
+ description: 'Search indexed symbols',
808
+ execute: async (args, ctx) => {
809
+ const parts = args.trim().split(/\s+/).filter(Boolean);
810
+ if (parts.length === 0) {
811
+ console.log(pc.red('[WARN] Usage: /find <query> [limit]'));
812
+ return;
813
+ }
814
+ const query = parts[0];
815
+ const limit = parts[1] ? parseInt(parts[1], 10) : 30;
816
+ if (isNaN(limit)) {
817
+ console.log(pc.red('[WARN] Invalid limit'));
818
+ return;
819
+ }
820
+ const indexDbPath = ctx.getIndexDbPath();
821
+ if (!fs.existsSync(indexDbPath)) {
822
+ console.log(pc.yellow('[WARN] No index found. Run /index first.'));
823
+ return;
824
+ }
825
+ const { initIndexDb, searchSymbols } = await import('./indexing/fts.js');
826
+ const db = initIndexDb(indexDbPath);
827
+ console.log(pc.bold(`\n--- Symbol Search: "${query}" ---`));
828
+ const symbols = searchSymbols(db, query, ctx.projectHash, limit);
829
+ if (symbols.length === 0) {
830
+ console.log(pc.gray(' No symbols found.'));
831
+ return;
832
+ }
833
+ console.log(pc.white(`\nFound ${symbols.length} symbol(s):`));
834
+ for (const s of symbols) {
835
+ const kindColor = s.kind === 'function' ? pc.cyan : s.kind === 'class' ? pc.green : s.kind === 'interface' ? pc.blue : pc.white;
836
+ const loc = `${s.file_path}:${s.line_start}${s.line_end !== s.line_start ? '-' + s.line_end : ''}`;
837
+ console.log(` ${kindColor(`[${s.kind}]`)} ${pc.bold(s.name)} ${pc.dim(`(${loc})`)}`);
838
+ if (s.signature) {
839
+ console.log(pc.dim(` ${s.signature.slice(0, 100)}${s.signature.length > 100 ? '...' : ''}`));
840
+ }
841
+ }
842
+ }
843
+ },
844
+ {
845
+ name: '/refs',
846
+ description: 'Find symbol references (callers)',
847
+ execute: async (args, ctx) => {
848
+ const symbol = args.trim();
849
+ if (!symbol) {
850
+ console.log(pc.red('[WARN] Usage: /refs <symbol>'));
851
+ return;
852
+ }
853
+ const indexDbPath = ctx.getIndexDbPath();
854
+ if (!fs.existsSync(indexDbPath)) {
855
+ console.log(pc.yellow('[WARN] No index found. Run /index first.'));
856
+ return;
857
+ }
858
+ const { initIndexDb, findReferences } = await import('./indexing/fts.js');
859
+ const db = initIndexDb(indexDbPath);
860
+ console.log(pc.bold(`\n--- References to: ${symbol} ---`));
861
+ const refs = findReferences(db, symbol, ctx.projectHash);
862
+ if (refs.length === 0) {
863
+ console.log(pc.gray(' No references found.'));
864
+ return;
865
+ }
866
+ const byCaller = new Map();
867
+ for (const r of refs) {
868
+ const key = `${r.caller_name} (${r.caller_file}:${r.caller_line})`;
869
+ if (!byCaller.has(key))
870
+ byCaller.set(key, []);
871
+ byCaller.get(key).push(r);
872
+ }
873
+ console.log(pc.white(`\nFound ${refs.length} reference(s) from ${byCaller.size} caller(s):`));
874
+ for (const [caller, refs] of byCaller) {
875
+ console.log(pc.cyan(`\n ${caller}:`));
876
+ for (const r of refs.slice(0, 5)) {
877
+ console.log(pc.dim(` ${r.callee_name} at ${r.callee_file}:${r.callee_line}`));
878
+ }
879
+ if (refs.length > 5) {
880
+ console.log(pc.dim(` ... and ${refs.length - 5} more`));
881
+ }
882
+ }
883
+ }
884
+ },
885
+ {
886
+ name: '/def',
887
+ description: 'Get symbol definition',
888
+ execute: async (args, ctx) => {
889
+ const symbol = args.trim();
890
+ if (!symbol) {
891
+ console.log(pc.red('[WARN] Usage: /def <symbol>'));
892
+ return;
893
+ }
894
+ const indexDbPath = ctx.getIndexDbPath();
895
+ if (!fs.existsSync(indexDbPath)) {
896
+ console.log(pc.yellow('[WARN] No index found. Run /index first.'));
897
+ return;
898
+ }
899
+ const { initIndexDb, findDefinitions } = await import('./indexing/fts.js');
900
+ const db = initIndexDb(indexDbPath);
901
+ console.log(pc.bold(`\n--- Definition: ${symbol} ---`));
902
+ const defs = findDefinitions(db, symbol, ctx.projectHash);
903
+ if (defs.length === 0) {
904
+ console.log(pc.gray(' No definitions found.'));
905
+ return;
906
+ }
907
+ console.log(pc.white(`\nFound ${defs.length} definition(s):`));
908
+ for (const d of defs) {
909
+ const kindColor = d.kind === 'function' ? pc.cyan : d.kind === 'class' ? pc.green : d.kind === 'interface' ? pc.blue : pc.white;
910
+ const loc = `${d.file_path}:${d.line_start}${d.line_end !== d.line_start ? '-' + d.line_end : ''}`;
911
+ console.log(` ${kindColor(`[${d.kind}]`)} ${pc.bold(d.name)} ${pc.dim(`(${loc})`)}`);
912
+ if (d.signature) {
913
+ console.log(pc.dim(` ${d.signature.slice(0, 120)}${d.signature.length > 120 ? '...' : ''}`));
914
+ }
915
+ }
916
+ }
917
+ },
918
+ {
919
+ name: '/models',
920
+ description: 'List available and healthy models',
921
+ execute: async (args, ctx) => {
922
+ console.log(pc.bold('\n--- Available Models ---'));
923
+ const models = await ctx.router.listModels();
924
+ if (models.length === 0) {
925
+ console.log(pc.yellow(' No models found. Check your local servers (LM Studio, Ollama, etc.)'));
926
+ }
927
+ else {
928
+ for (const model of models) {
929
+ console.log(` • ${pc.cyan(model)}`);
930
+ }
931
+ }
932
+ const { checkModelHealth } = await import('./router/health.js');
933
+ const healthyModels = ctx.router.getHealthyModels();
934
+ console.log(pc.bold('\n--- Healthy Models ---'));
935
+ for (const model of healthyModels) {
936
+ const health = await checkModelHealth(model, 5000);
937
+ const status = health?.healthy ? pc.green('●') : pc.red('●');
938
+ console.log(` ${status} ${pc.cyan(model.name)} (${model.endpoint}) - ${model.model}`);
939
+ }
940
+ console.log(pc.bold('----------------------\n'));
941
+ }
942
+ },
943
+ {
944
+ name: '/config',
945
+ description: 'Show current configuration',
946
+ execute: async (args, ctx) => {
947
+ console.log(pc.bold('\n--- Current Configuration ---'));
948
+ console.log(JSON.stringify(ctx.config, null, 2));
949
+ console.log(pc.bold('-----------------------------'));
950
+ console.log(pc.gray(`\nEdit ${ctx.configDir}/config.json to modify settings.`));
951
+ console.log(pc.gray('Run `daedalus /doctor` to auto-discover local servers.'));
952
+ }
953
+ },
954
+ {
955
+ name: '/doctor',
956
+ description: 'Diagnose connection and discovery',
957
+ execute: async (args, ctx) => {
958
+ console.log(pc.bold('\n--- Daedalus Doctor ---'));
959
+ console.log(pc.gray('Checking local server connections...\n'));
960
+ const discovered = await discoverLocalServers();
961
+ if (discovered.length === 0) {
962
+ console.log(pc.yellow(' No local servers detected.'));
963
+ console.log(pc.gray(' Start one of:'));
964
+ console.log(pc.gray(' • LM Studio (http://localhost:1234)'));
965
+ console.log(pc.gray(' • Ollama (http://localhost:11434)'));
966
+ console.log(pc.gray(' • llama.cpp server (--server, default :8080)'));
967
+ console.log(pc.gray(' • vLLM (http://localhost:8000)'));
968
+ }
969
+ else {
970
+ console.log(pc.green(` Found ${discovered.length} running server(s):\n`));
971
+ for (const server of discovered) {
972
+ console.log(` ${pc.green('●')} ${server.name} at ${server.endpoint}`);
973
+ for (const model of server.models.slice(0, 5)) {
974
+ console.log(` - ${model}`);
975
+ }
976
+ if (server.models.length > 5) {
977
+ console.log(pc.gray(` ... and ${server.models.length - 5} more`));
978
+ }
979
+ }
980
+ }
981
+ console.log(pc.bold('\n--- Router Health ---'));
982
+ const enabledModels = ctx.router.getEnabledModels();
983
+ if (enabledModels.length === 0) {
984
+ console.log(pc.yellow(' No models configured. Run /onboard to set one up.'));
985
+ }
986
+ else {
987
+ for (const model of enabledModels) {
988
+ const { checkModelHealth } = await import('./router/health.js');
989
+ const health = await checkModelHealth(model, 5000);
990
+ const status = health.healthy ? pc.green('●') : pc.red('●');
991
+ const latency = health.latencyMs ? ` (${health.latencyMs}ms)` : '';
992
+ const err = health.error ? ` ${pc.red(health.error)}` : '';
993
+ console.log(` ${status} ${model.name}: ${model.endpoint}${latency}${err}`);
994
+ }
995
+ }
996
+ console.log(pc.bold(' Config:') + pc.gray(` ${ctx.configDir}\\config.json`));
997
+ console.log(pc.bold('----------------------\n'));
998
+ }
999
+ },
1000
+ {
1001
+ name: 'exit',
1002
+ aliases: ['quit'],
1003
+ description: 'Save session and exit',
1004
+ execute: async (args, ctx) => {
1005
+ const todos = getSessionTodos(ctx.toolContext.sessionId);
1006
+ ctx.sessionManager.saveSessionState(ctx.messages, ctx.activeFiles, todos);
1007
+ console.log(pc.dim(' [EXTRACT] Extracting facts from session...'));
1008
+ await extractAndSave(ctx.router, ctx.sessionManager, ctx.messages);
1009
+ console.log(pc.gray(`Session saved: ${ctx.sessionManager.sessionId}`));
1010
+ console.log(pc.yellow('\nEnding session. Goodbye!\n'));
1011
+ ctx.rl.close();
1012
+ process.exit(0);
1013
+ }
1014
+ }
1015
+ ];
1016
+ export async function executeCommand(input, ctx) {
1017
+ const trimmed = input.trim();
1018
+ if (!trimmed)
1019
+ return false;
1020
+ const parts = trimmed.split(/\s+/);
1021
+ const commandName = parts[0].toLowerCase();
1022
+ const args = trimmed.substring(parts[0].length).trim();
1023
+ let mappedName = commandName;
1024
+ if (commandName === '?') {
1025
+ mappedName = '/help';
1026
+ }
1027
+ const command = commandsList.find(c => c.name.toLowerCase() === mappedName ||
1028
+ c.aliases?.some(alias => alias.toLowerCase() === mappedName));
1029
+ if (command) {
1030
+ try {
1031
+ await command.execute(args, ctx);
1032
+ }
1033
+ catch (err) {
1034
+ console.log(pc.red(`[ERROR] Command ${command.name} failed: ${err.message}`));
1035
+ }
1036
+ return true; // Handled
1037
+ }
1038
+ if (trimmed.startsWith('/')) {
1039
+ console.log(pc.red(`[WARN] Unknown command: ${commandName}. Type /help or ? to view all available commands.`));
1040
+ return true; // We treated it as a command, so don't pass it to the model
1041
+ }
1042
+ return false; // Not a command
1043
+ }
1044
+ //# sourceMappingURL=commands.js.map