@ornncompute/cli 0.2.24 → 1.0.34

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 (90) hide show
  1. package/README.md +351 -426
  2. package/package.json +12 -6
  3. package/src/admin-capacity-reservations.mjs +305 -0
  4. package/src/admin-command-handlers.mjs +57 -0
  5. package/src/admin-enrollment.mjs +681 -0
  6. package/src/admin-mutations.mjs +187 -0
  7. package/src/admin-node-diagnostics.mjs +412 -0
  8. package/src/admin-node-lifecycle.mjs +905 -0
  9. package/src/admin-shell.mjs +456 -0
  10. package/src/api-client.mjs +81 -28
  11. package/src/atomic-output.mjs +112 -0
  12. package/src/auth-store.mjs +182 -11
  13. package/src/capability-catalog.mjs +7 -0
  14. package/src/catalog-shell.mjs +1363 -0
  15. package/src/cli.mjs +123 -10266
  16. package/src/connect-mode-handlers.mjs +170 -0
  17. package/src/device-auth.mjs +179 -62
  18. package/src/evidence-commands.mjs +1354 -0
  19. package/src/external-bucket-commands.mjs +1374 -0
  20. package/src/gpu-catalog.mjs +21 -0
  21. package/src/identity-file.mjs +28 -0
  22. package/src/interaction-runtime.mjs +310 -0
  23. package/src/kubernetes-commands.mjs +2235 -0
  24. package/src/launch-mode-handlers.mjs +119 -0
  25. package/src/machine-reference.mjs +285 -0
  26. package/src/mount-commands.mjs +1148 -0
  27. package/src/session-commands.mjs +565 -0
  28. package/src/slurm-commands.mjs +274 -0
  29. package/src/startup.mjs +34 -11
  30. package/src/storage-commands.mjs +1164 -0
  31. package/src/update.mjs +172 -24
  32. package/src/vm-commands.mjs +4308 -0
  33. package/src/vpn-secret-output.mjs +150 -0
  34. package/src/wait-runtime.mjs +231 -0
  35. package/vendor/capabilities/capabilities/admin-capacity-reservations.js +96 -0
  36. package/vendor/capabilities/capabilities/admin-enrollment.js +321 -0
  37. package/vendor/capabilities/capabilities/admin-node-diagnostics.js +88 -0
  38. package/vendor/capabilities/capabilities/admin-nodes-lifecycle.js +235 -0
  39. package/vendor/capabilities/capabilities/admin.js +89 -0
  40. package/vendor/capabilities/capabilities/cli.js +156 -0
  41. package/vendor/capabilities/capabilities/clusters.js +150 -1
  42. package/vendor/capabilities/capabilities/commerce-market.js +330 -0
  43. package/vendor/capabilities/capabilities/compute.js +522 -0
  44. package/vendor/capabilities/capabilities/evidence.js +145 -0
  45. package/vendor/capabilities/capabilities/external-buckets.js +191 -0
  46. package/vendor/capabilities/capabilities/fleet.js +77 -25
  47. package/vendor/capabilities/capabilities/identity.js +114 -3
  48. package/vendor/capabilities/capabilities/listings.js +141 -144
  49. package/vendor/capabilities/capabilities/market-shared.js +44 -0
  50. package/vendor/capabilities/capabilities/markets.js +449 -0
  51. package/vendor/capabilities/capabilities/mounts.js +176 -0
  52. package/vendor/capabilities/capabilities/networks.js +1264 -0
  53. package/vendor/capabilities/capabilities/observability.js +38 -8
  54. package/vendor/capabilities/capabilities/tokens.js +32 -17
  55. package/vendor/capabilities/capabilities/volumes.js +178 -0
  56. package/vendor/capabilities/capability.js +21 -14
  57. package/vendor/capabilities/cli-catalog.js +67 -0
  58. package/vendor/capabilities/cli-migrations.js +261 -0
  59. package/vendor/capabilities/cli.js +7 -0
  60. package/vendor/capabilities/software-catalog.js +36 -0
  61. package/vendor/capabilities/spec-catalog.js +28 -5
  62. package/vendor/capabilities/supplier-directory.js +20 -0
  63. package/src/catalog-dispatch.mjs +0 -605
  64. package/src/color.mjs +0 -71
  65. package/vendor/capabilities/capabilities/clusters.d.ts +0 -2
  66. package/vendor/capabilities/capabilities/fleet.d.ts +0 -8
  67. package/vendor/capabilities/capabilities/identity.d.ts +0 -3
  68. package/vendor/capabilities/capabilities/listings.d.ts +0 -51
  69. package/vendor/capabilities/capabilities/observability.d.ts +0 -5
  70. package/vendor/capabilities/capabilities/staff-access.d.ts +0 -29
  71. package/vendor/capabilities/capabilities/staff-access.js +0 -278
  72. package/vendor/capabilities/capabilities/staff-commerce.d.ts +0 -160
  73. package/vendor/capabilities/capabilities/staff-commerce.js +0 -689
  74. package/vendor/capabilities/capabilities/staff-fleet.d.ts +0 -140
  75. package/vendor/capabilities/capabilities/staff-fleet.js +0 -538
  76. package/vendor/capabilities/capabilities/staff-gtm.d.ts +0 -142
  77. package/vendor/capabilities/capabilities/staff-gtm.js +0 -521
  78. package/vendor/capabilities/capabilities/staff-shared.d.ts +0 -47
  79. package/vendor/capabilities/capabilities/staff-shared.js +0 -98
  80. package/vendor/capabilities/capabilities/tokens.d.ts +0 -10
  81. package/vendor/capabilities/capabilities/users.d.ts +0 -2
  82. package/vendor/capabilities/capability.d.ts +0 -93
  83. package/vendor/capabilities/catalog.d.ts +0 -3
  84. package/vendor/capabilities/catalog.js +0 -26
  85. package/vendor/capabilities/index.d.ts +0 -16
  86. package/vendor/capabilities/index.js +0 -16
  87. package/vendor/capabilities/names.d.ts +0 -6
  88. package/vendor/capabilities/role.d.ts +0 -6
  89. package/vendor/capabilities/role.js +0 -17
  90. package/vendor/capabilities/spec-catalog.d.ts +0 -78
