@btcemail/cli 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
- import pc9 from "picocolors";
5
+ import pc11 from "picocolors";
6
6
 
7
7
  // src/commands/login.ts
8
8
  import { randomUUID } from "crypto";
@@ -200,6 +200,7 @@ async function logoutCommand() {
200
200
 
201
201
  // src/commands/whoami.ts
202
202
  import pc3 from "picocolors";
203
+ import ora2 from "ora";
203
204
 
204
205
  // src/api.ts
205
206
  async function apiRequest(endpoint, options = {}) {
@@ -324,6 +325,100 @@ async function getL402Invoice(options) {
324
325
  };
325
326
  }
326
327
  }
328
+ async function getDeliveredEmails(options = {}) {
329
+ const params = new URLSearchParams();
330
+ if (options.limit) params.set("limit", String(options.limit));
331
+ if (options.offset) params.set("offset", String(options.offset));
332
+ return apiRequest(`/inbound/delivered?${params}`);
333
+ }
334
+ async function getCreditTransactions(options = {}) {
335
+ const params = new URLSearchParams();
336
+ if (options.limit) params.set("limit", String(options.limit));
337
+ if (options.offset) params.set("offset", String(options.offset));
338
+ if (options.type) params.set("type", options.type);
339
+ return apiRequest(`/credits/transactions?${params}`);
340
+ }
341
+ async function purchaseCredits(optionIndex) {
342
+ return apiRequest("/credits/purchase", {
343
+ method: "POST",
344
+ body: JSON.stringify({ optionIndex })
345
+ });
346
+ }
347
+ async function checkPaymentStatus(paymentHash) {
348
+ const v1BaseUrl = getApiBaseUrl();
349
+ const rootApiUrl = v1BaseUrl.replace("/api/v1", "/api");
350
+ const token = getToken();
351
+ const headers = {
352
+ "Content-Type": "application/json"
353
+ };
354
+ if (token) {
355
+ headers.Authorization = `Bearer ${token}`;
356
+ }
357
+ try {
358
+ const response = await fetch(`${rootApiUrl}/lightning/webhook?payment_hash=${paymentHash}`, {
359
+ headers
360
+ });
361
+ const json = await response.json();
362
+ if (!response.ok || json.success === false) {
363
+ return {
364
+ ok: false,
365
+ error: json.error || {
366
+ code: "UNKNOWN_ERROR",
367
+ message: `Request failed with status ${response.status}`
368
+ }
369
+ };
370
+ }
371
+ return {
372
+ ok: true,
373
+ data: {
374
+ paid: json.paid || false,
375
+ delivered: json.delivered,
376
+ status: json.status || json.state,
377
+ pendingEmailId: json.pendingEmailId
378
+ }
379
+ };
380
+ } catch (error) {
381
+ return {
382
+ ok: false,
383
+ error: {
384
+ code: "NETWORK_ERROR",
385
+ message: error instanceof Error ? error.message : "Network request failed"
386
+ }
387
+ };
388
+ }
389
+ }
390
+ function getCreditPurchaseOptions() {
391
+ return [
392
+ {
393
+ amountSats: 1e3,
394
+ priceSats: 1e3,
395
+ bonusSats: 0,
396
+ label: "1,000 sats",
397
+ description: "Good for ~10 emails"
398
+ },
399
+ {
400
+ amountSats: 5e3,
401
+ priceSats: 4500,
402
+ bonusSats: 500,
403
+ label: "5,000 sats",
404
+ description: "Good for ~50 emails (+10% bonus)"
405
+ },
406
+ {
407
+ amountSats: 1e4,
408
+ priceSats: 8500,
409
+ bonusSats: 1500,
410
+ label: "10,000 sats",
411
+ description: "Good for ~100 emails (+15% bonus)"
412
+ },
413
+ {
414
+ amountSats: 5e4,
415
+ priceSats: 4e4,
416
+ bonusSats: 1e4,
417
+ label: "50,000 sats",
418
+ description: "Good for ~500 emails (+20% bonus)"
419
+ }
420
+ ];
421
+ }
327
422
 
328
423
  // src/commands/whoami.ts
