@layr-labs/ecloud-cli 0.1.0-dev.3 → 0.1.0-rc.2

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 (53) hide show
  1. package/README.md +4 -4
  2. package/VERSION +2 -2
  3. package/dist/commands/auth/generate.js +46 -184
  4. package/dist/commands/auth/generate.js.map +1 -1
  5. package/dist/commands/auth/login.js +93 -234
  6. package/dist/commands/auth/login.js.map +1 -1
  7. package/dist/commands/auth/logout.js +30 -170
  8. package/dist/commands/auth/logout.js.map +1 -1
  9. package/dist/commands/auth/migrate.js +76 -216
  10. package/dist/commands/auth/migrate.js.map +1 -1
  11. package/dist/commands/auth/whoami.js +17 -145
  12. package/dist/commands/auth/whoami.js.map +1 -1
  13. package/dist/commands/billing/cancel.js +30 -164
  14. package/dist/commands/billing/cancel.js.map +1 -1
  15. package/dist/commands/billing/status.js +80 -213
  16. package/dist/commands/billing/status.js.map +1 -1
  17. package/dist/commands/billing/subscribe.js +45 -179
  18. package/dist/commands/billing/subscribe.js.map +1 -1
  19. package/dist/commands/compute/app/create.js +20 -148
  20. package/dist/commands/compute/app/create.js.map +1 -1
  21. package/dist/commands/compute/app/deploy.js +145 -243
  22. package/dist/commands/compute/app/deploy.js.map +1 -1
  23. package/dist/commands/compute/app/info.js +1 -2
  24. package/dist/commands/compute/app/info.js.map +1 -1
  25. package/dist/commands/compute/app/list.js +111 -194
  26. package/dist/commands/compute/app/list.js.map +1 -1
  27. package/dist/commands/compute/app/logs.js +20 -105
  28. package/dist/commands/compute/app/logs.js.map +1 -1
  29. package/dist/commands/compute/app/profile/set.js +64 -153
  30. package/dist/commands/compute/app/profile/set.js.map +1 -1
  31. package/dist/commands/compute/app/start.js +43 -132
  32. package/dist/commands/compute/app/start.js.map +1 -1
  33. package/dist/commands/compute/app/stop.js +43 -132
  34. package/dist/commands/compute/app/stop.js.map +1 -1
  35. package/dist/commands/compute/app/terminate.js +44 -131
  36. package/dist/commands/compute/app/terminate.js.map +1 -1
  37. package/dist/commands/compute/app/upgrade.js +108 -209
  38. package/dist/commands/compute/app/upgrade.js.map +1 -1
  39. package/dist/commands/compute/environment/list.js +12 -104
  40. package/dist/commands/compute/environment/list.js.map +1 -1
  41. package/dist/commands/compute/environment/set.js +18 -103
  42. package/dist/commands/compute/environment/set.js.map +1 -1
  43. package/dist/commands/compute/environment/show.js +30 -122
  44. package/dist/commands/compute/environment/show.js.map +1 -1
  45. package/dist/commands/compute/undelegate.js +18 -112
  46. package/dist/commands/compute/undelegate.js.map +1 -1
  47. package/dist/commands/upgrade.js +19 -159
  48. package/dist/commands/upgrade.js.map +1 -1
  49. package/dist/commands/version.js +23 -163
  50. package/dist/commands/version.js.map +1 -1
  51. package/package.json +2 -2
  52. package/dist/commands/telemetry.js +0 -213
  53. package/dist/commands/telemetry.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/commands/auth/logout.ts","../../../src/telemetry.ts","../../../src/utils/globalConfig.ts"],"sourcesContent":["/**\n * Auth Logout Command\n *\n * Remove private key from OS keyring\n */\n\nimport { Command, Flags } from \"@oclif/core\";\nimport { confirm } from \"@inquirer/prompts\";\nimport { deletePrivateKey, getPrivateKey, getAddressFromPrivateKey } from \"@layr-labs/ecloud-sdk\";\nimport { withTelemetry } from \"../../telemetry\";\n\nexport default class AuthLogout extends Command {\n static description = \"Remove private key from OS keyring\";\n\n static examples = [\n \"<%= config.bin %> <%= command.id %>\",\n \"<%= config.bin %> <%= command.id %> --force\",\n ];\n\n static flags = {\n force: Flags.boolean({\n description: \"Skip confirmation prompt\",\n default: false,\n }),\n };\n\n async run(): Promise<void> {\n return withTelemetry(this, async () => {\n const { flags } = await this.parse(AuthLogout);\n\n // Check if key exists\n const privateKey = await getPrivateKey();\n\n if (!privateKey) {\n this.log(\"No key found in keyring\");\n this.log(\"\\nNothing to remove.\");\n return;\n }\n\n // Show address\n const address = getAddressFromPrivateKey(privateKey);\n this.log(\"Found stored key:\");\n this.log(` Address: ${address}`);\n this.log(\"\");\n\n // Confirm unless forced\n if (!flags.force) {\n const confirmed = await confirm({\n message: \"Remove private key from keyring?\",\n default: false,\n });\n\n if (!confirmed) {\n this.log(\"Logout cancelled\");\n return;\n }\n }\n\n // Remove from keyring\n try {\n const deleted = await deletePrivateKey();\n\n if (deleted) {\n this.log(\"\\n✓ Successfully removed key from keyring\");\n this.log(\"\\nYou will need to provide --private-key flag for future commands,\");\n this.log(\"or run 'ecloud auth login' to store a key again.\");\n } else {\n this.log(\"\\nFailed to remove key (it may have already been removed)\");\n }\n } catch (err: any) {\n this.error(`Failed to remove key: ${err.message}`);\n }\n });\n }\n}\n","/**\n * Telemetry utilities for CLI commands\n *\n * Provides helpers to wrap command execution with telemetry tracking\n */\n\nimport {\n createTelemetryClient,\n createAppEnvironment,\n createMetricsContext,\n addMetric,\n addMetricWithDimensions,\n emitMetrics,\n type TelemetryClient,\n getBuildType,\n} from \"@layr-labs/ecloud-sdk\";\nimport { Command } from \"@oclif/core\";\nimport {\n getDefaultEnvironment,\n getOrCreateUserUUID,\n getGlobalTelemetryPreference,\n} from \"./utils/globalConfig\";\n\n/**\n * Create a telemetry client for CLI usage\n */\nexport function createCLITelemetryClient(): TelemetryClient {\n // Get user UUID from CLI's globalConfig (handles I/O)\n const userUUID = getOrCreateUserUUID();\n const environment = createAppEnvironment(userUUID);\n\n // Get telemetry preference from CLI's globalConfig\n const telemetryEnabled = getGlobalTelemetryPreference();\n\n return createTelemetryClient(environment, \"ecloud-cli\", {\n telemetryEnabled: telemetryEnabled === true, // Only enabled if explicitly set to true\n });\n}\n\n/**\n * Wrap a command execution with telemetry\n *\n * @param command - The CLI command instance\n * @param action - The command action to execute\n * @returns The result of the action\n */\nexport async function withTelemetry<T>(command: Command, action: () => Promise<T>): Promise<T> {\n const client = createCLITelemetryClient();\n const metrics = createMetricsContext();\n\n // Set source to identify CLI usage\n metrics.properties[\"source\"] = \"ecloud-cli\";\n\n // Set command name in properties\n metrics.properties[\"command\"] = command.id || command.constructor.name;\n\n // Set environment in properties\n const environment = getDefaultEnvironment() || \"sepolia\";\n metrics.properties[\"environment\"] = environment;\n\n // Set buildType in properties\n const buildType = getBuildType() || \"prod\";\n metrics.properties[\"build_type\"] = buildType;\n\n // Set CLI version if available\n const cliVersion = command.config.version;\n if (cliVersion) {\n metrics.properties[\"cli_version\"] = cliVersion;\n }\n\n // Add initial count metric\n addMetric(metrics, \"Count\", 1);\n\n let actionError: Error | undefined;\n let result: T;\n\n try {\n result = await action();\n return result;\n } catch (err) {\n actionError = err instanceof Error ? err : new Error(String(err));\n throw err;\n } finally {\n // Add result metric\n const resultValue = actionError ? \"Failure\" : \"Success\";\n const dimensions: Record<string, string> = {};\n if (actionError) {\n dimensions[\"error\"] = actionError.message;\n }\n addMetricWithDimensions(metrics, resultValue, 1, dimensions);\n\n // Add duration metric\n const duration = Date.now() - metrics.startTime.getTime();\n addMetric(metrics, \"DurationMilliseconds\", duration);\n\n // Emit all metrics\n try {\n await emitMetrics(client, metrics);\n await client.close();\n } catch {\n // Silently ignore telemetry errors\n }\n }\n}\n","/**\n * Global configuration management\n *\n * Stores user-level configuration that persists across all CLI usage.\n * - $XDG_CONFIG_HOME/ecloud[BuildSuffix]/config.yaml (if XDG_CONFIG_HOME is set)\n * - Or ~/.config/ecloud[BuildSuffix]/config.yaml (fallback)\n *\n * Where BuildSuffix is:\n * - \"\" (empty) for production builds\n * - \"-dev\" for development builds\n */\n\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport * as os from \"os\";\nimport { load as loadYaml, dump as dumpYaml } from \"js-yaml\";\nimport { getBuildType } from \"@layr-labs/ecloud-sdk\";\nimport * as crypto from \"crypto\";\nconst GLOBAL_CONFIG_FILE = \"config.yaml\";\n\nexport interface ProfileCacheEntry {\n updated_at: number; // Unix timestamp in milliseconds\n profiles: { [appId: string]: string }; // appId -> profile name\n}\n\nexport interface GlobalConfig {\n first_run?: boolean;\n telemetry_enabled?: boolean;\n user_uuid?: string;\n default_environment?: string;\n last_version_check?: number;\n last_known_version?: string;\n profile_cache?: {\n [environment: string]: ProfileCacheEntry;\n };\n}\n\n// Profile cache TTL: 24 hours in milliseconds\nconst PROFILE_CACHE_TTL_MS = 24 * 60 * 60 * 1000;\n\n/**\n * Get the XDG-compliant directory where global ecloud config should be stored\n */\nfunction getGlobalConfigDir(): string {\n // First check XDG_CONFIG_HOME\n const configHome = process.env.XDG_CONFIG_HOME;\n\n let baseDir: string;\n if (configHome && path.isAbsolute(configHome)) {\n baseDir = configHome;\n } else {\n // Fall back to ~/.config\n baseDir = path.join(os.homedir(), \".config\");\n }\n\n // Use environment-specific config directory\n const buildType = getBuildType();\n const buildSuffix = buildType === \"dev\" ? \"-dev\" : \"\";\n const configDirName = `ecloud${buildSuffix}`;\n\n return path.join(baseDir, configDirName);\n}\n\n/**\n * Get the full path to the global config file\n */\nfunction getGlobalConfigPath(): string {\n return path.join(getGlobalConfigDir(), GLOBAL_CONFIG_FILE);\n}\n\n/**\n * Load global configuration, creating defaults if needed\n */\nexport function loadGlobalConfig(): GlobalConfig {\n const configPath = getGlobalConfigPath();\n\n // If file doesn't exist, return defaults for first run\n if (!fs.existsSync(configPath)) {\n return {\n first_run: true,\n };\n }\n\n try {\n const content = fs.readFileSync(configPath, \"utf-8\");\n const config = loadYaml(content) as GlobalConfig;\n return config || { first_run: true };\n } catch {\n // If parsing fails, return defaults\n return {\n first_run: true,\n };\n }\n}\n\n/**\n * Save global configuration to disk\n */\nexport function saveGlobalConfig(config: GlobalConfig): void {\n const configPath = getGlobalConfigPath();\n\n // Ensure directory exists\n const configDir = path.dirname(configPath);\n fs.mkdirSync(configDir, { recursive: true, mode: 0o755 });\n\n // Write config file\n const content = dumpYaml(config, { lineWidth: -1 });\n fs.writeFileSync(configPath, content, { mode: 0o644 });\n}\n\n/**\n * Get the user's preferred deployment environment\n */\nexport function getDefaultEnvironment(): string | undefined {\n const config = loadGlobalConfig();\n return config.default_environment;\n}\n\n/**\n * Set the user's preferred deployment environment\n */\nexport function setDefaultEnvironment(environment: string): void {\n const config = loadGlobalConfig();\n config.default_environment = environment;\n config.first_run = false; // No longer first run after setting environment\n saveGlobalConfig(config);\n}\n\n/**\n * Check if this is the user's first time running the CLI\n */\nexport function isFirstRun(): boolean {\n const config = loadGlobalConfig();\n return config.first_run === true;\n}\n\n/**\n * Mark that the first run has been completed\n */\nexport function markFirstRunComplete(): void {\n const config = loadGlobalConfig();\n config.first_run = false;\n saveGlobalConfig(config);\n}\n\n/**\n * Get the global telemetry preference\n */\nexport function getGlobalTelemetryPreference(): boolean | undefined {\n const config = loadGlobalConfig();\n return config.telemetry_enabled;\n}\n\n/**\n * Set the global telemetry preference\n */\nexport function setGlobalTelemetryPreference(enabled: boolean): void {\n const config = loadGlobalConfig();\n config.telemetry_enabled = enabled;\n config.first_run = false; // No longer first run after setting preference\n saveGlobalConfig(config);\n}\n\n// ==================== Profile Cache Functions ====================\n\n/**\n * Get cached profile names for an environment\n * Returns null if cache is missing or expired (older than 24 hours)\n */\nexport function getProfileCache(environment: string): Record<string, string> | null {\n const config = loadGlobalConfig();\n const cacheEntry = config.profile_cache?.[environment];\n\n if (!cacheEntry) {\n return null;\n }\n\n // Check if cache is expired\n const now = Date.now();\n if (now - cacheEntry.updated_at > PROFILE_CACHE_TTL_MS) {\n return null;\n }\n\n return cacheEntry.profiles;\n}\n\n/**\n * Set cached profile names for an environment\n */\nexport function setProfileCache(environment: string, profiles: Record<string, string>): void {\n const config = loadGlobalConfig();\n\n if (!config.profile_cache) {\n config.profile_cache = {};\n }\n\n config.profile_cache[environment] = {\n updated_at: Date.now(),\n profiles,\n };\n\n saveGlobalConfig(config);\n}\n\n/**\n * Invalidate profile cache for a specific environment or all environments\n */\nexport function invalidateProfileCache(environment?: string): void {\n const config = loadGlobalConfig();\n\n if (!config.profile_cache) {\n return;\n }\n\n if (environment) {\n // Invalidate specific environment\n delete config.profile_cache[environment];\n } else {\n // Invalidate all environments\n config.profile_cache = {};\n }\n\n saveGlobalConfig(config);\n}\n\n/**\n * Update a single profile name in the cache\n * This is useful after deploy or profile set to update just one entry\n */\nexport function updateProfileCacheEntry(\n environment: string,\n appId: string,\n profileName: string,\n): void {\n const config = loadGlobalConfig();\n\n if (!config.profile_cache) {\n config.profile_cache = {};\n }\n\n if (!config.profile_cache[environment]) {\n config.profile_cache[environment] = {\n updated_at: Date.now(),\n profiles: {},\n };\n }\n\n // Normalize appId to lowercase for consistent lookups\n const normalizedAppId = appId.toLowerCase();\n config.profile_cache[environment].profiles[normalizedAppId] = profileName;\n config.profile_cache[environment].updated_at = Date.now();\n\n saveGlobalConfig(config);\n}\n\n/**\n * Get the user UUID from global config, or generate a new one if it doesn't exist\n */\nexport function getOrCreateUserUUID(): string {\n const config = loadGlobalConfig();\n if (config.user_uuid) {\n return config.user_uuid;\n }\n\n // Generate a new UUID (v4)\n const uuid = generateUUID();\n\n // Save it to config\n config.user_uuid = uuid;\n config.first_run = false;\n saveGlobalConfig(config);\n\n return uuid;\n}\n\n/**\n * Generate a UUID v4\n */\nfunction generateUUID(): string {\n // UUID v4 format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\n // Use cryptographically secure random values.\n const bytes = crypto.randomBytes(16);\n // Per RFC 4122 section 4.4, set bits for version and `clock_seq_hi_and_reserved`\n bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4\n bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant 10\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\"));\n return (\n hex.slice(0, 4).join(\"\") +\n hex.slice(4, 6).join(\"\") +\n \"-\" +\n hex.slice(6, 8).join(\"\") +\n \"-\" +\n hex.slice(8, 10).join(\"\") +\n \"-\" +\n hex.slice(10, 12).join(\"\") +\n \"-\" +\n hex.slice(12, 16).join(\"\")\n );\n}\n\n/**\n * Save user UUID to global config (preserves existing UUID if present)\n */\nexport function saveUserUUID(userUUID: string): void {\n const config = loadGlobalConfig();\n // Only update if not already set\n if (!config.user_uuid) {\n config.user_uuid = userUUID;\n saveGlobalConfig(config);\n }\n}\n"],"mappings":"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","names":["getBuildType","getBuildType"]}
