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 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 wallets via x402 micropayments.
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 wallet
21
- run402 wallet create
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 wallet fund
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 wallet`
32
+ ### `run402 allowance`
33
33
 
34
- Manage your local x402 wallet.
34
+ Manage your local agent allowance.
35
35
 
36
36
  ```bash
37
- run402 wallet create # Generate a new wallet
38
- run402 wallet status # Show address, network, funding status
39
- run402 wallet fund # Request test USDC from the faucet
40
- run402 wallet export # Print wallet address (for scripting)
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 wallet --help
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
- - Wallet stored at `~/.run402/wallet.json`
109
+ - Agent allowance stored at `~/.run402/allowance.json`
110
110
  - Project credentials stored at `~/.run402/projects.json`
111
- - Network: Base Sepolia (testnet) USDC for x402 payments
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 wallet, funding, and check tier status
17
- wallet Manage your x402 wallet (create, fund, balance, status)
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 wallet create
35
- run402 wallet fund
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 "wallet": {
61
- const { run } = await import("./lib/wallet.mjs");
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, walletAuthHeaders } from "./config.mjs";
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 wallet auth
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 walletAuthHeaders();
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 { readWallet, saveWallet, WALLET_FILE, API } from "./config.mjs";
1
+ import { readAllowance, saveAllowance, ALLOWANCE_FILE, API } from "./config.mjs";
2
2
 
3
- const HELP = `run402 wallet — Manage your x402 wallet
3
+ const HELP = `run402 allowance — Manage your agent allowance
4
4
 
5
5
  Usage:
6
- run402 wallet <subcommand>
6
+ run402 allowance <subcommand>
7
7
 
8
8
  Subcommands:
9
- status Show wallet address, network, and funding status
10
- create Generate a new wallet and save it locally
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 wallet address (useful for scripting)
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
- - Wallet is stored locally at ~/.run402/wallet.json
19
- - The wallet works on any EVM chain (currently Run402 uses Base Mainnet and Sepolia for testnet)
20
- - You need to create and fund a wallet before any x402 transaction with Run402
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 wallet create
24
- run402 wallet status
25
- run402 wallet fund
26
- run402 wallet export
27
- run402 wallet checkout --amount 5000000
28
- run402 wallet history --limit 10
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 = readWallet();
43
+ const w = readAllowance();
44
44
  if (!w) {
45
- console.log(JSON.stringify({ status: "no_wallet", message: "No wallet found. Run: run402 wallet create" }));
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: WALLET_FILE }));
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 (readWallet()) {
53
- console.log(JSON.stringify({ status: "error", message: "Wallet already exists. Use 'status' to check it." }));
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
- saveWallet({ address: account.address, privateKey, created: new Date().toISOString(), funded: false });
60
- console.log(JSON.stringify({ status: "ok", address: account.address, message: `Wallet created. Stored locally at ${WALLET_FILE}` }));
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 = readWallet();
65
- if (!w) { console.log(JSON.stringify({ status: "error", message: "No wallet. Run: run402 wallet create" })); process.exit(1); }
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
- saveWallet({ ...w, funded: true, lastFaucet: new Date().toISOString() });
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
- saveWallet({ ...w, funded: true, lastFaucet: new Date().toISOString() });
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 = readWallet();
105
- if (!w) { console.log(JSON.stringify({ status: "error", message: "No wallet. Run: run402 wallet create" })); process.exit(1); }
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 = readWallet();
131
- if (!w) { console.log(JSON.stringify({ status: "error", message: "No wallet." })); process.exit(1); }
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 = readWallet();
137
- if (!w) { console.log(JSON.stringify({ status: "error", message: "No wallet. Run: run402 wallet create" })); process.exit(1); }
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 = readWallet();
155
- if (!w) { console.log(JSON.stringify({ status: "error", message: "No wallet. Run: run402 wallet create" })); process.exit(1); }
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, walletAuthHeaders, saveProject } from "./config.mjs";
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 walletAuthHeaders();
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, getWalletPath } from "../../core/dist/config.js";
7
- import { readWallet as coreReadWallet, saveWallet as coreSaveWallet } from "../../core/dist/wallet.js";
8
- import { getWalletAuthHeaders } from "../../core/dist/wallet-auth.js";
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 WALLET_FILE = getWalletPath();
11
+ export const ALLOWANCE_FILE = getAllowancePath();
13
12
  export const PROJECTS_FILE = getKeystorePath();
