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

warpline

v0.7.0

Published

Deterministic plugin runtime with LLM-judgment dispatch and side-effect approval gates

Readme

Warpline

A deterministic plugin runtime where the LLM is a step you dispatch, not a capability the code carries around.

npm CI Secret scan License

Warpline runs the recurring work that keeps a business going, on a schedule. Outbound sequencing, market research pulls, content pipelines, competitor monitoring, lead qualification, report generation.

A plugin that declares a side effect (sending email, creating issues, writing to a database, calling external APIs, modifying files) doesn't run without explicit human session approval. That includes at autonomous, because the autonomy level is dispatch autonomy. It decides whether the scheduler can start a plugin without asking. It never decides whether that plugin can act on the world without asking.

Blanket approval exists and it's deliberate. warpline approve --all writes a wildcard grant. What it covers, what it prints before writing anything, when it expires, and why a run can't widen its own scope: Can the gate be bypassed?.

Published docs: warplinehq.github.io/warpline.

warpline plan — preview only; nothing was executed.

Session grant: none — plugins with side effects would be SKIPPED this run
Plugins: /tmp/warpline-demo/plugins

Due (10):

  announce-fanout (level 0)
    llm_handoff: may hand judgment to the LLM ([needs-llm])
    (no declared side effects)
  anomaly-watch (level 0)
    (no declared side effects)
  cadence-replies (level 0)
    (no declared side effects)
  candidate-propose (level 0)
    (no declared side effects)
  derived-summary (level 0)
    (no declared side effects)
  draft-writer (level 0)
    llm_handoff: may hand judgment to the LLM ([needs-llm])
    (no declared side effects)
  metrics-rollup (level 0)
    (no declared side effects)
  cadence-plan (level 1)
    (no declared side effects)
  daily-digest (level 1)
    (no declared side effects)
  feed-triage (level 1)
    llm_handoff: may hand judgment to the LLM ([needs-llm])
    (no declared side effects)

Not due (11):

  change-watch — unapproved: side effects require session approval
    external_api: ⚠ unapproved — would be SKIPPED this run
  feed-monitor — unapproved: side effects require session approval
    external_api: ⚠ unapproved — would be SKIPPED this run
  github-poll — unapproved: side effects require session approval
    external_api: ⚠ unapproved — would be SKIPPED this run
  graph-sync — unapproved: side effects require session approval
    external_api: ⚠ unapproved — would be SKIPPED this run
    writes_db: ⚠ unapproved — would be SKIPPED this run
  ledger-runner — unapproved: side effects require session approval
    external_api: ⚠ unapproved — would be SKIPPED this run
    modifies_file: ⚠ unapproved — would be SKIPPED this run
  link-enrich — unapproved: side effects require session approval
    external_api: ⚠ unapproved — would be SKIPPED this run
  note-intake — schedule 'manual': requires profile 'manual'
  search-console — unapproved: side effects require session approval
    external_api: ⚠ unapproved — would be SKIPPED this run
  anomaly-issue — unapproved: side effects require session approval
    creates_issue: ⚠ unapproved — would be SKIPPED this run
    external_api: ⚠ unapproved — would be SKIPPED this run
  candidate-promote — unapproved: no content approval on file for 'candidate-promote'
    modifies_file: ⚠ unapproved — would be SKIPPED this run
  cadence-send — unapproved: no content approval on file for 'cadence-send'
    sends_email: ⚠ unapproved — would be SKIPPED this run

Quickstart

npm i warpline          # or: bun add warpline
npx warpline --help

Runs on Node 22.18+ or 23.6+ (engines.node excludes 23.0–23.5), or Bun ≥ 1.4.2. Node alone is enough. You don't need Bun to use warpline. v0.1 supports POSIX systems (macOS, Linux). Windows is untested and unclaimed.

Every file warpline reads or writes lives under one home directory: the WARPLINE_HOME env var, else the nearest ancestor .warpline/ directory, else <cwd>/.warpline. The one exception is the plugin root: <home>/plugins unless WARPLINE_PLUGINS_DIR names another directory.

