Skip to content
Conduit

skill

trellis

Tree-of-thought canvas chat (Claude / Codex backed, Next.js)

SmokingMouse/trelliscommunitylistedunreviewed

Set up

Skills are installed by placing their folder where your agent loads skills.

The download comes from github.com/SmokingMouse/trellis itself, not from our server, so what you get is exactly what its author published.

Install instructions come from the README

Skills do not use an MCP config. Follow the steps in the README below.

README

synced Oct 25, 2026

30 秒跑起来

  1. 安装依赖:make setup
  2. 启动服务:make dev
  3. 访问地址:http://localhost:3000

前置依赖和进阶说明见下方 Quickstart。


树状画布 — 思维树概览聚焦阅读 — 锚点回跳参考材料 — 飞书 / YouTube / 网页通吃

行内分叉:线性视图里真分叉折成「↳ N 个分支」,点开就地切到那条 lineage

选区分叉:在任意回复里选一段 → ⌘K 长出子节点

笔记本:选中 → 📌 进抽屉,点笔记跳回原节点 + 闪烁定位

Wide tree:55+ 节点的真实使用形态,深度学习 / 个股研究都能撑住


它解决什么

线性聊天有两个老问题:

  1. 想顺手追问一句细节,整个上下文就跑偏了。 你被迫在「问完再回主线」和「假装没看见」之间二选一。
  2. 长会话尾段越聊越钝。 Claude/GPT 的实际有效上下文比标称小得多,一窗到底等于反复让模型在长 prompt 里做摘要。

Trellis 的处理方式:每个回答都是一个节点,选中文字 → ⌘K 即可在那一句旁边长出分叉子节点,原会话不动。每个分支独立流式生成、独立持久化、独立可回跳,画布上呈现整棵树。

而且它不是另起炉灶——Trellis 把请求转交给你本机的 claude / codex CLI 子进程,并且能把 CLI 已有的会话 attach 进来双向同步:CLI 里聊的、trellis 里聊的,落的是同一份 transcript,两边都看得到、都能续。

适合:长篇研究式问答(个股深度研究、技术学习、文档导读)、需要保留多条思路的探索、需要随时回到某个引用处对照的阅读,以及——把你在终端里跑的 Claude Code 会话搬进一个能浏览/搜索/分叉的工作台。


核心特性

双视图:线性 thread + 树状画布

  • 线性 thread 主视图(Project 默认):像 ChatGPT 一样把当前 lineage 纵向铺开连续阅读;真分叉折成行内「↳ N 个分支」可展开切换;右下角 SVG 树缩略图导航全貌,点节点即跳
  • 树状画布:ReactFlow + Dagre 自动布局的全局思维树,一键在「线性 / 画布」之间切换
  • 三层视图:Layer 1 全局画布 / Layer 2 单节点聚焦 / Layer 3 全屏阅读
  • 子树折叠:卡片右下角 ▶ N / ▼ N 角标一键收起后代;折叠状态按 session 持久化
  • 大纲侧边栏:左侧树形 outline,缩进按「分叉深度」而非轮数——线性长聊保持平铺不跑出面板,只有真分叉才缩进
  • 缩放分级渲染(LoD)、Fit View(F)、切 active 平滑 pan

选区分叉与锚点

  • ⌘K 分叉:任意回复里选一段 → 浮 popover → 输入追问,新节点带 anchor 出生
  • 锚点高亮 + 回跳:父节点正文那段变黄 <mark>,点击跳到子节点;子节点全屏顶部常驻「从『…』分叉」横幅,B 一键回父节点引用处 + emerald 闪烁定位
  • 编辑即重问:全屏问题区铅笔 → 改问法 = 新建 sibling 分支,原问答无损保留,天然多版本对比
  • 跨 markdown 结构:代码块 / 表格 / 链接 / 加粗 / 列表都能正确包裹(React 渲染后直接 wrap textNode,不改 markdown 源)

与 CLI 双向打通

详见 下方专章。这是 Trellis 区别于一般 AI 客户端的核心。

  • Attach 本机 CLI 会话:按 provider 浏览 ~/.claude/projects 或 $CODEX_HOME/sessions 里的已有会话,手选 attach 进 Trellis(不按目录批量灌)
  • 真双向实时同步:CLI 侧聊新一轮 → watcher ≤1s 自动导入 trellis;trellis 侧续聊 → resume 写回同一份 jsonl + 身份对账。两边落同一 transcript
  • 分支对齐:CLI 的 rewind / /branch ↔ trellis 分叉双向对齐(一棵 trellis 树 = 一组 CLI session 按 message uuid 求并集)。trellis 从任意历史节点分叉 → 构造前缀 jsonl,在 CLI 侧也成为独立可 --resume 的会话
  • 「在 CLI 继续」:Project 会话每个回答下一键复制 claude --resume <id> 或 codex resume <id> 命令,回终端无缝接着聊

