Command Reference

Every protonfs command, with its synopsis, arguments/options, what it actually does, and worked examples. This page describes behavior; for the frozen contract (exact exit codes, option names, config keys, env vars that will not change without a major version bump) see Stability Promise (M4.1).

Synopsis

protonfs

Sync a local directory tree with Proton Drive.

Usage

protonfs [OPTIONS] COMMAND [ARGS]...

Options

--version

Show the version and exit.

-v, --verbose

Increase console detail (-v..-vvvv).

--progress-inline, --progress-lines

Update progress in place (inline) vs. print each poll on a new line. Default: config (defaults.progress_style), else inline on a TTY.

--event-log, --no-event-log

Write a full debug event log to .protonfs/events.log. Default: config (defaults.event_log), else off.

protonfs is the command group; every operation is a subcommand documented under Subcommands. The global options above (verbosity, progress style, event log) may appear before or after the subcommand.

Examples:

protonfs setup && protonfs push        # first-time setup, then upload
protonfs refresh && protonfs pull       # first pull on a new machine
protonfs status; echo "exit=$?"         # drift check for scripts

Global behavior

Every command that mutates a repo’s index (push, pull, rm, restore, refresh, offload) takes an exclusive, non-blocking advisory lock on .protonfs/lock for its duration, so two protonfs processes never interleave writes to index.json. If another process already holds it, the command fails fast with an instructive message rather than blocking or racing (see Durability and Drift-Resolution Guarantees).

Runtime commands that shell out to proton-drive (everything except config and shell-init) share an error boundary: a Drive/auth failure, a locked keyring, or a held repo lock all surface as a clean one-line error instead of a Python traceback. An auth failure additionally suggests protonfs auth login.

Diagnostics & verbosity

Added in version 1.3.0: Layered -v verbosity, the progress-style flag, and the rotating event log.

Changed in version 1.4.0: Global options became position-independent — they may appear before or after the subcommand (see reorder_argv()).

Every protonfs command accepts three global options, which may appear before or after the subcommand (e.g. protonfs -vv --event-log pull or protonfs pull -vv), that control how much it narrates and where that narration goes. All narration goes to stderr; each command’s result summary stays on stdout, so piping/scripting a command’s output is unaffected by verbosity. A flag, when given, overrides its config key; when unset, the resolved config value is used, falling back to the built-in default (see Stability Promise (M4.1) for the frozen option/config contract).

Verbosity ladder (-v)

-v is repeatable, from -v up to -vvvv. Each level is a superset of the one below it.

Level

Console detail

(none)

Warnings/errors only, plus each command’s normal stdout result summary.

-v

Adds curated phase narration, progress updates throttled to roughly every 30s, and phase durations.

-vv

Adds per-item paths (each file transferred/affected) and sub-steps; progress throttled to roughly every 5s.

-vvv

Adds INFO-level diagnostics; progress throttled to roughly every 1s.

-vvvv

DEBUG from every protonfs module, the proton-drive subprocess’s argv and stderr, and third-party library logging; progress updates continuously (no throttle).

Progress style (--progress-inline / --progress-lines)

Controls how progress updates are rendered on stderr:

  • --progress-inline — one live line, rewritten in place as progress advances. This is the default when stderr is a TTY. An open inline progress line is always closed (a newline written) before any other message, so nothing it printed is overwritten.

  • --progress-lines — every poll gets its own line instead of rewriting in place. Used automatically when stderr is not a TTY (e.g. redirected to a file or a CI log), regardless of the configured/default style.

Default: the defaults.progress_style config key, else inline on a TTY.

Event log (--event-log / --no-event-log)

When enabled, protonfs writes a structured log to .protonfs/events.log, rotating at roughly 5 MB with one backup file (events.log.1). Each line is aligned text: TIMESTAMP LEVEL component message key=value. The event log always records full DEBUG-level detail while enabled, independent of the console -v level chosen for the same run; the proton-drive subprocess’s stderr is included in it only at -vvvv.

.protonfs/events.log is gitignored automatically — new repos get this from the setup template, and existing repos pick it up via a protonfs upgrade migration. protonfs deinit removes it along with the rest of .protonfs/. It is never treated as sync payload: protonfs excludes .protonfs/ from scans entirely.

Default: the defaults.event_log config key, else off.

Configuration

Environment variables

PROTONFS_CONFIG

Overrides the global config file path outright.

Added in version 1.0.0.

PROTONFS_REMOTE_ROOT

Per-key override for remote_root.

Added in version 1.0.0.

PROTONFS_DEVICE_ID

Per-key override for device_id.

Added in version 1.0.0.

PROTONFS_ON_CONFLICT

Per-key override for defaults.on_conflict.

Added in version 1.0.0.

PROTONFS_LOW_IO

Per-key override for defaults.low_io.

Added in version 1.0.0.

PROTONFS_EVENT_LOG

Per-key override for defaults.event_log.

Added in version 1.3.0.

PROTONFS_PROGRESS_STYLE

Per-key override for defaults.progress_style.

Added in version 1.3.0.

Operational & tuning environment variables

These have no config-key equivalent; they tune how protonfs invokes and installs the proton-drive binary and how it bootstraps the keyring. All are part of the frozen contract (see Stability Promise (M4.1)).

PROTONFS_DRIVE_BIN

Path/name of the proton-drive binary to invoke, in place of the default.

Added in version 1.0.0.

PROTONFS_DRIVE_VERSION

Overrides the proton-drive version install-drive installs when --version is not passed.

Added in version 1.0.0.

PROTONFS_DRIVE_SHA512

Explicit SHA-512 to verify a proton-drive download against, required for versions/platforms without a built-in checksum pin.

