@layr-labs/ecloud-cli 1.0.0-dev.6 → 1.0.0-dev.8

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 (82) hide show
  1. package/VERSION +2 -2
  2. package/dist/commands/admin/admins/add.js +7 -1
  3. package/dist/commands/admin/admins/add.js.map +1 -1
  4. package/dist/commands/admin/admins/list.js +7 -1
  5. package/dist/commands/admin/admins/list.js.map +1 -1
  6. package/dist/commands/admin/admins/remove.js +7 -1
  7. package/dist/commands/admin/admins/remove.js.map +1 -1
  8. package/dist/commands/admin/coupons/create.js +7 -1
  9. package/dist/commands/admin/coupons/create.js.map +1 -1
  10. package/dist/commands/admin/coupons/deactivate.js +7 -1
  11. package/dist/commands/admin/coupons/deactivate.js.map +1 -1
  12. package/dist/commands/admin/coupons/get.js +7 -1
  13. package/dist/commands/admin/coupons/get.js.map +1 -1
  14. package/dist/commands/admin/coupons/list.js +7 -1
  15. package/dist/commands/admin/coupons/list.js.map +1 -1
  16. package/dist/commands/admin/coupons/redeem.js +7 -1
  17. package/dist/commands/admin/coupons/redeem.js.map +1 -1
  18. package/dist/commands/auth/whoami.js +7 -1
  19. package/dist/commands/auth/whoami.js.map +1 -1
  20. package/dist/commands/billing/__tests__/status.test.js +79 -3
  21. package/dist/commands/billing/__tests__/status.test.js.map +1 -1
  22. package/dist/commands/billing/__tests__/subscribe.test.js +7 -1
  23. package/dist/commands/billing/__tests__/subscribe.test.js.map +1 -1
  24. package/dist/commands/billing/__tests__/top-up.test.js +473 -6
  25. package/dist/commands/billing/__tests__/top-up.test.js.map +1 -1
  26. package/dist/commands/billing/cancel.js +8 -3
  27. package/dist/commands/billing/cancel.js.map +1 -1
  28. package/dist/commands/billing/list-cards.js +7 -1
  29. package/dist/commands/billing/list-cards.js.map +1 -1
  30. package/dist/commands/billing/redeem-coupon.js +7 -1
  31. package/dist/commands/billing/redeem-coupon.js.map +1 -1
  32. package/dist/commands/billing/status.js +33 -1
  33. package/dist/commands/billing/status.js.map +1 -1
  34. package/dist/commands/billing/subscribe.js +7 -1
  35. package/dist/commands/billing/subscribe.js.map +1 -1
  36. package/dist/commands/billing/top-up.js +369 -7
  37. package/dist/commands/billing/top-up.js.map +1 -1
  38. package/dist/commands/compute/app/__tests__/status.test.js +1017 -0
  39. package/dist/commands/compute/app/__tests__/status.test.js.map +1 -0
  40. package/dist/commands/compute/app/create.js.map +1 -1
  41. package/dist/commands/compute/app/deploy.js +300 -68
  42. package/dist/commands/compute/app/deploy.js.map +1 -1
  43. package/dist/commands/compute/app/info.js +8 -2
  44. package/dist/commands/compute/app/info.js.map +1 -1
  45. package/dist/commands/compute/app/list.js +8 -2
  46. package/dist/commands/compute/app/list.js.map +1 -1
  47. package/dist/commands/compute/app/logs.js +11 -4
  48. package/dist/commands/compute/app/logs.js.map +1 -1
  49. package/dist/commands/compute/app/profile/set.js +11 -4
  50. package/dist/commands/compute/app/profile/set.js.map +1 -1
  51. package/dist/commands/compute/app/releases.js +8 -2
  52. package/dist/commands/compute/app/releases.js.map +1 -1
  53. package/dist/commands/compute/app/start.js +15 -8
  54. package/dist/commands/compute/app/start.js.map +1 -1
  55. package/dist/commands/compute/app/status.js +845 -0
  56. package/dist/commands/compute/app/status.js.map +1 -0
  57. package/dist/commands/compute/app/stop.js +15 -8
  58. package/dist/commands/compute/app/stop.js.map +1 -1
  59. package/dist/commands/compute/app/terminate.js +15 -8
  60. package/dist/commands/compute/app/terminate.js.map +1 -1
  61. package/dist/commands/compute/app/upgrade.js +297 -67
  62. package/dist/commands/compute/app/upgrade.js.map +1 -1
  63. package/dist/commands/compute/build/info.js +8 -2
  64. package/dist/commands/compute/build/info.js.map +1 -1
  65. package/dist/commands/compute/build/list.js +8 -2
  66. package/dist/commands/compute/build/list.js.map +1 -1
  67. package/dist/commands/compute/build/logs.js +8 -2
  68. package/dist/commands/compute/build/logs.js.map +1 -1
  69. package/dist/commands/compute/build/status.js +8 -2
  70. package/dist/commands/compute/build/status.js.map +1 -1
  71. package/dist/commands/compute/build/submit.js +8 -2
  72. package/dist/commands/compute/build/submit.js.map +1 -1
  73. package/dist/commands/compute/build/verify.js +8 -2
  74. package/dist/commands/compute/build/verify.js.map +1 -1
  75. package/dist/commands/compute/environment/set.js.map +1 -1
  76. package/dist/commands/compute/undelegate.js +11 -4
  77. package/dist/commands/compute/undelegate.js.map +1 -1
  78. package/dist/hooks/init/__tests__/version-check.test.js +32 -2
  79. package/dist/hooks/init/__tests__/version-check.test.js.map +1 -1
  80. package/dist/hooks/init/version-check.js +6 -1
  81. package/dist/hooks/init/version-check.js.map +1 -1
  82. package/package.json +2 -2
