thatgfsj-code 3.0.3 → 3.0.6

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.
Files changed (212) hide show
  1. package/README.md +64 -21
  2. package/dist/app/index.d.ts +93 -22
  3. package/dist/app/index.d.ts.map +1 -1
  4. package/dist/app/index.js +232 -40
  5. package/dist/app/index.js.map +1 -1
  6. package/dist/cache/smartModel.d.ts +29 -22
  7. package/dist/cache/smartModel.d.ts.map +1 -1
  8. package/dist/cache/smartModel.js +33 -40
  9. package/dist/cache/smartModel.js.map +1 -1
  10. package/dist/cmd/index.d.ts +12 -20
  11. package/dist/cmd/index.d.ts.map +1 -1
  12. package/dist/cmd/index.js +192 -146
  13. package/dist/cmd/index.js.map +1 -1
  14. package/dist/config/index.d.ts +4 -0
  15. package/dist/config/index.d.ts.map +1 -1
  16. package/dist/config/index.js +9 -6
  17. package/dist/config/index.js.map +1 -1
  18. package/dist/config/types.d.ts +5 -0
  19. package/dist/config/types.d.ts.map +1 -1
  20. package/dist/llm/anthropic.d.ts +1 -1
  21. package/dist/llm/anthropic.d.ts.map +1 -1
  22. package/dist/llm/anthropic.js +116 -84
  23. package/dist/llm/anthropic.js.map +1 -1
  24. package/dist/llm/gemini.d.ts +27 -3
  25. package/dist/llm/gemini.d.ts.map +1 -1
  26. package/dist/llm/gemini.js +196 -73
  27. package/dist/llm/gemini.js.map +1 -1
  28. package/dist/llm/index.d.ts +20 -1
  29. package/dist/llm/index.d.ts.map +1 -1
  30. package/dist/llm/index.js +71 -10
  31. package/dist/llm/index.js.map +1 -1
  32. package/dist/llm/openai.d.ts +5 -1
  33. package/dist/llm/openai.d.ts.map +1 -1
  34. package/dist/llm/openai.js +96 -56
  35. package/dist/llm/openai.js.map +1 -1
  36. package/dist/mcp/client.d.ts +84 -34
  37. package/dist/mcp/client.d.ts.map +1 -1
  38. package/dist/mcp/client.js +291 -126
  39. package/dist/mcp/client.js.map +1 -1
  40. package/dist/session/compactor.d.ts +27 -7
  41. package/dist/session/compactor.d.ts.map +1 -1
  42. package/dist/session/compactor.js +78 -23
  43. package/dist/session/compactor.js.map +1 -1
  44. package/dist/session/index.d.ts +96 -33
  45. package/dist/session/index.d.ts.map +1 -1
  46. package/dist/session/index.js +278 -60
  47. package/dist/session/index.js.map +1 -1
  48. package/dist/tools/file.d.ts +10 -2
  49. package/dist/tools/file.d.ts.map +1 -1
  50. package/dist/tools/file.js +54 -5
  51. package/dist/tools/file.js.map +1 -1
  52. package/dist/tools/git.d.ts +16 -2
  53. package/dist/tools/git.d.ts.map +1 -1
  54. package/dist/tools/git.js +61 -18
  55. package/dist/tools/git.js.map +1 -1
  56. package/dist/tools/nwt.d.ts.map +1 -1
  57. package/dist/tools/nwt.js +8 -1
  58. package/dist/tools/nwt.js.map +1 -1
  59. package/dist/tools/search.d.ts +8 -1
  60. package/dist/tools/search.d.ts.map +1 -1
  61. package/dist/tools/search.js +105 -34
  62. package/dist/tools/search.js.map +1 -1
  63. package/dist/tools/shell.d.ts +0 -4
  64. package/dist/tools/shell.d.ts.map +1 -1
  65. package/dist/tools/shell.js +11 -26
  66. package/dist/tools/shell.js.map +1 -1
  67. package/dist/tools/types.d.ts +7 -0
  68. package/dist/tools/types.d.ts.map +1 -1
  69. package/dist/tools/types.js.map +1 -1
  70. package/dist/tui/app.d.ts.map +1 -1
  71. package/dist/tui/app.js +90 -18
  72. package/dist/tui/app.js.map +1 -1
  73. package/dist/tui/components/ChatMessage.d.ts.map +1 -1
  74. package/dist/tui/components/ChatMessage.js +9 -2
  75. package/dist/tui/components/ChatMessage.js.map +1 -1
  76. package/dist/tui/components/ConfirmPrompt.d.ts +21 -0
  77. package/dist/tui/components/ConfirmPrompt.d.ts.map +1 -0
  78. package/dist/tui/components/ConfirmPrompt.js +45 -0
  79. package/dist/tui/components/ConfirmPrompt.js.map +1 -0
  80. package/dist/tui/components/Header.d.ts +10 -5
  81. package/dist/tui/components/Header.d.ts.map +1 -1
  82. package/dist/tui/components/Header.js +13 -5
  83. package/dist/tui/components/Header.js.map +1 -1
  84. package/dist/tui/components/InitWizard.d.ts.map +1 -1
  85. package/dist/tui/components/InitWizard.js +8 -13
  86. package/dist/tui/components/InitWizard.js.map +1 -1
  87. package/dist/tui/components/StatusBar.d.ts +7 -0
  88. package/dist/tui/components/StatusBar.d.ts.map +1 -1
  89. package/dist/tui/components/StatusBar.js +8 -3
  90. package/dist/tui/components/StatusBar.js.map +1 -1
  91. package/dist/tui/components/Thinking.d.ts +5 -0
  92. package/dist/tui/components/Thinking.d.ts.map +1 -1
  93. package/dist/tui/components/Thinking.js +24 -2
  94. package/dist/tui/components/Thinking.js.map +1 -1
  95. package/dist/tui/components/ToolCall.d.ts +6 -1
  96. package/dist/tui/components/ToolCall.d.ts.map +1 -1
  97. package/dist/tui/components/ToolCall.js +50 -22
  98. package/dist/tui/components/ToolCall.js.map +1 -1
  99. package/dist/tui/components/UserInput.d.ts +5 -0
  100. package/dist/tui/components/UserInput.d.ts.map +1 -1
  101. package/dist/tui/components/UserInput.js +9 -13
  102. package/dist/tui/components/UserInput.js.map +1 -1
  103. package/dist/tui/hooks/useChat.d.ts +1 -0
  104. package/dist/tui/hooks/useChat.d.ts.map +1 -1
  105. package/dist/tui/hooks/useChat.js +36 -16
  106. package/dist/tui/hooks/useChat.js.map +1 -1
  107. package/dist/tui/hooks/useCommands.d.ts +7 -1
  108. package/dist/tui/hooks/useCommands.d.ts.map +1 -1
  109. package/dist/tui/hooks/useCommands.js +83 -49
  110. package/dist/tui/hooks/useCommands.js.map +1 -1
  111. package/dist/tui/theme.d.ts +34 -0
  112. package/dist/tui/theme.d.ts.map +1 -0
  113. package/dist/tui/theme.js +34 -0
  114. package/dist/tui/theme.js.map +1 -0
  115. package/dist/tui/welcome.d.ts.map +1 -1
  116. package/dist/tui/welcome.js +9 -7
  117. package/dist/tui/welcome.js.map +1 -1
  118. package/dist/types.d.ts +16 -1
  119. package/dist/types.d.ts.map +1 -1
  120. package/dist/utils/diff.d.ts +5 -0
  121. package/dist/utils/diff.d.ts.map +1 -1
  122. package/dist/utils/diff.js +15 -0
  123. package/dist/utils/diff.js.map +1 -1
  124. package/dist/version.d.ts +12 -0
  125. package/dist/version.d.ts.map +1 -0
  126. package/dist/version.js +29 -0
  127. package/dist/version.js.map +1 -0
  128. package/package.json +19 -7
  129. package/CHANGELOG.md +0 -192
  130. package/DEVELOPMENT.md +0 -286
  131. package/ROADMAP.md +0 -91
  132. package/docs/API_KEY_GUIDE.md +0 -236
  133. package/docs/FAQ.md +0 -182
  134. package/install.bat +0 -63
  135. package/install.ps1 +0 -238
  136. package/install.sh +0 -113
  137. package/src/app/index.ts +0 -197
  138. package/src/cache/fingerprint.ts +0 -101
  139. package/src/cache/index.ts +0 -21
  140. package/src/cache/smartModel.ts +0 -145
  141. package/src/cache/stats.ts +0 -199
  142. package/src/cache/volatile.ts +0 -47
  143. package/src/cmd/index.tsx +0 -288
  144. package/src/config/index.ts +0 -158
  145. package/src/config/providers.ts +0 -234
  146. package/src/config/types.ts +0 -71
  147. package/src/hooks/index.ts +0 -111
  148. package/src/llm/anthropic.ts +0 -413
  149. package/src/llm/gemini.ts +0 -169
  150. package/src/llm/index.ts +0 -302
  151. package/src/llm/openai.ts +0 -243
  152. package/src/llm/provider.ts +0 -71
  153. package/src/mcp/client.ts +0 -330
  154. package/src/prompts/index.ts +0 -260
  155. package/src/session/compactor.ts +0 -103
  156. package/src/session/index.ts +0 -181
  157. package/src/session/message.ts +0 -42
  158. package/src/skills/brainstorming.ts +0 -43
  159. package/src/skills/code-review.ts +0 -45
  160. package/src/skills/executing-plans.ts +0 -27
  161. package/src/skills/frontend-design.ts +0 -35
  162. package/src/skills/git-workflow.ts +0 -36
  163. package/src/skills/improve-architecture.ts +0 -38
  164. package/src/skills/index.ts +0 -136
  165. package/src/skills/neuroweave.ts +0 -47
  166. package/src/skills/playwright.ts +0 -72
  167. package/src/skills/prototype.ts +0 -30
  168. package/src/skills/subagent.ts +0 -28
  169. package/src/skills/supabase.ts +0 -44
  170. package/src/skills/systematic-debugging.ts +0 -44
  171. package/src/skills/tdd.ts +0 -39
  172. package/src/skills/triage.ts +0 -35
  173. package/src/skills/verification.ts +0 -31
  174. package/src/skills/writing-plans.ts +0 -42
  175. package/src/tools/file.ts +0 -143
  176. package/src/tools/git.ts +0 -132
  177. package/src/tools/index.ts +0 -111
  178. package/src/tools/nwt.ts +0 -598
  179. package/src/tools/search.ts +0 -263
  180. package/src/tools/shell.ts +0 -136
  181. package/src/tools/types.ts +0 -122
  182. package/src/tui/app.tsx +0 -159
  183. package/src/tui/components/ChatList.tsx +0 -41
  184. package/src/tui/components/ChatMessage.tsx +0 -54
  185. package/src/tui/components/Header.tsx +0 -66
  186. package/src/tui/components/InitWizard.tsx +0 -251
  187. package/src/tui/components/Markdown.tsx +0 -35
  188. package/src/tui/components/ModelSelector.tsx +0 -107
  189. package/src/tui/components/StatusBar.tsx +0 -30
  190. package/src/tui/components/Thinking.tsx +0 -17
  191. package/src/tui/components/ToolCall.tsx +0 -102
  192. package/src/tui/components/UserInput.tsx +0 -151
  193. package/src/tui/hooks/useChat.ts +0 -287
  194. package/src/tui/hooks/useCommands.ts +0 -273
  195. package/src/tui/index.ts +0 -6
  196. package/src/tui/welcome.ts +0 -177
  197. package/src/types.ts +0 -104
  198. package/src/utils/diff.ts +0 -71
  199. package/src/utils/project.ts +0 -99
  200. package/src/utils/stableStringify.ts +0 -47
  201. package/src/utils/thinking.ts +0 -119
  202. package/tests/cache/fingerprint.test.ts +0 -75
  203. package/tests/cache/providerCatalog.test.ts +0 -31
  204. package/tests/cache/smartModel.test.ts +0 -112
  205. package/tests/cache/stableStringify.test.ts +0 -43
  206. package/tests/cache/stats.test.ts +0 -146
  207. package/tests/cache/volatile.test.ts +0 -75
  208. package/tests/smoke-pollution.mjs +0 -78
  209. package/tests/smoke-thinking.mjs +0 -110
  210. package/tests/smoke-tool-stream.mjs +0 -69
  211. package/tests/smoke-tool.mjs +0 -117
  212. package/tsconfig.json +0 -21
