@ornncompute/cli 1.0.35 → 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.
Files changed (31) hide show
  1. package/README.md +92 -47
  2. package/package.json +2 -2
  3. package/src/admin-capacity-reservations.mjs +65 -153
  4. package/src/admin-command-handlers.mjs +2 -0
  5. package/src/admin-credits.mjs +57 -0
  6. package/src/admin-node-lifecycle.mjs +48 -7
  7. package/src/admin-shell.mjs +113 -27
  8. package/src/catalog-shell.mjs +13 -21
  9. package/src/evidence-commands.mjs +30 -12
  10. package/src/kubernetes-commands.mjs +58 -16
  11. package/src/launch-mode-handlers.mjs +2 -2
  12. package/src/storage-commands.mjs +9 -1
  13. package/src/vm-commands.mjs +3 -3
  14. package/vendor/capabilities/capabilities/admin-capacity-reservations.js +55 -57
  15. package/vendor/capabilities/capabilities/admin-enrollment.js +3 -3
  16. package/vendor/capabilities/capabilities/admin.js +9 -7
  17. package/vendor/capabilities/capabilities/commerce-market.js +179 -104
  18. package/vendor/capabilities/capabilities/compute.js +4 -2
  19. package/vendor/capabilities/capabilities/credits.js +78 -0
  20. package/vendor/capabilities/capabilities/fleet.js +6 -5
  21. package/vendor/capabilities/capabilities/listings.js +58 -38
  22. package/vendor/capabilities/capabilities/market-shared.js +2 -0
  23. package/vendor/capabilities/capabilities/markets.js +254 -221
  24. package/vendor/capabilities/capabilities/native-listing.js +63 -0
  25. package/vendor/capabilities/capabilities/organization-invites.js +53 -0
  26. package/vendor/capabilities/capabilities/reservation-list.js +68 -0
  27. package/vendor/capabilities/capabilities/staff-shared.js +64 -0
  28. package/vendor/capabilities/capabilities/users.js +141 -31
  29. package/vendor/capabilities/cli-catalog.js +8 -2
  30. package/vendor/capabilities/cli-migrations.js +13 -10
  31. package/vendor/capabilities/software-catalog.js +52 -6
@@ -46,22 +46,16 @@ export const listingCreateInput = z.object({
46
46
  .describe("End of the availability window. YYYY-MM-DD or RFC3339."),
47
47
  available_from: z.string().optional().describe("Alias for available_start_at."),
48
48
  available_to: z.string().optional().describe("Alias for available_end_at."),
49
- buy_now_price_per_gpu_hour: z
50
- .number()
51
- .optional()
52
- .describe("Price buyers pay, USD per GPU-hour. The spread over cost_per_gpu_hour is Ornn's."),
53
- cost_per_gpu_hour: z
54
- .number()
55
- .optional()
56
- .describe("Supplier price, USD per GPU-hour. Defaults to the buy-now price (spread 0)."),
49
+ buy_now_price_per_gpu_hour: z.number().optional().describe("Price buyers pay, USD per GPU-hour."),
57
50
  deposit_percent: z.number().optional(),
58
51
  down_payment_cents: z.number().int().nonnegative().optional(),
59
52
  access_mode: z.enum(["bare-metal", "virtual-machine"]).optional(),
60
- state: z.enum(["available", "internal"]).optional(),
53
+ state: z.enum(["available", "draft"]).optional(),
61
54
  price_omitted: z.boolean().optional(),
62
55
  announce: z.boolean().optional(),
63
56
  text: z.string().optional(),
64
57
  confirm: z.boolean().optional(),
58
+ idempotencyKey: z.string().min(1).max(200).optional(),
65
59
  });
66
60
  const catalogTermsInput = z.object({ kind: z.enum(["gpu", "cpu", "node", "link"]).optional() });
