Commit Graph

39 Commits

Author SHA1 Message Date
leetcrypt 3a54578f0a perf(net): near-real-time sandbox for non-host viewers
Two causes made a shared shell's PTY stream lag for non-host viewers and
then arrive "all at once":

1. Nagle's algorithm — PTY echo/keystrokes/chat are tiny frames; Nagle +
   delayed-ACK (amplified by Tailscale RTT) coalesced them into bursts.
   Set TCP_NODELAY on the client websocket (Plain/--no-tls path) and on
   each server connection's socket (via ws.io_proto.transport).

2. Head-of-line blocking — ConnectionManager.broadcast awaited each
   send() serially under a global lock, so the slowest viewer gated
   delivery to everyone AND stalled the server's read of the broker's
   next frame, backing the stream up. Give each connection its own
   bounded outbound queue + writer task; broadcast now only enqueues and
   never waits on a socket. A peer that overflows its backlog (4096) is
   evicted (close 1013) and resyncs on reconnect rather than buffering
   without bound or being fed a gapped terminal stream. Init snapshot is
   enqueued as the guaranteed-first frame (send_state -> state_frame).

Adds tests/test_manager.py (FIFO, slow-client isolation, wedged
eviction, reconnect replacement). Also fmt-clean sbx.rs/theme.rs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-06 17:11:51 -07:00
leetcrypt c552ece6b1 feat(theme): WCAG-legible backgrounds + oc-demo theme & reaction assets
Derive each rolled palette's surface from its ink via legible_bg so the
background is as expressive as it can be while keeping chat text above a
contrast floor. Adds the oc-demo theme and reaction images.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-06 16:47:57 -07:00
leetcrypt d6d44128c0 feat(sbx,ai): snapshot all backends w/ local export; robust AI + rejoin
- /sbx save [--local] and new /sbx vmsave/vmsnaps: snapshot Docker,
  Multipass, and VirtualBox; --local also writes a portable artifact
  (docker .tar / VBox .ova) under hh-snapshots/ that survives pruning.
- sandbox reappears for anyone who leaves and rejoins (host replays
  status + screen snapshot + ACL on Joined); SbxStatus ready handler is
  now idempotent so it never wipes scrollback.
- received files auto-bridge into the hosted sandbox (ft::tar_path).
- AI agent: translate Ollama's cryptic 404 into model/host/fix guidance.
- bootstrap installs the AI layer (Ollama + default model) by default,
  with consent gates; --no-ai opts out, --yes skips prompts.
- help menu lists the new save/vm flags.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-06 16:47:57 -07:00
leetcrypt 70245cbd9d feat(hh): random room password by default; join.sh syncs before connect
- lets-hack.sh: default PW is now `openssl rand -hex 12` (printed in the
  summary for sharing) instead of the well-known hardcoded `malware-bless`
- join.sh: drop the hardcoded password (uses $HH_PASSWORD or a no-echo
  prompt), and fast-forward pull from gitea+origin then `cargo build`
  before connecting so joiners run the latest code

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-06 14:42:12 -07:00
leetcrypt 2c4a4f9a22 harden(ft,auth,net): cap transfers/frames, evict stale SRP, distrust XFF
CI / rust client (hh) (macos-latest) (push) Has been cancelled
CI / rust client (hh) (ubuntu-latest) (push) Has been cancelled
CI / rust coverage (push) Has been cancelled
CI / python server (3.10) (push) Has been cancelled
CI / python server (3.11) (push) Has been cancelled
CI / python server (3.12) (push) Has been cancelled
CI / headless e2e smoke (push) Has been cancelled
CI / dependency audit (push) Has been cancelled
CI / secret scanning (push) Has been cancelled
M1: enforce the declared transfer size (clamped to MAX_SIZE) on chunk
receipt in both the Rust and Python clients — a malicious sender can no
longer grow the receive buffer unboundedly.
M2: only honor X-Forwarded-For when TRUST_PROXY is set, so a direct
client can't spoof a source IP to dodge the per-IP rate limiter.
M3: evict unverified SRP sessions after a 60s TTL on each new handshake,
preventing half-finished auths from exhausting memory.
M4: drop WS frames larger than 256 KB before they hit the store or
broadcast, bounding per-message memory and flood blast radius.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-05 06:59:16 -07:00
leetcrypt 5676216a2f feat(ui): stacking role badges in roster + chat; drop default pentagram
- introduce a Role model + App::host()/roles_of(): the host is the first
  occupant of the roster (shown the moment they join, no sandbox required),
  and roles are additive — a host who summoned a sandbox and can drive reads
  ✝◆. Badges read the same ACL the broker enforces, so they can never
  advertise a power the room won't honour
