Files
tvctl/AGENT.md
T
44r0n7 584da2d825 chore: scaffold tvctl foundation
Set up the Rust crate, baseline module layout, and project docs so the
repository matches the design bundle and builds cleanly as a starting point.
2026-04-14 09:02:32 -04:00

210 lines
6.9 KiB
Markdown

# AGENT.md
# Instructions for AI Agents Working on tvctl
# Read this file completely before writing any code.
---
## Step 1: Orient Yourself
Before doing anything else, read these files in this order:
1. `PROJECT_MAP.md` — architecture, decisions, data shapes, command tree, API surface
2. `ROADMAP.md` — current milestone, what's done, what's next
3. `README.md` — user-facing documentation (understand what you're building toward)
Do not skip any of these. Do not begin coding until you have read all three.
---
## Step 2: Understand What You Must Not Change
The following decisions are final and were made intentionally during design.
Do not re-open, re-litigate, or silently deviate from them:
- **Resource-verb CLI pattern** — `tvctl device list`, not `tvctl list-devices`
- **Unix socket for CLI↔daemon** — not TCP, not pipes
- **HTTP API for tool builders** — versioned at `/v1/`, loopback-only default
- **Adapter trait** — the exact interface defined in PROJECT_MAP.md
- **TOML config** — not YAML, not JSON
- **kebab-case key names** — `volume-up`, not `VolumeUp`, not `volume_up`
- **JSON response envelope** — `{ "ok": true, "data": {...} }` always
- **Organic app cache** — no pre-populated database, grows from live TV data
- **User-level systemd service** — not system-level, not root
If you believe one of these decisions is wrong, document your concern in a
comment or note and ask for clarification. Do not silently work around them.
---
## Step 3: Understand the Codebase Before Touching It
Before modifying any file:
1. Read the file you are about to change completely
2. Read any files it imports or depends on
3. Understand the data flow through the component you are changing
Do not make changes based on filenames or directory structure alone.
---
## Code Standards
### Rust conventions
- Use `thiserror` for error types
- Use `tokio` for all async runtime
- Use `axum` for HTTP server
- Use `clap` (derive API) for CLI
- Use `serde` + `serde_json` for all serialization
- Use `uuid` crate for UUIDs
- Use `chrono` for timestamps
- Prefer `anyhow` for application-level error propagation
- All public types must have doc comments
### Error handling
- Never use `.unwrap()` in non-test code unless you can prove it cannot fail
- Every error returned to the user (CLI or API) must include a `hint` field
- CLI errors must suggest the next action — not just report what went wrong
- API error `code` values are stable contracts — do not change existing codes
### CLI output
- Human-readable output by default
- `--json` flag must work on every command
- Errors go to stderr, data goes to stdout
- Do not mix human text and JSON in the same output stream
### API
- All endpoints return the standard envelope — no exceptions
- `error.code` values are snake_case strings
- `error.hint` is optional but strongly encouraged
- Never return platform-specific field names at the API surface level
### Help text
Every command must have:
1. A one-line description
2. A short paragraph of context
3. A usage line
4. All subcommands/args listed with descriptions
5. At least two concrete examples
6. A notes section for technical details (if applicable)
---
## File Responsibilities
| File/Directory | Responsibility | Notes |
|----------------|----------------|-------|
| `src/main.rs` | Binary entry point, daemon vs CLI dispatch | Keep thin |
| `src/cli/` | All clap definitions and CLI handlers | No business logic here |
| `src/daemon/` | Daemon lifecycle, routing, services | Core of the application |
| `src/daemon/registry.rs` | Device registry, persistence | Owns devices.json |
| `src/daemon/discovery.rs` | SSDP discovery, polling | Platform-agnostic |
| `src/daemon/cache.rs` | App cache, persistence | Per-platform json files |
| `src/daemon/state.rs` | In-memory state cache | Never persisted |
| `src/api/` | axum HTTP server, route definitions | Thin layer over core |
| `src/adapters/mod.rs` | TvAdapter trait, TvKey, shared types | The contract |
| `src/adapters/roku/` | Roku ECP implementation | Only place Roku logic lives |
The CLI and API layers must contain no business logic. They translate
user input into core calls and translate core results into output.
All logic lives in the daemon and adapter layers.
---
## App Resolution Flow
When a user runs `tvctl app launch netflix` or `POST /v1/devices/{id}/apps/launch`:
```
1. Check per-device installed app list in memory
2. If found → launch directly
3. If not found → check platform cache (roku.apps.json)
4. If found in cache → attempt launch (app might be installed)
5. If launch fails → report "not installed", suggest `tvctl app list`
6. If not in cache at all → fetch live app list from TV
7. Populate platform cache with all returned apps
8. Persist to cache file
9. Retry launch
```
Name matching is case-insensitive. Users can also pass raw platform IDs with `--id`.
---
## Discovery Flow
```
1. tvctld starts
2. If discovery.auto_discover = true → run SSDP scan
3. SSDP returns device IP addresses
4. For each address → instantiate appropriate adapter → call adapter.discover()
5. Adapter returns DeviceInfo
6. Check if device UUID already exists in registry
7. If new → add to registry, use device-reported name as default friendly name
8. If existing → update last_seen timestamp
9. Persist registry to devices.json
```
Manual add bypasses SSDP — takes IP and platform directly.
---
## Updating PROJECT_MAP.md
You MUST update PROJECT_MAP.md when you:
- Add a new source file
- Change the directory structure
- Implement a new CLI command or API endpoint
- Change a data shape
- Make a significant architectural decision
- Complete a roadmap milestone
Update the "Last updated" date at the top of PROJECT_MAP.md with every change.
Do not let PROJECT_MAP.md drift from the actual codebase.
---
## Updating ROADMAP.md
When you complete a task:
1. Move it from "In Progress" or its milestone section to "Completed"
2. Add the completion date
3. Update the "Current Focus" section if the milestone changed
When you start a task:
1. Move it to "In Progress"
2. Note what you're working on
---
## What To Do If You Are Unsure
If you are unsure about:
- **A design decision** → check PROJECT_MAP.md first. If not covered, ask.
- **What to build next** → check ROADMAP.md current milestone.
- **How a component should behave** → check README.md for user-facing behavior.
- **Whether to add a feature** → default to no. Prefer smaller scope.
When in doubt, do less. A smaller correct implementation is better than
a larger incorrect one. This project has a clear identity — do not add
things that don't serve it.
---
## Definition of Done
A feature is done when:
- [ ] It works correctly
- [ ] `--json` output works if it's a CLI command
- [ ] Errors include helpful `hint` text
- [ ] Help text is complete (description, usage, examples, notes)
- [ ] No `.unwrap()` in non-test paths
- [ ] PROJECT_MAP.md is updated if structure changed
- [ ] ROADMAP.md is updated to reflect completion