Skip to content

Sharing files

The room is the canonical copy of a shared workspace. Each machine works in a normal project folder, called the workspace root, and mesh moves files between that folder and the room. Files hydrate in place; there is no .mesh/fs shadow copy and no generated checkout layout.

Three paths are deliberately separate:

PathWhat it holds
Your workspace rootProject files that editors, tests, and build tools use normally
The room file planeThe canonical file tree and each path's current version
MESH_HOMEIdentity keys, joined-room credentials, and profile state — never project files

Attach a project folder

There is no separate mesh attach command. A workspace is attached on the first command that writes local file state: fs put, fs get, fs hydrate, fs edit, or fs grep --hydrate.

Start from an existing project

Run the first command from the project root:

sh
cd ~/my-project
mesh fs put .
mesh fs status

fs put . attaches ~/my-project to the active room, uploads the eligible files, and records their sync bases. fs status should then show = for every uploaded path.

Start from an existing room

Use an empty project folder and hydrate the room into it:

sh
mkdir -p ~/my-project
cd ~/my-project
mesh fs hydrate
mesh fs status

Hydration is safe: mesh does not overwrite local-ahead work that the room has not seen. To pull only part of the tree, pass a prefix such as mesh fs hydrate src/.

The first attachment for a profile and room also becomes that membership's default workspace. mesh ui, mesh rooms, fs status, fs diff, and mesh doctor use that default when they need local context outside the project folder. Later checkouts can attach to the same room, but they do not silently replace the first default.

Check the machine-wide inventory:

sh
mesh rooms
# * my-room  origin=https://usemesh.dev  attachment=/Users/you/my-project

Each attached folder gets local-only state under .mesh/:

  • .mesh/attachments.json records the room origin + room id attached to the folder.
  • .mesh/lineage/ stores the merge base for each room path.

The folder owns this state, not an identity. Two profiles sharing one project folder therefore share one consistent sync base. The .mesh/ directory is private local metadata and is skipped by fs put.

Attach the project root, not your home directory

Run the first file command from the actual project folder, or pass --root <dir> / --into <dir>. Mesh refuses to attach $HOME or MESH_HOME because those locations may hold unrelated files or credentials.

How mesh chooses the workspace root

The resolution order depends on whether a command can modify local files:

Command kindCommandsRoot resolution
Local mutationfs put, fs get, fs hydrate, fs edit, fs grep --hydrateexplicit --root / --into → nearest enclosing attachment → current directory, then attach it
Local read-onlyfs status, fs diff, mesh doctorexplicit --root → nearest enclosing attachment → membership default → refuse with a recovery hint
Remote metadata onlyfs grep without --hydrateNo local root and no attachment side effect
Tree + local-size viewfs ls--into--root → current directory; listing does not attach it

An enclosing attachment means commands work from a nested directory:

sh
cd ~/my-project/src
mesh fs status       # resolves ~/my-project through its .mesh/ attachment
mesh fs put app.ts   # room path is src/app.ts, relative to the attached root

For commands that accept both flags, --into wins over --root. fs put accepts --root; download/edit commands accept either spelling. An explicit root is also attached when the command mutates local file state.

Mutating commands never jump to the membership default. If you run fs get from an unrelated directory with no enclosing attachment, that directory becomes another attached checkout. This prevents a write from silently landing somewhere other than where you are standing.

The command reference

CommandWhat it does
mesh fs put <path|dir> [--root <dir>]Upload a file or directory relative to the resolved workspace root
mesh fs get <path|prefix> [--into <dir>]Hydrate a file or subtree without clobbering unseen local edits
mesh fs hydrate [<prefix>] [--into <dir>]Bulk-download a subtree; an omitted prefix means the room root
mesh fs ls [<prefix>] [-f] [--into <dir>]List the room tree; -f follows file events and shows local hydration
mesh fs grep "<query>" [--prefix <path>] [--hydrate]Search contents server-side; optionally hydrate only the matches
mesh fs status [<prefix>] [--deep] [--porcelain]Classify local files against the room; --deep dry-runs a merge
mesh fs diff <path> [--base]Show local vs. room diff; --base also summarizes both sides against the merge base
mesh fs edit <path>Hydrate and live-edit a prose document through the CRDT lane
mesh fs lock <path> / mesh fs unlock <path>Acquire or release an exclusive path lease
mesh fs rm <path> [-r]Delete a room path or subtree
mesh fs log [-f]Read or follow file, grant, role, lease, and config events

Common machine-readable and safety options:

  • fs put --json and fs get --json stream NDJSON progress.
  • fs put --strict stops before publishing a sync conflict.
  • fs put --stop-on-error aborts the batch on the first per-file error; otherwise it reports the error and continues.
  • .meshignore excludes paths from directory puts. fs put . --prune-ignored also deletes room copies that were synced before they became ignored.
  • fs put --all includes other hidden files, but .mesh/ and .meshignore remain private. Directory walks skip symlinks, and a direct symlink put is refused instead of following it.
  • fs get --prune and fs hydrate --prune remove clean local copies already deleted in the room; local edits are preserved.

Bulk commands stream a plan and progress, then end with an explicit summary. Exit codes for fs put / fs get are 0 clean, 1 conflicts or forks need attention, and 2 errors or an aborted batch.

Sync-state glyphs

mesh fs status shows one glyph per local path:

