pebble-mcp 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pebble_mcp-0.1.0/.gitignore +5 -0
- pebble_mcp-0.1.0/EMU-TESTING-SPEC.md +84 -0
- pebble_mcp-0.1.0/LAUNCH.md +171 -0
- pebble_mcp-0.1.0/LICENSE +21 -0
- pebble_mcp-0.1.0/PKG-INFO +133 -0
- pebble_mcp-0.1.0/README.md +110 -0
- pebble_mcp-0.1.0/TOOL-SURFACE.md +98 -0
- pebble_mcp-0.1.0/USABILITY-FINDINGS.md +73 -0
- pebble_mcp-0.1.0/assets/palettes/pebble_colors_64.act +0 -0
- pebble_mcp-0.1.0/assets/palettes/pebble_colors_64.gif +0 -0
- pebble_mcp-0.1.0/assets/palettes/pebble_colors_64.pal +0 -0
- pebble_mcp-0.1.0/assets/palettes/pebble_colors_sunlight.aseprite +0 -0
- pebble_mcp-0.1.0/assets/palettes/pebble_colors_uncorrected.aseprite +0 -0
- pebble_mcp-0.1.0/community.html +445 -0
- pebble_mcp-0.1.0/docs/demo.gif +0 -0
- pebble_mcp-0.1.0/pebble-mcp-requirements.md +130 -0
- pebble_mcp-0.1.0/pebble_mcp/__init__.py +3 -0
- pebble_mcp-0.1.0/pebble_mcp/capabilities.py +52 -0
- pebble_mcp-0.1.0/pebble_mcp/devloop.py +449 -0
- pebble_mcp-0.1.0/pebble_mcp/examples/flows/coach-logger.flow +27 -0
- pebble_mcp-0.1.0/pebble_mcp/examples/flows/coach-watchface.flow +8 -0
- pebble_mcp-0.1.0/pebble_mcp/examples/flows/coach-workout.flow +43 -0
- pebble_mcp-0.1.0/pebble_mcp/examples/flows/hail-watch.flow +13 -0
- pebble_mcp-0.1.0/pebble_mcp/flow.py +569 -0
- pebble_mcp-0.1.0/pebble_mcp/fonts.py +385 -0
- pebble_mcp-0.1.0/pebble_mcp/images.py +512 -0
- pebble_mcp-0.1.0/pebble_mcp/palette.py +443 -0
- pebble_mcp-0.1.0/pebble_mcp/pdc.py +653 -0
- pebble_mcp-0.1.0/pebble_mcp/platforms.py +50 -0
- pebble_mcp-0.1.0/pebble_mcp/resources.py +272 -0
- pebble_mcp-0.1.0/pebble_mcp/server.py +50 -0
- pebble_mcp-0.1.0/pebble_mcp/store.py +637 -0
- pebble_mcp-0.1.0/pebble_mcp/tools_design.py +207 -0
- pebble_mcp-0.1.0/pebble_mcp/tools_devloop.py +86 -0
- pebble_mcp-0.1.0/pebble_mcp/tools_flow.py +391 -0
- pebble_mcp-0.1.0/pebble_mcp/tools_fonts.py +94 -0
- pebble_mcp-0.1.0/pebble_mcp/tools_store.py +491 -0
- pebble_mcp-0.1.0/pyproject.toml +72 -0
- pebble_mcp-0.1.0/roadmap.md +81 -0
- pebble_mcp-0.1.0/scripts/gen_palette.py +110 -0
- pebble_mcp-0.1.0/tests/__init__.py +0 -0
- pebble_mcp-0.1.0/tests/fixtures/store/app_by_id_hubble.json +217 -0
- pebble_mcp-0.1.0/tests/fixtures/store/app_not_found.json +1 -0
- pebble_mcp-0.1.0/tests/fixtures/store/apps_bulk.json +337 -0
- pebble_mcp-0.1.0/tests/fixtures/store/apps_bulk_missing.json +1 -0
- pebble_mcp-0.1.0/tests/fixtures/store/category_faces.json +461 -0
- pebble_mcp-0.1.0/tests/fixtures/store/collection_all_apps.json +342 -0
- pebble_mcp-0.1.0/tests/fixtures/store/collection_most_loved_watchfaces.json +505 -0
- pebble_mcp-0.1.0/tests/fixtures/store/collection_not_found.json +1 -0
- pebble_mcp-0.1.0/tests/fixtures/store/developer_apps.json +512 -0
- pebble_mcp-0.1.0/tests/fixtures/store/invalid_app_type.json +1 -0
- pebble_mcp-0.1.0/tests/test_capabilities.py +59 -0
- pebble_mcp-0.1.0/tests/test_degradation.py +232 -0
- pebble_mcp-0.1.0/tests/test_devloop.py +389 -0
- pebble_mcp-0.1.0/tests/test_flow.py +468 -0
- pebble_mcp-0.1.0/tests/test_fonts.py +275 -0
- pebble_mcp-0.1.0/tests/test_images.py +337 -0
- pebble_mcp-0.1.0/tests/test_palette.py +355 -0
- pebble_mcp-0.1.0/tests/test_pdc.py +313 -0
- pebble_mcp-0.1.0/tests/test_platforms.py +34 -0
- pebble_mcp-0.1.0/tests/test_resources.py +126 -0
- pebble_mcp-0.1.0/tests/test_server.py +47 -0
- pebble_mcp-0.1.0/tests/test_store.py +442 -0
- pebble_mcp-0.1.0/tests/test_tools_design.py +185 -0
- pebble_mcp-0.1.0/tests/test_tools_devloop.py +60 -0
- pebble_mcp-0.1.0/tests/test_tools_flow.py +326 -0
- pebble_mcp-0.1.0/tests/test_tools_fonts.py +89 -0
- pebble_mcp-0.1.0/tests/test_tools_store.py +491 -0
- pebble_mcp-0.1.0/uv.lock +781 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# pebble-mcp — Emulator boot / manage / test-app tool spec
|
|
2
|
+
|
|
3
|
+
What it takes to launch and drive a Pebble app in the emery emulator, distilled
|
|
4
|
+
from doing it by hand across a full app build. This is the contract for the
|
|
5
|
+
Tier-3 dev-loop tools that let an agent boot the emulator, install apps, drive
|
|
6
|
+
them, and see the result. Honors TOOL-SURFACE.md.
|
|
7
|
+
|
|
8
|
+
> **STATUS — implemented (2026-08-01).** The wiring "gap" this spec describes is
|
|
9
|
+
> closed: all Tier-3 tools listed below are registered on the server
|
|
10
|
+
> (`pebble_build`, `pebble_install`, `emu_start`, `emu_stop`, `emu_screenshot`,
|
|
11
|
+
> `emu_input`, `emu_logs`, `flow_run`, `flow_validate`). The "Operational
|
|
12
|
+
> knowledge to bake in" folklore is now infrastructure: button-press pacing
|
|
13
|
+
> (`BUTTON_SETTLE_SEC`) and post-install settle (`POST_INSTALL_SETTLE_SEC`) are
|
|
14
|
+
> baked into the flow driver and `emu_input`, so correctness no longer depends
|
|
15
|
+
> on a flow author inserting waits. Kept as the design record. The two items
|
|
16
|
+
> still deliberately unimplemented — auto kill+wipe recovery on *standalone* emu
|
|
17
|
+
> tools (they surface a clear hint instead, to avoid destroying state the agent
|
|
18
|
+
> may want), and hard serialization of the single emulator — are noted inline.
|
|
19
|
+
|
|
20
|
+
## Current state (what exists vs. the gap)
|
|
21
|
+
- `devloop.py` LIBRARY already has: `build()`, `install()` (kill+wipe-first),
|
|
22
|
+
`emu_start()`, `emu_stop(wipe=)`, `logs_capture()`, plus GCC-diagnostic parsing
|
|
23
|
+
and WEDGE_MARKERS.
|
|
24
|
+
- `flow.py` LIBRARY already has the full driver: parse a flow spec, install, and
|
|
25
|
+
step through button/tap/wait/screenshot with wedge recovery.
|
|
26
|
+
- MISSING in the library: `emu_screenshot()` and `emu_input()` (button/tap).
|
|
27
|
+
- MISSING on the server: NONE of these are registered as MCP tools yet — the
|
|
28
|
+
server only exposes `capabilities()` + `pebble://platforms`. Wiring is the gap.
|
|
29
|
+
|
|
30
|
+
## The launch/test loop (what "launch an app" actually requires)
|
|
31
|
+
1. **Gate** on `capabilities().tier3_devloop` (pebble CLI on PATH, ~/.local/bin).
|
|
32
|
+
If absent, fail fast with the install hint — never a traceback.
|
|
33
|
+
2. **Build** (optional): `pebble build` in a project dir → `build/<name>.pbw`.
|
|
34
|
+
Return structured diagnostics (already implemented).
|
|
35
|
+
3. **Clean install**: `pebble kill` → `pebble wipe` → sleep ~3s →
|
|
36
|
+
`pebble install --emulator emery <pbw>`. The kill+wipe BEFORE install is
|
|
37
|
+
load-bearing — a persisted active app otherwise stays foreground and the new
|
|
38
|
+
app never launches. (Already baked into `install()`.)
|
|
39
|
+
4. **Settle**: sleep ~6s after install for the app to fully render (some apps
|
|
40
|
+
load resources / animate).
|
|
41
|
+
5. **Observe**: screenshot the current screen (native 200x228 PNG).
|
|
42
|
+
6. **Drive**: send button/tap input, screenshot again, repeat.
|
|
43
|
+
|
|
44
|
+
## Tools to add / wire (small surface, per TOOL-SURFACE.md)
|
|
45
|
+
- `pebble_build(project_dir)` — wire `build()`. Structured errors.
|
|
46
|
+
- `pebble_install(pbw_or_project)` — wire `install()` (kill+wipe baked in).
|
|
47
|
+
- `emu_start()` / `emu_stop(wipe?)` — wire existing.
|
|
48
|
+
- **`emu_screenshot()`** — NEW. `pebble screenshot --no-open <tmp>`; verify the
|
|
49
|
+
file is non-empty; return as an **MCP image content block** (agent sees it)
|
|
50
|
+
PLUS the saved path. This is the key tool for "testing".
|
|
51
|
+
- **`emu_input(action, button?, duration_ms?)`** — NEW. Wraps
|
|
52
|
+
`pebble emu-button click <back|up|select|down>` (+ `--duration` for long-press)
|
|
53
|
+
and `pebble emu-tap`. Validate button against the enum.
|
|
54
|
+
- `emu_logs(...)` — wire `logs_capture()` (bounded).
|
|
55
|
+
- **`flow_run(flow_or_project)`** — wire `flow.py`'s `run_flow()`: install + step
|
|
56
|
+
a scripted flow, returning shots as image content + a contact sheet. This is
|
|
57
|
+
the one-call "test an app" payoff (roadmap C3).
|
|
58
|
+
|
|
59
|
+
## Operational knowledge to BAKE IN (folklore → infrastructure)
|
|
60
|
+
- **Button-press pacing:** the emery emulator DROPS TO THE WATCHFACE / desyncs on
|
|
61
|
+
rapid presses. `emu_input` must pace internally (~0.2-0.3s between presses) and
|
|
62
|
+
flow_run must space steps — never fire presses back-to-back.
|
|
63
|
+
- **Wedge recovery:** output containing any WEDGE_MARKER ("not responding",
|
|
64
|
+
"no emulator", "failed to connect", "connection refused", "timed out") → the
|
|
65
|
+
emulator is wedged; run kill+wipe and retry the operation ONCE, then fail with
|
|
66
|
+
a clear message. (flow.py already does this; emu tools should too.)
|
|
67
|
+
- **Screenshot capture is racy for animations:** a sub-second animation can't be
|
|
68
|
+
reliably sampled through screenshot latency. For animated content, expose an
|
|
69
|
+
optional per-frame step (or document capturing a slowed build) rather than
|
|
70
|
+
promising to catch a transient.
|
|
71
|
+
- **Single emulator:** there is ONE QEMU instance. Serialize emulator-touching
|
|
72
|
+
tools (queue or fail clearly); never let two installs/screenshots interleave.
|
|
73
|
+
- **Window vs headless:** launching surfaces a QEMU window on a local display
|
|
74
|
+
when one is available (the user can interact directly), and still screenshots
|
|
75
|
+
fine when headless. Don't assume either.
|
|
76
|
+
- **Timings:** ~3s after wipe, ~6s after install, before the first screenshot.
|
|
77
|
+
|
|
78
|
+
## Acceptance
|
|
79
|
+
A fresh agent with the server configured can, in ≤ a few calls:
|
|
80
|
+
1. build a project and report an actionable compile error,
|
|
81
|
+
2. install it and SEE the launch screen (image content),
|
|
82
|
+
3. press a button and see the screen change,
|
|
83
|
+
4. run a multi-step flow and get back the screenshots + contact sheet —
|
|
84
|
+
each degrading gracefully (named gate error) when the pebble CLI is absent.
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Launch checklist — get pebble-mcp on PyPI
|
|
2
|
+
|
|
3
|
+
Your follow-along working doc. The community page's TODO is the public
|
|
4
|
+
overview; **this** is the granular execution list. Check boxes as you go.
|
|
5
|
+
|
|
6
|
+
**Legend**
|
|
7
|
+
- 🟢 **Claude does it** — just say "go" and I'll do it (you review after).
|
|
8
|
+
- 🔵 **You do it** — needs your account, credentials, or a decision only you can make.
|
|
9
|
+
- ⏱ rough time.
|
|
10
|
+
|
|
11
|
+
**This weekend's goal: get fully publish-READY and kick the tires — not publish.**
|
|
12
|
+
Publishing (Steps 5–6) waits until you've tested enough to feel confident. So the
|
|
13
|
+
plan is: I make everything publish-ready *in place* (no repo carve-out yet, so your
|
|
14
|
+
testing environment stays intact), and you spend the weekend actually using it
|
|
15
|
+
(see "Kick the tires" below). Pull the trigger on PyPI whenever you're ready.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Step 0 — Decisions · 🔵 · ✅ DONE
|
|
20
|
+
|
|
21
|
+
- [x] **License: MIT.**
|
|
22
|
+
- [x] **Package name: `pebble-mcp`** (confirmed available on PyPI).
|
|
23
|
+
- [x] **Authorship: Daniel Bonomo <d.z.bonomo@gmail.com>.**
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Kick the tires this weekend — get a feel for it · 🔵 · ⏱ as long as it's fun
|
|
28
|
+
|
|
29
|
+
Do this from your **second terminal** (the MCP-connected one). **Restart that
|
|
30
|
+
instance first** so it picks up all 23 tools — it connected when the server had
|
|
31
|
+
only one. Then just talk to Claude naturally; these are prompts to try, grouped by
|
|
32
|
+
tier. Jot down anything that feels wrong — that's exactly the feedback Phase 3 needs.
|
|
33
|
+
|
|
34
|
+
**Tier 1 · Store (works even with no emulator):**
|
|
35
|
+
- [ ] "Search the Pebble store for weather watchfaces that run on my Time 2."
|
|
36
|
+
- [ ] "Show me the most-loved watchfaces — which ones include weather?"
|
|
37
|
+
- [ ] "Compare Hubble against another astronomy app: hearts, platforms, size."
|
|
38
|
+
- [ ] "Download the Hubble pbw so we can try it."
|
|
39
|
+
|
|
40
|
+
**Tier 2 · Design (the fun, visual one — works anywhere, even claude.ai):**
|
|
41
|
+
- [ ] "What's the nearest Pebble color to #3b82f6? Is it legible on black?"
|
|
42
|
+
- [ ] "Quantize this image to the Pebble 64-color palette." *(drop in a logo/photo)*
|
|
43
|
+
- [ ] "Prep this image as a 25×25 launcher menu icon."
|
|
44
|
+
- [ ] "Make me a swatch of the coach app's accent colors."
|
|
45
|
+
- [ ] "Which font for a big clock numeral? Give me the characterRegex for `15:37`."
|
|
46
|
+
- [ ] "Convert this SVG to PDC." *(try one with a curve — watch it refuse honestly)*
|
|
47
|
+
|
|
48
|
+
**Tier 3 · Dev loop (uses your local `pebble` CLI):**
|
|
49
|
+
- [ ] "Run a screenshot flow on hail-watch and show me the frames."
|
|
50
|
+
- [ ] "Build coach-workout and tell me about any warnings."
|
|
51
|
+
- [ ] "Install Hubble in the emulator and screenshot the moon screen."
|
|
52
|
+
- [ ] "Screenshot whatever's on the emulator right now."
|
|
53
|
+
|
|
54
|
+
**Resources (reference data the model can pull):**
|
|
55
|
+
- [ ] "Read the pebble://colors resource — what are the role colors?"
|
|
56
|
+
- [ ] "Show me the wire-format conventions from pebble-mcp."
|
|
57
|
+
|
|
58
|
+
**Try to break it / find rough edges (this is the valuable part):**
|
|
59
|
+
- [ ] Give a vague request ("make my watchface art Pebble-ready") and see if Claude
|
|
60
|
+
picks the right tool from the descriptions alone.
|
|
61
|
+
- [ ] Feed junk: a corrupt image, a nonsense color, a 10,000-character search.
|
|
62
|
+
- [ ] Note **any** moment Claude picks the wrong tool, an error message confuses
|
|
63
|
+
you, or you expected a tool that doesn't exist. Add it to
|
|
64
|
+
`pebble-mcp/TOOL-SURFACE.md` (your other terminal already started that doc).
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Step 0-decisions are done. The rest below is the ready-then-publish path.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Step 1 — Make it publish-ready in place (decouple from the monorepo) · 🟢 · IN PROGRESS
|
|
73
|
+
|
|
74
|
+
Doing this now, *without* carving out a separate repo yet — so your testing
|
|
75
|
+
environment stays a single tree all weekend. The physical carve-out + GitHub push
|
|
76
|
+
becomes a mechanical step at publish time (Step 5b), because after this the
|
|
77
|
+
package has no monorepo coupling left.
|
|
78
|
+
|
|
79
|
+
- [ ] 🟢 **Break the monorepo coupling:** `tests/test_flow.py` reads the flow
|
|
80
|
+
specs from `../tools/flows/`. Vendor those `.flow` files into the package
|
|
81
|
+
(`pebble_mcp/examples/flows/`) and repoint the test — verified by running
|
|
82
|
+
pytest on a copy of the package alone, outside the repo.
|
|
83
|
+
- [ ] 🟢 Genericize the last coach/hail mentions in `tools_flow.py` / `devloop.py`
|
|
84
|
+
docstrings (public repo hygiene; safety wording stays).
|
|
85
|
+
- [ ] 🟢 Confirm no personal leaks (`btf4e`, `dbonomo`, `/media/`, `trainer`).
|
|
86
|
+
|
|
87
|
+
### Step 5b (deferred to publish day) — carve out & push · 🔵
|
|
88
|
+
- [ ] Create the empty **public** GitHub repo `pebble-mcp` (no README/license — we
|
|
89
|
+
bring our own). Copy the now-self-contained `pebble-mcp/` dir into it; I'll
|
|
90
|
+
give you the exact `git init` + `remote add` + `push`.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Step 2 — Packaging metadata · 🟢 · ⏱ 20 min
|
|
95
|
+
|
|
96
|
+
- [ ] 🟢 Add the `LICENSE` file (once you've picked one in Step 0).
|
|
97
|
+
- [ ] 🟢 Fill out `pyproject.toml`: `description`, `authors`, `license`,
|
|
98
|
+
`keywords`, `classifiers` (Python 3.13, MCP, License, OS), `readme`,
|
|
99
|
+
`requires-python = ">=3.13"`, and `[project.urls]` (Homepage / Repository
|
|
100
|
+
/ Issues).
|
|
101
|
+
- [ ] 🟢 Verify the build: `uv build` produces an sdist + wheel, and
|
|
102
|
+
`uvx --from ./dist/pebble_mcp-*.whl pebble-mcp` boots from the wheel alone.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Step 3 — README · 🟢 · ⏱ 20 min
|
|
107
|
+
|
|
108
|
+
- [ ] 🟢 Write `README.md`: one-paragraph what-it-is, the four tiers, the `uvx`
|
|
109
|
+
config snippet, a quickstart per host (Claude Code / Desktop), the
|
|
110
|
+
`capabilities()` note, license, and the "not affiliated with Core
|
|
111
|
+
Devices/Rebble" line.
|
|
112
|
+
- [ ] 🟢 Embed the demo GIF from Step 4 at the top.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Step 4 — The demo GIF · 🟢 (needs the local emulator) · ⏱ 20 min
|
|
117
|
+
|
|
118
|
+
- [ ] 🟢 Run the Hubble store→screenshot flow through `flow_run` and assemble the
|
|
119
|
+
captured frames into an animated GIF. **This is the single most convincing
|
|
120
|
+
launch artifact** — it's what earns the retweet.
|
|
121
|
+
- [ ] 🟢 Drop it in the repo (`docs/demo.gif`) and wire it into the README + the
|
|
122
|
+
community page.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Step 5 — Release workflow, trusted publishing (no tokens) · 🟢 write / 🔵 configure · ⏱ 15 min
|
|
127
|
+
|
|
128
|
+
- [ ] 🟢 Add `.github/workflows/release.yml` — builds on a `v*` tag and publishes
|
|
129
|
+
via `pypa/gh-action-pypi-publish` with `permissions: id-token: write` (OIDC,
|
|
130
|
+
so **no API token to store**).
|
|
131
|
+
- [ ] 🔵 On PyPI: create an account if needed, verify email, **enable 2FA**.
|
|
132
|
+
- [ ] 🔵 On PyPI → your account → *Publishing* → **Add a pending publisher** with:
|
|
133
|
+
PyPI project name `pebble-mcp`, owner `<your-github-user>`, repository
|
|
134
|
+
`pebble-mcp`, workflow `release.yml`, environment (leave blank or `pypi`).
|
|
135
|
+
*(This authorizes GitHub to publish without a password.)*
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Step 6 — Ship it · 🔵 tag / 🟢 verify · ⏱ 10 min
|
|
140
|
+
|
|
141
|
+
- [ ] 🔵 `git tag v0.1.0 && git push origin v0.1.0` — this triggers the workflow,
|
|
142
|
+
which publishes to PyPI.
|
|
143
|
+
- [ ] 🟢 Verify the real thing: `uvx pebble-mcp` from a clean directory boots and
|
|
144
|
+
`capabilities()` responds. (I'll walk the install as a stranger would.)
|
|
145
|
+
- [ ] 🟢 Flip the community page + config snippets from "not yet on PyPI / local
|
|
146
|
+
checkout" to the live `uvx pebble-mcp` one-liner.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Step 7 — Announce (in this order) · 🟢 draft / 🔵 post · ⏱ 30 min
|
|
151
|
+
|
|
152
|
+
- [ ] 🔵 **Rebble Showcase forum thread** (forum.rebble.io) — the durable home
|
|
153
|
+
base. 🟢 I'll draft it (lead with the SVG→PDC + 64-color quantization hook).
|
|
154
|
+
- [ ] 🔵 **X, tagging @ericmigi** — highest leverage; he's publicly wanted exactly
|
|
155
|
+
this. 🟢 I'll draft the post; the GIF is the payload.
|
|
156
|
+
- [ ] 🔵 **Discord `#app-dev`** (rebble.io/discord) — cross-post the forum thread.
|
|
157
|
+
🟢 I'll draft the message.
|
|
158
|
+
- [ ] 🔵 **GitHub `pebble/community-resources` PR** — adds pebble-mcp to the
|
|
159
|
+
official tools listing (permanent, low-effort). 🟢 I'll prep the branch +
|
|
160
|
+
the markdown file; you submit the PR.
|
|
161
|
+
- [ ] Secondary / opportunistic: Show HN, r/pebble (check sidebar rules first),
|
|
162
|
+
and a demo slot if the monthly dev hangout lands near launch.
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
### Status
|
|
167
|
+
Step 0 decisions are locked. Steps 1–4 (decouple, license, packaging, README, demo
|
|
168
|
+
GIF) are being prepared **now, in place** — so by the time you're done testing,
|
|
169
|
+
publishing is just: create the public repo (5b), configure the PyPI trusted
|
|
170
|
+
publisher (5), and push a `v0.1.0` tag (6). No rush — pull that trigger only once
|
|
171
|
+
the weekend's testing has you confident.
|
pebble_mcp-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Daniel Bonomo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pebble-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for the Pebble smartwatch ecosystem: appstore search, a 64-color design toolkit, and the local dev loop (build, emulator, screenshot flows).
|
|
5
|
+
Project-URL: Homepage, https://github.com/dzbonomo/pebble-mcp
|
|
6
|
+
Project-URL: Repository, https://github.com/dzbonomo/pebble-mcp
|
|
7
|
+
Project-URL: Issues, https://github.com/dzbonomo/pebble-mcp/issues
|
|
8
|
+
Author-email: Daniel Bonomo <d.z.bonomo@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: mcp,model-context-protocol,pebble,rebble,watchface
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Software Development
|
|
17
|
+
Requires-Python: >=3.13
|
|
18
|
+
Requires-Dist: mcp>=1.9.0
|
|
19
|
+
Requires-Dist: pillow>=10.0.0
|
|
20
|
+
Provides-Extra: design
|
|
21
|
+
Requires-Dist: pillow>=10.0.0; extra == 'design'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# pebble-mcp
|
|
25
|
+
|
|
26
|
+
<p align="center">
|
|
27
|
+
<img src="docs/demo.gif" alt="pebble-mcp downloading a store app and screenshotting it in the emulator" width="200">
|
|
28
|
+
</p>
|
|
29
|
+
|
|
30
|
+
An [MCP](https://modelcontextprotocol.io) server for the Pebble smartwatch
|
|
31
|
+
ecosystem: appstore search, a 64-color design toolkit, and the local dev loop
|
|
32
|
+
(build → emulator → screenshot flows) exposed as typed tools usable from any
|
|
33
|
+
MCP host — Claude Code, Claude Desktop, claude.ai, or any other agent — not
|
|
34
|
+
just a shell. The demo above is a single `flow_run`: it installs a real
|
|
35
|
+
appstore app (Hubble) onto the emery emulator, drives the buttons, and captures
|
|
36
|
+
each screen — all through MCP.
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
Once published to PyPI, no checkout is needed — `uvx` runs it on demand. Add
|
|
41
|
+
this to your MCP host's config (e.g. a Claude Code `.mcp.json`):
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"mcpServers": {
|
|
46
|
+
"pebble-mcp": {
|
|
47
|
+
"command": "uvx",
|
|
48
|
+
"args": ["pebble-mcp"]
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
To run from a local checkout instead (development, or before the PyPI release):
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"mcpServers": {
|
|
59
|
+
"pebble-mcp": {
|
|
60
|
+
"command": "uv",
|
|
61
|
+
"args": ["run", "--directory", "/absolute/path/to/pebble-mcp", "pebble-mcp"]
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Quickstart (Claude Code)
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# from PyPI (once published)
|
|
71
|
+
claude mcp add pebble-mcp -- uvx pebble-mcp
|
|
72
|
+
|
|
73
|
+
# or from a local checkout
|
|
74
|
+
claude mcp add pebble-mcp -- uv run --directory /absolute/path/to/pebble-mcp pebble-mcp
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Then, in a session, ask the agent to call `capabilities()` to see which tiers
|
|
78
|
+
are live, or `store_search("weather")` to hit the appstore immediately.
|
|
79
|
+
|
|
80
|
+
## Capability tiers
|
|
81
|
+
|
|
82
|
+
The server registers tools in four tiers and probes the environment at startup,
|
|
83
|
+
so an agent only ever sees the tools it can actually run.
|
|
84
|
+
|
|
85
|
+
- **Tier 1 — Appstore** (pure HTTPS, no auth, always on):
|
|
86
|
+
`store_search`, `store_app`, `store_collection`, `store_category`,
|
|
87
|
+
`store_developer`, `store_compare`, `store_download_pbw`.
|
|
88
|
+
- **Tier 2 — Design toolkit** (pure Python + Pillow):
|
|
89
|
+
`color_nearest`, `palette_swatch`, `image_quantize`, `image_prep`,
|
|
90
|
+
`font_plan`, `pdc_convert`. Pillow is a core dependency, so this tier is
|
|
91
|
+
normally always on; `capabilities()` still probes for it and degrades
|
|
92
|
+
gracefully if it is somehow absent.
|
|
93
|
+
- **Tier 3 — Dev loop** (requires the `pebble` CLI on `PATH`):
|
|
94
|
+
`pebble_build`, `pebble_install`, `emu_start`, `emu_stop`,
|
|
95
|
+
`emu_screenshot`, `emu_input`, `emu_logs`, `flow_validate`, and the crown
|
|
96
|
+
jewel `flow_run` — run a gallery-style flow spec and get every screenshot
|
|
97
|
+
back as MCP images. These tools register only when `pebble` is found.
|
|
98
|
+
- **Tier 4 — Authenticated** (requires `PEBBLE_API_TOKEN`; off by default):
|
|
99
|
+
reserved for token-gated, network-mutating operations (heart an app,
|
|
100
|
+
publish, timeline pins). Detected by `capabilities()`; opt-in only.
|
|
101
|
+
|
|
102
|
+
Read-only reference is also exposed as MCP resources: `pebble://colors`,
|
|
103
|
+
`pebble://fonts`, `pebble://platforms`, `pebble://wire-conventions`.
|
|
104
|
+
|
|
105
|
+
### The `capabilities()` tier probe
|
|
106
|
+
|
|
107
|
+
Call the `capabilities()` tool first. It reports which tiers are live in the
|
|
108
|
+
running environment (`tier1_appstore`, `tier2_design`, `tier3_devloop`,
|
|
109
|
+
`tier4_auth`) so an agent can plan its work instead of calling a tool that
|
|
110
|
+
isn't wired up. Tier 1 is always true; the rest reflect Pillow, the `pebble`
|
|
111
|
+
CLI, and `PEBBLE_API_TOKEN` respectively.
|
|
112
|
+
|
|
113
|
+
## Development
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
uv sync # install deps (+ dev group: pytest, ruff)
|
|
117
|
+
uv run pytest # run the test suite
|
|
118
|
+
uv run ruff check # lint
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The `pebble_mcp/examples/flows/` directory holds sample flow specs (used by the
|
|
122
|
+
tests and as living documentation of the flow format, including the
|
|
123
|
+
`# SAFETY RULES (live-write hazards — DO NOT TRIGGER):` convention that flow
|
|
124
|
+
authors should follow for any app with server-mutating screens).
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
MIT — see [LICENSE](LICENSE). Copyright (c) 2026 Daniel Bonomo.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
*Not affiliated with, endorsed by, or sponsored by Core Devices or Rebble.
|
|
133
|
+
"Pebble" and related marks belong to their respective owners.*
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# pebble-mcp
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="docs/demo.gif" alt="pebble-mcp downloading a store app and screenshotting it in the emulator" width="200">
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
An [MCP](https://modelcontextprotocol.io) server for the Pebble smartwatch
|
|
8
|
+
ecosystem: appstore search, a 64-color design toolkit, and the local dev loop
|
|
9
|
+
(build → emulator → screenshot flows) exposed as typed tools usable from any
|
|
10
|
+
MCP host — Claude Code, Claude Desktop, claude.ai, or any other agent — not
|
|
11
|
+
just a shell. The demo above is a single `flow_run`: it installs a real
|
|
12
|
+
appstore app (Hubble) onto the emery emulator, drives the buttons, and captures
|
|
13
|
+
each screen — all through MCP.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
Once published to PyPI, no checkout is needed — `uvx` runs it on demand. Add
|
|
18
|
+
this to your MCP host's config (e.g. a Claude Code `.mcp.json`):
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"mcpServers": {
|
|
23
|
+
"pebble-mcp": {
|
|
24
|
+
"command": "uvx",
|
|
25
|
+
"args": ["pebble-mcp"]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
To run from a local checkout instead (development, or before the PyPI release):
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"mcpServers": {
|
|
36
|
+
"pebble-mcp": {
|
|
37
|
+
"command": "uv",
|
|
38
|
+
"args": ["run", "--directory", "/absolute/path/to/pebble-mcp", "pebble-mcp"]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Quickstart (Claude Code)
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
# from PyPI (once published)
|
|
48
|
+
claude mcp add pebble-mcp -- uvx pebble-mcp
|
|
49
|
+
|
|
50
|
+
# or from a local checkout
|
|
51
|
+
claude mcp add pebble-mcp -- uv run --directory /absolute/path/to/pebble-mcp pebble-mcp
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Then, in a session, ask the agent to call `capabilities()` to see which tiers
|
|
55
|
+
are live, or `store_search("weather")` to hit the appstore immediately.
|
|
56
|
+
|
|
57
|
+
## Capability tiers
|
|
58
|
+
|
|
59
|
+
The server registers tools in four tiers and probes the environment at startup,
|
|
60
|
+
so an agent only ever sees the tools it can actually run.
|
|
61
|
+
|
|
62
|
+
- **Tier 1 — Appstore** (pure HTTPS, no auth, always on):
|
|
63
|
+
`store_search`, `store_app`, `store_collection`, `store_category`,
|
|
64
|
+
`store_developer`, `store_compare`, `store_download_pbw`.
|
|
65
|
+
- **Tier 2 — Design toolkit** (pure Python + Pillow):
|
|
66
|
+
`color_nearest`, `palette_swatch`, `image_quantize`, `image_prep`,
|
|
67
|
+
`font_plan`, `pdc_convert`. Pillow is a core dependency, so this tier is
|
|
68
|
+
normally always on; `capabilities()` still probes for it and degrades
|
|
69
|
+
gracefully if it is somehow absent.
|
|
70
|
+
- **Tier 3 — Dev loop** (requires the `pebble` CLI on `PATH`):
|
|
71
|
+
`pebble_build`, `pebble_install`, `emu_start`, `emu_stop`,
|
|
72
|
+
`emu_screenshot`, `emu_input`, `emu_logs`, `flow_validate`, and the crown
|
|
73
|
+
jewel `flow_run` — run a gallery-style flow spec and get every screenshot
|
|
74
|
+
back as MCP images. These tools register only when `pebble` is found.
|
|
75
|
+
- **Tier 4 — Authenticated** (requires `PEBBLE_API_TOKEN`; off by default):
|
|
76
|
+
reserved for token-gated, network-mutating operations (heart an app,
|
|
77
|
+
publish, timeline pins). Detected by `capabilities()`; opt-in only.
|
|
78
|
+
|
|
79
|
+
Read-only reference is also exposed as MCP resources: `pebble://colors`,
|
|
80
|
+
`pebble://fonts`, `pebble://platforms`, `pebble://wire-conventions`.
|
|
81
|
+
|
|
82
|
+
### The `capabilities()` tier probe
|
|
83
|
+
|
|
84
|
+
Call the `capabilities()` tool first. It reports which tiers are live in the
|
|
85
|
+
running environment (`tier1_appstore`, `tier2_design`, `tier3_devloop`,
|
|
86
|
+
`tier4_auth`) so an agent can plan its work instead of calling a tool that
|
|
87
|
+
isn't wired up. Tier 1 is always true; the rest reflect Pillow, the `pebble`
|
|
88
|
+
CLI, and `PEBBLE_API_TOKEN` respectively.
|
|
89
|
+
|
|
90
|
+
## Development
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
uv sync # install deps (+ dev group: pytest, ruff)
|
|
94
|
+
uv run pytest # run the test suite
|
|
95
|
+
uv run ruff check # lint
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The `pebble_mcp/examples/flows/` directory holds sample flow specs (used by the
|
|
99
|
+
tests and as living documentation of the flow format, including the
|
|
100
|
+
`# SAFETY RULES (live-write hazards — DO NOT TRIGGER):` convention that flow
|
|
101
|
+
authors should follow for any app with server-mutating screens).
|
|
102
|
+
|
|
103
|
+
## License
|
|
104
|
+
|
|
105
|
+
MIT — see [LICENSE](LICENSE). Copyright (c) 2026 Daniel Bonomo.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
*Not affiliated with, endorsed by, or sponsored by Core Devices or Rebble.
|
|
110
|
+
"Pebble" and related marks belong to their respective owners.*
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# pebble-mcp — Tool Surface Contract
|
|
2
|
+
|
|
3
|
+
The usability contract for every MCP tool this server exposes. The requirements
|
|
4
|
+
doc says *what* ships; this says *how it must feel*. A tool that violates this
|
|
5
|
+
doc is not done, even if it works. Scope changes go through Fable + Dan.
|
|
6
|
+
|
|
7
|
+
Guiding test for every design decision: **an agent that has never seen Pebble
|
|
8
|
+
before reads only the tool list and schemas — can it do the task on the first
|
|
9
|
+
call, and when it can't, does the error tell it what to do next?**
|
|
10
|
+
|
|
11
|
+
## 1. Small surface, strong verbs
|
|
12
|
+
|
|
13
|
+
- Target **≤ 15 tools total** across all tiers. Prefer one tool with a clear
|
|
14
|
+
argument over two near-duplicate tools. (Counter-example to avoid: separate
|
|
15
|
+
`store_faces` / `store_apps` — it's one `store_search` with a `type` arg.)
|
|
16
|
+
- Every tool name is `noun_verb` or `noun` scoped by prefix: `store_*`,
|
|
17
|
+
`image_*`, `font_*`, `emu_*`, `pebble_*` (build/install), `flow_*`.
|
|
18
|
+
- No tool exists just to expose a library function. If an agent wouldn't reach
|
|
19
|
+
for it while building or shipping a watchface, it doesn't get a tool.
|
|
20
|
+
Library ≠ surface.
|
|
21
|
+
|
|
22
|
+
## 2. Descriptions and schemas are the product
|
|
23
|
+
|
|
24
|
+
- Tool description: first sentence = what it does for the caller. Then when to
|
|
25
|
+
use it, then sharp edges. Written for a model, tuned like docs.
|
|
26
|
+
- Every enum-ish argument IS an enum in the schema (platforms, buttons,
|
|
27
|
+
collection slugs, dither modes). Never "string, see docs".
|
|
28
|
+
- Defaults encode expertise: `hardware="emery"`, sane limits, safe modes. A
|
|
29
|
+
zero-argument or one-argument call should do the obviously-right thing.
|
|
30
|
+
(Lesson from GymTracker: value on first call, never "configure elsewhere
|
|
31
|
+
first".)
|
|
32
|
+
|
|
33
|
+
## 3. Returns: compact, structured, honest
|
|
34
|
+
|
|
35
|
+
- **Context is the scarce resource.** List-shaped returns are trimmed
|
|
36
|
+
projections (id, title, author, hearts, one-line summary) — never the raw
|
|
37
|
+
App object. Full detail is a *by-id* lookup. Cap list returns (default
|
|
38
|
+
~10, max ~50) and always include pagination state the agent can act on:
|
|
39
|
+
`{offset, returned, has_more}`.
|
|
40
|
+
- Screenshots and swatches return as **MCP image content blocks** (the agent
|
|
41
|
+
sees them) *plus* the saved file path (the human keeps them). Both, always.
|
|
42
|
+
- Numbers the store API lies about get fixed in the tool: hearts sorting is
|
|
43
|
+
client-side (`sort=hearts` is silently ignored upstream — measured, not
|
|
44
|
+
assumed). The tool's output order is the order it promises.
|
|
45
|
+
- Anything cached carries `fetched_at`. Cached list tools accept
|
|
46
|
+
`refresh: bool = false`. Stale-on-network-failure returns data + a
|
|
47
|
+
`stale: true` flag, not an exception.
|
|
48
|
+
|
|
49
|
+
## 4. Errors an agent can act on
|
|
50
|
+
|
|
51
|
+
- Every failure path returns: what failed, why (upstream detail bounded to a
|
|
52
|
+
few hundred chars), and **the next move** ("run capabilities()", "the id
|
|
53
|
+
looks malformed — store ids are 24 hex chars", "emulator wedged; it was
|
|
54
|
+
kill+wiped, retry your call").
|
|
55
|
+
- Tier-gated tools fail fast with the gate named: calling `pebble_build`
|
|
56
|
+
without the CLI returns the install hint, not a traceback.
|
|
57
|
+
- No silent None. A lookup that finds nothing says so and echoes what it
|
|
58
|
+
looked for.
|
|
59
|
+
|
|
60
|
+
## 5. Sharp edges stay inside the tool
|
|
61
|
+
|
|
62
|
+
- `pebble_install` bakes in kill+wipe. `flow_run` bakes in wedge recovery
|
|
63
|
+
(one restart, then a structured failure). Agents never need to know the
|
|
64
|
+
folklore; that's the whole point of the server.
|
|
65
|
+
- Emulator tools are serialized internally (one QEMU); a second concurrent
|
|
66
|
+
call queues or fails clearly — it never interleaves.
|
|
67
|
+
- Log/output capture is always bounded (bytes and seconds) with the truncation
|
|
68
|
+
stated in the return.
|
|
69
|
+
|
|
70
|
+
## 6. Safety and tiers
|
|
71
|
+
|
|
72
|
+
- Tier 4 (authed) tools: mutating calls (`store_heart`, `pebble_publish`,
|
|
73
|
+
timeline pushes) require an explicit `confirm: true` argument and describe
|
|
74
|
+
exactly what will happen when called without it. Publish is a two-step
|
|
75
|
+
handshake, never one call.
|
|
76
|
+
- `capabilities()` stays cheap, evaluated per-call, and is the single source
|
|
77
|
+
of truth the other tools' gate errors point at.
|
|
78
|
+
- Nothing phones home; the only network calls are the ones the tool name
|
|
79
|
+
promises.
|
|
80
|
+
|
|
81
|
+
## 7. Known upstream quirks (encode, don't rediscover)
|
|
82
|
+
|
|
83
|
+
| Quirk | Tool-level answer |
|
|
84
|
+
|---|---|
|
|
85
|
+
| `sort=hearts` ignored by collection API | client-side sort, documented in description |
|
|
86
|
+
| collection type is `apps`/`faces` (not `watchapps`) | enum hides the slug entirely |
|
|
87
|
+
| `Page.total` absent | expose `has_more` from returned-vs-limit instead |
|
|
88
|
+
| no text-search read endpoint mapped yet (A1 gap) | `store_search` is the #1 user verb — needs an endpoint or client-side scan; resolve before A2 ships |
|
|
89
|
+
| watchapps need wipe-before-install to foreground | baked into `pebble_install` |
|
|
90
|
+
|
|
91
|
+
## 8. Definition of slick (acceptance)
|
|
92
|
+
|
|
93
|
+
A fresh agent session with only this server configured can, unprompted:
|
|
94
|
+
1. name the top-5 most-loved emery faces (correct order, one call),
|
|
95
|
+
2. build a broken project and report the *actionable* compiler error,
|
|
96
|
+
3. run a 3-step flow and *show* the screenshots,
|
|
97
|
+
4. explain why a Tier-4 tool call didn't run and what's needed to enable it —
|
|
98
|
+
each in ≤ 2 tool calls, no call returning > ~4 KB of text.
|