# Show HN: Oh my wrist – Garmin alerts for Claude Code and OpenCode

> Source: <https://github.com/yazon/oh-my-wrist>
> Published: 2026-06-06 15:51:50+00:00

**oh-my-wrist displays real-time Claude Code and OpenCode activity on your Garmin smartwatch over Bluetooth Low Energy. See what your AI coding assistant is doing — right on your wrist.**

**Real-time BLE updates**— tool calls, file edits, and session state streamed to your watch** Haptic alerts**— vibration patterns for idle, session done, destructive commands, and agent completion** CLI-styled watch UI**— terminal aesthetic with animated amber spinner, event stack, and per-provider stats** Multi-provider**— supports Claude Code and OpenCode simultaneously** Cross-platform**— runs on Linux, macOS, and Windows** Quiet hours**— suppress vibrations during configurable time windows** Connection ID filter**— optional 0–255 ID keeps nearby users' watches from pairing with each other

```
pip install oh-my-wrist
```

Or with [uv](https://docs.astral.sh/uv/):

```
uv pip install oh-my-wrist
oh-my-wrist install
```

This automatically:

- Patches
**Claude Code** hooks in`~/.claude/settings.json`

- Configures a
**Claude Code statusLine** that streams`/usage`

quota to the watch (chaining any existing statusLine) - Installs the
**OpenCode** TypeScript plugin (when`opencode`

is on PATH) - Registers a background
**system service**(systemd / launchd / Task Scheduler)

To install a single provider only:

```
oh-my-wrist install --provider claude
oh-my-wrist install --provider opencode
```

Install it one of two ways:

- GitHub Releases: download
`oh-my-wrist-prg-<version>.zip`

from the latest release, unzip it, connect your Garmin device over USB, then copy the matching`oh-my-wrist-<device-id>.prg`

file to`/GARMIN/Apps/`

. - Connect IQ Store:
[DOWNLOAD](https://apps.garmin.com/en-US/apps/1fff0ec7-4813-4f94-8d24-3dc9fef0b9df) - Build from source: install Connect IQ SDK 9.1+, then run
`tools/build_garmin.sh release`

and copy the generated device-specific`.prg`

file from`build/garmin/`

to`/GARMIN/Apps/`

. VS Code single-device builds from`garmin/`

write`bin/oh-my-wrist.prg`

.

```
oh-my-wrist start
```

Open the **oh-my-wrist** app on your watch — it connects automatically and updates in real time.

| Platform | Guide |
|---|---|
| 🐧 Linux |
|

[docs/INSTALL_MACOS.md](/yazon/oh-my-wrist/blob/main/docs/INSTALL_MACOS.md)[docs/INSTALL_WINDOWS.md](/yazon/oh-my-wrist/blob/main/docs/INSTALL_WINDOWS.md)Start the daemon in the background (or foreground for debugging):

```
oh-my-wrist start
oh-my-wrist start --foreground
```

Start a Claude Code or OpenCode session — the watch updates automatically.

From a source checkout, run the one-minute diagnostic stream to verify the daemon-to-watch link without starting a real Claude Code or OpenCode session:

```
python tools/check_connection.py
```

Keep the daemon running and the watch app open. You should see history rows change, Claude/OpenCode stats increment, and Claude usage bars move. Useful options:

```
python tools/check_connection.py --duration 30
python tools/check_connection.py --dry-run
python tools/check_connection.py --provider claude
```

Four swipeable views (left/right or UP/DOWN on button watches). History is the
initial view — swipe/press **UP** for the Claude usage screen, **DOWN** for the
per-provider stats screens:

| View | Direction from History | Content |
|---|---|---|
| Claude Usage | UP | `/usage` -style quota bars: session (5h) and week (7d), htop-style |
| History | — (initial) | CLI-style event stack (3 visible rows with animated spinner) |
| Claude Stats | DOWN | Session duration, tool calls, files edited, bash count, idle time |
| OpenCode Stats | DOWN ×2 | Same metrics, isolated from Claude |

The usage screen is Claude-only and shows an empty bar with no percentage when quota data is unavailable (API-key users, or before the first API response in a session).

Press **SELECT/START** on any view to open the app menu. Use **Set id** to
save the same 0–255 connection ID configured on your desktop daemon, then
restart the watch app so the new BLE service UUID is registered cleanly.

| Command | Purpose |
|---|---|
`oh-my-wrist start [-f|--foreground]` |
Start the BLE daemon |
`oh-my-wrist stop` |
Stop the daemon |
`oh-my-wrist status` |
Show daemon PID, service status, hook/plugin state |
`oh-my-wrist install [--provider claude|opencode|both]` |
Configure hooks, plugin, and OS service |
`oh-my-wrist uninstall [--provider …]` |
Remove hooks, plugin, and service |
`oh-my-wrist test [message] [--provider claude|opencode]` |
Send a test message to the daemon |
`oh-my-wrist config [--show|--haptic on|off|--quiet-start HH:MM|--quiet-end HH:MM]` |
View or update configuration |
`oh-my-wrist set-id ID` |
Set BLE connection ID (`0` –`255` ) and queue an update for a running daemon |
`oh-my-wrist opencode install|uninstall|status` |
Manage the OpenCode plugin |
`oh-my-wrist logs [-n LINES]` |
Tail the daemon log |

Settings are stored at `~/.oh-my-wrist/config.json`

and take effect immediately (no restart needed).

```
# View current config
oh-my-wrist config --show

# Toggle haptic alerts
oh-my-wrist config --haptic on
oh-my-wrist config --haptic off

# Set quiet hours (vibrations suppressed)
oh-my-wrist config --quiet-start 22:00 --quiet-end 08:00

# Set BLE connection ID to match the watch menu's Set id value
oh-my-wrist set-id 42
```

| Setting | Default | Description |
|---|---|---|
`haptic_enabled` |
`true` |
Enable/disable all vibration alerts |
`quiet_start` |
`22:00` |
Start of quiet window (HH:MM) |
`quiet_end` |
`08:00` |
End of quiet window (HH:MM) |
`connection_id` |
`0` |
BLE scan filter ID. The watch only connects to daemons with the same ID. |

The connection ID is not authentication; it is a collision reducer for rooms
where multiple people run oh-my-wrist. ID `0`

is the default and preserves the
original BLE service UUID.

Requires a Garmin watch with Connect IQ Generic BLE support (API level alone is not enough):

Manifest product IDs: `approachs50`

, `approachs7042mm`

, `approachs7047mm`

, `d2air`

, `d2airx10`

, `d2mach1`

, `d2mach2`

, `d2mach2pro`

, `descentg1`

, `descentg2`

, `descentmk2`

, `descentmk2s`

, `descentmk343mm`

, `descentmk351mm`

, `edge1030`

, `edge1030plus`

, `edge1040`

, `edge1050`

, `edge530`

, `edge540`

, `edge550`

, `edge830`

, `edge840`

, `edge850`

, `edgeexplore`

, `edgeexplore2`

, `edgemtb`

, `enduro`

, `enduro3`

, `epix2`

, `epix2pro42mm`

, `epix2pro47mm`

, `epix2pro51mm`

, `etrextouch`

, `fenix5plus`

, `fenix5splus`

, `fenix5xplus`

, `fenix6`

, `fenix6pro`

, `fenix6s`

, `fenix6spro`

, `fenix6xpro`

, `fenix7`

, `fenix7pro`

, `fenix7pronowifi`

, `fenix7s`

, `fenix7spro`

, `fenix7x`

, `fenix7xpro`

, `fenix7xpronowifi`

, `fenix843mm`

, `fenix847mm`

, `fenix8pro47mm`

, `fenix8solar47mm`

, `fenix8solar51mm`

, `fenixe`

, `fr165`

, `fr165m`

, `fr170`

, `fr170m`

, `fr245`

, `fr245m`

, `fr255`

, `fr255m`

, `fr255s`

, `fr255sm`

, `fr265`

, `fr265s`

, `fr55`

, `fr57042mm`

, `fr57047mm`

, `fr645m`

, `fr70`

, `fr745`

, `fr945`

, `fr945lte`

, `fr955`

, `fr965`

, `fr970`

, `gpsmap66`

, `gpsmap67`

, `gpsmaph1`

, `instinct2`

, `instinct2s`

, `instinct2x`

, `instinct3amoled45mm`

, `instinct3amoled50mm`

, `instinct3solar45mm`

, `instinctcrossover`

, `instinctcrossoveramoled`

, `instincte40mm`

, `instincte45mm`

, `legacyherocaptainmarvel`

, `legacyherofirstavenger`

, `legacysagadarthvader`

, `legacysagarey`

, `marq2`

, `marq2aviator`

, `marqadventurer`

, `marqathlete`

, `marqaviator`

, `marqcaptain`

, `marqcommander`

, `marqdriver`

, `marqexpedition`

, `marqgolfer`

, `montana7xx`

, `venu`

, `venu2`

, `venu2plus`

, `venu2s`

, `venu3`

, `venu3s`

, `venu441mm`

, `venu445mm`

, `venud`

, `venusq2m`

, `venusqm`

, `venux1`

, `vivoactive3m`

, `vivoactive3mlte`

, `vivoactive4`

, `vivoactive4s`

, `vivoactive5`

, `vivoactive6`

.

| Problem | Solution |
|---|---|
| Watch can't find daemon | Run `oh-my-wrist status` — verify daemon is advertising and that desktop/watch connection IDs match. Restart with `oh-my-wrist stop && oh-my-wrist start` . From a source checkout, run `python tools/check_connection.py` with the watch app open to exercise live HISTORY, stats, and usage updates. |
| Garmin app does not connect to daemon on PC | Make sure your Bluetooth adapter is supported. Some adapters or OS settings, such as Windows random MAC behavior, can prevent stable BLE connections. If problems persist, macOS or Linux is preferred. |
| Garmin app does not start on watch | Create an empty `/GARMIN/Apps/Logs/oh-my-wrist-YOUR_WATCH_ID.log (e.g oh-my-wrist-fenix7x.log)` file on the device, run the app, wait for the issue to occur, reconnect the device to your PC, download log file, then create a GitHub issue with the log for debugging. |
| Hook not firing | Run `oh-my-wrist status` — confirm hooks in `~/.claude/settings.json` . Re-run `oh-my-wrist install` . |
| OpenCode not updating | Check plugin: `oh-my-wrist opencode status` . Re-install: `oh-my-wrist opencode install` . |
| BLE permission errors | See your platform guide:
|

`oh-my-wrist start --foreground`

to see the error. Check `oh-my-wrist logs`

.

```
oh-my-wrist uninstall
pip uninstall oh-my-wrist
```

To remove the watch app, delete it via Garmin Express or the Connect IQ app on your phone.

Contributions are more than welcome. Please contribute! See [CONTRIBUTING.md](/yazon/oh-my-wrist/blob/main/docs/CONTRIBUTING.md) for guidelines.

```
# pip
pip install -e ".[dev]"
pytest tests/

# or uv
uv venv && uv pip install -e ".[dev]"
uv run pytest

oh-my-wrist start --foreground
```

This project is licensed under the BSD 3-Clause License. See the [LICENSE](/yazon/oh-my-wrist/blob/main/LICENSE) file for details.
