@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 +78 -4
- package/dist/index.js +29 -2
- package/dist/mcp.js +51 -4
- package/dist/sentinel-client.d.ts +45 -1
- package/package.json +1 -1
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 = () => ({
|
|
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 = () => ({
|
|
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 = () => ({
|
|
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.
|
|
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.
|
|
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",
|