1
+ {"version":3,"sources":["../../../src/commands/auth/logout.ts"],"sourcesContent":["/**\n * Auth Logout Command\n *\n * Remove private key from OS keyring\n */\n\nimport { Command, Flags } from \"@oclif/core\";\nimport { confirm } from \"@inquirer/prompts\";\nimport { deletePrivateKey, getPrivateKey, getAddressFromPrivateKey } from \"@layr-labs/ecloud-sdk\";\n\nexport default class AuthLogout extends Command {\n static description = \"Remove private key from OS keyring\";\n\n static examples = [\n \"<%= config.bin %> <%= command.id %>\",\n \"<%= config.bin %> <%= command.id %> --force\",\n ];\n\n static flags = {\n force: Flags.boolean({\n description: \"Skip confirmation prompt\",\n default: false,\n }),\n };\n\n async run(): Promise<void> {\n const { flags } = await this.parse(AuthLogout);\n\n // Check if key exists\n const privateKey = await getPrivateKey();\n\n if (!privateKey) {\n this.log(\"No key found in keyring\");\n this.log(\"\\nNothing to remove.\");\n return;\n }\n\n // Show address\n const address = getAddressFromPrivateKey(privateKey);\n this.log(\"Found stored key:\");\n this.log(` Address: ${address}`);\n this.log(\"\");\n\n // Confirm unless forced\n if (!flags.force) {\n const confirmed = await confirm({\n message: \"Remove private key from keyring?\",\n default: false,\n });\n\n if (!confirmed) {\n this.log(\"Logout cancelled\");\n return;\n }\n }\n\n // Remove from keyring\n try {\n const deleted = await deletePrivateKey();\n\n if (deleted) {\n this.log(\"\\n✓ Successfully removed key from keyring\");\n this.log(\"\\nYou will need to provide --private-key flag for future commands,\");\n this.log(\"or run 'ecloud auth login' to store a key again.\");\n } else {\n this.log(\"\\nFailed to remove key (it may have already been removed)\");\n }\n } catch (err: any) {\n this.error(`Failed to remove key: ${err.message}`);\n }\n }\n}\n"],"mappings":";;;AAMA,SAAS,SAAS,aAAa;AAC/B,SAAS,eAAe;AACxB,SAAS,kBAAkB,eAAe,gCAAgC;AAE1E,IAAqB,aAArB,MAAqB,oBAAmB,QAAQ;AAAA,EAC9C,OAAO,cAAc;AAAA,EAErB,OAAO,WAAW;AAAA,IAChB;AAAA,IACA;AAAA,EACF;AAAA,EAEA,OAAO,QAAQ;AAAA,IACb,OAAO,MAAM,QAAQ;AAAA,MACnB,aAAa;AAAA,MACb,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,MAAqB;AACzB,UAAM,EAAE,MAAM,IAAI,MAAM,KAAK,MAAM,WAAU;AAG7C,UAAM,aAAa,MAAM,cAAc;AAEvC,QAAI,CAAC,YAAY;AACf,WAAK,IAAI,yBAAyB;AAClC,WAAK,IAAI,sBAAsB;AAC/B;AAAA,IACF;AAGA,UAAM,UAAU,yBAAyB,UAAU;AACnD,SAAK,IAAI,mBAAmB;AAC5B,SAAK,IAAI,cAAc,OAAO,EAAE;AAChC,SAAK,IAAI,EAAE;AAGX,QAAI,CAAC,MAAM,OAAO;AAChB,YAAM,YAAY,MAAM,QAAQ;AAAA,QAC9B,SAAS;AAAA,QACT,SAAS;AAAA,MACX,CAAC;AAED,UAAI,CAAC,WAAW;AACd,aAAK,IAAI,kBAAkB;AAC3B;AAAA,MACF;AAAA,IACF;AAGA,QAAI;AACF,YAAM,UAAU,MAAM,iBAAiB;AAEvC,UAAI,SAAS;AACX,aAAK,IAAI,gDAA2C;AACpD,aAAK,IAAI,oEAAoE;AAC7E,aAAK,IAAI,kDAAkD;AAAA,MAC7D,OAAO;AACL,aAAK,IAAI,2DAA2D;AAAA,MACtE;AAAA,IACF,SAAS,KAAU;AACjB,WAAK,MAAM,yBAAyB,IAAI,OAAO,EAAE;AAAA,IACnD;AAAA,EACF;AACF;","names":[]}
@@ -28,234 +28,94 @@ function displayWarning(lines) {
28
28
  console.log("");
29
29
  }