67
61
  const slackListingCreateParams = {
@@ -80,7 +74,6 @@ const slackListingCreateParams = {
80
74
  available_start_at: { type: "string" },
81
75
  available_end_at: { type: "string" },
82
76
  buy_now_price_per_gpu_hour: { type: "number" },
83
- cost_per_gpu_hour: { type: "number" },
84
77
  deposit_percent: { type: "number" },
85
78
  price_omitted: { type: "boolean" },
86
79
  announce: { type: "boolean" },
@@ -166,6 +159,23 @@ export function primaryDownPaymentCents(terms) {
166
159
  const totalCents = (terms.pricePerGpuHourMicroUsd * terms.nodeCount * terms.gpusPerNode * hours) / 10_000;
167
160
  return Math.max(0, Math.round((totalCents * terms.percent) / 100));
168
161
  }
162
+ /** Shared native creation body for Slack's preview and human-confirmed write. */
163
+ export function primaryListingBody(input, pricePerGpuHourMicroUsd, downPaymentCents) {
164
+ return {
165
+ ...input,
166
+ kind: "primary",
167
+ terms: [
168
+ {
169
+ type: "reserved",
170
+ minDurationHours: 1 / 3600,
171
+ maxDurationHours: (Date.parse(input.availableUntil) - Date.parse(input.availableAt)) / 3600000,
172
+ renewable: false,
173
+ pricePerGpuHourMicroUsd,
174
+ downPaymentCents,
175
+ },
176
+ ],
177
+ };
178
+ }
169
179
  /** Commerce parses listing windows as RFC3339 timestamps, not bare dates. */
170
180
  function rfc3339(value) {
171
181
  const trimmed = value.trim();
@@ -193,13 +203,13 @@ const LISTING_CREATE_INPUT_ONLY_KEYS = [
193
203
  "site",
194
204
  "kind",
195
205
  "confirm",
206
+ "idempotencyKey",
196
207
  "organizationId",
197
208
  "organization_id",
198
209
  "site_supplier",
199
210
  "facilityId",
200
211
  "facility_id",
201
212
  "buy_now_price_per_gpu_hour",
202
- "cost_per_gpu_hour",
203
213
  "deposit_percent",
204
214
  "down_payment_cents",
205
215
  "access_mode",
@@ -208,12 +218,13 @@ const LISTING_CREATE_INPUT_ONLY_KEYS = [
208
218
  ];
209
219
  export const createListingCapability = defineCapability({
210
220
  id: "listings.create",
221
+ mcp: false,
211
222
  domain: "listings",
212
223
  roles: ["admin"],
213
224
  description: "Create a primary marketplace listing (admin). gpu_type, cpu, node_model, and fabric_type must exact-match an orchestrator catalog display name, canonical, or alias; gpu_type becomes the listing's GPU slug. " +
214
- "cost_per_gpu_hour is the supplier price and buy_now_price_per_gpu_hour the price buyers pay; the difference is Ornn's spread. Nodes are attached afterwards. CLI asks y/N. Slack never POSTs until a human replies confirm. MCP creates on this tool call.",
225
+ "buy_now_price_per_gpu_hour is the customer price; deposit_percent is the upfront payment as a percentage of the full availability window. Nodes are attached afterwards. Slack never POSTs until a human replies confirm. MCP and CLI use native listing.create instead.",
215
226
  mutation: "preview-confirm",
216
- http: { method: "POST", path: "/commerce/listings" },
227
+ http: { method: "POST", path: "/commerce/v2/listings" },
217
228
  slack: { parameters: slackListingCreateParams },
218
229
  input: listingCreateInput,
219
230
  execute: async (ctx, input) => {
@@ -244,14 +255,21 @@ export const createListingCapability = defineCapability({
244
255
  const seenCursors = new Set();
245
256
  let cursor;
246
257
  do {
247
- const response = (await ctx.fetch(queryPath("/provisioning/facilities", { cursor }), { method: "GET" }));
258
+ const response = (await ctx.fetch(queryPath("/provisioning/facilities", { cursor }), {
259
+ method: "GET",
260
+ }));
248
261
  if (!Array.isArray(response.facilities))
249
262
  throw new Error("invalid facility catalog");
250
- matches.push(...response.facilities.filter((facility) => (!organizationId || (facility.organizationId ?? facility.organization_id) === organizationId) &&
263
+ matches.push(...response.facilities.filter((facility) => (!organizationId ||
264
+ (facility.organizationId ?? facility.organization_id) === organizationId) &&
251
265
  (facilityId
252
266
  ? (facility.facilityId ?? facility.id) === facilityId
253
- : !facilityQuery || [facility.facilityId ?? facility.id, facility.slug, facility.displayName ?? facility.display_name]
254
- .some((value) => value?.toLowerCase() === facilityQuery))));
267
+ : !facilityQuery ||
268
+ [
269
+ facility.facilityId ?? facility.id,
270
+ facility.region,
271
+ facility.displayName ?? facility.display_name,
272
+ ].some((value) => value?.toLowerCase() === facilityQuery))));
255
273
  cursor = response.nextCursor ?? undefined;
256
274
  if (cursor) {
257
275
  if (seenCursors.has(cursor) || seenCursors.size >= 49) {
@@ -274,12 +292,12 @@ export const createListingCapability = defineCapability({
274
292
  const startsAt = rfc3339(parsed.available_start_at ??
275
293
  parsed.available_from ??
276
294
  parsed.starts_after ??
277
- new Date().toISOString());
295
+ new Date(Math.floor(Date.now() / 1000) * 1000).toISOString());
278
296
  const endsAt = rfc3339(parsed.available_end_at ??
279
297
  parsed.available_to ??
280
298
  parsed.ends_before ??
281
299
  parsed.ends_after ??
282
- new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString());
300
+ new Date(Math.floor(Date.now() / 1000) * 1000 + 30 * 24 * 60 * 60 * 1000).toISOString());
283
301
  const nodeCount = parsed.node_count ?? parsed.min_nodes ?? 1;
284
302
  const gpusPerNode = parsed.gpus_per_node ?? 8;
285
303
  const boundPayload = bound.payload;
@@ -288,12 +306,7 @@ export const createListingCapability = defineCapability({
288
306
  ? resolveCatalogTerm(hardware.terms, "gpu", normalized.gpu_type).canonical
289
307
  : null;
290
308
  const buyerPrice = parsed.buy_now_price_per_gpu_hour;
291
- const supplierPrice = parsed.cost_per_gpu_hour ?? buyerPrice;
292
- const supplierMicro = supplierPrice != null ? microUsd(supplierPrice) : null;
293
- const buyerMicro = buyerPrice != null ? microUsd(buyerPrice) : supplierMicro;
294
- if (supplierMicro != null && buyerMicro != null && buyerMicro < supplierMicro) {
295
- return { ok: false, error: "price_below_supplier_price" };
296
- }
309
+ const buyerMicro = buyerPrice != null ? microUsd(buyerPrice) : null;
297
310
  const downPaymentCents = parsed.down_payment_cents ??
298
311
  (parsed.deposit_percent != null && buyerMicro != null
299
312
  ? primaryDownPaymentCents({
@@ -305,26 +318,23 @@ export const createListingCapability = defineCapability({
305
318
  percent: parsed.deposit_percent,
306
319
  })
307
320
  : 0);
308
- const listing = organizationId && facilityId && gpuSlug && supplierMicro != null && buyerMicro != null
309
- ? {
321
+ const listing = organizationId && facilityId && gpuSlug && buyerMicro != null
322
+ ? primaryListingBody({
310
323
  organizationId,
311
324
  facilityId,
312
325
  gpuSlug,
313
326
  gpusPerNode,
314
- accessMode: parsed.access_mode ?? "virtual-machine",
315
- startsAt,
316
- endsAt,
327
+ provisioningType: parsed.access_mode ?? "virtual-machine",
328
+ availableAt: startsAt,
329
+ availableUntil: endsAt,
317
330
  ...(typeof draft.cpu === "string" ? { cpu: draft.cpu } : {}),
318
331
  ...(typeof draft.ram === "string" ? { ram: draft.ram } : {}),
319
332
  ...(typeof draft.storage === "string" ? { storage: draft.storage } : {}),
320
333
  ...(typeof draft.fabric_type === "string" ? { fabricType: draft.fabric_type } : {}),
321
334
  ...(typeof draft.internet === "string" ? { internet: draft.internet } : {}),
322
- ...(parsed.node_count ? { plannedNodeCount: parsed.node_count } : {}),
323
- supplierPricePerGpuHourMicroUsd: supplierMicro,
324
- spreadPerGpuHourMicroUsd: buyerMicro - supplierMicro,
325
- downPaymentCents,
326
- ...(parsed.state ? { state: parsed.state } : {}),
327
- }
335
+ capacityNodeCount: nodeCount,
336
+ state: parsed.state ?? "available",
337
+ }, buyerMicro, downPaymentCents)
328
338
  : null;
329
339
  if (!ctx.confirmed) {
330
340
  return preview("create_listing", {
@@ -337,10 +347,20 @@ export const createListingCapability = defineCapability({
337
347
  if (!listing) {
338
348
  return {
339
349
  ok: false,
340
- error: !organizationId || !facilityId ? "facility_required" : gpuSlug ? "price_required" : "gpu_required",
350
+ error: !organizationId || !facilityId
351
+ ? "facility_required"
352
+ : gpuSlug
353
+ ? "price_required"
354
+ : "gpu_required",
341
355
  };
342
356
  }
343
- return ctx.fetch("/commerce/listings", { method: "POST", body: listing });
357
+ if (!parsed.idempotencyKey)
358
+ return { ok: false, error: "idempotency_key_required" };
359
+ return ctx.fetch("/commerce/v2/listings", {
360
+ method: "POST",
361
+ body: listing,
362
+ headers: { "Idempotency-Key": parsed.idempotencyKey },
363
+ });
344
364
  },
345
365
  });
346
366
  export const listingCapabilities = [
@@ -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);