thatgfsj-code 0.8.1 → 0.8.4
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/ROADMAP.md +72 -42
- package/dist/cmd/index.js +1 -1
- package/dist/tui/app.d.ts.map +1 -1
- package/dist/tui/app.js +29 -9
- package/dist/tui/app.js.map +1 -1
- package/dist/tui/components/Header.js +1 -1
- package/dist/tui/components/ModelSelector.d.ts +10 -0
- package/dist/tui/components/ModelSelector.d.ts.map +1 -0
- package/dist/tui/components/ModelSelector.js +36 -0
- package/dist/tui/components/ModelSelector.js.map +1 -0
- package/dist/tui/hooks/useCommands.d.ts +0 -5
- package/dist/tui/hooks/useCommands.d.ts.map +1 -1
- package/dist/tui/hooks/useCommands.js +63 -103
- package/dist/tui/hooks/useCommands.js.map +1 -1
- package/package.json +2 -1
- package/src/cmd/index.tsx +1 -1
- package/src/tui/app.tsx +38 -14
- package/src/tui/components/Header.tsx +1 -1
- package/src/tui/components/ModelSelector.tsx +61 -0
- package/src/tui/hooks/useCommands.ts +64 -110
package/ROADMAP.md
CHANGED
|
@@ -2,60 +2,90 @@
|
|
|
2
2
|
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
## ✅ v0.
|
|
5
|
+
## ✅ v0.9.0 - 稳定版 (当前版本)
|
|
6
6
|
|
|
7
|
-
###
|
|
8
|
-
- [x]
|
|
7
|
+
### 核心架构
|
|
8
|
+
- [x] Ink (React) TUI 框架
|
|
9
9
|
- [x] LLM Provider 抽象层 (OpenAI/Anthropic/Gemini)
|
|
10
|
-
- [x]
|
|
11
|
-
- [x]
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
10
|
+
- [x] 15 个 Provider + 自定义中转站
|
|
11
|
+
- [x] 模块化目录结构 (cmd/app/config/llm/session/tools/tui/skills/hooks/prompts)
|
|
12
|
+
|
|
13
|
+
### 工具系统
|
|
14
|
+
- [x] file (读/写/列表/删除)
|
|
15
|
+
- [x] shell (执行命令)
|
|
16
|
+
- [x] git (操作)
|
|
17
|
+
- [x] search (搜索)
|
|
18
|
+
- [x] nwt (项目记忆,30天自动归档)
|
|
19
|
+
|
|
20
|
+
### Skills 系统
|
|
21
|
+
- [x] 16 个内置 Skills (规划/调试/TDD/架构/浏览器等)
|
|
22
|
+
- [x] /skills 命令管理
|
|
23
|
+
- [x] 自动激活
|
|
24
|
+
|
|
25
|
+
### UI/UX
|
|
26
|
+
- [x] Ink TUI 流式输出
|
|
27
|
+
- [x] Markdown 渲染 (marked-terminal)
|
|
28
|
+
- [x] 工具调用显示 (opencode 风格)
|
|
29
|
+
- [x] 状态栏
|
|
30
|
+
- [x] 命令系统 (/model, /new, /compact, /skills, /mcp, /help)
|
|
31
|
+
- [x] /model 交互式选择框
|
|
32
|
+
|
|
33
|
+
### 智能功能
|
|
34
|
+
- [x] 自动读取 CLAUDE.md/SKILLS.md (通用路径)
|
|
35
|
+
- [x] NWT 项目历史自动注入系统提示
|
|
36
|
+
- [x] 上下文自动压缩 (超限触发)
|
|
37
|
+
- [x] API 错误检测 (401/403/429)
|
|
38
|
+
- [x] 启动时检测 API Key
|
|
39
|
+
|
|
40
|
+
### 安装
|
|
41
|
+
- [x] npm install -g thatgfsj-code
|
|
42
|
+
- [x] Windows PowerShell 一键安装
|
|
43
|
+
- [x] Linux/macOS Bash 一键安装
|
|
44
|
+
- [x] 自动下载 Node.js (如果没有)
|
|
45
|
+
|
|
46
|
+
### 测试通过
|
|
47
|
+
- [x] 简单任务: 读文件 + 回答
|
|
48
|
+
- [x] 中等任务: 修改文件 + 验证
|
|
49
|
+
- [x] 困难任务: 多步骤 + NWT 记录
|
|
50
|
+
- [x] Git + Search + NWT 联合测试
|
|
51
|
+
- [x] 多文件创建和修改
|
|
30
52
|
|
|
31
53
|
---
|
|
32
54
|
|
|
33
55
|
## 📋 未来计划
|
|
34
56
|
|
|
35
|
-
###
|
|
57
|
+
### v1.0.0
|
|
36
58
|
- [ ] SQLite 会话持久化
|
|
37
|
-
- [ ]
|
|
38
|
-
- [ ]
|
|
39
|
-
- [ ] MCP 服务器配置文件支持
|
|
40
|
-
|
|
41
|
-
### v0.5.0 - TUI 升级
|
|
42
|
-
- [ ] Ink/React TUI 替换 readline
|
|
43
|
-
- [ ] 分屏显示 (代码 + 对话)
|
|
44
|
-
- [ ] Vim 模式编辑器
|
|
45
|
-
- [ ] 文件变更预览面板
|
|
46
|
-
|
|
47
|
-
### v1.0.0 - 稳定版
|
|
48
|
-
- [ ] LSP 集成
|
|
59
|
+
- [ ] Token 使用量显示
|
|
60
|
+
- [ ] 命令 Tab 补全
|
|
49
61
|
- [ ] 多会话管理
|
|
50
|
-
- [ ]
|
|
51
|
-
- [ ]
|
|
62
|
+
- [ ] LSP 集成
|
|
63
|
+
- [ ] 图片支持
|
|
52
64
|
|
|
53
65
|
---
|
|
54
66
|
|
|
55
|
-
##
|
|
67
|
+
## 历史版本
|
|
56
68
|
|
|
57
|
-
### ✅ v0.
|
|
58
|
-
-
|
|
69
|
+
### ✅ v0.8.0 - 命令系统
|
|
70
|
+
- /model, /new, /compact, /skills, /mcp 命令
|
|
71
|
+
|
|
72
|
+
### ✅ v0.7.0 - Skills + NWT
|
|
73
|
+
- 16 个内置 Skills, NWT 项目记忆
|
|
74
|
+
|
|
75
|
+
### ✅ v0.6.0 - Ink TUI
|
|
76
|
+
- React 组件化 UI, Markdown 渲染
|
|
77
|
+
|
|
78
|
+
### ✅ v0.5.0 - Provider 重写
|
|
79
|
+
- 15 个 Provider, 自定义中转站
|
|
80
|
+
|
|
81
|
+
### ✅ v0.4.0 - UI 优化
|
|
82
|
+
- 流式输出, 工具调用显示
|
|
83
|
+
|
|
84
|
+
### ✅ v0.3.0 - 架构重构
|
|
85
|
+
- 模块化目录, Provider 抽象
|
|
59
86
|
|
|
60
87
|
### ✅ v0.2.0 - 工具 + LLM
|
|
61
|
-
- Git
|
|
88
|
+
- Git/搜索工具, 多 Provider
|
|
89
|
+
|
|
90
|
+
### ✅ v0.1.0 - REPL 基础
|
|
91
|
+
- 彩色 UI, 命令提示符
|
package/dist/cmd/index.js
CHANGED
|
@@ -24,7 +24,7 @@ process.on('unhandledRejection', (reason) => {
|
|
|
24
24
|
program
|
|
25
25
|
.name('gfcode')
|
|
26
26
|
.description('Thatgfsj Code - AI Coding Assistant')
|
|
27
|
-
.version('0.8.
|
|
27
|
+
.version('0.8.4')
|
|
28
28
|
.argument('[prompt]', 'Task to execute (omit to start interactive mode)')
|
|
29
29
|
.option('-m, --model <model>', 'Specify model')
|
|
30
30
|
.option('-i, --interactive', 'Force interactive mode')
|
package/dist/tui/app.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/tui/app.tsx"],"names":[],"mappings":"AAAA,6BAA6B;AAC7B,OAAO,KAAgC,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/tui/app.tsx"],"names":[],"mappings":"AAAA,6BAA6B;AAC7B,OAAO,KAAgC,MAAM,OAAO,CAAC;AAUrD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAG3C,UAAU,KAAK;IACb,GAAG,EAAE,GAAG,CAAC;CACV;AAED,wBAAgB,MAAM,CAAC,EAAE,GAAG,EAAE,EAAE,KAAK,qBA2FpC"}
|
package/dist/tui/app.js
CHANGED
|
@@ -1,24 +1,40 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/** @jsxImportSource react */
|
|
3
3
|
import { useState, useCallback } from 'react';
|
|
4
|
-
import { Box,
|
|
4
|
+
import { Box, useStdout } from 'ink';
|
|
5
5
|
import { Header } from './components/Header.js';
|
|
6
6
|
import { ChatList } from './components/ChatList.js';
|
|
7
7
|
import { Thinking } from './components/Thinking.js';
|
|
8
8
|
import { UserInput } from './components/UserInput.js';
|
|
9
9
|
import { StatusBar } from './components/StatusBar.js';
|
|
10
|
+
import { ModelSelector } from './components/ModelSelector.js';
|
|
10
11
|
import { useChat } from './hooks/useChat.js';
|
|
11
12
|
import { useCommands } from './hooks/useCommands.js';
|
|
12
13
|
export function TuiApp({ app }) {
|
|
13
|
-
const config = app.config.get();
|
|
14
14
|
const { messages, isThinking, streaming, streamingToolCalls, sendMessage } = useChat(app);
|
|
15
|
-
const { handleCommand
|
|
15
|
+
const { handleCommand } = useCommands(app);
|
|
16
16
|
const [systemMessages, setSystemMessages] = useState([]);
|
|
17
|
+
const [showModelSelector, setShowModelSelector] = useState(false);
|
|
17
18
|
const { stdout } = useStdout();
|
|
18
19
|
const terminalWidth = stdout?.columns || 80;
|
|
19
20
|
const onSubmit = useCallback(async (input) => {
|
|
21
|
+
// If model selector is showing, treat input as custom model name
|
|
22
|
+
if (showModelSelector) {
|
|
23
|
+
app.config.save({ model: input.trim() });
|
|
24
|
+
setShowModelSelector(false);
|
|
25
|
+
setSystemMessages(prev => [
|
|
26
|
+
...prev,
|
|
27
|
+
{ role: 'assistant', content: `Model → ${input.trim()}` },
|
|
28
|
+
]);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
20
31
|
const result = handleCommand(input);
|
|
21
32
|
if (result.handled) {
|
|
33
|
+
// Special: /model shows selector
|
|
34
|
+
if (input.trim().toLowerCase() === '/model') {
|
|
35
|
+
setShowModelSelector(true);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
22
38
|
if (result.output) {
|
|
23
39
|
setSystemMessages(prev => [
|
|
24
40
|
...prev,
|
|
@@ -26,29 +42,33 @@ export function TuiApp({ app }) {
|
|
|
26
42
|
{ role: 'assistant', content: result.output },
|
|
27
43
|
]);
|
|
28
44
|
}
|
|
29
|
-
// Handle special actions
|
|
30
45
|
if (result.action === 'clear') {
|
|
31
46
|
setSystemMessages([]);
|
|
32
47
|
}
|
|
33
48
|
if (result.action === 'reinit') {
|
|
34
|
-
// Run init wizard then reload
|
|
35
49
|
const { WelcomeScreen } = await import('./welcome.js');
|
|
36
50
|
await WelcomeScreen.interactiveSetup();
|
|
37
|
-
// Reload app config
|
|
38
51
|
const { App: AppClass } = await import('../app/index.js');
|
|
39
52
|
const newApp = await AppClass.create();
|
|
40
53
|
Object.assign(app, newApp);
|
|
41
54
|
setSystemMessages(prev => [
|
|
42
55
|
...prev,
|
|
43
|
-
{ role: 'assistant', content: '
|
|
56
|
+
{ role: 'assistant', content: 'Config updated. Model: ' + app.config.get().model },
|
|
44
57
|
]);
|
|
45
58
|
}
|
|
46
59
|
return;
|
|
47
60
|
}
|
|
48
61
|
await sendMessage(input);
|
|
49
|
-
}, [handleCommand, sendMessage, app]);
|
|
62
|
+
}, [handleCommand, sendMessage, app, showModelSelector]);
|
|
50
63
|
const allMessages = [...systemMessages, ...messages];
|
|
51
64
|
const activeSkills = app.skills.listActive().map(s => s.id);
|
|
52
|
-
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Header, { provider: app.config.get().provider, model: app.config.get().model }), _jsx(ChatList, { messages: allMessages, streaming: streaming, streamingToolCalls: streamingToolCalls, width: terminalWidth - 4 }),
|
|
65
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Header, { provider: app.config.get().provider, model: app.config.get().model }), _jsx(ChatList, { messages: allMessages, streaming: streaming, streamingToolCalls: streamingToolCalls, width: terminalWidth - 4 }), _jsx(Thinking, { active: isThinking }), showModelSelector ? (_jsx(ModelSelector, { currentModel: app.config.get().model, onSelect: (model) => {
|
|
66
|
+
app.config.save({ model });
|
|
67
|
+
setShowModelSelector(false);
|
|
68
|
+
setSystemMessages(prev => [
|
|
69
|
+
...prev,
|
|
70
|
+
{ role: 'assistant', content: `Model → ${model}` },
|
|
71
|
+
]);
|
|
72
|
+
}, onCancel: () => setShowModelSelector(false) })) : (_jsx(UserInput, { onSubmit: onSubmit, disabled: isThinking })), _jsx(StatusBar, { messageCount: allMessages.length, skills: activeSkills })] }));
|
|
53
73
|
}
|
|
54
74
|
//# sourceMappingURL=app.js.map
|
package/dist/tui/app.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/tui/app.tsx"],"names":[],"mappings":";AAAA,6BAA6B;AAC7B,OAAc,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AACrD,OAAO,EAAE,GAAG,
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/tui/app.tsx"],"names":[],"mappings":"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"}
|
|
@@ -3,6 +3,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import React from 'react';
|
|
4
4
|
import { Box, Text } from 'ink';
|
|
5
5
|
export const Header = React.memo(function Header({ provider, model }) {
|
|
6
|
-
return (_jsxs(Box, { flexDirection: "column", marginBottom: 0, children: [_jsxs(Box, { justifyContent: "space-between", width: "100%", children: [_jsxs(Box, { children: [_jsx(Text, { color: "#06B6D4", bold: true, children: " \u26A1 " }), _jsx(Text, { color: "#22D3EE", bold: true, children: "THATGFSJ CODE" }), _jsx(Text, { dimColor: true, children: " v0.8.
|
|
6
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 0, children: [_jsxs(Box, { justifyContent: "space-between", width: "100%", children: [_jsxs(Box, { children: [_jsx(Text, { color: "#06B6D4", bold: true, children: " \u26A1 " }), _jsx(Text, { color: "#22D3EE", bold: true, children: "THATGFSJ CODE" }), _jsx(Text, { dimColor: true, children: " v0.8.4" })] }), _jsxs(Box, { children: [_jsxs(Text, { color: "#06B6D4", bold: true, children: [" ", provider, " "] }), _jsx(Text, { dimColor: true, children: "/" }), _jsxs(Text, { color: "#22D3EE", children: [" ", model, " "] })] })] }), _jsx(Text, { color: "#374151", children: '─'.repeat(80) })] }));
|
|
7
7
|
});
|
|
8
8
|
//# sourceMappingURL=Header.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** @jsxImportSource react */
|
|
2
|
+
import React from 'react';
|
|
3
|
+
interface Props {
|
|
4
|
+
currentModel: string;
|
|
5
|
+
onSelect: (model: string) => void;
|
|
6
|
+
onCancel: () => void;
|
|
7
|
+
}
|
|
8
|
+
export declare function ModelSelector({ currentModel, onSelect, onCancel }: Props): React.JSX.Element;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=ModelSelector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ModelSelector.d.ts","sourceRoot":"","sources":["../../../src/tui/components/ModelSelector.tsx"],"names":[],"mappings":"AAAA,6BAA6B;AAC7B,OAAO,KAAmB,MAAM,OAAO,CAAC;AAIxC,UAAU,KAAK;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAkBD,wBAAgB,aAAa,CAAC,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,KAAK,qBAiCxE"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/** @jsxImportSource react */
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { Box, Text } from 'ink';
|
|
5
|
+
import SelectInput from 'ink-select-input';
|
|
6
|
+
const COMMON_MODELS = [
|
|
7
|
+
{ label: 'deepseek-chat', value: 'deepseek-chat' },
|
|
8
|
+
{ label: 'deepseek-reasoner', value: 'deepseek-reasoner' },
|
|
9
|
+
{ label: 'gpt-4o', value: 'gpt-4o' },
|
|
10
|
+
{ label: 'gpt-4o-mini', value: 'gpt-4o-mini' },
|
|
11
|
+
{ label: 'claude-sonnet-4-20250514', value: 'claude-sonnet-4-20250514' },
|
|
12
|
+
{ label: 'claude-3-5-haiku-20241022', value: 'claude-3-5-haiku-20241022' },
|
|
13
|
+
{ label: 'gemini-2.0-flash', value: 'gemini-2.0-flash' },
|
|
14
|
+
{ label: 'gemini-2.5-pro', value: 'gemini-2.5-pro' },
|
|
15
|
+
{ label: 'qwen3-235b-a22b', value: 'qwen3-235b-a22b' },
|
|
16
|
+
{ label: 'glm-4-flash', value: 'glm-4-flash' },
|
|
17
|
+
{ label: 'kimi-k2.6', value: 'kimi-k2.6' },
|
|
18
|
+
{ label: 'mimo-v2.5-pro', value: 'mimo-v2.5-pro' },
|
|
19
|
+
{ label: '─ type custom name below ─', value: '__custom__' },
|
|
20
|
+
];
|
|
21
|
+
export function ModelSelector({ currentModel, onSelect, onCancel }) {
|
|
22
|
+
const [customMode, setCustomMode] = useState(false);
|
|
23
|
+
const [customValue, setCustomValue] = useState('');
|
|
24
|
+
if (customMode) {
|
|
25
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "#06B6D4", bold: true, children: "Enter model name:" }), _jsxs(Box, { children: [_jsx(Text, { color: "#06B6D4", children: "\u276F " }), _jsx(Text, { children: customValue }), _jsx(Text, { color: "#06B6D4", children: "\u2588" })] })] }));
|
|
26
|
+
}
|
|
27
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "#06B6D4", bold: true, children: ["Current: ", currentModel] }), _jsx(Text, { dimColor: true, children: "Select model (\u2191\u2193 Enter):" }), _jsx(SelectInput, { items: COMMON_MODELS, onSelect: (item) => {
|
|
28
|
+
if (item.value === '__custom__') {
|
|
29
|
+
setCustomMode(true);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
onSelect(item.value);
|
|
33
|
+
}
|
|
34
|
+
} })] }));
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=ModelSelector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ModelSelector.js","sourceRoot":"","sources":["../../../src/tui/components/ModelSelector.tsx"],"names":[],"mappings":";AAAA,6BAA6B;AAC7B,OAAc,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACxC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAChC,OAAO,WAAW,MAAM,kBAAkB,CAAC;AAQ3C,MAAM,aAAa,GAAG;IACpB,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,EAAE;IAClD,EAAE,KAAK,EAAE,mBAAmB,EAAE,KAAK,EAAE,mBAAmB,EAAE;IAC1D,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE;IACpC,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,aAAa,EAAE;IAC9C,EAAE,KAAK,EAAE,0BAA0B,EAAE,KAAK,EAAE,0BAA0B,EAAE;IACxE,EAAE,KAAK,EAAE,2BAA2B,EAAE,KAAK,EAAE,2BAA2B,EAAE;IAC1E,EAAE,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAE,kBAAkB,EAAE;IACxD,EAAE,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,gBAAgB,EAAE;IACpD,EAAE,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,iBAAiB,EAAE;IACtD,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,aAAa,EAAE;IAC9C,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE;IAC1C,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,EAAE;IAClD,EAAE,KAAK,EAAE,4BAA4B,EAAE,KAAK,EAAE,YAAY,EAAE;CAC7D,CAAC;AAEF,MAAM,UAAU,aAAa,CAAC,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAQ,EAAS;IACvE,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACpD,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IAEnD,IAAI,UAAU,EAAE,CAAC;QACf,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,aACzB,KAAC,IAAI,IAAC,KAAK,EAAC,SAAS,EAAC,IAAI,wCAAyB,EACnD,MAAC,GAAG,eACF,KAAC,IAAI,IAAC,KAAK,EAAC,SAAS,wBAAU,EAC/B,KAAC,IAAI,cAAE,WAAW,GAAQ,EAC1B,KAAC,IAAI,IAAC,KAAK,EAAC,SAAS,uBAAS,IAC1B,IACF,CACP,CAAC;IACJ,CAAC;IAED,OAAO,CACL,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,aACzB,MAAC,IAAI,IAAC,KAAK,EAAC,SAAS,EAAC,IAAI,gCAAW,YAAY,IAAQ,EACzD,KAAC,IAAI,IAAC,QAAQ,yDAAgC,EAC9C,KAAC,WAAW,IACV,KAAK,EAAE,aAAa,EACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE;oBACjB,IAAI,IAAI,CAAC,KAAK,KAAK,YAAY,EAAE,CAAC;wBAChC,aAAa,CAAC,IAAI,CAAC,CAAC;oBACtB,CAAC;yBAAM,CAAC;wBACN,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;oBACvB,CAAC;gBACH,CAAC,GACD,IACE,CACP,CAAC;AACJ,CAAC"}
|
|
@@ -6,11 +6,6 @@ interface CommandResult {
|
|
|
6
6
|
}
|
|
7
7
|
export declare function useCommands(app: App): {
|
|
8
8
|
handleCommand: (input: string) => CommandResult;
|
|
9
|
-
awaitingInput: {
|
|
10
|
-
type: string;
|
|
11
|
-
prompt: string;
|
|
12
|
-
handler: (input: string) => CommandResult;
|
|
13
|
-
} | null;
|
|
14
9
|
};
|
|
15
10
|
export {};
|
|
16
11
|
//# sourceMappingURL=useCommands.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCommands.d.ts","sourceRoot":"","sources":["../../../src/tui/hooks/useCommands.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AAE9C,UAAU,aAAa;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC7B;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,GAAG;
|
|
1
|
+
{"version":3,"file":"useCommands.d.ts","sourceRoot":"","sources":["../../../src/tui/hooks/useCommands.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,oBAAoB,CAAC;AAE9C,UAAU,aAAa;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC7B;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,GAAG;2BAEQ,MAAM,KAAG,aAAa;EA+GjE"}
|
|
@@ -1,146 +1,106 @@
|
|
|
1
|
-
import { useCallback
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
2
|
export function useCommands(app) {
|
|
3
|
-
const [awaitingInput, setAwaitingInput] = useState(null);
|
|
4
3
|
const handleCommand = useCallback((input) => {
|
|
5
|
-
// If we're waiting for input from a multi-step command
|
|
6
|
-
if (awaitingInput) {
|
|
7
|
-
const result = awaitingInput.handler(input);
|
|
8
|
-
setAwaitingInput(null);
|
|
9
|
-
return result;
|
|
10
|
-
}
|
|
11
4
|
const cmd = input.trim();
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
});
|
|
41
|
-
return { handled: true, output: lines.join('\n') };
|
|
5
|
+
const parts = cmd.split(/\s+/);
|
|
6
|
+
const name = parts[0].toLowerCase();
|
|
7
|
+
const arg = parts.slice(1).join(' ');
|
|
8
|
+
// ── /model [name] ───────────────────────────────────
|
|
9
|
+
if (name === '/model') {
|
|
10
|
+
if (!arg) {
|
|
11
|
+
const c = app.config.get();
|
|
12
|
+
return {
|
|
13
|
+
handled: true,
|
|
14
|
+
output: [
|
|
15
|
+
`Current: ${c.provider} / ${c.model}`,
|
|
16
|
+
'',
|
|
17
|
+
'Usage: /model <name>',
|
|
18
|
+
' /model deepseek-chat',
|
|
19
|
+
' /model gpt-4o',
|
|
20
|
+
' /model claude-sonnet-4-20250514',
|
|
21
|
+
' /model qwen3-235b-a22b',
|
|
22
|
+
'',
|
|
23
|
+
'Or: /provider to change provider',
|
|
24
|
+
].join('\n'),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
app.config.save({ model: arg });
|
|
28
|
+
return { handled: true, output: `Model → ${arg}` };
|
|
29
|
+
}
|
|
30
|
+
// ── /provider ───────────────────────────────────────
|
|
31
|
+
if (name === '/provider') {
|
|
32
|
+
return { handled: true, action: 'reinit' };
|
|
42
33
|
}
|
|
43
34
|
// ── /new, /clear ────────────────────────────────────
|
|
44
|
-
if (
|
|
35
|
+
if (name === '/new' || name === '/clear') {
|
|
45
36
|
app.session.clear();
|
|
46
|
-
return { handled: true, output: '
|
|
37
|
+
return { handled: true, output: 'New session.', action: 'clear' };
|
|
47
38
|
}
|
|
48
39
|
// ── /compact ────────────────────────────────────────
|
|
49
|
-
if (
|
|
40
|
+
if (name === '/compact') {
|
|
50
41
|
const before = app.session.getMessageCount();
|
|
51
42
|
app.session.truncate();
|
|
52
43
|
const after = app.session.getMessageCount();
|
|
53
44
|
return { handled: true, output: `Compacted: ${before} → ${after} messages` };
|
|
54
45
|
}
|
|
55
|
-
// ── /skills
|
|
56
|
-
if (
|
|
46
|
+
// ── /skills [id] ────────────────────────────────────
|
|
47
|
+
if (name === '/skills') {
|
|
48
|
+
if (arg) {
|
|
49
|
+
// Toggle specific skill
|
|
50
|
+
if (app.skills.isActive(arg)) {
|
|
51
|
+
app.skills.deactivate(arg);
|
|
52
|
+
return { handled: true, output: `✗ ${arg}` };
|
|
53
|
+
}
|
|
54
|
+
else if (app.skills.activate(arg)) {
|
|
55
|
+
return { handled: true, output: `✓ ${arg}` };
|
|
56
|
+
}
|
|
57
|
+
return { handled: true, output: `Unknown skill: ${arg}` };
|
|
58
|
+
}
|
|
57
59
|
const all = app.skills.list();
|
|
58
60
|
const lines = [
|
|
59
|
-
'Skills
|
|
61
|
+
'Skills:',
|
|
60
62
|
...all.map(s => {
|
|
61
|
-
const
|
|
62
|
-
return `
|
|
63
|
+
const mark = app.skills.isActive(s.id) ? '✓' : '·';
|
|
64
|
+
return ` ${mark} ${s.id}`;
|
|
63
65
|
}),
|
|
64
66
|
'',
|
|
65
|
-
'Usage: /
|
|
67
|
+
'Usage: /skills <id> to toggle',
|
|
66
68
|
];
|
|
67
|
-
setAwaitingInput({
|
|
68
|
-
type: 'skill',
|
|
69
|
-
prompt: lines.join('\n'),
|
|
70
|
-
handler: (input) => {
|
|
71
|
-
const id = input.trim();
|
|
72
|
-
if (!id)
|
|
73
|
-
return { handled: true, output: 'Cancelled.' };
|
|
74
|
-
if (app.skills.isActive(id)) {
|
|
75
|
-
app.skills.deactivate(id);
|
|
76
|
-
return { handled: true, output: `Deactivated: ${id}` };
|
|
77
|
-
}
|
|
78
|
-
else if (app.skills.activate(id)) {
|
|
79
|
-
return { handled: true, output: `Activated: ${id}` };
|
|
80
|
-
}
|
|
81
|
-
return { handled: true, output: `Unknown skill: ${id}` };
|
|
82
|
-
},
|
|
83
|
-
});
|
|
84
69
|
return { handled: true, output: lines.join('\n') };
|
|
85
70
|
}
|
|
86
|
-
// ── /skill <id> (direct toggle) ─────────────────────
|
|
87
|
-
if (lower.startsWith('/skill ')) {
|
|
88
|
-
const id = cmd.slice(7).trim();
|
|
89
|
-
if (app.skills.isActive(id)) {
|
|
90
|
-
app.skills.deactivate(id);
|
|
91
|
-
return { handled: true, output: `Deactivated: ${id}` };
|
|
92
|
-
}
|
|
93
|
-
else if (app.skills.activate(id)) {
|
|
94
|
-
return { handled: true, output: `Activated: ${id}` };
|
|
95
|
-
}
|
|
96
|
-
return { handled: true, output: `Unknown skill: ${id}` };
|
|
97
|
-
}
|
|
98
71
|
// ── /mcp ────────────────────────────────────────────
|
|
99
|
-
if (
|
|
72
|
+
if (name === '/mcp') {
|
|
100
73
|
return {
|
|
101
74
|
handled: true,
|
|
102
75
|
output: [
|
|
103
|
-
'MCP
|
|
104
|
-
'',
|
|
105
|
-
'MCP servers allow connecting external tool providers.',
|
|
106
|
-
'To configure, add MCP servers to ~/.thatgfsj/mcp.json:',
|
|
76
|
+
'MCP: ~/.thatgfsj/mcp.json',
|
|
107
77
|
'',
|
|
108
78
|
' {',
|
|
109
79
|
' "servers": {',
|
|
110
|
-
' "
|
|
111
|
-
' "command": "npx",',
|
|
112
|
-
' "args": ["-y", "my-mcp-server"]',
|
|
113
|
-
' }',
|
|
80
|
+
' "name": { "command": "npx", "args": ["-y", "server"] }',
|
|
114
81
|
' }',
|
|
115
82
|
' }',
|
|
116
|
-
'',
|
|
117
|
-
'Then restart gfcode to load MCP tools.',
|
|
118
83
|
].join('\n'),
|
|
119
84
|
};
|
|
120
85
|
}
|
|
121
86
|
// ── /help ───────────────────────────────────────────
|
|
122
|
-
if (
|
|
87
|
+
if (name === '/help' || name === 'help') {
|
|
123
88
|
return {
|
|
124
89
|
handled: true,
|
|
125
90
|
output: [
|
|
126
|
-
'
|
|
127
|
-
'
|
|
128
|
-
'
|
|
129
|
-
'
|
|
130
|
-
'
|
|
131
|
-
'
|
|
132
|
-
'
|
|
133
|
-
'
|
|
134
|
-
' exit Quit',
|
|
135
|
-
'',
|
|
136
|
-
'Keyboard:',
|
|
137
|
-
' ↑/↓ History',
|
|
138
|
-
' Ctrl+C Exit',
|
|
91
|
+
'/model <name> Switch model',
|
|
92
|
+
'/provider Change provider',
|
|
93
|
+
'/new New session',
|
|
94
|
+
'/compact Compress context',
|
|
95
|
+
'/skills [id] List/toggle skills',
|
|
96
|
+
'/mcp MCP settings',
|
|
97
|
+
'/help This help',
|
|
98
|
+
'exit Quit',
|
|
139
99
|
].join('\n'),
|
|
140
100
|
};
|
|
141
101
|
}
|
|
142
102
|
return { handled: false };
|
|
143
|
-
}, [app
|
|
144
|
-
return { handleCommand
|
|
103
|
+
}, [app]);
|
|
104
|
+
return { handleCommand };
|
|
145
105
|
}
|
|
146
106
|
//# sourceMappingURL=useCommands.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCommands.js","sourceRoot":"","sources":["../../../src/tui/hooks/useCommands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"useCommands.js","sourceRoot":"","sources":["../../../src/tui/hooks/useCommands.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thatgfsj-code",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4",
|
|
4
4
|
"description": "Thatgfsj Code - AI Coding Assistant",
|
|
5
5
|
"main": "dist/cmd/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"chalk": "^5.3.0",
|
|
27
27
|
"commander": "^11.1.0",
|
|
28
28
|
"ink": "^7.1.0",
|
|
29
|
+
"ink-select-input": "^6.2.0",
|
|
29
30
|
"ink-spinner": "^5.0.0",
|
|
30
31
|
"ink-text-input": "^6.0.0",
|
|
31
32
|
"inquirer": "^13.3.2",
|
package/src/cmd/index.tsx
CHANGED
|
@@ -28,7 +28,7 @@ process.on('unhandledRejection', (reason) => {
|
|
|
28
28
|
program
|
|
29
29
|
.name('gfcode')
|
|
30
30
|
.description('Thatgfsj Code - AI Coding Assistant')
|
|
31
|
-
.version('0.8.
|
|
31
|
+
.version('0.8.4')
|
|
32
32
|
.argument('[prompt]', 'Task to execute (omit to start interactive mode)')
|
|
33
33
|
.option('-m, --model <model>', 'Specify model')
|
|
34
34
|
.option('-i, --interactive', 'Force interactive mode')
|
package/src/tui/app.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import { ChatList } from './components/ChatList.js';
|
|
|
6
6
|
import { Thinking } from './components/Thinking.js';
|
|
7
7
|
import { UserInput } from './components/UserInput.js';
|
|
8
8
|
import { StatusBar } from './components/StatusBar.js';
|
|
9
|
+
import { ModelSelector } from './components/ModelSelector.js';
|
|
9
10
|
import { useChat } from './hooks/useChat.js';
|
|
10
11
|
import { useCommands } from './hooks/useCommands.js';
|
|
11
12
|
import type { App } from '../app/index.js';
|
|
@@ -16,17 +17,34 @@ interface Props {
|
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
export function TuiApp({ app }: Props) {
|
|
19
|
-
const config = app.config.get();
|
|
20
20
|
const { messages, isThinking, streaming, streamingToolCalls, sendMessage } = useChat(app);
|
|
21
|
-
const { handleCommand
|
|
21
|
+
const { handleCommand } = useCommands(app);
|
|
22
22
|
const [systemMessages, setSystemMessages] = useState<MessageData[]>([]);
|
|
23
|
+
const [showModelSelector, setShowModelSelector] = useState(false);
|
|
23
24
|
const { stdout } = useStdout();
|
|
24
25
|
const terminalWidth = stdout?.columns || 80;
|
|
25
26
|
|
|
26
27
|
const onSubmit = useCallback(async (input: string) => {
|
|
28
|
+
// If model selector is showing, treat input as custom model name
|
|
29
|
+
if (showModelSelector) {
|
|
30
|
+
app.config.save({ model: input.trim() });
|
|
31
|
+
setShowModelSelector(false);
|
|
32
|
+
setSystemMessages(prev => [
|
|
33
|
+
...prev,
|
|
34
|
+
{ role: 'assistant', content: `Model → ${input.trim()}` },
|
|
35
|
+
]);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
27
39
|
const result = handleCommand(input);
|
|
28
40
|
|
|
29
41
|
if (result.handled) {
|
|
42
|
+
// Special: /model shows selector
|
|
43
|
+
if (input.trim().toLowerCase() === '/model') {
|
|
44
|
+
setShowModelSelector(true);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
30
48
|
if (result.output) {
|
|
31
49
|
setSystemMessages(prev => [
|
|
32
50
|
...prev,
|
|
@@ -35,22 +53,19 @@ export function TuiApp({ app }: Props) {
|
|
|
35
53
|
]);
|
|
36
54
|
}
|
|
37
55
|
|
|
38
|
-
// Handle special actions
|
|
39
56
|
if (result.action === 'clear') {
|
|
40
57
|
setSystemMessages([]);
|
|
41
58
|
}
|
|
42
59
|
|
|
43
60
|
if (result.action === 'reinit') {
|
|
44
|
-
// Run init wizard then reload
|
|
45
61
|
const { WelcomeScreen } = await import('./welcome.js');
|
|
46
62
|
await WelcomeScreen.interactiveSetup();
|
|
47
|
-
// Reload app config
|
|
48
63
|
const { App: AppClass } = await import('../app/index.js');
|
|
49
64
|
const newApp = await AppClass.create();
|
|
50
65
|
Object.assign(app, newApp);
|
|
51
66
|
setSystemMessages(prev => [
|
|
52
67
|
...prev,
|
|
53
|
-
{ role: 'assistant', content: '
|
|
68
|
+
{ role: 'assistant', content: 'Config updated. Model: ' + app.config.get().model },
|
|
54
69
|
]);
|
|
55
70
|
}
|
|
56
71
|
|
|
@@ -58,7 +73,7 @@ export function TuiApp({ app }: Props) {
|
|
|
58
73
|
}
|
|
59
74
|
|
|
60
75
|
await sendMessage(input);
|
|
61
|
-
}, [handleCommand, sendMessage, app]);
|
|
76
|
+
}, [handleCommand, sendMessage, app, showModelSelector]);
|
|
62
77
|
|
|
63
78
|
const allMessages = [...systemMessages, ...messages];
|
|
64
79
|
const activeSkills = app.skills.listActive().map(s => s.id);
|
|
@@ -72,14 +87,23 @@ export function TuiApp({ app }: Props) {
|
|
|
72
87
|
streamingToolCalls={streamingToolCalls}
|
|
73
88
|
width={terminalWidth - 4}
|
|
74
89
|
/>
|
|
75
|
-
{isThinking
|
|
76
|
-
{
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
90
|
+
<Thinking active={isThinking} />
|
|
91
|
+
{showModelSelector ? (
|
|
92
|
+
<ModelSelector
|
|
93
|
+
currentModel={app.config.get().model}
|
|
94
|
+
onSelect={(model) => {
|
|
95
|
+
app.config.save({ model });
|
|
96
|
+
setShowModelSelector(false);
|
|
97
|
+
setSystemMessages(prev => [
|
|
98
|
+
...prev,
|
|
99
|
+
{ role: 'assistant', content: `Model → ${model}` },
|
|
100
|
+
]);
|
|
101
|
+
}}
|
|
102
|
+
onCancel={() => setShowModelSelector(false)}
|
|
103
|
+
/>
|
|
104
|
+
) : (
|
|
105
|
+
<UserInput onSubmit={onSubmit} disabled={isThinking} />
|
|
81
106
|
)}
|
|
82
|
-
<UserInput onSubmit={onSubmit} disabled={isThinking} />
|
|
83
107
|
<StatusBar messageCount={allMessages.length} skills={activeSkills} />
|
|
84
108
|
</Box>
|
|
85
109
|
);
|
|
@@ -14,7 +14,7 @@ export const Header = React.memo(function Header({ provider, model }: Props) {
|
|
|
14
14
|
<Box>
|
|
15
15
|
<Text color="#06B6D4" bold> ⚡ </Text>
|
|
16
16
|
<Text color="#22D3EE" bold>THATGFSJ CODE</Text>
|
|
17
|
-
<Text dimColor> v0.8.
|
|
17
|
+
<Text dimColor> v0.8.4</Text>
|
|
18
18
|
</Box>
|
|
19
19
|
<Box>
|
|
20
20
|
<Text color="#06B6D4" bold> {provider} </Text>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** @jsxImportSource react */
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import SelectInput from 'ink-select-input';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
currentModel: string;
|
|
8
|
+
onSelect: (model: string) => void;
|
|
9
|
+
onCancel: () => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const COMMON_MODELS = [
|
|
13
|
+
{ label: 'deepseek-chat', value: 'deepseek-chat' },
|
|
14
|
+
{ label: 'deepseek-reasoner', value: 'deepseek-reasoner' },
|
|
15
|
+
{ label: 'gpt-4o', value: 'gpt-4o' },
|
|
16
|
+
{ label: 'gpt-4o-mini', value: 'gpt-4o-mini' },
|
|
17
|
+
{ label: 'claude-sonnet-4-20250514', value: 'claude-sonnet-4-20250514' },
|
|
18
|
+
{ label: 'claude-3-5-haiku-20241022', value: 'claude-3-5-haiku-20241022' },
|
|
19
|
+
{ label: 'gemini-2.0-flash', value: 'gemini-2.0-flash' },
|
|
20
|
+
{ label: 'gemini-2.5-pro', value: 'gemini-2.5-pro' },
|
|
21
|
+
{ label: 'qwen3-235b-a22b', value: 'qwen3-235b-a22b' },
|
|
22
|
+
{ label: 'glm-4-flash', value: 'glm-4-flash' },
|
|
23
|
+
{ label: 'kimi-k2.6', value: 'kimi-k2.6' },
|
|
24
|
+
{ label: 'mimo-v2.5-pro', value: 'mimo-v2.5-pro' },
|
|
25
|
+
{ label: '─ type custom name below ─', value: '__custom__' },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
export function ModelSelector({ currentModel, onSelect, onCancel }: Props) {
|
|
29
|
+
const [customMode, setCustomMode] = useState(false);
|
|
30
|
+
const [customValue, setCustomValue] = useState('');
|
|
31
|
+
|
|
32
|
+
if (customMode) {
|
|
33
|
+
return (
|
|
34
|
+
<Box flexDirection="column">
|
|
35
|
+
<Text color="#06B6D4" bold>Enter model name:</Text>
|
|
36
|
+
<Box>
|
|
37
|
+
<Text color="#06B6D4">❯ </Text>
|
|
38
|
+
<Text>{customValue}</Text>
|
|
39
|
+
<Text color="#06B6D4">█</Text>
|
|
40
|
+
</Box>
|
|
41
|
+
</Box>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<Box flexDirection="column">
|
|
47
|
+
<Text color="#06B6D4" bold>Current: {currentModel}</Text>
|
|
48
|
+
<Text dimColor>Select model (↑↓ Enter):</Text>
|
|
49
|
+
<SelectInput
|
|
50
|
+
items={COMMON_MODELS}
|
|
51
|
+
onSelect={(item) => {
|
|
52
|
+
if (item.value === '__custom__') {
|
|
53
|
+
setCustomMode(true);
|
|
54
|
+
} else {
|
|
55
|
+
onSelect(item.value);
|
|
56
|
+
}
|
|
57
|
+
}}
|
|
58
|
+
/>
|
|
59
|
+
</Box>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
2
|
import type { App } from '../../app/index.js';
|
|
3
3
|
|
|
4
4
|
interface CommandResult {
|
|
@@ -8,162 +8,116 @@ interface CommandResult {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export function useCommands(app: App) {
|
|
11
|
-
const [awaitingInput, setAwaitingInput] = useState<{
|
|
12
|
-
type: string;
|
|
13
|
-
prompt: string;
|
|
14
|
-
handler: (input: string) => CommandResult;
|
|
15
|
-
} | null>(null);
|
|
16
11
|
|
|
17
12
|
const handleCommand = useCallback((input: string): CommandResult => {
|
|
18
|
-
// If we're waiting for input from a multi-step command
|
|
19
|
-
if (awaitingInput) {
|
|
20
|
-
const result = awaitingInput.handler(input);
|
|
21
|
-
setAwaitingInput(null);
|
|
22
|
-
return result;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
13
|
const cmd = input.trim();
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
app.config.save({ model: val });
|
|
54
|
-
return { handled: true, output: `Switched to: ${val}` };
|
|
55
|
-
},
|
|
56
|
-
});
|
|
14
|
+
const parts = cmd.split(/\s+/);
|
|
15
|
+
const name = parts[0].toLowerCase();
|
|
16
|
+
const arg = parts.slice(1).join(' ');
|
|
17
|
+
|
|
18
|
+
// ── /model [name] ───────────────────────────────────
|
|
19
|
+
if (name === '/model') {
|
|
20
|
+
if (!arg) {
|
|
21
|
+
const c = app.config.get();
|
|
22
|
+
return {
|
|
23
|
+
handled: true,
|
|
24
|
+
output: [
|
|
25
|
+
`Current: ${c.provider} / ${c.model}`,
|
|
26
|
+
'',
|
|
27
|
+
'Usage: /model <name>',
|
|
28
|
+
' /model deepseek-chat',
|
|
29
|
+
' /model gpt-4o',
|
|
30
|
+
' /model claude-sonnet-4-20250514',
|
|
31
|
+
' /model qwen3-235b-a22b',
|
|
32
|
+
'',
|
|
33
|
+
'Or: /provider to change provider',
|
|
34
|
+
].join('\n'),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
app.config.save({ model: arg });
|
|
38
|
+
return { handled: true, output: `Model → ${arg}` };
|
|
39
|
+
}
|
|
57
40
|
|
|
58
|
-
|
|
41
|
+
// ── /provider ───────────────────────────────────────
|
|
42
|
+
if (name === '/provider') {
|
|
43
|
+
return { handled: true, action: 'reinit' };
|
|
59
44
|
}
|
|
60
45
|
|
|
61
46
|
// ── /new, /clear ────────────────────────────────────
|
|
62
|
-
if (
|
|
47
|
+
if (name === '/new' || name === '/clear') {
|
|
63
48
|
app.session.clear();
|
|
64
|
-
return { handled: true, output: '
|
|
49
|
+
return { handled: true, output: 'New session.', action: 'clear' };
|
|
65
50
|
}
|
|
66
51
|
|
|
67
52
|
// ── /compact ────────────────────────────────────────
|
|
68
|
-
if (
|
|
53
|
+
if (name === '/compact') {
|
|
69
54
|
const before = app.session.getMessageCount();
|
|
70
55
|
app.session.truncate();
|
|
71
56
|
const after = app.session.getMessageCount();
|
|
72
57
|
return { handled: true, output: `Compacted: ${before} → ${after} messages` };
|
|
73
58
|
}
|
|
74
59
|
|
|
75
|
-
// ── /skills
|
|
76
|
-
if (
|
|
60
|
+
// ── /skills [id] ────────────────────────────────────
|
|
61
|
+
if (name === '/skills') {
|
|
62
|
+
if (arg) {
|
|
63
|
+
// Toggle specific skill
|
|
64
|
+
if (app.skills.isActive(arg)) {
|
|
65
|
+
app.skills.deactivate(arg);
|
|
66
|
+
return { handled: true, output: `✗ ${arg}` };
|
|
67
|
+
} else if (app.skills.activate(arg)) {
|
|
68
|
+
return { handled: true, output: `✓ ${arg}` };
|
|
69
|
+
}
|
|
70
|
+
return { handled: true, output: `Unknown skill: ${arg}` };
|
|
71
|
+
}
|
|
72
|
+
|
|
77
73
|
const all = app.skills.list();
|
|
78
74
|
const lines = [
|
|
79
|
-
'Skills
|
|
75
|
+
'Skills:',
|
|
80
76
|
...all.map(s => {
|
|
81
|
-
const
|
|
82
|
-
return `
|
|
77
|
+
const mark = app.skills.isActive(s.id) ? '✓' : '·';
|
|
78
|
+
return ` ${mark} ${s.id}`;
|
|
83
79
|
}),
|
|
84
80
|
'',
|
|
85
|
-
'Usage: /
|
|
81
|
+
'Usage: /skills <id> to toggle',
|
|
86
82
|
];
|
|
87
|
-
|
|
88
|
-
setAwaitingInput({
|
|
89
|
-
type: 'skill',
|
|
90
|
-
prompt: lines.join('\n'),
|
|
91
|
-
handler: (input: string) => {
|
|
92
|
-
const id = input.trim();
|
|
93
|
-
if (!id) return { handled: true, output: 'Cancelled.' };
|
|
94
|
-
if (app.skills.isActive(id)) {
|
|
95
|
-
app.skills.deactivate(id);
|
|
96
|
-
return { handled: true, output: `Deactivated: ${id}` };
|
|
97
|
-
} else if (app.skills.activate(id)) {
|
|
98
|
-
return { handled: true, output: `Activated: ${id}` };
|
|
99
|
-
}
|
|
100
|
-
return { handled: true, output: `Unknown skill: ${id}` };
|
|
101
|
-
},
|
|
102
|
-
});
|
|
103
|
-
|
|
104
83
|
return { handled: true, output: lines.join('\n') };
|
|
105
84
|
}
|
|
106
85
|
|
|
107
|
-
// ── /skill <id> (direct toggle) ─────────────────────
|
|
108
|
-
if (lower.startsWith('/skill ')) {
|
|
109
|
-
const id = cmd.slice(7).trim();
|
|
110
|
-
if (app.skills.isActive(id)) {
|
|
111
|
-
app.skills.deactivate(id);
|
|
112
|
-
return { handled: true, output: `Deactivated: ${id}` };
|
|
113
|
-
} else if (app.skills.activate(id)) {
|
|
114
|
-
return { handled: true, output: `Activated: ${id}` };
|
|
115
|
-
}
|
|
116
|
-
return { handled: true, output: `Unknown skill: ${id}` };
|
|
117
|
-
}
|
|
118
|
-
|
|
119
86
|
// ── /mcp ────────────────────────────────────────────
|
|
120
|
-
if (
|
|
87
|
+
if (name === '/mcp') {
|
|
121
88
|
return {
|
|
122
89
|
handled: true,
|
|
123
90
|
output: [
|
|
124
|
-
'MCP
|
|
125
|
-
'',
|
|
126
|
-
'MCP servers allow connecting external tool providers.',
|
|
127
|
-
'To configure, add MCP servers to ~/.thatgfsj/mcp.json:',
|
|
91
|
+
'MCP: ~/.thatgfsj/mcp.json',
|
|
128
92
|
'',
|
|
129
93
|
' {',
|
|
130
94
|
' "servers": {',
|
|
131
|
-
' "
|
|
132
|
-
' "command": "npx",',
|
|
133
|
-
' "args": ["-y", "my-mcp-server"]',
|
|
134
|
-
' }',
|
|
95
|
+
' "name": { "command": "npx", "args": ["-y", "server"] }',
|
|
135
96
|
' }',
|
|
136
97
|
' }',
|
|
137
|
-
'',
|
|
138
|
-
'Then restart gfcode to load MCP tools.',
|
|
139
98
|
].join('\n'),
|
|
140
99
|
};
|
|
141
100
|
}
|
|
142
101
|
|
|
143
102
|
// ── /help ───────────────────────────────────────────
|
|
144
|
-
if (
|
|
103
|
+
if (name === '/help' || name === 'help') {
|
|
145
104
|
return {
|
|
146
105
|
handled: true,
|
|
147
106
|
output: [
|
|
148
|
-
'
|
|
149
|
-
'
|
|
150
|
-
'
|
|
151
|
-
'
|
|
152
|
-
'
|
|
153
|
-
'
|
|
154
|
-
'
|
|
155
|
-
'
|
|
156
|
-
' exit Quit',
|
|
157
|
-
'',
|
|
158
|
-
'Keyboard:',
|
|
159
|
-
' ↑/↓ History',
|
|
160
|
-
' Ctrl+C Exit',
|
|
107
|
+
'/model <name> Switch model',
|
|
108
|
+
'/provider Change provider',
|
|
109
|
+
'/new New session',
|
|
110
|
+
'/compact Compress context',
|
|
111
|
+
'/skills [id] List/toggle skills',
|
|
112
|
+
'/mcp MCP settings',
|
|
113
|
+
'/help This help',
|
|
114
|
+
'exit Quit',
|
|
161
115
|
].join('\n'),
|
|
162
116
|
};
|
|
163
117
|
}
|
|
164
118
|
|
|
165
119
|
return { handled: false };
|
|
166
|
-
}, [app
|
|
120
|
+
}, [app]);
|
|
167
121
|
|
|
168
|
-
return { handleCommand
|
|
122
|
+
return { handleCommand };
|
|
169
123
|
}
|