{"id":50112333,"url":"https://github.com/cristianonescu/dashd","last_synced_at":"2026-05-23T13:04:25.106Z","repository":{"id":359727951,"uuid":"1244384499","full_name":"cristianonescu/dashd","owner":"cristianonescu","description":"A Python agent on your computer collects developer-relevant metrics and streams them to an ESP32-C3 driving a 240×320 ST7789V display — over USB-CDC serial, Bluetooth LE, or auto-switching between the two. A single tactile button cycles pages.","archived":false,"fork":false,"pushed_at":"2026-05-23T07:10:44.000Z","size":2618,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-23T07:29:21.732Z","etag":null,"topics":["ai-tools","developer-tools","iot"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cristianonescu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-20T08:06:13.000Z","updated_at":"2026-05-23T07:10:47.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/cristianonescu/dashd","commit_stats":null,"previous_names":["cristianonescu/dashd"],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/cristianonescu/dashd","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cristianonescu%2Fdashd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cristianonescu%2Fdashd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cristianonescu%2Fdashd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cristianonescu%2Fdashd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cristianonescu","download_url":"https://codeload.github.com/cristianonescu/dashd/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cristianonescu%2Fdashd/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33396586,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-23T04:15:53.637Z","status":"ssl_error","status_checked_at":"2026-05-23T04:15:53.242Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-tools","developer-tools","iot"],"created_at":"2026-05-23T13:04:24.434Z","updated_at":"2026-05-23T13:04:25.099Z","avatar_url":"https://github.com/cristianonescu.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# dashd\n\nDesk widget for developers. A Python agent on your computer collects developer-relevant metrics and streams them to an ESP32-C3 driving a 240×320 ST7789V display — over **USB-CDC serial**, **Bluetooth LE**, or auto-switching between the two. A single tactile button cycles pages.\n\nUSB: power + data over one cable. Bluetooth: cable-free, the device runs on any 5 V source. Nothing leaves your machine either way.\n\n## Status\n\n**v0.1.9 — device auto-cycles between pages on a timer; toggle / change interval / pick sequential or random from Settings.** See [GitHub Releases](https://github.com/cristianonescu/dashd/releases) for installers (macOS / Windows / Linux) and firmware binaries.\n\n- **Agent** (Python 3.11+): 8 active collectors + 3 stubs (Slack / Teams / WhatsApp), in-process pub/sub bus, local IPC server, transport abstraction over USB-CDC + BLE GATT, ships as a single PyInstaller binary.\n- **AI usage accuracy** (v0.1.6+): hybrid pipeline — direct Anthropic OAuth API for Session/Weekly/Sonnet/Extra gauges (opt-in, matches Claude.ai exactly) + local JSONL scan for token counts, cost, per-project breakdown, burn rate. ccusage-style block segmentation; pace metric (\"on track / behind / far behind\"); LiteLLM-backed per-model pricing with daily refresh.\n- **Firmware** (ESP32-C3 + ST7789V via LovyanGFX): 8 pages, host-driven theme + thresholds + page enable mask, USB and BLE links both active, **dual-slot OTA partition layout with SHA256 verification + auto-rollback**, all persisted to NVS across reboots.\n- **Bluetooth LE**: cable-free transport built on NimBLE-Arduino + bleak. Pairing with a 6-digit code shown on the device, trusted-device list on the host, auto-reconnect, log/event forwarding identical to USB.\n- **Auto-update**: the Electron app self-updates via `electron-updater` (6-hour periodic check + manual via tray menu + Settings → Updates). The **device firmware also updates from the same release page** — the agent downloads the matching `.bin`, streams it over the active USB or BLE link, the device flashes the inactive slot, verifies SHA256, flips the boot pointer, and reboots into the new firmware. Auto-rollback if the new image doesn't come up cleanly.\n- **Electron UI**: live view, full settings, VSCode-style dockable logs panel, Connection pane with transport selector + BLE scan/pair/forget flow, contextual hint layer (`data-hint` tooltips on every control), update banner + Settings → Updates pane, autostart, restart, reload config, reset device prefs, drag-to-reorder pages with enable toggle, color-zone editor, text-size sliders per role (title / label / value / big), threshold sliders, brightness, title-bar + footer visibility, screen rotation. Menu-bar / system-tray icon with the same controls + dynamic update menu. App icon shipped.\n- **Installers**: `electron-builder` produces DMG (ad-hoc codesigned for Gatekeeper-friendly first launch), NSIS / AppImage / .deb (via the GitHub Actions workflow in `.github/workflows/build.yml`). Silent auto-update works on Windows + Linux; on macOS it falls back to opening the Releases page (Apple Developer ID needed for silent mac updates).\n\n## Wiring\n\nSchematic — one row per connection, follow it top-to-bottom while wiring:\n\n![wiring schematic](docs/wiring.svg)\n\nPhysical board view — how it looks on the breadboard:\n\n![physical board view](docs/wiring_physical.svg)\n\nFull pinout table and BOM in [docs/wiring.md](docs/wiring.md).\n\n## Prerequisites\n\n| Tool        | Purpose                            | macOS                                  | Linux                                       | Windows                                     |\n|-------------|------------------------------------|----------------------------------------|---------------------------------------------|---------------------------------------------|\n| Python 3.11+| Agent runtime                       | `brew install python` (3.14 OK)       | `apt install python3 python3-venv` / `dnf install python3` | [python.org installer](https://www.python.org/downloads/) (tick \"Add to PATH\") |\n| PlatformIO  | Firmware build/flash for ESP32-C3   | `brew install platformio`              | `pipx install platformio` (or `pip install --user`)         | `pip install platformio`                   |\n| Node.js 22+ | Electron UI build                   | `brew install node`                    | `apt install nodejs npm` or [nodesource](https://github.com/nodesource/distributions) | [nodejs.org installer](https://nodejs.org/) |\n| Git         | Source control                      | `xcode-select --install` or Homebrew   | `apt install git` / `dnf install git`       | [git-scm.com installer](https://git-scm.com/) |\n\nThe ESP32-C3 SuperMini uses the built-in USB Serial/JTAG (no CP2102/CH340 driver needed on modern macOS/Linux/Windows). It enumerates with USB **VID `303A`** / **PID `1001`**.\n\n## Build\n\ndashd has three buildable pieces — firmware, Python agent, Electron UI — and a packaged installer that bundles the agent inside the UI.\n\n### Clone\n\n```bash\ngit clone https://github.com/cristianonescu/dashd.git\ncd dashd\n```\n\n### Firmware (ESP32-C3)\n\n```bash\ncd firmware\npio run                         # compile only\npio run -t upload               # compile + flash (auto-detects the USB port)\npio device monitor -b 115200    # optional: tail device output without the agent\n```\n\nThe board's native USB Serial/JTAG enters bootloader mode automatically when PlatformIO requests it — no BOOT/RESET button dance. macOS may prompt for permission on the first flash.\n\n### Agent (Python)\n\n```bash\ncd agent\npython3 -m venv .venv\nsource .venv/bin/activate                      # Windows: .venv\\Scripts\\activate\npip install -e '.[dev,net]'                    # all deps incl. network collectors\npytest -q                                       # ~110 tests should pass\n```\n\nTo produce the **standalone single-binary** (≈22 MB) that the installer bundles:\n\n```bash\npyinstaller dashd-agent.spec --clean\n# → agent/dist/dashd-agent  (or dashd-agent.exe on Windows)\n```\n\nThe binary embeds the matching CPython interpreter and every collector module. End users don't need Python installed.\n\n### UI (Electron + Vite + React + TS)\n\n```bash\ncd ui\nnpm install\nnpm run build                                  # tsc -p tsconfig.electron.json \u0026\u0026 vite build\n```\n\nOutputs:\n- `dist/` — renderer bundle (HTML, CSS, JS)\n- `dist-electron/` — compiled main process + preload\n\n### Installers (electron-builder)\n\nAfter the agent binary AND `npm run build` finish, package the desktop installer for your host OS:\n\n```bash\ncd ui\nnpx electron-builder --mac --arm64       # → release/dashd-\u003cv\u003e-arm64.dmg\nnpx electron-builder --mac --x64         # → release/dashd-\u003cv\u003e-x64.dmg\nnpx electron-builder --win               # → release/dashd Setup \u003cv\u003e.exe   (run on Windows)\nnpx electron-builder --linux             # → release/*.AppImage + .deb     (run on Linux)\n```\n\nFor **cross-platform installers in one shot**, push a tag and let the GitHub Actions workflow build on all three OSes:\n\n```bash\ngit tag v0.1.9 \u0026\u0026 git push --tags        # triggers .github/workflows/build.yml\n```\n\nEach runner builds the agent binary for its own OS, builds the UI, runs `electron-builder`, and uploads the installer as a release artifact. Full packaging notes: [docs/packaging.md](docs/packaging.md).\n\n## Run\n\nThree ways to actually use dashd — pick whichever matches what you're doing.\n\n### A. From source (development loop)\n\nBest while you're iterating on collectors or the firmware. Three terminals, one each:\n\n```bash\n# 1) Flash the device (only when firmware changes)\ncd firmware \u0026\u0026 pio run -t upload\n\n# 2) Run the agent from source\ncd agent \u0026\u0026 source .venv/bin/activate \u0026\u0026 python -m dashd -v\n\n# 3) Run the UI in dev mode (live reload)\ncd ui \u0026\u0026 npm run electron:dev\n```\n\nThe agent prints state pushes + firmware log events to your terminal. The UI window shows the live state and lets you tweak the device with no flashing required.\n\n### B. From the packaged installer (recommended for daily use)\n\nOpen the installer for your OS, drag the app into Applications / install via the wizard, then launch **dashd** from your launcher.\n\nOn first launch dashd asks once: *\"Start dashd at login? Yes / Not now.\"*\n- **Yes** → the agent is installed as a launchd LaunchAgent (macOS), systemd-user unit (Linux), or HKCU\\Run entry (Windows) and starts on every login.\n- **Not now** → the agent only runs while the app is open.\n\nYou can change this any time from **Settings → General → Start at login**.\n\nThe agent runs in the background; closing the app window hides it but the menu-bar / system-tray icon keeps you in control:\n\n```\n🟢 Agent running\n🟢 Device connected\n─────────────────────\nShow window / Hide window\nRestart agent\nReload config\n☐ Start at login\n─────────────────────\nQuit dashd\n```\n\n### C. Headless (agent only, no UI)\n\nIf you don't want the Electron app — just the agent pushing to the device:\n\n```bash\n# Foreground\ndashd                           # entry-point installed by `pip install -e`\n# or\n./dist/dashd-agent              # the PyInstaller single-binary\n\n# Background (macOS, manual launchd install)\ncp packaging/ro.softwarechef.dashd.agent.plist ~/Library/LaunchAgents/\nlaunchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/ro.softwarechef.dashd.agent.plist\n```\n\nSubscribe to the agent's local IPC server (TCP `127.0.0.1:52317`) to consume the same JSON state frames the device gets — useful for scripting / other widgets. See [docs/ipc.md](docs/ipc.md).\n\n## Connecting the device\n\ndashd talks to the ESP32 over one of three transports, selected in **Settings → Connection** (or `[transport].mode` in `config.toml`, or the `DASHD_TRANSPORT` env var):\n\n| Mode        | Behavior |\n|-------------|----------|\n| `cable`     | USB-CDC serial only. Fastest, no pairing. |\n| `bluetooth` | BLE GATT only. No cable required — the device just needs 5 V power. |\n| `auto`      | USB when a dashd device is plugged in, BLE otherwise. **Recommended.** |\n\nSwitching modes restarts the agent so the new transport is picked up.\n\n### Bluetooth pairing\n\nThe first time you connect to a device over BLE you'll be asked to confirm a code:\n\n1. Power the device (any USB-C source works — wall brick, power bank, the host).\n2. Open **Settings → Connection → Bluetooth devices → Scan**.\n3. Click **Pair** next to the device you want. A 6-digit code shows on the device's screen — and is also surfaced in the Logs panel and the Connection pane itself.\n4. Type the code, click **Pair**. The agent stores a trust token under `~/.config/dashd/ble_trust.json` (mode 0600); future connects to that device skip the code.\n5. To revoke trust, **Settings → Connection → Paired devices → Forget**.\n\nTrusted-device storage and the on-device pairing protocol are documented in [docs/protocol.md](docs/protocol.md).\n\n### Logs panel\n\nLogs live in a VSCode-style dockable panel at the bottom of the window, toggled via the **Logs ▴** button at the bottom-right corner. It overlays whichever tab is active (Live or Settings), so you can watch agent + firmware logs while tweaking settings or watching the live view. Filter by level (all / info / warn / error); the panel keeps the last 1000 lines and auto-scrolls when you're at the bottom.\n\n## Auto-update\n\nBoth the desktop app and the device firmware update from the same GitHub Releases page. Each one is its own prompt — you can defer one without affecting the other.\n\n### App\n\nThe app checks GitHub Releases on launch and then every 6 hours. When a newer release is found:\n\n- A banner appears in the top-right corner: *\"dashd v0.1.9 available — Download\"*.\n- The tray icon's right-click menu adds **▾ v0.1.9 available → Download update**.\n- **Settings → Updates** has the full picture: current version, last-check timestamp, release notes, Download / Restart-and-install / Open release page.\n\nDownloads run in the background; the new version installs on the next quit (or right away if you click **Restart and install**).\n\n**macOS caveat:** the unsigned-by-Apple-Developer builds are accepted by Gatekeeper after a one-time *\"identified developer cannot be verified\"* click-through, but `electron-updater`'s Squirrel.Mac path won't silently install updates against them. When Squirrel reports a code-signature error, dashd falls back to opening the GitHub release page in your browser — you download the new DMG and replace the app manually. Once we sign with a paid Apple Developer ID, silent updates work like on Linux + Windows.\n\n### Device firmware\n\nPre-flight checklist:\n- Device must be powered and connected (USB or BLE — whichever transport is active).\n- One-time-only: if upgrading from v0.1.1, the partition layout changed in v0.1.2 (single-app → dual-slot OTA). v0.1.1 firmware can't OTA to v0.1.2 — flash once via USB with `pio run -e dashd_ble -t upload`. From v0.1.2 onwards, OTA works.\n\nThe flow:\n\n1. Open **Settings → Updates → Device firmware → Check for updates**.\n2. If a newer firmware is published, the card shows the version + release notes.\n3. Click **Update firmware**. Confirm the dialog.\n4. Agent downloads the matching `.bin` from GitHub (`-ble.bin` if the device is on Bluetooth, `-usb.bin` over the cable), computes the SHA256, and streams it in ~2 KB chunks to the inactive OTA slot. Each chunk is ACK'd by the device for flow control.\n5. The device shows a fullscreen progress overlay (version transition, big progress bar, KB counter, *\"do not unplug\"* warning). Same data also surfaces in the Electron logs panel.\n6. After the last chunk, the device re-verifies the SHA256 against the host-provided hash, flips the boot pointer, and reboots into the new slot.\n7. If the new firmware fails to send its first `boot` event within 30 s after reset, the ESP-IDF bootloader rolls back to the previous slot automatically — your device cannot be bricked by a bad image.\n\nOver USB, the whole transfer takes a few seconds. Over BLE, expect 1–3 minutes (GATT writes at ~10 KB/s).\n\nIf anything goes wrong, the card shows the error and the previous firmware keeps running — you can retry, or unplug + replug to start fresh.\n\n## Deploy\n\n### To your own machine\n\n1. Build the installer for your OS (see *Build* above) **or** download the artifact from the GitHub Actions workflow run.\n2. Run the installer (`dashd-\u003cversion\u003e-\u003carch\u003e.dmg`, `.exe`, `.AppImage`, or `.deb`).\n3. First launch: opt in to autostart if you want it.\n4. Set the network-collector env vars (next section) and reload config from Settings.\n\n### To other users\n\nTagged releases go through the CI workflow:\n\n```bash\n# In your local clone\ngit tag v0.2.0\ngit push --tags\n# → .github/workflows/build.yml runs a firmware (esp32-c3) job + per-OS\n#   matrix (macos-14 cross-builds arm64+x64, windows-latest, ubuntu-latest).\n#   Installers + firmware bins attach to a draft GitHub Release.\n```\n\nPromote the draft release to published when you've smoke-tested the artifacts. Users with an older dashd installed get the update automatically via `electron-updater` (configured to read from the same GitHub Releases endpoint).\n\n### Production notes\n\n- **Unsigned v1.** macOS Gatekeeper / Windows SmartScreen will warn on first launch:\n  - macOS: right-click the app → Open → confirm. Once accepted, future launches are silent.\n  - Windows: SmartScreen \"More info\" → Run anyway.\n  - Both workarounds documented in [docs/packaging.md](docs/packaging.md). To remove the warnings, sign with an Apple Developer cert + Windows code-signing cert and re-build — `electron-builder.yml` has hooks for both.\n- **Run-at-login** is per-user, off by default. Toggled live from Settings → General; writes a launchd plist / systemd unit / Run-key entry under the user's home, never anything system-wide.\n- **Secrets** (GitHub PAT, IMAP app password) stay in environment variables — they never enter `config.toml` or git. See *Network collector setup* below.\n- **Update channel.** `electron-updater` checks GitHub Releases at every launch. To disable, comment out the `publish:` block in `ui/electron-builder.yml`.\n- **Multiple machines.** The agent owns TCP port `52317` per host; a second dashd-agent on the same machine refuses to start (port-busy guard). Different machines are fully independent — each has its own device, agent, and `~/.config/dashd/` state.\n\n## Layout\n\n```\ndashd/\n├── agent/         # Python agent (collectors, transports, IPC server)\n│   ├── dashd/transport/  # USB-CDC + BLE GATT links + base interface\n│   └── dashd-agent.spec  # PyInstaller bundle spec\n├── firmware/      # ESP32-C3 PlatformIO project (LovyanGFX + ArduinoJson + NimBLE)\n│   └── src/       # usb_link.cpp, ble_transport.cpp, pages, pet, transitions\n├── ui/            # Electron + Vite + React + TS desktop app\n│   ├── electron/  # main process (supervisor, IPC client, autostart)\n│   └── src/       # renderer (React) — ConnectionPane, LogsPanel, HintLayer, …\n└── docs/          # Wiring, protocol, setup guides, IPC, packaging\n```\n\n## Pages\n\n| # | Page     | Sources                          |\n|---|----------|----------------------------------|\n| 1 | Home     | Live tiles: CPU, RAM, AI $, git, PRs, msgs |\n| 2 | System   | `psutil` — CPU/core, RAM, disk, net, battery, CPU temp |\n| 3 | AI Spend | Claude Code (tokens + cost + 5h block) and Codex (block %) |\n| 4 | Dev Flow | Branch, commits today, ±LOC, time since last commit |\n| 5 | GitHub   | PRs awaiting your review, CI failures (24h), unread notifications |\n| 6 | Calendar | Countdown to next event, title, remaining events today |\n| 7 | Messages | Email + iMessage + (stubs for Slack/Teams/WhatsApp) |\n| 8 | Tips     | Real-time advice (close-this-app / slow-down-claude / battery-low) + top processes by CPU and RAM |\n\nShort-press the button to advance one page; long-press to return to Home. Page swaps are animated — **forward** (short-press, host `show_page`) plays a horizontal wipe with the pet running ahead of the new page; **home** (long-press) plays a centre zoom with the pet jumping in the middle. The 5h block on AI Spend mirrors Anthropic's rolling rate-limit window; cost is computed from a vendored snapshot of [LiteLLM's pricing catalog](https://github.com/BerriAI/litellm) (see [agent/dashd/pricing/](agent/dashd/pricing/) — refreshed daily over httpx, overridable per-model via `[collectors.claude_code.rates]` in `config.toml`).\n\n### Pet overlay\n\nA small animated pet sits on every page (default **bottom-right** corner, toggleable from Settings → Pet). The agent reacts to your activity:\n\n| Trigger | Pet animation |\n|---|---|\n| New commit since last tick | `wave` |\n| CI failure appeared | `failed` |\n| Meeting in ≤ 5 min | `jump` |\n| AI 5h block ≥ 75 % | `review` |\n| RAM ≥ 92 % or CPU ≥ 80 % | `running` |\n| Nothing happening for \u003e 60 s | `waiting` |\n| Otherwise | `idle` |\n\nThe default pet is **Claw'd** (creator: krrsantan, [source](https://codexpets.net/gallery/claw-d)), embedded directly into the firmware as RGB565 frames (no download needed on first use, full attribution in Settings → Pet).\n\nSwap pets from **Settings → Pet → Catalog**: the agent fetches the full codexpets.net catalog from its public sitemap into a lazy-scrolled list, downloads + converts + streams the bundle to the device, and stores it in the on-device LittleFS partition. Direct slug or URL entry works too (`pixel-coder` or `https://codexpets.net/gallery/pixel-coder`). The active pet survives reboots; switching back to the default is one click.\n\n## Network collector setup\n\nSet these env vars (or paste secrets into `~/.config/dashd/config.toml`, less safe):\n\n```bash\nexport GITHUB_TOKEN=ghp_...                      # PAT with `repo` + `notifications`\nexport DASHD_EMAIL_PASSWORD='app-password-here'  # Gmail app password for IMAP\n```\n\nFor Microsoft Graph Calendar, follow [docs/microsoft-graph-setup.md](docs/microsoft-graph-setup.md) once to get `client_id` + `tenant_id`. First run prints a device-code URL — sign in once, the refresh token caches to `~/.config/dashd/msgraph_token.json` (mode 0600).\n\n### AI usage tuning\n\ndashd's AI usage display has two data pipelines:\n\n1. **Anthropic OAuth API** (opt-in, v0.1.6+). When enabled, dashd reads your local Claude Code OAuth token and calls Anthropic's usage endpoint directly. Session % / Weekly % / Sonnet % / Extra-usage match Claude.ai exactly because they ARE Claude.ai's numbers. Toggle in **Settings → Privacy → \"Enable Anthropic OAuth Usage API\"** (default off).\n2. **Local JSONL scan** (always on). Reads `~/.claude/projects/**/*.jsonl` to derive token counts, cost, per-project breakdowns, burn rate. Includes cache reads — token numbers will be larger than Claude.ai's plan-unit gauges, which is correct: the cost is real, the percentages are different metrics.\n\nThe two pipelines coexist on the wire. The new `anthropic` block is additive — UI prefers it for gauges, falls back to JSONL when the OAuth pipeline is disabled or unreachable.\n\n| Env var / config key | Effect |\n|---|---|\n| `DASHD_ANTHROPIC_OAUTH=1` *(or `[collectors.anthropic_oauth] enabled = true`, or Settings → Privacy)* | Enable the Anthropic OAuth Usage API pipeline. Default off. Toggling restarts the agent so the env var takes effect. |\n| `DASHD_CLAUDE_BLOCK_BUDGET=1500000` *(or `[collectors.claude_code] block_token_budget = 1500000`)* | Unlocks `block_used_pct` (real quota %) and the \"hits cap in N min\" burn-rate projection on the AI Spend page. Without it, only the time-elapsed metric (`block_elapsed_pct`) is shown. |\n| `CLAUDE_CONFIG_DIR=/path1,/path2,…` | Override Claude Code's project-data location. Comma-separated, all scanned. Defaults to `$XDG_CONFIG_HOME/claude` then `~/.claude`. |\n| `DASHD_TRANSPORT={cable\\|bluetooth\\|auto}` | Pick the device link. Defaults to `auto`. The Electron app sets this for you via Settings → Connection. |\n\nThe agent persists Codex's cumulative-delta state at `~/.config/dashd/codex_state.json` and a cached LiteLLM pricing snapshot at `~/.config/dashd/litellm-pricing.cache.json` (refreshed daily via httpx; falls back to the bundled snapshot silently on failure). Both are safe to delete — they're rebuilt on next collect.\n\n### Tray popover + Usage tab (v0.1.6+)\n\nClick the dashd tray/menu-bar icon — a frameless popover opens at the icon with Session, Weekly, Sonnet, Extra-usage gauges plus today/last-7-days cost (codexbar-style at-a-glance). Right-click still shows the full context menu (restart, autostart, quit, etc.).\n\nThe main window gains a **Usage** tab between Live and Settings: same gauges at the top, plus dashd-only deep-dive sections (cost \u0026 tokens by window, top projects today, per-model breakdown, Codex stats).\n\n## Troubleshooting\n\n**`python -m dashd` says \"no matching serial device\"**\nThe agent scans `pyserial`'s port list for VID `0x303A` / PID `0x1001`. List what your OS sees:\n\n```bash\npython -c \"from serial.tools.list_ports import comports\nfor p in comports(): print(p.device, hex(p.vid or 0), hex(p.pid or 0), p.description)\"\n```\n\nIf the VID/PID differ, override the port in `~/.config/dashd/config.toml`:\n\n```toml\n[serial]\nport = \"/dev/tty.usbmodem01\"\n```\n\nor via env var: `DASHD_SERIAL_PORT=/dev/tty.usbmodem01 python -m dashd`.\n\n**Display is black**\nCheck the BLK wire to GPIO 3 — the firmware drives it HIGH on boot. Verify the SPI pinout against [docs/wiring.md](docs/wiring.md).\n\n**`pio run -t upload` fails to find a port**\nUnplug and replug. If still missing, check `ls /dev/tty.usbmodem*` (macOS) or `ls /dev/ttyACM*` (Linux). On Linux, your user may need to be in the `dialout` group (`sudo usermod -aG dialout $USER \u0026\u0026 newgrp dialout`).\n\n**Firmware compiles but the display shows garbage**\nSPI clock is 40 MHz in [include/lgfx_panel.h](firmware/include/lgfx_panel.h). Drop `freq_write` to 27 MHz if you see corruption; some unbranded ST7789V panels can't keep up.\n\n**Colors are inverted (background looks light)**\nToggle `cfg.invert` in `lgfx_panel.h` — ST7789V variants differ.\n\n**I want to see firmware logs**\nThe agent prints them: any `usb_log(...)` call from the firmware arrives as a `{\"type\":\"event\",\"name\":\"log\",\"level\":...,\"msg\":...}` line over the same USB-CDC port and renders through `dashd.fw` at the matching log level. Run `dashd -v` to include debug logs.\n\nFor raw `pio device monitor`, stop the agent first — macOS lets only one process own the CDC port at a time.\n\n**Agent shows live data but the screen doesn't update**\nThe firmware shows a \"host disconnected\" (red ×) indicator if it hasn't received a state message in 10 s. Check `pio device monitor` for parse errors.\n\n**Bluetooth: Scan finds nothing**\nThe device only advertises while no host is connected to it. If the cable is plugged in *and* `[transport].mode = \"auto\"`, the agent is already attached over USB and BLE advertising is suppressed — unplug it (or set mode to `bluetooth`) and scan again. On macOS, also confirm Bluetooth is on under System Settings and that the dashd app has been granted Bluetooth permission (System Settings → Privacy \u0026 Security → Bluetooth) — first scan triggers the prompt.\n\n**Bluetooth: \"Pairing failed\"**\nMost often the 6-digit code timed out (≈30 s). Click **Pair** again; a fresh code appears on the device. If the device is paired with a different host (the macOS Bluetooth menu shows it as \"Connected\"), unpair it there first — the dashd link is in-band, not OS-level Bluetooth.\n\n**Bluetooth: keeps reconnecting / drops every few seconds**\nRun `dashd -v` and watch the Logs panel for `ble`-prefixed lines. RSSI under −80 dBm usually means range or interference; move the device closer or restart it. If logs show repeated `trust check failed`, the device's NVS lost the trust token (e.g. you re-flashed firmware) — click **Forget** for that address and re-pair.\n\n## Protocol\n\nSee [docs/protocol.md](docs/protocol.md). Newline-delimited UTF-8 JSON, shared between transports: USB-CDC runs at 460800 baud; BLE wraps the same JSON in a write-without-response GATT characteristic plus a notify characteristic for state/event traffic.\n\n## Collectors\n\nSee [docs/collectors.md](docs/collectors.md) for a per-collector reference: data sources, fallback behavior, and known caveats (e.g. Codex JSONL doesn't expose per-call token counts, only block %).\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcristianonescu%2Fdashd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcristianonescu%2Fdashd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcristianonescu%2Fdashd/lists"}