@btcemail/cli 0.1.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 ADDED
@@ -0,0 +1,170 @@
1
+ # @btcemail/cli
2
+
3
+ Command-line interface for btc.email - spam-free email powered by Bitcoin Lightning.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g @btcemail/cli
9
+ ```
10
+
11
+ Or with bun:
12
+
13
+ ```bash
14
+ bun install -g @btcemail/cli
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```bash
20
+ btcemail [command] [options]
21
+ ```
22
+
23
+ ## Commands
24
+
25
+ ### Authentication
26
+
27
+ ```bash
28
+ # Log in (opens browser for authentication)
29
+ btcemail login
30
+
31
+ # Show current user
32
+ btcemail whoami
33
+
34
+ # Log out
35
+ btcemail logout
36
+ ```
37
+
38
+ ### Email
39
+
40
+ ```bash
41
+ # List emails (default: inbox, 20 emails)
42
+ btcemail list
43
+
44
+ # List with options
45
+ btcemail list --folder sent --limit 50
46
+
47
+ # Output as JSON
48
+ btcemail list --json
49
+
50
+ # Read a specific email
51
+ btcemail read <email-id>
52
+
53
+ # Read as JSON
54
+ btcemail read <email-id> --json
55
+
56
+ # Send an email (L402 payment required for external recipients)
57
+ btcemail send --to recipient@example.com --subject "Hello" --body "Message body"
58
+
59
+ # Send with payment (after paying Lightning invoice)
60
+ btcemail send --to recipient@example.com --subject "Hello" --body "Message" --payment-hash <preimage>
61
+ ```
62
+
63
+ ### Inbound (Pending Emails)
64
+
65
+ Emails from unknown senders require payment before delivery.
66
+
67
+ ```bash
68
+ # List pending emails awaiting payment
69
+ btcemail inbound pending
70
+
71
+ # View pending email details and payment info
72
+ btcemail inbound view <email-id>
73
+
74
+ # Pay for a pending email (after paying the Lightning invoice)
75
+ btcemail inbound pay <email-id> --payment-hash <preimage>
76
+ ```
77
+
78
+ ### Credits
79
+
80
+ ```bash
81
+ # Show credit balance
82
+ btcemail credits balance
83
+ ```
84
+
85
+ ## Options
86
+
87
+ ### list
88
+
89
+ | Option | Description | Default |
90
+ |--------|-------------|---------|
91
+ | `-f, --folder <folder>` | Folder to list (inbox, sent, drafts) | inbox |
92
+ | `-l, --limit <number>` | Number of emails to show | 20 |
93
+ | `--json` | Output as JSON | false |
94
+
95
+ ### read
96
+
97
+ | Option | Description | Default |
98
+ |--------|-------------|---------|
99
+ | `--json` | Output as JSON | false |
100
+
101
+ ### send
102
+
103
+ | Option | Description | Default |
104
+ |--------|-------------|---------|
105
+ | `-t, --to <emails>` | Recipient email(s), comma-separated | required |
106
+ | `-s, --subject <text>` | Email subject | required |
107
+ | `-b, --body <text>` | Email body | required |
108
+ | `-f, --from <email>` | From email (@btc.email address) | auto-detected |
109
+ | `--payment-hash <hash>` | Payment preimage for L402 | optional |
110
+
111
+ ### inbound pending
112
+
113
+ | Option | Description | Default |
114
+ |--------|-------------|---------|
115
+ | `-l, --limit <number>` | Number of emails to show | 20 |
116
+ | `--json` | Output as JSON | false |
117
+
118
+ ### inbound view
119
+
120
+ | Option | Description | Default |
121
+ |--------|-------------|---------|
122
+ | `--json` | Output as JSON | false |
123
+
124
+ ### inbound pay
125
+
126
+ | Option | Description | Default |
127
+ |--------|-------------|---------|
128
+ | `--payment-hash <hash>` | Payment preimage from Lightning invoice | required |
129
+
130
+ ## Authentication Flow
131
+
132
+ The CLI uses browser-based authentication:
133
+
134
+ 1. Run `btcemail login`
135
+ 2. Browser opens to btc.email login page
136
+ 3. Complete authentication in browser
137
+ 4. CLI receives credentials automatically
138
+
139
+ Credentials are stored locally and expire after 24 hours.
140
+
141
+ ## Environment Variables
142
+
143
+ | Variable | Description |
144
+ |----------|-------------|
145
+ | `BTCEMAIL_API_URL` | Override API base URL (for development) |
146
+
147
+ ## Development
148
+
149
+ ```bash
150
+ # Install dependencies
151
+ bun install
152
+
153
+ # Build
154
+ bun run build
155
+
156
+ # Run locally
157
+ bun run start
158
+
159
+ # Watch mode
160
+ bun run dev
161
+ ```
162
+
163
+ ## Requirements
164
+
165
+ - Node.js 18 or later
166
+ - An active btc.email account
167
+
168
+ ## License
169
+
170
+ MIT
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,797 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+ import pc9 from "picocolors";
6
+
7
+ // src/commands/login.ts
8
+ import { randomUUID } from "crypto";
9
+ import open from "open";
10
+ import ora from "ora";
11
+ import pc from "picocolors";
12
+
13
+ // src/config.ts
14
+ import Conf from "conf";
15
+ var config = new Conf({
16
+ projectName: "btcemail",
17
+ schema: {
18
+ auth: {
19
+ type: "object",
20
+ properties: {
21
+ token: { type: "string" },
22
+ expiresAt: { type: "string" },
23
+ userId: { type: "string" },
24
+ email: { type: "string" }
25
+ }
26
+ },
27
+ api: {
28
+ type: "object",
29
+ properties: {
30
+ baseUrl: { type: "string" }
31
+ },
32
+ default: {
33
+ baseUrl: "https://btc.email/api/v1"
34
+ }
35
+ },
36
+ preferences: {
37
+ type: "object",
38
+ properties: {
39
+ useCredits: { type: "boolean" },
40
+ defaultEditor: { type: "string" }
41
+ },
42
+ default: {
43
+ useCredits: true,
44
+ defaultEditor: "vim"
45
+ }
46
+ }
47
+ }
48
+ });
49
+ function getAuth() {
50
+ return config.get("auth");
51
+ }
52
+ function setAuth(auth) {
53
+ config.set("auth", auth);
54
+ }
55
+ function clearAuth() {
56
+ config.delete("auth");
57
+ }
58
+ function getApiBaseUrl() {
59
+ return process.env.BTCEMAIL_API_URL || config.get("api.baseUrl") || "https://btc.email/api/v1";
60
+ }
61
+ function isAuthenticated() {
62
+ const auth = getAuth();
63
+ if (!auth?.token || !auth?.expiresAt) {
64
+ return false;
65
+ }
66
+ const expiresAt = new Date(auth.expiresAt);
67
+ return expiresAt > /* @__PURE__ */ new Date();
68
+ }
69
+ function isTokenExpiringSoon() {
70
+ const auth = getAuth();
71
+ if (!auth?.token || !auth?.expiresAt) {
72
+ return false;
73
+ }
74
+ const expiresAt = new Date(auth.expiresAt);
75
+ const fiveMinutesFromNow = new Date(Date.now() + 5 * 60 * 1e3);
76
+ return expiresAt <= fiveMinutesFromNow && expiresAt > /* @__PURE__ */ new Date();
77
+ }
78
+ function getTokenExpiryInfo() {
79
+ const auth = getAuth();
80
+ if (!auth?.token || !auth?.expiresAt) {
81
+ return { expired: true };
82
+ }
83
+ const expiresAt = new Date(auth.expiresAt);
84
+ const now = /* @__PURE__ */ new Date();
85
+ if (expiresAt <= now) {
86
+ return { expired: true };
87
+ }
88
+ const diffMs = expiresAt.getTime() - now.getTime();
89
+ const diffMins = Math.floor(diffMs / 6e4);
90
+ const diffHours = Math.floor(diffMins / 60);
91
+ if (diffHours > 0) {
92
+ return { expired: false, expiresIn: `${diffHours}h ${diffMins % 60}m` };
93
+ }
94
+ return { expired: false, expiresIn: `${diffMins}m` };
95
+ }
96
+ function getToken() {
97
+ const auth = getAuth();
98
+ if (!auth?.token) return null;
99
+ const expiresAt = new Date(auth.expiresAt);
100
+ if (expiresAt <= /* @__PURE__ */ new Date()) {
101
+ clearAuth();
102
+ return null;
103
+ }
104
+ return auth.token;
105
+ }
106
+
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
+
204
+ // src/api.ts
205
+ async function apiRequest(endpoint, options = {}) {
206
+ const token = getToken();
207
+ const baseUrl = getApiBaseUrl();
208
+ const headers = {
209
+ "Content-Type": "application/json",
210
+ ...options.headers
211
+ };
212
+ if (token) {
213
+ headers.Authorization = `Bearer ${token}`;
214
+ }
215
+ try {
216
+ const response = await fetch(`${baseUrl}${endpoint}`, {
217
+ ...options,
218
+ headers
219
+ });
220
+ const json = await response.json();
221
+ if (!response.ok || json.success === false) {
222
+ return {
223
+ ok: false,
224
+ error: json.error || {
225
+ code: "UNKNOWN_ERROR",
226
+ message: `Request failed with status ${response.status}`
227
+ }
228
+ };
229
+ }
230
+ if (json.pagination) {
231
+ return {
232
+ ok: true,
233
+ data: { data: json.data, pagination: json.pagination }
234
+ };
235
+ }
236
+ return { ok: true, data: json.data };
237
+ } catch (error) {
238
+ return {
239
+ ok: false,
240
+ error: {
241
+ code: "NETWORK_ERROR",
242
+ message: error instanceof Error ? error.message : "Network request failed"
243
+ }
244
+ };
245
+ }
246
+ }
247
+ async function getEmails(options) {
248
+ const params = new URLSearchParams();
249
+ if (options.folder) params.set("folder", options.folder);
250
+ if (options.limit) params.set("limit", String(options.limit));
251
+ if (options.offset) params.set("offset", String(options.offset));
252
+ if (options.search) params.set("search", options.search);
253
+ return apiRequest(`/email?${params}`);
254
+ }
255
+ async function getEmail(id) {
256
+ return apiRequest(`/email/${id}`);
257
+ }
258
+ async function getCreditsBalance() {
259
+ return apiRequest("/credits/balance");
260
+ }
261
+ async function getWhoami() {
262
+ return apiRequest(
263
+ "/auth/whoami"
264
+ );
265
+ }
266
+ async function getPendingEmails(options = {}) {
267
+ const params = new URLSearchParams();
268
+ if (options.limit) params.set("limit", String(options.limit));
269
+ if (options.offset) params.set("offset", String(options.offset));
270
+ return apiRequest(`/inbound/pending?${params}`);
271
+ }
272
+ async function getPendingEmail(id) {
273
+ return apiRequest(`/inbound/${id}`);
274
+ }
275
+ async function payForEmail(id, paymentHash) {
276
+ return apiRequest(`/inbound/${id}/pay`, {
277
+ method: "POST",
278
+ body: JSON.stringify({ paymentHash })
279
+ });
280
+ }
281
+ async function sendEmail(options) {
282
+ return apiRequest("/email/send", {
283
+ method: "POST",
284
+ body: JSON.stringify(options)
285
+ });
286
+ }
287
+ async function getL402Invoice(options) {
288
+ return apiRequest("/l402/invoice", {
289
+ method: "POST",
290
+ body: JSON.stringify({
291
+ sender_email: options.fromEmail,
292
+ recipient_emails: options.toEmails,
293
+ amount_sats: options.amountSats
294
+ })
295
+ });
296
+ }
297
+
298
+ // src/commands/whoami.ts
299
+ async function whoamiCommand() {
300
+ if (!isAuthenticated()) {
301
+ console.log(pc3.yellow("Not logged in."));
302
+ console.log(pc3.dim("Run `btcemail login` to authenticate."));
303
+ process.exit(1);
304
+ }
305
+ const auth = getAuth();
306
+ if (!auth) {
307
+ console.log(pc3.yellow("Not logged in."));
308
+ process.exit(1);
309
+ }
310
+ const result = await getWhoami();
311
+ if (result.ok) {
312
+ console.log();
313
+ console.log(pc3.bold("Current User"));
314
+ console.log();
315
+ console.log(` ${pc3.dim("Email:")} ${result.data.email}`);
316
+ if (result.data.username) {
317
+ console.log(` ${pc3.dim("Username:")} ${result.data.username}`);
318
+ }
319
+ console.log(` ${pc3.dim("User ID:")} ${result.data.id}`);
320
+ const expiryInfo = getTokenExpiryInfo();
321
+ if (expiryInfo.expiresIn) {
322
+ const expiryColor = isTokenExpiringSoon() ? pc3.yellow : pc3.dim;
323
+ console.log(` ${pc3.dim("Session:")} ${expiryColor(`expires in ${expiryInfo.expiresIn}`)}`);
324
+ }
325
+ console.log();
326
+ if (isTokenExpiringSoon()) {
327
+ console.log(pc3.yellow("Session expiring soon. Run `btcemail login` to refresh."));
328
+ console.log();
329
+ }
330
+ } else {
331
+ console.log();
332
+ console.log(pc3.bold("Current User (cached)"));
333
+ console.log();
334
+ console.log(` ${pc3.dim("Email:")} ${auth.email}`);
335
+ console.log(` ${pc3.dim("User ID:")} ${auth.userId}`);
336
+ console.log();
337
+ if (result.error.code === "UNAUTHENTICATED" || result.error.code === "UNAUTHORIZED") {
338
+ console.log(pc3.yellow("Session expired. Run `btcemail login` to re-authenticate."));
339
+ }
340
+ }
341
+ }
342
+
343
+ // src/commands/list.ts
344
+ import pc4 from "picocolors";
345
+ async function listCommand(options) {
346
+ if (!isAuthenticated()) {
347
+ console.log(pc4.yellow("Not logged in."));
348
+ console.log(pc4.dim("Run `btcemail login` to authenticate."));
349
+ process.exit(1);
350
+ }
351
+ const result = await getEmails({
352
+ folder: options.folder || "inbox",
353
+ limit: options.limit || 20
354
+ });
355
+ if (!result.ok) {
356
+ console.error(pc4.red(`Error: ${result.error.message}`));
357
+ process.exit(1);
358
+ }
359
+ const { data: emails, pagination } = result.data;
360
+ if (options.json) {
361
+ console.log(JSON.stringify({ emails, pagination }, null, 2));
362
+ return;
363
+ }
364
+ console.log();
365
+ console.log(
366
+ pc4.bold(`${options.folder || "Inbox"} (${pagination.total} emails)`)
367
+ );
368
+ console.log();
369
+ if (emails.length === 0) {
370
+ console.log(pc4.dim(" No emails found."));
371
+ console.log();
372
+ return;
373
+ }
374
+ console.log(
375
+ ` ${pc4.dim("ID".padEnd(12))} ${pc4.dim("From".padEnd(25))} ${pc4.dim("Subject".padEnd(40))} ${pc4.dim("Date")}`
376
+ );
377
+ console.log(pc4.dim(" " + "-".repeat(90)));
378
+ for (const email of emails) {
379
+ const unreadMarker = email.unread ? pc4.cyan("*") : " ";
380
+ const id = email.id.slice(0, 10).padEnd(12);
381
+ const from = truncate(email.from.name || email.from.email, 23).padEnd(25);
382
+ const subject = truncate(email.subject, 38).padEnd(40);
383
+ const date = formatDate(email.date);
384
+ console.log(`${unreadMarker} ${id} ${from} ${subject} ${pc4.dim(date)}`);
385
+ }
386
+ console.log();
387
+ if (pagination.hasMore) {
388
+ console.log(
389
+ pc4.dim(
390
+ ` Showing ${emails.length} of ${pagination.total}. Use --limit to see more.`
391
+ )
392
+ );
393
+ console.log();
394
+ }
395
+ }
396
+ function truncate(str, maxLength) {
397
+ if (str.length <= maxLength) return str;
398
+ return str.slice(0, maxLength - 1) + "\u2026";
399
+ }
400
+ function formatDate(dateString) {
401
+ const date = new Date(dateString);
402
+ const now = /* @__PURE__ */ new Date();
403
+ const diffMs = now.getTime() - date.getTime();
404
+ const diffDays = Math.floor(diffMs / (1e3 * 60 * 60 * 24));
405
+ if (diffDays === 0) {
406
+ return date.toLocaleTimeString("en-US", {
407
+ hour: "numeric",
408
+ minute: "2-digit"
409
+ });
410
+ }
411
+ if (diffDays < 7) {
412
+ return date.toLocaleDateString("en-US", { weekday: "short" });
413
+ }
414
+ return date.toLocaleDateString("en-US", {
415
+ month: "short",
416
+ day: "numeric"
417
+ });
418
+ }
419
+
420
+ // src/commands/read.ts
421
+ import pc5 from "picocolors";
422
+ async function readCommand(id, options) {
423
+ if (!isAuthenticated()) {
424
+ console.log(pc5.yellow("Not logged in."));
425
+ console.log(pc5.dim("Run `btcemail login` to authenticate."));
426
+ process.exit(1);
427
+ }
428
+ const result = await getEmail(id);
429
+ if (!result.ok) {
430
+ if (result.error.code === "NOT_FOUND") {
431
+ console.error(pc5.red(`Email not found: ${id}`));
432
+ } else {
433
+ console.error(pc5.red(`Error: ${result.error.message}`));
434
+ }
435
+ process.exit(1);
436
+ }
437
+ const email = result.data;
438
+ if (options.json) {
439
+ console.log(JSON.stringify(email, null, 2));
440
+ return;
441
+ }
442
+ console.log();
443
+ console.log(pc5.bold(email.subject || "(No subject)"));
444
+ console.log();
445
+ console.log(`${pc5.dim("From:")} ${formatAddress(email.from)}`);
446
+ console.log(`${pc5.dim("To:")} ${email.to.map(formatAddress).join(", ")}`);
447
+ console.log(`${pc5.dim("Date:")} ${formatFullDate(email.date)}`);
448
+ console.log(`${pc5.dim("ID:")} ${email.id}`);
449
+ if (email.labels.length > 0) {
450
+ console.log(`${pc5.dim("Labels:")} ${email.labels.join(", ")}`);
451
+ }
452
+ console.log();
453
+ console.log(pc5.dim("-".repeat(60)));
454
+ console.log();
455
+ const content = email.bodyText || email.body || email.snippet;
456
+ if (content) {
457
+ const displayContent = email.bodyText || stripHtml(email.body || email.snippet);
458
+ console.log(displayContent);
459
+ } else {
460
+ console.log(pc5.dim("(No content)"));
461
+ }
462
+ console.log();
463
+ }
464
+ function stripHtml(html) {
465
+ return html.replace(/<br\s*\/?>/gi, "\n").replace(/<\/p>/gi, "\n\n").replace(/<\/div>/gi, "\n").replace(/<[^>]*>/g, "").replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&quot;/g, '"').trim();
466
+ }
467
+ function formatAddress(addr) {
468
+ if (addr.name) {
469
+ return `${addr.name} <${addr.email}>`;
470
+ }
471
+ return addr.email;
472
+ }
473
+ function formatFullDate(dateString) {
474
+ const date = new Date(dateString);
475
+ return date.toLocaleString("en-US", {
476
+ weekday: "short",
477
+ year: "numeric",
478
+ month: "short",
479
+ day: "numeric",
480
+ hour: "numeric",
481
+ minute: "2-digit"
482
+ });
483
+ }
484
+
485
+ // src/commands/credits.ts
486
+ import pc6 from "picocolors";
487
+ async function creditsBalanceCommand() {
488
+ if (!isAuthenticated()) {
489
+ console.log(pc6.yellow("Not logged in."));
490
+ console.log(pc6.dim("Run `btcemail login` to authenticate."));
491
+ process.exit(1);
492
+ }
493
+ const result = await getCreditsBalance();
494
+ if (!result.ok) {
495
+ console.error(pc6.red(`Error: ${result.error.message}`));
496
+ process.exit(1);
497
+ }
498
+ const { balanceSats } = result.data;
499
+ console.log();
500
+ console.log(pc6.bold("Credit Balance"));
501
+ console.log();
502
+ console.log(` ${pc6.green(formatSats(balanceSats))} sats`);
503
+ console.log();
504
+ if (balanceSats === 0) {
505
+ console.log(pc6.dim(" No credits available."));
506
+ console.log(pc6.dim(" Purchase credits at https://btc.email/payments"));
507
+ console.log();
508
+ }
509
+ }
510
+ function formatSats(sats) {
511
+ return sats.toLocaleString();
512
+ }
513
+
514
+ // src/commands/send.ts
515
+ import pc7 from "picocolors";
516
+ async function sendCommand(options) {
517
+ if (!isAuthenticated()) {
518
+ console.log(pc7.yellow("Not logged in."));
519
+ console.log(pc7.dim("Run `btcemail login` to authenticate."));
520
+ process.exit(1);
521
+ }
522
+ let fromEmail = options.fromEmail;
523
+ if (!fromEmail) {
524
+ const whoamiResult = await getWhoami();
525
+ if (whoamiResult.ok && whoamiResult.data.username) {
526
+ fromEmail = `${whoamiResult.data.username}@btc.email`;
527
+ } else {
528
+ const auth = getAuth();
529
+ if (auth?.email?.endsWith("@btc.email")) {
530
+ fromEmail = auth.email;
531
+ }
532
+ }
533
+ }
534
+ if (!fromEmail || !fromEmail.endsWith("@btc.email")) {
535
+ console.error(pc7.red("No @btc.email address found."));
536
+ console.log(pc7.dim("Register a username at https://btc.email"));
537
+ process.exit(1);
538
+ }
539
+ if (!options.to) {
540
+ console.error(pc7.red("Recipient is required. Use --to <email>"));
541
+ process.exit(1);
542
+ }
543
+ if (!options.subject) {
544
+ console.error(pc7.red("Subject is required. Use --subject <text>"));
545
+ process.exit(1);
546
+ }
547
+ if (!options.body) {
548
+ console.error(pc7.red("Body is required. Use --body <text>"));
549
+ process.exit(1);
550
+ }
551
+ const toEmails = options.to.split(",").map((e) => e.trim());
552
+ console.log();
553
+ console.log(pc7.bold("Sending Email"));
554
+ console.log();
555
+ console.log(`${pc7.dim("From:")} ${fromEmail}`);
556
+ console.log(`${pc7.dim("To:")} ${toEmails.join(", ")}`);
557
+ console.log(`${pc7.dim("Subject:")} ${options.subject}`);
558
+ console.log();
559
+ const invoiceResult = await getL402Invoice({
560
+ fromEmail,
561
+ toEmails
562
+ });
563
+ if (invoiceResult.ok && invoiceResult.data.amountSats > 0) {
564
+ const invoice = invoiceResult.data;
565
+ console.log(pc7.yellow(`Payment required: ${invoice.amountSats} sats`));
566
+ console.log();
567
+ console.log(pc7.dim("Lightning Invoice:"));
568
+ console.log(pc7.cyan(invoice.invoice));
569
+ console.log();
570
+ console.log(pc7.dim("Pay this invoice with your Lightning wallet, then run:"));
571
+ console.log(
572
+ pc7.cyan(
573
+ ` btcemail send --to "${options.to}" --subject "${options.subject}" --body "${options.body}" --payment-hash ${invoice.paymentHash}`
574
+ )
575
+ );
576
+ console.log();
577
+ return;
578
+ }
579
+ const result = await sendEmail({
580
+ to: toEmails,
581
+ subject: options.subject,
582
+ body: options.body,
583
+ fromEmail
584
+ });
585
+ if (!result.ok) {
586
+ if (result.error.code === "PAYMENT_REQUIRED") {
587
+ console.error(pc7.yellow("Payment required to send this email."));
588
+ console.log(pc7.dim("Use the invoice above to pay, then include --payment-hash"));
589
+ } else {
590
+ console.error(pc7.red(`Error: ${result.error.message}`));
591
+ }
592
+ process.exit(1);
593
+ }
594
+ console.log(pc7.green("Email sent successfully!"));
595
+ console.log();
596
+ console.log(`${pc7.dim("Email ID:")} ${result.data.emailId}`);
597
+ if (result.data.messageId) {
598
+ console.log(`${pc7.dim("Message ID:")} ${result.data.messageId}`);
599
+ }
600
+ console.log();
601
+ }
602
+
603
+ // src/commands/inbound.ts
604
+ import pc8 from "picocolors";
605
+ async function inboundPendingCommand(options) {
606
+ if (!isAuthenticated()) {
607
+ console.log(pc8.yellow("Not logged in."));
608
+ console.log(pc8.dim("Run `btcemail login` to authenticate."));
609
+ process.exit(1);
610
+ }
611
+ const result = await getPendingEmails({
612
+ limit: options.limit || 20
613
+ });
614
+ if (!result.ok) {
615
+ console.error(pc8.red(`Error: ${result.error.message}`));
616
+ process.exit(1);
617
+ }
618
+ const { data: emails, pagination } = result.data;
619
+ if (options.json) {
620
+ console.log(JSON.stringify({ emails, pagination }, null, 2));
621
+ return;
622
+ }
623
+ console.log();
624
+ console.log(pc8.bold(`Pending Emails (${pagination.total} awaiting payment)`));
625
+ console.log();
626
+ if (emails.length === 0) {
627
+ console.log(pc8.dim(" No pending emails."));
628
+ console.log();
629
+ return;
630
+ }
631
+ console.log(
632
+ ` ${pc8.dim("ID".padEnd(12))} ${pc8.dim("From".padEnd(30))} ${pc8.dim("Subject".padEnd(30))} ${pc8.dim("Sats")}`
633
+ );
634
+ console.log(pc8.dim(" " + "-".repeat(85)));
635
+ for (const email of emails) {
636
+ const id = email.id.slice(0, 10).padEnd(12);
637
+ const from = truncate2(email.from.name || email.from.email, 28).padEnd(30);
638
+ const subject = truncate2(email.subject, 28).padEnd(30);
639
+ const sats = pc8.yellow(String(email.amountSats));
640
+ console.log(` ${id} ${from} ${subject} ${sats}`);
641
+ }
642
+ console.log();
643
+ if (pagination.hasMore) {
644
+ console.log(
645
+ pc8.dim(` Showing ${emails.length} of ${pagination.total}. Use --limit to see more.`)
646
+ );
647
+ console.log();
648
+ }
649
+ console.log(pc8.dim(" Use `btcemail inbound view <id>` to see details and payment invoice."));
650
+ console.log();
651
+ }
652
+ async function inboundViewCommand(id, options) {
653
+ if (!isAuthenticated()) {
654
+ console.log(pc8.yellow("Not logged in."));
655
+ console.log(pc8.dim("Run `btcemail login` to authenticate."));
656
+ process.exit(1);
657
+ }
658
+ const result = await getPendingEmail(id);
659
+ if (!result.ok) {
660
+ if (result.error.code === "NOT_FOUND") {
661
+ console.error(pc8.red(`Pending email not found: ${id}`));
662
+ } else {
663
+ console.error(pc8.red(`Error: ${result.error.message}`));
664
+ }
665
+ process.exit(1);
666
+ }
667
+ const email = result.data;
668
+ if (options.json) {
669
+ console.log(JSON.stringify(email, null, 2));
670
+ return;
671
+ }
672
+ console.log();
673
+ console.log(pc8.bold(email.subject || "(No subject)"));
674
+ console.log();
675
+ console.log(`${pc8.dim("From:")} ${formatAddress2(email.from)}`);
676
+ console.log(`${pc8.dim("Date:")} ${formatFullDate2(email.createdAt)}`);
677
+ console.log(`${pc8.dim("ID:")} ${email.id}`);
678
+ console.log();
679
+ console.log(pc8.yellow(`Payment Required: ${email.amountSats} sats`));
680
+ console.log();
681
+ if (email.paymentHash) {
682
+ console.log(pc8.dim("To receive this email, pay the invoice and run:"));
683
+ console.log(pc8.cyan(` btcemail inbound pay ${email.id} --payment-hash <preimage>`));
684
+ } else {
685
+ console.log(pc8.dim("Payment information not available."));
686
+ }
687
+ console.log();
688
+ console.log(pc8.dim("-".repeat(60)));
689
+ console.log();
690
+ console.log(pc8.dim("Preview (full content available after payment):"));
691
+ console.log();
692
+ console.log(truncate2(email.bodyText || email.body, 200));
693
+ console.log();
694
+ }
695
+ async function inboundPayCommand(id, options) {
696
+ if (!isAuthenticated()) {
697
+ console.log(pc8.yellow("Not logged in."));
698
+ console.log(pc8.dim("Run `btcemail login` to authenticate."));
699
+ process.exit(1);
700
+ }
701
+ if (!options.paymentHash) {
702
+ console.error(pc8.red("Payment hash is required. Use --payment-hash <preimage>"));
703
+ process.exit(1);
704
+ }
705
+ console.log();
706
+ console.log(pc8.dim("Verifying payment..."));
707
+ const result = await payForEmail(id, options.paymentHash);
708
+ if (!result.ok) {
709
+ if (result.error.code === "PAYMENT_INVALID") {
710
+ console.error(pc8.red("Payment verification failed."));
711
+ console.log(pc8.dim("Make sure you paid the correct invoice and provided the preimage."));
712
+ } else if (result.error.code === "NOT_FOUND") {
713
+ console.error(pc8.red(`Pending email not found or already delivered: ${id}`));
714
+ } else {
715
+ console.error(pc8.red(`Error: ${result.error.message}`));
716
+ }
717
+ process.exit(1);
718
+ }
719
+ console.log();
720
+ console.log(pc8.green("Payment verified! Email delivered."));
721
+ console.log();
722
+ console.log(`${pc8.dim("Email ID:")} ${result.data.emailId}`);
723
+ console.log(`${pc8.dim("Status:")} ${result.data.status}`);
724
+ console.log();
725
+ console.log(pc8.dim("Run `btcemail read " + result.data.emailId + "` to read the email."));
726
+ console.log();
727
+ }
728
+ function truncate2(str, maxLength) {
729
+ if (str.length <= maxLength) return str;
730
+ return str.slice(0, maxLength - 1) + "\u2026";
731
+ }
732
+ function formatAddress2(addr) {
733
+ if (addr.name) {
734
+ return `${addr.name} <${addr.email}>`;
735
+ }
736
+ return addr.email;
737
+ }
738
+ function formatFullDate2(dateString) {
739
+ const date = new Date(dateString);
740
+ return date.toLocaleString("en-US", {
741
+ weekday: "short",
742
+ year: "numeric",
743
+ month: "short",
744
+ day: "numeric",
745
+ hour: "numeric",
746
+ minute: "2-digit"
747
+ });
748
+ }
749
+
750
+ // src/index.ts
751
+ var program = new Command();
752
+ program.name("btcemail").description("btc.email CLI - Spam-free email powered by Bitcoin Lightning").version("0.1.0");
753
+ program.command("login").description("Authenticate with btc.email (opens browser)").action(loginCommand);
754
+ program.command("logout").description("Clear stored credentials").action(logoutCommand);
755
+ program.command("whoami").description("Show current authenticated user").action(whoamiCommand);
756
+ program.command("list").description("List emails").option("-f, --folder <folder>", "Folder to list (inbox, sent, drafts)", "inbox").option("-l, --limit <number>", "Number of emails to show", "20").option("--json", "Output as JSON").action(async (options) => {
757
+ await listCommand({
758
+ folder: options.folder,
759
+ limit: parseInt(options.limit, 10),
760
+ json: options.json
761
+ });
762
+ });
763
+ program.command("read <id>").description("Read an email by ID").option("--json", "Output as JSON").action(async (id, options) => {
764
+ await readCommand(id, { json: options.json });
765
+ });
766
+ program.command("send").description("Send an email").option("-t, --to <emails>", "Recipient email(s), comma-separated").option("-s, --subject <subject>", "Email subject").option("-b, --body <body>", "Email body").option("-f, --from <email>", "From email (@btc.email address)").option("--payment-hash <hash>", "Payment hash/preimage for L402").action(async (options) => {
767
+ await sendCommand({
768
+ to: options.to,
769
+ subject: options.subject,
770
+ body: options.body,
771
+ fromEmail: options.from
772
+ });
773
+ });
774
+ var inbound = program.command("inbound").description("Manage pending emails requiring payment");
775
+ inbound.command("pending").description("List pending emails awaiting payment").option("-l, --limit <number>", "Number of emails to show", "20").option("--json", "Output as JSON").action(async (options) => {
776
+ await inboundPendingCommand({
777
+ limit: parseInt(options.limit, 10),
778
+ json: options.json
779
+ });
780
+ });
781
+ inbound.command("view <id>").description("View pending email details and payment info").option("--json", "Output as JSON").action(async (id, options) => {
782
+ await inboundViewCommand(id, { json: options.json });
783
+ });
784
+ inbound.command("pay <id>").description("Pay for a pending email").requiredOption("--payment-hash <hash>", "Payment preimage from Lightning invoice").action(async (id, options) => {
785
+ await inboundPayCommand(id, { paymentHash: options.paymentHash });
786
+ });
787
+ var credits = program.command("credits").description("Manage credits");
788
+ credits.command("balance").description("Show credit balance").action(creditsBalanceCommand);
789
+ program.action(() => {
790
+ console.log();
791
+ console.log(pc9.bold("btc.email CLI"));
792
+ console.log(pc9.dim("Spam-free email powered by Bitcoin Lightning"));
793
+ console.log();
794
+ program.outputHelp();
795
+ });
796
+ program.parse();
797
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/commands/login.ts","../src/config.ts","../src/commands/logout.ts","../src/commands/whoami.ts","../src/api.ts","../src/commands/list.ts","../src/commands/read.ts","../src/commands/credits.ts","../src/commands/send.ts","../src/commands/inbound.ts"],"sourcesContent":["/**\n * btc.email CLI\n *\n * Spam-free email powered by Bitcoin Lightning.\n *\n * Pattern from Claude Code:\n * - No args → Show help (TUI coming later)\n * - btcemail <command> → Execute and exit\n */\n\nimport { Command } from \"commander\"\nimport pc from \"picocolors\"\nimport {\n\tloginCommand,\n\tlogoutCommand,\n\twhoamiCommand,\n\tlistCommand,\n\treadCommand,\n\tcreditsBalanceCommand,\n\tsendCommand,\n\tinboundPendingCommand,\n\tinboundViewCommand,\n\tinboundPayCommand,\n} from \"./commands/index.js\"\n\nconst program = new Command()\n\nprogram\n\t.name(\"btcemail\")\n\t.description(\"btc.email CLI - Spam-free email powered by Bitcoin Lightning\")\n\t.version(\"0.1.0\")\n\n// Authentication commands\nprogram\n\t.command(\"login\")\n\t.description(\"Authenticate with btc.email (opens browser)\")\n\t.action(loginCommand)\n\nprogram\n\t.command(\"logout\")\n\t.description(\"Clear stored credentials\")\n\t.action(logoutCommand)\n\nprogram\n\t.command(\"whoami\")\n\t.description(\"Show current authenticated user\")\n\t.action(whoamiCommand)\n\n// Email commands\nprogram\n\t.command(\"list\")\n\t.description(\"List emails\")\n\t.option(\"-f, --folder <folder>\", \"Folder to list (inbox, sent, drafts)\", \"inbox\")\n\t.option(\"-l, --limit <number>\", \"Number of emails to show\", \"20\")\n\t.option(\"--json\", \"Output as JSON\")\n\t.action(async (options) => {\n\t\tawait listCommand({\n\t\t\tfolder: options.folder,\n\t\t\tlimit: parseInt(options.limit, 10),\n\t\t\tjson: options.json,\n\t\t})\n\t})\n\nprogram\n\t.command(\"read <id>\")\n\t.description(\"Read an email by ID\")\n\t.option(\"--json\", \"Output as JSON\")\n\t.action(async (id, options) => {\n\t\tawait readCommand(id, { json: options.json })\n\t})\n\nprogram\n\t.command(\"send\")\n\t.description(\"Send an email\")\n\t.option(\"-t, --to <emails>\", \"Recipient email(s), comma-separated\")\n\t.option(\"-s, --subject <subject>\", \"Email subject\")\n\t.option(\"-b, --body <body>\", \"Email body\")\n\t.option(\"-f, --from <email>\", \"From email (@btc.email address)\")\n\t.option(\"--payment-hash <hash>\", \"Payment hash/preimage for L402\")\n\t.action(async (options) => {\n\t\tawait sendCommand({\n\t\t\tto: options.to,\n\t\t\tsubject: options.subject,\n\t\t\tbody: options.body,\n\t\t\tfromEmail: options.from,\n\t\t})\n\t})\n\n// Inbound commands (pending emails requiring payment)\nconst inbound = program.command(\"inbound\").description(\"Manage pending emails requiring payment\")\n\ninbound\n\t.command(\"pending\")\n\t.description(\"List pending emails awaiting payment\")\n\t.option(\"-l, --limit <number>\", \"Number of emails to show\", \"20\")\n\t.option(\"--json\", \"Output as JSON\")\n\t.action(async (options) => {\n\t\tawait inboundPendingCommand({\n\t\t\tlimit: parseInt(options.limit, 10),\n\t\t\tjson: options.json,\n\t\t})\n\t})\n\ninbound\n\t.command(\"view <id>\")\n\t.description(\"View pending email details and payment info\")\n\t.option(\"--json\", \"Output as JSON\")\n\t.action(async (id, options) => {\n\t\tawait inboundViewCommand(id, { json: options.json })\n\t})\n\ninbound\n\t.command(\"pay <id>\")\n\t.description(\"Pay for a pending email\")\n\t.requiredOption(\"--payment-hash <hash>\", \"Payment preimage from Lightning invoice\")\n\t.action(async (id, options) => {\n\t\tawait inboundPayCommand(id, { paymentHash: options.paymentHash })\n\t})\n\n// Credits commands\nconst credits = program.command(\"credits\").description(\"Manage credits\")\n\ncredits\n\t.command(\"balance\")\n\t.description(\"Show credit balance\")\n\t.action(creditsBalanceCommand)\n\n// Default action (no command specified)\nprogram.action(() => {\n\tconsole.log()\n\tconsole.log(pc.bold(\"btc.email CLI\"))\n\tconsole.log(pc.dim(\"Spam-free email powered by Bitcoin Lightning\"))\n\tconsole.log()\n\tprogram.outputHelp()\n})\n\n// Parse and run\nprogram.parse()\n","/**\n * Login Command\n *\n * Implements polling-based browser authentication:\n * 1. Generate session_id\n * 2. Open browser to auth page\n * 3. Poll for tokens\n * 4. Store tokens in config\n */\n\nimport { randomUUID } from \"node:crypto\"\nimport open from \"open\"\nimport ora from \"ora\"\nimport pc from \"picocolors\"\nimport { setAuth, getApiBaseUrl } from \"../config.js\"\n\nconst POLL_INTERVAL_MS = 2000\nconst POLL_TIMEOUT_MS = 300000 // 5 minutes\n\ntype CLISessionResponse =\n\t| { status: \"pending\" }\n\t| {\n\t\t\tstatus: \"complete\"\n\t\t\tdata: {\n\t\t\t\ttoken: string\n\t\t\t\texpiresAt: string\n\t\t\t\tuserId: string\n\t\t\t\temail: string\n\t\t\t}\n\t }\n\nexport async function loginCommand(): Promise<void> {\n\tconst sessionId = randomUUID()\n\tconst baseUrl = getApiBaseUrl().replace(\"/api/v1\", \"\")\n\tconst authUrl = `${baseUrl}/auth/cli?session_id=${sessionId}`\n\tconst pollUrl = `${baseUrl}/api/auth/cli-session?session_id=${sessionId}`\n\n\tconsole.log()\n\tconsole.log(pc.bold(\"btc.email CLI Login\"))\n\tconsole.log()\n\n\t// Create pending session first\n\ttry {\n\t\tconst createResponse = await fetch(\n\t\t\t`${baseUrl}/api/auth/cli-session`,\n\t\t\t{\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\tbody: JSON.stringify({ session_id: sessionId }),\n\t\t\t},\n\t\t)\n\n\t\tif (!createResponse.ok) {\n\t\t\tconsole.error(pc.red(\"Failed to initialize login session\"))\n\t\t\tprocess.exit(1)\n\t\t}\n\t} catch (error) {\n\t\tconsole.error(pc.red(\"Failed to connect to btc.email server\"))\n\t\tconsole.error(\n\t\t\tpc.dim(error instanceof Error ? error.message : \"Unknown error\"),\n\t\t)\n\t\tprocess.exit(1)\n\t}\n\n\t// Open browser\n\tconsole.log(pc.dim(\"Opening browser for authentication...\"))\n\tconsole.log()\n\tconsole.log(pc.dim(\"If the browser doesn't open, visit:\"))\n\tconsole.log(pc.cyan(authUrl))\n\tconsole.log()\n\n\ttry {\n\t\tawait open(authUrl)\n\t} catch {\n\t\t// Browser open failed, user can use the URL\n\t}\n\n\t// Poll for tokens\n\tconst spinner = ora(\"Waiting for authentication...\").start()\n\tconst startTime = Date.now()\n\n\twhile (Date.now() - startTime < POLL_TIMEOUT_MS) {\n\t\ttry {\n\t\t\tconst response = await fetch(pollUrl)\n\n\t\t\tif (response.status === 202) {\n\t\t\t\t// Still pending, continue polling\n\t\t\t\tawait sleep(POLL_INTERVAL_MS)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif (response.status === 410) {\n\t\t\t\tspinner.fail(\"Session expired. Please try again.\")\n\t\t\t\tprocess.exit(1)\n\t\t\t}\n\n\t\t\tif (response.ok) {\n\t\t\t\tconst data: CLISessionResponse = await response.json()\n\n\t\t\t\tif (data.status === \"complete\") {\n\t\t\t\t\t// Store tokens\n\t\t\t\t\tsetAuth({\n\t\t\t\t\t\ttoken: data.data.token,\n\t\t\t\t\t\texpiresAt: data.data.expiresAt,\n\t\t\t\t\t\tuserId: data.data.userId,\n\t\t\t\t\t\temail: data.data.email,\n\t\t\t\t\t})\n\n\t\t\t\t\tspinner.succeed(pc.green(\"Successfully logged in!\"))\n\t\t\t\t\tconsole.log()\n\t\t\t\t\tconsole.log(` ${pc.dim(\"Email:\")} ${data.data.email}`)\n\t\t\t\t\tconsole.log()\n\t\t\t\t\tconsole.log(pc.dim(\"You can now use btcemail commands.\"))\n\t\t\t\t\tconsole.log(pc.dim(\"Run `btcemail --help` to see available commands.\"))\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tawait sleep(POLL_INTERVAL_MS)\n\t\t} catch (error) {\n\t\t\t// Network error, continue polling\n\t\t\tawait sleep(POLL_INTERVAL_MS)\n\t\t}\n\t}\n\n\tspinner.fail(\"Authentication timed out. Please try again.\")\n\tprocess.exit(1)\n}\n\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms))\n}\n","/**\n * CLI Configuration Management\n *\n * Stores auth tokens and user preferences in ~/.btcemail/config.json\n */\n\nimport Conf from \"conf\"\n\nexport type CLIConfig = {\n\tauth?: {\n\t\ttoken: string\n\t\texpiresAt: string\n\t\tuserId: string\n\t\temail: string\n\t}\n\tapi?: {\n\t\tbaseUrl: string\n\t}\n\tpreferences?: {\n\t\tuseCredits: boolean\n\t\tdefaultEditor: string\n\t}\n}\n\nconst config = new Conf<CLIConfig>({\n\tprojectName: \"btcemail\",\n\tschema: {\n\t\tauth: {\n\t\t\ttype: \"object\",\n\t\t\tproperties: {\n\t\t\t\ttoken: { type: \"string\" },\n\t\t\t\texpiresAt: { type: \"string\" },\n\t\t\t\tuserId: { type: \"string\" },\n\t\t\t\temail: { type: \"string\" },\n\t\t\t},\n\t\t},\n\t\tapi: {\n\t\t\ttype: \"object\",\n\t\t\tproperties: {\n\t\t\t\tbaseUrl: { type: \"string\" },\n\t\t\t},\n\t\t\tdefault: {\n\t\t\t\tbaseUrl: \"https://btc.email/api/v1\",\n\t\t\t},\n\t\t},\n\t\tpreferences: {\n\t\t\ttype: \"object\",\n\t\t\tproperties: {\n\t\t\t\tuseCredits: { type: \"boolean\" },\n\t\t\t\tdefaultEditor: { type: \"string\" },\n\t\t\t},\n\t\t\tdefault: {\n\t\t\t\tuseCredits: true,\n\t\t\t\tdefaultEditor: \"vim\",\n\t\t\t},\n\t\t},\n\t},\n})\n\nexport function getConfig(): CLIConfig {\n\treturn config.store\n}\n\nexport function getAuth(): CLIConfig[\"auth\"] | undefined {\n\treturn config.get(\"auth\")\n}\n\nexport function setAuth(auth: CLIConfig[\"auth\"]): void {\n\tconfig.set(\"auth\", auth)\n}\n\nexport function clearAuth(): void {\n\tconfig.delete(\"auth\")\n}\n\nexport function getApiBaseUrl(): string {\n\treturn (\n\t\tprocess.env.BTCEMAIL_API_URL ||\n\t\tconfig.get(\"api.baseUrl\") ||\n\t\t\"https://btc.email/api/v1\"\n\t)\n}\n\nexport function isAuthenticated(): boolean {\n\tconst auth = getAuth()\n\tif (!auth?.token || !auth?.expiresAt) {\n\t\treturn false\n\t}\n\n\t// Check if token is expired\n\tconst expiresAt = new Date(auth.expiresAt)\n\treturn expiresAt > new Date()\n}\n\n/**\n * Check if token is expiring soon (within 5 minutes)\n */\nexport function isTokenExpiringSoon(): boolean {\n\tconst auth = getAuth()\n\tif (!auth?.token || !auth?.expiresAt) {\n\t\treturn false\n\t}\n\n\tconst expiresAt = new Date(auth.expiresAt)\n\tconst fiveMinutesFromNow = new Date(Date.now() + 5 * 60 * 1000)\n\treturn expiresAt <= fiveMinutesFromNow && expiresAt > new Date()\n}\n\n/**\n * Get time until token expires in human-readable format\n */\nexport function getTokenExpiryInfo(): { expired: boolean; expiresIn?: string } {\n\tconst auth = getAuth()\n\tif (!auth?.token || !auth?.expiresAt) {\n\t\treturn { expired: true }\n\t}\n\n\tconst expiresAt = new Date(auth.expiresAt)\n\tconst now = new Date()\n\n\tif (expiresAt <= now) {\n\t\treturn { expired: true }\n\t}\n\n\tconst diffMs = expiresAt.getTime() - now.getTime()\n\tconst diffMins = Math.floor(diffMs / 60000)\n\tconst diffHours = Math.floor(diffMins / 60)\n\n\tif (diffHours > 0) {\n\t\treturn { expired: false, expiresIn: `${diffHours}h ${diffMins % 60}m` }\n\t}\n\treturn { expired: false, expiresIn: `${diffMins}m` }\n}\n\nexport function getToken(): string | null {\n\tconst auth = getAuth()\n\tif (!auth?.token) return null\n\n\t// Check if token is expired\n\tconst expiresAt = new Date(auth.expiresAt)\n\tif (expiresAt <= new Date()) {\n\t\tclearAuth()\n\t\treturn null\n\t}\n\n\treturn auth.token\n}\n\nexport { config }\n","/**\n * Logout Command\n *\n * Clears stored authentication tokens.\n */\n\nimport pc from \"picocolors\"\nimport { clearAuth, isAuthenticated } from \"../config.js\"\n\nexport async function logoutCommand(): Promise<void> {\n\tif (!isAuthenticated()) {\n\t\tconsole.log(pc.yellow(\"You are not logged in.\"))\n\t\treturn\n\t}\n\n\tclearAuth()\n\tconsole.log(pc.green(\"Successfully logged out.\"))\n}\n","/**\n * Whoami Command\n *\n * Shows current authenticated user info.\n */\n\nimport pc from \"picocolors\"\nimport {\n\tgetAuth,\n\tgetTokenExpiryInfo,\n\tisAuthenticated,\n\tisTokenExpiringSoon,\n} from \"../config.js\"\nimport { getWhoami } from \"../api.js\"\n\nexport async function whoamiCommand(): Promise<void> {\n\tif (!isAuthenticated()) {\n\t\tconsole.log(pc.yellow(\"Not logged in.\"))\n\t\tconsole.log(pc.dim(\"Run `btcemail login` to authenticate.\"))\n\t\tprocess.exit(1)\n\t}\n\n\tconst auth = getAuth()\n\tif (!auth) {\n\t\tconsole.log(pc.yellow(\"Not logged in.\"))\n\t\tprocess.exit(1)\n\t}\n\n\t// Try to get fresh info from API\n\tconst result = await getWhoami()\n\n\tif (result.ok) {\n\t\tconsole.log()\n\t\tconsole.log(pc.bold(\"Current User\"))\n\t\tconsole.log()\n\t\tconsole.log(` ${pc.dim(\"Email:\")} ${result.data.email}`)\n\t\tif (result.data.username) {\n\t\t\tconsole.log(` ${pc.dim(\"Username:\")} ${result.data.username}`)\n\t\t}\n\t\tconsole.log(` ${pc.dim(\"User ID:\")} ${result.data.id}`)\n\n\t\t// Show token expiry info\n\t\tconst expiryInfo = getTokenExpiryInfo()\n\t\tif (expiryInfo.expiresIn) {\n\t\t\tconst expiryColor = isTokenExpiringSoon() ? pc.yellow : pc.dim\n\t\t\tconsole.log(` ${pc.dim(\"Session:\")} ${expiryColor(`expires in ${expiryInfo.expiresIn}`)}`)\n\t\t}\n\t\tconsole.log()\n\n\t\tif (isTokenExpiringSoon()) {\n\t\t\tconsole.log(pc.yellow(\"Session expiring soon. Run `btcemail login` to refresh.\"))\n\t\t\tconsole.log()\n\t\t}\n\t} else {\n\t\t// Fall back to cached info\n\t\tconsole.log()\n\t\tconsole.log(pc.bold(\"Current User (cached)\"))\n\t\tconsole.log()\n\t\tconsole.log(` ${pc.dim(\"Email:\")} ${auth.email}`)\n\t\tconsole.log(` ${pc.dim(\"User ID:\")} ${auth.userId}`)\n\t\tconsole.log()\n\n\t\tif (result.error.code === \"UNAUTHENTICATED\" || result.error.code === \"UNAUTHORIZED\") {\n\t\t\tconsole.log(pc.yellow(\"Session expired. Run `btcemail login` to re-authenticate.\"))\n\t\t}\n\t}\n}\n","/**\n * API Client for btc.email CLI\n *\n * Handles all HTTP requests to the btc.email API.\n * v1 API returns: { success: true, data: {...}, meta: {...} }\n */\n\nimport { getApiBaseUrl, getToken } from \"./config.js\"\n\nexport type ApiResponse<T> =\n\t| { ok: true; data: T }\n\t| { ok: false; error: { code: string; message: string; details?: unknown } }\n\nexport type PaginatedResponse<T> = {\n\tdata: T[]\n\tpagination: {\n\t\ttotal: number\n\t\tlimit: number\n\t\toffset: number\n\t\thasMore: boolean\n\t}\n}\n\n/**\n * v1 API response format\n */\ntype V1ApiResponse<T> = {\n\tsuccess: boolean\n\tdata?: T\n\terror?: { code: string; message: string; details?: unknown }\n\tmeta?: { requestId: string; timestamp: string }\n\tpagination?: {\n\t\ttotal: number\n\t\tlimit: number\n\t\toffset: number\n\t\thasMore: boolean\n\t}\n}\n\n/**\n * Make an authenticated API request\n */\nexport async function apiRequest<T>(\n\tendpoint: string,\n\toptions: RequestInit = {},\n): Promise<ApiResponse<T>> {\n\tconst token = getToken()\n\tconst baseUrl = getApiBaseUrl()\n\n\tconst headers: HeadersInit = {\n\t\t\"Content-Type\": \"application/json\",\n\t\t...options.headers,\n\t}\n\n\tif (token) {\n\t\theaders.Authorization = `Bearer ${token}`\n\t}\n\n\ttry {\n\t\tconst response = await fetch(`${baseUrl}${endpoint}`, {\n\t\t\t...options,\n\t\t\theaders,\n\t\t})\n\n\t\tconst json = (await response.json()) as V1ApiResponse<T>\n\n\t\t// Handle v1 API format: { success: boolean, data?: T, error?: {...} }\n\t\tif (!response.ok || json.success === false) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: json.error || {\n\t\t\t\t\tcode: \"UNKNOWN_ERROR\",\n\t\t\t\t\tmessage: `Request failed with status ${response.status}`,\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\n\t\t// For paginated responses, include pagination in the result\n\t\tif (json.pagination) {\n\t\t\treturn {\n\t\t\t\tok: true,\n\t\t\t\tdata: { data: json.data, pagination: json.pagination } as T,\n\t\t\t}\n\t\t}\n\n\t\t// Extract data from v1 response format\n\t\treturn { ok: true, data: json.data as T }\n\t} catch (error) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: {\n\t\t\t\tcode: \"NETWORK_ERROR\",\n\t\t\t\tmessage:\n\t\t\t\t\terror instanceof Error ? error.message : \"Network request failed\",\n\t\t\t},\n\t\t}\n\t}\n}\n\n/**\n * Make an unauthenticated API request (for login, etc.)\n */\nexport async function publicRequest<T>(\n\tendpoint: string,\n\toptions: RequestInit = {},\n): Promise<ApiResponse<T>> {\n\tconst baseUrl = getApiBaseUrl()\n\n\ttry {\n\t\tconst response = await fetch(`${baseUrl}${endpoint}`, {\n\t\t\t...options,\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t...options.headers,\n\t\t\t},\n\t\t})\n\n\t\tconst json = (await response.json()) as V1ApiResponse<T>\n\n\t\tif (!response.ok || json.success === false) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: json.error || {\n\t\t\t\t\tcode: \"UNKNOWN_ERROR\",\n\t\t\t\t\tmessage: `Request failed with status ${response.status}`,\n\t\t\t\t},\n\t\t\t}\n\t\t}\n\n\t\treturn { ok: true, data: json.data as T }\n\t} catch (error) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\terror: {\n\t\t\t\tcode: \"NETWORK_ERROR\",\n\t\t\t\tmessage:\n\t\t\t\t\terror instanceof Error ? error.message : \"Network request failed\",\n\t\t\t},\n\t\t}\n\t}\n}\n\n// API endpoint helpers\n\nexport async function getEmails(options: {\n\tfolder?: string\n\tlimit?: number\n\toffset?: number\n\tsearch?: string\n}) {\n\tconst params = new URLSearchParams()\n\tif (options.folder) params.set(\"folder\", options.folder)\n\tif (options.limit) params.set(\"limit\", String(options.limit))\n\tif (options.offset) params.set(\"offset\", String(options.offset))\n\tif (options.search) params.set(\"search\", options.search)\n\n\treturn apiRequest<PaginatedResponse<Email>>(`/email?${params}`)\n}\n\nexport async function getEmail(id: string) {\n\treturn apiRequest<Email>(`/email/${id}`)\n}\n\nexport async function getCreditsBalance() {\n\treturn apiRequest<CreditBalance>(\"/credits/balance\")\n}\n\nexport type CreditBalance = {\n\tbalanceSats: number\n\tlifetimePurchasedSats: number\n\tlifetimeSpentSats: number\n\tlastPurchaseAt: string | null\n}\n\nexport async function getWhoami() {\n\treturn apiRequest<{ id: string; email: string; username?: string }>(\n\t\t\"/auth/whoami\",\n\t)\n}\n\n// Pending/Inbound email functions\n\nexport async function getPendingEmails(options: { limit?: number; offset?: number } = {}) {\n\tconst params = new URLSearchParams()\n\tif (options.limit) params.set(\"limit\", String(options.limit))\n\tif (options.offset) params.set(\"offset\", String(options.offset))\n\n\treturn apiRequest<PaginatedResponse<PendingEmail>>(`/inbound/pending?${params}`)\n}\n\nexport async function getPendingEmail(id: string) {\n\treturn apiRequest<PendingEmailDetail>(`/inbound/${id}`)\n}\n\nexport async function payForEmail(id: string, paymentHash: string) {\n\treturn apiRequest<{ emailId: string; status: string }>(`/inbound/${id}/pay`, {\n\t\tmethod: \"POST\",\n\t\tbody: JSON.stringify({ paymentHash }),\n\t})\n}\n\n// Send email functions\n\nexport async function sendEmail(options: {\n\tto: string[]\n\tsubject: string\n\tbody: string\n\tbodyText?: string\n\tfromEmail: string\n\tpaymentHash?: string\n}) {\n\treturn apiRequest<{ emailId: string; status: string; messageId?: string }>(\"/email/send\", {\n\t\tmethod: \"POST\",\n\t\tbody: JSON.stringify(options),\n\t})\n}\n\nexport async function getL402Invoice(options: {\n\tfromEmail: string\n\ttoEmails: string[]\n\tamountSats?: number\n}) {\n\treturn apiRequest<L402Invoice>(\"/l402/invoice\", {\n\t\tmethod: \"POST\",\n\t\tbody: JSON.stringify({\n\t\t\tsender_email: options.fromEmail,\n\t\t\trecipient_emails: options.toEmails,\n\t\t\tamount_sats: options.amountSats,\n\t\t}),\n\t})\n}\n\n// Types\n\nexport type Email = {\n\tid: string\n\tsubject: string\n\tfrom: { email: string; name?: string }\n\tto: { email: string; name?: string }[]\n\tsnippet: string\n\tbody?: string\n\tbodyText?: string\n\tdate: string\n\tunread: boolean\n\tlabels: string[]\n}\n\nexport type PendingEmail = {\n\tid: string\n\tsubject: string\n\tfrom: { email: string; name?: string }\n\tamountSats: number\n\tcreatedAt: string\n}\n\nexport type PendingEmailDetail = {\n\tid: string\n\tsubject: string\n\tfrom: { email: string; name?: string }\n\tbody: string\n\tbodyText: string\n\tamountSats: number\n\tcreatedAt: string\n\tpaymentHash: string | null\n}\n\nexport type L402Invoice = {\n\tinvoice: string\n\tpaymentHash: string\n\tamountSats: number\n\texpiresAt: string\n}\n","/**\n * List Command\n *\n * Lists emails with pagination and filtering.\n */\n\nimport pc from \"picocolors\"\nimport { isAuthenticated } from \"../config.js\"\nimport { getEmails, type Email } from \"../api.js\"\n\nexport type ListOptions = {\n\tfolder?: string\n\tlimit?: number\n\tjson?: boolean\n}\n\nexport async function listCommand(options: ListOptions): Promise<void> {\n\tif (!isAuthenticated()) {\n\t\tconsole.log(pc.yellow(\"Not logged in.\"))\n\t\tconsole.log(pc.dim(\"Run `btcemail login` to authenticate.\"))\n\t\tprocess.exit(1)\n\t}\n\n\tconst result = await getEmails({\n\t\tfolder: options.folder || \"inbox\",\n\t\tlimit: options.limit || 20,\n\t})\n\n\tif (!result.ok) {\n\t\tconsole.error(pc.red(`Error: ${result.error.message}`))\n\t\tprocess.exit(1)\n\t}\n\n\tconst { data: emails, pagination } = result.data\n\n\t// JSON output mode\n\tif (options.json) {\n\t\tconsole.log(JSON.stringify({ emails, pagination }, null, 2))\n\t\treturn\n\t}\n\n\t// Table output\n\tconsole.log()\n\tconsole.log(\n\t\tpc.bold(`${options.folder || \"Inbox\"} (${pagination.total} emails)`),\n\t)\n\tconsole.log()\n\n\tif (emails.length === 0) {\n\t\tconsole.log(pc.dim(\" No emails found.\"))\n\t\tconsole.log()\n\t\treturn\n\t}\n\n\t// Header\n\tconsole.log(\n\t\t` ${pc.dim(\"ID\".padEnd(12))} ${pc.dim(\"From\".padEnd(25))} ${pc.dim(\"Subject\".padEnd(40))} ${pc.dim(\"Date\")}`,\n\t)\n\tconsole.log(pc.dim(\" \" + \"-\".repeat(90)))\n\n\t// Rows\n\tfor (const email of emails) {\n\t\tconst unreadMarker = email.unread ? pc.cyan(\"*\") : \" \"\n\t\tconst id = email.id.slice(0, 10).padEnd(12)\n\t\tconst from = truncate(email.from.name || email.from.email, 23).padEnd(25)\n\t\tconst subject = truncate(email.subject, 38).padEnd(40)\n\t\tconst date = formatDate(email.date)\n\n\t\tconsole.log(`${unreadMarker} ${id} ${from} ${subject} ${pc.dim(date)}`)\n\t}\n\n\tconsole.log()\n\n\tif (pagination.hasMore) {\n\t\tconsole.log(\n\t\t\tpc.dim(\n\t\t\t\t` Showing ${emails.length} of ${pagination.total}. Use --limit to see more.`,\n\t\t\t),\n\t\t)\n\t\tconsole.log()\n\t}\n}\n\nfunction truncate(str: string, maxLength: number): string {\n\tif (str.length <= maxLength) return str\n\treturn str.slice(0, maxLength - 1) + \"…\"\n}\n\nfunction formatDate(dateString: string): string {\n\tconst date = new Date(dateString)\n\tconst now = new Date()\n\tconst diffMs = now.getTime() - date.getTime()\n\tconst diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24))\n\n\tif (diffDays === 0) {\n\t\treturn date.toLocaleTimeString(\"en-US\", {\n\t\t\thour: \"numeric\",\n\t\t\tminute: \"2-digit\",\n\t\t})\n\t}\n\n\tif (diffDays < 7) {\n\t\treturn date.toLocaleDateString(\"en-US\", { weekday: \"short\" })\n\t}\n\n\treturn date.toLocaleDateString(\"en-US\", {\n\t\tmonth: \"short\",\n\t\tday: \"numeric\",\n\t})\n}\n","/**\n * Read Command\n *\n * Display a single email by ID.\n */\n\nimport pc from \"picocolors\"\nimport { isAuthenticated } from \"../config.js\"\nimport { getEmail, type Email } from \"../api.js\"\n\nexport type ReadOptions = {\n\tjson?: boolean\n}\n\nexport async function readCommand(id: string, options: ReadOptions): Promise<void> {\n\tif (!isAuthenticated()) {\n\t\tconsole.log(pc.yellow(\"Not logged in.\"))\n\t\tconsole.log(pc.dim(\"Run `btcemail login` to authenticate.\"))\n\t\tprocess.exit(1)\n\t}\n\n\tconst result = await getEmail(id)\n\n\tif (!result.ok) {\n\t\tif (result.error.code === \"NOT_FOUND\") {\n\t\t\tconsole.error(pc.red(`Email not found: ${id}`))\n\t\t} else {\n\t\t\tconsole.error(pc.red(`Error: ${result.error.message}`))\n\t\t}\n\t\tprocess.exit(1)\n\t}\n\n\tconst email = result.data\n\n\t// JSON output mode\n\tif (options.json) {\n\t\tconsole.log(JSON.stringify(email, null, 2))\n\t\treturn\n\t}\n\n\t// Formatted output\n\tconsole.log()\n\tconsole.log(pc.bold(email.subject || \"(No subject)\"))\n\tconsole.log()\n\tconsole.log(`${pc.dim(\"From:\")} ${formatAddress(email.from)}`)\n\tconsole.log(`${pc.dim(\"To:\")} ${email.to.map(formatAddress).join(\", \")}`)\n\tconsole.log(`${pc.dim(\"Date:\")} ${formatFullDate(email.date)}`)\n\tconsole.log(`${pc.dim(\"ID:\")} ${email.id}`)\n\n\tif (email.labels.length > 0) {\n\t\tconsole.log(`${pc.dim(\"Labels:\")} ${email.labels.join(\", \")}`)\n\t}\n\n\tconsole.log()\n\tconsole.log(pc.dim(\"-\".repeat(60)))\n\tconsole.log()\n\n\t// Show full body if available, otherwise fall back to snippet\n\tconst content = email.bodyText || email.body || email.snippet\n\tif (content) {\n\t\t// Strip HTML tags for plain text display if showing HTML body\n\t\tconst displayContent = email.bodyText || stripHtml(email.body || email.snippet)\n\t\tconsole.log(displayContent)\n\t} else {\n\t\tconsole.log(pc.dim(\"(No content)\"))\n\t}\n\tconsole.log()\n}\n\nfunction stripHtml(html: string): string {\n\t// Basic HTML stripping for terminal display\n\treturn html\n\t\t.replace(/<br\\s*\\/?>/gi, \"\\n\")\n\t\t.replace(/<\\/p>/gi, \"\\n\\n\")\n\t\t.replace(/<\\/div>/gi, \"\\n\")\n\t\t.replace(/<[^>]*>/g, \"\")\n\t\t.replace(/&nbsp;/g, \" \")\n\t\t.replace(/&lt;/g, \"<\")\n\t\t.replace(/&gt;/g, \">\")\n\t\t.replace(/&amp;/g, \"&\")\n\t\t.replace(/&quot;/g, '\"')\n\t\t.trim()\n}\n\nfunction formatAddress(addr: { email: string; name?: string }): string {\n\tif (addr.name) {\n\t\treturn `${addr.name} <${addr.email}>`\n\t}\n\treturn addr.email\n}\n\nfunction formatFullDate(dateString: string): string {\n\tconst date = new Date(dateString)\n\treturn date.toLocaleString(\"en-US\", {\n\t\tweekday: \"short\",\n\t\tyear: \"numeric\",\n\t\tmonth: \"short\",\n\t\tday: \"numeric\",\n\t\thour: \"numeric\",\n\t\tminute: \"2-digit\",\n\t})\n}\n","/**\n * Credits Command\n *\n * Shows credit balance and transaction history.\n */\n\nimport pc from \"picocolors\"\nimport { isAuthenticated } from \"../config.js\"\nimport { getCreditsBalance } from \"../api.js\"\n\nexport async function creditsBalanceCommand(): Promise<void> {\n\tif (!isAuthenticated()) {\n\t\tconsole.log(pc.yellow(\"Not logged in.\"))\n\t\tconsole.log(pc.dim(\"Run `btcemail login` to authenticate.\"))\n\t\tprocess.exit(1)\n\t}\n\n\tconst result = await getCreditsBalance()\n\n\tif (!result.ok) {\n\t\tconsole.error(pc.red(`Error: ${result.error.message}`))\n\t\tprocess.exit(1)\n\t}\n\n\tconst { balanceSats } = result.data\n\n\tconsole.log()\n\tconsole.log(pc.bold(\"Credit Balance\"))\n\tconsole.log()\n\tconsole.log(` ${pc.green(formatSats(balanceSats))} sats`)\n\tconsole.log()\n\n\tif (balanceSats === 0) {\n\t\tconsole.log(pc.dim(\" No credits available.\"))\n\t\tconsole.log(pc.dim(\" Purchase credits at https://btc.email/payments\"))\n\t\tconsole.log()\n\t}\n}\n\nfunction formatSats(sats: number): string {\n\treturn sats.toLocaleString()\n}\n","/**\n * Send Command\n *\n * Compose and send an email with L402 payment flow.\n */\n\nimport pc from \"picocolors\"\nimport { isAuthenticated, getAuth } from \"../config.js\"\nimport { sendEmail, getL402Invoice, getWhoami } from \"../api.js\"\n\nexport type SendOptions = {\n\tto?: string\n\tsubject?: string\n\tbody?: string\n\tfromEmail?: string\n}\n\nexport async function sendCommand(options: SendOptions): Promise<void> {\n\tif (!isAuthenticated()) {\n\t\tconsole.log(pc.yellow(\"Not logged in.\"))\n\t\tconsole.log(pc.dim(\"Run `btcemail login` to authenticate.\"))\n\t\tprocess.exit(1)\n\t}\n\n\t// Get user's email address\n\tlet fromEmail = options.fromEmail\n\tif (!fromEmail) {\n\t\tconst whoamiResult = await getWhoami()\n\t\tif (whoamiResult.ok && whoamiResult.data.username) {\n\t\t\tfromEmail = `${whoamiResult.data.username}@btc.email`\n\t\t} else {\n\t\t\tconst auth = getAuth()\n\t\t\tif (auth?.email?.endsWith(\"@btc.email\")) {\n\t\t\t\tfromEmail = auth.email\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!fromEmail || !fromEmail.endsWith(\"@btc.email\")) {\n\t\tconsole.error(pc.red(\"No @btc.email address found.\"))\n\t\tconsole.log(pc.dim(\"Register a username at https://btc.email\"))\n\t\tprocess.exit(1)\n\t}\n\n\t// Validate required fields\n\tif (!options.to) {\n\t\tconsole.error(pc.red(\"Recipient is required. Use --to <email>\"))\n\t\tprocess.exit(1)\n\t}\n\n\tif (!options.subject) {\n\t\tconsole.error(pc.red(\"Subject is required. Use --subject <text>\"))\n\t\tprocess.exit(1)\n\t}\n\n\tif (!options.body) {\n\t\tconsole.error(pc.red(\"Body is required. Use --body <text>\"))\n\t\tprocess.exit(1)\n\t}\n\n\tconst toEmails = options.to.split(\",\").map((e) => e.trim())\n\n\tconsole.log()\n\tconsole.log(pc.bold(\"Sending Email\"))\n\tconsole.log()\n\tconsole.log(`${pc.dim(\"From:\")} ${fromEmail}`)\n\tconsole.log(`${pc.dim(\"To:\")} ${toEmails.join(\", \")}`)\n\tconsole.log(`${pc.dim(\"Subject:\")} ${options.subject}`)\n\tconsole.log()\n\n\t// Get L402 invoice to check if payment is required\n\tconst invoiceResult = await getL402Invoice({\n\t\tfromEmail,\n\t\ttoEmails,\n\t})\n\n\tif (invoiceResult.ok && invoiceResult.data.amountSats > 0) {\n\t\t// Payment required\n\t\tconst invoice = invoiceResult.data\n\t\tconsole.log(pc.yellow(`Payment required: ${invoice.amountSats} sats`))\n\t\tconsole.log()\n\t\tconsole.log(pc.dim(\"Lightning Invoice:\"))\n\t\tconsole.log(pc.cyan(invoice.invoice))\n\t\tconsole.log()\n\t\tconsole.log(pc.dim(\"Pay this invoice with your Lightning wallet, then run:\"))\n\t\tconsole.log(\n\t\t\tpc.cyan(\n\t\t\t\t` btcemail send --to \"${options.to}\" --subject \"${options.subject}\" --body \"${options.body}\" --payment-hash ${invoice.paymentHash}`\n\t\t\t)\n\t\t)\n\t\tconsole.log()\n\t\treturn\n\t}\n\n\t// No payment required or payment hash provided - send email\n\tconst result = await sendEmail({\n\t\tto: toEmails,\n\t\tsubject: options.subject,\n\t\tbody: options.body,\n\t\tfromEmail,\n\t})\n\n\tif (!result.ok) {\n\t\tif (result.error.code === \"PAYMENT_REQUIRED\") {\n\t\t\tconsole.error(pc.yellow(\"Payment required to send this email.\"))\n\t\t\tconsole.log(pc.dim(\"Use the invoice above to pay, then include --payment-hash\"))\n\t\t} else {\n\t\t\tconsole.error(pc.red(`Error: ${result.error.message}`))\n\t\t}\n\t\tprocess.exit(1)\n\t}\n\n\tconsole.log(pc.green(\"Email sent successfully!\"))\n\tconsole.log()\n\tconsole.log(`${pc.dim(\"Email ID:\")} ${result.data.emailId}`)\n\tif (result.data.messageId) {\n\t\tconsole.log(`${pc.dim(\"Message ID:\")} ${result.data.messageId}`)\n\t}\n\tconsole.log()\n}\n\nexport type SendWithPaymentOptions = SendOptions & {\n\tpaymentHash?: string\n}\n\nexport async function sendWithPaymentCommand(options: SendWithPaymentOptions): Promise<void> {\n\tif (!isAuthenticated()) {\n\t\tconsole.log(pc.yellow(\"Not logged in.\"))\n\t\tconsole.log(pc.dim(\"Run `btcemail login` to authenticate.\"))\n\t\tprocess.exit(1)\n\t}\n\n\t// Get user's email address\n\tlet fromEmail = options.fromEmail\n\tif (!fromEmail) {\n\t\tconst whoamiResult = await getWhoami()\n\t\tif (whoamiResult.ok && whoamiResult.data.username) {\n\t\t\tfromEmail = `${whoamiResult.data.username}@btc.email`\n\t\t}\n\t}\n\n\tif (!fromEmail || !fromEmail.endsWith(\"@btc.email\")) {\n\t\tconsole.error(pc.red(\"No @btc.email address found.\"))\n\t\tprocess.exit(1)\n\t}\n\n\tif (!options.to || !options.subject || !options.body) {\n\t\tconsole.error(pc.red(\"Missing required fields: --to, --subject, --body\"))\n\t\tprocess.exit(1)\n\t}\n\n\tconst toEmails = options.to.split(\",\").map((e) => e.trim())\n\n\tconst result = await sendEmail({\n\t\tto: toEmails,\n\t\tsubject: options.subject,\n\t\tbody: options.body,\n\t\tfromEmail,\n\t\tpaymentHash: options.paymentHash,\n\t})\n\n\tif (!result.ok) {\n\t\tconsole.error(pc.red(`Error: ${result.error.message}`))\n\t\tprocess.exit(1)\n\t}\n\n\tconsole.log()\n\tconsole.log(pc.green(\"Email sent successfully!\"))\n\tconsole.log()\n\tconsole.log(`${pc.dim(\"Email ID:\")} ${result.data.emailId}`)\n\tconsole.log()\n}\n","/**\n * Inbound Commands\n *\n * View and pay for pending emails that require payment.\n */\n\nimport pc from \"picocolors\"\nimport { isAuthenticated } from \"../config.js\"\nimport {\n\tgetPendingEmails,\n\tgetPendingEmail,\n\tpayForEmail,\n\ttype PendingEmail,\n} from \"../api.js\"\n\nexport type InboundListOptions = {\n\tlimit?: number\n\tjson?: boolean\n}\n\nexport async function inboundPendingCommand(options: InboundListOptions): Promise<void> {\n\tif (!isAuthenticated()) {\n\t\tconsole.log(pc.yellow(\"Not logged in.\"))\n\t\tconsole.log(pc.dim(\"Run `btcemail login` to authenticate.\"))\n\t\tprocess.exit(1)\n\t}\n\n\tconst result = await getPendingEmails({\n\t\tlimit: options.limit || 20,\n\t})\n\n\tif (!result.ok) {\n\t\tconsole.error(pc.red(`Error: ${result.error.message}`))\n\t\tprocess.exit(1)\n\t}\n\n\tconst { data: emails, pagination } = result.data\n\n\t// JSON output mode\n\tif (options.json) {\n\t\tconsole.log(JSON.stringify({ emails, pagination }, null, 2))\n\t\treturn\n\t}\n\n\tconsole.log()\n\tconsole.log(pc.bold(`Pending Emails (${pagination.total} awaiting payment)`))\n\tconsole.log()\n\n\tif (emails.length === 0) {\n\t\tconsole.log(pc.dim(\" No pending emails.\"))\n\t\tconsole.log()\n\t\treturn\n\t}\n\n\t// Header\n\tconsole.log(\n\t\t` ${pc.dim(\"ID\".padEnd(12))} ${pc.dim(\"From\".padEnd(30))} ${pc.dim(\"Subject\".padEnd(30))} ${pc.dim(\"Sats\")}`\n\t)\n\tconsole.log(pc.dim(\" \" + \"-\".repeat(85)))\n\n\t// Rows\n\tfor (const email of emails) {\n\t\tconst id = email.id.slice(0, 10).padEnd(12)\n\t\tconst from = truncate(email.from.name || email.from.email, 28).padEnd(30)\n\t\tconst subject = truncate(email.subject, 28).padEnd(30)\n\t\tconst sats = pc.yellow(String(email.amountSats))\n\n\t\tconsole.log(` ${id} ${from} ${subject} ${sats}`)\n\t}\n\n\tconsole.log()\n\n\tif (pagination.hasMore) {\n\t\tconsole.log(\n\t\t\tpc.dim(` Showing ${emails.length} of ${pagination.total}. Use --limit to see more.`)\n\t\t)\n\t\tconsole.log()\n\t}\n\n\tconsole.log(pc.dim(\" Use `btcemail inbound view <id>` to see details and payment invoice.\"))\n\tconsole.log()\n}\n\nexport type InboundViewOptions = {\n\tjson?: boolean\n}\n\nexport async function inboundViewCommand(id: string, options: InboundViewOptions): Promise<void> {\n\tif (!isAuthenticated()) {\n\t\tconsole.log(pc.yellow(\"Not logged in.\"))\n\t\tconsole.log(pc.dim(\"Run `btcemail login` to authenticate.\"))\n\t\tprocess.exit(1)\n\t}\n\n\tconst result = await getPendingEmail(id)\n\n\tif (!result.ok) {\n\t\tif (result.error.code === \"NOT_FOUND\") {\n\t\t\tconsole.error(pc.red(`Pending email not found: ${id}`))\n\t\t} else {\n\t\t\tconsole.error(pc.red(`Error: ${result.error.message}`))\n\t\t}\n\t\tprocess.exit(1)\n\t}\n\n\tconst email = result.data\n\n\t// JSON output mode\n\tif (options.json) {\n\t\tconsole.log(JSON.stringify(email, null, 2))\n\t\treturn\n\t}\n\n\t// Formatted output\n\tconsole.log()\n\tconsole.log(pc.bold(email.subject || \"(No subject)\"))\n\tconsole.log()\n\tconsole.log(`${pc.dim(\"From:\")} ${formatAddress(email.from)}`)\n\tconsole.log(`${pc.dim(\"Date:\")} ${formatFullDate(email.createdAt)}`)\n\tconsole.log(`${pc.dim(\"ID:\")} ${email.id}`)\n\tconsole.log()\n\tconsole.log(pc.yellow(`Payment Required: ${email.amountSats} sats`))\n\tconsole.log()\n\n\tif (email.paymentHash) {\n\t\tconsole.log(pc.dim(\"To receive this email, pay the invoice and run:\"))\n\t\tconsole.log(pc.cyan(` btcemail inbound pay ${email.id} --payment-hash <preimage>`))\n\t} else {\n\t\tconsole.log(pc.dim(\"Payment information not available.\"))\n\t}\n\n\tconsole.log()\n\tconsole.log(pc.dim(\"-\".repeat(60)))\n\tconsole.log()\n\tconsole.log(pc.dim(\"Preview (full content available after payment):\"))\n\tconsole.log()\n\tconsole.log(truncate(email.bodyText || email.body, 200))\n\tconsole.log()\n}\n\nexport type InboundPayOptions = {\n\tpaymentHash: string\n}\n\nexport async function inboundPayCommand(id: string, options: InboundPayOptions): Promise<void> {\n\tif (!isAuthenticated()) {\n\t\tconsole.log(pc.yellow(\"Not logged in.\"))\n\t\tconsole.log(pc.dim(\"Run `btcemail login` to authenticate.\"))\n\t\tprocess.exit(1)\n\t}\n\n\tif (!options.paymentHash) {\n\t\tconsole.error(pc.red(\"Payment hash is required. Use --payment-hash <preimage>\"))\n\t\tprocess.exit(1)\n\t}\n\n\tconsole.log()\n\tconsole.log(pc.dim(\"Verifying payment...\"))\n\n\tconst result = await payForEmail(id, options.paymentHash)\n\n\tif (!result.ok) {\n\t\tif (result.error.code === \"PAYMENT_INVALID\") {\n\t\t\tconsole.error(pc.red(\"Payment verification failed.\"))\n\t\t\tconsole.log(pc.dim(\"Make sure you paid the correct invoice and provided the preimage.\"))\n\t\t} else if (result.error.code === \"NOT_FOUND\") {\n\t\t\tconsole.error(pc.red(`Pending email not found or already delivered: ${id}`))\n\t\t} else {\n\t\t\tconsole.error(pc.red(`Error: ${result.error.message}`))\n\t\t}\n\t\tprocess.exit(1)\n\t}\n\n\tconsole.log()\n\tconsole.log(pc.green(\"Payment verified! Email delivered.\"))\n\tconsole.log()\n\tconsole.log(`${pc.dim(\"Email ID:\")} ${result.data.emailId}`)\n\tconsole.log(`${pc.dim(\"Status:\")} ${result.data.status}`)\n\tconsole.log()\n\tconsole.log(pc.dim(\"Run `btcemail read \" + result.data.emailId + \"` to read the email.\"))\n\tconsole.log()\n}\n\nfunction truncate(str: string, maxLength: number): string {\n\tif (str.length <= maxLength) return str\n\treturn str.slice(0, maxLength - 1) + \"…\"\n}\n\nfunction formatAddress(addr: { email: string; name?: string }): string {\n\tif (addr.name) {\n\t\treturn `${addr.name} <${addr.email}>`\n\t}\n\treturn addr.email\n}\n\nfunction formatFullDate(dateString: string): string {\n\tconst date = new Date(dateString)\n\treturn date.toLocaleString(\"en-US\", {\n\t\tweekday: \"short\",\n\t\tyear: \"numeric\",\n\t\tmonth: \"short\",\n\t\tday: \"numeric\",\n\t\thour: \"numeric\",\n\t\tminute: \"2-digit\",\n\t})\n}\n"],"mappings":"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","names":["pc","pc","pc","pc","pc","pc","pc","pc","pc","pc","pc","pc","pc","pc","pc","truncate","formatAddress","formatFullDate","pc"]}
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@btcemail/cli",
3
+ "version": "0.1.0",
4
+ "description": "btc.email CLI - Spam-free email powered by Bitcoin Lightning",
5
+ "type": "module",
6
+ "bin": {
7
+ "btcemail": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "README.md"
12
+ ],
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
16
+ "scripts": {
17
+ "build": "tsup",
18
+ "dev": "tsup --watch",
19
+ "start": "node dist/index.js",
20
+ "typecheck": "tsc --noEmit"
21
+ },
22
+ "keywords": [
23
+ "btc.email",
24
+ "bitcoin",
25
+ "lightning",
26
+ "email",
27
+ "cli"
28
+ ],
29
+ "author": "btc.email",
30
+ "license": "MIT",
31
+ "homepage": "https://btc.email",
32
+ "bugs": {
33
+ "url": "https://github.com/btcemail/btcemail/issues"
34
+ },
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/btcemail/btcemail.git",
38
+ "directory": "packages/cli"
39
+ },
40
+ "dependencies": {
41
+ "commander": "^12.1.0",
42
+ "conf": "^13.0.1",
43
+ "open": "^10.1.0",
44
+ "ora": "^8.1.1",
45
+ "picocolors": "^1.1.1"
46
+ },
47
+ "devDependencies": {
48
+ "@types/node": "^22.10.2",
49
+ "tsup": "^8.3.5",
50
+ "typescript": "^5.7.2"
51
+ },
52
+ "engines": {
53
+ "node": ">=18"
54
+ }
55
+ }