@secondlayer/sentinel 0.1.0 → 0.1.1

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/README.md CHANGED
@@ -31,7 +31,7 @@ Anything that spends balance or changes monitoring shows what it would do first
31
31
  claude mcp add sentinel -- npx -y -p @secondlayer/sentinel sentinel-mcp
32
32
  ```
33
33
 
34
- Uses your login, or add `--env SENTINEL_API_KEY=sk-sl_…` for a scoped key. Every write tool takes `confirm`; without it you get what would happen and nothing changes.
34
+ Uses your login, or add `--env SENTINEL_API_KEY=sk-snt_…` for a scoped key. A secondlayer API key won't work here; Sentinel keys start `sk-snt_`. Every write tool takes `confirm`; without it you get what would happen and nothing changes.
35
35
 
36
36
  ## SDK
37
37
 
package/dist/cli.js CHANGED
@@ -279,6 +279,16 @@ async function planOfContract(client, contractId) {
279
279
  }
280
280
 
281
281
  // cli/auth.ts
282
+ var NEEDS_TTY = 'sentinel login needs an interactive terminal. In CI: echo "$KEY" | sentinel login --with-token';
283
+ var API_KEY_LOGIN = "Your secondlayer login is an API key Sentinel can't use. Run: sentinel login --force";
284
+
285
+ class PromptClosedError extends Error {
286
+ constructor() {
287
+ super(NEEDS_TTY);
288
+ this.name = "PromptClosedError";
289
+ }
290
+ }
291
+ var isOtherProductKey = (message) => message.startsWith("This is a secondlayer API key");
282
292
  var sessionExpiry = () => new Date(Date.now() + 90 * 24 * 60 * 60 * 1000).toISOString();
283
293
  async function post(doFetch, path, body) {
284
294
  try {
@@ -324,9 +334,39 @@ async function login(flags, io) {
324
334
  }
325
335
  const existing = await readSession();
326
336
  if (existing && !flags.includes("--force")) {
327
- io.out(`Already logged in as ${existing.email}. Run again with --force to switch accounts.`);
328
- return 0;
337
+ const r = await check(auth.clientFor(existing.token));
338
+ if (!("error" in r)) {
339
+ io.out(`Already logged in as ${r.email ?? existing.email}. Run again with --force to switch accounts.`);
340
+ return 0;
341
+ }
342
+ if (r.status === 401 && (existing.token.startsWith("sk-sl_") || isOtherProductKey(r.error))) {
343
+ io.err(API_KEY_LOGIN);
344
+ return 1;
345
+ }
346
+ if (r.status === 403) {
347
+ io.err(r.error);
348
+ return 0;
349
+ }
350
+ if (r.status !== 401) {
351
+ io.err(r.error);
352
+ return 1;
353
+ }
329
354
  }
355
+ if (!auth.interactive) {
356
+ io.err(NEEDS_TTY);
357
+ return 2;
358
+ }
359
+ try {
360
+ return await promptLogin(io);
361
+ } catch (e) {
362
+ if (!(e instanceof PromptClosedError))
363
+ throw e;
364
+ io.err(NEEDS_TTY);
365
+ return 2;
366
+ }
367
+ }
368
+ async function promptLogin(io) {
369
+ const { auth } = io;
330
370
  const doFetch = auth.fetch ?? globalThis.fetch;
331
371
  const email = (await auth.prompt("Email: ")).trim();
332
372
  if (!/^.+@.+\..+$/.test(email)) {
@@ -383,6 +423,22 @@ async function whoami(client, io) {
383
423
  return 0;
384
424
  }
385
425
 
426
+ // client/labels.ts
427
+ var isPrelaunchKey = (id) => id.startsWith("github:") || id.startsWith("gist:");
428
+ function contractName(id) {
429
+ if (isPrelaunchKey(id))
430
+ return id.split(":").pop()?.split("/").pop()?.replace(/\.clar$/, "") ?? id;
431
+ return id.split(".")[1] ?? id;
432
+ }
433
+ var MONITOR_LABELS = {
434
+ draft: "not on yet",
435
+ requested: "setting up",
436
+ live: "watching",
437
+ armed: "armed for launch",
438
+ stale: "deploy didn't match"
439
+ };
440
+ var monitoringLabel = (status) => status ? MONITOR_LABELS[status] ?? status : "not on yet";
441
+
386
442
  // cli/sentinel.ts
387
443
  var USAGE = `sentinel <command>
388
444
 
@@ -484,8 +540,8 @@ ${USAGE}` : USAGE);
484
540
  io.out(JSON.stringify(plans, null, 2));
485
541
  else {
486
542
  const rows = plans.map((p) => [
487
- p.contractId,
488
- cell(p.monitoring, "none"),
543
+ contractName(p.contractId),
544
+ monitoringLabel(p.monitoring),
489
545
  p.evidence?.worst ? `${p.evidence.worst.severity} ${p.evidence.worst.evidence}` : null,
490
546
  p.planId
491
547
  ]);
@@ -893,7 +949,10 @@ async function readStdin() {
893
949
  async function prompt(question) {
894
950
  const rl = createInterface({ input: process.stdin, output: process.stderr });
895
951
  try {
896
- return await rl.question(question);
952
+ return await new Promise((resolve, reject) => {
953
+ rl.once("close", () => reject(new PromptClosedError));
954
+ rl.question(question).then(resolve, reject);
955
+ });
897
956
  } finally {
898
957
  rl.close();
899
958
  }
@@ -913,7 +972,13 @@ async function main() {
913
972
  client: clientFor(credential?.token ?? ""),
914
973
  out: console.log,
915
974
  err: console.error,
916
- auth: { source: credential?.source ?? null, clientFor, prompt, readStdin }
975
+ auth: {
976
+ source: credential?.source ?? null,
977
+ interactive: Boolean(process.stdin.isTTY),
978
+ clientFor,
979
+ prompt,
980
+ readStdin
981
+ }
917
982
  });
918
983
  }
919
984
  process.exit(await main());
package/dist/mcp.js CHANGED
@@ -20713,7 +20713,7 @@ async function runTool(tool, args) {
20713
20713
  }
20714
20714
  }
20715
20715
  function buildServer(client) {
20716
- const server = new McpServer({ name: "sentinel", version: "0.1.0" });
20716
+ const server = new McpServer({ name: "sentinel", version: "0.1.1" });
20717
20717
  for (const [name, tool] of Object.entries(buildTools(client))) {
20718
20718
  server.registerTool(name, { description: tool.description, inputSchema: tool.input }, (args) => runTool(tool, args));
20719
20719
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@secondlayer/sentinel",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
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",