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

sentinel-trading-agent

v0.1.1

Published

Read-only CLI that watches a Solana perp wallet on Jupiter Perpetuals, enforces your own discipline rules, and journals every breach.

Readme

Sentinel Trading Agent

A read-only CLI that watches your Solana perp wallet, alerts you in real time when you break your own discipline rules, and journals every breach. It does not sign transactions, hold keys, or give trade ideas. It is the laptop saying "you said you would not do that".

What it looks like

→ sentinel watch (Jupiter Perps, mainnet)
  wallet:  DemoWa11etPub1111111111111111111111111111111
  rpc:     https://mainnet.helius-rpc.com/?api-key=***
  session: 16:25–17:40 America/Sao_Paulo
  rules:   4 built-in + 1 custom: enforceSessionWindow, noExecDay,
           maxNewPositions, drawdownStop, no-eth
  poll every 10s — Ctrl+C to stop

3-questions gate — 2026-05-09
1. What is BTC's current trend on my timeframe? > uptrend, holding above 115k
2. Where am I looking for entries today?      > 115k retest long
3. What invalidates my setup?                  > close below 112k
✓ recorded to journal/2026-05-09.json

▶ session started — baseline equity $34.20

● [16:31:18]  equity $34.20  (wallet $14.20 + coll $20.00)
  ● enforceSessionWindow  session open 16:25–17:40 (local 16:31)
  ● noExecDay             FRI is an exec day
  ● maxNewPositions       1/1 new positions opened this session
  ● drawdownStop          equity +0.00% (limit -2%)
  ● no-eth                no ETH-PERP position
  LONG  BTC-PERP   size $    20.00  coll $   14.50  entry $119750.00  1.4x

● [16:32:09]  equity $33.95  (wallet $14.20 + coll $19.75)
  ● enforceSessionWindow  session open 16:25–17:40 (local 16:32)
  ● noExecDay             FRI is an exec day
  ● maxNewPositions       VIOLATION — 2 new positions opened, limit 1
  ● drawdownStop          equity -0.73% (limit -2%)
  ● no-eth                no ETH-PERP position
  LONG  BTC-PERP   size $    20.00  coll $   14.50  entry $119750.00  1.4x
  LONG  BTC-PERP   size $    15.00  coll $    9.75  entry $119820.00  1.5x

Demo video

