{"slug": "show-hn-hoop-a-sandboxed-p2p-live-collaboration-harness-for-claude-code", "title": "Show HN: Hoop – A sandboxed P2P live collaboration harness for Claude Code", "summary": "Hoop, a sandboxed P2P live collaboration harness for Claude Code, is now available as an open-source plugin that runs entirely in Docker containers, requiring no host installation of Claude Code or Node.js. The tool provides an interactive setup wizard, a local web dashboard with live sessions and event search, and ships its own Claude binary with a separate account, enabling collaboration on bare machines with just Docker. Creator Bruno de Queiroz designed Hoop to install, document, and observe third-party MCPs and skills without re-implementing them, supporting features like memory, code-graph RAG, and observability.", "body_md": "## hoop-showcase.mp4\n\nBeing alone is not a requirement. A sandboxed and web harness on top of Claude Code. Two deliverables in one install:\n\n— an interactive wizard that wires a curated stack (memory, code-graph RAG, automation, platform MCPs, docs RAG, observability, design, second-brain).`/hoop:setup`\n\n— a containerized local web dashboard (Next.js,`/hoop:dashboard`\n\n[http://localhost:7842/](http://localhost:7842/)) with live sessions, a skill browser with one-click triggers, a nested sub-agent tree, push-based event observability, and BM25 + optional semantic search across all events.\n\nThe plugin **does not re-implement** any of the third-party MCPs or skills it installs. It picks, installs, documents, and observes them.\n\nThere are two ways in. Pick the one that matches your host.\n\n```\n/plugin marketplace add bruno-de-queiroz/hoop\n/plugin install hoop@hoop-marketplace\n/plugin list\n/reload-plugins\n/hoop:setup\n```\n\n(The `/plugin list`\n\n+ `/reload-plugins`\n\ndance is required on Claude Code v2.1.138 to activate a freshly pre-seeded plugin in the current session. New sessions don't need it.)\n\nYou do **not** need Claude Code (or even Node) installed on the host. The sandbox ships its own `claude`\n\nbinary and authenticates with its **own** Claude account, so a bare machine with Docker is enough.\n\n**Prerequisites**\n\n**Docker**— Docker Desktop, or any Docker engine + Compose v2 (`docker compose`\n\n). This is the**only** hard dependency for`hoop start`\n\n— Claude Code, Node, gh, uv, gws, and the dashboard all live in containers, never on your host, and the sandbox seeds its own Claude profile on boot (so`hoop start`\n\nneeds no host jq). A few*other*subcommands do shell out to small host tools — see the optional list below.**git** and**bash**— to clone the repo and run the CLI.** A web browser you can reach**— for the one-time login approval. It does** not**have to be on the same machine; you just need to open a URL and copy a code back (see step 4). Handy for headless servers.**A Claude account with an active plan**(Pro / Max / Team / Enterprise) that can sign in via`/login`\n\n.**jq***(needed by some subcommands, not*—`hoop start`\n\n)**required** by`hoop setup`\n\nand`hoop logout`\n\n, which edit JSON on the host (the wizard reads your gh/Claude identity into`profile.md`\n\n; logout rewrites the sandbox credentials).`hoop open`\n\nuses it too but degrades with a warning if it's absent. Install: macOS`brew install jq`\n\n; Debian/Ubuntu`sudo apt-get install -y jq`\n\n.**curl***(optional — never strictly required)*— used by`hoop setup`\n\nto probe Docker Model Runner and by the dashboard health-wait; both fall back to a bash`/dev/tcp`\n\nprobe when it's missing. Install:`brew install curl`\n\n/`sudo apt-get install -y curl`\n\n.**awk***(needed only for*— required by`hoop mount`\n\n)`hoop mount add`\n\n/`remove`\n\nto rewrite the mounts list; unused by every other command.**Docker Model Runner***(optional)*— for semantic-search embeddings.`hoop setup`\n\nenables it (Docker Desktop AI, or the`docker-model-plugin`\n\non Docker Engine) and appends the embedding model to the compose stack via Compose's`models:`\n\nelement (**Docker Compose v2.38+**), so`hoop start`\n\nauto-pulls it and wires the endpoint into the sandbox. If DMR is ever unreachable — or Compose is older than v2.38 —`hoop start`\n\ntransparently drops the override and falls back to BM25 rather than failing. Skippable — Ollama, OpenAI, or a custom OpenAI-compatible endpoint also work.\n\nRun `hoop doctor`\n\nat any time to verify the host has everything and see how close you are to a clean Docker-only setup.\n\n**Steps**\n\n```\n# 1. Clone and enter the repo\ngit clone https://github.com/bruno-de-queiroz/hoop && cd hoop\n\n# 2. (optional) Put `hoop` on your PATH + shell completion.\n#    Skip this and prefix every command with ./plugins/hoop/cli/hoop.sh instead.\n./plugins/hoop/cli/hoop.sh install      # then open a new shell so `hoop` resolves\n\n# 3. Bring up the whole stack. The FIRST run builds both images (a few minutes);\n#    later starts are fast. Serves the dashboard at http://localhost:7842/.\nhoop start\n\n# 4. (optional) Configure your stack interactively — memory, code-graph RAG,\n#    platform MCPs, docs RAG, semantic search, observability, second-brain, etc.\n#    This is the /hoop:setup wizard, native to the CLI (needs no host Claude Code).\nhoop setup\n\n# 5. One-time: authenticate the sandbox with its OWN Claude account.\n#    Drops you into `claude` inside the sandbox — type /login, open the printed\n#    URL in your browser, approve, paste the code back, then /exit.\nhoop login\n```\n\nThen open ** http://localhost:7842/**. Because the dashboard only trusts localhost, your browser is recognized as the host automatically — the access token is minted for you, nothing to paste. (Remote access is deliberately gated; see\n\n[Architecture](#architecture).)\n\nThat's the full loop: `hoop start`\n\n→ `hoop setup`\n\n(optional) → `hoop login`\n\n→ open the dashboard. A few notes for the standalone path:\n\n**Configure your stack with** This is the interactive wizard (memory, code-graph RAG, automation, platform MCPs, docs RAG, semantic search, observability, design, second-brain, telemetry isolation) — the same one behind`hoop setup`\n\n.`/hoop:setup`\n\n, but native to the CLI so it needs no host Claude Code. It runs**before**`hoop login`\n\n(writing MCP config needs no auth). For one-off additions later,`hoop add mcp/plugin/skill`\n\n(see the[CLI](#cli-hoop)table) persists into the sandbox profile across rebuilds.**Terminal-only?**`hoop open`\n\nruns an interactive`claude`\n\nin a throwaway sandbox over your current directory — no dashboard, no browser needed after login.**Updating:**`git pull`\n\nthen`hoop rebuild`\n\npicks up new code into both images.**Signing out / switching accounts:**`hoop logout`\n\nclears the sandbox's Claude login so a different account can`hoop login`\n\n.\n\n`hoop setup`\n\n(or `/hoop:setup`\n\n, which points you to it) walks you through these steps with one consent at the top, then installs each pick — auto-runnable and secret-taking MCPs run immediately (every command printed first); browser-login, plugin-marketplace, and host-CLI options are printed as guided steps to finish yourself:\n\n| # | Step | Pick |\n|---|---|---|\n| 1 | Consent | Y / N |\n| 2 | Detect prior state | (read-only) |\n| 3 | Memory | claude-mem (installed automatically — no choice) |\n| 4 | Code-graph RAG (if you code) | Serena / claude-context / code-graph-mcp / Cognee / skip |\n| 5 | Automation | n8n-mcp yes / no |\n| 6 | Platform MCPs | multi-select: Atlassian, Google Workspace, GitHub, incident.io, Slack |\n| 7 | Docs RAG | Context7 yes / no |\n| 8 | Observability | Sentry / Datadog (multi-select) |\n| 9 | Design | Excalidraw yes / no |\n| 10 | Second-brain | Obsidian (3 flavors) / Notion (2) / Logseq / NotebookLM |\n| 11 | Sign-ins | Claude Code `/login` , then gh (device flow) + any queued MCP OAuth — all inside the sandbox |\n\nEach run appends to the sandbox profile's `~/.claude/hoop/sandbox/profile/.claude/hoop/install-log.md`\n\n(also viewable from the dashboard) so re-runs are auditable. Secrets never reach the log — they go straight to `claude mcp add -e`\n\nor the 0600 `~/.claude/hoop/hoop.env`\n\n.\n\n`/hoop:dashboard`\n\n(or `start | stop | restart | rebuild | status | logs`\n\n) runs the dashboard **inside a container**. Your host only needs Docker Desktop — no Node, no `npm install`\n\n, no Next.js build pollution. Each verb takes an optional service target (`all`\n\n(default) · `sandbox`\n\n· `dashboard`\n\n); `start`\n\nbuilds lazily (only when an image is missing) while `rebuild`\n\nalways rebuilds.\n\nPairing (inviting a teammate to co-drive a session) uses ** cloudflared** to expose the local dashboard over a public tunnel — it's\n\n**baked into the dashboard image**, so nothing to install on the host. The dashboard runs fine without pairing; only share links start a tunnel.\n\nFive panels:\n\n**Sessions**— fs.watch on`~/.claude/sessions/`\n\n; updates in real time.**Skills**— every skill on disk (user + plugin), filterable, with a one-click \"Run\" that spawns`claude -p '/<name>'`\n\ninside the dashboard container and streams stdout back to the panel.**Sub-agents**— nested tree reconstructed from PreToolUse / PostToolUse events on the`Agent`\n\ntool. Click a node to see its prompt, tool calls, and final output.**Events**— chronological live tail via Server-Sent Events. Hooks push each event to the sandbox's`/ingest`\n\nover the Unix domain socket; the dashboard tails the resulting stream with zero polling.**Search**— opens with ⌘K. BM25 (FTS5) always works; semantic search (sqlite-vec, 768-dim embeddings) activates when an embedding backend is configured. Set one up via`hoop setup`\n\n/`/hoop:setup`\n\n— recommended is**Docker Model Runner**(added to the compose stack via Compose's`models:`\n\nelement and pulled on`hoop start`\n\n), with Ollama (`nomic-embed-text`\n\n), OpenAI, or any custom OpenAI-compatible endpoint as alternatives. Hybrid fuses BM25 + semantic via Reciprocal Rank Fusion.\n\nThe dashboard is single-user and localhost-only by design; access is gated by a per-install token (see [Architecture](#architecture) below).\n\n`plugins/hoop/cli/`\n\nships a small [oosh](https://github.com/bruno-de-queiroz/oosh)-based CLI that wraps the runtime. It lives **inside the plugin** (framework engine + entry point + completions + the stack engine in `lib/stack.sh`\n\n) so it ships with the plugin and the slash commands (`/hoop:setup`\n\n, `/hoop:dashboard`\n\n) can invoke it directly. It needs no external install and resolves its own paths.\n\n```\n./plugins/hoop/cli/hoop.sh install     # symlink `hoop` onto PATH + shell completion (bash/zsh)\n# or run in place without installing:\n./plugins/hoop/cli/hoop.sh <module> <command>\n```\n\nTwo levels: **top-level verbs** act on the whole stack; **modules** scope a single service. All of them drive one engine (`cli/lib/stack.sh`\n\n) — the single source of truth for host-side preflight (profile bind-mount prep, dashboard/peer auth tokens, forwarded embedding env, compose orchestration). The Claude onboarding bypass + plugin/hook wiring run *inside* the sandbox on boot (`sandbox/seed-profile.mjs`\n\n), so the host needs no jq. `start`\n\n/`rebuild`\n\nare deliberately split — `start`\n\nonly builds an image when it's missing (fast otherwise), while `rebuild`\n\nalways rebuilds so you pick up code changes.\n\n| Command | What it does |\n|---|---|\n`hoop start` · `stop` · `restart` · `rebuild` · `status` · `logs` |\nWhole stack (`agent-sandbox` + `dashboard` ) via the engine (`up -d` / `down` / `build + up -d --force-recreate` for the project). `rebuild` takes `-n|--no-cache` . |\n`hoop login` · `logout` |\nAuthenticate the sandbox with its own Claude account (one-time). `login` drops you into `claude` inside the running sandbox to run `/login` (paste-code flow — approve the URL in your host browser); the sandbox mints and self-refreshes its own OAuth token. `logout` clears it so you can sign in as a different account. Your host credentials are never read or copied. |\n\n| Module | Commands | What it does |\n|---|---|---|\n`dashboard` |\n`start` · `stop` · `restart` · `rebuild` · `status` · `logs` |\nControls only the (`dashboard` UI container`--no-deps` , leaves `agent-sandbox` alone). `rebuild` takes `-n|--no-cache` . |\n`sandbox` |\n`start` · `stop` · `restart` · `rebuild` · `update` |\nControls only the . Lifecycle verbs run the shared engine scoped to the sandbox (so plugin wiring + forwarded env still happen); `agent-sandbox` container`rebuild` recreates the container (`-n|--no-cache` to skip the layer cache); `update` pins the baked-in `claude-code` version (`-c|--claude-version` ). |\n`open` |\n(default) |\nRuns a fresh, telemetry-isolated sandbox over the current directory: mounts `$PWD` read-write into the agent workspace and launches `claude` interactively (`docker run --rm -it` , real tty for the TUI). Forces `HOOP_DISABLE_TELEMETRY=1` (add `-T|--telemetry` to allow bundled-tool telemetry) and strips the dashboard-only hooks + the hoop plugin from an overlay `settings.json` , while keeping credentials, setup MCPs, skills, and other plugins. Extra args pass straight through, e.g. `hoop open --model opus` or `hoop open \"fix the failing test\"` . |\n`add` |\n`mcp` · `plugin` · `skill` |\nInstalls a component into the sandbox profile so it persists across rebuild/restart/recreate and is shared by both dashboard sessions and `hoop open` (the profile is bind-mounted into both). `mcp <name> [flags] [-- <cmd>]` → `claude mcp add` , defaulting to `--scope user` so the server is global (not stranded under one project — pass your own `-s|--scope` to override); `plugin [-m|--marketplace <spec>] <plugin[@marketplace]>` → `claude plugin install` ; `skill -d|--dir <dir> [-f|--force]` copies a local skill directory (must contain `SKILL.md` ) into `~/.claude/skills/<name>` , dereferencing symlinks so the real files resolve inside the container. `mcp` /`plugin` need the sandbox running; `skill` is host-only. |\n`mount` |\n`add` · `list` · `remove` |\nBind-mounts host folders into the sandbox workspace at `/home/agent/workspace/<name>` . `add -p|--path <host-dir> [-n|--name <name>]` adds one (the `-p` path tab-completes directories), `list` shows the configured mounts, `remove <name>` drops one. Mounts persist via a generated compose override; each `add` /`remove` recreates the .`agent-sandbox` container |\n`install` |\n(default) |\nBare `hoop install` symlinks `hoop` onto PATH + wires shell completion (`-f|--force` reinstalls over an existing symlink). The repo itself is never modified; `hoop uninstall` removes the symlink and shell wiring. |\n`setup` |\n(default) |\nRuns the interactive stack wizard (the native port of `/hoop:setup` ): boots the sandbox, then walks memory / code-graph / automation / platform MCPs / docs RAG / semantic search / observability / design / second-brain / telemetry, installing each pick into the sandbox profile. Auto/secret MCPs run via `claude mcp add` ; OAuth/plugin/host-CLI sign-ins complete at the end inside the sandbox. Needs a TTY (refuses head-less), runs before `hoop login` , writes `profile.md` + `install-log.md` . `--reset-first` wipes all sandbox state for a blank slate first. |\n`doctor` |\n(default) |\nRead-only health check of the host + stack through a Docker-only-standalone lens: verifies Docker + Compose v2, reports which optional subcommand tools (`jq` /`curl` /`awk` ) are present, checks the sandbox image/containers, Claude auth, and the semantic-search backend (incl. the DMR Compose `models:` override + reachability). Fails only on truly blocking problems; everything else is advisory. |\n\n```\nhoop start                    # bring up the whole stack at http://localhost:7842/\nhoop setup                    # interactive wizard: configure the sandbox stack (no host Claude Code needed)\nhoop login                    # one-time: authenticate the sandbox with its own Claude account\nhoop dashboard rebuild        # rebuild + recreate ONLY the dashboard container\nhoop sandbox rebuild          # rebuild + recreate ONLY the agent-sandbox container\nhoop open                     # interactive claude in a sandbox over $PWD\nhoop add mcp context7 -- npx -y @upstash/context7-mcp   # install an MCP (user scope) into the sandbox\nhoop add skill -d ~/.claude/skills/impeccable           # copy a local skill into the sandbox profile\nhoop mount add -p ~/code/myproject                      # expose a host folder to the sandbox workspace\n```\n\nThe `add`\n\n/ `mount`\n\nsubcommands are also exposed as the ** /hoop:add** and\n\n**slash commands, which delegate to this CLI. Everything**\n\n`/hoop:mount`\n\n`add`\n\nwrites lives in `~/.claude/hoop/sandbox/profile`\n\n(bind-mounted at `/home/agent`\n\n), so a component installed once is available in every dashboard session and in `hoop open`\n\n, and survives image rebuilds.`hoop open`\n\nuses the `hoop-sandbox`\n\nimage (build it once with `hoop sandbox rebuild`\n\n) and mounts the sandbox Claude profile (`~/.claude/hoop/sandbox/profile`\n\n, `-p\\|--profile`\n\nto override) so `claude`\n\nis already authenticated — run `hoop login`\n\nonce to authenticate the sandbox with its own Claude account. Unlike the dashboard's `agent-sandbox`\n\n, `open`\n\nruns with telemetry blackholed and without hoop's dashboard hooks/plugin (which need the dashboard's socket), so it's a clean, isolated interactive session that still has your setup MCPs and skills. `hoop install`\n\n/ `hoop uninstall`\n\nmanage the PATH symlink and shell wiring; the repo itself is never modified.\n\nThe `hoop-sandbox`\n\nimage ships a **headless Chromium + the official **, registered in the sandbox profile automatically (from inside the container, via\n\n`@playwright/mcp`\n\n`claude mcp add`\n\n, so the registration can't point at a browser the running image doesn't have). The agent gets `browser_navigate`\n\n, `browser_click`\n\n, `browser_type`\n\n, `browser_fill_form`\n\n, `browser_snapshot`\n\n, `browser_take_screenshot`\n\n, and more — driven by a browser that runs **entirely in-container** as the unprivileged\n\n`agent`\n\nuser. No host browser, no host process, no `host.docker.internal`\n\ngymnastics, and nothing runs in *your*user context. Because the same image + profile back both surfaces, browser tools are available in every dashboard session\n\n**and** in\n\n`hoop open`\n\n.Two deliberate hardening choices:\n\n**Ephemeral profile (** The browser profile is kept in memory and never written to disk, so no cookies or logins persist across sessions and there's no ambient auth lying around. To drive a site that needs a login, hand the browser tools a`--isolated`\n\n).`--storage-state`\n\nfile (see the).`@playwright/mcp`\n\ndocs**No arbitrary-code tool.**`@playwright/mcp`\n\nexposes`browser_run_code_unsafe`\n\n(arbitrary JavaScript in the Playwright process, RCE-equivalent) as a non-removable \"core\" capability. hoop denies it via Claude's own`permissions.deny`\n\nin the sandbox`settings.json`\n\n, so the model never sees it — the rest of the toolset is unaffected.\n\n`/hoop:dashboard`\n\ncan hand a **share link** to a teammate over a `cloudflared`\n\ntunnel. They open it, pick a name, and the host admits them; from that point both sides see the same live transcript and can chat (`>`\n\nprefix) or co-drive the model — from a laptop or a phone.\n\nRun a turn with `/plan <task>`\n\nand the sandbox forces the agent **read-only**: it investigates, then submits a plan that opens in a **review panel**. The host and any full-capability peer can drop **inline comments** anchored to the exact passage — synced live across everyone — then **Approve** or **Request changes**. A rejection feeds the comments back and the agent revises the plan.\n\n**7 · Iterate** — “Request changes” feeds the comments back; the agent revises read-only and resubmits, and once the host approves the session leaves plan mode and implements it.\n\nPeers co-drive the same session from anywhere — here's “Rafael” reviewing the plan from his phone:\n\nThe runtime is split across **two containers** so a compromise of the web layer can't reach your credentials:\n\n(trusted) — owns the`agent-sandbox`\n\n`claude`\n\nbinary, your Claude profile (OAuth credentials, plugins, MCP config, sessions/transcripts), the long-lived`claude -p --input-format=stream-json`\n\nsubprocesses, and`events.db`\n\n(sole writer). It exposes a small HTTP API over a**Unix domain socket**(`/var/run/hoop/sandbox.sock`\n\n) — no TCP port. The model runs as an unprivileged`agent`\n\nuser, and the claude-facing plugin surface the sandbox actually uses (hook scripts + the`.mcp.json`\n\ntools server + the plugin manifest) is**baked into the sandbox image** at`/opt/hoop`\n\n, root-owned so that user can't tamper with the hook scripts. Host-only pieces are deliberately not baked: the`commands/`\n\n(host-stack slash commands) and`agents/`\n\n(host diagnostics) aren't meaningful inside the sandbox, and`catalog/`\n\n+`templates/`\n\nare read only by`hoop setup`\n\nrunning on the host. The image is self-contained — no host repo bind-mount — so it runs on a host with neither the repo at a fixed path nor Claude Code installed. (Set`HOOP_PLUGIN_DEV=1`\n\nto overlay the full host repo back onto`/opt/hoop`\n\nfor live-editing plugin source in development.)(untrusted view) — Next.js bound to`dashboard`\n\n`127.0.0.1:7842`\n\n, with**no**. Every API route is a thin proxy that calls the sandbox over the socket, so a compromised dashboard can only do what the sandbox API allows.`claude`\n\nbinary and no access to`~/.claude`\n\n`events.db`\n\nstays the source of truth: the sandbox writes it (hooks fire there), the dashboard only reads it via the socket. Three tokens gate the three hops:\n\n| Token | Hop | Header |\n|---|---|---|\n`dashboard.token` |\nbrowser ↔ dashboard | `x-dashboard-token` (+ cookie) |\n`sandbox.token` |\ndashboard ↔ sandbox | `x-sandbox-token` |\n`hook.token` |\nhook scripts ↔ sandbox `/ingest` |\n`x-hook-token` |\n\nThis is the \"sandboxed agent\" model: the OS-process boundary is the security boundary, and the dashboard holds no secrets. Pairing (co-driving a session with a teammate) layers on top via `cloudflared`\n\nand per-peer share tokens.\n\n```\n~/.claude/hoop/\n  hoop.env                    opt-in overrides forwarded into the sandbox (0600):\n                              OPENAI_API_KEY / EMBEDDING_BASE_URL / EMBEDDING_MODEL /\n                              EMBED_DIM / telemetry switches\n  host-gateway.cache          cached host.docker.internal address\n  shares.json                 active pairing share links\n  sandbox/profile/            the sandbox's Claude HOME (bind-mounted to /home/agent)\n    .claude/\n      .credentials.json       the sandbox's OWN Claude OAuth token (never the host's)\n      hoop/\n        events.db             SQLite (FTS5 + sqlite-vec); the dashboard reads it over the socket\n        events.jsonl          append-only event audit log + replay buffer if the dashboard is down\n        install-log.md        audit trail of every `hoop setup` run\n        profile.md            identity + installed-stack summary written by the wizard\n~/.local/share/hoop/          per-install secrets: dashboard.token, peer-signing.secret (0600)\n```\n\nBecause the sandbox's HOME is bind-mounted from `sandbox/profile/`\n\n, everything the\ncontainer writes to its own `~/.claude/hoop/`\n\nlands under that nested path on the host —\nthe agent owns all runtime state; the dashboard only reads it over the socket.\n\nThe plugin does **not** edit your `~/.claude/CLAUDE.md`\n\nor `~/.claude/settings.json`\n\n.\n\n```\nhoop/\n  .claude-plugin/marketplace.json        self-hosted marketplace\n  plugins/hoop/\n    .claude-plugin/plugin.json           manifest\n    commands/                            /hoop:setup, /hoop:dashboard, /hoop:plan, /hoop:add, /hoop:mount\n    catalog/                             8 install recipes (one per wizard layer)\n    hooks/scripts/                       emit-event.sh (Unix-socket push, <50ms) + permission-gate.sh\n    sandbox/                             trusted agent runtime (owns claude + all state)\n      Dockerfile                         sandbox image (claude + Node HTTP server on a UDS)\n      server.ts                          HTTP-over-Unix-socket API the dashboard proxies to\n      lib/                               active-sessions, db, ingestor, embeddings, sessions,\n                                         skills, agents, search, spawn, shares, peer-joins, …\n    dashboard/                           untrusted view (no claude, no credentials)\n      Dockerfile                         dashboard image (Next.js standalone; no claude, no compilers)\n      docker-compose.yml                 agent-sandbox + dashboard services (+ optional DMR embedding model via Compose `models:`)\n      app/api/*                          proxy routes → sandbox over the socket\n      lib/sandbox-client/                HTTP-over-UDS client; lib/auth*, lib/peer-*  (auth + pairing)\n    shared/                              logger, clamp, shutdown (used by both images)\n    templates/                           profile.md + install-log.md wizard templates (read host-side)\n    cli/                                 oosh-based `hoop` CLI (ships inside the plugin)\n      oo.sh                              oosh framework engine\n      hoop.sh                            entry point (+ hoop.comp.sh / hoop.zcomp.sh)\n      lib/stack.sh                       the two-service runtime engine (preflight + compose)\n      modules/                           dashboard, sandbox, open, login, logout, add, mount, setup, doctor, install, uninstall\n  README.md\n  LICENSE\n```\n\nThe sandbox seeds its **own** profile on boot (`sandbox/seed-profile.mjs`\n\n, run by the container entrypoint using the image's baked Node — no host jq): it wires `settings.json`\n\nhooks on PreToolUse, PostToolUse, SessionStart, Stop, and UserPromptSubmit — declared in the sandbox profile (not in a host `hooks.json`\n\n) so they only ever run inside the sandbox, never on your host. Every event runs `hooks/scripts/emit-event.sh`\n\nwhich:\n\n- POSTs the JSON event to the sandbox's\n`/ingest`\n\nendpoint over the Unix domain socket (`--unix-socket`\n\n) via`curl --max-time 1`\n\n— push, not polling. (`HOOP_INGEST_URL`\n\ncan override the target for legacy/dev setups.) - Falls back to appending to\n`events.jsonl`\n\nif the socket isn't reachable. - Exits in <50ms — pure bash + curl, no node/python/jq.\n\nThe sandbox is the sole writer: its ingest route persists to SQLite + FTS5 (and sqlite-vec if semantic search is enabled), then emits on an in-process EventEmitter that feeds the SSE stream the dashboard proxies to the browser. On startup, the ingestor drains `events.jsonl`\n\nfrom its saved offset so events written while the dashboard was down replay automatically.\n\n`hooks/scripts/permission-gate.sh`\n\n(PreToolUse) is the sole tool-permission gate: it asks the sandbox over the same socket and blocks until the host (or a peer allowed to decide) responds — this is what backs `/plan`\n\n's read-only enforcement and the plan-review approval flow below.\n\n**v0.1**(this release): wizard, containerized dashboard, push-based event pipeline, BM25 + opt-in semantic search.** v0.2**: inject skill triggers into existing Claude sessions instead of spawning new ones; per-skill-run isolation via ephemeral containers.**v0.3+**: more catalog entries; non-Claude clients (Cursor, Codex) where MCPs overlap.\n\nThis is opinionated by design. PRs welcome, especially:\n\n- Verifying install commands on less-common platforms (Windows, NixOS).\n- Adding new catalog options with verified install recipes.\n\nPlease open an issue before changing the curation philosophy (curated menus, one-consent install, no re-implementation, single-user localhost dashboard).\n\nMIT", "url": "https://wpnews.pro/news/show-hn-hoop-a-sandboxed-p2p-live-collaboration-harness-for-claude-code", "canonical_source": "https://github.com/bruno-de-queiroz/hoop", "published_at": "2026-07-21 23:46:06+00:00", "updated_at": "2026-07-21 23:52:10.522098+00:00", "lang": "en", "topics": ["developer-tools", "ai-tools", "ai-agents", "ai-infrastructure"], "entities": ["Hoop", "Claude Code", "Bruno de Queiroz", "Docker", "Next.js", "Anthropic"], "alternates": {"html": "https://wpnews.pro/news/show-hn-hoop-a-sandboxed-p2p-live-collaboration-harness-for-claude-code", "markdown": "https://wpnews.pro/news/show-hn-hoop-a-sandboxed-p2p-live-collaboration-harness-for-claude-code.md", "text": "https://wpnews.pro/news/show-hn-hoop-a-sandboxed-p2p-live-collaboration-harness-for-claude-code.txt", "jsonld": "https://wpnews.pro/news/show-hn-hoop-a-sandboxed-p2p-live-collaboration-harness-for-claude-code.jsonld"}}