@secondlayer/sentinel 0.5.0 → 0.6.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 +54 -4
- package/dist/index.js +24 -2
- package/dist/mcp.js +25 -4
- package/dist/sentinel-client.d.ts +36 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -198,10 +198,30 @@ function mutedUntilOf(w, now = Date.now()) {
|
|
|
198
198
|
const at = w.delivery.mutedUntil;
|
|
199
199
|
return at && Date.parse(at) > now ? at : null;
|
|
200
200
|
}
|
|
201
|
+
function changeText(c) {
|
|
202
|
+
const name = c.subject.kind === "asset" ? subjectName(c.subject, c.units) : c.watch.title;
|
|
203
|
+
const rule = (r) => ruleSummary(r, c.units);
|
|
204
|
+
switch (c.action) {
|
|
205
|
+
case "on":
|
|
206
|
+
return `${c.watch.title} turned on`;
|
|
207
|
+
case "off":
|
|
208
|
+
return `${c.watch.title} turned off`;
|
|
209
|
+
case "muted":
|
|
210
|
+
case "cooldown":
|
|
211
|
+
return `${c.watch.title}: ${c.action === "muted" ? "mute" : "cooldown"} ${c.note ?? ""}`.trim();
|
|
212
|
+
case "applied":
|
|
213
|
+
if (c.prevRule && c.rule)
|
|
214
|
+
return `${name} rule changed: ${rule(c.prevRule)} → ${rule(c.rule)}`;
|
|
215
|
+
return c.rule ? `${name} rule set: ${rule(c.rule)}` : `${name} rule set`;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
201
218
|
function createClient(opts) {
|
|
202
219
|
const baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
203
220
|
const doFetch = opts.fetch ?? globalThis.fetch;
|
|
204
|
-
const authHeader = () => ({
|
|
221
|
+
const authHeader = () => ({
|
|
222
|
+
authorization: `Bearer ${opts.token}`,
|
|
223
|
+
...opts.client ? { "x-sentinel-client": opts.client } : {}
|
|
224
|
+
});
|
|
205
225
|
async function call(method, path, body) {
|
|
206
226
|
const res = await doFetch(`${baseUrl}${path}`, {
|
|
207
227
|
method,
|
|
@@ -304,7 +324,8 @@ function createClient(opts) {
|
|
|
304
324
|
setProjectDestinations: (projectId, dest) => call("POST", `${appProject(projectId)}/destinations`, dest),
|
|
305
325
|
setProjectMonitoring: (projectId, on) => call("POST", `${appProject(projectId)}/monitoring`, { on }),
|
|
306
326
|
projectWatches: (projectId) => call("GET", `${appProject(projectId)}/watches`),
|
|
307
|
-
projectActivity: (projectId, intentId, opts = {}) => call("GET", `${appProject(projectId)}/watches/${encodeURIComponent(intentId)}/activity${query(opts)}`)
|
|
327
|
+
projectActivity: (projectId, intentId, opts = {}) => call("GET", `${appProject(projectId)}/watches/${encodeURIComponent(intentId)}/activity${query(opts)}`),
|
|
328
|
+
projectChanges: (projectId, opts = {}) => call("GET", `${appProject(projectId)}/changes${query(opts)}`)
|
|
308
329
|
};
|
|
309
330
|
}
|
|
310
331
|
function query(params) {
|
|
@@ -873,6 +894,7 @@ projects (the contracts of one protocol; a write only happens with --yes):
|
|
|
873
894
|
projects remove <project> --confirm "<name>" [--yes] every contract you added in it, and the project
|
|
874
895
|
projects watches <project> [--json] the project's watches as one list
|
|
875
896
|
projects activity <project> <watch> [--contract <id>] [--window 7|90] [--json]
|
|
897
|
+
projects changes <project> [--watch <watch>] [--mutes] [--limit N] [--cursor C] [--json] who turned watches on/off or changed rules
|
|
876
898
|
projects destinations <project> [--email on|off] [--webhook URL] [--slack URL] [--yes] a URL left out is cleared
|
|
877
899
|
projects monitoring <project> on|off [--yes]
|
|
878
900
|
|
|
@@ -917,7 +939,10 @@ var VALUE_FLAGS = new Set([
|
|
|
917
939
|
"--commit",
|
|
918
940
|
"--into",
|
|
919
941
|
"--window",
|
|
920
|
-
"--example"
|
|
942
|
+
"--example",
|
|
943
|
+
"--watch",
|
|
944
|
+
"--limit",
|
|
945
|
+
"--cursor"
|
|
921
946
|
]);
|
|
922
947
|
var cell2 = (v, empty = "—") => v === null || v === undefined || v === "" ? empty : String(v);
|
|
923
948
|
var table = (rows) => {
|
|
@@ -1844,6 +1869,7 @@ async function projects(pos, flags, values, io, usage) {
|
|
|
1844
1869
|
"remove",
|
|
1845
1870
|
"watches",
|
|
1846
1871
|
"activity",
|
|
1872
|
+
"changes",
|
|
1847
1873
|
"destinations",
|
|
1848
1874
|
"monitoring"
|
|
1849
1875
|
];
|
|
@@ -1998,6 +2024,30 @@ async function projects(pos, flags, values, io, usage) {
|
|
|
1998
2024
|
printActivity(io, intent.title, a);
|
|
1999
2025
|
return 0;
|
|
2000
2026
|
}
|
|
2027
|
+
case "changes": {
|
|
2028
|
+
const limit = values["--limit"] === undefined ? undefined : Number(values["--limit"]);
|
|
2029
|
+
if (limit !== undefined && !(Number.isInteger(limit) && limit > 0))
|
|
2030
|
+
return usage("--limit takes a whole number above 0");
|
|
2031
|
+
const watchName = values["--watch"];
|
|
2032
|
+
const watch = watchName ? pickIntent((await io.client.projectWatches(id)).intents, watchName).id : undefined;
|
|
2033
|
+
const r = await io.client.projectChanges(id, {
|
|
2034
|
+
limit,
|
|
2035
|
+
cursor: values["--cursor"],
|
|
2036
|
+
watch,
|
|
2037
|
+
...flags.includes("--mutes") ? { filter: "mutes" } : {}
|
|
2038
|
+
});
|
|
2039
|
+
if (json) {
|
|
2040
|
+
io.out(JSON.stringify(r, null, 2));
|
|
2041
|
+
return 0;
|
|
2042
|
+
}
|
|
2043
|
+
if (!r.events.length)
|
|
2044
|
+
io.out("No changes yet.");
|
|
2045
|
+
for (const c of r.events)
|
|
2046
|
+
io.out([c.at, changeText(c), c.source ?? ""].join(" ").trimEnd());
|
|
2047
|
+
if (r.next)
|
|
2048
|
+
io.out(`More: --cursor ${r.next}`);
|
|
2049
|
+
return 0;
|
|
2050
|
+
}
|
|
2001
2051
|
case "destinations": {
|
|
2002
2052
|
const email = values["--email"];
|
|
2003
2053
|
if (email !== undefined && onOff(email) === null)
|
|
@@ -2075,7 +2125,7 @@ async function main() {
|
|
|
2075
2125
|
return 2;
|
|
2076
2126
|
}
|
|
2077
2127
|
const baseUrl = process.env.SENTINEL_API_URL;
|
|
2078
|
-
const clientFor = (token) => createClient({ token, baseUrl });
|
|
2128
|
+
const clientFor = (token) => createClient({ token, baseUrl, client: "cli" });
|
|
2079
2129
|
return run(argv, {
|
|
2080
2130
|
client: clientFor(credential?.token ?? ""),
|
|
2081
2131
|
out: console.log,
|
package/dist/index.js
CHANGED
|
@@ -213,10 +213,30 @@ function mutedUntilOf(w, now = Date.now()) {
|
|
|
213
213
|
const at = w.delivery.mutedUntil;
|
|
214
214
|
return at && Date.parse(at) > now ? at : null;
|
|
215
215
|
}
|
|
216
|
+
function changeText(c) {
|
|
217
|
+
const name = c.subject.kind === "asset" ? subjectName(c.subject, c.units) : c.watch.title;
|
|
218
|
+
const rule = (r) => ruleSummary(r, c.units);
|
|
219
|
+
switch (c.action) {
|
|
220
|
+
case "on":
|
|
221
|
+
return `${c.watch.title} turned on`;
|
|
222
|
+
case "off":
|
|
223
|
+
return `${c.watch.title} turned off`;
|
|
224
|
+
case "muted":
|
|
225
|
+
case "cooldown":
|
|
226
|
+
return `${c.watch.title}: ${c.action === "muted" ? "mute" : "cooldown"} ${c.note ?? ""}`.trim();
|
|
227
|
+
case "applied":
|
|
228
|
+
if (c.prevRule && c.rule)
|
|
229
|
+
return `${name} rule changed: ${rule(c.prevRule)} → ${rule(c.rule)}`;
|
|
230
|
+
return c.rule ? `${name} rule set: ${rule(c.rule)}` : `${name} rule set`;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
216
233
|
function createClient(opts) {
|
|
217
234
|
const baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
218
235
|
const doFetch = opts.fetch ?? globalThis.fetch;
|
|
219
|
-
const authHeader = () => ({
|
|
236
|
+
const authHeader = () => ({
|
|
237
|
+
authorization: `Bearer ${opts.token}`,
|
|
238
|
+
...opts.client ? { "x-sentinel-client": opts.client } : {}
|
|
239
|
+
});
|
|
220
240
|
async function call(method, path, body) {
|
|
221
241
|
const res = await doFetch(`${baseUrl}${path}`, {
|
|
222
242
|
method,
|
|
@@ -319,7 +339,8 @@ function createClient(opts) {
|
|
|
319
339
|
setProjectDestinations: (projectId, dest) => call("POST", `${appProject(projectId)}/destinations`, dest),
|
|
320
340
|
setProjectMonitoring: (projectId, on) => call("POST", `${appProject(projectId)}/monitoring`, { on }),
|
|
321
341
|
projectWatches: (projectId) => call("GET", `${appProject(projectId)}/watches`),
|
|
322
|
-
projectActivity: (projectId, intentId, opts = {}) => call("GET", `${appProject(projectId)}/watches/${encodeURIComponent(intentId)}/activity${query(opts)}`)
|
|
342
|
+
projectActivity: (projectId, intentId, opts = {}) => call("GET", `${appProject(projectId)}/watches/${encodeURIComponent(intentId)}/activity${query(opts)}`),
|
|
343
|
+
projectChanges: (projectId, opts = {}) => call("GET", `${appProject(projectId)}/changes${query(opts)}`)
|
|
323
344
|
};
|
|
324
345
|
}
|
|
325
346
|
function query(params) {
|
|
@@ -365,6 +386,7 @@ export {
|
|
|
365
386
|
SentinelError,
|
|
366
387
|
SubjectError,
|
|
367
388
|
assetClause,
|
|
389
|
+
changeText,
|
|
368
390
|
clearSession,
|
|
369
391
|
clearTokenFile,
|
|
370
392
|
createClient,
|
package/dist/mcp.js
CHANGED
|
@@ -12781,7 +12781,10 @@ function mutedUntilOf(w, now = Date.now()) {
|
|
|
12781
12781
|
function createClient(opts) {
|
|
12782
12782
|
const baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
12783
12783
|
const doFetch = opts.fetch ?? globalThis.fetch;
|
|
12784
|
-
const authHeader = () => ({
|
|
12784
|
+
const authHeader = () => ({
|
|
12785
|
+
authorization: `Bearer ${opts.token}`,
|
|
12786
|
+
...opts.client ? { "x-sentinel-client": opts.client } : {}
|
|
12787
|
+
});
|
|
12785
12788
|
async function call(method, path, body) {
|
|
12786
12789
|
const res = await doFetch(`${baseUrl}${path}`, {
|
|
12787
12790
|
method,
|
|
@@ -12884,7 +12887,8 @@ function createClient(opts) {
|
|
|
12884
12887
|
setProjectDestinations: (projectId, dest) => call("POST", `${appProject(projectId)}/destinations`, dest),
|
|
12885
12888
|
setProjectMonitoring: (projectId, on) => call("POST", `${appProject(projectId)}/monitoring`, { on }),
|
|
12886
12889
|
projectWatches: (projectId) => call("GET", `${appProject(projectId)}/watches`),
|
|
12887
|
-
projectActivity: (projectId, intentId, opts = {}) => call("GET", `${appProject(projectId)}/watches/${encodeURIComponent(intentId)}/activity${query(opts)}`)
|
|
12890
|
+
projectActivity: (projectId, intentId, opts = {}) => call("GET", `${appProject(projectId)}/watches/${encodeURIComponent(intentId)}/activity${query(opts)}`),
|
|
12891
|
+
projectChanges: (projectId, opts = {}) => call("GET", `${appProject(projectId)}/changes${query(opts)}`)
|
|
12888
12892
|
};
|
|
12889
12893
|
}
|
|
12890
12894
|
function query(params) {
|
|
@@ -21577,6 +21581,22 @@ function buildTools(client) {
|
|
|
21577
21581
|
window: a.window
|
|
21578
21582
|
})
|
|
21579
21583
|
},
|
|
21584
|
+
list_project_changes: {
|
|
21585
|
+
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.',
|
|
21586
|
+
input: {
|
|
21587
|
+
projectId,
|
|
21588
|
+
watch: string2().optional(),
|
|
21589
|
+
filter: literal("mutes").optional(),
|
|
21590
|
+
limit: number2().int().min(1).max(200).optional(),
|
|
21591
|
+
cursor: string2().optional()
|
|
21592
|
+
},
|
|
21593
|
+
run: async (a) => client.projectChanges(a.projectId, {
|
|
21594
|
+
watch: a.watch,
|
|
21595
|
+
filter: a.filter,
|
|
21596
|
+
limit: a.limit,
|
|
21597
|
+
cursor: a.cursor
|
|
21598
|
+
})
|
|
21599
|
+
},
|
|
21580
21600
|
set_project_destinations: {
|
|
21581
21601
|
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
21602
|
input: {
|
|
@@ -21622,7 +21642,7 @@ async function runTool(tool, args) {
|
|
|
21622
21642
|
}
|
|
21623
21643
|
}
|
|
21624
21644
|
function buildServer(client) {
|
|
21625
|
-
const server = new McpServer({ name: "sentinel", version: "0.
|
|
21645
|
+
const server = new McpServer({ name: "sentinel", version: "0.6.0" });
|
|
21626
21646
|
for (const [name, tool] of Object.entries(buildTools(client))) {
|
|
21627
21647
|
server.registerTool(name, { description: tool.description, inputSchema: tool.input }, (args) => runTool(tool, args));
|
|
21628
21648
|
}
|
|
@@ -21637,6 +21657,7 @@ if (!credential) {
|
|
|
21637
21657
|
}
|
|
21638
21658
|
var client = createClient({
|
|
21639
21659
|
token: credential.token,
|
|
21640
|
-
baseUrl: process.env.SENTINEL_API_URL
|
|
21660
|
+
baseUrl: process.env.SENTINEL_API_URL,
|
|
21661
|
+
client: "mcp"
|
|
21641
21662
|
});
|
|
21642
21663
|
await buildServer(client).connect(new StdioServerTransport);
|
|
@@ -542,6 +542,35 @@ export type ProjectWatchesView = {
|
|
|
542
542
|
locked: number;
|
|
543
543
|
[field: string]: unknown;
|
|
544
544
|
};
|
|
545
|
+
/** One change to what Sentinel watches: turned on or off, a rule edit (`prevRule` is what it replaced), a mute or
|
|
546
|
+
* a cooldown. `source` is how it was made (`web`, `key "<name>"`, …), null when not known. */
|
|
547
|
+
export type WatchChange = {
|
|
548
|
+
id: string;
|
|
549
|
+
at: string;
|
|
550
|
+
action: "applied" | "on" | "off" | "muted" | "cooldown";
|
|
551
|
+
contractId: string;
|
|
552
|
+
watch: {
|
|
553
|
+
id: string;
|
|
554
|
+
title: string;
|
|
555
|
+
};
|
|
556
|
+
subject: WatchSubject;
|
|
557
|
+
units: Units | null;
|
|
558
|
+
rule: WatchRule | null;
|
|
559
|
+
prevRule: WatchRule | null;
|
|
560
|
+
note: string | null;
|
|
561
|
+
by: "you";
|
|
562
|
+
source: string | null;
|
|
563
|
+
};
|
|
564
|
+
export type ChangesOptions = {
|
|
565
|
+
limit?: number;
|
|
566
|
+
cursor?: string | null;
|
|
567
|
+
/** One intent watch's id (from projectWatches). */
|
|
568
|
+
watch?: string;
|
|
569
|
+
/** `mutes`: only mutes and cooldowns. */
|
|
570
|
+
filter?: "mutes";
|
|
571
|
+
};
|
|
572
|
+
/** One change as a line: `Money out turned on`, `sBTC rule changed: ≥ 5 → ≥ 2`. */
|
|
573
|
+
export declare function changeText(c: WatchChange): string;
|
|
545
574
|
/** One intent watch's activity, as the web app draws it: points, the history page, counts. */
|
|
546
575
|
export type WatchActivity = {
|
|
547
576
|
window: 7 | 90;
|
|
@@ -633,6 +662,8 @@ export type ClientOptions = {
|
|
|
633
662
|
baseUrl?: string;
|
|
634
663
|
token: string;
|
|
635
664
|
fetch?: typeof globalThis.fetch;
|
|
665
|
+
/** Which tool is calling (sent as `x-sentinel-client`), so a change's history can say "sentinel CLI" or "MCP". */
|
|
666
|
+
client?: "cli" | "mcp";
|
|
636
667
|
};
|
|
637
668
|
export declare function createClient(opts: ClientOptions): {
|
|
638
669
|
me: () => Promise<Record<string, unknown>>;
|
|
@@ -890,6 +921,11 @@ export declare function createClient(opts: ClientOptions): {
|
|
|
890
921
|
contract?: string;
|
|
891
922
|
fn?: string;
|
|
892
923
|
}) => Promise<WatchActivity>;
|
|
924
|
+
/** The account's changes to what Sentinel watches across the project, newest first; `next` pages back. */
|
|
925
|
+
projectChanges: (projectId: string, opts?: ChangesOptions) => Promise<{
|
|
926
|
+
events: WatchChange[];
|
|
927
|
+
next: string | null;
|
|
928
|
+
}>;
|
|
893
929
|
};
|
|
894
930
|
export type SentinelClient = ReturnType<typeof createClient>;
|
|
895
931
|
/** 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.6.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",
|