329
424
  async function whoamiCommand() {
@@ -337,14 +432,16 @@ async function whoamiCommand() {
337
432
  console.log(pc3.yellow("Not logged in."));
338
433
  process.exit(1);
339
434
  }
435
+ const spinner = ora2("Fetching account info...").start();
340
436
  const result = await getWhoami();
437
+ spinner.stop();
341
438
  if (result.ok) {
342
439
  console.log();
343
440
  console.log(pc3.bold("Current User"));
344
441
  console.log();
345
442
  console.log(` ${pc3.dim("Email:")} ${result.data.email}`);
346
443
  if (result.data.username) {
347
- console.log(` ${pc3.dim("Username:")} ${result.data.username}`);
444
+ console.log(` ${pc3.dim("Username:")} ${result.data.username}@btc.email`);
348
445
  }
349
446
  console.log(` ${pc3.dim("User ID:")} ${result.data.id}`);
350
447
  const expiryInfo = getTokenExpiryInfo();
@@ -359,7 +456,7 @@ async function whoamiCommand() {
359
456
  }
360
457
  } else {
361
458
  console.log();
362
- console.log(pc3.bold("Current User (cached)"));
459
+ console.log(pc3.bold("Current User") + pc3.dim(" (cached)"));
363
460
  console.log();
364
461
  console.log(` ${pc3.dim("Email:")} ${auth.email}`);
365
462
  console.log(` ${pc3.dim("User ID:")} ${auth.userId}`);
@@ -372,29 +469,78 @@ async function whoamiCommand() {
372
469
 
373
470
  // src/commands/list.ts
374
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
+ });
483
+ }
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;
490
+ }
491
+ if (folder && cache.folder !== folder) {
492
+ return null;
493
+ }
494
+ return cache;
495
+ }
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;
502
+ }
503
+ return cache.emails[idx].id;
504
+ }
505
+ function isNumericId(id) {
506
+ return /^\d+$/.test(id);
507
+ }
508
+
509
+ // src/commands/list.ts
375
510
  async function listCommand(options) {
376
511
  if (!isAuthenticated()) {
377
512
  console.log(pc4.yellow("Not logged in."));
378
513
  console.log(pc4.dim("Run `btcemail login` to authenticate."));
379
514
  process.exit(1);
380
515
  }
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();
381
521
  const result = await getEmails({
382
- folder: options.folder || "inbox",
383
- limit: options.limit || 20
522
+ folder,
523
+ limit,
524
+ offset
384
525
  });
385
526
  if (!result.ok) {
386
- console.error(pc4.red(`Error: ${result.error.message}`));
527
+ spinner.fail(`Error: ${result.error.message}`);
387
528
  process.exit(1);
388
529
  }
389
530
  const { data: emails, pagination } = result.data;
531
+ 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);
390
538
  if (options.json) {
391
539
  console.log(JSON.stringify({ emails, pagination }, null, 2));
392
540
  return;
393
541
  }
394
542
  console.log();
395
- console.log(
396
- pc4.bold(`${options.folder || "Inbox"} (${pagination.total} emails)`)
397
- );
543
+ console.log(pc4.bold(`${capitalize(folder)} (${pagination.total} emails)`));
398
544
  console.log();
399
545
  if (emails.length === 0) {
400
546
  console.log(pc4.dim(" No emails found."));
@@ -402,26 +548,34 @@ async function listCommand(options) {
402
548
  return;
403
549
  }
404
550
  console.log(
405
- ` ${pc4.dim("ID".padEnd(12))} ${pc4.dim("From".padEnd(25))} ${pc4.dim("Subject".padEnd(40))} ${pc4.dim("Date")}`
551
+ ` ${pc4.dim("#".padEnd(4))} ${pc4.dim("From".padEnd(25))} ${pc4.dim("Subject".padEnd(45))} ${pc4.dim("Date")}`
406
552
  );
407
- console.log(pc4.dim(" " + "-".repeat(90)));
408
- for (const email of emails) {
409
- const unreadMarker = email.unread ? pc4.cyan("*") : " ";
410
- const id = email.id.slice(0, 10).padEnd(12);
553
+ console.log(pc4.dim(" " + "-".repeat(85)));
554
+ emails.forEach((email, index) => {
555
+ const num = pc4.cyan(String(index + 1).padEnd(4));
556
+ const unreadMarker = email.unread ? pc4.cyan("\u25CF") : " ";
411
557
  const from = truncate(email.from.name || email.from.email, 23).padEnd(25);
412
- const subject = truncate(email.subject, 38).padEnd(40);
558
+ const subject = truncate(email.subject, 43).padEnd(45);
413
559
  const date = formatDate(email.date);
414
- console.log(`${unreadMarker} ${id} ${from} ${subject} ${pc4.dim(date)}`);
415
- }
560
+ console.log(`${unreadMarker} ${num}${from} ${subject} ${pc4.dim(date)}`);
561
+ });
416
562
  console.log();
417
- if (pagination.hasMore) {
563
+ const totalPages = Math.ceil(pagination.total / limit);
564
+ const currentPage = page;
565
+ if (totalPages > 1) {
418
566
  console.log(
419
- pc4.dim(
420
- ` Showing ${emails.length} of ${pagination.total}. Use --limit to see more.`
421
- )
567
+ pc4.dim(` Page ${currentPage} of ${totalPages} (${emails.length} of ${pagination.total})`)
422
568
  );
569
+ if (currentPage < totalPages) {
570
+ console.log(pc4.dim(` Next page: btcemail list --page ${currentPage + 1}`));
571
+ }
423
572
  console.log();
424
573
  }
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);
425
579
  }