What you get

  • Plugin runtime — Zod-validated manifests; per-attempt timeouts; bounded retries with exponential backoff + jitter; AbortSignal threaded into your I/O; per-run artifacts (runs/<id>.json + captured log with attempt delimiters).
  • Side-effect approval gate — a closed enum of side-effect types declared per plugin; declared effects gate execution behind warpline approve-style session approval, regardless of autonomy level.
  • Engine — TTL freshness (skip work that is still fresh), dependency topological ordering, quiet hours, review gate, and idle-based degradation tiers (normal → degraded → extended → suspended).
  • Event board — append-only events.jsonl + acknowledgements; tasks with ack / defer / complete states and severity-FIFO ordering.
  • The [needs-llm] contract — plugins emit judgment work as a typed handoff; a Claude Code companion skill picks it up. Deterministic work costs nothing to run; judgment work uses your existing Claude subscription. See docs/needs-llm-contract.md. Measured against an agent doing the same day's work, warpline used 20–27% less wall-clock and 25–37% fewer output tokens, on a public scenario and again at private scale. Cache reads were not lower in the public run. The method was committed before the first result: bench/README.md.

Writing a plugin

A plugin is a directory under <home>/plugins/<name>/ with two files:

my-plugin/
  manifest.ts   # export const manifest = PluginManifestSchema.parse({...})
  handler.ts    # export const handler: CapabilityHandlerFn = async (manifest, args, signal, capabilities) => SkillResult

The manifest declares what the plugin is allowed to do (side effects, schedule, TTL, timeout, retries, minimum degradation tier). The handler does the work and returns a structured SkillResult. Invalid manifests are a hard-stop at load, so a misconfigured plugin never silently runs. <name> is lowercase letters, digits and hyphens, starting with a letter, at most 64 characters, and manifest.name equals it (the rule).

Worked examples in examples/plugins/:

| Example | Demonstrates | | --- | --- | | anomaly-watch | Compare against a prior observation — reads the record it wrote last run, reports what newly breached and what cleared, and returns the breached set as an Output | | github-poll | external_api side effect gating an autonomous plugin; writes one snapshot of what it polled and reports the delta on the next run | | feed-monitor | Deterministic fetch/parse that reports new entries — the producer half of the feed chain; the judgment handoff is feed-triage's, not this one's | | feed-triage | The on_run consumer half — per-entry judgment handed off via [needs-llm] through skillHandoff, the payload written under the home; no declared side effects | | metrics-rollup | daily schedule with retained state — append-only rows, a retention window, weekly rollups; writes only under the home | | anomaly-issue | dependencies ordering after anomaly-watch, supervised autonomy, and a creates_issue side effect through the gate — irreversible, so the result says how to undo it | | daily-digest | Aggregate — declares two producers, lets the engine order them, and folds what they last reported into one digest Output | | derived-summary | Derive, don't store — reads a source under the home, returns the summary, writes nothing; ttl_hours decides whether recomputing is worth it | | note-intake | Operator text for one run — schedule: 'manual' plus a required input supplied with warpline run note-intake default --input note=<text>, routed whole to a file under the home | | link-enrich | Fan in from three sources with per-source isolation — one refused source is a partial run that names it, every source refused is a failure; credentials are names on secrets | | draft-writer | Config-heavy writer — every adopter choice is a declared input with a placeholder default, three reference files named by path and refused outside the home, the drafting handed off | | announce-fanout | Config-heavy fan-out — channels, calls to action and a cadence as declared inputs, per-channel isolation, the per-channel rewrite handed off | | change-watch | Watch pages you depend on — fetch each declared target, keep a normalised snapshot, report new, changed with a capped line diff, unchanged or failed; the defaults are three upstream runtimes' tag feeds, so a first run shows real data | | search-console | Derive over one Bearer token — week-over-week clicks and impressions for the top queries and pages of a declared site; persists nothing | | graph-sync | Idempotent per record — upsert by id so a re-run overwrites, one failing record isolated to its own error, every record failing is a failure | | ledger-runner | Write a product file — read declared instruments, rewrite the ledger atomically, report attempted against succeeded; a failed instrument keeps its last value | | cadence-replies → cadence-plan → cadence-send | Content approval, sending — a reply seam you replace with your inbox reader, a plan that works out each contact's due step from time and stops a contact who replied, and a sends_email sender that ships only the outbox an operator approved with warpline approve cadence-send --content, once per recipient | | candidate-propose → candidate-promote | Content approval, promoting — at most three candidates a week, each proposal replacing the last, appended to a file only when an operator approves those exact bytes |

