Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,6 @@ npx run402-mcp
| `setup_rls` | Apply row-level security templates to tables. |
| `get_schema` | Introspect database schema — tables, columns, types, constraints, RLS policies. |
| `get_usage` | Get project usage report — API calls, storage, limits, lease expiry. |
| `upload_file` | Upload text content to project storage. |
| `download_file` | Download a file from project storage. |
| `delete_file` | Delete a file from project storage. |
| `list_files` | List files in a storage bucket. |
| `deploy_function` | Deploy a serverless function (Node 22) to a project. |
| `invoke_function` | Invoke a deployed function via HTTP. |
| `get_function_logs` | Get recent logs from a deployed function. |
Expand Down Expand Up @@ -214,7 +210,7 @@ claude mcp add run402 -- npx -y run402-mcp
## How It Works

1. **Provision** — Call `provision_postgres_project` to create a database. The server handles x402 payment negotiation and stores credentials locally.
2. **Build** — Use `run_sql` to create tables, `rest_query` to insert/query data, and `upload_file` for storage.
2. **Build** — Use `run_sql` to create tables, `rest_query` to insert/query data, and `blob_put` for storage.
3. **Deploy** — Use `deploy_site` for static sites, `deploy_function` for serverless functions, or `bundle_deploy` for a full-stack app in one call.
4. **Renew** — Call `set_tier` before your lease expires.

Expand Down
3 changes: 1 addition & 2 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ npx run402-mcp
| `provision_postgres_project` | 创建新的 Postgres 数据库(prototype/hobby/team 等级) |
| `run_sql` | 对项目执行 SQL(DDL 或查询) |
| `rest_query` | 通过 PostgREST 查询/修改数据 |
| `upload_file` | 上传文件到项目存储 |
| `renew_project` | 续期数据库租约 |
| `deploy_site` | 部署静态 HTML/CSS/JS 站点 |
| `deploy_function` | 部署 Node 22 Serverless 函数 |
Expand Down Expand Up @@ -103,7 +102,7 @@ Run402 已发布为 OpenClaw Skill。支持所有 OpenClaw 兼容的模型和平
## 工作流程

1. **创建项目** — 调用 `provision_postgres_project` 创建数据库。服务器自动处理 x402 支付协商,并在本地保存凭证。
2. **构建应用** — 用 `run_sql` 创建表结构,`rest_query` 插入/查询数据,`upload_file` 管理文件存储。
2. **构建应用** — 用 `run_sql` 创建表结构,`rest_query` 插入/查询数据,`blob_put` 管理文件存储。
3. **部署上线** — 用 `deploy_site` 部署前端,`deploy_function` 部署后端函数。
4. **续期维护** — 在租约到期前调用 `renew_project`。

Expand Down
34 changes: 6 additions & 28 deletions SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,6 @@ html += styles.linkTag();

The legacy `size_bytes`, `sha256`, `immutable_url` fields stay populated for back-compat with pre-v1.45 callers.

Supersedes `upload_file` (deprecated).

### blob_get

Download a blob to a local file. Writes bytes directly to disk — no context-window bloat.
Expand All @@ -147,7 +145,7 @@ Download a blob to a local file. Writes bytes directly to disk — no context-wi
- `key` (required) — Object key
- `local_path` (required) — Local path to write

**Returns:** `{ key, size_bytes, sha256? }`. Supersedes `download_file` (deprecated).
**Returns:** `{ key, size_bytes, sha256? }`.

### blob_ls

Expand All @@ -159,11 +157,11 @@ List blobs in a project with optional prefix filter and keyset pagination.
- `limit` (optional, default: 100, max: 1000)
- `cursor` (optional) — Pagination cursor from a prior call

**Returns:** `{ blobs: [{ key, size_bytes, content_type, visibility, sha256?, created_at }], next_cursor? }`. Supersedes `list_files` (deprecated).
**Returns:** `{ blobs: [{ key, size_bytes, content_type, visibility, sha256?, created_at }], next_cursor? }`.

### blob_rm

Delete a blob and decrement the project's storage usage. Supersedes `delete_file` (deprecated).
Delete a blob and decrement the project's storage usage.

**Parameters:**
- `project_id` (required) — Project ID
Expand Down Expand Up @@ -210,26 +208,6 @@ Polls the CDN until a **mutable** blob URL serves the expected SHA-256, or the t

