Local development workflow for TrunkScope contributors and coding agents.
If you are an OpenCode, Cursor, or other coding agent, start at CONTRIBUTING.md and ai-contributing.md before editing. Human developers can skip straight to prerequisites below.
| Tool | Version |
|---|---|
| Rust | 1.85+ (see rust-version in Cargo.toml) |
| Node.js | 22+ |
| pnpm | 10+ |
| Docker | Linux engine (for appliance image builds) |
apps/control-plane/ Rust binary (API + workers)
apps/web/ React/Vite frontend
crates/domain/ Shared types
native/radiod/ SDR daemon (Linux)
deploy/appliance.yml Supported deploy file for integration testing
deploy/compose.yml Deferred multi-service stack (dev reference only)
deploy/vllm-asr/ Optional Windows/WSL2 vLLM stack for Qwen3-ASR transcription
| Path | Status |
|---|---|
deploy/appliance.yml |
Supported operator install |
deploy/compose.yml |
Deferred dev reference only |
deploy/vllm-asr/ |
Optional external transcription host |
- Hooks order: never
returnearly before alluseState/useEffectcalls in a component. Early return after hooks is fine (seeApplianceDrawer.tsx). - Frequencies: use
MhzField+mhzToHz/hzToMhzinformat.ts— UI shows MHz, API stores Hz. - New entity IDs: use nil UUID
00000000-0000-0000-0000-000000000000, not"". Empty strings break UUID deserialization (HTTP 422). - API client: add helpers in
apps/web/src/api.ts; normalize payloads before POST when IDs or optional fields need cleanup.
- Shared settings/types:
apps/control-plane/src/state.rsandcrates/domain/. - Register new routes in
router()insideapi.rs; add#[cfg(test)]coverage when behavior is non-trivial. - Regenerate decoder config when receivers or systems change (
write_decoder_config).
- Models are discovered from configured endpoints (
providers.rs,integrationModels.ts). - Do not reintroduce manual ASR profile dropdowns.
- Appliance containers must use LAN IPs for AI URLs, not
localhost. - GPU transcription on Windows Docker Desktop requires WSL 2 backend, not Docker VMM — see
deploy/vllm-asr/README.md.
.env(any path) — use.env.exampletemplateshardware-acceptance.jsonunless from a real hardware acceptance run- Secrets, operator passwords, or live tokens from chat
# Rust
cargo fmt --all --check
cargo test --workspace
cargo clippy --workspace -- -D warnings # optional
# Web
pnpm install --frozen-lockfile
pnpm --filter @trunkscope/web lint
pnpm --filter @trunkscope/web test
pnpm --filter @trunkscope/web buildexport TRUNKSCOPE_RADIO_MODE=simulator
export TRUNKSCOPE_AI_ENABLED=false
cargo run -p trunkscope-control-planeAPI: http://127.0.0.1:8080
pnpm --filter @trunkscope/web devUI: http://127.0.0.1:5173 (proxies API to control plane if configured in Vite)
docker compose -f deploy/appliance.yml up -d --buildValidate Compose syntax:
docker compose -f deploy/appliance.yml config --quietDo not treat deploy/compose.yml as the operator install path. Use it only when explicitly testing the deferred multi-service layout.
- Bootstrap: environment variables (see configuration.md)
- Runtime: JSON files under a writable
TRUNKSCOPE_*_PATHtree - Simulator mode emits synthetic trunked calls without hardware
Optional legacy Postgres (TRUNKSCOPE_DATABASE_URL) mirrors writes when testing the deferred Compose stack. The appliance path uses SQLite only.
Linux-only. Build with SoapySDR when hardware is available:
# Hardware-independent build uses simulator backend
cargo build -p trunkscope-radiod| Module | Purpose |
|---|---|
api.rs |
HTTP routes, decoder config generation |
state.rs |
AppSettings, AppState, persistence paths |
decoder.rs |
Trunk Recorder WebSocket + ingest |
processor.rs |
AI call pipeline |
providers.rs |
External ASR/LLM/geocode adapters |
sqlite.rs |
Call history database |
retention.rs |
Scheduled retention enforcement |
imports.rs |
RadioReference CSV parsers |
receiver_presets.rs |
SDR driver defaults and capabilities |
- Handler in
apps/control-plane/src/api.rs - Register in
router() - Client helper in
apps/web/src/api.ts - UI in
ApplianceDrawer.tsxor relevant component - Test in
api.rs#[cfg(test)]module
- Protocol
analog-fmrequiresfrequencyHz,bandwidthHz(6250 | 12500 | 25000), andmodulation. - PL tone optional; blank means carrier squelch. CTCSS/DCS validated server-side.
- Assign
receiverIdwhen multiple SDRs are configured.
- Use
IntegrationModelField.tsxwithdiscoverTranscribeModels/discoverSummaryModels. - POST discovery overrides so models can be listed before settings are saved.
Operator docs live in docs/. Update them when changing install paths, env vars, or provider behavior. The index is docs/README.md.
Agent context for automation:
- AGENTS.md — architecture and live deployment facts
- ai-contributing.md — git and verification rules
- CONTRIBUTING.md — entry index for humans and agents
These are not a substitute for reading the code.
cargo test --workspace && \
pnpm --filter @trunkscope/web lint && \
pnpm --filter @trunkscope/web test && \
pnpm --filter @trunkscope/web build && \
docker compose -f deploy/appliance.yml config --quiet