run402 1.38.0 → 1.40.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/functions.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import { readFileSync } from "fs";
2
2
  import { findProject, API } from "./config.mjs";
3
- import { setupPaidFetch } from "./paid-fetch.mjs";
3
+ import { getSdk } from "./sdk.mjs";
4
+ import { reportSdkError } from "./sdk-errors.mjs";
4
5
 
5
6
  const HELP = `run402 functions — Manage serverless functions
6
7
 
@@ -128,7 +129,6 @@ Examples:
128
129
  };
129
130
 
130
131
  async function deploy(projectId, name, args) {
131
- const p = findProject(projectId);
132
132
  const opts = { file: null, timeout: undefined, memory: undefined, deps: undefined, schedule: undefined };
133
133
  for (let i = 0; i < args.length; i++) {
134
134
  if (args[i] === "--file" && args[i + 1]) opts.file = args[++i];
@@ -139,47 +139,48 @@ async function deploy(projectId, name, args) {
139
139
  }
140
140
  if (!opts.file) { console.error(JSON.stringify({ status: "error", message: "Missing --file <file>" })); process.exit(1); }
141
141
  const code = readFileSync(opts.file, "utf-8");
142
- const body = { name, code };
143
- if (opts.timeout || opts.memory) body.config = {};
144
- if (opts.timeout) body.config.timeout = opts.timeout;
145
- if (opts.memory) body.config.memory = opts.memory;
146
- if (opts.deps) body.deps = opts.deps;
147
- if (opts.schedule !== undefined) body.schedule = opts.schedule === "" ? null : opts.schedule;
148
-
149
- const fetchPaid = await setupPaidFetch();
150
- const res = await fetchPaid(`${API}/projects/v1/admin/${projectId}/functions`, {
151
- method: "POST",
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));
142
+
143
+ const deployOpts = { name, code };
144
+ if (opts.timeout !== undefined || opts.memory !== undefined) {
145
+ deployOpts.config = {};
146
+ if (opts.timeout !== undefined) deployOpts.config.timeout = opts.timeout;
147
+ if (opts.memory !== undefined) deployOpts.config.memory = opts.memory;
148
+ }
149
+ if (opts.deps !== undefined) deployOpts.deps = opts.deps;
150
+ if (opts.schedule !== undefined) deployOpts.schedule = opts.schedule === "" ? null : opts.schedule;
151
+
152
+ try {
153
+ const data = await getSdk().functions.deploy(projectId, deployOpts);
154
+ console.log(JSON.stringify(data, null, 2));
155
+ } catch (err) {
156
+ reportSdkError(err);
157
+ }
158
158
  }
159
159
 
160
160
  async function invoke(projectId, name, args) {
161
- const p = findProject(projectId);
162
161
  const opts = { method: "POST", body: undefined };
163
162
  for (let i = 0; i < args.length; i++) {
164
163
  if (args[i] === "--method" && args[i + 1]) opts.method = args[++i];
165
164
  if (args[i] === "--body" && args[i + 1]) opts.body = args[++i];
166
165
  }
167
- const fetchOpts = {
168
- method: opts.method,
169
- headers: { "apikey": p.service_key },
170
- };
171
- if (opts.body && opts.method !== "GET" && opts.method !== "HEAD") {
172
- fetchOpts.headers["Content-Type"] = "application/json";
173
- fetchOpts.body = opts.body;
166
+ const invokeOpts = { method: opts.method };
167
+ if (opts.body !== undefined && opts.method !== "GET" && opts.method !== "HEAD") {
168
+ invokeOpts.body = opts.body;
169
+ }
170
+ try {
171
+ const result = await getSdk().functions.invoke(projectId, name, invokeOpts);
172
+ const body = result.body;
173
+ if (typeof body === "string") {
174
+ process.stdout.write(body + "\n");
175
+ } else {
176
+ console.log(JSON.stringify(body, null, 2));
177
+ }
178
+ } catch (err) {
179
+ reportSdkError(err);
174
180
  }
175
- const res = await fetch(`${API}/functions/v1/${name}`, fetchOpts);
176
- const text = await res.text();
177
- if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, body: text })); process.exit(1); }
178
- try { console.log(JSON.stringify(JSON.parse(text), null, 2)); } catch { process.stdout.write(text + "\n"); }
179
181
  }
180
182
 
181
183
  async function logs(projectId, name, args) {
182
- const p = findProject(projectId);
183
184
  let tail = 50;
184
185
  let since = undefined;
185
186
  let follow = false;
@@ -189,21 +190,28 @@ async function logs(projectId, name, args) {
189
190
  if (args[i] === "--follow") follow = true;
190
191
  }
191
192
 
192
- // Parse since: accept ISO string or epoch ms
193
- let sinceMs = undefined;
193
+ // Parse since: accept ISO string or epoch ms — keep CLI-side validation
194
+ // so a bad `--since` errors with a clear message rather than silently
195
+ // being dropped by the SDK.
196
+ let sinceIso = undefined;
194
197
  if (since !== undefined) {
195
198
  const parsed = Number(since);
196
- sinceMs = Number.isNaN(parsed) ? new Date(since).getTime() : parsed;
197
- if (Number.isNaN(sinceMs)) { console.error(JSON.stringify({ status: "error", message: `Invalid --since value: ${since}` })); process.exit(1); }
199
+ const ms = Number.isNaN(parsed) ? new Date(since).getTime() : parsed;
200
+ if (Number.isNaN(ms)) { console.error(JSON.stringify({ status: "error", message: `Invalid --since value: ${since}` })); process.exit(1); }
201
+ sinceIso = new Date(ms).toISOString();
198
202
  }
199
203
 
200
204
  const fetchLogs = async () => {
201
- let url = `${API}/projects/v1/admin/${projectId}/functions/${encodeURIComponent(name)}/logs?tail=${tail}`;
202
- if (sinceMs !== undefined) url += `&since=${sinceMs}`;
203
- const res = await fetch(url, { headers: { "Authorization": `Bearer ${p.service_key}` } });
204
- const data = await res.json();
205
- if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
206
- return data.logs || [];
205
+ try {
206
+ const data = await getSdk().functions.logs(projectId, name, {
207
+ tail,
208
+ since: sinceIso,
209
+ });
210
+ return data.logs || [];
211
+ } catch (err) {
212
+ reportSdkError(err);
213
+ return [];
214
+ }
207
215
  };
208
216
 
209
217
  if (!follow) {
@@ -212,17 +220,16 @@ async function logs(projectId, name, args) {
212
220
  return;
213
221
  }
214
222
 
215
- // Follow mode: poll every 3s, print new entries
223
+ // Follow mode: poll every 3s, print new entries.
216
224
  let running = true;
217
225
  process.on("SIGINT", () => { running = false; });
218
226
 
219
- // Initial fetch
220
227
  const initial = await fetchLogs();
221
228
  for (const entry of initial) {
222
229
  console.log(`[${entry.timestamp}] ${entry.message}`);
223
230
  }
224
231
  if (initial.length > 0) {
225
- sinceMs = new Date(initial[initial.length - 1].timestamp).getTime() + 1;
232
+ sinceIso = new Date(new Date(initial[initial.length - 1].timestamp).getTime() + 1).toISOString();
226
233
  }
227
234
 
228
235
  while (running) {
@@ -233,13 +240,12 @@ async function logs(projectId, name, args) {
233
240
  console.log(`[${entry.timestamp}] ${entry.message}`);
234
241
  }
235
242
  if (entries.length > 0) {
236
- sinceMs = new Date(entries[entries.length - 1].timestamp).getTime() + 1;
243
+ sinceIso = new Date(new Date(entries[entries.length - 1].timestamp).getTime() + 1).toISOString();
237
244
  }
238
245
  }
239
246
  }
240
247
 
241
248
  async function update(projectId, name, args) {
242
- const p = findProject(projectId);
243
249
  let schedule = undefined;
244
250
  let scheduleRemove = false;
245
251
  let timeout = undefined;
@@ -250,52 +256,44 @@ async function update(projectId, name, args) {
250
256
  if (args[i] === "--timeout" && args[i + 1]) timeout = parseInt(args[++i]);
251
257
  if (args[i] === "--memory" && args[i + 1]) memory = parseInt(args[++i]);
252
258
  }
253
- const body = {};
259
+
260
+ const updateOpts = {};
254
261
  if (scheduleRemove || schedule === "") {
255
- body.schedule = null;
262
+ updateOpts.schedule = null;
256
263
  } else if (schedule !== undefined) {
257
- body.schedule = schedule;
258
- }
259
- if (timeout !== undefined || memory !== undefined) {
260
- body.config = {};
261
- if (timeout !== undefined) body.config.timeout = timeout;
262
- if (memory !== undefined) body.config.memory = memory;
264
+ updateOpts.schedule = schedule;
263
265
  }
264
- if (Object.keys(body).length === 0) {
266
+ if (timeout !== undefined) updateOpts.timeout = timeout;
267
+ if (memory !== undefined) updateOpts.memory = memory;
268
+
269
+ if (Object.keys(updateOpts).length === 0) {
265
270
  console.error(JSON.stringify({ status: "error", message: "Provide at least one of: --schedule, --schedule-remove, --timeout, --memory" }));
266
271
  process.exit(1);
267
272
  }
268
- const res = await fetch(`${API}/projects/v1/admin/${projectId}/functions/${encodeURIComponent(name)}`, {
269
- method: "PATCH",
270
- headers: { "Authorization": `Bearer ${p.service_key}`, "Content-Type": "application/json" },
271
- body: JSON.stringify(body),
272
- });
273
- const data = await res.json();
274
- if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
275
- console.log(JSON.stringify(data, null, 2));
273
+
274
+ try {
275
+ const data = await getSdk().functions.update(projectId, name, updateOpts);
276
+ console.log(JSON.stringify(data, null, 2));
277
+ } catch (err) {
278
+ reportSdkError(err);
279
+ }
276
280
  }
277
281
 
278
282
  async function list(projectId) {
279
- const p = findProject(projectId);
280
- const res = await fetch(`${API}/projects/v1/admin/${projectId}/functions`, {
281
- headers: { "Authorization": `Bearer ${p.service_key}` },
282
- });
283
- const data = await res.json();
284
- if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
285
- console.log(JSON.stringify(data, null, 2));
283
+ try {
284
+ const data = await getSdk().functions.list(projectId);
285
+ console.log(JSON.stringify(data, null, 2));
286
+ } catch (err) {
287
+ reportSdkError(err);
288
+ }
286
289
  }
287
290
 
288
291
  async function deleteFunction(projectId, name) {
289
- const p = findProject(projectId);
290
- const res = await fetch(`${API}/projects/v1/admin/${projectId}/functions/${encodeURIComponent(name)}`, {
291
- method: "DELETE",
292
- headers: { "Authorization": `Bearer ${p.service_key}` },
293
- });
294
- if (res.status === 204 || res.ok) {
292
+ try {
293
+ await getSdk().functions.delete(projectId, name);
295
294
  console.log(JSON.stringify({ status: "ok", message: `Function '${name}' deleted.` }));
296
- } else {
297
- const data = await res.json().catch(() => ({}));
298
- console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1);
295
+ } catch (err) {
296
+ reportSdkError(err);
299
297
  }
300
298
  }
301
299
 
package/lib/image.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { writeFileSync } from "fs";
2
- import { API, ALLOWANCE_FILE } from "./config.mjs";
3
- import { setupPaidFetch } from "./paid-fetch.mjs";
2
+ import { getSdk } from "./sdk.mjs";
3
+ import { reportSdkError } from "./sdk-errors.mjs";
4
4
 
5
5
  const HELP = `run402 image — Generate AI images via x402 micropayments
