thatgfsj-code 0.2.1

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 (179) hide show
  1. package/.patches/0001-fix-repl-replace-readline-with-inquirer-input-for-ke.patch +279 -0
  2. package/.patches/0002-fix-repl-stream-AI-output-directly-to-stdout-so-term.patch +564 -0
  3. package/.patches/0003-fix-session-break-the-hallucination-loop-in-assistan.patch +194 -0
  4. package/.patches/0004-chore-release-bump-version-to-0.2.1.patch +24 -0
  5. package/LICENSE +21 -0
  6. package/README.md +100 -0
  7. package/ROADMAP.md +107 -0
  8. package/dist/agent/core.d.ts +56 -0
  9. package/dist/agent/core.d.ts.map +1 -0
  10. package/dist/agent/core.js +142 -0
  11. package/dist/agent/core.js.map +1 -0
  12. package/dist/agent/index.d.ts +8 -0
  13. package/dist/agent/index.d.ts.map +1 -0
  14. package/dist/agent/index.js +8 -0
  15. package/dist/agent/index.js.map +1 -0
  16. package/dist/agent/intent.d.ts +36 -0
  17. package/dist/agent/intent.d.ts.map +1 -0
  18. package/dist/agent/intent.js +146 -0
  19. package/dist/agent/intent.js.map +1 -0
  20. package/dist/agent/streaming.d.ts +50 -0
  21. package/dist/agent/streaming.d.ts.map +1 -0
  22. package/dist/agent/streaming.js +110 -0
  23. package/dist/agent/streaming.js.map +1 -0
  24. package/dist/core/ai-engine.d.ts +59 -0
  25. package/dist/core/ai-engine.d.ts.map +1 -0
  26. package/dist/core/ai-engine.js +378 -0
  27. package/dist/core/ai-engine.js.map +1 -0
  28. package/dist/core/cli.d.ts +21 -0
  29. package/dist/core/cli.d.ts.map +1 -0
  30. package/dist/core/cli.js +143 -0
  31. package/dist/core/cli.js.map +1 -0
  32. package/dist/core/config.d.ts +38 -0
  33. package/dist/core/config.d.ts.map +1 -0
  34. package/dist/core/config.js +129 -0
  35. package/dist/core/config.js.map +1 -0
  36. package/dist/core/context-compactor.d.ts +54 -0
  37. package/dist/core/context-compactor.d.ts.map +1 -0
  38. package/dist/core/context-compactor.js +197 -0
  39. package/dist/core/context-compactor.js.map +1 -0
  40. package/dist/core/hooks.d.ts +75 -0
  41. package/dist/core/hooks.d.ts.map +1 -0
  42. package/dist/core/hooks.js +146 -0
  43. package/dist/core/hooks.js.map +1 -0
  44. package/dist/core/permissions.d.ts +52 -0
  45. package/dist/core/permissions.d.ts.map +1 -0
  46. package/dist/core/permissions.js +237 -0
  47. package/dist/core/permissions.js.map +1 -0
  48. package/dist/core/session.d.ts +54 -0
  49. package/dist/core/session.d.ts.map +1 -0
  50. package/dist/core/session.js +147 -0
  51. package/dist/core/session.js.map +1 -0
  52. package/dist/core/skills.d.ts +60 -0
  53. package/dist/core/skills.d.ts.map +1 -0
  54. package/dist/core/skills.js +175 -0
  55. package/dist/core/skills.js.map +1 -0
  56. package/dist/core/state.d.ts +60 -0
  57. package/dist/core/state.d.ts.map +1 -0
  58. package/dist/core/state.js +106 -0
  59. package/dist/core/state.js.map +1 -0
  60. package/dist/core/subagent.d.ts +68 -0
  61. package/dist/core/subagent.d.ts.map +1 -0
  62. package/dist/core/subagent.js +142 -0
  63. package/dist/core/subagent.js.map +1 -0
  64. package/dist/core/system-prompt.d.ts +40 -0
  65. package/dist/core/system-prompt.d.ts.map +1 -0
  66. package/dist/core/system-prompt.js +138 -0
  67. package/dist/core/system-prompt.js.map +1 -0
  68. package/dist/core/tool-registry.d.ts +53 -0
  69. package/dist/core/tool-registry.d.ts.map +1 -0
  70. package/dist/core/tool-registry.js +138 -0
  71. package/dist/core/tool-registry.js.map +1 -0
  72. package/dist/core/types.d.ts +206 -0
  73. package/dist/core/types.d.ts.map +1 -0
  74. package/dist/core/types.js +127 -0
  75. package/dist/core/types.js.map +1 -0
  76. package/dist/index.d.ts +7 -0
  77. package/dist/index.d.ts.map +1 -0
  78. package/dist/index.js +481 -0
  79. package/dist/index.js.map +1 -0
  80. package/dist/mcp/client.d.ts +83 -0
  81. package/dist/mcp/client.d.ts.map +1 -0
  82. package/dist/mcp/client.js +267 -0
  83. package/dist/mcp/client.js.map +1 -0
  84. package/dist/repl/index.d.ts +8 -0
  85. package/dist/repl/index.d.ts.map +1 -0
  86. package/dist/repl/index.js +8 -0
  87. package/dist/repl/index.js.map +1 -0
  88. package/dist/repl/input.d.ts +61 -0
  89. package/dist/repl/input.d.ts.map +1 -0
  90. package/dist/repl/input.js +127 -0
  91. package/dist/repl/input.js.map +1 -0
  92. package/dist/repl/loop.d.ts +53 -0
  93. package/dist/repl/loop.d.ts.map +1 -0
  94. package/dist/repl/loop.js +241 -0
  95. package/dist/repl/loop.js.map +1 -0
  96. package/dist/repl/output.d.ts +105 -0
  97. package/dist/repl/output.d.ts.map +1 -0
  98. package/dist/repl/output.js +197 -0
  99. package/dist/repl/output.js.map +1 -0
  100. package/dist/repl/welcome.d.ts +68 -0
  101. package/dist/repl/welcome.d.ts.map +1 -0
  102. package/dist/repl/welcome.js +249 -0
  103. package/dist/repl/welcome.js.map +1 -0
  104. package/dist/tools/file.d.ts +46 -0
  105. package/dist/tools/file.d.ts.map +1 -0
  106. package/dist/tools/file.js +100 -0
  107. package/dist/tools/file.js.map +1 -0
  108. package/dist/tools/git.d.ts +52 -0
  109. package/dist/tools/git.d.ts.map +1 -0
  110. package/dist/tools/git.js +112 -0
  111. package/dist/tools/git.js.map +1 -0
  112. package/dist/tools/index.d.ts +22 -0
  113. package/dist/tools/index.d.ts.map +1 -0
  114. package/dist/tools/index.js +43 -0
  115. package/dist/tools/index.js.map +1 -0
  116. package/dist/tools/search.d.ts +36 -0
  117. package/dist/tools/search.d.ts.map +1 -0
  118. package/dist/tools/search.js +223 -0
  119. package/dist/tools/search.js.map +1 -0
  120. package/dist/tools/shell.d.ts +57 -0
  121. package/dist/tools/shell.d.ts.map +1 -0
  122. package/dist/tools/shell.js +153 -0
  123. package/dist/tools/shell.js.map +1 -0
  124. package/dist/utils/diff-preview.d.ts +42 -0
  125. package/dist/utils/diff-preview.d.ts.map +1 -0
  126. package/dist/utils/diff-preview.js +174 -0
  127. package/dist/utils/diff-preview.js.map +1 -0
  128. package/dist/utils/index.d.ts +8 -0
  129. package/dist/utils/index.d.ts.map +1 -0
  130. package/dist/utils/index.js +8 -0
  131. package/dist/utils/index.js.map +1 -0
  132. package/dist/utils/memory.d.ts +81 -0
  133. package/dist/utils/memory.d.ts.map +1 -0
  134. package/dist/utils/memory.js +186 -0
  135. package/dist/utils/memory.js.map +1 -0
  136. package/dist/utils/project-context.d.ts +62 -0
  137. package/dist/utils/project-context.d.ts.map +1 -0
  138. package/dist/utils/project-context.js +197 -0
  139. package/dist/utils/project-context.js.map +1 -0
  140. package/docs/API_KEY_GUIDE.md +236 -0
  141. package/docs/FAQ.md +182 -0
  142. package/install.bat +63 -0
  143. package/install.ps1 +238 -0
  144. package/install.sh +113 -0
  145. package/package.json +36 -0
  146. package/src/agent/core.ts +179 -0
  147. package/src/agent/index.ts +8 -0
  148. package/src/agent/intent.ts +181 -0
  149. package/src/agent/streaming.ts +132 -0
  150. package/src/core/ai-engine.ts +437 -0
  151. package/src/core/cli.ts +171 -0
  152. package/src/core/config.ts +147 -0
  153. package/src/core/context-compactor.ts +245 -0
  154. package/src/core/hooks.ts +196 -0
  155. package/src/core/permissions.ts +308 -0
  156. package/src/core/session.ts +165 -0
  157. package/src/core/skills.ts +208 -0
  158. package/src/core/state.ts +120 -0
  159. package/src/core/subagent.ts +195 -0
  160. package/src/core/system-prompt.ts +163 -0
  161. package/src/core/tool-registry.ts +157 -0
  162. package/src/core/types.ts +280 -0
  163. package/src/index.ts +544 -0
  164. package/src/mcp/client.ts +330 -0
  165. package/src/repl/index.ts +8 -0
  166. package/src/repl/input.ts +139 -0
  167. package/src/repl/loop.ts +280 -0
  168. package/src/repl/output.ts +222 -0
  169. package/src/repl/welcome.ts +296 -0
  170. package/src/tools/file.ts +117 -0
  171. package/src/tools/git.ts +132 -0
  172. package/src/tools/index.ts +48 -0
  173. package/src/tools/search.ts +263 -0
  174. package/src/tools/shell.ts +181 -0
  175. package/src/utils/diff-preview.ts +202 -0
  176. package/src/utils/index.ts +8 -0
  177. package/src/utils/memory.ts +223 -0
  178. package/src/utils/project-context.ts +207 -0
  179. package/tsconfig.json +19 -0
@@ -0,0 +1,280 @@
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
+ }
@@ -0,0 +1,222 @@
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
+ }