A virtual bash environment with a persistent filesystem backed by Tigris object
storage, written in TypeScript and designed for AI agents.

AI agents produce artifacts — reports, data, configs, logs. These need to go
somewhere durable, shareable, and globally accessible. Agent Shell gives agents
a familiar bash interface (`cat`, `grep`, `sed`, `jq`, `awk`, pipes, redirects)
where every file operation is backed by a Tigris bucket.

- **Isolated** — writes stay in-memory until you explicitly flush. No partial
state leaks to storage.
- **Durable** — flush persists files to Tigris, globally distributed.
- **Checkpointable** — take snapshots of your storage at any point. Roll back if
needed.
- **Forkable** — create copy-on-write forks of a bucket for safe
experimentation.
- **Shareable** — generate presigned URLs for any stored file.

Built on [just-bash](https://github.com/vercel-labs/just-bash) for the shell
engine and [@tigrisdata/storage](https://www.npmjs.com/package/@tigrisdata/storage) for the
storage layer.

## Getting Started

### Installation

- NPM
- Yarn

```bash
npm install @tigrisdata/agent-shell
```

```bash
yarn add @tigrisdata/agent-shell
```

### Programmatic Usage

```ts
import { TigrisShell } from "@tigrisdata/agent-shell";

const shell = new TigrisShell({

accessKeyId: process.env.TIGRIS_STORAGE_ACCESS_KEY_ID,

secretAccessKey: process.env.TIGRIS_STORAGE_SECRET_ACCESS_KEY,

bucket: process.env.TIGRIS_STORAGE_BUCKET, // optional — auto-mounts at cwd (default: /workspace)

});

await shell.exec('echo "Hello from agent-shell" > greeting.txt');

await shell.exec("cat greeting.txt | tr a-z A-Z");

// stdout: "HELLO FROM AGENT-SHELL"

// Persist changes to Tigris

await shell.flush();
```

### Interactive Shell

Launch a shell directly — no install needed:

```bash
npx @tigrisdata/agent-shell
```

Authenticate with access keys:

```text
$ configure --key tid_... --secret tsec_...

Mounted 2 bucket(s) at /. Run 'df' to list them.

/ $ ls

my-bucket  shared-data

/ $ cd my-bucket

/my-bucket $ echo "hello" > greeting.txt

/my-bucket $ cat greeting.txt

hello

/my-bucket $ flush

Flushed 2 mount(s)
```

Or login with your Tigris account:

```text
$ login

Open this URL in your browser:

https://auth.storage.tigrisdata.io/activate?user_code=XKCD-1234

Waiting for authorization... done!

Logged in as you@example.com

Mounted 2 bucket(s) at /. Run 'df' to list them.
```

You can also pass credentials as flags:

```bash
npx @tigrisdata/agent-shell --key tid_... --secret tsec_... --bucket my-bucket
```

## Storage Model

Agent Shell uses an in-memory write-back cache that provides isolation:

```text
Agent writes file  →  cached in memory (isolated)

Agent reads file   →  cache hit or fetch from Tigris

Agent calls flush  →  all changes persisted atomically
```

This gives you:

- **Isolation** — nothing touches storage until you say so
- **Atomic commits** — if the agent fails midway, no partial state is written
- **Fast execution** — most operations never hit the network

```ts
const shell = new TigrisShell({

accessKeyId: process.env.TIGRIS_STORAGE_ACCESS_KEY_ID,

secretAccessKey: process.env.TIGRIS_STORAGE_SECRET_ACCESS_KEY,

bucket: process.env.TIGRIS_STORAGE_BUCKET,

});

try {

await shell.exec('echo "processing..." > status.txt');

await shell.exec("echo '{\"score\": 0.95}' > results.json");

await shell.exec("cat results.json | jq .score"); // "0.95\n"

// Only persist on success

await shell.flush();

} catch (e) {

// Nothing was written to Tigris — storage is clean

}
```

## Configuration

### TigrisConfig

| Option              | Type      | Required | Description                                   |
|---------------------|-----------|----------|-----------------------------------------------|
| `bucket`            | `string`  | No       | Bucket name — auto-mounted at cwd             |
| `accessKeyId`      | `string`  | No\*    | Tigris access key ID                          |
| `secretAccessKey`   | `string`  | No\*    | Tigris secret access key                      |
| `sessionToken`      | `string`  | No\*    | Session token (from OAuth)                    |
| `organizationId`    | `string`  | No\*    | Organization ID (required with token)        |
| `endpoint`          | `string`  | No       | Tigris endpoint URL                           |

note  
At least one auth mode is required: (accessKeyId + secretAccessKey) or
(sessionToken + organizationId).

### ShellOptions

| Option | Type                        | Default     | Description                                            |
|--------|-----------------------------|-------------|--------------------------------------------------------|
| `cwd`  | `string`                   | `/workspace`| Starting working directory and auto-mount point         |
| `env`  | `Record<string, string>`    | `{}`       | Initial environment variables for the shell             |

```ts
const shell = new TigrisShell(

{

accessKeyId: process.env.TIGRIS_STORAGE_ACCESS_KEY_ID,

secretAccessKey: process.env.TIGRIS_STORAGE_SECRET_ACCESS_KEY,

bucket: process.env.TIGRIS_STORAGE_BUCKET,

},

{ cwd: "/my-project", env: { NODE_ENV: "production" } },

);
```

## API Reference

### TigrisShell

| Method / Property              | Type                               | Description                                   |
|---------------------------------|------------------------------------|-----------------------------------------------|
| `constructor`                   | `(config: TigrisConfig, options?: ShellOptions)` | Create a new shell instance                 |
| `exec(command)`                  | `Promise<BashExecResult>`          | Execute a bash command                       |
| `mount(bucket, mountPoint)`     | `void`                              | Mount a bucket at a path                    |
| `unmount(mountPoint)`           | `void`                              | Unmount a path                               |
| `listMounts()`                  | `Array<{ bucket, mountPoint }>`    | List current mounts                           |
| `flush(mountPoint?)`            | `Promise<void>`                    | Flush all mounts or a specific one            |
| `engine`                        | `Bash`                              | Underlying just-bash instance                  |

### Sub-exports

Agent Shell exposes additional modules for advanced use cases:

```ts
// Filesystem adapter for custom storage layouts

import { TigrisAdapter } from "@tigrisdata/agent-shell/fs";

// Individual Tigris command factories

import {

createTigrisCommands,

createPresignCommand,

createSnapshotCommand,

createForkCommand,

} from "@tigrisdata/agent-shell/commands";
```

## Built-in Tigris Commands

Beyond standard bash commands, Agent Shell includes Tigris-specific commands.

### presign

Generate presigned URLs for sharing or uploading:

```bash
presign /path/to/file.txt                    # GET URL, 1 hour expiry

presign /path/to/file.txt --expires 7200     # GET URL, 2 hour expiry

presign /path/to/file.txt --put              # PUT URL for uploads
```

### snapshot

Checkpoint your storage. Create or list point-in-time bucket snapshots:

```bash
snapshot my-bucket                           # Create a snapshot

snapshot my-bucket --name "checkpoint-1"     # Create a named snapshot

snapshot my-bucket --list                    # List all snapshots
```

### fork

Branch your storage. Create a copy-on-write fork for safe experimentation:

```bash
fork source-bucket my-fork                   # Fork a bucket

fork source-bucket my-fork --snapshot 1713200000   # Fork from a specific snapshot
```

### forks

List forks of a bucket:

```bash
forks my-bucket
```

## Multi-Bucket

Mount multiple buckets at different paths:

```ts
import { TigrisShell } from "@tigrisdata/agent-shell";

const shell = new TigrisShell({

accessKeyId: process.env.TIGRIS_STORAGE_ACCESS_KEY_ID,

secretAccessKey: process.env.TIGRIS_STORAGE_SECRET_ACCESS_KEY,

bucket: "agent-workspace", // auto-mounted at cwd (default: /workspace)

});

shell.mount("shared-datasets", "/datasets");

await shell.exec("cat /datasets/training/labels.csv | head -10");

await shell.exec("cp /datasets/training/labels.csv ./local-copy.csv");

await shell.exec('echo "processed" > results.txt');

// Flush all mounts, or a specific one

await shell.flush(); // all

await shell.flush("/datasets"); // just /datasets

// List and unmount

shell.listMounts();

shell.unmount("/datasets");
```

Or in the interactive shell:

```text
$ mount shared-datasets /datasets

Mounted shared-datasets at /datasets

$ cp /datasets/data.csv ./local.csv

$ df

Bucket                    Mounted on

agent-workspace           /agent-workspace

shared-datasets           /datasets

$ umount /datasets

Unmounted /datasets
```

## Examples

### Processing Files

```ts
import { TigrisShell } from "@tigrisdata/agent-shell";

const shell = new TigrisShell({

bucket: process.env.TIGRIS_STORAGE_BUCKET,

accessKeyId: process.env.TIGRIS_STORAGE_ACCESS_KEY_ID,

secretAccessKey: process.env.TIGRIS_STORAGE_SECRET_ACCESS_KEY,

});

// Read a file already in the bucket

const result = await shell.exec("cat data.csv");

console.log(result.stdout);

// Process with standard Unix tools

const sorted = await shell.exec("cat data.csv | tail -n +2 | sort -t, -k2 -rn");

console.log(sorted.stdout);

// Write results and persist

await shell.exec("cat data.csv | wc -l > stats.txt");

await shell.flush();
```

### Snapshots, Forks, and Presigned URLs

```ts
import { TigrisShell } from "@tigrisdata/agent-shell";

const shell = new TigrisShell({

bucket: process.env.TIGRIS_STORAGE_BUCKET,

accessKeyId: process.env.TIGRIS_STORAGE_ACCESS_KEY_ID,

secretAccessKey: process.env.TIGRIS_STORAGE_SECRET_ACCESS_KEY,

});

// Write a file and persist

await shell.exec('echo "quarterly report" > report.txt');

await shell.flush();

// Generate a shareable URL

const getUrl = await shell.exec("presign report.txt");

console.log(getUrl.stdout.trim());

// Checkpoint storage before changes

await shell.exec("snapshot my-bucket --name before-migration");

// Branch storage for safe experimentation

await shell.exec("fork my-bucket my-bucket-experiment");
```
