run402 1.26.0 → 1.28.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/cli.mjs +18 -0
- package/lib/auth.mjs +153 -0
- package/lib/billing.mjs +169 -0
- package/lib/sender-domain.mjs +96 -0
- package/package.json +1 -1
- package/core-dist/wallet-auth.js +0 -62
- package/core-dist/wallet.js +0 -25
package/cli.mjs
CHANGED
|
@@ -37,6 +37,9 @@ Commands:
|
|
|
37
37
|
image Generate AI images via x402 or MPP micropayments
|
|
38
38
|
email Send template-based emails from your project
|
|
39
39
|
message Send messages to Run402 developers
|
|
40
|
+
auth Manage project user authentication (magic link, passwords, settings)
|
|
41
|
+
sender-domain Manage custom email sender domain (register, status, remove)
|
|
42
|
+
billing Email billing accounts, Stripe tier checkout, email packs
|
|
40
43
|
agent Manage agent identity (contact info)
|
|
41
44
|
|
|
42
45
|
Run 'run402 <command> --help' for detailed usage of each command.
|
|
@@ -159,6 +162,21 @@ switch (cmd) {
|
|
|
159
162
|
await run(sub, rest);
|
|
160
163
|
break;
|
|
161
164
|
}
|
|
165
|
+
case "auth": {
|
|
166
|
+
const { run } = await import("./lib/auth.mjs");
|
|
167
|
+
await run(sub, rest);
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
case "sender-domain": {
|
|
171
|
+
const { run } = await import("./lib/sender-domain.mjs");
|
|
172
|
+
await run(sub, rest);
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
case "billing": {
|
|
176
|
+
const { run } = await import("./lib/billing.mjs");
|
|
177
|
+
await run(sub, rest);
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
162
180
|
default:
|
|
163
181
|
console.error(`Unknown command: ${cmd}\n`);
|
|
164
182
|
console.log(HELP);
|
package/lib/auth.mjs
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { findProject, resolveProjectId, API } from "./config.mjs";
|
|
2
|
+
|
|
3
|
+
const HELP = `run402 auth — Manage project user authentication
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
run402 auth <subcommand> [args...]
|
|
7
|
+
|
|
8
|
+
Subcommands:
|
|
9
|
+
magic-link --email <addr> --redirect <url> [--project <id>]
|
|
10
|
+
Send a passwordless login link to the given email. Auto-creates user on first use.
|
|
11
|
+
|
|
12
|
+
verify --token <token> [--project <id>]
|
|
13
|
+
Exchange a magic link token for access_token + refresh_token.
|
|
14
|
+
|
|
15
|
+
set-password --token <bearer> --new <password> [--current <password>] [--project <id>]
|
|
16
|
+
Change, reset, or set a user's password. Requires the user's access_token.
|
|
17
|
+
|
|
18
|
+
settings --allow-password-set <true|false> [--project <id>]
|
|
19
|
+
Update project auth settings (requires service_key).
|
|
20
|
+
|
|
21
|
+
providers [--project <id>]
|
|
22
|
+
List available auth providers for the project.
|
|
23
|
+
|
|
24
|
+
Examples:
|
|
25
|
+
run402 auth magic-link --email user@example.com --redirect https://myapp.run402.com/cb
|
|
26
|
+
run402 auth verify --token abc123def456
|
|
27
|
+
run402 auth set-password --token eyJ... --new "new-pass" --current "old-pass"
|
|
28
|
+
run402 auth settings --allow-password-set true
|
|
29
|
+
run402 auth providers
|
|
30
|
+
`;
|
|
31
|
+
|
|
32
|
+
function parseFlag(args, flag) {
|
|
33
|
+
for (let i = 0; i < args.length; i++) {
|
|
34
|
+
if (args[i] === flag && args[i + 1]) return args[i + 1];
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function magicLink(args) {
|
|
40
|
+
const email = parseFlag(args, "--email");
|
|
41
|
+
const redirect = parseFlag(args, "--redirect");
|
|
42
|
+
const projectId = resolveProjectId(parseFlag(args, "--project"));
|
|
43
|
+
const p = findProject(projectId);
|
|
44
|
+
|
|
45
|
+
if (!email) { console.error(JSON.stringify({ status: "error", message: "Missing --email" })); process.exit(1); }
|
|
46
|
+
if (!redirect) { console.error(JSON.stringify({ status: "error", message: "Missing --redirect <url>" })); process.exit(1); }
|
|
47
|
+
|
|
48
|
+
const res = await fetch(`${API}/auth/v1/magic-link`, {
|
|
49
|
+
method: "POST",
|
|
50
|
+
headers: { "Authorization": `Bearer ${p.anon_key}`, "Content-Type": "application/json" },
|
|
51
|
+
body: JSON.stringify({ email, redirect_url: redirect }),
|
|
52
|
+
});
|
|
53
|
+
const data = await res.json();
|
|
54
|
+
if (!res.ok) {
|
|
55
|
+
console.error(JSON.stringify({ status: "error", http: res.status, ...data }));
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
console.log(JSON.stringify({ status: "ok", ...data }));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function verify(args) {
|
|
62
|
+
const token = parseFlag(args, "--token");
|
|
63
|
+
const projectId = resolveProjectId(parseFlag(args, "--project"));
|
|
64
|
+
const p = findProject(projectId);
|
|
65
|
+
|
|
66
|
+
if (!token) { console.error(JSON.stringify({ status: "error", message: "Missing --token" })); process.exit(1); }
|
|
67
|
+
|
|
68
|
+
const res = await fetch(`${API}/auth/v1/token?grant_type=magic_link`, {
|
|
69
|
+
method: "POST",
|
|
70
|
+
headers: { "Authorization": `Bearer ${p.anon_key}`, "Content-Type": "application/json" },
|
|
71
|
+
body: JSON.stringify({ token }),
|
|
72
|
+
});
|
|
73
|
+
const data = await res.json();
|
|
74
|
+
if (!res.ok) {
|
|
75
|
+
console.error(JSON.stringify({ status: "error", http: res.status, ...data }));
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
console.log(JSON.stringify({ status: "ok", ...data }));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function setPassword(args) {
|
|
82
|
+
const accessToken = parseFlag(args, "--token");
|
|
83
|
+
const newPassword = parseFlag(args, "--new");
|
|
84
|
+
const currentPassword = parseFlag(args, "--current");
|
|
85
|
+
|
|
86
|
+
if (!accessToken) { console.error(JSON.stringify({ status: "error", message: "Missing --token <bearer_token>" })); process.exit(1); }
|
|
87
|
+
if (!newPassword) { console.error(JSON.stringify({ status: "error", message: "Missing --new <password>" })); process.exit(1); }
|
|
88
|
+
|
|
89
|
+
const body = { new_password: newPassword };
|
|
90
|
+
if (currentPassword) body.current_password = currentPassword;
|
|
91
|
+
|
|
92
|
+
const res = await fetch(`${API}/auth/v1/user/password`, {
|
|
93
|
+
method: "PUT",
|
|
94
|
+
headers: { "Authorization": `Bearer ${accessToken}`, "Content-Type": "application/json" },
|
|
95
|
+
body: JSON.stringify(body),
|
|
96
|
+
});
|
|
97
|
+
const data = await res.json();
|
|
98
|
+
if (!res.ok) {
|
|
99
|
+
console.error(JSON.stringify({ status: "error", http: res.status, ...data }));
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
console.log(JSON.stringify({ status: "ok", ...data }));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function settings(args) {
|
|
106
|
+
const allowPasswordSet = parseFlag(args, "--allow-password-set");
|
|
107
|
+
const projectId = resolveProjectId(parseFlag(args, "--project"));
|
|
108
|
+
const p = findProject(projectId);
|
|
109
|
+
|
|
110
|
+
if (allowPasswordSet === null) { console.error(JSON.stringify({ status: "error", message: "Missing --allow-password-set <true|false>" })); process.exit(1); }
|
|
111
|
+
|
|
112
|
+
const res = await fetch(`${API}/auth/v1/settings`, {
|
|
113
|
+
method: "PATCH",
|
|
114
|
+
headers: { "Authorization": `Bearer ${p.service_key}`, "Content-Type": "application/json" },
|
|
115
|
+
body: JSON.stringify({ allow_password_set: allowPasswordSet === "true" }),
|
|
116
|
+
});
|
|
117
|
+
const data = await res.json();
|
|
118
|
+
if (!res.ok) {
|
|
119
|
+
console.error(JSON.stringify({ status: "error", http: res.status, ...data }));
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
console.log(JSON.stringify({ status: "ok", ...data }));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function providers(args) {
|
|
126
|
+
const projectId = resolveProjectId(parseFlag(args, "--project"));
|
|
127
|
+
const p = findProject(projectId);
|
|
128
|
+
|
|
129
|
+
const res = await fetch(`${API}/auth/v1/providers`, {
|
|
130
|
+
headers: { "Authorization": `Bearer ${p.anon_key}` },
|
|
131
|
+
});
|
|
132
|
+
const data = await res.json();
|
|
133
|
+
if (!res.ok) {
|
|
134
|
+
console.error(JSON.stringify({ status: "error", http: res.status, ...data }));
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
console.log(JSON.stringify(data, null, 2));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export async function run(sub, args) {
|
|
141
|
+
if (!sub || sub === "--help" || sub === "-h") { console.log(HELP); process.exit(0); }
|
|
142
|
+
switch (sub) {
|
|
143
|
+
case "magic-link": await magicLink(args); break;
|
|
144
|
+
case "verify": await verify(args); break;
|
|
145
|
+
case "set-password": await setPassword(args); break;
|
|
146
|
+
case "settings": await settings(args); break;
|
|
147
|
+
case "providers": await providers(args); break;
|
|
148
|
+
default:
|
|
149
|
+
console.error(`Unknown subcommand: ${sub}\n`);
|
|
150
|
+
console.log(HELP);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
}
|
package/lib/billing.mjs
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { API } from "./config.mjs";
|
|
2
|
+
|
|
3
|
+
const HELP = `run402 billing — Email billing accounts, Stripe tier checkout, email packs
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
run402 billing <subcommand> [args...]
|
|
7
|
+
|
|
8
|
+
Subcommands:
|
|
9
|
+
create-email <email> Create an email billing account
|
|
10
|
+
link-wallet <account_id> <wallet> Link a wallet to an email account
|
|
11
|
+
tier-checkout <tier> [--email <e> | --wallet <w>] Stripe tier checkout
|
|
12
|
+
buy-pack [--email <e> | --wallet <w>] Buy \$5 email pack (10,000 emails)
|
|
13
|
+
auto-recharge <account_id> <on|off> [--threshold <n>]
|
|
14
|
+
balance <identifier> Balance by email or wallet (0x...)
|
|
15
|
+
history <identifier> [--limit <n>] Ledger history by email or wallet
|
|
16
|
+
|
|
17
|
+
Examples:
|
|
18
|
+
run402 billing create-email user@example.com
|
|
19
|
+
run402 billing tier-checkout hobby --email user@example.com
|
|
20
|
+
run402 billing buy-pack --wallet 0x1234...
|
|
21
|
+
run402 billing auto-recharge acct_abc on --threshold 2000
|
|
22
|
+
run402 billing balance user@example.com
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
function parseFlag(args, flag) {
|
|
26
|
+
for (let i = 0; i < args.length; i++) {
|
|
27
|
+
if (args[i] === flag && args[i + 1]) return args[i + 1];
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function createEmail(args) {
|
|
33
|
+
const email = args[0];
|
|
34
|
+
if (!email) {
|
|
35
|
+
console.error(JSON.stringify({ status: "error", message: "Missing email. Usage: run402 billing create-email <email>" }));
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
const res = await fetch(`${API}/billing/v1/accounts`, {
|
|
39
|
+
method: "POST",
|
|
40
|
+
headers: { "Content-Type": "application/json" },
|
|
41
|
+
body: JSON.stringify({ email }),
|
|
42
|
+
});
|
|
43
|
+
const data = await res.json();
|
|
44
|
+
if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
|
|
45
|
+
console.log(JSON.stringify(data, null, 2));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function linkWallet(args) {
|
|
49
|
+
const accountId = args[0];
|
|
50
|
+
const wallet = args[1];
|
|
51
|
+
if (!accountId || !wallet) {
|
|
52
|
+
console.error(JSON.stringify({ status: "error", message: "Usage: run402 billing link-wallet <account_id> <wallet>" }));
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
const res = await fetch(`${API}/billing/v1/accounts/${encodeURIComponent(accountId)}/link-wallet`, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers: { "Content-Type": "application/json" },
|
|
58
|
+
body: JSON.stringify({ wallet }),
|
|
59
|
+
});
|
|
60
|
+
const data = await res.json();
|
|
61
|
+
if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
|
|
62
|
+
console.log(JSON.stringify(data, null, 2));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function tierCheckout(args) {
|
|
66
|
+
const tier = args[0];
|
|
67
|
+
if (!tier) {
|
|
68
|
+
console.error(JSON.stringify({ status: "error", message: "Usage: run402 billing tier-checkout <tier> [--email <e> | --wallet <w>]" }));
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
const email = parseFlag(args, "--email");
|
|
72
|
+
const wallet = parseFlag(args, "--wallet");
|
|
73
|
+
if (!email && !wallet) {
|
|
74
|
+
console.error(JSON.stringify({ status: "error", message: "Must provide --email or --wallet" }));
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
const body = email ? { email } : { wallet };
|
|
78
|
+
const res = await fetch(`${API}/billing/v1/tiers/${encodeURIComponent(tier)}/checkout`, {
|
|
79
|
+
method: "POST",
|
|
80
|
+
headers: { "Content-Type": "application/json" },
|
|
81
|
+
body: JSON.stringify(body),
|
|
82
|
+
});
|
|
83
|
+
const data = await res.json();
|
|
84
|
+
if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
|
|
85
|
+
console.log(JSON.stringify(data, null, 2));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function buyPack(args) {
|
|
89
|
+
const email = parseFlag(args, "--email");
|
|
90
|
+
const wallet = parseFlag(args, "--wallet");
|
|
91
|
+
if (!email && !wallet) {
|
|
92
|
+
console.error(JSON.stringify({ status: "error", message: "Must provide --email or --wallet" }));
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
const body = email ? { email } : { wallet };
|
|
96
|
+
const res = await fetch(`${API}/billing/v1/email-packs/checkout`, {
|
|
97
|
+
method: "POST",
|
|
98
|
+
headers: { "Content-Type": "application/json" },
|
|
99
|
+
body: JSON.stringify(body),
|
|
100
|
+
});
|
|
101
|
+
const data = await res.json();
|
|
102
|
+
if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
|
|
103
|
+
console.log(JSON.stringify(data, null, 2));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function autoRecharge(args) {
|
|
107
|
+
const accountId = args[0];
|
|
108
|
+
const state = args[1];
|
|
109
|
+
if (!accountId || !state || !["on", "off"].includes(state)) {
|
|
110
|
+
console.error(JSON.stringify({ status: "error", message: "Usage: run402 billing auto-recharge <account_id> <on|off> [--threshold <n>]" }));
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
const thresholdStr = parseFlag(args, "--threshold");
|
|
114
|
+
const body = {
|
|
115
|
+
billing_account_id: accountId,
|
|
116
|
+
enabled: state === "on",
|
|
117
|
+
};
|
|
118
|
+
if (thresholdStr) body.threshold = Number(thresholdStr);
|
|
119
|
+
const res = await fetch(`${API}/billing/v1/email-packs/auto-recharge`, {
|
|
120
|
+
method: "POST",
|
|
121
|
+
headers: { "Content-Type": "application/json" },
|
|
122
|
+
body: JSON.stringify(body),
|
|
123
|
+
});
|
|
124
|
+
const data = await res.json();
|
|
125
|
+
if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
|
|
126
|
+
console.log(JSON.stringify(data, null, 2));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function balance(args) {
|
|
130
|
+
const id = args[0];
|
|
131
|
+
if (!id) {
|
|
132
|
+
console.error(JSON.stringify({ status: "error", message: "Usage: run402 billing balance <email-or-wallet>" }));
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
const res = await fetch(`${API}/billing/v1/accounts/${encodeURIComponent(id)}`);
|
|
136
|
+
const data = await res.json();
|
|
137
|
+
if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
|
|
138
|
+
console.log(JSON.stringify(data, null, 2));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function history(args) {
|
|
142
|
+
const id = args[0];
|
|
143
|
+
if (!id) {
|
|
144
|
+
console.error(JSON.stringify({ status: "error", message: "Usage: run402 billing history <email-or-wallet> [--limit <n>]" }));
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
const limit = parseFlag(args, "--limit") || "50";
|
|
148
|
+
const res = await fetch(`${API}/billing/v1/accounts/${encodeURIComponent(id)}/history?limit=${encodeURIComponent(limit)}`);
|
|
149
|
+
const data = await res.json();
|
|
150
|
+
if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
|
|
151
|
+
console.log(JSON.stringify(data, null, 2));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function run(sub, args) {
|
|
155
|
+
if (!sub || sub === "--help" || sub === "-h") { console.log(HELP); process.exit(0); }
|
|
156
|
+
switch (sub) {
|
|
157
|
+
case "create-email": await createEmail(args); break;
|
|
158
|
+
case "link-wallet": await linkWallet(args); break;
|
|
159
|
+
case "tier-checkout": await tierCheckout(args); break;
|
|
160
|
+
case "buy-pack": await buyPack(args); break;
|
|
161
|
+
case "auto-recharge": await autoRecharge(args); break;
|
|
162
|
+
case "balance": await balance(args); break;
|
|
163
|
+
case "history": await history(args); break;
|
|
164
|
+
default:
|
|
165
|
+
console.error(`Unknown subcommand: ${sub}\n`);
|
|
166
|
+
console.log(HELP);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { findProject, resolveProjectId, API } from "./config.mjs";
|
|
2
|
+
|
|
3
|
+
const HELP = `run402 sender-domain — Manage custom email sender domain
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
run402 sender-domain <subcommand> [args...]
|
|
7
|
+
|
|
8
|
+
Subcommands:
|
|
9
|
+
register <domain> [--project <id>] Register a custom sender domain (returns DNS records)
|
|
10
|
+
status [--project <id>] Check domain verification status
|
|
11
|
+
remove [--project <id>] Remove custom sender domain
|
|
12
|
+
|
|
13
|
+
Examples:
|
|
14
|
+
run402 sender-domain register kysigned.com
|
|
15
|
+
run402 sender-domain status
|
|
16
|
+
run402 sender-domain remove
|
|
17
|
+
`;
|
|
18
|
+
|
|
19
|
+
function parseFlag(args, flag) {
|
|
20
|
+
for (let i = 0; i < args.length; i++) {
|
|
21
|
+
if (args[i] === flag && args[i + 1]) return args[i + 1];
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function register(args) {
|
|
27
|
+
let domain = null;
|
|
28
|
+
let projectOpt = null;
|
|
29
|
+
for (let i = 0; i < args.length; i++) {
|
|
30
|
+
if (args[i] === "--project" && args[i + 1]) { projectOpt = args[++i]; }
|
|
31
|
+
else if (!args[i].startsWith("--") && !domain) { domain = args[i]; }
|
|
32
|
+
}
|
|
33
|
+
const projectId = resolveProjectId(projectOpt);
|
|
34
|
+
const p = findProject(projectId);
|
|
35
|
+
|
|
36
|
+
if (!domain) {
|
|
37
|
+
console.error(JSON.stringify({ status: "error", message: "Missing domain. Usage: run402 sender-domain register <domain>" }));
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const res = await fetch(`${API}/email/v1/domains`, {
|
|
42
|
+
method: "POST",
|
|
43
|
+
headers: { apikey: p.service_key, "Content-Type": "application/json" },
|
|
44
|
+
body: JSON.stringify({ domain }),
|
|
45
|
+
});
|
|
46
|
+
const data = await res.json();
|
|
47
|
+
if (!res.ok) {
|
|
48
|
+
console.error(JSON.stringify({ status: "error", http: res.status, ...data }));
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
console.log(JSON.stringify(data, null, 2));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function status(args) {
|
|
55
|
+
const projectId = resolveProjectId(parseFlag(args, "--project"));
|
|
56
|
+
const p = findProject(projectId);
|
|
57
|
+
|
|
58
|
+
const res = await fetch(`${API}/email/v1/domains`, {
|
|
59
|
+
headers: { apikey: p.service_key },
|
|
60
|
+
});
|
|
61
|
+
const data = await res.json();
|
|
62
|
+
if (!res.ok) {
|
|
63
|
+
console.error(JSON.stringify({ status: "error", http: res.status, ...data }));
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
console.log(JSON.stringify(data, null, 2));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function remove(args) {
|
|
70
|
+
const projectId = resolveProjectId(parseFlag(args, "--project"));
|
|
71
|
+
const p = findProject(projectId);
|
|
72
|
+
|
|
73
|
+
const res = await fetch(`${API}/email/v1/domains`, {
|
|
74
|
+
method: "DELETE",
|
|
75
|
+
headers: { apikey: p.service_key },
|
|
76
|
+
});
|
|
77
|
+
const data = await res.json();
|
|
78
|
+
if (!res.ok) {
|
|
79
|
+
console.error(JSON.stringify({ status: "error", http: res.status, ...data }));
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
console.log(JSON.stringify(data));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function run(sub, args) {
|
|
86
|
+
if (!sub || sub === "--help" || sub === "-h") { console.log(HELP); process.exit(0); }
|
|
87
|
+
switch (sub) {
|
|
88
|
+
case "register": await register(args); break;
|
|
89
|
+
case "status": await status(args); break;
|
|
90
|
+
case "remove": await remove(args); break;
|
|
91
|
+
default:
|
|
92
|
+
console.error(`Unknown subcommand: ${sub}\n`);
|
|
93
|
+
console.log(HELP);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
}
|
package/package.json
CHANGED
package/core-dist/wallet-auth.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
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
|
package/core-dist/wallet.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
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
|