# Bash

Run bash inside the VM. Shell commands are the simplest agentOS execution
surface, and share the same execution lifecycle as JavaScript, TypeScript,
Python, and package workflows.

- **`process.exec()`**: Runs a bash command line. Use for pipes, redirects, globs.
- **`process.execFile()`**: Injection-safe; args are never parsed by a shell.
- **In-VM only**: Commands run inside the VM, never in the host shell.

For multi-step work, consider letting the agent write [JavaScript](/agentos/docs/javascript)
or [Python](/agentos/docs/python) instead — one round trip and real data structures
rather than a chain of shell calls.

## Run commands

examples/quickstart/bash/index.ts:

```ts
import { AgentOs } from "@rivet-dev/agentos";

const runtime = await AgentOs.create();

try {
	// No capture: you get the outcome and exit code only.
	await runtime.process.exec("mkdir -p /workspace/demo");

	// Capture "stderr" for diagnostics, "all" for both streams.
	const listing = await runtime.process.exec("ls -la /workspace", {
		output: { capture: "all" },
	});
	console.log(listing.outcome === "succeeded" ? listing.stdout : listing.error);

	// Pipes, redirects, and globs work — it's a real shell.
	const piped = await runtime.process.exec("echo 'hello world' | tr a-z A-Z", {
		output: { capture: "all" },
	});
	console.log(piped.stdout?.trim()); // "HELLO WORLD"

	// execFile sends each arg verbatim — no shell parses it, so no injection.
	const raw = await runtime.process.execFile("echo", ["$(whoami)"], {
		output: { capture: "all" },
	});
	console.log(raw.stdout?.trim()); // "$(whoami)" — printed literally, never run
} finally {
	await runtime.dispose();
}
```

## Background and interactive work

`spawn` starts a long-lived process and returns a `pid`. From there you get
stdin, a PTY, output replay, signals, and waiting.

examples/quickstart/bash/spawn.ts:

```ts
import { AgentOs } from "@rivet-dev/agentos";

const runtime = await AgentOs.create();

try {
	const shell = await runtime.process.spawn("sh", [], {
		output: { retainEvents: true }, // buffer output so it can be replayed
	});

	await runtime.process.writeStdin(shell.pid, "echo from the background\n");
	await runtime.process.resizePty(shell.pid, { cols: 120, rows: 40 });
	await runtime.process.closeStdin(shell.pid);

	const exit = await runtime.process.wait(shell.pid);
	const replay = await runtime.process.readOutput(shell.pid);
	console.log(exit.exitCode, replay.events.length); // 0 1
} finally {
	await runtime.dispose();
}
```

## Files and software

Shell commands see the persistent [filesystem](/agentos/docs/filesystem) shared by
agents, JavaScript, and Python. Common POSIX commands ship by default; more is
projected through the [software registry](/agentos/docs/software).

examples/quickstart/bash/files.ts:

```ts
import { AgentOs } from "@rivet-dev/agentos";

const runtime = await AgentOs.create();

try {
	// Files written by the shell are the same files agents, JavaScript, and
	// Python see — one persistent VM filesystem.
	await runtime.process.exec("printf 'a\\nb\\nc\\n' > /workspace/out.txt");

	const lines = await runtime.process.exec("cat /workspace/out.txt | wc -l", {
		output: { capture: "all" },
	});
	console.log(lines.stdout?.trim()); // "3"
} finally {
	await runtime.dispose();
}
```

## Bindings

[Bindings](/agentos/docs/bindings) appear as commands, so pipelines use trusted host
capabilities without putting credentials inside the VM.

examples/bindings/exec-bash.ts:

```ts
import { AgentOs, type Bindings } from "@rivet-dev/agentos";
import { z } from "zod";

// The handler runs on the host, so the API key never enters the VM.
const weather: Bindings = {
	name: "weather",
	description: "Weather data bindings",
	bindings: {
		forecast: {
			description: "Get the weather forecast for a city",
			inputSchema: z.object({ city: z.string() }),
			execute: async ({ city }: { city: string }) => {
				const res = await fetch(
					`https://api.weather.example/forecast?city=${city}&key=${process.env.WEATHER_API_KEY}`,
				);
				return res.json();
			},
		},
	},
};

// The collection is projected into the VM as an `agentos-weather` command, so
// it composes with pipes and redirects like any other program.
const runtime = await AgentOs.create({ bindings: [weather] });

try {
	const result = await runtime.process.exec(
		"agentos-weather forecast --city Paris > /workspace/forecast.json && wc -c < /workspace/forecast.json",
		{ output: { capture: "all" } },
	);
	console.log(result.outcome === "succeeded" ? result.stdout : result.error);
} finally {
	await runtime.dispose();
}
```

## Permissions, limits, and timeouts

Every command inherits the VM [permission policy](/agentos/docs/permissions) and
[resource limits](/agentos/docs/resource-limits).

examples/quickstart/bash/limits.ts:

```ts
import { AgentOs } from "@rivet-dev/agentos";

const runtime = await AgentOs.create();

try {
	// timeoutMs is a wall-clock deadline. It expires into a result rather than
	// throwing; a denied operation gets the normal POSIX error.
	const result = await runtime.process.exec("sleep 10", { timeoutMs: 1_000 });
	console.log(result.outcome); // "timed_out"
} finally {
	await runtime.dispose();
}
```
