@layr-labs/ecloud-cli 0.0.1-rfc.1 → 0.1.0-dev

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 (75) hide show
  1. package/README.md +264 -48
  2. package/VERSION +2 -0
  3. package/dist/commands/auth/generate.js +116 -10
  4. package/dist/commands/auth/generate.js.map +1 -1
  5. package/dist/commands/auth/login.js +37 -35
  6. package/dist/commands/auth/login.js.map +1 -1
  7. package/dist/commands/auth/logout.js +2 -8
  8. package/dist/commands/auth/logout.js.map +1 -1
  9. package/dist/commands/auth/migrate.js +32 -37
  10. package/dist/commands/auth/migrate.js.map +1 -1
  11. package/dist/commands/auth/whoami.js +53 -21
  12. package/dist/commands/auth/whoami.js.map +1 -1
  13. package/dist/commands/billing/cancel.js +83 -22
  14. package/dist/commands/billing/cancel.js.map +1 -1
  15. package/dist/commands/billing/status.js +92 -29
  16. package/dist/commands/billing/status.js.map +1 -1
  17. package/dist/commands/billing/subscribe.js +86 -31
  18. package/dist/commands/billing/subscribe.js.map +1 -1
  19. package/dist/commands/compute/app/configure/tls.js +150 -0
  20. package/dist/commands/compute/app/configure/tls.js.map +1 -0
  21. package/dist/commands/compute/app/create.js +134 -0
  22. package/dist/commands/compute/app/create.js.map +1 -0
  23. package/dist/commands/compute/app/deploy.js +1101 -0
  24. package/dist/commands/compute/app/deploy.js.map +1 -0
  25. package/dist/commands/compute/app/info.js +818 -0
  26. package/dist/commands/compute/app/info.js.map +1 -0
  27. package/dist/commands/compute/app/list.js +578 -0
  28. package/dist/commands/compute/app/list.js.map +1 -0
  29. package/dist/commands/compute/app/logs.js +639 -0
  30. package/dist/commands/compute/app/logs.js.map +1 -0
  31. package/dist/commands/compute/app/profile/set.js +1086 -0
  32. package/dist/commands/compute/app/profile/set.js.map +1 -0
  33. package/dist/commands/compute/app/start.js +675 -0
  34. package/dist/commands/compute/app/start.js.map +1 -0
  35. package/dist/commands/compute/app/stop.js +675 -0
  36. package/dist/commands/compute/app/stop.js.map +1 -0
  37. package/dist/commands/compute/app/terminate.js +681 -0
  38. package/dist/commands/compute/app/terminate.js.map +1 -0
  39. package/dist/commands/compute/app/upgrade.js +1072 -0
  40. package/dist/commands/compute/app/upgrade.js.map +1 -0
  41. package/dist/commands/compute/environment/list.js +89 -0
  42. package/dist/commands/compute/environment/list.js.map +1 -0
  43. package/dist/commands/compute/environment/set.js +215 -0
  44. package/dist/commands/compute/environment/set.js.map +1 -0
  45. package/dist/commands/compute/environment/show.js +96 -0
  46. package/dist/commands/compute/environment/show.js.map +1 -0
  47. package/dist/commands/compute/undelegate.js +259 -0
  48. package/dist/commands/compute/undelegate.js.map +1 -0
  49. package/dist/commands/upgrade.js +91 -0
  50. package/dist/commands/upgrade.js.map +1 -0
  51. package/dist/commands/version.js +65 -0
  52. package/dist/commands/version.js.map +1 -0
  53. package/package.json +30 -5
  54. package/dist/commands/app/create.js +0 -29
  55. package/dist/commands/app/create.js.map +0 -1
  56. package/dist/commands/app/deploy.js +0 -142
  57. package/dist/commands/app/deploy.js.map +0 -1
  58. package/dist/commands/app/logs.js +0 -108
  59. package/dist/commands/app/logs.js.map +0 -1
  60. package/dist/commands/app/start.js +0 -121
  61. package/dist/commands/app/start.js.map +0 -1
  62. package/dist/commands/app/stop.js +0 -121
  63. package/dist/commands/app/stop.js.map +0 -1
  64. package/dist/commands/app/terminate.js +0 -128
  65. package/dist/commands/app/terminate.js.map +0 -1
  66. package/dist/commands/app/upgrade.js +0 -142
  67. package/dist/commands/app/upgrade.js.map +0 -1
  68. package/dist/keys/mainnet-alpha/prod/kms-encryption-public-key.pem +0 -14
  69. package/dist/keys/mainnet-alpha/prod/kms-signing-public-key.pem +0 -4
  70. package/dist/keys/sepolia/dev/kms-encryption-public-key.pem +0 -14
  71. package/dist/keys/sepolia/dev/kms-signing-public-key.pem +0 -4
  72. package/dist/keys/sepolia/prod/kms-encryption-public-key.pem +0 -14
  73. package/dist/keys/sepolia/prod/kms-signing-public-key.pem +0 -4
  74. package/dist/templates/Dockerfile.layered.tmpl +0 -58
  75. package/dist/templates/compute-source-env.sh.tmpl +0 -110
