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.
Files changed (53) hide show
  1. package/dist/app/index.d.ts +6 -14
  2. package/dist/app/index.d.ts.map +1 -1
  3. package/dist/app/index.js +28 -30
  4. package/dist/app/index.js.map +1 -1
  5. package/dist/cmd/index.js +64 -8
  6. package/dist/cmd/index.js.map +1 -1
  7. package/dist/llm/anthropic.d.ts +4 -17
  8. package/dist/llm/anthropic.d.ts.map +1 -1
  9. package/dist/llm/anthropic.js +54 -12
  10. package/dist/llm/anthropic.js.map +1 -1
  11. package/dist/llm/gemini.d.ts +3 -16
  12. package/dist/llm/gemini.d.ts.map +1 -1
  13. package/dist/llm/gemini.js +26 -22
  14. package/dist/llm/gemini.js.map +1 -1
  15. package/dist/llm/index.d.ts +5 -19
  16. package/dist/llm/index.d.ts.map +1 -1
  17. package/dist/llm/index.js +86 -87
  18. package/dist/llm/index.js.map +1 -1
  19. package/dist/llm/openai.d.ts +9 -23
  20. package/dist/llm/openai.d.ts.map +1 -1
  21. package/dist/llm/openai.js +48 -14
  22. package/dist/llm/openai.js.map +1 -1
  23. package/dist/llm/provider.d.ts +11 -16
  24. package/dist/llm/provider.d.ts.map +1 -1
  25. package/dist/tui/input.d.ts +0 -1
  26. package/dist/tui/input.d.ts.map +1 -1
  27. package/dist/tui/input.js +2 -5
  28. package/dist/tui/input.js.map +1 -1
  29. package/dist/tui/output.d.ts +36 -12
  30. package/dist/tui/output.d.ts.map +1 -1
  31. package/dist/tui/output.js +172 -40
  32. package/dist/tui/output.js.map +1 -1
  33. package/dist/tui/repl.d.ts +0 -10
  34. package/dist/tui/repl.d.ts.map +1 -1
  35. package/dist/tui/repl.js +91 -43
  36. package/dist/tui/repl.js.map +1 -1
  37. package/dist/tui/welcome.d.ts +1 -11
  38. package/dist/tui/welcome.d.ts.map +1 -1
  39. package/dist/tui/welcome.js +60 -56
  40. package/dist/tui/welcome.js.map +1 -1
  41. package/package.json +1 -1
  42. package/src/app/index.ts +28 -33
  43. package/src/cmd/index.ts +53 -8
  44. package/src/llm/anthropic.ts +60 -14
  45. package/src/llm/gemini.ts +31 -24
  46. package/src/llm/index.ts +93 -92
  47. package/src/llm/openai.ts +58 -15
  48. package/src/llm/provider.ts +12 -16
  49. package/src/tui/input.ts +2 -5
  50. package/src/tui/output.ts +192 -40
  51. package/src/tui/repl.ts +94 -44
  52. package/src/tui/welcome.ts +62 -59
  53. package/src/app/agent.ts +0 -140
@@ -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.5.0'));
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
  }
