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

@secondlayer/sentinel

v0.36.0

Published

Security monitoring for Stacks (Clarity) smart contracts: CLI, MCP server and TypeScript client. Describe what to watch; get alerts by email, Slack or signed webhook.

Readme

@secondlayer/sentinel

Security monitoring for Stacks (Clarity) smart contracts. Describe what to watch from a terminal, MCP or TypeScript, and get an alert by email, Slack or signed webhook when it happens. Node 22 or newer. Docs: https://runsentinel.app/docs

npm i -g @secondlayer/sentinel
sentinel login
sentinel contracts

sentinel login signs in with your secondlayer account: open the link it prints and confirm the code. From an agent's shell (no terminal) it exits; sentinel login --wait finishes it. sentinel login --email uses an emailed 6-digit code instead. It shares ~/.secondlayer/session.json with the secondlayer CLI, so either login serves both. A first login creates your Sentinel account.

For CI, pipe a key from the web app (API keys): echo "$KEY" | sentinel login --with-token, or set SENTINEL_API_KEY. Order: --token, SENTINEL_API_KEY, saved key, saved login.

CLI

sentinel when transfer zest-vault sBTC --out --over 5 --dry-run  # what it would have triggered, and the cost
sentinel when transfer zest-vault sBTC --out --over 5            # add it (the contract joins your account)
sentinel list                                                    # your watches, by id

| Command | Does | |---|---| | sentinel whoami [--json] | who you are, where the credential came from, your tier, its live contracts and this period's check usage | | sentinel contracts [--json] | the contracts on your account: added, or only watched | | sentinel describe "<sentence>"\|<line> [--json] | a sentence (or a line with names) as one watch's line, what it would have done in the last 90 days and what it costs; writes nothing. | | sentinel when call\|transfer\|mint\|print\|state\|deploy <target> … \| when sbtc\|pox5 <check> … [--to <ref>]… [--note "<text>"] [--check "<question>" [--deep] [--cap N]] [--hold] [--dry-run] [--yes] [--wait] | a watch, as one line, added at once with what it would have done in the last 90 days (a contract not on your account joins it, its monitoring on; a destination's first alert is a test one); --dry-run: only that backtest and the cost; --wait waits until it is live. The same line again edits it, and a --check, --to or --note it leaves out is removed. --check: a model answers your question on each event first and it alerts only on a cited yes ($0.25 a run, --deep $0.75, at most --cap a month, default 100); a check, or a Builder contract over its count, shows its cost and waits for --yes | | sentinel list [<target>] [--all] [--json] | your watches and schedules by id (w_…, sch_…): what each is as a line and whether it alerts now; --all: the suggestions too | | sentinel show <w_id\|sch_id\|chk_id\|alr_id> [--json] | one watch (its line, where it goes, its check, a group's members and where each came from, its last alert), schedule, check run or alert; or a contract: what its code has (functions, tokens, groups, prints), starting its free plan | | sentinel logs [<w_id\|sch_id\|target>] [--only events\|alerts\|checks\|schedules\|changes] | what happened, oldest first: a watch's matched events, alerts, check runs, schedule runs and changes; -f follows (every 5 s) | | sentinel destinations [--json] | where alerts can go: your address and what you added; a default gets every watch that names none (slack:#oncall, email:a@b, webhook:ops) | | sentinel --help | everything else |