- render the stacked badge in the clergy panel and inline next to the author
  on every chat message; split VirtualBox commands into their own help cluster
- default styling: the startup handle prompt now prints ✝ (crypt sigil)
  instead of the inverted pentagram
- README: document VirtualBox VMs, snapshot save/load, AI streaming + recall,
  the badge system, and the expanded theme set
- gitignore out-of-tree experiments, the heavy demo-build kit, and logs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-04 22:56:10 -07:00
leetcrypt 01e607dced test(client),ci: fuzz frame parsers, VT-x classifier tests, smoke + CI hardening
- proptest-fuzz the untrusted frame parsers (sbx/ai/perm/users/decode_msg) so
  a hostile relay/peer can never panic a client; fixes a decode_msg timestamp
  byte-slice that panicked on a non-ASCII stamp (now char-boundary safe)
- extract a pure classify_vtx_holders() out of vtx_holders() and unit-test the
  KVM/QEMU/multipass detection and stoppability rules
- headless cross-stack smoke test (smoke-e2e.sh): real relay + two TUI clients
  in tmux, asserting SRP join, Fernet chat round-trip, and command dispatch
- CI: macOS matrix for the Rust client, cargo-audit + pip-audit, gitleaks
  secret scan, llvm-cov/pytest-cov coverage, and a smoke-test job

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-04 22:56:00 -07:00
leetcrypt 7519df1695 feat(sbx): VirtualBox detect-first install + local GUI VM launch
Integrate VirtualBox as a local facility rather than a shared-PTY backend: a
Windows guest has no shell to relay, so the honest fit is launching the VM's
GUI on the caller's own machine (the "share a VM, run it locally" path) — no
display is relayed to the room, so zero-knowledge is untouched.

- ensure-vbox.sh: detect-first installer mirroring ensure-docker.sh; --check,
  --plan (real apt --simulate download plan, no changes), --yes; apt/dnf/
  pacman/brew/winget; Secure Boot MOK warning. HH_VBOX_FORCE_MISSING lets a
  demo exercise the missing->install path without uninstalling.
- sbx.rs: vbox_installed/vbox_version/list_vms/vm_running/gui_launch +
  ensure_vbox_install.
- app.rs: /sbx vms (detect + list) and /sbx gui <vm> [--install] (detect-first
  then startvm --type gui); /sbx launch virtualbox steers to /sbx gui.
- ui.rs help: /sbx vms and /sbx gui entries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-03 10:41:32 -07:00
leetcrypt 60168a4341 feat(ui): help entries for AI agent sandbox drive
Document /ai start <model> allow, /ai <name> !<task>, and the agent-aware
/grant|/revoke variants in the clustered help menu.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-03 10:10:49 -07:00
leetcrypt ca1666fbbb docs(sbx): VirtualBox backend spec, crypto pay-gate, save/load PoC
Add the VirtualBox sandbox design spec (headless 4th backend + share-an-
appliance GUI mode with detect-first install), the crypto pay-to-join gate
design, and the save/load PoC writeup with its demo/film driver scripts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-03 10:10:44 -07:00
leetcrypt 07e9c30846 feat(sbx,ui): VM snapshot save/load + collapsible clustered help menu
- /sbx save|load|snaps: docker commit → hh-snap:<label> image that
  survives /sbx stop; load relaunches a fresh sandbox from it; multipass
  delegates to `multipass snapshot`. Local backend unsupported.