参考材料

  • 多种来源:粘贴文本 / URL 抓取(飞书 / YouTube / GitHub / 普通网页通吃)
  • 抓取策略不 hard-code:URL 内容由 Claude/Codex CLI 自己挑工具(飞书走 feishu-cli、YouTube 走字幕、普通网页走 web-fetch)
  • 抓取进度 ring + 失败降级手动粘贴 + ⟳ 重新拉取
  • 参考节点同样能选区分叉发问,体验和 QA 节点一致

两种 LLM 上下文模式

session 创建时锁定 mode + workspace + model(per-session 持久化,切走再回来不会变),顶栏只读 badge 显示。详见 下方专章。

模式类比cwdTools / Skills / MCP跨节点记忆适合
ChatGPT 网页客户端无Claude:WebSearch + WebFetch;Codex:内置 cached web search + read-only;⚡ 增强模式 = scratch 目录 + 全工具无默认日常问答、补认知、查信息;临时跑个命令 / skill 开增强
ProjectClaude Projects / 长期项目session 绑定全开有(沿祖先链,分支间隔离)在仓库里干活、跨节点延续记忆,cache 命中通常 70%+

Provider 可切 Claude(Sonnet / Opus / Haiku)/ Codex(OpenAI)/ Mock(确定性假回复,看 UI 用),两档语义对齐。

工具调用与文件预览(Project / 增强 Chat)

  • 工具调用可视化:解析 stream-json 的 tool_use / tool_result,在节点折叠区展示每次 Bash / Read / Write / WebFetch 的入参与输出
  • 交互式工具表单(Claude):模型发起 AskUserQuestion / ExitPlanMode 时渲染成可填表单(单选/多选/批准计划),就地作答回灌
  • 本地文件预览:回答里生成或提到的文件 / HTML 直接在 trellis 内预览(HTML 走 opaque-origin sandbox iframe,图片/PDF/markdown 各自渲染);行内像路径的代码也可点开。安全围栏 = session 实际碰过的目录范围,realpath 归一防逃逸

全文搜索 · 命令面板 · Skill

  • ⌘P 跨 session 全文搜:FTS5 trigram 分词(中英混排都能子串匹配),覆盖问题 / 回答 / 参考 / 笔记,按 mode facet 过滤
  • 命令面板:首屏输入框 / 前缀触发 —— /new /clear(🧹 开新话题/清空上下文)/archive /switch /model 等 session 元操作,纯 Trellis 命令本地执行不发 LLM
  • Skill 入口:Claude 输入 /<skill-name>;Codex 输入 $<skill-name>,并按 .agents/skills、~/.agents/skills、$CODEX_HOME/skills 等作用域发现(纯 Chat 点选自动开启增强模式)
  • Agent / System Prompt 可配:5 个内置 Agent + 自定义人设,Claude 与 Codex 都可用;per-session 锁定,也支持 @slug 单轮调用

会话工作台

  • Session tabs:tmux 式常驻 tab 条 + ⌘1-9 快切 + live 状态点(streaming / done / error)
  • 归档:软隐藏不删 jsonl/节点,可恢复
  • Header context 徽章:当前上下文压力实时显示,≥50% 变可点 popover 提示开新话题
  • 重开恢复浏览位置:切回 session 回到上次离开的节点 + 视图层

图片输入(vision)

  • 粘贴 / 拖拽 / 选文件,两档模式通吃;Project 模式连续追问可跨节点引用同一张图
  • 内容寻址存储:blob 落 ~/.trellis/blobs/<sha256>.<ext> 自动去重,不进 SQLite
  • 重试自动带回原图;单图 ≤ 10MB,单节点 ≤ 6 张;PNG / JPEG / WebP / GIF

笔记本

  • 阅读时选中 + 📌 → 当前 session 笔记抽屉(桌面右侧 320px / 移动端底部 sheet)
  • 点笔记卡 → 跳回源节点 + scroll-to + emerald 闪烁;per-session,删 session 自动清理

流式与中止

  • Enter 发送(可一键切回 ⌘Enter);流式中 Esc 或卡片 ⏹ 中止
  • 保留半截响应:中止后部分内容落库(aborted 灰色态),原 prompt 回填便于编辑重试
  • In-place retry:失败/中止节点原地重试不新增节点
  • Durable streams:spawn 与 HTTP 解耦,断线/切 tab 不杀生成,回来自动续上

视觉与未读追踪

  • 节点编号 #N + 未读小圆点(停留 1s+ 自动标已读)+ J/K 跳未读 + Done Toast
  • 四桶 Token 计量:input / output / cacheRead / cacheCreation;⚡ emerald 强调 cache 复用。context 占用按主 agent 当前窗口实际值算(非跨工具迭代累计,避免虚高数倍)

键盘快捷键

键行为作用域
⌘K选区分叉 popover文本选中时
⌘D选区入笔记本文本选中时
⌘P跨 session 全文搜索全局
⌘↩ / Enter发送(默认 Enter,可切 ⌘Enter)textarea
Esc关 popover / 中止流 / 关弹窗全局
J / K下/上一个未读节点(环绕)全局
Alt + 方向节点导航:上=父 / 下=首子 / 左右=兄弟全局
⌘1-9切到第 N 个 session tab全局
FFit canvas to viewport画布
B回父节点锚点引用处全屏阅读且有父节点时

