run402 1.17.0 → 1.18.1
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 +1 -1
- package/lib/functions.mjs +9 -28
- package/lib/paid-fetch.mjs +65 -1
- package/lib/projects.mjs +14 -4
- package/lib/secrets.mjs +14 -4
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -44,7 +44,7 @@ Examples:
|
|
|
44
44
|
run402 deploy --manifest app.json
|
|
45
45
|
run402 projects list
|
|
46
46
|
run402 projects sql <project_id> "SELECT * FROM users LIMIT 5"
|
|
47
|
-
run402 functions deploy <project_id> my-fn --
|
|
47
|
+
run402 functions deploy <project_id> my-fn --file handler.ts
|
|
48
48
|
run402 secrets set <project_id> API_KEY sk-1234
|
|
49
49
|
run402 image generate "a startup mascot, pixel art" --output logo.png
|
|
50
50
|
|
package/lib/functions.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { readFileSync
|
|
2
|
-
import { findProject,
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { findProject, API } from "./config.mjs";
|
|
3
|
+
import { setupPaidFetch } from "./paid-fetch.mjs";
|
|
3
4
|
|
|
4
5
|
const HELP = `run402 functions — Manage serverless functions
|
|
5
6
|
|
|
@@ -7,7 +8,7 @@ Usage:
|
|
|
7
8
|
run402 functions <subcommand> [args...]
|
|
8
9
|
|
|
9
10
|
Subcommands:
|
|
10
|
-
deploy <id> <name> --
|
|
11
|
+
deploy <id> <name> --file <file> [--timeout <s>] [--memory <mb>] [--deps <pkg,...>]
|
|
11
12
|
Deploy a function to a project
|
|
12
13
|
invoke <id> <name> [--method <M>] [--body <json>]
|
|
13
14
|
Invoke a deployed function
|
|
@@ -16,7 +17,7 @@ Subcommands:
|
|
|
16
17
|
delete <id> <name> Delete a function
|
|
17
18
|
|
|
18
19
|
Examples:
|
|
19
|
-
run402 functions deploy abc123 stripe-webhook --
|
|
20
|
+
run402 functions deploy abc123 stripe-webhook --file handler.ts
|
|
20
21
|
run402 functions invoke abc123 stripe-webhook --body '{"event":"test"}'
|
|
21
22
|
run402 functions logs abc123 stripe-webhook --tail 100
|
|
22
23
|
run402 functions list abc123
|
|
@@ -27,37 +28,17 @@ Notes:
|
|
|
27
28
|
- Deploy may require payment if the project lease has expired
|
|
28
29
|
`;
|
|
29
30
|
|
|
30
|
-
async function setupPaidFetch() {
|
|
31
|
-
if (!existsSync(ALLOWANCE_FILE)) {
|
|
32
|
-
console.error(JSON.stringify({ status: "error", message: "No agent allowance found. Run: run402 allowance create && run402 allowance fund" }));
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
const allowance = readAllowance();
|
|
36
|
-
const { privateKeyToAccount } = await import("viem/accounts");
|
|
37
|
-
const { createPublicClient, http } = await import("viem");
|
|
38
|
-
const { baseSepolia } = await import("viem/chains");
|
|
39
|
-
const { x402Client, wrapFetchWithPayment } = await import("@x402/fetch");
|
|
40
|
-
const { ExactEvmScheme } = await import("@x402/evm/exact/client");
|
|
41
|
-
const { toClientEvmSigner } = await import("@x402/evm");
|
|
42
|
-
const account = privateKeyToAccount(allowance.privateKey);
|
|
43
|
-
const publicClient = createPublicClient({ chain: baseSepolia, transport: http() });
|
|
44
|
-
const signer = toClientEvmSigner(account, publicClient);
|
|
45
|
-
const client = new x402Client();
|
|
46
|
-
client.register("eip155:84532", new ExactEvmScheme(signer));
|
|
47
|
-
return wrapFetchWithPayment(fetch, client);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
31
|
async function deploy(projectId, name, args) {
|
|
51
32
|
const p = findProject(projectId);
|
|
52
|
-
const opts = {
|
|
33
|
+
const opts = { file: null, timeout: undefined, memory: undefined, deps: undefined };
|
|
53
34
|
for (let i = 0; i < args.length; i++) {
|
|
54
|
-
if (args[i] === "--
|
|
35
|
+
if (args[i] === "--file" && args[i + 1]) opts.file = args[++i];
|
|
55
36
|
if (args[i] === "--timeout" && args[i + 1]) opts.timeout = parseInt(args[++i]);
|
|
56
37
|
if (args[i] === "--memory" && args[i + 1]) opts.memory = parseInt(args[++i]);
|
|
57
38
|
if (args[i] === "--deps" && args[i + 1]) opts.deps = args[++i].split(",");
|
|
58
39
|
}
|
|
59
|
-
if (!opts.
|
|
60
|
-
const code = readFileSync(opts.
|
|
40
|
+
if (!opts.file) { console.error(JSON.stringify({ status: "error", message: "Missing --file <file>" })); process.exit(1); }
|
|
41
|
+
const code = readFileSync(opts.file, "utf-8");
|
|
61
42
|
const body = { name, code };
|
|
62
43
|
if (opts.timeout || opts.memory) body.config = {};
|
|
63
44
|
if (opts.timeout) body.config.timeout = opts.timeout;
|
package/lib/paid-fetch.mjs
CHANGED
|
@@ -3,11 +3,33 @@
|
|
|
3
3
|
* Branches on allowance rail:
|
|
4
4
|
* - "mpp": uses mppx.fetch (Tempo pathUSD)
|
|
5
5
|
* - "x402" (default): uses @x402/fetch (Base USDC)
|
|
6
|
+
*
|
|
7
|
+
* Checks on-chain balances at setup time and selects funded networks.
|
|
6
8
|
*/
|
|
7
9
|
|
|
8
10
|
import { readAllowance, ALLOWANCE_FILE } from "./config.mjs";
|
|
9
11
|
import { existsSync } from "fs";
|
|
10
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
|
+
|
|
11
33
|
export async function setupPaidFetch() {
|
|
12
34
|
if (!existsSync(ALLOWANCE_FILE)) {
|
|
13
35
|
console.error(JSON.stringify({ status: "error", message: "No agent allowance found. Run: run402 allowance create && run402 allowance fund" }));
|
|
@@ -18,6 +40,23 @@ export async function setupPaidFetch() {
|
|
|
18
40
|
const account = privateKeyToAccount(allowance.privateKey);
|
|
19
41
|
|
|
20
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
|
+
|
|
21
60
|
const { Mppx, tempo } = await import("mppx/client");
|
|
22
61
|
const mppx = Mppx.create({
|
|
23
62
|
polyfill: false,
|
|
@@ -26,7 +65,7 @@ export async function setupPaidFetch() {
|
|
|
26
65
|
return mppx.fetch;
|
|
27
66
|
}
|
|
28
67
|
|
|
29
|
-
// Default: x402
|
|
68
|
+
// Default: x402
|
|
30
69
|
const { createPublicClient, http } = await import("viem");
|
|
31
70
|
const { base, baseSepolia } = await import("viem/chains");
|
|
32
71
|
const { x402Client, wrapFetchWithPayment } = await import("@x402/fetch");
|
|
@@ -36,8 +75,33 @@ export async function setupPaidFetch() {
|
|
|
36
75
|
const mainnetClient = createPublicClient({ chain: base, transport: http() });
|
|
37
76
|
const sepoliaClient = createPublicClient({ chain: baseSepolia, transport: http() });
|
|
38
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
|
+
|
|
39
92
|
const client = new x402Client();
|
|
40
93
|
client.register("eip155:8453", new ExactEvmScheme(toClientEvmSigner(account, mainnetClient)));
|
|
41
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
|
+
|
|
42
106
|
return wrapFetchWithPayment(fetch, client);
|
|
43
107
|
}
|
package/lib/projects.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
1
2
|
import { findProject, loadKeyStore, saveProject, removeProject, API, allowanceAuthHeaders, setActiveProjectId, getActiveProjectId } from "./config.mjs";
|
|
2
3
|
|
|
3
4
|
const HELP = `run402 projects — Manage your deployed Run402 projects
|
|
@@ -12,7 +13,7 @@ Subcommands:
|
|
|
12
13
|
list List all your projects (IDs, URLs, active marker)
|
|
13
14
|
info <id> Show project details: REST URL, keys
|
|
14
15
|
keys <id> Print anon_key and service_key as JSON
|
|
15
|
-
sql <id> "<query>"
|
|
16
|
+
sql <id> "<query>" [--file <path>] Run a SQL query against a project's Postgres DB
|
|
16
17
|
rest <id> <table> [params] Query a table via the REST API (PostgREST)
|
|
17
18
|
usage <id> Show compute/storage usage for a project
|
|
18
19
|
schema <id> Inspect the database schema
|
|
@@ -28,6 +29,7 @@ Examples:
|
|
|
28
29
|
run402 projects list
|
|
29
30
|
run402 projects info abc123
|
|
30
31
|
run402 projects sql abc123 "SELECT * FROM users LIMIT 5"
|
|
32
|
+
run402 projects sql abc123 --file setup.sql
|
|
31
33
|
run402 projects rest abc123 users "limit=10&select=id,name"
|
|
32
34
|
run402 projects usage abc123
|
|
33
35
|
run402 projects schema abc123
|
|
@@ -115,9 +117,17 @@ async function keys(projectId) {
|
|
|
115
117
|
console.log(JSON.stringify({ project_id: projectId, anon_key: p.anon_key, service_key: p.service_key }, null, 2));
|
|
116
118
|
}
|
|
117
119
|
|
|
118
|
-
async function sqlCmd(projectId,
|
|
120
|
+
async function sqlCmd(projectId, args = []) {
|
|
119
121
|
const p = findProject(projectId);
|
|
120
|
-
|
|
122
|
+
let file = null;
|
|
123
|
+
let query = null;
|
|
124
|
+
for (let i = 0; i < args.length; i++) {
|
|
125
|
+
if (args[i] === "--file" && args[i + 1]) { file = args[++i]; }
|
|
126
|
+
else if (!query && !args[i].startsWith("--")) { query = args[i]; }
|
|
127
|
+
}
|
|
128
|
+
const sql = file ? readFileSync(file, "utf-8") : query;
|
|
129
|
+
if (!sql) { console.error(JSON.stringify({ status: "error", message: "Missing SQL query. Provide inline or use --file <path>" })); process.exit(1); }
|
|
130
|
+
const res = await fetch(`${API}/projects/v1/admin/${projectId}/sql`, { method: "POST", headers: { "Authorization": `Bearer ${p.service_key}`, "Content-Type": "text/plain" }, body: sql });
|
|
121
131
|
console.log(JSON.stringify(await res.json(), null, 2));
|
|
122
132
|
}
|
|
123
133
|
|
|
@@ -193,7 +203,7 @@ export async function run(sub, args) {
|
|
|
193
203
|
case "list": await list(); break;
|
|
194
204
|
case "info": await info(args[0]); break;
|
|
195
205
|
case "keys": await keys(args[0]); break;
|
|
196
|
-
case "sql": await sqlCmd(args[0], args
|
|
206
|
+
case "sql": await sqlCmd(args[0], args.slice(1)); break;
|
|
197
207
|
case "rest": await rest(args[0], args[1], args[2]); break;
|
|
198
208
|
case "usage": await usage(args[0]); break;
|
|
199
209
|
case "schema": await schema(args[0]); break;
|
package/lib/secrets.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
1
2
|
import { findProject, API } from "./config.mjs";
|
|
2
3
|
|
|
3
4
|
const HELP = `run402 secrets — Manage project secrets
|
|
@@ -6,12 +7,13 @@ Usage:
|
|
|
6
7
|
run402 secrets <subcommand> [args...]
|
|
7
8
|
|
|
8
9
|
Subcommands:
|
|
9
|
-
set <id> <key> <value>
|
|
10
|
+
set <id> <key> <value> [--file <path>] Set a secret on a project
|
|
10
11
|
list <id> List all secrets for a project
|
|
11
12
|
delete <id> <key> Delete a secret from a project
|
|
12
13
|
|
|
13
14
|
Examples:
|
|
14
15
|
run402 secrets set abc123 STRIPE_KEY sk-1234
|
|
16
|
+
run402 secrets set abc123 TLS_CERT --file cert.pem
|
|
15
17
|
run402 secrets list abc123
|
|
16
18
|
run402 secrets delete abc123 STRIPE_KEY
|
|
17
19
|
|
|
@@ -20,12 +22,20 @@ Notes:
|
|
|
20
22
|
- Values are never shown after being set
|
|
21
23
|
`;
|
|
22
24
|
|
|
23
|
-
async function set(projectId, key,
|
|
25
|
+
async function set(projectId, key, args = []) {
|
|
24
26
|
const p = findProject(projectId);
|
|
27
|
+
let file = null;
|
|
28
|
+
let value = null;
|
|
29
|
+
for (let i = 0; i < args.length; i++) {
|
|
30
|
+
if (args[i] === "--file" && args[i + 1]) { file = args[++i]; }
|
|
31
|
+
else if (!value && !args[i].startsWith("--")) { value = args[i]; }
|
|
32
|
+
}
|
|
33
|
+
const val = file ? readFileSync(file, "utf-8") : value;
|
|
34
|
+
if (!val) { console.error(JSON.stringify({ status: "error", message: "Missing secret value. Provide inline or use --file <path>" })); process.exit(1); }
|
|
25
35
|
const res = await fetch(`${API}/projects/v1/admin/${projectId}/secrets`, {
|
|
26
36
|
method: "POST",
|
|
27
37
|
headers: { "Authorization": `Bearer ${p.service_key}`, "Content-Type": "application/json" },
|
|
28
|
-
body: JSON.stringify({ key, value }),
|
|
38
|
+
body: JSON.stringify({ key, value: val }),
|
|
29
39
|
});
|
|
30
40
|
const data = await res.json();
|
|
31
41
|
if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
|
|
@@ -59,7 +69,7 @@ async function deleteSecret(projectId, key) {
|
|
|
59
69
|
export async function run(sub, args) {
|
|
60
70
|
if (!sub || sub === '--help' || sub === '-h') { console.log(HELP); process.exit(0); }
|
|
61
71
|
switch (sub) {
|
|
62
|
-
case "set": await set(args[0], args[1], args
|
|
72
|
+
case "set": await set(args[0], args[1], args.slice(2)); break;
|
|
63
73
|
case "list": await list(args[0]); break;
|
|
64
74
|
case "delete": await deleteSecret(args[0], args[1]); break;
|
|
65
75
|
default:
|
package/package.json
CHANGED