styx-app
CLAUDE.md31 starsChanged 2 months agoApache-2.0View on GitHub
Desktop app (macOS, Windows, and Linux in beta; Electron + React) that runs coding agents (Claude Code, Codex, Gemini CLI, Cursor, OpenCode, Goose, Amp, shell) on many projects at once. Each task is a lane: one agent session on its own git worktree and branch, with its chat, changes, preview, checks and access. Agents reach deploy/server targets (Vercel, Supabase, AWS, GCP, Fly.io, Railway, Render, GitHub, SSH) only through scoped, expiring grants, and every request, grant, use and revoke goes into…
- Reads credentials
- Deletes or force-pushes
- Installs packages
In this repository10 other files it gives its agents.
- db-migrationSkill
- ipc-commandSkill
- port-screenSkill
- provider-adapterSkill
- releaseSkill
- run-appSkill
- spec-lookupSkill
- state-machine-changeSkill
- ui-componentSkill
- visual-diffSkill
The file
We split this CLAUDE.md into the rules it gives an agent
8 rules. About 0 are common, 2 are distinctive, and 6 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈2
Distinctive · estimate
This author's own choices. Worth a look.
6
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- could be automatic: a setting could enforce it
- Very specific: line 93
Every mutation is a named command: zod schema in packages/core/src/ipc/contract.ts, handler in …
- Distinctive: line 149
No Tailwind, CSS-in-JS, icon fonts or a second state library.
- Very specific: line 169
Before calling a task done: pnpm typecheck && pnpm lint && pnpm -F <package> test.
- Very specific: line 205
Don't hand-edit design/handoff/, .planning/STATE.md or apps/desktop/e2e/visual/__baseline__/ (regenerate baselines through the visual …
- Very specific: line 207
Don't write .env*, *.pem, *.key, *.p12, or commit fixtures with real tokens.
- Very specific: line 208
Don't push to main, force-push, git reset --hard, git clean or rm -rf.
- Very specific: line 209
Don't change UX silently: a new screen or flow needs a mockup in design/next/, an ADR for a real decision, and a discrepancy row.
- Distinctive: line 211
Don't skip the story, the test, or the visual check for UI work.
≈ “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
Styx
Desktop app (macOS, Windows, and Linux in beta; Electron + React) that runs coding agents (Claude Code, Codex, Gemini CLI, Cursor, OpenCode, Goose, Amp, shell) on many projects at once. Each task is a lane: one agent session on its own git worktree and branch, with its chat, changes, preview, checks and access. Agents reach deploy/server targets (Vercel, Supabase, AWS, GCP, Fly.io, Railway, Render, GitHub, SSH) only through scoped, expiring grants, and every request, grant, use and revoke goes into an append-only audit log.
Open source under Apache-2.0. The hosted accounts/usage service and the marketing material live in a separate private
repo and are not part of this tree. The app works fully without them: sign-in is optional and gates nothing, builds
from source never send usage counts (sendUsage is on only for packaged, non-fixture builds or when
STYX_API is set; see apps/desktop/src/main/index.ts), and STYX_API / DEFAULT_API in apps/desktop/src/main/services/account-service.ts
point a fork at its own service.
This file is for coding agents and people alike. CONTRIBUTING.md covers the PR flow, SECURITY.md how to report vulnerabilities.
Where truth lives
- The code, then
docs/adr/(decisions with rationale; read ADR-0027 and ADR-0028 before UI work). Numbers run to 0030; the next is 0031 (ADR-0024 is filed as0020-agent-clis-…md). docs/handoff-discrepancies.md: one row per deliberate departure from the original handoff or from established behaviour (# | Where | Prototype | Spec | Resolution). Add a row (next number after the last) when you change what a screen shows, a flow, a default or a rule the handoff states; name the files touched and the reason. Don't renumber or rewrite old rows: supersede them with a new one.design/handoff/: the original design system, no longer the feature spec. Still binding: token values (styx-tokens.css, mirrored intopackages/tokens), square corners, no shadows or gradients, 1px--txborders on floating surfaces, thedata-inv/data-onselection vocabulary, motion timings, Archivo + JetBrains Mono. Historical: the prototype's screen layouts, 10px uppercase labels and pixel sizes, which ADR-0027 replaced. The spec (Styx Spec.dc.html) is background for behaviour; where code, ADRs or the discrepancy log disagree, they win.Wireframes.dc.html,support.js,doc-page.jsare viewer history: never port./spec-lookupreads it.design/next/: approved HTML mockups for the lane-centred UI (ADR-0027/0028). New screens or interaction models start as a mockup here, reviewed before building.- Also:
docs/state-machines.md,docs/research/agent-parity.md(runner protocols),docs/plan.md(original build plan, partly historical),.planning/(local roadmap state, gitignored here;STATE.mdis tool-owned).
Monorepo map (pnpm workspaces)
apps/desktop: Electron app. Seeapps/desktop/CLAUDE.md.src/main: source of truth.container.tswires everything;services/(sessions, git, lanes, land/publish, checkpoints, grants, audit, preview/run, design, devices, updates, …);agents/(one file per agent CLI);providers/(one adapter per target);ipc/commands/(handlers by domain);broker/host.ts;db/(drizzle schema, SQL migrations, seed);store/(read-model projection + delta publisher).src/preload/index.ts: thewindow.styxbridge (shape:StyxApiinpackages/core/src/ipc/api.ts).src/renderer: React 19.app/shell + rails,screens/<Screen>/,features/<area>/,state/(mirrored store + ui-store),overlays/,keys/.e2e/: Playwright over Electron (*.spec.ts),visual/,sim/(long user simulations),readme/(README media).
apps/website: Next.js 16 static site (port 3100). Same tokens; plan inapps/website/DESIGN.md.packages/core: domain model, zod schemas, session/grant machines, policy engine, selectors, IPC contract,copy.ts. Pure: no Electron, DOM or I/O.packages/ui: component library (CSS Modules + tokens) with Storybook.packages/tokens:tokens.json→ generated CSS/TS, bundled fonts.packages/broker: local broker protocol +styx mcp(the tools agents call).packages/cli: thestyxCLI and provider shims (vercel,supabase,aws,gcloud,fly/flyctl,railway,render,gh,ssh).
Commands
Node ≥ 22, pnpm 10 (corepack enable). Run one package with pnpm -F <name> <script>.
- Install:
pnpm install(postinstall rebuilds better-sqlite3/node-pty for Electron), thennode node_modules/electron/install.jsonce, thenpnpm tokens:build. - Dev:
pnpm dev. Switches (main/preload read them):STYX_FIXTURE=demo|empty|error(seeds a temp userData; real data untouched),STYX_KEYCHAIN=memory,STYX_MFA=auto|deny,STYX_NOW=<ms>,STYX_SCREEN=<state>,STYX_THEME=dark|light|system,STYX_CHROME=mac|win,STYX_USER_DATA=<dir>,STYX_FIXTURE_RESET=1,STYX_DEMO_REPOS=0,STYX_API=<url>,STYX_UPDATE_URL=<feed>. - Build:
pnpm build(e2e, visual and readme recordings launch the builtapps/desktop/out). - Typecheck:
pnpm typecheck. Lint:pnpm lint. Format:pnpm format. - Unit tests:
pnpm test(all vitest projects) orpnpm -F @styx/core test(@styx/desktop,@styx/ui,@styx/broker,@styx/cli,@styx/tokens,@styx/website). Live checks (real catalogue, IDE detection):STYX_LIVE=1. - e2e:
pnpm e2e(pnpm e2e -- --grep a11yto filter). Packaged smoke:pnpm -F @styx/desktop package:smoke. - Visual:
pnpm visualcompares againste2e/visual/__baseline__/app/(else the prototype baselines).STYX_VISUAL_UPDATE=1 pnpm visualre-takes the app references after a reviewed change; per-state ratios:pnpm -F @styx/desktop visual:report.pnpm visual:baselinere-bakes the prototype history only. - Storybook:
pnpm storybook;pnpm storybook:testbuilds it and runs every story through axe. - Package:
pnpm package:mac/pnpm package:win/pnpm package:linux(AppImage + .deb; build on Linux). Unsigned dir builds:pnpm -F @styx/desktop package:mac:dir(also:win:dir,:linux:dir). - Public repo: STYX main reaches NicholasFlemmer/styx-app as a pull request from its
syncbranch (mirror.ymlhere,sync-pr.ymlthere) that merges itself once CI passes; contributors' merged pull requests flow back into STYX. - Release: CI only. Bump
apps/desktop/package.json, then runrelease.ymlon styx-app (it waits for green CI, builds signed Mac + Windows + Linux into a draft release); publishing the draft runspublish.yml(update feed, Homebrew cask), andsite.ymlthen redeploys heystyx.com. See.claude/skills/release/SKILL.md. - README media:
pnpm -F @styx/desktop readme:gif(afterpnpm build; needs ffmpeg). - DB:
pnpm db:generate(drizzle-kit, then hand-add CHECKs/triggers; see/db-migration),pnpm db:migrate,pnpm db:seed. Tokens:pnpm tokens:build. Native ABI trouble:pnpm rebuild:native.
Architecture rules
- Main is the source of truth (ADR-0006). The renderer holds a read-only mirrored Zustand store fed by
store.snapshot+ seq-numbered delta batches, plus a ui-store for view state. No optimistic domain updates. - The renderer never touches fs, git, child processes, network or secrets, and never imports
electronornode:*. - Every mutation is a named command: zod schema in
packages/core/src/ipc/contract.ts, handler inapps/desktop/src/main/ipc/commands/<domain>.ts, called aswindow.styx.command(name, input). Handlers return{ ok, value } | { ok: false, error }, never throw across IPC. New command:/ipc-command. - Session and Grant machines live in
packages/core/src/machines: exhaustive transition tables, effects as data, time injected (ctx.now; noDate.now()in core, main usesclock.ts). Change them via/state-machine-change. - Policy evaluation is pure (
packages/core/src/policy); main applies the result and audits it. - Business logic goes in core (selectors, machines, policy) or main services, not in React components or IPC handlers.
- IDs are ULIDs with branded types (
packages/core/src/ids.ts); timestamps are epoch ms. Never join by names. .styx/project.jsonis versioned (version: 1) and zod-validated (packages/core/src/project-file.ts); never holds secrets.- Agent runners (ADR-0010/0016/0017): Claude Code over
stream-runner.ts, Codex overcodex app-server(app-server-runner.ts), Gemini, Cursor, OpenCode and Goose over ACP (acp-runner.ts; Cursor falls back to--printstream-json), Amp and shell over a pty;runner-mux.tsroutes them. Codex's TUI cannot take messages through a pty, so never route Codex there. New agent:docs/contributing/adding-an-agent.md. - Lanes: one session = one worktree on
agent/<name>-<n>(branchPrefix). Lane services:lane-sync-service(keep current with base),lane-ledger-service,land-service,merge-resolve-service,publish-service,checkpoint-service(turns as hidden refs underrefs/styx/checkpoints/). Merge base in, never rebase (it would orphan checkpoints).
Security invariants
- Secrets live only in the OS keychain (
@napi-rs/keyring,services/credential-vault.ts) behindcredentialRef: never in SQLite, logs, IPC payloads, deltas, renderer state, fixtures, project.json or the repo. audit_entriesis append-only (triggers +prev_hash/hashchain); write only viaAuditService.append. Revoke inserts a new row. Every request/grant/use/revoke/deny is audited with actor, session, worktree and trigger.- Prod ∧ write/deploy/delete needs OS authentication (Touch ID / Windows Hello / polkit on Linux, ADR-0029) before
issuance.
requireMfais recomputed inGrantServicefrom DB rows, never taken from the renderer. - Agents get scoped, short-lived tokens or env for the grant's lifetime only; SSH gets a forwarded agent socket
(
SSH_AUTH_SOCK), never a key file. Broker connections are bound to one session. - Everything an agent supplies that gets persisted passes
redact()/redactArgv()first. Scope heuristics fail closed. Details:.claude/rules/security.md. Touching broker, cli, vault, providers, grants or MFA: run thesecurity-revieweragent. New target:docs/contributing/adding-a-target.mdand/provider-adapter.
Design rules
- Colours only from tokens:
--bg --s1 --s2 --ln --tx --mu --ac --acx --act --add --term --termtx --dim, plus ADR-0027's--agent-*(a small square beside an agent's name only),--paper-*(result cards) and--del.--actis accent-as-text; never--acas text on light. Sizes and spacing frompackages/tokens/tokens.jsonvars. border-radius: 0, nobox-shadow, no gradients, no hover transitions. Floating surfaces:1px solid var(--tx).- Selection:
data-inv="true"current,data-on="true"accent/armed. No.active/.selectedclasses. - Motion: sheet 160ms slide, modal 120ms fade, toast 160ms slide; reduced motion → fades (
@styx/tokensmotion.css). - Type (ADR-0027 §6): sentence-case labels (no uppercase tracked labels), 14px body, Archivo 600 headings (never wide Archivo), JetBrains Mono only for code, paths and branches. Lime marks only the move that is the person's.
- User-facing strings come from
packages/core/src/copy.ts; plain words, sentence case. - Full CSS rules:
.claude/rules/css-tokens.md(thecheck-design-csshook enforces the basics).
Coding conventions
- TS strict +
noUncheckedIndexedAccess+exactOptionalPropertyTypes(tsconfig.base.json). Noany, no!in core,satisfiesoveras, discriminated unions over enums, type-only imports. Named exports only (config, stories, Next route files excepted). - zod at every boundary: IPC, SQLite rows out, project.json, broker messages, provider and CLI responses.
- Files
kebab-case.ts; componentsPascalCase.tsx+.module.css+.stories.tsx(ui) +.test.tsx; tests sit next to source. Errors are typed results in core, thrown only at process edges. - Spawn agent CLIs through
services/spawn-cli.ts(Windows.cmdshims, process-tree kill), not rawspawn. - No Tailwind, CSS-in-JS, icon fonts or a second state library.
Testing
- core: table-driven, deterministic, fixtures from
packages/core/src/fixtures/demo.ts. 100% coverage is enforced onmachines/,policy/,selectors/,arcade/(packages/core/vitest.config.ts); one row per (state, event) pair including invalid ones. - desktop main: build services with
makeTestApp()(src/main/test-support.ts): in-memory vault, fake CLIs and MFA, no simulator tools unless a test passesdeviceExec/deviceWhich. Every command gets a contract test. Never hit real providers, the real keychain or the network. - Timeouts: vitest stretches limits on CI (60s) and Windows (30s); a test that sets its own limit wraps it in
slow(ms)fromapps/desktop/src/main/test-timeouts.ts(×4 on CI). Playwright allows 180s per test on CI. - ui: a story per variant × state (Storybook axe gate via
pnpm storybook:test), testing-library tests for keyboard, focus and aria. - e2e:
e2e/launch.tsstarts the built app withSTYX_E2E=1, a tempSTYX_USER_DATA,STYX_FIXTURE=demo,STYX_KEYCHAIN=memory, a fixedSTYX_NOW, and fakeclaude/codex/gemini/gh/adb/xcrunfrome2e/fixtures/binfirst on PATH. Local runs are hidden by default (STYX_E2E_HIDDEN=1: no window shows or takes focus, no Dock icon), so they don't interrupt your work;STYX_E2E_SHOW=1to watch one. CI runs visible windows. Screens set[data-screen-ready](inside the Suspense boundary) when painted. - CI (
.github/workflows/ci.yml) runs on macOS, Windows and Linux (e2e underxvfb-run): install, Electron unpack, tokens, typecheck, lint, test, build, Storybook axe, e2e, and a packaged-app smoke. Keep all three green. - Before calling a task done:
pnpm typecheck && pnpm lint && pnpm -F <package> test; UI changes alsopnpm e2e,pnpm visualand the story.
Gotchas
- Three platforms, two layouts (ADR-0029):
Platform(darwin | win32) is layout and keys, and Linux renders win32-style; words useCopyPlatform(platformCopy,useCopyPlatform()), where Linux has its own. In main,platform === 'darwin' ? mac : winsilently gives Linux the Windows branch: writewin32 ? … : posix, or handlelinuxexplicitly. - Electron starts as plain Node: the shell has
ELECTRON_RUN_AS_NODEset (VS Code terminals). The scripts strip it; launch Electron by hand withenv -u ELECTRON_RUN_AS_NODE. pnpm -s typecheckcan hide errors in some shells. Verify per package and check the exit code:pnpm -F @styx/desktop typecheck(runstsc -p tsconfig.node.jsonandtsconfig.web.json), likewise for others.- Fresh worktree: no
node_modules.pnpm install --frozen-lockfile, thennode node_modules/electron/install.jsin one process before vitest; otherwise test workers race the download and leave a half-extractednode_modules/electron/dist(move it aside and re-run the installer). - Demo fixture paths are literal (
~/code/acme-shop): a main test that starts a run or writes project settings must point the project at a temp dir, orapps/desktop/~/code/…appears in the repo. - Per-session UI state (drafts, attachments) belongs in the ui-store keyed by session id: screens unmount.
- Claude Code hooks (
.claude/hooks/): the secrets guard greps the command text and the staged diff, so write commit messages to a file (git commit -F) and expect merges carrying the redaction tests' fake tokens to be blocked;check-design-cssreads#110in a comment as a hex colour (write "row 110"). - Reading a failed run:
<userData>/styx.db(sessions,transcript_messages,pending_asks),<userData>/logs/pty/<sessionId>.log, and the main log (~/Library/Logs/Styx/main.logon a Mac). userData is~/Library/Application Support/Styxon a Mac; fixture runs log their temp userData on thefixtureline.
Tooling for agents
- Skills in
.claude/skills/:ipc-command,state-machine-change,db-migration,provider-adapter,ui-component,run-app,spec-lookup,visual-diff,release;port-screenis from the prototype-port era. - Reviewers in
.claude/agents/:security-reviewer,state-machine-auditor,a11y-reviewer,design-fidelity-reviewer,electron-native-debugger. - Path rules in
.claude/rules/load for matching files (CSS tokens, IPC, security, testing).
Don't
- Don't hand-edit
design/handoff/,.planning/STATE.mdorapps/desktop/e2e/visual/__baseline__/(regenerate baselines through the visual scripts). - Don't write
.env*,*.pem,*.key,*.p12, or commit fixtures with real tokens. - Don't push to
main, force-push,git reset --hard,git cleanorrm -rf. Work on a branch and open a PR. - Don't change UX silently: a new screen or flow needs a mockup in
design/next/, an ADR for a real decision, and a discrepancy row. - Don't skip the story, the test, or the visual check for UI work.
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.

