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
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dimo",
"version": "0.2.3",
"version": "0.2.4",
"description": "Ask Claude where your car is, how the battery's doing, or what trips you took. Sets up in about a minute from the DIMO mobile app.",
"author": {
"name": "DIMO Network",
Expand Down
56 changes: 56 additions & 0 deletions docs/product/2026-06-10-first-license-fee-memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Decision memo: the developer-license fee blocks the first "wow"

**For:** Yev / product + tokenomics
**Context:** Claude plugin onboarding is now ~1 minute, but the first step
mints a developer license with an on-chain fee paid in DIMO from the user's
in-app balance. A new user who wants to ask "where's my car?" hits a paywall
before seeing any value. Conversion folklore and our own adoption feedback
say this is the single biggest remaining drop-off.

## Constraint

`DevLicenseDimo.issueInDimo` charges `licenseCostInUsd1e18` (admin-settable)
in DIMO at spot price. The fee exists to deter squatting and sybil license
farming, and it feeds protocol revenue. Removing it globally invites spam.

## Options

**A. First license free, app-sponsored (recommended).**
The app's paymaster/sponsor wallet covers the fee for a user's first
license, gated by: account age or at least one connected vehicle, one per
wallet, normal price afterward. No contract change if implemented as an
app-side top-up/rebate at mint time; cleaner long-term as a contract-level
`issueSponsored` allowance held by the app's license.
- Cost: bounded (one fee × converting users — exactly the users we want).
- Sybil risk: low with the connected-vehicle gate; a vehicle NFT is a far
stronger sybil cost than the license fee.

**B. Fee rebate after activity.**
Pay normally, refunded in DIMO after N successful API days. Keeps the
deterrent fully intact.
- Worse conversion: the paywall still hits at minute one; rebates don't fix
first-session drop-off. More moving parts (tracking, payout).

**C. Free tier at the API layer instead.**
Skip the license for read-only personal use: the hosted OAuth connector
(see oauth plan) authenticates the *user*, not a developer license, so the
consumer path never mints a license at all. The fee then only applies to
actual developers shipping apps — its intended audience.
- This is the structurally right end state, but it waits on the OAuth
gateway. Doesn't help the plugin path this quarter.

## Recommendation

A now, C as the end state. Ship A behind a feature flag in the app
(sponsor first mint when `vehicleCount >= 1`), measure conversion delta on
the Developer API Key screen, and let the OAuth connector eventually make
the consumer fee question moot. B only if A's sponsorship budget proves
abusable, which the vehicle gate should prevent.

## What's needed to ship A

- Product sign-off on gate (≥1 connected vehicle, one per wallet).
- App: sponsor flow in `useDeveloperLicense` mint path + LaunchDarkly flag.
- Treasury: sponsor wallet funding + monthly cap alert.
- Analytics: funnel events on the fee screen (already have
`AnalyticEventNames` infra) to measure before/after.
76 changes: 76 additions & 0 deletions docs/superpowers/plans/2026-06-10-oauth-remote-mcp-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# OAuth 2.1 remote MCP — engineering plan

Companion to `docs/superpowers/specs/2026-06-10-oauth-remote-mcp-design.md`.
Goal: DIMO appears as a one-click connector in claude.ai, Claude Desktop, and
Claude Code. Target: smallest path to a working connector, not a general
OAuth platform.

## Architecture decision (proposed)

Build a thin **MCP gateway** service (`dimo-mcp-gateway`, Go, standard DIMO
service layout) instead of modifying telemetry-api:

- telemetry-api keeps doing what it does; the gateway terminates MCP +
OAuth and proxies tool calls with internally-minted Vehicle JWTs.
- The gateway is also where identity tools, command tools (consent-gated),
and future attestation tools mount — one connector, full surface.
- Auth server work stays inside the existing DIMO auth stack; the gateway
is a standard OAuth resource server.

## Milestones

### M1 — Authorization server (auth stack)
- `/.well-known/oauth-authorization-server` metadata document.
- Authorization-code + PKCE flow on top of the existing login.dimo.org
session (web login works day one; app push approval is M4 polish).
- Token endpoint with refresh tokens; access tokens are JWTs carrying the
DIMO user (wallet) and granted scopes.
- **Dynamic client registration** (RFC 7591) — required by MCP clients;
this is what removes the developer-console step for end users entirely.
- Scopes: `vehicle:data:read` (privileges 1,3,4,5,6,8), `vehicle:commands`
(privilege 2, separate consent screen), `vehicle:vin` (5, included in
read), `attestations:create`.

### M2 — MCP gateway (new repo)
- Streamable-HTTP MCP server in Go; `/.well-known/oauth-protected-resource`
pointing at M1.
- Tool surface v1: the 10 telemetry tools (proxy to telemetry-api `/mcp`
with a service-minted Vehicle JWT per call), identity vehicle list, and
`check_access` (vehicles + privileges for the signed-in user).
- Per-call authorization: user-token wallet → vehicles owned or shared to
the user → SACD check → mint Vehicle JWT via token-exchange service
credentials. Cache per (user, vehicle) with the same expiry semantics as
the plugin script.
- Rate limiting + DCX metering hooks from day one (connector traffic is
unmetered developer traffic otherwise).

### M3 — Connector launch
- Register with Anthropic's connector directory (claude.ai → Settings →
Connectors); verify Claude Code `claude mcp add --transport http` and
Desktop flows.
- dimo-plugin v0.3: `.mcp.json` points at the gateway; auth script,
credentials file, and preview setup form deleted; skill becomes usage
guidance only. `~/.dimo` cleanup on first run.