426
580
  function truncate(str, maxLength) {
427
581
  if (str.length <= maxLength) return str;
@@ -449,21 +603,42 @@ function formatDate(dateString) {
449
603
 
450
604
  // src/commands/read.ts
451
605
  import pc5 from "picocolors";
452
- async function readCommand(id, options) {
606
+ import ora4 from "ora";
607
+ async function readCommand(idOrNumber, options) {
453
608
  if (!isAuthenticated()) {
454
609
  console.log(pc5.yellow("Not logged in."));
455
610
  console.log(pc5.dim("Run `btcemail login` to authenticate."));
456
611
  process.exit(1);
457
612
  }
458
- const result = await getEmail(id);
613
+ let emailId = idOrNumber;
614
+ if (isNumericId(idOrNumber)) {
615
+ const index = parseInt(idOrNumber, 10);
616
+ const cachedId = getEmailIdByIndex(index);
617
+ if (cachedId) {
618
+ emailId = cachedId;
619
+ } else {
620
+ const cache = getCachedEmailList();
621
+ 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.`));
624
+ } else {
625
+ console.error(pc5.red(`No email list cached.`));
626
+ console.log(pc5.dim(`Run 'btcemail list' first, then use 'btcemail read <#>'.`));
627
+ }
628
+ process.exit(1);
629
+ }
630
+ }
631
+ const spinner = ora4("Loading email...").start();
632
+ const result = await getEmail(emailId);
459
633
  if (!result.ok) {
460
634
  if (result.error.code === "NOT_FOUND") {
461
- console.error(pc5.red(`Email not found: ${id}`));
635
+ spinner.fail(`Email not found: ${emailId}`);
462
636
  } else {
463
- console.error(pc5.red(`Error: ${result.error.message}`));
637
+ spinner.fail(`Error: ${result.error.message}`);
464
638
  }
465
639
  process.exit(1);
466
640
  }
641
+ spinner.stop();
467
642
  const email = result.data;
468
643
  if (options.json) {
469
644
  console.log(JSON.stringify(email, null, 2));
@@ -512,46 +687,349 @@ function formatFullDate(dateString) {
512
687
  });
513
688
  }
514
689
 
515
- // src/commands/credits.ts
690
+ // src/commands/search.ts
516
691
  import pc6 from "picocolors";
517
- async function creditsBalanceCommand() {
692
+ import ora5 from "ora";
693
+ async function searchCommand(query, options) {
518
694
  if (!isAuthenticated()) {
519
695
  console.log(pc6.yellow("Not logged in."));
520
696
  console.log(pc6.dim("Run `btcemail login` to authenticate."));
521
697
  process.exit(1);
522
698
  }
699
+ if (!query || query.trim().length === 0) {
700
+ console.error(pc6.red("Error: Search query is required"));
701
+ process.exit(1);
702
+ }
703
+ const spinner = ora5(`Searching for "${query}"...`).start();
704
+ const result = await getEmails({
705
+ search: query.trim(),
706
+ limit: options.limit || 20
707
+ });
708
+ if (!result.ok) {
709
+ spinner.fail(`Error: ${result.error.message}`);
710
+ process.exit(1);
711
+ }
712
+ const { data: emails, pagination } = result.data;
713
+ spinner.stop();
714
+ const cachedEmails = emails.map((e) => ({
715
+ id: e.id,
716
+ subject: e.subject,
717
+ from: e.from.name || e.from.email
718
+ }));
719
+ cacheEmailList("search", cachedEmails);
720
+ if (options.json) {
721
+ console.log(JSON.stringify({ query, emails, pagination }, null, 2));
722
+ return;
723
+ }
724
+ console.log();
725
+ console.log(pc6.bold(`Search: "${query}" (${pagination.total} found)`));
726
+ console.log();
727
+ if (emails.length === 0) {
728
+ console.log(pc6.dim(" No emails found matching your search."));
729
+ console.log();
730
+ return;
731
+ }
732
+ console.log(
733
+ ` ${pc6.dim("#".padEnd(4))} ${pc6.dim("From".padEnd(25))} ${pc6.dim("Subject".padEnd(45))} ${pc6.dim("Date")}`
734
+ );
735
+ console.log(pc6.dim(" " + "-".repeat(85)));
736
+ 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)}`);
743
+ });
744
+ console.log();
745
+ if (pagination.hasMore) {
746
+ console.log(
747
+ pc6.dim(
748
+ ` Showing ${emails.length} of ${pagination.total}. Use --limit to see more.`
749
+ )
750
+ );
751
+ console.log();
752
+ }
753
+ console.log(pc6.dim(" Read email: btcemail read <#> or btcemail read <id>"));
754
+ console.log();
755
+ }
756
+ function truncate2(str, maxLength) {
757
+ if (str.length <= maxLength) return str;
758
+ return str.slice(0, maxLength - 1) + "\u2026";
759
+ }
760
+ function formatDate2(dateString) {
761
+ const date = new Date(dateString);
762
+ const now = /* @__PURE__ */ new Date();
763
+ const diffMs = now.getTime() - date.getTime();
764
+ const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
765
+ if (diffDays === 0) {
766
+ return date.toLocaleTimeString("en-US", {
767
+ hour: "numeric",
768
+ minute: "2-digit"
769
+ });
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();
523
828
  const result = await getCreditsBalance();
524
829
  if (!result.ok) {
525
- console.error(pc6.red(`Error: ${result.error.message}`));
830
+ spinner.fail(`Error: ${result.error.message}`);
526
831
  process.exit(1);
527
832
  }
528
- const { balanceSats } = result.data;
833
+ spinner.stop();
834
+ const { balanceSats, lifetimePurchasedSats, lifetimeSpentSats } = result.data;
529
835
  console.log();
530
- console.log(pc6.bold("Credit Balance"));
836
+ console.log(pc8.bold("Credit Balance"));
531
837
  console.log();
532
- console.log(` ${pc6.green(formatSats(balanceSats))} sats`);
838
+ console.log(` ${pc8.green(formatSats(balanceSats))} sats`);
533
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
+ }
534
845
  if (balanceSats === 0) {
535
- console.log(pc6.dim(" No credits available."));
536
- console.log(pc6.dim(" Purchase credits at https://btc.email/payments"));
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."));
537
988
  console.log();
538
989
  }
539
990
  }
540
991
  function formatSats(sats) {
541
992
  return sats.toLocaleString();
542
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);
1012
+ return date.toLocaleDateString("en-US", {
1013
+ month: "short",
1014
+ day: "numeric"
1015
+ });
1016
+ }
543
1017
 
544
1018
  // src/commands/send.ts
545
- import pc7 from "picocolors";
1019
+ import pc9 from "picocolors";
1020
+ import ora7 from "ora";
1021
+ import qrcode2 from "qrcode-terminal";
546
1022
  async function sendCommand(options) {
547
1023
  if (!isAuthenticated()) {
548
- console.log(pc7.yellow("Not logged in."));
549
- console.log(pc7.dim("Run `btcemail login` to authenticate."));
1024
+ console.log(pc9.yellow("Not logged in."));
1025
+ console.log(pc9.dim("Run `btcemail login` to authenticate."));
550
1026
  process.exit(1);
551
1027
  }
552
1028
  let fromEmail = options.fromEmail;
553
1029
  if (!fromEmail) {
1030
+ const spinner2 = ora7("Fetching account info...").start();
554
1031
  const whoamiResult = await getWhoami();
1032
+ spinner2.stop();
555
1033
  if (whoamiResult.ok && whoamiResult.data.username) {
556
1034
  fromEmail = `${whoamiResult.data.username}@btc.email`;
557
1035
  } else {
@@ -562,52 +1040,100 @@ async function sendCommand(options) {
562
1040
  }
563
1041
  }
564
1042
  if (!fromEmail || !fromEmail.endsWith("@btc.email")) {
565
- console.error(pc7.red("No @btc.email address found."));
566
- console.log(pc7.dim("Register a username at https://btc.email"));
1043
+ console.error(pc9.red("No @btc.email address found."));
1044
+ console.log(pc9.dim("Register a username at https://btc.email"));
567
1045
  process.exit(1);
568
1046
  }
569
1047
  if (!options.to) {
570
- console.error(pc7.red("Recipient is required. Use --to <email>"));
1048
+ console.error(pc9.red("Recipient is required. Use --to <email>"));
571
1049
  process.exit(1);
572
1050
  }
573
1051
  if (!options.subject) {
574
- console.error(pc7.red("Subject is required. Use --subject <text>"));
1052
+ console.error(pc9.red("Subject is required. Use --subject <text>"));
575
1053
  process.exit(1);
576
1054
  }
577
1055
  if (!options.body) {
578
- console.error(pc7.red("Body is required. Use --body <text>"));
1056
+ console.error(pc9.red("Body is required. Use --body <text>"));
579
1057
  process.exit(1);
580
1058
  }
581
1059
  const toEmails = options.to.split(",").map((e) => e.trim());
582
1060
  console.log();
583
- console.log(pc7.bold("Sending Email"));
1061
+ console.log(pc9.bold("Sending Email"));
584
1062
  console.log();
585
- console.log(`${pc7.dim("From:")} ${fromEmail}`);
586
- console.log(`${pc7.dim("To:")} ${toEmails.join(", ")}`);
587
- console.log(`${pc7.dim("Subject:")} ${options.subject}`);
1063
+ console.log(`${pc9.dim("From:")} ${fromEmail}`);
1064
+ console.log(`${pc9.dim("To:")} ${toEmails.join(", ")}`);
1065
+ console.log(`${pc9.dim("Subject:")} ${options.subject}`);
588
1066
  console.log();
589
1067
  if (!options.paymentHash) {
1068
+ const spinner2 = ora7("Getting invoice...").start();
590
1069
  const invoiceResult = await getL402Invoice({
591
1070
  fromEmail,
592
1071
  toEmails
593
1072
  });
1073
+ spinner2.stop();
594
1074
  if (invoiceResult.ok && invoiceResult.data.amountSats > 0) {
595
1075
  const invoice = invoiceResult.data;
596
- console.log(pc7.yellow(`Payment required: ${invoice.amountSats} sats`));
1076
+ console.log(pc9.yellow(`Payment required: ${invoice.amountSats} sats`));
1077
+ console.log();
1078
+ console.log(pc9.bold("Scan to pay:"));
1079
+ console.log();
1080
+ qrcode2.generate(invoice.invoice, { small: true }, (qr) => {
1081
+ const indentedQr = qr.split("\n").map((line) => " " + line).join("\n");
1082
+ console.log(indentedQr);
1083
+ });
597
1084
  console.log();
598
- console.log(pc7.dim("Lightning Invoice:"));
599
- console.log(pc7.cyan(invoice.invoice));
1085
+ console.log(pc9.dim("Lightning Invoice:"));
1086
+ console.log(pc9.cyan(invoice.invoice));
600
1087
  console.log();
601
- console.log(pc7.dim("Pay this invoice with your Lightning wallet, then run:"));
1088
+ if (options.wait) {
1089
+ const status = await pollForPayment({
1090
+ paymentHash: invoice.paymentHash,
1091
+ checkFn: async (hash) => {
1092
+ const result2 = await checkPaymentStatus(hash);
1093
+ if (result2.ok) {
1094
+ return { paid: result2.data.paid };
1095
+ }
1096
+ return { paid: false };
1097
+ }
1098
+ });
1099
+ if (status.paid) {
1100
+ const sendSpinner = ora7("Sending email...").start();
1101
+ const result2 = await sendEmail({
1102
+ to: toEmails,
1103
+ subject: options.subject,
1104
+ body: options.body,
1105
+ fromEmail,
1106
+ paymentHash: invoice.paymentHash
1107
+ });
1108
+ if (!result2.ok) {
1109
+ sendSpinner.fail(`Error: ${result2.error.message}`);
1110
+ process.exit(1);
1111
+ }
1112
+ sendSpinner.succeed("Email sent!");
1113
+ console.log();
1114
+ console.log(`${pc9.dim("Email ID:")} ${result2.data.emailId}`);
1115
+ if (result2.data.messageId) {
1116
+ console.log(`${pc9.dim("Message ID:")} ${result2.data.messageId}`);
1117
+ }
1118
+ console.log();
1119
+ } else {
1120
+ console.log(pc9.dim("Payment not confirmed. You can still pay and resend with --payment-hash."));
1121
+ }
1122
+ return;
1123
+ }
1124
+ console.log(pc9.dim("Pay this invoice with your Lightning wallet, then run:"));
602
1125
  console.log(
603
- pc7.cyan(
1126
+ pc9.cyan(
604
1127
  ` btcemail send --to "${options.to}" --subject "${options.subject}" --body "${options.body}" --payment-hash ${invoice.paymentHash}`
605
1128
  )
606
1129
  );
607
1130
  console.log();
1131
+ console.log(pc9.dim("Or use --wait to automatically wait for payment."));
1132
+ console.log();
608
1133
  return;
609
1134
  }
610
1135
  }
1136
+ const spinner = ora7("Sending email...").start();
611
1137
  const result = await sendEmail({
612
1138
  to: toEmails,
613
1139
  subject: options.subject,
@@ -617,148 +1143,200 @@ async function sendCommand(options) {
617
1143
  });
618
1144
  if (!result.ok) {
619
1145
  if (result.error.code === "PAYMENT_REQUIRED") {
620
- console.error(pc7.yellow("Payment required to send this email."));
621
- console.log(pc7.dim("Use the invoice above to pay, then include --payment-hash"));
1146
+ spinner.fail("Payment required to send this email.");
1147
+ console.log(pc9.dim("Use the invoice above to pay, then include --payment-hash"));
622
1148
  } else {
623
- console.error(pc7.red(`Error: ${result.error.message}`));
1149
+ spinner.fail(`Error: ${result.error.message}`);
624
1150
  }
625
1151
  process.exit(1);
626
1152
  }
627
- console.log(pc7.green("Email sent successfully!"));
1153
+ spinner.succeed("Email sent!");
628
1154
  console.log();
629
- console.log(`${pc7.dim("Email ID:")} ${result.data.emailId}`);
1155
+ console.log(`${pc9.dim("Email ID:")} ${result.data.emailId}`);
630
1156
  if (result.data.messageId) {
631
- console.log(`${pc7.dim("Message ID:")} ${result.data.messageId}`);
1157
+ console.log(`${pc9.dim("Message ID:")} ${result.data.messageId}`);
632
1158
  }
633
1159
  console.log();
634
1160
  }
635
1161
 
636
1162
  // src/commands/inbound.ts
637
- import pc8 from "picocolors";
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."));
1169
+ process.exit(1);
1170
+ }
1171
+ const spinner = ora8("Loading delivered emails...").start();
1172
+ const result = await getDeliveredEmails({
1173
+ limit: options.limit || 20
1174
+ });
1175
+ if (!result.ok) {
1176
+ spinner.fail(`Error: ${result.error.message}`);
1177
+ process.exit(1);
1178
+ }
1179
+ const { data: emails, pagination } = result.data;
1180
+ spinner.stop();
1181
+ if (options.json) {
1182
+ console.log(JSON.stringify({ emails, pagination }, null, 2));
1183
+ return;
1184
+ }
1185
+ 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;
1192
+ }
1193
+ console.log(
1194
+ ` ${pc10.dim("#".padEnd(4))} ${pc10.dim("From".padEnd(28))} ${pc10.dim("Subject".padEnd(40))} ${pc10.dim("Date")}`
1195
+ );
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
+ 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();
1210
+ }
1211
+ console.log(pc10.dim(" Read email: btcemail read <id>"));
1212
+ console.log();
1213
+ }
638
1214
  async function inboundPendingCommand(options) {
639
1215
  if (!isAuthenticated()) {
640
- console.log(pc8.yellow("Not logged in."));
641
- console.log(pc8.dim("Run `btcemail login` to authenticate."));
1216
+ console.log(pc10.yellow("Not logged in."));
1217
+ console.log(pc10.dim("Run `btcemail login` to authenticate."));
642
1218
  process.exit(1);
643
1219
  }
1220
+ const spinner = ora8("Loading pending emails...").start();
644
1221
  const result = await getPendingEmails({
645
1222
  limit: options.limit || 20
646
1223
  });
647
1224
  if (!result.ok) {
648
- console.error(pc8.red(`Error: ${result.error.message}`));
1225
+ spinner.fail(`Error: ${result.error.message}`);
649
1226
  process.exit(1);
650
1227
  }
651
1228
  const { data: emails, pagination } = result.data;
1229
+ spinner.stop();
652
1230
  if (options.json) {
653
1231
  console.log(JSON.stringify({ emails, pagination }, null, 2));
654
1232
  return;
655
1233
  }
656
1234
  console.log();
657
- console.log(pc8.bold(`Pending Emails (${pagination.total} awaiting payment)`));
1235
+ console.log(pc10.bold(`Pending (${pagination.total} awaiting payment)`));
658
1236
  console.log();
659
1237
  if (emails.length === 0) {
660
- console.log(pc8.dim(" No pending emails."));
1238
+ console.log(pc10.dim(" No pending emails."));
661
1239
  console.log();
662
1240
  return;
663
1241
  }
664
1242
  console.log(
665
- ` ${pc8.dim("ID".padEnd(12))} ${pc8.dim("From".padEnd(30))} ${pc8.dim("Subject".padEnd(30))} ${pc8.dim("Sats")}`
1243
+ ` ${pc10.dim("#".padEnd(4))} ${pc10.dim("From".padEnd(28))} ${pc10.dim("Subject".padEnd(35))} ${pc10.dim("Sats")}`
666
1244
  );
667
- console.log(pc8.dim(" " + "-".repeat(85)));
668
- for (const email of emails) {
669
- const id = email.id.slice(0, 10).padEnd(12);
670
- const from = truncate2(email.from.name || email.from.email, 28).padEnd(30);
671
- const subject = truncate2(email.subject, 28).padEnd(30);
672
- const sats = pc8.yellow(String(email.amountSats));
673
- console.log(` ${id} ${from} ${subject} ${sats}`);
674
- }
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
+ });
675
1253
  console.log();
