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.

Files changed (192) hide show
  1. package/README.md +358 -0
  2. package/bin/nova +38 -0
  3. package/bin/nova.js +12 -0
  4. package/package.json +67 -0
  5. package/src/cli/commands/SmartCompletion.ts +458 -0
  6. package/src/cli/index.ts +5 -0
  7. package/src/cli/startup/IFlowRepl.ts +212 -0
  8. package/src/cli/startup/InkBasedRepl.ts +1056 -0
  9. package/src/cli/startup/InteractiveRepl.ts +2833 -0
  10. package/src/cli/startup/NovaApp.ts +1861 -0
  11. package/src/cli/startup/index.ts +4 -0
  12. package/src/cli/startup/parseArgs.ts +293 -0
  13. package/src/cli/test-modules.ts +27 -0
  14. package/src/cli/ui/IFlowDropdown.ts +425 -0
  15. package/src/cli/ui/ModernReplUI.ts +276 -0
  16. package/src/cli/ui/SimpleSelector2.ts +215 -0
  17. package/src/cli/ui/components/ConfirmDialog.ts +176 -0
  18. package/src/cli/ui/components/ErrorPanel.ts +364 -0
  19. package/src/cli/ui/components/InkAppRunner.tsx +67 -0
  20. package/src/cli/ui/components/InkComponents.tsx +613 -0
  21. package/src/cli/ui/components/NovaInkApp.tsx +312 -0
  22. package/src/cli/ui/components/ProgressBar.ts +177 -0
  23. package/src/cli/ui/components/ProgressIndicator.ts +298 -0
  24. package/src/cli/ui/components/QuickActions.ts +396 -0
  25. package/src/cli/ui/components/SimpleErrorPanel.ts +231 -0
  26. package/src/cli/ui/components/StatusBar.ts +194 -0
  27. package/src/cli/ui/components/ThinkingBlockRenderer.ts +401 -0
  28. package/src/cli/ui/components/index.ts +27 -0
  29. package/src/cli/ui/ink-prototype.tsx +347 -0
  30. package/src/cli/utils/CliUI.ts +336 -0
  31. package/src/cli/utils/CompletionHelper.ts +388 -0
  32. package/src/cli/utils/EnhancedCompleter.test.ts +226 -0
  33. package/src/cli/utils/EnhancedCompleter.ts +513 -0
  34. package/src/cli/utils/ErrorEnhancer.ts +429 -0
  35. package/src/cli/utils/OutputFormatter.ts +193 -0
  36. package/src/cli/utils/index.ts +9 -0
  37. package/src/core/agents/AgentOrchestrator.ts +515 -0
  38. package/src/core/agents/index.ts +17 -0
  39. package/src/core/audit/AuditLogger.ts +509 -0
  40. package/src/core/audit/index.ts +11 -0
  41. package/src/core/auth/AuthManager.d.ts.map +1 -0
  42. package/src/core/auth/AuthManager.ts +138 -0
  43. package/src/core/auth/index.d.ts.map +1 -0
  44. package/src/core/auth/index.ts +2 -0
  45. package/src/core/config/ConfigManager.d.ts.map +1 -0
  46. package/src/core/config/ConfigManager.test.ts +183 -0
  47. package/src/core/config/ConfigManager.ts +1219 -0
  48. package/src/core/config/index.d.ts.map +1 -0
  49. package/src/core/config/index.ts +1 -0
  50. package/src/core/context/ContextBuilder.d.ts.map +1 -0
  51. package/src/core/context/ContextBuilder.ts +171 -0
  52. package/src/core/context/ContextCompressor.d.ts.map +1 -0
  53. package/src/core/context/ContextCompressor.ts +642 -0
  54. package/src/core/context/LayeredMemoryManager.ts +657 -0
  55. package/src/core/context/MemoryDiscovery.d.ts.map +1 -0
  56. package/src/core/context/MemoryDiscovery.ts +175 -0
  57. package/src/core/context/defaultSystemPrompt.d.ts.map +1 -0
  58. package/src/core/context/defaultSystemPrompt.ts +35 -0
  59. package/src/core/context/index.d.ts.map +1 -0
  60. package/src/core/context/index.ts +22 -0
  61. package/src/core/extensions/SkillGenerator.ts +421 -0
  62. package/src/core/extensions/SkillInstaller.d.ts.map +1 -0
  63. package/src/core/extensions/SkillInstaller.ts +257 -0
  64. package/src/core/extensions/SkillRegistry.d.ts.map +1 -0
  65. package/src/core/extensions/SkillRegistry.ts +361 -0
  66. package/src/core/extensions/SkillValidator.ts +525 -0
  67. package/src/core/extensions/index.ts +15 -0
  68. package/src/core/index.d.ts.map +1 -0
  69. package/src/core/index.ts +42 -0
  70. package/src/core/mcp/McpManager.d.ts.map +1 -0
  71. package/src/core/mcp/McpManager.ts +632 -0
  72. package/src/core/mcp/index.d.ts.map +1 -0
  73. package/src/core/mcp/index.ts +2 -0
  74. package/src/core/model/ModelClient.d.ts.map +1 -0
  75. package/src/core/model/ModelClient.ts +217 -0
  76. package/src/core/model/ModelConnectionTester.ts +363 -0
  77. package/src/core/model/ModelValidator.ts +348 -0
  78. package/src/core/model/index.d.ts.map +1 -0
  79. package/src/core/model/index.ts +6 -0
  80. package/src/core/model/providers/AnthropicProvider.d.ts.map +1 -0
  81. package/src/core/model/providers/AnthropicProvider.ts +279 -0
  82. package/src/core/model/providers/CodingPlanProvider.d.ts.map +1 -0
  83. package/src/core/model/providers/CodingPlanProvider.ts +210 -0
  84. package/src/core/model/providers/OllamaCloudProvider.d.ts.map +1 -0
  85. package/src/core/model/providers/OllamaCloudProvider.ts +405 -0
  86. package/src/core/model/providers/OllamaManager.d.ts.map +1 -0
  87. package/src/core/model/providers/OllamaManager.ts +201 -0
  88. package/src/core/model/providers/OllamaProvider.d.ts.map +1 -0
  89. package/src/core/model/providers/OllamaProvider.ts +73 -0
  90. package/src/core/model/providers/OpenAICompatibleProvider.d.ts.map +1 -0
  91. package/src/core/model/providers/OpenAICompatibleProvider.ts +327 -0
  92. package/src/core/model/providers/OpenAIProvider.d.ts.map +1 -0
  93. package/src/core/model/providers/OpenAIProvider.ts +29 -0
  94. package/src/core/model/providers/index.d.ts.map +1 -0
  95. package/src/core/model/providers/index.ts +12 -0
  96. package/src/core/model/types.d.ts.map +1 -0
  97. package/src/core/model/types.ts +77 -0
  98. package/src/core/security/ApprovalManager.d.ts.map +1 -0
  99. package/src/core/security/ApprovalManager.ts +174 -0
  100. package/src/core/security/FileFilter.d.ts.map +1 -0
  101. package/src/core/security/FileFilter.ts +141 -0
  102. package/src/core/security/HookExecutor.d.ts.map +1 -0
  103. package/src/core/security/HookExecutor.ts +178 -0
  104. package/src/core/security/SandboxExecutor.ts +447 -0
  105. package/src/core/security/index.d.ts.map +1 -0
  106. package/src/core/security/index.ts +8 -0
  107. package/src/core/session/AgentLoop.d.ts.map +1 -0
  108. package/src/core/session/AgentLoop.ts +501 -0
  109. package/src/core/session/SessionManager.d.ts.map +1 -0
  110. package/src/core/session/SessionManager.test.ts +183 -0
  111. package/src/core/session/SessionManager.ts +460 -0
  112. package/src/core/session/index.d.ts.map +1 -0
  113. package/src/core/session/index.ts +3 -0
  114. package/src/core/telemetry/Telemetry.d.ts.map +1 -0
  115. package/src/core/telemetry/Telemetry.ts +90 -0
  116. package/src/core/telemetry/TelemetryService.ts +531 -0
  117. package/src/core/telemetry/index.d.ts.map +1 -0
  118. package/src/core/telemetry/index.ts +12 -0
  119. package/src/core/testing/AutoFixer.ts +385 -0
  120. package/src/core/testing/ErrorAnalyzer.ts +499 -0
  121. package/src/core/testing/TestRunner.ts +265 -0
  122. package/src/core/testing/agent-cli-tests.ts +538 -0
  123. package/src/core/testing/index.ts +11 -0
  124. package/src/core/tools/ToolRegistry.d.ts.map +1 -0
  125. package/src/core/tools/ToolRegistry.test.ts +206 -0
  126. package/src/core/tools/ToolRegistry.ts +260 -0
  127. package/src/core/tools/impl/EditFileTool.d.ts.map +1 -0
  128. package/src/core/tools/impl/EditFileTool.ts +97 -0
  129. package/src/core/tools/impl/ListDirectoryTool.d.ts.map +1 -0
  130. package/src/core/tools/impl/ListDirectoryTool.ts +142 -0
  131. package/src/core/tools/impl/MemoryTool.d.ts.map +1 -0
  132. package/src/core/tools/impl/MemoryTool.ts +102 -0
  133. package/src/core/tools/impl/ReadFileTool.d.ts.map +1 -0
  134. package/src/core/tools/impl/ReadFileTool.ts +58 -0
  135. package/src/core/tools/impl/SearchContentTool.d.ts.map +1 -0
  136. package/src/core/tools/impl/SearchContentTool.ts +94 -0
  137. package/src/core/tools/impl/SearchFileTool.d.ts.map +1 -0
  138. package/src/core/tools/impl/SearchFileTool.ts +61 -0
  139. package/src/core/tools/impl/ShellTool.d.ts.map +1 -0
  140. package/src/core/tools/impl/ShellTool.ts +118 -0
  141. package/src/core/tools/impl/TaskTool.d.ts.map +1 -0
  142. package/src/core/tools/impl/TaskTool.ts +207 -0
  143. package/src/core/tools/impl/TodoTool.d.ts.map +1 -0
  144. package/src/core/tools/impl/TodoTool.ts +122 -0
  145. package/src/core/tools/impl/WebFetchTool.d.ts.map +1 -0
  146. package/src/core/tools/impl/WebFetchTool.ts +103 -0
  147. package/src/core/tools/impl/WebSearchTool.d.ts.map +1 -0
  148. package/src/core/tools/impl/WebSearchTool.ts +89 -0
  149. package/src/core/tools/impl/WriteFileTool.d.ts.map +1 -0
  150. package/src/core/tools/impl/WriteFileTool.ts +49 -0
  151. package/src/core/tools/impl/index.d.ts.map +1 -0
  152. package/src/core/tools/impl/index.ts +16 -0
  153. package/src/core/tools/index.d.ts.map +1 -0
  154. package/src/core/tools/index.ts +7 -0
  155. package/src/core/tools/schemas/execution.d.ts.map +1 -0
  156. package/src/core/tools/schemas/execution.ts +42 -0
  157. package/src/core/tools/schemas/file.d.ts.map +1 -0
  158. package/src/core/tools/schemas/file.ts +119 -0
  159. package/src/core/tools/schemas/index.d.ts.map +1 -0
  160. package/src/core/tools/schemas/index.ts +11 -0
  161. package/src/core/tools/schemas/memory.d.ts.map +1 -0
  162. package/src/core/tools/schemas/memory.ts +52 -0
  163. package/src/core/tools/schemas/orchestration.d.ts.map +1 -0
  164. package/src/core/tools/schemas/orchestration.ts +44 -0
  165. package/src/core/tools/schemas/search.d.ts.map +1 -0
  166. package/src/core/tools/schemas/search.ts +112 -0
  167. package/src/core/tools/schemas/todo.d.ts.map +1 -0
  168. package/src/core/tools/schemas/todo.ts +32 -0
  169. package/src/core/tools/schemas/web.d.ts.map +1 -0
  170. package/src/core/tools/schemas/web.ts +86 -0
  171. package/src/core/types/config.d.ts.map +1 -0
  172. package/src/core/types/config.ts +200 -0
  173. package/src/core/types/errors.d.ts.map +1 -0
  174. package/src/core/types/errors.ts +204 -0
  175. package/src/core/types/index.d.ts.map +1 -0
  176. package/src/core/types/index.ts +8 -0
  177. package/src/core/types/session.d.ts.map +1 -0
  178. package/src/core/types/session.ts +216 -0
  179. package/src/core/types/tools.d.ts.map +1 -0
  180. package/src/core/types/tools.ts +157 -0
  181. package/src/core/utils/CheckpointManager.d.ts.map +1 -0
  182. package/src/core/utils/CheckpointManager.ts +327 -0
  183. package/src/core/utils/Logger.d.ts.map +1 -0
  184. package/src/core/utils/Logger.ts +98 -0
  185. package/src/core/utils/RetryManager.ts +471 -0
  186. package/src/core/utils/TokenCounter.d.ts.map +1 -0
  187. package/src/core/utils/TokenCounter.ts +414 -0
  188. package/src/core/utils/VectorMemoryStore.ts +440 -0
  189. package/src/core/utils/helpers.d.ts.map +1 -0
  190. package/src/core/utils/helpers.ts +89 -0
  191. package/src/core/utils/index.d.ts.map +1 -0
  192. package/src/core/utils/index.ts +19 -0
