# @epoch-agent/cli 中文文档

规范地址：https://npmcn.com/package/%40epoch-agent/cli/

版本：6\.2\.0
npm 元数据更新时间：2026-11-13T08:30:38.769Z
中文译文更新时间：2026-11-13T19:51:27.566Z
软件包许可：Apache-2\.0

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40epoch-agent/cli
原文对照：https://npmcn.com/package/%40epoch-agent/cli/?lang=original
项目主页：https://github.com/Ddbor/epoch-agent#readme
源码仓库：https://github.com/Ddbor/epoch-agent

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：273 kB，8 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- Node.js 要求：\>=22\.0\.0
- 运行依赖：10 个
- 首次发布：2026-08-19T10:31:22.560Z
- 最新版本发布：2026-11-13T08:30:38.387Z
- 近 30 天下载量：3727（2026-10-14 至 2026-11-12，npm 官方下载统计）
- 已发布版本数：57（近 12 个月 57 个）

## 中文简介

时代代理人 CLI XXX

## 安装

```sh
npm install @epoch-agent/cli
```

也可以使用：`pnpm add @epoch-agent/cli`、`yarn add @epoch-agent/cli`、`bun add @epoch-agent/cli`

## README

# @epoch-agent/cli

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

-   ✅ **做**：commander 参数解析、TUI 启动、审批的终端交互、输出格式化、 退出码、更新检查、shell 补全
-   ❌ **不做**：装配（在 [runtime](https://npmcn.com/package/%40epoch-agent/runtime)）、领域逻辑（在 [core](https://npmcn.com/package/%40epoch-agent/core)）、 渲染细节（在 [tui](https://npmcn.com/package/%40epoch-agent/tui)）
-   **依赖**：[protocol](https://npmcn.com/package/%40epoch-agent/protocol) + [infra](https://npmcn.com/package/%40epoch-agent/infra) + [core](https://npmcn.com/package/%40epoch-agent/core) + [runtime](https://npmcn.com/package/%40epoch-agent/runtime) + [server](https://npmcn.com/package/%40epoch-agent/server) + [tui](https://npmcn.com/package/%40epoch-agent/tui) + `commander` + `@inquirer/prompts` + `ink` + `react`

**命令的完整清单（含全局选项和每个子命令）在 [docs/CLI.md](https://npmcn.com/package/docs/CLI.md)**，这里不重复一遍——两处抄同一份清单必然漂。

```bash
epoch model              # 首次使用：交互式添加一条模型（「我的模型」），key 进系统钥匙串
epoch doctor             # 装不上 / 跑不起来时第一个跑这个
```

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

## 文件

| 文件 / 目录 | 职责 |
| --- | --- |
| `index.ts` | commander 装配，每个子命令挂在这里 |
| `commands/run.ts` | `epoch [query]`：判断走 TUI / 单次问答 / 长驻模式 / 读 stdin |
| `commands/run-once.ts` | 单次问答的输出（三种 `--output-format`）+ 收尾（后台任务摘要、退出码） |
| `commands/run-stream.ts` | `text` 档两条流（模型正文 / 工具实时输出）在同一块屏幕上怎么交错：`createStreamWriter` |
| `commands/run-flags.ts` | 全局选项的解析（纯函数）：会话续接三态、格式、限额、审批器路径 |
| `commands/run-overrides.ts` | **分流之前**那一步：所有带值 flag 的落地与校验都挂在 `applyRunFlags` 这一个入口（见下） |
| `commands/sessions.ts` | `epoch sessions`，同时是**会话续接的唯一实现处**：`-c` / `-r` 与 `sessions resume` 共用它的 id 解析 |
| `headless/` | 程序驱动那条路（方案 28）：见下面「headless 协议」 |
| `commands/schedule.ts` | `epoch schedule` 的命令注册（薄壳） |
| `schedule/` | 定时任务那一族的实现：`add` / `view` / `run` / `manage` / `doctor`，`labels.ts` 是**码 → 文案的唯一出口**（见下） |
| `commands/*.ts` | `config` / `mcp` / `model` / `status` / `sessions` / `trust` / `agents` / `plugin` / `upgrade` / `web` / `completion` |
| `commands/web-open.ts` | 跨平台打开浏览器（`start` / `open` / `xdg-open`） |
| `tui-entry.ts` | TUI 启动：建 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.ts` | CLI 侧的路径与版本号；`--version` 那三行也在 `version.ts`，**第一行必须是裸版本号** |

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

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

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

七个走环境变量的（其余全局选项是解析成 `BuildRuntimeOptions` 的字段传下去的）：

| 选项 | 环境变量 | 值 |
| --- | --- | --- |
| `--settings` | `EPOCH_SETTINGS` | 绝对路径 |
| `--add-dir` | `EPOCH_ADD_DIR` | 绝对路径，按 `path.delimiter` 连起来 |
| `--agent` | `EPOCH_AGENT` | 角色名原样 |
| `-m/--model` | `EPOCH_MODEL` | `@<id>`（「我的模型」里的一条） |
| `--fallback-model` | `EPOCH_FALLBACK_MODEL` | `@<id>` |
| `--permission` | `EPOCH_PERMISSION` | 收窄过的权限级别 |
| `-c` / `-r` | `EPOCH_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](https://npmcn.com/package/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](https://npmcn.com/package/docs/HEADLESS.md)**（含「什么时候该直接 用库而不是走协议」）。线上的形状在 [protocol/src/headless-wire.ts](https://npmcn.com/package/%40epoch-agent/protocol/src/headless-wire.ts) —— 本目录只做编解码。

| 文件 | 职责 |
| --- | --- |
| `headless/emit.ts` | 事件 → stdout 的一行 JSON。`seq` 编号、审批中继、artifact 的 base64 摘除 |
| `headless/read.ts` | stdin 的一行 → 入站事件 + **给人看的**错误信息（坏行跳过不杀进程） |
| `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](https://npmcn.com/package/%40epoch-agent/server/src/hub.ts) 逐条一致** —— `fixtures/wire/*.json` 那几份录像因此能同时当两边的夹具
3.  **宿主没了不等于用户拒绝。** 不许逐个 `respond('deny')`，要放弃就 `abandonApprovals()` + `abort()`（同 [runtime/src/serialize.ts](https://npmcn.com/package/%40epoch-agent/runtime/src/serialize.ts)）

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

[`schedule/labels.ts`](https://npmcn.com/package/%40epoch-agent/cli/src/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`](https://npmcn.com/package/%40epoch-agent/cli/src/goal-view.ts)：`tui` 只依赖 protocol，够不着 `t()`， 而 `check:i18n` 那条棘轮只许降不许升。它走的是**已有的注入路径** （`renderApp({ startupNotices })`），和启动诊断、目标那一行同一栏 —— 用户只有一个地方要看。

## Web UI 的产物不在这个包里

`epoch web` 托管的前端产物随 [server](https://npmcn.com/package/%40epoch-agent/server) 一起发（`<server 包>/dist/web`）， 本包**不再**拷一份——2026-08-14 之前它在 `cli/dist/web`，搬家的理由见 [server/scripts/copy-web-assets.mjs](https://npmcn.com/package/%40epoch-agent/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](https://npmcn.com/package/docs/CLI.md) 的「Web UI」一节。

## 开发

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

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

## 常见问题

### @epoch-agent/cli 是什么？

时代代理人 CLI XXX

### 如何安装 @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 和提交记录判断。