Added in version 1.0.0.

PROTONFS_LIST_TIMEOUT

Timeout in seconds for a Drive listing call (default 45).

Added in version 1.0.0.

PROTONFS_LIST_RETRIES

Max retries for a Drive listing call (default 4).

Added in version 1.0.0.

PROTONFS_LIST_BACKOFF

Base backoff in seconds between Drive listing retries (default 2).

Added in version 1.0.0.

PROTONFS_LIST_BACKOFF_CAP

Cap in seconds on the Drive listing retry backoff (default 60).

Added in version 1.0.0.

PROTONFS_TRANSFER_TIMEOUT

Timeout in seconds for a Drive upload/download call (default 300).

Added in version 1.0.0.

PROTONFS_TRANSFER_RETRIES

Max retries for a Drive upload/download call (default 4).

Added in version 1.0.0.

PROTONFS_TRANSFER_BACKOFF

Base backoff in seconds between Drive upload/download retries (default 2).

Added in version 1.0.0.

PROTONFS_TRANSFER_BACKOFF_CAP

Cap in seconds on the Drive upload/download retry backoff (default 60).

Added in version 1.0.0.

PROTONFS_KEYRING_PASSWORD

Supplies the password for the protonfs-owned keyring bootstrap, instead of generating one.

Added in version 1.0.0.

PROTONFS_MANIFEST

Per-key override for defaults.manifest.

Added in version 2.1.0.

PROTONFS_NO_MANIFEST

Set (to any truthy value) to switch every remote-manifest read and write off on this host, whatever defaults.manifest says.

Added in version 2.1.0.

PROTONFS_NO_KEYRING_BOOTSTRAP

Set (to any truthy value) to disable protonfs’s Secret Service/keyring bootstrap entirely; the caller is responsible for providing one.

Added in version 1.0.0.

PROTONFS_CREDENTIALS_STORE

Forces which proton-drive credentials store protonfs prepares and uses: keychain (freedesktop Secret Service) or pass (password-store). The default, auto, tries keychain first and falls back to a protonfs-managed pass store only when the Secret Service cannot be made ready on this host (see Credentials store: automatic pass fallback). The resolved choice is written to a per-host sticky file so later commands never read a different (empty) store. Native PROTON_DRIVE_CREDENTIALS_STORE, if already set in the environment, is passed straight through to proton-drive and takes precedence over this variable’s resolution.

Added in version 1.9.0.

Configuration file and keys

remote_root
Type:
str

Proton Drive path that maps to this repo’s ProtonFS root. Overridable via PROTONFS_REMOTE_ROOT.

Added in version 1.0.0.

device_id
Type:
str

Stable identifier for this client device in the index. Overridable via PROTONFS_DEVICE_ID.

Added in version 1.0.0.

defaults.on_conflict
Type:
str
Default:
"skip"

Action when a file is in SyncState conflict. Overridable via PROTONFS_ON_CONFLICT.

Added in version 1.0.0.

defaults.low_io
Type:
bool
Default:
false

Skip hashing unchanged files. Overridable via PROTONFS_LOW_IO.

Added in version 1.0.0.

defaults.event_log
Type:
bool
Default:
false

Enable the structured rotating event log. Overridable via PROTONFS_EVENT_LOG.

Added in version 1.3.0.

defaults.progress_style
Type:
str
Default:
"inline"

Progress display style, inline or lines. Overridable via PROTONFS_PROGRESS_STYLE.

Added in version 1.3.0.

defaults.manifest
Type:
bool
Default:
false

Keep the remote manifest (see verify) current on push and rm. Only an existing manifest is updated; protonfs verify --repair creates one. Set it in the shared config.json so every host maintains it. Overridable via PROTONFS_MANIFEST.

Added in version 2.1.0.

Subcommands

setup

protonfs setup

Install/verify the proton-drive CLI, init .protonfs/, migrate off git-lfs if present.

Usage

protonfs setup [OPTIONS]

Options

--dry-run

Preview the LFS migration without making changes.

--migrate-lfs, --no-migrate-lfs

Force or skip the repo-wide git-LFS migration. Default: migrate only when this directory is the git toplevel, so setting up a subdirectory never migrates the enclosing repo off LFS.

Installs/verifies the proton-drive CLI, initializes .protonfs/ in the current directory (config.json, ignore, a .gitignore that excludes index.json/refresh-state.json, and a .gitattributes that exempts those control files from git-LFS), and migrates the repo off git-LFS if it is tracked. It also creates the configured remote_root on Drive if it does not exist yet, so the first push has somewhere to land without hand-creating folders.

Default: migrate only when the current directory is the git toplevel, so running setup in a subdirectory of a larger repo never migrates the enclosing repo off LFS by surprise.

Examples:

protonfs setup                        # first-time setup in the current directory
protonfs setup --dry-run               # see what setup would do first
protonfs setup --no-migrate-lfs        # set up without touching git-LFS either way

deinit

protonfs deinit

Remove .protonfs/ from this directory: clean teardown of a protonfs root.

Only protonfs’s own bookkeeping under .protonfs/ (config, index, ignore/include, control .gitattributes/.gitignore) is removed – synced payload files, local or remote, are never touched.

Usage

protonfs deinit [OPTIONS]

Options

--dry-run

List what would be removed; delete nothing.

--yes

Skip confirmation prompt.

The inverse of setup: removes every file setup writes under .protonfs/ — the shared config.json, the per-device config.local.json, the index, the resumable-refresh state, ignore/include, and the control .gitattributes/.gitignore — after printing a summary and asking for confirmation. It only ever looks inside .protonfs/: synced payload files, local or remote, are never touched, so deinit is a clean teardown of protonfs’s own bookkeeping and nothing else.