@@ -0,0 +1,388 @@
1
+ // ============================================================================
2
+ // Completion Helper - 命令自动补全助手
3
+ // ============================================================================
4
+
5
+ import { CliUI, Colors } from './CliUI.js';
6
+
7
+ /**
8
+ * 补全项
9
+ */
10
+ export interface CompletionItem {
11
+ text: string;
12
+ description?: string;
13
+ type?: 'command' | 'argument' | 'option' | 'file' | 'directory';
14
+ }
15
+
16
+ /**
17
+ * 补全上下文
18
+ */
19
+ export interface CompletionContext {
20
+ command?: string;
21
+ subcommand?: string;
22
+ args: string[];
23
+ currentArg: string;
24
+ argIndex: number;
25
+ }
26
+
27
+ /**
28
+ * 命令定义
29
+ */
30
+ interface CommandDefinition {
31
+ name: string;
32
+ description: string;
33
+ subcommands?: Record<string, SubcommandDefinition>;
34
+ options?: OptionDefinition[];
35
+ args?: ArgumentDefinition[];
36
+ }
37
+
38
+ /**
39
+ * 子命令定义
40
+ */
41
+ interface SubcommandDefinition {
42
+ name: string;
43
+ description: string;
44
+ options?: OptionDefinition[];
45
+ args?: ArgumentDefinition[];
46
+ }
47
+
48
+ /**
49
+ * 选项定义
50
+ */
51
+ interface OptionDefinition {
52
+ name: string;
53
+ short?: string;
54
+ description: string;
55
+ type?: 'string' | 'number' | 'boolean';
56
+ requiresValue?: boolean;
57
+ }
58
+
59
+ /**
60
+ * 参数定义
61
+ */
62
+ interface ArgumentDefinition {
63
+ name: string;
64
+ description: string;
65
+ type?: 'string' | 'number' | 'file' | 'directory';
66
+ }
67
+
68
+ /**
69
+ * 命令自动补全助手
70
+ */
71
+ export class CompletionHelper {
72
+ private static commands: Map<string, CommandDefinition> = new Map();
73
+ private static initialized = false;
74
+
75
+ /**
76
+ * 初始化命令定义
77
+ */
78
+ private static initialize(): void {
79
+ if (this.initialized) return;
80
+
81
+ // 核心命令
82
+ this.registerCommand({
83
+ name: 'config',
84
+ description: 'Manage configuration',
85
+ subcommands: {
86
+ show: { name: 'show', description: 'Show current configuration' },
87
+ edit: { name: 'edit', description: 'Open config file in editor' },
88
+ },
89
+ });
90
+
91
+ this.registerCommand({
92
+ name: 'auth',
93
+ description: 'Manage API credentials',
94
+ subcommands: {
95
+ set: {
96
+ name: 'set',
97
+ description: 'Set API key for a provider',
98
+ args: [{ name: 'provider', description: 'Provider name (anthropic, openai, etc.)' }],
99
+ options: [
100
+ { name: '--key', description: 'API key' },
101
+ { name: '--base-url', description: 'Base URL' },
102
+ ],
103
+ },
104
+ remove: {
105
+ name: 'remove',
106
+ description: 'Remove credentials for a provider',
107
+ args: [{ name: 'provider', description: 'Provider name' }],
108
+ },
109
+ status: { name: 'status', description: 'Show credential status' },
110
+ },
111
+ });
112
+
113
+ this.registerCommand({
114
+ name: 'model',
115
+ description: 'Manage AI models',
116
+ subcommands: {
117
+ list: { name: 'list', description: 'List available models' },
118
+ switch: { name: 'switch', description: 'Switch to a model', args: [{ name: 'model-id', description: 'Model ID' }] },
119
+ },
120
+ });
121
+
122
+ this.registerCommand({
123
+ name: 'provider',
124
+ description: 'Manage custom providers',
125
+ subcommands: {
126
+ add: { name: 'add', description: 'Add a custom provider' },
127
+ 'add-model': { name: 'add-model', description: 'Add a model to a provider' },
128
+ remove: { name: 'remove', description: 'Remove a provider' },
129
+ list: { name: 'list', description: 'List all providers' },
130
+ },
131
+ });
132
+
133
+ this.registerCommand({
134
+ name: 'mcp',
135
+ description: 'Manage MCP servers',
136
+ subcommands: {
137
+ status: { name: 'status', description: 'Show MCP server status' },
138
+ list: { name: 'list', description: 'List MCP servers' },
139
+ },
140
+ });
141
+
142
+ this.registerCommand({
143
+ name: 'skills',
144
+ description: 'Manage skills',
145
+ subcommands: {
146
+ list: { name: 'list', description: 'List available skills' },
147
+ },
148
+ });
149
+
150
+ this.registerCommand({
151
+ name: 'coding-plan',
152
+ description: 'Manage Coding Plan providers',
153
+ subcommands: {
154
+ list: { name: 'list', description: 'List Coding Plan platforms' },
155
+ add: { name: 'add', description: 'Add a Coding Plan provider' },
156
+ },
157
+ });
158
+
159
+ this.registerCommand({
160
+ name: 'ollama',
161
+ description: 'Manage Ollama',
162
+ subcommands: {
163
+ list: { name: 'list', description: 'List local models' },
164
+ pull: { name: 'pull', description: 'Pull a model' },
165
+ rm: { name: 'rm', description: 'Remove a model' },
166
+ info: { name: 'info', description: 'Show model info' },
167
+ run: { name: 'run', description: 'Run a model' },
168
+ },
169
+ });
170
+
171
+ this.initialized = true;
172
+ }
173
+
174
+ /**
175
+ * 注册命令
176
+ */
177
+ static registerCommand(cmd: CommandDefinition): void {
178
+ this.commands.set(cmd.name, cmd);
179
+ }
180
+
181
+ /**
182
+ * 获取补全建议
183
+ */
184
+ static getCompletions(context: CompletionContext): CompletionItem[] {
185
+ this.initialize();
186
+
187
+ const completions: CompletionItem[] = [];
188
+
189
+ // 如果没有命令,补全命令
190
+ if (!context.command) {
191
+ return this.getCommandCompletions();
192
+ }
193
+
194
+ const command = this.commands.get(context.command);
195
+ if (!command) {
196
+ return [];
197
+ }
198
+
199
+ // 如果有子命令但没有输入子命令,补全子命令
200
+ if (command.subcommands && !context.subcommand) {
201
+ return this.getSubcommandCompletions(command.subcommands);
202
+ }
203
+
204
+ // 补全选项
205
+ const options = this.getOptionsForContext(command, context.subcommand);
206
+ completions.push(...this.getOptionCompletions(options));
207
+
208
+ // 补全参数
209
+ const args = this.getArgsForContext(command, context.subcommand);
210
+ completions.push(...this.getArgCompletions(args, context));
211
+
212
+ return completions;
213
+ }
214
+
215
+ /**
216
+ * 获取命令补全
217
+ */
218
+ private static getCommandCompletions(): CompletionItem[] {
219
+ return Array.from(this.commands.values()).map(cmd => ({
220
+ text: cmd.name,
221
+ description: cmd.description,
222
+ type: 'command',
223
+ }));
224
+ }
225
+
226
+ /**
227
+ * 获取子命令补全
228
+ */
229
+ private static getSubcommandCompletions(subcommands: Record<string, SubcommandDefinition>): CompletionItem[] {
230
+ return Object.values(subcommands).map(sub => ({
231
+ text: sub.name,
232
+ description: sub.description,
233
+ type: 'command',
234
+ }));
235
+ }
236
+
237
+ /**
238
+ * 获取选项补全
239
+ */
240
+ private static getOptionCompletions(options: OptionDefinition[]): CompletionItem[] {
241
+ return options.map(opt => {
242
+ const texts = [];
243
+ if (opt.short) texts.push(opt.short);
244
+ texts.push(opt.name);
245
+
246
+ return {
247
+ text: texts[0],
248
+ description: opt.description || texts.join(', '),
249
+ type: 'option',
250
+ };
251
+ });
252
+ }
253
+
254
+ /**
255
+ * 获取参数补全
256
+ */
257
+ private static getArgCompletions(args: ArgumentDefinition[], context: CompletionContext): CompletionItem[] {
258
+ const currentArgIndex = context.argIndex;
259
+
260
+ if (currentArgIndex >= args.length) {
261
+ return [];
262
+ }
263
+
264
+ const arg = args[currentArgIndex];
265
+ if (!arg) {
266
+ return [];
267
+ }
268
+
269
+ // 如果是文件或目录类型,可以添加文件/目录补全
270
+ if (arg.type === 'file' || arg.type === 'directory') {
271
+ return [{
272
+ text: context.currentArg,
273
+ description: arg.description,
274
+ type: arg.type as 'file' | 'directory',
275
+ }];
276
+ }
277
+
278
+ return [{
279
+ text: arg.name,
280
+ description: arg.description,
281
+ type: 'argument',
282
+ }];
283
+ }
284
+
285
+ /**
286
+ * 获取上下文的选项
287
+ */
288
+ private static getOptionsForContext(command: CommandDefinition, subcommand?: string): OptionDefinition[] {
289
+ if (!subcommand) {
290
+ return command.options || [];
291
+ }
292
+
293
+ const sub = command.subcommands?.[subcommand];
294
+ return sub?.options || [];
295
+ }
296
+
297
+ /**
298
+ * 获取上下文的参数
299
+ */
300
+ private static getArgsForContext(command: CommandDefinition, subcommand?: string): ArgumentDefinition[] {
301
+ if (!subcommand) {
302
+ return command.args || [];
303
+ }
304
+
305
+ const sub = command.subcommands?.[subcommand];
306
+ return sub?.args || [];
307
+ }
308
+
309
+ /**
310
+ * 显示补全提示
311
+ */
312
+ static showCompletionTip(context: CompletionContext): void {
313
+ const completions = this.getCompletions(context);
314
+
315
+ if (completions.length === 0) {
316
+ return;
317
+ }
318
+
319
+ // 过滤匹配的补全
320
+ const filtered = completions.filter(c =>
321
+ c.text.toLowerCase().startsWith(context.currentArg.toLowerCase())
322
+ );
323
+
324
+ if (filtered.length === 0) {
325
+ return;
326
+ }
327
+
328
+ console.log('');
329
+ console.log(`${Colors.info} Available completions:${Colors.reset}`);
330
+ console.log('');
331
+
332
+ const maxTextLength = Math.max(...filtered.map(c => c.text.length));
333
+ filtered.forEach(c => {
334
+ const typeIcon = this.getTypeIcon(c.type);
335
+ const text = c.text.padEnd(maxTextLength + 2);
336
+ const desc = c.description || '';
337
+ console.log(` ${typeIcon} ${Colors.primary}${text}${Colors.reset}${Colors.dim}${desc}${Colors.reset}`);
338
+ });
339
+
340
+ console.log('');
341
+ }
342
+
343
+ /**
344
+ * 获取类型图标
345
+ */
346
+ private static getTypeIcon(type?: string): string {
347
+ const icons: Record<string, string> = {
348
+ command: '⌘',
349
+ argument: '◇',
350
+ option: '⚙',
351
+ file: '📄',
352
+ directory: '📁',
353
+ };
354
+ return icons[type || ''] || icons['•'];
355
+ }
356
+
357
+ /**
358
+ * 智能提示
359
+ */
360
+ static showSmartHint(input: string): void {
361
+ const trimmed = input.trim();
362
+
363
+ // 如果输入以 - 开头,可能是选项
364
+ if (trimmed.startsWith('-')) {
365
+ console.log(`${Colors.dim} 💡 Tip: Use ${Colors.info}--help${Colors.dim} to see all options${Colors.reset}`);
366
+ return;
367
+ }
368
+
369
+ // 如果输入是已知命令
370
+ if (this.commands.has(trimmed)) {
371
+ const cmd = this.commands.get(trimmed);
372
+ if (cmd?.subcommands && Object.keys(cmd.subcommands).length > 0) {
373
+ const subcommands = Object.keys(cmd.subcommands).join(', ');
374
+ console.log(`${Colors.dim} 💡 Tip: ${Colors.primary}${trimmed}${Colors.dim} has subcommands: ${Colors.info}${subcommands}${Colors.reset}`);
375
+ }
376
+ return;
377
+ }
378
+
379
+ // 模糊匹配
380
+ const matches = Array.from(this.commands.keys()).filter(cmd =>
381
+ cmd.includes(trimmed.toLowerCase()) || trimmed.toLowerCase().includes(cmd)
382
+ );
383
+
384
+ if (matches.length > 0 && matches.length < 5) {
385
+ console.log(`${Colors.dim} 💡 Did you mean: ${matches.map(m => Colors.info + m + Colors.dim).join(', ')}?${Colors.reset}`);
386
+ }
387
+ }
388
+ }
@@ -0,0 +1,226 @@
1
+ // ============================================================================
2
+ // EnhancedCompleter Tests
3
+ // ============================================================================
4
+
5
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
6
+ import { EnhancedCompleter } from './EnhancedCompleter.js';
7
+ import type { ConfigManager } from '../core/config/ConfigManager.js';
8
+
9
+ // Mock ConfigManager
10
+ function createMockConfigManager(): ConfigManager {
11
+ return {
12
+ getConfig: vi.fn(() => ({
13
+ core: {
14
+ defaultModel: 'test-model',
15
+ maxTokens: 4096,
16
+ temperature: 0.7,
17
+ },
18
+ models: {
19
+ providers: {
20
+ 'test-provider': {
21
+ type: 'openai-compatible',
22
+ baseUrl: 'https://api.test.com',
23
+ models: {
24
+ 'test-model-1': {
25
+ name: 'test-model-1',
26
+ contextWindow: 4096,
27
+ maxOutput: 2048,
28
+ pricing: { input: 0.001, output: 0.002 },
29
+ },
30
+ 'test-model-2': {
31
+ name: 'test-model-2',
32
+ contextWindow: 8192,
33
+ maxOutput: 4096,
34
+ pricing: { input: 0.002, output: 0.004 },
35
+ },
36
+ },
37
+ },
38
+ 'ollama': {
39
+ type: 'ollama',
40
+ baseUrl: 'http://localhost:11434',
41
+ models: {
42
+ 'llama3.2': {
43
+ name: 'llama3.2',
44
+ contextWindow: 128000,
45
+ maxOutput: 4096,
46
+ pricing: { input: 0, output: 0 },
47
+ },
48
+ },
49
+ },
50
+ },
51
+ aliases: {
52
+ 'alias-model': 'test-model-1',
53
+ },
54
+ },
55
+ })),
56
+ load: vi.fn(),
57
+ save: vi.fn(),
58
+ getModelConfig: vi.fn(),
59
+ } as unknown as ConfigManager;
60
+ }
61
+
62
+ describe('EnhancedCompleter', () => {
63
+ let completer: EnhancedCompleter;
64
+ let mockConfigManager: ConfigManager;
65
+
66
+ beforeEach(() => {
67
+ mockConfigManager = createMockConfigManager();
68
+ completer = new EnhancedCompleter({
69
+ configManager: mockConfigManager,
70
+ cwd: '/test/workspace',
71
+ history: ['/help', '/model test-model-1', 'hello world', 'previous query'],
72
+ skills: ['pdf', 'xlsx', 'browser'],
73
+ mcpServers: [
74
+ { name: 'playwright', status: 'running' },
75
+ { name: 'filesystem', status: 'stopped' },
76
+ ],
77
+ });
78
+ });
79
+
80
+ describe('getCompletions()', () => {
81
+ describe('empty input', () => {
82
+ it('should return all commands for empty input', () => {
83
+ const completions = completer.getCompletions('');
84
+ expect(completions.length).toBeGreaterThan(0);
85
+ expect(completions[0].type).toBe('command');
86
+ });
87
+ });
88
+
89
+ describe('command completions', () => {
90
+ it('should complete /help command', () => {
91
+ const completions = completer.getCompletions('/hel');
92
+ expect(completions.some(c => c.text === '/help')).toBe(true);
93
+ });
94
+
95
+ it('should complete /model command', () => {
96
+ const completions = completer.getCompletions('/mod');
97
+ expect(completions.some(c => c.text === '/model')).toBe(true);
98
+ });
99
+
100
+ it('should show all commands starting with /', () => {
101
+ const completions = completer.getCompletions('/');
102
+ expect(completions.length).toBeGreaterThan(5);
103
+ completions.forEach(c => {
104
+ expect(c.text.startsWith('/')).toBe(true);
105
+ });
106
+ });
107
+ });
108
+
109
+ describe('model completions', () => {
110
+ it('should show all models after /model ', () => {
111
+ const completions = completer.getCompletions('/model ');
112
+ expect(completions.length).toBeGreaterThan(0);
113
+ expect(completions[0].type).toBe('model');
114
+ });
115
+
116
+ it('should filter models by prefix', () => {
117
+ const completions = completer.getCompletions('/model test');
118
+ expect(completions.every(c => c.text.toLowerCase().includes('test'))).toBe(true);
119
+ });
120
+ });
121
+
122
+ describe('subcommand completions', () => {
123
+ it('should show subcommands for /skills ', () => {
124
+ const completions = completer.getCompletions('/skills ');
125
+ expect(completions.some(c => c.text === 'list')).toBe(true);
126
+ expect(completions.some(c => c.text === 'use')).toBe(true);
127
+ });
128
+
129
+ it('should show subcommands for /mcp ', () => {
130
+ const completions = completer.getCompletions('/mcp ');
131
+ expect(completions.some(c => c.text === 'status')).toBe(true);
132
+ expect(completions.some(c => c.text === 'list')).toBe(true);
133
+ });
134
+
135
+ it('should filter subcommands by prefix', () => {
136
+ const completions = completer.getCompletions('/skills us');
137
+ expect(completions.some(c => c.text === 'use')).toBe(true);
138
+ expect(completions.some(c => c.text === 'list')).toBe(false);
139
+ });
140
+ });
141
+
142
+ describe('skill completions', () => {
143
+ it('should show skills after /skills use ', () => {
144
+ const completions = completer.getCompletions('/skills use ');
145
+ expect(completions.some(c => c.text === 'pdf')).toBe(true);
146
+ expect(completions.some(c => c.text === 'xlsx')).toBe(true);
147
+ });
148
+ });
149
+
150
+ describe('history completions', () => {
151
+ it('should match history for partial input', () => {
152
+ const completions = completer.getCompletions('hel');
153
+ expect(completions.some(c => c.text === 'hello world')).toBe(true);
154
+ });
155
+
156
+ it('should not match slash commands as history', () => {
157
+ const completions = completer.getCompletions('/hel');
158
+ // Should return command type, not history
159
+ expect(completions.every(c => c.type === 'command')).toBe(true);
160
+ });
161
+ });
162
+
163
+ describe('file completions', () => {
164
+ it('should trigger file completion for @', () => {
165
+ // This will depend on actual filesystem, so just check type
166
+ const completions = completer.getCompletions('@');
167
+ expect(completions.every(c => c.type === 'file' || c.type === 'directory')).toBe(true);
168
+ });
169
+ });
170
+
171
+ describe('shell completions', () => {
172
+ it('should suggest common commands for !', () => {
173
+ const completions = completer.getCompletions('!gi');
174
+ expect(completions.some(c => c.text === 'git')).toBe(true);
175
+ });
176
+ });
177
+ });
178
+
179
+ describe('updateHistory()', () => {
180
+ it('should update history for completions', () => {
181
+ completer.updateHistory(['new', 'history']);
182
+ const completions = completer.getCompletions('new');
183
+ expect(completions.some(c => c.text === 'new')).toBe(true);
184
+ });
185
+ });
186
+
187
+ describe('updateSkills()', () => {
188
+ it('should update skills for completions', () => {
189
+ completer.updateSkills(['new-skill']);
190
+ const completions = completer.getCompletions('/skills use ');
191
+ expect(completions.some(c => c.text === 'new-skill')).toBe(true);
192
+ });
193
+ });
194
+
195
+ describe('updateMcpServers()', () => {
196
+ it('should update MCP servers for completions', () => {
197
+ completer.updateMcpServers([{ name: 'new-server', status: 'running' }]);
198
+ // MCP server completions are for /mcp tools command
199
+ const completions = completer.getCompletions('/mcp tools ');
200
+ expect(completions.some(c => c.text === 'new-server')).toBe(true);
201
+ });
202
+ });
203
+
204
+ describe('applyCompletion()', () => {
205
+ it('should apply completion to input', () => {
206
+ const result = completer.applyCompletion('/mod', { text: '/model ', displayText: '/model', description: '', type: 'command', priority: 100 }, 4);
207
+ expect(result.text).toBe('/model ');
208
+ expect(result.cursorPos).toBe(7);
209
+ });
210
+ });
211
+
212
+ describe('formatCompletion()', () => {
213
+ it('should format completion with icon', () => {
214
+ const formatted = completer.formatCompletion({
215
+ text: 'test',
216
+ displayText: 'test',
217
+ description: 'Test description',
218
+ type: 'command',
219
+ priority: 100,
220
+ }, 40);
221
+
222
+ expect(formatted).toContain('test');
223
+ expect(formatted).toContain('Test description');
224
+ });
225
+ });
226
+ });