@@ -2,7 +2,12 @@
2
2
 
3
3
  // src/commands/compute/app/upgrade.ts
4
4
  import { Command, Args, Flags as Flags2 } from "@oclif/core";
5
- import { getEnvironmentConfig as getEnvironmentConfig4, UserApiClient as UserApiClient3, isMainnet } from "@layr-labs/ecloud-sdk";
5
+ import {
6
+ getEnvironmentConfig as getEnvironmentConfig4,
7
+ UserApiClient as UserApiClient4,
8
+ isMainnet,
9
+ WatchTimeoutError
10
+ } from "@layr-labs/ecloud-sdk";
6
11
 
7
12
  // src/telemetry.ts
8
13
  import {
@@ -323,7 +328,7 @@ function listApps(environment) {
323
328
 
324
329
  // src/utils/version.ts
325
330
  function getCliVersion() {
326
- return true ? "1.0.0-dev.6" : "0.0.0";
331
+ return true ? "1.0.0-dev.8" : "0.0.0";
327
332
  }
328
333
  function getClientId() {
329
334
  return `ecloud-cli/v${getCliVersion()}`;
@@ -360,7 +365,30 @@ function ensureInteractive(missingFlagHint) {
360
365
  );
361
366
  }
362
367
  }
363
- async function getDockerfileInteractive(dockerfilePath) {
368
+ function isNonInteractive(flags) {
369
+ if (flags?.["non-interactive"]) return true;
370
+ if (process.env.CI === "true") return true;
371
+ return !process.stdin.isTTY;
372
+ }
373
+ function warnDefaulted(flagHint, chosen) {
374
+ console.warn(`Warning: ${flagHint} not set in non-interactive mode; defaulting to ${chosen}.`);
375
+ }
376
+ function collectMissingRequiredInputs(state, identityFlag) {
377
+ const missing = [];
378
+ const hasImageSource = !!state.imageRef || !!state.dockerfile || !!state.verifiable && !!state.repo && !!state.commit;
379
+ if (!hasImageSource) {
380
+ missing.push(
381
+ "an image source (one of: --image-ref, --dockerfile, or --verifiable with --repo and --commit)"
382
+ );
383
+ } else if (state.dockerfile && !state.imageRef && !state.verifiable) {
384
+ missing.push("--image-ref (registry destination for the built image)");
385
+ }
386
+ if (identityFlag === "name" && !state.name) {
387
+ missing.push("--name");
388
+ }
389
+ return missing;
390
+ }
391
+ async function getDockerfile(dockerfilePath, nonInteractive = false) {
364
392
  if (dockerfilePath) {
365
393
  return dockerfilePath;
366
394
  }
@@ -369,6 +397,10 @@ async function getDockerfileInteractive(dockerfilePath) {
369
397
  if (!fs3.existsSync(dockerfilePath_resolved)) {
370
398
  return "";
371
399
  }
400
+ if (nonInteractive) {
401
+ warnDefaulted("--dockerfile/--image-ref", `build from '${dockerfilePath_resolved}'`);
402
+ return dockerfilePath_resolved;
403
+ }
372
404
  ensureInteractive("--dockerfile or --image-ref");
373
405
  console.log(`
374
406
  Found Dockerfile in ${cwd}`);
@@ -786,13 +818,17 @@ async function getImageReferenceInteractive(imageRef, buildFromDockerfile = fals
786
818
  });
787
819
  return imageRefInput;
788
820
  }
789
- async function getEnvFileInteractive(envFilePath) {
821
+ async function getEnvFile(envFilePath, nonInteractive = false) {
790
822
  if (envFilePath && fs3.existsSync(envFilePath)) {
791
823
  return envFilePath;
792
824
  }
793
825
  if (fs3.existsSync(".env")) {
794
826
  return ".env";
795
827
  }
828
+ if (nonInteractive) {
829
+ warnDefaulted("--env-file", "no env file");
830
+ return "";
831
+ }
796
832
  ensureInteractive("--env-file");
797
833
  console.log("\nEnvironment file not found.");
798
834
  console.log("Environment files contain variables like RPC_URL, etc.");
@@ -820,6 +856,7 @@ async function getEnvFileInteractive(envFilePath) {
820
856
  throw new Error(`Unexpected choice: ${choice}`);
821
857
  }
822
858
  }
859
+ var DEFAULT_NONINTERACTIVE_SKU = "g1-standard-2s";
823
860
  function formatSkuChoice(it) {
824
861
  if (it.vcpus != null && it.memory_mb != null && it.monthly_price_usd != null && it.hourly_price_usd != null) {
825
862
  const tier = it.friendly_name ?? it.sku;
@@ -836,7 +873,7 @@ function formatSkuChoice(it) {
836
873
  }
837
874
  return `${it.sku} - ${it.description}`;
838
875
  }
839
- async function getInstanceTypeInteractive(instanceType, defaultSKU, availableTypes) {
876
+ async function getInstanceType(instanceType, defaultSKU, availableTypes, nonInteractive = false) {
840
877
  if (instanceType) {
841
878
  const valid = availableTypes.find((t) => t.sku === instanceType);
842
879
  if (valid) {
@@ -845,6 +882,20 @@ async function getInstanceTypeInteractive(instanceType, defaultSKU, availableTyp
845
882
  const validSKUs = availableTypes.map((t) => t.sku).join(", ");
846
883
  throw new Error(`Invalid instance-type: ${instanceType} (must be one of: ${validSKUs})`);
847
884
  }
885
+ if (nonInteractive) {
886
+ if (defaultSKU) {
887
+ warnDefaulted("--instance-type", `current type '${defaultSKU}'`);
888
+ return defaultSKU;
889
+ }
890
+ if (availableTypes.some((t) => t.sku === DEFAULT_NONINTERACTIVE_SKU)) {
891
+ warnDefaulted("--instance-type", `'${DEFAULT_NONINTERACTIVE_SKU}'`);
892
+ return DEFAULT_NONINTERACTIVE_SKU;
893
+ }
894
+ const validSKUs = availableTypes.map((t) => t.sku).join(", ");
895
+ throw new Error(
896
+ `Cannot pick a default --instance-type in non-interactive mode: '${DEFAULT_NONINTERACTIVE_SKU}' not offered (available: ${validSKUs}). Provide --instance-type.`
897
+ );
898
+ }
848
899
  ensureInteractive("--instance-type");
849
900
  const isCurrentType = defaultSKU !== "";
850
901
  const hasPricing = availableTypes.some((t) => t.monthly_price_usd != null);
@@ -876,7 +927,7 @@ async function getInstanceTypeInteractive(instanceType, defaultSKU, availableTyp
876
927
  });
877
928
  return choice;
878
929
  }
879
- async function getLogSettingsInteractive(logVisibility) {
930
+ async function getLogSettings(logVisibility, nonInteractive = false) {
880
931
  if (logVisibility) {
881
932
  switch (logVisibility) {
882
933
  case "public":
@@ -891,6 +942,10 @@ async function getLogSettingsInteractive(logVisibility) {
891
942
  );
892
943
  }
893
944
  }
945
+ if (nonInteractive) {
946
+ warnDefaulted("--log-visibility", "'private'");
947
+ return { logRedirect: "always", publicLogs: false };
948
+ }
894
949
  ensureInteractive("--log-visibility");
895
950
  const choice = await select({
896
951
  message: "Do you want to view your app's logs?",
@@ -1211,7 +1266,7 @@ Select an app to ${action}:`);
1211
1266
  }
1212
1267
  return addHexPrefix2(selected);
1213
1268
  }
1214
- async function getResourceUsageMonitoringInteractive(resourceUsageMonitoring) {
1269
+ async function getResourceUsageMonitoring(resourceUsageMonitoring, nonInteractive = false) {
1215
1270
  if (resourceUsageMonitoring) {
1216
1271
  switch (resourceUsageMonitoring) {
1217
1272
  case "enable":
@@ -1223,6 +1278,10 @@ async function getResourceUsageMonitoringInteractive(resourceUsageMonitoring) {
1223
1278
  );
1224
1279
  }
1225
1280
  }
1281
+ if (nonInteractive) {
1282
+ warnDefaulted("--resource-usage-monitoring", "'disable'");
1283
+ return "disable";
1284
+ }
1226
1285
  ensureInteractive("--resource-usage-monitoring");
1227
1286
  const choice = await select({
1228
1287
  message: "Show resource usage (CPU/memory) for your app?",
@@ -1238,9 +1297,7 @@ async function confirm(prompt) {
1238
1297
  }
1239
1298
  async function confirmWithDefault(prompt, defaultValue = false) {
1240
1299
  if (!process.stdin.isTTY) {
1241
- throw new Error(
1242
- `Cannot confirm "${prompt}" in non-interactive mode. Use --force to skip confirmation prompts.`
1243
- );
1300
+ return defaultValue;
1244
1301
  }
1245
1302
  return await inquirerConfirm({
1246
1303
  message: prompt,
@@ -1323,7 +1380,7 @@ var commonFlags = {
1323
1380
  required: false,
1324
1381
  description: "Deployment environment to use",
1325
1382
  env: "ECLOUD_ENV",
1326
- default: async () => getDefaultEnvironment() || (getBuildType3() === "dev" ? "sepolia-dev" : "sepolia")
1383
+ default: async () => getDefaultEnvironment() || (getBuildType3() === "dev" ? "sepolia-dev" : "mainnet-alpha")
1327
1384
  }),
1328
1385
  "private-key": Flags.string({
1329
1386
  required: false,
@@ -1353,6 +1410,12 @@ var commonFlags = {
1353
1410
  nonce: Flags.string({
1354
1411
  required: false,
1355
1412
  description: 'Override transaction nonce (integer or "latest" to replace a stuck transaction)'
1413
+ }),
1414
+ "non-interactive": Flags.boolean({
1415
+ required: false,
1416
+ description: "Assume non-interactive mode: default safe prompts and error all-at-once on missing required inputs",
1417
+ env: "ECLOUD_NON_INTERACTIVE",
1418
+ default: false
1356
1419
  })
1357
1420
  };
1358
1421
  async function applyTxOverrides(estimate, flags, opts) {
@@ -1386,7 +1449,9 @@ async function applyTxOverrides(estimate, flags, opts) {
1386
1449
  } else {
1387
1450
  const parsed = Number(nonceStr);
1388
1451
  if (!Number.isInteger(parsed) || parsed < 0) {
1389
- throw new Error(`Invalid nonce: "${nonceStr}". Must be a non-negative integer or "latest".`);
1452
+ throw new Error(
1453
+ `Invalid nonce: "${nonceStr}". Must be a non-negative integer or "latest".`
1454
+ );
1390
1455
  }
1391
1456
  nonce = parsed;
1392
1457
  }
@@ -1410,7 +1475,7 @@ import {
1410
1475
  getEnvironmentConfig as getEnvironmentConfig3,
1411
1476
  requirePrivateKey
1412
1477
  } from "@layr-labs/ecloud-sdk";
1413
- async function createComputeClient(flags) {
1478
+ async function createComputeClient(flags, options = {}) {
1414
1479
  flags = await validateCommonFlags(flags);
1415
1480
  const environment = flags.environment;
1416
1481
  const environmentConfig = getEnvironmentConfig3(environment);
@@ -1432,8 +1497,9 @@ async function createComputeClient(flags) {
1432
1497
  publicClient,
1433
1498
  environment,
1434
1499
  clientId: getClientId(),
1435
- skipTelemetry: true
1500
+ skipTelemetry: true,
1436
1501
  // CLI already has telemetry, skip SDK telemetry
1502
+ logger: options.logger
1437
1503
  });
1438
1504
  }
1439
1505
  async function createBuildClient(flags) {
@@ -1459,6 +1525,29 @@ async function createBuildClient(flags) {
1459
1525
  });
1460
1526
  }
1461
1527
 
1528
+ // src/utils/instanceTypes.ts
1529
+ import { UserApiClient as UserApiClient3 } from "@layr-labs/ecloud-sdk";
1530
+ async function fetchAvailableInstanceTypes(environment, environmentConfig, privateKey, rpcUrl) {
1531
+ try {
1532
+ const { publicClient, walletClient } = createViemClients({
1533
+ privateKey,
1534
+ rpcUrl,
1535
+ environment
1536
+ });
1537
+ const userApiClient = new UserApiClient3(environmentConfig, walletClient, publicClient, {
1538
+ clientId: getClientId()
1539
+ });
1540
+ const skuList = await userApiClient.getSKUs();
1541
+ if (skuList.skus.length === 0) {
1542
+ throw new Error("No instance types available from server");
1543
+ }
1544
+ return skuList.skus;
1545
+ } catch (err) {
1546
+ console.warn(`Failed to fetch instance types: ${err.message}`);
1547
+ return [{ sku: "g1-standard-4t", description: "4 vCPUs, 16 GB memory, TDX" }];
1548
+ }
1549
+ }
1550
+
1462
1551
  // src/commands/compute/app/upgrade.ts
1463
1552
  import chalk2 from "chalk";
1464
1553
 
@@ -1620,8 +1709,63 @@ async function resolveDockerHubImageDigest(imageRef) {
1620
1709
  if (!/^sha256:[0-9a-f]{64}$/i.test(digest)) {
1621
1710
  throw new Error(`Unexpected digest format from Docker registry: ${digest}`);
1622
1711
  }
1712
+ await assertImageHasAmd64(owner, repo, tag, token, imageRef);
1623
1713
  return digest;
1624
1714
  }
1715
+ var AMD64_ACCEPT = [
1716
+ "application/vnd.oci.image.index.v1+json",
1717
+ "application/vnd.docker.distribution.manifest.list.v2+json",
1718
+ "application/vnd.oci.image.manifest.v1+json",
1719
+ "application/vnd.docker.distribution.manifest.v2+json"
1720
+ ].join(", ");
1721
+ async function assertImageHasAmd64(owner, repo, tag, token, imageRef) {
1722
+ const base = `https://registry-1.docker.io/v2/${owner}/${repo}`;
1723
+ const headers = { Authorization: `Bearer ${token}`, Accept: AMD64_ACCEPT };
1724
+ const res = await fetch(`${base}/manifests/${encodeURIComponent(tag)}`, { headers });
1725
+ if (!res.ok) {
1726
+ const body = await safeReadText(res);
1727
+ throw new Error(
1728
+ `Failed to read manifest for ${imageRef} (${res.status}): ${body || res.statusText}`
1729
+ );
1730
+ }
1731
+ const manifest = await res.json();
1732
+ const isAmd64 = (os5, arch) => os5 === "linux" && arch === "amd64";
1733
+ if (Array.isArray(manifest.manifests) && manifest.manifests.length > 0) {
1734
+ const platforms = manifest.manifests.map(
1735
+ (m) => m.platform ? `${m.platform.os}/${m.platform.architecture}` : "unknown"
1736
+ );
1737
+ if (manifest.manifests.some((m) => isAmd64(m.platform?.os, m.platform?.architecture))) {
1738
+ return;
1739
+ }
1740
+ throw amd64Error(imageRef, platforms);
1741
+ }
1742
+ const configDigest = manifest.config?.digest;
1743
+ if (!configDigest) {
1744
+ throw amd64Error(imageRef, ["unknown (no platform info in manifest)"]);
1745
+ }
1746
+ const cfgRes = await fetch(`${base}/blobs/${configDigest}`, {
1747
+ headers: { Authorization: `Bearer ${token}` }
1748
+ });
1749
+ if (!cfgRes.ok) {
1750
+ throw amd64Error(imageRef, ["unknown (could not read image config)"]);
1751
+ }
1752
+ const cfg = await cfgRes.json();
1753
+ if (isAmd64(cfg.os, cfg.architecture)) {
1754
+ return;
1755
+ }
1756
+ throw amd64Error(imageRef, [`${cfg.os ?? "unknown"}/${cfg.architecture ?? "unknown"}`]);
1757
+ }
1758
+ function amd64Error(imageRef, platforms) {
1759
+ return new Error(
1760
+ `ecloud requires linux/amd64 images for TEE deployment.
1761
+
1762
+ Image: ${imageRef}
1763
+ Found platform(s): ${platforms.join(", ")}
1764
+ Required platform: linux/amd64
1765
+
1766
+ To fix: rebuild for linux/amd64 (e.g. docker buildx build --platform linux/amd64 ... --push), or use a verifiable build (--verifiable --repo <repo> --commit <sha>), which builds server-side.`
1767
+ );
1768
+ }
1625
1769
 
1626
1770
  // src/utils/tls.ts
1627
1771
  import fs4 from "fs";
@@ -1686,12 +1830,45 @@ function mergeInlineEnvVars(envFilePath, inlineEnvVars) {
1686
1830
  return tmpFile;
1687
1831
  }
1688
1832
 
1833
+ // src/utils/exitCodes.ts
1834
+ import { InsufficientGasError } from "@layr-labs/ecloud-sdk";
1835
+ var EXIT_CODES = {
1836
+ INVALID_INPUT: 2,
1837
+ BUILD_FAILED: 3,
1838
+ ONCHAIN_FAILED: 4
1839
+ };
1840
+ function errorMessage(err) {
1841
+ if (err instanceof Error) return err.message;
1842
+ return String(err);
1843
+ }
1844
+ function stageFailure(operation, stage, err) {
1845
+ const noun = operation === "deploy" ? "deployment" : "upgrade";
1846
+ if (err instanceof InsufficientGasError) {
1847
+ stage = "onchain";
1848
+ }
1849
+ switch (stage) {
1850
+ case "invalid-input":
1851
+ return { message: errorMessage(err), exit: EXIT_CODES.INVALID_INPUT };
1852
+ case "build":
1853
+ return {
1854
+ message: `Build/push failed (no ${noun} was attempted): ${errorMessage(err)}`,
1855
+ exit: EXIT_CODES.BUILD_FAILED
1856
+ };
1857
+ case "onchain":
1858
+ return {
1859
+ message: `On-chain ${noun} failed after the image was built and pushed: ${errorMessage(err)}
1860
+ The image is already pushed \u2014 re-running ${operation} will reuse it.`,
1861
+ exit: EXIT_CODES.ONCHAIN_FAILED
1862
+ };
1863
+ }
1864
+ }
1865
+
1689
1866
  // src/commands/compute/app/upgrade.ts
1690
1867
  var AppUpgrade = class _AppUpgrade extends Command {
1691
1868
  static description = "Upgrade existing deployment";
1692
1869
  static args = {
1693
1870
  "app-id": Args.string({
1694
- description: "App ID or name to upgrade",
1871
+ description: "App ID or name to upgrade (env: ECLOUD_APP_ID)",
1695
1872
  required: false
1696
1873
  })
1697
1874
  };
@@ -1725,7 +1902,7 @@ var AppUpgrade = class _AppUpgrade extends Command {
1725
1902
  }),
1726
1903
  "log-visibility": Flags2.string({
1727
1904
  required: false,
1728
- description: "Log visibility setting: public, private, or off",
1905
+ description: "Log visibility setting: public, private, or off (non-interactive default: private)",
1729
1906
  options: ["public", "private", "off"],
1730
1907
  env: "ECLOUD_LOG_VISIBILITY"
1731
1908
  }),
@@ -1736,7 +1913,7 @@ var AppUpgrade = class _AppUpgrade extends Command {
1736
1913
  }),
1737
1914
  "resource-usage-monitoring": Flags2.string({
1738
1915
  required: false,
1739
- description: "Resource usage monitoring: enable or disable",
1916
+ description: "Resource usage monitoring: enable or disable (non-interactive default: disable)",
1740
1917
  options: ["enable", "disable"],
1741
1918
  env: "ECLOUD_RESOURCE_USAGE_MONITORING"
1742
1919
  }),
@@ -1774,19 +1951,50 @@ var AppUpgrade = class _AppUpgrade extends Command {
1774
1951
  }),
1775
1952
  force: Flags2.boolean({
1776
1953
  description: "Skip all confirmation prompts",
1777
- default: false
1954
+ default: false,
1955
+ env: "ECLOUD_FORCE"
1956
+ }),
1957
+ "watch-timeout": Flags2.integer({
1958
+ description: "Maximum seconds to wait for the upgrade to complete before returning a recovery hint (default: 600)",
1959
+ env: "ECLOUD_WATCH_TIMEOUT_SECONDS"
1778
1960
  })
1779
1961
  };
1780
1962
  async run() {
1781
1963
  return withTelemetry(this, async () => {
1782
1964
  const { args, flags } = await this.parse(_AppUpgrade);
1965
+ const appIdInput = args["app-id"] ?? process.env.ECLOUD_APP_ID;
1966
+ const nonInteractive = isNonInteractive(flags);
1967
+ if (nonInteractive) {
1968
+ const missing = collectMissingRequiredInputs(
1969
+ {
1970
+ imageRef: flags["image-ref"],
1971
+ dockerfile: flags.dockerfile,
1972
+ verifiable: flags.verifiable,
1973
+ repo: flags.repo,
1974
+ commit: flags.commit
1975
+ },
1976
+ "app-id"
1977
+ );
1978
+ if (!appIdInput) {
1979
+ missing.push("app-id (positional arg or ECLOUD_APP_ID)");
1980
+ }
1981
+ if (missing.length > 0) {
1982
+ const { message, exit } = stageFailure(
1983
+ "upgrade",
1984
+ "invalid-input",
1985
+ `Missing required input(s) for non-interactive upgrade:
1986
+ - ${missing.join("\n - ")}`
1987
+ );
1988
+ this.error(message, { exit });
1989
+ }
1990
+ }
1783
1991
  const compute = await createComputeClient(flags);
1784
1992
  const environment = flags.environment;
1785
1993
  const environmentConfig = getEnvironmentConfig4(environment);
1786
1994
  const rpcUrl = flags["rpc-url"] || environmentConfig.defaultRPCURL;
1787
1995
  const privateKey = flags["private-key"];
1788
1996
  const appID = await getOrPromptAppID({
1789
- appID: args["app-id"],
1997
+ appID: appIdInput,
1790
1998
  environment,
1791
1999
  privateKey,
1792
2000
  rpcUrl,
@@ -1847,7 +2055,7 @@ var AppUpgrade = class _AppUpgrade extends Command {
1847
2055
  buildContextPath: flags["build-context"],
1848
2056
  dependencies: flags["build-dependencies"]
1849
2057
  } : await promptVerifiableGitSourceInputs();
1850
- envFilePath = await getEnvFileInteractive(flags["env-file"]);
2058
+ envFilePath = await getEnvFile(flags["env-file"], nonInteractive);
1851
2059
  const includeTlsCaddyfile = isTlsEnabledFromEnvFile(envFilePath);
1852
2060
  if (includeTlsCaddyfile && !inputs.caddyfilePath) {
1853
2061
  inputs.caddyfilePath = "Caddyfile";
@@ -1907,10 +2115,11 @@ var AppUpgrade = class _AppUpgrade extends Command {
1907
2115
  }
1908
2116
  }
1909
2117
  const isVerifiable = verifiableMode !== "none";
1910
- const dockerfilePath = isVerifiable ? "" : await getDockerfileInteractive(flags.dockerfile);
2118
+ const deployExistingImageRef = !!flags["image-ref"] && !flags.dockerfile;
2119
+ const dockerfilePath = isVerifiable || deployExistingImageRef ? "" : await getDockerfile(flags.dockerfile, nonInteractive);
1911
2120
  const buildFromDockerfile = dockerfilePath !== "";
1912
2121
  const imageRef = verifiableImageUrl ? verifiableImageUrl : await getImageReferenceInteractive(flags["image-ref"], buildFromDockerfile);
1913
- envFilePath = envFilePath ?? await getEnvFileInteractive(flags["env-file"]);
2122
+ envFilePath = envFilePath ?? await getEnvFile(flags["env-file"], nonInteractive);
1914
2123
  if (flags.env && flags.env.length > 0) {
1915
2124
  envFilePath = mergeInlineEnvVars(envFilePath, flags.env);
1916
2125
  }
@@ -1921,7 +2130,7 @@ var AppUpgrade = class _AppUpgrade extends Command {
1921
2130
  });
1922
2131
  let currentInstanceType = "";
1923
2132
  try {
1924
- const userApiClient = new UserApiClient3(environmentConfig, walletClient, publicClient, {
2133
+ const userApiClient = new UserApiClient4(environmentConfig, walletClient, publicClient, {
1925
2134
  clientId: getClientId()
1926
2135
  });
1927
2136
  const infos = await userApiClient.getInfos([appID], 1);
@@ -1936,33 +2145,43 @@ var AppUpgrade = class _AppUpgrade extends Command {
1936
2145
  privateKey,
1937
2146
  rpcUrl
1938
2147
  );
1939
- const instanceType = await getInstanceTypeInteractive(
2148
+ const instanceType = await getInstanceType(
1940
2149
  flags["instance-type"],
1941
2150
  currentInstanceType,
1942
- availableTypes
2151
+ availableTypes,
2152
+ nonInteractive
1943
2153
  );
1944
- const logSettings = await getLogSettingsInteractive(
1945
- flags["log-visibility"]
2154
+ const logSettings = await getLogSettings(
2155
+ flags["log-visibility"],
2156
+ nonInteractive
1946
2157
  );
1947
- const resourceUsageMonitoring = await getResourceUsageMonitoringInteractive(
1948
- flags["resource-usage-monitoring"]
2158
+ const resourceUsageMonitoring = await getResourceUsageMonitoring(
2159
+ flags["resource-usage-monitoring"],
2160
+ nonInteractive
1949
2161
  );
1950
2162
  const logVisibility = logSettings.publicLogs ? "public" : logSettings.logRedirect ? "private" : "off";
1951
- const { prepared, gasEstimate } = verifiableMode === "git" ? await compute.app.prepareUpgradeFromVerifiableBuild(appID, {
1952
- imageRef,
1953
- imageDigest: verifiableImageDigest,
1954
- envFile: envFilePath,
1955
- instanceType,
1956
- logVisibility,
1957
- resourceUsageMonitoring
1958
- }) : await compute.app.prepareUpgrade(appID, {
1959
- dockerfile: dockerfilePath,
1960
- imageRef,
1961
- envFile: envFilePath,
1962
- instanceType,
1963
- logVisibility,
1964
- resourceUsageMonitoring
1965
- });
2163
+ let prepared;
2164
+ let gasEstimate;
2165
+ try {
2166
+ ({ prepared, gasEstimate } = verifiableMode === "git" ? await compute.app.prepareUpgradeFromVerifiableBuild(appID, {
2167
+ imageRef,
2168
+ imageDigest: verifiableImageDigest,
2169
+ envFile: envFilePath,
2170
+ instanceType,
2171
+ logVisibility,
2172
+ resourceUsageMonitoring
2173
+ }) : await compute.app.prepareUpgrade(appID, {
2174
+ dockerfile: dockerfilePath,
2175
+ imageRef,
2176
+ envFile: envFilePath,
2177
+ instanceType,
2178
+ logVisibility,
2179
+ resourceUsageMonitoring
2180
+ }));
2181
+ } catch (err) {
2182
+ const { message, exit } = stageFailure("upgrade", "build", err);
2183
+ this.error(message, { exit });
2184
+ }
1966
2185
  const finalTx = await applyTxOverrides(gasEstimate, flags, { publicClient, address });
1967
2186
  if (flags["max-fee-per-gas"] || flags["max-priority-fee"]) {
1968
2187
  this.log(
@@ -1985,8 +2204,39 @@ ${chalk2.gray(`Upgrade cancelled`)}`);
1985
2204
  return;
1986
2205
  }
1987
2206
  }
1988
- const res = await compute.app.executeUpgrade(prepared, finalTx);
1989
- await compute.app.watchUpgrade(res.appId);
2207
+ let res;
2208
+ try {
2209
+ res = await compute.app.executeUpgrade(prepared, finalTx);
2210
+ } catch (err) {
2211
+ const { message, exit } = stageFailure("upgrade", "onchain", err);
2212
+ this.error(message, { exit });
2213
+ }
2214
+ try {
2215
+ await compute.app.watchUpgrade(res.appId, { timeoutSeconds: flags["watch-timeout"] });
2216
+ } catch (err) {
2217
+ if (err instanceof WatchTimeoutError) {
2218
+ this.log("");
2219
+ this.log(
2220
+ chalk2.yellow(
2221
+ `Timed out after ${err.elapsedSeconds}s waiting for upgrade to complete (last status: ${err.lastStatus ?? "unknown"}).`
2222
+ )
2223
+ );
2224
+ this.log(chalk2.gray("The on-chain transaction was submitted; the orchestrator may"));
2225
+ this.log(chalk2.gray("still be processing. To check the current status, run:"));
2226
+ this.log("");
2227
+ this.log(` ${chalk2.cyan(`ecloud compute app info ${res.appId}`)}`);
2228
+ this.log("");
2229
+ this.log(chalk2.gray(`appId: ${res.appId}`));
2230
+ this.log(chalk2.gray(`txHash: ${res.txHash}`));
2231
+ this.log(
2232
+ chalk2.gray(
2233
+ `(override the watch deadline with ECLOUD_WATCH_TIMEOUT_SECONDS, currently ${err.timeoutSeconds}s)`
2234
+ )
2235
+ );
2236
+ this.exit(1);
2237
+ }
2238
+ throw err;
2239
+ }
1990
2240
  try {
1991
2241
  const cwd = process.env.INIT_CWD || process.cwd();
1992
2242
  setLinkedAppForDirectory(environment, cwd, res.appId);
@@ -2004,7 +2254,7 @@ ${chalk2.gray(`Upgrade cancelled`)}`);
2004
2254
  rpcUrl,
2005
2255
  environment
2006
2256
  });
2007
- const userApiClient = new UserApiClient3(environmentConfig, walletClient2, publicClient2, {
2257
+ const userApiClient = new UserApiClient4(environmentConfig, walletClient2, publicClient2, {
2008
2258
  clientId: getClientId()
2009
2259
  });
2010
2260
  const infos = await userApiClient.getInfos([res.appId], 1);
@@ -2033,26 +2283,6 @@ Note: "Running" means the container started. Verify your app is serving traffic
2033
2283
  });
2034
2284
  }
2035
2285
  };
2036
- async function fetchAvailableInstanceTypes(environment, environmentConfig, privateKey, rpcUrl) {
2037
- try {
2038
- const { publicClient, walletClient } = createViemClients({
2039
- privateKey,
2040
- rpcUrl,
2041
- environment
2042
- });
2043
- const userApiClient = new UserApiClient3(environmentConfig, walletClient, publicClient, {
2044
- clientId: getClientId()
2045
- });
2046
- const skuList = await userApiClient.getSKUs();
2047
- if (skuList.skus.length === 0) {
2048
- throw new Error("No instance types available from server");
2049
- }
2050
- return skuList.skus;
2051
- } catch (err) {
2052
- console.warn(`Failed to fetch instance types: ${err.message}`);
2053
- return [{ sku: "g1-standard-4t", description: "4 vCPUs, 16 GB memory, TDX" }];
2054
- }
2055
- }
2056
2286
  export {
2057
2287
  AppUpgrade as default
2058
2288
  };