run402 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli.mjs CHANGED
@@ -23,6 +23,8 @@ Commands:
23
23
  subdomains Manage custom subdomains (claim, list, delete)
24
24
  apps Browse and manage the app marketplace
25
25
  image Generate AI images via x402 micropayments
26
+ message Send messages to Run402 developers
27
+ agent Manage agent identity (contact info)
26
28
 
27
29
  Run 'run402 <command> --help' for detailed usage of each command.
28
30
 
@@ -98,6 +100,16 @@ switch (cmd) {
98
100
  await run(sub, rest);
99
101
  break;
100
102
  }
103
+ case "message": {
104
+ const { run } = await import("./lib/message.mjs");
105
+ await run(sub, rest);
106
+ break;
107
+ }
108
+ case "agent": {
109
+ const { run } = await import("./lib/agent.mjs");
110
+ await run(sub, rest);
111
+ break;
112
+ }
101
113
  default:
102
114
  console.error(`Unknown command: ${cmd}\n`);
103
115
  console.log(HELP);
package/lib/agent.mjs ADDED
@@ -0,0 +1,68 @@
1
+ import { readWallet, API, WALLET_FILE } from "./config.mjs";
2
+ import { existsSync } from "fs";
3
+
4
+ const HELP = `run402 agent — Manage agent identity
5
+
6
+ Usage:
7
+ run402 agent contact --name <name> [--email <email>] [--webhook <url>]
8
+
9
+ Notes:
10
+ - Costs $0.001 USDC via x402
11
+ - Registers contact info so Run402 can reach your agent
12
+ - Only name is required; email and webhook are optional
13
+
14
+ Examples:
15
+ run402 agent contact --name my-agent
16
+ run402 agent contact --name my-agent --email ops@example.com --webhook https://example.com/hook
17
+ `;
18
+
19
+ async function contact(args) {
20
+ let name = null, email = null, webhook = null;
21
+ for (let i = 0; i < args.length; i++) {
22
+ if (args[i] === "--name" && args[i + 1]) name = args[++i];
23
+ if (args[i] === "--email" && args[i + 1]) email = args[++i];
24
+ if (args[i] === "--webhook" && args[i + 1]) webhook = args[++i];
25
+ }
26
+ if (!name) { console.error(JSON.stringify({ status: "error", message: "Missing --name <name>" })); process.exit(1); }
27
+ if (!existsSync(WALLET_FILE)) {
28
+ console.error(JSON.stringify({ status: "error", message: "No wallet found. Run: run402 wallet create && run402 wallet fund" }));
29
+ process.exit(1);
30
+ }
31
+
32
+ const wallet = readWallet();
33
+ const { privateKeyToAccount } = await import("viem/accounts");
34
+ const { createPublicClient, http } = await import("viem");
35
+ const { baseSepolia } = await import("viem/chains");
36
+ const { x402Client, wrapFetchWithPayment } = await import("@x402/fetch");
37
+ const { ExactEvmScheme } = await import("@x402/evm/exact/client");
38
+ const { toClientEvmSigner } = await import("@x402/evm");
39
+ const account = privateKeyToAccount(wallet.privateKey);
40
+ const publicClient = createPublicClient({ chain: baseSepolia, transport: http() });
41
+ const signer = toClientEvmSigner(account, publicClient);
42
+ const client = new x402Client();
43
+ client.register("eip155:84532", new ExactEvmScheme(signer));
44
+ const fetchPaid = wrapFetchWithPayment(fetch, client);
45
+
46
+ const body = { name };
47
+ if (email) body.email = email;
48
+ if (webhook) body.webhook = webhook;
49
+
50
+ const res = await fetchPaid(`${API}/agent/v1/contact`, {
51
+ method: "PUT",
52
+ headers: { "Content-Type": "application/json" },
53
+ body: JSON.stringify(body),
54
+ });
55
+ const data = await res.json();
56
+ if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
57
+ console.log(JSON.stringify(data, null, 2));
58
+ }
59
+
60
+ export async function run(sub, args) {
61
+ if (!sub || sub === '--help' || sub === '-h') { console.log(HELP); process.exit(0); }
62
+ if (sub !== "contact") {
63
+ console.error(`Unknown subcommand: ${sub}\n`);
64
+ console.log(HELP);
65
+ process.exit(1);
66
+ }
67
+ await contact(args);
68
+ }
package/lib/apps.mjs CHANGED
@@ -14,6 +14,10 @@ Subcommands:
14
14
  publish <id> [--description <desc>] [--tags <t1,t2>] [--visibility <v>] [--fork-allowed]
15
15
  Publish a project as an app
16
16
  versions <id> List published versions of a project
17
+ inspect <version_id> Inspect a published app version
18
+ update <project_id> <version_id> [--description <desc>] [--tags <t1,t2>] [--visibility <v>] [--fork-allowed] [--no-fork]
19
+ Update a published version
20
+ delete <project_id> <version_id> Delete a published version
17
21
 
