@ornncompute/cli 0.2.24 → 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 +298 -426
  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
@@ -0,0 +1,310 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { createInterface } from "node:readline/promises";
3
+
4
+ import { resolveAuthBaseUrl } from "./device-auth.mjs";
5
+
6
+ const BOOLEAN_OPTIONS = new Set([
7
+ "all-nodes",
8
+ "erase-local-data",
9
+ "force",
10
+ "follow",
11
+ "major",
12
+ "guided",
13
+ "no-browser",
14
+ "no-wait",
15
+ "overwrite",
16
+ "print",
17
+ "read-only",
18
+ "read-write",
19
+ "recursive",
20
+ "wait",
21
+ "yes",
22
+ ]);
23
+
24
+ const OPTIONAL_VALUE_OPTIONS = new Set(["resume"]);
25
+
26
+ function optionName(raw) {
27
+ return raw.replace(/-([a-z0-9])/g, (_, character) => character.toUpperCase());
28
+ }
29
+
30
+ export class InteractionError extends Error {
31
+ constructor(
32
+ message,
33
+ {
34
+ action = null,
35
+ code = "command_failed",
36
+ exitCode = 1,
37
+ field,
38
+ outcomeClass = "failed",
39
+ retryable = false,
40
+ } = {}
41
+ ) {
42
+ super(message);
43
+ this.name = "InteractionError";
44
+ this.action = action;
45
+ this.code = code;
46
+ this.exitCode = exitCode;
47
+ this.field = field;
48
+ this.outcomeClass = outcomeClass;
49
+ this.retryable = retryable;
50
+ }
51
+ }
52
+
53
+ export function isRecoverableWaitFailure(error) {
54
+ const status = Number(error?.status);
55
+ if (status === 401 || status === 403 || (Number.isFinite(status) && status >= 500)) {
56
+ return true;
57
+ }
58
+ if (error?.retryable === true) return true;
59
+ const code = typeof error?.code === "string" ? error.code : "";
60
+ if (/^E(?:AI_|CONN|HOST|NET|PIPE|TIMEDOUT)/.test(code)) return true;
61
+ return (
62
+ error instanceof TypeError &&
63
+ /fetch|network|socket|connection|timed? ?out|lost response/i.test(error.message)
64
+ );
65
+ }
66
+
67
+ export function parseSemanticArguments(argv) {
68
+ const parsed = { $positionals: [] };
69
+ for (let index = 0; index < argv.length; index += 1) {
70
+ const argument = argv[index];
71
+ if (argument === "--" || /^-[^-]/.test(argument)) {
72
+ throw new InteractionError(`Unsupported option: ${argument}.`, {
73
+ code: "usage_error",
74
+ exitCode: 2,
75
+ });
76
+ }
77
+ if (!argument.startsWith("--")) {
78
+ parsed.$positionals.push(argument);
79
+ continue;
80
+ }
81
+
82
+ const equals = argument.indexOf("=");
83
+ const rawName = argument.slice(2, equals === -1 ? undefined : equals);
84
+ if (!rawName) {
85
+ throw new InteractionError("An option name is required after --.", {
86
+ code: "usage_error",
87
+ exitCode: 2,
88
+ });
89
+ }
90
+ const name = optionName(rawName);
91
+ if (Object.hasOwn(parsed, name)) {
92
+ throw new InteractionError(`--${rawName} may be supplied only once.`, {
93
+ code: "usage_error",
94
+ exitCode: 2,
95
+ field: name,
96
+ });
97
+ }
98
+
99
+ if (OPTIONAL_VALUE_OPTIONS.has(rawName)) {
100
+ const value = equals === -1 ? argv[index + 1] : argument.slice(equals + 1);
101
+ if (equals !== -1 && value === "") {
102
+ throw new InteractionError(`--${rawName} requires a value after =.`, {
103
+ code: "usage_error",
104
+ exitCode: 2,
105
+ field: name,
106
+ });
107
+ }
108
+ if (equals === -1 && (value === undefined || value.startsWith("-"))) {
109
+ parsed[name] = true;
110
+ } else {
111
+ if (equals === -1) index += 1;
112
+ parsed[name] = value;
113
+ }
114
+ continue;
115
+ }
116
+
117
+ if (BOOLEAN_OPTIONS.has(rawName)) {
118
+ if (equals !== -1) {
119
+ throw new InteractionError(`--${rawName} does not accept a value.`, {
120
+ code: "usage_error",
121
+ exitCode: 2,
122
+ field: name,
123
+ });
124
+ }
125
+ parsed[name] = true;
126
+ continue;
127
+ }
128
+
129
+ const value = equals === -1 ? argv[index + 1] : argument.slice(equals + 1);
130
+ if (value === undefined || (equals === -1 && value.startsWith("-")) || value === "") {
131
+ throw new InteractionError(`--${rawName} requires a value.`, {
132
+ code: "usage_error",
133
+ exitCode: 2,
134
+ field: name,
135
+ });
136
+ }
137
+ if (equals === -1) index += 1;
138
+ parsed[name] = value;
139
+ }
140
+ return parsed;
141
+ }
142
+
143
+ function execution({
144
+ action = null,
145
+ data = null,
146
+ errors = [],
147
+ exitCode = 0,
148
+ outcome = { class: "ready", phase: "complete" },
149
+ page = null,
150
+ progress = [],
151
+ presentation = null,
152
+ resume = null,
153
+ } = {}) {
154
+ return {
155
+ __ornnCliExecution: true,
156
+ action,
157
+ data,
158
+ errors,
159
+ exitCode,
160
+ outcome,
161
+ page,
162
+ progress,
163
+ presentation,
164
+ resume,
165
+ };
166
+ }
167
+
168
+ export function isInteractionExecution(value) {
169
+ return value?.__ornnCliExecution === true;
170
+ }
171
+
172
+ export function createInteractionRuntime({
173
+ eligibleHuman,
174
+ extensions = {},
175
+ format,
176
+ formatProvided = false,
177
+ io,
178
+ options,
179
+ }) {
180
+ const machine = format === "json" || format === "ndjson";
181
+ const redirected = !eligibleHuman;
182
+ const stdin = io.stdin ?? process.stdin;
183
+ const stderr = io.stderr ?? process.stderr;
184
+ const sleep = io.sleep;
185
+ const now = io.now ?? (() => Date.now());
186
+ const progressIntervalMs = io.progressIntervalMs ?? 10_000;
187
+ let reader;
188
+
189
+ function readline() {
190
+ reader ??= createInterface({ input: stdin, output: stderr, terminal: false });
191
+ return reader;
192
+ }
193
+
194
+ function usage(message, { code = "usage_error", field } = {}) {
195
+ throw new InteractionError(message, { code, exitCode: 2, field });
196
+ }
197
+
198
+ function error(message, init = {}) {
199
+ throw new InteractionError(message, init);
200
+ }
201
+
202
+ async function ask({ choices = [], label }) {
203
+ if (!eligibleHuman || machine) {
204
+ usage(`${label} must be supplied explicitly outside an interactive terminal.`, {
205
+ code: "missing_input",
206
+ });
207
+ }
208
+ for (const [index, choice] of choices.entries()) {
209
+ stderr.write(` ${index + 1}) ${choice.label}\n`);
210
+ }
211
+ let answer;
212
+ try {
213
+ answer = await readline().question(`${label}: `);
214
+ } catch (caught) {
215
+ if (caught?.code === "ERR_USE_AFTER_CLOSE") return null;
216
+ throw caught;
217
+ }
218
+ if (!answer) return null;
219
+ if (choices.length) {
220
+ const numeric = Number(answer);
221
+ if (Number.isInteger(numeric) && numeric >= 1 && numeric <= choices.length) {
222
+ return choices[numeric - 1].value;
223
+ }
224
+ const exact = choices.filter(
225
+ (choice) => choice.label.toLowerCase() === answer.toLowerCase() || choice.value === answer
226
+ );
227
+ if (exact.length === 1) return exact[0].value;
228
+ usage(`Choose one exact ${label.toLowerCase()} value.`, { code: "ambiguous_input" });
229
+ }
230
+ return answer;
231
+ }
232
+
233
+ async function review({ prompt = "Continue?", requireLive = false, terms, title }) {
234
+ if (machine) {
235
+ if (!requireLive && options.yes === true) return true;
236
+ usage(
237
+ requireLive
238
+ ? "This operation is supported only in an eligible interactive terminal."
239
+ : "Consent is required. Review the terms and supply --yes to continue.",
240
+ {
241
+ code: requireLive ? "unsupported_in_machine_mode" : "consent_required",
242
+ ...(!requireLive ? { field: "yes" } : {}),
243
+ }
244
+ );
245
+ }
246
+ if (requireLive && !eligibleHuman) {
247
+ usage("This operation is supported only in an eligible interactive terminal.", {
248
+ code: "unsupported_in_machine_mode",
249
+ });
250
+ }
251
+ stderr.write(`${title}\n`);
252
+ for (const term of terms) stderr.write(` ${term.label}: ${term.value}\n`);
253
+
254
+ if (!requireLive && options.yes === true) return true;
255
+ if (!eligibleHuman) {
256
+ usage("Consent is required. Review the terms and supply --yes to continue.", {
257
+ code: "consent_required",
258
+ field: "yes",
259
+ });
260
+ }
261
+ let answer;
262
+ try {
263
+ answer = await readline().question(`${prompt} [y/N] `);
264
+ } catch (caught) {
265
+ if (caught?.code === "ERR_USE_AFTER_CLOSE") return false;
266
+ throw caught;
267
+ }
268
+ return /^(y|yes)$/i.test(String(answer ?? "").trim());
269
+ }
270
+
271
+ function webUrl(path) {
272
+ return new URL(path, `${resolveAuthBaseUrl({ env: io.env })}/`).toString();
273
+ }
274
+
275
+ const interaction = {
276
+ actionRequired: ({ action, data, resume }) =>
277
+ execution({
278
+ action,
279
+ data,
280
+ exitCode: 3,
281
+ outcome: { class: "action_required", phase: action.kind ?? "external_action" },
282
+ resume,
283
+ }),
284
+ ask,
285
+ close() {
286
+ reader?.close();
287
+ },
288
+ inform(lines) {
289
+ if (!eligibleHuman || machine) return;
290
+ for (const line of lines) stderr.write(`${line}\n`);
291
+ },
292
+ eligibleHuman,
293
+ error,
294
+ idempotencyKey(supplied) {
295
+ const stable = typeof supplied === "string" ? supplied.trim() : "";
296
+ return stable || `ornn-${randomUUID()}`;
297
+ },
298
+ format,
299
+ formatProvided,
300
+ machine,
301
+ options,
302
+ redirected,
303
+ result: execution,
304
+ review,
305
+ usage,
306
+ webUrl,
307
+ };
308
+ Object.assign(interaction, extensions);
309
+ return interaction;
310
+ }