676
1254
  if (pagination.hasMore) {
677
1255
  console.log(
678
- pc8.dim(` Showing ${emails.length} of ${pagination.total}. Use --limit to see more.`)
1256
+ pc10.dim(` Showing ${emails.length} of ${pagination.total}. Use --limit to see more.`)
679
1257
  );
680
1258
  console.log();
681
1259
  }
682
- console.log(pc8.dim(" Use `btcemail inbound view <id>` to see details and payment invoice."));
1260
+ console.log(pc10.dim(" View details: btcemail inbound view <id>"));
683
1261
  console.log();
684
1262
  }
685
1263
  async function inboundViewCommand(id, options) {
686
1264
  if (!isAuthenticated()) {
687
- console.log(pc8.yellow("Not logged in."));
688
- console.log(pc8.dim("Run `btcemail login` to authenticate."));
1265
+ console.log(pc10.yellow("Not logged in."));
1266
+ console.log(pc10.dim("Run `btcemail login` to authenticate."));
689
1267
  process.exit(1);
690
1268
  }
1269
+ const spinner = ora8("Loading email details...").start();
691
1270
  const result = await getPendingEmail(id);
692
1271
  if (!result.ok) {
693
1272
  if (result.error.code === "NOT_FOUND") {
694
- console.error(pc8.red(`Pending email not found: ${id}`));
1273
+ spinner.fail(`Pending email not found: ${id}`);
695
1274
  } else {
696
- console.error(pc8.red(`Error: ${result.error.message}`));
1275
+ spinner.fail(`Error: ${result.error.message}`);
697
1276
  }
698
1277
  process.exit(1);
699
1278
  }
1279
+ spinner.stop();
700
1280
  const email = result.data;
701
1281
  if (options.json) {
702
1282
  console.log(JSON.stringify(email, null, 2));
703
1283
  return;
704
1284
  }
705
1285
  console.log();
706
- console.log(pc8.bold(email.subject || "(No subject)"));
1286
+ console.log(pc10.bold(email.subject || "(No subject)"));
707
1287
  console.log();
708
- console.log(`${pc8.dim("From:")} ${formatAddress2(email.from)}`);
709
- console.log(`${pc8.dim("Date:")} ${formatFullDate2(email.createdAt)}`);
710
- console.log(`${pc8.dim("ID:")} ${email.id}`);
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}`);
711
1291
  console.log();
712
- console.log(pc8.yellow(`Payment Required: ${email.amountSats} sats`));
1292
+ console.log(pc10.yellow(`Payment Required: ${email.amountSats} sats`));
713
1293
  console.log();
714
1294
  if (email.paymentHash) {
715
- console.log(pc8.dim("To receive this email, pay the invoice and run:"));
716
- console.log(pc8.cyan(` btcemail inbound pay ${email.id} --payment-hash <preimage>`));
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>`));
717
1297
  } else {
718
- console.log(pc8.dim("Payment information not available."));
1298
+ console.log(pc10.dim("Payment information not available."));
719
1299
  }
