Code mode for agents.
Turn any API or host capability into a callable action. Install a plugin, write JavaScript, call actions. Code runs in a QuickJS WASM runtime with plugin globals; enable the built-in node plugin when you want filesystem, path, OS, process, crypto, shell, and fetch actions.
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 203 built-in 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 runtime: each configured plugin is a top-level global, dot-chain into resource and action. Configure the built-in node plugin for host work like node.fs.readFile, node.process.execFile, node.path.join, node.crypto.hash, and node.fetch.
// 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 };203 built-in plugins covering popular SaaS, DevOps, productivity, image-generation APIs, and host Node capabilities. 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
# List actions only for connected services
runline actions --connected
# 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 runtime
runline exec 'return actions.list()'
runline exec 'return actions.list("brandfetch")'
runline exec 'return actions.find("create issue")'
runline exec 'return actions.describe("github.issue.create")'
runline exec 'return actions.check("github.issue.create", { owner: "a" })'
# 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. Use TypeBox schemas by default for both connection config and action input schemas; Runline uses them for action discovery, actions.check(...), and runtime validation before execute runs.
import type { RunlinePluginAPI } from "runline";
import * as t from "typebox";
export default function orders(rl: RunlinePluginAPI) {
rl.setName("orders");
rl.setVersion("1.0.0");
// Connection config — env vars override config.json values
rl.setConnectionSchema(t.Object({
apiKey: t.String({ env: "ORDERS_API_KEY" }),
baseUrl: t.String({ env: "ORDERS_BASE_URL" }),
}));
rl.registerAction("list", {
description: "List orders for an organization",
inputSchema: t.Object({
orgId: t.String(),
status: t.Optional(t.String({ description: "open, closed, or all" })),
limit: t.Optional(t.Number()),
}),
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: t.Object({
orgId: t.String(),
customer: t.String(),
total: t.Number(),
}),
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();
},
});
}Custom plugins are discovered from the nearest project .runline/ directory first, then .runline/plugins.json, then ~/.runline/plugins, then the bundled built-ins named by configured connections. Project-local plugins win over same-named global or built-in plugins; duplicates discovered later are skipped and reported on stderr.
Supported local layouts:
.runline/plugins/orders.ts
.runline/plugins/orders.js
.runline/plugins/orders/index.ts
.runline/plugins/orders/index.js
.runline/plugins/orders/src/index.ts
.runline/plugins/orders/src/index.js
.runline/plugins/orders/package.json # { "main": "dist/index.js" }
A package directory can also expose multiple plugins:
{
"runline": { "plugins": ["./orders.js", "./billing.js"] }
}plugins.json can also point at explicit plugin paths:
{
"plugins": [{ "path": "./tools/orders.ts" }]
}A plugin may export the default registration function shown above, or a plain plugin definition object with { name, version, actions }. ESM, TypeScript, and CommonJS object exports are supported. The function form is preferred because it validates plugin names, keeps action registration consistent, and gives authors the public RunlinePluginAPI surface.
Plugin execute handlers run outside the QuickJS runtime with full Node.js access (fetch, fs, npm dependencies available to the host process, etc). Agent code calls them through configured plugin globals. ctx.connection.config contains the resolved connection config with env var overrides applied, and ctx.updateConnection(patch) can persist refreshed credentials. Add a node connection only when you explicitly want to expose host APIs through the built-in node plugin.
Agents can inspect custom plugins from inside runline exec before calling them:
runline exec 'return actions.list("orders")'
runline exec 'return actions.find("list orders")'
runline exec 'return actions.describe("orders.list")'
runline exec 'return actions.check("orders.list", { orgId: "acme" })'
runline exec 'return await orders.list({ orgId: "acme", status: "open" })'For debugging, run runline actions --json to see loaded actions and schemas. Loader failures include the plugin path and reason on stderr. A broken plugin does not stop discovery of healthy plugins in the same directory; common failures include invalid exports, invalid plugin names, malformed package.json/plugins.json, missing entrypoints, and duplicate plugin names.
See packages/runline-plugins/ for 202 real-world examples.
Agent code runs in a QuickJS WASM runtime:
- 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) - Configured Node globals — when configured, the built-in
nodeplugin exposes host-backed actions such asnode.fs.readFile,node.fs.writeFile,node.path.join,node.os.info,node.process.execFile,node.crypto.hash, andnode.fetch
Every command supports --json. Use runline actions --json for full schemas with input types.
runline actions --json # all actions with schemas
runline actions --connected --json # only connected services
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 the QuickJS runtime
runline exec -f ./script.js # execute a file
runline actions # list all actions
runline actions --connected # only actions for configured connections
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 (203 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 a single native tool:
execute_runline— run JavaScript in the runline runtime. Discovery happens inside the runtime viaactions.list / find / describe / check, so the agent never needs a separate listing tool.
It ships with /runline-plugins, a fuzzy multi-select picker for choosing which of the 202 plugins the agent should see, plus a guided credential prompt for the ones you enable.
pi install npm: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