HTTP API 参考
ccmux 守护进程提供 HTTP 和 JSON API,可用于列出会话、读取项目数据和构建远程客户端。本参考文档介绍连接设置、信任模型、端点和响应类型。
路由和类型定义维护在 ccmux 源码中。旧版守护进程可能不支持此处列出的所有端点;请通过健康检查端点查看运行版本。
传输方式与可达性
同一组路由通过两种传输方式提供:
| 传输方式 | 地址 | 默认值 |
|---|---|---|
| 本地 Unix 套接字 | ~/.local/state/ccmux/ccmuxd.sock (权限模式为 0600) |
始终开启 |
| Tailnet HTTP | http://TAILNET-IPv4:PORT (默认端口 7474) |
关闭 |
若要从其他设备访问守护进程,请在 守护进程 所在主机设置
daemon.listen_tailnet = true (可选设置 daemon.tailnet_port),配置文件为
~/.config/ccmux/config.toml ,然后重启 ccmuxd。绑定地址为主机的 tailscale ip -4。如果 Tailscale 未运行,则不会启动 tailnet 监听器(仅提供 Unix 套接字服务)。
- 所有路径均以此为前缀:
/v1/。不含/v2. - 请求和响应正文均为
application/json— ,以下接口除外:/v1/events(text/event-stream)和终端连接接口(WebSocket 升级)。 - 无 TLS。 Tailscale 的 WireGuard 隧道负责加密和身份验证;HTTP 监听器绑定到 tailnet IP,使用明文 HTTP。
- 请求正文大小上限为 64 KiB.
身份验证与信任模型——请先阅读
HTTP API 没有应用层身份验证。 没有 Bearer 令牌、API 密钥、请求签名或 IP 白名单。信任边界是 你的 Tailscale tailnet:任何能够访问守护进程
100.x.x.x:7474 的主机都能调用所有接口——列出、创建、终止、重命名、发送按键, 连接完整的交互式终端,以及读取笔记、对话和用量。
请通过 Tailscale ACL保护访问权限,而非应用层身份验证。将任何能路由到守护进程 tailnet IP 的设备视为完全受信任的设备。
只有两个接口会逐请求验证,其用途是建立设备信任和推送服务,而非保护整个 API:
POST /v1/pair需要有效的一次性配对令牌 以及 可解析的 SSH 公钥。兑换令牌后,该公钥会安装到主机的~/.ssh/authorized_keys.POST /v1/pair-token仅限 Unix 套接字 (绝不通过 tailnet 提供),并且会返回503,除非listen_tailnet已开启。
错误格式
错误采用 纯文本 (text/plain):单行消息, 不是 JSON 对象。请检查 状态码,并将响应正文作为可读的错误字符串。成功的 JSON 响应使用 application/json.
| 状态码 | 含义 |
|---|---|
200 |
成功(JSON 正文) |
204 |
成功,无正文(终止会话 / 发送按键 / 注册设备) |
400 |
输入错误或验证失败 |
401 |
配对令牌无效或已过期(仅限/v1/pair ) |
404 |
未找到—— 也可能是守护进程版本尚不支持该接口 |
405 |
HTTP 方法错误 |
500 |
服务器 / tmux / 项目模板出错 |
503 |
功能不可用(例如 /v1/pair-token 且 listen_tailnet 关闭时) |
API 通过 新增 JSON 字段(所有可选字段使用 omitempty)和 新增 路由来演进。如果整个接口返回 404 ,应视为“此守护进程版本不支持该功能”,并妥善降级。
接口概览
| 方法 | 路径 | 响应 |
|---|---|---|
GET |
/v1/health |
HealthInfo |
GET |
/v1/peers |
[]PeerInfo |
GET |
/v1/sessions |
[]SessionState |
POST |
/v1/sessions |
SessionState (创建或连接) |
POST |
/v1/sessions/bare |
NewBareSessionResponse |
POST |
/v1/sessions/NAME/kill |
204 |
POST |
/v1/sessions/NAME/rename |
SessionState |
POST |
/v1/sessions/NAME/send-keys |
204 |
GET |
/v1/sessions/NAME/preview |
PreviewResponse |
GET |
/v1/sessions/NAME/attach |
WebSocket(交互式 PTY) |
GET |
/v1/projects |
[]ProjectInfo |
POST |
/v1/projects |
NewProjectResponse |
GET |
/v1/conversations |
[]Conversation |
GET |
/v1/usage?window=… |
AgentUsage |
GET |
/v1/notes?project=…[&file=…] |
[]NoteEntry 或 NoteContent |
GET |
/v1/notes/search?project=…&q=… |
[]SearchHit |
GET |
/v1/events |
SSE 事件流,类型为 SessionEvent |
POST |
/v1/pair-token (仅 Unix) |
PairTokenResponse |
POST |
/v1/pair |
PairResponse |
POST |
/v1/devices |
204 |
POST |
/v1/devices/test |
204 |
(路径中的 NAME 指 tmux 会话名称这一段,即 /v1/sessions/{name}/kill.)
接口详情
会话
GET /v1/sessions→[]SessionState。此守护进程管理的所有会话,包含由守护进程推导的state(active/idle/needs_input/error/unknown).POST /v1/sessions(NewSessionRequest,project必填)→SessionState。创建或连接绑定项目的代理会话(以 tmux 名称保证幂等)。path默认值为PROJECTS_ROOT/PROJECT,位于守护进程主机上。POST /v1/sessions/bare(NewBareSessionRequest) →NewBareSessionResponse。纯 shell 会话(无项目、无模板初始化)。POST /v1/sessions/NAME/kill→204。发出一个killedSSE 事件。POST /v1/sessions/NAME/rename(RenameRequest) →SessionState.NAME为当前名称;正文包含新名称。POST /v1/sessions/NAME/send-keys(SendKeysRequest) →204。直接传递给tmux send-keys(例如输入回复并按\n).GET /v1/sessions/NAME/preview?lines=N→PreviewResponse。活动窗格最后 N 行纯文本(已去除 ANSI 转义码)。N仅限1..200,默认24。无需打开终端连接套接字即可快速查看。
交互式终端—— GET /v1/sessions/NAME/attach (WebSocket)
升级为 WebSocket,通过 PTY 桥接到真实的 tmux attach-session ,提供真正的交互式终端(实时输出、输入、调整尺寸), 无需 ssh/mosh。使用 coder/websocket 帧格式。
- 客户端 → 服务器,二进制帧: 原始标准输入字节(按键)。
- 客户端 → 服务器,文本帧: JSON
{"cols":N,"rows":N},用于调整尺寸。 - 服务器 → 客户端,二进制帧: 原始 PTY 输出字节。
初始 PTY 尺寸为 80x24 ,直到首次调整尺寸。服务器每 25 秒发送一次 ping,超时为 10 秒;请回应 pong,否则连接会断开。
InsecureSkipVerify 已设置(不检查 Origin )。关闭套接字只会
断开终端连接;tmux 会话继续运行。交互式界面应优先使用此接口,而非轮询
/preview 。
项目
GET /v1/projects→[]ProjectInfo。在守护进程项目根目录下发现的项目,带有主机名。POST /v1/projects(NewProjectRequest,name必填)→NewProjectResponse。创建新项目(仅创建目录,不执行CLAUDE.md或 git 初始化),然后启动代理会话。name必须是单个非隐藏路径段(不含/,\,且不能以此开头:.).
对话、用量、笔记
GET /v1/conversations→[]Conversation。历史代理对话,按时间倒序排列;id为代理自身的 UUID(其--resume标识)。GET /v1/usage?window=DURATION→AgentUsage。滚动时间窗口内各代理的 token 用量和费用(Go 时间格式,例如2h,24h;默认5h).GET /v1/notes?project=NAME→[]NoteEntry;配合&file=REL→NoteContent.file必须是相对于项目的.md路径,不能包含...GET /v1/notes/search?project=NAME&q=QUERY→[]SearchHit。基于 ripgrep。(旧版守护进程会返回404,应视为“搜索不可用”。)
实时更新—— GET /v1/events (SSE)
text/event-stream。每个 data: 帧均为 JSON 格式的 SessionEvent; kind 仅限
created / killed / state_change / needs_input。心跳: : connected 在连接时发送, : ping 每 20 秒发送一次(以 : 开头的注释行可忽略)。收到 event: drops / data: N 帧表示遗漏了 N 个事件,应重新获取 /v1/sessions 以恢复同步。
健康检查与发现
GET /v1/health→HealthInfo。存活状态和身份探测。GET /v1/peers→[]PeerInfo。所有 tailnet 节点及其是否运行 ccmuxd(未安装 Tailscale 时返回[],而非500)。
配对与推送(移动端)
可选流程,用于原生推送(APNs/iOS、FCM/Android)和安装 SSH 密钥,以便设备使用 ssh/mosh 连接终端。 无需 配对即可通过 tailnet 使用读取和操作接口。
POST /v1/pair-token(仅 Unix 套接字)→PairTokenResponse。生成一次性令牌(128 位十六进制,单次使用,5 分钟有效期)以及ccmux://pair?…深层链接。503当listen_tailnet关闭时返回。POST /v1/pair(PairRequest) →PairResponse。兑换令牌:安装设备的 SSH 公钥,并可同时注册推送令牌。401在令牌无效或过期时返回。可通过 tailnet 访问。POST /v1/devices(RegisterDeviceRequest) →204。在已配对主机上注册或刷新推送令牌。设备通过其 SSHpublic_key标识(仅存储 SHA-256 哈希)。provider仅限apns(默认)或fcm;APNs 需要env=development/production,FCM 需要留空的env.POST /v1/devices/test({"public_key":"…"}) →204。向该密钥对应的设备发送验证推送。
推送在两种状态变化时触发:会话进入 needs_input,以及
active → idle (“代理已完成”)。推送中的会话 ID 为
local/SESSIONNAME。APNs 和 FCM 默认关闭 ,需要服务器端配置;FCM 路由已实现,但 Android 投递尚未接通。
类型
摘自 internal/daemon/protocol.go (Go 结构体及其 JSON 标签)。
type SessionState struct {
Name string `json:"name"`
Host string `json:"host"` // "local" or a remote host name
Project string `json:"project"`
Path string `json:"path"` // session working directory
State string `json:"state"` // active|idle|needs_input|error|unknown
Attached bool `json:"attached"`
Windows int `json:"windows"`
Created time.Time `json:"created"`
LastChange time.Time `json:"last_change"`
PromptCount int `json:"prompt_count"`
Agent string `json:"agent,omitempty"`
}
type HealthInfo struct {
OK bool `json:"ok"`
Hostname string `json:"hostname"`
Version string `json:"version"`
Sessions int `json:"sessions"`
SleepMode string `json:"sleep_mode"` // off|safe|dangerous|very_dangerous
}
type SessionEvent struct {
At time.Time `json:"at"`
Kind string `json:"kind"` // state_change|created|killed|needs_input
Session SessionState `json:"session"`
}
type NewSessionRequest struct {
Project string `json:"project"`
Path string `json:"path"` // defaults to PROJECTS_ROOT/PROJECT
Continue bool `json:"continue"` // start the agent with --continue
Name string `json:"name,omitempty"`
Agent string `json:"agent,omitempty"`
}
type NewBareSessionRequest struct {
Name string `json:"name,omitempty"`
Path string `json:"path,omitempty"`
Agent string `json:"agent,omitempty"`
}
type NewBareSessionResponse struct {
Session string `json:"session"`
Path string `json:"path"`
Host string `json:"host"`
}
type NewProjectRequest struct {
Name string `json:"name"`
Agent string `json:"agent,omitempty"`
}
type NewProjectResponse struct {
Session string `json:"session"`
Path string `json:"path"`
Host string `json:"host"`
}
type ProjectInfo struct {
Name string `json:"name"`
Host string `json:"host"`
Path string `json:"path"` // absolute on the daemon host
HasGit bool `json:"has_git"`
HasCM bool `json:"has_cm"`
HasAgents bool `json:"has_agents,omitempty"`
HasDocs bool `json:"has_docs"`
Agent string `json:"agent,omitempty"`
Modified time.Time `json:"modified"`
}
type PeerInfo struct {
Hostname string `json:"hostname"`
Addr string `json:"addr"` // tailnet IPv4
OS string `json:"os"`
Online bool `json:"online"`
RunsCCMuxd bool `json:"runs_ccmuxd"`
Port *int `json:"port,omitempty"`
}
type Conversation struct {
ID string `json:"id"` // agent UUID (its --resume id)
Agent string `json:"agent"`
Project string `json:"project,omitempty"`
Path string `json:"path,omitempty"`
Preview string `json:"preview,omitempty"`
Modified time.Time `json:"modified"`
}
type AgentUsage struct {
Claude UsageSummary `json:"claude"`
Codex UsageSummary `json:"codex"`
Antigravity UsageSummary `json:"antigravity"`
}
type UsageSummary struct {
HasData bool `json:"has_data"`
WindowSeconds int `json:"window_seconds"`
Prompts int `json:"prompts"`
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
EstimatedCost float64 `json:"estimated_cost"` // USD
}
type NoteEntry struct {
Rel string `json:"rel"`
Dir string `json:"dir"`
Display string `json:"display"`
Modified time.Time `json:"modified"`
}
type NoteContent struct {
Rel string `json:"rel"`
Content string `json:"content"`
}
type SearchHit struct {
Rel string `json:"rel"`
LineNum int `json:"line_num"`
Snippet string `json:"snippet"`
}
type PreviewResponse struct {
Lines int `json:"lines"`
Content string `json:"content"`
}
type RenameRequest struct{ Name string `json:"name"` }
type SendKeysRequest struct{ Keys string `json:"keys"` }
type PairRequest struct {
Token string `json:"token"`
PublicKey string `json:"public_key"`
DeviceToken string `json:"device_token,omitempty"`
APNsEnv string `json:"apns_env,omitempty"` // development|production
}
type PairResponse struct{ Hostname string `json:"hostname"`; Version string `json:"version"` }
type PairTokenResponse struct{ Token string `json:"token"`; URL string `json:"url"` }
type RegisterDeviceRequest struct {
Token string `json:"token"`
Env string `json:"env,omitempty"` // apns: development|production; fcm: empty
PublicKey string `json:"public_key"`
Provider string `json:"provider,omitempty"` // apns (default) | fcm
}
// POST /v1/devices/test body is { "public_key": "..." }
客户端应同步遵循的验证规则
- tmux 会话名称 不能包含
/,\,或:. - 项目名称 (用于
POST /v1/projects)必须为单个非隐藏路径段。 - 笔记文件路径 必须相对于项目,不含
..,并以此结尾:.md. - 请求正文大小上限为 64 KiB.
示例
# Health (over the tailnet)
curl -s http://100.75.64.20:7474/v1/health | jq
# List sessions
curl -s http://100.75.64.20:7474/v1/sessions | jq
# Type a reply into a session and press Enter
curl -s -X POST http://100.75.64.20:7474/v1/sessions/c-auth/send-keys \
-H 'content-type: application/json' \
-d '{"keys":"yes, ship it\n"}'
# Peek at the last 40 lines of a pane
curl -s 'http://100.75.64.20:7474/v1/sessions/c-auth/preview?lines=40' | jq -r .content
# Subscribe to live events
curl -sN http://100.75.64.20:7474/v1/events
Go 参考客户端位于
internal/daemon/client.go
,是标准调用实现,也可作为方法与接口的对应表。
一起改进 ccmux
发现了问题、说明不够清楚,或者翻译可以更自然?欢迎任何大小的贡献。你可以报告问题,或提交拉取请求。
发现错误或过时内容? 在 GitHub 上编辑此页.