@ornncompute/cli 1.0.72 → 1.0.97

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.
Files changed (59) hide show
  1. package/README.md +55 -24
  2. package/package.json +4 -2
  3. package/src/admin-capacity-reservations.mjs +6 -13
  4. package/src/admin-command-handlers.mjs +2 -1
  5. package/src/admin-credits.mjs +1 -2
  6. package/src/admin-enrollment.mjs +22 -30
  7. package/src/admin-mutations.mjs +6 -56
  8. package/src/admin-node-diagnostics.mjs +3 -41
  9. package/src/admin-node-lifecycle.mjs +40 -160
  10. package/src/admin-node-sanitize.mjs +243 -0
  11. package/src/admin-shell.mjs +39 -32
  12. package/src/catalog-shell.mjs +6 -6
  13. package/src/evidence-commands.mjs +4 -6
  14. package/src/external-bucket-commands.mjs +76 -96
  15. package/src/interaction-runtime.mjs +5 -37
  16. package/src/kubernetes-commands.mjs +25 -183
  17. package/src/launch-mode-handlers.mjs +0 -1
  18. package/src/machine-reference.mjs +33 -56
  19. package/src/mount-commands.mjs +25 -131
  20. package/src/slurm-commands.mjs +13 -2
  21. package/src/storage-commands.mjs +2 -87
  22. package/src/vm-commands.mjs +48 -101
  23. package/vendor/api-client/commerce.js +743 -0
  24. package/vendor/capabilities/cli.js +7688 -7
  25. package/vendor/capabilities/capabilities/admin-capacity-reservations.js +0 -101
  26. package/vendor/capabilities/capabilities/admin-enrollment.js +0 -321
  27. package/vendor/capabilities/capabilities/admin-node-diagnostics.js +0 -88
  28. package/vendor/capabilities/capabilities/admin-nodes-lifecycle.js +0 -235
  29. package/vendor/capabilities/capabilities/admin.js +0 -91
  30. package/vendor/capabilities/capabilities/cli.js +0 -156
  31. package/vendor/capabilities/capabilities/clusters.js +0 -248
  32. package/vendor/capabilities/capabilities/commerce-market.js +0 -405
  33. package/vendor/capabilities/capabilities/compute.js +0 -523
  34. package/vendor/capabilities/capabilities/credits.js +0 -78
  35. package/vendor/capabilities/capabilities/evidence.js +0 -145
  36. package/vendor/capabilities/capabilities/external-buckets.js +0 -191
  37. package/vendor/capabilities/capabilities/fleet.js +0 -306
  38. package/vendor/capabilities/capabilities/identity.js +0 -129
  39. package/vendor/capabilities/capabilities/listings.js +0 -398
  40. package/vendor/capabilities/capabilities/locality.js +0 -42
  41. package/vendor/capabilities/capabilities/market-shared.js +0 -46
  42. package/vendor/capabilities/capabilities/markets.js +0 -482
  43. package/vendor/capabilities/capabilities/mounts.js +0 -176
  44. package/vendor/capabilities/capabilities/native-listing.js +0 -99
  45. package/vendor/capabilities/capabilities/networks.js +0 -1264
  46. package/vendor/capabilities/capabilities/observability.js +0 -218
  47. package/vendor/capabilities/capabilities/organization-invites.js +0 -53
  48. package/vendor/capabilities/capabilities/reservation-list.js +0 -68
  49. package/vendor/capabilities/capabilities/staff-shared.js +0 -64
  50. package/vendor/capabilities/capabilities/tokens.js +0 -165
  51. package/vendor/capabilities/capabilities/users.js +0 -159
  52. package/vendor/capabilities/capabilities/volumes.js +0 -178
  53. package/vendor/capabilities/capability.js +0 -99
  54. package/vendor/capabilities/cli-catalog.js +0 -73
  55. package/vendor/capabilities/cli-migrations.js +0 -264
  56. package/vendor/capabilities/names.js +0 -6
  57. package/vendor/capabilities/software-catalog.js +0 -82
  58. package/vendor/capabilities/spec-catalog.js +0 -151
  59. package/vendor/capabilities/supplier-directory.js +0 -20
@@ -1,3 +1,4 @@
1
+ import { getReservation } from "../vendor/api-client/commerce.js";
1
2
  import { createHash } from "node:crypto";
