@layr-labs/ecloud-cli 0.3.4 → 0.4.0-dev.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 (56) hide show
  1. package/dist/commands/auth/whoami.js +1 -0
  2. package/dist/commands/auth/whoami.js.map +1 -1
  3. package/dist/commands/billing/__tests__/status.test.js +552 -0
  4. package/dist/commands/billing/__tests__/status.test.js.map +1 -0
  5. package/dist/commands/billing/__tests__/subscribe.test.js +580 -0
  6. package/dist/commands/billing/__tests__/subscribe.test.js.map +1 -0
  7. package/dist/commands/billing/__tests__/top-up.test.js +729 -0
  8. package/dist/commands/billing/__tests__/top-up.test.js.map +1 -0
  9. package/dist/commands/billing/cancel.js +8 -7
  10. package/dist/commands/billing/cancel.js.map +1 -1
  11. package/dist/commands/billing/status.js +25 -19
  12. package/dist/commands/billing/status.js.map +1 -1
  13. package/dist/commands/billing/subscribe.js +43 -14
  14. package/dist/commands/billing/subscribe.js.map +1 -1
  15. package/dist/commands/billing/top-up.js +491 -0
  16. package/dist/commands/billing/top-up.js.map +1 -0
  17. package/dist/commands/compute/app/create.js +1 -0
  18. package/dist/commands/compute/app/create.js.map +1 -1
  19. package/dist/commands/compute/app/deploy.js +62 -23
  20. package/dist/commands/compute/app/deploy.js.map +1 -1
  21. package/dist/commands/compute/app/info.js +32 -31
  22. package/dist/commands/compute/app/info.js.map +1 -1
  23. package/dist/commands/compute/app/list.js +31 -30
  24. package/dist/commands/compute/app/list.js.map +1 -1
  25. package/dist/commands/compute/app/logs.js +2 -1
  26. package/dist/commands/compute/app/logs.js.map +1 -1
  27. package/dist/commands/compute/app/profile/set.js +6 -5
  28. package/dist/commands/compute/app/profile/set.js.map +1 -1
  29. package/dist/commands/compute/app/releases.js +18 -17
  30. package/dist/commands/compute/app/releases.js.map +1 -1
  31. package/dist/commands/compute/app/start.js +6 -5
  32. package/dist/commands/compute/app/start.js.map +1 -1
  33. package/dist/commands/compute/app/stop.js +6 -5
  34. package/dist/commands/compute/app/stop.js.map +1 -1
  35. package/dist/commands/compute/app/terminate.js +6 -5
  36. package/dist/commands/compute/app/terminate.js.map +1 -1
  37. package/dist/commands/compute/app/upgrade.js +51 -22
  38. package/dist/commands/compute/app/upgrade.js.map +1 -1
  39. package/dist/commands/compute/build/info.js +16 -15
  40. package/dist/commands/compute/build/info.js.map +1 -1
  41. package/dist/commands/compute/build/list.js +13 -12
  42. package/dist/commands/compute/build/list.js.map +1 -1
  43. package/dist/commands/compute/build/logs.js +4 -3
  44. package/dist/commands/compute/build/logs.js.map +1 -1
  45. package/dist/commands/compute/build/status.js +9 -8
  46. package/dist/commands/compute/build/status.js.map +1 -1
  47. package/dist/commands/compute/build/submit.js +16 -15
  48. package/dist/commands/compute/build/submit.js.map +1 -1
  49. package/dist/commands/compute/build/verify.js +10 -9
  50. package/dist/commands/compute/build/verify.js.map +1 -1
  51. package/dist/commands/compute/environment/set.js +1 -0
  52. package/dist/commands/compute/environment/set.js.map +1 -1
  53. package/dist/commands/compute/undelegate.js +6 -5
  54. package/dist/commands/compute/undelegate.js.map +1 -1
  55. package/package.json +3 -2
  56. package/VERSION +0 -2
