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
42 changes: 32 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ If you're using [Alby Cloud](https://getalby.com/alby-hub):
4. The CLI now auto-connects to `https://my.albyhub.com` with the correct routing headers. Use `start`/`unlock` normally:
```bash
npx @getalby/hub-cli start --password YOUR_PASSWORD --save
npx @getalby/hub-cli balances
npx @getalby/hub-cli get-balances
```

To override the hub name for a single invocation, set `ALBY_HUB_NAME` env var.
Expand Down Expand Up @@ -140,7 +140,7 @@ npx @getalby/hub-cli get-health

```bash
# Lightning + on-chain balances
npx @getalby/hub-cli balances
npx @getalby/hub-cli get-balances

# Get an on-chain deposit address
npx @getalby/hub-cli get-onchain-address
Expand Down Expand Up @@ -200,6 +200,21 @@ npx @getalby/hub-cli pay-invoice <invoice>
```bash
# Stop the Lightning node (hub HTTP server keeps running)
npx @getalby/hub-cli stop

# Trigger a wallet sync (queued, may take up to a minute)
npx @getalby/hub-cli sync

# Export wallet recovery phrase to a file (default: ~/.hub-cli/albyhub.recovery)
npx @getalby/hub-cli backup --password YOUR_PASSWORD

# Export to a custom path
npx @getalby/hub-cli backup --password YOUR_PASSWORD --output /path/to/backup.recovery

# Change the hub unlock password
npx @getalby/hub-cli change-password \
--current-password YOUR_PASSWORD \
--confirm-current-password YOUR_PASSWORD \
--new-password NEW_PASSWORD
```

### Payments
Expand All @@ -211,6 +226,9 @@ npx @getalby/hub-cli pay-invoice lnbc...
# Pay a zero-amount invoice, specifying the amount
npx @getalby/hub-cli pay-invoice lnbc... --amount 1000

# Pay a lightning address (user@domain), amount in satoshis
npx @getalby/hub-cli pay-lightning-address user@domain.com --amount 1000

# Create an invoice
npx @getalby/hub-cli make-invoice --amount 1000 --description "test"
```
Expand Down Expand Up @@ -269,7 +287,7 @@ npx @getalby/hub-cli create-app --name "Isolated App" --isolated --unlock-passwo

| Command | Description | Required Options |
| --------------------- | ----------------------------- | ---------------- |
| `balances` | Lightning + on-chain balances | — |
| `get-balances` | Lightning + on-chain balances | — |
| `get-onchain-address` | On-chain deposit address | — |

### Channels & Peers
Expand All @@ -288,16 +306,20 @@ npx @getalby/hub-cli create-app --name "Isolated App" --isolated --unlock-passwo

### Node Management

| Command | Description | Required Options |
| ------- | --------------------------------------------------- | ---------------- |
| `stop` | Stop the Lightning node (HTTP server keeps running) | — |
| Command | Description | Required Options |
| ----------------- | --------------------------------------------------- | --------------------------------------------------------------------- |
| `stop` | Stop the Lightning node (HTTP server keeps running) | — |
| `sync` | Trigger a wallet sync | — |
| `backup` | Export wallet recovery phrase to a file | `--password` |
| `change-password` | Change the hub unlock password | `--current-password`, `--confirm-current-password`, `--new-password` |

### Payments

| Command | Description | Required Options |
| -------------- | ----------------------- | ---------------------- |
| `pay-invoice` | Pay a BOLT11 invoice | `<invoice>` (argument) |
| `make-invoice` | Create a BOLT11 invoice | `--amount` |
| Command | Description | Required Options |
| ------------------------- | -------------------------------- | ----------------------------- |
| `pay-invoice` | Pay a BOLT11 invoice | `<invoice>` (argument) |
| `pay-lightning-address` | Pay a lightning address | `<address>` (argument), `--amount` |
| `make-invoice` | Create a BOLT11 invoice | `--amount` |

