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.
- package/README.md +7 -12
- package/dist/app/index.js +1 -1
- package/dist/app/index.js.map +1 -1
- package/dist/cmd/index.d.ts +1 -2
- package/dist/cmd/index.d.ts.map +1 -1
- package/dist/cmd/index.js +26 -23
- package/dist/cmd/index.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +1 -0
- package/dist/config/index.js.map +1 -1
- package/dist/config/types.d.ts +1 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/tui/app.d.ts +9 -0
- package/dist/tui/app.d.ts.map +1 -0
- package/dist/tui/app.js +40 -0
- package/dist/tui/app.js.map +1 -0
- package/dist/tui/components/ChatList.d.ts +14 -0
- package/dist/tui/components/ChatList.d.ts.map +1 -0
- package/dist/tui/components/ChatList.js +11 -0
- package/dist/tui/components/ChatList.js.map +1 -0
- package/dist/tui/components/ChatMessage.d.ts +14 -0
- package/dist/tui/components/ChatMessage.d.ts.map +1 -0
- package/dist/tui/components/ChatMessage.js +17 -0
- package/dist/tui/components/ChatMessage.js.map +1 -0
- package/dist/tui/components/Header.d.ts +9 -0
- package/dist/tui/components/Header.d.ts.map +1 -0
- package/dist/tui/components/Header.js +6 -0
- package/dist/tui/components/Header.js.map +1 -0
- package/dist/tui/components/Markdown.d.ts +8 -0
- package/dist/tui/components/Markdown.d.ts.map +1 -0
- package/dist/tui/components/Markdown.js +31 -0
- package/dist/tui/components/Markdown.js.map +1 -0
- package/dist/tui/components/Thinking.d.ts +8 -0
- package/dist/tui/components/Thinking.d.ts.map +1 -0
- package/dist/tui/components/Thinking.js +7 -0
- package/dist/tui/components/Thinking.js.map +1 -0
- package/dist/tui/components/ToolCall.d.ts +14 -0
- package/dist/tui/components/ToolCall.d.ts.map +1 -0
- package/dist/tui/components/ToolCall.js +37 -0
- package/dist/tui/components/ToolCall.js.map +1 -0
- package/dist/tui/components/UserInput.d.ts +9 -0
- package/dist/tui/components/UserInput.d.ts.map +1 -0
- package/dist/tui/components/UserInput.js +82 -0
- package/dist/tui/components/UserInput.js.map +1 -0
- package/dist/tui/hooks/useChat.d.ts +11 -0
- package/dist/tui/hooks/useChat.d.ts.map +1 -0
- package/dist/tui/hooks/useChat.js +138 -0
- package/dist/tui/hooks/useChat.js.map +1 -0
- package/dist/tui/hooks/useCommands.d.ts +10 -0
- package/dist/tui/hooks/useCommands.d.ts.map +1 -0
- package/dist/tui/hooks/useCommands.js +51 -0
- package/dist/tui/hooks/useCommands.js.map +1 -0
- package/dist/tui/index.d.ts +2 -4
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +2 -4
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/welcome.d.ts.map +1 -1
- package/dist/tui/welcome.js +22 -4
- package/dist/tui/welcome.js.map +1 -1
- package/install.ps1 +171 -192
- package/package.json +9 -1
- package/src/app/index.ts +1 -1
- package/src/cmd/{index.ts → index.tsx} +28 -27
- package/src/config/index.ts +1 -0
- package/src/config/types.ts +1 -0
- package/src/tui/app.tsx +60 -0
- package/src/tui/components/ChatList.tsx +29 -0
- package/src/tui/components/ChatMessage.tsx +51 -0
- package/src/tui/components/Header.tsx +22 -0
- package/src/tui/components/Markdown.tsx +35 -0
- package/src/tui/components/Thinking.tsx +19 -0
- package/src/tui/components/ToolCall.tsx +72 -0
- package/src/tui/components/UserInput.tsx +101 -0
- package/src/tui/hooks/useChat.ts +154 -0
- package/src/tui/hooks/useCommands.ts +63 -0
- package/src/tui/index.ts +2 -4
- package/src/tui/welcome.ts +24 -5
- package/tsconfig.json +5 -3
package/install.ps1
CHANGED
|
@@ -1,238 +1,217 @@
|
|
|
1
1
|
# Thatgfsj Code Installer for Windows
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
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
|
-
|
|
14
|
-
function
|
|
15
|
-
function
|
|
16
|
-
function
|
|
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
|
|
21
|
-
Write-Host "
|
|
15
|
+
Write-Host " Thatgfsj Code Installer" -ForegroundColor Cyan
|
|
16
|
+
Write-Host " ────────────────────────" -ForegroundColor DarkGray
|
|
22
17
|
Write-Host ""
|
|
23
18
|
|
|
24
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
#
|
|
40
|
-
Write-Step "检查 Node.js..."
|
|
27
|
+
# ── Helper: Download file with progress ───────────────────
|
|
41
28
|
|
|
42
|
-
function
|
|
29
|
+
function Download-File {
|
|
30
|
+
param($url, $dest)
|
|
31
|
+
Log "Downloading $url ..."
|
|
43
32
|
try {
|
|
44
|
-
$
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
#
|
|
97
|
-
|
|
94
|
+
# ── Step 1: Check / Install Node.js ───────────────────────
|
|
95
|
+
|
|
96
|
+
Step "Checking Node.js..."
|
|
98
97
|
|
|
99
|
-
$
|
|
98
|
+
$nodeOk = $false
|
|
100
99
|
|
|
101
|
-
if
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if ($
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
#
|
|
127
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
#
|
|
147
|
-
|
|
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
|
-
|
|
150
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
178
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
#
|
|
206
|
+
# ── Done ──────────────────────────────────────────────────
|
|
207
|
+
|
|
225
208
|
Write-Host ""
|
|
226
|
-
Write-Host "
|
|
227
|
-
Write-
|
|
228
|
-
Write-Host " ======================================" -ForegroundColor Cyan
|
|
209
|
+
Write-Host " ────────────────────────────────" -ForegroundColor DarkGray
|
|
210
|
+
Write-Host " Done!" -ForegroundColor Green
|
|
229
211
|
Write-Host ""
|
|
230
|
-
Write-Host "
|
|
231
|
-
Write-Host " gfcode init
|
|
232
|
-
Write-Host " gfcode
|
|
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 "
|
|
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.
|
|
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
|
|
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('\n
|
|
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('\n
|
|
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('
|
|
43
|
-
.version('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
|
-
.
|
|
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
|
|
69
|
-
const
|
|
70
|
-
await
|
|
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
|
|
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
|
|
129
|
+
console.error(chalk.red(`\n ${error.message}`));
|
|
128
130
|
process.exit(1);
|
|
129
131
|
}
|
|
130
132
|
});
|
|
131
133
|
|
|
132
|
-
// Init command
|
|
134
|
+
// Init command
|
|
133
135
|
program
|
|
134
136
|
.command('init')
|
|
135
|
-
.description('
|
|
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);
|
package/src/config/index.ts
CHANGED
package/src/config/types.ts
CHANGED
package/src/tui/app.tsx
ADDED
|
@@ -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
|
+
}
|