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

@senpi-ai/runtime

v3.0.98

Published

Senpi runtime plugin for OpenClaw

Readme

@senpi-ai/runtime

npm version

Event-driven automated trading plugin for OpenClaw. Orchestrates scanners, LLM-based trade decisions, and a DSL trailing stop-loss exit engine.

Prerequisites

  • OpenClaw >= 2026.2.0
  • Node >= 22
  • Senpi API key (for live trading)

Install

openclaw plugins install @senpi-ai/runtime

Custom installs (Git checkout, dist/-only copy, or Docker without devDependencies): run npm install in the package root (or npm ci --omit=dev from a lockfile) so node_modules includes runtime dependencies. The runtime uses TypeBox at load time for scanner option validation (@sinclair/typebox / @sinclair/typebox/value); if that package is missing, the gateway will fail to load the plugin with Cannot find module '@sinclair/typebox/value'.

Configure

Add the plugin to your OpenClaw config (e.g. ~/.openclaw/openclaw.json or via openclaw config set). Use the plugin id @senpi-ai/runtime as the key:

{
  "plugins": {
    "entries": {
      "@senpi-ai/runtime": {
        "enabled": true,
        "config": {
          "stateDir": "~/.openclaw/senpi-state",
          "apiKey": "YOUR_SENPI_API_KEY",
          "logLevel": "info"
        }
      }
    }
  }
}

Log level

The runtime's minimum log level defaults to info. Accepted values are debug, info, warn, and error (case-insensitive); anything else falls back to info with a one-time console warning at startup.

Resolution order (highest priority first):

  1. Plugin config logLevel — set it inside the plugin config block in openclaw.json. This is the recommended place so the level lives next to the rest of the runtime's configuration and survives gateway restarts without shell env wiring.
  2. SENPI_LOG_LEVEL env var — useful for one-off overrides or when running the gateway under Docker Compose where env vars are easier to inject than config patches.
  3. Defaultinfo.
// ~/.openclaw/openclaw.json
{
  "plugins": {
    "entries": {
      "@senpi-ai/runtime": {
        "enabled": true,
        "config": {
          "logLevel": "debug"
        }
      }
    }
  }
}
# Env-var alternative (ignored if plugin config logLevel is set)
export SENPI_LOG_LEVEL=warn

The runtime logs the resolved level and its source on plugin start:

[senpi-runtime] log level: info (source: default). Set plugin config "logLevel" or env SENPI_LOG_LEVEL to debug|info|warn|error to override.

The source: field will be one of plugin config logLevel, env SENPI_LOG_LEVEL, or default. You can confirm the active level at any time with openclaw senpi status, which prints a Log Level: <level> line for each running runtime (also available under openclaw senpi state).

What each level shows

  • warn / error — failures, retries, clearinghouse unavailable, scanner watchdog alerts.
  • info (default) — the above, plus lifecycle events (open/close positions, scanner signal processed), DSL monitor ticks when there are active positions, position-tracker scans when positions or deltas change.
  • debug — the above, plus per-tick scanner heartbeats, DSL monitor ticks with zero active positions, and full MCP tool-call request/response envelopes (with secrets redacted). Useful for diagnosing individual scan or price-fetch behavior; very noisy.

Notifications