导出与持久化

  • JSON 导出(完整树 + token + 元数据)/ Markdown 导出(按深度生成层级标题,飞书友好)
  • 本地落地:SQLite ~/.trellis/data.db(WAL,启动自迁移),全部 session / 节点 / 引用 / 笔记 / 节点位置持久化
  • session 管理:picker 切换 / 改名 / 归档 / 删除(带确认)

主题与移动端

  • 明暗主题一键切(localStorage 持久化 + 预水合防闪白)
  • iOS Safari 选区双保险(polled + selectionchange),移动端专属 📌 按钮
  • 响应式:outline / 笔记 / 缩略图在移动端转抽屉或底部 sheet;触屏支持画布缩放拖动

它不是什么

  • 不是 SaaS。SQLite 落本地(~/.trellis/data.db),单人单机。
  • 不是直接调 API。Trellis 把请求转交给本机 claude / codex CLI 子进程——订阅 / 余额 / 模型权限完全跟 CLI 走,Trellis 不存任何 API key。
  • 不是为多人协作设计的。没有用户系统、没有同步;公网暴露请自带一层认证(仓库内置一个可选的 cookie 登录闸,见 proxy.ts)。

Quickstart

前置依赖

  • Bun v1.1+
  • 至少装一个 LLM CLI(Trellis 不直接打 API,是 spawn 本机 CLI):
    • Claude Code CLI:npm i -g @anthropic-ai/claude-code → claude 可用并已登录
    • Codex CLI(可选):codex login 完成登录
  • Web 终端功能依赖宿主机安装 ttyd 与 tmux。macOS:brew install ttyd tmux;Debian/Ubuntu:apt install ttyd tmux
  • 完全不装 CLI 也能启动——provider picker 选 Mock,返回固定假回复,仅用于看 UI

Trellis 的 LLM/CLI 运行时层是普通 npm 依赖(@smokingmouse/agent + @smokingmouse/llm,源码在 sm-toolkit),bun install 一步装完,无需额外 clone/build 任何仓库。

跑起来

git clone https://github.com/SmokingMouse/trellis.git
cd trellis
make setup   # = bun install + 前置检查
make dev

只想看当前环境缺什么、不装任何东西:make check。

若未安装 ttyd,Trellis 仍可启动,但 Web 终端不可用;启动/部署日志与终端面板会提示 ttyd 缺失和对应平台的安装命令。Trellis 不会自动执行安装命令。

唯一的运行时注意点:必须用 bun --bun run dev(make dev/make build/make start 已经这么做了)——bun run 不会把 Next/Turbopack 内部起的 worker 进程纳入 bun 运行时,导致 bun:sqlite(lib/server/sqlite.ts 用)解析不到。

打开 http://localhost:3000,第一次输入问题即创建 session。想 attach 已有 CLI 会话:左侧 sidebar →「Attach CLI 会话」。

第三方模型(可选)

原生 claude / codex 走 CLI 登录态,零配置。想接 Anthropic 兼容的第三方端点(deepseek / kimi / ark …):

mkdir -p ~/.config/sm
cp node_modules/@smokingmouse/llm/endpoints.example.yaml ~/.config/sm/endpoints.yaml
# 编辑它:填你自己的 provider / 模型清单,API key 只写环境变量名,key 本体放 env

搜索顺序 $SM_ENDPOINTS_PATH → ~/.config/sm/endpoints.yaml → ~/.claude/global/endpoints.yaml(legacy)。改完重启即出现在模型 picker。

本地开发 SDK(可选)

要改 sm-toolkit 本体时:clone 到 ~/sdk,make link-sdk 把两个包软链过来(bun install 会冲掉软链,重跑即可);make unlink-sdk 回到 registry 版本。

生产构建

单机随手跑一把:

make build && make start   # 固定 -p 3088;等价 bun --bun run build && bun --bun run start -- -p 3088

数据落 ~/.trellis/data.db(SQLite WAL,自动迁移)。卸载只需删掉这个目录。

长驻部署与升级

如果 Trellis 是常驻服务(launchd / systemd),不要在它正在运行的那个目录里 make build 再重启:next build 会就地改掉运行中的 .next,build 失败就把服务打成半死,build 成功却忘了重启则是「内存里旧模块 + 磁盘上新文件」混跑(页面能开但交互全挂)。而且没有回滚路径。

改用 make deploy:

make deploy              # 部署当前 HEAD
make deploy REF=v1.2.0   # 部署指定 ref
make rollback            # 切回上一个 release
make deploy-status       # 看 current / previous 与上次部署状态

它做的事:把目标 commit 导出到 ~/.trellis/releases/<sha>/ 里装依赖并 build(全程不碰正在跑的服务)→ 用真数据库的一致性快照起一个临时实例,验证它真的能启动、能出页面、能读现网数据 → 备份数据库 → 原子换 ~/.trellis/current 软链 + 重启 → 验活;验活不过自动回滚到上一个 release。

