@layr-labs/ecloud-cli 1.0.0-devep7 → 1.0.0

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 (84) hide show
  1. package/VERSION +2 -2
  2. package/dist/commands/admin/admins/add.js +411 -0
  3. package/dist/commands/admin/admins/add.js.map +1 -0
  4. package/dist/commands/admin/admins/list.js +414 -0
  5. package/dist/commands/admin/admins/list.js.map +1 -0
  6. package/dist/commands/admin/admins/remove.js +409 -0
  7. package/dist/commands/admin/admins/remove.js.map +1 -0
  8. package/dist/commands/admin/coupons/create.js +424 -0
  9. package/dist/commands/admin/coupons/create.js.map +1 -0
  10. package/dist/commands/admin/coupons/deactivate.js +409 -0
  11. package/dist/commands/admin/coupons/deactivate.js.map +1 -0
  12. package/dist/commands/admin/coupons/get.js +418 -0
  13. package/dist/commands/admin/coupons/get.js.map +1 -0
  14. package/dist/commands/admin/coupons/list.js +442 -0
  15. package/dist/commands/admin/coupons/list.js.map +1 -0
  16. package/dist/commands/admin/coupons/redeem.js +413 -0
  17. package/dist/commands/admin/coupons/redeem.js.map +1 -0
  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 +82 -4
  21. package/dist/commands/billing/__tests__/status.test.js.map +1 -1
  22. package/dist/commands/billing/__tests__/subscribe.test.js +10 -2
  23. package/dist/commands/billing/__tests__/subscribe.test.js.map +1 -1
  24. package/dist/commands/billing/__tests__/top-up.test.js +565 -13
  25. package/dist/commands/billing/__tests__/top-up.test.js.map +1 -1
  26. package/dist/commands/billing/cancel.js +11 -4
  27. package/dist/commands/billing/cancel.js.map +1 -1
  28. package/dist/commands/billing/list-cards.js +10 -2
  29. package/dist/commands/billing/list-cards.js.map +1 -1
  30. package/dist/commands/billing/redeem-coupon.js +421 -0
  31. package/dist/commands/billing/redeem-coupon.js.map +1 -0
  32. package/dist/commands/billing/status.js +36 -2
  33. package/dist/commands/billing/status.js.map +1 -1
  34. package/dist/commands/billing/subscribe.js +10 -2
  35. package/dist/commands/billing/subscribe.js.map +1 -1
  36. package/dist/commands/billing/top-up.js +392 -11
  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/configure/tls.js +70 -30
  41. package/dist/commands/compute/app/configure/tls.js.map +1 -1
  42. package/dist/commands/compute/app/create.js.map +1 -1
  43. package/dist/commands/compute/app/deploy.js +301 -68
  44. package/dist/commands/compute/app/deploy.js.map +1 -1
  45. package/dist/commands/compute/app/info.js +8 -2
  46. package/dist/commands/compute/app/info.js.map +1 -1
  47. package/dist/commands/compute/app/list.js +8 -2
  48. package/dist/commands/compute/app/list.js.map +1 -1
  49. package/dist/commands/compute/app/logs.js +12 -4
  50. package/dist/commands/compute/app/logs.js.map +1 -1
  51. package/dist/commands/compute/app/profile/set.js +12 -4
  52. package/dist/commands/compute/app/profile/set.js.map +1 -1
  53. package/dist/commands/compute/app/releases.js +8 -2
  54. package/dist/commands/compute/app/releases.js.map +1 -1
  55. package/dist/commands/compute/app/start.js +16 -8
  56. package/dist/commands/compute/app/start.js.map +1 -1
  57. package/dist/commands/compute/app/status.js +845 -0
  58. package/dist/commands/compute/app/status.js.map +1 -0
  59. package/dist/commands/compute/app/stop.js +16 -8
  60. package/dist/commands/compute/app/stop.js.map +1 -1
  61. package/dist/commands/compute/app/terminate.js +16 -8
  62. package/dist/commands/compute/app/terminate.js.map +1 -1
  63. package/dist/commands/compute/app/upgrade.js +298 -67
  64. package/dist/commands/compute/app/upgrade.js.map +1 -1
  65. package/dist/commands/compute/build/info.js +9 -2
  66. package/dist/commands/compute/build/info.js.map +1 -1
  67. package/dist/commands/compute/build/list.js +9 -2
  68. package/dist/commands/compute/build/list.js.map +1 -1
  69. package/dist/commands/compute/build/logs.js +9 -2
  70. package/dist/commands/compute/build/logs.js.map +1 -1
  71. package/dist/commands/compute/build/status.js +9 -2
  72. package/dist/commands/compute/build/status.js.map +1 -1
  73. package/dist/commands/compute/build/submit.js +9 -2
  74. package/dist/commands/compute/build/submit.js.map +1 -1
  75. package/dist/commands/compute/build/verify.js +9 -2
  76. package/dist/commands/compute/build/verify.js.map +1 -1
  77. package/dist/commands/compute/environment/set.js.map +1 -1
  78. package/dist/commands/compute/undelegate.js +12 -4
  79. package/dist/commands/compute/undelegate.js.map +1 -1
  80. package/dist/hooks/init/__tests__/version-check.test.js +32 -2
  81. package/dist/hooks/init/__tests__/version-check.test.js.map +1 -1
  82. package/dist/hooks/init/version-check.js +6 -1
  83. package/dist/hooks/init/version-check.js.map +1 -1
  84. package/package.json +11 -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-devep7" : "0.0.0";
