thatgfsj-code 0.5.0 → 0.6.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.
Files changed (78) hide show
  1. package/README.md +7 -12
  2. package/dist/app/index.js +1 -1
  3. package/dist/app/index.js.map +1 -1
  4. package/dist/cmd/index.d.ts +1 -2
  5. package/dist/cmd/index.d.ts.map +1 -1
  6. package/dist/cmd/index.js +26 -23
  7. package/dist/cmd/index.js.map +1 -1
  8. package/dist/config/index.d.ts.map +1 -1
  9. package/dist/config/index.js +1 -0
  10. package/dist/config/index.js.map +1 -1
  11. package/dist/config/types.d.ts +1 -0
  12. package/dist/config/types.d.ts.map +1 -1
  13. package/dist/tui/app.d.ts +9 -0
  14. package/dist/tui/app.d.ts.map +1 -0
  15. package/dist/tui/app.js +40 -0
  16. package/dist/tui/app.js.map +1 -0
  17. package/dist/tui/components/ChatList.d.ts +14 -0
  18. package/dist/tui/components/ChatList.d.ts.map +1 -0
  19. package/dist/tui/components/ChatList.js +11 -0
  20. package/dist/tui/components/ChatList.js.map +1 -0
  21. package/dist/tui/components/ChatMessage.d.ts +14 -0
  22. package/dist/tui/components/ChatMessage.d.ts.map +1 -0
  23. package/dist/tui/components/ChatMessage.js +17 -0
  24. package/dist/tui/components/ChatMessage.js.map +1 -0
  25. package/dist/tui/components/Header.d.ts +9 -0
  26. package/dist/tui/components/Header.d.ts.map +1 -0
  27. package/dist/tui/components/Header.js +6 -0
  28. package/dist/tui/components/Header.js.map +1 -0
  29. package/dist/tui/components/Markdown.d.ts +8 -0
  30. package/dist/tui/components/Markdown.d.ts.map +1 -0
  31. package/dist/tui/components/Markdown.js +31 -0
  32. package/dist/tui/components/Markdown.js.map +1 -0
  33. package/dist/tui/components/Thinking.d.ts +8 -0
  34. package/dist/tui/components/Thinking.d.ts.map +1 -0
  35. package/dist/tui/components/Thinking.js +7 -0
  36. package/dist/tui/components/Thinking.js.map +1 -0
  37. package/dist/tui/components/ToolCall.d.ts +14 -0
  38. package/dist/tui/components/ToolCall.d.ts.map +1 -0
  39. package/dist/tui/components/ToolCall.js +37 -0
  40. package/dist/tui/components/ToolCall.js.map +1 -0
  41. package/dist/tui/components/UserInput.d.ts +9 -0
  42. package/dist/tui/components/UserInput.d.ts.map +1 -0
  43. package/dist/tui/components/UserInput.js +82 -0
  44. package/dist/tui/components/UserInput.js.map +1 -0
  45. package/dist/tui/hooks/useChat.d.ts +11 -0
  46. package/dist/tui/hooks/useChat.d.ts.map +1 -0
  47. package/dist/tui/hooks/useChat.js +138 -0
  48. package/dist/tui/hooks/useChat.js.map +1 -0
  49. package/dist/tui/hooks/useCommands.d.ts +10 -0
  50. package/dist/tui/hooks/useCommands.d.ts.map +1 -0
  51. package/dist/tui/hooks/useCommands.js +51 -0
  52. package/dist/tui/hooks/useCommands.js.map +1 -0
  53. package/dist/tui/index.d.ts +2 -4
  54. package/dist/tui/index.d.ts.map +1 -1
  55. package/dist/tui/index.js +2 -4
  56. package/dist/tui/index.js.map +1 -1
  57. package/dist/tui/welcome.d.ts.map +1 -1
  58. package/dist/tui/welcome.js +22 -4
  59. package/dist/tui/welcome.js.map +1 -1
  60. package/install.ps1 +171 -192
  61. package/package.json +9 -1
  62. package/src/app/index.ts +1 -1
  63. package/src/cmd/{index.ts → index.tsx} +28 -27
  64. package/src/config/index.ts +1 -0
  65. package/src/config/types.ts +1 -0
  66. package/src/tui/app.tsx +60 -0
  67. package/src/tui/components/ChatList.tsx +29 -0
  68. package/src/tui/components/ChatMessage.tsx +51 -0
  69. package/src/tui/components/Header.tsx +22 -0
  70. package/src/tui/components/Markdown.tsx +35 -0
  71. package/src/tui/components/Thinking.tsx +19 -0
  72. package/src/tui/components/ToolCall.tsx +72 -0
  73. package/src/tui/components/UserInput.tsx +101 -0
  74. package/src/tui/hooks/useChat.ts +154 -0
  75. package/src/tui/hooks/useCommands.ts +63 -0
  76. package/src/tui/index.ts +2 -4
  77. package/src/tui/welcome.ts +24 -5
  78. package/tsconfig.json +5 -3
