Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ writer + concurrent readers).
| `ledger.rs` | (external only: `rusqlite`, `serde_json`, `sha2`) — callers pass `db_path: &str` + `retain_days`, so ledger stays decoupled from `config` | `strategies`, `proxy::*` |
| `config.rs` | `error` | Everything else |
| `error.rs` | – | – |
| `admin/*` *(POC: cockpit control API)* | `config`, `ledger`, `fsperm`, `proxy::*` read-only handles (external: `hyper`, `http-body-util`, `serde_json`, `getrandom`, `hex`) | `proxy::gateway` internals, `strategies::*` mutation — reads config/ledger only; **must not** share a surface with the OAuth-token-bearing gateway listener |
| `summarizer/mod.rs` | `config`, `proxy::upstream`, `strategies`, `reprune`, `ledger`, `summarizer::{api,harm_check,slice}` | `proxy::gateway` |
| `summarizer/api.rs` | `config`, `proxy::upstream`, `summarizer` (sibling constants) | `strategies`, `pairing`, `ledger` |
| `summarizer/harm_check.rs` | `summarizer` (sibling constants) | Everything else |
Expand Down
62 changes: 62 additions & 0 deletions app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# trimwire Flightdeck — desktop app shell (Tauri 2, POC scaffold)

A minimal **Tauri 2** shell that wraps the **same web cockpit** the trimwire binary
serves on its loopback control API. This is the "multi-platform app" layer of the
cockpit POC (see [`../docs/cockpit/05-multiplatform-app.md`](../docs/cockpit/05-multiplatform-app.md)).

> **Scaffold only — not wired into the Rust workspace or CI.** The trimwire repo
> root is a single Rust package (no `[workspace]`), so this nested `src-tauri/`
> crate is ignored by `cargo build`/`fmt`/`clippy` at the root. Building the app
> needs the Tauri toolchain (`npm`, `tauri-cli`) and is intentionally out of scope
> for the daemon's CI. It exists to show the shape.

## What it demonstrates

- **Shape A** from the plan (`05-multiplatform-app.md` §1): the app is a *thin
webview shell* pointed at the already-running daemon's cockpit
(`http://127.0.0.1:8766`). No second UI — it loads the exact frontend the
browser does.
- The Rust core is tiny (Tauri's value): `src-tauri/src/main.rs` is the standard
builder. The daemon is a *separate process* the app talks to over the loopback
control API — so there's no sidecar to notarize (sidestepping the worst signing
pitfalls, per `05-multiplatform-app.md` §3/§6).
- One frontend, many shells: browser PWA + this desktop app + a future mobile/
remote client are the same bundle with different transports.

## Run (requires the Tauri toolchain)

```bash
# 1. Start the daemon's cockpit (from the repo root):
trimwire cockpit # serves the control API + web UI on 127.0.0.1:8766

# 2. In another terminal, run the desktop shell:
cd app
npm install
npm run tauri dev # opens a native window onto the cockpit
```

## Layout

```
app/
package.json # tauri-cli dev/build scripts
dist/index.html # offline fallback shown if the daemon isn't up yet
src-tauri/
Cargo.toml # independent crate (NOT a root workspace member)
build.rs # tauri-build
tauri.conf.json # window points at the loopback cockpit (shape A)
src/main.rs # standard Tauri 2 builder
```

## Production notes (from the plan, not done here)

