@ornncompute/cli 1.0.48 → 1.0.62

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.
@@ -53,7 +53,7 @@ export const nativeListingUpdateFields = {
53
53
  facilityId: z.string().uuid().optional(),
54
54
  provisioningType: z.enum(["bare-metal", "virtual-machine"]).optional(),
55
55
  availableAt: z.iso.datetime({ offset: true }).optional(),
56
- availableUntil: z.iso.datetime({ offset: true }).optional(),
56
+ availableUntil: z.iso.datetime({ offset: true }).nullable().optional(),
57
57
  daysToRfs: z.coerce.number().int().nonnegative().optional(),
58
58
  cpu: z.string().optional(),
59
59
  ram: z.string().optional(),
@@ -0,0 +1,68 @@
1
+ import { z } from "zod";
2
+ import { defineCapability } from "../capability.js";
3
+ import { queryString } from "./staff-shared.js";
4
+ export const reservationListInput = z.object({
5
+ customerId: z.string().uuid().optional(),
6
+ listingId: z.string().uuid().optional(),
7
+ listingTermId: z.string().uuid().optional(),
8
+ status: z.enum(["confirmed", "active", "completed", "cancelled"]).optional(),
9
+ cursor: z.string().optional(),
10
+ limit: z.number().int().min(1).max(100).optional(),
11
+ });
12
+ // Staff on every surface: Slack and the CLI resolve a customer's reservations
13
+ // with this, then nodes.list reservation_id for that reservation's machines.
14
+ export const reservationListCapability = defineCapability({
15
+ id: "reservation.list",
16
+ domain: "reservations",
17
+ roles: ["admin"],
18
+ description: "Internal staff only: list Commerce reservations across organizations, optionally filtered by customer organization (customerId), listing, listing term, and derived status. Resolve a customer's organization with users.list or organization.list first; pass each reservationId to nodes.list reservation_id for its machines. Page with nextCursor as cursor.",
19
+ mutation: "read",
20
+ http: { method: "GET", path: "/commerce/reservations" },
21
+ cli: {
22
+ command: ["admin", "reservations", "list"],
23
+ vnext: {
24
+ audience: "admin",
25
+ category: "resource",
26
+ command: ["admin", "reservations", "list"],
27
+ outcome: "List reservations across organizations.",
28
+ human: "ornn admin reservations list [--customer UUID] [--listing UUID] [--listing-term UUID] [--status confirmed|active|completed|cancelled] [--limit 50] [--cursor CURSOR]",
29
+ automation: "ornn admin reservations list --customer UUID --limit 50 [--cursor CURSOR] --format json",
30
+ acceptedReferences: [],
31
+ },
32
+ },
33
+ slack: {
34
+ parameters: {
35
+ type: "object",
36
+ properties: {
37
+ customerId: {
38
+ type: "string",
39
+ description: "Optional customer organization UUID from users_list or organization_list.",
40
+ },
41
+ listingId: { type: "string", description: "Optional listing UUID." },
42
+ listingTermId: { type: "string", description: "Optional listing term UUID." },
43
+ status: {
44
+ type: "string",
45
+ enum: ["confirmed", "active", "completed", "cancelled"],
46
+ description: "Optional derived reservation status.",
47
+ },
48
+ cursor: {
49
+ type: "string",
50
+ description: "Optional. Pass the previous response's nextCursor to fetch the next page.",
51
+ },
52
+ limit: { type: "integer", description: "Optional page size (Slack returns at most 10)." },
53
+ },
54
+ },
55
+ },
56
+ input: reservationListInput,
57
+ execute: async (ctx, input) => {
58
+ const parsed = reservationListInput.parse(input ?? {});
59
+ return ctx.fetch(`/commerce/reservations${queryString({
60
+ customerId: parsed.customerId,
61
+ listingId: parsed.listingId,
62
+ status: parsed.status,
63
+ listingTermId: parsed.listingTermId,
64
+ cursor: parsed.cursor,
65
+ limit: parsed.limit,
66
+ })}`, { method: "GET" });
67
+ },
68
+ });
@@ -0,0 +1,64 @@
1
+ /** Helpers shared by staff capabilities. */
2
+ /**
3
+ * Build a request body from the fields that were actually supplied.
4
+ *
5
+ * Staff writes are almost all "PATCH the fields the caller named": `undefined`
6
+ * means leave alone, and `null` is a real value that clears the field. So the
7
+ * check is `!== undefined`, never truthiness.
8
+ */
9
+ export function mapBody(input, mapping) {
10
+ const body = {};
11
+ for (const [field, wire] of Object.entries(mapping)) {
12
+ if (input[field] !== undefined)
13
+ body[wire] = input[field];
14
+ }
15
+ return body;
16
+ }
17
+ /** Path segment escape. */
18
+ export function seg(value) {
19
+ return encodeURIComponent(value);
20
+ }
21
+ /**
22
+ * A transport failure's HTTP status, when it carries one.
23
+ *
24
+ * Capabilities cannot import a surface's error class (`apps/mcp`'s
25
+ * `WebApiError`, say), so read the status structurally.
26
+ */
27
+ export function statusOf(error) {
28
+ if (error && typeof error === "object" && "status" in error) {
29
+ const status = error.status;
30
+ if (typeof status === "number")
31
+ return status;
32
+ }
33
+ return undefined;
34
+ }
35
+ /**
36
+ * Read a resource's current optimistic-concurrency version.
37
+ *
38
+ * Commerce rejects a PATCH or lifecycle action whose `version` is absent or
39
+ * non-positive ("missing version" / "version must be positive"), but no staff
40
+ * caller has that number to hand. Read it back when it was not supplied, and
41
+ * only once confirmed so a preview stays a pure dry run.
42
+ */
43
+ export async function resolveVersion(ctx, path, provided) {
44
+ if (provided && provided > 0)
45
+ return provided;
46
+ if (!ctx.confirmed)
47
+ return 1;
48
+ try {
49
+ const current = (await ctx.fetch(path, { method: "GET" }));
50
+ if (typeof current?.version === "number" && current.version > 0)
51
+ return current.version;
52
+ }
53
+ catch {
54
+ // Fall through: send 1 and let commerce report the conflict, which names
55
+ // the expected version, rather than failing on a lookup the caller can't see.
56
+ }
57
+ return 1;
58
+ }
59
+ export function queryString(params) {
60
+ const parts = Object.entries(params)
61
+ .filter((entry) => entry[1] !== undefined && entry[1] !== "")
62
+ .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
63
+ return parts.length > 0 ? `?${parts.join("&")}` : "";
64
+ }
@@ -6,6 +6,7 @@ import { adminEnrollmentCapabilities, bindAdminEnrollmentCapabilities, } from ".
6
6
  import { adminNodeDiagnosticCapabilities } from "./capabilities/admin-node-diagnostics.js";
7
7
  import { adminNodeLifecycleCapabilities } from "./capabilities/admin-nodes-lifecycle.js";
8
8
  import { computeCapabilities } from "./capabilities/compute.js";
9
+ import { creditGrantCapability } from "./capabilities/credits.js";
9
10
  import { cliShellCapabilities } from "./capabilities/cli.js";
10
11
  import { clusterCliCapabilities, kubernetesCapabilities, slurmCapabilities, } from "./capabilities/clusters.js";
11
12
  import { externalBucketCapabilities } from "./capabilities/external-buckets.js";
@@ -18,6 +19,7 @@ import { listingCapabilities } from "./capabilities/listings.js";
18
19
  import { observabilityCapabilities } from "./capabilities/observability.js";
19
20
  import { organizationInviteCapability } from "./capabilities/organization-invites.js";
20
21
  import { networkCapabilities } from "./capabilities/networks.js";
22
+ import { reservationListCapability } from "./capabilities/reservation-list.js";
21
23
  import { volumeCapabilities } from "./capabilities/volumes.js";
22
24
  import { userCapabilities } from "./capabilities/users.js";
23
25
  function canonicalCliCatalog(capabilities) {
@@ -62,8 +64,10 @@ export const catalog = [
62
64
  ])),
