run402 1.8.2 → 1.9.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/lib/apps.mjs CHANGED
@@ -1,5 +1,4 @@
1
- import { findProject, loadProjects, saveProjects, API, PROJECTS_FILE, walletAuthHeaders } from "./config.mjs";
2
- import { mkdirSync, writeFileSync } from "fs";
1
+ import { findProject, API, walletAuthHeaders, saveProject } from "./config.mjs";
3
2
 
4
3
  const HELP = `run402 apps — Browse and manage the app marketplace
5
4
 
@@ -63,15 +62,12 @@ async function fork(versionId, name, args) {
63
62
 
64
63
  // Save project credentials locally
65
64
  if (data.project_id) {
66
- const projects = loadProjects();
67
- projects.push({
68
- project_id: data.project_id, anon_key: data.anon_key, service_key: data.service_key,
65
+ saveProject(data.project_id, {
66
+ anon_key: data.anon_key, service_key: data.service_key,
69
67
  tier: data.tier, lease_expires_at: data.lease_expires_at,
70
- site_url: data.site_url || data.subdomain_url, deployed_at: new Date().toISOString(),
68
+ site_url: data.site_url || data.subdomain_url,
69
+ deployed_at: new Date().toISOString(),
71
70
  });
72
- const dir = PROJECTS_FILE.replace(/\/[^/]+$/, "");
73
- mkdirSync(dir, { recursive: true });
74
- writeFileSync(PROJECTS_FILE, JSON.stringify(projects, null, 2), { mode: 0o600 });
75
71
  }
76
72
  console.log(JSON.stringify(data, null, 2));
77
73
  }
package/lib/config.mjs CHANGED
@@ -1,53 +1,37 @@
1
1
  /**
2
- * Run402 config loader — reads local project and wallet state.
3
- * Kept in a separate module so credential reads stay isolated.
2
+ * Run402 config loader — thin wrapper over core/ shared modules.
3
+ * Adds CLI-specific behavior: process.exit() on errors.
4
4
  */
5
5
 
6
- import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync, renameSync } from "fs";
7
- import { join, dirname } from "path";
8
- import { homedir } from "os";
9
- import { randomBytes } from "crypto";
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";
10
10
 
11
- export const CONFIG_DIR = join(homedir(), ".config", "run402");
12
- export const WALLET_FILE = join(CONFIG_DIR, "wallet.json");
13
- export const PROJECTS_FILE = join(CONFIG_DIR, "projects.json");
14
- export const API = "https://api.run402.com";
11
+ export const CONFIG_DIR = getConfigDir();
12
+ export const WALLET_FILE = getWalletPath();
13
+ export const PROJECTS_FILE = getKeystorePath();
14
+ export const API = getApiBase();
15
15
 
16
16
  export function readWallet() {
17
- if (!existsSync(WALLET_FILE)) return null;
18
- return JSON.parse(readFileSync(WALLET_FILE, "utf-8"));
17
+ return coreReadWallet();
19
18
  }
20
19
 
21
20
  export function saveWallet(data) {
22
- mkdirSync(CONFIG_DIR, { recursive: true });
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);
27
- }
28
-
29
- export function loadProjects() {
30
- if (!existsSync(PROJECTS_FILE)) return [];
31
- return JSON.parse(readFileSync(PROJECTS_FILE, "utf-8"));
32
- }
33
-
34
- export function saveProjects(projects) {
35
- mkdirSync(CONFIG_DIR, { recursive: true });
36
- writeFileSync(PROJECTS_FILE, JSON.stringify(projects, null, 2), { mode: 0o600 });
21
+ coreSaveWallet(data);
37
22
  }
38
23
 
39
24
  export async function walletAuthHeaders() {
40
- const w = readWallet();
41
- if (!w) { console.error(JSON.stringify({ status: "error", message: "No wallet found. Run: run402 wallet create" })); process.exit(1); }
42
- const { privateKeyToAccount } = await import("viem/accounts");
43
- const account = privateKeyToAccount(w.privateKey);
44
- const timestamp = Math.floor(Date.now() / 1000).toString();
45
- const signature = await account.signMessage({ message: `run402:${timestamp}` });
46
- return { "X-Run402-Wallet": account.address, "X-Run402-Signature": signature, "X-Run402-Timestamp": timestamp };
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;
47
28
  }
48
29
 
49
30
  export function findProject(id) {
50
- const p = loadProjects().find(p => p.project_id === id);
31
+ const p = getProject(id);
51
32
  if (!p) { console.error(`Project ${id} not found in local registry.`); process.exit(1); }
52
33
  return p;
53
34
  }
35
+
36
+ // Re-export core keystore functions for direct use
37
+ export { loadKeyStore, saveProject, removeProject, saveKeyStore };
package/lib/deploy.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { readFileSync, mkdirSync, writeFileSync } from "fs";
2
- import { loadProjects, API, PROJECTS_FILE, walletAuthHeaders } from "./config.mjs";
1
+ import { readFileSync } from "fs";
2
+ import { API, walletAuthHeaders, saveProject } from "./config.mjs";
3
3
 
4
4
  const HELP = `run402 deploy — Deploy a full-stack app or static site on Run402