@@ -0,0 +1,491 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/commands/billing/top-up.ts
4
+ import { Command, Flags as Flags2 } from "@oclif/core";
5
+
6
+ // src/client.ts
7
+ import {
8
+ createComputeModule,
9
+ createBillingModule,
10
+ createBuildModule,
11
+ getEnvironmentConfig as getEnvironmentConfig3,
12
+ requirePrivateKey,
13
+ getPrivateKeyWithSource,
14
+ addHexPrefix as addHexPrefix2
15
+ } from "@layr-labs/ecloud-sdk";
16
+
17
+ // src/flags.ts
18
+ import { Flags } from "@oclif/core";
19
+ import { getBuildType as getBuildType2 } from "@layr-labs/ecloud-sdk";
20
+
21
+ // src/utils/prompts.ts
22
+ import { input, select, password, confirm as inquirerConfirm } from "@inquirer/prompts";
23
+ import chalk from "chalk";
24
+ import fs3 from "fs";
25
+ import path3 from "path";
26
+ import os3 from "os";
27
+ import { isAddress as isAddress2 } from "viem";
28
+ import { privateKeyToAccount as privateKeyToAccount3 } from "viem/accounts";
29
+ import {
30
+ getEnvironmentConfig as getEnvironmentConfig2,
31
+ getAvailableEnvironments,
32
+ isEnvironmentAvailable,
33
+ getAllAppsByDeveloper as getAllAppsByDeveloper2,
34
+ getCategoryDescriptions,
35
+ fetchTemplateCatalog,
36
+ PRIMARY_LANGUAGES,
37
+ validateAppName,
38
+ validateImageReference,
39
+ validateFilePath,
40
+ validatePrivateKeyFormat,
41
+ extractAppNameFromImage,
42
+ UserApiClient as UserApiClient2
43
+ } from "@layr-labs/ecloud-sdk";
44
+
45
+ // src/utils/appResolver.ts
46
+ import { isAddress } from "viem";
47
+ import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
48
+ import {
49
+ UserApiClient,
50
+ getAllAppsByDeveloper
51
+ } from "@layr-labs/ecloud-sdk";
52
+
53
+ // src/utils/viemClients.ts
54
+ import {
55
+ createPublicClient,
56
+ http
57
+ } from "viem";
58
+ import { privateKeyToAccount } from "viem/accounts";
59
+ import {
60
+ getEnvironmentConfig,
61
+ addHexPrefix,
62
+ createViemClients as sdkCreateViemClients,
63
+ getChainFromID
64
+ } from "@layr-labs/ecloud-sdk";
65
+ function createViemClients(options) {
66
+ const privateKey = addHexPrefix(options.privateKey);
67
+ const environmentConfig = getEnvironmentConfig(options.environment);
68
+ const rpcUrl = options.rpcUrl || environmentConfig.defaultRPCURL;
69
+ const chain = getChainFromID(environmentConfig.chainID);
70
+ const { publicClient, walletClient } = sdkCreateViemClients({
71
+ privateKey,
72
+ rpcUrl,
73
+ chainId: environmentConfig.chainID
74
+ });
75
+ const account = privateKeyToAccount(privateKey);
76
+ return {
77
+ publicClient,
78
+ walletClient,
79
+ chain,
80
+ address: account.address
81
+ };
82
+ }
83
+
84
+ // src/utils/globalConfig.ts
85
+ import * as fs from "fs";
86
+ import * as path from "path";
87
+ import * as os from "os";
88
+ import { load as loadYaml, dump as dumpYaml } from "js-yaml";
89
+ import { getBuildType } from "@layr-labs/ecloud-sdk";
90
+ import * as crypto from "crypto";
91
+ var GLOBAL_CONFIG_FILE = "config.yaml";
92
+ var PROFILE_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
93
+ function getGlobalConfigDir() {
94
+ const configHome = process.env.XDG_CONFIG_HOME;
95
+ let baseDir;
96
+ if (configHome && path.isAbsolute(configHome)) {
97
+ baseDir = configHome;
98
+ } else {
99
+ baseDir = path.join(os.homedir(), ".config");
100
+ }
101
+ const buildType = getBuildType();
102
+ const buildSuffix = buildType === "dev" ? "-dev" : "";
103
+ const configDirName = `ecloud${buildSuffix}`;
104
+ return path.join(baseDir, configDirName);
105
+ }
106
+ function getGlobalConfigPath() {
107
+ return path.join(getGlobalConfigDir(), GLOBAL_CONFIG_FILE);
108
+ }
109
+ function loadGlobalConfig() {
110
+ const configPath = getGlobalConfigPath();
111
+ if (!fs.existsSync(configPath)) {
112
+ return {
113
+ first_run: true
114
+ };
115
+ }
116
+ try {
117
+ const content = fs.readFileSync(configPath, "utf-8");
118
+ const config = loadYaml(content);
119
+ return config || { first_run: true };
120
+ } catch {
121
+ return {
122
+ first_run: true
123
+ };
124
+ }
125
+ }
126
+ function saveGlobalConfig(config) {
127
+ const configPath = getGlobalConfigPath();
128
+ const configDir = path.dirname(configPath);
129
+ fs.mkdirSync(configDir, { recursive: true, mode: 493 });
130
+ const content = dumpYaml(config, { lineWidth: -1 });
131
+ fs.writeFileSync(configPath, content, { mode: 420 });
132
+ }
133
+ function getDefaultEnvironment() {
134
+ const config = loadGlobalConfig();
135
+ return config.default_environment;
136
+ }
137
+ function getGlobalTelemetryPreference() {
138
+ const config = loadGlobalConfig();
139
+ return config.telemetry_enabled;
140
+ }
141
+ function getOrCreateUserUUID() {
142
+ const config = loadGlobalConfig();
143
+ if (config.user_uuid) {
144
+ return config.user_uuid;
145
+ }
146
+ const uuid = generateUUID();
147
+ config.user_uuid = uuid;
148
+ config.first_run = false;
149
+ saveGlobalConfig(config);
150
+ return uuid;
151
+ }
152
+ function generateUUID() {
153
+ const bytes = crypto.randomBytes(16);
154
+ bytes[6] = bytes[6] & 15 | 64;
155
+ bytes[8] = bytes[8] & 63 | 128;
156
+ const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0"));
157
+ return hex.slice(0, 4).join("") + hex.slice(4, 6).join("") + "-" + hex.slice(6, 8).join("") + "-" + hex.slice(8, 10).join("") + "-" + hex.slice(10, 12).join("") + "-" + hex.slice(12, 16).join("");
158
+ }
159
+
160
+ // src/utils/appNames.ts
161
+ import * as fs2 from "fs";
162
+ import * as path2 from "path";
163
+ import * as os2 from "os";
164
+ import { load as loadYaml2, dump as dumpYaml2 } from "js-yaml";
165
+ var CONFIG_DIR = path2.join(os2.homedir(), ".eigenx");
166
+ var APPS_DIR = path2.join(CONFIG_DIR, "apps");
167
+
168
+ // src/utils/prompts.ts
169
+ async function getPrivateKeyInteractive(privateKey) {
170
+ if (privateKey) {
171
+ if (!validatePrivateKeyFormat(privateKey)) {
172
+ throw new Error("Invalid private key format");
173
+ }
174
+ return privateKey;
175
+ }
176
+ const { getPrivateKeyWithSource: getPrivateKeyWithSource2 } = await import("@layr-labs/ecloud-sdk");
177
+ const result = await getPrivateKeyWithSource2({ privateKey: void 0 });
178
+ if (result) {
179
+ return result.key;
180
+ }
181
+ const key = await password({
182
+ message: "Enter private key:",
183
+ mask: true,
184
+ validate: (value) => {
185
+ if (!value.trim()) {
186
+ return "Private key is required";
187
+ }
188
+ if (!validatePrivateKeyFormat(value)) {
189
+ return "Invalid private key format (must be 64 hex characters, optionally prefixed with 0x)";
190
+ }
191
+ return true;
192
+ }
193
+ });
194
+ return key.trim();
195
+ }
196
+ var MAX_IMAGE_SIZE = 4 * 1024 * 1024;
197
+
198
+ // src/flags.ts
199
+ var commonFlags = {
200
+ environment: Flags.string({
201
+ required: false,
202
+ description: "Deployment environment to use",
203
+ env: "ECLOUD_ENV",
204
+ default: async () => getDefaultEnvironment() || (getBuildType2() === "dev" ? "sepolia-dev" : "sepolia")
205
+ }),
206
+ "private-key": Flags.string({
207
+ required: false,
208
+ description: "Private key for signing transactions",
209
+ env: "ECLOUD_PRIVATE_KEY"
210
+ }),
211
+ "rpc-url": Flags.string({
212
+ required: false,
213
+ description: "RPC URL to connect to blockchain",
214
+ env: "ECLOUD_RPC_URL"
215
+ }),
216
+ verbose: Flags.boolean({
217
+ required: false,
218
+ description: "Enable verbose logging (default: false)",
219
+ default: false
220
+ })
221
+ };
222
+
223
+ // src/client.ts
224
+ import { createWalletClient, custom } from "viem";
225
+ import { privateKeyToAccount as privateKeyToAccount4 } from "viem/accounts";
226
+ async function createBillingClient(flags) {
227
+ const result = await getPrivateKeyWithSource({
228
+ privateKey: flags["private-key"]
229
+ });
230
+ const privateKey = await getPrivateKeyInteractive(result?.key);
231
+ const account = privateKeyToAccount4(addHexPrefix2(privateKey));
232
+ const walletClient = createWalletClient({
233
+ account,
234
+ transport: custom({
235
+ async request() {
236
+ throw new Error("RPC not available - billing uses local signing only");
237
+ }
238
+ })
239
+ });
240
+ return createBillingModule({
241
+ verbose: flags.verbose ?? false,
242
+ walletClient,
243
+ skipTelemetry: true
244
+ // CLI already has telemetry, skip SDK telemetry
245
+ });
246
+ }
247
+
248
+ // src/commands/billing/top-up.ts
249
+ import {
250
+ getEnvironmentConfig as getEnvironmentConfig4,
251
+ USDCCreditsABI,
252
+ ERC20ABI
253
+ } from "@layr-labs/ecloud-sdk";
254
+ import { formatUnits } from "viem";
255
+ import chalk2 from "chalk";
256
+ import { input as input2 } from "@inquirer/prompts";
257
+
258
+ // src/telemetry.ts
259
+ import {
260
+ createTelemetryClient,
261
+ createAppEnvironment,
262
+ createMetricsContext,
263
+ addMetric,
264
+ addMetricWithDimensions,
265
+ emitMetrics,
266
+ getBuildType as getBuildType3
267
+ } from "@layr-labs/ecloud-sdk";
268
+ function createCLITelemetryClient() {
269
+ const userUUID = getOrCreateUserUUID();
270
+ const environment = createAppEnvironment(userUUID);
271
+ const telemetryEnabled = getGlobalTelemetryPreference();
272
+ return createTelemetryClient(environment, "ecloud-cli", {
273
+ telemetryEnabled: telemetryEnabled !== false
274
+ // Enabled by default, disabled only if explicitly set to false
275
+ });
276
+ }
277
+ async function withTelemetry(command, action) {
278
+ const client = createCLITelemetryClient();
279
+ const metrics = createMetricsContext();
280
+ metrics.properties["source"] = "ecloud-cli";
281
+ metrics.properties["command"] = command.id || command.constructor.name;
282
+ const environment = getDefaultEnvironment() || "sepolia";
283
+ metrics.properties["environment"] = environment;
284
+ const buildType = getBuildType3() || "prod";
285
+ metrics.properties["build_type"] = buildType;
286
+ const cliVersion = command.config.version;
287
+ if (cliVersion) {
288
+ metrics.properties["cli_version"] = cliVersion;
289
+ }
290
+ addMetric(metrics, "Count", 1);
291
+ let actionError;
292
+ let result;
293
+ try {
294
+ result = await action();
295
+ return result;
296
+ } catch (err) {
297
+ actionError = err instanceof Error ? err : new Error(String(err));
298
+ throw err;
299
+ } finally {
300
+ const resultValue = actionError ? "Failure" : "Success";
301
+ const dimensions = {};
302
+ if (actionError) {
303
+ dimensions["error"] = actionError.message;
304
+ }
305
+ addMetricWithDimensions(metrics, resultValue, 1, dimensions);
306
+ const duration = Date.now() - metrics.startTime.getTime();
307
+ addMetric(metrics, "DurationMilliseconds", duration);
308
+ try {
309
+ await emitMetrics(client, metrics);
310
+ await client.close();
311
+ } catch {
312
+ }
313
+ }
314
+ }
315
+
316
+ // src/commands/billing/top-up.ts
317
+ var POLL_INTERVAL_MS = 5e3;
318
+ var POLL_TIMEOUT_MS = 3 * 60 * 1e3;
319
+ var BillingTopUp = class _BillingTopUp extends Command {
320
+ static description = "Purchase EigenCompute credits with USDC";
321
+ static flags = {
322
+ ...commonFlags,
323
+ amount: Flags2.string({
324
+ required: false,
325
+ description: "Amount of USDC to spend (e.g., '50')"
326
+ }),
327
+ account: Flags2.string({
328
+ required: false,
329
+ description: "Target account address for purchaseCreditsFor (defaults to your wallet)"
330
+ }),
331
+ product: Flags2.string({
332
+ required: false,
333
+ description: "Product ID",
334
+ default: "compute",
335
+ options: ["compute"],
336
+ env: "ECLOUD_PRODUCT_ID"
337
+ })
338
+ };
339
+ async run() {
340
+ return withTelemetry(this, async () => {
341
+ const { flags } = await this.parse(_BillingTopUp);
342
+ const billing = await createBillingClient(flags);
343
+ const privateKey = await getPrivateKeyInteractive(flags["private-key"]);
344
+ const { publicClient, walletClient, address: walletAddress } = createViemClients({
345
+ privateKey,
346
+ rpcUrl: flags["rpc-url"],
347
+ environment: flags.environment
348
+ });
349
+ const environmentConfig = getEnvironmentConfig4(flags.environment);
350
+ const usdcCreditsAddress = environmentConfig.usdcCreditsAddress;
351
+ if (!usdcCreditsAddress) {
352
+ this.error(
353
+ `USDCCredits contract is not configured for environment "${flags.environment}". USDC credit purchasing is only available on environments with a deployed USDCCredits contract.`
354
+ );
355
+ }
356
+ const targetAccount = flags.account ?? walletAddress;
357
+ this.log(`
358
+ ${chalk2.bold("Purchase EigenCompute credits")}`);
359
+ this.log(`${chalk2.gray("\u2500".repeat(45))}`);
360
+ this.log(`
361
+ ${chalk2.bold("Wallet:")} ${walletAddress}`);
362
+ if (targetAccount !== walletAddress) {
363
+ this.log(` ${chalk2.bold("Target:")} ${targetAccount}`);
364
+ }
365
+ let currentCredits;
366
+ try {
367
+ const status = await billing.getStatus({
368
+ productId: flags.product
369
+ });
370
+ currentCredits = status.remainingCredits;
371
+ if (currentCredits !== void 0) {
372
+ this.log(` ${chalk2.bold("Credits:")} ${chalk2.cyan(`$${currentCredits.toFixed(2)}`)}`);
373
+ }
374
+ } catch {
375
+ this.debug("Could not fetch current credit balance");
376
+ }
377
+ const usdcAddress = await publicClient.readContract({
378
+ address: usdcCreditsAddress,
379
+ abi: USDCCreditsABI,
380
+ functionName: "usdc"
381
+ });
382
+ const minimumPurchase = await publicClient.readContract({
383
+ address: usdcCreditsAddress,
384
+ abi: USDCCreditsABI,
385
+ functionName: "minimumPurchase"
386
+ });
387
+ const usdcBalance = await publicClient.readContract({
388
+ address: usdcAddress,
389
+ abi: ERC20ABI,
390
+ functionName: "balanceOf",
391
+ args: [walletAddress]
392
+ });
393
+ const balanceFormatted = formatUnits(usdcBalance, 6);
394
+ this.log(` ${chalk2.bold("USDC:")} ${balanceFormatted} USDC`);
395
+ if (usdcBalance === BigInt(0)) {
396
+ this.log(`
397
+ ${chalk2.yellow(" No USDC in wallet.")}`);
398
+ this.log(` Send USDC on Sepolia to: ${chalk2.cyan(walletAddress)}`);
399
+ this.log(` Then re-run: ${chalk2.cyan("ecloud billing top-up")}
400
+ `);
401
+ return;
402
+ }
403
+ const minimumFormatted = formatUnits(minimumPurchase, 6);
404
+ const amountStr = flags.amount ?? await input2({
405
+ message: `How much USDC to spend on credits? (minimum: ${minimumFormatted})`,
406
+ validate: (val) => {
407
+ const n = parseFloat(val);
408
+ if (isNaN(n) || n <= 0) return "Enter a positive number";
409
+ const raw = BigInt(Math.round(n * 1e6));
410
+ if (raw < minimumPurchase)
411
+ return `Minimum purchase is ${minimumFormatted} USDC`;
412
+ if (raw > usdcBalance)
413
+ return `Insufficient balance. You have ${balanceFormatted} USDC`;
414
+ return true;
415
+ }
416
+ });
417
+ const amountFloat = parseFloat(amountStr);
418
+ const amountRaw = BigInt(Math.round(amountFloat * 1e6));
419
+ if (amountRaw < minimumPurchase) {
420
+ this.error(`Minimum purchase is ${minimumFormatted} USDC`);
421
+ }
422
+ if (amountRaw > usdcBalance) {
423
+ this.error(
424
+ `Insufficient USDC balance. You have ${balanceFormatted} USDC but requested ${amountFloat.toFixed(2)}`
425
+ );
426
+ }
427
+ this.log(`
428
+ Purchasing ${chalk2.bold(`$${amountFloat.toFixed(2)}`)} in credits...`);
429
+ const currentAllowance = await publicClient.readContract({
430
+ address: usdcAddress,
431
+ abi: ERC20ABI,
432
+ functionName: "allowance",
433
+ args: [walletAddress, usdcCreditsAddress]
434
+ });
435
+ if (currentAllowance < amountRaw) {
436
+ this.log(chalk2.gray(" Approving USDC spend..."));
437
+ const approveTx = await walletClient.writeContract({
438
+ address: usdcAddress,
439
+ abi: ERC20ABI,
440
+ functionName: "approve",
441
+ args: [usdcCreditsAddress, amountRaw],
442
+ chain: walletClient.chain,
443
+ account: walletClient.account
444
+ });
445
+ await publicClient.waitForTransactionReceipt({ hash: approveTx });
446
+ this.log(` ${chalk2.green("\u2713")} Approved`);
447
+ }
448
+ this.log(chalk2.gray(" Submitting credit purchase..."));
449
+ const purchaseTx = await walletClient.writeContract({
450
+ address: usdcCreditsAddress,
451
+ abi: USDCCreditsABI,
452
+ functionName: "purchaseCreditsFor",
453
+ args: [amountRaw, targetAccount],
454
+ chain: walletClient.chain,
455
+ account: walletClient.account
456
+ });
457
+ const receipt = await publicClient.waitForTransactionReceipt({ hash: purchaseTx });
458
+ this.log(` ${chalk2.green("\u2713")} Transaction confirmed: ${receipt.transactionHash}`);
459
+ this.log(chalk2.gray("\n Waiting for credits to appear..."));
460
+ const startTime = Date.now();
461
+ while (Date.now() - startTime < POLL_TIMEOUT_MS) {
462
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
463
+ try {
464
+ const status = await billing.getStatus({
465
+ productId: flags.product
466
+ });
467
+ if (status.remainingCredits !== void 0 && (currentCredits === void 0 || status.remainingCredits > currentCredits)) {
468
+ this.log(
469
+ `
470
+ ${chalk2.green("\u2713")} Credits received! Balance: ${chalk2.cyan(`$${status.remainingCredits.toFixed(2)}`)}`
471
+ );
472
+ this.log();
473
+ return;
474
+ }
475
+ } catch {
476
+ this.debug("Error polling for credit balance");
477
+ }
478
+ }
479
+ this.log(
480
+ `
481
+ ${chalk2.yellow("\u26A0")} Credits haven't appeared yet. This can take a few minutes.`
482
+ );
483
+ this.log(` ${chalk2.gray("Check your balance:")} ecloud billing status
484
+ `);
485
+ });
486
+ }
487
+ };
488
+ export {
489
+ BillingTopUp as default
490
+ };
491
+ //# sourceMappingURL=top-up.js.map