18
22
  Examples:
19
23
  run402 apps browse
@@ -21,10 +25,13 @@ Examples:
21
25
  run402 apps fork ver_abc123 my-todo --tier prototype
22
26
  run402 apps publish proj123 --description "Todo app" --tags todo,auth --visibility public --fork-allowed
23
27
  run402 apps versions proj123
28
+ run402 apps inspect ver_abc123
29
+ run402 apps update proj123 ver_abc123 --description "Updated" --tags todo
30
+ run402 apps delete proj123 ver_abc123
24
31
  `;
25
32
 
26
33
  async function browse(args) {
27
- let url = `${API}/v1/apps`;
34
+ let url = `${API}/apps/v1`;
28
35
  const tags = [];
29
36
  for (let i = 0; i < args.length; i++) {
30
37
  if (args[i] === "--tag" && args[i + 1]) tags.push(args[++i]);
@@ -64,7 +71,7 @@ async function fork(versionId, name, args) {
64
71
  const body = { version_id: versionId, name };
65
72
  if (opts.subdomain) body.subdomain = opts.subdomain;
66
73
 
67
- const res = await fetchPaid(`${API}/v1/fork/${opts.tier}`, {
74
+ const res = await fetchPaid(`${API}/fork/v1/${opts.tier}`, {
68
75
  method: "POST",
69
76
  headers: { "Content-Type": "application/json" },
70
77
  body: JSON.stringify(body),
@@ -102,7 +109,7 @@ async function publish(projectId, args) {
102
109
  if (opts.visibility) body.visibility = opts.visibility;
103
110
  if (opts.forkAllowed !== undefined) body.fork_allowed = opts.forkAllowed;
104
111
 
105
- const res = await fetch(`${API}/admin/v1/projects/${projectId}/publish`, {
112
+ const res = await fetch(`${API}/projects/v1/admin/${projectId}/publish`, {
106
113
  method: "POST",
107
114
  headers: { "Authorization": `Bearer ${p.service_key}`, "Content-Type": "application/json" },
108
115
  body: JSON.stringify(body),
@@ -114,7 +121,7 @@ async function publish(projectId, args) {
114
121
 
115
122
  async function versions(projectId) {
116
123
  const p = findProject(projectId);
117
- const res = await fetch(`${API}/admin/v1/projects/${projectId}/versions`, {
124
+ const res = await fetch(`${API}/projects/v1/admin/${projectId}/versions`, {
118
125
  headers: { "Authorization": `Bearer ${p.service_key}` },
119
126
  });
120
127
  const data = await res.json();
@@ -122,6 +129,49 @@ async function versions(projectId) {
122
129
  console.log(JSON.stringify(data, null, 2));
123
130
  }
124
131
 
132
+ async function inspect(versionId) {
133
+ if (!versionId) { console.error(JSON.stringify({ status: "error", message: "Missing version ID" })); process.exit(1); }
134
+ const res = await fetch(`${API}/apps/v1/${versionId}`);
135
+ const data = await res.json();
136
+ if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
137
+ console.log(JSON.stringify(data, null, 2));
138
+ }
139
+
140
+ async function update(projectId, versionId, args) {
141
+ const p = findProject(projectId);
142
+ const body = {};
143
+ for (let i = 0; i < args.length; i++) {
144
+ if (args[i] === "--description" && args[i + 1]) body.description = args[++i];
145
+ if (args[i] === "--tags" && args[i + 1]) body.tags = args[++i].split(",");
146
+ if (args[i] === "--visibility" && args[i + 1]) body.visibility = args[++i];
147
+ if (args[i] === "--fork-allowed") body.fork_allowed = true;
148
+ if (args[i] === "--no-fork") body.fork_allowed = false;
149
+ }
150
+ const res = await fetch(`${API}/projects/v1/admin/${projectId}/versions/${versionId}`, {
151
+ method: "PATCH",
152
+ headers: { "Authorization": `Bearer ${p.service_key}`, "Content-Type": "application/json" },
153
+ body: JSON.stringify(body),
154
+ });
155
+ const data = await res.json();
156
+ if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
157
+ console.log(JSON.stringify(data, null, 2));
158
+ }
159
+
160
+ async function deleteVersion(projectId, versionId) {
161
+ const p = findProject(projectId);
162
+ const res = await fetch(`${API}/projects/v1/admin/${projectId}/versions/${versionId}`, {
163
+ method: "DELETE",
164
+ headers: { "Authorization": `Bearer ${p.service_key}` },
165
+ });
166
+ if (res.status === 204 || res.ok) {
167
+ console.log(JSON.stringify({ status: "ok", message: `Version ${versionId} deleted.` }));
168
+ } else {
169
+ const data = await res.json();
170
+ console.error(JSON.stringify({ status: "error", http: res.status, ...data }));
171
+ process.exit(1);
172
+ }
173
+ }
174
+
125
175
  export async function run(sub, args) {
126
176
  if (!sub || sub === '--help' || sub === '-h') { console.log(HELP); process.exit(0); }
127
177
  switch (sub) {
@@ -129,6 +179,9 @@ export async function run(sub, args) {
129
179
  case "fork": await fork(args[0], args[1], args.slice(2)); break;
130
180
  case "publish": await publish(args[0], args.slice(1)); break;
131
181
  case "versions": await versions(args[0]); break;
182
+ case "inspect": await inspect(args[0]); break;
183
+ case "update": await update(args[0], args[1], args.slice(2)); break;
184
+ case "delete": await deleteVersion(args[0], args[1]); break;
132
185
  default:
133
186
  console.error(`Unknown subcommand: ${sub}\n`);
134
187
  console.log(HELP);
package/lib/config.mjs CHANGED
@@ -3,9 +3,10 @@
3
3
  * Kept in a separate module so credential reads stay isolated.
4
4
  */
5
5
 
6
- import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync } from "fs";
7
- import { join } from "path";
6
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync, renameSync } from "fs";
7
+ import { join, dirname } from "path";
8
8
  import { homedir } from "os";
9
+ import { randomBytes } from "crypto";
9
10
 
10
11
  export const CONFIG_DIR = join(homedir(), ".config", "run402");
11
12
  export const WALLET_FILE = join(CONFIG_DIR, "wallet.json");
@@ -19,8 +20,10 @@ export function readWallet() {
19
20
 
20
21
  export function saveWallet(data) {
21
22
  mkdirSync(CONFIG_DIR, { recursive: true });
22
- writeFileSync(WALLET_FILE, JSON.stringify(data, null, 2), { mode: 0o600 });
23
- try { chmodSync(WALLET_FILE, 0o600); } catch {}
23
+ const tmp = join(CONFIG_DIR, `.wallet.${randomBytes(4).toString("hex")}.tmp`);
24
+ writeFileSync(tmp, JSON.stringify(data, null, 2), { mode: 0o600 });
25
+ renameSync(tmp, WALLET_FILE);
26
+ chmodSync(WALLET_FILE, 0o600);
24
27
  }
25
28
 
26
29
  export function loadProjects() {
package/lib/deploy.mjs CHANGED
@@ -84,7 +84,7 @@ export async function run(args) {
84
84
  client.register("eip155:84532", new ExactEvmScheme(signer));
85
85
  const fetchPaid = wrapFetchWithPayment(fetch, client);
86
86
 
87
- const res = await fetchPaid(`${API}/v1/deploy/${opts.tier}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(manifest) });
87
+ const res = await fetchPaid(`${API}/deploy/v1/${opts.tier}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(manifest) });
88
88
  const result = await res.json();
89
89
  if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...result })); process.exit(1); }
90
90
  saveProject(result);
package/lib/functions.mjs CHANGED
@@ -65,7 +65,7 @@ async function deploy(projectId, name, args) {
65
65
  if (opts.deps) body.deps = opts.deps;
66
66
 
67
67
  const fetchPaid = await setupPaidFetch();
68
- const res = await fetchPaid(`${API}/admin/v1/projects/${projectId}/functions`, {
68
+ const res = await fetchPaid(`${API}/projects/v1/admin/${projectId}/functions`, {
69
69
  method: "POST",
70
70
  headers: { "Authorization": `Bearer ${p.service_key}`, "Content-Type": "application/json" },
71
71
  body: JSON.stringify(body),
@@ -102,7 +102,7 @@ async function logs(projectId, name, args) {
102
102
  for (let i = 0; i < args.length; i++) {
103
103
  if (args[i] === "--tail" && args[i + 1]) tail = parseInt(args[++i]);
104
104
  }
105
- const res = await fetch(`${API}/admin/v1/projects/${projectId}/functions/${encodeURIComponent(name)}/logs?tail=${tail}`, {
105
+ const res = await fetch(`${API}/projects/v1/admin/${projectId}/functions/${encodeURIComponent(name)}/logs?tail=${tail}`, {
106
106
  headers: { "Authorization": `Bearer ${p.service_key}` },
107
107
  });
108
108
  const data = await res.json();
@@ -112,7 +112,7 @@ async function logs(projectId, name, args) {
112
112
 
113
113
  async function list(projectId) {
114
114
  const p = findProject(projectId);
115
- const res = await fetch(`${API}/admin/v1/projects/${projectId}/functions`, {
115
+ const res = await fetch(`${API}/projects/v1/admin/${projectId}/functions`, {
116
116
  headers: { "Authorization": `Bearer ${p.service_key}` },
117
117
  });
118
118
  const data = await res.json();
@@ -122,7 +122,7 @@ async function list(projectId) {
122
122
 
123
123
  async function deleteFunction(projectId, name) {
124
124
  const p = findProject(projectId);
125
- const res = await fetch(`${API}/admin/v1/projects/${projectId}/functions/${encodeURIComponent(name)}`, {
125
+ const res = await fetch(`${API}/projects/v1/admin/${projectId}/functions/${encodeURIComponent(name)}`, {
126
126
  method: "DELETE",
127
127
  headers: { "Authorization": `Bearer ${p.service_key}` },
128
128
  });
package/lib/image.mjs CHANGED
@@ -66,7 +66,7 @@ export async function run(sub, args) {
66
66
  client.register("eip155:84532", new ExactEvmScheme(signer));
67
67
  const fetchPaid = wrapFetchWithPayment(fetch, client);
68
68
 
69
- const res = await fetchPaid(`${API}/v1/generate-image`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ prompt: opts.prompt, aspect: opts.aspect }) });
69
+ const res = await fetchPaid(`${API}/generate-image/v1`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ prompt: opts.prompt, aspect: opts.aspect }) });
70
70
  const data = await res.json();
71
71
  if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
72
72
 
@@ -0,0 +1,56 @@
1
+ import { readWallet, API, WALLET_FILE } from "./config.mjs";
2
+ import { existsSync } from "fs";
3
+
4
+ const HELP = `run402 message — Send messages to Run402 developers
5
+
6
+ Usage:
7
+ run402 message send <text>
8
+
9
+ Notes:
10
+ - Costs $0.01 USDC via x402
11
+ - Requires a funded wallet
12
+
13
+ Examples:
14
+ run402 message send "Hello from my agent!"
15
+ `;
16
+
17
+ async function send(text) {
18
+ if (!text) { console.error(JSON.stringify({ status: "error", message: "Missing message text" })); process.exit(1); }
19
+ if (!existsSync(WALLET_FILE)) {
20
+ console.error(JSON.stringify({ status: "error", message: "No wallet found. Run: run402 wallet create && run402 wallet fund" }));
21
+ process.exit(1);
22
+ }
23
+
24
+ const wallet = readWallet();
25
+ const { privateKeyToAccount } = await import("viem/accounts");
26
+ const { createPublicClient, http } = await import("viem");
27
+ const { baseSepolia } = await import("viem/chains");
28
+ const { x402Client, wrapFetchWithPayment } = await import("@x402/fetch");
29
+ const { ExactEvmScheme } = await import("@x402/evm/exact/client");
30
+ const { toClientEvmSigner } = await import("@x402/evm");
31
+ const account = privateKeyToAccount(wallet.privateKey);
32
+ const publicClient = createPublicClient({ chain: baseSepolia, transport: http() });
33
+ const signer = toClientEvmSigner(account, publicClient);
34
+ const client = new x402Client();
35
+ client.register("eip155:84532", new ExactEvmScheme(signer));
36
+ const fetchPaid = wrapFetchWithPayment(fetch, client);
37
+
38
+ const res = await fetchPaid(`${API}/message/v1`, {
39
+ method: "POST",
40
+ headers: { "Content-Type": "application/json" },
41
+ body: JSON.stringify({ message: text }),
42
+ });
43
+ const data = await res.json();
44
+ if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
45
+ console.log(JSON.stringify(data, null, 2));
46
+ }
47
+
48
+ export async function run(sub, args) {
49
+ if (!sub || sub === '--help' || sub === '-h') { console.log(HELP); process.exit(0); }
50
+ if (sub !== "send") {
51
+ console.error(`Unknown subcommand: ${sub}\n`);
52
+ console.log(HELP);
53
+ process.exit(1);
54
+ }
55
+ await send(args.join(" "));
56
+ }
package/lib/projects.mjs CHANGED
@@ -59,7 +59,7 @@ async function setupPaidFetch() {
59
59
  }
60
60
 
61
61
  async function quote() {
62
- const res = await fetch(`${API}/v1/projects`);
62
+ const res = await fetch(`${API}/projects/v1`);
63
63
  const data = await res.json();
64
64
  if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
65
65
  console.log(JSON.stringify(data, null, 2));
@@ -74,7 +74,7 @@ async function provision(args) {
74
74
  const fetchPaid = await setupPaidFetch();
75
75
  const body = { tier: opts.tier };
76
76
  if (opts.name) body.name = opts.name;
77
- const res = await fetchPaid(`${API}/v1/projects`, {
77
+ const res = await fetchPaid(`${API}/projects/v1`, {
78
78
  method: "POST",
79
79
  headers: { "Content-Type": "application/json" },
80
80
  body: JSON.stringify(body),
@@ -98,7 +98,7 @@ async function provision(args) {
98
98
  async function rls(projectId, template, tablesJson) {
99
99
  const p = findProject(projectId);
100
100
  const tables = JSON.parse(tablesJson);
101
- const res = await fetch(`${API}/admin/v1/projects/${projectId}/rls`, {
101
+ const res = await fetch(`${API}/projects/v1/admin/${projectId}/rls`, {
102
102
  method: "POST",
103
103
  headers: { "Authorization": `Bearer ${p.service_key}`, "Content-Type": "application/json" },
104
104
  body: JSON.stringify({ template, tables }),
@@ -116,7 +116,7 @@ async function list() {
116
116
 
117
117
  async function sqlCmd(projectId, query) {
118
118
  const p = findProject(projectId);
119
- const res = await fetch(`${API}/admin/v1/projects/${projectId}/sql`, { method: "POST", headers: { "Authorization": `Bearer ${p.service_key}`, "Content-Type": "text/plain" }, body: query });
119
+ const res = await fetch(`${API}/projects/v1/admin/${projectId}/sql`, { method: "POST", headers: { "Authorization": `Bearer ${p.service_key}`, "Content-Type": "text/plain" }, body: query });
120
120
  console.log(JSON.stringify(await res.json(), null, 2));
121
121
  }
122
122
 
@@ -128,7 +128,7 @@ async function rest(projectId, table, queryParams) {
128
128
 
129
129
  async function usage(projectId) {
130
130
  const p = findProject(projectId);
131
- const res = await fetch(`${API}/admin/v1/projects/${projectId}/usage`, { headers: { "Authorization": `Bearer ${p.service_key}` } });
131
+ const res = await fetch(`${API}/projects/v1/admin/${projectId}/usage`, { headers: { "Authorization": `Bearer ${p.service_key}` } });
132
132
  const data = await res.json();
133
133
  if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
134
134
  console.log(JSON.stringify(data, null, 2));
@@ -136,7 +136,7 @@ async function usage(projectId) {
136
136
 
137
137
  async function schema(projectId) {
138
138
  const p = findProject(projectId);
139
- const res = await fetch(`${API}/admin/v1/projects/${projectId}/schema`, { headers: { "Authorization": `Bearer ${p.service_key}` } });
139
+ const res = await fetch(`${API}/projects/v1/admin/${projectId}/schema`, { headers: { "Authorization": `Bearer ${p.service_key}` } });
140
140
  const data = await res.json();
141
141
  if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
142
142
  console.log(JSON.stringify(data, null, 2));
@@ -144,7 +144,7 @@ async function schema(projectId) {
144
144
 
145
145
  async function renew(projectId) {
146
146
  const fetchPaid = await setupPaidFetch();
147
- const res = await fetchPaid(`${API}/v1/projects/${projectId}/renew`, { method: "POST", headers: { "Content-Type": "application/json" } });
147
+ const res = await fetchPaid(`${API}/projects/v1/${projectId}/renew`, { method: "POST", headers: { "Content-Type": "application/json" } });
148
148
  const data = await res.json();
149
149
  if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
150
150
  const projects = loadProjects();
@@ -155,7 +155,7 @@ async function renew(projectId) {
155
155
 
156
156
  async function deleteProject(projectId) {
157
157
  const p = findProject(projectId);
158
- const res = await fetch(`${API}/v1/projects/${projectId}`, { method: "DELETE", headers: { "Authorization": `Bearer ${p.service_key}` } });
158
+ const res = await fetch(`${API}/projects/v1/${projectId}`, { method: "DELETE", headers: { "Authorization": `Bearer ${p.service_key}` } });
159
159
  if (res.status === 204 || res.ok) {
160
160
  saveProjects(loadProjects().filter(pr => pr.project_id !== projectId));
161
161
  console.log(JSON.stringify({ status: "ok", message: `Project ${projectId} deleted.` }));
package/lib/secrets.mjs CHANGED
@@ -22,7 +22,7 @@ Notes:
22
22
 
23
23
  async function set(projectId, key, value) {
24
24
  const p = findProject(projectId);
25
- const res = await fetch(`${API}/admin/v1/projects/${projectId}/secrets`, {
25
+ const res = await fetch(`${API}/projects/v1/admin/${projectId}/secrets`, {
26
26
  method: "POST",
27
27
  headers: { "Authorization": `Bearer ${p.service_key}`, "Content-Type": "application/json" },
28
28
  body: JSON.stringify({ key, value }),
@@ -34,7 +34,7 @@ async function set(projectId, key, value) {
34
34
 
35
35
  async function list(projectId) {
36
36
  const p = findProject(projectId);
37
- const res = await fetch(`${API}/admin/v1/projects/${projectId}/secrets`, {
37
+ const res = await fetch(`${API}/projects/v1/admin/${projectId}/secrets`, {
38
38
  headers: { "Authorization": `Bearer ${p.service_key}` },
39
39
  });
40
40
  const data = await res.json();
@@ -44,7 +44,7 @@ async function list(projectId) {
44
44
 
45
45
  async function deleteSecret(projectId, key) {
46
46
  const p = findProject(projectId);
47
- const res = await fetch(`${API}/admin/v1/projects/${projectId}/secrets/${encodeURIComponent(key)}`, {
47
+ const res = await fetch(`${API}/projects/v1/admin/${projectId}/secrets/${encodeURIComponent(key)}`, {
48
48
  method: "DELETE",
49
49
  headers: { "Authorization": `Bearer ${p.service_key}` },
50
50
  });
package/lib/sites.mjs CHANGED
@@ -1,13 +1,18 @@
1
1
  import { readFileSync, existsSync } from "fs";
2
2
  import { readWallet, API, WALLET_FILE } from "./config.mjs";
3
3
 
4
- const HELP = `run402 sites — Deploy static sites
4
+ const HELP = `run402 sites — Deploy and manage static sites
5
5
 
6
6
  Usage:
7
7
  run402 sites deploy --name <name> --manifest <file> [--project <id>] [--target <target>]
8
+ run402 sites status <deployment_id>
8
9
  cat manifest.json | run402 sites deploy --name <name>
9
10
 
10
- Options:
11
+ Subcommands:
12
+ deploy Deploy a static site
13
+ status Check the status of a deployment
14
+
15
+ Options (deploy):
11
16
  --name <name> Site name (e.g. 'portfolio', 'family-todo')
12
17
  --manifest <file> Path to manifest JSON file (or read from stdin)
13
18
  --project <id> Optional project ID to link this deployment to
@@ -24,6 +29,7 @@ Manifest format (JSON):
24
29
 
25
30
  Examples:
26
31
  run402 sites deploy --name my-site --manifest site.json
32
+ run402 sites status dep_abc123
27
33
  cat site.json | run402 sites deploy --name my-site
28
34
 
29
35
  Notes:
@@ -71,7 +77,7 @@ async function deploy(args) {
71
77
  client.register("eip155:84532", new ExactEvmScheme(signer));
72
78
  const fetchPaid = wrapFetchWithPayment(fetch, client);
73
79
 
74
- const res = await fetchPaid(`${API}/v1/deployments`, {
80
+ const res = await fetchPaid(`${API}/deployments/v1`, {
75
81
  method: "POST",
76
82
  headers: { "Content-Type": "application/json" },
77
83
  body: JSON.stringify(body),
@@ -81,12 +87,26 @@ async function deploy(args) {
81
87
  console.log(JSON.stringify(data, null, 2));
82
88
  }
83
89
 
90
+ async function status(args) {
91
+ let deploymentId = null;
92
+ for (let i = 0; i < args.length; i++) {
93
+ if (!args[i].startsWith("-")) { deploymentId = args[i]; break; }
94
+ }
95
+ if (!deploymentId) { console.error(JSON.stringify({ status: "error", message: "Missing deployment ID" })); process.exit(1); }
96
+ const res = await fetch(`${API}/deployments/v1/${deploymentId}`);
97
+ const data = await res.json();
98
+ if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
99
+ console.log(JSON.stringify(data, null, 2));
100
+ }
101
+
84
102
  export async function run(sub, args) {
85
103
  if (!sub || sub === '--help' || sub === '-h') { console.log(HELP); process.exit(0); }
86
- if (sub !== "deploy") {
87
- console.error(`Unknown subcommand: ${sub}\n`);
88
- console.log(HELP);
89
- process.exit(1);
104
+ switch (sub) {
105
+ case "deploy": await deploy(args); break;
106
+ case "status": await status(args); break;
107
+ default:
108
+ console.error(`Unknown subcommand: ${sub}\n`);
109
+ console.log(HELP);
110
+ process.exit(1);
90
111
  }
91
- await deploy(args);
92
112
  }
@@ -31,7 +31,7 @@ async function claim(deploymentId, name, args) {
31
31
  const p = findProject(opts.project);
32
32
  headers["Authorization"] = `Bearer ${p.service_key}`;
33
33
  }
34
- const res = await fetch(`${API}/v1/subdomains`, {
34
+ const res = await fetch(`${API}/subdomains/v1`, {
35
35
  method: "POST",
36
36
  headers,
37
37
  body: JSON.stringify({ name, deployment_id: deploymentId }),
@@ -51,7 +51,7 @@ async function deleteSubdomain(name, args) {
51
51
  const p = findProject(opts.project);
52
52
  headers["Authorization"] = `Bearer ${p.service_key}`;
53
53
  }
54
- const res = await fetch(`${API}/v1/subdomains/${encodeURIComponent(name)}`, {
54
+ const res = await fetch(`${API}/subdomains/v1/${encodeURIComponent(name)}`, {
55
55
  method: "DELETE",
56
56
  headers,
57
57
  });
@@ -65,7 +65,7 @@ async function deleteSubdomain(name, args) {
65
65
 
66
66
  async function list(projectId) {
67
67
  const p = findProject(projectId);
68
- const res = await fetch(`${API}/v1/subdomains`, {
68
+ const res = await fetch(`${API}/subdomains/v1`, {
69
69
  headers: { "Authorization": `Bearer ${p.service_key}` },
70
70
  });
71
71
  const data = await res.json();
package/lib/wallet.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { readWallet, saveWallet, API } from "./config.mjs";
1
+ import { readWallet, saveWallet, WALLET_FILE, API } from "./config.mjs";
2
2
 
3
3
  const HELP = `run402 wallet — Manage your x402 wallet
4
4
 
@@ -9,26 +9,34 @@ Subcommands:
9
9
  status Show wallet address, network, and funding status
10
10
  create Generate a new wallet and save it locally
11
11
  fund Request test USDC from the Run402 faucet (Base Sepolia)
12
- balance Check billing balance for this wallet
12
+ balance Show on-chain USDC (mainnet + testnet) and Run402 billing balance
13
13
  export Print the wallet address (useful for scripting)
14
+ checkout Create a billing checkout session (--amount <usd_micros>)
15
+ history View billing transaction history (--limit <n>)
14
16
 
15
17
  Notes:
16
18
  - Wallet is stored locally at ~/.run402/wallet.json
17
- - Network: Base Sepolia (testnet) uses USDC for x402 payments
18
- - You need to create and fund a wallet before deploying or generating images
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
19
21
 
20
22
  Examples:
21
23
  run402 wallet create
22
24
  run402 wallet status
23
25
  run402 wallet fund
24
26
  run402 wallet export
27
+ run402 wallet checkout --amount 5000000
28
+ run402 wallet history --limit 10
25
29
  `;
26
30
 
31
+ const USDC_ABI = [{ name: "balanceOf", type: "function", stateMutability: "view", inputs: [{ name: "account", type: "address" }], outputs: [{ name: "", type: "uint256" }] }];
32
+ const USDC_MAINNET = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
33
+ const USDC_SEPOLIA = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
34
+
27
35
  async function loadDeps() {
28
36
  const { generatePrivateKey, privateKeyToAccount } = await import("viem/accounts");
29
37
  const { createPublicClient, http } = await import("viem");
30
- const { baseSepolia } = await import("viem/chains");
31
- return { generatePrivateKey, privateKeyToAccount, createPublicClient, http, baseSepolia };
38
+ const { base, baseSepolia } = await import("viem/chains");
39
+ return { generatePrivateKey, privateKeyToAccount, createPublicClient, http, base, baseSepolia };
32
40
  }
33
41
 
34
42
  async function status() {
@@ -37,7 +45,7 @@ async function status() {
37
45
  console.log(JSON.stringify({ status: "no_wallet", message: "No wallet found. Run: run402 wallet create" }));
38
46
  return;
39
47
  }
40
- console.log(JSON.stringify({ status: "ok", address: w.address, network: w.network || "base-sepolia", created: w.created, funded: w.funded || false }));
48
+ console.log(JSON.stringify({ status: "ok", address: w.address, created: w.created, funded: w.funded || false, path: WALLET_FILE }));
41
49
  }
42
50
 
43
51
  async function create() {
@@ -48,31 +56,74 @@ async function create() {
48
56
  const { generatePrivateKey, privateKeyToAccount } = await loadDeps();
49
57
  const privateKey = generatePrivateKey();
50
58
  const account = privateKeyToAccount(privateKey);
51
- saveWallet({ address: account.address, privateKey, network: "base-sepolia", created: new Date().toISOString(), funded: false });
52
- console.log(JSON.stringify({ status: "ok", address: account.address, message: "Wallet created and saved." }));
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}` }));
53
61
  }
