@ornncompute/cli 0.2.23 → 1.0.25

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 (85) hide show
  1. package/README.md +304 -420
  2. package/package.json +12 -6
  3. package/src/admin-capacity-reservations.mjs +280 -0
  4. package/src/admin-command-handlers.mjs +57 -0
  5. package/src/admin-enrollment.mjs +680 -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 +908 -0
  9. package/src/admin-shell.mjs +322 -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 +1350 -0
  15. package/src/cli.mjs +123 -10266
  16. package/src/connect-mode-handlers.mjs +170 -0
  17. package/src/device-auth.mjs +178 -61
  18. package/src/evidence-commands.mjs +1340 -0
  19. package/src/external-bucket-commands.mjs +1374 -0
  20. package/src/interaction-runtime.mjs +310 -0
  21. package/src/kubernetes-commands.mjs +2229 -0
  22. package/src/launch-mode-handlers.mjs +119 -0
  23. package/src/machine-reference.mjs +277 -0
  24. package/src/mount-commands.mjs +1146 -0
  25. package/src/session-commands.mjs +565 -0
  26. package/src/slurm-commands.mjs +263 -0
  27. package/src/startup.mjs +34 -11
  28. package/src/storage-commands.mjs +1144 -0
  29. package/src/update.mjs +172 -24
  30. package/src/vm-commands.mjs +4273 -0
  31. package/src/vpn-secret-output.mjs +150 -0
  32. package/src/wait-runtime.mjs +231 -0
  33. package/vendor/capabilities/capabilities/admin-capacity-reservations.js +126 -0
  34. package/vendor/capabilities/capabilities/admin-enrollment.js +321 -0
  35. package/vendor/capabilities/capabilities/admin-node-diagnostics.js +88 -0
  36. package/vendor/capabilities/capabilities/admin-nodes-lifecycle.js +235 -0
  37. package/vendor/capabilities/capabilities/admin.js +89 -0
  38. package/vendor/capabilities/capabilities/capacity-offers.js +368 -0
  39. package/vendor/capabilities/capabilities/cli.js +156 -0
  40. package/vendor/capabilities/capabilities/clusters.js +150 -1
  41. package/vendor/capabilities/capabilities/compute.js +522 -0
  42. package/vendor/capabilities/capabilities/evidence.js +145 -0
  43. package/vendor/capabilities/capabilities/external-buckets.js +191 -0
  44. package/vendor/capabilities/capabilities/fleet.js +21 -5
  45. package/vendor/capabilities/capabilities/identity.js +114 -3
  46. package/vendor/capabilities/capabilities/listings.js +14 -8
  47. package/vendor/capabilities/capabilities/mounts.js +176 -0
  48. package/vendor/capabilities/capabilities/networks.js +1257 -0
  49. package/vendor/capabilities/capabilities/observability.js +38 -8
  50. package/vendor/capabilities/capabilities/tokens.js +20 -8
  51. package/vendor/capabilities/capabilities/volumes.js +178 -0
  52. package/vendor/capabilities/capability.js +19 -13
  53. package/vendor/capabilities/cli-catalog.js +65 -0
  54. package/vendor/capabilities/cli-migrations.js +288 -0
  55. package/vendor/capabilities/cli.js +7 -0
  56. package/vendor/capabilities/software-catalog.js +36 -0
  57. package/vendor/capabilities/spec-catalog.js +2 -2
  58. package/src/catalog-dispatch.mjs +0 -605
  59. package/src/color.mjs +0 -71
  60. package/vendor/capabilities/capabilities/clusters.d.ts +0 -2
  61. package/vendor/capabilities/capabilities/fleet.d.ts +0 -8
  62. package/vendor/capabilities/capabilities/identity.d.ts +0 -3
  63. package/vendor/capabilities/capabilities/listings.d.ts +0 -51
  64. package/vendor/capabilities/capabilities/observability.d.ts +0 -5
  65. package/vendor/capabilities/capabilities/staff-access.d.ts +0 -29
  66. package/vendor/capabilities/capabilities/staff-access.js +0 -278
  67. package/vendor/capabilities/capabilities/staff-commerce.d.ts +0 -160
  68. package/vendor/capabilities/capabilities/staff-commerce.js +0 -689
  69. package/vendor/capabilities/capabilities/staff-fleet.d.ts +0 -140
  70. package/vendor/capabilities/capabilities/staff-fleet.js +0 -538
  71. package/vendor/capabilities/capabilities/staff-gtm.d.ts +0 -142
  72. package/vendor/capabilities/capabilities/staff-gtm.js +0 -521
  73. package/vendor/capabilities/capabilities/staff-shared.d.ts +0 -47
  74. package/vendor/capabilities/capabilities/staff-shared.js +0 -98
  75. package/vendor/capabilities/capabilities/tokens.d.ts +0 -10
  76. package/vendor/capabilities/capabilities/users.d.ts +0 -2
  77. package/vendor/capabilities/capability.d.ts +0 -93
  78. package/vendor/capabilities/catalog.d.ts +0 -3
  79. package/vendor/capabilities/catalog.js +0 -26
  80. package/vendor/capabilities/index.d.ts +0 -16
  81. package/vendor/capabilities/index.js +0 -16
  82. package/vendor/capabilities/names.d.ts +0 -6
  83. package/vendor/capabilities/role.d.ts +0 -6
  84. package/vendor/capabilities/role.js +0 -17
  85. 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.23",