@@ -0,0 +1,449 @@
1
+ import { z } from "zod";
2
+ import { defineCapability } from "../capability.js";
3
+ import { gpuDisplay, loadHardwareCatalog } from "../spec-catalog.js";
4
+ import { consent, idempotencyHeaders, microUsdLabel, positionals, query, seg, } from "./market-shared.js";
5
+ // Markets verbs: one capability per `/v1/markets` route. Spot order books are
6
+ // per GPU slug; schedules are per listing. Prices are integer micro-USD per
7
+ // GPU-hour, exactly as Markets reads and writes them.
8
+ const BASE = "/v1/markets";
9
+ function help(command, outcome, human, automation, related, order, acceptedReferences = []) {
10
+ return {
11
+ arguments: "semantic",
12
+ audience: "customer",
13
+ category: "resource",
14
+ command,
15
+ outcome,
16
+ human,
17
+ automation,
18
+ acceptedReferences,
19
+ related,
20
+ order,
21
+ };
22
+ }
23
+ const microUsd = z.coerce.number().int().positive();
24
+ export const spotBooksInput = z.object({ $positionals: positionals });
25
+ export const spotBooksCapability = defineCapability({
26
+ id: "spot.books",
27
+ domain: "markets",
28
+ roles: ["user"],
29
+ description: "List the spot order books, one per GPU type, with the best bid and ask in micro-USD per GPU-hour.",
30
+ mutation: "read",
31
+ http: { method: "GET", path: `${BASE}/spot/books` },
32
+ input: spotBooksInput,
33
+ cli: {
34
+ command: ["spot", "books"],
35
+ vnext: help(["spot", "books"], "List spot order books by GPU type with their best bid and ask.", "ornn spot books", "ornn spot books --format json", ["spot book", "spot bid"], 30),
36
+ },
37
+ slack: false,
38
+ execute: async (ctx) => ctx.fetch(`${BASE}/spot/books`, { method: "GET" }),
39
+ });
40
+ export const spotBookInput = z.object({
41
+ $positionals: positionals,
42
+ gpuSlug: z.string().min(1).describe("Catalog GPU slug, for example nvidia_h100_sxm."),
43
+ });
44
+ export const spotBookCapability = defineCapability({
45
+ id: "spot.book",
46
+ domain: "markets",
47
+ roles: ["user"],
48
+ description: "Show one spot order book's depth: GPUs and order counts at each bid and ask price level.",
49
+ mutation: "read",
50
+ http: { method: "GET", path: `${BASE}/spot/books/{gpuSlug}` },
51
+ input: spotBookInput,
52
+ cli: {
53
+ command: ["spot", "book"],
54
+ vnext: help(["spot", "book"], "Show bid and ask depth for one GPU type.", "ornn spot book --gpu-slug <slug>", "ornn spot book --gpu-slug <slug> --format json", ["spot books", "spot trades", "spot bid"], 31, ["catalog GPU slug"]),
55
+ },
56
+ slack: false,
57
+ execute: async (ctx, input) => {
58
+ const parsed = spotBookInput.parse(input);
59
+ return ctx.fetch(`${BASE}/spot/books/${seg(parsed.gpuSlug)}`, { method: "GET" });
60
+ },
61
+ });
62
+ export const spotTradesCapability = defineCapability({
63
+ id: "spot.trades",
64
+ domain: "markets",
65
+ roles: ["user"],
66
+ description: "List recent spot trades for one GPU type: price, GPUs and time, with no identities.",
67
+ mutation: "read",
68
+ http: { method: "GET", path: `${BASE}/spot/books/{gpuSlug}/trades` },
69
+ input: spotBookInput,
70
+ cli: {
71
+ command: ["spot", "trades"],
72
+ vnext: help(["spot", "trades"], "List recent spot fills for one GPU type.", "ornn spot trades --gpu-slug <slug>", "ornn spot trades --gpu-slug <slug> --format json", ["spot book"], 32, ["catalog GPU slug"]),
73
+ },
74
+ slack: false,
75
+ execute: async (ctx, input) => {
76
+ const parsed = spotBookInput.parse(input);
77
+ return ctx.fetch(`${BASE}/spot/books/${seg(parsed.gpuSlug)}/trades`, { method: "GET" });
78
+ },
79
+ });
80
+ export const spotBidInput = z
81
+ .object({
82
+ $positionals: positionals,
83
+ gpuSlug: z.string().min(1).describe("Catalog GPU slug, for example nvidia_h100_sxm."),
84
+ gpus: z.coerce
85
+ .number()
86
+ .int()
87
+ .min(1)
88
+ .max(8)
89
+ .describe("GPUs on one node, 1-8. To buy N whole nodes, place N bids of 8 GPUs."),
90
+ orderType: z
91
+ .enum(["limit", "market"])
92
+ .default("limit")
93
+ .describe("A limit bid rests until filled; a market bid fills now or is cancelled."),
94
+ pricePerGpuHourMicroUsd: microUsd
95
+ .optional()
96
+ .describe("Limit price in micro-USD per GPU-hour (1000000 = $1.00). Required for limit bids."),
97
+ idempotencyKey: z.string().min(1).max(200).optional(),
98
+ confirm: z.boolean().optional(),
99
+ })
100
+ .refine((bid) => (bid.orderType === "limit") === (bid.pricePerGpuHourMicroUsd !== undefined), {
101
+ message: "A limit bid needs pricePerGpuHourMicroUsd; a market bid takes no price.",
102
+ path: ["pricePerGpuHourMicroUsd"],
103
+ });
104
+ export const spotBidCapability = defineCapability({
105
+ id: "spot.bid",
106
+ domain: "markets",
107
+ roles: ["user"],
108
+ description: "Place a spot bid for 1-8 GPUs on one node. A filled bid becomes a running spot reservation, " +
109
+ "prepaid hourly from Ornn credits first and then the saved card; the side that takes a resting order pays the transaction fee. Requires confirm: true.",
110
+ mutation: "preview-confirm",
111
+ http: { method: "POST", path: `${BASE}/spot/orders` },
112
+ input: spotBidInput,
113
+ cli: {
114
+ command: ["spot", "bid"],
115
+ vnext: help(["spot", "bid"], "Place one spot bid for 1-8 GPUs on a single node.", "ornn spot bid --gpu-slug <slug> --gpus <1-8> [--order-type limit|market] [--price-per-gpu-hour-micro-usd <n>]", "ornn spot bid --gpu-slug <slug> --gpus 8 --price-per-gpu-hour-micro-usd 2500000 --yes --format json", ["spot orders", "spot cancel"], 33, ["catalog GPU slug"]),
116
+ },
117
+ slack: false,
118
+ execute: async (ctx, input) => {
119
+ const parsed = spotBidInput.parse(input);
120
+ const body = {
121
+ gpuSlug: parsed.gpuSlug,
122
+ gpus: parsed.gpus,
123
+ orderType: parsed.orderType,
124
+ ...(parsed.pricePerGpuHourMicroUsd !== undefined
125
+ ? { pricePerGpuHourMicroUsd: parsed.pricePerGpuHourMicroUsd }
126
+ : {}),
127
+ };
128
+ // Only the CLI review renders terms; MCP and Slack preview the payload.
129
+ const hardware = ctx.cli?.interaction
130
+ ? await loadHardwareCatalog(ctx.fetch).catch(() => null)
131
+ : null;
132
+ const gate = await consent(ctx, "spot.bid", "Place spot bid", [
133
+ { label: "GPU type", value: gpuDisplay(hardware?.terms ?? [], parsed.gpuSlug) },
134
+ { label: "GPUs", value: String(parsed.gpus) },
135
+ { label: "Order", value: parsed.orderType },
136
+ { label: "Limit price", value: microUsdLabel(parsed.pricePerGpuHourMicroUsd) },
137
+ ], body);
138
+ if (gate)
139
+ return gate;
140
+ return ctx.fetch(`${BASE}/spot/orders`, {
141
+ method: "POST",
142
+ body,
143
+ headers: idempotencyHeaders(ctx, parsed.idempotencyKey),
144
+ });
145
+ },
146
+ });
147
+ export const spotOrdersInput = z.object({ $positionals: positionals });
148
+ export const spotOrdersCapability = defineCapability({
149
+ id: "spot.orders",
150
+ domain: "markets",
151
+ roles: ["user"],
152
+ description: "List this organization's spot bids (resting, running and ended) and the asks its listings have on the books.",
153
+ mutation: "read",
154
+ http: { method: "GET", path: `${BASE}/spot/orders` },
155
+ input: spotOrdersInput,
156
+ cli: {
157
+ command: ["spot", "orders"],
158
+ vnext: help(["spot", "orders"], "List your organization's spot bids and listing asks.", "ornn spot orders", "ornn spot orders --format json", ["spot bid", "spot cancel"], 34),
159
+ },
160
+ slack: false,
161
+ execute: async (ctx) => ctx.fetch(`${BASE}/spot/orders`, { method: "GET" }),
162
+ });
163
+ export const spotCancelInput = z.object({
164
+ $positionals: positionals,
165
+ orderId: z
166
+ .string()
167
+ .uuid()
168
+ .describe("A spot bid ID; for a running bid this is also its reservation ID."),
169
+ confirm: z.boolean().optional(),
170
+ });
171
+ export const spotCancelCapability = defineCapability({
172
+ id: "spot.cancel",
173
+ domain: "markets",
174
+ roles: ["user"],
175
+ description: "Cancel a resting spot bid, or end a running spot reservation (the buyer or the seller may end it; the GPUs go back on the book). Requires confirm: true.",
176
+ mutation: "preview-confirm",
177
+ http: { method: "DELETE", path: `${BASE}/spot/orders/{orderId}` },
178
+ input: spotCancelInput,
179
+ cli: {
180
+ command: ["spot", "cancel"],
181
+ vnext: help(["spot", "cancel"], "Cancel a resting bid or end a running spot reservation.", "ornn spot cancel --order-id <id>", "ornn spot cancel --order-id <id> --yes --format json", ["spot orders"], 35, ["spot order ID", "spot reservation ID"]),
182
+ },
183
+ slack: false,
184
+ execute: async (ctx, input) => {
185
+ const parsed = spotCancelInput.parse(input);
186
+ const gate = await consent(ctx, "spot.cancel", "Cancel spot order", [
187
+ { label: "Order", value: parsed.orderId },
188
+ {
189
+ label: "Consequence",
190
+ value: "A resting bid is cancelled; a running spot reservation ends and its machine stops.",
191
+ },
192
+ ], { orderId: parsed.orderId });
193
+ if (gate)
194
+ return gate;
195
+ return ctx.fetch(`${BASE}/spot/orders/${seg(parsed.orderId)}`, { method: "DELETE" });
196
+ },
197
+ });
198
+ export const schedulesListInput = z.object({
199
+ $positionals: positionals,
200
+ listingKind: z.enum(["primary", "secondary"]).optional(),
201
+ gpuSlug: z.string().min(1).optional(),
202
+ from: z.string().min(1).optional().describe("RFC 3339 lower bound for the schedule window."),
203
+ to: z.string().min(1).optional().describe("RFC 3339 upper bound for the schedule window."),
204
+ });
205
+ export const schedulesListCapability = defineCapability({
206
+ id: "schedules.list",
207
+ domain: "markets",
208
+ roles: ["user"],
209
+ description: "List reservable schedules with their available windows (sold windows removed). Primary schedules are shown only to approved organizations; secondary (relisted) schedules are open to every signed-in organization.",
210
+ mutation: "read",
211
+ http: { method: "GET", path: `${BASE}/schedules` },
212
+ input: schedulesListInput,
213
+ cli: {
214
+ command: ["schedules", "list"],
215
+ vnext: help(["schedules", "list"], "List primary and secondary schedules with their open windows.", "ornn schedules list [--listing-kind primary|secondary] [--gpu-slug <slug>] [--from <time>] [--to <time>]", "ornn schedules list --listing-kind secondary --format json", ["schedules show", "schedules buy", "offers post"], 40),
216
+ },
217
+ slack: false,
218
+ execute: async (ctx, input) => {
219
+ const parsed = schedulesListInput.parse(input ?? {});
220
+ return ctx.fetch(`${BASE}/schedules${query({
221
+ listingKind: parsed.listingKind,
222
+ gpuSlug: parsed.gpuSlug,
223
+ from: parsed.from,
224
+ to: parsed.to,
225
+ })}`, { method: "GET" });
226
+ },
227
+ });
228
+ export const scheduleShowInput = z.object({
229
+ $positionals: positionals,
230
+ listingId: z.string().uuid(),
231
+ });
232
+ export const scheduleShowCapability = defineCapability({
233
+ id: "schedules.show",
234
+ domain: "markets",
235
+ roles: ["user"],
236
+ description: "Show one listing's schedule: a primary's price, down payment and open offer book, or a secondary's asking price, and its sold windows.",
237
+ mutation: "read",
238
+ http: { method: "GET", path: `${BASE}/schedules/{listingId}` },
239
+ input: scheduleShowInput,
240
+ cli: {
241
+ command: ["schedules", "show"],
242
+ vnext: help(["schedules", "show"], "Show one schedule with its sold windows and offers.", "ornn schedules show --listing-id <id>", "ornn schedules show --listing-id <id> --format json", ["schedules list", "schedules buy", "offers post", "offers accept"], 41, ["listing ID"]),
243
+ },
244
+ slack: false,
245
+ execute: async (ctx, input) => {
246
+ const parsed = scheduleShowInput.parse(input);
247
+ return ctx.fetch(`${BASE}/schedules/${seg(parsed.listingId)}`, { method: "GET" });
248
+ },
249
+ });
250
+ export const schedulePurchaseInput = z.object({
251
+ $positionals: positionals,
252
+ listingId: z.string().uuid().describe("A secondary (relisted) listing."),
253
+ startsAt: z
254
+ .string()
255
+ .min(1)
256
+ .describe("RFC 3339 start of the window: inside the listing, now or later, on the hour."),
257
+ endsAt: z
258
+ .string()
259
+ .min(1)
260
+ .describe("RFC 3339 end of the window, at least an hour after the start."),
261
+ askPricePerGpuHourMicroUsd: microUsd.describe("The listing's current asking price in micro-USD per GPU-hour, from schedules show. A changed price refuses the purchase."),
262
+ idempotencyKey: z.string().min(1).max(200).optional(),
263
+ confirm: z.boolean().optional(),
264
+ });
265
+ export const schedulePurchaseCapability = defineCapability({
266
+ id: "schedules.purchase",
267
+ domain: "markets",
268
+ roles: ["user"],
269
+ description: "Buy a window of a secondary (relisted) listing at its asking price, for every node on it. There is no offer: the window is yours once it is paid. You pay the asking price plus the Ornn trading fee up front, from Ornn credits and then your saved card; $10,000 or more is invoiced by wire. Requires confirm: true.",
270
+ mutation: "preview-confirm",
271
+ http: { method: "POST", path: `${BASE}/schedules/purchases` },
272
+ input: schedulePurchaseInput,
273
+ cli: {
274
+ command: ["schedules", "buy"],
275
+ vnext: help(["schedules", "buy"], "Buy a window of relisted capacity at its asking price.", "ornn schedules buy --listing-id <id> --starts-at <time> --ends-at <time> --ask-price-per-gpu-hour-micro-usd <n>", "ornn schedules buy --listing-id <id> --starts-at <time> --ends-at <time> --ask-price-per-gpu-hour-micro-usd 4000000 --yes --format json", ["schedules show", "reservations show"], 42, ["listing ID"]),
276
+ },
277
+ slack: false,
278
+ execute: async (ctx, input) => {
279
+ const parsed = schedulePurchaseInput.parse(input);
280
+ const body = {
281
+ legs: [
282
+ {
283
+ listingId: parsed.listingId,
284
+ startsAt: parsed.startsAt,
285
+ endsAt: parsed.endsAt,
286
+ askPricePerGpuHourMicroUsd: parsed.askPricePerGpuHourMicroUsd,
287
+ },
288
+ ],
289
+ };
290
+ const gate = await consent(ctx, "schedules.purchase", "Buy relisted window", [
291
+ { label: "Listing", value: parsed.listingId },
292
+ { label: "Window", value: `${parsed.startsAt} to ${parsed.endsAt}` },
293
+ { label: "Asking price", value: microUsdLabel(parsed.askPricePerGpuHourMicroUsd) },
294
+ {
295
+ label: "Payment",
296
+ value: "Up front, with the Ornn trading fee on top: Ornn credits, then your saved card.",
297
+ },
298
+ ], body);
299
+ if (gate)
300
+ return gate;
301
+ return ctx.fetch(`${BASE}/schedules/purchases`, {
302
+ method: "POST",
303
+ body,
304
+ headers: idempotencyHeaders(ctx, parsed.idempotencyKey),
305
+ });
306
+ },
307
+ });
308
+ export const offersListInput = z.object({ $positionals: positionals });
309
+ export const offersListCapability = defineCapability({
310
+ id: "offers.list",
311
+ domain: "markets",
312
+ roles: ["user"],
313
+ description: "List the offers this organization has posted on schedules, with their status.",
314
+ mutation: "read",
315
+ http: { method: "GET", path: `${BASE}/offers` },
316
+ input: offersListInput,
317
+ cli: {
318
+ command: ["offers", "list"],
319
+ vnext: help(["offers", "list"], "List the offers your organization has posted.", "ornn offers list", "ornn offers list --format json", ["offers post", "offers cancel"], 42),
320
+ },
321
+ slack: false,
322
+ execute: async (ctx) => ctx.fetch(`${BASE}/offers`, { method: "GET" }),
323
+ });
324
+ export const offerPostInput = z.object({
325
+ $positionals: positionals,
326
+ listingId: z.string().uuid(),
327
+ pricePerGpuHourMicroUsd: microUsd.describe("Offer price in micro-USD per GPU-hour (1000000 = $1.00). Covers every node for the rest of the listing."),
328
+ downPaymentCents: z.coerce
329
+ .number()
330
+ .int()
331
+ .min(0)
332
+ .optional()
333
+ .describe("Primary listings only: the down payment you offer, in cents."),
334
+ idempotencyKey: z.string().min(1).max(200).optional(),
335
+ confirm: z.boolean().optional(),
336
+ });
337
+ export const offerPostCapability = defineCapability({
338
+ id: "offers.post",
339
+ domain: "markets",
340
+ roles: ["user"],
341
+ description: "Offer a price for a whole primary listing: every node, from the later of the listing's start and now to its end. The listing owner accepts it to create your reservation. Relisted (secondary) capacity takes no offers: buy it with schedules purchase. Requires confirm: true.",
342
+ mutation: "preview-confirm",
343
+ http: { method: "POST", path: `${BASE}/schedules/{listingId}/offers` },
344
+ input: offerPostInput,
345
+ cli: {
346
+ command: ["offers", "post"],
347
+ vnext: help(["offers", "post"], "Offer a price for a whole primary listing.", "ornn offers post --listing-id <id> --price-per-gpu-hour-micro-usd <n> [--down-payment-cents <n>]", "ornn offers post --listing-id <id> --price-per-gpu-hour-micro-usd 2500000 --yes --format json", ["schedules show", "offers list", "offers cancel"], 43, ["listing ID"]),
348
+ },
349
+ slack: false,
350
+ execute: async (ctx, input) => {
351
+ const parsed = offerPostInput.parse(input);
352
+ const body = {
353
+ pricePerGpuHourMicroUsd: parsed.pricePerGpuHourMicroUsd,
354
+ ...(parsed.downPaymentCents !== undefined
355
+ ? { downPaymentCents: parsed.downPaymentCents }
356
+ : {}),
357
+ };
358
+ const gate = await consent(ctx, "offers.post", "Post schedule offer", [
359
+ { label: "Listing", value: parsed.listingId },
360
+ { label: "Window", value: "The rest of the listing" },
361
+ { label: "Price", value: microUsdLabel(parsed.pricePerGpuHourMicroUsd) },
362
+ ...(parsed.downPaymentCents !== undefined
363
+ ? [{ label: "Down payment", value: `$${(parsed.downPaymentCents / 100).toFixed(2)}` }]
364
+ : []),
365
+ ], { listingId: parsed.listingId, ...body });
366
+ if (gate)
367
+ return gate;
368
+ return ctx.fetch(`${BASE}/schedules/${seg(parsed.listingId)}/offers`, {
369
+ method: "POST",
370
+ body,
371
+ headers: idempotencyHeaders(ctx, parsed.idempotencyKey),
372
+ });
373
+ },
374
+ });
375
+ export const offerDecisionInput = z.object({
376
+ $positionals: positionals,
377
+ listingId: z.string().uuid(),
378
+ offerId: z.string().uuid(),
379
+ confirm: z.boolean().optional(),
380
+ });
381
+ export const offerAcceptCapability = defineCapability({
382
+ id: "offers.accept",
383
+ domain: "markets",
384
+ roles: ["user"],
385
+ description: "Accept an offer on a primary listing you own. The accepted window becomes the buyer's reservation and overlapping offers and relists from the same holding are cancelled. Requires confirm: true.",
386
+ mutation: "preview-confirm",
387
+ http: { method: "POST", path: `${BASE}/schedules/{listingId}/offers/{offerId}/accept` },
388
+ input: offerDecisionInput,
389
+ cli: {
390
+ command: ["offers", "accept"],
391
+ vnext: help(["offers", "accept"], "Accept one offer on a listing you own.", "ornn offers accept --listing-id <id> --offer-id <id>", "ornn offers accept --listing-id <id> --offer-id <id> --yes --format json", ["schedules show"], 44, ["listing ID", "offer ID"]),
392
+ },
393
+ slack: false,
394
+ execute: async (ctx, input) => {
395
+ const parsed = offerDecisionInput.parse(input);
396
+ const gate = await consent(ctx, "offers.accept", "Accept schedule offer", [
397
+ { label: "Listing", value: parsed.listingId },
398
+ { label: "Offer", value: parsed.offerId },
399
+ {
400
+ label: "Consequence",
401
+ value: "The window is sold; overlapping open offers and conflicting relists from this holding are cancelled.",
402
+ },
403
+ ], { listingId: parsed.listingId, offerId: parsed.offerId });
404
+ if (gate)
405
+ return gate;
406
+ return ctx.fetch(`${BASE}/schedules/${seg(parsed.listingId)}/offers/${seg(parsed.offerId)}/accept`, { method: "POST", body: {} });
407
+ },
408
+ });
409
+ export const offerCancelCapability = defineCapability({
410
+ id: "offers.cancel",
411
+ domain: "markets",
412
+ roles: ["user"],
413
+ description: "Cancel an open offer you posted, or one on a listing you own. Requires confirm: true.",
414
+ mutation: "preview-confirm",
415
+ http: { method: "DELETE", path: `${BASE}/schedules/{listingId}/offers/{offerId}` },
416
+ input: offerDecisionInput,
417
+ cli: {
418
+ command: ["offers", "cancel"],
419
+ vnext: help(["offers", "cancel"], "Cancel one open offer.", "ornn offers cancel --listing-id <id> --offer-id <id>", "ornn offers cancel --listing-id <id> --offer-id <id> --yes --format json", ["offers list"], 45, ["listing ID", "offer ID"]),
420
+ },
421
+ slack: false,
422
+ execute: async (ctx, input) => {
423
+ const parsed = offerDecisionInput.parse(input);
424
+ const gate = await consent(ctx, "offers.cancel", "Cancel schedule offer", [
425
+ { label: "Listing", value: parsed.listingId },
426
+ { label: "Offer", value: parsed.offerId },
427
+ ], { listingId: parsed.listingId, offerId: parsed.offerId });
428
+ if (gate)
429
+ return gate;
430
+ return ctx.fetch(`${BASE}/schedules/${seg(parsed.listingId)}/offers/${seg(parsed.offerId)}`, {
431
+ method: "DELETE",
432
+ });
433
+ },
434
+ });
435
+ export const marketCapabilities = [
436
+ spotBooksCapability,
437
+ spotBookCapability,
438
+ spotTradesCapability,
439
+ spotBidCapability,
440
+ spotOrdersCapability,
441
+ spotCancelCapability,
442
+ schedulesListCapability,
443
+ scheduleShowCapability,
444
+ schedulePurchaseCapability,
445
+ offersListCapability,
446
+ offerPostCapability,
447
+ offerAcceptCapability,
448
+ offerCancelCapability,
449
+ ];
@@ -0,0 +1,176 @@
1
+ import { z } from "zod";
2
+ import { defineCapability, } from "../capability.js";
3
+ const mountInput = z
4
+ .object({
5
+ $positionals: z.array(z.string()).default([]),
6
+ access: z.string().optional(),
7
+ capacityGib: z.union([z.string(), z.number()]).optional(),
8
+ cursor: z.string().optional(),
9
+ idempotencyKey: z.string().optional(),
10
+ limit: z.union([z.string(), z.number()]).optional(),
11
+ noWait: z.boolean().optional(),
12
+ performanceTier: z.string().optional(),
13
+ timeout: z.string().optional(),
14
+ volume: z.string().optional(),
15
+ wait: z.boolean().optional(),
16
+ yes: z.boolean().optional(),
17
+ })
18
+ .strict();
19
+ function mountExecutor(context) {
20
+ const interaction = context.cli?.interaction;
21
+ if (!interaction?.mounts) {
22
+ throw new Error("This capability must execute through the Ornn CLI mount runtime.");
23
+ }
24
+ return interaction.mounts.bind(interaction);
25
+ }
26
+ function mountCapability({ acceptedReferences = [], automation, command, description, human, id, mutation, order, outcome, related = [], resumableStates = [], review, }) {
27
+ return defineCapability({
28
+ id,
29
+ domain: "storage",
30
+ roles: ["user"],
31
+ description,
32
+ mutation,
33
+ input: mountInput,
34
+ cli: {
35
+ command,
36
+ vnext: {
37
+ arguments: "semantic",
38
+ audience: "customer",
39
+ category: "resource",
40
+ order,
41
+ outcome,
42
+ human,
43
+ automation,
44
+ acceptedReferences,
45
+ related,
46
+ resumableStates,
47
+ ...(review ? { review } : {}),
48
+ },
49
+ },
50
+ mcp: false,
51
+ slack: false,
52
+ execute: async (context, input) => mountExecutor(context)(id, input),
53
+ });
54
+ }
55
+ export const mountCapabilities = [
56
+ mountCapability({
57
+ id: "storage.volume-mounts.candidates",
58
+ command: ["volume-mounts", "candidates"],
59
+ description: "List capacity reservations eligible for a managed-volume mount.",
60
+ mutation: "read",
61
+ order: 540,
62
+ outcome: "List one bounded page of safe Bare Metal volume-mount candidates.",
63
+ human: "ornn volume-mounts candidates [--limit <1-500>] [--cursor <cursor>]",
64
+ automation: "ornn volume-mounts candidates --limit 50 --format json",
65
+ related: ["volume-mounts attach", "volumes list"],
66
+ }),
67
+ mountCapability({
68
+ id: "storage.volume-mounts.show",
69
+ command: ["volume-mounts", "show"],
70
+ description: "Show one reservation's exact managed-volume relationship and per-machine readiness.",
71
+ mutation: "read",
72
+ order: 541,
73
+ outcome: "Show actual per-machine mount state without rewriting volume lifecycle.",
74
+ human: "ornn volume-mounts show <canonical-reservation-id>",
75
+ automation: "ornn volume-mounts show <canonical-reservation-id> --format json",
76
+ acceptedReferences: ["canonical capacity-reservation ID"],
77
+ related: ["volume-mounts wait", "volumes show"],
78
+ }),
79
+ mountCapability({
80
+ id: "storage.volume-mounts.attach",
81
+ command: ["volume-mounts", "attach"],
82
+ description: "Attach one exact managed volume to every machine in one exact capacity reservation.",
83
+ mutation: "preview-confirm",
84
+ order: 542,
85
+ outcome: "Reconcile one exact volume-mount goal and preserve per-machine readiness.",
86
+ human: "ornn volume-mounts attach <canonical-reservation-id> --volume <exact-name-or-id> --access read-only|read-write [--no-wait]",
87
+ automation: "ornn volume-mounts attach <canonical-reservation-id> --volume <canonical-volume-id> --access read-only|read-write --yes --idempotency-key <key> --format json",
88
+ acceptedReferences: [
89
+ "canonical capacity-reservation ID",
90
+ "canonical or exact unique managed-volume reference",
91
+ ],
92
+ review: "Review the exact capacity reservation, volume, and access mode.",
93
+ resumableStates: ["accepted", "partial", "ready"],
94
+ related: ["volume-mounts wait", "volume-mounts detach"],
95
+ }),
96
+ mountCapability({
97
+ id: "storage.volume-mounts.wait",
98
+ command: ["volume-mounts", "wait"],
99
+ description: "Wait locally for every requested machine mount to reach a durable terminal state.",
100
+ mutation: "read",
101
+ order: 543,
102
+ outcome: "Wait with a bounded deadline while preserving useful per-machine partial data.",
103
+ human: "ornn volume-mounts wait <canonical-reservation-id> [--timeout <duration>]",
104
+ automation: "ornn volume-mounts wait <canonical-reservation-id> --timeout <duration> --format json",
105
+ acceptedReferences: ["canonical capacity-reservation ID"],
106
+ resumableStates: ["partial", "ready"],
107
+ related: ["volume-mounts show", "volume-mounts detach"],
108
+ }),
109
+ mountCapability({
110
+ id: "storage.volume-mounts.detach",
111
+ command: ["volume-mounts", "detach"],
112
+ description: "Detach one exact reservation relationship without deleting its volume.",
113
+ mutation: "preview-confirm",
114
+ order: 544,
115
+ outcome: "Reconcile an exact detached goal and preserve the underlying volume.",
116
+ human: "ornn volume-mounts detach <canonical-reservation-id>",
117
+ automation: "ornn volume-mounts detach <canonical-reservation-id> --yes --idempotency-key <key> --format json",
118
+ acceptedReferences: ["canonical capacity-reservation ID"],
119
+ review: "Review the exact relationship and access loss; the managed volume remains.",
120
+ resumableStates: ["detaching", "ended"],
121
+ related: ["volumes show", "private-networks list"],
122
+ }),
123
+ mountCapability({
124
+ id: "storage.filesystems.deploy",
125
+ command: ["filesystems", "deploy"],
126
+ description: "Deploy one managed filesystem for an exact capacity reservation.",
127
+ mutation: "preview-confirm",
128
+ order: 550,
129
+ outcome: "Create or rediscover one filesystem deployment and expose every machine mount.",
130
+ human: "ornn filesystems deploy <canonical-reservation-id> --performance-tier <tier> --capacity-gib <gib> [--no-wait]",
131
+ automation: "ornn filesystems deploy <canonical-reservation-id> --performance-tier <tier> --capacity-gib <gib> --yes --idempotency-key <key> --format json",
132
+ acceptedReferences: ["canonical capacity-reservation ID"],
133
+ review: "Review the exact reservation, performance tier, capacity, and managed filesystem consequence.",
134
+ resumableStates: ["accepted", "partial", "ready"],
135
+ related: ["filesystems wait", "filesystems delete"],
136
+ }),
137
+ mountCapability({
138
+ id: "storage.filesystems.show",
139
+ command: ["filesystems", "show"],
140
+ description: "Summarize one filesystem deployment from its underlying per-machine mounts.",
141
+ mutation: "read",
142
+ order: 551,
143
+ outcome: "Show actual per-machine filesystem readiness without changing volume state.",
144
+ human: "ornn filesystems show <canonical-reservation-id>",
145
+ automation: "ornn filesystems show <canonical-reservation-id> --format json",
146
+ acceptedReferences: ["canonical capacity-reservation ID"],
147
+ related: ["filesystems wait", "volume-mounts show"],
148
+ }),
149
+ mountCapability({
150
+ id: "storage.filesystems.wait",
151
+ command: ["filesystems", "wait"],
152
+ description: "Wait locally for a filesystem deployment's per-machine mount goal.",
153
+ mutation: "read",
154
+ order: 552,
155
+ outcome: "Wait for every filesystem member while retaining partial member data.",
156
+ human: "ornn filesystems wait <canonical-reservation-id> [--timeout <duration>]",
157
+ automation: "ornn filesystems wait <canonical-reservation-id> --timeout <duration> --format json",
158
+ acceptedReferences: ["canonical capacity-reservation ID"],
159
+ resumableStates: ["partial", "ready"],
160
+ related: ["filesystems show", "filesystems delete"],
161
+ }),
162
+ mountCapability({
163
+ id: "storage.filesystems.delete",
164
+ command: ["filesystems", "delete"],
165
+ description: "Remove one filesystem deployment relationship without deleting its managed volume.",
166
+ mutation: "preview-confirm",
167
+ order: 553,
168
+ outcome: "Detach every filesystem member while preserving the underlying managed volume.",
169
+ human: "ornn filesystems delete <canonical-reservation-id>",
170
+ automation: "ornn filesystems delete <canonical-reservation-id> --yes --idempotency-key <key> --format json",
171
+ acceptedReferences: ["canonical capacity-reservation ID"],
172
+ review: "Review the exact deployment and access loss; its managed volume remains.",
173
+ resumableStates: ["detaching", "ended"],
174
+ related: ["volumes show", "filesystems deploy"],
175
+ }),
176
+ ];