Contribute to Subshell
Set up the Subshell dev environment: the Bun monorepo, one watch command, and the verification checks every change must pass.
Set up the Subshell development environment, run the stack, and learn the loop every change goes through.
The repository's operational document, AGENTS.md, is the source of truth for everything here and for hundreds of details this page deliberately does not repeat, including release pipelines and the CI arrangement. Where this summary and AGENTS.md disagree, AGENTS.md is right.
Before you start
- Bun installed. Bun is the package manager and the runtime throughout; the project does not use npm or pnpm.
- A GitHub account, for the pull request and the one-time CLA signature.
Start the stack
-
Clone the repository:
git clone https://github.com/subshell-ai/subshell && cd subshell -
Install dependencies and start the watch loop:
bun install bun run startbun run startisturbo watch dev. It builds the workspace packages incrementally (build:devvia a hash runner), then runs both apps and restarts them when a package changes. The backend (API plus WebSocket) listens on :3080, and the SPA runs on Vite at :5174, proxying/apiand/ws. -
Open
http://localhost:5174in a browser. The first visit runs the setup assistant.
The tree and the vocabulary
The three product words are the directory taxonomy: server (the control plane), node (a machine running agents), and client (a person's interface to a control plane). Reusing one word for two things is the style violation that bounces a PR:
apps/server/api the control plane: subshell-server (Elysia + SQLite)
apps/server/web the SPA the server serves
apps/server/desktop Subshell Server (Tauri)
apps/node/agent the node daemon: subshell
apps/client/desktop Subshell Client (Tauri): also where a node is managed
apps/client/mobile the React Native companion
apps/docs these docs (the taxonomy's exception: it names no word)
packages/ plugin-api, plugins/*, pane-runtime, mcp-core,
subshell-protocol, backend-client (the type-safe SDK),
backend-errors, tsconfigEach app under apps/*/*/ carries its own AGENTS.md. It loads for agents working there, and for that app it wins over any general rule.
The verification loop
bun run verify-types
bun run lint:check
bun run testRun all three after any modification, not only when you feel confident.
bun run lintfixes;bun run lint:checkverifies.lintruns biome with--writeand rarely fails.lint:checkis read-only, and it is the one that matters. The usual loop is fix, then check.bun run testis TypeScript only. Anything undercrates/or a desktop app'ssrc-tauri/also requiresbun run rust:check: fmt, clippy, and tests across all three Rust crates.cargo checkalone misses formatting, and the app crates cannot build without a staged sidecar; that script handles the staging.bun run test:clicompiles both binaries and drives them as an operator does. It is not part ofbun run testbecause it builds about 100 MB (node) plus 140 MB (server) of binaries. If you touchinit,configure,status,service, either CLI'supdate, the nodesetup/enrollverbs, or the renderedinstall.sh, this suite is the only thing that answers whether the compiled versions work.- Changes under
packages/needbunx turbo build.verify-typesand the other checks run against source and stay green while the workspace build is broken. One trap has caused failures twice: adding anode:*import to a package barrel that the React Native app resolves through Metro, which cannot see node builtins. The mobile build then dies in CI while every local check passes. - Never test against the live instance, such as a dev :3080 you may have running. Scripted tests get their own backend on :3199 with a temp database, and the shared
bun testsuites stub service seams.
Git hooks are wired by bun install (lefthook): pre-commit formats staged files, and pre-push runs verify-types, lint:check, lint:licenses and lint:design with no tests, so pushes stay fast. CI owns the test suite, so we recommend running bun run test yourself before pushing work you want green on the first try.
Standing project rules
- Pinned versions. Every dependency in every
package.jsonis exact, with no^or~. Afterbun add, run syncpack to strip prefixes, then reinstall. One exception is codified: bun never resyncs theversionfield it records per workspace inbun.lock, sobun run lint:lockfile:fixrewrites exactly that field and nothing else. - No dynamic imports.
await import()breaksbun build --compile, so the repo uses static imports only. There is exactly one sanctioned exception, named in the rules:packages/pane-runtime/src/plugin-runtime.ts, where the bundler's blindness is the point, because a plugin installed after the binary was built must not be bundled into it. A second exception requires a design conversation. - File size and organization. Around 300–400 lines is the signal to split. A resource with several endpoints becomes a per-route directory (the API's
api/subshells/,api/channels/, andapi/nodes/are the shape). Tests live in__tests__/beside the code. New schemas use named constants, and properties that are not self-explanatory carry JSDoc, per the style rules in the repo. - Changesets carry the version bumps. After user-visible changes, run
bunx changesetand name the app the user sees the change through: one of the four released components (@internal/server,@internal/node,@internal/desktop-server,@internal/desktop-client), or@internal/docsfor site work. Never write one for an ignored package like@internal/server-web: the SPA ships embedded in the server binary, and a changeset naming an ignored package wedges the release version PR. - The CLA is signed once. Your first pull request signs the contributor license agreement. A bot asks, you keep copyright, and the grant is what keeps the dual licensing possible (AGPL control plane, Apache everything else).
For anything this page condenses, such as release cuts, the update pipeline, the desktop apps' ACL model, or the runner arrangement, AGENTS.md has the full text, usually with the incident that made it a rule. The security posture lives in docs/security.md, summarized on the site's Security model.
Notes
- The two Tauri desktop apps are deliberately not in
bun run start: adevtask there would open a window on every developer's machine. They have their own root commands (dev:desktop-server,dev:desktop-client), which stage the sidecar binary each app wraps. - The docs site follows the same rule: it starts only with
bun run dev:docs, on :3400.
Other pages
- Write the docs: the docs-only loop (this site)
- Architecture: what you are about to change
- subshell-server CLI and subshell CLI: the surfaces
test:clidrives
Last updated on
