thatgfsj-code 0.3.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 (165) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +136 -0
  3. package/ROADMAP.md +61 -0
  4. package/dist/app/agent.d.ts +31 -0
  5. package/dist/app/agent.d.ts.map +1 -0
  6. package/dist/app/agent.js +106 -0
  7. package/dist/app/agent.js.map +1 -0
  8. package/dist/app/index.d.ts +39 -0
  9. package/dist/app/index.d.ts.map +1 -0
  10. package/dist/app/index.js +80 -0
  11. package/dist/app/index.js.map +1 -0
  12. package/dist/cmd/index.d.ts +12 -0
  13. package/dist/cmd/index.d.ts.map +1 -0
  14. package/dist/cmd/index.js +85 -0
  15. package/dist/cmd/index.js.map +1 -0
  16. package/dist/config/index.d.ts +39 -0
  17. package/dist/config/index.d.ts.map +1 -0
  18. package/dist/config/index.js +110 -0
  19. package/dist/config/index.js.map +1 -0
  20. package/dist/config/providers.d.ts +23 -0
  21. package/dist/config/providers.d.ts.map +1 -0
  22. package/dist/config/providers.js +152 -0
  23. package/dist/config/providers.js.map +1 -0
  24. package/dist/config/types.d.ts +33 -0
  25. package/dist/config/types.d.ts.map +1 -0
  26. package/dist/config/types.js +5 -0
  27. package/dist/config/types.js.map +1 -0
  28. package/dist/hooks/index.d.ts +31 -0
  29. package/dist/hooks/index.d.ts.map +1 -0
  30. package/dist/hooks/index.js +71 -0
  31. package/dist/hooks/index.js.map +1 -0
  32. package/dist/llm/anthropic.d.ts +17 -0
  33. package/dist/llm/anthropic.d.ts.map +1 -0
  34. package/dist/llm/anthropic.js +126 -0
  35. package/dist/llm/anthropic.js.map +1 -0
  36. package/dist/llm/gemini.d.ts +16 -0
  37. package/dist/llm/gemini.d.ts.map +1 -0
  38. package/dist/llm/gemini.js +107 -0
  39. package/dist/llm/gemini.js.map +1 -0
  40. package/dist/llm/index.d.ts +48 -0
  41. package/dist/llm/index.d.ts.map +1 -0
  42. package/dist/llm/index.js +174 -0
  43. package/dist/llm/index.js.map +1 -0
  44. package/dist/llm/openai.d.ts +17 -0
  45. package/dist/llm/openai.d.ts.map +1 -0
  46. package/dist/llm/openai.js +113 -0
  47. package/dist/llm/openai.js.map +1 -0
  48. package/dist/llm/provider.d.ts +33 -0
  49. package/dist/llm/provider.d.ts.map +1 -0
  50. package/dist/llm/provider.js +6 -0
  51. package/dist/llm/provider.js.map +1 -0
  52. package/dist/mcp/client.d.ts +83 -0
  53. package/dist/mcp/client.d.ts.map +1 -0
  54. package/dist/mcp/client.js +267 -0
  55. package/dist/mcp/client.js.map +1 -0
  56. package/dist/prompts/index.d.ts +27 -0
  57. package/dist/prompts/index.d.ts.map +1 -0
  58. package/dist/prompts/index.js +102 -0
  59. package/dist/prompts/index.js.map +1 -0
  60. package/dist/session/compactor.d.ts +48 -0
  61. package/dist/session/compactor.d.ts.map +1 -0
  62. package/dist/session/compactor.js +77 -0
  63. package/dist/session/compactor.js.map +1 -0
  64. package/dist/session/index.d.ts +48 -0
  65. package/dist/session/index.d.ts.map +1 -0
  66. package/dist/session/index.js +69 -0
  67. package/dist/session/index.js.map +1 -0
  68. package/dist/session/message.d.ts +26 -0
  69. package/dist/session/message.d.ts.map +1 -0
  70. package/dist/session/message.js +34 -0
  71. package/dist/session/message.js.map +1 -0
  72. package/dist/tools/file.d.ts +46 -0
  73. package/dist/tools/file.d.ts.map +1 -0
  74. package/dist/tools/file.js +100 -0
  75. package/dist/tools/file.js.map +1 -0
  76. package/dist/tools/git.d.ts +52 -0
  77. package/dist/tools/git.d.ts.map +1 -0
  78. package/dist/tools/git.js +112 -0
  79. package/dist/tools/git.js.map +1 -0
  80. package/dist/tools/index.d.ts +42 -0
  81. package/dist/tools/index.d.ts.map +1 -0
  82. package/dist/tools/index.js +92 -0
  83. package/dist/tools/index.js.map +1 -0
  84. package/dist/tools/search.d.ts +36 -0
  85. package/dist/tools/search.d.ts.map +1 -0
  86. package/dist/tools/search.js +223 -0
  87. package/dist/tools/search.js.map +1 -0
  88. package/dist/tools/shell.d.ts +50 -0
  89. package/dist/tools/shell.d.ts.map +1 -0
  90. package/dist/tools/shell.js +117 -0
  91. package/dist/tools/shell.js.map +1 -0
  92. package/dist/tools/types.d.ts +68 -0
  93. package/dist/tools/types.d.ts.map +1 -0
  94. package/dist/tools/types.js +57 -0
  95. package/dist/tools/types.js.map +1 -0
  96. package/dist/tui/index.d.ts +8 -0
  97. package/dist/tui/index.d.ts.map +1 -0
  98. package/dist/tui/index.js +8 -0
  99. package/dist/tui/index.js.map +1 -0
  100. package/dist/tui/input.d.ts +14 -0
  101. package/dist/tui/input.d.ts.map +1 -0
  102. package/dist/tui/input.js +50 -0
  103. package/dist/tui/input.js.map +1 -0
  104. package/dist/tui/output.d.ts +20 -0
  105. package/dist/tui/output.d.ts.map +1 -0
  106. package/dist/tui/output.js +70 -0
  107. package/dist/tui/output.js.map +1 -0
  108. package/dist/tui/repl.d.ts +25 -0
  109. package/dist/tui/repl.d.ts.map +1 -0
  110. package/dist/tui/repl.js +114 -0
  111. package/dist/tui/repl.js.map +1 -0
  112. package/dist/tui/welcome.d.ts +15 -0
  113. package/dist/tui/welcome.d.ts.map +1 -0
  114. package/dist/tui/welcome.js +86 -0
  115. package/dist/tui/welcome.js.map +1 -0
  116. package/dist/types.d.ts +38 -0
  117. package/dist/types.d.ts.map +1 -0
  118. package/dist/types.js +6 -0
  119. package/dist/types.js.map +1 -0
  120. package/dist/utils/diff.d.ts +22 -0
  121. package/dist/utils/diff.d.ts.map +1 -0
  122. package/dist/utils/diff.js +60 -0
  123. package/dist/utils/diff.js.map +1 -0
  124. package/dist/utils/project.d.ts +32 -0
  125. package/dist/utils/project.d.ts.map +1 -0
  126. package/dist/utils/project.js +89 -0
  127. package/dist/utils/project.js.map +1 -0
  128. package/docs/API_KEY_GUIDE.md +236 -0
  129. package/docs/FAQ.md +182 -0
  130. package/install.bat +63 -0
  131. package/install.ps1 +238 -0
  132. package/install.sh +113 -0
  133. package/package.json +36 -0
  134. package/src/app/agent.ts +140 -0
  135. package/src/app/index.ts +101 -0
  136. package/src/cmd/index.ts +96 -0
  137. package/src/config/index.ts +130 -0
  138. package/src/config/providers.ts +160 -0
  139. package/src/config/types.ts +46 -0
  140. package/src/hooks/index.ts +111 -0
  141. package/src/llm/anthropic.ts +146 -0
  142. package/src/llm/gemini.ts +127 -0
  143. package/src/llm/index.ts +209 -0
  144. package/src/llm/openai.ts +132 -0
  145. package/src/llm/provider.ts +38 -0
  146. package/src/mcp/client.ts +330 -0
  147. package/src/prompts/index.ts +123 -0
  148. package/src/session/compactor.ts +103 -0
  149. package/src/session/index.ts +81 -0
  150. package/src/session/message.ts +42 -0
  151. package/src/tools/file.ts +117 -0
  152. package/src/tools/git.ts +132 -0
  153. package/src/tools/index.ts +108 -0
  154. package/src/tools/search.ts +263 -0
  155. package/src/tools/shell.ts +136 -0
  156. package/src/tools/types.ts +122 -0
  157. package/src/tui/index.ts +8 -0
  158. package/src/tui/input.ts +56 -0
  159. package/src/tui/output.ts +83 -0
  160. package/src/tui/repl.ts +131 -0
  161. package/src/tui/welcome.ts +101 -0
  162. package/src/types.ts +42 -0
  163. package/src/utils/diff.ts +71 -0
  164. package/src/utils/project.ts +99 -0
  165. package/tsconfig.json +19 -0
