Code mode for agents.
Turn any API into a callable action. Install a plugin, write JavaScript, call actions. The code runs in a QuickJS WASM sandbox — no filesystem, no network, just plugin actions via a proxy.
npm install -g runlinenpm install -g runline
runline init
runline connection add bf --plugin brandfetch --set apiKey=xxx
runline exec 'return await brandfetch.brand.getColors({ domain: "nike.com" })'
# => [{ hex: "#E5E5E5", type: "accent" }, { hex: "#111111", type: "dark" }, ...]All 188 plugins ship bundled inside runline — no per-plugin install step. Just add a connection for the one you want to use. Agent code runs in a QuickJS sandbox: each plugin is a top-level global, dot-chain into resource and action. Plugin actions execute outside the sandbox with full network access; the agent can only reach APIs through the actions you've configured.
// agent writes this
const company = await brandfetch.brand.getCompany({ domain: "stripe.com" });
const deals = await pipedrive.deal.list({ limit: 10 });
const issue = await github.issue.create({
owner: "acme", repo: "api",
title: `New lead: ${company.name}`,
body: `${deals.length} open deals`
});
return { company: company.name, issue: issue.number };188 built-in plugins covering popular SaaS, DevOps, and productivity APIs. All ship with the package — no separate install needed.
Set the env var shown in the Auth column, add a connection, and go:
export GITHUB_TOKEN=ghp_xxx
runline connection add gh --plugin github --set token=$GITHUB_TOKEN
runline exec 'return await github.user.listRepos({ username: "torvalds" })'# List all available actions
runline actions
# Get Nike's brand colors
runline exec 'return await brandfetch.brand.getColors({ domain: "nike.com" })'
# Create a GitHub issue
runline exec '
return await github.issue.create({
owner: "acme", repo: "api",
title: "Bug: login broken",
labels: ["bug", "urgent"]
})
'
# Search Pipedrive deals
runline exec 'return await pipedrive.deal.search({ term: "Acme" })'
# Chain actions together
runline exec '
const contact = await hubspot.contact.get({ id: "123" });
const task = await todoist.task.create({
content: `Follow up with ${contact.properties.firstname}`,
priority: 4
});
return { contact: contact.properties.email, taskId: task.id };
'
# Discover actions from inside the sandbox
runline exec 'return brandfetch.help()'
runline exec 'return help()'
# Output as JSON (for agents)
runline exec 'return await github.repo.list({ owner: "torvalds" })' --jsonPlugins export a function that receives a RunlinePluginAPI and registers actions.
import type { RunlinePluginAPI } from "runline";
export default function orders(rl: RunlinePluginAPI) {
rl.setName("orders");
rl.setVersion("1.0.0");
// Connection config — env vars override config.json values
rl.setConnectionSchema({
apiKey: { type: "string", required: true, env: "ORDERS_API_KEY" },
baseUrl: { type: "string", required: true, env: "ORDERS_BASE_URL" },
});
rl.registerAction("list", {
description: "List orders for an organization",
inputSchema: {
orgId: { type: "string", required: true },
status: { type: "string", required: false, description: "open, closed, or all" },
limit: { type: "number", required: false },
},
async execute(input, ctx) {
const { orgId, status, limit } = input as Record<string, unknown>;
const url = new URL(`${ctx.connection.config.baseUrl}/orgs/${orgId}/orders`);
if (status) url.searchParams.set("status", status as string);
if (limit) url.searchParams.set("limit", String(limit));
const res = await fetch(url.toString(), {
headers: { Authorization: `Bearer ${ctx.connection.config.apiKey}` },
});
if (!res.ok) throw new Error(`Orders API ${res.status}: ${await res.text()}`);
return res.json();
},
});
rl.registerAction("create", {
description: "Create a new order",
inputSchema: {
orgId: { type: "string", required: true },
customer: { type: "string", required: true },
total: { type: "number", required: true },
},
async execute(input, ctx) {
const res = await fetch(`${ctx.connection.config.baseUrl}/orders`, {
method: "POST",
headers: {
Authorization: `Bearer ${ctx.connection.config.apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify(input),
});
if (!res.ok) throw new Error(`Orders API ${res.status}: ${await res.text()}`);
return res.json();
},
});
}Key points: execute runs outside the sandbox with full Node.js access (fetch, fs, etc). The sandbox can only call your actions through the proxy. ctx.connection.config holds the resolved config with env var overrides applied.
See packages/runline-plugins/ for 188 real-world examples.
Agent code runs in a QuickJS WASM sandbox:
- No
fetch— network access is only through plugin actions - No
fs— no filesystem access - Timeout — configurable, kills infinite loops
- Memory limit — configurable, prevents OOM
console.log— captured and returned inresult.logs- Plugin globals — each installed plugin is a top-level proxy (e.g.
github,slack,brandfetch). Dot-chain into resource and action:github.issue.create(input)
Every command supports --json. Use runline actions --json for full schemas with input types.
runline actions --json # all actions with schemas
runline exec '<code>' --json # structured { result, logs } outputimport { Runline } from "runline";
import brandfetch from "runline-plugin-brandfetch";
const rl = Runline.create({
plugins: [brandfetch],
connections: [{ name: "bf", plugin: "brandfetch", config: { apiKey: "xxx" } }],
});
const result = await rl.execute(`
const colors = await brandfetch.brand.getColors({ domain: "stripe.com" });
return colors.filter(c => c.type === "accent");
`);
console.log(result.result); // [{ hex: "#635BFF", type: "accent", brightness: 116 }]runline exec "<code>" # execute JS in sandbox
runline exec -f ./script.js # execute a file
runline actions # list all actions
runline plugin install <source> # install from git/npm/local
runline plugin list # list installed plugins
runline plugin remove <name> # remove a plugin
runline connection add <n> -p <plugin> -s key=val # add connection
runline connection list # list connections
runline connection remove <name> # remove a connection
runline init # create .runline/ directory.runline/config.json:
{
"connections": [
{ "name": "gh", "plugin": "github", "config": { "token": "ghp_xxx" } },
{ "name": "bf", "plugin": "brandfetch", "config": { "apiKey": "xxx" } }
],
"timeoutMs": 30000,
"memoryLimitBytes": 67108864
}Env vars override config values. Plugins declare env var names in their connection schema (e.g. GITHUB_TOKEN).
Runline is a bun workspace monorepo: packages/runline (library + CLI), packages/runline-plugins (188 built-in plugins, bundled into runline's dist at build time), and packages/pi-runline (pi extension that exposes runline to agents).
bun install
bun --filter runline dev -- exec 'return 1 + 2'
bun --filter runline test
bun run checkThe pi-runline package is a pi extension that plugs runline into coding agents as two native tools:
list_runline_actions— enumerate the action catalog (optionally filtered to one plugin).execute_runline— run JavaScript in the runline sandbox.
It ships with /runline-plugins, a fuzzy multi-select picker for choosing which of the 188 plugins the agent should see, plus a guided credential prompt for the ones you enable.
pi install pi-runline
# then in any pi session inside a project with .runline/
/runline-pluginsSee packages/pi-runline/README.md for details.
dripline is query mode — SQL tables over live APIs. runline is code mode — JavaScript actions over the same APIs. Same plugin architecture, same connection config, different interface. Use dripline when you want to SELECT rows; use runline when you want to create, update, delete, or chain multiple API calls together.
MIT