Skip to content

CLI ​

状态:✅ packages/cli(v0.29)· 默认偏向本机 coding,写文件 / 跑命令前先问 · 终端 TUI(v0.24)· 网页工作台 tah serve(v0.25)· 约定式项目热更新(v0.26)· 附件(v0.27)· 项目配置能力包(v0.28)· Skills(v0.29)

模型回复边生成边显示(agent.assistant-stream),不必等整段 generate 结束。

命令 ​

sh
npx @typescript-agent-harness/cli run --mock "列出当前目录并说明这个项目"
npx @typescript-agent-harness/cli --mock chat

全局安装:npm install -g @typescript-agent-harness/cli,之后直接 tah …。仓库内:pnpm build 后用 pnpm tah -- …。

命令作用
tah run <prompt>一次性 Session
tah chat多轮;默认续上一次持久化 Session;终端里是 TUI(v0.24),管道或 --plain 仍是逐行模式;/exit / /reset / /compact / /sessions / /resume / /fork
tah serve在本机起网页工作台,浏览器里操作当前目录的 Agent(v0.25,见下文「网页工作台」)
tah sessions列出已保存的 Session(最新在前;不需要 API key,v0.22)
tah init在当前目录生成约定式项目骨架并装好依赖(v0.23,见下文「约定式项目」)
tah help用法
tah version打印 CLI 版本(也可 --version / -v)

标志 ​

标志作用
--cwd <path>工作区(工具的根目录)
--mock本地假模型;没有 API key 时必须加,否则退出码 1
--persistSQLite:<cwd>/.tah/cli.db(默认开)
--no-persist关掉 SQLite;chat 不会跨 /exit 续聊
--quiet只打印模型回复;tah run 只在结束时输出最后一条回复,调工具前说的话不输出,适合脚本(v0.26.2)
--exec挂上 execute_command(默认开)
--no-exec关掉 execute_command
--yes / -y跳过 write_file / edit_file / execute_command 的审批
--max-steps <n>每轮 LLM 上限(默认不限,Ctrl+C 停)
--system-file <path>用文件内容替换默认角色「workspace coding agent」(v0.22)
--session <id|n>tah chat 续指定 Session,而不是最近一个(v0.22)
--mcp <cmd>挂上 stdio MCP 工具(默认关)
--mcp-arg <a>--mcp 的额外参数(可重复)
--allow <tool>白名单(可重复;不写则不限制)
--deny <tool>黑名单(可重复;优先于 --allow)
--once <ms>延迟后跑一轮 tah run(默认关;仅 run)
--builtin-tools约定式项目有 tools/ 时,仍挂上内置的文件 / 命令工具(v0.23)
--no-installtah init 只生成文件,不装依赖(v0.23.1)
--plaintah chat 强制逐行模式,即使在终端里也不进 TUI(v0.24)
--port <n>tah serve 的端口(默认 7420;被占用时报错,不自动换,v0.25)
--no-opentah serve 不自动打开浏览器(v0.25)
--no-watch约定式项目里不热更新(终端 tah chat / tah serve,v0.26)
--visiontah serve:图片附件以多模态 parts 发给模型(也可用 TAH_VISION=1,v0.27)

