thatgfsj-code 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +131 -0
  2. package/DEVELOPMENT.md +286 -0
  3. package/README.md +131 -30
  4. package/dist/core/ai-engine.d.ts +17 -0
  5. package/dist/core/ai-engine.d.ts.map +1 -1
  6. package/dist/core/ai-engine.js +23 -7
  7. package/dist/core/ai-engine.js.map +1 -1
  8. package/dist/index.js +18 -27
  9. package/dist/index.js.map +1 -1
  10. package/dist/repl/input.d.ts +8 -0
  11. package/dist/repl/input.d.ts.map +1 -1
  12. package/dist/repl/input.js +12 -0
  13. package/dist/repl/input.js.map +1 -1
  14. package/dist/repl/loop.d.ts +56 -0
  15. package/dist/repl/loop.d.ts.map +1 -1
  16. package/dist/repl/loop.js +333 -4
  17. package/dist/repl/loop.js.map +1 -1
  18. package/dist/repl/output.d.ts.map +1 -1
  19. package/dist/repl/output.js +3 -1
  20. package/dist/repl/output.js.map +1 -1
  21. package/dist/repl/welcome.js +1 -1
  22. package/dist/repl/welcome.js.map +1 -1
  23. package/docs/API_KEY_GUIDE.md +6 -0
  24. package/docs/FAQ.md +25 -3
  25. package/package.json +35 -3
  26. package/.patches/0001-fix-repl-replace-readline-with-inquirer-input-for-ke.patch +0 -279
  27. package/.patches/0002-fix-repl-stream-AI-output-directly-to-stdout-so-term.patch +0 -564
  28. package/.patches/0003-fix-session-break-the-hallucination-loop-in-assistan.patch +0 -194
  29. package/.patches/0004-chore-release-bump-version-to-0.2.1.patch +0 -24
  30. package/ROADMAP.md +0 -107
  31. package/src/agent/core.ts +0 -179
  32. package/src/agent/index.ts +0 -8
  33. package/src/agent/intent.ts +0 -181
  34. package/src/agent/streaming.ts +0 -132
  35. package/src/core/ai-engine.ts +0 -437
  36. package/src/core/cli.ts +0 -171
  37. package/src/core/config.ts +0 -147
  38. package/src/core/context-compactor.ts +0 -245
  39. package/src/core/hooks.ts +0 -196
  40. package/src/core/permissions.ts +0 -308
  41. package/src/core/session.ts +0 -165
  42. package/src/core/skills.ts +0 -208
  43. package/src/core/state.ts +0 -120
  44. package/src/core/subagent.ts +0 -195
  45. package/src/core/system-prompt.ts +0 -163
  46. package/src/core/tool-registry.ts +0 -157
  47. package/src/core/types.ts +0 -280
  48. package/src/index.ts +0 -544
  49. package/src/mcp/client.ts +0 -330
  50. package/src/repl/index.ts +0 -8
  51. package/src/repl/input.ts +0 -139
  52. package/src/repl/loop.ts +0 -280
  53. package/src/repl/output.ts +0 -222
  54. package/src/repl/welcome.ts +0 -296
  55. package/src/tools/file.ts +0 -117
  56. package/src/tools/git.ts +0 -132
  57. package/src/tools/index.ts +0 -48
  58. package/src/tools/search.ts +0 -263
  59. package/src/tools/shell.ts +0 -181
  60. package/src/utils/diff-preview.ts +0 -202
  61. package/src/utils/index.ts +0 -8
  62. package/src/utils/memory.ts +0 -223
  63. package/src/utils/project-context.ts +0 -207
  64. package/tsconfig.json +0 -19
