@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 +170 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +797 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -0
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
|
package/dist/index.d.ts
ADDED
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(/ /g, " ").replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&").replace(/"/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(/ /g, \" \")\n\t\t.replace(/</g, \"<\")\n\t\t.replace(/>/g, \">\")\n\t\t.replace(/&/g, \"&\")\n\t\t.replace(/"/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
|
+
}
|