run402 1.38.0 → 1.40.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/lib/agent.mjs +16 -15
- package/lib/ai.mjs +21 -33
- package/lib/allowance.mjs +41 -16
- package/lib/apps.mjs +71 -79
- package/lib/auth.mjs +27 -66
- package/lib/billing.mjs +37 -47
- package/lib/blob.mjs +37 -35
- package/lib/contracts.mjs +82 -193
- package/lib/domains.mjs +30 -34
- package/lib/email.mjs +94 -344
- package/lib/functions.mjs +77 -79
- package/lib/image.mjs +13 -14
- package/lib/message.mjs +11 -10
- package/lib/projects.mjs +121 -90
- package/lib/sdk-errors.mjs +45 -0
- package/lib/sdk.mjs +14 -0
- package/lib/secrets.mjs +18 -26
- package/lib/sender-domain.mjs +28 -45
- package/lib/service.mjs +16 -19
- package/lib/sites.mjs +24 -20
- package/lib/subdomains.mjs +22 -29
- package/lib/tier.mjs +10 -24
- package/lib/webhooks.mjs +32 -129
- package/package.json +1 -1
- package/lib/paid-fetch.mjs +0 -107
package/lib/email.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveProjectId } from "./config.mjs";
|
|
2
|
+
import { getSdk } from "./sdk.mjs";
|
|
3
|
+
import { reportSdkError } from "./sdk-errors.mjs";
|
|
2
4
|
|
|
3
5
|
const HELP = `run402 email — Send emails from your project
|
|
4
6
|
|
|
@@ -44,17 +46,11 @@ Examples:
|
|
|
44
46
|
run402 email create my-app
|
|
45
47
|
run402 email send --template project_invite --to user@example.com \\
|
|
46
48
|
--var project_name="My App" --var invite_url="https://example.com/invite/abc"
|
|
47
|
-
run402 email send --template project_invite --to user@example.com \\
|
|
48
|
-
--vars '{"project_name":"My App","invite_url":"https://example.com/invite/abc"}'
|
|
49
49
|
run402 email send --to user@example.com --subject "Welcome!" \\
|
|
50
|
-
--html "<h1>Hello</h1
|
|
51
|
-
run402 email send --template notification --to admin@example.com \\
|
|
52
|
-
--var project_name="My App" --var message="Deploy complete"
|
|
50
|
+
--html "<h1>Hello</h1>" --from-name "My App"
|
|
53
51
|
run402 email list --limit 50
|
|
54
|
-
run402 email list --limit 50 --after msg_abc123
|
|
55
52
|
run402 email info
|
|
56
53
|
run402 email get msg_abc123
|
|
57
|
-
run402 email get-raw msg_abc123 --output reply.eml
|
|
58
54
|
run402 email reply msg_abc123 --html "<p>Thanks!</p>"
|
|
59
55
|
run402 email delete --confirm
|
|
60
56
|
run402 email webhooks list
|
|
@@ -64,7 +60,6 @@ Notes:
|
|
|
64
60
|
- One mailbox per project
|
|
65
61
|
- Single recipient per send (no CC/BCC)
|
|
66
62
|
- Slug: 3-63 chars, lowercase alphanumeric + hyphens, no consecutive hyphens
|
|
67
|
-
- Rate limits vary by tier (prototype: 10/day, hobby: 50/day, team: 500/day)
|
|
68
63
|
- --project defaults to the active project
|
|
69
64
|
`;
|
|
70
65
|
|
|
@@ -80,99 +75,33 @@ Options:
|
|
|
80
75
|
--to <email> Recipient email address (required; single recipient)
|
|
81
76
|
--template <name> Template name (template mode): project_invite, magic_link,
|
|
82
77
|
notification
|
|
83
|
-
--var key=value Template variable (repeatable
|
|
84
|
-
template)
|
|
78
|
+
--var key=value Template variable (repeatable)
|
|
85
79
|
--vars '<json>' All template variables as a single JSON object
|
|
86
|
-
(alternative to multiple --var). Later --var overrides.
|
|
87
80
|
--subject "..." Subject line (raw HTML mode; required with --html)
|
|
88
81
|
--html "..." HTML body (raw HTML mode; required with --subject)
|
|
89
82
|
--text "..." Plain-text body (raw HTML mode; optional)
|
|
90
83
|
--from-name "..." Display name for the From header
|
|
91
84
|
--project <id> Project ID (defaults to the active project)
|
|
92
|
-
|
|
93
|
-
Templates:
|
|
94
|
-
project_invite project_name, invite_url
|
|
95
|
-
magic_link project_name, link_url, expires_in
|
|
96
|
-
notification project_name, message (max 500 chars)
|
|
97
|
-
|
|
98
|
-
Examples:
|
|
99
|
-
run402 email send --template project_invite --to user@example.com \\
|
|
100
|
-
--var project_name="My App" --var invite_url="https://example.com/invite/abc"
|
|
101
|
-
run402 email send --template project_invite --to user@example.com \\
|
|
102
|
-
--vars '{"project_name":"My App","invite_url":"https://example.com/invite/abc"}'
|
|
103
|
-
run402 email send --to user@example.com --subject "Welcome!" \\
|
|
104
|
-
--html "<h1>Hello</h1><p>Welcome aboard.</p>" --from-name "My App"
|
|
105
85
|
`,
|
|
106
86
|
list: `run402 email list — List messages in the mailbox
|
|
107
87
|
|
|
108
88
|
Usage:
|
|
109
89
|
run402 email list [--limit <n>] [--after <cursor>] [--project <id>]
|
|
110
|
-
|
|
111
|
-
Options:
|
|
112
|
-
--limit <n> Max messages to return (server caps at 200)
|
|
113
|
-
--after <cursor> Pagination cursor (message id from prior page)
|
|
114
|
-
--project <id> Project ID (defaults to the active project)
|
|
115
|
-
|
|
116
|
-
Examples:
|
|
117
|
-
run402 email list
|
|
118
|
-
run402 email list --limit 50
|
|
119
|
-
run402 email list --limit 50 --after msg_abc123
|
|
120
90
|
`,
|
|
121
91
|
reply: `run402 email reply — Reply to an inbound message (threaded via In-Reply-To)
|
|
122
92
|
|
|
123
93
|
Usage:
|
|
124
94
|
run402 email reply <message_id> --html "..." [--text "..."] [options]
|
|
125
|
-
|
|
126
|
-
Arguments:
|
|
127
|
-
<message_id> Inbound message ID to reply to
|
|
128
|
-
|
|
129
|
-
Options:
|
|
130
|
-
--html "..." HTML reply body (required unless --text is given)
|
|
131
|
-
--text "..." Plain-text reply body (required unless --html is given)
|
|
132
|
-
--subject "..." Override the reply subject (default: "Re: <original>")
|
|
133
|
-
--from-name "..." Display name for the From header
|
|
134
|
-
--project <id> Project ID (defaults to the active project)
|
|
135
|
-
|
|
136
|
-
Notes:
|
|
137
|
-
The CLI fetches the original message to derive the reply-to address and
|
|
138
|
-
subject, then POSTs a new message with in_reply_to = <message_id> so the
|
|
139
|
-
server can wire the RFC-822 In-Reply-To / References headers.
|
|
140
|
-
|
|
141
|
-
Examples:
|
|
142
|
-
run402 email reply msg_abc123 --html "<p>Thanks, here's the info you asked for.</p>"
|
|
143
|
-
run402 email reply msg_abc123 --subject "Re: invoice #42" --text "Paid, thanks."
|
|
144
95
|
`,
|
|
145
96
|
delete: `run402 email delete — Delete the project's mailbox (irreversible)
|
|
146
97
|
|
|
147
98
|
Usage:
|
|
148
99
|
run402 email delete [<mailbox_id>] --confirm [--project <id>]
|
|
149
|
-
|
|
150
|
-
Arguments:
|
|
151
|
-
<mailbox_id> Mailbox ID to delete (defaults to the project's mailbox)
|
|
152
|
-
|
|
153
|
-
Options:
|
|
154
|
-
--confirm Required: explicit confirmation flag
|
|
155
|
-
--project <id> Project ID (defaults to the active project)
|
|
156
|
-
|
|
157
|
-
Notes:
|
|
158
|
-
Destructive. Drops all messages and webhook subscriptions. Cached
|
|
159
|
-
mailbox_id in the local keystore is cleared on success.
|
|
160
|
-
|
|
161
|
-
Examples:
|
|
162
|
-
run402 email delete --confirm
|
|
163
|
-
run402 email delete mbx_abc123 --confirm
|
|
164
100
|
`,
|
|
165
101
|
info: `run402 email info — Show mailbox info (ID, address, slug)
|
|
166
102
|
|
|
167
103
|
Usage:
|
|
168
104
|
run402 email info [--project <id>]
|
|
169
|
-
|
|
170
|
-
Options:
|
|
171
|
-
--project <id> Project ID (defaults to the active project)
|
|
172
|
-
|
|
173
|
-
Notes:
|
|
174
|
-
Same output as 'run402 email status' (kept as an alias for backward
|
|
175
|
-
compatibility). 'info' is the preferred name.
|
|
176
105
|
`,
|
|
177
106
|
status: `run402 email status — Alias for 'run402 email info' (prefer 'info')
|
|
178
107
|
|
|
@@ -186,22 +115,9 @@ compatibility; new code should use 'info'.
|
|
|
186
115
|
|
|
187
116
|
Usage:
|
|
188
117
|
run402 email get-raw <message_id> [--output <file>] [--project <id>]
|
|
189
|
-
|
|
190
|
-
Arguments:
|
|
191
|
-
<message_id> Message ID to fetch (inbound messages only)
|
|
192
|
-
|
|
193
|
-
Options:
|
|
194
|
-
--output <file> Write raw bytes to this file; omit to stream to stdout
|
|
195
|
-
--project <id> Project ID (defaults to the active project)
|
|
196
|
-
|
|
197
|
-
Examples:
|
|
198
|
-
run402 email get-raw msg_abc123 --output reply.eml
|
|
199
|
-
run402 email get-raw msg_abc123 > reply.eml
|
|
200
118
|
`,
|
|
201
119
|
};
|
|
202
120
|
|
|
203
|
-
const SLUG_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
|
|
204
|
-
|
|
205
121
|
function parseFlag(args, flag) {
|
|
206
122
|
for (let i = 0; i < args.length; i++) {
|
|
207
123
|
if (args[i] === flag && args[i + 1]) return args[i + 1];
|
|
@@ -211,7 +127,6 @@ function parseFlag(args, flag) {
|
|
|
211
127
|
|
|
212
128
|
function parseVars(args) {
|
|
213
129
|
const vars = {};
|
|
214
|
-
// Apply --vars '<json>' first so later --var can override on key collision.
|
|
215
130
|
for (let i = 0; i < args.length; i++) {
|
|
216
131
|
if (args[i] === "--vars" && args[i + 1]) {
|
|
217
132
|
const raw = args[++i];
|
|
@@ -227,53 +142,16 @@ function parseVars(args) {
|
|
|
227
142
|
for (const [k, v] of Object.entries(parsed)) vars[k] = typeof v === "string" ? v : String(v);
|
|
228
143
|
}
|
|
229
144
|
}
|
|
230
|
-
// Then --var key=value (later wins).
|
|
231
145
|
for (let i = 0; i < args.length; i++) {
|
|
232
146
|
if (args[i] === "--var" && args[i + 1]) {
|
|
233
147
|
const raw = args[++i];
|
|
234
148
|
const eq = raw.indexOf("=");
|
|
235
|
-
if (eq > 0)
|
|
236
|
-
vars[raw.slice(0, eq)] = raw.slice(eq + 1);
|
|
237
|
-
}
|
|
149
|
+
if (eq > 0) vars[raw.slice(0, eq)] = raw.slice(eq + 1);
|
|
238
150
|
}
|
|
239
151
|
}
|
|
240
152
|
return vars;
|
|
241
153
|
}
|
|
242
154
|
|
|
243
|
-
async function resolveMailboxId(projectId, serviceKey) {
|
|
244
|
-
const store = loadKeyStore();
|
|
245
|
-
const proj = store.projects[projectId];
|
|
246
|
-
if (proj && proj.mailbox_id) return proj.mailbox_id;
|
|
247
|
-
|
|
248
|
-
// Fallback: discover via API
|
|
249
|
-
const res = await fetch(`${API}/mailboxes/v1`, {
|
|
250
|
-
headers: { "Authorization": `Bearer ${serviceKey}` },
|
|
251
|
-
});
|
|
252
|
-
if (!res.ok) {
|
|
253
|
-
const data = await res.json().catch(() => ({}));
|
|
254
|
-
throw Object.assign(new Error("Failed to resolve mailbox"), { http: res.status, ...data });
|
|
255
|
-
}
|
|
256
|
-
const body = await res.json();
|
|
257
|
-
const mailboxes = body.mailboxes || body;
|
|
258
|
-
if (!Array.isArray(mailboxes) || mailboxes.length === 0) {
|
|
259
|
-
throw new Error("No mailbox found. Run: run402 email create <slug>");
|
|
260
|
-
}
|
|
261
|
-
const mb = mailboxes[0];
|
|
262
|
-
updateProject(projectId, { mailbox_id: mb.mailbox_id, mailbox_address: mb.address });
|
|
263
|
-
return mb.mailbox_id;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
async function requireMailboxId(projectId, serviceKey) {
|
|
267
|
-
try {
|
|
268
|
-
return await resolveMailboxId(projectId, serviceKey);
|
|
269
|
-
} catch (err) {
|
|
270
|
-
const out = { status: "error", message: err.message };
|
|
271
|
-
if (err.http) out.http = err.http;
|
|
272
|
-
console.error(JSON.stringify(out));
|
|
273
|
-
process.exit(1);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
155
|
async function create(args) {
|
|
278
156
|
let slug = null;
|
|
279
157
|
let projectOpt = null;
|
|
@@ -282,48 +160,22 @@ async function create(args) {
|
|
|
282
160
|
else if (!args[i].startsWith("--") && !slug) { slug = args[i]; }
|
|
283
161
|
}
|
|
284
162
|
const projectId = resolveProjectId(projectOpt);
|
|
285
|
-
const p = findProject(projectId);
|
|
286
|
-
|
|
287
163
|
if (!slug) {
|
|
288
164
|
console.error(JSON.stringify({ status: "error", message: "Missing slug. Usage: run402 email create <slug>" }));
|
|
289
165
|
process.exit(1);
|
|
290
166
|
}
|
|
291
|
-
if (slug.length < 3 || slug.length > 63) {
|
|
292
|
-
console.error(JSON.stringify({ status: "error", message: "Slug must be 3-63 characters." }));
|
|
293
|
-
process.exit(1);
|
|
294
|
-
}
|
|
295
|
-
if (!SLUG_RE.test(slug)) {
|
|
296
|
-
console.error(JSON.stringify({ status: "error", message: "Slug must be lowercase alphanumeric + hyphens, start/end with alphanumeric." }));
|
|
297
|
-
process.exit(1);
|
|
298
|
-
}
|
|
299
|
-
if (slug.includes("--")) {
|
|
300
|
-
console.error(JSON.stringify({ status: "error", message: "Slug must not contain consecutive hyphens." }));
|
|
301
|
-
process.exit(1);
|
|
302
|
-
}
|
|
303
167
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
// On 409 (mailbox already exists), discover existing mailbox and return it
|
|
312
|
-
if (res.status === 409) {
|
|
313
|
-
const mbId = await resolveMailboxId(projectId, p.service_key).catch(() => null);
|
|
314
|
-
if (mbId) {
|
|
315
|
-
const store = loadKeyStore();
|
|
316
|
-
const proj = store.projects[projectId];
|
|
317
|
-
console.log(JSON.stringify({ status: "ok", mailbox_id: mbId, address: proj?.mailbox_address || mbId, already_existed: true }));
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
168
|
+
try {
|
|
169
|
+
const data = await getSdk().email.createMailbox(projectId, slug);
|
|
170
|
+
// SDK may return CreateMailboxResult (with slug) on success, or MailboxInfo on 409 idempotency.
|
|
171
|
+
if (data.slug) {
|
|
172
|
+
console.log(JSON.stringify({ status: "ok", mailbox_id: data.mailbox_id, address: data.address, slug: data.slug }));
|
|
173
|
+
} else {
|
|
174
|
+
console.log(JSON.stringify({ status: "ok", mailbox_id: data.mailbox_id, address: data.address, already_existed: true }));
|
|
320
175
|
}
|
|
321
|
-
|
|
322
|
-
|
|
176
|
+
} catch (err) {
|
|
177
|
+
reportSdkError(err);
|
|
323
178
|
}
|
|
324
|
-
|
|
325
|
-
updateProject(projectId, { mailbox_id: data.mailbox_id, mailbox_address: data.address });
|
|
326
|
-
console.log(JSON.stringify({ status: "ok", mailbox_id: data.mailbox_id, address: data.address, slug: data.slug }));
|
|
327
179
|
}
|
|
328
180
|
|
|
329
181
|
async function send(args) {
|
|
@@ -334,7 +186,6 @@ async function send(args) {
|
|
|
334
186
|
const text = parseFlag(args, "--text");
|
|
335
187
|
const fromName = parseFlag(args, "--from-name");
|
|
336
188
|
const projectId = resolveProjectId(parseFlag(args, "--project"));
|
|
337
|
-
const p = findProject(projectId);
|
|
338
189
|
const variables = parseVars(args);
|
|
339
190
|
|
|
340
191
|
if (!to) {
|
|
@@ -342,73 +193,35 @@ async function send(args) {
|
|
|
342
193
|
process.exit(1);
|
|
343
194
|
}
|
|
344
195
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
const missing = hasSubject ? "--html" : "--subject";
|
|
359
|
-
console.error(JSON.stringify({ status: "error", message: `Raw mode requires both --subject and --html (missing ${missing})` }));
|
|
360
|
-
process.exit(1);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
const mailboxId = await requireMailboxId(projectId, p.service_key);
|
|
364
|
-
|
|
365
|
-
const body = { to };
|
|
366
|
-
if (isTemplate) {
|
|
367
|
-
body.template = template;
|
|
368
|
-
body.variables = variables;
|
|
369
|
-
} else {
|
|
370
|
-
body.subject = subject;
|
|
371
|
-
body.html = html;
|
|
372
|
-
if (text) body.text = text;
|
|
373
|
-
}
|
|
374
|
-
if (fromName) body.from_name = fromName;
|
|
375
|
-
|
|
376
|
-
const res = await fetch(`${API}/mailboxes/v1/${mailboxId}/messages`, {
|
|
377
|
-
method: "POST",
|
|
378
|
-
headers: { "Authorization": `Bearer ${p.service_key}`, "Content-Type": "application/json" },
|
|
379
|
-
body: JSON.stringify(body),
|
|
380
|
-
});
|
|
381
|
-
const data = await res.json();
|
|
382
|
-
if (!res.ok) {
|
|
383
|
-
console.error(JSON.stringify({ status: "error", http: res.status, ...data }));
|
|
384
|
-
process.exit(1);
|
|
196
|
+
try {
|
|
197
|
+
const data = await getSdk().email.send(projectId, {
|
|
198
|
+
to,
|
|
199
|
+
template: template ?? undefined,
|
|
200
|
+
variables: template ? variables : undefined,
|
|
201
|
+
subject: subject ?? undefined,
|
|
202
|
+
html: html ?? undefined,
|
|
203
|
+
text: text ?? undefined,
|
|
204
|
+
from_name: fromName ?? undefined,
|
|
205
|
+
});
|
|
206
|
+
console.log(JSON.stringify({ status: "ok", message_id: data.id, to: data.to, template: data.template || null, subject: data.subject || null }));
|
|
207
|
+
} catch (err) {
|
|
208
|
+
reportSdkError(err);
|
|
385
209
|
}
|
|
386
|
-
|
|
387
|
-
console.log(JSON.stringify({ status: "ok", message_id: data.id, to: data.to, template: data.template || null, subject: data.subject || null }));
|
|
388
210
|
}
|
|
389
211
|
|
|
390
212
|
async function list(args) {
|
|
391
213
|
const projectId = resolveProjectId(parseFlag(args, "--project"));
|
|
392
|
-
const p = findProject(projectId);
|
|
393
214
|
const limit = parseFlag(args, "--limit");
|
|
394
215
|
const after = parseFlag(args, "--after");
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
headers: { "Authorization": `Bearer ${p.service_key}` },
|
|
404
|
-
});
|
|
405
|
-
const data = await res.json();
|
|
406
|
-
if (!res.ok) {
|
|
407
|
-
console.error(JSON.stringify({ status: "error", http: res.status, ...data }));
|
|
408
|
-
process.exit(1);
|
|
216
|
+
try {
|
|
217
|
+
const data = await getSdk().email.list(projectId, {
|
|
218
|
+
limit: limit ? Number(limit) : undefined,
|
|
219
|
+
after: after ?? undefined,
|
|
220
|
+
});
|
|
221
|
+
console.log(JSON.stringify(data, null, 2));
|
|
222
|
+
} catch (err) {
|
|
223
|
+
reportSdkError(err);
|
|
409
224
|
}
|
|
410
|
-
|
|
411
|
-
console.log(JSON.stringify(data, null, 2));
|
|
412
225
|
}
|
|
413
226
|
|
|
414
227
|
async function get(args) {
|
|
@@ -419,25 +232,16 @@ async function get(args) {
|
|
|
419
232
|
else if (!args[i].startsWith("--") && !messageId) { messageId = args[i]; }
|
|
420
233
|
}
|
|
421
234
|
const projectId = resolveProjectId(projectOpt);
|
|
422
|
-
const p = findProject(projectId);
|
|
423
|
-
|
|
424
235
|
if (!messageId) {
|
|
425
236
|
console.error(JSON.stringify({ status: "error", message: "Missing message_id. Usage: run402 email get <message_id>" }));
|
|
426
237
|
process.exit(1);
|
|
427
238
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
});
|
|
434
|
-
const data = await res.json();
|
|
435
|
-
if (!res.ok) {
|
|
436
|
-
console.error(JSON.stringify({ status: "error", http: res.status, ...data }));
|
|
437
|
-
process.exit(1);
|
|
239
|
+
try {
|
|
240
|
+
const data = await getSdk().email.get(projectId, messageId);
|
|
241
|
+
console.log(JSON.stringify(data, null, 2));
|
|
242
|
+
} catch (err) {
|
|
243
|
+
reportSdkError(err);
|
|
438
244
|
}
|
|
439
|
-
|
|
440
|
-
console.log(JSON.stringify(data, null, 2));
|
|
441
245
|
}
|
|
442
246
|
|
|
443
247
|
async function getRaw(args) {
|
|
@@ -450,42 +254,30 @@ async function getRaw(args) {
|
|
|
450
254
|
else if (!args[i].startsWith("--") && !messageId) { messageId = args[i]; }
|
|
451
255
|
}
|
|
452
256
|
const projectId = resolveProjectId(projectOpt);
|
|
453
|
-
const p = findProject(projectId);
|
|
454
|
-
|
|
455
257
|
if (!messageId) {
|
|
456
258
|
console.error(JSON.stringify({ status: "error", message: "Missing message_id. Usage: run402 email get-raw <message_id> [--output <file>]" }));
|
|
457
259
|
process.exit(1);
|
|
458
260
|
}
|
|
459
261
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
if (outputFile) {
|
|
475
|
-
const { writeFileSync } = await import("node:fs");
|
|
476
|
-
writeFileSync(outputFile, buf);
|
|
477
|
-
console.log(JSON.stringify({ status: "ok", message_id: messageId, bytes: buf.length, output: outputFile }));
|
|
478
|
-
} else {
|
|
479
|
-
// Write raw bytes to stdout (no JSON wrapping — binary pipe-friendly)
|
|
480
|
-
process.stdout.write(buf);
|
|
262
|
+
try {
|
|
263
|
+
const result = await getSdk().email.getRaw(projectId, messageId);
|
|
264
|
+
const buf = Buffer.from(result.bytes);
|
|
265
|
+
|
|
266
|
+
if (outputFile) {
|
|
267
|
+
const { writeFileSync } = await import("node:fs");
|
|
268
|
+
writeFileSync(outputFile, buf);
|
|
269
|
+
console.log(JSON.stringify({ status: "ok", message_id: messageId, bytes: buf.length, output: outputFile }));
|
|
270
|
+
} else {
|
|
271
|
+
process.stdout.write(buf);
|
|
272
|
+
}
|
|
273
|
+
} catch (err) {
|
|
274
|
+
reportSdkError(err);
|
|
481
275
|
}
|
|
482
276
|
}
|
|
483
277
|
|
|
484
278
|
async function reply(args) {
|
|
485
279
|
let messageId = null;
|
|
486
280
|
let projectOpt = null;
|
|
487
|
-
let outputFile = null;
|
|
488
|
-
void outputFile;
|
|
489
281
|
for (let i = 0; i < args.length; i++) {
|
|
490
282
|
const a = args[i];
|
|
491
283
|
if (a === "--project" && args[i + 1]) { projectOpt = args[++i]; }
|
|
@@ -497,7 +289,6 @@ async function reply(args) {
|
|
|
497
289
|
const subjectOverride = parseFlag(args, "--subject");
|
|
498
290
|
const fromName = parseFlag(args, "--from-name");
|
|
499
291
|
const projectId = resolveProjectId(projectOpt);
|
|
500
|
-
const p = findProject(projectId);
|
|
501
292
|
|
|
502
293
|
if (!messageId) {
|
|
503
294
|
console.error(JSON.stringify({ status: "error", message: "Missing message_id. Usage: run402 email reply <message_id> --html \"...\"" }));
|
|
@@ -508,45 +299,36 @@ async function reply(args) {
|
|
|
508
299
|
process.exit(1);
|
|
509
300
|
}
|
|
510
301
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
headers: { "Authorization": `Bearer ${p.service_key}`, "Content-Type": "application/json" },
|
|
541
|
-
body: JSON.stringify(body),
|
|
542
|
-
});
|
|
543
|
-
const data = await res.json().catch(() => ({}));
|
|
544
|
-
if (!res.ok) {
|
|
545
|
-
console.error(JSON.stringify({ status: "error", http: res.status, ...data }));
|
|
546
|
-
process.exit(1);
|
|
302
|
+
try {
|
|
303
|
+
// Fetch the original message to derive the reply-to address and subject.
|
|
304
|
+
const original = await getSdk().email.get(projectId, messageId);
|
|
305
|
+
const replyTo = original.from || original.from_address || original.sender || null;
|
|
306
|
+
if (!replyTo) {
|
|
307
|
+
console.error(JSON.stringify({
|
|
308
|
+
status: "error",
|
|
309
|
+
message: "Original message has no from address to reply to",
|
|
310
|
+
original_keys: Object.keys(original),
|
|
311
|
+
}));
|
|
312
|
+
process.exit(1);
|
|
313
|
+
}
|
|
314
|
+
const origSubject = typeof original.subject === "string" ? original.subject : "";
|
|
315
|
+
const defaultSubject = origSubject && origSubject.toLowerCase().startsWith("re:")
|
|
316
|
+
? origSubject
|
|
317
|
+
: `Re: ${origSubject || "(no subject)"}`;
|
|
318
|
+
const replySubject = subjectOverride || defaultSubject;
|
|
319
|
+
|
|
320
|
+
const data = await getSdk().email.send(projectId, {
|
|
321
|
+
to: replyTo,
|
|
322
|
+
subject: replySubject,
|
|
323
|
+
html: html ?? undefined,
|
|
324
|
+
text: text ?? undefined,
|
|
325
|
+
from_name: fromName ?? undefined,
|
|
326
|
+
in_reply_to: messageId,
|
|
327
|
+
});
|
|
328
|
+
console.log(JSON.stringify({ status: "ok", message_id: data.id, to: data.to, subject: replySubject, in_reply_to: messageId }));
|
|
329
|
+
} catch (err) {
|
|
330
|
+
reportSdkError(err);
|
|
547
331
|
}
|
|
548
|
-
|
|
549
|
-
console.log(JSON.stringify({ status: "ok", message_id: data.id, to: data.to, subject: replySubject, in_reply_to: messageId }));
|
|
550
332
|
}
|
|
551
333
|
|
|
552
334
|
async function deleteMailbox(args) {
|
|
@@ -559,7 +341,6 @@ async function deleteMailbox(args) {
|
|
|
559
341
|
else if (!a.startsWith("--") && !positional) { positional = a; }
|
|
560
342
|
}
|
|
561
343
|
const projectId = resolveProjectId(projectOpt);
|
|
562
|
-
const p = findProject(projectId);
|
|
563
344
|
const confirmed = args.includes("--confirm");
|
|
564
345
|
|
|
565
346
|
if (!confirmed) {
|
|
@@ -570,53 +351,22 @@ async function deleteMailbox(args) {
|
|
|
570
351
|
process.exit(1);
|
|
571
352
|
}
|
|
572
353
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
});
|
|
579
|
-
if (res.status !== 204 && !res.ok) {
|
|
580
|
-
let errBody;
|
|
581
|
-
try { errBody = await res.json(); } catch { errBody = {}; }
|
|
582
|
-
console.error(JSON.stringify({ status: "error", http: res.status, ...errBody }));
|
|
583
|
-
process.exit(1);
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
// Clear the cached mailbox_id/address from the local keystore so future
|
|
587
|
-
// email commands re-discover (or fail-fast with "no mailbox found").
|
|
588
|
-
const store = loadKeyStore();
|
|
589
|
-
const proj = store.projects[projectId];
|
|
590
|
-
if (proj) {
|
|
591
|
-
delete proj.mailbox_id;
|
|
592
|
-
delete proj.mailbox_address;
|
|
593
|
-
saveKeyStore(store);
|
|
354
|
+
try {
|
|
355
|
+
await getSdk().email.deleteMailbox(projectId, positional ?? undefined);
|
|
356
|
+
console.log(JSON.stringify({ status: "ok", mailbox_id: positional ?? "(resolved)", deleted: true }));
|
|
357
|
+
} catch (err) {
|
|
358
|
+
reportSdkError(err);
|
|
594
359
|
}
|
|
595
|
-
|
|
596
|
-
console.log(JSON.stringify({ status: "ok", mailbox_id: mailboxId, deleted: true }));
|
|
597
360
|
}
|
|
598
361
|
|
|
599
362
|
async function status(args) {
|
|
600
363
|
const projectId = resolveProjectId(parseFlag(args, "--project"));
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
if (!res.ok) {
|
|
607
|
-
const data = await res.json().catch(() => ({}));
|
|
608
|
-
console.error(JSON.stringify({ status: "error", http: res.status, ...data }));
|
|
609
|
-
process.exit(1);
|
|
610
|
-
}
|
|
611
|
-
const body = await res.json();
|
|
612
|
-
const mailboxes = body.mailboxes || body;
|
|
613
|
-
if (!Array.isArray(mailboxes) || mailboxes.length === 0) {
|
|
614
|
-
console.error(JSON.stringify({ status: "error", message: "No mailbox found. Run: run402 email create <slug>" }));
|
|
615
|
-
process.exit(1);
|
|
364
|
+
try {
|
|
365
|
+
const mb = await getSdk().email.getMailbox(projectId);
|
|
366
|
+
console.log(JSON.stringify({ status: "ok", mailbox_id: mb.mailbox_id, address: mb.address, slug: mb.slug }));
|
|
367
|
+
} catch (err) {
|
|
368
|
+
reportSdkError(err);
|
|
616
369
|
}
|
|
617
|
-
const mb = mailboxes[0];
|
|
618
|
-
updateProject(projectId, { mailbox_id: mb.mailbox_id, mailbox_address: mb.address });
|
|
619
|
-
console.log(JSON.stringify({ status: "ok", mailbox_id: mb.mailbox_id, address: mb.address, slug: mb.slug }));
|
|
620
370
|
}
|
|
621
371
|
|
|
622
372
|
export async function run(sub, args) {
|
|
@@ -624,7 +374,7 @@ export async function run(sub, args) {
|
|
|
624
374
|
if (Array.isArray(args) && (args.includes("--help") || args.includes("-h")) && sub !== "webhooks") { console.log(SUB_HELP[sub] || HELP); process.exit(0); }
|
|
625
375
|
switch (sub) {
|
|
626
376
|
case "create": await create(args); break;
|
|
627
|
-
case "info":
|
|
377
|
+
case "info":
|
|
628
378
|
case "status": await status(args); break;
|
|
629
379
|
case "send": await send(args); break;
|
|
630
380
|
case "list": await list(args); break;
|