14
13
  export const API = getApiBase();
15
14
 
16
- export function readWallet() {
17
- return coreReadWallet();
15
+ export function readAllowance() {
16
+ return coreReadAllowance();
18
17
  }
19
18
 
20
- export function saveWallet(data) {
21
- coreSaveWallet(data);
19
+ export function saveAllowance(data) {
20
+ coreSaveAllowance(data);
22
21
  }
23
22
 
24
- export async function walletAuthHeaders() {
25
- const headers = getWalletAuthHeaders();
26
- if (!headers) { console.error(JSON.stringify({ status: "error", message: "No wallet found. Run: run402 wallet create" })); process.exit(1); }
27
- return headers;
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, walletAuthHeaders, saveProject } from "./config.mjs";
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 wallet and funding
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 walletAuthHeaders();
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, readWallet, API, WALLET_FILE } from "./config.mjs";
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(WALLET_FILE)) {
32
- console.error(JSON.stringify({ status: "error", message: "No wallet found. Run: run402 wallet create && run402 wallet fund" }));
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 wallet = readWallet();
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(wallet.privateKey);
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, WALLET_FILE } from "./config.mjs";
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 wallet (run402 wallet create && run402 wallet fund)
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 { readWallet, saveWallet, loadKeyStore, CONFIG_DIR, WALLET_FILE, API } from "./config.mjs";
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. Wallet
18
- let wallet = readWallet();
19
- if (!wallet) {
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
- wallet = { address: account.address, privateKey, created: new Date().toISOString(), funded: false };
24
- saveWallet(wallet);
25
- line("Wallet", `${short(wallet.address)} (created)`);
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("Wallet", short(wallet.address));
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: [wallet.address] });
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: wallet.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: [wallet.address] });
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
- saveWallet({ ...wallet, funded: true, lastFaucet: new Date().toISOString() });
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(wallet.privateKey);
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, walletAuthHeaders } from "./config.mjs";
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 wallet auth
10
- - Requires a wallet (run402 wallet create)
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 walletAuthHeaders();
18
+ const authHeaders = await allowanceAuthHeaders();
19
19
 
20
20
  const res = await fetch(`${API}/message/v1`, {
21
21
  method: "POST",
@@ -1,24 +1,24 @@
1
1
  /**
2
2
  * Shared x402 payment wrapper for CLI commands that need paid fetch.
3
- * Uses viem for wallet signing + @x402/fetch for payment wrapping.
3
+ * Uses viem for allowance signing + @x402/fetch for payment wrapping.
4
4
  */
5
5
 
6
- import { readWallet, WALLET_FILE } from "./config.mjs";
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(WALLET_FILE)) {
11
- console.error(JSON.stringify({ status: "error", message: "No wallet found. Run: run402 wallet create && run402 wallet fund" }));
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 wallet = readWallet();
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(wallet.privateKey);
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, walletAuthHeaders } from "./config.mjs";
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 wallet — payment is automatic via x402
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 walletAuthHeaders();
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, walletAuthHeaders } from "./config.mjs";
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 wallet auth
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 walletAuthHeaders();
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 { readWallet, WALLET_FILE, API } from "./config.mjs";
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 ($0.10/7d), hobby ($5/30d), team ($20/30d)
13
+ Tiers: prototype (free/testnet, 7d), hobby ($5/30d), team ($20/30d)
14
14
 
15
- The server auto-detects the action based on your wallet state:
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 = readWallet();
29
- if (!w) { console.log(JSON.stringify({ status: "error", message: "No wallet. Run: run402 wallet create" })); process.exit(1); }
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.0",
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",