6
6
 
@@ -51,17 +51,16 @@ export async function run(sub, args) {
51
51
 
52
52
  if (!opts.prompt) { console.error(JSON.stringify({ status: "error", message: "Prompt required. Usage: run402 image generate \"your prompt\"" })); process.exit(1); }
53
53
 
54
- const fetchPaid = await setupPaidFetch();
55
-
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 }) });
57
- const data = await res.json();
58
- if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
59
-
60
- if (opts.output) {
61
- const buf = Buffer.from(data.image, "base64");
62
- writeFileSync(opts.output, buf);
63
- console.log(JSON.stringify({ status: "ok", file: opts.output, size: buf.length, aspect: data.aspect }));
64
- } else {
65
- console.log(JSON.stringify({ status: "ok", aspect: data.aspect, content_type: data.content_type, image: data.image }));
54
+ try {
55
+ const data = await getSdk().ai.generateImage({ prompt: opts.prompt, aspect: opts.aspect });
56
+ if (opts.output) {
57
+ const buf = Buffer.from(data.image, "base64");
58
+ writeFileSync(opts.output, buf);
59
+ console.log(JSON.stringify({ status: "ok", file: opts.output, size: buf.length, aspect: data.aspect }));
60
+ } else {
61
+ console.log(JSON.stringify({ status: "ok", aspect: data.aspect, content_type: data.content_type, image: data.image }));
62
+ }
63
+ } catch (err) {
64
+ reportSdkError(err);
66
65
  }
67
66
  }