任何一步失败都不会动到正在跑的版本。实测切换窗口 ≈ 0.2s,且期间返回的是维护页而不是连接被拒——网关进程先占住端口、再拉起 Next,Next 起不来时它不会跟着自杀,而是退避重启并出 503 维护页(未登录只显示「正在更新」,版本号与日志要登录后才看得到)。GET /__gate/health 给出网关与 Next 的真实状态。

首次启用需要把常驻服务的工作目录指向软链,一次性:

make deploy            # 先建出第一个 release
make install-service   # 把常驻服务的工作目录改成 ~/.trellis/current(会先备份原定义文件)

之后仓库目录就只是开发用的 checkout 了,在里面 build 不再影响线上。应急回滚不依赖仓库:~/.trellis/bin/rollback.sh。

macOS / Linux 都支持:重启走 launchd(launchctl kickstart -k)还是 systemd user unit(systemctl --user restart)按平台自动判定,unit 名默认取 label 的最后一段(com.smokingmouse.trellis → trellis.service,用 TRELLIS_DEPLOY_UNIT 覆盖)。make deploy-status 会把认到的那套连同它当前的工作目录一起打出来。

两种情况 make deploy 会拒绝执行,都可以 FORCE=1 强行继续:① 有会话正在生成(切换会中断它们,会列出是哪些);② 常驻服务的工作目录不是 ~/.trellis/current(这种状态下重启只是让服务在原目录里重来一遍,跟部署的那个 sha 没关系)。

新机器从零部署

部署产物(release / 数据库 / 配置)全部落在 ~/.trellis/,代码里没有写死的机器路径;真正跟机器走的是宿主机状态:bun、已登录的 CLI、常驻服务定义、以及不进 git 的 .env.local。按顺序补齐:

  1. 装依赖:bun(必须,运行时用了 bun:sqlite/Bun.serve,Node 跑不了)→ claude CLI 并 claude login → 可选 codex / ttyd / tmux。make check 随时报缺什么。

  2. clone + make setup。

  3. 建运行期配置 ~/.trellis/shared/.env.local(shared/ 下的文件会被软链进每个 release,不随 release 清理丢失):

    TRELLIS_AUTH_PASS=<登录密码>
    TRELLIS_AUTH_TOKEN=<随机长串,cookie 会话令牌>
    TRELLIS_REPO_DIR=<trellis 仓库 checkout 的绝对路径>
    # 可选:agent-server 主页收编(TRELLIS_AS_ADOPT),配置说明见下
    TRELLIS_AS=off
    

    前两个任一缺失 = 认证闸静默关闭。机器只在内网/Tailscale 里可以不配;挂公网隧道必须配——Trellis 能 spawn CLI 在宿主机执行任意代码,这道闸是唯一的门。TRELLIS_REPO_DIR 给应用内「检查更新/更新到最新」用(release 目录不是 git 仓库,部署脚本要回到 checkout 里跑)。

  4. 装常驻服务定义(模板见下),WorkingDirectory 先指向仓库 checkout,并在 checkout 里 make build 一次让它有东西可跑,然后加载服务。

  5. 首次部署:make deploy FORCE=1——此时服务工作目录还不是 ~/.trellis/current(正是下一步要改的),preflight 会拦,首次明知故犯一次。

  6. make install-service:把服务定义的工作目录改到 ~/.trellis/current(自动备份原文件并重载)。之后 make deploy 不再需要 FORCE,仓库目录退化为纯开发 checkout。

  7. 验证:make deploy-status;curl http://127.0.0.1:3088/__gate/health 应给出 next=ready 且 auth=on。

agent-server 主页收编与 project 切流

主页收编与 project 切流共用启用判定:TRELLIS_AS=off 优先级最高,即使设置了 socket 也关闭;否则 TRELLIS_AS=on 或设置 TRELLIS_AS_SOCKET 即启用。另设 TRELLIS_AS_PROJECT=on 才给新 project 会话打 thread 标记。只关 PROJECT 会停止新会话打标,已绑定会话继续使用 daemon;关 AS 则 project 新请求带 notice 回退兼容模式,保留输入与祖先历史,外部会话保留历史并拒绝发送。pane 绑定仍由 herdr-bridge 接线。

先独立启动 agent-server daemon,再在开发用 .env.local 或部署用 ~/.trellis/shared/.env.local 配置下表变量并重启 Trellis。可参考仓库的 .env.example。设置 TRELLIS_AS_ADOPT=on 后,在主页查看并继续外部会话,审批与系统日志也在主页呈现。

