@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,1350 @@
1
+ import { HARDWARE_CATALOG_PATH } from "../vendor/capabilities/cli.js";
2
+
3
+ import {
4
+ CliApiError,
5
+ cliRequest,
6
+ operatorRequest,
7
+ resolveApiBaseUrl,
8
+ resolveInstallerBaseUrl,
9
+ } from "./api-client.mjs";
10
+ import {
11
+ clearPendingWorkspace,
12
+ loadAuthSession,
13
+ loadPendingWorkspace,
14
+ savePendingWorkspace,
15
+ } from "./auth-store.mjs";
16
+ import {
17
+ AdminUsageError,
18
+ adminCommandData,
19
+ adminRootData,
20
+ finalizeAdminResult,
21
+ resolveAdminRequest,
22
+ writeAdminHelpHuman,
23
+ writeAdminResultHuman,
24
+ writeAdminRootHuman,
25
+ } from "./admin-shell.mjs";
26
+ import { adminCommandName, preflightAdminCommand } from "./admin-command-handlers.mjs";
27
+ import { AdminMutationError } from "./admin-mutations.mjs";
28
+ import { loadCapabilities } from "./capability-catalog.mjs";
29
+ import {
30
+ continueConnectWithHandlers,
31
+ resolveConnectTargetWithHandlers,
32
+ } from "./connect-mode-handlers.mjs";
33
+ import { dispatchLaunchWithHandlers } from "./launch-mode-handlers.mjs";
34
+ import { updateCommand } from "./update.mjs";
35
+ import {
36
+ executeExternalBucketCommand,
37
+ writeExternalBucketResultHuman,
38
+ } from "./external-bucket-commands.mjs";
39
+ import { executeEvidenceCommand } from "./evidence-commands.mjs";
40
+ import {
41
+ executeKubernetesClusterCommand,
42
+ kubernetesConnectModeHandler,
43
+ kubernetesLaunchModeHandler,
44
+ } from "./kubernetes-commands.mjs";
45
+ import {
46
+ executeSessionCommand,
47
+ parseSessionCommandInput,
48
+ SessionCommandError,
49
+ } from "./session-commands.mjs";
50
+ import {
51
+ createInteractionRuntime,
52
+ InteractionError,
53
+ isInteractionExecution,
54
+ parseSemanticArguments,
55
+ } from "./interaction-runtime.mjs";
56
+ import { executeMountCommand } from "./mount-commands.mjs";
57
+ import {
58
+ executeStorageCommand,
59
+ StorageCommandError,
60
+ writeStorageResultHuman,
61
+ } from "./storage-commands.mjs";
62
+ import {
63
+ exportVpnSecret,
64
+ hasLocalNetworkAddress,
65
+ prepareVpnSecretExport,
66
+ wireGuardPublicKeyFromFile,
67
+ } from "./vpn-secret-output.mjs";
68
+ import { createSlurmConnectModeHandler, slurmLaunchModeHandler } from "./slurm-commands.mjs";
69
+ import {
70
+ bareMetalLaunchModeHandler,
71
+ createBareMetalConnectModeHandler,
72
+ createVmConnectModeHandler,
73
+ executeMachineCommand,
74
+ executeSshKeyCommand,
75
+ vmLaunchModeHandler,
76
+ } from "./vm-commands.mjs";
77
+ import { waitForGoal } from "./wait-runtime.mjs";
78
+
79
+ const MACHINE_SCHEMA = "ornn.cli/v1";
80
+ const UPDATE_CHANNEL = "stable";
81
+ const FORMATS = { human: true, json: true, ndjson: true };
82
+ const SESSION_CAPABILITY_IDS = new Set(["identity.context", "identity.login", "identity.logout"]);
83
+ export const CLI_EXIT_STATUS = Object.freeze({
84
+ success: 0,
85
+ failure: 1,
86
+ usage: 2,
87
+ actionRequired: 3,
88
+ partial: 4,
89
+ authorization: 5,
90
+ unavailable: 6,
91
+ timeout: 124,
92
+ interrupted: 130,
93
+ });
94
+
95
+ class VnextUsageError extends Error {
96
+ constructor(message, { code = "usage_error", field } = {}) {
97
+ super(message);
98
+ this.name = "VnextUsageError";
99
+ this.code = code;
100
+ this.field = field;
101
+ }
102
+ }
103
+
104
+ class VnextContractError extends Error {
105
+ constructor(message, code) {
106
+ super(message);
107
+ this.name = "VnextContractError";
108
+ this.code = code;
109
+ }
110
+ }
111
+
112
+ function extractFormat(argv) {
113
+ const remaining = [];
114
+ let format;
115
+ for (let index = 0; index < argv.length; index += 1) {
116
+ const argument = argv[index];
117
+ if (argument === "--format" || argument.startsWith("--format=")) {
118
+ if (format !== undefined) {
119
+ return {
120
+ error: new VnextUsageError("--format may be supplied only once.", { field: "format" }),
121
+ format,
122
+ remaining,
123
+ };
124
+ }
125
+ const inline = argument.startsWith("--format=") ? argument.slice("--format=".length) : null;
126
+ const value = inline ?? argv[index + 1];
127
+ if (!value || (!inline && value.startsWith("-"))) {
128
+ return {
129
+ error: new VnextUsageError("--format requires human, json, or ndjson.", {
130
+ field: "format",
131
+ }),
132
+ format,
133
+ remaining,
134
+ };
135
+ }
136
+ if (inline === null) index += 1;
137
+ format = value;
138
+ continue;
139
+ }
140
+ remaining.push(argument);
141
+ }
142
+ if (format !== undefined && FORMATS[format] !== true) {
143
+ return {
144
+ error: new VnextUsageError(`Unsupported format: ${format}. Choose human, json, or ndjson.`, {
145
+ field: "format",
146
+ }),
147
+ format,
148
+ remaining,
149
+ };
150
+ }
151
+ return { error: null, format, remaining };
152
+ }
153
+
154
+ function vnextCommand(capability) {
155
+ return capability.cli?.vnext?.command ?? capability.cli?.command ?? [];
156
+ }
157
+
158
+ function customerCommands(catalog) {
159
+ return catalog
160
+ .filter((capability) => capability.cli?.vnext?.audience === "customer")
161
+ .sort((left, right) => {
162
+ const leftOrder = left.cli.vnext.order ?? Number.MAX_SAFE_INTEGER;
163
+ const rightOrder = right.cli.vnext.order ?? Number.MAX_SAFE_INTEGER;
164
+ return (
165
+ leftOrder - rightOrder ||
166
+ vnextCommand(left).join(" ").localeCompare(vnextCommand(right).join(" "))
167
+ );
168
+ });
169
+ }
170
+
171
+ function commandMatch(catalog, argv) {
172
+ return customerCommands(catalog)
173
+ .filter((capability) => vnextCommand(capability).every((part, index) => argv[index] === part))
174
+ .sort((left, right) => vnextCommand(right).length - vnextCommand(left).length)[0];
175
+ }
176
+
177
+ function editDistance(left, right) {
178
+ const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
179
+ for (let leftIndex = 1; leftIndex <= left.length; leftIndex += 1) {
180
+ const current = [leftIndex];
181
+ for (let rightIndex = 1; rightIndex <= right.length; rightIndex += 1) {
182
+ current[rightIndex] = Math.min(
183
+ current[rightIndex - 1] + 1,
184
+ previous[rightIndex] + 1,
185
+ previous[rightIndex - 1] + (left[leftIndex - 1] === right[rightIndex - 1] ? 0 : 1)
186
+ );
187
+ }
188
+ previous.splice(0, previous.length, ...current);
189
+ }
190
+ return previous[right.length];
191
+ }
192
+
193
+ function suggestedCommand(catalog, input) {
194
+ if (!input || input.startsWith("-")) return null;
195
+ const roots = [
196
+ ...new Set(customerCommands(catalog).map((capability) => vnextCommand(capability)[0])),
197
+ ];
198
+ const candidates = roots
199
+ .map((command) => ({ command, distance: editDistance(input, command) }))
200
+ .sort(
201
+ (left, right) => left.distance - right.distance || left.command.localeCompare(right.command)
202
+ );
203
+ return candidates[0]?.distance <= 2 ? candidates[0].command : null;
204
+ }
205
+
206
+ function commandGroup(catalog, path) {
207
+ const matches = customerCommands(catalog).filter((capability) => {
208
+ const command = vnextCommand(capability);
209
+ return path.length < command.length && path.every((part, index) => command[index] === part);
210
+ });
211
+ return matches.length ? matches : null;
212
+ }
213
+
214
+ function retiredSyntaxError(argv, findLegacyCliMigration, migrationGuideUrl) {
215
+ if (argv.includes("--json")) {
216
+ const replacement = argv
217
+ .map((argument) => (argument === "--json" ? "--format json" : argument))
218
+ .join(" ");
219
+ return new VnextUsageError(
220
+ `Retired syntax: --json. Use \`ornn ${replacement}\`. See ${migrationGuideUrl}`,
221
+ { code: "legacy_command_removed", field: "format" }
222
+ );
223
+ }
224
+ if (argv.includes("--interactive")) {
225
+ const replacement = argv
226
+ .map((argument) => (argument === "--interactive" ? "--guided" : argument))
227
+ .join(" ");
228
+ return new VnextUsageError(
229
+ `Retired syntax: --interactive. Use \`ornn ${replacement}\`. See ${migrationGuideUrl}`,
230
+ { code: "legacy_command_removed", field: "guided" }
231
+ );
232
+ }
233
+ const migration = findLegacyCliMigration?.(argv);
234
+ if (!migration) return null;
235
+ return new VnextUsageError(
236
+ `Retired command: ornn ${migration.legacy.join(" ")}. Use \`${migration.replacement}\`. See ${migrationGuideUrl}`,
237
+ { code: "legacy_command_removed" }
238
+ );
239
+ }
240
+
241
+ function resolveRequest(catalog, argv, findLegacyCliMigration, migrationGuideUrl) {
242
+ if (argv.length === 0 || (argv.length === 1 && (argv[0] === "--help" || argv[0] === "-h"))) {
243
+ return { kind: "root", command: "help" };
244
+ }
245
+ if (argv[0] === "--help" || argv[0] === "-h") {
246
+ return {
247
+ kind: "error",
248
+ command: "help",
249
+ error: new VnextUsageError("Usage: ornn [--format human|json|ndjson]"),
250
+ };
251
+ }
252
+
253
+ const retired = retiredSyntaxError(argv, findLegacyCliMigration, migrationGuideUrl);
254
+ if (retired) {
255
+ return { kind: "error", command: argv.slice(0, 2).join(" ") || "help", error: retired };
256
+ }
257
+
258
+ if (argv[0] === "admin") {
259
+ return { kind: "admin", command: "admin", argv: argv.slice(1) };
260
+ }
261
+
262
+ if (argv[0] === "help") {
263
+ if (argv.length === 1) return { kind: "root", command: "help" };
264
+ if (argv[1] === "admin") {
265
+ return { kind: "admin", command: "admin", argv: ["help", ...argv.slice(2)] };
266
+ }
267
+ const targetArgv = argv.slice(1);
268
+ const target = commandMatch(catalog, targetArgv);
269
+ if (target && targetArgv.length === vnextCommand(target).length) {
270
+ return {
271
+ kind: "help",
272
+ command: `help ${vnextCommand(target).join(" ")}`,
273
+ capability: target,
274
+ };
275
+ }
276
+ const group = commandGroup(catalog, targetArgv);
277
+ if (group) {
278
+ return {
279
+ kind: "help-group",
280
+ command: `help ${targetArgv.join(" ")}`,
281
+ path: targetArgv,
282
+ capabilities: group,
283
+ };
284
+ }
285
+ const suggestion = suggestedCommand(catalog, argv[1]);
286
+ return {
287
+ kind: "error",
288
+ command: `help ${argv[1]}`,
289
+ error: new VnextUsageError(
290
+ suggestion
291
+ ? `Unknown command: ${argv[1]}. Did you mean \`ornn help ${suggestion}\`?`
292
+ : `Unknown command: ${targetArgv.join(" ")}. Run \`ornn help\` to list registered commands.`,
293
+ { code: "unknown_command" }
294
+ ),
295
+ };
296
+ }
297
+
298
+ const trailingHelp = argv.at(-1) === "--help" || argv.at(-1) === "-h";
299
+ if (trailingHelp) {
300
+ const targetArgv = argv.slice(0, -1);
301
+ const group = commandGroup(catalog, targetArgv);
302
+ if (group) {
303
+ return {
304
+ kind: "help-group",
305
+ command: `help ${targetArgv.join(" ")}`,
306
+ path: targetArgv,
307
+ capabilities: group,
308
+ };
309
+ }
310
+ }
311
+
312
+ const capability = commandMatch(catalog, argv);
313
+ if (capability) {
314
+ const command = vnextCommand(capability);
315
+ const rest = argv.slice(command.length);
316
+ const helpFlags = rest.filter((argument) => argument === "--help" || argument === "-h");
317
+ if (helpFlags.length) {
318
+ if (rest.length !== 1) {
319
+ return {
320
+ kind: "error",
321
+ command: command.join(" "),
322
+ error: new VnextUsageError(`Usage: ${capability.cli.vnext.human}`),
323
+ };
324
+ }
325
+ return { kind: "help", command: `help ${command.join(" ")}`, capability };
326
+ }
327
+ if (rest.length && capability.cli.vnext.arguments !== "semantic") {
328
+ return {
329
+ kind: "error",
330
+ command: command.join(" "),
331
+ error: new VnextUsageError(`Usage: ${capability.cli.vnext.human}`),
332
+ };
333
+ }
334
+ return { kind: "command", command: command.join(" "), capability, argv: rest };
335
+ }
336
+
337
+ const suggestion = suggestedCommand(catalog, argv[0]);
338
+ return {
339
+ kind: "error",
340
+ command: argv[0] ?? "help",
341
+ error: new VnextUsageError(
342
+ suggestion
343
+ ? `Unknown command: ${argv[0]}. Did you mean \`ornn ${suggestion} --help\`?`
344
+ : `Unknown command: ${argv.join(" ")}. Run \`ornn help\` to list registered commands.`,
345
+ { code: "unknown_command" }
346
+ ),
347
+ };
348
+ }
349
+
350
+ function commandData(capability) {
351
+ const help = capability.cli.vnext;
352
+ return {
353
+ command: vnextCommand(capability).join(" "),
354
+ outcome: help.outcome,
355
+ human: help.human,
356
+ automation: help.automation,
357
+ accepted_references: help.acceptedReferences ?? [],
358
+ review: help.review ?? null,
359
+ resumable_states: help.resumableStates ?? [],
360
+ related: help.related ?? [],
361
+ };
362
+ }
363
+
364
+ function rootData(catalog) {
365
+ const commands = customerCommands(catalog);
366
+ return {
367
+ journey: commands
368
+ .filter((capability) => capability.cli.vnext.category === "journey")
369
+ .map(commandData),
370
+ session: commands
371
+ .filter((capability) => capability.cli.vnext.category === "session")
372
+ .map(commandData),
373
+ platform: commands
374
+ .filter((capability) => capability.cli.vnext.category === "platform")
375
+ .map(commandData),
376
+ advanced_resources: [
377
+ ...new Set(
378
+ commands
379
+ .filter((capability) => capability.cli.vnext.category === "resource")
380
+ .map((capability) => vnextCommand(capability)[0])
381
+ ),
382
+ ],
383
+ };
384
+ }
385
+
386
+ function completionData(catalog, shell) {
387
+ const paths = customerCommands(catalog).map((capability) => vnextCommand(capability).join(" "));
388
+ if (shell === "fish") {
389
+ const children = {};
390
+ const lines = ["complete -c ornn -f"];
391
+ for (const path of paths) {
392
+ const parts = path.split(" ");
393
+ for (let index = 0; index < parts.length; index += 1) {
394
+ const parent = parts.slice(0, index).join(" ");
395
+ (children[parent] ??= []).push(parts[index]);
396
+ }
397
+ }
398
+ for (const [parent, candidates] of Object.entries(children)) {
399
+ const unique = [...new Set(candidates)].sort().join(" ");
400
+ const condition = parent
401
+ ? ` -n '__fish_seen_subcommand_from ${parent.split(" ").join(" ")}'`
402
+ : " -n '__fish_use_subcommand'";
403
+ lines.push(`complete -c ornn -f${condition} -a '${unique}'`);
404
+ }
405
+ return { commands: paths, script: `${lines.join("\n")}\n`, shell };
406
+ }
407
+ const quotedPaths = paths.map((path) => `'${path}'`).join(" ");
408
+ if (shell === "zsh") {
409
+ return {
410
+ commands: paths,
411
+ shell,
412
+ script: `#compdef ornn\n_ornn() {\n local -a paths candidates\n paths=(${quotedPaths})\n local prefix=\"\${(j: :)words[2,CURRENT-1]}\" path rest\n for path in \"\${paths[@]}\"; do\n if [[ -z \"$prefix\" ]]; then rest=\"$path\"\n elif [[ \"$path\" == \"$prefix \"* ]]; then rest=\"\${path#$prefix }\"\n else continue\n fi\n candidates+=(\"\${rest%% *}\")\n done\n compadd -a candidates\n}\ncompdef _ornn ornn\n`,
413
+ };
414
+ }
415
+ return {
416
+ commands: paths,
417
+ shell,
418
+ script: `_ornn_complete() {\n local current=\"\${COMP_WORDS[COMP_CWORD]}\" prefix=\"\" path rest candidate candidates=\"\"\n local paths=(${quotedPaths})\n if (( COMP_CWORD > 1 )); then prefix=\"\${COMP_WORDS[*]:1:COMP_CWORD-1}\"; fi\n for path in \"\${paths[@]}\"; do\n if [[ -z \"$prefix\" ]]; then rest=\"$path\"\n elif [[ \"$path\" == \"$prefix \"* ]]; then rest=\"\${path#\"$prefix \"}\"\n else continue\n fi\n candidate=\"\${rest%% *}\"\n [[ \" $candidates \" == *\" $candidate \"* ]] || candidates+=\" $candidate\"\n done\n COMPREPLY=( $(compgen -W \"$candidates\" -- \"$current\") )\n}\ncomplete -F _ornn_complete ornn\n`,
419
+ };
420
+ }
421
+
422
+ function groupData(path, capabilities) {
423
+ return {
424
+ path: path.join(" "),
425
+ commands: capabilities.map(commandData),
426
+ };
427
+ }
428
+
429
+ function resultEnvelope(
430
+ command,
431
+ data,
432
+ outcome = { class: "ready", phase: "complete" },
433
+ page = null
434
+ ) {
435
+ return {
436
+ schema: MACHINE_SCHEMA,
437
+ type: "result",
438
+ command,
439
+ outcome,
440
+ data,
441
+ resume: null,
442
+ action: null,
443
+ page,
444
+ errors: [],
445
+ };
446
+ }
447
+ function executionEnvelope(command, execution) {
448
+ return {
449
+ ...resultEnvelope(command, execution.data, execution.outcome, execution.page ?? null),
450
+ action: execution.action ?? null,
451
+ errors: execution.errors ?? [],
452
+ resume: execution.resume ?? null,
453
+ };
454
+ }
455
+
456
+ function errorDetail(error) {
457
+ const payload = error?.detail;
458
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return null;
459
+ if (payload.detail && typeof payload.detail === "object" && !Array.isArray(payload.detail)) {
460
+ return payload.detail;
461
+ }
462
+ if (payload.error && typeof payload.error === "object" && !Array.isArray(payload.error)) {
463
+ return payload.error;
464
+ }
465
+ return payload;
466
+ }
467
+
468
+ function modeledError(error) {
469
+ if (error instanceof SessionCommandError) {
470
+ return {
471
+ action: error.action,
472
+ exitCode: error.exitCode,
473
+ outcomeClass: error.outcomeClass,
474
+ error: {
475
+ code: error.code,
476
+ message: error.message,
477
+ retryable: error.retryable,
478
+ },
479
+ };
480
+ }
481
+ if (error instanceof StorageCommandError) {
482
+ return {
483
+ action: error.action ?? null,
484
+ exitCode: error.exitCode,
485
+ outcomeClass: error.outcomeClass,
486
+ error: {
487
+ code: error.code,
488
+ message: error.message,
489
+ retryable: error.retryable,
490
+ ...(error.field ? { field: error.field } : {}),
491
+ },
492
+ };
493
+ }
494
+
495
+ if (
496
+ error instanceof VnextUsageError ||
497
+ error instanceof AdminUsageError ||
498
+ error instanceof VnextContractError ||
499
+ error instanceof AdminMutationError ||
500
+ error instanceof InteractionError
501
+ ) {
502
+ return {
503
+ action: error.action ?? null,
504
+ exitCode:
505
+ error instanceof VnextContractError
506
+ ? CLI_EXIT_STATUS.failure
507
+ : error instanceof InteractionError
508
+ ? error.exitCode
509
+ : CLI_EXIT_STATUS.usage,
510
+ outcomeClass: error.outcomeClass,
511
+ error: {
512
+ code: error.code,
513
+ message: error.message,
514
+ retryable: error.retryable ?? false,
515
+ ...(error.field ? { field: error.field } : {}),
516
+ },
517
+ };
518
+ }
519
+
520
+ const detail = errorDetail(error);
521
+ const suppliedCode = detail?.code;
522
+ if (typeof suppliedCode === "string" && /^[a-z][a-z0-9_]*$/.test(suppliedCode)) {
523
+ const retryable = detail.retryable === true;
524
+ return {
525
+ exitCode:
526
+ error?.status === 401 || error?.status === 403
527
+ ? CLI_EXIT_STATUS.authorization
528
+ : retryable
529
+ ? CLI_EXIT_STATUS.unavailable
530
+ : CLI_EXIT_STATUS.failure,
531
+ error: { code: suppliedCode, message: error.message, retryable },
532
+ };
533
+ }
534
+ if (error instanceof CliApiError && (error.status === 401 || error.status === 403)) {
535
+ return {
536
+ exitCode: CLI_EXIT_STATUS.authorization,
537
+ error: {
538
+ code: error.status === 401 ? "authentication_required" : "authorization_denied",
539
+ message: error.message,
540
+ retryable: false,
541
+ },
542
+ };
543
+ }
544
+ if (error instanceof CliApiError && (error.status === undefined || error.status >= 500)) {
545
+ return {
546
+ exitCode: CLI_EXIT_STATUS.unavailable,
547
+ error: { code: "service_unavailable", message: error.message, retryable: true },
548
+ };
549
+ }
550
+ return {
551
+ exitCode: CLI_EXIT_STATUS.failure,
552
+ error: {
553
+ code: "command_failed",
554
+ message: error instanceof Error ? error.message : String(error),
555
+ retryable: false,
556
+ },
557
+ };
558
+ }
559
+
560
+ function errorEnvelope(command, modeled) {
561
+ return {
562
+ ...resultEnvelope(command, null, {
563
+ class: modeled.outcomeClass ?? "failed",
564
+ phase: modeled.error.code,
565
+ }),
566
+ action: modeled.action ?? null,
567
+ errors: [modeled.error],
568
+ };
569
+ }
570
+
571
+ function writeMachine(stdout, format, envelope) {
572
+ const serialized = JSON.stringify(envelope);
573
+ stdout.write(format === "json" ? `${JSON.stringify(envelope, null, 2)}\n` : `${serialized}\n`);
574
+ }
575
+
576
+ function writeExecutionMachine(stdout, format, command, execution) {
577
+ if (format === "ndjson") {
578
+ for (const progress of execution.progress ?? []) {
579
+ writeMachine(stdout, format, {
580
+ ...resultEnvelope(command, progress.data ?? null, progress.outcome),
581
+ type: "progress",
582
+ ...(progress.sequence !== undefined ? { sequence: progress.sequence } : {}),
583
+ ...(progress.observedAt !== undefined ? { observed_at: progress.observedAt } : {}),
584
+ ...(progress.message !== undefined ? { message: progress.message } : {}),
585
+ action: progress.action ?? null,
586
+ errors: progress.errors ?? [],
587
+ resume: progress.resume ?? null,
588
+ });
589
+ }
590
+ }
591
+ writeMachine(stdout, format, executionEnvelope(command, execution));
592
+ }
593
+
594
+ function writeRootHuman(stdout, catalog) {
595
+ const data = rootData(catalog);
596
+ const writeCommands = (title, commands) => {
597
+ stdout.write(`${title}:\n`);
598
+ for (const command of commands) {
599
+ stdout.write(` ${command.command} ${command.outcome}\n`);
600
+ }
601
+ stdout.write("\n");
602
+ };
603
+ stdout.write("Ornn CLI\n\n");
604
+ stdout.write("Usage:\n ornn <command> [options]\n\n");
605
+ stdout.write("Available commands:\n\n");
606
+ writeCommands("Journey", data.journey);
607
+ writeCommands("Session", data.session);
608
+ writeCommands("Platform", data.platform);
609
+ stdout.write("Advanced resources:\n");
610
+ for (const resource of data.advanced_resources) stdout.write(` ${resource}\n`);
611
+ stdout.write(
612
+ "\nRun `ornn resources` for advanced outcomes or `ornn help <command>` for scoped help.\n"
613
+ );
614
+ stdout.write("Redirected commands require --format human, json, or ndjson.\n");
615
+ }
616
+
617
+ function writeHelpHuman(stdout, capability) {
618
+ const data = commandData(capability);
619
+ stdout.write(`${data.command}\n\nOutcome: ${data.outcome}\n\n`);
620
+ stdout.write(`Usage:\n ${data.human}\n\nAutomation:\n ${data.automation}\n`);
621
+ stdout.write(
622
+ `\nAccepted references: ${data.accepted_references.length ? data.accepted_references.join(", ") : "none"}\n`
623
+ );
624
+ stdout.write(`Review: ${data.review ?? "none"}\n`);
625
+ stdout.write(
626
+ `Resumable states: ${data.resumable_states.length ? data.resumable_states.join(", ") : "none"}\n`
627
+ );
628
+ stdout.write(
629
+ `Related: ${data.related.length ? data.related.map((command) => `ornn ${command}`).join(", ") : "none"}\n`
630
+ );
631
+ stdout.write("Environment: redirected vNext commands require --format human, json, or ndjson.\n");
632
+ }
633
+
634
+ function writeGroupHelpHuman(stdout, path, capabilities) {
635
+ stdout.write(`${path.join(" ")}\n\nAvailable commands:\n`);
636
+ for (const capability of capabilities) {
637
+ const data = commandData(capability);
638
+ stdout.write(` ${data.human}\n ${data.outcome}\n`);
639
+ }
640
+ stdout.write("\nRun `ornn help <full-command>` for references, review, and recovery details.\n");
641
+ }
642
+
643
+ function writeStatusHuman(stdout, snapshot) {
644
+ stdout.write(`${snapshot?.overallStatusLabel ?? snapshot?.status ?? "Status available"}\n`);
645
+ if (snapshot?.overallDetail) stdout.write(`${snapshot.overallDetail}\n`);
646
+ if (snapshot?.generatedAtLabel) stdout.write(`Generated: ${snapshot.generatedAtLabel}\n`);
647
+ for (const group of snapshot?.groups ?? []) {
648
+ stdout.write(`${group.name}: ${group.statusLabel}\n`);
649
+ }
650
+ }
651
+
652
+ function writeContextHuman(stdout, data) {
653
+ const user = data.user?.email ?? data.user?.id ?? "unknown";
654
+ stdout.write(`User: ${user}\n`);
655
+ if (data.selected_organization) {
656
+ stdout.write(`Organization: ${data.selected_organization.name}\n`);
657
+ stdout.write(`Approved: ${data.selected_organization.approved ? "yes" : "no"}\n`);
658
+ } else {
659
+ stdout.write("Organization: none selected\n");
660
+ stdout.write("Approved: not selected\n");
661
+ }
662
+ stdout.write("Available organizations:\n");
663
+ if (!data.organizations?.length) {
664
+ stdout.write(" none\n");
665
+ return;
666
+ }
667
+ for (const organization of data.organizations) {
668
+ stdout.write(
669
+ ` ${organization.name} (${organization.approved ? "approved" : "not approved"})\n`
670
+ );
671
+ }
672
+ }
673
+
674
+ function writeCommandHuman(stdout, stderr, capability, execution) {
675
+ const { action, data, errors = [] } = execution;
676
+ if (capability.id === "identity.status") {
677
+ writeStatusHuman(stdout, data);
678
+ return;
679
+ }
680
+ if (capability.id === "identity.login") {
681
+ if (data.session_saved) stdout.write("Logged in to Ornn.\n");
682
+ if (data.user) writeContextHuman(stdout, data);
683
+ if (action?.type === "browser") {
684
+ stderr.write(`Complete login in a browser:\n${action.url}\nCode: ${action.code}\n`);
685
+ } else if (action?.command) {
686
+ stderr.write(`Next: ${action.command}\n`);
687
+ }
688
+ return;
689
+ }
690
+ if (capability.id === "identity.context") {
691
+ writeContextHuman(stdout, data);
692
+ if (action?.command) stderr.write(`Next: ${action.command}\n`);
693
+ return;
694
+ }
695
+ if (capability.id === "identity.logout") {
696
+ stdout.write(
697
+ data.remote_session_revoked ? "Logged out of Ornn.\n" : "Local Ornn session cleared.\n"
698
+ );
699
+ for (const error of errors) stderr.write(`${error.message}\n`);
700
+ return;
701
+ }
702
+ if (capability.id === "cli.version") {
703
+ stdout.write(`Ornn CLI ${data.version}\n`);
704
+ stdout.write(`Machine schema: ${data.machine_schema}\n`);
705
+ stdout.write(`Update channel: ${data.update_channel}\n`);
706
+ return;
707
+ }
708
+ if (capability.id === "cli.resources") {
709
+ stdout.write("Advanced resources\n");
710
+ if (!data.resources.length) {
711
+ stdout.write("No advanced resource commands are registered yet.\n");
712
+ return;
713
+ }
714
+ for (const resource of data.resources) {
715
+ stdout.write(`${resource.command}: ${resource.outcome}\n`);
716
+ stdout.write(` Human: ${resource.human}\n`);
717
+ stdout.write(` Automation: ${resource.automation}\n`);
718
+ }
719
+ return;
720
+ }
721
+ if (capability.id === "cli.completion") {
722
+ stdout.write(data.script);
723
+ return;
724
+ }
725
+ if (capability.id.startsWith("storage.volumes.")) {
726
+ writeStorageResultHuman(stdout, capability.id, execution);
727
+ return;
728
+ }
729
+ if (capability.id.startsWith("storage.external-buckets.")) {
730
+ writeExternalBucketResultHuman(stdout, stderr, capability.id, execution);
731
+ return;
732
+ }
733
+ if (execution.presentation?.lines) {
734
+ for (const line of execution.presentation.lines) stdout.write(`${line}\n`);
735
+ if (execution.presentation.next) stderr.write(`Next: ${execution.presentation.next}\n`);
736
+ return;
737
+ }
738
+ if (action?.url) {
739
+ stderr.write(`${action.description ?? "External action required"}\n${action.url}\n`);
740
+ const command =
741
+ action.command ??
742
+ (execution.resume?.ref
743
+ ? `ornn ${vnextCommand(capability).join(" ")} --resume ${execution.resume.ref}`
744
+ : null);
745
+ if (command) stderr.write(`Next: ${command}\n`);
746
+ } else if (data !== null && data !== undefined) {
747
+ stdout.write(`${JSON.stringify(data, null, 2)}\n`);
748
+ }
749
+ }
750
+
751
+ const CUSTOMER_FORBIDDEN_FIELDS = {
752
+ client_secret: true,
753
+ clientSecret: true,
754
+ payment_secret: true,
755
+ paymentSecret: true,
756
+ operator: true,
757
+ operatorId: true,
758
+ operatorName: true,
759
+ operator_id: true,
760
+ operator_name: true,
761
+ internal_owner: true,
762
+ internalOwner: true,
763
+ payment_intent_client_secret: true,
764
+ checkout_key: true,
765
+ checkoutKey: true,
766
+ provider_id: true,
767
+ provider_name: true,
768
+ providerId: true,
769
+ providerName: true,
770
+ paymentIntentClientSecret: true,
771
+ payment_provider: true,
772
+ paymentProvider: true,
773
+ provider: true,
774
+ site_operator: true,
775
+ siteOperator: true,
776
+ site_operator_id: true,
777
+ site_operator_name: true,
778
+ siteOperatorId: true,
779
+ siteOperatorName: true,
780
+ reconciliation: true,
781
+ reconciliation_status: true,
782
+ reconciliationStatus: true,
783
+ stripe_client_secret: true,
784
+ stripeClientSecret: true,
785
+ stripe_invoice_id: true,
786
+ stripeInvoiceId: true,
787
+ supplier: true,
788
+ supplier_id: true,
789
+ supplier_name: true,
790
+ supplierId: true,
791
+ supplierName: true,
792
+ };
793
+
794
+ function assertCustomerResultSafe(value) {
795
+ if (Array.isArray(value)) {
796
+ for (const item of value) assertCustomerResultSafe(item);
797
+ return;
798
+ }
799
+ if (!value || typeof value !== "object") return;
800
+ for (const [key, child] of Object.entries(value)) {
801
+ if (CUSTOMER_FORBIDDEN_FIELDS[key] === true) {
802
+ throw new VnextContractError(
803
+ "The gateway returned an unexpected response shape.",
804
+ "customer_result_unsafe"
805
+ );
806
+ }
807
+ assertCustomerResultSafe(child);
808
+ }
809
+ }
810
+
811
+ function parseCapabilityInput(capability, rawInput) {
812
+ const parsed = capability.input.safeParse(rawInput);
813
+ if (parsed.success) return parsed.data;
814
+ const issue = parsed.error.issues[0];
815
+ if (issue?.path?.[0] === "$positionals") {
816
+ const usage = capability.cli?.vnext?.human ?? `ornn ${vnextCommand(capability).join(" ")}`;
817
+ throw new VnextUsageError(`Usage: ${usage}`);
818
+ }
819
+ const field = issue?.path?.length ? issue.path.join(".") : undefined;
820
+ throw new InteractionError(issue?.message ?? "Invalid command input.", {
821
+ code: "invalid_input",
822
+ exitCode: CLI_EXIT_STATUS.usage,
823
+ field,
824
+ });
825
+ }
826
+
827
+ async function customerCapabilityFetch(io, path, init = {}, sessionSnapshot) {
828
+ const authRequired = path !== "/v1/cli/status";
829
+ const session = sessionSnapshot?.current;
830
+ const method = init.method ?? "GET";
831
+ if (authRequired && sessionSnapshot && !session?.accessToken) {
832
+ throw new CliApiError("Not logged in. Run `ornn login` first.", { status: 401 });
833
+ }
834
+ if (authRequired && sessionSnapshot && !["GET", "HEAD"].includes(method)) {
835
+ const current = await loadAuthSession({ env: io.env });
836
+ if (current?.accessToken !== session?.accessToken) {
837
+ throw new InteractionError(
838
+ "Authentication changed while this command was running; no mutation was sent. Review and retry.",
839
+ {
840
+ code: "auth_session_changed",
841
+ exitCode: 3,
842
+ }
843
+ );
844
+ }
845
+ }
846
+ return cliRequest({
847
+ authRequired,
848
+ endpoint: path,
849
+ env: io.env,
850
+ fetchImpl: io.fetchImpl,
851
+ headers: init.headers,
852
+ method,
853
+ ...(authRequired && session ? { session } : {}),
854
+ ...(init.body !== undefined ? { body: init.body } : {}),
855
+ ...(init.signal !== undefined ? { signal: init.signal } : {}),
856
+ });
857
+ }
858
+
859
+ async function executeCapability(
860
+ capability,
861
+ catalog,
862
+ io,
863
+ version,
864
+ {
865
+ audience = "customer",
866
+ input,
867
+ role = "user",
868
+ confirmed = false,
869
+ argv = [],
870
+ executionRuntime,
871
+ } = {}
872
+ ) {
873
+ const sessionCommand = SESSION_CAPABILITY_IDS.has(capability.id);
874
+ const authSnapshot = executionRuntime?.authSnapshot;
875
+ if (
876
+ audience === "customer" &&
877
+ !sessionCommand &&
878
+ authSnapshot &&
879
+ authSnapshot.current === undefined
880
+ ) {
881
+ authSnapshot.current = await loadAuthSession({ env: io.env });
882
+ }
883
+ const rawInput =
884
+ input ??
885
+ (sessionCommand ? parseSessionCommandInput(capability.id, argv) : parseSemanticArguments(argv));
886
+ const interaction =
887
+ audience === "customer" && !sessionCommand
888
+ ? createInteractionRuntime({
889
+ eligibleHuman: executionRuntime.eligibleHuman,
890
+ extensions: {
891
+ ...executionRuntime.interactionExtensions,
892
+ externalBuckets(command, externalBucketInput) {
893
+ return executeExternalBucketCommand(command, externalBucketInput, {
894
+ fetch: (path, init) => customerCapabilityFetch(io, path, init, authSnapshot),
895
+ interaction: this,
896
+ signal: io.signal,
897
+ sleep: io.sleep,
898
+ stderr: io.stderr,
899
+ });
900
+ },
901
+ evidence(command, evidenceInput) {
902
+ return executeEvidenceCommand(command, evidenceInput, {
903
+ fetch: (path, init) => customerCapabilityFetch(io, path, init, authSnapshot),
904
+ interaction: this,
905
+ now: io.now,
906
+ signal: io.signal,
907
+ sleep:
908
+ io.sleep ??
909
+ ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds))),
910
+ stdout: io.stdout ?? process.stdout,
911
+ });
912
+ },
913
+ now: io.now,
914
+ signal: io.signal,
915
+ sleep: io.sleep,
916
+ stderr: io.stderr ?? process.stderr,
917
+ cluster(command, clusterInput) {
918
+ return executeKubernetesClusterCommand(command, clusterInput, {
919
+ fetch: (path, init) => customerCapabilityFetch(io, path, init, authSnapshot),
920
+ interaction: this,
921
+ now: io.now,
922
+ signal: io.signal,
923
+ sleep: io.sleep,
924
+ stderr: io.stderr ?? process.stderr,
925
+ });
926
+ },
927
+ mounts(command, mountInput) {
928
+ return executeMountCommand(command, mountInput, {
929
+ fetch: (path, init) => customerCapabilityFetch(io, path, init, authSnapshot),
930
+ interaction: this,
931
+ });
932
+ },
933
+ storage(command, storageInput) {
934
+ return executeStorageCommand(command, storageInput, {
935
+ env: io.env,
936
+ fetch: (path, init) => customerCapabilityFetch(io, path, init, authSnapshot),
937
+ fetchImpl: io.fetchImpl,
938
+ interaction: this,
939
+ });
940
+ },
941
+ },
942
+ format: executionRuntime.format,
943
+ formatProvided: executionRuntime.formatProvided,
944
+ io,
945
+ options: rawInput,
946
+ })
947
+ : null;
948
+ const installerBaseUrl =
949
+ audience === "admin"
950
+ ? (resolveInstallerBaseUrl({ env: io.env }) ??
951
+ `${resolveApiBaseUrl({ env: io.env })}/v1/orchestrator`)
952
+ : undefined;
953
+ const parsedInput = parseCapabilityInput(capability, rawInput);
954
+ let data;
955
+ try {
956
+ data = await capability.execute(
957
+ {
958
+ actor: "",
959
+ confirmed,
960
+ installerBaseUrl,
961
+ role,
962
+ fetch: (path, init = {}) => {
963
+ if (executionRuntime?.transport) return executionRuntime.transport(path, init);
964
+ if (
965
+ audience === "admin" &&
966
+ capability.id === "listings.create" &&
967
+ path === HARDWARE_CATALOG_PATH
968
+ ) {
969
+ return operatorRequest({
970
+ endpoint: "/catalog/hardware",
971
+ env: io.env,
972
+ fetchImpl: io.fetchImpl,
973
+ method: init.method ?? "GET",
974
+ ...(init.headers !== undefined ? { headers: init.headers } : {}),
975
+ ...(init.signal !== undefined ? { signal: init.signal } : {}),
976
+ });
977
+ }
978
+ if (audience === "admin") {
979
+ const search = new URL(path, "https://cli.invalid").search;
980
+ const endpoint =
981
+ capability.id === "telemetry.latest"
982
+ ? `/telemetry/latest${search}`
983
+ : capability.id === "logs.latest"
984
+ ? `/logs/latest${search}`
985
+ : path;
986
+ return operatorRequest({
987
+ endpoint,
988
+ env: io.env,
989
+ fetchImpl: io.fetchImpl,
990
+ method: init.method ?? "GET",
991
+ ...(init.body !== undefined ? { body: init.body } : {}),
992
+ ...(init.headers !== undefined ? { headers: init.headers } : {}),
993
+ ...((init.signal ?? io.signal) !== undefined
994
+ ? { signal: init.signal ?? io.signal }
995
+ : {}),
996
+ });
997
+ }
998
+ return customerCapabilityFetch(io, path, init, authSnapshot);
999
+ },
1000
+ cli: {
1001
+ catalog,
1002
+ interaction: interaction ?? undefined,
1003
+ completion: async ({ shell }) => completionData(catalog, shell),
1004
+ machineSchema: MACHINE_SCHEMA,
1005
+ session: (command, sessionInput) =>
1006
+ executeSessionCommand(command, sessionInput, {
1007
+ ...executionRuntime,
1008
+ sessionSnapshot: authSnapshot,
1009
+ }),
1010
+ updateChannel: UPDATE_CHANNEL,
1011
+ version,
1012
+ },
1013
+ },
1014
+ parsedInput
1015
+ );
1016
+ } finally {
1017
+ interaction?.close();
1018
+ }
1019
+ if (audience === "customer") assertCustomerResultSafe(data);
1020
+ if (audience === "admin" || sessionCommand || isInteractionExecution(data)) return data;
1021
+ return {
1022
+ action: null,
1023
+ data,
1024
+ errors: [],
1025
+ exitCode: CLI_EXIT_STATUS.success,
1026
+ outcome: { class: "ready", phase: "complete" },
1027
+ page: null,
1028
+ presentation: null,
1029
+ resume: null,
1030
+ };
1031
+ }
1032
+
1033
+ async function resolveAdminRole(io) {
1034
+ await cliRequest({
1035
+ endpoint: "/v1/staff/context",
1036
+ env: io.env,
1037
+ fetchImpl: io.fetchImpl,
1038
+ method: "HEAD",
1039
+ raw: true,
1040
+ });
1041
+ return "admin";
1042
+ }
1043
+
1044
+ export async function runCatalogCli(argv, io, { version }) {
1045
+ const capabilities = await loadCapabilities();
1046
+ const catalog = capabilities.catalog;
1047
+ const parsed = extractFormat(argv);
1048
+ const request = resolveRequest(
1049
+ catalog,
1050
+ parsed.remaining,
1051
+ capabilities.findLegacyCliMigration,
1052
+ capabilities.CLI_MIGRATION_GUIDE_URL
1053
+ );
1054
+
1055
+ const stdin = io.stdin ?? process.stdin;
1056
+ const stdout = io.stdout ?? process.stdout;
1057
+ const stderr = io.stderr ?? process.stderr;
1058
+ const format = parsed.format ?? "human";
1059
+ const machine = format === "json" || format === "ndjson";
1060
+ const eligibleHuman = !machine && stdin.isTTY === true && stdout.isTTY === true;
1061
+ if (parsed.error) {
1062
+ const modeled = modeledError(parsed.error);
1063
+ if (machine) {
1064
+ writeMachine(stdout, format, errorEnvelope(request.command, modeled));
1065
+ } else {
1066
+ stderr.write(`${modeled.error.message}\n`);
1067
+ }
1068
+ return {
1069
+ exitCode: modeled.exitCode,
1070
+ suppressUpdateNotification: true,
1071
+ };
1072
+ }
1073
+ const customerRawPrint =
1074
+ request.kind === "command" &&
1075
+ request.capability.id === "vpn.connections.configure" &&
1076
+ parsed.remaining.includes("--print");
1077
+ const rawPrint =
1078
+ (request.kind === "admin" && parsed.remaining.includes("--print")) || customerRawPrint;
1079
+ const unformattedUpdate = request.kind === "command" && request.capability.id === "cli.update";
1080
+ if (
1081
+ parsed.format === undefined &&
1082
+ !eligibleHuman &&
1083
+ !rawPrint &&
1084
+ !unformattedUpdate &&
1085
+ request.kind !== "error"
1086
+ ) {
1087
+ stderr.write(
1088
+ "Output format is required outside an interactive terminal. Choose --format human, --format json, or --format ndjson.\n"
1089
+ );
1090
+ return {
1091
+ exitCode: CLI_EXIT_STATUS.usage,
1092
+ suppressUpdateNotification: true,
1093
+ };
1094
+ }
1095
+
1096
+ const success = {
1097
+ exitCode: CLI_EXIT_STATUS.success,
1098
+ suppressUpdateNotification: machine,
1099
+ };
1100
+ let activeCommand = request.command;
1101
+ try {
1102
+ if (request.kind === "error") throw request.error;
1103
+ if (request.kind === "root") {
1104
+ const data = rootData(catalog);
1105
+ if (machine) writeMachine(stdout, format, resultEnvelope(request.command, data));
1106
+ else writeRootHuman(stdout, catalog);
1107
+ return success;
1108
+ }
1109
+ if (request.kind === "help") {
1110
+ const data = commandData(request.capability);
1111
+ if (machine) writeMachine(stdout, format, resultEnvelope(request.command, data));
1112
+ else writeHelpHuman(stdout, request.capability);
1113
+ return success;
1114
+ }
1115
+ if (request.kind === "help-group") {
1116
+ const data = groupData(request.path, request.capabilities);
1117
+ if (machine) writeMachine(stdout, format, resultEnvelope(request.command, data));
1118
+ else writeGroupHelpHuman(stdout, request.path, request.capabilities);
1119
+ return success;
1120
+ }
1121
+ if (request.kind === "command" && request.capability.id === "cli.update") {
1122
+ if (machine) {
1123
+ throw new VnextUsageError("ornn update supports human output only. Use --format human.", {
1124
+ field: "format",
1125
+ });
1126
+ }
1127
+ const input = parseCapabilityInput(
1128
+ request.capability,
1129
+ parseSemanticArguments(request.argv ?? [])
1130
+ );
1131
+ const exitCode = await updateCommand({
1132
+ allowMajor: input.major === true,
1133
+ binPath: io.binPath ?? process.argv[1],
1134
+ currentVersion: version,
1135
+ env: io.env,
1136
+ fetchImpl: io.fetchImpl,
1137
+ platform: io.platform,
1138
+ spawnProcess: io.spawnProcess,
1139
+ stderr,
1140
+ stdout,
1141
+ });
1142
+ return {
1143
+ exitCode,
1144
+ suppressUpdateNotification: true,
1145
+ };
1146
+ }
1147
+ if (request.kind === "admin") {
1148
+ const earlyCommand = adminCommandName(catalog, request.argv);
1149
+ if (earlyCommand) activeCommand = earlyCommand;
1150
+ preflightAdminCommand(catalog, request.argv, {
1151
+ eligibleHuman,
1152
+ machine,
1153
+ });
1154
+ const role = await resolveAdminRole(io);
1155
+ const adminRequest = resolveAdminRequest(catalog, capabilities.ADMIN_CLI_ROOTS, request.argv);
1156
+ activeCommand = adminRequest.command;
1157
+ if (adminRequest.kind === "root") {
1158
+ const data = adminRootData(catalog, capabilities.ADMIN_CLI_ROOTS, adminRequest.root);
1159
+ if (machine) writeMachine(stdout, format, resultEnvelope(adminRequest.command, data));
1160
+ else writeAdminRootHuman(stdout, data);
1161
+ return success;
1162
+ }
1163
+ if (adminRequest.kind === "help") {
1164
+ const data = adminCommandData(adminRequest.capability);
1165
+ if (machine) writeMachine(stdout, format, resultEnvelope(adminRequest.command, data));
1166
+ else writeAdminHelpHuman(stdout, adminRequest.capability);
1167
+ return success;
1168
+ }
1169
+ const execute = (input, { confirmed = false, transport } = {}) =>
1170
+ executeCapability(adminRequest.capability, catalog, io, version, {
1171
+ audience: "admin",
1172
+ confirmed,
1173
+ executionRuntime: transport ? { transport } : undefined,
1174
+ input,
1175
+ role,
1176
+ });
1177
+ const fetch = (path, init = {}) =>
1178
+ operatorRequest({
1179
+ endpoint: path,
1180
+ env: io.env,
1181
+ fetchImpl: io.fetchImpl,
1182
+ method: init.method ?? "GET",
1183
+ ...(init.body !== undefined ? { body: init.body } : {}),
1184
+ ...(init.headers !== undefined ? { headers: init.headers } : {}),
1185
+ ...(init.signal !== undefined ? { signal: init.signal } : {}),
1186
+ });
1187
+ const execution = adminRequest.handler
1188
+ ? await adminRequest.handler.run({
1189
+ capability: adminRequest.capability,
1190
+ execute,
1191
+ fetch,
1192
+ input: adminRequest.input,
1193
+ mutation: adminRequest.mutation,
1194
+ role,
1195
+ runtime: {
1196
+ eligibleHuman,
1197
+ env: io.env,
1198
+ formatProvided: parsed.format !== undefined,
1199
+ machine,
1200
+ now: io.now,
1201
+ signal: io.signal,
1202
+ sleep: io.sleep,
1203
+ stderr,
1204
+ stdin,
1205
+ stdout,
1206
+ },
1207
+ })
1208
+ : {
1209
+ data: await execute(adminRequest.input),
1210
+ exitCode: CLI_EXIT_STATUS.success,
1211
+ outcome: { class: "ready", phase: "complete" },
1212
+ };
1213
+ if (execution.raw || execution.suppressResult) {
1214
+ return {
1215
+ exitCode: execution.exitCode ?? CLI_EXIT_STATUS.success,
1216
+ suppressUpdateNotification: true,
1217
+ };
1218
+ }
1219
+ const result = finalizeAdminResult(
1220
+ adminRequest.capability,
1221
+ execution.data,
1222
+ execution.pagination ?? adminRequest.pagination
1223
+ );
1224
+ const envelope = {
1225
+ ...resultEnvelope(adminRequest.command, result.data, execution.outcome, result.page),
1226
+ action: execution.action ?? null,
1227
+ errors: execution.errors ?? [],
1228
+ resume: execution.resume ?? null,
1229
+ };
1230
+ if (machine) writeMachine(stdout, format, envelope);
1231
+ else writeAdminResultHuman(stdout, adminRequest.command, result);
1232
+ return {
1233
+ exitCode: execution.exitCode ?? CLI_EXIT_STATUS.success,
1234
+ suppressUpdateNotification:
1235
+ machine || (execution.exitCode ?? CLI_EXIT_STATUS.success) !== CLI_EXIT_STATUS.success,
1236
+ };
1237
+ }
1238
+
1239
+ const authSnapshot = {};
1240
+ const execution = await executeCapability(request.capability, catalog, io, version, {
1241
+ argv: request.argv ?? [],
1242
+ executionRuntime: {
1243
+ authSnapshot,
1244
+ eligibleHuman,
1245
+ env: io.env,
1246
+ fetchImpl: io.fetchImpl,
1247
+ machine,
1248
+ format,
1249
+ formatProvided: parsed.format !== undefined,
1250
+ interactionExtensions: {
1251
+ exportSecret: (options) => exportVpnSecret(options, { machine, stdout }),
1252
+ prepareSecretExport: prepareVpnSecretExport,
1253
+ wireGuardPublicKeyFromFile,
1254
+ hasLocalNetworkAddress,
1255
+ workspaceResume: {
1256
+ clear: (id) =>
1257
+ clearPendingWorkspace(id, {
1258
+ env: io.env,
1259
+ expectedAccessToken: authSnapshot.current?.accessToken,
1260
+ }),
1261
+ load: () => loadPendingWorkspace({ env: io.env, session: authSnapshot.current }),
1262
+ save: (value) =>
1263
+ savePendingWorkspace(value, {
1264
+ env: io.env,
1265
+ expectedAccessToken: authSnapshot.current?.accessToken,
1266
+ }),
1267
+ },
1268
+ launch: (state) =>
1269
+ dispatchLaunchWithHandlers(state, [
1270
+ bareMetalLaunchModeHandler,
1271
+ vmLaunchModeHandler,
1272
+ kubernetesLaunchModeHandler,
1273
+ slurmLaunchModeHandler,
1274
+ ...(io.launchModeHandlers ?? []),
1275
+ ]),
1276
+ sshKeys: (command, input, context) =>
1277
+ executeSshKeyCommand(command, input, context, context.cli.interaction),
1278
+ machines: (command, input, context) =>
1279
+ executeMachineCommand(command, input, context, context.cli.interaction),
1280
+ waitForGoal: (options) =>
1281
+ waitForGoal(options, {
1282
+ format,
1283
+ now: io.now,
1284
+ signal: io.signal,
1285
+ sleep: io.sleep,
1286
+ stderr,
1287
+ stdout,
1288
+ }),
1289
+ resolveConnectTarget: (state) =>
1290
+ resolveConnectTargetWithHandlers(
1291
+ state,
1292
+ io.connectModeHandlers ?? [
1293
+ createSlurmConnectModeHandler({ spawnProcess: io.spawnProcess }),
1294
+ createBareMetalConnectModeHandler({ spawnProcess: io.spawnProcess }),
1295
+ createVmConnectModeHandler({ spawnProcess: io.spawnProcess }),
1296
+ kubernetesConnectModeHandler,
1297
+ ]
1298
+ ),
1299
+ async continueConnect(state) {
1300
+ if (io.connectModeHandlers) {
1301
+ return continueConnectWithHandlers(state, io.connectModeHandlers);
1302
+ }
1303
+ const normalized = {
1304
+ ...state,
1305
+ context: state.context ?? {
1306
+ fetch: (path, init) => customerCapabilityFetch(io, path, init, authSnapshot),
1307
+ },
1308
+ input: state.input ?? this.options,
1309
+ interaction: state.interaction ?? this,
1310
+ };
1311
+ return continueConnectWithHandlers(normalized, [
1312
+ createSlurmConnectModeHandler({ spawnProcess: io.spawnProcess }),
1313
+ createBareMetalConnectModeHandler({ spawnProcess: io.spawnProcess }),
1314
+ createVmConnectModeHandler({ spawnProcess: io.spawnProcess }),
1315
+ kubernetesConnectModeHandler,
1316
+ ]);
1317
+ },
1318
+ },
1319
+ now: io.now,
1320
+ openBrowserImpl: io.openBrowser,
1321
+ sleep: io.sleep,
1322
+ signal: io.signal,
1323
+ stderr,
1324
+ stdin,
1325
+ stdout,
1326
+ },
1327
+ });
1328
+ if (customerRawPrint) {
1329
+ return {
1330
+ exitCode: execution.exitCode ?? CLI_EXIT_STATUS.success,
1331
+ suppressUpdateNotification: true,
1332
+ };
1333
+ }
1334
+ if (machine) writeExecutionMachine(stdout, format, request.command, execution);
1335
+ else writeCommandHuman(stdout, stderr, request.capability, execution);
1336
+ return {
1337
+ exitCode: execution.exitCode,
1338
+ suppressUpdateNotification: machine || execution.exitCode !== CLI_EXIT_STATUS.success,
1339
+ };
1340
+ } catch (error) {
1341
+ const modeled = modeledError(error);
1342
+ if (machine) {
1343
+ writeMachine(stdout, format, errorEnvelope(activeCommand, modeled));
1344
+ } else {
1345
+ stderr.write(`${modeled.error.message}\n`);
1346
+ if (modeled.action?.command) stderr.write(`Next: ${modeled.action.command}\n`);
1347
+ }
1348
+ return { exitCode: modeled.exitCode, suppressUpdateNotification: true };
1349
+ }
1350
+ }