@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.
Maintainers
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 contractssentinel 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/mcpSigns 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.appTeaches 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 itWebhooks
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, thedestinationsMCP tool) carrieslastDelivery: { alertId, ok, detail, at } | null;sentinel destinationsadds aLASTcolumn and, for a destination whose newest delivery failed, aResend: sentinel redeliver <alertId>line. - Changed:
sentinel statusends withNext:, like every other command (it printednext:).
0.35.0
- Changed: every agent route lives under
/v1, the same path with a/v1prefix and/plan→/v1/plans:POST /planand/plan/preview,/plan/:id/{latest,update,settings,remove,alert-preview,monitor,testalert},/contracts/importand/contracts/import/preview,/integrations/slack(and/install,/:team),/alerts/read-alland/alerts/:id/{read,redeliver,mute},GET /billing,POST /billing/checkoutandGET /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.
createClientreturns 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.createApiandApiClientare no longer exported. - Changed:
contracts(),alerts(options)anddestinations()answer{ contracts, next },{ alerts, next }and{ destinations, next }(an array before).testAlert(target, { yes }),redeliver(alertId, { dryRun }),update(target, { commit, dryRun })andconnectSlack({ 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→updatewithdryRun;renameContract→rename;removeContract→remove. - Removed (use the verb):
addDestination,setDefaultDestination,removeDestination→destinationsAdd,destinationsDefault,destinationsRemove;slackInstalls,disconnectSlack→connectSlackwithstatusorremove. - Removed:
claims,unreadAlertsandbillingfromGET /v1/me(andsentinel whoami --json): it answersuser,contracts,latestAlertsandliveBlocked. TheClaimtype is gone from the SDK. - Removed: the
Sentinel-API-Versionresponse header on/v1. The API changes without a version bump; breaking changes are dated in the API changelog. - Removed:
sentinel retry-history, theretry_historyMCP tool, the SDKretryHistorymethod andPOST /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 linkandsentinel plan-linked, theauto_plan,linkandplan_linkedMCP tools, the SDKautoPlan,linkandplanLinkedmethods, andPOST /plan/:id/wait,/linkand/plan-linked.waitingon a plan view dropsauto,linkedand each missing contract'slink. A waiting plan plans itself once the contracts it calls deploy; to plan sooner,importthe repo that has them. - Changed:
latest(GET /plan/:id/latest) dropsfree, and itsmodeiscarriedorfull.updateis free and plans the whole file. - Removed:
sentinel status watch, thestatus_watchMCP tool, the SDKstatusWatchmethod andPOST /v1/status/watch. Every digest now carries the monitoring-is-up line;account.heartbeatis gone fromGET /v1/status. - Removed:
GET /v1/plansand the SDKplans()method. UseGET /v1/contracts(contracts()) orGET /v1/me(contracts). - Removed: the
sentinel-mcpbin (local stdio MCP server). Use the hosted server:claude mcp add -t http sentinel https://api.runsentinel.app/mcp. - Removed:
sentinel ask, theaskMCP tool, the SDKaskmethod andPOST /ask(the docs answerer). Read the docs or/llms.txtinstead.
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;whoamion Free namessentinel upgrade builder. - Changed:
login --emaillinks 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(andat) with no target is a digest of all your watches; a digest'stargetis a contract or the account ({ kind: "account" }).- Import is
POST /contracts/import(preview:/contracts/import/preview); a repo contract's state isaddedwhere it wasin-project. removetakes a contract you only watch off your account too, with one message.
0.27.0
Changed:
- Destinations belong to the account;
--toworks on any watch; a watch without--togoes 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,--projecton destinations and onwhen deploy|sbtc|pox5, the per-contract webhook and its signing secret, MCPdestinations_list.
0.26.0
Changed:
- A free write happens at once;
--dry-runpreviews it;--yesis only for a cost or for stopping coverage.when,every,at,on,group,move,merge,import,destinations,destinations add,connect slackand the pre-launch upkeep verbs no longer need--yes. - A written
whenprints 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(MCPconfirm: true, SDK{ yes: true }). POST /v1/scheduleswith acheckoraskneedsconfirm: true; without it the route answers the preview and its cost and creates nothing.- SDK
addWatch(spec, { confirm })andcreateSchedule(input, { confirm })sendconfirmonly when given; an unconfirmed answer is returned as data (confirmed: false). - MCP: the free write tools lose
confirm.off,remove,destinations_remove,backtestandtest_alertkeep it.when,everyandatkeep it for a cost. - A line sent to MCP or the SDK that carries
--dry-runis refused, like--yesand--wait: usedry_run: true/{ dryRun: true }.
Added:
--dry-runon every write command, MCPdry_runon every write tool, SDKdryRunon every write verb. On a write with nothing to preview it says so and writes nothing.
0.25.0
Removed:
sentinel when --specand--off, the SDKremoveWatchandDELETE /v1/watches: turn a watch off by its id,sentinel off <w_id>.- The bare
sentinel "<sentence>": usesentinel describe "<sentence>". remove --confirm <name>and the MCPremovetool'sname:remove <target> --yes(MCP:confirm: true).
0.24.1
- A
when,everyoratpreview 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
liveonly once it alerts. While its contract's monitoring is being set up it ispending, withpending: { reason, failedAt }saying why (a failed setup, in words); while that monitoring is off it isoff. TypePublicWatch.status: WatchStatus | "pending" | "off",PublicWatch.pending. sentinel when … --yes --waitexits 1 at once, with the reason, when the watch can't go live, instead of waiting out the timeout. The MCPwhentool'swait: truegives 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 asOct 9 2.5 sBTC left the contract(an amount once, not twice), and the preview ends with its--yesline asNext:. 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
Functionfact (the function called, quoted in every sentence) and aCalled withfact (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 andsentinel alerts; Slack's notification text ends with it. TypeSentinelClientpreviewbacktest.matches[].detail?: string.
0.23.0
- An alert's
summaryis renamedtext(no alias). - An alert gains
facts[](each fact a label and a typed value:id,amount,timeortext, full ids and base units, withchainSourced) andsignals[](why it fired, with the numbers). checkFirstis set on every alert Sentinel raises; alerts stored before this release readnull.- 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 showprints 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 kindmintand actor signalnewMinter, and the triggersentinel 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 areAmount,ToandMinted 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 kindoutflowShare,bps; alert signaloutflowShare, factShare). 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-mcpbundles@modelcontextprotocol/sdk1.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
chainEventcarries the event's decodedvalue(a print, up to 1,000 characters) orargs(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);wouldAlertstill counts them all.sample(5) is gone. The CLI still prints 5 and says how many more--jsonlists.
0.18.0
- Print watches match the event name inside the printed value: the printed string, or a tuple's
topic,event,action,typeorobject. A watch onswapnow fires on a print whose tuple saysaction: "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 ofMints 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--yesadds the contract and charges it now (prorated). Any other write that would put a contract live at your count gets402 tier_limitnamingsentinel when. Turning a contract off lowers the next invoice. - New
402codecost_unconfirmedonPOST /v1/watcheswithoutconfirm: truewhen the add costs money (the contract is not added, nothing is charged). GET /billing:tier.liveCodes.limitis what the account has bought (Builder: its quantity). Newtier.contractCap(the most the tier can cover: 3 on Builder) andtier.usdMonth(the subscription's monthly price; null for Free and a grant).sentinel whoamishows 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
--deepchecks run and are billed per run, invoiced at $10 owed or at month end, with no monthly tier.GET /billingaddschecks.billing(per_runonce a card is on file, else null); SDKBillingViewmatches.sentinel whoamion Free then ends withchecks 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.alerton alerts (API, webhook, SDKAlerttype).linkskeepstx. Each alert ends with the command to inspect it. sentinel alert showdrops itsOpen: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 /billingaddstier(id,name,founding,status,periodEnd,cancelAt,until,source,liveCodes),checks(periodRuns,periodUnits,periodUsd,pricePerRunUsd,deepPricePerRunUsd),selfServeandmanageUrl. It dropsbalanceUsd,spentMonthUsd,capUsd,owedUsd,refill,prices,eventAllowanceandunavailable. SDK:BillingViewandTierIdchange to match.sentinel whoamiand MCPwhoamiprint the tier, its live contracts and this period's check usage.GET /billingalso carriescutover(when an account with live mainnet contracts and no tier is told its watches turn off; else null).- Removed routes:
POST /billing/checkoutandPOST /billing/settings. Added, web app only:POST /billing/tierandPOST /billing/portal. - A write that would put a contract live past the tier's limit answers
402(code: "tier_limit", withtier,used,limit,upgradeUrl);tier_requiredwhere the tier doesn't include it (when sbtcorwhen pox5on Free, a check on Free). The old403for a check on an account billing couldn't charge is402 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 theWebhookEventunion, so exhaustive switches need a case).monitoring.pausedgainsreason(unpaid,cancelled,grant_endorcutover) andmonitoring.resumedgainsreason(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 /alertsandGET /plan/:id. UseGET /v1/me(it also opens the account on secondlayer, as/medid),GET /v1/alertsandGET /v1/plans/:id.?kinds=allonGET /plan/:id/watches: the list always carries every kind of watch.- A watch choice named by its function alone (
{ fn, on }):POST /plan/:id/monitorneedswatchIdon each choice, or answers400.fnis only a label now. POST /keys { name, actions }answers400: say what the key may touch withareas. The oldreadandread,actionskey scopes are gone.- Local
snt_keys stop working (401): make ansk-snt_key.GET /keyslists nolegacykeys. - Watches no longer have
pendingRule, and the statuspending-reviewis 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,checkRuns30dandeventsToday. 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;
/healthshows 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),payoutandmint(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) onstall,--after <hours>h(1–168, default 6) onrotation.--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 andpause-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(alsopox5 custody,pox5 stbtc); SDKsystemSeries(target, series);GET /v1/system/:target/:series.
Reliability.
sentinel status [--json], MCPstatus, SDKstatus(),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(MCPstatus_watch, SDKsetStatusLine) adds a "monitoring is up" line to your digests.- New webhook events:
monitoring.degradedandmonitoring.recovered(once per outage), andalert.updated(a check's verdict on an urgent alert, a reorg note). They grow theWebhookEventunion, 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 gainsno; a digest gainsmonitoring. - 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,planandnew→watchable,nameandprojects rename→rename,projects showandprojects overview→project,projects addandprojects move→move,projects mergeandprojects suggestion→merge,projects removeandprojects remove-contract→remove,projects destinations→destinations,follow,monitor on,watch onandprojects monitoring→on,unfollowandwatch off→off,unmuteandmute-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,muteandnoteact at once (--yesis accepted and changes nothing).mute <target> <subject> offunmutes;mute <alert id>mutes the watch behind an alert.test-alertshows the sample alert and sends it with--yes.offtakes a schedule id too.removetakes 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(oncheck,every,at) and--mutes(onprojects changes) were refused as unknown flags; they work now.SDK:
latest,update,autoPlan,linkandredeliver. The old nameslatestCommit,updatePlan,setAutoPlan,setLinkandredeliverAlertare the same functions,@deprecated, until the next minor.Watches have a public id,
w_…, the same on every contract (publicIdon every watch the API returns).list,show,logs [-f],on,off,muteandnotetake it;showandlogstake a schedule (sch_…) or check run (chk_…) id too. More CLI renames, old names still run:watches,projects watchesandschedules→list;schedules showandchecks show→show;schedules off→off;activity,projects activity,projects changesandchecks→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, andonresumes them. Taking a contract off the account isremove.Alert
Inspect:lines name the new verbs:sentinel logs <contract> --tx <id>, orsentinel list <contract>.sentinel describe "<sentence>"(or justsentinel "<sentence>"), MCPdescribe {text}, SDKcompileWatch(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-callerand--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--yesit is added;--waitwaits until it is live (exit 1 if it can't be).when --spec '<json>'still takes a JSON spec. MCPwhen,everyandattake the same line ({ line, confirm?, wait? }).sentinel "<sentence>" --yesadds 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 (testAlerton the add).sentinelwith 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'sClarinet.tomland the.clarfiles undercontracts/(at most 40 files and 1 MB, as text; nothing runs) and imports them like a repo: each plan's key islocal:<folder>@<hash>:<path>. SDKpreviewImportalso takes{ name, sources }.sentinel backtest <w_id> [--run N];backtestChecktakes a watch's public id withoutcontractId.More CLI renames, old names still run:
rule,thresholdandcheck→when;functions→watchable.A transfer spec's
targetmay 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) untilyes: trueand answersnext.createApiis 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 isverbsOf(client).<name>. New exports:parseLine,lineWords,GRAMMAR_USAGE,resolveTarget,resolveProject,TargetError,verbsOf,waitLive,WriteResult.SDK:
setWatchOn,setWatchDelivery,groups,setGroup, and the/v1/watchesand/v1/logsreadspublicWatches,publicWatchandlogEntries; typesPublicWatch,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(andoff);stop_watching,remove_watch,delete_schedule→off;add_rule,set_rule,set_threshold,set_watch_check,add_watch→when;create_schedule→everyandat;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,groupand the rest keep their names.MCP tools take a
targetas the CLI does (a contract's id or name, a project, or an id) instead ofplanId/projectId, and answer one shape: a write{ done, willDo, effect, next, … }(a preview untilconfirm: true; the cheap ones,mute,note,read,rename,retry_historyandredeliver, act at once), a read its data andnext. Each tool carries its annotations (readOnlyHint,destructiveHint,idempotentHint), the server says its package version and gives agents short instructions.tools/listis about 15,600 characters (it was 42,555).A line passed to MCP
when/every/ator the SDK'swhen/every/atcan't confirm itself: one carrying--yesor--waitis refused (useconfirm: true/{ yes: true }andwait). An MCP preview'snextsays to call again withconfirm: true.CLI renames, old names still run:
alert <id>→show <alr_id>;latest→update(its preview says where the branch is).redeliverandretry-historyact at once, like the other small changes (--yesis accepted and changes nothing);retry-historytakes a contract by its id or name.describe … --yesonly reads now;sentinel "<sentence>" --yesstill adds the line.sentinel import <path|url> --deployer SP… [--hold]arms each imported contract to go live when it deploys from there; thenwhen,everyandaton those contracts (by name) save armed, named where they deploy (SP….name).--hold(onimport,when,every,at) keeps a watch off, or a schedule waiting, after the deploy untilsentinel 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.changedwithsource: "deploy"per watch), with the later commit the deployed code matches. Monitoring no longer goesstale. A schedule'sstatusgainsheld; SDKresumeSchedule(id)(POST /v1/schedules/:id/on), CLI and MCPon <sch_id>.ProjectContract.origingainswatched: a contract the account monitors without a claim.POST /contracts/watch(MCPwatch_contract) puts it in a project; stopping its monitoring takes it out.me()no longer returnswatching: a contract you only monitor is awatchedmember ofme().projects.planOfContractreads claims and project members.Agent skill:
npx skills add https://runsentinel.appinstallssentinelinto a project (also served at/.well-known/skills/), and the package carries it asskills/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,--checkand--askwith exit 2 (sentinel logs only reads; drop --yes), so an allow rule on its prefix can't be widened by appending flags.sentinel "<sentence>" --yesstill 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, andStateSpec/StateFilters.WatchSubjectgainsprint,deployandstate;WatchRulegainseach,stateChange,stateDropandstateCross.IntentKindand an alert'skindgainprint,deployandstate. WatchPreviewgainswarnings?,history.kind?andhistory.note?, andhistory.fromcan be"now". A deploy watch'splanIdisnull.removeWatch(spec)(DELETE /v1/watches); CLIsentinel when --spec … --off; MCPremove_watch.effectOfaccepts a nullplanIdand handles"paused".
Schedules and digests.
- SDK:
previewSchedule,createSchedule,schedules,scheduleanddeleteSchedule, with the typesSchedule,ScheduleRun,SchedulePreview,ScheduleInput,DigestandDigestFlow. - CLI:
sentinel every,sentinel atandsentinel schedules [show|off]. MCP:create_schedule,list_schedules,get_scheduleanddelete_schedule. - New webhook event
digest.created. It grows theWebhookEventunion, 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 --spectakescheck
- MCP:
set_watch_check,backtest_check,list_check_runsandget_check_run.create_scheduletakescheck,ask,deepandcap. - SDK:
- methods:
setWatchCheck,backtestCheck(returns the queued runs; read each verdict withcheckRun),checkRunsandcheckRun; - fields:
TriggerSpec.check,Alert.checkand kindcheck,Digest.checksandDigest.ask(statusanswered,not_ready, …); addWatchsendsconfirm: true.
- methods:
ScheduleInput.digestis optional, andSchedulePreview.samplecan benull.
Alerts.
- Every alert ends with
Inspect:, the commands to dig in (inspecton alerts;inspectCommands,shellArgandinspectLineexported). - Email and Slack carry signed Mute 1 h and Mark read links. Webhook bodies carry none.
- The Sentinel Slack app:
- SDK:
connectSlack,slackInstalls,disconnectSlackandSlackInstall; - CLI:
sentinel connect slack [--status | --remove <team>]; - MCP:
connect_slack,list_slack_installsanddisconnect_slack.
- SDK:
- 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(withCallSpec,TransferSpec,CallFilters,TransferFilters),parseTriggerSpecandspecLine(the one renderer of a spec as a line, chain values quoted). - SDK methods:
previewWatch(spec)andaddWatch(spec)(/v1/watches);destinations(project),addDestination({ project, kind, target, label? }),removeDestination(id).watches(planId)asks for every kind (kinds=all). WatchSubjectgains{ kind: "inflow", asset, nft? },nft?on an asset and the groupsadmin/value;subjectKeygives themin:,nft:,nft-in:andgroup:<g>.IntentKindand an alert'skindgainmoney_in,admin,value. A listed watch carriesspecandline.- CLI:
sentinel when --spec '<json>' [--yes],sentinel destinations list|add|remove. MCP:add_watch,list_destinations,add_destination,remove_destination.
Sign-in.
sentinel loginsigns in through the browser (device code): it prints a link and a code, and you approve on secondlayer. With no terminal,sentinel login --waitfinishes it.sentinel logoutrevokes the session.--emailkeeps 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.
--tokenbeatsSENTINEL_API_KEY.sentinel keys list|create|revokemanages keys from a login session (never from a key).- Hosted MCP:
claude mcp add --transport http sentinel https://api.runsentinel.app/mcpsigns in with OAuth in the browser; connected apps are listed under API keys and can be revoked. - A first
sentinel logincreates the Sentinel account from the email secondlayer verified. sentinel loginexits1(it was0) 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
403with how to allow it, andme()/billing()carryliveBlocked({ reason: "consent" | "setting_up", message }, or null; typeLiveBlocked).sentinel whoamiandsentinel balanceprint it; MCPwhoamiandget_balancereturn 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.planprints the plan's watches;planshas no evidence column;alert-preview --exampletakesmoney_outonly. - 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_planreturns no verdict or findings. - SDK methods removed:
finding,runs,reverify,reproduce,challenge,replay,directed,tests,testsFromRepo,fuzzCheck,fuzz,acknowledge,reopen,ackHistory,notes,addNote.BillingView.pricesis{ monitoredEvent }. - Alerts:
findingKind,watchSubject,possibleApproachInFindingandoriginare gone;kindis neverfinding. WatchRulehas nosignatureorauditkind. A plan'saccessisclaimed, or absent on the public teaser.
