# VDK guidance for coding assistants

Save this as `AGENTS.md` in an app that uses the Visor Development Kit (`visor agents > AGENTS.md`), or point a
rules file at it. It tells a coding assistant how to build on the VDK. For any detail, search the docs: run
`visor docs QUERY`, or add the docs MCP server with `claude mcp add vdk-docs -- visor docs-mcp` and call
`search_vdk_docs` and `read_vdk_doc`.

## What the VDK is

The VDK lets an Android app, or a program on a computer, use Ray-Ban Meta glasses, the Meta Ray-Ban Display and the
Meta Neural Band through Visor, the Android app that runs them. It is an alternative to Meta's Wearables Device
Access Toolkit (DAT): no Meta AI app, no Meta account, no developer centre. Porting from DAT: read
`porting-from-dat`.

- Android: `implementation("com.macedonga.visor:kit:<version>")`, then `val visor = Visor.connect(context)`.
- Tests: `com.macedonga.visor:kit-testing` gives `FakeVisor` and `fake.connect()` in a plain JVM test.
- Other languages: JSON-RPC over adb or Visor's network door; Python `visor_kit` and TypeScript clients.
- A web app on the Display: the TypeScript client's `Visor.connectGlasses()`, guarded by `onGlasses()`. On a
  `refused` with reason `not_allowed`, tell the person to allow the app in Visor > Settings > Web apps.

## Rules

1. Ask for scopes before using them: `registerForActivityResult(RequestScopes())`, then `launch(setOf(...))`. A
   call without its scope fails with `KitException` code `scope_denied`, naming the missing `scope`.
2. Every call is a `suspend fun`; every stream is a cold `Flow` that runs while collected. Collect streams in a
   lifecycle-aware scope and cancel them to stop: there are no start and stop pairs.
3. Handle `KitException` by its `code` (`unsupported`, `not_connected`, `busy`, `invalid_params`, `failed`,
   `scope_denied`) and `reason`. Treat an unknown code as `failed` and unknown enum-like values as unknown: the
   value sets (scopes, input actions, gesture actions, layout types) grow.
4. Layouts for the Display are whole screens: build one with `Layout { }` and show it with
   `visor.display.layout.show(...)`; a new show replaces it. Clicks come on `display.layout.clicks` by the
   element's `id`; `display.layout.state` says when the wearer went back.
   For something the wearer should see all day (a timer, a delivery, a score), prefer a Briefing card:
   `visor.display.cards.set(id, name, icon) { header(...); stat(...); detail { } }`, kept with `cards.keep(id,
   name, icon, flow) { }`, with clicks in its detail on `display.cards.clicks`. A card takes at most 256 KiB.
5. A voice phrase reaches the app while it collects `visor.voice.requests(phrases)`, or starts it when its
   activity declares the phrases (`VoiceLaunch`). Reply within about 20 seconds with `visor.voice.reply`.
6. `band.motion` needs a band the person shares with the phone: check `band.status` `shared` first, and on
   `unsupported` with reason `not_shared`, show the person the exception's message, which names the setting.
7. Do not invent methods. The methods and topics are in `kit-protocol` (`Methods`, `Topics`) and in the docs'
   JSON-RPC tables; `visor methods` lists what a Visor answers.

## Testing without glasses

- `FakeVisor` in unit tests: set `fake.glasses.*` and `fake.band.shared`, send `fake.glasses.input(...)`,
  `fake.band.gesture(...)`, `fake.band.motion(...)`,
  `fake.voice.say(...)`, `fake.display.click(id)`, `fake.display.back()`, `fake.display.clickCard(card, id)`.
- `visor fake` on a computer: JSON-RPC on `ws://127.0.0.1:7333/rpc`, MCP on `/mcp`, the Display simulator on
  `http://127.0.0.1:7333/display`, and a console that plays the glasses.
- `visor record FILE` and `visor replay FILE` capture and play back what the wearer did.
- `FakeGlasses.playMotion` plays DAT's motion CSV; `FakeLive.play` plays a recorded H.264 or H.265 stream.