> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mesa.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# mesa.fs()

> Define a layout, mount it as a Mesa virtual filesystem, or mint its token.

`mesa.fs({ layout, ttl?, authors? })` builds an `FsLayoutDefinition`: a prepared path map bundled with `layout()`, `mount()`, and `token()`. Calling `.mount()` returns a `MesaFileSystem` backed by Mesa's native filesystem.

```ts theme={null}
import { Buffer } from 'node:buffer';
import { Mesa, repo } from '@mesadev/sdk';

const mesa = new Mesa({ privateKey: process.env.MESA_PRIVATE_KEY });

const fs = await mesa
  .fs({
    layout: {
      '/workspace': repo('app', { mode: 'rw', at: { bookmark: 'main' } }),
    },
    authors: [{ name: 'Mesa Bot', email: 'mesa-bot@example.com' }],
  })
  .mount();

const data = await fs.readFileBuffer('/workspace/README.md');
console.log(Buffer.from(data).toString('utf8'));
```

Map `repos.list()` results into layout declarations when the repository set is dynamic:

```ts theme={null}
const { repos } = await mesa.repos.list({
  tags: {
    $and: [{ environment: 'prod' }, { workload: { $in: ['sync', 'index'] } }],
  },
});

const fs = await mesa
  .fs({
    layout: {
      '/skills': repos.map((r) => repo(r.name, { mode: 'ro' })),
    },
    authors: [{ name: 'Mesa Bot', email: 'mesa-bot@example.com' }],
  })
  .mount();
```

Private-key and API-key clients sign a short-lived, layout-scoped token locally for the mount. A client built from an access token forwards that token unchanged. Either way the mount keeps the token it started with for its whole lifetime; nothing refreshes it in the background.

See [Layouts](/content/mesafs/layouts) for nesting, structural rules, and running a layout mount in a sandbox.

## `mesa.fs(options)`

Synchronous. Builds the definition eagerly so invalid layouts throw at this call.

<ParamField path="layout" type="LayoutSpec" required>
  Map of absolute mount path (`/`-prefixed) to one `repo(...)` declaration or an array of them. The mount namespace is exactly this map.
</ParamField>

<ParamField path="ttl" type="number | undefined">
  Lifetime, in seconds, of every token the definition mints — through `token()` and under the hood in `mount()`. Private-key clients default to `900` and allow up to `14400`; API-key clients default to `3600` and allow up to `86400`. Not accepted by access-token clients, whose token lifetime is already fixed.
</ParamField>

<ParamField path="authors" type="[{ name: string; email?: string }, ...]">
  Commit authors, in order, with at least one entry. **Required** for private-key clients; rejected for API-key and access-token clients.
</ParamField>

### Definition members

<ResponseField name="layout()" type="Layout">
  The prepared layout. `layout().toString()` produces the JSON document `mesa mount --layout` reads.
</ResponseField>

<ResponseField name="mount(options?)" type="Promise<MesaFileSystem>">
  Mount the layout as the complete namespace. Accepts only runtime options (`cache`, `telemetry`). The mount's token lifetime is the definition's `ttl`.
</ResponseField>

<ResponseField name="token()" type="Promise<TokensCreateResponse>">
  Mint the layout-scoped, least-privilege access token: repositories collected from every declaration and scoped by name; `['read']` when every `mode` is `'ro'`, otherwise `['read', 'write']`. Not available on access-token clients, which already hold a token.
</ResponseField>

`token()` validates the layout structurally before minting, so an invalid layout fails there with the same error the mount would report. Repository names resolve only at mount time — a layout naming a nonexistent repository still mints a token and fails when the mount resolves it.

```ts theme={null}
const definition = mesa.fs({
  layout: { '/workspace': repo('app', { mode: 'rw' }) },
  authors: [{ name: 'Mesa Bot', email: 'mesa-bot@example.com' }],
  ttl: 3600,
});

sandbox.writeFile('layout.json', definition.layout().toString());
const { token } = await definition.token();
```

## `repo(selector, options)`

Declare one repository in a layout. Do not construct `Repo` objects by hand.

```ts theme={null}
repo('app', { mode: 'rw', at: { bookmark: 'main' } });
repo({ name: 'app' }, { mode: 'ro', at: { changeId: 'zyxwvutsrqponmlkzyxwvutsrqponmlk' } });
repo('app', {
  mode: 'rw',
  branchedFrom: {
    bookmark: 'main',
    as: { bookmark: 'run-27', describe: 'initiate plan...' },
  },
});
```

<ParamField path="selector" type="string | { name: string }" required>
  Repository name within the client's organization.
</ParamField>

<ParamField path="options.mode" type="'rw' | 'ro'" required>
  Presentation mode. Always required — there is no default. `'ro'` rejects writes with `EROFS`.
