thatgfsj-code 1.0.3 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +192 -0
- package/DEVELOPMENT.md +286 -0
- package/dist/app/index.d.ts +40 -3
- package/dist/app/index.d.ts.map +1 -1
- package/dist/app/index.js +87 -12
- package/dist/app/index.js.map +1 -1
- package/dist/cache/fingerprint.d.ts +49 -0
- package/dist/cache/fingerprint.d.ts.map +1 -0
- package/dist/cache/fingerprint.js +100 -0
- package/dist/cache/fingerprint.js.map +1 -0
- package/dist/cache/index.d.ts +15 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +15 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/smartModel.d.ts +40 -0
- package/dist/cache/smartModel.d.ts.map +1 -0
- package/dist/cache/smartModel.js +43 -0
- package/dist/cache/smartModel.js.map +1 -0
- package/dist/cache/stats.d.ts +93 -0
- package/dist/cache/stats.d.ts.map +1 -0
- package/dist/cache/stats.js +155 -0
- package/dist/cache/stats.js.map +1 -0
- package/dist/cache/volatile.d.ts +32 -0
- package/dist/cache/volatile.d.ts.map +1 -0
- package/dist/cache/volatile.js +44 -0
- package/dist/cache/volatile.js.map +1 -0
- package/dist/cmd/index.d.ts +21 -0
- package/dist/cmd/index.d.ts.map +1 -1
- package/dist/cmd/index.js +155 -31
- package/dist/cmd/index.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +8 -0
- package/dist/config/index.js.map +1 -1
- package/dist/config/types.d.ts +14 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/llm/anthropic.d.ts +55 -5
- package/dist/llm/anthropic.d.ts.map +1 -1
- package/dist/llm/anthropic.js +172 -26
- package/dist/llm/anthropic.js.map +1 -1
- package/dist/llm/index.d.ts +26 -8
- package/dist/llm/index.d.ts.map +1 -1
- package/dist/llm/index.js +83 -15
- package/dist/llm/index.js.map +1 -1
- package/dist/llm/openai.d.ts +28 -8
- package/dist/llm/openai.d.ts.map +1 -1
- package/dist/llm/openai.js +58 -8
- package/dist/llm/openai.js.map +1 -1
- package/dist/llm/provider.d.ts +40 -8
- package/dist/llm/provider.d.ts.map +1 -1
- package/dist/llm/provider.js +8 -1
- package/dist/llm/provider.js.map +1 -1
- package/dist/prompts/index.d.ts +32 -0
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +33 -11
- package/dist/prompts/index.js.map +1 -1
- package/dist/session/index.d.ts +43 -3
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/index.js +114 -17
- package/dist/session/index.js.map +1 -1
- package/dist/skills/brainstorming.js +33 -33
- package/dist/skills/code-review.js +35 -35
- package/dist/skills/executing-plans.js +18 -18
- package/dist/skills/frontend-design.js +25 -25
- package/dist/skills/git-workflow.js +26 -26
- package/dist/skills/improve-architecture.js +28 -28
- package/dist/skills/neuroweave.js +37 -37
- package/dist/skills/playwright.js +62 -62
- package/dist/skills/prototype.js +20 -20
- package/dist/skills/subagent.js +19 -19
- package/dist/skills/supabase.js +34 -34
- package/dist/skills/systematic-debugging.js +34 -34
- package/dist/skills/tdd.js +29 -29
- package/dist/skills/triage.js +25 -25
- package/dist/skills/verification.js +22 -22
- package/dist/skills/writing-plans.js +32 -32
- package/dist/tools/nwt.js +16 -16
- package/dist/tui/app.d.ts.map +1 -1
- package/dist/tui/app.js +9 -2
- package/dist/tui/app.js.map +1 -1
- package/dist/tui/components/ChatList.d.ts.map +1 -1
- package/dist/tui/components/ChatList.js +4 -3
- package/dist/tui/components/ChatList.js.map +1 -1
- package/dist/tui/components/Header.d.ts +8 -0
- package/dist/tui/components/Header.d.ts.map +1 -1
- package/dist/tui/components/Header.js +17 -3
- package/dist/tui/components/Header.js.map +1 -1
- package/dist/tui/components/InitWizard.d.ts +12 -1
- package/dist/tui/components/InitWizard.d.ts.map +1 -1
- package/dist/tui/components/InitWizard.js +58 -3
- package/dist/tui/components/InitWizard.js.map +1 -1
- package/dist/tui/components/ToolCall.d.ts +1 -1
- package/dist/tui/components/ToolCall.d.ts.map +1 -1
- package/dist/tui/components/ToolCall.js +4 -4
- package/dist/tui/components/ToolCall.js.map +1 -1
- package/dist/tui/hooks/useChat.d.ts +22 -0
- package/dist/tui/hooks/useChat.d.ts.map +1 -1
- package/dist/tui/hooks/useChat.js +127 -50
- package/dist/tui/hooks/useChat.js.map +1 -1
- package/dist/tui/hooks/useCommands.d.ts.map +1 -1
- package/dist/tui/hooks/useCommands.js +78 -0
- package/dist/tui/hooks/useCommands.js.map +1 -1
- package/dist/types.d.ts +79 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -1
- package/dist/utils/stableStringify.d.ts +21 -0
- package/dist/utils/stableStringify.d.ts.map +1 -0
- package/dist/utils/stableStringify.js +53 -0
- package/dist/utils/stableStringify.js.map +1 -0
- package/dist/utils/thinking.d.ts +59 -0
- package/dist/utils/thinking.d.ts.map +1 -0
- package/dist/utils/thinking.js +104 -0
- package/dist/utils/thinking.js.map +1 -0
- package/install.bat +63 -0
- package/install.ps1 +238 -0
- package/install.sh +113 -0
- package/package.json +7 -4
- package/src/app/index.ts +180 -108
- package/src/cache/fingerprint.ts +101 -0
- package/src/cache/index.ts +15 -0
- package/src/cache/smartModel.ts +52 -0
- package/src/cache/stats.ts +199 -0
- package/src/cache/volatile.ts +47 -0
- package/src/cmd/index.tsx +288 -160
- package/src/config/index.ts +156 -148
- package/src/config/providers.ts +234 -234
- package/src/config/types.ts +67 -53
- package/src/hooks/index.ts +111 -111
- package/src/llm/anthropic.ts +388 -243
- package/src/llm/gemini.ts +169 -169
- package/src/llm/index.ts +265 -200
- package/src/llm/openai.ts +243 -196
- package/src/llm/provider.ts +66 -34
- package/src/prompts/index.ts +260 -220
- package/src/session/compactor.ts +103 -103
- package/src/session/index.ts +181 -87
- package/src/session/message.ts +42 -42
- package/src/skills/brainstorming.ts +43 -43
- package/src/skills/code-review.ts +45 -45
- package/src/skills/executing-plans.ts +27 -27
- package/src/skills/frontend-design.ts +35 -35
- package/src/skills/git-workflow.ts +36 -36
- package/src/skills/improve-architecture.ts +38 -38
- package/src/skills/index.ts +136 -136
- package/src/skills/neuroweave.ts +47 -47
- package/src/skills/playwright.ts +72 -72
- package/src/skills/prototype.ts +30 -30
- package/src/skills/subagent.ts +28 -28
- package/src/skills/supabase.ts +44 -44
- package/src/skills/systematic-debugging.ts +44 -44
- package/src/skills/tdd.ts +39 -39
- package/src/skills/triage.ts +35 -35
- package/src/skills/verification.ts +31 -31
- package/src/skills/writing-plans.ts +42 -42
- package/src/tools/nwt.ts +598 -598
- package/src/tools/types.ts +122 -122
- package/src/tui/app.tsx +199 -186
- package/src/tui/components/ChatList.tsx +41 -32
- package/src/tui/components/ChatMessage.tsx +54 -54
- package/src/tui/components/Header.tsx +56 -28
- package/src/tui/components/InitWizard.tsx +217 -132
- package/src/tui/components/Markdown.tsx +35 -35
- package/src/tui/components/ModelSelector.tsx +87 -87
- package/src/tui/components/StatusBar.tsx +30 -30
- package/src/tui/components/Thinking.tsx +17 -17
- package/src/tui/components/ToolCall.tsx +102 -71
- package/src/tui/components/UserInput.tsx +131 -131
- package/src/tui/hooks/useChat.ts +287 -194
- package/src/tui/hooks/useCommands.ts +234 -154
- package/src/tui/index.ts +6 -6
- package/src/tui/welcome.ts +177 -177
- package/src/types.ts +104 -42
- package/src/utils/diff.ts +71 -71
- package/src/utils/project.ts +99 -99
- package/src/utils/stableStringify.ts +47 -0
- package/src/utils/thinking.ts +119 -0
- package/tests/cache/fingerprint.test.ts +75 -0
- package/tests/cache/stableStringify.test.ts +43 -0
- package/tests/cache/stats.test.ts +146 -0
- package/tests/cache/volatile.test.ts +75 -0
- package/tests/smoke-pollution.mjs +78 -0
- package/tests/smoke-thinking.mjs +110 -0
- package/tests/smoke-tool-stream.mjs +69 -0
- package/tests/smoke-tool.mjs +117 -0
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
/** @jsxImportSource react */
|
|
2
|
-
import React, { useMemo } from 'react';
|
|
3
|
-
import { Text } from 'ink';
|
|
4
|
-
import { marked } from 'marked';
|
|
5
|
-
import TerminalRenderer from 'marked-terminal';
|
|
6
|
-
|
|
7
|
-
// Configure marked to use terminal renderer
|
|
8
|
-
marked.setOptions({
|
|
9
|
-
// @ts-ignore - marked-terminal types are incomplete
|
|
10
|
-
renderer: new TerminalRenderer({
|
|
11
|
-
showSectionPrefix: false,
|
|
12
|
-
tab: 2,
|
|
13
|
-
}),
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
interface Props {
|
|
17
|
-
content: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function Markdown({ content }: Props) {
|
|
21
|
-
const rendered = useMemo(() => {
|
|
22
|
-
try {
|
|
23
|
-
const result = marked.parse(content);
|
|
24
|
-
// marked.parse returns string | Promise<string> depending on async option
|
|
25
|
-
if (typeof result === 'string') {
|
|
26
|
-
return result.trimEnd();
|
|
27
|
-
}
|
|
28
|
-
return content;
|
|
29
|
-
} catch {
|
|
30
|
-
return content;
|
|
31
|
-
}
|
|
32
|
-
}, [content]);
|
|
33
|
-
|
|
34
|
-
return <Text>{rendered}</Text>;
|
|
35
|
-
}
|
|
1
|
+
/** @jsxImportSource react */
|
|
2
|
+
import React, { useMemo } from 'react';
|
|
3
|
+
import { Text } from 'ink';
|
|
4
|
+
import { marked } from 'marked';
|
|
5
|
+
import TerminalRenderer from 'marked-terminal';
|
|
6
|
+
|
|
7
|
+
// Configure marked to use terminal renderer
|
|
8
|
+
marked.setOptions({
|
|
9
|
+
// @ts-ignore - marked-terminal types are incomplete
|
|
10
|
+
renderer: new TerminalRenderer({
|
|
11
|
+
showSectionPrefix: false,
|
|
12
|
+
tab: 2,
|
|
13
|
+
}),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
content: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function Markdown({ content }: Props) {
|
|
21
|
+
const rendered = useMemo(() => {
|
|
22
|
+
try {
|
|
23
|
+
const result = marked.parse(content);
|
|
24
|
+
// marked.parse returns string | Promise<string> depending on async option
|
|
25
|
+
if (typeof result === 'string') {
|
|
26
|
+
return result.trimEnd();
|
|
27
|
+
}
|
|
28
|
+
return content;
|
|
29
|
+
} catch {
|
|
30
|
+
return content;
|
|
31
|
+
}
|
|
32
|
+
}, [content]);
|
|
33
|
+
|
|
34
|
+
return <Text>{rendered}</Text>;
|
|
35
|
+
}
|
|
@@ -1,87 +1,87 @@
|
|
|
1
|
-
/** @jsxImportSource react */
|
|
2
|
-
import React, { useState, useEffect } from 'react';
|
|
3
|
-
import { Box, Text } from 'ink';
|
|
4
|
-
import SelectInput from 'ink-select-input';
|
|
5
|
-
import { readFileSync, existsSync } from 'fs';
|
|
6
|
-
import { join } from 'path';
|
|
7
|
-
import { homedir } from 'os';
|
|
8
|
-
|
|
9
|
-
interface Props {
|
|
10
|
-
currentModel: string;
|
|
11
|
-
onSelect: (model: string) => void;
|
|
12
|
-
onAddNew: () => void;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface SavedModel {
|
|
16
|
-
label: string;
|
|
17
|
-
value: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function loadSavedModels(): SavedModel[] {
|
|
21
|
-
const configPath = join(homedir(), '.thatgfsj', 'config.json');
|
|
22
|
-
const models: SavedModel[] = [];
|
|
23
|
-
const seen = new Set<string>();
|
|
24
|
-
|
|
25
|
-
// Load from history if exists
|
|
26
|
-
const historyPath = join(homedir(), '.thatgfsj', 'models.json');
|
|
27
|
-
if (existsSync(historyPath)) {
|
|
28
|
-
try {
|
|
29
|
-
const history = JSON.parse(readFileSync(historyPath, 'utf-8'));
|
|
30
|
-
for (const m of history) {
|
|
31
|
-
if (!seen.has(m)) {
|
|
32
|
-
seen.add(m);
|
|
33
|
-
models.push({ label: m, value: m });
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
} catch {}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Always include current model
|
|
40
|
-
if (existsSync(configPath)) {
|
|
41
|
-
try {
|
|
42
|
-
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
43
|
-
if (config.model && !seen.has(config.model)) {
|
|
44
|
-
seen.add(config.model);
|
|
45
|
-
models.push({ label: `${config.model} (当前)`, value: config.model });
|
|
46
|
-
}
|
|
47
|
-
} catch {}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Add some common defaults if list is empty
|
|
51
|
-
if (models.length === 0) {
|
|
52
|
-
const defaults = ['deepseek-chat', 'gpt-4o', 'mimo-v2.5-pro'];
|
|
53
|
-
for (const m of defaults) {
|
|
54
|
-
models.push({ label: m, value: m });
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return models;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function ModelSelector({ currentModel, onSelect, onAddNew }: Props) {
|
|
62
|
-
const [items, setItems] = useState<SavedModel[]>([]);
|
|
63
|
-
|
|
64
|
-
useEffect(() => {
|
|
65
|
-
const saved = loadSavedModels();
|
|
66
|
-
// Add "add new" option at the end
|
|
67
|
-
saved.push({ label: '+ 添加新模型', value: '__add_new__' });
|
|
68
|
-
setItems(saved);
|
|
69
|
-
}, []);
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
<Box flexDirection="column" paddingLeft={1}>
|
|
73
|
-
<Text color="#06B6D4" bold>当前模型: {currentModel}</Text>
|
|
74
|
-
<Text dimColor>选择模型 (↑↓ 回车):</Text>
|
|
75
|
-
<SelectInput
|
|
76
|
-
items={items}
|
|
77
|
-
onSelect={(item) => {
|
|
78
|
-
if (item.value === '__add_new__') {
|
|
79
|
-
onAddNew();
|
|
80
|
-
} else {
|
|
81
|
-
onSelect(item.value);
|
|
82
|
-
}
|
|
83
|
-
}}
|
|
84
|
-
/>
|
|
85
|
-
</Box>
|
|
86
|
-
);
|
|
87
|
-
}
|
|
1
|
+
/** @jsxImportSource react */
|
|
2
|
+
import React, { useState, useEffect } from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import SelectInput from 'ink-select-input';
|
|
5
|
+
import { readFileSync, existsSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { homedir } from 'os';
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
currentModel: string;
|
|
11
|
+
onSelect: (model: string) => void;
|
|
12
|
+
onAddNew: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface SavedModel {
|
|
16
|
+
label: string;
|
|
17
|
+
value: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function loadSavedModels(): SavedModel[] {
|
|
21
|
+
const configPath = join(homedir(), '.thatgfsj', 'config.json');
|
|
22
|
+
const models: SavedModel[] = [];
|
|
23
|
+
const seen = new Set<string>();
|
|
24
|
+
|
|
25
|
+
// Load from history if exists
|
|
26
|
+
const historyPath = join(homedir(), '.thatgfsj', 'models.json');
|
|
27
|
+
if (existsSync(historyPath)) {
|
|
28
|
+
try {
|
|
29
|
+
const history = JSON.parse(readFileSync(historyPath, 'utf-8'));
|
|
30
|
+
for (const m of history) {
|
|
31
|
+
if (!seen.has(m)) {
|
|
32
|
+
seen.add(m);
|
|
33
|
+
models.push({ label: m, value: m });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
} catch {}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Always include current model
|
|
40
|
+
if (existsSync(configPath)) {
|
|
41
|
+
try {
|
|
42
|
+
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
43
|
+
if (config.model && !seen.has(config.model)) {
|
|
44
|
+
seen.add(config.model);
|
|
45
|
+
models.push({ label: `${config.model} (当前)`, value: config.model });
|
|
46
|
+
}
|
|
47
|
+
} catch {}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Add some common defaults if list is empty
|
|
51
|
+
if (models.length === 0) {
|
|
52
|
+
const defaults = ['deepseek-chat', 'gpt-4o', 'mimo-v2.5-pro'];
|
|
53
|
+
for (const m of defaults) {
|
|
54
|
+
models.push({ label: m, value: m });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return models;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function ModelSelector({ currentModel, onSelect, onAddNew }: Props) {
|
|
62
|
+
const [items, setItems] = useState<SavedModel[]>([]);
|
|
63
|
+
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
const saved = loadSavedModels();
|
|
66
|
+
// Add "add new" option at the end
|
|
67
|
+
saved.push({ label: '+ 添加新模型', value: '__add_new__' });
|
|
68
|
+
setItems(saved);
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<Box flexDirection="column" paddingLeft={1}>
|
|
73
|
+
<Text color="#06B6D4" bold>当前模型: {currentModel}</Text>
|
|
74
|
+
<Text dimColor>选择模型 (↑↓ 回车):</Text>
|
|
75
|
+
<SelectInput
|
|
76
|
+
items={items}
|
|
77
|
+
onSelect={(item) => {
|
|
78
|
+
if (item.value === '__add_new__') {
|
|
79
|
+
onAddNew();
|
|
80
|
+
} else {
|
|
81
|
+
onSelect(item.value);
|
|
82
|
+
}
|
|
83
|
+
}}
|
|
84
|
+
/>
|
|
85
|
+
</Box>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
/** @jsxImportSource react */
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import { Box, Text } from 'ink';
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
messageCount: number;
|
|
7
|
-
skills: string[];
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export const StatusBar = React.memo(function StatusBar({ messageCount, skills }: Props) {
|
|
11
|
-
const activeSkills = skills.slice(0, 3).join(', ');
|
|
12
|
-
const moreSkills = skills.length > 3 ? ` +${skills.length - 3}` : '';
|
|
13
|
-
|
|
14
|
-
return (
|
|
15
|
-
<Box flexDirection="column" marginTop={0}>
|
|
16
|
-
<Text color="#374151">{'─'.repeat(80)}</Text>
|
|
17
|
-
<Box justifyContent="space-between" width="100%">
|
|
18
|
-
<Box>
|
|
19
|
-
<Text backgroundColor="#06B6D4" color="#0F172A" bold> ⚡ THATGFSJ CODE </Text>
|
|
20
|
-
<Text dimColor> │ {messageCount} 条消息</Text>
|
|
21
|
-
</Box>
|
|
22
|
-
<Box>
|
|
23
|
-
{skills.length > 0 && (
|
|
24
|
-
<Text dimColor> 技能: {activeSkills}{moreSkills} </Text>
|
|
25
|
-
)}
|
|
26
|
-
</Box>
|
|
27
|
-
</Box>
|
|
28
|
-
</Box>
|
|
29
|
-
);
|
|
30
|
-
});
|
|
1
|
+
/** @jsxImportSource react */
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
messageCount: number;
|
|
7
|
+
skills: string[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const StatusBar = React.memo(function StatusBar({ messageCount, skills }: Props) {
|
|
11
|
+
const activeSkills = skills.slice(0, 3).join(', ');
|
|
12
|
+
const moreSkills = skills.length > 3 ? ` +${skills.length - 3}` : '';
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<Box flexDirection="column" marginTop={0}>
|
|
16
|
+
<Text color="#374151">{'─'.repeat(80)}</Text>
|
|
17
|
+
<Box justifyContent="space-between" width="100%">
|
|
18
|
+
<Box>
|
|
19
|
+
<Text backgroundColor="#06B6D4" color="#0F172A" bold> ⚡ THATGFSJ CODE </Text>
|
|
20
|
+
<Text dimColor> │ {messageCount} 条消息</Text>
|
|
21
|
+
</Box>
|
|
22
|
+
<Box>
|
|
23
|
+
{skills.length > 0 && (
|
|
24
|
+
<Text dimColor> 技能: {activeSkills}{moreSkills} </Text>
|
|
25
|
+
)}
|
|
26
|
+
</Box>
|
|
27
|
+
</Box>
|
|
28
|
+
</Box>
|
|
29
|
+
);
|
|
30
|
+
});
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
/** @jsxImportSource react */
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import { Box, Text } from 'ink';
|
|
4
|
-
|
|
5
|
-
interface Props {
|
|
6
|
-
active?: boolean;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function Thinking({ active = false }: Props) {
|
|
10
|
-
if (!active) return null;
|
|
11
|
-
|
|
12
|
-
return (
|
|
13
|
-
<Box paddingLeft={1}>
|
|
14
|
-
<Text color="#06B6D4">⟳ Thinking...</Text>
|
|
15
|
-
</Box>
|
|
16
|
-
);
|
|
17
|
-
}
|
|
1
|
+
/** @jsxImportSource react */
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
active?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function Thinking({ active = false }: Props) {
|
|
10
|
+
if (!active) return null;
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<Box paddingLeft={1}>
|
|
14
|
+
<Text color="#06B6D4">⟳ Thinking...</Text>
|
|
15
|
+
</Box>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -1,71 +1,102 @@
|
|
|
1
|
-
/** @jsxImportSource react */
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import { Box, Text } from 'ink';
|
|
4
|
-
|
|
5
|
-
interface ToolCallData {
|
|
6
|
-
name: string;
|
|
7
|
-
args: string;
|
|
8
|
-
result?: string;
|
|
9
|
-
isError?: boolean;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface Props {
|
|
13
|
-
tool: ToolCallData;
|
|
14
|
-
width?: number;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function formatToolName(name: string, args: string): string {
|
|
18
|
-
try {
|
|
19
|
-
const obj = JSON.parse(args);
|
|
20
|
-
switch (name) {
|
|
21
|
-
case 'file': {
|
|
22
|
-
const action = obj.action || '';
|
|
23
|
-
const path = (obj.path || '').replace(/.*[/\\]/, '');
|
|
24
|
-
return `${action} ${path || obj.path || ''}`;
|
|
25
|
-
}
|
|
26
|
-
case 'shell': return obj.command?.slice(0, 60) || '';
|
|
27
|
-
case 'git': return obj.command || '';
|
|
28
|
-
case 'search': return obj.query || obj.pattern || '';
|
|
29
|
-
case 'nwt': return obj.action || '';
|
|
30
|
-
default: return Object.keys(obj).slice(0, 2).join(', ');
|
|
31
|
-
}
|
|
32
|
-
} catch {
|
|
33
|
-
return '';
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function truncateOutput(output: string, maxLines = 8): { text: string; truncated: boolean } {
|
|
38
|
-
const lines = output.split('\n');
|
|
39
|
-
if (lines.length <= maxLines) return { text: output, truncated: false };
|
|
40
|
-
return { text: lines.slice(0, maxLines).join('\n'), truncated: true };
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function ToolCall({ tool }: Props) {
|
|
44
|
-
const label = formatToolName(tool.name, tool.args);
|
|
45
|
-
const result = tool.result ? truncateOutput(tool.result) : null;
|
|
46
|
-
|
|
47
|
-
return (
|
|
48
|
-
<Box flexDirection="column" marginBottom={0} paddingLeft={1}>
|
|
49
|
-
{/* Tool header */}
|
|
50
|
-
<Box>
|
|
51
|
-
<Text color="#06B6D4" bold>⚙ </Text>
|
|
52
|
-
<Text color="#06B6D4">{tool.name}</Text>
|
|
53
|
-
{label && <Text color="#64748B"> {label}</Text>}
|
|
54
|
-
</Box>
|
|
55
|
-
|
|
56
|
-
{/* Result
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
1
|
+
/** @jsxImportSource react */
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
|
|
5
|
+
interface ToolCallData {
|
|
6
|
+
name: string;
|
|
7
|
+
args: string;
|
|
8
|
+
result?: string;
|
|
9
|
+
isError?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
tool: ToolCallData;
|
|
14
|
+
width?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function formatToolName(name: string, args: string): string {
|
|
18
|
+
try {
|
|
19
|
+
const obj = JSON.parse(args);
|
|
20
|
+
switch (name) {
|
|
21
|
+
case 'file': {
|
|
22
|
+
const action = obj.action || '';
|
|
23
|
+
const path = (obj.path || '').replace(/.*[/\\]/, '');
|
|
24
|
+
return `${action} ${path || obj.path || ''}`;
|
|
25
|
+
}
|
|
26
|
+
case 'shell': return obj.command?.slice(0, 60) || '';
|
|
27
|
+
case 'git': return obj.command || '';
|
|
28
|
+
case 'search': return obj.query || obj.pattern || '';
|
|
29
|
+
case 'nwt': return obj.action || '';
|
|
30
|
+
default: return Object.keys(obj).slice(0, 2).join(', ');
|
|
31
|
+
}
|
|
32
|
+
} catch {
|
|
33
|
+
return '';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function truncateOutput(output: string, maxLines = 8): { text: string; truncated: boolean } {
|
|
38
|
+
const lines = output.split('\n');
|
|
39
|
+
if (lines.length <= maxLines) return { text: output, truncated: false };
|
|
40
|
+
return { text: lines.slice(0, maxLines).join('\n'), truncated: true };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function ToolCall({ tool, width }: Props) {
|
|
44
|
+
const label = formatToolName(tool.name, tool.args);
|
|
45
|
+
const result = tool.result !== undefined ? truncateOutput(tool.result) : null;
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<Box flexDirection="column" marginBottom={0} paddingLeft={1}>
|
|
49
|
+
{/* Tool header */}
|
|
50
|
+
<Box>
|
|
51
|
+
<Text color="#06B6D4" bold>⚙ </Text>
|
|
52
|
+
<Text color="#06B6D4">{tool.name}</Text>
|
|
53
|
+
{label && <Text color="#64748B"> {label}</Text>}
|
|
54
|
+
</Box>
|
|
55
|
+
|
|
56
|
+
{/* Result — v2.2.6 fix:
|
|
57
|
+
- Use `!== undefined` (not truthy) so empty string results
|
|
58
|
+
still render (truthy check dropped them, making it look
|
|
59
|
+
like the tool result was missing).
|
|
60
|
+
- Switch wrap from "truncate" to "wrap" so long lines don't
|
|
61
|
+
silently disappear off-screen.
|
|
62
|
+
- Always render the Box even when result is empty (just
|
|
63
|
+
with a "(no output)" marker), so the visual frame stays
|
|
64
|
+
consistent.
|
|
65
|
+
- If truncated, show explicit "(+N more lines)" indicator
|
|
66
|
+
so users know the result was clipped. */}
|
|
67
|
+
{result !== null && (
|
|
68
|
+
<Box paddingLeft={2} flexDirection="column">
|
|
69
|
+
{result.text.length === 0 ? (
|
|
70
|
+
<Text color="#94A3B8" dimColor>(no output)</Text>
|
|
71
|
+
) : (
|
|
72
|
+
<>
|
|
73
|
+
{result.text.split('\n').map((line, i) => (
|
|
74
|
+
<Text
|
|
75
|
+
key={i}
|
|
76
|
+
color={tool.isError ? '#EF4444' : '#64748B'}
|
|
77
|
+
wrap="wrap"
|
|
78
|
+
>
|
|
79
|
+
{line || ' '}
|
|
80
|
+
</Text>
|
|
81
|
+
))}
|
|
82
|
+
{result.truncated && (
|
|
83
|
+
<Text color="#94A3B8" dimColor>
|
|
84
|
+
{' '}(+{tool.result!.split('\n').length - 8} more lines)
|
|
85
|
+
</Text>
|
|
86
|
+
)}
|
|
87
|
+
</>
|
|
88
|
+
)}
|
|
89
|
+
</Box>
|
|
90
|
+
)}
|
|
91
|
+
{/* v2.2.6: if result is undefined (still running), show a
|
|
92
|
+
pending indicator so the user knows the tool is in flight. */}
|
|
93
|
+
{result === null && tool.result === undefined && (
|
|
94
|
+
<Box paddingLeft={2}>
|
|
95
|
+
<Text color="#94A3B8" dimColor> ⏳ running...</Text>
|
|
96
|
+
</Box>
|
|
97
|
+
)}
|
|
98
|
+
</Box>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export type { ToolCallData };
|