@secondlayer/sentinel 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -176,16 +176,15 @@ function ruleSummary(rule, units) {
176
176
  }
177
177
  }
178
178
  }
179
+ function assetClause(a) {
180
+ const symbol = subjectName({ kind: "asset", asset: a.asset }, a.units);
181
+ const amount = a.threshold.amount;
182
+ if (amount === null)
183
+ return `${symbol} every outflow`;
184
+ return `${symbol} over ${a.units ? toUnits(amount, a.units.decimals) : `${group(amount)} base units`}`;
185
+ }
179
186
  function intentLine(i) {
180
- const t = i.threshold;
181
- if (i.kind !== "money_out" || !t)
182
- return i.summary;
183
- const units = i.units ?? null;
184
- if (t.amount === null) {
185
- const sug = t.suggested ? ` (suggested ${amountText(t.suggested.amount, units)}, ${t.suggested.basis})` : "";
186
- return `no threshold yet: every outflow alerts${sug}`;
187
- }
188
- return `${amountText(t.amount, units)} or more in one tx · ${t.basis}`;
187
+ return i.kind === "money_out" && i.assets?.length ? i.assets.map(assetClause).join(" · ") : i.summary;
189
188
  }
190
189
  var isMutableRule = (rule) => rule.kind !== "signature" && rule.kind !== "audit";
191
190
  function mutedUntilOf(w, now = Date.now()) {
package/dist/index.js CHANGED
@@ -185,16 +185,15 @@ function kindTag(rule) {
185
185
  return rule.triggerClass === "governance.proposal_submitted" ? "governance" : "upgrade";
186
186
  }
187
187
  }
188
+ function assetClause(a) {
189
+ const symbol = subjectName({ kind: "asset", asset: a.asset }, a.units);
190
+ const amount = a.threshold.amount;
191
+ if (amount === null)
192
+ return `${symbol} every outflow`;
193
+ return `${symbol} over ${a.units ? toUnits(amount, a.units.decimals) : `${group(amount)} base units`}`;
194
+ }
188
195
  function intentLine(i) {
189
- const t = i.threshold;
190
- if (i.kind !== "money_out" || !t)
191
- return i.summary;
192
- const units = i.units ?? null;
193
- if (t.amount === null) {
194
- const sug = t.suggested ? ` (suggested ${amountText(t.suggested.amount, units)}, ${t.suggested.basis})` : "";
195
- return `no threshold yet: every outflow alerts${sug}`;
196
- }
197
- return `${amountText(t.amount, units)} or more in one tx · ${t.basis}`;
196
+ return i.kind === "money_out" && i.assets?.length ? i.assets.map(assetClause).join(" · ") : i.summary;
198
197
  }
199
198
  function subjectStatus(ws) {
200
199
  const st = ws.map((w) => w.status);
@@ -295,6 +294,7 @@ export {
295
294
  NOT_LOGGED_IN,
296
295
  SentinelError,
297
296
  SubjectError,
297
+ assetClause,
298
298
  clearSession,
299
299
  clearTokenFile,
300
300
  createClient,
package/dist/mcp.js CHANGED
@@ -12750,16 +12750,15 @@ function ruleSummary(rule, units) {
12750
12750
  }
12751
12751
  }
12752
12752
  }
12753
+ function assetClause(a) {
12754
+ const symbol = subjectName({ kind: "asset", asset: a.asset }, a.units);
12755
+ const amount = a.threshold.amount;
12756
+ if (amount === null)
12757
+ return `${symbol} every outflow`;
12758
+ return `${symbol} over ${a.units ? toUnits(amount, a.units.decimals) : `${group(amount)} base units`}`;
12759
+ }
12753
12760
  function intentLine(i) {
12754
- const t = i.threshold;
12755
- if (i.kind !== "money_out" || !t)
12756
- return i.summary;
12757
- const units = i.units ?? null;
12758
- if (t.amount === null) {
12759
- const sug = t.suggested ? ` (suggested ${amountText(t.suggested.amount, units)}, ${t.suggested.basis})` : "";
12760
- return `no threshold yet: every outflow alerts${sug}`;
12761
- }
12762
- return `${amountText(t.amount, units)} or more in one tx · ${t.basis}`;
12761
+ return i.kind === "money_out" && i.assets?.length ? i.assets.map(assetClause).join(" · ") : i.summary;
12763
12762
  }
