Skip to main content

ttd

Terminal-first time tracking, reporting, and invoicing for solo developers.

Track time where you already live — the terminal. Log work in natural language, run timers, roll everything up into reports, and send client-ready PDF invoices, all without leaving your shell.

$ ttd log "today 8am to 5pm" -p api-rewrite --note "auth endpoints"
✓ Logged 9:00 on Tue Jun 9 (8:00am–5:00pm)

$ ttd invoice create --client acme-corp --month 2026-06 --pdf
✓ Created invoice 2026-001
✓ Wrote ~/Documents/invoices/2026-001-acme-corp.pdf

Full documentation: syn54x.github.io/ttd — guides, the TUI tour, time-expression cookbook, and the complete CLI and configuration reference.

Install

uv tool install ttd-ledger     # or: pipx install ttd-ledger
ttd --install-completion       # shell completion

The three ways in

  1. CLI — every feature is a command. Scriptable, pipeable.
  2. -i forms — add -i to any mutating command for an interactive form; any flags you already passed pre-fill it.
  3. TUI — run bare ttd for the full-screen app: live timer, activity heatmap, day-by-day timesheet with as-you-type time parsing, reports, and invoice management. Keys: 1–5 screens, s start/stop, l quick-log.

Try it with demo data: ttd db seed-demo && ttd (add --reset to wipe and reseed)

Logging time

Natural language, retrospective or live:

ttd log "today 8am to 5pm"            # interval
ttd log "yesterday 9-11:30"           # am/pm inferred from your workday window
ttd log "monday 1pm for 3 hours"      # most recent monday
ttd log "2h this morning"             # duration-only entry
ttd log "6/3 10am-1pm"                # explicit date

ttd start api-rewrite                 # live timer
ttd stop --at 5pm
ttd status

Multiple entries per project per day are normal; reports and invoices roll them up into hours per day. Ambiguous times ("6 to 8") are rejected with the candidate readings instead of silently guessing.

Clients, projects, rates

ttd client add "Acme Corp" --rate 150 --email billing@acme.test
ttd project add "API Rewrite" --client acme-corp        # inherits $150/h
ttd project add "Mobile App" --client acme-corp --rate 175

Rates resolve project → client → [business].default_hourly_rate.

Reports

ttd report day                  # today, entry by entry
ttd report week --by project    # sparklines + billable value
ttd report month 2026-05 --client acme-corp
ttd report range --from 2026-01-01 --to 2026-03-31 --by client

Invoicing

ttd invoice create --client acme-corp --month 2026-05 --pdf --md
ttd invoice list
ttd invoice mark 2026-001 sent     # then: paid, or void

Billable, uninvoiced entries roll up to one line per project per day, rounded per your [billing] policy. Invoiced entries lock; voiding an invoice releases them (numbers are never reused).

Export / import

CSV, JSON, XLSX, and Apple Numbers — both directions:

ttd export hours.xlsx                  # format inferred from extension
ttd export backup.json                 # JSON carries client/project metadata
ttd import hours.csv --dry-run         # preview: new/update/skip/errors
ttd import hours.csv --on-conflict update --create-missing

Imports match by entry id, then by content; invoiced entries are never touched.

Configuration

Layered TOML, nearest-wins (like ruff): CLI flags → TTD_* env → .ttd.toml (walks up from cwd) → ~/.config/ttd/config.toml → defaults.

# ~/.config/ttd/config.toml
[user]
name = "Taylor"
email = "taylor@example.com"

[business]
currency = "USD"
default_hourly_rate = 150

[billing]
rounding = "nearest"      # nearest | up | none
increment_minutes = 15    # applied per project-day

[invoice]
number_format = "{year}-{seq:03d}"
payment_terms_days = 30
output_dir = "~/Documents/invoices"

[parsing]
workday_start = 7         # am/pm inference window
workday_end = 19

Pin a repo to a project so bare ttd start / ttd log "2h" just work:

cd ~/code/acme-api
ttd config set defaults.client acme-corp --local
ttd config set defaults.project api-rewrite --local

ttd config list --origin shows where every value came from.

Development

uv sync
just test     # pytest
just lint     # ruff + ty
just tui      # textual dev mode

Stack: Python 3.13, Ferro-ORM over SQLite, Cyclopts, Rich, Textual, questionary, fpdf2, openpyxl, numbers-parser.

Metadata

Release files for ttd-ledger 0.9.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ttd-ledger 0.9.0
File Size Uploaded
ttd_ledger-0.9.0.tar.gz 94.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ttd-ledger 0.9.0
File Interpreter ABI Platform
ttd_ledger-0.9.0-py3-none-any.whl Python 3 none any Details

Total release size: 229.1 kB

Release files / ttd_ledger-0.9.0.tar.gz

Download URL ttd_ledger-0.9.0.tar.gz
Size 94.0 kB
Tags Source
SHA-256 checksum
How to use checksums
8d178f7e104983e9e4525851c4ff69efd914dfa747adc0a72e5b4855f1fe65ab
BLAKE2b-256 checksum
How to use checksums
0ab4ce75be2e6ad2d6be1b14a9e4845ed266af813681724a993cd0459bf7227e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 7, 2026.

Transparency log

Release files / ttd_ledger-0.9.0-py3-none-any.whl

Download URL ttd_ledger-0.9.0-py3-none-any.whl
Size 135.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
53c57be9286b4b9e1f4246620a61fa4bd37013b76a1116c379407068cba8ea5b
BLAKE2b-256 checksum
How to use checksums
3174eb5c9591f76fb923aaf45a4eb5126f998b526f5b00f14138b0ab521c25c6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 7, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.9.0 This release

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page