变量含义与默认值
TRELLIS_AS默认不启用;on 启用,off 是最高优先级硬关闸,即使已配置 socket 也禁用。未设置时,非空 TRELLIS_AS_SOCKET 会自动启用。
TRELLIS_AS_PROJECTon 时为新 project 会话绑定 daemon thread,默认关闭。已绑定会话由 TRELLIS_AS 控制。
TRELLIS_AS_ADOPT默认 off;设为 on 自动收编 daemon 上至少有一个 turn、尚未绑定且非 closed 的外部线程。TRELLIS_AS=off 覆盖它。
TRELLIS_AS_PROJECT_ID可选的精确 projects.id,只允许该项目所属工作区的新会话绑定 AS;未知归属保持 legacy。未设置时保留全部新 project 会话可切流的行为;停流请关闭 PROJECT 或 AS,不要只删此筛选值。
TRELLIS_AS_SOCKETdaemon Unix socket 的绝对路径。未设置时遵循 agent-server 路径规则:AGENT_SERVER_SOCKET_PATH 优先,其次绝对 XDG_RUNTIME_DIR 下的 sm-toolkit/agent-server.sock,其次绝对 XDG_STATE_HOME 下的同一路径,最后为 $HOME/.sm-toolkit/agent-server.sock。
TRELLIS_AS_TOKEN_PATH已运行 daemon 的 token 文件绝对路径,不是 token 内容。默认绝对 XDG_STATE_HOME 下的 sm-toolkit/agent-server/token,否则 $HOME/.agent-server/token。自定义 socket 不会自动改变 token 路径,两个配置需指向同一个 daemon。

连接失败不会阻断 Trellis 启动;收编扫描器按下述策略退避。主页权限与系统日志通过节点 SSE 订阅 daemon 快照与通知,保留断线续传、15 秒保活与慢消费者保护。主页与接口沿用 Trellis 现有鉴权闸;这些变量仅在服务端读取,不要使用 NEXT_PUBLIC_ 前缀。

实时审批依赖 daemon 的 pendingRequests 能力:首次 attach/断线重连用快照初始化,随后订阅 thread/pendingRequests;project 的可操作表单来自 server request。静默时不重复推送历史;旧 daemon 应升级后使用实时审批。

project 从最新空闲节点续聊复用 thread;早期节点续聊、显式 fork、重试,或原 thread 已被其他客户端推进时,需要新 thread。daemon 声明 midThreadFork 时,以 as_turns.last_item_id 为边界发送 thread/fork {fromItemId},新历史只含所选节点之前的完整前缀。缺少该能力才把所选祖先历史播种到新 thread;有能力但边界无效时直接报错,不静默退回播种。无 daemon 映射的兼容历史仍需播种。

客户端随仓库 vendor,来源固定在 vendor/agent-server/VENDORED_FROM;刷新用 SM_TOOLKIT_DIR=/path/to/sm-toolkit sh scripts/vendor-agent-server.sh 后执行 bun install。复核命令:bunx tsc --noEmit、bun test、scripts/mobile-verify/mobile-as-project.sh、scripts/mobile-verify/mobile-as-adopt.sh;移动验收只用 mock 引擎与脚本锁定的隔离端口。

外部会话收编由 TRELLIS_AS_ADOPT=on 单独开启,不依赖 PROJECT 开关或 PROJECT_ID 灰度范围。启动发现线程并读取首次快照,健康时每 1.5 秒用一次 thread/list 发现新线程(超过协议每页 10000 条才分页);已订阅线程由通知标记变化,只有新增、摘要变化或收到通知的线程按已存 sinceSeq 增量 attach,无变化时零 attach。故障按 2–30 秒指数退避,重连从已投影 cursor 补齐断线变化;关闭/删除线程清理观察缓存。关闭 ADOPT 不启动扫描器,不向 daemon 发出收编请求。零 turn 线程持续观察但不建会话,首个 turn 出现后才收编;扫描快照及 turn 通知都可识别首轮。

每轮 thread/list 底噪随历史累计线程数(含 closed)线性增长,按二审实测约 160 条线程后单轮超过 100 KB,已记 backlog。

非 closed、未绑定的线程按 realpath 后 cwd 的最长包含真实 workspace 根归属;真实根必须有 .git 文件/目录,或是 Herdr 已知的 repo/worktree。非 git 目录项目与系统 home/scratch 根均不参与,cwd 精确等于系统根也不复用其归属。非 git 目录项目如「投研」不参与归属,其下外部线程进入「外部会话」。没有真实匹配时自动创建唯一的系统项目「外部会话」(cluster_key=trellis:external)。若 cwd 已被其他非真实 workspace 占用,则使用外部项目的独立归组 workspace,实际执行目录仍保存在 sessions.workspace_path;不移动已有 workspace 或其中的会话。会话及节点携带 origin=external、backend,标题保留 meta/fjContext 中的标题或契约号。

主页提问复用原 thread,审批竞答、中断和分叉沿用 project 路径;正在运行的轮次不能被静默覆盖。外部线程关闭后,主页保留历史并显示已结束,拒绝新提问。Trellis 不会调用 thread/close 收编线程;删除会话只解绑,as_adoptions 留永久墓碑,即使外部再有新 turn 或 Trellis 重启也不会重新收编。关闭 ADOPT 不删除已收编历史;需要回退时设 TRELLIS_AS_ADOPT=off 并重启。整体关闭可设 TRELLIS_AS=off,收编会话保持历史并拒绝发送,不会另起兼容引擎。