2
3
 
3
4
  import {
@@ -8,7 +9,7 @@ import {
8
9
  import { isRecoverableWaitFailure } from "./interaction-runtime.mjs";
9
10
  import { waitForGoal as waitForReadGoal } from "./wait-runtime.mjs";
10
11
  import { gpuLabeler } from "./gpu-catalog.mjs";
11
- import { reservationLabeler } from "./machine-reference.mjs";
12
+ import { listActiveReservations, reservationLabeler } from "./machine-reference.mjs";
12
13
  import { keyInputShapePreflight, keyPlanFromInput, materializeKey } from "./vm-commands.mjs";
13
14
 
14
15
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
@@ -51,8 +52,7 @@ function clusterNativeAccess(accessMode) {
51
52
  return accessMode === "slurm" ? "SSH login" : "kubeconfig";
52
53
  }
53
54
  function reservationAccessMode(value) {
54
- const mode = cleanState(record(value).access_mode ?? record(value).accessMode);
55
- return mode === "virtual-machine" ? "vm" : mode;
55
+ return text(record(value).provisioningType);
56
56
  }
57
57
 
58
58
  function clusterConnectCommand(cluster) {
@@ -736,17 +736,13 @@ async function waitForClusterGoal({
736
736
  }
737
737
 
738
738
  async function fetchReservation(fetch, runtime, reservationId, accessMode = "kubernetes") {
739
- const reservation = record(
740
- await fetch(`/v1/workflows/commerce/reservations/${encodeURIComponent(reservationId)}`, {
741
- method: "GET",
742
- })
743
- );
744
- if (text(reservation.id) !== reservationId) {
739
+ const reservation = record(await getReservation(fetch, reservationId));
740
+ if (text(reservation.reservationId) !== reservationId) {
745
741
  runtime.error("The gateway returned a different capacity reservation.", {
746
742
  code: "reservation_identity_changed",
747
743
  });
748
744
  }
749
- const status = cleanState(reservation.status ?? reservation.state);
745
+ const status = cleanState(reservation.status);
750
746
  if (reservationAccessMode(reservation) !== accessMode) {
751
747
  runtime.error(
752
748
  `The selected capacity reservation is not reserved for ${clusterModeLabel(accessMode)}.`,
@@ -766,50 +762,6 @@ async function fetchReservation(fetch, runtime, reservationId, accessMode = "kub
766
762
  return reservation;
767
763
  }
768
764
 
769
- async function fetchActiveReservations(fetch, runtime) {
770
- const reservations = [];
771
- const seen = new Set();
772
- let cursor;
773
- for (let page = 0; page < 20; page += 1) {
774
- const query = new URLSearchParams({ limit: "500", state: "active" });
775
- if (cursor) query.set("cursor", cursor);
776
- const payload = await fetch(`/v1/workflows/commerce/reservations?${query}`, {
777
- method: "GET",
778
- });
779
- const source = record(payload);
780
- const rows = Array.isArray(payload)
781
- ? payload
782
- : Array.isArray(source.reservations)
783
- ? source.reservations
784
- : null;
785
- if (!rows) {
786
- runtime.error("Capacity reservations returned an unsafe response.", {
787
- code: "reservation_projection_invalid",
788
- });
789
- }
790
- reservations.push(
791
- ...rows.filter((value) => {
792
- const reservation = record(value);
793
- return (
794
- UUID.test(text(reservation.id) ?? "") &&
795
- cleanState(reservation.status ?? reservation.state) === "active"
796
- );
797
- })
798
- );
799
- const next = text(source.next_cursor);
800
- if (!next) return reservations;
801
- if (seen.has(next))
802
- runtime.error("Capacity-reservation pagination did not advance.", {
803
- code: "reservation_pagination_invalid",
804
- });
805
- seen.add(next);
806
- cursor = next;
807
- }
808
- runtime.error("Capacity-reservation pagination exceeded its safety bound.", {
809
- code: "reservation_pagination_invalid",
810
- });
811
- }
812
-
813
765
  async function resolveLaunchReservation(context, runtime, input, accessMode) {
814
766
  const positionals = input.$positionals ?? [];
815
767
  if (positionals.length > 1) {
@@ -838,7 +790,7 @@ async function resolveLaunchReservation(context, runtime, input, accessMode) {
838
790
  field: "reservation",
839
791
  });
840
792
  }
841
- const active = (await fetchActiveReservations(context.fetch, runtime)).filter(
793
+ const active = (await listActiveReservations(context, runtime)).filter(
842
794
  (reservation) => reservationAccessMode(reservation) === accessMode
843
795
  );
844
796
  if (active.length === 0) {
@@ -850,14 +802,14 @@ async function resolveLaunchReservation(context, runtime, input, accessMode) {
850
802
  );
851
803
  }
852
804
  if (active.length === 1) {
853
- return fetchReservation(context.fetch, runtime, text(record(active[0]).id), accessMode);
805
+ return fetchReservation(context.fetch, runtime, text(active[0].reservationId), accessMode);
854
806
  }
855
807
  const reservationLabel = await reservationLabeler(context.fetch);
856
808
  const selected = await runtime.ask({
857
809
  label: "Capacity reservation",
858
810
  choices: active.flatMap((value) => {
859
811
  const reservation = record(value);
860
- const id = text(reservation.id);
812
+ const id = text(reservation.reservationId);
861
813
  return id ? [{ label: reservationLabel(reservation), value: id }] : [];
862
814
  }),
863
815
  });
@@ -914,12 +866,6 @@ function clusterMachineLaunchPreflight(runtime, input, membershipCount) {
914
866
  });
915
867
  }
916
868
  if (runtime.eligibleHuman) return;
917
- if (input.yes !== true) {
918
- runtime.usage("--yes is required for automation after reviewing the exact intent.", {
919
- code: "consent_required",
920
- field: "yes",
921
- });
922
- }
923
869
  const references = requestedMachineReferences(input);
924
870
  if (membershipCount === null || references.length !== membershipCount) {
925
871
  runtime.usage("Automation requires --machines with exactly the --nodes membership count.", {
@@ -1130,7 +1076,7 @@ export async function launchClusterMode(
1130
1076
  const organization = await selectedOrganization(context, runtime);
1131
1077
  const keyPlan = accessMode === "slurm" ? await keyPlanFromInput(context, input, runtime) : null;
1132
1078
  const reservation = await resolveLaunchReservation(context, runtime, input, accessMode);
1133
- const reservationId = text(reservation.id);
1079
+ const reservationId = text(reservation.reservationId);
1134
1080
  const initialExisting = await existingReservationCluster(
1135
1081
  context.fetch,
1136
1082
  runtime,
@@ -1257,47 +1203,13 @@ export async function launchClusterMode(
1257
1203
  accessMode
1258
1204
  );
1259
1205
  if (eligibilitySignature(reviewEligibility) !== eligibilitySignature(initialEligibility)) {
1260
- runtime.error(`${label} eligibility changed before review; no cluster was created.`, {
1206
+ runtime.error(`${label} eligibility changed before launch; no cluster was created.`, {
1261
1207
  code: "cluster_eligibility_changed",
1262
1208
  });
1263
1209
  }
1264
- const confirmed = await runtime.review({
1265
- title: `Launch ${label} cluster`,
1266
- terms: [
1267
- { label: "Organization", value: `${organization.name} (${organization.id})` },
1268
- { label: "Capacity reservation", value: reservationId },
1269
- { label: "Access mode", value: label },
1270
- { label: "Network", value: networkMode },
1271
- ...(keyPlan
1272
- ? [
1273
- {
1274
- label: "SSH key",
1275
- value: `${text(reviewedKey.label) ?? "SSH key"} (${text(reviewedKey.fingerprint) ?? text(reviewedKey.id) ?? "new key"})`,
1276
- },
1277
- ]
1278
- : []),
1279
- { label: "Cluster membership", value: String(membershipCount) },
1280
- ...selected.map((machine, index) => ({
1281
- label: `Machine ${index + 1}`,
1282
- value: `${machine.hostname} (${machine.id})`,
1283
- })),
1284
- ...virtualMachineIds.map((virtualMachineId, index) => ({
1285
- label: `VM ${index + 1}`,
1286
- value: `${virtualMachineId} (SSH keys are set aside while it is in the cluster)`,
1287
- })),
1288
- ],
1289
- });
1290
- if (!confirmed) {
1291
- return runtime.result({
1292
- data: { created: false, requested },
1293
- exitCode: 1,
1294
- outcome: { class: "ended", phase: "declined" },
1295
- presentation: { lines: ["No cluster was created."] },
1296
- });
1297
- }
1298
1210
  const currentOrganization = await selectedOrganization(context, runtime);
1299
1211
  if (currentOrganization.id !== organization.id) {
1300
- runtime.error("The selected organization changed after review; no cluster was created.", {
1212
+ runtime.error("The selected organization changed before launch; no cluster was created.", {
1301
1213
  code: "organization_changed",
1302
1214
  exitCode: 2,
1303
1215
  });
@@ -1312,20 +1224,20 @@ export async function launchClusterMode(
1312
1224
  if (racedExisting) {
1313
1225
  const current = await fetchClusterDetail(context.fetch, runtime, racedExisting);
1314
1226
  if (current.network_mode && current.network_mode !== networkMode) {
1315
- runtime.error(`A different ${label} network goal was accepted after review.`, {
1227
+ runtime.error(`A different ${label} network goal was accepted before launch.`, {
1316
1228
  code: "cluster_launch_goal_conflict",
1317
1229
  });
1318
1230
  }
1319
1231
  const currentIds = memberIds(current);
1320
1232
  if (currentIds.length === 0) {
1321
1233
  if (current.lifecycle.phase !== "provisioning") {
1322
- runtime.error(`A non-recoverable empty ${label} cluster appeared after review.`, {
1234
+ runtime.error(`A non-recoverable empty ${label} cluster appeared before launch.`, {
1323
1235
  code: "cluster_launch_goal_conflict",
1324
1236
  });
1325
1237
  }
1326
1238
  } else {
1327
1239
  if (!sameMembers(currentIds, goalIds)) {
1328
- runtime.error(`A different ${label} membership goal was accepted after review.`, {
1240
+ runtime.error(`A different ${label} membership goal was accepted before launch.`, {
1329
1241
  code: "cluster_launch_goal_conflict",
1330
1242
  });
1331
1243
  }
@@ -1618,21 +1530,6 @@ async function addNode(input, deps) {
1618
1530
  if (!machine)
1619
1531
  deps.interaction.usage("Choose one exact eligible machine.", { code: "machine_not_eligible" });
1620
1532
  const goalIds = [...initialMembers, machine.id].sort();
1621
- const confirmed = await deps.interaction.review({
1622
- title: `Add ${clusterModeLabel(prepared.cluster.access_mode)} cluster node`,
1623
- terms: [
1624
- { label: "Cluster", value: prepared.cluster.id },
1625
- { label: "Machine", value: `${machine.hostname} (${machine.id})` },
1626
- { label: "Membership goal", value: String(goalIds.length) },
1627
- ],
1628
- });
1629
- if (!confirmed)
1630
- return deps.interaction.result({
1631
- data: { changed: false, cluster: prepared.cluster },
1632
- exitCode: 1,
1633
- outcome: { class: "ended", phase: "declined" },
1634
- presentation: { lines: ["Cluster membership was not changed."] },
1635
- });
1636
1533
  const current = await fetchClusterDetail(deps.fetch, deps.interaction, prepared.cluster);
1637
1534
  const currentIds = memberIds(current);
1638
1535
  if (sameMembers(currentIds, goalIds)) {
@@ -1643,7 +1540,7 @@ async function addNode(input, deps) {
1643
1540
  });
1644
1541
  }
1645
1542
  if (!sameMembers(currentIds, initialMembers)) {
1646
- deps.interaction.error("Cluster membership changed after review; no node was added.", {
1543
+ deps.interaction.error("Cluster membership changed before adding the node.", {
1647
1544
  code: "cluster_membership_changed",
1648
1545
  });
1649
1546
  }
@@ -1654,7 +1551,7 @@ async function addNode(input, deps) {
1654
1551
  current.access_mode
1655
1552
  );
1656
1553
  if (eligibilitySignature(currentEligibility) !== eligibilitySignature(initialEligibility)) {
1657
- deps.interaction.error("Eligible machines changed after review; no node was added.", {
1554
+ deps.interaction.error("Eligible machines changed before adding the node.", {
1658
1555
  code: "cluster_eligibility_changed",
1659
1556
  });
1660
1557
  }
@@ -1736,22 +1633,6 @@ async function removeNode(input, deps) {
1736
1633
  code: "cluster_member_not_found",
1737
1634
  });
1738
1635
  const goalIds = initialMembers.filter((id) => id !== machine.id);
1739
- const confirmed = await deps.interaction.review({
1740
- title: `Remove ${clusterModeLabel(prepared.cluster.access_mode)} cluster node`,
1741
- terms: [
1742
- { label: "Cluster", value: prepared.cluster.id },
1743
- { label: "Machine", value: `${machine.hostname} (${machine.id})` },
1744
- { label: "Membership goal", value: String(goalIds.length) },
1745
- { label: "Consequence", value: "Workloads and native access on this member stop." },
1746
- ],
1747
- });
1748
- if (!confirmed)
1749
- return deps.interaction.result({
1750
- data: { changed: false, cluster: prepared.cluster },
1751
- exitCode: 1,
1752
- outcome: { class: "ended", phase: "declined" },
1753
- presentation: { lines: ["Cluster membership was not changed."] },
1754
- });
1755
1636
  const current = await fetchClusterDetail(deps.fetch, deps.interaction, prepared.cluster);
1756
1637
  const currentIds = memberIds(current);
1757
1638
  if (sameMembers(currentIds, goalIds)) {
@@ -1762,7 +1643,7 @@ async function removeNode(input, deps) {
1762
1643
  });
1763
1644
  }
1764
1645
  if (!sameMembers(currentIds, initialMembers)) {
1765
- deps.interaction.error("Cluster membership changed after review; no node was removed.", {
1646
+ deps.interaction.error("Cluster membership changed before removing the node.", {
1766
1647
  code: "cluster_membership_changed",
1767
1648
  });
1768
1649
  }
@@ -1836,29 +1717,9 @@ async function teardownCluster(input, deps) {
1836
1717
  });
1837
1718
  }
1838
1719
  const reviewedMembers = memberIds(reviewed);
1839
- const confirmed = await deps.interaction.review({
1840
- title: `Tear down ${clusterModeLabel(reviewed.access_mode)} cluster`,
1841
- terms: [
1842
- { label: "Cluster", value: reviewed.id },
1843
- { label: "Members", value: String(reviewedMembers.length) },
1844
- {
1845
- label: "Native access",
1846
- value: `${clusterNativeAccess(reviewed.access_mode)} access ends.`,
1847
- },
1848
- { label: "Capacity reservation", value: `${reviewed.reservation_id} remains active.` },
1849
- { label: "Charges", value: "Capacity charges continue after cluster teardown." },
1850
- ],
1851
- });
1852
- if (!confirmed)
1853
- return deps.interaction.result({
1854
- data: { changed: false, cluster: reviewed },
1855
- exitCode: 1,
1856
- outcome: { class: "ready", phase: "unchanged" },
1857
- presentation: { lines: ["The cluster remains active. Capacity charges continue."] },
1858
- });
1859
1720
  const current = await fetchClusterDetail(deps.fetch, deps.interaction, reviewed);
1860
1721
  if (!sameMembers(memberIds(current), reviewedMembers)) {
1861
- deps.interaction.error("Cluster membership changed after review; teardown was not submitted.", {
1722
+ deps.interaction.error("Cluster membership changed before teardown was submitted.", {
1862
1723
  code: "cluster_membership_changed",
1863
1724
  });
1864
1725
  }
@@ -1901,44 +1762,25 @@ export async function executeKubernetesClusterCommand(command, input, deps) {
1901
1762
  return handler(input, deps);
1902
1763
  }
1903
1764
 
1904
- function projectReservationFacts(value, expectedId, runtime) {
1905
- const reservation = record(value);
1906
- const id = text(reservation.id);
1907
- if (id !== expectedId) {
1765
+ export async function reservationFactsForCluster(fetch, runtime, cluster) {
1766
+ const reservation = await getReservation(fetch, cluster.reservation_id);
1767
+ if (reservation.reservationId !== cluster.reservation_id) {
1908
1768
  runtime.error("The gateway returned facts for a different capacity reservation.", {
1909
1769
  code: "reservation_identity_changed",
1910
1770
  });
1911
1771
  }
1912
- return {
1913
- id,
1914
- facility:
1915
- text(reservation.facility_name) ??
1916
- text(reservation.site_nickname) ??
1917
- text(reservation.facility),
1918
- hardware: text(reservation.gpu_type) ?? text(reservation.hardware),
1919
- machine_count: integer(reservation.node_count) ?? integer(reservation.machines),
1920
- region: text(reservation.region),
1921
- };
1922
- }
1923
-
1924
- export async function reservationFactsForCluster(fetch, runtime, cluster) {
1925
- const payload = await fetch(
1926
- `/v1/workflows/commerce/reservations/${encodeURIComponent(cluster.reservation_id)}`,
1927
- { method: "GET" }
1928
- );
1929
- return projectReservationFacts(payload, cluster.reservation_id, runtime);
1772
+ return reservation;
1930
1773
  }
1931
1774
 
1932
1775
  export function clusterFactLabel(cluster, reservation, gpuLabel) {
1933
- const location = reservation.facility ?? reservation.region ?? "location unknown";
1934
- const gpu = reservation.hardware ?? cluster.gpu_type;
1776
+ const gpu = reservation.gpuSlug ?? (reservation.reservedGpuCount === 0 ? "cpu" : null);
1935
1777
  const hardware = gpu ? gpuLabel(gpu) : "hardware unknown";
1936
1778
  const nodeCount =
1937
1779
  integer(cluster.membership_count) ??
1938
1780
  integer(cluster.node_count) ??
1939
1781
  (Array.isArray(cluster.machines) ? cluster.machines.length : 0);
1940
1782
  const lifecycle = text(record(cluster.lifecycle).class) ?? text(cluster.state) ?? "unknown";
1941
- return `${location} · ${clusterModeLabel(cluster.access_mode)} · ${hardware} · ${nodeCount} nodes · ${lifecycle}`;
1783
+ return `${clusterModeLabel(cluster.access_mode)} · ${hardware} · ${nodeCount} nodes · ${lifecycle}`;
1942
1784
  }
1943
1785
  const clusterConnectCandidatesCache = new WeakMap();
1944
1786
 
@@ -15,7 +15,6 @@ const COMMON_OPTIONS = [
15
15
  "reservation",
16
16
  "timeout",
17
17
  "wait",
18
- "yes",
19
18
  ];
20
19
  const KEY_OPTIONS = ["key", "keyId", "label", "publicKey", "publicKeyFile"];
21
20
  const MODE_OPTIONS = new Map([
@@ -1,8 +1,7 @@
1
+ import { listAllReservations } from "../vendor/api-client/commerce.js";
1
2
  import { gpuLabeler } from "./gpu-catalog.mjs";
2
3
 
3
4
  const CANONICAL_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
4
- const PAGE_SIZE = 500;
5
- const MAX_RESERVATION_PAGES = 20;
6
5
  const TERMINAL_MACHINE_STATES = new Set([
7
6
  "canceled",
8
7
  "cancelled",
@@ -31,18 +30,12 @@ function rows(payload, field) {
31
30
  }
32
31
 
33
32
  function reservationId(value) {
34
- const source = record(value);
35
- return (
36
- text(source.id) ??
37
- text(source.ref) ??
38
- text(source.reservation_id) ??
39
- text(source.commerce_reservation_id)
40
- );
33
+ return text(value.reservationId);
41
34
  }
42
35
 
43
36
  function reservationMode(value) {
44
37
  const source = record(value);
45
- const mode = text(source.access_mode) ?? text(source.accessMode);
38
+ const mode = text(source.provisioningType);
46
39
  return mode?.toLowerCase() === "virtual-machine" ? "vm" : (mode?.toLowerCase() ?? null);
47
40
  }
48
41
 
@@ -63,21 +56,16 @@ function reservationModeLabel(value) {
63
56
 
64
57
  export function reservationChoiceLabel(value, gpuLabel) {
65
58
  const reservation = record(value);
66
- const gpu = text(reservation.gpu_type);
67
- const hardware = gpu ? gpuLabel(gpu) : (text(reservation.hardware) ?? "GPU capacity");
68
- const quantity = Number(reservation.node_count ?? reservation.machine_count ?? 1);
59
+ // CPU capacity has no GPU type and no GPUs; the labeler names it CPU.
60
+ const gpu = text(reservation.gpuSlug) ?? (reservation.reservedGpuCount === 0 ? "cpu" : null);
61
+ const hardware = gpu ? gpuLabel(gpu) : "GPU capacity";
62
+ const quantity = reservation.reservedNodeCount;
69
63
  const machines = Number.isInteger(quantity) && quantity > 0 ? quantity : 1;
70
- const location =
71
- text(reservation.site_nickname) ??
72
- text(reservation.facility) ??
73
- text(reservation.location) ??
74
- text(reservation.region) ??
75
- "location unreported";
76
- const start = text(reservation.start_at) ?? text(reservation.start_date);
77
- const end = text(reservation.end_at) ?? text(reservation.end_date);
78
- const createdAt = text(reservation.created_at);
64
+ const start = text(reservation.startsAt);
65
+ const end = text(reservation.endsAt);
66
+ const createdAt = text(reservation.createdAt);
79
67
  return [
80
- `${hardware}, ${machines} machine${machines === 1 ? "" : "s"}, ${location}`,
68
+ `${hardware}, ${machines} machine${machines === 1 ? "" : "s"}`,
81
69
  reservationModeLabel(reservation),
82
70
  start && end ? `${start} to ${end}` : null,
83
71
  createdAt ? `reserved ${createdAt}` : null,
@@ -91,13 +79,6 @@ export async function reservationLabeler(fetch) {
91
79
  return (reservation) => reservationChoiceLabel(reservation, gpuLabel);
92
80
  }
93
81
 
94
- function isActiveDirectReservation(value) {
95
- const source = record(value);
96
- const status = (text(source.status) ?? text(source.state) ?? "").toLowerCase();
97
- const mode = reservationMode(source);
98
- return status === "active" && (!mode || mode === "vm" || mode === "bare-metal");
99
- }
100
-
101
82
  export function canonicalMachineId(value) {
102
83
  const source = record(value);
103
84
  return (
@@ -130,35 +111,31 @@ function invalidGateway(interaction, message) {
130
111
  });
131
112
  }
132
113
 
133
- export async function listActiveDirectReservations(context, interaction) {
134
- const reservations = [];
135
- const seen = new Set();
136
- let cursor = null;
137
- for (let page = 0; page < MAX_RESERVATION_PAGES; page += 1) {
138
- const query = new URLSearchParams({ limit: String(PAGE_SIZE), status: "active" });
139
- if (cursor) query.set("cursor", cursor);
140
- const payload = await context.fetch(`/v1/workflows/commerce/reservations?${query}`, {
141
- method: "GET",
114
+ export async function listActiveReservations(context, interaction) {
115
+ const session = context.cli?.session
116
+ ? (await context.cli.session("context", {})).data
117
+ : await context.fetch("/v1/cli/session");
118
+ const customerId = text((session.selectedOrganization ?? session.selected_organization)?.id);
119
+ if (!customerId) {
120
+ interaction.usage("Select an organization before choosing a reservation.", {
121
+ code: "organization_required",
122
+ field: "organization",
142
123
  });
143
- const pageRows = rows(payload, "reservations");
144
- if (!pageRows)
145
- invalidGateway(interaction, "The gateway returned an invalid capacity-reservation page.");
146
- reservations.push(...pageRows.map(record).filter(isActiveDirectReservation));
147
-
148
- const source = record(payload);
149
- const next = text(source.next_cursor) ?? text(source.nextCursor);
150
- if (!next || Array.isArray(payload)) return reservations;
151
- if (seen.has(next)) {
152
- invalidGateway(interaction, "Capacity-reservation pagination repeated a cursor.");
153
- }
154
- seen.add(next);
155
- cursor = next;
156
124
  }
157
- interaction.error("Machine resolution exceeded its bounded capacity-reservation search.", {
158
- code: "resource_search_incomplete",
159
- exitCode: 6,
160
- retryable: true,
125
+ // Native Commerce permits staff to read all organizations. Customer pickers
126
+ // retain the selected-organization scope of the published CLI workflow.
127
+ const { reservations } = await listAllReservations(context.fetch, {
128
+ customerId,
129
+ status: "active",
161
130
  });
131
+ return reservations.filter((reservation) => reservation.status === "active");
132
+ }
133
+
134
+ export async function listActiveDirectReservations(context, interaction) {
135
+ return (await listActiveReservations(context, interaction)).filter(
136
+ ({ provisioningType }) =>
137
+ provisioningType === "virtual-machine" || provisioningType === "bare-metal"
138
+ );
162
139
  }
163
140
 
164
141
  export async function listOwnedActiveMachines(context, interaction) {