720
1300
  console.log();
721
- console.log(pc8.dim("-".repeat(60)));
1301
+ console.log(pc10.dim("-".repeat(60)));
722
1302
  console.log();
723
- console.log(pc8.dim("Preview (full content available after payment):"));
1303
+ console.log(pc10.dim("Preview (full content available after payment):"));
724
1304
  console.log();
725
- console.log(truncate2(email.bodyText || email.body, 200));
1305
+ console.log(truncate3(email.bodyText || email.body, 200));
726
1306
  console.log();
727
1307
  }
728
1308
  async function inboundPayCommand(id, options) {
729
1309
  if (!isAuthenticated()) {
730
- console.log(pc8.yellow("Not logged in."));
731
- console.log(pc8.dim("Run `btcemail login` to authenticate."));
1310
+ console.log(pc10.yellow("Not logged in."));
1311
+ console.log(pc10.dim("Run `btcemail login` to authenticate."));
732
1312
  process.exit(1);
733
1313
  }
734
1314
  if (!options.paymentHash) {
735
- console.error(pc8.red("Payment hash is required. Use --payment-hash <preimage>"));
1315
+ console.error(pc10.red("Payment hash is required. Use --payment-hash <preimage>"));
736
1316
  process.exit(1);
737
1317
  }
738
- console.log();
739
- console.log(pc8.dim("Verifying payment..."));
1318
+ const spinner = ora8("Verifying payment...").start();
740
1319
  const result = await payForEmail(id, options.paymentHash);
741
1320
  if (!result.ok) {
742
1321
  if (result.error.code === "PAYMENT_INVALID") {
743
- console.error(pc8.red("Payment verification failed."));
744
- console.log(pc8.dim("Make sure you paid the correct invoice and provided the preimage."));
1322
+ spinner.fail("Payment verification failed.");
1323
+ console.log(pc10.dim("Make sure you paid the correct invoice and provided the preimage."));
745
1324
  } else if (result.error.code === "NOT_FOUND") {
746
- console.error(pc8.red(`Pending email not found or already delivered: ${id}`));
1325
+ spinner.fail(`Pending email not found or already delivered: ${id}`);
747
1326
  } else {
748
- console.error(pc8.red(`Error: ${result.error.message}`));
1327
+ spinner.fail(`Error: ${result.error.message}`);
749
1328
  }
750
1329
  process.exit(1);
751
1330
  }
1331
+ spinner.succeed("Payment verified! Email delivered.");
752
1332
  console.log();
753
- console.log(pc8.green("Payment verified! Email delivered."));
1333
+ console.log(`${pc10.dim("Email ID:")} ${result.data.emailId}`);
1334
+ console.log(`${pc10.dim("Status:")} ${result.data.status}`);
754
1335
  console.log();
755
- console.log(`${pc8.dim("Email ID:")} ${result.data.emailId}`);
756
- console.log(`${pc8.dim("Status:")} ${result.data.status}`);
757
- console.log();
758
- console.log(pc8.dim("Run `btcemail read " + result.data.emailId + "` to read the email."));
1336
+ console.log(pc10.dim(`Read email: btcemail read ${result.data.emailId}`));
759
1337
  console.log();
760
1338
  }