30
30
 
31
- // src/telemetry.ts
32
- import {
33
- createTelemetryClient,
34
- createAppEnvironment,
35
- createMetricsContext,
36
- addMetric,
37
- addMetricWithDimensions,
38
- emitMetrics,
39
- getBuildType as getBuildType2
40
- } from "@layr-labs/ecloud-sdk";
41
-
42
- // src/utils/globalConfig.ts
43
- import * as fs from "fs";
44
- import * as path from "path";
45
- import * as os from "os";
46
- import { load as loadYaml, dump as dumpYaml } from "js-yaml";
47
- import { getBuildType } from "@layr-labs/ecloud-sdk";
48
- import * as crypto from "crypto";
49
- var GLOBAL_CONFIG_FILE = "config.yaml";
50
- var PROFILE_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
51
- function getGlobalConfigDir() {
52
- const configHome = process.env.XDG_CONFIG_HOME;
53
- let baseDir;
54
- if (configHome && path.isAbsolute(configHome)) {
55
- baseDir = configHome;
56
- } else {
57
- baseDir = path.join(os.homedir(), ".config");
58
- }
59
- const buildType = getBuildType();
60
- const buildSuffix = buildType === "dev" ? "-dev" : "";
61
- const configDirName = `ecloud${buildSuffix}`;
62
- return path.join(baseDir, configDirName);
63
- }
64
- function getGlobalConfigPath() {
65
- return path.join(getGlobalConfigDir(), GLOBAL_CONFIG_FILE);
66
- }
67
- function loadGlobalConfig() {
68
- const configPath = getGlobalConfigPath();
69
- if (!fs.existsSync(configPath)) {
70
- return {
71
- first_run: true
72
- };
73
- }
74
- try {
75
- const content = fs.readFileSync(configPath, "utf-8");
76
- const config = loadYaml(content);
77
- return config || { first_run: true };
78
- } catch {
79
- return {
80
- first_run: true
81
- };
82
- }
83
- }
84
- function saveGlobalConfig(config) {
85
- const configPath = getGlobalConfigPath();
86
- const configDir = path.dirname(configPath);
87
- fs.mkdirSync(configDir, { recursive: true, mode: 493 });
88
- const content = dumpYaml(config, { lineWidth: -1 });
89
- fs.writeFileSync(configPath, content, { mode: 420 });
90
- }
91
- function getDefaultEnvironment() {
92
- const config = loadGlobalConfig();
93
- return config.default_environment;
94
- }
95
- function getGlobalTelemetryPreference() {
96
- const config = loadGlobalConfig();
97
- return config.telemetry_enabled;
98
- }
99
- function getOrCreateUserUUID() {
100
- const config = loadGlobalConfig();
101
- if (config.user_uuid) {
102
- return config.user_uuid;
103
- }
104
- const uuid = generateUUID();
105
- config.user_uuid = uuid;
106
- config.first_run = false;
107
- saveGlobalConfig(config);
108
- return uuid;
109
- }
110
- function generateUUID() {
111
- const bytes = crypto.randomBytes(16);
112
- bytes[6] = bytes[6] & 15 | 64;
113
- bytes[8] = bytes[8] & 63 | 128;
114
- const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0"));
115
- 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("");
116
- }
117
-
118
- // src/telemetry.ts
119
- function createCLITelemetryClient() {
120
- const userUUID = getOrCreateUserUUID();
121
- const environment = createAppEnvironment(userUUID);
122
- const telemetryEnabled = getGlobalTelemetryPreference();
123
- return createTelemetryClient(environment, "ecloud-cli", {
124
- telemetryEnabled: telemetryEnabled === true
125
- // Only enabled if explicitly set to true
126
- });
127
- }
128
- async function withTelemetry(command, action) {
129
- const client = createCLITelemetryClient();
130
- const metrics = createMetricsContext();
131
- metrics.properties["source"] = "ecloud-cli";
132
- metrics.properties["command"] = command.id || command.constructor.name;
133
- const environment = getDefaultEnvironment() || "sepolia";
134
- metrics.properties["environment"] = environment;
135
- const buildType = getBuildType2() || "prod";
136
- metrics.properties["build_type"] = buildType;
137
- const cliVersion = command.config.version;
138
- if (cliVersion) {
139
- metrics.properties["cli_version"] = cliVersion;
140
- }
141
- addMetric(metrics, "Count", 1);
142
- let actionError;
143
- let result;
144
- try {
145
- result = await action();
146
- return result;
147
- } catch (err) {
148
- actionError = err instanceof Error ? err : new Error(String(err));
149
- throw err;
150
- } finally {
151
- const resultValue = actionError ? "Failure" : "Success";
152
- const dimensions = {};
153
- if (actionError) {
154
- dimensions["error"] = actionError.message;
155
- }
156
- addMetricWithDimensions(metrics, resultValue, 1, dimensions);
157
- const duration = Date.now() - metrics.startTime.getTime();
158
- addMetric(metrics, "DurationMilliseconds", duration);
159
- try {
160
- await emitMetrics(client, metrics);
161
- await client.close();
162
- } catch {
163
- }
164
- }
165
- }
166
-
167
31
  // src/commands/auth/migrate.ts