A free write happens at once and --dry-run previews it. --yes is for a cost (a check, a contract over your tier's count) and for stopping coverage (off, remove, destinations remove, keys revoke), plus backtest and test-alert, which preview until then. Plan upkeep works here too: everything the web app does past sign-in and billing. sentinel keys create <name> makes a key from your login.

MCP

claude mcp add -t http sentinel https://api.runsentinel.app/mcp

Signs in through your browser, or add --header "Authorization: Bearer sk-snt_…" for a scoped key. A secondlayer API key won't work here; Sentinel keys start sk-snt_. One tool per verb, the CLI's words (when, list, on …). A write that spends, reroutes alerts or stops coverage only previews until confirm: true.

Agent skill

npx skills add https://runsentinel.app

Teaches an agent the loop: look, preview, show the person the cost, wait for a yes. It installs into the project (.claude/skills/sentinel/, and the folders of the other agents it finds), so it is committed with the repo; -g installs it for you everywhere. The same file ships in this package as skills/sentinel/SKILL.md, for npx skills add ./node_modules/@secondlayer/sentinel or for reading.

To let an agent run the reads without asking, allow-list only the read verbs: Let an agent run it.

SDK

import { createClient, resolveToken } from "@secondlayer/sentinel";

const credential = await resolveToken();
const sentinel = createClient({ token: credential?.token ?? "" });
console.log(await sentinel.when("transfer zest-vault sBTC --out --over 5")); // added at once; { dryRun: true } previews it

Webhooks

verifyWebhook checks a Sentinel webhook (Standard Webhooks, 5-minute tolerance) and returns the event: alert.created, delivery.failed, monitoring.paused, monitoring.resumed or watch.changed. Pass the raw body; dedupe on event.id.

import { verifyWebhook } from "@secondlayer/sentinel";

const event = await verifyWebhook(await req.text(), req.headers, process.env.SENTINEL_WEBHOOK_SECRET!);
if (event.type === "alert.created" && event.data.alertLevel === "urgent") page(event.data);

Missed some? sentinel.alerts({ after: lastAlertId }) returns the ones since, oldest first.

Claiming, choosing a tier and key creation stay in the web app.

0.36.0

  • Added: each destination reports its last delivery. destinations() (GET /v1/destinations, the destinations MCP tool) carries lastDelivery: { alertId, ok, detail, at } | null; sentinel destinations adds a LAST column and, for a destination whose newest delivery failed, a Resend: sentinel redeliver <alertId> line.
  • Changed: sentinel status ends with Next:, like every other command (it printed next:).

0.35.0

  • Changed: every agent route lives under /v1, the same path with a /v1 prefix and /plan → /v1/plans: POST /plan and /plan/preview, /plan/:id/{latest,update,settings,remove,alert-preview,monitor,testalert}, /contracts/import and /contracts/import/preview, /integrations/slack (and /install, /:team), /alerts/read-all and /alerts/:id/{read,redeliver,mute}, GET /billing, POST /billing/checkout and GET /billing/checkout/:id. The CLI, MCP and SDK moved with them; scripts calling the old paths use the new ones.
  • Changed: the SDK is the verbs. createClient returns one method per CLI verb, with the CLI's answers ({ done, willDo, effect, next } for a write): when, every, at, describe, list, show, logs, on, off, mute, read, rename, remove, group, whoami, upgrade, checkoutStatus, status, import, backtest, contracts, alerts, testAlert, redeliver, update, connectSlack, destinations, destinationsAdd, destinationsDefault, destinationsRemove, keys, keysCreate, keysRevoke. createApi and ApiClient are no longer exported.
  • Changed: contracts(), alerts(options) and destinations() answer { contracts, next }, { alerts, next } and { destinations, next } (an array before). testAlert(target, { yes }), redeliver(alertId, { dryRun }), update(target, { commit, dryRun }) and connectSlack({ status, remove, yes }) take what the CLI takes.
  • Removed (use the verb): me, billing → whoami; plan, alert, publicWatch, schedule, checkRun, systemSeries, previewPlan, createPlan → show; previewImport, importRepo → import; startCheckout → upgrade; checkoutState → checkoutStatus; createKey, revokeKey → keysCreate, keysRevoke.
  • Removed (use the verb): previewWatch, addWatch → when; compileWatch → describe; previewSchedule, createSchedule → every, at; publicWatches, schedules → list; logEntries, checkRuns → logs; groups, setGroup → group; backtestCheck → backtest.
  • Removed (use the verb): startMonitoring, stopMonitoring, setWatchOn, resumeSchedule → on, off; deleteSchedule → off; setWatchDelivery, muteAlert → mute; readAlert, readAllAlerts → read; alertPreview → testAlert; latest → update with dryRun; renameContract → rename; removeContract → remove.
  • Removed (use the verb): addDestination, setDefaultDestination, removeDestination → destinationsAdd, destinationsDefault, destinationsRemove; slackInstalls, disconnectSlack → connectSlack with status or remove.
  • Removed: claims, unreadAlerts and billing from GET /v1/me (and sentinel whoami --json): it answers user, contracts, latestAlerts and liveBlocked. The Claim type is gone from the SDK.
  • Removed: the Sentinel-API-Version response header on /v1. The API changes without a version bump; breaking changes are dated in the API changelog.
  • Removed: sentinel retry-history, the retry_history MCP tool, the SDK retryHistory method and POST /plan/:id/history/retry. A failed history read is read again on its own (at the next nightly run, a new watch or a backtest).
  • Removed: sentinel auto-plan, sentinel link and sentinel plan-linked, the auto_plan, link and plan_linked MCP tools, the SDK autoPlan, link and planLinked methods, and POST /plan/:id/wait, /link and /plan-linked. waiting on a plan view drops auto, linked and each missing contract's link. A waiting plan plans itself once the contracts it calls deploy; to plan sooner, import the repo that has them.
  • Changed: latest (GET /plan/:id/latest) drops free, and its mode is carried or full. update is free and plans the whole file.
  • Removed: sentinel status watch, the status_watch MCP tool, the SDK statusWatch method and POST /v1/status/watch. Every digest now carries the monitoring-is-up line; account.heartbeat is gone from GET /v1/status.
  • Removed: GET /v1/plans and the SDK plans() method. Use GET /v1/contracts (contracts()) or GET /v1/me (contracts).
  • Removed: the sentinel-mcp bin (local stdio MCP server). Use the hosted server: claude mcp add -t http sentinel https://api.runsentinel.app/mcp.
  • Removed: sentinel ask, the ask MCP tool, the SDK ask method and POST /ask (the docs answerer). Read the docs or /llms.txt instead.

0.34.1

  • Fixed: the Free waiting line shows $19; a waiting add no longer says its monitoring turns on; a replayed test alert's footer says it is a past event; whoami on Free names sentinel upgrade builder.
  • Changed: login --email links a new account (with secondlayer's matching change).

0.34.0

Changed: show takes a contract (SP….name, your name for it, or a GitHub .clar link) and returns what its code has: { kind: "contract", contractId, contract, next }, with a functions line in the text output. It starts the contract's free plan, as watchable did.

Removed: the watchable verb (CLI command, MCP tool, SDK watchable and WatchableResult): use show <contract>; its result's watchable key is now contract. The raw API follows: a plan's report.watchable and the teaser's watchable are contract. SDK type Watchable is ContractMenu.

Changed: when on a live mainnet contract your tier doesn't cover (Free, or a full tier) saves the watch and the contract waits instead of answering 402: the result's monitoring is over_limit, with tier. It goes live when you upgrade, with no command. System watches and mainnet deploy watches keep the 402. The 402 texts name sentinel upgrade.

Changed: test-alert sends the newest real match of the contract's watches in the last 90 days (a sample if there is none), marked a test with its date. A test webhook carries replayedFrom: { txId, at }.

Added: upgrade (CLI sentinel upgrade <builder|team> [--wait], MCP tool, SDK upgrade and checkoutStatus, POST /billing/checkout and GET /billing/checkout/:id): a Stripe link to subscribe, or to the portal once subscribed. It only gives the link; the person pays on Stripe.

0.33.0

Changed: POST /plan/:id/monitor takes { deployer } only. Removed: functions on a plan's report.

0.32.0

Removed: SDK watches, addRule, setRule, watchActivity, functions, functionActivity, threshold, setThreshold, monitorSettings, intentLine and assetClause, and the plan routes behind them (write a watch with when, read it with list/show/logs, see a contract's surface with watchable).

0.31.0

Changed: sending a watch's line again replaces its --check, --to and --note. One the line leaves out is removed (a check stops running). Mute and --hold are kept.

Removed: the note verb (CLI, MCP, SDK) and note on POST /v1/watches/:id/delivery: set a note with --note on the watch's line.

0.30.0

Removed: SDK parseRule and POST /plan/:id/rules/parse; /v1/me.running.

0.29.0

Removed: on|off|mute|note <contract> <subject> (use the watch's w_ id; on and off still take a contract, to resume or stop all of its monitoring), alerts --intent, plan ids as targets and in output, PublicWatch.planId, the SDK methods setWatches, watchContract, mute, setWatchNote and setWatchCheck, and on <contract> adding a contract (use when). sentinel contracts no longer prints the plan.

Changed: update, auto-plan, link and plan-linked take the pre-launch contract, not its plan id.

0.28.0

Removed: projects (projects, project, move, merge, --project, project targets, prj_ ids, MCP projects/project/move/merge, SDK Project and project methods, /projects* routes, ?project=). A contract's name or id is the only target; a plan id still works until it leaves the user surfaces.

Added: sentinel contracts (MCP contracts, SDK contracts(), GET /v1/contracts): the contracts on your account, added or only watched. import --hold now holds each imported contract.

Changed:

  • every … --digest (and at) with no target is a digest of all your watches; a digest's target is a contract or the account ({ kind: "account" }).
  • Import is POST /contracts/import (preview: /contracts/import/preview); a repo contract's state is added where it was in-project.
  • remove takes a contract you only watch off your account too, with one message.

0.27.0

Changed:

  • Destinations belong to the account; --to works on any watch; a watch without --to goes to your default destinations (and your email).

Added:

  • destinations add webhook <url> --label, destinations default <ref> on|off, --default.

Removed:

  • destinations <target|project> --email/--slack/--webhook, --project on destinations and on when deploy|sbtc|pox5, the per-contract webhook and its signing secret, MCP destinations_list.

0.26.0

Changed:

  • A free write happens at once; --dry-run previews it; --yes is only for a cost or for stopping coverage. when, every, at, on, group, move, merge, import, destinations, destinations add, connect slack and the pre-launch upkeep verbs no longer need --yes.
  • A written when prints its 90-day backtest line. A check, a check or ask schedule, or a Builder contract over its count still shows its cost and waits for --yes (MCP confirm: true, SDK { yes: true }).
  • POST /v1/schedules with a check or ask needs confirm: true; without it the route answers the preview and its cost and creates nothing.
  • SDK addWatch(spec, { confirm }) and createSchedule(input, { confirm }) send confirm only when given; an unconfirmed answer is returned as data (confirmed: false).
  • MCP: the free write tools lose confirm. off, remove, destinations_remove, backtest and test_alert keep it. when, every and at keep it for a cost.
  • A line sent to MCP or the SDK that carries --dry-run is refused, like --yes and --wait: use dry_run: true / { dryRun: true }.

Added:

  • --dry-run on every write command, MCP dry_run on every write tool, SDK dryRun on every write verb. On a write with nothing to preview it says so and writes nothing.

0.25.0

Removed:

  • sentinel when --spec and --off, the SDK removeWatch and DELETE /v1/watches: turn a watch off by its id, sentinel off <w_id>.
  • The bare sentinel "<sentence>": use sentinel describe "<sentence>".
  • remove --confirm <name> and the MCP remove tool's name: remove <target> --yes (MCP: confirm: true).

0.24.1

  • A when, every or at preview from MCP or the SDK ends with its cost again: Events are counted, never billed. (0.24.0 dropped it there by accident; the CLI's own preview is unchanged.)
  • zod 4.6.7 (memory exhaustion on oversized constrained collections, GHSA-mrhx-c7xc-m3w5); the MCP SDK's dependencies patched.

0.24.0

  • A watch is live only once it alerts. While its contract's monitoring is being set up it is pending, with pending: { reason, failedAt } saying why (a failed setup, in words); while that monitoring is off it is off. Type PublicWatch.status: WatchStatus | "pending" | "off", PublicWatch.pending.
  • sentinel when … --yes --wait exits 1 at once, with the reason, when the watch can't go live, instead of waiting out the timeout. The MCP when tool's wait: true gives the same reason.
  • A test alert (sentinel test-alert --yes, and the first alert a new destination gets) is the full alert, marked a test, sent to every place the contract's alerts go, named destinations included. Its Mute 1 h and Mark read work and change nothing.
  • Previews read like a person wrote them: Last 90 days: 1,904 events, 3 would have alerted (last Oct 9)., matches as Oct 9 2.5 sBTC left the contract (an amount once, not twice), and the preview ends with its --yes line as Next:. A watch that is live starts with ✓ Live.
  • Turning monitoring on names where its watches' alerts go (Alerts go to: #oncall). Previews no longer say events are billed: events are counted, never billed.

0.23.1

  • A call alert leads with a Function fact (the function called, quoted in every sentence) and a Called with fact (up to 5 arguments, quoted; and N more), so alerts of one headline tell apart.
  • A backtest match carries an optional detail: its first fact's value, quoted (a call's function). The CLI prints it after the headline in previews and sentinel alerts; Slack's notification text ends with it. Type SentinelClient preview backtest.matches[].detail?: string.

0.23.0

  • An alert's summary is renamed text (no alias).
  • An alert gains facts[] (each fact a label and a typed value: id, amount, time or text, full ids and base units, with chainSourced) and signals[] (why it fired, with the numbers).
  • checkFirst is set on every alert Sentinel raises; alerts stored before this release read null.
  • Headlines are plain words and name nothing the chain chose. In a fact's value a plain contract, function or address shows as code (shortened where a person reads, full in the JSON); sentences and free text the chain wrote stay quoted.
  • sentinel show prints a headline, one fact per line and the next commands; a Money out alert with no limit leads with the command that sets one.
  • New intent kind minted (INTENT_KINDS), subject kind mint and actor signal newMinter, and the trigger sentinel when mint <token-contract> <token-name> [--new-minter | --over N]: a fungible token minted, paging on a minter that never minted it before (a contract that defines a token gets this watch by default) or on a mint over an amount. A Minted alert's facts are Amount, To and Minted through.
  • sentinel when transfer <contract> <token> --out --over 20% pages on one outflow that is 20% or more of the contract's balance before it (rule kind outflowShare, bps; alert signal outflowShare, fact Share). The balance is read from the node at the block before the outflow; unread, it pages and says so.

0.22.0

  • New intent kind proposals (INTENT_KINDS): a governance proposal fn's watch. It pages urgent when a call names a contract published in the last 30 days by an address that deploys none of the protocol's contracts, and normal on a first-time proposer.
  • A call alert's text adds Names «SP….contract».: every contract its arguments carry, at any depth, in full (at most 3).

0.21.1

  • sentinel-mcp bundles @modelcontextprotocol/sdk 1.32.1 (two high advisories fixed: session-bound tasks, request-size memory). No behavior change.

0.21.0

  • A call alert's text (email, Slack, the webhook's summary) says what the function was called with: Called with «u500», «SP….vault»., up to 5 arguments, quoted.

0.20.0

  • An alert's chainEvent carries the event's decoded value (a print, up to 1,000 characters) or args (a call, each up to 300), and so does each backtest match. Chain-sourced. Alert text is unchanged.

0.19.0

  • A watch preview's backtest lists the newest 50 matches that would have alerted (backtest.matches: block, transaction, headline); wouldAlert still counts them all. sample (5) is gone. The CLI still prints 5 and says how many more --json lists.

0.18.0

  • Print watches match the event name inside the printed value: the printed string, or a tuple's topic, event, action, type or object. A watch on swap now fires on a print whose tuple says action: "swap", in backtests and live. One unfiltered print subscription per contract serves every print watch on it.
  • A call alert on a watch with no caller list is headlined Call to <fn>; Unlisted caller on <fn> stays for a watch with a list.
  • A function that reaches a mint or burn only through another public function is described as Can mint or burn tokens through <fn>. instead of Mints or burns tokens.

0.17.0

Builder is $19 per live contract a month, up to 3, no longer a flat price for one. Its subscription's quantity follows your live-contract count.

  • Going live past your count on Builder: sentinel when … previews the added $19, and --yes adds the contract and charges it now (prorated). Any other write that would put a contract live at your count gets 402 tier_limit naming sentinel when. Turning a contract off lowers the next invoice.
  • New 402 code cost_unconfirmed on POST /v1/watches without confirm: true when the add costs money (the contract is not added, nothing is charged).
  • GET /billing: tier.liveCodes.limit is what the account has bought (Builder: its quantity). New tier.contractCap (the most the tier can cover: 3 on Builder) and tier.usdMonth (the subscription's monthly price; null for Free and a grant).
  • sentinel whoami shows the monthly price: Tier: Builder · 2 of 3 live contracts · $38/mo · renews ….
  • Protocol is sold by hand, so the site and docs say to contact us.
  • A Free account can add a card for checks (account page): live and --deep checks run and are billed per run, invoiced at $10 owed or at month end, with no monthly tier. GET /billing adds checks.billing (per_run once a card is on file, else null); SDK BillingView matches. sentinel whoami on Free then ends with checks billed per run · N this month ($X). The card does not put a mainnet contract live.

0.16.0

Alerts no longer link to a web page: the web app is now the site, sign-in and one Account page.

  • Removed (no replacement): links.alert on alerts (API, webhook, SDK Alert type). links keeps tx. Each alert ends with the command to inspect it.
  • sentinel alert show drops its Open: line.
  • Server links (billing, keys, a contract going live, digests' links.app) point at /account.

0.15.0

Billing is tiers on Stripe. The prepaid balance, the starter credit, top-ups, the monthly cap and per-event pricing are gone, with no replacement fields.

  • Tiers: Free (plans, pre-launch watches, testnet), Builder (1 live mainnet contract), Team (15) and Protocol (by hand). A live contract is a unique code: the same source on two contracts counts once. Monitored events are counted and never billed.
  • GET /billing adds tier (id, name, founding, status, periodEnd, cancelAt, until, source, liveCodes), checks (periodRuns, periodUnits, periodUsd, pricePerRunUsd, deepPricePerRunUsd), selfServe and manageUrl. It drops balanceUsd, spentMonthUsd, capUsd, owedUsd, refill, prices, eventAllowance and unavailable. SDK: BillingView and TierId change to match. sentinel whoami and MCP whoami print the tier, its live contracts and this period's check usage.
  • GET /billing also carries cutover (when an account with live mainnet contracts and no tier is told its watches turn off; else null).
  • Removed routes: POST /billing/checkout and POST /billing/settings. Added, web app only: POST /billing/tier and POST /billing/portal.
  • A write that would put a contract live past the tier's limit answers 402 (code: "tier_limit", with tier, used, limit, upgradeUrl); tier_required where the tier doesn't include it (when sbtc or when pox5 on Free, a check on Free). The old 403 for a check on an account billing couldn't charge is 402 tier_required.
  • Checks need a paid tier and are added to its monthly invoice ($0.25 a run, $0.75 --deep). Free previews a check and runs 10 Sonnet backtest runs a month.
  • Webhooks: new event monitoring.over_limit (a contract waits for room in the tier; it grows the WebhookEvent union, so exhaustive switches need a case). monitoring.paused gains reason (unpaid, cancelled, grant_end or cutover) and monitoring.resumed gains reason (paid).

0.14.0

Everything kept only for an old name, an old client or old data is gone. Nothing below has a replacement alias.

Removed from the CLI. The old command names no longer run (they are "unknown command"), and renamed: sentinel <new> is not printed any more. Use the current names: balance is whoami; mark-read is read; plans is projects; plan, new and functions are watchable; name is rename; projects show|overview is project; projects rename is rename; projects add|move is move; projects merge|suggestion is merge; projects remove|remove-contract is remove; projects destinations is destinations; projects monitoring, monitor on, follow and watch on are on; unfollow and watch off are off; unmute and mute-alert are mute; watch-note is note; alert-preview is test-alert; watches, schedules and projects watches are list; rule, threshold and check are when; schedules show, checks show and alert are show; activity, checks, projects activity and projects changes are logs; schedules off is off; latest is update. projects now only lists, and its table has no FINDINGS column.

Removed from the SDK: latestCommit, updatePlan, setAutoPlan, setLink and redeliverAlert (use latest, update, autoPlan, link and redeliver). IntentWatch.stats7d is gone: read the last 7 slots of stats30d. ApiKeyInfo.legacy is gone. IntentKind and INTENT_KINDS are exported (the one list of intent kinds, including check and system); WatchRule gains the system kind.

Removed from the API:

  • GET /me, GET /alerts and GET /plan/:id. Use GET /v1/me (it also opens the account on secondlayer, as /me did), GET /v1/alerts and GET /v1/plans/:id.
  • ?kinds=all on GET /plan/:id/watches: the list always carries every kind of watch.
  • A watch choice named by its function alone ({ fn, on }): POST /plan/:id/monitor needs watchId on each choice, or answers 400. fn is only a label now.
  • POST /keys { name, actions } answers 400: say what the key may touch with areas. The old read and read,actions key scopes are gone.
  • Local snt_ keys stop working (401): make an sk-snt_ key. GET /keys lists no legacy keys.
  • Watches no longer have pendingRule, and the status pending-review is gone.

Dependencies: @secondlayer/sdk 15.5, @secondlayer/subgraphs 7.7.

Checked in prod on 2026-10-19 before removing: 0 watches held for review or with a held rule, 0 keys with an old scope, 0 active snt_ keys (none had ever been used), 0 held watch changes.

0.13.0

The web is read-only; every change is a command.

  • The web app's editors, Settings page, add-contract sheet, ⌘K command menu and docs search are gone. Where a control used to be, the page shows the command that does it (sentinel rename, merge, move, remove, import, test-alert, a watch's limit and mute, monitoring on or off).
  • A project's page is the one place to see it: a summary row, each watch with its last 30 days and last alert, and every change with where it came from. A project with only monitored contracts, or whose watches are still loading or failed to load, still shows its panels.
  • Old web addresses (/projects/<id>/monitor, /projects/<id>/settings, /plans/<id>/monitor, /plans/<id>/settings) are gone. Links Sentinel sends (mute catch-up, deploy notices, activation email) open the project page.
  • Project and plan summaries add stats30d, lastAlertAt, checkRuns30d and eventsToday. Each event is counted once, and only on watches that are on. A deploy watch reports its own alerts. Check-run counts match what a watch's cap counts. A schedule run counts on every contract it reads.
  • Project and contract names refuse backslashes and control characters (400). A printed command never carries a backslash inside its quotes.
  • Examples and the sample alert use a fictional contract.

0.12.1

sBTC checks: safer on missing data, and complete history.

  • Each signer rotation's key set is read from its own rotation transaction and kept for good, so a sweep still in flight to the previous set never looks like an unknown peg script. The signer registry is only a cross-check.
  • A check alerts only after fresh, verified data disagrees 60 times in a row; a missing or failed read never counts. A check that can't be read for a day pages the operator once and is retried daily; /health shows how many checks wait and for how long.
  • The peg's spend checks ("spent outside a sweep", "spent by a tx that isn't a sweep") run even while the Stacks feed lags; only the script cross-check and the 90-minute acknowledgment check wait for it.
  • The first backfill no longer skips history around past reorgs; prod was re-read once and now matches secondlayer's Index totals (a mismatch shows on /health, never as an alert).
  • Mint lookups page through every result.

0.12.0

One grammar for the CLI, MCP and the SDK; sBTC and PoX-5 watches; monitoring that tells you when it is down.

sBTC and PoX-5 (system watches; no contract of your own needed).

  • sentinel when sbtc <check>: stall (no sweep for 7+ Bitcoin blocks while requests wait, or a request open past 30), payout and mint (each sweep's payouts and each mint checked against the SPV-proven Bitcoin tx), rotation (deposits still swept by the previous key set), protocol-update, reserve (peg vs supply by sweep; a deficit only on 3 sweeps in a row over 0.01 BTC), peg (the peg output spent outside a sweep, or a sweep not acknowledged on Stacks in about 90 minutes). Thresholds tune within bounds: --gap <blocks> (3–50, default 7) and --open <blocks> (24–144, default 30) on stall, --after <hours>h (1–168, default 6) on rotation. --project <id> routes alerts to a project's destinations.
  • sentinel when pox5 <check>: bond-setup (urgent when a parameter is outside earlier bonds' range), admin (admin changes and pause-rewards, always urgent), early-exit (a Bitcoin bond output spent before unlock with no announce).
  • Missing, stale or failed data never alerts: a check waits and says so on /health. Witness-derived fields are read from the SPV-proven tx's bytes, and alerts say so.
  • sentinel show sbtc reserve (also pox5 custody, pox5 stbtc); SDK systemSeries(target, series); GET /v1/system/:target/:series.

Reliability.

  • sentinel status [--json], MCP status, SDK status(), GET /v1/status (public, no token needed; your own part when signed in): subscriptions, outbox, lanes and block-to-alert latency p50/p95 over 24 h and 7 d. sentinel status watch on|off (MCP status_watch, SDK setStatusLine) adds a "monitoring is up" line to your digests.
  • New webhook events: monitoring.degraded and monitoring.recovered (once per outage), and alert.updated (a check's verdict on an urgent alert, a reorg note). They grow the WebhookEvent union, so exhaustive switches need cases.
  • Alerts gain late: true (an event recovered after an outage or a paused delivery, with its original block); a check's status gains no; a digest gains monitoring.
  • An urgent alert is never held for its check: it goes at once, and the verdict follows as alert.updated.
  • A new Money out watch without a threshold uses a window rule once there are 20 samples, and every outflow before that. Existing watches keep their rules.
  • An alert whose tx a reorg took off chain gets one follow-up, after secondlayer confirms the tx isn't on the new chain.

Grammar and verbs.

One set of verbs for the CLI, and next for MCP and the SDK: a capability's verb is the CLI's words, and its MCP tool and client method are named from it (destinations list → destinations_list, destinationsList).

  • CLI renames. The old names still run until the next minor, and print renamed: sentinel <new> on stderr: balance → whoami (it now prints the balance and prices too), mark-read → read, plans → projects, plan and new → watchable, name and projects rename → rename, projects show and projects overview → project, projects add and projects move → move, projects merge and projects suggestion → merge, projects remove and projects remove-contract → remove, projects destinations → destinations, follow, monitor on, watch on and projects monitoring → on, unfollow and watch off → off, unmute and mute-alert → mute, watch-note → note, alert-preview → test-alert.

  • The new verbs take a target as typed: a contract's id or its name on your account, a unique part of one, a plan id, or a project's name or id (sentinel on example-vault --yes). A name that matches two contracts is refused with both ids.

  • read, rename, mute and note act at once (--yes is accepted and changes nothing). mute <target> <subject> off unmutes; mute <alert id> mutes the watch behind an alert. test-alert shows the sample alert and sends it with --yes. off takes a schedule id too. remove takes a contract you added, one you never claimed (it leaves its project) or a project, always with --confirm <name>.

  • on <contract> adds a contract that isn't on your account yet (monitoring stays off); on <target> <subject> turns one watch on.

  • --deep (on check, every, at) and --mutes (on projects changes) were refused as unknown flags; they work now.

  • SDK: latest, update, autoPlan, link and redeliver. The old names latestCommit, updatePlan, setAutoPlan, setLink and redeliverAlert are the same functions, @deprecated, until the next minor.

  • Watches have a public id, w_…, the same on every contract (publicId on every watch the API returns). list, show, logs [-f], on, off, mute and note take it; show and logs take a schedule (sch_…) or check run (chk_…) id too. More CLI renames, old names still run: watches, projects watches and schedules → list; schedules show and checks show → show; schedules off → off; activity, projects activity, projects changes and checks → logs.

  • sentinel group <target> [@admin|@value|@control] [+fn …] [-fn …]: each group's members and where each came from (facts, jev, you), and a correction for your account only. Every group then resolves with it: the menu, a watch's members, its subscriptions and its alerts.

  • Turning monitoring off no longer deletes anything (off, unfollow, DELETE /plan/:id/monitor, a project's monitoring off): watches, destinations, alerts and history stay, and on resumes them. Taking a contract off the account is remove.

  • Alert Inspect: lines name the new verbs: sentinel logs <contract> --tx <id>, or sentinel list <contract>.

  • sentinel describe "<sentence>" (or just sentinel "<sentence>"), MCP describe {text}, SDK compileWatch(text): a sentence, or a line whose target and token are names, as one watch: its line, the 90-day backtest and its cost. It writes nothing. A sentence is one model call against your own contracts' menus; a name or a token that isn't on them is refused, with the menu.

  • sentinel when …, sentinel every …, sentinel at … take one line of the grammar: when call|transfer|print|state|deploy <target> … with repeatable --to, --caller, --not-caller and --arg, and --note, --check, --deep, --cap, --yes, --wait. A target is a contract's id, its name on your account, a plan id or a project. Without --yes: the line, the 90-day backtest (a schedule: its next fires and a 7-day sample) and the cost. With --yes it is added; --wait waits until it is live (exit 1 if it can't be). when --spec '<json>' still takes a JSON spec. MCP when, every and at take the same line ({ line, confirm?, wait? }). sentinel "<sentence>" --yes adds the line a sentence reads as.

  • The first watch on a contract you don't follow adds it to your account (a project named after it) and turns its monitoring on; the preview says so (adds). A destination that never had an alert from you gets one test alert when a watch to it goes live (testAlert on the add).

  • sentinel with no command, on an account that watches nothing yet, prints three lines that start; not signed in, how to sign in.

  • A local project's files are its sender's only (another account's preview, import or plan of its key is a 404); an account keeps at most 20 (10 MB), its oldest unused ones pruned, and import previews are limited to 30 an hour. A watch preview, or compiling a line, starts no plan for a key without Plans: write (a preview of a contract Sentinel hasn't read is a 403 that says how to read it).

  • sentinel import . (or any folder) reads the folder's Clarinet.toml and the .clar files under contracts/ (at most 40 files and 1 MB, as text; nothing runs) and imports them like a repo: each plan's key is local:<folder>@<hash>:<path>. SDK previewImport also takes { name, sources }.

  • sentinel backtest <w_id> [--run N]; backtestCheck takes a watch's public id without contractId.

  • More CLI renames, old names still run: rule, threshold and check → when; functions → watchable.

  • A transfer spec's target may be a wallet (a standard address): it compiles and previews; adding one isn't on yet (501).

  • SDK: the grammar's verbs as methods of createClient, named from the verb: when(line | spec, { yes, wait }), every, at, describe, list, show, logs, on, off, note, read, rename, remove, move, merge, group, whoami, import, backtest, destinationsList, destinationsAdd, destinationsRemove. Each previews (done: false) until yes: true and answers next. createApi is the client without them. The verbs whose name an existing method has (mute, projects, project, alerts, testAlert, destinations, watchable, ask, redeliver, retryHistory, update, autoPlan, link, planLinked, connectSlack) keep that method in this minor; their verb is verbsOf(client).<name>. New exports: parseLine, lineWords, GRAMMAR_USAGE, resolveTarget, resolveProject, TargetError, verbsOf, waitLive, WriteResult.

  • SDK: setWatchOn, setWatchDelivery, groups, setGroup, and the /v1/watches and /v1/logs reads publicWatches, publicWatch and logEntries; types PublicWatch, LogEntry, LogsOptions, GroupsView, GroupMember.

  • MCP: one tool per verb, 37 in all, named from the CLI's words (destinations list → destinations_list). The old tool names are gone, with no aliases (agents list the tools each session). Renames: list_contracts, list_projects → projects; get_plan, create_plan, get_watchable, list_functions → watchable; list_alerts → alerts; get_alert, get_schedule, get_check_run → show; list_watches, list_project_watches, list_schedules → list; describe_rule, get_function_activity → describe; get_watch_activity, get_project_watch_activity, list_project_changes, list_check_runs → logs; preview_alert, send_test_alert → test_alert; get_latest_commit, update_plan → update; get_project, get_project_overview → project; ask_sentinel → ask; get_balance → whoami; preview_repo_import, import_repo → import; set_auto_plan → auto_plan; link_dependency → link; plan_with_links → plan_linked; rename_contract, rename_project → rename; remove_contract, remove_project, remove_project_contract → remove; add_project_contract, move_project_contract → move; merge_project, answer_merge_suggestion → merge; set_destinations, set_project_destinations → destinations; start_monitoring, watch_contract, set_watch, set_project_monitoring → on (and off); stop_watching, remove_watch, delete_schedule → off; add_rule, set_rule, set_threshold, set_watch_check, add_watch → when; create_schedule → every and at; mute_watch, unmute_watch, mute_alert → mute; set_watch_note → note; backtest_check → backtest; redeliver_alert → redeliver; mark_alert_read, mark_all_alerts_read → read; list_destinations, add_destination, remove_destination → destinations_list, destinations_add, destinations_remove; list_slack_installs, disconnect_slack → connect_slack (status, remove). retry_history, when, every, at, group and the rest keep their names.

  • MCP tools take a target as the CLI does (a contract's id or name, a project, or an id) instead of planId / projectId, and answer one shape: a write { done, willDo, effect, next, … } (a preview until confirm: true; the cheap ones, mute, note, read, rename, retry_history and redeliver, act at once), a read its data and next. Each tool carries its annotations (readOnlyHint, destructiveHint, idempotentHint), the server says its package version and gives agents short instructions. tools/list is about 15,600 characters (it was 42,555).

  • A line passed to MCP when / every / at or the SDK's when / every / at can't confirm itself: one carrying --yes or --wait is refused (use confirm: true / { yes: true } and wait). An MCP preview's next says to call again with confirm: true.

  • CLI renames, old names still run: alert <id> → show <alr_id>; latest → update (its preview says where the branch is).

  • redeliver and retry-history act at once, like the other small changes (--yes is accepted and changes nothing); retry-history takes a contract by its id or name. describe … --yes only reads now; sentinel "<sentence>" --yes still adds the line.

  • sentinel import <path|url> --deployer SP… [--hold] arms each imported contract to go live when it deploys from there; then when, every and at on those contracts (by name) save armed, named where they deploy (SP….name). --hold (on import, when, every, at) keeps a watch off, or a schedule waiting, after the deploy until sentinel on <id>; on a deployed contract it is refused. ImportInput.hold, ScheduleInput.hold, TriggerSpec.hold; Claim.deploysAs.

  • A pre-launch contract now goes live once it is on chain, whatever its code (flagged, not blocked): code that differs from the imported commit, or a contract it needs that is missing, is named in one "now live" notice (email and Slack; webhooks get watch.changed with source: "deploy" per watch), with the later commit the deployed code matches. Monitoring no longer goes stale. A schedule's status gains held; SDK resumeSchedule(id) (POST /v1/schedules/:id/on), CLI and MCP on <sch_id>.

  • ProjectContract.origin gains watched: a contract the account monitors without a claim. POST /contracts/watch (MCP watch_contract) puts it in a project; stopping its monitoring takes it out.

  • me() no longer returns watching: a contract you only monitor is a watched member of me().projects. planOfContract reads claims and project members.

  • Agent skill: npx skills add https://runsentinel.app installs sentinel into a project (also served at /.well-known/skills/), and the package carries it as skills/sentinel/SKILL.md. The MCP server's instructions and the skill are written from one source. The docs and the skill show which verbs to allow-list for an agent (Claude Code and OpenCode snippets).

  • A read verb (whoami, alerts, ask, watchable, projects, project, describe, list, show, logs, destinations list) refuses --yes, --confirm, --to, --note, --check and --ask with exit 2 (sentinel logs only reads; drop --yes), so an allow rule on its prefix can't be widened by appending flags. sentinel "<sentence>" --yes still adds the line.

0.11.0

Schedules, digests, checks, more triggers and alerts you can act on from a phone.

Triggers.

  • New trigger kinds: PrintSpec, DeploySpec, and StateSpec / StateFilters. WatchSubject gains print, deploy and state; WatchRule gains each, stateChange, stateDrop and stateCross. IntentKind and an alert's kind gain print, deploy and state.
  • WatchPreview gains warnings?, history.kind? and history.note?, and history.from can be "now". A deploy watch's planId is null.
  • removeWatch(spec) (DELETE /v1/watches); CLI sentinel when --spec … --off; MCP remove_watch.
  • effectOf accepts a null planId and handles "paused".

Schedules and digests.

  • SDK: previewSchedule, createSchedule, schedules, schedule and deleteSchedule, with the types Schedule, ScheduleRun, SchedulePreview, ScheduleInput, Digest and DigestFlow.
  • CLI: sentinel every, sentinel at and sentinel schedules [show|off]. MCP: create_schedule, list_schedules, get_schedule and delete_schedule.
  • New webhook event digest.created. It grows the WebhookEvent union, so exhaustive switches need a case.

Checks (a developer-written question on any trigger or schedule, answered with cited reasons; $0.25 a run, $0.75 with --deep).

  • CLI:
    • sentinel check <plan> <subject> "<question>"|--clear [--deep] [--cap N] [--yes]
    • sentinel backtest <plan> <subject> [--run N] [--yes]
    • sentinel checks [show <id>]
    • every / at … --check "<q>" or --digest --ask "<q>"
    • when --spec takes check
  • MCP: set_watch_check, backtest_check, list_check_runs and get_check_run. create_schedule takes check, ask, deep and cap.
  • SDK:
    • methods: setWatchCheck, backtestCheck (returns the queued runs; read each verdict with checkRun), checkRuns and checkRun;
    • fields: TriggerSpec.check, Alert.check and kind check, Digest.checks and Digest.ask (status answered, not_ready, …);
    • addWatch sends confirm: true.
  • ScheduleInput.digest is optional, and SchedulePreview.sample can be null.

Alerts.

  • Every alert ends with Inspect:, the commands to dig in (inspect on alerts; inspectCommands, shellArg and inspectLine exported).
  • Email and Slack carry signed Mute 1 h and Mark read links. Webhook bodies carry none.
  • The Sentinel Slack app:
    • SDK: connectSlack, slackInstalls, disconnectSlack and SlackInstall;
    • CLI: sentinel connect slack [--status | --remove <team>];
    • MCP: connect_slack, list_slack_installs and disconnect_slack.
  • No model writes alert or digest text; a check's verdict appears only as quoted, cited fields.

0.10.0 (breaking)

Findings and the audit around them are gone; Sentinel is monitoring only.

Triggers (additive).

  • New exports: TriggerSpec (with CallSpec, TransferSpec, CallFilters, TransferFilters), parseTriggerSpec and specLine (the one renderer of a spec as a line, chain values quoted).
  • SDK methods: previewWatch(spec) and addWatch(spec) (/v1/watches); destinations(project), addDestination({ project, kind, target, label? }), removeDestination(id). watches(planId) asks for every kind (kinds=all).
  • WatchSubject gains { kind: "inflow", asset, nft? }, nft? on an asset and the groups admin / value; subjectKey gives them in:, nft:, nft-in: and group:<g>. IntentKind and an alert's kind gain money_in, admin, value. A listed watch carries spec and line.
  • CLI: sentinel when --spec '<json>' [--yes], sentinel destinations list|add|remove. MCP: add_watch, list_destinations, add_destination, remove_destination.

Sign-in.

  • sentinel login signs in through the browser (device code): it prints a link and a code, and you approve on secondlayer. With no terminal, sentinel login --wait finishes it. sentinel logout revokes the session. --email keeps the email-code sign-in.
  • The first command run with no login signs in, then runs once. The local MCP signs in from a tool call.
  • --token beats SENTINEL_API_KEY. sentinel keys list|create|revoke manages keys from a login session (never from a key).
  • Hosted MCP: claude mcp add --transport http sentinel https://api.runsentinel.app/mcp signs in with OAuth in the browser; connected apps are listed under API keys and can be revoked.
  • A first sentinel login creates the Sentinel account from the email secondlayer verified.
  • sentinel login exits 1 (it was 0) when Sentinel refuses the new or saved session, e.g. 403 Approve Sentinel on this secondlayer account: run sentinel login --force.
  • An account is always linked to its secondlayer account before anything goes live. Until then every write that turns a watch on gets 403 with how to allow it, and me() / billing() carry liveBlocked ({ reason: "consent" | "setting_up", message }, or null; type LiveBlocked). sentinel whoami and sentinel balance print it; MCP whoami and get_balance return it.

Removed.

  • CLI commands removed (they answer Unknown command): finding, runs, watch <id>, reverify, reproduce, replay, challenge, directed, tests, fuzz, ack, reopen, acks, notes, note. plan prints the plan's watches; plans has no evidence column; alert-preview --example takes money_out only.
  • MCP tools removed: get_finding, get_run_status, reverify, reproduce, replay, challenge, directed, run_tests, run_fuzz, acknowledge_finding, reopen_finding, get_ack_history, list_notes, add_note. get_plan returns no verdict or findings.
  • SDK methods removed: finding, runs, reverify, reproduce, challenge, replay, directed, tests, testsFromRepo, fuzzCheck, fuzz, acknowledge, reopen, ackHistory, notes, addNote. BillingView.prices is { monitoredEvent }.
  • Alerts: findingKind, watchSubject, possibleApproachInFinding and origin are gone; kind is never finding.
  • WatchRule has no signature or audit kind. A plan's access is claimed, or absent on the public teaser.