- Swap the hardcoded `127.0.0.1:8766` window URL for a discovered/configured base
URL once the control API's `[admin] listen` is read by the app.
- Offer **shape B** (bundle + manage the daemon as a sidecar) as an opt-in mode.
- Add desktop signing via `tauri-action`; defer mobile/iOS signing to the mobile
phase. Remote control (phone -> laptop) is the deferred, ToS-gated v3 phase
(`06-remote-control.md`) — the app just points the same window at a remote URL.
- **Lock Tauri's CSP + capabilities.** `src-tauri/tauri.conf.json` now ships a **restrictive
CSP** (not `null`), but capabilities still need scoping to deny-by-default before this ships —
the Aug-2024 Tauri audit found any-origin IPC + an unauthenticated dev-server disk exposure.
See `../docs/cockpit/10-security-fresh-sources.md` G5.
36 changes: 36 additions & 0 deletions app/dist/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<!doctype html>
<html lang="en" data-theme="dark">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>trimwire · Flightdeck</title>
<style>
:root { --accent: #2aa39c; --bg: #0f1417; --ink: #e6edf0; --dim: #8aa0a6; }
body { margin: 0; min-height: 100vh; display: grid; place-items: center;
background: var(--bg); color: var(--ink);
font: 15px/1.6 system-ui, -apple-system, Segoe UI, Roboto, sans-serif; }
.box { text-align: center; max-width: 30rem; padding: 2rem; }
b { color: var(--accent); }
a { color: var(--accent); }
.dim { color: var(--dim); font-size: 0.9rem; }
</style>
</head>
<body>
<div class="box">
<h1><b>trim</b>wire · Flightdeck</h1>
<p>Connecting to the local trimwire cockpit…</p>
<p class="dim">
If this persists, start the daemon's cockpit with
<code>trimwire cockpit</code>, then reopen this window.
The app loads the same control UI the binary serves on
<a href="http://127.0.0.1:8766">127.0.0.1:8766</a>.
</p>
</div>
<script>
// Shape A: this shell points at the running daemon's cockpit. The Tauri
// window URL (tauri.conf.json) already targets the loopback cockpit; this
// page is only the offline fallback shown before the daemon is reachable.
setTimeout(() => { window.location.href = "http://127.0.0.1:8766"; }, 1200);
</script>
</body>
</html>
14 changes: 14 additions & 0 deletions app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "trimwire-flightdeck",
"version": "0.1.0",
"private": true,
"description": "Tauri 2 desktop shell for the trimwire cockpit (POC scaffold)",
"scripts": {
"tauri": "tauri",
"dev": "tauri dev",
"build": "tauri build"
},
"devDependencies": {
"@tauri-apps/cli": "^2"
}
}
21 changes: 21 additions & 0 deletions app/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Independent crate — NOT a member of the trimwire root package (the root has no
# [workspace], so `cargo build` at the repo root ignores this). Build it with the
# Tauri toolchain (`npm run tauri build`), never as part of the daemon's CI.
[package]
name = "trimwire-flightdeck"
version = "0.1.0"
edition = "2021"
description = "Tauri 2 desktop shell for the trimwire cockpit (POC scaffold)"
publish = false

[build-dependencies]
tauri-build = { version = "2", features = [] }

[dependencies]
tauri = { version = "2", features = [] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"

[features]
# Keep the default Tauri custom-protocol feature for production bundles.
custom-protocol = ["tauri/custom-protocol"]
3 changes: 3 additions & 0 deletions app/src-tauri/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
tauri_build::build();
}
18 changes: 18 additions & 0 deletions app/src-tauri/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// trimwire Flightdeck — desktop shell (POC scaffold).
//
// Deliberately tiny: the window (configured in tauri.conf.json) points at the
// daemon's loopback cockpit (http://127.0.0.1:8766), so this shell reuses the
// exact web frontend the trimwire binary serves. The daemon is a *separate*
// process reached over the loopback control API — there is no sidecar here, so
// nothing to notarize beyond the app itself. See ../README.md and
// ../../docs/cockpit/05-multiplatform-app.md.
#![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]

fn main() {
tauri::Builder::default()
.run(tauri::generate_context!())
.expect("error while running trimwire Flightdeck");
}
29 changes: 29 additions & 0 deletions app/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "trimwire Flightdeck",
"version": "0.1.0",
"identifier": "dev.trimwire.flightdeck",
"build": {
"frontendDist": "../dist"
},
"app": {
"windows": [
{
"title": "trimwire · Flightdeck",
"width": 980,
"height": 740,
"minWidth": 720,
"minHeight": 520,
"url": "http://127.0.0.1:8766"
}
],
"security": {
"comment": "Scaffold CSP — restrictive by default (the Aug-2024 Tauri audit found any-origin IPC + unauthenticated dev-server disk exposure). Tighten/scope capabilities before shipping; see ../docs/cockpit/10-security-fresh-sources.md G5.",
"csp": "default-src 'self'; connect-src 'self' http://127.0.0.1:8766; img-src 'self'; style-src 'self'"
}
},
"bundle": {
"active": true,
"targets": "all"
}
}
124 changes: 124 additions & 0 deletions docs/cockpit/01-research-grounding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# 01 — Research & Grounding: what the cockpit builds on

