@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ornncompute/cli",
3
- "version": "0.2.24",
3
+ "version": "1.0.34",
4
4
  "description": "Command-line interface for Ornn compute access workflows.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,16 +27,22 @@
27
27
  "registry": "https://registry.npmjs.org/"
28
28
  },
29
29
  "scripts": {
30
- "test": "node --test",
31
- "prepack": "node scripts/vendor-capabilities.mjs"
30
+ "acceptance:packaged": "node scripts/packaged-vnext-gate.mjs",
31
+ "evidence:participant": "node scripts/record-participant-evidence.mjs",
32
+ "prepack": "node scripts/vendor-capabilities.mjs",
33
+ "pretest": "node scripts/vendor-capabilities.mjs",
34
+ "smoke:nonproduction": "node scripts/nonproduction-release-smoke.mjs",
35
+ "test": "node --test 'test/*.test.mjs'"
32
36
  },
33
37
  "devDependencies": {
34
38
  "@ornn/capabilities": "0.1.0"
35
39
  },
36
- "dependencies": {
37
- "update-notifier": "7.3.1"
38
- },
39
40
  "engines": {
40
41
  "node": ">=20"
42
+ },
43
+ "dependencies": {
44
+ "picocolors": "^1.1.1",
45
+ "zod": "^4.6.5",
46
+ "update-notifier": "7.3.1"
41
47
  }
42
48
  }
