cmdr-agent 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +238 -0
  3. package/dist/bin/cmdr.d.ts +9 -0
  4. package/dist/bin/cmdr.d.ts.map +1 -0
  5. package/dist/bin/cmdr.js +49 -0
  6. package/dist/bin/cmdr.js.map +1 -0
  7. package/dist/src/cli/args.d.ts +19 -0
  8. package/dist/src/cli/args.d.ts.map +1 -0
  9. package/dist/src/cli/args.js +89 -0
  10. package/dist/src/cli/args.js.map +1 -0
  11. package/dist/src/cli/commands.d.ts +12 -0
  12. package/dist/src/cli/commands.d.ts.map +1 -0
  13. package/dist/src/cli/commands.js +400 -0
  14. package/dist/src/cli/commands.js.map +1 -0
  15. package/dist/src/cli/renderer.d.ts +8 -0
  16. package/dist/src/cli/renderer.d.ts.map +1 -0
  17. package/dist/src/cli/renderer.js +47 -0
  18. package/dist/src/cli/renderer.js.map +1 -0
  19. package/dist/src/cli/repl.d.ts +18 -0
  20. package/dist/src/cli/repl.d.ts.map +1 -0
  21. package/dist/src/cli/repl.js +751 -0
  22. package/dist/src/cli/repl.js.map +1 -0
  23. package/dist/src/cli/spinner.d.ts +16 -0
  24. package/dist/src/cli/spinner.d.ts.map +1 -0
  25. package/dist/src/cli/spinner.js +233 -0
  26. package/dist/src/cli/spinner.js.map +1 -0
  27. package/dist/src/cli/theme.d.ts +95 -0
  28. package/dist/src/cli/theme.d.ts.map +1 -0
  29. package/dist/src/cli/theme.js +178 -0
  30. package/dist/src/cli/theme.js.map +1 -0
  31. package/dist/src/communication/message-bus.d.ts +35 -0
  32. package/dist/src/communication/message-bus.d.ts.map +1 -0
  33. package/dist/src/communication/message-bus.js +60 -0
  34. package/dist/src/communication/message-bus.js.map +1 -0
  35. package/dist/src/communication/shared-memory.d.ts +19 -0
  36. package/dist/src/communication/shared-memory.d.ts.map +1 -0
  37. package/dist/src/communication/shared-memory.js +37 -0
  38. package/dist/src/communication/shared-memory.js.map +1 -0
  39. package/dist/src/communication/task-queue.d.ts +50 -0
  40. package/dist/src/communication/task-queue.d.ts.map +1 -0
  41. package/dist/src/communication/task-queue.js +158 -0
  42. package/dist/src/communication/task-queue.js.map +1 -0
  43. package/dist/src/config/config-loader.d.ts +23 -0
  44. package/dist/src/config/config-loader.d.ts.map +1 -0
  45. package/dist/src/config/config-loader.js +91 -0
  46. package/dist/src/config/config-loader.js.map +1 -0
  47. package/dist/src/config/defaults.d.ts +6 -0
  48. package/dist/src/config/defaults.d.ts.map +1 -0
  49. package/dist/src/config/defaults.js +21 -0
  50. package/dist/src/config/defaults.js.map +1 -0
  51. package/dist/src/config/schema.d.ts +135 -0
  52. package/dist/src/config/schema.d.ts.map +1 -0
  53. package/dist/src/config/schema.js +35 -0
  54. package/dist/src/config/schema.js.map +1 -0
  55. package/dist/src/config/telemetry.d.ts +41 -0
  56. package/dist/src/config/telemetry.d.ts.map +1 -0
  57. package/dist/src/config/telemetry.js +57 -0
  58. package/dist/src/config/telemetry.js.map +1 -0
  59. package/dist/src/core/agent-pool.d.ts +40 -0
  60. package/dist/src/core/agent-pool.d.ts.map +1 -0
  61. package/dist/src/core/agent-pool.js +66 -0
  62. package/dist/src/core/agent-pool.js.map +1 -0
  63. package/dist/src/core/agent-runner.d.ts +51 -0
  64. package/dist/src/core/agent-runner.d.ts.map +1 -0
  65. package/dist/src/core/agent-runner.js +251 -0
  66. package/dist/src/core/agent-runner.js.map +1 -0
  67. package/dist/src/core/agent.d.ts +34 -0
  68. package/dist/src/core/agent.d.ts.map +1 -0
  69. package/dist/src/core/agent.js +143 -0
  70. package/dist/src/core/agent.js.map +1 -0
  71. package/dist/src/core/intent.d.ts +33 -0
  72. package/dist/src/core/intent.d.ts.map +1 -0
  73. package/dist/src/core/intent.js +91 -0
  74. package/dist/src/core/intent.js.map +1 -0
  75. package/dist/src/core/orchestrator.d.ts +43 -0
  76. package/dist/src/core/orchestrator.d.ts.map +1 -0
  77. package/dist/src/core/orchestrator.js +115 -0
  78. package/dist/src/core/orchestrator.js.map +1 -0
  79. package/dist/src/core/permissions.d.ts +36 -0
  80. package/dist/src/core/permissions.d.ts.map +1 -0
  81. package/dist/src/core/permissions.js +129 -0
  82. package/dist/src/core/permissions.js.map +1 -0
  83. package/dist/src/core/presets.d.ts +12 -0
  84. package/dist/src/core/presets.d.ts.map +1 -0
  85. package/dist/src/core/presets.js +148 -0
  86. package/dist/src/core/presets.js.map +1 -0
  87. package/dist/src/core/team.d.ts +44 -0
  88. package/dist/src/core/team.d.ts.map +1 -0
  89. package/dist/src/core/team.js +156 -0
  90. package/dist/src/core/team.js.map +1 -0
  91. package/dist/src/core/types.d.ts +257 -0
  92. package/dist/src/core/types.d.ts.map +1 -0
  93. package/dist/src/core/types.js +7 -0
  94. package/dist/src/core/types.js.map +1 -0
  95. package/dist/src/index.d.ts +44 -0
  96. package/dist/src/index.d.ts.map +1 -0
  97. package/dist/src/index.js +45 -0
  98. package/dist/src/index.js.map +1 -0
  99. package/dist/src/llm/adapter.d.ts +5 -0
  100. package/dist/src/llm/adapter.d.ts.map +1 -0
  101. package/dist/src/llm/adapter.js +5 -0
  102. package/dist/src/llm/adapter.js.map +1 -0
  103. package/dist/src/llm/model-registry.d.ts +14 -0
  104. package/dist/src/llm/model-registry.d.ts.map +1 -0
  105. package/dist/src/llm/model-registry.js +30 -0
  106. package/dist/src/llm/model-registry.js.map +1 -0
  107. package/dist/src/llm/ollama.d.ts +26 -0
  108. package/dist/src/llm/ollama.d.ts.map +1 -0
  109. package/dist/src/llm/ollama.js +375 -0
  110. package/dist/src/llm/ollama.js.map +1 -0
  111. package/dist/src/llm/token-counter.d.ts +11 -0
  112. package/dist/src/llm/token-counter.d.ts.map +1 -0
  113. package/dist/src/llm/token-counter.js +35 -0
  114. package/dist/src/llm/token-counter.js.map +1 -0
  115. package/dist/src/plugins/mcp-client.d.ts +38 -0
  116. package/dist/src/plugins/mcp-client.d.ts.map +1 -0
  117. package/dist/src/plugins/mcp-client.js +113 -0
  118. package/dist/src/plugins/mcp-client.js.map +1 -0
  119. package/dist/src/plugins/plugin-manager.d.ts +37 -0
  120. package/dist/src/plugins/plugin-manager.d.ts.map +1 -0
  121. package/dist/src/plugins/plugin-manager.js +146 -0
  122. package/dist/src/plugins/plugin-manager.js.map +1 -0
  123. package/dist/src/scheduling/semaphore.d.ts +20 -0
  124. package/dist/src/scheduling/semaphore.d.ts.map +1 -0
  125. package/dist/src/scheduling/semaphore.js +52 -0
  126. package/dist/src/scheduling/semaphore.js.map +1 -0
  127. package/dist/src/scheduling/strategies.d.ts +39 -0
  128. package/dist/src/scheduling/strategies.d.ts.map +1 -0
  129. package/dist/src/scheduling/strategies.js +88 -0
  130. package/dist/src/scheduling/strategies.js.map +1 -0
  131. package/dist/src/session/compaction.d.ts +32 -0
  132. package/dist/src/session/compaction.d.ts.map +1 -0
  133. package/dist/src/session/compaction.js +172 -0
  134. package/dist/src/session/compaction.js.map +1 -0
  135. package/dist/src/session/cost-tracker.d.ts +41 -0
  136. package/dist/src/session/cost-tracker.d.ts.map +1 -0
  137. package/dist/src/session/cost-tracker.js +76 -0
  138. package/dist/src/session/cost-tracker.js.map +1 -0
  139. package/dist/src/session/project-context.d.ts +6 -0
  140. package/dist/src/session/project-context.d.ts.map +1 -0
  141. package/dist/src/session/project-context.js +147 -0
  142. package/dist/src/session/project-context.js.map +1 -0
  143. package/dist/src/session/prompt-builder.d.ts +11 -0
  144. package/dist/src/session/prompt-builder.d.ts.map +1 -0
  145. package/dist/src/session/prompt-builder.js +30 -0
  146. package/dist/src/session/prompt-builder.js.map +1 -0
  147. package/dist/src/session/session-manager.d.ts +32 -0
  148. package/dist/src/session/session-manager.d.ts.map +1 -0
  149. package/dist/src/session/session-manager.js +84 -0
  150. package/dist/src/session/session-manager.js.map +1 -0
  151. package/dist/src/session/session-persistence.d.ts +44 -0
  152. package/dist/src/session/session-persistence.d.ts.map +1 -0
  153. package/dist/src/session/session-persistence.js +150 -0
  154. package/dist/src/session/session-persistence.js.map +1 -0
  155. package/dist/src/session/undo-manager.d.ts +35 -0
  156. package/dist/src/session/undo-manager.d.ts.map +1 -0
  157. package/dist/src/session/undo-manager.js +79 -0
  158. package/dist/src/session/undo-manager.js.map +1 -0
  159. package/dist/src/tools/built-in/ask-user.d.ts +7 -0
  160. package/dist/src/tools/built-in/ask-user.d.ts.map +1 -0
  161. package/dist/src/tools/built-in/ask-user.js +28 -0
  162. package/dist/src/tools/built-in/ask-user.js.map +1 -0
  163. package/dist/src/tools/built-in/bash.d.ts +9 -0
  164. package/dist/src/tools/built-in/bash.d.ts.map +1 -0
  165. package/dist/src/tools/built-in/bash.js +67 -0
  166. package/dist/src/tools/built-in/bash.js.map +1 -0
  167. package/dist/src/tools/built-in/file-edit.d.ts +9 -0
  168. package/dist/src/tools/built-in/file-edit.d.ts.map +1 -0
  169. package/dist/src/tools/built-in/file-edit.js +39 -0
  170. package/dist/src/tools/built-in/file-edit.js.map +1 -0
  171. package/dist/src/tools/built-in/file-read.d.ts +9 -0
  172. package/dist/src/tools/built-in/file-read.d.ts.map +1 -0
  173. package/dist/src/tools/built-in/file-read.js +41 -0
  174. package/dist/src/tools/built-in/file-read.js.map +1 -0
  175. package/dist/src/tools/built-in/file-write.d.ts +8 -0
  176. package/dist/src/tools/built-in/file-write.d.ts.map +1 -0
  177. package/dist/src/tools/built-in/file-write.js +29 -0
  178. package/dist/src/tools/built-in/file-write.js.map +1 -0
  179. package/dist/src/tools/built-in/git-commit.d.ts +12 -0
  180. package/dist/src/tools/built-in/git-commit.d.ts.map +1 -0
  181. package/dist/src/tools/built-in/git-commit.js +96 -0
  182. package/dist/src/tools/built-in/git-commit.js.map +1 -0
  183. package/dist/src/tools/built-in/git-diff.d.ts +8 -0
  184. package/dist/src/tools/built-in/git-diff.d.ts.map +1 -0
  185. package/dist/src/tools/built-in/git-diff.js +43 -0
  186. package/dist/src/tools/built-in/git-diff.js.map +1 -0
  187. package/dist/src/tools/built-in/git-log.d.ts +8 -0
  188. package/dist/src/tools/built-in/git-log.d.ts.map +1 -0
  189. package/dist/src/tools/built-in/git-log.js +39 -0
  190. package/dist/src/tools/built-in/git-log.js.map +1 -0
  191. package/dist/src/tools/built-in/glob.d.ts +8 -0
  192. package/dist/src/tools/built-in/glob.d.ts.map +1 -0
  193. package/dist/src/tools/built-in/glob.js +60 -0
  194. package/dist/src/tools/built-in/glob.js.map +1 -0
  195. package/dist/src/tools/built-in/grep.d.ts +9 -0
  196. package/dist/src/tools/built-in/grep.d.ts.map +1 -0
  197. package/dist/src/tools/built-in/grep.js +115 -0
  198. package/dist/src/tools/built-in/grep.js.map +1 -0
  199. package/dist/src/tools/built-in/index.d.ts +21 -0
  200. package/dist/src/tools/built-in/index.d.ts.map +1 -0
  201. package/dist/src/tools/built-in/index.js +30 -0
  202. package/dist/src/tools/built-in/index.js.map +1 -0
  203. package/dist/src/tools/built-in/think.d.ts +7 -0
  204. package/dist/src/tools/built-in/think.d.ts.map +1 -0
  205. package/dist/src/tools/built-in/think.js +18 -0
  206. package/dist/src/tools/built-in/think.js.map +1 -0
  207. package/dist/src/tools/built-in/web-fetch.d.ts +11 -0
  208. package/dist/src/tools/built-in/web-fetch.d.ts.map +1 -0
  209. package/dist/src/tools/built-in/web-fetch.js +70 -0
  210. package/dist/src/tools/built-in/web-fetch.js.map +1 -0
  211. package/dist/src/tools/executor.d.ts +25 -0
  212. package/dist/src/tools/executor.d.ts.map +1 -0
  213. package/dist/src/tools/executor.js +61 -0
  214. package/dist/src/tools/executor.js.map +1 -0
  215. package/dist/src/tools/registry.d.ts +25 -0
  216. package/dist/src/tools/registry.d.ts.map +1 -0
  217. package/dist/src/tools/registry.js +135 -0
  218. package/dist/src/tools/registry.js.map +1 -0
  219. package/package.json +63 -0