331
+ return true ? "1.0.0" : "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
  }
@@ -1406,10 +1471,11 @@ import {
1406
1471
  createComputeModule,
1407
1472
  createBillingModule,
1408
1473
  createBuildModule,
1474
+ createAdminModule,
1409
1475
  getEnvironmentConfig as getEnvironmentConfig3,
1410
1476
  requirePrivateKey
1411
1477
  } from "@layr-labs/ecloud-sdk";
1412
- async function createComputeClient(flags) {
1478
+ async function createComputeClient(flags, options = {}) {
1413
1479
  flags = await validateCommonFlags(flags);
1414
1480
  const environment = flags.environment;
1415
1481
  const environmentConfig = getEnvironmentConfig3(environment);
@@ -1431,8 +1497,9 @@ async function createComputeClient(flags) {
1431
1497
  publicClient,
1432
1498
  environment,
1433
1499
  clientId: getClientId(),
1434
- skipTelemetry: true
1500
+ skipTelemetry: true,
1435
1501
  // CLI already has telemetry, skip SDK telemetry
1502
+ logger: options.logger
1436
1503
  });
1437
1504
  }
1438
1505
  async function createBuildClient(flags) {
@@ -1458,6 +1525,29 @@ async function createBuildClient(flags) {
1458
1525
  });
1459
1526
  }
1460
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
+
1461
1551
  // src/commands/compute/app/upgrade.ts
1462
1552
  import chalk2 from "chalk";
1463
1553
 
@@ -1619,8 +1709,63 @@ async function resolveDockerHubImageDigest(imageRef) {
1619
1709
  if (!/^sha256:[0-9a-f]{64}$/i.test(digest)) {
1620
1710
  throw new Error(`Unexpected digest format from Docker registry: ${digest}`);
1621
1711
  }
1712
+ await assertImageHasAmd64(owner, repo, tag, token, imageRef);
1622
1713
  return digest;
1623
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
+ }
1624
1769
 
1625
1770
  // src/utils/tls.ts
1626
1771
  import fs4 from "fs";
@@ -1685,12 +1830,45 @@ function mergeInlineEnvVars(envFilePath, inlineEnvVars) {
1685
1830
  return tmpFile;
1686
1831
  }
1687
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
+
1688
1866
  // src/commands/compute/app/upgrade.ts
1689
1867
  var AppUpgrade = class _AppUpgrade extends Command {
1690
1868
  static description = "Upgrade existing deployment";
1691
1869
  static args = {
1692
1870
  "app-id": Args.string({
1693
- description: "App ID or name to upgrade",
1871
+ description: "App ID or name to upgrade (env: ECLOUD_APP_ID)",
1694
1872
  required: false
1695
1873
  })
1696
1874
  };
@@ -1724,7 +1902,7 @@ var AppUpgrade = class _AppUpgrade extends Command {
1724
1902
  }),