- Help overlay redesigned into topical clusters (SANDBOX, AI AGENTS,
  PERMISSIONS, FILES, APPEARANCE, KEYS, ROSTER GLYPHS), collapsed by
  default; up/down highlight a cluster, left/right/Enter expand-collapse
  it (tmux-style), PgUp/PgDn scroll overflow, Esc closes.
- docstring: example uses --model qwen2.5:3b (the locally-pulled model),
  not llama3.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-02 23:03:00 -07:00
leetcrypt 69bce5ead8 feat(ai): stream agent replies token-by-token to the room
Closes the cross-language half of token streaming (perf-plan A3). On the
CPU-only box perceived latency is time-to-first-token, so showing the reply
as it generates makes a slow model feel live.

- Agent: OllamaProvider.stream() runs on a worker thread; bridge relays
  cumulative previews as throttled (~5/sec) `_ai:"stream"` control frames,
  then a `done` frame clears the preview as the final persisted chat message
  is posted. Providers without stream() fall back to blocking complete().
- Rust client: new Net::AiStream variant + parse_ai branch; App.ai_stream
  map holds the in-progress text per agent; draw_chat renders it as a dim,
  italic preview bubble below history. Cleared on done and on agent leave.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-02 22:42:08 -07:00
leetcrypt 00c1f1c3c9 feat(theme): add theme randomizer and save-to-disk
Theme::random() conjures a fresh procedural vestment — a coherent HSV
palette (dark tinted surface, one bright accent, legible ink), a random
sigil, and a generated arcane name. Bound to Ctrl+Alt+P and `/theme random`.

Theme::save() persists the vestment you're wearing to themes/<slug>.toml
(via `/theme save [name]`), so a roll you like can be re-donned later with
`/theme <name>`. Theme now derives Serialize and slugify() sanitizes the
filename. Help text and the /theme usage line advertise both verbs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-02 17:39:37 -07:00
leetcrypt 0417ef89cc feat(theme): add goldcrypt vestment preset
A saved random() roll: gold ink and steel-blue borders on a deep slate
surface. Self-registers via THEMES_DIR, so `/theme goldcrypt` resolves it
at runtime alongside the other bundled presets.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-02 17:39:31 -07:00
leetcrypt 47019dd630 feat(ai): let agents drive the sandbox on request (/ai <name> !<task>)
Agents can now run commands and build files in the shared sandbox, but
only when explicitly invoked with the `!` verb and only while the owner
has granted drive. Reuses the existing driver ACL + `_sbx:input` frames:
the Python agent emits the same input frames a human driver does, gated
by the broker's `app.drivers` check — no new transport.

Guardrails: a regex gate holds destructive commands until `/ai <name>
confirm`; blast-radius caps (20 cmds / 8KB); the agent echoes its plan to
the room before running (audit trail). Owner controls: `/grant`, `/ai
start <model> allow` to pre-grant on spawn, and a Ctrl-X panic kill
switch (revoke all non-owner drive + Ctrl-C the shell). The broker now
re-broadcasts the ACL on join so a freshly-summoned agent actually
receives its grant.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-02 16:42:24 -07:00
leetcrypt 9158a488f7 fix(ui): batch-drain incoming frames so a sandbox stream can't stall chat
The reader funnels both chat and high-volume _sbx:data terminal frames
through one channel, and the UI loop redraws after handling a single frame
per turn — so on a viewer's side each chat message queued behind hundreds
of sandbox frames only surfaced one-per-redraw, making chat appear to
buffer/stall whenever a shared shell was scrolling output.

Drain a bounded burst (up to 256) of ready frames per turn via a new
drain_ready() helper, keeping chat latency bounded no matter how hard the
sandbox is streaming. Add regression tests covering FIFO/cap behavior and
chat surfacing within a few turns under flood.