761
- function truncate2(str, maxLength) {
1339
+ function truncate3(str, maxLength) {
762
1340
  if (str.length <= maxLength) return str;
763
1341
  return str.slice(0, maxLength - 1) + "\u2026";
764
1342
  }
@@ -779,39 +1357,72 @@ function formatFullDate2(dateString) {
779
1357
  minute: "2-digit"
780
1358
  });
781
1359
  }
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
+ });
1370
+ }
1371
+ if (diffDays < 7) {
1372
+ return date.toLocaleDateString("en-US", { weekday: "short" });
1373
+ }
1374
+ return date.toLocaleDateString("en-US", {
1375
+ month: "short",
1376
+ day: "numeric"
1377
+ });
1378
+ }
782
1379
 
783
1380
  // src/index.ts
784
1381
  var program = new Command();
785
- program.name("btcemail").description("btc.email CLI - Spam-free email powered by Bitcoin Lightning").version("0.1.0");
1382
+ program.name("btcemail").description("btc.email CLI - Spam-free email powered by Bitcoin Lightning").version("0.3.0");
786
1383
  program.command("login").description("Authenticate with btc.email (opens browser)").action(loginCommand);
787
1384
  program.command("logout").description("Clear stored credentials").action(logoutCommand);
788
1385
  program.command("whoami").description("Show current authenticated user").action(whoamiCommand);
