🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@getalby/cli

Package Overview
Dependencies
Maintainers
4
Versions
15
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@getalby/cli - npm Package Compare versions

Comparing version
0.2.4
to
0.3.0
+66
build/commands/auth.js
import { NWCClient } from "@getalby/sdk";
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import { getConnectionSecretPath, getPendingConnectionRelayPath, getPendingConnectionSecretPath, handleError, } from "../utils.js";
import { generateSecretKey, getPublicKey } from "nostr-tools";
import { bytesToHex, hexToBytes } from "@noble/hashes/utils.js";
export function registerAuthCommand(program) {
program
.command("auth <wallet-url>")
.description("Securely connect a wallet with human confirmation via the browser\n\n" +
" Step 1: npx @getalby/cli auth https://my.albyhub.com --app-name MyApp\n" +
" Step 2: after human confirmation, run any command to finalize the connection")
.option("--app-name <name>", 'Name of the agent or app that will use this wallet (e.g. "Claude Code")')
.option("--relay-url <url>", "Relay URL for the pending connection", "wss://relay.getalby.com/v1")
.option("--force", "Overwrite existing connection secret")
.option("--remove-pending", "Remove a pending connection and start fresh")
.action(async (walletUrl, options) => {
await handleError(async () => {
const walletName = program.opts().walletName;
const connectionSecretPath = getConnectionSecretPath(walletName);
const pendingSecretPath = getPendingConnectionSecretPath(walletName);
const pendingRelayPath = getPendingConnectionRelayPath(walletName);
// Remove pending connection
if (options.removePending) {
if (!existsSync(pendingSecretPath)) {
console.log(`No pending connection found at ${pendingSecretPath}`);
}
else {
rmSync(pendingSecretPath);
console.log(`Removed pending connection at ${pendingSecretPath}`);
}
if (existsSync(pendingRelayPath)) {
rmSync(pendingRelayPath);
}
return;
}
// Generate auth URL
if (!options.appName) {
console.error(`Error: No app name provided.\n` +
`Add --app-name <name> to identify the app in the wallet.`);
process.exit(1);
}
if (existsSync(connectionSecretPath) && !options.force) {
console.error(`Error: Already connected. Connection secret exists at ${connectionSecretPath}\n` +
`To overwrite, use --force.`);
process.exit(1);
}
const secret = bytesToHex(generateSecretKey());
const pubkey = getPublicKey(hexToBytes(secret));
const authUrl = NWCClient.getAuthorizationUrl(`${walletUrl}/apps/new`, { name: options.appName }, pubkey).toString();
const dir = join(homedir(), ".alby-cli");
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true });
}
writeFileSync(pendingSecretPath, secret, { mode: 0o600 });
writeFileSync(pendingRelayPath, options.relayUrl, { mode: 0o600 });
console.log("Click the following URL to approve the connection in your wallet:\n" +
authUrl);
const retryCmd = walletName
? `npx @getalby/cli get-balance --wallet-name ${walletName}`
: `npx @getalby/cli get-balance`;
console.log(`\nOnce approved, run any command, e.g.:\n ${retryCmd}`);
});
});
}
import { NWCClient } from "@getalby/sdk";
import { existsSync } from "node:fs";
import { getConnectionSecretPath, handleError, saveConnectionSecret, testAndLogConnection, } from "../utils.js";
export function registerConnectCommand(program) {
program
.command('connect "[connection-secret]"')
.description("Connect to a Nostr Wallet Connect wallet")
.option("--force", "Overwrite existing connection secret")
.action(async (connectionSecret, options) => {
await handleError(async () => {
const connectionSecretPath = getConnectionSecretPath(program.opts().walletName);
if (existsSync(connectionSecretPath) && !options.force) {
console.error(`Error: Already connected. Connection secret exists at ${connectionSecretPath}\n` +
`To overwrite, use --force.`);
process.exit(1);
}
if (!connectionSecret) {
console.error(`Usage: npx @getalby/cli connect "<connection-secret>"\n` +
`Provide a NWC connection secret (nostr+walletconnect://...)`);
process.exit(1);
}
if (!connectionSecret.startsWith("nostr+walletconnect://")) {
console.error(`Error: Invalid connection secret. Expected format: nostr+walletconnect://...`);
process.exit(1);
}
const client = new NWCClient({
nostrWalletConnectUrl: connectionSecret,
});
if (!client.secret || !/^[0-9a-f]{64}$/i.test(client.secret)) {
console.error(`Error: Invalid connection secret. Missing or invalid secret key.`);
process.exit(1);
}
if (!client.walletPubkey ||
!/^[0-9a-f]{64}$/i.test(client.walletPubkey)) {
console.error(`Error: Invalid connection secret. Missing or invalid wallet pubkey.`);
process.exit(1);
}
if (!client.relayUrls?.length) {
console.error(`Error: Invalid connection secret. Missing relay URL.`);
process.exit(1);
}
await testAndLogConnection(client);
saveConnectionSecret(connectionSecretPath, connectionSecret, program.opts().verbose);
});
});
}
+1
-1

