thatgfsj-code 1.0.3 → 2.2.9
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/.nwt/meta.json +5 -0
- package/CHANGELOG.md +244 -0
- package/DEVELOPMENT.md +286 -0
- package/dist/app/index.d.ts +14 -0
- package/dist/app/index.d.ts.map +1 -1
- package/dist/app/index.js +21 -5
- package/dist/app/index.js.map +1 -1
- package/dist/cmd/index.d.ts +21 -0
- package/dist/cmd/index.d.ts.map +1 -1
- package/dist/cmd/index.js +144 -12
- package/dist/cmd/index.js.map +1 -1
- package/dist/session/index.d.ts +11 -0
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/index.js +83 -1
- package/dist/session/index.js.map +1 -1
- package/dist/tui/components/ChatList.d.ts.map +1 -1
- package/dist/tui/components/ChatList.js +4 -3
- package/dist/tui/components/ChatList.js.map +1 -1
- package/dist/tui/components/Header.d.ts.map +1 -1
- package/dist/tui/components/Header.js +2 -1
- package/dist/tui/components/Header.js.map +1 -1
- package/dist/tui/components/ToolCall.d.ts +1 -1
- package/dist/tui/components/ToolCall.d.ts.map +1 -1
- package/dist/tui/components/ToolCall.js +9 -4
- package/dist/tui/components/ToolCall.js.map +1 -1
- package/dist/tui/hooks/useChat.d.ts.map +1 -1
- package/dist/tui/hooks/useChat.js +47 -6
- 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 +21 -0
- package/dist/tui/hooks/useCommands.js.map +1 -1
- package/dist/tui/welcome.d.ts.map +1 -1
- package/dist/tui/welcome.js +3 -2
- package/dist/tui/welcome.js.map +1 -1
- package/dist/utils/thinking.d.ts +59 -0
- package/dist/utils/thinking.d.ts.map +1 -0
- package/dist/utils/thinking.js +107 -0
- package/dist/utils/thinking.js.map +1 -0
- package/dist/version.d.ts +16 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +16 -0
- package/dist/version.js.map +1 -0
- package/install.bat +63 -0
- package/install.ps1 +238 -0
- package/install.sh +113 -0
- package/package.json +5 -2
- package/src/app/index.ts +21 -5
- package/src/cmd/index.tsx +145 -13
- package/src/session/index.ts +82 -1
- package/src/tui/components/ChatList.tsx +15 -6
- package/src/tui/components/Header.tsx +2 -1
- package/src/tui/components/ToolCall.tsx +46 -10
- package/src/tui/hooks/useChat.ts +50 -6
- package/src/tui/hooks/useCommands.ts +22 -0
- package/src/tui/welcome.ts +3 -2
- package/src/utils/thinking.ts +122 -0
- package/src/version.ts +16 -0
package/install.ps1
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# Thatgfsj Code Installer for Windows
|
|
2
|
+
# Usage:
|
|
3
|
+
# powershell -c "irm https://raw.githubusercontent.com/Thatgfsj/thatgfsj-code/main/install.ps1 | iex"
|
|
4
|
+
# powershell -c "& ([scriptblock]::Create((irm https://raw.githubusercontent.com/Thatgfsj/thatgfsj-code/main/install.ps1)))"
|
|
5
|
+
|
|
6
|
+
param(
|
|
7
|
+
[switch]$NoOpen,
|
|
8
|
+
[switch]$DryRun
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
$ErrorActionPreference = "Stop"
|
|
12
|
+
|
|
13
|
+
# Colors
|
|
14
|
+
function Write-Step { param($msg) Write-Host "[*] $msg" -ForegroundColor Yellow }
|
|
15
|
+
function Write-Success { param($msg) Write-Host "[✓] $msg" -ForegroundColor Green }
|
|
16
|
+
function Write-Error { param($msg) Write-Host "[✗] $msg" -ForegroundColor Red }
|
|
17
|
+
function Write-Info { param($msg) Write-Host " $msg" -ForegroundColor Gray }
|
|
18
|
+
|
|
19
|
+
Write-Host ""
|
|
20
|
+
Write-Host " Thatgfsj Code 安装向导" -ForegroundColor Cyan
|
|
21
|
+
Write-Host " =======================" -ForegroundColor Cyan
|
|
22
|
+
Write-Host ""
|
|
23
|
+
|
|
24
|
+
if ($DryRun) {
|
|
25
|
+
Write-Host "[DRY RUN] 仅显示将要执行的操作" -ForegroundColor Yellow
|
|
26
|
+
Write-Host ""
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# ============== Step 1: Check PowerShell ==============
|
|
30
|
+
Write-Step "检查 PowerShell 版本..."
|
|
31
|
+
|
|
32
|
+
if ($PSVersionTable.PSVersion.Major -lt 5) {
|
|
33
|
+
Write-Error "需要 PowerShell 5.0 或更高版本"
|
|
34
|
+
Write-Host "请升级您的 PowerShell: https://aka.ms/powershell" -ForegroundColor Gray
|
|
35
|
+
exit 1
|
|
36
|
+
}
|
|
37
|
+
Write-Success "PowerShell $($PSVersionTable.PSVersion) 检测正常"
|
|
38
|
+
|
|
39
|
+
# ============== Step 2: Check/Fetch Node.js ==============
|
|
40
|
+
Write-Step "检查 Node.js..."
|
|
41
|
+
|
|
42
|
+
function Test-NodeInstalled {
|
|
43
|
+
try {
|
|
44
|
+
$nodeVersion = node --version 2>$null
|
|
45
|
+
if ($nodeVersion) {
|
|
46
|
+
$version = [int]($nodeVersion -replace 'v(\d+)\..*', '$1')
|
|
47
|
+
return @{ installed = $true; version = $nodeVersion; major = $version }
|
|
48
|
+
}
|
|
49
|
+
} catch {}
|
|
50
|
+
return @{ installed = $false; version = $null; major = 0 }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
$nodeStatus = Test-NodeInstalled
|
|
54
|
+
if ($nodeStatus.installed -and $nodeStatus.major -ge 18) {
|
|
55
|
+
Write-Success "Node.js $($nodeStatus.version) 已安装"
|
|
56
|
+
} else {
|
|
57
|
+
Write-Host " 未检测到 Node.js 18+,开始安装..." -ForegroundColor Gray
|
|
58
|
+
|
|
59
|
+
# Try winget first
|
|
60
|
+
if (Get-Command winget -ErrorAction SilentlyContinue) {
|
|
61
|
+
Write-Info "使用 winget 安装..."
|
|
62
|
+
winget install OpenJS.NodeJS.LTS --source winget --accept-package-agreements --accept-source-agreements
|
|
63
|
+
|
|
64
|
+
# Refresh PATH
|
|
65
|
+
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
|
|
66
|
+
|
|
67
|
+
$nodeStatus = Test-NodeInstalled
|
|
68
|
+
if ($nodeStatus.installed) {
|
|
69
|
+
Write-Success "Node.js 安装完成"
|
|
70
|
+
} else {
|
|
71
|
+
Write-Host " 需要重启 PowerShell 后重新运行安装" -ForegroundColor Yellow
|
|
72
|
+
exit 0
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
# Try choco
|
|
76
|
+
elseif (Get-Command choco -ErrorAction SilentlyContinue) {
|
|
77
|
+
Write-Info "使用 Chocolatey 安装..."
|
|
78
|
+
choco install nodejs-lts -y
|
|
79
|
+
Write-Success "Node.js 安装完成 (可能需要重启)"
|
|
80
|
+
}
|
|
81
|
+
# Manual fallback
|
|
82
|
+
else {
|
|
83
|
+
Write-Host ""
|
|
84
|
+
Write-Error "未检测到包管理器 (winget/choco/scoop)"
|
|
85
|
+
Write-Host ""
|
|
86
|
+
Write-Host "请手动安装 Node.js:" -ForegroundColor Yellow
|
|
87
|
+
Write-Host " 1. 访问 https://nodejs.org" -ForegroundColor Gray
|
|
88
|
+
Write-Host " 2. 下载 LTS 版本 (推荐 v20 或 v22)" -ForegroundColor Gray
|
|
89
|
+
Write-Host " 3. 运行安装程序" -ForegroundColor Gray
|
|
90
|
+
Write-Host " 4. 重新运行此安装脚本" -ForegroundColor Gray
|
|
91
|
+
Write-Host ""
|
|
92
|
+
exit 1
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# ============== Step 3: Clone/Update Repository ==============
|
|
97
|
+
Write-Step "准备安装 Thatgfsj Code..."
|
|
98
|
+
|
|
99
|
+
$installDir = Join-Path $env:USERPROFILE "thatgfsj-code"
|
|
100
|
+
|
|
101
|
+
if (Test-Path $installDir) {
|
|
102
|
+
Write-Info "检测到已有安装,正在更新..."
|
|
103
|
+
if (-not $DryRun) {
|
|
104
|
+
Set-Location $installDir
|
|
105
|
+
git pull origin main 2>$null
|
|
106
|
+
if ($LASTEXITCODE -ne 0) {
|
|
107
|
+
# If pull fails, re-clone
|
|
108
|
+
Remove-Item $installDir -Recurse -Force
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
if (-not $DryRun) {
|
|
113
|
+
Write-Info "正在克隆仓库..."
|
|
114
|
+
git clone https://github.com/Thatgfsj/thatgfsj-code.git $installDir
|
|
115
|
+
Set-Location $installDir
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (-not (Test-Path (Join-Path $installDir "package.json"))) {
|
|
120
|
+
Write-Error "安装目录无效: $installDir"
|
|
121
|
+
exit 1
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
Write-Success "代码准备完成: $installDir"
|
|
125
|
+
|
|
126
|
+
# ============== Step 4: Install Dependencies ==============
|
|
127
|
+
Write-Step "安装依赖..."
|
|
128
|
+
|
|
129
|
+
if (-not $DryRun) {
|
|
130
|
+
Set-Location $installDir
|
|
131
|
+
npm install
|
|
132
|
+
if ($LASTEXITCODE -ne 0) {
|
|
133
|
+
Write-Error "npm install 失败"
|
|
134
|
+
exit 1
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
Write-Info "编译 TypeScript..."
|
|
138
|
+
npm run build
|
|
139
|
+
if ($LASTEXITCODE -ne 0) {
|
|
140
|
+
Write-Error "编译失败"
|
|
141
|
+
exit 1
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
Write-Success "依赖安装完成"
|
|
145
|
+
|
|
146
|
+
# ============== Step 5: Link Command ==============
|
|
147
|
+
Write-Step "设置命令..."
|
|
148
|
+
|
|
149
|
+
if (-not $DryRun) {
|
|
150
|
+
npm link
|
|
151
|
+
}
|
|
152
|
+
Write-Success "命令 'gfcode' 已可用"
|
|
153
|
+
|
|
154
|
+
# ============== Step 6: Setup API Key ==============
|
|
155
|
+
Write-Host ""
|
|
156
|
+
Write-Step "配置 API Key (可选)..."
|
|
157
|
+
|
|
158
|
+
$apiKeySet = $false
|
|
159
|
+
$providers = @{
|
|
160
|
+
"1" = @{name="SiliconFlow (推荐)"; var="SILICONFLOW_API_KEY"; url="https://siliconflow.cn"}
|
|
161
|
+
"2" = @{name="MiniMax"; var="MINIMAX_API_KEY"; url="https://platform.minimax.io"}
|
|
162
|
+
"3" = @{name="OpenAI"; var="OPENAI_API_KEY"; url="https://platform.openai.com"}
|
|
163
|
+
"4" = @{name="Anthropic"; var="ANTHROPIC_API_KEY"; url="https://www.anthropic.com"}
|
|
164
|
+
"5" = @{name="Google Gemini"; var="GEMINI_API_KEY"; url="https://aistudio.google.com/app/apikey"}
|
|
165
|
+
"6" = @{name="跳过 (稍后配置)"; var=""; url=""}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
Write-Host ""
|
|
169
|
+
Write-Host " 选择 AI Provider:" -ForegroundColor White
|
|
170
|
+
Write-Host ""
|
|
171
|
+
foreach ($key in $providers.Keys | Sort-Object) {
|
|
172
|
+
$p = $providers[$key]
|
|
173
|
+
Write-Host " $key. $($p.name)" -ForegroundColor Gray
|
|
174
|
+
}
|
|
175
|
+
Write-Host ""
|
|
176
|
+
|
|
177
|
+
if ($NoOpen) {
|
|
178
|
+
Write-Host " 使用 --NoOpen 跳过配置" -ForegroundColor Gray
|
|
179
|
+
} else {
|
|
180
|
+
$choice = Read-Host " 请选择 (1-6, 直接回车跳过)"
|
|
181
|
+
|
|
182
|
+
if ($choice -and $providers.ContainsKey($choice)) {
|
|
183
|
+
$selected = $providers[$choice]
|
|
184
|
+
if ($selected.var) {
|
|
185
|
+
Write-Host ""
|
|
186
|
+
Write-Host " 访问 $($selected.url) 获取 API Key" -ForegroundColor Cyan
|
|
187
|
+
Write-Host " 获取后粘贴到下方" -ForegroundColor Gray
|
|
188
|
+
Write-Host ""
|
|
189
|
+
|
|
190
|
+
$key = Read-Host " 请输入 API Key (输入后回车)"
|
|
191
|
+
|
|
192
|
+
if ($key) {
|
|
193
|
+
# Save to config file
|
|
194
|
+
$configDir = Join-Path $env:USERPROFILE ".thatgfsj"
|
|
195
|
+
if (-not (Test-Path $configDir)) {
|
|
196
|
+
New-Item -ItemType Directory -Path $configDir -Force | Out-Null
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
$configFile = Join-Path $configDir "config.json"
|
|
200
|
+
$config = @{
|
|
201
|
+
model = "Qwen/Qwen2.5-7B-Instruct"
|
|
202
|
+
apiKey = $key
|
|
203
|
+
provider = "siliconflow"
|
|
204
|
+
temperature = 0.7
|
|
205
|
+
maxTokens = 4096
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
# Set provider-specific defaults
|
|
209
|
+
switch ($choice) {
|
|
210
|
+
"1" { $config.provider = "siliconflow"; $config.model = "Qwen/Qwen2.5-7B-Instruct" }
|
|
211
|
+
"2" { $config.provider = "minimax"; $config.model = "MiniMax-M2.5" }
|
|
212
|
+
"3" { $config.provider = "openai"; $config.model = "gpt-4o-mini" }
|
|
213
|
+
"4" { $config.provider = "anthropic"; $config.model = "claude-3-haiku-20240307" }
|
|
214
|
+
"5" { $config.provider = "gemini"; $config.model = "gemini-1.5-flash-8b" }
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
$config | ConvertTo-Json | Set-Content $configFile -Encoding UTF8
|
|
218
|
+
Write-Success "配置已保存到: $configFile"
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
# ============== Done ==============
|
|
225
|
+
Write-Host ""
|
|
226
|
+
Write-Host " ======================================" -ForegroundColor Cyan
|
|
227
|
+
Write-Success " 安装完成!"
|
|
228
|
+
Write-Host " ======================================" -ForegroundColor Cyan
|
|
229
|
+
Write-Host ""
|
|
230
|
+
Write-Host " 使用方法:" -ForegroundColor White
|
|
231
|
+
Write-Host " gfcode init - 重新配置" -ForegroundColor Gray
|
|
232
|
+
Write-Host " gfcode - 启动交互模式" -ForegroundColor Gray
|
|
233
|
+
Write-Host " gfcode '你的问题' - 直接提问" -ForegroundColor Gray
|
|
234
|
+
Write-Host " gfcode explain '代码' - 解释代码" -ForegroundColor Gray
|
|
235
|
+
Write-Host " gfcode debug '代码' - 调试代码" -ForegroundColor Gray
|
|
236
|
+
Write-Host ""
|
|
237
|
+
Write-Host " 文档: https://github.com/Thatgfsj/thatgfsj-code" -ForegroundColor Gray
|
|
238
|
+
Write-Host ""
|
package/install.sh
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Thatgfsj Code Installer for macOS/Linux
|
|
3
|
+
# Usage: curl -sL https://raw.githubusercontent.com/Thatgfsj/thatgfsj-code/main/install.sh | bash
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
# Colors
|
|
8
|
+
RED='\033[0;31m'
|
|
9
|
+
GREEN='\033[0;32m'
|
|
10
|
+
YELLOW='\033[1;33m'
|
|
11
|
+
CYAN='\033[0;36m'
|
|
12
|
+
NC='\033[0m' # No Color
|
|
13
|
+
|
|
14
|
+
echo ""
|
|
15
|
+
echo -e "${CYAN} Thatgfsj Code 安装向导${NC}"
|
|
16
|
+
echo -e "${CYAN} =======================${NC}"
|
|
17
|
+
echo ""
|
|
18
|
+
|
|
19
|
+
# ============== Step 1: Check Node.js ==============
|
|
20
|
+
echo -e "${YELLOW}[*] 检查 Node.js...${NC}"
|
|
21
|
+
|
|
22
|
+
if command -v node &> /dev/null; then
|
|
23
|
+
NODE_VERSION=$(node --version)
|
|
24
|
+
MAJOR_VERSION=$(echo $NODE_VERSION | cut -d'v' -f2 | cut -d'.' -f1)
|
|
25
|
+
if [ "$MAJOR_VERSION" -ge 18 ]; then
|
|
26
|
+
echo -e "${GREEN}[✓] Node.js $NODE_VERSION 已安装${NC}"
|
|
27
|
+
else
|
|
28
|
+
echo -e "${RED}[✗] Node.js 版本过低,需要 v18+${NC}"
|
|
29
|
+
echo " 请访问 https://nodejs.org 升级"
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
else
|
|
33
|
+
echo -e "${YELLOW}[*] 未检测到 Node.js,开始安装...${NC}"
|
|
34
|
+
|
|
35
|
+
# Try Homebrew (macOS)
|
|
36
|
+
if command -v brew &> /dev/null; then
|
|
37
|
+
echo -e "${YELLOW}[*] 使用 Homebrew 安装...${NC}"
|
|
38
|
+
brew install node
|
|
39
|
+
# Try apt (Ubuntu/Debian)
|
|
40
|
+
elif command -v apt-get &> /dev/null; then
|
|
41
|
+
echo -e "${YELLOW}[*] 使用 apt 安装...${NC}"
|
|
42
|
+
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
|
|
43
|
+
sudo apt-get install -y nodejs
|
|
44
|
+
# Try yum (CentOS/RHEL)
|
|
45
|
+
elif command -v yum &> /dev/null; then
|
|
46
|
+
echo -e "${YELLOW}[*] 使用 yum 安装...${NC}"
|
|
47
|
+
curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -
|
|
48
|
+
sudo yum install -y nodejs
|
|
49
|
+
else
|
|
50
|
+
echo -e "${RED}[✗] 未找到包管理器${NC}"
|
|
51
|
+
echo " 请手动安装: https://nodejs.org"
|
|
52
|
+
exit 1
|
|
53
|
+
fi
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# ============== Step 2: Clone/Update ==============
|
|
57
|
+
echo -e "${YELLOW}[*] 准备安装 Thatgfsj Code...${NC}"
|
|
58
|
+
|
|
59
|
+
INSTALL_DIR="$HOME/thatgfsj-code"
|
|
60
|
+
|
|
61
|
+
if [ -d "$INSTALL_DIR" ]; then
|
|
62
|
+
echo -e "${YELLOW}[*] 检测到已有安装,正在更新...${NC}"
|
|
63
|
+
cd "$INSTALL_DIR"
|
|
64
|
+
git pull origin main 2>/dev/null || {
|
|
65
|
+
echo -e "${YELLOW}[*] 更新失败,重新克隆...${NC}"
|
|
66
|
+
rm -rf "$INSTALL_DIR"
|
|
67
|
+
}
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
if [ ! -d "$INSTALL_DIR" ]; then
|
|
71
|
+
echo -e "${YELLOW}[*] 克隆仓库...${NC}"
|
|
72
|
+
git clone https://github.com/Thatgfsj/thatgfsj-code.git "$INSTALL_DIR"
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
cd "$INSTALL_DIR"
|
|
76
|
+
|
|
77
|
+
if [ ! -f "package.json" ]; then
|
|
78
|
+
echo -e "${RED}[✗] 安装目录无效${NC}"
|
|
79
|
+
exit 1
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
echo -e "${GREEN}[✓] 代码准备完成: $INSTALL_DIR${NC}"
|
|
83
|
+
|
|
84
|
+
# ============== Step 3: Install Dependencies ==============
|
|
85
|
+
echo -e "${YELLOW}[*] 安装依赖...${NC}"
|
|
86
|
+
|
|
87
|
+
npm install
|
|
88
|
+
npm run build
|
|
89
|
+
|
|
90
|
+
echo -e "${GREEN}[✓] 依赖安装完成${NC}"
|
|
91
|
+
|
|
92
|
+
# ============== Step 4: Link Command ==============
|
|
93
|
+
echo -e "${YELLOW}[*] 设置命令...${NC}"
|
|
94
|
+
|
|
95
|
+
npm link
|
|
96
|
+
|
|
97
|
+
echo -e "${GREEN}[✓] 命令 'gfcode' 已可用${NC}"
|
|
98
|
+
|
|
99
|
+
# ============== Done ==============
|
|
100
|
+
echo ""
|
|
101
|
+
echo -e "${CYAN} ======================================${NC}"
|
|
102
|
+
echo -e "${GREEN} 安装完成!${NC}"
|
|
103
|
+
echo -e "${CYAN} ======================================${NC}"
|
|
104
|
+
echo ""
|
|
105
|
+
echo -e " ${WHITE}使用方法:${NC}"
|
|
106
|
+
echo -e " gfcode init - 重新配置"
|
|
107
|
+
echo -e " gfcode - 启动交互模式"
|
|
108
|
+
echo -e " gfcode '你的问题' - 直接提问"
|
|
109
|
+
echo -e " gfcode explain '代码' - 解释代码"
|
|
110
|
+
echo -e " gfcode debug '代码' - 调试代码"
|
|
111
|
+
echo ""
|
|
112
|
+
echo -e " 文档: ${CYAN}https://github.com/Thatgfsj/thatgfsj-code${NC}"
|
|
113
|
+
echo ""
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thatgfsj-code",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.2.9",
|
|
4
4
|
"description": "Thatgfsj Code - AI Coding Assistant",
|
|
5
5
|
"main": "dist/cmd/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
"build": "tsc",
|
|
13
13
|
"start": "node dist/cmd/index.js",
|
|
14
14
|
"dev": "tsc && node dist/cmd/index.js",
|
|
15
|
-
"link": "npm link"
|
|
15
|
+
"link": "npm link",
|
|
16
|
+
"test": "node tests/smoke-thinking.mjs && node tests/smoke-tool.mjs && node tests/smoke-pollution.mjs && node tests/edge-thinking.mjs && node tests/edge-tool.mjs && node tests/edge-pollution.mjs && node tests/edge-session.mjs && node tests/edge-cli.mjs",
|
|
17
|
+
"test:single": "node tests/",
|
|
18
|
+
"prepublishOnly": "npm run build && npm test"
|
|
16
19
|
},
|
|
17
20
|
"keywords": [
|
|
18
21
|
"ai",
|
package/src/app/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { ToolRegistry } from '../tools/index.js';
|
|
|
10
10
|
import { HookManager } from '../hooks/index.js';
|
|
11
11
|
import { SystemPromptBuilder } from '../prompts/index.js';
|
|
12
12
|
import { SkillRegistry } from '../skills/index.js';
|
|
13
|
+
import { compressThinking } from '../utils/thinking.js';
|
|
13
14
|
import type { ChatMessage, ChatResponse } from '../types.js';
|
|
14
15
|
|
|
15
16
|
export class App {
|
|
@@ -20,6 +21,12 @@ export class App {
|
|
|
20
21
|
hooks: HookManager;
|
|
21
22
|
prompts: SystemPromptBuilder;
|
|
22
23
|
skills: SkillRegistry;
|
|
24
|
+
/**
|
|
25
|
+
* v2.2.5 (product 0.4.2): toggle <think> block compression. Default
|
|
26
|
+
* true. Toggled by `--show-thinking` on the CLI or `/thinking on|off`
|
|
27
|
+
* in the REPL.
|
|
28
|
+
*/
|
|
29
|
+
showThinking: boolean = false;
|
|
23
30
|
|
|
24
31
|
private constructor(
|
|
25
32
|
config: ConfigManager,
|
|
@@ -83,6 +90,14 @@ export class App {
|
|
|
83
90
|
|
|
84
91
|
/**
|
|
85
92
|
* Run a single prompt (non-interactive mode)
|
|
93
|
+
*
|
|
94
|
+
* v2.2.4 (port from v2.1.0): persistence of the assistant message
|
|
95
|
+
* uses addMessageSafe, which drops the message if it contains
|
|
96
|
+
* pollution markers like "[已中断]".
|
|
97
|
+
*
|
|
98
|
+
* v2.2.5 (product 0.4.2): persistence also strips <think> blocks
|
|
99
|
+
* (and similar reasoning delimiters) when showThinking is false,
|
|
100
|
+
* so the conversation log stays compact.
|
|
86
101
|
*/
|
|
87
102
|
async runPrompt(prompt: string): Promise<string> {
|
|
88
103
|
this.session.addMessage('user', prompt);
|
|
@@ -94,15 +109,16 @@ export class App {
|
|
|
94
109
|
fullResponse += chunk;
|
|
95
110
|
}
|
|
96
111
|
} catch (err) {
|
|
97
|
-
// Re-throw
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
112
|
+
// Re-throw without persisting partial response. Persisting
|
|
113
|
+
// truncated output here was the source of the [已中断] loop in
|
|
114
|
+
// v2.2.3.
|
|
101
115
|
throw err;
|
|
102
116
|
}
|
|
103
117
|
|
|
104
118
|
console.log();
|
|
105
|
-
|
|
119
|
+
// v2.2.5: compress <think> blocks before persisting.
|
|
120
|
+
const toPersist = compressThinking(fullResponse, this.showThinking);
|
|
121
|
+
this.session.addMessageSafe('assistant', toPersist);
|
|
106
122
|
return fullResponse;
|
|
107
123
|
}
|
|
108
124
|
}
|
package/src/cmd/index.tsx
CHANGED
|
@@ -2,18 +2,60 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
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.
|
|
5
26
|
*/
|
|
6
27
|
|
|
7
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.
|
|
8
34
|
try {
|
|
9
|
-
require('child_process').execSync('chcp 65001', { stdio: 'ignore', windowsHide: true });
|
|
10
|
-
} catch {
|
|
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');
|
|
11
51
|
}
|
|
12
52
|
|
|
13
53
|
import { program } from 'commander';
|
|
14
54
|
import chalk from 'chalk';
|
|
15
55
|
import { App } from '../app/index.js';
|
|
16
56
|
import { WelcomeScreen } from '../tui/welcome.js';
|
|
57
|
+
import { compressThinking, summarizeThinking, splitThinking } from '../utils/thinking.js';
|
|
58
|
+
import { PRODUCT_VERSION } from '../version.js';
|
|
17
59
|
|
|
18
60
|
process.on('uncaughtException', (error) => {
|
|
19
61
|
console.error(chalk.red('\n Error:'), error.message);
|
|
@@ -28,11 +70,12 @@ process.on('unhandledRejection', (reason) => {
|
|
|
28
70
|
program
|
|
29
71
|
.name('gfcode')
|
|
30
72
|
.description('Thatgfsj Code - AI Coding Assistant')
|
|
31
|
-
.version(
|
|
73
|
+
.version(PRODUCT_VERSION)
|
|
32
74
|
.argument('[prompt]', 'Task to execute (omit to start interactive mode)')
|
|
33
75
|
.option('-m, --model <model>', 'Specify model')
|
|
34
76
|
.option('-i, --interactive', 'Force interactive mode')
|
|
35
|
-
.
|
|
77
|
+
.option('--show-thinking', 'Show full <think>...</think> reasoning blocks (default: compress to one-line summary)')
|
|
78
|
+
.action(async (prompt: string | undefined, options: { model?: string; interactive?: boolean; showThinking?: boolean }) => {
|
|
36
79
|
try {
|
|
37
80
|
const app = await App.create();
|
|
38
81
|
|
|
@@ -78,16 +121,36 @@ program
|
|
|
78
121
|
|
|
79
122
|
app.session.addMessage('user', prompt);
|
|
80
123
|
let fullResponse = '';
|
|
124
|
+
// v2.2.4: track whether the stream was aborted so we don't
|
|
125
|
+
// persist a truncated assistant message (which is what caused
|
|
126
|
+
// the [已中断] hallucination loop in v2.2.3).
|
|
127
|
+
const abortCtrl = new AbortController();
|
|
128
|
+
const onSigInt = () => { abortCtrl.abort(); };
|
|
129
|
+
process.once('SIGINT', onSigInt);
|
|
130
|
+
// v2.2.5 (product 0.4.2): compress <think> blocks at render time
|
|
131
|
+
// AND before persisting to history. Without this, every assistant
|
|
132
|
+
// message balloons history with reasoning the user has already
|
|
133
|
+
// seen compressed, and context windows blow up fast.
|
|
134
|
+
const showThinking = !!options.showThinking;
|
|
81
135
|
|
|
82
136
|
try {
|
|
83
|
-
process.stdout.write(chalk.gray(' Thinking
|
|
137
|
+
process.stdout.write(chalk.gray(' Thinking...'));
|
|
84
138
|
const stream = app.streamResponse();
|
|
85
139
|
|
|
140
|
+
// v2.2.5: stream-time rendering stays simple — we just buffer
|
|
141
|
+
// the full response. Post-process at the end (compress
|
|
142
|
+
// thinking blocks once). This avoids a state machine for
|
|
143
|
+
// "are we inside <think> right now" mid-stream which would
|
|
144
|
+
// be flaky if the model splits the tag across chunks.
|
|
86
145
|
for await (const chunk of stream) {
|
|
87
|
-
|
|
88
|
-
|
|
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');
|
|
89
150
|
|
|
90
|
-
// Parse tool messages
|
|
151
|
+
// Parse tool messages — these should NOT be compressed even
|
|
152
|
+
// when thinking is hidden, because they carry real signal
|
|
153
|
+
// (tool name + args + result).
|
|
91
154
|
if (chunk.includes('@@TOOL@@')) {
|
|
92
155
|
const parts = chunk.split('\n');
|
|
93
156
|
for (const part of parts) {
|
|
@@ -97,26 +160,93 @@ program
|
|
|
97
160
|
if (data.action === 'call') {
|
|
98
161
|
console.log();
|
|
99
162
|
console.log(chalk.cyan(` ⚙ ${data.name}: ${formatArgs(data.args)}`));
|
|
163
|
+
console.log(chalk.gray(' ' + '─'.repeat(40)));
|
|
100
164
|
} else if (data.action === 'result') {
|
|
101
165
|
const output = data.output || data.error || '';
|
|
102
|
-
const lines = output.split('\n')
|
|
103
|
-
|
|
166
|
+
const lines = output.split('\n');
|
|
167
|
+
const MAX_TOOL_LINES = 20;
|
|
168
|
+
const truncated = lines.length > MAX_TOOL_LINES;
|
|
169
|
+
const visible = truncated ? lines.slice(0, MAX_TOOL_LINES) : lines;
|
|
170
|
+
for (const line of visible) {
|
|
104
171
|
console.log(chalk.gray(' │ ') + line);
|
|
105
172
|
}
|
|
173
|
+
if (truncated) {
|
|
174
|
+
console.log(chalk.gray(` │ ... (${lines.length - MAX_TOOL_LINES} more lines truncated)`));
|
|
175
|
+
}
|
|
176
|
+
console.log(chalk.gray(' ' + '─'.repeat(40)));
|
|
106
177
|
}
|
|
107
178
|
} catch {}
|
|
108
179
|
} else if (part) {
|
|
180
|
+
// Mid-stream text chunk — leave it raw. The post-
|
|
181
|
+
// process step below will compress any <think>
|
|
182
|
+
// blocks before printing them as the final rendered
|
|
183
|
+
// output. (We can't reliably strip mid-stream
|
|
184
|
+
// because the closing </think> might be in a later
|
|
185
|
+
// chunk.)
|
|
109
186
|
process.stdout.write(chalk.cyan(' │ ') + part);
|
|
110
187
|
}
|
|
111
188
|
}
|
|
112
189
|
} else {
|
|
113
|
-
|
|
190
|
+
// Plain AI text chunk — buffer only. Same reasoning:
|
|
191
|
+
// post-process at end avoids mid-stream delimiter
|
|
192
|
+
// races.
|
|
193
|
+
fullResponse += chunk;
|
|
194
|
+
// Show the chunk as it streams, but we'll re-render the
|
|
195
|
+
// final compressed version below. To avoid double-
|
|
196
|
+
// printing, only echo when we're NOT going to post-
|
|
197
|
+
// process (i.e., when showThinking is true and we want
|
|
198
|
+
// to see everything live).
|
|
199
|
+
if (showThinking) {
|
|
200
|
+
process.stdout.write(chalk.cyan(' │ ') + chunk);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// v2.2.5: post-process. Strip <think> blocks, summarize,
|
|
206
|
+
// and print the cleaned conclusion if we suppressed it
|
|
207
|
+
// during streaming.
|
|
208
|
+
if (!showThinking && !abortCtrl.signal.aborted && fullResponse) {
|
|
209
|
+
const split = splitThinking(fullResponse);
|
|
210
|
+
if (split.thinking) {
|
|
211
|
+
// Clear what we already streamed (we suppressed text
|
|
212
|
+
// chunks during streaming, so there's nothing to clear
|
|
213
|
+
// unless showThinking was on; in that case the text
|
|
214
|
+
// already contains the <think> block live and there's
|
|
215
|
+
// no point double-printing).
|
|
216
|
+
const summary = summarizeThinking(split);
|
|
217
|
+
if (summary) {
|
|
218
|
+
console.log(chalk.gray(` ${summary}`));
|
|
219
|
+
}
|
|
220
|
+
if (split.conclusion) {
|
|
221
|
+
console.log(chalk.cyan(' │ ') + split.conclusion);
|
|
222
|
+
}
|
|
223
|
+
} else if (fullResponse.trim()) {
|
|
224
|
+
// No thinking block found — print the conclusion if we
|
|
225
|
+
// hadn't already (showThinking=false suppresses
|
|
226
|
+
// streaming text).
|
|
227
|
+
console.log(chalk.cyan(' │ ') + fullResponse);
|
|
114
228
|
}
|
|
115
|
-
fullResponse += chunk;
|
|
116
229
|
}
|
|
117
230
|
|
|
118
231
|
console.log();
|
|
119
|
-
|
|
232
|
+
// v2.2.4: skip persistence on abort.
|
|
233
|
+
if (!abortCtrl.signal.aborted) {
|
|
234
|
+
// v2.2.5: persist only the conclusion (no <think> blocks)
|
|
235
|
+
// when compression is enabled. This keeps the conversation
|
|
236
|
+
// log readable AND keeps context-window usage low.
|
|
237
|
+
const toPersist = showThinking
|
|
238
|
+
? fullResponse
|
|
239
|
+
: compressThinking(fullResponse, false);
|
|
240
|
+
const accepted = app.session.addMessageSafe('assistant', toPersist);
|
|
241
|
+
if (!accepted) {
|
|
242
|
+
console.error(chalk.yellow(
|
|
243
|
+
' ⚠️ Dropped assistant message containing [已中断] marker.\n' +
|
|
244
|
+
' (prevents hallucination loop — try your question again)'
|
|
245
|
+
));
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
console.log(chalk.yellow(' ⏹ Cancelled (response not saved)'));
|
|
249
|
+
}
|
|
120
250
|
} catch (error: any) {
|
|
121
251
|
const msg = error.message || String(error);
|
|
122
252
|
if (msg.includes('401') || msg.includes('403') || msg.includes('Unauthorized')) {
|
|
@@ -131,6 +261,8 @@ program
|
|
|
131
261
|
} else {
|
|
132
262
|
console.error(chalk.red(`\n Error: ${msg}`));
|
|
133
263
|
}
|
|
264
|
+
} finally {
|
|
265
|
+
process.removeListener('SIGINT', onSigInt);
|
|
134
266
|
}
|
|
135
267
|
}
|
|
136
268
|
} catch (error: any) {
|