thatgfsj-code 0.3.0 → 0.3.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 (262) hide show
  1. package/CHANGELOG.md +229 -0
  2. package/DEVELOPMENT.md +286 -0
  3. package/README.md +144 -79
  4. package/dist/agent/core.d.ts +56 -0
  5. package/dist/agent/core.d.ts.map +1 -0
  6. package/dist/agent/core.js +142 -0
  7. package/dist/agent/core.js.map +1 -0
  8. package/dist/agent/index.d.ts +8 -0
  9. package/dist/agent/index.d.ts.map +1 -0
  10. package/dist/agent/index.js +8 -0
  11. package/dist/agent/index.js.map +1 -0
  12. package/dist/agent/intent.d.ts +36 -0
  13. package/dist/agent/intent.d.ts.map +1 -0
  14. package/dist/agent/intent.js +146 -0
  15. package/dist/agent/intent.js.map +1 -0
  16. package/dist/agent/streaming.d.ts +50 -0
  17. package/dist/agent/streaming.d.ts.map +1 -0
  18. package/dist/agent/streaming.js +110 -0
  19. package/dist/agent/streaming.js.map +1 -0
  20. package/dist/core/ai-engine.d.ts +76 -0
  21. package/dist/core/ai-engine.d.ts.map +1 -0
  22. package/dist/core/ai-engine.js +394 -0
  23. package/dist/core/ai-engine.js.map +1 -0
  24. package/dist/core/cli.d.ts +21 -0
  25. package/dist/core/cli.d.ts.map +1 -0
  26. package/dist/core/cli.js +143 -0
  27. package/dist/core/cli.js.map +1 -0
  28. package/dist/core/config.d.ts +55 -0
  29. package/dist/core/config.d.ts.map +1 -0
  30. package/dist/core/config.js +162 -0
  31. package/dist/core/config.js.map +1 -0
  32. package/dist/core/context-compactor.d.ts +54 -0
  33. package/dist/core/context-compactor.d.ts.map +1 -0
  34. package/dist/core/context-compactor.js +197 -0
  35. package/dist/core/context-compactor.js.map +1 -0
  36. package/dist/core/hooks.d.ts +75 -0
  37. package/dist/core/hooks.d.ts.map +1 -0
  38. package/dist/core/hooks.js +146 -0
  39. package/dist/core/hooks.js.map +1 -0
  40. package/dist/core/permissions.d.ts +52 -0
  41. package/dist/core/permissions.d.ts.map +1 -0
  42. package/dist/core/permissions.js +237 -0
  43. package/dist/core/permissions.js.map +1 -0
  44. package/dist/core/session.d.ts +54 -0
  45. package/dist/core/session.d.ts.map +1 -0
  46. package/dist/core/session.js +147 -0
  47. package/dist/core/session.js.map +1 -0
  48. package/dist/core/skills.d.ts +60 -0
  49. package/dist/core/skills.d.ts.map +1 -0
  50. package/dist/core/skills.js +175 -0
  51. package/dist/core/skills.js.map +1 -0
  52. package/dist/core/state.d.ts +60 -0
  53. package/dist/core/state.d.ts.map +1 -0
  54. package/dist/core/state.js +106 -0
  55. package/dist/core/state.js.map +1 -0
  56. package/dist/core/subagent.d.ts +68 -0
  57. package/dist/core/subagent.d.ts.map +1 -0
  58. package/dist/core/subagent.js +142 -0
  59. package/dist/core/subagent.js.map +1 -0
  60. package/dist/core/system-prompt.d.ts +40 -0
  61. package/dist/core/system-prompt.d.ts.map +1 -0
  62. package/dist/core/system-prompt.js +138 -0
  63. package/dist/core/system-prompt.js.map +1 -0
  64. package/dist/core/tool-registry.d.ts +53 -0
  65. package/dist/core/tool-registry.d.ts.map +1 -0
  66. package/dist/core/tool-registry.js +138 -0
  67. package/dist/core/tool-registry.js.map +1 -0
  68. package/dist/core/types.d.ts +206 -0
  69. package/dist/core/types.d.ts.map +1 -0
  70. package/dist/core/types.js +127 -0
  71. package/dist/core/types.js.map +1 -0
  72. package/dist/index.d.ts +7 -0
  73. package/dist/{cmd/index.d.ts.map → index.d.ts.map} +1 -1
  74. package/dist/index.js +472 -0
  75. package/dist/index.js.map +1 -0
  76. package/dist/mcp/client.d.ts +1 -1
  77. package/dist/mcp/client.d.ts.map +1 -1
  78. package/dist/repl/index.d.ts +8 -0
  79. package/dist/repl/index.d.ts.map +1 -0
  80. package/dist/repl/index.js +8 -0
  81. package/dist/repl/index.js.map +1 -0
  82. package/dist/repl/input.d.ts +69 -0
  83. package/dist/repl/input.d.ts.map +1 -0
  84. package/dist/repl/input.js +139 -0
  85. package/dist/repl/input.js.map +1 -0
  86. package/dist/repl/loop.d.ts +184 -0
  87. package/dist/repl/loop.d.ts.map +1 -0
  88. package/dist/repl/loop.js +932 -0
  89. package/dist/repl/loop.js.map +1 -0
  90. package/dist/repl/output.d.ts +105 -0
  91. package/dist/repl/output.d.ts.map +1 -0
  92. package/dist/repl/output.js +199 -0
  93. package/dist/repl/output.js.map +1 -0
  94. package/dist/repl/welcome.d.ts +68 -0
  95. package/dist/repl/welcome.d.ts.map +1 -0
  96. package/dist/repl/welcome.js +254 -0
  97. package/dist/repl/welcome.js.map +1 -0
  98. package/dist/tools/file.d.ts +1 -1
  99. package/dist/tools/file.d.ts.map +1 -1
  100. package/dist/tools/git.d.ts +1 -1
  101. package/dist/tools/git.d.ts.map +1 -1
  102. package/dist/tools/index.d.ts +15 -35
  103. package/dist/tools/index.d.ts.map +1 -1
  104. package/dist/tools/index.js +31 -80
  105. package/dist/tools/index.js.map +1 -1
  106. package/dist/tools/search.d.ts +1 -1
  107. package/dist/tools/search.d.ts.map +1 -1
  108. package/dist/tools/shell.d.ts +11 -4
  109. package/dist/tools/shell.d.ts.map +1 -1
  110. package/dist/tools/shell.js +78 -42
  111. package/dist/tools/shell.js.map +1 -1
  112. package/dist/utils/diff-preview.d.ts +42 -0
  113. package/dist/utils/diff-preview.d.ts.map +1 -0
  114. package/dist/utils/diff-preview.js +174 -0
  115. package/dist/utils/diff-preview.js.map +1 -0
  116. package/dist/utils/index.d.ts +8 -0
  117. package/dist/utils/index.d.ts.map +1 -0
  118. package/dist/utils/index.js +8 -0
  119. package/dist/utils/index.js.map +1 -0
  120. package/dist/utils/memory.d.ts +81 -0
  121. package/dist/utils/memory.d.ts.map +1 -0
  122. package/dist/utils/memory.js +186 -0
  123. package/dist/utils/memory.js.map +1 -0
  124. package/dist/utils/project-context.d.ts +62 -0
  125. package/dist/utils/project-context.d.ts.map +1 -0
  126. package/dist/utils/project-context.js +197 -0
  127. package/dist/utils/project-context.js.map +1 -0
  128. package/docs/API_KEY_GUIDE.md +6 -0
  129. package/docs/FAQ.md +25 -3
  130. package/package.json +41 -9
  131. package/ROADMAP.md +0 -61
  132. package/dist/app/agent.d.ts +0 -31
  133. package/dist/app/agent.d.ts.map +0 -1
  134. package/dist/app/agent.js +0 -106
  135. package/dist/app/agent.js.map +0 -1
  136. package/dist/app/index.d.ts +0 -39
  137. package/dist/app/index.d.ts.map +0 -1
  138. package/dist/app/index.js +0 -80
  139. package/dist/app/index.js.map +0 -1
  140. package/dist/cmd/index.d.ts +0 -12
  141. package/dist/cmd/index.js +0 -85
  142. package/dist/cmd/index.js.map +0 -1
  143. package/dist/config/index.d.ts +0 -39
  144. package/dist/config/index.d.ts.map +0 -1
  145. package/dist/config/index.js +0 -110
  146. package/dist/config/index.js.map +0 -1
  147. package/dist/config/providers.d.ts +0 -23
  148. package/dist/config/providers.d.ts.map +0 -1
  149. package/dist/config/providers.js +0 -152
  150. package/dist/config/providers.js.map +0 -1
  151. package/dist/config/types.d.ts +0 -33
  152. package/dist/config/types.d.ts.map +0 -1
  153. package/dist/config/types.js +0 -5
  154. package/dist/config/types.js.map +0 -1
  155. package/dist/hooks/index.d.ts +0 -31
  156. package/dist/hooks/index.d.ts.map +0 -1
  157. package/dist/hooks/index.js +0 -71
  158. package/dist/hooks/index.js.map +0 -1
  159. package/dist/llm/anthropic.d.ts +0 -17
  160. package/dist/llm/anthropic.d.ts.map +0 -1
  161. package/dist/llm/anthropic.js +0 -126
  162. package/dist/llm/anthropic.js.map +0 -1
  163. package/dist/llm/gemini.d.ts +0 -16
  164. package/dist/llm/gemini.d.ts.map +0 -1
  165. package/dist/llm/gemini.js +0 -107
  166. package/dist/llm/gemini.js.map +0 -1
  167. package/dist/llm/index.d.ts +0 -48
  168. package/dist/llm/index.d.ts.map +0 -1
  169. package/dist/llm/index.js +0 -174
  170. package/dist/llm/index.js.map +0 -1
  171. package/dist/llm/openai.d.ts +0 -17
  172. package/dist/llm/openai.d.ts.map +0 -1
  173. package/dist/llm/openai.js +0 -113
  174. package/dist/llm/openai.js.map +0 -1
  175. package/dist/llm/provider.d.ts +0 -33
  176. package/dist/llm/provider.d.ts.map +0 -1
  177. package/dist/llm/provider.js +0 -6
  178. package/dist/llm/provider.js.map +0 -1
  179. package/dist/prompts/index.d.ts +0 -27
  180. package/dist/prompts/index.d.ts.map +0 -1
  181. package/dist/prompts/index.js +0 -102
  182. package/dist/prompts/index.js.map +0 -1
  183. package/dist/session/compactor.d.ts +0 -48
  184. package/dist/session/compactor.d.ts.map +0 -1
  185. package/dist/session/compactor.js +0 -77
  186. package/dist/session/compactor.js.map +0 -1
  187. package/dist/session/index.d.ts +0 -48
  188. package/dist/session/index.d.ts.map +0 -1
  189. package/dist/session/index.js +0 -69
  190. package/dist/session/index.js.map +0 -1
  191. package/dist/session/message.d.ts +0 -26
  192. package/dist/session/message.d.ts.map +0 -1
  193. package/dist/session/message.js +0 -34
  194. package/dist/session/message.js.map +0 -1
  195. package/dist/tools/types.d.ts +0 -68
  196. package/dist/tools/types.d.ts.map +0 -1
  197. package/dist/tools/types.js +0 -57
  198. package/dist/tools/types.js.map +0 -1
  199. package/dist/tui/index.d.ts +0 -8
  200. package/dist/tui/index.d.ts.map +0 -1
  201. package/dist/tui/index.js +0 -8
  202. package/dist/tui/index.js.map +0 -1
  203. package/dist/tui/input.d.ts +0 -14
  204. package/dist/tui/input.d.ts.map +0 -1
  205. package/dist/tui/input.js +0 -50
  206. package/dist/tui/input.js.map +0 -1
  207. package/dist/tui/output.d.ts +0 -20
  208. package/dist/tui/output.d.ts.map +0 -1
  209. package/dist/tui/output.js +0 -70
  210. package/dist/tui/output.js.map +0 -1
  211. package/dist/tui/repl.d.ts +0 -25
  212. package/dist/tui/repl.d.ts.map +0 -1
  213. package/dist/tui/repl.js +0 -114
  214. package/dist/tui/repl.js.map +0 -1
  215. package/dist/tui/welcome.d.ts +0 -15
  216. package/dist/tui/welcome.d.ts.map +0 -1
  217. package/dist/tui/welcome.js +0 -86
  218. package/dist/tui/welcome.js.map +0 -1
  219. package/dist/types.d.ts +0 -38
  220. package/dist/types.d.ts.map +0 -1
  221. package/dist/types.js +0 -6
  222. package/dist/types.js.map +0 -1
  223. package/dist/utils/diff.d.ts +0 -22
  224. package/dist/utils/diff.d.ts.map +0 -1
  225. package/dist/utils/diff.js +0 -60
  226. package/dist/utils/diff.js.map +0 -1
  227. package/dist/utils/project.d.ts +0 -32
  228. package/dist/utils/project.d.ts.map +0 -1
  229. package/dist/utils/project.js +0 -89
  230. package/dist/utils/project.js.map +0 -1
  231. package/src/app/agent.ts +0 -140
  232. package/src/app/index.ts +0 -101
  233. package/src/cmd/index.ts +0 -96
  234. package/src/config/index.ts +0 -130
  235. package/src/config/providers.ts +0 -160
  236. package/src/config/types.ts +0 -46
  237. package/src/hooks/index.ts +0 -111
  238. package/src/llm/anthropic.ts +0 -146
  239. package/src/llm/gemini.ts +0 -127
  240. package/src/llm/index.ts +0 -209
  241. package/src/llm/openai.ts +0 -132
  242. package/src/llm/provider.ts +0 -38
  243. package/src/mcp/client.ts +0 -330
  244. package/src/prompts/index.ts +0 -123
  245. package/src/session/compactor.ts +0 -103
  246. package/src/session/index.ts +0 -81
  247. package/src/session/message.ts +0 -42
  248. package/src/tools/file.ts +0 -117
  249. package/src/tools/git.ts +0 -132
  250. package/src/tools/index.ts +0 -108
  251. package/src/tools/search.ts +0 -263
  252. package/src/tools/shell.ts +0 -136
  253. package/src/tools/types.ts +0 -122
  254. package/src/tui/index.ts +0 -8
  255. package/src/tui/input.ts +0 -56
  256. package/src/tui/output.ts +0 -83
  257. package/src/tui/repl.ts +0 -131
  258. package/src/tui/welcome.ts +0 -101
  259. package/src/types.ts +0 -42
  260. package/src/utils/diff.ts +0 -71
  261. package/src/utils/project.ts +0 -99
  262. package/tsconfig.json +0 -19
