导入
Agent CLI 这两年出了不少, Claude Code、Codex、Gemini CLI 等等,用了一圈之后还是想自己写一个:一来想完全按自己的习惯来,二来想要一个能随便接各种中转站的简单工具。于是用 TypeScript 写了 Star CLI。一开始只是想做个能调工具的简单 REPL,结果做着做着功能越加越多,现在也算是一个比较完整的小型 agent CLI 了。
项目地址:github.com/cryer/star-cli,npm install -g @cryer/star-cli 就能装。
功能
日常用的那些都有:流式 REPL,斜杠命令带自动补全,@file 把文件内容注入上下文,!cmd 直接跑 shell。模型接入支持 OpenAI 兼容、Anthropic 和 OpenAI Responses 三种协议,baseURL 自选,所以各种中转站都能用,配 key 有 /connect 向导,不用手改配置文件。
工具方面,文件读写、glob、grep(这两个是自己实现的,零依赖)、bash(支持后台任务)、联网搜索抓取(DuckDuckGo,不用 API key)、todo 清单,还有 subagent 可以把聚焦的子任务丢给子代理去做,主对话保持干净。
权限分了五档:ask、auto、readonly、yolo、plan。危险命令、工作目录外的路径、.env 这类敏感文件是硬性规则,任何白名单都覆盖不了。写文件前会给彩色 diff 预览,按 a 可以永久放行同类操作。plan 模式下先做只读调研,计划批准之后才动手。写错了有 /undo 撤销上一轮改动,还有 /rewind 可以回滚到更早的检查点。
会话会自动持久化,断了能 /resume 恢复,还会自动给会话起标题。另外还有 /fork 分叉会话、/search 全局搜索、git 集成(/commit 起草提交信息、/diff 看改动)、长期记忆、自定义斜杠命令、钩子、token 成本估算这些,细节就不一一展开了,README 里写得很全。
再挑几个自己特别喜欢的点展开说说。
回合中途插入提示词(steer)。想法来自 Kimi Code 里我非常喜欢的一个功能:Ctrl+S 可以直接把新提示词插入正在运行的会话,不用先中断再重新组织语言。这个功能在其他 agent CLI 里基本没见过,用习惯之后就回不去了,所以 Star CLI 也实现了,而且做得更细一点:回合还在跑的时候提交提示词,会弹出选择——排队等回合结束,还是直接插入当前回合。选插入的话,消息会在当前 step 跑完后进入上下文(在途的模型回复和工具调用不会被打断),回合接着往下走;选排队就存进 FIFO 队列,回合结束自动按顺序发送。
图片与视觉 debug。Alt+V 直接把剪贴板里的图片贴进上下文(Windows 走 PowerShell,macOS 用 pngpaste,Linux 用 xclip,都是零依赖调用系统能力,超大图自动降采样),也可以 @图片路径 提及。更有意思的是 agent 自己有 screenshot 工具:debug 和测试的时候它会主动截图看真实渲染结果,而不是只盯着文字 log 猜——典型流程是起个 dev server、截个图、看着图判断问题,和真人 debug 的路子一样。配了 vision = false 的模型会自动屏蔽这些工具。
辅助模型。可以单独配一个 smallModel 干杂活:上下文压缩摘要、自动起会话标题、回合完成度检查都走这个次模型,省钱也省时间。不配就回落到主模型。
为中转站做的打磨
既然定位是”随便接中转站”,中转站的各种不规范就得自己消化,这块花的功夫可能比主流程还多:
- 自己掌控重试。关掉 SDK 自带的重试,拿到原始 status、headers、body 后自己分类:状态码之外还看错误消息正则和响应体(中转站经常报个 Cloudflare 403 HTML 或者 body 里写一句 “provider returned error”);400/401/403/404 这类即使文案像瞬时错误也快速失败。
- 200 流里的错误 chunk。中转站在上游过载时经常以 200 流返回一个
{"error": ...}chunk,SDK 会把它当成普通对象。现在能逐字段解包识别,overloaded、rate limit 这类无状态码的服务端瞬时错误也能正确重试。 - Retry-After 感知的退避。优先认
retry-after头,否则指数退避加抖动,封顶 60 秒。 - 两阶段空闲看门狗。首 chunk 给 300 秒宽限(慢中转站排队久),内容开始之后 idle 窗口收窄到 60 秒;reasoning delta 不算”可见内容”,因为推理模型思考时几十秒零字节是正常的。流被截断的回复不让模型从头再来,而是让它从断点续写。
- 字节级活跃度探针。包装 fetch 给原始 body chunk 打时间戳,中转站发的 SSE 心跳注释(SDK 会吞掉)也能给看门狗续命。
- SSE 规范化。有的中转站把多个 JSON 事件塞进一个 SSE data payload,SDK 直接解析炸掉,在 fetch 层用花括号计数拆回一行一个事件。
include_usage。OpenAI 兼容协议主动请求流里带 usage,否则 DeepSeek、Moonshot 这类渠道不给用量,/cost和状态栏全读零。- 空回复分诊。空回复且零计费判为中转站过载,吃满重试预算;有计费但无可见输出(中转站隐藏 reasoning 的典型特征)不重发相同请求,改用 nudge 提示模型继续。
架构
整体是普通的分层结构:入口、Ink 写的终端 UI、agent 主循环、工具和权限、底层的模型接入和会话持久化。层与层之间靠两个接口解耦:UI 面对的是 ChatBackend 接口,所以测试时可以拿一个假后端驱动整个界面;agent 主循环对外只吐 StreamEvent 事件流,REPL 把它渲染成卡片,print 模式把它序列化成 NDJSON,subagent 把它聚合成报告,同一条流水线三种消费方式。
入口层 main.tsx —— commander 参数解析,分发到 REPL 或 print 模式
│
UI 层 cli/ —— Ink/React 终端界面,slash 命令体系
│ 通过 ChatBackend 接口与 agent 解耦(UI 可独立测试)
│
核心层 agent/ —— AgentLoop 主循环:多步工具调用、权限闸、压缩、持久化
│ 通过 StreamEvent 事件流向上汇报(文本/推理/工具调用/结果/完成/错误)
│
能力层 tools/(13+ 个工具)+ permissions/(纯函数权限门)+ tasks/(后台任务)
│
基础层 llm/(模型接入与流式封装)+ config/(TOML+zod 配置)+ session/(JSONL 持久化)
+ context/(token 估算与压缩)+ core/(共享类型)
src 下各模块的职责:
| 模块 | 职责 |
|---|---|
src/core/ |
共享类型与对话历史修复算法 |
src/config/ |
zod schema、TOML 三级加载链(全局→项目→CLI)、密钥解析 |
src/llm/ |
provider 工厂、流式封装(空闲看门狗、活跃度探针)、中转站容错重试 |
src/agent/ |
AgentLoop 主循环 + subagent 子代理 |
src/tools/ |
工具接口、注册表、全部内置工具、文件快照(/undo 的基础) |
src/permissions/ |
权限模式、硬安全规则、”始终允许”白名单 |
src/tasks/ |
后台 shell 任务管理器 |
src/session/ |
JSONL 会话持久化与恢复、会话标题 |
src/context/ |
token 估算、上下文压缩、陈旧内容省略 |
src/cli/ |
Ink REPL 的全部组件、slash 命令、@file、diff 预览、输入队列 |
还有两个设计上的原则。一是权限是两层防线:plan 模式下写工具干脆不发给模型,模型看不到,权限门再兜一层。二是一切皆可降级:LLM 摘要失败就退回纯截断,diff 预览失败就退回参数摘要,辅助功能永远不该阻塞主流程。
跑分
顺手在 Terminal-Bench 2.0 上随机抽了 20 个任务跑了一下,和 terminus-2 对比,用同一个模型:
| Agent | 模型 | 通过率 | 平均 token | 平均耗时 |
|---|---|---|---|---|
| Star CLI | Kimi K3 | 17/20 (85%) | 129.2K | 414s |
| terminus-2 | Kimi K3 | 9/20 (45%) | 49.5K | 484s |
token 花得多一些,通过率高出不少,算是拿 token 换成功率了。
说明一下:这是 0.3.9 版本跑的结果。写这篇文章时最新版已经是 0.3.11,中间又加了几个上下文压缩优化——最主要是陈旧内容原地省略(stale-content elision):用量到压缩阈值一半时,老的工具结果、图片、工具调用参数会被原地替换成占位符(”需要就重新跑一遍工具”),消息数量和顺序不变,往往缩完之后压缩根本不用触发;另外 token 估算从 chars/4 启发式换成了真 BPE tokenizer,再用服务端返回的 usage 做校准(解决非 OpenAI tokenizer 的中文会话估算偏低、压缩触发过晚的问题)。现在再跑一遍的话,token 用量应该会比表里好看不少。
结语
自己日常一直在用,有问题就顺手修。如果你也想要一个简单透明、随便接中转站的 agent CLI,可以试试看。