Delivery is env-configured, not a per-strategy setting: the runtime POSTs each notification to the ingest endpoint named by NOTIFICATIONS_INGEST_URL (default: the production endpoint https://notifications.prod.senpi.ai/notify), authenticated with its own Senpi token (SENPI_AUTH_TOKEN, falling back to SENPI_API_KEY). A runtime with notifications configured refuses to boot if the token is missing rather than silently dropping them.

Which strategy alerts fire is controlled by the notifications block in your strategy YAML (e.g. dsl_lifecycle, dsl_notify_sl_updates) — see the strategy YAML spec. The runtime emits DSL exit alerts (profit-lock, stop-raise), the daily trade/loss-limit and drawdown trips, decision-failure alerts, and market-scanner alerts; each event notifies once. Position open/close are announced by the backend, not the runtime.

Plugin lifecycle (start/stop) and auto-update status are not sent as notifications — they are behind-the-scenes housekeeping and go to the plugin log only.

Auto-update

Auto-update is enabled by default (opt-out). When a new minor or patch version is published to npm, the plugin will install it automatically and attempt to restart the OpenClaw gateway so the new code takes effect.

  • Major versions are never auto-installed. Availability is logged ([senpi-auto-update]), asking you to review the changelog and update manually.
  • Default mode: auto-apply — the plugin polls every 6 hours, installs the update, and triggers a gateway restart.
  • Gateway restart requires gateway.reload.mode set to restart or hybrid in your OpenClaw config. If the mode is hot or off, or the config helpers are unavailable, the plugin logs a warning that you need to restart the gateway manually.

To customise or disable auto-update, add an autoUpdate block to the plugin config:

{
  "plugins": {
    "entries": {
      "runtime": {
        "enabled": true,
        "config": {
          "autoUpdate": {
            "mode": "notify-only",
            "pollIntervalMinutes": 720
          }
        }
      }
    }
  }
}

Set "enabled": false inside the autoUpdate block to disable it entirely.

Known limitation: If the gateway cannot be restarted automatically (wrong reload mode or missing config access), runtimes that were stopped for the update remain stopped until you restart the gateway manually.

Risk guardrails

Strategy YAML may include a risk block with guard_rails (daily loss, drawdown, max entries per day, consecutive-loss cooldown, per-asset cooldown, and optional bypass when the day is profitable at the entry cap). Gates are evaluated in real time via MCP before each open. See docs/risk-component.md for gate order, data sources, and semantics.

Quickstart

  1. Copy a minimal recipe template and set your wallet:

    cp $(openclaw plugins path @senpi-ai/runtime)/examples/strategies/iguana/recipe.yaml ~/my-recipe.yaml
    # Edit my-recipe.yaml: set the strategy's wallet env var (e.g. IGUANA_WALLET) in env
  2. Set environment variables (e.g. .env): SENPI_API_KEY, WALLET_ADDRESS.

  3. Load the recipe (hot-loads; no gateway restart):

    openclaw senpi runtime create -p ~/my-recipe.yaml
  4. List recipes:

    openclaw senpi runtime list
  5. Watch gateway logs for scanner runs and signal processing.

Strategy templates

Four supervised v2 strategies ship as worked examples. Each has its own README, a recipe.yaml, and a supervised scanners/scan.py:

| Strategy | Thesis | Recipe | |------|-------|----------| | iguana | XYZ macro index trend — broad equity exposure on Hyperliquid without picking stocks | examples/strategies/iguana/recipe.yaml | | raptor | Momentum breakout on high-conviction movers | examples/strategies/raptor/recipe.yaml | | turbine | Volume + runners; one shared scanner, two recipes | examples/strategies/turbine/recipe-volume.yaml, recipe-runners.yaml | | spider | Two-leg scalp + swing trend system | examples/strategies/spider/scalp/recipe.yaml, swing/recipe.yaml |

Templates live under examples/strategies/ in the package. See examples/strategies/README.md.

CLI reference

Deploy a strategy package (the supported go-live path):

senpi deploy runs the whole path — funds preflight → wallet create+fund (with skillName/skillVersion attribution from strategy.yaml) → install → one observed scanner tick — as a detached background job. The verb returns in ~1s with a deployId; monitoring is explicit polling.

  • openclaw senpi deploy -p <package-dir> --budget <usd> — start the job (detached) and print the deployId plus the watch command
    • -p, --path <dir> — strategy package directory (contains strategy.yaml)
    • --budget <usd> — total budget across the package's wallets (min $10/wallet — the platform floor min_budget.py owns)
    • --decision-model <model> — required if the package uses decision_mode: llm
    • --tick-wait <seconds> — how long the job waits to observe one verified scanner tick (default 120, 0 skips)
    • --max-wait <seconds> — how long the job waits for wallets to reach ACTIVE (default 150)
    • --json — print the raw start response
    • Exit codes (start): 0 the job started · 2 refused · 1 internal/transport error. A refusal is recognisable by its message, which leads with the code ([INVALID_REQUEST] …, [E_DEPLOY_IN_PROGRESS] …) — the same 2 = refused class deploy status uses, so a caller branching on the code obeys a refusal instead of retrying it. 1 is reserved for a gateway that could not be reached or whose response could not be read; on that code the deploy's state is unknown and senpi deploy status is the way to find out.
  • openclaw senpi deploy status [deployId] — the job's phase while running; the full verified report once terminal. Exit codes: 0 live · 2 refused · 3 failed · 4 installed-unobserved · 5 interrupted · 6 pending (a wallet still funding, or the job still running) · 1 internal/transport error. The code is set on the --json path too. Anything richer than the overall status: read --json. A tick that was not observed within the window reports as unobserved (with the check-later command) — never as success. After a gateway restart an unfinished job reports as interrupted, showing both the journal history and a fresh read of what actually exists, plus the resume command.

There is no cancel verb: undeploying a strategy is closing it (senpi-strategy-ops/scripts/close.py), not stopping the job. Every MCP call a deploy makes is deadline-bounded — the job stops waiting on a call that overruns (the request may still be in flight on the server, so the report says the outcome is unknown rather than claiming it failed) — and the job itself has a wall-clock deadline — past it the run is abandoned at its next step boundary and the slot is freed, so a wedged deploy can never block the next one until the gateway restarts.

One deploy runs at a time ([E_DEPLOY_IN_PROGRESS]): concurrent deploys share one funding waterfall and could jointly overdraw. Re-running senpi deploy on the same package resumes — reconciliation reads the backend and the registry and adopts whatever already exists, waiting for a wallet that is still initializing rather than creating a second one. A match that is paused or being torn down is refused outright, naming its real status and what to do about it — it is never waited on as if it were funding. Deploy never adds funds to a wallet that already exists; when the requested --budget exceeds what an adopted wallet holds, the report says so instead of implying the money was applied.

A package whose instances declare no DSL exit block (exit.dsl_preset, or exit.engine: dsl) is refused before anything is created: a strategy that cannot stop itself out is never funded.

A package that hardcodes an instrument which is not live on Hyperliquid is refused the same way, pre-money, before the deploy reads the backend at all ([E_UNIVERSE_NOT_LIVE]). A dead name does not error at runtime — the scan skips it and the strategy silently trades nothing — so the one refusal enumerates every dead name with the exact file and key path it appears in (catalog.assets, or an instance's scanners[].inputs), plus the read-only re-check command and the re-run. A bare ticker counts as live if either T or xyz:T is; a package that hardcodes nothing (a derived universe) never reads the instrument list at all. Names under an exclusion key (excludeAssets, deny…, skip…) are not hardcoded instruments in this sense and are never checked: the package is naming what it will not trade, and such a list routinely names things the venue does not carry — which is usually the reason they are on it. When the live instrument list cannot be read, the deploy fails closed — the step reports the read failure with no refusal code and names no instrument dead, because unknown is never "not live", and nothing is created either way.

The budget is checked in two tiers, and only the first one refuses. The hard floor is $10/wallet (the platform minimum): a budget the accessible balance cannot fund at that floor halts with [E_FUNDS_BELOW_FLOOR] and no wallet is created. The soft tier is the package's calculated minimum — the smallest total at which every sleeve's smallest slot can still open, computed locally from the package's own sizing (the same calculation min_budget.py bakes into the discovery card). When a wallet this deploy funds is allocated less than its own sizing needs, it deploys anyway and reports [W_BUDGET_BELOW_STRATEGY_MIN], naming each short wallet and both its numbers: the strategy runs degraded (fewer slots than authored), which is a choice, not a fault. When a sleeve exposes no readable slot size the minimum is a lower bound and the report says so with [W_BUDGET_UNRESOLVED] — plus the shortfall, if a wallet is short anyway. Both are warnings — they never refuse, and they show up as calculated minimum: / warn: lines in deploy status and as minBudget/minWalletCount/belowMin/minBudgetNote/minBudgetUnresolved in --json. belowMin is a claim about the funding PLAN — set whenever the plan allocated a wallet under its own sizing, under either code, and left true if the deploy then failed; the note marks that difference in tense ("would have funded").

The shortfall is judged per wallet, against what planFunding actually allocates each one — never whole-budget against whole-package minimum. Those differ as soon as a sleeve is adopted (the budget is then split among fewer wallets), and comparing the totals reports shortfalls that do not exist. minBudget rides the report as context — "deploying the whole package fresh needs $30 across 2 wallets" — not as the thing that was violated.

Running the authored design after a below-minimum deploy means starting over, not re-running at a bigger number: deploy never adds funds to a wallet that already exists, so a re-run would simply adopt the one just created. The warn names the exact sequence, scoped to the short sleeves (close.py <id> --instance <name>, which returns their funds) so adopted live wallets beside them are left alone — and it is omitted entirely when the deploy created no wallet at all.

Runtime recipes:

  • openclaw senpi runtime createinternal; prefer senpi deploy, which adds the funds preflight, attribution and the verified tick that direct create skips. Hidden from help; still executes.
    • --runtime-yaml-dir <dir> — directory to resolve relative scanner paths against (content installs only). Without it, a content install whose YAML declares a relative scanner path is refused with [E_VALIDATE_UNRESOLVABLE_SCANNER_PATH] rather than silently resolving against the gateway's working directory. The refusal comes from the install gate, which also names the other ways to supply the directory.
  • openclaw senpi runtime list — list installed runtimes
  • openclaw senpi runtime delete <runtime_id> — remove by id

In-shell reference:

  • openclaw senpi guide — overview
  • openclaw senpi guide scanners — scanner types and config fields
  • openclaw senpi guide actions — action types and decision modes
  • openclaw senpi guide dsl — DSL two-phase exit engine
  • openclaw senpi guide examples — print minimal strategy YAML
  • openclaw senpi guide schema — full YAML schema
  • openclaw senpi guide version — plugin version and changelog URL

HTTP API

The runtime exposes an HTTP API on 127.0.0.1:8787 (default; configurable via plugin config). Use it to ingest signals from external sources, query audit history, and check liveness.

Three endpoints:

  • POST /signals — ingest external scanner signals; body is a JSON array of signal items
  • GET /audit?address=<wallet> — query strategy audit history via MCP
  • GET /health — liveness probe with per-runtime signal queue depth

Default binding: 127.0.0.1:8787 (localhost-only). For Postman testing on the same host, flip api.host to 0.0.0.0 in openclaw.json and add a Docker port mapping 127.0.0.1:8787:8787 (the 127.0.0.1: prefix is mandatory to avoid LAN exposure). See docs/runtime-docs/runtime-api.md for full details and the Postman testing recipe.

Latency benchmark: npm run bench runs benchmarks/ingest-latency.ts — compares the in-process gateway-shim path against HTTP POST /signals across three scenarios and prints p50/p95/p99 with a PASS/FAIL gate.

External scanners

Use type: external_scanner when the signal or context source lives outside the runtime and should push data in through the gateway instead of being scheduled on an interval.

scanners:
  - name: funding_arb
    type: external_scanner
    outputs:
      signals: true
      context: false
    retention: rolling_window
    retention_max_runs: 50
    config:
      fields:
        funding_rate: { type: number, required: true }
        spread: { type: number, required: true }
        exchange: { type: string }

Ingest data with the gateway RPC:

openclaw gateway call senpi.ingestExternalScannerData --params '{
  "address": "0xYourStrategyWallet",
  "scanner": "funding_arb",
  "asset": "ETH-PERP",
  "direction": "LONG",
  "score": 0.91,
  "signal_type": "EXTERNAL_FUNDING_ARB",
  "data": {
    "funding_rate": 0.015,
    "spread": 0.003,
    "exchange": "binance"
  }
}'

Prompt access stays namespaced:

  • {{signal_funding_arb}} for ingested signals
  • {{context_custom_regime}} for retained external context
  • flat aliases like {{funding_arb}} are not created

Guide skill

Once published, the @senpi-guide skill on ClawHub provides the same reference in chat. Install with clawhub install skills/senpi-guide/ (or from the ClawHub UI).

Install on an existing OpenClaw setup

If you already have OpenClaw installed (local, VPS, or self-hosted), see Installing on existing OpenClaw for the full install sequence, config stanza, and troubleshooting.

Docs

Changelog

CHANGELOG.md

Development

npm run build            # install deps + compile TypeScript (excludes *.test.ts)
npm run typecheck:tests  # typecheck all src including tests (no emit)
npm test                 # unit + integration tests (no credentials required)
npm run dev              # build + run

Pre-commit runs npm run build, npm run typecheck:tests, and npm test. See CLAUDE.md and docs/runtime-docs/ for conventions and design.