rekor-cli 0.1.27 → 0.1.29

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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/program.ts
4
- import { Command as Command18 } from "commander";
4
+ import { Command as Command21 } from "commander";
5
5
 
6
6
  // src/commands/login.ts
7
7
  import { Command } from "commander";
@@ -527,6 +527,9 @@ async function currentAuthKind() {
527
527
  const resolved = await getResolvedToken();
528
528
  return resolved?.kind ?? null;
529
529
  }
530
+ async function isAuthenticated() {
531
+ return await getResolvedToken() !== null;
532
+ }
530
533
 
531
534
  // src/pkce.ts
532
535
  import * as crypto from "crypto";
@@ -540,6 +543,17 @@ function generateState() {
540
543
  return crypto.randomBytes(16).toString("base64url");
541
544
  }
542
545
 
546
+ // src/env.ts
547
+ function isCI() {
548
+ return Boolean(process.env["CI"]);
549
+ }
550
+ function isInteractive() {
551
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
552
+ }
553
+ function isSSH() {
554
+ return Boolean(process.env["SSH_CONNECTION"] || process.env["SSH_TTY"]);
555
+ }
556
+
543
557
  // src/commands/login.ts
544
558
  var FALLBACK_URL_DELAY_MS = 5e3;
545
559
  var loginCommand = new Command("login").description("Authenticate with Rekor").option("--token <token>", "API key for headless/CI authentication (rec_...)").option("--api-url <url>", "API base URL").action(async (opts) => {
@@ -548,18 +562,10 @@ var loginCommand = new Command("login").description("Authenticate with Rekor").o
548
562
  console.log("Authenticated successfully");
549
563
  return;
550
564
  }
551
- try {
552
- await browserLoginPkce(opts.apiUrl);
553
- console.log("Authenticated successfully");
554
- } catch (err) {
555
- console.error(
556
- `Login failed: ${err instanceof Error ? err.message : "Unknown error"}`
557
- );
558
- process.exit(1);
559
- }
565
+ await browserLoginPkce(opts.apiUrl);
566
+ console.log("Authenticated successfully");
560
567
  });
