# THESEUS

> Attack path graph for penetration testing. Single binary — CLI + axum REST API + embedded Cytoscape.js frontend.

## Project Type

**fullstack** — Rust CLI binary with embedded HTTP server + vanilla JS frontend.

## Quick Reference

| What | Value |
|------|-------|
| Language | Rust 2021 (edition) |
| Framework | axum 0.7 |
| Runtime | tokio 1 (full) |
| Package Manager | cargo |
| Entry Point | `src/main.rs` (CLI), `src/lib.rs` (server) |
| Test Command | `cargo test` / `python3 tests/e2e_test.py` / `cd e2e && ./run_all.sh` |
| Dev Command | `cargo run -- serve --port 3080` |
| Database | SQLite (rusqlite, WAL mode, `theseus.db`) |
| Network | HTTP + WebSocket (inbound), DeepSeek API (outbound) |

## Structure

```
theseus/
├── Cargo.toml              # Single crate — [lib] + [[bin]]
├── src/
│   ├── main.rs             # CLI binary (~2656 lines) — clap derive, HTTP client
│   ├── lib.rs              # Server library (~901 lines) — axum app, AppState, middleware
│   ├── types.rs            # All shared DTOs, enums (~1147 lines)
│   ├── db.rs               # SQLite wrapper — 6 tables, CRUD + analysis (~1992 lines)
│   ├── agent/              # LLM agent system (llm.rs, loop.rs, memory.rs, prompts.rs, tools.rs)
│   └── routes/             # 16 route modules (3892 lines total)
├── static/
│   ├── index.html          # HTML skeleton (embedded via include_str!)
│   ├── css/style.css       # Dark theme, 30+ CSS variables (2645 lines)
│   └── js/                 # 14 vanilla JS modules
├── tests/                  # Standalone E2E test
├── e2e/                    # E2E test suite (10 files)
└── install.sh              # Build release + install to ~/.local/bin
```

## Architecture Overview

CLI commands (non-`serve`) are HTTP clients talking to a running server via reqwest. Server uses `AppState` with `Box::leak`'d `Database` (`Mutex<Connection>`), `broadcast::Sender` for WebSocket, and `Config` for API keys. After every mutation, server broadcasts `graph:{pid}` — frontend reloads graph on receipt.

→ [Full architecture analysis](docs/context/architecture.md)

## Key Modules

| Module | Purpose | Lines |
|--------|---------|-------|
| `src/main.rs` | CLI binary — all subcommands + HTTP client | ~2656 |
| `src/lib.rs` | Server core — AppState, middleware, ID resolution, DeepSeek client | ~901 |
| `src/types.rs` | All DTOs, enums, request/response structs | ~1147 |
| `src/db.rs` | SQLite CRUD + BFS analysis + health checks | ~1992 |
| `src/routes/` | 16 route modules (projects, nodes, edges, credentials, evidence, analysis, agent, ws, ...) | ~3892 |
| `src/agent/` | LLM agent loop with tool-calling (13 tools) | ~1446 |
| `static/js/` | 14 vanilla JS modules (graph, projects, nodes, ws, ...) | ~1998 |

→ [Full module breakdown](docs/context/modules.md)

## Tech Stack

| Category | Choice | Version |
|----------|--------|---------|
| Language | Rust | 2021 edition |
| Web Framework | axum | 0.7.9 |
| Database | rusqlite (bundled SQLite) | 0.31.0 |
| CLI | clap (derive) | 4.6.1 |
| Async Runtime | tokio | 1.52.3 |
| HTTP Client | reqwest | 0.12.28 |
| Frontend Graph | Cytoscape.js + ELK/Dagre | vendored |
| AI | DeepSeek API (deepseek-v4-flash) | via reqwest |

→ [Full dependency analysis](docs/context/tech-stack.md)

## Commands

```bash
cargo build                                  # Debug build
cargo check                                  # Fast compile check
cargo run -- serve --port 3080               # Start server
cargo test                                   # Unit tests
python3 tests/e2e_test.py                    # Standalone E2E
cd e2e && ./run_all.sh                       # Full E2E suite
./install.sh                                 # Build release + install to ~/.local/bin
```

## Conventions

- **axum routes**: `:param` ONLY. `{param}` silently 404s.
- **`NodeType::Custom(String)`**: `#[serde(untagged)]` — specific variants MUST come before Custom.
- **Static assets**: HTML embedded via `include_str!` (rebuild required). CSS/JS served at runtime (no rebuild).
- **Short IDs**: API accepts UUID prefix ≥6 chars everywhere.
- **CST timezone**: Daily reports use UTC+8, not server local time.
- **Error handling**: `AppError` with bilingual messages (EN/ZH). DB errors mapped via `from_db()`.
- **Row deserializers**: Hardcoded column indices in `db.rs` — SELECT reordering = silent bug.

→ [Full conventions](docs/context/conventions.md)

## API

REST API with 15 route modules. All responses wrapped in `ApiResponse<T>`. CLI subcommands map 1:1 to API endpoints.

| Key Endpoints | Method | Path |
|--------------|--------|------|
| Projects CRUD | POST/GET/DELETE | `/api/projects[/:pid]` |
| Nodes CRUD + filter | POST/GET/PUT/DELETE | `/api/projects/:pid/nodes[/:nid]` |
| Edges CRUD | POST/GET/PUT/DELETE | `/api/projects/:pid/edges[/:eid]` |
| Credentials | POST/GET/PUT/DELETE | `/api/projects/:pid/credentials[/:cid]` |
| Evidence upload | POST/GET/DELETE | `/api/projects/:pid/nodes/:nid/evidence[/:filename]` |
| Analysis | GET | `/api/projects/:pid/analysis?ai=true` |
| AI chat | POST | `/api/projects/:pid/tell`, `/api/projects/:pid/ask` |
| Agent | POST | `/api/agent/run` |
| WebSocket | WS | `/api/ws` |
| Search | GET | `/api/search?q=` |

→ [Full API reference](docs/context/api.md)

## Network

- **Inbound**: HTTP + WebSocket on `0.0.0.0:3080`. Optional API key auth (`x-api-key` / `Bearer` / `?api_key=`).
- **Outbound**: DeepSeek API (`https://api.deepseek.com/chat/completions`). Used by analysis, report, chat, agent.
- **CORS**: Permissive by default. Configure via `cors_origins` in `theseus.toml`.

→ [Full network analysis](docs/context/network.md)

## Database

SQLite (`theseus.db`) with WAL mode. 6 tables: `projects`, `nodes`, `edges`, `credentials`, `evidence_files`, `agent_memory`. All use UUID primary keys and RFC3339 timestamps. `Mutex<Connection>` with poisoned-lock recovery.

→ [Full database analysis](docs/context/database.md)

## Frontend

Vanilla JS (14 modules), no framework, no build step. Cytoscape.js with ELK/Dagre layout. Dark theme with 30+ CSS variables. HTML embedded at compile time (`include_str!`), CSS/JS hot-reloadable.

→ [Full frontend analysis](docs/context/frontend.md)

## Gotchas

- **`{param}` in axum routes** silently 404s — always use `:param`.
- **Editing `index.html`** requires `cargo build` — it's compiled in. CSS/JS don't.
- **SELECT column order** in `db.rs` is hardcoded — reordering without updating deserializers = silent data corruption.
- **Deleting DB record ≠ deleting file** — evidence files on disk must be cleaned up separately.
- **`#[serde(untagged)]` on `NodeType::Custom`** — must be last variant, or it swallows all deserialization.

---

*Generated by /init-local on 2026-01-09. Incremental update.*
