Cc Connect
Peer-to-peer chat substrate that lets multiple Claude Code instances share the same context.
Ask AI about Cc Connect
Powered by Claude Β· Grounded in docs
I know everything about Cc Connect. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
cc-connect
Multiplex shared context across Claudes β not one Claude across humans.
A peer-to-peer substrate that lets multiple Claude Code instances share the same chat history and dropped files. Each developer keeps their own Claude. The shared layer rides on iroh-gossip; each Claude reads its local replica via a UserPromptSubmit hook. Both halves of the experience ship as one project:
- VSCode extension (recommended) β Rooms tree + chat/Claude panel inside your editor.
- TUI β
cc-connect room startfor terminal users; same Tickets, same hook injection.
v0.1 status: feature-complete in commits, full protocol drafted in
PROTOCOL.md. Vendored ed25519 patches block crates.io publish until upstream releases aned25519-dalekagainst fixedpkcs8(seeTODOS.md).
β Read
SECURITY.mdbefore inviting anyone to a Room. A Ticket is a capability β anyone holding it can read your chat, drop files, and prompt-inject your Claude. v0.1 has no end-to-end Message signatures and no Ticket revocation. The threat model lays out exactly what is and isn't protected.
How the magic moment works
βββββββββ Alice's machine βββββββββ βββββββββ Bob's machine βββββββββ
β β β β
β $ cc-connect room start β β $ cc-connect room join cc1-β¦ β
β βββ claude βββ βββ chat βββ β gossip β βββ claude βββ βββ chat βββ β
β β β β β β βββββββΊ β β β β β β
β ββββββββββββββ βββββββββββββ β βββββββ β ββββββββββββββ βββββββββββββ β
β β β β
β Alice asks her Claude: β β Bob types in his chat pane: β
β "Redis or Postgres?" β β "postgres, we have it" β
β β β β
β Hook fires on Alice's next β β β
β prompt β injects Bob's message β β β
β into Alice's Claude context. β β β
β Alice's Claude: "going Postgres β β β
β per the chat" β β β
ββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββββ
Bob never typed anything special. Alice never copy-pasted anything. The hook reads Bob's messages from a locally-replicated log.jsonl and prepends them to Alice's prompt.
Full architecture: PROTOCOL.md. Decision rationale: docs/adr/.
Install
You need: macOS or Linux, a working Claude Code install. Rust is not required for the default path β the bootstrap downloads a pre-built binary for your platform.
One-liner (recommended β no Rust needed)
curl -fsSL https://raw.githubusercontent.com/Minara-AI/cc-connect/main/scripts/bootstrap.sh | bash
Detects your platform (macOS arm64 / x86_64, Linux x86_64), pulls the matching tarball from the latest GitHub release, verifies its sha256, then runs the bundled install.sh --skip-build to register the UserPromptSubmit hook + cc-connect-mcp server in ~/.claude/, symlink binaries into ~/.local/bin/, and run cc-connect doctor. Total time: ~30 seconds on a fast network. Idempotent β safe to re-run.
Pin a specific version (handy for CI):
curl -fsSL <β¦/bootstrap.sh> | CC_CONNECT_VERSION=v0.1.0 bash
Build from source (developers / unsupported platforms)
If you want to hack on cc-connect, or your platform isn't in the release matrix (e.g. Linux aarch64, BSD), build from source β needs Rust β₯ 1.89:
# One-liner, source mode:
curl -fsSL <β¦/bootstrap.sh> | CC_CONNECT_FROM_SOURCE=1 bash
# Or clone + install yourself:
git clone https://github.com/Minara-AI/cc-connect.git
cd cc-connect
./install.sh
install.sh checks the toolchain (offers rustup if Rust is missing), builds the workspace, backs up ~/.claude/settings.json, idempotently registers the hook + MCP server, symlinks every binary, runs cc-connect doctor. --yes for unattended, --skip-build to reuse an existing target/release/. First build takes ~5β10 minutes (iroh stack + vendored ed25519).
Restart Claude Code afterwards (either path) so it picks up the new hook + MCP tools. After install, every command is available as cc-connect β¦ from any directory.
Install the VSCode extension
The default way to use cc-connect β see next section for what you get.
- Grab the latest
.vsixfrom GitHub Releases (e.g.cc-connect-vscode-0.2.2.vsix). - Install:
code --install-extension cc-connect-vscode-0.2.2.vsix - Fully quit Claude Code (Cmd-Q on macOS, not just close the window) and reopen, so it picks up the new hook + MCP entries the bootstrap installer wrote.
That's it β VSCode's activity bar gets a cc-connect icon. Marketplace publish is on the roadmap; for now .vsix is the canonical channel.
Developer mode (extension contributors)
Hacking on the extension itself? Open vscode-extension/ in VSCode and press F5 for an Extension Development Host (auto-builds + reloads). To package locally:
cd vscode-extension
bun install
bun run compile
bunx @vscode/vsce package
Use it in VSCode (recommended)
The cleanest day-to-day experience is the editor extension. Both halves of cc-connect β the chat substrate and your Claude Code session β live inside one VSCode panel, no terminal multiplexer needed.
End-to-end in 3 steps, after you've run the bootstrap installer once on this machine:
- Install the extension β
code --install-extension cc-connect-vscode-X.Y.Z.vsix(download from Releases). - Fully quit + reopen Claude Code so it picks up the new hook + MCP entries.
- Click the cc-connect activity-bar icon in VSCode β Start Room (or Join Room with a peer's ticket). The first time, a 4-step walkthrough auto-opens to verify your setup.
ββ Activity Bar βββββββββββββββββββββββββββββββββββββββββββββ
β cc-connect Rooms β
β βΈ team-A ALIVE β
β βΈ design ALIVE β
β βΈ debug DORMANT β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββ Bottom panel ββββββββββββββββββββββββββββββββββββββββββββββ
β team-Aβ¦ @alice ready [π copy ticket] β
ββ [π¬ Chat] [β¦ Claude 3] ββββββββββββββββββββββββββββββββββ€
β ββ chat ββββββββββββββββββββββ ββ claude ββββββββββββββ β
β β @bob: postgres, we have it β β β Thought for 2s β β
β β (me): yes, on it β β β cc_send Β· 13 bytes β β
β β β β β cc_wait_for_β¦ β β
β β [Message Β· @ to mention] β β [Ask Claudeβ¦ ]π‘οΈββ β
β ββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Drag the Room panel to the secondary side bar for a vertical Slack-style split next to your editor.
First Room
After the 3-step install above:
- Start a Room (you become host) β click the + in the Rooms tree title bar. Your
cc1-β¦ticket is auto-copied to your clipboard. Share it however you normally share text. - Or Join a peer's Room β click the cloud-download icon, paste the
cc1-β¦ticket they sent you. - The Room panel opens at the bottom of VSCode. Claude auto-greets the room (
o/ joined.) and starts listening for@you-ccmentions. - Click copy ticket in the room-meta strip any time to re-share.
If the Rooms view ever says "binary not found", the cc-connect CLI got upgraded out from under you β re-run the bootstrap one-liner.
What's in the Room panel
| Tab | What it gives you |
|---|---|
| Chat | IM-style rows: own messages right-aligned with iMessage bubbles, peers on the left. @-mention autocomplete from recent senders. / button opens a slash-command picker (/drop, /at). + button opens VSCode's native file picker β drops the file into the room. |
| Claude | Your local Claude Code session for this Room. Tool calls render as IN/OUT cards with VSCode-native styling + per-tool codicons. Live "Thought for Xs" indicator. Full-markdown text replies. Active-editor chip above the input β click to attach @<workspace-relative-path> to your prompt. |
Permission modes (Claude pane bottom-right pill)
Click the pill to cycle:
| Mode | Behaviour |
|---|---|
| auto (default) | Every tool runs without asking. The cc-connect Room model is "trusted substrate"; this is the ergonomic default. |
| ask edits | Claude can read freely; Edit / Write / Bash calls prompt for approval. |
| plan | Claude can read but cannot run any side-effectful tool. |
| ask all | Every tool call shows an inline Allow / Deny / Always allow bubble in the Claude log. The textarea greys out until you decide. |
Other niceties
- Conversation history β the clock-icon button in the Claude pane lists every past Claude session for this workspace (parses
~/.claude/projects/); click one to replay it read-only. - Auto-greet on join β uses the same
bootstrap-prompt.md+auto-reply-prompt.mdas the TUI launcher, so the embedded Claude knows it's in a Room and enters the listener loop without you typing anything. - File-reference chips β paths the user types in the Claude prompt render as clickable codicons; click to open the file in the editor.
- New chat β
+icon in the Claude pane head mints a freshsessionIdwithout closing the Room. - Tickets are interchangeable β Tickets minted by the VSCode extension are byte-identical to those from
cc-connect room start. Use either side freely.
The extension is purely TypeScript β no native code, no extra runtime deps beyond the cc-connect binaries you already installed. Source: vscode-extension/.
Or via the terminal (TUI alternative)
The TUI is the same Room model and same Tickets β pick whichever you prefer, mix freely between machines. Two commands cover everything:
cc-connect room start # mint a new Room β opens the TUI
cc-connect room join cc1-β¦ # join an existing Room by ticket
The host daemon, chat substrate, and MCP server are spawned + torn down for you. Ctrl-Q quits without stopping host daemons β that way peers can still join while you're not looking. Use cc-connect clear to stop them later.
ββ cc-connect [1-9] tab [Ctrl-N] new [Ctrl-W] close [F2/Tab] pane [Ctrl-Y] copy ββ
β [1] team-AΒ·H [2] design β β tab strip
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β ββ π€ claude Β· team-A ββββββββββββββββ ββ π¬ chat Β· team-A ββββββββββββββββββ β
β β $ β β [bob] use postgres β β
β β β β (@me) [alice] @dave PR ? β β
β β β β βΊ yes, on it β β
β ββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Key | Action |
|---|---|
1β9 | Switch tab |
Ctrl-N | New tab β paste a ticket to join |
Ctrl-W | Close tab (if you started the host daemon, prompts to also stop it) |
F2 / Tab | Toggle focus between chat and Claude panes |
Ctrl-Y | Copy the active tab's ticket to clipboard |
Ctrl-Q | Quit; host daemons stay alive |
Β·H on a tab label means you originated the Room (host-bg daemon is yours). Close the tab without stopping the daemon and the Room stays joinable for peers.
Configuration
These knobs apply to both VSCode and TUI paths (the extension shells out to the same cc-connect binary).
Pick a nickname
cc-connect room start --nick alice # persists to ~/.cc-connect/config.json
Skip the flag and the first run prompts you. The nick is local-only β peers see the Pubkey of your machine plus whichever nick you sent in your last message.
Use your own relay (optional)
By default cc-connect routes through n0's free public relay cluster (the same iroh deployment everyone uses). For your own:
cc-connect room start --relay https://relay.yourdomain.com
The host's --relay URL is baked into the printed Ticket so joiners pick it up automatically. Stand-up walkthrough: .claude/skills/cc-connect-relay-setup/SKILL.md.
Multiplexer mode (TUI only)
The embedded TUI is the default. If you have zellij or tmux installed, you can opt in to a multiplexer-managed layout that uses the richer Bun + React + Ink chat panel (cc-chat-ui) on the right:
CC_CONNECT_MULTIPLEXER=zellij cc-connect room start
CC_CONNECT_MULTIPLEXER=tmux cc-connect room start
CC_CONNECT_MULTIPLEXER=auto cc-connect room start # zellij β tmux β embedded
Exit hint: Ctrl-q + y (zellij), Ctrl-b + d (tmux detach), Ctrl-Q (embedded).
Pin a binary version
For reproducible installs (CI, second machines, demo setups) pin the bootstrap to a specific release tag:
curl -fsSL <β¦/bootstrap.sh> | CC_CONNECT_VERSION=v0.5.0-alpha bash
Two-laptop demo
The real magic-moment test. Works the same in VSCode or the TUI β pick whichever side you're on.
- Both machines: install cc-connect (no-Rust one-liner above), then restart Claude Code.
- Alice (host):
- VSCode: click the cc-connect activity-bar icon β Start Room β click copy ticket.
- TUI:
cc-connect room start(thecc1-β¦ticket is auto-copied to clipboard).
- Bob (joiner): paste Alice's ticket.
- VSCode: Join Room β paste.
- TUI:
cc-connect room join 'cc1-β¦'.
- Bob types into his chat pane:
try sqlite for now. - Alice asks her Claude anything in her Claude pane. On submit, the hook reads Bob's message from Alice's local replica and injects it as context. Alice's Claude reply should reference Bob's suggestion.
That's the magic moment: Bob never @-mentioned Alice, Alice never copy-pasted anything. The substrate did the work.
If it doesn't fire, see Troubleshooting.
Sharing files
Inside the chat pane:
> /drop ./design.svg
[chat] dropped design.svg (148 bytes)
/drop <path> hashes the file into a local iroh-blobs MemStore, broadcasts a tiny gossip Message announcing the hash, then peers fetch the bytes out-of-band over the iroh-blobs ALPN against your NodeId. Both peers' Claudes see it as @file:<path> on the next prompt.
v0.2 cap: 1 GiB per file. Bytes flow via iroh-blobs, not gossip. Files persist for the lifetime of the room's chat-daemon. The cc_drop MCP tool refuses sensitive paths by default (SSH/AWS/GPG/Kube/Docker credentials, .env*, id_rsa*, *.pem, etc.); override per-process with CC_CONNECT_DROP_ALLOW_DANGEROUS=1. See SECURITY.md.
Letting Claude talk back (MCP tools)
cc-connect-mcp is registered as a Claude Code MCP server at install time, so any Claude Code session β TUI, VSCode extension, or the CLI elsewhere β sees the same seven tools (the cc-connect-hook gates them on CC_CONNECT_ROOM so they're only visible inside a Room):
| Tool | What it does |
|---|---|
cc_send | Broadcast a chat message into your room |
cc_at | Same as cc_send, but with @<nick> prefix |
cc_drop | Share a local file with peers (iroh-blobs) |
cc_recent | Last N chat lines from this room's log |
cc_list_files | Files dropped into the room (with local paths) |
cc_save_summary | Overwrite this room's rolling summary (auto-injected on every prompt) |
cc_wait_for_mention | Block until someone @-mentions this Claude (or a timeout) |
Try it: ask Claude (VSCode pane or TUI claude pane), "send '@all standup in 5' to the room". Claude calls cc_at and the message lands in every peer's chat scrollback.
Layered context injection
Every prompt's hook output is composed from three sections, each budget-bounded to keep the total β€ 8 KiB (PROTOCOL Β§7.3 step 6 / ADR-0004):
[cc-connect summary] β rolling summary (β€ 1.5 KiB)
Discussed Postgres vs SQLite (decided Postgres). β¦
[cc-connect files] β INDEX.md tail (β€ 1.5 KiB)
- bob design.svg (148B) @file:/Users/.../files/01XX-design.svg
- alice api.md (4096B) @file:/Users/.../files/01YY-api.md
[chatroom @bob 12:00Z] use postgres β unread chat verbatim (~5 KiB)
[chatroom for-you @alice 12:01Z] @dave PR ?
INDEX.md is auto-maintained β every file_drop appends a line. summary.md is Claude-driven: ask the embedded Claude to "summarise the room and save it" and it'll call cc_save_summary.
Command reference
cc-connect room start and cc-connect room join are the only commands you need day-to-day. Everything below is supporting / management / debug surface β most of it is invoked for you by the room launcher.
| Command | Audience | What it does |
|---|---|---|
cc-connect room start | everyone | Mint a fresh ticket, spawn the host-bg daemon, open the TUI. The recommended entry point. |
cc-connect room join <ticket> | everyone | Join an existing room by ticket, open the TUI. The recommended entry point. |
cc-connect doctor | everyone | Sanity-check the install. Prints binary mtimes, hook entry, MCP entry, identity perms. Run this if anything's misbehaving. |
cc-connect clear | everyone | Stop every running cc-connect background process (chat-daemons + host-bg). Use if a daemon got stuck or before reinstalling a fresh build. --purge also wipes ~/.cc-connect/rooms/. |
cc-connect upgrade | everyone | git pull + rebuild + reinstall in one shot. Identity + nicknames are preserved. --yes skips the y/N. |
cc-connect uninstall | everyone | Reverse install.sh entirely: stop daemons, strip the hook + MCP entries, remove ~/.local/bin symlinks. --purge also wipes ~/.cc-connect/, /tmp/cc-connect-$UID/, and stale ~/.claude/*.json.bak.* backups. |
cc-connect host-bg list | management | List running background-host daemons (one line per daemon). |
cc-connect host-bg stop <topic-prefix> | management | SIGTERM a specific daemon by topic-hex prefix. |
cc-connect host-bg start [--relay <url>] | management | Start a daemon without opening the TUI. Mainly for headless / CI scenarios. room start does this for you. |
cc-connect chat-daemon {list,stop,start} | management | Same shape as host-bg, but for chat-session daemons (the gossip + chat.sock side; only matters in the multiplexer path). |
cc-connect host | low-level | Bare-bones blocking host (no TUI, no claude, no MCP). Mostly useful for protocol smoke tests. Prefer room start. |
cc-connect chat <ticket> | low-level | Bare-bones REPL-only joiner (no TUI). Mostly useful for protocol smoke tests. Prefer room join. |
cc-connect host-bg-daemon | internal | Daemon entry point. Don't run directly β host-bg start spawns it. |
cc-connect chat-daemon-daemon | internal | Same shape, chat-daemon side. Don't run directly. |
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
cc-connect room start hangs at "binding endpoint" | Firewall blocks n0's relay servers | Try a different network. |
Joiner sees (joined late, no history available) | Both peers already moved past pre-join messages, or backfill RPC failed | Re-test on a clean room; if persistent, run with CC_CONNECT_GOSSIP_DEBUG=1 and inspect ~/.cc-connect/gossip-debug.log. |
Room says (peers: 1) but no messages flow | mDNS is blocked (corporate WiFi client isolation) | Try a coffee-shop / home network. |
| Hook silently does nothing | Settings.json hook path is relative, or stale binary on PATH | cc-connect doctor β it prints the registered hook path + binary mtimes. cc-connect upgrade to refresh. |
| Restarted Claude Code but it still doesn't see chat | Old cc-connect-mcp child still running | cc-connect clear, then restart Claude Code. |
| Can't see remote peer's messages but they see yours | Stale daemon from before the post-Apr fixes | cc-connect clear on both machines, cc-connect upgrade, retry. |
cargo build fails on ed25519-3.0.0-rc.4 | Missing [patch.crates-io] (you cloned without vendored/) | Re-clone or git fetch origin main && git reset --hard origin/main. |
| Identity file mode wrong | Drifted from 0600 | chmod 600 ~/.cc-connect/identity.key. The loader and doctor both warn. |
/tmp/cc-connect-$UID/ mode wrong / pre-existed as a symlink | Hostile co-tenant or earlier crash | rm -rf "$TMPDIR/cc-connect-$UID/" && cc-connect room start. PROTOCOL Β§8 mandates a 0700 non-symlink parent. |
If cc-connect-hook fired but you suspect it failed, check ~/.cc-connect/hook.log. The hook always exits 0 (PROTOCOL Β§7.4) so errors don't propagate to Claude Code.
Project layout
cc-connect/
βββ PROTOCOL.md v0.1 wire-and-disk specification
βββ CONTEXT.md Domain glossary (DDD-style)
βββ SECURITY.md Threat model
βββ CLAUDE.md Agent guide for Claude Code sessions in this repo
βββ crates/ Rust workspace (5 crates)
β βββ cc-connect-core/ Protocol primitives library (104 tests)
β βββ cc-connect/ host / chat / room / host-bg / chat-daemon / lifecycle / doctor / setup binary
β βββ cc-connect-tui/ Embedded TUI binary + library
β βββ cc-connect-mcp/ MCP stdio server (Claude Code β chat tools)
β βββ cc-connect-hook/ UserPromptSubmit hook binary
βββ chat-ui/ Bun + React + Ink chat panel (β cc-chat-ui), used in zellij/tmux paths
βββ vscode-extension/ VSCode extension (TS + React webview, no native code)
β βββ src/ Extension host (sidebar, panel, daemon orchestration, Claude SDK runner)
β βββ webview/ React app (chat, Claude pane, tool cards, permission bubbles)
β βββ media/walkthrough/ First-run setup walkthrough markdown
βββ layouts/ zellij KDL + tmux script + claude-wrap.sh + bootstrap/auto-reply prompts
βββ docs/
β βββ adr/ Architecture decision records
β βββ agents/ Per-repo config the engineering skills consume
βββ .github/workflows/ CI β release.yml (Rust binaries), vscode-extension-release.yml (.vsix), ci.yml (per-PR)
βββ .claude/skills/ Project-local Claude Code skills (publish, push, cc-connect-setup, β¦)
βββ .githooks/ Polyglot pre-commit + commit-msg hooks
βββ scripts/ bootstrap.sh + smoke tests + repo-config helpers
βββ tests/ FAKE-CLAUDE-CODE integration test
βββ vendored/ Patched ed25519 + ed25519-dalek (temporary; see TODOS.md)
Status / contributing
Want to contribute? Read CONTRIBUTING.md for the dev setup, commit conventions, and PR checklist. The CONTEXT.md glossary is load-bearing β domain terms in the codebase must match it. Architectural decisions get an ADR; wire-format changes get a v bump per PROTOCOL.md.
Bugs and feature requests: GitHub Issues. Security: private advisory, not a public issue (SECURITY.md).
Release process
Release tag namespaces
cc-connect ships two independent artifacts with their own release cadence. The namespace lives in the tag, not in separate repos β pick the right tag pattern for what you're releasing:
| Artifact | Tag pattern | What it ships | CI workflow |
|---|---|---|---|
| cc-connect CLI / TUI (Rust binaries) | v0.1.0, v0.2.0-rc.1 | cc-connect, cc-connect-hook, cc-chat-ui tarballs per platform attached to the GitHub release | release.yml |
| VSCode extension | vscode-extension-v0.1.0, vscode-extension-v0.2.0-rc.1 | cc-connect-vscode-<version>.vsix attached to the GitHub release | vscode-extension-release.yml |
The two pipelines are completely independent β bumping one never triggers the other. The version numbers don't have to track each other either; the extension declares the minimum cc-connect binary it needs through package.json (and the VSCode usage section makes the dependency explicit for users). Cutting a release:
# CLI / TUI
git tag v0.2.0
git push origin v0.2.0 # β release.yml builds tarballs
# VSCode extension (bump vscode-extension/package.json::version first β
# the workflow refuses to build if the tag and package.json disagree)
$EDITOR vscode-extension/package.json # version: "0.1.0" β "0.2.0"
git add vscode-extension/package.json
git commit -m "chore(vscode-extension): bump to 0.2.0"
git tag vscode-extension-v0.2.0
git push origin main vscode-extension-v0.2.0 # β vscode-extension-release.yml packages .vsix
The extension workflow refuses to build if the tag version doesn't match vscode-extension/package.json::version β keeps the on-disk version, the tag, and the .vsix filename in lockstep.
Install / uninstall surface contract
cc-connect uninstall and cc-connect upgrade are user-facing promises: a clean wipe and a clean reinstall. Honoring those promises is a release-time discipline.
Every release MUST keep the cleanup surface in sync with the install surface. The cleanup lives in crates/cc-connect/src/lifecycle.rs; when a release adds anything to the install surface β a new binary, a new ~/.claude/settings.json key, a new file under ~/.cc-connect/, a new MCP tool that registers itself somewhere β the matching removal must land in lifecycle.rs in the same PR.
For every release-shaped PR (anything touching install.sh, crates/cc-connect/src/setup.rs, or persistent file paths), the reviewer checks:
- Did
INSTALLED_BIN_NAMESget the new binary? - Did
run_clearget the new daemon'srun_stop? - Did
remove_hook_from_settings/remove_mcp_from_claude_jsonget the new JSON key? - Did
--purge(or another explicit removal step) cover any new persistent file outside~/.cc-connect/?
The contract is: a user who runs cc-connect uninstall --purge ends up with zero cc-connect-touched state on their machine, regardless of which version installed it.
License
Dual-licensed under MIT OR Apache-2.0 at your option. Contributions are accepted under the same dual license; there is no separate CLA. Participants in project spaces are expected to follow the Code of Conduct.
