Skip to main content

Terminal time tracker with JSON, SQLite, and PostgreSQL backends and github issues integration.

Project description

time-tracker

A terminal-based time tracking application built with Textual. Track time entries, tag them, filter by customer/project, and generate reports — all from the terminal.

Features

  • Full TUI interface with keyboard navigation
  • Start/stop live time tracking sessions
  • Create and edit entries with customer, project, description, and tags
  • Flexible grouping — choose which fields to group entries by (date, customer, project, description, ticket, tags)
  • Filter entries by date range and text fields, with recency-ranked suggestions
  • Pluggable storage backends: JSON, SQLite, PostgreSQL
  • Smart recency-ranked suggestions for fast re-selection
  • Configurable dark and light themes with system color-scheme detection

Installation

Requires Python 3.12+. Uses uv for dependency management.

# Install with uv
uv sync

# With PostgreSQL support
uv sync --extra postgres

# Run
uv run time-tracker

Usage

time-tracker [--backend {json|sqlite|postgres}] [--db PATH] [--postgres-url URL]
Flag Description Default
--backend Storage backend (json, sqlite, postgres) json
--db Path to JSON or SQLite database file ~/.local/share/time-tracker/entries.json
--postgres-url PostgreSQL connection URL (overrides config and keyring) postgresql://time-tracker@localhost:5432/time-tracker
--set-postgres-password Store the Postgres password in the system keyring

Keyboard shortcuts

Press ? in the app at any time to see the full keybinding reference. The most common ones:

Key Action
n New entry
s Start tracking (opens entry form, begins live session)
p Stop tracking (ends current session, saves to history)
e Edit selected entry or group (group edit preserves individual times)
c Continue — start a new session pre-filled from the selected entry or group
d Delete selected entry
o Open ticket URL in browser
t Toggle timeline mode (flat chronological view with gap/overlap markers)
f Filter entries (by date range, customer, project, description, tag, ticket)
g Open grouping dialog — choose which fields to group entries by
w / W Write (commit) current entry / all pending changes
u / U Undo current / all pending changes
T Toggle dark/light theme
? Show full keybinding help
q Quit

Filtering (f)

Opens a dialog with inputs for date range (From / To) and text fields (customer, project, description, tag, ticket). A suggestions panel on the right shows ranked completions for text fields — navigate with Alt+↑↓ and pick with . Typing also activates inline completion. Press Enter or Apply to apply, Clear to reset all filters, Escape to cancel. Active filters are shown in the mode line as Filter (N active).

Grouping (g)

Entries that share the same values across all selected fields are collapsed into a single group row (shown with / indicators and an entry count). Uncheck a field to ignore differences in that field when grouping — the column displays (~) in the group row to indicate mixed values. The subtitle bar shows the active grouping when it differs from the default (all fields).

When Date is unchecked, the day-separator headers are removed entirely and entries are grouped across all dates (timestamps show the full date+time). When Date is checked (default), entries are separated by date headers and show time-only within each day.

c on a group header starts a new session pre-filled with whichever fields are identical across all entries in the group; fields that vary between entries are left blank.

Data model

class Entry(BaseModel):
    start: datetime | None
    end: datetime | None
    customer: str | None
    project: str | None
    description: str | None
    tags: list[Tag] | None

class Tag(BaseModel):
    name: str

Entries are the core unit. A "current" entry represents an in-progress session and is displayed with a green dot in the main table. Tags are stored as a list of Tag objects.

Storage backends

All backends implement the abstract StorageBackend interface in src/time_tracker/backends/base.py.

JSON (default)

Single file at ~/.local/share/time-tracker/entries.json. Stores a list of completed entries plus a separate current entry. No dependencies beyond the standard library.

SQLite

Local SQLite database. Two tables: entries and current_entry. Tags stored as a JSON string column. Supports automatic schema migration.

PostgreSQL

Remote PostgreSQL database. Same logical schema as SQLite but uses TIMESTAMPTZ columns and stores tags as TEXT[]. Requires uv sync --extra postgres.

Authentication — the connection URL is stored in config without a password. The password is kept separately in the system keyring (Gnome Keyring, KWallet, macOS Keychain, Windows Credential Locker) and injected at connect time:

# Store the password once
uv run time-tracker --set-postgres-password

# Run — password is fetched from the keyring automatically
uv run time-tracker --backend postgres --postgres-url "postgresql://user@host:5432/db"

The URL is saved to config so --postgres-url only needs to be passed once. On subsequent runs just --backend postgres (or set backend in config) is enough.

If you pass a full URL including the password via --postgres-url, that takes precedence over the keyring and the config value.

A docker-compose.yml is included for local dev (no password needed):

docker compose up -d
uv run time-tracker --backend postgres

If startup fails with could not translate host name ... to address, the PostgreSQL hostname is not resolvable from your current DNS setup. The Postgres client connects directly and does not use http_proxy or https_proxy, so those variables do not help with this class of failure.