54
62
 
55
63
  async function fund() {
56
64
  const w = readWallet();
57
65
  if (!w) { console.log(JSON.stringify({ status: "error", message: "No wallet. Run: run402 wallet create" })); process.exit(1); }
58
- const res = await fetch(`${API}/v1/faucet`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ address: w.address }) });
66
+
67
+ const { createPublicClient, http, baseSepolia } = await loadDeps();
68
+ const client = createPublicClient({ chain: baseSepolia, transport: http() });
69
+ const before = await readUsdcBalance(client, USDC_SEPOLIA, w.address).catch(() => 0);
70
+
71
+ const res = await fetch(`${API}/faucet/v1`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ address: w.address }) });
59
72
  const data = await res.json();
60
- if (res.ok) {
61
- saveWallet({ ...w, funded: true, lastFaucet: new Date().toISOString() });
62
- console.log(JSON.stringify({ status: "ok", ...data }));
63
- } else {
73
+ if (!res.ok) {
64
74
  console.log(JSON.stringify({ status: "error", ...data }));
65
75
  process.exit(1);
66
76
  }
77
+
78
+ const MAX_WAIT = 30;
79
+ for (let i = 0; i < MAX_WAIT; i++) {
80
+ await new Promise(r => setTimeout(r, 1000));
81
+ const now = await readUsdcBalance(client, USDC_SEPOLIA, w.address).catch(() => before);
82
+ if (now > before) {
83
+ saveWallet({ ...w, funded: true, lastFaucet: new Date().toISOString() });
84
+ console.log(JSON.stringify({
85
+ address: w.address,
86
+ onchain: {
87
+ "base-sepolia_usd_micros": now,
88
+ },
89
+ }, null, 2));
90
+ return;
91
+ }
92
+ }
93
+
94
+ saveWallet({ ...w, funded: true, lastFaucet: new Date().toISOString() });
95
+ console.log(JSON.stringify({ status: "ok", message: "Faucet request sent but balance not yet confirmed", ...data }));
96
+ }
97
+
98
+ async function readUsdcBalance(client, usdc, address) {
99
+ const raw = await client.readContract({ address: usdc, abi: USDC_ABI, functionName: "balanceOf", args: [address] });
100
+ return Number(raw);
67
101
  }