Examples:

protonfs deinit --dry-run    # see exactly which .protonfs/ files would go
protonfs deinit              # remove them after confirming

status

protonfs status

Summarize sync state as a count per state (locally-indexed, local-only, …).

Without --remote nothing is checked on Drive: every count compares the working tree against this machine’s index (what protonfs last recorded). locally-indexed therefore means “matches the index”, not “verified on Drive”; --remote walks Drive and classifies against it too.

Accepts any number of PATHs (e.g. from a shell glob); counts are combined. Exit code, so an unattended caller can branch without parsing the counts: 0 = clean (every file locally-indexed, metadata-only or an LFS pointer stub), 1 = drift present (something to push/pull/prune), 2 = conflict present (conflict: a local change with no remote view to attribute it; both-modified: changed on both sides). Conflict outranks drift when both are present.

Usage

protonfs status [OPTIONS] [PATH]...

Options

--format <fmt>

Output format: the classic state-per-line counts, or one JSON object.

Default:

'plain'

Options:

plain | json

--remote

Walk Drive and classify against it as well as the local index (slower, but catches remote changes that refresh hasn’t seen yet). Without it nothing is checked on Drive: ‘locally-indexed’ means the file matches this machine’s index.

Arguments

PATH

Optional argument(s)

Changed in version 1.1.0: Added --format (plain/json) and multiple PATH pathspecs.

Changed in version 2.0.0: synced renamed to locally-indexed; a file deleted locally is local-deleted rather than remote-only when Drive is not checked; added --remote; the JSON output gained a remote field.

Scans the local tree (optionally scoped to PATH), compares it against the local index, and prints a count per sync state (locally-indexed, local-only, remote-only, metadata-only, conflict, local-modified, remote-modified, both-modified, local-deleted, remote-changed, remote-deleted, lfs-pointer).

By default it does not talk to Drive at all, so every count describes the local files against the index: locally-indexed means “matches what this machine last recorded”, not “verified on Drive”. --remote walks Drive and classifies against it as well — slower, and a failed or throttled walk is an error rather than a silent fall-back to the index-only answer. --format json includes "remote": true|false so a consumer can tell which answer it got.

Exit code: 0 clean, 1 drift present, 2 conflict present (conflict outranks drift) — identical in both formats. See Stability Promise (M4.1) for the exact mapping and Durability and Drift-Resolution Guarantees for how states are classified.

Examples:

protonfs status
protonfs status subdir/
protonfs status; echo "exit=$?"
protonfs status --format json | jq .counts
protonfs status --remote            # classify against a live Drive walk

ls

protonfs ls

List tracked files with their sync state (any number of PATHs).

–dirs summarizes each immediate subdirectory (counts by state, cumulative local/indexed sizes) instead of listing thousands of files; –state filters to the states you care about; –format plain|json makes the output scriptable; –visual treemap|waffle draws a storage-usage chart of those directories.

Usage

protonfs ls [OPTIONS] [PATH]...

Options

--remote

Force a live Drive listing to compute state instead of relying on the local index alone (slower, but catches remote changes that refresh hasn’t seen yet).

--trash

List /trash instead of the working tree (name and type only; no sync-state column, since trashed items aren’t tracked in the index).

--dirs

Aggregate per immediate subdirectory: file counts by state plus cumulative local/indexed sizes, instead of listing every file.

--state <states>

Only show files in this sync state (repeatable).

Options:

locally-indexed | local-only | remote-only | metadata-only | conflict | local-modified | remote-modified | both-modified | local-deleted | remote-changed | remote-deleted | lfs-pointer

--format <fmt>

Output format: rich table, tab-separated lines, or JSON.

Default:

'table'

Options:

table | plain | json

--visual <visual>

Draw a per-directory storage-usage chart (by each dir’s apparent footprint) instead of the listing. Implies directory aggregation; terminal-only (not for –format/–trash).

Options:

treemap | waffle

Arguments

PATH

Optional argument(s)

Lists tracked files with their sync state, as a table of path / state.

Added in version 1.1.0: --dirs per-directory aggregation with sizes, the --state filter, and --format on ls/status.

Changed in version 1.2.0: --visual treemap/waffle storage charts.

--dirs aggregates per immediate subdirectory instead of listing every file: one row per directory with its file count, cumulative local size (bytes on disk; 0 for fully offloaded dirs), cumulative indexed size (what the index records — the remote-side size), and a per-state count summary. This is the storage-breakdown view: protonfs ls --dirs on a large tree answers “which directories are taking space locally vs on Drive” without printing 10,000 metadata-only lines. --state STATE applies before --dirs aggregation, so the two compose. --state synced is still accepted as a deprecated alias of locally-indexed (with a warning on stderr) and will be removed in the next major release. remote-only is only ever reported with --remote; without it a file deleted locally is local-deleted. The --dirs JSON/columns also carry an apparent_bytes field: each directory’s true footprint, taking per file whichever of the local/indexed size is known (they agree when locally-indexed, local for a not-yet-pushed file, indexed for an offloaded one). --visual {treemap,waffle} draws a per-directory storage-usage chart instead of the listing, sized by that apparent_bytes footprint so a fresh local-only tree and a fully-offloaded tree both chart correctly. treemap gives nested rectangles whose areas are proportional to size (squarified for readability); waffle gives a proportional grid of cells. Both print a colour legend with each directory’s size and percentage. This is a terminal-only view — it cannot be combined with --format plain/json or --trash (both raise a usage error).

Examples:

