Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@
"overrides": {
"handlebars": "4.7.9"
}
}
},
"packageManager": "pnpm@9.15.0+sha512.76e2379760a4328ec4415815bcd6628dee727af3779aaa4c914e3944156c4299921a89f976381ee107d41f12cfa4b66681ca9c718f0668fa0831ed4c6d8ba56c"
}
7 changes: 6 additions & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
"README.md"
],
"bin": {
"ecloud": "./bin/run.js"
"ecloud": "./bin/run.js",
"ecloud-dev": "./bin/run.js"
},
"dependencies": {
"@inquirer/prompts": "^7.10.1",
"@layr-labs/ecloud-sdk": "workspace:*",
"@napi-rs/keyring": "^1.0.5",
"@oclif/core": "^4.8.0",
"@oclif/plugin-autocomplete": "^3.2.40",
"axios": "^1.13.2",
"chalk": "^5.6.2",
"cli-table3": "^0.6.5",
Expand Down Expand Up @@ -44,6 +46,9 @@
"commands": "./dist/commands",
"dirname": "",
"topicSeparator": " ",
"plugins": [
"@oclif/plugin-autocomplete"
],
"hooks": {
"init": "./dist/hooks/init/version-check"
},
Expand Down
156 changes: 95 additions & 61 deletions packages/cli/src/commands/auth/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Store an existing private key in OS keyring
*/

import { Command } from "@oclif/core";
import { Command, Flags } from "@oclif/core";
import { confirm, select } from "@inquirer/prompts";
import {
storePrivateKey,
Expand All @@ -22,81 +22,112 @@ import { withTelemetry } from "../../telemetry";
export default class AuthLogin extends Command {
static description = "Store your private key in OS keyring";

static examples = ["<%= config.bin %> <%= command.id %>"];
static examples = [
"<%= config.bin %> auth login",
"<%= config.bin %> auth login --private-key 0x...",
"<%= config.bin %> auth login --private-key 0x... --force",
];

static flags = {
"private-key": Flags.string({
description: "Private key to store (skips interactive prompt)",
env: "ECLOUD_PRIVATE_KEY",
}),
force: Flags.boolean({
Comment thread
Gajesh2007 marked this conversation as resolved.
description: "Skip all confirmation prompts",
default: false,
}),
};

async run(): Promise<void> {
return withTelemetry(this, async () => {
const { flags } = await this.parse(AuthLogin);
const isNonInteractive = !!flags["private-key"];

// Check if key already exists
const exists = await keyExists();

if (exists) {
displayWarning([
"WARNING: A private key for ecloud already exists!",
"Replacing it will cause PERMANENT DATA LOSS if not backed up.",
"The previous key will be lost forever.",
]);

const confirmReplace = await confirm({
message: "Replace existing key?",
default: false,
});
if (isNonInteractive) {
if (!flags.force) {
this.error(
"A private key already exists. Use --force to replace it.",
);
}
} else {
displayWarning([
"WARNING: A private key for ecloud already exists!",
"Replacing it will cause PERMANENT DATA LOSS if not backed up.",
"The previous key will be lost forever.",
]);

const confirmReplace = await confirm({
message: "Replace existing key?",
default: false,
});

if (!confirmReplace) {
this.log("\nLogin cancelled.");
return;
if (!confirmReplace) {
this.log("\nLogin cancelled.");
return;
}
}
}

// Check for legacy keys from eigenx-cli
const legacyKeys = await getLegacyKeys();
let privateKey: string | null = null;
let selectedKey: LegacyKey | null = null;

if (legacyKeys.length > 0) {
this.log("\nFound legacy keys from eigenx-cli:");
this.log("");
if (isNonInteractive) {
// Use flag value directly
privateKey = flags["private-key"]!;
} else {
// Check for legacy keys from eigenx-cli
const legacyKeys = await getLegacyKeys();

// Display legacy keys
for (const key of legacyKeys) {
this.log(` Address: ${key.address}`);
this.log(` Environment: ${key.environment}`);
this.log(` Source: ${key.source}`);
if (legacyKeys.length > 0) {
this.log("\nFound legacy keys from eigenx-cli:");
this.log("");
}

const importLegacy = await confirm({
message: "Would you like to import one of these legacy keys?",
default: false,
});

if (importLegacy) {
// Create choices for selection
const choices = legacyKeys.map((key) => ({
name: `${key.address} (${key.environment} - ${key.source})`,
value: key,
}));
// Display legacy keys
for (const key of legacyKeys) {
this.log(` Address: ${key.address}`);
this.log(` Environment: ${key.environment}`);
this.log(` Source: ${key.source}`);
this.log("");
}

selectedKey = await select<LegacyKey>({
message: "Select a key to import:",
choices,
const importLegacy = await confirm({
message: "Would you like to import one of these legacy keys?",
default: false,
});

// Retrieve the actual private key
privateKey = await getLegacyPrivateKey(selectedKey.environment, selectedKey.source);
if (importLegacy) {
// Create choices for selection
const choices = legacyKeys.map((key) => ({
name: `${key.address} (${key.environment} - ${key.source})`,
value: key,
}));

if (!privateKey) {
this.error(`Failed to retrieve legacy key for ${selectedKey.environment}`);
}
selectedKey = await select<LegacyKey>({
message: "Select a key to import:",
choices,
});

this.log(`\nImporting key from ${selectedKey.source}:${selectedKey.environment}`);
}
}
// Retrieve the actual private key
privateKey = await getLegacyPrivateKey(selectedKey.environment, selectedKey.source);

// If no legacy key was selected, prompt for private key input
if (!privateKey) {
privateKey = await getHiddenInput("Enter your private key:");
if (!privateKey) {
this.error(`Failed to retrieve legacy key for ${selectedKey.environment}`);
}

this.log(`\nImporting key from ${selectedKey.source}:${selectedKey.environment}`);
}
}

privateKey = privateKey.trim();
// If no legacy key was selected, prompt for private key input
if (!privateKey) {
privateKey = await getHiddenInput("Enter your private key:");
privateKey = privateKey.trim();
}
}

if (!validatePrivateKey(privateKey)) {
Expand All @@ -108,14 +139,16 @@ export default class AuthLogin extends Command {

this.log(`\nAddress: ${address}`);

const confirmStore = await confirm({
message: "Store this key in OS keyring?",
default: true,
});
if (!isNonInteractive) {
const confirmStore = await confirm({
message: "Store this key in OS keyring?",
default: true,
});

if (!confirmStore) {
this.log("\nLogin cancelled.");
return;
if (!confirmStore) {
this.log("\nLogin cancelled.");
return;
}
}

// Store in keyring
Expand All @@ -129,12 +162,13 @@ export default class AuthLogin extends Command {
// Ask if user wants to delete the legacy key (only if save was successful)
if (selectedKey) {
this.log("");
const confirmDelete = await confirm({

const shouldDelete = flags.force || await confirm({
message: `Delete the legacy key from ${selectedKey.source}:${selectedKey.environment}?`,
default: false,
});

if (confirmDelete) {
if (shouldDelete) {
const deleted = await deleteLegacyPrivateKey(
selectedKey.environment,
selectedKey.source,
Expand Down
114 changes: 79 additions & 35 deletions packages/cli/src/commands/auth/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Migrate a legacy eigenx-cli key to ecloud
*/

import { Command } from "@oclif/core";
import { Command, Flags } from "@oclif/core";
import { confirm, select } from "@inquirer/prompts";
import {
storePrivateKey,
Expand All @@ -21,10 +21,30 @@ import { withTelemetry } from "../../telemetry";
export default class AuthMigrate extends Command {
static description = "Migrate a private key from eigenx-cli to ecloud";

static examples = ["<%= config.bin %> <%= command.id %>"];
static examples = [
"<%= config.bin %> auth migrate",
"<%= config.bin %> auth migrate --environment sepolia",
"<%= config.bin %> auth migrate --environment sepolia --delete-legacy --force",
];

static flags = {
environment: Flags.string({
description: "Environment of the legacy key to migrate (e.g. sepolia, mainnet)",
}),
"delete-legacy": Flags.boolean({
description: "Delete the legacy key after migration",
default: false,
}),
force: Flags.boolean({
description: "Skip all confirmation prompts",
default: false,
}),
};

async run(): Promise<void> {
return withTelemetry(this, async () => {
const { flags } = await this.parse(AuthMigrate);

const legacyKeys = await getLegacyKeys();

if (legacyKeys.length === 0) {
Expand All @@ -46,16 +66,31 @@ export default class AuthMigrate extends Command {
this.log("");
}

// Create choices for selection
const choices = legacyKeys.map((key) => ({
name: `${key.address} (${key.environment} - ${key.source})`,
value: key,
}));

const selectedKey = await select<LegacyKey>({
message: "Select a key to migrate:",
choices,
});
let selectedKey: LegacyKey;

if (flags.environment) {
// Match by environment flag
const match = legacyKeys.find(
(k) => k.environment.toLowerCase() === flags.environment!.toLowerCase(),
);
if (!match) {
this.error(
`No legacy key found for environment '${flags.environment}'. Available: ${legacyKeys.map((k) => k.environment).join(", ")}`,
);
}
selectedKey = match;
} else {
// Interactive selection
const choices = legacyKeys.map((key) => ({
name: `${key.address} (${key.environment} - ${key.source})`,
value: key,
}));

selectedKey = await select<LegacyKey>({
message: "Select a key to migrate:",
choices,
});
}

// Retrieve the actual private key
const privateKey = await getLegacyPrivateKey(selectedKey.environment, selectedKey.source);
Expand All @@ -73,21 +108,25 @@ export default class AuthMigrate extends Command {
const exists = await keyExists();

if (exists) {
this.log("");
displayWarning([
"WARNING: A private key for ecloud already exists!",
"Replacing it will cause PERMANENT DATA LOSS if not backed up.",
"The previous key will be lost forever.",
]);

const confirmReplace = await confirm({
message: "Replace existing ecloud key?",
default: false,
});

if (!confirmReplace) {
this.log("\nMigration cancelled.");
return;
if (flags.force) {
// Skip confirmation
} else {
this.log("");
displayWarning([
"WARNING: A private key for ecloud already exists!",
"Replacing it will cause PERMANENT DATA LOSS if not backed up.",
"The previous key will be lost forever.",
]);

const confirmReplace = await confirm({
message: "Replace existing ecloud key?",
default: false,
});

if (!confirmReplace) {
this.log("\nMigration cancelled.");
return;
}
}
}

Expand All @@ -98,14 +137,19 @@ export default class AuthMigrate extends Command {
this.log(`✓ Address: ${address}`);
this.log("\nNote: This key will be used for all environments (mainnet, sepolia, etc.)");

// Ask if user wants to delete the legacy key (only if save was successful)
this.log("");
const confirmDelete = await confirm({
message: `Delete the legacy key from ${selectedKey.source}:${selectedKey.environment}?`,
default: false,
});

if (confirmDelete) {
// Delete legacy key
const shouldDelete =
flags["delete-legacy"] ||
(!flags.force &&
(await (async () => {
this.log("");
return confirm({
message: `Delete the legacy key from ${selectedKey.source}:${selectedKey.environment}?`,
default: false,
});
})()));

if (shouldDelete) {
const deleted = await deleteLegacyPrivateKey(selectedKey.environment, selectedKey.source);

if (deleted) {
Expand Down
Loading
Loading