561
568
  async function browserLoginPkce(apiUrl) {
562
- const open = await import("open").then((m) => m.default);
563
569
  const codeVerifier = generateCodeVerifier();
564
570
  const codeChallenge = generateCodeChallenge(codeVerifier);
565
571
  const state = generateState();
@@ -575,24 +581,31 @@ async function browserLoginPkce(apiUrl) {
575
581
  authorizeUrl.searchParams.set("code_challenge_method", "S256");
576
582
  authorizeUrl.searchParams.set("state", state);
577
583
  authorizeUrl.searchParams.set("scope", "openid profile email offline_access");
578
- console.log("Opening browser for authentication...");
579
584
  const callbackPromise = startCallbackServer(port, state);
580
- let fallbackPrinted = false;
581
- const fallbackTimer = setTimeout(() => {
582
- fallbackPrinted = true;
583
- console.log(`If the browser didn't open, visit: ${authorizeUrl.toString()}`);
584
- }, FALLBACK_URL_DELAY_MS);
585
- open(authorizeUrl.toString()).catch(() => {
586
- if (fallbackPrinted) return;
587
- clearTimeout(fallbackTimer);
588
- console.log("Could not open browser automatically.");
589
- console.log(`Visit: ${authorizeUrl.toString()}`);
590
- });
585
+ let fallbackTimer;
586
+ if (isSSH()) {
587
+ console.log("SSH session detected. Open this URL in a browser to authenticate:");
588
+ console.log(authorizeUrl.toString());
589
+ } else {
590
+ console.log("Opening browser for authentication...");
591
+ const open = await import("open").then((m) => m.default);
592
+ let fallbackPrinted = false;
593
+ fallbackTimer = setTimeout(() => {
594
+ fallbackPrinted = true;
595
+ console.log(`If the browser didn't open, visit: ${authorizeUrl.toString()}`);
596
+ }, FALLBACK_URL_DELAY_MS);
597
+ open(authorizeUrl.toString()).catch(() => {
598
+ if (fallbackPrinted) return;
599
+ clearTimeout(fallbackTimer);
600
+ console.log("Could not open browser automatically.");
601
+ console.log(`Visit: ${authorizeUrl.toString()}`);
602
+ });
603
+ }
591
604
  let code;
592
605
  try {
593
606
  ({ code } = await callbackPromise);
594
607
  } finally {
595
- clearTimeout(fallbackTimer);
608
+ if (fallbackTimer) clearTimeout(fallbackTimer);
596
609
  }
597
610
  const tokens = await exchangeCodeForTokens(authkitDomain, clientId, code, codeVerifier, redirectUri);
598
611
  const expiresAt = expiresInToIso(tokens.expires_in);
@@ -607,6 +620,43 @@ async function browserLoginPkce(apiUrl) {
607
620
  // src/commands/logout.ts
608
621
  import { Command as Command2 } from "commander";
609
622
 
623
+ // src/errors.ts
624
+ var ApiError = class extends Error {
625
+ constructor(status, message, code, body) {
626
+ super(message);
627
+ this.status = status;
628
+ this.code = code;
629
+ this.body = body;
630
+ this.name = "ApiError";
631
+ }
632
+ /** 4xx are user-facing (bad input, auth, quota) — not bugs worth reporting. */
633
+ get isExpected() {
634
+ return this.status >= 400 && this.status < 500;
635
+ }
636
+ };
637
+ function friendlyHint(err) {
638
+ if (!(err instanceof ApiError)) return null;
639
+ switch (err.status) {
640
+ case 401:
641
+ return "Authentication failed or session expired. Run `rekor login`.";
642
+ case 402:
643
+ return "Quota exceeded for your plan. Review usage or upgrade your plan.";
644
+ case 403:
645
+ return "Access denied. Your token may lack permission for this resource.";
646
+ case 429:
647
+ return "Rate limited. Wait a moment and try again.";
648
+ default:
649
+ if (err.status >= 500) return "Rekor server error. Try again shortly.";
650
+ return null;
651
+ }
652
+ }
653
+ function handleCliError(err) {
654
+ const message = err instanceof Error ? err.message : String(err);
655
+ console.error(`Error: ${message}`);
656
+ const hint = friendlyHint(err);
657
+ if (hint) console.error(hint);
658
+ }
659
+
610
660
  // src/client.ts
611
661
  var ApiClient = class {
612
662
  baseUrl;
@@ -632,11 +682,24 @@ var ApiClient = class {
632
682
  },
633
683
  body: body ? JSON.stringify(body) : void 0
634
684
  });
635
- const json = await res.json();
685
+ const text = await res.text();
686
+ let parsed;
687
+ if (text) {
688
+ try {
689
+ parsed = JSON.parse(text);
690
+ } catch {
691
+ parsed = void 0;
692
+ }
693
+ }
636
694
  if (!res.ok) {
637
- throw new Error(json.error?.message ?? `HTTP ${res.status}`);
695
+ throw new ApiError(
696
+ res.status,
697
+ parsed?.error?.message ?? `HTTP ${res.status}`,
698
+ parsed?.error?.code,
699
+ parsed
700
+ );
638
701
  }
639
- return json.data;
702
+ return parsed?.data;
640
703
  }
641
704
  async uploadFile(url, body, contentType) {
642
705
  const res = await fetch(url, {
@@ -648,7 +711,7 @@ var ApiClient = class {
648
711
  body
649
712
  });
650
713
  if (!res.ok) {
651
- throw new Error(`Upload failed: HTTP ${res.status}`);
714
+ throw new ApiError(res.status, `Upload failed: HTTP ${res.status}`);
652
715
  }
653
716
  }
654
717
  };
@@ -732,6 +795,22 @@ function getFormat(cmd) {
732
795
  return cmd.parent?.parent?.opts().output ?? cmd.parent?.opts().output ?? "table";
733
796
  }
734
797
 
798
+ // src/prompt.ts
799
+ import * as readline from "readline";
800
+ function confirm(question, opts = {}) {
801
+ if (!isInteractive()) return Promise.resolve(true);
802
+ const suffix = opts.defaultYes ? "[Y/n]" : "[y/N]";
803
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
804
+ return new Promise((resolve) => {
805
+ rl.question(`${question} ${suffix}: `, (answer) => {
806
+ rl.close();
807
+ const a = answer.trim().toLowerCase();
808
+ if (a === "") return resolve(Boolean(opts.defaultYes));
809
+ resolve(a === "y" || a === "yes");
810
+ });
811
+ });
812
+ }
813
+
735
814
  // src/commands/databases.ts
736
815
  var databasesCommand = new Command3("databases").description("Manage databases");
737
816
  databasesCommand.command("list").description("List all databases").option("--tag <tag>", "Filter by tag").action(async function(opts) {
@@ -764,9 +843,13 @@ databasesCommand.command("tag <id>").description("Set tags on a database").requi
764
843
  });
765
844
  console.log(formatOutput(data, getFormat(this)));
766
845
  });
