npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@botiverse/oar

v0.66.0

Published

A programming interface for all agent harnesses: provider-independent contracts for controlling and observing agent runtimes

Readme

@botiverse/oar

Provider-independent TypeScript contracts and built-in implementations for controlling and observing Antigravity, Claude, Codex, Cursor, Grok, Kimi, OpenCode, Pi, and Pi Durable.

import { defaultRuntimes, promptAndWait } from "@botiverse/oar";

const runtime = defaultRuntimes.require("grok");
const installation = await runtime.installation?.();

if (installation?.kind === "available") {
  const session = await runtime.session(installation, { cwd: process.cwd() });
  session.events((event) => {
    switch (event.kind) {
      case "text_delta": process.stdout.write(event.text); break;
      case "tool_call_started": console.log(`[${event.tool}]`); break;
      case "turn_ended": console.log(event.outcome.kind); break;
    }
  }, { coalesceText: true });
  const run = await promptAndWait(session, "Inspect this repository");
  console.log(run.kind === "rejected" ? run.reason : run.outcome);
  console.log(session.usage(), await runtime.accountUsage?.(installation));
  await session.dispose();
}

promptAndWait uses your inputId, or generates a UUID, to associate native queued/started reports with its answer. A notification turn before the input starts does not satisfy that wait. Without those native reports it keeps the request-based fallback. For a lower-level wait, pass the inputId to awaitTurnEnd(session, requestSeq, inputId); omitting it waits for the first root turn end after that cursor. session.prompt() already generates an id when omitted; it is recorded on the returned prompt request. A native before-start cancellation resolves an input-scoped wait as aborted, even without a turn-end event. awaitIdle waits through an intervening notification while a host prompt remains pending.

