@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
package/src/update.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { existsSync, realpathSync } from "node:fs";
3
- import { readFile } from "node:fs/promises";
3
+ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
4
4
  import { homedir } from "node:os";
5
5
  import { dirname, isAbsolute, join, posix, win32 } from "node:path";
6
6
  import { CliApiError } from "./api-client.mjs";
@@ -8,6 +8,9 @@ import { getAuthConfigDir } from "./auth-store.mjs";
8
8
  import { forwardSignals } from "./signals.mjs";
9
9
  import { acquireFileLock } from "./update-lock.mjs";
10
10
 
11
+ export const UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1000;
12
+ const UPDATE_CACHE_FILE = "update-check.json";
13
+ export const CLI_MIGRATION_GUIDE_URL = "https://docs.ornn.com/cli#migrating-to-vnext";
11
14
  export const UPDATE_CHECK_TIMEOUT_MS = 3000;
12
15
  const UPDATE_LOCK_PREFIX = "update-install";
13
16
  const PACKAGE_NAME = "@ornncompute/cli";
@@ -104,7 +107,9 @@ export function detectInstallContext(binPath, env = process.env) {
104
107
 
105
108
  if (
106
109
  has("_npx") ||
107
- segments.some((segment) => segment === "dlx" || segment.startsWith("dlx-") || segment.startsWith("xfs-")) ||
110
+ segments.some(
111
+ (segment) => segment === "dlx" || segment.startsWith("dlx-") || segment.startsWith("xfs-")
112
+ ) ||
108
113
  lower.includes("/.bun/install/cache/") ||
109
114
  lower.includes("/.yarn/berry/cache/")
110
115
  ) {
@@ -159,7 +164,9 @@ function isUnderDir(lowerPath, dir) {
159
164
  if (!trimmed) {
160
165
  return false;
161
166
  }
162
- return lowerPath.startsWith(`${trimmed.replaceAll("\\", "/").toLowerCase().replace(/\/+$/, "")}/`);
167
+ return lowerPath.startsWith(
168
+ `${trimmed.replaceAll("\\", "/").toLowerCase().replace(/\/+$/, "")}/`
169
+ );
163
170
  }
164
171
 
165
172
  function isBunGlobalInstall(lowerPath, env) {
@@ -170,11 +177,16 @@ function isBunGlobalInstall(lowerPath, env) {
170
177
  }
171
178
 
172
179
  function isPnpmGlobalInstall(lowerPath, env) {
173
- return /\/pnpm\/global\/\d+\/node_modules\//.test(lowerPath) || isUnderDir(lowerPath, env.PNPM_HOME);
180
+ return (
181
+ /\/pnpm\/global\/\d+\/node_modules\//.test(lowerPath) || isUnderDir(lowerPath, env.PNPM_HOME)
182
+ );
174
183
  }
175
184
 
176
185
  function isYarnGlobalInstall(lowerPath) {
177
- return lowerPath.includes("/yarn/global/node_modules/") || lowerPath.includes("/yarn/data/global/node_modules/");
186
+ return (
187
+ lowerPath.includes("/yarn/global/node_modules/") ||
188
+ lowerPath.includes("/yarn/data/global/node_modules/")
189
+ );
178
190
  }
179
191
 
180
192
  function isNpmGlobalInstall(lowerPath, env) {
@@ -185,7 +197,10 @@ function isNpmGlobalInstall(lowerPath, env) {
185
197
  resolvedPrefix = realpathSync(configuredPrefix);
186
198
  } catch {}
187
199
  const prefix = resolvedPrefix.replaceAll("\\", "/").toLowerCase().replace(/\/+$/, "");
188
- if (lowerPath.startsWith(`${prefix}/node_modules/`) || lowerPath.startsWith(`${prefix}/lib/node_modules/`)) {
200
+ if (
201
+ lowerPath.startsWith(`${prefix}/node_modules/`) ||
202
+ lowerPath.startsWith(`${prefix}/lib/node_modules/`)
203
+ ) {
189
204
  return true;
190
205
  }
191
206
  }
@@ -289,8 +304,8 @@ export async function installVersion({
289
304
  new CliApiError(
290
305
  error?.code === "ENOENT"
291
306
  ? `${command} was not found on PATH. Install it or run: npm install -g ${PACKAGE_NAME}@latest`
292
- : `Failed to run ${command}: ${error?.message ?? error}`,
293
- ),
307
+ : `Failed to run ${command}: ${error?.message ?? error}`
308
+ )
294
309
  );
295
310
  });
296
311
  child.on("exit", (code, signal) => {
@@ -358,7 +373,9 @@ export async function resolveInstalledBin({
358
373
  let configuredRoot = output;
359
374
  if (context.kind === "bun") {
360
375
  // Bun lists the effective global directory before its dependency tree.
361
- configuredRoot = /^(.+) node_modules \(\d+(?: installed)?\)$/.exec(output?.split(/\r?\n/)[0] ?? "")?.[1];
376
+ configuredRoot = /^(.+) node_modules \(\d+(?: installed)?\)$/.exec(
377
+ output?.split(/\r?\n/)[0] ?? ""
378
+ )?.[1];
362
379
  }
363
380
  if (!configuredRoot || !paths.isAbsolute(configuredRoot)) {
364
381
  return null;
@@ -405,6 +422,7 @@ export async function fetchLatestVersion({
405
422
 
406
423
  export async function updateCommand({
407
424
  binPath,
425
+ allowMajor = false,
408
426
  currentVersion,
409
427
  env = process.env,
410
428
  fetchImpl = fetch,
@@ -415,19 +433,21 @@ export async function updateCommand({
415
433
  }) {
416
434
  const context = detectInstallContext(binPath, env);
417
435
  if (context.kind === "ephemeral") {
418
- throw new CliApiError("ornn is running via a package runner (npx/dlx); there is no installed copy to update.");
436
+ throw new CliApiError(
437
+ "ornn is running via a package runner (npx/dlx); there is no installed copy to update."
438
+ );
419
439
  }
420
440
  if (context.kind === "dev") {
421
441
  throw new CliApiError("ornn is running from a development checkout; update it with git pull.");
422
442
  }
423
443
  if (context.kind === "project") {
424
444
  throw new CliApiError(
425
- "ornn is installed as a project dependency; update it there with: npm update @ornncompute/cli",
445
+ "ornn is installed as a project dependency; update it there with: npm update @ornncompute/cli"
426
446
  );
427
447
  }
428
448
  if (context.kind === "unknown") {
429
449
  throw new CliApiError(
430
- "Could not determine how ornn was installed. Reinstall with: npm install -g @ornncompute/cli@latest",
450
+ "Could not determine how ornn was installed. Reinstall with: npm install -g @ornncompute/cli@latest"
431
451
  );
432
452
  }
433
453
 
@@ -439,7 +459,7 @@ export async function updateCommand({
439
459
  lockPath
440
460
  ? ` If no updater or package installer is running, remove the stale lock at ${lockPath} and retry.`
441
461
  : " Check the config directory permissions and retry."
442
- }`,
462
+ }`
443
463
  );
444
464
  }
445
465
 
@@ -447,7 +467,7 @@ export async function updateCommand({
447
467
  const installedBin = await resolveInstalledBin({ context, spawnProcess, env, platform });
448
468
  if (!installedBin) {
449
469
  throw new CliApiError(
450
- `Could not verify the ${context.kind} global installation root. No files were changed; reinstall with: npm install -g ${PACKAGE_NAME}@latest`,
470
+ `Could not verify the ${context.kind} global installation root. No files were changed; reinstall with: npm install -g ${PACKAGE_NAME}@latest`
451
471
  );
452
472
  }
453
473
 
@@ -456,14 +476,29 @@ export async function updateCommand({
456
476
  stdout.write(`ornn CLI is already up to date (${currentVersion}).\n`);
457
477
  return 0;
458
478
  }
479
+ const majorUpgrade = latest ? isMajorUpgrade(latest, currentVersion) : false;
480
+ if (majorUpgrade && !allowMajor) {
481
+ throw new CliApiError(
482
+ `ornn CLI ${latest} is a major update and was not installed. Review ${CLI_MIGRATION_GUIDE_URL}, then run \`ornn update --major\`.`
483
+ );
484
+ }
485
+ if (!latest && !allowMajor) {
486
+ throw new CliApiError(
487
+ `Could not determine the latest version, so no update was installed: \`@latest\` could cross a major version. Retry the registry check, or review ${CLI_MIGRATION_GUIDE_URL} and run \`ornn update --major\`.`
488
+ );
489
+ }
459
490
  if (!latest) {
460
- stderr.write("Could not check the npm registry for the latest version; attempting the update anyway.\n");
491
+ stderr.write(
492
+ "Could not check the npm registry for the latest version; explicit --major allows an @latest update.\n"
493
+ );
461
494
  }
462
495
 
463
496
  const version = latest ?? "latest";
464
497
  const [command, commandArgs] = installInvocation(context, version);
465
498
  stderr.write(`Running: ${command} ${commandArgs.join(" ")}\n`);
466
- const result = await lock.runInstall(() => installVersion({ context, version, spawnProcess, platform, env }));
499
+ const result = await lock.runInstall(() =>
500
+ installVersion({ context, version, spawnProcess, platform, env })
501
+ );
467
502
  if (result.exitCode !== 0) {
468
503
  throw new CliApiError(
469
504
  updateFailureMessage({
@@ -473,12 +508,14 @@ export async function updateCommand({
473
508
  exitCode: result.exitCode,
474
509
  kind: context.kind,
475
510
  platform,
476
- }),
511
+ })
477
512
  );
478
513
  }
479
514
 
480
515
  if (platform === "win32") {
481
- stderr.write("If this terminal reports an error after updating, it is safe to ignore; open a new terminal.\n");
516
+ stderr.write(
517
+ "If this terminal reports an error after updating, it is safe to ignore; open a new terminal.\n"
518
+ );
482
519
  }
483
520
 
484
521
  const installedVersion = await readInstalledVersion(installedBin);
@@ -486,14 +523,16 @@ export async function updateCommand({
486
523
  stdout.write("Update finished. Run `ornn --version` to confirm the installed version.\n");
487
524
  } else if (installedVersion === currentVersion && latest) {
488
525
  stderr.write(
489
- `The installer exited successfully, but this ornn still runs ${currentVersion} from ${context.binPath}. Your registry mirror may be behind, or another install shadows this one; check your PATH.\n`,
526
+ `The installer exited successfully, but this ornn still runs ${currentVersion} from ${context.binPath}. Your registry mirror may be behind, or another install shadows this one; check your PATH.\n`
490
527
  );
491
528
  return 1;
492
529
  } else if (installedVersion === currentVersion) {
493
- stdout.write(`ornn CLI is still ${currentVersion}; the registry may not have a newer version.\n`);
530
+ stdout.write(
531
+ `ornn CLI is still ${currentVersion}; the registry may not have a newer version.\n`
532
+ );
494
533
  } else if (compareVersions(installedVersion, currentVersion) < 0) {
495
534
  stderr.write(
496
- `The installer replaced ornn CLI ${currentVersion} with the older ${installedVersion}; your registry mirror may be behind. Reinstall with: npm install -g ${PACKAGE_NAME}@${currentVersion}\n`,
535
+ `The installer replaced ornn CLI ${currentVersion} with the older ${installedVersion}; your registry mirror may be behind. Reinstall with: npm install -g ${PACKAGE_NAME}@${currentVersion}\n`
497
536
  );
498
537
  return 1;
499
538
  } else {
@@ -517,7 +556,9 @@ function updateFailureMessage({ binPath, command, commandArgs, exitCode, kind, p
517
556
  lines.push(`If the output above shows a permissions error, try: sudo ${manualCommand}`);
518
557
  }
519
558
  if (kind === "yarn") {
520
- lines.push("If yarn global is unavailable (yarn 2+), use: npm install -g @ornncompute/cli@latest");
559
+ lines.push(
560
+ "If yarn global is unavailable (yarn 2+), use: npm install -g @ornncompute/cli@latest"
561
+ );
521
562
  }
522
563
  return lines.join("\n");
523
564
  }
@@ -528,9 +569,116 @@ async function readInstalledVersion(binPath) {
528
569
  try {
529
570
  resolved = realpathSync(binPath);
530
571
  } catch {}
531
- const parsed = JSON.parse(await readFile(join(dirname(resolved), "..", "package.json"), "utf8"));
532
- return parsed?.name === PACKAGE_NAME && typeof parsed.version === "string" ? parsed.version : null;
572
+ const parsed = JSON.parse(
573
+ await readFile(join(dirname(resolved), "..", "package.json"), "utf8")
574
+ );
575
+ return parsed?.name === PACKAGE_NAME && typeof parsed.version === "string"
576
+ ? parsed.version
577
+ : null;
533
578
  } catch {
534
579
  return null;
535
580
  }
536
581
  }
582
+
583
+ function isMajorUpgrade(candidate, current) {
584
+ const candidateVersion = parseVersion(candidate);
585
+ const currentVersion = parseVersion(current);
586
+ return Boolean(
587
+ candidateVersion && currentVersion && candidateVersion.nums[0] > currentVersion.nums[0]
588
+ );
589
+ }
590
+
591
+ function updateCachePath(env) {
592
+ return join(getAuthConfigDir(env), UPDATE_CACHE_FILE);
593
+ }
594
+
595
+ async function readUpdateCache(path) {
596
+ try {
597
+ const parsed = JSON.parse(await readFile(path, "utf8"));
598
+ return parsed && typeof parsed === "object" ? parsed : null;
599
+ } catch {
600
+ return null;
601
+ }
602
+ }
603
+
604
+ async function writeUpdateCache(path, payload) {
605
+ // Atomic tmp+rename: survives concurrent runs and replaces a root-owned file left by a past sudo run.
606
+ const tmpPath = `${path}.${process.pid}.tmp`;
607
+ try {
608
+ await mkdir(dirname(path), { mode: 0o700, recursive: true });
609
+ await writeFile(tmpPath, `${JSON.stringify(payload)}\n`, { encoding: "utf8", mode: 0o600 });
610
+ await rename(tmpPath, path);
611
+ return true;
612
+ } catch {
613
+ await rm(tmpPath, { force: true }).catch(() => {});
614
+ return false;
615
+ }
616
+ }
617
+
618
+ export async function maybeNotifyUpdate({
619
+ binPath,
620
+ currentVersion,
621
+ env = process.env,
622
+ fetchImpl = fetch,
623
+ now = Date.now(),
624
+ stderr,
625
+ interactive = stderr?.isTTY === true,
626
+ }) {
627
+ try {
628
+ if (env.CI && env.CI !== "false") {
629
+ return;
630
+ }
631
+ if (!interactive || !stderr?.isTTY) {
632
+ return;
633
+ }
634
+ if (!INSTALLED_KINDS.has(detectInstallContext(binPath, env).kind)) {
635
+ return;
636
+ }
637
+ const cachePath = updateCachePath(env);
638
+ if (!isAbsolute(cachePath)) {
639
+ return;
640
+ }
641
+
642
+ const cache = await readUpdateCache(cachePath);
643
+ let latestKnown =
644
+ typeof cache?.latestVersion === "string" && VERSION_PATTERN.test(cache.latestVersion)
645
+ ? cache.latestVersion
646
+ : null;
647
+ const checkedAt = Date.parse(cache?.lastCheckedAt ?? "");
648
+ const fresh =
649
+ Number.isFinite(checkedAt) && now - checkedAt >= 0 && now - checkedAt < UPDATE_CHECK_TTL_MS;
650
+
651
+ if (!fresh) {
652
+ // Reserve the TTL before fetching so an unwritable cache disables the check instead of taxing every command.
653
+ const reserved = await writeUpdateCache(cachePath, {
654
+ lastCheckedAt: new Date(now).toISOString(),
655
+ latestVersion: latestKnown,
656
+ });
657
+ if (!reserved) {
658
+ return;
659
+ }
660
+ const fetched = await fetchLatestVersion({ env, fetchImpl });
661
+ if (fetched) {
662
+ latestKnown = fetched;
663
+ await writeUpdateCache(cachePath, {
664
+ lastCheckedAt: new Date(now).toISOString(),
665
+ latestVersion: fetched,
666
+ });
667
+ }
668
+ }
669
+
670
+ if (latestKnown && compareVersions(latestKnown, currentVersion) > 0) {
671
+ if (isMajorUpgrade(latestKnown, currentVersion)) {
672
+ stderr.write(
673
+ `Major update available: ornn CLI ${currentVersion} -> ${latestKnown}. \`ornn update\` will not install it. Review ${CLI_MIGRATION_GUIDE_URL}, then run \`ornn update --major\`.\n`
674
+ );
675
+ } else {
676
+ stderr.write(
677
+ `Update available: ornn CLI ${currentVersion} -> ${latestKnown}. Run \`ornn update\`.\n`
678
+ );
679
+ }
680
+ }
681
+ } catch {
682
+ // Never break or slow the command the notifier rides on.
683
+ }
684
+ }