767
- databasesCommand.command("delete <id>").description("Delete a database").action(async (_id) => {
846
+ databasesCommand.command("delete <id>").description("Delete a database").option("-y, --yes", "Skip confirmation prompt").action(async (id, opts) => {
847
+ if (!opts.yes && !await confirm(`Delete database "${id}"? This cannot be undone.`)) {
848
+ console.log("Aborted");
849
+ return;
850
+ }
768
851
  const client = new ApiClient();
769
- await client.request("DELETE", `/v1/databases/${_id}`);
852
+ await client.request("DELETE", `/v1/databases/${id}`);
770
853
  console.log("Deleted");
771
854
  });
772
855
  databasesCommand.command("create-preview <production-id>").description("Create a preview database linked to a production database").requiredOption("--name <name>", "Preview database name").option("--description <desc>", "Description").action(async function(productionId, opts) {
@@ -833,7 +916,11 @@ collectionsCommand.command("upsert <id>").description("Create or update a collec
833
916
  const data = await client.request("PUT", `/v1/${ws}/collections/${id}`, body);
834
917
  console.log(formatOutput(data, getFormat(this)));
835
918
  });
836
- collectionsCommand.command("delete <id>").description("Delete a collection").action(async function(id) {
919
+ collectionsCommand.command("delete <id>").description("Delete a collection").option("-y, --yes", "Skip confirmation prompt").action(async function(id, opts) {
920
+ if (!opts.yes && !await confirm(`Delete collection "${id}"? This cannot be undone.`)) {
921
+ console.log("Aborted");
922
+ return;
923
+ }
837
924
  const ws = getDatabase(this);
838
925
  const client = new ApiClient();
839
926
  await client.request("DELETE", `/v1/${ws}/collections/${id}`);
@@ -859,7 +946,11 @@ documentsCommand.command("get <collection> <id>").description("Get a document by
859
946
  const data = await client.request("GET", `/v1/${ws}/documents/${collection}/${id}`);
860
947
  console.log(formatOutput(data, getFormat(this)));
861
948
  });
862
- documentsCommand.command("delete <collection> <id>").description("Delete a document").action(async function(collection, id) {
949
+ documentsCommand.command("delete <collection> <id>").description("Delete a document").option("-y, --yes", "Skip confirmation prompt").action(async function(collection, id, opts) {
950
+ if (!opts.yes && !await confirm(`Delete document ${collection}/${id}? This cannot be undone.`)) {
951
+ console.log("Aborted");
952
+ return;
953
+ }
863
954
  const ws = getDatabase(this);
864
955
  const client = new ApiClient();
865
956
  await client.request("DELETE", `/v1/${ws}/documents/${collection}/${id}`);
@@ -925,7 +1016,11 @@ relationshipsCommand.command("get <id>").description("Get a relationship by ID")
925
1016
  const data = await client.request("GET", `/v1/${ws}/relationships/${id}`);
926
1017
  console.log(formatOutput(data, getFormat(this)));
927
1018
  });
928
- relationshipsCommand.command("delete <id>").description("Delete a relationship").action(async function(id) {
1019
+ relationshipsCommand.command("delete <id>").description("Delete a relationship").option("-y, --yes", "Skip confirmation prompt").action(async function(id, opts) {
1020
+ if (!opts.yes && !await confirm(`Delete relationship ${id}? This cannot be undone.`)) {
1021
+ console.log("Aborted");
1022
+ return;
1023
+ }
929
1024
  const ws = getDatabase(this);
930
1025
  const client = new ApiClient();
931
1026
  await client.request("DELETE", `/v1/${ws}/relationships/${id}`);
@@ -985,7 +1080,11 @@ attachmentsCommand.command("list <collection> <id>").description("List attachmen
985
1080
  const data = await client.request("GET", `/v1/${ws}/documents/${collection}/${id}/attachments${query}`);
986
1081
  console.log(formatOutput(data, getFormat(this)));
987
1082
  });
988
- attachmentsCommand.command("delete <collection> <id> <attachment-id>").description("Delete an attachment").action(async function(collection, id, attachmentId) {
1083
+ attachmentsCommand.command("delete <collection> <id> <attachment-id>").description("Delete an attachment").option("-y, --yes", "Skip confirmation prompt").action(async function(collection, id, attachmentId, opts) {
1084
+ if (!opts.yes && !await confirm(`Delete attachment ${attachmentId} on ${collection}/${id}? This cannot be undone.`)) {
1085
+ console.log("Aborted");
1086
+ return;
1087
+ }
989
1088
  const ws = getDatabase(this);
990
1089
  const client = new ApiClient();
991
1090
  await client.request("DELETE", `/v1/${ws}/documents/${collection}/${id}/attachments/${attachmentId}`);
@@ -1023,7 +1122,11 @@ hooksCommand.command("list").description("List all hooks").action(async function
1023
1122
  const data = await client.request("GET", `/v1/${ws}/hooks`);
1024
1123
  console.log(formatOutput(data, getFormat(this)));
1025
1124
  });
1026
- hooksCommand.command("delete <id>").description("Delete a hook").action(async function(id) {
1125
+ hooksCommand.command("delete <id>").description("Delete a hook").option("-y, --yes", "Skip confirmation prompt").action(async function(id, opts) {
1126
+ if (!opts.yes && !await confirm(`Delete hook ${id}? This cannot be undone.`)) {
1127
+ console.log("Aborted");
1128
+ return;
1129
+ }
1027
1130
  const ws = getDatabase(this);
1028
1131
  const client = new ApiClient();
1029
1132
  await client.request("DELETE", `/v1/${ws}/hooks/${id}`);
@@ -1066,7 +1169,11 @@ triggersCommand.command("list").description("List all triggers").action(async fu
1066
1169
  const data = await client.request("GET", `/v1/${ws}/triggers`);
1067
1170
  console.log(formatOutput(data, getFormat(this)));
1068
1171
  });
1069
- triggersCommand.command("delete <id>").description("Delete a trigger").action(async function(id) {
1172
+ triggersCommand.command("delete <id>").description("Delete a trigger").option("-y, --yes", "Skip confirmation prompt").action(async function(id, opts) {
1173
+ if (!opts.yes && !await confirm(`Delete trigger ${id}? This cannot be undone.`)) {
1174
+ console.log("Aborted");
1175
+ return;
1176
+ }
1070
1177
  const ws = getDatabase(this);
1071
1178
  const client = new ApiClient();
1072
1179
  await client.request("DELETE", `/v1/${ws}/triggers/${id}`);
@@ -1120,7 +1227,11 @@ endpointsCommand.command("list").description("List all endpoints").action(async
1120
1227
  const data = await client.request("GET", `/v1/${ws}/endpoints`);
1121
1228
  console.log(formatOutput(data, getFormat(this)));
1122
1229
  });
1123
- endpointsCommand.command("delete <slug>").description("Delete an endpoint").action(async function(slug) {
1230
+ endpointsCommand.command("delete <slug>").description("Delete an endpoint").option("-y, --yes", "Skip confirmation prompt").action(async function(slug, opts) {
1231
+ if (!opts.yes && !await confirm(`Delete endpoint "${slug}"? This cannot be undone.`)) {
1232
+ console.log("Aborted");
1233
+ return;
1234
+ }
1124
1235
  const ws = getDatabase(this);
1125
1236
  const client = new ApiClient();
1126
1237
  await client.request("DELETE", `/v1/${ws}/endpoints/${slug}`);
@@ -1180,8 +1291,8 @@ providersCommand.command("export <provider>").description(`Export collections as
1180
1291
  const query = opts.collections ? `?collections=${opts.collections}` : "";
1181
1292
  const data = await client.request("GET", `/v1/${ws}/providers/${provider}/export${query}`);
1182
1293
  if (opts.output) {
1183
- const { writeFileSync: writeFileSync3 } = await import("fs");
1184
- writeFileSync3(opts.output, JSON.stringify(data, null, 2));
1294
+ const { writeFileSync: writeFileSync4 } = await import("fs");
1295
+ writeFileSync4(opts.output, JSON.stringify(data, null, 2));
1185
1296
  console.log(`Written to ${opts.output}`);
1186
1297
  } else {
1187
1298
  console.log(formatOutput(data, getFormat(this)));
@@ -1257,11 +1368,12 @@ doCommand.command("read <type> <id> <table>").description("Read rows from an int
1257
1368
  if (data.truncated) console.log("(truncated \u2014 pass --limit to see more)");
1258
1369
  });
1259
1370
  var reportCommand = debugCommand.command("report").description("Inspect and triage the platform_report queue (Sentry + CLI bug reports)");
1260
- reportCommand.command("list").description("List reports with optional filters").option("--status <s>", "pending | grouped | escalated | addressed | dismissed").option("--source <s>", "sentry | cli_report").option("--limit <n>", "Max rows (default 50, cap 200)", (v) => Number(v)).option("--offset <n>", "Offset for pagination", (v) => Number(v)).option("--since <iso>", "Lower bound on created_at (ISO timestamp)").action(async function(opts) {
1371
+ reportCommand.command("list").description("List reports with optional filters").option("--status <s>", "pending | grouped | escalated | addressed | dismissed").option("--source <s>", "sentry | cli_report | review | security_audit").option("--classification <c>", "bug | flaky_test | security").option("--limit <n>", "Max rows (default 50, cap 200)", (v) => Number(v)).option("--offset <n>", "Offset for pagination", (v) => Number(v)).option("--since <iso>", "Lower bound on created_at (ISO timestamp)").action(async function(opts) {
1261
1372
  const client = new ApiClient();
1262
1373
  const params = new URLSearchParams();
1263
1374
  if (opts.status) params.set("status", opts.status);
1264
1375
  if (opts.source) params.set("source", opts.source);
1376
+ if (opts.classification) params.set("classification", opts.classification);
1265
1377
  if (opts.limit !== void 0) params.set("limit", String(opts.limit));
1266
1378
  if (opts.offset !== void 0) params.set("offset", String(opts.offset));
1267
1379
  if (opts.since) params.set("since", opts.since);
@@ -1329,13 +1441,16 @@ reportCommand.command("dismiss <id>").description("Mark a report as dismissed (n
1329
1441
 
1330
1442
  // src/commands/report-bug.ts
1331
1443
  import { Command as Command17 } from "commander";
1332
- var reportBugCommand = new Command17("report-bug").description("Submit a bug report to Rekor (deduplicated by content)").requiredOption("--title <title>", "Short summary").requiredOption("--description <text>", "What happened").option("--severity <level>", "low | medium | high | critical").option("--steps <text>", "Steps to reproduce").option("--error-message <text>", "Exact error text if any").option("--context <text>", "Additional context (logs, request IDs)").option("--locale <code>", "Notification locale (en | pt | es)", "en").option("--reporter-email <addr>", "Override reporter email (defaults to session email)").action(async function(opts) {
1444
+ var reportBugCommand = new Command17("report-bug").description("Submit a report to the Rekor triage queue (deduplicated)").requiredOption("--title <title>", "Short summary").requiredOption("--description <text>", "What happened").option("--source <source>", "Origin: cli_report (default) | review | security_audit").option("--classification <c>", "Nature: bug (default) | flaky_test | security").option("--dedup-key <key>", 'Stable dedup key (e.g. "<file>::<test>"); collapses repeat occurrences').option("--severity <level>", "low | medium | high | critical").option("--steps <text>", "Steps to reproduce").option("--error-message <text>", "Exact error text if any").option("--context <text>", "Additional context (logs, request IDs)").option("--locale <code>", "Notification locale (en | pt | es)", "en").option("--reporter-email <addr>", "Override reporter email (defaults to session email)").action(async function(opts) {
1333
1445
  const client = new ApiClient();
1334
1446
  const body = {
1335
1447
  title: opts.title,
1336
1448
  description: opts.description,
1337
1449
  locale: opts.locale
1338
1450
  };
1451
+ if (opts.source) body["source"] = opts.source;
1452
+ if (opts.classification) body["classification"] = opts.classification;
1453
+ if (opts.dedupKey) body["dedup_key"] = opts.dedupKey;
1339
1454
  if (opts.severity) body["severity"] = opts.severity;
1340
1455
  if (opts.steps) body["steps"] = opts.steps;
1341
1456
  if (opts.errorMessage) body["error_message"] = opts.errorMessage;
@@ -1350,10 +1465,95 @@ var reportBugCommand = new Command17("report-bug").description("Submit a bug rep
1350
1465
  }
1351
1466
  });
1352
1467
 
1468
+ // src/commands/update.ts
1469
+ import { Command as Command18 } from "commander";
1470
+ import { execFileSync } from "child_process";
1471
+
1472
+ // src/version.ts
1473
+ function isNewerVersion(latest, current) {
1474
+ const strip = (v) => v.replace(/[-+].*$/, "");
1475
+ const l = strip(latest).split(".").map(Number);
1476
+ const c = strip(current).split(".").map(Number);
1477
+ for (let i = 0; i < Math.max(l.length, c.length); i++) {
1478
+ const lv = l[i] ?? 0;
1479
+ const cv = c[i] ?? 0;
1480
+ if (Number.isNaN(lv) || Number.isNaN(cv)) return false;
1481
+ if (lv > cv) return true;
1482
+ if (lv < cv) return false;
1483
+ }
1484
+ return false;
1485
+ }
1486
+
1487
+ // src/version-check.ts
1488
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
1489
+ import { join as join2 } from "path";
1490
+ import { execFile } from "child_process";
1491
+ var NPM_PACKAGE = "rekor-cli";
1492
+ var CACHE_FILE = join2(CONFIG_DIR, "update-check.json");
1493
+ var MAX_AGE_MS = 24 * 60 * 60 * 1e3;
1494
+ function isDisabled() {
1495
+ return Boolean(
1496
+ process.env["NO_UPDATE_NOTIFIER"] || process.env["REKOR_NO_UPDATE_CHECK"] || isCI()
1497
+ );
1498
+ }
1499
+ function readCache() {
1500
+ try {
1501
+ const parsed = JSON.parse(readFileSync6(CACHE_FILE, "utf-8"));
1502
+ if (typeof parsed.lastCheck !== "number") return null;
1503
+ if (parsed.latest !== null && typeof parsed.latest !== "string") return null;
1504
+ return parsed;
1505
+ } catch {
1506
+ return null;
1507
+ }
1508
+ }
1509
+ function writeCache(cache) {
1510
+ try {
1511
+ mkdirSync3(CONFIG_DIR, { recursive: true, mode: 448 });
1512
+ writeFileSync3(CACHE_FILE, JSON.stringify(cache), { mode: 384 });
1513
+ } catch {
1514
+ }
1515
+ }
1516
+ function readCachedLatest(current) {
1517
+ const cache = readCache();
1518
+ if (!cache?.latest) return null;
1519
+ if (Date.now() - cache.lastCheck > MAX_AGE_MS) return null;
1520
+ return isNewerVersion(cache.latest, current) ? cache.latest : null;
1521
+ }
1522
+ function notify(latest, current) {
1523
+ console.error(`
1524
+ Update available: ${current} \u2192 ${latest}
1525
+ Run \`rekor update\` to update.`);
1526
+ }
1527
+ function checkForUpdates(current) {
1528
+ if (isDisabled()) return;
1529
+ try {
1530
+ const cache = readCache();
1531
+ if (cache && Date.now() - cache.lastCheck < MAX_AGE_MS) {
1532
+ if (cache.latest && isNewerVersion(cache.latest, current)) notify(cache.latest, current);
1533
+ return;
1534
+ }
1535
+ writeCache({ lastCheck: Date.now(), latest: cache?.latest ?? null });
1536
+ const child = execFile(
1537
+ "npm",
1538
+ ["view", NPM_PACKAGE, "version"],
1539
+ { timeout: 5e3, shell: true },
1540
+ (err, stdout) => {
1541
+ if (err) return;
1542
+ const latest = stdout.trim();
1543
+ if (!latest) return;
1544
+ writeCache({ lastCheck: Date.now(), latest });
1545
+ if (isNewerVersion(latest, current)) notify(latest, current);
1546
+ }
1547
+ );
1548
+ child.unref();
1549
+ } catch {
1550
+ }
1551
+ }
1552
+
1353
1553
  // package.json
1354
1554
  var package_default = {
1355
1555
  name: "rekor-cli",
1356
- version: "0.1.27",
1556
+ version: "0.1.29",
1357
1557
  type: "module",
1358
1558
  engines: {
1359
1559
  node: ">=20.0.0"
@@ -1377,6 +1577,9 @@ var package_default = {
1377
1577
  commander: "^13.1.0",
1378
1578
  open: "^11.0.0"
1379
1579
  },
1580
+ optionalDependencies: {
1581
+ "@sentry/node": "^10.0.0"
1582
+ },
1380
1583
  devDependencies: {
1381
1584
  "@types/node": "^25.5.0",
1382
1585
  tsup: "^8.4.0",
@@ -1385,10 +1588,110 @@ var package_default = {
1385
1588
  }
1386
1589
  };
1387
1590
 
1591
+ // src/commands/update.ts
1592
+ var updateCommand = new Command18("update").description("Update the Rekor CLI to the latest published version").action(() => {
1593
+ console.log(`Current version: ${package_default.version}`);
1594
+ console.log("Checking for updates...");
1595
+ let latest;
1596
+ try {
1597
+ latest = execFileSync("npm", ["view", NPM_PACKAGE, "version"], { timeout: 1e4, shell: true }).toString().trim();
1598
+ } catch {
1599
+ console.error(`Could not reach npm to check for updates.`);
1600
+ console.error(`Try manually: npm install -g ${NPM_PACKAGE}`);
1601
+ process.exit(1);
1602
+ }
1603
+ if (!isNewerVersion(latest, package_default.version)) {
1604
+ console.log(`Already on the latest version (${package_default.version}).`);
1605
+ return;
1606
+ }
1607
+ console.log(`Updating ${package_default.version} \u2192 ${latest}...`);
1608
+ try {
1609
+ execFileSync("npm", ["install", "-g", `${NPM_PACKAGE}@latest`], {
1610
+ timeout: 12e4,
1611
+ shell: true,
1612
+ stdio: "inherit"
1613
+ });
1614
+ console.log(`Updated to ${latest}.`);
1615
+ } catch (err) {
1616
+ console.error(`Update failed: ${err instanceof Error ? err.message : String(err)}`);
1617
+ console.error(`Try manually: npm install -g ${NPM_PACKAGE}`);
1618
+ process.exit(1);
1619
+ }
1620
+ });
1621
+
1622
+ // src/commands/whoami.ts
1623
+ import { Command as Command19 } from "commander";
1624
+ var whoamiCommand = new Command19("whoami").description("Show the authenticated identity").action(async function() {
1625
+ const client = new ApiClient();
1626
+ const me = await client.request("GET", "/v1/auth/me");
1627
+ if (getFormat(this) === "json") {
1628
+ console.log(JSON.stringify(me, null, 2));
1629
+ return;
1630
+ }
1631
+ console.log(`auth_kind: ${me.auth_kind}`);
1632
+ if (me.email) console.log(`email: ${me.email}`);
1633
+ if (me.user_id) console.log(`user_id: ${me.user_id}`);
1634
+ if (me.org_id) console.log(`org_id: ${me.org_id}`);
1635
+ console.log(`grants: ${me.grants_summary}`);
1636
+ if (me.orgs && me.orgs.length > 0) {
1637
+ console.log("organizations:");
1638
+ for (const o of me.orgs) {
1639
+ console.log(` ${o.name} (${o.org_id}) \u2014 ${o.role}`);
1640
+ }
1641
+ }
1642
+ });
1643
+
1644
+ // src/commands/status.ts
1645
+ import { Command as Command20 } from "commander";
1646
+ var statusCommand = new Command20("status").description("Show auth, connectivity, and CLI version diagnostics").action(async function() {
1647
+ const config = loadConfig();
1648
+ const loggedIn = await isAuthenticated();
1649
+ const cliLatest = readCachedLatest(package_default.version);
1650
+ let tokenValid = null;
1651
+ let me = null;
1652
+ if (loggedIn) {
1653
+ try {
1654
+ me = await new ApiClient().request("GET", "/v1/auth/me");
1655
+ tokenValid = true;
1656
+ } catch (err) {
1657
+ if (err instanceof ApiError && (err.status === 401 || err.status === 403)) tokenValid = false;
1658
+ }
1659
+ }
1660
+ const snapshot = {
1661
+ logged_in: loggedIn,
1662
+ token_valid: tokenValid,
1663
+ api_url: config.api_url,
1664
+ auth_kind: me?.auth_kind ?? null,
1665
+ email: me?.email ?? null,
1666
+ org_id: me?.org_id ?? null,
1667
+ grants: me?.grants_summary ?? null,
1668
+ cli: { version: package_default.version, latest: cliLatest }
1669
+ };
1670
+ if (getFormat(this) === "json") {
1671
+ console.log(JSON.stringify(snapshot, null, 2));
1672
+ return;
1673
+ }
1674
+ if (!loggedIn) {
1675
+ console.log("Not logged in. Run `rekor login`.");
1676
+ console.log(`API: ${config.api_url}`);
1677
+ if (cliLatest) console.log(`CLI: ${package_default.version} (latest: ${cliLatest} \u2014 run \`rekor update\`)`);
1678
+ return;
1679
+ }
1680
+ const tokenStatus = tokenValid === true ? "token valid" : tokenValid === false ? "token rejected \u2014 run `rekor login` to re-authenticate" : "token validity unknown \u2014 backend unreachable";
1681
+ console.log(`Logged in${me?.email ? ` as ${me.email}` : ""} \u2014 ${tokenStatus}`);
1682
+ console.log(`API: ${config.api_url}`);
1683
+ if (me?.auth_kind) console.log(`Auth: ${me.auth_kind}`);
1684
+ if (me?.org_id) console.log(`Org: ${me.org_id}`);
1685
+ if (me?.grants_summary) console.log(`Grants: ${me.grants_summary}`);
1686
+ if (cliLatest) console.log(`CLI: ${package_default.version} (latest: ${cliLatest} \u2014 run \`rekor update\`)`);
1687
+ });
1688
+
1388
1689
  // src/program.ts
1389
- var program = new Command18("rekor").description("Rekor CLI \u2014 System of Record for AI agents").version(package_default.version).option("--database <id>", "Database ID").option("--output <format>", "Output format: json or table", "table");
1690
+ var program = new Command21("rekor").description("Rekor CLI \u2014 System of Record for AI agents").version(package_default.version).option("--database <id>", "Database ID").option("--output <format>", "Output format: json or table", "table");
1390
1691
  program.addCommand(loginCommand);
1391
1692
  program.addCommand(logoutCommand);
1693
+ program.addCommand(whoamiCommand);
1694
+ program.addCommand(statusCommand);
1392
1695
  program.addCommand(databasesCommand);
1393
1696
  program.addCommand(collectionsCommand);
1394
1697
  program.addCommand(documentsCommand);
@@ -1404,7 +1707,84 @@ program.addCommand(tokensCommand);
1404
1707
  program.addCommand(endpointsCommand);
1405
1708
  program.addCommand(debugCommand);
1406
1709
  program.addCommand(reportBugCommand);
1710
+ program.addCommand(updateCommand);
1711
+
1712
+ // src/telemetry.ts
1713
+ import { homedir as homedir2, platform, arch } from "os";
1714
+ var sentry = null;
1715
+ function getDsn() {
1716
+ if (process.env["REKOR_TELEMETRY_DISABLED"]) return void 0;
1717
+ return process.env["REKOR_CLI_SENTRY_DSN"] || void 0;
1718
+ }
1719
+ function scrubHome(event, home) {
1720
+ const root = home.replace(/[\\/]+$/, "");
1721
+ if (!root) return event;
1722
+ const re = new RegExp(root.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "(?=[\\\\/]|$)", "g");
1723
+ const scrub = (s) => s.replace(re, "~");
1724
+ for (const value of event.exception?.values ?? []) {
1725
+ if (value.value) value.value = scrub(value.value);
1726
+ for (const frame of value.stacktrace?.frames ?? []) {
1727
+ if (frame.filename) frame.filename = scrub(frame.filename);
1728
+ if (frame.abs_path) frame.abs_path = scrub(frame.abs_path);
1729
+ }
1730
+ }
1731
+ return event;
1732
+ }
1733
+ async function initTelemetry(command2, version) {
1734
+ if (sentry) return;
1735
+ const dsn = getDsn();
1736
+ if (!dsn) return;
1737
+ try {
1738
+ const mod = await import("@sentry/node");
1739
+ const home = homedir2();
1740
+ mod.init({
1741
+ dsn,
1742
+ tracesSampleRate: 0,
1743
+ release: `rekor-cli@${version}`,
1744
+ environment: "production",
1745
+ defaultIntegrations: false,
1746
+ beforeSend: (event) => scrubHome(event, home)
1747
+ });
1748
+ mod.setTag("command", command2 || "unknown");
1749
+ mod.setTag("node_version", process.version);
1750
+ mod.setTag("os_platform", platform());
1751
+ mod.setTag("os_arch", arch());
1752
+ sentry = mod;
1753
+ } catch {
1754
+ sentry = null;
1755
+ }
1756
+ }
1757
+ function captureException(err) {
1758
+ if (!sentry) return;
1759
+ if (err instanceof ApiError && err.isExpected) return;
1760
+ try {
1761
+ sentry.captureException(err);
1762
+ } catch {
1763
+ }
1764
+ }
1765
+ async function closeTelemetry() {
1766
+ if (!sentry) return;
1767
+ try {
1768
+ await sentry.close(2e3);
1769
+ } catch {
1770
+ }
1771
+ sentry = null;
1772
+ }
1407
1773
 
1408
1774
  // src/index.ts
1409
- program.parse();
1775
+ var SKIP_UPDATE_CHECK = ["--version", "-v", "--help", "-h", "help", "update"];
1776
+ var command = process.argv[2];
1777
+ async function main() {
1778
+ await initTelemetry(command, package_default.version);
1779
+ await program.parseAsync();
1780
+ if (command && !SKIP_UPDATE_CHECK.includes(command)) {
1781
+ checkForUpdates(package_default.version);
1782
+ }
1783
+ }
1784
+ main().then(() => closeTelemetry()).catch(async (err) => {
1785
+ handleCliError(err);
1786
+ captureException(err);
1787
+ await closeTelemetry();
1788
+ process.exit(1);
1789
+ });
1410
1790
  //# sourceMappingURL=index.js.map