hcent 0.1.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 (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +160 -0
  3. package/dist/agent/context-manager.d.ts +14 -0
  4. package/dist/agent/context-manager.js +109 -0
  5. package/dist/agent/context-manager.js.map +1 -0
  6. package/dist/agent/index.d.ts +27 -0
  7. package/dist/agent/index.js +120 -0
  8. package/dist/agent/index.js.map +1 -0
  9. package/dist/bin.d.ts +2 -0
  10. package/dist/bin.js +40 -0
  11. package/dist/bin.js.map +1 -0
  12. package/dist/config/index.d.ts +25 -0
  13. package/dist/config/index.js +109 -0
  14. package/dist/config/index.js.map +1 -0
  15. package/dist/main.d.ts +1 -0
  16. package/dist/main.js +43 -0
  17. package/dist/main.js.map +1 -0
  18. package/dist/provider/index.d.ts +23 -0
  19. package/dist/provider/index.js +124 -0
  20. package/dist/provider/index.js.map +1 -0
  21. package/dist/provider/provider-manager.d.ts +9 -0
  22. package/dist/provider/provider-manager.js +43 -0
  23. package/dist/provider/provider-manager.js.map +1 -0
  24. package/dist/session/index.d.ts +39 -0
  25. package/dist/session/index.js +69 -0
  26. package/dist/session/index.js.map +1 -0
  27. package/dist/session/session-store.d.ts +19 -0
  28. package/dist/session/session-store.js +109 -0
  29. package/dist/session/session-store.js.map +1 -0
  30. package/dist/setup/index.d.ts +11 -0
  31. package/dist/setup/index.js +97 -0
  32. package/dist/setup/index.js.map +1 -0
  33. package/dist/skill/command-palette.d.ts +12 -0
  34. package/dist/skill/command-palette.js +62 -0
  35. package/dist/skill/command-palette.js.map +1 -0
  36. package/dist/skill/index.d.ts +15 -0
  37. package/dist/skill/index.js +145 -0
  38. package/dist/skill/index.js.map +1 -0
  39. package/dist/skill/skill-pointer.d.ts +18 -0
  40. package/dist/skill/skill-pointer.js +70 -0
  41. package/dist/skill/skill-pointer.js.map +1 -0
  42. package/dist/tools/index.d.ts +26 -0
  43. package/dist/tools/index.js +745 -0
  44. package/dist/tools/index.js.map +1 -0
  45. package/dist/tools/permission.d.ts +11 -0
  46. package/dist/tools/permission.js +11 -0
  47. package/dist/tools/permission.js.map +1 -0
  48. package/dist/tui/aligned-text.d.ts +14 -0
  49. package/dist/tui/aligned-text.js +46 -0
  50. package/dist/tui/aligned-text.js.map +1 -0
  51. package/dist/tui/builtin-commands.d.ts +40 -0
  52. package/dist/tui/builtin-commands.js +137 -0
  53. package/dist/tui/builtin-commands.js.map +1 -0
  54. package/dist/tui/editor-history.d.ts +6 -0
  55. package/dist/tui/editor-history.js +11 -0
  56. package/dist/tui/editor-history.js.map +1 -0
  57. package/dist/tui/editor-placeholder.d.ts +2 -0
  58. package/dist/tui/editor-placeholder.js +16 -0
  59. package/dist/tui/editor-placeholder.js.map +1 -0
  60. package/dist/tui/editor-with-placeholder.d.ts +8 -0
  61. package/dist/tui/editor-with-placeholder.js +49 -0
  62. package/dist/tui/editor-with-placeholder.js.map +1 -0
  63. package/dist/tui/hcent-autocomplete.d.ts +19 -0
  64. package/dist/tui/hcent-autocomplete.js +63 -0
  65. package/dist/tui/hcent-autocomplete.js.map +1 -0
  66. package/dist/tui/help-text.d.ts +1 -0
  67. package/dist/tui/help-text.js +15 -0
  68. package/dist/tui/help-text.js.map +1 -0
  69. package/dist/tui/index.d.ts +18 -0
  70. package/dist/tui/index.js +520 -0
  71. package/dist/tui/index.js.map +1 -0
  72. package/dist/tui/message-display.d.ts +8 -0
  73. package/dist/tui/message-display.js +41 -0
  74. package/dist/tui/message-display.js.map +1 -0
  75. package/dist/tui/pi-themes.d.ts +4 -0
  76. package/dist/tui/pi-themes.js +30 -0
  77. package/dist/tui/pi-themes.js.map +1 -0
  78. package/dist/tui/render-message.d.ts +3 -0
  79. package/dist/tui/render-message.js +32 -0
  80. package/dist/tui/render-message.js.map +1 -0
  81. package/dist/tui/thinking-loader.d.ts +17 -0
  82. package/dist/tui/thinking-loader.js +108 -0
  83. package/dist/tui/thinking-loader.js.map +1 -0
  84. package/dist/tui/user-message.d.ts +12 -0
  85. package/dist/tui/user-message.js +35 -0
  86. package/dist/tui/user-message.js.map +1 -0
  87. package/package.json +75 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 HanChen-cn
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,160 @@
1
+ # hcent
2
+
3
+ 终端编码助手 —— 在终端里用自然语言驱动 AI 完成编程任务。
4
+
5
+ 基于 [pi-tui](https://www.npmjs.com/package/@earendil-works/pi-tui) 的交互界面,内置 Agent 循环、10 个文件/Shell 工具、权限确认与 Skill(`/k-xxx`)引用。默认接入 [DeepSeek 官方 API](https://platform.deepseek.com)。
6
+
7
+ ## 环境要求
8
+
9
+ - **Node.js** >= 20
10
+ - **pnpm**(推荐,见 `packageManager` 字段)
11
+ - 在真实 TTY 终端中运行(不要在非交互管道里用)
12
+
13
+ ## 安装
14
+
15
+ ### 全局安装(推荐)
16
+
17
+ 发布到 npm 后:
18
+
19
+ ```bash
20
+ npm install -g hcent
21
+ ```
22
+
23
+ 在发布前,也可从 GitHub 安装(会触发编译,需 Node.js >= 20):
24
+
25
+ ```bash
26
+ npm install -g git+https://github.com/HanChen-cn/hcent.git
27
+ ```
28
+
29
+ 安装后在**任意目录**执行 `hcent` 即可(工作目录决定 Agent 操作的项目路径)。
30
+
31
+ 验证:
32
+
33
+ ```bash
34
+ where hcent # Windows
35
+ which hcent # macOS / Linux
36
+ hcent --setup # 首次配置 API Key(可选)
37
+ ```
38
+
39
+ ### 本地开发
40
+
41
+ ```bash
42
+ git clone https://github.com/HanChen-cn/hcent.git
43
+ cd hcent
44
+ pnpm install
45
+ pnpm dev # 开发模式,tsx 直跑 src/
46
+ ```
47
+
48
+ 全局调试(改代码后需重新编译):
49
+
50
+ ```bash
51
+ pnpm build
52
+ pnpm link --global
53
+ ```
54
+
55
+ ## 首次配置
56
+
57
+ 需要 [DeepSeek API Key](https://platform.deepseek.com/api_keys)。任选一种方式:
58
+
59
+ | 方式 | 说明 |
60
+ |------|------|
61
+ | `hcent --setup` | 交互向导,写入 `~/.hcent/config.json` |
62
+ | 直接 `hcent` | 无 Key 时 TUI 引导填写 |
63
+ | 环境变量 | `HCENT_API_KEY=...` |
64
+ | 项目 `.env` | 复制 `.env.example`,放在**当前工作目录**根下 |
65
+ | 配置文件 | `~/.hcent/config.json` 或 `<cwd>/.hcent/config.json` |
66
+
67
+ `hcent` 启动时会读取当前目录下的 `.env`(不覆盖已在 shell 中设置的变量)。
68
+
69
+ 完整配置示例见仓库根目录 [`config.example.json`](config.example.json)。
70
+
71
+ ### 配置优先级
72
+
73
+ 从高到低:
74
+
75
+ 1. 已在 shell 中 `export` 的 `HCENT_*`
76
+ 2. 项目根目录 `.env`(填充尚未设置的环境变量)
77
+ 3. 项目 `.hcent/config.json`
78
+ 4. 用户 `~/.hcent/config.json`
79
+ 5. 内置默认值
80
+
81
+ ### 常用环境变量
82
+
83
+ | 变量 | 说明 | 默认值 |
84
+ |------|------|--------|
85
+ | `HCENT_API_KEY` | API Key | (必填) |
86
+ | `HCENT_MODEL` | 模型标识 | `deepseek-v4-pro` |
87
+ | `HCENT_BASE_URL` | API Base URL | `https://api.deepseek.com` |
88
+ | `HCENT_SEARCH_API_KEY` | 网络搜索(`websearch` 工具) | 空 |
89
+
90
+ `provider` 支持 `deepseek`(默认)与 `openai-compatible`。多模型通过 `models[]` 配置,TUI 内 `/model` 热切换。
91
+
92
+ > API Key 勿提交 Git。`.env` 已在 `.gitignore` 中。
93
+
94
+ ## 使用
95
+
96
+ ```bash
97
+ cd your-project # 进入要操作的项目目录
98
+ hcent # 启动 TUI
99
+ ```
100
+
101
+ 用自然语言描述任务即可。常用斜杠命令:
102
+
103
+ | 命令 | 说明 |
104
+ |------|------|
105
+ | `/help` | 显示帮助 |
106
+ | `/clear` | 清空当前会话 |
107
+ | `/model [name]` | 列出或切换模型 |
108
+ | `/status` | 查看当前状态 |
109
+ | `/save` | 保存会话 |
110
+ | `/load [id]` | 加载会话(无 id 时列出) |
111
+ | `/sessions` | 列出已保存会话 |
112
+ | `/k-xxx` | 引用 skill(如 `/k-flow`),支持一行多个 |
113
+ | `/exit` | 退出 |
114
+
115
+ ### 内置工具
116
+
117
+ | 工具 | 权限 | 说明 |
118
+ |------|------|------|
119
+ | `ls` `read` `glob` `grep` `tree` `fetch` | 自动 | 浏览、搜索、读取 |
120
+ | `write` `edit` `bash` `websearch` | 需确认 | 写入、编辑、Shell、搜索 |
121
+
122
+ 写操作与 Shell 执行前 TUI 会弹出确认;拒绝结果会写入会话上下文。
123
+
124
+ ### Skill 加载目录
125
+
126
+ 后者同名覆盖前者:
127
+
128
+ `~/.claude/skills` → `~/.cursor/skills` → `~/.agents/skills` → `~/.hcent/skills` → 项目 `.agents` / `.hcent` / `.cursor/skills`
129
+
130
+ ## 开发
131
+
132
+ ```bash
133
+ pnpm install
134
+ pnpm dev # 开发运行(--env-file=.env)
135
+ pnpm build # 编译到 dist/
136
+ pnpm test # vitest
137
+ pnpm test:watch # 监听模式
138
+ pnpm lint # ESLint 检查
139
+ pnpm lint:fix # 自动修复(含 import 合并)
140
+ ```
141
+
142
+ 代码在 `src/`(按模块分子目录),测试在 `test/`(镜像结构,别名 `@` → `src`)。
143
+
144
+ 提交前会经 `lint-staged` 跑 ESLint;规则包括:同文件 import 合并、圈复杂度 ≤ 10、单文件 ≤ 999 行。
145
+
146
+ 架构与模块说明见 [`.kflow/architecture/ARCHITECTURE.md`](.kflow/architecture/ARCHITECTURE.md)。
147
+
148
+ ### 发布到 npm(维护者)
149
+
150
+ ```bash
151
+ pnpm build
152
+ npm pack # 检查 tarball 是否含 dist/
153
+ npm publish # 需 npm 登录且有包名权限
154
+ ```
155
+
156
+ 发布前 `prepack` 会自动编译;用户 `npm install -g hcent` 时拿到的是预编译 `dist/`,无需本机 TypeScript。
157
+
158
+ ## License
159
+
160
+ MIT — 见 [LICENSE](LICENSE)。
@@ -0,0 +1,14 @@
1
+ import type { Message } from '../session/index.js';
2
+ import type { Provider } from '../provider/index.js';
3
+ import type { ToolSchema } from '../tools/index.js';
4
+ export interface ContextManagerConfig {
5
+ maxContextTokens: number;
6
+ reserveForReply: number;
7
+ summarizeThreshold: number;
8
+ }
9
+ export interface ContextManager {
10
+ prepare(messages: Message[], toolSchemas: ToolSchema[]): Promise<Message[]>;
11
+ estimateTokens(messages: Message[]): number;
12
+ }
13
+ export declare function estimateMessagesTokens(messages: Message[]): number;
14
+ export declare function createContextManager(config: ContextManagerConfig, provider: Provider): ContextManager;
@@ -0,0 +1,109 @@
1
+ export function estimateMessagesTokens(messages) {
2
+ return Math.ceil(JSON.stringify(messages).length * 0.6);
3
+ }
4
+ function estimateToolSchemasTokens(toolSchemas) {
5
+ return Math.ceil(JSON.stringify(toolSchemas).length * 0.6);
6
+ }
7
+ export function createContextManager(config, provider) {
8
+ let cachedSummary = null;
9
+ let cachedSummaryKey = null;
10
+ return {
11
+ estimateTokens(messages) {
12
+ return estimateMessagesTokens(messages);
13
+ },
14
+ async prepare(messages, toolSchemas) {
15
+ const systemMessages = messages.filter((m) => m.role === 'system');
16
+ const nonSystemMessages = messages.filter((m) => m.role !== 'system');
17
+ const overhead = estimateMessagesTokens(systemMessages) +
18
+ estimateToolSchemasTokens(toolSchemas) +
19
+ config.reserveForReply;
20
+ const budget = config.maxContextTokens - overhead;
21
+ if (budget <= 0)
22
+ return messages;
23
+ const historyTokens = estimateMessagesTokens(nonSystemMessages);
24
+ if (historyTokens <= budget)
25
+ return messages;
26
+ const discardTokens = historyTokens - budget;
27
+ const discardRatio = discardTokens / historyTokens;
28
+ if (discardRatio <= config.summarizeThreshold) {
29
+ return truncateMessages(systemMessages, nonSystemMessages, budget);
30
+ }
31
+ return await summarizeAndTruncate(systemMessages, nonSystemMessages, budget, provider, config, (key) => {
32
+ if (cachedSummaryKey === key)
33
+ return cachedSummary;
34
+ return null;
35
+ }, (key, summary) => {
36
+ cachedSummaryKey = key;
37
+ cachedSummary = summary;
38
+ });
39
+ },
40
+ };
41
+ }
42
+ function truncateMessages(systemMessages, nonSystemMessages, budget) {
43
+ const result = [...nonSystemMessages];
44
+ while (result.length > 0 && estimateMessagesTokens(result) > budget) {
45
+ result.shift();
46
+ }
47
+ return [...systemMessages, ...result];
48
+ }
49
+ async function summarizeAndTruncate(systemMessages, nonSystemMessages, budget, provider, config, getCachedSummary, setCachedSummary) {
50
+ const discardCount = Math.ceil(nonSystemMessages.length * 0.5);
51
+ const toSummarize = nonSystemMessages.slice(0, discardCount);
52
+ const toKeep = nonSystemMessages.slice(discardCount);
53
+ const cacheKey = JSON.stringify(toSummarize.map((m) => m.ts));
54
+ const cached = getCachedSummary(cacheKey);
55
+ let summaryText;
56
+ if (cached) {
57
+ summaryText = cached;
58
+ }
59
+ else {
60
+ try {
61
+ summaryText = await generateSummary(toSummarize, provider);
62
+ setCachedSummary(cacheKey, summaryText);
63
+ }
64
+ catch {
65
+ return truncateMessages(systemMessages, nonSystemMessages, budget);
66
+ }
67
+ }
68
+ const summaryMessage = {
69
+ role: 'system',
70
+ content: `[对话摘要] ${summaryText}`,
71
+ kind: 'summary',
72
+ ts: new Date().toISOString(),
73
+ };
74
+ const systemAndSummaryTokens = estimateMessagesTokens([...systemMessages, summaryMessage]);
75
+ const remainingBudget = budget - systemAndSummaryTokens + estimateMessagesTokens(systemMessages);
76
+ const kept = [...toKeep];
77
+ while (kept.length > 0 && estimateMessagesTokens(kept) > remainingBudget) {
78
+ kept.shift();
79
+ }
80
+ return [...systemMessages, summaryMessage, ...kept];
81
+ }
82
+ async function generateSummary(messages, provider) {
83
+ const conversation = messages
84
+ .map((m) => {
85
+ const prefix = m.role === 'user' ? '用户' : m.role === 'assistant' ? 'AI' : m.role;
86
+ return `${prefix}: ${m.content.slice(0, 200)}`;
87
+ })
88
+ .join('\n');
89
+ const prompt = {
90
+ role: 'user',
91
+ content: `请用 200 字以内概括以下对话要点:\n\n${conversation}`,
92
+ ts: new Date().toISOString(),
93
+ };
94
+ const req = {
95
+ messages: [
96
+ { role: 'system', content: '你是一个对话摘要助手。请简洁概括对话要点。', ts: new Date().toISOString() },
97
+ prompt,
98
+ ],
99
+ tools: [],
100
+ signal: AbortSignal.timeout(15000),
101
+ };
102
+ let summary = '';
103
+ for await (const delta of provider.stream(req)) {
104
+ if (delta.contentDelta)
105
+ summary += delta.contentDelta;
106
+ }
107
+ return summary || '(摘要生成失败)';
108
+ }
109
+ //# sourceMappingURL=context-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-manager.js","sourceRoot":"","sources":["../../src/agent/context-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import { type ToolCall, type Session } from '../session/index.js';
2
+ import type { Provider } from '../provider/index.js';
3
+ import type { ToolRegistry, ToolResult } from '../tools/index.js';
4
+ import type { PermissionGate, PermissionDecision } from '../tools/permission.js';
5
+ import type { AppConfig } from '../config/index.js';
6
+ import type { ContextManager } from './context-manager.js';
7
+ export type AgentStatus = 'idle' | 'thinking' | 'calling_tool' | 'awaiting_permission';
8
+ export interface AgentEvents {
9
+ onStatus(s: AgentStatus): void;
10
+ onAssistantDelta(text: string): void;
11
+ /** assistant 消息已写入 session(含 toolCalls),可提交流式 UI */
12
+ onAssistantComplete(): void;
13
+ /** 即将调用 Provider 流式推理(含 prepare 完成后的等待首 token 阶段) */
14
+ onStreamStart(): void;
15
+ onToolCall(call: ToolCall): void;
16
+ onToolResult(call: ToolCall, result: ToolResult, decision: PermissionDecision): void;
17
+ onError(message: string): void;
18
+ }
19
+ export interface AgentDeps {
20
+ provider: Provider;
21
+ tools: ToolRegistry;
22
+ permission: PermissionGate;
23
+ session: Session;
24
+ config: AppConfig;
25
+ contextManager?: ContextManager;
26
+ }
27
+ export declare function runAgentTurn(input: string, deps: AgentDeps, events: AgentEvents): Promise<void>;
@@ -0,0 +1,120 @@
1
+ import { createMessage } from '../session/index.js';
2
+ // ── runAgentTurn ──
3
+ async function consumeProviderStream(deps, events) {
4
+ const controller = new AbortController();
5
+ let fullContent = '';
6
+ let lastToolCalls;
7
+ try {
8
+ const messages = deps.contextManager
9
+ ? await deps.contextManager.prepare(deps.session.snapshot(), deps.tools.schemas())
10
+ : deps.session.snapshot();
11
+ events.onStreamStart();
12
+ for await (const delta of deps.provider.stream({
13
+ messages,
14
+ tools: deps.tools.schemas(),
15
+ signal: controller.signal,
16
+ })) {
17
+ if (delta.contentDelta) {
18
+ fullContent += delta.contentDelta;
19
+ events.onAssistantDelta(delta.contentDelta);
20
+ }
21
+ if (delta.toolCalls) {
22
+ lastToolCalls = delta.toolCalls;
23
+ }
24
+ }
25
+ return { fullContent, lastToolCalls };
26
+ }
27
+ catch (err) {
28
+ const errMsg = createMessage({
29
+ role: 'system',
30
+ content: `错误: ${err instanceof Error ? err.message : String(err)}`,
31
+ kind: 'error',
32
+ });
33
+ deps.session.append(errMsg);
34
+ events.onError(errMsg.content);
35
+ events.onStatus('idle');
36
+ return { error: true };
37
+ }
38
+ }
39
+ async function executeToolCall(tc, deps, events) {
40
+ events.onToolCall(tc);
41
+ const tool = deps.tools.get(tc.name);
42
+ if (!tool) {
43
+ deps.session.append(createMessage({
44
+ role: 'tool',
45
+ content: `错误: 未知工具 ${tc.name}`,
46
+ toolCallId: tc.id,
47
+ kind: 'error',
48
+ }));
49
+ return;
50
+ }
51
+ events.onStatus(tool.permission === 'auto' ? 'calling_tool' : 'awaiting_permission');
52
+ const decision = await deps.permission.check(tool, tc.arguments);
53
+ if (decision === 'deny') {
54
+ deps.session.append(createMessage({
55
+ role: 'tool',
56
+ content: `用户拒绝执行 ${tc.name}`,
57
+ toolCallId: tc.id,
58
+ kind: 'permission_reject',
59
+ }));
60
+ events.onToolResult(tc, { ok: false, output: '', error: '用户拒绝执行' }, 'deny');
61
+ return;
62
+ }
63
+ if (tool.permission !== 'auto') {
64
+ events.onStatus('calling_tool');
65
+ }
66
+ const result = await tool.run(tc.arguments, {
67
+ cwd: process.cwd(),
68
+ signal: new AbortController().signal,
69
+ });
70
+ deps.session.append(createMessage({
71
+ role: 'tool',
72
+ content: result.ok ? result.output : `错误: ${result.error ?? '未知错误'}`,
73
+ toolCallId: tc.id,
74
+ kind: result.ok ? undefined : 'error',
75
+ }));
76
+ events.onToolResult(tc, result, 'allow');
77
+ }
78
+ export async function runAgentTurn(input, deps, events) {
79
+ const trimmed = input.trim();
80
+ if (!trimmed)
81
+ return;
82
+ // 用户消息入历史
83
+ const userMsg = createMessage({ role: 'user', content: trimmed });
84
+ deps.session.append(userMsg);
85
+ events.onStatus('thinking');
86
+ let loopCount = 0;
87
+ while (true) {
88
+ loopCount++;
89
+ const streamed = await consumeProviderStream(deps, events);
90
+ if ('error' in streamed)
91
+ return;
92
+ const { fullContent, lastToolCalls } = streamed;
93
+ deps.session.append(createMessage({
94
+ role: 'assistant',
95
+ content: fullContent,
96
+ toolCalls: lastToolCalls && lastToolCalls.length > 0 ? lastToolCalls : undefined,
97
+ }));
98
+ events.onAssistantComplete();
99
+ if (!lastToolCalls || lastToolCalls.length === 0) {
100
+ events.onStatus('idle');
101
+ return;
102
+ }
103
+ if (loopCount >= deps.config.maxLoops) {
104
+ const limitMsg = createMessage({
105
+ role: 'system',
106
+ content: `达到最大轮次限制 (${deps.config.maxLoops})`,
107
+ kind: 'error',
108
+ });
109
+ deps.session.append(limitMsg);
110
+ events.onError(limitMsg.content);
111
+ events.onStatus('idle');
112
+ return;
113
+ }
114
+ for (const tc of lastToolCalls) {
115
+ await executeToolCall(tc, deps, events);
116
+ }
117
+ events.onStatus('thinking');
118
+ }
119
+ }
120
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/agent/index.ts"],"names":[],"mappings":"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"}
package/dist/bin.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/bin.js ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ // hcent — 终端编码助手 CLI 入口
3
+ // 配置来源:项目 .env > 项目 .hcent/config.json > 用户 ~/.hcent/config.json > 默认值
4
+ import { readFileSync } from 'node:fs';
5
+ import { join } from 'node:path';
6
+ // 加载项目根目录的 .env(不在 pnpm dev --env-file 下也能工作)
7
+ function loadDotEnv() {
8
+ try {
9
+ const dotEnvPath = join(process.cwd(), '.env');
10
+ const content = readFileSync(dotEnvPath, 'utf-8');
11
+ for (const line of content.split('\n')) {
12
+ const trimmed = line.trim();
13
+ if (!trimmed || trimmed.startsWith('#'))
14
+ continue;
15
+ const eqIdx = trimmed.indexOf('=');
16
+ if (eqIdx === -1)
17
+ continue;
18
+ const key = trimmed.slice(0, eqIdx).trim();
19
+ const val = trimmed.slice(eqIdx + 1).trim();
20
+ if (!process.env[key]) {
21
+ process.env[key] = val;
22
+ }
23
+ }
24
+ }
25
+ catch {
26
+ // .env 不存在则忽略
27
+ }
28
+ }
29
+ loadDotEnv();
30
+ import { run } from './main.js';
31
+ import { runSetup } from './setup/index.js';
32
+ async function main() {
33
+ if (process.argv.slice(2).includes('--setup')) {
34
+ await runSetup();
35
+ return;
36
+ }
37
+ await run();
38
+ }
39
+ main();
40
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,wBAAwB;AACxB,uEAAuE;AAEvE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,8CAA8C;AAC9C,SAAS,UAAU;IACjB,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,CAAC;QAC/C,MAAM,OAAO,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAClD,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACvC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YAC5B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;gBAAE,SAAS;YAClD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACnC,IAAI,KAAK,KAAK,CAAC,CAAC;gBAAE,SAAS;YAC3B,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;YAC3C,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAC5C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBACtB,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;YACzB,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,cAAc;IAChB,CAAC;AACH,CAAC;AAED,UAAU,EAAE,CAAC;AAEb,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,KAAK,UAAU,IAAI;IACjB,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QAC9C,MAAM,QAAQ,EAAE,CAAC;QACjB,OAAO;IACT,CAAC;IAED,MAAM,GAAG,EAAE,CAAC;AACd,CAAC;AAED,IAAI,EAAE,CAAC"}
@@ -0,0 +1,25 @@
1
+ export interface ModelConfig {
2
+ name: string;
3
+ model: string;
4
+ baseUrl: string;
5
+ apiKey: string;
6
+ maxContextTokens: number;
7
+ timeoutMs?: number;
8
+ maxRetries?: number;
9
+ }
10
+ export type ProviderType = 'deepseek' | 'openai-compatible';
11
+ export interface AppConfig {
12
+ provider: ProviderType;
13
+ model: string;
14
+ baseUrl: string;
15
+ apiKey: string;
16
+ timeoutMs: number;
17
+ maxRetries: number;
18
+ maxLoops: number;
19
+ models: ModelConfig[];
20
+ activeModel: string;
21
+ searchApiKey: string;
22
+ }
23
+ export declare const DEFAULTS: AppConfig;
24
+ export declare function loadConfig(cwd: string): AppConfig;
25
+ export declare function sanitizeConfig(config: AppConfig): AppConfig;
@@ -0,0 +1,109 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ export const DEFAULTS = {
5
+ provider: 'deepseek',
6
+ model: 'deepseek-v4-pro',
7
+ baseUrl: 'https://api.deepseek.com',
8
+ apiKey: '',
9
+ timeoutMs: 60_000,
10
+ maxRetries: 2,
11
+ maxLoops: 68,
12
+ models: [],
13
+ activeModel: '',
14
+ searchApiKey: '',
15
+ };
16
+ function readJSON(path) {
17
+ try {
18
+ const raw = readFileSync(path, 'utf-8');
19
+ return JSON.parse(raw);
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ }
25
+ function shallowMerge(base, overrides) {
26
+ const merged = { ...base };
27
+ for (const key of Object.keys(DEFAULTS)) {
28
+ if (key === 'models')
29
+ continue; // models 由 mergeModels 单独处理
30
+ if (key in overrides && overrides[key] !== undefined && overrides[key] !== null) {
31
+ merged[key] = overrides[key];
32
+ }
33
+ }
34
+ return merged;
35
+ }
36
+ function mergeModels(base, override) {
37
+ if (!Array.isArray(override))
38
+ return base;
39
+ const map = new Map();
40
+ for (const m of base)
41
+ map.set(m.name, m);
42
+ for (const m of override) {
43
+ if (m && typeof m.name === 'string')
44
+ map.set(m.name, m);
45
+ }
46
+ return [...map.values()];
47
+ }
48
+ function backfillModels(config) {
49
+ if (config.models.length > 0)
50
+ return config;
51
+ return {
52
+ ...config,
53
+ models: [{
54
+ name: config.model,
55
+ model: config.model,
56
+ baseUrl: config.baseUrl,
57
+ apiKey: config.apiKey,
58
+ maxContextTokens: 65536,
59
+ }],
60
+ };
61
+ }
62
+ function applyFileOverrides(config, path, mergeModelsWith) {
63
+ const overrides = readJSON(path);
64
+ if (!overrides)
65
+ return config;
66
+ const next = shallowMerge(config, overrides);
67
+ if ('models' in overrides) {
68
+ const base = mergeModelsWith === 'replace' ? [] : config.models;
69
+ next.models = mergeModels(base, overrides.models);
70
+ }
71
+ return next;
72
+ }
73
+ function applyEnvOverrides(config) {
74
+ const next = { ...config, models: config.models.map((m) => ({ ...m })) };
75
+ const envKey = process.env.HCENT_API_KEY;
76
+ if (envKey) {
77
+ next.apiKey = envKey;
78
+ for (const m of next.models)
79
+ m.apiKey = envKey;
80
+ }
81
+ const envModel = process.env.HCENT_MODEL;
82
+ if (envModel) {
83
+ next.model = envModel;
84
+ next.activeModel = envModel;
85
+ }
86
+ if (process.env.HCENT_BASE_URL)
87
+ next.baseUrl = process.env.HCENT_BASE_URL;
88
+ if (process.env.HCENT_SEARCH_API_KEY)
89
+ next.searchApiKey = process.env.HCENT_SEARCH_API_KEY;
90
+ return next;
91
+ }
92
+ export function loadConfig(cwd) {
93
+ let config = applyFileOverrides({ ...DEFAULTS }, join(homedir(), '.hcent', 'config.json'), 'replace');
94
+ config = applyFileOverrides(config, join(cwd, '.hcent', 'config.json'), config.models);
95
+ config = applyEnvOverrides(config);
96
+ config = backfillModels(config);
97
+ if (!config.activeModel && config.models.length > 0) {
98
+ config.activeModel = config.models[0].name;
99
+ }
100
+ return config;
101
+ }
102
+ export function sanitizeConfig(config) {
103
+ return {
104
+ ...config,
105
+ apiKey: config.apiKey ? '***' : '',
106
+ models: config.models.map((m) => ({ ...m, apiKey: m.apiKey ? '***' : '' })),
107
+ };
108
+ }
109
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AA2BjC,MAAM,CAAC,MAAM,QAAQ,GAAc;IACjC,QAAQ,EAAE,UAAU;IACpB,KAAK,EAAE,iBAAiB;IACxB,OAAO,EAAE,0BAA0B;IACnC,MAAM,EAAE,EAAE;IACV,SAAS,EAAE,MAAM;IACjB,UAAU,EAAE,CAAC;IACb,QAAQ,EAAE,EAAE;IACZ,MAAM,EAAE,EAAE;IACV,WAAW,EAAE,EAAE;IACf,YAAY,EAAE,EAAE;CACjB,CAAC;AAEF,SAAS,QAAQ,CAAC,IAAY;IAC5B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACxC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,IAAe,EAAE,SAAkC;IACvE,MAAM,MAAM,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;IAC3B,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAwB,EAAE,CAAC;QAC/D,IAAI,GAAG,KAAK,QAAQ;YAAE,SAAS,CAAC,4BAA4B;QAC5D,IAAI,GAAG,IAAI,SAAS,IAAI,SAAS,CAAC,GAAG,CAAC,KAAK,SAAS,IAAI,SAAS,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;YAC/E,MAAkC,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,WAAW,CAAC,IAAmB,EAAE,QAAiB;IACzD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1C,MAAM,GAAG,GAAG,IAAI,GAAG,EAAuB,CAAC;IAC3C,KAAK,MAAM,CAAC,IAAI,IAAI;QAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACzC,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,CAAC,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ;YAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAgB,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;AAC3B,CAAC;AAED,SAAS,cAAc,CAAC,MAAiB;IACvC,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,MAAM,CAAC;IAC5C,OAAO;QACL,GAAG,MAAM;QACT,MAAM,EAAE,CAAC;gBACP,IAAI,EAAE,MAAM,CAAC,KAAK;gBAClB,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,gBAAgB,EAAE,KAAK;aACxB,CAAC;KACH,CAAC;AACJ,CAAC;AAED,SAAS,kBAAkB,CACzB,MAAiB,EACjB,IAAY,EACZ,eAA0C;IAE1C,MAAM,SAAS,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IACjC,IAAI,CAAC,SAAS;QAAE,OAAO,MAAM,CAAC;IAC9B,MAAM,IAAI,GAAG,YAAY,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAC7C,IAAI,QAAQ,IAAI,SAAS,EAAE,CAAC;QAC1B,MAAM,IAAI,GAAG,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC;QAChE,IAAI,CAAC,MAAM,GAAG,WAAW,CAAC,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,iBAAiB,CAAC,MAAiB;IAC1C,MAAM,IAAI,GAAG,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;IACzE,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IACzC,IAAI,MAAM,EAAE,CAAC;QACX,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM;YAAE,CAAC,CAAC,MAAM,GAAG,MAAM,CAAC;IACjD,CAAC;IACD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;IACzC,IAAI,QAAQ,EAAE,CAAC;QACb,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAC;QACtB,IAAI,CAAC,WAAW,GAAG,QAAQ,CAAC;IAC9B,CAAC;IACD,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc;QAAE,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IAC1E,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB;QAAE,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC;IAC3F,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,IAAI,MAAM,GAAG,kBAAkB,CAC7B,EAAE,GAAG,QAAQ,EAAE,EACf,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,aAAa,CAAC,EACxC,SAAS,CACV,CAAC;IACF,MAAM,GAAG,kBAAkB,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IACvF,MAAM,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IACnC,MAAM,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpD,MAAM,CAAC,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,IAAI,CAAC;IAC9C,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAiB;IAC9C,OAAO;QACL,GAAG,MAAM;QACT,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;QAClC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;KAC5E,CAAC;AACJ,CAAC"}
package/dist/main.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function run(): Promise<void>;