Self-hosted time tracking for developers. Daemon + CLI + Web Dashboard + MCP, written in Rust.
git clone https://github.com/Blysspeak/timeforged.git
cd timeforged
bash install.shThe installer will:
- Build the web dashboard (Vue 3 + Tailwind)
- Compile Rust binaries (
timeforgeddaemon +tfCLI) - Install to
~/.local/bin/ - Set up a systemd user service
- Register on the remote server (
timeforged.nexalix.io) for sync & public profile card - Configure auto-sync (every 15 min via systemd timer)
- Install Claude Code hooks (if detected)
- Install the Waybar module (if Waybar is detected)
- Start the daemon and display your API key
Then initialize tracking:
tf init ~/projects # watch a directory treeOpen http://127.0.0.1:6175 in your browser — the dashboard works immediately, no login required on localhost.
# Build dashboard
cd crates/timeforged/web && npm install && npx vite build && cd ../../..
# Build binaries
cargo build --release
# Run daemon
./target/release/timeforgedOn first run, an admin user and API key are created automatically:
==============================================
TimeForged — first run setup
Created admin user with API key:
tf_abc123...
Save this key! It won't be shown again.
==============================================
The daemon serves a built-in web dashboard at http://127.0.0.1:6175:
- Period switcher — Today / 7 Days / 30 Days / All Time
- All Time total — always visible, shows cumulative hours, active days, and project count
- Activity chart — time per day for the selected period
- Projects & Languages — breakdown with progress bars and language icons
- Sessions — recent coding sessions with duration
- Stats — top project, session count
Localhost requests are auto-authenticated — no API key needed to view the dashboard.
TimeForged automatically tracks your coding activity via filesystem events — no editor plugins needed.
tf init ~/projects # start watching (recursive, inotify-based)
tf list # show watched directories
tf unwatch ~/projects # stop watchingThe daemon watches registered directories recursively and creates heartbeat events on file changes. Features:
- Project detection — first-level subdirectory of the watched root becomes the project name
- Language detection — inferred from file extension and filename patterns
- Git branch — cached per project (60s TTL)
- Debounce — 30s per file to avoid event spam
- Ignored paths —
.git,node_modules,target,__pycache__, lock files, binaries - Window tracker (optional) — polls
hyprctl/xdotoolevery 15s for active editor file
Watched directories persist in ~/.config/timeforged/watched.toml.
Show today's coding time in your Waybar panel:
bash contrib/waybar/install.shThis installs a custom module that queries the TimeForged API every 60s, displays time as 1:25, and opens the dashboard on click. The tooltip shows per-project breakdown.
The installer is also run automatically by install.sh when Waybar is detected.
TimeForged can automatically track your AI-assisted coding sessions via Claude Code hooks.
bash install.sh # choose "yes" when prompted for Claude Code hooksOr install manually:
mkdir -p ~/.claude/hooks
cp contrib/claude-code/timeforged-heartbeat.sh ~/.claude/hooks/
chmod +x ~/.claude/hooks/timeforged-heartbeat.shThen add to ~/.claude/settings.json:
{
"hooks": {
"UserPromptSubmit": [{ "type": "command", "command": "~/.claude/hooks/timeforged-heartbeat.sh" }],
"PostToolUse": [{ "type": "command", "command": "~/.claude/hooks/timeforged-heartbeat.sh" }],
"Stop": [{ "type": "command", "command": "~/.claude/hooks/timeforged-heartbeat.sh" }]
}
}The hook fires on every interaction with 30s debounce, sends heartbeats in the background (non-blocking), and auto-detects project, language, branch, and file paths from hook context.
crates/
timeforged-core/ # Shared types, models, config
timeforged/ # Daemon — Axum REST API + SQLite + embedded SPA
web/ # Vue 3 + Tailwind CSS dashboard
tf/ # CLI client
contrib/
waybar/ # Waybar module + installer
claude-code/ # Claude Code heartbeat hook
File Watcher (inotify) ──┐
Window Tracker (optional) ┼──→ Events ──→ Storage (SQLite)
Claude Code (hooks) ──────┤
HTTP API (POST /events) ──┘
↓
Browser → Embedded SPA (rust-embed) ──→ Reports API ──→ Storage
Waybar module ─────────────────────────→ Reports API
tf init ~/projects # watch a directory tree
tf list # show watched directories
tf unwatch ~/projects # stop watching
tf status # daemon status
tf today # today's summary
tf report --range week # weekly report
tf report --range month --project myapp
tf send /path/to/file.rs --project myapp --language Rust # manual heartbeatAPI key is configured once in ~/.config/timeforged/cli.toml or via TF_API_KEY.
TimeForged can sync your local activity to a remote server and generate an SVG card for your GitHub profile.
The installer handles this automatically. To set up manually:
# Register on the remote server
tf register <username> --remote https://timeforged.nexalix.io
# Enable public profile (makes your card visible)
tf profile --public
# Sync events
tf syncAdd this to your GitHub profile README:
<a href="https://github.com/Blysspeak/timeforged">
<img src="https://timeforged.nexalix.io/api/v1/card/<username>?theme=dark" width="766" alt="TimeForged Activity" />
</a>The card updates automatically as you sync. Available parameters:
theme—dark(default) orlightdays— number of days to show (1-365, default 7)
# ~/.config/timeforged/cli.toml
server_url = "http://127.0.0.1:6175"
api_key = "tf_..."
remote_url = "https://timeforged.nexalix.io"
remote_key = "tf_..."| Method | Path | Description |
|---|---|---|
| POST | /api/v1/register |
Register new user (rate limited) |
| GET | /api/v1/card/{username} |
Public SVG profile card |
| GET | /api/v1/status |
Server status |
| GET | /health |
Health check |
docker compose up -dOr build manually:
docker build -t timeforged .
docker run -d -p 6175:6175 -v timeforged-data:/data timeforgedAll authenticated endpoints require the X-Api-Key header (or localhost access).
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /health |
Health check | |
| GET | /api/v1/status |
Daemon status | |
| POST | /api/v1/register |
Register new user (rate limited) | |
| GET | /api/v1/card/{username} |
Public SVG profile card | |
| GET | /api/v1/card.svg |
key | Private SVG card |
| POST | /api/v1/events |
key | Create event |
| POST | /api/v1/events/batch |
key | Batch create (up to 100) |
| GET | /api/v1/events |
key | Export events (for sync) |
| GET | /api/v1/reports/summary |
key | Time summary by project/language/day |
| GET | /api/v1/reports/sessions |
key | Coding sessions |
| GET | /api/v1/reports/activity |
key | Hourly activity |
| GET | /api/v1/me |
key | Current user |
| PUT | /api/v1/me/public-profile |
key | Toggle public profile |
| POST | /api/v1/api-keys |
key | Create API key |
| GET | /api/v1/api-keys |
key | List API keys |
| DELETE | /api/v1/api-keys/{id} |
key | Delete API key |
| POST | /api/v1/watch |
key | Add watched directory |
| DELETE | /api/v1/watch |
key | Remove watched directory |
| GET | /api/v1/watched |
key | List watched directories |
from, to (ISO 8601), project, language
{
"timestamp": "2026-02-27T10:00:00Z",
"event_type": "file",
"entity": "/home/user/project/src/main.rs",
"project": "app",
"language": "Rust",
"branch": "main",
"activity": "coding"
}event_type: file | terminal | browser | meeting | custom
activity: coding | browsing | debugging | building | communicating | designing | other
Project and language are auto-inferred from file path when omitted.
host = "127.0.0.1"
port = 6175
database_url = "sqlite:~/.local/share/timeforged/timeforged.db?mode=rwc"
idle_timeout = 300
log_level = "info"server_url = "http://127.0.0.1:6175"
api_key = "tf_..."
remote_url = "https://timeforged.nexalix.io"
remote_key = "tf_..."[[directories]]
path = "/home/user/projects"Managed via tf init / tf unwatch.
All settings can be overridden with TF_ prefix:
TF_HOST, TF_PORT, TF_DATABASE_URL, TF_IDLE_TIMEOUT, TF_LOG_LEVEL, TF_SERVER_URL, TF_API_KEY, TF_REMOTE_URL, TF_REMOTE_KEY