as/1 的 attach 提供 items、不提供历史 Turn 记录;回填文本和工具项保持原投影格式,历史状态从 items 与线程状态恢复,不虚构历史 usage。新 turn 的生命周期通知用于完成状态和 usage。验证脚本 scripts/mobile-verify/mobile-as-adopt.sh 使用隔离 fixture daemon、数据库副本、3479/3480 与共用互斥锁,清理所有自建进程并输出手机截图。

常驻服务模板

launchd(macOS):存为 ~/Library/LaunchAgents/com.smokingmouse.trellis.plist(label 可用 TRELLIS_DEPLOY_LABEL 换),launchctl bootstrap gui/$(id -u) <plist路径> 加载。把 /Users/YOU 全部换成真实家目录——plist 不展开 ~:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.smokingmouse.trellis</string>
    <key>ProgramArguments</key>
    <array>
        <string>/Users/YOU/.bun/bin/bun</string>
        <string>--bun</string><string>run</string><string>start</string>
        <string>--</string><string>-p</string><string>3088</string>
    </array>
    <!-- install-service 只改这一行;首次先指向仓库 checkout -->
    <key>WorkingDirectory</key>
    <string>/Users/YOU/path/to/trellis</string>
    <key>EnvironmentVariables</key>
    <dict>
        <key>HOME</key>
        <string>/Users/YOU</string>
        <!-- launchd 不继承 shell 环境。spawn claude/git/ttyd 全靠这条 PATH:
             必须含 claude CLI 所在 bin(npm -g 的目录,nvm 下形如
             ~/.nvm/versions/node/vXX/bin)和 Homebrew bin。 -->
        <key>PATH</key>
        <string>/Users/YOU/.bun/bin:/Users/YOU/.nvm/versions/node/vXX.X.X/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
        <key>NODE_ENV</key>
        <string>production</string>
    </dict>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
    <key>StandardOutPath</key>
    <string>/Users/YOU/Library/Logs/trellis.log</string>
    <key>StandardErrorPath</key>
    <string>/Users/YOU/Library/Logs/trellis.log</string>
</dict>
</plist>

macOS 已知雷:claude CLI 的凭证默认存 keychain,launchd 会话读 keychain 可能读到失效凭证——症状是终端里 claude 一切正常、Trellis spawn 出的却报未登录。修法:删掉 keychain 里的 Claude Code 凭证条目让 CLI 回退到 ~/.claude/.credentials.json 文件存储,重新 claude login。

systemd user unit(Linux):存为 ~/.config/systemd/user/trellis.service,systemctl --user daemon-reload && systemctl --user enable --now trellis。ssh 登出后要继续跑:loginctl enable-linger $USER(缺它连 systemctl --user 都不可用,部署 preflight 会报 XDG_RUNTIME_DIR 相关错误):

[Unit]
Description=trellis
After=network.target

[Service]
# install-service 只改这一行;首次先指向仓库 checkout
WorkingDirectory=%h/path/to/trellis
ExecStart=%h/.bun/bin/bun --bun run start -- -p 3088
# systemd 不继承 shell 环境。PATH 必须含 claude CLI 所在 bin。
Environment=PATH=%h/.bun/bin:%h/.local/bin:/usr/local/bin:/usr/bin:/bin
Environment=NODE_ENV=production
Restart=always
RestartSec=2

[Install]
WantedBy=default.target

与 CLI 双向打通

