arc space manages local DID Space data, the per-app, per-user storage that backs DID Space. Ten user-facing subcommands: three read, one moves data, one deletes, five manage a folder-backed space — plus an eleventh, internal one; see sync-bench at the bottom of this page.
Captured against arc 2.0.0-beta.48, commit 5a5316bde (main, 2026-09-10) — pinned to the commit, not the semver: a second build has carried the same 2.0.0-beta.48 string. Run arc --version before you copy dumps.
arc space <subcommand> [options]Global flags (see Overview): --json, --view, --instance / -i (which local ARC instance; omit for default), and --home (instance root; to pick which instance, use --instance).
The inspection subcommands (list, tree, path, rm) take --scope to pick which DID Space to operate on, plus --root-path/--user-did to override where that data lives. Only list accepts --scope all; tree, path and rm take instance|user. The folder subcommands (init, check, repair, set, migrate, and sync) address a directory directly — a path already says which space it belongs to.
Every subcommand's default (non-TTY) view is its own — this page's own drift, found while recapturing it: list's default already is the full human-readable block shown below; check and migrate default to JSON, not text; sync defaults to a condensed one-line-per-rejection summary, not the long-form explanation. --view human is the one view that is stable and consistent across every subcommand, and it is what every prose example on this page uses from here on — pass it explicitly rather than relying on the default, which differs by subcommand and is not this page's contract. --json is unaffected by any of this — it is always the full structured payload.
In the examples below, the demo space path is shortened to ~/notes; everything else is copied verbatim from a real run.
Inspecting what's stored
arc space list
List app spaces with their file counts and roots.
Usage
arc space list [options]--scope <instance|user|all>: which DID Space to operate on (defaultall)--root-path <path>: root path for DID Space storage, overrides config--user-did <did>: user DID, overrides config; the root path falls back to the config default--all: show all entries (default caps at 100)
With no app spaces under the root, list prints one block per scope (Instance DID Space and User DID Space), each saying (no app spaces) — there is no single-line "nothing here" empty state; every response is scope-shaped:
$ arc space list --root-path ~/spaces
Instance DID Space z1bp5ncJyUiKBv8BVCBH1RVnKks6u9VkW7G
Root: ~/spaces
(no app spaces)
User DID Space z1bp5ncJyUiKBv8BVCBH1RVnKks6u9VkW7G
Root: ~/spaces
(no app spaces)With at least one app present, each block gets a small table instead of (no app spaces):
$ arc space list --root-path ~/spaces --view human
Instance DID Space z1bp5ncJyUiKBv8BVCBH1RVnKks6u9VkW7G
Root: ~/spaces
DID Files Last Modified
─── ───── ─────────────
did:abt:notes-app 0 —
User DID Space z1bp5ncJyUiKBv8BVCBH1RVnKks6u9VkW7G
Root: ~/spaces
DID Files Last Modified
─── ───── ─────────────
did:abt:notes-app 0 —list's default (non-TTY) view is already this same human-readable, per-scope block — it is the one subcommand on this page where --view human changes nothing. --json carries the same two groups as a groups array, each with role, userDid, rootPath, apps, and total.
arc space tree
Show the file tree for one app space.
Usage
arc space tree <app-did> [options]--app-did <did>(required, also the first positional): app DID to inspect--scope <instance|user>: which DID Space (defaultinstance)--path <path>: path inside the app fragment (default/)--depth <n>: maximum tree depth (default99)--limit <n>: maximum entries, unless--all(default100)--all: show all entries, ignores--limit--root-path <path>/--user-did <did>: same overrides aslist
--app-did must be given positionally — arc space tree --app-did <did> alone, with no positional, is refused as a missing argument even though --app-did also appears in the flag table above and in --help's Options: list. tree on an app DID with no space at all is a real reject (exit 5, App space not found: <did>) — the accept-path counterpart is arc space path just below, which answers the same missing DID without erroring, because it only computes where the data would live.
arc space path
Print the AFS path for an app DID Space fragment, useful when you want to feed it into arc afs commands directly.
Usage
arc space path <app-did> [path] [options]--app-did <did>(required, also the first positional): app DID to inspect--scope <instance|user>: which DID Space (defaultinstance)--path <path>: path inside the app fragment (default/)--root-path <path>/--user-did <did>: same overrides aslist
Unlike tree, path does not require the app space to actually exist — it prints the computed AFS path regardless, exit 0:
$ arc space path did:abt:no-such-app --root-path ~/spaces
/spaces/z1bp5ncJyUiKBv8BVCBH1RVnKks6u9VkW7G/blocklets/no-such-app/systemMoving data
arc space sync
Move data between two subtrees — local folders, remote hosts, or one of each. Makes --to converge toward --from.
This is the one command for "move data from A to B", in either direction and between any two ends. Direction and both ends are arguments, never the command name — to pull instead of push, swap --from and --to.
It addresses spaces by path, which is why it has no --server, --app-did or --scope: a layout=files space puts real directories on disk, so a path already says which space and which fragment you mean. (--scope still applies to list/tree/path/rm, which address a space by DID.) Global --instance / -i still picks which local ARC instance the command talks to; it is not a DID Space id.
Usage
arc space sync --from <ref> --to <ref> [options]Each <ref> is either a local folder or https://<host>[/<sub>] for a remote app's user fragment. The two forms are told apart by scheme alone, so a mistyped URL is never silently reinterpreted as a relative directory name.
--from <ref>(required): source--to <ref>(required): destination--mirror: delete destination-only files (default: additive, nothing is removed)--dry-run: report the differences without writing anything--engine <auto|changelog|cursor|manifest>(defaultauto): force an engine.autonegotiates the strongest one both ends support. A named engine that is unavailable is an error, never a silent downgrade--verify: after syncing, recompute every destination file's CID against the source. Combined with--dry-runit asserts the destination already matches, exiting non-zero if not--full: ignore any recorded cursor and re-read everything — no cursor, and no reading either end's index; both trees are walked directly. The first sync into a destination is full either way; use this to rebuild a destination you no longer trust, or to sync a claimed folder that is deliberately written outside AFS (whose index is expected to lag, and which otherwise fails with an index-diverged error — see the note after the third example below)--init: claim a folder destination aslayout=files(default when the source is claimed)--external-writes <none|possible>(defaultpossible): query-freshness declaration for a claimed destination, only consulted when the destination is actually claimed--token <token>: access token for a remote endpoint, an alternative to browser auth--browser: open a browser for the auth flow (defaulttrue); pass--no-browserwith--tokenfor unattended runs
Every run below also prints a short progress trace on stderr (Scanning source N files, destination M (…ms) / Comparing (…ms) / Syncing X/Y (…ms) / Total: …ms, omitted from the stdout dumps below) and a File list: line stating whether each side answered from an index snapshot or fell back to a scan — neither is documented elsewhere on this page, so both are new since the last capture.
Additive by default. A first sync reports what it moved, and names the engine it negotiated along with why the others were rejected. --view human gives the long form of each rejection reason; the default (non-TTY) view gives a one-line summary instead — see the note above Inspecting what's stored for why this page uses --view human throughout:
$ arc space sync --from src --to dst --view human
src → dst
Base path: /
Mode: additive
Engine: manifest — the manifest engine works between any two endpoints
not changelog: the changelog engine is a device↔cloud engine — it needs exactly one local folder (the device) and one remote host (the cloud); neither folder↔folder nor remote↔remote fits it
not cursor: the cursor engine mirrors (it deletes destination-only paths); this run is additive, so use --mirror to allow deletions or let the manifest engine handle it
File list: source scan (Operation not supported: paginated snapshot), destination scan (Operation not supported: paginated snapshot)
Added: 2
Modified: 0
Deleted: 0
Unchanged: 0
Transferred: 2 files
Verified: no
Duration: 348mssrc/dst here are two plain, never-arc space init-ed folders — that is why File list: says scan (Operation not supported: paginated snapshot) for both: neither side has an index to snapshot from, so the manifest engine falls back to walking the tree directly. Files that exist only in the destination are left alone. Pass --mirror when you want the destination to become an exact copy — this second run also removed b.txt from src first, so Deleted: 1 reflects a real removal, not a contrived number:
$ arc space sync --from src --to dst --mirror --view human
src → dst
Base path: /
Mode: mirror (deletes destination-only files)
Engine: manifest — the manifest engine works between any two endpoints
not changelog: the changelog engine is a device↔cloud engine — it needs exactly one local folder (the device) and one remote host (the cloud); neither folder↔folder nor remote↔remote fits it
not cursor: source is not a claimed DID Space, so it keeps no changelog ledger to take a cursor from
File list: source scan (Operation not supported: paginated snapshot), destination scan (Operation not supported: paginated snapshot)
Added: 0
Modified: 0
Deleted: 1
Unchanged: 1
Transferred: 0 files
Verified: no
Duration: 322msWhen the source is itself a claimed layout=files space (arc space init), the same rejection reads differently — this is the wording worth remembering, because it names the actual remedy:
not cursor: source is layout=files with externalWrites="possible" — the changelog cannot see edits made outside AFS; run `arc space set <from> external-writes=none` (only if the folder really is exclusively AFS-managed) to enable cursor-incremental sync
File list: source snapshot, destination snapshot(Real capture, claimed source/destination — File list: now says snapshot on both sides with no fallback reason, because both have an index to read from.)
--dry-run shows, --verify asserts. Together they check a destination without touching it — the run below found one tampered file and exited 1:
$ arc space sync --from src --to dst --dry-run --verify --view human
src ⇢ (dry-run) dst
Base path: /
Mode: additive
Engine: manifest — the manifest engine works between any two endpoints
not changelog: the changelog engine is a device↔cloud engine — it needs exactly one local folder (the device) and one remote host (the cloud); neither folder↔folder nor remote↔remote fits it
not cursor: the cursor engine mirrors (it deletes destination-only paths); this run is additive, so use --mirror to allow deletions or let the manifest engine handle it
File list: source scan (Operation not supported: paginated snapshot), destination scan (Operation not supported: paginated snapshot)
Added: 0
Modified: 1
Deleted: 0
Unchanged: 0
Transferred: 0 files
Verified: no
Duration: 357ms--dry-run needs both ends to exist already; only a real run creates the destination directory.
New since the last capture: --dry-run --verify against a claimed destination that was hand-edited outside AFS refuses outright, rather than reporting the drift as Modified — --help's own text for --full names this exact failure:
$ arc space sync --from src-claimed --to dst-claimed --dry-run --verify --view human
ERROR: Provider "did-space-files" cannot reach its "query-freshness" dependency at "/": index has diverged from disk in 1 directory — /: a.txt (size-mismatch) — run `arc space repair --folder dst-claimed` to repair. Or re-run with --full to read both trees directly and ignore the indexes (slower, but it is the right choice for a folder that is deliberately written outside AFS).(exit 5.) Passing --full (or running arc space repair --folder <dst> first) is the accept-path counterpart — same tampered file, same command, one flag added, and it reports Modified: 1 and exits 1 (the --verify mismatch exit, not a refusal) exactly like the plain-folder example above.
Editing
arc space rm
Delete a file or directory, recursively, inside a local app space. It goes through the space's ledger — a bare rm -rf leaves the index with a phantom row that only arc space repair clears.
Usage
arc space rm <app-did> <path> [options]--app-did <did>(required, also the first positional): target app DID--path <path>(required, also the second positional): path inside the app fragment--scope <instance|user>: which DID Space (defaultuser)--root-path <path>/--user-did <did>: same overrides aslist
Both positionals are required — arc space rm <app-did> alone (no path) is refused as "not enough non-option arguments", the same yargs-level guard tree/path hit above for a missing --app-did.
Folder spaces
A claimed folder is a DID Space backed by a plain, hand-editable file tree plus a .did-space/ index, as opposed to the layout=cas (content-addressed) form. These five commands claim a folder, check whether its index still agrees with disk, rebuild it when it doesn't, and convert a CAS space to the folder form.
arc space init
Initialize a folder as a writable DID Space — creates .did-space/ only.
Usage
arc space init <dir> [options]dir(required): folder to claim as a DID Space (a ledger, when--sourceis set)--external-writes <none|possible>(defaultpossible): query-freshness declaration for the new space.possible— a human-initialized folder can always be hand-edited, so every query re-scans disk.none— pass only when something else (a daemon) will manage this folder exclusively; queries then answer straight from the index. The external-ledger form (--source) requirespossible--source <path>: read-only truth layer for the external-ledger form; the ledger (.did-space/config+space.db) is created atdir, the source itself is never written
$ arc space init ~/notes
Initialized DID Space at ~/notes (layout=files, externalWrites=possible)init's default view is this same one-line text; --json gives { "dir", "layout", "externalWrites" }.
Claiming a folder that already has files in it does not index them — init only creates .did-space/. Run arc space repair --folder <dir> afterwards, or arc space check --folder <dir> first to see what the index is missing.
arc space check
Audit local DID Spaces: layout (migrated / needs migration / unreadable) and index-vs-disk freshness. Read-only — it never writes the index or the config. Exits non-zero when anything needs migrating, cannot be read, or has drifted.
Usage
arc space check [options]--folder <dir>: check exactly one claimed folder's freshness, skipping the layout sweep entirely--root <dir>(repeatable): root directory for the layout sweep. Default: this instance's space root and its sibling<home>/.afs/work— the host work ledger lives there, outsideroot_path, and a sweep that misses it reports a false all-clear--root-path <path>: root path override for the sweep
check's default (non-TTY) view is JSON, not the text below — this is new since the last capture on this page, which showed the text as if it were the default. Every example below passes --view human for the readable form; drop it and you get the same data as { "folder", "layout", "externalWrites", "directoriesChecked", "fresh", "dirty" } instead (identical payload whether you ask via the default view or --json explicitly).
Drift is reported per directory, with the remedy spelled out:
$ arc space check --folder ~/notes --view human
Check freshness: ~/notes (layout=files, externalWrites=possible)
Directories checked: 2
Result: DRIFT DETECTED — 2 of 2 directories disagree with disk
/
! b.txt [missing-in-index]
! notes [missing-in-index]
/notes
! a.md [missing-in-index]
Run `arc space repair --folder ~/notes` to repair (review the entries above first — it rebuilds the WHOLE index from disk).A clean space says so and exits 0:
$ arc space check --folder ~/notes --view human
Check freshness: ~/notes (layout=files, externalWrites=possible)
Directories checked: 2
Result: CLEAN — index agrees with diskarc space repair
Repair a claimed folder space by rebuilding its index from the real file tree. Use it after hand-editing files under a claimed folder, or after a bare rm -rf inside one.
Usage
arc space repair --folder <dir> [options]--folder <dir>(required): claimed folder to repair (must belayout=files)--index(defaulttrue): rebuild the folder-backend index from disk — the only repair this command performs today
$ arc space repair --folder ~/notes
Repaired index for ~/notes (layout=files)
Added: 2
Modified: 0
Deleted: 0
Unchanged: 0
Duration: 43msrepair's default view is this same text; --json gives { "folder", "layout", "added", "modified", "unchanged", "deleted", "elapsed" } (note elapsed, not Duration — the two views use different key names for the same number).
arc space set
Set a declaration on an existing layout=files DID Space.
Usage
arc space set <folder> <assignment>--folder <dir>(required, also the first positional): claimed folder to update (must already belayout=files— seearc space init)--assignment <key>=<value>(required, also the second positional): the declaration to set.external-writes=none— this folder is under exclusive (e.g. daemon) management, so queries answer straight from the index with no disk scan.external-writes=possible— the folder can still be hand-edited, so a query re-scans disk before every answer. Idempotent and reversible either direction; every other key already in.did-space/configis preserved untouched
$ arc space set ~/notes external-writes=none
Set externalWrites=none for ~/notes (layout=files)arc space migrate
Migrate a layout=cas DID Space in place to layout=files (copy → verify → atomic replace; the old CAS directory is kept for rollback).
Usage
arc space migrate <dir> --to-layout files [options]dir(required): claimed DID Space folder to migrate (must belayout=cas)--to-layout <files>(required): target layout — onlyfilesis supported--dry-run: report what would happen (space count, file count, byte count, expected result) — zero disk changes--external-writes <none|possible>(defaultnone): query-freshness declaration for the resultinglayout=filesspace. Defaultnonebecause alayout=casspace's truth layer (objects/<cid>+_metadata.db) isn't something a human hand-edits, so "no external writer at migration time" is an assertable fact; passpossibleonly if this space has actually been treated as a plain hand-editable folder
migrate's default (non-TTY) view is JSON too, same as check; --view human gives a short sentence-form summary instead.
Migrating a folder that is already layout=files is not an error — it is a real, idempotent accept path, exit 0:
$ arc space migrate ~/notes --to-layout files --view human
Migrate ~/notes (layout=cas -> layout=files)
Already layout=files — nothing to migrate.
External writes: noneRunning arc space migrate on a folder with no .did-space/ at all is an error — a different one than "already migrated" above (exit 5):
$ arc space migrate ~/plain-dir --to-layout files
ERROR: arc space migrate: ~/plain-dir is not a claimed DID Space (no .did-space/). Run `arc space init ~/plain-dir` first.Rollback if a migration needs to be undone:
rm -rf "<dir>"
mv "<parent-of-dir>/.cas-backups/<dir-name>" "<dir>"The pre-migration layout=cas directory is kept at <parent-of-dir>/.cas-backups/<dir-name> and is never deleted automatically. It deliberately sits one level below the space's own parent, in a container that is not itself a claimed space, so space enumeration — and therefore daemon boot — never rediscovers the backup.
sync-bench (internal)
An eleventh subcommand exists on this build: arc space sync-bench, a SyncEngine throughput benchmark driven over afs-rpc (the afs-rpc-batch P6 driver). It requires --server <url> and --token <token> — a real remote endpoint, not a local folder — and seeds/benchmarks/cleans up throwaway files under a --prefix (default e2e-batch/bench). It has no read/write/inspect role in a space's data model the way the other ten do; see arc space sync-bench --help for its flags (--blocklet, --files, --size, --single-op, --header, --cleanup). Not documented further on this page.