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