From 0067244bd4d02088e7b909374fa98f7a03909501 Mon Sep 17 00:00:00 2001 From: crumb Date: Thu, 11 Jun 2026 15:19:12 -0700 Subject: [PATCH 1/3] fix: improve browser fallback and install guidance --- README.md | 26 ++++++++++++++++ scripts/install-release.sh | 6 +++- scripts/install.sh | 8 +++-- src/auth/loopback.ts | 61 ++++++++++++++++++++++++++----------- src/cli.ts | 7 ++++- src/commands/wallet.test.ts | 61 +++++++++++++++++++++++++++++++++++++ src/commands/wallet.ts | 16 +++++++++- 7 files changed, 162 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 629a122..ace8c8e 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,10 @@ writes from a terminal or automation workflow. ### Shell Script +> **Note:** The installer requires a POSIX shell (`bash`/`sh`). On Windows, run this +> command inside [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) +> or Git Bash. + ```bash curl -fsSL https://account.megaeth.com/install | sh ``` @@ -37,6 +41,28 @@ pnpm build Requires Node.js 22 or newer and pnpm. +## Uninstall + +Remove the CLI, installed releases, and the wrapper script: + +```bash +curl -fsSL https://raw.githubusercontent.com/megaeth-labs/wallet-cli/main/scripts/uninstall.sh | sh +``` + +To also remove local wallet profiles and delegated key material: + +```bash +curl -fsSL https://raw.githubusercontent.com/megaeth-labs/wallet-cli/main/scripts/uninstall.sh | sh -- --config +``` + +If you installed from source: + +```bash +./scripts/uninstall.sh +# or with profile cleanup: +./scripts/uninstall.sh --config +``` + ## Quick Start ```bash diff --git a/scripts/install-release.sh b/scripts/install-release.sh index 231bf4a..c208152 100755 --- a/scripts/install-release.sh +++ b/scripts/install-release.sh @@ -173,7 +173,11 @@ check_prerequisites() { node_major="$(node -p 'Number(process.versions.node.split(".")[0])')" if [ "$node_major" -lt "$required_node_major" ]; then - error "Node.js >= $required_node_major is required; found major version $node_major" + error "Node.js >= $required_node_major is required, but you have Node.js $(node -v 2>/dev/null || echo unknown). + +To install Node.js 22: + • Using nvm: nvm install 22 + • Using apt: curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - && sudo apt-get install -y nodejs" fi } diff --git a/scripts/install.sh b/scripts/install.sh index 8755401..82cc9a4 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -177,7 +177,11 @@ install_node_with_brew() { fi if ! confirm_install "$reason Install or upgrade Node.js with Homebrew now?"; then - echo "Node.js >= $required_node_major is required" >&2 + echo "Node.js >= $required_node_major is required, but you have Node.js $(node -v 2>/dev/null || echo unknown)." >&2 + echo >&2 + echo "To install Node.js 22:" >&2 + echo " • Using nvm: nvm install 22" >&2 + echo " • Using apt: curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - && sudo apt-get install -y nodejs" >&2 exit 1 fi @@ -201,7 +205,7 @@ ensure_node() { current_major="$(node_major_version)" if [ "$current_major" -lt "$required_node_major" ]; then - install_node_with_brew "Node.js >= $required_node_major is required; found major version $current_major." + install_node_with_brew "Node.js >= $required_node_major is required, but you have Node.js $(node -v 2>/dev/null || echo unknown)." fi } diff --git a/src/auth/loopback.ts b/src/auth/loopback.ts index a3c70b1..85a89ed 100644 --- a/src/auth/loopback.ts +++ b/src/auth/loopback.ts @@ -103,7 +103,7 @@ export type DelegatedKeyPair = { accessAddress: HexString; }; -export type BrowserOpener = (url: string) => Promise | void; +export type BrowserOpener = (url: string) => Promise | boolean | void; export type LoopbackLoginOptions = { network: Network; @@ -316,7 +316,18 @@ export async function runLoopbackLogin( waitForCallback.catch(() => undefined); try { - await (options.openBrowser ?? openSystemBrowser)(authUrl); + const browserOpened = await (options.openBrowser ?? openSystemBrowser)(authUrl); + if (browserOpened === false) { + process.stderr.write( + `⚠️ Could not open a browser automatically. +Open this URL in your browser to continue: + +${authUrl} + +Waiting for approval... +`, + ); + } const callback = await waitForCallback; if (callback.status !== "approved") { @@ -397,7 +408,18 @@ export async function authorizeLoopbackKey( waitForCallback.catch(() => undefined); try { - await (options.openBrowser ?? openSystemBrowser)(authUrl); + const browserOpened = await (options.openBrowser ?? openSystemBrowser)(authUrl); + if (browserOpened === false) { + process.stderr.write( + `⚠️ Could not open a browser automatically. +Open this URL in your browser to continue: + +${authUrl} + +Waiting for approval... +`, + ); + } const callback = await waitForCallback; if (callback.status !== "approved") { @@ -526,7 +548,18 @@ export async function runLoopbackRevoke( waitForCallback.catch(() => undefined); try { - await (options.openBrowser ?? openSystemBrowser)(authUrl); + const browserOpened = await (options.openBrowser ?? openSystemBrowser)(authUrl); + if (browserOpened === false) { + process.stderr.write( + `⚠️ Could not open a browser automatically. +Open this URL in your browser to continue: + +${authUrl} + +Waiting for approval... +`, + ); + } const callback = await waitForCallback; if (callback.status !== "approved") { @@ -908,27 +941,19 @@ export function keccak256(input: Uint8Array): Buffer { return output; } -export async function openSystemBrowser(url: string): Promise { +export async function openSystemBrowser(url: string): Promise { const { command, args } = browserCommand(url); const child = spawn(command, args, { - stdio: ["ignore", "ignore", "pipe"], + stdio: ["ignore", "ignore", "ignore"], windowsHide: true, }); - let stderr = ""; - child.stderr?.on("data", (chunk: Buffer) => { - stderr += chunk.toString("utf8"); + const result = await new Promise((resolve) => { + child.once("error", () => resolve(false)); + child.once("close", (code) => resolve(code === 0)); }); - const code = await new Promise((resolve, reject) => { - child.once("error", reject); - child.once("close", resolve); - }); - - if (code !== 0) { - const message = stderr.trim() || `${command} exited with status ${code}`; - throw new CliError(`failed to open browser: ${message}`); - } + return result; } function handleCallbackRequest( diff --git a/src/cli.ts b/src/cli.ts index 8263be9..1f8b5ed 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,3 +1,5 @@ +import { createRequire } from "node:module"; + import { Command } from "commander"; import { registerWalletCommands } from "./commands/wallet.js"; @@ -5,13 +7,16 @@ import { formatErrorMessage } from "./errors.js"; export const commandName = "mega"; +const require = createRequire(import.meta.url); +const { version } = require("../package.json") as { version: string }; + export function createCli(): Command { const program = new Command(); program .name(commandName) .description("MegaETH MOSS account CLI") - .version("0.1.0") + .version(version) .showHelpAfterError() .exitOverride(); diff --git a/src/commands/wallet.test.ts b/src/commands/wallet.test.ts index 3dd6697..3d198a5 100644 --- a/src/commands/wallet.test.ts +++ b/src/commands/wallet.test.ts @@ -1103,6 +1103,67 @@ describe("wallet status commands", () => { expect(stdout.text).toContain("Network: mainnet"); }); + + it("falls back to a printed auth URL when the browser cannot be opened", async () => { + const env = await tempEnv(); + const stdout = memoryOutput(); + const stderr = memoryOutput({ columns: 80, isTTY: true }); + const program = new Command(); + program.exitOverride(); + registerWalletCommands(program, { + env, + now: () => activeNow, + openBrowser: async (url) => { + const authUrl = new URL(url); + const redirectUri = authUrl.searchParams.get("redirectUri"); + expect(redirectUri).not.toBeNull(); + setTimeout(async () => { + const callbackUrl = new URL(redirectUri!); + callbackUrl.searchParams.set("state", authUrl.searchParams.get("state")!); + callbackUrl.searchParams.set("status", "approved"); + callbackUrl.searchParams.set( + "accountAddress", + "0x1111111111111111111111111111111111111111", + ); + await fetch(callbackUrl); + }, 20); + return false; + }, + stderr, + stdout, + }); + + const originalWrite = process.stderr.write.bind(process.stderr); + let fallbackOutput = ""; + process.stderr.write = ((chunk: string | Uint8Array) => { + fallbackOutput += typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8"); + return true; + }) as typeof process.stderr.write; + + try { + await program.parseAsync([ + "node", + "mega", + "moss", + "login", + "--wallet-url", + "https://wallet.example", + "--wallet-api-url", + "https://wallet-api.example", + "--relay-url", + "https://relay.example", + ]); + } finally { + process.stderr.write = originalWrite; + } + + const plainStderr = stripAnsi(stderr.text + fallbackOutput); + expect(plainStderr).toContain("⚠️ Could not open a browser automatically."); + expect(plainStderr).toContain("Open this URL in your browser to continue:"); + expect(plainStderr).toContain("Waiting for approval..."); + expect(stdout.text).toContain("[ok] MOSS wallet connected"); + }); + it("prints the login intro and auth URL in no-browser mode", async () => { const env = await tempEnv(); const stdout = memoryOutput(); diff --git a/src/commands/wallet.ts b/src/commands/wallet.ts index 96be277..9051581 100644 --- a/src/commands/wallet.ts +++ b/src/commands/wallet.ts @@ -1459,7 +1459,21 @@ function makeBrowserOpener( return; } - await opener(url); + const opened = await opener(url); + if (opened === false) { + getStderr(dependencies).write( + `⚠️ Could not open a browser automatically. +Open this URL in your browser to continue: + +${url} + +Waiting for approval... +`, + ); + return false; + } + + return true; }; } From cde99a962b5365fe1b173a22e5f6eaeed1358a30 Mon Sep 17 00:00:00 2001 From: crumb Date: Thu, 11 Jun 2026 15:44:01 -0700 Subject: [PATCH 2/3] chore: trim local installer Node messaging --- scripts/install.sh | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 82cc9a4..8755401 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -177,11 +177,7 @@ install_node_with_brew() { fi if ! confirm_install "$reason Install or upgrade Node.js with Homebrew now?"; then - echo "Node.js >= $required_node_major is required, but you have Node.js $(node -v 2>/dev/null || echo unknown)." >&2 - echo >&2 - echo "To install Node.js 22:" >&2 - echo " • Using nvm: nvm install 22" >&2 - echo " • Using apt: curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - && sudo apt-get install -y nodejs" >&2 + echo "Node.js >= $required_node_major is required" >&2 exit 1 fi @@ -205,7 +201,7 @@ ensure_node() { current_major="$(node_major_version)" if [ "$current_major" -lt "$required_node_major" ]; then - install_node_with_brew "Node.js >= $required_node_major is required, but you have Node.js $(node -v 2>/dev/null || echo unknown)." + install_node_with_brew "Node.js >= $required_node_major is required; found major version $current_major." fi } From cb10611dd9cee7ff5597ea050b93cfd63adf6805 Mon Sep 17 00:00:00 2001 From: crumb Date: Thu, 11 Jun 2026 15:58:42 -0700 Subject: [PATCH 3/3] docs: tighten install trust signaling copy --- README.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ace8c8e..f0ad10b 100644 --- a/README.md +++ b/README.md @@ -11,17 +11,18 @@ writes from a terminal or automation workflow. ### Shell Script -> **Note:** The installer requires a POSIX shell (`bash`/`sh`). On Windows, run this -> command inside [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) -> or Git Bash. +> **Note:** The installer downloads a versioned GitHub Release asset and verifies +> its `.sha256` checksum before installing. +> +> On Windows, run this command inside +> [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) or Git Bash. ```bash curl -fsSL https://account.megaeth.com/install | sh ``` -The installer downloads the latest release, verifies its checksum, installs the -`mega` command, and installs the bundled agent skill. Add the printed install -directory to `PATH` if needed. +The installer installs the `mega` command and the bundled agent skill. Add the +printed install directory to `PATH` if needed. Install a specific release: @@ -29,6 +30,9 @@ Install a specific release: curl -fsSL https://account.megaeth.com/install | sh -- --version v0.1.0 ``` +Prefer to inspect the artifact manually first? See the +[GitHub Releases page](https://github.com/megaeth-labs/wallet-cli/releases). + ### Build From Source ```bash