neuro-cli 4.1.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 (175) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +510 -0
  3. package/dist/advisor/advisor.d.ts +50 -0
  4. package/dist/advisor/advisor.js +178 -0
  5. package/dist/agents/base.d.ts +62 -0
  6. package/dist/agents/base.js +215 -0
  7. package/dist/agents/orchestrator.d.ts +46 -0
  8. package/dist/agents/orchestrator.js +192 -0
  9. package/dist/agents/team.d.ts +51 -0
  10. package/dist/agents/team.js +210 -0
  11. package/dist/api/models.d.ts +23 -0
  12. package/dist/api/models.js +514 -0
  13. package/dist/api/ollama.d.ts +153 -0
  14. package/dist/api/ollama.js +751 -0
  15. package/dist/api/openrouter.d.ts +55 -0
  16. package/dist/api/openrouter.js +223 -0
  17. package/dist/commands/commands.d.ts +43 -0
  18. package/dist/commands/commands.js +308 -0
  19. package/dist/config/config.d.ts +8 -0
  20. package/dist/config/config.js +311 -0
  21. package/dist/context/compaction.d.ts +54 -0
  22. package/dist/context/compaction.js +251 -0
  23. package/dist/context/custom-agents.d.ts +107 -0
  24. package/dist/context/custom-agents.js +397 -0
  25. package/dist/context/custom-tools.d.ts +120 -0
  26. package/dist/context/custom-tools.js +564 -0
  27. package/dist/context/git-checkpoint.d.ts +52 -0
  28. package/dist/context/git-checkpoint.js +240 -0
  29. package/dist/context/neuro-md.d.ts +48 -0
  30. package/dist/context/neuro-md.js +202 -0
  31. package/dist/context/neuroignore.d.ts +102 -0
  32. package/dist/context/neuroignore.js +441 -0
  33. package/dist/context/repo-map.d.ts +38 -0
  34. package/dist/context/repo-map.js +220 -0
  35. package/dist/context/skill-standard.d.ts +262 -0
  36. package/dist/context/skill-standard.js +1156 -0
  37. package/dist/context/skill-system.d.ts +75 -0
  38. package/dist/context/skill-system.js +578 -0
  39. package/dist/context/tree-sitter.d.ts +99 -0
  40. package/dist/context/tree-sitter.js +1956 -0
  41. package/dist/core/acp.d.ts +325 -0
  42. package/dist/core/acp.js +1498 -0
  43. package/dist/core/api-server.d.ts +143 -0
  44. package/dist/core/api-server.js +550 -0
  45. package/dist/core/approval.d.ts +81 -0
  46. package/dist/core/approval.js +432 -0
  47. package/dist/core/auto-compact.d.ts +127 -0
  48. package/dist/core/auto-compact.js +436 -0
  49. package/dist/core/auto-mode.d.ts +232 -0
  50. package/dist/core/auto-mode.js +831 -0
  51. package/dist/core/background-session.d.ts +166 -0
  52. package/dist/core/background-session.js +696 -0
  53. package/dist/core/cicd.d.ts +257 -0
  54. package/dist/core/cicd.js +1443 -0
  55. package/dist/core/cloud-sync.d.ts +156 -0
  56. package/dist/core/cloud-sync.js +582 -0
  57. package/dist/core/code-review.d.ts +132 -0
  58. package/dist/core/code-review.js +1191 -0
  59. package/dist/core/completion.d.ts +49 -0
  60. package/dist/core/completion.js +384 -0
  61. package/dist/core/context.d.ts +38 -0
  62. package/dist/core/context.js +144 -0
  63. package/dist/core/diff-preview.d.ts +35 -0
  64. package/dist/core/diff-preview.js +173 -0
  65. package/dist/core/doom-loop.d.ts +51 -0
  66. package/dist/core/doom-loop.js +179 -0
  67. package/dist/core/engine.d.ts +183 -0
  68. package/dist/core/engine.js +942 -0
  69. package/dist/core/extended-thinking.d.ts +103 -0
  70. package/dist/core/extended-thinking.js +269 -0
  71. package/dist/core/fallback.d.ts +54 -0
  72. package/dist/core/fallback.js +104 -0
  73. package/dist/core/git-worktree.d.ts +139 -0
  74. package/dist/core/git-worktree.js +614 -0
  75. package/dist/core/headless.d.ts +30 -0
  76. package/dist/core/headless.js +101 -0
  77. package/dist/core/i18n.d.ts +83 -0
  78. package/dist/core/i18n.js +739 -0
  79. package/dist/core/linting.d.ts +129 -0
  80. package/dist/core/linting.js +699 -0
  81. package/dist/core/model-router.d.ts +109 -0
  82. package/dist/core/model-router.js +581 -0
  83. package/dist/core/multi-model.d.ts +243 -0
  84. package/dist/core/multi-model.js +1099 -0
  85. package/dist/core/multi-session.d.ts +144 -0
  86. package/dist/core/multi-session.js +442 -0
  87. package/dist/core/multimodal.d.ts +125 -0
  88. package/dist/core/multimodal.js +286 -0
  89. package/dist/core/observability.d.ts +93 -0
  90. package/dist/core/observability.js +737 -0
  91. package/dist/core/os-sandbox.d.ts +122 -0
  92. package/dist/core/os-sandbox.js +1193 -0
  93. package/dist/core/outcome-grading.d.ts +228 -0
  94. package/dist/core/outcome-grading.js +1123 -0
  95. package/dist/core/output-styles.d.ts +57 -0
  96. package/dist/core/output-styles.js +382 -0
  97. package/dist/core/parallel-agents.d.ts +183 -0
  98. package/dist/core/parallel-agents.js +563 -0
  99. package/dist/core/plugin-bundle.d.ts +236 -0
  100. package/dist/core/plugin-bundle.js +887 -0
  101. package/dist/core/plugin-sdk.d.ts +139 -0
  102. package/dist/core/plugin-sdk.js +273 -0
  103. package/dist/core/prompt-cache.d.ts +163 -0
  104. package/dist/core/prompt-cache.js +599 -0
  105. package/dist/core/sandbox.d.ts +127 -0
  106. package/dist/core/sandbox.js +369 -0
  107. package/dist/core/scheduled-tasks.d.ts +151 -0
  108. package/dist/core/scheduled-tasks.js +502 -0
  109. package/dist/core/security-scanner.d.ts +160 -0
  110. package/dist/core/security-scanner.js +1494 -0
  111. package/dist/core/session.d.ts +83 -0
  112. package/dist/core/session.js +269 -0
  113. package/dist/core/shell-completion.d.ts +51 -0
  114. package/dist/core/shell-completion.js +674 -0
  115. package/dist/core/smart-monitor.d.ts +146 -0
  116. package/dist/core/smart-monitor.js +1199 -0
  117. package/dist/core/spec-driven.d.ts +233 -0
  118. package/dist/core/spec-driven.js +1485 -0
  119. package/dist/core/spending-warnings.d.ts +123 -0
  120. package/dist/core/spending-warnings.js +456 -0
  121. package/dist/core/sub-agent.d.ts +298 -0
  122. package/dist/core/sub-agent.js +1023 -0
  123. package/dist/core/telemetry.d.ts +157 -0
  124. package/dist/core/telemetry.js +412 -0
  125. package/dist/core/terminal-ux.d.ts +134 -0
  126. package/dist/core/terminal-ux.js +649 -0
  127. package/dist/core/testing.d.ts +146 -0
  128. package/dist/core/testing.js +1200 -0
  129. package/dist/core/types.d.ts +439 -0
  130. package/dist/core/types.js +6 -0
  131. package/dist/core/undo-redo.d.ts +112 -0
  132. package/dist/core/undo-redo.js +290 -0
  133. package/dist/core/updater.d.ts +159 -0
  134. package/dist/core/updater.js +608 -0
  135. package/dist/core/vim-mode.d.ts +151 -0
  136. package/dist/core/vim-mode.js +771 -0
  137. package/dist/core/voice.d.ts +137 -0
  138. package/dist/core/voice.js +538 -0
  139. package/dist/core/web-dashboard.d.ts +109 -0
  140. package/dist/core/web-dashboard.js +484 -0
  141. package/dist/hooks/hooks.d.ts +74 -0
  142. package/dist/hooks/hooks.js +160 -0
  143. package/dist/hooks/llm-evaluator.d.ts +165 -0
  144. package/dist/hooks/llm-evaluator.js +560 -0
  145. package/dist/index.d.ts +3 -0
  146. package/dist/index.js +1186 -0
  147. package/dist/lsp/lsp-manager.d.ts +63 -0
  148. package/dist/lsp/lsp-manager.js +351 -0
  149. package/dist/mcp/client.d.ts +133 -0
  150. package/dist/mcp/client.js +684 -0
  151. package/dist/mcp/mcp-apps.d.ts +70 -0
  152. package/dist/mcp/mcp-apps.js +1007 -0
  153. package/dist/tools/bash.d.ts +5 -0
  154. package/dist/tools/bash.js +195 -0
  155. package/dist/tools/browser.d.ts +92 -0
  156. package/dist/tools/browser.js +1570 -0
  157. package/dist/tools/extended.d.ts +6 -0
  158. package/dist/tools/extended.js +191 -0
  159. package/dist/tools/file.d.ts +10 -0
  160. package/dist/tools/file.js +382 -0
  161. package/dist/tools/github.d.ts +389 -0
  162. package/dist/tools/github.js +759 -0
  163. package/dist/tools/index.d.ts +9 -0
  164. package/dist/tools/index.js +40 -0
  165. package/dist/tools/memory.d.ts +6 -0
  166. package/dist/tools/memory.js +197 -0
  167. package/dist/tools/registry.d.ts +29 -0
  168. package/dist/tools/registry.js +64 -0
  169. package/dist/tools/web.d.ts +6 -0
  170. package/dist/tools/web.js +150 -0
  171. package/dist/ui/renderer.d.ts +97 -0
  172. package/dist/ui/renderer.js +279 -0
  173. package/dist/ui/theme.d.ts +27 -0
  174. package/dist/ui/theme.js +106 -0
  175. package/package.json +83 -0