@@ -10,3 +10,3 @@ import { cancelHoldInvoice } from "../tools/nwc/cancel_hold_invoice.js";

await handleError(async () => {
const client = getClient(program);
const client = await getClient(program);
const result = await cancelHoldInvoice(client, {

@@ -13,0 +13,0 @@ payment_hash: options.paymentHash,

@@ -13,3 +13,3 @@ import { fetchL402 } from "../tools/lightning/fetch_l402.js";

await handleError(async () => {
const client = getClient(program);
const client = await getClient(program);
const result = await fetchL402(client, {

@@ -16,0 +16,0 @@ url: options.url,

@@ -9,3 +9,3 @@ import { getBalance } from "../tools/nwc/get_balance.js";

await handleError(async () => {
const client = getClient(program);
const client = await getClient(program);
const result = await getBalance(client);

@@ -12,0 +12,0 @@ output(result);

@@ -9,3 +9,3 @@ import { getBudget } from "../tools/nwc/get_budget.js";

await handleError(async () => {
const client = getClient(program);
const client = await getClient(program);
const result = await getBudget(client);

@@ -12,0 +12,0 @@ output(result);

@@ -9,3 +9,3 @@ import { getInfo } from "../tools/nwc/get_info.js";

await handleError(async () => {
const client = getClient(program);
const client = await getClient(program);
const result = await getInfo(client);

@@ -12,0 +12,0 @@ output(result);

@@ -9,3 +9,3 @@ import { getWalletServiceInfo } from "../tools/nwc/get_wallet_service_info.js";

await handleError(async () => {
const client = getClient(program);
const client = await getClient(program);
const result = await getWalletServiceInfo(client);

@@ -12,0 +12,0 @@ output(result);

@@ -15,3 +15,3 @@ import { listTransactions } from "../tools/nwc/list_transactions.js";

await handleError(async () => {
const client = getClient(program);
const client = await getClient(program);
const result = await listTransactions(client, {

@@ -18,0 +18,0 @@ from: options.from,

@@ -15,3 +15,3 @@ import { lookupInvoice } from "../tools/nwc/lookup_invoice.js";

}
const client = getClient(program);
const client = await getClient(program);
const result = await lookupInvoice(client, {

@@ -18,0 +18,0 @@ payment_hash: options.paymentHash,

@@ -13,3 +13,3 @@ import { makeHoldInvoice } from "../tools/nwc/make_hold_invoice.js";

await handleError(async () => {
const client = getClient(program);
const client = await getClient(program);
const result = await makeHoldInvoice(client, {

@@ -16,0 +16,0 @@ amount_in_sats: options.amount,

@@ -12,3 +12,3 @@ import { makeInvoice } from "../tools/nwc/make_invoice.js";

await handleError(async () => {
const client = getClient(program);
const client = await getClient(program);
const result = await makeInvoice(client, {

@@ -15,0 +15,0 @@ amount_in_sats: options.amount,

@@ -11,3 +11,3 @@ import { payInvoice } from "../tools/nwc/pay_invoice.js";

await handleError(async () => {
const client = getClient(program);
const client = await getClient(program);
const result = await payInvoice(client, {

@@ -14,0 +14,0 @@ invoice: options.invoice,

@@ -13,3 +13,3 @@ import { payKeysend } from "../tools/nwc/pay_keysend.js";

await handleError(async () => {
const client = getClient(program);
const client = await getClient(program);
let tlvRecords;

@@ -16,0 +16,0 @@ if (options.tlvRecords) {

@@ -10,3 +10,3 @@ import { settleHoldInvoice } from "../tools/nwc/settle_hold_invoice.js";

await handleError(async () => {
const client = getClient(program);
const client = await getClient(program);
const result = await settleHoldInvoice(client, {

@@ -13,0 +13,0 @@ preimage: options.preimage,

@@ -10,3 +10,3 @@ import { signMessage } from "../tools/nwc/sign_message.js";

await handleError(async () => {
const client = getClient(program);
const client = await getClient(program);
const result = await signMessage(client, {

@@ -13,0 +13,0 @@ message: options.message,

@@ -12,3 +12,3 @@ import { waitForPayment } from "../tools/nwc/wait_for_payment.js";

await handleError(async () => {
const client = getClient(program);
const client = await getClient(program);
const result = await waitForPayment(client, {

@@ -15,0 +15,0 @@ payment_hash: options.paymentHash,

@@ -23,29 +23,49 @@ #!/usr/bin/env node

import { registerFetchL402Command } from "./commands/fetch-l402.js";
import { registerConnectCommand } from "./commands/connect.js";
import { registerAuthCommand } from "./commands/auth.js";
const program = new Command();
program
.name("alby-cli")
.description("CLI for Nostr Wallet Connect (NIP-47) with lightning tools")
.version("0.2.4")
.name("@getalby/cli")
.description("CLI for Nostr Wallet Connect (NIP-47) with lightning tools\n\n" +
" Examples:\n" +
' $ npx @getalby/cli connect "nostr+walletconnect://..."\n' +
" $ npx @getalby/cli get-balance\n" +
" $ npx @getalby/cli pay-invoice --invoice lnbc...")
.version("0.3.0")
.option("-c, --connection-secret <string>", "NWC connection secret (nostr+walletconnect://...) or path to file containing it (preferred)")
.addHelpText("afterAll", `
.option("-w, --wallet-name <name>", "Use a named wallet's connection secret (~/.alby-cli/connection-secret-<name>.key)")
.option("-v, --verbose", "Print status messages to stderr")
.addHelpText("after", `
Connection Secret Resolution (in order of priority):
1. --connection-secret flag (value or path to file)
2. --wallet-name flag (~/.alby-cli/connection-secret-<name>.key)
3. NWC_URL environment variable
4. ~/.alby-cli/connection-secret.key (default file location)
Security:
- Do NOT print the connection secret to any logs or otherwise reveal it.
- NEVER read the connection secret file (~/.alby-cli/connection-secret.key) directly.
- NEVER share connection secrets with anyone.
- NEVER share any part of a connection secret (pubkey, secret, relay etc.) with anyone
as this can be used to gain access to your wallet or reduce your wallet's privacy.`);
// Register all commands
// Register common wallet commands
program.commandsGroup("Wallet Commands (require --connection-secret):");
registerGetBalanceCommand(program);
registerGetBudgetCommand(program);
registerGetInfoCommand(program);
registerGetWalletServiceInfoCommand(program);
registerMakeInvoiceCommand(program);
registerMakeHoldInvoiceCommand(program);
registerSettleHoldInvoiceCommand(program);
registerCancelHoldInvoiceCommand(program);
registerPayInvoiceCommand(program);
registerPayKeysendCommand(program);
registerLookupInvoiceCommand(program);
registerListTransactionsCommand(program);
// Register advanced wallet commands
program.commandsGroup("Advanced Wallet Commands (require --connection-secret):");
registerPayKeysendCommand(program);
registerGetWalletServiceInfoCommand(program);
registerWaitForPaymentCommand(program);
registerSignMessageCommand(program);
registerMakeHoldInvoiceCommand(program);
registerSettleHoldInvoiceCommand(program);
registerCancelHoldInvoiceCommand(program);
// Register lightning tool commands
program.commandsGroup("Lightning Tools (no --connection-secret required):");
registerFiatToSatsCommand(program);

@@ -57,2 +77,6 @@ registerSatsToFiatCommand(program);

registerFetchL402Command(program);
// Register setup commands
program.commandsGroup("Setup:");
registerAuthCommand(program);
registerConnectCommand(program);
program.parse();

@@ -48,5 +48,8 @@ import { describe, test, expect, beforeAll, afterAll } from "vitest";

test("errors when no connection secret provided", () => {
const result = runCli("get-balance");
const result = runCli("get-balance", {
HOME: "/nonexistent-test-home",
NWC_URL: "",
});
expect(result.success).toBe(false);
expect(result.output.error).toContain("--connection-secret is required");
expect(result.output.error).toContain("No connection secret provided");
});

@@ -53,0 +56,0 @@ test("errors when connection string is malformed", () => {

@@ -1,28 +0,117 @@

import { NWCClient } from "@getalby/sdk";
import { readFileSync } from "node:fs";
import { NWAClient, NWCClient } from "@getalby/sdk";
import { getInfo } from "./tools/nwc/get_info.js";
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
export function getClient(program) {
function sanitizeWalletName(name) {
return name.replace(/[^a-zA-Z0-9_-]/g, "_");
}
export function getConnectionSecretPath(name) {
const filename = name
? `connection-secret-${sanitizeWalletName(name)}.key`
: "connection-secret.key";
return join(homedir(), ".alby-cli", filename);
}
export function getPendingConnectionSecretPath(name) {
const filename = name
? `pending-connection-secret-${sanitizeWalletName(name)}.key`
: "pending-connection-secret.key";
return join(homedir(), ".alby-cli", filename);
}
export function getPendingConnectionRelayPath(name) {
const filename = name
? `pending-connection-relay-${sanitizeWalletName(name)}.txt`
: "pending-connection-relay.txt";
return join(homedir(), ".alby-cli", filename);
}
export function saveConnectionSecret(path, secret, verbose) {
const alreadyExists = existsSync(path);
const dir = join(homedir(), ".alby-cli");
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true });
}
writeFileSync(path, secret, { mode: 0o600 });
if (alreadyExists) {
chmodSync(path, 0o600);
}
if (verbose) {
console.error(`Connection saved to ${path}`);
}
}
export async function testAndLogConnection(client) {
console.log("Testing connection...");
const info = await getInfo(client);
console.log(`Connected to ${info.alias || "wallet"} (${info.network || "unknown network"})`);
}
export async function completePendingConnection(pendingSecretPath, connectionSecretPath, relayUrl, verbose, pendingRelayPath) {
const secret = readFileSync(pendingSecretPath, "utf-8").trim();
const DEFAULT_RELAY = "wss://relay.getalby.com/v1";
if (!relayUrl && pendingRelayPath && existsSync(pendingRelayPath)) {
relayUrl = readFileSync(pendingRelayPath, "utf-8").trim();
}
const resolvedRelay = relayUrl ?? DEFAULT_RELAY;
const nwaClient = new NWAClient({
appSecretKey: secret,
relayUrls: [resolvedRelay],
requestMethods: [],
});
return new Promise((resolve, reject) => {
let settled = false;
const timer = setTimeout(() => {
if (settled)
return;
settled = true;
unsub?.();
reject(new Error("Timed out waiting for wallet approval.\n\nTo retry, run the command again.\nTo cancel: npx @getalby/cli auth --remove-pending"));
}, 5000);
let unsub;
nwaClient
.subscribe({
onSuccess: async (nwcClient) => {
if (settled)
return;
settled = true;
clearTimeout(timer);
unsub?.();
saveConnectionSecret(connectionSecretPath, nwcClient.getNostrWalletConnectUrl(), verbose);
rmSync(pendingSecretPath);
if (pendingRelayPath && existsSync(pendingRelayPath)) {
rmSync(pendingRelayPath);
}
resolve(nwcClient);
},
})
.then(({ unsub: u }) => {
unsub = u;
});
});
}
export async function getClient(program) {
const opts = program.opts();
let connectionSecret = opts.connectionSecret;
// Check environment variables if --connection-secret not provided
if (!connectionSecret) {
const walletName = opts.walletName;
const connectionPath = getConnectionSecretPath(walletName);
const pendingPath = getPendingConnectionSecretPath(walletName);
if (!connectionSecret && !walletName) {
connectionSecret = process.env.NWC_URL;
}
if (!connectionSecret) {
const defaultPath = join(homedir(), ".alby-cli", "connection-secret.key");
try {
connectionSecret = readFileSync(defaultPath, "utf-8").trim();
connectionSecret = readFileSync(connectionPath, "utf-8").trim();
}
catch (error) {
const err = error;
if (err.code !== "ENOENT") {
// only throw an error if it's not a file not found error
if (err.code !== "ENOENT")
throw err;
}
}
}
if (!connectionSecret && existsSync(pendingPath)) {
if (opts.verbose) {
console.error("Pending connection found. Waiting for wallet approval...");
}
const pendingRelayPath = getPendingConnectionRelayPath(walletName);
return await completePendingConnection(pendingPath, connectionPath, undefined, opts.verbose, pendingRelayPath);
}
if (!connectionSecret) {
console.error("Error: No connection secret found. Pass -c <secret>, set NWC_URL, or create ~/.alby-cli/connection-secret.key");
process.exit(1);
throw new Error("No connection secret provided. Pass -c <secret or file path>, set NWC_URL, use --wallet-name <name>, or create ~/.alby-cli/connection-secret.key");
}

@@ -29,0 +118,0 @@ // Auto-detect: if it doesn't start with the protocol, treat as file path

@@ -5,8 +5,7 @@ {

"repository": "https://github.com/getAlby/cli.git",
"version": "0.2.4",
"version": "0.3.0",
"type": "module",
"main": "build/index.js",
"bin": {
"cli": "build/index.js",
"alby-cli": "build/index.js"
"cli": "build/index.js"
},

@@ -37,6 +36,11 @@ "files": [

"license": "MIT",
"engines": {
"node": ">=20"
},
"dependencies": {
"@getalby/lightning-tools": "^7.0.2",
"@getalby/sdk": "^7.0.0",
"commander": "^13.1.0"
"@noble/hashes": "^2.0.1",
"commander": "^14.0.3",
"nostr-tools": "^2.23.3"
},

@@ -43,0 +47,0 @@ "devDependencies": {

@@ -9,4 +9,54 @@ # Alby NWC CLI

### First-time setup
The CLI is an interface to a wallet and therefore needs a connection secret.
**Option 1: `auth` — for wallets that support it (e.g. Alby Hub)**
```bash
# Pass a file path to a connection secret (preferred)
# Step 1: generate a connection URL and open it in your wallet to approve
# --app-name is the name of the agent/app that will use the wallet via the CLI (e.g. "Claude Code", "OpenClaw")
npx @getalby/cli auth https://my.albyhub.com --app-name "Claude Code"
# Step 2: after approving in the wallet, complete the connection
npx @getalby/cli auth --complete
```
**Option 2: `connect` — paste a NWC connection secret directly**
```bash
npx @getalby/cli connect "nostr+walletconnect://..."
```
### Multiple wallets
Use `--wallet-name` when setting up to save named connections:
```bash
npx @getalby/cli connect "nostr+walletconnect://..." --wallet-name work
npx @getalby/cli auth https://my.albyhub.com --app-name "Claude Code" --wallet-name personal
```
Then pass `--wallet-name` to any command to use that wallet:
```bash
npx @getalby/cli --wallet-name work get-balance
npx @getalby/cli --wallet-name personal pay-invoice --invoice lnbc...
```
### Connection secret resolution (in order of priority)
1. `--connection-secret` flag (value or path to file)
2. `--wallet-name` flag (`~/.alby-cli/connection-secret-<name>.key`)
3. `NWC_URL` environment variable
4. `~/.alby-cli/connection-secret.key` (default file location)
```bash
# Use the default saved wallet connection (preferred)
npx @getalby/cli <command> [options]
# Use a named wallet
npx @getalby/cli --wallet-name alice <command> [options]
# Pass a file path to a connection secret
npx @getalby/cli -c /path/to/secret.txt <command> [options]

@@ -20,3 +70,3 @@

You can also pass a connection string via the `NWC_URL` environment variable instead of using the `-c` option:
You can also set the `NWC_URL` environment variable instead of using the `-c` option:

@@ -47,3 +97,3 @@ ```txt

These commands require `--connection-secret`:
These commands require a wallet connection (`-c`, `--wallet-name`, or `NWC_URL`):

@@ -128,3 +178,3 @@ ```bash

These require `-c` or `--connection-secret`:
These require a wallet connection (`-c`, `--wallet-name`, or `NWC_URL`):

@@ -148,3 +198,3 @@ | Command | Description | Required Options |

These require `-c` or `--connection-secret`:
These require a wallet connection (`-c`, `--wallet-name`, or `NWC_URL`):

@@ -151,0 +201,0 @@ | Command | Description | Required Options |