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

@openprose/reactor-cli

v0.2.2

Published

Deterministic command-line driver for the @openprose/reactor SDK.

Readme

@openprose/reactor-cli

The deterministic command-line driver for the @openprose/reactor SDK. It configures the SDK — it never re-implements the reconciler and never parses .prose itself. Compile freezes intelligence (model sessions) into deterministic, content-addressed artifacts; run/serve execute those frozen artifacts with a dumb reconciler.

The command is reactor, shipped from this reactor-cli package. Reactor is React.memo applied to expensive LLM work: you declare standing Responsibilities, the harness maintains a world-model and re-renders only what materially moved (the reconciler is dumb — no judge step), and every decision leaves a content-addressed receipt. Cost scales with surprise, not the clock.

Versions (live on npm): reactor-cli 0.2.0 · @openprose/reactor 0.3.0 · reactor-devtools 0.2.0. reactor --version prints the CLI version (0.2.0), not the SDK version (0.3.0) — expected, not a mismatch.

Install

All three packages are live on npm. Prefer a project-local install (no root, no global binary collisions) and call the binaries through npx:

npm install --save-dev @openprose/reactor-cli @openprose/reactor @openai/agents zod
# then: `npx reactor …` / `npx reactor-devtools …`

To touch the keyless replay with no install at all:

npx -p @openprose/reactor-devtools reactor-devtools --example masked-relay --describe

A global install is an alternative — but -g can collide with other tools' binaries and is EACCES-prone on Linux/WSL (use a user prefix/nvm or sudo):

npm i -g @openprose/reactor-cli @openprose/reactor-devtools
# The live render also needs two peers:
npm i -g @openprose/reactor @openai/agents zod

Zero runtime deps in the SDK core. The live render needs two peers (@openai/agents, zod); doctor, init, the whole observability suite, and the reactor-devtools replay need neither.