12764
12763
  function subjectStatus(ws) {
12765
12764
  const st = ws.map((w) => w.status);
@@ -20646,8 +20645,17 @@ function intentView(i) {
20646
20645
  kind: i.kind,
20647
20646
  on: i.on,
20648
20647
  alertLevel: i.alertLevel,
20648
+ coverage: i.coverage,
20649
20649
  summary: intentLine(i),
20650
+ ...i.assets ? {
20651
+ assets: i.assets.map((a) => ({
20652
+ subject: subjectName({ kind: "asset", asset: a.asset }, a.units),
20653
+ limit: assetClause(a),
20654
+ watchIds: a.watchIds
20655
+ }))
20656
+ } : {},
20650
20657
  members: i.members,
20658
+ ...i.alsoChecks ? { alsoChecks: i.alsoChecks } : {},
20651
20659
  status: subjectStatus(i.rows),
20652
20660
  mutedUntil: i.rows.map((w) => mutedUntilOf(w)).find(Boolean) ?? null,
20653
20661
  rules: i.rows.map((w) => ({
@@ -20857,7 +20865,7 @@ function buildTools(client) {
20857
20865
  run: async (a) => a.confirm === true ? client.testsFromRepo(a.planId) : paidRun("Run the Clarinet suite from this pre-launch plan's repo in the airgapped sandbox.")
20858
20866
  },
20859
20867
  list_watches: {
20860
- description: "What a plan watches, as intent watches (Money out per token, Control changes, one per finding, the fns you added): on or off, the threshold or summary, the fns covered, each rule in token units, any mute, and the subject name the other watch tools take.",
20868
+ description: "What a plan watches, as intent watches (Money out once per contract with a limit per token, Control changes, one per finding, the fns you added): on or off, the threshold or summary, the fns covered, what it can see (coverage: direct-calls means a call made from another contract isn't visible yet), each rule in token units, any mute, and the subject name the other watch tools take.",
20861
20869
  input: { planId },
20862
20870
  run: async (a) => {
20863
20871
  const ctx = await watchContext(client, a.planId);
@@ -21041,7 +21049,7 @@ async function runTool(tool, args) {
21041
21049
  }
21042
21050
  }
21043
21051
  function buildServer(client) {
21044
- const server = new McpServer({ name: "sentinel", version: "0.2.0" });
21052
+ const server = new McpServer({ name: "sentinel", version: "0.3.0" });
21045
21053
  for (const [name, tool] of Object.entries(buildTools(client))) {
21046
21054
  server.registerTool(name, { description: tool.description, inputSchema: tool.input }, (args) => runTool(tool, args));
21047
21055
  }
@@ -170,8 +170,29 @@ export type StoredWatch = {
170
170
  units: Units | null;
171
171
  };
172
172
  export type IntentKind = "money_out" | "control" | "finding" | "owner_fn";
173
+ /** A Money out limit: null amount means every outflow alerts until one is set. */
174
+ export type Threshold = {
175
+ amount: string | null;
176
+ basis: string;
177
+ confirmed: boolean;
178
+ suggested: {
179
+ amount: string;
180
+ basis: string;
181
+ } | null;
182
+ };
183
+ /** One asset inside Money out: its own limit, the rows behind it and its last 7 UTC days (oldest first). */
184
+ export type AssetWatch = {
185
+ asset: string;
186
+ units: Units | null;
187
+ threshold: Threshold;
188
+ watchIds: string[];
189
+ stats7d: {
190
+ events: number[];
191
+ alerts: number[];
192
+ };
193
+ };
173
194
  /**
174
- * One intent watch: what a team sees (Money out per asset, Control changes, one per finding, a fn the team
195
+ * One intent watch: what a team sees (Money out holding every asset, Control changes, one per finding, a fn the team
175
196
  * added). `rows` are the stored watches behind it: what a rule edit, a switch or a mute acts on.
176
197
  */
177
198
  export type IntentWatch<R = StoredWatch & {
@@ -184,28 +205,26 @@ export type IntentWatch<R = StoredWatch & {
184
205
  summary: string;
185
206
  on: boolean;
186
207
  alertLevel: "urgent" | "normal";
208
+ /** What it can see: `direct-calls` (a call another contract makes isn't visible yet) or `all-calls`. */
209
+ coverage: "direct-calls" | "all-calls";
187
210
  watchIds: string[];
188
211
  rows: R[];
189
212
  members: {
190
213
  group: string;
191
214
  fns: string[];
192
215
  }[];
193
- /** Money out: the asset, its token units and its threshold (null amount: every outflow alerts). */
194
- asset?: string;
195
- units?: Units | null;
196
- threshold?: {
197
- amount: string | null;
198
- basis: string;
199
- confirmed: boolean;
200
- suggested: {
201
- amount: string;
202
- basis: string;
203
- } | null;
204
- };
216
+ /** Money out: every asset the contract sends, STX first then by symbol, each with its own limit. */
217
+ assets?: AssetWatch[];
205
218
  findingRef?: {
206
219
  title: string;
207
220
  index: number | null;
208
221
  };
222
+ /** Control changes: the state only its members write, read every `everyMinutes` between calls. */
223
+ alsoChecks?: {
224
+ vars: string[];
225
+ maps: string[];
226
+ everyMinutes: number;
227
+ };
209
228
  };
210
229
  /** The stored rows behind a list of intent watches, in order. */
211
230
  export declare const rowsOf: <R>(intents: {
@@ -305,8 +324,10 @@ export declare function ruleSummary(rule: WatchRule, units: Units | null): strin
305
324
  export type WatchKindTag = "finding" | "outflow" | "who-acts" | "governance" | "upgrade";
306
325
  /** The plain tag a rule is listed under. */
307
326
  export declare function kindTag(rule: WatchRule): WatchKindTag;
308
- /** An intent watch's one line: a Money out threshold in token units (or its fail-safe), else its summary. */
309
- export declare function intentLine(i: Pick<IntentWatch<unknown>, "kind" | "summary" | "threshold" | "units">): string;
327
+ /** One Money out clause: `STX over 500` or `MIA every outflow`, in token units where they are known. */
328
+ export declare function assetClause(a: Pick<AssetWatch, "asset" | "units" | "threshold">): string;
329
+ /** An intent watch's one line: a Money out clause per asset, else its summary. */
330
+ export declare function intentLine(i: Pick<IntentWatch<unknown>, "kind" | "summary" | "assets">): string;
310
331
  /** A subject's status from its rules: live if any is, else paused if all are, else suggested. */
311
332
  export declare function subjectStatus(ws: {
312
333
  status: WatchStatus;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@secondlayer/sentinel",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Sentinel from a terminal or an agent: the CLI, an MCP server and a client for your Stacks contract monitoring.",
5
5
  "license": "MIT",
6
6
  "repository": "https://runsentinel.app/docs",