Copy any of them into your own home as a starting point: npx warpline scaffold my-plugin --from <example> copies the directory with only the manifest's name rewritten. That includes the example's handler.test.ts, so the copy arrives with its tests. Nothing in warpline runs that file, and the default .warpline/ home is a dot-directory that bun test does not descend into; a home placed as a plain directory inside a project (WARPLINE_HOME=./warpline-home) is on that project's discovery path, and its own bun test will run the copied file.

Six examples carry a credential. Four name one *_TOKEN on secrets: search-console, graph-sync, ledger-runner and cadence-send. Each sends it only as an Authorization: Bearer header, and only over https or plain http to localhost. link-enrich names three the same way, one per source, and anomaly-issue reads GITHUB_TOKEN without declaring it. Until a declared token is set, the runtime refuses the run and names the variable, and that refusal is the first-run demo. To watch one work against a stub, run its test from your home with Bun: bun test ./.warpline/plugins/<name>/handler.test.ts on a default install (the leading ./ makes Bun read it as a path, not a name filter). Refreshing the token is yours, for example a job that rewrites the variable before an advance.

The two content-approval examples ship nothing until an operator approves the exact bytes their producer made: the outbox cadence-plan wrote, or the candidates candidate-propose chose. A session grant never runs them, not even warpline approve --all. The companion skill approve-review, in the warpline-examples plugin of this repository's Claude Code marketplace (plugin-examples), shows you those bytes first and runs warpline approve <plugin> --content --not-before <soon> --not-after <when> only on an explicit yes. The window opens a few minutes out, so the skill can check the bytes the command bound before anything can ship them. A send that stops part-way is partial, and re-approving the unchanged Output retries it, skipping what already went. A send that stops before anything went out, a rejected token for example, is failed, and so is a first request that throws, which may have delivered. Either leaves the approval indeterminate. Check the sink for the fire's effect id, and when nothing arrived, answer it with warpline resolve <plugin> --not-shipped <effect-id> and approve again. On a default install (review_gate: true) the level-0 producers are recorded gated and the run stops there until you review them.

Authoring guide: docs/plugin-authoring.md.

# First run: create the home, copy one example plugin in, write its config.
# Asks for each input the plugin declares on a terminal; writes the declared
# defaults when stdin is not one. Safe to run again.
npx warpline init

# Scaffold a plugin — also prepares the home directory
npx warpline scaffold my-plugin

# Write a plugin's config from the inputs its manifest declares. Prompts on a
# terminal; takes --from '<json>' when stdin is not one. Never writes a secret.
# Safe to run again: a value already in the file is kept unless you replace it.
npx warpline configure my-plugin

# Preview what the next engine advance would do. Executes nothing.
npx warpline plan

# Execute everything that's due, unattended, and exit with a code a scheduler
# can read. --strict reports an approval gate still waiting on a human, or a
# content approval that refused the fire, as a failure instead of 0.
npx warpline advance

# Invoke one plugin handler directly
npx warpline run my-plugin default

# Answer whichever gate is waiting — record a parked result, or grant a
# side-effecting plugin permission to run for this session
npx warpline approve my-plugin

# Say no to what a plugin proposed. The answer is bound to the proposal, so
# the question comes back if what it proposes changes.
npx warpline deny my-plugin

# See what is waiting on a human, then one Ask with its content and the diff from what you last approved
npx warpline approvals
npx warpline approvals show <ask-id>

# Answer a content fire the runtime could not confirm, after the sink shows it
# did not ship. Takes the effect id the refusal printed; then approve again.
npx warpline resolve my-plugin --not-shipped <effect-id>

