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/webhooks.mjs CHANGED
@@ -1,4 +1,6 @@
1
- import { findProject, resolveProjectId, API, loadKeyStore, updateProject } from "./config.mjs";
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 webhooks — Manage mailbox webhooks
4
6
 
@@ -26,39 +28,11 @@ const SUB_HELP = {
26
28
 
27
29
  Usage:
28
30
  run402 email webhooks update <webhook_id> [--url <url>] [--events <e1,e2>] [--project <id>]
29
-
30
- Arguments:
31
- <webhook_id> Webhook ID to update
32
-
33
- Options:
34
- --url <url> New delivery URL for the webhook
35
- --events <e1,e2> Comma-separated event list to replace the current events
36
- Valid: delivery, bounced, complained, reply_received
37
- --project <id> Project ID (defaults to the active project)
38
-
39
- Notes:
40
- - Provide at least one of --url or --events
41
-
42
- Examples:
43
- run402 email webhooks update whk_123 --url https://new.example.com/hook
44
- run402 email webhooks update whk_123 --events delivery,bounced
45
31
  `,
46
32
  register: `run402 email webhooks register — Register a new webhook
47
33
 
48
34
  Usage:
49
35
  run402 email webhooks register --url <url> --events <e1,e2> [--project <id>]
50
-
51
- Options:
52
- --url <url> Delivery URL for the webhook (required)
53
- --events <e1,e2> Comma-separated event list (required)
54
- Valid: delivery, bounced, complained, reply_received
55
- --project <id> Project ID (defaults to the active project)
56
-
57
- Examples:
58
- run402 email webhooks register --url https://example.com/hook \\
59
- --events delivery,bounced
60
- run402 email webhooks register --url https://example.com/hook \\
61
- --events reply_received --project proj123
62
36
  `,
63
37
  };
64
38
 
@@ -69,53 +43,14 @@ function parseFlag(args, flag) {
69
43
  return null;
70
44
  }
71
45
 
72
- async function resolveMailboxId(projectId, serviceKey) {
73
- const store = loadKeyStore();
74
- const proj = store.projects[projectId];
75
- if (proj && proj.mailbox_id) return proj.mailbox_id;
76
-
77
- const res = await fetch(`${API}/mailboxes/v1`, {
78
- headers: { "Authorization": `Bearer ${serviceKey}` },
79
- });
80
- if (!res.ok) {
81
- const data = await res.json().catch(() => ({}));
82
- throw Object.assign(new Error("Failed to resolve mailbox"), { http: res.status, ...data });
83
- }
84
- const body = await res.json();
85
- const mailboxes = body.mailboxes || body;
86
- if (!Array.isArray(mailboxes) || mailboxes.length === 0) {
87
- throw new Error("No mailbox found. Run: run402 email create <slug>");
88
- }
89
- const mb = mailboxes[0];
90
- updateProject(projectId, { mailbox_id: mb.mailbox_id, mailbox_address: mb.address });
91
- return mb.mailbox_id;
92
- }
93
-
94
- async function requireMailboxId(projectId, serviceKey) {
95
- try {
96
- return await resolveMailboxId(projectId, serviceKey);
97
- } catch (err) {
98
- const out = { status: "error", message: err.message };
99
- if (err.http) out.http = err.http;
100
- console.error(JSON.stringify(out));
101
- process.exit(1);
102
- }
103
- }
104
-
105
46
  async function list(args) {
106
47
  const projectId = resolveProjectId(parseFlag(args, "--project"));
107
- const p = findProject(projectId);
108
- const mailboxId = await requireMailboxId(projectId, p.service_key);
109
-
110
- const res = await fetch(`${API}/mailboxes/v1/${mailboxId}/webhooks`, {
111
- headers: { "Authorization": `Bearer ${p.service_key}` },
112
- });
113
- const data = await res.json();
114
- if (!res.ok) {
115
- console.error(JSON.stringify({ status: "error", http: res.status, ...data }));
116
- process.exit(1);
48
+ try {
49
+ const data = await getSdk().email.webhooks.list(projectId);
50
+ console.log(JSON.stringify(data, null, 2));
51
+ } catch (err) {
52
+ reportSdkError(err);
117
53
  }
118
- console.log(JSON.stringify(data, null, 2));
119
54
  }
120
55
 
121
56
  async function get(args) {
@@ -126,23 +61,16 @@ async function get(args) {
126
61
  else if (!args[i].startsWith("--") && !webhookId) { webhookId = args[i]; }
127
62
  }
128
63
  const projectId = resolveProjectId(projectOpt);
129
- const p = findProject(projectId);
130
-
131
64
  if (!webhookId) {
132
65
  console.error(JSON.stringify({ status: "error", message: "Missing webhook_id. Usage: run402 email webhooks get <webhook_id>" }));
133
66
  process.exit(1);
134
67
  }
135
-
136
- const mailboxId = await requireMailboxId(projectId, p.service_key);
137
- const res = await fetch(`${API}/mailboxes/v1/${mailboxId}/webhooks/${webhookId}`, {
138
- headers: { "Authorization": `Bearer ${p.service_key}` },
139
- });
140
- const data = await res.json();
141
- if (!res.ok) {
142
- console.error(JSON.stringify({ status: "error", http: res.status, ...data }));
143
- process.exit(1);
68
+ try {
69
+ const data = await getSdk().email.webhooks.get(projectId, webhookId);
70
+ console.log(JSON.stringify(data, null, 2));
71
+ } catch (err) {
72
+ reportSdkError(err);
144
73
  }
145
- console.log(JSON.stringify(data, null, 2));
146
74
  }
147
75
 
148
76
  async function del(args) {
@@ -153,25 +81,16 @@ async function del(args) {
153
81
  else if (!args[i].startsWith("--") && !webhookId) { webhookId = args[i]; }
154
82
  }
155
83
  const projectId = resolveProjectId(projectOpt);
156
- const p = findProject(projectId);
157
-
158
84
  if (!webhookId) {
159
85
  console.error(JSON.stringify({ status: "error", message: "Missing webhook_id. Usage: run402 email webhooks delete <webhook_id>" }));
160
86
  process.exit(1);
161
87
  }
162
-
163
- const mailboxId = await requireMailboxId(projectId, p.service_key);
164
- const res = await fetch(`${API}/mailboxes/v1/${mailboxId}/webhooks/${webhookId}`, {
165
- method: "DELETE",
166
- headers: { "Authorization": `Bearer ${p.service_key}` },
167
- });
168
- if (!res.ok) {
169
- let errBody;
170
- try { errBody = await res.json(); } catch { errBody = {}; }
171
- console.error(JSON.stringify({ status: "error", http: res.status, ...errBody }));
172
- process.exit(1);
88
+ try {
89
+ await getSdk().email.webhooks.delete(projectId, webhookId);
90
+ console.log(JSON.stringify({ status: "ok", webhook_id: webhookId, deleted: true }));
91
+ } catch (err) {
92
+ reportSdkError(err);
173
93
  }
174
- console.log(JSON.stringify({ status: "ok", webhook_id: webhookId, deleted: true }));
175
94
  }
176
95
 
177
96
  async function update(args) {
@@ -186,8 +105,6 @@ async function update(args) {
186
105
  else if (!args[i].startsWith("--") && !webhookId) { webhookId = args[i]; }
187
106
  }
188
107
  const projectId = resolveProjectId(projectOpt);
189
- const p = findProject(projectId);
190
-
191
108
  if (!webhookId) {
192
109
  console.error(JSON.stringify({ status: "error", message: "Missing webhook_id. Usage: run402 email webhooks update <webhook_id> [--url <url>] [--events <e1,e2>]" }));
193
110
  process.exit(1);
@@ -197,22 +114,15 @@ async function update(args) {
197
114
  process.exit(1);
198
115
  }
199
116
 
200
- const body = {};
201
- if (url) body.url = url;
202
- if (eventsRaw) body.events = eventsRaw.split(",").map(e => e.trim());
203
-
204
- const mailboxId = await requireMailboxId(projectId, p.service_key);
205
- const res = await fetch(`${API}/mailboxes/v1/${mailboxId}/webhooks/${webhookId}`, {
206
- method: "PATCH",
207
- headers: { "Authorization": `Bearer ${p.service_key}`, "Content-Type": "application/json" },
208
- body: JSON.stringify(body),
209
- });
210
- const data = await res.json();
211
- if (!res.ok) {
212
- console.error(JSON.stringify({ status: "error", http: res.status, ...data }));
213
- process.exit(1);
117
+ try {
118
+ const data = await getSdk().email.webhooks.update(projectId, webhookId, {
119
+ url: url ?? undefined,
120
+ events: eventsRaw ? eventsRaw.split(",").map((e) => e.trim()) : undefined,
121
+ });
122
+ console.log(JSON.stringify({ status: "ok", ...data }));
123
+ } catch (err) {
124
+ reportSdkError(err);
214
125
  }
215
- console.log(JSON.stringify({ status: "ok", ...data }));
216
126
  }
217
127
 
218
128
  async function register(args) {
@@ -220,7 +130,6 @@ async function register(args) {
220
130
  const eventsRaw = parseFlag(args, "--events");
221
131
  const projectOpt = parseFlag(args, "--project");
222
132
  const projectId = resolveProjectId(projectOpt);
223
- const p = findProject(projectId);
224
133
 
225
134
  if (!url) {
226
135
  console.error(JSON.stringify({ status: "error", message: "Missing --url. Usage: run402 email webhooks register --url <url> --events <e1,e2>" }));
@@ -231,19 +140,13 @@ async function register(args) {
231
140
  process.exit(1);
232
141
  }
233
142
 
234
- const events = eventsRaw.split(",").map(e => e.trim());
235
- const mailboxId = await requireMailboxId(projectId, p.service_key);
236
- const res = await fetch(`${API}/mailboxes/v1/${mailboxId}/webhooks`, {
237
- method: "POST",
238
- headers: { "Authorization": `Bearer ${p.service_key}`, "Content-Type": "application/json" },
239
- body: JSON.stringify({ url, events }),
240
- });
241
- const data = await res.json();
242
- if (!res.ok) {
243
- console.error(JSON.stringify({ status: "error", http: res.status, ...data }));
244
- process.exit(1);
143
+ const events = eventsRaw.split(",").map((e) => e.trim());
144
+ try {
145
+ const data = await getSdk().email.webhooks.register(projectId, { url, events });
146
+ console.log(JSON.stringify({ status: "ok", ...data }));
147
+ } catch (err) {
148
+ reportSdkError(err);
245
149
  }
246
- console.log(JSON.stringify({ status: "ok", ...data }));
247
150
  }
248
151
 
249
152
  export async function run(sub, args) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "run402",
3
- "version": "1.38.0",
3
+ "version": "1.40.0",
4
4
  "description": "CLI for Run402 — provision Postgres databases, deploy static sites, generate images, and manage wallets via x402 and MPP micropayments.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,107 +0,0 @@
1
- /**
2
- * Shared payment wrapper for CLI commands that need paid fetch.
3
- * Branches on allowance rail:
4
- * - "mpp": uses mppx.fetch (Tempo pathUSD)
5
- * - "x402" (default): uses @x402/fetch (Base USDC)
6
- *
7
- * Checks on-chain balances at setup time and selects funded networks.
8
- */
9
-
10
- import { readAllowance, ALLOWANCE_FILE } from "./config.mjs";
11
- import { existsSync } from "fs";
12
-
13
- const USDC_ABI = [{ name: "balanceOf", type: "function", stateMutability: "view", inputs: [{ name: "account", type: "address" }], outputs: [{ name: "", type: "uint256" }] }];
14
- const USDC_MAINNET = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
15
- const USDC_SEPOLIA = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
16
- const PATH_USD = "0x20c0000000000000000000000000000000000000";
17
- const TEMPO_RPC = "https://rpc.moderato.tempo.xyz/";
18
-
19
- async function checkBalance(publicClient, tokenAddress, walletAddress) {
20
- try {
21
- const raw = await publicClient.readContract({
22
- address: tokenAddress,
23
- abi: USDC_ABI,
24
- functionName: "balanceOf",
25
- args: [walletAddress],
26
- });
27
- return Number(raw);
28
- } catch {
29
- return 0;
30
- }
31
- }
32
-
33
- export async function setupPaidFetch() {
34
- if (!existsSync(ALLOWANCE_FILE)) {
35
- console.error(JSON.stringify({ status: "error", message: "No agent allowance found. Run: run402 allowance create && run402 allowance fund" }));
36
- process.exit(1);
37
- }
38
- const allowance = readAllowance();
39
- const { privateKeyToAccount } = await import("viem/accounts");
40
- const account = privateKeyToAccount(allowance.privateKey);
41
-
42
- if (allowance.rail === "mpp") {
43
- const { createPublicClient, http, defineChain } = await import("viem");
44
- const tempoModerato = defineChain({
45
- id: 42431,
46
- name: "Tempo Moderato",
47
- nativeCurrency: { name: "pathUSD", symbol: "pathUSD", decimals: 6 },
48
- rpcUrls: { default: { http: [TEMPO_RPC] } },
49
- });
50
- const tempoClient = createPublicClient({ chain: tempoModerato, transport: http() });
51
- const balance = await checkBalance(tempoClient, PATH_USD, allowance.address);
52
- if (balance === 0) {
53
- console.error(JSON.stringify({
54
- status: "error",
55
- message: `No pathUSD balance on Tempo Moderato (0). Fund your wallet: run402 allowance fund`,
56
- }));
57
- process.exit(1);
58
- }
59
-
60
- const { Mppx, tempo } = await import("mppx/client");
61
- const mppx = Mppx.create({
62
- polyfill: false,
63
- methods: [tempo({ account })],
64
- });
65
- return mppx.fetch;
66
- }
67
-
68
- // Default: x402
69
- const { createPublicClient, http } = await import("viem");
70
- const { base, baseSepolia } = await import("viem/chains");
71
- const { x402Client, wrapFetchWithPayment } = await import("@x402/fetch");
72
- const { ExactEvmScheme } = await import("@x402/evm/exact/client");
73
- const { toClientEvmSigner } = await import("@x402/evm");
74
-
75
- const mainnetClient = createPublicClient({ chain: base, transport: http() });
76
- const sepoliaClient = createPublicClient({ chain: baseSepolia, transport: http() });
77
-
78
- // Check balances in parallel
79
- const [mainnetBalance, sepoliaBalance] = await Promise.all([
80
- checkBalance(mainnetClient, USDC_MAINNET, allowance.address),
81
- checkBalance(sepoliaClient, USDC_SEPOLIA, allowance.address),
82
- ]);
83
-
84
- if (mainnetBalance === 0 && sepoliaBalance === 0) {
85
- console.error(JSON.stringify({
86
- status: "error",
87
- message: `No USDC balance on any supported network (Base: $${(mainnetBalance / 1e6).toFixed(2)}, Base Sepolia: $${(sepoliaBalance / 1e6).toFixed(2)}). Fund your wallet or run: run402 allowance fund`,
88
- }));
89
- process.exit(1);
90
- }
91
-
92
- const client = new x402Client();
93
- client.register("eip155:8453", new ExactEvmScheme(toClientEvmSigner(account, mainnetClient)));
94
- client.register("eip155:84532", new ExactEvmScheme(toClientEvmSigner(account, sepoliaClient)));
95
-
96
- // Policy: only allow networks where the wallet has funds
97
- client.registerPolicy((_version, reqs) => {
98
- const funded = reqs.filter((r) => {
99
- if (r.network === "eip155:8453") return mainnetBalance > 0;
100
- if (r.network === "eip155:84532") return sepoliaBalance > 0;
101
- return false;
102
- });
103
- return funded.length > 0 ? funded : reqs;
104
- });
105
-
106
- return wrapFetchWithPayment(fetch, client);
107
- }