TALLYGRAPHUNIFIED SPEND CONSOLE

Correr en contenedores

Corre el colector en Docker, en sandboxes de desarrollo como Daytona, devcontainers o Codespaces, y en Kubernetes.

tallygraph collect lee el historial del agente dentro de $HOME. En un contenedor tiene que correr donde vive ese historial: en el mismo contenedor que el agente, o en otro con la carpeta del historial montada. No hace falta iniciar sesión: el token llega por una variable de entorno.

Requisitos

  • Node.js 22.13 o más nuevo en la imagen. La imagen oficial node:22-slim sirve.
  • Un token de escritura de la página de Conectores, pasado como TALLYGRAPH_WRITE_TOKEN.
  • Salida HTTPS a www.tallygraph.dev, o a la URL que pongas en TALLYGRAPH_ENDPOINT.
  • El historial del agente dentro del contenedor, en su ruta por defecto bajo HOME (para Claude Code, ~/.claude/projects). Todas las rutas están en Inicio rápido.

No uses tallygraph service install en un contenedor. Registra la tarea en launchd, systemd o Task Scheduler, y un contenedor normalmente no tiene ninguno. Usa collect --watch o una tarea programada.

Docker

Primero revisa qué encuentra

Monta el historial en solo lectura y haz una corrida en seco. Lee y valida, pero no manda nada. No necesita token.

terminal
docker run --rm -u node -e HOME=/home/node \
  -v /ruta/a/.claude/projects:/home/node/.claude/projects:ro \
  node:22-slim npx --yes tallygraph collect --dry-run
[tallygraph] discovered: claude (cost basis: estimate)
[tallygraph] file: discover — 1 valid, 0 invalid, 0.4 KiB
[tallygraph] dry-run: nothing sent, spool untouched

Arma una imagen del colector

Con el CLI instalado en la imagen no se descarga en cada arranque. La carpeta .tallygraph se crea con el usuario node para que un volumen montado ahí se pueda escribir.

Dockerfile
FROM node:22-slim
ARG TALLYGRAPH_VERSION=0.5.0
RUN npm i -g tallygraph@${TALLYGRAPH_VERSION}
USER node
RUN mkdir -p /home/node/.tallygraph
ENTRYPOINT ["tallygraph"]
CMD ["collect"]
terminal
docker build -t tallygraph-collector .

export TALLYGRAPH_WRITE_TOKEN=tg_xxxxxxxx
docker run --rm -e TALLYGRAPH_WRITE_TOKEN \
  -v /ruta/a/.claude/projects:/home/node/.claude/projects:ro \
  -v tallygraph-state:/home/node/.tallygraph \
  tallygraph-collector collect

El volumen tallygraph-state guarda el spool entre corridas, así que lo que no se pudo mandar sale en la siguiente. Para dejarlo corriendo, pasa collect --watch en lugar de collect. Se detiene limpio con docker stop.

Sandboxes de desarrollo: Daytona, devcontainers, Codespaces

Aquí el agente corre dentro del sandbox, así que su historial ya está en el home del sandbox. Corre el colector ahí mismo.

  • Instálalo en la imagen o en el script de setup con npm i -g tallygraph, o llámalo con npx --yes tallygraph.
  • Pon TALLYGRAPH_WRITE_TOKEN como secreto o variable de entorno del sandbox. No lo subas al repo.
  • Para tareas cortas, corre tallygraph collect al terminar la tarea.
  • Para sesiones largas, arranca tallygraph collect --watch --interval 60 en otra terminal o en segundo plano.
  • Si el sandbox puede reiniciar con el disco limpio, guarda el historial del agente y TALLYGRAPH_STATE_DIR en un volumen persistente. Si no, el uso que no se mandó antes del reinicio se pierde junto con el historial.
terminal
npm i -g tallygraph
tallygraph collect --dry-run   # no manda nada
tallygraph collect --watch --interval 60

Correr collect varias veces no duplica nada. La API descarta los eventos que ya tiene, por external_id.

Kubernetes

Sidecar en el pod del agente

