client.vfs
client.storage gives you files identified by a manifest.
client.vfs gives you a filesystem: directories, paths you can navigate,
version history, and copies that cost nothing.
The bytes go through client.storage.writeFile exactly as
they always have, into a real Space with a real gated manifest. What the VFS adds
is the metadata: one encrypted record per directory in your personal space, each
listing only its immediate children.
Where you are
Section titled “Where you are”The VFS has a working directory, so paths behave the way they do in a shell.
await client.vfs.cd("/apps/scratch");client.vfs.cwd; // "/apps/scratch"
await client.vfs.readText("src/App.tsx"); // relative to cwdawait client.vfs.readText("/other.txt"); // absolute, unaffectedawait client.vfs.cd(".."); // climbs; stops at the rootcd checks the target is a real directory, so a typo fails there — where the
error can name the path — instead of turning every later relative path into a
puzzling “does not exist”.
It lives here rather than in each consumer so a CLI, a script and an app all
agree on what “here” means. muhkoo vfs cd is this, persisted between commands.
Reading
Section titled “Reading”await client.vfs.list("/apps/my-app"); // immediate children, dirs firstawait client.vfs.stat("/apps/my-app/App.tsx");await client.vfs.exists("/apps/my-app");await client.vfs.walk("/apps"); // every file path beneath, depth-firstawait client.vfs.readText("/apps/my-app/App.tsx");await client.vfs.readFile("/apps/my-app/logo.png"); // Uint8Arrayawait client.vfs.glob("/apps/**/*.{ts,tsx}");list is one record fetch. walk and glob are one fetch per directory in the
subtree — cheap for a project, worth thinking about at the root of a large
filesystem.
Writing
Section titled “Writing”await client.vfs.writeFile("/apps/my-app/src/App.tsx", source); // parents createdawait client.vfs.writeFile("/apps/my-app/logo.png", pngBytes); // binary is fineawait client.vfs.mkdir("/apps/my-app/assets");await client.vfs.rename("/apps/my-app", "/archive/my-app"); // also movesawait client.vfs.copy("/apps/my-app", "/apps/my-app-2");await client.vfs.delete("/archive", { recursive: true });Every mutation is a read-modify-write of exactly one directory record, so a write cannot leave the tree half-updated: either the parent names the new child or it does not.
Moving a directory is O(1) however much is inside it. Nothing in a subtree records its own path, so re-parenting edits one entry and the subtree is not touched.
Copying a file moves no bytes. Content is immutable and content-addressed, so the copy is a second handle to the same shards — duplicating a 200 MB asset is one small metadata write. The copy does get a fresh identity, so the two files do not share version history.
Versions
Section titled “Versions”Every write keeps the previous manifest, so you get undo that survives a reload.
await client.vfs.history("/apps/my-app/App.tsx"); // newest firstawait client.vfs.restore("/apps/my-app/App.tsx", 0);Restoring pushes the current version onto history first, so it is itself undoable. Unchanged chunks dedupe in the shard store, so keeping versions costs little beyond the history record — which is capped per file (default 20).
Locking
Section titled “Locking”The root key is derived from your master seed, which lives in memory only and is
lost on reload until you sign in again. While it is missing, every VFS call
throws VfsLockedError.
if (!client.vfs.unlocked) await client.auth.zk.resume();Live changes
Section titled “Live changes”const stop = client.vfs.watch(() => { // re-read whatever you are showing});Fires when your filesystem is written somewhere else — another tab, another
machine, the CLI. It shares the personal space’s websocket with
client.kv, so subscribing costs no extra connection.
It is a notification, not a diff: the change names a record, and a record is not
a path, so the cache is dropped and you re-read what you are actually showing.
In a runtime with no WebSocket it is a no-op — watching is an optimisation,
never required for correctness.
Maintenance
Section titled “Maintenance”const { removed } = await client.vfs.sweep();Because each write touches a single record, two tabs editing the same directory
can leave a child whose parent entry lost the race. Such a record is unreachable
rather than corrupting, but it costs storage. sweep() reclaims them. Run it
when the filesystem is idle — a sweep racing an in-flight write could remove a
record that is about to be referenced.
How keys are arranged
Section titled “How keys are arranged”Only the root key comes from your master seed. Every other directory has a
random key stored in its parent’s entry, which chains capabilities downward:
holding {id, key} for a subtree lets you walk that subtree and nothing above
it, because no key can be derived upward.
That is the seam sharing hangs off — a subtree can later be handed to a Space without re-encrypting the rest of the filesystem.