run402 1.30.0 → 1.32.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.
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Wallet auth helper — generates EIP-191 signature headers for Run402 API.
3
+ * Uses @noble/curves (lighter than viem) for signing.
4
+ */
5
+ import { secp256k1 } from "@noble/curves/secp256k1.js";
6
+ import { keccak_256 } from "@noble/hashes/sha3.js";
7
+ import { bytesToHex } from "@noble/hashes/utils.js";
8
+ import { readWallet } from "./wallet.js";
9
+ /**
10
+ * EIP-191 personal_sign: sign a message with the wallet's private key.
11
+ */
12
+ function personalSign(privateKeyHex, address, message) {
13
+ const msgBytes = new TextEncoder().encode(message);
14
+ const prefix = new TextEncoder().encode(`\x19Ethereum Signed Message:\n${msgBytes.length}`);
15
+ const prefixed = new Uint8Array(prefix.length + msgBytes.length);
16
+ prefixed.set(prefix);
17
+ prefixed.set(msgBytes, prefix.length);
18
+ const hash = keccak_256(prefixed);
19
+ const pkHex = privateKeyHex.startsWith("0x")
20
+ ? privateKeyHex.slice(2)
21
+ : privateKeyHex;
22
+ const pkBytes = Uint8Array.from(Buffer.from(pkHex, "hex"));
23
+ const rawSig = secp256k1.sign(hash, pkBytes);
24
+ const sig = secp256k1.Signature.fromBytes(rawSig);
25
+ // Determine recovery bit by trying both and matching the address
26
+ let recovery = 0;
27
+ for (const v of [0, 1]) {
28
+ try {
29
+ const recovered = sig.addRecoveryBit(v).recoverPublicKey(hash);
30
+ const pubBytes = recovered.toBytes(false).slice(1); // uncompressed, drop 04 prefix
31
+ const addrBytes = keccak_256(pubBytes).slice(-20);
32
+ if ("0x" + bytesToHex(addrBytes) === address.toLowerCase()) {
33
+ recovery = v;
34
+ break;
35
+ }
36
+ }
37
+ catch {
38
+ continue;
39
+ }
40
+ }
41
+ const r = sig.r.toString(16).padStart(64, "0");
42
+ const s = sig.s.toString(16).padStart(64, "0");
43
+ const vHex = (recovery + 27).toString(16).padStart(2, "0");
44
+ return "0x" + r + s + vHex;
45
+ }
46
+ /**
47
+ * Get wallet auth headers for the Run402 API.
48
+ * Returns null if no wallet is configured.
49
+ */
50
+ export function getWalletAuthHeaders(walletPath) {
51
+ const wallet = readWallet(walletPath);
52
+ if (!wallet || !wallet.address || !wallet.privateKey)
53
+ return null;
54
+ const timestamp = Math.floor(Date.now() / 1000).toString();
55
+ const signature = personalSign(wallet.privateKey, wallet.address, `run402:${timestamp}`);
56
+ return {
57
+ "X-Run402-Wallet": wallet.address,
58
+ "X-Run402-Signature": signature,
59
+ "X-Run402-Timestamp": timestamp,
60
+ };
61
+ }
62
+ //# sourceMappingURL=wallet-auth.js.map
@@ -0,0 +1,25 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync, renameSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { randomBytes } from "node:crypto";
4
+ import { getWalletPath } from "./config.js";
5
+ export function readWallet(path) {
6
+ const p = path ?? getWalletPath();
7
+ if (!existsSync(p))
8
+ return null;
9
+ try {
10
+ return JSON.parse(readFileSync(p, "utf-8"));
11
+ }
12
+ catch {
13
+ return null;
14
+ }
15
+ }
16
+ export function saveWallet(data, path) {
17
+ const p = path ?? getWalletPath();
18
+ const dir = dirname(p);
19
+ mkdirSync(dir, { recursive: true });
20
+ const tmp = join(dir, `.wallet.${randomBytes(4).toString("hex")}.tmp`);
21
+ writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 0o600 });
22
+ renameSync(tmp, p);
23
+ chmodSync(p, 0o600);
24
+ }
25
+ //# sourceMappingURL=wallet.js.map
package/lib/projects.mjs CHANGED
@@ -18,7 +18,7 @@ Subcommands:
18
18
  usage <id> Show compute/storage usage for a project