package/src/app/agent.ts DELETED
@@ -1,140 +0,0 @@
1
- /**
2
- * Agent Loop - Core agent logic with streaming and tool execution
3
- * Extracted from old src/core/ai-engine.ts
4
- *
5
- * The agent loop: while(true) { stream response → check tool calls → execute → repeat }
6
- */
7
-
8
- import chalk from 'chalk';
9
- import type { ChatMessage, ChatResponse } from '../types.js';
10
- import type { Tool } from '../tools/types.js';
11
- import type { HookManager } from '../hooks/index.js';
12
- import { LLMService } from '../llm/index.js';
13
- import { ToolRegistry } from '../tools/index.js';
14
-
15
- export interface AgentConfig {
16
- maxIterations?: number;
17
- hooks?: HookManager;
18
- }
19
-
20
- export class Agent {
21
- private llm: LLMService;
22
- private registry: ToolRegistry;
23
- private hooks?: HookManager;
24
- private maxIterations: number;
25
-
26
- constructor(llm: LLMService, registry: ToolRegistry, config: AgentConfig = {}) {
27
- this.llm = llm;
28
- this.registry = registry;
29
- this.hooks = config.hooks;
30
- this.maxIterations = config.maxIterations ?? 10;
31
-
32
- // Register tools with LLM service
33
- this.llm.registerTools(registry.list());
34
- }
35
-
36
- /**
37
- * Run agent loop with streaming output
38
- * Returns the final response
39
- */
40
- async *run(messages: ChatMessage[]): AsyncGenerator<string, ChatResponse> {
41
- let currentMessages = [...messages];
42
- let iterations = 0;
43
-
44
- while (iterations < this.maxIterations) {
45
- iterations++;
46
-
47
- // Hook: beforeAgentLoop
48
- if (this.hooks) {
49
- await this.hooks.emit('beforeAgentLoop', { messages: currentMessages, iteration: iterations });
50
- }
51
-
52
- // Stream the response
53
- let fullContent = '';
54
-
55
- for await (const chunk of this.llm.chatStream(currentMessages, { maxIterations: 1 })) {
56
- fullContent += chunk;
57
- yield chunk;
58
- }
59
-
60
- // Get the full response to check for tool calls
61
- const response: ChatResponse = {
62
- content: fullContent,
63
- role: 'assistant',
64
- };
65
-
66
- // Check if the response contains tool calls
67
- // For now, we rely on the LLM service to handle tool calls internally
68
- // If the LLM service returned tool_calls, we need to process them
69
- if (response.tool_calls && response.tool_calls.length > 0) {
70
- currentMessages.push({
71
- role: 'assistant',
72
- content: response.content,
73
- tool_calls: response.tool_calls,
74
- });
75
-
76
- for (const toolCall of response.tool_calls) {
77
- // Hook: beforeToolCall
78
- if (this.hooks) {
79
- await this.hooks.emit('beforeToolCall', {
80
- toolName: toolCall.function.name,
81
- toolParams: JSON.parse(toolCall.function.arguments || '{}'),
82
- toolCallId: toolCall.id,
83
- });
84
- }
85
-
86
- const result = await this.registry.execute(
87
- toolCall.function.name,
88
- JSON.parse(toolCall.function.arguments || '{}')
89
- );
90
-
91
- // Hook: afterToolCall
92
- if (this.hooks) {
93
- await this.hooks.emit('afterToolCall', {
94
- toolName: toolCall.function.name,
95
- toolParams: JSON.parse(toolCall.function.arguments || '{}'),
96
- toolResult: result,
97
- toolCallId: toolCall.id,
98
- });
99
- }
100
-
101
- currentMessages.push({
102
- role: 'tool',
103
- content: result.success ? (result.output || JSON.stringify(result.data)) : (result.error || 'Tool failed'),
104
- tool_call_id: toolCall.id,
105
- name: toolCall.function.name,
106
- });
107
-
108
- yield `\n${chalk.gray(`[tool: ${toolCall.function.name}]`)}`;
109
- }
110
-
111
- continue; // Next iteration
112
- }
113
-
114
- // No tool calls - done
115
- if (response.content) {
116
- currentMessages.push({ role: 'assistant', content: response.content });
117
- }
118
-
119
- // Hook: afterAgentLoop
120
- if (this.hooks) {
121
- await this.hooks.emit('afterAgentLoop', { messages: currentMessages, iteration: iterations });
122
- }
123
-
124
- return response;
125
- }
126
-
127
- return { content: '[Agent loop exceeded maximum iterations]', role: 'assistant' };
128
- }
129
-
130
- /**
131
- * Non-streaming chat (convenience method)
132
- */
133
- async chat(messages: ChatMessage[]): Promise<ChatResponse> {
134
- let fullContent = '';
135
- for await (const chunk of this.run(messages)) {
136
- fullContent += chunk;
137
- }
138
- return { content: fullContent, role: 'assistant' };
139
- }
140
- }