@btcemail/cli 0.3.0 → 0.5.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/dist/index.js CHANGED
@@ -2,12 +2,9 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
- import pc11 from "picocolors";
5
+ import pc16 from "picocolors";
6
6
 
7
- // src/commands/login.ts
8
- import { randomUUID } from "crypto";
9
- import open from "open";
10
- import ora from "ora";
7
+ // src/commands/blocklist.ts
11
8
  import pc from "picocolors";
12
9
 
13
10
  // src/config.ts
@@ -104,104 +101,6 @@ function getToken() {
104
101
  return auth.token;
105
102
  }
106
103
 
107
- // src/commands/login.ts
108
- var POLL_INTERVAL_MS = 2e3;
109
- var POLL_TIMEOUT_MS = 3e5;
110
- async function loginCommand() {
111
- const sessionId = randomUUID();
112
- const baseUrl = getApiBaseUrl().replace("/api/v1", "");
113
- const authUrl = `${baseUrl}/auth/cli?session_id=${sessionId}`;
114
- const pollUrl = `${baseUrl}/api/auth/cli-session?session_id=${sessionId}`;
115
- console.log();
116
- console.log(pc.bold("btc.email CLI Login"));
117
- console.log();
118
- try {
119
- const createResponse = await fetch(
120
- `${baseUrl}/api/auth/cli-session`,
121
- {
122
- method: "POST",
123
- headers: { "Content-Type": "application/json" },
124
- body: JSON.stringify({ session_id: sessionId })
125
- }
126
- );
127
- if (!createResponse.ok) {
128
- console.error(pc.red("Failed to initialize login session"));
129
- process.exit(1);
130
- }
131
- } catch (error) {
132
- console.error(pc.red("Failed to connect to btc.email server"));
133
- console.error(
134
- pc.dim(error instanceof Error ? error.message : "Unknown error")
135
- );
136
- process.exit(1);
137
- }
138
- console.log(pc.dim("Opening browser for authentication..."));
139
- console.log();
140
- console.log(pc.dim("If the browser doesn't open, visit:"));
141
- console.log(pc.cyan(authUrl));
142
- console.log();
143
- try {
144
- await open(authUrl);
145
- } catch {
146
- }
147
- const spinner = ora("Waiting for authentication...").start();
148
- const startTime = Date.now();
149
- while (Date.now() - startTime < POLL_TIMEOUT_MS) {
150
- try {
151
- const response = await fetch(pollUrl);
152
- if (response.status === 202) {
153
- await sleep(POLL_INTERVAL_MS);
154
- continue;
155
- }
156
- if (response.status === 410) {
157
- spinner.fail("Session expired. Please try again.");
158
- process.exit(1);
159
- }
160
- if (response.ok) {
161
- const data = await response.json();
162
- if (data.status === "complete") {
163
- setAuth({
164
- token: data.data.token,
165
- expiresAt: data.data.expiresAt,
166
- userId: data.data.userId,
167
- email: data.data.email
168
- });
169
- spinner.succeed(pc.green("Successfully logged in!"));
170
- console.log();
171
- console.log(` ${pc.dim("Email:")} ${data.data.email}`);
172
- console.log();
173
- console.log(pc.dim("You can now use btcemail commands."));
174
- console.log(pc.dim("Run `btcemail --help` to see available commands."));
175
- return;
176
- }
177
- }
178
- await sleep(POLL_INTERVAL_MS);
179
- } catch (error) {
180
- await sleep(POLL_INTERVAL_MS);
181
- }
182
- }
183
- spinner.fail("Authentication timed out. Please try again.");
184
- process.exit(1);
185
- }
186
- function sleep(ms) {
187
- return new Promise((resolve) => setTimeout(resolve, ms));
188
- }
189
-
190
- // src/commands/logout.ts
191
- import pc2 from "picocolors";
192
- async function logoutCommand() {
193
- if (!isAuthenticated()) {
194
- console.log(pc2.yellow("You are not logged in."));
195
- return;
196
- }
197
- clearAuth();
198
- console.log(pc2.green("Successfully logged out."));
199
- }
200
-
201
- // src/commands/whoami.ts
202
- import pc3 from "picocolors";
203
- import ora2 from "ora";
204
-
205
104
  // src/api.ts
206
105
  async function apiRequest(endpoint, options = {}) {
207
106
  const token = getToken();
@@ -259,9 +158,7 @@ async function getCreditsBalance() {
259
158
  return apiRequest("/credits/balance");
260
159
  }
261
160
  async function getWhoami() {
262
- return apiRequest(
263
- "/auth/whoami"
264
- );
161
+ return apiRequest("/auth/whoami");
265
162
  }
266
163
  async function getPendingEmails(options = {}) {
267
164
  const params = new URLSearchParams();
@@ -278,6 +175,15 @@ async function payForEmail(id, paymentHash) {
278
175
  body: JSON.stringify({ paymentHash })
279
176
  });
280
177
  }
