Run an existing application

Point an application's existing provider boundary at one local instance, then arrange and inspect that same world.

This guide assumes the runtime and one emulator plugin are installed and mounted in localhost.config.ts. Start with Getting started if they are not.

Keep the application path

The example application's entire provider boundary is ordinary HTTP and two environment values:

src/read-workspace.ts
type UsersListResponse =
	| { members: Array<{ id: string; name: string }>; ok: true }
	| { error: string; ok: false };

const apiUrl = requiredEnvironment("SLACK_API_URL");
const botToken = requiredEnvironment("SLACK_BOT_TOKEN");
const response = await fetch(new URL("users.list", apiUrl), {
	headers: { authorization: `Bearer ${botToken}` },
});
const payload = (await response.json()) as UsersListResponse;

if (!response.ok || !payload.ok) {
	throw new Error(
		`Local Slack request failed: ${"error" in payload ? payload.error : response.status}`,
	);
}

console.log(JSON.stringify(payload.members.map(({ id, name }) => ({ id, name }))));

function requiredEnvironment(name: string): string {
	const value = process.env[name];
	if (!value) throw new Error(`Missing ${name}. Run this command through localhost run.`);
	return value;
}

Keep this same SDK, HTTP client, callback handler, or adapter in local and production runs. Make its endpoint and credentials configurable; do not add a second implementation of provider behavior. Some SDKs accept a base URL directly, while others need the transport adapter documented by the installed plugin. Check Provider compatibility before relying on a route.

Application code does not import the localhost2137 control client. Control operations arrange and inspect the local world from scripts or tests.

Run one persistent world

# Terminal 1: keep the daemon in the foreground
pnpm exec localhost dev
# Terminal 2: the next line is only for the crash-course config's first unseeded dev world
# pnpm exec localhost seed

# Inspect the app-facing values, then run the normal app command
pnpm exec localhost env --format json
pnpm exec localhost run -- pnpm dev

localhost run overrides plugin-owned connection variables for that child process. It does not inject the runtime control token, alter unrelated variables, restart the child, or replace its standard streams. Tools that already load dotenv can instead use the .env path printed by dev.

Changing localhost.config.ts requires a daemon restart. Restarting preserves the persistent dev world; seed, reset, and destroy change world state explicitly.

Arrange, run, inspect

Use the concrete service key from localhost.config.ts. The crash-course project mounts Slack as slack, so the complete loop is:

# Discover the installed plugin's actual control surface
pnpm exec localhost describe slack --json
pnpm exec localhost exec slack --help
pnpm exec localhost exec slack create-user --help

# Arrange state the application reads
pnpm exec localhost exec slack create-user --name Grace --json

# Exercise the application through provider-shaped traffic; its output includes Grace
pnpm exec localhost run -- pnpm dev

# Collect evidence from that same service and instance
pnpm exec localhost logs slack --tail 50 --json

Generated operation help is the source of truth for flags and nested JSON input. exec is for local actors and inspection; it is not an alternate provider API for application code. Runtime logs contain request, operation, delivery, and plugin entries. Prefer an application assertion for application behavior and use logs to find the first boundary that failed.

If arranged data is missing, compare the instance and service in the application's connection URL with every exec target. Operations and emulated APIs explains how both interfaces reach one state without being interchangeable.

Run the same app against another world

pnpm exec localhost instance create review-42 --seed
pnpm exec localhost exec slack create-user \
  --instance review-42 --name Lin --json
pnpm exec localhost env --instance review-42 --format json
# Output includes Lin and does not include Grace
pnpm exec localhost run --instance review-42 -- pnpm dev
pnpm exec localhost logs slack --instance review-42 --tail 50 --json
pnpm exec localhost instance destroy review-42

Every command in a scenario must select the same instance. Instances isolate state, clock, logs, and routes; they still share plugin config and callback destinations. Read Instances and isolation before parallelizing callback-driven scenarios. For temporary ownership and guaranteed cleanup, continue with Integration testing.