1725
1903
  "log-visibility": Flags2.string({
1726
1904
  required: false,
1727
- description: "Log visibility setting: public, private, or off",
1905
+ description: "Log visibility setting: public, private, or off (non-interactive default: private)",
1728
1906
  options: ["public", "private", "off"],
1729
1907
  env: "ECLOUD_LOG_VISIBILITY"
1730
1908
  }),
@@ -1735,7 +1913,7 @@ var AppUpgrade = class _AppUpgrade extends Command {
1735
1913
  }),
1736
1914
  "resource-usage-monitoring": Flags2.string({
1737
1915
  required: false,
1738
- description: "Resource usage monitoring: enable or disable",
1916
+ description: "Resource usage monitoring: enable or disable (non-interactive default: disable)",
1739
1917
  options: ["enable", "disable"],
1740
1918
  env: "ECLOUD_RESOURCE_USAGE_MONITORING"
1741
1919
  }),
@@ -1773,19 +1951,50 @@ var AppUpgrade = class _AppUpgrade extends Command {
1773
1951
  }),
1774
1952
  force: Flags2.boolean({
1775
1953
  description: "Skip all confirmation prompts",
1776
- 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"
1777
1960
  })
1778
1961
  };
1779
1962
  async run() {
1780
1963
  return withTelemetry(this, async () => {
1781
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
+ }
1782
1991
  const compute = await createComputeClient(flags);
1783
1992
  const environment = flags.environment;
1784
1993
  const environmentConfig = getEnvironmentConfig4(environment);
1785
1994
  const rpcUrl = flags["rpc-url"] || environmentConfig.defaultRPCURL;
1786
1995
  const privateKey = flags["private-key"];
1787
1996
  const appID = await getOrPromptAppID({
1788
- appID: args["app-id"],
1997
+ appID: appIdInput,
1789
1998
  environment,
1790
1999
  privateKey,
1791
2000
  rpcUrl,
@@ -1846,7 +2055,7 @@ var AppUpgrade = class _AppUpgrade extends Command {
1846
2055
  buildContextPath: flags["build-context"],
1847
2056
  dependencies: flags["build-dependencies"]
1848
2057
  } : await promptVerifiableGitSourceInputs();
1849
- envFilePath = await getEnvFileInteractive(flags["env-file"]);
2058
+ envFilePath = await getEnvFile(flags["env-file"], nonInteractive);
1850
2059
  const includeTlsCaddyfile = isTlsEnabledFromEnvFile(envFilePath);
1851
2060
  if (includeTlsCaddyfile && !inputs.caddyfilePath) {
1852
2061
  inputs.caddyfilePath = "Caddyfile";
@@ -1906,10 +2115,11 @@ var AppUpgrade = class _AppUpgrade extends Command {
1906
2115
  }
1907
2116
  }
1908
2117
  const isVerifiable = verifiableMode !== "none";
1909
- 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);
1910
2120
  const buildFromDockerfile = dockerfilePath !== "";
1911
2121
  const imageRef = verifiableImageUrl ? verifiableImageUrl : await getImageReferenceInteractive(flags["image-ref"], buildFromDockerfile);
1912
- envFilePath = envFilePath ?? await getEnvFileInteractive(flags["env-file"]);
2122
+ envFilePath = envFilePath ?? await getEnvFile(flags["env-file"], nonInteractive);
1913
2123
  if (flags.env && flags.env.length > 0) {
1914
2124
  envFilePath = mergeInlineEnvVars(envFilePath, flags.env);
1915
2125
  }
@@ -1920,7 +2130,7 @@ var AppUpgrade = class _AppUpgrade extends Command {
1920
2130
  });
1921
2131
  let currentInstanceType = "";
1922
2132
  try {
1923
- const userApiClient = new UserApiClient3(environmentConfig, walletClient, publicClient, {
2133
+ const userApiClient = new UserApiClient4(environmentConfig, walletClient, publicClient, {
1924
2134
  clientId: getClientId()
1925
2135
  });
1926
2136
  const infos = await userApiClient.getInfos([appID], 1);
@@ -1935,33 +2145,43 @@ var AppUpgrade = class _AppUpgrade extends Command {
1935
2145
  privateKey,
1936
2146
  rpcUrl
1937
2147
  );
1938
- const instanceType = await getInstanceTypeInteractive(
2148
+ const instanceType = await getInstanceType(
1939
2149
  flags["instance-type"],
1940
2150
  currentInstanceType,
1941
- availableTypes
2151
+ availableTypes,
2152
+ nonInteractive
1942
2153
  );
1943
- const logSettings = await getLogSettingsInteractive(
1944
- flags["log-visibility"]
2154
+ const logSettings = await getLogSettings(
2155
+ flags["log-visibility"],
2156
+ nonInteractive
1945
2157
  );
1946
- const resourceUsageMonitoring = await getResourceUsageMonitoringInteractive(
1947
- flags["resource-usage-monitoring"]
2158
+ const resourceUsageMonitoring = await getResourceUsageMonitoring(
2159
+ flags["resource-usage-monitoring"],
2160
+ nonInteractive
1948
2161
  );
1949
2162
  const logVisibility = logSettings.publicLogs ? "public" : logSettings.logRedirect ? "private" : "off";
1950
- const { prepared, gasEstimate } = verifiableMode === "git" ? await compute.app.prepareUpgradeFromVerifiableBuild(appID, {
1951
- imageRef,
1952
- imageDigest: verifiableImageDigest,
1953
- envFile: envFilePath,
1954
- instanceType,
1955
- logVisibility,
1956
- resourceUsageMonitoring
1957
- }) : await compute.app.prepareUpgrade(appID, {
1958
- dockerfile: dockerfilePath,
1959
- imageRef,
1960
- envFile: envFilePath,
1961
- instanceType,
1962
- logVisibility,
1963
- resourceUsageMonitoring
1964
- });
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
+ }
1965
2185
  const finalTx = await applyTxOverrides(gasEstimate, flags, { publicClient, address });
1966
2186
  if (flags["max-fee-per-gas"] || flags["max-priority-fee"]) {
1967
2187
  this.log(
@@ -1984,8 +2204,39 @@ ${chalk2.gray(`Upgrade cancelled`)}`);
1984
2204
  return;
1985
2205
  }
1986
2206
  }
1987
- const res = await compute.app.executeUpgrade(prepared, finalTx);
1988
- 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
+ }
1989
2240
  try {
1990
2241
  const cwd = process.env.INIT_CWD || process.cwd();
1991
2242
  setLinkedAppForDirectory(environment, cwd, res.appId);
@@ -2003,7 +2254,7 @@ ${chalk2.gray(`Upgrade cancelled`)}`);
2003
2254
  rpcUrl,
2004
2255
  environment
2005
2256
  });
2006
- const userApiClient = new UserApiClient3(environmentConfig, walletClient2, publicClient2, {
2257
+ const userApiClient = new UserApiClient4(environmentConfig, walletClient2, publicClient2, {
2007
2258
  clientId: getClientId()
2008
2259
  });
2009
2260
  const infos = await userApiClient.getInfos([res.appId], 1);
@@ -2032,26 +2283,6 @@ Note: "Running" means the container started. Verify your app is serving traffic
2032
2283
  });
2033
2284
  }
2034
2285
  };
2035
- async function fetchAvailableInstanceTypes(environment, environmentConfig, privateKey, rpcUrl) {
2036
- try {
2037
- const { publicClient, walletClient } = createViemClients({
2038
- privateKey,
2039
- rpcUrl,
2040
- environment
2041
- });
2042
- const userApiClient = new UserApiClient3(environmentConfig, walletClient, publicClient, {
2043
- clientId: getClientId()
2044
- });
2045
- const skuList = await userApiClient.getSKUs();
2046
- if (skuList.skus.length === 0) {
2047
- throw new Error("No instance types available from server");
2048
- }
2049
- return skuList.skus;
2050
- } catch (err) {
2051
- console.warn(`Failed to fetch instance types: ${err.message}`);
2052
- return [{ sku: "g1-standard-4t", description: "4 vCPUs, 16 GB memory, TDX" }];
2053
- }
2054
- }
2055
2286
  export {
2056
2287
  AppUpgrade as default
2057
2288
  };