Referência da API HTTP
O daemon ccmux expõe um HTTP e JSON API para listar sessões, ler dados de projetos e construir clientes remotos. Esta referência abrange a configuração da conexão, o modelo de confiança, os terminais e os tipos de resposta.
As rotas e tipos são mantidos no ccmux source. Versões mais antigas do daemon podem não suportar todos os endpoints documentados aqui; verifique o endpoint de integridade da versão em execução.
Transporte e acessibilidade
As mesmas rotas são servidas em dois transportes:
| Transporte | Endereço | Padrão |
|---|---|---|
| Soquete Unix local | ~/.local/state/ccmux/ccmuxd.sock (modo 0600) |
sempre ativado |
| tailnet HTTP | http://TAILNET-IPv4:PORT (porta padrão 7474) |
desativado |
Para acessar um daemon de outro dispositivo, no daemon host: definido
daemon.listen_tailnet = true (opcionalmente daemon.tailnet_port) em
~/.config/ccmux/config.toml e reinicie ccmuxd. O endereço de ligação é o do host tailscale ip -4. Se Tailscale não estiver em execução, o ouvinte tailnet não será iniciado silenciosamente (apenas o soquete Unix será servido).
- Todos os caminhos estão abaixo
/v1/. Não há/v2. - Os órgãos de solicitação/resposta são
application/json— exceto/v1/events(text/event-stream) e o endpoint de anexação (uma atualização do WebSocket). - Sem TLS. O túnel WireGuard do Tailscale é o limite de criptografia + identidade; o ouvinte HTTP é simplesmente HTTP vinculado ao IP da tailnet.
- Os corpos da solicitação são limitados a 64 KiB.
Modelo de autenticação e confiança — leia isto primeiro
Não há autenticação em nível de aplicativo no HTTP API. Nenhum token de portador, nenhuma chave API, nenhuma assinatura por solicitação, nenhuma lista de permissões de IP. O limite de confiança é sua tailnet Tailscale: qualquer host que possa alcançar o daemon
100.x.x.x:7474 pode chamar todos os endpoints – listar/criar/matar/renomear/enviar-chaves, anexado a um terminal interativo completoe leia notas/conversas/uso.
Proteja isso com ACLs Tailscale, não autenticação em nível de aplicativo. Trate qualquer coisa que possa ser roteada para o IP da tailnet do daemon como totalmente confiável.
Dois endpoints são validados por solicitação e apenas para inicializar a confiança do dispositivo + push — não para proteger o API:
POST /v1/pairrequer um token de emparelhamento válido e de uso único mais uma chave pública SSH analisável. Resgatá-lo instala essa chave no host~/.ssh/authorized_keys.POST /v1/pair-tokené Somente soquete Unix (nunca na tailnet) e retorna adicionalmente503a menos quelisten_tailnetestá ativado.
Formato do erro
Os erros são texto simples (text/plain): uma mensagem de linha única, não um envelope JSON. Verifique o código de statuse leia o corpo como uma string para a mensagem humana. As respostas JSON bem-sucedidas são application/json.
| Código | Significado |
|---|---|
200 |
OK (corpo JSON) |
204 |
OK, sem corpo (kill / send-keys / registro de dispositivo) |
400 |
falha de entrada/validação incorreta |
401 |
token de emparelhamento inválido ou expirado (/v1/pair apenas) |
404 |
não encontrado — ou este daemon é anterior ao endpoint |
405 |
método HTTP errado |
500 |
servidor / tmux / falha no andaime |
503 |
recurso indisponível (por ex. /v1/pair-token com listen_tailnet desativado) |
O API evolui em adicionando Campos JSON (todos os campos opcionais usam omitempty) e adicionando rotas. Trate um 404 em um endpoint inteiro como “este daemon é mais antigo que esse recurso” e degrada normalmente.
Resumo de endpoint
| Método | Caminho | Resposta |
|---|---|---|
GET |
/v1/health |
HealthInfo |
GET |
/v1/peers |
[]PeerInfo |
GET |
/v1/sessions |
[]SessionState |
POST |
/v1/sessions |
SessionState (criar ou anexar) |
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 interativo) |
GET |
/v1/projects |
[]ProjectInfo |
POST |
/v1/projects |
NewProjectResponse |
GET |
/v1/conversations |
[]Conversation |
GET |
/v1/usage?window=… |
AgentUsage |
GET |
/v1/notes?project=…[&file=…] |
[]NoteEntry ou NoteContent |
GET |
/v1/notes/search?project=…&q=… |
[]SearchHit |
GET |
/v1/events |
Fluxo SSE de SessionEvent |
POST |
/v1/pair-token (somente Unix) |
PairTokenResponse |
POST |
/v1/pair |
PairResponse |
POST |
/v1/devices |
204 |
POST |
/v1/devices/test |
204 |
(Onde um caminho mostra NAME é o segmento do caminho do nome da sessão tmux, ou seja, /v1/sessions/{name}/kill.)
Detalhes do terminal
Sessões
GET /v1/sessions→[]SessionState. Cada sessão que este daemon gerencia, com funções derivadas do daemonstate(active/idle/needs_input/error/unknown).POST /v1/sessions(NewSessionRequest,projectobrigatório) →SessionState. Crie ou anexe uma sessão de agente vinculada ao projeto (idempotente no nome tmux).patho padrão éPROJECTS_ROOT/PROJECTno host do daemon.POST /v1/sessions/bare(NewBareSessionRequest) →NewBareSessionResponse. Uma sessão somente shell (sem projeto, sem andaime).POST /v1/sessions/NAME/kill→204. Emite umkilledEvento SSE.POST /v1/sessions/NAME/rename(RenameRequest) →SessionState.NAMEé o nome atual; o corpo carrega o novo.POST /v1/sessions/NAME/send-keys(SendKeysRequest) →204. Passou paratmux send-keys(por exemplo, digite uma resposta +\n).GET /v1/sessions/NAME/preview?lines=N→PreviewResponse. Últimas N linhas do painel ativo como texto simples (ANSI removido).Né1..200, padrão24. Uma espiada leve sem abrir o soquete de fixação.
Terminal interativo — GET /v1/sessions/NAME/attach (WebSocket)
Atualização para um WebSocket interligado a um real tmux attach-session em um PTY: um verdadeiro terminal interativo (saída ao vivo, entrada, redimensionamento) sem ssh/mosh. Usos coder/websocket enquadramento.
- cliente → servidor, quadro binário: bytes stdin brutos (pressionamentos de teclas).
- cliente → servidor, quadro de texto: JSON
{"cols":N,"rows":N}para redimensionar. - servidor → cliente, quadro binário: bytes de saída PTY brutos.
O tamanho inicial do PTY é 80x24 até o primeiro redimensionamento. O servidor faz ping a cada 25 segundos com um prazo de 10 segundos – responda aos pongs ou espere uma desmontagem.
InsecureSkipVerify está definido (sem Origin verificação). Fechando apenas o soquete
desconecta; a sessão tmux continua em execução. Prefira isso a pesquisas
/preview para uma visualização interativa.
Projetos
GET /v1/projects→[]ProjectInfo. Projetos descobertos na raiz de projetos do daemon, marcados com seu nome de host.POST /v1/projects(NewProjectRequest,nameobrigatório) →NewProjectResponse. Cria um novo projeto (somente diretório — nãoCLAUDE.md/git) e inicia uma sessão do agente.namedeve ser um único segmento de caminho não oculto (sem/,\, sem início.).
Conversas, uso, notas
GET /v1/conversations→[]Conversation. Transcrições de agentes anteriores, mais recentes primeiro;idé o UUID do próprio agente (seu--resumeid).GET /v1/usage?window=DURATION→AgentUsage. Token por agente + custo em uma janela contínua (duração Go como2h,24h; padrão5h).GET /v1/notes?project=NAME→[]NoteEntry; com&file=REL→NoteContent.filedeve ser relativo ao projeto.mdcaminho, não...GET /v1/notes/search?project=NAME&q=QUERY→[]SearchHit. Apoiado por Ripgrep. (Demônios mais antigos404— tratado como “pesquisa indisponível”.)
Atualizações ao vivo — GET /v1/events (SSE)
text/event-stream. Cada data: quadro é um JSON SessionEvent; kind é
created / killed / state_change / needs_input. Batimentos cardíacos: : connected aberto, : ping a cada 20 segundos (linhas de comentários começando com : são ignoráveis). Um event: drops / data: N frame significa que você perdeu N eventos e deve buscar novamente /v1/sessions para ressincronizar.
Saúde e descoberta
GET /v1/health→HealthInfo. Vivência + investigação de identidade.GET /v1/peers→[]PeerInfo. Cada peer da tailnet + se cada um executa ccmuxd (retorna[], não500, quando Tailscale está ausente).
Emparelhamento e push (móvel)
Fluxo opcional para push nativo (APNs/iOS, FCM/Android) e instalação de uma chave SSH para que o dispositivo possa ssh/mosh anexar. Não necessário para usar os endpoints de leitura/ação na tailnet.
POST /v1/pair-token(somente soquete Unix) →PairTokenResponse. Crie um token único (hexadecimal de 128 bits, uso único, TTL de 5 minutos) + umccmux://pair?…link direto.503selisten_tailnetestá desligado.POST /v1/pair(PairRequest) →PairResponse. Resgatar um token: instale a chave pública SSH do dispositivo, opcionalmente registre um token push inline.401em token inválido/expirado. Acessível na tailnet.POST /v1/devices(RegisterDeviceRequest) →204. Registre/atualize um token push em um host já emparelhado. Dispositivo identificado por seu SSHpublic_key(armazenado apenas como um hash SHA-256).provideréapns(padrão) oufcm; Necessidades de APNsenv=development/production, FCM precisa estar vazioenv.POST /v1/devices/test({"public_key":"…"}) →204. Envio de verificação para o dispositivo dessa chave.
Aciona duas transições: uma sessão entrando needs_inpute
active → idle (“agente finalizado”). O ID da sessão do push é
local/SESSIONNAME. APNs e FCM são desativado por padrão e precisa de configuração do lado do servidor; O roteamento FCM existe, mas a entrega do Android ainda não está configurada.
Tipos
Copiado de internal/daemon/protocol.go (estruturas Go com suas tags 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": "..." }
Regras de validação para espelhar o lado do cliente
- tmux session names não deve conter
/,\ou:. - nomes de projetos (para
POST /v1/projects) deve ser um único segmento de caminho não oculto. - observa caminhos de arquivo deve ser relativo ao projeto, não
.., terminando em.md. - os corpos da solicitação são limitados a 64 KiB.
Exemplos
# 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
O cliente de referência Go em
internal/daemon/client.go
é o consumidor canônico e um mapa útil do método → endpoint.
Ajude a melhorar o ccmux
Encontrou um bug, uma explicação confusa ou uma tradução que pode melhorar? Toda contribuição é bem-vinda. Abra uma issue ou envie um pull request.
Detectou um erro ou algo desatualizado? Edite esta página em GitHub.