@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/README.md +77 -1
- package/dist/index.js +1233 -728
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,12 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
-
import
|
|
5
|
+
import pc16 from "picocolors";
|
|
6
6
|
|
|
7
|
-
// src/commands/
|
|
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/
|
|
424
|
-
async function
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
console.
|
|
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
|
|
431
|
-
if (!
|
|
432
|
-
console.
|
|
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
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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(
|
|
462
|
-
console.log(
|
|
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
|
-
|
|
465
|
-
|
|
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
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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
|
|
485
|
-
const
|
|
486
|
-
if (!
|
|
487
|
-
|
|
488
|
-
|
|
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
|
-
|
|
492
|
-
|
|
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
|
-
|
|
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
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
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
|
-
|
|
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
|
|
506
|
-
return
|
|
489
|
+
function sleep(ms) {
|
|
490
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
507
491
|
}
|
|
508
492
|
|
|
509
|
-
// src/commands/
|
|
510
|
-
async function
|
|
493
|
+
// src/commands/credits.ts
|
|
494
|
+
async function creditsBalanceCommand() {
|
|
511
495
|
if (!isAuthenticated()) {
|
|
512
|
-
console.log(
|
|
513
|
-
console.log(
|
|
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
|
|
517
|
-
const
|
|
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
|
|
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(
|
|
509
|
+
console.log(pc3.bold("Credit Balance"));
|
|
544
510
|
console.log();
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
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(/ /g, " ").replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&").replace(/"/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(
|
|
847
|
-
console.log(
|
|
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(
|
|
854
|
-
console.log(
|
|
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 =
|
|
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(
|
|
546
|
+
console.log(pc3.bold("Transaction History"));
|
|
874
547
|
console.log();
|
|
875
548
|
if (transactions.length === 0) {
|
|
876
|
-
console.log(
|
|
549
|
+
console.log(pc3.dim(" No transactions found."));
|
|
877
550
|
console.log();
|
|
878
551
|
return;
|
|
879
552
|
}
|
|
880
553
|
console.log(
|
|
881
|
-
` ${
|
|
554
|
+
` ${pc3.dim("Date".padEnd(12))} ${pc3.dim("Type".padEnd(15))} ${pc3.dim("Amount".padEnd(12))} ${pc3.dim("Balance")}`
|
|
882
555
|
);
|
|
883
|
-
console.log(
|
|
556
|
+
console.log(pc3.dim(` ${"-".repeat(55)}`));
|
|
884
557
|
for (const tx of transactions) {
|
|
885
|
-
const date =
|
|
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} ${
|
|
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(
|
|
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(
|
|
900
|
-
console.log(
|
|
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(
|
|
579
|
+
console.log(pc3.bold("Purchase Options"));
|
|
907
580
|
console.log();
|
|
908
581
|
purchaseOptions.forEach((opt, index) => {
|
|
909
|
-
console.log(` ${
|
|
910
|
-
console.log(` ${
|
|
911
|
-
console.log(` ${
|
|
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(` ${
|
|
586
|
+
console.log(` ${pc3.green(`+${formatSats(opt.bonusSats)} bonus sats`)}`);
|
|
914
587
|
}
|
|
915
588
|
console.log();
|
|
916
589
|
});
|
|
917
|
-
console.log(
|
|
918
|
-
console.log(
|
|
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(
|
|
596
|
+
console.error(pc3.red(`Error: Invalid option. Choose 0-${purchaseOptions.length - 1}.`));
|
|
924
597
|
process.exit(1);
|
|
925
598
|
}
|
|
926
|
-
const
|
|
927
|
-
const spinner =
|
|
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(
|
|
613
|
+
console.log(pc3.bold("Lightning Invoice"));
|
|
941
614
|
console.log();
|
|
942
|
-
console.log(` ${
|
|
943
|
-
console.log(` ${
|
|
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(` ${
|
|
618
|
+
console.log(` ${pc3.dim("Bonus:")} ${pc3.green(`+${formatSats(purchase.bonusSats)} sats`)}`);
|
|
946
619
|
}
|
|
947
620
|
console.log();
|
|
948
|
-
console.log(
|
|
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) =>
|
|
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(
|
|
628
|
+
console.log(pc3.bold("Invoice:"));
|
|
956
629
|
console.log();
|
|
957
630
|
console.log(` ${purchase.invoice}`);
|
|
958
631
|
console.log();
|
|
959
|
-
console.log(
|
|
960
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
987
|
-
console.log(
|
|
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
|
|
671
|
+
return pc3.green(`+${formatSats(sats)}`);
|
|
997
672
|
}
|
|
998
|
-
return
|
|
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
|
|
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(/ /g, " ").replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&").replace(/"/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(
|
|
1025
|
-
console.log(
|
|
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(
|
|
1044
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
1424
|
+
console.log(pc11.bold("Sending Email"));
|
|
1062
1425
|
console.log();
|
|
1063
|
-
console.log(`${
|
|
1064
|
-
console.log(`${
|
|
1065
|
-
console.log(`${
|
|
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(
|
|
1439
|
+
console.log(pc11.yellow(`Payment required: ${invoice.amountSats} sats`));
|
|
1077
1440
|
console.log();
|
|
1078
|
-
console.log(
|
|
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) =>
|
|
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(
|
|
1086
|
-
console.log(
|
|
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(`${
|
|
1477
|
+
console.log(`${pc11.dim("Email ID:")} ${result2.data.emailId}`);
|
|
1115
1478
|
if (result2.data.messageId) {
|
|
1116
|
-
console.log(`${
|
|
1479
|
+
console.log(`${pc11.dim("Message ID:")} ${result2.data.messageId}`);
|
|
1117
1480
|
}
|
|
1118
1481
|
console.log();
|
|
1119
1482
|
} else {
|
|
1120
|
-
console.log(
|
|
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(
|
|
1489
|
+
console.log(pc11.dim("Pay this invoice with your Lightning wallet, then run:"));
|
|
1125
1490
|
console.log(
|
|
1126
|
-
|
|
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(
|
|
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(
|
|
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(`${
|
|
1520
|
+
console.log(`${pc11.dim("Email ID:")} ${result.data.emailId}`);
|
|
1156
1521
|
if (result.data.messageId) {
|
|
1157
|
-
console.log(`${
|
|
1522
|
+
console.log(`${pc11.dim("Message ID:")} ${result.data.messageId}`);
|
|
1158
1523
|
}
|
|
1159
1524
|
console.log();
|
|
1160
1525
|
}
|
|
1161
1526
|
|
|
1162
|
-
// src/commands/
|
|
1163
|
-
import
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
if (!
|
|
1167
|
-
console.
|
|
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
|
|
1172
|
-
const result = await getDeliveredEmails({
|
|
1173
|
-
limit: options.limit || 20
|
|
1174
|
-
});
|
|
1535
|
+
const result = await getSettings(options.username);
|
|
1175
1536
|
if (!result.ok) {
|
|
1176
|
-
|
|
1537
|
+
console.error(pc12.red(`Error: ${result.error.message}`));
|
|
1177
1538
|
process.exit(1);
|
|
1178
1539
|
}
|
|
1179
|
-
const {
|
|
1180
|
-
spinner.stop();
|
|
1540
|
+
const { username, usernames, settings: settings2 } = result.data;
|
|
1181
1541
|
if (options.json) {
|
|
1182
|
-
console.log(JSON.stringify({
|
|
1542
|
+
console.log(JSON.stringify({ username, usernames, settings: settings2 }, null, 2));
|
|
1183
1543
|
return;
|
|
1184
1544
|
}
|
|
1185
1545
|
console.log();
|
|
1186
|
-
console.log(
|
|
1187
|
-
|
|
1188
|
-
|
|
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
|
-
`
|
|
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
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
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
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
console.
|
|
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
|
|
1221
|
-
|
|
1222
|
-
|
|
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
|
-
|
|
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({
|
|
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(
|
|
1670
|
+
console.log(pc13.bold("Dashboard Statistics"));
|
|
1236
1671
|
console.log();
|
|
1237
|
-
|
|
1238
|
-
|
|
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
|
-
|
|
1243
|
-
|
|
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
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
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
|
|
1270
|
-
const result = await getPendingEmail(id);
|
|
1698
|
+
const result = await getWhitelist(options.username);
|
|
1271
1699
|
if (!result.ok) {
|
|
1272
|
-
|
|
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
|
-
|
|
1280
|
-
const email = result.data;
|
|
1703
|
+
const { username, entries } = result.data;
|
|
1281
1704
|
if (options.json) {
|
|
1282
|
-
console.log(JSON.stringify(
|
|
1705
|
+
console.log(JSON.stringify({ username, entries }, null, 2));
|
|
1283
1706
|
return;
|
|
1284
1707
|
}
|
|
1285
1708
|
console.log();
|
|
1286
|
-
console.log(
|
|
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 (
|
|
1295
|
-
console.log(
|
|
1296
|
-
console.log(
|
|
1297
|
-
|
|
1298
|
-
console.log(
|
|
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
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
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
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
console.
|
|
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
|
-
|
|
1315
|
-
|
|
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
|
-
|
|
1319
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
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
|
-
|
|
1764
|
+
console.log(pc14.green("Removed from whitelist"));
|
|
1348
1765
|
}
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
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
|
-
|
|
1372
|
-
|
|
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.
|
|
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(
|
|
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
|
});
|