@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.
- package/README.md +65 -47
- package/package.json +2 -2
- package/src/admin-capacity-reservations.mjs +36 -54
- package/src/admin-command-handlers.mjs +2 -0
- package/src/admin-credits.mjs +57 -0
- package/src/admin-shell.mjs +13 -3
- package/src/catalog-shell.mjs +4 -7
- package/vendor/capabilities/capabilities/admin-capacity-reservations.js +32 -15
- package/vendor/capabilities/capabilities/admin-enrollment.js +3 -3
- package/vendor/capabilities/capabilities/admin.js +1 -1
- package/vendor/capabilities/capabilities/commerce-market.js +122 -22
- package/vendor/capabilities/capabilities/credits.js +78 -0
- package/vendor/capabilities/capabilities/fleet.js +6 -5
- package/vendor/capabilities/capabilities/listings.js +1 -1
- package/vendor/capabilities/capabilities/market-shared.js +2 -0
- package/vendor/capabilities/capabilities/markets.js +255 -222
- package/vendor/capabilities/capabilities/native-listing.js +1 -1
- package/vendor/capabilities/capabilities/reservation-list.js +68 -0
- package/vendor/capabilities/capabilities/staff-shared.js +64 -0
- package/vendor/capabilities/cli-catalog.js +4 -0
- package/vendor/capabilities/cli-migrations.js +12 -8
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { nativeTermsInput, nativeTermFields, listingWriteControls } from "./native-listing.js";
|
|
3
3
|
import { defineCapability, preview } from "../capability.js";
|
|
4
|
-
import { consent, idempotencyHeaders, positionals, query, seg } from "./market-shared.js";
|
|
4
|
+
import { consent, idempotencyHeaders, microUsd, microUsdLabel, positionals, query, seg, } from "./market-shared.js";
|
|
5
5
|
// Commerce market verbs: one capability per role-aware `/commerce` route.
|
|
6
6
|
// Commerce decides from the caller's session what each listing or
|
|
7
7
|
// reservation reveals; these verbs return its JSON unchanged.
|
|
@@ -20,12 +20,6 @@ function help(command, outcome, human, automation, related, order, acceptedRefer
|
|
|
20
20
|
};
|
|
21
21
|
}
|
|
22
22
|
const pageLimit = z.coerce.number().int().min(1).max(200).optional();
|
|
23
|
-
const nodeIdList = z.preprocess((value) => typeof value === "string"
|
|
24
|
-
? value
|
|
25
|
-
.split(",")
|
|
26
|
-
.map((part) => part.trim())
|
|
27
|
-
.filter(Boolean)
|
|
28
|
-
: value, z.array(z.string().uuid()).min(1));
|
|
29
23
|
export const listingsListInput = z.object({
|
|
30
24
|
$positionals: positionals,
|
|
31
25
|
kind: z.enum(["primary", "secondary", "on_demand"]).optional(),
|
|
@@ -45,7 +39,7 @@ export const listingsListCapability = defineCapability({
|
|
|
45
39
|
input: listingsListInput,
|
|
46
40
|
cli: {
|
|
47
41
|
command: ["capacity-offers", "list"],
|
|
48
|
-
vnext: help(["capacity-offers", "list"], "List capacity parents with bounded pagination; read their financial terms separately.", "ornn capacity-offers list [--kind primary|secondary|on_demand] [--source-reservation-id <id>] [--limit <1-100>] [--cursor <opaque>]", "ornn capacity-offers list --limit 50 --format json", ["capacity-offers show", "capacity-offers terms", "
|
|
42
|
+
vnext: help(["capacity-offers", "list"], "List capacity parents with bounded pagination; read their financial terms separately.", "ornn capacity-offers list [--kind primary|secondary|on_demand] [--source-reservation-id <id>] [--limit <1-100>] [--cursor <opaque>]", "ornn capacity-offers list --limit 50 --format json", ["capacity-offers show", "capacity-offers terms", "capacity-offers availability"], 20, ["opaque page cursor"]),
|
|
49
43
|
},
|
|
50
44
|
slack: false,
|
|
51
45
|
execute: async (ctx, input) => {
|
|
@@ -74,7 +68,7 @@ export const listingShowCapability = defineCapability({
|
|
|
74
68
|
input: listingShowInput,
|
|
75
69
|
cli: {
|
|
76
70
|
command: ["capacity-offers", "show"],
|
|
77
|
-
vnext: help(["capacity-offers", "show"], "Inspect one listing's hardware, capacity and availability.", "ornn capacity-offers show --listing-id <id>", "ornn capacity-offers show --listing-id <id> --format json", ["capacity-offers list", "capacity-offers terms", "
|
|
71
|
+
vnext: help(["capacity-offers", "show"], "Inspect one listing's hardware, capacity and availability.", "ornn capacity-offers show --listing-id <id>", "ornn capacity-offers show --listing-id <id> --format json", ["capacity-offers list", "capacity-offers terms", "capacity-offers availability"], 21, ["listing ID"]),
|
|
78
72
|
},
|
|
79
73
|
slack: false,
|
|
80
74
|
execute: async (ctx, input) => {
|
|
@@ -86,13 +80,13 @@ export const listingTermsCapability = defineCapability({
|
|
|
86
80
|
id: "listings.terms",
|
|
87
81
|
domain: "listings",
|
|
88
82
|
roles: ["user"],
|
|
89
|
-
description: "Read a capacity listing's advertised financial terms. Each term returns listingTermsId, capacityListingId, pricePerGpuHourMicroUsd, duration bounds, downPaymentCents and renewable. Use the parent's capacityListingId as listingId. Prices are integer micro-USD per GPU-hour.",
|
|
83
|
+
description: "Read a capacity listing's advertised financial terms. Each term returns listingTermsId, capacityListingId, pricePerGpuHourMicroUsd, duration bounds, downPaymentCents and renewable. Use the parent's capacityListingId as listingId. Prices are gross integer micro-USD per GPU-hour, the price reservations.purchase expects.",
|
|
90
84
|
mutation: "read",
|
|
91
85
|
http: { method: "GET", path: "/commerce/v2/listings/{listingId}/terms" },
|
|
92
86
|
input: listingShowInput,
|
|
93
87
|
cli: {
|
|
94
88
|
command: ["capacity-offers", "terms"],
|
|
95
|
-
vnext: help(["capacity-offers", "terms"], "Read the advertised financial terms for a capacity listing.", "ornn capacity-offers terms --listing-id <id>", "ornn capacity-offers terms --listing-id <id> --format json", ["capacity-offers show", "offers post"], 22, ["capacityListingId from capacity-offers list or show"]),
|
|
89
|
+
vnext: help(["capacity-offers", "terms"], "Read the advertised financial terms for a capacity listing.", "ornn capacity-offers terms --listing-id <id>", "ornn capacity-offers terms --listing-id <id> --format json", ["capacity-offers show", "offers post", "reservations buy"], 22, ["capacityListingId from capacity-offers list or show"]),
|
|
96
90
|
},
|
|
97
91
|
slack: false,
|
|
98
92
|
execute: async (ctx, input) => {
|
|
@@ -100,33 +94,65 @@ export const listingTermsCapability = defineCapability({
|
|
|
100
94
|
return ctx.fetch(`/commerce/v2/listings/${seg(parsed.listingId)}/terms`, { method: "GET" });
|
|
101
95
|
},
|
|
102
96
|
});
|
|
103
|
-
export const
|
|
97
|
+
export const listingAvailabilityInput = z.object({
|
|
98
|
+
$positionals: positionals,
|
|
99
|
+
listingId: z.string().uuid(),
|
|
100
|
+
from: z.iso
|
|
101
|
+
.datetime({ offset: true })
|
|
102
|
+
.optional()
|
|
103
|
+
.describe("RFC 3339 start of the range to read. Defaults to now or the listing's start."),
|
|
104
|
+
to: z.iso
|
|
105
|
+
.datetime({ offset: true })
|
|
106
|
+
.optional()
|
|
107
|
+
.describe("RFC 3339 end of the range to read. Defaults to the listing's end."),
|
|
108
|
+
});
|
|
109
|
+
export const listingAvailabilityCapability = defineCapability({
|
|
110
|
+
id: "listings.availability",
|
|
111
|
+
domain: "listings",
|
|
112
|
+
roles: ["user"],
|
|
113
|
+
description: "Read how much of a capacity listing is free to book over time: windows of startsAt, endsAt, gpus and nodes (the nodes with any GPU free) from now until the listing ends (for an open-ended listing, 30 days ahead for a Market listing and far enough for any term of a Reserve listing), optionally narrowed by from and to. Use the parent's capacityListingId as listingId.",
|
|
114
|
+
mutation: "read",
|
|
115
|
+
http: { method: "GET", path: "/v1/markets/listings/{listingId}/availability" },
|
|
116
|
+
input: listingAvailabilityInput,
|
|
117
|
+
cli: {
|
|
118
|
+
command: ["capacity-offers", "availability"],
|
|
119
|
+
vnext: help(["capacity-offers", "availability"], "Show how many of a listing's nodes are free over time.", "ornn capacity-offers availability --listing-id <id> [--from <time>] [--to <time>]", "ornn capacity-offers availability --listing-id <id> --format json", ["capacity-offers terms", "reservations buy", "offers post"], 23, ["capacityListingId from capacity-offers list or show"]),
|
|
120
|
+
},
|
|
121
|
+
slack: false,
|
|
122
|
+
execute: async (ctx, input) => {
|
|
123
|
+
const parsed = listingAvailabilityInput.parse(input);
|
|
124
|
+
return ctx.fetch(`/v1/markets/listings/${seg(parsed.listingId)}/availability${query({
|
|
125
|
+
from: parsed.from,
|
|
126
|
+
to: parsed.to,
|
|
127
|
+
})}`, { method: "GET" });
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
export const listingRelistInput = z.strictObject({
|
|
104
131
|
$positionals: positionals,
|
|
105
132
|
reservationId: z.string().uuid(),
|
|
106
133
|
startsAt: z.iso.datetime({ offset: true }),
|
|
107
134
|
endsAt: z.iso.datetime({ offset: true }),
|
|
108
135
|
nodeCount: z.coerce.number().int().positive(),
|
|
109
|
-
nodeIds: nodeIdList.optional(),
|
|
110
|
-
reservedNodeIds: nodeIdList.optional(),
|
|
111
136
|
terms: nativeTermsInput,
|
|
112
137
|
idempotencyKey: z.string().min(1).max(200).optional(),
|
|
113
138
|
confirm: z.boolean().optional(),
|
|
139
|
+
yes: z.boolean().optional(),
|
|
114
140
|
});
|
|
115
141
|
export const listingRelistCapability = defineCapability({
|
|
116
142
|
id: "listings.relist",
|
|
117
143
|
domain: "listings",
|
|
118
144
|
roles: ["user"],
|
|
119
|
-
description: "Relist
|
|
145
|
+
description: "Relist whole nodes of a reservation's capacity using a native availability window, nodeCount and financial terms. Buyers run on nodes Ornn assigns when their rental starts. Requires confirm: true.",
|
|
120
146
|
mutation: "preview-confirm",
|
|
121
147
|
http: { method: "POST", path: "/commerce/reservations/{reservationId}/relist" },
|
|
122
148
|
input: listingRelistInput,
|
|
123
149
|
cli: {
|
|
124
150
|
command: ["capacity-offers", "relist"],
|
|
125
|
-
vnext: help(["capacity-offers", "relist"], "Relist reserved capacity with explicit financial terms.", "ornn capacity-offers relist --reservation-id UUID --node-count COUNT --starts-at TIME --ends-at TIME --terms JSON
|
|
151
|
+
vnext: help(["capacity-offers", "relist"], "Relist reserved capacity with explicit financial terms.", "ornn capacity-offers relist --reservation-id UUID --node-count COUNT --starts-at TIME --ends-at TIME --terms JSON", "ornn capacity-offers relist --reservation-id UUID --node-count 1 --starts-at TIME --ends-at TIME --terms JSON --idempotency-key KEY --yes --format json", ["capacity-offers terms", "capacity-offers delist"], 24, ["reservation ID"]),
|
|
126
152
|
},
|
|
127
153
|
slack: false,
|
|
128
154
|
execute: async (ctx, input) => {
|
|
129
|
-
const { $positionals: _, reservationId, confirm: _confirm, idempotencyKey, ...body } = listingRelistInput.parse(input);
|
|
155
|
+
const { $positionals: _, reservationId, confirm: _confirm, yes: _yes, idempotencyKey, ...body } = listingRelistInput.parse(input);
|
|
130
156
|
const gate = await consent(ctx, "listings.relist", "Relist capacity", [
|
|
131
157
|
{ label: "Reservation", value: reservationId },
|
|
132
158
|
{ label: "Capacity and financial terms", value: JSON.stringify(body) },
|
|
@@ -160,7 +186,7 @@ export const listingUpdateCapability = defineCapability({
|
|
|
160
186
|
cli: {
|
|
161
187
|
command: ["admin", "capacity-offers", "update"],
|
|
162
188
|
vnext: {
|
|
163
|
-
...help(["admin", "capacity-offers", "update"], "Edit one advertised financial term as staff.", "ornn admin capacity-offers update --listing-id UUID --listing-terms-id UUID --min-duration-hours HOURS --max-duration-hours HOURS --price-per-gpu-hour-micro-usd RATE --renewable true|false --idempotency-key KEY [--type reserved|spot|on-demand] [--down-payment-cents CENTS]", "Requires an eligible human terminal; reuse --idempotency-key on retries.", ["capacity-offers terms"],
|
|
189
|
+
...help(["admin", "capacity-offers", "update"], "Edit one advertised financial term as staff.", "ornn admin capacity-offers update --listing-id UUID --listing-terms-id UUID --min-duration-hours HOURS --max-duration-hours HOURS --price-per-gpu-hour-micro-usd RATE --renewable true|false --idempotency-key KEY [--type reserved|spot|on-demand] [--down-payment-cents CENTS]", "Requires an eligible human terminal; reuse --idempotency-key on retries.", ["capacity-offers terms"], 25),
|
|
164
190
|
audience: "admin",
|
|
165
191
|
},
|
|
166
192
|
},
|
|
@@ -192,7 +218,7 @@ export const listingDelistCapability = defineCapability({
|
|
|
192
218
|
input: listingDelistInput,
|
|
193
219
|
cli: {
|
|
194
220
|
command: ["capacity-offers", "delist"],
|
|
195
|
-
vnext: help(["capacity-offers", "delist"], "Delist a listing you relisted.", "ornn capacity-offers delist --listing-id <id>", "ornn capacity-offers delist --listing-id <id> --yes --format json", ["capacity-offers relist"],
|
|
221
|
+
vnext: help(["capacity-offers", "delist"], "Delist a listing you relisted.", "ornn capacity-offers delist --listing-id <id>", "ornn capacity-offers delist --listing-id <id> --yes --format json", ["capacity-offers relist"], 26, ["listing ID"]),
|
|
196
222
|
},
|
|
197
223
|
slack: false,
|
|
198
224
|
execute: async (ctx, input) => {
|
|
@@ -236,7 +262,7 @@ export const reservationsListCapability = defineCapability({
|
|
|
236
262
|
input: reservationsListInput,
|
|
237
263
|
cli: {
|
|
238
264
|
command: ["reservations", "list"],
|
|
239
|
-
vnext: help(["reservations", "list"], "List your organization's schedule and spot reservations.", "ornn reservations list [--kind schedule|spot] [--status <status>] [--listing-id <id>] [--limit <1-200>] [--cursor <opaque>]", "ornn reservations list --format json", ["reservations show", "capacity-offers relist"],
|
|
265
|
+
vnext: help(["reservations", "list"], "List your organization's schedule and spot reservations.", "ornn reservations list [--kind schedule|spot] [--status <status>] [--listing-id <id>] [--limit <1-200>] [--cursor <opaque>]", "ornn reservations list --format json", ["reservations show", "capacity-offers relist"], 27, ["opaque page cursor"]),
|
|
240
266
|
},
|
|
241
267
|
slack: false,
|
|
242
268
|
execute: async (ctx, input) => {
|
|
@@ -264,7 +290,7 @@ export const reservationsShowCapability = defineCapability({
|
|
|
264
290
|
input: reservationLookupInput,
|
|
265
291
|
cli: {
|
|
266
292
|
command: ["reservations", "show"],
|
|
267
|
-
vnext: help(["reservations", "show"], "Inspect one reservation.", "ornn reservations show --reservation-id <id>", "ornn reservations show --reservation-id <id> --format json", ["reservations list", "reservations invoices"],
|
|
293
|
+
vnext: help(["reservations", "show"], "Inspect one reservation.", "ornn reservations show --reservation-id <id>", "ornn reservations show --reservation-id <id> --format json", ["reservations list", "reservations invoices"], 28, ["reservation ID"]),
|
|
268
294
|
},
|
|
269
295
|
slack: false,
|
|
270
296
|
execute: async (ctx, input) => {
|
|
@@ -282,7 +308,7 @@ export const reservationInvoicesCapability = defineCapability({
|
|
|
282
308
|
input: reservationLookupInput,
|
|
283
309
|
cli: {
|
|
284
310
|
command: ["reservations", "invoices"],
|
|
285
|
-
vnext: help(["reservations", "invoices"], "List one reservation's invoices.", "ornn reservations invoices --reservation-id <id>", "ornn reservations invoices --reservation-id <id> --format json", ["reservations show"],
|
|
311
|
+
vnext: help(["reservations", "invoices"], "List one reservation's invoices.", "ornn reservations invoices --reservation-id <id>", "ornn reservations invoices --reservation-id <id> --format json", ["reservations show"], 29, ["reservation ID"]),
|
|
286
312
|
},
|
|
287
313
|
slack: false,
|
|
288
314
|
execute: async (ctx, input) => {
|
|
@@ -292,14 +318,88 @@ export const reservationInvoicesCapability = defineCapability({
|
|
|
292
318
|
});
|
|
293
319
|
},
|
|
294
320
|
});
|
|
321
|
+
export const reservationPurchaseInput = z.object({
|
|
322
|
+
$positionals: positionals,
|
|
323
|
+
listingId: z
|
|
324
|
+
.string()
|
|
325
|
+
.uuid()
|
|
326
|
+
.describe("The relisted (secondary) listing's capacityListingId, from listings.list."),
|
|
327
|
+
listingTermId: z
|
|
328
|
+
.string()
|
|
329
|
+
.uuid()
|
|
330
|
+
.describe("The listingTermsId of the listing's term, from listings.terms."),
|
|
331
|
+
nodeCount: z.coerce
|
|
332
|
+
.number()
|
|
333
|
+
.int()
|
|
334
|
+
.positive()
|
|
335
|
+
.optional()
|
|
336
|
+
.describe("Whole nodes to book for the window. Defaults to every node the listing sells."),
|
|
337
|
+
startsAt: z.iso
|
|
338
|
+
.datetime({ offset: true })
|
|
339
|
+
.describe("RFC 3339 start of the window, in whole seconds, inside the listing and not in the past."),
|
|
340
|
+
endsAt: z.iso
|
|
341
|
+
.datetime({ offset: true })
|
|
342
|
+
.describe("RFC 3339 end of the window, at least an hour after the start and within the term's duration range, or at the listing's end."),
|
|
343
|
+
pricePerGpuHourMicroUsd: microUsd.describe("The term's price from listings.terms, in micro-USD per GPU-hour. A changed price refuses the purchase."),
|
|
344
|
+
idempotencyKey: z.string().min(1).max(200).optional(),
|
|
345
|
+
confirm: z.boolean().optional(),
|
|
346
|
+
});
|
|
347
|
+
export const reservationPurchaseCapability = defineCapability({
|
|
348
|
+
id: "reservations.purchase",
|
|
349
|
+
domain: "reservations",
|
|
350
|
+
roles: ["user"],
|
|
351
|
+
description: "Book a window of a relisted (secondary) listing on one of its terms at the term's price. The GPUs are held for you until the window starts; then it becomes your reservation, with the booking's ID. Nothing is charged when you book. Primary listings take offers instead (offers.post). Requires confirm: true.",
|
|
352
|
+
mutation: "preview-confirm",
|
|
353
|
+
http: { method: "POST", path: "/v1/markets/bookings" },
|
|
354
|
+
input: reservationPurchaseInput,
|
|
355
|
+
cli: {
|
|
356
|
+
command: ["reservations", "buy"],
|
|
357
|
+
vnext: help(["reservations", "buy"], "Book a window of relisted capacity at its term's price.", "ornn reservations buy --listing-id <id> --listing-term-id <id> --starts-at <time> --ends-at <time> --price-per-gpu-hour-micro-usd <n> [--node-count <n>]", "ornn reservations buy --listing-id <id> --listing-term-id <id> --starts-at <time> --ends-at <time> --price-per-gpu-hour-micro-usd 4000000 --idempotency-key <key> --yes --format json", ["capacity-offers availability", "capacity-offers terms", "reservations show"], 30, ["listingTermsId from capacity-offers terms"]),
|
|
358
|
+
},
|
|
359
|
+
slack: false,
|
|
360
|
+
execute: async (ctx, input) => {
|
|
361
|
+
const parsed = reservationPurchaseInput.parse(input);
|
|
362
|
+
const body = {
|
|
363
|
+
legs: [
|
|
364
|
+
{
|
|
365
|
+
listingId: parsed.listingId,
|
|
366
|
+
listingTermId: parsed.listingTermId,
|
|
367
|
+
...(parsed.nodeCount !== undefined ? { nodes: parsed.nodeCount } : {}),
|
|
368
|
+
startsAt: parsed.startsAt,
|
|
369
|
+
endsAt: parsed.endsAt,
|
|
370
|
+
pricePerGpuHourMicroUsd: parsed.pricePerGpuHourMicroUsd,
|
|
371
|
+
},
|
|
372
|
+
],
|
|
373
|
+
};
|
|
374
|
+
const gate = await consent(ctx, "reservations.purchase", "Book relisted window", [
|
|
375
|
+
{ label: "Listing term", value: parsed.listingTermId },
|
|
376
|
+
{
|
|
377
|
+
label: "Nodes",
|
|
378
|
+
value: parsed.nodeCount !== undefined ? String(parsed.nodeCount) : "Every node on the listing",
|
|
379
|
+
},
|
|
380
|
+
{ label: "Window", value: `${parsed.startsAt} to ${parsed.endsAt}` },
|
|
381
|
+
{ label: "Price", value: microUsdLabel(parsed.pricePerGpuHourMicroUsd) },
|
|
382
|
+
{ label: "Payment", value: "Nothing is charged when you book." },
|
|
383
|
+
], body);
|
|
384
|
+
if (gate)
|
|
385
|
+
return gate;
|
|
386
|
+
return ctx.fetch("/v1/markets/bookings", {
|
|
387
|
+
method: "POST",
|
|
388
|
+
body,
|
|
389
|
+
headers: idempotencyHeaders(ctx, parsed.idempotencyKey),
|
|
390
|
+
});
|
|
391
|
+
},
|
|
392
|
+
});
|
|
295
393
|
export const commerceMarketCapabilities = [
|
|
296
394
|
listingsListCapability,
|
|
297
395
|
listingShowCapability,
|
|
298
396
|
listingTermsCapability,
|
|
397
|
+
listingAvailabilityCapability,
|
|
299
398
|
listingRelistCapability,
|
|
300
399
|
listingUpdateCapability,
|
|
301
400
|
listingDelistCapability,
|
|
302
401
|
reservationsListCapability,
|
|
303
402
|
reservationsShowCapability,
|
|
304
403
|
reservationInvoicesCapability,
|
|
404
|
+
reservationPurchaseCapability,
|
|
305
405
|
];
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { defineCapability, preview } from "../capability.js";
|
|
3
|
+
export const creditGrantInput = z.object({
|
|
4
|
+
organizationId: z
|
|
5
|
+
.uuid()
|
|
6
|
+
.describe("Customer organization UUID. Resolve names or emails with organization_list; ask which organization if matches are ambiguous."),
|
|
7
|
+
adjustmentId: z
|
|
8
|
+
.uuid()
|
|
9
|
+
.describe("The credit-adjustment API's required grant UUID. Keep the same UUID when confirming this grant."),
|
|
10
|
+
amountCents: z
|
|
11
|
+
.number()
|
|
12
|
+
.int()
|
|
13
|
+
.min(1)
|
|
14
|
+
.max(100_000_000)
|
|
15
|
+
.describe("USD cents: 10000 grants $100.00."),
|
|
16
|
+
note: z
|
|
17
|
+
.string()
|
|
18
|
+
.trim()
|
|
19
|
+
.min(1)
|
|
20
|
+
.refine((value) => new TextEncoder().encode(value).length <= 500, "Keep the note under 500 UTF-8 bytes.")
|
|
21
|
+
.describe("Reason for the credit grant."),
|
|
22
|
+
confirm: z.boolean().optional(),
|
|
23
|
+
});
|
|
24
|
+
export const creditGrantCapability = defineCapability({
|
|
25
|
+
id: "credits.grant",
|
|
26
|
+
domain: "billing",
|
|
27
|
+
roles: ["admin"],
|
|
28
|
+
description: "Grant Ornn credits to a customer organization using the same operation as Internal Billing. " +
|
|
29
|
+
"Credits belong to the organization, not an individual person. Resolve the intended organization first. " +
|
|
30
|
+
"Preview first; confirm:true posts the same grant with its adjustmentId. The verified staff actor supplies audit identity.",
|
|
31
|
+
mutation: "preview-confirm",
|
|
32
|
+
http: { method: "POST", path: "/internal/v1/organizations/{organizationId}/credit-adjustments" },
|
|
33
|
+
cli: {
|
|
34
|
+
command: ["admin", "organizations", "grant-credit"],
|
|
35
|
+
vnext: {
|
|
36
|
+
audience: "admin",
|
|
37
|
+
category: "resource",
|
|
38
|
+
command: ["admin", "organizations", "grant-credit"],
|
|
39
|
+
outcome: "Grant Ornn credits to a customer organization.",
|
|
40
|
+
human: "ornn admin organizations grant-credit --organization UUID --amount-cents N --note TEXT --idempotency-key UUID [--yes]",
|
|
41
|
+
automation: "Requires an eligible human terminal.",
|
|
42
|
+
acceptedReferences: ["organization UUID from admin organizations list"],
|
|
43
|
+
review: "Review the organization, USD amount, note, and grant UUID before default-no consent.",
|
|
44
|
+
related: ["admin organizations list", "admin users list"],
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
slack: {
|
|
48
|
+
parameters: {
|
|
49
|
+
type: "object",
|
|
50
|
+
properties: {
|
|
51
|
+
organizationId: {
|
|
52
|
+
type: "string",
|
|
53
|
+
format: "uuid",
|
|
54
|
+
description: "Exact customer organization UUID from organization_list.",
|
|
55
|
+
},
|
|
56
|
+
amountCents: {
|
|
57
|
+
type: "integer",
|
|
58
|
+
minimum: 1,
|
|
59
|
+
maximum: 100_000_000,
|
|
60
|
+
description: "USD cents: 10000 grants $100.00.",
|
|
61
|
+
},
|
|
62
|
+
note: {
|
|
63
|
+
type: "string",
|
|
64
|
+
description: "Reason for the credit grant, at most 500 UTF-8 bytes.",
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
required: ["organizationId", "amountCents", "note"],
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
input: creditGrantInput,
|
|
71
|
+
execute: async (ctx, input) => {
|
|
72
|
+
const { organizationId, confirm: _confirm, ...grant } = creditGrantInput.parse(input);
|
|
73
|
+
const body = { ...grant, direction: "credit" };
|
|
74
|
+
if (!ctx.confirmed)
|
|
75
|
+
return preview("credits.grant", { organizationId, ...body });
|
|
76
|
+
return ctx.fetch(`/internal/v1/organizations/${encodeURIComponent(organizationId)}/credit-adjustments`, { method: "POST", body });
|
|
77
|
+
},
|
|
78
|
+
});
|
|
@@ -27,7 +27,8 @@ const nodesInput = z.object({
|
|
|
27
27
|
facility_id: z.string().optional(),
|
|
28
28
|
facilityId: z.string().optional(),
|
|
29
29
|
status: z.string().optional(),
|
|
30
|
-
|
|
30
|
+
reservation_id: z.string().optional(),
|
|
31
|
+
reservationId: z.string().optional(),
|
|
31
32
|
cursor: z.string().optional(),
|
|
32
33
|
max: z.number().int().positive().max(5000).optional(),
|
|
33
34
|
limit: z.number().int().positive().max(5000).optional(),
|
|
@@ -113,7 +114,7 @@ export const listNodesCapability = defineCapability({
|
|
|
113
114
|
id: "nodes.list",
|
|
114
115
|
domain: "fleet",
|
|
115
116
|
roles: ["user", "admin"],
|
|
116
|
-
description: "List nodes. Users see their own reserved machines. Admins see enrolled, ghost, and off-grid nodes. Call with no filters for the whole fleet. Optional
|
|
117
|
+
description: "List nodes. Users see their own reserved machines. Admins see enrolled, ghost, and off-grid nodes. Call with no filters for the whole fleet. Optional reservation_id lists the nodes on one reservation (find a customer's reservations with reservation.list customerId). Optional organization_id (supplier), facility_id, and status (ghost, off-grid, bare-metal) narrow the same list. Paginate large fleets with cursor + max: pass the response's next_cursor back as cursor to fetch the next page; next_cursor is omitted once the page is not full. Never invent nodes. Never print private keys.",
|
|
117
118
|
mutation: "read",
|
|
118
119
|
http: { method: "GET", path: "/nodes" },
|
|
119
120
|
slack: {
|
|
@@ -128,9 +129,9 @@ export const listNodesCapability = defineCapability({
|
|
|
128
129
|
type: "string",
|
|
129
130
|
description: "Optional commerce facility UUID from facilities.list.",
|
|
130
131
|
},
|
|
131
|
-
|
|
132
|
+
reservation_id: {
|
|
132
133
|
type: "string",
|
|
133
|
-
description: "Optional
|
|
134
|
+
description: "Optional reservation UUID from reservation_list. Lists that reservation's nodes.",
|
|
134
135
|
},
|
|
135
136
|
status: {
|
|
136
137
|
type: "string",
|
|
@@ -162,7 +163,7 @@ export const listNodesCapability = defineCapability({
|
|
|
162
163
|
organization_id: parsed.organization_id ?? parsed.organizationId,
|
|
163
164
|
facility_id: parsed.facility_id ?? parsed.facilityId,
|
|
164
165
|
status: parsed.status,
|
|
165
|
-
|
|
166
|
+
reservation_id: parsed.reservation_id ?? parsed.reservationId,
|
|
166
167
|
...pagination,
|
|
167
168
|
}), { method: "GET" });
|
|
168
169
|
},
|
|
@@ -267,7 +267,7 @@ export const createListingCapability = defineCapability({
|
|
|
267
267
|
: !facilityQuery ||
|
|
268
268
|
[
|
|
269
269
|
facility.facilityId ?? facility.id,
|
|
270
|
-
facility.
|
|
270
|
+
facility.region,
|
|
271
271
|
facility.displayName ?? facility.display_name,
|
|
272
272
|
].some((value) => value?.toLowerCase() === facilityQuery))));
|
|
273
273
|
cursor = response.nextCursor ?? undefined;
|
|
@@ -2,6 +2,8 @@ import { z } from "zod";
|
|
|
2
2
|
import { preview, } from "../capability.js";
|
|
3
3
|
/** CLI semantic arguments arrive as strings; MCP and Slack send JSON values. */
|
|
4
4
|
export const positionals = z.array(z.string()).max(0).default([]);
|
|
5
|
+
/** Integer micro-USD per GPU-hour (1000000 = $1.00). */
|
|
6
|
+
export const microUsd = z.coerce.number().int().positive();
|
|
5
7
|
export const flag = z.preprocess((value) => (value === "true" ? true : value === "false" ? false : value), z.boolean());
|
|
6
8
|
export function seg(value) {
|
|
7
9
|
return encodeURIComponent(value);
|