### Transactions

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@getalby/hub-cli",
"description": "CLI for managing Alby Hub - a self-custodial Lightning node",
"repository": "https://github.com/getAlby/hub.git",
"version": "0.1.0",
"version": "0.2.0",
"type": "module",
"main": "build/index.js",
"bin": {
Expand Down Expand Up @@ -32,6 +32,7 @@
"author": "Alby contributors",
"license": "Apache-2.0",
"dependencies": {
"@getalby/lightning-tools": "^7.0.2",
"commander": "^13.1.0"
},
"devDependencies": {
Expand Down
9 changes: 9 additions & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ export class HubClient {
return this.handleResponse<T>(res);
}

async patch<T>(path: string, body?: unknown): Promise<T> {
const res = await fetch(`${this.baseUrl}${path}`, {
method: "PATCH",
headers: this.headers(),
body: body !== undefined ? JSON.stringify(body) : undefined,
});
return this.handleResponse<T>(res);
}

async delete<T>(path: string): Promise<T> {
const res = await fetch(`${this.baseUrl}${path}`, {
method: "DELETE",
Expand Down
29 changes: 29 additions & 0 deletions src/commands/backup-mnemonic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Command } from "commander";
import { mkdirSync, writeFileSync } from "node:fs";
import { homedir } from "node:os";
import { join, resolve } from "node:path";
import { getClient, handleError, output } from "../utils.js";

const DEFAULT_BACKUP_DIR = join(homedir(), ".hub-cli");
const DEFAULT_BACKUP_FILE = join(DEFAULT_BACKUP_DIR, "albyhub.recovery");

export function registerBackupMnemonicCommand(program: Command): void {
program
.command("backup-mnemonic")
.description("Export the wallet recovery phrase to a file")
.requiredOption("-p, --password <string>", "Unlock password")
.option("--output <file>", "Output file path", DEFAULT_BACKUP_FILE)
.action(async (opts: { password: string; output: string }) => {
await handleError(async () => {
const client = getClient(program);
const result = await client.post<{ mnemonic: string }>("/api/mnemonic", {
unlockPassword: opts.password,
});
const filePath = resolve(opts.output);
const dir = filePath.substring(0, filePath.lastIndexOf("/"));
if (dir) mkdirSync(dir, { recursive: true });
writeFileSync(filePath, result.mnemonic, { encoding: "utf-8", mode: 0o600 });
output({ success: true, file: filePath });
});
});
}
35 changes: 35 additions & 0 deletions src/commands/change-password.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Command } from "commander";
import { getClient, handleError, output } from "../utils.js";

export function registerChangePasswordCommand(program: Command): void {
program
.command("change-password")
.description("Change the hub unlock password")
.requiredOption("--current-password <string>", "Current unlock password")
.requiredOption(
"--confirm-current-password <string>",
"Confirm current unlock password",
)
.requiredOption("--new-password <string>", "New unlock password")
.action(
async (opts: {
currentPassword: string;
confirmCurrentPassword: string;
newPassword: string;
}) => {
await handleError(async () => {
if (opts.currentPassword !== opts.confirmCurrentPassword) {
throw new Error(
"Current password and confirmation do not match",
);
}
const client = getClient(program);
await client.patch<void>("/api/unlock-password", {
currentUnlockPassword: opts.currentPassword,
newUnlockPassword: opts.newPassword,
});
output({ success: true });
});
},
);
}
4 changes: 2 additions & 2 deletions src/commands/balances.ts → src/commands/get-balances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { Command } from "commander";
import { BalancesResponse } from "../types.js";
import { getClient, handleError, output } from "../utils.js";

export function registerBalancesCommand(program: Command): void {
export function registerGetBalancesCommand(program: Command): void {
program
.command("balances")
.command("get-balances")
.description("Get Lightning and on-chain balances")
.action(async () => {
await handleError(async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Command } from "commander";
import { ChannelPeerSuggestion } from "../types.js";
import { getClient, handleError, output } from "../utils.js";

export function registerChannelSuggestionsCommand(program: Command): void {
export function registerListChannelSuggestionsCommand(program: Command): void {
program
.command("get-channel-suggestions")
.description(
Expand Down
2 changes: 1 addition & 1 deletion src/commands/health.ts → src/commands/get-health.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Command } from "commander";
import { HealthResponse } from "../types.js";
import { getClient, handleError, output } from "../utils.js";

export function registerHealthCommand(program: Command): void {
export function registerGetHealthCommand(program: Command): void {
program
.command("get-health")
.description(
Expand Down
2 changes: 1 addition & 1 deletion src/commands/info.ts → src/commands/get-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Command } from "commander";
import { InfoResponse } from "../types.js";
import { getClient, handleError, output } from "../utils.js";

export function registerInfoCommand(program: Command): void {
export function registerGetInfoCommand(program: Command): void {
program
.command("get-info")
.description("Get hub status, version, and configuration")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Command } from "commander";
import { NodeStatus } from "../types.js";
import { getClient, handleError, output } from "../utils.js";

export function registerNodeStatusCommand(program: Command): void {
export function registerGetNodeStatusCommand(program: Command): void {
program
.command("get-node-status")
.description("Get Lightning node readiness status")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Command } from "commander";
import { getClient, handleError, output } from "../utils.js";

export function registerWalletAddressCommand(program: Command): void {
export function registerGetWalletAddressCommand(program: Command): void {
program
.command("get-onchain-address")
.description("Get an on-chain Bitcoin deposit address")
Expand Down
4 changes: 2 additions & 2 deletions src/commands/apps.ts → src/commands/list-apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { Command } from "commander";
import { ListAppsResponse } from "../types.js";
import { getClient, handleError, output } from "../utils.js";

export function registerAppsCommand(program: Command): void {
export function registerListAppsCommand(program: Command): void {
program
.command("apps")
.command("list-apps")
.description("List NWC app connections")
.action(async () => {
await handleError(async () => {
Expand Down
2 changes: 1 addition & 1 deletion src/commands/channels.ts → src/commands/list-channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Command } from "commander";
import { Channel } from "../types.js";
import { getClient, handleError, output } from "../utils.js";

export function registerChannelsCommand(program: Command): void {
export function registerListChannelsCommand(program: Command): void {
program
.command("list-channels")
.description("List Lightning channels")
Expand Down
2 changes: 1 addition & 1 deletion src/commands/peers.ts → src/commands/list-peers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Command } from "commander";
import { PeerDetails } from "../types.js";
import { getClient, handleError, output } from "../utils.js";

export function registerPeersCommand(program: Command): void {
export function registerListPeersCommand(program: Command): void {
program
.command("list-peers")
.description("List connected Lightning peers")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ import { Command } from "commander";
import { ListTransactionsResponse } from "../types.js";
import { getClient, handleError, output } from "../utils.js";

export function registerTransactionsCommand(program: Command): void {
export function registerListTransactionsCommand(program: Command): void {
program
.command("list-transactions")
.description("List payment history")
.option("--limit <number>", "Maximum number of transactions to return", "20")
.option(
"--limit <number>",
"Maximum number of transactions to return",
"20",
)
.option("--offset <number>", "Pagination offset", "0")
.action(async (opts: { limit: string; offset: string }) => {
await handleError(async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Command } from "commander";
import { LSPChannelOffer } from "../types.js";
import { getClient, handleError, output } from "../utils.js";

export function registerChannelOfferCommand(program: Command): void {
export function registerRequestAlbyChannelOfferCommand(program: Command): void {
program
.command("request-alby-lsp-channel-offer")
.description(
Expand Down
27 changes: 27 additions & 0 deletions src/commands/request-invoice-from-lightning-address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Command } from "commander";
import { LightningAddress } from "@getalby/lightning-tools";
import { handleError, output } from "../utils.js";

export function registerRequestInvoiceFromLightningAddressCommand(
program: Command,
): void {
program
.command("request-invoice-from-lightning-address")
.description("Request an invoice from a lightning address")
.requiredOption("-a, --address <ln-address>", "Lightning address")
.requiredOption("-s, --amount <sats>", "Amount in satoshis", parseInt)
.option("--comment <text>", "Optional comment")
.action(
async (opts: { address: string; amount: number; comment?: string }) => {
await handleError(async () => {
const ln = new LightningAddress(opts.address);
await ln.fetch();
const { paymentRequest, paymentHash } = await ln.requestInvoice({
satoshi: opts.amount,
comment: opts.comment,
});
output({ paymentRequest, paymentHash });
});
},
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Command } from "commander";
import { LSPOrderResponse } from "../types.js";
import { getClient, handleError, output } from "../utils.js";

export function registerLspOrderCommand(program: Command): void {
export function registerRequestLspOrderCommand(program: Command): void {
program
.command("request-lsp-order")
.description(
Expand Down
15 changes: 15 additions & 0 deletions src/commands/sync.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Command } from "commander";
import { getClient, handleError, output } from "../utils.js";

export function registerSyncCommand(program: Command): void {
program
.command("sync")
.description("Trigger a wallet sync (queued, may take up to a minute)")
.action(async () => {
await handleError(async () => {
const client = getClient(program);
await client.post<void>("/api/wallet/sync");
output({ success: true, message: "Wallet sync queued. May take up to a minute." });
});
});
}
Loading
Loading