package/install.ps1 CHANGED
@@ -1,238 +1,217 @@
1
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
- )
2
+ # Works on any Windows machine - auto-downloads Node.js if needed
3
+ #
4
+ # Usage:
5
+ # irm https://raw.githubusercontent.com/Thatgfsj/thatgfsj-code/master/install.ps1 | iex
10
6
 
11
7
  $ErrorActionPreference = "Stop"
12
8
 
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 }
9
+ function Log($msg) { Write-Host " $msg" -ForegroundColor Gray }
10
+ function Step($msg) { Write-Host " > $msg" -ForegroundColor Cyan }
11
+ function Ok($msg) { Write-Host " OK $msg" -ForegroundColor Green }
12
+ function Fail($msg) { Write-Host " !! $msg" -ForegroundColor Red }
18
13
 
19
14
  Write-Host ""
20
- Write-Host " Thatgfsj Code 安装向导" -ForegroundColor Cyan
21
- Write-Host " =======================" -ForegroundColor Cyan
15
+ Write-Host " Thatgfsj Code Installer" -ForegroundColor Cyan
16
+ Write-Host " ────────────────────────" -ForegroundColor DarkGray
22
17
  Write-Host ""
23
18
 
24
- if ($DryRun) {
25
- Write-Host "[DRY RUN] 仅显示将要执行的操作" -ForegroundColor Yellow
26
- Write-Host ""
27
- }
28
-
29
- # ============== Step 1: Check PowerShell ==============
30
- Write-Step "检查 PowerShell 版本..."
19
+ # ── Helper: Refresh PATH from registry ────────────────────
31
20
 
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
21
+ function Refresh-Path {
22
+ $machinePath = [System.Environment]::GetEnvironmentVariable("Path", "Machine")
23
+ $userPath = [System.Environment]::GetEnvironmentVariable("Path", "User")
24
+ $env:Path = "$machinePath;$userPath"
36
25
  }
37
- Write-Success "PowerShell $($PSVersionTable.PSVersion) 检测正常"
38
26
 
39
- # ============== Step 2: Check/Fetch Node.js ==============
40
- Write-Step "检查 Node.js..."
27
+ # ── Helper: Download file with progress ───────────────────
41
28
 
42
- function Test-NodeInstalled {
29
+ function Download-File {
30
+ param($url, $dest)
31
+ Log "Downloading $url ..."
43
32
  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 }
33
+ $wc = New-Object System.Net.WebClient
34
+ $wc.DownloadFile($url, $dest)
35
+ } catch {
36
+ # Fallback to Invoke-WebRequest
37
+ Invoke-WebRequest -Uri $url -OutFile $dest -UseBasicParsing
38
+ }
51
39
  }
52
40
 
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 安装完成 (可能需要重启)"
41
+ # ── Helper: Install Node.js by downloading binary ─────────
42
+
43
+ function Install-NodeJs-Binary {
44
+ Log "Downloading Node.js LTS binary..."
45
+
46
+ $nodeVersion = "v22.16.0"
47
+ $arch = if ([Environment]::Is64BitOperatingSystem) { "x64" } else { "x86" }
48
+ $filename = "node-$nodeVersion-win-$arch"
49
+ $zipUrl = "https://nodejs.org/dist/$nodeVersion/$filename.zip"
50
+
51
+ $tempDir = Join-Path $env:TEMP "gfcode-node-install"
52
+ $zipFile = Join-Path $tempDir "$filename.zip"
53
+ $extractDir = Join-Path $tempDir $filename
54
+
55
+ # Clean up previous attempts
56
+ if (Test-Path $tempDir) { Remove-Item $tempDir -Recurse -Force }
57
+ New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
58
+
59
+ # Download
60
+ Download-File $zipUrl $zipFile
61
+
62
+ # Extract
63
+ Log "Extracting..."
64
+ Expand-Archive -Path $zipFile -DestinationPath $tempDir -Force
65
+
66
+ # Move to install location
67
+ $installBase = Join-Path $env:USERPROFILE ".gfcode-node"
68
+ if (Test-Path $installBase) { Remove-Item $installBase -Recurse -Force }
69
+ Move-Item $extractDir $installBase
70
+
71
+ # Add to PATH (current session + user env)
72
+ $nodeBin = $installBase
73
+ $env:Path = "$nodeBin;$env:Path"
74
+
75
+ # Persist to user PATH
76
+ $userPath = [System.Environment]::GetEnvironmentVariable("Path", "User")
77
+ if ($userPath -notlike "*$nodeBin*") {
78
+ [System.Environment]::SetEnvironmentVariable("Path", "$nodeBin;$userPath", "User")
80
79
  }
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
80
+
81
+ # Cleanup
82
+ Remove-Item $tempDir -Recurse -Force -ErrorAction SilentlyContinue
83
+
84
+ # Verify
85
+ try {
86
+ $ver = & "$nodeBin\node.exe" --version
87
+ Ok "Node.js $ver (portable)"
88
+ return $true
89
+ } catch {
90
+ return $false
93
91
  }
94
92
  }