# Register who may act, by id; disable is the only way out, and every change is on the audit record
npx warpline principal add ops --type human

# Change one guardrail; the change is on the audit record before it is written
npx warpline prefs set review_gate true

# Print the head of the audit record, its seq and hash, to keep off this box
npx warpline audit head

# Pull every record after a seq as CloudEvents JSON lines, for your shipper or an archive
npx warpline audit export --after 0

# Check the record against a head you kept off this box: tamper-evident relative to the last exported Checkpoint
npx warpline audit verify --checkpoint anchor.txt

# Restart a standing grant's period before it lapses; a named human renews it, never the machine that holds it
npx warpline renew <grant-id> --principal ops

# Clear the session approval
npx warpline revoke

Those fifteen subcommands are the whole CLI surface. advance is the one a scheduler calls, and its exit codes are contract surface — the exit code table is what a monitor keys on. The same work is also a library call, runAdvance() from the package root:

import { runAdvance } from 'warpline'

const result = await runAdvance()

// Another home, leaving the process home untouched:
const other = await runAdvance({ home: '/srv/tenant-a/.warpline' })

A plugin sees that home only where it resolves it inside its handler call. A module is evaluated once per process, outside any advance, so a path built from warplineHome() at a handler's top level holds the process home for every tenant (plugin authoring).

Where the LLM fits

If you can write an if/else for it, it's code. If it needs understanding or judgment, it's an LLM task, and the plugin hands it off instead of calling a model.

Read the full doctrine: docs/doctrine.md.

Nowhere in this repo. That's the point. Plugins that reach a judgment step return a [needs-llm] handoff (declared in the manifest as llm_handoff: true, mapped to the delegated run status, never retried). An orchestrating Claude Code session consumes those handoffs via companion skills, and a template lives in skills/needs-llm-template/. That directory is deliberately not shipped in the package, so the link is absolute. It resolves the same from npm, from GitHub, and from node_modules. Side effects that follow from judgment work still go through the approval gate.

FAQ

Can the gate be bypassed?

Only deliberately, and only by a human at a keyboard. warpline approve --all is the one route to a wildcard scopes: '*' grant, and it refuses to run if you also name plugins, so no plugin name, glob or shell expansion can widen a grant past what you typed. It prints the coverage it's about to grant before it writes anything. The grant is session-scoped and it expires. See the session approval file for the default lifetime and the ceiling from first grant. And nothing inside a run can widen its own scope. The grant helpers have no caller on the advance path, so a run can only ever spend approval a human already gave.

Why not just let the plugin call a model?

Because a plugin that calls a model has quietly made every future rerun an experiment. The deterministic half stops being reproducible, the judgment half stops being reviewable, and you find out which was which when they disagree with each other. If you can write an if/else for it, it's code. If it needs understanding, it's a handoff. Keep the boundary in the manifest and you can read a plugin and know which one you're looking at.

Do I need Bun / a Claude subscription?

No to both, in different ways. Node 22.18+ or 23.6+ is enough to install and run warpline. 23.0–23.5 is excluded by engines.node, and npm reports EBADENGINE there (a hard failure wherever engine-strict is set). Bun is the development runtime for this repository's own test suite, not a user requirement. The [needs-llm] half is a handoff, not an API call. It uses whatever Claude Code session you already have, and if nobody ever picks a handoff up, the deterministic work carries on running without it.

Docs

From source

Cloning gets you the test suite and the board, neither of which ships in the package:

git clone https://github.com/warplinehq/warpline
cd warpline
bun install
bun run test                  # builds, then runs the full suite

Requires bun ≥ 1.4.2. This is the one place it's genuinely required, because the suite is written against bun:test.

# The board — a repo-only surface, not wired into the published bin
bun run src/cli/board-cli.ts status
bun run src/cli/board-cli.ts tasks

Provenance

Warpline was extracted in August 2026 from the private automation engine that's run one company's marketing operations since early 2026. The domain plugins stayed behind. The runtime, gates, board, and doctrine are what generalised.

License

Apache-2.0