68
102
 
69
103
  async function balance() {
70
104
  const w = readWallet();
71
105
  if (!w) { console.log(JSON.stringify({ status: "error", message: "No wallet. Run: run402 wallet create" })); process.exit(1); }
72
- const res = await fetch(`${API}/v1/billing/accounts/${w.address.toLowerCase()}`);
73
- const data = await res.json();
74
- if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
75
- console.log(JSON.stringify(data, null, 2));
106
+
107
+ const { createPublicClient, http, base, baseSepolia } = await loadDeps();
108
+ const mainnetClient = createPublicClient({ chain: base, transport: http() });
109
+ const sepoliaClient = createPublicClient({ chain: baseSepolia, transport: http() });
110
+
111
+ const [mainnetUsdc, sepoliaUsdc, billingRes] = await Promise.all([
112
+ readUsdcBalance(mainnetClient, USDC_MAINNET, w.address).catch(() => null),
113
+ readUsdcBalance(sepoliaClient, USDC_SEPOLIA, w.address).catch(() => null),
114
+ fetch(`${API}/billing/v1/accounts/${w.address.toLowerCase()}`),
115
+ ]);
116
+
117
+ const billing = billingRes.ok ? await billingRes.json() : null;
118
+
119
+ console.log(JSON.stringify({
120
+ address: w.address,
121
+ onchain: {
122
+ "base-mainnet_usd_micros": mainnetUsdc,
123
+ "base-sepolia_usd_micros": sepoliaUsdc,
124
+ },
125
+ run402: billing ? { balance_usd_micros: billing.available_usd_micros } : "no billing account",
126
+ }, null, 2));
76
127
  }
