seamux
CLAUDE.md9 starsChanged 23 days agoMITView on GitHub
A board over every Claude Code session on this Mac, and the tools to drive and dispatch them. README.md covers how it works and links to docs/getting-started.md (setup and every setting) and docs/remote-connections.md (mDNS and the tunnel); keep them current when behaviour changes. CONTRIBUTING.md says what makes a proposal fit seamux; read it before drafting an issue or weighing one. Taskless checks the code against the rules below that a single file can show, and its rules are in .taskless/rules/.…
- Reads credentials
- Commits and pushes
In this repository3 other files it gives its agents.
- pushSkill
- tasklessSkill
- seamux-dispatchSkill
The file
We split this CLAUDE.md into the rules it gives an agent
2 rules. About 0 are common, 1 is distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 31
If the change alters what someone running seamux gets, add a changeset in the same commit (see Release notes below).
- Distinctive: line 32
When the work is done, ask the user whether to land it on main or open a pull request, and wait for the answer.
≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
seamux
A board over every Claude Code session on this Mac, and the tools to drive and dispatch them. README.md covers how it works and links to docs/getting-started.md (setup and every setting) and docs/remote-connections.md (mDNS and the tunnel); keep them current when behaviour changes. CONTRIBUTING.md says what makes a proposal fit seamux; read it before drafting an issue or weighing one. Taskless checks the code against the rules below that a single file can show, and its rules are in .taskless/rules/. A pre-commit hook checks the staged files, and CI and npm run check check everything.
Rules
- seamux never destroys. The tool has no
claude rm, nogit worktree remove, nothing that deletes a session, worktree or transcript. When something should be removed, the session that owns it does it: seamux sends it a prompt asking. - Localhost only, except mDNS and the tunnel. The board always answers this Mac at localhost, and the Remote tab can open mDNS, the tunnel, or both, each on its own switch.
remoteGateinapp/lib/remote.server.ts, ahead of everything the server answers, decides every request by its socket and Host: always, a request from loopback addressed tolocalhost,127.0.0.1or[::1]; with mDNS on, one from anywhere addressed to this Mac's.localname, which needs the HTTP Basic credentials set; with the tunnel on, one from loopback, and never the network, addressed to the tunnel's hostname, which must carry a Cloudflare Access token thatremote.server.tsverifies, in both the auth middleware and that gate. Everything but the tunnel must carry the HTTP Basic credentials whenever they're set. Every action also goes throughassertFromBoardinapp/lib/guard.server.ts, but one:/theme/set(app/routes/theme-set.ts), which the Themes tab turns on for scripts, which send no Origin, refuses any Origin but the board's own instead, can only pick a saved theme, and never answers the tunnel. React Router's own Origin check doesn't cover a plain POST to a resource route (knowledge/vite-and-react-router.md), soassertFromBoardis all that stands in front of every other action. The dev server runs the gate as Vite middleware (vite.config.ts); the compiled server (server/serve.ts) runs it first too. Keep the two servers' checks the same, and never let a request from the network or the tunnel through on anything weaker. - Derive, don't store. Session state comes from
claude agents --json, cmux, and the transcripts on every poll. The store (app/lib/store.server.ts) holds only what nothing else records: subagent lifecycle, dispatch intent, pins, queued messages, project colours, which terminal is the board's, and settings. - No migrations. What the user chose (settings, themes, macros, pins, project colours) goes in the
configtable, one JSON value per key, which is never dropped: something new is a new key, and the code reading a key accepts every shape it held before. Every other table must be safe to lose, and a change to one bumpsSCHEMA_VERSION, which drops and remakes them all on the next open. - Always pass a surface to cmux. cmux RPCs default to the caller's own surface, which is whatever terminal seamux runs in. The fake cmux in the tests refuses a surface call without one.
- Blur what a chat says. Any element that shows a session's content or where it runs (names, paths, branches, how long ago, prompts, replies, questions, files) carries the
sensitiveclass, which the Debug tab's screenshot blur covers. - Ship a changeset with every change someone running seamux would notice. See Release notes below:
patchorminor, nevermajorwhile seamux is0.y.z. - Verify tool behaviour by running it. Several documented cmux and Claude Code behaviours turned out wrong.
knowledge/records what each tool actually does, one file per domain; readknowledge/index.mdbefore adding to it, and add every new finding there, in the same commit as the code that depends on it.
Layout
app/lib/board.server.ts: derives the board.codex.server.ts: reads Codex sessions, which have noclaude agentsof their own.drive.server.ts: every write verb, through cmux.harness.server.ts: what each harness (Claude Code, Codex) needs to know, andSessionand its baseTerminal, the primitives every keystroke goes through:clearInput,write,submit,press,keys,paste,type,screen.macros.server.ts: the built-in macros made of them (send, resume, exit, rename, approve, answer a question, accept trust). A key sequence goes in a macro or a primitive, never straight into a verb, and a new harness supplies aHarnessentry rather than branching onengine: what the browser needs as well goes onENGINE_FEATURESinconfig.ts, and what only one module reads in aRecord<Engine, …>beside the code that reads it, such as the transcript readers inboard.server.tsand the sign-ins inservice.server.ts. Theavoid-engine-branchingrule flags an engine compared to a name anywhere else.cmux.server.ts: the connection to cmux, its control socket spoken directly, andcmux sessions list.queue.server.ts: sends queued messages once their chat is idle.reconnect.server.ts: resumes chats stopped on a failed request once the API answers again.terminal.server.ts: the board's one plain shell, typed at and read throughTerminal, the part ofSessionwith no harness.protocol.server.ts: fan-out manifests and markers.config.ts/config.server.ts: settings and the system macros' defaults.remote.server.ts: the Remote tab's switches, the mDNS name and network check, the tunnel's settings, and the Access token check.app/lib/paths.server.ts: where the package is, andSEAMUX_HOME, where.env,.seamux.jsonanddata/live: the checkout, or~/.seamuxfor an installed package. Read state from there, never fromprocess.cwd()or a path relative to a module.hooks/subagent-event.ts, everything underscripts/andserver/serve.tsrun under plain Node with type stripping in a checkout, and so does everything they import: relative imports with.tsextensions,import type, no enums, no constructor parameter properties (constructor(readonly x: T)), which Node rejects and neither tsc nor the tests catch.npm run buildbundles them intodist/(scripts/build-dist.ts) for an installed package, where Node won't strip types.bin/seamuxruns the TypeScript in a checkout anddist/otherwise.server/serve.ts: the compiled board, which the supervisor runs in place of the dev server for an installed package, or withSEAMUX_COMPILED=1(npm start).knowledge/: what seamux has measured of Claude Code, Codex, cmux and the rest, one file per domain, cross-linked, with the code that depends on each finding.knowledge/index.mdlists the domains and says how to keep them;tests/knowledge.test.tschecks its links and that every entry names the versions it was measured on.skills/seamux-dispatch/SKILL.mdis a template;scripts/setup.tsrenders it into~/.claude/skills, with the subagent hooks, whenever the board starts (and onseamux setup).
Working on seamux
The board runs from the main checkout, on main, under npm run seamux, which keeps the dev server up and restarts it when needed. Never edit the main checkout directly: it is what the board serves.
- Work in your own worktree, on your own branch, under
.agents/.worktrees/, the place every agent harness shares. A session dispatched from the board with "new worktree" already has one there. Claude Code's own worktrees (claude -w, isolated subagents) go there too, through theWorktreeCreateandWorktreeRemovehooks in.claude/settings.json, which run the scripts of taskless/cli'sworktrees-pnpmskill; dotagents installs it, gitignored, withnpx @sentry/dotagents --project install, so run that in a checkout whose hooks can't find it. Otherwise make one yourself withgit worktree add .agents/.worktrees/<name>.worktrees/and.claude/worktrees/are earlier locations, still ignored for the worktrees living there; never start a new one in them. Runnpm installin your worktree, and never symlinknode_modulesinto it. - Before any work, unless told to start from somewhere else, bring your branch up to the latest main, so it starts from as recent a point as possible.
git fetch origin main, thengit rebaseonto whichever ofmainandorigin/mainis ahead; land takes the branch from there. If each has commits the other lacks, merge them first as the push skill's step 3 does (.claude/skills/push/SKILL.md). A worktree Claude Code made itself branches fromorigin/HEAD, which is behind local main whenever main hasn't been pushed, so this matters there most. - If the change alters what someone running seamux gets, add a changeset in the same commit (see Release notes below). Commit.
- When the work is done, ask the user whether to land it on main or open a pull request, and wait for the answer. Do neither unasked.
- To land: run
npm run landfrom the worktree. It waits for any other landing to finish, brings in GitHub's new commits when main is simply behind them, rebases your branch onto main, typechecks it, fast-forwards main, restarts the board if dependencies or any.server.tsmodule changed (otherwise hot reload picks the change up within seconds), and pushes main to GitHub, so the two never drift apart. If it reports a conflict, rebase yourself, resolve, and run it again; main is untouched until a landing succeeds. If it lands but doesn't push, as when GitHub moved during the landing, push with the push skill (/push). - To open a pull request: push your branch and open the PR against
mainwithgh pr create, and give the user its link. Keep the worktree and branch for fixes from review. - Once landed and pushed, as your very last step, remove your own worktree and branch (
git -C <main checkout> worktree remove <your worktree>, thengit branch -d), and never anyone else's. The user closes the chat from the board.
Release notes
Releases come from changesets. Each change that someone running seamux would notice gets one .changeset/<short-name>.md, which becomes its line in CHANGELOG.md. Write the file by hand, since npx changeset is interactive:
---
"seamux": patch
---
What changed, for someone running seamux: what they'll see, and anything they must do.- One change, one changeset. A later commit on the same change extends the existing file instead of adding a second.
- Pick the bump by what someone running seamux has to do. seamux is pre-1.0, so only two are used, and never
major:minor: a change they may have to migrate to or adopt, such as a moved or renamed setting, a new requirement, a changed command, or a change to where seamux keeps its state. Say in the note what they must do.patch: a bug fix, or a small addition that asks nothing of them.
- No changeset for what nobody running seamux sees: docs, CI, refactors, tooling.
On GitHub, release-version.yml keeps a "Version Packages" pull request open that folds pending changesets into CHANGELOG.md and bumps the version, and merging it releases: release.yml publishes the new version to npm through trusted publishing, once someone approves the npm-production environment, then tags v<version> and creates its GitHub Release from CHANGELOG.md.
Checking changes
npm run typecheckandnpm test.tests/runs againsttests/fake-cmux.ts, a stand-in for cmux's control socket andcmux sessions list, and never reaches the real cmux:tests/setup.tsreplaces the socket path and drops the capability token. CI runs it.npm run test:cmux, from a cmux terminal, checks the same contract against the real cmux:tests-cmux/is the vendor contract, and never runs in CI. Run it after cmux updates, and whenever you change a cmux call. When it fails, cmux changed: update seamux, the fake to match what cmux now does (its refusals use cmux's own codes and words), andknowledge/cmux.md. A new cmux call gets all three: a contract test intests-cmux/, the method in the fake, and a test intests/of what seamux sends.- Try new cmux or Claude Code calls against a throwaway session in its own cmux workspace, never against the user's live sessions, and close the workspace afterwards.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

