@epoch-agent/cli

中文文档
6.2.0发布于 2026/11/13Apache-2.0TypeScriptES Module近 30 天下载 3727

epoch-agent CLI 入口

原始使用文档
文档目录8 节
  1. 文件
  2. 带值的 flag 在 分流之前 落地,其中七个走环境变量
  3. 单次问答的两条流
  4. headless 协议
  5. 定时任务的文案走 穷举的表 ,不走模板字符串
  6. Web UI 的产物不在这个包里
  7. epoch web 是一个 宿主 ,但插件页不再需要它点头
  8. 开发

@epoch-agent/cli

CLI 入口。装了它才有 epoch 这个可执行文件。

  • ✅ 做:commander 参数解析、TUI 启动、审批的终端交互、输出格式化、 退出码、更新检查、shell 补全
  • ❌ 不做:装配(在 runtime)、领域逻辑(在 core)、 渲染细节(在 tui)
  • 依赖:protocol + infra + core + runtime + server + tui + commander + @inquirer/prompts + ink + react

命令的完整清单(含全局选项和每个子命令)在 docs/CLI.md,这里不重复一遍——两处抄同一份清单必然漂。

epoch model              # 首次使用:交互式添加一条模型(「我的模型」),key 进系统钥匙串
epoch doctor             # 装不上 / 跑不起来时第一个跑这个

没有 epoch setup,也没有 epoch config init——初始化就走 epoch model。

文件

