cctv 2.0.2

Terminal interface for CoolerControl
# Architecture

## Overview

```
┌─────────────────────────────────────────────────────────────┐
│                        cctv binary                          │
│                                                             │
│  ┌───────┐   ┌────────┐   ┌────────┐   ┌───────────────┐    │
│  │  CLI  │──►│ Config │──►│ Daemon │──►│  TUI / Dump / │    │
│  │ parse │   │  load  │   │  init  │   │   Daemonize   │    │
│  └───────┘   └────────┘   └────────┘   └───────────────┘    │
│                                                             │
└─────────────────────────────────────────────────────────────┘
                          HTTP + SSE
                    ┌───────────▼──────────┐
                    │   coolercontrold     │
                    │  (system daemon)     │
                    └──────────────────────┘
```

## Layered Architecture

```
┌──────────────────────────────────────────────────────────────┐
│  TUI Layer                    tui/                           │
│  ┌────────┐ ┌─────────┐ ┌──────┐ ┌───────┐ ┌──────┐          │
│  │ Device │ │ Profile │ │ Mode │ │ Alert │ │ Task │          │
│  │  Page  │ │  Page   │ │ Page │ │ Page  │ │ Page │          │
│  └───┬────┘ └────┬────┘ └──┬───┘ └───┬───┘ └──┬───┘          │
│      │           │         │         │         │             │
│      └───────────┴────┬────┴─────────┴─────────┘             │
│                       ▼                                      │
│              daemon.state() (read)                           │
│              daemon.change_mode() (write)                    │
│              daemon.update_profile() (write)                 │
├──────────────────────────────────────────────────────────────┤
│  Daemon Layer                 daemon/                        │
│  ┌────────────────────────────────────────────────────┐      │
│  │ Daemon { client, config, profiles, modes, state }  │      │
│  │           │                          │             │      │
│  │           │              Arc<RwLock<State>>        │      │
│  │           │              ┌─────────────────┐       │      │
│  │           │              │ devices, alerts, │      │      │
│  │           │              │ current_mode_uid,│      │      │
│  │           │              │ tasks            │      │      │
│  │           │              └────────▲─────────┘      │      │
│  │           │                       │ Message        │      │
│  │           │              ┌────────┴─────────┐      │      │
│  │           │              │  message handler │      │      │
│  │           │              │     thread       │      │      │
│  │           │              └────────▲─────────┘      │      │
│  │           │                       │ mpsc           │      │
│  │           │         ┌─────────────┼──────────┐     │      │
│  │           │     ┌───┴───┐  ┌──────┴──┐ ┌─────┴──┐  │      │
│  │           │     │  SSE  │  │   SSE   │ │  SSE   │  │      │
│  │           │     │ modes │  │ status  │ │ alerts │  │      │
│  │           │     └───┬───┘  └────┬────┘ └────┬───┘  │      │
│  │           │         └───────────┼───────────┘      │      │
│  └───────────┼─────────────────────┼──────────────────┘      │
├──────────────┼─────────────────────┼─────────────────────────┤
│  Domain Layer │       domain/      │                         │
│              │                     │                         │
│  Device::load()  Profile::load()  Mode::load()  Alert::load()
│       │               │              │              │        │
│       ▼               ▼              ▼              ▼        │
│    api structs ──► domain structs (richer models)            │
├──────────────────────────────────────────────────────────────┤
│  API Layer                    api/                           │
│                                                              │
│  Raw serde types: Devices, CCStatus, Profiles, Functions,    │
│                   Modes, Alerts, Sse                         │
│                                                              │
│  ┌───────────────────────────────────────────┐               │
│  │  Client { agent, url, cookie }            │               │
│  │  GET/POST/PUT ──► coolercontrold HTTP API │               │
│  └───────────────────────────────────────────┘               │
└──────────────────────────────────────────────────────────────┘
```

## SSE Event Flow

