Contributing to Subshell
Set up the dev loop: the Bun monorepo, one watch command, and the verification trio every change must pass.
This page is the first hour: a running stack, the shape of the tree, and the loop every change goes through. The repository's operational document, AGENTS.md, is the source of truth for everything here and 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.
The stack in one command
git clone https://github.com/subshell-ai/subshell && cd subshell
bun install
bun run startBun is the package manager and the runtime throughout, never npm or pnpm. bun run start is turbo watch dev: it builds the workspace packages incrementally (build:dev via a hash runner), then runs both apps and restarts them when a package changes: the backend (API + WebSocket) on :3080, the SPA on Vite (:5174, proxying /api and /ws). Open http://localhost:5174; the first visit runs the setup assistant. The two Tauri desktop apps are not in bun run start on purpose: a dev task 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: bun run dev:docs only, on :3400.
The tree is the vocabulary
The three product words are the directory taxonomy: server (the control plane), node (a machine running agents), 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 it wins over any general rule for that app.
The loop every change passes through
bun run verify-types
bun run lint:check
bun run testAll three, after any modification, not when you feel confident. Notes that bite:
lintfixes;lint:checkverifies.bun run lintruns biome with--writeand rarely fails;lint:checkis read-only and 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 owesbun run rust:check(fmt + clippy + tests across all three Rust crates;cargo checkalone misses formatting and cannot build the app crates without a staged sidecar, which that script handles).bun run test:clicompiles both binaries and drives them as an operator does. It is not inbun run test(it builds ~150 MB of binaries), but touchinginit,configure,status,service, either CLI'supdate, the nodesetup/enrollverbs, or the renderedinstall.shmeans this suite is the only thing that answers whether the compiled versions work.- Changes under
packages/needbunx turbo build.verify-typesand friends run against source and stay green while the workspace build is broken; the trap that has bitten twice is adding anode:*import to a package barrel that the React Native app resolves through Metro, which cannot see it; the mobile build dies in CI and nothing local objects. - Never test against the live instance (a dev :3080 you may have running). Scripted tests get their own backend on :3199 with a temp database; the shared
bun testsuites stub service seams.
Git hooks are wired by bun install (lefthook): pre-commit formats staged files, pre-push runs verify-types, lint:check, lint:licenses and lint:design (no tests), so pushes stay fast: CI owns the test suite, so run bun run test yourself before pushing anything you want green on the first try.
The rules that outlive any one PR
- Pinned versions. Every dependency in every
package.jsonis exact, 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 static imports only, with exactly one sanctioned exception, named in the rules:packages/pane-runtime/src/plugin-runtime.ts, where the bundler's blindness is the point (a plugin installed after the binary was built must not be bundled into it). No second exception without a design conversation. - Files earn their size. Around 300–400 lines is the split signal; a resource with several endpoints becomes a per-route directory (the API's
api/subshells/,api/channels/,api/nodes/are the shape), tests live in__tests__/beside the code, and new schemas, named constants, and JSDoc on non-obvious properties follow the style rules in the repo. - Changesets carry the version bumps. After user-visible changes,
bunx changesetnames the app the user sees the change through: 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 one-time. First pull request signs the contributor license agreement: a bot asks, you keep copyright, and the grant is what keeps the dual licensing (AGPL control plane, Apache everything else) possible.
For anything this page condenses (release cuts, the update pipeline, the desktop apps' ACL model, the runner arrangement), AGENTS.md has the real text, usually with the incident that made it a rule. The security posture lives in docs/security.md, summarized in the site's Security Model.
See also
- Contributing to These Docs: the docs-only loop (this site)
- Architecture: what you are about to change
- Server CLI and Node CLI: the surfaces
test:clidrives
Last updated on
