Skip to main content

arc space

arc space manages local DID Space data: inspecting what's stored, moving data between two subtrees with a single sync command, and claiming, checking and repairing folder-backed spaces.

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.

bash
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

bash
arc space list [options]
  • --scope <instance|user|all>: which DID Space to operate on (default all)
  • --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:

bash
$ 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):

bash
$ 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

bash
arc space tree <app-did> [options]
  • --app-did <did> (required, also the first positional): app DID to inspect
  • --scope <instance|user>: which DID Space (default instance)
  • --path <path>: path inside the app fragment (default /)
  • --depth <n>: maximum tree depth (default 99)
  • --limit <n>: maximum entries, unless --all (default 100)
  • --all: show all entries, ignores --limit
  • --root-path <path> / --user-did <did>: same overrides as list

--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

bash
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 (default instance)
  • --path <path>: path inside the app fragment (default /)
  • --root-path <path> / --user-did <did>: same overrides as list

Unlike tree, path does not require the app space to actually exist — it prints the computed AFS path regardless, exit 0:

bash
$ arc space path did:abt:no-such-app --root-path ~/spaces
/spaces/z1bp5ncJyUiKBv8BVCBH1RVnKks6u9VkW7G/blocklets/no-such-app/system

Moving 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

bash
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> (default auto): force an engine. auto negotiates 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-run it 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 as layout=files (default when the source is claimed)
  • --external-writes <none|possible> (default possible): 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 (default true); pass --no-browser with --token for 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:

bash
$ 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:    348ms

src/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:

bash
$ 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:    322ms

When 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:

bash
$ 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

bash
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 (default user)
  • --root-path <path> / --user-did <did>: same overrides as list

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

bash
arc space init <dir> [options]
  • dir (required): folder to claim as a DID Space (a ledger, when --source is set)
  • --external-writes <none|possible> (default possible): 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) requires possible
  • --source <path>: read-only truth layer for the external-ledger form; the ledger (.did-space/config + space.db) is created at dir, the source itself is never written
bash
$ 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

bash
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, outside root_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:

bash
$ 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:

bash
$ arc space check --folder ~/notes --view human
Check freshness: ~/notes (layout=files, externalWrites=possible)
Directories checked: 2
Result: CLEAN — index agrees with disk

arc 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

bash
arc space repair --folder <dir> [options]
  • --folder <dir> (required): claimed folder to repair (must be layout=files)
  • --index (default true): rebuild the folder-backend index from disk — the only repair this command performs today
bash
$ arc space repair --folder ~/notes
Repaired index for ~/notes (layout=files)
Added:     2
Modified:  0
Deleted:   0
Unchanged: 0
Duration:  43ms

repair'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

bash
arc space set <folder> <assignment>
  • --folder <dir> (required, also the first positional): claimed folder to update (must already be layout=files — see arc 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/config is preserved untouched
bash
$ 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

bash
arc space migrate <dir> --to-layout files [options]
  • dir (required): claimed DID Space folder to migrate (must be layout=cas)
  • --to-layout <files> (required): target layout — only files is supported
  • --dry-run: report what would happen (space count, file count, byte count, expected result) — zero disk changes
  • --external-writes <none|possible> (default none): query-freshness declaration for the resulting layout=files space. Default none because a layout=cas space'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; pass possible only 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:

bash
$ arc space migrate ~/notes --to-layout files --view human
Migrate ~/notes (layout=cas -> layout=files)
Already layout=files — nothing to migrate.
External writes: none

Running arc space migrate on a folder with no .did-space/ at all is an error — a different one than "already migrated" above (exit 5):

bash
$ 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:

bash
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.