@getalby/cli 0.2.3 → 0.3.0

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 CHANGED
@@ -6,8 +6,58 @@ Built for agents - use with the [Alby Bitcoin Payments CLI Skill](https://github
6
6
 
7
7
  ## Usage
8
8
 
9
+ ### First-time setup
10
+
11
+ The CLI is an interface to a wallet and therefore needs a connection secret.
12
+
13
+ **Option 1: `auth` — for wallets that support it (e.g. Alby Hub)**
14
+
15
+ ```bash
16
+ # Step 1: generate a connection URL and open it in your wallet to approve
17
+ # --app-name is the name of the agent/app that will use the wallet via the CLI (e.g. "Claude Code", "OpenClaw")
18
+ npx @getalby/cli auth https://my.albyhub.com --app-name "Claude Code"
19
+
20
+ # Step 2: after approving in the wallet, complete the connection
21
+ npx @getalby/cli auth --complete
22
+ ```
23
+
24
+ **Option 2: `connect` — paste a NWC connection secret directly**
25
+
26
+ ```bash
27
+ npx @getalby/cli connect "nostr+walletconnect://..."
28
+ ```
29
+
30
+ ### Multiple wallets
31
+
32
+ Use `--wallet-name` when setting up to save named connections:
33
+
34
+ ```bash
35
+ npx @getalby/cli connect "nostr+walletconnect://..." --wallet-name work
36
+ npx @getalby/cli auth https://my.albyhub.com --app-name "Claude Code" --wallet-name personal
37
+ ```
38
+
39
+ Then pass `--wallet-name` to any command to use that wallet:
40
+
41
+ ```bash
42
+ npx @getalby/cli --wallet-name work get-balance
43
+ npx @getalby/cli --wallet-name personal pay-invoice --invoice lnbc...
44
+ ```
45
+
46
+ ### Connection secret resolution (in order of priority)
47
+
48
+ 1. `--connection-secret` flag (value or path to file)
49
+ 2. `--wallet-name` flag (`~/.alby-cli/connection-secret-<name>.key`)
50
+ 3. `NWC_URL` environment variable
51
+ 4. `~/.alby-cli/connection-secret.key` (default file location)
52
+
9
53
  ```bash
10
- # Pass a file path to a connection secret (preferred)
54
+ # Use the default saved wallet connection (preferred)
55
+ npx @getalby/cli <command> [options]
56
+
57
+ # Use a named wallet
58
+ npx @getalby/cli --wallet-name alice <command> [options]
59
+
60
+ # Pass a file path to a connection secret
11
61
  npx @getalby/cli -c /path/to/secret.txt <command> [options]
12
62
 
13
63
  # Or pass connection string directly
@@ -16,7 +66,7 @@ npx @getalby/cli -c "nostr+walletconnect://..." <command> [options]
16
66
 
17
67
  The `-c` option auto-detects whether you're passing a connection string or a file path. You can get a connection string from your NWC-compatible wallet (e.g., [Alby](https://getalby.com)).
18
68
 
19
- You can also pass a connection string via the `NWC_URL` environment variable instead of using the `-c` option:
69
+ You can also set the `NWC_URL` environment variable instead of using the `-c` option:
20
70
 
21
71
  ```txt
22
72
  NWC_URL="nostr+walletconnect://..."
@@ -42,7 +92,7 @@ curl -X POST "https://faucet.nwc.dev/wallets/<username>/topup?amount=5000"
42
92
 
43
93
  ### Wallet Commands
44
94
 
45
- These commands require `--connection-secret`:
95
+ These commands require a wallet connection (`-c`, `--wallet-name`, or `NWC_URL`):
46
96
 
47
97
  ```bash
48
98
  # Get wallet balance
@@ -122,7 +172,7 @@ npx @getalby/cli request-invoice-from-lightning-address --address "hello@getalby
122
172
 
123
173
  ### Wallet Commands
124
174
 
125
- These require `-c` or `--connection-secret`:
175
+ These require a wallet connection (`-c`, `--wallet-name`, or `NWC_URL`):
126
176
 
127
177
  | Command | Description | Required Options |
128
178
  | ------------------------- | ------------------------------ | ------------------------------- |
@@ -141,7 +191,7 @@ These require `-c` or `--connection-secret`:
141
191
 
142
192
  ### HOLD Invoice Commands
143
193
 
144
- These require `-c` or `--connection-secret`:
194
+ These require a wallet connection (`-c`, `--wallet-name`, or `NWC_URL`):
145
195
 
146
196
  | Command | Description | Required Options |
147
197
  | --------------------- | --------------------- | ---------------------------- |
@@ -0,0 +1,66 @@
1
+ import { NWCClient } from "@getalby/sdk";
2
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { getConnectionSecretPath, getPendingConnectionRelayPath, getPendingConnectionSecretPath, handleError, } from "../utils.js";
6
+ import { generateSecretKey, getPublicKey } from "nostr-tools";
7
+ import { bytesToHex, hexToBytes } from "@noble/hashes/utils.js";
8
+ export function registerAuthCommand(program) {
9
+ program
10
+ .command("auth <wallet-url>")
11
+ .description("Securely connect a wallet with human confirmation via the browser\n\n" +
12
+ " Step 1: npx @getalby/cli auth https://my.albyhub.com --app-name MyApp\n" +
13
+ " Step 2: after human confirmation, run any command to finalize the connection")
14
+ .option("--app-name <name>", 'Name of the agent or app that will use this wallet (e.g. "Claude Code")')
15
+ .option("--relay-url <url>", "Relay URL for the pending connection", "wss://relay.getalby.com/v1")
16
+ .option("--force", "Overwrite existing connection secret")
17
+ .option("--remove-pending", "Remove a pending connection and start fresh")
18
+ .action(async (walletUrl, options) => {
19
+ await handleError(async () => {
20
+ const walletName = program.opts().walletName;
21
+ const connectionSecretPath = getConnectionSecretPath(walletName);
22
+ const pendingSecretPath = getPendingConnectionSecretPath(walletName);
23
+ const pendingRelayPath = getPendingConnectionRelayPath(walletName);
24
+ // Remove pending connection
25
+ if (options.removePending) {
26
+ if (!existsSync(pendingSecretPath)) {
27
+ console.log(`No pending connection found at ${pendingSecretPath}`);
28
+ }
29
+ else {
30
+ rmSync(pendingSecretPath);
31
+ console.log(`Removed pending connection at ${pendingSecretPath}`);
32
+ }
33
+ if (existsSync(pendingRelayPath)) {
34
+ rmSync(pendingRelayPath);
35
+ }
36
+ return;
37
+ }
38
+ // Generate auth URL
39
+ if (!options.appName) {
40
+ console.error(`Error: No app name provided.\n` +
41
+ `Add --app-name <name> to identify the app in the wallet.`);
42
+ process.exit(1);
43
+ }
44
+ if (existsSync(connectionSecretPath) && !options.force) {
45
+ console.error(`Error: Already connected. Connection secret exists at ${connectionSecretPath}\n` +
46
+ `To overwrite, use --force.`);
47
+ process.exit(1);
48
+ }
49
+ const secret = bytesToHex(generateSecretKey());
50
+ const pubkey = getPublicKey(hexToBytes(secret));
51
+ const authUrl = NWCClient.getAuthorizationUrl(`${walletUrl}/apps/new`, { name: options.appName }, pubkey).toString();
52
+ const dir = join(homedir(), ".alby-cli");
53
+ if (!existsSync(dir)) {
54
+ mkdirSync(dir, { recursive: true });
55
+ }
56
+ writeFileSync(pendingSecretPath, secret, { mode: 0o600 });
57
+ writeFileSync(pendingRelayPath, options.relayUrl, { mode: 0o600 });
58
+ console.log("Click the following URL to approve the connection in your wallet:\n" +
59
+ authUrl);
60
+ const retryCmd = walletName
61
+ ? `npx @getalby/cli get-balance --wallet-name ${walletName}`
62
+ : `npx @getalby/cli get-balance`;
63
+ console.log(`\nOnce approved, run any command, e.g.:\n ${retryCmd}`);
64
+ });
65
+ });
66
+ }
@@ -7,7 +7,7 @@ export function registerCancelHoldInvoiceCommand(program) {
7
7
  .requiredOption("--payment-hash <hex>", "Payment hash (32 bytes hex)")
8
8
  .action(async (options) => {
9
9
  await handleError(async () => {
10
- const client = getClient(program);
10
+ const client = await getClient(program);
11
11
  const result = await cancelHoldInvoice(client, {
12
12
  payment_hash: options.paymentHash,
13
13
  });
@@ -0,0 +1,46 @@
1
+ import { NWCClient } from "@getalby/sdk";
2
+ import { existsSync } from "node:fs";
3
+ import { getConnectionSecretPath, handleError, saveConnectionSecret, testAndLogConnection, } from "../utils.js";
4
+ export function registerConnectCommand(program) {
5
+ program
6
+ .command('connect "[connection-secret]"')
7
+ .description("Connect to a Nostr Wallet Connect wallet")
8
+ .option("--force", "Overwrite existing connection secret")
9
+ .action(async (connectionSecret, options) => {
10
+ await handleError(async () => {
11
+ const connectionSecretPath = getConnectionSecretPath(program.opts().walletName);
12
+ if (existsSync(connectionSecretPath) && !options.force) {
13
+ console.error(`Error: Already connected. Connection secret exists at ${connectionSecretPath}\n` +
14
+ `To overwrite, use --force.`);
15
+ process.exit(1);
16
+ }
17
+ if (!connectionSecret) {
18
+ console.error(`Usage: npx @getalby/cli connect "<connection-secret>"\n` +
19
+ `Provide a NWC connection secret (nostr+walletconnect://...)`);
20
+ process.exit(1);
21
+ }
22
+ if (!connectionSecret.startsWith("nostr+walletconnect://")) {
23
+ console.error(`Error: Invalid connection secret. Expected format: nostr+walletconnect://...`);
24
+ process.exit(1);
25
+ }
26
+ const client = new NWCClient({
27
+ nostrWalletConnectUrl: connectionSecret,
28
+ });
29
+ if (!client.secret || !/^[0-9a-f]{64}$/i.test(client.secret)) {
30
+ console.error(`Error: Invalid connection secret. Missing or invalid secret key.`);
31
+ process.exit(1);
32
+ }
33
+ if (!client.walletPubkey ||
34
+ !/^[0-9a-f]{64}$/i.test(client.walletPubkey)) {
35
+ console.error(`Error: Invalid connection secret. Missing or invalid wallet pubkey.`);
36
+ process.exit(1);
37
+ }
38
+ if (!client.relayUrls?.length) {
39
+ console.error(`Error: Invalid connection secret. Missing relay URL.`);
40
+ process.exit(1);
41
+ }
42
+ await testAndLogConnection(client);
43
+ saveConnectionSecret(connectionSecretPath, connectionSecret, program.opts().verbose);
44
+ });
45
+ });
46
+ }
@@ -10,7 +10,7 @@ export function registerFetchL402Command(program) {
10
10
  .option("-H, --headers <json>", "Additional headers (JSON string)")
11
11
  .action(async (options) => {
12
12
  await handleError(async () => {
13
- const client = getClient(program);
13
+ const client = await getClient(program);
14
14
  const result = await fetchL402(client, {
15
15
  url: options.url,
16
16
  method: options.method,
@@ -6,7 +6,7 @@ export function registerGetBalanceCommand(program) {
6
6
  .description("Get wallet balance")
7
7
  .action(async () => {
8
8
  await handleError(async () => {
9
- const client = getClient(program);
9
+ const client = await getClient(program);
10
10
  const result = await getBalance(client);
11
11
  output(result);
12
12
  });
@@ -6,7 +6,7 @@ export function registerGetBudgetCommand(program) {
6
6
  .description("Get wallet budget information")
7
7
  .action(async () => {
8
8
  await handleError(async () => {
9
- const client = getClient(program);
9
+ const client = await getClient(program);
10
10
  const result = await getBudget(client);
11
11
  output(result);
12
12
  });
@@ -6,7 +6,7 @@ export function registerGetInfoCommand(program) {
6
6
  .description("Get wallet info")
7
7
  .action(async () => {
8
8
  await handleError(async () => {
9
- const client = getClient(program);
9
+ const client = await getClient(program);
10
10
  const result = await getInfo(client);
11
11
  output(result);
12
12
  });
@@ -6,7 +6,7 @@ export function registerGetWalletServiceInfoCommand(program) {
6
6
  .description("Get wallet service capabilities")
7
7
  .action(async () => {
8
8
  await handleError(async () => {
9
- const client = getClient(program);
9
+ const client = await getClient(program);
10
10
  const result = await getWalletServiceInfo(client);
11
11
  output(result);
12
12
  });
@@ -12,7 +12,7 @@ export function registerListTransactionsCommand(program) {
12
12
  .option("-t, --type <type>", "Filter by type (incoming|outgoing)")
13
13
  .action(async (options) => {
14
14
  await handleError(async () => {
15
- const client = getClient(program);
15
+ const client = await getClient(program);
16
16
  const result = await listTransactions(client, {
17
17
  from: options.from,
18
18
  until: options.until,
@@ -12,7 +12,7 @@ export function registerLookupInvoiceCommand(program) {
12
12
  console.error("Error: --payment-hash or --invoice is required");
13
13
  process.exit(1);
14
14
  }
15
- const client = getClient(program);
15
+ const client = await getClient(program);
16
16
  const result = await lookupInvoice(client, {
17
17
  payment_hash: options.paymentHash,
18
18
  invoice: options.invoice,
@@ -10,7 +10,7 @@ export function registerMakeHoldInvoiceCommand(program) {
10
10
  .option("-e, --expiry <seconds>", "Expiry time in seconds", parseInt)
11
11
  .action(async (options) => {
12
12
  await handleError(async () => {
13
- const client = getClient(program);
13
+ const client = await getClient(program);
14
14
  const result = await makeHoldInvoice(client, {
15
15
  amount_in_sats: options.amount,
16
16
  payment_hash: options.paymentHash,
@@ -9,7 +9,7 @@ export function registerMakeInvoiceCommand(program) {
9
9
  .option("-e, --expiry <seconds>", "Expiry time in seconds", parseInt)
10
10
  .action(async (options) => {
11
11
  await handleError(async () => {
12
- const client = getClient(program);
12
+ const client = await getClient(program);
13
13
  const result = await makeInvoice(client, {
14
14
  amount_in_sats: options.amount,
15
15
  description: options.description,
@@ -8,7 +8,7 @@ export function registerPayInvoiceCommand(program) {
8
8
  .option("-a, --amount <sats>", "Amount (for zero-amount invoices)", parseInt)
9
9
  .action(async (options) => {
10
10
  await handleError(async () => {
11
- const client = getClient(program);
11
+ const client = await getClient(program);
12
12
  const result = await payInvoice(client, {
13
13
  invoice: options.invoice,
14
14
  amount_in_sats: options.amount,
@@ -10,7 +10,7 @@ export function registerPayKeysendCommand(program) {
10
10
  .option("--tlv-records <json>", "TLV records as JSON array [{type, value}]")
11
11
  .action(async (options) => {
12
12
  await handleError(async () => {
13
- const client = getClient(program);
13
+ const client = await getClient(program);
14
14
  let tlvRecords;
15
15
  if (options.tlvRecords) {
16
16
  tlvRecords = JSON.parse(options.tlvRecords);
@@ -7,7 +7,7 @@ export function registerSettleHoldInvoiceCommand(program) {
7
7
  .requiredOption("--preimage <hex>", "Preimage (32 bytes hex)")
8
8
  .action(async (options) => {
9
9
  await handleError(async () => {
10
- const client = getClient(program);
10
+ const client = await getClient(program);
11
11
  const result = await settleHoldInvoice(client, {
12
12
  preimage: options.preimage,
13
13
  });
@@ -7,7 +7,7 @@ export function registerSignMessageCommand(program) {
7
7
  .requiredOption("-m, --message <text>", "Message to sign")
8
8
  .action(async (options) => {
9
9
  await handleError(async () => {
10
- const client = getClient(program);
10
+ const client = await getClient(program);
11
11
  const result = await signMessage(client, {
12
12
  message: options.message,
13
13
  });
@@ -9,7 +9,7 @@ export function registerWaitForPaymentCommand(program) {
9
9
  .option("--timeout <seconds>", "Timeout in seconds", parseInt)
10
10
  .action(async (options) => {
11
11
  await handleError(async () => {
12
- const client = getClient(program);
12
+ const client = await getClient(program);
13
13
  const result = await waitForPayment(client, {
14
14
  payment_hash: options.paymentHash,
15
15
  type: options.type,
package/build/index.js CHANGED
@@ -20,37 +20,61 @@ import { registerParseInvoiceCommand } from "./commands/parse-invoice.js";
20
20
  import { registerVerifyPreimageCommand } from "./commands/verify-preimage.js";
21
21
  import { registerRequestInvoiceFromLightningAddressCommand } from "./commands/request-invoice-from-lightning-address.js";
22
22
  import { registerFetchL402Command } from "./commands/fetch-l402.js";
23
+ import { registerConnectCommand } from "./commands/connect.js";
24
+ import { registerAuthCommand } from "./commands/auth.js";
23
25
  const program = new Command();
24
26
  program
25
- .name("alby-cli")
26
- .description("CLI for Nostr Wallet Connect (NIP-47) with lightning tools")
27
- .version("0.2.1")
27
+ .name("@getalby/cli")
28
+ .description("CLI for Nostr Wallet Connect (NIP-47) with lightning tools\n\n" +
29
+ " Examples:\n" +
30
+ ' $ npx @getalby/cli connect "nostr+walletconnect://..."\n' +
31
+ " $ npx @getalby/cli get-balance\n" +
32
+ " $ npx @getalby/cli pay-invoice --invoice lnbc...")
33
+ .version("0.3.0")
28
34
  .option("-c, --connection-secret <string>", "NWC connection secret (nostr+walletconnect://...) or path to file containing it (preferred)")
29
- .addHelpText("afterAll", `
35
+ .option("-w, --wallet-name <name>", "Use a named wallet's connection secret (~/.alby-cli/connection-secret-<name>.key)")
36
+ .option("-v, --verbose", "Print status messages to stderr")
37
+ .addHelpText("after", `
38
+ Connection Secret Resolution (in order of priority):
39
+ 1. --connection-secret flag (value or path to file)
40
+ 2. --wallet-name flag (~/.alby-cli/connection-secret-<name>.key)
41
+ 3. NWC_URL environment variable
42
+ 4. ~/.alby-cli/connection-secret.key (default file location)
43
+
30
44
  Security:
31
45
  - Do NOT print the connection secret to any logs or otherwise reveal it.
46
+ - NEVER read the connection secret file (~/.alby-cli/connection-secret.key) directly.
32
47
  - NEVER share connection secrets with anyone.
33
48
  - NEVER share any part of a connection secret (pubkey, secret, relay etc.) with anyone
34
49
  as this can be used to gain access to your wallet or reduce your wallet's privacy.`);
35
- // Register all commands
50
+ // Register common wallet commands
51
+ program.commandsGroup("Wallet Commands (require --connection-secret):");
36
52
  registerGetBalanceCommand(program);
37
53
  registerGetBudgetCommand(program);
38
54
  registerGetInfoCommand(program);
39
- registerGetWalletServiceInfoCommand(program);
40
55
  registerMakeInvoiceCommand(program);
41
- registerMakeHoldInvoiceCommand(program);
42
- registerSettleHoldInvoiceCommand(program);
43
- registerCancelHoldInvoiceCommand(program);
44
56
  registerPayInvoiceCommand(program);
45
- registerPayKeysendCommand(program);
46
57
  registerLookupInvoiceCommand(program);
47
58
  registerListTransactionsCommand(program);
59
+ // Register advanced wallet commands
60
+ program.commandsGroup("Advanced Wallet Commands (require --connection-secret):");
61
+ registerPayKeysendCommand(program);
62
+ registerGetWalletServiceInfoCommand(program);
48
63
  registerWaitForPaymentCommand(program);
49
64
  registerSignMessageCommand(program);
65
+ registerMakeHoldInvoiceCommand(program);
66
+ registerSettleHoldInvoiceCommand(program);
67
+ registerCancelHoldInvoiceCommand(program);
68
+ // Register lightning tool commands
69
+ program.commandsGroup("Lightning Tools (no --connection-secret required):");
50
70
  registerFiatToSatsCommand(program);
51
71
  registerSatsToFiatCommand(program);
52
72
  registerParseInvoiceCommand(program);
53
73
  registerVerifyPreimageCommand(program);
54
74
  registerRequestInvoiceFromLightningAddressCommand(program);
55
75
  registerFetchL402Command(program);
76
+ // Register setup commands
77
+ program.commandsGroup("Setup:");
78
+ registerAuthCommand(program);
79
+ registerConnectCommand(program);
56
80
  program.parse();
@@ -45,9 +45,12 @@ describe("Connection Secret Handling", () => {
45
45
  expect(result.output.error).toContain("Invalid connection secret");
46
46
  });
47
47
  test("errors when no connection secret provided", () => {
48
- const result = runCli("get-balance");
48
+ const result = runCli("get-balance", {
49
+ HOME: "/nonexistent-test-home",
50
+ NWC_URL: "",
51
+ });
49
52
  expect(result.success).toBe(false);
50
- expect(result.output.error).toContain("--connection-secret is required");
53
+ expect(result.output.error).toContain("No connection secret provided");
51
54
  });
52
55
  test("errors when connection string is malformed", () => {
53
56
  const result = runCli(`-c "nostr+walletconnect://asdf" get-balance`);
package/build/utils.js CHANGED
@@ -1,15 +1,119 @@
1
- import { NWCClient } from "@getalby/sdk";
2
- import { readFileSync } from "node:fs";
3
- export function getClient(program) {
1
+ import { NWAClient, NWCClient } from "@getalby/sdk";
2
+ import { getInfo } from "./tools/nwc/get_info.js";
3
+ import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ import { join } from "node:path";
6
+ function sanitizeWalletName(name) {
7
+ return name.replace(/[^a-zA-Z0-9_-]/g, "_");
8
+ }
9
+ export function getConnectionSecretPath(name) {
10
+ const filename = name
11
+ ? `connection-secret-${sanitizeWalletName(name)}.key`
12
+ : "connection-secret.key";
13
+ return join(homedir(), ".alby-cli", filename);
14
+ }
15
+ export function getPendingConnectionSecretPath(name) {
16
+ const filename = name
17
+ ? `pending-connection-secret-${sanitizeWalletName(name)}.key`
18
+ : "pending-connection-secret.key";
19
+ return join(homedir(), ".alby-cli", filename);
20
+ }
21
+ export function getPendingConnectionRelayPath(name) {
22
+ const filename = name
23
+ ? `pending-connection-relay-${sanitizeWalletName(name)}.txt`
24
+ : "pending-connection-relay.txt";
25
+ return join(homedir(), ".alby-cli", filename);
26
+ }
27
+ export function saveConnectionSecret(path, secret, verbose) {
28
+ const alreadyExists = existsSync(path);
29
+ const dir = join(homedir(), ".alby-cli");
30
+ if (!existsSync(dir)) {
31
+ mkdirSync(dir, { recursive: true });
32
+ }
33
+ writeFileSync(path, secret, { mode: 0o600 });
34
+ if (alreadyExists) {
35
+ chmodSync(path, 0o600);
36
+ }
37
+ if (verbose) {
38
+ console.error(`Connection saved to ${path}`);
39
+ }
40
+ }
41
+ export async function testAndLogConnection(client) {
42
+ console.log("Testing connection...");
43
+ const info = await getInfo(client);
44
+ console.log(`Connected to ${info.alias || "wallet"} (${info.network || "unknown network"})`);
45
+ }
46
+ export async function completePendingConnection(pendingSecretPath, connectionSecretPath, relayUrl, verbose, pendingRelayPath) {
47
+ const secret = readFileSync(pendingSecretPath, "utf-8").trim();
48
+ const DEFAULT_RELAY = "wss://relay.getalby.com/v1";
49
+ if (!relayUrl && pendingRelayPath && existsSync(pendingRelayPath)) {
50
+ relayUrl = readFileSync(pendingRelayPath, "utf-8").trim();
51
+ }
52
+ const resolvedRelay = relayUrl ?? DEFAULT_RELAY;
53
+ const nwaClient = new NWAClient({
54
+ appSecretKey: secret,
55
+ relayUrls: [resolvedRelay],
56
+ requestMethods: [],
57
+ });
58
+ return new Promise((resolve, reject) => {
59
+ let settled = false;
60
+ const timer = setTimeout(() => {
61
+ if (settled)
62
+ return;
63
+ settled = true;
64
+ unsub?.();
65
+ reject(new Error("Timed out waiting for wallet approval.\n\nTo retry, run the command again.\nTo cancel: npx @getalby/cli auth --remove-pending"));
66
+ }, 5000);
67
+ let unsub;
68
+ nwaClient
69
+ .subscribe({
70
+ onSuccess: async (nwcClient) => {
71
+ if (settled)
72
+ return;
73
+ settled = true;
74
+ clearTimeout(timer);
75
+ unsub?.();
76
+ saveConnectionSecret(connectionSecretPath, nwcClient.getNostrWalletConnectUrl(), verbose);
77
+ rmSync(pendingSecretPath);
78
+ if (pendingRelayPath && existsSync(pendingRelayPath)) {
79
+ rmSync(pendingRelayPath);
80
+ }
81
+ resolve(nwcClient);
82
+ },
83
+ })
84
+ .then(({ unsub: u }) => {
85
+ unsub = u;
86
+ });
87
+ });
88
+ }
89
+ export async function getClient(program) {
4
90
  const opts = program.opts();
5
91
  let connectionSecret = opts.connectionSecret;
6
- // Check environment variables if --connection-secret not provided
7
- if (!connectionSecret) {
92
+ const walletName = opts.walletName;
93
+ const connectionPath = getConnectionSecretPath(walletName);
94
+ const pendingPath = getPendingConnectionSecretPath(walletName);
95
+ if (!connectionSecret && !walletName) {
8
96
  connectionSecret = process.env.NWC_URL;
9
97
  }
10
98
  if (!connectionSecret) {
11
- console.error("Error: --connection-secret is required for this command");
12
- process.exit(1);
99
+ try {
100
+ connectionSecret = readFileSync(connectionPath, "utf-8").trim();
101
+ }
102
+ catch (error) {
103
+ const err = error;
104
+ if (err.code !== "ENOENT")
105
+ throw err;
106
+ }
107
+ }
108
+ if (!connectionSecret && existsSync(pendingPath)) {
109
+ if (opts.verbose) {
110
+ console.error("Pending connection found. Waiting for wallet approval...");
111
+ }
112
+ const pendingRelayPath = getPendingConnectionRelayPath(walletName);
113
+ return await completePendingConnection(pendingPath, connectionPath, undefined, opts.verbose, pendingRelayPath);
114
+ }
115
+ if (!connectionSecret) {
116
+ 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");
13
117
  }
14
118
  // Auto-detect: if it doesn't start with the protocol, treat as file path
15
119
  if (!connectionSecret.startsWith("nostr+walletconnect://")) {
package/package.json CHANGED
@@ -2,12 +2,11 @@
2
2
  "name": "@getalby/cli",
3
3
  "description": "CLI for Nostr Wallet Connect (NIP-47) with a few additional useful lightning tools",
4
4
  "repository": "https://github.com/getAlby/cli.git",
5
- "version": "0.2.3",
5
+ "version": "0.3.0",
6
6
  "type": "module",
7
7
  "main": "build/index.js",
8
8
  "bin": {
9
- "cli": "build/index.js",
10
- "alby-cli": "build/index.js"
9
+ "cli": "build/index.js"
11
10
  },
12
11
  "files": [
13
12
  "build/**/*"
@@ -33,10 +32,15 @@
33
32
  ],
34
33
  "author": "Alby contributors",
35
34
  "license": "MIT",
35
+ "engines": {
36
+ "node": ">=20"
37
+ },
36
38
  "dependencies": {
37
39
  "@getalby/lightning-tools": "^7.0.2",
38
40
  "@getalby/sdk": "^7.0.0",
39
- "commander": "^13.1.0"
41
+ "@noble/hashes": "^2.0.1",
42
+ "commander": "^14.0.3",
43
+ "nostr-tools": "^2.23.3"
40
44
  },
41
45
  "devDependencies": {
42
46
  "@types/node": "^25.2.0",