thatgfsj-code 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/cmd/index.js +56 -8
  2. package/dist/cmd/index.js.map +1 -1
  3. package/dist/llm/anthropic.d.ts +4 -17
  4. package/dist/llm/anthropic.d.ts.map +1 -1
  5. package/dist/llm/anthropic.js +54 -12
  6. package/dist/llm/anthropic.js.map +1 -1
  7. package/dist/llm/gemini.d.ts +3 -16
  8. package/dist/llm/gemini.d.ts.map +1 -1
  9. package/dist/llm/gemini.js +26 -22
  10. package/dist/llm/gemini.js.map +1 -1
  11. package/dist/llm/index.d.ts +5 -19
  12. package/dist/llm/index.d.ts.map +1 -1
  13. package/dist/llm/index.js +87 -87
  14. package/dist/llm/index.js.map +1 -1
  15. package/dist/llm/openai.d.ts +9 -23
  16. package/dist/llm/openai.d.ts.map +1 -1
  17. package/dist/llm/openai.js +48 -14
  18. package/dist/llm/openai.js.map +1 -1
  19. package/dist/llm/provider.d.ts +11 -16
  20. package/dist/llm/provider.d.ts.map +1 -1
  21. package/dist/tui/input.d.ts +0 -1
  22. package/dist/tui/input.d.ts.map +1 -1
  23. package/dist/tui/input.js +2 -5
  24. package/dist/tui/input.js.map +1 -1
  25. package/dist/tui/output.d.ts +27 -12
  26. package/dist/tui/output.d.ts.map +1 -1
  27. package/dist/tui/output.js +176 -40
  28. package/dist/tui/output.js.map +1 -1
  29. package/dist/tui/repl.d.ts +0 -10
  30. package/dist/tui/repl.d.ts.map +1 -1
  31. package/dist/tui/repl.js +76 -42
  32. package/dist/tui/repl.js.map +1 -1
  33. package/dist/tui/welcome.d.ts +1 -11
  34. package/dist/tui/welcome.d.ts.map +1 -1
  35. package/dist/tui/welcome.js +60 -56
  36. package/dist/tui/welcome.js.map +1 -1
  37. package/package.json +1 -1
  38. package/src/cmd/index.ts +49 -8
  39. package/src/llm/anthropic.ts +60 -14
  40. package/src/llm/gemini.ts +31 -24
  41. package/src/llm/index.ts +94 -92
  42. package/src/llm/openai.ts +58 -15
  43. package/src/llm/provider.ts +12 -16
  44. package/src/tui/input.ts +2 -5
  45. package/src/tui/output.ts +193 -40
  46. package/src/tui/repl.ts +82 -43
  47. package/src/tui/welcome.ts +62 -59
package/src/tui/output.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  /**
2
- * REPL Output Handler
3
- * Migrated from old src/repl/output.ts
2
+ * REPL Output - Beautiful terminal UI
4
3
  */
5
4
 
6
5
  import chalk from 'chalk';
@@ -8,76 +7,230 @@ import ora, { Ora } from 'ora';
8
7
 
