Référence de l’API HTTP
Le démon ccmux expose un HTTP et un JSON API pour répertorier les sessions, lire les données du projet et créer des clients distants. Cette référence couvre la configuration de la connexion, le modèle de confiance, les points de terminaison et les types de réponse.
Les routes et les types sont conservés dans le ccmux source. Les anciennes versions du démon peuvent ne pas prendre en charge tous les points de terminaison documentés ici ; vérifiez le point de terminaison d’intégrité pour la version en cours d’exécution.
Transport et accessibilité
Les mêmes itinéraires sont desservis sur deux transports :
| Transports | Adresse | Par défaut |
|---|---|---|
| Socket Unix local | ~/.local/state/ccmux/ccmuxd.sock (mode 0600) |
toujours activé |
| tailnet HTTP | http://TAILNET-IPv4:PORT (port par défaut 7474) |
désactivé |
Pour accéder à un démon depuis un autre appareil, sur le démon hôte : défini
daemon.listen_tailnet = true (en option daemon.tailnet_port) dans
~/.config/ccmux/config.toml et redémarrez ccmuxd. L'adresse de liaison est celle de l'hôte tailscale ip -4. Si Tailscale n'est pas en cours d'exécution, l'écouteur tailnet ne démarre pas silencieusement (seule le socket Unix est servi).
- Tous les chemins sont sous
/v1/. Il n'y a pas/v2. - Les corps de requête/réponse sont
application/json— sauf/v1/events(text/event-stream) et le point de terminaison de connexion (une mise à niveau WebSocket). - Pas de TLS. Le tunnel WireGuard de Tailscale constitue la limite entre chiffrement et identité ; l'écouteur HTTP est simplement HTTP lié à l'adresse IP du tailnet.
- Les corps de requête sont limités à 64 Ko.
Modèle d'authentification et de confiance — lisez ceci en premier
Il n'y a pas d'authentification au niveau de l'application sur le HTTP API. Pas de jeton de porteur, pas de clé API, pas de signature par demande, pas de liste blanche IP. La limite de confiance est votre filet arrière Tailscale: tout hôte pouvant atteindre le démon
100.x.x.x:7474 peut appeler chaque point de terminaison - lister / créer / tuer / renommer / envoyer des clés, attacher à un terminal interactif completet lire des notes/conversations/utilisation.
Sécurisez-le avec Listes de contrôle d'accès Tailscale, pas d'authentification au niveau de l'application. Traitez tout ce qui peut être acheminé vers l’adresse IP tailnet du démon comme étant entièrement fiable.
Deux points de terminaison sont validés par requête, et uniquement pour amorcer la confiance + le push du périphérique — et non pour protéger le API :
POST /v1/pairnécessite un jeton d'association valide à usage unique plus une clé publique SSH analysable. L'utiliser installe cette clé dans le système de l'hôte.~/.ssh/authorized_keys.POST /v1/pair-tokenest Socket Unix uniquement (jamais sur le tailnet) et renvoie en outre503sauf silisten_tailnetest activé.
Forme d'erreur
Les erreurs sont texte brut (text/plain) : un message sur une seule ligne, pas une enveloppe JSON. Vérifiez le code d'état, et lisez le corps comme une chaîne pour le message humain. Les réponses JSON réussies sont application/json.
| Code | Signification |
|---|---|
200 |
OK (corps JSON) |
204 |
OK, pas de corps (kill / send-keys / registre de périphérique) |
400 |
mauvaise entrée/échec de validation |
401 |
jeton d'association invalide ou expiré (/v1/pair uniquement) |
404 |
introuvable — ou ce démon est antérieur au point de terminaison |
405 |
mauvaise méthode HTTP |
500 |
serveur / tmux / panne d'échafaudage |
503 |
fonctionnalité indisponible (par ex. /v1/pair-token avec listen_tailnet désactivé) |
Le API évolue de ajout Champs JSON (tous les champs facultatifs utilisent omitempty) et ajout itinéraires. Traiter un 404 sur l'ensemble d'un point de terminaison car « ce démon est plus ancien que cette fonctionnalité » et se dégrade progressivement.
Résumé du point de terminaison
| Méthode | Chemin | Réponse |
|---|---|---|
GET |
/v1/health |
HealthInfo |
GET |
/v1/peers |
[]PeerInfo |
GET |
/v1/sessions |
[]SessionState |
POST |
/v1/sessions |
SessionState (créer ou joindre) |
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 interactif) |
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 |
Flux SSE de SessionEvent |
POST |
/v1/pair-token (Unix uniquement) |
PairTokenResponse |
POST |
/v1/pair |
PairResponse |
POST |
/v1/devices |
204 |
POST |
/v1/devices/test |
204 |
(Là où un chemin indique NAME il s'agit du segment de chemin du nom de session tmux, c'est-à-dire /v1/sessions/{name}/kill.)
Détails du point de terminaison
Sessions
GET /v1/sessions→[]SessionState. Chaque session gérée par ce démon, avec des informations dérivées du démonstate(active/idle/needs_input/error/unknown).POST /v1/sessions(NewSessionRequest,projectrequis) →SessionState. Créez ou attachez une session d'agent liée au projet (idempotent sur le nom tmux).pathest par défautPROJECTS_ROOT/PROJECTsur l'hôte démon.POST /v1/sessions/bare(NewBareSessionRequest) →NewBareSessionResponse. Une session shell uniquement (pas de projet, pas d'échafaudage).POST /v1/sessions/NAME/kill→204. Émet unkilledÉvénement SSE.POST /v1/sessions/NAME/rename(RenameRequest) →SessionState.NAMEest le nom actuel ; le corps porte le nouveau.POST /v1/sessions/NAME/send-keys(SendKeysRequest) →204. Passé partmux send-keys(par exemple, tapez une réponse +\n).GET /v1/sessions/NAME/preview?lines=N→PreviewResponse. N dernières lignes du volet actif sous forme de texte brut (ANSI supprimé).Nest1..200, par défaut24. Un aperçu léger sans ouvrir la prise de fixation.
Borne interactive — GET /v1/sessions/NAME/attach (WebSocket)
Mise à niveau vers un WebSocket ponté vers un véritable tmux attach-session dans un PTY : une véritable borne interactive (sortie live, entrée, redimensionnement) sans chut/mosh. Utilisations coder/websocket encadrement.
- client → serveur, trame binaire : octets stdin bruts (frappes de touches).
- client → serveur, cadre de texte : JSON
{"cols":N,"rows":N}à redimensionner. - serveur → client, trame binaire : octets de sortie PTY bruts.
La taille initiale du PTY est 80x24 jusqu'au premier redimensionnement. Le serveur envoie un ping toutes les 25 secondes avec un délai de 10 secondes – répondez aux pongs ou attendez-vous à un démontage.
InsecureSkipVerify est défini (non Origin vérification). Fermeture de la prise uniquement
se détache; la session tmux continue de s'exécuter. Préférez ceci aux sondages
/preview pour une vue interactive.
Projets
GET /v1/projects→[]ProjectInfo. Projets découverts sous la racine des projets du démon, étiquetés avec son nom d'hôte.POST /v1/projects(NewProjectRequest,namerequis) →NewProjectResponse. Crée un nouveau projet (répertoire uniquement — nonCLAUDE.md/git) et démarre une session d'agent.namedoit être un seul segment de chemin non masqué (non/,\, sans interligne.).
Conversations, utilisation, notes
GET /v1/conversations→[]Conversation. Relevés de notes des anciens agents, les plus récents en premier ;idest le propre UUID de l'agent (son--resume).GET /v1/usage?window=DURATION→AgentUsage. Jeton par agent + coût sur une fenêtre glissante (durée Go comme2h,24h; par défaut5h).GET /v1/notes?project=NAME→[]NoteEntry; avec&file=REL→NoteContent.filedoit être un parent du projet.mdchemin, non...GET /v1/notes/search?project=NAME&q=QUERY→[]SearchHit. Endos Ripgrep. (Démons plus anciens404— traiter comme « recherche indisponible ».)
Mises à jour en direct — GET /v1/events (ESS)
text/event-stream. Chaque data: le cadre est un JSON SessionEvent; kind est
created / killed / state_change / needs_input. Pulsations cardiaques: : connected à l'ouverture, : ping toutes les 20 secondes (lignes de commentaires commençant par : sont ignorés). Un event: drops / data: N le cadre signifie que vous avez manqué N événements et que vous devez récupérer à nouveau /v1/sessions pour resynchroniser.
Santé et découverte
GET /v1/health→HealthInfo. Vivacité + enquête d'identité.GET /v1/peers→[]PeerInfo. Chaque homologue tailnet + si chacun exécute ccmuxd (renvoie[], non500, lorsque Tailscale est absent).
Couplage et push (mobile)
Flux facultatif pour le push natif (APN/iOS, FCM/Android) et installation d'une clé SSH afin que l'appareil puisse ssh/mosh joindre. Non requis pour utiliser les points de terminaison de lecture/action sur le tailnet.
POST /v1/pair-token(socket Unix uniquement) →PairTokenResponse. Créez un jeton unique (hexadécimal 128 bits, à usage unique, TTL de 5 minutes) + unccmux://pair?…lien profond.503silisten_tailnetest désactivé.POST /v1/pair(PairRequest) →PairResponse. Échanger un jeton : installez la clé publique SSH de l'appareil, enregistrez éventuellement un jeton push en ligne.401sur un jeton invalide/expiré. Accessible sur le tailnet.POST /v1/devices(RegisterDeviceRequest) →204. Enregistrez/actualisez un jeton push sur un hôte déjà couplé. Appareil identifié par son SSHpublic_key(stocké uniquement sous forme de hachage SHA-256).providerestapns(par défaut) oufcm; Besoins en APNenv=development/production, FCM doit être videenv.POST /v1/devices/test({"public_key":"…"}) →204. Vérification poussée vers l'appareil pour cette clé.
Pousse le feu sur deux transitions : une session entrante needs_input, et
active → idle (« agent terminé »). L'identifiant de session du push est
local/SESSIONNAME. Les APN et FCM sont désactivé par défaut et nécessite une configuration côté serveur ; Le routage FCM existe mais la livraison Android n'est pas encore câblée.
Types
Copié depuis internal/daemon/protocol.go (structures Go avec leurs balises 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": "..." }
Règles de validation pour refléter côté client
- tmux session names ne doit pas contenir
/,\, ou:. - noms de projets (pour
POST /v1/projects) doit être un seul segment de chemin non masqué. - note les chemins des fichiers doit être relatif au projet, non
.., se terminant par.md. - les corps de requête sont limités à 64 Ko.
Exemples
# 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
Le client de référence Go dans
internal/daemon/client.go
est le consommateur canonique et une carte utile méthode → point de terminaison.
Contribuez à améliorer ccmux
Vous avez trouvé un bug, une explication peu claire ou une traduction à améliorer ? Toutes les contributions sont bienvenues. Ouvrez une issue ou proposez une pull request.
Vous avez repéré une erreur ou quelque chose de obsolète ? Modifier cette page sur GitHub.