thatgfsj-code 2.2.9 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -67
- package/dist/app/index.d.ts +28 -5
- package/dist/app/index.d.ts.map +1 -1
- package/dist/app/index.js +69 -10
- package/dist/app/index.js.map +1 -1
- package/dist/cache/fingerprint.d.ts +49 -0
- package/dist/cache/fingerprint.d.ts.map +1 -0
- package/dist/cache/fingerprint.js +100 -0
- package/dist/cache/fingerprint.js.map +1 -0
- package/dist/cache/index.d.ts +15 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +15 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/smartModel.d.ts +40 -0
- package/dist/cache/smartModel.d.ts.map +1 -0
- package/dist/cache/smartModel.js +43 -0
- package/dist/cache/smartModel.js.map +1 -0
- package/dist/cache/stats.d.ts +93 -0
- package/dist/cache/stats.d.ts.map +1 -0
- package/dist/cache/stats.js +155 -0
- package/dist/cache/stats.js.map +1 -0
- package/dist/cache/volatile.d.ts +32 -0
- package/dist/cache/volatile.d.ts.map +1 -0
- package/dist/cache/volatile.js +44 -0
- package/dist/cache/volatile.js.map +1 -0
- package/dist/cmd/index.js +49 -57
- package/dist/cmd/index.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +8 -0
- package/dist/config/index.js.map +1 -1
- package/dist/config/types.d.ts +14 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/llm/anthropic.d.ts +55 -5
- package/dist/llm/anthropic.d.ts.map +1 -1
- package/dist/llm/anthropic.js +172 -26
- package/dist/llm/anthropic.js.map +1 -1
- package/dist/llm/index.d.ts +26 -8
- package/dist/llm/index.d.ts.map +1 -1
- package/dist/llm/index.js +83 -15
- package/dist/llm/index.js.map +1 -1
- package/dist/llm/openai.d.ts +28 -8
- package/dist/llm/openai.d.ts.map +1 -1
- package/dist/llm/openai.js +58 -8
- package/dist/llm/openai.js.map +1 -1
- package/dist/llm/provider.d.ts +40 -8
- package/dist/llm/provider.d.ts.map +1 -1
- package/dist/llm/provider.js +8 -1
- package/dist/llm/provider.js.map +1 -1
- package/dist/prompts/index.d.ts +32 -0
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +33 -11
- package/dist/prompts/index.js.map +1 -1
- package/dist/session/index.d.ts +32 -3
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/index.js +51 -36
- package/dist/session/index.js.map +1 -1
- package/dist/skills/brainstorming.js +33 -33
- package/dist/skills/code-review.js +35 -35
- package/dist/skills/executing-plans.js +18 -18
- package/dist/skills/frontend-design.js +25 -25
- package/dist/skills/git-workflow.js +26 -26
- package/dist/skills/improve-architecture.js +28 -28
- package/dist/skills/neuroweave.js +37 -37
- package/dist/skills/playwright.js +62 -62
- package/dist/skills/prototype.js +20 -20
- package/dist/skills/subagent.js +19 -19
- package/dist/skills/supabase.js +34 -34
- package/dist/skills/systematic-debugging.js +34 -34
- package/dist/skills/tdd.js +29 -29
- package/dist/skills/triage.js +25 -25
- package/dist/skills/verification.js +22 -22
- package/dist/skills/writing-plans.js +32 -32
- package/dist/tools/nwt.js +16 -16
- package/dist/tui/app.d.ts.map +1 -1
- package/dist/tui/app.js +9 -2
- package/dist/tui/app.js.map +1 -1
- package/dist/tui/components/Header.d.ts +8 -0
- package/dist/tui/components/Header.d.ts.map +1 -1
- package/dist/tui/components/Header.js +17 -4
- package/dist/tui/components/Header.js.map +1 -1
- package/dist/tui/components/InitWizard.d.ts +12 -1
- package/dist/tui/components/InitWizard.d.ts.map +1 -1
- package/dist/tui/components/InitWizard.js +58 -3
- package/dist/tui/components/InitWizard.js.map +1 -1
- package/dist/tui/components/ToolCall.d.ts.map +1 -1
- package/dist/tui/components/ToolCall.js +1 -6
- package/dist/tui/components/ToolCall.js.map +1 -1
- package/dist/tui/hooks/useChat.d.ts +22 -0
- package/dist/tui/hooks/useChat.d.ts.map +1 -1
- package/dist/tui/hooks/useChat.js +82 -46
- package/dist/tui/hooks/useChat.js.map +1 -1
- package/dist/tui/hooks/useCommands.d.ts.map +1 -1
- package/dist/tui/hooks/useCommands.js +57 -0
- package/dist/tui/hooks/useCommands.js.map +1 -1
- package/dist/tui/welcome.d.ts.map +1 -1
- package/dist/tui/welcome.js +2 -3
- package/dist/tui/welcome.js.map +1 -1
- package/dist/types.d.ts +79 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -1
- package/dist/utils/stableStringify.d.ts +21 -0
- package/dist/utils/stableStringify.d.ts.map +1 -0
- package/dist/utils/stableStringify.js +53 -0
- package/dist/utils/stableStringify.js.map +1 -0
- package/dist/utils/thinking.d.ts.map +1 -1
- package/dist/utils/thinking.js +0 -3
- package/dist/utils/thinking.js.map +1 -1
- package/package.json +6 -6
- package/src/app/index.ts +180 -124
- package/src/cache/fingerprint.ts +101 -0
- package/src/cache/index.ts +15 -0
- package/src/cache/smartModel.ts +52 -0
- package/src/cache/stats.ts +199 -0
- package/src/cache/volatile.ts +47 -0
- package/src/cmd/index.tsx +288 -292
- package/src/config/index.ts +156 -148
- package/src/config/providers.ts +234 -234
- package/src/config/types.ts +67 -53
- package/src/hooks/index.ts +111 -111
- package/src/llm/anthropic.ts +388 -243
- package/src/llm/gemini.ts +169 -169
- package/src/llm/index.ts +265 -200
- package/src/llm/openai.ts +243 -196
- package/src/llm/provider.ts +66 -34
- package/src/prompts/index.ts +260 -220
- package/src/session/compactor.ts +103 -103
- package/src/session/index.ts +181 -168
- package/src/session/message.ts +42 -42
- package/src/skills/brainstorming.ts +43 -43
- package/src/skills/code-review.ts +45 -45
- package/src/skills/executing-plans.ts +27 -27
- package/src/skills/frontend-design.ts +35 -35
- package/src/skills/git-workflow.ts +36 -36
- package/src/skills/improve-architecture.ts +38 -38
- package/src/skills/index.ts +136 -136
- package/src/skills/neuroweave.ts +47 -47
- package/src/skills/playwright.ts +72 -72
- package/src/skills/prototype.ts +30 -30
- package/src/skills/subagent.ts +28 -28
- package/src/skills/supabase.ts +44 -44
- package/src/skills/systematic-debugging.ts +44 -44
- package/src/skills/tdd.ts +39 -39
- package/src/skills/triage.ts +35 -35
- package/src/skills/verification.ts +31 -31
- package/src/skills/writing-plans.ts +42 -42
- package/src/tools/nwt.ts +598 -598
- package/src/tools/types.ts +122 -122
- package/src/tui/app.tsx +199 -186
- package/src/tui/components/ChatList.tsx +41 -41
- package/src/tui/components/ChatMessage.tsx +54 -54
- package/src/tui/components/Header.tsx +56 -29
- package/src/tui/components/InitWizard.tsx +217 -132
- package/src/tui/components/Markdown.tsx +35 -35
- package/src/tui/components/ModelSelector.tsx +87 -87
- package/src/tui/components/StatusBar.tsx +30 -30
- package/src/tui/components/Thinking.tsx +17 -17
- package/src/tui/components/ToolCall.tsx +102 -107
- package/src/tui/components/UserInput.tsx +131 -131
- package/src/tui/hooks/useChat.ts +287 -238
- package/src/tui/hooks/useCommands.ts +234 -176
- package/src/tui/index.ts +6 -6
- package/src/tui/welcome.ts +177 -178
- package/src/types.ts +104 -42
- package/src/utils/diff.ts +71 -71
- package/src/utils/project.ts +99 -99
- package/src/utils/stableStringify.ts +47 -0
- package/src/utils/thinking.ts +118 -121
- package/tests/cache/fingerprint.test.ts +75 -0
- package/tests/cache/stableStringify.test.ts +43 -0
- package/tests/cache/stats.test.ts +146 -0
- package/tests/cache/volatile.test.ts +75 -0
- package/tests/smoke-pollution.mjs +78 -0
- package/tests/smoke-thinking.mjs +110 -0
- package/tests/smoke-tool-stream.mjs +69 -0
- package/tests/smoke-tool.mjs +117 -0
- package/.nwt/meta.json +0 -5
- package/dist/version.d.ts +0 -16
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js +0 -16
- package/dist/version.js.map +0 -1
- package/src/version.ts +0 -16
package/src/cmd/index.tsx
CHANGED
|
@@ -1,292 +1,288 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Thatgfsj Code - CLI Entry Point
|
|
5
|
-
*
|
|
6
|
-
* v2.2.4 (product 0.4.1): three concrete fixes for the bugs visible
|
|
7
|
-
* in the 2.2.3 (product 0.4.0) session log:
|
|
8
|
-
*
|
|
9
|
-
* 1. Encoding: chcp 65001 used to be wrapped in a silent try/catch,
|
|
10
|
-
* and stdout never had its default encoding set. On Windows this
|
|
11
|
-
* caused Chinese characters from tool output to render as
|
|
12
|
-
* mojibake (ļ ʹ ϵͳ Ƿ ...).
|
|
13
|
-
*
|
|
14
|
-
* 2. [已中断] hallucination loop: 1.0.4's SessionManager had no
|
|
15
|
-
* anti-pollution filter. When the user pressed Ctrl+C mid-
|
|
16
|
-
* stream, the truncated assistant message (containing the
|
|
17
|
-
* model's literal "[已中断]" marker) got persisted to history.
|
|
18
|
-
* On the next turn, the model saw that and kept echoing it.
|
|
19
|
-
* v2.1.0 had the right fix (_wasAborted flag + filter) — we
|
|
20
|
-
* port it back here.
|
|
21
|
-
*
|
|
22
|
-
* 3. Tool output visual: 1.0.4 printed `@@TOOL@@{...}` markers
|
|
23
|
-
* inline with text, no separator, no truncation. Long tool
|
|
24
|
-
* output (registry dumps, dir listings) bled into the next
|
|
25
|
-
* AI message. Add separator + truncation + color separation.
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
if (process.platform === 'win32') {
|
|
29
|
-
// Switch Windows console to UTF-8 BEFORE any other code runs.
|
|
30
|
-
// v2.2.3 wrapped this in `try { } catch {}` which silently swallowed
|
|
31
|
-
// failures (e.g. when running under a non-interactive shell where
|
|
32
|
-
// chcp is meaningless). Now we still try, but we also fall back to
|
|
33
|
-
// setting the codepage via the parent process's stdio handles.
|
|
34
|
-
try {
|
|
35
|
-
require('child_process').execSync('chcp 65001 >NUL', { stdio: 'ignore', windowsHide: true });
|
|
36
|
-
} catch {
|
|
37
|
-
// Non-Windows shell, or chcp unavailable (e.g. git-bash on CI) —
|
|
38
|
-
// that's fine, Node defaults to UTF-8 in that environment.
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Force stdout/stderr to UTF-8 regardless of platform. Without this,
|
|
43
|
-
// Node will emit GBK-encoded bytes for non-ASCII characters on
|
|
44
|
-
// Windows even after `chcp 65001`, because the underlying file
|
|
45
|
-
// descriptors still report a non-UTF-8 code page.
|
|
46
|
-
if (process.stdout.setDefaultEncoding) {
|
|
47
|
-
process.stdout.setDefaultEncoding('utf8');
|
|
48
|
-
}
|
|
49
|
-
if (process.stderr.setDefaultEncoding) {
|
|
50
|
-
process.stderr.setDefaultEncoding('utf8');
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
import { program } from 'commander';
|
|
54
|
-
import chalk from 'chalk';
|
|
55
|
-
import { App } from '../app/index.js';
|
|
56
|
-
import { WelcomeScreen } from '../tui/welcome.js';
|
|
57
|
-
import { compressThinking, summarizeThinking, splitThinking } from '../utils/thinking.js';
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
.
|
|
72
|
-
.
|
|
73
|
-
.
|
|
74
|
-
.
|
|
75
|
-
.option('-
|
|
76
|
-
.option('-
|
|
77
|
-
.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
console.log(chalk.
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const {
|
|
109
|
-
const {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
console.log(chalk.
|
|
117
|
-
console.log(chalk.gray('
|
|
118
|
-
console.log(
|
|
119
|
-
console.log(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
for await (const chunk of stream) {
|
|
146
|
-
if (abortCtrl.signal.aborted) break;
|
|
147
|
-
// Clear the entire "Thinking..." line — must use enough
|
|
148
|
-
// spaces to overwrite any longer thinking indicator.
|
|
149
|
-
process.stdout.write('\r' + ' '.repeat(80) + '\r');
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
//
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
console.
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
console.
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
return
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
} catch {
|
|
290
|
-
return args.length > 60 ? args.slice(0, 60) + '...' : args;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Thatgfsj Code - CLI Entry Point
|
|
5
|
+
*
|
|
6
|
+
* v2.2.4 (product 0.4.1): three concrete fixes for the bugs visible
|
|
7
|
+
* in the 2.2.3 (product 0.4.0) session log:
|
|
8
|
+
*
|
|
9
|
+
* 1. Encoding: chcp 65001 used to be wrapped in a silent try/catch,
|
|
10
|
+
* and stdout never had its default encoding set. On Windows this
|
|
11
|
+
* caused Chinese characters from tool output to render as
|
|
12
|
+
* mojibake (ļ ʹ ϵͳ Ƿ ...).
|
|
13
|
+
*
|
|
14
|
+
* 2. [已中断] hallucination loop: 1.0.4's SessionManager had no
|
|
15
|
+
* anti-pollution filter. When the user pressed Ctrl+C mid-
|
|
16
|
+
* stream, the truncated assistant message (containing the
|
|
17
|
+
* model's literal "[已中断]" marker) got persisted to history.
|
|
18
|
+
* On the next turn, the model saw that and kept echoing it.
|
|
19
|
+
* v2.1.0 had the right fix (_wasAborted flag + filter) — we
|
|
20
|
+
* port it back here.
|
|
21
|
+
*
|
|
22
|
+
* 3. Tool output visual: 1.0.4 printed `@@TOOL@@{...}` markers
|
|
23
|
+
* inline with text, no separator, no truncation. Long tool
|
|
24
|
+
* output (registry dumps, dir listings) bled into the next
|
|
25
|
+
* AI message. Add separator + truncation + color separation.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
if (process.platform === 'win32') {
|
|
29
|
+
// Switch Windows console to UTF-8 BEFORE any other code runs.
|
|
30
|
+
// v2.2.3 wrapped this in `try { } catch {}` which silently swallowed
|
|
31
|
+
// failures (e.g. when running under a non-interactive shell where
|
|
32
|
+
// chcp is meaningless). Now we still try, but we also fall back to
|
|
33
|
+
// setting the codepage via the parent process's stdio handles.
|
|
34
|
+
try {
|
|
35
|
+
require('child_process').execSync('chcp 65001 >NUL', { stdio: 'ignore', windowsHide: true });
|
|
36
|
+
} catch {
|
|
37
|
+
// Non-Windows shell, or chcp unavailable (e.g. git-bash on CI) —
|
|
38
|
+
// that's fine, Node defaults to UTF-8 in that environment.
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Force stdout/stderr to UTF-8 regardless of platform. Without this,
|
|
43
|
+
// Node will emit GBK-encoded bytes for non-ASCII characters on
|
|
44
|
+
// Windows even after `chcp 65001`, because the underlying file
|
|
45
|
+
// descriptors still report a non-UTF-8 code page.
|
|
46
|
+
if (process.stdout.setDefaultEncoding) {
|
|
47
|
+
process.stdout.setDefaultEncoding('utf8');
|
|
48
|
+
}
|
|
49
|
+
if (process.stderr.setDefaultEncoding) {
|
|
50
|
+
process.stderr.setDefaultEncoding('utf8');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
import { program } from 'commander';
|
|
54
|
+
import chalk from 'chalk';
|
|
55
|
+
import { App } from '../app/index.js';
|
|
56
|
+
import { WelcomeScreen } from '../tui/welcome.js';
|
|
57
|
+
import { compressThinking, summarizeThinking, splitThinking } from '../utils/thinking.js';
|
|
58
|
+
|
|
59
|
+
process.on('uncaughtException', (error) => {
|
|
60
|
+
console.error(chalk.red('\n Error:'), error.message);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
process.on('unhandledRejection', (reason) => {
|
|
65
|
+
console.error(chalk.red('\n Error:'), reason);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
program
|
|
70
|
+
.name('gfcode')
|
|
71
|
+
.description('Thatgfsj Code - AI Coding Assistant')
|
|
72
|
+
.version('0.5.0')
|
|
73
|
+
.argument('[prompt]', 'Task to execute (omit to start interactive mode)')
|
|
74
|
+
.option('-m, --model <model>', 'Specify model')
|
|
75
|
+
.option('-i, --interactive', 'Force interactive mode')
|
|
76
|
+
.option('--show-thinking', 'Show full <think>...</think> reasoning blocks (default: compress to one-line summary)')
|
|
77
|
+
.action(async (prompt: string | undefined, options: { model?: string; interactive?: boolean; showThinking?: boolean }) => {
|
|
78
|
+
try {
|
|
79
|
+
const app = await App.create();
|
|
80
|
+
|
|
81
|
+
// Check if API key is configured
|
|
82
|
+
if (!app.config.hasApiKey()) {
|
|
83
|
+
console.log(chalk.yellow('\n ⚠ No API key configured\n'));
|
|
84
|
+
console.log(chalk.gray(' Run ') + chalk.cyan('gfcode init') + chalk.gray(' to set up your provider.\n'));
|
|
85
|
+
const readline = await import('readline');
|
|
86
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
87
|
+
const answer = await new Promise<string>(resolve => {
|
|
88
|
+
rl.question(chalk.cyan(' Run init now? (Y/n): '), resolve);
|
|
89
|
+
});
|
|
90
|
+
rl.close();
|
|
91
|
+
if (answer.toLowerCase() !== 'n') {
|
|
92
|
+
await WelcomeScreen.interactiveSetup();
|
|
93
|
+
// Reload config after setup
|
|
94
|
+
const newApp = await App.create();
|
|
95
|
+
Object.assign(app, newApp);
|
|
96
|
+
} else {
|
|
97
|
+
process.exit(0);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (options.model) {
|
|
102
|
+
await app.config.save({ model: options.model });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!prompt || options.interactive) {
|
|
106
|
+
// Interactive mode - Ink TUI
|
|
107
|
+
const { render } = await import('ink');
|
|
108
|
+
const { TuiApp } = await import('../tui/app.js');
|
|
109
|
+
const { unmount } = render(<TuiApp app={app} />);
|
|
110
|
+
await new Promise<void>((resolve) => {
|
|
111
|
+
process.on('exit', () => { unmount(); resolve(); });
|
|
112
|
+
});
|
|
113
|
+
} else {
|
|
114
|
+
// Single prompt mode
|
|
115
|
+
console.log(chalk.cyan.bold('\n ⚡ THATGFSJ CODE\n'));
|
|
116
|
+
console.log(chalk.gray(' You'));
|
|
117
|
+
console.log(chalk.gray(' ' + '─'.repeat(40)));
|
|
118
|
+
console.log(' ' + prompt);
|
|
119
|
+
console.log();
|
|
120
|
+
|
|
121
|
+
app.session.addMessage('user', prompt);
|
|
122
|
+
let fullResponse = '';
|
|
123
|
+
// v2.2.4: track whether the stream was aborted so we don't
|
|
124
|
+
// persist a truncated assistant message (which is what caused
|
|
125
|
+
// the [已中断] hallucination loop in v2.2.3).
|
|
126
|
+
const abortCtrl = new AbortController();
|
|
127
|
+
const onSigInt = () => { abortCtrl.abort(); };
|
|
128
|
+
process.once('SIGINT', onSigInt);
|
|
129
|
+
// v2.2.5 (product 0.4.2): compress <think> blocks at render time
|
|
130
|
+
// AND before persisting to history. Without this, every assistant
|
|
131
|
+
// message balloons history with reasoning the user has already
|
|
132
|
+
// seen compressed, and context windows blow up fast.
|
|
133
|
+
const showThinking = !!options.showThinking;
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
process.stdout.write(chalk.gray(' Thinking...'));
|
|
137
|
+
const stream = app.streamResponse();
|
|
138
|
+
|
|
139
|
+
// v3.0.0: stream is now AsyncGenerator<StreamChunk> instead of
|
|
140
|
+
// string. Tool calls come as { type: 'tool_calls', toolCalls }
|
|
141
|
+
// with structured args; usage comes as { type: 'usage', usage }
|
|
142
|
+
// at end. We render text live, render tool dispatches inline, and
|
|
143
|
+
// capture the final response text into fullResponse for the
|
|
144
|
+
// post-process step below.
|
|
145
|
+
for await (const chunk of stream) {
|
|
146
|
+
if (abortCtrl.signal.aborted) break;
|
|
147
|
+
// Clear the entire "Thinking..." line — must use enough
|
|
148
|
+
// spaces to overwrite any longer thinking indicator.
|
|
149
|
+
process.stdout.write('\r' + ' '.repeat(80) + '\r');
|
|
150
|
+
|
|
151
|
+
switch (chunk.type) {
|
|
152
|
+
case 'text': {
|
|
153
|
+
if (chunk.content) {
|
|
154
|
+
fullResponse += chunk.content;
|
|
155
|
+
// Show the chunk as it streams, but we'll re-render the
|
|
156
|
+
// final compressed version below. To avoid double-
|
|
157
|
+
// printing, only echo when we're NOT going to post-
|
|
158
|
+
// process (i.e., when showThinking is true and we want
|
|
159
|
+
// to see everything live).
|
|
160
|
+
if (showThinking) {
|
|
161
|
+
process.stdout.write(chalk.cyan(' │ ') + chunk.content);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
case 'thinking': {
|
|
167
|
+
// Reasoning content. Strip from persisted content via
|
|
168
|
+
// compressThinking; show live if user asked for it.
|
|
169
|
+
if (showThinking && chunk.content) {
|
|
170
|
+
fullResponse += chunk.content;
|
|
171
|
+
process.stdout.write(chalk.cyan(' │ ') + chunk.content);
|
|
172
|
+
}
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
case 'tool_calls': {
|
|
176
|
+
if (chunk.toolCalls && chunk.toolCalls.length > 0) {
|
|
177
|
+
for (const tc of chunk.toolCalls) {
|
|
178
|
+
console.log();
|
|
179
|
+
console.log(chalk.cyan(` ⚙ ${tc.function.name}: ${formatArgs(tc.function.arguments)}`));
|
|
180
|
+
console.log(chalk.gray(' ' + '─'.repeat(40)));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
case 'usage': {
|
|
186
|
+
// v3.0.0: surface cache stats at the end of single-prompt
|
|
187
|
+
// mode so CLI users can see hit-rate without /cache command.
|
|
188
|
+
const u = chunk.usage;
|
|
189
|
+
const hit = u.prompt_cache_hit_tokens || u.cache_read_input_tokens || 0;
|
|
190
|
+
const miss = u.prompt_cache_miss_tokens || u.cache_creation_input_tokens || (u.prompt_tokens - hit);
|
|
191
|
+
if (hit > 0 || miss > 0) {
|
|
192
|
+
const total = hit + miss;
|
|
193
|
+
const rate = total > 0 ? ((hit / total) * 100).toFixed(1) : '0.0';
|
|
194
|
+
console.log(chalk.gray(` ⚡ cache: ${rate}% hit (${hit} / ${total} tokens)`));
|
|
195
|
+
}
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// v2.2.5: post-process. Strip <think> blocks, summarize,
|
|
202
|
+
// and print the cleaned conclusion if we suppressed it
|
|
203
|
+
// during streaming.
|
|
204
|
+
if (!showThinking && !abortCtrl.signal.aborted && fullResponse) {
|
|
205
|
+
const split = splitThinking(fullResponse);
|
|
206
|
+
if (split.thinking) {
|
|
207
|
+
// Clear what we already streamed (we suppressed text
|
|
208
|
+
// chunks during streaming, so there's nothing to clear
|
|
209
|
+
// unless showThinking was on; in that case the text
|
|
210
|
+
// already contains the <think> block live and there's
|
|
211
|
+
// no point double-printing).
|
|
212
|
+
const summary = summarizeThinking(split);
|
|
213
|
+
if (summary) {
|
|
214
|
+
console.log(chalk.gray(` ${summary}`));
|
|
215
|
+
}
|
|
216
|
+
if (split.conclusion) {
|
|
217
|
+
console.log(chalk.cyan(' │ ') + split.conclusion);
|
|
218
|
+
}
|
|
219
|
+
} else if (fullResponse.trim()) {
|
|
220
|
+
// No thinking block found — print the conclusion if we
|
|
221
|
+
// hadn't already (showThinking=false suppresses
|
|
222
|
+
// streaming text).
|
|
223
|
+
console.log(chalk.cyan(' │ ') + fullResponse);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
console.log();
|
|
228
|
+
// v2.2.4: skip persistence on abort.
|
|
229
|
+
if (!abortCtrl.signal.aborted) {
|
|
230
|
+
// v2.2.5: persist only the conclusion (no <think> blocks)
|
|
231
|
+
// when compression is enabled. This keeps the conversation
|
|
232
|
+
// log readable AND keeps context-window usage low.
|
|
233
|
+
const toPersist = showThinking
|
|
234
|
+
? fullResponse
|
|
235
|
+
: compressThinking(fullResponse, false);
|
|
236
|
+
const accepted = app.session.addMessageSafe('assistant', toPersist);
|
|
237
|
+
if (!accepted) {
|
|
238
|
+
console.error(chalk.yellow(
|
|
239
|
+
' ⚠️ Dropped assistant message containing [已中断] marker.\n' +
|
|
240
|
+
' (prevents hallucination loop — try your question again)'
|
|
241
|
+
));
|
|
242
|
+
}
|
|
243
|
+
} else {
|
|
244
|
+
console.log(chalk.yellow(' ⏹ Cancelled (response not saved)'));
|
|
245
|
+
}
|
|
246
|
+
} catch (error: any) {
|
|
247
|
+
const msg = error.message || String(error);
|
|
248
|
+
if (msg.includes('401') || msg.includes('403') || msg.includes('Unauthorized')) {
|
|
249
|
+
console.error(chalk.red('\n ❌ API key invalid or expired.'));
|
|
250
|
+
console.log(chalk.gray(' Run ') + chalk.cyan('gfcode init') + chalk.gray(' to reconfigure.\n'));
|
|
251
|
+
} else if (msg.includes('429') || msg.includes('rate limit') || msg.includes('quota')) {
|
|
252
|
+
console.error(chalk.red('\n ❌ Rate limit or quota exceeded.'));
|
|
253
|
+
console.log(chalk.gray(' Wait or run ') + chalk.cyan('gfcode init') + chalk.gray(' to switch provider.\n'));
|
|
254
|
+
} else if (msg.includes('ECONNREFUSED') || msg.includes('ENOTFOUND')) {
|
|
255
|
+
console.error(chalk.red('\n ❌ Cannot connect to API server.'));
|
|
256
|
+
console.log(chalk.gray(' Check network or run ') + chalk.cyan('gfcode init') + chalk.gray('.\n'));
|
|
257
|
+
} else {
|
|
258
|
+
console.error(chalk.red(`\n Error: ${msg}`));
|
|
259
|
+
}
|
|
260
|
+
} finally {
|
|
261
|
+
process.removeListener('SIGINT', onSigInt);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
} catch (error: any) {
|
|
265
|
+
console.error(chalk.red(`\n ${error.message}`));
|
|
266
|
+
process.exit(1);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
program
|
|
271
|
+
.command('init')
|
|
272
|
+
.description('Configure API key and model')
|
|
273
|
+
.action(async () => {
|
|
274
|
+
await WelcomeScreen.interactiveSetup();
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
program.parse(process.argv);
|
|
278
|
+
|
|
279
|
+
function formatArgs(args: string): string {
|
|
280
|
+
try {
|
|
281
|
+
const obj = JSON.parse(args);
|
|
282
|
+
return Object.entries(obj)
|
|
283
|
+
.map(([k, v]) => `${k}=${typeof v === 'string' && v.length > 40 ? v.slice(0, 40) + '...' : JSON.stringify(v)}`)
|
|
284
|
+
.join(' ');
|
|
285
|
+
} catch {
|
|
286
|
+
return args.length > 60 ? args.slice(0, 60) + '...' : args;
|
|
287
|
+
}
|
|
288
|
+
}
|