Agrega el colector como segundo contenedor en el pod donde corre el agente. Los dos montan el mismo volumen en la ruta del historial. El colector lo monta en solo lectura y guarda su spool en otro volumen. Arma la imagen con el Dockerfile de arriba y súbela a tu registry. Primero crea el Secret:

terminal
kubectl create secret generic tallygraph --from-literal=write-token="$TALLYGRAPH_WRITE_TOKEN"
pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: agent
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    fsGroup: 1000
  volumes:
    - name: claude-history
      emptyDir: {}
    - name: tallygraph-state
      emptyDir: {}
  containers:
    - name: agent
      image: your-agent-image
      env:
        - name: HOME
          value: /home/node
      volumeMounts:
        - name: claude-history
          mountPath: /home/node/.claude/projects
    - name: tallygraph
      image: registry.example.com/tallygraph-collector
      args: ["collect", "--watch", "--interval", "300"]
      env:
        - name: HOME
          value: /home/node
        - name: TALLYGRAPH_STATE_DIR
          value: /var/lib/tallygraph
        - name: TALLYGRAPH_WRITE_TOKEN
          valueFrom:
            secretKeyRef:
              name: tallygraph
              key: write-token
      volumeMounts:
        - name: claude-history
          mountPath: /home/node/.claude/projects
          readOnly: true
        - name: tallygraph-state
          mountPath: /var/lib/tallygraph
  • Cambia /home/node por el HOME del agente. El colector busca en las rutas bajo su propio HOME, así que los dos tienen que coincidir con el montaje.
  • Para Codex, monta ~/.codex/sessions de la misma forma.
  • emptyDir dura lo que dura el pod. Usa un PersistentVolumeClaim para los dos volúmenes si el pod se reemplaza antes de que el colector alcance a mandar todo.
  • Si el agente corre como Job, no le pongas un sidecar que se queda corriendo, porque el Job nunca termina. Mejor corre tallygraph collect al final del comando del agente.

CronJob

Solo si el historial está en un PersistentVolumeClaim que el job pueda montar. Un volumen ReadWriteOnce solo lo pueden montar pods en el mismo nodo que el agente.

cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: tallygraph-collect
spec:
  schedule: "*/15 * * * *"
  concurrencyPolicy: Forbid
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: Never
          securityContext:
            runAsNonRoot: true
            runAsUser: 1000
            fsGroup: 1000
          volumes:
            - name: claude-history
              persistentVolumeClaim:
                claimName: agent-claude-history
                readOnly: true
            - name: tallygraph-state
              persistentVolumeClaim:
                claimName: tallygraph-state
          containers:
            - name: tallygraph
              image: registry.example.com/tallygraph-collector
              args: ["collect"]
              env:
                - name: HOME
                  value: /home/node
                - name: TALLYGRAPH_STATE_DIR
                  value: /var/lib/tallygraph
                - name: TALLYGRAPH_WRITE_TOKEN
                  valueFrom:
                    secretKeyRef:
                      name: tallygraph
                      key: write-token
              volumeMounts:
                - name: claude-history
                  mountPath: /home/node/.claude/projects
                  readOnly: true
                - name: tallygraph-state
                  mountPath: /var/lib/tallygraph

Problemas comunes

VesQué significaQué hacer
no local sources foundCódigo de salida 2. No hay historial en las rutas por defecto bajo HOME: el volumen no está montado, está en otra ruta, o HOME apunta a otro lado.Revisa HOME y la ruta del montaje, y corre collect --dry-run hasta que diga discovered.
missing --token (or TALLYGRAPH_WRITE_TOKEN env)El token no llegó al contenedor.Revisa que la variable exista donde corres docker, o el nombre y la key del Secret en secretKeyRef.
cannot open spool at …Código de salida 3. El usuario del contenedor no puede escribir en la carpeta de estado.Monta ahí un volumen con permiso de escritura. En Kubernetes, pon fsGroup.
watch: tick N produced no eventsCon --watch el proceso sigue vivo y reintenta en el siguiente ciclo.Lo mismo que para no local sources found.
Probado con Docker y el CLI publicado. Todavía no se probó en Daytona ni en un cluster real de Kubernetes: los manifiestos de arriba solo se validaron contra el schema de Kubernetes.