package/CHANGELOG.md ADDED
@@ -0,0 +1,229 @@
1
+ # Changelog
2
+
3
+ All notable changes to **Thatgfsj Code** are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/) and the project adheres to
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ### Planned
10
+ - 实现真实的 provider 工具调用解析(OpenAI `delta.tool_calls`、Anthropic
11
+ `tool_use` block、Gemini `functionCall`),让已注册的 Tool 真正参与 Agent Loop
12
+ - `install.sh` / `install.ps1` 默认分支自适应(`master` ↔ `main`)
13
+ - 移除 README 中失效的 `git.io/thatgfsj` 短链,全部改回 `raw.githubusercontent.com`
14
+ - MCP 客户端 `this.process?.connected` → `this.process?.stdin?.writable`
15
+
16
+ ---
17
+
18
+ ## [0.3.1] - 2026-07-06
19
+
20
+ ### Added
21
+ - **「已保存的模型」作为 `/model` 主视图**:0.2.x 进 `/model` 先列内置模型,
22
+ 这版改为先列 `~/.thatgfsj/models.json` 里**已经保存的模型**(显示 id +
23
+ `ctx` + `thinking` + `note/provider`),与 1.0.4 的 `ModelSelector` 行为
24
+ 对齐。内置 provider 模型列表作为辅助信息保留在底部。
25
+ - **`+ 添加新模型` 向导**:在 `/model` 选 `a` / `add` / `+` 启动完整向导:
26
+ 1. **步骤 1/6: 选择 provider**(10 个:8 个内置 + `custom_openai` + `custom_anthropic`)
27
+ 2. **步骤 2/6: 输入 API Key**(custom_* 可选跳过)
28
+ 3. **步骤 3/6: 输入 baseUrl**(仅 custom_*;要求 `https://.../v1` 形式)
29
+ 4. **步骤 4/6: 输入模型 id**
30
+ 5. **步骤 5/6: 上下文长度 (MiB)**(默认 8;常见 8 / 32 / 128 / 200)
31
+ 6. **步骤 6/6: 思考强度**(`none` / `low` / `medium` / `high` / `max`,默认 `none`)
32
+ 完成后:写入 `~/.thatgfsj/models.json`,同步切换到新模型(更新
33
+ `AIEngine` + `~/.thatgfsj/config.json`),并把 `[system: model ... added]`
34
+ 注入 `SessionManager`,让 LLM 下一轮看到。
35
+ - **`/edit` 修改已保存模型**:1.0.4 时代就期望的能力,本次补齐:
36
+ - `/edit` 列出全部已保存模型,输入编号或完整 id 进入
37
+ - `/edit 1` / `/edit Qwen3-32B` 直接跳到对应模型
38
+ - 修改 `ctx` / `thinking` / `note`,空回车保留旧值,`-` 清空字段
39
+ - **`/provider` 支持自定义 provider**:
40
+ - 新增 `custom_openai`(OpenAI 兼容中转站)与 `custom_anthropic`
41
+ (Anthropic 兼容中转站)两项
42
+ - 选中后立刻提示输入 baseUrl,跳过内置的 `envKey` 校验
43
+ - 自动跟进 `/model` 让你顺便挑 / 添加一个模型
44
+ - **`SavedModel` 类型导出**(`src/repl/loop.ts`):
45
+ ```ts
46
+ interface SavedModel {
47
+ id: string;
48
+ addedAt?: number;
49
+ ctx?: number; // 上下文长度,单位 MiB
50
+ thinking?: 'none' | 'low' | 'medium' | 'high' | 'max';
51
+ note?: string; // 备注(如 provider id、quant 说明等)
52
+ }
53
+ ```
54
+ - **6 个新单元测试**(`tests/unit-wizard.test.js`):覆盖 v0.2.x → v0.3.x
55
+ history 形状的透明迁移、`appendSavedModel` 大小写不敏感去重 + canonical
56
+ casing 保留、`replaceSavedModel` 单条更新、`SavedModel` 形状 JSON 往返。
57
+
58
+ ### Fixed
59
+ - **appendSavedModel casing**:之前大小写不一致的同一模型会被当成两条;
60
+ 现在若已经存了 `Qwen3-32B`,再次输入 `qwen3-32b` 仍然只保留 `Qwen3-32B`
61
+ (原始拼写规范)。
62
+
63
+ > ⚠️ 上游 npm 上 `0.3.0` 已经被 1.0.4 时代占用过,因此本次发布使用 `0.3.1`,
64
+ > 仅是 patch 编号变化,feature 内容就是上面这份。
65
+
66
+ ---
67
+
68
+ ## [0.2.3] - 2026-07-06
69
+
70
+ ### Added
71
+ - **中文命令别名**:1.0.4 老版本支持的 `/模型`、`/提供商`、`/提供商切换`、
72
+ `/帮助`、`/退出`、`/清除`、`/清屏`、`/历史`、`/工具`、`/上下文`、
73
+ `/模型列表` 现在都被识别。同时支持全角斜杠 `/模型`。详见
74
+ `src/repl/loop.ts::handleCommand`。
75
+ - **`/model`「当前」标记**:即使当前模型不在内置列表里(老配置 / 自定义 id),
76
+ `/model` 现在会在顶部显示一行 `⮕ 当前 (非内置): <id>`,并保留内置 / 历史
77
+ 列表中的 `✓` 标记逻辑。
78
+ - **3 个新单元测试**(`tests/unit-config.test.js`):验证 0.2.3 的
79
+ `ConfigManager` 行为契约(不再有 silent fallback 警告、从 `CUSTOM_BASE_URL`
80
+ / `OPENAI_API_KEY` 派生 baseUrl、识别 8 个 provider name)。
81
+
82
+ ### Fixed
83
+ - **legacy provider 名称崩溃**:0.2.2 的 `ConfigManager.resolveProvider` 对
84
+ 任何不在白名单的 provider(如 1.0.4 的 `custom_openai`)会强行 overwrite
85
+ 成 `siliconflow` 并打印 `Unknown provider: custom_openai, falling back to
86
+ siliconflow` 警告,结果用户的 `MiniMax-M3` 等真实配置被悄悄丢掉,下一个
87
+ AI 请求直接撞上 401。0.2.3 改为:
88
+ - 保留用户的 provider id 不动。
89
+ - 从 `CUSTOM_BASE_URL` / `BASE_URL` 环境变量派生 baseUrl,如果都没有则
90
+ 退到 `https://api.openai.com/v1`。
91
+ - API Key 从 `OPENAI_API_KEY` / `CUSTOM_API_KEY` / config 里取。
92
+ - 不再打「Unknown provider」警告。
93
+ - **空 prompt / 引号包裹 prompt**:`REPLInput.prompt()` 收到被意外粘贴成
94
+ `""` 或 `''` 的内容时,0.2.3 会在 loop 里剥离外层引号再判断是否为空。
95
+ 这避免了用户从 markdown 复制示例时把空串丢给 LLM(命中 `[API Error: 401]`)。
96
+ - **DEFAULT_CONFIG 太老**:`ConfigManager` 默认从 `Qwen2.5-7B-Instruct`
97
+ 升级到 `Qwen3-235B-A22B-Instruct-2507`(或 `Qwen3-32B` 作为
98
+ `PROVIDERS.siliconflow.defaultModel`)。
99
+
100
+ ### Changed
101
+ - **刷新内置模型列表**(`src/repl/welcome.ts` + `src/core/types.ts`):
102
+ - SiliconFlow:Qwen3-235B / Qwen3-Coder-480B / Kimi-K2 / DeepSeek-V3.2 /
103
+ ERNIE-4.5 等。
104
+ - MiniMax:M3(推荐)、M2.5、M2.1(默认从 `MiniMax-M2.5` 升到 `MiniMax-M3`)。
105
+ - OpenAI:GPT-4.1 / GPT-4.1-mini / GPT-4o / o4-mini / GPT-5。
106
+ - Anthropic:Claude-Haiku-4.5 / Claude-Sonnet-4.5 / Claude-Opus-4.1。
107
+ - Gemini:Gemini-2.5-Flash / Gemini-2.5-Pro / Gemini-2.0-Flash。
108
+ - Kimi:Moonshot-V1-128K / Kimi-K2。
109
+ - DeepSeek:DeepSeek-V3(带 R1 推理)。
110
+ - ERNIE:ERNIE-4.5-8K(百度最新旗舰)。
111
+ - **`PROVIDERS` 默认模型**全部更新到 2025+ 推荐(Qwen3-32B、MiniMax-M3、
112
+ GPT-4.1-mini、Claude-Haiku-4.5、Llama3、Gemini-2.5-Flash、Kimi-K2 等)。
113
+
114
+ ---
115
+
116
+ ## [0.2.2] - 2026-07-06
117
+
118
+ ### Added
119
+ - **`/model` / `/provider` REPL 命令**:1.0.4 老版本里这两个交互式切换命令
120
+ 在 0.2.x 系列中曾经被删掉,这次重新实现并对齐新代码结构,并且从
121
+ `thatgfsj-code@1.0.4` 的 TUI 版本里借鉴并移植了两项核心体验:
122
+ - `/model` 列出当前 provider 的所有模型(含 `✓` 标记当前选中),输入编号
123
+ 或完整 model id 切换;立即写入 `~/.thatgfsj/config.json` 并通过
124
+ `AIEngine.updateConfig()` 让下一次流式请求生效。空输入回车保持当前,
125
+ Ctrl+C 取消。
126
+ - **历史记录持久化**(借鉴 1.0.4 的 `saveModelToHistory`):切换过的
127
+ model id 写入 `~/.thatgfsj/models.json`,下次打开 `/model` 时会在
128
+ 内置列表上方显示「最近使用」段(最多 5 条,按时间排序,可通过 `r1`..`r5`
129
+ 快捷选取,自动 dedup)。
130
+ - **任意 model id**:输入既不在内置列表也不在历史里的字符串,按自定义模型
131
+ 处理(适合 OpenAI-compatible 中转站 / Ollama 自定义模型名场景)。
132
+ - `/provider` 列出 9 个支持的 provider,选完自动跟进 `/model` 让用户选
133
+ 新 provider 下的模型;如果新 provider 缺少对应的 API Key 环境变量,会提示
134
+ 用户先 `gfcode init` 或 `export`。
135
+ - 两个命令都接受带或不带前导 `/`(与 1.0.4 的 `/模型` 老习惯兼容)。
136
+ - 切换后会把一行 `[system: ... switched to ...]` 注入 `SessionManager`,
137
+ 让 LLM 在下一轮看到变更而不丢失上下文。
138
+ - **`AIEngine.updateConfig()` / `getConfig()`**:暴露给 REPL 内部用,支持
139
+ 在不重启引擎的情况下替换 `model` / `provider` / `baseUrl` / `temperature` /
140
+ `maxTokens`。
141
+ - **34 个单元 / 冒烟测试**(`tests/`):npm 内置 `node:test`,覆盖:
142
+ - `smoke-cli.test.js` — `--version` / `--help` / 子命令空参守卫 / `bash -n`。
143
+ - `unit-session.test.js` — `SessionManager` 拦截「已中断」、相邻助手去重、
144
+ `truncate` 保留 system。
145
+ - `unit-diff.test.js` — `DiffPreview.compare` 边界(unicode / 大输入 / CRLF /
146
+ 空输入)。
147
+ - `unit-switcher.test.js` — `/model` 的纯 helper(编号 / id 解析)
148
+ 与 `AIEngine.updateConfig` 行为。
149
+ - **新增** `unit-model-history.test.js` — `~/.thatgfsj/models.json` 的
150
+ 读写、dedup、corrupt-file 容错(`tests` 5 个 case)。
151
+
152
+ ### Fixed
153
+ - **ESM `require()` 报错**:`src/index.ts` 顶层原先使用
154
+ `require('child_process')` / `require('fs')`,在 `"type": "module"` 项目里
155
+ 直接抛 `ReferenceError: require is not defined`,导致 CLI 在包含 `package.json`
156
+ 的项目目录下完全无法启动。改为 `createRequire(import.meta.url)` 兼容。
157
+ - **`--version` 与 banner 打印老版本号**:`src/index.ts:102` 与
158
+ `src/repl/output.ts:198` 硬编码 `0.2.0`/`v0.2.0`,而 `package.json` 是 `0.2.1`
159
+ 甚至后续 release。两处都改为从 `package.json` 动态读取。
160
+ - **REPL Ctrl+C 无法退出**:`src/repl/loop.ts` 的全局 SIGINT 处理只打
161
+ warning,从来不调用 `this.running = false`。新增 `REPLInput.requestCancel()`,
162
+ 在空输入连按两次 Ctrl+C 时正确退出。
163
+ - **`executeTask` 助手消息丢失**:单命令模式下助手回复从来没写回
164
+ `SessionManager`,导致同名 SessionManager 实例对多轮 prompt 拿不到上下文。
165
+ - **`(this.ai as any).chatStream`** 多余 `any` 强转:`this.ai` 已经过
166
+ null-check,删掉即可。
167
+ - **`extractToolCalls` 死分支**:`src/core/ai-engine.ts` 中 `if (…includes('tool_use'))
168
+ return undefined; return undefined;` 等价于永远返回 `undefined`。改为单行
169
+ `return undefined;` 并添加注释说明当前实际无法解析 tool call(属于已知限制)。
170
+ - **欢迎页 banner 字样写错**:`src/repl/welcome.ts` 顶部仍写着 `Claude Code`,
171
+ 与项目名 `Thatgfsj Code` 不一致。已更正。
172
+
173
+ ### Changed
174
+ - **`init` 命令动态 import 去重**:`src/index.ts:54` 的
175
+ `await import('./repl/welcome.js')` 与顶部静态 import 重复,删除。
176
+ - **`handleModelSwitch`** 在 `src/index.ts` 中属于死代码(无调用者),删除。
177
+ - **欢迎页标题框宽度自适应**:把 banner 顶部的 `Claude Code ` 换成
178
+ `Thatgfsj Code ` 后调整间隔符长度,保证盒子保持 62 列对齐。
179
+
180
+ ### Documentation
181
+ - 新增 `DEVELOPMENT.md`:覆盖架构图、构建/测试流程、添加 provider / 命令 / tool
182
+ 的步骤、跨平台注意事项、发布流程。
183
+ - 新增本文件 `CHANGELOG.md`。
184
+ - 更新 `README.md`:替换失效的 `git.io/thatgfsj` 安装链接为 GitHub raw 直链;
185
+ 补上 `gfcode init` 使用示例与所有顶层 / 子命令 flag;附 REPL 内置命令列表
186
+ 以及 Node ≥ 18 的前置条件。
187
+ - `docs/FAQ.md`、`docs/API_KEY_GUIDE.md` 交叉链接到 `DEVELOPMENT.md`。
188
+
189
+ ### Packaging
190
+ - `package.json` 新增 `files`、`engines`、`repository`、`bugs`、`homepage`
191
+ 字段;原先没有 `files` 白名单会导致 `npm publish` 不会自动包含 `dist/`
192
+ (dist 在 `.gitignore` 里),现在通过显式白名单确保 tarball 包含
193
+ 构建产物与文档。
194
+ - 新增 `scripts.test`、`scripts.test:silent`、`scripts.prepublishOnly`,
195
+ 发布前自动跑测试和 build,防止 release 出错。
196
+
197
+ ### Tests
198
+ - 新增 `tests/` 目录,使用 Node 内置 `node --test` 跑通:
199
+ - `tests/smoke-cli.test.js` — `dist/index.js --version` / `--help` / `chat`
200
+ / `explain` / `debug` / `template` 空参守卫;`bash -n install.sh` 语法。
201
+ - `tests/unit-session.test.js` — `SessionManager` 拦截「已中断」污染、
202
+ 相邻助手消息去重、`truncate` 保留 system。
203
+ - `tests/unit-diff.test.js` — `DiffPreview.compare` 在相同 / 不同 /
204
+ 空 / unicode / CRLF 输入下的行为。
205
+
206
+ ---
207
+
208
+ ## [0.2.1] - 2026-07-XX
209
+
210
+ ### Fixed
211
+ - REPL 数字小键盘方向键响应(升级到 `@inquirer/input`,不再用 Node `readline`
212
+ 直接 `.question()`)。
213
+ - 流式输出保留完整内容、不再 reset 终端 / 跳顶,用户可滚动查看历史。
214
+ - 修复 AI 输出 `[已中断]` 等污染字符串时反复循环同一段的 bug(`SessionManager`
215
+ 拦截 `已中断` / `think` block,`getMessages()` 做相邻助手去重)。
216
+
217
+ ### Added
218
+ - `src/repl/welcome.ts` Claude Code 风格欢迎页与 `gfcode init` 配置向导。
219
+ - `src/tools/{file,shell,git,search}.ts` 完整工具实现,并通过
220
+ `ToolRegistry` 注册到 AI Engine。
221
+ - `src/agent/{core,intent,streaming}.ts` Agent Loop、意图识别、流式输出。
222
+ - `src/utils/{diff-preview,memory,project-context}.ts`。
223
+ - `src/mcp/client.ts`:MCP 客户端雏形。
224
+
225
+ ---
226
+
227
+ ## [0.1.0] - initial
228
+
229
+ - 初版发布:基础 `explain` / `debug` / `chat` / `template` 命令与 REPL 雏形。
package/DEVELOPMENT.md ADDED
@@ -0,0 +1,286 @@
1
+ # Development Guide — Thatgfsj Code
2
+
3
+ This document is for contributors / maintainers. End users should read
4
+ [`README.md`](./README.md) instead.
5
+
6
+ ---
7
+
8
+ ## 1. Overview
9
+
10
+ **Thatgfsj Code** is a Claude Code–style interactive AI coding assistant that
11
+ runs entirely in your terminal. It is written in TypeScript, published as an
12
+ ESM npm package, distributed under MIT, and targets **Node.js ≥ 18**.
13
+
14
+ The tool exposes:
15
+
16
+ - A non-interactive single-shot CLI (`gfcode "..."`).
17
+ - An interactive REPL (`gfcode` with no args).
18
+ - Subcommands: `init`, `explain`, `debug`, `chat`, `template`.
19
+
20
+ The release pipeline is: **bug fix → docs → smoke test → git commit + tag →
21
+ push → GitHub release → `npm publish`**. Everything in `src/` compiles to
22
+ `dist/` via `tsc`; the `package.json` `files` whitelist decides what ships
23
+ in the npm tarball.
24
+
25
+ ---
26
+
27
+ ## 2. Architecture
28
+
29
+ ```
30
+ ┌──────────────────────────────────────────────────────────────┐
31
+ │ src/index.ts │
32
+ │ CLI entry · commander program · subcommand dispatch │
33
+ │ (init / explain / debug / chat / template / default REPL) │
34
+ └─────────────────────────┬────────────────────┬───────────────┘
35
+ │ │
36
+ ┌───────────▼──────────┐ ┌───────▼─────────────┐
37
+ │ AI Engine + Tools │ │ REPL Loop │
38
+ │ src/core/ai-engine │ │ src/repl/{loop,...} │
39
+ │ src/tools/{file,…} │ │ @inquirer/input │
40
+ └───────────┬──────────┘ └───────┬─────────────┘
41
+ │ │
42
+ ┌───────────▼────────────────────▼───────────────┐
43
+ │ Core / Utils / Agent / MCP │
44
+ │ src/core/{config,session,types,subagent,…} │
45
+ │ src/utils/{diff-preview,memory,project-…} │
46
+ │ src/agent/{core,intent,streaming} │
47
+ │ src/mcp/client.ts │
48
+ └────────────────────────────────────────────────┘
49
+ ```
50
+
51
+ ### Module responsibilities
52
+
53
+ | Layer | Files | Job |
54
+ |-----------|----------------------------------------------|--------------------------------------------------------------------|
55
+ | Entry | `src/index.ts` | commander program, subcommand handlers, REPL bootstrap |
56
+ | REPL | `src/repl/{input,output,loop,welcome}.ts` | interactive prompt, ANSI output, SIGINT, welcome/init wizard |
57
+ | Core | `src/core/{ai-engine,config,session,…}.ts` | AI provider abstraction, config loading, session state |
58
+ | Tools | `src/tools/{file,shell,git,search}.ts` | file I/O, shell exec, git ops, code search |
59
+ | Agent | `src/agent/{core,intent,streaming}.ts` | intent classification, agent loop, streamed terminal output |
60
+ | Utils | `src/utils/{diff-preview,memory,…}.ts` | side-effect-free helpers |
61
+ | MCP | `src/mcp/client.ts` | Model Context Protocol stdio client |
62
+
63
+ ### Data flow on a single prompt
64
+
65
+ ```
66
+ User ──▶ commander ──▶ executeTask(prompt)
67
+
68
+ ├─▶ ConfigManager.load() (process.env + ~/.thatgfsj/config.json)
69
+
70
+ ├─▶ AIEngine.chatStream(messages) ──▶ provider base URL /v1/chat/completions
71
+ │ or Anthropic /messages
72
+ │ or Gemini :generateContent
73
+
74
+ └─▶ process.stdout.write(chunks) ──▶ terminal (scrollable history)
75
+ ```
76
+
77
+ ---
78
+
79
+ ## 3. Build & install
80
+
81
+ ### Prerequisites
82
+
83
+ - Node.js ≥ 18 (project uses native `fetch`, `AbortController`, top-level `await`,
84
+ `node --test`).
85
+ - npm ≥ 9 (so `npm publish` understands the `files` whitelist).
86
+ - Git (for the install scripts and during release).
87
+
88
+ ### Local development
89
+
90
+ ```bash
91
+ git clone https://github.com/Thatgfsj/thatgfsj-code.git
92
+ cd thatgfsj-code
93
+ npm install
94
+ npm run build # produces ./dist
95
+ npm test # node --test tests/
96
+ npm start # node dist/index.js (interactive REPL)
97
+ npm run dev # tsc && node dist/index.js
98
+
99
+ # Optional: link globally so `gfcode` is in your PATH
100
+ npm link
101
+ ```
102
+
103
+ `npm install` after cloning will trigger `prepublishOnly` if you have it
104
+ configured (we do). Always run `npm run build` after editing source so the
105
+ `dist/` matches `src/`.
106
+
107
+ ### Platform notes
108
+
109
+ - **Windows**: `src/index.ts` invokes `chcp 65001` synchronously before any
110
+ output to force UTF-8 in the legacy Windows console. Done via
111
+ `createRequire(import.meta.url)` so it works under `"type": "module"`.
112
+ - **Numeric keypad arrows**: the REPL uses `@inquirer/input` instead of Node's
113
+ built-in `readline.question()` because the latter doesn't reliably translate
114
+ numeric-keypad ANSI escape sequences on Windows terminals (Bug #1 history).
115
+ - **Streaming output**: `process.stdout.write` directly, never `rl.question`
116
+ inside the loop — the latter would reset the terminal and lose scrollback.
117
+
118
+ ---
119
+
120
+ ## 4. Adding a provider
121
+
122
+ 1. Add the env-var map to `src/core/config.ts` (`envKeys`).
123
+ 2. Add a `*_MODELS` array to `src/repl/welcome.ts` and wire it through
124
+ `getModelsForProvider()` and `interactiveSetup()` (numbered choice).
125
+ 3. Update `WelcomeScreen.hasApiKey()` in `src/repl/welcome.ts` to include the
126
+ new env-var name.
127
+ 4. If the provider uses a non-OpenAI request format, extend the dispatcher
128
+ in `src/core/ai-engine.ts::streamRequest`.
129
+ 5. Update `docs/API_KEY_GUIDE.md` and `README.md`'s provider list.
130
+
131
+ No other files should care about the specific provider.
132
+
133
+ ---
134
+
135
+ ## 5. Adding a built-in CLI command
136
+
137
+ `src/index.ts` is the single `commander` program. Register a new command
138
+ **before** `program.parse(process.argv)`:
139
+
140
+ ```ts
141
+ program
142
+ .command('foo')
143
+ .description('...')
144
+ .argument('<bar>', '...')
145
+ .action(async (bar) => { /* ... */ });
146
+ ```
147
+
148
+ For REPL-internal slash-style commands (visible only inside the interactive
149
+ loop), add a new `case` to `src/repl/loop.ts::handleCommand`.
150
+
151
+ ### Built-in REPL commands (current)
152
+
153
+ | Command | Behavior |
154
+ | ---------------- | -------------------------------------------------------------------------------- |
155
+ | `help` | Show built-in command list |
156
+ | `exit` / `quit` | Leave the REPL |
157
+ | `clear` | Clear the screen |
158
+ | `context` | Show the current project context |
159
+ | `history` | Show the command history for this session |
160
+ | `tools` | List registered tools |
161
+ | `models` | Read-only listing of the current provider's models |
162
+ | `providers` | Read-only listing of all providers |
163
+ | `/model` | **Interactive picker — actually switches the active model** for the current provider. Persists to `~/.thatgfsj/config.json` and calls `AIEngine.updateConfig()`. |
164
+ | `/provider` | **Interactive picker — switches provider + chains into `/model`** for the new one. Warns if the corresponding env-var / saved API key is missing. |
165
+ | `Ctrl+C` | Aborts the in-flight stream once, exits after two empty-prompt cancels. |
166
+
167
+ Both `/model` and `/provider` accept either a numeric index or the exact
168
+ provider/model id; pressing Enter with no input keeps the current value. After
169
+ a switch, `REPLLoop` injects a one-line `[system: ... switched to ...]`
170
+ message into the session so the LLM can see the change in the next turn.
171
+
172
+ ---
173
+
174
+ ## 6. Adding a tool
175
+
176
+ Tools implement the `Tool` interface declared in `src/core/types.ts`:
177
+
178
+ ```ts
179
+ export interface Tool {
180
+ name: string;
181
+ description: string;
182
+ parameters: ToolParameter[];
183
+ execute(params: any, ctx: ToolContext): Promise<ToolResult>;
184
+ }
185
+ ```
186
+
187
+ Steps:
188
+ 1. Create `src/tools/<name>.ts` exporting a class implementing `Tool`.
189
+ 2. Register it in `src/tools/index.ts::getBuiltInTools()` so both the REPL
190
+ `REPLLoop.init` and `executeTask` pick it up automatically.
191
+
192
+ > **Known limitation (0.2.2):** `src/core/ai-engine.ts::extractToolCalls`
193
+ > currently returns `undefined`. Tools are registered but never actually
194
+ > invoked from a streamed response. Wiring this up requires parsing each
195
+ > provider's native `tool_calls` delta and is tracked under "Unreleased" in
196
+ > `CHANGELOG.md`. Until then, `ToolRegistry` is a forward-compatible seam.
197
+
198
+ ---
199
+
200
+ ## 7. MCP integration
201
+
202
+ `src/mcp/client.ts` defines a small stdio MCP client. It spawns a child
203
+ process and exchanges JSON-RPC via newline-delimited messages. The current
204
+ shape is intentionally minimal — see the file for the protocol mapping.
205
+
206
+ ---
207
+
208
+ ## 8. Cross-platform & UX details
209
+
210
+ - **Encoding**: Windows-only `chcp 65001` in `src/index.ts:9-15`. Skipped
211
+ silently if it fails.
212
+ - **Color**: `chalk` v5 is ESM-only; the project is `"type": "module"` so
213
+ this is fine.
214
+ - **Spinner**: `ora` v7 with `dots` spinner; cyan.
215
+ - **History**: local to a single REPL session (not persisted across runs).
216
+
217
+ ---
218
+
219
+ ## 9. Tests
220
+
221
+ ```bash
222
+ npm test # runs node --test tests/ (full TAP output)
223
+ npm run test:silent # dot reporter, for noisy CI logs
224
+ ```
225
+
226
+ Tests live in `tests/` and use Node's built-in `node:test` runner — no
227
+ vitest / jest dev-dep. They cover:
228
+
229
+ - Smoke tests of the compiled `dist/index.js` binary (`--version`, `--help`,
230
+ subcommand empty-arg guards, `bash -n install.sh`).
231
+ - Boundary tests of pure modules (`SessionManager`, `DiffPreview`,
232
+ `REPLOutput`) — empty input, unicode, CRLF, very long input.
233
+ - No network. No mocked API keys. No shell-out side-effects other than the
234
+ install script's `bash -n` parse-check.
235
+
236
+ CI integration: if you set this up later, run `npm test && npm run build`
237
+ in the job; the `prepublishOnly` script already chains them.
238
+
239
+ ---
240
+
241
+ ## 10. Release process
242
+
243
+ 1. Edit `package.json` `version` (semver).
244
+ 2. Add a new section at the top of `CHANGELOG.md`.
245
+ 3. `npm test && npm run build` locally — make sure both are green.
246
+ 4. `git add -A && git commit -m "release: vX.Y.Z"`
247
+ 5. `git tag vX.Y.Z`
248
+ 6. `git push origin main --follow-tags`
249
+ 7. `gh release create vX.Y.Z --title "vX.Y.Z" --notes-file CHANGELOG.md`
250
+ (the section for the new version is the top entry; trim if needed).
251
+ 8. `npm publish --access public` (a `prepublishOnly` step in `package.json`
252
+ re-runs tests + build, so you can't accidentally publish a stale build).
253
+
254
+ ### Semver policy
255
+
256
+ - **patch** (`Z`): bug fixes, docs, refactors, no public API change.
257
+ - **minor** (`Y`): new public command, new provider, new tool.
258
+ - **major** (`X`): breaking change to the CLI surface or the JS API surface
259
+ (currently no JS API is stable).
260
+
261
+ ---
262
+
263
+ ## 11. Troubleshooting
264
+
265
+ ### `ReferenceError: require is not defined`
266
+
267
+ You're editing TypeScript and accidentally called `require(...)` outside of a
268
+ `createRequire(import.meta.url)` shim. The project is ESM. Use `import x from 'y'`
269
+ at the top of the file.
270
+
271
+ ### `npm publish` ships no `dist/`
272
+
273
+ You forgot to run `npm run build` (or the `prepublishOnly` script was
274
+ removed). The `files` whitelist in `package.json` requires the `dist/`
275
+ directory to exist locally when publishing.
276
+
277
+ ### `gfcode --version` shows the wrong number
278
+
279
+ Edit it in `package.json` only — `src/index.ts` and `src/repl/output.ts` both
280
+ read `VERSION` from `package.json` via `import pkg from '../package.json' …`.
281
+
282
+ ### REPL keypresses / Ctrl+C misbehave on Windows
283
+
284
+ Make sure you're on Windows Terminal or any TTY that emits ANSI escape
285
+ sequences natively. The legacy `cmd.exe` console has incomplete ANSI
286
+ support — upgrade to Windows Terminal or use PowerShell.