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