run402 1.9.0 → 1.9.2
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/README.md +13 -13
- package/cli.mjs +6 -6
- package/core-dist/allowance-auth.js +62 -0
- package/core-dist/allowance.js +25 -0
- package/core-dist/client.js +42 -0
- package/core-dist/config.js +24 -0
- package/core-dist/keystore.js +75 -0
- package/core-dist/wallet-auth.js +62 -0
- package/core-dist/wallet.js +25 -0
- package/lib/agent.mjs +3 -3
- package/lib/{wallet.mjs → allowance.mjs} +34 -34
- package/lib/apps.mjs +2 -2
- package/lib/config.mjs +16 -13
- package/lib/deploy.mjs +3 -3
- package/lib/functions.mjs +5 -5
- package/lib/image.mjs +2 -2
- package/lib/init.mjs +13 -13
- package/lib/message.mjs +4 -4
- package/lib/paid-fetch.mjs +6 -6
- package/lib/projects.mjs +3 -3
- package/lib/sites.mjs +3 -3
- package/lib/tier.mjs +5 -5
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# run402 CLI
|
|
2
2
|
|
|
3
|
-
Command-line interface for [Run402](https://run402.com) — provision Postgres databases, deploy static sites, generate images, and manage
|
|
3
|
+
Command-line interface for [Run402](https://run402.com) — provision Postgres databases, deploy static sites, generate images, and manage agent allowances via x402 micropayments.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -17,11 +17,11 @@ npx run402 <command>
|
|
|
17
17
|
## Getting Started
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
# 1. Create a local
|
|
21
|
-
run402
|
|
20
|
+
# 1. Create a local agent allowance
|
|
21
|
+
run402 allowance create
|
|
22
22
|
|
|
23
23
|
# 2. Fund it with test USDC (Base Sepolia faucet)
|
|
24
|
-
run402
|
|
24
|
+
run402 allowance fund
|
|
25
25
|
|
|
26
26
|
# 3. Deploy your app
|
|
27
27
|
run402 deploy --tier prototype --manifest app.json
|
|
@@ -29,15 +29,15 @@ run402 deploy --tier prototype --manifest app.json
|
|
|
29
29
|
|
|
30
30
|
## Commands
|
|
31
31
|
|
|
32
|
-
### `run402
|
|
32
|
+
### `run402 allowance`
|
|
33
33
|
|
|
34
|
-
Manage your local
|
|
34
|
+
Manage your local agent allowance.
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
run402
|
|
38
|
-
run402
|
|
39
|
-
run402
|
|
40
|
-
run402
|
|
37
|
+
run402 allowance create # Generate a new allowance
|
|
38
|
+
run402 allowance status # Show address, network, funding status
|
|
39
|
+
run402 allowance fund # Request test USDC from the faucet
|
|
40
|
+
run402 allowance export # Print allowance address (for scripting)
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
### `run402 deploy`
|
|
@@ -98,7 +98,7 @@ Every command supports `--help` / `-h`:
|
|
|
98
98
|
|
|
99
99
|
```bash
|
|
100
100
|
run402 --help
|
|
101
|
-
run402
|
|
101
|
+
run402 allowance --help
|
|
102
102
|
run402 deploy --help
|
|
103
103
|
run402 projects --help
|
|
104
104
|
run402 image --help
|
|
@@ -106,9 +106,9 @@ run402 image --help
|
|
|
106
106
|
|
|
107
107
|
## Notes
|
|
108
108
|
|
|
109
|
-
-
|
|
109
|
+
- Agent allowance stored at `~/.run402/allowance.json`
|
|
110
110
|
- Project credentials stored at `~/.run402/projects.json`
|
|
111
|
-
- Network: Base Sepolia (testnet)
|
|
111
|
+
- Network: Base Sepolia (testnet) for prototype tier (free). Base mainnet or Stripe for paid tiers (hobby/team).
|
|
112
112
|
- Payments are handled automatically — no manual signing required
|
|
113
113
|
|
|
114
114
|
## License
|
package/cli.mjs
CHANGED
|
@@ -13,8 +13,8 @@ Usage:
|
|
|
13
13
|
run402 <command> [subcommand] [options]
|
|
14
14
|
|
|
15
15
|
Commands:
|
|
16
|
-
init Set up
|
|
17
|
-
|
|
16
|
+
init Set up allowance, funding, and check tier status
|
|
17
|
+
allowance Manage your agent allowance (create, fund, balance, status)
|
|
18
18
|
tier Manage tier subscription (status, set)
|
|
19
19
|
projects Manage projects (provision, list, query, inspect, delete)
|
|
20
20
|
deploy Deploy a full-stack app or static site (requires active tier)
|
|
@@ -31,8 +31,8 @@ Commands:
|
|
|
31
31
|
Run 'run402 <command> --help' for detailed usage of each command.
|
|
32
32
|
|
|
33
33
|
Examples:
|
|
34
|
-
run402
|
|
35
|
-
run402
|
|
34
|
+
run402 allowance create
|
|
35
|
+
run402 allowance fund
|
|
36
36
|
run402 deploy --manifest app.json
|
|
37
37
|
run402 projects list
|
|
38
38
|
run402 projects sql <project_id> "SELECT * FROM users LIMIT 5"
|
|
@@ -57,8 +57,8 @@ switch (cmd) {
|
|
|
57
57
|
await run();
|
|
58
58
|
break;
|
|
59
59
|
}
|
|
60
|
-
case "
|
|
61
|
-
const { run } = await import("./lib/
|
|
60
|
+
case "allowance": {
|
|
61
|
+
const { run } = await import("./lib/allowance.mjs");
|
|
62
62
|
await run(sub, rest);
|
|
63
63
|
break;
|
|
64
64
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Allowance 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 { readAllowance } from "./allowance.js";
|
|
9
|
+
/**
|
|
10
|
+
* EIP-191 personal_sign: sign a message with the allowance'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 allowance auth headers for the Run402 API.
|
|
48
|
+
* Returns null if no allowance is configured.
|
|
49
|
+
*/
|
|
50
|
+
export function getAllowanceAuthHeaders(allowancePath) {
|
|
51
|
+
const allowance = readAllowance(allowancePath);
|
|
52
|
+
if (!allowance || !allowance.address || !allowance.privateKey)
|
|
53
|
+
return null;
|
|
54
|
+
const timestamp = Math.floor(Date.now() / 1000).toString();
|
|
55
|
+
const signature = personalSign(allowance.privateKey, allowance.address, `run402:${timestamp}`);
|
|
56
|
+
return {
|
|
57
|
+
"X-Run402-Wallet": allowance.address,
|
|
58
|
+
"X-Run402-Signature": signature,
|
|
59
|
+
"X-Run402-Timestamp": timestamp,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=allowance-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 { getAllowancePath } from "./config.js";
|
|
5
|
+
export function readAllowance(path) {
|
|
6
|
+
const p = path ?? getAllowancePath();
|
|
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 saveAllowance(data, path) {
|
|
17
|
+
const p = path ?? getAllowancePath();
|
|
18
|
+
const dir = dirname(p);
|
|
19
|
+
mkdirSync(dir, { recursive: true });
|
|
20
|
+
const tmp = join(dir, `.allowance.${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=allowance.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { getApiBase } from "./config.js";
|
|
2
|
+
export async function apiRequest(path, opts = {}) {
|
|
3
|
+
const { method = "GET", headers = {}, body, rawBody } = opts;
|
|
4
|
+
const url = `${getApiBase()}${path}`;
|
|
5
|
+
const fetchHeaders = { ...headers };
|
|
6
|
+
let fetchBody;
|
|
7
|
+
if (rawBody !== undefined) {
|
|
8
|
+
fetchBody = rawBody;
|
|
9
|
+
}
|
|
10
|
+
else if (body !== undefined) {
|
|
11
|
+
fetchHeaders["Content-Type"] = fetchHeaders["Content-Type"] || "application/json";
|
|
12
|
+
fetchBody = JSON.stringify(body);
|
|
13
|
+
}
|
|
14
|
+
let res;
|
|
15
|
+
try {
|
|
16
|
+
res = await fetch(url, {
|
|
17
|
+
method,
|
|
18
|
+
headers: fetchHeaders,
|
|
19
|
+
body: fetchBody,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
return {
|
|
24
|
+
ok: false,
|
|
25
|
+
status: 0,
|
|
26
|
+
body: { error: `Network error: ${err.message}` },
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
let resBody;
|
|
30
|
+
const contentType = res.headers.get("content-type") || "";
|
|
31
|
+
if (contentType.includes("application/json")) {
|
|
32
|
+
resBody = await res.json();
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
resBody = await res.text();
|
|
36
|
+
}
|
|
37
|
+
if (res.status === 402) {
|
|
38
|
+
return { ok: false, is402: true, status: 402, body: resBody };
|
|
39
|
+
}
|
|
40
|
+
return { ok: res.ok, status: res.status, body: resBody };
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { existsSync, renameSync, mkdirSync } from "node:fs";
|
|
4
|
+
export function getApiBase() {
|
|
5
|
+
return process.env.RUN402_API_BASE || "https://api.run402.com";
|
|
6
|
+
}
|
|
7
|
+
export function getConfigDir() {
|
|
8
|
+
return process.env.RUN402_CONFIG_DIR || join(homedir(), ".config", "run402");
|
|
9
|
+
}
|
|
10
|
+
export function getKeystorePath() {
|
|
11
|
+
return join(getConfigDir(), "projects.json");
|
|
12
|
+
}
|
|
13
|
+
export function getAllowancePath() {
|
|
14
|
+
const dir = getConfigDir();
|
|
15
|
+
const newPath = join(dir, "allowance.json");
|
|
16
|
+
const oldPath = join(dir, "wallet.json");
|
|
17
|
+
// Auto-migrate from wallet.json → allowance.json
|
|
18
|
+
if (!existsSync(newPath) && existsSync(oldPath)) {
|
|
19
|
+
mkdirSync(dir, { recursive: true });
|
|
20
|
+
renameSync(oldPath, newPath);
|
|
21
|
+
}
|
|
22
|
+
return newPath;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, renameSync, chmodSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { randomBytes } from "node:crypto";
|
|
4
|
+
import { getKeystorePath } from "./config.js";
|
|
5
|
+
/**
|
|
6
|
+
* Load the keystore from disk.
|
|
7
|
+
* Auto-migrates legacy formats:
|
|
8
|
+
* - Array format (CLI legacy): [{project_id, ...}] → {projects: {id: {...}}}
|
|
9
|
+
* - Old field name: expires_at → lease_expires_at
|
|
10
|
+
*/
|
|
11
|
+
export function loadKeyStore(path) {
|
|
12
|
+
const p = path ?? getKeystorePath();
|
|
13
|
+
try {
|
|
14
|
+
const data = readFileSync(p, "utf-8");
|
|
15
|
+
const parsed = JSON.parse(data);
|
|
16
|
+
// Auto-migrate array format (CLI legacy) to object format
|
|
17
|
+
if (Array.isArray(parsed)) {
|
|
18
|
+
const projects = {};
|
|
19
|
+
for (const item of parsed) {
|
|
20
|
+
if (item.project_id) {
|
|
21
|
+
projects[item.project_id] = {
|
|
22
|
+
anon_key: item.anon_key,
|
|
23
|
+
service_key: item.service_key,
|
|
24
|
+
tier: item.tier,
|
|
25
|
+
lease_expires_at: item.lease_expires_at || item.expires_at || "",
|
|
26
|
+
...(item.site_url && { site_url: item.site_url }),
|
|
27
|
+
...(item.deployed_at && { deployed_at: item.deployed_at }),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return { projects };
|
|
32
|
+
}
|
|
33
|
+
if (parsed && typeof parsed === "object" && parsed.projects) {
|
|
34
|
+
// Auto-normalize expires_at → lease_expires_at
|
|
35
|
+
for (const proj of Object.values(parsed.projects)) {
|
|
36
|
+
const rec = proj;
|
|
37
|
+
if (rec.expires_at && !rec.lease_expires_at) {
|
|
38
|
+
rec.lease_expires_at = rec.expires_at;
|
|
39
|
+
delete rec.expires_at;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return parsed;
|
|
43
|
+
}
|
|
44
|
+
return { projects: {} };
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return { projects: {} };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export function saveKeyStore(store, path) {
|
|
51
|
+
const p = path ?? getKeystorePath();
|
|
52
|
+
const dir = dirname(p);
|
|
53
|
+
mkdirSync(dir, { recursive: true });
|
|
54
|
+
const tmp = join(dir, `.projects.${randomBytes(4).toString("hex")}.tmp`);
|
|
55
|
+
writeFileSync(tmp, JSON.stringify(store, null, 2), { mode: 0o600 });
|
|
56
|
+
renameSync(tmp, p);
|
|
57
|
+
chmodSync(p, 0o600);
|
|
58
|
+
}
|
|
59
|
+
export function getProject(projectId, path) {
|
|
60
|
+
const store = loadKeyStore(path);
|
|
61
|
+
return store.projects[projectId];
|
|
62
|
+
}
|
|
63
|
+
export function saveProject(projectId, project, path) {
|
|
64
|
+
const p = path ?? getKeystorePath();
|
|
65
|
+
const store = loadKeyStore(p);
|
|
66
|
+
store.projects[projectId] = project;
|
|
67
|
+
saveKeyStore(store, p);
|
|
68
|
+
}
|
|
69
|
+
export function removeProject(projectId, path) {
|
|
70
|
+
const p = path ?? getKeystorePath();
|
|
71
|
+
const store = loadKeyStore(p);
|
|
72
|
+
delete store.projects[projectId];
|
|
73
|
+
saveKeyStore(store, p);
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=keystore.js.map
|
|
@@ -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/agent.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { API,
|
|
1
|
+
import { API, allowanceAuthHeaders } from "./config.mjs";
|
|
2
2
|
|
|
3
3
|
const HELP = `run402 agent — Manage agent identity
|
|
4
4
|
|
|
@@ -6,7 +6,7 @@ Usage:
|
|
|
6
6
|
run402 agent contact --name <name> [--email <email>] [--webhook <url>]
|
|
7
7
|
|
|
8
8
|
Notes:
|
|
9
|
-
- Free with
|
|
9
|
+
- Free with allowance auth
|
|
10
10
|
- Registers contact info so Run402 can reach your agent
|
|
11
11
|
- Only name is required; email and webhook are optional
|
|
12
12
|
|
|
@@ -23,7 +23,7 @@ async function contact(args) {
|
|
|
23
23
|
if (args[i] === "--webhook" && args[i + 1]) webhook = args[++i];
|
|
24
24
|
}
|
|
25
25
|
if (!name) { console.error(JSON.stringify({ status: "error", message: "Missing --name <name>" })); process.exit(1); }
|
|
26
|
-
const authHeaders = await
|
|
26
|
+
const authHeaders = await allowanceAuthHeaders();
|
|
27
27
|
|
|
28
28
|
const body = { name };
|
|
29
29
|
if (email) body.email = email;
|
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readAllowance, saveAllowance, ALLOWANCE_FILE, API } from "./config.mjs";
|
|
2
2
|
|
|
3
|
-
const HELP = `run402
|
|
3
|
+
const HELP = `run402 allowance — Manage your agent allowance
|
|
4
4
|
|
|
5
5
|
Usage:
|
|
6
|
-
run402
|
|
6
|
+
run402 allowance <subcommand>
|
|
7
7
|
|
|
8
8
|
Subcommands:
|
|
9
|
-
status Show
|
|
10
|
-
create Generate a new
|
|
9
|
+
status Show allowance address, network, and funding status
|
|
10
|
+
create Generate a new allowance and save it locally
|
|
11
11
|
fund Request test USDC from the Run402 faucet (Base Sepolia)
|
|
12
12
|
balance Show on-chain USDC (mainnet + testnet) and Run402 billing balance
|
|
13
|
-
export Print the
|
|
13
|
+
export Print the allowance address (useful for scripting)
|
|
14
14
|
checkout Create a billing checkout session (--amount <usd_micros>)
|
|
15
15
|
history View billing transaction history (--limit <n>)
|
|
16
16
|
|
|
17
17
|
Notes:
|
|
18
|
-
-
|
|
19
|
-
- The
|
|
20
|
-
- You need to create and fund
|
|
18
|
+
- Agent allowance is stored locally at ~/.run402/allowance.json
|
|
19
|
+
- The allowance works on any EVM chain (currently Run402 uses Base Mainnet and Sepolia for testnet)
|
|
20
|
+
- You need to create and fund an allowance before any x402 transaction with Run402
|
|
21
21
|
|
|
22
22
|
Examples:
|
|
23
|
-
run402
|
|
24
|
-
run402
|
|
25
|
-
run402
|
|
26
|
-
run402
|
|
27
|
-
run402
|
|
28
|
-
run402
|
|
23
|
+
run402 allowance create
|
|
24
|
+
run402 allowance status
|
|
25
|
+
run402 allowance fund
|
|
26
|
+
run402 allowance export
|
|
27
|
+
run402 allowance checkout --amount 5000000
|
|
28
|
+
run402 allowance history --limit 10
|
|
29
29
|
`;
|
|
30
30
|
|
|
31
31
|
const USDC_ABI = [{ name: "balanceOf", type: "function", stateMutability: "view", inputs: [{ name: "account", type: "address" }], outputs: [{ name: "", type: "uint256" }] }];
|
|
@@ -40,29 +40,29 @@ async function loadDeps() {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
async function status() {
|
|
43
|
-
const w =
|
|
43
|
+
const w = readAllowance();
|
|
44
44
|
if (!w) {
|
|
45
|
-
console.log(JSON.stringify({ status: "no_wallet", message: "No
|
|
45
|
+
console.log(JSON.stringify({ status: "no_wallet", message: "No agent allowance found. Run: run402 allowance create" }));
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
|
-
console.log(JSON.stringify({ status: "ok", address: w.address, created: w.created, funded: w.funded || false, path:
|
|
48
|
+
console.log(JSON.stringify({ status: "ok", address: w.address, created: w.created, funded: w.funded || false, path: ALLOWANCE_FILE }));
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
async function create() {
|
|
52
|
-
if (
|
|
53
|
-
console.log(JSON.stringify({ status: "error", message: "
|
|
52
|
+
if (readAllowance()) {
|
|
53
|
+
console.log(JSON.stringify({ status: "error", message: "Agent allowance already exists. Use 'status' to check it." }));
|
|
54
54
|
process.exit(1);
|
|
55
55
|
}
|
|
56
56
|
const { generatePrivateKey, privateKeyToAccount } = await loadDeps();
|
|
57
57
|
const privateKey = generatePrivateKey();
|
|
58
58
|
const account = privateKeyToAccount(privateKey);
|
|
59
|
-
|
|
60
|
-
console.log(JSON.stringify({ status: "ok", address: account.address, message: `
|
|
59
|
+
saveAllowance({ address: account.address, privateKey, created: new Date().toISOString(), funded: false });
|
|
60
|
+
console.log(JSON.stringify({ status: "ok", address: account.address, message: `Agent allowance created. Stored locally at ${ALLOWANCE_FILE}` }));
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
async function fund() {
|
|
64
|
-
const w =
|
|
65
|
-
if (!w) { console.log(JSON.stringify({ status: "error", message: "No
|
|
64
|
+
const w = readAllowance();
|
|
65
|
+
if (!w) { console.log(JSON.stringify({ status: "error", message: "No agent allowance. Run: run402 allowance create" })); process.exit(1); }
|
|
66
66
|
|
|
67
67
|
const { createPublicClient, http, baseSepolia } = await loadDeps();
|
|
68
68
|
const client = createPublicClient({ chain: baseSepolia, transport: http() });
|
|
@@ -80,7 +80,7 @@ async function fund() {
|
|
|
80
80
|
await new Promise(r => setTimeout(r, 1000));
|
|
81
81
|
const now = await readUsdcBalance(client, USDC_SEPOLIA, w.address).catch(() => before);
|
|
82
82
|
if (now > before) {
|
|
83
|
-
|
|
83
|
+
saveAllowance({ ...w, funded: true, lastFaucet: new Date().toISOString() });
|
|
84
84
|
console.log(JSON.stringify({
|
|
85
85
|
address: w.address,
|
|
86
86
|
onchain: {
|
|
@@ -91,7 +91,7 @@ async function fund() {
|
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
saveAllowance({ ...w, funded: true, lastFaucet: new Date().toISOString() });
|
|
95
95
|
console.log(JSON.stringify({ status: "ok", message: "Faucet request sent but balance not yet confirmed", ...data }));
|
|
96
96
|
}
|
|
97
97
|
|
|
@@ -101,8 +101,8 @@ async function readUsdcBalance(client, usdc, address) {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
async function balance() {
|
|
104
|
-
const w =
|
|
105
|
-
if (!w) { console.log(JSON.stringify({ status: "error", message: "No
|
|
104
|
+
const w = readAllowance();
|
|
105
|
+
if (!w) { console.log(JSON.stringify({ status: "error", message: "No agent allowance. Run: run402 allowance create" })); process.exit(1); }
|
|
106
106
|
|
|
107
107
|
const { createPublicClient, http, base, baseSepolia } = await loadDeps();
|
|
108
108
|
const mainnetClient = createPublicClient({ chain: base, transport: http() });
|
|
@@ -127,14 +127,14 @@ async function balance() {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
async function exportAddr() {
|
|
130
|
-
const w =
|
|
131
|
-
if (!w) { console.log(JSON.stringify({ status: "error", message: "No
|
|
130
|
+
const w = readAllowance();
|
|
131
|
+
if (!w) { console.log(JSON.stringify({ status: "error", message: "No agent allowance." })); process.exit(1); }
|
|
132
132
|
console.log(w.address);
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
async function checkout(args) {
|
|
136
|
-
const w =
|
|
137
|
-
if (!w) { console.log(JSON.stringify({ status: "error", message: "No
|
|
136
|
+
const w = readAllowance();
|
|
137
|
+
if (!w) { console.log(JSON.stringify({ status: "error", message: "No agent allowance. Run: run402 allowance create" })); process.exit(1); }
|
|
138
138
|
let amount = null;
|
|
139
139
|
for (let i = 0; i < args.length; i++) {
|
|
140
140
|
if (args[i] === "--amount" && args[i + 1]) amount = parseInt(args[++i], 10);
|
|
@@ -151,8 +151,8 @@ async function checkout(args) {
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
async function history(args) {
|
|
154
|
-
const w =
|
|
155
|
-
if (!w) { console.log(JSON.stringify({ status: "error", message: "No
|
|
154
|
+
const w = readAllowance();
|
|
155
|
+
if (!w) { console.log(JSON.stringify({ status: "error", message: "No agent allowance. Run: run402 allowance create" })); process.exit(1); }
|
|
156
156
|
let limit = 20;
|
|
157
157
|
for (let i = 0; i < args.length; i++) {
|
|
158
158
|
if (args[i] === "--limit" && args[i + 1]) limit = parseInt(args[++i], 10);
|
package/lib/apps.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { findProject, API,
|
|
1
|
+
import { findProject, API, allowanceAuthHeaders, saveProject } from "./config.mjs";
|
|
2
2
|
|
|
3
3
|
const HELP = `run402 apps — Browse and manage the app marketplace
|
|
4
4
|
|
|
@@ -47,7 +47,7 @@ async function fork(versionId, name, args) {
|
|
|
47
47
|
if (args[i] === "--tier" && args[i + 1]) opts.tier = args[++i];
|
|
48
48
|
if (args[i] === "--subdomain" && args[i + 1]) opts.subdomain = args[++i];
|
|
49
49
|
}
|
|
50
|
-
const authHeaders = await
|
|
50
|
+
const authHeaders = await allowanceAuthHeaders();
|
|
51
51
|
|
|
52
52
|
const body = { version_id: versionId, name };
|
|
53
53
|
if (opts.subdomain) body.subdomain = opts.subdomain;
|
package/lib/config.mjs
CHANGED
|
@@ -3,28 +3,31 @@
|
|
|
3
3
|
* Adds CLI-specific behavior: process.exit() on errors.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { getApiBase, getConfigDir, getKeystorePath,
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { loadKeyStore, getProject, saveProject, removeProject, saveKeyStore } from "../../core/dist/keystore.js";
|
|
6
|
+
import { getApiBase, getConfigDir, getKeystorePath, getAllowancePath } from "../core-dist/config.js";
|
|
7
|
+
import { readAllowance as coreReadAllowance, saveAllowance as coreSaveAllowance } from "../core-dist/allowance.js";
|
|
8
|
+
import { loadKeyStore, getProject, saveProject, removeProject, saveKeyStore } from "../core-dist/keystore.js";
|
|
10
9
|
|
|
11
10
|
export const CONFIG_DIR = getConfigDir();
|
|
12
|
-
export const
|
|
11
|
+
export const ALLOWANCE_FILE = getAllowancePath();
|
|
13
12
|
export const PROJECTS_FILE = getKeystorePath();
|
|
14
13
|
export const API = getApiBase();
|
|
15
14
|
|
|
16
|
-
export function
|
|
17
|
-
return
|
|
15
|
+
export function readAllowance() {
|
|
16
|
+
return coreReadAllowance();
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
export function
|
|
21
|
-
|
|
19
|
+
export function saveAllowance(data) {
|
|
20
|
+
coreSaveAllowance(data);
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
export async function
|
|
25
|
-
const
|
|
26
|
-
if (!
|
|
27
|
-
|
|
23
|
+
export async function allowanceAuthHeaders() {
|
|
24
|
+
const w = readAllowance();
|
|
25
|
+
if (!w) { console.error(JSON.stringify({ status: "error", message: "No agent allowance found. Run: run402 allowance create" })); process.exit(1); }
|
|
26
|
+
const { privateKeyToAccount } = await import("viem/accounts");
|
|
27
|
+
const account = privateKeyToAccount(w.privateKey);
|
|
28
|
+
const timestamp = Math.floor(Date.now() / 1000).toString();
|
|
29
|
+
const signature = await account.signMessage({ message: `run402:${timestamp}` });
|
|
30
|
+
return { "X-Run402-Wallet": account.address, "X-Run402-Signature": signature, "X-Run402-Timestamp": timestamp };
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
export function findProject(id) {
|
package/lib/deploy.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from "fs";
|
|
2
|
-
import { API,
|
|
2
|
+
import { API, allowanceAuthHeaders, saveProject } from "./config.mjs";
|
|
3
3
|
|
|
4
4
|
const HELP = `run402 deploy — Deploy a full-stack app or static site on Run402
|
|
5
5
|
|
|
@@ -24,7 +24,7 @@ Examples:
|
|
|
24
24
|
cat app.json | run402 deploy
|
|
25
25
|
|
|
26
26
|
Prerequisites:
|
|
27
|
-
- run402 init Set up
|
|
27
|
+
- run402 init Set up allowance and funding
|
|
28
28
|
- run402 tier set prototype Subscribe to a tier
|
|
29
29
|
|
|
30
30
|
Notes:
|
|
@@ -48,7 +48,7 @@ export async function run(args) {
|
|
|
48
48
|
|
|
49
49
|
const manifest = opts.manifest ? JSON.parse(readFileSync(opts.manifest, "utf-8")) : JSON.parse(await readStdin());
|
|
50
50
|
|
|
51
|
-
const authHeaders = await
|
|
51
|
+
const authHeaders = await allowanceAuthHeaders();
|
|
52
52
|
const res = await fetch(`${API}/deploy/v1`, { method: "POST", headers: { "Content-Type": "application/json", ...authHeaders }, body: JSON.stringify(manifest) });
|
|
53
53
|
const result = await res.json();
|
|
54
54
|
if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...result })); process.exit(1); }
|
package/lib/functions.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFileSync, existsSync } from "fs";
|
|
2
|
-
import { findProject,
|
|
2
|
+
import { findProject, readAllowance, API, ALLOWANCE_FILE } from "./config.mjs";
|
|
3
3
|
|
|
4
4
|
const HELP = `run402 functions — Manage serverless functions
|
|
5
5
|
|
|
@@ -28,18 +28,18 @@ Notes:
|
|
|
28
28
|
`;
|
|
29
29
|
|
|
30
30
|
async function setupPaidFetch() {
|
|
31
|
-
if (!existsSync(
|
|
32
|
-
console.error(JSON.stringify({ status: "error", message: "No
|
|
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
33
|
process.exit(1);
|
|
34
34
|
}
|
|
35
|
-
const
|
|
35
|
+
const allowance = readAllowance();
|
|
36
36
|
const { privateKeyToAccount } = await import("viem/accounts");
|
|
37
37
|
const { createPublicClient, http } = await import("viem");
|
|
38
38
|
const { baseSepolia } = await import("viem/chains");
|
|
39
39
|
const { x402Client, wrapFetchWithPayment } = await import("@x402/fetch");
|
|
40
40
|
const { ExactEvmScheme } = await import("@x402/evm/exact/client");
|
|
41
41
|
const { toClientEvmSigner } = await import("@x402/evm");
|
|
42
|
-
const account = privateKeyToAccount(
|
|
42
|
+
const account = privateKeyToAccount(allowance.privateKey);
|
|
43
43
|
const publicClient = createPublicClient({ chain: baseSepolia, transport: http() });
|
|
44
44
|
const signer = toClientEvmSigner(account, publicClient);
|
|
45
45
|
const client = new x402Client();
|
package/lib/image.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { writeFileSync } from "fs";
|
|
2
|
-
import { API,
|
|
2
|
+
import { API, ALLOWANCE_FILE } from "./config.mjs";
|
|
3
3
|
import { setupPaidFetch } from "./paid-fetch.mjs";
|
|
4
4
|
|
|
5
5
|
const HELP = `run402 image — Generate AI images via x402 micropayments
|
|
@@ -22,7 +22,7 @@ Output (without --output):
|
|
|
22
22
|
{ "status": "ok", "aspect": "square", "content_type": "image/png", "image": "<base64>" }
|
|
23
23
|
|
|
24
24
|
Notes:
|
|
25
|
-
- Requires a funded
|
|
25
|
+
- Requires a funded allowance (run402 allowance create && run402 allowance fund)
|
|
26
26
|
- Payments are processed automatically via x402 micropayments (Base Sepolia USDC)
|
|
27
27
|
- Use --output to save directly to a file instead of printing base64
|
|
28
28
|
`;
|
package/lib/init.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readAllowance, saveAllowance, loadKeyStore, CONFIG_DIR, ALLOWANCE_FILE, API } from "./config.mjs";
|
|
2
2
|
import { mkdirSync } from "fs";
|
|
3
3
|
|
|
4
4
|
const USDC_ABI = [{ name: "balanceOf", type: "function", stateMutability: "view", inputs: [{ name: "account", type: "address" }], outputs: [{ name: "", type: "uint256" }] }];
|
|
@@ -14,17 +14,17 @@ export async function run() {
|
|
|
14
14
|
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
15
15
|
line("Config", CONFIG_DIR);
|
|
16
16
|
|
|
17
|
-
// 2.
|
|
18
|
-
let
|
|
19
|
-
if (!
|
|
17
|
+
// 2. Allowance
|
|
18
|
+
let allowance = readAllowance();
|
|
19
|
+
if (!allowance) {
|
|
20
20
|
const { generatePrivateKey, privateKeyToAccount } = await import("viem/accounts");
|
|
21
21
|
const privateKey = generatePrivateKey();
|
|
22
22
|
const account = privateKeyToAccount(privateKey);
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
line("
|
|
23
|
+
allowance = { address: account.address, privateKey, created: new Date().toISOString(), funded: false };
|
|
24
|
+
saveAllowance(allowance);
|
|
25
|
+
line("Allowance", `${short(allowance.address)} (created)`);
|
|
26
26
|
} else {
|
|
27
|
-
line("
|
|
27
|
+
line("Allowance", short(allowance.address));
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
// 3. Balance — check on-chain, faucet if zero
|
|
@@ -34,7 +34,7 @@ export async function run() {
|
|
|
34
34
|
|
|
35
35
|
let balance = 0;
|
|
36
36
|
try {
|
|
37
|
-
const raw = await client.readContract({ address: USDC_SEPOLIA, abi: USDC_ABI, functionName: "balanceOf", args: [
|
|
37
|
+
const raw = await client.readContract({ address: USDC_SEPOLIA, abi: USDC_ABI, functionName: "balanceOf", args: [allowance.address] });
|
|
38
38
|
balance = Number(raw);
|
|
39
39
|
} catch {}
|
|
40
40
|
|
|
@@ -43,19 +43,19 @@ export async function run() {
|
|
|
43
43
|
const res = await fetch(`${API}/faucet/v1`, {
|
|
44
44
|
method: "POST",
|
|
45
45
|
headers: { "Content-Type": "application/json" },
|
|
46
|
-
body: JSON.stringify({ address:
|
|
46
|
+
body: JSON.stringify({ address: allowance.address }),
|
|
47
47
|
});
|
|
48
48
|
if (res.ok) {
|
|
49
49
|
// Poll for up to 30s
|
|
50
50
|
for (let i = 0; i < 30; i++) {
|
|
51
51
|
await new Promise(r => setTimeout(r, 1000));
|
|
52
52
|
try {
|
|
53
|
-
const raw = await client.readContract({ address: USDC_SEPOLIA, abi: USDC_ABI, functionName: "balanceOf", args: [
|
|
53
|
+
const raw = await client.readContract({ address: USDC_SEPOLIA, abi: USDC_ABI, functionName: "balanceOf", args: [allowance.address] });
|
|
54
54
|
balance = Number(raw);
|
|
55
55
|
if (balance > 0) break;
|
|
56
56
|
} catch {}
|
|
57
57
|
}
|
|
58
|
-
|
|
58
|
+
saveAllowance({ ...allowance, funded: true, lastFaucet: new Date().toISOString() });
|
|
59
59
|
if (balance > 0) {
|
|
60
60
|
line("Balance", `${(balance / 1e6).toFixed(2)} USDC (funded)`);
|
|
61
61
|
} else {
|
|
@@ -74,7 +74,7 @@ export async function run() {
|
|
|
74
74
|
let tierInfo = null;
|
|
75
75
|
try {
|
|
76
76
|
const { privateKeyToAccount } = await import("viem/accounts");
|
|
77
|
-
const account = privateKeyToAccount(
|
|
77
|
+
const account = privateKeyToAccount(allowance.privateKey);
|
|
78
78
|
const timestamp = Math.floor(Date.now() / 1000).toString();
|
|
79
79
|
const signature = await account.signMessage({ message: `run402:${timestamp}` });
|
|
80
80
|
const res = await fetch(`${API}/tiers/v1/status`, {
|
package/lib/message.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { API,
|
|
1
|
+
import { API, allowanceAuthHeaders } from "./config.mjs";
|
|
2
2
|
|
|
3
3
|
const HELP = `run402 message — Send messages to Run402 developers
|
|
4
4
|
|
|
@@ -6,8 +6,8 @@ Usage:
|
|
|
6
6
|
run402 message send <text>
|
|
7
7
|
|
|
8
8
|
Notes:
|
|
9
|
-
- Free with
|
|
10
|
-
- Requires
|
|
9
|
+
- Free with allowance auth
|
|
10
|
+
- Requires an allowance (run402 allowance create)
|
|
11
11
|
|
|
12
12
|
Examples:
|
|
13
13
|
run402 message send "Hello from my agent!"
|
|
@@ -15,7 +15,7 @@ Examples:
|
|
|
15
15
|
|
|
16
16
|
async function send(text) {
|
|
17
17
|
if (!text) { console.error(JSON.stringify({ status: "error", message: "Missing message text" })); process.exit(1); }
|
|
18
|
-
const authHeaders = await
|
|
18
|
+
const authHeaders = await allowanceAuthHeaders();
|
|
19
19
|
|
|
20
20
|
const res = await fetch(`${API}/message/v1`, {
|
|
21
21
|
method: "POST",
|
package/lib/paid-fetch.mjs
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared x402 payment wrapper for CLI commands that need paid fetch.
|
|
3
|
-
* Uses viem for
|
|
3
|
+
* Uses viem for allowance signing + @x402/fetch for payment wrapping.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { readAllowance, ALLOWANCE_FILE } from "./config.mjs";
|
|
7
7
|
import { existsSync } from "fs";
|
|
8
8
|
|
|
9
9
|
export async function setupPaidFetch() {
|
|
10
|
-
if (!existsSync(
|
|
11
|
-
console.error(JSON.stringify({ status: "error", message: "No
|
|
10
|
+
if (!existsSync(ALLOWANCE_FILE)) {
|
|
11
|
+
console.error(JSON.stringify({ status: "error", message: "No agent allowance found. Run: run402 allowance create && run402 allowance fund" }));
|
|
12
12
|
process.exit(1);
|
|
13
13
|
}
|
|
14
|
-
const
|
|
14
|
+
const allowance = readAllowance();
|
|
15
15
|
const { privateKeyToAccount } = await import("viem/accounts");
|
|
16
16
|
const { createPublicClient, http } = await import("viem");
|
|
17
17
|
const { baseSepolia } = await import("viem/chains");
|
|
18
18
|
const { x402Client, wrapFetchWithPayment } = await import("@x402/fetch");
|
|
19
19
|
const { ExactEvmScheme } = await import("@x402/evm/exact/client");
|
|
20
20
|
const { toClientEvmSigner } = await import("@x402/evm");
|
|
21
|
-
const account = privateKeyToAccount(
|
|
21
|
+
const account = privateKeyToAccount(allowance.privateKey);
|
|
22
22
|
const publicClient = createPublicClient({ chain: baseSepolia, transport: http() });
|
|
23
23
|
const signer = toClientEvmSigner(account, publicClient);
|
|
24
24
|
const client = new x402Client();
|
package/lib/projects.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { findProject, loadKeyStore, saveProject, removeProject, API,
|
|
1
|
+
import { findProject, loadKeyStore, saveProject, removeProject, API, allowanceAuthHeaders } from "./config.mjs";
|
|
2
2
|
|
|
3
3
|
const HELP = `run402 projects — Manage your deployed Run402 projects
|
|
4
4
|
|
|
@@ -31,7 +31,7 @@ Examples:
|
|
|
31
31
|
Notes:
|
|
32
32
|
- <id> is the project_id shown in 'run402 projects list'
|
|
33
33
|
- 'rest' uses PostgREST query syntax (table name + optional query string)
|
|
34
|
-
- 'provision' requires a funded
|
|
34
|
+
- 'provision' requires a funded allowance — payment is automatic via x402
|
|
35
35
|
- RLS templates: user_owns_rows, public_read, public_read_write
|
|
36
36
|
`;
|
|
37
37
|
|
|
@@ -48,7 +48,7 @@ async function provision(args) {
|
|
|
48
48
|
if (args[i] === "--tier" && args[i + 1]) opts.tier = args[++i];
|
|
49
49
|
if (args[i] === "--name" && args[i + 1]) opts.name = args[++i];
|
|
50
50
|
}
|
|
51
|
-
const authHeaders = await
|
|
51
|
+
const authHeaders = await allowanceAuthHeaders();
|
|
52
52
|
const body = { tier: opts.tier };
|
|
53
53
|
if (opts.name) body.name = opts.name;
|
|
54
54
|
const res = await fetch(`${API}/projects/v1`, {
|
package/lib/sites.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from "fs";
|
|
2
|
-
import { API,
|
|
2
|
+
import { API, allowanceAuthHeaders } from "./config.mjs";
|
|
3
3
|
|
|
4
4
|
const HELP = `run402 sites — Deploy and manage static sites
|
|
5
5
|
|
|
@@ -34,7 +34,7 @@ Examples:
|
|
|
34
34
|
|
|
35
35
|
Notes:
|
|
36
36
|
- Must include at least index.html in the files array
|
|
37
|
-
- Free with active tier — requires
|
|
37
|
+
- Free with active tier — requires allowance auth
|
|
38
38
|
`;
|
|
39
39
|
|
|
40
40
|
async function readStdin() {
|
|
@@ -58,7 +58,7 @@ async function deploy(args) {
|
|
|
58
58
|
if (opts.project) body.project = opts.project;
|
|
59
59
|
if (opts.target) body.target = opts.target;
|
|
60
60
|
|
|
61
|
-
const authHeaders = await
|
|
61
|
+
const authHeaders = await allowanceAuthHeaders();
|
|
62
62
|
const res = await fetch(`${API}/deployments/v1`, {
|
|
63
63
|
method: "POST",
|
|
64
64
|
headers: { "Content-Type": "application/json", ...authHeaders },
|
package/lib/tier.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readAllowance, ALLOWANCE_FILE, API } from "./config.mjs";
|
|
2
2
|
import { setupPaidFetch } from "./paid-fetch.mjs";
|
|
3
3
|
|
|
4
4
|
const HELP = `run402 tier — Manage your Run402 tier subscription
|
|
@@ -10,9 +10,9 @@ Subcommands:
|
|
|
10
10
|
status Show current tier (tier name, status, expiry)
|
|
11
11
|
set <tier> Subscribe, renew, or upgrade (pays via x402)
|
|
12
12
|
|
|
13
|
-
Tiers: prototype (
|
|
13
|
+
Tiers: prototype (free/testnet, 7d), hobby ($5/30d), team ($20/30d)
|
|
14
14
|
|
|
15
|
-
The server auto-detects the action based on your
|
|
15
|
+
The server auto-detects the action based on your allowance state:
|
|
16
16
|
- No tier or expired → subscribe
|
|
17
17
|
- Same tier, active → renew (extends from expiry)
|
|
18
18
|
- Higher tier → upgrade (prorated refund to allowance)
|
|
@@ -25,8 +25,8 @@ Examples:
|
|
|
25
25
|
`;
|
|
26
26
|
|
|
27
27
|
async function status() {
|
|
28
|
-
const w =
|
|
29
|
-
if (!w) { console.log(JSON.stringify({ status: "error", message: "No
|
|
28
|
+
const w = readAllowance();
|
|
29
|
+
if (!w) { console.log(JSON.stringify({ status: "error", message: "No agent allowance. Run: run402 allowance create" })); process.exit(1); }
|
|
30
30
|
const { privateKeyToAccount } = await import("viem/accounts");
|
|
31
31
|
const account = privateKeyToAccount(w.privateKey);
|
|
32
32
|
const timestamp = Math.floor(Date.now() / 1000).toString();
|
package/package.json
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "run402",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.2",
|
|
4
4
|
"description": "CLI for Run402 — provision Postgres databases, deploy static sites, generate images, and manage wallets via x402 micropayments.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"run402": "cli.mjs"
|
|
8
8
|
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"prepack": "mkdir -p core-dist && cp ../core/dist/*.js core-dist/"
|
|
11
|
+
},
|
|
9
12
|
"files": [
|
|
10
13
|
"cli.mjs",
|
|
11
|
-
"lib/"
|
|
14
|
+
"lib/",
|
|
15
|
+
"core-dist/"
|
|
12
16
|
],
|
|
13
17
|
"dependencies": {
|
|
14
18
|
"@x402/evm": "^2.6.0",
|