run402-mcp 1.40.0 → 1.40.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/package.json +4 -2
- package/sdk/README.md +69 -0
- package/sdk/core-dist/allowance-auth.d.ts +35 -0
- package/sdk/core-dist/allowance-auth.js +129 -0
- package/sdk/core-dist/allowance.d.ts +11 -0
- package/sdk/core-dist/allowance.js +25 -0
- package/sdk/core-dist/client.d.ts +15 -0
- package/sdk/core-dist/client.js +42 -0
- package/sdk/core-dist/config.d.ts +5 -0
- package/sdk/core-dist/config.js +26 -0
- package/sdk/core-dist/keystore.d.ts +26 -0
- package/sdk/core-dist/keystore.js +97 -0
- package/sdk/core-dist/wallet-auth.d.ts +15 -0
- package/sdk/core-dist/wallet-auth.js +62 -0
- package/sdk/core-dist/wallet.d.ts +10 -0
- package/sdk/core-dist/wallet.js +25 -0
- package/sdk/dist/credentials.d.ts +70 -0
- package/sdk/dist/credentials.d.ts.map +1 -0
- package/sdk/dist/credentials.js +19 -0
- package/sdk/dist/credentials.js.map +1 -0
- package/sdk/dist/errors.d.ts +34 -0
- package/sdk/dist/errors.d.ts.map +1 -0
- package/sdk/dist/errors.js +46 -0
- package/sdk/dist/errors.js.map +1 -0
- package/sdk/dist/index.d.ts +68 -0
- package/sdk/dist/index.d.ts.map +1 -0
- package/sdk/dist/index.js +81 -0
- package/sdk/dist/index.js.map +1 -0
- package/sdk/dist/kernel.d.ts +47 -0
- package/sdk/dist/kernel.d.ts.map +1 -0
- package/sdk/dist/kernel.js +76 -0
- package/sdk/dist/kernel.js.map +1 -0
- package/sdk/dist/namespaces/admin.d.ts +30 -0
- package/sdk/dist/namespaces/admin.d.ts.map +1 -0
- package/sdk/dist/namespaces/admin.js +36 -0
- package/sdk/dist/namespaces/admin.js.map +1 -0
- package/sdk/dist/namespaces/ai.d.ts +57 -0
- package/sdk/dist/namespaces/ai.d.ts.map +1 -0
- package/sdk/dist/namespaces/ai.js +62 -0
- package/sdk/dist/namespaces/ai.js.map +1 -0
- package/sdk/dist/namespaces/allowance.d.ts +51 -0
- package/sdk/dist/namespaces/allowance.d.ts.map +1 -0
- package/sdk/dist/namespaces/allowance.js +116 -0
- package/sdk/dist/namespaces/allowance.js.map +1 -0
- package/sdk/dist/namespaces/apps.d.ts +158 -0
- package/sdk/dist/namespaces/apps.d.ts.map +1 -0
- package/sdk/dist/namespaces/apps.js +150 -0
- package/sdk/dist/namespaces/apps.js.map +1 -0
- package/sdk/dist/namespaces/auth.d.ts +53 -0
- package/sdk/dist/namespaces/auth.d.ts.map +1 -0
- package/sdk/dist/namespaces/auth.js +106 -0
- package/sdk/dist/namespaces/auth.js.map +1 -0
- package/sdk/dist/namespaces/billing.d.ts +64 -0
- package/sdk/dist/namespaces/billing.d.ts.map +1 -0
- package/sdk/dist/namespaces/billing.js +105 -0
- package/sdk/dist/namespaces/billing.js.map +1 -0
- package/sdk/dist/namespaces/blobs.d.ts +41 -0
- package/sdk/dist/namespaces/blobs.d.ts.map +1 -0
- package/sdk/dist/namespaces/blobs.js +202 -0
- package/sdk/dist/namespaces/blobs.js.map +1 -0
- package/sdk/dist/namespaces/blobs.types.d.ts +58 -0
- package/sdk/dist/namespaces/blobs.types.d.ts.map +1 -0
- package/sdk/dist/namespaces/blobs.types.js +9 -0
- package/sdk/dist/namespaces/blobs.types.js.map +1 -0
- package/sdk/dist/namespaces/contracts.d.ts +65 -0
- package/sdk/dist/namespaces/contracts.d.ts.map +1 -0
- package/sdk/dist/namespaces/contracts.js +163 -0
- package/sdk/dist/namespaces/contracts.js.map +1 -0
- package/sdk/dist/namespaces/domains.d.ts +57 -0
- package/sdk/dist/namespaces/domains.d.ts.map +1 -0
- package/sdk/dist/namespaces/domains.js +60 -0
- package/sdk/dist/namespaces/domains.js.map +1 -0
- package/sdk/dist/namespaces/email.d.ts +131 -0
- package/sdk/dist/namespaces/email.d.ts.map +1 -0
- package/sdk/dist/namespaces/email.js +318 -0
- package/sdk/dist/namespaces/email.js.map +1 -0
- package/sdk/dist/namespaces/functions.d.ts +43 -0
- package/sdk/dist/namespaces/functions.d.ts.map +1 -0
- package/sdk/dist/namespaces/functions.js +146 -0
- package/sdk/dist/namespaces/functions.js.map +1 -0
- package/sdk/dist/namespaces/functions.types.d.ts +96 -0
- package/sdk/dist/namespaces/functions.types.d.ts.map +1 -0
- package/sdk/dist/namespaces/functions.types.js +6 -0
- package/sdk/dist/namespaces/functions.types.js.map +1 -0
- package/sdk/dist/namespaces/projects.d.ts +97 -0
- package/sdk/dist/namespaces/projects.d.ts.map +1 -0
- package/sdk/dist/namespaces/projects.js +214 -0
- package/sdk/dist/namespaces/projects.js.map +1 -0
- package/sdk/dist/namespaces/projects.types.d.ts +112 -0
- package/sdk/dist/namespaces/projects.types.d.ts.map +1 -0
- package/sdk/dist/namespaces/projects.types.js +9 -0
- package/sdk/dist/namespaces/projects.types.js.map +1 -0
- package/sdk/dist/namespaces/secrets.d.ts +23 -0
- package/sdk/dist/namespaces/secrets.d.ts.map +1 -0
- package/sdk/dist/namespaces/secrets.js +45 -0
- package/sdk/dist/namespaces/secrets.js.map +1 -0
- package/sdk/dist/namespaces/sender-domain.d.ts +40 -0
- package/sdk/dist/namespaces/sender-domain.d.ts.map +1 -0
- package/sdk/dist/namespaces/sender-domain.js +69 -0
- package/sdk/dist/namespaces/sender-domain.js.map +1 -0
- package/sdk/dist/namespaces/service.d.ts +51 -0
- package/sdk/dist/namespaces/service.d.ts.map +1 -0
- package/sdk/dist/namespaces/service.js +25 -0
- package/sdk/dist/namespaces/service.js.map +1 -0
- package/sdk/dist/namespaces/sites.d.ts +50 -0
- package/sdk/dist/namespaces/sites.d.ts.map +1 -0
- package/sdk/dist/namespaces/sites.js +38 -0
- package/sdk/dist/namespaces/sites.js.map +1 -0
- package/sdk/dist/namespaces/subdomains.d.ts +36 -0
- package/sdk/dist/namespaces/subdomains.d.ts.map +1 -0
- package/sdk/dist/namespaces/subdomains.js +54 -0
- package/sdk/dist/namespaces/subdomains.js.map +1 -0
- package/sdk/dist/namespaces/tier.d.ts +36 -0
- package/sdk/dist/namespaces/tier.d.ts.map +1 -0
- package/sdk/dist/namespaces/tier.js +31 -0
- package/sdk/dist/namespaces/tier.js.map +1 -0
- package/sdk/dist/node/credentials.d.ts +26 -0
- package/sdk/dist/node/credentials.d.ts.map +1 -0
- package/sdk/dist/node/credentials.js +69 -0
- package/sdk/dist/node/credentials.js.map +1 -0
- package/sdk/dist/node/index.d.ts +44 -0
- package/sdk/dist/node/index.d.ts.map +1 -0
- package/sdk/dist/node/index.js +43 -0
- package/sdk/dist/node/index.js.map +1 -0
- package/sdk/dist/node/paid-fetch.d.ts +22 -0
- package/sdk/dist/node/paid-fetch.d.ts.map +1 -0
- package/sdk/dist/node/paid-fetch.js +116 -0
- package/sdk/dist/node/paid-fetch.js.map +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "run402-mcp",
|
|
3
|
-
"version": "1.40.
|
|
3
|
+
"version": "1.40.2",
|
|
4
4
|
"description": "MCP server for Run402 — AI-native Postgres databases with REST API, auth, storage, and row-level security. Pay with x402 USDC micropayments.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -11,12 +11,14 @@
|
|
|
11
11
|
"files": [
|
|
12
12
|
"dist",
|
|
13
13
|
"core/dist",
|
|
14
|
+
"sdk/dist",
|
|
15
|
+
"sdk/core-dist",
|
|
14
16
|
"README.md"
|
|
15
17
|
],
|
|
16
18
|
"scripts": {
|
|
17
19
|
"build:core": "tsc -p core/tsconfig.json",
|
|
18
20
|
"build:sdk": "mkdir -p sdk/core-dist && cp core/dist/*.js sdk/core-dist/ && cp core/dist/*.d.ts sdk/core-dist/ && tsc -p sdk/tsconfig.json",
|
|
19
|
-
"build": "npm run build:core && npm run build:sdk && tsc && mkdir -p cli/core-dist && cp core/dist/*.js cli/core-dist/",
|
|
21
|
+
"build": "npm run build:core && npm run build:sdk && tsc && mkdir -p cli/core-dist cli/sdk/dist cli/sdk/core-dist && cp core/dist/*.js cli/core-dist/ && cp -R sdk/dist/. cli/sdk/dist/ && cp sdk/core-dist/*.js cli/sdk/core-dist/",
|
|
20
22
|
"start": "node dist/index.js",
|
|
21
23
|
"test:skill": "node --test --import tsx SKILL.test.ts",
|
|
22
24
|
"test:sync": "node --test --import tsx sync.test.ts",
|
package/sdk/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# @run402/sdk
|
|
2
|
+
|
|
3
|
+
Typed TypeScript client for the [Run402](https://run402.com) API.
|
|
4
|
+
|
|
5
|
+
This package is the kernel shared by the `run402-mcp` MCP server, the `run402` CLI, and user-deployed run402 functions. It exposes every run402 API operation as a method on a resource namespace (`sdk.projects.provision()`, `sdk.blobs.put()`, `sdk.functions.deploy()`, etc.).
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @run402/sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick start (Node)
|
|
14
|
+
|
|
15
|
+
The `/node` subpath provides zero-config defaults: reads credentials from the existing `~/.config/run402/` keystore and auto-retries x402 payments from your local allowance.
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { run402 } from "@run402/sdk/node";
|
|
19
|
+
|
|
20
|
+
const r = run402();
|
|
21
|
+
const project = await r.projects.provision({ tier: "prototype" });
|
|
22
|
+
await r.blobs.put(project.id, "hello.txt", "hello world");
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick start (isomorphic / sandbox)
|
|
26
|
+
|
|
27
|
+
The root entry is isomorphic — no filesystem access, no Node-only imports. Supply your own `CredentialsProvider`:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { Run402 } from "@run402/sdk";
|
|
31
|
+
|
|
32
|
+
const r = new Run402({
|
|
33
|
+
apiBase: "https://api.run402.com",
|
|
34
|
+
credentials: {
|
|
35
|
+
async getAuth(path) {
|
|
36
|
+
return { Authorization: `Bearer ${mySessionToken}` };
|
|
37
|
+
},
|
|
38
|
+
async getProject(id) {
|
|
39
|
+
return mySessionProjects[id] ?? null;
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Stability
|
|
46
|
+
|
|
47
|
+
This package is `0.x`. Breaking changes may occur between minor versions until `1.0`. Pin an exact version in production dependencies until stabilization.
|
|
48
|
+
|
|
49
|
+
## Errors
|
|
50
|
+
|
|
51
|
+
All failures throw subclasses of `Run402Error`:
|
|
52
|
+
|
|
53
|
+
- `PaymentRequired` — HTTP 402, carries the x402 payment requirements
|
|
54
|
+
- `ProjectNotFound` — project ID not in the credential provider
|
|
55
|
+
- `Unauthorized` — HTTP 401 / 403
|
|
56
|
+
- `ApiError` — other non-2xx responses
|
|
57
|
+
- `NetworkError` — fetch rejected with no HTTP response
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { PaymentRequired } from "@run402/sdk";
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
await r.projects.provision({ tier: "prototype" });
|
|
64
|
+
} catch (e) {
|
|
65
|
+
if (e instanceof PaymentRequired) {
|
|
66
|
+
console.log("needs funding:", e.body);
|
|
67
|
+
} else throw e;
|
|
68
|
+
}
|
|
69
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Allowance auth helper — generates SIWX (Sign-In With X / EIP-4361) headers for Run402 API.
|
|
3
|
+
* Uses @noble/curves (lighter than viem) for signing.
|
|
4
|
+
*/
|
|
5
|
+
export interface SIWxAuthHeaders {
|
|
6
|
+
"SIGN-IN-WITH-X": string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* EIP-55 mixed-case checksum encoding.
|
|
10
|
+
*/
|
|
11
|
+
export declare function toChecksumAddress(address: string): string;
|
|
12
|
+
interface SIWEMessageOpts {
|
|
13
|
+
domain: string;
|
|
14
|
+
uri: string;
|
|
15
|
+
statement: string;
|
|
16
|
+
version: string;
|
|
17
|
+
chainId: number;
|
|
18
|
+
nonce: string;
|
|
19
|
+
issuedAt: string;
|
|
20
|
+
expirationTime?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Format an EIP-4361 (SIWE) message. Must be byte-for-byte compatible
|
|
24
|
+
* with the `siwe` library's message format used server-side for verification.
|
|
25
|
+
*/
|
|
26
|
+
export declare function formatSIWEMessage(opts: SIWEMessageOpts, address: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Get SIWX auth headers for the Run402 API.
|
|
29
|
+
* Returns null if no allowance is configured.
|
|
30
|
+
*
|
|
31
|
+
* @param path - API path (e.g. "/projects/v1") used to build the SIWE uri field.
|
|
32
|
+
*/
|
|
33
|
+
export declare function getAllowanceAuthHeaders(path: string, allowancePath?: string): SIWxAuthHeaders | null;
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=allowance-auth.d.ts.map
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Allowance auth helper — generates SIWX (Sign-In With X / EIP-4361) headers for Run402 API.
|
|
3
|
+
* Uses @noble/curves (lighter than viem) for signing.
|
|
4
|
+
*/
|
|
5
|
+
import { randomBytes } from "node:crypto";
|
|
6
|
+
import { secp256k1 } from "@noble/curves/secp256k1.js";
|
|
7
|
+
import { keccak_256 } from "@noble/hashes/sha3.js";
|
|
8
|
+
import { bytesToHex } from "@noble/hashes/utils.js";
|
|
9
|
+
import { readAllowance } from "./allowance.js";
|
|
10
|
+
import { getApiBase } from "./config.js";
|
|
11
|
+
/**
|
|
12
|
+
* EIP-55 mixed-case checksum encoding.
|
|
13
|
+
*/
|
|
14
|
+
export function toChecksumAddress(address) {
|
|
15
|
+
const lower = address.toLowerCase().replace("0x", "");
|
|
16
|
+
const hash = bytesToHex(keccak_256(new TextEncoder().encode(lower)));
|
|
17
|
+
let checksummed = "0x";
|
|
18
|
+
for (let i = 0; i < lower.length; i++) {
|
|
19
|
+
checksummed += parseInt(hash[i], 16) >= 8 ? lower[i].toUpperCase() : lower[i];
|
|
20
|
+
}
|
|
21
|
+
return checksummed;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* EIP-191 personal_sign: sign a message with the allowance's private key.
|
|
25
|
+
*/
|
|
26
|
+
function personalSign(privateKeyHex, address, message) {
|
|
27
|
+
const msgBytes = new TextEncoder().encode(message);
|
|
28
|
+
const prefix = new TextEncoder().encode(`\x19Ethereum Signed Message:\n${msgBytes.length}`);
|
|
29
|
+
const prefixed = new Uint8Array(prefix.length + msgBytes.length);
|
|
30
|
+
prefixed.set(prefix);
|
|
31
|
+
prefixed.set(msgBytes, prefix.length);
|
|
32
|
+
const hash = keccak_256(prefixed);
|
|
33
|
+
const pkHex = privateKeyHex.startsWith("0x")
|
|
34
|
+
? privateKeyHex.slice(2)
|
|
35
|
+
: privateKeyHex;
|
|
36
|
+
const pkBytes = Uint8Array.from(Buffer.from(pkHex, "hex"));
|
|
37
|
+
const rawSig = secp256k1.sign(hash, pkBytes, { prehash: false });
|
|
38
|
+
const sig = secp256k1.Signature.fromBytes(rawSig);
|
|
39
|
+
// Determine recovery bit by trying both and matching the address
|
|
40
|
+
let recovery = 0;
|
|
41
|
+
for (const v of [0, 1]) {
|
|
42
|
+
try {
|
|
43
|
+
const recovered = sig.addRecoveryBit(v).recoverPublicKey(hash);
|
|
44
|
+
const pubBytes = recovered.toBytes(false).slice(1); // uncompressed, drop 04 prefix
|
|
45
|
+
const addrBytes = keccak_256(pubBytes).slice(-20);
|
|
46
|
+
if ("0x" + bytesToHex(addrBytes) === address.toLowerCase()) {
|
|
47
|
+
recovery = v;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const r = sig.r.toString(16).padStart(64, "0");
|
|
56
|
+
const s = sig.s.toString(16).padStart(64, "0");
|
|
57
|
+
const vHex = (recovery + 27).toString(16).padStart(2, "0");
|
|
58
|
+
return "0x" + r + s + vHex;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Format an EIP-4361 (SIWE) message. Must be byte-for-byte compatible
|
|
62
|
+
* with the `siwe` library's message format used server-side for verification.
|
|
63
|
+
*/
|
|
64
|
+
export function formatSIWEMessage(opts, address) {
|
|
65
|
+
const checksummed = toChecksumAddress(address);
|
|
66
|
+
const lines = [
|
|
67
|
+
`${opts.domain} wants you to sign in with your Ethereum account:`,
|
|
68
|
+
checksummed,
|
|
69
|
+
"",
|
|
70
|
+
opts.statement,
|
|
71
|
+
"",
|
|
72
|
+
`URI: ${opts.uri}`,
|
|
73
|
+
`Version: ${opts.version}`,
|
|
74
|
+
`Chain ID: ${opts.chainId}`,
|
|
75
|
+
`Nonce: ${opts.nonce}`,
|
|
76
|
+
`Issued At: ${opts.issuedAt}`,
|
|
77
|
+
];
|
|
78
|
+
if (opts.expirationTime) {
|
|
79
|
+
lines.push(`Expiration Time: ${opts.expirationTime}`);
|
|
80
|
+
}
|
|
81
|
+
return lines.join("\n");
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get SIWX auth headers for the Run402 API.
|
|
85
|
+
* Returns null if no allowance is configured.
|
|
86
|
+
*
|
|
87
|
+
* @param path - API path (e.g. "/projects/v1") used to build the SIWE uri field.
|
|
88
|
+
*/
|
|
89
|
+
export function getAllowanceAuthHeaders(path, allowancePath) {
|
|
90
|
+
const allowance = readAllowance(allowancePath);
|
|
91
|
+
if (!allowance || !allowance.address || !allowance.privateKey)
|
|
92
|
+
return null;
|
|
93
|
+
const apiBase = getApiBase();
|
|
94
|
+
const url = new URL(apiBase);
|
|
95
|
+
const domain = url.hostname;
|
|
96
|
+
const uri = `${apiBase}${path}`;
|
|
97
|
+
const nonce = randomBytes(16).toString("hex");
|
|
98
|
+
const now = new Date();
|
|
99
|
+
const issuedAt = now.toISOString();
|
|
100
|
+
const expirationTime = new Date(now.getTime() + 5 * 60 * 1000).toISOString();
|
|
101
|
+
const message = formatSIWEMessage({
|
|
102
|
+
domain,
|
|
103
|
+
uri,
|
|
104
|
+
statement: "Sign in to Run402",
|
|
105
|
+
version: "1",
|
|
106
|
+
chainId: 84532, // Base Sepolia
|
|
107
|
+
nonce,
|
|
108
|
+
issuedAt,
|
|
109
|
+
expirationTime,
|
|
110
|
+
}, allowance.address);
|
|
111
|
+
const signature = personalSign(allowance.privateKey, allowance.address, message);
|
|
112
|
+
const payload = {
|
|
113
|
+
domain,
|
|
114
|
+
address: toChecksumAddress(allowance.address),
|
|
115
|
+
statement: "Sign in to Run402",
|
|
116
|
+
uri,
|
|
117
|
+
version: "1",
|
|
118
|
+
chainId: "eip155:84532",
|
|
119
|
+
type: "eip191",
|
|
120
|
+
nonce,
|
|
121
|
+
issuedAt,
|
|
122
|
+
expirationTime,
|
|
123
|
+
signature,
|
|
124
|
+
};
|
|
125
|
+
return {
|
|
126
|
+
"SIGN-IN-WITH-X": Buffer.from(JSON.stringify(payload)).toString("base64"),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=allowance-auth.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface AllowanceData {
|
|
2
|
+
address: string;
|
|
3
|
+
privateKey: string;
|
|
4
|
+
created?: string;
|
|
5
|
+
funded?: boolean;
|
|
6
|
+
lastFaucet?: string;
|
|
7
|
+
rail?: "x402" | "mpp";
|
|
8
|
+
}
|
|
9
|
+
export declare function readAllowance(path?: string): AllowanceData | null;
|
|
10
|
+
export declare function saveAllowance(data: AllowanceData, path?: string): void;
|
|
11
|
+
//# sourceMappingURL=allowance.d.ts.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,15 @@
|
|
|
1
|
+
export interface ApiResponse {
|
|
2
|
+
ok: boolean;
|
|
3
|
+
is402?: boolean;
|
|
4
|
+
status: number;
|
|
5
|
+
body: unknown;
|
|
6
|
+
}
|
|
7
|
+
export interface ApiRequestOptions {
|
|
8
|
+
method?: string;
|
|
9
|
+
headers?: Record<string, string>;
|
|
10
|
+
body?: unknown;
|
|
11
|
+
/** Send body as raw string (e.g. for text/plain SQL) */
|
|
12
|
+
rawBody?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function apiRequest(path: string, opts?: ApiRequestOptions): Promise<ApiResponse>;
|
|
15
|
+
//# sourceMappingURL=client.d.ts.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,26 @@
|
|
|
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
|
+
if (process.env.RUN402_ALLOWANCE_PATH)
|
|
15
|
+
return process.env.RUN402_ALLOWANCE_PATH;
|
|
16
|
+
const dir = getConfigDir();
|
|
17
|
+
const newPath = join(dir, "allowance.json");
|
|
18
|
+
const oldPath = join(dir, "wallet.json");
|
|
19
|
+
// Auto-migrate from wallet.json → allowance.json
|
|
20
|
+
if (!existsSync(newPath) && existsSync(oldPath)) {
|
|
21
|
+
mkdirSync(dir, { recursive: true });
|
|
22
|
+
renameSync(oldPath, newPath);
|
|
23
|
+
}
|
|
24
|
+
return newPath;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface StoredProject {
|
|
2
|
+
anon_key: string;
|
|
3
|
+
service_key: string;
|
|
4
|
+
site_url?: string;
|
|
5
|
+
deployed_at?: string;
|
|
6
|
+
last_deployment_id?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface KeyStore {
|
|
9
|
+
active_project_id?: string;
|
|
10
|
+
projects: Record<string, StoredProject>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Load the keystore from disk.
|
|
14
|
+
* Auto-migrates legacy formats:
|
|
15
|
+
* - Array format (CLI legacy): [{project_id, ...}] → {projects: {id: {...}}}
|
|
16
|
+
* - Old field name: expires_at → lease_expires_at
|
|
17
|
+
*/
|
|
18
|
+
export declare function loadKeyStore(path?: string): KeyStore;
|
|
19
|
+
export declare function saveKeyStore(store: KeyStore, path?: string): void;
|
|
20
|
+
export declare function getProject(projectId: string, path?: string): StoredProject | undefined;
|
|
21
|
+
export declare function saveProject(projectId: string, project: StoredProject, path?: string): void;
|
|
22
|
+
export declare function updateProject(projectId: string, update: Partial<StoredProject>, path?: string): void;
|
|
23
|
+
export declare function removeProject(projectId: string, path?: string): void;
|
|
24
|
+
export declare function getActiveProjectId(path?: string): string | undefined;
|
|
25
|
+
export declare function setActiveProjectId(projectId: string, path?: string): void;
|
|
26
|
+
//# sourceMappingURL=keystore.d.ts.map
|
|
@@ -0,0 +1,97 @@
|
|
|
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
|
+
...(item.site_url && { site_url: item.site_url }),
|
|
25
|
+
...(item.deployed_at && { deployed_at: item.deployed_at }),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return { projects };
|
|
30
|
+
}
|
|
31
|
+
if (parsed && typeof parsed === "object" && parsed.projects) {
|
|
32
|
+
// Strip legacy fields (tier, lease_expires_at, expires_at) from projects
|
|
33
|
+
for (const proj of Object.values(parsed.projects)) {
|
|
34
|
+
const rec = proj;
|
|
35
|
+
delete rec.tier;
|
|
36
|
+
delete rec.lease_expires_at;
|
|
37
|
+
delete rec.expires_at;
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
...(parsed.active_project_id && { active_project_id: parsed.active_project_id }),
|
|
41
|
+
projects: parsed.projects,
|
|
42
|
+
};
|
|
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 updateProject(projectId, update, path) {
|
|
70
|
+
const p = path ?? getKeystorePath();
|
|
71
|
+
const store = loadKeyStore(p);
|
|
72
|
+
const existing = store.projects[projectId];
|
|
73
|
+
if (existing) {
|
|
74
|
+
store.projects[projectId] = { ...existing, ...update };
|
|
75
|
+
saveKeyStore(store, p);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export function removeProject(projectId, path) {
|
|
79
|
+
const p = path ?? getKeystorePath();
|
|
80
|
+
const store = loadKeyStore(p);
|
|
81
|
+
delete store.projects[projectId];
|
|
82
|
+
if (store.active_project_id === projectId) {
|
|
83
|
+
delete store.active_project_id;
|
|
84
|
+
}
|
|
85
|
+
saveKeyStore(store, p);
|
|
86
|
+
}
|
|
87
|
+
export function getActiveProjectId(path) {
|
|
88
|
+
const store = loadKeyStore(path);
|
|
89
|
+
return store.active_project_id;
|
|
90
|
+
}
|
|
91
|
+
export function setActiveProjectId(projectId, path) {
|
|
92
|
+
const p = path ?? getKeystorePath();
|
|
93
|
+
const store = loadKeyStore(p);
|
|
94
|
+
store.active_project_id = projectId;
|
|
95
|
+
saveKeyStore(store, p);
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=keystore.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wallet auth helper — generates EIP-191 signature headers for Run402 API.
|
|
3
|
+
* Uses @noble/curves (lighter than viem) for signing.
|
|
4
|
+
*/
|
|
5
|
+
export interface WalletAuthHeaders {
|
|
6
|
+
"X-Run402-Wallet": string;
|
|
7
|
+
"X-Run402-Signature": string;
|
|
8
|
+
"X-Run402-Timestamp": string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Get wallet auth headers for the Run402 API.
|
|
12
|
+
* Returns null if no wallet is configured.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getWalletAuthHeaders(walletPath?: string): WalletAuthHeaders | null;
|
|
15
|
+
//# sourceMappingURL=wallet-auth.d.ts.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,10 @@
|
|
|
1
|
+
export interface WalletData {
|
|
2
|
+
address: string;
|
|
3
|
+
privateKey: string;
|
|
4
|
+
created?: string;
|
|
5
|
+
funded?: boolean;
|
|
6
|
+
lastFaucet?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function readWallet(path?: string): WalletData | null;
|
|
9
|
+
export declare function saveWallet(data: WalletData, path?: string): void;
|
|
10
|
+
//# sourceMappingURL=wallet.d.ts.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
|