Repository navigation
Architecture
toard collects AI coding usage on a user's machine, authenticates it at the server, calculates cost there, and stores the result for dashboards. The complete, versioned design contract is docs/ARCHITECTURE.md; this page is its map.
| Location | Responsibility |
|---|---|
apps/web |
Next.js application, API routes, dashboard, Auth.js, and server-side attribution and pricing. |
packages/core |
Dependency-free domain types and the StorageBackend contract. |
packages/ingest |
OTLP/JSON parsing, provider identification, normalization, and deduplication. |
packages/pricing |
LiteLLM pricing synchronization and token-to-cost calculation. |
packages/storage-postgres |
Default Postgres StorageBackend implementation. |
packages/storage-clickhouse |
Optional ClickHouse analytics backend implementation. |
shim/rust |
Rust wrapper, local session-file collector, scheduled collection, and self-update. |
migrations and clickhouse/init
|
Postgres migrations and ClickHouse initialization schema. |
The default collection path is local session-file pull. The shim parses supported tool session files, normalizes them to UsageEvent[], and posts them to /api/v1/events with an ingest token. The server uses that token to set the user identity and calculates the cost itself; it does not trust a client-provided user ID or cost.
flowchart LR
tool["AI coding tool"] --> files["Local session files"]
files --> shim["toard-shim"]
shim -->|"UsageEvent[] + Bearer token"| ingest["/api/v1/events"]
ingest --> pricing["Server attribution and pricing"]
pricing --> storage["StorageBackend"]
storage --> dashboard["Dashboard"]
The shim supports Claude, Codex, Gemini, and Qwen session-file collection. Its default scheduled collector handles use that does not pass through the wrapper. Experimental OTLP is separately gated and is not required for the normal pull flow. Read the shim README for supported sources, cursor behavior, and the collection triggers.
Prompt and response content follows a separate opt-in route, /api/v1/prompts, with separate cursors. It is not part of default usage ingestion.
Postgres is the default single-store deployment. In ClickHouse mode, events and analytics move to ClickHouse while users, teams, authentication, and tokens remain in Postgres. StorageBackend keeps the application-level storage contract consistent across those modes.
ClickHouse rollup reads are deliberately off by default. They require operational validation before enablement; see Operating toard and the exact rollup runbook.
-
UsageEventis a mirrored contract in TypeScript core and the Rust shim. Update both when it changes. - Data model and collection-contract changes are difficult to reverse. Discuss them first and update the relevant architecture decision record.
- Keep migrations forward-only and use expand-then-contract sequencing for rolling deployments.
- Event timestamps are stored in UTC; dashboard presentation uses the viewer timezone while organization timezone still governs materialized daily boundaries.