One workspace for every coding agent, on your Mac.
realm.computer · Download · Changelog
macOS · Apple silicon · in active development
Every space in the sidebar, a session and the two files its turn edited, and the file its answer named, open beside it at the line it named.
Local-first agent control plane for macOS — profiles → spaces, every space in one sidebar, split panes for sessions beside one side panel for terminals / browsers / devices / documents, a context pool, and an MCP gateway. See docs/superpowers/specs/2026-08-17-realm-v1-design.md.
- Node ≥ 22.13, pnpm 10, macOS.
- Hugeicons Pro token in
.npmrc(see.npmrc.example). pnpm install && pnpm dev- Tests:
pnpm test· Types:pnpm typecheck - Data lives in
~/Realm/(override withREALM_HOME).
site/ is the marketing site and docs (Next.js, deployed on Vercel with Root Directory = site).
It is deliberately outside the pnpm workspace and carries its own lockfile — see site/README.md for
why that matters and how to verify the WebGPU hero shader headlessly.
- Claude sessions run on
@anthropic-ai/claude-agent-sdk, which drives theclaudeCLI: install it and log in first (claude auth login). An expired login shows up as an error in the transcript. - ACP agents — Cursor, Gemini, OpenCode, GitHub Copilot, goose, Qwen Code, Grok and fx all speak the
Agent Client Protocol, so they share one generic adapter (
packages/adapters/src/acp/) and adding another is anAcpAgentSpecentry inapps/server/src/app.ts. Install and sign in to each out of band; Settings → Engines probes what is on the machine and shows the exact install or login command for what is not. Realm never calls ACP'sauthenticateitself.- Note that ACP has deprecated
modes/modelsin favour ofconfigOptions, and agents have split: Cursor still answers with the old shape, OpenCode answers with only the new one, Copilot sends both.acpSessionConfig(in@realm/contracts) normalizes both and carries the id to write back through — reading one channel and writing on the other is a silent no-op.
- Note that ACP has deprecated
- Offline / UI work:
REALM_ENABLE_FAKE_AGENT=1 pnpm devregisters a scripted Fake agent (echoes what you send) in the model picker. - MCP gateway — third-party MCP servers are configured in a space's settings, not per-agent: every session gets one Realm gateway endpoint, and credentials or OAuth tokens never reach the agent CLI. Every proxied tool call shows up in the Activity view (Connections ▸ Activity, or "MCP Activity" in the command palette).
Graphify extracts a queryable knowledge graph from a checkout. It is a Python CLI, installed out of band like the agent CLIs — Realm probes for it and never installs it.
- Install it with the
mcpextra:uv tool install "graphifyy[mcp]". A plainuv tool install graphifyystill puts agraphify-mcpon PATH, but it dies onModuleNotFoundError: No module named 'mcp'the first time the hub dials it. graphify.probe/graphify.updateare Realm's own RPC methods, not agent kinds — graphify has no models, no login and no transcript, so it stays offAgentKind.graphify updaterunsgraphify update .in the space's primary checkout and needs no LLM and no API key.- As an MCP server, add it in a space's Connections: transport
stdio, commandgraphify-mcp, args the absolute path ofgraphify-out/graph.json. Its ten graph tools then reach every session in that space through the one Realm gateway endpoint. - The rendered
graphify-out/graph.htmlopens in the documents pane. graphify points its only script tag at unpkg with an SRI hash; the guide CSP isdefault-src 'none', so the preview server rewrites that tag to a vendoredvis-networkand drops the hash, which no longer describes the bytes being served (localizeVisNetworkinapps/server/src/documents/preview.ts). - Proof:
pnpm --filter @realm/server exec tsx scripts/live-graphify-check.tsdrives the real CLI, the real hub and the real preview server, and skips rather than passes when graphify is absent.
An agent driving a browser pane can sign you in to a site without ever seeing the password.
- You enroll your own passwords in Settings → Sign-ins. That is the only path for a value you supply. No tool, no RPC method, no file import and no chat message can hand a password to the store — which is what makes the origin check below worth anything, since an agent that could enroll one could enroll a password you use elsewhere against whatever page it is standing on.
- An agent can ask Realm to GENERATE one, with
browser_fill_credential'sgenerateargument instead of acredentialId: Realm mints a random password in the main process, saves it to the same store, and fills it under the same three gates. The agent never receives the value and cannot read it back, so this is the way to set a password on a sign-up form — strictly better for secret hygiene than an agent printing one into the transcript for you to copy. The origin is not the agent's to choose: it is read off the pane, shown on the card, and checked again against the live page before anything is typed. What it costs you is stated on that card — nothing can read the value back afterwards, so the site's own reset is the way in if you ever need the password outside Realm. Those rows are marked "Generated by Realm" in Settings, and the audit log records them asgenerated. - Values live in the OS Keychain, encrypted by Electron's
safeStorage(apps/desktop/src/main/secret-store.ts), decrypted only inside Realm's main process, and never readable back — not by you, not over IPC, not through the gateway. MCP OAuth tokens share the same store, sorealm.dbno longer holds them in the clear. (Per-server MCPenv/headersstill do;MCP_SECRET_STORAGE_NOTEsays so.) - Every fill is gated three ways: the pane's current origin, read from CDP, must exactly equal the
origin the credential belongs to (no subdomains, no lookalikes); you approve that specific fill on
a card naming the origin, username and label; and Touch ID confirms you are there. A generated fill
runs the identical three, in the identical order, through the same executor. The card appears
in every permission mode including
bypassPermissions, is never batched, and answering "always" licenses nothing. Fills are logged to~/Realm/logs/credential-audit.log— timestamp, origin, credential id, outcome, never the value. - Typing into a password field with
browser_actis still refused, in every mode.browser_fill_credentialis a separate op, not a way around it. - Two-factor is not automated, and will not be. A Duo/Okta push cannot be driven from here, and a TOTP prompt is out of scope. Realm fills the username and password and stops; an SSO + 2FA sign-in stays partly manual, and you finish it in the pane. If TOTP support is ever added it would be a separately enrolled secret under the same rules — never a code the model sees.
- A Mac without a Touch ID sensor can save sign-ins but cannot fill them:
promptTouchIDis biometrics-only, with no password fallback. Settings says so on the tab.
pnpm dist— full build + DMG and zip inapps/desktop/release/(pnpm dist:dirstops at an unpackedRealm.appfor fast iteration;pnpm app:updateruns it and swaps the result into/Applications/Realm.app, quitting a running copy first). Under the hood: rootpnpm build, thenapps/desktop/scripts/stage-pack.mjsstages.pack-stage/(apnpm deployof realm-server with its productionnode_modules, the bundledskills/, the ScrollPhase helper, the icon), then electron-builder (apps/desktop/electron-builder.yml) packs it — server and skills as real files underContents/Resources/, never inside the asar, becausenode-pty's native prebuilds and a spawnable server entry can't load from an archive.pnpm app:update— the fast local update loop: builds the unpacked app, gracefully quits the installed/Applications/Realm.app, replaces it with rollback protection, and relaunches it. SetREALM_APP_PATH=/another/location/Realm.appto target a nonstandard install. This is for locally built unsigned copies; published builds continue to use the signed updater below.- No system Node needed: the packaged app runs realm-server under its own binary with
ELECTRON_RUN_AS_NODE. Launched from Finder (launchd's minimalPATH), main adopts the login shell'sPATHat startup (login-shell-path.ts) before anything spawns, so agent CLIs andmacresolve; if the login shell can't be asked (exotic shell, timeout), it falls back to the inheritedPATHplus/opt/homebrew/bin:/usr/local/bin. Terminals spawn login shells (-l). - Proof:
node apps/desktop/scripts/packaged-smoke.cjslaunches the packaged binary with a scratchREALM_HOMEand a strippedPATH=/usr/bin:/binand asserts boot,agents.probefindingclaude, bundled skills, and a terminal resolvingclaude. - Unsigned by default: with no signing credentials in the env,
pnpm distbuilds an unsigned, un-notarized app (scripts/pack.mjspasses-c.mac.identity=nulland says so). A copy downloaded to another Mac will be quarantined: first launch needs right-click → Open, and on Apple Silicon Gatekeeper may report the app "damaged" — clear it withxattr -cr /Applications/Realm.app. Locally built copies launch normally. Signing and notarization are fully wired and env-activated (CSC_LINK/CSC_KEY_PASSWORD+APPLE_ID/APPLE_APP_SPECIFIC_PASSWORD/APPLE_TEAM_ID): with those set, the samepnpm distsigns, notarizes and staples with zero code changes — exact steps indocs/dev/signing.md.
pnpm release— bumps the app version (patch;--minor/--majorfor more), prepends a changelog stub toCHANGELOG.mdfrom merged PR titles since the last tag (viaghwhen it answers, else git log's squash-merge subjects; offline it degrades to plain commit subjects), builds the dmg + zip +latest-mac.ymlthrough the normalpnpm dist, commits, and creates thevX.Y.Ztag locally. It never pushes and never publishes — it ends by printing the exact manual next steps (review the stub, push branch + tag,gh release createwith the artifacts).--dry-runshows the whole plan without touching anything.
- Signed packaged builds check the public GitHub release feed on launch, download a newer version in the background, and ask before restarting to install it. Settings → General → Updates also supports manual checks and installing a downloaded update.
- Public releases must carry the dmg, zip, and
latest-mac.ymlartifacts produced bypnpm release. The updater never embeds a GitHub token. - The hard gate in
apps/desktop/src/main/updater.tsstill disables updates in development and in unsigned builds: macOS cannot apply an unsigned Squirrel update. Configure signing and notarization as described indocs/dev/signing.md;pnpm app:updatehandles local unsigned builds.
Settings → Import brings what Claude Code, Codex and Cursor already have on disk into Realm: transcripts, the Claude memory tool's per-project fact folders, and user-level skills.
- The agents' directories are read-only.
~/.claude,~/.codex,~/.cursor,~/.agentsand~/.geminiare copied from — never written, moved or cleaned up. Everything the import produces lands in Realm's database or under~/Realm/. import.scanwrites nothing. It opens files, matches candidates to spaces and answers; no space, session or environment is created by looking. Onlyimport.applywrites, and only for the keys it is handed — so the preview you approve is the work that happens.- Space matching is most-specific-location-wins (
apps/server/src/import/match.ts): walking the cwd and its parent, asking in turn for an environment, a project root, a space folder, and a directory named after a space. The walk is bounded (MATCH_MAX_HOPS) because one broadly-registered ancestor would otherwise capture every session on the machine. Anything unmatched falls to a profile'sImportedspace, and every row shows the rule that placed it so a wrong guess is visible. - Imported sessions keep their provider id when the recorded cwd still exists, so sending a
message resumes the real CLI conversation. Where the directory is gone the link is left off and the
session imports as searchable history. Re-target rows in the preview:
sessions.moveToSpacerefuses once a session has events, and an imported session has a transcript from the moment it exists. - Memory is not flattened into the space doc. Fact files are copied to
~/Realm/memory/imported/<spaceId>/<project>/and the index goes into the space's memory document between<!-- realm:imported-memory -->markers (replaced on re-import, never duplicated). The largest folder here was 712k characters against a 100k doc cap; inlining would have dropped most of it and called that an import. - Skills are copied, never symlinked or overwritten, and land unscoped — visible in every space, the honest translation of "installed for my user".
One transcript is not one file. Codex rewrites a whole thread into a new rollout file every time it is resumed — on one machine here, 241 files were 71 conversations, with 158 of them replays of a single Stora thread. The scan keeps the fullest copy of each and counts the rest, so the panel offers conversations rather than files.
Two scripts, from apps/server:
tsx scripts/live-import-check.ts— prints what an import would do against this machine's real CLI directories, reading aVACUUM INTOcopy of the database so it can reason over your actual spaces without being able to write to them.tsx scripts/undo-import.ts --all-imported --sweep-environments— takes an import back out. Only rows whose dispatch origin isimportare touched, and the agents' own directories are never read or written. Worth knowing about before a big import: an imported session cannot be re-targeted afterwards, and the environment rows an import leaves behind will out-match everything on the next run if they are not swept with it.
Real captures of a real space, taken against the built app by site/scripts/capture-product.mjs.
These are the site's frames with Realm's page colour painted in behind them — the window is made of
translucent material, so a capture laid straight onto GitHub's white theme washes its sidebar out.
node scripts/readme-images.mjs repaints them after a re-capture.
Every space, in one sidebar. Each space is a section of one list, and Needs you heads it with every session waiting on a permission or a question, from every space and every profile.
Three sessions waiting on you, above every space's sessions.
Bring the agent you already use. Claude Code, Codex, Cursor, Gemini, OpenCode, GitHub Copilot, goose, Qwen Code and Grok all run here, each keeping its own login, models and permission modes.
Every installed agent's models in one list: Opus 5.5 picked, at Max, with fast mode on.
Your tools, without your credentials. Connections belong to the space rather than to an agent, so a session is handed the tools and never the token — it calls Realm, and Realm calls the server.
Connecting an app to a space. One click each, and every session in the space can use them.
Confine what an agent can touch. A space can put its agents and terminals behind a macOS Seatbelt policy. It ships off, per space — Seatbelt is not a container, and the network stays open.
The three postures a space can take, on the one it ships with.
skills/ holds skills Realm ships, one folder per skill, laid out exactly like the library at
~/Realm/skills/ (spec §7) so installing one is a copy. SkillSync — per-profile enablement and
the symlink into each session's .claude/skills/ — is not built yet, so until it is, enable a
bundled skill by hand:
ln -s "$PWD/skills/mac" ~/.claude/skills/macstudy-guide/lecture-notes— the school workflow (Plan 22): how to write a self-contained interactive HTML guide the Documents pane renders (quizzes, step-throughs, flashcards, KaTeX; progress in a sidecar), and how to answer questions during a lecture and wrap one up afterwards. Both lean on therealm-docsgateway tools (docs_search,docs_list,docs_open,docs_progress), and the palette's New lecture… / Wrap up a lecture… / Import recording from Plynn… entries drive the loop.mac— the mac-cli binary: Calendar, Reminders, Contacts, Mail, Messages, Notes, Music, TV, Shortcuts, Finder, and iWork from the shell. Realm spawns agents and terminals with its own environment inherited, somacis already on a session'sPATHwhenever it is on thePATHRealm was launched from — the skill exists to make it discoverable, not reachable. Note that a Realm launched from Finder rather than a terminal inherits launchd's minimalPATH, which has neithermacnorclaude/codex/nodeon it; that is a packaging problem for all of them, not a mac-cli one.