168
32
  var AuthMigrate = class extends Command {
169
33
  static description = "Migrate a private key from eigenx-cli to ecloud";
170
34
  static examples = ["<%= config.bin %> <%= command.id %>"];
171
35
  async run() {
172
- return withTelemetry(this, async () => {
173
- const legacyKeys = await getLegacyKeys();
174
- if (legacyKeys.length === 0) {
175
- this.log("No legacy keys found from eigenx-cli.");
176
- this.log("");
177
- this.log("To manually add a key to ecloud, use:");
178
- this.log(" ecloud auth login");
36
+ const legacyKeys = await getLegacyKeys();
37
+ if (legacyKeys.length === 0) {
38
+ this.log("No legacy keys found from eigenx-cli.");
39
+ this.log("");
40
+ this.log("To manually add a key to ecloud, use:");
41
+ this.log(" ecloud auth login");
42
+ return;
43
+ }
44
+ this.log("\nFound legacy keys from eigenx-cli:");
45
+ this.log("");
46
+ for (const key of legacyKeys) {
47
+ this.log(` Address: ${key.address}`);
48
+ this.log(` Environment: ${key.environment}`);
49
+ this.log(` Source: ${key.source}`);
50
+ this.log("");
51
+ }
52
+ const choices = legacyKeys.map((key) => ({
53
+ name: `${key.address} (${key.environment} - ${key.source})`,
54
+ value: key
55
+ }));
56
+ const selectedKey = await select2({
57
+ message: "Select a key to migrate:",
58
+ choices
59
+ });
60
+ const privateKey = await getLegacyPrivateKey(selectedKey.environment, selectedKey.source);
61
+ if (!privateKey) {
62
+ this.error(`Failed to retrieve legacy key for ${selectedKey.environment}`);
63
+ }
64
+ const address = getAddressFromPrivateKey(privateKey);
65
+ this.log(`
66
+ Migrating key: ${address}`);
67
+ this.log(`From: ${selectedKey.source}:${selectedKey.environment}`);
68
+ const exists = await keyExists();
69
+ if (exists) {
70
+ this.log("");
71
+ displayWarning([
72
+ "WARNING: A private key for ecloud already exists!",
73
+ "Replacing it will cause PERMANENT DATA LOSS if not backed up.",
74
+ "The previous key will be lost forever."
75
+ ]);
76
+ const confirmReplace = await confirm({
77
+ message: "Replace existing ecloud key?",
78
+ default: false
79
+ });
80
+ if (!confirmReplace) {
81
+ this.log("\nMigration cancelled.");
179
82
  return;
180
83
  }
181
- this.log("\nFound legacy keys from eigenx-cli:");
84
+ }
85
+ try {
86
+ await storePrivateKey(privateKey);
87
+ this.log("\n\u2713 Private key migrated to ecloud keyring");
88
+ this.log(`\u2713 Address: ${address}`);
89
+ this.log("\nNote: This key will be used for all environments (mainnet, sepolia, etc.)");
182
90
  this.log("");
183
- for (const key of legacyKeys) {
184
- this.log(` Address: ${key.address}`);
185
- this.log(` Environment: ${key.environment}`);
186
- this.log(` Source: ${key.source}`);
187
- this.log("");
188
- }
189
- const choices = legacyKeys.map((key) => ({
190
- name: `${key.address} (${key.environment} - ${key.source})`,
191
- value: key
192
- }));
193
- const selectedKey = await select2({
194
- message: "Select a key to migrate:",
195
- choices
91
+ const confirmDelete = await confirm({
92
+ message: `Delete the legacy key from ${selectedKey.source}:${selectedKey.environment}?`,
93
+ default: false
196
94
  });
197
- const privateKey = await getLegacyPrivateKey(selectedKey.environment, selectedKey.source);
198
- if (!privateKey) {
199
- this.error(`Failed to retrieve legacy key for ${selectedKey.environment}`);
200
- }
201
- const address = getAddressFromPrivateKey(privateKey);
202
- this.log(`
203
- Migrating key: ${address}`);
204
- this.log(`From: ${selectedKey.source}:${selectedKey.environment}`);
205
- const exists = await keyExists();
206
- if (exists) {
207
- this.log("");
208
- displayWarning([
209
- "WARNING: A private key for ecloud already exists!",
210
- "Replacing it will cause PERMANENT DATA LOSS if not backed up.",
211
- "The previous key will be lost forever."
212
- ]);
213
- const confirmReplace = await confirm({
214
- message: "Replace existing ecloud key?",
215
- default: false
216
- });
217
- if (!confirmReplace) {
218
- this.log("\nMigration cancelled.");
219
- return;
220
- }
221
- }
222
- try {
223
- await storePrivateKey(privateKey);
224
- this.log("\n\u2713 Private key migrated to ecloud keyring");
225
- this.log(`\u2713 Address: ${address}`);
226
- this.log("\nNote: This key will be used for all environments (mainnet, sepolia, etc.)");
227
- this.log("");
228
- const confirmDelete = await confirm({
229
- message: `Delete the legacy key from ${selectedKey.source}:${selectedKey.environment}?`,
230
- default: false
231
- });
232
- if (confirmDelete) {
233
- const deleted = await deleteLegacyPrivateKey(selectedKey.environment, selectedKey.source);
234
- if (deleted) {
235
- this.log(
236
- `
237
- \u2713 Legacy key deleted from ${selectedKey.source}:${selectedKey.environment}`
238
- );
239
- this.log("\nNote: The key is now only stored in ecloud. You can still use it with");
240
- this.log("eigenx-cli by providing --private-key flag or EIGENX_PRIVATE_KEY env var.");
241
- } else {
242
- this.log(
243
- `
244
- \u26A0\uFE0F Failed to delete legacy key from ${selectedKey.source}:${selectedKey.environment}`
245
- );
246
- this.log("The key may have already been removed.");
247
- }
248
- } else {
95
+ if (confirmDelete) {
96
+ const deleted = await deleteLegacyPrivateKey(selectedKey.environment, selectedKey.source);
97
+ if (deleted) {
249
98
  this.log(`
250
- Legacy key kept in ${selectedKey.source}:${selectedKey.environment}`);
251
- this.log("You can delete it later using 'eigenx auth logout' if needed.");
99
+ \u2713 Legacy key deleted from ${selectedKey.source}:${selectedKey.environment}`);
100
+ this.log("\nNote: The key is now only stored in ecloud. You can still use it with");
101
+ this.log("eigenx-cli by providing --private-key flag or EIGENX_PRIVATE_KEY env var.");
102
+ } else {
103
+ this.log(
104
+ `
105
+ \u26A0\uFE0F Failed to delete legacy key from ${selectedKey.source}:${selectedKey.environment}`
106
+ );
107
+ this.log("The key may have already been removed.");
252
108
  }
253
- this.log("");
254
- this.log("Migration complete! You can now use ecloud commands without --private-key flag.");
255
- } catch (err) {
256
- this.error(`Failed to migrate key: ${err.message}`);
109
+ } else {
110
+ this.log(`
111
+ Legacy key kept in ${selectedKey.source}:${selectedKey.environment}`);
112
+ this.log("You can delete it later using 'eigenx auth logout' if needed.");
257
113
  }
258
- });
114
+ this.log("");
115
+ this.log("Migration complete! You can now use ecloud commands without --private-key flag.");
116
+ } catch (err) {
117
+ this.error(`Failed to migrate key: ${err.message}`);
118
+ }
259
119
  }
260
120
  };
261
121
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/commands/auth/migrate.ts","../../../src/utils/security.ts","../../../src/telemetry.ts","../../../src/utils/globalConfig.ts"],"sourcesContent":["/**\n * Auth Migrate Command\n *\n * Migrate a legacy eigenx-cli key to ecloud\n */\n\nimport { Command } from \"@oclif/core\";\nimport { confirm, select } from \"@inquirer/prompts\";\nimport {\n storePrivateKey,\n keyExists,\n getAddressFromPrivateKey,\n getLegacyKeys,\n getLegacyPrivateKey,\n deleteLegacyPrivateKey,\n type LegacyKey,\n} from \"@layr-labs/ecloud-sdk\";\nimport { displayWarning } from \"../../utils/security\";\nimport { withTelemetry } from \"../../telemetry\";\n\nexport default class AuthMigrate extends Command {\n static description = \"Migrate a private key from eigenx-cli to ecloud\";\n\n static examples = [\"<%= config.bin %> <%= command.id %>\"];\n\n async run(): Promise<void> {\n return withTelemetry(this, async () => {\n const legacyKeys = await getLegacyKeys();\n\n if (legacyKeys.length === 0) {\n this.log(\"No legacy keys found from eigenx-cli.\");\n this.log(\"\");\n this.log(\"To manually add a key to ecloud, use:\");\n this.log(\" ecloud auth login\");\n return;\n }\n\n // Display found legacy keys\n this.log(\"\\nFound legacy keys from eigenx-cli:\");\n this.log(\"\");\n\n for (const key of legacyKeys) {\n this.log(` Address: ${key.address}`);\n this.log(` Environment: ${key.environment}`);\n this.log(` Source: ${key.source}`);\n this.log(\"\");\n }\n\n // Create choices for selection\n const choices = legacyKeys.map((key) => ({\n name: `${key.address} (${key.environment} - ${key.source})`,\n value: key,\n }));\n\n const selectedKey = await select<LegacyKey>({\n message: \"Select a key to migrate:\",\n choices,\n });\n\n // Retrieve the actual private key\n const privateKey = await getLegacyPrivateKey(selectedKey.environment, selectedKey.source);\n\n if (!privateKey) {\n this.error(`Failed to retrieve legacy key for ${selectedKey.environment}`);\n }\n\n // Derive address for display\n const address = getAddressFromPrivateKey(privateKey);\n this.log(`\\nMigrating key: ${address}`);\n this.log(`From: ${selectedKey.source}:${selectedKey.environment}`);\n\n // Check if ecloud key already exists\n const exists = await keyExists();\n\n if (exists) {\n this.log(\"\");\n displayWarning([\n \"WARNING: A private key for ecloud already exists!\",\n \"Replacing it will cause PERMANENT DATA LOSS if not backed up.\",\n \"The previous key will be lost forever.\",\n ]);\n\n const confirmReplace = await confirm({\n message: \"Replace existing ecloud key?\",\n default: false,\n });\n\n if (!confirmReplace) {\n this.log(\"\\nMigration cancelled.\");\n return;\n }\n }\n\n // Store in ecloud keyring\n try {\n await storePrivateKey(privateKey);\n this.log(\"\\n✓ Private key migrated to ecloud keyring\");\n this.log(`✓ Address: ${address}`);\n this.log(\"\\nNote: This key will be used for all environments (mainnet, sepolia, etc.)\");\n\n // Ask if user wants to delete the legacy key (only if save was successful)\n this.log(\"\");\n const confirmDelete = await confirm({\n message: `Delete the legacy key from ${selectedKey.source}:${selectedKey.environment}?`,\n default: false,\n });\n\n if (confirmDelete) {\n const deleted = await deleteLegacyPrivateKey(selectedKey.environment, selectedKey.source);\n\n if (deleted) {\n this.log(\n `\\n✓ Legacy key deleted from ${selectedKey.source}:${selectedKey.environment}`,\n );\n this.log(\"\\nNote: The key is now only stored in ecloud. You can still use it with\");\n this.log(\"eigenx-cli by providing --private-key flag or EIGENX_PRIVATE_KEY env var.\");\n } else {\n this.log(\n `\\n⚠️ Failed to delete legacy key from ${selectedKey.source}:${selectedKey.environment}`,\n );\n this.log(\"The key may have already been removed.\");\n }\n } else {\n this.log(`\\nLegacy key kept in ${selectedKey.source}:${selectedKey.environment}`);\n this.log(\"You can delete it later using 'eigenx auth logout' if needed.\");\n }\n\n this.log(\"\");\n this.log(\"Migration complete! You can now use ecloud commands without --private-key flag.\");\n } catch (err: any) {\n this.error(`Failed to migrate key: ${err.message}`);\n }\n });\n }\n}\n","/**\n * Security utilities for CLI\n *\n * Functions for securely displaying and handling sensitive content\n * like private keys.\n */\n\nimport { spawn, execSync } from \"child_process\";\nimport { platform } from \"os\";\nimport { select, password } from \"@inquirer/prompts\";\n\n/**\n * Display sensitive content using system pager (less/more)\n * Returns true if content was displayed, false if user aborted\n */\nexport async function showPrivateKey(content: string): Promise<boolean> {\n // Try to use system pager\n const pager = detectPager();\n\n if (pager) {\n try {\n await runPager(pager, content);\n return true;\n } catch (err) {\n console.error(`Failed to run pager: ${err}`);\n // Fall through to fallback\n }\n }\n\n // No pager available - give user a choice\n console.log(\"\\nNo pager (less/more) found on PATH.\");\n console.log(\"For security, avoid printing private keys to the terminal.\");\n console.log(\"\");\n\n const choice = await select({\n message: \"Choose an option:\",\n choices: [\n { name: \"Abort (recommended)\", value: \"abort\" },\n { name: \"Print and clear screen\", value: \"print\" },\n ],\n });\n\n if (choice === \"print\") {\n console.log(content);\n console.log(\"\");\n console.log(\"Press Enter after you have securely saved the key.\");\n console.log(\"The screen will be cleared...\");\n\n // Wait for Enter\n await password({\n message: \"\",\n mask: \"\",\n });\n\n clearTerminal();\n return true;\n }\n\n return false; // User aborted\n}\n\n/**\n * Detect system pager (less or more)\n */\nfunction detectPager(): string | null {\n // Check PAGER env var first\n if (process.env.PAGER) {\n const pagerEnv = process.env.PAGER.trim();\n // Only allow simple command names without arguments or special characters\n if (/^[a-zA-Z0-9_-]+$/.test(pagerEnv)) {\n return pagerEnv;\n }\n }\n\n // Try common pagers\n const pagers = [\"less\", \"more\"];\n\n for (const pagerCmd of pagers) {\n if (commandExists(pagerCmd)) {\n return pagerCmd;\n }\n }\n\n return null;\n}\n\n/**\n * Run pager with content\n */\nfunction runPager(pager: string, content: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const child = spawn(pager, [], {\n stdio: [\"pipe\", \"inherit\", \"inherit\"],\n });\n\n child.on(\"error\", reject);\n child.on(\"exit\", (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(new Error(`Pager exited with code ${code}`));\n }\n });\n\n try {\n const written = child.stdin!.write(content);\n if (!written) {\n child.stdin!.once(\"drain\", () => {\n try {\n child.stdin!.end();\n } catch (err) {\n reject(err);\n }\n });\n } else {\n child.stdin!.end();\n }\n } catch (err) {\n reject(err);\n }\n });\n}\n\n/**\n * Check if command exists\n */\nfunction commandExists(command: string): boolean {\n try {\n const cmd = platform() === \"win32\" ? `where ${command}` : `which ${command}`;\n execSync(cmd, { stdio: \"ignore\" });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Clear terminal screen\n */\nexport function clearTerminal(): void {\n if (platform() === \"win32\") {\n process.stdout.write(\"\\x1Bc\");\n } else {\n process.stdout.write(\"\\x1B[2J\\x1B[3J\\x1B[H\");\n }\n}\n\n/**\n * Get hidden input (password-style)\n */\nexport async function getHiddenInput(message: string): Promise<string> {\n return await password({\n message,\n mask: \"*\",\n });\n}\n\n/**\n * Display multi-line warning for destructive operations\n */\nexport function displayWarning(lines: string[]): void {\n const width = lines.length > 0 ? Math.max(...lines.map((l) => l.length)) + 4 : 4;\n const border = \"⚠\".repeat(width);\n\n console.log(\"\");\n console.log(border);\n for (const line of lines) {\n console.log(`⚠ ${line}`);\n }\n console.log(border);\n console.log(\"\");\n}\n","/**\n * Telemetry utilities for CLI commands\n *\n * Provides helpers to wrap command execution with telemetry tracking\n */\n\nimport {\n createTelemetryClient,\n createAppEnvironment,\n createMetricsContext,\n addMetric,\n addMetricWithDimensions,\n emitMetrics,\n type TelemetryClient,\n getBuildType,\n} from \"@layr-labs/ecloud-sdk\";\nimport { Command } from \"@oclif/core\";\nimport {\n getDefaultEnvironment,\n getOrCreateUserUUID,\n getGlobalTelemetryPreference,\n} from \"./utils/globalConfig\";\n\n/**\n * Create a telemetry client for CLI usage\n */\nexport function createCLITelemetryClient(): TelemetryClient {\n // Get user UUID from CLI's globalConfig (handles I/O)\n const userUUID = getOrCreateUserUUID();\n const environment = createAppEnvironment(userUUID);\n\n // Get telemetry preference from CLI's globalConfig\n const telemetryEnabled = getGlobalTelemetryPreference();\n\n return createTelemetryClient(environment, \"ecloud-cli\", {\n telemetryEnabled: telemetryEnabled === true, // Only enabled if explicitly set to true\n });\n}\n\n/**\n * Wrap a command execution with telemetry\n *\n * @param command - The CLI command instance\n * @param action - The command action to execute\n * @returns The result of the action\n */\nexport async function withTelemetry<T>(command: Command, action: () => Promise<T>): Promise<T> {\n const client = createCLITelemetryClient();\n const metrics = createMetricsContext();\n\n // Set source to identify CLI usage\n metrics.properties[\"source\"] = \"ecloud-cli\";\n\n // Set command name in properties\n metrics.properties[\"command\"] = command.id || command.constructor.name;\n\n // Set environment in properties\n const environment = getDefaultEnvironment() || \"sepolia\";\n metrics.properties[\"environment\"] = environment;\n\n // Set buildType in properties\n const buildType = getBuildType() || \"prod\";\n metrics.properties[\"build_type\"] = buildType;\n\n // Set CLI version if available\n const cliVersion = command.config.version;\n if (cliVersion) {\n metrics.properties[\"cli_version\"] = cliVersion;\n }\n\n // Add initial count metric\n addMetric(metrics, \"Count\", 1);\n\n let actionError: Error | undefined;\n let result: T;\n\n try {\n result = await action();\n return result;\n } catch (err) {\n actionError = err instanceof Error ? err : new Error(String(err));\n throw err;\n } finally {\n // Add result metric\n const resultValue = actionError ? \"Failure\" : \"Success\";\n const dimensions: Record<string, string> = {};\n if (actionError) {\n dimensions[\"error\"] = actionError.message;\n }\n addMetricWithDimensions(metrics, resultValue, 1, dimensions);\n\n // Add duration metric\n const duration = Date.now() - metrics.startTime.getTime();\n addMetric(metrics, \"DurationMilliseconds\", duration);\n\n // Emit all metrics\n try {\n await emitMetrics(client, metrics);\n await client.close();\n } catch {\n // Silently ignore telemetry errors\n }\n }\n}\n","/**\n * Global configuration management\n *\n * Stores user-level configuration that persists across all CLI usage.\n * - $XDG_CONFIG_HOME/ecloud[BuildSuffix]/config.yaml (if XDG_CONFIG_HOME is set)\n * - Or ~/.config/ecloud[BuildSuffix]/config.yaml (fallback)\n *\n * Where BuildSuffix is:\n * - \"\" (empty) for production builds\n * - \"-dev\" for development builds\n */\n\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport * as os from \"os\";\nimport { load as loadYaml, dump as dumpYaml } from \"js-yaml\";\nimport { getBuildType } from \"@layr-labs/ecloud-sdk\";\nimport * as crypto from \"crypto\";\nconst GLOBAL_CONFIG_FILE = \"config.yaml\";\n\nexport interface ProfileCacheEntry {\n updated_at: number; // Unix timestamp in milliseconds\n profiles: { [appId: string]: string }; // appId -> profile name\n}\n\nexport interface GlobalConfig {\n first_run?: boolean;\n telemetry_enabled?: boolean;\n user_uuid?: string;\n default_environment?: string;\n last_version_check?: number;\n last_known_version?: string;\n profile_cache?: {\n [environment: string]: ProfileCacheEntry;\n };\n}\n\n// Profile cache TTL: 24 hours in milliseconds\nconst PROFILE_CACHE_TTL_MS = 24 * 60 * 60 * 1000;\n\n/**\n * Get the XDG-compliant directory where global ecloud config should be stored\n */\nfunction getGlobalConfigDir(): string {\n // First check XDG_CONFIG_HOME\n const configHome = process.env.XDG_CONFIG_HOME;\n\n let baseDir: string;\n if (configHome && path.isAbsolute(configHome)) {\n baseDir = configHome;\n } else {\n // Fall back to ~/.config\n baseDir = path.join(os.homedir(), \".config\");\n }\n\n // Use environment-specific config directory\n const buildType = getBuildType();\n const buildSuffix = buildType === \"dev\" ? \"-dev\" : \"\";\n const configDirName = `ecloud${buildSuffix}`;\n\n return path.join(baseDir, configDirName);\n}\n\n/**\n * Get the full path to the global config file\n */\nfunction getGlobalConfigPath(): string {\n return path.join(getGlobalConfigDir(), GLOBAL_CONFIG_FILE);\n}\n\n/**\n * Load global configuration, creating defaults if needed\n */\nexport function loadGlobalConfig(): GlobalConfig {\n const configPath = getGlobalConfigPath();\n\n // If file doesn't exist, return defaults for first run\n if (!fs.existsSync(configPath)) {\n return {\n first_run: true,\n };\n }\n\n try {\n const content = fs.readFileSync(configPath, \"utf-8\");\n const config = loadYaml(content) as GlobalConfig;\n return config || { first_run: true };\n } catch {\n // If parsing fails, return defaults\n return {\n first_run: true,\n };\n }\n}\n\n/**\n * Save global configuration to disk\n */\nexport function saveGlobalConfig(config: GlobalConfig): void {\n const configPath = getGlobalConfigPath();\n\n // Ensure directory exists\n const configDir = path.dirname(configPath);\n fs.mkdirSync(configDir, { recursive: true, mode: 0o755 });\n\n // Write config file\n const content = dumpYaml(config, { lineWidth: -1 });\n fs.writeFileSync(configPath, content, { mode: 0o644 });\n}\n\n/**\n * Get the user's preferred deployment environment\n */\nexport function getDefaultEnvironment(): string | undefined {\n const config = loadGlobalConfig();\n return config.default_environment;\n}\n\n/**\n * Set the user's preferred deployment environment\n */\nexport function setDefaultEnvironment(environment: string): void {\n const config = loadGlobalConfig();\n config.default_environment = environment;\n config.first_run = false; // No longer first run after setting environment\n saveGlobalConfig(config);\n}\n\n/**\n * Check if this is the user's first time running the CLI\n */\nexport function isFirstRun(): boolean {\n const config = loadGlobalConfig();\n return config.first_run === true;\n}\n\n/**\n * Mark that the first run has been completed\n */\nexport function markFirstRunComplete(): void {\n const config = loadGlobalConfig();\n config.first_run = false;\n saveGlobalConfig(config);\n}\n\n/**\n * Get the global telemetry preference\n */\nexport function getGlobalTelemetryPreference(): boolean | undefined {\n const config = loadGlobalConfig();\n return config.telemetry_enabled;\n}\n\n/**\n * Set the global telemetry preference\n */\nexport function setGlobalTelemetryPreference(enabled: boolean): void {\n const config = loadGlobalConfig();\n config.telemetry_enabled = enabled;\n config.first_run = false; // No longer first run after setting preference\n saveGlobalConfig(config);\n}\n\n// ==================== Profile Cache Functions ====================\n\n/**\n * Get cached profile names for an environment\n * Returns null if cache is missing or expired (older than 24 hours)\n */\nexport function getProfileCache(environment: string): Record<string, string> | null {\n const config = loadGlobalConfig();\n const cacheEntry = config.profile_cache?.[environment];\n\n if (!cacheEntry) {\n return null;\n }\n\n // Check if cache is expired\n const now = Date.now();\n if (now - cacheEntry.updated_at > PROFILE_CACHE_TTL_MS) {\n return null;\n }\n\n return cacheEntry.profiles;\n}\n\n/**\n * Set cached profile names for an environment\n */\nexport function setProfileCache(environment: string, profiles: Record<string, string>): void {\n const config = loadGlobalConfig();\n\n if (!config.profile_cache) {\n config.profile_cache = {};\n }\n\n config.profile_cache[environment] = {\n updated_at: Date.now(),\n profiles,\n };\n\n saveGlobalConfig(config);\n}\n\n/**\n * Invalidate profile cache for a specific environment or all environments\n */\nexport function invalidateProfileCache(environment?: string): void {\n const config = loadGlobalConfig();\n\n if (!config.profile_cache) {\n return;\n }\n\n if (environment) {\n // Invalidate specific environment\n delete config.profile_cache[environment];\n } else {\n // Invalidate all environments\n config.profile_cache = {};\n }\n\n saveGlobalConfig(config);\n}\n\n/**\n * Update a single profile name in the cache\n * This is useful after deploy or profile set to update just one entry\n */\nexport function updateProfileCacheEntry(\n environment: string,\n appId: string,\n profileName: string,\n): void {\n const config = loadGlobalConfig();\n\n if (!config.profile_cache) {\n config.profile_cache = {};\n }\n\n if (!config.profile_cache[environment]) {\n config.profile_cache[environment] = {\n updated_at: Date.now(),\n profiles: {},\n };\n }\n\n // Normalize appId to lowercase for consistent lookups\n const normalizedAppId = appId.toLowerCase();\n config.profile_cache[environment].profiles[normalizedAppId] = profileName;\n config.profile_cache[environment].updated_at = Date.now();\n\n saveGlobalConfig(config);\n}\n\n/**\n * Get the user UUID from global config, or generate a new one if it doesn't exist\n */\nexport function getOrCreateUserUUID(): string {\n const config = loadGlobalConfig();\n if (config.user_uuid) {\n return config.user_uuid;\n }\n\n // Generate a new UUID (v4)\n const uuid = generateUUID();\n\n // Save it to config\n config.user_uuid = uuid;\n config.first_run = false;\n saveGlobalConfig(config);\n\n return uuid;\n}\n\n/**\n * Generate a UUID v4\n */\nfunction generateUUID(): string {\n // UUID v4 format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\n // Use cryptographically secure random values.\n const bytes = crypto.randomBytes(16);\n // Per RFC 4122 section 4.4, set bits for version and `clock_seq_hi_and_reserved`\n bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4\n bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant 10\n const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\"));\n return (\n hex.slice(0, 4).join(\"\") +\n hex.slice(4, 6).join(\"\") +\n \"-\" +\n hex.slice(6, 8).join(\"\") +\n \"-\" +\n hex.slice(8, 10).join(\"\") +\n \"-\" +\n hex.slice(10, 12).join(\"\") +\n \"-\" +\n hex.slice(12, 16).join(\"\")\n );\n}\n\n/**\n * Save user UUID to global config (preserves existing UUID if present)\n */\nexport function saveUserUUID(userUUID: string): void {\n const config = loadGlobalConfig();\n // Only update if not already set\n if (!config.user_uuid) {\n config.user_uuid = userUUID;\n saveGlobalConfig(config);\n }\n}\n"],"mappings":"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","names":["select","getBuildType","getBuildType","select"]}
1
+ {"version":3,"sources":["../../../src/commands/auth/migrate.ts","../../../src/utils/security.ts"],"sourcesContent":["/**\n * Auth Migrate Command\n *\n * Migrate a legacy eigenx-cli key to ecloud\n */\n\nimport { Command } from \"@oclif/core\";\nimport { confirm, select } from \"@inquirer/prompts\";\nimport {\n storePrivateKey,\n keyExists,\n getAddressFromPrivateKey,\n getLegacyKeys,\n getLegacyPrivateKey,\n deleteLegacyPrivateKey,\n type LegacyKey,\n} from \"@layr-labs/ecloud-sdk\";\nimport { displayWarning } from \"../../utils/security\";\n\nexport default class AuthMigrate extends Command {\n static description = \"Migrate a private key from eigenx-cli to ecloud\";\n\n static examples = [\"<%= config.bin %> <%= command.id %>\"];\n\n async run(): Promise<void> {\n const legacyKeys = await getLegacyKeys();\n\n if (legacyKeys.length === 0) {\n this.log(\"No legacy keys found from eigenx-cli.\");\n this.log(\"\");\n this.log(\"To manually add a key to ecloud, use:\");\n this.log(\" ecloud auth login\");\n return;\n }\n\n // Display found legacy keys\n this.log(\"\\nFound legacy keys from eigenx-cli:\");\n this.log(\"\");\n\n for (const key of legacyKeys) {\n this.log(` Address: ${key.address}`);\n this.log(` Environment: ${key.environment}`);\n this.log(` Source: ${key.source}`);\n this.log(\"\");\n }\n\n // Create choices for selection\n const choices = legacyKeys.map((key) => ({\n name: `${key.address} (${key.environment} - ${key.source})`,\n value: key,\n }));\n\n const selectedKey = await select<LegacyKey>({\n message: \"Select a key to migrate:\",\n choices,\n });\n\n // Retrieve the actual private key\n const privateKey = await getLegacyPrivateKey(selectedKey.environment, selectedKey.source);\n\n if (!privateKey) {\n this.error(`Failed to retrieve legacy key for ${selectedKey.environment}`);\n }\n\n // Derive address for display\n const address = getAddressFromPrivateKey(privateKey);\n this.log(`\\nMigrating key: ${address}`);\n this.log(`From: ${selectedKey.source}:${selectedKey.environment}`);\n\n // Check if ecloud key already exists\n const exists = await keyExists();\n\n if (exists) {\n this.log(\"\");\n displayWarning([\n \"WARNING: A private key for ecloud already exists!\",\n \"Replacing it will cause PERMANENT DATA LOSS if not backed up.\",\n \"The previous key will be lost forever.\",\n ]);\n\n const confirmReplace = await confirm({\n message: \"Replace existing ecloud key?\",\n default: false,\n });\n\n if (!confirmReplace) {\n this.log(\"\\nMigration cancelled.\");\n return;\n }\n }\n\n // Store in ecloud keyring\n try {\n await storePrivateKey(privateKey);\n this.log(\"\\n✓ Private key migrated to ecloud keyring\");\n this.log(`✓ Address: ${address}`);\n this.log(\"\\nNote: This key will be used for all environments (mainnet, sepolia, etc.)\");\n\n // Ask if user wants to delete the legacy key (only if save was successful)\n this.log(\"\");\n const confirmDelete = await confirm({\n message: `Delete the legacy key from ${selectedKey.source}:${selectedKey.environment}?`,\n default: false,\n });\n\n if (confirmDelete) {\n const deleted = await deleteLegacyPrivateKey(selectedKey.environment, selectedKey.source);\n\n if (deleted) {\n this.log(`\\n✓ Legacy key deleted from ${selectedKey.source}:${selectedKey.environment}`);\n this.log(\"\\nNote: The key is now only stored in ecloud. You can still use it with\");\n this.log(\"eigenx-cli by providing --private-key flag or EIGENX_PRIVATE_KEY env var.\");\n } else {\n this.log(\n `\\n⚠️ Failed to delete legacy key from ${selectedKey.source}:${selectedKey.environment}`,\n );\n this.log(\"The key may have already been removed.\");\n }\n } else {\n this.log(`\\nLegacy key kept in ${selectedKey.source}:${selectedKey.environment}`);\n this.log(\"You can delete it later using 'eigenx auth logout' if needed.\");\n }\n\n this.log(\"\");\n this.log(\"Migration complete! You can now use ecloud commands without --private-key flag.\");\n } catch (err: any) {\n this.error(`Failed to migrate key: ${err.message}`);\n }\n }\n}\n","/**\n * Security utilities for CLI\n *\n * Functions for securely displaying and handling sensitive content\n * like private keys.\n */\n\nimport { spawn, execSync } from \"child_process\";\nimport { platform } from \"os\";\nimport { select, password } from \"@inquirer/prompts\";\n\n/**\n * Display sensitive content using system pager (less/more)\n * Returns true if content was displayed, false if user aborted\n */\nexport async function showPrivateKey(content: string): Promise<boolean> {\n // Try to use system pager\n const pager = detectPager();\n\n if (pager) {\n try {\n await runPager(pager, content);\n return true;\n } catch (err) {\n console.error(`Failed to run pager: ${err}`);\n // Fall through to fallback\n }\n }\n\n // No pager available - give user a choice\n console.log(\"\\nNo pager (less/more) found on PATH.\");\n console.log(\"For security, avoid printing private keys to the terminal.\");\n console.log(\"\");\n\n const choice = await select({\n message: \"Choose an option:\",\n choices: [\n { name: \"Abort (recommended)\", value: \"abort\" },\n { name: \"Print and clear screen\", value: \"print\" },\n ],\n });\n\n if (choice === \"print\") {\n console.log(content);\n console.log(\"\");\n console.log(\"Press Enter after you have securely saved the key.\");\n console.log(\"The screen will be cleared...\");\n\n // Wait for Enter\n await password({\n message: \"\",\n mask: \"\",\n });\n\n clearTerminal();\n return true;\n }\n\n return false; // User aborted\n}\n\n/**\n * Detect system pager (less or more)\n */\nfunction detectPager(): string | null {\n // Check PAGER env var first\n if (process.env.PAGER) {\n const pagerEnv = process.env.PAGER.trim();\n // Only allow simple command names without arguments or special characters\n if (/^[a-zA-Z0-9_-]+$/.test(pagerEnv)) {\n return pagerEnv;\n }\n }\n\n // Try common pagers\n const pagers = [\"less\", \"more\"];\n\n for (const pagerCmd of pagers) {\n if (commandExists(pagerCmd)) {\n return pagerCmd;\n }\n }\n\n return null;\n}\n\n/**\n * Run pager with content\n */\nfunction runPager(pager: string, content: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const child = spawn(pager, [], {\n stdio: [\"pipe\", \"inherit\", \"inherit\"],\n });\n\n child.on(\"error\", reject);\n child.on(\"exit\", (code) => {\n if (code === 0) {\n resolve();\n } else {\n reject(new Error(`Pager exited with code ${code}`));\n }\n });\n\n try {\n const written = child.stdin!.write(content);\n if (!written) {\n child.stdin!.once(\"drain\", () => {\n try {\n child.stdin!.end();\n } catch (err) {\n reject(err);\n }\n });\n } else {\n child.stdin!.end();\n }\n } catch (err) {\n reject(err);\n }\n });\n}\n\n/**\n * Check if command exists\n */\nfunction commandExists(command: string): boolean {\n try {\n const cmd = platform() === \"win32\" ? `where ${command}` : `which ${command}`;\n execSync(cmd, { stdio: \"ignore\" });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Clear terminal screen\n */\nexport function clearTerminal(): void {\n if (platform() === \"win32\") {\n process.stdout.write(\"\\x1Bc\");\n } else {\n process.stdout.write(\"\\x1B[2J\\x1B[3J\\x1B[H\");\n }\n}\n\n/**\n * Get hidden input (password-style)\n */\nexport async function getHiddenInput(message: string): Promise<string> {\n return await password({\n message,\n mask: \"*\",\n });\n}\n\n/**\n * Display multi-line warning for destructive operations\n */\nexport function displayWarning(lines: string[]): void {\n const width = lines.length > 0 ? Math.max(...lines.map((l) => l.length)) + 4 : 4;\n const border = \"⚠\".repeat(width);\n\n console.log(\"\");\n console.log(border);\n for (const line of lines) {\n console.log(`⚠ ${line}`);\n }\n console.log(border);\n console.log(\"\");\n}\n"],"mappings":"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","names":["select","select"]}
@@ -44,75 +44,7 @@ import * as path from "path";
44
44
  import * as os from "os";
45
45
  import { load as loadYaml, dump as dumpYaml } from "js-yaml";
46
46
  import { getBuildType } from "@layr-labs/ecloud-sdk";
47
- import * as crypto from "crypto";
48
- var GLOBAL_CONFIG_FILE = "config.yaml";
49
47
  var PROFILE_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
50
- function getGlobalConfigDir() {
51
- const configHome = process.env.XDG_CONFIG_HOME;
52
- let baseDir;
53
- if (configHome && path.isAbsolute(configHome)) {
54
- baseDir = configHome;
55
- } else {
56
- baseDir = path.join(os.homedir(), ".config");
57
- }
58
- const buildType = getBuildType();
59
- const buildSuffix = buildType === "dev" ? "-dev" : "";
60
- const configDirName = `ecloud${buildSuffix}`;
61
- return path.join(baseDir, configDirName);
62
- }
63
- function getGlobalConfigPath() {
64
- return path.join(getGlobalConfigDir(), GLOBAL_CONFIG_FILE);
65
- }
66
- function loadGlobalConfig() {
67
- const configPath = getGlobalConfigPath();
68
- if (!fs.existsSync(configPath)) {
69
- return {
70
- first_run: true
71
- };
72
- }
73
- try {
74
- const content = fs.readFileSync(configPath, "utf-8");
75
- const config = loadYaml(content);
76
- return config || { first_run: true };
77
- } catch {
78
- return {
79
- first_run: true
80
- };
81
- }
82
- }
83
- function saveGlobalConfig(config) {
84
- const configPath = getGlobalConfigPath();
85
- const configDir = path.dirname(configPath);
86
- fs.mkdirSync(configDir, { recursive: true, mode: 493 });
87
- const content = dumpYaml(config, { lineWidth: -1 });
88
- fs.writeFileSync(configPath, content, { mode: 420 });
89
- }
90
- function getDefaultEnvironment() {
91
- const config = loadGlobalConfig();
92
- return config.default_environment;
93
- }
94
- function getGlobalTelemetryPreference() {
95
- const config = loadGlobalConfig();
96
- return config.telemetry_enabled;
97
- }
98
- function getOrCreateUserUUID() {
99
- const config = loadGlobalConfig();
100
- if (config.user_uuid) {
101
- return config.user_uuid;
102
- }
103
- const uuid = generateUUID();
104
- config.user_uuid = uuid;
105
- config.first_run = false;
106
- saveGlobalConfig(config);
107
- return uuid;
108
- }
109
- function generateUUID() {
110
- const bytes = crypto.randomBytes(16);
111
- bytes[6] = bytes[6] & 15 | 64;
112
- bytes[8] = bytes[8] & 63 | 128;
113
- const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0"));
114
- 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("");
115
- }
116
48
 
117
49
  // src/utils/appNames.ts
118
50
  import * as fs2 from "fs";
@@ -149,64 +81,6 @@ var commonFlags = {
149
81
  })
150
82
  };
151
83
 
152
- // src/telemetry.ts
153
- import {
154
- createTelemetryClient,
155
- createAppEnvironment,
156
- createMetricsContext,
157
- addMetric,
158
- addMetricWithDimensions,
159
- emitMetrics,
160
- getBuildType as getBuildType2
161
- } from "@layr-labs/ecloud-sdk";
162
- function createCLITelemetryClient() {
163
- const userUUID = getOrCreateUserUUID();
164
- const environment = createAppEnvironment(userUUID);
165
- const telemetryEnabled = getGlobalTelemetryPreference();
166
- return createTelemetryClient(environment, "ecloud-cli", {
167
- telemetryEnabled: telemetryEnabled === true
168
- // Only enabled if explicitly set to true
169
- });
170
- }
171
- async function withTelemetry(command, action) {
172
- const client = createCLITelemetryClient();
173
- const metrics = createMetricsContext();
174
- metrics.properties["source"] = "ecloud-cli";
175
- metrics.properties["command"] = command.id || command.constructor.name;
176
- const environment = getDefaultEnvironment() || "sepolia";
177
- metrics.properties["environment"] = environment;
178
- const buildType = getBuildType2() || "prod";
179
- metrics.properties["build_type"] = buildType;
180
- const cliVersion = command.config.version;
181
- if (cliVersion) {
182
- metrics.properties["cli_version"] = cliVersion;
183
- }
184
- addMetric(metrics, "Count", 1);
185
- let actionError;
186
- let result;
187
- try {
188
- result = await action();
189
- return result;
190
- } catch (err) {
191
- actionError = err instanceof Error ? err : new Error(String(err));
192
- throw err;
193
- } finally {
194
- const resultValue = actionError ? "Failure" : "Success";
195
- const dimensions = {};
196
- if (actionError) {
197
- dimensions["error"] = actionError.message;
198
- }
199
- addMetricWithDimensions(metrics, resultValue, 1, dimensions);
200
- const duration = Date.now() - metrics.startTime.getTime();
201
- addMetric(metrics, "DurationMilliseconds", duration);
202
- try {
203
- await emitMetrics(client, metrics);
204
- await client.close();
205
- } catch {
206
- }
207
- }
208
- }
209
-
210
84
  // src/commands/auth/whoami.ts
211
85
  var AuthWhoami = class _AuthWhoami extends Command {
212
86
  static description = "Show current authentication status and address";
@@ -219,26 +93,24 @@ var AuthWhoami = class _AuthWhoami extends Command {
219
93
  }
220
94
  };
221
95
  async run() {
222
- return withTelemetry(this, async () => {
223
- const { flags } = await this.parse(_AuthWhoami);
224
- const result = await getPrivateKeyWithSource({
225
- privateKey: flags["private-key"]
226
- });
227
- if (!result) {
228
- this.log("Not authenticated");
229
- this.log("");
230
- this.log("To authenticate, use one of:");
231
- this.log(" ecloud auth login # Store key in keyring");
232
- this.log(" export ECLOUD_PRIVATE_KEY=0x... # Use environment variable");
233
- this.log(" ecloud <command> --private-key 0x... # Use flag");
234
- return;
235
- }
236
- const address = getAddressFromPrivateKey(result.key);
237
- this.log(`Address: ${address}`);
238
- this.log(`Source: ${result.source}`);
239
- this.log("");
240
- this.log("Note: This key is used for all environments (mainnet, sepolia, etc.)");
96
+ const { flags } = await this.parse(_AuthWhoami);
97
+ const result = await getPrivateKeyWithSource({
98
+ privateKey: flags["private-key"]
241
99
  });
100
+ if (!result) {
101
+ this.log("Not authenticated");
102
+ this.log("");
103
+ this.log("To authenticate, use one of:");
104
+ this.log(" ecloud auth login # Store key in keyring");
105
+ this.log(" export ECLOUD_PRIVATE_KEY=0x... # Use environment variable");
106
+ this.log(" ecloud <command> --private-key 0x... # Use flag");
107
+ return;
108
+ }
109
+ const address = getAddressFromPrivateKey(result.key);
110
+ this.log(`Address: ${address}`);
111
+ this.log(`Source: ${result.source}`);
112
+ this.log("");
113
+ this.log("Note: This key is used for all environments (mainnet, sepolia, etc.)");
242
114
  }
243
115
  };
244
116
  export {