package/README.md CHANGED
@@ -1,87 +1,304 @@
1
- # ECloud SDK
1
+ # ECloud SDK and CLI
2
2
 
3
- A TypeScript SDK and CLI for deploying and managing applications on eigenx TEE (Trusted Execution Environment). This monorepo provides both programmatic SDK access and a command-line interface for interacting with ecloud's decentralized compute platform.
3
+ A TypeScript SDK and CLI for deploying and managing applications on EigenCloud TEE (Trusted Execution Environment). This monorepo provides both programmatic SDK access and a command-line interface for interacting with ecloud's decentralized compute platform.
4
4
 
5
5
  ## Overview
6
6
 
7
- ECloud SDK enables developers to:
7
+ ECloud SDK and CLI enables developers to:
8
+
8
9
  - Deploy containerized applications to ecloud TEE
9
10
  - Manage application lifecycle (start, stop, terminate)
10
11
  - Build and push Docker images with encryption
11
- - Interact with ecloud smart contracts on Ethereum networks
12
12
  - Monitor application status and logs
13
13
 
14
- ## Packages
14
+ ## Prerequsites
15
+ * Docker - To package and publish application images ([Download](https://www.docker.com/get-started/))
16
+ * ETH for gas - For deployment transactions
15
17
 
16
- This monorepo contains two main packages:
18
+ ## Mainnet Alpha Limitations
19
+ * Not recommended for customer funds - Mainnet Alpha is intended to enable developers to build, test and ship applications. We do not recommend holding significant customer funds at this stage in Mainnet Alpha.
20
+ * Developer is still trusted - Mainnet Alpha does not enable full verifiable and trustless execution. * A later version will ensure developers can not upgrade code maliciously, and liveness guarantees.
21
+ No SLA - Mainnet Alpha does not have SLAs around support, and uptime of infrastructure.
17
22
 
18
- ### `@layr-labs/ecloud-sdk`
19
23
 
20
- The core TypeScript SDK for programmatic access to ecloud services.
24
+ ## Quick Start
25
+ ### Installation
21
26
 
22
- **Features:**
23
- - Type-safe client for ecloud operations
24
- - Docker image building and pushing
25
- - KMS encryption for secure deployments
26
- - Smart contract interactions (EIP7702)
27
- - Environment configuration management
27
+ ```bash
28
+ npm install -g @layr-labs/ecloud-cli
29
+ ```
28
30
 
29
- ### `@layr-labs/ecloud-cli`
31
+ ### Initial Setup
32
+ ```bash
33
+ # Log in to your Docker registry (required to push images)
34
+ docker login
30
35
 
31
- Command-line interface built with oclif for deploying and managing applications.
36
+ # Log in with an existing private key
37
+ ecloud auth login
38
+ ```
32
39
 
33
- **Features:**
34
- - Deploy applications from Docker images
35
- - Manage application lifecycle
36
- - Environment-aware configuration
37
- - Support for multiple networks
40
+ **Don't have a private key?** Use `ecloud auth generate --store` instead
38
41
 
39
- ## Installation
42
+ **Need ETH for gas?** Run `ecloud auth whoami` to see your address. For sepolia, get funds from [Google Cloud](https://cloud.google.com/application/web3/faucet/ethereum/sepolia) or [Alchemy](https://sepoliafaucet.com/)
40
43
 
41
- ### Prerequisites
44
+ ### Get a billing account
45
+ This is required to create apps
46
+ ```bash
47
+ ecloud billing subscribe
48
+ ```
42
49
 
43
- - Node.js 18+
44
- - pnpm (recommended) or npm
45
- - Docker (for building and pushing images)
50
+ ### **Create & Deploy**
51
+
52
+ ```bash
53
+ # Create your app (choose: typescript | python | golang | rust)
54
+ ecloud compute app create my-app typescript
55
+ cd my-app
56
+
57
+ # Configure environment variables
58
+ cp .env.example .env
59
+
60
+ # Deploy to TEE
61
+ ecloud compute app deploy
62
+ ```
63
+
64
+ ### **Working with Existing Projects**
46
65
 
47
- ### Install Dependencies
66
+ Have an existing project? You don't need `ecloud compute app create` - the CLI works with any Docker-based project:
48
67
 
49
68
  ```bash
50
- pnpm install
69
+ # From your existing project directory
70
+ cd my-existing-project
71
+
72
+ # Ensure you have a Dockerfile and .env file
73
+ # The CLI will prompt for these if not found in standard locations
74
+
75
+ # Deploy directly - the CLI will detect your project
76
+ ecloud compute app deploy
77
+ ```
78
+
79
+ **What you need:**
80
+ - **Dockerfile** - Must target `linux/amd64` and run as root user
81
+ - **.env file** - For environment variables (optional but recommended)
82
+
83
+ The CLI will automatically prompt for the Dockerfile and .env paths if they're not in the default locations. This means you can use ecloud with any existing containerized application without restructuring your project.
84
+
85
+ **Need TLS/HTTPS?** Run `ecloud compute app configure tls` to add the necessary configuration files for domain setup with private traffic termination in the TEE.
86
+
87
+ ### **View Your App**
88
+
89
+ ```bash
90
+ # View app information and logs
91
+ ecloud compute app info
92
+ ecloud compute app logs
93
+
94
+ # Add --watch (or -w) to continuously poll for live updates
95
+ ecloud compute app info --watch
96
+ ecloud compute app logs --watch
51
97
  ```
52
98
 
53
- ### Build
99
+ That's it! Your starter app is now running in a TEE with access to a MNEMONIC that only it can access.
100
+
101
+ **Ready to customize?** Edit your application code, update `.env` with any API keys you need, then run `ecloud compute app upgrade my-app` to deploy your changes
102
+
103
+ ## Application Environment
104
+
105
+ Your TEE application runs with these capabilities:
106
+
107
+ 1. **Secure Execution** - Your code runs in an Intel TDX instance with hardware-level isolation
108
+ 2. **Auto-Generated Wallet** - Access a private mnemonic via `process.env.MNEMONIC`
109
+ - Derive wallet accounts using standard libraries (e.g., viem’s `mnemonicToAccount(process.env.MNEMONIC)`)
110
+ - Only your TEE can decrypt and use this mnemonic
111
+ 3. **Environment Variables** - All variables from your `.env` file are available in your container
112
+ - Variables with `_PUBLIC` suffix are visible to users for transparency
113
+ - Standard variables remain private and encrypted within the TEE
114
+ 4. **Onchain Management** - Your app's lifecycle is controlled via Ethereum smart contracts
115
+
116
+ ### Working with Your App
54
117
 
55
118
  ```bash
56
- pnpm build
119
+ # List all your apps
120
+ ecloud compute app list
121
+
122
+ # Stop/start your app
123
+ ecloud compute app stop my-app
124
+ ecloud compute app start my-app
125
+
126
+ # Terminate your app
127
+ ecloud compute app terminate my-app
57
128
  ```
58
129
 
59
- ## Usage
130
+ ## Authentication
131
+
132
+ Ecloud CLI needs a private key to sign transactions. Three options:
133
+
134
+ ### 1. OS Keyring (Recommended)
135
+
136
+ ```bash
137
+ ecloud auth generate --store # Generate new key and store it
138
+ ecloud auth login # Store an existing key securely
139
+ ecloud auth whoami # Check authentication
140
+ ecloud auth logout # Remove key
141
+ ```
142
+
143
+ ### 2. Environment Variable
144
+
145
+ ```bash
146
+ export ECLOUD_PRIVATE_KEY=0x1234...
147
+ ecloud compute app deploy
148
+ ```
149
+
150
+ ### 3. Command Flag
151
+
152
+ ```bash
153
+ ecloud compute app deploy --private-key 0x1234...
154
+ ```
155
+
156
+ **Priority:** Flag → Environment → Keyring
157
+
158
+ ## TLS/HTTPS Setup
159
+
160
+ ### Enable TLS
161
+
162
+ ```bash
163
+ # Add TLS configuration to your project
164
+ ecloud compute app configure tls
60
165
 
61
- ### CLI Usage
166
+ # Add variables to .env
167
+ cat .env.example.tls >> .env
168
+ ```
169
+
170
+ ### Configure
171
+
172
+ Required in `.env`:
173
+ ```bash
174
+ DOMAIN=yourdomain.com
175
+ APP_PORT=3000
176
+ ```
177
+
178
+ Recommended for first deployment:
179
+ ```bash
180
+ ENABLE_CADDY_LOGS=true # Debug logs
181
+ ACME_STAGING=true # Test certificates (avoid rate limits)
182
+ ```
183
+
184
+ ### DNS Setup
185
+
186
+ Create A record pointing to instance IP:
187
+ - Type: A
188
+ - Name: yourdomain.com
189
+ - Value: `<instance-ip>` (get from `ecloud compute app info`)
190
+
191
+ ### Deploy
192
+
193
+ ```bash
194
+ ecloud compute app upgrade
195
+ ```
196
+
197
+ ### Production Certificates
198
+
199
+ To switch from staging to production:
200
+ ```bash
201
+ # Set in .env:
202
+ ACME_STAGING=false
203
+ ACME_FORCE_ISSUE=true # Only if staging cert exists
204
+
205
+ # Deploy, then set ACME_FORCE_ISSUE=false for future deploys
206
+ ```
207
+
208
+ **Notes:**
209
+ - Let's Encrypt rate limit: 5 certificates/week per domain
210
+ - Test with staging certificates first to avoid rate limits
211
+ - DNS changes may take a few minutes to propagate
62
212
 
63
- The CLI is available via the `ecloud` command after building:
213
+ ## Advanced Usage
214
+
215
+ ### Building and Pushing Images Manually
216
+
217
+ If you prefer to build and push Docker images yourself instead of letting the CLI handle it, or already have an existing image:
64
218
 
65
219
  ```bash
66
- # Deploy an application
67
- npx ecloud app deploy \
68
- --private-key <your-private-key> \
69
- --environment sepolia \
70
- --image <docker-image-reference>
220
+ # Build and push your image manually
221
+ docker build --platform linux/amd64 -t myregistry/myapp:v1.0 .
222
+ docker push myregistry/myapp:v1.0
223
+
224
+ # Deploy using the image reference
225
+ ecloud compute app deploy myregistry/myapp:v1.0
71
226
  ```
72
227
 
73
- **Common Flags:**
74
- - `--private-key`: Your Ethereum private key (or set `ECLOUD_PRIVATE_KEY` env var)
75
- - `--environment`: Target environment (`sepolia` or `mainnet-alpha`)
76
- - `--rpc-url`: Custom RPC URL (optional, or set `ECLOUD_RPC_URL` env var)
228
+ **Requirements:**
229
+
230
+ - Image must target `linux/amd64` architecture
231
+ - Application must run as root user (TEE requirement)
232
+
233
+ ## Telemetry
234
+
235
+ Ecloud collects anonymous usage data to help us improve the CLI and understand how it's being used. This telemetry is enabled by default but can be easily disabled.
236
+
237
+ ### What We Collect
238
+
239
+ - Commands used (e.g., `ecloud compute app create`, `ecloud compute app deploy`)
240
+ - Error counts and types to identify common issues
241
+ - Performance metrics (command execution times)
242
+ - System information (OS, architecture)
243
+ - Deployment environment (e.g., sepolia, mainnet-alpha)
244
+ - User Ethereum address
245
+
246
+ ### What We DON'T Collect
247
+
248
+ - Personal information or identifiers
249
+ - Private keys or sensitive credentials
250
+ - Application source code or configurations
251
+ - Specific file paths or project names
252
+
253
+ ## Architecture
254
+
255
+ For a detailed understanding of how Ecloud enables verifiable applications with deterministic identities, see our [Architecture Documentation](docs/ECLOUD_ARCHITECTURE.md).
256
+
257
+ ### Key Components
258
+
259
+ - **Hardware Isolation** - Intel TDX secure enclaves with memory encryption
260
+ - **Attestation** - Cryptographic proof of exact Docker image integrity
261
+ - **Deterministic Keys** - Apps receive consistent identities via KMS
262
+ - **Smart Contracts** - Onchain configuration and lifecycle management
263
+
264
+ ## Development
265
+
266
+ ### Prerequisites
267
+
268
+ - Node.js 18+
269
+ - pnpm (recommended) or npm
270
+ - Docker (for building and pushing images)
271
+
272
+ ### Build from source
77
273
 
78
- **Example:**
79
274
  ```bash
80
- npx ecloud app deploy \
81
- --private-key 0x... \
82
- --environment sepolia
275
+ git clone https://github.com/Layr-Labs/ecloud
276
+ cd ecloud
277
+ pnpm install
278
+ pnpm build
279
+ pnpm ecloud version
83
280
  ```
84
281
 
282
+
283
+ ## SDK Packages
284
+
285
+ This monorepo contains two main packages:
286
+
287
+ ### `@layr-labs/ecloud-sdk`
288
+
289
+ The core TypeScript SDK for programmatic access to ecloud services.
290
+
291
+ **Features:**
292
+
293
+ - Type-safe client for ecloud operations
294
+ - Docker image building and pushing
295
+ - KMS encryption for secure deployments
296
+ - Smart contract interactions (EIP7702)
297
+ - Environment configuration management
298
+
299
+
300
+ ## Usage
301
+
85
302
  ### SDK Usage
86
303
 
87
304
  ```typescript
@@ -90,7 +307,7 @@ import { createECloudClient } from "@layr-labs/ecloud-sdk";
90
307
  // Create a client
91
308
  const client = createECloudClient({
92
309
  privateKey: "0x...",
93
- environment: "sepolia", // or "sepolia-dev" or "mainnet-alpha"
310
+ environment: "sepolia", // or "sepolia" or "mainnet-alpha"
94
311
  rpcUrl: "https://sepolia.infura.io/v3/...",
95
312
  });
96
313
 
@@ -192,4 +409,3 @@ The deployment process involves several steps:
192
409
  ## Support
193
410
 
194
411
  For issues and questions, please open an issue on GitHub.
195
-
package/VERSION ADDED
@@ -0,0 +1,2 @@
1
+ version=0.1.0-dev
2
+ commit=77a74ce3558ed2d1331016cab7356a0c36c2833f
@@ -3,13 +3,121 @@
3
3
  // src/commands/auth/generate.ts
4
4
  import { Command, Flags } from "@oclif/core";
5
5
  import { confirm } from "@inquirer/prompts";
6
- import {
7
- generateNewPrivateKey,
8
- storePrivateKey,
9
- keyExists,
10
- showPrivateKey,
11
- displayWarning
12
- } from "@layr-labs/ecloud-sdk";
6
+ import { generateNewPrivateKey, storePrivateKey, keyExists } from "@layr-labs/ecloud-sdk";
7
+
8
+ // src/utils/security.ts
9
+ import { spawn, execSync } from "child_process";
10
+ import { platform } from "os";
11
+ import { select, password } from "@inquirer/prompts";
12
+ async function showPrivateKey(content) {
13
+ const pager = detectPager();
14
+ if (pager) {
15
+ try {
16
+ await runPager(pager, content);
17
+ return true;
18
+ } catch (err) {
19
+ console.error(`Failed to run pager: ${err}`);
20
+ }
21
+ }
22
+ console.log("\nNo pager (less/more) found on PATH.");
23
+ console.log("For security, avoid printing private keys to the terminal.");
24
+ console.log("");
25
+ const choice = await select({
26
+ message: "Choose an option:",
27
+ choices: [
28
+ { name: "Abort (recommended)", value: "abort" },
29
+ { name: "Print and clear screen", value: "print" }
30
+ ]
31
+ });
32
+ if (choice === "print") {
33
+ console.log(content);
34
+ console.log("");
35
+ console.log("Press Enter after you have securely saved the key.");
36
+ console.log("The screen will be cleared...");
37
+ await password({
38
+ message: "",
39
+ mask: ""
40
+ });
41
+ clearTerminal();
42
+ return true;
43
+ }
44
+ return false;
45
+ }
46
+ function detectPager() {
47
+ if (process.env.PAGER) {
48
+ const pagerEnv = process.env.PAGER.trim();
49
+ if (/^[a-zA-Z0-9_-]+$/.test(pagerEnv)) {
50
+ return pagerEnv;
51
+ }
52
+ }
53
+ const pagers = ["less", "more"];
54
+ for (const pagerCmd of pagers) {
55
+ if (commandExists(pagerCmd)) {
56
+ return pagerCmd;
57
+ }
58
+ }
59
+ return null;
60
+ }
61
+ function runPager(pager, content) {
62
+ return new Promise((resolve, reject) => {
63
+ const child = spawn(pager, [], {
64
+ stdio: ["pipe", "inherit", "inherit"]
65
+ });
66
+ child.on("error", reject);
67
+ child.on("exit", (code) => {
68
+ if (code === 0) {
69
+ resolve();
70
+ } else {
71
+ reject(new Error(`Pager exited with code ${code}`));
72
+ }
73
+ });
74
+ try {
75
+ const written = child.stdin.write(content);
76
+ if (!written) {
77
+ child.stdin.once("drain", () => {
78
+ try {
79
+ child.stdin.end();
80
+ } catch (err) {
81
+ reject(err);
82
+ }
83
+ });
84
+ } else {
85
+ child.stdin.end();
86
+ }
87
+ } catch (err) {
88
+ reject(err);
89
+ }
90
+ });
91
+ }
92
+ function commandExists(command) {
93
+ try {
94
+ const cmd = platform() === "win32" ? `where ${command}` : `which ${command}`;
95
+ execSync(cmd, { stdio: "ignore" });
96
+ return true;
97
+ } catch {
98
+ return false;
99
+ }
100
+ }
101
+ function clearTerminal() {
102
+ if (platform() === "win32") {
103
+ process.stdout.write("\x1Bc");
104
+ } else {
105
+ process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
106
+ }
107
+ }
108
+ function displayWarning(lines) {
109
+ const width = lines.length > 0 ? Math.max(...lines.map((l) => l.length)) + 4 : 4;
110
+ const border = "\u26A0".repeat(width);
111
+ console.log("");
112
+ console.log(border);
113
+ for (const line of lines) {
114
+ console.log(`\u26A0 ${line}`);
115
+ }
116
+ console.log(border);
117
+ console.log("");
118
+ }
119
+
120
+ // src/commands/auth/generate.ts
13
121
  var AuthGenerate = class _AuthGenerate extends Command {
14
122
  static description = "Generate a new private key";
15
123
  static aliases = ["auth:gen", "auth:new"];
@@ -83,9 +191,7 @@ Press 'q' to exit and continue...
83
191
  this.log(`
84
192
  \u2713 Private key stored in OS keyring`);
85
193
  this.log(`\u2713 Address: ${address}`);
86
- this.log(
87
- "\nYou can now use ecloud commands without --private-key flag."
88
- );
194
+ this.log("\nYou can now use ecloud commands without --private-key flag.");
89
195
  } catch (err) {
90
196
  this.error(`Failed to store key: ${err.message}`);
91
197
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/commands/auth/generate.ts"],"sourcesContent":["/**\n * Auth Generate Command\n *\n * Generate a new private key and optionally store it in OS keyring\n */\n\nimport { Command, Flags } from \"@oclif/core\";\nimport { confirm } from \"@inquirer/prompts\";\nimport {\n generateNewPrivateKey,\n storePrivateKey,\n keyExists,\n showPrivateKey,\n displayWarning,\n} from \"@layr-labs/ecloud-sdk\";\n\nexport default class AuthGenerate extends Command {\n static description = \"Generate a new private key\";\n\n static aliases = [\"auth:gen\", \"auth:new\"];\n\n static examples = [\n \"<%= config.bin %> <%= command.id %>\",\n \"<%= config.bin %> <%= command.id %> --store\",\n ];\n\n static flags = {\n store: Flags.boolean({\n description: \"Automatically store in OS keyring\",\n default: false,\n }),\n };\n\n async run(): Promise<void> {\n const { flags } = await this.parse(AuthGenerate);\n\n // Generate new key\n this.log(\"Generating new private key...\\n\");\n const { privateKey, address } = generateNewPrivateKey();\n\n // Display key securely\n const content = `\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nA new private key was generated for you.\n\nIMPORTANT: You MUST backup this key now.\n It will never be shown again.\n\nAddress: ${address}\nPrivate key: ${privateKey}\n\n⚠️ SECURITY WARNING:\n • Anyone with this key can control your account\n • Never share it or commit it to version control\n • Store it in a secure password manager\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nPress 'q' to exit and continue...\n`;\n\n const displayed = await showPrivateKey(content);\n\n if (!displayed) {\n this.log(\"Key generation cancelled.\");\n return;\n }\n\n // Ask about storing\n let shouldStore = flags.store;\n\n if (!shouldStore && displayed) {\n shouldStore = await confirm({\n message: \"Store this key in your OS keyring?\",\n default: true,\n });\n }\n\n if (shouldStore) {\n // Check if key already exists\n const exists = await keyExists();\n\n if (exists) {\n displayWarning([\n `WARNING: A private key for ecloud already exists!`,\n \"If you continue, the existing key will be PERMANENTLY REPLACED.\",\n \"This cannot be undone!\",\n \"\",\n \"The previous key will be lost forever if you haven't backed it up.\",\n ]);\n\n const confirmReplace = await confirm({\n message: `Replace existing key for ecloud?`,\n default: false,\n });\n\n if (!confirmReplace) {\n this.log(\n \"\\nKey not stored. If you did not save your new key when it was displayed, it is now lost and cannot be recovered.\"\n );\n return;\n }\n }\n\n // Store the key\n try {\n await storePrivateKey(privateKey);\n this.log(`\\n✓ Private key stored in OS keyring`);\n this.log(`✓ Address: ${address}`);\n this.log(\n \"\\nYou can now use ecloud commands without --private-key flag.\"\n );\n } catch (err: any) {\n this.error(`Failed to store key: ${err.message}`);\n }\n } else {\n this.log(\"\\nKey not stored in keyring.\");\n this.log(\"Remember to save the key shown above in a secure location.\");\n }\n }\n}\n"],"mappings":";;;AAMA,SAAS,SAAS,aAAa;AAC/B,SAAS,eAAe;AACxB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,IAAqB,eAArB,MAAqB,sBAAqB,QAAQ;AAAA,EAChD,OAAO,cAAc;AAAA,EAErB,OAAO,UAAU,CAAC,YAAY,UAAU;AAAA,EAExC,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,aAAY;AAG/C,SAAK,IAAI,iCAAiC;AAC1C,UAAM,EAAE,YAAY,QAAQ,IAAI,sBAAsB;AAGtD,UAAM,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAOL,OAAO;AAAA,eACP,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWrB,UAAM,YAAY,MAAM,eAAe,OAAO;AAE9C,QAAI,CAAC,WAAW;AACd,WAAK,IAAI,2BAA2B;AACpC;AAAA,IACF;AAGA,QAAI,cAAc,MAAM;AAExB,QAAI,CAAC,eAAe,WAAW;AAC7B,oBAAc,MAAM,QAAQ;AAAA,QAC1B,SAAS;AAAA,QACT,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AAEA,QAAI,aAAa;AAEf,YAAM,SAAS,MAAM,UAAU;AAE/B,UAAI,QAAQ;AACV,uBAAe;AAAA,UACb;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAED,cAAM,iBAAiB,MAAM,QAAQ;AAAA,UACnC,SAAS;AAAA,UACT,SAAS;AAAA,QACX,CAAC;AAED,YAAI,CAAC,gBAAgB;AACnB,eAAK;AAAA,YACH;AAAA,UACF;AACA;AAAA,QACF;AAAA,MACF;AAGA,UAAI;AACF,cAAM,gBAAgB,UAAU;AAChC,aAAK,IAAI;AAAA,wCAAsC;AAC/C,aAAK,IAAI,mBAAc,OAAO,EAAE;AAChC,aAAK;AAAA,UACH;AAAA,QACF;AAAA,MACF,SAAS,KAAU;AACjB,aAAK,MAAM,wBAAwB,IAAI,OAAO,EAAE;AAAA,MAClD;AAAA,IACF,OAAO;AACL,WAAK,IAAI,8BAA8B;AACvC,WAAK,IAAI,4DAA4D;AAAA,IACvE;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../src/commands/auth/generate.ts","../../../src/utils/security.ts"],"sourcesContent":["/**\n * Auth Generate Command\n *\n * Generate a new private key and optionally store it in OS keyring\n */\n\nimport { Command, Flags } from \"@oclif/core\";\nimport { confirm } from \"@inquirer/prompts\";\nimport { generateNewPrivateKey, storePrivateKey, keyExists } from \"@layr-labs/ecloud-sdk\";\nimport { showPrivateKey, displayWarning } from \"../../utils/security\";\n\nexport default class AuthGenerate extends Command {\n static description = \"Generate a new private key\";\n\n static aliases = [\"auth:gen\", \"auth:new\"];\n\n static examples = [\n \"<%= config.bin %> <%= command.id %>\",\n \"<%= config.bin %> <%= command.id %> --store\",\n ];\n\n static flags = {\n store: Flags.boolean({\n description: \"Automatically store in OS keyring\",\n default: false,\n }),\n };\n\n async run(): Promise<void> {\n const { flags } = await this.parse(AuthGenerate);\n\n // Generate new key\n this.log(\"Generating new private key...\\n\");\n const { privateKey, address } = generateNewPrivateKey();\n\n // Display key securely\n const content = `\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nA new private key was generated for you.\n\nIMPORTANT: You MUST backup this key now.\n It will never be shown again.\n\nAddress: ${address}\nPrivate key: ${privateKey}\n\n⚠️ SECURITY WARNING:\n • Anyone with this key can control your account\n • Never share it or commit it to version control\n • Store it in a secure password manager\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nPress 'q' to exit and continue...\n`;\n\n const displayed = await showPrivateKey(content);\n\n if (!displayed) {\n this.log(\"Key generation cancelled.\");\n return;\n }\n\n // Ask about storing\n let shouldStore = flags.store;\n\n if (!shouldStore && displayed) {\n shouldStore = await confirm({\n message: \"Store this key in your OS keyring?\",\n default: true,\n });\n }\n\n if (shouldStore) {\n // Check if key already exists\n const exists = await keyExists();\n\n if (exists) {\n displayWarning([\n `WARNING: A private key for ecloud already exists!`,\n \"If you continue, the existing key will be PERMANENTLY REPLACED.\",\n \"This cannot be undone!\",\n \"\",\n \"The previous key will be lost forever if you haven't backed it up.\",\n ]);\n\n const confirmReplace = await confirm({\n message: `Replace existing key for ecloud?`,\n default: false,\n });\n\n if (!confirmReplace) {\n this.log(\n \"\\nKey not stored. If you did not save your new key when it was displayed, it is now lost and cannot be recovered.\",\n );\n return;\n }\n }\n\n // Store the key\n try {\n await storePrivateKey(privateKey);\n this.log(`\\n✓ Private key stored in OS keyring`);\n this.log(`✓ Address: ${address}`);\n this.log(\"\\nYou can now use ecloud commands without --private-key flag.\");\n } catch (err: any) {\n this.error(`Failed to store key: ${err.message}`);\n }\n } else {\n this.log(\"\\nKey not stored in keyring.\");\n this.log(\"Remember to save the key shown above in a secure location.\");\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":[]}
@@ -2,18 +2,40 @@
2
2
 
3
3
  // src/commands/auth/login.ts
4
4
  import { Command } from "@oclif/core";
5
- import { confirm, select } from "@inquirer/prompts";
5
+ import { confirm, select as select2 } from "@inquirer/prompts";
6
6
  import {
7
7
  storePrivateKey,
8
8
  keyExists,
9
- getHiddenInput,
10
9
  validatePrivateKey,
11
10
  getAddressFromPrivateKey,
12
- displayWarning,
13
11
  getLegacyKeys,
14
12
  getLegacyPrivateKey,
15
13
  deleteLegacyPrivateKey
16
14
  } from "@layr-labs/ecloud-sdk";
15
+
16
+ // src/utils/security.ts
17
+ import { spawn, execSync } from "child_process";
18
+ import { platform } from "os";
19
+ import { select, password } from "@inquirer/prompts";
20
+ async function getHiddenInput(message) {
21
+ return await password({
22
+ message,
23
+ mask: "*"
24
+ });
25
+ }
26
+ function displayWarning(lines) {
27
+ const width = lines.length > 0 ? Math.max(...lines.map((l) => l.length)) + 4 : 4;
28
+ const border = "\u26A0".repeat(width);
29
+ console.log("");
30
+ console.log(border);
31
+ for (const line of lines) {
32
+ console.log(`\u26A0 ${line}`);
33
+ }
34
+ console.log(border);
35
+ console.log("");
36
+ }
37
+
38
+ // src/commands/auth/login.ts
17
39
  var AuthLogin = class extends Command {
18
40
  static description = "Store your private key in OS keyring";
19
41
  static examples = ["<%= config.bin %> <%= command.id %>"];
@@ -55,23 +77,16 @@ var AuthLogin = class extends Command {
55
77
  name: `${key.address} (${key.environment} - ${key.source})`,
56
78
  value: key
57
79
  }));
58
- selectedKey = await select({
80
+ selectedKey = await select2({
59
81
  message: "Select a key to import:",
60
82
  choices
61
83
  });
62
- privateKey = await getLegacyPrivateKey(
63
- selectedKey.environment,
64
- selectedKey.source
65
- );
84
+ privateKey = await getLegacyPrivateKey(selectedKey.environment, selectedKey.source);
66
85
  if (!privateKey) {
67
- this.error(
68
- `Failed to retrieve legacy key for ${selectedKey.environment}`
69
- );
86
+ this.error(`Failed to retrieve legacy key for ${selectedKey.environment}`);
70
87
  }
71
- this.log(
72
- `
73
- Importing key from ${selectedKey.source}:${selectedKey.environment}`
74
- );
88
+ this.log(`
89
+ Importing key from ${selectedKey.source}:${selectedKey.environment}`);
75
90
  }
76
91
  }
77
92
  if (!privateKey) {
@@ -96,9 +111,7 @@ Address: ${address}`);
96
111
  await storePrivateKey(privateKey);
97
112
  this.log("\n\u2713 Private key stored in OS keyring");
98
113
  this.log(`\u2713 Address: ${address}`);
99
- this.log(
100
- "\nNote: This key will be used for all environments (mainnet, sepolia, etc.)"
101
- );
114
+ this.log("\nNote: This key will be used for all environments (mainnet, sepolia, etc.)");
102
115
  this.log("You can now use ecloud commands without --private-key flag.");
103
116
  if (selectedKey) {
104
117
  this.log("");
@@ -107,21 +120,14 @@ Address: ${address}`);
107
120
  default: false
108
121
  });
109
122
  if (confirmDelete) {
110
- const deleted = await deleteLegacyPrivateKey(
111
- selectedKey.environment,
112
- selectedKey.source
113
- );
123
+ const deleted = await deleteLegacyPrivateKey(selectedKey.environment, selectedKey.source);
114
124
  if (deleted) {
115
125
  this.log(
116
126
  `
117
127
  \u2713 Legacy key deleted from ${selectedKey.source}:${selectedKey.environment}`
118
128
  );
119
- this.log(
120
- "\nNote: The key is now only stored in ecloud. You can still use it with"
121
- );
122
- this.log(
123
- "eigenx-cli by providing --private-key flag or EIGENX_PRIVATE_KEY env var."
124
- );
129
+ this.log("\nNote: The key is now only stored in ecloud. You can still use it with");
130
+ this.log("eigenx-cli by providing --private-key flag or EIGENX_PRIVATE_KEY env var.");
125
131
  } else {
126
132
  this.log(
127
133
  `
@@ -130,13 +136,9 @@ Address: ${address}`);
130
136
  this.log("The key may have already been removed.");
131
137
  }
132
138
  } else {
133
- this.log(
134
- `
135
- Legacy key kept in ${selectedKey.source}:${selectedKey.environment}`
136
- );
137
- this.log(
138
- "You can delete it later using 'eigenx auth logout' if needed."
139
- );
139
+ this.log(`
140
+ Legacy key kept in ${selectedKey.source}:${selectedKey.environment}`);
141
+ this.log("You can delete it later using 'eigenx auth logout' if needed.");
140
142
  }
141
143
  }
142
144
  } catch (err) {