@layr-labs/ecloud-cli 0.0.1-dev-rfc.1 → 0.0.1-dev.1
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.
- package/README.md +6 -2
- package/VERSION +2 -0
- package/dist/commands/auth/generate.js +116 -10
- package/dist/commands/auth/generate.js.map +1 -1
- package/dist/commands/auth/login.js +37 -35
- package/dist/commands/auth/login.js.map +1 -1
- package/dist/commands/auth/logout.js +2 -8
- package/dist/commands/auth/logout.js.map +1 -1
- package/dist/commands/auth/migrate.js +32 -37
- package/dist/commands/auth/migrate.js.map +1 -1
- package/dist/commands/auth/whoami.js +53 -21
- package/dist/commands/auth/whoami.js.map +1 -1
- package/dist/commands/billing/cancel.js +83 -22
- package/dist/commands/billing/cancel.js.map +1 -1
- package/dist/commands/billing/status.js +92 -29
- package/dist/commands/billing/status.js.map +1 -1
- package/dist/commands/billing/subscribe.js +86 -31
- package/dist/commands/billing/subscribe.js.map +1 -1
- package/dist/commands/compute/app/configure/tls.js +150 -0
- package/dist/commands/compute/app/configure/tls.js.map +1 -0
- package/dist/commands/compute/app/create.js +134 -0
- package/dist/commands/compute/app/create.js.map +1 -0
- package/dist/commands/compute/app/deploy.js +1081 -0
- package/dist/commands/compute/app/deploy.js.map +1 -0
- package/dist/commands/compute/app/info.js +809 -0
- package/dist/commands/compute/app/info.js.map +1 -0
- package/dist/commands/compute/app/list.js +570 -0
- package/dist/commands/compute/app/list.js.map +1 -0
- package/dist/commands/compute/app/logs.js +629 -0
- package/dist/commands/compute/app/logs.js.map +1 -0
- package/dist/commands/compute/app/profile/set.js +1072 -0
- package/dist/commands/compute/app/profile/set.js.map +1 -0
- package/dist/commands/compute/app/start.js +665 -0
- package/dist/commands/compute/app/start.js.map +1 -0
- package/dist/commands/compute/app/stop.js +665 -0
- package/dist/commands/compute/app/stop.js.map +1 -0
- package/dist/commands/compute/app/terminate.js +671 -0
- package/dist/commands/compute/app/terminate.js.map +1 -0
- package/dist/commands/compute/app/upgrade.js +1063 -0
- package/dist/commands/compute/app/upgrade.js.map +1 -0
- package/dist/commands/compute/environment/list.js +89 -0
- package/dist/commands/compute/environment/list.js.map +1 -0
- package/dist/commands/compute/environment/set.js +215 -0
- package/dist/commands/compute/environment/set.js.map +1 -0
- package/dist/commands/compute/environment/show.js +96 -0
- package/dist/commands/compute/environment/show.js.map +1 -0
- package/dist/commands/compute/undelegate.js +250 -0
- package/dist/commands/compute/undelegate.js.map +1 -0
- package/dist/commands/upgrade.js +91 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/commands/version.js +65 -0
- package/dist/commands/version.js.map +1 -0
- package/package.json +29 -4
- package/dist/commands/app/create.js +0 -29
- package/dist/commands/app/create.js.map +0 -1
- package/dist/commands/app/deploy.js +0 -142
- package/dist/commands/app/deploy.js.map +0 -1
- package/dist/commands/app/logs.js +0 -108
- package/dist/commands/app/logs.js.map +0 -1
- package/dist/commands/app/start.js +0 -121
- package/dist/commands/app/start.js.map +0 -1
- package/dist/commands/app/stop.js +0 -121
- package/dist/commands/app/stop.js.map +0 -1
- package/dist/commands/app/terminate.js +0 -128
- package/dist/commands/app/terminate.js.map +0 -1
- package/dist/commands/app/upgrade.js +0 -142
- package/dist/commands/app/upgrade.js.map +0 -1
- package/dist/keys/mainnet-alpha/prod/kms-encryption-public-key.pem +0 -14
- package/dist/keys/mainnet-alpha/prod/kms-signing-public-key.pem +0 -4
- package/dist/keys/sepolia/dev/kms-encryption-public-key.pem +0 -14
- package/dist/keys/sepolia/dev/kms-signing-public-key.pem +0 -4
- package/dist/keys/sepolia/prod/kms-encryption-public-key.pem +0 -14
- package/dist/keys/sepolia/prod/kms-signing-public-key.pem +0 -4
- package/dist/templates/Dockerfile.layered.tmpl +0 -58
- package/dist/templates/compute-source-env.sh.tmpl +0 -110
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@ A TypeScript SDK and CLI for deploying and managing applications on eigenx TEE (
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
7
|
ECloud SDK 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
|
|
@@ -20,6 +21,7 @@ This monorepo contains two main packages:
|
|
|
20
21
|
The core TypeScript SDK for programmatic access to ecloud services.
|
|
21
22
|
|
|
22
23
|
**Features:**
|
|
24
|
+
|
|
23
25
|
- Type-safe client for ecloud operations
|
|
24
26
|
- Docker image building and pushing
|
|
25
27
|
- KMS encryption for secure deployments
|
|
@@ -31,6 +33,7 @@ The core TypeScript SDK for programmatic access to ecloud services.
|
|
|
31
33
|
Command-line interface built with oclif for deploying and managing applications.
|
|
32
34
|
|
|
33
35
|
**Features:**
|
|
36
|
+
|
|
34
37
|
- Deploy applications from Docker images
|
|
35
38
|
- Manage application lifecycle
|
|
36
39
|
- Environment-aware configuration
|
|
@@ -40,7 +43,7 @@ Command-line interface built with oclif for deploying and managing applications.
|
|
|
40
43
|
|
|
41
44
|
### Prerequisites
|
|
42
45
|
|
|
43
|
-
- Node.js 18+
|
|
46
|
+
- Node.js 18+
|
|
44
47
|
- pnpm (recommended) or npm
|
|
45
48
|
- Docker (for building and pushing images)
|
|
46
49
|
|
|
@@ -71,11 +74,13 @@ npx ecloud app deploy \
|
|
|
71
74
|
```
|
|
72
75
|
|
|
73
76
|
**Common Flags:**
|
|
77
|
+
|
|
74
78
|
- `--private-key`: Your Ethereum private key (or set `ECLOUD_PRIVATE_KEY` env var)
|
|
75
79
|
- `--environment`: Target environment (`sepolia` or `mainnet-alpha`)
|
|
76
80
|
- `--rpc-url`: Custom RPC URL (optional, or set `ECLOUD_RPC_URL` env var)
|
|
77
81
|
|
|
78
82
|
**Example:**
|
|
83
|
+
|
|
79
84
|
```bash
|
|
80
85
|
npx ecloud app deploy \
|
|
81
86
|
--private-key 0x... \
|
|
@@ -192,4 +197,3 @@ The deployment process involves several steps:
|
|
|
192
197
|
## Support
|
|
193
198
|
|
|
194
199
|
For issues and questions, please open an issue on GitHub.
|
|
195
|
-
|
package/VERSION
ADDED
|
@@ -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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/commands/auth/login.ts"],"sourcesContent":["/**\n * Auth Login Command\n *\n * Store an existing private key in OS keyring\n */\n\nimport { Command } from \"@oclif/core\";\nimport { confirm, select } from \"@inquirer/prompts\";\nimport {\n storePrivateKey,\n keyExists,\n getHiddenInput,\n validatePrivateKey,\n getAddressFromPrivateKey,\n displayWarning,\n getLegacyKeys,\n getLegacyPrivateKey,\n deleteLegacyPrivateKey,\n type LegacyKey,\n} from \"@layr-labs/ecloud-sdk\";\n\nexport default class AuthLogin extends Command {\n static description = \"Store your private key in OS keyring\";\n\n static examples = [\"<%= config.bin %> <%= command.id %>\"];\n\n async run(): Promise<void> {\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 \"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 key?\",\n default: false,\n });\n\n if (!confirmReplace) {\n this.log(\"\\nLogin cancelled.\");\n return;\n }\n }\n\n // Check for legacy keys from eigenx-cli\n const legacyKeys = await getLegacyKeys();\n let privateKey: string | null = null;\n let selectedKey: LegacyKey | null = null;\n\n if (legacyKeys.length > 0) {\n this.log(\"\\nFound legacy keys from eigenx-cli:\");\n this.log(\"\");\n\n // Display legacy keys\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 const importLegacy = await confirm({\n message: \"Would you like to import one of these legacy keys?\",\n default: false,\n });\n\n if (importLegacy) {\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 selectedKey = await select<LegacyKey>({\n message: \"Select a key to import:\",\n choices,\n });\n\n // Retrieve the actual private key\n privateKey = await getLegacyPrivateKey(\n selectedKey.environment,\n selectedKey.source\n );\n\n if (!privateKey) {\n this.error(\n `Failed to retrieve legacy key for ${selectedKey.environment}`\n );\n }\n\n this.log(\n `\\nImporting key from ${selectedKey.source}:${selectedKey.environment}`\n );\n }\n }\n\n // If no legacy key was selected, prompt for private key input\n if (!privateKey) {\n privateKey = await getHiddenInput(\"Enter your private key:\");\n\n privateKey = privateKey.trim();\n }\n\n if (!validatePrivateKey(privateKey)) {\n this.error(\"Invalid private key format. Please check and try again.\");\n }\n\n // Derive address for confirmation\n const address = getAddressFromPrivateKey(privateKey);\n\n this.log(`\\nAddress: ${address}`);\n\n const confirmStore = await confirm({\n message: \"Store this key in OS keyring?\",\n default: true,\n });\n\n if (!confirmStore) {\n this.log(\"\\nLogin cancelled.\");\n return;\n }\n\n // Store in keyring\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 \"\\nNote: This key will be used for all environments (mainnet, sepolia, etc.)\"\n );\n this.log(\"You can now use ecloud commands without --private-key flag.\");\n\n // Ask if user wants to delete the legacy key (only if save was successful)\n if (selectedKey) {\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(\n selectedKey.environment,\n selectedKey.source\n );\n\n if (deleted) {\n this.log(\n `\\n✓ Legacy key deleted from ${selectedKey.source}:${selectedKey.environment}`\n );\n this.log(\n \"\\nNote: The key is now only stored in ecloud. You can still use it with\"\n );\n this.log(\n \"eigenx-cli by providing --private-key flag or EIGENX_PRIVATE_KEY env var.\"\n );\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(\n `\\nLegacy key kept in ${selectedKey.source}:${selectedKey.environment}`\n );\n this.log(\n \"You can delete it later using 'eigenx auth logout' if needed.\"\n );\n }\n }\n } catch (err: any) {\n this.error(`Failed to store key: ${err.message}`);\n }\n }\n}\n"],"mappings":";;;AAMA,SAAS,eAAe;AACxB,SAAS,SAAS,cAAc;AAChC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAEP,IAAqB,YAArB,cAAuC,QAAQ;AAAA,EAC7C,OAAO,cAAc;AAAA,EAErB,OAAO,WAAW,CAAC,qCAAqC;AAAA,EAExD,MAAM,MAAqB;AAEzB,UAAM,SAAS,MAAM,UAAU;AAE/B,QAAI,QAAQ;AACV,qBAAe;AAAA,QACb;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAED,YAAM,iBAAiB,MAAM,QAAQ;AAAA,QACnC,SAAS;AAAA,QACT,SAAS;AAAA,MACX,CAAC;AAED,UAAI,CAAC,gBAAgB;AACnB,aAAK,IAAI,oBAAoB;AAC7B;AAAA,MACF;AAAA,IACF;AAGA,UAAM,aAAa,MAAM,cAAc;AACvC,QAAI,aAA4B;AAChC,QAAI,cAAgC;AAEpC,QAAI,WAAW,SAAS,GAAG;AACzB,WAAK,IAAI,sCAAsC;AAC/C,WAAK,IAAI,EAAE;AAGX,iBAAW,OAAO,YAAY;AAC5B,aAAK,IAAI,cAAc,IAAI,OAAO,EAAE;AACpC,aAAK,IAAI,kBAAkB,IAAI,WAAW,EAAE;AAC5C,aAAK,IAAI,aAAa,IAAI,MAAM,EAAE;AAClC,aAAK,IAAI,EAAE;AAAA,MACb;AAEA,YAAM,eAAe,MAAM,QAAQ;AAAA,QACjC,SAAS;AAAA,QACT,SAAS;AAAA,MACX,CAAC;AAED,UAAI,cAAc;AAEhB,cAAM,UAAU,WAAW,IAAI,CAAC,SAAS;AAAA,UACvC,MAAM,GAAG,IAAI,OAAO,KAAK,IAAI,WAAW,MAAM,IAAI,MAAM;AAAA,UACxD,OAAO;AAAA,QACT,EAAE;AAEF,sBAAc,MAAM,OAAkB;AAAA,UACpC,SAAS;AAAA,UACT;AAAA,QACF,CAAC;AAGD,qBAAa,MAAM;AAAA,UACjB,YAAY;AAAA,UACZ,YAAY;AAAA,QACd;AAEA,YAAI,CAAC,YAAY;AACf,eAAK;AAAA,YACH,qCAAqC,YAAY,WAAW;AAAA,UAC9D;AAAA,QACF;AAEA,aAAK;AAAA,UACH;AAAA,qBAAwB,YAAY,MAAM,IAAI,YAAY,WAAW;AAAA,QACvE;AAAA,MACF;AAAA,IACF;AAGA,QAAI,CAAC,YAAY;AACf,mBAAa,MAAM,eAAe,yBAAyB;AAE3D,mBAAa,WAAW,KAAK;AAAA,IAC/B;AAEA,QAAI,CAAC,mBAAmB,UAAU,GAAG;AACnC,WAAK,MAAM,yDAAyD;AAAA,IACtE;AAGA,UAAM,UAAU,yBAAyB,UAAU;AAEnD,SAAK,IAAI;AAAA,WAAc,OAAO,EAAE;AAEhC,UAAM,eAAe,MAAM,QAAQ;AAAA,MACjC,SAAS;AAAA,MACT,SAAS;AAAA,IACX,CAAC;AAED,QAAI,CAAC,cAAc;AACjB,WAAK,IAAI,oBAAoB;AAC7B;AAAA,IACF;AAGA,QAAI;AACF,YAAM,gBAAgB,UAAU;AAChC,WAAK,IAAI,2CAAsC;AAC/C,WAAK,IAAI,mBAAc,OAAO,EAAE;AAChC,WAAK;AAAA,QACH;AAAA,MACF;AACA,WAAK,IAAI,6DAA6D;AAGtE,UAAI,aAAa;AACf,aAAK,IAAI,EAAE;AACX,cAAM,gBAAgB,MAAM,QAAQ;AAAA,UAClC,SAAS,8BAA8B,YAAY,MAAM,IAAI,YAAY,WAAW;AAAA,UACpF,SAAS;AAAA,QACX,CAAC;AAED,YAAI,eAAe;AACjB,gBAAM,UAAU,MAAM;AAAA,YACpB,YAAY;AAAA,YACZ,YAAY;AAAA,UACd;AAEA,cAAI,SAAS;AACX,iBAAK;AAAA,cACH;AAAA,iCAA+B,YAAY,MAAM,IAAI,YAAY,WAAW;AAAA,YAC9E;AACA,iBAAK;AAAA,cACH;AAAA,YACF;AACA,iBAAK;AAAA,cACH;AAAA,YACF;AAAA,UACF,OAAO;AACL,iBAAK;AAAA,cACH;AAAA,iDAA0C,YAAY,MAAM,IAAI,YAAY,WAAW;AAAA,YACzF;AACA,iBAAK,IAAI,wCAAwC;AAAA,UACnD;AAAA,QACF,OAAO;AACL,eAAK;AAAA,YACH;AAAA,qBAAwB,YAAY,MAAM,IAAI,YAAY,WAAW;AAAA,UACvE;AACA,eAAK;AAAA,YACH;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF,SAAS,KAAU;AACjB,WAAK,MAAM,wBAAwB,IAAI,OAAO,EAAE;AAAA,IAClD;AAAA,EACF;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/auth/login.ts","../../../src/utils/security.ts"],"sourcesContent":["/**\n * Auth Login Command\n *\n * Store an existing private key in OS keyring\n */\n\nimport { Command } from \"@oclif/core\";\nimport { confirm, select } from \"@inquirer/prompts\";\nimport {\n storePrivateKey,\n keyExists,\n validatePrivateKey,\n getAddressFromPrivateKey,\n getLegacyKeys,\n getLegacyPrivateKey,\n deleteLegacyPrivateKey,\n type LegacyKey,\n} from \"@layr-labs/ecloud-sdk\";\nimport { getHiddenInput, displayWarning } from \"../../utils/security\";\n\nexport default class AuthLogin extends Command {\n static description = \"Store your private key in OS keyring\";\n\n static examples = [\"<%= config.bin %> <%= command.id %>\"];\n\n async run(): Promise<void> {\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 \"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 key?\",\n default: false,\n });\n\n if (!confirmReplace) {\n this.log(\"\\nLogin cancelled.\");\n return;\n }\n }\n\n // Check for legacy keys from eigenx-cli\n const legacyKeys = await getLegacyKeys();\n let privateKey: string | null = null;\n let selectedKey: LegacyKey | null = null;\n\n if (legacyKeys.length > 0) {\n this.log(\"\\nFound legacy keys from eigenx-cli:\");\n this.log(\"\");\n\n // Display legacy keys\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 const importLegacy = await confirm({\n message: \"Would you like to import one of these legacy keys?\",\n default: false,\n });\n\n if (importLegacy) {\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 selectedKey = await select<LegacyKey>({\n message: \"Select a key to import:\",\n choices,\n });\n\n // Retrieve the actual private key\n 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 this.log(`\\nImporting key from ${selectedKey.source}:${selectedKey.environment}`);\n }\n }\n\n // If no legacy key was selected, prompt for private key input\n if (!privateKey) {\n privateKey = await getHiddenInput(\"Enter your private key:\");\n\n privateKey = privateKey.trim();\n }\n\n if (!validatePrivateKey(privateKey)) {\n this.error(\"Invalid private key format. Please check and try again.\");\n }\n\n // Derive address for confirmation\n const address = getAddressFromPrivateKey(privateKey);\n\n this.log(`\\nAddress: ${address}`);\n\n const confirmStore = await confirm({\n message: \"Store this key in OS keyring?\",\n default: true,\n });\n\n if (!confirmStore) {\n this.log(\"\\nLogin cancelled.\");\n return;\n }\n\n // Store in keyring\n try {\n await storePrivateKey(privateKey);\n this.log(\"\\n✓ Private key stored in OS keyring\");\n this.log(`✓ Address: ${address}`);\n this.log(\"\\nNote: This key will be used for all environments (mainnet, sepolia, etc.)\");\n this.log(\"You can now use ecloud commands without --private-key flag.\");\n\n // Ask if user wants to delete the legacy key (only if save was successful)\n if (selectedKey) {\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 } catch (err: any) {\n this.error(`Failed to store 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"]}
|
|
@@ -3,11 +3,7 @@
|
|
|
3
3
|
// src/commands/auth/logout.ts
|
|
4
4
|
import { Command, Flags } from "@oclif/core";
|
|
5
5
|
import { confirm } from "@inquirer/prompts";
|
|
6
|
-
import {
|
|
7
|
-
deletePrivateKey,
|
|
8
|
-
getPrivateKey,
|
|
9
|
-
getAddressFromPrivateKey
|
|
10
|
-
} from "@layr-labs/ecloud-sdk";
|
|
6
|
+
import { deletePrivateKey, getPrivateKey, getAddressFromPrivateKey } from "@layr-labs/ecloud-sdk";
|
|
11
7
|
var AuthLogout = class _AuthLogout extends Command {
|
|
12
8
|
static description = "Remove private key from OS keyring";
|
|
13
9
|
static examples = [
|
|
@@ -46,9 +42,7 @@ var AuthLogout = class _AuthLogout extends Command {
|
|
|
46
42
|
const deleted = await deletePrivateKey();
|
|
47
43
|
if (deleted) {
|
|
48
44
|
this.log("\n\u2713 Successfully removed key from keyring");
|
|
49
|
-
this.log(
|
|
50
|
-
"\nYou will need to provide --private-key flag for future commands,"
|
|
51
|
-
);
|
|
45
|
+
this.log("\nYou will need to provide --private-key flag for future commands,");
|
|
52
46
|
this.log("or run 'ecloud auth login' to store a key again.");
|
|
53
47
|
} else {
|
|
54
48
|
this.log("\nFailed to remove key (it may have already been removed)");
|
|
@@ -1 +1 @@
|
|
|
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 {
|
|
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":[]}
|
|
@@ -2,16 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
// src/commands/auth/migrate.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
9
|
getAddressFromPrivateKey,
|
|
10
|
-
displayWarning,
|
|
11
10
|
getLegacyKeys,
|
|
12
11
|
getLegacyPrivateKey,
|
|
13
12
|
deleteLegacyPrivateKey
|
|
14
13
|
} from "@layr-labs/ecloud-sdk";
|
|
14
|
+
|
|
15
|
+
// src/utils/security.ts
|
|
16
|
+
import { spawn, execSync } from "child_process";
|
|
17
|
+
import { platform } from "os";
|
|
18
|
+
import { select, password } from "@inquirer/prompts";
|
|
19
|
+
function displayWarning(lines) {
|
|
20
|
+
const width = lines.length > 0 ? Math.max(...lines.map((l) => l.length)) + 4 : 4;
|
|
21
|
+
const border = "\u26A0".repeat(width);
|
|
22
|
+
console.log("");
|
|
23
|
+
console.log(border);
|
|
24
|
+
for (const line of lines) {
|
|
25
|
+
console.log(`\u26A0 ${line}`);
|
|
26
|
+
}
|
|
27
|
+
console.log(border);
|
|
28
|
+
console.log("");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/commands/auth/migrate.ts
|
|
15
32
|
var AuthMigrate = class extends Command {
|
|
16
33
|
static description = "Migrate a private key from eigenx-cli to ecloud";
|
|
17
34
|
static examples = ["<%= config.bin %> <%= command.id %>"];
|
|
@@ -36,18 +53,13 @@ var AuthMigrate = class extends Command {
|
|
|
36
53
|
name: `${key.address} (${key.environment} - ${key.source})`,
|
|
37
54
|
value: key
|
|
38
55
|
}));
|
|
39
|
-
const selectedKey = await
|
|
56
|
+
const selectedKey = await select2({
|
|
40
57
|
message: "Select a key to migrate:",
|
|
41
58
|
choices
|
|
42
59
|
});
|
|
43
|
-
const privateKey = await getLegacyPrivateKey(
|
|
44
|
-
selectedKey.environment,
|
|
45
|
-
selectedKey.source
|
|
46
|
-
);
|
|
60
|
+
const privateKey = await getLegacyPrivateKey(selectedKey.environment, selectedKey.source);
|
|
47
61
|
if (!privateKey) {
|
|
48
|
-
this.error(
|
|
49
|
-
`Failed to retrieve legacy key for ${selectedKey.environment}`
|
|
50
|
-
);
|
|
62
|
+
this.error(`Failed to retrieve legacy key for ${selectedKey.environment}`);
|
|
51
63
|
}
|
|
52
64
|
const address = getAddressFromPrivateKey(privateKey);
|
|
53
65
|
this.log(`
|
|
@@ -74,30 +86,19 @@ Migrating key: ${address}`);
|
|
|
74
86
|
await storePrivateKey(privateKey);
|
|
75
87
|
this.log("\n\u2713 Private key migrated to ecloud keyring");
|
|
76
88
|
this.log(`\u2713 Address: ${address}`);
|
|
77
|
-
this.log(
|
|
78
|
-
"\nNote: This key will be used for all environments (mainnet, sepolia, etc.)"
|
|
79
|
-
);
|
|
89
|
+
this.log("\nNote: This key will be used for all environments (mainnet, sepolia, etc.)");
|
|
80
90
|
this.log("");
|
|
81
91
|
const confirmDelete = await confirm({
|
|
82
92
|
message: `Delete the legacy key from ${selectedKey.source}:${selectedKey.environment}?`,
|
|
83
93
|
default: false
|
|
84
94
|
});
|
|
85
95
|
if (confirmDelete) {
|
|
86
|
-
const deleted = await deleteLegacyPrivateKey(
|
|
87
|
-
selectedKey.environment,
|
|
88
|
-
selectedKey.source
|
|
89
|
-
);
|
|
96
|
+
const deleted = await deleteLegacyPrivateKey(selectedKey.environment, selectedKey.source);
|
|
90
97
|
if (deleted) {
|
|
91
|
-
this.log(
|
|
92
|
-
|
|
93
|
-
\
|
|
94
|
-
);
|
|
95
|
-
this.log(
|
|
96
|
-
"\nNote: The key is now only stored in ecloud. You can still use it with"
|
|
97
|
-
);
|
|
98
|
-
this.log(
|
|
99
|
-
"eigenx-cli by providing --private-key flag or EIGENX_PRIVATE_KEY env var."
|
|
100
|
-
);
|
|
98
|
+
this.log(`
|
|
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.");
|
|
101
102
|
} else {
|
|
102
103
|
this.log(
|
|
103
104
|
`
|
|
@@ -106,18 +107,12 @@ Migrating key: ${address}`);
|
|
|
106
107
|
this.log("The key may have already been removed.");
|
|
107
108
|
}
|
|
108
109
|
} else {
|
|
109
|
-
this.log(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
);
|
|
113
|
-
this.log(
|
|
114
|
-
"You can delete it later using 'eigenx auth logout' if needed."
|
|
115
|
-
);
|
|
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.");
|
|
116
113
|
}
|
|
117
114
|
this.log("");
|
|
118
|
-
this.log(
|
|
119
|
-
"Migration complete! You can now use ecloud commands without --private-key flag."
|
|
120
|
-
);
|
|
115
|
+
this.log("Migration complete! You can now use ecloud commands without --private-key flag.");
|
|
121
116
|
} catch (err) {
|
|
122
117
|
this.error(`Failed to migrate key: ${err.message}`);
|
|
123
118
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/commands/auth/migrate.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 displayWarning,\n getLegacyKeys,\n getLegacyPrivateKey,\n deleteLegacyPrivateKey,\n type LegacyKey,\n} from \"@layr-labs/ecloud-sdk\";\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(\n selectedKey.environment,\n selectedKey.source\n );\n\n if (!privateKey) {\n this.error(\n `Failed to retrieve legacy key for ${selectedKey.environment}`\n );\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(\n \"\\nNote: This key will be used for all environments (mainnet, sepolia, etc.)\"\n );\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(\n selectedKey.environment,\n selectedKey.source\n );\n\n if (deleted) {\n this.log(\n `\\n✓ Legacy key deleted from ${selectedKey.source}:${selectedKey.environment}`\n );\n this.log(\n \"\\nNote: The key is now only stored in ecloud. You can still use it with\"\n );\n this.log(\n \"eigenx-cli by providing --private-key flag or EIGENX_PRIVATE_KEY env var.\"\n );\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(\n `\\nLegacy key kept in ${selectedKey.source}:${selectedKey.environment}`\n );\n this.log(\n \"You can delete it later using 'eigenx auth logout' if needed.\"\n );\n }\n\n this.log(\"\");\n this.log(\n \"Migration complete! You can now use ecloud commands without --private-key flag.\"\n );\n } catch (err: any) {\n this.error(`Failed to migrate key: ${err.message}`);\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
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"]}
|