package/src/repl/loop.ts DELETED
@@ -1,280 +0,0 @@
1
- /**
2
- * REPL Loop
3
- * Main interactive loop with streaming support
4
- *
5
- * S07-fix:
6
- * - 输入端换用 REPLInput (基于 @inquirer/input),支持方向键/小键盘
7
- * - 流式输出保留完整内容(用户可滚动终端查看历史),不跳顶不刷屏
8
- * - Ctrl+C 行为:有未提交内容=>清空当前输入;空输入两次=>退出
9
- */
10
-
11
- import chalk from 'chalk';
12
- import { REPLInput, type PromptResult } from './input.js';
13
- import { REPLOutput } from './output.js';
14
- import { AIEngine } from '../core/ai-engine.js';
15
- import { SessionManager } from '../core/session.js';
16
- import { ConfigManager } from '../core/config.js';
17
- import { SystemPromptBuilder } from '../core/system-prompt.js';
18
- import { getBuiltInTools } from '../tools/index.js';
19
-
20
- export class REPLLoop {
21
- private input: REPLInput;
22
- private output: REPLOutput;
23
- private ai: AIEngine | null = null;
24
- private session: SessionManager | null = null;
25
- private running: boolean = false;
26
- // Used to abort an in-flight AI stream when the user presses Ctrl+C twice
27
- private streamAbort: AbortController | null = null;
28
-
29
- constructor() {
30
- this.input = new REPLInput();
31
- this.output = new REPLOutput();
32
- }
33
-
34
- /**
35
- * Initialize the REPL
36
- * S04: Use SystemPromptBuilder for dynamic prompt construction
37
- */
38
- async init(): Promise<void> {
39
- const config = await ConfigManager.load();
40
-
41
- this.ai = new AIEngine(config);
42
-
43
- const builtInTools = getBuiltInTools();
44
- for (const tool of builtInTools) {
45
- this.ai!.registerTool(tool);
46
- }
47
-
48
- this.session = new SessionManager();
49
-
50
- const promptBuilder = new SystemPromptBuilder({
51
- cwd: process.cwd(),
52
- tools: builtInTools,
53
- permissionMode: 'ask'
54
- });
55
- const systemPrompt = promptBuilder.build();
56
-
57
- this.session.addMessage('system', systemPrompt);
58
-
59
- this.running = true;
60
-
61
- // 全局 SIGINT 处理:第一次按下 abort 当前 AI stream,第二次退出 REPL
62
- process.on('SIGINT', () => {
63
- if (this.streamAbort) {
64
- this.streamAbort.abort();
65
- this.streamAbort = null;
66
- this.output.stopSpinner();
67
- this.output.printWarning('\n⏹ Generation cancelled (Ctrl+C again to exit)');
68
- return;
69
- }
70
- // 没有在生成:交给 REPLInput 处理(它会检测是否要退出)
71
- this.output.printWarning('\n(press Ctrl+C again on an empty prompt to exit)');
72
- });
73
- }
74
-
75
- /**
76
- * Start the REPL loop
77
- */
78
- async start(): Promise<void> {
79
- await this.init();
80
-
81
- this.output.clear();
82
- this.output.printBanner();
83
- this.output.printInfo('\nType "help" for available commands\n');
84
- this.output.printDivider();
85
-
86
- while (this.running) {
87
- let result: PromptResult;
88
- try {
89
- result = await this.input.prompt();
90
- } catch (err: any) {
91
- this.output.printError(err?.message ?? String(err));
92
- continue;
93
- }
94
-
95
- if (result.kind === 'cancelled') {
96
- if (this.input.shouldExitOnCancel()) {
97
- this.output.printInfo('\n👋 Goodbye!');
98
- this.running = false;
99
- break;
100
- }
101
- // 继续让用户输入
102
- continue;
103
- }
104
-
105
- // input layer 自动重置 cancel 计数,无需手动调
106
-
107
- const userInput = result.value;
108
- if (!userInput) continue;
109
-
110
- const handled = await this.handleCommand(userInput);
111
- if (handled) continue;
112
-
113
- await this.processInput(userInput);
114
- }
115
-
116
- // 清理全局监听
117
- process.removeAllListeners('SIGINT');
118
- }
119
-
120
- /**
121
- * Handle built-in commands
122
- */
123
- private async handleCommand(input: string): Promise<boolean> {
124
- const cmd = input.toLowerCase().trim();
125
-
126
- switch (cmd) {
127
- case 'exit':
128
- case 'quit':
129
- case '\\x03':
130
- this.output.printInfo('\n👋 Goodbye!');
131
- this.running = false;
132
- return true;
133
-
134
- case 'clear':
135
- this.output.clear();
136
- this.output.printBanner();
137
- return true;
138
-
139
- case 'context':
140
- this.showContext();
141
- return true;
142
-
143
- case 'history':
144
- this.showHistory();
145
- return true;
146
-
147
- case 'tools':
148
- this.showTools();
149
- return true;
150
-
151
- case 'help':
152
- this.output.printHelp();
153
- return true;
154
-
155
- case 'models':
156
- case 'providers':
157
- this.showProviders();
158
- return true;
159
-
160
- default:
161
- return false;
162
- }
163
- }
164
-
165
- /**
166
- * Process user input with AI (S01: streaming, no prompt reset)
167
- *
168
- * 关键修复:
169
- * - 不再调用 rl.question,所以不会 reset 终端
170
- * - chunk 直接 process.stdout.write,完整保留所有输出,用户可滚动查看
171
- * - spinner 在第一个 chunk 到达时停止,然后正常流式
172
- */
173
- private async processInput(input: string): Promise<void> {
174
- if (!this.ai || !this.session) {
175
- this.output.printError('AI not initialized');
176
- return;
177
- }
178
-
179
- this.session.addMessage('user', input);
180
-
181
- // 视觉提示用户:AI 开始工作,但不阻塞,可滚动
182
- this.output.printInfo(chalk.gray('🤖 Thinking...\n'));
183
-
184
- let fullResponse = '';
185
- let firstChunk = true;
186
- this.streamAbort = new AbortController();
187
-
188
- try {
189
- const stream = (this.ai as any).chatStream(this.session.getMessages());
190
- for await (const chunk of stream) {
191
- if (firstChunk) {
192
- // 第一个 chunk 到来,把 "Thinking..." 那行通过 ANSI 清除
193
- // 但不能清光——只把光标移到下一行的开头即可,内容自然出现在前面
194
- firstChunk = false;
195
- }
196
- // 关键:直接 stdout.write,允许任意长度、可滚动
197
- process.stdout.write(chunk);
198
- fullResponse += chunk;
199
-
200
- if (this.streamAbort.signal.aborted) break;
201
- }
202
-
203
- console.log(); // 流结束换行
204
- } catch (error: any) {
205
- if (error?.name === 'AbortError' || this.streamAbort.signal.aborted) {
206
- console.log();
207
- this.output.printWarning('[cancelled]');
208
- } else {
209
- this.output.printError(error.message);
210
- }
211
- } finally {
212
- this.streamAbort = null;
213
- }
214
-
215
- const accepted = this.session.addMessage('assistant', fullResponse);
216
- if (!accepted) {
217
- this.output.printWarning(
218
- chalk.yellow(
219
- `⚠️ 上轮回复包含 "[已中断]" 等污染标记,已自动丢弃以避免循环。本次回复不会基于它继续;请重说你的问题。\n` +
220
- `(本次会话已累计过滤 ${this.session.getDroppedCount()} 条污染消息)`
221
- )
222
- );
223
- }
224
- this.session.truncate(20);
225
- }
226
-
227
- private showContext(): void {
228
- const cwd = process.cwd();
229
- this.output.printHeader('📁 Project Context');
230
- this.output.printInfo(`Working directory: ${cwd}`);
231
- }
232
-
233
- private showHistory(): void {
234
- const items = this.input.getHistory();
235
- this.output.printHeader('📜 Command History');
236
- if (items.length === 0) {
237
- this.output.printInfo('(no history yet)');
238
- return;
239
- }
240
- items.forEach((h, i) => this.output.printInfo(` ${i + 1}. ${h}`));
241
- }
242
-
243
- private showTools(): void {
244
- this.output.printHeader('🔧 Available Tools');
245
- this.output.printInfo(' file - File operations (read, write, list, delete)');
246
- this.output.printInfo(' shell - Execute shell commands');
247
- this.output.printInfo(' git - Git operations (coming soon)');
248
- }
249
-
250
- private showProviders(): void {
251
- this.output.printHeader('🌐 Available Providers');
252
- this.output.printInfo(' siliconflow - 硅基流动 (default)');
253
- this.output.printInfo(' minimax - MiniMax M2.5');
254
- this.output.printInfo(' openai - OpenAI GPT');
255
- this.output.printInfo(' anthropic - Anthropic Claude');
256
- }
257
-
258
- /**
259
- * Stop the REPL
260
- */
261
- stop(): void {
262
- this.running = false;
263
- if (this.streamAbort) {
264
- this.streamAbort.abort();
265
- this.streamAbort = null;
266
- }
267
- this.output.stopStreaming();
268
- }
269
-
270
- /**
271
- * Handle interrupt (Ctrl+C) while streaming
272
- */
273
- interrupt(): void {
274
- if (this.streamAbort) {
275
- this.streamAbort.abort();
276
- this.streamAbort = null;
277
- }
278
- this.output.stopStreaming();
279
- }
280
- }
@@ -1,222 +0,0 @@
1
- /**
2
- * REPL Output Handler
3
- * Handles streaming output, colors, and formatting
4
- */
5
-
6
- import chalk from 'chalk';
7
- import ora, { Ora } from 'ora';
8
-
9
- export class REPLOutput {
10
- private spinner: Ora | null = null;
11
- private isStreaming: boolean = false;
12
-
13
- // Color schemes
14
- readonly colors = {
15
- user: chalk.cyan,
16
- assistant: chalk.green,
17
- system: chalk.yellow,
18
- error: chalk.red,
19
- warning: chalk.yellow,
20
- info: chalk.gray,
21
- code: chalk.bgBlack,
22
- success: chalk.green,
23
- tool: chalk.magenta,
24
- diff: {
25
- add: chalk.green,
26
- remove: chalk.red,
27
- header: chalk.blue
28
- }
29
- };
30
-
31
- /**
32
- * Print user input
33
- */
34
- printUser(input: string): void {
35
- console.log(this.colors.user('\n👤 ') + input);
36
- }
37
-
38
- /**
39
- * Print assistant response
40
- */
41
- printAssistant(content: string): void {
42
- console.log(this.colors.assistant('\n🤖 ') + content);
43
- }
44
-
45
- /**
46
- * Print tool execution
47
- */
48
- printTool(name: string, result: string): void {
49
- console.log(this.colors.tool(`\n🔧 Tool: ${name}`));
50
- console.log(this.colors.info(result));
51
- }
52
-
53
- /**
54
- * Print error
55
- */
56
- printError(error: string): void {
57
- console.error(this.colors.error(`\n❌ Error: ${error}`));
58
- }
59
-
60
- /**
61
- * Print warning
62
- */
63
- printWarning(warning: string): void {
64
- console.warn(this.colors.warning(`\n⚠️ ${warning}`));
65
- }
66
-
67
- /**
68
- * Print success
69
- */
70
- printSuccess(message: string): void {
71
- console.log(this.colors.success(`\n✅ ${message}`));
72
- }
73
-
74
- /**
75
- * Print info
76
- */
77
- printInfo(message: string): void {
78
- console.log(this.colors.info(message));
79
- }
80
-
81
- /**
82
- * Print code block
83
- */
84
- printCode(code: string, language: string = ''): void {
85
- console.log(chalk.bgBlack.gray('```' + language));
86
- console.log(chalk.bgBlack(code));
87
- console.log(chalk.bgBlack.gray('```'));
88
- }
89
-
90
- /**
91
- * Start spinner
92
- */
93
- startSpinner(text: string): void {
94
- this.spinner = ora({
95
- text,
96
- color: 'cyan',
97
- spinner: 'dots'
98
- }).start();
99
- }
100
-
101
- /**
102
- * Update spinner text
103
- */
104
- updateSpinner(text: string): void {
105
- if (this.spinner) {
106
- this.spinner.text = text;
107
- }
108
- }
109
-
110
- /**
111
- * Stop spinner with success
112
- */
113
- stopSpinnerSuccess(text?: string): void {
114
- if (this.spinner) {
115
- this.spinner.succeed(text || this.spinner.text);
116
- this.spinner = null;
117
- }
118
- }
119
-
120
- /**
121
- * Stop spinner with error
122
- */
123
- stopSpinnerError(text?: string): void {
124
- if (this.spinner) {
125
- this.spinner.fail(text || this.spinner.text);
126
- this.spinner = null;
127
- }
128
- }
129
-
130
- /**
131
- * Stop spinner
132
- */
133
- stopSpinner(): void {
134
- if (this.spinner) {
135
- this.spinner.stop();
136
- this.spinner = null;
137
- }
138
- }
139
-
140
- /**
141
- * Print divider
142
- */
143
- printDivider(char: string = '─', length: number = 40): void {
144
- console.log(chalk.gray(char.repeat(length)));
145
- }
146
-
147
- /**
148
- * Print header
149
- */
150
- printHeader(text: string): void {
151
- this.printDivider();
152
- console.log(chalk.cyan(text));
153
- this.printDivider();
154
- }
155
-
156
- /**
157
- * Print with typewriter effect (streaming)
158
- */
159
- async printTypewriter(text: string, delay: number = 20): Promise<void> {
160
- this.isStreaming = true;
161
-
162
- process.stdout.write(chalk.green('\n🤖 '));
163
-
164
- for (let i = 0; i < text.length; i++) {
165
- if (!this.isStreaming) {
166
- // User interrupted, print rest immediately
167
- process.stdout.write(text.slice(i));
168
- break;
169
- }
170
- process.stdout.write(text[i]);
171
- await new Promise(r => setTimeout(r, delay));
172
- }
173
-
174
- process.stdout.write('\n');
175
- }
176
-
177
- /**
178
- * Stop streaming (for Ctrl+C)
179
- */
180
- stopStreaming(): void {
181
- this.isStreaming = false;
182
- }
183
-
184
- /**
185
- * Clear screen
186
- */
187
- clear(): void {
188
- console.clear();
189
- }
190
-
191
- /**
192
- * Print banner
193
- */
194
- printBanner(): void {
195
- console.log(chalk.cyan(`
196
- ╔═══════════════════════════════════════╗
197
- ║ 🤖 Thatgfsj Code v0.2.0 ║
198
- ║ Claude Code Style REPL ║
199
- ╚═══════════════════════════════════════╝
200
- `));
201
- }
202
-
203
- /**
204
- * Print help
205
- */
206
- printHelp(): void {
207
- console.log(`
208
- ${chalk.cyan('Commands:')}
209
- ${chalk.gray('exit, Ctrl+C')} - Exit the REPL
210
- ${chalk.gray('clear')} - Clear screen
211
- ${chalk.gray('context')} - Show project context
212
- ${chalk.gray('history')} - Show command history
213
- ${chalk.gray('tools')} - List available tools
214
- ${chalk.gray('help')} - Show this help
215
-
216
- ${chalk.cyan('Tips:')}
217
- • Use ${chalk.gray('\\')} at end for multiline input
218
- • Use ${chalk.gray('↑/↓')} for command history
219
- • Use ${chalk.gray('Tab')} for auto-complete
220
- `);
221
- }
222
- }