package/lib/message.mjs CHANGED
@@ -1,4 +1,6 @@
1
- import { API, allowanceAuthHeaders } from "./config.mjs";
1
+ import { allowanceAuthHeaders } from "./config.mjs";
2
+ import { getSdk } from "./sdk.mjs";
3
+ import { reportSdkError } from "./sdk-errors.mjs";
2
4
 
3
5
  const HELP = `run402 message — Send messages to Run402 developers
4
6
 
@@ -15,16 +17,15 @@ Examples:
15
17
 
16
18
  async function send(text) {
17
19
  if (!text) { console.error(JSON.stringify({ status: "error", message: "Missing message text" })); process.exit(1); }
18
- const authHeaders = allowanceAuthHeaders("/message/v1");
20
+ // Preserve the aggressive early exit when no allowance is configured.
21
+ allowanceAuthHeaders("/message/v1");
19
22
 
20
- const res = await fetch(`${API}/message/v1`, {
21
- method: "POST",
22
- headers: { "Content-Type": "application/json", ...authHeaders },
23
- body: JSON.stringify({ message: text }),
24
- });
25
- const data = await res.json();
26
- if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
27
- console.log(JSON.stringify(data, null, 2));
23
+ try {
24
+ await getSdk().admin.sendMessage(text);
25
+ console.log(JSON.stringify({ status: "ok", message: "Message sent to Run402 developers." }));
26
+ } catch (err) {
27
+ reportSdkError(err);
28
+ }
28
29
  }
29
30
 
30
31
  export async function run(sub, args) {
package/lib/projects.mjs CHANGED
@@ -1,5 +1,7 @@
1
1
  import { readFileSync } from "fs";
2
- import { findProject, loadKeyStore, saveProject, removeProject, API, allowanceAuthHeaders, setActiveProjectId, getActiveProjectId, resolveProjectId } from "./config.mjs";
2
+ import { findProject, loadKeyStore, API, allowanceAuthHeaders, resolveProjectId } from "./config.mjs";
3
+ import { getSdk } from "./sdk.mjs";
4
+ import { reportSdkError } from "./sdk-errors.mjs";
3
5
 
4
6
  const HELP = `run402 projects — Manage your deployed Run402 projects
