thatgfsj-code 0.7.9 → 0.8.1
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 +19 -5
- package/dist/tui/app.js.map +1 -1
- package/dist/tui/components/Header.js +1 -1
- package/dist/tui/hooks/useCommands.d.ts +6 -0
- package/dist/tui/hooks/useCommands.d.ts.map +1 -1
- package/dist/tui/hooks/useCommands.js +138 -70
- package/dist/tui/hooks/useCommands.js.map +1 -1
- package/package.json +1 -1
- package/src/cmd/index.tsx +1 -1
- package/src/tui/app.tsx +30 -5
- package/src/tui/components/Header.tsx +1 -1
- package/src/tui/hooks/useCommands.ts +152 -74
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.1')
|
|
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;AASrD,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,
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/tui/app.tsx"],"names":[],"mappings":"AAAA,6BAA6B;AAC7B,OAAO,KAAgC,MAAM,OAAO,CAAC;AASrD,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,qBAoEpC"}
|
package/dist/tui/app.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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, useStdout } from 'ink';
|
|
4
|
+
import { Box, Text, 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';
|
|
@@ -12,7 +12,7 @@ import { useCommands } from './hooks/useCommands.js';
|
|
|
12
12
|
export function TuiApp({ app }) {
|
|
13
13
|
const config = app.config.get();
|
|
14
14
|
const { messages, isThinking, streaming, streamingToolCalls, sendMessage } = useChat(app);
|
|
15
|
-
const { handleCommand } = useCommands(app);
|
|
15
|
+
const { handleCommand, awaitingInput } = useCommands(app);
|
|
16
16
|
const [systemMessages, setSystemMessages] = useState([]);
|
|
17
17
|
const { stdout } = useStdout();
|
|
18
18
|
const terminalWidth = stdout?.columns || 80;
|
|
@@ -26,15 +26,29 @@ export function TuiApp({ app }) {
|
|
|
26
26
|
{ role: 'assistant', content: result.output },
|
|
27
27
|
]);
|
|
28
28
|
}
|
|
29
|
-
|
|
29
|
+
// Handle special actions
|
|
30
|
+
if (result.action === 'clear') {
|
|
30
31
|
setSystemMessages([]);
|
|
31
32
|
}
|
|
33
|
+
if (result.action === 'reinit') {
|
|
34
|
+
// Run init wizard then reload
|
|
35
|
+
const { WelcomeScreen } = await import('./welcome.js');
|
|
36
|
+
await WelcomeScreen.interactiveSetup();
|
|
37
|
+
// Reload app config
|
|
38
|
+
const { App: AppClass } = await import('../app/index.js');
|
|
39
|
+
const newApp = await AppClass.create();
|
|
40
|
+
Object.assign(app, newApp);
|
|
41
|
+
setSystemMessages(prev => [
|
|
42
|
+
...prev,
|
|
43
|
+
{ role: 'assistant', content: 'Configuration updated. Model: ' + app.config.get().model },
|
|
44
|
+
]);
|
|
45
|
+
}
|
|
32
46
|
return;
|
|
33
47
|
}
|
|
34
48
|
await sendMessage(input);
|
|
35
|
-
}, [handleCommand, sendMessage]);
|
|
49
|
+
}, [handleCommand, sendMessage, app]);
|
|
36
50
|
const allMessages = [...systemMessages, ...messages];
|
|
37
51
|
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, {
|
|
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 }), isThinking && _jsx(Thinking, {}), awaitingInput && (_jsxs(Box, { paddingLeft: 1, children: [_jsx(Text, { color: "#F59E0B", children: "\u25B8 " }), _jsx(Text, { color: "#94A3B8", children: awaitingInput.prompt.split('\n').pop() })] })), _jsx(UserInput, { onSubmit: onSubmit, disabled: isThinking }), _jsx(StatusBar, { messageCount: allMessages.length, skills: activeSkills })] }));
|
|
39
53
|
}
|
|
40
54
|
//# 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.
|
|
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.1" })] }), _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
|
|
@@ -2,9 +2,15 @@ import type { App } from '../../app/index.js';
|
|
|
2
2
|
interface CommandResult {
|
|
3
3
|
handled: boolean;
|
|
4
4
|
output?: string;
|
|
5
|
+
action?: 'clear' | 'reinit';
|
|
5
6
|
}
|
|
6
7
|
export declare function useCommands(app: App): {
|
|
7
8
|
handleCommand: (input: string) => CommandResult;
|
|
9
|
+
awaitingInput: {
|
|
10
|
+
type: string;
|
|
11
|
+
prompt: string;
|
|
12
|
+
handler: (input: string) => CommandResult;
|
|
13
|
+
} | null;
|
|
8
14
|
};
|
|
9
15
|
export {};
|
|
10
16
|
//# 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;
|
|
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;2BAOQ,MAAM,KAAG,aAAa;;cALxD,MAAM;gBACJ,MAAM;iBACL,CAAC,KAAK,EAAE,MAAM,KAAK,aAAa;;EA2J5C"}
|
|
@@ -1,78 +1,146 @@
|
|
|
1
|
-
import { useCallback } from 'react';
|
|
1
|
+
import { useCallback, useState } from 'react';
|
|
2
2
|
export function useCommands(app) {
|
|
3
|
+
const [awaitingInput, setAwaitingInput] = useState(null);
|
|
3
4
|
const handleCommand = useCallback((input) => {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
` Model: ${c.model}`,
|
|
36
|
-
` Base URL: ${c.baseUrl || 'default'}`,
|
|
37
|
-
` API Key: ${c.apiKey ? '••••' + c.apiKey.slice(-4) : 'not set'}`,
|
|
38
|
-
` Context Length: ${c.contextLength || 50} messages`,
|
|
39
|
-
].join('\n'),
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
case 'clear':
|
|
43
|
-
return { handled: true };
|
|
44
|
-
default:
|
|
45
|
-
// Handle /skill commands
|
|
46
|
-
if (cmd.startsWith('skill') || cmd.startsWith('/skill')) {
|
|
47
|
-
const parts = input.trim().split(/\s+/);
|
|
48
|
-
const sub = parts[1] || 'list';
|
|
49
|
-
if (sub === 'list') {
|
|
50
|
-
const all = app.skills.list();
|
|
51
|
-
const lines = all.map(s => {
|
|
52
|
-
const active = app.skills.isActive(s.id) ? '✓' : ' ';
|
|
53
|
-
return ` [${active}] ${s.name.padEnd(28)} ${s.description}`;
|
|
54
|
-
});
|
|
55
|
-
return {
|
|
56
|
-
handled: true,
|
|
57
|
-
output: ['Skills ([✓] = active):', ...lines, '', 'Usage: skill <id> to toggle'].join('\n'),
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
// Toggle skill
|
|
61
|
-
const skillId = sub;
|
|
62
|
-
if (app.skills.isActive(skillId)) {
|
|
63
|
-
app.skills.deactivate(skillId);
|
|
64
|
-
return { handled: true, output: ` Deactivated: ${skillId}` };
|
|
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
|
+
const cmd = input.trim();
|
|
12
|
+
const lower = cmd.toLowerCase();
|
|
13
|
+
// ── /model ──────────────────────────────────────────
|
|
14
|
+
if (lower === '/model' || lower === 'model') {
|
|
15
|
+
const current = app.config.get();
|
|
16
|
+
const lines = [
|
|
17
|
+
`Current: ${current.provider} / ${current.model}`,
|
|
18
|
+
'',
|
|
19
|
+
'Enter new model name directly, e.g.:',
|
|
20
|
+
' deepseek-chat',
|
|
21
|
+
' gpt-4o',
|
|
22
|
+
' claude-sonnet-4-20250514',
|
|
23
|
+
' qwen3-235b-a22b',
|
|
24
|
+
'',
|
|
25
|
+
'Or type "provider" to change provider.',
|
|
26
|
+
];
|
|
27
|
+
setAwaitingInput({
|
|
28
|
+
type: 'model',
|
|
29
|
+
prompt: lines.join('\n'),
|
|
30
|
+
handler: (input) => {
|
|
31
|
+
const val = input.trim();
|
|
32
|
+
if (!val)
|
|
33
|
+
return { handled: true, output: 'Cancelled.' };
|
|
34
|
+
if (val.toLowerCase() === 'provider') {
|
|
35
|
+
return { handled: true, action: 'reinit' };
|
|
65
36
|
}
|
|
66
|
-
|
|
67
|
-
|
|
37
|
+
app.config.save({ model: val });
|
|
38
|
+
return { handled: true, output: `Switched to: ${val}` };
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
return { handled: true, output: lines.join('\n') };
|
|
42
|
+
}
|
|
43
|
+
// ── /new, /clear ────────────────────────────────────
|
|
44
|
+
if (lower === '/new' || lower === '/clear' || lower === 'clear') {
|
|
45
|
+
app.session.clear();
|
|
46
|
+
return { handled: true, output: 'Session cleared.', action: 'clear' };
|
|
47
|
+
}
|
|
48
|
+
// ── /compact ────────────────────────────────────────
|
|
49
|
+
if (lower === '/compact') {
|
|
50
|
+
const before = app.session.getMessageCount();
|
|
51
|
+
app.session.truncate();
|
|
52
|
+
const after = app.session.getMessageCount();
|
|
53
|
+
return { handled: true, output: `Compacted: ${before} → ${after} messages` };
|
|
54
|
+
}
|
|
55
|
+
// ── /skills ─────────────────────────────────────────
|
|
56
|
+
if (lower === '/skills' || lower === 'skills') {
|
|
57
|
+
const all = app.skills.list();
|
|
58
|
+
const lines = [
|
|
59
|
+
'Skills ([✓] = active):',
|
|
60
|
+
...all.map(s => {
|
|
61
|
+
const active = app.skills.isActive(s.id) ? '✓' : ' ';
|
|
62
|
+
return ` [${active}] ${s.id.padEnd(24)} ${s.description}`;
|
|
63
|
+
}),
|
|
64
|
+
'',
|
|
65
|
+
'Usage: /skill <id> to toggle',
|
|
66
|
+
];
|
|
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}` };
|
|
68
77
|
}
|
|
69
|
-
else {
|
|
70
|
-
return { handled: true, output: `
|
|
78
|
+
else if (app.skills.activate(id)) {
|
|
79
|
+
return { handled: true, output: `Activated: ${id}` };
|
|
71
80
|
}
|
|
72
|
-
|
|
73
|
-
|
|
81
|
+
return { handled: true, output: `Unknown skill: ${id}` };
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
return { handled: true, output: lines.join('\n') };
|
|
85
|
+
}
|
|
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
|
+
// ── /mcp ────────────────────────────────────────────
|
|
99
|
+
if (lower === '/mcp') {
|
|
100
|
+
return {
|
|
101
|
+
handled: true,
|
|
102
|
+
output: [
|
|
103
|
+
'MCP (Model Context Protocol)',
|
|
104
|
+
'',
|
|
105
|
+
'MCP servers allow connecting external tool providers.',
|
|
106
|
+
'To configure, add MCP servers to ~/.thatgfsj/mcp.json:',
|
|
107
|
+
'',
|
|
108
|
+
' {',
|
|
109
|
+
' "servers": {',
|
|
110
|
+
' "my-server": {',
|
|
111
|
+
' "command": "npx",',
|
|
112
|
+
' "args": ["-y", "my-mcp-server"]',
|
|
113
|
+
' }',
|
|
114
|
+
' }',
|
|
115
|
+
' }',
|
|
116
|
+
'',
|
|
117
|
+
'Then restart gfcode to load MCP tools.',
|
|
118
|
+
].join('\n'),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
// ── /help ───────────────────────────────────────────
|
|
122
|
+
if (lower === '/help' || lower === 'help') {
|
|
123
|
+
return {
|
|
124
|
+
handled: true,
|
|
125
|
+
output: [
|
|
126
|
+
'Commands:',
|
|
127
|
+
' /model Switch model',
|
|
128
|
+
' /new, /clear New session',
|
|
129
|
+
' /compact Compress context',
|
|
130
|
+
' /skills Manage skills',
|
|
131
|
+
' /skill <id> Toggle skill',
|
|
132
|
+
' /mcp MCP settings',
|
|
133
|
+
' /help This help',
|
|
134
|
+
' exit Quit',
|
|
135
|
+
'',
|
|
136
|
+
'Keyboard:',
|
|
137
|
+
' ↑/↓ History',
|
|
138
|
+
' Ctrl+C Exit',
|
|
139
|
+
].join('\n'),
|
|
140
|
+
};
|
|
74
141
|
}
|
|
75
|
-
|
|
76
|
-
|
|
142
|
+
return { handled: false };
|
|
143
|
+
}, [app, awaitingInput]);
|
|
144
|
+
return { handleCommand, awaitingInput };
|
|
77
145
|
}
|
|
78
146
|
//# 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,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
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.1')
|
|
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
|
@@ -18,13 +18,14 @@ interface Props {
|
|
|
18
18
|
export function TuiApp({ app }: Props) {
|
|
19
19
|
const config = app.config.get();
|
|
20
20
|
const { messages, isThinking, streaming, streamingToolCalls, sendMessage } = useChat(app);
|
|
21
|
-
const { handleCommand } = useCommands(app);
|
|
21
|
+
const { handleCommand, awaitingInput } = useCommands(app);
|
|
22
22
|
const [systemMessages, setSystemMessages] = useState<MessageData[]>([]);
|
|
23
23
|
const { stdout } = useStdout();
|
|
24
24
|
const terminalWidth = stdout?.columns || 80;
|
|
25
25
|
|
|
26
26
|
const onSubmit = useCallback(async (input: string) => {
|
|
27
27
|
const result = handleCommand(input);
|
|
28
|
+
|
|
28
29
|
if (result.handled) {
|
|
29
30
|
if (result.output) {
|
|
30
31
|
setSystemMessages(prev => [
|
|
@@ -33,27 +34,51 @@ export function TuiApp({ app }: Props) {
|
|
|
33
34
|
{ role: 'assistant', content: result.output! },
|
|
34
35
|
]);
|
|
35
36
|
}
|
|
36
|
-
|
|
37
|
+
|
|
38
|
+
// Handle special actions
|
|
39
|
+
if (result.action === 'clear') {
|
|
37
40
|
setSystemMessages([]);
|
|
38
41
|
}
|
|
42
|
+
|
|
43
|
+
if (result.action === 'reinit') {
|
|
44
|
+
// Run init wizard then reload
|
|
45
|
+
const { WelcomeScreen } = await import('./welcome.js');
|
|
46
|
+
await WelcomeScreen.interactiveSetup();
|
|
47
|
+
// Reload app config
|
|
48
|
+
const { App: AppClass } = await import('../app/index.js');
|
|
49
|
+
const newApp = await AppClass.create();
|
|
50
|
+
Object.assign(app, newApp);
|
|
51
|
+
setSystemMessages(prev => [
|
|
52
|
+
...prev,
|
|
53
|
+
{ role: 'assistant', content: 'Configuration updated. Model: ' + app.config.get().model },
|
|
54
|
+
]);
|
|
55
|
+
}
|
|
56
|
+
|
|
39
57
|
return;
|
|
40
58
|
}
|
|
59
|
+
|
|
41
60
|
await sendMessage(input);
|
|
42
|
-
}, [handleCommand, sendMessage]);
|
|
61
|
+
}, [handleCommand, sendMessage, app]);
|
|
43
62
|
|
|
44
63
|
const allMessages = [...systemMessages, ...messages];
|
|
45
64
|
const activeSkills = app.skills.listActive().map(s => s.id);
|
|
46
65
|
|
|
47
66
|
return (
|
|
48
67
|
<Box flexDirection="column" paddingX={1}>
|
|
49
|
-
<Header provider={config.provider} model={config.model} />
|
|
68
|
+
<Header provider={app.config.get().provider} model={app.config.get().model} />
|
|
50
69
|
<ChatList
|
|
51
70
|
messages={allMessages}
|
|
52
71
|
streaming={streaming}
|
|
53
72
|
streamingToolCalls={streamingToolCalls}
|
|
54
73
|
width={terminalWidth - 4}
|
|
55
74
|
/>
|
|
56
|
-
<Thinking
|
|
75
|
+
{isThinking && <Thinking />}
|
|
76
|
+
{awaitingInput && (
|
|
77
|
+
<Box paddingLeft={1}>
|
|
78
|
+
<Text color="#F59E0B">▸ </Text>
|
|
79
|
+
<Text color="#94A3B8">{awaitingInput.prompt.split('\n').pop()}</Text>
|
|
80
|
+
</Box>
|
|
81
|
+
)}
|
|
57
82
|
<UserInput onSubmit={onSubmit} disabled={isThinking} />
|
|
58
83
|
<StatusBar messageCount={allMessages.length} skills={activeSkills} />
|
|
59
84
|
</Box>
|
|
@@ -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.1</Text>
|
|
18
18
|
</Box>
|
|
19
19
|
<Box>
|
|
20
20
|
<Text color="#06B6D4" bold> {provider} </Text>
|
|
@@ -1,91 +1,169 @@
|
|
|
1
|
-
import { useCallback } from 'react';
|
|
1
|
+
import { useCallback, useState } from 'react';
|
|
2
2
|
import type { App } from '../../app/index.js';
|
|
3
3
|
|
|
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) {
|
|
11
|
+
const [awaitingInput, setAwaitingInput] = useState<{
|
|
12
|
+
type: string;
|
|
13
|
+
prompt: string;
|
|
14
|
+
handler: (input: string) => CommandResult;
|
|
15
|
+
} | null>(null);
|
|
16
|
+
|
|
10
17
|
const handleCommand = useCallback((input: string): CommandResult => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
.join('\n'),
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
case 'model': {
|
|
40
|
-
const c = app.config.get();
|
|
41
|
-
return {
|
|
42
|
-
handled: true,
|
|
43
|
-
output: [
|
|
44
|
-
` Provider: ${c.provider}`,
|
|
45
|
-
` Model: ${c.model}`,
|
|
46
|
-
` Base URL: ${c.baseUrl || 'default'}`,
|
|
47
|
-
` API Key: ${c.apiKey ? '••••' + c.apiKey.slice(-4) : 'not set'}`,
|
|
48
|
-
` Context Length: ${c.contextLength || 50} messages`,
|
|
49
|
-
].join('\n'),
|
|
50
|
-
};
|
|
51
|
-
}
|
|
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
|
+
const cmd = input.trim();
|
|
26
|
+
const lower = cmd.toLowerCase();
|
|
27
|
+
|
|
28
|
+
// ── /model ──────────────────────────────────────────
|
|
29
|
+
if (lower === '/model' || lower === 'model') {
|
|
30
|
+
const current = app.config.get();
|
|
31
|
+
|
|
32
|
+
const lines = [
|
|
33
|
+
`Current: ${current.provider} / ${current.model}`,
|
|
34
|
+
'',
|
|
35
|
+
'Enter new model name directly, e.g.:',
|
|
36
|
+
' deepseek-chat',
|
|
37
|
+
' gpt-4o',
|
|
38
|
+
' claude-sonnet-4-20250514',
|
|
39
|
+
' qwen3-235b-a22b',
|
|
40
|
+
'',
|
|
41
|
+
'Or type "provider" to change provider.',
|
|
42
|
+
];
|
|
52
43
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (sub === 'list') {
|
|
63
|
-
const all = app.skills.list();
|
|
64
|
-
const lines = all.map(s => {
|
|
65
|
-
const active = app.skills.isActive(s.id) ? '✓' : ' ';
|
|
66
|
-
return ` [${active}] ${s.name.padEnd(28)} ${s.description}`;
|
|
67
|
-
});
|
|
68
|
-
return {
|
|
69
|
-
handled: true,
|
|
70
|
-
output: ['Skills ([✓] = active):', ...lines, '', 'Usage: skill <id> to toggle'].join('\n'),
|
|
71
|
-
};
|
|
44
|
+
setAwaitingInput({
|
|
45
|
+
type: 'model',
|
|
46
|
+
prompt: lines.join('\n'),
|
|
47
|
+
handler: (input: string) => {
|
|
48
|
+
const val = input.trim();
|
|
49
|
+
if (!val) return { handled: true, output: 'Cancelled.' };
|
|
50
|
+
if (val.toLowerCase() === 'provider') {
|
|
51
|
+
return { handled: true, action: 'reinit' };
|
|
72
52
|
}
|
|
53
|
+
app.config.save({ model: val });
|
|
54
|
+
return { handled: true, output: `Switched to: ${val}` };
|
|
55
|
+
},
|
|
56
|
+
});
|
|
73
57
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
58
|
+
return { handled: true, output: lines.join('\n') };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ── /new, /clear ────────────────────────────────────
|
|
62
|
+
if (lower === '/new' || lower === '/clear' || lower === 'clear') {
|
|
63
|
+
app.session.clear();
|
|
64
|
+
return { handled: true, output: 'Session cleared.', action: 'clear' };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ── /compact ────────────────────────────────────────
|
|
68
|
+
if (lower === '/compact') {
|
|
69
|
+
const before = app.session.getMessageCount();
|
|
70
|
+
app.session.truncate();
|
|
71
|
+
const after = app.session.getMessageCount();
|
|
72
|
+
return { handled: true, output: `Compacted: ${before} → ${after} messages` };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── /skills ─────────────────────────────────────────
|
|
76
|
+
if (lower === '/skills' || lower === 'skills') {
|
|
77
|
+
const all = app.skills.list();
|
|
78
|
+
const lines = [
|
|
79
|
+
'Skills ([✓] = active):',
|
|
80
|
+
...all.map(s => {
|
|
81
|
+
const active = app.skills.isActive(s.id) ? '✓' : ' ';
|
|
82
|
+
return ` [${active}] ${s.id.padEnd(24)} ${s.description}`;
|
|
83
|
+
}),
|
|
84
|
+
'',
|
|
85
|
+
'Usage: /skill <id> to toggle',
|
|
86
|
+
];
|
|
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}` };
|
|
83
99
|
}
|
|
84
|
-
|
|
100
|
+
return { handled: true, output: `Unknown skill: ${id}` };
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
return { handled: true, output: lines.join('\n') };
|
|
105
|
+
}
|
|
106
|
+
|
|
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
|
+
}
|
|
85
118
|
|
|
86
|
-
|
|
119
|
+
// ── /mcp ────────────────────────────────────────────
|
|
120
|
+
if (lower === '/mcp') {
|
|
121
|
+
return {
|
|
122
|
+
handled: true,
|
|
123
|
+
output: [
|
|
124
|
+
'MCP (Model Context Protocol)',
|
|
125
|
+
'',
|
|
126
|
+
'MCP servers allow connecting external tool providers.',
|
|
127
|
+
'To configure, add MCP servers to ~/.thatgfsj/mcp.json:',
|
|
128
|
+
'',
|
|
129
|
+
' {',
|
|
130
|
+
' "servers": {',
|
|
131
|
+
' "my-server": {',
|
|
132
|
+
' "command": "npx",',
|
|
133
|
+
' "args": ["-y", "my-mcp-server"]',
|
|
134
|
+
' }',
|
|
135
|
+
' }',
|
|
136
|
+
' }',
|
|
137
|
+
'',
|
|
138
|
+
'Then restart gfcode to load MCP tools.',
|
|
139
|
+
].join('\n'),
|
|
140
|
+
};
|
|
87
141
|
}
|
|
88
|
-
}, [app]);
|
|
89
142
|
|
|
90
|
-
|
|
143
|
+
// ── /help ───────────────────────────────────────────
|
|
144
|
+
if (lower === '/help' || lower === 'help') {
|
|
145
|
+
return {
|
|
146
|
+
handled: true,
|
|
147
|
+
output: [
|
|
148
|
+
'Commands:',
|
|
149
|
+
' /model Switch model',
|
|
150
|
+
' /new, /clear New session',
|
|
151
|
+
' /compact Compress context',
|
|
152
|
+
' /skills Manage skills',
|
|
153
|
+
' /skill <id> Toggle skill',
|
|
154
|
+
' /mcp MCP settings',
|
|
155
|
+
' /help This help',
|
|
156
|
+
' exit Quit',
|
|
157
|
+
'',
|
|
158
|
+
'Keyboard:',
|
|
159
|
+
' ↑/↓ History',
|
|
160
|
+
' Ctrl+C Exit',
|
|
161
|
+
].join('\n'),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return { handled: false };
|
|
166
|
+
}, [app, awaitingInput]);
|
|
167
|
+
|
|
168
|
+
return { handleCommand, awaitingInput };
|
|
91
169
|
}
|