**Returns:** `{ fresh, observedSha256, attempts, elapsedMs, vantage }`. The tool returns `isError=true` on timeout so an agent can branch into a fallback (typically: switch to the immutableUrl, which is always immediately correct).

### upload_file (deprecated)

Legacy file upload. **Deprecated — sunset 2026-06-01.** Use `blob_put` instead.

**Parameters:**
- `project_id` (required) — Project ID
- `bucket` (required) — Storage bucket name (e.g., `"assets"`)
- `path` (required) — File path within bucket (e.g., `"logs/2024-01-01.txt"`)
- `content` (required) — Text content to upload
- `content_type` (optional, default: `"text/plain"`) — MIME type

**Returns:** `{ key: "assets/logs/2024-01-01.txt", size: 1234 }` with the stored file path and size in bytes.

**Example:**
```
upload_file(project_id: "prj_...", bucket: "assets", path: "data.csv", content: "name,age\nAlice,30\nBob,25")
```

Uses the stored `anon_key` automatically.

### renew_project

Renew a project's lease before it expires.
Expand Down Expand Up @@ -816,7 +794,7 @@ If your app has users, use the HTTP auth endpoints directly:
### Step 7: Upload files (optional)

```
upload_file(project_id: "prj_...", bucket: "assets", path: "report.csv", content: "col1,col2\nval1,val2")
blob_put(project_id: "prj_...", key: "assets/report.csv", content: "col1,col2\nval1,val2")
```

### Step 8: Monitor usage
Expand All @@ -830,7 +808,7 @@ run_sql(project_id: "prj_...", sql: "SELECT count(*) FROM todos")

Run402 supports two payment protocols: **x402** (USDC on Base) and **MPP** (pathUSD on Tempo). Both use the same wallet key. Here's what you need to know:

**When payment is needed:** Only `provision_postgres_project` and `renew_project` require x402 payment. All other tools (run_sql, rest_query, upload_file) use stored project keys — no payment needed.
**When payment is needed:** Only `provision_postgres_project` and `renew_project` require x402 payment. All other tools (run_sql, rest_query, blob_put) use stored project keys — no payment needed.

**What a 402 response looks like:** When payment is required, the tool returns payment details as informational text (not an error). The response includes the price, network (Base L2), and payment address.

Expand Down Expand Up @@ -859,7 +837,7 @@ Prototype uses testnet tokens — no real money needed. With x402: Base Sepolia

**Key usage patterns:**
- Use `service_key` (via `run_sql` or `key_type: "service"`) for: table creation, RLS setup, seeding data, admin queries
- Use `anon_key` (via `rest_query` default or `upload_file`) for: user-facing reads, file uploads
- Use `anon_key` (via `rest_query` default or `blob_put`) for: user-facing reads, file uploads
- Use `access_token` (from auth login, via HTTP) for: user-scoped CRUD subject to RLS

**Tier selection:**
Expand Down
2 changes: 1 addition & 1 deletion SKILL.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ const TOOLS = [
"provision_postgres_project",
"run_sql",
"rest_query",
"upload_file",
"blob_put",
"renew_project",
];

Expand Down
38 changes: 0 additions & 38 deletions cli-e2e.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1481,36 +1481,6 @@ describe("CLI e2e happy path", () => {
assert.ok(captured().includes("TEST_KEY"), "should list secrets");
});

// ── Storage ─────────────────────────────────────────────────────────────

it("storage upload", async () => {
const { run } = await import("./cli/lib/storage.mjs");
const filePath = join(tempDir, "readme.txt");
const { writeFileSync: wf } = await import("node:fs");
wf(filePath, "Hello, world!");
captureStart();
await run("upload", ["prj_test123", "assets", "readme.txt", "--file", filePath]);
captureStop();
assert.ok(captured().includes("readme.txt") || captured().includes("key"), "should upload file");
});

it("storage list", async () => {
const { run } = await import("./cli/lib/storage.mjs");
captureStart();
await run("list", ["prj_test123", "assets"]);
captureStop();
assert.ok(captured().includes("readme.txt"), "should list files");
});

it("storage download", async () => {
const { run } = await import("./cli/lib/storage.mjs");
captureStart();
await run("download", ["prj_test123", "assets", "readme.txt"]);
captureStop();
// download uses process.stdout.write, not console.log — just verify no error
assert.ok(true, "should download without error");
});