5
7
 
@@ -17,7 +19,10 @@ Subcommands:
17
19
  rest [id] <table> [params] Query a table via the REST API (PostgREST)
18
20
  usage [id] Show compute/storage usage for a project
19
21
  schema [id] Inspect the database schema
20
- rls [id] <template> <tables_json> Apply Row-Level Security policies
22
+ rls [id] <template> <tables_json> DEPRECATED (sunset 2026-05-23) - use 'apply-expose' instead
23
+ apply-expose [id] <manifest_json> Apply a declarative authorization manifest (supersedes 'rls')
24
+ apply-expose [id] --file <path> Apply a manifest from a JSON file
25
+ get-expose [id] Get the current authorization manifest
21
26
  delete [id] Immediately and irreversibly delete a project (cascade purge) and remove from local state
22
27
  pin [id] Pin a project (prevents expiry/GC)
23
28
  promote-user [id] <email> Promote a user to project_admin role
@@ -37,6 +42,8 @@ Examples:
37
42
  run402 projects usage abc123
38
43
  run402 projects schema abc123
39
44
  run402 projects rls abc123 public_read_authenticated_write '[{"table":"posts"}]'
45
+ run402 projects apply-expose abc123 --file manifest.json
46
+ run402 projects get-expose abc123
40
47
  run402 projects keys abc123
41
48
  run402 projects delete abc123
42
49
 
@@ -52,6 +59,14 @@ Notes:
52
59
  public_read_authenticated_write anyone reads; any authenticated user writes any row
