Brainy Brainy
Docs Brainy

Connect your terminal

In this section

The fastest way to reach brainy from a terminal is not a config file — it is one plugin, and every brainy door hands it out. .claude-plugin/plugin.json, a .mcp.json pointer, and eleven skills (recall, remember, briefs, the Accords board, the working method) ship together, versioned with the engine itself, and are always current because the door that installs them is the door you are about to talk to.

The two-word install

Tell your terminal:

join brainy.soulcraft.com

or, for a door you run yourself:

join https://<your-door>

Either phrase is an instruction to fetch that door's own GET / (or GET /join) page and follow it. The page is agent-readable on purpose — markdown by default, a minimal HTML page for a browser — and names exactly two commands:

claude plugin marketplace add https://<your-door>/plugin/marketplace.json
claude plugin install brainy@brainy

Then restart: claude --continue. That is the whole install, for any door, hosted or self-hosted — the page always names ITS OWN URL, never a fixed address, so the same two-word phrase works against any brainy instance.

The three paths

1. Hosted — brainy.soulcraft.com

Nothing to run. Add the marketplace and install as above (door defaults to https://brainy.soulcraft.com), then either:

  • Sign in. When the hosted door offers it, the first tool call opens your browser, you log in, and the harness stores the token — nothing copied, nothing typed. This is the MCP authorization flow the harness itself implements: the door answers an unauthenticated POST /mcp with a 401 naming its own /.well-known/oauth-protected-resource document, the harness reads it, and the sign-in runs.

  • A suite key. Set BRAINY_API_KEY to a key your account issues. Only needed when the door does not offer sign-in.

2. Self-hosted — your own brainy host

Run brainy host --brains-dir <dir> --listen <host:port> --keys <file> --public-url <https://your-door>. That same process now serves the plugin too — GET /plugin/marketplace.json and GET /plugin/brainy-<version>.zip — with .mcp.json baked to point at ITSELF, so a plugin installed from your door always talks back to your door, never brainy.soulcraft.com.

Two ways a caller gets a credential at your door:

  • Offer sign-in. Start the host with --auth-server <url> naming your authorization server. An unauthenticated /mcp request then answers 401 with the same RFC 9728 discovery document the hosted door uses, and the tokens that server mints are the bk1 tokens this host already verifies against your issuers file — nothing else changes. The served plugin's .mcp.json OMITS the Authorization header entirely in this mode, so the harness's own sign-in flow runs instead of sending an unset environment variable literally.

  • Issue keys yourself. POST /v1/keys with the service key mints a static key for a person, business or service principal. Hand it out, and each holder sets BRAINY_API_KEY. The served plugin's .mcp.json carries the ordinary Authorization: Bearer ${BRAINY_API_KEY} header in this mode.

3. Package holders — offline install

Already have @soulcraft/brainy installed? No door needs to be running yet:

npx brainy plugin install --door <https://your-door>

This copies the package's own bundled plugin to ~/.config/soulcraft/brainy-plugin/<version>/ (a stable path, never node_modules), bakes --door into its .mcp.json, and runs the harness's own claude plugin marketplace add / claude plugin install commands against it. It never writes or asks for a key — it prints the same guidance as above: set BRAINY_API_KEY, or sign in if the door offers it.

Local zero-config — when the store and the terminal are on the SAME machine and you own both ends: npx brainy plugin install --local <brains-dir> --door <url> --keys <file> (the same --keys file your brainy host was started with) mints a per-install key, writes it to ~/.config/soulcraft/brainy-plugin/key (mode 0600), and sets BRAINY_API_KEY in your harness settings (~/.claude/settings.json's env block) — nothing to type, nothing to copy. This is the one case this CLI ever writes a credential; every remote door still gets sign-in or an admin-issued key.

npx brainy plugin url [--door <url>] prints a door's served marketplace URL, for scripting.

Embedded in an application

There is no terminal door here unless the embedding application ALSO runs brainy host — the plugin is a terminal-facing artifact; an application that links @soulcraft/brainy directly uses the in-process API (docs/overview.md) and never needs it.

Environment variables

Variable

What it does

BRAINY_URL

The door the plugin's .mcp.json points at, and the host every hook's session doors POST to. Defaults to https://brainy.soulcraft.com when unset.

BRAINY_API_KEY

The bearer credential, read by .mcp.json's Authorization header and by every session-door hook — a FLAT variable, never a nested default. Unused when the door offers browser sign-in.

BRAINY_PROJECT

The project field a session door's own request carries. Unset means null — never guessed from the terminal's working directory.

What the plugin contains

  • The pointer. .mcp.json names one door, over Streamable HTTP — nothing else runs locally.

  • The hooks, ported to plain Node (no bun required — this package already requires Node ≥ 22) and audited one by one for what a plugin every brainy user installs may do. Five of the six are thin door calls — read the harness's own hook stdin, redact it at the client's fixed denylist, POST it, answer per the door's shape — and every one fails the same way when the door cannot answer: one named line on stderr, never a blocked session.

    • policy-check (PreToolUse) — calls policy_check at the door before a tool runs (see docs/policy-door.md). Fails OPEN, loudly, when the door is unreachable — a default-shipped hook must never turn a network blip into a blocked session; only a real refuse/ask verdict blocks.

    • session-start (SessionStartPOST /v1/session/start) — hands the door your session's identity and gets back markdown context for the session that is beginning, printed verbatim.

    • session-capture (PostToolUsePOST /v1/session/capture) — hands the door the tool call you just made; when the answer carries a doorbell pointer (a board write that named parties who should hear about it now), relays the ring instruction as context.

    • session-save (Stop and PreCompactPOST /v1/session/save) — saves the session so far, on a stop or ahead of a compaction.

    • session-wrap (SessionEndPOST /v1/session/wrap) — wraps the session as it ends.

    Every door answers { served: true, … } or { served: false, reason } — a self-hosted door that has not composed sessions yet says so by name, never a silent no-op and never a stub. Every request is bounded at 262,144 bytes (256 KiB) — over it, the door refuses SessionPayloadTooLargeError rather than reading an unbounded body, since session-capture now fires on every tool call.

  • The skillsmemory-keeper (the recall → cite → remember loop) plus ten method skills (brief, counsel, design-craft, diagnose, home, oracle, panel, table-voice, verify, wrap) carrying a working method onto every machine that installs this plugin.

One client-side redaction pass, nothing semantic. Before any hook payload leaves the terminal, a FIXED denylist strips five machine-shaped secrets — this plugin's own key files under ~/.config/soulcraft/, an Authorization: Bearer … value, a SOULCRAFT_SERVICE_SECRET=… assignment, any *_API_KEY=… assignment, and a PEM-armored block — replacing each with a [REDACTED:<kind>] marker; nothing else is touched, because a pattern that guesses at "sensitive-looking" prose is a pattern that mangles ordinary conversation.

No secrets. The shipped archive's own build pins two things: its entry list matches an explicit allow-list (no file outside it ever ships), and no entry's text matches a bearer value, an sk--prefixed key, a minted brainy credential, or a JWT — the only Bearer the archive ever carries is the literal ${BRAINY_API_KEY} placeholder.

What an expired plan changes

A minted key can carry an optional entitlement claim — "suite" or "expired" — the bearer's plan, as its issuer wrote it. Most keys carry no claim at all, and nothing here applies to them: absence is today's law, unchanged, never read as expired.

When a key's plan reads "expired", every door that writes refuses by name (EntitlementExpiredError) — the five hooks' own session doors included, since a session door writes no entity but wants the same write posture as one that does. Reading never stops. find, get, counts and every other read door keep answering, export keeps answering, and session-start's own context read keeps composing — an expired plan narrows what a bearer may CHANGE, never what it may SEE. Renewing the plan is the only cure; nothing about the key itself changed, so no new key is needed once it is renewed.

The claim is per BRAIN, not per credential: a key minted for someone other than the brain's own owner (a grant) carries the OWNER's own plan, set by the issuer at mint time, never the guest's — a guest of a suite owner writes freely, and a guest of an expired owner is refused the same way the owner themselves would be.