{
  "schema_version": 1,
  "type": "skill",
  "slug": "adom-api",
  "title": "Adom API",
  "brief": "> **Status: Not yet available.** This API is on the Adom roadmap and is actively being developed. The endpoints, WebSocket events, and CLI wrapper described below represent the planned design. This do",
  "version": "1.0.0",
  "tags": [],
  "license": "MIT",
  "source_path": "SKILL.md",
  "readme": "---\nname: adom-api\nuser-invokable: true\ndescription: Adom Docker Container API reference. Use when the user wants to control workcell hardware, cameras, messaging, repo sync, or other platform features from code running in the Docker container.\n---\n\n# Adom Docker Container API\n\n> **Status: Not yet available.** This API is on the Adom roadmap and is actively being developed. The endpoints, WebSocket events, and CLI wrapper described below represent the planned design. This document will be updated with final URLs, ports, and details as the API becomes available.\n\nThe Docker container in every Adom project will have access to the **Adom API** — a set of HTTP REST endpoints and a WebSocket channel for controlling workcell hardware, communicating with the front-end web app, and managing project state. All API calls originate from inside the Docker container.\n\n## Adom Platform URLs\n\nUnderstanding how Adom URLs work is important for generating correct links in READMEs, documentation, and scripts.\n\n### URL Scheme\n\n| What | URL pattern |\n|------|-------------|\n| Hydrogen web app | `https://hydrogen.adom.inc/` |\n| Project directory browser | `https://hydrogen.adom.inc/{username}/{project}/tree/latest/{path}` |\n| Project file viewer | `https://hydrogen.adom.inc/{username}/{project}/blob/latest/{path}` |\n| Adom REST API (external) | `https://carbon.adom.inc/` (requires browser session auth) |\n| Adom WebSocket (external) | `wss://iron.adom.inc/` (requires auth token) |\n| Container VS Code | `https://coder.{username}-{project}-{hash}.containers.adom.inc/` |\n| Container proxy (ports) | `https://coder.{username}-{project}-{hash}.containers.adom.inc/proxy/{port}/` |\n\n> **`/tree/` vs `/blob/`**: Use `/tree/latest/{path}` for directories, `/blob/latest/{path}` for individual files — same convention as GitHub.\n\n### Deriving username and project name\n\nBoth can be read from the `VSCODE_PROXY_URI` environment variable inside the container:\n\n```bash\n# Full URI: https://coder.{username}-{project}-{hash}.containers.adom.inc/proxy/{{port}}/\necho $VSCODE_PROXY_URI\n# Example: https://coder.john-adom-conduit-d4d7f7f287915e49.containers.adom.inc/proxy/{{port}}/\n```\n\nThe hostname format is `coder.{username}-{project}-{hash}.containers.adom.inc`. Extract username and project by splitting on `-` up to the 32-char hex hash at the end.\n\n### Linking to project files from READMEs\n\nAlways use absolute Hydrogen URLs when linking to project files in a README — relative paths resolve incorrectly in the Hydrogen file browser:\n\n```markdown\n# WRONG — relative path resolves incorrectly on Hydrogen:\n\\[ICM-42688-P\\](project-content/schematics/symbols/ICM-42688-P/)\n\n# CORRECT — absolute Hydrogen URL:\n[ICM-42688-P](https://hydrogen.adom.inc/john/adom-conduit/tree/latest/project-content/schematics/symbols/ICM-42688-P)\n```\n\n---\n\n## Access\n\n- **REST API base URL:** `http://localhost:{{ADOM_API_PORT}}/api/v1` *(exact port and base URL forthcoming — will be provided as an environment variable in the Docker container)*\n- **WebSocket endpoint:** `ws://localhost:{{ADOM_API_PORT}}/ws` *(for real-time subscriptions and bidirectional updates)*\n- **CLI wrapper:** `adom` command pre-installed in the Docker container for shell convenience *(forthcoming)*\n\nAll REST endpoints accept and return JSON. The WebSocket channel sends/receives JSON messages with a `type` field indicating the event or command.\n\n## Authentication\n\nAPI calls from the Docker container are automatically authenticated — no API key or token is needed. The container's identity is tied to the project and user session.\n\n---\n\n## Camera Control\n\nEvery workcell includes a camera system. The base tier is **xyZoom**; the upgrade tier is **xyPanTiltZoom**.\n\n### xyZoom (base camera)\n\nThe xyZoom system has **two cameras** on a single gantry:\n\n1. **Wide camera** — Fixed 120° FOV. Always shows a full birds-eye view of the entire workcell, even as the gantry moves. Provides constant spatial awareness.\n2. **Zoom camera** — Variable zoom that can focus down to a **10 mm x 10 mm** area filling the entire live feed. Used for close inspection of components, solder joints, traces, etc.\n\nThe gantry moves along X and Y axes to position both cameras over any point in the workcell.\n\n#### REST endpoints\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| `POST` | `/camera/move` | Move the camera gantry to an X/Y position and set zoom level |\n| `GET` | `/camera/position` | Get current gantry position and zoom level |\n| `GET` | `/camera/capabilities` | Returns which camera system is installed (xyZoom or xyPanTiltZoom) |\n\n#### `POST /camera/move` — request body\n\n```json\n{\n  \"x\": 288,\n  \"y\": 288,\n  \"zoom\": 1.0\n}\n```\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `x` | number | X position in mm on the base scaffold (0–576) |\n| `y` | number | Y position in mm on the base scaffold (0–576) |\n| `zoom` | number | Zoom level. `1.0` = fully zoomed out, higher values zoom in. Maximum zoom fills the feed with a 10 mm x 10 mm area. |\n\n#### `GET /camera/position` — response\n\n```json\n{\n  \"x\": 288,\n  \"y\": 288,\n  \"zoom\": 1.0,\n  \"cameraType\": \"xyZoom\"\n}\n```\n\n#### WebSocket events\n\nSubscribe to camera position updates in real time:\n\n```json\n{ \"type\": \"camera.position\", \"x\": 288, \"y\": 288, \"zoom\": 1.0 }\n```\n\nThe front-end web app automatically reflects camera movements made via the API, and vice versa — if a user moves the camera from the web panel, the Docker container receives position updates over the WebSocket.\n\n#### CLI examples *(forthcoming)*\n\n```bash\nadom camera move --x 100 --y 200 --zoom 5\nadom camera position\n```\n\n### xyPanTiltZoom (upgrade camera)\n\nThe xyPanTiltZoom adds **pan** and **tilt** axes to the zoom camera, allowing angled views rather than only top-down. It also supports **3D viewer linking** — the camera physically matches whatever angle the user navigates to in the web app's 3D layout viewer.\n\n#### Additional REST endpoints\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| `POST` | `/camera/move` | Same endpoint, with additional `pan` and `tilt` fields |\n| `POST` | `/camera/link-3d` | Enable or disable 3D viewer linking |\n\n#### `POST /camera/move` — extended request body (xyPanTiltZoom)\n\n```json\n{\n  \"x\": 288,\n  \"y\": 288,\n  \"zoom\": 1.0,\n  \"pan\": 0,\n  \"tilt\": 90\n}\n```\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `pan` | number | Pan angle in degrees (horizontal rotation) |\n| `tilt` | number | Tilt angle in degrees. `90` = straight down (top-down view), lower values angle the camera. |\n\n#### `POST /camera/link-3d` — request body\n\n```json\n{\n  \"enabled\": true\n}\n```\n\nWhen enabled, the physical camera continuously tracks the user's viewpoint in the 3D layout editor. The user navigates in 3D on the web; the real camera follows. Disable linking to return to manual/API control.\n\n---\n\n## Messaging\n\nSend messages from the Docker container to the front-end web app so the user sees them in their browser.\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| `POST` | `/message/send` | Send a message to the web app |\n\n#### `POST /message/send` — request body\n\n```json\n{\n  \"text\": \"Firmware upload complete. Board is ready for testing.\",\n  \"level\": \"info\"\n}\n```\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `text` | string | The message content |\n| `level` | string | `info`, `warning`, or `error`. Controls how the message is displayed in the web app. |\n\n#### CLI example *(forthcoming)*\n\n```bash\nadom message send \"Firmware upload complete\" --level info\n```\n\n---\n\n## Repository Sync\n\nSync changes from the Docker container back to the project's main repository on Adom.\n\n**The easiest way**: click the **\"Commit & Save Project Files\"** button on the Adom project page in Hydrogen. This commits and pushes all project directory changes in one click — no git setup required.\n\n> **Important for AI agents**: This button requires the user's authenticated browser session. It **cannot be triggered programmatically from inside the Docker container** — the container does not hold the user's Hydrogen session token. Do not attempt to call the Adom REST API (carbon.adom.inc) to trigger a sync; it will return 401. Just tell the user to click the button.\n\nThe programmatic API (planned, not yet available) will expose the same functionality:\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| `POST` | `/repo/sync` | Push Docker container changes upstream to the main repo |\n| `GET` | `/repo/status` | Check sync status (pending changes, last sync time) |\n\n#### `POST /repo/sync` — request body\n\n```json\n{\n  \"message\": \"Updated motor control firmware\"\n}\n```\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `message` | string | Optional commit/sync message describing the changes |\n\n---\n\n## Viewers\n\nQuery how many users are currently viewing this project in their browser.\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| `GET` | `/viewers/count` | Number of active browser sessions viewing this project |\n| `GET` | `/viewers/list` | List of active viewers (usernames) |\n\n#### `GET /viewers/count` — response\n\n```json\n{\n  \"count\": 3\n}\n```\n\n#### WebSocket events\n\nSubscribe to viewer count changes in real time:\n\n```json\n{ \"type\": \"viewers.update\", \"count\": 3 }\n```\n\n---\n\n## 3D Layout Viewer\n\nControl the 3D layout editor panel in the front-end web app from the Docker container.\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| `POST` | `/layout/reload` | Reload the layout from its backing file (picks up any changes made on disk) |\n| `GET` | `/layout/camera` | Get the user's current view angle and camera position in the 3D viewer |\n| `POST` | `/layout/camera` | Set the view angle and camera position (animate the 3D viewer) |\n| `POST` | `/layout/animate` | Execute a scripted camera move sequence in the 3D viewer |\n| `POST` | `/layout/open` | Open a new layout panel showing an alternate layout file |\n| `POST` | `/layout/save` | Force the layout editor to save its current state to the backing file |\n\n#### `GET /layout/camera` — response\n\n```json\n{\n  \"position\": { \"x\": 288, \"y\": 288, \"z\": 500 },\n  \"target\": { \"x\": 288, \"y\": 288, \"z\": 0 },\n  \"zoom\": 1.0\n}\n```\n\n#### `POST /layout/camera` — request body\n\n```json\n{\n  \"position\": { \"x\": 100, \"y\": 200, \"z\": 300 },\n  \"target\": { \"x\": 100, \"y\": 200, \"z\": 0 },\n  \"zoom\": 2.0,\n  \"animate\": true,\n  \"duration\": 1000\n}\n```\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `position` | `{ x, y, z }` | Camera position in 3D space (mm) |\n| `target` | `{ x, y, z }` | Point the camera looks at (mm) |\n| `zoom` | number | Zoom level |\n| `animate` | boolean | Whether to smoothly animate to the new position or jump instantly |\n| `duration` | number | Animation duration in milliseconds (only used when `animate` is true) |\n\n#### `POST /layout/animate` — request body\n\n```json\n{\n  \"moves\": [\n    { \"position\": { \"x\": 100, \"y\": 100, \"z\": 400 }, \"target\": { \"x\": 100, \"y\": 100, \"z\": 0 }, \"duration\": 1000 },\n    { \"position\": { \"x\": 400, \"y\": 400, \"z\": 200 }, \"target\": { \"x\": 400, \"y\": 400, \"z\": 0 }, \"duration\": 2000 }\n  ]\n}\n```\n\nExecutes a sequence of camera moves in order — useful for creating guided tours or focusing on specific areas of the layout.\n\n#### `POST /layout/open` — request body\n\n```json\n{\n  \"file\": \"project-content/layouts/alternate-layout.json\"\n}\n```\n\nOpens a new layout panel showing the specified file. The original layout panel remains open.\n\n#### WebSocket events\n\n```json\n{ \"type\": \"layout.camera\", \"position\": { \"x\": 288, \"y\": 288, \"z\": 500 }, \"target\": { \"x\": 288, \"y\": 288, \"z\": 0 }, \"zoom\": 1.0 }\n```\n\nSubscribe to `layout.camera` to track the user's 3D viewer navigation in real time.\n\n---\n\n## Schematic Viewer\n\nControl the schematic editor panel in the front-end web app from the Docker container.\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| `POST` | `/schematic/reload` | Reload the schematic from its backing file (picks up any changes made on disk) |\n| `GET` | `/schematic/camera` | Get the user's current view position and zoom in the schematic viewer |\n| `POST` | `/schematic/open` | Open a new schematic panel showing an alternate schematic file |\n| `POST` | `/schematic/save` | Force the schematic editor to save its current state to the backing file |\n\n#### `GET /schematic/camera` — response\n\n```json\n{\n  \"page\": \"Main\",\n  \"position\": { \"x\": 500, \"y\": 300 },\n  \"zoom\": 1.0\n}\n```\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `page` | string | The name of the schematic page currently visible |\n| `position` | `{ x, y }` | Pan position in the 2D schematic view |\n| `zoom` | number | Zoom level |\n\n#### `POST /schematic/open` — request body\n\n```json\n{\n  \"file\": \"project-content/schematics/alternate-schematic.json\"\n}\n```\n\nOpens a new schematic panel showing the specified file. The original schematic panel remains open.\n\n#### WebSocket events\n\n```json\n{ \"type\": \"schematic.camera\", \"page\": \"Main\", \"position\": { \"x\": 500, \"y\": 300 }, \"zoom\": 1.0 }\n```\n\nSubscribe to `schematic.camera` to track the user's schematic viewer navigation in real time.\n\n---\n\n## WebSocket Reference\n\nConnect to the WebSocket endpoint to receive real-time events and send commands without polling.\n\n**Connection:** `ws://localhost:{{ADOM_API_PORT}}/ws`\n\n### Subscribing to events\n\nAfter connecting, send a subscribe message:\n\n```json\n{ \"type\": \"subscribe\", \"channels\": [\"camera.position\", \"viewers.update\", \"layout.camera\", \"schematic.camera\"] }\n```\n\n### Event types\n\n| Event type | Description |\n|------------|-------------|\n| `camera.position` | Camera gantry moved (x, y, zoom, and optionally pan/tilt) |\n| `viewers.update` | Active viewer count changed |\n| `message.received` | A message was sent to the web app (echo for confirmation) |\n| `repo.sync.complete` | Repo sync finished |\n| `layout.camera` | User navigated the 3D layout viewer (position, target, zoom) |\n| `schematic.camera` | User navigated the schematic viewer (page, position, zoom) |\n\n---\n\n## Future APIs\n\nThe following APIs are planned and will be documented here as they become available:\n\n- **Power control** — Programmatic control of control panel power rails (variable DC, 24V DC)\n- **GPIO control** — Read/write GPIO pins on the control panel\n- **Oscilloscope** — Trigger measurements and retrieve waveform data\n- **Robot shuttle** — Request scaffold moves between workcell, powered storage, and warehouse\n- **InstaPCB** — Submit fabrication jobs and check status programmatically. InstaPCB provides 4-hour PCB turnaround at JLCPCB-like pricing using UV fiber laser fabrication. Runs on ~14 dedicated \"tool workcells\" on the factory floor (separate from subscriber cloud workcells). Components sourced via Mouser drone delivery. Fabricated boards are delivered directly to the subscriber's cloud workcell for immediate testing. See `use-cases.md` for full details.\n\n---\n\n## Notes for AI agents\n\nWhen writing scripts that call the Adom API:\n\n- Always check `/camera/capabilities` before sending pan/tilt commands — the workcell may only have xyZoom.\n- Use the WebSocket for anything that needs live updates (camera tracking, viewer monitoring). Use REST for one-shot commands.\n- All coordinates are in **millimeters** relative to the base scaffold origin (0,0 is one corner, 576,576 is the opposite).\n- The API is only accessible from inside the Docker container. It is not exposed to the public internet.\n- **Project directory is root-owned**: `/home/adom/project` is owned by root by default. To write files there, use `sudo tee` (e.g. `echo \"content\" | sudo tee /home/adom/project/file.txt`) or fix ownership with `sudo chown -R $USER /home/adom/project`.\n- **Do not attempt programmatic repo sync**: The \"Commit & Save Project Files\" button in Hydrogen requires the user's browser session. Calling `carbon.adom.inc` from the container returns 401. Tell the user to click the button in the Hydrogen UI.\n",
  "author": {
    "id": "695820315b5f1e4db2fcf602",
    "name": "Kyle Bergstedt",
    "email": "kyle@adom.inc"
  },
  "visibility": {
    "public": true
  },
  "hero": null,
  "sample_prompts": [],
  "discovery_triggers": [],
  "discovery_pitch": null,
  "metadata": {},
  "created_at": "2026-05-28T05:30:22.615Z",
  "updated_at": "2026-05-28T05:30:22.615Z",
  "sub_skills": [],
  "parent_app": null
}