不加 --mock 时必须有 DEEPSEEK_API_KEY 或 OPENAI_API_KEY(<cwd>/.env,以及仓库内 examples/basic-agent/.env)。没有 key 不会再静默 mock。只有 OPENAI_API_KEY 时连 OpenAI 官方(https://api.openai.com/v1,模型 gpt-4o-mini,v0.26.3);其他厂商走 OpenAI 兼容接口,还要设 OPENAI_BASE_URL 和 OPENAI_MODEL(DEEPSEEK_MODEL 可换 DeepSeek 的模型)。.env 里值为空的行(如 DEEPSEEK_API_KEY=)等于没写(v0.26.3)。也可以在 tah serve 网页左下角的 Settings 里添加模型(存在 ~/.tah/settings.json,选中的模型优先于 .env,tah chat / tah run 也会用它);tah serve 没有 key 也能启动,再去 Settings 里加(v0.30)。各家地址见 教程 · 接上真模型。

默认一轮跑到模型自己停下为止,中途 Ctrl+C 打断。传了 --max-steps 后,走到上限会打印 finishReason=max_steps(状态行的 steps 是本轮 LLM 次数);tah chat 在终端里会接着问「继续?」,回车就用新的一份额度继续同一轮,n 停下。tah run 或管道输入时不问,可之后在会话里说「继续」。

管道多轮:

sh
printf '第一句\n第二句\n' | npx @typescript-agent-harness/cli --mock chat

续聊(默认 persist):

sh
tah --mock chat          # 聊几句后 /exit
tah --mock chat          # 应看到 resumed session …

默认挂上的能力 ​

LLM + list_files / read_file / grep / write_file / edit_file + execute_command + Agent Loop + SQLite。
MCP / scheduler 仍默认关。

execute_command 默认 30 秒超时;装依赖、构建、跑测试这类慢命令,模型会按次传 timeoutMs(最长 10 分钟)。--max-steps 对续上的旧 Session 同样生效。

sh
pnpm tah -- --mock run "用 node 打印 1+1"

审批(v0.22) ​

write_file、edit_file 和 execute_command 每次执行前会弹一张卡片(写文件时预览前 8 行内容,改文件时用 - / + 预览被替换的原文和新文本)。终端 TUI 里直接按 y / a / n(回车、Esc 也是拒绝),网页里点卡片上的按钮;--plain 逐行模式下输入字母后回车:

text
╭─ Run command
│ $ npm test
╰─
  y allow once  ·  a always allow execute_command  ·  n / Enter reject  ›
回答效果
y只放行这一次
a本次进程内该工具不再问(热更新后仍有效)
其他 / 回车拒绝;模型收到 rejected by user,应改问你怎么办
  • --yes / -y:全部放行,不问
  • stdin 不是终端(管道、CI)且没加 --yes:直接拒绝,避免把管道里的下一行当成回答
  • 被 --deny 拦下的工具不会弹审批
sh
printf '把 README 第一行改成 Hello\n' | tah --yes chat   # 脚本里要写文件必须 --yes

多个 Session:列出、切换、分叉(v0.22) ​

sh
tah sessions
#   1  session_31_muyxw3su  completed   2 turns  2026-10-08 10:50  帮我看下 auth 模块…
#   2  session_1_muyxvyp2   completed   3 turns  2026-10-08 10:42  跑一下单测…
tah chat --session 2        # 序号或完整 id

chat 里:

命令作用
/sessions列出 Session,* 是当前这个
/resume <id|n>切到另一个 Session
/fork复制当前 Session 到新 Session 并切过去;原 Session 不变(想试另一条路时用)
/fork <n>只保留前 n 轮再分叉(「回到第 n 轮重来」)
/compact把到目前为止的对话压缩成摘要,之后只发摘要和新对话(0.31,见「长会话上下文」)

序号随最近更新时间变化,以最近一次 tah sessions / /sessions 为准。分叉不需要 persist;--session / /resume 需要(默认开)。

自定义角色(v0.22) ​

sh
echo "You are a strict code reviewer. Report bugs first, cite file:line. Answer in Chinese." > reviewer.md
tah --system-file reviewer.md chat
  • 只替换角色那一句;工具使用规则、exec 提示仍会追加在后面
  • 续上一次 Session 时同样生效(存下来的旧 system 会被换掉)
  • 每次启动都要带上;不带就回到默认 coding 角色

约定式项目(v0.23) ​

不写启动代码,按约定放文件就能做出自己的 Agent:

sh
mkdir my-agent && cd my-agent
npx @typescript-agent-harness/cli init   # 生成文件并装依赖
npx tah --mock chat

tah init 生成文件后自动装依赖(v0.23.1):目录里有 pnpm-lock.yaml / yarn.lock / package-lock.json 就用对应的包管理器,否则用启动它的那个(pnpm dlx → pnpm),默认 npm。项目放在别的 pnpm 工作区(上层有 pnpm-workspace.yaml)里时,用 pnpm install --ignore-workspace,否则 pnpm 会去装外层工作区、跳过这个项目。装失败或加了 --no-install,会打印要自己跑的命令。

text
my-agent/
├── AGENTS.md              # 人设和规则,替换默认的 coding agent 角色
├── tah.config.json        # 可选:官方能力包 / 扩展(v0.28)
├── tools/                 # 一个文件 = 一个工具
│   └── query-order.ts     # → 工具 query_order
├── plugins/               # 一个文件 = 一个插件(可选)
│   └── audit-log.ts       # → 插件 project:audit-log
├── skills/                # Agent Skills(Markdown 指令,v0.29)
│   └── hello/SKILL.md     # init 示例;模型用 skill 工具或 /hello 加载正文
├── lib/                   # 共用代码,不自动加载(改了会热更新)
└── package.json           # 依赖 @typescript-agent-harness/cli

项目配置 tah.config.json(v0.28) ​

约定目录管角色与自定义工具;根目录可选 tah.config.json 管官方能力(coding / vision)和 extensions.mcp。

json
{
  "capabilities": { "coding": true, "vision": true },
  "extensions": {
    "mcp": { "command": "npx", "args": ["-y", "some-mcp-server"] }
  }
}

合并顺序:内置默认 → config → CLI / 环境变量。键位、与 tools/ 的关系、上网(capabilities.web:web_fetch 默认开,web_search 填 Tavily key 后开;capabilities.browser:内置浏览器,browser_open 批准后弹出 Chrome)见配置参考:tah.config.json。

工具文件:

ts
// tools/query-order.ts
import { defineTool } from "@typescript-agent-harness/cli";

export default defineTool({
  description: "按订单号查询订单状态",
  inputSchema: {
    type: "object",
    properties: { orderId: { type: "string" } },
    required: ["orderId"],
  },
  async execute(input: { orderId: string }, ctx) {
    return { id: input.orderId, status: "shipped" };
  },
});

插件文件:export default definePlugin({ setup(ctx) { … } }),可以 ctx.on(...) 监听事件、ctx.get(TOOLS).register(...) 按条件加工具。

规则:

  • 何时生效:只有 --cwd(默认当前目录)的 package.json 在 dependencies 或 devDependencies 里有 @typescript-agent-harness/cli 时才算项目。普通代码仓库里的 tools/、plugins/、AGENTS.md 不会被加载。
  • 人设:AGENTS.md 替换默认角色;--system-file 优先于 AGENTS.md。
  • 内置工具:有 tools/ 目录时,list_files / read_file / grep / write_file / edit_file / execute_command 和对应的提示词规则都不挂(web_fetch / web_search 也跟着不挂,除非 tah.config.json 写 "web": true);加 --builtin-tools 恢复。只有 AGENTS.md 没有 tools/ 时,内置工具照常挂上。
  • 工具名:文件名去掉扩展名、- 换成 _;必须是小写字母开头的 a-z0-9_。defineTool({ name }) 可以覆盖。
  • 扫描:只扫 tools/、plugins/ 第一层的 .ts / .mts / .js / .mjs;跳过 _ 开头、*.test.*、*.spec.*、.d.ts 和子目录。按文件名排序加载,插件间有先后依赖时用 01- 前缀。
  • 注册顺序:项目插件在所有内置插件之后注册,setup 时 LLM、Tools、Session 都已可用。
  • TypeScript:用 jiti 加载,不用编译;可以 import "../lib/x.ts"。
  • 出错:文件加载失败、导出不对、名字不合法、重名,都在启动时报错并给出文件路径,退出码 1。运行中改出错不会退出,见下文「热更新」。
  • 审批:自定义工具执行前不询问。

启动行会多一行 [tah] project: AGENTS.md tools+1 plugins+1;有可用 skill 时还会带 skills+N。完整示例见 examples/convention-agent(pnpm demo:convention);从零做到部署的教程见 做一个文档问答 Agent。

Skills(v0.29) ​

skills/<name>/SKILL.md 放可复用流程说明(对齐 Agent Skills):启动只在 system prompt 里列出 name + description;模型用内置 skill 工具加载正文,或在 tah chat / tah serve 里输入 /hello 这类 slash(UI 仍只显示你打的那行字)。也读 .agents/skills/、.cursor/skills/ 与 ~/.tah/skills/(优先级与格式见 Skills 设计)。tah init 会生成 skills/hello/SKILL.md 作试玩示例。工具名 skill 由 CLI 保留,不要在 tools/ 里占同名文件。

热更新(v0.26) ​

终端里的 tah chat 和 tah serve 会监听 AGENTS.md、tools/、plugins/、lib/(含子目录),以及 skills/、.agents/skills/、.cursor/skills/ 与(若存在)~/.tah/skills/(v0.29)。保存后自动重新加载,当前会话和历史都保留,新的人设、工具与 skill catalog 从下一轮起生效:

text
[tah] reloaded: AGENTS.md tools+1 plugins+1
  • 一轮进行中(包括等待审批)改了文件:等这一轮结束再重载,不会中途换工具。重载期间发的消息会等重载完成再发。
  • 新文件有错(语法错误、导出不对、重名、插件 setup 抛错):继续用旧版本,提示 [tah] reload failed — still using the previous version: …,改对后保存即可。
  • 重载会重建整个 Runtime:插件的 dispose 会被调用,MCP 子进程会重启;「总是允许」的审批选择保留。
  • --no-persist 时会话只在内存里,重建会丢,所以只提示 … changed — restart to apply (persist is off),不重载。
  • 其他文件(package.json、.env、--system-file 指定的文件、lib/ 以外的目录)改了要重启。tah run、管道 / --plain 逐行模式没有热更新。--no-watch 关闭。

设计说明见 约定式项目热更新。

终端 UI(v0.24) ​

终端里直接 tah chat 进入 Ink TUI(内联滚动区,不切备用屏):流式 Markdown、工具调用行、界面内审批(y / a / n)、斜杠命令补全、会话选择器、多行输入(Alt+Enter 或行尾 \ 换行,粘贴整段进输入框)。管道、CI、--plain 仍是原来的逐行模式。

操作行为
Enter发送
Alt+Enter / Shift+Enter(支持的终端)/ 行尾 \ + Enter换行
/命令菜单;/resume 不带参数打开会话列表
Ctrl+C回复中:停本轮;有输入:清空;空闲:退出
Ctrl+D空闲且输入为空时退出

设计说明见 TUI 与会话接口。

网页工作台(v0.25) ​

sh
tah serve                 # 打开 http://127.0.0.1:7420/?token=…
tah serve --yes --port 8000 --no-open

tah serve 在本机起服务并打开浏览器,网页里操作的就是当前目录的 Agent:流式 Markdown、工具调用行、审批卡片(允许一次 / 总是允许 / 拒绝)、步数上限的「继续」、会话侧栏(切换 / 新建 / 分叉)、停止当前轮。和 TUI 一样同一时间只有一个当前会话;开多个标签页看到的是同一个会话,操作实时同步,刷新或断线重连后从服务端补齐。tah chat 的其他参数(--yes、--max-steps、--no-persist、--session、--mock …)照常可用。

工作文件夹(v0.30):侧栏按文件夹分组列出会话(Pinned 跨文件夹置顶);「Workspaces」标题旁的文件夹图标有两项:「Open project…」先列最近移除过的项目,「Choose folder…」弹系统的选文件夹对话框(macOS / Windows;其他系统用网页里的目录浏览器),「New folder…」默认在桌面新建(可换位置)并直接打开。左下角齿轮打开 Settings:Models 管理模型,Web search 填 Tavily key 打开 web_search(v0.31),Skills 列出已加载的 skill,并可把 ~/.cursor/skills 这类文件夹里的 skill 导入 ~/.tah/skills(v0.30.1,见 Skills)。文件夹列表存在 ~/.tah/workspaces.json。打开另一个文件夹里的会话(或点该组的 +)时,Agent 的工作目录随之切换;运行中的一轮结束前不能切换。.env 始终用启动时那个目录的。见 工作文件夹设计。

它是本机工作台,不是对外服务:

  • 只监听 127.0.0.1。要远程用,走 SSH 端口转发(ssh -L 7420:127.0.0.1:7420 …)。
  • 每次启动生成一次性令牌,只有终端打印的链接能登录;网页拿令牌换成 HttpOnly cookie 后从地址栏去掉令牌。
  • 校验 Host / Origin 头,挡住其他网页伪造请求。
  • 可附加文件与图片(选择 / 拖拽 / 粘贴):存到 <cwd>/.tah/attachments/,默认把只读路径写进 user 文本供工具读取;--vision 时图片进多模态请求。见 附件设计。

Ctrl+C 停止服务并打印续聊命令(tah chat --session …),再按一次强制退出。

改网页时:先 tah serve --no-open,再在 packages/web 里 pnpm dev,Vite 把 /api 代理到 127.0.0.1:7420;打开 Vite 的地址并带上终端里的 ?token=…。

设计说明见 tah serve 与网页工作台。

Ctrl+C 与失败重试(v0.22) ​

场景行为
tah chat(TUI)正在跑一轮(含等审批)中断这一轮;历史保留
tah chat(TUI)空闲有输入则清空,否则退出(同 /exit)
tah chat --plain 正在跑一轮中断这一轮,回到 you>;历史保留
tah chat --plain 空闲在 you>退出(同 /exit)
tah run直接退出
模型接口网络错误 / 429 / 5xx自动重试 3 次(1s、2s、4s),打印 [llm] … retry N in Xs
重试仍失败chat 打印失败提示并继续可聊,不退出

模型在调用工具前说的话(如「我先读一下文件」)会单独成行,和后面的 [tool] 日志分开;最终回答若复述了过程,那是模型行为,不是重复打印。

输出样式 ​

在终端里,模型回复的 Markdown 用 marked-terminal 的默认样式渲染(标题、粗体、行内代码、列表、带边框的表格、代码块语法高亮);[tah] / [llm] / [tool] / [ctx] 日志变灰;审批卡片用黄色边框。

  • 按 Markdown 块显示:一段文字、一个列表、一张表或一个代码块收完才出现(不再逐字出现)
  • 输出不是终端(管道、重定向)、设置了 NO_COLOR、或 TERM=dumb 时,原样输出纯文本

长会话上下文(v0.22) ​

每次请求最多发约 100,000 字符(约 25k token 的英文/代码;中文更费)。超了会打印:

text
[ctx] trimmed: 2 old turn(s) dropped, 5 tool output(s) omitted, 98712 chars sent (full history kept; /reset for a fresh session)

先省略旧的工具输出,再整轮丢最早的对话;当前这一轮始终完整。SQLite 里的完整历史不删。话题换了就 /reset,比靠裁剪更省 token。

从 0.31 起,超出预算时会先尝试压缩:让模型把之前的轮次总结成一份摘要,之后的请求只发摘要和新的对话。压缩成功会打印:

text
[ctx] compacted: 3 earlier turn(s) summarized in 1840 chars (full history kept)

压缩失败(例如模型报错)时退回上面的裁剪。也可以随时手动压缩:终端里输入 /compact,网页点状态栏右侧的 Compact。压缩过程中 Ctrl+C(网页上点停止)可以中止。

关掉命令执行:

sh
pnpm tah -- --no-exec --mock chat

挂 stdio MCP(子进程,不是官方 SDK;约定式项目也可写在 tah.config.json 的 extensions.mcp,见 配置参考):

sh
pnpm tah -- --mcp node --mcp-arg ./packages/mcp/test/fixtures/ping-server.mjs --mock "调用 ping"

只读策略:

sh
pnpm tah -- --deny write_file --deny execute_command --mock "只读这个仓库"

延迟一轮:

sh
pnpm tah -- --once 200 --mock run "列出当前目录"

Released under the MIT License.