// ── Blob (GH-40: fall back to active project from 'projects use') ───────

it("blob ls falls back to active project (GH-40)", async () => {
Expand Down Expand Up @@ -1821,14 +1791,6 @@ describe("CLI e2e happy path", () => {

// ── Cleanup commands (deletions) ────────────────────────────────────────

it("storage delete", async () => {
const { run } = await import("./cli/lib/storage.mjs");
captureStart();
await run("delete", ["prj_test123", "assets", "readme.txt"]);
captureStop();
assert.ok(captured().includes("ok") || captured().includes("delete"), "should delete file");
});

it("secrets delete", async () => {
const { run } = await import("./cli/lib/secrets.mjs");
captureStart();
Expand Down
1 change: 0 additions & 1 deletion cli-help.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ const MATRIX = {
shared: [],
specific: ["put", "get", "ls", "rm", "sign"],
},
storage: { shared: ["download", "delete", "list"], specific: ["upload"] },
sites: { shared: ["status"], specific: ["deploy", "deploy-dir"] },
subdomains: { shared: ["delete", "list"], specific: ["claim"] },
domains: { shared: ["add", "list", "status", "delete"], specific: [] },
Expand Down
37 changes: 0 additions & 37 deletions cli-integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,35 +350,6 @@ describe("CLI integration (live API, no mocks)", { timeout: 180_000 }, () => {
assert.ok(captured().includes("TEST_KEY"), "should list the secret");
});

// ── Storage ───────────────────────────────────────────────────────────

it("storage upload", async () => {
const { run } = await import("./cli/lib/storage.mjs");
const filePath = join(tempDir, "test-file.txt");
writeFileSync(filePath, "Hello from integration test!");
captureStart();
await run("upload", [projectId, "assets", "test-file.txt", "--file", filePath]);
captureStop();
assert.ok(
captured().includes("test-file") || captured().includes("key") || captured().includes("size"),
"should upload file",
);
});

it("storage list", async () => {
const { run } = await import("./cli/lib/storage.mjs");
captureStart();
try {
await run("list", [projectId, "assets"]);
captureStop();
assert.ok(captured().includes("test-file"), "should list uploaded file");
} catch {
// Storage list may 404 if the bucket prefix doesn't exist yet — verify upload worked instead
captureStop();
assert.ok(true, "storage list returned 404 (bucket prefix may not be listable)");
}
});

// ── Sites ─────────────────────────────────────────────────────────────

it("sites deploy", async () => {
Expand Down Expand Up @@ -626,14 +597,6 @@ describe("CLI integration (live API, no mocks)", { timeout: 180_000 }, () => {
assert.ok(captured().includes("ok") || captured().includes("delete"), "should delete subdomain");
});

it("storage delete", async () => {
const { run } = await import("./cli/lib/storage.mjs");
captureStart();
await run("delete", [projectId, "assets", "test-file.txt"]);
captureStop();
assert.ok(captured().includes("ok") || captured().includes("delete"), "should delete file");
});

it("secrets delete", async () => {
const { run } = await import("./cli/lib/secrets.mjs");
captureStart();
Expand Down
10 changes: 0 additions & 10 deletions cli/cli.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ Commands:
functions Manage serverless functions (deploy, invoke, logs, list, delete)
secrets Manage project secrets (set, list, delete)
blob Direct-to-S3 blob storage (put, get, ls, rm, sign, diagnose) — up to 5 TiB
storage Legacy file storage (deprecated — sunset 2026-06-01, use 'blob')
sites Deploy static sites
cdn CloudFront CDN diagnostics (wait-fresh) for public blob URLs
subdomains Manage custom subdomains (claim, list, delete)
Expand Down Expand Up @@ -116,15 +115,6 @@ switch (cmd) {
await run(sub, rest);
break;
}
case "storage": {
process.stderr.write(
"run402 storage is deprecated — sunset 2026-06-01. Use `run402 blob` instead.\n" +
"See https://run402.com/docs/blob#migration\n\n",
);
const { run } = await import("./lib/storage.mjs");
await run(sub, rest);
break;
}
case "blob": {
const { run } = await import("./lib/blob.mjs");
await run(sub, rest);
Expand Down
125 changes: 0 additions & 125 deletions cli/lib/storage.mjs

This file was deleted.

Loading
Loading