package/install.sh ADDED
@@ -0,0 +1,113 @@
1
+ #!/bin/bash
2
+ # Thatgfsj Code Installer for macOS/Linux
3
+ # Usage: curl -sL https://raw.githubusercontent.com/Thatgfsj/thatgfsj-code/main/install.sh | bash
4
+
5
+ set -e
6
+
7
+ # Colors
8
+ RED='\033[0;31m'
9
+ GREEN='\033[0;32m'
10
+ YELLOW='\033[1;33m'
11
+ CYAN='\033[0;36m'
12
+ NC='\033[0m' # No Color
13
+
14
+ echo ""
15
+ echo -e "${CYAN} Thatgfsj Code 安装向导${NC}"
16
+ echo -e "${CYAN} =======================${NC}"
17
+ echo ""
18
+
19
+ # ============== Step 1: Check Node.js ==============
20
+ echo -e "${YELLOW}[*] 检查 Node.js...${NC}"
21
+
22
+ if command -v node &> /dev/null; then
23
+ NODE_VERSION=$(node --version)
24
+ MAJOR_VERSION=$(echo $NODE_VERSION | cut -d'v' -f2 | cut -d'.' -f1)
25
+ if [ "$MAJOR_VERSION" -ge 18 ]; then
26
+ echo -e "${GREEN}[✓] Node.js $NODE_VERSION 已安装${NC}"
27
+ else
28
+ echo -e "${RED}[✗] Node.js 版本过低,需要 v18+${NC}"
29
+ echo " 请访问 https://nodejs.org 升级"
30
+ exit 1
31
+ fi
32
+ else
33
+ echo -e "${YELLOW}[*] 未检测到 Node.js,开始安装...${NC}"
34
+
35
+ # Try Homebrew (macOS)
36
+ if command -v brew &> /dev/null; then
37
+ echo -e "${YELLOW}[*] 使用 Homebrew 安装...${NC}"
38
+ brew install node
39
+ # Try apt (Ubuntu/Debian)
40
+ elif command -v apt-get &> /dev/null; then
41
+ echo -e "${YELLOW}[*] 使用 apt 安装...${NC}"
42
+ curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
43
+ sudo apt-get install -y nodejs
44
+ # Try yum (CentOS/RHEL)
45
+ elif command -v yum &> /dev/null; then
46
+ echo -e "${YELLOW}[*] 使用 yum 安装...${NC}"
47
+ curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -
48
+ sudo yum install -y nodejs
49
+ else
50
+ echo -e "${RED}[✗] 未找到包管理器${NC}"
51
+ echo " 请手动安装: https://nodejs.org"
52
+ exit 1
53
+ fi
54
+ fi
55
+
56
+ # ============== Step 2: Clone/Update ==============
57
+ echo -e "${YELLOW}[*] 准备安装 Thatgfsj Code...${NC}"
58
+
59
+ INSTALL_DIR="$HOME/thatgfsj-code"
60
+
61
+ if [ -d "$INSTALL_DIR" ]; then
62
+ echo -e "${YELLOW}[*] 检测到已有安装,正在更新...${NC}"
63
+ cd "$INSTALL_DIR"
64
+ git pull origin main 2>/dev/null || {
65
+ echo -e "${YELLOW}[*] 更新失败,重新克隆...${NC}"
66
+ rm -rf "$INSTALL_DIR"
67
+ }
68
+ fi
69
+
70
+ if [ ! -d "$INSTALL_DIR" ]; then
71
+ echo -e "${YELLOW}[*] 克隆仓库...${NC}"
72
+ git clone https://github.com/Thatgfsj/thatgfsj-code.git "$INSTALL_DIR"
73
+ fi
74
+
75
+ cd "$INSTALL_DIR"
76
+
77
+ if [ ! -f "package.json" ]; then
78
+ echo -e "${RED}[✗] 安装目录无效${NC}"
79
+ exit 1
80
+ fi
81
+
82
+ echo -e "${GREEN}[✓] 代码准备完成: $INSTALL_DIR${NC}"
83
+
84
+ # ============== Step 3: Install Dependencies ==============
85
+ echo -e "${YELLOW}[*] 安装依赖...${NC}"
86
+
87
+ npm install
88
+ npm run build
89
+
90
+ echo -e "${GREEN}[✓] 依赖安装完成${NC}"
91
+
92
+ # ============== Step 4: Link Command ==============
93
+ echo -e "${YELLOW}[*] 设置命令...${NC}"
94
+
95
+ npm link
96
+
97
+ echo -e "${GREEN}[✓] 命令 'gfcode' 已可用${NC}"
98
+
99
+ # ============== Done ==============
100
+ echo ""
101
+ echo -e "${CYAN} ======================================${NC}"
102
+ echo -e "${GREEN} 安装完成!${NC}"
103
+ echo -e "${CYAN} ======================================${NC}"
104
+ echo ""
105
+ echo -e " ${WHITE}使用方法:${NC}"
106
+ echo -e " gfcode init - 重新配置"
107
+ echo -e " gfcode - 启动交互模式"
108
+ echo -e " gfcode '你的问题' - 直接提问"
109
+ echo -e " gfcode explain '代码' - 解释代码"
110
+ echo -e " gfcode debug '代码' - 调试代码"
111
+ echo ""
112
+ echo -e " 文档: ${CYAN}https://github.com/Thatgfsj/thatgfsj-code${NC}"
113
+ echo ""
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "thatgfsj-code",
3
+ "version": "0.3.0",
4
+ "description": "Thatgfsj Code - AI Coding Assistant",
5
+ "main": "dist/cmd/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "thatgfsj": "./dist/cmd/index.js",
9
+ "gfcode": "./dist/cmd/index.js"
10
+ },
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "start": "node dist/cmd/index.js",
14
+ "dev": "tsc && node dist/cmd/index.js",
15
+ "link": "npm link"
16
+ },
17
+ "keywords": [
18
+ "ai",
19
+ "cli",
20
+ "coding",
21
+ "assistant"
22
+ ],
23
+ "author": "Thatgfsj",
24
+ "license": "MIT",
25
+ "dependencies": {
26
+ "chalk": "^5.3.0",
27
+ "commander": "^11.1.0",
28
+ "inquirer": "^13.3.2",
29
+ "ora": "^7.0.1",
30
+ "readline": "^1.3.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^20.10.0",
34
+ "typescript": "^5.3.0"
35
+ }
36
+ }
@@ -0,0 +1,140 @@
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
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * App - Core application singleton
3
+ * Assembles all services: config, LLM, session, tools, hooks, prompts
4
+ * Replaces the scattered initialization in old src/index.ts
5
+ */
6
+
7
+ import { ConfigManager } from '../config/index.js';
8
+ import { LLMService } from '../llm/index.js';
9
+ import { SessionManager } from '../session/index.js';
10
+ import { ToolRegistry } from '../tools/index.js';
11
+ import { HookManager } from '../hooks/index.js';
12
+ import { SystemPromptBuilder } from '../prompts/index.js';
13
+ import { Agent } from './agent.js';
14
+ import type { ChatMessage } from '../types.js';
15
+
16
+ export class App {
17
+ config: ConfigManager;
18
+ llm: LLMService;
19
+ session: SessionManager;
20
+ tools: ToolRegistry;
21
+ hooks: HookManager;
22
+ prompts: SystemPromptBuilder;
23
+ private agent: Agent;
24
+
25
+ private constructor(
26
+ config: ConfigManager,
27
+ llm: LLMService,
28
+ session: SessionManager,
29
+ tools: ToolRegistry,
30
+ hooks: HookManager,
31
+ prompts: SystemPromptBuilder,
32
+ agent: Agent,
33
+ ) {
34
+ this.config = config;
35
+ this.llm = llm;
36
+ this.session = session;
37
+ this.tools = tools;
38
+ this.hooks = hooks;
39
+ this.prompts = prompts;
40
+ this.agent = agent;
41
+ }
42
+
43
+ /**
44
+ * Create and initialize the App
45
+ */
46
+ static async create(): Promise<App> {
47
+ // Load config
48
+ const config = await ConfigManager.load();
49
+ const aiConfig = config.getAIConfig();
50
+
51
+ // Create services
52
+ const llm = LLMService.fromConfig(aiConfig);
53
+ const session = new SessionManager();
54
+ const tools = new ToolRegistry();
55
+ const hooks = new HookManager();
56
+ const prompts = new SystemPromptBuilder({
57
+ cwd: process.cwd(),
58
+ tools: tools.list(),
59
+ permissionMode: 'ask',
60
+ });
61
+
62
+ // Create agent
63
+ const agent = new Agent(llm, tools, { hooks, maxIterations: 10 });
64
+
65
+ // Set up system prompt
66
+ session.addMessage('system', prompts.build());
67
+
68
+ return new App(config, llm, session, tools, hooks, prompts, agent);
69
+ }
70
+
71
+ /**
72
+ * Run a single prompt (non-interactive mode)
73
+ */
74
+ async runPrompt(prompt: string): Promise<string> {
75
+ this.session.addMessage('user', prompt);
76
+
77
+ let fullResponse = '';
78
+ for await (const chunk of this.agent.run(this.session.getMessages())) {
79
+ process.stdout.write(chunk);
80
+ fullResponse += chunk;
81
+ }
82
+
83
+ console.log(); // newline
84
+ this.session.addMessage('assistant', fullResponse);
85
+ return fullResponse;
86
+ }
87
+
88
+ /**
89
+ * Get the agent for streaming control
90
+ */
91
+ getAgent(): Agent {
92
+ return this.agent;
93
+ }
94
+
95
+ /**
96
+ * Get current config
97
+ */
98
+ getConfig(): ConfigManager {
99
+ return this.config;
100
+ }
101
+ }
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Thatgfsj Code - CLI Entry Point
5
+ * Simplified from old src/index.ts (655 lines → ~60 lines)
6
+ *
7
+ * Two modes:
8
+ * gfcode → Interactive REPL
9
+ * gfcode "prompt" → Single prompt mode
10
+ * gfcode init → Setup wizard
11
+ */
12
+
13
+ // Force UTF-8 on Windows
14
+ if (process.platform === 'win32') {
15
+ try {
16
+ require('child_process').execSync('chcp 65001', { stdio: 'ignore', windowsHide: true });
17
+ } catch {}
18
+ }
19
+
20
+ import { program } from 'commander';
21
+ import chalk from 'chalk';
22
+ import { App } from '../app/index.js';
23
+ import { REPL } from '../tui/repl.js';
24
+ import { WelcomeScreen } from '../tui/welcome.js';
25
+ import { ProjectContext } from '../utils/project.js';
26
+
27
+ // ==================== Error Handling ====================
28
+
29
+ process.on('uncaughtException', (error) => {
30
+ console.error(chalk.red('\n❌ Error:'), error.message);
31
+ process.exit(1);
32
+ });
33
+
34
+ process.on('unhandledRejection', (reason) => {
35
+ console.error(chalk.red('\n❌ Error:'), reason);
36
+ process.exit(1);
37
+ });
38
+
39
+ // ==================== CLI Commands ====================
40
+
41
+ program
42
+ .name('gfcode')
43
+ .description('🤖 Thatgfsj Code - AI Coding Assistant')
44
+ .version('0.3.0')
45
+ .argument('[prompt]', 'Task to execute (omit to start interactive mode)')
46
+ .option('-m, --model <model>', 'Specify model')
47
+ .option('-i, --interactive', 'Force interactive mode')
48
+ .option('--hooks', 'Enable audit logging hooks')
49
+ .action(async (prompt: string | undefined, options: { model?: string; interactive?: boolean; hooks?: boolean }) => {
50
+ try {
51
+ // Initialize app
52
+ const app = await App.create();
53
+
54
+ // Show welcome if no API key
55
+ WelcomeScreen.show(app.config.hasApiKey());
56
+
57
+ // Override model if specified
58
+ if (options.model) {
59
+ await app.config.save({ model: options.model });
60
+ }
61
+
62
+ // Enable hooks if requested
63
+ if (options.hooks) {
64
+ const { auditLogHook } = await import('../hooks/index.js');
65
+ app.hooks.register('afterToolCall', auditLogHook);
66
+ }
67
+
68
+ if (!prompt || options.interactive) {
69
+ // Interactive REPL mode
70
+ const repl = new REPL(app);
71
+ await repl.start();
72
+ } else {
73
+ // Single prompt mode
74
+ console.log(chalk.cyan('\n🤖 Thatgfsj Code\n'));
75
+ console.log(chalk.gray(ProjectContext.getSummary()));
76
+ console.log(chalk.gray('─'.repeat(40)));
77
+ console.log(chalk.cyan('\n> ') + prompt + '\n');
78
+
79
+ await app.runPrompt(prompt);
80
+ }
81
+ } catch (error: any) {
82
+ console.error(chalk.red(`\n❌ ${error.message}`));
83
+ process.exit(1);
84
+ }
85
+ });
86
+
87
+ // Init command - setup wizard
88
+ program
89
+ .command('init')
90
+ .description('配置 API Key 和模型')
91
+ .action(async () => {
92
+ await WelcomeScreen.interactiveSetup();
93
+ });
94
+
95
+ // Parse and run
96
+ program.parse(process.argv);
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Config Manager - Handles configuration loading and saving
3
+ * Migrated from old src/core/config.ts
4
+ */
5
+
6
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
7
+ import { join, dirname } from 'path';
8
+ import { homedir } from 'os';
9
+ import type { Config, AIConfig, ProviderName } from './types.js';
10
+ import { PROVIDERS, getApiKeyFromEnv } from './providers.js';
11
+
12
+ const DEFAULT_CONFIG: Config = {
13
+ model: 'Qwen/Qwen2.5-7B-Instruct',
14
+ apiKey: '',
15
+ temperature: 0.7,
16
+ maxTokens: 4096,
17
+ provider: 'siliconflow',
18
+ };
19
+
20
+ export class ConfigManager {
21
+ private configPath: string;
22
+ private config: Config;
23
+
24
+ private constructor(configPath: string, config: Config) {
25
+ this.configPath = configPath;
26
+ this.config = config;
27
+ }
28
+
29
+ /**
30
+ * Load configuration from file + environment variables
31
+ */
32
+ static async load(): Promise<ConfigManager> {
33
+ const configDir = join(homedir(), '.thatgfsj');
34
+ const configPath = join(configDir, 'config.json');
35
+
36
+ let config = { ...DEFAULT_CONFIG };
37
+
38
+ // Load from file
39
+ try {
40
+ if (existsSync(configPath)) {
41
+ const data = readFileSync(configPath, 'utf-8');
42
+ config = { ...config, ...JSON.parse(data) };
43
+ }
44
+ } catch {
45
+ // Use defaults if file is corrupted
46
+ }
47
+
48
+ // Resolve provider
49
+ config = ConfigManager.resolveProvider(config);
50
+
51
+ return new ConfigManager(configPath, config);
52
+ }
53
+
54
+ /**
55
+ * Resolve provider settings: API key from env, base URL, model
56
+ */
57
+ private static resolveProvider(config: Config): Config {
58
+ const provider = config.provider || 'siliconflow';
59
+ const providerConfig = PROVIDERS[provider];
60
+
61
+ if (!providerConfig) {
62
+ return { ...config, provider: 'siliconflow', baseUrl: PROVIDERS.siliconflow.baseUrl };
63
+ }
64
+
65
+ // Model: env MODEL > config > provider default
66
+ const model = process.env.MODEL || config.model || providerConfig.defaultModel;
67
+
68
+ // API key: env > config
69
+ let apiKey = config.apiKey;
70
+ if (provider === 'ollama') {
71
+ apiKey = '';
72
+ } else if (!apiKey) {
73
+ apiKey = getApiKeyFromEnv(provider);
74
+ }
75
+
76
+ // Base URL: config > provider default
77
+ const baseUrl = config.baseUrl || providerConfig.baseUrl;
78
+
79
+ return { ...config, provider, baseUrl, model, apiKey: apiKey || '' };
80
+ }
81
+
82
+ /**
83
+ * Get the current config
84
+ */
85
+ get(): Config {
86
+ return { ...this.config };
87
+ }
88
+
89
+ /**
90
+ * Get AIConfig for LLM providers
91
+ */
92
+ getAIConfig(): AIConfig {
93
+ return {
94
+ model: this.config.model,
95
+ apiKey: this.config.apiKey,
96
+ temperature: this.config.temperature,
97
+ maxTokens: this.config.maxTokens,
98
+ baseUrl: this.config.baseUrl,
99
+ provider: this.config.provider,
100
+ };
101
+ }
102
+
103
+ /**
104
+ * Get provider format (openai | anthropic | gemini)
105
+ */
106
+ getProviderFormat(): 'openai' | 'anthropic' | 'gemini' {
107
+ return PROVIDERS[this.config.provider]?.format || 'openai';
108
+ }
109
+
110
+ /**
111
+ * Update config and save to file
112
+ */
113
+ async save(updates: Partial<Config>): Promise<void> {
114
+ this.config = { ...this.config, ...updates };
115
+
116
+ const dir = dirname(this.configPath);
117
+ if (!existsSync(dir)) {
118
+ mkdirSync(dir, { recursive: true });
119
+ }
120
+
121
+ writeFileSync(this.configPath, JSON.stringify(this.config, null, 2));
122
+ }
123
+
124
+ /**
125
+ * Check if an API key is configured
126
+ */
127
+ hasApiKey(): boolean {
128
+ return !!this.config.apiKey;
129
+ }
130
+ }