> What trimwire exposes **today**, and the gaps a cockpit must fill. All claims are
> code-grounded (file:line) from a read-only exploration pass. This is the factual base
> the rest of the plan stands on.

## 1. Control surface (what you can drive today)

trimwire runs as a daemon: `trimwire serve` (alias `daemon`), binding **`127.0.0.1:8765`** by
default (`src/config.rs` `ServerConfig::default`), upstream `https://api.anthropic.com`.
Service lifecycle auto-detects **systemd** (Linux), **launchd** (macOS), or a **supervisor**
fallback (WSL2, pidfile `~/.trimwire/daemon.pid`) — `src/cli/service.rs`. systemd/launchd use
**socket activation**.

**The entire HTTP surface today is two endpoints** (`src/proxy/gateway.rs`):

- `GET /healthz` → `{"ok":true,"version":"x.y.z"}`, answered locally, never forwarded.
- `POST /v1/messages` → the Anthropic proxy passthrough (this connection carries the OAuth
`Authorization: Bearer` token).

**There is no management/control API.** Everything else is CLI:

| Group | Commands |
|---|---|
| Lifecycle | `install`, `uninstall`, `on`, `off`, `status`, `doctor`, `update`/`upgrade` |
| Inspect | `stats` (`--json`), `recall` (`--json`), `preview` (`--json`), `dashboard` (HTML report) |
| Summarizer | `summarizer setup/status/benchmark/probe` |
| Share | `share enable/disable/stats/benchmark` |
| Maintenance | `sweep list/all/file/undo`, `config show/edit` |
| Shell | `statusline`, `completions`, `man` |
| Hidden | `serve`/`daemon`, `run`, `hook` |

**Config** is a figment merge of global `~/.config/trimwire.toml` + project `./.trimwire.toml`
+ `TRIMWIRE_*` env. Crucially, **`[server] upstream` is never read from a project file** —
that key decides where the OAuth token is sent, so honoring it from a cloned repo would let a
project redirect your token (`src/config.rs`, with a regression test). **The daemon reads
config only at startup** — any change needs an `off → on` restart. There is no config *write*
API; only `config edit` ($EDITOR).

### Gaps the cockpit must fill

1. **No local control API** — only `/healthz`. No config read/write, no service toggle over
HTTP, no strategy tuning.
2. **No live event stream** — state can only be polled.
3. **No hot-reload** — config changes require a restart.
4. **No config-validation endpoint** (only `doctor`, CLI-only).
5. **No structured live metrics export** beyond `stats --json` snapshots.

## 2. Data surface (what a dashboard can show)

The **ledger** is a local SQLite DB (`~/.trimwire/ledger.db`, `src/ledger.rs`) with three
tables: `requests` (one row per `POST /v1/messages`), `summarizer_events`, `upstream_errors`.

**Content-free guarantee (confirmed in code):** the ledger stores only byte counts, token
counts (input / cache_read / cache_creation / output), **prefix hashes** (with `messages`
removed), timestamps, `session_id`, model name, strategy names + per-strategy elided bytes,
TTFT (µs), and status codes. **Never** message content, prompts, tool results, or file paths.
*This is structural: the data to leak simply isn't there.*

Already-available metrics (all via existing `--json`):