GlyphMeaningNext action
=In syncNone
Ahead — local edits are not in the roommesh fs put <path>
Behind — the room movedmesh fs get <path>
Diverged — both local and room changedmesh fs status --deep, then get or put
CLocal conflict markers presentResolve the markers, test, then put
🔒Locked by another participantWait for unlock/expiry
?Untracked — never syncedmesh fs put <path>
Deleted in the roommesh fs get <path> --prune or intentionally restore with put

Add --porcelain for stable STATE<TAB>path rows suited to scripts and agents.

How writes are reconciled

Write behavior follows the path's policy:

File type / modeBehavior
Code (.ts, .js, .py, .go, .rs, .java, …)fs put performs a 3-way merge. A genuine overlap returns conflict markers locally and leaves the room untouched.
Prose (.md, .txt)Use fs edit for live CRDT co-editing. A concurrent plain put preserves both versions by forking instead of losing one.
Binary / unknownConcurrent versions fork; both survive.
Any locked pathAn exclusive lease serializes writes until unlock or expiry.

The merge base lives in the workspace folder. This matters when two identities share the same checkout: when either one successfully gets or puts a file, both immediately classify against the same updated lineage. Mesh still coordinates only operations that pass through it; two processes writing the same raw disk file at the same instant can race before mesh sees either edit. Use fs lock or separate folders for that case.

Access grants

Access follows a lattice; each level includes the ones before it:

discover < read < write < exclusive

  • discover — see that a path exists (name and size), not its contents.
  • read — download contents.
  • write — change it.
  • exclusive — take an exclusive lock.
sh
mesh fs grant teammate@team read src/      # grant read on a subtree
mesh fs grants                             # list current grants
mesh fs revoke teammate@team src/          # take a grant back
mesh fs request src/shared/ --grade read   # ask for access
mesh fs deps src/app.ts                     # inspect access dependencies

Room owners can also change defaults and limits:

sh
mesh fs config public-share on
mesh fs config write open
mesh fs config artifact 10485760
mesh fs config rate "60/min;burst=120"

Shared folder or separate folders?

Agents on one machine and inside one trust boundary can use the same project folder. Keep a different MESH_HOME per identity; credentials, memberships, roles, and room grants remain separate while the room attachment and .mesh/lineage/ belong to the shared checkout.

Use separate workspace folders for another machine, independent hydration, or different access boundaries. Each identity can attach its own root with --root / --into, and the room filters discovery, reads, and writes using that identity's grants.

Room ACLs do not replace operating-system permissions. An identity denied read by the room can still read bytes another process already hydrated into a mutually readable local folder. Folder separation alone is also insufficient if one process can traverse the other's folder. For confidential paths, combine distinct workspace roots with OS users and permissions, containers, VMs, or separate machines.

Browse rooms and local status

Local multiroom manager

Run:

sh
mesh ui

The local manager groups every profile and joined room on this OS user. The room pane shows the remote conversation and file tree; the Local workspace pane compares the room against that membership's default workspace and shows daemon state beside it. Identity keys and room tokens stay in the loopback broker and never enter browser JavaScript.

If the Local workspace pane says No local workspace attached, close or leave the tab open, then attach one from a terminal:

sh
cd ~/my-project
mesh fs put .       # existing project
# or: mesh fs hydrate   # empty checkout of an existing room

Reselect the room in the manager. mesh rooms shows which path is registered as the default.

mesh ui is one manager per OS user. The terminal owns the loopback broker; the browser owns its tabs. Stopping the terminal does not close an old tab, and that tab's in-memory session does not survive a broker restart. Session ended means that browser tab lost its manager session, not necessarily that the current manager stopped.

Recover in this order:

  1. Run mesh ui in a terminal.
  2. Use the tab that command opens or refreshes. If the CLI says opening existing manager, the broker was already live; any other tab that remains ended is stale.
  3. If the platform opener surfaces no authenticated tab, run mesh ui --print and open the printed one-time URL in a new tab.
  4. Only if that authenticated tab also fails, stop the terminal that owns the manager with Ctrl-C, then run mesh ui again.

Every launch URL also carries a fresh, non-secret launch= query nonce. That changes the full URL and forces a real navigation even when the old tab is running an older bundle with no relaunch listener. The current bundle also watches for a new #s= fragment as defense in depth.

One room

mesh open opens the active room directly. Its browser view provides the shared tree and member conversation, but the file tree is read-only in the browser.

  • A member session sees the full tree allowed by its grants, last-editor and lock details, and the conversation.
  • A public-key view shows only that identity's card. It lists file structure only when the owner enabled mesh fs config public-share on; contents and conversation stay private.

See the demos guide for both browser flows.

Troubleshooting

fs status cannot resolve a workspace root

Attach a real project folder with fs put, fs get, fs hydrate, fs edit, or fs grep --hydrate; or pass --root <dir> explicitly. Plain fs grep and fs ls do not create attachments.

A command is using the wrong folder

Run mesh rooms to inspect the membership default. From the intended folder, pass an explicit root to the operation:

sh
mesh fs status --root ~/my-project
mesh fs get src/app.ts --into ~/my-project

Remember that a later checkout does not replace the first default automatically. Inside an attached folder, the nearest enclosing .mesh/attachments.json wins.

The room id exists on more than one origin

Use --url <origin> with the room-scoped command. Attachments and lineage are keyed by both origin and room id, so same-named rooms on different hosts cannot share sync state accidentally.

Next: Trust & verify.

Signed, headless, cross-vendor.