Crosscode lets several people and coding agents work on the same repository at the same time without overwriting each other.
Each person (or agent) works in their own normal Git checkout. A small background program — the daemon — watches that checkout and notices whenever an edit has settled. It sends a record of that edit to a shared coordination service, which passes it on to everyone else's checkout.
The important part: those incoming edits are never written to your files automatically. They show up as proposals. You look at the diff and decide to accept or reject it, exactly like reviewing a pull request, except it happens in seconds instead of after a push.
Everything stays ordinary Git. Crosscode doesn't replace your editor, your agent, your Git host, your branches, your staging area, or your commits — and if you turn it off, your repository is unchanged.
Who it's for. Teams sharing one codebase where the people are all running coding agents. That is the case Crosscode is built for, and the one it is worth setting up for. It keeps working when you are the only person online that day — the project is still shared, and your teammates' agents will land work on it again — but a repository only you will ever touch has nothing to coordinate with, and plain Git is the better tool for it. Human-only teams can use it too, though the review-before-it-lands workflow pays off most once agents are in the mix.
There is no web app. Everything you do day to day — signing in, claiming work, reviewing proposals, accepting, publishing — is a crosscode command or an MCP tool call your agent makes against your local daemon. The website is only a landing page, sign-up/sign-in, and these docs.
Crosscode is not a hosted service yet. Someone on your team has to run the coordination service (a small Node process backed by a Supabase project) — see Set up Supabase and run the coordination service. Once that exists, everyone else just points their checkout at it.
This assumes the coordination service is already running and you know its URL (http://127.0.0.1:8788 if you started it locally).
-
Set up the checkout.
initwrites Crosscode's local state for this worktree, and has to come first.crosscode init --json
-
Get an account. If you don't have one, sign up straight from the terminal:
crosscode signup --email <email> --password <password> --service <service-url> --json
This creates the account, logs you in, and gives you a personal workspace, so there is nothing else to join. You can also sign up on the website if one is deployed, then log in below.
Accounts live in a Supabase project. Crosscode ships with the hosted Crosscode project compiled in — its
anonpublic key, the same one the website serves to browsers — so this step needs no environment setup. If the coordination service you are pointing at is self-hosted, it verifies tokens against its own Supabase project, so you must sign in against that same project:export SUPABASE_URL="https://<project-ref>.supabase.co" # the project the service uses export SUPABASE_ANON_KEY="<anon public key>"
Set both or neither; setting one alone fails with
SUPABASE_CONFIG_MISSING. Whoever runs the service has these two values — see Set up Supabase and run the coordination service. -
Or log in, if the account already exists.
crosscode login --web <site-url> --service <service-url>
In a real terminal this opens the sign-in page in your browser and the CLI picks up the session on a loopback callback; tokens are never printed. Both flags are optional:
--servicedefaults to the hosted coordination service and--webto the hosted website (apps/daemon/src/hosted.ts), so barecrosscode loginworks. Self-hosters override with the flags or withCROSSCODE_SERVICE_URL/CROSSCODE_WEB_URL. Add--no-browserto print the URL instead of opening it.Agents, CI, and anything without a browser should use the headless path instead, which needs no
--webat all:crosscode login --email <email> --password <password> --json # {"value":{"userId":"…","email":"…"}}
Full contract: BUILD_INSTRUCTIONS.md § Authentication and
docs/onboarding-contracts.md. -
Join someone else's workspace — only if you're not using your own personal one.
crosscode join --workspace <workspaceId> --json # or --invite <code>, or --pair <code>
-
Do real work. Start the daemon (
pnpm daemon, or let the MCP server start it for you) and use the commands under Normal workflow.
Paste the prompt in docs/install-prompt.md into any MCP-capable coding agent (Claude Code, Codex CLI, OpenCode, Cursor, etc.). The agent clones this repository, installs dependencies, and registers the Crosscode MCP server for your project itself — no manual init/daemon step. The daemon starts itself, in the background, the first time the agent calls a Crosscode tool.
Watching your checkout
- One daemon per Git checkout/worktree, which survives restarts
- Captures an edit once it has settled, with before/after hashes so a stale edit can't be applied on top of a newer one
- Records everything to a local append-only SQLite log, plus an outbox for work made while offline
- Hidden Git checkpoints — snapshots that never move
HEAD, touch your index, or show up in branch history - Notices branch switches, commits, resets, index changes, merges, rebases, cherry-picks, and reverts
Sharing work with everyone else
- A coordination service backed by Supabase-hosted PostgreSQL, with an immutable audit log
- Uploads are idempotent, and after a disconnect the daemon resumes from exactly where it left off
- Live presence, tasks, claims, handoffs, and intents over WebSocket, falling back to polling when the socket drops. Paired installs (
crosscode join --pair) subscribe with their workspace token and get the same live updates as a logged-in one - Text and binary files (binaries travel base64-encoded and are restored byte-for-byte), and renames tracked as real renames rather than a delete plus an add
Reviewing before anything lands
- Inspect, diff, accept, or reject each incoming proposal explicitly
- Applying a change is crash-safe and never clobbers a newer local edit
- Validation profiles that only come from a committed
.crosscode/config.yaml, so nobody can smuggle in an arbitrary command - An optional AI reviewer for genuinely ambiguous conflicts. It only ever advises — it can't decide anything on its own, it's off unless workspace policy turns it on, and its suggestions still need human approval
- A per-workspace autonomy setting (always ask / auto-apply when clean / auto-apply always) for teams that want to loosen the always-ask default
publish --branchturns accepted work into ordinary commits on a real branch, with a dry-run plan first
Accounts and teams
- Sign up yourself, from the website or
crosscode signup— no administrator step needed. Your first account gets a personal workspace automatically - Multiple workspaces, memberships, roles, invite codes, and one-time pairing codes, all from the CLI or the service's HTTP API
- Sign-in goes through Supabase Auth, in a browser (
crosscode login) or headlessly for agents and CI (crosscode login --email/--password), and this machine registers itself the first time the daemon starts
Connecting your tools
- A standards-compliant MCP server that starts the daemon for you on the first tool call
- Any MCP-capable agent or editor works: Claude Code, Codex CLI, OpenCode, Cursor, VS Code, Gemini CLI. There's no editor plugin to install — see
docs/mcp-clients.md - Nobody, human or agent, has to open a website to do routine work. See
AGENTS.md
See BUILD_INSTRUCTIONS.md for the authoritative, milestone-by-milestone status of what's implemented and tested, including which of the above are v1/placeholder implementations with known gaps, and Current limitations for the honest gaps.
Crosscode follows four rules:
- Your files win locally. Whatever is on your disk is the truth for your own work.
- Nothing from anyone else is written without your say-so. Remote work arrives as a proposal; accepting it is an explicit act.
- Every write is checked and backed up first. Before applying a proposal, Crosscode re-checks that your files still match what the change was based on, and takes a checkpoint it can roll back to.
- Some things are refused outright: excluded paths, files that look like secrets, symlinks pointing outside the repository, and payloads that are malformed or whose content doesn't match its hash.
If Crosscode is stopped or removed, the repository remains an ordinary Git repository. Checkpoints live under refs/crosscode/checkpoints/... and do not pollute normal branch history.
editor / agent / CLI
|
v
per-worktree daemon --- SQLite events + outbox
|
| authenticated HTTP sync
v
coordination service --- Supabase-hosted PostgreSQL operations + audit log
|
v
other daemons receive reviewable proposals
The daemon binds only to loopback and publishes a mode-0600 connection descriptor under the worktree Git directory. Service access tokens remain in daemon memory; the longer-lived Supabase refresh token is stored in the OS keychain when available (macOS security, Linux secret-tool), or otherwise in the mode-0600 local daemon configuration outside versioned files.
- Node.js 24 or newer
- pnpm 11
- Git
- A Supabase project (or Docker Desktop/another Docker-compatible runtime, for local-only testing against a plain Postgres instance — see
infra/docker-compose.yml)
Install dependencies:
pnpm install
pnpm build
pnpm testCrosscode's coordination service verifies Supabase-issued JWTs and stores workspace/operation state in Supabase-hosted PostgreSQL. Create a Supabase project (or use an existing one), then from its dashboard collect:
- Project URL (Project Settings → API) →
SUPABASE_URL anonpublic key (Project Settings → API) →SUPABASE_ANON_KEY, used by every member'scrosscode login. Members of a self-hosted deployment must set this andSUPABASE_URLin their own environment, because the CLI otherwise signs in against the hosted Crosscode project it has compiled in, and this service would reject those tokens as having the wrong issuer.service_rolekey (Project Settings → API) →SUPABASE_SERVICE_ROLE_KEY, used only by the admin-sideservice:provisioncommand — never distribute this key to members- Connection string (Project Settings → Database) →
DATABASE_URL(Supabase's pooledpostgres://connection string works as-is)
Supabase projects sign access tokens with an asymmetric key (ES256 by default), verified via the project's public JWKS endpoint (<SUPABASE_URL>/auth/v1/.well-known/jwks.json) rather than a shared secret — there is no JWT secret to configure.
export SUPABASE_URL="https://<project-ref>.supabase.co"
export SUPABASE_SERVICE_ROLE_KEY="<project service_role key>"
export DATABASE_URL="<Supabase pooled connection string>"
export MIGRATION_DATABASE_URL="${DATABASE_URL}"
pnpm service:migrate
pnpm serviceMigration 004_supabase_auth.sql enables Row Level Security and maps members to Supabase's auth.users by user_id; 005_rls_hardening.sql adds the RLS policies for handoffs/intents and moves the membership_workspace_ids() helper into a private schema so it isn't directly callable as a PostgREST RPC endpoint. Because a real REFERENCES auth.users(id) foreign key requires the auth schema (which only exists inside an actual Supabase project, not a plain test Postgres instance), add it once by hand after migrating against Supabase:
ALTER TABLE members ADD CONSTRAINT members_user_id_fkey
FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE;The service defaults to http://127.0.0.1:8788. Plain HTTP is allowed only on loopback. To bind to another interface, configure both CROSSCODE_TLS_KEY and CROSSCODE_TLS_CERT; startup refuses non-loopback plaintext HTTP.
Service environment variables:
| Variable | Purpose | Default |
|---|---|---|
DATABASE_URL |
Supabase (or plain) PostgreSQL connection string | required |
SUPABASE_URL |
Supabase project URL, used to verify token issuer and fetch its JWKS | required |
SUPABASE_SERVICE_ROLE_KEY |
Supabase service-role key; only needed by pnpm service:provision, not by pnpm service itself |
required for provisioning |
CROSSCODE_SERVICE_HOST |
Listen address | 127.0.0.1 |
CROSSCODE_SERVICE_PORT |
Listen port | 8788 |
CROSSCODE_TLS_KEY |
TLS private-key path | unset |
CROSSCODE_TLS_CERT |
TLS certificate path | unset |
CROSSCODE_TRUST_PROXY_TLS |
Set to true only when a proxy in front terminates TLS and forwards plaintext (managed container hosts do this). Permits a non-loopback bind without a local certificate. |
unset |
CROSSCODE_ALLOWED_ORIGINS |
Comma-separated exact browser origins allowed to call the API cross-origin, e.g. https://crosscode-one.vercel.app. Empty means no browser may call it. |
unset |
The service's clients are daemons and the CLI, not a browser — the website talks only to
Supabase (sign-in) and to a loopback port on your own machine (the crosscode login
callback), never to this service.
The service must run as a persistent process, not on serverless functions: apps/daemon
holds an open WebSocket to /v1/stream for live coordination, and serverless platforms drop
long-lived connections between invocations. apps/service/Dockerfile builds a deployable
image for any container host (Fly, Railway, Render, Cloud Run, or Docker on a VPS):
# Build from the repository root, not from apps/service.
docker build -f apps/service/Dockerfile -t crosscode-service .Set on the host: DATABASE_URL, SUPABASE_URL, and — when a proxy in front terminates TLS —
CROSSCODE_TRUST_PROXY_TLS=true. CROSSCODE_ALLOWED_ORIGINS stays unset in the CLI-only
product, which means no browser origin may call the API cross-origin; it exists for anyone
building their own browser client against the service.
Run pnpm service:migrate with a migration-owner connection before starting a new service version. Set CROSSCODE_RETENTION_DATABASE_URL (optionally CROSSCODE_RETENTION_SWEEP_MINUTES, default 60) to that same privileged connection to enable the scheduled history-retention sweep; the least-privilege runtime role cannot delete operations, so without it retention only runs when an admin invokes pnpm service:prune. The interval needs a persistent process, so on the Vercel function deployment the sweep must be driven externally (a scheduled pnpm service:prune) until a platform cron is wired. CROSSCODE_RUNTIME_DB_ROLE applies the required least-privilege grants, and service startup refuses a role that can update/delete immutable operations or audit rows. The runtime never executes DDL. Non-loopback PostgreSQL URLs must specify exactly one sslmode=verify-full and cannot use host/SSL query overrides. For local-only testing against a plain (non-Supabase) Postgres instance, infra/docker-compose.yml still starts one on 127.0.0.1:5432; it is not used in production, where DATABASE_URL points at Supabase.
Crosscode's multi-tenant machinery is intact — workspaces, memberships, invites, one-time pairing codes, roles, Row Level Security, presence, and billing all live in the coordination service. None of it has a web UI. You reach it from the CLI or directly over the service's HTTP API:
| What | How |
|---|---|
| Create a workspace | POST /v1/workspaces, or automatically: a user with no memberships gets a personal workspace on their first authenticated request |
| List your memberships | GET /v1/memberships |
| Create / list / revoke an invite | POST /v1/invites, GET /v1/invites, DELETE /v1/invites/:id (owner only) |
| Redeem an invite | crosscode join --invite <code>, or POST /v1/invites/:code/redeem |
| Mint a one-time pairing code | POST /v1/pairing-codes (owner or member) |
| Redeem a pairing code from a checkout | crosscode join --pair <code> — no prior init and no login required |
| List / revoke a paired device | crosscode devices list / crosscode devices revoke <tokenId>, or GET/DELETE /v1/workspace-tokens[/:id] (owner only) |
| List / remove a member | crosscode members list / crosscode members remove <memberId>, or GET /v1/members, DELETE /v1/members/:id (owner only) |
| Set the autonomy tier | crosscode workspace autonomy get / crosscode workspace autonomy set <tier> |
| Read plan and usage | crosscode billing status [--workspace <id>] |
Revoking a device or removing a member takes effect on that credential's very next
request: ccw_ workspace tokens are opaque and resolved against the database on every
call, and every authorization path filters on the member's disabled_at. Removing a
member also retires their replicas and revokes their workspace tokens in the same
transaction. A workspace always keeps at least one owner, and an owner cannot remove
themselves. Both actions are refused to a ccw_ token: team management needs a real
Supabase session, so a leaked terminal-side credential cannot retire its own audit trail.
pnpm service:provision remains available as an administrator-side command for the self-hosted case. It creates or invites a Supabase Auth user by email (via the Supabase admin API, using SUPABASE_URL/SUPABASE_SERVICE_ROLE_KEY) and writes the corresponding workspace/member row directly to Postgres (MIGRATION_DATABASE_URL or DATABASE_URL).
Create the workspace and its owner:
export SUPABASE_URL="https://<project-ref>.supabase.co"
export SUPABASE_SERVICE_ROLE_KEY="<project service_role key>"
export MIGRATION_DATABASE_URL="<Supabase pooled connection string>"
OWNER_JSON="$(pnpm --silent service:provision create my-workspace alice@example.com)"
echo "${OWNER_JSON}"The JSON contains the new workspaceId. create calls Supabase's auth.admin.createUser with email_confirm: true but no password, so Alice needs to set one (Supabase dashboard → Authentication → Users → send a password-reset email, or supabase.auth.resetPasswordForEmail) before her first login. In Alice's checkout:
pnpm crosscode init --json
pnpm crosscode join --workspace <workspaceId> --json
pnpm crosscode login --email alice@example.com --password <her password> --service http://127.0.0.1:8788 --json
pnpm daemoncrosscode login reaches Supabase Auth through the hosted Crosscode project compiled into the CLI unless SUPABASE_URL and SUPABASE_ANON_KEY are set in its own environment (the project URL and anon key collected above) — which, for a self-hosted service, they must be, so that the tokens it mints carry the issuer this service verifies. Set both or neither; one alone fails with SUPABASE_CONFIG_MISSING. There is no separate replica-enrollment step: the daemon self-registers a replica for the authenticated member the first time it starts with a logged-in session and no replica identity of its own yet.
Invite another member to the same workspace:
MEMBER_JSON="$(pnpm --silent service:provision join <workspaceId> bob@example.com member)"
echo "${MEMBER_JSON}"join calls Supabase's auth.admin.inviteUserByEmail, which sends Bob a real Supabase invite email with a link to set his own password. Once he has one, run the same init, join --workspace <workspaceId>, login, and daemon commands in Bob's separate checkout.
To create a read-only service member, use viewer instead of member. Viewers may download operations but cannot upload them.
With the daemon running:
# Repository, daemon, outbox, cursor, and service health
pnpm crosscode status --json
# Declare local work
pnpm crosscode task create "Implement checkout API" --path server/routes/checkout --json
pnpm crosscode claim path server/routes/checkout --task <task-id> --json
# Create or inspect safety checkpoints
pnpm crosscode checkpoint --message "before integration" --json
pnpm crosscode checkpoint inspect <checkpoint-ref> --json
# Review remote work
pnpm crosscode proposals list --json
pnpm crosscode proposals inspect <operation-id> --json
# Materialize only after an explicit decision
pnpm crosscode accept <operation-id> --json
pnpm crosscode reject <operation-id> --json
# Run commands from a committed validation profile
pnpm crosscode validate --profile fast --jsonThe daemon continues capturing work while the service is unavailable. Pending outbound events survive daemon restarts. When connectivity returns, the daemon retries the same immutable event IDs, records acknowledgements, downloads operations after its saved cursor, and stores remote operations as proposals without changing files.
Crosscode is designed to be driven by an agent with shell access and no browser. See AGENTS.md for the full agent-facing contract; the short version:
Discovery. crosscode commands --json prints the entire command tree — every command, its arguments, its options, and its description — as machine-readable JSON. Branch on that rather than parsing --help.
crosscode commands --jsonOutput. --json is a position-independent flag accepted on every command. With it, stdout is exactly one line of compact JSON: {"value":…} on success, {"error":{"code":…,"message":…,"hint":…}} on failure. Without it, the same data is pretty-printed for a human: the success value on its own, without the value envelope, and errors still under error. Nothing else is written to stdout, so it is safe to parse directly.
Login. Never shell out to a browser. Use the non-interactive path:
crosscode login --email "$EMAIL" --password "$PASSWORD" --json
# {"value":{"userId":"…","email":"…"}}CROSSCODE_EMAIL / CROSSCODE_PASSWORD work in place of the flags. Access and refresh tokens are never printed and never appear in --json output — they go straight to the mode-0600 daemon config. There is no token environment variable to set.
Exit codes. 0 on success. 1 on any error, with the failure described by the error.code on stdout. crosscode run -- <command> is the exception: it propagates the wrapped command's own exit code unchanged.
Error codes to branch on:
error.code |
Meaning | What an agent should do |
|---|---|---|
USAGE_ERROR |
Bad or missing arguments | Re-read crosscode commands --json; do not retry verbatim |
UNKNOWN_COMMAND |
No such command | Re-read crosscode commands --json |
DAEMON_UNAVAILABLE |
No daemon for this worktree | Run crosscode init, then start the daemon (or make one MCP tool call, which bootstraps it) |
LOGIN_STATE_MISMATCH |
Browser callback carried a wrong or missing state |
Do not retry the browser flow unattended; use --email/--password |
LOGIN_TIMEOUT |
No browser callback within 300s | Use --email/--password, or --no-browser and hand the URL to a human |
SUPABASE_CONFIG_MISSING |
No Supabase project resolved, or only one of the two variables is set | Set both SUPABASE_URL and SUPABASE_ANON_KEY, or unset both to use the compiled-in hosted project |
UNTRUSTED_VALIDATION_ARGS |
Tried to pass validation commands as arguments | Use --profile <name>; profiles come only from committed .crosscode/config.yaml |
CONFIRMATION_REQUIRED |
publish needs confirmation and there is no TTY |
Pass --yes, only if publishing was actually authorized |
CANCELLED |
A confirmation was declined | Stop; do not retry |
COMMAND_FAILED |
Anything else | Report error.message verbatim rather than guessing |
MCP. The same operations are available as MCP tools with no shell at all — including the full proposal lifecycle (inspect_proposal, diff_proposal, accept_proposal, reject_proposal, publish_branch). See docs/mcp-clients.md for the tool catalog and client setup.
Validation commands and exclusions come only from committed .crosscode/config.yaml at HEAD:
version: 1
validation:
profiles:
fast:
commands:
- pnpm build
- pnpm test
excludedPaths:
- private/**
- '**/*.pem'Arbitrary validation commands are not accepted through HTTP or CLI arguments. A validation result records its command, exit code, duration, bounded/redacted output, and exact checkpoint tree. If the tree changes while validation runs, the result is invalidated.
Crosscode stores machine-local state under the repository's resolved Git directory:
<git-dir>/crosscode/config.json # replica identity, service URL, Supabase session (access + refresh token) (0600)
<git-dir>/crosscode/daemon.json # ephemeral loopback descriptor and secret (0600)
<git-dir>/crosscode/state.sqlite # events, projections, cursor, outbox (0600)
<git-dir>/crosscode/daemon.lock # one-daemon ownership
Safety snapshots use:
refs/crosscode/checkpoints/<replica-id>/<timestamp>-<uuid>
Crosscode never stages, unstages, commits, pushes, force-pushes, resets, rebases, or changes remotes automatically.
pnpm build # tsc --noEmit under strict, then esbuild-bundles dist/
pnpm test # unit + local-daemon suites
pnpm test:postgres # PostgreSQL suites, serialized; needs CROSSCODE_TEST_DATABASE_URL
pnpm docs:build # regenerates the docs site from docs/*.md
pnpm audit --audit-level highpnpm build bundles three entrypoints into dist/ with esbuild — cli.js (the
crosscode bin), mcp.js (the crosscode-mcp bin), and daemon.js, which is not a bin
but is spawned by the MCP server's bootstrap from wherever it was installed. The
@crosscode/* workspace packages are inlined; the ten real npm dependencies stay external
and are declared on the root manifest. scripts/build.mjs fails the build if anything from
node_modules gets inlined, which is what keeps that list honest.
The root package is the published one. To check the tarball before publishing:
npm pack # inspect contents; dist/ + README + LICENSE only
npm i -g ./crosscode-cli-*.tgz # or --prefix <dir> to keep it out of your global bin
cd $(mktemp -d) && git init -q . && crosscode init --json && crosscode status --jsonapps/service is deliberately not part of this package: it deploys as a container.
pnpm test skips the PostgreSQL suites unless CROSSCODE_TEST_DATABASE_URL is set, and
they should be run through pnpm test:postgres rather than by setting that variable for
pnpm test: they share one database, and running them alongside parallel test files lets
unrelated suites interleave session and presence rows. CI runs both steps separately for
the same reason.
The current suite covers protocol boundaries, authenticated daemon HTTP, Git checkpoints, filesystem capture, SQLite restart recovery, outbox identity, stale-base refusal, exclusions, binary safety, crash rollback, Git transitions, MCP-to-daemon mapping, and real daemon child-process restart behavior.
For the implementation plan and current milestone ledger, see BUILD_INSTRUCTIONS.md.
- Production PostgreSQL role grants still need environment-specific deployment hardening. Operation history is pruned to the workspace plan's
historyRetentionDays— on a service-side schedule whenCROSSCODE_RETENTION_DATABASE_URLnames a role that may delete, and on demand viapnpm service:prune, which also deletes audit events and ended sessions older than--older-than-days <n>. A replica whose cursor falls outside the retained window is told to resynchronize explicitly; the other cursor-reconnect tables (tasks, claims, handoffs, intents, validations) are still never pruned. - Supabase refresh tokens are stored in the OS keychain when available (macOS
security, Linuxsecret-tool); otherwise, including on Windows, the mode-0600Git-directory configuration fallback is used. - Binary files are shared base64-encoded with byte-exact materialization; any conflict involving a binary file requires human approval, since deterministic hunk/merge analysis is text-only.
- Renames are tracked as first-class rename changes (old path, new path, content); a rename conflicting with pending work on either path, moving into or out of a critical path, or whose source has diverged locally always requires approval.
- Dependency-impact analysis for
.ts/.tsxis a syntactic AST walk, not a type-checker-backed analysis (see BUILD_INSTRUCTIONS.md Milestone C for exact scope). - There is no hosted/managed coordination service yet — you run a Supabase project and the service yourself.
- There is no production website deployed yet, so
crosscode loginhas no default site to open. Pass--web <url>or setCROSSCODE_WEB_URL, or use the headless--email/--passwordpath, which needs neither. (CROSSCODE_DASHBOARD_URLis still read as a deprecated fallback for setups made before the web dashboard was removed; it warns once on stderr. PreferCROSSCODE_WEB_URL.) - Workspace, membership, invite, and billing management is CLI- and API-only. There is no web UI for any of it, by decision — the website is landing, sign-up/sign-in, and docs.
- Billing has no payment provider behind it yet (see BUILD_INSTRUCTIONS.md Phase 10). The limits themselves are enforced: seat caps are checked inside the transaction that adds a member, and the autonomy tier a plan unlocks is checked on the write path, both answering
402rather than403so a client can tell "out of seats" from "not allowed". The semantic-review call counter is deliberately not metered — review is delegated to your own already-connected MCP agent and never leaves your machine, so there is no per-call cost to bill andGET /v1/workspace/billingcorrectly reports zero calls used. pnpm testskips the PostgreSQL integration suites unlessCROSSCODE_TEST_DATABASE_URLis set, so a local run leaves the service's store, pairing, and reconnect paths unexercised. CI sets it; to run them locally usepnpm test:postgres.- There is no linter or formatter configured.
pnpm build(tsc --noEmit) understrictis the only static gate. - Not on npm yet. The
@crosscode/clipackage builds, packs, and installs —npm packproduces a tarball whosecrosscodeandcrosscode-mcpbinaries work outside this repo on nothing but Node 24 — but it has never been published, so the documented install path is still a cloned checkout run viapnpm installandtsx(seedocs/install-prompt.md). The unscoped namecrosscodeis owned by an unrelated project, which is why the package is published under the@crosscodescope while both binaries keep their short names. Publishing is onenpm publishaway;docs/install-prompt.md,docs/mcp-clients.md, and the marketing site's install snippet all need updating to the npm path at the same time. There is no editor marketplace extension.
Contributions are welcome. See CONTRIBUTING.md for dev setup, workspace layout, and PR expectations. Participation in this project is governed by the Code of Conduct. To report a security vulnerability, see SECURITY.md rather than opening a public issue. Crosscode is licensed under the MIT License.