### M4 — Mobile approval surface
- OAuth consent screen offers "Approve in DIMO app" (push / deep link
`dimo://approve?...`), falling back to web. The in-app developer-license
screen gains a "Connected AI apps" list with revoke.

## Sequencing and ownership

M1 and M2 can run in parallel (M2 stubs token validation against M1's JWKS
from week one). M3 is days, not weeks, once M1+M2 are in staging. M4 is
independent app work. Suggested first PRs: auth-stack metadata + PKCE
endpoint; gateway repo scaffold with health/metadata endpoints and one
proxied tool behind a fake validator.

## Risks

- Dynamic client registration is the only genuinely new auth-stack surface;
scope it to public clients with PKCE-only, no client secrets.
- Commands behind a connector raise the abuse bar: keep `vehicle:commands`
out of the default scope set and require per-vehicle consent.
- Revocation: SACD revoke and OAuth token revoke must both cut access;
gateway checks SACD at mint time and caps Vehicle JWT cache at 10 min,
matching today's exposure window.
7 changes: 6 additions & 1 deletion skills/dimo/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: dimo
description: This skill should be used when the user asks to "connect my DIMO vehicle", "query my vehicle data", "get vehicle telemetry", "check my car's battery", "see my vehicle signals", "show my car stats", "use DIMO", "query DIMO", or invokes /dimo. Guides users from zero to querying live telemetry from a DIMO-connected vehicle — 1-minute setup from the DIMO mobile app, automatic JWT handling, and real-time signal queries.
version: 0.2.3
version: 0.2.4
allowed-tools: Bash, mcp__Claude_Preview__preview_start, mcp__Claude_Preview__preview_eval, mcp__Claude_Preview__preview_list
---

Expand Down Expand Up @@ -334,6 +334,10 @@ Use `preview_eval` to **append** a new `.signal-card` to `#signalsContent` after

**Always show data age.** Every signal carries a `timestamp` (and snapshots a `lastSeen`). If the data is older than ~1 hour, say so in plain words next to the answer (e.g. *"last reported 3 weeks ago — the car hasn't sent data since"*). Never present stale values as the current state. For location answers, give a human-readable place (city/area) alongside coordinates when possible.

### Vehicle commands (lock, unlock, charge)

