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,49 @@
1
+ import { CompleterResult } from 'readline';
2
+ export declare class CompletionEngine {
3
+ private slashCommands;
4
+ private modelIds;
5
+ private agentNames;
6
+ private cwd;
7
+ private commandHistory;
8
+ private maxHistory;
9
+ private historyPath;
10
+ private permissionModes;
11
+ private themes;
12
+ private mcpSubcommands;
13
+ private skillSubcommands;
14
+ private cacheSubcommands;
15
+ private ignoreSubcommands;
16
+ private styleNames;
17
+ private thinkingModes;
18
+ private effortLevels;
19
+ private telemetrySubcommands;
20
+ private syncSubcommands;
21
+ private voiceSubcommands;
22
+ private locales;
23
+ private serverSubcommands;
24
+ private dashboardSubcommands;
25
+ private fileExtensions;
26
+ constructor(cwd?: string);
27
+ setAgentNames(names: string[]): void;
28
+ addHistory(command: string): void;
29
+ private loadHistory;
30
+ private saveHistory;
31
+ getHistory(): string[];
32
+ searchHistory(prefix: string): string[];
33
+ complete: (line: string) => CompleterResult;
34
+ private completeSlashCommand;
35
+ private completeModel;
36
+ private completeAgent;
37
+ private completeFilePath;
38
+ private completeSessionId;
39
+ private looksLikeFilePath;
40
+ /**
41
+ * Display completion suggestions nicely
42
+ */
43
+ static displaySuggestions(suggestions: string[]): void;
44
+ /**
45
+ * Get contextual help for a partial command
46
+ */
47
+ getContextualHelp(partial: string): string | null;
48
+ }
49
+ //# sourceMappingURL=completion.d.ts.map
@@ -0,0 +1,384 @@
1
+ // ============================================================
2
+ // NeuroCLI - Enhanced Tab Completion System
3
+ // Slash commands, file paths, model names, agent names,
4
+ // command history, context-aware suggestions
5
+ // ============================================================
6
+ import { readdirSync, statSync, existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
7
+ import { join, basename, dirname } from 'path';
8
+ import { homedir } from 'os';
9
+ import { MODELS } from '../api/models.js';
10
+ export class CompletionEngine {
11
+ slashCommands = new Map();
12
+ modelIds = [];
13
+ agentNames = [];
14
+ cwd;
15
+ commandHistory = [];
16
+ maxHistory = 500;
17
+ historyPath;
18
+ permissionModes = ['manual', 'auto', 'plan', 'yolo'];
19
+ themes = ['dracula', 'dark', 'nord', 'light'];
20
+ mcpSubcommands = ['list', 'add', 'remove', 'connect', 'disconnect', 'health'];
21
+ skillSubcommands = ['list', 'activate', 'deactivate', 'clear'];
22
+ cacheSubcommands = ['on', 'off', 'clear', 'stats'];
23
+ ignoreSubcommands = ['list', 'add', 'check'];
24
+ styleNames = ['default', 'concise', 'explanatory', 'learning', 'narrative', 'technical', 'review', 'debug'];
25
+ thinkingModes = ['none', 'brief', 'full', 'ultrathink'];
26
+ effortLevels = ['low', 'medium', 'high', 'ultrathink'];
27
+ // P2/P3 new completion lists
28
+ telemetrySubcommands = ['on', 'off', 'status', 'export', 'clear'];
29
+ syncSubcommands = ['push', 'pull', 'status', 'config'];
30
+ voiceSubcommands = ['on', 'off', 'status', 'speak', 'listen'];
31
+ locales = ['en', 'tr', 'zh', 'ja', 'es'];
32
+ serverSubcommands = ['start', 'stop', 'status'];
33
+ dashboardSubcommands = ['start', 'stop', 'status'];
34
+ fileExtensions = new Map([
35
+ ['typescript', ['.ts', '.tsx', '.d.ts']],
36
+ ['javascript', ['.js', '.jsx', '.mjs', '.cjs']],
37
+ ['python', ['.py', '.pyi', '.pyw']],
38
+ ['rust', ['.rs']],
39
+ ['go', ['.go']],
40
+ ['java', ['.java']],
41
+ ['config', ['.json', '.yaml', '.yml', '.toml', '.env']],
42
+ ['web', ['.html', '.css', '.scss', '.vue', '.svelte']],
43
+ ['docs', ['.md', '.txt', '.rst']],
44
+ ]);
45
+ constructor(cwd) {
46
+ this.cwd = cwd || process.cwd();
47
+ this.modelIds = Object.keys(MODELS);
48
+ this.historyPath = join(homedir(), '.neuro', 'history');
49
+ // Built-in slash commands with descriptions (v3.0 expanded)
50
+ const commands = [
51
+ ['help', 'Show help message'],
52
+ ['model', 'Switch or list models'],
53
+ ['agent', 'Switch or list agents'],
54
+ ['auto', 'Auto mode (smart orchestration)'],
55
+ ['orchestrate', 'Multi-agent orchestration mode'],
56
+ ['plan', 'Plan mode (read-only)'],
57
+ ['direct', 'Direct agent mode'],
58
+ ['plan-mode', 'Plan mode (read-only, no modifications)'],
59
+ ['stats', 'Show session statistics'],
60
+ ['theme', 'Switch UI theme'],
61
+ ['clear', 'Clear terminal'],
62
+ ['exit', 'Exit NeuroCLI'],
63
+ ['quit', 'Exit NeuroCLI'],
64
+ ['resume', 'Resume a previous session'],
65
+ ['compact', 'Compact conversation context'],
66
+ ['undo', 'Undo last change'],
67
+ ['redo', 'Redo undone change'],
68
+ ['rewind', 'Rewind n changes'],
69
+ ['mcp', 'Manage MCP servers'],
70
+ ['fork', 'Fork current session'],
71
+ ['init', 'Initialize NEURO.md for this project'],
72
+ ['permission', 'Cycle or set permission mode'],
73
+ ['perm', 'Alias for /permission'],
74
+ ['doctor', 'Health check'],
75
+ ['export', 'Export current session as JSON'],
76
+ ['import', 'Import a session from JSON file'],
77
+ ['sandbox', 'Toggle sandbox mode'],
78
+ ['whitelist', 'Manage tool whitelist'],
79
+ ['blacklist', 'Manage tool blacklist'],
80
+ // v3.0 new commands
81
+ ['style', 'Switch output style (concise, explanatory, learning, etc.)'],
82
+ ['thinking', 'Toggle thinking mode (none|brief|full|ultrathink)'],
83
+ ['effort', 'Set effort level (low|medium|high|ultrathink)'],
84
+ ['skills', 'Manage skills (list|activate|deactivate|clear)'],
85
+ ['cache', 'Manage prompt cache (on|off|clear|stats)'],
86
+ ['spending', 'Show detailed spending report'],
87
+ ['ignore', 'Manage .neuroignore rules'],
88
+ ['ollama', 'List Ollama local models'],
89
+ ['cost', 'Show spending and cache report'],
90
+ ['commit-push-pr', 'Commit + push + create PR'],
91
+ ['code-review', 'Multi-agent code review'],
92
+ ['feedback', 'Give feedback'],
93
+ // P2 new commands
94
+ ['telemetry', 'Manage telemetry (on|off|status|export|clear)'],
95
+ ['vim', 'Toggle vim keybindings mode'],
96
+ ['lang', 'Switch language (en|tr|zh|ja|es)'],
97
+ ['voice', 'Manage voice I/O (on|off|status|speak|listen)'],
98
+ ['image', 'Analyze an image file'],
99
+ // P3 new commands
100
+ ['server', 'Manage API server (start|stop|status)'],
101
+ ['sync', 'Cloud sync (push|pull|status|config)'],
102
+ ['dashboard', 'Manage web dashboard (start|stop|status)'],
103
+ ];
104
+ for (const [cmd, desc] of commands) {
105
+ this.slashCommands.set(cmd, desc);
106
+ }
107
+ this.loadHistory();
108
+ }
109
+ setAgentNames(names) {
110
+ this.agentNames = names;
111
+ }
112
+ // --- Command History ---
113
+ addHistory(command) {
114
+ if (!command.trim())
115
+ return;
116
+ // Don't add duplicates at the end
117
+ if (this.commandHistory.length > 0 && this.commandHistory[this.commandHistory.length - 1] === command)
118
+ return;
119
+ this.commandHistory.push(command);
120
+ if (this.commandHistory.length > this.maxHistory) {
121
+ this.commandHistory = this.commandHistory.slice(-this.maxHistory);
122
+ }
123
+ this.saveHistory();
124
+ }
125
+ loadHistory() {
126
+ try {
127
+ if (existsSync(this.historyPath)) {
128
+ const data = readFileSync(this.historyPath, 'utf-8');
129
+ this.commandHistory = data.split('\n').filter(Boolean).slice(-this.maxHistory);
130
+ }
131
+ }
132
+ catch { /* Ignore */ }
133
+ }
134
+ saveHistory() {
135
+ try {
136
+ const dir = dirname(this.historyPath);
137
+ if (!existsSync(dir))
138
+ mkdirSync(dir, { recursive: true });
139
+ writeFileSync(this.historyPath, this.commandHistory.join('\n'), 'utf-8');
140
+ }
141
+ catch { /* Ignore */ }
142
+ }
143
+ getHistory() {
144
+ return [...this.commandHistory];
145
+ }
146
+ searchHistory(prefix) {
147
+ return this.commandHistory.filter(cmd => cmd.startsWith(prefix)).slice(-10);
148
+ }
149
+ // --- Main Completion Handler ---
150
+ complete = (line) => {
151
+ // Slash command completion
152
+ if (line.startsWith('/')) {
153
+ return this.completeSlashCommand(line);
154
+ }
155
+ // File path completion (for paths starting with ./ or / or ~)
156
+ if (this.looksLikeFilePath(line)) {
157
+ return this.completeFilePath(line);
158
+ }
159
+ // @-mention for agents
160
+ if (line.startsWith('@')) {
161
+ return this.completeAgent(line);
162
+ }
163
+ // History-based completion (if not empty)
164
+ if (line.length > 0) {
165
+ const historyMatches = this.searchHistory(line);
166
+ if (historyMatches.length > 0) {
167
+ return [historyMatches, line];
168
+ }
169
+ }
170
+ // Default: try slash commands
171
+ return this.completeSlashCommand('/' + line);
172
+ };
173
+ completeSlashCommand(line) {
174
+ const partial = line.slice(1).toLowerCase();
175
+ const parts = partial.split(/\s+/);
176
+ // First part: command name
177
+ if (parts.length === 1) {
178
+ const matches = Array.from(this.slashCommands.keys())
179
+ .filter(cmd => cmd.startsWith(partial))
180
+ .map(cmd => '/' + cmd + ' ');
181
+ if (matches.length === 1) {
182
+ const cmd = matches[0].trim().slice(1);
183
+ const desc = this.slashCommands.get(cmd);
184
+ return [matches, line];
185
+ }
186
+ return [matches.length > 0 ? matches : Array.from(this.slashCommands.keys()).map(c => '/' + c + ' '), line];
187
+ }
188
+ // Second part: context-specific completion
189
+ const command = parts[0];
190
+ const argPartial = parts.slice(1).join(' ');
191
+ switch (command) {
192
+ case 'model':
193
+ return this.completeModel(argPartial, line);
194
+ case 'agent':
195
+ return [this.agentNames.filter(a => a.toLowerCase().startsWith(argPartial.toLowerCase())), line];
196
+ case 'theme':
197
+ return [this.themes.filter(t => t.startsWith(argPartial)), line];
198
+ case 'resume':
199
+ return this.completeSessionId(argPartial, line);
200
+ case 'mcp':
201
+ if (parts.length === 2) {
202
+ return [this.mcpSubcommands.filter(c => c.startsWith(argPartial)), line];
203
+ }
204
+ return [[], line];
205
+ case 'permission':
206
+ case 'perm':
207
+ return [this.permissionModes.filter(m => m.startsWith(argPartial)), line];
208
+ case 'whitelist':
209
+ case 'blacklist':
210
+ return [[], line]; // TODO: complete tool names
211
+ case 'import':
212
+ return this.completeFilePath(argPartial || './');
213
+ // v3.0 new command completions
214
+ case 'style':
215
+ return [this.styleNames.filter(s => s.startsWith(argPartial)), line];
216
+ case 'thinking':
217
+ if (argPartial === 'toggle')
218
+ return [['toggle'], line];
219
+ return [this.thinkingModes.filter(m => m.startsWith(argPartial)), line];
220
+ case 'effort':
221
+ return [this.effortLevels.filter(l => l.startsWith(argPartial)), line];
222
+ case 'skills':
223
+ if (parts.length === 2) {
224
+ return [this.skillSubcommands.filter(c => c.startsWith(argPartial)), line];
225
+ }
226
+ return [[], line];
227
+ case 'cache':
228
+ if (parts.length === 2) {
229
+ return [this.cacheSubcommands.filter(c => c.startsWith(argPartial)), line];
230
+ }
231
+ return [[], line];
232
+ case 'ignore':
233
+ if (parts.length === 2) {
234
+ return [this.ignoreSubcommands.filter(c => c.startsWith(argPartial)), line];
235
+ }
236
+ return [[], line];
237
+ // P2/P3 new command completions
238
+ case 'telemetry':
239
+ if (parts.length === 2) {
240
+ return [this.telemetrySubcommands.filter(c => c.startsWith(argPartial)), line];
241
+ }
242
+ return [[], line];
243
+ case 'vim':
244
+ return [['on', 'off'].filter(v => v.startsWith(argPartial)), line];
245
+ case 'lang':
246
+ return [this.locales.filter(l => l.startsWith(argPartial)), line];
247
+ case 'voice':
248
+ if (parts.length === 2) {
249
+ return [this.voiceSubcommands.filter(c => c.startsWith(argPartial)), line];
250
+ }
251
+ return [[], line];
252
+ case 'image':
253
+ return this.completeFilePath(argPartial || './');
254
+ case 'server':
255
+ if (parts.length === 2) {
256
+ return [this.serverSubcommands.filter(c => c.startsWith(argPartial)), line];
257
+ }
258
+ return [[], line];
259
+ case 'sync':
260
+ if (parts.length === 2) {
261
+ return [this.syncSubcommands.filter(c => c.startsWith(argPartial)), line];
262
+ }
263
+ return [[], line];
264
+ case 'dashboard':
265
+ if (parts.length === 2) {
266
+ return [this.dashboardSubcommands.filter(c => c.startsWith(argPartial)), line];
267
+ }
268
+ return [[], line];
269
+ default:
270
+ return [[], line];
271
+ }
272
+ }
273
+ completeModel(partial, line) {
274
+ // Match by ID or name
275
+ const matches = this.modelIds.filter(m => m.startsWith(partial) ||
276
+ MODELS[m]?.name.toLowerCase().includes(partial.toLowerCase()) ||
277
+ m.split('/')[1]?.split(':')[0].startsWith(partial));
278
+ // Also categorize
279
+ if (partial === 'free' || partial === 'free-') {
280
+ const freeMatches = this.modelIds.filter(m => m.includes(':free'));
281
+ return [freeMatches, line];
282
+ }
283
+ return [matches.length > 0 ? matches : this.modelIds, line];
284
+ }
285
+ completeAgent(line) {
286
+ const partial = line.slice(1).toLowerCase();
287
+ return [this.agentNames.filter(a => a.toLowerCase().startsWith(partial)), line];
288
+ }
289
+ completeFilePath(line) {
290
+ let dirPath;
291
+ let filePrefix;
292
+ if (line.startsWith('~/')) {
293
+ dirPath = join(homedir(), dirname(line.slice(2)));
294
+ filePrefix = basename(line);
295
+ }
296
+ else if (line.startsWith('/')) {
297
+ dirPath = dirname(line);
298
+ filePrefix = basename(line);
299
+ }
300
+ else if (line.startsWith('./')) {
301
+ dirPath = join(this.cwd, dirname(line.slice(2)));
302
+ filePrefix = basename(line);
303
+ }
304
+ else {
305
+ dirPath = this.cwd;
306
+ filePrefix = line;
307
+ }
308
+ try {
309
+ if (!existsSync(dirPath))
310
+ return [[], line];
311
+ const entries = readdirSync(dirPath);
312
+ const matches = entries
313
+ .filter(e => e.toLowerCase().startsWith(filePrefix.toLowerCase()))
314
+ .map(e => {
315
+ const fullPath = join(dirPath, e);
316
+ try {
317
+ const stat = statSync(fullPath);
318
+ return stat.isDirectory() ? e + '/' : e;
319
+ }
320
+ catch {
321
+ return e;
322
+ }
323
+ });
324
+ return [matches, line];
325
+ }
326
+ catch {
327
+ return [[], line];
328
+ }
329
+ }
330
+ completeSessionId(partial, line) {
331
+ const suggestions = ['latest'];
332
+ if (partial) {
333
+ try {
334
+ const sessionDir = join(homedir(), '.neuro', 'sessions');
335
+ if (existsSync(sessionDir)) {
336
+ const files = readdirSync(sessionDir)
337
+ .filter(f => f.endsWith('.json'))
338
+ .map(f => f.replace('.json', ''))
339
+ .filter(f => f.startsWith(partial));
340
+ suggestions.push(...files.slice(0, 5));
341
+ }
342
+ }
343
+ catch { }
344
+ }
345
+ return [suggestions.filter(s => s.startsWith(partial)), line];
346
+ }
347
+ looksLikeFilePath(line) {
348
+ return line.startsWith('./') || line.startsWith('/') || line.startsWith('~/') ||
349
+ (line.includes('/') && !line.startsWith('/'));
350
+ }
351
+ /**
352
+ * Display completion suggestions nicely
353
+ */
354
+ static displaySuggestions(suggestions) {
355
+ if (suggestions.length === 0)
356
+ return;
357
+ if (suggestions.length === 1)
358
+ return;
359
+ console.log();
360
+ const cols = Math.min(process.stdout.columns || 80, 100);
361
+ const maxLen = Math.max(...suggestions.map(s => s.length)) + 2;
362
+ const perRow = Math.floor(cols / maxLen) || 1;
363
+ let row = '';
364
+ for (let i = 0; i < suggestions.length; i++) {
365
+ row += suggestions[i].padEnd(maxLen);
366
+ if ((i + 1) % perRow === 0) {
367
+ console.log(row);
368
+ row = '';
369
+ }
370
+ }
371
+ if (row)
372
+ console.log(row);
373
+ }
374
+ /**
375
+ * Get contextual help for a partial command
376
+ */
377
+ getContextualHelp(partial) {
378
+ if (!partial.startsWith('/'))
379
+ return null;
380
+ const cmd = partial.slice(1).split(' ')[0];
381
+ return this.slashCommands.get(cmd) || null;
382
+ }
383
+ }
384
+ //# sourceMappingURL=completion.js.map
@@ -0,0 +1,38 @@
1
+ import { Message } from '../core/types.js';
2
+ export interface ContextSummary {
3
+ totalMessages: number;
4
+ totalTokens: number;
5
+ systemTokens: number;
6
+ conversationTokens: number;
7
+ availableTokens: number;
8
+ truncationNeeded: boolean;
9
+ }
10
+ export declare class ContextManager {
11
+ private modelId;
12
+ private maxContextTokens;
13
+ private systemPromptRatio;
14
+ private reservedOutputTokens;
15
+ constructor(modelId: string, maxContextTokens?: number, systemPromptRatio?: number, reservedOutputRatio?: number);
16
+ get maxInputTokens(): number;
17
+ /**
18
+ * Analyze the current context
19
+ */
20
+ analyze(messages: Message[]): ContextSummary;
21
+ /**
22
+ * Count total tokens in messages
23
+ */
24
+ countTokens(messages: Message[]): number;
25
+ /**
26
+ * Manage context window - truncate if needed
27
+ */
28
+ manage(messages: Message[]): Message[];
29
+ /**
30
+ * Create a summary of old messages
31
+ */
32
+ private createSummary;
33
+ /**
34
+ * Build the final message array for an API call
35
+ */
36
+ buildMessages(systemPrompt: string, conversation: Message[], injectedContext?: string): Message[];
37
+ }
38
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1,144 @@
1
+ // ============================================================
2
+ // NeuroCLI - Context Manager
3
+ // Smart context window management
4
+ // ============================================================
5
+ import { MODELS } from '../api/models.js';
6
+ // Simple token estimation (≈4 chars per token for English, ≈2 for CJK)
7
+ function estimateTokens(text) {
8
+ const cjkChars = (text.match(/[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/g) || []).length;
9
+ const otherChars = text.length - cjkChars;
10
+ return Math.ceil(cjkChars / 2 + otherChars / 4);
11
+ }
12
+ export class ContextManager {
13
+ modelId;
14
+ maxContextTokens;
15
+ systemPromptRatio;
16
+ reservedOutputTokens;
17
+ constructor(modelId, maxContextTokens, systemPromptRatio = 0.15, reservedOutputRatio = 0.3) {
18
+ this.modelId = modelId;
19
+ const model = MODELS[modelId];
20
+ this.maxContextTokens = maxContextTokens || (model?.contextWindow || 128000);
21
+ this.systemPromptRatio = systemPromptRatio;
22
+ this.reservedOutputTokens = Math.floor(this.maxContextTokens * reservedOutputRatio);
23
+ }
24
+ get maxInputTokens() {
25
+ return this.maxContextTokens - this.reservedOutputTokens;
26
+ }
27
+ /**
28
+ * Analyze the current context
29
+ */
30
+ analyze(messages) {
31
+ const totalTokens = this.countTokens(messages);
32
+ const systemTokens = messages
33
+ .filter(m => m.role === 'system')
34
+ .reduce((sum, m) => sum + estimateTokens(m.content), 0);
35
+ return {
36
+ totalMessages: messages.length,
37
+ totalTokens,
38
+ systemTokens,
39
+ conversationTokens: totalTokens - systemTokens,
40
+ availableTokens: this.maxInputTokens - totalTokens,
41
+ truncationNeeded: totalTokens > this.maxInputTokens,
42
+ };
43
+ }
44
+ /**
45
+ * Count total tokens in messages
46
+ */
47
+ countTokens(messages) {
48
+ return messages.reduce((sum, m) => {
49
+ let msgTokens = estimateTokens(m.content);
50
+ if (m.toolCalls) {
51
+ for (const tc of m.toolCalls) {
52
+ msgTokens += estimateTokens(tc.function.name + tc.function.arguments);
53
+ }
54
+ }
55
+ return sum + msgTokens;
56
+ }, 0);
57
+ }
58
+ /**
59
+ * Manage context window - truncate if needed
60
+ */
61
+ manage(messages) {
62
+ const summary = this.analyze(messages);
63
+ if (!summary.truncationNeeded) {
64
+ return messages;
65
+ }
66
+ // Strategy: Keep system prompt + recent messages, summarize older ones
67
+ const result = [];
68
+ const systemMessages = messages.filter(m => m.role === 'system');
69
+ result.push(...systemMessages);
70
+ // Calculate how many tokens we need to remove
71
+ const tokensToRemove = summary.totalTokens - this.maxInputTokens + 500; // buffer
72
+ // Add a context summary of removed messages
73
+ const nonSystemMessages = messages.filter(m => m.role !== 'system');
74
+ let removedTokens = 0;
75
+ let cutoffIndex = 0;
76
+ for (let i = 0; i < nonSystemMessages.length; i++) {
77
+ const msgTokens = estimateTokens(nonSystemMessages[i].content);
78
+ if (removedTokens + msgTokens > tokensToRemove) {
79
+ cutoffIndex = i;
80
+ break;
81
+ }
82
+ removedTokens += msgTokens;
83
+ }
84
+ if (cutoffIndex > 0) {
85
+ // Create a summary of the removed messages
86
+ const removedMessages = nonSystemMessages.slice(0, cutoffIndex);
87
+ const summaryContent = this.createSummary(removedMessages);
88
+ result.push({
89
+ role: 'system',
90
+ content: `## Previous Context Summary\n${summaryContent}`,
91
+ timestamp: Date.now(),
92
+ });
93
+ }
94
+ // Add remaining messages
95
+ result.push(...nonSystemMessages.slice(cutoffIndex));
96
+ return result;
97
+ }
98
+ /**
99
+ * Create a summary of old messages
100
+ */
101
+ createSummary(messages) {
102
+ const parts = [];
103
+ for (const msg of messages) {
104
+ const preview = msg.content.length > 200
105
+ ? msg.content.slice(0, 200) + '...'
106
+ : msg.content;
107
+ switch (msg.role) {
108
+ case 'user':
109
+ parts.push(`[User asked]: ${preview}`);
110
+ break;
111
+ case 'assistant':
112
+ if (msg.toolCalls?.length) {
113
+ parts.push(`[Assistant called tools: ${msg.toolCalls.map(tc => tc.function.name).join(', ')}]`);
114
+ }
115
+ else {
116
+ parts.push(`[Assistant responded]: ${preview}`);
117
+ }
118
+ break;
119
+ case 'tool':
120
+ parts.push(`[Tool result]: ${preview}`);
121
+ break;
122
+ }
123
+ }
124
+ return parts.join('\n');
125
+ }
126
+ /**
127
+ * Build the final message array for an API call
128
+ */
129
+ buildMessages(systemPrompt, conversation, injectedContext) {
130
+ const messages = [
131
+ { role: 'system', content: systemPrompt, timestamp: Date.now() },
132
+ ];
133
+ if (injectedContext) {
134
+ messages.push({
135
+ role: 'system',
136
+ content: `## Project Context\n${injectedContext}`,
137
+ timestamp: Date.now(),
138
+ });
139
+ }
140
+ messages.push(...conversation);
141
+ return this.manage(messages);
142
+ }
143
+ }
144
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1,35 @@
1
+ export interface DiffLine {
2
+ type: 'add' | 'remove' | 'context';
3
+ content: string;
4
+ lineNumber?: number;
5
+ }
6
+ export interface FileDiff {
7
+ filePath: string;
8
+ added: number;
9
+ removed: number;
10
+ lines: DiffLine[];
11
+ }
12
+ export declare class DiffPreview {
13
+ /**
14
+ * Create a diff preview between old content and new content
15
+ */
16
+ static createDiff(oldContent: string, newContent: string, filePath: string): FileDiff;
17
+ /**
18
+ * Create diff for edit_file operation (old_text -> new_text replacement)
19
+ */
20
+ static createEditDiff(filePath: string, oldText: string, newText: string): FileDiff | null;
21
+ /**
22
+ * Render diff to terminal with colors
23
+ */
24
+ static renderDiff(diff: FileDiff, contextLines?: number): void;
25
+ /**
26
+ * Render a compact summary of multiple diffs
27
+ */
28
+ static renderSummary(diffs: FileDiff[]): void;
29
+ /**
30
+ * Ask user to confirm diff changes
31
+ */
32
+ static confirmDiff(diff: FileDiff): Promise<boolean>;
33
+ private static lcs;
34
+ }
35
+ //# sourceMappingURL=diff-preview.d.ts.map