AI-first time tracker — "if you can clone it, you can bill it."
This repository is the open-source surface of Dira: the command-line client, the capture daemon, and the wire contract. It is published under the Apache License 2.0.
Dira captures how you actually spend time supervising AI coding agents — human-engaged minutes, agent wall-clock, token usage, and the git artifacts behind the work — as policy-free, metadata-only proofs. Billing is resolved later, in the cloud, against effective-dated policy.
- Capture is policy-free & metadata-only. Durations, counts, SHAs, session ids, tool names — never prompt text, file contents, or diffs. Open so you can audit exactly that.
- Human time is de-duplicated across concurrent sessions: one person supervising three agents is still one human-minute per minute. Agent wall-clock sums freely.
- Offline-first. Everything works with no network and no account; the cloud is an optional sync target.
/contract Rust source-of-truth wire schema (serde + schemars) → attestation.schema.json
/cli
/core capture engine: event model, accounting FSM, store, project resolver, report
/dirad resident daemon (tokio): ingress (loopback HTTP + UDS), accounting, store
/dira thin CLI client over the daemon's Unix domain socket
/sources per-harness hook normalization (claude_code, …)
/docs docs/install.md (installer reference), docs/zavet.md (knowledge module)
install.sh curl | sh installer for dira + dirad (see docs/install.md)
mise.toml toolchain pins (rust, just)
justfile task runner
The Dira cloud — the hosted verify/billing/policy/dashboard service — is a proprietary service in a separate repository and is not part of this project.
The zavet plugin — the repo-local
knowledge layer (decision records, guard hooks, commit-trailer conventions) — is its own
product in its own repository. dira ships the optional integration only: repos that carry
.zavet/ get their decisions, trailers, and guard events captured and correlated with
session time (dira zavet why D-0042 = the decision and what it cost). See
docs/zavet.md.
curl -fsSL https://dirahq.sh/install | shInstalls dira + dirad into ~/.local/bin (override with DIRA_BIN_DIR). Targets:
macOS (universal — Apple Silicon and Intel, one download) and Linux x86_64/arm64 (static
musl — works on Alpine and old glibc alike); Windows via WSL2.
dira init # wire Claude Code hooks (also: codex, gemini, cursor, opencode, grok)
dira daemon start # start the resident tracker daemon
dira status # today's summary — engaged, agent, compute, unbilledRun dira daemon install once so dirad survives reboots (launchd/systemd-user).
Stay current with dira update — sha256-verified, atomic, restarts the daemon for you.
See docs/install.md for every flag/env var, air-gapped installs, and
troubleshooting.
The contributor path — build dira yourself instead of downloading a release.
mise install # rust + just
just test # unit + property tests for the accounting invariants
just install # build release binaries, symlink onto PATH, restart daemonjust install symlinks target/release/{dira,dirad} onto $DIRA_BIN_DIR (default
~/.local/bin) for a fast dogfood loop — it is not a real install. dira update
deliberately refuses to touch a just install dev symlink (see
docs/install.md); re-run just install instead to pick up a new build.
dira start --label meeting # a manual session (several may run at once)
dira stop --all
dira log 45 --note "review" # retroactive entry (bare number = minutes)
dira report --weekdira init writes Claude Code command hooks into .claude/settings.json; each event
runs dira hook claude, which forwards the payload to the daemon over the socket. The hot
path only does a non-blocking enqueue, so the agent loop never waits on us.
Other harnesses are wired the same way — dira init <harness>:
| Harness | dira init … |
How it's wired |
|---|---|---|
| Claude Code | dira init (default) |
command hooks → .claude/settings.json |
| Codex | dira init codex |
prints ~/.codex/config.toml [[hooks.…]] snippet to paste |
| Gemini CLI | dira init gemini |
command hooks → ~/.gemini/settings.json |
| Cursor | dira init cursor |
command hooks → ~/.cursor/hooks.json |
| OpenCode | dira init opencode |
forwarder plugin → ~/.config/opencode/plugin/dira.js (HTTP) |
| Grok Build | dira init grok |
command hooks → ~/.grok/hooks/dira.json |
The command-hook harnesses (Claude, Codex, Gemini, Cursor, Grok Build) all forward over the same
stdin→socket shim (dira hook <harness>); OpenCode has no command hooks, so it POSTs to the
daemon's loopback /hooks/opencode route instead. Each harness's own hook vocabulary is
normalized into Dira's shared event set in cli/sources.
The CLI/daemon point at the hosted cloud (https://app.dirahq.sh) out of the box, but
nothing is ever sent until you link the device — unlinked, dira is fully offline.
dira device link # enter the one-time code from the dashboard's Connections screenThe URL is ordinary layered config (defaults → config.toml → DIRA_* env, env wins),
so pointing a checkout at a local or self-hosted cloud is one line:
DIRA_CLOUD_URL=http://localhost:3000 dira device link --code LOCALDEV1 # per-invocation
dira config set cloud_url http://localhost:3000 # persistentThe wire schema is authored once in Rust (/contract) because the daemon is the producer.
just contract emits contract/attestation.schema.json and the deterministic signing
fixture contract/testdata/signing-vector.json. Both are drift-gated in CI; never hand-edit
them. The cloud consumes them by vendoring.
Contributions are accepted under the DCO (git commit -s) and licensed Apache-2.0. See
CONTRIBUTING.md. "Dira" and the Dira logo are trademarks of Dodi Smart
OOD; the Apache-2.0 license does not grant trademark rights.
- Rust is managed by
mise; run cargo viamise exec -- cargo …ifmiseisn't shell-activated. - The accounting invariants (no double-count, idle-trim) are property-tested against random
interleaved multi-session event streams — see
cli/core/src/accounting.rs.