nova-terminal-assistant 0.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.
Potentially problematic release.
This version of nova-terminal-assistant might be problematic. Click here for more details.
- package/README.md +358 -0
- package/bin/nova +38 -0
- package/bin/nova.js +12 -0
- package/package.json +67 -0
- package/src/cli/commands/SmartCompletion.ts +458 -0
- package/src/cli/index.ts +5 -0
- package/src/cli/startup/IFlowRepl.ts +212 -0
- package/src/cli/startup/InkBasedRepl.ts +1056 -0
- package/src/cli/startup/InteractiveRepl.ts +2833 -0
- package/src/cli/startup/NovaApp.ts +1861 -0
- package/src/cli/startup/index.ts +4 -0
- package/src/cli/startup/parseArgs.ts +293 -0
- package/src/cli/test-modules.ts +27 -0
- package/src/cli/ui/IFlowDropdown.ts +425 -0
- package/src/cli/ui/ModernReplUI.ts +276 -0
- package/src/cli/ui/SimpleSelector2.ts +215 -0
- package/src/cli/ui/components/ConfirmDialog.ts +176 -0
- package/src/cli/ui/components/ErrorPanel.ts +364 -0
- package/src/cli/ui/components/InkAppRunner.tsx +67 -0
- package/src/cli/ui/components/InkComponents.tsx +613 -0
- package/src/cli/ui/components/NovaInkApp.tsx +312 -0
- package/src/cli/ui/components/ProgressBar.ts +177 -0
- package/src/cli/ui/components/ProgressIndicator.ts +298 -0
- package/src/cli/ui/components/QuickActions.ts +396 -0
- package/src/cli/ui/components/SimpleErrorPanel.ts +231 -0
- package/src/cli/ui/components/StatusBar.ts +194 -0
- package/src/cli/ui/components/ThinkingBlockRenderer.ts +401 -0
- package/src/cli/ui/components/index.ts +27 -0
- package/src/cli/ui/ink-prototype.tsx +347 -0
- package/src/cli/utils/CliUI.ts +336 -0
- package/src/cli/utils/CompletionHelper.ts +388 -0
- package/src/cli/utils/EnhancedCompleter.test.ts +226 -0
- package/src/cli/utils/EnhancedCompleter.ts +513 -0
- package/src/cli/utils/ErrorEnhancer.ts +429 -0
- package/src/cli/utils/OutputFormatter.ts +193 -0
- package/src/cli/utils/index.ts +9 -0
- package/src/core/agents/AgentOrchestrator.ts +515 -0
- package/src/core/agents/index.ts +17 -0
- package/src/core/audit/AuditLogger.ts +509 -0
- package/src/core/audit/index.ts +11 -0
- package/src/core/auth/AuthManager.d.ts.map +1 -0
- package/src/core/auth/AuthManager.ts +138 -0
- package/src/core/auth/index.d.ts.map +1 -0
- package/src/core/auth/index.ts +2 -0
- package/src/core/config/ConfigManager.d.ts.map +1 -0
- package/src/core/config/ConfigManager.test.ts +183 -0
- package/src/core/config/ConfigManager.ts +1219 -0
- package/src/core/config/index.d.ts.map +1 -0
- package/src/core/config/index.ts +1 -0
- package/src/core/context/ContextBuilder.d.ts.map +1 -0
- package/src/core/context/ContextBuilder.ts +171 -0
- package/src/core/context/ContextCompressor.d.ts.map +1 -0
- package/src/core/context/ContextCompressor.ts +642 -0
- package/src/core/context/LayeredMemoryManager.ts +657 -0
- package/src/core/context/MemoryDiscovery.d.ts.map +1 -0
- package/src/core/context/MemoryDiscovery.ts +175 -0
- package/src/core/context/defaultSystemPrompt.d.ts.map +1 -0
- package/src/core/context/defaultSystemPrompt.ts +35 -0
- package/src/core/context/index.d.ts.map +1 -0
- package/src/core/context/index.ts +22 -0
- package/src/core/extensions/SkillGenerator.ts +421 -0
- package/src/core/extensions/SkillInstaller.d.ts.map +1 -0
- package/src/core/extensions/SkillInstaller.ts +257 -0
- package/src/core/extensions/SkillRegistry.d.ts.map +1 -0
- package/src/core/extensions/SkillRegistry.ts +361 -0
- package/src/core/extensions/SkillValidator.ts +525 -0
- package/src/core/extensions/index.ts +15 -0
- package/src/core/index.d.ts.map +1 -0
- package/src/core/index.ts +42 -0
- package/src/core/mcp/McpManager.d.ts.map +1 -0
- package/src/core/mcp/McpManager.ts +632 -0
- package/src/core/mcp/index.d.ts.map +1 -0
- package/src/core/mcp/index.ts +2 -0
- package/src/core/model/ModelClient.d.ts.map +1 -0
- package/src/core/model/ModelClient.ts +217 -0
- package/src/core/model/ModelConnectionTester.ts +363 -0
- package/src/core/model/ModelValidator.ts +348 -0
- package/src/core/model/index.d.ts.map +1 -0
- package/src/core/model/index.ts +6 -0
- package/src/core/model/providers/AnthropicProvider.d.ts.map +1 -0
- package/src/core/model/providers/AnthropicProvider.ts +279 -0
- package/src/core/model/providers/CodingPlanProvider.d.ts.map +1 -0
- package/src/core/model/providers/CodingPlanProvider.ts +210 -0
- package/src/core/model/providers/OllamaCloudProvider.d.ts.map +1 -0
- package/src/core/model/providers/OllamaCloudProvider.ts +405 -0
- package/src/core/model/providers/OllamaManager.d.ts.map +1 -0
- package/src/core/model/providers/OllamaManager.ts +201 -0
- package/src/core/model/providers/OllamaProvider.d.ts.map +1 -0
- package/src/core/model/providers/OllamaProvider.ts +73 -0
- package/src/core/model/providers/OpenAICompatibleProvider.d.ts.map +1 -0
- package/src/core/model/providers/OpenAICompatibleProvider.ts +327 -0
- package/src/core/model/providers/OpenAIProvider.d.ts.map +1 -0
- package/src/core/model/providers/OpenAIProvider.ts +29 -0
- package/src/core/model/providers/index.d.ts.map +1 -0
- package/src/core/model/providers/index.ts +12 -0
- package/src/core/model/types.d.ts.map +1 -0
- package/src/core/model/types.ts +77 -0
- package/src/core/security/ApprovalManager.d.ts.map +1 -0
- package/src/core/security/ApprovalManager.ts +174 -0
- package/src/core/security/FileFilter.d.ts.map +1 -0
- package/src/core/security/FileFilter.ts +141 -0
- package/src/core/security/HookExecutor.d.ts.map +1 -0
- package/src/core/security/HookExecutor.ts +178 -0
- package/src/core/security/SandboxExecutor.ts +447 -0
- package/src/core/security/index.d.ts.map +1 -0
- package/src/core/security/index.ts +8 -0
- package/src/core/session/AgentLoop.d.ts.map +1 -0
- package/src/core/session/AgentLoop.ts +501 -0
- package/src/core/session/SessionManager.d.ts.map +1 -0
- package/src/core/session/SessionManager.test.ts +183 -0
- package/src/core/session/SessionManager.ts +460 -0
- package/src/core/session/index.d.ts.map +1 -0
- package/src/core/session/index.ts +3 -0
- package/src/core/telemetry/Telemetry.d.ts.map +1 -0
- package/src/core/telemetry/Telemetry.ts +90 -0
- package/src/core/telemetry/TelemetryService.ts +531 -0
- package/src/core/telemetry/index.d.ts.map +1 -0
- package/src/core/telemetry/index.ts +12 -0
- package/src/core/testing/AutoFixer.ts +385 -0
- package/src/core/testing/ErrorAnalyzer.ts +499 -0
- package/src/core/testing/TestRunner.ts +265 -0
- package/src/core/testing/agent-cli-tests.ts +538 -0
- package/src/core/testing/index.ts +11 -0
- package/src/core/tools/ToolRegistry.d.ts.map +1 -0
- package/src/core/tools/ToolRegistry.test.ts +206 -0
- package/src/core/tools/ToolRegistry.ts +260 -0
- package/src/core/tools/impl/EditFileTool.d.ts.map +1 -0
- package/src/core/tools/impl/EditFileTool.ts +97 -0
- package/src/core/tools/impl/ListDirectoryTool.d.ts.map +1 -0
- package/src/core/tools/impl/ListDirectoryTool.ts +142 -0
- package/src/core/tools/impl/MemoryTool.d.ts.map +1 -0
- package/src/core/tools/impl/MemoryTool.ts +102 -0
- package/src/core/tools/impl/ReadFileTool.d.ts.map +1 -0
- package/src/core/tools/impl/ReadFileTool.ts +58 -0
- package/src/core/tools/impl/SearchContentTool.d.ts.map +1 -0
- package/src/core/tools/impl/SearchContentTool.ts +94 -0
- package/src/core/tools/impl/SearchFileTool.d.ts.map +1 -0
- package/src/core/tools/impl/SearchFileTool.ts +61 -0
- package/src/core/tools/impl/ShellTool.d.ts.map +1 -0
- package/src/core/tools/impl/ShellTool.ts +118 -0
- package/src/core/tools/impl/TaskTool.d.ts.map +1 -0
- package/src/core/tools/impl/TaskTool.ts +207 -0
- package/src/core/tools/impl/TodoTool.d.ts.map +1 -0
- package/src/core/tools/impl/TodoTool.ts +122 -0
- package/src/core/tools/impl/WebFetchTool.d.ts.map +1 -0
- package/src/core/tools/impl/WebFetchTool.ts +103 -0
- package/src/core/tools/impl/WebSearchTool.d.ts.map +1 -0
- package/src/core/tools/impl/WebSearchTool.ts +89 -0
- package/src/core/tools/impl/WriteFileTool.d.ts.map +1 -0
- package/src/core/tools/impl/WriteFileTool.ts +49 -0
- package/src/core/tools/impl/index.d.ts.map +1 -0
- package/src/core/tools/impl/index.ts +16 -0
- package/src/core/tools/index.d.ts.map +1 -0
- package/src/core/tools/index.ts +7 -0
- package/src/core/tools/schemas/execution.d.ts.map +1 -0
- package/src/core/tools/schemas/execution.ts +42 -0
- package/src/core/tools/schemas/file.d.ts.map +1 -0
- package/src/core/tools/schemas/file.ts +119 -0
- package/src/core/tools/schemas/index.d.ts.map +1 -0
- package/src/core/tools/schemas/index.ts +11 -0
- package/src/core/tools/schemas/memory.d.ts.map +1 -0
- package/src/core/tools/schemas/memory.ts +52 -0
- package/src/core/tools/schemas/orchestration.d.ts.map +1 -0
- package/src/core/tools/schemas/orchestration.ts +44 -0
- package/src/core/tools/schemas/search.d.ts.map +1 -0
- package/src/core/tools/schemas/search.ts +112 -0
- package/src/core/tools/schemas/todo.d.ts.map +1 -0
- package/src/core/tools/schemas/todo.ts +32 -0
- package/src/core/tools/schemas/web.d.ts.map +1 -0
- package/src/core/tools/schemas/web.ts +86 -0
- package/src/core/types/config.d.ts.map +1 -0
- package/src/core/types/config.ts +200 -0
- package/src/core/types/errors.d.ts.map +1 -0
- package/src/core/types/errors.ts +204 -0
- package/src/core/types/index.d.ts.map +1 -0
- package/src/core/types/index.ts +8 -0
- package/src/core/types/session.d.ts.map +1 -0
- package/src/core/types/session.ts +216 -0
- package/src/core/types/tools.d.ts.map +1 -0
- package/src/core/types/tools.ts +157 -0
- package/src/core/utils/CheckpointManager.d.ts.map +1 -0
- package/src/core/utils/CheckpointManager.ts +327 -0
- package/src/core/utils/Logger.d.ts.map +1 -0
- package/src/core/utils/Logger.ts +98 -0
- package/src/core/utils/RetryManager.ts +471 -0
- package/src/core/utils/TokenCounter.d.ts.map +1 -0
- package/src/core/utils/TokenCounter.ts +414 -0
- package/src/core/utils/VectorMemoryStore.ts +440 -0
- package/src/core/utils/helpers.d.ts.map +1 -0
- package/src/core/utils/helpers.ts +89 -0
- package/src/core/utils/index.d.ts.map +1 -0
- package/src/core/utils/index.ts +19 -0
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// EnhancedCompleter - 高级自动补全系统
|
|
3
|
+
// 支持命令、模型、文件路径、历史命令补全
|
|
4
|
+
// ============================================================================
|
|
5
|
+
|
|
6
|
+
import * as fs from 'node:fs';
|
|
7
|
+
import * as path from 'node:path';
|
|
8
|
+
import type { ConfigManager } from '../core/config/ConfigManager.js';
|
|
9
|
+
|
|
10
|
+
export interface CompletionCandidate {
|
|
11
|
+
text: string;
|
|
12
|
+
displayText: string;
|
|
13
|
+
description: string;
|
|
14
|
+
type: 'command' | 'model' | 'file' | 'directory' | 'history' | 'option' | 'skill' | 'mcp';
|
|
15
|
+
priority: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface CompleterOptions {
|
|
19
|
+
configManager: ConfigManager;
|
|
20
|
+
cwd: string;
|
|
21
|
+
history: string[];
|
|
22
|
+
skills: string[];
|
|
23
|
+
mcpServers: { name: string; status: string }[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* REPL 命令定义
|
|
28
|
+
*/
|
|
29
|
+
interface ReplCommand {
|
|
30
|
+
name: string;
|
|
31
|
+
description: string;
|
|
32
|
+
subcommands?: Record<string, { description: string; args?: string[] }>;
|
|
33
|
+
requiresArg?: boolean;
|
|
34
|
+
argHint?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const REPL_COMMANDS: ReplCommand[] = [
|
|
38
|
+
{ name: '/help', description: '显示帮助信息' },
|
|
39
|
+
{ name: '/quit', description: '退出 Nova' },
|
|
40
|
+
{ name: '/clear', description: '清空对话历史' },
|
|
41
|
+
{ name: '/status', description: '显示会话状态' },
|
|
42
|
+
{ name: '/model', description: '切换模型', requiresArg: true, argHint: '<model-id>' },
|
|
43
|
+
{ name: '/mode', description: '切换交互模式', subcommands: {
|
|
44
|
+
auto: { description: '自动执行模式' },
|
|
45
|
+
plan: { description: '计划确认模式' },
|
|
46
|
+
ask: { description: '仅回答模式' },
|
|
47
|
+
}},
|
|
48
|
+
{ name: '/thinking', description: '切换思考显示' },
|
|
49
|
+
{ name: '/compact', description: '切换紧凑模式' },
|
|
50
|
+
{ name: '/init', description: '初始化项目 NOVA.md' },
|
|
51
|
+
{ name: '/memory', description: '管理记忆', subcommands: {
|
|
52
|
+
add: { description: '添加记忆', args: ['<text>'] },
|
|
53
|
+
show: { description: '显示记忆' },
|
|
54
|
+
clear: { description: '清空记忆' },
|
|
55
|
+
edit: { description: '编辑记忆' },
|
|
56
|
+
}},
|
|
57
|
+
{ name: '/history', description: '管理历史会话', subcommands: {
|
|
58
|
+
list: { description: '列出历史' },
|
|
59
|
+
restore: { description: '恢复会话', args: ['<id>'] },
|
|
60
|
+
delete: { description: '删除会话', args: ['<id>'] },
|
|
61
|
+
}},
|
|
62
|
+
{ name: '/mcp', description: 'MCP 服务器管理', subcommands: {
|
|
63
|
+
status: { description: '查看状态' },
|
|
64
|
+
list: { description: '列出服务器' },
|
|
65
|
+
tools: { description: '列出工具', args: ['<server>'] },
|
|
66
|
+
}},
|
|
67
|
+
{ name: '/skills', description: '技能管理', subcommands: {
|
|
68
|
+
list: { description: '列出技能' },
|
|
69
|
+
use: { description: '激活技能', args: ['<name>'] },
|
|
70
|
+
info: { description: '查看技能详情', args: ['<name>'] },
|
|
71
|
+
}},
|
|
72
|
+
{ name: '/ollama', description: 'Ollama 管理', subcommands: {
|
|
73
|
+
list: { description: '列出本地模型' },
|
|
74
|
+
pull: { description: '拉取模型', args: ['<model>'] },
|
|
75
|
+
rm: { description: '删除模型', args: ['<model>'] },
|
|
76
|
+
}},
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 增强版自动补全器
|
|
81
|
+
*/
|
|
82
|
+
export class EnhancedCompleter {
|
|
83
|
+
private configManager: ConfigManager;
|
|
84
|
+
private cwd: string;
|
|
85
|
+
private history: string[];
|
|
86
|
+
private skills: string[];
|
|
87
|
+
private mcpServers: { name: string; status: string }[];
|
|
88
|
+
private modelCache: string[] = [];
|
|
89
|
+
private modelCacheTime = 0;
|
|
90
|
+
private readonly MODEL_CACHE_TTL = 5000; // 5 seconds
|
|
91
|
+
|
|
92
|
+
constructor(options: CompleterOptions) {
|
|
93
|
+
this.configManager = options.configManager;
|
|
94
|
+
this.cwd = options.cwd;
|
|
95
|
+
this.history = options.history;
|
|
96
|
+
this.skills = options.skills;
|
|
97
|
+
this.mcpServers = options.mcpServers;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 更新历史记录
|
|
102
|
+
*/
|
|
103
|
+
updateHistory(history: string[]): void {
|
|
104
|
+
this.history = history;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 更新技能列表
|
|
109
|
+
*/
|
|
110
|
+
updateSkills(skills: string[]): void {
|
|
111
|
+
this.skills = skills;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 更新 MCP 服务器列表
|
|
116
|
+
*/
|
|
117
|
+
updateMcpServers(servers: { name: string; status: string }[]): void {
|
|
118
|
+
this.mcpServers = servers;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 获取可用模型列表(带缓存)
|
|
123
|
+
*/
|
|
124
|
+
private getAvailableModels(): string[] {
|
|
125
|
+
const now = Date.now();
|
|
126
|
+
if (this.modelCache.length > 0 && now - this.modelCacheTime < this.MODEL_CACHE_TTL) {
|
|
127
|
+
return this.modelCache;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const config = this.configManager.getConfig();
|
|
132
|
+
const models: string[] = [];
|
|
133
|
+
|
|
134
|
+
for (const [providerName, provider] of Object.entries(config.models.providers)) {
|
|
135
|
+
for (const modelName of Object.keys(provider.models)) {
|
|
136
|
+
// 同时支持 provider/model 和单独 model 两种格式
|
|
137
|
+
models.push(modelName);
|
|
138
|
+
if (providerName !== modelName) {
|
|
139
|
+
models.push(`${providerName}/${modelName}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 添加别名
|
|
145
|
+
if (config.models.aliases) {
|
|
146
|
+
models.push(...Object.keys(config.models.aliases));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
this.modelCache = [...new Set(models)];
|
|
150
|
+
this.modelCacheTime = now;
|
|
151
|
+
return this.modelCache;
|
|
152
|
+
} catch {
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 主补全入口
|
|
159
|
+
*/
|
|
160
|
+
getCompletions(input: string, cursorPosition?: number): CompletionCandidate[] {
|
|
161
|
+
const trimmed = input.trim();
|
|
162
|
+
const pos = cursorPosition ?? input.length;
|
|
163
|
+
|
|
164
|
+
// 空输入 - 显示所有命令
|
|
165
|
+
if (trimmed === '') {
|
|
166
|
+
return this.getCommandCompletions('');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// !shell 命令补全
|
|
170
|
+
if (trimmed.startsWith('!')) {
|
|
171
|
+
return this.getShellCompletions(trimmed.slice(1));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// @file 文件补全
|
|
175
|
+
if (trimmed.startsWith('@')) {
|
|
176
|
+
return this.getFileCompletions(trimmed.slice(1));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// /command 命令补全 - 使用原始 input 以保留尾随空格
|
|
180
|
+
if (trimmed.startsWith('/')) {
|
|
181
|
+
return this.getSlashCommandCompletions(input);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 普通输入 - 检查是否匹配历史
|
|
185
|
+
return this.getHistoryCompletions(trimmed);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* 命令补全
|
|
190
|
+
*/
|
|
191
|
+
private getCommandCompletions(partial: string): CompletionCandidate[] {
|
|
192
|
+
const candidates: CompletionCandidate[] = [];
|
|
193
|
+
|
|
194
|
+
for (const cmd of REPL_COMMANDS) {
|
|
195
|
+
if (cmd.name.startsWith(partial.toLowerCase())) {
|
|
196
|
+
candidates.push({
|
|
197
|
+
text: cmd.name,
|
|
198
|
+
displayText: cmd.name,
|
|
199
|
+
description: cmd.description,
|
|
200
|
+
type: 'command',
|
|
201
|
+
priority: 100,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return candidates.sort((a, b) => b.priority - a.priority);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* /command 格式补全
|
|
211
|
+
*/
|
|
212
|
+
private getSlashCommandCompletions(input: string): CompletionCandidate[] {
|
|
213
|
+
const inputEndsWithSpace = input.endsWith(' ');
|
|
214
|
+
const parts = input.trim().split(/\s+/);
|
|
215
|
+
const mainCmd = parts[0].toLowerCase();
|
|
216
|
+
const subCmd = parts.length > 1 ? parts[1].toLowerCase() : '';
|
|
217
|
+
const argValue = parts.length > 2 ? parts[2] : '';
|
|
218
|
+
|
|
219
|
+
// 找到匹配的主命令
|
|
220
|
+
const matchedCmd = REPL_COMMANDS.find(cmd => cmd.name === mainCmd);
|
|
221
|
+
|
|
222
|
+
// 仅输入了主命令的一部分(没有空格)
|
|
223
|
+
if (!inputEndsWithSpace && parts.length === 1) {
|
|
224
|
+
return this.getCommandCompletions(mainCmd);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// 主命令后有空格,需要子命令或参数
|
|
228
|
+
if (matchedCmd) {
|
|
229
|
+
// /model 需要模型参数
|
|
230
|
+
if (matchedCmd.name === '/model') {
|
|
231
|
+
return this.getModelCompletions(subCmd);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// 有子命令的命令
|
|
235
|
+
if (matchedCmd.subcommands) {
|
|
236
|
+
// 正在输入子命令(还没有空格)
|
|
237
|
+
if (!inputEndsWithSpace && parts.length === 2) {
|
|
238
|
+
return this.getSubcommandCompletions(matchedCmd, subCmd);
|
|
239
|
+
}
|
|
240
|
+
// 子命令后有空格,需要参数
|
|
241
|
+
if (inputEndsWithSpace && parts.length >= 2) {
|
|
242
|
+
const sub = matchedCmd.subcommands[subCmd];
|
|
243
|
+
if (sub?.args) {
|
|
244
|
+
// /skills use <name> 补全技能名
|
|
245
|
+
if (matchedCmd.name === '/skills' && subCmd === 'use') {
|
|
246
|
+
return this.getSkillCompletions(argValue);
|
|
247
|
+
}
|
|
248
|
+
// /mcp tools <server> 补全服务器名
|
|
249
|
+
if (matchedCmd.name === '/mcp' && subCmd === 'tools') {
|
|
250
|
+
return this.getMcpServerCompletions(argValue);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// 主命令后空格,显示所有子命令
|
|
255
|
+
return this.getSubcommandCompletions(matchedCmd, subCmd);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return [];
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* 子命令补全
|
|
264
|
+
*/
|
|
265
|
+
private getSubcommandCompletions(cmd: ReplCommand, partial: string): CompletionCandidate[] {
|
|
266
|
+
if (!cmd.subcommands) return [];
|
|
267
|
+
|
|
268
|
+
const candidates: CompletionCandidate[] = [];
|
|
269
|
+
|
|
270
|
+
for (const [name, sub] of Object.entries(cmd.subcommands)) {
|
|
271
|
+
if (name.startsWith(partial.toLowerCase())) {
|
|
272
|
+
candidates.push({
|
|
273
|
+
text: name,
|
|
274
|
+
displayText: name,
|
|
275
|
+
description: sub.description,
|
|
276
|
+
type: 'command',
|
|
277
|
+
priority: 90,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return candidates;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* 模型补全
|
|
287
|
+
*/
|
|
288
|
+
private getModelCompletions(partial: string): CompletionCandidate[] {
|
|
289
|
+
const models = this.getAvailableModels();
|
|
290
|
+
const candidates: CompletionCandidate[] = [];
|
|
291
|
+
const lowerPartial = partial.toLowerCase();
|
|
292
|
+
|
|
293
|
+
for (const model of models) {
|
|
294
|
+
if (model.toLowerCase().startsWith(lowerPartial)) {
|
|
295
|
+
candidates.push({
|
|
296
|
+
text: model,
|
|
297
|
+
displayText: model,
|
|
298
|
+
description: 'AI 模型',
|
|
299
|
+
type: 'model',
|
|
300
|
+
priority: model.includes('/') ? 95 : 85, // provider/model 优先
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return candidates.sort((a, b) => b.priority - a.priority);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* 技能补全
|
|
310
|
+
*/
|
|
311
|
+
private getSkillCompletions(partial: string): CompletionCandidate[] {
|
|
312
|
+
const candidates: CompletionCandidate[] = [];
|
|
313
|
+
const lowerPartial = partial.toLowerCase();
|
|
314
|
+
|
|
315
|
+
for (const skill of this.skills) {
|
|
316
|
+
if (skill.toLowerCase().startsWith(lowerPartial)) {
|
|
317
|
+
candidates.push({
|
|
318
|
+
text: skill,
|
|
319
|
+
displayText: skill,
|
|
320
|
+
description: '可用技能',
|
|
321
|
+
type: 'skill',
|
|
322
|
+
priority: 80,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return candidates;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* MCP 服务器补全
|
|
332
|
+
*/
|
|
333
|
+
private getMcpServerCompletions(partial: string): CompletionCandidate[] {
|
|
334
|
+
const candidates: CompletionCandidate[] = [];
|
|
335
|
+
const lowerPartial = partial.toLowerCase();
|
|
336
|
+
|
|
337
|
+
for (const server of this.mcpServers) {
|
|
338
|
+
if (server.name.toLowerCase().startsWith(lowerPartial)) {
|
|
339
|
+
candidates.push({
|
|
340
|
+
text: server.name,
|
|
341
|
+
displayText: server.name,
|
|
342
|
+
description: `MCP 服务器 (${server.status})`,
|
|
343
|
+
type: 'mcp',
|
|
344
|
+
priority: 80,
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return candidates;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* 文件路径补全
|
|
354
|
+
*/
|
|
355
|
+
private getFileCompletions(partial: string): CompletionCandidate[] {
|
|
356
|
+
const candidates: CompletionCandidate[] = [];
|
|
357
|
+
|
|
358
|
+
try {
|
|
359
|
+
// 解析路径
|
|
360
|
+
const isAbsolute = path.isAbsolute(partial);
|
|
361
|
+
const baseDir = isAbsolute
|
|
362
|
+
? path.dirname(partial) || '/'
|
|
363
|
+
: path.join(this.cwd, path.dirname(partial) || '.');
|
|
364
|
+
const prefix = path.basename(partial);
|
|
365
|
+
|
|
366
|
+
// 列出目录内容
|
|
367
|
+
if (fs.existsSync(baseDir)) {
|
|
368
|
+
const entries = fs.readdirSync(baseDir, { withFileTypes: true });
|
|
369
|
+
|
|
370
|
+
for (const entry of entries) {
|
|
371
|
+
if (entry.name.startsWith(prefix)) {
|
|
372
|
+
const fullPath = isAbsolute
|
|
373
|
+
? path.join(baseDir, entry.name)
|
|
374
|
+
: path.relative(this.cwd, path.join(baseDir, entry.name));
|
|
375
|
+
|
|
376
|
+
const isDir = entry.isDirectory();
|
|
377
|
+
candidates.push({
|
|
378
|
+
text: isDir ? fullPath + '/' : fullPath,
|
|
379
|
+
displayText: entry.name + (isDir ? '/' : ''),
|
|
380
|
+
description: isDir ? '目录' : '文件',
|
|
381
|
+
type: isDir ? 'directory' : 'file',
|
|
382
|
+
priority: isDir ? 75 : 70,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
} catch {
|
|
388
|
+
// 忽略错误
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return candidates.sort((a, b) => b.priority - a.priority);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Shell 命令补全(简单实现)
|
|
396
|
+
*/
|
|
397
|
+
private getShellCompletions(partial: string): CompletionCandidate[] {
|
|
398
|
+
// 提取命令名
|
|
399
|
+
const cmdName = partial.split(' ')[0];
|
|
400
|
+
|
|
401
|
+
// 常用命令提示
|
|
402
|
+
const commonCommands = [
|
|
403
|
+
{ cmd: 'git', desc: 'Git 版本控制' },
|
|
404
|
+
{ cmd: 'npm', desc: 'Node 包管理器' },
|
|
405
|
+
{ cmd: 'pnpm', desc: '快速包管理器' },
|
|
406
|
+
{ cmd: 'node', desc: 'Node.js 运行时' },
|
|
407
|
+
{ cmd: 'ls', desc: '列出文件' },
|
|
408
|
+
{ cmd: 'cd', desc: '切换目录' },
|
|
409
|
+
{ cmd: 'cat', desc: '查看文件' },
|
|
410
|
+
{ cmd: 'grep', desc: '搜索文本' },
|
|
411
|
+
];
|
|
412
|
+
|
|
413
|
+
const candidates: CompletionCandidate[] = [];
|
|
414
|
+
|
|
415
|
+
if (!partial.includes(' ')) {
|
|
416
|
+
for (const { cmd, desc } of commonCommands) {
|
|
417
|
+
if (cmd.startsWith(partial.toLowerCase())) {
|
|
418
|
+
candidates.push({
|
|
419
|
+
text: cmd,
|
|
420
|
+
displayText: cmd,
|
|
421
|
+
description: desc,
|
|
422
|
+
type: 'command',
|
|
423
|
+
priority: 60,
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return candidates;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* 历史命令补全
|
|
434
|
+
*/
|
|
435
|
+
private getHistoryCompletions(partial: string): CompletionCandidate[] {
|
|
436
|
+
const candidates: CompletionCandidate[] = [];
|
|
437
|
+
const lowerPartial = partial.toLowerCase();
|
|
438
|
+
const seen = new Set<string>();
|
|
439
|
+
|
|
440
|
+
// 从最近的开始
|
|
441
|
+
for (let i = this.history.length - 1; i >= 0; i--) {
|
|
442
|
+
const item = this.history[i];
|
|
443
|
+
if (item.toLowerCase().startsWith(lowerPartial) && !seen.has(item)) {
|
|
444
|
+
seen.add(item);
|
|
445
|
+
candidates.push({
|
|
446
|
+
text: item,
|
|
447
|
+
displayText: item.length > 50 ? item.slice(0, 47) + '...' : item,
|
|
448
|
+
description: '历史命令',
|
|
449
|
+
type: 'history',
|
|
450
|
+
priority: 50 - candidates.length, // 最近的优先
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
if (candidates.length >= 5) break;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return candidates;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* 应用补全到输入
|
|
462
|
+
*/
|
|
463
|
+
applyCompletion(input: string, candidate: CompletionCandidate, cursorPos: number): { text: string; cursorPos: number } {
|
|
464
|
+
// 确定要替换的范围
|
|
465
|
+
const beforeCompletion = input.slice(0, this.getCompletionStart(input, cursorPos));
|
|
466
|
+
const afterCursor = input.slice(cursorPos);
|
|
467
|
+
|
|
468
|
+
// 构建新输入
|
|
469
|
+
let newText = candidate.text;
|
|
470
|
+
|
|
471
|
+
// 如果是命令,添加空格
|
|
472
|
+
if (candidate.type === 'command' && !candidate.text.includes(' ')) {
|
|
473
|
+
newText += ' ';
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return {
|
|
477
|
+
text: beforeCompletion + newText + afterCursor,
|
|
478
|
+
cursorPos: beforeCompletion.length + newText.length,
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* 获取补全起始位置
|
|
484
|
+
*/
|
|
485
|
+
private getCompletionStart(input: string, cursorPos: number): number {
|
|
486
|
+
// 找到当前词的起始位置
|
|
487
|
+
let start = cursorPos;
|
|
488
|
+
while (start > 0 && !/\s/.test(input[start - 1])) {
|
|
489
|
+
start--;
|
|
490
|
+
}
|
|
491
|
+
return start;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* 格式化补全项显示
|
|
496
|
+
*/
|
|
497
|
+
formatCompletion(candidate: CompletionCandidate, maxWidth: number): string {
|
|
498
|
+
const typeIcons: Record<string, string> = {
|
|
499
|
+
command: '⌘',
|
|
500
|
+
model: '🤖',
|
|
501
|
+
file: '📄',
|
|
502
|
+
directory: '📁',
|
|
503
|
+
history: '📜',
|
|
504
|
+
option: '⚙️',
|
|
505
|
+
skill: '🎯',
|
|
506
|
+
mcp: '🔌',
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
const icon = typeIcons[candidate.type] || '•';
|
|
510
|
+
const text = candidate.displayText.padEnd(Math.min(maxWidth - 20, 30));
|
|
511
|
+
return ` ${icon} ${text} ${candidate.description}`;
|
|
512
|
+
}
|
|
513
|
+
}
|