@@ -0,0 +1,305 @@
1
+ import { ADMIN_MUTATION_SAFETY, AdminMutationError, runAdminMutation } from "./admin-mutations.mjs";
2
+ import { gpuLabeler } from "./gpu-catalog.mjs";
3
+
4
+ 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;
5
+ const DECIMAL = /^\d+(?:\.\d{1,6})?$/;
6
+
7
+ const USAGE = {
8
+ "listings.create":
9
+ "Usage: ornn admin capacity-offers create --gpu GPU --nodes COUNT --gpus-per-node COUNT --site SITE --starts-at TIME --ends-at TIME --price-per-gpu-hour USD [--supplier-price-per-gpu-hour USD] [--down-payment-cents CENTS] [--access-mode bare-metal|virtual-machine] [--yes]",
10
+ "reservations.create":
11
+ "Usage: ornn admin reservations create --organization ID --capacity-offer ID --starts-at TIME --ends-at TIME --price-per-gpu-hour USD [--down-payment-cents CENTS] [--yes]",
12
+ };
13
+ const ACCESS_MODES = new Set(["bare-metal", "virtual-machine"]);
14
+ const STAFF_HARDWARE_CATALOG = "/catalog/hardware";
15
+
16
+ function parseOptions(argv, { values, positionals }, usage) {
17
+ const options = {};
18
+ const positionalValues = [];
19
+ for (let index = 0; index < argv.length; index += 1) {
20
+ const argument = argv[index];
21
+ if (!argument.startsWith("--")) {
22
+ positionalValues.push(argument);
23
+ continue;
24
+ }
25
+ if (argument === "--yes") {
26
+ if (options.yes !== undefined)
27
+ throw new AdminMutationError(`${usage}\n--yes may be supplied only once.`, {
28
+ field: "yes",
29
+ });
30
+ options.yes = true;
31
+ continue;
32
+ }
33
+ const separator = argument.indexOf("=");
34
+ const name = argument.slice(2, separator === -1 ? undefined : separator);
35
+ if (!values.includes(name)) throw new AdminMutationError(`${usage}\nUnknown option: --${name}`);
36
+ const value = separator === -1 ? argv[index + 1] : argument.slice(separator + 1);
37
+ if (!value || (separator === -1 && value.startsWith("--"))) {
38
+ throw new AdminMutationError(`${usage}\n--${name} requires a value.`, { field: name });
39
+ }
40
+ if (options[name] !== undefined) {
41
+ throw new AdminMutationError(`${usage}\n--${name} may be supplied only once.`, {
42
+ field: name,
43
+ });
44
+ }
45
+ options[name] = value;
46
+ if (separator === -1) index += 1;
47
+ }
48
+ if (positionalValues.length !== positionals) throw new AdminMutationError(usage);
49
+ return { options, positionals: positionalValues };
50
+ }
51
+
52
+ function required(options, name, usage) {
53
+ const value = options[name]?.trim();
54
+ if (!value) throw new AdminMutationError(`${usage}\n--${name} is required.`, { field: name });
55
+ return value;
56
+ }
57
+
58
+ function positiveInteger(options, name, usage) {
59
+ const value = required(options, name, usage);
60
+ const parsed = Number(value);
61
+ if (!Number.isSafeInteger(parsed) || parsed < 1) {
62
+ throw new AdminMutationError(`--${name} must be a positive integer.`, { field: name });
63
+ }
64
+ return parsed;
65
+ }
66
+
67
+ function uuid(options, name, usage) {
68
+ const value = required(options, name, usage);
69
+ if (!UUID.test(value)) {
70
+ throw new AdminMutationError(`--${name} must be a canonical UUID.`, { field: name });
71
+ }
72
+ return value.toLowerCase();
73
+ }
74
+
75
+ function timestamp(options, name, usage) {
76
+ const value = required(options, name, usage);
77
+ const parsed = Date.parse(value);
78
+ if (!Number.isFinite(parsed)) {
79
+ throw new AdminMutationError(`--${name} must be an ISO timestamp.`, { field: name });
80
+ }
81
+ return new Date(parsed).toISOString();
82
+ }
83
+
84
+ function price(options, usage, name = "price-per-gpu-hour") {
85
+ const value = required(options, name, usage);
86
+ if (!DECIMAL.test(value)) {
87
+ throw new AdminMutationError(
88
+ `--${name} must be zero or a positive decimal with at most 6 decimal places.`,
89
+ { field: name }
90
+ );
91
+ }
92
+ return value;
93
+ }
94
+
95
+ function optionalCents(options) {
96
+ const value = options["down-payment-cents"]?.trim();
97
+ if (value === undefined) return undefined;
98
+ const parsed = Number(value);
99
+ if (!Number.isSafeInteger(parsed) || parsed < 0) {
100
+ throw new AdminMutationError("--down-payment-cents must be a whole number of cents.", {
101
+ field: "down-payment-cents",
102
+ });
103
+ }
104
+ return parsed;
105
+ }
106
+
107
+ function assertWindow(start, end) {
108
+ if (Date.parse(start) >= Date.parse(end)) {
109
+ throw new AdminMutationError("--ends-at must be later than --starts-at.", { field: "ends-at" });
110
+ }
111
+ }
112
+
113
+ function parseCapacityOffer(argv) {
114
+ const usage = USAGE["listings.create"];
115
+ const { options } = parseOptions(
116
+ argv,
117
+ {
118
+ positionals: 0,
119
+ values: [
120
+ "gpu",
121
+ "nodes",
122
+ "gpus-per-node",
123
+ "site",
124
+ "starts-at",
125
+ "ends-at",
126
+ "price-per-gpu-hour",
127
+ "supplier-price-per-gpu-hour",
128
+ "down-payment-cents",
129
+ "access-mode",
130
+ "idempotency-key",
131
+ ],
132
+ },
133
+ usage
134
+ );
135
+ const startsAt = timestamp(options, "starts-at", usage);
136
+ const endsAt = timestamp(options, "ends-at", usage);
137
+ assertWindow(startsAt, endsAt);
138
+ const accessMode = options["access-mode"]?.trim();
139
+ if (accessMode !== undefined && !ACCESS_MODES.has(accessMode)) {
140
+ throw new AdminMutationError("--access-mode must be bare-metal or virtual-machine.", {
141
+ field: "access-mode",
142
+ });
143
+ }
144
+ const downPaymentCents = optionalCents(options);
145
+ return {
146
+ input: {
147
+ gpu_type: required(options, "gpu", usage),
148
+ node_count: positiveInteger(options, "nodes", usage),
149
+ gpus_per_node: positiveInteger(options, "gpus-per-node", usage),
150
+ site_nickname: required(options, "site", usage),
151
+ available_start_at: startsAt,
152
+ available_end_at: endsAt,
153
+ buy_now_price_per_gpu_hour: Number(price(options, usage)),
154
+ ...(options["supplier-price-per-gpu-hour"] !== undefined
155
+ ? {
156
+ cost_per_gpu_hour: Number(price(options, usage, "supplier-price-per-gpu-hour")),
157
+ }
158
+ : {}),
159
+ ...(downPaymentCents !== undefined ? { down_payment_cents: downPaymentCents } : {}),
160
+ ...(accessMode ? { access_mode: accessMode } : {}),
161
+ },
162
+ mutation: {
163
+ idempotencyKey: options["idempotency-key"],
164
+ safety: ADMIN_MUTATION_SAFETY.eligibleHumanOnly,
165
+ yes: options.yes === true,
166
+ },
167
+ };
168
+ }
169
+
170
+ function parseReservationCreate(argv) {
171
+ const usage = USAGE["reservations.create"];
172
+ const { options } = parseOptions(
173
+ argv,
174
+ {
175
+ positionals: 0,
176
+ values: [
177
+ "organization",
178
+ "capacity-offer",
179
+ "starts-at",
180
+ "ends-at",
181
+ "price-per-gpu-hour",
182
+ "down-payment-cents",
183
+ "idempotency-key",
184
+ ],
185
+ },
186
+ usage
187
+ );
188
+ const startsAt = timestamp(options, "starts-at", usage);
189
+ const endsAt = timestamp(options, "ends-at", usage);
190
+ assertWindow(startsAt, endsAt);
191
+ const downPaymentCents = optionalCents(options);
192
+ return {
193
+ input: {
194
+ organization_id: uuid(options, "organization", usage),
195
+ listing_id: uuid(options, "capacity-offer", usage),
196
+ start_at: startsAt,
197
+ end_at: endsAt,
198
+ price_per_gpu_hour: price(options, usage),
199
+ ...(downPaymentCents !== undefined ? { down_payment_cents: downPaymentCents } : {}),
200
+ },
201
+ mutation: {
202
+ idempotencyKey: options["idempotency-key"],
203
+ safety: ADMIN_MUTATION_SAFETY.eligibleHumanOnly,
204
+ yes: options.yes === true,
205
+ },
206
+ };
207
+ }
208
+
209
+ function previewPayload(value) {
210
+ if (value?.ok === false) {
211
+ throw new AdminMutationError("The capacity offer contains an unknown hardware catalog term.", {
212
+ code: value.error ?? "unknown_catalog_term",
213
+ });
214
+ }
215
+ if (!value?.payload || typeof value.payload !== "object") {
216
+ throw new AdminMutationError("Capacity-offer normalization returned no effective terms.", {
217
+ code: "admin_review_incomplete",
218
+ });
219
+ }
220
+ return value.payload;
221
+ }
222
+
223
+ function usd(microUsd) {
224
+ return typeof microUsd === "number" ? (microUsd / 1_000_000).toFixed(6) : undefined;
225
+ }
226
+
227
+ function capacityOfferReview(payload, input, gpuLabel) {
228
+ const listing = payload.listing ?? {};
229
+ return {
230
+ title: "Create capacity offer",
231
+ fields: [
232
+ { label: "GPU", value: gpuLabel(payload.gpu_type ?? listing.gpuSlug) },
233
+ { label: "Nodes to attach", value: payload.node_count ?? input.node_count },
234
+ { label: "GPUs per node", value: listing.gpusPerNode ?? input.gpus_per_node },
235
+ { label: "Site", value: payload.site_nickname ?? input.site_nickname },
236
+ { label: "Access mode", value: listing.accessMode },
237
+ { label: "Starts at", value: listing.startsAt ?? input.available_start_at },
238
+ { label: "Ends at", value: listing.endsAt ?? input.available_end_at },
239
+ { label: "Price per GPU-hour (USD)", value: input.buy_now_price_per_gpu_hour },
240
+ {
241
+ label: "Supplier price per GPU-hour (USD)",
242
+ value: usd(listing.supplierPricePerGpuHourMicroUsd),
243
+ },
244
+ { label: "Spread per GPU-hour (USD)", value: usd(listing.spreadPerGpuHourMicroUsd) },
245
+ { label: "Down payment (cents)", value: listing.downPaymentCents },
246
+ ],
247
+ consequence:
248
+ "Publishes a primary listing with these terms; attach its nodes before it reaches the market.",
249
+ };
250
+ }
251
+
252
+ function reservationCreateReview(input) {
253
+ return {
254
+ title: "Create capacity reservation",
255
+ fields: [
256
+ { label: "Organization", value: input.organization_id },
257
+ { label: "Capacity offer", value: input.listing_id },
258
+ { label: "Starts at", value: input.start_at },
259
+ { label: "Ends at", value: input.end_at },
260
+ { label: "Price per GPU-hour (USD)", value: input.price_per_gpu_hour },
261
+ ...(input.down_payment_cents !== undefined
262
+ ? [{ label: "Down payment (cents)", value: input.down_payment_cents }]
263
+ : []),
264
+ ],
265
+ consequence:
266
+ "Creates a schedule reservation on the listing's nodes with the reviewed dates and price.",
267
+ };
268
+ }
269
+
270
+ async function runCapacityOffer({ execute, fetch, input, mutation, runtime }) {
271
+ return runAdminMutation({
272
+ ...runtime,
273
+ ...mutation,
274
+ prepare: async () => {
275
+ const payload = previewPayload(await execute(input));
276
+ const gpuLabel = await gpuLabeler(fetch, STAFF_HARDWARE_CATALOG);
277
+ return { review: capacityOfferReview(payload, input, gpuLabel), value: input };
278
+ },
279
+ execute: (value) => execute(value, { confirmed: true }),
280
+ });
281
+ }
282
+
283
+ async function runReservationCreate({ execute, input, mutation, runtime }) {
284
+ return runAdminMutation({
285
+ ...runtime,
286
+ ...mutation,
287
+ prepare: async () => ({ review: reservationCreateReview(input), value: input }),
288
+ execute: (value) => execute(value, { confirmed: true }),
289
+ });
290
+ }
291
+
292
+ export const adminCapacityReservationHandlers = [
293
+ {
294
+ capabilityId: "listings.create",
295
+ parse: parseCapacityOffer,
296
+ run: runCapacityOffer,
297
+ safety: ADMIN_MUTATION_SAFETY.eligibleHumanOnly,
298
+ },
299
+ {
300
+ capabilityId: "reservations.create",
301
+ parse: parseReservationCreate,
302
+ run: runReservationCreate,
303
+ safety: ADMIN_MUTATION_SAFETY.eligibleHumanOnly,
304
+ },
305
+ ];
@@ -0,0 +1,57 @@
1
+ import { adminCapacityReservationHandlers } from "./admin-capacity-reservations.mjs";
2
+ import { adminEnrollmentHandlers } from "./admin-enrollment.mjs";
3
+ import { adminNodeDiagnosticsHandlers } from "./admin-node-diagnostics.mjs";
4
+ import { adminNodeLifecycleHandlers } from "./admin-node-lifecycle.mjs";
5
+ import { ADMIN_MUTATION_SAFETY, assertAdminMutationContext } from "./admin-mutations.mjs";
6
+
7
+ const handlers = [
8
+ ...adminCapacityReservationHandlers,
9
+ ...adminEnrollmentHandlers,
10
+ ...adminNodeDiagnosticsHandlers,
11
+ ...adminNodeLifecycleHandlers,
12
+ ];
13
+
14
+ export function adminCommandHandler(capability) {
15
+ return handlers.find((handler) => handler.capabilityId === capability.id) ?? null;
16
+ }
17
+
18
+ function commandParts(capability) {
19
+ return capability.cli?.vnext?.command ?? capability.cli?.command ?? [];
20
+ }
21
+ function matchedAdminCapability(catalog, argv) {
22
+ return catalog.find((candidate) => {
23
+ if (candidate.cli?.vnext?.audience !== "admin") return false;
24
+ const command = commandParts(candidate).slice(1);
25
+ return command.length <= argv.length && command.every((part, index) => argv[index] === part);
26
+ });
27
+ }
28
+
29
+ export function adminCommandName(catalog, argv) {
30
+ const capability = matchedAdminCapability(catalog, argv);
31
+ return capability ? commandParts(capability).join(" ") : null;
32
+ }
33
+
34
+ /** Parses supported mutation semantics and rejects unsafe contexts before auth preflight I/O. */
35
+ export function preflightAdminCommand(catalog, argv, runtime) {
36
+ if (argv[0] === "help" || argv.includes("--help") || argv.includes("-h")) return null;
37
+ const capability = matchedAdminCapability(catalog, argv);
38
+ if (!capability) return null;
39
+ const handler = adminCommandHandler(capability);
40
+ if (!handler) return null;
41
+ if (handler.safety === ADMIN_MUTATION_SAFETY.eligibleHumanOnly) {
42
+ assertAdminMutationContext({ ...runtime, safety: handler.safety });
43
+ }
44
+ const command = commandParts(capability);
45
+ const parsed = handler.parse(argv.slice(command.length - 1), capability);
46
+ if (handler.safety) {
47
+ assertAdminMutationContext({
48
+ ...runtime,
49
+ idempotencyKey: parsed.mutation?.idempotencyKey,
50
+ safety: handler.safety,
51
+ yes: parsed.mutation?.yes,
52
+ });
53
+ }
54
+ return { capabilityId: capability.id, command: command.join(" "), parsed };
55
+ }
56
+
57
+ export const adminCommandHandlers = handlers;