- **`stats --json`**: totals, `bytes_saved`, `reduction_pct`, `est_tokens_removed`, `per_day`
timeseries, `per_strategy` (count + bytes), `cache_stability` ratio, `response_metrics`
(avg TTFT, token buckets, `cache_hit_pct`), summarizer outcomes, upstream errors.
- **`stats --session <id> --json`**: per-session, per-model breakdown.
- **`recall --json`**: recent sessions (id, last_day, requests, bytes, tokens, model).
- **`preview <file> --json`**: deterministic what-if prune estimate per profile — powers a
live "what would be pruned" pane *without touching the daemon* (strategies are pure fns).
- **`trimwire dashboard`**: already emits a **self-contained HTML report**
(`src/cli/dashboard.rs` + `src/cli/dashboard_template.html`).
- **`statusline`**: a live per-session reduction signal Claude Code renders after each turn.

**The 9 strategies** (`src/strategies/mod.rs`): `failed_input_purge`, `stale_input_cap`,
`cross_turn_dedup`, `stale_reads`, `simhash_dedup` (opt-in), `bloat_cap`, `sliding_window`,
`image_strip`, `thinking_strip`. **Two profiles**: `default` (aggressive) and `gentle`
(conservative). The product guardrails forbid a third profile and any intensity dial.

### Useful data that does NOT exist yet (dashboard opportunities, not v1 blockers)

Per-strategy latency cost; strategy fire-rate trend; cache-busting root cause; compression by
model; summarizer cost/latency; reprune checkpoint timeline; sub-agent (sidechain) metrics;
ledger growth/retention pressure. All are additive ledger columns or queries — defer until a
pane proves it needs them.

## 3. Web & telemetry surface (what to align with / reuse)

**Site** (`site/`): **Astro 6 + Starlight 0.40**, TypeScript, **vanilla DOM (no React/Vue)**,
Vitest+jsdom, Playwright. Deployed static (trimwire.dev). It already has interactive,
dependency-free components built exactly like the cockpit needs:

- `CommunityDashboard.astro` + `dashboard.ts` — sortable sticky tables, in-cell bars, KPI
strip, expandable detail rows.
- `BenchmarkTable.astro`, `Hero.astro` (flow diagram + before/after bars).

**Design system** (`site/src/styles/custom.css`): accent teal **`#2aa39c`** (dark `#178a83` /
light `#9fe7e2`); semantic good `#22c87a` / warn `#e0a000` / bad `#e85d3f`; radius `0.55rem`;
3px top-accent cap; tight headings (`-0.02em`); `tabular-nums` data tables; dark/light parity
via `:root[data-theme]`; `prefers-reduced-motion` respected.

**Collector** (`collector/`): a Cloudflare Worker + D1 that ingests **k-anonymous, content-free
community aggregates** (`POST /ingest`, `GET /aggregates.json`, benchmark equivalents). This is
the *community* backend — **the local cockpit reads the local `ledger.db`, not the collector.**
The collector is a model for privacy discipline, not a data source for the cockpit.

**Brand voice:** restrained, technical, honest ("headroom, not dollars"), data-first, minimal
ornament. The cockpit should honor the *cockpit metaphor* but pick an on-brand, non-hype
visible name (see doc 04).

## 4. Constraints inherited from AGENTS.md (carried into every later doc)

- Single static binary, **no heavy runtime deps**; latency is acceptable, weight is not.
- Transparent **ToS-compliant** proxy: never originate model calls on the subscription token;
**no detection-evasion** ("transparent" = faithful, not undetectable).
- `main` is protected; every change is a PR through green CI: `fmt + clippy + test`,
MSRV 1.85, **Python parity oracle**, cargo-deny/audit, 3 cross-platform builds.
- Layer rules: `gateway.rs` must not contain mutation logic; `strategies/*` are pure (no I/O);
`ledger.rs` is the only SQLite I/O; new modules require an `ARCHITECTURE.md` update.

## Takeaway

The cockpit is **~80% presentation over data and verbs that already exist**, plus **one new
backend component** (the control API, doc 03). That framing — reuse the HTML-report data, the
vanilla-DOM components, the design system, and the CLI verbs — is what keeps the build
proportionate to trimwire's size and ethos.
Loading
Loading