</ParamField>

<ParamField path="options.at" type="RevisionIdentifier | undefined">
  Pin an existing revision: `{ bookmark }` or `{ changeId }`. Mutually exclusive with `branchedFrom`. When neither `at` nor `branchedFrom` is set, the repository's default bookmark is used.
</ParamField>

<ParamField path="options.branchedFrom" type="BranchedRevision | undefined">
  Fork a new empty descendant from a parent tip at cold open and check it out. Requires `mode: 'rw'`. Shape: parent `{ bookmark }` or `{ changeId }`, optional `as: { bookmark?, describe? }`. Omit `as` (or `as.bookmark`) for an anonymous tip. Parent bookmark is not moved. Mutually exclusive with `at`.
</ParamField>

<ParamField path="options.alias" type="string | undefined">
  Directory-name override. Only valid when the declaration is an element of an array value in the layout.
</ParamField>

<ParamField path="options.subPaths" type="Record<string, Repo | Repo[]> | undefined">
  Nested mounts declared beneath this repository's path. Keys are relative paths.
</ParamField>

## `FsMountRuntimeOptions`

Non-token options accepted by `definition.mount(options?)`.

<ParamField path="cache.diskCache" type="{ path: string; maxSizeBytes?: number } | undefined">
  Optional on-disk cache. When omitted, the mount uses in-memory caching only.
</ParamField>

<ParamField path="telemetry.logLevel" type="'error' | 'warn' | 'info' | 'debug' | undefined">
  Minimum native log level. Defaults to `warn`.
</ParamField>

<ParamField path="telemetry.onLog" type="(record: LogRecord) => void | undefined">
  Per-instance structured log callback from the native filesystem.
</ParamField>

### Disk cache

```ts theme={null}
const fs = await mesa
  .fs({
    layout: { '/workspace': repo('app', { mode: 'rw' }) },
    authors: [{ name: 'Mesa Bot' }],
  })
  .mount({
    cache: { diskCache: { path: '/tmp/mesa-cache', maxSizeBytes: 500_000_000 } },
  });
```

<ParamField path="cache.diskCache.path" type="string" required>
  Directory for the on-disk cache.
</ParamField>

<ParamField path="cache.diskCache.maxSizeBytes" type="number | undefined">
  Optional cache size cap. When omitted, the native extension auto-sizes the budget against system resources.
</ParamField>

## Paths

Mounted paths are whatever the layout declares:

```text theme={null}
/workspace/README.md
/workspace/src/main.ts
/skills/code-review/SKILL.md
```

## Response

`definition.mount()` returns a `Promise<MesaFileSystem>`.

## MesaFileSystem

The returned `fs` object implements the `just-bash` filesystem interface and exposes async file I/O, metadata, traversal, mutation, Bash, and mounted-repo version-control helpers.

```ts theme={null}
await fs.mkdir('/workspace/src', { recursive: true });
await fs.writeFile('/workspace/src/main.ts', 'console.log("hello")\n');
const data = await fs.readFile('/workspace/src/main.ts', 'utf8');
```

### Byte and text I/O

<ParamField path="readFile(path, options?)" type="Promise<string>">
  Read a file as text using a Node-compatible encoding. The `binary` encoding follows the `just-bash` latin1 byte-string convention.
</ParamField>

<ParamField path="readFileBuffer(path)" type="Promise<Uint8Array>">
  Read raw file bytes.
</ParamField>

<ParamField path="writeFile(path, content, options?)" type="Promise<void>">
  Replace file contents, creating the file if missing. Parent directories must already exist.
</ParamField>

<ParamField path="appendFile(path, content, options?)" type="Promise<void>">
  Append text or bytes to a file, creating it if missing.
</ParamField>

<ParamField path="exists(path)" type="Promise<boolean>">
  Return whether a path exists. Follows symlinks.
</ParamField>

### Metadata and traversal

<ParamField path="stat(path)" type="Promise<FsStat>">
  Return metadata for a path, following symlinks. `mtime` is a JavaScript `Date`.
</ParamField>

<ParamField path="lstat(path)" type="Promise<FsStat>">
  Return metadata for a path without following symlinks. `mtime` is a JavaScript `Date`.
</ParamField>

<ParamField path="readdir(path)" type="Promise<string[]>">
  Return entry names in a directory. Sort client-side if you need deterministic ordering.
</ParamField>

<ParamField path="readdirWithFileTypes(path)" type="Promise<Array<{ name: string; isFile: boolean; isDirectory: boolean; isSymbolicLink: boolean }>>">
  Return entry names with file type flags.
</ParamField>