Also add connect.sh: a join helper with a default port that keeps the room
password in RAM only (no-echo prompt or env var, never written to disk).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-02 14:20:40 -07:00
leetcrypt ea67796551 fix(ui): move websocket writer off the UI loop to stop input/chat lag
Outgoing frames were drained inline in the main select! loop with a blocking
sink.send().await. While a sandbox streams its PTY to the room, those _sbx:data
frames flood the socket; if the server backpressures (e.g. relaying to a remote
peer), each await stalled the loop, so keystrokes and incoming chat arrived in
laggy bursts. Hand the write half to a dedicated writer task; reconnect passes
it a fresh sink. Disconnects are still detected by the reader (Net::Closed).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-01 23:04:59 -07:00
leetcrypt ee9d0f7ff9 feat(client): prompt for a handle on join when none is given
Make the connect `user` arg optional. When omitted, the client prompts
"choose your handle" as the first thing on join (before the TUI opens) and
re-prompts if the server rejects the name (e.g. already taken, 409). Passing
a name on the CLI still works unchanged, so the demo script is unaffected.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-01 22:31:48 -07:00
leetcrypt b4c5f9a9fa feat(ui): scrollable help menu, Esc-to-close, + blush/matrix/wraith themes
The help overlay now scrolls (↑/↓, PgUp/PgDn, Home/End) with a position
indicator and only Esc dismisses it, so stray keystrokes can't close a menu
that overflows the screen. Adds three bundled vestments (blush, matrix,
wraith); they're auto-discovered via Theme::available(), so they appear in
the menu and /theme list with no hardcoded entries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-01 22:09:08 -07:00
leetcrypt 65df12de9e feat(ai): model profiles, capability discovery, and agentless /ai list|models
Make connecting any model a config step, not a code change:
- models.toml named profiles (api_key_env names an env var, never the key)
- providers gain available_models(); add preflight + --list-models/--check
- /ai list and /ai models in-room; client probes local Ollama for
  /ai models when no agent is running, and /ai list hints to summon one
- docs/providers.md provider guide + examples/echo_provider.py
- README: command table, AI section, layout updated

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-01 15:25:07 -07:00
leetcrypt 05bdc2d802 feat(ai): /ai start|stop agent control + in-room typing indicator
Owner of the spawning client can summon/dismiss a local AI agent from inside
the room (default ollama/qwen2.5:3b); the agent emits encrypted typing frames
that drive a "thinking" spinner in the client.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-01 11:38:15 -07:00
leetcrypt 54b7637ec8 feat(agent): model-agnostic AI agent bridge (PoC) + pin lets-hack demo to main
Add cmd_chat/agent: a headless client that joins a room via SRP, decrypts
broadcasts, and answers /ai <question> through a pluggable model provider
(ollama default + anthropic + openai-compatible + module:Class). Server and
zero-knowledge guarantees unchanged; the agent is just another encrypted client.

Also pin the lets-hack demo to a detached worktree of main (default) so running
it from dev still demos stable main without touching the working checkout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-01 02:05:48 -07:00
leetcrypt 8eacf4d27b ci: proper Rust+Python CI workflow; cargo fmt + clippy clean
Replace the stale Django CI template with a CI workflow that builds and
tests both codebases: cargo fmt/clippy/build/test for the hh client and
pytest across Python 3.10-3.12 for the server. Apply cargo fmt and fix
all clippy lints so the gates pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-01 00:52:20 -07:00
leetcrypt ff5186a9d3 feat(hh): graceful shutdown, crypt default theme, neutralize branding, share-prep
- Graceful shutdown: Ctrl+C quits in chat (interrupts PTY while driving),
  RAII TermGuard + panic hook + SIGTERM/SIGHUP always restore the terminal
- Default theme is now "crypt" (neutral monochrome); theme sigil mirrored in
  chat/roster/help so the pentagram only renders under the "church" theme
- Neutralize inverted-pentagram branding across CLI, scripts, docs, and Cargo
  metadata (kept only in themes/church.toml + the render-time placeholder)
- Rewrite root README around hack-house; add bootstrap.sh, SECURITY.md,
  CODE_OF_CONDUCT.md, CHANGELOG.md, and issue/PR templates