@@ -0,0 +1,751 @@
1
+ /**
2
+ * Interactive REPL — the primary cmdr interface.
3
+ *
4
+ * Streaming output, tool execution display, markdown rendering,
5
+ * AMOLED black + green/purple aesthetic.
6
+ */
7
+ import * as readline from 'readline';
8
+ import { Agent } from '../core/agent.js';
9
+ import { OllamaAdapter } from '../llm/ollama.js';
10
+ import { ToolRegistry } from '../tools/registry.js';
11
+ import { registerBuiltInTools } from '../tools/built-in/index.js';
12
+ import { SOLO_CODER, getTeamPreset } from '../core/presets.js';
13
+ import { Orchestrator } from '../core/orchestrator.js';
14
+ import { SessionManager } from '../session/session-manager.js';
15
+ import { discoverProject } from '../session/project-context.js';
16
+ import { buildSystemPrompt } from '../session/prompt-builder.js';
17
+ import { startThinking, startToolExec, stopSpinner, spinnerSuccess, spinnerFail, getCompletionSummary } from './spinner.js';
18
+ import { renderWelcome, renderToolExec, renderError, PROMPT_SYMBOL, GREEN, PURPLE, DIM, WHITE, CYAN, renderInfo, GREEN_DIM, YELLOW, RED, SUCCESS_SYMBOL, ERROR_SYMBOL, } from './theme.js';
19
+ import { isSlashCommand, parseSlashCommand, getCommand, } from './commands.js';
20
+ import { PermissionManager } from '../core/permissions.js';
21
+ import { saveSession, loadSession, findRecentSession, DebouncedSaver } from '../session/session-persistence.js';
22
+ import { PluginManager } from '../plugins/plugin-manager.js';
23
+ import { McpClient } from '../plugins/mcp-client.js';
24
+ import { loadConfig } from '../config/config-loader.js';
25
+ import { CostTracker } from '../session/cost-tracker.js';
26
+ import { UndoManager } from '../session/undo-manager.js';
27
+ export async function startRepl(options) {
28
+ const cwd = process.cwd();
29
+ const verbose = options.verbose ?? false;
30
+ // --- Setup ---
31
+ const adapter = new OllamaAdapter(options.ollamaUrl);
32
+ // Check Ollama connectivity
33
+ const healthy = await adapter.healthCheck();
34
+ if (!healthy) {
35
+ console.error(renderError(`Cannot connect to Ollama at ${options.ollamaUrl}\n` +
36
+ ` Make sure Ollama is running: ollama serve\n` +
37
+ ` Then pull a model: ollama pull ${options.model}`));
38
+ process.exit(1);
39
+ }
40
+ // Discover project
41
+ const projectContext = await discoverProject(cwd);
42
+ const projectInfo = projectContext.language !== 'unknown'
43
+ ? `${projectContext.language}${projectContext.framework ? ' / ' + projectContext.framework : ''}`
44
+ : cwd.split('/').pop() || 'unknown';
45
+ // Session
46
+ const session = new SessionManager(projectContext);
47
+ // Build system prompt with project context
48
+ const systemPrompt = buildSystemPrompt({
49
+ basePrompt: SOLO_CODER.systemPrompt,
50
+ projectContext,
51
+ model: options.model,
52
+ });
53
+ // Tool registry
54
+ const toolRegistry = new ToolRegistry();
55
+ registerBuiltInTools(toolRegistry);
56
+ // Load config
57
+ const config = await loadConfig(cwd);
58
+ // Plugin manager
59
+ const pluginManager = new PluginManager();
60
+ for (const pluginSource of config.plugins) {
61
+ try {
62
+ await pluginManager.load(pluginSource);
63
+ }
64
+ catch (err) {
65
+ const msg = err instanceof Error ? err.message : String(err);
66
+ console.log(` ${DIM('⚠ Plugin load failed:')} ${msg}`);
67
+ }
68
+ }
69
+ pluginManager.registerTools(toolRegistry);
70
+ // MCP client
71
+ const mcpClient = new McpClient();
72
+ for (const server of config.mcp.servers) {
73
+ try {
74
+ await mcpClient.connect(server);
75
+ }
76
+ catch (err) {
77
+ const msg = err instanceof Error ? err.message : String(err);
78
+ console.log(` ${DIM('⚠ MCP connect failed:')} ${msg}`);
79
+ }
80
+ }
81
+ mcpClient.registerTools(toolRegistry);
82
+ // Cost tracker
83
+ const costTracker = new CostTracker();
84
+ // Undo manager
85
+ const undoManager = new UndoManager();
86
+ // Permission manager
87
+ const permissionManager = new PermissionManager(options.dangerouslySkipPermissions ? 'yolo' : 'normal');
88
+ await permissionManager.loadSettings();
89
+ // CLI flag overrides persisted mode
90
+ if (options.dangerouslySkipPermissions) {
91
+ permissionManager.setMode('yolo');
92
+ }
93
+ // Orchestrator for team mode
94
+ const orchestrator = new Orchestrator(adapter, toolRegistry, {
95
+ maxConcurrency: 2,
96
+ defaultModel: options.model,
97
+ }, cwd, permissionManager);
98
+ let activeTeamConfig;
99
+ if (options.team) {
100
+ activeTeamConfig = getTeamPreset(options.team);
101
+ if (!activeTeamConfig) {
102
+ console.error(renderError(`Unknown team preset: ${options.team}. Use: review, fullstack, security`));
103
+ process.exit(1);
104
+ }
105
+ }
106
+ // Create agent
107
+ let currentModel = options.model;
108
+ const agent = new Agent({ ...SOLO_CODER, model: currentModel, systemPrompt }, adapter, toolRegistry, cwd, permissionManager);
109
+ // --- Welcome ---
110
+ const modeLabel = permissionManager.getMode() === 'yolo'
111
+ ? YELLOW('⚠ yolo (all tools auto-approved)')
112
+ : permissionManager.getMode() === 'strict'
113
+ ? RED('strict (all tools require approval)')
114
+ : GREEN('normal (write tools require approval)');
115
+ console.log(renderWelcome(currentModel, projectInfo));
116
+ console.log(` ${DIM('Permissions:')} ${modeLabel}`);
117
+ if (activeTeamConfig) {
118
+ const teamAgents = activeTeamConfig.agents.map(a => a.name).join(', ');
119
+ console.log(` ${DIM('Team:')} ${PURPLE(activeTeamConfig.name)} ${DIM(`(${teamAgents})`)}`);
120
+ }
121
+ // Show CMDR.md loading status
122
+ if (projectContext.cmdrInstructions) {
123
+ const lineCount = projectContext.cmdrInstructions.split('\n').length;
124
+ console.log(` ${DIM(`CMDR.md loaded (${lineCount} lines)`)}`);
125
+ }
126
+ // --- Resume session if requested ---
127
+ if (options.resume) {
128
+ const saved = await loadSession(options.resume);
129
+ if (saved) {
130
+ agent.replaceMessages(saved.messages);
131
+ session.syncFromAgent(saved.messages);
132
+ console.log(renderInfo(`Resumed session ${DIM(saved.id)} (${saved.messages.length} messages)`));
133
+ }
134
+ else {
135
+ console.log(renderError(`Session not found: ${options.resume}`));
136
+ }
137
+ }
138
+ else if (options.continue) {
139
+ const saved = await findRecentSession(cwd);
140
+ if (saved) {
141
+ agent.replaceMessages(saved.messages);
142
+ session.syncFromAgent(saved.messages);
143
+ console.log(renderInfo(`Continued session ${DIM(saved.id)} (${saved.messages.length} messages)`));
144
+ }
145
+ else {
146
+ console.log(DIM(' No previous session found for this directory.'));
147
+ }
148
+ }
149
+ // Debounced auto-save (max once per 5s)
150
+ const autoSaver = new DebouncedSaver(5000);
151
+ const doSave = async () => {
152
+ session.syncFromAgent(agent.getHistory());
153
+ if (session.messages.length > 0) {
154
+ await saveSession(session.getState(), currentModel);
155
+ }
156
+ };
157
+ console.log('');
158
+ // --- Handle initial prompt if provided ---
159
+ if (options.initialPrompt) {
160
+ await handleUserMessage(options.initialPrompt, agent, session, currentModel, permissionManager, verbose, adapter, costTracker, undoManager);
161
+ await doSave();
162
+ return;
163
+ }
164
+ // --- Interactive REPL ---
165
+ const rl = readline.createInterface({
166
+ input: process.stdin,
167
+ output: process.stdout,
168
+ prompt: PROMPT_SYMBOL,
169
+ terminal: true,
170
+ });
171
+ rl.prompt();
172
+ // --- Paste detection ---
173
+ // Buffer lines arriving within 50ms of each other and join as single input
174
+ let pasteBuffer = [];
175
+ let pasteTimer = null;
176
+ const PASTE_THRESHOLD_MS = 50;
177
+ // Async queue — ensures commands are processed sequentially
178
+ let processing = false;
179
+ let closed = false;
180
+ async function processInput(input) {
181
+ processing = true;
182
+ try {
183
+ await processLine(input);
184
+ }
185
+ finally {
186
+ processing = false;
187
+ if (closed) {
188
+ process.exit(0);
189
+ }
190
+ else {
191
+ rl.prompt();
192
+ }
193
+ }
194
+ }
195
+ function flushPasteBuffer() {
196
+ pasteTimer = null;
197
+ if (pasteBuffer.length === 0)
198
+ return;
199
+ const lines = pasteBuffer.slice();
200
+ pasteBuffer = [];
201
+ if (lines.length > 1) {
202
+ console.log(` ${DIM(`+${lines.length} lines pasted`)}`);
203
+ }
204
+ const merged = lines.join('\n').trim();
205
+ if (!merged) {
206
+ rl.prompt();
207
+ return;
208
+ }
209
+ processInput(merged);
210
+ }
211
+ async function processLine(input) {
212
+ // Slash commands
213
+ if (isSlashCommand(input)) {
214
+ const { name, args } = parseSlashCommand(input);
215
+ const cmd = getCommand(name);
216
+ if (!cmd) {
217
+ console.log(renderError(`Unknown command: /${name}. Type /help for available commands.`));
218
+ return;
219
+ }
220
+ const result = await cmd.execute(args, {
221
+ session: session.getState(),
222
+ switchModel: (model) => {
223
+ currentModel = model;
224
+ },
225
+ clearHistory: () => {
226
+ session.clear();
227
+ agent.reset();
228
+ permissionManager.resetSession();
229
+ },
230
+ ollamaUrl: options.ollamaUrl,
231
+ adapter,
232
+ model: currentModel,
233
+ agentTokenUsage: agent.getState().tokenUsage,
234
+ permissionManager,
235
+ });
236
+ if (result === '__QUIT__') {
237
+ autoSaver.cancel();
238
+ session.syncFromAgent(agent.getHistory());
239
+ if (session.messages.length > 0) {
240
+ const sid = await saveSession(session.getState(), currentModel);
241
+ console.log(`\n ${DIM('Session saved:')} ${DIM(sid)}`);
242
+ }
243
+ console.log(`\n ${PURPLE('Goodbye.')} ${DIM('Session ended.')}\n`);
244
+ closed = true;
245
+ rl.close();
246
+ process.exit(0);
247
+ return;
248
+ }
249
+ if (result === '__COMPACT__') {
250
+ session.syncFromAgent(agent.getHistory());
251
+ const beforeTokens = session.tokenCount;
252
+ const stats = await session.compact(adapter, currentModel);
253
+ agent.replaceMessages(session.messages);
254
+ const afterTokens = session.tokenCount;
255
+ console.log(renderInfo(`${DIM(`◇ compacted: ${stats.before} messages → ${stats.after} messages (saved ~${stats.tokensSaved} tokens)`)}`));
256
+ return;
257
+ }
258
+ if (result === '__DIFF__') {
259
+ const gitTool = toolRegistry.get('git_diff');
260
+ if (gitTool) {
261
+ const diffResult = await gitTool.execute({ staged: false }, {
262
+ agent: { name: 'cmdr', role: 'assistant', model: currentModel },
263
+ cwd,
264
+ });
265
+ console.log(`\n${WHITE(diffResult.data)}\n`);
266
+ }
267
+ return;
268
+ }
269
+ if (result === '__SESSION_SAVE__') {
270
+ session.syncFromAgent(agent.getHistory());
271
+ if (session.messages.length > 0) {
272
+ const sid = await saveSession(session.getState(), currentModel);
273
+ console.log(renderInfo(`Session saved: ${DIM(sid)}`));
274
+ }
275
+ else {
276
+ console.log(renderInfo('No messages to save.'));
277
+ }
278
+ return;
279
+ }
280
+ if (typeof result === 'string' && result.startsWith('__SESSION_RESUME__:')) {
281
+ const sessionId = result.slice('__SESSION_RESUME__:'.length);
282
+ const saved = await loadSession(sessionId);
283
+ if (saved) {
284
+ agent.replaceMessages(saved.messages);
285
+ session.syncFromAgent(saved.messages);
286
+ console.log(renderInfo(`Resumed session ${DIM(saved.id)} (${saved.messages.length} messages)`));
287
+ }
288
+ else {
289
+ console.log(renderError(`Session not found: ${sessionId}`));
290
+ }
291
+ return;
292
+ }
293
+ if (typeof result === 'string' && result.startsWith('__TEAM_SWITCH__:')) {
294
+ const preset = result.slice('__TEAM_SWITCH__:'.length);
295
+ const teamCfg = getTeamPreset(preset);
296
+ if (teamCfg) {
297
+ activeTeamConfig = teamCfg;
298
+ const teamAgents = teamCfg.agents.map(a => a.name).join(', ');
299
+ console.log(renderInfo(`Switched to team: ${PURPLE(teamCfg.name)} (${teamAgents})`));
300
+ }
301
+ else {
302
+ console.log(renderError(`Unknown team: ${preset}. Use: review, fullstack, security`));
303
+ }
304
+ return;
305
+ }
306
+ if (result === '__AGENTS_STATUS__') {
307
+ if (!activeTeamConfig) {
308
+ console.log(renderInfo(`Solo mode (agent: ${GREEN('cmdr')}). Use /team <preset> for multi-agent.`));
309
+ }
310
+ else {
311
+ const status = orchestrator.getStatus();
312
+ const lines = ['', ` ${PURPLE.bold(`Team: ${activeTeamConfig.name}`)}`, ''];
313
+ for (const agentCfg of activeTeamConfig.agents) {
314
+ const agentStatus = status?.agents.find(a => a.name === agentCfg.name);
315
+ const statusLabel = agentStatus ? DIM(agentStatus.status) : DIM('idle');
316
+ lines.push(` ${GREEN('•')} ${WHITE(agentCfg.name.padEnd(12))} ${statusLabel}`);
317
+ }
318
+ lines.push('');
319
+ console.log(lines.join('\n'));
320
+ }
321
+ return;
322
+ }
323
+ if (result === '__TASKS_STATUS__') {
324
+ const status = orchestrator.getStatus();
325
+ if (!status) {
326
+ console.log(renderInfo('No active team or tasks.'));
327
+ }
328
+ else {
329
+ const s = status.tasks;
330
+ if (s) {
331
+ console.log(renderInfo(`Tasks: ${GREEN(`${s.completed} done`)} · ${YELLOW(`${s.in_progress} running`)} · ${DIM(`${s.pending} pending`)} · ${s.failed > 0 ? RED(`${s.failed} failed`) : DIM('0 failed')}`));
332
+ }
333
+ }
334
+ return;
335
+ }
336
+ if (result === '__COST__') {
337
+ const summary = costTracker.getSummary();
338
+ const elapsed = costTracker.formatElapsed();
339
+ const lines = [
340
+ '',
341
+ ` ${PURPLE.bold('Token usage')}`,
342
+ '',
343
+ ` ${DIM('Model:')} ${WHITE(summary.model)}`,
344
+ ` ${DIM('Turns:')} ${WHITE(String(summary.turns))}`,
345
+ ` ${DIM('Input tokens:')} ${WHITE(String(summary.totalInputTokens))}`,
346
+ ` ${DIM('Output tokens:')} ${WHITE(String(summary.totalOutputTokens))}`,
347
+ ` ${DIM('Total tokens:')} ${GREEN(String(summary.totalTokens))}`,
348
+ ` ${DIM('Tool calls:')} ${WHITE(String(summary.totalToolCalls))}`,
349
+ ` ${DIM('Avg/turn:')} ${WHITE(String(summary.avgTokensPerTurn))}`,
350
+ ` ${DIM('Session time:')} ${WHITE(elapsed)}`,
351
+ '',
352
+ ];
353
+ console.log(lines.join('\n'));
354
+ return;
355
+ }
356
+ if (result === '__UNDO__') {
357
+ if (undoManager.count === 0) {
358
+ console.log(renderInfo('Nothing to undo.'));
359
+ }
360
+ else {
361
+ const change = await undoManager.undoLast();
362
+ if (change) {
363
+ const action = change.originalContent === null ? 'deleted' : 'restored';
364
+ const fname = change.path.split('/').pop() ?? change.path;
365
+ console.log(renderInfo(`Undid ${change.type} on ${GREEN(fname)} (${action})`));
366
+ }
367
+ }
368
+ return;
369
+ }
370
+ if (typeof result === 'string' && result.startsWith('__PLUGIN__:')) {
371
+ const sub = result.slice('__PLUGIN__:'.length).trim();
372
+ if (sub === 'list' || !sub) {
373
+ const plugins = pluginManager.list();
374
+ if (plugins.length === 0) {
375
+ console.log(renderInfo('No plugins loaded. Add plugins to ~/.cmdr/config.toml'));
376
+ }
377
+ else {
378
+ const lines = ['', ` ${PURPLE.bold('Loaded plugins')}`, ''];
379
+ for (const p of plugins) {
380
+ const hooks = p.hooks ? Object.keys(p.hooks).length : 0;
381
+ const tools = p.tools?.length ?? 0;
382
+ console.log(` ${GREEN('•')} ${WHITE(p.name)} v${p.version} ${DIM(`(${hooks} hooks, ${tools} tools)`)}`);
383
+ }
384
+ lines.push('');
385
+ console.log(lines.join('\n'));
386
+ }
387
+ }
388
+ return;
389
+ }
390
+ if (typeof result === 'string' && result.startsWith('__MCP__:')) {
391
+ const sub = result.slice('__MCP__:'.length).trim().split(/\s+/);
392
+ const action = sub[0];
393
+ if (action === 'list' || !action) {
394
+ const conns = mcpClient.listConnections();
395
+ if (conns.length === 0) {
396
+ console.log(renderInfo('No MCP servers connected. Add to ~/.cmdr/config.toml or use /mcp connect <name> <url>'));
397
+ }
398
+ else {
399
+ const lines = ['', ` ${PURPLE.bold('MCP servers')}`, ''];
400
+ for (const c of conns) {
401
+ const status = c.connected ? GREEN('connected') : RED('disconnected');
402
+ lines.push(` ${GREEN('•')} ${WHITE(c.name)} ${DIM(c.url)} ${status} ${DIM(`(${c.tools} tools)`)}`);
403
+ }
404
+ lines.push('');
405
+ console.log(lines.join('\n'));
406
+ }
407
+ }
408
+ else if (action === 'connect') {
409
+ const name = sub[1];
410
+ const url = sub[2];
411
+ if (!name || !url) {
412
+ console.log(renderInfo('Usage: /mcp connect <name> <url>'));
413
+ }
414
+ else {
415
+ try {
416
+ const tools = await mcpClient.connect({ name, url });
417
+ mcpClient.registerTools(toolRegistry);
418
+ console.log(renderInfo(`Connected to ${name}: ${tools.length} tools discovered`));
419
+ }
420
+ catch (err) {
421
+ const msg = err instanceof Error ? err.message : String(err);
422
+ console.log(renderError(msg));
423
+ }
424
+ }
425
+ }
426
+ else if (action === 'disconnect') {
427
+ const name = sub[1];
428
+ if (name && mcpClient.disconnect(name)) {
429
+ console.log(renderInfo(`Disconnected from ${name}`));
430
+ }
431
+ else {
432
+ console.log(renderError(`MCP server "${name}" not found`));
433
+ }
434
+ }
435
+ return;
436
+ }
437
+ if (result)
438
+ console.log(result);
439
+ return;
440
+ }
441
+ // Regular message — team mode or solo mode
442
+ if (activeTeamConfig) {
443
+ await handleTeamMessage(input, orchestrator, activeTeamConfig, currentModel, verbose);
444
+ }
445
+ else {
446
+ await handleUserMessage(input, agent, session, currentModel, permissionManager, verbose, adapter, costTracker, undoManager, () => {
447
+ autoSaver.schedule(doSave);
448
+ });
449
+ }
450
+ }
451
+ rl.on('line', (line) => {
452
+ pasteBuffer.push(line);
453
+ if (pasteTimer)
454
+ clearTimeout(pasteTimer);
455
+ pasteTimer = setTimeout(flushPasteBuffer, PASTE_THRESHOLD_MS);
456
+ });
457
+ rl.on('close', async () => {
458
+ autoSaver.cancel();
459
+ if (!closed) {
460
+ session.syncFromAgent(agent.getHistory());
461
+ if (session.messages.length > 0) {
462
+ try {
463
+ const sid = await saveSession(session.getState(), currentModel);
464
+ console.log(`\n ${DIM('Session saved:')} ${DIM(sid)}`);
465
+ }
466
+ catch {
467
+ // best effort
468
+ }
469
+ }
470
+ console.log(`\n ${PURPLE('Goodbye.')} ${DIM('Session ended.')}\n`);
471
+ }
472
+ if (processing) {
473
+ closed = true;
474
+ }
475
+ else {
476
+ process.exit(0);
477
+ }
478
+ });
479
+ }
480
+ // ---------------------------------------------------------------------------
481
+ // Approval prompt — asks the user to approve a tool call
482
+ // ---------------------------------------------------------------------------
483
+ function promptApproval(toolName, input, riskLevel) {
484
+ return new Promise((resolve) => {
485
+ const riskColor = riskLevel === 'dangerous' ? RED : YELLOW;
486
+ const riskLabel = riskColor(riskLevel.toUpperCase());
487
+ // Show the tool call details
488
+ console.log('');
489
+ console.log(` ${YELLOW('⚠')} ${WHITE('Tool approval required')} ${DIM('[')}${riskLabel}${DIM(']')}`);
490
+ console.log(` ${DIM('Tool:')} ${CYAN(toolName)}`);
491
+ // Show a summary of key arguments
492
+ for (const [key, val] of Object.entries(input)) {
493
+ const display = typeof val === 'string'
494
+ ? val.length > 120 ? val.slice(0, 120) + DIM('...') : val
495
+ : JSON.stringify(val).slice(0, 120);
496
+ console.log(` ${DIM(key + ':')} ${WHITE(display)}`);
497
+ }
498
+ console.log('');
499
+ console.log(` ${GREEN('y')}${DIM('es')} ${DIM('/')} ${RED('n')}${DIM('o')} ${DIM('/')} ${PURPLE('a')}${DIM('lways allow this tool')}`);
500
+ // Create a one-shot readline for the approval prompt
501
+ const approvalRl = readline.createInterface({
502
+ input: process.stdin,
503
+ output: process.stdout,
504
+ terminal: true,
505
+ });
506
+ approvalRl.question(` ${YELLOW('?')} `, (answer) => {
507
+ approvalRl.close();
508
+ const trimmed = answer.trim().toLowerCase();
509
+ if (trimmed === 'y' || trimmed === 'yes' || trimmed === '') {
510
+ resolve('allow');
511
+ }
512
+ else if (trimmed === 'a' || trimmed === 'always') {
513
+ resolve('allow-always');
514
+ }
515
+ else {
516
+ resolve('deny');
517
+ }
518
+ });
519
+ });
520
+ }
521
+ // ---------------------------------------------------------------------------
522
+ // Tool result summary — collapsed single-line display
523
+ // ---------------------------------------------------------------------------
524
+ function summarizeToolResult(toolName, input, content, isError) {
525
+ const lineCount = content.split('\n').length;
526
+ const prefix = isError ? ERROR_SYMBOL : SUCCESS_SYMBOL;
527
+ let summary;
528
+ switch (toolName) {
529
+ case 'file_read': {
530
+ const file = input.path ?? 'unknown';
531
+ const fname = file.split('/').pop() ?? file;
532
+ summary = `${fname} (${lineCount} lines)`;
533
+ break;
534
+ }
535
+ case 'glob': {
536
+ const pattern = input.pattern ?? '*';
537
+ const matches = content === '(no matches)' ? 0 : lineCount;
538
+ summary = `${pattern} (${matches} matches)`;
539
+ break;
540
+ }
541
+ case 'bash': {
542
+ const cmd = input.command ?? '';
543
+ const truncCmd = cmd.length > 60 ? cmd.slice(0, 57) + '...' : cmd;
544
+ // Extract exit code from result if present
545
+ const exitMatch = content.match(/\[exit code: (\d+)\]/);
546
+ const exitCode = exitMatch ? exitMatch[1] : '0';
547
+ summary = `\`${truncCmd}\` exit=${exitCode} (${lineCount} lines)`;
548
+ break;
549
+ }
550
+ case 'grep': {
551
+ const pattern = input.pattern ?? '';
552
+ const matches = content === '(no matches)' ? 0 : lineCount;
553
+ summary = `/${pattern}/ (${matches} matches)`;
554
+ break;
555
+ }
556
+ case 'think': {
557
+ const thought = input.thought ?? '';
558
+ const preview = thought.length > 60 ? thought.slice(0, 57) + '...' : thought;
559
+ summary = preview;
560
+ break;
561
+ }
562
+ default: {
563
+ const bytes = Buffer.byteLength(content, 'utf-8');
564
+ summary = `${bytes > 1024 ? Math.round(bytes / 1024) + ' KB' : bytes + ' B'}`;
565
+ break;
566
+ }
567
+ }
568
+ return ` ${prefix} ${DIM(toolName)} ${DIM(summary)}`;
569
+ }
570
+ // ---------------------------------------------------------------------------
571
+ // Message handler — streaming output with tool execution display
572
+ // ---------------------------------------------------------------------------
573
+ async function handleUserMessage(message, agent, session, model, permissionManager, verbose, adapter, costTracker, undoManager, onAfterResponse) {
574
+ console.log(''); // spacing
575
+ startThinking();
576
+ let fullOutput = '';
577
+ let firstText = true;
578
+ let currentTool = '';
579
+ let currentToolInput = {};
580
+ let toolCallCount = 0;
581
+ // Build callbacks with the approval gate
582
+ const callbacks = {
583
+ onToolApproval: (toolName, input, riskLevel) => promptApproval(toolName, input, riskLevel),
584
+ };
585
+ try {
586
+ for await (const event of agent.stream(message, callbacks)) {
587
+ switch (event.type) {
588
+ case 'text': {
589
+ if (firstText) {
590
+ stopSpinner();
591
+ process.stdout.write(`\n ${PURPLE('│')} `);
592
+ firstText = false;
593
+ }
594
+ const chunk = event.data;
595
+ fullOutput += chunk;
596
+ // Stream raw text token-by-token (no markdown on partial chunks)
597
+ // Handle newlines by adding the prefix
598
+ const formatted = chunk.replace(/\n/g, `\n ${PURPLE('│')} `);
599
+ process.stdout.write(formatted);
600
+ break;
601
+ }
602
+ case 'tool_use': {
603
+ stopSpinner();
604
+ if (!firstText) {
605
+ // Terminate previous text stream line
606
+ process.stdout.write('\n');
607
+ firstText = true;
608
+ }
609
+ const block = event.data;
610
+ currentTool = block.name;
611
+ currentToolInput = block.input;
612
+ toolCallCount++;
613
+ // Record file state for undo before write/edit tools
614
+ if (undoManager && (block.name === 'file_write' || block.name === 'file_edit')) {
615
+ const filePath = (block.input.path ?? block.input.file_path);
616
+ if (filePath) {
617
+ await undoManager.recordBefore(filePath, block.name === 'file_write' ? 'write' : 'edit');
618
+ }
619
+ }
620
+ console.log(renderToolExec(block.name, block.input));
621
+ startToolExec(block.name);
622
+ break;
623
+ }
624
+ case 'tool_result': {
625
+ const block = event.data;
626
+ if (block.is_error) {
627
+ spinnerFail(currentTool);
628
+ }
629
+ else {
630
+ spinnerSuccess(currentTool);
631
+ }
632
+ if (verbose) {
633
+ // Full output in verbose mode
634
+ const truncated = block.content.length > 2000
635
+ ? block.content.slice(0, 2000) + DIM('\n... (truncated)')
636
+ : block.content;
637
+ const prefix = block.is_error ? ERROR_SYMBOL : SUCCESS_SYMBOL;
638
+ console.log(` ${prefix} ${DIM(currentTool + ':')} ${block.is_error ? RED(truncated) : DIM(truncated)}`);
639
+ }
640
+ else {
641
+ console.log(summarizeToolResult(currentTool, currentToolInput, block.content, block.is_error));
642
+ }
643
+ currentTool = '';
644
+ currentToolInput = {};
645
+ startThinking();
646
+ firstText = true;
647
+ break;
648
+ }
649
+ case 'done': {
650
+ stopSpinner();
651
+ break;
652
+ }
653
+ case 'error': {
654
+ stopSpinner();
655
+ const err = event.data;
656
+ console.error(renderError(err.message));
657
+ break;
658
+ }
659
+ }
660
+ }
661
+ }
662
+ catch (err) {
663
+ stopSpinner();
664
+ const msg = err instanceof Error ? err.message : String(err);
665
+ console.error(renderError(msg));
666
+ }
667
+ // Ensure we end the text block with a newline
668
+ if (!firstText) {
669
+ process.stdout.write('\n');
670
+ }
671
+ // Add spacing after response
672
+ if (fullOutput) {
673
+ console.log('');
674
+ }
675
+ // Show turn summary with whimsical verb
676
+ const state = agent.getState();
677
+ const tokens = state.tokenUsage;
678
+ const summary = getCompletionSummary();
679
+ const tokenInfo = tokens.input_tokens > 0 || tokens.output_tokens > 0
680
+ ? ` ${DIM('·')} ${DIM(`${tokens.input_tokens} in / ${tokens.output_tokens} out`)}`
681
+ : '';
682
+ console.log(` ${DIM(summary)}${tokenInfo}`);
683
+ // Record cost data
684
+ costTracker?.record(model, tokens.input_tokens, tokens.output_tokens, toolCallCount);
685
+ // Sync agent messages into session for compaction tracking
686
+ session.syncFromAgent(agent.getHistory());
687
+ // Auto-compact if context is getting full
688
+ if (session.shouldCompact()) {
689
+ try {
690
+ const stats = await session.compact(adapter, model);
691
+ agent.replaceMessages(session.messages);
692
+ console.log(` ${DIM(`◇ compacted: ${stats.before} messages → ${stats.after} messages (saved ~${stats.tokensSaved} tokens)`)}`);
693
+ }
694
+ catch {
695
+ // best effort — don't break the REPL
696
+ }
697
+ }
698
+ // Trigger debounced auto-save
699
+ onAfterResponse?.();
700
+ console.log(GREEN_DIM('─'.repeat(60)));
701
+ console.log('');
702
+ }
703
+ // ---------------------------------------------------------------------------
704
+ // Team message handler — runs goal through the orchestrator
705
+ // ---------------------------------------------------------------------------
706
+ async function handleTeamMessage(goal, orchestrator, teamConfig, model, verbose) {
707
+ console.log('');
708
+ console.log(` ${PURPLE('◈')} Running team ${PURPLE.bold(teamConfig.name)} with ${teamConfig.agents.length} agents...`);
709
+ console.log('');
710
+ startThinking();
711
+ try {
712
+ const result = await orchestrator.runTeam(teamConfig, goal);
713
+ stopSpinner();
714
+ // Display results from each agent
715
+ for (const [agentName, agentResult] of result.agentResults) {
716
+ const status = agentResult.success ? GREEN('✓') : RED('✗');
717
+ console.log(` ${status} ${CYAN(agentName)}`);
718
+ if (agentResult.output) {
719
+ const lines = agentResult.output.split('\n');
720
+ const displayLines = verbose ? lines : lines.slice(0, 20);
721
+ for (const line of displayLines) {
722
+ console.log(` ${PURPLE('│')} ${line}`);
723
+ }
724
+ if (!verbose && lines.length > 20) {
725
+ console.log(` ${PURPLE('│')} ${DIM(`... ${lines.length - 20} more lines (use --verbose)`)}`);
726
+ }
727
+ console.log('');
728
+ }
729
+ // Tool call summary
730
+ if (agentResult.toolCalls.length > 0) {
731
+ const tools = agentResult.toolCalls.map(t => t.toolName);
732
+ const unique = [...new Set(tools)];
733
+ console.log(` ${DIM(` tools: ${unique.join(', ')} (${tools.length} calls)`)}`);
734
+ }
735
+ }
736
+ // Summary
737
+ const usage = result.totalTokenUsage;
738
+ const summary = getCompletionSummary();
739
+ const tokenInfo = `${usage.input_tokens} in / ${usage.output_tokens} out`;
740
+ console.log(` ${DIM(summary)} ${DIM('·')} ${DIM(tokenInfo)}`);
741
+ console.log(` ${result.success ? GREEN('Team completed successfully') : RED('Team had failures')}`);
742
+ }
743
+ catch (err) {
744
+ stopSpinner();
745
+ const msg = err instanceof Error ? err.message : String(err);
746
+ console.error(renderError(msg));
747
+ }
748
+ console.log(GREEN_DIM('─'.repeat(60)));
749
+ console.log('');
750
+ }
751
+ //# sourceMappingURL=repl.js.map