swarmfile
swarmfile drives Swarmfile version-control operations against a running engine: commit, checkout, branches, merge, and the rest of the day-to-day project commands. It talks to the engine over its control socket - every subcommand mirrors an operation the tray can do, and every subcommand requires a running engine except completions, which works standalone.
If the engine isn't running, start it (or the tray) first, or use the standalone swarmfile-doctor binary to diagnose connectivity without one.
swarmfile doesn't include a search subcommand - search needs its own hub-authenticated round trip independent of the control socket, so it lives in the standalone swarmfile-search binary instead.
Global flags#
These apply to every subcommand.
| Flag | Description |
|---|---|
--cache-dir <path> | Engine cache dir (where control.json lives). Respects $SWARMFILE_CACHE_DIR. |
--json | Print raw JSON instead of a human-readable summary. |
Version control#
See Version Control and Branches & Merging for the conceptual walkthrough - this section is the flag-by-flag reference.
commit#
Open a changelist (if none is open) and submit it in one step.
| Flag | Description |
|---|---|
-m, --message <String> | Commit message |
swarmfile commit -m "Fix layout regression in the deck"
changelist#
Explicit changelist control (Mode B: staged atomic submit) - for when you want to stage multiple saves before committing them atomically, rather than using commit's one-step flow.
| Subcommand | Flags | Description |
|---|---|---|
open | -m, --message <String> | Open a changelist; subsequent saves stage into it until submit/cancel. |
submit | - | Atomically apply the open changelist. |
cancel | - | Abandon the open changelist. |
status | - | Show whether a changelist is open and what's staged. |
swarmfile changelist open -m "Reorganize asset folders"
# ...edit and save files...
swarmfile changelist status
swarmfile changelist submit
checkout#
Restore a scope (the whole project, a folder, an entry, or a mount-relative path) to a past commit.
| Flag | Description |
|---|---|
--at-seq <N> | Commit sequence number to restore to |
--path <path> | Mount-relative path to restore |
--folder-id | Restore a specific folder by ID |
--entry-id | Restore a specific entry by ID |
changesets list#
The commit log, newest first.
| Flag | Description |
|---|---|
--limit <N> | Default 50 |
--offset <N> | Default 0 |
branch#
Branch management.
| Subcommand | Flags | Description |
|---|---|---|
list | - | Active branches for this mount's project. |
create | <name>, --from <branch>, --from-commit <seq>, --from-tag <tag> | Fork a new branch (defaults to forking from main's live head). At most one of --from-commit/--from-tag may be given - either forks the source as of that past commit instead of its head. --from is optional with either (the target commit/tag already names its branch); giving both is an error if they disagree. |
switch | [name] | Switch which branch this mount works on. Omit name to switch back to main. |
archive | <name> | Soft-archive a branch (main cannot be archived). |
branch switchis hot - no engine restart. It's refused (exit 2) while a changelist is open; submit or cancel it first.branch createnever touches the running mount either, even with--from-commit/--from-tag- creating a branch doesn't change which branch this mount is checked out to.
swarmfile branch create recover --from-commit 482
swarmfile branch create recover --from-tag v1.0
tag#
A project-scoped, immutable named pointer to a commit - "branch from tag" is "branch from commit, resolved via a name." There is no move/repoint subcommand: to repoint a name, delete it and create it again.
| Subcommand | Flags | Description |
|---|---|---|
list | - | Tags for this mount's project. |
create | <name>, --at-seq <seq>, --from <branch> | Tag a commit. --from scopes which branch's commit log --at-seq reads from (default main). Omit --at-seq to tag that branch's CURRENT head instead - the submission-script case: tag what you just committed with no separate lookup of the seq number. Errors if the branch has no commits yet. |
delete | <name> | Delete a tag. Real delete, not soft - a tag has no archived state. |
swarmfile commit -m "final render pass"
swarmfile tag create v1.0
swarmfile tag create v0.9 --at-seq 482
swarmfile tag list
swarmfile tag delete v1.0
merge#
Merge this mount's branch into the branch it was forked from - its parent (usually main, but a branch forked from a non-main branch merges back into that one). The target is implicit: a branch already knows its own parent, so there's no target flag.
| Flag | Description |
|---|---|
--record-conflicts | Record entry_conflicts rows for interactive resolution instead of only listing them on conflict. |
swarmfile branch create feature-x
swarmfile branch switch feature-x
# ...work, commit...
swarmfile branch switch
swarmfile merge --record-conflicts
ec-placement#
Deliberate erasure-coded shard placement across LAN peers.
| Subcommand | Description |
|---|---|
status | Whether this mount currently has deliberate LAN shard placement on. |
enable | Turn on deliberate LAN shard placement. |
disable | Turn off deliberate LAN shard placement. |
ec-placement enable/disablerestart the engine to take effect.
Placement spreads shards across your LAN peers, which are normally found by mDNS. If your network blocks mDNS multicast (common on corporate/VLAN'd networks), the office won't be discovered and every shard falls back to this machine - set lan_from_office (see Engine Config File) so same-office peers count as LAN.
shards <path>#
Where a file's erasure-coded shards live across the office - for each piece, the machines it's assigned to and whether this one holds it. Requires ec-placement enable and a live P2P fabric.
Day-to-day#
status#
This mount's engine, peers, and settings at a glance.
conflicts#
Lists files that are blocked because the same file was changed in two places and the two versions haven't been reconciled yet.
Worth knowing why this command exists: while a file is in that state, writes to it are refused, and an application will report a plain I/O error. That's not Swarmfile being unhelpful - a filesystem has no way to say "someone else changed this too", so there is no error an application could show you instead. This command is where the reason lives.
$ swarmfile conflicts
1 file(s) blocked by an unresolved conflict:
/Projects/plan.dwg
Writes to these files are refused until the conflict is resolved.
Resolve them in the tray, or in the web dashboard.
Resolving is done from the tray or the web dashboard, where you can see both versions and choose which to keep (or keep both).
throttle#
Bandwidth throttling - applies immediately, no restart.
| Subcommand | Flags | Description |
|---|---|---|
status | - | Current throttle settings. |
enable | --download-mbps <N>, --upload-mbps <N> | Turn throttling on. Omit a flag to leave that direction's rate at whatever it already resolves to. |
disable | - | Turn throttling off. |
What the cap covers. Bulk background transfer: uploads, hydrate, and
fetch. A read an application is actually waiting on - opening a file through
the drive - is not throttled, so a cap set to protect the office link can't
turn somebody's file open into a stall. The practical consequence is that a
large hydrate or fetch is bounded by this setting: both are pre-fetches
filling the cache ahead of use, not somebody sitting in front of a spinner.
cache#
Local disk cache size cap - applies immediately, no restart.
| Subcommand | Flags | Description |
|---|---|---|
status | - | Current cache cap and usage. |
set | <gib: u64> | Set the local disk cache cap, in GiB. |
hydrate#
Bulk-materialize a scope of this mount into the local cache, pinned against eviction until released - for a machine that needs to work fully offline, or that wants everything local before a job starts rather than fetching lazily during it.
| Subcommand | Flags | Description |
|---|---|---|
start | [path], --yes | Download and pin every file under path (default: the whole project). Prints a size estimate and a warning if it exceeds your cache cap, and asks for confirmation, unless --yes. Then polls progress until done. |
status | - | Progress of the current (or most recent) hydrate job. |
cancel | - | Cancel a running hydrate job. A no-op if nothing is running. |
release | [path] | Unpin everything hydrated under path (default: the whole project), making it evictable again. |
swarmfile hydrate start ./Projects/shots/seqA --yes
swarmfile hydrate status
swarmfile hydrate release ./Projects/shots/seqA
Pinned blocks are never evicted by the cache's normal LRU sweep, so a large
hydrate can push disk usage past your configured cache cap - hydrate start warns about this up front rather than silently growing past it.
Pins are permanent until you run hydrate release, not time-limited.
fetch#
Pull a byte range of one file into the local cache - including from a version somebody else is still uploading.
On a slow link a large upload takes hours or days, and until it finishes the new version isn't readable at all. This asks the uploading machine to send the part you need first, then downloads it as it lands. What you get back is the time it takes to send that range, not the time it takes to send the file.
| Flag | Description |
|---|---|
--tail <bytes> | Fetch the last N bytes. The common case for media, where the part you need is at the end. Resolved against the in-flight size, which is why it's a flag rather than arithmetic you do yourself - see below. |
--start <bytes> | First byte of the range. Default 0. Conflicts with --tail. |
--len <bytes> | How many bytes from --start. Clamped to the end of the file. Conflicts with --tail. |
--version <which> | newest (default - the in-flight version if there is one, otherwise the committed one), pending (fail unless something is uploading), or head (the committed version only). |
--follow | Track a moving reader instead of fetching a fixed range: keep a buffer ahead of wherever it is and re-target when it seeks. --start becomes the initial position; --len/--tail don't apply. |
# The last 200 MB of a file a colleague is still uploading
swarmfile fetch ./Projects/reel-04.r3d --tail 209715200
# A specific window of the committed version
swarmfile fetch ./Projects/site.rvt --start 0 --len 52428800 --version head
The command polls until every block has landed, printing progress as it goes:
$ swarmfile fetch ./Projects/reel-04.r3d --tail 209715200
19/160 blocks, 25.8 MiB fetched (waiting on the upload)
...
fetch complete: 160/160 blocks, 200.0 MiB now local
"waiting on the upload" is the normal state, not a stall. The blocks you asked for may not exist yet - the other machine is still sending them at its uplink speed - so this command can legitimately run for a long time. It gives up only when nothing is arriving and the hub reports that the upload has stopped, and says so rather than hanging.
Why --tail rather than working out the offset yourself: the size to measure
back from is the in-flight version's, and the file's committed size is
still the old version's. A number you compute locally would measure from the
wrong end of the wrong file.
--version pending fails outright when nothing is uploading, rather than
quietly giving you the committed version. That is deliberate: somebody who
asked for the version in progress and silently received yesterday's bytes has
no way to tell.
Fetched blocks are pinned exactly like hydrate, so they aren't evicted by
the cache's LRU sweep before you open the file - and they are released the
same way, with swarmfile hydrate release <path>. One job runs at a time;
starting a second while one is running is refused rather than queued.
You can also do this without the CLI: reading the file normally through the
mount always works, and gets the committed version. fetch is for the case
where the version you want is still on its way. (A mount can additionally be
opted into opening in-progress uploads directly - see stream_in_flight in
the config reference.)
Following a moving reader#
A fixed range is the right shape for "give me the last 200 MB". It's the wrong
shape for watching a file as it uploads, because a viewer plays forward and
seeks. --follow keeps a buffer ahead of a read position you report as it
moves, and re-targets when it jumps.
| Command | Flags | Description |
|---|---|---|
fetch <path> --follow | --start <bytes>, --version <which> | Start following from --start (default 0). Returns once the job is running; it does not complete on its own. |
fetch-seek | <position: u64> | Report where the reader has got to. The buffer re-targets there immediately. A no-op if nothing is following. |
fetch-status | - | Progress of the current (or last) fetch. The way to watch a --follow job. |
swarmfile fetch ./Projects/reel-04.r3d --follow --version pending
swarmfile fetch-seek 5000000
swarmfile fetch-status
For a follow job, fetch-status reports position, bufferedBytes and
readRateBps rather than a block count. bufferedBytes is the number worth
watching: it counts contiguous bytes from the read position, so it answers
"will this play" - where a block total does not. A hole two blocks ahead stalls
a reader however full the rest of the window is.
Buffer depth is derived from how fast the reader is actually consuming, not
from a fixed byte count: 30 seconds of a 6 Mbit/s proxy and 30 seconds of a
200 Mbit/s master are very different numbers of bytes, and a single figure
would be useless for one of them. Set the seconds with
SWARMFILE_STREAM_BUFFER_SECS (default 30). A seek is not counted as
playback, so scrubbing doesn't inflate the estimate.
A follow job ends when you cancel it, when the upload finishes and the buffer
reaches the end of the file, or after five minutes with no fetch-seek - a
paused viewer reports nothing, and one job runs at a time, so an abandoned
follow would otherwise block every later fetch.
seed#
NAS/seed-node mode.
| Subcommand | Description |
|---|---|
status | Current seed-mode state. |
enable | Turn on seed mode. Requires the Pro plan or above, and is mutually exclusive with Cloud-only mode. |
disable | Turn off seed mode. |
seed enable/disablerestart the engine.
watch / unwatch / watches#
| Command | Description |
|---|---|
watch <path> | Watch a file or folder. |
unwatch <path> | Stop watching a file or folder. |
watches | Everything you're currently watching in this project. |
comment / comments#
| Command | Flags | Description |
|---|---|---|
comment <path> | -m, --message <String> | Post a comment on a file. |
comments <path> | - | List comments on a file. |
presence (alias who)#
Who's currently editing what in this project.
completions <shell>#
Generate a shell completion script. Works without a running engine. Supports bash, zsh, fish, elvish, powershell.
swarmfile completions zsh > ~/.zsh/completions/_swarmfile
doctor#
Connectivity diagnostics - asks the running engine to run its probe suite. For a machine where the engine isn't running, use the standalone swarmfile-doctor binary instead.
log#
The commit log as a compact, colored feed (like git log --oneline).
| Flag | Description |
|---|---|
--limit <N> | Default 20 |
tail#
Watch this project's activity live - comments, watched-file changes, lock conflicts. Ctrl+C to stop.
Headless engines#
api-key#
Project-scoped API keys, for an engine that runs where nobody can complete a browser sign-in - a render-farm node, a CI runner, a build box. A machine holding one authenticates without an interactive OIDC session at all.
| Subcommand | Description |
|---|---|
list | Every key for this mount's org. Never shows key material. |
create <name> [--project <id>] | Mint a key. --project defaults to this mount's own project. |
revoke <id> | Revoke by id (from list). Immediate - the key stops authenticating right away. |
Minting and revoking require an admin or owner role in this mount's org. The same mint/list/revoke lives in the dashboard, under API keys.
createprints the raw secret once. The hub never returns it again - copy it before you scroll. With--jsonthe secret is in the response body'skeyfield, which is what makes it scriptable; treat that output as the credential it is.
A key is walled off to exactly one project. It cannot read or write anything else in the org - not even projects the admin who minted it can see - so a compromised farm node is confined to the project it was built for, and revoking a key stops that one machine without touching anybody's own session.
Using one. Set it as SWARMFILE_API_KEY on the machine, and the engine skips sign-in entirely: no browser, no refresh token, no impersonating whoever happened to mint it. If both SWARMFILE_API_KEY and SWARMFILE_OIDC_REFRESH_TOKEN are set the API key wins, and the engine says so in its log rather than choosing quietly - a leftover personal refresh token on a shared machine is the more likely mistake of the two.
swarmfile api-key create farm-node-12 --project shots
If the mount covers more than one project, --project is required: there is no single obvious answer, and guessing one would scope a credential to the wrong place.
See Deployment Topologies for how this fits with per-node config files and seed nodes.
Scripting against the CLI#
Every subcommand takes the global --json flag and prints the engine's raw response, which is the intended path for automation - commit from CI, hydrate before a render, check conflicts in a pre-flight script. Pair it with an API key and no part of the loop needs a human.
For reacting to changes rather than polling for them, tail streams this project's activity as it happens. There are no outbound webhooks, so an integration that needs to be notified has to hold that connection open or poll on its own schedule.