Configuration

Config is stored at ~/.config/time-tracker/config.json. The backend, path/URL, and UI preferences are persisted there so you don't need to pass flags every run.

Theme

Field Default Description
color_scheme "auto" Active mode: "dark", "light", or "auto" (detect from system)
dark_theme "textual-dark" Textual theme name to use in dark mode
light_theme "textual-light" Textual theme name to use in light mode

T toggles between dark and light and saves the choice. "auto" is only resolved at startup — once you press T, the preference becomes explicit.

Built-in Textual theme names include: textual-dark, textual-light, nord, gruvbox, dracula, catppuccin-mocha, catppuccin-latte, tokyo-night, monokai, solarized-light.

Project structure

src/time_tracker/
├── __init__.py          # CLI entry point (argument parsing, main())
├── app.py               # TimeTrackerApp: bindings, CRUD actions, init/compose
├── app_filter.py        # FilterMixin: _apply_filter, _hidden_cols, action_filter_entries
├── app_rows.py          # RowsMixin: _build_rows, _refresh_list, status/totals display
├── app_timeline.py      # TimelineMixin: snap, _move_start/end, align, join
├── models.py            # Pydantic models: Entry, Tag
├── pending.py           # PendingChanges: staged edits/deletes, commit/cancel logic
├── config.py            # Config load/save (~/.config/time-tracker/)
├── about_modal.py       # AboutModal
├── help_modal.py        # HelpModal (keybinding reference, shown with ?)
├── modals.py            # ConfirmModal, FilterModal, GroupingModal, BulkEditModal, SearchModal, CopyToBackendModal
├── time_tracker.tcss    # Textual CSS styles
├── backends/
│   ├── base.py          # Abstract StorageBackend
│   ├── json_backend.py  # JSON file backend
│   ├── sqlite_backend.py
│   └── postgres_backend.py
├── integrations/
│   ├── base.py          # TicketProvider ABC, TicketRef, resolve_token
│   └── github.py        # GitHubProvider (PyGithub)
└── widgets/
    ├── common.py        # fmt_duration, group_entries, rank_options, GroupKey
    ├── entry_list.py    # Row/Item dataclasses and list widget types
    ├── entry_modal.py   # EntryModal (create/edit form)
    └── ticket_picker.py # TicketPickerModal (GitHub issue search)

GitHub Issues integration

Install the optional dependency:

uv sync --extra github

Authentication — no manual token setup is needed if the GitHub CLI is installed and authenticated (gh auth login). The app reads the token automatically via gh auth token. Alternatively, set github_token in ~/.config/time-tracker/config.json.

Default repository — set github_repo (e.g. "owner/repo") in config to scope searches to one repo. Without it, searches are global across GitHub.

Once configured, a Browse… button appears next to the Ticket field in the entry form. It opens an issue picker with a live search (open issues by default; toggle [ ] closed to include closed ones). Selecting an issue fills the ticket field with owner/repo#number.

Note: This integration uses the GitHub CLI (gh) to obtain the auth token if github_token is not set in config. The gh CLI must be installed and authenticated for this to work.

Development

# Format
uv run black src/

# Run with Textual dev console (live reload, inspector)
uv run textual run --dev src/time_tracker/__init__.py

No automated tests currently exist. Textual's testing framework (textual.testing) is available via the textual-dev dependency group if tests are added.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

listcrawler_time_tracker-0.1.0.tar.gz (141.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

listcrawler_time_tracker-0.1.0-py3-none-any.whl (70.2 kB view details)

Uploaded Python 3

File details

Details for the file listcrawler_time_tracker-0.1.0.tar.gz.

File metadata

  • Download URL: listcrawler_time_tracker-0.1.0.tar.gz
  • Upload date:
  • Size: 141.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for listcrawler_time_tracker-0.1.0.tar.gz
Algorithm Hash digest
SHA256 89d4f47e30c3e2a609023698dd141a42f3b0b17882b76cb9d45d3d3c5c17c92a
MD5 659717be288e3990f515154886af00b9
BLAKE2b-256 a6879a79cbdedca803bb3afddd0c0f20e61063d1f8d10656f039e95a85ee5caa

See more details on using hashes here.

Provenance

The following attestation bundles were made for listcrawler_time_tracker-0.1.0.tar.gz:

Publisher: publish.yml on listcrawler/time-tracker

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file listcrawler_time_tracker-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for listcrawler_time_tracker-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f76eb6a9b2e337264ce7c8e0eb9c4bde585baea5a1c92764410c99a37cbfaea9
MD5 7d58b34ad59a751d6427623617abc969
BLAKE2b-256 7ee6e87b56a7c4942d362abf793cdb099b7468c47b4d2bebe927fd50cd93b3be

See more details on using hashes here.

Provenance

The following attestation bundles were made for listcrawler_time_tracker-0.1.0-py3-none-any.whl:

Publisher: publish.yml on listcrawler/time-tracker

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page