9
8
  export class REPLOutput {
10
9
  private spinner: Ora | null = null;
10
+ private inAssistantBlock = false;
11
+ private currentLineHasContent = false;
11
12
 
12
- printError(error: string): void {
13
- console.error(chalk.red(`\n❌ Error: ${error}`));
13
+ // ── Banner ──────────────────────────────────────────────
14
+
15
+ printBanner(): void {
16
+ console.log();
17
+ console.log(chalk.cyan.bold(' ⚡ Thatgfsj Code') + chalk.gray(' v0.4.1'));
18
+ console.log(chalk.gray(' AI Coding Assistant'));
19
+ console.log(chalk.gray(' ' + '─'.repeat(52)));
20
+ console.log();
14
21
  }
15
22
 
16
- printWarning(warning: string): void {
17
- console.warn(chalk.yellow(`\n⚠️ ${warning}`));
23
+ // ── Prompt ──────────────────────────────────────────────
24
+
25
+ getPrompt(): string {
26
+ return chalk.cyan.bold('❯ ');
18
27
  }
19
28
 
20
- printSuccess(message: string): void {
21
- console.log(chalk.green(`\n✅ ${message}`));
29
+ // ── User Input ──────────────────────────────────────────
30
+
31
+ printUserInput(input: string): void {
32
+ console.log();
33
+ console.log(chalk.gray(' ┌─ ') + chalk.bold('You'));
34
+ for (const line of input.split('\n')) {
35
+ console.log(chalk.gray(' │ ') + line);
36
+ }
37
+ console.log(chalk.gray(' └─'));
22
38
  }
23
39
 
24
- printInfo(message: string): void {
25
- console.log(chalk.gray(message));
40
+ // ── Assistant Streaming ─────────────────────────────────
41
+
42
+ beginAssistant(): void {
43
+ if (this.inAssistantBlock) return;
44
+ this.inAssistantBlock = true;
45
+ this.currentLineHasContent = false;
46
+ console.log();
47
+ process.stdout.write(chalk.cyan(' ┌─ ') + chalk.bold('AI') + '\n' + chalk.cyan(' │ '));
26
48
  }
27
49
 
28
- printDivider(char = '─', length = 40): void {
29
- console.log(chalk.gray(char.repeat(length)));
50
+ writeChunk(chunk: string): void {
51
+ if (!this.inAssistantBlock) this.beginAssistant();
52
+
53
+ for (const char of chunk) {
54
+ if (char === '\n') {
55
+ process.stdout.write('\n' + chalk.cyan(' │ '));
56
+ this.currentLineHasContent = false;
57
+ } else {
58
+ process.stdout.write(char);
59
+ this.currentLineHasContent = true;
60
+ }
61
+ }
30
62
  }
31
63
 
32
- printHeader(text: string): void {
33
- this.printDivider();
34
- console.log(chalk.cyan(text));
35
- this.printDivider();
64
+ endAssistant(): void {
65
+ if (!this.inAssistantBlock) return;
66
+ if (this.currentLineHasContent) {
67
+ process.stdout.write('\n');
68
+ }
69
+ process.stdout.write(chalk.cyan(' └─') + '\n\n');
70
+ this.inAssistantBlock = false;
71
+ this.currentLineHasContent = false;
36
72
  }
37
73
 
38
- startSpinner(text: string): void {
39
- this.spinner = ora({ text, color: 'cyan', spinner: 'dots' }).start();
74
+ // ── Tool Calls ──────────────────────────────────────────
75
+
76
+ printToolCall(name: string, args: string): void {
77
+ if (this.inAssistantBlock) this.endAssistant();
78
+
79
+ console.log();
80
+ console.log(chalk.yellow(' ⚙ ') + chalk.bold(name) + chalk.gray(' ─────────────────────────'));
81
+
82
+ if (args) {
83
+ try {
84
+ const obj = JSON.parse(args);
85
+ for (const [key, val] of Object.entries(obj)) {
86
+ let display: string;
87
+ if (typeof val === 'string') {
88
+ display = val.length > 120 ? val.slice(0, 120) + '...' : val;
89
+ } else {
90
+ display = JSON.stringify(val);
91
+ if (display.length > 120) display = display.slice(0, 120) + '...';
92
+ }
93
+ console.log(chalk.gray(' ' + key + ': ') + chalk.white(display));
94
+ }
95
+ } catch {
96
+ const display = args.length > 150 ? args.slice(0, 150) + '...' : args;
97
+ console.log(chalk.gray(' ') + display);
98
+ }
99
+ }
100
+ }
101
+
102
+ printToolResult(output: string, isError = false): void {
103
+ if (!output) return;
104
+
105
+ const lines = output.split('\n');
106
+ const maxLines = 25;
107
+ const display = lines.slice(0, maxLines);
108
+
109
+ for (const line of display) {
110
+ if (isError) {
111
+ console.log(chalk.red(' ✖ ') + line);
112
+ } else {
113
+ console.log(chalk.gray(' │ ') + line);
114
+ }
115
+ }
116
+
117
+ if (lines.length > maxLines) {
118
+ console.log(chalk.gray(' │ ') + chalk.dim(`... +${lines.length - maxLines} more lines`));
119
+ }
120
+ }
121
+
122
+ printToolEnd(): void {
123
+ console.log(chalk.yellow(' ────────────────────────────────────'));
124
+ }
125
+
126
+ // ── Thinking ────────────────────────────────────────────
127
+
128
+ startThinking(): void {
129
+ this.spinner = ora({
130
+ text: chalk.gray('Thinking'),
131
+ color: 'cyan',
132
+ spinner: 'dots',
133
+ }).start();
40
134
  }
41
135
 
42
- stopSpinner(): void {
136
+ startExecuting(toolName: string): void {
137
+ this.spinner = ora({
138
+ text: chalk.gray(`Executing ${chalk.yellow(toolName)}`),
139
+ color: 'cyan',
140
+ spinner: 'dots',
141
+ }).start();
142
+ }
143
+
144
+ stopThinking(): void {
43
145
  if (this.spinner) {
44
146
  this.spinner.stop();
45
147
  this.spinner = null;
46
148
  }
47
149
  }
48
150
 
49
- stopSpinnerFail(text?: string): void {
151
+ stopThinkingFail(msg: string): void {
50
152
  if (this.spinner) {
51
- this.spinner.fail(text || this.spinner.text);
153
+ this.spinner.fail(chalk.red(msg));
52
154
  this.spinner = null;
53
155
  }
54
156
  }
55
157
 
158
+ // ── Messages ────────────────────────────────────────────
159
+
160
+ info(msg: string): void {
161
+ console.log(chalk.gray(' ') + msg);
162
+ }
163
+
164
+ success(msg: string): void {
165
+ console.log(chalk.green(' ✅ ') + msg);
166
+ }
167
+
168
+ error(msg: string): void {
169
+ console.log();
170
+ console.log(chalk.red(' ┌─ Error'));
171
+ for (const line of msg.split('\n')) {
172
+ console.log(chalk.red(' │ ') + line);
173
+ }
174
+ console.log(chalk.red(' └─'));
175
+ console.log();
176
+ }
177
+
178
+ warning(msg: string): void {
179
+ console.log(chalk.yellow(' ⚠ ') + msg);
180
+ }
181
+
182
+ dim(msg: string): void {
183
+ console.log(chalk.dim(' ' + msg));
184
+ }
185
+
186
+ // ── Layout ──────────────────────────────────────────────
187
+
188
+ divider(): void {
189
+ console.log(chalk.gray(' ' + '─'.repeat(52)));
190
+ }
191
+
192
+ spacer(): void {
193
+ console.log();
194
+ }
195
+
56
196
  clear(): void {
57
197
  console.clear();
58
198
  }
59
199
 
60
- printBanner(): void {
61
- console.log(chalk.cyan(`
62
- ╔═══════════════════════════════════════╗
63
- ║ 🤖 Thatgfsj Code v0.4.0 ║
64
- ║ AI Coding Assistant ║
65
- ╚═══════════════════════════════════════╝
66
- `));
200
+ // ── Sections ────────────────────────────────────────────
201
+
202
+ section(title: string, items: Array<{ label: string; value: string }>): void {
203
+ console.log();
204
+ console.log(chalk.bold(' ' + title));
205
+ this.divider();
206
+ for (const item of items) {
207
+ console.log(' ' + chalk.gray(item.label.padEnd(14)) + chalk.white(item.value));
208
+ }
209
+ console.log();
67
210
  }
68
211
 
212
+ // ── Help ────────────────────────────────────────────────
213
+
69
214
  printHelp(): void {
70
- console.log(`
71
- ${chalk.cyan('Commands:')}
72
- ${chalk.gray('exit, Ctrl+C')} - Exit
73
- ${chalk.gray('clear')} - Clear screen
74
- ${chalk.gray('help')} - Show this help
75
- ${chalk.gray('tools')} - List available tools
76
- ${chalk.gray('model')} - Show current model
77
-
78
- ${chalk.cyan('Tips:')}
79
- • Use ${chalk.gray('↑/↓')} for command history
80
- Use ${chalk.gray('Tab')} for auto-complete
81
- `);
215
+ console.log();
216
+ console.log(chalk.bold(' Commands'));
217
+ this.divider();
218
+ const cmds: [string, string][] = [
219
+ ['help', 'Show this help'],
220
+ ['tools', 'List available tools'],
221
+ ['model', 'Show current model info'],
222
+ ['clear', 'Clear screen'],
223
+ ['exit', 'Exit'],
224
+ ];
225
+ for (const [cmd, desc] of cmds) {
226
+ console.log(' ' + chalk.cyan(cmd.padEnd(14)) + chalk.gray(desc));
227
+ }
228
+ console.log();
229
+ console.log(chalk.bold(' Keyboard'));
230
+ this.divider();
231
+ console.log(' ' + chalk.cyan('↑ / ↓'.padEnd(14)) + chalk.gray('Browse command history'));
232
+ console.log(' ' + chalk.cyan('Tab'.padEnd(14)) + chalk.gray('Auto-complete'));
233
+ console.log(' ' + chalk.cyan('Ctrl+C'.padEnd(14)) + chalk.gray('Exit'));
234
+ console.log();
82
235
  }
83
236
  }
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
- // Show project context
37
- console.log(chalk.gray(ProjectContext.getSummary()));
38
- this.output.printDivider();
39
- console.log();
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.printError(error.message);
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.printInfo('\n👋 Goodbye!');
61
+ this.output.dim('Goodbye! 👋');
71
62
  this.running = false;
72
63
  this.input.close();
73
64
  return true;
@@ -82,50 +73,98 @@ export class REPL {
82
73
  return true;
83
74
 
84
75
  case 'tools':
85
- this.output.printHeader('🔧 Available Tools');
86
- for (const tool of this.app.tools.list()) {
87
- this.output.printInfo(` ${tool.name} - ${tool.description}`);
88
- }
89
- console.log();
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.output.printHeader('🤖 Current Model');
94
- const cfg = this.app.config.get();
95
- this.output.printInfo(` Provider: ${cfg.provider}`);
96
- this.output.printInfo(` Model: ${cfg.model}`);
97
- this.output.printInfo(` Base URL: ${cfg.baseUrl || 'default'}`);
98
- console.log();
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
109
  const stream = this.app.getAgent().run(this.app.session.getMessages());
110
+
117
111
  for await (const chunk of stream) {
118
- this.output.stopSpinner();
119
- process.stdout.write(chunk);
112
+ this.output.stopThinking();
113
+
114
+ const parts = chunk.split('\n');
115
+ for (const part of parts) {
116
+ // Tool message: @@TOOL@@{json}
117
+ if (part.startsWith('@@TOOL@@')) {
118
+ try {
119
+ const data = JSON.parse(part.slice(8));
120
+ if (data.action === 'call') {
121
+ if (hasStartedOutput) {
122
+ this.output.endAssistant();
123
+ hasStartedOutput = false;
124
+ }
125
+ this.output.printToolCall(data.name, data.args || '');
126
+ // Show executing state
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
+ this.output.writeChunk(part + '\n');
135
+ }
136
+ continue;
137
+ }
138
+
139
+ // Regular text
140
+ if (part) {
141
+ if (!hasStartedOutput) {
142
+ this.output.beginAssistant();
143
+ hasStartedOutput = true;
144
+ }
145
+ this.output.writeChunk(part + '\n');
146
+ }
147
+ }
148
+
120
149
  fullResponse += chunk;
121
150
  }
122
151
 
123
- console.log(); // newline
152
+ if (hasStartedOutput) {
153
+ this.output.endAssistant();
154
+ }
155
+
156
+ // Don't save empty responses
157
+ if (fullResponse.trim()) {
158
+ this.app.session.addMessage('assistant', fullResponse);
159
+ this.app.session.truncate();
160
+ }
124
161
 
125
- this.app.session.addMessage('assistant', fullResponse);
126
- this.app.session.truncate();
127
162
  } catch (error: any) {
128
- this.output.stopSpinnerFail(chalk.red(`Error: ${error.message}`));
163
+ if (hasStartedOutput) {
164
+ this.output.endAssistant();
165
+ }
166
+ this.output.stopThinkingFail(error.message);
167
+ this.output.error(error.message);
129
168
  }
130
169
  }
131
170
  }
@@ -1,6 +1,5 @@
1
1
  /**
2
- * Welcome / Setup Wizard
3
- * Supports all providers + custom relay stations (中转站)
2
+ * Welcome / Setup Wizard - Clean UI
4
3
  */
5
4
 
6
5
  import chalk from 'chalk';
@@ -11,119 +10,124 @@ import { homedir } from 'os';
11
10
  import { PROVIDERS, getModelsForProvider, listProviders, isCustomProvider } from '../config/providers.js';
12
11
  import type { ProviderName } from '../config/types.js';
13
12
 
13
+ const line = chalk.gray('─'.repeat(52));
14
+
14
15
  export class WelcomeScreen {
15
- /**
16
- * Show welcome if no API key is configured
17
- */
16
+
18
17
  static show(hasApiKey: boolean): void {
19
18
  if (hasApiKey) return;
20
19
 
21
- const w = 62;
22
- console.log(chalk.cyan('+') + chalk.white.bold(' Thatgfsj Code ') + chalk.cyan('-'.repeat(w - 16)) + '+');
23
- console.log(chalk.cyan('|') + chalk.yellow(' 快速开始指南').padEnd(w) + chalk.cyan('|'));
24
- console.log(chalk.cyan('|') + ' '.repeat(w) + chalk.cyan('|'));
25
- console.log(chalk.cyan('|') + chalk.gray(' 运行 gfcode init 配置你的 API Key').padEnd(w) + chalk.cyan('|'));
26
- console.log(chalk.cyan('|') + chalk.gray(' 支持: OpenAI / Claude / DeepSeek / Kimi / 智谱 / 中转站').padEnd(w) + chalk.cyan('|'));
27
- console.log(chalk.cyan('+') + '-'.repeat(w) + '+');
20
+ console.log();
21
+ console.log(chalk.cyan.bold(' Thatgfsj Code') + chalk.gray(' v0.4.1'));
22
+ console.log(chalk.gray(' AI Coding Assistant'));
23
+ console.log(line);
24
+ console.log();
25
+ console.log(chalk.yellow(' ⚠ No API key configured'));
26
+ console.log();
27
+ console.log(chalk.gray(' Run ') + chalk.cyan.bold('gfcode init') + chalk.gray(' to set up your provider.'));
28
+ console.log();
29
+ console.log(chalk.gray(' Supported providers:'));
30
+ console.log(chalk.gray(' OpenAI · Claude · DeepSeek · Kimi · GLM · Gemini · 中转站'));
31
+ console.log(line);
28
32
  console.log();
29
33
  }
30
34
 
31
- /**
32
- * Interactive setup wizard
33
- */
34
35
  static async interactiveSetup(): Promise<void> {
35
36
  console.clear();
36
- console.log(chalk.cyan('\n 🤖 Thatgfsj Code 配置向导\n'));
37
+ console.log();
38
+ console.log(chalk.cyan.bold(' ⚡ Thatgfsj Code') + chalk.gray(' - Setup'));
39
+ console.log(line);
40
+ console.log();
37
41
 
38
42
  const rl = readline.createInterface({
39
43
  input: process.stdin,
40
44
  output: process.stdout,
41
45
  });
42
46
 
43
- const question = (prompt: string): Promise<string> =>
47
+ const ask = (prompt: string): Promise<string> =>
44
48
  new Promise(resolve => rl.question(prompt, answer => resolve(answer.trim())));
45
49
 
46
50
  try {
47
- // Step 1: Choose provider
48
- console.log(chalk.white(' 步骤 1/3: 选择 AI 提供商\n'));
51
+ // ── Step 1: Provider ────────────────────────────────
52
+ console.log(chalk.bold(' 1. Choose Provider'));
53
+ console.log();
49
54
  const providers = listProviders();
50
55
  providers.forEach((p, i) => {
51
- const num = (i + 1).toString().padStart(2);
52
- console.log(chalk.green(` ${num}. ${p.name}`));
56
+ const num = chalk.cyan((i + 1).toString().padStart(2));
57
+ console.log(` ${num} ${p.name}`);
53
58
  });
54
59
  console.log();
55
60
 
56
- const choice = await question(chalk.green(' 请选择 (1-' + providers.length + '): '));
61
+ const choice = await ask(chalk.cyan(' '));
57
62
  const selected = providers[parseInt(choice) - 1] || providers[0];
58
63
  const providerName = selected.key;
59
64
 
60
- // Step 2: Enter API Key (and base URL for custom providers)
61
- console.log(chalk.white('\n 步骤 2/3: 配置连接信息\n'));
65
+ // ── Step 2: API Key ─────────────────────────────────
66
+ console.log();
67
+ console.log(chalk.bold(' 2. API Key'));
68
+ console.log();
62
69
 
63
70
  let baseUrl: string | undefined;
64
71
 
65
72
  if (isCustomProvider(providerName)) {
66
- // Custom provider needs base URL
67
- console.log(chalk.yellow(' 自定义 Provider (中转站) 模式'));
68
- console.log(chalk.gray(' 支持 OpenAI 兼容 或 Anthropic 兼容 的中转站'));
73
+ console.log(chalk.yellow(' Custom Provider (Relay Station)'));
74
+ console.log(chalk.gray(' OpenAI-compatible or Anthropic-compatible'));
69
75
  console.log();
70
- baseUrl = await question(chalk.green(' 请输入中转站 Base URL: '));
76
+ baseUrl = await ask(chalk.cyan(' Base URL '));
71
77
  if (!baseUrl) {
72
- console.log(chalk.red(' 中转站必须提供 Base URL'));
78
+ console.log(chalk.red(' ❌ Base URL required'));
73
79
  rl.close();
74
80
  return;
75
81
  }
76
- // Remove trailing slash
77
82
  baseUrl = baseUrl.replace(/\/+$/, '');
78
- // Remove /v1 suffix if present (we'll add it in the provider)
79
- // Actually keep it as-is since the user knows their endpoint
80
83
  }
81
84
 
82
- const apiKey = await question(chalk.green(' 请输入 API Key: '));
85
+ const apiKey = await ask(chalk.cyan(' API Key '));
83
86
 
84
- // Step 3: Choose model
85
- console.log(chalk.white('\n 步骤 3/3: 选择模型\n'));
87
+ // ── Step 3: Model ───────────────────────────────────
88
+ console.log();
89
+ console.log(chalk.bold(' 3. Choose Model'));
90
+ console.log();
86
91
 
87
92
  if (isCustomProvider(providerName)) {
88
- // Custom provider: let user type model name
89
- console.log(chalk.gray(' 输入中转站支持的模型名称'));
90
- console.log(chalk.gray(' 例如: gpt-4o-mini, claude-sonnet-4-20250514, deepseek-chat'));
91
- const model = await question(chalk.green(' 模型名称: '));
93
+ console.log(chalk.gray(' Enter model name for your relay station'));
94
+ console.log(chalk.gray(' e.g. gpt-4o-mini, claude-sonnet-4-20250514'));
95
+ console.log();
96
+ const model = await ask(chalk.cyan(' Model '));
92
97
  if (!model) {
93
- console.log(chalk.red(' 必须指定模型'));
98
+ console.log(chalk.red(' Model name required'));
94
99
  rl.close();
95
100
  return;
96
101
  }
97
-
98
- // Save config
99
102
  this.saveConfig(providerName, model, apiKey, baseUrl);
100
103
  } else {
101
- // Standard provider: show model list
102
104
  const models = getModelsForProvider(providerName);
103
105
  models.forEach((m, i) => {
104
- const num = (i + 1).toString().padStart(2);
105
- console.log(chalk.green(` ${num}. ${m.name}`) + chalk.gray(` - ${m.desc}`));
106
+ const num = chalk.cyan((i + 1).toString().padStart(2));
107
+ console.log(` ${num} ${chalk.white(m.name)} ${chalk.gray(m.desc)}`);
106
108
  });
107
109
  console.log();
108
110
 
109
- const modelChoice = await question(chalk.green(' 请选择模型 (1-' + models.length + '): '));
111
+ const modelChoice = await ask(chalk.cyan(' '));
110
112
  const selectedModel = models[parseInt(modelChoice) - 1] || models[0];
111
-
112
113
  this.saveConfig(providerName, selectedModel.id, apiKey, baseUrl);
113
114
  }
114
115
 
115
- console.log(chalk.green('\n ✅ 配置已保存!'));
116
- console.log(chalk.gray(` 配置文件: ${join(homedir(), '.thatgfsj', 'config.json')}`));
117
- console.log(chalk.cyan('\n 运行 gfcode 开始使用\n'));
116
+ // ── Done ────────────────────────────────────────────
117
+ console.log();
118
+ console.log(line);
119
+ console.log(chalk.green.bold(' ✅ Configuration saved!'));
120
+ console.log();
121
+ console.log(chalk.gray(' Config: ') + chalk.white(join(homedir(), '.thatgfsj', 'config.json')));
122
+ console.log(chalk.gray(' Run ') + chalk.cyan.bold('gfcode') + chalk.gray(' to start.'));
123
+ console.log(line);
124
+ console.log();
118
125
 
119
126
  } finally {
120
127
  rl.close();
121
128
  }
122
129
  }
123
130
 
124
- /**
125
- * Save config to file
126
- */
127
131
  private static saveConfig(provider: ProviderName, model: string, apiKey: string, baseUrl?: string): void {
128
132
  const configDir = join(homedir(), '.thatgfsj');
129
133
  const configPath = join(configDir, 'config.json');
@@ -140,16 +144,15 @@ export class WelcomeScreen {
140
144
  maxTokens: 4096,
141
145
  };
142
146
 
143
- if (baseUrl) {
144
- config.baseUrl = baseUrl;
145
- }
147
+ if (baseUrl) config.baseUrl = baseUrl;
146
148
 
147
149
  writeFileSync(configPath, JSON.stringify(config, null, 2));
148
150
 
149
- console.log(chalk.gray(` 提供商: ${provider}`));
150
- console.log(chalk.gray(` 模型: ${model}`));
151
+ console.log();
152
+ console.log(chalk.gray(' Provider: ') + chalk.white(provider));
153
+ console.log(chalk.gray(' Model: ') + chalk.white(model));
151
154
  if (baseUrl) {
152
- console.log(chalk.gray(` Base URL: ${baseUrl}`));
155
+ console.log(chalk.gray(' URL: ') + chalk.white(baseUrl));
153
156
  }
154
157
  }
155
158
  }