Requires Node >=20 (matches the SDK's engines floor).

Quickstart

The keyless reactor-devtools replay is the first thing to touch — it's the payoff that works with no key and no model call. Then scaffold and check your own responsibility offline; only the live compile/run needs a model key.

# 1. See the thesis — keyless, no model call, no setup.
#    Replay a real saved run's receipt ledger: dispositions, cost-by-surprise, chain-verify.
#    Use the bundled fixture by name — no path to compute, works from any cwd:
reactor-devtools --example masked-relay --describe   # headless summary (the text an agent reads)
reactor-devtools --example masked-relay              # browser viewer at http://localhost:4555
# (or, against the bundled path explicitly:)
# reactor-devtools "$(npm root -g)/@openprose/reactor-devtools/fixtures/masked-relay" --describe

# 2. Scaffold and check your own responsibility — offline.
reactor init my-project        # scaffold a gateway + responsibility + reactor.yml
cd my-project
reactor doctor                 # check node, SDK, key/deps, sandbox, state-dir, IR
reactor compile --check        # honest STALE + contract fingerprint; zero cost

# 3. Run it live (needs a model key).
export OPENROUTER_API_KEY=...   # doctor confirms it's present, never echoes it
reactor compile                # run the compile sessions -> content-addressed IR cache
reactor topology               # the DAG Forme wired from your contracts
reactor serve --http 8080      # drive the scaffold's static gateway to a real receipt
                               # (binds 127.0.0.1 by default; no auth in v1 — see "Deploying reactor serve")
reactor-devtools .reactor --describe  # replay YOUR run's receipts

The scaffold seeds a static gateway, so drive it with reactor serve (which ingests the seeded items), not reactor run (which is for graphs whose connectors emit on their own). We're fixing run to either ingest the static case or say so explicitly rather than no-op.

compile/run/serve reach the model surface and need a live key (OPENROUTER_API_KEY) plus the optional peer deps (@openai/agents, zod). Every other command — doctor, init, and all of the observability commands — runs fully offline, with no key and with the model deps absent.

If you're an agent onboarding on behalf of a user: the binary is reactor. Run reactor init → doctor → compile → run, then open reactor-devtools <state-dir> to inspect the receipt ledger. OpenProse contracts run on any Prose-Complete harness; the Reactor CLI is the deterministic host layer that compiles, runs, and inspects them.

Benchmarks are openly pending — the proof is the receipts and the keyless replay above, not a number in our marketing.

The reference client: compile → run → serve

The CLI is the reference client for the SDK's three-phase lifecycle:

  1. compile runs the intelligent compile sessions (Forme topology, per-node canonicalizer, postconditions) and freezes them into a content-addressed IR cache under <state-dir>/compile/. The cache KEY is (contract-set fingerprint, SDK version, model id)cost is never part of cache identity. An unchanged contract set re-compiles at zero session cost (a cache hit); --check exits non-zero when the cache is stale (wire it into CI). The IR persists a serializable spec, so a fresh process re-lowers each node's canonicalizer with the keyless compileNode(spec)no model, no network — to mount it.

  2. run ensures the IR is fresh (compiles if stale), boots the reactor, drains to quiescence, prints per-node dispositions + cost, and exits. One-shot.

  3. serve boots the durable host (filesystem receipts + world-models), runs the continuity driver loop, and exposes an HTTP surface. It stays up until SIGINT/SIGTERM, then drains in-flight work and exits.

Cost scales with surprise

Every receipt carries a surprise_cause. A node that re-wakes but whose inputs did not move memo-skips at zero render cost; a node renders (and spends tokens) only when its (contract_fp, input_fps) memo key actually moves. So the standing cost of a quiet system trends to a bounded audit floor, not zero, and a cost spike is always a real change propagating — reactor receipts cost and reactor status roll cost up by surprise_cause so you can see exactly what surprised the system.

Deploying reactor serve

reactor serve --http <port> boots the durable host and exposes a small HTTP surface for liveness, observability, and manual ingress. The routes (namespaced under /<reactor-name>/... on a multi-reactor host; the prefix is omitted for a single reactor):

| Method + path | What it returns | | --- | --- | | GET /health | Liveness — 200 with { "status": "ok" } once the host is up. | | GET /status | Standing compile cost beside live run cost + per-node dispositions (the status command's JSON). | | GET /cost | The cost rollup by surprise_cause (the receipts cost JSON). | | POST /trigger/<node> | Wake <node> with an optional JSON body as an external arrival; returns the resulting disposition. |

It drains in-flight work on SIGINT/SIGTERM before exiting.

⚠ Bind address and auth — read before exposing this. As of this release serve --http binds 127.0.0.1 by default — it is not reachable off the host unless you pass an explicit --host 0.0.0.0. And v1 ships no auth. POST /trigger/<node> is unauthenticated, so anything that can reach the port can wake a node and cause model spend. Exposing it externally (--host 0.0.0.0) is only safe behind a reverse proxy or network policy that adds authentication and rate-limiting. Treat the bare HTTP surface as a single-operator, trusted-network interface.

Kubernetes / container deployment

Probe liveness on the HTTP server and readiness with reactor doctor --json (keyless, offline — it reports node/SDK/key/deps/state-dir/IR health):

livenessProbe:
  httpGet:
    path: /health
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 10
readinessProbe:
  exec:
    command: ["reactor", "doctor", "--json"]
  initialDelaySeconds: 5
  periodSeconds: 15

A reference Dockerfile sketch — pin Node 22, install the three packages globally (SDK first), and run serve --http bound to all interfaces inside the container (keep the bind at 127.0.0.1 on the host and front it with an ingress/proxy that adds auth):

FROM node:22-slim
RUN npm i -g @openprose/reactor @openprose/reactor-cli @openprose/reactor-devtools \
    && npm i -g @openai/agents zod
WORKDIR /app
COPY . .
EXPOSE 8080
CMD ["reactor", "serve", "--http", "8080", "--host", "0.0.0.0"]

Configuration — reactor.yml

reactor init writes a fully-commented reactor.yml. The schema:

state:
  dir: ./.reactor              # durable state (receipts, world-models, IR cache)

model:
  provider: openrouter
  render_model: google/gemini-3.5-flash
  compile_model: google/gemini-3.5-flash
  temperature: 0
  max_turns: 200

sandbox:
  mode: none                   # none (default) | docker
  shell_timeout_ms: 300000

gateways:                      # external-driven entry points
  - node: inbox
    source_id: inbox
    connector:
      type: static             # static | http | file (or a connectors.{cjs,js} plugin)
      id_field: id
      items: [{ id: item-1, body: "the first item" }]

reactors: []                   # optional: a multi-reactor host (see below)

Global flags --state-dir, --project, --json, --offline override the file on every command.

Sandbox

The sandbox block is the render threat-model knob:

  • mode: none (the locked default) — renders run in the SDK's cwd-scoped, time/output-bounded shell (shell_timeout_ms tunes the bound; default 300 s). The trusted posture.
  • mode: docker — each render command runs inside a throwaway, network- disabled container (docker run --rm --network=none -v <ws>:<ws> -w <ws> <image> ...), bind-mounting only the workspace. If Docker is absent, the run degrades to the bounded shell with a surfaced note (it never crashes). reactor doctor reports Docker availability when mode: docker.

Connectors + gateways

A gateway is an external-driven entry point. A connector is three pieces: fetch (source I/O) + extract (payload → arrivals keyed by id_field) + stage (write the arrival into the gateway's truth before the wake). Built-ins by type:

  • static — a fixed items list (great for init/examples/tests).
  • httpGET <url> (substitutes {cursor}), JSON array → arrivals.
  • file — watch a dir of .json files.

A project may also ship a connectors.cjs/connectors.js plugin exporting { connectors: { [source_id]: { fetch, extract? } } }. Idempotency is durable: a per-source cursor dedups arrivals, so a restart never re-ingests the backlog.

Multi-reactor host + --concurrency

A reactors: list in reactor.yml hosts N isolated reactors (each its own state-dir, substrate, schedule, cursors). The HTTP surface namespaces each under /<name>/... (the prefix is omitted for a single-reactor host).

--concurrency N is an across-reactor worker-pool bound: independent reactors render in parallel up to N. Within a single reactor, drains stay strictly serial — at most one drain in flight per reactor, behind a per-reactor serialization queue (the SDK's single-flight atomicity requires this).

Within-reactor parallelism is a future enhancement. The current SDK has no maxConcurrency option, so --concurrency parallelizes reactors, not nodes within a reactor. See the Change-B deferral note in the implementation plan.

Command reference

Run reactor <command> --help for the full options of any command.

| Command | Live? | What it does | | --- | --- | --- | | reactor init [dir] | offline | Scaffold a minimal project (gateway + responsibility + reactor.yml + .gitignore). Refuses a non-empty target dir without --force. | | reactor doctor [--live] | offline (--live probes) | Report node/SDK/key/deps/offline/sandbox/state-dir/IR health. --live runs one smoke render. | | reactor compile [--force] [--check] | live (cache hit/--check offline) | Run compile sessions → IR cache. --check exits non-zero when stale. | | reactor run | live | Ensure IR fresh, boot, drain to quiescence, report + exit. | | reactor serve [--http <port>] [--concurrency <n>] [--poll-interval <ms>] | live | Boot the durable host + continuity loop + HTTP surface. | | reactor trigger <node> [--data <json>|@file] | live | Trigger a node with an external wake (one-shot mount, or POST to a daemon). With --data, the payload is STAGED into the node's ingress so it actually reaches the render (not just the report). | | reactor status | offline | Standing compile cost beside live run cost + dispositions. | | reactor topology | offline | Print the compiled DAG: nodes (+ wake source) and resolved edges. | | reactor inspect <node> [--strict] | offline | A node's topology position, fingerprints, last receipt, chain. | | reactor logs [--node <node>] | offline | The receipt stream (optionally filtered to one node). | | reactor trace [<node>] | offline | Each node's receipt chain: wake → disposition. | | reactor receipts [list\|verify\|cost] [--node <node>] [--rate <rate>] | offline | Audit the receipt trail (verify is non-zero on a broken chain). An unknown subcommand is a usage error (exit 2). cost --rate $3/Mtok (or 500000tpd) prices the token rollup into a dollar column. |

What receipts verify proves (and doesn't). It proves receipt-chain consistency: each receipt's content_hash matches its payload, and each receipt links to its prev. In v1 it does not bind the world-model artifacts — editing a world-models/*/published.json while leaving receipts.json intact is not caught. That is the null-signer / meaning-layer boundary: verify covers the chain, not the maintained truth on disk.

Documented exit codes

The CLI uses stable, documented exit codes so it composes in CI/scripts:

| Code | Meaning | | --- | --- | | 0 | Success / healthy. | | 1 | A reported failure with an actionable message: stale cache (compile --check), a broken receipt chain (receipts verify, inspect --strict), no contracts found, a bad config, an unhealthy environment (doctor), a missing live key/dep (--live), or a connector/render error. | | 2 | A usage error: an unknown command/flag (emitted by the arg parser), an unknown receipts subcommand (e.g. receipts verifyy), or a --state-dir that points at a file instead of a directory. |

Failure modes carry actionable messages, e.g. a missing live key → "set OPENROUTER_API_KEY"; mode: docker with no daemon → "install/start Docker, or renders fall back to the bounded shell"; a stale cache → "run reactor compile".

Reading the exit code in CI? Check $? directly — do not pipe if you need the status:

reactor compile --check; echo "exit=$?"   # 1 when STALE, 0 when fresh

A pipe (reactor compile --check | tee log) reports the last command's exit, not reactor's — so a STALE failure silently looks like a pass. Read $? from the bare command, or capture it before piping.

Examples

The examples/ directory runs end-to-end from a fresh checkout:

  • examples/quickstart — the scaffold reactor init produces: a gateway + a responsibility, compiled → run.
  • examples/gateway-connector — a gateway wired to a static connector, showing the fetch → extract → stage → wake ingress.

Each example's README.md lists the exact commands.

Offline boundary

The default import surface and every model-free command are keyless: requiring the CLI entrypoint loads neither @openai/agents nor zod. compile, run, serve, trigger, and the connector/render paths reach the model surface only via dynamic import() inside the handler — so doctor, init, and the whole observability suite work with the model deps absent.

Contributing — the offline commit gate

The commit gate is the per-package offline test, which runs with no model key and no network. Install with a frozen lockfile, then run the gate:

pnpm install --frozen-lockfile
pnpm -C packages/reactor test:offline
pnpm -C packages/reactor-cli test:offline
pnpm -C packages/reactor-devtools test:offline

If the repo root defines a test:offline script, pnpm test:offline from the root is the one-shot gate that chains all three. (Root pnpm test chains the live + skill suites and will go red on a keyless box — use the offline gate to verify a commit.)