95
93
 
96
- # ============== Step 3: Clone/Update Repository ==============
97
- Write-Step "准备安装 Thatgfsj Code..."
94
+ # ── Step 1: Check / Install Node.js ───────────────────────
95
+
96
+ Step "Checking Node.js..."
98
97
 
99
- $installDir = Join-Path $env:USERPROFILE "thatgfsj-code"
98
+ $nodeOk = $false
100
99
 
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
100
+ # Check if node is available
101
+ try {
102
+ $ver = node --version 2>$null
103
+ if ($ver) {
104
+ $major = [int]($ver -replace 'v(\d+)\..*', '$1')
105
+ if ($major -ge 18) {
106
+ Ok "Node.js $ver"
107
+ $nodeOk = $true
108
+ } else {
109
+ Log "Node.js $ver found but too old (need 18+)"
109
110
  }
110
111
  }
111
- } else {
112
- if (-not $DryRun) {
113
- Write-Info "正在克隆仓库..."
114
- git clone https://github.com/Thatgfsj/thatgfsj-code.git $installDir
115
- Set-Location $installDir
112
+ } catch {}
113
+
114
+ if (-not $nodeOk) {
115
+ Log "Node.js 18+ not found."
116
+
117
+ # Try 1: winget
118
+ if (-not $nodeOk -and (Get-Command winget -ErrorAction SilentlyContinue)) {
119
+ Log "Trying winget..."
120
+ try {
121
+ winget install OpenJS.NodeJS.LTS --accept-package-agreements --accept-source-agreements 2>&1 | Out-Null
122
+ Refresh-Path
123
+ $ver = node --version 2>$null
124
+ if ($ver -and [int]($ver -replace 'v(\d+)\..*', '$1') -ge 18) {
125
+ Ok "Node.js $ver via winget"
126
+ $nodeOk = $true
127
+ }
128
+ } catch {}
116
129
  }
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
130
 
126
- # ============== Step 4: Install Dependencies ==============
127
- Write-Step "安装依赖..."
131
+ # Try 2: choco
132
+ if (-not $nodeOk -and (Get-Command choco -ErrorAction SilentlyContinue)) {
133
+ Log "Trying Chocolatey..."
134
+ try {
135
+ choco install nodejs-lts -y 2>&1 | Out-Null
136
+ Refresh-Path
137
+ $ver = node --version 2>$null
138
+ if ($ver -and [int]($ver -replace 'v(\d+)\..*', '$1') -ge 18) {
139
+ Ok "Node.js $ver via Chocolatey"
140
+ $nodeOk = $true
141
+ }
142
+ } catch {}
143
+ }
128
144
 
129
- if (-not $DryRun) {
130
- Set-Location $installDir
131
- npm install
132
- if ($LASTEXITCODE -ne 0) {
133
- Write-Error "npm install 失败"
134
- exit 1
145
+ # Try 3: Direct download (works on any machine)
146
+ if (-not $nodeOk) {
147
+ $nodeOk = Install-NodeJs-Binary
135
148
  }
136
-
137
- Write-Info "编译 TypeScript..."
138
- npm run build
139
- if ($LASTEXITCODE -ne 0) {
140
- Write-Error "编译失败"
149
+
150
+ if (-not $nodeOk) {
151
+ Fail "Failed to install Node.js"
152
+ Log "Please install manually: https://nodejs.org"
141
153
  exit 1
142
154
  }
143
155
  }
144
- Write-Success "依赖安装完成"
145
156
 
146
- # ============== Step 5: Link Command ==============
147
- Write-Step "设置命令..."
157
+ # ── Step 2: Install gfcode via npm ────────────────────────
158
+
159
+ Step "Installing thatgfsj-code..."
160
+
161
+ $npmPath = Join-Path (Split-Path (Get-Command node).Source) "npm.cmd"
162
+ $npmGlobalPrefix = & node -e "console.log(require('path').resolve(process.execPath, '..','..'))" 2>$null
163
+ $npmBinDir = Join-Path $npmGlobalPrefix "bin"
148
164
 
149
- if (-not $DryRun) {
150
- npm link
165
+ # Install globally
166
+ $env:npm_config_prefix = $npmGlobalPrefix
167
+ & node (Join-Path (Split-Path (Get-Command node).Source) "node_modules\npm\bin\npm-cli.js") install -g thatgfsj-code 2>&1 | Out-Null
168
+
169
+ if ($LASTEXITCODE -ne 0) {
170
+ # Fallback: try npm directly
171
+ npm install -g thatgfsj-code 2>&1 | Out-Null
151
172
  }
152
- Write-Success "命令 'gfcode' 已可用"
153
173
 
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=""}
174
+ if ($LASTEXITCODE -ne 0) {
175
+ Fail "npm install failed"
176
+ exit 1
166
177
  }
167
178
 
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
179
+ Ok "thatgfsj-code installed"
180
+
181
+ # Make sure gfcode is in PATH for this session
182
+ $npmBin = Join-Path $npmGlobalPrefix "bin"
183
+ if (Test-Path $npmBin) {
184
+ $env:Path = "$npmBin;$env:Path"
174
185
  }
175
- Write-Host ""
176
186
 
177
- if ($NoOpen) {
178
- Write-Host " 使用 --NoOpen 跳过配置" -ForegroundColor Gray
187
+ # ── Step 3: Verify ────────────────────────────────────────
188
+
189
+ Step "Verifying..."
190
+
191
+ $gfcodePath = Get-Command gfcode -ErrorAction SilentlyContinue
192
+ if ($gfcodePath) {
193
+ $cliVer = gfcode --version 2>$null
194
+ Ok "gfcode v$cliVer"
179
195
  } 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
- }
196
+ # Try direct path
197
+ $directPath = Join-Path $npmGlobalPrefix "bin\gfcode.cmd"
198
+ if (Test-Path $directPath) {
199
+ Ok "gfcode installed at $directPath"
200
+ Log "You may need to restart your terminal."
201
+ } else {
202
+ Ok "gfcode installed (restart terminal if command not found)"
221
203
  }
222
204
  }
223
205
 
224
- # ============== Done ==============
206
+ # ── Done ──────────────────────────────────────────────────
207
+
225
208
  Write-Host ""
226
- Write-Host " ======================================" -ForegroundColor Cyan
227
- Write-Success " 安装完成!"
228
- Write-Host " ======================================" -ForegroundColor Cyan
209
+ Write-Host " ────────────────────────────────" -ForegroundColor DarkGray
210
+ Write-Host " Done!" -ForegroundColor Green
229
211
  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
212
+ Write-Host " Next steps:" -ForegroundColor White
213
+ Write-Host " gfcode init " -ForegroundColor Cyan -NoNewline; Write-Host "# Configure API key" -ForegroundColor DarkGray
214
+ Write-Host " gfcode " -ForegroundColor Cyan -NoNewline; Write-Host "# Start coding" -ForegroundColor DarkGray
236
215
  Write-Host ""
237
- Write-Host " 文档: https://github.com/Thatgfsj/thatgfsj-code" -ForegroundColor Gray
216
+ Write-Host " Docs: https://github.com/Thatgfsj/thatgfsj-code" -ForegroundColor DarkGray
238
217
  Write-Host ""
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thatgfsj-code",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Thatgfsj Code - AI Coding Assistant",
5
5
  "main": "dist/cmd/index.js",
6
6
  "type": "module",
@@ -25,12 +25,20 @@
25
25
  "dependencies": {
26
26
  "chalk": "^5.3.0",
27
27
  "commander": "^11.1.0",
28
+ "ink": "^7.1.0",
29
+ "ink-spinner": "^5.0.0",
30
+ "ink-text-input": "^6.0.0",
28
31
  "inquirer": "^13.3.2",
32
+ "marked": "^15.0.12",
33
+ "marked-terminal": "^7.3.0",
29
34
  "ora": "^7.0.1",
35
+ "react": "^19.2.7",
30
36
  "readline": "^1.3.0"
31
37
  },
32
38
  "devDependencies": {
39
+ "@types/marked-terminal": "^6.1.1",
33
40
  "@types/node": "^20.10.0",
41
+ "@types/react": "^19.2.17",
34
42
  "typescript": "^5.3.0"
35
43
  }
36
44
  }
package/src/app/index.ts CHANGED
@@ -40,7 +40,7 @@ export class App {
40
40
  const aiConfig = config.getAIConfig();
41
41
 
42
42
  const llm = LLMService.fromConfig(aiConfig);
43
- const session = new SessionManager();
43
+ const session = new SessionManager(config.get().contextLength || 50);
44
44
  const tools = new ToolRegistry();
45
45
  const hooks = new HookManager();
46
46
  const prompts = new SystemPromptBuilder({
@@ -2,10 +2,9 @@
2
2
 
3
3
  /**
4
4
  * Thatgfsj Code - CLI Entry Point
5
- * Simplified from old src/index.ts (655 lines → ~60 lines)
6
5
  *
7
6
  * Two modes:
8
- * gfcode → Interactive REPL
7
+ * gfcode → Interactive TUI (Ink)
9
8
  * gfcode "prompt" → Single prompt mode
10
9
  * gfcode init → Setup wizard
11
10
  */
@@ -20,18 +19,18 @@ if (process.platform === 'win32') {
20
19
  import { program } from 'commander';
21
20
  import chalk from 'chalk';
22
21
  import { App } from '../app/index.js';
23
- import { REPL } from '../tui/repl.js';
24
22
  import { WelcomeScreen } from '../tui/welcome.js';
23
+ import { REPLOutput } from '../tui/output.js';
25
24
 
26
25
  // ==================== Error Handling ====================
27
26
 
28
27
  process.on('uncaughtException', (error) => {
29
- console.error(chalk.red('\nError:'), error.message);
28
+ console.error(chalk.red('\n Error:'), error.message);
30
29
  process.exit(1);
31
30
  });
32
31
 
33
32
  process.on('unhandledRejection', (reason) => {
34
- console.error(chalk.red('\nError:'), reason);
33
+ console.error(chalk.red('\n Error:'), reason);
35
34
  process.exit(1);
36
35
  });
37
36
 
@@ -39,15 +38,13 @@ process.on('unhandledRejection', (reason) => {
39
38
 
40
39
  program
41
40
  .name('gfcode')
42
- .description('🤖 Thatgfsj Code - AI Coding Assistant')
43
- .version('0.5.0')
41
+ .description('Thatgfsj Code - AI Coding Assistant')
42
+ .version('0.6.0')
44
43
  .argument('[prompt]', 'Task to execute (omit to start interactive mode)')
45
44
  .option('-m, --model <model>', 'Specify model')
46
45
  .option('-i, --interactive', 'Force interactive mode')
47
- .option('--hooks', 'Enable audit logging hooks')
48
- .action(async (prompt: string | undefined, options: { model?: string; interactive?: boolean; hooks?: boolean }) => {
46
+ .action(async (prompt: string | undefined, options: { model?: string; interactive?: boolean }) => {
49
47
  try {
50
- // Initialize app
51
48
  const app = await App.create();
52
49
 
53
50
  // Show welcome if no API key
@@ -58,21 +55,27 @@ program
58
55
  await app.config.save({ model: options.model });
59
56
  }
60
57
 
61
- // Enable hooks if requested
62
- if (options.hooks) {
63
- const { auditLogHook } = await import('../hooks/index.js');
64
- app.hooks.register('afterToolCall', auditLogHook);
65
- }
66
-
67
58
  if (!prompt || options.interactive) {
68
- // Interactive REPL mode
69
- const repl = new REPL(app);
70
- await repl.start();
59
+ // Interactive mode - use Ink TUI
60
+ const { render } = await import('ink');
61
+ const { TuiApp } = await import('../tui/app.js');
62
+
63
+ const { unmount } = render(<TuiApp app={app} />);
64
+
65
+ // Wait for the app to exit
66
+ await new Promise<void>((resolve) => {
67
+ const check = setInterval(() => {
68
+ // Ink will call process.exit or we detect it
69
+ }, 1000);
70
+ process.on('exit', () => {
71
+ clearInterval(check);
72
+ unmount();
73
+ resolve();
74
+ });
75
+ });
71
76
  } else {
72
- // Single prompt mode - use same UI as REPL
73
- const { REPLOutput } = await import('../tui/output.js');
77
+ // Single prompt mode - use simple output
74
78
  const out = new REPLOutput();
75
-
76
79
  out.printBanner();
77
80
  out.printUserInput(prompt);
78
81
  out.startThinking();
@@ -111,7 +114,6 @@ program
111
114
  if (!hasStarted) { out.beginAssistant(); hasStarted = true; }
112
115
  out.writeChunk(chunk);
113
116
  }
114
-
115
117
  fullResponse += chunk;
116
118
  }
117
119
  if (hasStarted) out.endAssistant();
@@ -124,18 +126,17 @@ program
124
126
  app.session.addMessage('assistant', fullResponse);
125
127
  }
126
128
  } catch (error: any) {
127
- console.error(chalk.red(`\n${error.message}`));
129
+ console.error(chalk.red(`\n ${error.message}`));
128
130
  process.exit(1);
129
131
  }
130
132
  });
131
133
 
132
- // Init command - setup wizard
134
+ // Init command
133
135
  program
134
136
  .command('init')
135
- .description('配置 API Key 和模型')
137
+ .description('Configure API key and model')
136
138
  .action(async () => {
137
139
  await WelcomeScreen.interactiveSetup();
138
140
  });
139
141
 
140
- // Parse and run
141
142
  program.parse(process.argv);
@@ -14,6 +14,7 @@ const DEFAULT_CONFIG: Config = {
14
14
  apiKey: '',
15
15
  temperature: 0.7,
16
16
  maxTokens: 4096,
17
+ contextLength: 50,
17
18
  provider: 'siliconflow',
18
19
  };
19
20
 
@@ -32,6 +32,7 @@ export interface Config {
32
32
  apiKey: string;
33
33
  temperature: number;
34
34
  maxTokens: number;
35
+ contextLength: number;
35
36
  provider: ProviderName;
36
37
  baseUrl?: string;
37
38
  }
@@ -0,0 +1,60 @@
1
+ /** @jsxImportSource react */
2
+ import React, { useState, useCallback, useEffect } from 'react';
3
+ import { Box, Text, useApp, useStdout } from 'ink';
4
+ import { Header } from './components/Header.js';
5
+ import { ChatList } from './components/ChatList.js';
6
+ import { Thinking } from './components/Thinking.js';
7
+ import { UserInput } from './components/UserInput.js';
8
+ import { useChat } from './hooks/useChat.js';
9
+ import { useCommands } from './hooks/useCommands.js';
10
+ import type { App } from '../app/index.js';
11
+ import type { MessageData } from './components/ChatMessage.js';
12
+
13
+ interface Props {
14
+ app: App;
15
+ }
16
+
17
+ export function TuiApp({ app }: Props) {
18
+ const config = app.config.get();
19
+ const { messages, isThinking, streaming, streamingToolCalls, sendMessage } = useChat(app);
20
+ const { handleCommand } = useCommands(app);
21
+ const [systemMessages, setSystemMessages] = useState<MessageData[]>([]);
22
+ const { exit } = useApp();
23
+
24
+ const onSubmit = useCallback(async (input: string) => {
25
+ // Check for built-in commands first
26
+ const result = handleCommand(input);
27
+ if (result.handled) {
28
+ if (result.output) {
29
+ setSystemMessages(prev => [
30
+ ...prev,
31
+ { role: 'user', content: input },
32
+ { role: 'assistant', content: result.output! },
33
+ ]);
34
+ }
35
+ // Handle clear
36
+ if (input.toLowerCase().trim() === 'clear') {
37
+ setSystemMessages([]);
38
+ }
39
+ return;
40
+ }
41
+
42
+ // Send to AI
43
+ await sendMessage(input);
44
+ }, [handleCommand, sendMessage]);
45
+
46
+ const allMessages = [...systemMessages, ...messages];
47
+
48
+ return (
49
+ <Box flexDirection="column" padding={1}>
50
+ <Header provider={config.provider} model={config.model} />
51
+ <ChatList
52
+ messages={allMessages}
53
+ streaming={streaming}
54
+ streamingToolCalls={streamingToolCalls}
55
+ />
56
+ {isThinking && <Thinking />}
57
+ <UserInput onSubmit={onSubmit} disabled={isThinking} />
58
+ </Box>
59
+ );
60
+ }