@gird/ccli 2.0.1 → 2.0.3
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.
- package/README.md +7 -39
- package/dist/ai.js +44 -0
- package/dist/ai.js.map +1 -0
- package/dist/repl.js +30 -12
- package/dist/repl.js.map +1 -1
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -4,17 +4,7 @@
|
|
|
4
4
|
[](./LICENSE)
|
|
5
5
|
[](https://nodejs.org)
|
|
6
6
|
|
|
7
|
-
**CCLI**
|
|
8
|
-
|
|
9
|
-
> ⚠️ **重建中(work in progress)**:本项目正在从零逐步重写,力求每一步都简单、可读、可运行。
|
|
10
|
-
> 当前进度见下方「实现进度」。每一步都配有说明文档,放在 [`docs/steps/`](docs/steps/)。
|
|
11
|
-
|
|
12
|
-
## 为什么重写
|
|
13
|
-
|
|
14
|
-
上一版实现一次性堆了太多功能,代码复杂到难以通读。现在推倒重来,遵循一个原则:
|
|
15
|
-
**从最小可运行的骨架开始,每次只加一个能独立讲清楚的功能,并为它写一份文档**
|
|
16
|
-
(这一步做什么、怎么做的、遇到什么问题、下一步是什么)。目标是过程可追、代码好懂,
|
|
17
|
-
最终长成一个可发布的工具。
|
|
7
|
+
**CCLI** 是一个运行在终端里的 AI 助手。在命令行直接和大模型多轮对话,回复逐字流式输出。
|
|
18
8
|
|
|
19
9
|
## 安装
|
|
20
10
|
|
|
@@ -27,20 +17,19 @@ npm install -g @gird/ccli
|
|
|
27
17
|
## 用法
|
|
28
18
|
|
|
29
19
|
```bash
|
|
30
|
-
ccli #
|
|
20
|
+
ccli # 进入交互式对话
|
|
31
21
|
ccli --version # 打印版本号
|
|
32
22
|
ccli --help # 打印帮助
|
|
33
23
|
```
|
|
34
24
|
|
|
35
|
-
|
|
25
|
+
进入对话后:
|
|
36
26
|
|
|
37
|
-
|
|
27
|
+
- 直接输入即与 AI 对话,模型会记住上文(多轮对话)。
|
|
28
|
+
- `/exit` 或 `/quit` 退出,也可按 Ctrl-C / Ctrl-D。
|
|
38
29
|
|
|
39
|
-
|
|
30
|
+
## 配置
|
|
40
31
|
|
|
41
|
-
|
|
42
|
-
export ANTHROPIC_API_KEY=sk-ant-...
|
|
43
|
-
```
|
|
32
|
+
密钥来源优先级:**环境变量 > 项目 `.env` > 全局 `~/.ccli/config.json`**。
|
|
44
33
|
|
|
45
34
|
**OpenAI 兼容(通义千问 / DeepSeek / GLM…)**
|
|
46
35
|
|
|
@@ -51,27 +40,6 @@ export QWEN_MODEL=qwen-plus
|
|
|
51
40
|
# 或通用:OPENAI_API_KEY / OPENAI_BASE_URL / OPENAI_MODEL
|
|
52
41
|
```
|
|
53
42
|
|
|
54
|
-
## 实现进度
|
|
55
|
-
|
|
56
|
-
| 步骤 | 内容 | 文档 |
|
|
57
|
-
| --- | --- | --- |
|
|
58
|
-
| 01 | 最小可执行入口 | [docs/steps/01-入口.md](docs/steps/01-入口.md) |
|
|
59
|
-
| 02 | 命令行参数解析(`--version` / `--help`) | [docs/steps/02-参数解析.md](docs/steps/02-参数解析.md) |
|
|
60
|
-
| 03 | 交互式会话循环(REPL) | [docs/steps/03-交互循环.md](docs/steps/03-交互循环.md) |
|
|
61
|
-
| 04 | 配置与密钥读取 | [docs/steps/04-配置读取.md](docs/steps/04-配置读取.md) |
|
|
62
|
-
| … | 接入 AI、工具调用、代理循环… | 进行中 |
|
|
63
|
-
|
|
64
|
-
## 本地开发
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
git clone https://github.com/GridGit/ai-cli.git
|
|
68
|
-
cd ai-cli
|
|
69
|
-
npm install
|
|
70
|
-
cp .env.example .env # 填入密钥
|
|
71
|
-
npm run dev # 用 tsx 直接运行源码(免编译)
|
|
72
|
-
npm run build # 编译到 dist/
|
|
73
|
-
```
|
|
74
|
-
|
|
75
43
|
## 许可
|
|
76
44
|
|
|
77
45
|
[MIT](./LICENSE)
|
package/dist/ai.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI 客户端 —— 把配置变成「能对话的对象」。
|
|
3
|
+
*
|
|
4
|
+
* 本步只做最基本的一问一答(带对话历史 + 流式输出),还没有工具调用 / 代理循环。
|
|
5
|
+
* 目前只对接 OpenAI 兼容后端(通义千问 / DeepSeek / GLM…);Anthropic 留待后续步骤。
|
|
6
|
+
*
|
|
7
|
+
* 流式(stream)的意义:模型边生成边返回,我们边收边打印,
|
|
8
|
+
* 用户不用干等一整段生成完,交互感更接近「打字机」。
|
|
9
|
+
*/
|
|
10
|
+
import OpenAI from 'openai';
|
|
11
|
+
export class AIClient {
|
|
12
|
+
client;
|
|
13
|
+
model;
|
|
14
|
+
constructor(config) {
|
|
15
|
+
this.client = new OpenAI({
|
|
16
|
+
apiKey: config.apiKey,
|
|
17
|
+
baseURL: config.baseURL,
|
|
18
|
+
});
|
|
19
|
+
this.model = config.model;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 发送整段对话历史,流式接收助手回复。
|
|
23
|
+
* @param messages 完整历史(含本轮 user 消息)
|
|
24
|
+
* @param onDelta 每收到一小段文本就回调(用于实时打印)
|
|
25
|
+
* @returns 助手本轮完整回复文本
|
|
26
|
+
*/
|
|
27
|
+
async chat(messages, onDelta) {
|
|
28
|
+
const stream = await this.client.chat.completions.create({
|
|
29
|
+
model: this.model,
|
|
30
|
+
messages,
|
|
31
|
+
stream: true,
|
|
32
|
+
});
|
|
33
|
+
let full = '';
|
|
34
|
+
for await (const chunk of stream) {
|
|
35
|
+
const delta = chunk.choices[0]?.delta?.content ?? '';
|
|
36
|
+
if (delta) {
|
|
37
|
+
full += delta;
|
|
38
|
+
onDelta(delta);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return full;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=ai.js.map
|
package/dist/ai.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai.js","sourceRoot":"","sources":["../src/ai.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,MAAM,MAAM,QAAQ,CAAC;AAS5B,MAAM,OAAO,QAAQ;IACT,MAAM,CAAS;IACf,KAAK,CAAS;IAEtB,YAAY,MAAc;QACtB,IAAI,CAAC,MAAM,GAAG,IAAI,MAAM,CAAC;YACrB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,OAAO,EAAE,MAAM,CAAC,OAAO;SAC1B,CAAC,CAAC;QACH,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IAC9B,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,IAAI,CACN,QAAuB,EACvB,OAA+B;QAE/B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;YACrD,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,QAAQ;YACR,MAAM,EAAE,IAAI;SACf,CAAC,CAAC;QAEH,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC/B,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,IAAI,EAAE,CAAC;YACrD,IAAI,KAAK,EAAE,CAAC;gBACR,IAAI,IAAI,KAAK,CAAC;gBACd,OAAO,CAAC,KAAK,CAAC,CAAC;YACnB,CAAC;QACL,CAAC;QACD,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ"}
|
package/dist/repl.js
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 交互式会话循环(REPL:Read-Eval-Print-Loop)。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* 读一行用户输入 → 调用 AI(流式打印回复)→ 打印提示符,再读下一行。
|
|
5
|
+
* 维护整段对话历史,所以模型能记住上文,形成真正的多轮对话。
|
|
6
6
|
* 支持优雅退出:/exit、/quit,或按 Ctrl-D(EOF)、Ctrl-C。
|
|
7
|
-
*
|
|
8
|
-
* 用 Node 内置 readline,零依赖。等骨架稳定后,下一步把「处理」那一环
|
|
9
|
-
* 换成「调用 AI」即可,循环结构不用动。
|
|
10
7
|
*/
|
|
11
8
|
import { createInterface } from 'node:readline';
|
|
12
9
|
import { NAME, VERSION } from './version.js';
|
|
13
10
|
import { loadConfig, maskKey } from './config.js';
|
|
11
|
+
import { AIClient } from './ai.js';
|
|
14
12
|
const PROMPT = '› ';
|
|
15
13
|
/** 用户输入这些即退出。 */
|
|
16
14
|
const EXIT_COMMANDS = new Set(['/exit', '/quit']);
|
|
15
|
+
const SYSTEM_PROMPT = '你是 CCLI,一个运行在终端里的 AI 助手。回答简洁、准确,使用中文。';
|
|
17
16
|
export function startRepl() {
|
|
18
17
|
// 启动即解析配置:缺失就明确报错退出,不进入循环
|
|
19
18
|
const result = loadConfig();
|
|
@@ -22,6 +21,9 @@ export function startRepl() {
|
|
|
22
21
|
process.exit(1);
|
|
23
22
|
}
|
|
24
23
|
const { config } = result;
|
|
24
|
+
const ai = new AIClient(config);
|
|
25
|
+
// 对话历史:以 system 提示词开场,之后累积 user / assistant 轮次
|
|
26
|
+
const history = [{ role: 'system', content: SYSTEM_PROMPT }];
|
|
25
27
|
const rl = createInterface({
|
|
26
28
|
input: process.stdin,
|
|
27
29
|
output: process.stdout,
|
|
@@ -31,20 +33,36 @@ export function startRepl() {
|
|
|
31
33
|
console.log(`后端: ${config.provider} 模型: ${config.model} 密钥: ${maskKey(config.apiKey)}`);
|
|
32
34
|
console.log('进入交互式会话。输入 /exit 退出,或按 Ctrl-C。\n');
|
|
33
35
|
rl.prompt();
|
|
34
|
-
|
|
36
|
+
// 正在等待 AI 回复时置 true,避免这期间的输入被重复处理
|
|
37
|
+
let busy = false;
|
|
38
|
+
rl.on('line', async (line) => {
|
|
35
39
|
const input = line.trim();
|
|
36
|
-
if (input.length === 0) {
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
if (busy || input.length === 0) {
|
|
41
|
+
if (!busy)
|
|
42
|
+
rl.prompt();
|
|
39
43
|
return;
|
|
40
44
|
}
|
|
41
45
|
if (EXIT_COMMANDS.has(input)) {
|
|
42
46
|
rl.close();
|
|
43
47
|
return;
|
|
44
48
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
busy = true;
|
|
50
|
+
history.push({ role: 'user', content: input });
|
|
51
|
+
try {
|
|
52
|
+
// 流式打印助手回复:边收边写到终端
|
|
53
|
+
const reply = await ai.chat(history, (delta) => process.stdout.write(delta));
|
|
54
|
+
process.stdout.write('\n'); // 回复末尾补个换行
|
|
55
|
+
history.push({ role: 'assistant', content: reply });
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
console.error(`\n[请求失败] ${e?.message ?? e}`);
|
|
59
|
+
// 失败则回滚本轮 user 消息,避免污染后续上下文
|
|
60
|
+
history.pop();
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
busy = false;
|
|
64
|
+
rl.prompt();
|
|
65
|
+
}
|
|
48
66
|
});
|
|
49
67
|
// Ctrl-D(EOF)或 rl.close() 触发
|
|
50
68
|
rl.on('close', () => {
|
package/dist/repl.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repl.js","sourceRoot":"","sources":["../src/repl.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"repl.js","sourceRoot":"","sources":["../src/repl.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gird/ccli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "CCLI —— 一步一步从零实现的代理型(Agentic)终端 AI 编程助手",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -43,5 +43,8 @@
|
|
|
43
43
|
"@types/node": "^25.9.3",
|
|
44
44
|
"tsx": "^4.22.4",
|
|
45
45
|
"typescript": "^6.0.3"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"openai": "^6.45.0"
|
|
46
49
|
}
|
|
47
50
|
}
|