@secondlayer/sentinel 0.5.0 → 0.7.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
@@ -92,6 +92,7 @@ function runPhase(run) {
92
92
  const last = run.events.at(-1);
93
93
  return last ? [last.name ?? last.kind, last.state].filter(Boolean).join(" ") : "starting";
94
94
  }
95
+ var WATCH_NOTE_MAX = 500;
95
96
  var rowsOf = (intents) => intents.flatMap((i) => i.rows);
96
97
  var subjectKey = (s) => s.kind === "fn" ? `fn:${s.fn}` : s.kind === "group" ? `group:${s.group}` : `asset:${s.asset}`;
97
98
  var CONTROL_NAME = "Control changes";
@@ -198,10 +199,32 @@ function mutedUntilOf(w, now = Date.now()) {
198
199
  const at = w.delivery.mutedUntil;
199
200
  return at && Date.parse(at) > now ? at : null;
200
201
  }
202
+ function changeText(c) {
203
+ const name = c.subject.kind === "asset" ? subjectName(c.subject, c.units) : c.watch.title;
204
+ const rule = (r) => ruleSummary(r, c.units);
205
+ switch (c.action) {
206
+ case "on":
207
+ return `${c.watch.title} turned on`;
208
+ case "off":
209
+ return `${c.watch.title} turned off`;
210
+ case "muted":
211
+ case "cooldown":
212
+ return `${c.watch.title}: ${c.action === "muted" ? "mute" : "cooldown"} ${c.note ?? ""}`.trim();
213
+ case "note":
214
+ return `${c.watch.title}: note ${c.note ?? "set"}`;
215
+ case "applied":
216
+ if (c.prevRule && c.rule)
217
+ return `${name} rule changed: ${rule(c.prevRule)} → ${rule(c.rule)}`;
218
+ return c.rule ? `${name} rule set: ${rule(c.rule)}` : `${name} rule set`;
219
+ }
220
+ }
201
221
  function createClient(opts) {
202
222
  const baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
203
223
  const doFetch = opts.fetch ?? globalThis.fetch;
204
- const authHeader = () => ({ authorization: `Bearer ${opts.token}` });
224
+ const authHeader = () => ({
225
+ authorization: `Bearer ${opts.token}`,
226
+ ...opts.client ? { "x-sentinel-client": opts.client } : {}
227
+ });
205
228
  async function call(method, path, body) {
206
229
  const res = await doFetch(`${baseUrl}${path}`, {
207
230
  method,
@@ -245,6 +268,7 @@ function createClient(opts) {
245
268
  on
246
269
  }),
247
270
  mute: (planId, watchId, mutedUntil) => call("POST", `${appPlan(planId)}/watches/${encodeURIComponent(watchId)}/delivery`, { mutedUntil }),
271
+ setWatchNote: (planId, watchId, note) => call("POST", `${appPlan(planId)}/watches/${encodeURIComponent(watchId)}/delivery`, { note }),
248
272
  muteAlert: (alertId, hours) => call("POST", `/alerts/${encodeURIComponent(alertId)}/mute`, { hours }),
249
273
  parseRule: (planId, subject, text) => call("POST", `${appPlan(planId)}/rules/parse`, { subject, text }),
250
274
  monitorSettings: (planId, settings) => call("POST", `${appPlan(planId)}/monitor`, settings),
@@ -304,7 +328,8 @@ function createClient(opts) {
304
328
  setProjectDestinations: (projectId, dest) => call("POST", `${appProject(projectId)}/destinations`, dest),
305
329
  setProjectMonitoring: (projectId, on) => call("POST", `${appProject(projectId)}/monitoring`, { on }),
306
330
  projectWatches: (projectId) => call("GET", `${appProject(projectId)}/watches`),
307
- projectActivity: (projectId, intentId, opts = {}) => call("GET", `${appProject(projectId)}/watches/${encodeURIComponent(intentId)}/activity${query(opts)}`)
331
+ projectActivity: (projectId, intentId, opts = {}) => call("GET", `${appProject(projectId)}/watches/${encodeURIComponent(intentId)}/activity${query(opts)}`),
332
+ projectChanges: (projectId, opts = {}) => call("GET", `${appProject(projectId)}/changes${query(opts)}`)
308
333
  };
309
334
  }
310
335
  function query(params) {
@@ -873,6 +898,7 @@ projects (the contracts of one protocol; a write only happens with --yes):
873
898
  projects remove <project> --confirm "<name>" [--yes] every contract you added in it, and the project
874
899
  projects watches <project> [--json] the project's watches as one list
875
900
  projects activity <project> <watch> [--contract <id>] [--window 7|90] [--json]
901
+ projects changes <project> [--watch <watch>] [--mutes] [--limit N] [--cursor C] [--json] who turned watches on/off or changed rules
876
902
  projects destinations <project> [--email on|off] [--webhook URL] [--slack URL] [--yes] a URL left out is cleared
877
903
  projects monitoring <project> on|off [--yes]
878
904
 
@@ -884,6 +910,7 @@ monitoring (a write only happens with --yes; without it you see what would chang
884
910
  watches <plan> [--json] subjects, rules, status, mutes
885
911
  rule <plan> <subject> "<sentence>" [--yes] read the sentence; --yes adds or edits the rules it found
886
912
  watch on|off <plan> <subject> [--yes] on and off apply now
913
+ watch-note <plan> <subject> "<text>"|--clear [--yes] the note sent with this watch's alerts (500 characters)
887
914
  mute <plan> <subject> [1h|24h|7d] [--yes] default 24h
888
915
  unmute <plan> <subject> [--yes]
889
916
  mute-alert <alert> [1h|24h|7d] [--yes] mute the watch behind an alert
@@ -917,7 +944,10 @@ var VALUE_FLAGS = new Set([
917
944
  "--commit",
918
945
  "--into",
919
946
  "--window",
920
- "--example"
947
+ "--example",
948
+ "--watch",
949
+ "--limit",
950
+ "--cursor"
921
951
  ]);
922
952
  var cell2 = (v, empty = "—") => v === null || v === undefined || v === "" ? empty : String(v);
923
953
  var table = (rows) => {
@@ -1216,6 +1246,7 @@ No run command: no PoC is stored for this finding.`);
1216
1246
  return await monitoringMore(cmd, args.slice(1), flags, values, io, usage);
1217
1247
  case "watches":
1218
1248
  case "rule":
1249
+ case "watch-note":
1219
1250
  case "mute":
1220
1251
  case "unmute":
1221
1252
  case "mute-alert":
@@ -1409,6 +1440,9 @@ async function monitoring(cmd, pos, on, flags, values, io, usage) {
1409
1440
  io.out([i.title, i.tag, i.on ? "on" : "off", intentLine(i), muted ? `muted until ${muted}` : ""].filter(Boolean).join(" "));
1410
1441
  for (const w of i.rows)
1411
1442
  io.out(` ${w.status.padEnd(14)}${ruleSummary(w.rule, w.units)}`);
1443
+ const note = i.rows.map((w) => w.delivery.note).find(Boolean);
1444
+ if (note)
1445
+ io.out(` note: ${note}`);
1412
1446
  }
1413
1447
  return 0;
1414
1448
  }
@@ -1482,6 +1516,21 @@ async function monitoring(cmd, pos, on, flags, values, io, usage) {
1482
1516
  io.out(`${willDo} ${effect}`);
1483
1517
  return 0;
1484
1518
  }
1519
+ if (cmd === "watch-note") {
1520
+ const clear = flags.includes("--clear");
1521
+ if (clear === (third !== undefined))
1522
+ return usage("watch-note takes a note in quotes, or --clear");
1523
+ const note = clear ? null : (third ?? "").trim();
1524
+ if (note !== null && note.length > WATCH_NOTE_MAX)
1525
+ return usage(`Keep the note under ${WATCH_NOTE_MAX} characters`);
1526
+ const willDo = note ? `Send this note with ${g.name}'s alerts: ${note}` : `Clear the note on ${g.name}.`;
1527
+ if (previewOnly(io, flags, willDo, "Takes effect now."))
1528
+ return 0;
1529
+ for (const w of g.watches)
1530
+ await io.client.setWatchNote(planId, w.id, note);
1531
+ io.out(note ? `Note set on ${g.name}.` : `Note cleared on ${g.name}.`);
1532
+ return 0;
1533
+ }
1485
1534
  const mute = cmd === "mute";
1486
1535
  const dur = third ?? "24h";
1487
1536
  if (mute && !(dur in DURATIONS))
@@ -1844,6 +1893,7 @@ async function projects(pos, flags, values, io, usage) {
1844
1893
  "remove",
1845
1894
  "watches",
1846
1895
  "activity",
1896
+ "changes",
1847
1897
  "destinations",
1848
1898
  "monitoring"
1849
1899
  ];
@@ -1998,6 +2048,30 @@ async function projects(pos, flags, values, io, usage) {
1998
2048
  printActivity(io, intent.title, a);
1999
2049
  return 0;
2000
2050
  }
2051
+ case "changes": {
2052
+ const limit = values["--limit"] === undefined ? undefined : Number(values["--limit"]);
2053
+ if (limit !== undefined && !(Number.isInteger(limit) && limit > 0))
2054
+ return usage("--limit takes a whole number above 0");
2055
+ const watchName = values["--watch"];
2056
+ const watch = watchName ? pickIntent((await io.client.projectWatches(id)).intents, watchName).id : undefined;
2057
+ const r = await io.client.projectChanges(id, {
2058
+ limit,
2059
+ cursor: values["--cursor"],
2060
+ watch,
2061
+ ...flags.includes("--mutes") ? { filter: "mutes" } : {}
2062
+ });
2063
+ if (json) {
2064
+ io.out(JSON.stringify(r, null, 2));
2065
+ return 0;
2066
+ }
2067
+ if (!r.events.length)
2068
+ io.out("No changes yet.");
2069
+ for (const c of r.events)
2070
+ io.out([c.at, changeText(c), c.source ?? ""].join(" ").trimEnd());
2071
+ if (r.next)
2072
+ io.out(`More: --cursor ${r.next}`);
2073
+ return 0;
2074
+ }
2001
2075
  case "destinations": {
2002
2076
  const email = values["--email"];
2003
2077
  if (email !== undefined && onOff(email) === null)
@@ -2075,7 +2149,7 @@ async function main() {
2075
2149
  return 2;
2076
2150
  }
2077
2151
  const baseUrl = process.env.SENTINEL_API_URL;
2078
- const clientFor = (token) => createClient({ token, baseUrl });
2152
+ const clientFor = (token) => createClient({ token, baseUrl, client: "cli" });
2079
2153
  return run(argv, {
2080
2154
  client: clientFor(credential?.token ?? ""),
2081
2155
  out: console.log,
package/dist/index.js CHANGED
@@ -86,6 +86,7 @@ function runPhase(run) {
86
86
  const last = run.events.at(-1);
87
87
  return last ? [last.name ?? last.kind, last.state].filter(Boolean).join(" ") : "starting";
88
88
  }
89
+ var WATCH_NOTE_MAX = 500;
89
90
  var rowsOf = (intents) => intents.flatMap((i) => i.rows);
90
91
  var subjectKey = (s) => s.kind === "fn" ? `fn:${s.fn}` : s.kind === "group" ? `group:${s.group}` : `asset:${s.asset}`;
91
92
  var CONTROL_NAME = "Control changes";
@@ -213,10 +214,32 @@ function mutedUntilOf(w, now = Date.now()) {
213
214
  const at = w.delivery.mutedUntil;
214
215
  return at && Date.parse(at) > now ? at : null;
215
216
  }
217
+ function changeText(c) {
218
+ const name = c.subject.kind === "asset" ? subjectName(c.subject, c.units) : c.watch.title;
219
+ const rule = (r) => ruleSummary(r, c.units);
220
+ switch (c.action) {
221
+ case "on":
222
+ return `${c.watch.title} turned on`;
223
+ case "off":
224
+ return `${c.watch.title} turned off`;
225
+ case "muted":
226
+ case "cooldown":
227
+ return `${c.watch.title}: ${c.action === "muted" ? "mute" : "cooldown"} ${c.note ?? ""}`.trim();
228
+ case "note":
229
+ return `${c.watch.title}: note ${c.note ?? "set"}`;
230
+ case "applied":
231
+ if (c.prevRule && c.rule)
232
+ return `${name} rule changed: ${rule(c.prevRule)} → ${rule(c.rule)}`;
233
+ return c.rule ? `${name} rule set: ${rule(c.rule)}` : `${name} rule set`;
234
+ }
235
+ }
216
236
  function createClient(opts) {
217
237
  const baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
218
238
  const doFetch = opts.fetch ?? globalThis.fetch;
219
- const authHeader = () => ({ authorization: `Bearer ${opts.token}` });
239
+ const authHeader = () => ({
240
+ authorization: `Bearer ${opts.token}`,
241
+ ...opts.client ? { "x-sentinel-client": opts.client } : {}
242
+ });
220
243
  async function call(method, path, body) {
221
244
  const res = await doFetch(`${baseUrl}${path}`, {
222
245
  method,
@@ -260,6 +283,7 @@ function createClient(opts) {
260
283
  on
261
284
  }),
262
285
  mute: (planId, watchId, mutedUntil) => call("POST", `${appPlan(planId)}/watches/${encodeURIComponent(watchId)}/delivery`, { mutedUntil }),
286
+ setWatchNote: (planId, watchId, note) => call("POST", `${appPlan(planId)}/watches/${encodeURIComponent(watchId)}/delivery`, { note }),
263
287
  muteAlert: (alertId, hours) => call("POST", `/alerts/${encodeURIComponent(alertId)}/mute`, { hours }),
264
288
  parseRule: (planId, subject, text) => call("POST", `${appPlan(planId)}/rules/parse`, { subject, text }),
265
289
  monitorSettings: (planId, settings) => call("POST", `${appPlan(planId)}/monitor`, settings),
@@ -319,7 +343,8 @@ function createClient(opts) {
319
343
  setProjectDestinations: (projectId, dest) => call("POST", `${appProject(projectId)}/destinations`, dest),
320
344
  setProjectMonitoring: (projectId, on) => call("POST", `${appProject(projectId)}/monitoring`, { on }),
321
345
  projectWatches: (projectId) => call("GET", `${appProject(projectId)}/watches`),
322
- projectActivity: (projectId, intentId, opts = {}) => call("GET", `${appProject(projectId)}/watches/${encodeURIComponent(intentId)}/activity${query(opts)}`)
346
+ projectActivity: (projectId, intentId, opts = {}) => call("GET", `${appProject(projectId)}/watches/${encodeURIComponent(intentId)}/activity${query(opts)}`),
347
+ projectChanges: (projectId, opts = {}) => call("GET", `${appProject(projectId)}/changes${query(opts)}`)
323
348
  };
324
349
  }
325
350
  function query(params) {
@@ -364,7 +389,9 @@ export {
364
389
  NOT_LOGGED_IN,
365
390
  SentinelError,
366
391
  SubjectError,
392
+ WATCH_NOTE_MAX,
367
393
  assetClause,
394
+ changeText,
368
395
  clearSession,
369
396
  clearTokenFile,
370
397
  createClient,
package/dist/mcp.js CHANGED
@@ -12666,6 +12666,7 @@ function runPhase(run) {
12666
12666
  const last = run.events.at(-1);
12667
12667
  return last ? [last.name ?? last.kind, last.state].filter(Boolean).join(" ") : "starting";
12668
12668
  }
12669
+ var WATCH_NOTE_MAX = 500;
12669
12670
  var rowsOf = (intents) => intents.flatMap((i) => i.rows);
12670
12671
  var subjectKey = (s) => s.kind === "fn" ? `fn:${s.fn}` : s.kind === "group" ? `group:${s.group}` : `asset:${s.asset}`;
12671
12672
  var CONTROL_NAME = "Control changes";
@@ -12781,7 +12782,10 @@ function mutedUntilOf(w, now = Date.now()) {
12781
12782
  function createClient(opts) {
12782
12783
  const baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
12783
12784
  const doFetch = opts.fetch ?? globalThis.fetch;
12784
- const authHeader = () => ({ authorization: `Bearer ${opts.token}` });
12785
+ const authHeader = () => ({
12786
+ authorization: `Bearer ${opts.token}`,
12787
+ ...opts.client ? { "x-sentinel-client": opts.client } : {}
12788
+ });
12785
12789
  async function call(method, path, body) {
12786
12790
  const res = await doFetch(`${baseUrl}${path}`, {
12787
12791
  method,
@@ -12825,6 +12829,7 @@ function createClient(opts) {
12825
12829
  on
12826
12830
  }),
12827
12831
  mute: (planId, watchId, mutedUntil) => call("POST", `${appPlan(planId)}/watches/${encodeURIComponent(watchId)}/delivery`, { mutedUntil }),
12832
+ setWatchNote: (planId, watchId, note) => call("POST", `${appPlan(planId)}/watches/${encodeURIComponent(watchId)}/delivery`, { note }),
12828
12833
  muteAlert: (alertId, hours) => call("POST", `/alerts/${encodeURIComponent(alertId)}/mute`, { hours }),
12829
12834
  parseRule: (planId, subject, text) => call("POST", `${appPlan(planId)}/rules/parse`, { subject, text }),
12830
12835
  monitorSettings: (planId, settings) => call("POST", `${appPlan(planId)}/monitor`, settings),
@@ -12884,7 +12889,8 @@ function createClient(opts) {
12884
12889
  setProjectDestinations: (projectId, dest) => call("POST", `${appProject(projectId)}/destinations`, dest),
12885
12890
  setProjectMonitoring: (projectId, on) => call("POST", `${appProject(projectId)}/monitoring`, { on }),
12886
12891
  projectWatches: (projectId) => call("GET", `${appProject(projectId)}/watches`),
12887
- projectActivity: (projectId, intentId, opts = {}) => call("GET", `${appProject(projectId)}/watches/${encodeURIComponent(intentId)}/activity${query(opts)}`)
12892
+ projectActivity: (projectId, intentId, opts = {}) => call("GET", `${appProject(projectId)}/watches/${encodeURIComponent(intentId)}/activity${query(opts)}`),
12893
+ projectChanges: (projectId, opts = {}) => call("GET", `${appProject(projectId)}/changes${query(opts)}`)
12888
12894
  };
12889
12895
  }
12890
12896
  function query(params) {
@@ -20725,6 +20731,7 @@ function intentView(i) {
20725
20731
  ...i.alsoChecks ? { alsoChecks: i.alsoChecks } : {},
20726
20732
  status: subjectStatus(i.rows),
20727
20733
  mutedUntil: i.rows.map((w) => mutedUntilOf(w)).find(Boolean) ?? null,
20734
+ note: i.rows.map((w) => w.delivery.note).find(Boolean) ?? null,
20728
20735
  rules: i.rows.map((w) => ({
20729
20736
  watchId: w.id,
20730
20737
  kind: w.rule.kind,
@@ -20748,6 +20755,19 @@ async function muteSubject(client, a, hours) {
20748
20755
  await client.mute(ctx.planId, w.id, mutedUntil);
20749
20756
  return done(willDo, "Takes effect now.", { mutedUntil });
20750
20757
  }
20758
+ async function noteSubject(client, a) {
20759
+ const text = typeof a.note === "string" ? a.note.trim() : "";
20760
+ if (text.length > WATCH_NOTE_MAX)
20761
+ throw new SentinelError(400, `Keep the note under ${WATCH_NOTE_MAX} characters`);
20762
+ const ctx = await watchContext(client, a.planId);
20763
+ const g = resolveSubject(ctx.watches, a.subject);
20764
+ const willDo = text ? `Send this note with ${g.name}'s alerts: ${text}` : `Clear the note on ${g.name}.`;
20765
+ if (a.confirm !== true)
20766
+ return preview(willDo, "Takes effect now.");
20767
+ for (const w of g.watches)
20768
+ await client.setWatchNote(ctx.planId, w.id, text || null);
20769
+ return done(willDo, "Takes effect now.");
20770
+ }
20751
20771
  function buildTools(client) {
20752
20772
  const planId = string2().describe("Plan id, from list_contracts");
20753
20773
  const subject = string2().describe("A watched subject: a function name or a token symbol, from list_watches");
@@ -21080,6 +21100,16 @@ function buildTools(client) {
21080
21100
  input: { planId, subject, hours: MUTE_HOURS, confirm },
21081
21101
  run: async (a) => muteSubject(client, a, a.hours)
21082
21102
  },
21103
+ set_watch_note: {
21104
+ description: "Set the note sent with a subject's alerts: your team's own words (who holds the key, where to post first), plain text, 500 characters at most. An empty note clears it. Sentinel adds nothing to it. Takes effect at once. Without confirm it only describes.",
21105
+ input: {
21106
+ planId,
21107
+ subject,
21108
+ note: string2().max(WATCH_NOTE_MAX).describe("The note; empty clears it"),
21109
+ confirm
21110
+ },
21111
+ run: async (a) => noteSubject(client, a)
21112
+ },
21083
21113
  unmute_watch: {
21084
21114
  description: "Clear a subject's mute. Takes effect at once. Without confirm it only describes.",
21085
21115
  input: { planId, subject, confirm },
@@ -21577,6 +21607,22 @@ function buildTools(client) {
21577
21607
  window: a.window
21578
21608
  })
21579
21609
  },
21610
+ list_project_changes: {
21611
+ description: 'The account\'s changes to what Sentinel watches across a project, newest first: each watch turned on or off, rule edits (rule and prevRule, the one it replaced), mutes and cooldowns, with how each was made (source) and when. `watch` keeps one watch (an id from list_project_watches); filter "mutes" keeps mutes and cooldowns; pass `next` back as cursor for older ones.',
21612
+ input: {
21613
+ projectId,
21614
+ watch: string2().optional(),
21615
+ filter: literal("mutes").optional(),
21616
+ limit: number2().int().min(1).max(200).optional(),
21617
+ cursor: string2().optional()
21618
+ },
21619
+ run: async (a) => client.projectChanges(a.projectId, {
21620
+ watch: a.watch,
21621
+ filter: a.filter,
21622
+ limit: a.limit,
21623
+ cursor: a.cursor
21624
+ })
21625
+ },
21580
21626
  set_project_destinations: {
21581
21627
  description: "Where a project's alerts go, set on each contract you added in it: email on or off, a webhook URL, a Slack URL. A URL left out is cleared. Without confirm it only describes.",
21582
21628
  input: {
@@ -21622,7 +21668,7 @@ async function runTool(tool, args) {
21622
21668
  }
21623
21669
  }
21624
21670
  function buildServer(client) {
21625
- const server = new McpServer({ name: "sentinel", version: "0.5.0" });
21671
+ const server = new McpServer({ name: "sentinel", version: "0.7.0" });
21626
21672
  for (const [name, tool] of Object.entries(buildTools(client))) {
21627
21673
  server.registerTool(name, { description: tool.description, inputSchema: tool.input }, (args) => runTool(tool, args));
21628
21674
  }
@@ -21637,6 +21683,7 @@ if (!credential) {
21637
21683
  }
21638
21684
  var client = createClient({
21639
21685
  token: credential.token,
21640
- baseUrl: process.env.SENTINEL_API_URL
21686
+ baseUrl: process.env.SENTINEL_API_URL,
21687
+ client: "mcp"
21641
21688
  });
21642
21689
  await buildServer(client).connect(new StdioServerTransport);
@@ -157,7 +157,11 @@ export type WatchDelivery = {
157
157
  routes: string[];
158
158
  cooldownBlocks: number;
159
159
  mutedUntil: string | null;
160
+ /** The team's own words, sent with every alert this watch fires. Absent from a server before notes. */
161
+ note?: string | null;
160
162
  };
163
+ /** The longest watch note the server takes (monitoring/watch.ts `WATCH_NOTE_MAX`). */
164
+ export declare const WATCH_NOTE_MAX = 500;
161
165
  /** One of the caller's own watches on a plan's contract, as `GET /plan/:id/watches` returns it. */
162
166
  export type StoredWatch = {
163
167
  id: string;
@@ -251,7 +255,7 @@ export type WatchesView = {
251
255
  events: {
252
256
  id: string;
253
257
  watchId: string;
254
- action: "proposed" | "applied" | "rejected" | "muted" | "cooldown";
258
+ action: "proposed" | "applied" | "rejected" | "muted" | "cooldown" | "note";
255
259
  rule: WatchRule | null;
256
260
  note: string | null;
257
261
  createdAt: string;
@@ -542,6 +546,35 @@ export type ProjectWatchesView = {
542
546
  locked: number;
543
547
  [field: string]: unknown;
544
548
  };
549
+ /** One change to what Sentinel watches: turned on or off, a rule edit (`prevRule` is what it replaced), a mute, a
550
+ * cooldown or a note set or cleared (never its text). `source` is how it was made (`web`, `key "<name>"`, …), null when not known. */
551
+ export type WatchChange = {
552
+ id: string;
553
+ at: string;
554
+ action: "applied" | "on" | "off" | "muted" | "cooldown" | "note";
555
+ contractId: string;
556
+ watch: {
557
+ id: string;
558
+ title: string;
559
+ };
560
+ subject: WatchSubject;
561
+ units: Units | null;
562
+ rule: WatchRule | null;
563
+ prevRule: WatchRule | null;
564
+ note: string | null;
565
+ by: "you";
566
+ source: string | null;
567
+ };
568
+ export type ChangesOptions = {
569
+ limit?: number;
570
+ cursor?: string | null;
571
+ /** One intent watch's id (from projectWatches). */
572
+ watch?: string;
573
+ /** `mutes`: only mutes and cooldowns. */
574
+ filter?: "mutes";
575
+ };
576
+ /** One change as a line: `Money out turned on`, `sBTC rule changed: ≥ 5 → ≥ 2`. */
577
+ export declare function changeText(c: WatchChange): string;
545
578
  /** One intent watch's activity, as the web app draws it: points, the history page, counts. */
546
579
  export type WatchActivity = {
547
580
  window: 7 | 90;
@@ -633,6 +666,8 @@ export type ClientOptions = {
633
666
  baseUrl?: string;
634
667
  token: string;
635
668
  fetch?: typeof globalThis.fetch;
669
+ /** Which tool is calling (sent as `x-sentinel-client`), so a change's history can say "sentinel CLI" or "MCP". */
670
+ client?: "cli" | "mcp";
636
671
  };
637
672
  export declare function createClient(opts: ClientOptions): {
638
673
  me: () => Promise<Record<string, unknown>>;
@@ -685,6 +720,10 @@ export declare function createClient(opts: ClientOptions): {
685
720
  mute: (planId: string, watchId: string, mutedUntil: string | null) => Promise<{
686
721
  watch: StoredWatch;
687
722
  }>;
723
+ /** Set the team's note on a watch (shown with every alert it sends), or clear it with null. Plain text, 500 characters at most. */
724
+ setWatchNote: (planId: string, watchId: string, note: string | null) => Promise<{
725
+ watch: StoredWatch;
726
+ }>;
688
727
  /** Mute the watch behind an alert for 1, 24 or 168 hours; 0 clears. */
689
728
  muteAlert: (alertId: string, hours: 0 | 1 | 24 | 168) => Promise<{
690
729
  mutedUntil: string | null;
@@ -890,6 +929,11 @@ export declare function createClient(opts: ClientOptions): {
890
929
  contract?: string;
891
930
  fn?: string;
892
931
  }) => Promise<WatchActivity>;
932
+ /** The account's changes to what Sentinel watches across the project, newest first; `next` pages back. */
933
+ projectChanges: (projectId: string, opts?: ChangesOptions) => Promise<{
934
+ events: WatchChange[];
935
+ next: string | null;
936
+ }>;
893
937
  };
894
938
  export type SentinelClient = ReturnType<typeof createClient>;
895
939
  /** A mute end `hours` out, a minute inside the 7-day limit so clock skew can't tip it over. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@secondlayer/sentinel",
3
- "version": "0.5.0",
3
+ "version": "0.7.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",