789
- program.command("list").description("List emails").option("-f, --folder <folder>", "Folder to list (inbox, sent, drafts)", "inbox").option("-l, --limit <number>", "Number of emails to show", "20").option("--json", "Output as JSON").action(async (options) => {
1386
+ program.command("list").alias("ls").description("List emails (numbered for quick access)").option("-f, --folder <folder>", "Folder to list (inbox, sent, drafts)", "inbox").option("-l, --limit <number>", "Number of emails to show", "20").option("-p, --page <number>", "Page number", "1").option("--json", "Output as JSON").action(async (options) => {
790
1387
  await listCommand({
791
1388
  folder: options.folder,
792
1389
  limit: parseInt(options.limit, 10),
1390
+ page: parseInt(options.page, 10),
793
1391
  json: options.json
794
1392
  });
795
1393
  });
796
- program.command("read <id>").description("Read an email by ID").option("--json", "Output as JSON").action(async (id, options) => {
1394
+ program.command("read <id>").alias("r").description("Read an email by # (from list) or ID").option("--json", "Output as JSON").action(async (id, options) => {
797
1395
  await readCommand(id, { json: options.json });
798
1396
  });
799
- program.command("send").description("Send an email").option("-t, --to <emails>", "Recipient email(s), comma-separated").option("-s, --subject <subject>", "Email subject").option("-b, --body <body>", "Email body").option("-f, --from <email>", "From email (@btc.email address)").option("--payment-hash <hash>", "Payment hash/preimage for L402").action(async (options) => {
1397
+ program.command("search <query>").alias("s").description("Search emails by subject, body, or sender").option("-l, --limit <number>", "Number of results to show", "20").option("--json", "Output as JSON").action(async (query, options) => {
1398
+ await searchCommand(query, {
1399
+ limit: parseInt(options.limit, 10),
1400
+ json: options.json
1401
+ });
1402
+ });
1403
+ program.command("send").description("Send an email (with Lightning payment)").option("-t, --to <emails>", "Recipient email(s), comma-separated").option("-s, --subject <subject>", "Email subject").option("-b, --body <body>", "Email body").option("-f, --from <email>", "From email (@btc.email address)").option("--payment-hash <hash>", "Payment preimage for L402").option("-w, --wait", "Wait for payment confirmation").action(async (options) => {
800
1404
  await sendCommand({
801
1405
  to: options.to,
802
1406
  subject: options.subject,
803
1407
  body: options.body,
804
1408
  fromEmail: options.from,
805
- paymentHash: options.paymentHash
1409
+ paymentHash: options.paymentHash,
1410
+ wait: options.wait
806
1411
  });
807
1412
  });
808
- var inbound = program.command("inbound").description("Manage pending emails requiring payment");
809
- inbound.command("pending").description("List pending emails awaiting payment").option("-l, --limit <number>", "Number of emails to show", "20").option("--json", "Output as JSON").action(async (options) => {
1413
+ var inbound = program.command("inbound").description("Manage inbound emails (pending and delivered)");
1414
+ 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) => {
810
1415
  await inboundPendingCommand({
811
1416
  limit: parseInt(options.limit, 10),
812
1417
  json: options.json
813
1418
  });
814
1419
  });
1420
+ inbound.command("delivered").alias("d").description("List delivered emails (paid by senders)").option("-l, --limit <number>", "Number of emails to show", "20").option("--json", "Output as JSON").action(async (options) => {
1421
+ await inboundDeliveredCommand({
1422
+ limit: parseInt(options.limit, 10),
1423
+ json: options.json
1424
+ });
1425
+ });
815
1426
  inbound.command("view <id>").description("View pending email details and payment info").option("--json", "Output as JSON").action(async (id, options) => {
816
1427
  await inboundViewCommand(id, { json: options.json });
817
1428
  });
@@ -819,11 +1430,24 @@ inbound.command("pay <id>").description("Pay for a pending email").requiredOptio
819
1430
  await inboundPayCommand(id, { paymentHash: options.paymentHash });
820
1431
  });
821
1432
  var credits = program.command("credits").description("Manage credits");
822
- credits.command("balance").description("Show credit balance").action(creditsBalanceCommand);
1433
+ credits.command("balance").alias("bal").description("Show credit balance").action(creditsBalanceCommand);
1434
+ 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) => {
1435
+ await creditsHistoryCommand({
1436
+ limit: parseInt(options.limit, 10),
1437
+ type: options.type,
1438
+ json: options.json
1439
+ });
1440
+ });
1441
+ credits.command("purchase").alias("buy").description("Purchase credits with Lightning").option("-o, --option <index>", "Purchase option index (0-3)").option("-w, --wait", "Wait for payment confirmation").option("--json", "Output as JSON").action(async (options) => {
1442
+ await creditsPurchaseCommand({
1443
+ option: options.option !== void 0 ? parseInt(options.option, 10) : void 0,
1444
+ json: options.json,
1445
+ wait: options.wait
1446
+ });
1447
+ });
823
1448
  program.action(() => {
824
1449
  console.log();
825
- console.log(pc9.bold("btc.email CLI"));
826
- console.log(pc9.dim("Spam-free email powered by Bitcoin Lightning"));
1450
+ console.log(pc11.bold("btc.email CLI") + pc11.dim(" - Spam-free email powered by Bitcoin Lightning"));
827
1451
  console.log();
828
1452
  program.outputHelp();
829
1453
  });