gravity
Health Warn
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 5 GitHub stars
Code Warn
- network request — Outbound network request in .claude/skills/bus-live-test/scripts/bus.mjs
- process.env — Environment variable access in .claude/skills/marketing-screenshot/scripts/drive.mjs
- process.env — Environment variable access in .claude/skills/terminal-perf-test/scripts/drive.mjs
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
Gravity — a desktop workspace for AI coding agents
Gravity
Always-on, multi-bot desktop app with Claude Code as the agent runtime. A Rust
daemon (gravityd) runs on a Mac mini; the Tauri desktop client attaches
from any machine. See the architecture for the system
design and the control-plane protocol for client APIs.
Builds and tests require no Linear, PostHog, or Cloudflare account. Running real
bots requires your own Claude Code installation and authentication; the test
runtime works without it. See public-build configuration
for telemetry, releases, and secret scanning.
Layout
crates/bus/ shared types, SQLite schema, envelope format
crates/gravityd/ daemon: runtime adapters, durable delivery, scheduler,
MCP bridge, WebSocket control plane
apps/desktop/ Tauri v2 + React + xterm.js client
docs/ architecture, WS protocol v2, public-build configuration
ops/ launchd plist + example gravityd.toml
scripts/ repository checks (file-length rule)
Build & test
After installing the desktop and marketing dependencies, run pnpm run verify
from the repository root for the full verification suite (macOS and Docker
required). See CONTRIBUTING.md for setup.
# Daemon
cargo build --release # target/release/gravityd
cargo test --workspace # unit + integration (uses the runtime double)
cargo clippy --workspace --all-targets
cargo fmt --all --check
# Desktop client
cd apps/desktop
pnpm install
pnpm typecheck && pnpm build # frontend
../../scripts/prepare-sidecar.sh # stage gravityd as the bundled sidecar (once,
# and after daemon changes)
pnpm tauri dev # run the app
Code rules & tooling
No source file may exceed 400 lines. Split by concern instead of growing a
file: crates/gravityd/src/db/, crates/gravityd/src/ws/ and apps/desktop/src/app/
are the reference examples.
./scripts/check-file-length.sh # enforces the limit for Rust + CSS
TypeScript is covered by oxlint's max-lines rule instead, so the limit is
checked in two places for two toolchains.
The desktop client carries the JS/TS toolchain; pnpm check runs the whole
gate and is what CI (and a pre-push hook, if you add one) should call:
cd apps/desktop
pnpm check # typecheck + format + lint + knip + tests-with-coverage + fallow
| Command | Tool | What it enforces |
|---|---|---|
pnpm typecheck |
tsc | strict types, no emit |
pnpm format:check / pnpm format |
oxfmt | formatting (.oxfmtrc.json) |
pnpm lint / pnpm lint:fix |
oxlint | correctness, suspicious, perf, React/a11y, max-lines: 400 (.oxlintrc.json) |
pnpm knip |
knip | unused files, exports and dependencies (knip.json) |
pnpm test / pnpm test:watch |
vitest + jsdom + Testing Library | unit and component tests (vitest.config.ts) |
pnpm test:coverage |
vitest + v8 | writes coverage/coverage-final.json |
pnpm fallow |
fallow | dead code, duplication, complexity and CRAP thresholds (fallow.config.json) |
Tests and CRAP
fallow health scores untested complexity as CRAP (CC² × (1 − coverage)³ + CC),
so it only passes with a real coverage report: fallow.config.json pointshealth.coverage at coverage/coverage-final.json, and pnpm check runstest:coverage before fallow. Running pnpm fallow on its own after changing
code will read a stale report — run pnpm test:coverage first.
Components are tested against FakeDaemon (src/test/fakeDaemon.ts), an
in-memory implementation of the DaemonApi interface in src/protocol/api.ts.
That interface exists so nothing in the UI depends on the concreteDaemonClient, and so the double needs no type casts. DaemonClient itself is
tested against a scripted WebSocket (src/test/fakeWebSocket.ts).
Pre-commit hook
.githooks/pre-commit runs oxfmt, oxlint and fallow's coverage-independent
checks (dead-code, dupes) whenever a commit touchesapps/desktop/. Enable it once per clone (it is a native git hook — no
dependency, and core.hooksPath is relative so it covers every worktree):
git config core.hooksPath .githooks
fallow health is deliberately left out of the hook: it needs a fresh coverage
report, and regenerating one on every commit is too slow. pnpm check covers it.
The hook inspects the working tree rather than the index, so a partial
staged-hunk commit is checked against the files as they are on disk. Resolve
validation failures before committing.
Install (end users)
Open the release DMG and drag Gravity to Applications. The app bundles
the daemon as a signed sidecar; on first launch a setup wizard either installs
it as a launchd user agent on this Mac (one click, no sudo) or attaches to a
remote daemon with a device token. Headless machines skip the app:./gravityd service install from the release tarball does the same
install, and service status / service restart / service uninstall manage
it.
A daemon already answering on 49777 stops the install: Gravity refuses to
start rather than run a second one against the same state. If something else —
not a daemon — holds 49777, the app-managed service waits a few seconds, then
serves on a free port instead and the app follows it there. That moves the bot
bus off the allowlisted http://127.0.0.1:49777/mcp URL, which a policy-managed
Mac silently drops, so Settings → Connection flags it and the daemon restarts
onto 49777 as soon as it comes free. Set negotiate_port = false ingravityd.toml to get a daemon that refuses to start instead.
Run the whole stack (development)
scripts/dev.sh starts a workspace-private daemon and the client against it —
nothing touches ~/.gravity, so it can run beside the installed production
daemon and beside other checkouts:
./scripts/dev.sh # gravityd + the real Tauri window
./scripts/dev.sh web # gravityd + Vite frontend in the browser
GRAVITY_RUNTIME=double ./scripts/dev.sh # deterministic echo runtime, no tokens spent
The daemon gets its own home at .dev/gravityd/ (gitignored) with a generatedgravityd.toml. Ports derive from CONDUCTOR_PORT when Conductor sets it —
frontend on CONDUCTOR_PORT, daemon on CONDUCTOR_PORT+1, defaulting to
1420/1421 — so parallel workspaces do not collide. In browser mode the script
prints the localStorage snippet that points the page at that daemon; the
Tauri window picks the daemon up from GRAVITY_HOME.
Conductor drives the same script from .conductor/settings.toml: the dev,
dev-double, app and test run scripts, with setup installing the
frontend dependencies.
Run the daemon (development)
mkdir -p ~/.gravity
cp ops/gravityd.example.toml ~/.gravity/gravityd.toml # optional; defaults are sane
cargo run -p gravityd # or: gravityd --config <path>
curl http://127.0.0.1:49777/health
On first start the daemon generates ~/.gravity/secrets/client.token
(mode 0600), which the desktop client reads automatically on the same machine.
For a remote client (laptop over Tailscale), do not copy the owner token.
Create a device-scoped credential instead — Devices panel in the app, orcreate_device over the protocol — choose read and/or control grants,
and enter the one-time token plus the Tailscale host/port in the remote app's
connection settings. Revoking the device immediately prevents reconnection.
Backup & restore
gravityd backup # ~/.gravity/backups/backup-<timestamp>
gravityd backup --out /path/to/dir
gravityd restore --from /path/to/dir # refuses if a db exists
gravityd restore --from /path/to/dir --overwrite # moves current db aside first
Backups use SQLite's online backup API and include configuration manifests
(project/bot config files) but never secrets or bot workspaces. Retention
pruning (messages, deliveries, routine runs; FTS index kept in sync) runs
daily by default — see [retention] in gravityd.toml.
Mac mini service
cargo build --release
./target/release/gravityd service install # binary → ~/.gravity/bin, launchd user agent
sudo pmset -a sleep 0 disablesleep 1 # keep the mini awake
service install copies the invoked binary to ~/.gravity/bin/gravityd,
writes ~/.gravity/gravityd.toml if missing, and bootstraps thein.mikolajczuk.gravityd launchd agent — rerun it after a rebuild to upgrade in
place. service restart bounces that agent without touching the install, which
kills every running bot session mid-turn; Settings → Connection offers the same
thing behind a confirmation. ops/in.mikolajczuk.gravityd.plist remains for
fully manual setups.
How it works, briefly
- Each bot is a provisioned directory (
~/.gravity/projects/<p>/bots/<b>)
withsystem.md,mcp.json, and a workspace containingCLAUDE.mdand
cooperative.claude/settings.json(permission rules + lifecycle hooks that
report state to the daemon). The runtime adapter spawnsclaudethere in a
PTY; the client renders it with xterm.js, preserving native prompts. - Messages are durable rows before delivery. A worker leases due
deliveries, posts rendered envelopes ([msg #42 from BOB · task] …)
to the session's inbox socket, retries with backoff, and surfaces terminal
failures. Bots consume and acknowledge via thegravity-busMCP server
(send_message,complete_task,check_inbox, …) with per-bot scoped
tokens. - Bot-to-bot sends create one task + one delivery per message, with hop
counts and origin chains for loop prevention. - Routines use cron, interval, or named-signal triggers with unique
occurrences, leases, overlap policies, and catch-up on restart. Bots can
create and manage their own routines, which are enabled on creation. - The WS control plane (protocol v2) is authenticated, versioned, and
reconnectable with terminal replay cursors. Multiple clients can watch a bot;
clients with thecontrolgrant can type and resize its terminal.
Contributing
See CONTRIBUTING.md for setup, validation, and pull requests.
License
Gravity is licensed under the MIT License. Third-party dependencies
retain their respective licenses.
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found