63
65
  ...adminCapabilities.filter((capability) => capability.id === "nodes.health"),
64
66
  ...adminCapacityReservationCapabilities,
67
+ reservationListCapability,
65
68
  ...adminNodeDiagnosticCapabilities,
66
69
  ...adminEnrollmentCapabilities,
67
70
  organizationInviteCapability,
71
+ creditGrantCapability,
68
72
  ]),
69
73
  ];
@@ -9,21 +9,25 @@ export const LEGACY_CLI_MIGRATIONS = [
9
9
  { legacy: ["catalog", "search"], replacement: "ornn capacity-offers list" },
10
10
  { legacy: ["gpu-types"], replacement: "ornn capacity-offers list" },
11
11
  { legacy: ["availability", "list"], replacement: "ornn capacity-offers list" },
12
- { legacy: ["availability", "forecast"], replacement: "ornn schedules list" },
12
+ { legacy: ["availability", "forecast"], replacement: "ornn capacity-offers availability" },
13
13
  { legacy: ["availability", "show"], replacement: "ornn capacity-offers show" },
14
14
  { legacy: ["listing", "list"], replacement: "ornn capacity-offers list" },
15
- { legacy: ["listing", "forecast"], replacement: "ornn schedules list" },
15
+ { legacy: ["listing", "forecast"], replacement: "ornn capacity-offers availability" },
16
16
  { legacy: ["listing", "show"], replacement: "ornn capacity-offers show" },
17
17
  { legacy: ["listings", "list"], replacement: "ornn capacity-offers list" },
18
- { legacy: ["listings", "forecast"], replacement: "ornn schedules list" },
18
+ { legacy: ["listings", "forecast"], replacement: "ornn capacity-offers availability" },
19
19
  { legacy: ["listings", "show"], replacement: "ornn capacity-offers show" },
20
20
  { legacy: ["listings", "create"], replacement: "ornn admin capacity-offers create" },
21
21
  { legacy: ["buy"], replacement: "ornn offers post" },
22
- { legacy: ["bid", "create"], replacement: "ornn spot bid" },
23
- { legacy: ["bid", "list"], replacement: "ornn spot orders" },
24
- { legacy: ["bid", "show"], replacement: "ornn spot orders" },
25
- { legacy: ["bid", "update"], replacement: "ornn spot bid" },
26
- { legacy: ["bid", "withdraw"], replacement: "ornn spot cancel" },
22
+ { legacy: ["schedules", "list"], replacement: "ornn capacity-offers list --kind secondary" },
23
+ { legacy: ["schedules", "show"], replacement: "ornn capacity-offers availability" },
24
+ { legacy: ["schedules", "buy"], replacement: "ornn reservations buy" },
25
+ { legacy: ["schedules"], replacement: "ornn capacity-offers list" },
26
+ { legacy: ["bid", "create"], replacement: "ornn market bid" },
27
+ { legacy: ["bid", "list"], replacement: "ornn market orders" },
28
+ { legacy: ["bid", "show"], replacement: "ornn market orders" },
29
+ { legacy: ["bid", "update"], replacement: "ornn market bid" },
30
+ { legacy: ["bid", "withdraw"], replacement: "ornn market cancel" },
27
31
  {
28
32
  legacy: ["exchange", "create"],
29
33
  replacement: "ornn capacity-offers list (use the Ornn web app for commercial workflows)",