5
5
 
@@ -39,14 +39,6 @@ async function readStdin() {
39
39
  return Buffer.concat(chunks).toString("utf-8");
40
40
  }
41
41
 
42
- function saveProject(project) {
43
- const projects = loadProjects();
44
- projects.push({ project_id: project.project_id, anon_key: project.anon_key, service_key: project.service_key, tier: project.tier, lease_expires_at: project.lease_expires_at, site_url: project.site_url || project.subdomain_url, deployed_at: new Date().toISOString() });
45
- const dir = PROJECTS_FILE.replace(/\/[^/]+$/, "");
46
- mkdirSync(dir, { recursive: true });
47
- writeFileSync(PROJECTS_FILE, JSON.stringify(projects, null, 2), { mode: 0o600 });
48
- }
49
-
50
42
  export async function run(args) {
51
43
  const opts = { manifest: null };
52
44
  for (let i = 0; i < args.length; i++) {
@@ -60,6 +52,13 @@ export async function run(args) {
60
52
  const res = await fetch(`${API}/deploy/v1`, { method: "POST", headers: { "Content-Type": "application/json", ...authHeaders }, body: JSON.stringify(manifest) });
61
53
  const result = await res.json();
62
54
  if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...result })); process.exit(1); }
63
- saveProject(result);
55
+ if (result.project_id) {
56
+ saveProject(result.project_id, {
57
+ anon_key: result.anon_key, service_key: result.service_key,
58
+ tier: result.tier, lease_expires_at: result.lease_expires_at,
59
+ site_url: result.site_url || result.subdomain_url,
60
+ deployed_at: new Date().toISOString(),
61
+ });
62
+ }
64
63
  console.log(JSON.stringify(result, null, 2));
65
64
  }
package/lib/image.mjs CHANGED
@@ -1,5 +1,6 @@
1
- import { writeFileSync, existsSync } from "fs";
2
- import { readWallet, API, WALLET_FILE } from "./config.mjs";
1
+ import { writeFileSync } from "fs";
2
+ import { API, WALLET_FILE } from "./config.mjs";
3
+ import { setupPaidFetch } from "./paid-fetch.mjs";
3
4
 
4
5
  const HELP = `run402 image — Generate AI images via x402 micropayments
5
6
 
@@ -49,22 +50,8 @@ export async function run(sub, args) {
49
50
  }
50
51
 
51
52
  if (!opts.prompt) { console.error(JSON.stringify({ status: "error", message: "Prompt required. Usage: run402 image generate \"your prompt\"" })); process.exit(1); }
52
- if (!existsSync(WALLET_FILE)) { console.error(JSON.stringify({ status: "error", message: "No wallet found. Run: run402 wallet create && run402 wallet fund" })); process.exit(1); }
53
53
 
54
- const wallet = readWallet();
55
- const { privateKeyToAccount } = await import("viem/accounts");
56
- const { createPublicClient, http } = await import("viem");
57
- const { baseSepolia } = await import("viem/chains");
58
- const { x402Client, wrapFetchWithPayment } = await import("@x402/fetch");
59
- const { ExactEvmScheme } = await import("@x402/evm/exact/client");
60
- const { toClientEvmSigner } = await import("@x402/evm");
61
-
62
- const account = privateKeyToAccount(wallet.privateKey);
63
- const publicClient = createPublicClient({ chain: baseSepolia, transport: http() });
64
- const signer = toClientEvmSigner(account, publicClient);
65
- const client = new x402Client();
66
- client.register("eip155:84532", new ExactEvmScheme(signer));
67
- const fetchPaid = wrapFetchWithPayment(fetch, client);
54
+ const fetchPaid = await setupPaidFetch();
68
55
 
69
56
  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
57
  const data = await res.json();
package/lib/init.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { readWallet, saveWallet, loadProjects, CONFIG_DIR, WALLET_FILE, API } from "./config.mjs";
1
+ import { readWallet, saveWallet, loadKeyStore, CONFIG_DIR, WALLET_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" }] }];
@@ -91,8 +91,8 @@ export async function run() {
91
91
  }
92
92
 
93
93
  // 5. Projects
94
- const projects = loadProjects();
95
- line("Projects", `${projects.length} active`);
94
+ const store = loadKeyStore();
95
+ line("Projects", `${Object.keys(store.projects).length} active`);
96
96
 
97
97
  // 6. Next step
98
98
  console.log();
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Shared x402 payment wrapper for CLI commands that need paid fetch.
3
+ * Uses viem for wallet signing + @x402/fetch for payment wrapping.
4
+ */
5
+
6
+ import { readWallet, WALLET_FILE } from "./config.mjs";
7
+ import { existsSync } from "fs";
8
+
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" }));
12
+ process.exit(1);
13
+ }
14
+ const wallet = readWallet();
15
+ const { privateKeyToAccount } = await import("viem/accounts");
16
+ const { createPublicClient, http } = await import("viem");
17
+ const { baseSepolia } = await import("viem/chains");
18
+ const { x402Client, wrapFetchWithPayment } = await import("@x402/fetch");
19
+ const { ExactEvmScheme } = await import("@x402/evm/exact/client");
20
+ const { toClientEvmSigner } = await import("@x402/evm");
21
+ const account = privateKeyToAccount(wallet.privateKey);
22
+ const publicClient = createPublicClient({ chain: baseSepolia, transport: http() });
23
+ const signer = toClientEvmSigner(account, publicClient);
24
+ const client = new x402Client();
25
+ client.register("eip155:84532", new ExactEvmScheme(signer));
26
+ return wrapFetchWithPayment(fetch, client);
27
+ }
package/lib/projects.mjs CHANGED
@@ -1,5 +1,4 @@
1
- import { findProject, loadProjects, saveProjects, API, PROJECTS_FILE, walletAuthHeaders } from "./config.mjs";
2
- import { mkdirSync, writeFileSync } from "fs";
1
+ import { findProject, loadKeyStore, saveProject, removeProject, API, walletAuthHeaders } from "./config.mjs";
3
2
 
4
3
  const HELP = `run402 projects — Manage your deployed Run402 projects