178
+ async function acceptPendingEmail(id, whitelist2 = true) {
179
+ return apiRequest(
180
+ `/inbound/${id}/accept`,
181
+ {
182
+ method: "POST",
183
+ body: JSON.stringify({ whitelist: whitelist2 })
184
+ }
185
+ );
186
+ }
281
187
  async function sendEmail(options) {
282
188
  return apiRequest("/email/send", {
283
189
  method: "POST",
@@ -387,6 +293,64 @@ async function checkPaymentStatus(paymentHash) {
387
293
  };
388
294
  }
389
295
  }
296
+ async function getSettings(username) {
297
+ const params = username ? `?username=${username}` : "";
298
+ return apiRequest(`/settings${params}`);
299
+ }
300
+ async function updateSettings(settings2, username) {
301
+ const params = username ? `?username=${username}` : "";
302
+ return apiRequest(
303
+ `/settings${params}`,
304
+ {
305
+ method: "POST",
306
+ body: JSON.stringify(settings2)
307
+ }
308
+ );
309
+ }
310
+ async function getWhitelist(username) {
311
+ const params = username ? `?username=${username}` : "";
312
+ return apiRequest(`/whitelist${params}`);
313
+ }
314
+ async function addToWhitelist(entry, username) {
315
+ const params = username ? `?username=${username}` : "";
316
+ return apiRequest(`/whitelist${params}`, {
317
+ method: "POST",
318
+ body: JSON.stringify(entry)
319
+ });
320
+ }
321
+ async function removeFromWhitelist(id) {
322
+ return apiRequest(`/whitelist/${id}`, {
323
+ method: "DELETE"
324
+ });
325
+ }
326
+ async function getBlocklist(username) {
327
+ const params = username ? `?username=${username}` : "";
328
+ return apiRequest(`/blocklist${params}`);
329
+ }
330
+ async function addToBlocklist(entry, username) {
331
+ const params = username ? `?username=${username}` : "";
332
+ return apiRequest(`/blocklist${params}`, {
333
+ method: "POST",
334
+ body: JSON.stringify(entry)
335
+ });
336
+ }
337
+ async function removeFromBlocklist(id) {
338
+ return apiRequest(`/blocklist/${id}`, {
339
+ method: "DELETE"
340
+ });
341
+ }
342
+ async function getNetwork() {
343
+ return apiRequest("/network");
344
+ }
345
+ async function setNetwork(network2) {
346
+ return apiRequest("/network", {
347
+ method: "POST",
348
+ body: JSON.stringify({ network: network2 })
349
+ });
350
+ }
351
+ async function getStats() {
352
+ return apiRequest("/stats");
353
+ }
390
354
  function getCreditPurchaseOptions() {
391
355
  return [
392
356
  {
@@ -420,194 +384,868 @@ function getCreditPurchaseOptions() {
420
384
  ];
421
385
  }
422
386
 
423
- // src/commands/whoami.ts
424
- async function whoamiCommand() {
425
- if (!isAuthenticated()) {
426
- console.log(pc3.yellow("Not logged in."));
427
- console.log(pc3.dim("Run `btcemail login` to authenticate."));
387
+ // src/commands/blocklist.ts
388
+ async function blocklistListCommand(options = {}) {
389
+ const token = getToken();
390
+ if (!token) {
391
+ console.error(pc.red("Not logged in. Run 'btcemail login' first."));
428
392
  process.exit(1);
429
393
  }
430
- const auth = getAuth();
431
- if (!auth) {
432
- console.log(pc3.yellow("Not logged in."));
394
+ const result = await getBlocklist(options.username);
395
+ if (!result.ok) {
396
+ console.error(pc.red(`Error: ${result.error.message}`));
433
397
  process.exit(1);
434
398
  }
435
- const spinner = ora2("Fetching account info...").start();
436
- const result = await getWhoami();
437
- spinner.stop();
438
- if (result.ok) {
439
- console.log();
440
- console.log(pc3.bold("Current User"));
441
- console.log();
442
- console.log(` ${pc3.dim("Email:")} ${result.data.email}`);
443
- if (result.data.username) {
444
- console.log(` ${pc3.dim("Username:")} ${result.data.username}@btc.email`);
445
- }
446
- console.log(` ${pc3.dim("User ID:")} ${result.data.id}`);
447
- const expiryInfo = getTokenExpiryInfo();
448
- if (expiryInfo.expiresIn) {
449
- const expiryColor = isTokenExpiringSoon() ? pc3.yellow : pc3.dim;
450
- console.log(` ${pc3.dim("Session:")} ${expiryColor(`expires in ${expiryInfo.expiresIn}`)}`);
451
- }
452
- console.log();
453
- if (isTokenExpiringSoon()) {
454
- console.log(pc3.yellow("Session expiring soon. Run `btcemail login` to refresh."));
455
- console.log();
456
- }
457
- } else {
458
- console.log();
459
- console.log(pc3.bold("Current User") + pc3.dim(" (cached)"));
399
+ const { username, entries } = result.data;
400
+ if (options.json) {
401
+ console.log(JSON.stringify({ username, entries }, null, 2));
402
+ return;
403
+ }
404
+ console.log();
405
+ console.log(pc.bold("Blocklist for ") + pc.cyan(`${username}@btc.email`));
406
+ console.log();
407
+ if (entries.length === 0) {
408
+ console.log(pc.dim(" No entries in blocklist."));
460
409
  console.log();
461
- console.log(` ${pc3.dim("Email:")} ${auth.email}`);
462
- console.log(` ${pc3.dim("User ID:")} ${auth.userId}`);
410
+ console.log(pc.dim(" Add entries with: btcemail blocklist add <email>"));
411
+ console.log(pc.dim(" Or for domains: btcemail blocklist add @domain.com"));
463
412
  console.log();
464
- if (result.error.code === "UNAUTHENTICATED" || result.error.code === "UNAUTHORIZED") {
465
- console.log(pc3.yellow("Session expired. Run `btcemail login` to re-authenticate."));
466
- }
413
+ return;
467
414
  }
415
+ console.log(pc.dim(` ${entries.length} ${entries.length === 1 ? "entry" : "entries"}:`));
416
+ console.log();
417
+ for (const entry of entries) {
418
+ const value = entry.sender_email || `@${entry.sender_domain}`;
419
+ const reason = entry.reason ? pc.dim(` - ${entry.reason}`) : "";
420
+ console.log(` ${pc.red(value)}${reason}`);
421
+ console.log(pc.dim(` ID: ${entry.id}`));
422
+ }
423
+ console.log();
468
424
  }
469
-
470
- // src/commands/list.ts
471
- import pc4 from "picocolors";
472
- import ora3 from "ora";
473
-
474
- // src/utils/cache.ts
475
- var CACHE_KEY = "emailCache";
476
- var CACHE_TTL_MS = 30 * 60 * 1e3;
477
- function cacheEmailList(folder, emails) {
478
- config.set(CACHE_KEY, {
479
- folder,
480
- emails,
481
- timestamp: Date.now()
482
- });
425
+ async function blocklistAddCommand(entry, options = {}) {
426
+ const token = getToken();
427
+ if (!token) {
428
+ console.error(pc.red("Not logged in. Run 'btcemail login' first."));
429
+ process.exit(1);
430
+ }
431
+ const isDomain = entry.startsWith("@");
432
+ const payload = isDomain ? { domain: entry.slice(1), reason: options.reason } : { email: entry, reason: options.reason };
433
+ const result = await addToBlocklist(payload, options.username);
434
+ if (!result.ok) {
435
+ console.error(pc.red(`Error: ${result.error.message}`));
436
+ process.exit(1);
437
+ }
438
+ if (options.json) {
439
+ console.log(JSON.stringify({ success: true, entry: result.data.entry }, null, 2));
440
+ return;
441
+ }
442
+ console.log(pc.green(`Added to blocklist: ${pc.red(entry)}`));
483
443
  }
484
- function getCachedEmailList(folder) {
485
- const cache = config.get(CACHE_KEY);
486
- if (!cache) return null;
487
- if (Date.now() - cache.timestamp > CACHE_TTL_MS) {
488
- config.delete(CACHE_KEY);
489
- return null;
444
+ async function blocklistRemoveCommand(id, options = {}) {
445
+ const token = getToken();
446
+ if (!token) {
447
+ console.error(pc.red("Not logged in. Run 'btcemail login' first."));
448
+ process.exit(1);
490
449
  }
491
- if (folder && cache.folder !== folder) {
492
- return null;
450
+ const result = await removeFromBlocklist(id);
451
+ if (!result.ok) {
452
+ console.error(pc.red(`Error: ${result.error.message}`));
453
+ process.exit(1);
493
454
  }
494
- return cache;
455
+ if (options.json) {
456
+ console.log(JSON.stringify({ success: true }, null, 2));
457
+ return;
458
+ }
459
+ console.log(pc.green("Removed from blocklist"));
495
460
  }
496
- function getEmailIdByIndex(index) {
497
- const cache = getCachedEmailList();
498
- if (!cache) return null;
499
- const idx = index - 1;
500
- if (idx < 0 || idx >= cache.emails.length) {
501
- return null;
461
+
462
+ // src/commands/credits.ts
463
+ import ora from "ora";
464
+ import pc3 from "picocolors";
465
+ import qrcode from "qrcode-terminal";
466
+
467
+ // src/utils/payment-polling.ts
468
+ import pc2 from "picocolors";
469
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
470
+ async function pollForPayment(options) {
471
+ const { paymentHash, checkFn, maxAttempts = 120, intervalMs = 2500, onPaid } = options;
472
+ let frame = 0;
473
+ let attempts = 0;
474
+ process.stdout.write(`${pc2.cyan(SPINNER_FRAMES[0])} Waiting for payment...`);
475
+ while (attempts < maxAttempts) {
476
+ try {
477
+ const status = await checkFn(paymentHash);
478
+ if (status.paid) {
479
+ process.stdout.write(`\r${" ".repeat(50)}\r`);
480
+ console.log(`${pc2.green("\u2713")} Payment received!`);
481
+ if (onPaid) onPaid();
482
+ return status;
483
+ }
484
+ } catch {
485
+ }
486
+ frame = (frame + 1) % SPINNER_FRAMES.length;
487
+ const timeLeft = Math.ceil((maxAttempts - attempts) * intervalMs / 1e3 / 60);
488
+ process.stdout.write(
489
+ `\r${pc2.cyan(SPINNER_FRAMES[frame])} Waiting for payment... (${timeLeft}m remaining)`
490
+ );
491
+ await sleep(intervalMs);
492
+ attempts++;
502
493
  }
503
- return cache.emails[idx].id;
494
+ process.stdout.write(`\r${" ".repeat(60)}\r`);
495
+ console.log(`${pc2.yellow("\u26A0")} Timed out waiting for payment.`);
496
+ return { paid: false };
504
497
  }
505
- function isNumericId(id) {
506
- return /^\d+$/.test(id);
498
+ function sleep(ms) {
499
+ return new Promise((resolve) => setTimeout(resolve, ms));
507
500
  }
508
501
 
509
- // src/commands/list.ts
510
- async function listCommand(options) {
502
+ // src/commands/credits.ts
503
+ async function creditsBalanceCommand() {
511
504
  if (!isAuthenticated()) {
512
- console.log(pc4.yellow("Not logged in."));
513
- console.log(pc4.dim("Run `btcemail login` to authenticate."));
505
+ console.log(pc3.yellow("Not logged in."));
506
+ console.log(pc3.dim("Run `btcemail login` to authenticate."));
514
507
  process.exit(1);
515
508
  }
516
- const folder = options.folder || "inbox";
517
- const limit = options.limit || 20;
518
- const page = options.page || 1;
519
- const offset = (page - 1) * limit;
520
- const spinner = ora3("Loading emails...").start();
521
- const result = await getEmails({
522
- folder,
523
- limit,
524
- offset
525
- });
509
+ const spinner = ora("Fetching balance...").start();
510
+ const result = await getCreditsBalance();
526
511
  if (!result.ok) {
527
512
  spinner.fail(`Error: ${result.error.message}`);
528
513
  process.exit(1);
529
514
  }
530
- const { data: emails, pagination } = result.data;
531
515
  spinner.stop();
532
- const cachedEmails = emails.map((e) => ({
533
- id: e.id,
534
- subject: e.subject,
535
- from: e.from.name || e.from.email
536
- }));
537
- cacheEmailList(folder, cachedEmails);
538
- if (options.json) {
539
- console.log(JSON.stringify({ emails, pagination }, null, 2));
540
- return;
541
- }
516
+ const { balanceSats, lifetimePurchasedSats, lifetimeSpentSats } = result.data;
542
517
  console.log();
543
- console.log(pc4.bold(`${capitalize(folder)} (${pagination.total} emails)`));
518
+ console.log(pc3.bold("Credit Balance"));
519
+ console.log();
520
+ console.log(` ${pc3.green(formatSats(balanceSats))} sats`);
521
+ console.log();
522
+ if (lifetimePurchasedSats > 0 || lifetimeSpentSats > 0) {
523
+ console.log(pc3.dim(` Purchased: ${formatSats(lifetimePurchasedSats)} sats`));
524
+ console.log(pc3.dim(` Spent: ${formatSats(lifetimeSpentSats)} sats`));
525
+ console.log();
526
+ }
527
+ if (balanceSats === 0) {
528
+ console.log(pc3.dim(" No credits available."));
529
+ console.log(pc3.dim(" Run `btcemail credits purchase` to buy credits."));
530
+ console.log();
531
+ }
532
+ }
533
+ async function creditsHistoryCommand(options) {
534
+ if (!isAuthenticated()) {
535
+ console.log(pc3.yellow("Not logged in."));
536
+ console.log(pc3.dim("Run `btcemail login` to authenticate."));
537
+ process.exit(1);
538
+ }
539
+ const spinner = ora("Loading transaction history...").start();
540
+ const result = await getCreditTransactions({
541
+ limit: options.limit || 20,
542
+ type: options.type
543
+ });
544
+ if (!result.ok) {
545
+ spinner.fail(`Error: ${result.error.message}`);
546
+ process.exit(1);
547
+ }
548
+ const { data: transactions, pagination } = result.data;
549
+ spinner.stop();
550
+ if (options.json) {
551
+ console.log(JSON.stringify({ transactions, pagination }, null, 2));
552
+ return;
553
+ }
554
+ console.log();
555
+ console.log(pc3.bold("Transaction History"));
556
+ console.log();
557
+ if (transactions.length === 0) {
558
+ console.log(pc3.dim(" No transactions found."));
559
+ console.log();
560
+ return;
561
+ }
562
+ console.log(
563
+ ` ${pc3.dim("Date".padEnd(12))} ${pc3.dim("Type".padEnd(15))} ${pc3.dim("Amount".padEnd(12))} ${pc3.dim("Balance")}`
564
+ );
565
+ console.log(pc3.dim(` ${"-".repeat(55)}`));
566
+ for (const tx of transactions) {
567
+ const date = formatDate(tx.createdAt);
568
+ const type = formatTransactionType(tx.transactionType).padEnd(15);
569
+ const amount = formatAmount(tx.amountSats).padEnd(12);
570
+ const balance = formatSats(tx.balanceAfter);
571
+ console.log(` ${date.padEnd(12)} ${type} ${amount} ${pc3.dim(balance)}`);
572
+ }
573
+ console.log();
574
+ if (pagination.hasMore) {
575
+ console.log(pc3.dim(` Use --limit to see more transactions.`));
576
+ console.log();
577
+ }
578
+ }
579
+ async function creditsPurchaseCommand(options) {
580
+ if (!isAuthenticated()) {
581
+ console.log(pc3.yellow("Not logged in."));
582
+ console.log(pc3.dim("Run `btcemail login` to authenticate."));
583
+ process.exit(1);
584
+ }
585
+ const purchaseOptions = getCreditPurchaseOptions();
586
+ if (options.option === void 0) {
587
+ console.log();
588
+ console.log(pc3.bold("Purchase Options"));
589
+ console.log();
590
+ purchaseOptions.forEach((opt, index) => {
591
+ console.log(` ${pc3.cyan(`[${index}]`)} ${opt.label}`);
592
+ console.log(` ${pc3.dim(opt.description)}`);
593
+ console.log(` ${pc3.dim(`Price: ${formatSats(opt.priceSats)} sats`)}`);
594
+ if (opt.bonusSats > 0) {
595
+ console.log(` ${pc3.green(`+${formatSats(opt.bonusSats)} bonus sats`)}`);
596
+ }
597
+ console.log();
598
+ });
599
+ console.log(pc3.dim(" Usage: btcemail credits purchase --option <index>"));
600
+ console.log(pc3.dim(" Add --wait to wait for payment confirmation."));
601
+ console.log();
602
+ return;
603
+ }
604
+ if (options.option < 0 || options.option >= purchaseOptions.length) {
605
+ console.error(pc3.red(`Error: Invalid option. Choose 0-${purchaseOptions.length - 1}.`));
606
+ process.exit(1);
607
+ }
608
+ const _selectedOption = purchaseOptions[options.option];
609
+ const spinner = ora("Creating invoice...").start();
610
+ const result = await purchaseCredits(options.option);
611
+ if (!result.ok) {
612
+ spinner.fail(`Error: ${result.error.message}`);
613
+ process.exit(1);
614
+ }
615
+ spinner.stop();
616
+ const purchase = result.data;
617
+ if (options.json) {
618
+ console.log(JSON.stringify(purchase, null, 2));
619
+ return;
620
+ }
621
+ console.log();
622
+ console.log(pc3.bold("Lightning Invoice"));
623
+ console.log();
624
+ console.log(` ${pc3.dim("Amount:")} ${formatSats(purchase.amountSats)} sats`);
625
+ console.log(` ${pc3.dim("Credits:")} ${formatSats(purchase.creditsToReceive)} sats`);
626
+ if (purchase.bonusSats > 0) {
627
+ console.log(` ${pc3.dim("Bonus:")} ${pc3.green(`+${formatSats(purchase.bonusSats)} sats`)}`);
628
+ }
629
+ console.log();
630
+ console.log(pc3.bold("Scan to pay:"));
631
+ console.log();
632
+ qrcode.generate(purchase.invoice, { small: true }, (qr) => {
633
+ const indentedQr = qr.split("\n").map((line) => ` ${line}`).join("\n");
634
+ console.log(indentedQr);
635
+ });
636
+ console.log();
637
+ console.log(pc3.bold("Invoice:"));
638
+ console.log();
639
+ console.log(` ${purchase.invoice}`);
640
+ console.log();
641
+ console.log(pc3.dim(` Payment hash: ${purchase.paymentHash}`));
642
+ console.log(pc3.dim(` Expires: ${new Date(purchase.expiresAt).toLocaleString()}`));
643
+ console.log();
644
+ if (options.wait) {
645
+ console.log();
646
+ const status = await pollForPayment({
647
+ paymentHash: purchase.paymentHash,
648
+ checkFn: async (hash) => {
649
+ const result2 = await checkPaymentStatus(hash);
650
+ if (result2.ok) {
651
+ return { paid: result2.data.paid };
652
+ }
653
+ return { paid: false };
654
+ },
655
+ onPaid: async () => {
656
+ const balanceResult = await getCreditsBalance();
657
+ if (balanceResult.ok) {
658
+ console.log();
659
+ console.log(
660
+ `${pc3.dim("New balance:")} ${pc3.green(formatSats(balanceResult.data.balanceSats))} sats`
661
+ );
662
+ }
663
+ }
664
+ });
665
+ if (!status.paid) {
666
+ console.log(pc3.dim(" You can still pay the invoice and credits will be added."));
667
+ }
668
+ console.log();
669
+ } else {
670
+ console.log(pc3.dim(" Scan the QR code or copy the invoice above to pay."));
671
+ console.log(pc3.dim(" Use --wait to wait for payment confirmation."));
672
+ console.log();
673
+ }
674
+ }
675
+ function formatSats(sats) {
676
+ return sats.toLocaleString();
677
+ }
678
+ function formatAmount(sats) {
679
+ if (sats >= 0) {
680
+ return pc3.green(`+${formatSats(sats)}`);
681
+ }
682
+ return pc3.red(formatSats(sats));
683
+ }
684
+ function formatTransactionType(type) {
685
+ const types = {
686
+ topup: "Top-up",
687
+ email_sent: "Email Sent",
688
+ email_received: "Email Received",
689
+ refund: "Refund",
690
+ bonus: "Bonus"
691
+ };
692
+ return types[type] || type;
693
+ }
694
+ function formatDate(dateString) {
695
+ const date = new Date(dateString);
696
+ return date.toLocaleDateString("en-US", {
697
+ month: "short",
698
+ day: "numeric"
699
+ });
700
+ }
701
+
702
+ // src/commands/inbound.ts
703
+ import ora2 from "ora";
704
+ import pc4 from "picocolors";
705
+ async function inboundDeliveredCommand(options) {
706
+ if (!isAuthenticated()) {
707
+ console.log(pc4.yellow("Not logged in."));
708
+ console.log(pc4.dim("Run `btcemail login` to authenticate."));
709
+ process.exit(1);
710
+ }
711
+ const spinner = ora2("Loading delivered emails...").start();
712
+ const result = await getDeliveredEmails({
713
+ limit: options.limit || 20
714
+ });
715
+ if (!result.ok) {
716
+ spinner.fail(`Error: ${result.error.message}`);
717
+ process.exit(1);
718
+ }
719
+ const { data: emails, pagination } = result.data;
720
+ spinner.stop();
721
+ if (options.json) {
722
+ console.log(JSON.stringify({ emails, pagination }, null, 2));
723
+ return;
724
+ }
725
+ console.log();
726
+ console.log(pc4.bold(`Delivered (${pagination.total} paid emails received)`));
727
+ console.log();
728
+ if (emails.length === 0) {
729
+ console.log(pc4.dim(" No delivered emails yet."));
730
+ console.log();
731
+ return;
732
+ }
733
+ console.log(
734
+ ` ${pc4.dim("#".padEnd(4))} ${pc4.dim("From".padEnd(28))} ${pc4.dim("Subject".padEnd(40))} ${pc4.dim("Date")}`
735
+ );
736
+ console.log(pc4.dim(` ${"-".repeat(85)}`));
737
+ emails.forEach((email, index) => {
738
+ const num = pc4.cyan(String(index + 1).padEnd(4));
739
+ const from = truncate(email.from.name || email.from.email, 26).padEnd(28);
740
+ const subject = truncate(email.subject, 38).padEnd(40);
741
+ const date = formatDate2(email.date);
742
+ console.log(` ${num}${from} ${subject} ${pc4.dim(date)}`);
743
+ });
744
+ console.log();
745
+ if (pagination.hasMore) {
746
+ console.log(
747
+ pc4.dim(` Showing ${emails.length} of ${pagination.total}. Use --limit to see more.`)
748
+ );
749
+ console.log();
750
+ }
751
+ console.log(pc4.dim(" Read email: btcemail read <id>"));
752
+ console.log();
753
+ }
754
+ async function inboundPendingCommand(options) {
755
+ if (!isAuthenticated()) {
756
+ console.log(pc4.yellow("Not logged in."));
757
+ console.log(pc4.dim("Run `btcemail login` to authenticate."));
758
+ process.exit(1);
759
+ }
760
+ const spinner = ora2("Loading pending emails...").start();
761
+ const result = await getPendingEmails({
762
+ limit: options.limit || 20
763
+ });
764
+ if (!result.ok) {
765
+ spinner.fail(`Error: ${result.error.message}`);
766
+ process.exit(1);
767
+ }
768
+ const { data: emails, pagination } = result.data;
769
+ spinner.stop();
770
+ if (options.json) {
771
+ console.log(JSON.stringify({ emails, pagination }, null, 2));
772
+ return;
773
+ }
774
+ console.log();
775
+ console.log(pc4.bold(`Pending (${pagination.total} awaiting payment)`));
776
+ console.log();
777
+ if (emails.length === 0) {
778
+ console.log(pc4.dim(" No pending emails."));
779
+ console.log();
780
+ return;
781
+ }
782
+ console.log(
783
+ ` ${pc4.dim("#".padEnd(4))} ${pc4.dim("From".padEnd(28))} ${pc4.dim("Subject".padEnd(35))} ${pc4.dim("Sats")}`
784
+ );
785
+ console.log(pc4.dim(` ${"-".repeat(80)}`));
786
+ emails.forEach((email, index) => {
787
+ const num = pc4.cyan(String(index + 1).padEnd(4));
788
+ const from = truncate(email.from.name || email.from.email, 26).padEnd(28);
789
+ const subject = truncate(email.subject, 33).padEnd(35);
790
+ const sats = pc4.yellow(String(email.amountSats));
791
+ console.log(` ${num}${from} ${subject} ${sats}`);
792
+ });
793
+ console.log();
794
+ if (pagination.hasMore) {
795
+ console.log(
796
+ pc4.dim(` Showing ${emails.length} of ${pagination.total}. Use --limit to see more.`)
797
+ );
798
+ console.log();
799
+ }
800
+ console.log(pc4.dim(" View details: btcemail inbound view <id>"));
801
+ console.log();
802
+ }
803
+ async function inboundViewCommand(id, options) {
804
+ if (!isAuthenticated()) {
805
+ console.log(pc4.yellow("Not logged in."));
806
+ console.log(pc4.dim("Run `btcemail login` to authenticate."));
807
+ process.exit(1);
808
+ }
809
+ const spinner = ora2("Loading email details...").start();
810
+ const result = await getPendingEmail(id);
811
+ if (!result.ok) {
812
+ if (result.error.code === "NOT_FOUND") {
813
+ spinner.fail(`Pending email not found: ${id}`);
814
+ } else {
815
+ spinner.fail(`Error: ${result.error.message}`);
816
+ }
817
+ process.exit(1);
818
+ }
819
+ spinner.stop();
820
+ const email = result.data;
821
+ if (options.json) {
822
+ console.log(JSON.stringify(email, null, 2));
823
+ return;
824
+ }
825
+ console.log();
826
+ console.log(pc4.bold(email.subject || "(No subject)"));
827
+ console.log();
828
+ console.log(`${pc4.dim("From:")} ${formatAddress(email.from)}`);
829
+ console.log(`${pc4.dim("Date:")} ${formatFullDate(email.createdAt)}`);
830
+ console.log(`${pc4.dim("ID:")} ${email.id}`);
831
+ console.log();
832
+ console.log(pc4.yellow(`Payment Required: ${email.amountSats} sats`));
833
+ console.log();
834
+ if (email.paymentHash) {
835
+ console.log(pc4.dim("To receive this email, pay the invoice and run:"));
836
+ console.log(pc4.cyan(` btcemail inbound pay ${email.id} --payment-hash <preimage>`));
837
+ } else {
838
+ console.log(pc4.dim("Payment information not available."));
839
+ }
840
+ console.log();
841
+ console.log(pc4.dim("-".repeat(60)));
842
+ console.log();
843
+ console.log(pc4.dim("Preview (full content available after payment):"));
844
+ console.log();
845
+ console.log(truncate(email.bodyText || email.body, 200));
846
+ console.log();
847
+ }
848
+ async function inboundPayCommand(id, options) {
849
+ if (!isAuthenticated()) {
850
+ console.log(pc4.yellow("Not logged in."));
851
+ console.log(pc4.dim("Run `btcemail login` to authenticate."));
852
+ process.exit(1);
853
+ }
854
+ if (!options.paymentHash) {
855
+ console.error(pc4.red("Payment hash is required. Use --payment-hash <preimage>"));
856
+ process.exit(1);
857
+ }
858
+ const spinner = ora2("Verifying payment...").start();
859
+ const result = await payForEmail(id, options.paymentHash);
860
+ if (!result.ok) {
861
+ if (result.error.code === "PAYMENT_INVALID") {
862
+ spinner.fail("Payment verification failed.");
863
+ console.log(pc4.dim("Make sure you paid the correct invoice and provided the preimage."));
864
+ } else if (result.error.code === "NOT_FOUND") {
865
+ spinner.fail(`Pending email not found or already delivered: ${id}`);
866
+ } else {
867
+ spinner.fail(`Error: ${result.error.message}`);
868
+ }
869
+ process.exit(1);
870
+ }
871
+ spinner.succeed("Payment verified! Email delivered.");
872
+ console.log();
873
+ console.log(`${pc4.dim("Email ID:")} ${result.data.emailId}`);
874
+ console.log(`${pc4.dim("Status:")} ${result.data.status}`);
875
+ console.log();
876
+ console.log(pc4.dim(`Read email: btcemail read ${result.data.emailId}`));
877
+ console.log();
878
+ }
879
+ async function inboundAcceptCommand(id, options) {
880
+ if (!isAuthenticated()) {
881
+ console.log(pc4.yellow("Not logged in."));
882
+ console.log(pc4.dim("Run `btcemail login` to authenticate."));
883
+ process.exit(1);
884
+ }
885
+ const spinner = ora2("Accepting email...").start();
886
+ const shouldWhitelist = !options.noWhitelist;
887
+ const result = await acceptPendingEmail(id, shouldWhitelist);
888
+ if (!result.ok) {
889
+ if (result.error.code === "NOT_FOUND") {
890
+ spinner.fail(`Pending email not found: ${id}`);
891
+ } else {
892
+ spinner.fail(`Error: ${result.error.message}`);
893
+ }
894
+ process.exit(1);
895
+ }
896
+ spinner.succeed("Email accepted and delivered!");
897
+ console.log();
898
+ console.log(`${pc4.dim("Email ID:")} ${result.data.emailId}`);
899
+ console.log(`${pc4.dim("Status:")} ${result.data.status}`);
900
+ if (result.data.whitelisted) {
901
+ console.log(`${pc4.dim("Sender:")} ${pc4.green(result.data.senderEmail)} ${pc4.dim("(whitelisted)")}`);
902
+ }
903
+ console.log();
904
+ console.log(pc4.dim(`Read email: btcemail read ${result.data.emailId}`));
905
+ console.log();
906
+ }
907
+ function truncate(str, maxLength) {
908
+ if (str.length <= maxLength) return str;
909
+ return `${str.slice(0, maxLength - 1)}\u2026`;
910
+ }
911
+ function formatAddress(addr) {
912
+ if (addr.name) {
913
+ return `${addr.name} <${addr.email}>`;
914
+ }
915
+ return addr.email;
916
+ }
917
+ function formatFullDate(dateString) {
918
+ const date = new Date(dateString);
919
+ return date.toLocaleString("en-US", {
920
+ weekday: "short",
921
+ year: "numeric",
922
+ month: "short",
923
+ day: "numeric",
924
+ hour: "numeric",
925
+ minute: "2-digit"
926
+ });
927
+ }
928
+ function formatDate2(dateString) {
929
+ const date = new Date(dateString);
930
+ const now = /* @__PURE__ */ new Date();
931
+ const diffMs = now.getTime() - date.getTime();
932
+ const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
933
+ if (diffDays === 0) {
934
+ return date.toLocaleTimeString("en-US", {
935
+ hour: "numeric",
936
+ minute: "2-digit"
937
+ });
938
+ }
939
+ if (diffDays < 7) {
940
+ return date.toLocaleDateString("en-US", { weekday: "short" });
941
+ }
942
+ return date.toLocaleDateString("en-US", {
943
+ month: "short",
944
+ day: "numeric"
945
+ });
946
+ }
947
+
948
+ // src/commands/list.ts
949
+ import ora3 from "ora";
950
+ import pc5 from "picocolors";
951
+
952
+ // src/utils/cache.ts
953
+ var CACHE_KEY = "emailCache";
954
+ var CACHE_TTL_MS = 30 * 60 * 1e3;
955
+ function cacheEmailList(folder, emails) {
956
+ config.set(CACHE_KEY, {
957
+ folder,
958
+ emails,
959
+ timestamp: Date.now()
960
+ });
961
+ }
962
+ function getCachedEmailList(folder) {
963
+ const cache = config.get(CACHE_KEY);
964
+ if (!cache) return null;
965
+ if (Date.now() - cache.timestamp > CACHE_TTL_MS) {
966
+ config.delete(CACHE_KEY);
967
+ return null;
968
+ }
969
+ if (folder && cache.folder !== folder) {
970
+ return null;
971
+ }
972
+ return cache;
973
+ }
974
+ function getEmailIdByIndex(index) {
975
+ const cache = getCachedEmailList();
976
+ if (!cache) return null;
977
+ const idx = index - 1;
978
+ if (idx < 0 || idx >= cache.emails.length) {
979
+ return null;
980
+ }
981
+ return cache.emails[idx].id;
982
+ }
983
+ function isNumericId(id) {
984
+ return /^\d+$/.test(id);
985
+ }
986
+
987
+ // src/commands/list.ts
988
+ async function listCommand(options) {
989
+ if (!isAuthenticated()) {
990
+ console.log(pc5.yellow("Not logged in."));
991
+ console.log(pc5.dim("Run `btcemail login` to authenticate."));
992
+ process.exit(1);
993
+ }
994
+ const folder = options.folder || "inbox";
995
+ const limit = options.limit || 20;
996
+ const page = options.page || 1;
997
+ const offset = (page - 1) * limit;
998
+ const spinner = ora3("Loading emails...").start();
999
+ const result = await getEmails({
1000
+ folder,
1001
+ limit,
1002
+ offset
1003
+ });
1004
+ if (!result.ok) {
1005
+ spinner.fail(`Error: ${result.error.message}`);
1006
+ process.exit(1);
1007
+ }
1008
+ const { data: emails, pagination } = result.data;
1009
+ spinner.stop();
1010
+ const cachedEmails = emails.map((e) => ({
1011
+ id: e.id,
1012
+ subject: e.subject,
1013
+ from: e.from.name || e.from.email
1014
+ }));
1015
+ cacheEmailList(folder, cachedEmails);
1016
+ if (options.json) {
1017
+ console.log(JSON.stringify({ emails, pagination }, null, 2));
1018
+ return;
1019
+ }
1020
+ console.log();
1021
+ console.log(pc5.bold(`${capitalize(folder)} (${pagination.total} emails)`));
544
1022
  console.log();
545
1023
  if (emails.length === 0) {
546
- console.log(pc4.dim(" No emails found."));
1024
+ console.log(pc5.dim(" No emails found."));
547
1025
  console.log();
548
1026
  return;
549
1027
  }
550
1028
  console.log(
551
- ` ${pc4.dim("#".padEnd(4))} ${pc4.dim("From".padEnd(25))} ${pc4.dim("Subject".padEnd(45))} ${pc4.dim("Date")}`
1029
+ ` ${pc5.dim("#".padEnd(4))} ${pc5.dim("From".padEnd(25))} ${pc5.dim("Subject".padEnd(45))} ${pc5.dim("Date")}`
552
1030
  );
553
- console.log(pc4.dim(" " + "-".repeat(85)));
1031
+ console.log(pc5.dim(` ${"-".repeat(85)}`));
554
1032
  emails.forEach((email, index) => {
555
- const num = pc4.cyan(String(index + 1).padEnd(4));
556
- const unreadMarker = email.unread ? pc4.cyan("\u25CF") : " ";
557
- const from = truncate(email.from.name || email.from.email, 23).padEnd(25);
558
- const subject = truncate(email.subject, 43).padEnd(45);
559
- const date = formatDate(email.date);
560
- console.log(`${unreadMarker} ${num}${from} ${subject} ${pc4.dim(date)}`);
1033
+ const num = pc5.cyan(String(index + 1).padEnd(4));
1034
+ const unreadMarker = email.unread ? pc5.cyan("\u25CF") : " ";
1035
+ const from = truncate2(email.from.name || email.from.email, 23).padEnd(25);
1036
+ const subject = truncate2(email.subject, 43).padEnd(45);
1037
+ const date = formatDate3(email.date);
1038
+ console.log(`${unreadMarker} ${num}${from} ${subject} ${pc5.dim(date)}`);
561
1039
  });
562
1040
  console.log();
563
1041
  const totalPages = Math.ceil(pagination.total / limit);
564
1042
  const currentPage = page;
565
1043
  if (totalPages > 1) {
566
1044
  console.log(
567
- pc4.dim(` Page ${currentPage} of ${totalPages} (${emails.length} of ${pagination.total})`)
1045
+ pc5.dim(` Page ${currentPage} of ${totalPages} (${emails.length} of ${pagination.total})`)
568
1046
  );
569
1047
  if (currentPage < totalPages) {
570
- console.log(pc4.dim(` Next page: btcemail list --page ${currentPage + 1}`));
1048
+ console.log(pc5.dim(` Next page: btcemail list --page ${currentPage + 1}`));
1049
+ }
1050
+ console.log();
1051
+ }
1052
+ console.log(pc5.dim(" Read email: btcemail read <#> or btcemail read <id>"));
1053
+ console.log();
1054
+ }
1055
+ function capitalize(str) {
1056
+ return str.charAt(0).toUpperCase() + str.slice(1);
1057
+ }
1058
+ function truncate2(str, maxLength) {
1059
+ if (str.length <= maxLength) return str;
1060
+ return `${str.slice(0, maxLength - 1)}\u2026`;
1061
+ }
1062
+ function formatDate3(dateString) {
1063
+ const date = new Date(dateString);
1064
+ const now = /* @__PURE__ */ new Date();
1065
+ const diffMs = now.getTime() - date.getTime();
1066
+ const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
1067
+ if (diffDays === 0) {
1068
+ return date.toLocaleTimeString("en-US", {
1069
+ hour: "numeric",
1070
+ minute: "2-digit"
1071
+ });
1072
+ }
1073
+ if (diffDays < 7) {
1074
+ return date.toLocaleDateString("en-US", { weekday: "short" });
1075
+ }
1076
+ return date.toLocaleDateString("en-US", {
1077
+ month: "short",
1078
+ day: "numeric"
1079
+ });
1080
+ }
1081
+
1082
+ // src/commands/login.ts
1083
+ import { randomUUID } from "crypto";
1084
+ import open from "open";
1085
+ import ora4 from "ora";
1086
+ import pc6 from "picocolors";
1087
+ var POLL_INTERVAL_MS = 2e3;
1088
+ var POLL_TIMEOUT_MS = 3e5;
1089
+ async function loginCommand() {
1090
+ const sessionId = randomUUID();
1091
+ const baseUrl = getApiBaseUrl().replace("/api/v1", "");
1092
+ const authUrl = `${baseUrl}/auth/cli?session_id=${sessionId}`;
1093
+ const pollUrl = `${baseUrl}/api/auth/cli-session?session_id=${sessionId}`;
1094
+ console.log();
1095
+ console.log(pc6.bold("btc.email CLI Login"));
1096
+ console.log();
1097
+ try {
1098
+ const createResponse = await fetch(`${baseUrl}/api/auth/cli-session`, {
1099
+ method: "POST",
1100
+ headers: { "Content-Type": "application/json" },
1101
+ body: JSON.stringify({ session_id: sessionId })
1102
+ });
1103
+ if (!createResponse.ok) {
1104
+ console.error(pc6.red("Failed to initialize login session"));
1105
+ process.exit(1);
1106
+ }
1107
+ } catch (error) {
1108
+ console.error(pc6.red("Failed to connect to btc.email server"));
1109
+ console.error(pc6.dim(error instanceof Error ? error.message : "Unknown error"));
1110
+ process.exit(1);
1111
+ }
1112
+ console.log(pc6.dim("Opening browser for authentication..."));
1113
+ console.log();
1114
+ console.log(pc6.dim("If the browser doesn't open, visit:"));
1115
+ console.log(pc6.cyan(authUrl));
1116
+ console.log();
1117
+ try {
1118
+ await open(authUrl);
1119
+ } catch {
1120
+ }
1121
+ const spinner = ora4("Waiting for authentication...").start();
1122
+ const startTime = Date.now();
1123
+ while (Date.now() - startTime < POLL_TIMEOUT_MS) {
1124
+ try {
1125
+ const response = await fetch(pollUrl);
1126
+ if (response.status === 202) {
1127
+ await sleep2(POLL_INTERVAL_MS);
1128
+ continue;
1129
+ }
1130
+ if (response.status === 410) {
1131
+ spinner.fail("Session expired. Please try again.");
1132
+ process.exit(1);
1133
+ }
1134
+ if (response.ok) {
1135
+ const data = await response.json();
1136
+ if (data.status === "complete") {
1137
+ setAuth({
1138
+ token: data.data.token,
1139
+ expiresAt: data.data.expiresAt,
1140
+ userId: data.data.userId,
1141
+ email: data.data.email
1142
+ });
1143
+ spinner.succeed(pc6.green("Successfully logged in!"));
1144
+ console.log();
1145
+ console.log(` ${pc6.dim("Email:")} ${data.data.email}`);
1146
+ console.log();
1147
+ console.log(pc6.dim("You can now use btcemail commands."));
1148
+ console.log(pc6.dim("Run `btcemail --help` to see available commands."));
1149
+ return;
1150
+ }
1151
+ }
1152
+ await sleep2(POLL_INTERVAL_MS);
1153
+ } catch (_error) {
1154
+ await sleep2(POLL_INTERVAL_MS);
571
1155
  }
1156
+ }
1157
+ spinner.fail("Authentication timed out. Please try again.");
1158
+ process.exit(1);
1159
+ }
1160
+ function sleep2(ms) {
1161
+ return new Promise((resolve) => setTimeout(resolve, ms));
1162
+ }
1163
+
1164
+ // src/commands/logout.ts
1165
+ import pc7 from "picocolors";
1166
+ async function logoutCommand() {
1167
+ if (!isAuthenticated()) {
1168
+ console.log(pc7.yellow("You are not logged in."));
1169
+ return;
1170
+ }
1171
+ clearAuth();
1172
+ console.log(pc7.green("Successfully logged out."));
1173
+ }
1174
+
1175
+ // src/commands/network.ts
1176
+ import pc8 from "picocolors";
1177
+ async function networkShowCommand(options = {}) {
1178
+ const token = getToken();
1179
+ if (!token) {
1180
+ console.error(pc8.red("Not logged in. Run 'btcemail login' first."));
1181
+ process.exit(1);
1182
+ }
1183
+ const result = await getNetwork();
1184
+ if (!result.ok) {
1185
+ console.error(pc8.red(`Error: ${result.error.message}`));
1186
+ process.exit(1);
1187
+ }
1188
+ const { network_mode, networks } = result.data;
1189
+ if (options.json) {
1190
+ console.log(JSON.stringify({ network_mode, networks }, null, 2));
1191
+ return;
1192
+ }
1193
+ console.log();
1194
+ console.log(pc8.bold("Network Mode"));
1195
+ console.log();
1196
+ const isTestnet = network_mode === "testnet";
1197
+ const currentLabel = isTestnet ? pc8.blue("testnet") + pc8.dim(" (fake Bitcoin)") : pc8.red("mainnet") + pc8.dim(" (real Bitcoin)");
1198
+ console.log(` Current: ${currentLabel}`);
1199
+ console.log();
1200
+ console.log(pc8.dim(" Available networks:"));
1201
+ console.log(
1202
+ ` testnet: ${networks.testnet.available ? pc8.green("available") : pc8.red("unavailable")}`
1203
+ );
1204
+ console.log(
1205
+ ` mainnet: ${networks.mainnet.available ? pc8.green("available") : pc8.red("unavailable")}`
1206
+ );
1207
+ console.log();
1208
+ if (!isTestnet) {
1209
+ console.log(pc8.yellow(" Warning: You are on mainnet. Payments use real Bitcoin!"));
572
1210
  console.log();
573
1211
  }
574
- console.log(pc4.dim(" Read email: btcemail read <#> or btcemail read <id>"));
575
- console.log();
576
- }
577
- function capitalize(str) {
578
- return str.charAt(0).toUpperCase() + str.slice(1);
579
1212
  }
580
- function truncate(str, maxLength) {
581
- if (str.length <= maxLength) return str;
582
- return str.slice(0, maxLength - 1) + "\u2026";
583
- }
584
- function formatDate(dateString) {
585
- const date = new Date(dateString);
586
- const now = /* @__PURE__ */ new Date();
587
- const diffMs = now.getTime() - date.getTime();
588
- const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
589
- if (diffDays === 0) {
590
- return date.toLocaleTimeString("en-US", {
591
- hour: "numeric",
592
- minute: "2-digit"
593
- });
1213
+ async function networkSwitchCommand(network2, options = {}) {
1214
+ const token = getToken();
1215
+ if (!token) {
1216
+ console.error(pc8.red("Not logged in. Run 'btcemail login' first."));
1217
+ process.exit(1);
594
1218
  }
595
- if (diffDays < 7) {
596
- return date.toLocaleDateString("en-US", { weekday: "short" });
1219
+ if (network2 !== "testnet" && network2 !== "mainnet") {
1220
+ console.error(pc8.red("Invalid network. Use 'testnet' or 'mainnet'."));
1221
+ process.exit(1);
597
1222
  }
598
- return date.toLocaleDateString("en-US", {
599
- month: "short",
600
- day: "numeric"
601
- });
1223
+ if (network2 === "mainnet") {
1224
+ console.log();
1225
+ console.log(pc8.yellow("Warning: You are switching to mainnet."));
1226
+ console.log(pc8.yellow("All payments will use real Bitcoin!"));
1227
+ console.log();
1228
+ }
1229
+ const result = await setNetwork(network2);
1230
+ if (!result.ok) {
1231
+ console.error(pc8.red(`Error: ${result.error.message}`));
1232
+ process.exit(1);
1233
+ }
1234
+ if (options.json) {
1235
+ console.log(JSON.stringify({ success: true, network_mode: result.data.network_mode }, null, 2));
1236
+ return;
1237
+ }
1238
+ const label = network2 === "testnet" ? pc8.blue("testnet") : pc8.red("mainnet");
1239
+ console.log(pc8.green(`Switched to ${label}`));
602
1240
  }
603
1241
 
604
1242
  // src/commands/read.ts
605
- import pc5 from "picocolors";
606
- import ora4 from "ora";
1243
+ import ora5 from "ora";
1244
+ import pc9 from "picocolors";
607
1245
  async function readCommand(idOrNumber, options) {
608
1246
  if (!isAuthenticated()) {
609
- console.log(pc5.yellow("Not logged in."));
610
- console.log(pc5.dim("Run `btcemail login` to authenticate."));
1247
+ console.log(pc9.yellow("Not logged in."));
1248
+ console.log(pc9.dim("Run `btcemail login` to authenticate."));
611
1249
  process.exit(1);
612
1250
  }
613
1251
  let emailId = idOrNumber;
@@ -619,16 +1257,18 @@ async function readCommand(idOrNumber, options) {
619
1257
  } else {
620
1258
  const cache = getCachedEmailList();
621
1259
  if (cache) {
622
- console.error(pc5.red(`Email #${index} not found in list.`));
623
- console.log(pc5.dim(`List has ${cache.emails.length} emails. Run 'btcemail list' to refresh.`));
1260
+ console.error(pc9.red(`Email #${index} not found in list.`));
1261
+ console.log(
1262
+ pc9.dim(`List has ${cache.emails.length} emails. Run 'btcemail list' to refresh.`)
1263
+ );
624
1264
  } else {
625
- console.error(pc5.red(`No email list cached.`));
626
- console.log(pc5.dim(`Run 'btcemail list' first, then use 'btcemail read <#>'.`));
1265
+ console.error(pc9.red(`No email list cached.`));
1266
+ console.log(pc9.dim(`Run 'btcemail list' first, then use 'btcemail read <#>'.`));
627
1267
  }
628
1268
  process.exit(1);
629
1269
  }
630
1270
  }
631
- const spinner = ora4("Loading email...").start();
1271
+ const spinner = ora5("Loading email...").start();
632
1272
  const result = await getEmail(emailId);
633
1273
  if (!result.ok) {
634
1274
  if (result.error.code === "NOT_FOUND") {
@@ -645,37 +1285,37 @@ async function readCommand(idOrNumber, options) {
645
1285
  return;
646
1286
  }
647
1287
  console.log();
648
- console.log(pc5.bold(email.subject || "(No subject)"));
1288
+ console.log(pc9.bold(email.subject || "(No subject)"));
649
1289
  console.log();
650
- console.log(`${pc5.dim("From:")} ${formatAddress(email.from)}`);
651
- console.log(`${pc5.dim("To:")} ${email.to.map(formatAddress).join(", ")}`);
652
- console.log(`${pc5.dim("Date:")} ${formatFullDate(email.date)}`);
653
- console.log(`${pc5.dim("ID:")} ${email.id}`);
1290
+ console.log(`${pc9.dim("From:")} ${formatAddress2(email.from)}`);
1291
+ console.log(`${pc9.dim("To:")} ${email.to.map(formatAddress2).join(", ")}`);
1292
+ console.log(`${pc9.dim("Date:")} ${formatFullDate2(email.date)}`);
1293
+ console.log(`${pc9.dim("ID:")} ${email.id}`);
654
1294
  if (email.labels.length > 0) {
655
- console.log(`${pc5.dim("Labels:")} ${email.labels.join(", ")}`);
1295
+ console.log(`${pc9.dim("Labels:")} ${email.labels.join(", ")}`);
656
1296
  }
657
1297
  console.log();
658
- console.log(pc5.dim("-".repeat(60)));
1298
+ console.log(pc9.dim("-".repeat(60)));
659
1299
  console.log();
660
1300
  const content = email.bodyText || email.body || email.snippet;
661
1301
  if (content) {
662
1302
  const displayContent = email.bodyText || stripHtml(email.body || email.snippet);
663
1303
  console.log(displayContent);
664
1304
  } else {
665
- console.log(pc5.dim("(No content)"));
1305
+ console.log(pc9.dim("(No content)"));
666
1306
  }
667
1307
  console.log();
668
1308
  }
669
1309
  function stripHtml(html) {
670
1310
  return html.replace(/<br\s*\/?>/gi, "\n").replace(/<\/p>/gi, "\n\n").replace(/<\/div>/gi, "\n").replace(/<[^>]*>/g, "").replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').trim();
671
1311
  }
672
- function formatAddress(addr) {
1312
+ function formatAddress2(addr) {
673
1313
  if (addr.name) {
674
1314
  return `${addr.name} <${addr.email}>`;
675
1315
  }
676
1316
  return addr.email;
677
1317
  }
678
- function formatFullDate(dateString) {
1318
+ function formatFullDate2(dateString) {
679
1319
  const date = new Date(dateString);
680
1320
  return date.toLocaleString("en-US", {
681
1321
  weekday: "short",
@@ -688,19 +1328,19 @@ function formatFullDate(dateString) {
688
1328
  }
689
1329
 
690
1330
  // src/commands/search.ts
691
- import pc6 from "picocolors";
692
- import ora5 from "ora";
1331
+ import ora6 from "ora";
1332
+ import pc10 from "picocolors";
693
1333
  async function searchCommand(query, options) {
694
1334
  if (!isAuthenticated()) {
695
- console.log(pc6.yellow("Not logged in."));
696
- console.log(pc6.dim("Run `btcemail login` to authenticate."));
1335
+ console.log(pc10.yellow("Not logged in."));
1336
+ console.log(pc10.dim("Run `btcemail login` to authenticate."));
697
1337
  process.exit(1);
698
1338
  }
699
1339
  if (!query || query.trim().length === 0) {
700
- console.error(pc6.red("Error: Search query is required"));
1340
+ console.error(pc10.red("Error: Search query is required"));
701
1341
  process.exit(1);
702
1342
  }
703
- const spinner = ora5(`Searching for "${query}"...`).start();
1343
+ const spinner = ora6(`Searching for "${query}"...`).start();
704
1344
  const result = await getEmails({
705
1345
  search: query.trim(),
706
1346
  limit: options.limit || 20
@@ -722,42 +1362,40 @@ async function searchCommand(query, options) {
722
1362
  return;
723
1363
  }
724
1364
  console.log();
725
- console.log(pc6.bold(`Search: "${query}" (${pagination.total} found)`));
1365
+ console.log(pc10.bold(`Search: "${query}" (${pagination.total} found)`));
726
1366
  console.log();
727
1367
  if (emails.length === 0) {
728
- console.log(pc6.dim(" No emails found matching your search."));
1368
+ console.log(pc10.dim(" No emails found matching your search."));
729
1369
  console.log();
730
1370
  return;
731
1371
  }
732
1372
  console.log(
733
- ` ${pc6.dim("#".padEnd(4))} ${pc6.dim("From".padEnd(25))} ${pc6.dim("Subject".padEnd(45))} ${pc6.dim("Date")}`
1373
+ ` ${pc10.dim("#".padEnd(4))} ${pc10.dim("From".padEnd(25))} ${pc10.dim("Subject".padEnd(45))} ${pc10.dim("Date")}`
734
1374
  );
735
- console.log(pc6.dim(" " + "-".repeat(85)));
1375
+ console.log(pc10.dim(` ${"-".repeat(85)}`));
736
1376
  emails.forEach((email, index) => {
737
- const num = pc6.cyan(String(index + 1).padEnd(4));
738
- const unreadMarker = email.unread ? pc6.cyan("\u25CF") : " ";
739
- const from = truncate2(email.from.name || email.from.email, 23).padEnd(25);
740
- const subject = truncate2(email.subject, 43).padEnd(45);
741
- const date = formatDate2(email.date);
742
- console.log(`${unreadMarker} ${num}${from} ${subject} ${pc6.dim(date)}`);
1377
+ const num = pc10.cyan(String(index + 1).padEnd(4));
1378
+ const unreadMarker = email.unread ? pc10.cyan("\u25CF") : " ";
1379
+ const from = truncate3(email.from.name || email.from.email, 23).padEnd(25);
1380
+ const subject = truncate3(email.subject, 43).padEnd(45);
1381
+ const date = formatDate4(email.date);
1382
+ console.log(`${unreadMarker} ${num}${from} ${subject} ${pc10.dim(date)}`);
743
1383
  });
744
1384
  console.log();
745
1385
  if (pagination.hasMore) {
746
1386
  console.log(
747
- pc6.dim(
748
- ` Showing ${emails.length} of ${pagination.total}. Use --limit to see more.`
749
- )
1387
+ pc10.dim(` Showing ${emails.length} of ${pagination.total}. Use --limit to see more.`)
750
1388
  );
751
1389
  console.log();
752
1390
  }
753
- console.log(pc6.dim(" Read email: btcemail read <#> or btcemail read <id>"));
1391
+ console.log(pc10.dim(" Read email: btcemail read <#> or btcemail read <id>"));
754
1392
  console.log();
755
1393
  }
756
- function truncate2(str, maxLength) {
1394
+ function truncate3(str, maxLength) {
757
1395
  if (str.length <= maxLength) return str;
758
- return str.slice(0, maxLength - 1) + "\u2026";
1396
+ return `${str.slice(0, maxLength - 1)}\u2026`;
759
1397
  }
760
- function formatDate2(dateString) {
1398
+ function formatDate4(dateString) {
761
1399
  const date = new Date(dateString);
762
1400
  const now = /* @__PURE__ */ new Date();
763
1401
  const diffMs = now.getTime() - date.getTime();
@@ -767,248 +1405,10 @@ function formatDate2(dateString) {
767
1405
  hour: "numeric",
768
1406
  minute: "2-digit"
769
1407
  });
770
- }
771
- if (diffDays < 7) {
772
- return date.toLocaleDateString("en-US", { weekday: "short" });
773
- }
774
- return date.toLocaleDateString("en-US", {
775
- month: "short",
776
- day: "numeric"
777
- });
778
- }
779
-
780
- // src/commands/credits.ts
781
- import pc8 from "picocolors";
782
- import ora6 from "ora";
783
- import qrcode from "qrcode-terminal";
784
-
785
- // src/utils/payment-polling.ts
786
- import pc7 from "picocolors";
787
- var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
788
- async function pollForPayment(options) {
789
- const { paymentHash, checkFn, maxAttempts = 120, intervalMs = 2500, onPaid } = options;
790
- let frame = 0;
791
- let attempts = 0;
792
- process.stdout.write(`${pc7.cyan(SPINNER_FRAMES[0])} Waiting for payment...`);
793
- while (attempts < maxAttempts) {
794
- try {
795
- const status = await checkFn(paymentHash);
796
- if (status.paid) {
797
- process.stdout.write("\r" + " ".repeat(50) + "\r");
798
- console.log(pc7.green("\u2713") + " Payment received!");
799
- if (onPaid) onPaid();
800
- return status;
801
- }
802
- } catch {
803
- }
804
- frame = (frame + 1) % SPINNER_FRAMES.length;
805
- const timeLeft = Math.ceil((maxAttempts - attempts) * intervalMs / 1e3 / 60);
806
- process.stdout.write(
807
- `\r${pc7.cyan(SPINNER_FRAMES[frame])} Waiting for payment... (${timeLeft}m remaining)`
808
- );
809
- await sleep2(intervalMs);
810
- attempts++;
811
- }
812
- process.stdout.write("\r" + " ".repeat(60) + "\r");
813
- console.log(pc7.yellow("\u26A0") + " Timed out waiting for payment.");
814
- return { paid: false };
815
- }
816
- function sleep2(ms) {
817
- return new Promise((resolve) => setTimeout(resolve, ms));
818
- }
819
-
820
- // src/commands/credits.ts
821
- async function creditsBalanceCommand() {
822
- if (!isAuthenticated()) {
823
- console.log(pc8.yellow("Not logged in."));
824
- console.log(pc8.dim("Run `btcemail login` to authenticate."));
825
- process.exit(1);
826
- }
827
- const spinner = ora6("Fetching balance...").start();
828
- const result = await getCreditsBalance();
829
- if (!result.ok) {
830
- spinner.fail(`Error: ${result.error.message}`);
831
- process.exit(1);
832
- }
833
- spinner.stop();
834
- const { balanceSats, lifetimePurchasedSats, lifetimeSpentSats } = result.data;
835
- console.log();
836
- console.log(pc8.bold("Credit Balance"));
837
- console.log();
838
- console.log(` ${pc8.green(formatSats(balanceSats))} sats`);
839
- console.log();
840
- if (lifetimePurchasedSats > 0 || lifetimeSpentSats > 0) {
841
- console.log(pc8.dim(` Purchased: ${formatSats(lifetimePurchasedSats)} sats`));
842
- console.log(pc8.dim(` Spent: ${formatSats(lifetimeSpentSats)} sats`));
843
- console.log();
844
- }
845
- if (balanceSats === 0) {
846
- console.log(pc8.dim(" No credits available."));
847
- console.log(pc8.dim(" Run `btcemail credits purchase` to buy credits."));
848
- console.log();
849
- }
850
- }
851
- async function creditsHistoryCommand(options) {
852
- if (!isAuthenticated()) {
853
- console.log(pc8.yellow("Not logged in."));
854
- console.log(pc8.dim("Run `btcemail login` to authenticate."));
855
- process.exit(1);
856
- }
857
- const spinner = ora6("Loading transaction history...").start();
858
- const result = await getCreditTransactions({
859
- limit: options.limit || 20,
860
- type: options.type
861
- });
862
- if (!result.ok) {
863
- spinner.fail(`Error: ${result.error.message}`);
864
- process.exit(1);
865
- }
866
- const { data: transactions, pagination } = result.data;
867
- spinner.stop();
868
- if (options.json) {
869
- console.log(JSON.stringify({ transactions, pagination }, null, 2));
870
- return;
871
- }
872
- console.log();
873
- console.log(pc8.bold("Transaction History"));
874
- console.log();
875
- if (transactions.length === 0) {
876
- console.log(pc8.dim(" No transactions found."));
877
- console.log();
878
- return;
879
- }
880
- console.log(
881
- ` ${pc8.dim("Date".padEnd(12))} ${pc8.dim("Type".padEnd(15))} ${pc8.dim("Amount".padEnd(12))} ${pc8.dim("Balance")}`
882
- );
883
- console.log(pc8.dim(" " + "-".repeat(55)));
884
- for (const tx of transactions) {
885
- const date = formatDate3(tx.createdAt);
886
- const type = formatTransactionType(tx.transactionType).padEnd(15);
887
- const amount = formatAmount(tx.amountSats).padEnd(12);
888
- const balance = formatSats(tx.balanceAfter);
889
- console.log(` ${date.padEnd(12)} ${type} ${amount} ${pc8.dim(balance)}`);
890
- }
891
- console.log();
892
- if (pagination.hasMore) {
893
- console.log(pc8.dim(` Use --limit to see more transactions.`));
894
- console.log();
895
- }
896
- }
897
- async function creditsPurchaseCommand(options) {
898
- if (!isAuthenticated()) {
899
- console.log(pc8.yellow("Not logged in."));
900
- console.log(pc8.dim("Run `btcemail login` to authenticate."));
901
- process.exit(1);
902
- }
903
- const purchaseOptions = getCreditPurchaseOptions();
904
- if (options.option === void 0) {
905
- console.log();
906
- console.log(pc8.bold("Purchase Options"));
907
- console.log();
908
- purchaseOptions.forEach((opt, index) => {
909
- console.log(` ${pc8.cyan(`[${index}]`)} ${opt.label}`);
910
- console.log(` ${pc8.dim(opt.description)}`);
911
- console.log(` ${pc8.dim(`Price: ${formatSats(opt.priceSats)} sats`)}`);
912
- if (opt.bonusSats > 0) {
913
- console.log(` ${pc8.green(`+${formatSats(opt.bonusSats)} bonus sats`)}`);
914
- }
915
- console.log();
916
- });
917
- console.log(pc8.dim(" Usage: btcemail credits purchase --option <index>"));
918
- console.log(pc8.dim(" Add --wait to wait for payment confirmation."));
919
- console.log();
920
- return;
921
- }
922
- if (options.option < 0 || options.option >= purchaseOptions.length) {
923
- console.error(pc8.red(`Error: Invalid option. Choose 0-${purchaseOptions.length - 1}.`));
924
- process.exit(1);
925
- }
926
- const selectedOption = purchaseOptions[options.option];
927
- const spinner = ora6("Creating invoice...").start();
928
- const result = await purchaseCredits(options.option);
929
- if (!result.ok) {
930
- spinner.fail(`Error: ${result.error.message}`);
931
- process.exit(1);
932
- }
933
- spinner.stop();
934
- const purchase = result.data;
935
- if (options.json) {
936
- console.log(JSON.stringify(purchase, null, 2));
937
- return;
938
- }
939
- console.log();
940
- console.log(pc8.bold("Lightning Invoice"));
941
- console.log();
942
- console.log(` ${pc8.dim("Amount:")} ${formatSats(purchase.amountSats)} sats`);
943
- console.log(` ${pc8.dim("Credits:")} ${formatSats(purchase.creditsToReceive)} sats`);
944
- if (purchase.bonusSats > 0) {
945
- console.log(` ${pc8.dim("Bonus:")} ${pc8.green(`+${formatSats(purchase.bonusSats)} sats`)}`);
946
- }
947
- console.log();
948
- console.log(pc8.bold("Scan to pay:"));
949
- console.log();
950
- qrcode.generate(purchase.invoice, { small: true }, (qr) => {
951
- const indentedQr = qr.split("\n").map((line) => " " + line).join("\n");
952
- console.log(indentedQr);
953
- });
954
- console.log();
955
- console.log(pc8.bold("Invoice:"));
956
- console.log();
957
- console.log(` ${purchase.invoice}`);
958
- console.log();
959
- console.log(pc8.dim(` Payment hash: ${purchase.paymentHash}`));
960
- console.log(pc8.dim(` Expires: ${new Date(purchase.expiresAt).toLocaleString()}`));
961
- console.log();
962
- if (options.wait) {
963
- console.log();
964
- const status = await pollForPayment({
965
- paymentHash: purchase.paymentHash,
966
- checkFn: async (hash) => {
967
- const result2 = await checkPaymentStatus(hash);
968
- if (result2.ok) {
969
- return { paid: result2.data.paid };
970
- }
971
- return { paid: false };
972
- },
973
- onPaid: async () => {
974
- const balanceResult = await getCreditsBalance();
975
- if (balanceResult.ok) {
976
- console.log();
977
- console.log(`${pc8.dim("New balance:")} ${pc8.green(formatSats(balanceResult.data.balanceSats))} sats`);
978
- }
979
- }
980
- });
981
- if (!status.paid) {
982
- console.log(pc8.dim(" You can still pay the invoice and credits will be added."));
983
- }
984
- console.log();
985
- } else {
986
- console.log(pc8.dim(" Scan the QR code or copy the invoice above to pay."));
987
- console.log(pc8.dim(" Use --wait to wait for payment confirmation."));
988
- console.log();
989
- }
990
- }
991
- function formatSats(sats) {
992
- return sats.toLocaleString();
993
- }
994
- function formatAmount(sats) {
995
- if (sats >= 0) {
996
- return pc8.green(`+${formatSats(sats)}`);
997
- }
998
- return pc8.red(formatSats(sats));
999
- }
1000
- function formatTransactionType(type) {
1001
- const types = {
1002
- topup: "Top-up",
1003
- email_sent: "Email Sent",
1004
- email_received: "Email Received",
1005
- refund: "Refund",
1006
- bonus: "Bonus"
1007
- };
1008
- return types[type] || type;
1009
- }
1010
- function formatDate3(dateString) {
1011
- const date = new Date(dateString);
1408
+ }
1409
+ if (diffDays < 7) {
1410
+ return date.toLocaleDateString("en-US", { weekday: "short" });
1411
+ }
1012
1412
  return date.toLocaleDateString("en-US", {
1013
1413
  month: "short",
1014
1414
  day: "numeric"
@@ -1016,13 +1416,13 @@ function formatDate3(dateString) {
1016
1416
  }
1017
1417
 
1018
1418
  // src/commands/send.ts
1019
- import pc9 from "picocolors";
1020
1419
  import ora7 from "ora";
1420
+ import pc11 from "picocolors";
1021
1421
  import qrcode2 from "qrcode-terminal";
1022
1422
  async function sendCommand(options) {
1023
1423
  if (!isAuthenticated()) {
1024
- console.log(pc9.yellow("Not logged in."));
1025
- console.log(pc9.dim("Run `btcemail login` to authenticate."));
1424
+ console.log(pc11.yellow("Not logged in."));
1425
+ console.log(pc11.dim("Run `btcemail login` to authenticate."));
1026
1426
  process.exit(1);
1027
1427
  }
1028
1428
  let fromEmail = options.fromEmail;
@@ -1031,7 +1431,7 @@ async function sendCommand(options) {
1031
1431
  const whoamiResult = await getWhoami();
1032
1432
  spinner2.stop();
1033
1433
  if (whoamiResult.ok && whoamiResult.data.username) {
1034
- fromEmail = `${whoamiResult.data.username}@btc.email`;
1434
+ fromEmail = whoamiResult.data.username;
1035
1435
  } else {
1036
1436
  const auth = getAuth();
1037
1437
  if (auth?.email?.endsWith("@btc.email")) {
@@ -1040,29 +1440,29 @@ async function sendCommand(options) {
1040
1440
  }
1041
1441
  }
1042
1442
  if (!fromEmail || !fromEmail.endsWith("@btc.email")) {
1043
- console.error(pc9.red("No @btc.email address found."));
1044
- console.log(pc9.dim("Register a username at https://btc.email"));
1443
+ console.error(pc11.red("No @btc.email address found."));
1444
+ console.log(pc11.dim("Register a username at https://btc.email"));
1045
1445
  process.exit(1);
1046
1446
  }
1047
1447
  if (!options.to) {
1048
- console.error(pc9.red("Recipient is required. Use --to <email>"));
1448
+ console.error(pc11.red("Recipient is required. Use --to <email>"));
1049
1449
  process.exit(1);
1050
1450
  }
1051
1451
  if (!options.subject) {
1052
- console.error(pc9.red("Subject is required. Use --subject <text>"));
1452
+ console.error(pc11.red("Subject is required. Use --subject <text>"));
1053
1453
  process.exit(1);
1054
1454
  }
1055
1455
  if (!options.body) {
1056
- console.error(pc9.red("Body is required. Use --body <text>"));
1456
+ console.error(pc11.red("Body is required. Use --body <text>"));
1057
1457
  process.exit(1);
1058
1458
  }
1059
1459
  const toEmails = options.to.split(",").map((e) => e.trim());
1060
1460
  console.log();
1061
- console.log(pc9.bold("Sending Email"));
1461
+ console.log(pc11.bold("Sending Email"));
1062
1462
  console.log();
1063
- console.log(`${pc9.dim("From:")} ${fromEmail}`);
1064
- console.log(`${pc9.dim("To:")} ${toEmails.join(", ")}`);
1065
- console.log(`${pc9.dim("Subject:")} ${options.subject}`);
1463
+ console.log(`${pc11.dim("From:")} ${fromEmail}`);
1464
+ console.log(`${pc11.dim("To:")} ${toEmails.join(", ")}`);
1465
+ console.log(`${pc11.dim("Subject:")} ${options.subject}`);
1066
1466
  console.log();
1067
1467
  if (!options.paymentHash) {
1068
1468
  const spinner2 = ora7("Getting invoice...").start();
@@ -1073,17 +1473,17 @@ async function sendCommand(options) {
1073
1473
  spinner2.stop();
1074
1474
  if (invoiceResult.ok && invoiceResult.data.amountSats > 0) {
1075
1475
  const invoice = invoiceResult.data;
1076
- console.log(pc9.yellow(`Payment required: ${invoice.amountSats} sats`));
1476
+ console.log(pc11.yellow(`Payment required: ${invoice.amountSats} sats`));
1077
1477
  console.log();
1078
- console.log(pc9.bold("Scan to pay:"));
1478
+ console.log(pc11.bold("Scan to pay:"));
1079
1479
  console.log();
1080
1480
  qrcode2.generate(invoice.invoice, { small: true }, (qr) => {
1081
- const indentedQr = qr.split("\n").map((line) => " " + line).join("\n");
1481
+ const indentedQr = qr.split("\n").map((line) => ` ${line}`).join("\n");
1082
1482
  console.log(indentedQr);
1083
1483
  });
1084
1484
  console.log();
1085
- console.log(pc9.dim("Lightning Invoice:"));
1086
- console.log(pc9.cyan(invoice.invoice));
1485
+ console.log(pc11.dim("Lightning Invoice:"));
1486
+ console.log(pc11.cyan(invoice.invoice));
1087
1487
  console.log();
1088
1488
  if (options.wait) {
1089
1489
  const status = await pollForPayment({
@@ -1111,24 +1511,26 @@ async function sendCommand(options) {
1111
1511
  }
1112
1512
  sendSpinner.succeed("Email sent!");
1113
1513
  console.log();
1114
- console.log(`${pc9.dim("Email ID:")} ${result2.data.emailId}`);
1514
+ console.log(`${pc11.dim("Email ID:")} ${result2.data.emailId}`);
1115
1515
  if (result2.data.messageId) {
1116
- console.log(`${pc9.dim("Message ID:")} ${result2.data.messageId}`);
1516
+ console.log(`${pc11.dim("Message ID:")} ${result2.data.messageId}`);
1117
1517
  }
1118
1518
  console.log();
1119
1519
  } else {
1120
- console.log(pc9.dim("Payment not confirmed. You can still pay and resend with --payment-hash."));
1520
+ console.log(
1521
+ pc11.dim("Payment not confirmed. You can still pay and resend with --payment-hash.")
1522
+ );
1121
1523
  }
1122
1524
  return;
1123
1525
  }
1124
- console.log(pc9.dim("Pay this invoice with your Lightning wallet, then run:"));
1526
+ console.log(pc11.dim("Pay this invoice with your Lightning wallet, then run:"));
1125
1527
  console.log(
1126
- pc9.cyan(
1528
+ pc11.cyan(
1127
1529
  ` btcemail send --to "${options.to}" --subject "${options.subject}" --body "${options.body}" --payment-hash ${invoice.paymentHash}`
1128
1530
  )
1129
1531
  );
1130
1532
  console.log();
1131
- console.log(pc9.dim("Or use --wait to automatically wait for payment."));
1533
+ console.log(pc11.dim("Or use --wait to automatically wait for payment."));
1132
1534
  console.log();
1133
1535
  return;
1134
1536
  }
@@ -1144,7 +1546,7 @@ async function sendCommand(options) {
1144
1546
  if (!result.ok) {
1145
1547
  if (result.error.code === "PAYMENT_REQUIRED") {
1146
1548
  spinner.fail("Payment required to send this email.");
1147
- console.log(pc9.dim("Use the invoice above to pay, then include --payment-hash"));
1549
+ console.log(pc11.dim("Use the invoice above to pay, then include --payment-hash"));
1148
1550
  } else {
1149
1551
  spinner.fail(`Error: ${result.error.message}`);
1150
1552
  }
@@ -1152,234 +1554,305 @@ async function sendCommand(options) {
1152
1554
  }
1153
1555
  spinner.succeed("Email sent!");
1154
1556
  console.log();
1155
- console.log(`${pc9.dim("Email ID:")} ${result.data.emailId}`);
1557
+ console.log(`${pc11.dim("Email ID:")} ${result.data.emailId}`);
1156
1558
  if (result.data.messageId) {
1157
- console.log(`${pc9.dim("Message ID:")} ${result.data.messageId}`);
1559
+ console.log(`${pc11.dim("Message ID:")} ${result.data.messageId}`);
1158
1560
  }
1159
1561
  console.log();
1160
1562
  }
1161
1563
 
1162
- // src/commands/inbound.ts
1163
- import pc10 from "picocolors";
1164
- import ora8 from "ora";
1165
- async function inboundDeliveredCommand(options) {
1166
- if (!isAuthenticated()) {
1167
- console.log(pc10.yellow("Not logged in."));
1168
- console.log(pc10.dim("Run `btcemail login` to authenticate."));
1564
+ // src/commands/settings.ts
1565
+ import pc12 from "picocolors";
1566
+ async function settingsShowCommand(options = {}) {
1567
+ const token = getToken();
1568
+ if (!token) {
1569
+ console.error(pc12.red("Not logged in. Run 'btcemail login' first."));
1169
1570
  process.exit(1);
1170
1571
  }
1171
- const spinner = ora8("Loading delivered emails...").start();
1172
- const result = await getDeliveredEmails({
1173
- limit: options.limit || 20
1174
- });
1572
+ const result = await getSettings(options.username);
1175
1573
  if (!result.ok) {
1176
- spinner.fail(`Error: ${result.error.message}`);
1574
+ console.error(pc12.red(`Error: ${result.error.message}`));
1177
1575
  process.exit(1);
1178
1576
  }
1179
- const { data: emails, pagination } = result.data;
1180
- spinner.stop();
1577
+ const { username, usernames, settings: settings2 } = result.data;
1181
1578
  if (options.json) {
1182
- console.log(JSON.stringify({ emails, pagination }, null, 2));
1579
+ console.log(JSON.stringify({ username, usernames, settings: settings2 }, null, 2));
1183
1580
  return;
1184
1581
  }
1185
1582
  console.log();
1186
- console.log(pc10.bold(`Delivered (${pagination.total} paid emails received)`));
1187
- console.log();
1188
- if (emails.length === 0) {
1189
- console.log(pc10.dim(" No delivered emails yet."));
1190
- console.log();
1191
- return;
1583
+ console.log(pc12.bold("Settings for ") + pc12.cyan(`${username}@btc.email`));
1584
+ if (usernames.length > 1) {
1585
+ console.log(pc12.dim(`(You have ${usernames.length} addresses: ${usernames.join(", ")})`));
1192
1586
  }
1587
+ console.log();
1588
+ console.log(pc12.dim("Pricing:"));
1589
+ console.log(` Email price: ${pc12.yellow(settings2.default_price_sats.toLocaleString())} sats`);
1590
+ console.log();
1591
+ console.log(pc12.dim("Toggles:"));
1193
1592
  console.log(
1194
- ` ${pc10.dim("#".padEnd(4))} ${pc10.dim("From".padEnd(28))} ${pc10.dim("Subject".padEnd(40))} ${pc10.dim("Date")}`
1593
+ ` Require payment: ${settings2.require_payment_from_unknown ? pc12.green("on") : pc12.red("off")}`
1594
+ );
1595
+ console.log(
1596
+ ` Auto-whitelist: ${settings2.auto_whitelist_on_payment ? pc12.green("on") : pc12.red("off")}`
1195
1597
  );
1196
- console.log(pc10.dim(" " + "-".repeat(85)));
1197
- emails.forEach((email, index) => {
1198
- const num = pc10.cyan(String(index + 1).padEnd(4));
1199
- const from = truncate3(email.from.name || email.from.email, 26).padEnd(28);
1200
- const subject = truncate3(email.subject, 38).padEnd(40);
1201
- const date = formatDate4(email.date);
1202
- console.log(` ${num}${from} ${subject} ${pc10.dim(date)}`);
1203
- });
1204
1598
  console.log();
1205
- if (pagination.hasMore) {
1206
- console.log(
1207
- pc10.dim(` Showing ${emails.length} of ${pagination.total}. Use --limit to see more.`)
1208
- );
1209
- console.log();
1599
+ console.log(pc12.dim("Auto-reply:"));
1600
+ if (settings2.custom_auto_reply_body) {
1601
+ console.log(` ${pc12.italic(settings2.custom_auto_reply_body)}`);
1602
+ } else {
1603
+ console.log(` ${pc12.dim("(default message)")}`);
1210
1604
  }
1211
- console.log(pc10.dim(" Read email: btcemail read <id>"));
1212
1605
  console.log();
1213
1606
  }
1214
- async function inboundPendingCommand(options) {
1215
- if (!isAuthenticated()) {
1216
- console.log(pc10.yellow("Not logged in."));
1217
- console.log(pc10.dim("Run `btcemail login` to authenticate."));
1607
+ async function settingsPricingCommand(sats, options = {}) {
1608
+ const token = getToken();
1609
+ if (!token) {
1610
+ console.error(pc12.red("Not logged in. Run 'btcemail login' first."));
1218
1611
  process.exit(1);
1219
1612
  }
1220
- const spinner = ora8("Loading pending emails...").start();
1221
- const result = await getPendingEmails({
1222
- limit: options.limit || 20
1223
- });
1613
+ const price = parseInt(sats, 10);
1614
+ if (Number.isNaN(price) || price < 0) {
1615
+ console.error(pc12.red("Invalid price. Must be a non-negative number."));
1616
+ process.exit(1);
1617
+ }
1618
+ const result = await updateSettings({ default_price_sats: price }, options.username);
1224
1619
  if (!result.ok) {
1225
- spinner.fail(`Error: ${result.error.message}`);
1620
+ console.error(pc12.red(`Error: ${result.error.message}`));
1226
1621
  process.exit(1);
1227
1622
  }
1228
- const { data: emails, pagination } = result.data;
1229
- spinner.stop();
1230
1623
  if (options.json) {
1231
- console.log(JSON.stringify({ emails, pagination }, null, 2));
1624
+ console.log(JSON.stringify({ success: true, price }, null, 2));
1625
+ return;
1626
+ }
1627
+ console.log(pc12.green(`Email price set to ${pc12.yellow(price.toLocaleString())} sats`));
1628
+ }
1629
+ async function settingsRequirePaymentCommand(value, options = {}) {
1630
+ const token = getToken();
1631
+ if (!token) {
1632
+ console.error(pc12.red("Not logged in. Run 'btcemail login' first."));
1633
+ process.exit(1);
1634
+ }
1635
+ const enabled = value.toLowerCase() === "on" || value === "true" || value === "1";
1636
+ const disabled = value.toLowerCase() === "off" || value === "false" || value === "0";
1637
+ if (!enabled && !disabled) {
1638
+ console.error(pc12.red("Invalid value. Use 'on' or 'off'."));
1639
+ process.exit(1);
1640
+ }
1641
+ const result = await updateSettings({ require_payment_from_unknown: enabled }, options.username);
1642
+ if (!result.ok) {
1643
+ console.error(pc12.red(`Error: ${result.error.message}`));
1644
+ process.exit(1);
1645
+ }
1646
+ console.log(pc12.green(`Require payment: ${enabled ? pc12.green("on") : pc12.red("off")}`));
1647
+ }
1648
+ async function settingsAutoWhitelistCommand(value, options = {}) {
1649
+ const token = getToken();
1650
+ if (!token) {
1651
+ console.error(pc12.red("Not logged in. Run 'btcemail login' first."));
1652
+ process.exit(1);
1653
+ }
1654
+ const enabled = value.toLowerCase() === "on" || value === "true" || value === "1";
1655
+ const disabled = value.toLowerCase() === "off" || value === "false" || value === "0";
1656
+ if (!enabled && !disabled) {
1657
+ console.error(pc12.red("Invalid value. Use 'on' or 'off'."));
1658
+ process.exit(1);
1659
+ }
1660
+ const result = await updateSettings({ auto_whitelist_on_payment: enabled }, options.username);
1661
+ if (!result.ok) {
1662
+ console.error(pc12.red(`Error: ${result.error.message}`));
1663
+ process.exit(1);
1664
+ }
1665
+ console.log(pc12.green(`Auto-whitelist: ${enabled ? pc12.green("on") : pc12.red("off")}`));
1666
+ }
1667
+ async function settingsAutoReplyCommand(message, options = {}) {
1668
+ const token = getToken();
1669
+ if (!token) {
1670
+ console.error(pc12.red("Not logged in. Run 'btcemail login' first."));
1671
+ process.exit(1);
1672
+ }
1673
+ const newMessage = options.clear ? null : message || null;
1674
+ const result = await updateSettings({ custom_auto_reply_body: newMessage }, options.username);
1675
+ if (!result.ok) {
1676
+ console.error(pc12.red(`Error: ${result.error.message}`));
1677
+ process.exit(1);
1678
+ }
1679
+ if (options.clear) {
1680
+ console.log(pc12.green("Auto-reply reset to default message"));
1681
+ } else if (newMessage) {
1682
+ console.log(pc12.green("Auto-reply message updated"));
1683
+ } else {
1684
+ console.log(pc12.yellow("No message provided. Use --clear to reset to default."));
1685
+ }
1686
+ }
1687
+
1688
+ // src/commands/stats.ts
1689
+ import pc13 from "picocolors";
1690
+ async function statsCommand(options = {}) {
1691
+ const token = getToken();
1692
+ if (!token) {
1693
+ console.error(pc13.red("Not logged in. Run 'btcemail login' first."));
1694
+ process.exit(1);
1695
+ }
1696
+ const result = await getStats();
1697
+ if (!result.ok) {
1698
+ console.error(pc13.red(`Error: ${result.error.message}`));
1699
+ process.exit(1);
1700
+ }
1701
+ const { totalReceived, totalSent, pendingCount, addressCount, usernames } = result.data;
1702
+ if (options.json) {
1703
+ console.log(JSON.stringify(result.data, null, 2));
1232
1704
  return;
1233
1705
  }
1234
1706
  console.log();
1235
- console.log(pc10.bold(`Pending (${pagination.total} awaiting payment)`));
1707
+ console.log(pc13.bold("Dashboard Statistics"));
1236
1708
  console.log();
1237
- if (emails.length === 0) {
1238
- console.log(pc10.dim(" No pending emails."));
1709
+ console.log(` Total Received: ${pc13.green(`+${totalReceived.toLocaleString()}`)} sats`);
1710
+ console.log(` Total Sent: ${pc13.yellow(`-${totalSent.toLocaleString()}`)} sats`);
1711
+ console.log(` Pending Emails: ${pendingCount > 0 ? pc13.yellow(pendingCount) : pc13.dim("0")}`);
1712
+ console.log(` Active Addresses: ${addressCount}`);
1713
+ console.log();
1714
+ if (usernames.length > 0) {
1715
+ console.log(pc13.dim(" Your addresses:"));
1716
+ for (const username of usernames) {
1717
+ console.log(` ${pc13.cyan(username)}@btc.email`);
1718
+ }
1239
1719
  console.log();
1240
- return;
1241
1720
  }
1242
- console.log(
1243
- ` ${pc10.dim("#".padEnd(4))} ${pc10.dim("From".padEnd(28))} ${pc10.dim("Subject".padEnd(35))} ${pc10.dim("Sats")}`
1244
- );
1245
- console.log(pc10.dim(" " + "-".repeat(80)));
1246
- emails.forEach((email, index) => {
1247
- const num = pc10.cyan(String(index + 1).padEnd(4));
1248
- const from = truncate3(email.from.name || email.from.email, 26).padEnd(28);
1249
- const subject = truncate3(email.subject, 33).padEnd(35);
1250
- const sats = pc10.yellow(String(email.amountSats));
1251
- console.log(` ${num}${from} ${subject} ${sats}`);
1252
- });
1253
- console.log();
1254
- if (pagination.hasMore) {
1255
- console.log(
1256
- pc10.dim(` Showing ${emails.length} of ${pagination.total}. Use --limit to see more.`)
1257
- );
1721
+ if (pendingCount > 0) {
1722
+ console.log(pc13.dim(" View pending emails: btcemail inbound pending"));
1258
1723
  console.log();
1259
1724
  }
1260
- console.log(pc10.dim(" View details: btcemail inbound view <id>"));
1261
- console.log();
1262
1725
  }
1263
- async function inboundViewCommand(id, options) {
1264
- if (!isAuthenticated()) {
1265
- console.log(pc10.yellow("Not logged in."));
1266
- console.log(pc10.dim("Run `btcemail login` to authenticate."));
1726
+
1727
+ // src/commands/whitelist.ts
1728
+ import pc14 from "picocolors";
1729
+ async function whitelistListCommand(options = {}) {
1730
+ const token = getToken();
1731
+ if (!token) {
1732
+ console.error(pc14.red("Not logged in. Run 'btcemail login' first."));
1267
1733
  process.exit(1);
1268
1734
  }
1269
- const spinner = ora8("Loading email details...").start();
1270
- const result = await getPendingEmail(id);
1735
+ const result = await getWhitelist(options.username);
1271
1736
  if (!result.ok) {
1272
- if (result.error.code === "NOT_FOUND") {
1273
- spinner.fail(`Pending email not found: ${id}`);
1274
- } else {
1275
- spinner.fail(`Error: ${result.error.message}`);
1276
- }
1737
+ console.error(pc14.red(`Error: ${result.error.message}`));
1277
1738
  process.exit(1);
1278
1739
  }
1279
- spinner.stop();
1280
- const email = result.data;
1740
+ const { username, entries } = result.data;
1281
1741
  if (options.json) {
1282
- console.log(JSON.stringify(email, null, 2));
1742
+ console.log(JSON.stringify({ username, entries }, null, 2));
1283
1743
  return;
1284
1744
  }
1285
1745
  console.log();
1286
- console.log(pc10.bold(email.subject || "(No subject)"));
1746
+ console.log(pc14.bold("Whitelist for ") + pc14.cyan(`${username}@btc.email`));
1287
1747
  console.log();
1288
- console.log(`${pc10.dim("From:")} ${formatAddress2(email.from)}`);
1289
- console.log(`${pc10.dim("Date:")} ${formatFullDate2(email.createdAt)}`);
1290
- console.log(`${pc10.dim("ID:")} ${email.id}`);
1291
- console.log();
1292
- console.log(pc10.yellow(`Payment Required: ${email.amountSats} sats`));
1293
- console.log();
1294
- if (email.paymentHash) {
1295
- console.log(pc10.dim("To receive this email, pay the invoice and run:"));
1296
- console.log(pc10.cyan(` btcemail inbound pay ${email.id} --payment-hash <preimage>`));
1297
- } else {
1298
- console.log(pc10.dim("Payment information not available."));
1748
+ if (entries.length === 0) {
1749
+ console.log(pc14.dim(" No entries in whitelist."));
1750
+ console.log();
1751
+ console.log(pc14.dim(" Add entries with: btcemail whitelist add <email>"));
1752
+ console.log(pc14.dim(" Or for domains: btcemail whitelist add @domain.com"));
1753
+ console.log();
1754
+ return;
1299
1755
  }
1756
+ console.log(pc14.dim(` ${entries.length} ${entries.length === 1 ? "entry" : "entries"}:`));
1300
1757
  console.log();
1301
- console.log(pc10.dim("-".repeat(60)));
1302
- console.log();
1303
- console.log(pc10.dim("Preview (full content available after payment):"));
1304
- console.log();
1305
- console.log(truncate3(email.bodyText || email.body, 200));
1758
+ for (const entry of entries) {
1759
+ const value = entry.sender_email || `@${entry.sender_domain}`;
1760
+ const via = entry.added_via !== "manual" ? pc14.dim(` (via ${entry.added_via})`) : "";
1761
+ const note = entry.note ? pc14.dim(` - ${entry.note}`) : "";
1762
+ console.log(` ${pc14.green(value)}${via}${note}`);
1763
+ console.log(pc14.dim(` ID: ${entry.id}`));
1764
+ }
1306
1765
  console.log();
1307
1766
  }
1308
- async function inboundPayCommand(id, options) {
1309
- if (!isAuthenticated()) {
1310
- console.log(pc10.yellow("Not logged in."));
1311
- console.log(pc10.dim("Run `btcemail login` to authenticate."));
1767
+ async function whitelistAddCommand(entry, options = {}) {
1768
+ const token = getToken();
1769
+ if (!token) {
1770
+ console.error(pc14.red("Not logged in. Run 'btcemail login' first."));
1312
1771
  process.exit(1);
1313
1772
  }
1314
- if (!options.paymentHash) {
1315
- console.error(pc10.red("Payment hash is required. Use --payment-hash <preimage>"));
1773
+ const isDomain = entry.startsWith("@");
1774
+ const payload = isDomain ? { domain: entry.slice(1), note: options.note } : { email: entry, note: options.note };
1775
+ const result = await addToWhitelist(payload, options.username);
1776
+ if (!result.ok) {
1777
+ console.error(pc14.red(`Error: ${result.error.message}`));
1316
1778
  process.exit(1);
1317
1779
  }
1318
- const spinner = ora8("Verifying payment...").start();
1319
- const result = await payForEmail(id, options.paymentHash);
1780
+ if (options.json) {
1781
+ console.log(JSON.stringify({ success: true, entry: result.data.entry }, null, 2));
1782
+ return;
1783
+ }
1784
+ console.log(pc14.green(`Added to whitelist: ${pc14.cyan(entry)}`));
1785
+ }
1786
+ async function whitelistRemoveCommand(id, options = {}) {
1787
+ const token = getToken();
1788
+ if (!token) {
1789
+ console.error(pc14.red("Not logged in. Run 'btcemail login' first."));
1790
+ process.exit(1);
1791
+ }
1792
+ const result = await removeFromWhitelist(id);
1320
1793
  if (!result.ok) {
1321
- if (result.error.code === "PAYMENT_INVALID") {
1322
- spinner.fail("Payment verification failed.");
1323
- console.log(pc10.dim("Make sure you paid the correct invoice and provided the preimage."));
1324
- } else if (result.error.code === "NOT_FOUND") {
1325
- spinner.fail(`Pending email not found or already delivered: ${id}`);
1326
- } else {
1327
- spinner.fail(`Error: ${result.error.message}`);
1328
- }
1794
+ console.error(pc14.red(`Error: ${result.error.message}`));
1329
1795
  process.exit(1);
1330
1796
  }
1331
- spinner.succeed("Payment verified! Email delivered.");
1332
- console.log();
1333
- console.log(`${pc10.dim("Email ID:")} ${result.data.emailId}`);
1334
- console.log(`${pc10.dim("Status:")} ${result.data.status}`);
1335
- console.log();
1336
- console.log(pc10.dim(`Read email: btcemail read ${result.data.emailId}`));
1337
- console.log();
1338
- }
1339
- function truncate3(str, maxLength) {
1340
- if (str.length <= maxLength) return str;
1341
- return str.slice(0, maxLength - 1) + "\u2026";
1342
- }
1343
- function formatAddress2(addr) {
1344
- if (addr.name) {
1345
- return `${addr.name} <${addr.email}>`;
1797
+ if (options.json) {
1798
+ console.log(JSON.stringify({ success: true }, null, 2));
1799
+ return;
1346
1800
  }
1347
- return addr.email;
1348
- }
1349
- function formatFullDate2(dateString) {
1350
- const date = new Date(dateString);
1351
- return date.toLocaleString("en-US", {
1352
- weekday: "short",
1353
- year: "numeric",
1354
- month: "short",
1355
- day: "numeric",
1356
- hour: "numeric",
1357
- minute: "2-digit"
1358
- });
1801
+ console.log(pc14.green("Removed from whitelist"));
1359
1802
  }
1360
- function formatDate4(dateString) {
1361
- const date = new Date(dateString);
1362
- const now = /* @__PURE__ */ new Date();
1363
- const diffMs = now.getTime() - date.getTime();
1364
- const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
1365
- if (diffDays === 0) {
1366
- return date.toLocaleTimeString("en-US", {
1367
- hour: "numeric",
1368
- minute: "2-digit"
1369
- });
1803
+
1804
+ // src/commands/whoami.ts
1805
+ import ora8 from "ora";
1806
+ import pc15 from "picocolors";
1807
+ async function whoamiCommand() {
1808
+ if (!isAuthenticated()) {
1809
+ console.log(pc15.yellow("Not logged in."));
1810
+ console.log(pc15.dim("Run `btcemail login` to authenticate."));
1811
+ process.exit(1);
1370
1812
  }
1371
- if (diffDays < 7) {
1372
- return date.toLocaleDateString("en-US", { weekday: "short" });
1813
+ const auth = getAuth();
1814
+ if (!auth) {
1815
+ console.log(pc15.yellow("Not logged in."));
1816
+ process.exit(1);
1817
+ }
1818
+ const spinner = ora8("Fetching account info...").start();
1819
+ const result = await getWhoami();
1820
+ spinner.stop();
1821
+ if (result.ok) {
1822
+ console.log();
1823
+ console.log(pc15.bold("Current User"));
1824
+ console.log();
1825
+ console.log(` ${pc15.dim("Email:")} ${result.data.email}`);
1826
+ if (result.data.username) {
1827
+ console.log(` ${pc15.dim("Username:")} ${result.data.username}@btc.email`);
1828
+ }
1829
+ console.log(` ${pc15.dim("User ID:")} ${result.data.id}`);
1830
+ const expiryInfo = getTokenExpiryInfo();
1831
+ if (expiryInfo.expiresIn) {
1832
+ const expiryColor = isTokenExpiringSoon() ? pc15.yellow : pc15.dim;
1833
+ console.log(` ${pc15.dim("Session:")} ${expiryColor(`expires in ${expiryInfo.expiresIn}`)}`);
1834
+ }
1835
+ console.log();
1836
+ if (isTokenExpiringSoon()) {
1837
+ console.log(pc15.yellow("Session expiring soon. Run `btcemail login` to refresh."));
1838
+ console.log();
1839
+ }
1840
+ } else {
1841
+ console.log();
1842
+ console.log(pc15.bold("Current User") + pc15.dim(" (cached)"));
1843
+ console.log();
1844
+ console.log(` ${pc15.dim("Email:")} ${auth.email}`);
1845
+ console.log(` ${pc15.dim("User ID:")} ${auth.userId}`);
1846
+ console.log();
1847
+ if (result.error.code === "UNAUTHENTICATED" || result.error.code === "UNAUTHORIZED") {
1848
+ console.log(pc15.yellow("Session expired. Run `btcemail login` to re-authenticate."));
1849
+ }
1373
1850
  }
1374
- return date.toLocaleDateString("en-US", {
1375
- month: "short",
1376
- day: "numeric"
1377
- });
1378
1851
  }
1379
1852
 
1380
1853
  // src/index.ts
1381
1854
  var program = new Command();
1382
- program.name("btcemail").description("btc.email CLI - Spam-free email powered by Bitcoin Lightning").version("0.3.0");
1855
+ program.name("btcemail").description("btc.email CLI - Spam-free email powered by Bitcoin Lightning").version("0.4.0");
1383
1856
  program.command("login").description("Authenticate with btc.email (opens browser)").action(loginCommand);
1384
1857
  program.command("logout").description("Clear stored credentials").action(logoutCommand);
1385
1858
  program.command("whoami").description("Show current authenticated user").action(whoamiCommand);
@@ -1410,7 +1883,13 @@ program.command("send").description("Send an email (with Lightning payment)").op
1410
1883
  wait: options.wait
1411
1884
  });
1412
1885
  });
1413
- var inbound = program.command("inbound").description("Manage inbound emails (pending and delivered)");
1886
+ program.command("bin").description("List pending inbound emails (alias for 'inbound pending')").option("-l, --limit <number>", "Number of emails to show", "20").option("--json", "Output as JSON").action(async (options) => {
1887
+ await inboundPendingCommand({
1888
+ limit: parseInt(options.limit, 10),
1889
+ json: options.json
1890
+ });
1891
+ });
1892
+ var inbound = program.command("inbound").alias("in").description("Manage inbound emails (pending and delivered)");
1414
1893
  inbound.command("pending").alias("p").description("List pending emails awaiting payment").option("-l, --limit <number>", "Number of emails to show", "20").option("--json", "Output as JSON").action(async (options) => {
1415
1894
  await inboundPendingCommand({
1416
1895
  limit: parseInt(options.limit, 10),
@@ -1429,6 +1908,9 @@ inbound.command("view <id>").description("View pending email details and payment
1429
1908
  inbound.command("pay <id>").description("Pay for a pending email").requiredOption("--payment-hash <hash>", "Payment preimage from Lightning invoice").action(async (id, options) => {
1430
1909
  await inboundPayCommand(id, { paymentHash: options.paymentHash });
1431
1910
  });
1911
+ inbound.command("accept <id>").alias("a").description("Accept email without payment and whitelist sender").option("--no-whitelist", "Don't add sender to whitelist").action(async (id, options) => {
1912
+ await inboundAcceptCommand(id, { noWhitelist: options.whitelist === false });
1913
+ });
1432
1914
  var credits = program.command("credits").description("Manage credits");
1433
1915
  credits.command("balance").alias("bal").description("Show credit balance").action(creditsBalanceCommand);
1434
1916
  credits.command("history").description("Show transaction history").option("-l, --limit <number>", "Number of transactions to show", "20").option("-t, --type <type>", "Filter by type (topup, email_sent, email_received, refund, bonus)").option("--json", "Output as JSON").action(async (options) => {
@@ -1445,9 +1927,78 @@ credits.command("purchase").alias("buy").description("Purchase credits with Ligh
1445
1927
  wait: options.wait
1446
1928
  });
1447
1929
  });
1930
+ var settings = program.command("settings").description("Manage email pricing and preferences");
1931
+ settings.command("show").description("Show current settings").option("-u, --username <name>", "Specific @btc.email address").option("--json", "Output as JSON").action(async (options) => {
1932
+ await settingsShowCommand({ username: options.username, json: options.json });
1933
+ });
1934
+ settings.command("pricing <sats>").description("Set email price in sats").option("-u, --username <name>", "Specific @btc.email address").option("--json", "Output as JSON").action(async (sats, options) => {
1935
+ await settingsPricingCommand(sats, { username: options.username, json: options.json });
1936
+ });
1937
+ settings.command("require-payment <on|off>").description("Toggle payment requirement for unknown senders").option("-u, --username <name>", "Specific @btc.email address").action(async (value, options) => {
1938
+ await settingsRequirePaymentCommand(value, { username: options.username });
1939
+ });
1940
+ settings.command("auto-whitelist <on|off>").description("Toggle auto-whitelist after payment").option("-u, --username <name>", "Specific @btc.email address").action(async (value, options) => {
1941
+ await settingsAutoWhitelistCommand(value, { username: options.username });
1942
+ });
1943
+ settings.command("auto-reply [message]").description("Set custom auto-reply message").option("-u, --username <name>", "Specific @btc.email address").option("--clear", "Reset to default message").action(async (message, options) => {
1944
+ await settingsAutoReplyCommand(message, { username: options.username, clear: options.clear });
1945
+ });
1946
+ settings.action(async () => {
1947
+ await settingsShowCommand({});
1948
+ });
1949
+ var whitelist = program.command("whitelist").alias("wl").description("Manage senders who can email you for free");
1950
+ whitelist.command("list").description("List whitelist entries").option("-u, --username <name>", "Specific @btc.email address").option("--json", "Output as JSON").action(async (options) => {
1951
+ await whitelistListCommand({ username: options.username, json: options.json });
1952
+ });
1953
+ whitelist.command("add <entry>").description("Add email or @domain to whitelist").option("-u, --username <name>", "Specific @btc.email address").option("-n, --note <note>", "Optional note").option("--json", "Output as JSON").action(async (entry, options) => {
1954
+ await whitelistAddCommand(entry, {
1955
+ username: options.username,
1956
+ note: options.note,
1957
+ json: options.json
1958
+ });
1959
+ });
1960
+ whitelist.command("remove <id>").description("Remove entry from whitelist by ID").option("--json", "Output as JSON").action(async (id, options) => {
1961
+ await whitelistRemoveCommand(id, { json: options.json });
1962
+ });
1963
+ whitelist.action(async () => {
1964
+ await whitelistListCommand({});
1965
+ });
1966
+ var blocklist = program.command("blocklist").alias("bl").description("Manage blocked senders");
1967
+ blocklist.command("list").description("List blocklist entries").option("-u, --username <name>", "Specific @btc.email address").option("--json", "Output as JSON").action(async (options) => {
1968
+ await blocklistListCommand({ username: options.username, json: options.json });
1969
+ });
1970
+ blocklist.command("add <entry>").description("Add email or @domain to blocklist").option("-u, --username <name>", "Specific @btc.email address").option("-r, --reason <reason>", "Optional reason").option("--json", "Output as JSON").action(async (entry, options) => {
1971
+ await blocklistAddCommand(entry, {
1972
+ username: options.username,
1973
+ reason: options.reason,
1974
+ json: options.json
1975
+ });
1976
+ });
1977
+ blocklist.command("remove <id>").description("Remove entry from blocklist by ID").option("--json", "Output as JSON").action(async (id, options) => {
1978
+ await blocklistRemoveCommand(id, { json: options.json });
1979
+ });
1980
+ blocklist.action(async () => {
1981
+ await blocklistListCommand({});
1982
+ });
1983
+ var network = program.command("network").description("Manage Lightning network mode");
1984
+ network.command("show").description("Show current network mode").option("--json", "Output as JSON").action(async (options) => {
1985
+ await networkShowCommand({ json: options.json });
1986
+ });
1987
+ network.command("testnet").description("Switch to testnet (fake Bitcoin)").option("--json", "Output as JSON").action(async (options) => {
1988
+ await networkSwitchCommand("testnet", { json: options.json });
1989
+ });
1990
+ network.command("mainnet").description("Switch to mainnet (real Bitcoin)").option("--json", "Output as JSON").action(async (options) => {
1991
+ await networkSwitchCommand("mainnet", { json: options.json });
1992
+ });
1993
+ network.action(async () => {
1994
+ await networkShowCommand({});
1995
+ });
1996
+ program.command("stats").description("Show dashboard statistics").option("--json", "Output as JSON").action(async (options) => {
1997
+ await statsCommand({ json: options.json });
1998
+ });
1448
1999
  program.action(() => {
1449
2000
  console.log();
1450
- console.log(pc11.bold("btc.email CLI") + pc11.dim(" - Spam-free email powered by Bitcoin Lightning"));
2001
+ console.log(pc16.bold("btc.email CLI") + pc16.dim(" - Spam-free email powered by Bitcoin Lightning"));
1451
2002
  console.log();
1452
2003
  program.outputHelp();
1453
2004
  });