@secondlayer/sentinel 0.4.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/index.js CHANGED
@@ -79,6 +79,13 @@ class SentinelError extends Error {
79
79
  this.name = "SentinelError";
80
80
  }
81
81
  }
82
+ var runInProgress = (status) => status === "queued" || status === "running";
83
+ function runPhase(run) {
84
+ if (run.status === "queued")
85
+ return "queued";
86
+ const last = run.events.at(-1);
87
+ return last ? [last.name ?? last.kind, last.state].filter(Boolean).join(" ") : "starting";
88
+ }
82
89
  var rowsOf = (intents) => intents.flatMap((i) => i.rows);
83
90
  var subjectKey = (s) => s.kind === "fn" ? `fn:${s.fn}` : s.kind === "group" ? `group:${s.group}` : `asset:${s.asset}`;
84
91
  var CONTROL_NAME = "Control changes";
@@ -206,10 +213,30 @@ function mutedUntilOf(w, now = Date.now()) {
206
213
  const at = w.delivery.mutedUntil;
207
214
  return at && Date.parse(at) > now ? at : null;
208
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
+ }
209
233
  function createClient(opts) {
210
234
  const baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
211
235
  const doFetch = opts.fetch ?? globalThis.fetch;
212
- const authHeader = () => ({ authorization: `Bearer ${opts.token}` });
236
+ const authHeader = () => ({
237
+ authorization: `Bearer ${opts.token}`,
238
+ ...opts.client ? { "x-sentinel-client": opts.client } : {}
239
+ });
213
240
  async function call(method, path, body) {
214
241
  const res = await doFetch(`${baseUrl}${path}`, {
215
242
  method,
@@ -226,6 +253,7 @@ function createClient(opts) {
226
253
  }
227
254
  const plan = (id) => `/v1/plans/${encodeURIComponent(id)}`;
228
255
  const appPlan = (id) => `/plan/${encodeURIComponent(id)}`;
256
+ const appProject = (id) => `/projects/${encodeURIComponent(id)}`;
229
257
  return {
230
258
  me: () => call("GET", "/v1/me"),
231
259
  plans: async () => (await call("GET", "/v1/plans")).plans,
@@ -241,6 +269,8 @@ function createClient(opts) {
241
269
  testsFromRepo: (id) => call("POST", `${appPlan(id)}/tests`, { fromRepo: true }),
242
270
  previewPlan: (contractId) => call("POST", "/plan/preview", { contractId }),
243
271
  createPlan: (contractId) => call("POST", "/plan", { contractId }),
272
+ previewImport: (url) => call("POST", "/projects/import/preview", { url }),
273
+ importRepo: (input) => call("POST", "/projects/import", input),
244
274
  billing: () => call("GET", "/billing"),
245
275
  watches: (planId) => call("GET", `${appPlan(planId)}/watches`),
246
276
  setRule: (planId, watchId, rule) => call("POST", `${appPlan(planId)}/watches/${encodeURIComponent(watchId)}/rule`, { rule }),
@@ -257,9 +287,70 @@ function createClient(opts) {
257
287
  watchContract: (contractId) => call("POST", "/contracts/watch", {
258
288
  contractId
259
289
  }),
260
- stopMonitoring: (planId) => call("DELETE", `${appPlan(planId)}/monitor`)
290
+ stopMonitoring: (planId) => call("DELETE", `${appPlan(planId)}/monitor`),
291
+ watchActivity: (planId, intentId, opts = {}) => call("GET", `${appPlan(planId)}/watches/${encodeURIComponent(intentId)}/activity${query(opts)}`),
292
+ functions: async (planId) => (await call("GET", `${appPlan(planId)}/functions`)).functions,
293
+ functionActivity: (planId, fn) => call("GET", `${appPlan(planId)}/functions/${encodeURIComponent(fn)}/activity`),
294
+ alertPreview: (planId, example) => call("GET", `${appPlan(planId)}/alert-preview${query({ example })}`),
295
+ threshold: (planId, asset) => call("GET", `${appPlan(planId)}/watches/asset/${encodeURIComponent(asset)}/threshold`),
296
+ setThreshold: (planId, asset, amount) => call("POST", `${appPlan(planId)}/watches/asset/${encodeURIComponent(asset)}/threshold`, { amount }),
297
+ retryHistory: (planId) => call("POST", `${appPlan(planId)}/history/retry`, {}),
298
+ readAlert: (alertId) => call("POST", `/alerts/${encodeURIComponent(alertId)}/read`),
299
+ readAllAlerts: () => call("POST", "/alerts/read-all"),
300
+ ask: (q) => call("POST", "/ask", { q }),
301
+ acknowledge: (planId, finding, title, reason, note = null) => call("POST", `${appPlan(planId)}/acknowledge`, {
302
+ finding,
303
+ title,
304
+ reason,
305
+ note
306
+ }),
307
+ reopen: (planId, finding, title) => call("POST", `${appPlan(planId)}/reopen`, { finding, title }),
308
+ ackHistory: async (planId) => (await call("GET", `${appPlan(planId)}/acks`)).events,
309
+ notes: async (planId, finding) => (await call("GET", `${appPlan(planId)}/findings/${finding}/notes`)).notes,
310
+ addNote: (planId, finding, title, body) => call("POST", `${appPlan(planId)}/findings/${finding}/notes`, {
311
+ title,
312
+ body
313
+ }),
314
+ tests: (planId, files) => call("POST", `${appPlan(planId)}/tests`, { files }),
315
+ fuzzCheck: (planId, harness) => call("POST", `${appPlan(planId)}/fuzzcheck`, { harness }),
316
+ fuzz: (planId, invariants) => call("POST", `${appPlan(planId)}/fuzz`, { invariants }),
317
+ latestCommit: (planId) => call("GET", `${appPlan(planId)}/latest`),
318
+ updatePlan: (planId, commit) => call("POST", `${appPlan(planId)}/update`, commit ? { commit } : {}),
319
+ setAutoPlan: (planId, auto) => call("POST", `${appPlan(planId)}/wait`, { auto }),
320
+ setLink: (planId, dependency, url) => call("POST", `${appPlan(planId)}/link`, { id: dependency, url }),
321
+ planLinked: (planId) => call("POST", `${appPlan(planId)}/plan-linked`, {}),
322
+ renameContract: (planId, name) => call("POST", `${appPlan(planId)}/settings`, { name }),
323
+ removeContract: (planId, confirm) => call("POST", `${appPlan(planId)}/remove`, {
324
+ confirm
325
+ }),
326
+ projects: () => call("GET", "/projects"),
327
+ project: (projectId) => call("GET", appProject(projectId)),
328
+ projectOverview: (projectId, opts = {}) => call("GET", `${appProject(projectId)}/overview${opts.watches ? "?watches=1" : ""}`),
329
+ renameProject: (projectId, name) => call("POST", `${appProject(projectId)}/rename`, { name }),
330
+ removeProject: (projectId, confirm) => call("POST", `${appProject(projectId)}/remove`, {
331
+ confirm
332
+ }),
333
+ answerMergeSuggestion: (from, into, accept) => call("POST", "/projects/merge", { from, into, accept }),
334
+ mergeProject: (projectId, into) => call("POST", `${appProject(projectId)}/merge-into`, { into }),
335
+ moveProjectContract: (projectId, contractId, into) => call("POST", `${appProject(projectId)}/contracts/${encodeURIComponent(contractId)}/move`, { into }),
336
+ removeProjectContract: (projectId, contractId, confirm) => call("POST", `${appProject(projectId)}/contracts/${encodeURIComponent(contractId)}/remove`, { confirm }),
337
+ previewProjectContract: (projectId, contractId) => call("POST", `${appProject(projectId)}/contracts/preview`, { contractId }),
338
+ addProjectContract: (projectId, contractId, opts = {}) => call("POST", `${appProject(projectId)}/contracts`, { contractId, ...opts }),
339
+ setProjectDestinations: (projectId, dest) => call("POST", `${appProject(projectId)}/destinations`, dest),
340
+ setProjectMonitoring: (projectId, on) => call("POST", `${appProject(projectId)}/monitoring`, { on }),
341
+ projectWatches: (projectId) => call("GET", `${appProject(projectId)}/watches`),
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)}`)
261
344
  };
262
345
  }
346
+ function query(params) {
347
+ const q = new URLSearchParams;
348
+ for (const [k, v] of Object.entries(params))
349
+ if (v !== undefined && v !== null)
350
+ q.set(k, String(v));
351
+ const s = q.toString();
352
+ return s ? `?${s}` : "";
353
+ }
263
354
  var HOUR_MS = 3600000;
264
355
  var muteEnd = (hours) => new Date(Date.now() + Math.min(hours * HOUR_MS, 7 * 24 * HOUR_MS - 60000)).toISOString();
265
356
  async function watchContext(client, planId) {
@@ -295,6 +386,7 @@ export {
295
386
  SentinelError,
296
387
  SubjectError,
297
388
  assetClause,
389
+ changeText,
298
390
  clearSession,
299
391
  clearTokenFile,
300
392
  createClient,
@@ -312,6 +404,8 @@ export {
312
404
  resolveToken,
313
405
  rowsOf,
314
406
  ruleSummary,
407
+ runInProgress,
408
+ runPhase,
315
409
  savedChoices,
316
410
  savedMonitor,
317
411
  sessionPath,