53
60
  public_read_write_UNRESTRICTED fully open (anon_key writes); use 'run402 deploy' with a manifest
54
61
  that includes "i_understand_this_is_unrestricted": true
62
+ - 'rls' is deprecated (sunset 2026-05-23) — migrate to 'apply-expose'.
63
+ The expose manifest declares the full authorization surface (tables, views,
64
+ RPCs) in one convergent call. Tables not listed with expose:true are dark
65
+ by default. Sample manifest:
66
+ {"version":"1",
67
+ "tables":[{"name":"posts","expose":true,"policy":"user_owns_rows","owner_column":"user_id","force_owner_on_insert":true}],
68
+ "views":[],
69
+ "rpcs":[]}
55
70
  `;
56
71
 
57
72
  const SUB_HELP = {
@@ -97,10 +112,12 @@ Examples:
97
112
  };
98
113
 
99
114
  async function quote() {
100
- const res = await fetch(`${API}/tiers/v1`);
101
- const data = await res.json();
102
- if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
103
- console.log(JSON.stringify(data, null, 2));
115
+ try {
116
+ const data = await getSdk().projects.getQuote();
117
+ console.log(JSON.stringify(data, null, 2));
118
+ } catch (err) {
119
+ reportSdkError(err);
120
+ }
104
121
  }
105
122
 
106
123
  async function provision(args) {
@@ -109,60 +126,64 @@ async function provision(args) {
109
126
  if (args[i] === "--tier" && args[i + 1]) opts.tier = args[++i];
110
127
  if (args[i] === "--name" && args[i + 1]) opts.name = args[++i];
111
128
  }
112
- const authHeaders = allowanceAuthHeaders("/projects/v1");
113
- const body = { tier: opts.tier };
114
- if (opts.name) body.name = opts.name;
115
- const res = await fetch(`${API}/projects/v1`, {
116
- method: "POST",
117
- headers: { "Content-Type": "application/json", ...authHeaders },
118
- body: JSON.stringify(body),
119
- });
120
- // Content-type aware parsing: gateways (ALB, CloudFront, etc.) return HTML on
121
- // 502/504/etc., which would otherwise crash res.json() with SyntaxError (GH-84).
122
- const contentType = res.headers.get("content-type") || "";
123
- let data = null;
124
- let parseError = null;
125
- let bodyText = null;
126
- if (contentType.includes("application/json")) {
127
- try {
128
- data = await res.json();
129
- } catch (e) {
130
- parseError = e;
131
- try { bodyText = await res.text(); } catch { bodyText = ""; }
132
- }
133
- } else {
134
- try { bodyText = await res.text(); } catch { bodyText = ""; }
129
+ // Preserve the aggressive early exit when no allowance is configured —
130
+ // gives the user a more specific prompt than the SDK's 401/402 path.
131
+ allowanceAuthHeaders("/projects/v1");
132
+
133
+ try {
134
+ const data = await getSdk().projects.provision({ tier: opts.tier, name: opts.name });
135
+ console.log(JSON.stringify(data, null, 2));
136
+ } catch (err) {
137
+ reportSdkError(err);
135
138
  }
136
- if (!res.ok || parseError || data === null) {
137
- const err = { status: "error", http: res.status, content_type: contentType || null };
138
- if (data && typeof data === "object") {
139
- Object.assign(err, data);
140
- } else {
141
- const preview = typeof bodyText === "string" ? bodyText.slice(0, 500) : "";
142
- err.body_preview = preview;
143
- if (parseError) err.parse_error = "response body was not valid JSON";
144
- }
145
- console.error(JSON.stringify(err));
139
+ }
140
+
141
+ async function rls(projectId, template, tablesJson) {
142
+ let tables;
143
+ try {
144
+ tables = JSON.parse(tablesJson);
145
+ } catch {
146
+ console.error(JSON.stringify({ status: "error", message: "Invalid JSON for tables argument" }));
146
147
  process.exit(1);
147
148
  }
148
- // Save project credentials locally and set as active
149
- if (data.project_id) {
150
- saveProject(data.project_id, {
151
- anon_key: data.anon_key, service_key: data.service_key,
152
- deployed_at: new Date().toISOString(),
153
- });
154
- setActiveProjectId(data.project_id);
149
+ try {
150
+ const data = await getSdk().projects.setupRls(projectId, { template, tables });
151
+ console.log(JSON.stringify(data, null, 2));
152
+ } catch (err) {
153
+ reportSdkError(err);
155
154
  }
156
- console.log(JSON.stringify(data, null, 2));
157
155
  }
158
156
 
159
- async function rls(projectId, template, tablesJson) {
157
+ async function applyExpose(projectId, args = []) {
160
158
  const p = findProject(projectId);
161
- const tables = JSON.parse(tablesJson);
162
- const res = await fetch(`${API}/projects/v1/admin/${projectId}/rls`, {
159
+ let file = null;
160
+ let inline = null;
161
+ for (let i = 0; i < args.length; i++) {
162
+ if (args[i] === "--file" && args[i + 1]) { file = args[++i]; }
163
+ else if (!inline && !args[i].startsWith("--")) { inline = args[i]; }
164
+ }
165
+ const raw = file ? readFileSync(file, "utf-8") : inline;
166
+ if (!raw) {
167
+ console.error(JSON.stringify({ status: "error", message: "Missing manifest. Provide inline JSON or use --file <path>" }));
168
+ process.exit(1);
169
+ }
170
+ let manifest;
171
+ try { manifest = JSON.parse(raw); }
172
+ catch { console.error(JSON.stringify({ status: "error", message: "Invalid JSON for manifest" })); process.exit(1); }
173
+ const res = await fetch(`${API}/projects/v1/admin/${projectId}/expose`, {
163
174
  method: "POST",
164
175
  headers: { "Authorization": `Bearer ${p.service_key}`, "Content-Type": "application/json" },
165
- body: JSON.stringify({ template, tables }),
176
+ body: JSON.stringify(manifest),
177
+ });
178
+ const data = await res.json();
179
+ if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
180
+ console.log(JSON.stringify(data, null, 2));
181
+ }
182
+
183
+ async function getExpose(projectId) {
184
+ const p = findProject(projectId);
185
+ const res = await fetch(`${API}/projects/v1/admin/${projectId}/expose`, {
186
+ headers: { "Authorization": `Bearer ${p.service_key}` },
166
187
  });
167
188
  const data = await res.json();
168
189
  if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
@@ -178,20 +199,28 @@ async function list() {
178
199
  }
179
200
 
180
201
  async function info(projectId) {
181
- const p = findProject(projectId);
182
- console.log(JSON.stringify({
183
- project_id: projectId,
184
- rest_url: `${API}/rest/v1`,
185
- anon_key: p.anon_key,
186
- service_key: p.service_key,
187
- site_url: p.site_url || null,
188
- deployed_at: p.deployed_at || null,
189
- }, null, 2));
202
+ try {
203
+ const data = await getSdk().projects.info(projectId);
204
+ console.log(JSON.stringify({
205
+ project_id: projectId,
206
+ rest_url: `${API}/rest/v1`,
207
+ anon_key: data.anon_key,
208
+ service_key: data.service_key,
209
+ site_url: data.site_url || null,
210
+ deployed_at: data.deployed_at || null,
211
+ }, null, 2));
212
+ } catch (err) {
213
+ reportSdkError(err);
214
+ }
190
215
  }
191
216
 
192
217
  async function keys(projectId) {
193
- const p = findProject(projectId);
194
- console.log(JSON.stringify({ project_id: projectId, anon_key: p.anon_key, service_key: p.service_key }, null, 2));
218
+ try {
219
+ const data = await getSdk().projects.keys(projectId);
220
+ console.log(JSON.stringify({ project_id: projectId, anon_key: data.anon_key, service_key: data.service_key }, null, 2));
221
+ } catch (err) {
222
+ reportSdkError(err);
223
+ }
195
224
  }
196
225
 
197
226
  async function sqlCmd(projectId, args = []) {
@@ -229,38 +258,41 @@ async function rest(projectId, table, queryParams) {
229
258
  }
230
259
 
231
260
  async function usage(projectId) {
232
- const p = findProject(projectId);
233
- const res = await fetch(`${API}/projects/v1/admin/${projectId}/usage`, { headers: { "Authorization": `Bearer ${p.service_key}` } });
234
- const data = await res.json();
235
- if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
236
- console.log(JSON.stringify(data, null, 2));
261
+ try {
262
+ const data = await getSdk().projects.getUsage(projectId);
263
+ console.log(JSON.stringify(data, null, 2));
264
+ } catch (err) {
265
+ reportSdkError(err);
266
+ }
237
267
  }
238
268
 
239
269
  async function schema(projectId) {
240
- const p = findProject(projectId);
241
- const res = await fetch(`${API}/projects/v1/admin/${projectId}/schema`, { headers: { "Authorization": `Bearer ${p.service_key}` } });
242
- const data = await res.json();
243
- if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
244
- console.log(JSON.stringify(data, null, 2));
270
+ try {
271
+ const data = await getSdk().projects.getSchema(projectId);
272
+ console.log(JSON.stringify(data, null, 2));
273
+ } catch (err) {
274
+ reportSdkError(err);
275
+ }
245
276
  }
246
277
 
247
278
  async function use(projectId) {
248
279
  if (!projectId) { console.error("Usage: run402 projects use <project_id>"); process.exit(1); }
249
- findProject(projectId); // verify it exists
250
- setActiveProjectId(projectId);
251
- console.log(JSON.stringify({ status: "ok", active_project_id: projectId }));
280
+ try {
281
+ await getSdk().projects.use(projectId);
282
+ console.log(JSON.stringify({ status: "ok", active_project_id: projectId }));
283
+ } catch (err) {
284
+ reportSdkError(err);
285
+ }
252
286
  }
253
287
 
254
288
  async function pin(projectId) {
255
289
  if (!projectId) { console.error(JSON.stringify({ status: "error", message: "Usage: run402 projects pin <project_id>" })); process.exit(1); }
256
- const p = findProject(projectId);
257
- const res = await fetch(`${API}/projects/v1/admin/${projectId}/pin`, {
258
- method: "POST",
259
- headers: { "Authorization": `Bearer ${p.service_key}` },
260
- });
261
- const data = await res.json();
262
- if (!res.ok) { console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1); }
263
- console.log(JSON.stringify(data, null, 2));
290
+ try {
291
+ const data = await getSdk().projects.pin(projectId);
292
+ console.log(JSON.stringify(data, null, 2));
293
+ } catch (err) {
294
+ reportSdkError(err);
295
+ }
264
296
  }
265
297
 
266
298
  async function promoteUser(projectId, email) {
@@ -290,14 +322,11 @@ async function demoteUser(projectId, email) {
290
322
  }
291
323
 
292
324
  async function deleteProject(projectId) {
293
- const p = findProject(projectId);
294
- const res = await fetch(`${API}/projects/v1/${projectId}`, { method: "DELETE", headers: { "Authorization": `Bearer ${p.service_key}` } });
295
- if (res.status === 204 || res.ok) {
296
- removeProject(projectId);
325
+ try {
326
+ await getSdk().projects.delete(projectId);
297
327
  console.log(JSON.stringify({ status: "ok", message: `Project ${projectId} deleted.` }));
298
- } else {
299
- const data = await res.json().catch(() => ({}));
300
- console.error(JSON.stringify({ status: "error", http: res.status, ...data })); process.exit(1);
328
+ } catch (err) {
329
+ reportSdkError(err);
301
330
  }
302
331
  }
303
332
 
@@ -335,6 +364,8 @@ export async function run(sub, args) {
335
364
  case "usage": { const { projectId } = resolvePositionalProject(args); await usage(projectId); break; }
336
365
  case "schema": { const { projectId } = resolvePositionalProject(args); await schema(projectId); break; }
337
366
  case "rls": { const { projectId, rest } = resolvePositionalProject(args); await rls(projectId, rest[0], rest[1]); break; }
367
+ case "apply-expose": { const { projectId, rest } = resolvePositionalProject(args); await applyExpose(projectId, rest); break; }
368
+ case "get-expose": { const { projectId } = resolvePositionalProject(args); await getExpose(projectId); break; }
338
369
  case "delete": { const { projectId } = resolvePositionalProject(args); await deleteProject(projectId); break; }
339
370
  case "pin": { const { projectId } = resolvePositionalProject(args); await pin(projectId); break; }
340
371
  case "promote-user": { const { projectId, rest } = resolvePositionalProject(args); await promoteUser(projectId, rest[0]); break; }