Teams
Run your own team sync server
Self-host the relay that keeps a team's workspaces in sync — written from a real end-to-end run, including the parts that are still hands-on.
On this page
- Read this before you start
- What syncs, and what does not
- 1. Prerequisites
- 2. What it costs
- 3. Stand up the relay
- The Docker path (this is what was run)
- The in-app provisioner
- Running it without Docker
- 4. Point a Mac at the relay
- 5. Adding a device to the roster
- a. Send them the workspace, so their node knows what to join
- b. Put their device on the roster, so the relay will talk to them
- What a first join actually looks like
- About invitations
- 6. HTTPS and a domain
- 7. Backups and restore
- Restoring the relay
- 8. Revoking a device
- 9. Upgrading
- 10. Is it up?
- 11. Troubleshooting
- Not yet supported
- When something is wrong
- Where to go next
Beta. Excellent is local-first: every person's app keeps a complete copy of the workspace on their own machine. To keep those copies converged, your team runs one small always-on server — the relay. You host it. Excellent never hosts it and never sees your data.
This page was written by provisioning a real relay in Docker and running real nodes against it — first on 2026-09-23, then re-run end to end on 2026-09-24 after the team-join fix landed. Every command and every error below was executed. Where a path was read in the product source but never run, it says so instead of implying otherwise.
Read this before you start
A second device can now join an existing workspace, and it works from the command line only. On the 2026-09-24 run, a clean second node received the first node's rows on its first tick, and its own rows reached the first node — both directions, with no hand-edited workspace id and no hand-edited cursor file. It is still beta, and two things will surprise you if nobody says them first.
1. The in-app switch cannot join an existing workspace. Settings → Team →
Primary & sync takes a relay address but no workspace id, so a node configured
that way is always the owner of its own workspace. A teammate joining yours
runs excellent-mcp sync setup --team-bundle in a terminal. See
§5.
2. Adding a teammate is a hand edit of a file on your server. There is no
roster API and no invite link. They send you one line, you paste it into
devices.json, and the relay picks it up without a restart. Removing the line
revokes them the same way.
All of this is newer than the published CLI. The join commands (sync setup --team-bundle, sync invite-team, sync workspace-id), the roster hot-reload
that removes the restart, and GET /health all landed on 2026-09-24. Check before
you start, on every machine and on the relay, because the relay installs the
CLI from npm too:
excellent-mcp sync # the subcommand list must include workspace-id and invite-team
curl -s http://<relay>:9700/health # must answer {"ok":true,"service":"excellent-relay",…}If either check comes back the old way, that install predates the fix: a second device on it will pair, sync, and pull zero rows, and roster edits will not take effect until you restart the relay. Upgrade first — see §9.
What syncs, and what does not
The relay carries your workspace. The sync registry classifies 127 tables as team-shared, merged conflict-free so two people can edit the same record, online or offline, without losing anything. Genuine conflicts — money, pipeline stage, lifecycle status, archival, a person's identity — are surfaced for a human instead of silently resolved. Project rows were what the test run exercised: the owner's project reached the joining node on its first tick, and the joiner's project reached the owner.
Verification evidence stays on the machine that produced it. verification_runs
is local-only. Gate-run receipts, DoD judgment receipts and the webhook delivery
records are excluded from the team exchange. The entire Universal Verification
store — attempts, evidence snapshots, run manifests — lives in a separate database
that the sync registry does not govern at all, and evidence blobs over 1 MiB are
written to local disk. Searching all 127 team-synced table names for
verif|receipt|attempt|evidence|gate|proof returns exactly one hit: dod_criteria,
the definition of done, not any receipt.
A teammate can see that you shipped something. They cannot open the proof behind it from their own laptop. If that is what you need, this server does not provide it. See Receipts for what a receipt binds and how to verify one offline.
1. Prerequisites
Every machine that people edit on must be an Apple-Silicon Mac. The
conflict-free merge engine (cr-sqlite) is vendored for darwin-arm64 only. On any
other platform it throws rather than half-loading, and the in-app switch refuses
with platform-unsupported. This is a hard limit, not a warning. CRSQLITE_EXT
exists as an escape hatch for an extension you build yourself; no binary for any
other platform ships.
The relay host does not need to be a Mac. It is a plain Node HTTP server and
never loads the merge engine. Verified: the relay ran in a node:22-slim Linux
container for the whole run.
On the relay host you need:
- Docker with Compose, or Node and the published CLI.
- Outbound access to the npm registry. There is no prebuilt relay image — the
container runs
npm i -g @excellent-so/clion every start, which took about 24 seconds in testing and is the relay's only hard external dependency. A registry outage breaks every restart. - One TCP port reachable by every teammate (9700 by default).
- A non-empty allowlist file. The relay exits with status 2 on an empty roster.
- If it crosses the public internet: a domain name and the ability to set its DNS.
On each teammate's Mac: Excellent installed, and Node 22. The run started on Node 24 and every command that opens the database failed:
$ node -v
v24.20.0
$ excellent-mcp sync pair
Error: The module '.../better-sqlite3-multiple-ciphers/build/Release/better_sqlite3.node'
was compiled against a different Node.js version using
NODE_MODULE_VERSION 127. This version of Node.js requires
NODE_MODULE_VERSION 137.
code: 'ERR_DLOPEN_FAILED'The product pins node: ">=22.0.0 <23" and its .nvmrc says 22.23.2. Everything
below ran on Node 22.23.2.
EXCELLENT_DATA_DIR is the one knob that moves a node's data directory; it
defaults to ~/.excellent.
2. What it costs
Excellent charges nothing for this — no per-seat fee, no licence check. Your cloud provider bills you directly. AI features run on each teammate's own Claude Code subscription, so there is no central AI bill either.
Provider list prices as the product records them, checked 2026-06. Confirm them in your provider's console; providers change prices.
| Where it runs | Size | Estimated USD/month |
|---|---|---|
| A machine you already run (Docker) | — | $0 |
| DigitalOcean | 1 vCPU · 512 MB | $4 |
| DigitalOcean (provisioner default) | 1 vCPU · 1 GB | $6 |
| DigitalOcean | 1 vCPU · 2 GB | $12 |
| DigitalOcean | 2 vCPU · 2 GB | $18 |
| DigitalOcean | 2 vCPU · 4 GB | $24 |
| DigitalOcean | 4 vCPU · 8 GB | $48 |
| AWS Lightsail (provisioner default) | 512 MB · 2 vCPU | $5 |
| AWS Lightsail | 1 GB · 2 vCPU | $7 |
| AWS Lightsail | 2 GB · 2 vCPU | $12 |
| Fly.io (usage-billed, from) | shared-cpu-1x · 256 MB | from $3 |
| Fly.io (usage-billed, from) | shared-cpu-1x · 1 GB | from $6 |
| A server you already have | — | whatever you already pay |
We do not publish a team size any of these fits. No benchmark has been run, so the only honest number is the price.
Fly.io and Cloudflare appear here as places you could run a container by hand. The one-click provisioner does not target them — see Not yet supported.
3. Stand up the relay
The Docker path (this is what was run)
The Compose file below is the product's own generated recipe, not something written
by hand. The test run imported the product's buildRelayComposeRecipe and ran its
output unchanged except for renaming the container, the volume and the published
port so it could be torn down cleanly.
In an empty directory on the relay host, devices.json — the allowlist. It must
not be empty. Start with your own device; §4 shows where that line comes from.
{
"244805373dc4e11fb92164033546a2db": "MCowBQYDK2VwAyEAPuhy+xCh1I9u5+RqySduhDTG43PuQ0dSILyVcDFslu4="
}docker-compose.yml:
services:
relay:
image: node:22-slim
container_name: excellent-relay
restart: unless-stopped
ports:
- "${RELAY_PORT:-9700}:9700"
environment:
EXCELLENT_DATA_DIR: /data
command: ["sh", "-c", "npm i -g @excellent-so/cli && exec excellent-mcp sync relay serve --host 0.0.0.0 --port 9700 --devices /etc/excellent/devices.json --log /data/relay-log.ndjson"]
volumes:
- relay-data:/data
- ./devices.json:/etc/excellent/devices.json:ro
volumes:
relay-data:
name: excellent-relay-datadocker compose up -d
docker compose logs -f relayStartup-line excerpt from the 2026-09-24 run, about 24 seconds after the container starts — the npm install runs first, and the relay is unavailable for that whole window on every restart:
relay listening on http://0.0.0.0:9700 (1 device(s); log /data/relay-log.ndjson)The old output also told every node to run the owner's setup command. Those
obsolete instruction lines are omitted here. Use a relay URL reachable from every
Mac; 0.0.0.0 above is a listen address, not the address to give teammates.
The current CLI help gives this sequence for switching from single-owner to team
sync: freeze writes, configure the owner, then join teammates with the owner's
workspace bundle. Pair every device and add its public key to devices.json;
retry pairing after the roster edit if the device was initially refused. Start
the owner to seed the relay, then start Excellent on the other machines. Only the
owner uses setup --mode team; teammates use setup --team-bundle. Follow
§4 and §5 for the
commands. This guidance was checked against current source; it is not additional
terminal output captured in the dated run above.
The in-app provisioner
Excellent ships a one-click provisioner for the relay under Settings → Team. It generates exactly the Compose file above, seeds the roster with your own device, and polls until the relay answers. Its targets are Docker, DigitalOcean and AWS Lightsail; give it a domain and it also stands up Caddy for HTTPS.
Its success message tells you to add each teammate's device to devices.json on
the relay host. The relay re-reads that file when it changes, so additions and
revocations take effect without a restart. The manual roster edit is still
required — see §5.
The provisioner's screens were read in the product source but not clicked through: walking them needs the full app running with an authenticated owner session, which this run did not do. So the screen-by-screen walkthrough is not published here. What is published is the Compose file it emits, which was generated by its own builder and run.
Running it without Docker
The container runs this command; on a host with Node 22 and the CLI installed it is the same command:
npm install -g @excellent-so/cli
excellent-mcp sync relay serve \
--host 0.0.0.0 \
--port 9700 \
--devices /etc/excellent/devices.json \
--log /var/lib/excellent/relay-log.ndjson| Flag | Meaning |
|---|---|
--host | Bind address. 0.0.0.0 is what the generated recipe uses; a loopback bind is invisible to other machines. |
--port | Listen port. The recipe's default is 9700. |
--devices <file> | The JSON allowlist, { "<deviceId>": "<publicKey>" }. Required, and must not be empty. |
--log <file> | The append-only change log. |
Wrap the process in systemd, launchd or a supervisor so it survives a reboot. Only the containerised form was exercised in testing.
4. Point a Mac at the relay
The sync subcommands that exist, from the CLI's own error message:
$ excellent-mcp sync --help
unknown 'sync' subcommand: --help (try setup|join|adopt|status|now|pair|workspace-id|invite-team|relay serve)If workspace-id and invite-team are missing from that list, your CLI predates
the join fix. Upgrade (§9) before going further.
Which setup you run depends on which side you are on.
The first machine — the owner of the workspace everyone else will join:
excellent-mcp sync setup --mode team --relay http://<relay-host>:9700
excellent-mcp sync pair --relay http://<relay-host>:9700A teammate joining that workspace uses the bundle the owner sends them (§5)
instead of --mode team:
excellent-mcp sync setup --team-bundle <blob>
excellent-mcp sync pair --relay http://<relay-host>:9700Running --mode team on the second machine is the mistake that costs you an
afternoon: it configures that node as the owner of its own workspace, and the
relay will correctly send it nothing, forever. The sync loop now says so out loud
after a few empty ticks, naming the command that fixes it.
sync setup writes sync.env in the data directory, mode 0600, holding
EXCELLENT_SYNC_MODE='team' and EXCELLENT_RELAY_URL. Source it before starting
Excellent. sync pair mints this machine's Ed25519 device key and prints the one
line the relay operator needs:
$ excellent-mcp sync pair --relay http://<relay-host>:9700
device: 244805373dc4e11fb92164033546a2db
public key (give this to the relay operator / devices.json):
"244805373dc4e11fb92164033546a2db": "MCowBQYDK2VwAyEAPuhy+xCh1I9u5+RqySduhDTG43PuQ0dSILyVcDFslu4="Against a relay that has not been told about this device, pairing is refused:
pairing with http://<relay-host>:9700 failed: relay /pair/challenge → HTTP 404 {"ok":false,"reason":"unknown-device"}
Is this device provisioned on the relay? (sync relay serve --devices …)That refusal is the allowlist doing its job.
This used to be refused outright. Until 2026-09-24,
sync setup --mode teamexited 2 with a solo-node identity message, because agent ids were derived from the process tree and were not unique across machines. They are now salted with a per-device fingerprint, and team sync is allowed. Theremote-workerrole is still refused, and for a reason that has not changed: it needs a distributed lock that does not exist.
The product's own runbook names the in-app equivalent as Settings → Team → Primary & sync → Team sync (conflict-free). That path was not walked in this run, so nothing about its screens is published here — and, as §5 says, it cannot join someone else's workspace in any case.
5. Adding a device to the roster
There is no roster API. The relay's complete route list is GET /health,
POST /pair/challenge, POST /pair/verify, POST /register, POST /push,
POST /pull, and — only when webhooks are enabled — POST /inbox/pull and the
/hooks/<platform>/<deviceId> pair. Nothing else exists to call.
Onboarding a teammate is therefore two separate things, and both have to happen. Doing only one is the failure mode this section exists to prevent.
a. Send them the workspace, so their node knows what to join
On the machine that already has the workspace:
excellent-mcp sync invite-teamIt prints one base64url line, then instructions. That line carries a relay URL and a workspace id and nothing else — it is not a credential, it grants no access, and on its own it gets a teammate nowhere. The command's own output says so. Send it however you would send an address.
excellent-mcp sync workspace-id prints just the workspace id on stdout, one line,
if you would rather assemble things yourself.
The teammate runs, on their machine:
excellent-mcp sync setup --team-bundle <the line you sent>
excellent-mcp sync pair --relay http://<relay-host>:9700setup --team-bundle points their node at your workspace instead of the one their
install minted for itself, and it says so when it does:
aligned workspace 1 ('default') to the joined uid: 01A0D1DD84B0068570230B9458 -> 01A0D1DD7A3315DA4231358547It refuses rather than guessing when the answer is not obvious — if their install has more than one live workspace, or if the local workspace already holds real work that would silently become yours. The refusal names the flag that overrides it.
b. Put their device on the roster, so the relay will talk to them
sync pair prints their device line. You paste it into devices.json on the
relay host:
{
"244805373dc4e11fb92164033546a2db": "MCowBQYDK2VwAyEAPuhy+xCh1I9u5+RqySduhDTG43PuQ0dSILyVcDFslu4=",
"68540a33f46110da273cb3ea789b0ed2": "MCowBQYDK2VwAyEAj1Xcz0Lo56gMNS7Z9GcSZr5CGrsPLRy0Z/H3QXPmElc="
}No restart. The relay re-reads that file whenever it changes, before it answers a pairing request and before it resolves any bearer token, so the addition lands on their next attempt. It logs what moved:
[relay] roster reloaded from /etc/excellent/devices.json: +1 device(s) (68540a33…), -0 REVOKED; 2 device(s) now allowed.A roster file that is unreadable or does not parse keeps the last good roster and says so on stderr. It never falls back to allowing everyone, and it never empties itself and locks the team out.
Until their line is in the file, pairing is refused with 404 unknown-device —
that refusal is the allowlist doing its job.
What a first join actually looks like
The joining node receives the workspace on its first tick. It will also record a
handful of parked rows and a couple of conflicts, every time, and that is
expected rather than a sign of damage: both installs seeded a project called
general and an identical set of loop and rubric rows at first run, so those
genuinely-different rows collide on the same unique keys. One copy of each loses
and is surfaced at /database/conflicts instead of being silently merged.
Nothing is lost. It is alarming to look at and it is not caused by the join.
About invitations
There is no invite link, and excellent-mcp sync join --invite still cannot join a
team relay. That bundle belongs to the older single-owner sync mode: it carries a
database URL and a login coupon and has no field for a relay address, so sync join refuses a bundle without a database URL and then writes a single-owner
configuration.
sync invite-team above is a different thing despite the name. It hands over an
address, not an authorisation, and the teammate is still refused by the relay until
you have done step (b) by hand.
6. HTTPS and a domain
The relay speaks plain HTTP and has no TLS of its own. Device authentication is a signed Ed25519 challenge, but the contents of your workspace are readable on the wire. Plain HTTP is acceptable only on a trusted LAN or a VPN. Do not expose the relay directly to the internet.
For anything crossing the internet, terminate TLS in front of it. Giving the provisioner a domain does this for you: the relay stops publishing its own port and Caddy takes 80/443 with an automatic Let's Encrypt certificate. The generated service:
caddy:
image: caddy:2
container_name: excellent-caddy
restart: unless-stopped
depends_on:
- relay
ports:
- "80:80"
- "443:443"
command: caddy reverse-proxy --from relay.your-team.example --to relay:9700
volumes:
- caddy-data:/data
- caddy-config:/configThe manual step is DNS: point the domain's A/AAAA record at the host's public
IP. Until it resolves, the provisioner reports the relay as pending-dns rather
than reachable. Teammates then use the https:// URL.
Not exercised. The Compose fragment above is what the product generates, and it was read from the source — but no certificate was ever issued during testing. Let's Encrypt's HTTP-01 challenge needs a public DNS name resolving to a publicly reachable host on port 80, which a laptop behind NAT cannot provide. Treat TLS as unverified until you have run it on a real host with a real domain.
7. Backups and restore
Your first line of defence is that every teammate's Mac already holds a complete copy of the workspace. Cold backups cover what redundancy does not: someone deletes something everywhere, disk loss, ransomware.
On the relay host, back up two things: the append-only change log, and
devices.json.
With the Docker recipe the log lives in a named volume, not in a home directory, so a file-level backup of the host will miss it entirely:
docker cp excellent-relay:/data/relay-log.ndjson ./relay-log.ndjson
cp devices.json ./devices.jsonThe log only grows, so a daily snapshot gives you a clean replay point. Stop the relay or snapshot the volume while copying so you do not capture a half-written line.
On each Mac, back up the whole data directory — data.db, local.db,
device-key.pem, sync.env, keys/. Do it before anything risky.
Read this before you rely on a Mac backup.
data.dbis encrypted at rest with SQLCipher and the key is not in the data directory — it lives in the machine's OS keychain. A copy ofdata.dbalone cannot be opened on a different machine. Either restore onto the same machine with its keychain intact, or export and preserve the key alongside the backup.
Restoring a single Mac end to end, including the keychain step, was not walked in testing, so no procedure for it is published here.
Restoring the relay
Verified by destroying the container and its volume, then restoring from a copy of the log:
docker compose up -d
docker cp ./relay-log.ndjson excellent-relay:/data/relay-log.ndjson
docker compose restart relayReal output after the restore — all 303 entries came back and devices reconnected:
relay listening on http://0.0.0.0:9700 (2 device(s); log /data/relay-log.ndjson)
$ docker exec excellent-relay wc -l /data/relay-log.ndjson
303 /data/relay-log.ndjsonThe product's runbook says a brand-new device can replay the whole history from cursor 0. That was tested against this restored log with a clean third node: it received nothing, for the same reason as §5. Do not plan on a fresh device as a recovery path.
8. Revoking a device
Delete the device's line from devices.json. That is the whole procedure.
No restart. The relay re-reads the roster when the file changes, and a removal drops the device's key, its live session, its bearer tokens and its routing scope together — so a token issued a second earlier stops working too. This was tested on 2026-09-24 with the relay process untouched: the removed device was refused, it never received a change written after its removal, and the container's restart count stayed at zero.
pairing with http://<relay>:9700 failed: relay /pair/challenge → HTTP 404 {"ok":false,"reason":"unknown-device"}On a Linux relay host this is immediate. The reload is triggered by the roster file's modification time as the relay process sees it. On the macOS Docker bind mount used for testing, that propagated about a second late — long enough that a
/healthsnapshot taken half a second after the edit still counted the old device. A Linux host, which is the documented deployment, has no such layer.
The relay host's own device is a base entry that a file edit cannot remove, so you cannot lock yourself out of your own relay by editing the roster.
The revoked machine keeps its own local copy of everything it had already synced. Revocation stops future exchange; it does not reach back and delete.
9. Upgrading
Migrations are additive-only, so nodes on different builds can straddle a version. Take a backup first.
Each Mac:
npm install -g @excellent-so/cli@latestThe relay has no schema of its own — its state is the NDJSON log plus the roster file, and a newer version replays the log unchanged. Because the container installs the CLI at every start, restarting it already picks up the latest published version:
docker compose restart relayKeep relay-log.ndjson and devices.json in place across the upgrade.
Pin the version in production. The generated command is
npm i -g @excellent-so/cliwith no version, which means the relay can change underneath you on an unrelated restart. The published version at the time of this run was0.1.0(checked 2026-09-23, and it is a point-in-time reading — runnpm view @excellent-so/cli dist-tagsfor today's). Pin@excellent-so/cli@<version>in the Compose command once you have one you trust.
10. Is it up?
Check that the container is running, that the port answers, and that the log is growing.
docker compose ps
docker compose logs --tail 20 relay
curl -s http://<relay>:9700/healthGET /health answers with the roster size, the uptime and the head of the change
log:
$ curl -s http://<relay>:9700/health
{"ok":true,"service":"excellent-relay","devices":2,"uptime":17,"head":135}A rising head is the real signal that changes are flowing. devices is the
roster as the relay currently reads it, which is how you confirm an addition or a
revocation landed without restarting anything.
That endpoint is new as of 2026-09-24, and it names itself on purpose: the
provisioner's "reachable" poll now requires ok: true and
service: "excellent-relay" before it calls a relay up. It used to accept any
status below 500, which is true of any HTTP server that happens to hold the port.
excellent-mcp sync statusstill cannot see team mode. It inspects only the older single-owner replica flag, and returns"state": "single-player"on a healthy, actively-syncing team node — and identically on a revoked one, and on a machine that was never configured. Do not use it to diagnose the relay; use/healthand the node's own tick log.
11. Troubleshooting
Every error below was produced during testing.
ERR_DLOPEN_FAILED … NODE_MODULE_VERSION 127 … requires 137
You are on Node 24. The native modules are built for Node 22. Switch (nvm use 22)
and retry.
sync setup: team-sync is refused: this node runs the SOLO-NODE identity implementation…
Your CLI predates 2026-09-24. Agent ids are now salted with a per-device
fingerprint and team sync is allowed; upgrade (§9). The same refusal on
--role remote-worker is not a version problem and is still correct.
SqliteError: no such table: main.document_targets when team sync starts
The database was created by the CLI alone and is missing tables only the app
creates. The sync registry classifies those tables as team-shared, so enabling the
merge shadow fails closed. Launch Excellent once on that machine first. The
terminal-only onboarding path dead-ends here.
relay /pair/challenge → HTTP 404 {"ok":false,"reason":"unknown-device"}
The device is not in devices.json, or the public key was truncated in transit, or
the file no longer parses — a broken roster keeps the last good one and says so on
the relay's stderr. Copy the whole "<deviceId>": "<publicKey>" line and check the
relay's log for the roster reloaded line naming the device. No restart is needed
to pick up the edit.
Everything reports healthy, and the other machine is empty
That node is not joined to your workspace — it is the owner of its own. It happens
when the second machine was set up with sync setup --mode team instead of sync setup --team-bundle, or from the in-app switch, which cannot join. The node now
says so itself after a few empty ticks:
[team-sync] 5 ticks and this node has NEVER received a row from a peer. … if you meant to JOIN a teammate's workspace, this node is NOT configured to (EXCELLENT_TEAM_WORKSPACE_UID is unset), so it is registering a scope the relay can never match and will correctly send you nothing, forever.Re-run sync setup --team-bundle <blob> on that machine (§5a). The sync cursors
are reset automatically when the scope changes, once, so you do not have to touch
team-sync-state.json — the warning fires only on a node that has never received
anything, so a quiet, converged team does not trip it.
The joining machine filled up with conflicts on its first sync
Expected, and not damage — see §5. Both installs seed a project called general
and an identical loop and rubric set, so those rows collide on their unique keys
and one copy of each is parked at /database/conflicts rather than silently
merged.
A node was working, then stopped after a relay restart Relay sessions are in-memory. Every restart de-authenticates every device; the app's boot loop re-pairs. If it does not, that device is no longer on the roster.
The relay dies about 90 seconds after it starts
Your CLI predates 2026-09-24, when sync relay serve was added to the long-running
exemption list in the CLI's own watchdog. Before that the watchdog killed it —
silently survivable inside Docker, where the relay is PID 1, and fatal under
systemd or in a plain shell. Upgrade (§9).
A node cannot reach the relay at all
In order: is the container up? Is it bound to 0.0.0.0 rather than loopback? Is
the port open through the host firewall or cloud security group? Does the URL in
sync.env match exactly, including scheme and port? For HTTPS, has the certificate
actually issued — that is, does DNS point at the box?
The team-sync switch refuses on a machine
That machine is not an Apple-Silicon Mac. The refusal reason is
platform-unsupported. There is no safe override; the merge engine is not built
for it.
Relay restarts are slow, or fail with an npm error
Every start runs npm i -g @excellent-so/cli, which took about 24 seconds and
needs the npm registry. If the registry is unreachable, the relay does not start.
Pin the version (§9) and keep the host's outbound access open.
Not yet supported
Each of these was confirmed absent, not assumed.
- Team-visible verification evidence. Attempts, gate-run receipts, DoD judgments and evidence blobs stay on the machine that produced them.
- Joining from the app. The in-app team switch takes a relay address but no workspace id, so it can only ever make a node the owner of its own workspace. A teammate joins from the command line. See §5.
- An invitation flow for the relay.
sync invite-teamhands over an address, not an authorisation, and the teammate is still added to the roster by hand.sync join --invitecannot join a team relay at all: it targets the older single-owner mode, and its bundle has no relay field. - A roster API. Adding or removing a device is a hand edit of
devices.jsonon the relay host. The relay picks the edit up without a restart, but nothing makes the edit for you. - Monitoring.
GET /healthexists and tells you the roster size, uptime and log head, but nothing watches the relay for you and nothing will tell you it is down. - Team-aware
sync status. - A prebuilt relay image. The relay reinstalls the CLI from npm on every start.
- Windows, Intel or Linux editing machines. Apple Silicon only. The relay host itself can be Linux.
- Fly.io and Cloudflare provisioning targets. The one-click provisioner covers Docker, DigitalOcean and AWS Lightsail.
- Owner transfer, and a guard against removing the last owner.
- Device-key rotation. Rotation exists for the older single-owner tokens. Relay device keys have no rotation path.
- mTLS and certificate pinning. TLS terminated by your own reverse proxy is the model.
- SSO, SAML, SCIM, directory sync, audit-log export.
- A managed or Excellent-hosted relay, per-seat pricing, or enterprise plans. Every team self-hosts. There is nothing to buy here.
- A long-running integrity soak or scheduled two-machine CI. Neither has been run, so this page publishes no uptime or durability claim.
When something is wrong
Send the relay's container status and last 20 log lines, and the current line count
of relay-log.ndjson. Those three localize nearly everything in §11.
Where to go next
- Security and data — what is stored locally, and what leaves the machine.
- Receipts — what a receipt binds, and how to verify one offline.
- CLI reference — every command the installed CLI actually has.