5
4
 
@@ -61,14 +60,11 @@ async function provision(args) {
61
60
  if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
62
61
  // Save project credentials locally
63
62
  if (data.project_id) {
64
- const projects = loadProjects();
65
- projects.push({
66
- project_id: data.project_id, anon_key: data.anon_key, service_key: data.service_key,
67
- tier: data.tier, lease_expires_at: data.lease_expires_at, deployed_at: new Date().toISOString(),
63
+ saveProject(data.project_id, {
64
+ anon_key: data.anon_key, service_key: data.service_key,
65
+ tier: data.tier, lease_expires_at: data.lease_expires_at,
66
+ deployed_at: new Date().toISOString(),
68
67
  });
69
- const dir = PROJECTS_FILE.replace(/\/[^/]+$/, "");
70
- mkdirSync(dir, { recursive: true });
71
- writeFileSync(PROJECTS_FILE, JSON.stringify(projects, null, 2), { mode: 0o600 });
72
68
  }
73
69
  console.log(JSON.stringify(data, null, 2));
74
70
  }
@@ -87,9 +83,10 @@ async function rls(projectId, template, tablesJson) {
87
83
  }
88
84
 
89
85
  async function list() {
90
- const projects = loadProjects();
91
- if (projects.length === 0) { console.log(JSON.stringify({ status: "ok", projects: [], message: "No projects yet." })); return; }
92
- console.log(JSON.stringify(projects.map(p => ({ project_id: p.project_id, tier: p.tier, site_url: p.site_url, lease_expires_at: p.lease_expires_at, deployed_at: p.deployed_at })), null, 2));
86
+ const store = loadKeyStore();
87
+ const entries = Object.entries(store.projects);
88
+ if (entries.length === 0) { console.log(JSON.stringify({ status: "ok", projects: [], message: "No projects yet." })); return; }
89
+ console.log(JSON.stringify(entries.map(([id, p]) => ({ project_id: id, tier: p.tier, site_url: p.site_url, lease_expires_at: p.lease_expires_at, deployed_at: p.deployed_at })), null, 2));
93
90
  }
94
91
 
95
92
  async function sqlCmd(projectId, query) {
@@ -124,7 +121,7 @@ async function deleteProject(projectId) {
124
121
  const p = findProject(projectId);
125
122
  const res = await fetch(`${API}/projects/v1/${projectId}`, { method: "DELETE", headers: { "Authorization": `Bearer ${p.service_key}` } });
126
123
  if (res.status === 204 || res.ok) {
127
- saveProjects(loadProjects().filter(pr => pr.project_id !== projectId));
124
+ removeProject(projectId);
128
125
  console.log(JSON.stringify({ status: "ok", message: `Project ${projectId} deleted.` }));
129
126
  } else {
130
127
  const data = await res.json().catch(() => ({}));
package/lib/tier.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { readWallet, WALLET_FILE, API } from "./config.mjs";
2
- import { existsSync } from "fs";
2
+ import { setupPaidFetch } from "./paid-fetch.mjs";
3
3
 
4
4
  const HELP = `run402 tier — Manage your Run402 tier subscription
5
5
 
@@ -24,26 +24,6 @@ Examples:
24
24
  run402 tier set hobby
25
25
  `;
26
26
 
27
- async function setupPaidFetch() {
28
- if (!existsSync(WALLET_FILE)) {
29
- console.error(JSON.stringify({ status: "error", message: "No wallet found. Run: run402 wallet create && run402 wallet fund" }));
30
- process.exit(1);
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
- return wrapFetchWithPayment(fetch, client);
45
- }
46
-
47
27
  async function status() {
48
28
  const w = readWallet();
49
29
  if (!w) { console.log(JSON.stringify({ status: "error", message: "No wallet. Run: run402 wallet create" })); process.exit(1); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "run402",
3
- "version": "1.8.2",
3
+ "version": "1.9.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": {