@@ -1,263 +0,0 @@
1
- /**
2
- * Search Tool - Code search and file operations
3
- */
4
-
5
- import type { Tool, ToolResult } from './types.js';
6
- import { exec } from 'child_process';
7
- import { promisify } from 'util';
8
- import { readdirSync, statSync, readFileSync } from 'fs';
9
- import { join, relative } from 'path';
10
-
11
- const execAsync = promisify(exec);
12
-
13
- export class SearchTool implements Tool {
14
- name = 'search';
15
- description = 'Search and find: grep, find files, list directory tree';
16
-
17
- parameters = [
18
- { name: 'action', type: 'string', description: 'Action: grep, find, tree, files', required: true },
19
- { name: 'pattern', type: 'string', description: 'Search pattern or file pattern', required: false },
20
- { name: 'path', type: 'string', description: 'Directory to search in', required: false },
21
- { name: 'options', type: 'string', description: 'Additional options', required: false }
22
- ];
23
-
24
- async execute(params: Record<string, any>): Promise<ToolResult> {
25
- const { action, pattern, path, options } = params;
26
- const workDir = path || process.cwd();
27
-
28
- try {
29
- switch (action) {
30
- case 'grep':
31
- case 'search':
32
- return await this.grep(pattern || '', workDir, options || '');
33
-
34
- case 'find':
35
- return await this.find(pattern || '*', workDir);
36
-
37
- case 'tree':
38
- return await this.tree(workDir, parseInt(options) || 3);
39
-
40
- case 'files':
41
- return await this.listFiles(workDir, pattern || '*');
42
-
43
- default:
44
- return { success: false, error: `Unknown action: ${action}` };
45
- }
46
- } catch (error: any) {
47
- return { success: false, error: error.message };
48
- }
49
- }
50
-
51
- /**
52
- * Grep - search for pattern in files
53
- */
54
- private async grep(pattern: string, path: string, options: string): Promise<ToolResult> {
55
- if (!pattern) {
56
- return { success: false, error: 'Pattern required' };
57
- }
58
-
59
- // Build grep command
60
- let cmd = `grep -rn "${pattern}" "${path}"`;
61
-
62
- if (options?.includes('i')) cmd += ' -i'; // Case insensitive
63
- if (options?.includes('w')) cmd += ' -w'; // Whole word
64
- if (options?.includes('l')) cmd += ' -l'; // Files only
65
- if (options?.includes('n')) cmd += ' -n'; // Line numbers
66
-
67
- cmd += ' --color=never';
68
-
69
- try {
70
- const { stdout, stderr } = await execAsync(cmd, { timeout: 30000 });
71
-
72
- if (!stdout && stderr) {
73
- return { success: false, error: stderr };
74
- }
75
-
76
- const lines = (stdout || '').split('\n').filter(l => l.trim());
77
-
78
- if (lines.length === 0) {
79
- return { success: true, output: 'No matches found' };
80
- }
81
-
82
- // Limit output
83
- const limited = lines.slice(0, 50);
84
- const output = limited.join('\n');
85
-
86
- return {
87
- success: true,
88
- output: lines.length > 50
89
- ? output + `\n... and ${lines.length - 50} more matches`
90
- : output
91
- };
92
-
93
- } catch (error: any) {
94
- if (error.killed) {
95
- return { success: false, error: 'Search timed out (>30s)' };
96
- }
97
- return { success: false, error: error.message };
98
- }
99
- }
100
-
101
- /**
102
- * Find - find files by pattern
103
- */
104
- private async find(pattern: string, path: string): Promise<ToolResult> {
105
- if (!pattern) {
106
- pattern = '*';
107
- }
108
-
109
- const results: string[] = [];
110
-
111
- const search = (dir: string, depth: number) => {
112
- if (depth > 5) return; // Max depth
113
-
114
- try {
115
- const items = readdirSync(dir);
116
-
117
- for (const item of items) {
118
- // Skip common ignored directories
119
- if (item === 'node_modules' || item === '.git' || item === 'dist' || item === 'build') {
120
- continue;
121
- }
122
-
123
- const fullPath = join(dir, item);
124
-
125
- try {
126
- const stat = statSync(fullPath);
127
-
128
- if (stat.isDirectory()) {
129
- // Check if matches pattern
130
- if (this.matchPattern(item, pattern)) {
131
- results.push(fullPath);
132
- }
133
- search(fullPath, depth + 1);
134
- } else if (stat.isFile()) {
135
- if (this.matchPattern(item, pattern)) {
136
- results.push(fullPath);
137
- }
138
- }
139
- } catch {
140
- // Skip inaccessible files
141
- }
142
- }
143
- } catch {
144
- // Skip inaccessible directories
145
- }
146
- };
147
-
148
- search(path, 0);
149
-
150
- if (results.length === 0) {
151
- return { success: true, output: 'No files found' };
152
- }
153
-
154
- const limited = results.slice(0, 100);
155
- const output = limited.join('\n');
156
-
157
- return {
158
- success: true,
159
- output: results.length > 100
160
- ? output + `\n... and ${results.length - 100} more files`
161
- : output
162
- };
163
- }
164
-
165
- /**
166
- * Tree - show directory structure
167
- */
168
- private async tree(path: string, maxDepth: number): Promise<ToolResult> {
169
- const lines: string[] = [];
170
-
171
- const walk = (dir: string, prefix: string, depth: number) => {
172
- if (depth > maxDepth) return;
173
-
174
- try {
175
- const items = readdirSync(dir).filter(i =>
176
- !i.startsWith('.') && i !== 'node_modules' && i !== 'dist'
177
- );
178
-
179
- for (let i = 0; i < items.length; i++) {
180
- const item = items[i];
181
- const fullPath = join(dir, item);
182
- const isLast = i === items.length - 1;
183
- const connector = isLast ? '└── ' : '├── ';
184
-
185
- lines.push(prefix + connector + item);
186
-
187
- try {
188
- const stat = statSync(fullPath);
189
- if (stat.isDirectory()) {
190
- const newPrefix = prefix + (isLast ? ' ' : '│ ');
191
- walk(fullPath, newPrefix, depth + 1);
192
- }
193
- } catch {
194
- // Skip
195
- }
196
- }
197
- } catch {
198
- // Skip inaccessible
199
- }
200
- };
201
-
202
- lines.push(relative(process.cwd(), path) || '.');
203
- walk(path, '', 0);
204
-
205
- return { success: true, output: lines.join('\n') };
206
- }
207
-
208
- /**
209
- * List files in directory
210
- */
211
- private async listFiles(path: string, pattern: string): Promise<ToolResult> {
212
- const files: string[] = [];
213
-
214
- const scan = (dir: string) => {
215
- try {
216
- const items = readdirSync(dir);
217
-
218
- for (const item of items) {
219
- if (item === 'node_modules' || item === '.git') continue;
220
-
221
- const fullPath = join(dir, item);
222
-
223
- try {
224
- const stat = statSync(fullPath);
225
- if (stat.isFile() && this.matchPattern(item, pattern)) {
226
- files.push(relative(process.cwd(), fullPath));
227
- }
228
- } catch {
229
- // Skip
230
- }
231
- }
232
- } catch {
233
- // Skip
234
- }
235
- };
236
-
237
- scan(path);
238
-
239
- if (files.length === 0) {
240
- return { success: true, output: 'No files found' };
241
- }
242
-
243
- return { success: true, output: files.join('\n') };
244
- }
245
-
246
- /**
247
- * Simple glob-like pattern matching
248
- */
249
- private matchPattern(filename: string, pattern: string): boolean {
250
- if (pattern === '*') return true;
251
-
252
- // Convert glob to regex
253
- const regex = new RegExp(
254
- '^' + pattern
255
- .replace(/\./g, '\\.')
256
- .replace(/\*/g, '.*')
257
- .replace(/\?/g, '.') + '$',
258
- 'i'
259
- );
260
-
261
- return regex.test(filename);
262
- }
263
- }
@@ -1,136 +0,0 @@
1
- /**
2
- * Shell Tool - Execute shell commands with security checks
3
- * Migrated from old src/tools/shell.ts (permission logic inlined)
4
- */
5
-
6
- import type { Tool, ToolResult, ToolContext } from './types.js';
7
- import { exec } from 'child_process';
8
- import { promisify } from 'util';
9
-
10
- const execAsync = promisify(exec);
11
-
12
- // Dangerous command patterns - blocked immediately
13
- const DANGEROUS_PATTERNS = [
14
- /^rm\s+-rf\s+\//i,
15
- /^del\s+\/f\s+\/s\s+\/q/i,
16
- /^format\s+[a-z]:/i,
17
- /^mkfs/i,
18
- /^dd\s+if=/i,
19
- /^shred/i,
20
- /^cat\s+\/dev\/null\s*>/i,
21
- />\s*\/dev\/sda/i,
22
- /^rm\s+-rf\s+\$HOME/i,
23
- /^rm\s+-rf\s+%USERPROFILE%/i,
24
- /^curl\s+.*\|.*sh/i,
25
- /^wget\s+.*\|.*sh/i,
26
- /^eval\s+/i,
27
- /base64\s+-d\s+.*\|/i,
28
- ];
29
-
30
- // Commands that need user confirmation
31
- const CONFIRM_REQUIRED = [
32
- 'rm -rf',
33
- 'rmdir',
34
- 'del /s /q',
35
- 'format',
36
- 'mkfs',
37
- 'dd',
38
- 'shutdown',
39
- 'reboot',
40
- 'pkill',
41
- 'killall',
42
- 'git push --force',
43
- 'git push -f',
44
- ];
45
-
46
- export class ShellTool implements Tool {
47
- name = 'shell';
48
- description = 'Execute shell commands and scripts. Some commands require user confirmation.';
49
-
50
- inputSchema = {
51
- type: 'object' as const,
52
- properties: {
53
- command: { type: 'string', description: 'Shell command to execute' },
54
- cwd: { type: 'string', description: 'Working directory' },
55
- timeout: { type: 'number', description: 'Timeout in seconds', default: 30 },
56
- },
57
- required: ['command'],
58
- };
59
-
60
- metadata = {
61
- permissions: ['execute', 'write', 'network'] as ('read' | 'write' | 'execute' | 'network')[],
62
- tags: ['shell', 'system', 'dangerous'],
63
- maxDuration: 120000,
64
- version: '1.0.0',
65
- };
66
-
67
- parameters = [
68
- { name: 'command', type: 'string', description: 'Shell command to execute', required: true },
69
- { name: 'cwd', type: 'string', description: 'Working directory', required: false },
70
- { name: 'timeout', type: 'number', description: 'Timeout in seconds', required: false },
71
- ];
72
-
73
- /**
74
- * Check if command matches dangerous patterns
75
- */
76
- private isDangerous(command: string): boolean {
77
- return DANGEROUS_PATTERNS.some(pattern => pattern.test(command.trim()));
78
- }
79
-
80
- /**
81
- * Check if command needs user confirmation
82
- */
83
- private needsConfirmation(command: string): boolean {
84
- const lower = command.toLowerCase();
85
- return CONFIRM_REQUIRED.some(cmd => lower.includes(cmd.toLowerCase()));
86
- }
87
-
88
- async execute(params: Record<string, any>, ctx?: ToolContext): Promise<ToolResult> {
89
- const { command, cwd, timeout = 30 } = params;
90
-
91
- if (!command || typeof command !== 'string') {
92
- return { success: false, error: 'Command is required and must be a string' };
93
- }
94
-
95
- const trimmed = command.trim();
96
-
97
- // Block dangerous commands
98
- if (this.isDangerous(trimmed)) {
99
- return { success: false, error: `Blocked: dangerous command "${trimmed.substring(0, 50)}..."` };
100
- }
101
-
102
- if (trimmed.length > 10000) {
103
- return { success: false, error: 'Command too long (max 10000 characters)' };
104
- }
105
-
106
- // Ask for confirmation on risky commands
107
- if (ctx?.confirmAction && this.needsConfirmation(trimmed)) {
108
- const confirmed = await ctx.confirmAction(
109
- `⚠️ Confirm command:\n ${command}\n\n[y] Yes [n] No`
110
- );
111
- if (!confirmed) {
112
- return { success: false, error: 'Command cancelled by user' };
113
- }
114
- }
115
-
116
- try {
117
- const options: any = {
118
- timeout: timeout * 1000,
119
- maxBuffer: 10 * 1024 * 1024, // 10MB
120
- };
121
- if (cwd) options.cwd = cwd;
122
-
123
- const { stdout, stderr } = await execAsync(command, options);
124
- const stdoutStr = stdout?.toString() || '';
125
- const stderrStr = stderr?.toString() || '';
126
- const output = stdoutStr + (stderrStr ? `\n[stderr]: ${stderrStr}` : '');
127
-
128
- return { success: true, output: output.trim() || '(command executed successfully with no output)' };
129
- } catch (error: any) {
130
- if (error.killed) {
131
- return { success: false, error: 'Command timed out' };
132
- }
133
- return { success: false, error: error.message || 'Command execution failed' };
134
- }
135
- }
136
- }
@@ -1,122 +0,0 @@
1
- /**
2
- * Tool interface definitions
3
- * Extracted from old src/core/types.ts
4
- */
5
-
6
- // ==================== Tool Parameter & Schema ====================
7
-
8
- export interface ToolParameter {
9
- name: string;
10
- type: string;
11
- description: string;
12
- required: boolean;
13
- default?: any;
14
- enum?: string[];
15
- }
16
-
17
- export interface ToolInputSchema {
18
- type: 'object';
19
- properties: Record<string, {
20
- type: string;
21
- description: string;
22
- default?: any;
23
- enum?: string[];
24
- }>;
25
- required?: string[];
26
- }
27
-
28
- export interface ToolMetadata {
29
- permissions?: ('read' | 'write' | 'execute' | 'network')[];
30
- maxDuration?: number;
31
- tags?: string[];
32
- version?: string;
33
- deprecated?: string;
34
- }
35
-
36
- // ==================== Tool Interface ====================
37
-
38
- export interface ToolContext {
39
- sessionId?: string;
40
- workingDirectory?: string;
41
- confirmAction?: (msg: string) => Promise<boolean>;
42
- signal?: AbortSignal;
43
- toolCallId?: string;
44
- }
45
-
46
- export interface ToolResult {
47
- success: boolean;
48
- output?: string;
49
- error?: string;
50
- data?: any;
51
- }
52
-
53
- export interface Tool {
54
- name: string;
55
- description: string;
56
- parameters: ToolParameter[];
57
- inputSchema?: ToolInputSchema;
58
- metadata?: ToolMetadata;
59
- execute(params: Record<string, any>, ctx?: ToolContext): Promise<ToolResult>;
60
- }
61
-
62
- // ==================== Tool Builder ====================
63
-
64
- export class ToolBuilder {
65
- private _name = '';
66
- private _description = '';
67
- private _parameters: ToolParameter[] = [];
68
- private _inputSchema: ToolInputSchema = { type: 'object', properties: {} };
69
- private _metadata: ToolMetadata = {};
70
- private _fn?: (params: Record<string, any>, ctx: ToolContext) => Promise<ToolResult>;
71
-
72
- name(n: string): this { this._name = n; return this; }
73
- description(d: string): this { this._description = d; return this; }
74
-
75
- param(p: ToolParameter): this {
76
- this._parameters.push(p);
77
- this._inputSchema.properties[p.name] = {
78
- type: p.type,
79
- description: p.description,
80
- default: p.default,
81
- enum: p.enum,
82
- };
83
- return this;
84
- }
85
-
86
- stringParam(name: string, description: string, required = true, defaultVal?: string): this {
87
- return this.param({ name, type: 'string', description, required, default: defaultVal });
88
- }
89
-
90
- booleanParam(name: string, description: string, required = false, defaultVal?: boolean): this {
91
- return this.param({ name, type: 'boolean', description, required, default: defaultVal });
92
- }
93
-
94
- permissions(...perms: ('read' | 'write' | 'execute' | 'network')[]): this {
95
- this._metadata.permissions = perms;
96
- return this;
97
- }
98
-
99
- tags(...t: string[]): this {
100
- this._metadata.tags = t;
101
- return this;
102
- }
103
-
104
- handle(fn: (params: Record<string, any>, ctx: ToolContext) => Promise<ToolResult>): this {
105
- this._fn = fn;
106
- return this;
107
- }
108
-
109
- build(): Tool {
110
- if (!this._name || !this._fn) {
111
- throw new Error('Tool name and handler are required');
112
- }
113
- return {
114
- name: this._name,
115
- description: this._description,
116
- parameters: this._parameters,
117
- inputSchema: this._inputSchema,
118
- metadata: this._metadata,
119
- execute: this._fn,
120
- };
121
- }
122
- }
package/src/tui/app.tsx DELETED
@@ -1,159 +0,0 @@
1
- /** @jsxImportSource react */
2
- import React, { useState, useCallback, useEffect } from 'react';
3
- import { Box, Text, useStdout } from 'ink';
4
- import { Header } from './components/Header.js';
5
- import { ChatList } from './components/ChatList.js';
6
- import { Thinking } from './components/Thinking.js';
7
- import { UserInput } from './components/UserInput.js';
8
- import { StatusBar } from './components/StatusBar.js';
9
- import { ModelSelector } from './components/ModelSelector.js';
10
- import { InitWizard } from './components/InitWizard.js';
11
- import { useChat } from './hooks/useChat.js';
12
- import { useCommands } from './hooks/useCommands.js';
13
- import type { App } from '../app/index.js';
14
- import type { MessageData } from './components/ChatMessage.js';
15
- import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'fs';
16
- import { join } from 'path';
17
- import { homedir } from 'os';
18
-
19
- interface Props {
20
- app: App;
21
- }
22
-
23
- function saveModelToHistory(model: string) {
24
- const dir = join(homedir(), '.thatgfsj');
25
- const path = join(dir, 'models.json');
26
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
27
- let history: string[] = [];
28
- if (existsSync(path)) {
29
- try { history = JSON.parse(readFileSync(path, 'utf-8')); } catch {}
30
- }
31
- if (!history.includes(model)) {
32
- history.push(model);
33
- writeFileSync(path, JSON.stringify(history, null, 2));
34
- }
35
- }
36
-
37
- type ViewMode = 'chat' | 'model_select' | 'init_wizard';
38
-
39
- export function TuiApp({ app }: Props) {
40
- const { messages, isThinking, streaming, streamingToolCalls, queuedMessage, sendMessage, cancel } = useChat(app);
41
- const { handleCommand } = useCommands(app);
42
- const [systemMessages, setSystemMessages] = useState<MessageData[]>([]);
43
- const [viewMode, setViewMode] = useState<ViewMode>('chat');
44
- const { stdout } = useStdout();
45
- const terminalWidth = stdout?.columns || 80;
46
-
47
- // v3.0.0: cache stats snapshot for the Header. Re-read whenever messages
48
- // change (i.e. a round just completed and recorded new usage). Polling on
49
- // an interval would be wasteful; React re-render is the trigger.
50
- const [cacheSnapshot, setCacheSnapshot] = useState(() => app.cacheStats.snapshot());
51
- // v3.0.3: resolved TTL (sticky per session). null until first round.
52
- const [resolvedTtl, setResolvedTtl] = useState<'5m' | '1h' | null>(app.resolvedTtl);
53
- // The user-pinned config TTL ('auto' | '5m' | '1h') — used to render the
54
- // Header chip BEFORE the first round, when resolvedTtl is still null.
55
- const configTtl = (app.config.get() as any).cache?.ttl as 'auto' | '5m' | '1h' | undefined;
56
- useEffect(() => {
57
- setCacheSnapshot(app.cacheStats.snapshot());
58
- setResolvedTtl(app.resolvedTtl);
59
- }, [messages.length]);
60
-
61
- const addMsg = useCallback((content: string) => {
62
- setSystemMessages(prev => [...prev, { role: 'assistant', content }]);
63
- }, []);
64
-
65
- const onSubmit = useCallback(async (input: string) => {
66
- // Model selector - ignore text input
67
- if (viewMode === 'model_select') return;
68
-
69
- // Normal command handling
70
- const result = handleCommand(input);
71
-
72
- if (result.handled) {
73
- if (input.trim() === '/模型' || input.trim() === '/model') {
74
- setViewMode('model_select');
75
- return;
76
- }
77
-
78
- if (result.output) {
79
- setSystemMessages(prev => [
80
- ...prev,
81
- { role: 'user', content: input },
82
- { role: 'assistant', content: result.output! },
83
- ]);
84
- }
85
-
86
- if (result.action === 'clear') setSystemMessages([]);
87
-
88
- if (result.action === 'reinit') {
89
- setViewMode('init_wizard');
90
- }
91
-
92
- return;
93
- }
94
-
95
- sendMessage(input);
96
- }, [handleCommand, sendMessage, app, viewMode, addMsg]);
97
-
98
- const allMessages = [...systemMessages, ...messages];
99
- const activeSkills = app.skills.listActive().map(s => s.id);
100
-
101
- return (
102
- <Box flexDirection="column" paddingX={1}>
103
- <Header
104
- provider={app.config.get().provider}
105
- model={app.config.get().model}
106
- cacheHitRate={cacheSnapshot.hitRate > 0 ? cacheSnapshot.hitRate : null}
107
- cacheSavingsCNY={cacheSnapshot.estimatedSavingsCNY}
108
- cacheTtl={resolvedTtl ?? configTtl ?? null}
109
- />
110
- <ChatList
111
- messages={allMessages}
112
- streaming={streaming}
113
- streamingToolCalls={streamingToolCalls}
114
- width={terminalWidth - 4}
115
- />
116
- <Thinking active={isThinking} />
117
- {queuedMessage && (
118
- <Box paddingLeft={1}>
119
- <Text color="#F59E0B">📎 已排队: </Text>
120
- <Text color="#94A3B8">{queuedMessage}</Text>
121
- </Box>
122
- )}
123
- {viewMode === 'model_select' ? (
124
- <ModelSelector
125
- currentModel={app.config.get().model}
126
- currentProvider={app.config.get().provider}
127
- onSelect={(model) => {
128
- app.config.save({ model });
129
- saveModelToHistory(model);
130
- setViewMode('chat');
131
- addMsg(`模型已切换: ${model}`);
132
- }}
133
- onAddNew={() => setViewMode('init_wizard')}
134
- />
135
- ) : viewMode === 'init_wizard' ? (
136
- // v3.0.1: InitWizard is now fully self-contained — it owns its own
137
- // input handling via useInput + ink-text-input. No external UserInput
138
- // is rendered here, and app.tsx no longer has the text-entry
139
- // viewModes (init_key / init_url / init_custom_model) because they
140
- // were the broken input-handling path that left users stuck on a
141
- // frozen "输入 API Key" screen with no way to type.
142
- <InitWizard
143
- onComplete={(provider, model, apiKey, baseUrl) => {
144
- app.config.save({ provider, model, apiKey, baseUrl });
145
- saveModelToHistory(model);
146
- setViewMode('chat');
147
- addMsg(`配置完成: ${provider} / ${model}`);
148
- }}
149
- onCancel={() => setViewMode('chat')}
150
- />
151
- ) : (
152
- <Box flexDirection="column">
153
- <UserInput onSubmit={onSubmit} onCancel={cancel} disabled={false} />
154
- </Box>
155
- )}
156
- <StatusBar messageCount={allMessages.length} skills={activeSkills} />
157
- </Box>
158
- );
159
- }