77
128
 
78
129
  async function exportAddr() {
@@ -81,6 +132,37 @@ async function exportAddr() {
81
132
  console.log(w.address);
82
133
  }
83
134
 
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); }
138
+ let amount = null;
139
+ for (let i = 0; i < args.length; i++) {
140
+ if (args[i] === "--amount" && args[i + 1]) amount = parseInt(args[++i], 10);
141
+ }
142
+ if (!amount) { console.error(JSON.stringify({ status: "error", message: "Missing --amount <usd_micros> (e.g. --amount 5000000 for $5)" })); process.exit(1); }
143
+ const res = await fetch(`${API}/billing/v1/checkouts`, {
144
+ method: "POST",
145
+ headers: { "Content-Type": "application/json" },
146
+ body: JSON.stringify({ wallet: w.address.toLowerCase(), amount_usd_micros: amount }),
147
+ });
148
+ const data = await res.json();
149
+ if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
150
+ console.log(JSON.stringify(data, null, 2));
151
+ }
152
+
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); }
156
+ let limit = 20;
157
+ for (let i = 0; i < args.length; i++) {
158
+ if (args[i] === "--limit" && args[i + 1]) limit = parseInt(args[++i], 10);
159
+ }
160
+ const res = await fetch(`${API}/billing/v1/accounts/${w.address.toLowerCase()}/history?limit=${limit}`);
161
+ const data = await res.json();
162
+ if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
163
+ console.log(JSON.stringify(data, null, 2));
164
+ }
165
+
84
166
  export async function run(sub, args) {
85
167
  if (!sub || sub === '--help' || sub === '-h') {
86
168
  console.log(HELP);
@@ -91,7 +173,9 @@ export async function run(sub, args) {
91
173
  case "create": await create(); break;
92
174
  case "fund": await fund(); break;
93
175
  case "balance": await balance(); break;
94
- case "export": await exportAddr(); break;
176
+ case "export": await exportAddr(); break;
177
+ case "checkout": await checkout(args); break;
178
+ case "history": await history(args); break;
95
179
  default:
96
180
  console.error(`Unknown subcommand: ${sub}\n`);
97
181
  console.log(HELP);
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "run402",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
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
- "run402": "./cli.mjs"
7
+ "run402": "cli.mjs"
8
8
  },
9
9
  "files": [
10
10
  "cli.mjs",
@@ -22,8 +22,14 @@
22
22
  "homepage": "https://run402.com",
23
23
  "repository": {
24
24
  "type": "git",
25
- "url": "https://github.com/kychee-com/run402.git",
25
+ "url": "git+https://github.com/kychee-com/run402.git",
26
26
  "directory": "cli"
27
27
  },
28
- "keywords": ["run402", "x402", "postgres", "ai-agent", "openclaw"]
28
+ "keywords": [
29
+ "run402",
30
+ "x402",
31
+ "postgres",
32
+ "ai-agent",
33
+ "openclaw"
34
+ ]
29
35
  }