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.
6.9 KiB
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:
PROJECT_MAP.md— architecture, decisions, data shapes, command tree, API surfaceROADMAP.md— current milestone, what's done, what's nextREADME.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, nottvctl 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, notVolumeUp, notvolume_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:
- Read the file you are about to change completely
- Read any files it imports or depends on
- 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
thiserrorfor error types - Use
tokiofor all async runtime - Use
axumfor HTTP server - Use
clap(derive API) for CLI - Use
serde+serde_jsonfor all serialization - Use
uuidcrate for UUIDs - Use
chronofor timestamps - Prefer
anyhowfor 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
hintfield - CLI errors must suggest the next action — not just report what went wrong
- API error
codevalues are stable contracts — do not change existing codes
CLI output
- Human-readable output by default
--jsonflag 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.codevalues are snake_case stringserror.hintis optional but strongly encouraged- Never return platform-specific field names at the API surface level
Help text
Every command must have:
- A one-line description
- A short paragraph of context
- A usage line
- All subcommands/args listed with descriptions
- At least two concrete examples
- 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:
- Move it from "In Progress" or its milestone section to "Completed"
- Add the completion date
- Update the "Current Focus" section if the milestone changed
When you start a task:
- Move it to "In Progress"
- 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
--jsonoutput works if it's a CLI command- Errors include helpful
hinttext - 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