Trellis 把每个 Project 会话当成真实 CLI thread:Claude transcript 位于 ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl,Codex rollout 位于 $CODEX_HOME/sessions/YYYY/MM/DD/*.jsonl。在此之上做了三件事:

Attach + 双向同步

  • 发现 + attach:浏览本机 CLI 会话清单(排除 trellis 自己 spawn 的,防回环),手选哪些 attach。attach 的会话 origin 标 cli-import,跑在 Project 模式
  • CLI → trellis:watcher(instrumentation.ts 启动)监听 attached jsonl 所在目录,文件一变(CLI 侧聊了新轮 / rewind / /branch)→ debounce 后增量重导 → SSE 推前端无刷新 reload
  • Trellis → CLI:在 attached 会话续聊 = 对应 provider 的 resume 写回同一 transcript;done 后身份对账(删临时流式节点,让 canonical CLI 节点接管),两个方向收敛到同一份 transcript
  • 物理约束:同一会话别在 CLI 和 trellis 同时各聊一轮(抢 append);串行无碍

分支对齐(一棵树 = 一组 CLI session)

CLI 的单条会话是线性的,Trellis 的是树。统一模型:一棵 Trellis 树 = 一组 CLI thread(root + 各 fork)的 lineage 并集。

  • CLI 里 Claude /branch / --fork-session 或 Codex fork 产生的新 transcript → Trellis 自动按共享前缀归并到同一棵树
  • Trellis 里从 tip 续 = 线性 append 同 transcript;从任意历史节点分叉 = 构造 root→X 的前缀 transcript 并换新 session/thread id,再由对应 CLI resume,因此分支在终端也能独立继续

Claude /clear 与 /compact 怎么对应

  • /clear(CLI 抹掉上下文、开新 session id)→ 那是另一份不共享 uuid 的 jsonl,trellis 不会自动并进当前树(它是「不相关的新对话」),而是作为一条独立可 attach 的会话出现。等价物 = trellis 的「🧹 新话题」(同树里加一个全新上下文的根)
  • /compact(同 session 摘要压缩、不换 jsonl)→ 在原 jsonl 追加一个 type:"system" 边界节点,对话继续。trellis 读全量 jsonl,边界前后所有原始轮都完整显示(compact 只压缩「模型那侧的窗口」,不动存储)——解析器专门桥接这个 system 边界节点,保证父链不在 compact 处断裂

CLI 里新建的 worktree 多久出现在侧栏

在终端 git worktree add 出来的目录不用重启 trellis 就会被服务端认到,但不是实时——扫描是后台节流跑的,不再挂在侧栏角标请求上(挂上去的代价:一个几十条 workspace 路径都指向同一个大仓的机器上,每次角标请求都要为每条路径同步 spawn 一次 git worktree list,主线程被占满,登录页能被拖到秒级)。

下表是服务端延迟上界——即新 worktree 多久进 DB、多久出现在侧栏接口返回的数据里。浏览器那一侧还要等一次刷新:客户端的侧栏 effect 依赖 sessionsRevision / gitNonce,后台重扫只更新服务端内存 generation,不会主动推给已经打开的静止页面。所以停在页面上什么都不做时,不保证在下表时限内自己冒出来;切回窗口(focus)、发生任何一次侧栏刷新或手动刷新即可见。

通道服务端可见延迟上界
fs.watch 盯住 <repo>/.git/worktrees(默认,最多同时盯 32 个 repo)去抖窗口 + 一轮扫描,通常 1–2 秒(实测 ~1.08s 进 DB 并出现在 listProjectTree)
后台定时兜底(watch 不可用 / 超过 32 个 repo / 该 repo 还没有任何 linked worktree)一个扫描周期,默认 60 秒
环境变量默认说明
TRELLIS_WORKTREE_RESCAN_MS60000后台重扫周期。0 = 不起定时器(只剩 fs.watch)
TRELLIS_WORKTREE_WATCH_MS1000fs.watch 事件去抖窗口——git worktree add 会连着触发好几次
TRELLIS_GIT_STATUS_CONCURRENCY6角标接口同时跑几个 git status。大仓上 6 条并发已经能吃满盘

扫描本身按 repo 去重(几十条同仓路径只跑一次 git worktree list),且 <repo>/.git/worktrees 的 mtime 没变时直接复用上一轮结果,一个 git 子进程都不起。侧栏角标请求只读扫描结果,rescan.skipped 恒为 true 明示「本次请求没有重扫」。


两种上下文模式(详解)

session 创建时锁定 mode + workspace,整棵树共用。两档差别在 prompt 怎么组装、CLI 走什么权限、是否绑定 cwd、跨节点要不要共享记忆,trade-off 是「成本 / 权限范围 / 上下文连续性」。

历史注:2026-07-16 砍掉了中间档 Workspace(一次性 CLI、每轮无状态)——实际用量为零,它的两个场景已被别人吃掉:临时跑命令/skill → Chat 增强模式;在仓库干活 → Project。存量 workspace 会话(如有)migrate 时自动归入 Project。

Chat —— GPT 网页客户端替代

  • Prompt:Trellis 自己组装"祖先链 → 当前问题"(深度可调 + anchor excerpt),用一个简短 system prompt
  • 执行:claude 仅开 WebSearch + WebFetch,cwd ~,不读 CLAUDE.md / 不加载 skills / MCP / Bash
  • 每条分支真正独立——claude 看到的只是 trellis 给的折叠历史,无外部副作用
  • claude 家族默认走 B-fork(--fork-session,历史活在 fork 出的 CLI session 里,不往 prompt 折叠)
  • ⚡ 增强模式(可随时开关,点选 skill 自动开):spawn 到 ~/.trellis/chat-scratch scratch 目录 + 权限全开——能跑 skill / Bash / 文件工具,但不绑定你的仓库

Project —— 长期协作 + per-lineage 真 CLI session

  • Prompt:只发当前问题给 CLI,让它 resume 持久 thread 自己找历史
  • 执行:一条岔一个 CLI lineage——顺着聊 = resume 线性 append 同一份 transcript;真分叉 = 在分叉点构造前缀 transcript 成新 session/thread;cwd 绑定 workspace_path
  • 沿祖先链共享 CLI 历史(分支间互相隔离),线性续聊通常能复用大量 cache
  • 「🧹 新话题」= 同树里另起一个 root(全新 fresh context,不 resume 旧记忆,对应 CLI /clear)
  • 存量会话(per-lineage 上线前建的)保持旧的全树共享行为不迁移;新建 project session 自动 per-lineage

Codex 那边

  • Chat → sandbox read-only,隔离 AGENTS.md / 环境 skills / plugins / MCP,保留 Codex 内置 web search(CLI 默认 cached);增强模式 = scratch 目录 + 全工具
  • Project → $CODEX_HOME/config.toml + MCP/tools/web 全开,一条分支一个可独立 resume 的 rollout lineage,cwd = workspace_path
  • Agent 人设、模型、静态 sandbox 权限、隔离与挂载 skill 生效;Codex exec 当前没有接入 Trellis 的逐项审批回调,工具白/黑名单也无法强制

技术架构

flowchart LR
    subgraph 浏览器["浏览器 (Next.js App Router)"]
        UI["React + Zustand<br/>线性 thread / ReactFlow 画布"]
        DOM["DOM mark injector<br/>(textNode wrap)"]
    end

    subgraph 服务端["Next.js Route Handlers (Node runtime)"]
        Chat["/api/chat · SSE 推流"]
        Refs["/api/references"]
        Sync["/api/cli-sync<br/>discover / attach / events"]
        Sess["/api/sessions · /api/search"]
    end

    subgraph CLI["CLI 子进程"]
        Claude["claude (stream-json)"]
        Codex["codex exec (jsonl)"]
        Mock["mock (确定性)"]
    end

    DB[("~/.trellis/data.db<br/>SQLite WAL")]
    JSONL[("~/.claude/projects · $CODEX_HOME/sessions<br/>CLI transcripts")]

    UI -- "POST /api/chat" --> Chat
    Chat -- "SSE delta" --> UI
    Chat -- "spawn" --> Claude
    Chat -- "spawn" --> Codex
    Chat -- "spawn" --> Mock
    Claude -- "stdout JSONL" --> Chat
    Refs -- "spawn URL fetch" --> Claude

    Watcher["cli-sync watcher<br/>(instrumentation boot)"]
    JSONL -- "fs.watch 增量" --> Watcher
    Watcher -- "import + SSE" --> UI
    Claude -- "resume 写回" --> JSONL

    Chat <--> DB
    Sync <--> DB
    Sess <--> DB
    Watcher <--> DB
  • 前端:Next.js 16 App Router,单页,所有"导航"都是 Zustand 状态。线性 thread + ReactFlow/Dagre 画布双视图;stream-bus 把 SSE delta 直喂 DOM,绕开 React 重渲染热路径
  • 服务端:Route Handlers(runtime: nodejs),spawn claude -p … --output-format stream-json / codex exec --json,按行解析 JSONL 转 SSE
  • CLI 同步:instrumentation.ts 启动单例 watcher;Claude/Codex provider parser(jsonl → Q/A 树)+ cli-import-db.ts(union upsert)+ cli-fork.ts(前缀 transcript / lineage 解析)
  • 存储:~/.trellis/data.db(better-sqlite3 WAL),schema 在 lib/server/sqlite.ts 启动自迁移
  • mark 注入:选区/笔记/锚点高亮是 React 渲染之后对 textNode wrap <mark>(lib/dom-mark-injector.ts),不改 markdown 源

更详细的架构决策见 progress/ 下各 spec(CLI 同步、分支对齐 P1/P2、线性视图等)。


项目结构

app/                 Next.js App Router 页面 + API routes(含 api/cli-sync)
components/           React UI(LinearThreadView / Canvas / NodeFullView / Outline / …)
hooks/               useUnreadNavigation / useCliSyncEvents / useReconnectStreams / …
lib/
  collapsed.ts        折叠/祖先链纯函数
  layout.ts           Dagre 布局 + LoD 阈值
  llm/                provider 抽象(claude / codex / mock)
  server/
    cli-import*.ts     CLI jsonl 解析 + DB union 导入
    cli-fork.ts        前缀 jsonl 构造 / lineage 解析 / 在 CLI 继续
    cli-sync-*.ts      watcher + SSE 事件
    repo.ts / sqlite.ts  DB 访问 + schema 迁移
    run-bus.ts         spawn 所有权 + durable streams
stores/               Zustand session store(视图 / 折叠 / 流式控制)
progress/             开发 dashboard + session log + 各 feature spec
instrumentation.ts    Next 启动钩子:拉起 CLI 同步 watcher

现状

Alpha,自用为主,已在生产环境跑了一段时间。还存在的粗糙边角:

  • 移动端(≤ 640px)只做到能用,iOS Safari 上选区分叉偶有抖动
  • 没有用户/权限/同步——多人用法请加 reverse proxy 或用内置 cookie 闸
  • 分支对齐 P2 的「从历史节点分叉」已端到端验证,但树内分叉的 fork 文件会在 CLI 项目目录里累积(可接受,Claude 自己 /branch 也这样)

欢迎提 Issue / PR,但请理解定位是个人工具,不会为了泛用化把模型 / 路由抽象到框架级别。


License

MIT(见 LICENSE)。底层依赖的 Claude Code / Codex CLI 各有自己的服务条款,自行确认。


Star History

↑↓ navigate openesc close