session.events() delivers flat, attributed Events (native user message echoes, text, reasoning, tool call start / later input / progress / end, turn start and end, usage, model, effort, compaction start / end, retry, background tasks and subagents started, updated and ended, runtime→app requests and oar's answers, control rejections, withdrawn inputs, the process exit), each carrying the seq and agentPath of the record it was read from. Kinds a runtime never says (claude has no compaction start, ACP runtimes and cursor no compaction, pi and pi-durable report retry) never appear; the runtime pages say which. It is a projection over the record stream, which session.rawEvents() and session.records() expose (RawEvent: Frame with the native payload verbatim, RequestRecord, ResponseRecord) for consumers who need the runtime's own frames.

Tool arguments can arrive while the model is still writing them. Append tool_call_input_delta.delta as raw text; tool_call_input.input replaces the whole input when complete. SessionView does this for you, marking a tool's preview with inputPartial: true until the complete input arrives. Claude's streamed tool_call_started carries no input, so read the later input event before parsing arguments. Calls without a partial stream still start with their complete input.

session.deliver(input, { when, origin }) sends input the host produces (a subagent's result, a finished job) at the right moment: a new turn when the session is idle, so the agent wakes, otherwise steered into the turn or queued behind it. origin is recorded so a UI can tell it from typed input. session.steer exists only on a session whose runtime can steer (kimi and antigravity sessions have none), so a host offers a steer control by reading the member, never a runtime name; deliver and steerOrQueue queue where it is absent. session.withdraw(inputId) takes a queued input back before it is sent (accepted), or answers not_queued once it went; with it a UI offers withdraw, edit (withdraw, then queue again) and send now (withdraw, then deliver). It exists where OAR holds the queue itself (claude, pi, cursor and the ACP runtimes), and on pi-durable through native submission cancellation, not on codex. session.resources() reads the memory a runtime takes on the host: resident bytes and a process count over the runtime process, its group and its descendants, null once it has exited (and on Windows). It is a reading only, never a handle: no pid leaves OAR. Sessions whose runtime runs in the host process (pi, pi-durable, cursor) have none.

For conversation UIs, use the browser-safe reduceConversation projection over session.rawEvents(). It joins input requests, responses and native echoes by identity, including steer → queue fallback. See the conversation contract.

failureAdvice suggests when to retry, based only on the failure class. It does not establish that repeating an input or an entire turn is safe: tools may already have run and files may already have changed. A host can collect the evidence for its own recovery decision:

import { failureAdvice, type ConversationInput, type FailureClass, type Session } from "@botiverse/oar";

function recoveryEvidence(failure: FailureClass, input: ConversationInput, reopened: Session) {
  return {
    timing: failureAdvice(failure),
    inputState: input.state,
    dropReason: input.state === "dropped" ? input.reason : undefined,
    recordsAfterReopening: reopened.records(),
  };
}

An accepted input should not be resubmitted without a later drop. Even dropped with reason: "runtime_exited" means only that no input echo was observed before exit; a durable queue may still hold it. Inspect the reopened session's records and what the runtime can restore before deciding to resend. See dropped input.

Public exports

The package has these public entry points, plus @botiverse/oar/community for community runtimes (below):

  • @botiverse/oar: the runtime registry and adapters, defineRuntime, UnsupportedOptionError, RuntimeFailureError, SessionNotFoundError, SessionUnavailableError, the oar-voyage/3 recorder (openVoyage), and everything the brands and observe entry points below export. Node-only (adapters import node:child_process and runtime SDKs).
  • @botiverse/oar/browser: portable contracts, registry, session kernel, observe helpers, agent report formatting, and branding. No Node adapters, process helpers or local image-file loaders.
  • @botiverse/oar/brands: browser-safe runtime names and SVG icons.
  • @botiverse/oar/runtimes/<id> (claude, codex, pi, cursor, opencode, kimi, grok, antigravity): one built-in runtime (claudeRuntime, …) without the others, for a host that needs only some of them. Each entry stays at about 1 to 2.5 MB of heap where the root entry takes about 50 MB: the ACP SDK (about 20 MB) loads only when an ACP runtime first opens a session, lists models (kimi, grok, antigravity) or reads grok's usage, and the pi and cursor SDKs when theirs first run. Node-only.
  • @botiverse/oar/observe: the browser-safe pure derivations over RawEvents and Events (eventsOf, graphOf, usageOf, coalesceText, observeAgent, reduceStatus, tasksOf, observeStalls, classifyTool, reduceConversation, viewOf, …) with no Node or adapter imports, so a browser or Electron-renderer bundle can import it directly. The root export re-exports all of them. It also supplies failureAdvice (recovery timing per FailureClass) and noticeText, noticeTone, phaseLabel, failureText and taskStatusLabel, alongside toolActionLabel and toolGroupSummary: English display wording for OAR types, usable in JavaScriptCore too. Wording may change in a minor release; hosts must use the typed values for decisions, not parse the text (display wording).
  • @botiverse/oar/kernel: the runtime-author SPI. createSessionKernel is the record stream every built-in adapter is built on (dense seq, cursor replay, control recording, the reachability rule) and sealSession derives the Session API face over an adapter. Pair with defineRuntime to ship a custom runtime (a scripted runtime for a host's tests, an in-process agent) without re-implementing the stream contract. inputImagesRefusal and withInputImages are the image rules every built-in runtime keeps, so a custom runtime refuses the inputs they refuse. withdrawHeld is the decision behind withdraw for a runtime that holds its own queue.
  • @botiverse/oar/agents: subagents. createSubagents() starts child sessions on any runtime that takes SessionOptions.env (not cursor), returns a report for every turn they end, takes follow-ups, enforces depth and concurrency limits, and reports each child as a task. The host takes reports with wait / next or receives them through an onReport hook; with formatReport and reportOrigin it can deliver one into a parent session so it wakes. See subagents. Node-only.
  • @botiverse/oar/agents/report: formatReport, parseReport, reportOrigin and their types, with no Node or adapter imports, for a host forwarding or displaying reports. parseReport reads the existing report format into header fields, a structured outcome and the original body, or returns null. @botiverse/oar/agents loads every built-in runtime, because createSubagents defaults to them.
  • @botiverse/oar/testing: scriptedRuntime({ turn }), a ready-made runtime on the kernel SPI whose model is a script. It yields a real Session (same records, folds and control semantics) with no binary, login or provider, for hosts' tests and demos. A script can also start tasks (turn.task) that end after the turn, like a background command. Node-only.

Brand SVG files are exported at @botiverse/oar/assets/brands/<runtime-id>.svg. Any other deep import (@botiverse/oar/dist/..., source paths) is internal and may break without notice.

Antigravity, Grok, Kimi, and OpenCode share an internal ACP v1 transport and session kernel, but only their concrete runtime identities are public. The registry deliberately does not expose a generic acp runtime. Pi and Cursor run in the host process through their SDKs. The pi SDK (@earendil-works/pi-coding-agent) is a dependency of this package. @cursor/sdk is an optional peer dependency, and cursor is not in defaultRuntimes: install the SDK (npm install @cursor/[email protected]) and hand it over, createRuntimeRegistry([...defaultRuntimes.list(), createCursorRuntime({ sdk: () => import("@cursor/sdk") })]). Written in your code, that import fails your compile if the package is missing. It is Cursor's own package under Cursor's terms, and it signs in with CURSOR_API_KEY or its own Cursor.auth.login().

Community runtimes are contributed and maintained outside the core team (each maintainer is named on its runtime page). They ship in this package from a separate entry point, @botiverse/oar/community, and are not in defaultRuntimes: createRuntimeRegistry([...defaultRuntimes.list(), createMorphRuntime()]) adds Mister Morph, driven through its Console Runtime API. The oar CLI includes them.

Pi 1.0.3 Azure migration: Pi renamed its provider from azure-openai-responses to azure. Update that prefix in native provider configuration and SessionOptions.model. When resuming an old Azure session, explicitly pass model: "azure/<model>"; without it, Pi can retain the conversation while selecting a different default model. The opening model event and Session.model() report the effective selection. OAR's ^1.0.2 dependency range already permits this upstream update. See the migration evidence and limits.

The command-line interface is a separate package: @botiverse/oar-cli.

What a runtime cannot honor is refused, never dropped: runtime.session() rejects with an UnsupportedOptionError (option names the refused SessionOptions key, the message is the reason), so a host can try and fall back on it. runtime.refusedSessionOptions names the options a runtime refuses at open, each with the reason, so a host can leave them out before opening. One key names a value rather than an option: mcpServerCredentials, an mcpServers entry carrying env or headers (as every serveMcpTools entry does), refused by antigravity; leave such entries out. Some refusals depend on the value or operation: Kimi and OpenCode refuse a resume in another directory than the session's own (option: "cwd"). See refused session options.

A missing resume target rejects with SessionNotFoundError on verified native signals, carrying sessionId and a redacted cause: { method, native }. Other failed opens retain their own errors. Missing is scoped to the runtime's configuration and, for Pi/Grok, the supplied cwd; save and reuse the cwd with the id. See missing resume targets. A target the runtime keeps but will not open now rejects with SessionUnavailableError, whose reason is archived (the user archived it in their app; OAR never unarchives it) or in_use (another process, such as the user's app, holds it); codex reports both. See unavailable resume targets.

SessionOptions.name is the session's display name in the runtime's own UI (claude, codex and pi; other runtimes refuse it). A new session gets it; a resume never replaces a name the session has. See session name.

SessionOptions.additionalDirectories adds native workspace paths, resolved relative to cwd. Claude, Codex, Cursor, Kimi (new sessions only), and OpenCode v2 support it; other runtimes refuse it. Empty or omitted adds no override. It keeps the primary cwd and does not change the sandbox mode. Project rules or instruction files in those directories may or may not load; this option does not guarantee their loading. Resume and configuration rules differ by runtime; see additional directories.

SessionOptions.outputSchema supplies a JSON Schema object for final output. Claude and Grok report their native validated value on a completed TurnOutcome.structuredOutput; promptAndWait, status and SessionView preserve it. Codex sends the schema to its provider but returns text only; OAR never parses or validates that text. Give the schema again on resume.

import { defaultRuntimes, promptAndWait } from "@botiverse/oar";

const runtime = defaultRuntimes.require("claude");
const installation = await runtime.installation?.();
if (installation?.kind !== "available") throw new Error("Claude unavailable");
const session = await runtime.session(installation, {
  cwd: process.cwd(),
  outputSchema: {
    type: "object", properties: { answer: { type: "integer" } },
    required: ["answer"], additionalProperties: false,
  },
});
try {
  const run = await promptAndWait(session, "What is 2 + 2?");
  if (run.kind !== "rejected" && run.outcome.kind === "completed" && "structuredOutput" in run.outcome) {
    console.log(run.outcome.structuredOutput);
  }
} finally { await session.dispose(); }

Even with a schema, Claude may complete without structuredOutput; check the field's presence as well as the outcome. Other core runtimes refuse outputSchema at open. Where each runtime applies the schema, and how it fails: structured output.

SessionOptions.mcpServers attaches MCP servers (stdio {name, command, args?, env?} or streamable HTTP {name, type: "http", url, headers?}) to one session, on top of the runtime's own: claude reads them from a 0600 FIFO that never puts them on a disk (a temporary file on Windows), codex from its thread config overrides, grok, kimi, opencode and antigravity from ACP session/new / session/resume, pi from an extension that registers them; cursor and pi-durable refuse them. Give them again on resume. Their env and headers values never appear in a record or an error; antigravity, which would store them in its conversation database, refuses an entry carrying them. See the channels and each runtime page's "Session MCP servers".

Tools the host implements itself go through the same option. serveMcpTools serves them from an MCP server inside the host process and returns the mcpServers entry to pass, so one implementation reaches every runtime that takes an http entry with headers (all but cursor, pi-durable and antigravity):

import { serveMcpTools } from "@botiverse/oar";

const served = await serveMcpTools({
  name: "workspace",
  tools: [{
    name: "read_note",
    description: "Read a note from the host's store",
    inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
    handler: async ({ id }, { signal }) => await notes.read(String(id), { signal }),
  }],
});
const session = await runtime.session(installation, { cwd, mcpServers: [served.server] });
// ... later, after the sessions that use it are disposed:
await served.close();

It listens on 127.0.0.1 behind a random bearer token (in the entry's headers, so it never appears in a record), so the runtime must run on this machine, as OAR's runtimes do. A handler returns text, or MCP content with isError for a failed call; a thrown error becomes an isError result with its message. Its signal aborts when the runtime cancels the call or the server closes. One served set can back several sessions. instructions (guidance across the tools) goes out as the MCP server instructions; claude, codex, opencode and grok show them to the model, pi and kimi do not. served.setTools(tools) replaces the tools while sessions run and sends MCP's notifications/tools/list_changed. Whether a running session's model sees the change is the runtime's choice, declared before opening as runtime.mcpToolsListChanged: full (pi, opencode), additions (claude: new tools appear, removed ones stay offered) or none (codex, kimi, grok); a session that does not take it gets the new tools when the host opens its next session. The declaration covers any MCP server that announces a change, so antigravity declares full too, though a served entry never attaches there. See host-served tools.

runtime.listModels(installation, options?) lists the models an installation can run now (ok, unauthenticated or unsupported); every built-in runtime has it. Entries include native effortLevels and serviceTiers where supported. default: true marks the model a session uses when no model is set, where the runtime says so (codex's configured model, else its catalog default; a new session's current model on grok, kimi and antigravity; the default saved in pi's settings). claude, cursor and opencode mark none: OAR never picks one. Where several providers serve models (pi, opencode), provider: { id, displayName? } says which, so a host never parses id. A project's own configuration can change the list and its default, so pass options.cwd, the directory a session would run in (the host's cwd by default). Like the inventory readers, listing starts the runtime there the way a session would and can run that workspace's code (pi loads the project's extensions), so do not list in a directory you do not trust. SessionOptions.serviceTier applies at open (also on resume) and is checked against the runtime's own report. Use default to explicitly clear a tier; it is not a model catalog entry. Session.serviceTier() reports the effective tier. Unsupported runtimes reject the option before opening. runtime.accountUsage(installation) reads account quota on claude, codex, grok and kimi; its failure semantics are in the account usage reference.

runtime.checkUpdate(installation) reports the version the runtime's own updater would install and where that answer came from; runtime.upgrade(installation) runs that updater without a terminal and judges the result by the version the same executable reports afterwards, never by its exit code. oar never upgrades on its own. Claude, Codex, Grok and Kimi have both; Antigravity has only the check; OpenCode has neither yet; Pi and Cursor have neither, their SDK versions following oar's (Pi's SDK is a dependency, Cursor's an exact peer dependency). See runtime updates.

runtime.installPlan() reports what installing a runtime would run on this machine (the vendor's documented installer and the page that documents it) or why it would run nothing (platform, requires_privileges, requires_gui, missing_tool; requires_privileges and missing_tool still carry the vendor's steps and source, for the person to run themselves once they have the rights or the tool); runtime.install() runs it, without a terminal and never with sudo, when installation() finds no copy, and judges the result by installation() afterwards, never by the installer's exit code. oar never installs on its own. Claude, Codex, Grok, Kimi and OpenCode install through their vendors' macOS and Linux scripts; OpenCode declares two release lines in runtime.installLines (v1, v2) and takes the host's choice as line (oar picks none), and a copy of either line is already_installed, never replaced. Antigravity's plan is requires_gui; Pi and Cursor have neither member. See runtime install.

runtime.login(installation, interaction) logs an installation in through the runtime's own login, without a terminal: the sign-in URL or device code arrives as a ProviderLoginInteraction event, and a code the person pastes back is answered through its manual_code prompt and written to the runtime's stdin only. It never logs the current account out first, and interaction.signal cancels it. runtime.authStatus(installation) reads the runtime's local status query. Claude, Codex and Cursor (through its SDK's own Cursor.auth) have both; Antigravity has neither, since its terms do not allow signing in through a third-party tool. runtime.logout(installation) signs those three out through their own logout (claude auth logout, codex logout, Cursor.auth.logout) and resolves logged_out when authStatus then reads logged out (or, when it cannot tell, on the runtime's own report of success); an API key in the environment is not touched, and one the status still reads is failed / still_logged_in. See runtime login.

Images go with an input through InputOptions.images ({ path } entries: absolute paths to png, jpeg, gif or webp files) on prompt, steer and queue, as the runtime's own image content. session.capabilities.images says whether the runtime takes them; an input it cannot deliver is rejected whole. See Images.

TypeScript hosts: keep skipLibCheck on. With skipLibCheck: false and module: nodenext, importing @botiverse/oar alone reports errors inside the pi SDK's own declarations (@earendil-works/pi-ai imports JSON without an import attribute, TS1543); OAR's own declarations check clean. A host that installs @cursor/sdk and checks library files also needs the DOM lib for its @connectrpc/connect dependency (HeadersInit).

Host-owned durable sessions

Install the optional native peers in the host: npm install @earendil-works/pi-durable@~1.2.0 @earendil-works/chord@~1.2.0. The factory lives only in @botiverse/oar/pi-durable, usable in Node and browsers. The root and browser core entries load without these peers.

import { viewOf } from "@botiverse/oar/browser";
import { createPiDurableRuntime } from "@botiverse/oar/pi-durable";

// Use the same Models instance that the host passed to Harness.open.
const runtime = createPiDurableRuntime({ harness, models });
const session = await runtime.session({ kind: "available", via: "bundled" }, {
  cwd: "/workspace", model: "provider/model",
});
session.rawEvents(() => render(viewOf(session.records())));
await session.prompt("Hello", { inputId: crypto.randomUUID() });

The host owns its Pi Durable 1.2.0 Harness, providers, credentials, extensions and storage. resume: session.id attaches to that storage's conversation and resumes unfinished work. Queue and input-id deduplication are native and durable. dispose() detaches the observer; it does not stop work or close the Harness. Use abort() to stop a run. The portable entry needs Web Crypto. See Pi Durable for configuration, snapshot and recovery semantics. This constructor is not in the default registry or CLI.

Native inventories

Every runtime exposes skills(installation, options?), commands(installation, options?), mcpServers(installation, options?), and tools(installation, options?). Options are { cwd?: string, timeoutMs?: number }; cwd defaults to process.cwd(). Queries discover native information independently: they do not inspect an existing Session or submit a model prompt, though native startup can load extensions and connect configured MCP servers.

Results distinguish ok, unsupported, and unavailable. Successful results contain items, scope.cwd, observedAt, view, and partial. A mcp-only view excludes built-in tools. Unknown schema or state fields stay absent, and an unsupported query never pretends to be an empty catalog.

Codex and Claude expose skills and MCP discovery (tools are MCP-only); Grok exposes independent skills and MCP configuration discovery; Pi exposes skills and registered tools with active membership. Pi commands include extension commands and prompt templates; Claude commands come from native initialize. Other core runtimes return unsupported for independent command discovery. Antigravity, Cursor, Kimi and OpenCode inventory queries are unsupported.

const catalog = await runtime.commands(installation, { cwd: "/my/project" });
if (catalog.kind === "ok") {
  for (const command of catalog.items) {
    console.log(command.name, command.description, command.argumentHint);
  }
}

Command entries use native invocation names (Pi resolves duplicate extension names itself), with optional source and aliases. Skills stay in skills(); hosts merge catalogs for a picker. Claude's native catalogs can overlap. This is discovery, not execution or a live session's command state. Command scope and limits.

Custom runtimes should use defineRuntime, which fills missing inventory methods with explicit unsupported results. A manually constructed Runtime must implement the four methods, including commands.

Runtime branding

runtime.brand contains { name, icon }. Built-in icons are self-contained SVG data URIs, usable as an <img src> offline and serializable across IPC. Reading branding needs no installation, login, or runtime process. Browser-only consumers can read it without loading native SDKs:

import { runtimeBrands } from "@botiverse/oar/brands";
const { name, icon } = runtimeBrands.claude;

The SVG files' attribution and licenses ship in assets/brands/NOTICE.md; Pi uses the official pi.dev logo assets. Custom runtimes created with defineRuntime may supply brand; otherwise it defaults to { name: runtime.id, icon: null }. Runtime branding identifies the provider and is independent of an application's project avatars.

brand.icons?.light and brand.icons?.dark optionally override the default for light and dark backgrounds; they need not be distinct or both present. runtimeBrandIcon(brand, theme) returns the matching variant, falling back to brand.icon (null for brands without artwork). Hosts choose the theme from their own surface, not necessarily the operating system setting.

import { runtimeBrands, runtimeBrandIcon } from "@botiverse/oar/brands";
const src = runtimeBrandIcon(runtimeBrands.codex, "dark");