<ParamField path="realpath(path)" type="Promise<string>">
  Resolve symlinks and `..` segments to a canonical path.
</ParamField>

<ParamField path="readlink(path)" type="Promise<string>">
  Return the target of a symlink.
</ParamField>

<ParamField path="resolvePath(base, path)" type="string">
  Join and normalize a path against a base path without touching the filesystem.
</ParamField>

<ParamField path="getAllPaths()" type="string[]">
  Return a synchronous snapshot of paths known to the filesystem.
</ParamField>

### Mutations

<ParamField path="mkdir(path, options?)" type="Promise<void>">
  Create a directory. With `{ recursive: true }`, create missing parents and do nothing when the path already exists as a directory.
</ParamField>

<ParamField path="rm(path, options?)" type="Promise<void>">
  Remove a file or directory. Use `{ recursive: true }` for non-empty directories and `{ force: true }` to ignore missing paths.
</ParamField>

<ParamField path="cp(src, dest, options?)" type="Promise<void>">
  Copy a file or directory. Use `{ recursive: true }` for directories.
</ParamField>

<ParamField path="mv(src, dest)" type="Promise<void>">
  Move or rename a file or directory.
</ParamField>

<ParamField path="chmod(path, mode)" type="Promise<void>">
  Set permission bits, such as `0o755`.
</ParamField>

<ParamField path="symlink(target, linkPath)" type="Promise<void>">
  Create a symlink. Relative targets are stored verbatim and resolve against the parent of `linkPath` at read time.
</ParamField>

<ParamField path="utimes(path, atime, mtime)" type="Promise<void>">
  Set access and modification times with JavaScript `Date` values.
</ParamField>

<ParamField path="link(existingPath, newPath)" type="Promise<void>">
  Create a hard link if supported by the native filesystem implementation.
</ParamField>

### Subscriptions

MesaFS reads and writes are realtime by default. Use subscriptions only when your process needs an event stream that identifies which paths changed, such as to refetch data and rerender a frontend.

<ParamField path="subscribe(handler)" type="MesaFileSystemSubscription">
  Subscribe to filesystem invalidation events. The handler is called after the changed state is visible through this filesystem instance.
</ParamField>

```ts theme={null}
const subscription = fs.subscribe(async (event) => {
  if (!event.recursive) {
    const content = await fs.readFile(event.path, 'utf8');
    console.log(event.path, content);
  }
});

subscription.unsubscribe();
```

<ParamField path="handler" type="(event: WatchEvent) => void | Promise<void>">
  Callback invoked for each filesystem invalidation.
</ParamField>

<ResponseField name="WatchEvent.path" type="string">
  Absolute MesaFS path that changed, such as `/workspace/src/index.ts`.
</ResponseField>

<ResponseField name="WatchEvent.recursive" type="boolean">
  Whether descendants of `path` may have changed. Refresh any cached directory or subtree state below `path` when this is `true`.
</ResponseField>

<ResponseField name="MesaFileSystemSubscription.unsubscribe()" type="void">
  Stop receiving events and close the underlying watcher.
</ResponseField>

### FsStat

`stat(...)` and `lstat(...)` return `FsStat`.

<ResponseField name="isFile" type="boolean">
  Whether the path is a regular file.
</ResponseField>

<ResponseField name="isDirectory" type="boolean">
  Whether the path is a directory.
</ResponseField>

<ResponseField name="isSymbolicLink" type="boolean">
  Whether the path is a symlink. This is `false` from `stat(...)` when the target exists because `stat` follows symlinks.
</ResponseField>

<ResponseField name="mode" type="number">
  POSIX mode bits.
</ResponseField>

<ResponseField name="size" type="number">
  Size in bytes.
</ResponseField>

<ResponseField name="mtime" type="Date">
  Modification time.
</ResponseField>

### Related filesystem methods

| Method namespace    | Reference                                              |
| ------------------- | ------------------------------------------------------ |
| `fs.bash(...)`      | [fs.bash()](/content/reference/ts/fs-bash)             |
| `fs.change`         | [fs.change](/content/reference/ts/fs-changes)          |
| `fs.bookmark`       | [fs.bookmark](/content/reference/ts/fs-bookmarks)      |
| `fs.subscribe(...)` | [Advanced Realtime](/content/mesafs/advanced/realtime) |

## Errors

Throws `InvalidOptionsError` for a missing or empty layout, a `ttl` outside the private-key range (`1..14400` seconds), missing or rejected `authors`, or a layout that breaks the [structural rules](/content/mesafs/layouts#declaration-fields). A missing layout and author problems fail at the `mesa.fs(...)` call; the empty-layout and structural checks run at `mount()` or `token()`, before any token is minted. Token signing or VCS connection failures can throw API errors or connection errors.
