syncport docs
syncport moves unfinished work between your own devices and between coding agents. It carries uncommitted Git work, the files Git ignores that you need, such as .env, and the agent session itself.
Run syncport handoff on your desktop, then syncport continue on your laptop. The project arrives exactly as you left it, staged, unstaged and untracked, and the session picks up where it stopped. Continue in the same agent and it resumes natively. Continue in a different one and syncport writes a handoff brief into its context.
- Four agents. Codex, Claude Code, Cursor and Hermes, in any combination.
- The other device can be off. Handoffs wait on an end-to-end encrypted relay for 14 days.
- Or ask for it. A device that is online can be asked to send its latest work.
- Only your devices can read it. The relay sees device, agent and project names, sizes and times. Nothing else.
- Clean history. No “wip” commits and no pushing to a remote first.
Install#
syncport is a single binary for macOS and Linux, on arm64 and amd64. It needs Git. Install it on every device you work from.
npm install -g syncportWithout Node.js#
For servers and minimal machines. The script downloads the release binary for your OS and CPU, verifies its SHA‑256 checksum and installs it to ~/.local/bin.
curl -fsSL https://syncport.live/install.sh | shShell completion#
syncport completion zsh > "${fpath[1]}/_syncport" # also: bash, fishQuick start#
This example hands a project from a desktop to a laptop and back. Both devices need syncport and a Google account you can sign in with.
- Set up each device once.
setupsigns you in, approves this device in the browser, installs the background daemon and adds the syncport skill to every agent it finds.syncport setup # repeat on the laptop - On the desktop, inside a Git project, hand off the work and the latest agent session:
cd ~/projects/shop syncport handoff # the laptop can be off - On the laptop, pick up the newest handoff. syncport applies the work and prints how to resume:
syncport continue codex resume <session-id> # or the agent you continue in - Back at the desktop, bring the work home. If the laptop is online, ask it directly:
syncport continue --from laptop
After setup, you can skip the terminal: tell your agent “hand this off” on one device and “continue what I was doing on my desktop” on the other.
Hand off#
syncport handoff packs the current project and its newest agent session, encrypts the package for your other devices, signs it with this device's key and uploads it to the relay.
syncport handoff # this project and its latest sessionA handoff carries staged, unstaged and untracked work, the Git objects the other side needs, the ignored files you need (encrypted when they look like secrets), the native session transcripts and the agent's project state. See What moves.
Outside a Git project, a handoff carries only the conversation: a context handoff with canonical transcripts and metadata, no workspace. Use it to move a research or planning session that has no repository behind it.
Who receives it#
A handoff is encrypted for the active devices of your account at the moment you send it. Each recipient can open it independently, and the dashboard shows which devices have picked it up. A device you add later cannot read older handoffs.
Stop agents and other processes that write to the project. syncport captures a consistent snapshot and does not merge simultaneous changes.
Continue#
syncport continue takes the newest handoff for this project, verifies the sender's signature against the key it pinned for that device, applies the work in place and tells you how to resume.
syncport continue # newest handoff for this project
syncport continue --from desktop # ask the desktop for its latest workRun it in the project directory. If the project is not on this device yet, syncport places it at the same path relative to your home directory, so ~/projects/shop on macOS becomes /home/me/projects/shop on Linux, and remaps paths inside the session to match.
Before anything in the workspace changes, syncport saves a Git backup of HEAD, the branch, the index and the working files. syncport undo restores the latest one.
undo can bring it back.Sessions everywhere#
syncport sessions lists every agent session on every device of your account: which agent, which device, which project, when it was last updated and how many messages it has.
syncport sessions # every device, every agentThe background daemon keeps this index current. Session titles, working directories and models are sealed for your devices, so the list in your terminal shows them, while the relay and the web dashboard see only the agent, device, project, time and message count.
To continue one of them here, run syncport continue --from <device> --session <agent>:<id> (syncport sessions --json shows the IDs). That device must be online; when it is not, continue from its newest handoff with syncport continue.
Remote requests#
A device that is online can be asked to send its latest work. Online means its daemon checked in during the last three minutes.
syncport continue --from desktop- This device files a request on the relay for the desktop: which project, and optionally which agent session.
- The desktop's daemon claims it, makes a normal handoff for the requesting device and marks the request fulfilled.
- This device applies the handoff as soon as it is ready, exactly like
syncport continue.
Requests expire after ten minutes. A device that is asleep or offline does not answer; continue from its newest handoff instead. Only devices of your own account can file or answer requests.
Crossing agents#
Every handoff records which agent the session came from. Where you continue decides what happens to it.
| From → to | What happens |
|---|---|
| Same agent | Native resume. The agent's own transcript travels and resumes where it stopped. |
| Claude Code or Codex → Hermes | Native import. Hermes imports the Claude Code or Codex session itself. |
| Any other pair | Handoff brief. syncport writes a brief into the receiving agent's context. |
Native resume commands#
codex resume <id> # Codex
claude --resume <id> # Claude Code
cursor-agent --resume <id> # Cursor
hermes --resume <id> # HermesThe handoff brief#
When the receiving agent cannot read the sender's format, syncport turns the session into a short brief and places it where that agent reads its context:
- Goal: what you were trying to do.
- Where it stopped: the last step, open questions and anything left half done.
- Recent conversation: the latest turns, trimmed to fit.
- Changed files: what the work touched, so the agent can read the diff.
The full canonical transcript is saved next to the brief under ~/.syncport/handoffs/<id>/, so nothing is lost if you want the agent to read more.
Inside your agent#
syncport setup installs a syncport skill into every agent it detects: Codex, Claude Code, Cursor and Hermes. The skill lets the agent run syncport for you when you ask in plain words.
- “hand this off” runs a handoff of the current project and session.
- “continue what I was doing on my desktop” picks up the desktop's latest work, asking the desktop directly when it is online.
- “what was I doing on the laptop?” lists that device's recent sessions.
Install an agent later? Run syncport setup again; it only adds what is missing.
Background daemon#
Setup installs a small per-user background service. It does three things:
- Heartbeat. Reports that this device is online, its syncport and agent versions, and the Git tips of your projects, so the other side sends only the objects it is missing.
- Session index. Publishes the list of agent sessions on this device, with titles sealed for your devices.
- Requests. Answers remote requests from your other devices.
The daemon never applies incoming work to a project by itself. Work only changes when you run continue or ask your agent to.
Approving devices#
Each device has its own keys, created on first run: an X25519 key for decryption and an Ed25519 key for signatures. A device joins your account only after you approve it in the browser.
- Run
syncport setup. It prints a code such asK7QXM-4TPRW, a key fingerprint such as3F9A-12C4-77E0-B5D1and a link tosyncport.live/app/connect. - Sign in with Google in the browser that opens.
- Compare the code and the fingerprint on the page with the ones in your terminal. Approve only if both match exactly. You can rename the device here.
Codes expire after ten minutes. Rename or remove devices on the dashboard. Removing a device revokes its token immediately, removes its sessions from the index and fails its open requests.
Direct SSH#
If you prefer that nothing is stored in between, pair two devices over SSH and hand off directly. Direct handoffs use the same packages, encryption and signatures, sent over your own SSH instead of the relay.
syncport init # once per project; commit .syncport.yaml
syncport device add laptop --host me@laptop.local --install
syncport sync laptop # push or pull, whichever is neededsync compares both sides with their last direct handoff and moves work away from the side that has not changed. If both changed, it stops and asks you to choose with syncport push laptop --replace or syncport pull laptop --replace. --install copies the matching syncport binary to the other device, so a server needs no Node.js.
SSH uses your normal setup: ~/.ssh/config, keys and known_hosts. Private keys are never copied and host-key checks stay on. syncport doctor laptop checks the whole chain.
Tailscale#
For direct handoffs between networks, Tailscale gives each device a stable private address and a MagicDNS name. syncport only reads tailscale status to find devices by name and never changes your Tailscale settings.
syncport tailscale # list tailnet devices and their state
syncport device add laptop --tailscale me@laptop --install
syncport sync laptopWith Tailscale SSH, set the rule for your own devices to "action": "accept". In “check” mode Tailscale periodically asks for a browser login, and a non-interactive handoff would wait for it. Macs cannot be Tailscale SSH servers with the App Store app; use Remote Login on them.
What moves and what stays#
| What | How it travels |
|---|---|
| Git history | Only the objects the other side is missing. No Git hosting needed. |
| Staged and unstaged work | Kept separate, including binary files and executable bits. |
| Untracked files | Included, subject to exclude. |
| Ignored files | Included, except output you can rebuild such as node_modules, build folders and caches. |
Secrets such as .env | Included, encrypted separately and never written into a Git object. |
| Agent sessions | Native Codex and Claude Code histories, Cursor CLI chats and Hermes sessions, plus canonical transcripts. Paths are remapped; content is not rewritten. |
| Agent project state | Claude Code memory and project settings, and Codex project trust. |
| Stays on each device | node_modules and other rebuildable output, credentials and private keys (.ssh/, id_ed25519, auth.json), Git config, hooks, remotes, stashes, agent logins and running processes. |
syncport inspect previews what travels from the current project, what stays and why.
The relay#
The relay at api.syncport.live stores and forwards encrypted packages. It exists so the receiving device can be off when you hand off, and so devices can find each other without opening ports.
| The relay sees | Only your devices see |
|---|---|
| Device names, platforms and versions | Source code, diffs and Git objects |
| Agent names and versions | .env and other secrets |
| Project names and IDs | Agent transcripts and handoff briefs |
| Package sizes and hashes | Session titles, working directories and models |
| Times and message counts | Local paths and branch names |
Retention and limits#
- Handoffs are kept for 14 days, and at most the newest 10 per project.
- Each account has 10 GiB of handoff storage. The dashboard shows how much you use.
- Delete a handoff from the dashboard at any time. Deleting your account removes your devices, the session index and every stored package.
Security model#
- Keys per device. Each device keeps its private keys in
~/.syncport/identity/keys.json. They never leave it. - Approved by you. A device joins your account only when you approve it in the browser after comparing its code and key fingerprint.
- Encrypted for each recipient. A handoff is encrypted with age, with one recipient stanza per device, and signed by the sending device with Ed25519.
- Pinned identities. The first time a device sees another device of your account, it pins that device's keys in
~/.syncport/known_devices.json. A changed key for a known device is rejected. - Checked before anything is written. The receiver verifies the signed manifest first, then hashes, project identity and paths, before touching the workspace.
- Sealed metadata. Session titles, working directories and the handoff headline are encrypted for your devices. The relay cannot read them, and neither can the web dashboard.
- Revocable. Removing a device revokes its token at once. Device tokens expire after a year.
Where state lives#
| Location | Contents |
|---|---|
.syncport.yaml | Project ID and selection policy. Commit it. |
~/.syncport/account.json | Relay origin, device token, device ID and account email (0600). |
~/.syncport/identity/keys.json | This device's private keys. |
~/.syncport/known_devices.json | Pinned keys of your other devices. |
~/.syncport/projects/<id>/ | Project paths, receipts and handoff baselines. |
~/.syncport/handoffs/<id>/ | Received briefs and canonical transcripts. |
Commands#
| Command | Purpose |
|---|---|
setup | Sign in, approve this device, install the daemon and the agent skills. |
handoff | Send this project's work and its latest agent session through the relay. |
continue | Apply the newest handoff here and print how to resume. |
continue --from <device> | Ask an online device for its latest work. |
sessions | Every session on every device and agent. |
inspect | Preview what travels, what stays and why. |
undo | Restore the backup taken before the last change. |
init | Write .syncport.yaml for the current project. |
sync / push / pull <device> | Direct handoff over SSH, without the relay. |
device add / list / path / install / remove | Pair SSH devices and manage where projects live on them. |
tailscale | List tailnet devices to pair. |
doctor [device] | Check Git, agents, the relay connection or an SSH device. |
completion, version | Shell completion and build info. |
.syncport.yaml#
syncport init, or your first handoff, writes .syncport.yaml at the project root. Commit it so every device shares the same project ID.
schema_version: 3
project_id: generated-by-syncport
include:
- .syncport.yaml
exclude:
- node_modules
- dist
- .cache
ignored: carry # carry or skip: files Git ignores
secrets: carry # carry or skip: .env and other secrets
agent: auto # auto, codex, claude or none: which native Codex and Claude Code histories travelexcludeworks like.gitignorefor ignored files: a name without a slash matches at any depth, a path with a slash starts at the project root.includecarries a path that would otherwise stay, such as a file inside a rebuildable folder.volatilelists files that travel but never count as a change on their own. By default these are local databases such as*.sqliteand*.db.agent: nonemoves the workspace only, never sessions.
Personal settings, such as your account, devices and where each project lives on them, stay in ~/.syncport and never go into the repository.
Troubleshooting#
Errors name the problem and the next step. Start with syncport doctor.
| You see | What to do |
|---|---|
| The browser says the code expired | Codes last ten minutes. Run syncport setup again for a new one. |
| The fingerprint does not match | Deny the request and run setup again on the device you are holding. Never approve a mismatch. |
| No handoff for this project | Run syncport handoff on the other device first, or continue --from <device> if it is online. |
| The device is offline | Its daemon has not checked in for three minutes. Wake it, or continue from its newest handoff. |
| Signed by an unknown key | A known device's key changed. Remove that device on the dashboard and approve it again if you replaced it. |
| Storage quota reached | Delete old handoffs on the dashboard. Expired ones are removed automatically. |
| Both devices changed (direct sync) | Choose a side with push --replace or pull --replace. A backup is kept and undo restores it. |
| A direct handoff hangs at SSH | Tailscale SSH is in “check” mode. Switch the rule to “accept”. |