19
19
  schema <id> Inspect the database schema
20
20
  rls <id> <template> <tables_json> Apply Row-Level Security policies
21
- delete <id> Delete a project and remove it from local state
21
+ delete <id> Soft-delete a project (enters ~104-day grace; renew to reactivate) and remove it from local state
22
22
  pin <id> Pin a project (prevents expiry/GC)
23
23
  promote-user <id> <email> Promote a user to project_admin role
24
24
  demote-user <id> <email> Demote a user from project_admin role
@@ -9,11 +9,15 @@ Subcommands:
9
9
  register <domain> [--project <id>] Register a custom sender domain (returns DNS records)
10
10
  status [--project <id>] Check domain verification status
11
11
  remove [--project <id>] Remove custom sender domain
12
+ inbound-enable <domain> [--project <id>] Enable inbound email (requires DKIM-verified)
13
+ inbound-disable <domain> [--project <id>] Disable inbound email
12
14
 
13
15
  Examples:
14
16
  run402 sender-domain register kysigned.com
15
17
  run402 sender-domain status
16
18
  run402 sender-domain remove
19
+ run402 sender-domain inbound-enable kysigned.com
20
+ run402 sender-domain inbound-disable kysigned.com
17
21
  `;
18
22
 
19
23
  function parseFlag(args, flag) {
@@ -82,12 +86,43 @@ async function remove(args) {
82
86
  console.log(JSON.stringify(data));
83
87
  }
84
88
 
89
+ async function inboundToggle(action, args) {
90
+ let domain = null;
91
+ let projectOpt = null;
92
+ for (let i = 0; i < args.length; i++) {
93
+ if (args[i] === "--project" && args[i + 1]) { projectOpt = args[++i]; }
94
+ else if (!args[i].startsWith("--") && !domain) { domain = args[i]; }
95
+ }
96
+ const projectId = resolveProjectId(projectOpt);
97
+ const p = findProject(projectId);
98
+
99
+ if (!domain) {
100
+ console.error(JSON.stringify({ status: "error", message: `Missing domain. Usage: run402 sender-domain inbound-${action} <domain>` }));
101
+ process.exit(1);
102
+ }
103
+
104
+ const method = action === "enable" ? "POST" : "DELETE";
105
+ const res = await fetch(`${API}/email/v1/domains/inbound`, {
106
+ method,
107
+ headers: { apikey: p.service_key, "Content-Type": "application/json" },
108
+ body: JSON.stringify({ domain }),
109
+ });
110
+ const data = await res.json();
111
+ if (!res.ok) {
112
+ console.error(JSON.stringify({ status: "error", http: res.status, ...data }));
113
+ process.exit(1);
114
+ }
115
+ console.log(JSON.stringify(data, null, 2));
116
+ }
117
+
85
118
  export async function run(sub, args) {
86
119
  if (!sub || sub === "--help" || sub === "-h") { console.log(HELP); process.exit(0); }
87
120
  switch (sub) {
88
121
  case "register": await register(args); break;
89
122
  case "status": await status(args); break;
90
123
  case "remove": await remove(args); break;
124
+ case "inbound-enable": await inboundToggle("enable", args); break;
125
+ case "inbound-disable": await inboundToggle("disable", args); break;
91
126
  default:
92
127
  console.error(`Unknown subcommand: ${sub}\n`);
93
128
  console.log(HELP);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "run402",
3
- "version": "1.30.0",
3
+ "version": "1.32.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": {