Supported via the Devices API with the same Vehicle JWT — endpoints and error mapping in `references/commands.md`. **Hard rule:** a command moves a real car. State the exact command and exact vehicle and wait for the user's confirmation in their next message before sending; one command per confirmation; never auto-retry an ambiguous or timed-out command. Requires privilege 2 on the grant; a 403 means re-share the vehicle in the DIMO app with command access.

### After the first successful query

Tell the user setup is done for good — credentials are stored and tokens renew automatically — and show 3-4 example asks so they know what's possible, e.g.:
Expand All @@ -342,6 +346,7 @@ Tell the user setup is done for good — credentials are stored and tokens renew
- "What's my battery / fuel level?"
- "Show my trips from last week"
- "Any fault codes on my car?"
- "Lock my car" (confirmed before sending)

### Preview fully gone (preview_list shows nothing)

Expand Down
56 changes: 56 additions & 0 deletions skills/dimo/references/commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Vehicle commands

Commands actuate the real, physical vehicle. They go to the DIMO Devices API
with the same Vehicle JWT the telemetry tools use.

## Safety rules (non-negotiable)

1. **Explicit confirmation every time.** Before sending any command, state
exactly what will happen to which vehicle ("I'm about to **unlock the
doors** of your **2019 Tesla Model 3**") and wait for the user to confirm
in their next message. A general request earlier in the conversation is
not confirmation for a specific command now.
2. **One command per confirmation.** Never batch or chain commands under a
single yes.
3. **Never retry a command automatically.** If the response is ambiguous or
times out, report it and let the user decide. A retried unlock is a
second unlock.
4. Commands require **privilege 2 (commands)** on the vehicle's grant. The
auth script requests exactly the granted privileges, so if the grant
lacks privilege 2 the call returns 403 — point the user at the DIMO app
to re-share with command access.

## Endpoint

```
POST https://devices-api.dimo.zone/v1/vehicle/<TOKEN_ID>/commands/<COMMAND>
Authorization: Bearer <VEHICLE_JWT>
Content-Type: application/json
Body: {}
```

| Command | Path |
|---|---|
| Lock doors | `doors/lock` |
| Unlock doors | `doors/unlock` |
| Start charging | `charge/start` |
| Stop charging | `charge/stop` |

```bash
JWT=$(node "${CLAUDE_PLUGIN_ROOT}/scripts/dimo-auth.mjs" vehicle-jwt <TOKEN_ID>)
curl -s --max-time 30 -X POST "https://devices-api.dimo.zone/v1/vehicle/<TOKEN_ID>/commands/doors/lock" \
-H "Authorization: Bearer $JWT" -H "Content-Type: application/json" -d '{}'
```

A successful response returns a command acknowledgment from the device
backend; actual execution on the vehicle can lag by a few seconds. Report
the acknowledgment honestly — "the command was accepted" rather than "the
doors are locked" — unless a follow-up signal confirms the new state.

## Errors

| Response | Meaning | What to tell the user |
|---|---|---|
| 403 | Grant lacks privilege 2 (commands) | Re-share the vehicle in the DIMO app with command access |
| 404 | Vehicle/command not found or not supported by this connection type | This vehicle's connection doesn't support that command |
| 5xx / timeout | Backend or vehicle unreachable | Command not confirmed — do not assume it ran, do not auto-retry |
2 changes: 2 additions & 0 deletions skills/dimo/references/mcp-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ Per-day driving activity summaries over a date range.

`from`, `to`, and `mechanism` are required (same mechanism values as trip segments). Optional: `"timezone"`. Heavy computation — use short date ranges.

> ⚠️ **Known issue (2026-06-10):** this tool and `telemetry_get_trip_segments` can hang or return an empty body even with valid params (tracked with the telemetry-api team). Always pass `--max-time 30` to curl; on an empty response, say trip/daily data is temporarily unavailable and answer from `telemetry_get_signals_time_series` instead — do not retry in a loop.

---

### telemetry_get_events
Expand Down
Loading