@@ -0,0 +1,831 @@
1
+ // ============================================================
2
+ // NeuroCLI - Auto Mode
3
+ // Full autonomous execution mode similar to Claude Code's Auto Mode
4
+ // Skips approval prompts while maintaining safety guardrails
5
+ // Supports /goal and /routine commands for autonomous workflows
6
+ // ============================================================
7
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, unlinkSync, } from 'fs';
8
+ import { join } from 'path';
9
+ import { homedir } from 'os';
10
+ import { randomUUID } from 'crypto';
11
+ import { execSync } from 'child_process';
12
+ // ---------------------------------------------------------------------------
13
+ // Dangerous-command patterns – always blocked regardless of safety level
14
+ // ---------------------------------------------------------------------------
15
+ const ALWAYS_BLOCKED_COMMANDS = [
16
+ 'rm -rf /',
17
+ 'rm -rf /*',
18
+ 'sudo rm -rf',
19
+ 'mkfs',
20
+ 'dd if=',
21
+ ':(){ :|:& };:', // fork bomb
22
+ 'chmod 777 /',
23
+ 'chown -R root',
24
+ 'curl | sh',
25
+ 'wget | sh',
26
+ 'curl | bash',
27
+ 'wget | bash',
28
+ '> /dev/sda',
29
+ 'mv / ',
30
+ 'shutdown',
31
+ 'reboot',
32
+ 'init 0',
33
+ 'init 6',
34
+ 'systemctl poweroff',
35
+ 'systemctl reboot',
36
+ ];
37
+ const CONSERVATIVE_BLOCKED_COMMANDS = [
38
+ 'rm -rf',
39
+ 'rm -r',
40
+ 'sudo',
41
+ 'chmod',
42
+ 'chown',
43
+ 'kill -9',
44
+ 'pkill',
45
+ 'killall',
46
+ 'pip uninstall',
47
+ 'npm uninstall',
48
+ 'npm publish',
49
+ 'git push --force',
50
+ 'git reset --hard',
51
+ 'git clean -fd',
52
+ 'drop table',
53
+ 'drop database',
54
+ 'truncate table',
55
+ ];
56
+ const MODERATE_BLOCKED_COMMANDS = [
57
+ 'rm -rf',
58
+ 'sudo rm',
59
+ 'npm publish',
60
+ 'git push --force',
61
+ 'drop table',
62
+ 'drop database',
63
+ ];
64
+ const ALWAYS_BLOCKED_PATTERNS = [
65
+ '**/.env',
66
+ '**/.env.*',
67
+ '**/id_rsa*',
68
+ '**/id_ed25519*',
69
+ '**/*.pem',
70
+ '**/*.key',
71
+ '**/credentials.json',
72
+ '**/secrets.*',
73
+ '**/.ssh/*',
74
+ '**/.gnupg/*',
75
+ '**/etc/shadow',
76
+ '**/etc/passwd',
77
+ ];
78
+ const CONSERVATIVE_BLOCKED_PATTERNS = [
79
+ '**/package.json',
80
+ '**/package-lock.json',
81
+ '**/yarn.lock',
82
+ '**/.gitignore',
83
+ '**/tsconfig.json',
84
+ '**/next.config.*',
85
+ '**/webpack.config.*',
86
+ '**/docker-compose.*',
87
+ '**/Dockerfile*',
88
+ ];
89
+ // ---------------------------------------------------------------------------
90
+ // AutoMode class
91
+ // ---------------------------------------------------------------------------
92
+ const NEURO_DIR = join(homedir(), '.neuro');
93
+ const AUTO_STATE_FILE = join(NEURO_DIR, 'auto-mode-state.json');
94
+ const GOALS_FILE = join(NEURO_DIR, 'auto-mode-goals.json');
95
+ const ROUTINES_FILE = join(NEURO_DIR, 'auto-mode-routines.json');
96
+ const CHECKPOINTS_DIR = join(NEURO_DIR, 'auto-checkpoints');
97
+ export class AutoMode {
98
+ config;
99
+ stats;
100
+ goals = new Map();
101
+ routines = new Map();
102
+ checkpoints = [];
103
+ startTime = 0;
104
+ running = false;
105
+ abortController = null;
106
+ onStatusChange;
107
+ constructor(config, onStatusChange) {
108
+ this.config = {
109
+ enabled: false,
110
+ safetyLevel: 'moderate',
111
+ maxIterations: 50,
112
+ maxCost: 0,
113
+ maxTimeMs: 0,
114
+ blockedCommands: [...ALWAYS_BLOCKED_COMMANDS],
115
+ blockedPatterns: [...ALWAYS_BLOCKED_PATTERNS],
116
+ autoCommit: false,
117
+ autoTest: false,
118
+ pauseOnError: true,
119
+ ...config,
120
+ };
121
+ this.onStatusChange = onStatusChange;
122
+ this.stats = this.freshStats();
123
+ this.loadState();
124
+ }
125
+ // -----------------------------------------------------------------------
126
+ // Enable / Disable
127
+ // -----------------------------------------------------------------------
128
+ /** Enable auto mode – skip all approval prompts, run safety checks in background */
129
+ enable() {
130
+ this.config.enabled = true;
131
+ this.emitStatus('enabled');
132
+ this.persistState();
133
+ }
134
+ /** Disable auto mode, return to interactive */
135
+ disable() {
136
+ this.config.enabled = false;
137
+ this.running = false;
138
+ if (this.abortController) {
139
+ this.abortController.abort();
140
+ this.abortController = null;
141
+ }
142
+ this.emitStatus('disabled');
143
+ this.persistState();
144
+ }
145
+ /** Check whether auto mode is currently enabled */
146
+ isEnabled() {
147
+ return this.config.enabled;
148
+ }
149
+ /** Check whether auto mode is currently running a task */
150
+ isRunning() {
151
+ return this.running;
152
+ }
153
+ // -----------------------------------------------------------------------
154
+ // Configuration
155
+ // -----------------------------------------------------------------------
156
+ getConfig() {
157
+ return Object.freeze({ ...this.config });
158
+ }
159
+ updateConfig(updates) {
160
+ // Merge blocked lists so ALWAYS_BLOCKED are never removed
161
+ if (updates.blockedCommands) {
162
+ const merged = new Set([...ALWAYS_BLOCKED_COMMANDS, ...updates.blockedCommands]);
163
+ updates.blockedCommands = Array.from(merged);
164
+ }
165
+ if (updates.blockedPatterns) {
166
+ const merged = new Set([...ALWAYS_BLOCKED_PATTERNS, ...updates.blockedPatterns]);
167
+ updates.blockedPatterns = Array.from(merged);
168
+ }
169
+ Object.assign(this.config, updates);
170
+ this.persistState();
171
+ }
172
+ setSafetyLevel(level) {
173
+ this.config.safetyLevel = level;
174
+ // Re-apply the pattern / command block lists for the new level
175
+ this.config.blockedCommands = this.buildBlockedCommands(level);
176
+ this.config.blockedPatterns = this.buildBlockedPatterns(level);
177
+ this.persistState();
178
+ }
179
+ // -----------------------------------------------------------------------
180
+ // Core autonomous execution
181
+ // -----------------------------------------------------------------------
182
+ /**
183
+ * Run a task fully autonomously.
184
+ *
185
+ * The engine will be called repeatedly until:
186
+ * - maxIterations is reached
187
+ * - maxCost is exceeded
188
+ * - maxTimeMs is exceeded
189
+ * - The task appears complete (engine signals no further action needed)
190
+ * - An error occurs and pauseOnError is true
191
+ * - The user aborts
192
+ */
193
+ async executeAuto(prompt, engine, maxIterations) {
194
+ if (this.running) {
195
+ return { ok: false, iterations: 0, finalText: '', error: 'Auto mode is already running a task' };
196
+ }
197
+ const limit = maxIterations ?? this.config.maxIterations;
198
+ this.running = true;
199
+ this.abortController = new AbortController();
200
+ this.startTime = Date.now();
201
+ this.stats = this.freshStats();
202
+ this.emitStatus('running');
203
+ const result = { ok: true, iterations: 0, finalText: '' };
204
+ try {
205
+ let currentPrompt = prompt;
206
+ for (let i = 0; i < limit; i++) {
207
+ // ---- Abort check ----
208
+ if (this.abortController.signal.aborted) {
209
+ result.ok = false;
210
+ result.error = 'Aborted by user';
211
+ break;
212
+ }
213
+ // ---- Time limit ----
214
+ if (this.config.maxTimeMs > 0 && Date.now() - this.startTime > this.config.maxTimeMs) {
215
+ result.error = `Time limit of ${this.config.maxTimeMs}ms exceeded`;
216
+ break;
217
+ }
218
+ // ---- Cost limit ----
219
+ if (this.config.maxCost > 0 && this.stats.totalCost >= this.config.maxCost) {
220
+ result.error = `Cost limit of $${this.config.maxCost} exceeded`;
221
+ break;
222
+ }
223
+ // ---- Safety pre-check ----
224
+ const safetyResult = this.preFlightSafetyCheck(currentPrompt);
225
+ if (!safetyResult.allowed) {
226
+ this.stats.blockedOperations++;
227
+ result.error = `Safety check blocked execution: ${safetyResult.reason}`;
228
+ if (this.config.pauseOnError)
229
+ break;
230
+ // If not pausing on error, try to continue with a modified prompt
231
+ currentPrompt = `The previous action was blocked for safety reasons (${safetyResult.reason}). ` +
232
+ `Please try a different, safer approach to accomplish the goal. Original task: ${prompt}`;
233
+ continue;
234
+ }
235
+ // ---- Execute ----
236
+ try {
237
+ const response = await engine.runPrompt(currentPrompt);
238
+ this.stats.iterations = i + 1;
239
+ this.stats.filesChanged += response.filesChanged;
240
+ this.stats.commandsRun += response.commandsRun;
241
+ this.stats.totalCost += response.cost;
242
+ this.stats.timeElapsedMs = Date.now() - this.startTime;
243
+ if (response.error) {
244
+ this.stats.errors++;
245
+ if (this.config.pauseOnError) {
246
+ result.error = response.error;
247
+ break;
248
+ }
249
+ }
250
+ result.iterations = i + 1;
251
+ result.finalText = response.text;
252
+ // ---- Auto-commit ----
253
+ if (this.config.autoCommit && response.filesChanged > 0) {
254
+ this.autoGitCommit(i + 1);
255
+ }
256
+ // ---- Auto-test ----
257
+ if (this.config.autoTest && response.filesChanged > 0) {
258
+ const testResult = this.autoRunTests();
259
+ if (!testResult.passed && this.config.pauseOnError) {
260
+ result.error = `Tests failed after iteration ${i + 1}`;
261
+ break;
262
+ }
263
+ }
264
+ // ---- Checkpoint ----
265
+ if (i > 0 && i % 5 === 0) {
266
+ this.createCheckpoint(i, undefined, undefined);
267
+ }
268
+ // ---- Detect completion ----
269
+ if (this.detectCompletion(response.text)) {
270
+ result.completed = true;
271
+ break;
272
+ }
273
+ // Feed the response back as context for the next iteration
274
+ currentPrompt =
275
+ `Continuing autonomous task. Previous iteration result:\n` +
276
+ `${response.text.slice(0, 2000)}\n\n` +
277
+ `If the task is complete, respond with "TASK_COMPLETE". ` +
278
+ `Otherwise continue working towards: ${prompt}`;
279
+ }
280
+ catch (err) {
281
+ this.stats.errors++;
282
+ if (this.config.pauseOnError) {
283
+ result.ok = false;
284
+ result.error = `Execution error: ${err instanceof Error ? err.message : String(err)}`;
285
+ break;
286
+ }
287
+ // Otherwise swallow and continue
288
+ }
289
+ }
290
+ }
291
+ finally {
292
+ this.running = false;
293
+ this.abortController = null;
294
+ this.emitStatus(this.config.enabled ? 'enabled' : 'disabled');
295
+ this.persistState();
296
+ }
297
+ return result;
298
+ }
299
+ /** Abort the currently running auto execution */
300
+ abort() {
301
+ if (this.abortController) {
302
+ this.abortController.abort();
303
+ }
304
+ }
305
+ // -----------------------------------------------------------------------
306
+ // /goal command support
307
+ // -----------------------------------------------------------------------
308
+ /** Set a high-level goal and let the agent work towards it autonomously */
309
+ setGoal(name, description, successCriteria = []) {
310
+ const goal = {
311
+ id: randomUUID(),
312
+ name,
313
+ description,
314
+ successCriteria,
315
+ subGoals: [],
316
+ createdAt: new Date().toISOString(),
317
+ status: 'pending',
318
+ progress: 0,
319
+ };
320
+ this.goals.set(goal.id, goal);
321
+ this.persistGoals();
322
+ return goal;
323
+ }
324
+ /** Get a goal by ID */
325
+ getGoal(goalId) {
326
+ return this.goals.get(goalId);
327
+ }
328
+ /** List all goals */
329
+ listGoals() {
330
+ return Array.from(this.goals.values());
331
+ }
332
+ /** Update goal progress */
333
+ updateGoalProgress(goalId, progress, status) {
334
+ const goal = this.goals.get(goalId);
335
+ if (!goal)
336
+ return;
337
+ goal.progress = Math.min(1, Math.max(0, progress));
338
+ if (status)
339
+ goal.status = status;
340
+ if (goal.progress >= 1 && goal.status !== 'failed' && goal.status !== 'cancelled') {
341
+ goal.status = 'completed';
342
+ goal.completedAt = new Date().toISOString();
343
+ }
344
+ this.persistGoals();
345
+ }
346
+ /** Add a sub-goal */
347
+ addSubGoal(parentId, name, description) {
348
+ const parent = this.goals.get(parentId);
349
+ if (!parent)
350
+ return null;
351
+ const sub = {
352
+ id: randomUUID(),
353
+ name,
354
+ description,
355
+ successCriteria: [],
356
+ subGoals: [],
357
+ createdAt: new Date().toISOString(),
358
+ status: 'pending',
359
+ progress: 0,
360
+ };
361
+ parent.subGoals.push(sub);
362
+ this.goals.set(sub.id, sub); // flat index too
363
+ this.persistGoals();
364
+ return sub;
365
+ }
366
+ /** Delete a goal */
367
+ deleteGoal(goalId) {
368
+ if (!this.goals.has(goalId))
369
+ return false;
370
+ // Also remove from any parent's subGoals
371
+ for (const g of Array.from(this.goals.values())) {
372
+ g.subGoals = g.subGoals.filter((s) => s.id !== goalId);
373
+ }
374
+ this.goals.delete(goalId);
375
+ this.persistGoals();
376
+ return true;
377
+ }
378
+ /**
379
+ * Execute a goal autonomously – will iterate until the goal is reached
380
+ * or limits are hit
381
+ */
382
+ async executeGoal(goalId, engine) {
383
+ const goal = this.goals.get(goalId);
384
+ if (!goal)
385
+ return { ok: false, iterations: 0, finalText: '', error: `Goal "${goalId}" not found` };
386
+ goal.status = 'in_progress';
387
+ this.persistGoals();
388
+ const prompt = this.buildGoalPrompt(goal);
389
+ const result = await this.executeAuto(prompt, engine, this.config.maxIterations);
390
+ if (result.completed) {
391
+ goal.status = 'completed';
392
+ goal.progress = 1;
393
+ goal.completedAt = new Date().toISOString();
394
+ }
395
+ else if (result.error) {
396
+ goal.status = 'failed';
397
+ }
398
+ this.persistGoals();
399
+ return result;
400
+ }
401
+ // -----------------------------------------------------------------------
402
+ // /routine command support
403
+ // -----------------------------------------------------------------------
404
+ /** Create a new routine */
405
+ createRoutine(name, description, steps, tags = []) {
406
+ const routine = {
407
+ id: randomUUID(),
408
+ name,
409
+ description,
410
+ steps,
411
+ createdAt: new Date().toISOString(),
412
+ runCount: 0,
413
+ tags,
414
+ };
415
+ this.routines.set(routine.id, routine);
416
+ this.persistRoutines();
417
+ return routine;
418
+ }
419
+ /** Get a routine by ID */
420
+ getRoutine(routineId) {
421
+ return this.routines.get(routineId);
422
+ }
423
+ /** List all routines, optionally filtered by tag */
424
+ listRoutines(tag) {
425
+ const all = Array.from(this.routines.values());
426
+ if (tag)
427
+ return all.filter(r => r.tags.includes(tag));
428
+ return all;
429
+ }
430
+ /** Delete a routine */
431
+ deleteRoutine(routineId) {
432
+ if (!this.routines.has(routineId))
433
+ return false;
434
+ this.routines.delete(routineId);
435
+ this.persistRoutines();
436
+ return true;
437
+ }
438
+ /** Update a routine's steps */
439
+ updateRoutineSteps(routineId, steps) {
440
+ const routine = this.routines.get(routineId);
441
+ if (!routine)
442
+ return false;
443
+ routine.steps = steps;
444
+ this.persistRoutines();
445
+ return true;
446
+ }
447
+ /** Replay a saved routine */
448
+ async executeRoutine(routineId, engine) {
449
+ const routine = this.routines.get(routineId);
450
+ if (!routine)
451
+ return { ok: false, iterations: 0, finalText: '', error: `Routine "${routineId}" not found` };
452
+ if (routine.steps.length === 0)
453
+ return { ok: false, iterations: 0, finalText: '', error: 'Routine has no steps' };
454
+ routine.runCount++;
455
+ routine.lastRunAt = new Date().toISOString();
456
+ this.persistRoutines();
457
+ const overallResult = { ok: true, iterations: 0, finalText: '' };
458
+ const stepResults = [];
459
+ for (let i = 0; i < routine.steps.length; i++) {
460
+ const step = routine.steps[i];
461
+ // Build context from previous steps
462
+ const contextPrompt = stepResults.length > 0
463
+ ? `Context from previous steps:\n${stepResults.map((r, idx) => `Step ${idx + 1}: ${r.slice(0, 500)}`).join('\n')}\n\n`
464
+ : '';
465
+ const fullPrompt = `${contextPrompt}Step ${i + 1}/${routine.steps.length}: ${step.prompt}`;
466
+ const stepMaxIter = step.maxIterations ?? Math.max(5, Math.floor(this.config.maxIterations / routine.steps.length));
467
+ const result = await this.executeAuto(fullPrompt, engine, stepMaxIter);
468
+ overallResult.iterations += result.iterations;
469
+ if (!result.ok) {
470
+ overallResult.ok = false;
471
+ overallResult.error = `Routine failed at step ${i + 1}: ${result.error}`;
472
+ break;
473
+ }
474
+ stepResults.push(result.finalText);
475
+ overallResult.finalText = result.finalText;
476
+ if (step.pauseAfter && i < routine.steps.length - 1) {
477
+ // In auto mode we just log; in interactive mode a real prompt would appear
478
+ // eslint-disable-next-line no-console
479
+ console.log(`[auto-mode] Routine "${routine.name}" paused after step ${i + 1}. Continuing automatically in auto mode.`);
480
+ }
481
+ }
482
+ if (overallResult.ok) {
483
+ overallResult.completed = true;
484
+ }
485
+ return overallResult;
486
+ }
487
+ // -----------------------------------------------------------------------
488
+ // Safety
489
+ // -----------------------------------------------------------------------
490
+ /**
491
+ * Pre-flight safety check – examines the prompt for dangerous commands
492
+ * or patterns that should be blocked even in auto mode.
493
+ */
494
+ preFlightSafetyCheck(prompt) {
495
+ const lower = prompt.toLowerCase();
496
+ // Check always-blocked commands
497
+ for (const cmd of this.config.blockedCommands) {
498
+ if (lower.includes(cmd.toLowerCase())) {
499
+ return { allowed: false, reason: `Blocked command detected: "${cmd}"` };
500
+ }
501
+ }
502
+ // Safety-level-specific checks for conservative / moderate
503
+ if (this.config.safetyLevel === 'conservative') {
504
+ for (const cmd of CONSERVATIVE_BLOCKED_COMMANDS) {
505
+ if (lower.includes(cmd.toLowerCase())) {
506
+ return { allowed: false, reason: `Conservative mode blocked command: "${cmd}"` };
507
+ }
508
+ }
509
+ }
510
+ else if (this.config.safetyLevel === 'moderate') {
511
+ for (const cmd of MODERATE_BLOCKED_COMMANDS) {
512
+ if (lower.includes(cmd.toLowerCase())) {
513
+ return { allowed: false, reason: `Moderate mode blocked command: "${cmd}"` };
514
+ }
515
+ }
516
+ }
517
+ // 'aggressive' – only always-blocked commands are blocked
518
+ // Check blocked file patterns
519
+ for (const pattern of this.config.blockedPatterns) {
520
+ // Simple substring check for path-like strings in prompt
521
+ if (lower.includes(pattern.replace(/\*\*/g, '').replace(/\*/g, '').toLowerCase())) {
522
+ return { allowed: false, reason: `Blocked file pattern detected: "${pattern}"` };
523
+ }
524
+ }
525
+ return { allowed: true };
526
+ }
527
+ /**
528
+ * Runtime safety check for a specific command about to be executed.
529
+ * This is meant to be called by the engine before running any shell command.
530
+ */
531
+ isCommandAllowed(command) {
532
+ const trimmed = command.trim();
533
+ // Always-blocked
534
+ for (const blocked of ALWAYS_BLOCKED_COMMANDS) {
535
+ if (trimmed.includes(blocked) || trimmed.startsWith(blocked.split(' ')[0])) {
536
+ return { allowed: false, reason: `Command blocked (always): "${blocked}"` };
537
+ }
538
+ }
539
+ // Config-level blocked
540
+ for (const blocked of this.config.blockedCommands) {
541
+ if (trimmed.includes(blocked)) {
542
+ return { allowed: false, reason: `Command blocked (config): "${blocked}"` };
543
+ }
544
+ }
545
+ // Safety-level checks
546
+ const levelLists = {
547
+ conservative: CONSERVATIVE_BLOCKED_COMMANDS,
548
+ moderate: MODERATE_BLOCKED_COMMANDS,
549
+ aggressive: [],
550
+ };
551
+ for (const blocked of levelLists[this.config.safetyLevel]) {
552
+ if (trimmed.includes(blocked)) {
553
+ return { allowed: false, reason: `Command blocked (${this.config.safetyLevel}): "${blocked}"` };
554
+ }
555
+ }
556
+ return { allowed: true };
557
+ }
558
+ /**
559
+ * Runtime safety check for a file path about to be modified.
560
+ * This is meant to be called by the engine before any file write.
561
+ */
562
+ isFileModificationAllowed(filePath) {
563
+ const normalised = filePath.replace(/\\/g, '/');
564
+ for (const pattern of this.config.blockedPatterns) {
565
+ if (this.matchesGlobPattern(normalised, pattern)) {
566
+ return { allowed: false, reason: `File pattern blocked: "${pattern}"` };
567
+ }
568
+ }
569
+ // Safety-level-specific patterns
570
+ const patternLists = {
571
+ conservative: CONSERVATIVE_BLOCKED_PATTERNS,
572
+ moderate: [],
573
+ aggressive: [],
574
+ };
575
+ for (const pattern of patternLists[this.config.safetyLevel]) {
576
+ if (this.matchesGlobPattern(normalised, pattern)) {
577
+ return { allowed: false, reason: `File pattern blocked (${this.config.safetyLevel}): "${pattern}"` };
578
+ }
579
+ }
580
+ return { allowed: true };
581
+ }
582
+ // -----------------------------------------------------------------------
583
+ // Statistics
584
+ // -----------------------------------------------------------------------
585
+ getStats() {
586
+ if (this.running) {
587
+ return { ...this.stats, timeElapsedMs: Date.now() - this.startTime };
588
+ }
589
+ return { ...this.stats };
590
+ }
591
+ resetStats() {
592
+ this.stats = this.freshStats();
593
+ }
594
+ // -----------------------------------------------------------------------
595
+ // Checkpoints
596
+ // -----------------------------------------------------------------------
597
+ listCheckpoints() {
598
+ return [...this.checkpoints];
599
+ }
600
+ getLatestCheckpoint() {
601
+ return this.checkpoints.length > 0
602
+ ? this.checkpoints[this.checkpoints.length - 1]
603
+ : undefined;
604
+ }
605
+ /** Create a checkpoint of the current state */
606
+ createCheckpoint(iteration, goalId, routineId) {
607
+ const cp = {
608
+ id: randomUUID(),
609
+ timestamp: new Date().toISOString(),
610
+ iteration,
611
+ goalId,
612
+ routineId,
613
+ snapshot: JSON.stringify({
614
+ stats: this.stats,
615
+ config: { ...this.config, enabled: false }, // don't persist enabled state in snapshot
616
+ }),
617
+ };
618
+ this.checkpoints.push(cp);
619
+ // Persist to disk
620
+ try {
621
+ if (!existsSync(CHECKPOINTS_DIR))
622
+ mkdirSync(CHECKPOINTS_DIR, { recursive: true });
623
+ const cpFile = join(CHECKPOINTS_DIR, `checkpoint-${cp.id}.json`);
624
+ writeFileSync(cpFile, JSON.stringify(cp, null, 2), 'utf-8');
625
+ }
626
+ catch {
627
+ // Best-effort persistence
628
+ }
629
+ // Keep at most 50 checkpoints in memory
630
+ if (this.checkpoints.length > 50) {
631
+ const removed = this.checkpoints.shift();
632
+ if (removed) {
633
+ try {
634
+ const cpFile = join(CHECKPOINTS_DIR, `checkpoint-${removed.id}.json`);
635
+ if (existsSync(cpFile))
636
+ unlinkSync(cpFile);
637
+ }
638
+ catch { /* ignore */ }
639
+ }
640
+ }
641
+ return cp;
642
+ }
643
+ // -----------------------------------------------------------------------
644
+ // Private helpers
645
+ // -----------------------------------------------------------------------
646
+ freshStats() {
647
+ return {
648
+ iterations: 0,
649
+ filesChanged: 0,
650
+ commandsRun: 0,
651
+ timeElapsedMs: 0,
652
+ totalCost: 0,
653
+ blockedOperations: 0,
654
+ errors: 0,
655
+ };
656
+ }
657
+ emitStatus(status) {
658
+ if (this.onStatusChange) {
659
+ this.onStatusChange(status);
660
+ }
661
+ }
662
+ buildBlockedCommands(level) {
663
+ const base = new Set(ALWAYS_BLOCKED_COMMANDS);
664
+ if (level === 'conservative') {
665
+ CONSERVATIVE_BLOCKED_COMMANDS.forEach(c => base.add(c));
666
+ }
667
+ else if (level === 'moderate') {
668
+ MODERATE_BLOCKED_COMMANDS.forEach(c => base.add(c));
669
+ }
670
+ // Merge user-defined on top
671
+ this.config.blockedCommands.forEach(c => base.add(c));
672
+ return Array.from(base);
673
+ }
674
+ buildBlockedPatterns(level) {
675
+ const base = new Set(ALWAYS_BLOCKED_PATTERNS);
676
+ if (level === 'conservative') {
677
+ CONSERVATIVE_BLOCKED_PATTERNS.forEach(p => base.add(p));
678
+ }
679
+ this.config.blockedPatterns.forEach(p => base.add(p));
680
+ return Array.from(base);
681
+ }
682
+ detectCompletion(text) {
683
+ const markers = [
684
+ 'TASK_COMPLETE',
685
+ 'task complete',
686
+ 'task is complete',
687
+ 'goal achieved',
688
+ 'goal reached',
689
+ 'all done',
690
+ 'finished successfully',
691
+ ];
692
+ const lower = text.toLowerCase();
693
+ return markers.some(m => lower.includes(m));
694
+ }
695
+ buildGoalPrompt(goal) {
696
+ const criteria = goal.successCriteria.length > 0
697
+ ? `\n\nSuccess criteria:\n${goal.successCriteria.map((c, i) => `${i + 1}. ${c}`).join('\n')}`
698
+ : '';
699
+ const subGoals = goal.subGoals.length > 0
700
+ ? `\n\nSub-goals:\n${goal.subGoals.map((sg, i) => `${i + 1}. ${sg.name}: ${sg.description}`).join('\n')}`
701
+ : '';
702
+ return (`Goal: ${goal.name}\n\n` +
703
+ `Description: ${goal.description}${criteria}${subGoals}\n\n` +
704
+ `Work towards this goal autonomously. When the goal is achieved, respond with "TASK_COMPLETE".`);
705
+ }
706
+ autoGitCommit(iteration) {
707
+ try {
708
+ execSync('git add -A', { stdio: 'pipe', timeout: 10_000 });
709
+ execSync(`git commit -m "auto-mode: iteration ${iteration}" --allow-empty`, {
710
+ stdio: 'pipe',
711
+ timeout: 10_000,
712
+ });
713
+ }
714
+ catch {
715
+ // Not a git repo, or nothing to commit – ignore
716
+ }
717
+ }
718
+ autoRunTests() {
719
+ // Try common test commands
720
+ const testCommands = ['npm test', 'bun test', 'yarn test', 'pnpm test'];
721
+ for (const cmd of testCommands) {
722
+ try {
723
+ const output = execSync(cmd, { stdio: 'pipe', timeout: 60_000 }).toString('utf-8');
724
+ return { passed: true, output };
725
+ }
726
+ catch (err) {
727
+ // execSync throws on non-zero exit
728
+ if (err instanceof Error && 'stdout' in err) {
729
+ const output = String(err.stdout);
730
+ return { passed: false, output };
731
+ }
732
+ // Command not found – try the next one
733
+ continue;
734
+ }
735
+ }
736
+ return { passed: true, output: 'No test runner found' };
737
+ }
738
+ matchesGlobPattern(filePath, pattern) {
739
+ // Convert simple glob to regex
740
+ const regexStr = pattern
741
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&') // escape regex special chars (except * and ?)
742
+ .replace(/\*\*/g, '{{GLOBSTAR}}') // preserve **
743
+ .replace(/\*/g, '[^/]*') // * matches anything except /
744
+ .replace(/\?/g, '[^/]') // ? matches single non-/
745
+ .replace(/\{\{GLOBSTAR\}\}/g, '.*'); // ** matches anything
746
+ try {
747
+ const regex = new RegExp(regexStr + '$', 'i');
748
+ return regex.test(filePath);
749
+ }
750
+ catch {
751
+ return false;
752
+ }
753
+ }
754
+ // -----------------------------------------------------------------------
755
+ // Persistence
756
+ // -----------------------------------------------------------------------
757
+ persistState() {
758
+ try {
759
+ if (!existsSync(NEURO_DIR))
760
+ mkdirSync(NEURO_DIR, { recursive: true });
761
+ writeFileSync(AUTO_STATE_FILE, JSON.stringify({
762
+ config: this.config,
763
+ stats: this.stats,
764
+ }, null, 2), 'utf-8');
765
+ }
766
+ catch { /* best effort */ }
767
+ }
768
+ loadState() {
769
+ try {
770
+ if (existsSync(AUTO_STATE_FILE)) {
771
+ const data = JSON.parse(readFileSync(AUTO_STATE_FILE, 'utf-8'));
772
+ if (data.config)
773
+ Object.assign(this.config, data.config);
774
+ if (data.stats)
775
+ Object.assign(this.stats, data.stats);
776
+ }
777
+ }
778
+ catch { /* ignore corrupt state */ }
779
+ // Load goals
780
+ try {
781
+ if (existsSync(GOALS_FILE)) {
782
+ const data = JSON.parse(readFileSync(GOALS_FILE, 'utf-8'));
783
+ for (const g of data)
784
+ this.goals.set(g.id, g);
785
+ }
786
+ }
787
+ catch { /* ignore */ }
788
+ // Load routines
789
+ try {
790
+ if (existsSync(ROUTINES_FILE)) {
791
+ const data = JSON.parse(readFileSync(ROUTINES_FILE, 'utf-8'));
792
+ for (const r of data)
793
+ this.routines.set(r.id, r);
794
+ }
795
+ }
796
+ catch { /* ignore */ }
797
+ // Load checkpoints from disk
798
+ try {
799
+ if (existsSync(CHECKPOINTS_DIR)) {
800
+ const files = readdirSync(CHECKPOINTS_DIR).filter(f => f.startsWith('checkpoint-'));
801
+ for (const f of files.slice(-50)) {
802
+ try {
803
+ const cp = JSON.parse(readFileSync(join(CHECKPOINTS_DIR, f), 'utf-8'));
804
+ this.checkpoints.push(cp);
805
+ }
806
+ catch { /* skip corrupt */ }
807
+ }
808
+ // Sort by timestamp
809
+ this.checkpoints.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
810
+ }
811
+ }
812
+ catch { /* ignore */ }
813
+ }
814
+ persistGoals() {
815
+ try {
816
+ if (!existsSync(NEURO_DIR))
817
+ mkdirSync(NEURO_DIR, { recursive: true });
818
+ writeFileSync(GOALS_FILE, JSON.stringify(Array.from(this.goals.values()), null, 2), 'utf-8');
819
+ }
820
+ catch { /* best effort */ }
821
+ }
822
+ persistRoutines() {
823
+ try {
824
+ if (!existsSync(NEURO_DIR))
825
+ mkdirSync(NEURO_DIR, { recursive: true });
826
+ writeFileSync(ROUTINES_FILE, JSON.stringify(Array.from(this.routines.values()), null, 2), 'utf-8');
827
+ }
828
+ catch { /* best effort */ }
829
+ }
830
+ }
831
+ //# sourceMappingURL=auto-mode.js.map