Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
# Changelog

## 1.1.0 — 2026-08-11

ClaudeThing.ai 1.1 makes the dashboard more reliable as an unattended desk display and introduces the project's first source-linked, ready-to-flash firmware release.

### Highlights

- Automatic Claude OAuth refresh with retained last-valid quota data and rate-limit-aware polling.
- More resilient USB snapshot mirroring and tunnel recovery after sleep, reconnects, and unreliable hub behavior.
- An authenticated Bluetooth fallback that automatically stands by while USB is healthy and resumes bounded snapshot delivery when USB data is unavailable.
- Visible USB/Bluetooth transport state plus an honest animated reconnecting status with exact last-observation times.
- All three available Claude limits in the split overview, clearer Codex account facts, and larger usage and reset typography throughout the 800×480 interface.
- Cleaner persistent boot presentation and refreshed documentation screenshots matching the shipped UI.
- A verified FlashThing-compatible firmware archive, SHA-256 checksum, build metadata, and complete generated license archive attached to the GitHub release.

### Install

Download the firmware and checksum from the [v1.1.0 release](https://github.com/petehottelet/ClaudeThing/releases/tag/v1.1.0), then follow the [installation and recovery guide](INSTALL.md). The release archive is built from the tagged public source; credentials and pairing material are provisioned locally after flashing and are not included in the image.

## 1.0.0 — 2026-08-10

ClaudeThing.ai 1.0 is the first public source release of the independent, MIT-licensed dashboard firmware project for Spotify Car Thing.
Expand Down
36 changes: 20 additions & 16 deletions HANDOFF.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Engineering handoff

Updated 2026-08-09. This is the starting point for the next agent; do not reconstruct the project from old setup notes.
Updated 2026-08-11. This is the starting point for the next agent; do not reconstruct the project from old setup notes.

## Current outcome

ClaudeThing development firmware boots on the physical Car Thing, brings up USB ADB, and can display the live dashboard. Codex subscription data has been observed on-device. The dial now emits usable horizontal navigation input. The host collector, authenticated USB tunnel, loopback dashboard HTTP service, Weston, and Chromium kiosk have all run together on hardware.
ClaudeThing development firmware boots on the physical Car Thing and displays live Claude and Codex data when attached directly over USB. The host collector, atomic USB snapshot mirror, loopback dashboard service, Weston, Chromium kiosk, and physical controls have all run together on hardware.

The connected unit is currently on an earlier development image. Its missing BusyBox `httpd` applet and current release UI were repaired with temporary runtime bind mounts for diagnosis. The device is presently serving the release whose gallery has colored source icons and whose YouTube/GA4 dial cycles Daily, Weekly, Monthly, and Year without leaving the selected module. These repairs do not survive a power cycle. Do not describe the unit as persistently fixed until a newly built and explicitly approved image is flashed.
The connected unit is on the last approved development image. It powers from the user's VIA Labs hub, but macOS does not enumerate the Car Thing on that path, so ADB cannot carry data and both feeds become stale. Direct USB remains healthy. The source branch now contains an original authenticated Bluetooth Classic fallback, but that candidate has not been flashed or paired on hardware yet.

The reported five-hour clock error was traced to Chromium using UTC. Source now provisions the host's IANA time zone and formats both the rail clock and same-day token buckets in that zone. This source change also requires a new firmware artifact for persistence.
The current final candidate was built only after the full host verification passed. Its assembled root filesystem was inspected and contains the enabled Bluetooth receiver, temporary pairing service, BlueZ, OpenSSL, the expected protocol markers, and the root-only token path. A rebuild invalidates the artifact details below.

## Source completed in this branch

Expand All @@ -26,29 +26,33 @@ The reported five-hour clock error was traced to Chromium using UTC. Source now
- Preset-3 dashboard gallery with weekly AI usage, configurable YouTube channel views, configurable GA4 active users, and dial-selectable individual/index/total-market charts.
- Reusable SVG line/area chart geometry with Node tests and physical-resolution screenshot coverage.
- Independent Daily/Weekly/Monthly/Year analytics range state; YouTube/GA4 dial input cannot spill into Markets, whose dial remains instrument-specific.
- Automatic transport selection: atomic USB/ADB remains preferred, with a macOS Bluetooth RFCOMM fallback only while USB is unavailable.
- Universal ad-hoc-signed Intel/Apple-silicon Bluetooth sender with an embedded macOS privacy description.
- Device receiver enforcing bonded-link security, one-MiB bounds, HMAC-SHA256 authentication, replay ordering, minimal JSON envelope checks, atomic promotion, and authenticated clock repair.
- Explicit two-minute, USB-triggered Bluetooth pairing window; the device is not left permanently discoverable or pairable.

The YouTube and GA4 screens currently use built-in demonstration series. Their configured display names are provisioned through `--youtube-channel` and `--ga4-property`. Real daily YouTube Analytics is owner-authorized; real GA4 reporting requires an account or service account with property access. Implement those as host-side adapters and transmit only bounded chart series—never OAuth credentials—to the device.

## Required next actions

1. Start from the GitHub branch/PR named in the publishing handoff; check `git status -sb` before editing.
2. Confirm `npm run verify` passes in a clean checkout.
3. Build a fresh development firmware artifact with `npm run firmware:build` because the time-zone/UI and documentation release content changed after the last image build.
4. Deep-inspect the resulting ZIP and WIC: integrity, metadata, GPT labels/geometry, package manifest, `ID=claudething`, original artwork, bundled UI, and BusyBox `httpd` applet.
5. Report the new exact artifact path, byte size, and SHA-256. Obtain fresh explicit approval; no previous hash approval carries forward.
6. Flash only that approved artifact through a compatible local-archive recovery interface.
7. Power cycle, provision with the existing token file, and run `doctor`.
8. Ask the human to verify dashboard pixels, `LIVE`, local clock, Codex values, and hardware controls.
9. Produce one normal Claude Code interaction. Confirm Claude quota state appears and survives a collector restart.
10. Complete reboot, cable reconnect, offline/recovery, control matrix, and soak tests before calling a production image ready.
1. Check `git status -sb` and do not rebuild after the exact candidate has been approved.
2. Obtain approval for the exact artifact path, byte size, and SHA-256 reported in the active task. No previous hash approval carries forward.
3. Flash only that approved artifact through the compatible local-archive recovery interface.
4. Attach directly by USB, provision with the existing token file, and run `doctor`.
5. Run `device-tool.mjs bluetooth-pairing`, then have the human connect to `ClaudeThing Display` in macOS Bluetooth settings.
6. Install/restart the packaged collector so its native helper is active, approving the one-time macOS Bluetooth privacy prompt if shown.
7. Confirm both feeds are live over direct USB, then move device power to the hub and confirm health switches from USB to Bluetooth and both feeds return live.
8. Cold boot from hub power and verify the authenticated Bluetooth timestamp repairs the local clock.
9. Push only after the physical gate passes; use the configured `petehottelet` identity and never introduce commits from another account.

## Hardware acceptance still open

- Permanent boot with the rebuilt BusyBox/UI/time-zone image.
- Correct local clock after a cold boot and provisioning.
- Every button, touch gesture, dial press, and long-press on physical hardware.
- Claude quota seeding and persistence with a real event.
- Automatic tunnel restoration after host sleep and USB reconnect.
- Bluetooth pairing, signed snapshot acceptance, automatic USB-to-Bluetooth failover, and reconnect after reboot.
- Automatic USB restoration after host sleep and reconnect, with Bluetooth returning to standby.
- Offline cache and recovery without manual reload.
- Multi-hour stability and bounded storage writes.
- Recovery restore rehearsal and production-image decision.
Expand All @@ -64,4 +68,4 @@ The YouTube and GA4 screens currently use built-in demonstration series. Their c

## Data behavior to remember

Codex quota is actively collected from the signed-in local service. Claude subscription quota is delivered only when Claude Code emits the relevant status-line rate-limit payload; there is no independent background quota poll in this implementation. The collector now stores the last valid rate-limit observation so a restart no longer discards it. Do not trigger paid activity merely to make the card populate—ask the user to perform a normal interaction.
Codex quota is actively collected from the signed-in local app server. Claude subscription quota uses the signed-in CLI credential through a bounded OAuth poller with automatic refresh and retained last-good state; the status-line hook remains a second source. A one-time macOS Keychain authorization is expected, but denying access suppresses repeated prompts for that collector run. Do not trigger paid activity merely to make a card populate—ask the user to perform a normal interaction.
38 changes: 30 additions & 8 deletions INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,17 @@ The packaged host payload is written to `release/`.
node release/install/install.mjs --host-name desk-mac
```

The installer creates a random pairing token, installs a per-user background collector, preserves compatible existing Claude status-line behavior, creates a commented display-configuration file, and keeps both files across upgrades. Use `--no-start`, `--no-claude-statusline`, `--no-adb`, or `--no-codex-appserver` only when that behavior is intentional.
The installer creates a random pairing token, installs a per-user background collector, preserves compatible existing Claude status-line behavior, creates a commented display-configuration file, and keeps both files across upgrades. A package built on macOS also installs a universal native Bluetooth sender. Use `--no-start`, `--no-claude-statusline`, `--no-adb`, `--no-bluetooth`, or `--no-codex-appserver` only when that behavior is intentional.

When ADB is available, the installer records its absolute executable path so macOS and Windows startup services do not depend on an interactive shell's `PATH`; explicit `--adb-command` and `--adb-serial` selections survive upgrades. The collector automatically restores the USB reverse tunnel. After confirming the attached device reports `ID=claudething`, it also repairs a stale device clock from the host before reconnecting the dashboard.
On macOS, automatic Claude account quotas require access to the existing
`Claude Code-credentials` Keychain item. Choosing **Always Allow** at the first
collector request permits unattended refreshes. Choosing **Deny** leaves the
OAuth lane disabled for that collector run; it will not ask again every polling
interval. Claude CLI status-line observations remain independent of this access.

When ADB is available, the installer records its absolute executable path so macOS and Windows startup services do not depend on an interactive shell's `PATH`; explicit `--adb-command` and `--adb-serial` selections survive upgrades. The collector atomically mirrors the latest bounded snapshot into the device's loopback-only web root and retries after transient USB failures. After confirming the attached device reports `ID=claudething`, it also repairs a stale device clock from the host before updating the dashboard.

On macOS, the collector prefers that USB path and automatically uses Bluetooth only while USB is unavailable. The first Bluetooth attempt may trigger macOS's one-time Bluetooth privacy prompt. Pairing is also one-time; the Bluetooth link reconnects without a button/replug sequence afterward. If exactly one paired device is named `ClaudeThing Display`, it is selected automatically. Install with `--bluetooth-address XX-XX-XX-XX-XX-XX` when multiple displays are paired.

Default token locations:

Expand Down Expand Up @@ -98,7 +106,7 @@ node release/device/device-tool.mjs provision-firmware \
The command:

- refuses non-ClaudeThing firmware;
- writes only `/var/lib/claudething/runtime-config.js`;
- writes `/var/lib/claudething/runtime-config.js` plus a root-only `/var/lib/claudething/pairing.token` used to authenticate Bluetooth snapshots;
- copies the host's IANA time zone (for example `America/Los_Angeles`) unless `--time-zone` overrides it;
- stores the selected YouTube channel and GA4 property display names (1–100 characters each);
- re-establishes `adb reverse tcp:8790 tcp:8790`;
Expand All @@ -107,6 +115,18 @@ The command:

This is application provisioning, not flashing.

### Optional macOS Bluetooth data fallback

Keep the device directly attached by USB for provisioning, then open its two-minute pairing window:

```sh
node release/device/device-tool.mjs bluetooth-pairing
```

Open **System Settings → Bluetooth** and connect to **ClaudeThing Display**. Approve the macOS Bluetooth privacy prompt if it appears. The collector continues to prefer USB automatically. If the device later receives power from a hub that does not pass its USB data connection, the collector sends the same bounded snapshot over the paired Bluetooth link instead; no config switch or recurring pairing step is required. The signed envelope also carries an authenticated host timestamp so a cold boot over a power-only hub can repair the display clock.

Bluetooth does not replace the recovery connection. Use direct USB for firmware flashing, `provision-firmware`, and `doctor`.

Preset 3 opens the additional dashboard gallery. In YouTube and GA4, turn the dial to switch Daily, Weekly, Monthly, and Year ranges; the dial stays within that analytics module. Markets automatically advance through every configured instrument after `rotationSeconds`; turning the dial changes the instrument immediately and restarts that timer. YouTube owner analytics require OAuth access to the channel; GA4 requires a user or service account granted access to the property. Those credentials and refresh tokens belong in future host adapters and must never be copied to the device. Until an authenticated adapter supplies a series, the gallery renders its built-in demonstration data without presenting an authentication control on the 800×480 display.

## 7. Diagnose and accept the hardware
Expand All @@ -115,17 +135,18 @@ Preset 3 opens the additional dashboard gallery. In YouTube and GA4, turn the di
node release/device/device-tool.mjs doctor
```

On ClaudeThing firmware, the doctor independently checks distro identity, the BusyBox HTTP applet, display service, dashboard service, browser service, and dashboard HTTP response. A passing doctor must still be paired with a human screen report.
On ClaudeThing firmware, the doctor independently checks distro identity, the BusyBox HTTP applet, display service, Bluetooth radio and snapshot receiver, dashboard service, browser service, and dashboard HTTP response. A passing doctor must still be paired with a human screen report.

Acceptance checklist:

- ClaudeThing boot artwork transitions to the dashboard.
- Bottom rail reads `LIVE`; Codex data appears when its host source is active.
- The clock matches the provisioned zone and the System screen names that zone.
- Claude quotas appear after a Claude Code terminal-CLI status-line event (the desktop app does not emit them); until then the Claude card shows its token totals with "No quota reported". The last valid quota survives collector restart.
- Claude quotas appear from the signed-in Claude CLI credential without requiring a model prompt. Terminal-CLI status-line events remain a second source for local activity totals, and the last valid quota survives collector restart.
- Dial rotation, dial press, Back, presets, touch, and swipes work without missed or doubled actions.
- Cable removal produces an honestly aged offline view; reconnect restores live data without a page reload.
- Reboot returns to the dashboard and the host restores the USB tunnel.
- With Bluetooth unpaired, cable removal produces an honestly aged offline view; reconnect restores live data without a page reload.
- With Bluetooth paired on macOS, moving power to a non-enumerating hub switches the collector from USB to Bluetooth and returns both feeds to live without re-pairing.
- Reboot returns to the dashboard and the host resumes the best available USB or Bluetooth snapshot path.

## 8. Failure interpretation

Expand All @@ -135,8 +156,9 @@ Acceptance checklist:
| ClaudeThing logo remains indefinitely | Early boot works, but userspace is not ready. Run `doctor`, then inspect failed systemd units. |
| `doctor` says `httpd applet: failed` | The built root filesystem lacks the required BusyBox feature; rebuild, inspect the actual image, and obtain new artifact approval. |
| Services are active but the screen is unchanged | Inspect loopback HTTP and kiosk logs; do not call the deployment successful until pixels change. |
| Dashboard is live but Claude quota is stale or shows "Claude login expired" | Claude quota updates via the CLI's stored login on a five-minute base cadence; provider rate limits trigger bounded Retry-After/exponential backoff, and transient failures retain the last-good observation. If the login token expires, run any `claude` command once to refresh it; the poller recovers automatically. Status-line events remain a second live source during CLI sessions. |
| Dashboard is live but Claude quota is stale or shows "Claude login expired" | Claude quota updates via the CLI's stored login on a five-minute base cadence. The collector refreshes an expiring access token without issuing a model request; provider rate limits trigger bounded Retry-After/exponential backoff, and transient failures retain the last-good observation. Run `claude auth login` only when the refresh grant itself has expired or been revoked. Status-line events remain a second live source during CLI sessions. |
| Clock is UTC | Re-run `provision-firmware`; do not hard-code a numeric offset because daylight saving changes it. |
| USB is unavailable and Bluetooth remains stale | Confirm `ClaudeThing Display` is paired in macOS, approve Bluetooth privacy access, and inspect the collector health object's `bluetooth.lastError`. Direct USB remains required for provisioning and `doctor`. |

## 9. Host uninstall

Expand Down
Loading