Skip to content

Add Pebble watch webhook + skill framework#69

Merged
max-tet merged 18 commits into
mainfrom
worktree-pebble-webhook
May 12, 2026
Merged

Add Pebble watch webhook + skill framework#69
max-tet merged 18 commits into
mainfrom
worktree-pebble-webhook

Conversation

@ClaydeCode
Copy link
Copy Markdown
Owner

Summary

Adds a FastAPI webhook to the Clayde container that receives speech-to-text messages from a Pebble watch app, plus a markdown-skill framework for dispatching them to the Claude CLI.

  • Webhook: POST /webhook/pebble with Authorization: Bearer <token> accepts {text, timestamp}, enqueues into an in-memory asyncio.Queue, returns 200. /health for liveness. 401/422/503 on bad token / payload / queue full.
  • Skills: Markdown files with name/description frontmatter mounted under the fixed in-container path /skills/. Recursive discovery on each request; deterministic alphabetical order; first-wins on duplicate name.
  • Worker: Single serial coroutine pops jobs and runs the Claude CLI fresh-session per request (no resume, no session persistence). Per-request scratch tempdir. OTel clayde.pebble.process span cross-references the enqueue span via pebble.job_id.
  • Concurrency: Single asyncio event loop runs uvicorn + the existing GitHub poll loop (via asyncio.to_thread) + the worker. Behavior unchanged when CLAYDE_PEBBLE_ENABLED=false.
  • Deployment: Adds Traefik (Let's Encrypt via HTTP-01) on a web network; clayde joins only a private internal network — no host ingress except via Traefik.
  • Docs: CLAUDE.md, README.md, config.env.template updated with new env vars and operator instructions.

Spec: docs/superpowers/specs/2026-05-06-pebble-webhook-design.md
Plan: docs/superpowers/plans/2026-05-07-pebble-webhook-framework.md

Test plan

  • uv run pytest -q — 335/335 passing (42 new tests added)
  • Deploy with CLAYDE_PEBBLE_ENABLED=true, verify /health returns 200 over HTTPS via Traefik
  • Configure Pebble app with bearer token; trigger a request; verify 200 + pebble.job_id in traces.jsonl
  • Drop a test skill into a mounted /skills/... dir; trigger a matching request; verify the skill's side effect (e.g. file written, calendar event, etc.)
  • Send a request with no matching skill; verify response is exactly "No matching skill" and pebble.skill=none on the process span
  • Verify Let's Encrypt cert issued and renewed via Traefik logs

🤖 Generated with Claude Code

ClaydeCode and others added 18 commits May 6, 2026 16:25
Specifies a FastAPI webhook endpoint inside the Clayde container that
receives speech-to-text messages from a Pebble watch app and dispatches
them to the Claude CLI with a catalog of markdown-defined skills.
Includes Traefik reverse proxy with Let's Encrypt for TLS.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ule, private docker network

- Drop CLAYDE_SKILL_DIRS env var. Hardcode /skills/ as the in-container
  scan root; recursive discovery means subdirectory layout is free.
- Drop startup scan; only scan per request (cheap, allows hot-add).
- Add explicit "choose at most one skill" rule to system prompt.
- Add two-network compose layout (web + internal). Traefik publishes
  ports on web, clayde joins only internal. No host ingress to clayde
  except via Traefik.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
14-task TDD plan with bite-sized steps. Adds FastAPI webhook
(/webhook/pebble + /health), bearer auth, in-memory asyncio queue,
single serial worker, fresh-session Claude CLI invocation, recursive
markdown-skill discovery under /skills/, OTel enqueue+process spans
cross-referenced by pebble.job_id, and a Traefik+private-network
docker-compose setup. Existing GitHub poll loop runs in a thread to
keep the asyncio event loop responsive.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tter

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wrap the communicate() await in a try/finally (via BaseException) so that
proc.kill() + proc.wait() run on both TimeoutError->InvocationTimeoutError
and external CancelledError, preventing leaked subprocesses.

Add three tests: is_error output triggers UsageLimitError, auth failure
triggers RuntimeError, and external cancellation kills the proc.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nabled

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ounts

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…etup

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@max-tet max-tet merged commit 1cdd645 into main May 12, 2026
2 checks passed
@max-tet max-tet deleted the worktree-pebble-webhook branch May 12, 2026 07:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants