thatgfsj-code 0.7.9 → 0.8.3
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/dist/cmd/index.js +1 -1
- package/dist/tui/app.d.ts.map +1 -1
- package/dist/tui/app.js +38 -4
- 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 +1 -0
- package/dist/tui/hooks/useCommands.d.ts.map +1 -1
- package/dist/tui/hooks/useCommands.js +90 -62
- 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 +54 -5
- package/src/tui/components/Header.tsx +1 -1
- package/src/tui/components/ModelSelector.tsx +61 -0
- package/src/tui/hooks/useCommands.ts +94 -62
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.
|
|
27
|
+
.version('0.8.3')
|
|
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
|
@@ -7,18 +7,34 @@ 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
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,15 +42,33 @@ export function TuiApp({ app }) {
|
|
|
26
42
|
{ role: 'assistant', content: result.output },
|
|
27
43
|
]);
|
|
28
44
|
}
|
|
29
|
-
if (
|
|
45
|
+
if (result.action === 'clear') {
|
|
30
46
|
setSystemMessages([]);
|
|
31
47
|
}
|
|
48
|
+
if (result.action === 'reinit') {
|
|
49
|
+
const { WelcomeScreen } = await import('./welcome.js');
|
|
50
|
+
await WelcomeScreen.interactiveSetup();
|
|
51
|
+
const { App: AppClass } = await import('../app/index.js');
|
|
52
|
+
const newApp = await AppClass.create();
|
|
53
|
+
Object.assign(app, newApp);
|
|
54
|
+
setSystemMessages(prev => [
|
|
55
|
+
...prev,
|
|
56
|
+
{ role: 'assistant', content: 'Config updated. Model: ' + app.config.get().model },
|
|
57
|
+
]);
|
|
58
|
+
}
|
|
32
59
|
return;
|
|
33
60
|
}
|
|
34
61
|
await sendMessage(input);
|
|
35
|
-
}, [handleCommand, sendMessage]);
|
|
62
|
+
}, [handleCommand, sendMessage, app, showModelSelector]);
|
|
36
63
|
const allMessages = [...systemMessages, ...messages];
|
|
37
64
|
const activeSkills = app.skills.listActive().map(s => s.id);
|
|
38
|
-
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Header, { provider: config.provider, model: config.model }), _jsx(ChatList, { messages: allMessages, streaming: streaming, streamingToolCalls: streamingToolCalls, width: terminalWidth - 4 }), _jsx(Thinking, { active: isThinking }),
|
|
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 })] }));
|
|
39
73
|
}
|
|
40
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,EAAQ,SAAS,EAAE,MAAM,KAAK,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAQrD,MAAM,UAAU,MAAM,CAAC,EAAE,GAAG,EAAS;IACnC,MAAM,
|
|
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.
|
|
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.3" })] }), _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"}
|
|
@@ -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;
|
|
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,77 +1,105 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
2
|
export function useCommands(app) {
|
|
3
3
|
const handleCommand = useCallback((input) => {
|
|
4
|
-
const cmd = input.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
' help Show this help',
|
|
12
|
-
' tools List available tools',
|
|
13
|
-
' model Show current model',
|
|
14
|
-
' clear Clear screen',
|
|
15
|
-
' exit Exit',
|
|
16
|
-
'',
|
|
17
|
-
'Keyboard:',
|
|
18
|
-
' ↑/↓ Browse history',
|
|
19
|
-
' Ctrl+C Exit',
|
|
20
|
-
].join('\n'),
|
|
21
|
-
};
|
|
22
|
-
case 'tools':
|
|
23
|
-
return {
|
|
24
|
-
handled: true,
|
|
25
|
-
output: app.tools.list()
|
|
26
|
-
.map(t => ` ${t.name.padEnd(16)} ${t.description}`)
|
|
27
|
-
.join('\n'),
|
|
28
|
-
};
|
|
29
|
-
case 'model': {
|
|
4
|
+
const cmd = input.trim();
|
|
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) {
|
|
30
11
|
const c = app.config.get();
|
|
31
12
|
return {
|
|
32
13
|
handled: true,
|
|
33
14
|
output: [
|
|
34
|
-
`
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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',
|
|
39
24
|
].join('\n'),
|
|
40
25
|
};
|
|
41
26
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
else {
|
|
70
|
-
return { handled: true, output: ` Unknown skill: ${skillId}` };
|
|
71
|
-
}
|
|
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' };
|
|
33
|
+
}
|
|
34
|
+
// ── /new, /clear ────────────────────────────────────
|
|
35
|
+
if (name === '/new' || name === '/clear') {
|
|
36
|
+
app.session.clear();
|
|
37
|
+
return { handled: true, output: 'New session.', action: 'clear' };
|
|
38
|
+
}
|
|
39
|
+
// ── /compact ────────────────────────────────────────
|
|
40
|
+
if (name === '/compact') {
|
|
41
|
+
const before = app.session.getMessageCount();
|
|
42
|
+
app.session.truncate();
|
|
43
|
+
const after = app.session.getMessageCount();
|
|
44
|
+
return { handled: true, output: `Compacted: ${before} → ${after} messages` };
|
|
45
|
+
}
|
|
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}` };
|
|
72
53
|
}
|
|
73
|
-
|
|
54
|
+
else if (app.skills.activate(arg)) {
|
|
55
|
+
return { handled: true, output: `✓ ${arg}` };
|
|
56
|
+
}
|
|
57
|
+
return { handled: true, output: `Unknown skill: ${arg}` };
|
|
58
|
+
}
|
|
59
|
+
const all = app.skills.list();
|
|
60
|
+
const lines = [
|
|
61
|
+
'Skills:',
|
|
62
|
+
...all.map(s => {
|
|
63
|
+
const mark = app.skills.isActive(s.id) ? '✓' : '·';
|
|
64
|
+
return ` ${mark} ${s.id}`;
|
|
65
|
+
}),
|
|
66
|
+
'',
|
|
67
|
+
'Usage: /skills <id> to toggle',
|
|
68
|
+
];
|
|
69
|
+
return { handled: true, output: lines.join('\n') };
|
|
70
|
+
}
|
|
71
|
+
// ── /mcp ────────────────────────────────────────────
|
|
72
|
+
if (name === '/mcp') {
|
|
73
|
+
return {
|
|
74
|
+
handled: true,
|
|
75
|
+
output: [
|
|
76
|
+
'MCP: ~/.thatgfsj/mcp.json',
|
|
77
|
+
'',
|
|
78
|
+
' {',
|
|
79
|
+
' "servers": {',
|
|
80
|
+
' "name": { "command": "npx", "args": ["-y", "server"] }',
|
|
81
|
+
' }',
|
|
82
|
+
' }',
|
|
83
|
+
].join('\n'),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
// ── /help ───────────────────────────────────────────
|
|
87
|
+
if (name === '/help' || name === 'help') {
|
|
88
|
+
return {
|
|
89
|
+
handled: true,
|
|
90
|
+
output: [
|
|
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',
|
|
99
|
+
].join('\n'),
|
|
100
|
+
};
|
|
74
101
|
}
|
|
102
|
+
return { handled: false };
|
|
75
103
|
}, [app]);
|
|
76
104
|
return { handleCommand };
|
|
77
105
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCommands.js","sourceRoot":"","sources":["../../../src/tui/hooks/useCommands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;
|
|
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.
|
|
3
|
+
"version": "0.8.3",
|
|
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.
|
|
31
|
+
.version('0.8.3')
|
|
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,16 +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
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);
|
|
40
|
+
|
|
28
41
|
if (result.handled) {
|
|
42
|
+
// Special: /model shows selector
|
|
43
|
+
if (input.trim().toLowerCase() === '/model') {
|
|
44
|
+
setShowModelSelector(true);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
29
48
|
if (result.output) {
|
|
30
49
|
setSystemMessages(prev => [
|
|
31
50
|
...prev,
|
|
@@ -33,20 +52,35 @@ export function TuiApp({ app }: Props) {
|
|
|
33
52
|
{ role: 'assistant', content: result.output! },
|
|
34
53
|
]);
|
|
35
54
|
}
|
|
36
|
-
|
|
55
|
+
|
|
56
|
+
if (result.action === 'clear') {
|
|
37
57
|
setSystemMessages([]);
|
|
38
58
|
}
|
|
59
|
+
|
|
60
|
+
if (result.action === 'reinit') {
|
|
61
|
+
const { WelcomeScreen } = await import('./welcome.js');
|
|
62
|
+
await WelcomeScreen.interactiveSetup();
|
|
63
|
+
const { App: AppClass } = await import('../app/index.js');
|
|
64
|
+
const newApp = await AppClass.create();
|
|
65
|
+
Object.assign(app, newApp);
|
|
66
|
+
setSystemMessages(prev => [
|
|
67
|
+
...prev,
|
|
68
|
+
{ role: 'assistant', content: 'Config updated. Model: ' + app.config.get().model },
|
|
69
|
+
]);
|
|
70
|
+
}
|
|
71
|
+
|
|
39
72
|
return;
|
|
40
73
|
}
|
|
74
|
+
|
|
41
75
|
await sendMessage(input);
|
|
42
|
-
}, [handleCommand, sendMessage]);
|
|
76
|
+
}, [handleCommand, sendMessage, app, showModelSelector]);
|
|
43
77
|
|
|
44
78
|
const allMessages = [...systemMessages, ...messages];
|
|
45
79
|
const activeSkills = app.skills.listActive().map(s => s.id);
|
|
46
80
|
|
|
47
81
|
return (
|
|
48
82
|
<Box flexDirection="column" paddingX={1}>
|
|
49
|
-
<Header provider={config.provider} model={config.model} />
|
|
83
|
+
<Header provider={app.config.get().provider} model={app.config.get().model} />
|
|
50
84
|
<ChatList
|
|
51
85
|
messages={allMessages}
|
|
52
86
|
streaming={streaming}
|
|
@@ -54,7 +88,22 @@ export function TuiApp({ app }: Props) {
|
|
|
54
88
|
width={terminalWidth - 4}
|
|
55
89
|
/>
|
|
56
90
|
<Thinking active={isThinking} />
|
|
57
|
-
|
|
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} />
|
|
106
|
+
)}
|
|
58
107
|
<StatusBar messageCount={allMessages.length} skills={activeSkills} />
|
|
59
108
|
</Box>
|
|
60
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.
|
|
17
|
+
<Text dimColor> v0.8.3</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
|
+
}
|
|
@@ -4,87 +4,119 @@ import type { App } from '../../app/index.js';
|
|
|
4
4
|
interface CommandResult {
|
|
5
5
|
handled: boolean;
|
|
6
6
|
output?: string;
|
|
7
|
+
action?: 'clear' | 'reinit';
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
export function useCommands(app: App) {
|
|
10
|
-
const handleCommand = useCallback((input: string): CommandResult => {
|
|
11
|
-
const cmd = input.toLowerCase().trim();
|
|
12
|
-
|
|
13
|
-
switch (cmd) {
|
|
14
|
-
case 'help':
|
|
15
|
-
return {
|
|
16
|
-
handled: true,
|
|
17
|
-
output: [
|
|
18
|
-
'Commands:',
|
|
19
|
-
' help Show this help',
|
|
20
|
-
' tools List available tools',
|
|
21
|
-
' model Show current model',
|
|
22
|
-
' clear Clear screen',
|
|
23
|
-
' exit Exit',
|
|
24
|
-
'',
|
|
25
|
-
'Keyboard:',
|
|
26
|
-
' ↑/↓ Browse history',
|
|
27
|
-
' Ctrl+C Exit',
|
|
28
|
-
].join('\n'),
|
|
29
|
-
};
|
|
30
11
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
.join('\n'),
|
|
37
|
-
};
|
|
12
|
+
const handleCommand = useCallback((input: string): CommandResult => {
|
|
13
|
+
const cmd = input.trim();
|
|
14
|
+
const parts = cmd.split(/\s+/);
|
|
15
|
+
const name = parts[0].toLowerCase();
|
|
16
|
+
const arg = parts.slice(1).join(' ');
|
|
38
17
|
|
|
39
|
-
|
|
18
|
+
// ── /model [name] ───────────────────────────────────
|
|
19
|
+
if (name === '/model') {
|
|
20
|
+
if (!arg) {
|
|
40
21
|
const c = app.config.get();
|
|
41
22
|
return {
|
|
42
23
|
handled: true,
|
|
43
24
|
output: [
|
|
44
|
-
`
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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',
|
|
49
34
|
].join('\n'),
|
|
50
35
|
};
|
|
51
36
|
}
|
|
37
|
+
app.config.save({ model: arg });
|
|
38
|
+
return { handled: true, output: `Model → ${arg}` };
|
|
39
|
+
}
|
|
52
40
|
|
|
53
|
-
|
|
54
|
-
|
|
41
|
+
// ── /provider ───────────────────────────────────────
|
|
42
|
+
if (name === '/provider') {
|
|
43
|
+
return { handled: true, action: 'reinit' };
|
|
44
|
+
}
|
|
55
45
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
46
|
+
// ── /new, /clear ────────────────────────────────────
|
|
47
|
+
if (name === '/new' || name === '/clear') {
|
|
48
|
+
app.session.clear();
|
|
49
|
+
return { handled: true, output: 'New session.', action: 'clear' };
|
|
50
|
+
}
|
|
61
51
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
handled: true,
|
|
70
|
-
output: ['Skills ([✓] = active):', ...lines, '', 'Usage: skill <id> to toggle'].join('\n'),
|
|
71
|
-
};
|
|
72
|
-
}
|
|
52
|
+
// ── /compact ────────────────────────────────────────
|
|
53
|
+
if (name === '/compact') {
|
|
54
|
+
const before = app.session.getMessageCount();
|
|
55
|
+
app.session.truncate();
|
|
56
|
+
const after = app.session.getMessageCount();
|
|
57
|
+
return { handled: true, output: `Compacted: ${before} → ${after} messages` };
|
|
58
|
+
}
|
|
73
59
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
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}` };
|
|
84
69
|
}
|
|
70
|
+
return { handled: true, output: `Unknown skill: ${arg}` };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const all = app.skills.list();
|
|
74
|
+
const lines = [
|
|
75
|
+
'Skills:',
|
|
76
|
+
...all.map(s => {
|
|
77
|
+
const mark = app.skills.isActive(s.id) ? '✓' : '·';
|
|
78
|
+
return ` ${mark} ${s.id}`;
|
|
79
|
+
}),
|
|
80
|
+
'',
|
|
81
|
+
'Usage: /skills <id> to toggle',
|
|
82
|
+
];
|
|
83
|
+
return { handled: true, output: lines.join('\n') };
|
|
84
|
+
}
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
// ── /mcp ────────────────────────────────────────────
|
|
87
|
+
if (name === '/mcp') {
|
|
88
|
+
return {
|
|
89
|
+
handled: true,
|
|
90
|
+
output: [
|
|
91
|
+
'MCP: ~/.thatgfsj/mcp.json',
|
|
92
|
+
'',
|
|
93
|
+
' {',
|
|
94
|
+
' "servers": {',
|
|
95
|
+
' "name": { "command": "npx", "args": ["-y", "server"] }',
|
|
96
|
+
' }',
|
|
97
|
+
' }',
|
|
98
|
+
].join('\n'),
|
|
99
|
+
};
|
|
87
100
|
}
|
|
101
|
+
|
|
102
|
+
// ── /help ───────────────────────────────────────────
|
|
103
|
+
if (name === '/help' || name === 'help') {
|
|
104
|
+
return {
|
|
105
|
+
handled: true,
|
|
106
|
+
output: [
|
|
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',
|
|
115
|
+
].join('\n'),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return { handled: false };
|
|
88
120
|
}, [app]);
|
|
89
121
|
|
|
90
122
|
return { handleCommand };
|