(https://youtu.be/9c5VFNbPHWQ)

Why discipline beats edge

Most retail traders do not lose money because their entries are bad. They lose money because the rules they wrote on Sunday are negotiable by Wednesday. One overleveraged position, one revenge trade after a stop, one extra session "just to recover today's loss". The account does not die from missing edge. It dies from a discretion that compounds.

Self-control is not an unlimited resource. The traders who survive the first year do not have more willpower than the ones who blow up. They externalise. They write rules down, they tell a coach, they set hard daily loss limits at the broker level. Sentinel is the same pattern, encoded. Your rules live in ~/.sentinelrc.json as JSON, not as a mood. The terminal does not negotiate.

The promise is narrow on purpose. Sentinel reads the chain. It compares your live state to the rule set you defined. When something is out of bounds, it prints a red VIOLATION and writes the breach to your journal. The transaction has already settled on chain by the time you see it, so Sentinel is observation, not enforcement. What disappears is the excuse "I did not realise I was breaking my own rules".

Install

Requires Node 20 or newer.

npm install -g sentinel-trading-agent
sentinel --version    # 0.1.1
sentinel init         # interactive wizard, writes ~/.sentinelrc.json
sentinel watch        # forces 3-questions gate, then polls every 10s

sentinel init walks you through wallet pubkey, RPC URL, session window, and which rules to enable. The resulting config file is written chmod 600. Re-run sentinel init to reset, or edit the JSON by hand to add or remove rules between sessions.

How watch works

| Step | What happens | |---|---| | 1 | Loads ~/.sentinelrc.json. Falls back to env vars (SENTINEL_WALLET_PUBKEY, SOLANA_RPC_URL) for local development. | | 2 | Connects to Jupiter Perpetuals on Solana mainnet via your RPC. | | 3 | Forces the 3-questions gate before any polling. Answers go to journal/YYYY-MM-DD.json. If today's file already has answers, the gate skips. | | 4 | Polls every pollIntervalMs milliseconds (default 10000). Each tick reads positions, native SOL + all 5 Jupiter custody token balances, and Pyth oracle prices in 3 RPC calls. | | 5 | At session-window entry, captures equity = wallet assets + collateral and the set of position pubkeys as the baseline. At session-window exit, writes a session_end event with final equity and percent change. | | 6 | Runs every enabled rule against the snapshot. Prints a header line plus one row per rule with traffic-light dots. Appends every warn and block to today's journal entry. |

Configuration reference

The full schema, with every optional block annotated:

{
  "wallet": "YourSolanaPubkey...",
  "rpcUrl": "https://mainnet.helius-rpc.com/?api-key=YOUR_KEY",

  "sessionWindow": {
    "start": "16:25",
    "end":   "17:40",
    "tz":    "America/Sao_Paulo"
  },

  "pollIntervalMs": 10000,

  "enforceSessionWindow": true,
  "noExecDay":          { "days": ["tue", "thu"] },
  "maxNewPositions":    { "max": 1 },
  "drawdownStop":       { "pct": -2 },
  "maxLeverage":        { "max": 10 },
  "maxPositionSizeUsd": { "max": 5000 },
  "allowedMarkets":     { "markets": ["BTC-PERP"] },
  "forbiddenMarkets":   { "markets": ["ETH-PERP"] }
}

Every rule block is optional. An absent block means the rule is off. enforceSessionWindow is the only boolean flag, since the window itself lives in sessionWindow. pollIntervalMs accepts any integer between 1000 (1s) and 600000 (10min); lower values react faster, higher values keep the terminal quiet and save RPC quota.

Rule catalog

| Key | Status when triggered | What it checks | |---|---|---| | enforceSessionWindow | block | Local time is outside sessionWindow.startsessionWindow.end. | | noExecDay | block | Local weekday is in the configured days list. | | maxNewPositions | warn at limit, block over | Count of positions whose pubkey is not in the session-start baseline. | | drawdownStop | block | Equity dropped pct% or more from the session-start baseline. | | maxLeverage | block | Any open position has sizeUsd / collateralUsd > max. | | maxPositionSizeUsd | block | Any open position has sizeUsd > max. | | allowedMarkets | block | An open position is in a market not on the whitelist. | | forbiddenMarkets | block | An open position is in a market on the blacklist. |

The header dot reflects the worst status across all enabled rules.

Example: minimal config (4 rules)

{
  "wallet": "YourSolanaPubkey...",
  "rpcUrl": "https://mainnet.helius-rpc.com/?api-key=YOUR_KEY",
  "sessionWindow": { "start": "16:25", "end": "17:40", "tz": "America/Sao_Paulo" },
  "enforceSessionWindow": true,
  "noExecDay":          { "days": ["tue", "thu"] },
  "maxNewPositions":    { "max": 1 },
  "drawdownStop":       { "pct": -2 }
}

Example: aggressive risk caps (BTC only, slow polling)

{
  "wallet": "YourSolanaPubkey...",
  "rpcUrl": "https://mainnet.helius-rpc.com/?api-key=YOUR_KEY",
  "sessionWindow": { "start": "13:00", "end": "21:00", "tz": "America/New_York" },
  "pollIntervalMs": 30000,
  "enforceSessionWindow": true,
  "maxNewPositions":    { "max": 3 },
  "drawdownStop":       { "pct": -5 },
  "maxLeverage":        { "max": 5 },
  "maxPositionSizeUsd": { "max": 1000 },
  "allowedMarkets":     { "markets": ["BTC-PERP"] }
}

Custom rules (no fork required)

If the catalog does not cover what you need, drop a JavaScript file into ~/.sentinel/rules/. Sentinel auto-loads every .js and .mjs file at the next sentinel watch startup.

// ~/.sentinel/rules/no-eth.js
export default function noEthRule(ctx) {
  const eth = ctx.snapshot.positions.find((p) => p.marketSymbol === "ETH-PERP");
  if (eth) {
    return { rule: "no-eth", status: "block", message: "VIOLATION — ETH-PERP open" };
  }
  return { rule: "no-eth", status: "ok", message: "no ETH position" };
}

The function receives the same RuleContext built-in rules see:

| Field | Type | What it gives you | |---|---|---| | ctx.now | Date | UTC timestamp of this tick | | ctx.config | SentinelConfig | Your parsed ~/.sentinelrc.json | | ctx.snapshot.positions | SentinelPosition[] | Each item: marketSymbol, side, sizeUsd, collateralUsd, entryPriceUsd, realisedPnlUsd, positionPubkey, openTime, updateTime | | ctx.snapshot.walletAssets | { total, byMint } | Wallet value in USD plus per-mint breakdown with balances and oracle prices | | ctx.snapshot.totalCollateralUsd | number | Sum of collateralUsd across all open positions | | ctx.session | SessionState | startedAt, baselineEquityUsd, baselinePositionPubkeys (if a session is active) |

The function must return { rule: string, status: "ok" | "warn" | "block", message: string }. If it throws or returns a malformed object, Sentinel turns the failure into a warn row instead of crashing the watch loop.

Override the directory with SENTINEL_CUSTOM_RULES_DIR=/path/to/rules. The startup banner reports how many custom rules loaded and from where.

Two working examples ship in examples/custom-rules/:

  • no-eth.js blocks any open ETH-PERP position.
  • min-collateral.js warns when any position has less than 25 USD of collateral.

Copy them into ~/.sentinel/rules/ and they take effect on the next sentinel watch.

What Sentinel does not do

  • It is read-only by design. Sentinel never touches a private key. By the time a VIOLATION shows in your terminal, the transaction has already settled on chain. If you want preventive control, set hard limits at the venue, not in code that only watches.
  • It does not include unrealised PnL on open positions in the drawdown calculation. Equity is walletAssets.total + totalCollateralUsd. A losing position only registers in equity once you close or reduce it. v0.2 will fetch Pyth mark price per market.
  • It monitors one wallet. Multi-wallet is a v0.4 feature; for now Sentinel watches the single owner pubkey in your config.
  • It polls. No websocket or gRPC subscription yet. The default 10s tick is fine for a daily-discretion tool, less so for high-frequency tracking.

Architecture

                    ┌──────────────────────┐
                    │   Solana mainnet     │
                    │     (Helius RPC)     │
                    └──────────┬───────────┘
                               │
                ┌──────────────▼──────────────┐
                │     JupiterPerpsClient      │
                │  positions + walletAssets   │
                │  via Anchor IDL + Pyth      │
                └──────────────┬──────────────┘
                               │  snapshot every 10s
                ┌──────────────▼──────────────┐
                │        Rule Engine          │
                │  built-in catalog (8) +     │
                │  ~/.sentinel/rules/*.js     │
                └──────────────┬──────────────┘
                               │
                 ┌─────────────┴─────────────┐
                 │                           │
            ┌────▼─────┐                ┌────▼────┐
            │ Terminal │                │ Journal │
            │  output  │                │  store  │
            │ (chalk)  │                │  (JSON) │
            └──────────┘                └─────────┘
  • src/jupiter/ reads positions via getProgramAccounts filtered by Anchor account discriminator and owner memcmp. Wallet equity reads native SOL plus all 5 Jupiter custody mints (USDC, USDT, WSOL, WBTC, WETH) and prices them with the same Pyth feeds Jupiter itself trusts. The Anchor IDL is embedded as a TypeScript constant; regenerate it with scripts/fetch-jupiter-idl.ts.
  • src/rules/ treats every rule as a factory (params) => (ctx) => RuleResult. The engine builds enabled rules from config blocks at startup; runtime dispatch is a flat array map. Adding a built-in rule is one factory file plus a branch in buildRules. Custom plugins from ~/.sentinel/rules/ are composed onto the same array via buildAllRules.
  • src/journal/ writes one JSON file per day at ./journal/YYYY-MM-DD.json (override via SENTINEL_JOURNAL_DIR). Writes are atomic (tmp + rename). Entries record session lifecycle, rule warnings, rule violations, and the answers to the 3-questions gate.
  • src/config/ uses Zod for schema validation. The loader prefers ~/.sentinelrc.json (chmod 600), falls back to env vars when no config file exists.

Develop

git clone https://github.com/BHHZIN/sentinel-trading-agent.git
cd sentinel-trading-agent
npm install
cp .env.example .env       # fill HELIUS_API_KEY and SENTINEL_WALLET_PUBKEY
npm run build
npm run dev -- watch       # runs from source via tsx, no global install needed
npm test                   # 33 vitest cases

Roadmap

| Version | Focus | |---|---| | v0.2 | Pyth mark-price for unrealised PnL drawdown. Position open and close events derived from snapshot diffs. Hot-reload of custom rules during a watch session. | | v0.3 | Telegram and Discord notifiers (env-driven). On-chain journal hashing via the Solana Memo Program. | | v0.4 | Multi-wallet monitoring. Multi-protocol adapter (Drift v2 once protocol stabilises, Mango v4). | | v0.5+ | Static HTML report from journal entries (sentinel report --date). Web dashboard for hosted users. |

Why this exists

I am Bruno, a 17-year-old participant in Brazil. I built Sentinel to help people like me with discipline problems. initally i wanted to do something more complex to help with discipline but i had just 4 days to code it

If you want to use Sentinel against your own wallet, run sentinel init and define your rules. If you want to add a rule the catalog does not cover, drop a JavaScript file in ~/.sentinel/rules/, no fork required. Pull requests for catalog additions are welcome.

License

MIT.