- .gitignore cleanup; stop tracking .venv

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-31 23:23:19 -07:00
leetcrypt 5de493e895 feat(hh): /pw command, RAM-only direnv autostart, robust lets-hack; coven→clergy
- add /pw (alias /password): reveal this room's password locally (never
  broadcast); surfaced in the F1 help overlay and the join hint
- direnv-autostart/: cd-to-launch a single real-user session via direnv;
  password is minted in memory at launch (never written to disk, matching the
  RAM-only model) and scoped to the child process. setup.sh installs direnv,
  hooks bash/zsh, and `direnv allow`s the dir
- lets-hack.sh: boot a FRESH server by default (replacing any live one) with a
  --reuse opt-out; add -h/--help/-help; guard against killing the tmux session
  you're attached to; switch-client into the coven when run inside tmux
- rename coven→clergy across rust/python/scripts; tests/test_coven.py→test_clergy.py
- snapshots in-progress hack-house client work (sandbox, themes, net, ui)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-31 22:29:17 -07:00
leetcrypt 8e6365a649 feat(hh): help overlay (F1 / /help)
Centered modal listing every command, keybinding, and roster glyph. Opens with
F1 (desktop) or the /help command (phone-friendly, since F-keys aren't on the
Termux keyboard); any key closes it. Rendered with a Clear overlay so it floats
above the panes. Works from chat or drive mode; Ctrl-Q still quits.

9 tests pass; clean build; verified live.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 23:20:08 -07:00
leetcrypt 51bc85e078 feat(hh): scrollback for chat + sandbox terminal
- Chat history: PgUp/PgDn (page), arrows (line when no sandbox), Home=oldest,
  End=live. Viewport holds steady when new lines arrive while scrolled up;
  sending a message jumps back to live. Backlog capped at 4000 lines.
- Sandbox terminal: vt100 parser now keeps 2000 rows of scrollback; ↑/↓ scroll
  it when not driving (arrows still go to the shell while driving). Offset
  applied each frame; reset on dismiss / End.
- Title indicators: 'chat ↑N (End=live)' and 'sandbox · ↑N scrollback'.

Termux's extra-keys row has arrows + PgUp/PgDn/Home/End, so it's phone-usable.
9 tests pass; clean build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 23:10:36 -07:00
leetcrypt d6595935d3 fix(hh): instant sandbox render + working Ctrl-C for the owner
The owner's keystrokes and terminal output used to round-trip through the
server, so output lagged (appeared only on the next keypress) and Ctrl-C got
queued behind a flood of outgoing output frames (e.g. 'tree') — so it never
interrupted and the command seemed to hang.

- Owner writes drive keystrokes straight to the local PTY (instant; Ctrl-C is
  never starved). Remote drivers still relay via the server.
- Owner renders its sandbox locally from the PTY and ignores its own echoed
  data/status frames (broker.is_none gate); others still render from echoes.
- Coalesce PTY output bursts into one frame (no flood).
- select! is biased on keyboard input; tick 120ms -> 50ms for snappier redraws.

Verified live: echo renders with no extra key; sleep+Ctrl-C interrupts cleanly.
9 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 22:10:18 -07:00
leetcrypt 1445b1151a feat(hh): /drive command (mobile-friendly) + Esc no longer quits
F2 is unreachable on phone/Termux keyboards, so add a /drive chat command to
enter sandbox drive mode (type into the shared shell; Esc releases). F2 still
works on desktop. Esc no longer quits from chat mode (footgun on mobile) — quit
is Ctrl-Q only. Updated on-screen hints + sandbox pane title.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 22:03:00 -07:00
leetcrypt 064ee67162 feat(hh): Church of Malware neon theme is now the default ⛧
Default vestments (was monochrome 'crypt') now match the Church of Malware
aesthetic: neon on black — cyan window-chrome borders, acid-green text/prompts
and your own messages, soft-cyan for others, purple system/occult lines,
hot-magenta self/owner. themes/church.toml ships the same palette; crypt.toml
(monochrome) and neon.toml remain as alternates via --theme.

