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 +424 -44
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
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
|
|
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
|
-
|
|
552
|
-
|
|
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
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
console.log(
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
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/${
|
|
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:
|
|
1184
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|