thatgfsj-code 0.4.0 → 0.5.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/dist/app/index.d.ts +6 -14
- package/dist/app/index.d.ts.map +1 -1
- package/dist/app/index.js +28 -30
- package/dist/app/index.js.map +1 -1
- package/dist/cmd/index.js +64 -8
- package/dist/cmd/index.js.map +1 -1
- package/dist/llm/anthropic.d.ts +4 -17
- package/dist/llm/anthropic.d.ts.map +1 -1
- package/dist/llm/anthropic.js +54 -12
- package/dist/llm/anthropic.js.map +1 -1
- package/dist/llm/gemini.d.ts +3 -16
- package/dist/llm/gemini.d.ts.map +1 -1
- package/dist/llm/gemini.js +26 -22
- package/dist/llm/gemini.js.map +1 -1
- package/dist/llm/index.d.ts +5 -19
- package/dist/llm/index.d.ts.map +1 -1
- package/dist/llm/index.js +86 -87
- package/dist/llm/index.js.map +1 -1
- package/dist/llm/openai.d.ts +9 -23
- package/dist/llm/openai.d.ts.map +1 -1
- package/dist/llm/openai.js +48 -14
- package/dist/llm/openai.js.map +1 -1
- package/dist/llm/provider.d.ts +11 -16
- package/dist/llm/provider.d.ts.map +1 -1
- package/dist/tui/input.d.ts +0 -1
- package/dist/tui/input.d.ts.map +1 -1
- package/dist/tui/input.js +2 -5
- package/dist/tui/input.js.map +1 -1
- package/dist/tui/output.d.ts +36 -12
- package/dist/tui/output.d.ts.map +1 -1
- package/dist/tui/output.js +172 -40
- package/dist/tui/output.js.map +1 -1
- package/dist/tui/repl.d.ts +0 -10
- package/dist/tui/repl.d.ts.map +1 -1
- package/dist/tui/repl.js +91 -43
- package/dist/tui/repl.js.map +1 -1
- package/dist/tui/welcome.d.ts +1 -11
- package/dist/tui/welcome.d.ts.map +1 -1
- package/dist/tui/welcome.js +60 -56
- package/dist/tui/welcome.js.map +1 -1
- package/package.json +1 -1
- package/src/app/index.ts +28 -33
- package/src/cmd/index.ts +53 -8
- package/src/llm/anthropic.ts +60 -14
- package/src/llm/gemini.ts +31 -24
- package/src/llm/index.ts +93 -92
- package/src/llm/openai.ts +58 -15
- package/src/llm/provider.ts +12 -16
- package/src/tui/input.ts +2 -5
- package/src/tui/output.ts +192 -40
- package/src/tui/repl.ts +94 -44
- package/src/tui/welcome.ts +62 -59
- package/src/app/agent.ts +0 -140
package/src/tui/output.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* REPL Output
|
|
3
|
-
*
|
|
2
|
+
* REPL Output - Clean terminal UI
|
|
3
|
+
* Inspired by opencode: simple indentation for streaming, clean boundaries
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import chalk from 'chalk';
|
|
@@ -9,75 +9,227 @@ import ora, { Ora } from 'ora';
|
|
|
9
9
|
export class REPLOutput {
|
|
10
10
|
private spinner: Ora | null = null;
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
// ── Banner ──────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
printBanner(): void {
|
|
15
|
+
console.log();
|
|
16
|
+
console.log(chalk.cyan.bold(' ⚡ Thatgfsj Code') + chalk.gray(' v0.5.0'));
|
|
17
|
+
console.log(chalk.gray(' AI Coding Assistant'));
|
|
18
|
+
console.log(chalk.gray(' ' + '─'.repeat(52)));
|
|
19
|
+
console.log();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ── Prompt ──────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
getPrompt(): string {
|
|
25
|
+
return chalk.cyan.bold('❯ ');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ── User Input ──────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
printUserInput(input: string): void {
|
|
31
|
+
console.log();
|
|
32
|
+
console.log(chalk.bold(' You'));
|
|
33
|
+
console.log(chalk.gray(' ' + '─'.repeat(40)));
|
|
34
|
+
for (const line of input.split('\n')) {
|
|
35
|
+
console.log(' ' + line);
|
|
36
|
+
}
|
|
37
|
+
console.log();
|
|
14
38
|
}
|
|
15
39
|
|
|
16
|
-
|
|
17
|
-
|
|
40
|
+
// ── Assistant Streaming ─────────────────────────────────
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Begin assistant response - just print the header
|
|
44
|
+
*/
|
|
45
|
+
beginAssistant(): void {
|
|
46
|
+
console.log(chalk.bold(' AI'));
|
|
47
|
+
console.log(chalk.gray(' ' + '─'.repeat(40)));
|
|
18
48
|
}
|
|
19
49
|
|
|
20
|
-
|
|
21
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Write streaming chunk - output as-is with indent.
|
|
52
|
+
* The chunk already contains proper newlines from the AI.
|
|
53
|
+
*/
|
|
54
|
+
writeChunk(chunk: string): void {
|
|
55
|
+
// Indent each line by 2 spaces
|
|
56
|
+
const lines = chunk.split('\n');
|
|
57
|
+
for (let i = 0; i < lines.length; i++) {
|
|
58
|
+
if (lines[i]) {
|
|
59
|
+
process.stdout.write(' ' + lines[i]);
|
|
60
|
+
}
|
|
61
|
+
if (i < lines.length - 1) {
|
|
62
|
+
process.stdout.write('\n');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
22
65
|
}
|
|
23
66
|
|
|
24
|
-
|
|
25
|
-
|
|
67
|
+
/**
|
|
68
|
+
* End assistant response
|
|
69
|
+
*/
|
|
70
|
+
endAssistant(): void {
|
|
71
|
+
process.stdout.write('\n');
|
|
72
|
+
console.log();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── Tool Calls ──────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
printToolCall(name: string, args: string): void {
|
|
78
|
+
console.log();
|
|
79
|
+
console.log(chalk.yellow(' ⚙ ') + chalk.bold(name));
|
|
80
|
+
|
|
81
|
+
if (args) {
|
|
82
|
+
try {
|
|
83
|
+
const obj = JSON.parse(args);
|
|
84
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
85
|
+
let display: string;
|
|
86
|
+
if (typeof val === 'string') {
|
|
87
|
+
display = val.length > 100 ? val.slice(0, 100) + '...' : val;
|
|
88
|
+
} else {
|
|
89
|
+
display = JSON.stringify(val);
|
|
90
|
+
if (display.length > 100) display = display.slice(0, 100) + '...';
|
|
91
|
+
}
|
|
92
|
+
console.log(chalk.gray(' ' + key + ': ') + display);
|
|
93
|
+
}
|
|
94
|
+
} catch {
|
|
95
|
+
const display = args.length > 120 ? args.slice(0, 120) + '...' : args;
|
|
96
|
+
console.log(chalk.gray(' ') + display);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
printToolResult(output: string, isError = false): void {
|
|
102
|
+
if (!output) return;
|
|
103
|
+
|
|
104
|
+
const lines = output.split('\n');
|
|
105
|
+
const maxLines = 20;
|
|
106
|
+
const display = lines.slice(0, maxLines);
|
|
107
|
+
|
|
108
|
+
for (const line of display) {
|
|
109
|
+
if (isError) {
|
|
110
|
+
console.log(chalk.red(' ✖ ') + line);
|
|
111
|
+
} else {
|
|
112
|
+
console.log(chalk.gray(' │ ') + line);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (lines.length > maxLines) {
|
|
117
|
+
console.log(chalk.gray(' │ ') + chalk.dim(`... +${lines.length - maxLines} more lines`));
|
|
118
|
+
}
|
|
26
119
|
}
|
|
27
120
|
|
|
28
|
-
|
|
29
|
-
console.log(
|
|
121
|
+
printToolEnd(): void {
|
|
122
|
+
console.log();
|
|
30
123
|
}
|
|
31
124
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
this.
|
|
125
|
+
// ── Thinking ────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
startThinking(): void {
|
|
128
|
+
this.spinner = ora({
|
|
129
|
+
text: chalk.gray('Thinking'),
|
|
130
|
+
color: 'cyan',
|
|
131
|
+
spinner: 'dots',
|
|
132
|
+
}).start();
|
|
36
133
|
}
|
|
37
134
|
|
|
38
|
-
|
|
39
|
-
this.spinner = ora({
|
|
135
|
+
startExecuting(toolName: string): void {
|
|
136
|
+
this.spinner = ora({
|
|
137
|
+
text: chalk.gray(`Executing ${chalk.yellow(toolName)}`),
|
|
138
|
+
color: 'cyan',
|
|
139
|
+
spinner: 'dots',
|
|
140
|
+
}).start();
|
|
40
141
|
}
|
|
41
142
|
|
|
42
|
-
|
|
143
|
+
stopThinking(): void {
|
|
43
144
|
if (this.spinner) {
|
|
44
145
|
this.spinner.stop();
|
|
45
146
|
this.spinner = null;
|
|
46
147
|
}
|
|
47
148
|
}
|
|
48
149
|
|
|
49
|
-
|
|
150
|
+
stopThinkingFail(msg: string): void {
|
|
50
151
|
if (this.spinner) {
|
|
51
|
-
this.spinner.fail(
|
|
152
|
+
this.spinner.fail(chalk.red(msg));
|
|
52
153
|
this.spinner = null;
|
|
53
154
|
}
|
|
54
155
|
}
|
|
55
156
|
|
|
157
|
+
// ── Messages ────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
info(msg: string): void {
|
|
160
|
+
console.log(chalk.gray(' ') + msg);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
success(msg: string): void {
|
|
164
|
+
console.log(chalk.green(' ✅ ') + msg);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
error(msg: string): void {
|
|
168
|
+
console.log();
|
|
169
|
+
console.log(chalk.red(' Error'));
|
|
170
|
+
console.log(chalk.red(' ' + '─'.repeat(40)));
|
|
171
|
+
for (const line of msg.split('\n')) {
|
|
172
|
+
console.log(chalk.red(' ') + line);
|
|
173
|
+
}
|
|
174
|
+
console.log();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
warning(msg: string): void {
|
|
178
|
+
console.log(chalk.yellow(' ⚠ ') + msg);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
dim(msg: string): void {
|
|
182
|
+
console.log(chalk.dim(' ' + msg));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ── Layout ──────────────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
divider(): void {
|
|
188
|
+
console.log(chalk.gray(' ' + '─'.repeat(52)));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
spacer(): void {
|
|
192
|
+
console.log();
|
|
193
|
+
}
|
|
194
|
+
|
|
56
195
|
clear(): void {
|
|
57
196
|
console.clear();
|
|
58
197
|
}
|
|
59
198
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
199
|
+
// ── Sections ────────────────────────────────────────────
|
|
200
|
+
|
|
201
|
+
section(title: string, items: Array<{ label: string; value: string }>): void {
|
|
202
|
+
console.log();
|
|
203
|
+
console.log(chalk.bold(' ' + title));
|
|
204
|
+
console.log(chalk.gray(' ' + '─'.repeat(40)));
|
|
205
|
+
for (const item of items) {
|
|
206
|
+
console.log(' ' + chalk.gray(item.label.padEnd(14)) + item.value);
|
|
207
|
+
}
|
|
208
|
+
console.log();
|
|
67
209
|
}
|
|
68
210
|
|
|
211
|
+
// ── Help ────────────────────────────────────────────────
|
|
212
|
+
|
|
69
213
|
printHelp(): void {
|
|
70
|
-
console.log(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
214
|
+
console.log();
|
|
215
|
+
console.log(chalk.bold(' Commands'));
|
|
216
|
+
console.log(chalk.gray(' ' + '─'.repeat(40)));
|
|
217
|
+
const cmds: [string, string][] = [
|
|
218
|
+
['help', 'Show this help'],
|
|
219
|
+
['tools', 'List available tools'],
|
|
220
|
+
['model', 'Show current model info'],
|
|
221
|
+
['clear', 'Clear screen'],
|
|
222
|
+
['exit', 'Exit'],
|
|
223
|
+
];
|
|
224
|
+
for (const [cmd, desc] of cmds) {
|
|
225
|
+
console.log(' ' + chalk.cyan(cmd.padEnd(14)) + chalk.gray(desc));
|
|
226
|
+
}
|
|
227
|
+
console.log();
|
|
228
|
+
console.log(chalk.bold(' Keyboard'));
|
|
229
|
+
console.log(chalk.gray(' ' + '─'.repeat(40)));
|
|
230
|
+
console.log(' ' + chalk.cyan('↑ / ↓'.padEnd(14)) + chalk.gray('Browse command history'));
|
|
231
|
+
console.log(' ' + chalk.cyan('Tab'.padEnd(14)) + chalk.gray('Auto-complete'));
|
|
232
|
+
console.log(' ' + chalk.cyan('Ctrl+C'.padEnd(14)) + chalk.gray('Exit'));
|
|
233
|
+
console.log();
|
|
82
234
|
}
|
|
83
235
|
}
|
package/src/tui/repl.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* REPL - Main interactive loop
|
|
3
|
-
* Migrated from old src/repl/loop.ts + src/index.ts interactive mode
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
import chalk from 'chalk';
|
|
@@ -21,53 +20,45 @@ export class REPL {
|
|
|
21
20
|
this.app = app;
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
/**
|
|
25
|
-
* Start the REPL
|
|
26
|
-
*/
|
|
27
23
|
async start(): Promise<void> {
|
|
28
24
|
this.running = true;
|
|
29
25
|
|
|
30
|
-
// Show banner
|
|
31
26
|
this.output.clear();
|
|
32
27
|
this.output.printBanner();
|
|
33
|
-
this.output.printInfo('Type "help" for available commands\n');
|
|
34
|
-
this.output.printDivider();
|
|
35
28
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
29
|
+
const cfg = this.app.config.get();
|
|
30
|
+
this.output.section('Session', [
|
|
31
|
+
{ label: 'Provider', value: cfg.provider },
|
|
32
|
+
{ label: 'Model', value: cfg.model },
|
|
33
|
+
{ label: 'Project', value: ProjectContext.detectType() },
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
this.output.info('Type ' + chalk.cyan('help') + ' for commands');
|
|
37
|
+
this.output.spacer();
|
|
40
38
|
|
|
41
|
-
// Main loop
|
|
42
39
|
while (this.running) {
|
|
43
40
|
try {
|
|
44
|
-
const userInput = await this.input.prompt();
|
|
45
|
-
|
|
41
|
+
const userInput = await this.input.prompt(this.output.getPrompt());
|
|
46
42
|
if (!userInput.trim()) continue;
|
|
47
43
|
|
|
48
|
-
// Handle commands
|
|
49
44
|
const handled = await this.handleCommand(userInput.trim());
|
|
50
45
|
if (handled) continue;
|
|
51
46
|
|
|
52
|
-
// Process with AI
|
|
53
47
|
await this.processInput(userInput.trim());
|
|
54
48
|
} catch (error: any) {
|
|
55
49
|
if (error.message === 'SIGINT') continue;
|
|
56
|
-
this.output.
|
|
50
|
+
this.output.error(error.message);
|
|
57
51
|
}
|
|
58
52
|
}
|
|
59
53
|
}
|
|
60
54
|
|
|
61
|
-
/**
|
|
62
|
-
* Handle built-in commands
|
|
63
|
-
*/
|
|
64
55
|
private async handleCommand(input: string): Promise<boolean> {
|
|
65
56
|
const cmd = input.toLowerCase();
|
|
66
57
|
|
|
67
58
|
switch (cmd) {
|
|
68
59
|
case 'exit':
|
|
69
60
|
case 'quit':
|
|
70
|
-
this.output.
|
|
61
|
+
this.output.dim('Goodbye! 👋');
|
|
71
62
|
this.running = false;
|
|
72
63
|
this.input.close();
|
|
73
64
|
return true;
|
|
@@ -82,50 +73,109 @@ export class REPL {
|
|
|
82
73
|
return true;
|
|
83
74
|
|
|
84
75
|
case 'tools':
|
|
85
|
-
this.output.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
76
|
+
this.output.section('Available Tools',
|
|
77
|
+
this.app.tools.list().map(t => ({
|
|
78
|
+
label: t.name,
|
|
79
|
+
value: t.description,
|
|
80
|
+
}))
|
|
81
|
+
);
|
|
90
82
|
return true;
|
|
91
83
|
|
|
92
|
-
case 'model':
|
|
93
|
-
this.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
84
|
+
case 'model': {
|
|
85
|
+
const c = this.app.config.get();
|
|
86
|
+
this.output.section('Current Model', [
|
|
87
|
+
{ label: 'Provider', value: c.provider },
|
|
88
|
+
{ label: 'Model', value: c.model },
|
|
89
|
+
{ label: 'Base URL', value: c.baseUrl || 'default' },
|
|
90
|
+
{ label: 'API Key', value: c.apiKey ? '••••' + c.apiKey.slice(-4) : 'not set' },
|
|
91
|
+
]);
|
|
99
92
|
return true;
|
|
93
|
+
}
|
|
100
94
|
|
|
101
95
|
default:
|
|
102
96
|
return false;
|
|
103
97
|
}
|
|
104
98
|
}
|
|
105
99
|
|
|
106
|
-
/**
|
|
107
|
-
* Process user input with AI
|
|
108
|
-
*/
|
|
109
100
|
private async processInput(input: string): Promise<void> {
|
|
110
101
|
this.app.session.addMessage('user', input);
|
|
102
|
+
this.output.printUserInput(input);
|
|
103
|
+
this.output.startThinking();
|
|
111
104
|
|
|
112
|
-
this.output.startSpinner('Thinking...');
|
|
113
105
|
let fullResponse = '';
|
|
106
|
+
let hasStartedOutput = false;
|
|
114
107
|
|
|
115
108
|
try {
|
|
116
|
-
const stream = this.app.
|
|
109
|
+
const stream = this.app.streamResponse();
|
|
110
|
+
|
|
117
111
|
for await (const chunk of stream) {
|
|
118
|
-
this.output.
|
|
119
|
-
|
|
112
|
+
this.output.stopThinking();
|
|
113
|
+
|
|
114
|
+
// Check for structured tool messages
|
|
115
|
+
if (chunk.includes('@@TOOL@@')) {
|
|
116
|
+
const parts = chunk.split('\n');
|
|
117
|
+
for (const part of parts) {
|
|
118
|
+
if (part.startsWith('@@TOOL@@')) {
|
|
119
|
+
try {
|
|
120
|
+
const data = JSON.parse(part.slice(8));
|
|
121
|
+
if (data.action === 'call') {
|
|
122
|
+
if (hasStartedOutput) {
|
|
123
|
+
this.output.endAssistant();
|
|
124
|
+
hasStartedOutput = false;
|
|
125
|
+
}
|
|
126
|
+
this.output.printToolCall(data.name, data.args || '');
|
|
127
|
+
this.output.startExecuting(data.name);
|
|
128
|
+
} else if (data.action === 'result') {
|
|
129
|
+
this.output.stopThinking();
|
|
130
|
+
this.output.printToolResult(data.output || data.error || '', !!data.error);
|
|
131
|
+
this.output.printToolEnd();
|
|
132
|
+
}
|
|
133
|
+
} catch {
|
|
134
|
+
// If parse fails, treat as regular text
|
|
135
|
+
if (part) {
|
|
136
|
+
if (!hasStartedOutput) {
|
|
137
|
+
this.output.beginAssistant();
|
|
138
|
+
hasStartedOutput = true;
|
|
139
|
+
}
|
|
140
|
+
this.output.writeChunk(part);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} else if (part) {
|
|
144
|
+
// Regular text between tool messages
|
|
145
|
+
if (!hasStartedOutput) {
|
|
146
|
+
this.output.beginAssistant();
|
|
147
|
+
hasStartedOutput = true;
|
|
148
|
+
}
|
|
149
|
+
this.output.writeChunk(part + '\n');
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} else {
|
|
153
|
+
// Pure text chunk - just output it
|
|
154
|
+
if (!hasStartedOutput) {
|
|
155
|
+
this.output.beginAssistant();
|
|
156
|
+
hasStartedOutput = true;
|
|
157
|
+
}
|
|
158
|
+
this.output.writeChunk(chunk);
|
|
159
|
+
}
|
|
160
|
+
|
|
120
161
|
fullResponse += chunk;
|
|
121
162
|
}
|
|
122
163
|
|
|
123
|
-
|
|
164
|
+
if (hasStartedOutput) {
|
|
165
|
+
this.output.endAssistant();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (fullResponse.trim()) {
|
|
169
|
+
this.app.session.addMessage('assistant', fullResponse);
|
|
170
|
+
this.app.session.truncate();
|
|
171
|
+
}
|
|
124
172
|
|
|
125
|
-
this.app.session.addMessage('assistant', fullResponse);
|
|
126
|
-
this.app.session.truncate();
|
|
127
173
|
} catch (error: any) {
|
|
128
|
-
|
|
174
|
+
if (hasStartedOutput) {
|
|
175
|
+
this.output.endAssistant();
|
|
176
|
+
}
|
|
177
|
+
this.output.stopThinkingFail(error.message);
|
|
178
|
+
this.output.error(error.message);
|
|
129
179
|
}
|
|
130
180
|
}
|
|
131
181
|
}
|