Confirmed ratatui's serde accepts #rrggbb (hex --theme files load). 9 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 20:54:41 -07:00
leetcrypt 0e269afce7 feat(hh): real VM unix users + sudo delegation ⛧
Linux-style user permissions inside the sandbox (the original superuser ask):
- Backends are now persistent (docker run -d + exec; multipass instance) so the
  broker can provision accounts and run the shell as a chosen user.
- sbx::provision(): create a real unix account per coven member at launch; the
  OWNER becomes a passwordless superuser (sudo group + /etc/sudoers.d NOPASSWD
  drop-in on multipass). The shared shell runs as the owner's account.
- /sudo <user> and /unsudo <user> (owner-only): real usermod + sudoers.d in the
  VM — delegate/withdraw superuser. ACL frame carries sudoers; roster shows
  ⛧ owner ·  sudoer · ◆ driver · • member.

Verified live on a real Multipass VM: shell runs as owner@vm with
'sudo -n whoami' == root; '/sudo member' gives member 'NOPASSWD: ALL';
teardown purges the instance. Docker provisions accounts + persistent
container (shell as root; sudo pkg absent so drive-grant is the delegation).

Tests: 7 cargo tests pass; clean build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 19:51:40 -07:00
leetcrypt 1dfc614cc5 feat(hh): P5 — file & directory uploads ⛧
Wire-compatible with the Python client's _ft protocol (offer/accept/reject/
chunk/done, 64KB chunks, SHA-256 verified), over the encrypted channel:
- ft.rs: read_payload (file or tar'd directory), save/extract with a zip-slip
  guard (relative-only, no .. / absolute; unpack_in double-checks), SHA-256.
- /send <file> and /sendd <dir>; receiver sees an offer banner, /accept or
  /reject; chunks stream in the background and the result is verified + saved
  to ./downloads (directories extracted in place).
- Refactor: all outgoing ws frames now funnel through an mpsc channel drained
  (batched) by the run loop, so the background chunk-sender and the PTY relay
  transmit without owning the socket.
- ui.rs: pending-offer banner on the input bar.

Tests: 7 cargo tests (file + dir tar round-trip, traversal guard, + crypto/sbx).
Verified live: two TUIs, file and directory transfer, SHA-256 verified, saved.

Note: dropping accepted files into the active sandbox VM dir is a future add-on.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 19:39:55 -07:00
leetcrypt f73c23bf57 feat(hh): P4 — permissions (owner/superuser + drive delegation) ⛧
App-level RBAC over the single shared PTY, enforced by the broker:
- The sandbox launcher becomes owner (superuser) and first driver; broadcasts
  an encrypted {"_perm":"acl",owner,drivers} frame all clients track.
- /grant <user> and /revoke <user> (owner-only) delegate/withdraw drive rights
  = delegating control of the shared (root) shell — the superuser-delegation ask.
- The broker honors {"_sbx":"input"} only from permitted drivers, keyed on the
  SERVER-AUTHENTICATED sender (the message username the Sanic session stamps),
  not a spoofable self-asserted field — closes the spec's identity-binding gap.
- F2 is gated: non-drivers get 'ask the owner to /grant you'; revoke drops drive
  live. Roster shows roles: ⛧ owner · ◆ driver · • member.

Verified live (two TUIs): member blocked pre-grant, owner /grant member, member
then drives a command in the sandbox; roster + permission messages all correct.
cargo test: 4 pass.

Note: per the single-shared-PTY decision, drive-grant *is* the permission model;
per-user unix accounts/sudo would need per-user shells (future mode).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 16:41:34 -07:00
leetcrypt d8018cbe2a feat(hh): P3b — multipass lifecycle + PTY resize sync
- sbx.rs: prepare()/teardown() — multipass launch (idempotent, reuses an
  existing instance) on /sbx launch multipass, delete --purge on stop;
  Backend::default_image() per backend (24.04 / ubuntu:24.04).
- app.rs: async non-freezing launch — prepare runs in spawn_blocking and the
  sandbox handle is handed to the run loop via a channel, so a ~30s multipass
  VM boot never freezes the UI (status: "summoning…"). Sandbox is sized to the
  actual pane (not fixed 24x80); broker resizes the PTY and broadcasts
  {"_sbx":"resize"} on terminal-size changes; clients set their vt100 size to
  match. Teardown on /sbx stop and on exit.
- net.rs: parse status rows/cols + resize frames.

Verified: cargo test (4 pass), clean build, live local sandbox via the async
path with dynamic full-width sizing. multipass 1.16.3 present; VM-boot path
implemented (live VM verify is slow, runs the same async flow).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 16:34:08 -07:00
leetcrypt 232a00cc9e feat(hh): P3 — summonable sandbox + shared PTY ⛧
Collaborative sandbox over the same zero-knowledge encrypted channel:
- sbx.rs: SandboxBackend (Local / Docker / Multipass) spawning a shell in a PTY
  (portable-pty); reader thread pumps output to the broker.
- Broker (owner's client): /sbx launch [backend] [image] boots the sandbox and
  relays PTY output as encrypted {"_sbx":"data"} frames; /sbx stop tears down.
  PTY input arrives as {"_sbx":"input"} frames and is written back.
- All clients render the shared terminal from data frames via a vt100 parser;
  F2 toggles drive mode (keystrokes -> input frames, incl. Ctrl-C); esc releases.
- ui.rs: sandbox pane (split below chat) with drive indicator.
- Server stays zero-knowledge: PTY bytes are Fernet-encrypted like chat/files;
  the VM runs on the initiator's client, never the server.

Tests (cargo test, 4 pass): PTY I/O round-trip + headless end-to-end relay
(PTY -> _sbx frame encode -> decode -> vt100 screen shows command output).

Note: Multipass assumes the instance is launched separately (lifecycle = P3b);
per-user unix accounts + sudo delegation = P4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 14:26:14 -07:00
leetcrypt d8acadd68b test: use-case coverage + end-to-end smoke test
- tests/test_coven.py: capacity cap (5th rejected, configurable), duplicate
  username, roster frame contents, slot/username freed on disconnect.
- tests/conftest.py: set app.ctx.max_users (fixes fixture vs new server code).
- hh/smoke.sh: one-command e2e — rust unit tests, SRP self-test, boot server,
  rust handshake round-trip, cross-language python decrypt of a rust message.
- hh: drop unused Session.user_id (clean build).

pytest: 85 passed. smoke: PASS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 14:15:04 -07:00
leetcrypt 651e7210b2 test(hh): stable python->rust fernet interop vector
Pin the cross-language fernet regression test to a fixed key+token (server-
independent) instead of a session token. Confirms rust decrypts python-encrypted
fernet; the live-chat path was verified end-to-end in the TUI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 14:01:03 -07:00
leetcrypt 14aa369fb2 feat(hh): ratatui TUI client — chat, live roster, themes
- Connect subcommand: SRP auth then a ratatui UI over tokio + crossterm.
- Async ws (tokio-tungstenite); reader task decrypts/parses frames into events.
- Panes: top bar (e2e + house N/cap), chat scrollback, roster (self marked ⛧),
  input box. Undecryptable frames surface as a system line, not a silent drop.
- Themes (vestments) via TOML --theme; default occult-monochrome + neon.
- Verified live in tmux: render, chat round-trip, roster, join/leave.
- Adds fernet python->rust interop regression test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 13:57:07 -07:00
leetcrypt bb1d662ee1 chore: rename project coven → hack-house ⛧
Rebrand the Rust client crate (coven/ → hh/, package+binary "hack-house"),
README, CLI strings, and branch (coven → hack-house). Gitea repo renamed
cmd-chat → hack-house to match. Crypto/server logic unchanged; selftest +
golden-vector test still green, binary is now `hack-house`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 13:29:14 -07:00