@computesdk/cli 1.0.11 → 1.0.13

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
@@ -708,6 +708,76 @@ var ActionsApiError = class extends Error {
708
708
  this.name = "ActionsApiError";
709
709
  }
710
710
  };
711
+ var ActionsCliError = class extends Error {
712
+ constructor(code, message) {
713
+ super(message);
714
+ this.code = code;
715
+ this.name = "ActionsCliError";
716
+ }
717
+ };
718
+ function codeForStatus(status) {
719
+ switch (status) {
720
+ case 400:
721
+ return "bad_request";
722
+ case 401:
723
+ return "unauthenticated";
724
+ case 403:
725
+ return "forbidden";
726
+ case 404:
727
+ return "not_found";
728
+ case 409:
729
+ return "conflict";
730
+ case 413:
731
+ return "payload_too_large";
732
+ case 429:
733
+ return "rate_limited";
734
+ default:
735
+ return status >= 500 ? "server_error" : "http_error";
736
+ }
737
+ }
738
+ function errorDetails(body) {
739
+ if (body && typeof body === "object" && "details" in body) {
740
+ const details = body.details;
741
+ if (details && typeof details === "object" && !Array.isArray(details)) {
742
+ return details;
743
+ }
744
+ }
745
+ return void 0;
746
+ }
747
+ function toErrorEnvelope(error) {
748
+ if (error instanceof ActionsApiError) {
749
+ const details = errorDetails(error.body);
750
+ return {
751
+ ok: false,
752
+ error: {
753
+ code: codeForStatus(error.status),
754
+ message: error.message,
755
+ httpStatus: error.status,
756
+ retryable: error.status === 429 || error.status === 502 || error.status === 503 || error.status === 504,
757
+ ...details !== void 0 && { details }
758
+ }
759
+ };
760
+ }
761
+ if (error instanceof ActionsCliError) {
762
+ return { ok: false, error: { code: error.code, message: error.message, retryable: false } };
763
+ }
764
+ if (error instanceof TypeError && /fetch failed/i.test(error.message)) {
765
+ const cause = error.cause;
766
+ const detail = cause instanceof Error ? `: ${cause.message}` : "";
767
+ return {
768
+ ok: false,
769
+ error: { code: "network", message: `${error.message}${detail}`, retryable: true }
770
+ };
771
+ }
772
+ return {
773
+ ok: false,
774
+ error: {
775
+ code: "unknown",
776
+ message: error instanceof Error ? error.message : String(error),
777
+ retryable: false
778
+ }
779
+ };
780
+ }
711
781
  var ActionsClient = class {
712
782
  constructor(auth, fetchImpl = fetch) {
713
783
  this.auth = auth;
@@ -834,22 +904,64 @@ function encodeWatchCursor(id, jobId, step, offset) {
834
904
  const stepPart = step === null ? "-" : step === "runner" ? "runner" : String(step);
835
905
  return `${id}:${jobId}:${stepPart}:${offset}`;
836
906
  }
837
- function resolveActionsAuth(opts) {
838
- const apiKey = opts.apiKey || process.env.COMPUTE_API_KEY || process.env.BENCHMARKS_PLATFORM_API_KEY;
839
- if (!apiKey) {
840
- throw new Error(
841
- "No API key. Set COMPUTE_API_KEY or pass --api-key."
842
- );
843
- }
907
+ var NO_CREDENTIALS_HINT = "Set COMPUTE_API_KEY, pass --api-key, or run `compute bench auth login`.";
908
+ async function resolveActionsAuth(opts, resolveStored = resolveStoredPlatformAuth) {
844
909
  const baseUrl = (opts.baseUrl || process.env.COMPUTE_PLATFORM_URL || process.env.BENCHMARKS_PLATFORM_URL || // legacy name
845
910
  DEFAULT_BASE_URL).replace(/\/+$/, "");
846
911
  if (!opts.allowUntrustedHost && !isTrustedActionsHost(baseUrl)) {
847
- throw new Error(
912
+ throw new ActionsCliError(
913
+ "untrusted_host",
848
914
  `Refusing to send the API key to ${baseUrl} \u2014 it is not a computesdk.com or localhost host. If this is a self-hosted/dev deployment you trust, pass --allow-untrusted-host.`
849
915
  );
850
916
  }
917
+ if (!isSecureActionsTransport(baseUrl)) {
918
+ throw new ActionsCliError(
919
+ "insecure_transport",
920
+ `Refusing to send the API key over plaintext HTTP to ${baseUrl}. Use an https:// base URL; http:// is only allowed for localhost/loopback.`
921
+ );
922
+ }
923
+ let apiKey = opts.apiKey || process.env.COMPUTE_API_KEY || process.env.BENCHMARKS_PLATFORM_API_KEY || // legacy name
924
+ void 0;
925
+ if (!apiKey) {
926
+ if (!isTrustedActionsHost(baseUrl)) {
927
+ throw new ActionsCliError(
928
+ "untrusted_host_stored_auth",
929
+ `Refusing to use stored platform credentials with untrusted host ${baseUrl}. Pass --api-key or set COMPUTE_API_KEY to use an explicit key with --allow-untrusted-host.`
930
+ );
931
+ }
932
+ let stored;
933
+ try {
934
+ stored = await resolveStored({ baseUrl });
935
+ } catch (error) {
936
+ const reason = error instanceof Error ? error.message : String(error);
937
+ const detail = reason.startsWith("No credentials found") ? "No API key." : reason;
938
+ throw new ActionsCliError("no_credentials", `${detail} ${NO_CREDENTIALS_HINT}`);
939
+ }
940
+ apiKey = stored.apiKey || stored.token || void 0;
941
+ }
942
+ if (!apiKey) {
943
+ throw new ActionsCliError("no_credentials", `No API key. ${NO_CREDENTIALS_HINT}`);
944
+ }
851
945
  return { apiKey, baseUrl };
852
946
  }
947
+ async function resolveStoredPlatformAuth(opts) {
948
+ const { resolveAuth } = await import("@benchsdk/cli");
949
+ const auth = await resolveAuth({ baseUrl: opts.baseUrl });
950
+ return { apiKey: auth.apiKey, token: auth.token };
951
+ }
952
+ function isLoopbackHost(host) {
953
+ return host === "localhost" || host === "127.0.0.1" || host === "[::1]" || host === "::1";
954
+ }
955
+ function isSecureActionsTransport(baseUrl) {
956
+ let url;
957
+ try {
958
+ url = new URL(baseUrl);
959
+ } catch {
960
+ return false;
961
+ }
962
+ if (url.protocol === "https:") return true;
963
+ return url.protocol === "http:" && isLoopbackHost(url.hostname);
964
+ }
853
965
  function isTrustedActionsHost(baseUrl) {
854
966
  let host;
855
967
  try {
@@ -857,7 +969,7 @@ function isTrustedActionsHost(baseUrl) {
857
969
  } catch {
858
970
  return false;
859
971
  }
860
- return host === "computesdk.com" || host.endsWith(".computesdk.com") || host === "localhost" || host === "127.0.0.1" || host === "::1";
972
+ return host === "computesdk.com" || host.endsWith(".computesdk.com") || isLoopbackHost(host);
861
973
  }
862
974
 
863
975
  // src/actions.ts
@@ -866,8 +978,21 @@ function sanitizePathPart(name) {
866
978
  if (base === "" || base === "." || base === "..") return "artifact";
867
979
  return base;
868
980
  }
869
- function client(opts) {
870
- return new ActionsClient(resolveActionsAuth(opts));
981
+ async function client(opts) {
982
+ return new ActionsClient(await resolveActionsAuth(opts));
983
+ }
984
+ function assertVaultValueHost(auth) {
985
+ if (!isTrustedActionsHost(auth.baseUrl)) {
986
+ throw new ActionsCliError(
987
+ "untrusted_host",
988
+ `Refusing to send or read vault values at ${auth.baseUrl} \u2014 only computesdk.com and localhost hosts are allowed, even with --allow-untrusted-host.`
989
+ );
990
+ }
991
+ }
992
+ async function vaultValueClient(opts) {
993
+ const auth = await resolveActionsAuth(opts);
994
+ assertVaultValueHost(auth);
995
+ return new ActionsClient(auth);
871
996
  }
872
997
  function output(opts, data, render) {
873
998
  if (opts.json) {
@@ -912,14 +1037,24 @@ async function fetchRunSummary(c, runId) {
912
1037
  return null;
913
1038
  }
914
1039
  }
915
- function fail(error) {
916
- if (error instanceof ActionsApiError) {
1040
+ function fail(error, opts = {}) {
1041
+ if (opts.json) {
1042
+ process.stderr.write(JSON.stringify(toErrorEnvelope(error)) + "\n");
1043
+ } else if (error instanceof ActionsApiError) {
917
1044
  console.error(pc.red(`Error (${error.status}): ${error.message}`));
918
1045
  } else {
919
- console.error(pc.red(`Error: ${error.message}`));
1046
+ console.error(pc.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
920
1047
  }
921
1048
  process.exit(1);
922
1049
  }
1050
+ function usageErrorOutput(str, write, argv = process.argv) {
1051
+ if (argv.includes("--json")) {
1052
+ const message = str.replace(/^error:\s*/i, "").trim();
1053
+ write(JSON.stringify(toErrorEnvelope(new ActionsCliError("invalid_argument", message))) + "\n");
1054
+ } else {
1055
+ write(str);
1056
+ }
1057
+ }
923
1058
  function formatDuration(ms) {
924
1059
  if (ms === null || ms === void 0) return "-";
925
1060
  if (ms < 1e3) return `${ms}ms`;
@@ -1085,7 +1220,7 @@ function parseInputs(pairs) {
1085
1220
  for (const pair of pairs ?? []) {
1086
1221
  const eq = pair.indexOf("=");
1087
1222
  if (eq === -1) {
1088
- throw new Error(`Invalid --inputs entry "${pair}". Expected key=value.`);
1223
+ throw new ActionsCliError("invalid_argument", `Invalid --inputs entry "${pair}". Expected key=value.`);
1089
1224
  }
1090
1225
  inputs[pair.slice(0, eq)] = pair.slice(eq + 1);
1091
1226
  }
@@ -1135,7 +1270,7 @@ function parseStep(step) {
1135
1270
  if (step === "runner") return "runner";
1136
1271
  const n = Number(step);
1137
1272
  if (!Number.isInteger(n) || n < 0) {
1138
- throw new Error(`Invalid --step "${step}". Expected a step ordinal or "runner".`);
1273
+ throw new ActionsCliError("invalid_argument", `Invalid --step "${step}". Expected a step ordinal or "runner".`);
1139
1274
  }
1140
1275
  return n;
1141
1276
  }
@@ -1233,14 +1368,79 @@ function printDiscovery(d) {
1233
1368
  }
1234
1369
  console.log(pc.dim(`discovery: ${d.workflowsFound ? "workflows found" : "no workflows yet"}${d.seeded ? ", cursors seeded" : ""}`));
1235
1370
  }
1371
+ function dispatchBody(workflow, opts) {
1372
+ if (!workflow.dispatchable && !opts.manual) {
1373
+ throw new ActionsCliError(
1374
+ "invalid_argument",
1375
+ `Workflow "${workflow.path}" does not declare workflow_dispatch. Pass --manual to run it anyway (no inputs).`
1376
+ );
1377
+ }
1378
+ if (!workflow.dispatchable && opts.inputs !== void 0 && opts.inputs.length > 0) {
1379
+ throw new ActionsCliError(
1380
+ "invalid_argument",
1381
+ `Workflow "${workflow.path}" has no workflow_dispatch inputs; --manual runs of it take no --inputs.`
1382
+ );
1383
+ }
1384
+ const ref = opts.ref ?? workflow.refs[0];
1385
+ if (!ref) throw new ActionsCliError("invalid_argument", "No --ref given and the workflow has no watched refs.");
1386
+ if (opts.providerRegion !== void 0 && opts.provider === void 0) {
1387
+ throw new ActionsCliError("invalid_argument", "--provider-region requires --provider.");
1388
+ }
1389
+ return {
1390
+ workflowId: workflow.id,
1391
+ ref,
1392
+ inputs: parseInputs(opts.inputs),
1393
+ ...opts.manual && { manual: true },
1394
+ ...opts.provider !== void 0 && {
1395
+ provider: opts.provider,
1396
+ ...opts.providerRegion !== void 0 && { providerRegion: opts.providerRegion }
1397
+ }
1398
+ };
1399
+ }
1400
+ function parseVaultKind(kind) {
1401
+ if (kind === void 0 || kind === "secret" || kind === "variable") return kind ?? "secret";
1402
+ throw new ActionsCliError("invalid_argument", `--kind must be secret or variable, got "${kind}".`);
1403
+ }
1404
+ function vaultPath(path5, params) {
1405
+ const search = new URLSearchParams();
1406
+ for (const [key, value] of Object.entries(params)) {
1407
+ if (value !== void 0) search.set(key, value);
1408
+ }
1409
+ const query = search.toString();
1410
+ return query ? `${path5}?${query}` : path5;
1411
+ }
1412
+ function vaultSetBody(name, value, opts) {
1413
+ if (value === "") throw new ActionsCliError("invalid_argument", "The value is empty.");
1414
+ if (opts.revealable && opts.kind === "variable") {
1415
+ throw new ActionsCliError("invalid_argument", "--revealable applies to secrets; variables are always readable.");
1416
+ }
1417
+ return {
1418
+ name,
1419
+ kind: opts.kind,
1420
+ value,
1421
+ ...opts.revealable && { revealable: true },
1422
+ ...opts.description !== void 0 && { description: opts.description },
1423
+ ...opts.labels !== void 0 && opts.labels.length > 0 && { labels: opts.labels }
1424
+ };
1425
+ }
1426
+ function formatVaultRow(item) {
1427
+ const flags = [
1428
+ item.kind,
1429
+ ...item.revealable && item.kind === "secret" ? ["revealable"] : [],
1430
+ `v${item.version}`,
1431
+ ...item.source === "organization" ? ["inherited"] : [],
1432
+ ...item.overridesOrganization ? ["overrides org"] : []
1433
+ ];
1434
+ return `${pc.cyan(safeTerm(item.name))} ${pc.dim(flags.join(", "))} ${pc.dim(`updated ${safeTerm(item.updatedAt)}`)}`;
1435
+ }
1236
1436
  function registerActionsCommands(program2) {
1237
- const actions = program2.command("actions").alias("ci").description("Drive the benchmarks-platform Actions API");
1238
- const common = (cmd) => cmd.option("--api-key <key>", "API key (default: $COMPUTE_API_KEY)").option("--base-url <url>", "API base URL (default: https://platform.computesdk.com)").option("--allow-untrusted-host", "send the API key to a non-computesdk, non-localhost --base-url").option("--json", "print machine-readable JSON");
1437
+ const actions = program2.command("actions").alias("ci").description("Drive the benchmarks-platform Actions API").configureOutput({ outputError: usageErrorOutput });
1438
+ const common = (cmd) => cmd.option("--api-key <key>", "API key (default: $COMPUTE_API_KEY)").option("--base-url <url>", "API base URL (default: https://platform.computesdk.com)").option("--allow-untrusted-host", "send an explicit --api-key/env key to a non-computesdk, non-localhost --base-url (stored login credentials are never sent)").option("--json", "print machine-readable JSON");
1239
1439
  common(
1240
- actions.command("dispatch").description("Dispatch a workflow_dispatch run").argument("<repo>", "repository in owner/repo format").requiredOption("--workflow <path|name>", "workflow path or name").option("--ref <ref>", "git ref to run (default: the workflow's first watched ref)").option("--inputs <pairs...>", "workflow inputs as key=value").option("--provider <id>", "place the run on one provider (e.g. namespace, vercel:sfo1) instead of the org provider order").option("--provider-region <region>", "region for --provider (same as --provider <id>:<region>)")
1440
+ actions.command("dispatch").description("Dispatch a workflow_dispatch run (or any workflow with --manual)").argument("<repo>", "repository in owner/repo format").requiredOption("--workflow <path|name>", "workflow path or name").option("--ref <ref>", "git ref to run (default: the workflow's first watched ref)").option("--inputs <pairs...>", "workflow inputs as key=value (workflow_dispatch inputs only)").option("--manual", "run a workflow even if it does not declare workflow_dispatch (such runs take no --inputs)").option("--provider <id>", "place the run on one provider (e.g. namespace, vercel:sfo1) instead of the org provider order").option("--provider-region <region>", "region for --provider (same as --provider <id>:<region>)")
1241
1441
  ).action(async (repo, opts) => {
1242
1442
  try {
1243
- const c = client(opts);
1443
+ const c = await client(opts);
1244
1444
  const { workflows } = await c.get(
1245
1445
  "/api/v1/actions/workflows",
1246
1446
  { repo }
@@ -1248,26 +1448,15 @@ function registerActionsCommands(program2) {
1248
1448
  const workflow = matchWorkflow(workflows, opts.workflow);
1249
1449
  if (!workflow) {
1250
1450
  const choices = workflows.map((w) => `${w.path} (${w.name})`).join(", ") || "none";
1251
- throw new Error(
1252
- `No dispatchable workflow "${opts.workflow}" in ${repo}. Available: ${choices}`
1451
+ throw new ActionsCliError(
1452
+ "workflow_not_found",
1453
+ `No workflow "${opts.workflow}" in ${repo}. Available: ${choices}`
1253
1454
  );
1254
1455
  }
1255
- const ref = opts.ref ?? workflow.refs[0];
1256
- if (!ref) throw new Error("No --ref given and the workflow has no watched refs.");
1257
- if (opts.providerRegion !== void 0 && opts.provider === void 0) {
1258
- throw new Error("--provider-region requires --provider.");
1259
- }
1456
+ const body = dispatchBody(workflow, opts);
1260
1457
  const result = await c.post(
1261
1458
  "/api/v1/actions/dispatch",
1262
- {
1263
- workflowId: workflow.id,
1264
- ref,
1265
- inputs: parseInputs(opts.inputs),
1266
- ...opts.provider !== void 0 && {
1267
- provider: opts.provider,
1268
- ...opts.providerRegion !== void 0 && { providerRegion: opts.providerRegion }
1269
- }
1270
- }
1459
+ body
1271
1460
  );
1272
1461
  const org = await c.org();
1273
1462
  const url = `${c.baseUrl}/${org.slug}/actions/runs/${result.runId}`;
@@ -1277,14 +1466,14 @@ function registerActionsCommands(program2) {
1277
1466
  console.log(`url: ${r.url}`);
1278
1467
  });
1279
1468
  } catch (e) {
1280
- fail(e);
1469
+ fail(e, opts);
1281
1470
  }
1282
1471
  });
1283
1472
  common(
1284
1473
  actions.command("runs").description("List recent runs for a repo").argument("<repo>", "repository in owner/repo format").option("--status <status...>", "filter by conclusion: passed, failed, running, cancelled, none").option("--branch <branch...>", "filter by branch/ref")
1285
1474
  ).action(async (repo, opts) => {
1286
1475
  try {
1287
- const { runs } = await client(opts).get(
1476
+ const { runs } = await (await client(opts)).get(
1288
1477
  "/api/v1/actions/runs",
1289
1478
  { repo, status: opts.status, branch: opts.branch }
1290
1479
  );
@@ -1296,14 +1485,14 @@ function registerActionsCommands(program2) {
1296
1485
  for (const run of rs) console.log(formatRunRow(run));
1297
1486
  });
1298
1487
  } catch (e) {
1299
- fail(e);
1488
+ fail(e, opts);
1300
1489
  }
1301
1490
  });
1302
1491
  common(
1303
1492
  actions.command("history").description("Recent run history for a workflow \u2014 per-job/step failure rates (is this failure flaky?)").argument("<repo>", "repository in owner/repo format").requiredOption("--workflow <path|name>", "workflow path or name").option("--branch <branch...>", "narrow the window to branches/refs").option("--job <job>", "narrow the rollup to one job name or workflow job id").option("--limit <n>", "runs in the window (default 10, max 50)")
1304
1493
  ).action(async (repo, opts) => {
1305
1494
  try {
1306
- const c = client(opts);
1495
+ const c = await client(opts);
1307
1496
  const { workflows } = await c.get(
1308
1497
  "/api/v1/actions/workflows",
1309
1498
  { repo }
@@ -1319,14 +1508,14 @@ function registerActionsCommands(program2) {
1319
1508
  });
1320
1509
  output(opts, history, (h) => console.log(formatRunHistory(h)));
1321
1510
  } catch (e) {
1322
- fail(e);
1511
+ fail(e, opts);
1323
1512
  }
1324
1513
  });
1325
1514
  common(
1326
1515
  actions.command("run").description("Show a run: jobs, provider:region placement, placement attempts").argument("<run-id>", "run ID")
1327
1516
  ).action(async (runId, opts) => {
1328
1517
  try {
1329
- const c = client(opts);
1518
+ const c = await client(opts);
1330
1519
  const run = await c.get(`/api/v1/actions/runs/${runId}`);
1331
1520
  const org = await c.org();
1332
1521
  const url = `${c.baseUrl}/${org.slug}/actions/runs/${run.id}`;
@@ -1339,37 +1528,37 @@ function registerActionsCommands(program2) {
1339
1528
  }
1340
1529
  });
1341
1530
  } catch (e) {
1342
- fail(e);
1531
+ fail(e, opts);
1343
1532
  }
1344
1533
  });
1345
1534
  common(
1346
1535
  actions.command("summary").description("Show a run's failure digest: failed jobs, failed steps, bounded log tails").argument("<run-id>", "run ID")
1347
1536
  ).action(async (runId, opts) => {
1348
1537
  try {
1349
- const summary = await client(opts).get(`/api/v1/actions/runs/${runId}/summary`);
1538
+ const summary = await (await client(opts)).get(`/api/v1/actions/runs/${runId}/summary`);
1350
1539
  output(opts, summary, (s) => console.log(formatRunSummary(s)));
1351
1540
  } catch (e) {
1352
- fail(e);
1541
+ fail(e, opts);
1353
1542
  }
1354
1543
  });
1355
1544
  common(
1356
1545
  actions.command("inspect").description("Inspect a run: resolved runner image, caches, secret names, concurrency, placement context").argument("<run-id>", "run ID")
1357
1546
  ).action(async (runId, opts) => {
1358
1547
  try {
1359
- const c = client(opts);
1548
+ const c = await client(opts);
1360
1549
  const inspection = await c.get(
1361
1550
  `/api/v1/actions/runs/${runId}/state`
1362
1551
  );
1363
1552
  output(opts, inspection, (r) => console.log(formatRunInspection(r)));
1364
1553
  } catch (e) {
1365
- fail(e);
1554
+ fail(e, opts);
1366
1555
  }
1367
1556
  });
1368
1557
  common(
1369
1558
  actions.command("logs").description("Print or follow a run's job logs").argument("<run-id>", "run ID").option("--job <job>", "job ID or name (default: all jobs)").option("--step <step>", 'step ordinal or "runner" (default: whole job)').option("--follow", "stream logs while the run is live")
1370
1559
  ).action(async (runId, opts) => {
1371
1560
  try {
1372
- const c = client(opts);
1561
+ const c = await client(opts);
1373
1562
  let jobs = await listRunJobs(c, runId);
1374
1563
  if (opts.job) {
1375
1564
  const job = matchJob(jobs, opts.job);
@@ -1420,25 +1609,25 @@ function registerActionsCommands(program2) {
1420
1609
  }
1421
1610
  }
1422
1611
  } catch (e) {
1423
- fail(e);
1612
+ fail(e, opts);
1424
1613
  }
1425
1614
  });
1426
1615
  const providers = actions.command("providers").description("List the org's registered compute providers (regions, act/usable status)");
1427
1616
  common(providers).action(async (opts) => {
1428
1617
  try {
1429
- const data = await client(opts).get("/api/v1/actions/providers");
1618
+ const data = await (await client(opts)).get("/api/v1/actions/providers");
1430
1619
  output(opts, data, (d) => {
1431
1620
  for (const p5 of d.providers) console.log(formatProviderRow(p5));
1432
1621
  });
1433
1622
  } catch (e) {
1434
- fail(e);
1623
+ fail(e, opts);
1435
1624
  }
1436
1625
  });
1437
1626
  common(
1438
1627
  providers.command("configure").description("Save the org's credential for a provider (owner/admin API key)").argument("<provider>", "provider id (e.g. tensorlake, blaxel)").option("--key <key>", "provider API key (single-field credentials)").option("--field <pair...>", "credential fields as name=value (multi-field providers)").option("--verify", "run the placement probe after saving")
1439
1628
  ).action(async (provider, opts) => {
1440
1629
  try {
1441
- const c = client(opts);
1630
+ const c = await client(opts);
1442
1631
  let body;
1443
1632
  if (opts.field !== void 0) {
1444
1633
  if (opts.key !== void 0) throw new Error("Pass either --key or --field, not both.");
@@ -1461,45 +1650,45 @@ function registerActionsCommands(program2) {
1461
1650
  });
1462
1651
  if (verification?.verified === false) process.exitCode = 1;
1463
1652
  } catch (e) {
1464
- fail(e);
1653
+ fail(e, opts);
1465
1654
  }
1466
1655
  });
1467
1656
  common(
1468
1657
  providers.command("verify").description('Probe the stored provider key with a real sandbox ("Test" in Settings)').argument("<provider>", "provider id")
1469
1658
  ).action(async (provider, opts) => {
1470
1659
  try {
1471
- const result = await client(opts).post(
1660
+ const result = await (await client(opts)).post(
1472
1661
  `/api/v1/actions/providers/${provider}/verify`
1473
1662
  );
1474
1663
  output(opts, result, (r) => console.log(formatVerifyResult(r)));
1475
1664
  if (result.verified === false) process.exitCode = 1;
1476
1665
  } catch (e) {
1477
- fail(e);
1666
+ fail(e, opts);
1478
1667
  }
1479
1668
  });
1480
1669
  common(
1481
1670
  providers.command("remove").description("Delete the org's stored credential for a provider").argument("<provider>", "provider id")
1482
1671
  ).action(async (provider, opts) => {
1483
1672
  try {
1484
- const result = await client(opts).del(
1673
+ const result = await (await client(opts)).del(
1485
1674
  `/api/v1/actions/providers/${provider}/key`
1486
1675
  );
1487
1676
  output(opts, result, (r) => {
1488
1677
  console.log(r.deleted ? `deleted ${r.provider}` : `no key stored for ${r.provider}`);
1489
1678
  });
1490
1679
  } catch (e) {
1491
- fail(e);
1680
+ fail(e, opts);
1492
1681
  }
1493
1682
  });
1494
1683
  const repos = actions.command("repos").description("List the org's connected repositories");
1495
1684
  common(repos).action(async (opts) => {
1496
1685
  try {
1497
- const data = await client(opts).get("/api/v1/actions/repos");
1686
+ const data = await (await client(opts)).get("/api/v1/actions/repos");
1498
1687
  output(opts, data, (d) => {
1499
1688
  for (const r of d.repos) console.log(formatRepoRow(r));
1500
1689
  });
1501
1690
  } catch (e) {
1502
- fail(e);
1691
+ fail(e, opts);
1503
1692
  }
1504
1693
  });
1505
1694
  common(
@@ -1529,7 +1718,7 @@ function registerActionsCommands(program2) {
1529
1718
  if (opts.knownHostsFile !== void 0 && opts.sshKeyFile === void 0) {
1530
1719
  throw new Error("--known-hosts-file requires --ssh-key-file.");
1531
1720
  }
1532
- const result = await client(opts).post("/api/v1/actions/repos", {
1721
+ const result = await (await client(opts)).post("/api/v1/actions/repos", {
1533
1722
  cloneUrl,
1534
1723
  ...body,
1535
1724
  ...opts.name !== void 0 && { name: opts.name },
@@ -1540,14 +1729,14 @@ function registerActionsCommands(program2) {
1540
1729
  printDiscovery(r.discovery);
1541
1730
  });
1542
1731
  } catch (e) {
1543
- fail(e);
1732
+ fail(e, opts);
1544
1733
  }
1545
1734
  });
1546
1735
  common(
1547
1736
  repos.command("enable").description("Enable a repo the org can already see (granted by a GitHub App install, or a connected remote)").argument("<repo>", "repository in owner/repo format").option("--repo-id <id>", "scope to one row when two installations grant the same name")
1548
1737
  ).action(async (repo, opts) => {
1549
1738
  try {
1550
- const result = await client(opts).patch("/api/v1/actions/repos", {
1739
+ const result = await (await client(opts)).patch("/api/v1/actions/repos", {
1551
1740
  fullName: repo,
1552
1741
  enabled: true,
1553
1742
  ...opts.repoId !== void 0 && { repoId: opts.repoId }
@@ -1557,42 +1746,110 @@ function registerActionsCommands(program2) {
1557
1746
  if (r.discovery) printDiscovery(r.discovery);
1558
1747
  });
1559
1748
  } catch (e) {
1560
- fail(e);
1749
+ fail(e, opts);
1561
1750
  }
1562
1751
  });
1563
1752
  common(
1564
1753
  repos.command("disable").description("Disable a repo \u2014 it stays connected but no runs are scheduled").argument("<repo>", "repository in owner/repo format").option("--repo-id <id>", "scope to one row when two installations grant the same name")
1565
1754
  ).action(async (repo, opts) => {
1566
1755
  try {
1567
- const result = await client(opts).patch("/api/v1/actions/repos", {
1756
+ const result = await (await client(opts)).patch("/api/v1/actions/repos", {
1568
1757
  fullName: repo,
1569
1758
  enabled: false,
1570
1759
  ...opts.repoId !== void 0 && { repoId: opts.repoId }
1571
1760
  });
1572
1761
  output(opts, result, (r) => console.log(`disabled ${pc.cyan(safeTerm(r.fullName))}`));
1573
1762
  } catch (e) {
1574
- fail(e);
1763
+ fail(e, opts);
1764
+ }
1765
+ });
1766
+ const vault = actions.command("vault").description("The org vault: secrets and variables (owner/admin key)");
1767
+ const vaultScope = (cmd) => common(cmd).option("--repo <owner/repo>", "scope to a repo's own items instead of the org's").option("--kind <kind>", "secret or variable", "secret");
1768
+ vaultScope(
1769
+ vault.command("ls").description("List names and metadata (never values)")
1770
+ ).action(async (opts) => {
1771
+ try {
1772
+ const path5 = vaultPath("/api/v1/vault", { repo: opts.repo, kind: parseVaultKind(opts.kind) });
1773
+ const data = await (await client(opts)).get(path5);
1774
+ output(opts, data, (d) => {
1775
+ for (const item of d.items) console.log(formatVaultRow(item));
1776
+ });
1777
+ } catch (e) {
1778
+ fail(e, opts);
1779
+ }
1780
+ });
1781
+ vaultScope(
1782
+ vault.command("set").description("Store a value, read from stdin (or --from-file) and sent exactly as read").argument("<name>", "item name, e.g. NPM_TOKEN").option("--from-file <path>", "read the value from a file instead of stdin").option("--revealable", "let the value be read back with `vault get` (secrets; fixed at creation)").option("--description <text>", "what the item is for").option("--labels <labels...>", "labels (fixed at creation)")
1783
+ ).action(async (name, opts) => {
1784
+ try {
1785
+ if (opts.fromFile === void 0 && process.stdin.isTTY) {
1786
+ throw new ActionsCliError(
1787
+ "invalid_argument",
1788
+ 'Pipe the value on stdin (e.g. printf %s "$VALUE" | compute actions vault set NAME) or pass --from-file.'
1789
+ );
1790
+ }
1791
+ const value = readFileSync(opts.fromFile ?? 0, "utf8");
1792
+ const kind = parseVaultKind(opts.kind);
1793
+ const body = vaultSetBody(name, value, { ...opts, kind });
1794
+ const result = await (await vaultValueClient(opts)).put(
1795
+ vaultPath("/api/v1/vault", { repo: opts.repo }),
1796
+ body
1797
+ );
1798
+ output(opts, result, (r) => {
1799
+ const where = opts.repo ? safeTerm(opts.repo) : "org";
1800
+ console.log(`saved ${pc.cyan(safeTerm(r.item.name))} ${pc.dim(`(${r.item.kind}, v${r.item.version}, ${where})`)}`);
1801
+ if (/\r?\n$/.test(value)) {
1802
+ console.error(pc.yellow("note: the stored value ends with a newline; use printf %s rather than echo to avoid one."));
1803
+ }
1804
+ });
1805
+ } catch (e) {
1806
+ fail(e, opts);
1807
+ }
1808
+ });
1809
+ vaultScope(
1810
+ vault.command("get").description("Print a value: any variable, or a secret created --revealable").argument("<name>", "item name")
1811
+ ).action(async (name, opts) => {
1812
+ try {
1813
+ const kind = parseVaultKind(opts.kind);
1814
+ const result = await (await vaultValueClient(opts)).post(
1815
+ vaultPath("/api/v1/vault/reveal", { repo: opts.repo }),
1816
+ { name, kind }
1817
+ );
1818
+ output(opts, result, (r) => process.stdout.write(r.value));
1819
+ } catch (e) {
1820
+ fail(e, opts);
1821
+ }
1822
+ });
1823
+ vaultScope(
1824
+ vault.command("rm").description("Delete an item (with --repo, only the repo's override)").argument("<name>", "item name")
1825
+ ).action(async (name, opts) => {
1826
+ try {
1827
+ const path5 = vaultPath("/api/v1/vault", { repo: opts.repo, kind: parseVaultKind(opts.kind), name });
1828
+ const result = await (await client(opts)).del(path5);
1829
+ output(opts, result, (r) => console.log(`deleted ${pc.cyan(safeTerm(r.name))} ${pc.dim(`(${r.kind})`)}`));
1830
+ } catch (e) {
1831
+ fail(e, opts);
1575
1832
  }
1576
1833
  });
1577
1834
  common(
1578
1835
  actions.command("cancel").description("Cancel a run").argument("<run-id>", "run ID")
1579
1836
  ).action(async (runId, opts) => {
1580
1837
  try {
1581
- const result = await client(opts).post(
1838
+ const result = await (await client(opts)).post(
1582
1839
  `/api/v1/actions/runs/${runId}/cancel`
1583
1840
  );
1584
1841
  output(opts, result, (r) => {
1585
1842
  console.log(r.cancelled ? `Cancelled ${runId}` : `Run ${runId} was already finished`);
1586
1843
  });
1587
1844
  } catch (e) {
1588
- fail(e);
1845
+ fail(e, opts);
1589
1846
  }
1590
1847
  });
1591
1848
  common(
1592
1849
  actions.command("rerun").description("Re-run a finished run at the same commit").argument("<run-id>", "run ID")
1593
1850
  ).action(async (runId, opts) => {
1594
1851
  try {
1595
- const c = client(opts);
1852
+ const c = await client(opts);
1596
1853
  const result = await c.post(
1597
1854
  `/api/v1/actions/runs/${runId}/rerun`
1598
1855
  );
@@ -1603,14 +1860,14 @@ function registerActionsCommands(program2) {
1603
1860
  console.log(`url: ${r.url}`);
1604
1861
  });
1605
1862
  } catch (e) {
1606
- fail(e);
1863
+ fail(e, opts);
1607
1864
  }
1608
1865
  });
1609
1866
  common(
1610
1867
  actions.command("artifacts").description("List or download a run's artifacts").argument("<run-id>", "run ID").option("--job <job>", "job ID or name (default: all jobs)").option("--out <dir>", "download artifacts into this directory instead of listing")
1611
1868
  ).action(async (runId, opts) => {
1612
1869
  try {
1613
- const c = client(opts);
1870
+ const c = await client(opts);
1614
1871
  let jobs = await listRunJobs(c, runId);
1615
1872
  if (opts.job) {
1616
1873
  const job = matchJob(jobs, opts.job);
@@ -1659,7 +1916,7 @@ function registerActionsCommands(program2) {
1659
1916
  if (!any) console.log("No artifacts.");
1660
1917
  });
1661
1918
  } catch (e) {
1662
- fail(e);
1919
+ fail(e, opts);
1663
1920
  }
1664
1921
  });
1665
1922
  }