44 Commits

Author SHA1 Message Date
leetcrypt 8ba04316c1 fix(mobile-web): collapse carriage-return redraws in the console terminal
readline repaints its prompt in place with a lone \r (…└─# \r└─# cmd); the
browser turned that \r into a newline, stacking the pre-redraw prompt as a
phantom extra line (the mobile-only "extra --#"). Add a collapseCR() mini-vt
pass that resolves \r as a column-0 overwrite (ANSI escapes treated as
zero-width so colours survive; honours \x1b[K erase-to-EOL) before ansiToHtml,
so the real prompt stays and only the duplicate is dropped. TUI was already
correct via its real vt emulator.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-16 11:07:17 -07:00
leetcrypt 012b7c15e5 docs(mobile): point quickstart setup at termux-bootstrap.sh
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 22:40:06 -07:00
leetcrypt 3a90ee985a feat(mobile): termux-bootstrap.sh + robust ADB serial detection
termux-bootstrap.sh: one-shot on-device installer (pkg cryptography to avoid a
rust build, pure/wheeled pip deps, no srp C-ext, wake-lock, hh alias, Phase-0
preflight) — verified green on the Fairphone.

phone-capture.sh: auto-detect the adb device serial instead of assuming :5555,
since Wireless Debugging uses a dynamic port; target that serial for
screencap/screenrecord/pull. Clarified the pairing help to use the Tailscale IP.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 22:39:47 -07:00
leetcrypt e97a45b78d docs(mobile): operator quickstart — hh launcher, direnv, web console, capture, skills
User-facing how-to distinct from the design spec: the port-probe reachability
gate, one-time Termux setup + tar-over-ssh sync, the hh launcher grammar,
per-directory direnv auto-host, the mobile web console, phone-capture.sh routes
(non-root file/term/webshot vs ADB shot/screen), and the Fairphone skill
ship-list. Linked from termux-operator.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 22:21:30 -07:00
leetcrypt f4572c985e feat(mobile): phone-capture.sh — pull screenshots/files/terminal from the Fairphone
Laptop-side capture tool that SSHes into the phone. Non-root routes work from
the Termux app uid (file pull via tar-over-ssh, tmux capture-pane, headless
chromium webshot of the hh web console); shot/screen use ADB (Wireless
Debugging) since screencap/screenrecord can't reach SurfaceFlinger as an app
uid. --tg [target] ships any artifact to Telegram via video-toolkit tg-send.sh.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 22:17:53 -07:00
leetcrypt 2cd61ee598 feat(mobile): hh launcher + direnv auto-host for Termux/Fairphone
Add a single `hh` launcher (host/join/web/status/stop + operator
passthrough) and a direnv snippet so `cd`-ing into a project dir
auto-hosts a room named after it. Verified live on the Fairphone:
two rooms coexist on port-scoped tmux windows (manual :8799 +
direnv :8801), status health-checks each via curl (ss/netstat are
unreliable on Android), stop-all tears down cleanly.

Termux notes baked in: no /usr/bin/env (invoke via the `hh` alias,
not the shebang); status enumerates operator daemons under
$TMPDIR/hh-bridge.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 22:10:53 -07:00
leetcrypt 4a17f1c6f1 docs(mobile): harden Fairphone/Termux SSH operations doctrine
Encode the robust way to reach + drive the phone in CLAUDE.md and the
hh-operator skill (which had no mobile section at all):

- One canonical hardened ssh prefix (ConnectTimeout/BatchMode/accept-new)
  that never hangs and dodges the wrong-alias trap.
- Reachability gate probes the TCP port, NOT `tailscale status` — the
  tailnet shows fairphone-6 "active" even when Android has killed
  backgrounded Termux and sshd is dead (observed live).
- Fix-is-on-device guidance (termux-wake-lock) instead of retrying a dead
  port; Termux env quirks (no /tmp, no rustc, skip srp).
- Status advanced: phone can both JOIN and HOST rooms (server-side SRP
  shim) + the stdlib mobile web console mirrors a real room.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 21:54:54 -07:00
leetcrypt 7bd972f39c feat(srp): pure-Python server Verifier so Termux can HOST a room
The srp package's C-extension has no aarch64/bionic wheel, so a phone in
Termux couldn't run the room server. Add the server side of SRP-6a
(create_salted_verification_key + Verifier) to the pure-Python shim and
fall srp_auth.py back to it on ImportError.

Proven live: a laptop (C-ext srp client) authenticated against a room
HOSTED ON THE PHONE (pure Verifier) over Tailscale — cross-implementation
handshake with matching session keys. Locked in with interop tests
covering real-client-vs-pure-Verifier, pure-vs-pure, and a wrong-password
negative control.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 21:39:17 -07:00
leetcrypt 1c36325d07 feat(operator): mobile hack-house console — terminal + keys + chat
Rebuild the web UI into a room mirror that looks/feels like the TUI:
tabbed Terminal + Chat, a live shared-sandbox terminal pane (polls
/api/screen, client-side ANSI/SGR render), keystroke drive with quick
keys + a command line (POST /api/keys), roster and a driver/watching
badge. Read-only until the room /grants this operator.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 16:19:53 -07:00
leetcrypt 35da940cc8 feat(operator): mobile web chat UI in front of the daemon
Add `operator web` — a stdlib-only http.server shim over the control
socket so a phone browser can drive a room: message bubbles, live
long-poll updates, roster, and a real text box (GET /, /api/events,
/api/status, POST /api/say). Solves the Termux input gap where the
tmux `read --wait` loop is read-only. Binds 127.0.0.1 by default since
the socket grants room-send.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 16:00:10 -07:00
leetcrypt 2d6258cab0 docs: record the Fairphone/Termux SSH path in the operating guide
The phone is a live SSH host on the tailnet, so on-device Phase-0 work can be
driven directly — not "paste it back". Document the verified reachability
(fairphone-6 100.95.202.68:8022, user u0_a203, ~/.ssh/phone-deploy), the
wrong-alias trap (`phone` points at an offline device), Termux env facts, the
rust-free dep install, and the tar-over-ssh staging so future sessions check
reachability first.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 15:40:11 -07:00
leetcrypt 2cfc7112fc build: add on-device Termux Phase-0 preflight script
scripts/termux-preflight.py runs the docs/termux-operator.md §0.1-0.5 checks on
the phone and prints a PASS/FAIL report: python>=3.11, requests/rich/websockets
import, cryptography+Fernet round-trip (version floor treated as soft), srp
C-ext OR the pure shim, that client/operator import without the server stack,
and an AF_UNIX bind under the $TMPDIR runtime root. Makes the one remaining
on-device Phase-0 step a single command. Exits non-zero on any hard failure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 15:21:29 -07:00
leetcrypt 3fddc17454 fix(operator): honor $TMPDIR for the runtime root (Termux has no /tmp)
runtime_root() fell back to /tmp, which does not exist on Android/Termux, so the
daemon's control.sock would land in an unwritable path. Fall back
XDG_RUNTIME_DIR -> TMPDIR -> /tmp; Termux sets TMPDIR to $PREFIX/tmp. No change
on a normal box (TMPDIR is typically unset there). Enables the Phase 1
join/read/say socket to work on-device.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 15:20:39 -07:00
leetcrypt 3d085eaec4 fix(operator): don't import the server stack on the operator path
cmd_chat/__init__.py eagerly imported run_server (sanic/pydantic) and Client,
so any `cmd_chat.*` import — including `python -m cmd_chat.operator` — pulled
the server stack at package init. On a phone/Termux operator that installs only
the operator deps (and where srp may be unbuildable), this failed before the
client's pure-SRP fallback could help.

Make both imports lazy inside main()'s serve/connect branches. The operator
path now imports cleanly with srp/sanic/pydantic all absent (client falls back
to _srp_pure). Add a subprocess regression test to keep it that way.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 15:14:13 -07:00
leetcrypt 4bd38d9c30 build: add requirements-operator.txt for phone/Termux operators
Operator-only dependency subset (requests, rich, websockets, cryptography>=41,
srp) so the C-heavy cryptography>=46 server pin can be satisfied by Termux's
packaged build. Server requirements.txt is left unchanged. srp is best-effort:
the client falls back to the pure-Python shim when it can't be built.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 15:06:39 -07:00
leetcrypt 750692b6aa test(client): interop-gate the pure SRP shim against a real server
SRP fails silently on any constant/hash mismatch, so gate the shim with a live
handshake: (1) pure User vs the real srp.Verifier, (2) a negative wrong-password
control, and (3) the full /srp/init -> /srp/verify HTTP flow against the real
server routes (backed by the real srp lib). All assert the phone operator
authenticates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 15:06:15 -07:00
leetcrypt ffb8ef52f0 feat(client): fall back to pure-Python SRP when srp is unimportable
Wrap `import srp` so a missing C-extension (e.g. Termux/aarch64) transparently
loads _srp_pure as srp. No call-site changes — the shim mirrors the srp API.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 15:04:31 -07:00
leetcrypt a7b4840e9e feat(client): pure-Python SRP-6a fallback for Termux/aarch64
The `srp` PyPI package is a C extension with no reliable aarch64 wheel and
often fails to build under Termux. Vendor _srp_pure.py, a stdlib-only port of
srp._pysrp with rfc5054_enable() active, exposing the exact client surface
client.py uses (rfc5054_enable, SHA256, User with start_authentication /
process_challenge / verify_session / authenticated).

Constants (N/g/k), x/u derivation, M1 and H_AMK match srp byte-for-byte —
verified by a live handshake of this pure User against the real srp Verifier
(the same backend the server runs).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 15:04:21 -07:00
leetcrypt 88e5dfa0cb docs: spec for running a hack-house operator from Android/Termux
Phased plan (P0 dependency spike → P1 read-only presence → P2 sandbox drive →
P3 remote-provider autonomy → P4 persistence) for joining a room as a user from
Termux. Grounded in the real SRP/Fernet/ws client and the operate provider path.
Covers the srp aarch64 build risk + pure-Python SRP fallback, the cryptography
pin workaround, and Tailscale / reverse-SSH / port-forward reachability.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 14:58:38 -07:00
leetcrypt 7afc38232b feat(cardex): population-curve rarity + interactive flip gallery
Rarity was inflating to 65% Legendary: the loop mass-produces ~70 near-clone
VMs that look near-identical on every axis, so no absolute PowerScore threshold
can create scarcity within that cluster. Fix (option C, hybrid):

- grade_curve() is now the primary rarity mechanism — a population quota
  (Leg 8 / Epic 15 / Rare 25 / Uncommon 27 / Common 25%) ranked by PowerScore
  with a deterministic label-seed tiebreak. Yields a clean 8/15/26/27/24%
  pyramid. Rebuilds each card's rarity-dependent name + flavor via _apply_rarity.
- Absolute RARITY_TIERS kept as a single-card (--label) fallback, recalibrated.
- Dead axes repaired: _pedigree now derives from author lineage (human/hh-loop/
  pulled/smoke) instead of a flat 2.0; _heft rescaled to a 512MB log reference
  so it spreads instead of pinning mid-size VMs at the cap.

cardimg: apply grade_curve to the gallery so rendered cards match hh-cardex,
plus the interactive 3D flip-card gallery (--flip: front dossier / back stats).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-07 09:51:14 -07:00
leetcrypt 2cfa388879 feat(cardex): image-search harvester + genre-diverse character library
Grow the hh card-art library so common VM types stop reusing one portrait.

- scripts/imgharvest.py: keyless DuckDuckGo image-search harvester — downloads
  many stills per character and runs them through charvest's crop/quality/dedup
  pipeline (static art crops far cleaner than motion-blurred video frames).
- scripts/harvest_roster.sh: batch roster (Pokemon/Digimon/Yu-Gi-Oh) sized to
  the live VM card types. Library now 37 characters / 99 portraits.
- charvest: top-anchor the head+shoulders crop for tall full-body art so faces
  are no longer clipped.

chardex matcher — stop same-type VMs collapsing onto a single face:
- fold a per-(VM,character) hash jitter INTO the score, not just a tie-break
- diminishing returns on extra type matches so a strong single-type face can
  compete with a dual-type one
- halve tier affinity and cap the keyword bonus
- stop-word rarity/boilerplate words that were polluting keyword overlap
Result: 20 distinct faces across the library. The one remaining cluster
(Sigilyph on the Psychic/Flying recon-scanner VMs) is coherent purpose-
matching — a scanner-themed bird on the scanner VMs — not a bug.

NOTE: assets/characters/ are third-party franchise IP with NO established reuse
license — quarantined, internal WIP only, do not distribute/merge/publish
(licensing record in docs/character-art-licensing.md).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-03 11:04:37 -07:00
leetcrypt 987985b203 feat(loop): retune hh-loop rubric to the cardex 5-axis PowerScore
Map the loop's "high value" guidance onto the measurable cardex model
(completeness/reusability/richness/pedigree/heft) and target Epic≥650 /
Legendary≥825. Stamp usage/setup/entrypoint + ≥4 provenance notes in the
manifest, drop --todo at done, and score each card before moving on.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-30 15:59:53 -07:00
leetcrypt 9c1ec55e0b feat(cardex): carry VM status onto cards + browser gallery
Add a status field to Card (from the registry entry) and render it as a
done/in-progress pill on the card art. Add `cardimg --gallery`: render every
VM as an inline-SVG card and lay them out in one self-contained index.html
grid (sorted by rarity, with a spread tally) for browser viewing / screenshot.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-30 15:12:14 -07:00
leetcrypt f993a41cd7 feat(cardex): retune value model for spread + add card image renderer
Retune cardex against the live 24-VM library: the old rubric tied 11 VMs at
615 Rare (completeness/reusability maxed for any done+shareable VM, only heft
varied). Rebalance the five sub-score caps (richness 20→30 becomes the
discriminator via per-tag CAPABILITY_WEIGHT; heft log2-scaled) and recalibrate
the rarity thresholds. Library now spreads 2 Legendary / 3 Epic / 16 Rare /
2 Uncommon / 2 Common instead of a single fat Rare band.

Add cmd_chat/cardimg.py: render a Card as a collectible trading-card image.
Free + local-first — a hand-built SVG frame (rarity holo, type badges, 6 stat
bars, dex#, flavor) plus a deterministic procedural creature sigil that needs
no network, rasterized to PNG via local cairosvg/ImageMagick. Pluggable art
backends are opt-in: free hosted Pollinations flux, and key-gated paid
Stability / OpenAI / RunwayML. Every backend only fills the portrait window,
so a card always renders fully offline; failures fall back to the sigil.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-30 14:58:27 -07:00
leetcrypt c41d85f8ec feat(loop): security-10 brief queue — drove 10 deep VM-library builds
Curated 10-brief queue (crypto-ctf, osint-recon, web-fuzz, log-ir-triage,
pcap-forensics, password-audit, threat-intel, vuln-flask, net-scan,
yara-triage) used by hh-loop to build + publish 10 in-depth, verified,
self-describing security VMs to the host registry.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-30 14:28:39 -07:00
leetcrypt dc8b68315e feat(loop): curated 10-brief deep-build queue for the VM library
Each brief encodes a meticulous-value contract (real software install + real
baked dataset + offline self-test + status:done manifest) so loop output scores
high on the cardex rubric instead of being shallow stubs. Security-focused and
distinct from the existing library.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-30 14:13:46 -07:00
leetcrypt e5fb820e63 feat(loop): headless sbx save/publish + hh-loop VM-library skill
Adds the autonomous /loop foundation: a headless `hack-house sbx save|publish`
subcommand so a non-TUI operator can persist a built VM to the host library
through the same canonical registry path the room UI uses (no schema drift).

- snapshot.rs: hoist register_saved_snapshot/publish_snapshot/oci_image_size out
  of app.rs into a shared module used by both the TUI and the new CLI.
- registry.rs: advisory cross-process lockfile (~/.hh/registry.lock, O_EXCL spin
  + stale-reclaim) around every read-modify-write, plus atomic temp+rename store,
  so concurrent /loop wave members can't clobber each other.
- main.rs: `Sbx { Save, Publish }` subcommand wired to the shared snapshot logic.
- skills/hh-loop: the loop doctrine — value rubric, adaptive 1-3 operator
  topology, visible-tmux-by-default run flow, --record logs/film, wave scaling,
  and safe `tmux -L hh-loop` teardown.

Proven end-to-end: built+verified a stdlib VM in a sandbox, headless
save+publish -> registry entry shareable:true with a portable tar.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-30 14:06:19 -07:00
leetcrypt 354205cc16 feat(cardex): score saved VMs as collectible mythic-creature cards
Deterministic value model over the host VM registry: a weighted five-axis
rubric (completeness/reusability/richness/pedigree/heft) yields a PowerScore
0-1000, an absolute-threshold rarity tier, a 6-stat block, an elemental type
from tags, and a stable procedural name + dex number. Pure stdlib, no model,
no network — same VM always mints the same card. Image pipeline lands next.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-30 14:05:24 -07:00
leetcrypt 0798db7941 feat(operator): harness-mode operator — drive a room with any function-calling model (P2)
The operator brain was Claude-only: spawn launched the claude CLI and let its
own loop drive the room. This adds the other first-class brain — a native
tool-calling loop (cmd_chat/operator/harness.py) that runs ANY function-calling
Provider (cmd_chat.ai) against an OPERATOR_TOOLS schema wired to the existing
bridge control verbs (say/exec/write/get/keys/screen/watch/manifest). No CLI
install, no creds carry.

The harness is a driver, not a new side-effect surface: every tool handler sends
the same control-socket request the hh-bridge CLI already sends, so the bridge
keeps enforcing grant-before-drive, the sandbox blast radius, and the recursion
budget. Layer-1 capabilities come from CAPABILITIES.md (the same portable
contract), and a per-agent token ceiling is enforced as a hard stop. Providers
that can't function-call are refused with a clear message rather than degrading
to prose.

New `operate` verb joins via the existing daemon socket and runs the loop with a
--profile or --provider/--model brain. Chat's native loop is left untouched
(self-contained harness over the shared Provider core, not a risky extraction).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-28 23:23:17 -07:00
leetcrypt 2091bcefed feat(operator): portable CAPABILITIES.md as the single Layer-1 prompt (P4)
The operator's capabilities contract was Claude-only — delivered via the
hh-operator skill, which non-Claude runners cannot load. Extract it into a
portable CAPABILITIES.md and make compose_directive runner-aware: the claude
runner still loads its skill, every other runner gets the capabilities prompt
inlined. Same contract, no skill machinery required.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-28 23:15:56 -07:00
leetcrypt 09cea40494 feat(operator): runner registry — spawn any agent CLI, not just Claude (P3)
Generalize the three Claude-hardcoded spots in bootstrap (launch argv,
install plan, creds/config-dir) behind a Runner registry so a nested
operator can be spawned with any agent CLI. The Claude path stays the
default and byte-identical.

- bootstrap.Runner + RUNNERS{claude,codex,gemini,cmd} + get_runner/runner_present
- build_run_argv / install_plan / creds_source / plan_creds / child_env all
  take an optional `runner` (default claude → unchanged behaviour)
- claude validated end-to-end; codex/gemini are best-effort defaults
  overridable via $HH_<RUNNER>_INSTALL etc.; generic `cmd` runner reads a
  full launch template from $HH_OPERATOR_CMD ({directive} placeholder)
- bridge _op_spawn resolves req["runner"], rejects unknown; plan now carries
  runner/runner_present (was claude_present)
- CLI: `spawn --runner {claude|codex|gemini|cmd}`
- 7 new unit tests; full suite 132 passed

Note: compose_directive still emits the Claude-flavoured ("hh-operator
skill") directive — the portable Layer-1 prompt is P4.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-28 23:11:41 -07:00
leetcrypt 300e33980a refactor(ai): hoist Provider core to shared cmd_chat/ai/ (P1)
Move providers.py + profiles.py from cmd_chat/agent/ to a shared
cmd_chat/ai/ package so the operator bridge and the /ai chat agent can
consume one model-agnostic Provider core (groundwork for harness-mode
operators). Pure refactor — no behaviour change.

- cmd_chat/ai/{providers,profiles}.py: the canonical modules (moved verbatim)
- cmd_chat/ai/__init__.py: re-exports the public API
- cmd_chat/agent/{providers,profiles}.py: thin back-compat shims re-exporting
  from cmd_chat.ai (keeps `from cmd_chat.agent.providers import …` working,
  e.g. hh/scripts/bench-native-harness.py)
- internal agent consumers (memory/bridge/__main__/__init__) point at cmd_chat.ai

125 tests pass; shim identity verified (re-exports are the same objects).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-28 22:57:29 -07:00
leetcrypt 7fbae3ca84 docs: add operator guide + model-agnostic operator plan
CLAUDE.md operating guide (room/operator/manifest/VM-registry map +
demo-toolkit pointers) and docs/model-agnostic-operator-plan.md — the
phased spec to let any function-calling model operate a room, not just
Claude (hoist Provider core, harness-mode operator, runner registry,
portable CAPABILITIES, role presets, MCP).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-28 22:53:47 -07:00
leetcrypt 12ae553ca1 feat(operator): VM marketplace — Phase A registry + Phase B trading
Phase A: host-global VM registry (~/.hh/registry.json) joining opaque
snapshots to their .hh-agent manifests. New src/registry.rs (serde_json),
one Entry per saved VM with cached purpose/status/todo scraped from the
live container at save time. Reconcile-on-read self-evicts pruned images.
/sbx browse TUI lister + operator-side `registry list|show` reader.

Phase B: trading/skill VMs. Entry gains shareable/tags/share_path +
publish/get/list_shareable helpers. New TUI verbs /sbx publish <label>
[tag...], /sbx catalog @user, /sbx pull @user <label>. Wire protocol adds
_sbx:catreq/catalog/pullreq frames (parse_sbx). Receiver auto-loads a
received hh-snap-*.tar, reads the in-image manifest, and self-registers
(kept shareable for re-trade). Reuses the existing E2E /send + ft.rs
streaming transport.

podman gotcha fixed: `podman load` prints registry-qualified
`localhost/hh-snap:<label>` vs docker's bare tag — parse_loaded_tag now
finds the hh-snap: marker anywhere in the line (unit-tested both shapes).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-27 15:21:53 -07:00
leetcrypt b998d28ebf fix(ai): repair native-harness bench — 3-tuple preflight + PTY-mirror stub
First functional bench run crashed 0/3 on two latent bench-only bugs:
(1) the Phase-4 token-count change made complete_with_tools return
(text, calls, usage), but the bench preflight still unpacked 2 values;
(2) make_bridge never stubbed _send_sbx_input, so the PTY-mirror
visibility feature hit ws=None and raised AttributeError on every tool
call. Stub it to capture mirrored lines for --verbose.

After the fix: 2/3 pass (write+read, script+run). mkdir+list still
fails — local-backend run_shell CWD divergence + 3B model churn, not a
working-memory regression.

Sprint: native-harness-working-memory (bench) — see also 46e5620, dc6317f, c83abbe

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-27 10:16:30 -07:00
leetcrypt 19d5b80a2c feat(operator): hh-agent manifest — VMs carry a self-describing handoff record
Add ICM-aligned agent-manifest tooling so a hack-house sandbox/VM can be
shared, traded, and resumed without a human briefing. manifest.py is a
dependency-light (stdlib + optional PyYAML) library + CLI that writes a
`.hh-agent/` bundle: manifest.yaml (canonical machine record) plus rendered
AGENT.md / last-state.md / summary.md / goals.yaml views.

Wire a `manifest` op into the operator bridge (push/pull/update) that moves the
bundle in and out of the *target sandbox* via the same exec path as write/get,
so the VM itself carries its purpose, goals, user intent, live state, and a
provenance chain — the unit of agent-to-agent work transmission. Pairs with
`spawn`: stamp → push → hand a child "load the .hh-agent manifest and continue."

Proven end-to-end against a real Kali podman container (push → on-disk verify →
update → pull-back) and covered by 6 new offline tests (34 green total).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-26 20:28:48 -07:00
leetcrypt 338e843e85 feat(operator): Phase 6 — relay-mode read (screen) + stop-condition engine (watch)
Completes the remote-drive triad for a broker-owned sandbox the operator can't
exec into directly: keys (in) → watch (wait) → screen (out).

- _sbx:data PTY-relay frames are absorbed into a capped rolling terminal buffer
  (not surfaced as chat); `screen` returns it ansi-stripped (or raw). sandbox.
  strip_ansi handles CSI/OSC/CR noise so output greps cleanly.
- `watch` is a formal stop-condition engine: blocks until a regex matches in the
  screen buffer or chat events, or an idle-quiescence window, or a hard timeout,
  then reports which fired — the autonomy loop's principled wait.
- Skill doctrine updated with the type→wait→read relay loop.

28 offline tests green.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-26 20:13:08 -07:00
leetcrypt 60215bbdd4 feat(operator): Phase 5 — bootstrap + budgeted recursive spawn
Lets an operator stand up a nested Claude Code operator against another room —
a tree of operators — behind hard guardrails:

- Budget(depth, fanout, cost): depth/fanout are hard caps decremented on every
  descend(); a leaf (depth 0) or a spent level (fanout 0) refuses to spawn,
  stopping a runaway tree. Cost is carried down as a soft ceiling.
- detect_system / install_plan: decide install from what's present; prefer the
  documented npm package `@anthropic-ai/claude-code` or a $HH_CLAUDE_INSTALL
  override — never a guessed URL.
- Credentials are gated OFF by default: a child authenticates itself unless the
  operator explicitly passes allow_creds, and only its own creds, only to a path
  it was handed.
- compose_directive bakes the objective, stop conditions and inherited budget
  into the child's `claude -p` prompt so it self-limits.

`spawn` op + CLI verb default to a dry-run plan (inspect the tree before growing
it); `--go` launches detached on the host. `up`/`serve` take --depth/--fanout/
--cost. 24 offline tests green; dry-run plan verified live through the socket.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-26 13:38:42 -07:00
leetcrypt 669266dc55 feat(operator): Phase 3 — hh-operator skill doctrine + /loop autonomy
Reusable SKILL.md any Claude Code session can invoke to drive a hack-house
room via the hh-bridge CLI: join, the read --wait → decide → say operator loop,
the permission-gated sandbox drive (exec/write/get + shared-PTY keystrokes),
and an embedded keystroke cheat-sheet centred on the stop-vocabulary
(ctrl-c/ctrl-d/ctrl-z/ctrl-\, esc, pager q, vim :q) so every session knows how
to end a stuck program up-front.

/loop autonomy is folded in as the "indefinite watch" pattern with an explicit
stop-condition vocabulary — the long-poll primitive + persistent daemon from
Phase 1 already support it, so no extra machinery. Symlinked into
~/.claude/skills/ for global reuse.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-26 13:25:07 -07:00
leetcrypt 9c1e2921e9 feat(operator): Phase 2 — sandbox drive (exec/write/get) + keystroke relay
Co-located mode: the operator launches its own podman/docker container and
execs into it out-of-band (argv-identical to the native harness), exposed as
`sbx launch|status|down`, `exec`, `write`, `get`. When granted, it can also
drive the room's broker-owned container directly on the same host.

Relay mode primitive: `keys` injects raw bytes into the shared PTY via the
same `_sbx:input` frame a human driver emits — full keyboard control incl. the
stop-vocabulary (ctrl-c/ctrl-d/ctrl-z/ctrl-\, esc, arrows, pager q). A compact
per-session cheat-sheet (`sandbox.KEYS_HELP`, `keys --help-keys`) documents
what each inject does and how to end a stuck program, token-efficiently.

Gating: exec/write/get refuse a host (`local`) inherited from the room; the
room sandbox is only driven when `granted`. Keystrokes are inert until granted.

17 offline tests green; e2e verified against real podman (launch→exec→write→
get round-trip→teardown) and through the CLI socket.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-26 12:34:24 -07:00
leetcrypt c83abbee8a feat(ai): calibrate native pruning against real Ollama token counts
Phase 4 (final) of the native-harness working-memory sprint. OllamaProvider
.complete_with_tools now returns (text, calls, usage), surfacing the response's
real prompt_eval_count/eval_count (free — already in the payload). The native loop
EMA-smooths real/estimate into self._tok_ratio (clamped [0.5,3.0]) and prunes
against native_token_budget / ratio, so context budgeting tracks the TRUE window
instead of the systematic bias of the len//4 char estimate. Providers that omit
counts leave the ratio at 1.0, so behaviour is unchanged where unavailable — a
free correctness win, no regression. RAM-only, no disk, no new network frames.

Scope note: touches only cmd_chat/agent/ + providers — disjoint from the parallel
feat(operator) work on this branch, so it merges/reverts independently by path.

Sprint: native-harness-working-memory (Phase 4/4) — see also 46e5620, dc6317f
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-26 12:27:59 -07:00
leetcrypt dc6317fc46 feat(ai): two-stage native pruning — digest tool outputs before eviction
Phase 3 of the working-memory sprint. _prune_native_messages now compacts in two
stages instead of only evicting whole turns: Stage 1 digests OLD tool-role outputs
to a one-line summary (exit marker + first error line, else first line) via the new
_digest_tool_output; Stage 2 falls back to oldest-first whole-message eviction only
if still over budget. Tool outputs are the biggest context hog, and digesting keeps
the action->result causal chain intact, so whole-turn eviction (which severs it)
becomes a last resort. The pinned head/TASK and the recent keep_recent window
(including the most recent tool output, verbatim) are still never touched.

Return is now (messages, dropped, digested); the sole caller logs both. Clean-room
counterpart to Goose's tool-output condensation track. RAM-only, no disk.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-26 12:20:02 -07:00
leetcrypt 7e81a059b9 feat(operator): Phase 1 — Claude-driven room bridge (join/read/say)
Invert the agent model: a headless OperatorBridge(Client) owns the
websocket while a Claude Code session drives it via a unix control
socket. Reuses Client SRP/Fernet and AgentBridge's reconnect/serve
shape. Ships an hh-bridge CLI (up/read/say/roster/status/down) with a
seq'd in-RAM inbox + asyncio.Condition long-poll (read --wait) for
in-turn autonomy. ACL/sandbox-status frames are recorded for later
phases. Sandbox drive, delegation and nesting are out of scope here.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-26 12:19:47 -07:00
leetcrypt 46e5620f89 feat(ai): RAM working-set + failure ledger + stuck/loop detection
Phase 1+2 of the native-harness working-memory sprint. All per-task state is
process RAM only (dies with the task, same lifecycle as MemoryIndex) — no disk,
consistent with the agent's encrypted-transmission / nothing-saved posture.

Phase 1 — _WorkSet dataclass holds what the loop kept re-deriving: sandbox
cwd/shell, files written/read, a failure ledger (cmd -> exit+category), and the
last good command. Discovered cwd/shell carry across tasks in-process via
self._sbx_known (RAM fallback grounding). _render_workset re-surfaces this into
the repair-turn system prompt so it survives context pruning without a NOTES.md
on disk. Folds the old reads_seen set into wset.files_read.

Phase 2 — semantic stuck/loop detection via _action_signature (run_shell keys on
the command, write_file on path+content-hash so real edits aren't repeats,
read_file on path). Aborts honestly when an action fails >=2x verbatim (model
ignoring REPAIR_STANCE) or the same (action,outcome) repeats >=3x, instead of
burning the turn cap re-running a dead action. Verified fix-and-retry does not
false-trip.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-26 12:18:09 -07:00
156 changed files with 12734 additions and 631 deletions
+4
View File
@@ -23,6 +23,10 @@ err.log
# Sandbox save/load snapshots (large runtime tarballs, not source)
/hh/hh-snapshots/
# VM-library /loop artifacts — exported snapshot tars (repo-root, when `sbx`
# is run from here) + per-run logs. Generated; the VMs live in the host registry.
/hh-snapshots/
/.loop-runs/
# Out-of-tree experiments (not part of hack-house)
/experiments/
+102
View File
@@ -0,0 +1,102 @@
# hack-house — Claude operating guide
Encrypted collaborative terminal sessions with a summoned, shareable sandbox.
**Rust TUI client** (`hh/`) + **Python server & operator bridge** (`cmd_chat/`).
A Claude Code session can join a room as a **first-class member**: read/answer
humans and other AIs, and — once granted — drive the shared sandbox with full
shell + keystroke control. VMs carry a self-describing `.hh-agent` manifest so
work + memory hand off between agents (and humans), and saved VM states are
tradeable via a host-global registry.
## Operating a room — use the `hh-operator` skill
To enter/run/watch a room, bridge a Claude session into one, or autonomously
monitor and respond, **use the `hh-operator` skill** (`~/.claude/skills/hh-operator/`).
It documents the full doctrine: the daemon + `hh-bridge` CLI, the read→think→act
loop, sandbox drive (exec/write/get), the keystroke relay + stop-vocabulary, the
`watch` stop-condition engine, and the `.hh-agent` manifest for handoffs.
```bash
# Run from this repo root; pin the interpreter to the venv.
HH=".venv/bin/python -m cmd_chat.operator" # = the hh-bridge CLI
$HH up <host> <port> <name> --password <pw> --no-tls # join (spawns the daemon)
$HH read --wait --timeout 30 # long-poll for activity
$HH say "…" # chat into the room
$HH keys "make build" enter ; $HH watch --for 'PASS|FAIL' --in screen # drive
$HH manifest push --root /root --name <vm> --purpose … --objective … # handoff
$HH spawn "<objective incl. the full join cmd>" --room-host … --go --skip-permissions
```
## Mobile operator on Android / Termux — the phone is REACHABLE, use it
Spec: `docs/termux-operator.md`. The operator side is pure Python and runs on
the **Fairphone-6** in Termux — it can both **join** and **host** rooms (the SRP
shim now covers the server side too, so no Rust / no `srp` C-ext). You can drive
the device directly over SSH — so **verify reachability before ever telling the
user "this can only run on-device, paste it back."** That framing is wrong when
the phone answers SSH.
One canonical, hardened invocation (use this prefix everywhere — it sidesteps
the wrong-alias trap and never hangs):
```bash
PHONE='ssh -i ~/.ssh/phone-deploy -p 8022 -o ConnectTimeout=8 -o BatchMode=yes -o StrictHostKeyChecking=accept-new u0_a203@100.95.202.68'
$PHONE 'uname -m; python --version' # aarch64 / Python 3.13.x
```
- **Reachability gate — probe the PORT, not `tailscale status`:**
`timeout 8 bash -c 'cat </dev/null >/dev/tcp/100.95.202.68/8022' && echo REACHABLE || echo OFFLINE`.
Tailscale reports `fairphone-6` **`active` even when sshd is dead** — Android
kills backgrounded Termux, taking `sshd` with it. The tailnet line is NOT
proof; the TCP probe is. If OFFLINE, the fix is **on-device** (wake the phone,
relaunch Termux, `termux-wake-lock` + start `sshd`) — say so, don't retry a
dead port.
- **Trap:** the `~/.ssh/config` aliases `phone` / `phone-deploy` point at
`100.95.180.14` (`trilluminati`), a *different, usually-offline* phone —
`ssh phone` will time out. Use the Fairphone IP `100.95.202.68` (the `$PHONE`
prefix), not the alias.
- Termux facts: Python 3.13 (≥3.11); **no `/tmp`**`$TMPDIR` is
`$HOME/.claude-temp`; `$PREFIX=/data/data/com.termux/files/usr`; `rustc`/
`openssl` not installed by default.
- Deps that work: `pkg install python-cryptography` (avoid a rust build),
`pip install requests rich websockets`; **skip `srp`** — both client and
server fall back to `cmd_chat/client/_srp_pure` (pure-Python SRP-6a shim).
- Push-restricted work: copy this tree to the phone with tar-over-ssh rather
than a stale gitea clone —
`tar czf - cmd_chat scripts requirements-operator.txt | $PHONE 'tar xzf - -C ~/hh-mobile'`.
- Phase-0 gate: `python scripts/termux-preflight.py` on the phone → expect
`RESULT: PASS`. Operator files staged at `~/hh-mobile` on-device.
- **Proven live (2026-07-07):** phone JOINS a dev-box room; the phone HOSTS a
room the laptop joins (cross-impl SRP: laptop C-ext ↔ phone pure Verifier);
and the stdlib mobile web console (`python -m cmd_chat.operator web`) mirrors a
real room (terminal + keystroke drive + chat) in a mobile browser.
## Key components
| What | Where |
|------|-------|
| Operator bridge (daemon + CLI) | `cmd_chat/operator/__main__.py`, `bridge.py` |
| Agent manifest (`hh-agent/v1`) | `cmd_chat/operator/manifest.py``.hh-agent/` bundle library + CLI |
| Recursive spawn (Claude-spawns-Claude) | `cmd_chat/operator/bootstrap.py` (budgeted: `--depth`/`--fanout`) |
| Saved-VM registry / discovery | `hh/src/registry.rs``~/.hh/registry.json`; reader: `$HH registry list\|show` |
| Sandbox grammar (TUI) | `/sbx podman\|launch`, `/grant <name>`, `/sbx save`, `/sbx browse`, `/sbx publish`, `/sbx catalog @user`, `/sbx pull @user <label>` |
| Server | `cmd_chat.py serve <host> <port> --password <pw> [--no-tls]` |
| Rust client | `hh/target/debug/hack-house connect <host> <port> <name> --password <pw>` |
## Demo videos — use the `video-toolkit`
hack-house feature reels are produced with `~/coding/video-toolkit` (headless
asciinema-in-tmux capture via `bin/tmux-demo.py`, then a `video-forge` cut).
- **Flagship reel — three Claudes, one shared VM:** `scenarios/hh-vmhub.json`
(capture) + `scenarios/hh-vmhub-cut.json` (forge cut). A planner, builder, and
tester (each a real nested `claude -p`) collaborate to build **VMHub** — a
GitHub-for-VMs web app — live inside one shared sandbox, then save it to the VM
library. Role task-scripts: `scenarios/vmhub-{builder,tester}-task.sh`; staged
app under `scenarios/assets/vmhub/`.
- A reel must **show the product**: the Rust TUI room (roster, encrypted chat, the
shared sandbox F2 view) with Claude visibly operating as a member and
Claude-spawning-Claude — not just a console of CLI calls.
> Provisioning note: the demo summons a pre-baked `localhost/hh-dev:slim` (python
> 3.11) podman sandbox so VMHub (stdlib, zero pip deps) runs with no installs.
Binary file not shown.

After

Width:  |  Height:  |  Size: 682 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 946 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 711 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1011 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 641 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 852 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 760 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1001 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 826 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 857 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 763 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 783 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 694 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 579 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 820 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 875 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 607 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 731 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 765 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 766 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 817 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 681 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 916 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 802 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 629 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 724 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 805 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 776 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 KiB

Some files were not shown because too many files have changed in this diff Show More