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 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.7.9')
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')
@@ -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,qBA2CpC"}
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 (input.toLowerCase().trim() === 'clear') {
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 }), _jsx(UserInput, { onSubmit: onSubmit, disabled: isThinking }), _jsx(StatusBar, { messageCount: allMessages.length, skills: activeSkills })] }));
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
@@ -1 +1 @@
1
- {"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/tui/app.tsx"],"names":[],"mappings":"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"}
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.7.9" })] }), _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) })] }));
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"}
@@ -2,6 +2,7 @@ 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;
@@ -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;CACjB;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,GAAG;2BACQ,MAAM,KAAG,aAAa;EAiFjE"}
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.toLowerCase().trim();
5
- switch (cmd) {
6
- case 'help':
7
- return {
8
- handled: true,
9
- output: [
10
- 'Commands:',
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
- ` Provider: ${c.provider}`,
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`,
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
- 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}` };
65
- }
66
- else if (app.skills.activate(skillId)) {
67
- return { handled: true, output: ` Activated: ${skillId}` };
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
- return { handled: false };
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":"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"}
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.7.9",
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.7.9')
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
- if (input.toLowerCase().trim() === 'clear') {
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
- <UserInput onSubmit={onSubmit} disabled={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} />
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.7.9</Text>
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
- case 'tools':
32
- return {
33
- handled: true,
34
- output: app.tools.list()
35
- .map(t => ` ${t.name.padEnd(16)} ${t.description}`)
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
- case 'model': {
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
- ` 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`,
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
- case 'clear':
54
- return { handled: true };
41
+ // ── /provider ───────────────────────────────────────
42
+ if (name === '/provider') {
43
+ return { handled: true, action: 'reinit' };
44
+ }
55
45
 
56
- default:
57
- // Handle /skill commands
58
- if (cmd.startsWith('skill') || cmd.startsWith('/skill')) {
59
- const parts = input.trim().split(/\s+/);
60
- const sub = parts[1] || 'list';
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
- 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
- };
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
- // Toggle skill
75
- const skillId = sub;
76
- if (app.skills.isActive(skillId)) {
77
- app.skills.deactivate(skillId);
78
- return { handled: true, output: ` Deactivated: ${skillId}` };
79
- } else if (app.skills.activate(skillId)) {
80
- return { handled: true, output: ` Activated: ${skillId}` };
81
- } else {
82
- return { handled: true, output: ` Unknown skill: ${skillId}` };
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
- return { handled: false };
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 };