Referencia de la API HTTP

El demonio ccmux expone un HTTP y un JSON API para enumerar sesiones, leer datos de proyectos y crear clientes remotos. Esta referencia cubre la configuración de la conexión, el modelo de confianza, los puntos finales y los tipos de respuesta.

Las rutas y tipos se mantienen en el ccmux source. Es posible que las versiones anteriores del demonio no admitan todos los puntos finales documentados aquí; verifique el punto final de salud para la versión en ejecución.

Transporte y accesibilidad

Las mismas rutas se atienden en dos transportes:

Transporte Dirección Predeterminado
Conector Unix local ~/.local/state/ccmux/ccmuxd.sock (modo 0600) siempre encendido
tailnet HTTP http://TAILNET-IPv4:PORT (puerto predeterminado 7474) desactivado

Para acceder a un demonio desde otro dispositivo, en el demonio anfitrión: establecer daemon.listen_tailnet = true (opcionalmente daemon.tailnet_port) en ~/.config/ccmux/config.toml y reinicie ccmuxd. La dirección de enlace es la del host. tailscale ip -4. Si Tailscale no se está ejecutando, el oyente de tailnet no se inicia silenciosamente (solo se sirve el socket Unix).

Modelo de autenticación y confianza: lea esto primero

No existe autenticación a nivel de aplicación en el HTTP API. Sin token de portador, sin clave API, sin firma por solicitud, sin lista de IP permitidas. El límite de confianza es tu tailnet Tailscale: cualquier host que pueda alcanzar el demonio 100.x.x.x:7474 puede llamar a todos los puntos finales: listar/crear/eliminar/renombrar/enviar claves, adjuntar a una terminal interactiva completay leer notas/conversaciones/uso.

Asegure esto con ACL Tailscale, no autenticación a nivel de aplicación. Trate todo lo que pueda enrutarse a la IP de la tailnet del demonio como de plena confianza.

Dos puntos finales se validan por solicitud y solo para iniciar la confianza del dispositivo + inserción, no para proteger el API:

Forma de error

Los errores son texto sin formato (text/plain): un mensaje de una sola línea, no un sobre JSON. Compruebe el código de estadoy leer el cuerpo como una cadena para el mensaje humano. Las respuestas exitosas JSON son application/json.

Código Significado
200 OK (cuerpo JSON)
204 OK, sin cuerpo (eliminar/enviar claves/registro de dispositivo)
400 entrada incorrecta/fallo de validación
401 token de emparejamiento no válido o caducado (/v1/pair solamente)
404 no encontrado — o este demonio es anterior al punto final
405 método HTTP incorrecto
500 servidor / tmux / falla del andamio
503 función no disponible (p. ej. /v1/pair-token con listen_tailnet desactivado)

El API evoluciona agregando Campos JSON (todos los campos opcionales usan omitempty) y agregando rutas. Tratar un 404 en un punto final completo como "este demonio es más antiguo que esa característica" y se degrada suavemente.

Resumen de puntos finales

Método Ruta Respuesta
GET /v1/health HealthInfo
GET /v1/peers []PeerInfo
GET /v1/sessions []SessionState
POST /v1/sessions SessionState (crear o adjuntar)
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 interactivo)
GET /v1/projects []ProjectInfo
POST /v1/projects NewProjectResponse
GET /v1/conversations []Conversation
GET /v1/usage?window=… AgentUsage
GET /v1/notes?project=…[&file=…] []NoteEntry o NoteContent
GET /v1/notes/search?project=…&q=… []SearchHit
GET /v1/events Flujo SSE de SessionEvent
POST /v1/pair-token (solo Unix) PairTokenResponse
POST /v1/pair PairResponse
POST /v1/devices 204
POST /v1/devices/test 204

(Donde una ruta muestra NAME es el segmento de ruta del nombre de sesión tmux, es decir. /v1/sessions/{name}/kill.)

Detalles del punto final

Sesiones

Terminal interactivo — GET /v1/sessions/NAME/attach (WebSocket)

Actualice a un WebSocket conectado a un servidor real tmux attach-session en un PTY: un verdadero terminal interactivo (salida en vivo, entrada, cambio de tamaño) sin ssh/mosh. Usos coder/websocket encuadre.

El tamaño de PTY inicial es 80x24 hasta el primer cambio de tamaño. El servidor hace ping cada 25 segundos con una fecha límite de 10 segundos: responda a los pings o espere un desmontaje. InsecureSkipVerify está configurado (no Origin comprobar). Cerrar solo el enchufe se separa; la sesión tmux continúa ejecutándose. Prefiero esto a las encuestas /preview para una vista interactiva.

Proyectos

Conversaciones, uso, notas

Actualizaciones en vivo — GET /v1/events (SSE)

text/event-stream. cada uno data: el marco es un JSON SessionEvent; kind es created / killed / state_change / needs_input. Latidos del corazón: : connected en abierto, : ping cada 20 segundos (líneas de comentarios que comienzan con : son ignorables). Un event: drops / data: N frame significa que te perdiste N eventos y debes volver a buscarlos /v1/sessions para resincronizar.

Salud y descubrimiento

Emparejamiento y push (móvil)

Flujo opcional para push nativo (APN/iOS, FCM/Android) e instalación de una clave SSH para que el dispositivo pueda ssh/mosh adjuntar. No necesario utilizar los puntos finales de lectura/actuación a través de la tailnet.

Activa dos transiciones: una sesión entrando needs_inputy active → idle (“agente finalizado”). La identificación de la sesión del push es local/SESSIONNAME. Los APN y FCM son desactivado de forma predeterminada y necesita configuración del lado del servidor; El enrutamiento FCM existe pero la entrega de Android aún no está conectada.

Tipos

Copiado de internal/daemon/protocol.go (estructuras Go con sus etiquetas 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": "..." }

Reglas de validación para reflejar el lado del cliente

Ejemplos

# 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

El cliente de referencia Go en internal/daemon/client.go es el consumidor canónico y un mapa útil de método → punto final.

Ayuda a mejorar ccmux

¿Encontraste un error, una guía poco clara o una traducción mejorable? Todas las aportaciones son bienvenidas. Abre una incidencia o envía una solicitud de incorporación de cambios.


¿Detectó un error o algo desactualizado? Editar esta página en GitHub.