thatgfsj-code 3.0.3 → 3.0.6
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/README.md +64 -21
- package/dist/app/index.d.ts +93 -22
- package/dist/app/index.d.ts.map +1 -1
- package/dist/app/index.js +232 -40
- package/dist/app/index.js.map +1 -1
- package/dist/cache/smartModel.d.ts +29 -22
- package/dist/cache/smartModel.d.ts.map +1 -1
- package/dist/cache/smartModel.js +33 -40
- package/dist/cache/smartModel.js.map +1 -1
- package/dist/cmd/index.d.ts +12 -20
- package/dist/cmd/index.d.ts.map +1 -1
- package/dist/cmd/index.js +192 -146
- package/dist/cmd/index.js.map +1 -1
- package/dist/config/index.d.ts +4 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +9 -6
- package/dist/config/index.js.map +1 -1
- package/dist/config/types.d.ts +5 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/llm/anthropic.d.ts +1 -1
- package/dist/llm/anthropic.d.ts.map +1 -1
- package/dist/llm/anthropic.js +116 -84
- package/dist/llm/anthropic.js.map +1 -1
- package/dist/llm/gemini.d.ts +27 -3
- package/dist/llm/gemini.d.ts.map +1 -1
- package/dist/llm/gemini.js +196 -73
- package/dist/llm/gemini.js.map +1 -1
- package/dist/llm/index.d.ts +20 -1
- package/dist/llm/index.d.ts.map +1 -1
- package/dist/llm/index.js +71 -10
- package/dist/llm/index.js.map +1 -1
- package/dist/llm/openai.d.ts +5 -1
- package/dist/llm/openai.d.ts.map +1 -1
- package/dist/llm/openai.js +96 -56
- package/dist/llm/openai.js.map +1 -1
- package/dist/mcp/client.d.ts +84 -34
- package/dist/mcp/client.d.ts.map +1 -1
- package/dist/mcp/client.js +291 -126
- package/dist/mcp/client.js.map +1 -1
- package/dist/session/compactor.d.ts +27 -7
- package/dist/session/compactor.d.ts.map +1 -1
- package/dist/session/compactor.js +78 -23
- package/dist/session/compactor.js.map +1 -1
- package/dist/session/index.d.ts +96 -33
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/index.js +278 -60
- package/dist/session/index.js.map +1 -1
- package/dist/tools/file.d.ts +10 -2
- package/dist/tools/file.d.ts.map +1 -1
- package/dist/tools/file.js +54 -5
- package/dist/tools/file.js.map +1 -1
- package/dist/tools/git.d.ts +16 -2
- package/dist/tools/git.d.ts.map +1 -1
- package/dist/tools/git.js +61 -18
- package/dist/tools/git.js.map +1 -1
- package/dist/tools/nwt.d.ts.map +1 -1
- package/dist/tools/nwt.js +8 -1
- package/dist/tools/nwt.js.map +1 -1
- package/dist/tools/search.d.ts +8 -1
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/search.js +105 -34
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/shell.d.ts +0 -4
- package/dist/tools/shell.d.ts.map +1 -1
- package/dist/tools/shell.js +11 -26
- package/dist/tools/shell.js.map +1 -1
- package/dist/tools/types.d.ts +7 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/types.js.map +1 -1
- package/dist/tui/app.d.ts.map +1 -1
- package/dist/tui/app.js +90 -18
- package/dist/tui/app.js.map +1 -1
- package/dist/tui/components/ChatMessage.d.ts.map +1 -1
- package/dist/tui/components/ChatMessage.js +9 -2
- package/dist/tui/components/ChatMessage.js.map +1 -1
- package/dist/tui/components/ConfirmPrompt.d.ts +21 -0
- package/dist/tui/components/ConfirmPrompt.d.ts.map +1 -0
- package/dist/tui/components/ConfirmPrompt.js +45 -0
- package/dist/tui/components/ConfirmPrompt.js.map +1 -0
- package/dist/tui/components/Header.d.ts +10 -5
- package/dist/tui/components/Header.d.ts.map +1 -1
- package/dist/tui/components/Header.js +13 -5
- package/dist/tui/components/Header.js.map +1 -1
- package/dist/tui/components/InitWizard.d.ts.map +1 -1
- package/dist/tui/components/InitWizard.js +8 -13
- package/dist/tui/components/InitWizard.js.map +1 -1
- package/dist/tui/components/StatusBar.d.ts +7 -0
- package/dist/tui/components/StatusBar.d.ts.map +1 -1
- package/dist/tui/components/StatusBar.js +8 -3
- package/dist/tui/components/StatusBar.js.map +1 -1
- package/dist/tui/components/Thinking.d.ts +5 -0
- package/dist/tui/components/Thinking.d.ts.map +1 -1
- package/dist/tui/components/Thinking.js +24 -2
- package/dist/tui/components/Thinking.js.map +1 -1
- package/dist/tui/components/ToolCall.d.ts +6 -1
- package/dist/tui/components/ToolCall.d.ts.map +1 -1
- package/dist/tui/components/ToolCall.js +50 -22
- package/dist/tui/components/ToolCall.js.map +1 -1
- package/dist/tui/components/UserInput.d.ts +5 -0
- package/dist/tui/components/UserInput.d.ts.map +1 -1
- package/dist/tui/components/UserInput.js +9 -13
- package/dist/tui/components/UserInput.js.map +1 -1
- package/dist/tui/hooks/useChat.d.ts +1 -0
- package/dist/tui/hooks/useChat.d.ts.map +1 -1
- package/dist/tui/hooks/useChat.js +36 -16
- package/dist/tui/hooks/useChat.js.map +1 -1
- package/dist/tui/hooks/useCommands.d.ts +7 -1
- package/dist/tui/hooks/useCommands.d.ts.map +1 -1
- package/dist/tui/hooks/useCommands.js +83 -49
- package/dist/tui/hooks/useCommands.js.map +1 -1
- package/dist/tui/theme.d.ts +34 -0
- package/dist/tui/theme.d.ts.map +1 -0
- package/dist/tui/theme.js +34 -0
- package/dist/tui/theme.js.map +1 -0
- package/dist/tui/welcome.d.ts.map +1 -1
- package/dist/tui/welcome.js +9 -7
- package/dist/tui/welcome.js.map +1 -1
- package/dist/types.d.ts +16 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/diff.d.ts +5 -0
- package/dist/utils/diff.d.ts.map +1 -1
- package/dist/utils/diff.js +15 -0
- package/dist/utils/diff.js.map +1 -1
- package/dist/version.d.ts +12 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +29 -0
- package/dist/version.js.map +1 -0
- package/package.json +19 -7
- package/CHANGELOG.md +0 -192
- package/DEVELOPMENT.md +0 -286
- package/ROADMAP.md +0 -91
- package/docs/API_KEY_GUIDE.md +0 -236
- package/docs/FAQ.md +0 -182
- package/install.bat +0 -63
- package/install.ps1 +0 -238
- package/install.sh +0 -113
- package/src/app/index.ts +0 -197
- package/src/cache/fingerprint.ts +0 -101
- package/src/cache/index.ts +0 -21
- package/src/cache/smartModel.ts +0 -145
- package/src/cache/stats.ts +0 -199
- package/src/cache/volatile.ts +0 -47
- package/src/cmd/index.tsx +0 -288
- package/src/config/index.ts +0 -158
- package/src/config/providers.ts +0 -234
- package/src/config/types.ts +0 -71
- package/src/hooks/index.ts +0 -111
- package/src/llm/anthropic.ts +0 -413
- package/src/llm/gemini.ts +0 -169
- package/src/llm/index.ts +0 -302
- package/src/llm/openai.ts +0 -243
- package/src/llm/provider.ts +0 -71
- package/src/mcp/client.ts +0 -330
- package/src/prompts/index.ts +0 -260
- package/src/session/compactor.ts +0 -103
- package/src/session/index.ts +0 -181
- package/src/session/message.ts +0 -42
- package/src/skills/brainstorming.ts +0 -43
- package/src/skills/code-review.ts +0 -45
- package/src/skills/executing-plans.ts +0 -27
- package/src/skills/frontend-design.ts +0 -35
- package/src/skills/git-workflow.ts +0 -36
- package/src/skills/improve-architecture.ts +0 -38
- package/src/skills/index.ts +0 -136
- package/src/skills/neuroweave.ts +0 -47
- package/src/skills/playwright.ts +0 -72
- package/src/skills/prototype.ts +0 -30
- package/src/skills/subagent.ts +0 -28
- package/src/skills/supabase.ts +0 -44
- package/src/skills/systematic-debugging.ts +0 -44
- package/src/skills/tdd.ts +0 -39
- package/src/skills/triage.ts +0 -35
- package/src/skills/verification.ts +0 -31
- package/src/skills/writing-plans.ts +0 -42
- package/src/tools/file.ts +0 -143
- package/src/tools/git.ts +0 -132
- package/src/tools/index.ts +0 -111
- package/src/tools/nwt.ts +0 -598
- package/src/tools/search.ts +0 -263
- package/src/tools/shell.ts +0 -136
- package/src/tools/types.ts +0 -122
- package/src/tui/app.tsx +0 -159
- package/src/tui/components/ChatList.tsx +0 -41
- package/src/tui/components/ChatMessage.tsx +0 -54
- package/src/tui/components/Header.tsx +0 -66
- package/src/tui/components/InitWizard.tsx +0 -251
- package/src/tui/components/Markdown.tsx +0 -35
- package/src/tui/components/ModelSelector.tsx +0 -107
- package/src/tui/components/StatusBar.tsx +0 -30
- package/src/tui/components/Thinking.tsx +0 -17
- package/src/tui/components/ToolCall.tsx +0 -102
- package/src/tui/components/UserInput.tsx +0 -151
- package/src/tui/hooks/useChat.ts +0 -287
- package/src/tui/hooks/useCommands.ts +0 -273
- package/src/tui/index.ts +0 -6
- package/src/tui/welcome.ts +0 -177
- package/src/types.ts +0 -104
- package/src/utils/diff.ts +0 -71
- package/src/utils/project.ts +0 -99
- package/src/utils/stableStringify.ts +0 -47
- package/src/utils/thinking.ts +0 -119
- package/tests/cache/fingerprint.test.ts +0 -75
- package/tests/cache/providerCatalog.test.ts +0 -31
- package/tests/cache/smartModel.test.ts +0 -112
- package/tests/cache/stableStringify.test.ts +0 -43
- package/tests/cache/stats.test.ts +0 -146
- package/tests/cache/volatile.test.ts +0 -75
- package/tests/smoke-pollution.mjs +0 -78
- package/tests/smoke-thinking.mjs +0 -110
- package/tests/smoke-tool-stream.mjs +0 -69
- package/tests/smoke-tool.mjs +0 -117
- package/tsconfig.json +0 -21
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
import { useCallback } from 'react';
|
|
2
|
-
import type { App } from '../../app/index.js';
|
|
3
|
-
|
|
4
|
-
interface CommandResult {
|
|
5
|
-
handled: boolean;
|
|
6
|
-
output?: string;
|
|
7
|
-
action?: 'clear' | 'reinit';
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
// 中文命令别名映射
|
|
11
|
-
const CMD_ALIASES: Record<string, string> = {
|
|
12
|
-
'/模型': '/model',
|
|
13
|
-
'/新建': '/new',
|
|
14
|
-
'/清空': '/new',
|
|
15
|
-
'/压缩': '/compact',
|
|
16
|
-
'/技能': '/skills',
|
|
17
|
-
'/技能管理': '/skills',
|
|
18
|
-
'/mcp': '/mcp',
|
|
19
|
-
'/帮助': '/help',
|
|
20
|
-
'/服务商': '/provider',
|
|
21
|
-
'/思考': '/thinking',
|
|
22
|
-
'/缓存': '/cache',
|
|
23
|
-
'/ttl': '/ttl',
|
|
24
|
-
'/TTL': '/ttl',
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export const COMMAND_LIST = [
|
|
28
|
-
{ name: '/模型', desc: '切换模型' },
|
|
29
|
-
{ name: '/服务商', desc: '更换服务商' },
|
|
30
|
-
{ name: '/新建', desc: '新建会话' },
|
|
31
|
-
{ name: '/压缩', desc: '压缩上下文' },
|
|
32
|
-
{ name: '/缓存', desc: '缓存命中率' },
|
|
33
|
-
{ name: '/ttl', desc: '查看/设置 Cache TTL' },
|
|
34
|
-
{ name: '/技能', desc: '管理技能' },
|
|
35
|
-
{ name: '/mcp', desc: 'MCP 设置' },
|
|
36
|
-
{ name: '/帮助', desc: '查看帮助' },
|
|
37
|
-
];
|
|
38
|
-
|
|
39
|
-
export function useCommands(app: App) {
|
|
40
|
-
|
|
41
|
-
const handleCommand = useCallback((input: string): CommandResult => {
|
|
42
|
-
const cmd = input.trim();
|
|
43
|
-
const parts = cmd.split(/\s+/);
|
|
44
|
-
let name = parts[0].toLowerCase();
|
|
45
|
-
const arg = parts.slice(1).join(' ');
|
|
46
|
-
|
|
47
|
-
// 中文别名映射
|
|
48
|
-
if (CMD_ALIASES[name]) {
|
|
49
|
-
name = CMD_ALIASES[name];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// ── /model [name] ───────────────────────────────────
|
|
53
|
-
if (name === '/model') {
|
|
54
|
-
if (!arg) {
|
|
55
|
-
const c = app.config.get();
|
|
56
|
-
return {
|
|
57
|
-
handled: true,
|
|
58
|
-
output: [
|
|
59
|
-
`当前: ${c.provider} / ${c.model}`,
|
|
60
|
-
'',
|
|
61
|
-
'用法: /模型 <名称>',
|
|
62
|
-
' /模型 deepseek-chat',
|
|
63
|
-
' /模型 gpt-4o',
|
|
64
|
-
' /模型 claude-sonnet-4-20250514',
|
|
65
|
-
'',
|
|
66
|
-
'或: /服务商 更换服务商',
|
|
67
|
-
].join('\n'),
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
app.config.save({ model: arg });
|
|
71
|
-
return { handled: true, output: `模型 → ${arg}` };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// ── /provider ───────────────────────────────────────
|
|
75
|
-
if (name === '/provider') {
|
|
76
|
-
return { handled: true, action: 'reinit' };
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// ── /new, /clear ────────────────────────────────────
|
|
80
|
-
if (name === '/new') {
|
|
81
|
-
app.session.clear();
|
|
82
|
-
return { handled: true, output: '新会话已创建。', action: 'clear' };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// ── /compact ────────────────────────────────────────
|
|
86
|
-
if (name === '/compact') {
|
|
87
|
-
const before = app.session.getMessageCount();
|
|
88
|
-
app.session.truncate();
|
|
89
|
-
const after = app.session.getMessageCount();
|
|
90
|
-
return { handled: true, output: `上下文已压缩: ${before} → ${after} 条消息` };
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// ── /cache — prompt cache hit-rate + cost savings ───
|
|
94
|
-
// v3.0.0: surfaces CacheStatsStore snapshots. Sub-commands:
|
|
95
|
-
// /cache show full breakdown
|
|
96
|
-
// /cache reset zero the persistent stats
|
|
97
|
-
if (name === '/cache') {
|
|
98
|
-
if (arg === 'reset' || arg === '重置') {
|
|
99
|
-
app.cacheStats.reset();
|
|
100
|
-
return { handled: true, output: '✓ 缓存统计已重置' };
|
|
101
|
-
}
|
|
102
|
-
if (arg === 'off' || arg === '关') {
|
|
103
|
-
// Toggle disable: writes through to provider config (currently a
|
|
104
|
-
// no-op until M4.5 wires Config.cache). Print a hint.
|
|
105
|
-
return {
|
|
106
|
-
handled: true,
|
|
107
|
-
output: [
|
|
108
|
-
'✗ 关闭缓存功能:在 ~/.thatgfsj/config.json 添加 "cache": { "enabled": false } 后重启。',
|
|
109
|
-
' (提示:完全关闭会大幅增加 token 成本;推荐保留开启)',
|
|
110
|
-
].join('\n'),
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
const s = app.cacheStats.snapshot();
|
|
114
|
-
if (s.totalRequests === 0) {
|
|
115
|
-
return {
|
|
116
|
-
handled: true,
|
|
117
|
-
output: [
|
|
118
|
-
'⚡ 缓存统计',
|
|
119
|
-
'─────────────────────────',
|
|
120
|
-
' 暂无请求。开启一段对话后再来查看。',
|
|
121
|
-
'',
|
|
122
|
-
'/cache reset · 重置统计',
|
|
123
|
-
].join('\n'),
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
const hitRateStr = (s.hitRate * 100).toFixed(1) + '%';
|
|
127
|
-
const recent = s.history.slice(-10);
|
|
128
|
-
const lines = [
|
|
129
|
-
'⚡ 缓存统计 (累计 ' + s.totalRequests + ' 轮)',
|
|
130
|
-
'─────────────────────────',
|
|
131
|
-
` 命中率: ${hitRateStr} (${s.totalReadTokens.toLocaleString()} / ${s.totalInputTokens.toLocaleString()} tokens)`,
|
|
132
|
-
` 累计创建: ${s.totalCreationTokens.toLocaleString()} tokens`,
|
|
133
|
-
` 累计节省: ¥${s.estimatedSavingsCNY.toFixed(4)}`,
|
|
134
|
-
'',
|
|
135
|
-
'最近 10 轮:',
|
|
136
|
-
];
|
|
137
|
-
for (let i = 0; i < recent.length; i++) {
|
|
138
|
-
const r = recent[i];
|
|
139
|
-
const tag = r.read > 0 ? '⚡' : '💸';
|
|
140
|
-
const pct = (r.hitRate * 100).toFixed(0) + '%';
|
|
141
|
-
lines.push(` #${s.history.length - recent.length + i + 1} [${tag}] ${String(r.read).padStart(5)} / ${String(r.input).padStart(5)} ${pct.padStart(4)}`);
|
|
142
|
-
}
|
|
143
|
-
lines.push('');
|
|
144
|
-
lines.push('/cache reset · 重置统计');
|
|
145
|
-
return { handled: true, output: lines.join('\n') };
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// v3.0.3: /ttl — view or pin the cache TTL.
|
|
149
|
-
//
|
|
150
|
-
// /ttl show current effective TTL + decision reason
|
|
151
|
-
// /ttl 5m|1h pin TTL for this session (sticky, no cache reset)
|
|
152
|
-
// /ttl auto resume auto-decision (clears the pin)
|
|
153
|
-
if (name === '/ttl') {
|
|
154
|
-
const configTtl = (app.config.get() as any).cache?.ttl as 'auto' | '5m' | '1h' | undefined;
|
|
155
|
-
const resolved = app.resolvedTtl;
|
|
156
|
-
if (arg === 'auto') {
|
|
157
|
-
app.config.save({ cache: { ...(app.config.get() as any).cache, ttl: 'auto' } });
|
|
158
|
-
app.resolvedTtl = null;
|
|
159
|
-
return { handled: true, output: '✓ Cache TTL 已重置为自动(下一轮重新评估)' };
|
|
160
|
-
}
|
|
161
|
-
if (arg === '5m' || arg === '1h') {
|
|
162
|
-
app.config.save({ cache: { ...(app.config.get() as any).cache, ttl: arg } });
|
|
163
|
-
app.resolvedTtl = arg;
|
|
164
|
-
return { handled: true, output: `✓ Cache TTL 已固定为 ${arg}(首次请求会重建缓存)` };
|
|
165
|
-
}
|
|
166
|
-
// No arg: show current state
|
|
167
|
-
const lines = [
|
|
168
|
-
'⏱ Cache TTL',
|
|
169
|
-
'─────────────────────────',
|
|
170
|
-
` 配置: ${configTtl ?? 'auto'}`,
|
|
171
|
-
` 当前会话: ${resolved ?? '尚未评估'}`,
|
|
172
|
-
'',
|
|
173
|
-
'说明: '+
|
|
174
|
-
(configTtl === 'auto'
|
|
175
|
-
? '自动模式 – 短会话(≤14 轮)用 5m节省写入成本,' +
|
|
176
|
-
'长会话(≥15 轮 或 ≥50k 字符)自动升级到 1h 复用缓存。'
|
|
177
|
-
: `${configTtl} 模式 – TTL 每次请求都固定。`),
|
|
178
|
-
'',
|
|
179
|
-
'/ttl 5m|1h|auto 调整 TTL',
|
|
180
|
-
];
|
|
181
|
-
return { handled: true, output: lines.join('\n') };
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// ── /skills [id] ────────────────────────────────────
|
|
185
|
-
if (name === '/skills') {
|
|
186
|
-
if (arg) {
|
|
187
|
-
if (app.skills.isActive(arg)) {
|
|
188
|
-
app.skills.deactivate(arg);
|
|
189
|
-
return { handled: true, output: `✗ 已关闭: ${arg}` };
|
|
190
|
-
} else if (app.skills.activate(arg)) {
|
|
191
|
-
return { handled: true, output: `✓ 已开启: ${arg}` };
|
|
192
|
-
}
|
|
193
|
-
return { handled: true, output: `未知技能: ${arg}` };
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const all = app.skills.list();
|
|
197
|
-
const lines = [
|
|
198
|
-
'技能列表:',
|
|
199
|
-
...all.map(s => {
|
|
200
|
-
const mark = app.skills.isActive(s.id) ? '✓' : '·';
|
|
201
|
-
return ` ${mark} ${s.id} ${s.description}`;
|
|
202
|
-
}),
|
|
203
|
-
'',
|
|
204
|
-
'用法: /技能 <id> 切换',
|
|
205
|
-
];
|
|
206
|
-
return { handled: true, output: lines.join('\n') };
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// ── /mcp ────────────────────────────────────────────
|
|
210
|
-
if (name === '/mcp') {
|
|
211
|
-
return {
|
|
212
|
-
handled: true,
|
|
213
|
-
output: [
|
|
214
|
-
'MCP 配置: ~/.thatgfsj/mcp.json',
|
|
215
|
-
'',
|
|
216
|
-
' {',
|
|
217
|
-
' "servers": {',
|
|
218
|
-
' "名称": { "command": "npx", "args": ["-y", "server"] }',
|
|
219
|
-
' }',
|
|
220
|
-
' }',
|
|
221
|
-
].join('\n'),
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// ── /thinking on|off ─────────────────────────────────
|
|
226
|
-
if (name === '/thinking' || name === '/思考') {
|
|
227
|
-
if (!arg) {
|
|
228
|
-
return {
|
|
229
|
-
handled: true,
|
|
230
|
-
output: `思考块显示: ${app.showThinking ? '开启 (显示完整 <think>...</think>)' : '关闭 (压缩为单行提示)'}`,
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
const want = arg.toLowerCase();
|
|
234
|
-
if (want === 'on' || want === '开启' || want === 'true' || want === '1') {
|
|
235
|
-
app.showThinking = true;
|
|
236
|
-
return { handled: true, output: '✓ 已开启完整思考块显示' };
|
|
237
|
-
}
|
|
238
|
-
if (want === 'off' || want === '关闭' || want === 'false' || want === '0') {
|
|
239
|
-
app.showThinking = false;
|
|
240
|
-
return { handled: true, output: '✓ 已关闭思考块显示 (压缩为单行提示)' };
|
|
241
|
-
}
|
|
242
|
-
return { handled: true, output: `用法: /thinking on|off (当前: ${app.showThinking ? 'on' : 'off'})` };
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// ── /help ───────────────────────────────────────────
|
|
246
|
-
if (name === '/help') {
|
|
247
|
-
return {
|
|
248
|
-
handled: true,
|
|
249
|
-
output: [
|
|
250
|
-
'命令列表:',
|
|
251
|
-
' /模型 <名称> 切换模型',
|
|
252
|
-
' /服务商 更换服务商',
|
|
253
|
-
' /新建 新建会话',
|
|
254
|
-
' /压缩 压缩上下文',
|
|
255
|
-
' /缓存 查看缓存命中率',
|
|
256
|
-
' /思考 [on|off] 切换思考块显示',
|
|
257
|
-
' /技能 [id] 管理技能',
|
|
258
|
-
' /mcp MCP 设置',
|
|
259
|
-
' /帮助 查看帮助',
|
|
260
|
-
' exit 退出',
|
|
261
|
-
'',
|
|
262
|
-
'快捷键:',
|
|
263
|
-
' ↑/↓ 历史记录',
|
|
264
|
-
' Ctrl+C 退出',
|
|
265
|
-
].join('\n'),
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
return { handled: false };
|
|
270
|
-
}, [app]);
|
|
271
|
-
|
|
272
|
-
return { handleCommand };
|
|
273
|
-
}
|
package/src/tui/index.ts
DELETED
package/src/tui/welcome.ts
DELETED
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Welcome / Setup Wizard - Clean UI
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import chalk from 'chalk';
|
|
6
|
-
import readline from 'readline';
|
|
7
|
-
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
8
|
-
import { join } from 'path';
|
|
9
|
-
import { homedir } from 'os';
|
|
10
|
-
import { PROVIDERS, getModelsForProvider, listProviders, isCustomProvider } from '../config/providers.js';
|
|
11
|
-
import type { ProviderName } from '../config/types.js';
|
|
12
|
-
|
|
13
|
-
const line = chalk.gray('─'.repeat(52));
|
|
14
|
-
|
|
15
|
-
export class WelcomeScreen {
|
|
16
|
-
|
|
17
|
-
static show(hasApiKey: boolean): void {
|
|
18
|
-
if (hasApiKey) return;
|
|
19
|
-
|
|
20
|
-
console.log();
|
|
21
|
-
console.log(chalk.cyan.bold(' ⚡ Thatgfsj Code') + chalk.gray(' v0.5.0'));
|
|
22
|
-
console.log(chalk.gray(' AI Coding Assistant'));
|
|
23
|
-
console.log(line);
|
|
24
|
-
console.log();
|
|
25
|
-
console.log(chalk.yellow(' ⚠ No API key configured'));
|
|
26
|
-
console.log();
|
|
27
|
-
console.log(chalk.gray(' Run ') + chalk.cyan.bold('gfcode init') + chalk.gray(' to set up your provider.'));
|
|
28
|
-
console.log();
|
|
29
|
-
console.log(chalk.gray(' Supported providers:'));
|
|
30
|
-
console.log(chalk.gray(' OpenAI · Claude · DeepSeek · Kimi · GLM · Gemini · 中转站'));
|
|
31
|
-
console.log(line);
|
|
32
|
-
console.log();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
static async interactiveSetup(): Promise<void> {
|
|
36
|
-
console.clear();
|
|
37
|
-
console.log();
|
|
38
|
-
console.log(chalk.cyan.bold(' ⚡ Thatgfsj Code') + chalk.gray(' - Setup'));
|
|
39
|
-
console.log(line);
|
|
40
|
-
console.log();
|
|
41
|
-
|
|
42
|
-
const rl = readline.createInterface({
|
|
43
|
-
input: process.stdin,
|
|
44
|
-
output: process.stdout,
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
const ask = (prompt: string): Promise<string> =>
|
|
48
|
-
new Promise(resolve => rl.question(prompt, answer => resolve(answer.trim())));
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
// ── Step 1: Provider ────────────────────────────────
|
|
52
|
-
console.log(chalk.bold(' 1. Choose Provider'));
|
|
53
|
-
console.log();
|
|
54
|
-
const providers = listProviders();
|
|
55
|
-
providers.forEach((p, i) => {
|
|
56
|
-
const num = chalk.cyan((i + 1).toString().padStart(2));
|
|
57
|
-
console.log(` ${num} ${p.name}`);
|
|
58
|
-
});
|
|
59
|
-
console.log();
|
|
60
|
-
|
|
61
|
-
const choice = await ask(chalk.cyan(' ❯ '));
|
|
62
|
-
const selected = providers[parseInt(choice) - 1] || providers[0];
|
|
63
|
-
const providerName = selected.key;
|
|
64
|
-
|
|
65
|
-
// ── Step 2: API Key ─────────────────────────────────
|
|
66
|
-
console.log();
|
|
67
|
-
console.log(chalk.bold(' 2. API Key'));
|
|
68
|
-
console.log();
|
|
69
|
-
|
|
70
|
-
let baseUrl: string | undefined;
|
|
71
|
-
|
|
72
|
-
if (isCustomProvider(providerName)) {
|
|
73
|
-
console.log(chalk.yellow(' Custom Provider (Relay Station)'));
|
|
74
|
-
console.log(chalk.gray(' OpenAI-compatible or Anthropic-compatible'));
|
|
75
|
-
console.log();
|
|
76
|
-
baseUrl = await ask(chalk.cyan(' Base URL ❯ '));
|
|
77
|
-
if (!baseUrl) {
|
|
78
|
-
console.log(chalk.red(' ❌ Base URL required'));
|
|
79
|
-
rl.close();
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
baseUrl = baseUrl.replace(/\/+$/, '');
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const apiKey = await ask(chalk.cyan(' API Key ❯ '));
|
|
86
|
-
|
|
87
|
-
// ── Step 3: Model ───────────────────────────────────
|
|
88
|
-
console.log();
|
|
89
|
-
console.log(chalk.bold(' 3. Choose Model'));
|
|
90
|
-
console.log();
|
|
91
|
-
|
|
92
|
-
let model: string;
|
|
93
|
-
if (isCustomProvider(providerName)) {
|
|
94
|
-
console.log(chalk.gray(' Enter model name for your relay station'));
|
|
95
|
-
console.log(chalk.gray(' e.g. gpt-4o-mini, claude-sonnet-4-20250514'));
|
|
96
|
-
console.log();
|
|
97
|
-
model = await ask(chalk.cyan(' Model ❯ '));
|
|
98
|
-
if (!model) {
|
|
99
|
-
console.log(chalk.red(' ❌ Model name required'));
|
|
100
|
-
rl.close();
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
} else {
|
|
104
|
-
const models = getModelsForProvider(providerName);
|
|
105
|
-
models.forEach((m, i) => {
|
|
106
|
-
const num = chalk.cyan((i + 1).toString().padStart(2));
|
|
107
|
-
console.log(` ${num} ${chalk.white(m.name)} ${chalk.gray(m.desc)}`);
|
|
108
|
-
});
|
|
109
|
-
console.log();
|
|
110
|
-
const modelChoice = await ask(chalk.cyan(' ❯ '));
|
|
111
|
-
const selectedModel = models[parseInt(modelChoice) - 1] || models[0];
|
|
112
|
-
model = selectedModel.id;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// ── Step 4: Context Length ───────────────────────────
|
|
116
|
-
console.log();
|
|
117
|
-
console.log(chalk.bold(' 4. Context Length'));
|
|
118
|
-
console.log();
|
|
119
|
-
console.log(chalk.gray(' Max messages kept in conversation history'));
|
|
120
|
-
console.log(chalk.gray(' Higher = more context, more tokens used'));
|
|
121
|
-
console.log();
|
|
122
|
-
console.log(chalk.cyan(' 1') + ' 20 ' + chalk.gray('(short,节省 token)'));
|
|
123
|
-
console.log(chalk.cyan(' 2') + ' 50 ' + chalk.gray('(default,推荐)'));
|
|
124
|
-
console.log(chalk.cyan(' 3') + ' 100 ' + chalk.gray('(long,长对话)'));
|
|
125
|
-
console.log(chalk.cyan(' 4') + ' 200 ' + chalk.gray('(very long,超长对话)'));
|
|
126
|
-
console.log();
|
|
127
|
-
const ctxChoice = await ask(chalk.cyan(' ❯ '));
|
|
128
|
-
const ctxMap: Record<string, number> = { '1': 20, '2': 50, '3': 100, '4': 200 };
|
|
129
|
-
const contextLength = ctxMap[ctxChoice] || 50;
|
|
130
|
-
|
|
131
|
-
// ── Save ────────────────────────────────────────────
|
|
132
|
-
this.saveConfig(providerName, model, apiKey, baseUrl, contextLength);
|
|
133
|
-
|
|
134
|
-
// ── Done ────────────────────────────────────────────
|
|
135
|
-
console.log();
|
|
136
|
-
console.log(line);
|
|
137
|
-
console.log(chalk.green.bold(' ✅ Configuration saved!'));
|
|
138
|
-
console.log();
|
|
139
|
-
console.log(chalk.gray(' Config: ') + chalk.white(join(homedir(), '.thatgfsj', 'config.json')));
|
|
140
|
-
console.log(chalk.gray(' Run ') + chalk.cyan.bold('gfcode') + chalk.gray(' to start.'));
|
|
141
|
-
console.log(line);
|
|
142
|
-
console.log();
|
|
143
|
-
|
|
144
|
-
} finally {
|
|
145
|
-
rl.close();
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
private static saveConfig(provider: ProviderName, model: string, apiKey: string, baseUrl?: string, contextLength = 50): void {
|
|
150
|
-
const configDir = join(homedir(), '.thatgfsj');
|
|
151
|
-
const configPath = join(configDir, 'config.json');
|
|
152
|
-
|
|
153
|
-
if (!existsSync(configDir)) {
|
|
154
|
-
mkdirSync(configDir, { recursive: true });
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const config: Record<string, any> = {
|
|
158
|
-
provider,
|
|
159
|
-
model,
|
|
160
|
-
apiKey,
|
|
161
|
-
temperature: 0.7,
|
|
162
|
-
maxTokens: 4096,
|
|
163
|
-
contextLength,
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
if (baseUrl) config.baseUrl = baseUrl;
|
|
167
|
-
|
|
168
|
-
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
169
|
-
|
|
170
|
-
console.log();
|
|
171
|
-
console.log(chalk.gray(' Provider: ') + chalk.white(provider));
|
|
172
|
-
console.log(chalk.gray(' Model: ') + chalk.white(model));
|
|
173
|
-
if (baseUrl) {
|
|
174
|
-
console.log(chalk.gray(' URL: ') + chalk.white(baseUrl));
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Global shared types for Thatgfsj Code
|
|
3
|
-
* Only the most fundamental types used across modules
|
|
4
|
-
*
|
|
5
|
-
* v3.0.0+: Extended for Reasonix-style prompt caching:
|
|
6
|
-
* - ChatMessage.content now accepts ContentBlock[] (Anthropic-style)
|
|
7
|
-
* - cache_control field for explicit Anthropic cache breakpoints
|
|
8
|
-
* - usage extended with cache_creation_input_tokens / cache_read_input_tokens
|
|
9
|
-
* (Anthropic) and prompt_cache_hit_tokens / prompt_cache_miss_tokens (DeepSeek)
|
|
10
|
-
* - StreamChunk now has 'thinking' / 'usage' variants for structured streaming
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
export type Role = 'system' | 'user' | 'assistant' | 'tool';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Anthropic-style cache control. ttl: '5m' (default) | '1h'
|
|
17
|
-
* - '5m': cheaper write cost, refreshed on each hit, expires 5 min after last hit
|
|
18
|
-
* - '1h': more expensive write cost, expires 1 hour after last hit
|
|
19
|
-
*/
|
|
20
|
-
export interface CacheControl {
|
|
21
|
-
type: 'ephemeral';
|
|
22
|
-
ttl?: '5m' | '1h';
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Anthropic-style content blocks. text carries optional cache_control so that
|
|
27
|
-
* Anthropic prompt caching breakpoints can be placed inside messages.
|
|
28
|
-
*/
|
|
29
|
-
export type ContentBlock =
|
|
30
|
-
| { type: 'text'; text: string; cache_control?: CacheControl }
|
|
31
|
-
| { type: 'image'; source: { type: 'base64'; media_type: string; data: string } };
|
|
32
|
-
|
|
33
|
-
export interface ChatMessage {
|
|
34
|
-
role: Role;
|
|
35
|
-
/**
|
|
36
|
-
* For 'system' / 'user' / 'assistant' (text only): a plain string.
|
|
37
|
-
* For 'user' (multimodal) or 'assistant' (Anthropic tool_use mixed with text):
|
|
38
|
-
* an array of ContentBlock.
|
|
39
|
-
* 'tool' messages must use string content (carries tool result text).
|
|
40
|
-
*/
|
|
41
|
-
content: string | ContentBlock[];
|
|
42
|
-
name?: string;
|
|
43
|
-
tool_calls?: ToolCall[];
|
|
44
|
-
tool_call_id?: string;
|
|
45
|
-
reasoning_content?: string;
|
|
46
|
-
/**
|
|
47
|
-
* Anthropic prompt caching marker attached to the message itself.
|
|
48
|
-
* Only honored when the underlying provider supports it (anthropic, anthropic-relay).
|
|
49
|
-
* OpenAI / Gemini / DeepSeek ignore it (DeepSeek uses automatic prefix cache).
|
|
50
|
-
*/
|
|
51
|
-
cache_control?: CacheControl;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export interface ToolCall {
|
|
55
|
-
id: string;
|
|
56
|
-
type: 'function';
|
|
57
|
-
function: {
|
|
58
|
-
name: string;
|
|
59
|
-
arguments: string;
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export interface Usage {
|
|
64
|
-
prompt_tokens: number;
|
|
65
|
-
completion_tokens: number;
|
|
66
|
-
total_tokens: number;
|
|
67
|
-
// Anthropic prompt cache fields
|
|
68
|
-
cache_creation_input_tokens?: number;
|
|
69
|
-
cache_read_input_tokens?: number;
|
|
70
|
-
// DeepSeek prompt cache fields
|
|
71
|
-
prompt_cache_hit_tokens?: number;
|
|
72
|
-
prompt_cache_miss_tokens?: number;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export interface ChatResponse {
|
|
76
|
-
content: string;
|
|
77
|
-
role: 'assistant';
|
|
78
|
-
usage?: Usage;
|
|
79
|
-
tool_calls?: ToolCall[];
|
|
80
|
-
reasoning_content?: string;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export interface ChatOptions {
|
|
84
|
-
temperature?: number;
|
|
85
|
-
maxTokens?: number;
|
|
86
|
-
stream?: boolean;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Structured streaming chunk. Replaces the legacy @@TOOL@@ sentinel-string
|
|
91
|
-
* protocol that the previous version of useChat parsed by string-splitting.
|
|
92
|
-
*
|
|
93
|
-
* - 'text': regular model output
|
|
94
|
-
* - 'tool_calls': one or more tool calls ready to dispatch
|
|
95
|
-
* - 'thinking': reasoning content (stripped from final persistence; kept in
|
|
96
|
-
* VolatileScratch for the current round only)
|
|
97
|
-
* - 'usage': token usage + cache stats; emitted at end of stream if
|
|
98
|
-
* the upstream provider returned them
|
|
99
|
-
*/
|
|
100
|
-
export type StreamChunk =
|
|
101
|
-
| { type: 'text'; content: string }
|
|
102
|
-
| { type: 'tool_calls'; toolCalls: ToolCall[] }
|
|
103
|
-
| { type: 'thinking'; content: string }
|
|
104
|
-
| { type: 'usage'; usage: Usage };
|
package/src/utils/diff.ts
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Diff Preview - Show file changes in a readable format
|
|
3
|
-
* Migrated from old src/utils/diff-preview.ts
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export interface DiffResult {
|
|
7
|
-
original: string;
|
|
8
|
-
modified: string;
|
|
9
|
-
diff: string;
|
|
10
|
-
added: number;
|
|
11
|
-
removed: number;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export class DiffPreview {
|
|
15
|
-
/**
|
|
16
|
-
* Generate a simple line-by-line diff
|
|
17
|
-
*/
|
|
18
|
-
static diff(original: string, modified: string): DiffResult {
|
|
19
|
-
const originalLines = original.split('\n');
|
|
20
|
-
const modifiedLines = modified.split('\n');
|
|
21
|
-
const diffLines: string[] = [];
|
|
22
|
-
let added = 0;
|
|
23
|
-
let removed = 0;
|
|
24
|
-
|
|
25
|
-
// Simple diff: show removed (-) and added (+) lines
|
|
26
|
-
const maxLen = Math.max(originalLines.length, modifiedLines.length);
|
|
27
|
-
|
|
28
|
-
for (let i = 0; i < maxLen; i++) {
|
|
29
|
-
const orig = originalLines[i];
|
|
30
|
-
const mod = modifiedLines[i];
|
|
31
|
-
|
|
32
|
-
if (orig === mod) {
|
|
33
|
-
diffLines.push(` ${orig}`);
|
|
34
|
-
} else {
|
|
35
|
-
if (orig !== undefined) {
|
|
36
|
-
diffLines.push(`- ${orig}`);
|
|
37
|
-
removed++;
|
|
38
|
-
}
|
|
39
|
-
if (mod !== undefined) {
|
|
40
|
-
diffLines.push(`+ ${mod}`);
|
|
41
|
-
added++;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return {
|
|
47
|
-
original,
|
|
48
|
-
modified,
|
|
49
|
-
diff: diffLines.join('\n'),
|
|
50
|
-
added,
|
|
51
|
-
removed,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Format diff for display
|
|
57
|
-
*/
|
|
58
|
-
static format(result: DiffResult): string {
|
|
59
|
-
const lines = result.diff.split('\n').map(line => {
|
|
60
|
-
if (line.startsWith('+')) return `\x1b[32m${line}\x1b[0m`;
|
|
61
|
-
if (line.startsWith('-')) return `\x1b[31m${line}\x1b[0m`;
|
|
62
|
-
return line;
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
return [
|
|
66
|
-
`\x1b[36m--- Changes: -${result.removed} / +${result.added} ---\x1b[0m`,
|
|
67
|
-
...lines,
|
|
68
|
-
`\x1b[36m--- End ---\x1b[0m`,
|
|
69
|
-
].join('\n');
|
|
70
|
-
}
|
|
71
|
-
}
|