3
+ "version": "1.0.25",
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,280 @@
1
+ import { ADMIN_MUTATION_SAFETY, AdminMutationError, runAdminMutation } from "./admin-mutations.mjs";
2
+
3
+ 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;
4
+ const DECIMAL = /^\d+(?:\.\d{1,6})?$/;
5
+
6
+ const USAGE = {
7
+ "listings.create":
8
+ "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 [--yes]",
9
+ "reservations.create":
10
+ "Usage: ornn admin reservations create --organization ID --capacity-offer ID --nodes COUNT --starts-at TIME --ends-at TIME --price-per-gpu-hour USD [--yes]",
11
+ };
12
+
13
+ function parseOptions(argv, { values, positionals }, usage) {
14
+ const options = {};
15
+ const positionalValues = [];
16
+ for (let index = 0; index < argv.length; index += 1) {
17
+ const argument = argv[index];
18
+ if (!argument.startsWith("--")) {
19
+ positionalValues.push(argument);
20
+ continue;
21
+ }
22
+ if (argument === "--yes") {
23
+ if (options.yes !== undefined)
24
+ throw new AdminMutationError(`${usage}\n--yes may be supplied only once.`, {
25
+ field: "yes",
26
+ });
27
+ options.yes = true;
28
+ continue;
29
+ }
30
+ const separator = argument.indexOf("=");
31
+ const name = argument.slice(2, separator === -1 ? undefined : separator);
32
+ if (!values.includes(name)) throw new AdminMutationError(`${usage}\nUnknown option: --${name}`);
33
+ const value = separator === -1 ? argv[index + 1] : argument.slice(separator + 1);
34
+ if (!value || (separator === -1 && value.startsWith("--"))) {
35
+ throw new AdminMutationError(`${usage}\n--${name} requires a value.`, { field: name });
36
+ }
37
+ if (options[name] !== undefined) {
38
+ throw new AdminMutationError(`${usage}\n--${name} may be supplied only once.`, {
39
+ field: name,
40
+ });
41
+ }
42
+ options[name] = value;
43
+ if (separator === -1) index += 1;
44
+ }
45
+ if (positionalValues.length !== positionals) throw new AdminMutationError(usage);
46
+ return { options, positionals: positionalValues };
47
+ }
48
+
49
+ function required(options, name, usage) {
50
+ const value = options[name]?.trim();
51
+ if (!value) throw new AdminMutationError(`${usage}\n--${name} is required.`, { field: name });
52
+ return value;
53
+ }
54
+
55
+ function positiveInteger(options, name, usage) {
56
+ const value = required(options, name, usage);
57
+ const parsed = Number(value);
58
+ if (!Number.isSafeInteger(parsed) || parsed < 1) {
59
+ throw new AdminMutationError(`--${name} must be a positive integer.`, { field: name });
60
+ }
61
+ return parsed;
62
+ }
63
+
64
+ function uuid(options, name, usage) {
65
+ const value = required(options, name, usage);
66
+ if (!UUID.test(value)) {
67
+ throw new AdminMutationError(`--${name} must be a canonical UUID.`, { field: name });
68
+ }
69
+ return value.toLowerCase();
70
+ }
71
+
72
+ function timestamp(options, name, usage) {
73
+ const value = required(options, name, usage);
74
+ const parsed = Date.parse(value);
75
+ if (!Number.isFinite(parsed)) {
76
+ throw new AdminMutationError(`--${name} must be an ISO timestamp.`, { field: name });
77
+ }
78
+ return new Date(parsed).toISOString();
79
+ }
80
+
81
+ function price(options, usage) {
82
+ const value = required(options, "price-per-gpu-hour", usage);
83
+ if (!DECIMAL.test(value)) {
84
+ throw new AdminMutationError(
85
+ "--price-per-gpu-hour must be zero or a positive decimal with at most 6 decimal places.",
86
+ { field: "price-per-gpu-hour" }
87
+ );
88
+ }
89
+ return value;
90
+ }
91
+
92
+ function assertWindow(start, end) {
93
+ if (Date.parse(start) >= Date.parse(end)) {
94
+ throw new AdminMutationError("--ends-at must be later than --starts-at.", { field: "ends-at" });
95
+ }
96
+ }
97
+
98
+ function parseCapacityOffer(argv) {
99
+ const usage = USAGE["listings.create"];
100
+ const { options } = parseOptions(
101
+ argv,
102
+ {
103
+ positionals: 0,
104
+ values: [
105
+ "gpu",
106
+ "nodes",
107
+ "gpus-per-node",
108
+ "site",
109
+ "starts-at",
110
+ "ends-at",
111
+ "price-per-gpu-hour",
112
+ "idempotency-key",
113
+ ],
114
+ },
115
+ usage
116
+ );
117
+ const startsAt = timestamp(options, "starts-at", usage);
118
+ const endsAt = timestamp(options, "ends-at", usage);
119
+ assertWindow(startsAt, endsAt);
120
+ return {
121
+ input: {
122
+ gpu_type: required(options, "gpu", usage),
123
+ node_count: positiveInteger(options, "nodes", usage),
124
+ gpus_per_node: positiveInteger(options, "gpus-per-node", usage),
125
+ site_nickname: required(options, "site", usage),
126
+ available_start_at: startsAt,
127
+ available_end_at: endsAt,
128
+ buy_now_price_per_gpu_hour: Number(price(options, usage)),
129
+ },
130
+ mutation: {
131
+ idempotencyKey: options["idempotency-key"],
132
+ safety: ADMIN_MUTATION_SAFETY.eligibleHumanOnly,
133
+ yes: options.yes === true,
134
+ },
135
+ };
136
+ }
137
+
138
+ function parseReservationCreate(argv) {
139
+ const usage = USAGE["reservations.create"];
140
+ const { options } = parseOptions(
141
+ argv,
142
+ {
143
+ positionals: 0,
144
+ values: [
145
+ "organization",
146
+ "capacity-offer",
147
+ "nodes",
148
+ "starts-at",
149
+ "ends-at",
150
+ "price-per-gpu-hour",
151
+ "idempotency-key",
152
+ ],
153
+ },
154
+ usage
155
+ );
156
+ const startsAt = timestamp(options, "starts-at", usage);
157
+ const endsAt = timestamp(options, "ends-at", usage);
158
+ assertWindow(startsAt, endsAt);
159
+ return {
160
+ input: {
161
+ organization_id: uuid(options, "organization", usage),
162
+ listing_id: uuid(options, "capacity-offer", usage),
163
+ node_count: positiveInteger(options, "nodes", usage),
164
+ start_at: startsAt,
165
+ end_at: endsAt,
166
+ price_per_gpu_hour: price(options, usage),
167
+ },
168
+ mutation: {
169
+ idempotencyKey: options["idempotency-key"],
170
+ safety: ADMIN_MUTATION_SAFETY.eligibleHumanOnly,
171
+ yes: options.yes === true,
172
+ },
173
+ };
174
+ }
175
+
176
+ function previewPayload(value) {
177
+ if (value?.ok === false) {
178
+ throw new AdminMutationError("The capacity offer contains an unknown hardware catalog term.", {
179
+ code: value.error ?? "unknown_catalog_term",
180
+ });
181
+ }
182
+ if (!value?.payload || typeof value.payload !== "object") {
183
+ throw new AdminMutationError("Capacity-offer normalization returned no effective terms.", {
184
+ code: "admin_review_incomplete",
185
+ });
186
+ }
187
+ return value.payload;
188
+ }
189
+
190
+ function capacityOfferReview(payload, input) {
191
+ return {
192
+ title: "Create capacity offer",
193
+ fields: [
194
+ { label: "GPU", value: payload.gpu_type ?? input.gpu_type },
195
+ {
196
+ label: "Nodes",
197
+ value:
198
+ payload.capacityNodes ?? payload.capacity_nodes ?? payload.node_count ?? input.node_count,
199
+ },
200
+ {
201
+ label: "GPUs per node",
202
+ value: payload.gpusPerNode ?? payload.gpus_per_node ?? input.gpus_per_node,
203
+ },
204
+ { label: "Site", value: payload.site_nickname ?? input.site_nickname },
205
+ {
206
+ label: "Starts at",
207
+ value:
208
+ payload.start_at ??
209
+ payload.startAt ??
210
+ payload.available_start_at ??
211
+ input.available_start_at,
212
+ },
213
+ {
214
+ label: "Ends at",
215
+ value:
216
+ payload.end_at ?? payload.endAt ?? payload.available_end_at ?? input.available_end_at,
217
+ },
218
+ {
219
+ label: "Price per GPU-hour (USD)",
220
+ value:
221
+ payload.buy_now_rate_per_gpu_hour ??
222
+ payload.buyNowRatePerGpuHour ??
223
+ payload.buy_now_price_per_gpu_hour ??
224
+ input.buy_now_price_per_gpu_hour,
225
+ },
226
+ ],
227
+ consequence: "Publishes new commercial capacity terms.",
228
+ };
229
+ }
230
+
231
+ function reservationCreateReview(input) {
232
+ return {
233
+ title: "Create capacity reservation",
234
+ fields: [
235
+ { label: "Organization", value: input.organization_id },
236
+ { label: "Capacity offer", value: input.listing_id },
237
+ { label: "Nodes", value: input.node_count },
238
+ { label: "Starts at", value: input.start_at },
239
+ { label: "Ends at", value: input.end_at },
240
+ { label: "Price per GPU-hour (USD)", value: input.price_per_gpu_hour },
241
+ ],
242
+ consequence: "Creates a reservation with the reviewed quantity, dates, and price.",
243
+ };
244
+ }
245
+
246
+ async function runCapacityOffer({ execute, input, mutation, runtime }) {
247
+ return runAdminMutation({
248
+ ...runtime,
249
+ ...mutation,
250
+ prepare: async () => {
251
+ const payload = previewPayload(await execute(input));
252
+ return { review: capacityOfferReview(payload, input), value: input };
253
+ },
254
+ execute: (value) => execute(value, { confirmed: true }),
255
+ });
256
+ }
257
+
258
+ async function runReservationCreate({ execute, input, mutation, runtime }) {
259
+ return runAdminMutation({
260
+ ...runtime,
261
+ ...mutation,
262
+ prepare: async () => ({ review: reservationCreateReview(input), value: input }),
263
+ execute: (value) => execute(value, { confirmed: true }),
264
+ });
265
+ }
266
+
267
+ export const adminCapacityReservationHandlers = [
268
+ {
269
+ capabilityId: "listings.create",
270
+ parse: parseCapacityOffer,
271
+ run: runCapacityOffer,
272
+ safety: ADMIN_MUTATION_SAFETY.eligibleHumanOnly,
273
+ },
274
+ {
275
+ capabilityId: "reservations.create",
276
+ parse: parseReservationCreate,
277
+ run: runReservationCreate,
278
+ safety: ADMIN_MUTATION_SAFETY.eligibleHumanOnly,
279
+ },
280
+ ];
@@ -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;