```
coolercontrold                       cctv
─────────────                  ──────────────────────

GET /sse/modes  ◄──────────── SSE thread (modes)
GET /sse/status ◄──────────── SSE thread (status)
GET /sse/alerts ◄──────────── SSE thread (alerts)
       │                             │
       │  event: mode/status/alert   │
       │  data: { json }             │
       ├────────────────────────────►│
       │                             ▼
       │                      parse into Sse enum
       │                             │
       │                      Message::Sse(event)
       │                             │  mpsc channel
       │                             ▼
       │                      ┌──────────────┐
       │                      │ msg handler  │
       │                      │    thread    │
       │                      └──────┬───────┘
       │                             │
       │              ┌──────────────┼──────────────┐
       │              ▼              ▼              ▼
       │        handle_mode    handle_status   handle_alert
       │              │              │              │
       │              ▼              ▼              ▼
       │        update mode    merge into      update state
       │        UIDs in State  device history  + append logs
       │              │              │              │
       │              └──────┬───────┘              │
       │                     ▼                      │
       │              run_matching_tasks() ◄────────┘
       │              (leader only)
       │                     │
       │              ┌──────┴──────┐
       │              ▼             ▼
       │         Script exec   ChangeMode / ActivatePrevious
       │                            │
       │  POST /modes-active/{uid}  │
       │  ◄─────────────────────────┘
```

## Task Automation

```
┌─────────────┐     ┌───────────────────┐     ┌──────────────────────┐
│   Trigger   │     │      Task         │     │       Action         │
├─────────────┤     ├───────────────────┤     ├──────────────────────┤
│ Alert       │────►│ name              │────►│ Script { path }      │
│  { uid,     │     │ state (lifecycle) │     │ ChangeMode { uid }   │
│    state }  │     │ trigger           │     │ ActivatePreviousMode │
│             │     │ action            │     └──────────────────────┘
│ ModeChange  │     └───────────────────┘
│  { uid }    │
└─────────────┘

Task lifecycle:   Applied ──► Draft ──► Unapplied ──► Applied
                                                 └──► Deleted

Leader election:  abstract unix socket "cctv-{url}"
                  only the leader instance executes tasks
```

## Config Resolution

```
┌──────────────────────────────────────┐
│ /etc/coolercontrol/config.toml       │  daemon_address, port
└──────────────────┬───────────────────┘
┌──────────────────────────────────────┐
│ $XDG_CONFIG_HOME/coolercontrol/      │  time_range_s, username,
│   cctv.json                          │  skip_splash, tasks,
│ (or $CCTV_CONFIG_FILEPATH)           │  address/port overrides
└──────────────────┬───────────────────┘
┌──────────────────────────────────────┐
│ Environment variables                │  CCTV_DAEMON_PASSWORD
└──────────────────┬───────────────────┘
┌──────────────────────────────────┐
│  Config                          │
│  { url,                          │
│  time_range, username, password, │
│  skip_splash, tasks }            │
└──────────────────────────────────┘
```

## Directory Structure

```
src/
  main.rs              Entry point, CLI dispatch
  cli.rs               CLI subcommands
  client.rs            HTTP client wrapper
  logging.rs           Logging setup
  config/
    mod.rs             Config loading and saving
  api/                 Raw wire types, deserialization only
    alert.rs
    device.rs
    function.rs
    mode.rs
    profile.rs         Profile PUT shim
    sse.rs
    status.rs
    mod.rs
  domain/              Domain types and business logic created from API data
    alert.rs
    device.rs
    mode.rs
    profile.rs
    mod.rs
  daemon/              Runtime state and coordination
    cctv.rs            Daemon struct, init, refresh
    state.rs           Mutable state, SSE handlers
    client.rs          SSE stream reader
    task.rs            Task, Trigger, Action types
    mod.rs
  tui/
    mod.rs             Render and input loop
    pages/
      mod.rs           Page trait, shared state
      alert.rs
      device.rs
      mode.rs
      profile.rs       Profile editor
      task.rs          Task editor
```