Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ These examples mount Mesa repos inside cloud sandboxes using the Mesa CLI. The C
| [blaxel-shell](blaxel-shell/) | Interactive shell in a Blaxel sandbox |
| [e2b-shell](e2b-shell/) | Interactive shell in an E2B sandbox |
| [sprites-shell](sprites-shell/) | Interactive shell in a Sprites sandbox |
| [superserve-shell](superserve-shell/) | Interactive shell in a Superserve sandbox |

## Other

Expand Down
4 changes: 4 additions & 0 deletions superserve-shell/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
MESA_ORG=
MESA_API_KEY=
# Get one at https://console.superserve.ai/
SUPERSERVE_API_KEY=
66 changes: 66 additions & 0 deletions superserve-shell/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# superserve-shell

Interactive shell over repos in a [Mesa](https://mesa.dev) org running inside a [Superserve](https://superserve.ai) sandbox, written in TypeScript.

Spins up a Superserve sandbox (Firecracker microVM, sub-second cold start), installs the Mesa CLI, mounts your org's repos via FUSE, and drops you into a minimal shell. Commands execute inside the sandbox against the mounted filesystem.

## Quick start

```bash
npm install

# Create a .env in this directory (gitignored)
cp .env.example .env

# Now populate your .env file with the required values

# Run
npm start
```

```
Creating Superserve sandbox...
Installing Mesa...
Mounting your-org...
Connected to ~/.local/share/mesa/mnt/your-org. Type "exit" or Ctrl+C to quit.

$ ls
repo-one repo-two repo-three
$ cd repo-one
$ ls
README.md src/ package.json
$ exit
Cleaning up sandbox...
```

## How it works

1. Creates a Superserve sandbox from the default `superserve/base` template
2. Installs the [Mesa CLI](https://docs.mesa.dev/content/virtual-filesystem/os-level) inside the sandbox
3. Runs `mesa mount -d -y` with your org and API key to start the FUSE daemon
4. Drops you into a REPL where commands run inside the sandbox via `sandbox.commands.run()`

Superserve sandboxes are full Firecracker microVMs running as root with a FUSE-enabled kernel, so the `user_allow_other` and `chmod 666 /dev/fuse` steps that other sandbox providers require aren't needed.

The REPL (`repl.ts`) tracks your working directory and handles `cd`, `~` expansion, and relative paths.

## Files

| File | Description |
|------|-------------|
| `index.ts` | Sandbox setup, Mesa installation and mount |
| `repl.ts` | Tiny REPL with `cd` and tilde expansion |

## Environment variables

| Variable | Description |
|----------|-------------|
| `MESA_ORG` | Your Mesa organization slug |
| `MESA_API_KEY` | Mesa API key ([get one here](https://mesa.dev)) |
| `SUPERSERVE_API_KEY` | Superserve API key ([get one here](https://superserve.ai)) |

## Requirements

- Node.js >= 18
- Mesa account with an API key
- Superserve account with an API key
83 changes: 83 additions & 0 deletions superserve-shell/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env node

// To run this example, create a .env file in this directory with:
// MESA_ORG=your-org
// MESA_API_KEY=your-mesa-key
// SUPERSERVE_API_KEY=your-superserve-key
//
// Then run:
// npm start

import 'dotenv/config';
import { Sandbox } from '@superserve/sdk';
import tinySuperserveRepl from './repl.ts';

const ORG =
process.env.MESA_ORG ??
(() => {
throw Error('$MESA_ORG not set.');
})();
const MESA_API_KEY =
process.env.MESA_API_KEY ??
(() => {
throw Error('$MESA_API_KEY not set.');
})();
if (!process.env.SUPERSERVE_API_KEY) {
throw Error('$SUPERSERVE_API_KEY not set.');
}

console.log('Creating Superserve sandbox...');
const sandbox = await Sandbox.create({ name: 'mesa-shell' });

try {
// Set up Mesa within the Superserve sandbox.
//
// We recommend installing Mesa as part of a custom template, but here we
// install it directly to keep the example small.

// You can install Mesa as per the guide in https://docs.mesa.dev/content/virtual-filesystem/os-level.
//
// Mesa's installer will install all its dependencies through your system's package manager.
console.log('Installing Mesa...');
await sandbox.commands.run('curl -fsSL https://mesa.dev/install.sh | sh -s -- --yes');

// Superserve sandboxes run as root inside a Firecracker microVM with a
// FUSE-enabled kernel, so the `user_allow_other` and `chmod 666 /dev/fuse`
// steps that other sandbox providers require aren't needed here.

// You can run mesa in daemon mode to kick it off in the background.
//
// The flags we are using here are:
// -d, --daemonize Spawns mesa in the background
// -y, --non-interactive Tells mesa to use the default values for all its configuration values. It will create a
// new config file for you.
//
// We also pass the environment variable:
// MESA_ORGS=<org>:<api-key>,... Tells mesa to configure the given organization with the given API key.
// Mesa will store this information in its configuration file. See
// https://docs.mesa.dev/content/reference/mesa-cli-configuration for more details.
//
// Note that mesa will write the orgs to the config file the first time it is booted up, so you do not need to
// specify it again. When mesa is already configured, it will append the orgs given through the environment to the
// ones in the config.toml.
//
// Additionally, we recommend creating and specifying an ephemeral key which persists for the lifetime of the sandbox,
// rather than using the main API key. In the spirit of keeping this example small, we use the main API key. See
// https://docs.mesa.dev/content/getting-started/auth-and-permissions for more details.
console.log(`Mounting ${ORG}...`);
await sandbox.commands.run('mesa mount -d -y', {
env: {
MESA_ORGS: `${ORG}:${MESA_API_KEY}`,
},
});

// You can now explore repos in your org. We've written a tiny REPL here you can use to explore the sandbox.
//
// The default configuration is created in ~/.config/mesa/config.toml
// and your files will be in ~/.local/share/mesa/mnt/<org>/<repo>
await tinySuperserveRepl(sandbox, { cwd: `~/.local/share/mesa/mnt/${ORG}` });
} finally {
// No matter what happens, let's make sure we clean up the sandbox so we don't burn Superserve resources.
console.log('\nCleaning up sandbox...');
await sandbox.kill();
}
18 changes: 18 additions & 0 deletions superserve-shell/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "superserve-shell",
"version": "0.1.0",
"description": "Interactive shell over a Mesa cloud repo running in a Superserve sandbox",
"type": "module",
"scripts": {
"start": "tsx index.ts"
},
"dependencies": {
"@superserve/sdk": "^0.7.0",
"dotenv": "^17.0.0"
},
"devDependencies": {
"@types/node": "^22.0.0",
"tsx": "^4.0.0",
"typescript": "^5.8.0"
}
}
128 changes: 128 additions & 0 deletions superserve-shell/repl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import path from 'node:path';
import * as readline from 'node:readline';
import type { Sandbox } from '@superserve/sdk';

/**
* Options parameter for the @see tinySuperserveRepl function.
*/
export interface ISuperserveReplOptions {
/** The current working directory. */
cwd: string | undefined;
}

type CommandResult = {
exitCode: number;
stdout: string;
stderr: string;
};

function expandTilde(filePath: string, homedir: string): string {
return filePath.startsWith('~') ? path.posix.join(homedir, filePath.slice(1)) : filePath;
}

class ShellState {
#homedir: string;
#cwd: string;
#sandbox: Sandbox;

private constructor(homedir: string, cwd: string, sandbox: Sandbox) {
this.#homedir = homedir;
this.#cwd = cwd;
this.#sandbox = sandbox;
}

static async create(sandbox: Sandbox, cwd: string): Promise<ShellState> {
const homedir = await ShellState.probeHome(sandbox);
return new ShellState(homedir, expandTilde(cwd, homedir), sandbox);
}

/** Execute a shell command. Handles `cd` gracefully enough. */
async run(cmd: string): Promise<CommandResult | null> {
const trimmed: string = cmd.trim();
if (!trimmed) {
return null;
}

const splitCommand = trimmed.split(/\s+/);
if (splitCommand[0] === 'cd') {
const target = splitCommand[1] ?? '~';
this.#cwd = path.posix.isAbsolute(target)
? splitCommand[1]
: path.posix.join(this.#cwd, this.expandTilde(target));
return null;
}

const result = await this.#sandbox.commands.run(`cd ${this.#cwd} && ${trimmed}`);
return {
exitCode: result.exitCode,
stdout: result.stdout,
stderr: result.stderr,
};
}

private static async probeHome(sandbox: Sandbox): Promise<string> {
const result = await sandbox.commands.run('echo $HOME');
return result.stdout.trim();
}

private expandTilde(filePath: string): string {
return expandTilde(filePath, this.#homedir);
}
}

function question(rl: readline.Interface, prompt: string): Promise<string | null> {
return new Promise((resolve) => {
rl.once('close', () => resolve(null));
rl.question(prompt, (answer) => resolve(answer));
});
}

/**
* Spawns the REPL so that users can use it.
*
* This is a tiny REPL and is not a full shell, so it has an incredibly limited subset of regular shell commands.
*/
export default async function tinySuperserveRepl(
sandbox: Sandbox,
options: ISuperserveReplOptions | undefined
): Promise<void> {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});

const shell = await ShellState.create(sandbox, options?.cwd ?? '~');

console.log(`Connected to ${options?.cwd ?? '~'}. Type "exit" or Ctrl+C to quit.\n`);

while (true) {
const input = await question(rl, '$ ');

if (input === null) {
break;
}

if (input.trim() === 'exit') {
break;
}

const result = await shell.run(input);
if (result === null) {
continue;
}

const { exitCode, stdout, stderr } = result;

if (stdout) {
process.stdout.write(stdout);
}

if (stderr) {
process.stderr.write(stderr);
}

if (exitCode !== null && exitCode !== 0) {
console.error(`The process exited with a non-zero exit code: ${exitCode}`);
}
}
}
10 changes: 10 additions & 0 deletions superserve-shell/sst-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* This file is auto-generated by SST. Do not edit. */
/* tslint:disable */
/* eslint-disable */
/* deno-fmt-ignore-file */
/* biome-ignore-all lint: auto-generated */

/// <reference path="../../../sst-env.d.ts" />

import "sst"
export {}
14 changes: 14 additions & 0 deletions superserve-shell/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"declaration": true,
"emitDeclarationOnly": true,
"allowImportingTsExtensions": true
},
"include": ["index.ts", "repl.ts"]
}