@clize/clize 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.
Files changed (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +52 -0
  3. package/dist/cli.js +197 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/config.js +52 -0
  6. package/dist/config.js.map +1 -0
  7. package/dist/context.js +51 -0
  8. package/dist/context.js.map +1 -0
  9. package/dist/core/addresses.js +84 -0
  10. package/dist/core/addresses.js.map +1 -0
  11. package/dist/core/dns.js +10 -0
  12. package/dist/core/dns.js.map +1 -0
  13. package/dist/core/domains.js +190 -0
  14. package/dist/core/domains.js.map +1 -0
  15. package/dist/core/email.js +100 -0
  16. package/dist/core/email.js.map +1 -0
  17. package/dist/core/handle.js +136 -0
  18. package/dist/core/handle.js.map +1 -0
  19. package/dist/core/setup.js +49 -0
  20. package/dist/core/setup.js.map +1 -0
  21. package/dist/core/site.js +57 -0
  22. package/dist/core/site.js.map +1 -0
  23. package/dist/core/sites.js +148 -0
  24. package/dist/core/sites.js.map +1 -0
  25. package/dist/index.js +54 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/lib/cloudflare.js +112 -0
  28. package/dist/lib/cloudflare.js.map +1 -0
  29. package/dist/lib/result.js +25 -0
  30. package/dist/lib/result.js.map +1 -0
  31. package/dist/lib/zone.js +36 -0
  32. package/dist/lib/zone.js.map +1 -0
  33. package/dist/providers/deploy/cloudflare.js +71 -0
  34. package/dist/providers/deploy/cloudflare.js.map +1 -0
  35. package/dist/providers/dns/cloudflare.js +43 -0
  36. package/dist/providers/dns/cloudflare.js.map +1 -0
  37. package/dist/providers/email/cloudflare-inbound.js +216 -0
  38. package/dist/providers/email/cloudflare-inbound.js.map +1 -0
  39. package/dist/providers/email/cloudflare-outbound.js +65 -0
  40. package/dist/providers/email/cloudflare-outbound.js.map +1 -0
  41. package/dist/providers/email/resend.js +62 -0
  42. package/dist/providers/email/resend.js.map +1 -0
  43. package/dist/providers/email/ses.js +74 -0
  44. package/dist/providers/email/ses.js.map +1 -0
  45. package/dist/providers/index.js +33 -0
  46. package/dist/providers/index.js.map +1 -0
  47. package/dist/providers/registrar/cloudflare.js +78 -0
  48. package/dist/providers/registrar/cloudflare.js.map +1 -0
  49. package/dist/selfcheck.js +49 -0
  50. package/dist/selfcheck.js.map +1 -0
  51. package/dist/state/file-store.js +194 -0
  52. package/dist/state/file-store.js.map +1 -0
  53. package/dist/state/store.js +2 -0
  54. package/dist/state/store.js.map +1 -0
  55. package/dist/tools/deploy.js +25 -0
  56. package/dist/tools/deploy.js.map +1 -0
  57. package/dist/tools/dns.js +25 -0
  58. package/dist/tools/dns.js.map +1 -0
  59. package/dist/tools/domains.js +58 -0
  60. package/dist/tools/domains.js.map +1 -0
  61. package/dist/tools/email.js +34 -0
  62. package/dist/tools/email.js.map +1 -0
  63. package/dist/types/index.js +7 -0
  64. package/dist/types/index.js.map +1 -0
  65. package/package.json +45 -0
@@ -0,0 +1,216 @@
1
+ import PostalMime from "postal-mime";
2
+ const INBOX_WORKER = "aw-email-inbox";
3
+ const INBOX_KV_TITLE = "aw-inbound-email";
4
+ /** agent 收件箱 Email Worker 源码:入站邮件 value 存完整 raw MIME(供 postal-mime 解析正文)+ metadata 存信封,可选转发(env.FORWARD_TO)。 */
5
+ const INBOX_WORKER_SRC = `
6
+ export default {
7
+ async email(message, env, ctx) {
8
+ const meta = {
9
+ from: message.from,
10
+ to: message.to,
11
+ subject: message.headers.get("subject") || "",
12
+ receivedAt: new Date().toISOString(),
13
+ size: message.rawSize,
14
+ };
15
+ // value 存完整 raw MIME(正文在里面,读时用 postal-mime 解析);metadata 存信封供列表快速读
16
+ const raw = await new Response(message.raw).text();
17
+ await env.EMAILS.put(message.to + "/" + Date.now(), raw, { expirationTtl: 2592000, metadata: meta });
18
+ if (env.FORWARD_TO) { try { await message.forward(env.FORWARD_TO); } catch (e) {} }
19
+ // 入站 webhook(push):该地址配了 _webhook/<addr> 就 POST 信封过去(正文按需再拉)
20
+ try {
21
+ const wh = await env.EMAILS.get("_webhook/" + message.to);
22
+ if (wh) ctx.waitUntil(fetch(wh, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(meta) }));
23
+ } catch (e) {}
24
+ }
25
+ };
26
+ `;
27
+ export class CloudflareInbound {
28
+ cf;
29
+ id = "cloudflare";
30
+ constructor(cf) {
31
+ this.cf = cf;
32
+ }
33
+ /**
34
+ * 启用 Cloudflare Email Routing 并写入所需 DNS 记录,使该域名能通过 Cloudflare 接收邮件。
35
+ * - apex(如 clize.app):`GET /email/routing/dns` 直接返回记录数组(MX + SPF + DKIM)。
36
+ * - 子域(如 hello.clize.app):`GET /email/routing/dns?subdomain=<完整FQDN>` 返回 { records }
37
+ * (子域只需 MX + SPF;subdomain 参数必须是完整域名,传 label 会报 [2007])。
38
+ */
39
+ async setup(domain, zoneId) {
40
+ // 启用 Email Routing + 读应配的 DNS:CF 把这两个端点只开给 Global Key(API Token 一律 403),
41
+ // 故定向兜底(auth:"global");下面真正写 DNS 记录仍走主凭证 token。
42
+ await this.cf
43
+ .request("POST", `/zones/${zoneId}/email/routing/enable`, {}, { auth: "global" })
44
+ .catch(() => { });
45
+ const zone = await this.cf.request("GET", `/zones/${zoneId}`);
46
+ const isSubdomain = domain !== zone.name;
47
+ const records = isSubdomain
48
+ ? (await this.cf.request("GET", `/zones/${zoneId}/email/routing/dns?subdomain=${encodeURIComponent(domain)}`, undefined, { auth: "global" })).records ?? []
49
+ : await this.cf.request("GET", `/zones/${zoneId}/email/routing/dns`, undefined, { auth: "global" });
50
+ for (const r of records) {
51
+ await this.cf
52
+ .request("POST", `/zones/${zoneId}/dns_records`, {
53
+ type: r.type,
54
+ name: r.name,
55
+ content: r.type === "TXT" ? unquote(r.content) : r.content,
56
+ ttl: r.ttl ?? 1,
57
+ priority: r.priority,
58
+ })
59
+ .catch(() => { }); // 已存在则忽略
60
+ }
61
+ }
62
+ /**
63
+ * 创建转发规则:from 地址 → 转发到外部 to(account 级建 destination 触发验证 + zone 级建 forward 规则)。
64
+ */
65
+ async addRoute(opts) {
66
+ const { zoneId, from, to } = opts;
67
+ await this.cf
68
+ .request("POST", `/accounts/${this.cf.accountId}/email/routing/addresses`, { email: to })
69
+ .catch(() => { });
70
+ const addresses = await this.cf
71
+ .request("GET", `/accounts/${this.cf.accountId}/email/routing/addresses`)
72
+ .catch(() => []);
73
+ const destinationVerified = Boolean(addresses.find((a) => a.email === to)?.verified);
74
+ const rule = await this.cf.request("POST", `/zones/${zoneId}/email/routing/rules`, {
75
+ name: `forward ${from} -> ${to}`,
76
+ enabled: true,
77
+ matchers: [{ type: "literal", field: "to", value: from }],
78
+ actions: [{ type: "forward", value: [to] }],
79
+ });
80
+ return {
81
+ from,
82
+ to,
83
+ ruleId: rule.id,
84
+ destinationVerified,
85
+ message: destinationVerified
86
+ ? `转发已生效:${from} → ${to}(目标已验证)`
87
+ : `转发规则已建:${from} → ${to}。Cloudflare 已向 ${to} 发送验证邮件,请点击确认后转发才会生效。`,
88
+ };
89
+ }
90
+ /**
91
+ * 开启 agent 自持收信:部署 Email Worker 接住 address 的入站邮件存入 KV(可选 forwardTo 同时转发一份),
92
+ * 并把该地址的 routing 规则指向该 Worker。之后 `clize email inbox <域>` 即可读取。幂等(可重复执行)。
93
+ */
94
+ async setupInbox(opts) {
95
+ const { address, zoneId, forwardTo } = opts;
96
+ // 1) KV namespace(找或建)
97
+ const kvNamespaceId = await this.ensureKvNamespace();
98
+ // 2) 部署 Email Worker(KV binding + 可选 FORWARD_TO 明文 binding)
99
+ const bindings = [
100
+ { type: "kv_namespace", name: "EMAILS", namespace_id: kvNamespaceId },
101
+ ];
102
+ if (forwardTo)
103
+ bindings.push({ type: "plain_text", name: "FORWARD_TO", text: forwardTo });
104
+ const form = new FormData();
105
+ form.append("metadata", JSON.stringify({ main_module: "worker.js", compatibility_date: "2024-11-01", bindings }));
106
+ form.append("worker.js", new Blob([INBOX_WORKER_SRC], { type: "application/javascript+module" }), "worker.js");
107
+ await this.cf.request("PUT", `/accounts/${this.cf.accountId}/workers/scripts/${INBOX_WORKER}`, form);
108
+ // 3) 确保 routing 启用 + 把该地址路由到 Worker(已有同地址规则则更新,否则新建)
109
+ await this.cf.request("POST", `/zones/${zoneId}/email/routing/enable`, {}).catch(() => { });
110
+ const rules = await this.cf.request("GET", `/zones/${zoneId}/email/routing/rules`);
111
+ const existing = rules.find((r) => r.matchers?.some((m) => m.value === address));
112
+ const body = {
113
+ name: `inbox ${address} -> worker(${INBOX_WORKER})`,
114
+ enabled: true,
115
+ matchers: [{ type: "literal", field: "to", value: address }],
116
+ actions: [{ type: "worker", value: [INBOX_WORKER] }],
117
+ };
118
+ if (existing) {
119
+ await this.cf.request("PUT", `/zones/${zoneId}/email/routing/rules/${existing.id}`, body);
120
+ }
121
+ else {
122
+ await this.cf.request("POST", `/zones/${zoneId}/email/routing/rules`, body);
123
+ }
124
+ return { address, worker: INBOX_WORKER, kvNamespaceId, forwardTo };
125
+ }
126
+ /** 配置某地址的入站 webhook:写 KV `_webhook/<address>` = url;来信时该 worker 会 POST 信封过去。 */
127
+ async setWebhook(address, url) {
128
+ const nsId = await this.ensureKvNamespace();
129
+ await this.cf.request("PUT", `/accounts/${this.cf.accountId}/storage/kv/namespaces/${nsId}/bulk`, [{ key: "_webhook/" + address.toLowerCase(), value: url }]);
130
+ }
131
+ /** 读取入站收件箱:从 KV 按域过滤取最近若干封,每封用 postal-mime 解析出正文(text/html)。 */
132
+ async checkInbox(domain, opts) {
133
+ const nsId = await this.kvNamespaceId();
134
+ if (!nsId)
135
+ return [];
136
+ const keys = await this.listKeys(nsId);
137
+ const picked = keys
138
+ .filter((k) => k.name.split("/")[0].endsWith(`@${domain}`))
139
+ .sort((a, b) => (b.metadata?.receivedAt ?? "").localeCompare(a.metadata?.receivedAt ?? ""))
140
+ .slice(0, opts?.limit ?? 20);
141
+ return Promise.all(picked.map((k) => this.readMessage(nsId, k.name, k.metadata)));
142
+ }
143
+ /** 读取单封邮件全文(含正文 text/html);id = KV key(`<to>/<ts>`),需属于该域。 */
144
+ async getMessage(domain, id) {
145
+ if (!id.split("/")[0].endsWith(`@${domain}`))
146
+ return null;
147
+ const nsId = await this.kvNamespaceId();
148
+ if (!nsId)
149
+ return null;
150
+ const meta = (await this.listKeys(nsId)).find((k) => k.name === id)?.metadata;
151
+ if (!meta)
152
+ return null;
153
+ return this.readMessage(nsId, id, meta);
154
+ }
155
+ /** 列 KV 全部 key(含 metadata 信封)。 */
156
+ listKeys(nsId) {
157
+ return this.cf.request("GET", `/accounts/${this.cf.accountId}/storage/kv/namespaces/${nsId}/keys?limit=1000`);
158
+ }
159
+ /** 从 KV value(raw MIME)用 postal-mime 解析单封;信封字段优先用 metadata。老数据(非 raw)或解析失败时降级为仅信封。 */
160
+ async readMessage(nsId, key, meta) {
161
+ let text;
162
+ let html;
163
+ try {
164
+ const raw = await this.cf.getRaw(`/accounts/${this.cf.accountId}/storage/kv/namespaces/${nsId}/values/${encodeURIComponent(key)}`);
165
+ const parsed = await PostalMime.parse(raw);
166
+ text = parsed.text ?? undefined;
167
+ html = typeof parsed.html === "string" ? parsed.html : undefined;
168
+ }
169
+ catch {
170
+ // 老数据(value 非 raw MIME)或解析失败:仅返回信封
171
+ }
172
+ return {
173
+ id: key,
174
+ from: decodeMimeWords(meta?.from ?? ""),
175
+ to: meta?.to ?? key.split("/")[0],
176
+ subject: decodeMimeWords(meta?.subject ?? ""),
177
+ receivedAt: meta?.receivedAt ?? "",
178
+ text,
179
+ html,
180
+ };
181
+ }
182
+ /** 查 clize 入站邮件 KV namespace 的 id(约定 title=aw-inbound-email)。 */
183
+ async kvNamespaceId() {
184
+ const list = await this.cf.request("GET", `/accounts/${this.cf.accountId}/storage/kv/namespaces?per_page=100`);
185
+ return list.find((n) => n.title === INBOX_KV_TITLE)?.id;
186
+ }
187
+ /** 找或建 clize 入站邮件 KV namespace,返回其 id。 */
188
+ async ensureKvNamespace() {
189
+ const existing = await this.kvNamespaceId();
190
+ if (existing)
191
+ return existing;
192
+ const ns = await this.cf.request("POST", `/accounts/${this.cf.accountId}/storage/kv/namespaces`, { title: INBOX_KV_TITLE });
193
+ return ns.id;
194
+ }
195
+ }
196
+ /** 剥离 TXT 记录值的外层双引号(CF email/routing/dns 返回的 TXT 带引号,写 dns_records 时不需要)。 */
197
+ function unquote(s) {
198
+ return s.startsWith('"') && s.endsWith('"') ? s.slice(1, -1) : s;
199
+ }
200
+ /** 解码 MIME encoded-word(RFC 2047),如 =?UTF-8?B?...?=(base64)/ =?UTF-8?Q?...?=(quoted-printable),用于中文等非 ASCII 主题。 */
201
+ function decodeMimeWords(s) {
202
+ return s.replace(/=\?[^?]+\?([BbQq])\?([^?]*)\?=/g, (_m, enc, text) => {
203
+ try {
204
+ if (enc.toUpperCase() === "B")
205
+ return Buffer.from(text, "base64").toString("utf8");
206
+ const bytes = text
207
+ .replace(/_/g, " ")
208
+ .replace(/=([0-9A-Fa-f]{2})/g, (_x, h) => String.fromCharCode(parseInt(h, 16)));
209
+ return Buffer.from(bytes, "binary").toString("utf8");
210
+ }
211
+ catch {
212
+ return text;
213
+ }
214
+ });
215
+ }
216
+ //# sourceMappingURL=cloudflare-inbound.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudflare-inbound.js","sourceRoot":"","sources":["../../../src/providers/email/cloudflare-inbound.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,aAAa,CAAC;AAiBrC,MAAM,YAAY,GAAG,gBAAgB,CAAC;AACtC,MAAM,cAAc,GAAG,kBAAkB,CAAC;AAE1C,gHAAgH;AAChH,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;CAqBxB,CAAC;AAEF,MAAM,OAAO,iBAAiB;IAGC;IAFpB,EAAE,GAAG,YAAY,CAAC;IAE3B,YAA6B,EAAoB;QAApB,OAAE,GAAF,EAAE,CAAkB;IAAG,CAAC;IAErD;;;;;OAKG;IACH,KAAK,CAAC,KAAK,CAAC,MAAc,EAAE,MAAc;QACxC,yEAAyE;QACzE,gDAAgD;QAChD,MAAM,IAAI,CAAC,EAAE;aACV,OAAO,CAAC,MAAM,EAAE,UAAU,MAAM,uBAAuB,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;aAChF,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAEnB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,OAAO,CAAmB,KAAK,EAAE,UAAU,MAAM,EAAE,CAAC,CAAC;QAChF,MAAM,WAAW,GAAG,MAAM,KAAK,IAAI,CAAC,IAAI,CAAC;QACzC,MAAM,OAAO,GAAG,WAAW;YACzB,CAAC,CAAC,CACE,MAAM,IAAI,CAAC,EAAE,CAAC,OAAO,CACnB,KAAK,EACL,UAAU,MAAM,gCAAgC,kBAAkB,CAAC,MAAM,CAAC,EAAE,EAC5E,SAAS,EACT,EAAE,IAAI,EAAE,QAAQ,EAAE,CACnB,CACF,CAAC,OAAO,IAAI,EAAE;YACjB,CAAC,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC,OAAO,CACnB,KAAK,EACL,UAAU,MAAM,oBAAoB,EACpC,SAAS,EACT,EAAE,IAAI,EAAE,QAAQ,EAAE,CACnB,CAAC;QAEN,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACxB,MAAM,IAAI,CAAC,EAAE;iBACV,OAAO,CAAC,MAAM,EAAE,UAAU,MAAM,cAAc,EAAE;gBAC/C,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,OAAO,EAAE,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO;gBAC1D,GAAG,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;gBACf,QAAQ,EAAE,CAAC,CAAC,QAAQ;aACrB,CAAC;iBACD,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC,CAAC,SAAS;QAC/B,CAAC;IACH,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,QAAQ,CAAC,IAAkD;QAC/D,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,IAAI,CAAC;QAElC,MAAM,IAAI,CAAC,EAAE;aACV,OAAO,CAAC,MAAM,EAAE,aAAa,IAAI,CAAC,EAAE,CAAC,SAAS,0BAA0B,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;aACxF,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACnB,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,EAAE;aAC5B,OAAO,CACN,KAAK,EACL,aAAa,IAAI,CAAC,EAAE,CAAC,SAAS,0BAA0B,CACzD;aACA,KAAK,CAAC,GAAG,EAAE,CAAC,EAAmD,CAAC,CAAC;QACpE,MAAM,mBAAmB,GAAG,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;QAErF,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,OAAO,CAChC,MAAM,EACN,UAAU,MAAM,sBAAsB,EACtC;YACE,IAAI,EAAE,WAAW,IAAI,OAAO,EAAE,EAAE;YAChC,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;YACzD,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;SAC5C,CACF,CAAC;QAEF,OAAO;YACL,IAAI;YACJ,EAAE;YACF,MAAM,EAAE,IAAI,CAAC,EAAE;YACf,mBAAmB;YACnB,OAAO,EAAE,mBAAmB;gBAC1B,CAAC,CAAC,SAAS,IAAI,MAAM,EAAE,SAAS;gBAChC,CAAC,CAAC,UAAU,IAAI,MAAM,EAAE,kBAAkB,EAAE,uBAAuB;SACtE,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,UAAU,CAAC,IAIhB;QACC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;QAE5C,uBAAuB;QACvB,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAErD,4DAA4D;QAC5D,MAAM,QAAQ,GAA8B;YAC1C,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE;SACtE,CAAC;QACF,IAAI,SAAS;YAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;QAC1F,MAAM,IAAI,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC5B,IAAI,CAAC,MAAM,CACT,UAAU,EACV,IAAI,CAAC,SAAS,CAAC,EAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC,CACzF,CAAC;QACF,IAAI,CAAC,MAAM,CACT,WAAW,EACX,IAAI,IAAI,CAAC,CAAC,gBAAgB,CAAC,EAAE,EAAE,IAAI,EAAE,+BAA+B,EAAE,CAAC,EACvE,WAAW,CACZ,CAAC;QACF,MAAM,IAAI,CAAC,EAAE,CAAC,OAAO,CACnB,KAAK,EACL,aAAa,IAAI,CAAC,EAAE,CAAC,SAAS,oBAAoB,YAAY,EAAE,EAChE,IAAI,CACL,CAAC;QAEF,qDAAqD;QACrD,MAAM,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,UAAU,MAAM,uBAAuB,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC3F,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,OAAO,CACjC,KAAK,EACL,UAAU,MAAM,sBAAsB,CACvC,CAAC;QACF,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC;QACjF,MAAM,IAAI,GAAG;YACX,IAAI,EAAE,SAAS,OAAO,cAAc,YAAY,GAAG;YACnD,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;YAC5D,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC;SACrD,CAAC;QACF,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,UAAU,MAAM,wBAAwB,QAAQ,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;QAC5F,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,UAAU,MAAM,sBAAsB,EAAE,IAAI,CAAC,CAAC;QAC9E,CAAC;QAED,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,CAAC;IACrE,CAAC;IAED,gFAAgF;IAChF,KAAK,CAAC,UAAU,CAAC,OAAe,EAAE,GAAW;QAC3C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC5C,MAAM,IAAI,CAAC,EAAE,CAAC,OAAO,CACnB,KAAK,EACL,aAAa,IAAI,CAAC,EAAE,CAAC,SAAS,0BAA0B,IAAI,OAAO,EACnE,CAAC,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAC3D,CAAC;IACJ,CAAC;IAED,gEAAgE;IAChE,KAAK,CAAC,UAAU,CAAC,MAAc,EAAE,IAAyB;QACxD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;QACxC,IAAI,CAAC,IAAI;YAAE,OAAO,EAAE,CAAC;QACrB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACvC,MAAM,MAAM,GAAG,IAAI;aAChB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,MAAM,EAAE,CAAC,CAAC;aAC1D,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,UAAU,IAAI,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,EAAE,UAAU,IAAI,EAAE,CAAC,CAAC;aAC1F,KAAK,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QAC/B,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IACpF,CAAC;IAED,8DAA8D;IAC9D,KAAK,CAAC,UAAU,CAAC,MAAc,EAAE,EAAU;QACzC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,MAAM,EAAE,CAAC;YAAE,OAAO,IAAI,CAAC;QAC1D,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;QACxC,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QACvB,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,EAAE,QAAQ,CAAC;QAC9E,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QACvB,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;IAC1C,CAAC;IAED,kCAAkC;IAC1B,QAAQ,CACd,IAAY;QAEZ,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,CACpB,KAAK,EACL,aAAa,IAAI,CAAC,EAAE,CAAC,SAAS,0BAA0B,IAAI,kBAAkB,CAC/E,CAAC;IACJ,CAAC;IAED,sFAAsF;IAC9E,KAAK,CAAC,WAAW,CACvB,IAAY,EACZ,GAAW,EACX,IAA6B;QAE7B,IAAI,IAAwB,CAAC;QAC7B,IAAI,IAAwB,CAAC;QAC7B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,MAAM,CAC9B,aAAa,IAAI,CAAC,EAAE,CAAC,SAAS,0BAA0B,IAAI,WAAW,kBAAkB,CAAC,GAAG,CAAC,EAAE,CACjG,CAAC;YACF,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC3C,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,SAAS,CAAC;YAChC,IAAI,GAAG,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;QACnE,CAAC;QAAC,MAAM,CAAC;YACP,mCAAmC;QACrC,CAAC;QACD,OAAO;YACL,EAAE,EAAE,GAAG;YACP,IAAI,EAAE,eAAe,CAAC,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC;YACvC,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACjC,OAAO,EAAE,eAAe,CAAC,IAAI,EAAE,OAAO,IAAI,EAAE,CAAC;YAC7C,UAAU,EAAE,IAAI,EAAE,UAAU,IAAI,EAAE;YAClC,IAAI;YACJ,IAAI;SACL,CAAC;IACJ,CAAC;IAED,iEAAiE;IACzD,KAAK,CAAC,aAAa;QACzB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,OAAO,CAChC,KAAK,EACL,aAAa,IAAI,CAAC,EAAE,CAAC,SAAS,qCAAqC,CACpE,CAAC;QACF,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,cAAc,CAAC,EAAE,EAAE,CAAC;IAC1D,CAAC;IAED,0CAA0C;IAClC,KAAK,CAAC,iBAAiB;QAC7B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;QAC5C,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC;QAC9B,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,OAAO,CAC9B,MAAM,EACN,aAAa,IAAI,CAAC,EAAE,CAAC,SAAS,wBAAwB,EACtD,EAAE,KAAK,EAAE,cAAc,EAAE,CAC1B,CAAC;QACF,OAAO,EAAE,CAAC,EAAE,CAAC;IACf,CAAC;CACF;AAED,6EAA6E;AAC7E,SAAS,OAAO,CAAC,CAAS;IACxB,OAAO,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACnE,CAAC;AAED,mHAAmH;AACnH,SAAS,eAAe,CAAC,CAAS;IAChC,OAAO,CAAC,CAAC,OAAO,CAAC,iCAAiC,EAAE,CAAC,EAAE,EAAE,GAAW,EAAE,IAAY,EAAE,EAAE;QACpF,IAAI,CAAC;YACH,IAAI,GAAG,CAAC,WAAW,EAAE,KAAK,GAAG;gBAAE,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YACnF,MAAM,KAAK,GAAG,IAAI;iBACf,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC;iBAClB,OAAO,CAAC,oBAAoB,EAAE,CAAC,EAAE,EAAE,CAAS,EAAE,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;YAC1F,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACvD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Cloudflare Email Service(2026 发信,public beta)。
3
+ * 纯 REST 发信:POST /accounts/{id}/email/sending/send —— 无需部署 Worker 中转。
4
+ * 前置:Workers Paid + 域名托管 CF DNS + Cloudflare 凭证(API Token scope `Email Service:Edit`,
5
+ * 或 Global Key —— 实测两者皆可调用 Email Service)。
6
+ */
7
+ export class CloudflareOutbound {
8
+ cf;
9
+ id = "cloudflare";
10
+ constructor(cf) {
11
+ this.cf = cf;
12
+ }
13
+ /** CF Email Service 需要有效凭证(API Token 或 Global Key 均可);无凭证时提前给清晰错误。 */
14
+ assertAuth() {
15
+ if (this.cf.authMode() === "none")
16
+ throw new Error("Cloudflare Email Service 需要 Cloudflare 凭证 —— 请配置 CLOUDFLARE_API_TOKEN(含 Email Service:Edit),或 CLOUDFLARE_EMAIL + CLOUDFLARE_GLOBAL_API_KEY");
17
+ }
18
+ async send(msg) {
19
+ this.assertAuth();
20
+ const body = {
21
+ from: msg.from,
22
+ to: msg.to,
23
+ subject: msg.subject,
24
+ };
25
+ if (msg.text)
26
+ body.text = msg.text;
27
+ if (msg.html)
28
+ body.html = msg.html;
29
+ if (msg.cc)
30
+ body.cc = msg.cc;
31
+ if (msg.bcc)
32
+ body.bcc = msg.bcc;
33
+ if (msg.replyTo)
34
+ body.reply_to = msg.replyTo; // REST 层用 snake_case
35
+ const res = await this.cf.request("POST", `/accounts/${this.cf.accountId}/email/sending/send`, body);
36
+ const status = res.delivered?.length
37
+ ? "delivered"
38
+ : res.queued?.length
39
+ ? "queued"
40
+ : "sent";
41
+ return { id: `cf:${status}` };
42
+ }
43
+ /**
44
+ * onboard 发信子域 —— zone 级 `POST /zones/{id}/email/sending/subdomains`。
45
+ * CF 自动写 cf-bounce 的 MX/SPF/DKIM + DMARC,无需手动配 DNS;发信地址用 `@mail.<domain>`。
46
+ * zoneId 由 core 解析后传入。
47
+ */
48
+ async setupSendingDomain(domain, zoneId) {
49
+ this.assertAuth();
50
+ const sub = domain.startsWith("mail.") ? domain : `mail.${domain}`;
51
+ let enabled = true;
52
+ try {
53
+ const r = await this.cf.request("POST", `/zones/${zoneId}/email/sending/subdomains`, { name: sub });
54
+ enabled = r?.enabled ?? true;
55
+ }
56
+ catch (e) {
57
+ // 已 onboard / 已启用则视为成功
58
+ const msg = e instanceof Error ? e.message : String(e);
59
+ if (!/exist|enabled|already|duplicate/i.test(msg))
60
+ throw e;
61
+ }
62
+ return { domain: sub, verified: enabled, records: [] };
63
+ }
64
+ }
65
+ //# sourceMappingURL=cloudflare-outbound.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudflare-outbound.js","sourceRoot":"","sources":["../../../src/providers/email/cloudflare-outbound.ts"],"names":[],"mappings":"AAOA;;;;;GAKG;AACH,MAAM,OAAO,kBAAkB;IAGA;IAFpB,EAAE,GAAG,YAAY,CAAC;IAE3B,YAA6B,EAAoB;QAApB,OAAE,GAAF,EAAE,CAAkB;IAAG,CAAC;IAErD,sEAAsE;IAC9D,UAAU;QAChB,IAAI,IAAI,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,MAAM;YAC/B,MAAM,IAAI,KAAK,CACb,4IAA4I,CAC7I,CAAC;IACN,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,GAAkB;QAC3B,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,MAAM,IAAI,GAA4B;YACpC,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,EAAE,EAAE,GAAG,CAAC,EAAE;YACV,OAAO,EAAE,GAAG,CAAC,OAAO;SACrB,CAAC;QACF,IAAI,GAAG,CAAC,IAAI;YAAE,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;QACnC,IAAI,GAAG,CAAC,IAAI;YAAE,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;QACnC,IAAI,GAAG,CAAC,EAAE;YAAE,IAAI,CAAC,EAAE,GAAG,GAAG,CAAC,EAAE,CAAC;QAC7B,IAAI,GAAG,CAAC,GAAG;YAAE,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;QAChC,IAAI,GAAG,CAAC,OAAO;YAAE,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,qBAAqB;QAEnE,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,OAAO,CAI9B,MAAM,EAAE,aAAa,IAAI,CAAC,EAAE,CAAC,SAAS,qBAAqB,EAAE,IAAI,CAAC,CAAC;QAEtE,MAAM,MAAM,GAAG,GAAG,CAAC,SAAS,EAAE,MAAM;YAClC,CAAC,CAAC,WAAW;YACb,CAAC,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM;gBAClB,CAAC,CAAC,QAAQ;gBACV,CAAC,CAAC,MAAM,CAAC;QACb,OAAO,EAAE,EAAE,EAAE,MAAM,MAAM,EAAE,EAAE,CAAC;IAChC,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,kBAAkB,CAAC,MAAc,EAAE,MAAc;QACrD,IAAI,CAAC,UAAU,EAAE,CAAC;QAClB,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,MAAM,EAAE,CAAC;QACnE,IAAI,OAAO,GAAG,IAAI,CAAC;QACnB,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,OAAO,CAC7B,MAAM,EACN,UAAU,MAAM,2BAA2B,EAC3C,EAAE,IAAI,EAAE,GAAG,EAAE,CACd,CAAC;YACF,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,IAAI,CAAC;QAC/B,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,uBAAuB;YACvB,MAAM,GAAG,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YACvD,IAAI,CAAC,kCAAkC,CAAC,IAAI,CAAC,GAAG,CAAC;gBAAE,MAAM,CAAC,CAAC;QAC7D,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACzD,CAAC;CACF"}
@@ -0,0 +1,62 @@
1
+ const BASE = "https://api.resend.com";
2
+ class ResendError extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = "ResendError";
6
+ }
7
+ }
8
+ export class ResendOutbound {
9
+ apiKey;
10
+ id = "resend";
11
+ constructor(apiKey) {
12
+ this.apiKey = apiKey;
13
+ }
14
+ async request(method, path, body) {
15
+ if (!this.apiKey)
16
+ throw new ResendError("缺少 RESEND_API_KEY —— 请配置(或改用 EMAIL_OUTBOUND_PROVIDER)");
17
+ const res = await fetch(`${BASE}${path}`, {
18
+ method,
19
+ headers: {
20
+ Authorization: `Bearer ${this.apiKey}`,
21
+ "Content-Type": "application/json",
22
+ },
23
+ body: body === undefined ? undefined : JSON.stringify(body),
24
+ });
25
+ const json = (await res.json().catch(() => null));
26
+ if (!res.ok) {
27
+ const m = json?.message ?? `HTTP ${res.status}`;
28
+ throw new ResendError(`Resend API 失败: ${m}`);
29
+ }
30
+ return json;
31
+ }
32
+ async send(msg) {
33
+ const res = await this.request("POST", "/emails", {
34
+ from: msg.from,
35
+ to: msg.to,
36
+ subject: msg.subject,
37
+ text: msg.text,
38
+ html: msg.html,
39
+ cc: msg.cc,
40
+ bcc: msg.bcc,
41
+ reply_to: msg.replyTo, // 注意 REST 层是下划线
42
+ });
43
+ return { id: res.id };
44
+ }
45
+ /** Resend 自管发信域验证,不依赖 CF zone,故忽略 zoneId。 */
46
+ async setupSendingDomain(domain, _zoneId) {
47
+ const created = await this.request("POST", "/domains", { name: domain });
48
+ if (created.id) {
49
+ // 触发验证(记录可能尚未生效,失败可忽略,稍后重试)
50
+ await this.request("POST", `/domains/${created.id}/verify`).catch(() => { });
51
+ }
52
+ const records = (created.records ?? []).map((r) => ({
53
+ type: r.type,
54
+ name: r.name,
55
+ content: r.value,
56
+ ttl: typeof r.ttl === "number" ? r.ttl : undefined,
57
+ priority: r.priority,
58
+ }));
59
+ return { domain, verified: created.status === "verified", records };
60
+ }
61
+ }
62
+ //# sourceMappingURL=resend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resend.js","sourceRoot":"","sources":["../../../src/providers/email/resend.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ import { SESv2Client, SendEmailCommand, CreateEmailIdentityCommand, GetEmailIdentityCommand, } from "@aws-sdk/client-sesv2";
2
+ import { CloudflareDns } from "../dns/cloudflare.js";
3
+ /**
4
+ * Amazon SES(v2)发信 provider —— 规模化主力。
5
+ * onboard 纯 API(CreateEmailIdentity + Easy DKIM),并自动把 DKIM CNAME 写进 Cloudflare DNS,
6
+ * 因此"agent 注册一堆域名 → 自动 onboard 发信"可全 API 闭环。
7
+ */
8
+ export class SesOutbound {
9
+ aws;
10
+ cf;
11
+ id = "ses";
12
+ constructor(aws, cf) {
13
+ this.aws = aws;
14
+ this.cf = cf;
15
+ }
16
+ client() {
17
+ if (!this.aws.accessKeyId || !this.aws.secretAccessKey)
18
+ throw new Error("缺少 AWS 凭证 —— 请配置 AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY(+ AWS_REGION)");
19
+ return new SESv2Client({
20
+ region: this.aws.region,
21
+ credentials: {
22
+ accessKeyId: this.aws.accessKeyId,
23
+ secretAccessKey: this.aws.secretAccessKey,
24
+ },
25
+ });
26
+ }
27
+ async send(msg) {
28
+ const arr = (v) => (v ? (Array.isArray(v) ? v : [v]) : undefined);
29
+ const res = await this.client().send(new SendEmailCommand({
30
+ FromEmailAddress: msg.from,
31
+ Destination: {
32
+ ToAddresses: arr(msg.to),
33
+ CcAddresses: arr(msg.cc),
34
+ BccAddresses: arr(msg.bcc),
35
+ },
36
+ ReplyToAddresses: arr(msg.replyTo),
37
+ Content: {
38
+ Simple: {
39
+ Subject: { Data: msg.subject },
40
+ Body: {
41
+ ...(msg.text ? { Text: { Data: msg.text } } : {}),
42
+ ...(msg.html ? { Html: { Data: msg.html } } : {}),
43
+ },
44
+ },
45
+ },
46
+ }));
47
+ return { id: res.MessageId ?? "ses:sent" };
48
+ }
49
+ /** zoneId 由 core 解析后传入,用于把 DKIM CNAME 写进 Cloudflare DNS。 */
50
+ async setupSendingDomain(domain, zoneId) {
51
+ const client = this.client();
52
+ // 1) 创建发信身份(Easy DKIM);已存在则忽略
53
+ await client
54
+ .send(new CreateEmailIdentityCommand({ EmailIdentity: domain }))
55
+ .catch((e) => {
56
+ if (e?.name !== "AlreadyExistsException")
57
+ throw e;
58
+ });
59
+ // 2) 取 DKIM tokens → 生成 3 条 CNAME
60
+ const info = await client.send(new GetEmailIdentityCommand({ EmailIdentity: domain }));
61
+ const tokens = info.DkimAttributes?.Tokens ?? [];
62
+ const records = tokens.map((t) => ({
63
+ type: "CNAME",
64
+ name: `${t}._domainkey.${domain}`,
65
+ content: `${t}.dkim.amazonses.com`,
66
+ }));
67
+ // 3) 自动把 DKIM CNAME 写进 Cloudflare DNS(域名须已在 CF) —— 批量全自动的关键
68
+ const cfDns = new CloudflareDns(this.cf);
69
+ for (const r of records)
70
+ await cfDns.setRecord(zoneId, r);
71
+ return { domain, verified: info.VerifiedForSendingStatus === true, records };
72
+ }
73
+ }
74
+ //# sourceMappingURL=ses.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ses.js","sourceRoot":"","sources":["../../../src/providers/email/ses.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,0BAA0B,EAC1B,uBAAuB,GACxB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAerD;;;;GAIG;AACH,MAAM,OAAO,WAAW;IAIH;IACA;IAJV,EAAE,GAAG,KAAK,CAAC;IAEpB,YACmB,GAAa,EACb,EAAoB;QADpB,QAAG,GAAH,GAAG,CAAU;QACb,OAAE,GAAF,EAAE,CAAkB;IACpC,CAAC;IAEI,MAAM;QACZ,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,eAAe;YACpD,MAAM,IAAI,KAAK,CACb,0EAA0E,CAC3E,CAAC;QACJ,OAAO,IAAI,WAAW,CAAC;YACrB,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM;YACvB,WAAW,EAAE;gBACX,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,WAAW;gBACjC,eAAe,EAAE,IAAI,CAAC,GAAG,CAAC,eAAe;aAC1C;SACF,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,GAAkB;QAC3B,MAAM,GAAG,GAAG,CAAC,CAAqB,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QACtF,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,CAClC,IAAI,gBAAgB,CAAC;YACnB,gBAAgB,EAAE,GAAG,CAAC,IAAI;YAC1B,WAAW,EAAE;gBACX,WAAW,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBACxB,WAAW,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBACxB,YAAY,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC;aAC3B;YACD,gBAAgB,EAAE,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC;YAClC,OAAO,EAAE;gBACP,MAAM,EAAE;oBACN,OAAO,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,OAAO,EAAE;oBAC9B,IAAI,EAAE;wBACJ,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;wBACjD,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;qBAClD;iBACF;aACF;SACF,CAAC,CACH,CAAC;QACF,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,SAAS,IAAI,UAAU,EAAE,CAAC;IAC7C,CAAC;IAED,4DAA4D;IAC5D,KAAK,CAAC,kBAAkB,CAAC,MAAc,EAAE,MAAc;QACrD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAE7B,8BAA8B;QAC9B,MAAM,MAAM;aACT,IAAI,CAAC,IAAI,0BAA0B,CAAC,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC,CAAC;aAC/D,KAAK,CAAC,CAAC,CAAU,EAAE,EAAE;YACpB,IAAK,CAAuB,EAAE,IAAI,KAAK,wBAAwB;gBAAE,MAAM,CAAC,CAAC;QAC3E,CAAC,CAAC,CAAC;QAEL,kCAAkC;QAClC,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,uBAAuB,CAAC,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;QACvF,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,EAAE,MAAM,IAAI,EAAE,CAAC;QACjD,MAAM,OAAO,GAAgB,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC9C,IAAI,EAAE,OAAO;YACb,IAAI,EAAE,GAAG,CAAC,eAAe,MAAM,EAAE;YACjC,OAAO,EAAE,GAAG,CAAC,qBAAqB;SACnC,CAAC,CAAC,CAAC;QAEJ,4DAA4D;QAC5D,MAAM,KAAK,GAAG,IAAI,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACzC,KAAK,MAAM,CAAC,IAAI,OAAO;YAAE,MAAM,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAE1D,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,wBAAwB,KAAK,IAAI,EAAE,OAAO,EAAE,CAAC;IAC/E,CAAC;CACF"}
@@ -0,0 +1,33 @@
1
+ import { CloudflareRegistrar } from "./registrar/cloudflare.js";
2
+ import { CloudflareDns } from "./dns/cloudflare.js";
3
+ import { CloudflareInbound } from "./email/cloudflare-inbound.js";
4
+ import { ResendOutbound } from "./email/resend.js";
5
+ import { CloudflareOutbound } from "./email/cloudflare-outbound.js";
6
+ import { CloudflareDeploy } from "./deploy/cloudflare.js";
7
+ import { SesOutbound } from "./email/ses.js";
8
+ /** 按租户装配全部 provider(cf 已按账户解析好)。 */
9
+ export function buildProviders(deps) {
10
+ const { cf, platform, tenant } = deps;
11
+ let outbound;
12
+ return {
13
+ registrar: new CloudflareRegistrar(cf, platform.registrant),
14
+ dns: new CloudflareDns(cf),
15
+ emailInbound: new CloudflareInbound(cf),
16
+ deploy: new CloudflareDeploy(cf),
17
+ emailOutbound: () => (outbound ??= buildOutbound(tenant.outboundProvider, cf, platform)),
18
+ };
19
+ }
20
+ /** 按 outboundProvider 返回发信 provider(可插拔)。 */
21
+ function buildOutbound(id, cf, platform) {
22
+ switch (id) {
23
+ case "resend":
24
+ return new ResendOutbound(platform.resend.apiKey);
25
+ case "cloudflare":
26
+ return new CloudflareOutbound(cf);
27
+ case "ses":
28
+ return new SesOutbound(platform.aws, cf);
29
+ default:
30
+ throw new Error(`未知发信 provider: "${id}"(支持 cloudflare | ses | resend)`);
31
+ }
32
+ }
33
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAW7C,oCAAoC;AACpC,MAAM,UAAU,cAAc,CAAC,IAI9B;IACC,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACtC,IAAI,QAA2C,CAAC;IAChD,OAAO;QACL,SAAS,EAAE,IAAI,mBAAmB,CAAC,EAAE,EAAE,QAAQ,CAAC,UAAU,CAAC;QAC3D,GAAG,EAAE,IAAI,aAAa,CAAC,EAAE,CAAC;QAC1B,YAAY,EAAE,IAAI,iBAAiB,CAAC,EAAE,CAAC;QACvC,MAAM,EAAE,IAAI,gBAAgB,CAAC,EAAE,CAAC;QAChC,aAAa,EAAE,GAAG,EAAE,CAAC,CAAC,QAAQ,KAAK,aAAa,CAAC,MAAM,CAAC,gBAAgB,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAC;KACzF,CAAC;AACJ,CAAC;AAED,6CAA6C;AAC7C,SAAS,aAAa,CACpB,EAAU,EACV,EAAoB,EACpB,QAAwB;IAExB,QAAQ,EAAE,EAAE,CAAC;QACX,KAAK,QAAQ;YACX,OAAO,IAAI,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACpD,KAAK,YAAY;YACf,OAAO,IAAI,kBAAkB,CAAC,EAAE,CAAC,CAAC;QACpC,KAAK,KAAK;YACR,OAAO,IAAI,WAAW,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAC3C;YACE,MAAM,IAAI,KAAK,CAAC,mBAAmB,EAAE,iCAAiC,CAAC,CAAC;IAC5E,CAAC;AACH,CAAC"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Cloudflare Registrar(2026-04 beta)。
3
+ * 当前 beta 仅支持「查询 + 注册」,续费 / 转入 / 改联系人暂无 API。
4
+ */
5
+ export class CloudflareRegistrar {
6
+ cf;
7
+ registrant;
8
+ id = "cloudflare";
9
+ constructor(cf, registrant) {
10
+ this.cf = cf;
11
+ this.registrant = registrant;
12
+ }
13
+ async checkAvailability(domains) {
14
+ const res = await this.cf.request("POST", `/accounts/${this.cf.accountId}/registrar/domain-check`, { domains });
15
+ return (res.domains ?? []).map((d) => ({
16
+ domain: d.name,
17
+ available: d.registrable ?? d.available ?? false,
18
+ reason: d.reason,
19
+ currency: d.pricing?.currency,
20
+ registrationCost: d.pricing?.registration_cost != null ? Number(d.pricing.registration_cost) : undefined,
21
+ renewalCost: d.pricing?.renewal_cost != null ? Number(d.pricing.renewal_cost) : undefined,
22
+ }));
23
+ }
24
+ async register(domain, opts) {
25
+ const r = this.registrant;
26
+ const body = {
27
+ domain_name: domain,
28
+ auto_renew: opts?.autoRenew ?? false,
29
+ };
30
+ // 若配了完整 registrant 则显式带上,否则使用账户默认 registrant。
31
+ if (r.name && r.email && r.country) {
32
+ body.contacts = {
33
+ registrant: {
34
+ email: r.email,
35
+ phone: r.phone || undefined,
36
+ postal_info: {
37
+ name: r.name,
38
+ organization: r.org || undefined,
39
+ address: {
40
+ street: r.street,
41
+ city: r.city,
42
+ state: r.state,
43
+ postal_code: r.postalCode,
44
+ country_code: r.country,
45
+ },
46
+ },
47
+ },
48
+ };
49
+ }
50
+ const res = await this.cf.request("POST", `/accounts/${this.cf.accountId}/registrar/registrations`, body);
51
+ return {
52
+ domain: res.domain_name,
53
+ state: res.state,
54
+ completed: res.completed,
55
+ expiresAt: res.context?.registration?.expires_at,
56
+ };
57
+ }
58
+ /**
59
+ * 开启 / 关闭自动续费。
60
+ * 注意:方法是 PUT(不是 PATCH),且该端点只接受 auto_renew / locked / privacy。
61
+ * Cloudflare 无「主动续 N 年」的 API(只能在 Dashboard 操作)。
62
+ */
63
+ async setAutoRenew(domain, enabled) {
64
+ await this.cf.request("PUT", `/accounts/${this.cf.accountId}/registrar/domains/${domain}`, {
65
+ auto_renew: enabled,
66
+ });
67
+ }
68
+ async listRegistered() {
69
+ const res = await this.cf.request("GET", `/accounts/${this.cf.accountId}/registrar/domains`);
70
+ return (res ?? []).map((d) => ({
71
+ domain: d.name ?? d.id ?? "",
72
+ expiresAt: d.expires_at,
73
+ locked: d.locked,
74
+ currentRegistrar: d.current_registrar,
75
+ }));
76
+ }
77
+ }
78
+ //# sourceMappingURL=cloudflare.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudflare.js","sourceRoot":"","sources":["../../../src/providers/registrar/cloudflare.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ export async function selfcheck(ctx, testDomain = "example-clize-test.com") {
2
+ console.log("=== Clize 连通性自检 ===\n");
3
+ const mode = ctx.cf.authMode();
4
+ console.log("配置:");
5
+ console.log(" Cloudflare 认证 :", mode === "token"
6
+ ? "API Token"
7
+ : mode === "global"
8
+ ? "Global API Key(建议改用 API Token)"
9
+ : "❌ 未配置");
10
+ console.log(" Cloudflare Account:", ctx.cf.accountId || "❌ 缺失");
11
+ console.log(" 发信 Provider :", ctx.tenant.outboundProvider);
12
+ console.log(" Resend Key :", ctx.platform.resend.apiKey ? "已配置" : "(M4 才需要)");
13
+ console.log(" 演练模式 DRY_RUN :", ctx.tenant.dryRun);
14
+ console.log(" 月度开销上限 :", ctx.tenant.monthlySpendLimitUsd ?? "不限");
15
+ console.log();
16
+ if (!ctx.cf.isConfigured()) {
17
+ console.log("⚠️ 请先配置 Cloudflare 凭证(API Token 或 Global Key)+ CLOUDFLARE_ACCOUNT_ID");
18
+ process.exitCode = 1;
19
+ return;
20
+ }
21
+ // 1) 验证 token + account
22
+ try {
23
+ const accounts = await ctx.cf.request("GET", "/accounts");
24
+ console.log("✅ Cloudflare Token 有效,可见账户:", accounts.map((a) => a.name).join(", ") || "(无)");
25
+ const match = accounts.find((a) => a.id === ctx.cf.accountId);
26
+ console.log(match ? `✅ Account ID 匹配:${match.name}` : "⚠️ 配置的 Account ID 不在可见账户列表中,请核对");
27
+ }
28
+ catch (e) {
29
+ console.log("❌ Cloudflare API 调用失败:", e instanceof Error ? e.message : String(e));
30
+ console.log(" 排查:token 是否有效?是否包含 Account 级读取权限?");
31
+ process.exitCode = 1;
32
+ return;
33
+ }
34
+ console.log();
35
+ // 2) 域名查价(只读,不花钱)—— 验证 Registrar API 是否对账户开放
36
+ try {
37
+ const [r] = await ctx.providers.registrar.checkAvailability([testDomain]);
38
+ console.log(`✅ domain search 可用 → ${testDomain}`);
39
+ console.log(` 可注册: ${r.available}${r.reason ? ` (${r.reason})` : ""}` +
40
+ (r.registrationCost != null ? ` | 注册价 ${r.registrationCost} ${r.currency}` : ""));
41
+ }
42
+ catch (e) {
43
+ console.log("⚠️ Registrar 查询失败:", e instanceof Error ? e.message : String(e));
44
+ console.log(" 可能原因:token 缺 Registrar 权限,或 Registrar API(beta)尚未对你的账户开放。");
45
+ console.log(" 注:即使如此,DNS / 部署 / 邮件能力仍可正常使用。");
46
+ }
47
+ console.log("\n=== 自检完成 ===");
48
+ }
49
+ //# sourceMappingURL=selfcheck.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selfcheck.js","sourceRoot":"","sources":["../src/selfcheck.ts"],"names":[],"mappings":"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"}