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.
- package/LICENSE +21 -0
- package/README.md +510 -0
- package/dist/advisor/advisor.d.ts +50 -0
- package/dist/advisor/advisor.js +178 -0
- package/dist/agents/base.d.ts +62 -0
- package/dist/agents/base.js +215 -0
- package/dist/agents/orchestrator.d.ts +46 -0
- package/dist/agents/orchestrator.js +192 -0
- package/dist/agents/team.d.ts +51 -0
- package/dist/agents/team.js +210 -0
- package/dist/api/models.d.ts +23 -0
- package/dist/api/models.js +514 -0
- package/dist/api/ollama.d.ts +153 -0
- package/dist/api/ollama.js +751 -0
- package/dist/api/openrouter.d.ts +55 -0
- package/dist/api/openrouter.js +223 -0
- package/dist/commands/commands.d.ts +43 -0
- package/dist/commands/commands.js +308 -0
- package/dist/config/config.d.ts +8 -0
- package/dist/config/config.js +311 -0
- package/dist/context/compaction.d.ts +54 -0
- package/dist/context/compaction.js +251 -0
- package/dist/context/custom-agents.d.ts +107 -0
- package/dist/context/custom-agents.js +397 -0
- package/dist/context/custom-tools.d.ts +120 -0
- package/dist/context/custom-tools.js +564 -0
- package/dist/context/git-checkpoint.d.ts +52 -0
- package/dist/context/git-checkpoint.js +240 -0
- package/dist/context/neuro-md.d.ts +48 -0
- package/dist/context/neuro-md.js +202 -0
- package/dist/context/neuroignore.d.ts +102 -0
- package/dist/context/neuroignore.js +441 -0
- package/dist/context/repo-map.d.ts +38 -0
- package/dist/context/repo-map.js +220 -0
- package/dist/context/skill-standard.d.ts +262 -0
- package/dist/context/skill-standard.js +1156 -0
- package/dist/context/skill-system.d.ts +75 -0
- package/dist/context/skill-system.js +578 -0
- package/dist/context/tree-sitter.d.ts +99 -0
- package/dist/context/tree-sitter.js +1956 -0
- package/dist/core/acp.d.ts +325 -0
- package/dist/core/acp.js +1498 -0
- package/dist/core/api-server.d.ts +143 -0
- package/dist/core/api-server.js +550 -0
- package/dist/core/approval.d.ts +81 -0
- package/dist/core/approval.js +432 -0
- package/dist/core/auto-compact.d.ts +127 -0
- package/dist/core/auto-compact.js +436 -0
- package/dist/core/auto-mode.d.ts +232 -0
- package/dist/core/auto-mode.js +831 -0
- package/dist/core/background-session.d.ts +166 -0
- package/dist/core/background-session.js +696 -0
- package/dist/core/cicd.d.ts +257 -0
- package/dist/core/cicd.js +1443 -0
- package/dist/core/cloud-sync.d.ts +156 -0
- package/dist/core/cloud-sync.js +582 -0
- package/dist/core/code-review.d.ts +132 -0
- package/dist/core/code-review.js +1191 -0
- package/dist/core/completion.d.ts +49 -0
- package/dist/core/completion.js +384 -0
- package/dist/core/context.d.ts +38 -0
- package/dist/core/context.js +144 -0
- package/dist/core/diff-preview.d.ts +35 -0
- package/dist/core/diff-preview.js +173 -0
- package/dist/core/doom-loop.d.ts +51 -0
- package/dist/core/doom-loop.js +179 -0
- package/dist/core/engine.d.ts +183 -0
- package/dist/core/engine.js +942 -0
- package/dist/core/extended-thinking.d.ts +103 -0
- package/dist/core/extended-thinking.js +269 -0
- package/dist/core/fallback.d.ts +54 -0
- package/dist/core/fallback.js +104 -0
- package/dist/core/git-worktree.d.ts +139 -0
- package/dist/core/git-worktree.js +614 -0
- package/dist/core/headless.d.ts +30 -0
- package/dist/core/headless.js +101 -0
- package/dist/core/i18n.d.ts +83 -0
- package/dist/core/i18n.js +739 -0
- package/dist/core/linting.d.ts +129 -0
- package/dist/core/linting.js +699 -0
- package/dist/core/model-router.d.ts +109 -0
- package/dist/core/model-router.js +581 -0
- package/dist/core/multi-model.d.ts +243 -0
- package/dist/core/multi-model.js +1099 -0
- package/dist/core/multi-session.d.ts +144 -0
- package/dist/core/multi-session.js +442 -0
- package/dist/core/multimodal.d.ts +125 -0
- package/dist/core/multimodal.js +286 -0
- package/dist/core/observability.d.ts +93 -0
- package/dist/core/observability.js +737 -0
- package/dist/core/os-sandbox.d.ts +122 -0
- package/dist/core/os-sandbox.js +1193 -0
- package/dist/core/outcome-grading.d.ts +228 -0
- package/dist/core/outcome-grading.js +1123 -0
- package/dist/core/output-styles.d.ts +57 -0
- package/dist/core/output-styles.js +382 -0
- package/dist/core/parallel-agents.d.ts +183 -0
- package/dist/core/parallel-agents.js +563 -0
- package/dist/core/plugin-bundle.d.ts +236 -0
- package/dist/core/plugin-bundle.js +887 -0
- package/dist/core/plugin-sdk.d.ts +139 -0
- package/dist/core/plugin-sdk.js +273 -0
- package/dist/core/prompt-cache.d.ts +163 -0
- package/dist/core/prompt-cache.js +599 -0
- package/dist/core/sandbox.d.ts +127 -0
- package/dist/core/sandbox.js +369 -0
- package/dist/core/scheduled-tasks.d.ts +151 -0
- package/dist/core/scheduled-tasks.js +502 -0
- package/dist/core/security-scanner.d.ts +160 -0
- package/dist/core/security-scanner.js +1494 -0
- package/dist/core/session.d.ts +83 -0
- package/dist/core/session.js +269 -0
- package/dist/core/shell-completion.d.ts +51 -0
- package/dist/core/shell-completion.js +674 -0
- package/dist/core/smart-monitor.d.ts +146 -0
- package/dist/core/smart-monitor.js +1199 -0
- package/dist/core/spec-driven.d.ts +233 -0
- package/dist/core/spec-driven.js +1485 -0
- package/dist/core/spending-warnings.d.ts +123 -0
- package/dist/core/spending-warnings.js +456 -0
- package/dist/core/sub-agent.d.ts +298 -0
- package/dist/core/sub-agent.js +1023 -0
- package/dist/core/telemetry.d.ts +157 -0
- package/dist/core/telemetry.js +412 -0
- package/dist/core/terminal-ux.d.ts +134 -0
- package/dist/core/terminal-ux.js +649 -0
- package/dist/core/testing.d.ts +146 -0
- package/dist/core/testing.js +1200 -0
- package/dist/core/types.d.ts +439 -0
- package/dist/core/types.js +6 -0
- package/dist/core/undo-redo.d.ts +112 -0
- package/dist/core/undo-redo.js +290 -0
- package/dist/core/updater.d.ts +159 -0
- package/dist/core/updater.js +608 -0
- package/dist/core/vim-mode.d.ts +151 -0
- package/dist/core/vim-mode.js +771 -0
- package/dist/core/voice.d.ts +137 -0
- package/dist/core/voice.js +538 -0
- package/dist/core/web-dashboard.d.ts +109 -0
- package/dist/core/web-dashboard.js +484 -0
- package/dist/hooks/hooks.d.ts +74 -0
- package/dist/hooks/hooks.js +160 -0
- package/dist/hooks/llm-evaluator.d.ts +165 -0
- package/dist/hooks/llm-evaluator.js +560 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1186 -0
- package/dist/lsp/lsp-manager.d.ts +63 -0
- package/dist/lsp/lsp-manager.js +351 -0
- package/dist/mcp/client.d.ts +133 -0
- package/dist/mcp/client.js +684 -0
- package/dist/mcp/mcp-apps.d.ts +70 -0
- package/dist/mcp/mcp-apps.js +1007 -0
- package/dist/tools/bash.d.ts +5 -0
- package/dist/tools/bash.js +195 -0
- package/dist/tools/browser.d.ts +92 -0
- package/dist/tools/browser.js +1570 -0
- package/dist/tools/extended.d.ts +6 -0
- package/dist/tools/extended.js +191 -0
- package/dist/tools/file.d.ts +10 -0
- package/dist/tools/file.js +382 -0
- package/dist/tools/github.d.ts +389 -0
- package/dist/tools/github.js +759 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.js +40 -0
- package/dist/tools/memory.d.ts +6 -0
- package/dist/tools/memory.js +197 -0
- package/dist/tools/registry.d.ts +29 -0
- package/dist/tools/registry.js +64 -0
- package/dist/tools/web.d.ts +6 -0
- package/dist/tools/web.js +150 -0
- package/dist/ui/renderer.d.ts +97 -0
- package/dist/ui/renderer.js +279 -0
- package/dist/ui/theme.d.ts +27 -0
- package/dist/ui/theme.js +106 -0
- 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
|