584da2d825
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.
210 lines
6.9 KiB
Markdown
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
|