文件 / 目录职责
index.tscommander 装配,每个子命令挂在这里
commands/run.tsepoch [query]:判断走 TUI / 单次问答 / 长驻模式 / 读 stdin
commands/run-once.ts单次问答的输出(三种 --output-format)+ 收尾(后台任务摘要、退出码)
commands/run-stream.tstext 档两条流(模型正文 / 工具实时输出)在同一块屏幕上怎么交错:createStreamWriter
commands/run-flags.ts全局选项的解析(纯函数):会话续接三态、格式、限额、审批器路径
commands/run-overrides.ts分流之前那一步:所有带值 flag 的落地与校验都挂在 applyRunFlags 这一个入口(见下)
commands/sessions.tsepoch sessions,同时是会话续接的唯一实现处:-c / -r 与 sessions resume 共用它的 id 解析
headless/程序驱动那条路(方案 28):见下面「headless 协议」
commands/schedule.tsepoch schedule 的命令注册(薄壳)
schedule/定时任务那一族的实现:add / view / run / manage / doctor,labels.ts 是码 → 文案的唯一出口(见下)
commands/*.tsconfig / mcp / model / status / sessions / trust / agents / plugin / upgrade / web / completion
commands/web-open.ts跨平台打开浏览器(start / open / xdg-open)
tui-entry.tsTUI 启动:建 runtime、接 AgentSession、renderApp()
approval.ts非 TUI 场景的终端审批交互
import-prompt.ts指令文件引用项目之外的文件时的逐个询问(方案 33)
files/config.yaml / .env 的读写(保留注释与格式);以及 epoch config schema 写出的 .epoch/schemas/*.json
errors.ts / exit-codes.ts错误 → 人话 + 稳定退出码(CI 靠它判断)。别的包抛的「预期内失败」按 err.name 查一张表,不打 stack
update-check.ts后台查新版本,detached 子进程,不拖慢启动
installation.ts判断安装方式(npm / pnpm / 本地),epoch upgrade 靠它给出正确命令
clipboard.ts读剪贴板里的图片(TUI 的 Ctrl+V)。放在 cli 是因为 tui 不该 spawn 子进程
statusline.ts自定义状态栏:跑用户配的命令,第一行给 TUI(超时 2s / 截 60 字 / 失败保留旧值)。放 cli 同上:要 spawn
worktree.ts--worktree 的 git 那一侧:建 worktree + 新分支、收尾时有改动就不删
paths.ts / version.tsCLI 侧的路径与版本号;--version 那三行也在 version.ts,第一行必须是裸版本号

带值的 flag 在分流之前落地,其中七个走环境变量

「分流」= commands/run.ts 里那句「没有 prompt 就起 TUI」。所有带值 flag 的落地 与校验都排在它前面,入口只有一个:commands/run-overrides.ts 的 applyRunFlags。加新 flag 时挂进那一个函数。

这条规矩是修出来的(前因见 方案 28 验收记录第七章):--model 的值校验原来住在 runOnce 里、排在分流后面,而打错一个带值的 flag 必然让位置参数为空 —— 于是 epoch -m "跑一下测试" 这类手滑永远走不到校验,一律表现为静默进 TUI, 一个字都不说。

七个走环境变量的(其余全局选项是解析成 BuildRuntimeOptions 的字段传下去的):

选项环境变量值
--settingsEPOCH_SETTINGS绝对路径
--add-dirEPOCH_ADD_DIR绝对路径,按 path.delimiter 连起来
--agentEPOCH_AGENT角色名原样
-m/--modelEPOCH_MODEL@<id>(「我的模型」里的一条)
--fallback-modelEPOCH_FALLBACK_MODEL@<id>
--permissionEPOCH_PERMISSION收窄过的权限级别
-c / -rEPOCH_RESUME选中的会话 id

⚠️ 2.0.0 删掉了 -p/--provider 和 EPOCH_PROVIDER(老写法的入口:模型只认 「我的模型」条目,每一条自带走哪家)。设了那个变量今天会被忽略并报一条诊断。

⚠️ --base-url / EPOCH_BASE_URL 2026-10-15 删了:端点是「我的模型」条目自己的属性 (hostPreset.models[].baseUrl,或者用户在设置页给那一条填的地址),引擎不再有进程级的 「统一端点」。设着那个变量的机器会收到一条诊断。

因为无参进 TUI 那条路不经过 runOnce——TUI 起的是另一个进程,选项对象过不去, 只有环境变量带得过去。路径一律转绝对:子进程的 cwd 不保证一样。

前六个在 action 一开头就落进 env,launchTui 那边 { ...process.env } 白拿; 只有 EPOCH_RESUME 要显式塞,因为它是算出来的(-c 要查库、-r 不带值 要弹选择器)。加新 flag 时按这条分。

取值是封闭集合的那两个(-m / --fallback-model)判字形:必须是 @<id> 形状(parseModelRef),指向「我的模型」里的一条。 ⚠️ 「base URL 得能被 new URL() 解析」那句已经不适用了 —— 2.0.0 起模型名 收窄成 @<id>,端点地址写在那一条模型自己身上,flag 不再收 base URL。 卡得松是刻意的 —— 误伤一个合法模型名的代价是 「那个模型压根用不了」,比放过一句话严重得多。

反面例子是 --worktree:它没有对应的 EPOCH_*,因为它不是「传下去的一个设置」, 而是父进程先 chdir 到新建的 worktree,子进程照常继承 cwd —— 落成环境变量的话 父子两边都会各建一个。它的时序也是硬的:建 + chdir 必须排在 --add-dir 落地和 -c 查会话之后,那两件事都以「用户敲命令的那个目录」为准,chdir 之后项目根就 变成 worktree 了,它们会一起错,而且错得很安静(-c 查不到任何会话,于是默默开新的)。

也因此 launchTui() 现在 await 到子进程退出才返回(以前是发射后不管,靠继承的 stdio 让父进程自然活着):worktree 的收尾得等 TUI 真的退出,否则会在用户还在里面 打字的时候问他「要不要删这个 worktree」。

-c / -r 还多一条:选哪一段在父进程里就定完(含 -r 不带 id 时的选择器)。 ink 一接管 stdin,inquirer 就抢不回来 —— 和项目外 @import 的询问必须排在 renderApp 之前是同一个约束。

三处的「坏输入」都报错退出,不静默当没给:

  • --settings 文件不存在 / 不是合法 JSON → 静默降级的表现是「规则一条都没生效」, 在 CI 里正好等于「审批全部走默认」,是最不该悄悄发生的那种失败。 JSON 坏了这一半由 core 抛 SettingsFileError(错误里带行号)
  • --add-dir 指的目录不存在 / 不是目录 → 静默失效等于 agent 的地盘和用户想的不一样, 而这件事只在它拒绝读某个文件时才露头。「白给」(已经在工作区里了)不算错, 由运行时报一条诊断
  • --agent 名字不认识 → 由 runtime 抛 AgentRoleError(可用角色都在消息里)。 故意不在 CLI 侧先查一遍:合并之后那张角色表(用户级 / 项目级 / 插件 / 宿主) 只有 buildRuntime() 合并得出来,在这儿再发现一遍等于养第二份逻辑, 而两份迟早会给出不同答案

这三个错误加上 ManagedPolicyError 共用 errors.ts 里那张 name → 退出码 表, 一律只打一行 message(有 hint 再一行)、不打 stack:那段 stack 指向别人包里的 一行 throw,对着它排查纯属浪费。

分层语义(谁压过谁、哪几层过信任闸门)见 docs/SETTINGS.md。

单次问答的两条流

流式模式下 stdout 与 stderr 各有分工,createStreamWriter 是唯一的出口:

流内容
stdout只有模型的回答——epoch "…" > a.txt 必须是纯回答
stderr工具执行期间的实时输出(每行 │ 前缀)、诊断、用量汇总

两条流必须由同一个东西管:它们在终端里落到同一块屏幕上,各写各的就会互相插队。 正文停在半行上时先补一个换行再插工具输出,否则 pnpm build 的几百行会怼在模型半 句话后面。两种机器格式(json / stream-json)下这个 writer 一个字节都不写—— 它们的契约是 stdout 只有协议内容。

实时输出不进 json 的载荷:它的最终版本本来就在工具结果里,塞进去是重复的。 stream-json 下它是协议的一部分(tool-output-delta 帧),所以也不再往 stderr 抄一份带 │ 前缀的。

headless 协议

对外的完整说明在 docs/HEADLESS.md(含「什么时候该直接 用库而不是走协议」)。线上的形状在 protocol/src/headless-wire.ts —— 本目录只做编解码。

文件职责
headless/emit.ts事件 → stdout 的一行 JSON。seq 编号、审批中继、artifact 的 base64 摘除
headless/read.tsstdin 的一行 → 入站事件 + 给人看的错误信息(坏行跳过不杀进程)
headless/outcome.ts收尾语义:报什么 reason、退什么码。单次问答与长驻模式共用这一份
headless/driver.ts长驻模式的状态机:排队、审批往返、abort / close、宿主没了怎么收摊
headless/session.ts长驻模式的装配(interactive: true、不问信任、不接交互式审批)
headless/approver.ts--permission-prompt-tool 的子进程桥:喂 JSON、收裁决、答不出来一律拒绝
headless/tasks.ts单次问答收尾时的后台任务摘要(措辞复用 core 的 describeBackgroundTask)

三条不许改回去的规矩,每一条都在对应文件的头注释里写了理由:

  1. stdout 只有协议内容。 混一行中文,epoch "…" --json | jq 当场碎
  2. seq 的编号规则和 server 的 hub 逐条一致 —— fixtures/wire/*.json 那几份录像因此能同时当两边的夹具
  3. 宿主没了不等于用户拒绝。 不许逐个 respond('deny'),要放弃就 abandonApprovals() + abort()(同 runtime/src/serialize.ts)

定时任务的文案走穷举的表,不走模板字符串

schedule/labels.ts 把每个码映射到一个 catalog key, 写成 Record<ScheduleIssueCode, string> 而不是 t(`schedule.issue.${code}`)。 两条理由缺一不可:

  1. 少一条当场编译不过。 模板字符串那种写法漏掉的表现是用户看到一行 schedule.issue.xxx 的 key 路径 —— 而那要等到有人真撞上那条校验才被发现。
  2. catalog 的「死 key」检查扫的是字面量(根 __tests__/i18n-catalog.test.ts)。 key 拼在模板里的话,那几十条文案会被判成死 key,然后下一个人照着报告删掉它们。

core / runtime 那两层一个中文都没有:它们给的是 ScheduleIssueCode、 ScheduleRunStatus 这种码和技术细节,人话全在这一层。判据同 SettingWriteRefused.reason 那条「是码不是句子」。

同一个文件里还有 TUI 开机那一行(pendingNotice(),方案 45 §八 ①): 库里有定时任务欠着授权时,那一行说清是哪条任务、差几条、以及 epoch schedule run <id> --fix。措辞落在这一层而不是 tui 包里,理由逐字同 goal-view.ts:tui 只依赖 protocol,够不着 t(), 而 check:i18n 那条棘轮只许降不许升。它走的是已有的注入路径 (renderApp({ startupNotices })),和启动诊断、目标那一行同一栏 —— 用户只有一个地方要看。

Web UI 的产物不在这个包里

epoch web 托管的前端产物随 server 一起发(<server 包>/dist/web), 本包不再拷一份——2026-08-14 之前它在 cli/dist/web,搬家的理由见 server/scripts/copy-web-assets.mjs 的头注释 (一句话:嵌入宿主依赖的是 server,产物留在 cli 里等于它们拿不到界面)。

所以本包的 build 就是一句 tsup,epoch web 也不再自己去探产物目录—— 不传 webRoot,由 createWebServer() 定位自带的那份。

epoch web 是一个宿主,但插件页不再需要它点头

buildRuntime() 上那几个 host* 选项(hostCapabilities / hostPreset)是给 「把引擎嵌进自己程序里」的人用的。2026-11-06 之前这里还有第三个: hostMarketplaces + --plugins,让 epoch web 显式决定开不开插件页。 市场 / 上传那一整套宿主定制删掉之后,本地安装不依赖任何宿主开关, runtime.plugins 永远装配 —— 那个旗子没有可切的东西了,于是删了。

⚠️ 写口那些 POST 的回环闸不受影响:--host 0.0.0.0 下读得到、装不了。 命令行上的用法在 docs/CLI.md 的「Web UI」一节。

开发

pnpm epoch:dev --version        # tsx 直跑源码,不用 build
pnpm epoch:dev doctor

⚠️ pnpm epoch:dev 只有本包是源码。 tui / runtime / core / server 都是按 package.json 的 exports 从各自 dist/ 解析的(没有 tsconfig paths 映射)。改了那些包不 pnpm build 就来测, 测的是上一次的产物——而且 typecheck 也读 dist 的 .d.ts,两边一起骗你。

版本与发布记录

@epoch-agent/cli 共发布 57 个版本,首次发布于 2026/08/19;过去 12 个月发布了 57 个版本。

版本发布日期距今
6.2.0latest2026/11/13今天
6.1.02026/11/112 天前
6.0.02026/11/112 天前
5.0.02026/11/103 天前
4.1.12026/11/067 天前
4.1.02026/11/067 天前

关于 @epoch-agent/cli 的常见问题

@epoch-agent/cli 是什么?

epoch-agent CLI 入口

如何安装 @epoch-agent/cli?

在项目目录运行 npm install @epoch-agent/cli,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 epoch,可以用 npx epoch 直接运行。

@epoch-agent/cli 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@epoch-agent/cli 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@epoch-agent/cli 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@epoch-agent/cli 可以商用吗?

@epoch-agent/cli 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@epoch-agent/cli 的下载量有多少?

根据 npm 官方下载统计,@epoch-agent/cli 在 2026/10/14 – 2026/11/12 这 30 天内被下载 3,727 次,日均约 124 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@epoch-agent/cli 还在维护吗?

最新版本 6.2.0 发布于 2026/11/13(今天);至今共发布 57 个版本,其中过去 12 个月发布了 57 个;首次发布于 2026/08/19。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。