@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,7 @@
2
2
 
3
3
  // src/commands/compute/app/deploy.ts
4
4
  import { Command, Flags as Flags2 } from "@oclif/core";
5
- import { getEnvironmentConfig as getEnvironmentConfig4, UserApiClient as UserApiClient3, isMainnet } from "@layr-labs/ecloud-sdk";
5
+ import { getEnvironmentConfig as getEnvironmentConfig4, isMainnet, WatchTimeoutError } from "@layr-labs/ecloud-sdk";
6
6
 
7
7
  // src/telemetry.ts
8
8
  import {
@@ -304,7 +304,7 @@ function findAvailableName(environment, baseName) {
304
304
 
305
305
  // src/utils/version.ts
306
306
  function getCliVersion() {
307
- return true ? "1.0.0-devep7" : "0.0.0";
307
+ return true ? "1.0.0" : "0.0.0";
308
308
  }
309
309
  function getClientId() {
310
310
  return `ecloud-cli/v${getCliVersion()}`;
@@ -319,7 +319,30 @@ function ensureInteractive(missingFlagHint) {
319
319
  );
320
320
  }
321
321
  }
322
- async function getDockerfileInteractive(dockerfilePath) {
322
+ function isNonInteractive(flags) {
323
+ if (flags?.["non-interactive"]) return true;
324
+ if (process.env.CI === "true") return true;
325
+ return !process.stdin.isTTY;
326
+ }
327
+ function warnDefaulted(flagHint, chosen) {
328
+ console.warn(`Warning: ${flagHint} not set in non-interactive mode; defaulting to ${chosen}.`);
329
+ }
330
+ function collectMissingRequiredInputs(state, identityFlag) {
331
+ const missing = [];
332
+ const hasImageSource = !!state.imageRef || !!state.dockerfile || !!state.verifiable && !!state.repo && !!state.commit;
333
+ if (!hasImageSource) {
334
+ missing.push(
335
+ "an image source (one of: --image-ref, --dockerfile, or --verifiable with --repo and --commit)"
336
+ );
337
+ } else if (state.dockerfile && !state.imageRef && !state.verifiable) {
338
+ missing.push("--image-ref (registry destination for the built image)");
339
+ }
340
+ if (identityFlag === "name" && !state.name) {
341
+ missing.push("--name");
342
+ }
343
+ return missing;
344
+ }
345
+ async function getDockerfile(dockerfilePath, nonInteractive = false) {
323
346
  if (dockerfilePath) {
324
347
  return dockerfilePath;
325
348
  }
@@ -328,6 +351,10 @@ async function getDockerfileInteractive(dockerfilePath) {
328
351
  if (!fs3.existsSync(dockerfilePath_resolved)) {
329
352
  return "";
330
353
  }
354
+ if (nonInteractive) {
355
+ warnDefaulted("--dockerfile/--image-ref", `build from '${dockerfilePath_resolved}'`);
356
+ return dockerfilePath_resolved;
357
+ }
331
358
  ensureInteractive("--dockerfile or --image-ref");
332
359
  console.log(`
333
360
  Found Dockerfile in ${cwd}`);
@@ -784,13 +811,17 @@ async function getOrPromptAppName(appName, environment, imageRef, suggestedBaseN
784
811
  }
785
812
  return getAvailableAppNameInteractive(environment, imageRef, suggestedBaseName, skipDefaultName);
786
813
  }
787
- async function getEnvFileInteractive(envFilePath) {
814
+ async function getEnvFile(envFilePath, nonInteractive = false) {
788
815
  if (envFilePath && fs3.existsSync(envFilePath)) {
789
816
  return envFilePath;
790
817
  }
791
818
  if (fs3.existsSync(".env")) {
792
819
  return ".env";
793
820
  }
821
+ if (nonInteractive) {
822
+ warnDefaulted("--env-file", "no env file");
823
+ return "";
824
+ }
794
825
  ensureInteractive("--env-file");
795
826
  console.log("\nEnvironment file not found.");
796
827
  console.log("Environment files contain variables like RPC_URL, etc.");
@@ -818,6 +849,7 @@ async function getEnvFileInteractive(envFilePath) {
818
849
  throw new Error(`Unexpected choice: ${choice}`);
819
850
  }
820
851
  }
852
+ var DEFAULT_NONINTERACTIVE_SKU = "g1-standard-2s";
821
853
  function formatSkuChoice(it) {
822
854
  if (it.vcpus != null && it.memory_mb != null && it.monthly_price_usd != null && it.hourly_price_usd != null) {
823
855
  const tier = it.friendly_name ?? it.sku;
@@ -834,7 +866,7 @@ function formatSkuChoice(it) {
834
866
  }
835
867
  return `${it.sku} - ${it.description}`;
836
868
  }
837
- async function getInstanceTypeInteractive(instanceType, defaultSKU, availableTypes) {
869
+ async function getInstanceType(instanceType, defaultSKU, availableTypes, nonInteractive = false) {
838
870
  if (instanceType) {
839
871
  const valid = availableTypes.find((t) => t.sku === instanceType);
840
872
  if (valid) {
@@ -843,6 +875,20 @@ async function getInstanceTypeInteractive(instanceType, defaultSKU, availableTyp
843
875
  const validSKUs = availableTypes.map((t) => t.sku).join(", ");
844
876
  throw new Error(`Invalid instance-type: ${instanceType} (must be one of: ${validSKUs})`);
845
877
  }
878
+ if (nonInteractive) {
879
+ if (defaultSKU) {
880
+ warnDefaulted("--instance-type", `current type '${defaultSKU}'`);
881
+ return defaultSKU;
882
+ }
883
+ if (availableTypes.some((t) => t.sku === DEFAULT_NONINTERACTIVE_SKU)) {
884
+ warnDefaulted("--instance-type", `'${DEFAULT_NONINTERACTIVE_SKU}'`);
885
+ return DEFAULT_NONINTERACTIVE_SKU;
886
+ }
887
+ const validSKUs = availableTypes.map((t) => t.sku).join(", ");
888
+ throw new Error(
889
+ `Cannot pick a default --instance-type in non-interactive mode: '${DEFAULT_NONINTERACTIVE_SKU}' not offered (available: ${validSKUs}). Provide --instance-type.`
890
+ );
891
+ }
846
892
  ensureInteractive("--instance-type");
847
893
  const isCurrentType = defaultSKU !== "";
848
894
  const hasPricing = availableTypes.some((t) => t.monthly_price_usd != null);
@@ -874,7 +920,7 @@ async function getInstanceTypeInteractive(instanceType, defaultSKU, availableTyp
874
920
  });
875
921
  return choice;
876
922
  }
877
- async function getLogSettingsInteractive(logVisibility) {
923
+ async function getLogSettings(logVisibility, nonInteractive = false) {
878
924
  if (logVisibility) {
879
925
  switch (logVisibility) {
880
926
  case "public":
@@ -889,6 +935,10 @@ async function getLogSettingsInteractive(logVisibility) {
889
935
  );
890
936
  }
891
937
  }
938
+ if (nonInteractive) {
939
+ warnDefaulted("--log-visibility", "'private'");
940
+ return { logRedirect: "always", publicLogs: false };
941
+ }
892
942
  ensureInteractive("--log-visibility");
893
943
  const choice = await select({
894
944
  message: "Do you want to view your app's logs?",
@@ -909,7 +959,7 @@ async function getLogSettingsInteractive(logVisibility) {
909
959
  throw new Error(`Unexpected choice: ${choice}`);
910
960
  }
911
961
  }
912
- async function getResourceUsageMonitoringInteractive(resourceUsageMonitoring) {
962
+ async function getResourceUsageMonitoring(resourceUsageMonitoring, nonInteractive = false) {
913
963
  if (resourceUsageMonitoring) {
914
964
  switch (resourceUsageMonitoring) {
915
965
  case "enable":
@@ -921,6 +971,10 @@ async function getResourceUsageMonitoringInteractive(resourceUsageMonitoring) {
921
971
  );
922
972
  }
923
973
  }
974
+ if (nonInteractive) {
975
+ warnDefaulted("--resource-usage-monitoring", "'disable'");
976
+ return "disable";
977
+ }
924
978
  ensureInteractive("--resource-usage-monitoring");
925
979
  const choice = await select({
926
980
  message: "Show resource usage (CPU/memory) for your app?",
@@ -936,9 +990,7 @@ async function confirm(prompt) {
936
990
  }
937
991
  async function confirmWithDefault(prompt, defaultValue = false) {
938
992
  if (!process.stdin.isTTY) {
939
- throw new Error(
940
- `Cannot confirm "${prompt}" in non-interactive mode. Use --force to skip confirmation prompts.`
941
- );
993
+ return defaultValue;
942
994
  }
943
995
  return await inquirerConfirm({
944
996
  message: prompt,
@@ -1257,7 +1309,7 @@ var commonFlags = {
1257
1309
  required: false,
1258
1310
  description: "Deployment environment to use",
1259
1311
  env: "ECLOUD_ENV",
1260
- default: async () => getDefaultEnvironment() || (getBuildType3() === "dev" ? "sepolia-dev" : "sepolia")
1312
+ default: async () => getDefaultEnvironment() || (getBuildType3() === "dev" ? "sepolia-dev" : "mainnet-alpha")
1261
1313
  }),
1262
1314
  "private-key": Flags.string({
1263
1315
  required: false,
@@ -1287,6 +1339,12 @@ var commonFlags = {
1287
1339
  nonce: Flags.string({
1288
1340
  required: false,
1289
1341
  description: 'Override transaction nonce (integer or "latest" to replace a stuck transaction)'
1342
+ }),
1343
+ "non-interactive": Flags.boolean({
1344
+ required: false,
1345
+ description: "Assume non-interactive mode: default safe prompts and error all-at-once on missing required inputs",
1346
+ env: "ECLOUD_NON_INTERACTIVE",
1347
+ default: false
1290
1348
  })
1291
1349
  };
1292
1350
  async function applyTxOverrides(estimate, flags, opts) {
@@ -1320,7 +1378,9 @@ async function applyTxOverrides(estimate, flags, opts) {
1320
1378
  } else {
1321
1379
  const parsed = Number(nonceStr);
1322
1380
  if (!Number.isInteger(parsed) || parsed < 0) {
1323
- throw new Error(`Invalid nonce: "${nonceStr}". Must be a non-negative integer or "latest".`);
1381
+ throw new Error(
1382
+ `Invalid nonce: "${nonceStr}". Must be a non-negative integer or "latest".`
1383
+ );
1324
1384
  }
1325
1385
  nonce = parsed;
1326
1386
  }
@@ -1340,10 +1400,11 @@ import {
1340
1400
  createComputeModule,
1341
1401
  createBillingModule,
1342
1402
  createBuildModule,
1403
+ createAdminModule,
1343
1404
  getEnvironmentConfig as getEnvironmentConfig3,
1344
1405
  requirePrivateKey
1345
1406
  } from "@layr-labs/ecloud-sdk";
1346
- async function createComputeClient(flags) {
1407
+ async function createComputeClient(flags, options = {}) {
1347
1408
  flags = await validateCommonFlags(flags);
1348
1409
  const environment = flags.environment;
1349
1410
  const environmentConfig = getEnvironmentConfig3(environment);
@@ -1365,8 +1426,9 @@ async function createComputeClient(flags) {
1365
1426
  publicClient,
1366
1427
  environment,
1367
1428
  clientId: getClientId(),
1368
- skipTelemetry: true
1429
+ skipTelemetry: true,
1369
1430
  // CLI already has telemetry, skip SDK telemetry
1431
+ logger: options.logger
1370
1432
  });
1371
1433
  }
1372
1434
  async function createBuildClient(flags) {
@@ -1392,6 +1454,29 @@ async function createBuildClient(flags) {
1392
1454
  });
1393
1455
  }
1394
1456
 
1457
+ // src/utils/instanceTypes.ts
1458
+ import { UserApiClient as UserApiClient3 } from "@layr-labs/ecloud-sdk";
1459
+ async function fetchAvailableInstanceTypes(environment, environmentConfig, privateKey, rpcUrl) {
1460
+ try {
1461
+ const { publicClient, walletClient } = createViemClients({
1462
+ privateKey,
1463
+ rpcUrl,
1464
+ environment
1465
+ });
1466
+ const userApiClient = new UserApiClient3(environmentConfig, walletClient, publicClient, {
1467
+ clientId: getClientId()
1468
+ });
1469
+ const skuList = await userApiClient.getSKUs();
1470
+ if (skuList.skus.length === 0) {
1471
+ throw new Error("No instance types available from server");
1472
+ }
1473
+ return skuList.skus;
1474
+ } catch (err) {
1475
+ console.warn(`Failed to fetch instance types: ${err.message}`);
1476
+ return [{ sku: "g1-standard-4t", description: "4 vCPUs, 16 GB memory, TDX" }];
1477
+ }
1478
+ }
1479
+
1395
1480
  // src/commands/compute/app/deploy.ts
1396
1481
  import chalk2 from "chalk";
1397
1482
 
@@ -1553,8 +1638,63 @@ async function resolveDockerHubImageDigest(imageRef) {
1553
1638
  if (!/^sha256:[0-9a-f]{64}$/i.test(digest)) {
1554
1639
  throw new Error(`Unexpected digest format from Docker registry: ${digest}`);
1555
1640
  }
1641
+ await assertImageHasAmd64(owner, repo, tag, token, imageRef);
1556
1642
  return digest;
1557
1643
  }
1644
+ var AMD64_ACCEPT = [
1645
+ "application/vnd.oci.image.index.v1+json",
1646
+ "application/vnd.docker.distribution.manifest.list.v2+json",
1647
+ "application/vnd.oci.image.manifest.v1+json",
1648
+ "application/vnd.docker.distribution.manifest.v2+json"
1649
+ ].join(", ");
1650
+ async function assertImageHasAmd64(owner, repo, tag, token, imageRef) {
1651
+ const base = `https://registry-1.docker.io/v2/${owner}/${repo}`;
1652
+ const headers = { Authorization: `Bearer ${token}`, Accept: AMD64_ACCEPT };
1653
+ const res = await fetch(`${base}/manifests/${encodeURIComponent(tag)}`, { headers });
1654
+ if (!res.ok) {
1655
+ const body = await safeReadText(res);
1656
+ throw new Error(
1657
+ `Failed to read manifest for ${imageRef} (${res.status}): ${body || res.statusText}`
1658
+ );
1659
+ }
1660
+ const manifest = await res.json();
1661
+ const isAmd64 = (os5, arch) => os5 === "linux" && arch === "amd64";
1662
+ if (Array.isArray(manifest.manifests) && manifest.manifests.length > 0) {
1663
+ const platforms = manifest.manifests.map(
1664
+ (m) => m.platform ? `${m.platform.os}/${m.platform.architecture}` : "unknown"
1665
+ );
1666
+ if (manifest.manifests.some((m) => isAmd64(m.platform?.os, m.platform?.architecture))) {
1667
+ return;
1668
+ }
1669
+ throw amd64Error(imageRef, platforms);
1670
+ }
1671
+ const configDigest = manifest.config?.digest;
1672
+ if (!configDigest) {
1673
+ throw amd64Error(imageRef, ["unknown (no platform info in manifest)"]);
1674
+ }
1675
+ const cfgRes = await fetch(`${base}/blobs/${configDigest}`, {
1676
+ headers: { Authorization: `Bearer ${token}` }
1677
+ });
1678
+ if (!cfgRes.ok) {
1679
+ throw amd64Error(imageRef, ["unknown (could not read image config)"]);
1680
+ }
1681
+ const cfg = await cfgRes.json();
1682
+ if (isAmd64(cfg.os, cfg.architecture)) {
1683
+ return;
1684
+ }
1685
+ throw amd64Error(imageRef, [`${cfg.os ?? "unknown"}/${cfg.architecture ?? "unknown"}`]);
1686
+ }
1687
+ function amd64Error(imageRef, platforms) {
1688
+ return new Error(
1689
+ `ecloud requires linux/amd64 images for TEE deployment.
1690
+
1691
+ Image: ${imageRef}
1692
+ Found platform(s): ${platforms.join(", ")}
1693
+ Required platform: linux/amd64
1694
+
1695
+ 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.`
1696
+ );
1697
+ }
1558
1698
 
1559
1699
  // src/utils/tls.ts
1560
1700
  import fs4 from "fs";
@@ -1619,6 +1759,39 @@ function mergeInlineEnvVars(envFilePath, inlineEnvVars) {
1619
1759
  return tmpFile;
1620
1760
  }
1621
1761
 
1762
+ // src/utils/exitCodes.ts
1763
+ import { InsufficientGasError } from "@layr-labs/ecloud-sdk";
1764
+ var EXIT_CODES = {
1765
+ INVALID_INPUT: 2,
1766
+ BUILD_FAILED: 3,
1767
+ ONCHAIN_FAILED: 4
1768
+ };
1769
+ function errorMessage(err) {
1770
+ if (err instanceof Error) return err.message;
1771
+ return String(err);
1772
+ }
1773
+ function stageFailure(operation, stage, err) {
1774
+ const noun = operation === "deploy" ? "deployment" : "upgrade";
1775
+ if (err instanceof InsufficientGasError) {
1776
+ stage = "onchain";
1777
+ }
1778
+ switch (stage) {
1779
+ case "invalid-input":
1780
+ return { message: errorMessage(err), exit: EXIT_CODES.INVALID_INPUT };
1781
+ case "build":
1782
+ return {
1783
+ message: `Build/push failed (no ${noun} was attempted): ${errorMessage(err)}`,
1784
+ exit: EXIT_CODES.BUILD_FAILED
1785
+ };
1786
+ case "onchain":
1787
+ return {
1788
+ message: `On-chain ${noun} failed after the image was built and pushed: ${errorMessage(err)}
1789
+ The image is already pushed \u2014 re-running ${operation} will reuse it.`,
1790
+ exit: EXIT_CODES.ONCHAIN_FAILED
1791
+ };
1792
+ }
1793
+ }
1794
+
1622
1795
  // src/commands/compute/app/deploy.ts
1623
1796
  var AppDeploy = class _AppDeploy extends Command {
1624
1797
  static description = "Deploy new app";
@@ -1652,7 +1825,7 @@ var AppDeploy = class _AppDeploy extends Command {
1652
1825
  }),
1653
1826
  "log-visibility": Flags2.string({
1654
1827
  required: false,
1655
- description: "Log visibility setting: public, private, or off",
1828
+ description: "Log visibility setting: public, private, or off (non-interactive default: private)",
1656
1829
  options: ["public", "private", "off"],
1657
1830
  env: "ECLOUD_LOG_VISIBILITY"
1658
1831
  }),
@@ -1668,7 +1841,7 @@ var AppDeploy = class _AppDeploy extends Command {
1668
1841
  }),
1669
1842
  "resource-usage-monitoring": Flags2.string({
1670
1843
  required: false,
1671
- description: "Resource usage monitoring: enable or disable",
1844
+ description: "Resource usage monitoring: enable or disable (non-interactive default: disable)",
1672
1845
  options: ["enable", "disable"],
1673
1846
  env: "ECLOUD_RESOURCE_USAGE_MONITORING"
1674
1847
  }),
@@ -1722,12 +1895,40 @@ var AppDeploy = class _AppDeploy extends Command {
1722
1895
  }),
1723
1896
  force: Flags2.boolean({
1724
1897
  description: "Skip all confirmation prompts",
1725
- default: false
1898
+ default: false,
1899
+ env: "ECLOUD_FORCE"
1900
+ }),
1901
+ "watch-timeout": Flags2.integer({
1902
+ description: "Maximum seconds to wait for the app to start before returning a recovery hint (default: 600)",
1903
+ env: "ECLOUD_WATCH_TIMEOUT_SECONDS"
1726
1904
  })
1727
1905
  };
1728
1906
  async run() {
1729
1907
  return withTelemetry(this, async () => {
1730
1908
  const { flags } = await this.parse(_AppDeploy);
1909
+ const nonInteractive = isNonInteractive(flags);
1910
+ if (nonInteractive) {
1911
+ const missing = collectMissingRequiredInputs(
1912
+ {
1913
+ imageRef: flags["image-ref"],
1914
+ dockerfile: flags.dockerfile,
1915
+ verifiable: flags.verifiable,
1916
+ repo: flags.repo,
1917
+ commit: flags.commit,
1918
+ name: flags.name
1919
+ },
1920
+ "name"
1921
+ );
1922
+ if (missing.length > 0) {
1923
+ const { message, exit } = stageFailure(
1924
+ "deploy",
1925
+ "invalid-input",
1926
+ `Missing required input(s) for non-interactive deploy:
1927
+ - ${missing.join("\n - ")}`
1928
+ );
1929
+ this.error(message, { exit });
1930
+ }
1931
+ }
1731
1932
  const compute = await createComputeClient(flags);
1732
1933
  const environment = flags.environment;
1733
1934
  const environmentConfig = getEnvironmentConfig4(environment);
@@ -1830,7 +2031,7 @@ Warning: Wallet ${chalk2.bold(address)} has zero balance on ${environment}.`
1830
2031
  buildContextPath: flags["build-context"],
1831
2032
  dependencies: flags["build-dependencies"]
1832
2033
  } : await promptVerifiableGitSourceInputs();
1833
- envFilePath = await getEnvFileInteractive(flags["env-file"]);
2034
+ envFilePath = await getEnvFile(flags["env-file"], nonInteractive);
1834
2035
  const includeTlsCaddyfile = isTlsEnabledFromEnvFile(envFilePath);
1835
2036
  if (includeTlsCaddyfile && !inputs.caddyfilePath) {
1836
2037
  inputs.caddyfilePath = "Caddyfile";
@@ -1892,7 +2093,8 @@ Warning: Wallet ${chalk2.bold(address)} has zero balance on ${environment}.`
1892
2093
  }
1893
2094
  }
1894
2095
  const isVerifiable = verifiableMode !== "none";
1895
- const dockerfilePath = isVerifiable ? "" : await getDockerfileInteractive(flags.dockerfile);
2096
+ const deployExistingImageRef = !!flags["image-ref"] && !flags.dockerfile;
2097
+ const dockerfilePath = isVerifiable || deployExistingImageRef ? "" : await getDockerfile(flags.dockerfile, nonInteractive);
1896
2098
  const buildFromDockerfile = dockerfilePath !== "";
1897
2099
  const imageRef = verifiableImageUrl ? verifiableImageUrl : await getImageReferenceInteractive(flags["image-ref"], buildFromDockerfile);
1898
2100
  const appName = await getOrPromptAppName(
@@ -1902,7 +2104,7 @@ Warning: Wallet ${chalk2.bold(address)} has zero balance on ${environment}.`
1902
2104
  suggestedAppBaseName,
1903
2105
  skipDefaultAppName
1904
2106
  );
1905
- envFilePath = envFilePath ?? await getEnvFileInteractive(flags["env-file"]);
2107
+ envFilePath = envFilePath ?? await getEnvFile(flags["env-file"], nonInteractive);
1906
2108
  if (flags.env && flags.env.length > 0) {
1907
2109
  envFilePath = mergeInlineEnvVars(envFilePath, flags.env);
1908
2110
  }
@@ -1912,38 +2114,48 @@ Warning: Wallet ${chalk2.bold(address)} has zero balance on ${environment}.`
1912
2114
  privateKey,
1913
2115
  rpcUrl
1914
2116
  );
1915
- const instanceType = await getInstanceTypeInteractive(
2117
+ const instanceType = await getInstanceType(
1916
2118
  flags["instance-type"],
1917
2119
  "",
1918
- // No default for new deployments
1919
- availableTypes
2120
+ // No pinned default for new deployments; non-interactive falls back to g1-standard-2s
2121
+ availableTypes,
2122
+ nonInteractive
1920
2123
  );
1921
- const logSettings = await getLogSettingsInteractive(
1922
- flags["log-visibility"]
2124
+ const logSettings = await getLogSettings(
2125
+ flags["log-visibility"],
2126
+ nonInteractive
1923
2127
  );
1924
- const resourceUsageMonitoring = await getResourceUsageMonitoringInteractive(
1925
- flags["resource-usage-monitoring"]
2128
+ const resourceUsageMonitoring = await getResourceUsageMonitoring(
2129
+ flags["resource-usage-monitoring"],
2130
+ nonInteractive
1926
2131
  );
1927
2132
  const logVisibility = logSettings.publicLogs ? "public" : logSettings.logRedirect ? "private" : "off";
1928
- const { prepared, gasEstimate } = verifiableMode === "git" ? await compute.app.prepareDeployFromVerifiableBuild({
1929
- name: appName,
1930
- imageRef,
1931
- imageDigest: verifiableImageDigest,
1932
- envFile: envFilePath,
1933
- instanceType,
1934
- logVisibility,
1935
- resourceUsageMonitoring,
1936
- billTo: "developer"
1937
- }) : await compute.app.prepareDeploy({
1938
- name: appName,
1939
- dockerfile: dockerfilePath,
1940
- imageRef,
1941
- envFile: envFilePath,
1942
- instanceType,
1943
- logVisibility,
1944
- resourceUsageMonitoring,
1945
- billTo: "developer"
1946
- });
2133
+ let prepared;
2134
+ let gasEstimate;
2135
+ try {
2136
+ ({ prepared, gasEstimate } = verifiableMode === "git" ? await compute.app.prepareDeployFromVerifiableBuild({
2137
+ name: appName,
2138
+ imageRef,
2139
+ imageDigest: verifiableImageDigest,
2140
+ envFile: envFilePath,
2141
+ instanceType,
2142
+ logVisibility,
2143
+ resourceUsageMonitoring,
2144
+ billTo: "developer"
2145
+ }) : await compute.app.prepareDeploy({
2146
+ name: appName,
2147
+ dockerfile: dockerfilePath,
2148
+ imageRef,
2149
+ envFile: envFilePath,
2150
+ instanceType,
2151
+ logVisibility,
2152
+ resourceUsageMonitoring,
2153
+ billTo: "developer"
2154
+ }));
2155
+ } catch (err) {
2156
+ const { message, exit } = stageFailure("deploy", "build", err);
2157
+ this.error(message, { exit });
2158
+ }
1947
2159
  const finalTx = await applyTxOverrides(gasEstimate, flags, { publicClient, address });
1948
2160
  if (flags["max-fee-per-gas"] || flags["max-priority-fee"]) {
1949
2161
  this.log(
@@ -1966,7 +2178,13 @@ ${chalk2.gray(`Deployment cancelled`)}`);
1966
2178
  return;
1967
2179
  }
1968
2180
  }
1969
- const res = await compute.app.executeDeploy(prepared, finalTx);
2181
+ let res;
2182
+ try {
2183
+ res = await compute.app.executeDeploy(prepared, finalTx);
2184
+ } catch (err) {
2185
+ const { message, exit } = stageFailure("deploy", "onchain", err);
2186
+ this.error(message, { exit });
2187
+ }
1970
2188
  if (!flags["skip-profile"]) {
1971
2189
  const hasProfileFlags = flags.website || flags.description || flags["x-url"] || flags.image;
1972
2190
  let profile = null;
@@ -2009,7 +2227,42 @@ ${chalk2.gray(`Deployment cancelled`)}`);
2009
2227
  }
2010
2228
  }
2011
2229
  }
2012
- const ipAddress = await compute.app.watchDeployment(res.appId);
2230
+ let ipAddress;
2231
+ try {
2232
+ ipAddress = await compute.app.watchDeployment(res.appId, {
2233
+ timeoutSeconds: flags["watch-timeout"]
2234
+ });
2235
+ } catch (watchErr) {
2236
+ if (watchErr instanceof WatchTimeoutError) {
2237
+ this.log(
2238
+ `
2239
+ ${chalk2.yellow("\u26A0")} ${chalk2.yellow(
2240
+ `Deployment did not reach Running within ${watchErr.elapsedSeconds}s (last status: ${watchErr.lastStatus ?? "unknown"}).`
2241
+ )}`
2242
+ );
2243
+ this.log(
2244
+ chalk2.gray(
2245
+ `The deploy transaction succeeded, but the orchestrator hasn't reported the app as Running yet.`
2246
+ )
2247
+ );
2248
+ this.log(chalk2.gray(` appId: ${res.appId}`));
2249
+ if (res.txHash) {
2250
+ this.log(chalk2.gray(` txHash: ${res.txHash}`));
2251
+ }
2252
+ this.log(
2253
+ chalk2.gray(
2254
+ `Check progress later with: ${chalk2.cyan(`ecloud compute app info ${res.appId}`)}`
2255
+ )
2256
+ );
2257
+ this.log(
2258
+ chalk2.gray(
2259
+ `Override the watch timeout with the ${chalk2.cyan("ECLOUD_WATCH_TIMEOUT_SECONDS")} environment variable.`
2260
+ )
2261
+ );
2262
+ this.exit(1);
2263
+ }
2264
+ throw watchErr;
2265
+ }
2013
2266
  try {
2014
2267
  const cwd = process.env.INIT_CWD || process.cwd();
2015
2268
  setLinkedAppForDirectory(environment, cwd, res.appId);
@@ -2035,26 +2288,6 @@ Note: "Running" means the container started \u2014 verify it is serving traffic
2035
2288
  });
2036
2289
  }
2037
2290
  };
2038
- async function fetchAvailableInstanceTypes(environment, environmentConfig, privateKey, rpcUrl) {
2039
- try {
2040
- const { publicClient, walletClient } = createViemClients({
2041
- privateKey,
2042
- rpcUrl,
2043
- environment
2044
- });
2045
- const userApiClient = new UserApiClient3(environmentConfig, walletClient, publicClient, {
2046
- clientId: getClientId()
2047
- });
2048
- const skuList = await userApiClient.getSKUs();
2049
- if (skuList.skus.length === 0) {
2050
- throw new Error("No instance types available from server");
2051
- }
2052
- return skuList.skus;
2053
- } catch (err) {
2054
- console.warn(`Failed to fetch instance types: ${err.message}`);
2055
- return [{ sku: "g1-standard-4t", description: "4 vCPUs, 16 GB memory, TDX" }];
2056
- }
2057
- }
2058
2291
  export {
2059
2292
  AppDeploy as default
2060
2293
  };