protonfs ls
protonfs ls --remote subdir/
protonfs ls --trash
protonfs ls --dirs                       # per-directory storage breakdown
protonfs ls --remote --state remote-only --format plain | cut -f1
protonfs ls sim/ --dirs --format json    # scriptable per-dir sizes/counts
protonfs ls --visual treemap             # squarified storage treemap
protonfs ls sim/ --visual waffle         # proportional waffle chart of sim/

push

protonfs push

Upload local-only/changed files to Drive (any number of PATHs).

Each PATH may be a directory, a single file, or a glob pattern. A quoted pattern ('mload*', 'mload*/*.ev') is expanded by protonfs itself against the local tree at run time; an unquoted one is expanded by the shell first, as before. A PATH that does not exist locally is a usage error (exit 2): push uploads local files, so a missing path can only be a typo – this is distinct from the exit 1 used for transfer failures.

A pattern matching nothing is reported and skipped (it never widens to the whole repo); pass --strict to make that an error instead.

Changed in version 1.5.2: PATH may now name a single file (previously only directories were scanned, so a file/glob pathspec silently uploaded nothing). A nonexistent PATH is now a usage error instead of a silent no-op, and an empty push prints nothing to push.

Changed in version 1.11.0: A quoted PATH glob pattern is now expanded by protonfs against the local tree, rather than depending on the shell to expand it first (#131) – so a scheduled job can carry a pattern that keeps matching as new directories appear. Added --strict.

Usage

protonfs push [OPTIONS] [PATH]...

Options

--resolve <resolve>

How to reconcile a file that changed on BOTH sides since the last sync: remote=keep the remote copy (skip the upload), local=replace the remote with your local copy (the existing remote file is moved to the trash), both=upload your local copy alongside the remote. A file changed only locally needs none of these: it is uploaded as a new revision of the remote file. The proton-drive strategy names merge|keep-both|replace|skip are also accepted (replace=local, skip=remote, keep-both=both; merge adds a revision regardless of what the remote holds).

Options:

remote | local | both | merge | keep-both | replace | skip

--dry-run

Report what would be pushed without transferring anything.

--strict

Fail (exit 1) if a PATH pattern matches nothing, instead of skipping it.

Arguments

PATH

Optional argument(s)

Changed in version 1.1.0: Interactive batch progress on stderr; accepts multiple PATH pathspecs.

Changed in version 1.11.0: A quoted PATH glob pattern is expanded by protonfs itself against the local tree, instead of relying on the shell to expand it first. Added --strict.

Uploads local-only and locally-modified files under PATH (or the whole repo) to Drive. When run interactively (stderr is a terminal), a running push: N/M file(s) progress line is shown on stderr after each uploaded batch; scripts and redirected output see only the frozen summary on stdout.

PATH may be a directory, a single file, or a glob pattern. An unquoted pattern is expanded by your shell before protonfs sees it, as always; a quoted one ('mload*', 'mload*/*.ev') reaches protonfs intact and is expanded by protonfs against the local tree at run time. The two are equivalent interactively, but only the quoted form survives into a scheduled job — see schedule — where the pattern must be re-expanded on each run so it keeps matching as new directories appear. A pattern segment never crosses /, matching shell semantics.

A pattern that matches nothing is reported and skipped, and never widens to the whole repo:

pattern(s) matched nothing, skipped (nothing to push): 'mload*' -- pass --strict to make this an error

Pass --strict to make that an error (exit 1) instead. --strict fails before anything is transferred, so a run is all-or-nothing rather than partial. Without --resolve, a genuine remote conflict is reported as a named per-file failure rather than silently resolved or skipped. A file that changed only locally since this machine last pushed it is not a conflict: it is uploaded as a new revision of the existing Drive file, so Drive’s version history keeps the earlier copy. --resolve local (replace) instead moves the existing remote file to the trash and uploads a new one in its place. Every batch is re-verified against a live remote listing after upload (matching each file’s plaintext claimedSize) before it is recorded in the index — proton-drive can report a transfer as successful when it did not actually land; an unverified file is left unindexed and retried on the next push instead of being recorded as delivered. A file that is an un-smudged git-LFS pointer stub is never pushed, even if misclassified upstream, because that would overwrite real Drive content with a 131-byte placeholder. See Durability and Drift-Resolution Guarantees for the full mechanism.

Progress is saved after each directory group, so an interrupted push resumes rather than restarting.

Examples:

protonfs push                         # everything in scope that is new/changed
protonfs push subdir/ --resolve replace
protonfs push --dry-run

pull

protonfs pull

Download remote-only/changed files from Drive (any number of PATHs).

Each PATH may be a directory, a single file, or a glob pattern. A quoted pattern ('mload*', 'mload*/*.ev') is expanded by protonfs itself, against the paths the INDEX knows about – not the local filesystem, because pull’s whole purpose is fetching files that are absent locally (an offloaded file has no local presence to glob). Run refresh first on a repo whose index has not seen those files yet.

A pattern matching nothing is reported and skipped (it never widens to the whole repo); pass --strict to make that an error instead.

Diverged files (edited locally AND changed on the remote since the last sync) are left untouched unless you pass –resolve; they are reported and pull exits non-zero.

Changed in version 1.11.0: A quoted PATH glob pattern is now expanded by protonfs against the index, rather than depending on the shell to expand it first (#131) – so a scheduled job can carry a pattern that keeps matching as new runs appear, and so a pattern can name offloaded files that no filesystem glob could find. Added --strict.

Changed in version 2.1.0: With an empty index, the index is seeded from the remote manifest when the root has one, instead of stopping with “run refresh first” (#146).

Usage

protonfs pull [OPTIONS] [PATH]...

Options

--resolve <resolve>

How to reconcile a file that changed on BOTH sides since the last sync: remote=overwrite local, local=keep local (stays queued for push), both=fetch the remote copy under a .remote suffix for a manual merge. replace is an alias for remote (replace the local copy). Without this, pull leaves diverged files untouched and reports them.

Options:

remote | local | both | replace

--dry-run

Preview transfers and unresolved conflicts without downloading anything.

--refresh

Discover remote files (seed the index) before pulling.

--strict

Fail (exit 1) if a PATH pattern matches nothing, instead of skipping it.

Arguments

PATH

Optional argument(s)

Changed in version 1.1.0: Interactive batch progress on stderr; accepts multiple PATH pathspecs.

Changed in version 1.11.0: A quoted PATH glob pattern is expanded by protonfs itself against the index, instead of relying on the shell to expand it first. Added --strict.

Downloads remote-only and (with --resolve) remote-modified files under PATH (or the whole repo). When run interactively (stderr is a terminal), a running pull: N/M file(s) progress line is shown on stderr after each transferred batch; scripts and redirected output see only the frozen summary on stdout.

PATH accepts a glob pattern on the same terms as push, with one important difference: pull expands a pattern against the paths the index knows about, not the local filesystem. That is deliberate — pull exists to fetch files that are absent locally, and an offloaded file has no local presence for a filesystem glob to find, so matching on disk would silently miss exactly the files you asked for. The practical consequence is that a pattern only matches what the index has already seen: run refresh first on a repo whose index does not yet know about those files (pull --refresh 'mload*' on a brand-new repo expands the pattern before the refresh seeds the index, so it matches nothing on that first run).

A pattern that matches nothing is reported and skipped, never widening to the whole repo; --strict makes it an error (exit 1) instead, failing before anything is transferred.

This is what makes the small-files case schedulable: 'mload*/*.ev' pulls a few megabytes of time-series data across every matching run, while leaving the much larger dumps in those same directories offloaded.

A file edited locally and changed on the remote since the last sync (a divergence) is left untouched by a bare pull — it is reported and the command exits non-zero, so a local edit is never silently overwritten. Choose a side with --resolve:

  • remote — overwrite the local copy with the remote one.

  • local — keep the local copy; it stays queued for the next push.

  • both — fetch the remote copy alongside the local one under a .remote suffix (untracked) for a manual merge.

--refresh seeds the index from a fresh remote listing before pulling (metadata only, no download) — useful on a machine whose index doesn’t yet know everything already on Drive. Without an index yet, plain pull refuses to run and tells you to run refresh first (or pass --refresh).

Examples:

protonfs refresh && protonfs pull       # typical first pull on a new machine
protonfs pull --refresh                 # equivalent, one command
protonfs pull path/to/file --resolve remote
protonfs pull --resolve both            # fetch remote copies as *.remote for merging

offload

protonfs offload

Delete local bytes of protonfs-tracked files confirmed present on Drive.

Accepts any number of PATHs (e.g. from a shell glob). The inverse of pull: reclaims local disk space while leaving the Drive copy intact. Reversible – a later pull restores the file in full. By default every file is re-verified against a live remote listing (not just the index) before its local copy is deleted; pass –no-verify to skip that check.

Usage

protonfs offload [OPTIONS] [PATH]...

Options

--no-verify

Skip re-verifying files against the remote before deleting local bytes (unsafe).

--dry-run

Preview what would be offloaded; delete nothing.

--yes

Skip confirmation prompt.

Arguments

PATH

Optional argument(s)

Deletes the local bytes of protonfs-tracked files already confirmed present on Drive, reclaiming disk space while leaving the index entry as local_state=metadata-only — a later pull restores the file in full. This is the inverse of pull; it never touches the remote copy.

Before deleting anything, every candidate is (a) checked for unsynced local edits — a file whose live content hash differs from what the index last recorded is never offloaded, verify or not, since offloading it would destroy the only copy of that edit — and (b) by default, re-verified against a live remote listing (not just the index), requiring the remote’s plaintext claimedSize to match the local file’s byte size. A file that fails either check is left alone and reported (skipped_modified / skipped_unverified); this is not treated as command failure. --no-verify skips the live remote re-verification (the unsynced-edit guard in (a) always still applies) — unsafe if the remote could have changed since the index was last updated.

Examples:

protonfs offload subdir/                  # prompts for confirmation
protonfs offload --dry-run
protonfs offload --yes --no-verify         # unsafe: trust the index alone

rm

protonfs rm

Remove files/directories from Drive (trash by default, -f for permanent).

Usage

protonfs rm [OPTIONS] PATH...

Options

-r, --recursive
-f, --force

Permanently delete (trash, then delete).

--yes

Skip confirmation prompt.

Arguments

PATH

Required argument(s)

Trashes PATH on Drive (reversible via restore). Requires -r/ --recursive for a directory. The command removes the matching index entries locally regardless of whether the permanent-delete step below runs.

-f/--force additionally attempts to permanently delete the trashed node after trashing it. proton-drive addresses a trashed node only by /trash/<basename> (no working UID addressing), so when two or more trashed items share a basename, protonfs cannot safely tell which is yours: it leaves the item trashed (still reversible) and reports the ambiguity rather than guessing. See Durability and Drift-Resolution Guarantees for the exact boundary.

Examples:

protonfs rm old-dump.ev
protonfs rm -r stale-dir/ --yes
protonfs rm -f duplicate.tmp             # trash, then attempt permanent delete

restore

protonfs restore

Restore trashed files/directories on Drive.

Usage

protonfs restore [OPTIONS] PATH...

Arguments

PATH

Required argument(s)

Restores a previously trashed file/directory on Drive by its original path. On proton-drive versions that reject original-path restore (0.5.0+), protonfs falls back to resolving the trashed entry by name — and refuses to act (raising an error naming the ambiguity) rather than guess when more than one trashed item shares that name under a different original parent. See the boundary spelled out in Durability and Drift-Resolution Guarantees and in DriveClient.restore’s docstring (src/protonfs/drive.py).

Examples:

protonfs restore old-dump.ev
protonfs restore stale-dir/

refresh

protonfs refresh

Discover remote files and seed the local index (any number of PATHs).

Usage

protonfs refresh [OPTIONS] [PATH]...

Options

--prune

Drop index entries for files deleted on the remote.

Arguments

PATH

Optional argument(s)

Walks Drive under the configured remote_root (or PATH within it) and seeds the local index with metadata-only entries for anything found there that this machine’s index doesn’t already know about. This is the cross-client primitive: a fresh machine (or one that missed files another client pushed) becomes aware of everything already on Drive without downloading any content, so it will not re-upload what’s already there. It also reports files that changed or were deleted on the remote since they were last seen.

The walk is resumable: progress (both the seeded entries and the walk’s own frontier) is saved incrementally, so a run interrupted by an API throttle picks up where it left off on the next invocation instead of restarting from the root. Change/deletion detection only runs after a complete pass, though — a resumed partial pass seeds but does not yet report changes/deletions.

Examples:

protonfs refresh                      # seed everything not yet known
protonfs refresh subdir/ --prune       # scope to a subtree, drop remote-deleted entries

Changed in version 2.1.0: When the repo maintains a remote manifest (defaults.manifest), a complete whole-root pass records the manifest generation it reconciled against. Nothing under the remote .protonfs/ directory is ever seeded.

verify

protonfs verify

Check the remote manifest against a full listing of the remote.

The manifest (.protonfs/manifest.json under the remote root) records every file protonfs verified on Drive, so a host can read what is there without walking the tree. It is a cache: this command walks the whole remote and reports entries Drive contradicts (missing, or a different size/sha1) and files the manifest does not list. –repair rewrites the manifest from that listing, and is how one is first created.

Exit code: 0 when every manifest entry matches Drive (or there is no manifest, or –repair rewrote it); 1 when entries are missing or differ, or on a Drive error.

Usage

protonfs verify [OPTIONS]

Options

--repair

Rewrite the remote manifest to match a full listing of the remote (creating it if the root has none).

Added in version 2.1.0.

The remote manifest is one JSON object per synced root, at <remote_root>/.protonfs/manifest.json. It records every file protonfs uploaded (or adopted) and verified on Drive: plaintext size, sha256, sha1, and the uid of the Drive revision that was verified. A generation counter goes up on every write, and each write is uploaded as a new revision of the manifest itself, so Drive’s version history keeps every earlier one.

It is a cache, not an authority:

  • Entries are written only after the upload they describe was verified, so the manifest can lag Drive but never runs ahead of it. A crash, or a host running an older protonfs, leaves it behind, never ahead.

  • It is only ever created by verify --repair from a full listing, so it starts complete. push and rm keep an existing manifest current when defaults.manifest is on. They never start one mid-history, because such a manifest would look complete while missing everything uploaded before it.

  • Nothing destructive trusts it. offload always verifies against a live listing and never reads the manifest.

  • Changes made outside protonfs (the web UI, another client) are invisible to it until something walks the remote. verify is that walk.

verify reports:

  • entries the manifest lists that Drive lacks, or holds at a different size or sha1 (faults: exit 1);

  • files on Drive the manifest does not list (tolerated, since the manifest may lag);

  • entries whose content matches but whose Drive revision has moved on;

  • files Drive listed without a plaintext size, which cannot be compared.

It also says which manifest generation this machine’s index was last reconciled with. --repair rewrites the manifest to match the listing. It keeps a sha256 only where this machine’s index, or the manifest itself, recorded one for exactly that content; otherwise the sha256 is left unknown rather than guessed. It leaves out any file Drive lists without a plaintext size.

Readers: pull on an empty index (a fresh clone) seeds the index from the manifest instead of stopping with “run refresh first”, and says that files the manifest does not list are not included. When the repo maintains a manifest, pull also notes when the manifest has changed since this index was reconciled, so another host has pushed files this index may not list yet.

Enabling it on an existing repo:

protonfs verify --repair                          # build it from a full listing
protonfs config set defaults.manifest true        # keep it current on push/rm
git add .protonfs/config.json && git commit -m "maintain the protonfs manifest"
protonfs refresh                                  # on each host: record its generation

Set PROTONFS_NO_MANIFEST on a host to switch every manifest read and write off there, whatever the repo config says.

install-drive

protonfs install-drive

Download and verify the official proton-drive CLI binary.

Usage

protonfs install-drive [OPTIONS]

Options

--version <version>

proton-drive version to install (default: pinned).

--skip-keyring

Do not prepare the OS keyring after installing.

Downloads the official proton-drive CLI binary for the current platform (linux-x64 requires AVX2, linux-arm64, macOS x64/arm64), verifies its SHA-512 against a pinned checksum before installing it (never installs an unverified binary), and by default also prepares the OS keyring proton-drive will use to store its session — done here, not at first login, so a keyring failure surfaces before a browser sign-in is thrown away.

Examples:

protonfs install-drive
protonfs install-drive --version 0.5.0
protonfs install-drive --skip-keyring

upgrade

protonfs upgrade

Upgrade proton-drive to the highest supported version + migrate repo state.

A protonfs release only ever upgrades proton-drive to its own highest supported version; a newer upstream release requires a newer protonfs (reported, never installed). Inside a protonfs root, pending repo-state migrations run too.

Usage

protonfs upgrade [OPTIONS]

Options

--check

Report what would happen; change nothing. Exit 0 if fully current, 1 if an upgrade or migration is available.

--drive-only

Only the proton-drive binary; skip migrations.

--repo-only

Only repo-state migrations; skip the binary.

Upgrades the installed proton-drive binary to the highest version this protonfs release supports (SHA-512-verified before an atomic swap; a newer upstream release is reported but never installed), verifies the session survived the swap, and – inside a protonfs root – runs any pending repo-state migrations. See Upgrading for the full upgrade story.

Examples:

protonfs upgrade --check     # what would happen?
protonfs upgrade             # binary + migrations
protonfs upgrade --repo-only # just bring .protonfs/ current

doctor

protonfs doctor

Check this host can run proton-drive (binary, session bus, OS keyring).

Usage

protonfs doctor [OPTIONS]

Options

--fix

Repair what protonfs can (bootstrap the keyring).

Checks that this host can actually run proton-drive: the binary is present and runnable, and on Linux, that a D-Bus session bus and a usable (unlocked) Secret Service keyring are reachable. Written for headless hosts (SSH, no desktop), where a graphical login’s sealed login.keyring is the most common silent failure.

Examples:

protonfs doctor
protonfs doctor --fix

shell-init

protonfs shell-init

Print shell exports so proton-drive run by hand sees the same keyring.

Usage

protonfs shell-init [OPTIONS]

Prints export VAR=value lines so that running the proton-drive binary by hand (outside of protonfs) sees the same session bus/keyring environment protonfs sets up for itself. Every protonfs command does this internally; this is only needed for manual proton-drive invocations.

Example:

eval "$(protonfs shell-init)"
proton-drive filesystem list /my-files

completions

protonfs completions

Print or install shell completion (bash|zsh|fish).

Command names, per-subcommand options, and the global flags (in any position) all complete.

Usage

protonfs completions [OPTIONS] {bash|zsh|fish}

Options

--install

Install the completion script (idempotent).

--uninstall

Remove the installed completion script.

Arguments

SHELL

Required argument

Prints, installs, or removes shell completion for bash, zsh, or fish. With --install it writes the generated script and wires it into your shell config (idempotent, marker-delimited); --uninstall removes it. Installed completions are refreshed automatically by protonfs upgrade. Global flags complete after a subcommand too, matching the position-independent argv handling.

Added in version 1.5.0.

Examples:

protonfs completions bash              # print the script to stdout
protonfs completions zsh --install     # install + wire into ~/.zshrc
protonfs completions fish --uninstall

schedule

protonfs schedule

Manage scheduled push/pull cron jobs for this repo.

Bare protonfs schedule lists the jobs (it never installs implicitly). --add installs a job (needs a cadence: --every/--cron/--at) and prints its id; --uninstall <id> (or -U, accepting the id or a --list index) removes one, --uninstall --all removes them all. Each job runs under flock with an absolute proton-drive path and tuned timeouts, logging to .protonfs/schedule/<id>.log.

--path takes a subtree or a glob pattern. A pattern is stored (and passed to push/pull) unexpanded, so protonfs re-expands it on every run – one job can cover a whole family of runs and keeps matching as new ones appear:

protonfs schedule --add --every daily --command pull --path 'mload*/*.ev'

Added in version 1.8.0.

Changed in version 1.11.0: --path accepts a glob pattern, re-expanded at run time (#131). Added --strict.

Usage

protonfs schedule [OPTIONS]

Options

--list

List this machine’s scheduled jobs.

--add

Install a new job from the options below.

-U, --uninstall <ID>

Remove the job with this id (or –list index). Use –all to remove every job.

--all

Remove every scheduled job.

--every <SPEC>

Cadence: hourly | daily | weekly | <N>h | <N>m.

--cron <EXPR>

Raw 5-field cron expression.

--at <HOURS>

Run daily at these hours (0-23, comma-separated).

--command <command>

What the job runs (sync = pull then push).

Default:

'push'

Options:

push | pull | sync

--path <PATHSPEC>

Scope the job to a subtree, or to a quoted glob pattern that protonfs re-expands on every run (see the examples below).

--resolve <sched_resolve>

Conflict strategy passed to push/pull.

--strict

Pass –strict to push/pull, so a run whose –path pattern matches nothing fails.

--label <label>

Human label shown in –list.

--json

With –list, emit JSON.

Installs, lists, and removes cron jobs that run push/ pull on a schedule. Bare protonfs schedule lists this machine’s jobs (it never installs implicitly); --add with a cadence (--every hourly|daily|weekly|<N>h|<N>m, or a raw --cron expression, or --at hours) installs one and prints a short id; --uninstall <id> (-U, also accepting a --list index) removes it, and --all removes them all.

Each job runs a generated wrapper under flock (no overlapping runs), with an absolute proton-drive path (cron has no useful PATH) and tuned list/transfer timeouts, logging to .protonfs/schedule/<id>.log. Jobs are recorded per-device in .protonfs/schedule.local.json (gitignored). --command sync runs pull then push.

Added in version 1.8.0.

Changed in version 1.11.0: --path accepts a glob pattern, re-expanded on every run. Added --strict.

Scoping a job with a pattern

--path takes a subtree or a glob pattern. The pattern is stored unexpanded and written into the wrapper quoted, so cron’s shell leaves it alone and protonfs expands it fresh on every run. That is the difference that matters for scheduling: a shell glob is expanded once, when you install the job, and is frozen from then on — a pattern keeps matching as new runs appear, with no need to reinstall the job.

This makes two otherwise inexpressible setups routine — pulling one family of runs while leaving the rest deliberately offloaded, and pulling only the small time-series files out of directories whose dumps are far too large to want locally:

protonfs schedule --add --every daily --command pull --path 'mload*'
protonfs schedule --add --every daily --command pull --path 'mload*/*.ev'

Note the quotes: without them your shell expands the pattern before protonfs sees it, and you get a job frozen to whatever matched at install time (the older behaviour).

By default a run whose pattern matches nothing is reported in the job log and treated as a no-op, which is usually what you want — a pattern for runs that do not exist yet should not fail nightly. Pass --strict when a job’s pattern is expected to always match something, and a run that matches nothing should fail loudly instead:

$ protonfs schedule --add --every daily --command pull --path 'mload*' --strict

Examples:

protonfs schedule --add --every daily --at 1,3,5   # nightly at 01/03/05h
protonfs schedule --add --cron "0 */6 * * *" --command sync
protonfs schedule --add --every daily --command pull --path 'mload*/*.ev'
protonfs schedule --list
protonfs schedule --uninstall a1d3ae
protonfs schedule --all                            # remove every job

auth

protonfs auth

Authenticate the proton-drive CLI: login | logout | status.

Usage

protonfs auth [OPTIONS] {login|logout|status}

Arguments

ACTION

Required argument

  • login / logout — passthrough to proton-drive auth <action> with inherited stdio, so an interactive login URL/prompt reaches your terminal directly. Exit code is whatever proton-drive returns.

  • status — checks for a valid session directly (without invoking proton-drive), printing authenticated or a reminder to log in. Exit 0 if authenticated, 1 otherwise.

Examples:

protonfs auth login
protonfs auth status
protonfs auth logout

trash

protonfs trash

Inspect and empty Proton Drive’s trash (#70).

Usage

protonfs trash [OPTIONS] COMMAND [ARGS]...

Commands

empty

Permanently empty /trash for the whole…

list

List /trash: name, original parent…

Groups the /trash inspection and account-wide emptying operations.

trash list

protonfs trash list

List /trash: name, original parent (best-effort), same-name duplicate count.

A nonzero duplicate count is the ambiguity restore refuses to resolve on its own (#56): proton-drive resolves /trash paths by name, first match wins.

Usage

protonfs trash list [OPTIONS]

Lists every item currently in /trash: its name, its original parent (resolved on a best-effort basis — shown as ? when proton-drive can’t resolve it), and how many other trashed items share the same name. A nonzero duplicate count is exactly the ambiguity restore can refuse to resolve on its own (#56): proton- drive resolves /trash paths by name, first match wins, so same-named entries can silently block a restore or shadow one another.

Examples:

protonfs trash list

trash empty

protonfs trash empty

Permanently empty /trash for the whole account (irreversible, account-global).

Requires typing an exact confirmation phrase unless –yes is passed. This is NOT scoped to this repo’s remote_root – it empties every trashed item on the account.

Usage

protonfs trash empty [OPTIONS]

Options

--yes

Skip the typed confirmation prompt.

Permanently empties /trash for the whole Proton Drive account by calling proton-drive filesystem empty-trash. This is irreversible and not scoped to this repo’s remote_root — it deletes every trashed item on the account, including ones unrelated to this repo. Without --yes, the command prints that warning and requires typing an exact confirmation phrase; anything else aborts without emptying trash.

Deliberately out of scope: permanently deleting a single trashed item by UID. proton-drive does not accept node UIDs for /trash paths (see rm’s duplicate-basename limitation above and #56’s analysis), so there is no safe way to target one item there — use trash list to find and resolve duplicates via the Drive web UI, or trash empty to clear everything.

Examples:

protonfs trash list                     # see what's there and any duplicates
protonfs trash empty                    # prompts for typed confirmation
protonfs trash empty --yes              # scripts / non-interactive use

config

protonfs config

Get/set protonfs config (#21): env > local > shared > global > built-in default.

Usage

protonfs config [OPTIONS] COMMAND [ARGS]...

Commands

get

Print the RESOLVED value of KEY (e.g.

set

Set KEY = VALUE.

Reads or writes protonfs’s layered configuration. Known keys: remote_root, device_id, defaults.on_conflict, defaults.low_io, defaults.event_log, defaults.progress_style, defaults.manifest (each defined in the Configuration section above).

config get

protonfs config get

Print the RESOLVED value of KEY (e.g. remote_root, defaults.low_io).

Usage

protonfs config get [OPTIONS] KEY

Arguments

KEY

Required argument

Always prints the fully resolved value across every layer (env var > per-device local config > shared per-repo config > global user config > built-in default) — see Stability Promise (M4.1) for the complete precedence list and the environment variables that can override each key.

Examples:

protonfs config get remote_root

config set

protonfs config set

Set KEY = VALUE. Default scope is the shared per-repo config (committed).

Usage

protonfs config set [OPTIONS] KEY VALUE

Options

--global

Write to the global user config.

--local

Write to the per-device local config.

Arguments

KEY

Required argument

VALUE

Required argument

Writes to exactly one layer:

  • default (no flag) — the shared per-repo file, .protonfs/config.json (the file you commit, so every clone syncs to the same place).

  • --local — the per-device file, .protonfs/config.local.json (gitignored).

  • --global — the user-wide file, ~/.config/protonfs/config.json (or PROTONFS_CONFIG).

--global and --local are mutually exclusive.

Examples:

protonfs config set defaults.low_io true --local
protonfs config set remote_root /my-files/sim-data --global

See also