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

# App Mount

> Run Mesa as an in-process virtual filesystem

App mounts let your agent or backend work with Mesa repositories without needing cloning, FUSE, or a sandbox.

MesaFS app mount is available in both the TypeScript and Python SDKs. There are two main ways of interacting the with app mount:

* **Basic Filesystem API**: call methods like `readFile` and `writeFile` to directly access files
* **Emulated Bash**: Execute shell commands using tools like `ls`, `cp`, `grep`. Runs against your Mesa repositories, entirely in-process.

Both read and write operations are supported.

<Note>
  MesaFS can be run as either a POSIX mount or app mount. If you aren't sure which to use, see [Filesystem](/content/concepts/filesystem) for an overview and
  comparison.
</Note>

## Quick Start

### Prerequisites

* A Mesa account. If you haven't signed up yet, you can do so [here](https://app.mesa.dev/).
* An API key with admin scope. You can create one at `https://app.mesa.dev/<your-org>/tokens`.
* A repository to access. You can create one at `https://app.mesa.dev/<your-org>/repositories`.

<Note>
  Running in Docker with a slim base image? See [Docker](/content/mesafs/posix-mount#docker)
  for required system packages.
</Note>

### Install the SDK

<CodeGroup>
  ```bash TypeScript theme={null}
  npm install @mesadev/sdk
  ```

  ```bash Python theme={null}
  pip install mesa-sdk
  ```
</CodeGroup>

### Create a Mesa Filesystem

Initialize a Mesa client and a filesystem handle scoped to the repos you want to access. You can create as many filesystem handles as you need.

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { Mesa } from "@mesadev/sdk";

  const mesa = new Mesa({ apiKey: process.env.MESA_API_KEY });

  // Mount a Mesa filesystem scoped to your repos
  const fs = await mesa.fs.mount({
    repos: [{ name: "my-repo", bookmark: "main" }],
  });
  ```

  ```python Python theme={null}
  import os
  from mesa_sdk import Mesa, RepoConfig

  mesa = Mesa(api_key=os.environ["MESA_API_KEY"])

  # Mount a Mesa filesystem scoped to your repos
  async with mesa.fs.mount(
      repos=[RepoConfig(name="my-repo", bookmark="main")],
  ) as fs:
      # ...
  ```
</CodeGroup>

### Run Bash commands or directly read/write files

<CodeGroup>
  ```typescript TypeScript theme={null}
  const fs = await mesa.fs.mount(...);

  // Get a bash shell backed by Mesa
  const bash = fs.bash();

  // Run shell commands
  const result = await bash.exec("ls /my-org/my-repo/src");
  console.log(result.stdout);

  // OR directly read/write 
  await fs.readFile('/my-org/my-repo/README.md')
  ```

  ```python Python theme={null}
  async with mesa.fs.mount(...) as fs:
      # Get a bash shell backed by Mesa
      bash = fs.bash()

      # Run shell commands
      result = await bash.exec("ls /my-org/my-repo/src")
      print(result.stdout.decode())

      # OR directly read/write files
      await fs.read('/my-org/my-repo/README.md')
  ```
</CodeGroup>

## Managing Changes and Bookmarks

Beyond file operations, the Mesa filesystem exposes APIs to create/switch changes and manage bookmarks (analogous to branches in git) directly from TypeScript.

<CodeGroup>
  ```typescript TypeScript theme={null}
  // Create a new change from a bookmark and switch to it
  await fs.change.new({
    repo: "my-repo",
    bookmark: "main",
  });

  const current = await fs.change.current({ repo: "my-repo" });

  // Switch to an existing change (does not create a new one)
  await fs.change.edit({
    repo: "my-repo",
    changeId: current.changeId,
  });

  // Create a bookmark at the current commit
  await fs.bookmark.create({
    repo: "my-repo",
    name: "feature/agent-output",
  });

  // List bookmark names
  const bookmarks = await fs.bookmark.list({ repo: "my-repo" });
  console.log(bookmarks);
  ```

  ```python Python theme={null}
  # Create a new change from a bookmark and switch to it
  await fs.changes.new("my-repo", bookmark="main")

  current = await fs.changes.current("my-repo")

  # Switch to an existing change (does not create a new one)
  await fs.changes.edit("my-repo", change_id=current.change_id)

  # Create a bookmark at the current commit
  await fs.bookmarks.create("my-repo", "feature/agent-output")

  # List bookmark names
  bookmarks = await fs.bookmarks.list("my-repo")
  print(bookmarks)
  ```
</CodeGroup>

<Tip>
  `fs.change.new(...)` always creates a new change. `fs.change.edit(...)` never creates a new change, it only switches to an existing one.
</Tip>

## How It Works

Mesa's app mount mode uses the same core read, write, caching, and version control logic as the POSIX mount -- just running in-process.

To provide an emulated bash shell, Mesa uses [just-bash](https://github.com/vercel-labs/just-bash) (TypeScript) or [Bashkit](https://github.com/everruns/bashkit) (Python) with MesaFS as the backing filesystem.

## Integrating with Agents

Mesa's app mount is designed to be used with any agent framework. By providing the app mount's bash function
as a tool, you can easily bring MesaFS into an agent's workflow. For end-to-end examples, see the [Mesa examples repo](https://github.com/mesa-dot-dev/examples).

### Vercel AI SDK

To define a [tool in the Vercel AI SDK](https://ai-sdk.dev/docs/ai-sdk-core/tools-and-tool-calling), you can use the `tool` function.

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { tool } from 'ai';
  import { z } from 'zod';

  const bashTool = tool({
    description: 'Execute bash commands',
    inputSchema: z.object({
      command: z.string(),
    }),
    execute: async ({ command }) => {
      return await bash.exec(command);
    },
  });
  ```

  ```python Python theme={null}
  async def bash_tool(command: str):
      """Execute bash commands against MesaFS."""
      return await bash.exec(command)
  ```
</CodeGroup>

### Langchain

To define a [tool in Langchain](https://docs.langchain.com/oss/javascript/langchain/tools), you can use the `tool` function.

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { tool } from 'langchain';
  import { z } from 'zod';

  const bashTool = tool(
    ({ command }) => bash.exec(command),
    {
      name: 'bash',
      description: 'Execute bash commands',
      schema: z.object({
        command: z.string(),
      }),
    }
  );
  ```

  ```python Python theme={null}
  async def bash_tool(command: str):
      """Execute bash commands against MesaFS."""
      return await bash.exec(command)
  ```
</CodeGroup>

### Mastra

To define a [tool in Mastra](https://mastra.ai/docs/agents/using-tools), you can use the `createTool` function.

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { createTool } from '@mastra/core/tools';
  import { z } from 'zod';

  const bashTool = createTool({
    id: 'bash-tool',
    description: 'Execute bash commands',
    inputSchema: z.object({
      command: z.string(),
    }),
    outputSchema: z.object({
      stdout: z.string(),
      stderr: z.string(),
      exitCode: z.number(),
    }),
    execute: async ({ command }) => {
      return await bash.exec(command);
    },
  });
  ```

  ```python Python theme={null}
  async def bash_tool(command: str):
      """Execute bash commands against MesaFS."""
      result = await bash.exec(command)
      return {
          "stdout": result.stdout.decode(),
          "stderr": result.stderr.decode(),
          "exit_code": result.exit_code,
      }
  ```
</CodeGroup>

## Configuring the Shell

The `.bash()` method accepts options to configure the shell environment. Mesa's Bash implementation is is a thin wrapper over the underlying `just-bash` library,
so all of the exposed options are pure passthrough to the underlying `just-bash` `Bash` instance. We omit some options that are not relevant to a typical Mesa `bash()` setup.
For more details, you can refer to the [just-bash documentation](https://github.com/vercel-labs/just-bash).

<Note>
  Options like `fs` and `files` from `BashOptions` are intentionally omitted -- the filesystem is always
  the `MesaFileSystem` instance, and files are populated from your Mesa repositories.
</Note>

<Tip>
  If you need to use advanced just-bash features such as overlay filesystems, you should separately install the `just-bash` package and use the `MesaFileSystem` instance directly to construct a `Bash` instance.

  <CodeGroup>
    ```typescript TypeScript theme={null}
    import { Mesa } from "@mesadev/sdk";
    import { Bash } from "just-bash";

    const mesa = new Mesa();
    const fs = await mesa.fs.mount({ repos: [{ name: "my-repo" }] });
    const bash = new Bash({ fs, ...otherOptions });
    ```

    ```python Python theme={null}
    # Python's Mesa SDK exposes fs.bash(env=..., cwd=..., timeout_ms=...). Advanced
    # just-bash constructor options are TypeScript-only today.
    mesa = Mesa()

    async with mesa.fs.mount(repos=["my-repo"]) as fs:
        bash = fs.bash(cwd="/my-org/my-repo")
    ```
  </CodeGroup>
</Tip>

For convenience, here is a summary of the options exposed by Mesa's Bash instance.

### cwd (Current Working Directory)

The `cwd` option sets the starting directory for the shell. The default is `/home/user`, and Mesa mounts your repos at `/<org>/<repo>`.
In most cases, you will want to set `cwd` to either `/<org>/<repo>` or, for multi-repo filesystems, `/<org>`.

<CodeGroup>
  ```typescript TypeScript theme={null}
  const bash = fs.bash({
    cwd: "/my-org/my-repo",
  });

  // Now commands run relative to /my-org/my-repo
  await bash.exec("ls src");
  ```

  ```python Python theme={null}
  bash = fs.bash(cwd="/my-org/my-repo")

  # Now commands run relative to /my-org/my-repo
  await bash.exec("ls src")
  ```

  ```bash CLI theme={null}
  cd ~/.local/share/mesa/mnt/my-org/my-repo

  # Now commands run relative to /my-org/my-repo
  ls src
  ```
</CodeGroup>

### env (Environment Variables)

The `env` option sets the environment variables available to commands. For compatibility, the defaults simulate a typical Linux/GNU environment,
ex. `PATH`, `HOME`, `PWD`, and `OLDPWD`.

<CodeGroup>
  ```typescript TypeScript theme={null}
  const bash = fs.bash({
    env: { NODE_ENV: "production" },
  });

  // Now commands run with NODE_ENV=production
  await bash.exec("echo $NODE_ENV");
  // Output: production
  ```

  ```python Python theme={null}
  bash = fs.bash(env={"NODE_ENV": "production"})

  # Now commands run with NODE_ENV=production
  await bash.exec("echo $NODE_ENV")
  # Output: production
  ```

  ```bash CLI theme={null}
  NODE_ENV=production sh -c 'echo "$NODE_ENV"'
  # Output: production
  ```
</CodeGroup>

### executionLimits (Resource Limits)

The `executionLimits` option sets iteration limits for commands, which protects against infinite loops and deep recursion.
All of these limits are optional and have [reasonable defaults](https://github.com/vercel-labs/just-bash/blob/main/src/limits.ts).
You can override them to suit your needs.

<CodeGroup>
  ```typescript TypeScript theme={null}
  const bash = fs.bash({
    executionLimits: {
      maxCallDepth: 100, // Max function recursion depth
      maxCommandCount: 10000, // Max total commands executed
      maxLoopIterations: 10000, // Max iterations per loop
      maxAwkIterations: 10000, // Max iterations in awk programs
      maxSedIterations: 10000, // Max iterations in sed scripts
    },
  });
  ```

  ```python Python theme={null}
  # Python MesaFS does not expose just-bash executionLimits today.
  # Use timeout_ms for a wall-clock limit on each bash.exec(...) call.
  bash = fs.bash(timeout_ms=30_000)
  ```
</CodeGroup>

### fetch (Custom Fetch)

The `fetch` option allows you to use a custom fetch implementation for network access. This is useful if you want to use a different HTTP client or proxy.

<CodeGroup>
  ```typescript TypeScript theme={null}
  const bash = fs.bash({
    fetch: customFetch,
  });
  ```

  ```python Python theme={null}
  # Custom fetch injection is TypeScript-only today.
  bash = fs.bash()
  ```
</CodeGroup>

### network (Network Access)

Network access is disabled by default. You can enable it with the network option.

<CodeGroup>
  ```typescript TypeScript theme={null}
  // Allow specific URLs with additional methods
  const bash = fs.bash({
    network: {
      allowedUrlPrefixes: ["https://api.example.com"],
      allowedMethods: ["GET", "HEAD", "POST"], // Default: ["GET", "HEAD"]
    },
  });

  // Inject credentials via header transforms (secrets never enter the sandbox)
  const authenticatedBash = fs.bash({
    network: {
      allowedUrlPrefixes: [
        "https://public-api.com", // plain string, no transforms
        {
          url: "https://ai-gateway.vercel.sh",
          transform: [{ headers: { Authorization: "Bearer secret" } }],
        },
      ],
    },
  });

  // Allow all URLs and methods (use with caution)
  const unrestrictedBash = fs.bash({
    network: { dangerouslyAllowFullInternetAccess: true },
  });
  ```

  ```python Python theme={null}
  # Network allowlists and header transforms are TypeScript-only today.
  bash = fs.bash()
  ```
</CodeGroup>

### python (Python Runtime)

Python (CPython compiled to WASM) is opt-in due to additional security surface. Enable with `python: true`:

<CodeGroup>
  ```typescript TypeScript theme={null}
  const bash = fs.bash({
    python: true,
  });

  // Execute Python code
  await bash.exec('python3 -c "print(1 + 2)"');

  // Run Python scripts
  await bash.exec('python3 script.py');
  ```

  ```python Python theme={null}
  # The embedded Python runtime toggle is TypeScript-only today.
  bash = fs.bash()
  ```
</CodeGroup>

### javascript (JavaScript Runtime)

JavaScript and TypeScript execution via QuickJS is opt-in due to additional security surface. Enable with `javascript: true`:

<CodeGroup>
  ```typescript TypeScript theme={null}
  const bash = fs.bash({
    javascript: true,
  });

  // Execute JavaScript code
  await bash.exec('js-exec -c "console.log(1 + 2)"');

  // Run script files (.js, .mjs, .ts, .mts)
  await bash.exec('js-exec script.js');

  // ES module mode with imports
  await bash.exec('js-exec -m -c "import fs from \'fs\'; console.log(fs.readFileSync(\'/data/file.txt\', \'utf8\'))"');
  ```

  ```python Python theme={null}
  # The embedded JavaScript runtime toggle is TypeScript-only today.
  bash = fs.bash()
  ```
</CodeGroup>

### commands (Built-in Commands)

The `commands` option allows you restrict the built-in commands that are available. This is useful if you want to limit what your agents can do.
By default, all 90+ built-in commands are enabled.

<CodeGroup>
  ```typescript TypeScript theme={null}
  const bash = fs.bash({
    commands: ["ls", "cd", "pwd"], // Enable only the specified commands.
  });
  ```

  ```python Python theme={null}
  # Built-in command allowlists are TypeScript-only today.
  bash = fs.bash()
  ```
</CodeGroup>

### customCommands (User-Defined Commands)

The `customCommands` option allows you to provide your own custom commands to the shell.

<CodeGroup>
  ```typescript TypeScript theme={null}
  import { defineCommand } from "just-bash";

  const hello = defineCommand("hello", async (args, ctx) => {
    const name = args[0] || "world";
    return { stdout: `Hello, ${name}!\n`, stderr: "", exitCode: 0 };
  });

  const upper = defineCommand("upper", async (args, ctx) => {
    return { stdout: ctx.stdin.toUpperCase(), stderr: "", exitCode: 0 };
  });

  const bash = fs.bash({ customCommands: [hello, upper] });
  const result = await bash.exec("upper \"Mesa is great!\"");
  console.log(result.stdout);
  // Output: MESA IS GREAT!
  ```

  ```python Python theme={null}
  # User-defined just-bash commands are TypeScript-only today.
  bash = fs.bash()
  ```
</CodeGroup>

### logger (Logging Hooks)

The `logger` option allows you to hook into the internal logging of the shell.
This is useful if you want to trace the execution of commands for debugging or monitoring.

<CodeGroup>
  ```typescript TypeScript theme={null}
  const bash = fs.bash({
    logger: {
      info(message, data) {
        console.log(`[INFO] ${message}:`, data);
      },
      debug(message, data) {
        console.log(`[DEBUG] ${message}:`, data);
      },
    },
  });

  await bash.exec("echo hello");
  // Logs:
  // [INFO] exec: { command: "echo hello" }
  // [DEBUG] stdout: { output: "hello\n" }
  // [INFO] exit: { exitCode: 0 }
  ```

  ```python Python theme={null}
  # just-bash logger hooks are TypeScript-only today.
  bash = fs.bash()
  ```
</CodeGroup>

### Available options

| Option            | Description                                    |
| ----------------- | ---------------------------------------------- |
| `env`             | Environment variables available to commands    |
| `cwd`             | Starting directory for command execution       |
| `executionLimits` | Iteration limits for commands                  |
| `fetch`           | Custom fetch implementation for network access |
| `network`         | Enable network access                          |
| `python`          | Enable Python runtime                          |
| `javascript`      | Enable JavaScript runtime                      |
| `commands`        | Which built-in commands to enable              |
| `customCommands`  | User-defined custom commands                   |
| `logger`          | Logging interface                              |

## Direct Filesystem Operations

You can also use the Mesa filesystem directly without going through bash.
The filesystem implements the full `just-bash` [`IFileSystem` interface](https://github.com/vercel-labs/just-bash/blob/main/src/fs/interface.ts):

<CodeGroup>
  ```typescript TypeScript theme={null}
  // Read a file
  const content = await fs.readFile("/my-org/my-repo/src/index.ts");

  // Write a file
  await fs.writeFile("/my-org/my-repo/src/new-file.ts", "export const x = 1;");

  // Check if a file exists
  const exists = await fs.exists("/my-org/my-repo/package.json");

  // List a directory
  const entries = await fs.readdir("/my-org/my-repo/src");

  // Copy files
  await fs.cp("/my-org/my-repo/src/a.ts", "/my-org/my-repo/src/b.ts");

  // Remove files
  await fs.rm("/my-org/my-repo/src/old-file.ts");
  ```

  ```python Python theme={null}
  # Read a file
  content = await fs.read("/my-org/my-repo/src/index.py")

  # Write a file
  await fs.write("/my-org/my-repo/src/new_file.py", b"x = 1\n")

  # Check if a file exists
  exists = await fs.exists("/my-org/my-repo/pyproject.toml")

  # List a directory
  entries = await fs.readdir("/my-org/my-repo/src")

  # Copy files
  await fs.cp("/my-org/my-repo/src/a.py", "/my-org/my-repo/src/b.py")

  # Remove files
  await fs.rm("/my-org/my-repo/src/old_file.py")
  ```

  ```bash CLI theme={null}
  cat /my-org/my-repo/src/index.ts
  printf 'export const x = 1;\n' > /my-org/my-repo/src/new-file.ts
  test -e /my-org/my-repo/package.json
  ls /my-org/my-repo/src
  cp /my-org/my-repo/src/a.ts /my-org/my-repo/src/b.ts
  rm /my-org/my-repo/src/old-file.ts
  ```
</CodeGroup>

<Tip>
  The filesystem supports symlinks, permissions (`chmod`), timestamps (`utimes`), and recursive directory operations.
  Hard links are not supported and will return `ENOTSUP`.
</Tip>

## Caching

For better performance on repeated reads, configure a disk cache:

<CodeGroup>
  ```typescript TypeScript theme={null}
  const fs = await mesa.fs.mount({
    repos: [{ name: "my-repo", bookmark: "main" }],
    mode: "rw",
    cache: {
      diskCache: {
        path: `/tmp/mesa-cache/my-org/my-repo`, // Provide a unique path for each filesystem to avoid conflicts.
        maxSizeBytes: 1024 * 1024 * 1024, // 1 GB
      },
    },
  });
  ```

  ```python Python theme={null}
  from mesa_sdk import DiskCacheConfig, RepoConfig

  async with mesa.fs.mount(
      repos=[RepoConfig(name="my-repo", bookmark="main")],
      mode="rw",
      disk_cache=DiskCacheConfig(
          path="/tmp/mesa-cache/my-org/my-repo",  # Provide a unique path for each filesystem to avoid conflicts.
          max_size_bytes=1024 * 1024 * 1024,  # 1 GB
      ),
  ) as fs:
      ...
  ```
</CodeGroup>

## Limitations

* By default, `just-bash` does not support installing dependencies (ex. running `npm install`) or executing arbitrary code (although you can enable Python and JavaScript execution via the `python` and `javascript` options).
* Mesa just-bash is not currently supported in the browser or browser-like runtimes like Cloudflare Workers or Vercel Edge Functions. NOTE: this limitation is due to the use of NAPI -- we will likely support a browser-runtime version in the future via a WASM build.
* Bun support is experimental and may not work in all cases. This is due to Bun's incomplete support for NAPI.
