Appearance
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:
| Path | What it holds |
|---|---|
| Your workspace root | Project files that editors, tests, and build tools use normally |
| The room file plane | The canonical file tree and each path's current version |
MESH_HOME | Identity 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 statusfs 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 statusHydration 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-projectEach attached folder gets local-only state under .mesh/:
.mesh/attachments.jsonrecords 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 kind | Commands | Root resolution |
|---|---|---|
| Local mutation | fs put, fs get, fs hydrate, fs edit, fs grep --hydrate | explicit --root / --into → nearest enclosing attachment → current directory, then attach it |
| Local read-only | fs status, fs diff, mesh doctor | explicit --root → nearest enclosing attachment → membership default → refuse with a recovery hint |
| Remote metadata only | fs grep without --hydrate | No local root and no attachment side effect |
| Tree + local-size view | fs 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 rootFor 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
| Command | What 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 --jsonandfs get --jsonstream NDJSON progress.fs put --strictstops before publishing a sync conflict.fs put --stop-on-erroraborts the batch on the first per-file error; otherwise it reports the error and continues..meshignoreexcludes paths from directory puts.fs put . --prune-ignoredalso deletes room copies that were synced before they became ignored.fs put --allincludes other hidden files, but.mesh/and.meshignoreremain private. Directory walks skip symlinks, and a direct symlink put is refused instead of following it.fs get --pruneandfs hydrate --pruneremove 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:
| Glyph | Meaning | Next action |
|---|---|---|
= | In sync | None |
↑ | Ahead — local edits are not in the room | mesh fs put <path> |
↓ | Behind — the room moved | mesh fs get <path> |
⇅ | Diverged — both local and room changed | mesh fs status --deep, then get or put |
C | Local conflict markers present | Resolve the markers, test, then put |
🔒 | Locked by another participant | Wait for unlock/expiry |
? | Untracked — never synced | mesh fs put <path> |
✝ | Deleted in the room | mesh 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 / mode | Behavior |
|---|---|
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 / unknown | Concurrent versions fork; both survive. |
| Any locked path | An 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 dependenciesRoom 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 uiThe 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 roomReselect 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:
- Run
mesh uiin a terminal. - 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. - If the platform opener surfaces no authenticated tab, run
mesh ui --printand open the printed one-time URL in a new tab. - Only if that authenticated tab also fails, stop the terminal that owns the manager with
Ctrl-C, then runmesh uiagain.
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-projectRemember 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.
