@supacloud/cli 0.1.0 → 0.3.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.
Files changed (3) hide show
  1. package/README.md +20 -0
  2. package/dist/index.js +267 -70
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -14,9 +14,29 @@ npx @supacloud/cli status
14
14
  npx @supacloud/cli project get
15
15
  npx @supacloud/cli project logs --log_type database
16
16
  npx @supacloud/cli database query --sql "select now()"
17
+ npx @supacloud/cli database query --ref abc123 --file ./queries/vector-search.sql
18
+ npx @supacloud/cli database push_migrations --ref abc123 --dir supabase/migrations --dry_run
17
19
  npx @supacloud/cli frontend list --ref abc123
18
20
  ```
19
21
 
22
+ Use `database query --file` for complex SQL, pgvector queries, and single-request transaction blocks.
23
+
24
+ ```sql
25
+ CREATE EXTENSION IF NOT EXISTS vector;
26
+
27
+ CREATE TABLE documents (
28
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
29
+ content text NOT NULL,
30
+ embedding vector(1536)
31
+ );
32
+
33
+ CREATE INDEX documents_embedding_hnsw_idx
34
+ ON documents
35
+ USING hnsw (embedding vector_cosine_ops);
36
+ ```
37
+
38
+ Transaction boundary: SupaCloud supports transaction blocks inside one SQL request and transactional migrations. It does not expose long-lived HTTP transaction sessions; use a direct Postgres DSN for application-side long transactions.
39
+
20
40
  Project commands owned by this CLI:
21
41
 
22
42
  - `project get`
package/dist/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { createRequire } from "node:module";
3
2
  var __defProp = Object.defineProperty;
4
3
  var __returnValue = (v) => v;
5
4
  function __exportSetter(name, newValue) {
@@ -14,7 +13,6 @@ var __export = (target, all) => {
14
13
  set: __exportSetter.bind(all, name)
15
14
  });
16
15
  };
17
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
18
16
 
19
17
  // node_modules/zod/v3/external.js
20
18
  var exports_external = {};
@@ -4347,20 +4345,57 @@ class HttpTransport {
4347
4345
  }
4348
4346
 
4349
4347
  // src/shared/tools/database-tools.ts
4350
- function normalizeSqlResponse(result) {
4351
- if (!result.ok)
4352
- return result;
4353
- const data = result.data;
4354
- if (Array.isArray(data?.result) && !Array.isArray(data.rows)) {
4355
- return {
4356
- ...result,
4357
- data: {
4358
- rows: data.result,
4359
- rowCount: data.result.length
4360
- }
4361
- };
4362
- }
4363
- return result;
4348
+ import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
4349
+ import { basename, join } from "node:path";
4350
+ function migrationVersionFromFilename(file, fallbackIndex = 0) {
4351
+ const match = basename(file).match(/^(\d{8,20})[_-]/);
4352
+ if (match)
4353
+ return Number(match[1]);
4354
+ return Date.now() * 1000 + fallbackIndex;
4355
+ }
4356
+ function appliedMigrationKeys(data) {
4357
+ const rows = Array.isArray(data) ? data : data?.rows || [];
4358
+ const keys = new Set;
4359
+ for (const row of rows) {
4360
+ if (!row || typeof row !== "object")
4361
+ continue;
4362
+ const migration = row;
4363
+ if (migration.version != null)
4364
+ keys.add(String(migration.version));
4365
+ if (migration.name != null)
4366
+ keys.add(String(migration.name));
4367
+ }
4368
+ return keys;
4369
+ }
4370
+ function sqlReferencesVector(sql) {
4371
+ return /\bvector\s*\(\s*\d+\s*\)/i.test(sql) || /::\s*vector\b/i.test(sql) || /\bvector_(cosine|l2|ip)_ops\b/i.test(sql) || /<=>|<#>|<->/.test(sql);
4372
+ }
4373
+ function sqlCreatesVectorExtension(sql) {
4374
+ return /\bCREATE\s+EXTENSION\s+(?:IF\s+NOT\s+EXISTS\s+)?["']?vector["']?/i.test(sql);
4375
+ }
4376
+ function extensionRows(data) {
4377
+ if (Array.isArray(data))
4378
+ return data;
4379
+ const shaped = data;
4380
+ const rows = shaped?.rows || [];
4381
+ return Array.isArray(rows) ? rows : [];
4382
+ }
4383
+ function vectorWarningsForPendingMigrations(migrations, vectorEnabled) {
4384
+ const warnings = [];
4385
+ const vectorUsers = migrations.filter(({ sql }) => sqlReferencesVector(sql));
4386
+ const vectorCreators = migrations.filter(({ sql }) => sqlCreatesVectorExtension(sql));
4387
+ if (!vectorUsers.length && !vectorCreators.length)
4388
+ return warnings;
4389
+ if (vectorEnabled === false && vectorUsers.length && !vectorCreators.length) {
4390
+ warnings.push(`vector extension is not enabled, but pending migrations use vector types/operators: ${vectorUsers.map(({ file }) => file).join(", ")}`);
4391
+ }
4392
+ if (vectorEnabled === false && vectorCreators.length) {
4393
+ warnings.push(`pending migrations will enable pgvector: ${vectorCreators.map(({ file }) => file).join(", ")}`);
4394
+ }
4395
+ if (vectorEnabled === null) {
4396
+ warnings.push("could not verify whether vector extension is enabled");
4397
+ }
4398
+ return warnings;
4364
4399
  }
4365
4400
  function registerDatabaseTools(server, http, config = {}) {
4366
4401
  const { readOnly = false, projectRef } = config;
@@ -4382,7 +4417,7 @@ function registerDatabaseTools(server, http, config = {}) {
4382
4417
  "project_url",
4383
4418
  "generate_types"
4384
4419
  ];
4385
- const writeActions = ["apply_migration", "create_table_rls"];
4420
+ const writeActions = ["apply_migration", "push_migrations", "create_table_rls"];
4386
4421
  const allActions = readOnly ? actions : [...actions, ...writeActions];
4387
4422
  server.tool("database", `Database operations: query, schema, RLS, migrations, stats.
4388
4423
  Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
@@ -4390,6 +4425,8 @@ Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
4390
4425
  ref: projectRef ? exports_external.string().optional() : exports_external.string().optional().describe("Project ref"),
4391
4426
  sql: exports_external.string().optional().describe("[query/apply_migration] SQL statement"),
4392
4427
  file: exports_external.string().optional().describe("[query/apply_migration] Read SQL from local file path (avoids shell escaping issues with $$ and multi-statement DDL)"),
4428
+ dir: exports_external.string().optional().describe("[push_migrations] Directory containing .sql migration files (default: supabase/migrations)"),
4429
+ dry_run: exports_external.boolean().optional().describe("[push_migrations] List pending migration files without applying them"),
4393
4430
  schema: exports_external.string().optional().describe("[*] Schema name (default: public)"),
4394
4431
  table: exports_external.string().optional().describe("[describe_columns/indexes/constraints/rls_*] Table name"),
4395
4432
  schemas: exports_external.array(exports_external.string()).optional().describe("[list_tables/generate_types] Schemas array"),
@@ -4404,12 +4441,12 @@ Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
4404
4441
  const schemas = args.schemas || ["public"];
4405
4442
  if (args.file && !args.sql) {
4406
4443
  try {
4407
- args.sql = __require("fs").readFileSync(args.file, "utf-8");
4444
+ args.sql = readFileSync2(args.file, "utf-8");
4408
4445
  } catch (e) {
4409
4446
  return { content: [{ type: "text", text: `❌ Failed to read file ${args.file}: ${e.message}` }] };
4410
4447
  }
4411
4448
  }
4412
- const execSql = async (sql) => normalizeSqlResponse(await http.post(`/v1/projects/${ref}/database/sql`, { sql }));
4449
+ const execSql = async (sql) => http.post(`/v1/projects/${ref}/database/sql`, { sql });
4413
4450
  let text;
4414
4451
  switch (action) {
4415
4452
  case "query": {
@@ -4532,6 +4569,85 @@ Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
4532
4569
  text = r.ok ? `✅ Migration '${args.name}' applied` : `❌ Failed (${r.status}): ${JSON.stringify(r.data)}`;
4533
4570
  break;
4534
4571
  }
4572
+ case "push_migrations": {
4573
+ const dir = args.dir || "supabase/migrations";
4574
+ if (!existsSync2(dir) || !statSync(dir).isDirectory()) {
4575
+ throw new Error(`Migration directory not found: ${dir}`);
4576
+ }
4577
+ const files = readdirSync(dir).filter((file) => file.endsWith(".sql")).sort();
4578
+ if (!files.length) {
4579
+ text = `No .sql migration files found in ${dir}`;
4580
+ break;
4581
+ }
4582
+ const migrationFiles = files.map((file, i) => ({
4583
+ file,
4584
+ name: basename(file, ".sql"),
4585
+ version: migrationVersionFromFilename(file, i)
4586
+ }));
4587
+ if (args.dry_run) {
4588
+ const r = await http.get(`/v1/projects/${ref}/database/migrations`);
4589
+ if (!r.ok) {
4590
+ text = `❌ Failed to load applied migrations (${r.status}): ${JSON.stringify(r.data)}`;
4591
+ break;
4592
+ }
4593
+ const appliedKeys = appliedMigrationKeys(r.data);
4594
+ const pending = migrationFiles.filter(({ name, version }) => !appliedKeys.has(name) && !appliedKeys.has(String(version)));
4595
+ const alreadyApplied = migrationFiles.filter(({ name, version }) => appliedKeys.has(name) || appliedKeys.has(String(version)));
4596
+ const pendingWithSql = pending.map((migration) => ({
4597
+ ...migration,
4598
+ sql: readFileSync2(join(dir, migration.file), "utf-8")
4599
+ }));
4600
+ let vectorEnabled = null;
4601
+ if (pendingWithSql.some(({ sql }) => sqlReferencesVector(sql) || sqlCreatesVectorExtension(sql))) {
4602
+ const extResult = await execSql("SELECT extname AS name FROM pg_extension WHERE extname = 'vector';");
4603
+ vectorEnabled = extResult.ok ? extensionRows(extResult.data).some((row) => row.name === "vector" || row.extname === "vector") : null;
4604
+ }
4605
+ const warnings = vectorWarningsForPendingMigrations(pendingWithSql, vectorEnabled);
4606
+ text = [
4607
+ `Migration dry run for ${dir}`,
4608
+ `Total: ${migrationFiles.length}`,
4609
+ "",
4610
+ "Pending:",
4611
+ ...pending.length ? pending.map(({ file, version }) => ` - ${file} (${version})`) : [" - none"],
4612
+ "",
4613
+ "Already applied:",
4614
+ ...alreadyApplied.length ? alreadyApplied.map(({ file, version }) => ` - ${file} (${version})`) : [" - none"],
4615
+ ...warnings.length ? ["", "Warnings:", ...warnings.map((warning) => ` - ${warning}`)] : []
4616
+ ].join(`
4617
+ `);
4618
+ break;
4619
+ }
4620
+ const applied = [];
4621
+ const skipped = [];
4622
+ for (const { file, name, version } of migrationFiles) {
4623
+ const sql = readFileSync2(join(dir, file), "utf-8");
4624
+ const r = await http.post(`/v1/projects/${ref}/database/migrations`, { name, sql, version });
4625
+ if (r.ok) {
4626
+ applied.push(file);
4627
+ } else if (r.status === 409) {
4628
+ skipped.push(file);
4629
+ } else {
4630
+ text = [
4631
+ `❌ Failed to apply ${file} (${r.status})`,
4632
+ JSON.stringify(r.data, null, 2),
4633
+ "",
4634
+ `Applied before failure: ${applied.length}`,
4635
+ `Skipped before failure: ${skipped.length}`
4636
+ ].join(`
4637
+ `);
4638
+ return { content: [{ type: "text", text }] };
4639
+ }
4640
+ }
4641
+ text = [
4642
+ `✅ Migration push completed for ${dir}`,
4643
+ `Applied: ${applied.length}`,
4644
+ `Skipped: ${skipped.length}`,
4645
+ ...applied.length ? ["", "Applied files:", ...applied.map((file) => ` - ${file}`)] : [],
4646
+ ...skipped.length ? ["", "Skipped files:", ...skipped.map((file) => ` - ${file}`)] : []
4647
+ ].join(`
4648
+ `);
4649
+ break;
4650
+ }
4535
4651
  case "create_table_rls": {
4536
4652
  if (!args.table || !args.columns)
4537
4653
  throw new Error("'table' and 'columns' required");
@@ -5028,69 +5144,125 @@ Actions: status, list_buckets, list_files, upload_base64, delete_file`, {
5028
5144
  }
5029
5145
 
5030
5146
  // src/shared/tools/advanced-tools.ts
5147
+ import { existsSync as existsSync3, mkdtempSync, readFileSync as readFileSync3, rmSync, statSync as statSync2, writeFileSync } from "node:fs";
5148
+ import { tmpdir } from "node:os";
5149
+ import { basename as basename2, join as join2, resolve as resolve2 } from "node:path";
5150
+ import { promisify } from "node:util";
5151
+ import { execFile } from "node:child_process";
5152
+ var execFileAsync = promisify(execFile);
5153
+ async function runBunBuild(args) {
5154
+ try {
5155
+ return await execFileAsync("bun", ["build", ...args], { maxBuffer: 10 * 1024 * 1024 });
5156
+ } catch (error) {
5157
+ const e = error;
5158
+ if (e.code === "ENOENT") {
5159
+ throw new Error("Bun is required for local edge function bundling. Install Bun or use deploy_bundle with explicit files.");
5160
+ }
5161
+ throw error;
5162
+ }
5163
+ }
5164
+ async function bundleEdgeFunctionPath(pathArg) {
5165
+ const entrypoint = resolveEntrypoint(pathArg);
5166
+ const tmpDir = mkdtempSync(join2(tmpdir(), "supacloud-edge-"));
5167
+ const outfile = join2(tmpDir, `${basename2(entrypoint).replace(/\.[^.]+$/, "") || "index"}.js`);
5168
+ try {
5169
+ const { stderr } = await runBunBuild([entrypoint, "--target", "bun", "--outfile", outfile]);
5170
+ if (!existsSync3(outfile))
5171
+ throw new Error(`Bundle failed: ${stderr}`);
5172
+ return readFileSync3(outfile, "utf-8");
5173
+ } finally {
5174
+ rmSync(tmpDir, { recursive: true, force: true });
5175
+ }
5176
+ }
5177
+ function resolveEntrypoint(pathArg) {
5178
+ const resolved = resolve2(pathArg);
5179
+ const stat = statSync2(resolved);
5180
+ if (!stat.isDirectory())
5181
+ return resolved;
5182
+ const entrypoint = join2(resolved, "index.ts");
5183
+ if (!existsSync3(entrypoint)) {
5184
+ throw new Error(`Directory provided but no index.ts found at ${entrypoint}`);
5185
+ }
5186
+ return entrypoint;
5187
+ }
5188
+ var backgroundRoutesSchema = exports_external.preprocess((value) => {
5189
+ if (typeof value !== "string")
5190
+ return value;
5191
+ const trimmed = value.trim();
5192
+ if (!trimmed)
5193
+ return [];
5194
+ if (!trimmed.startsWith("["))
5195
+ return trimmed.split(",").map((route) => route.trim()).filter(Boolean);
5196
+ try {
5197
+ return JSON.parse(trimmed);
5198
+ } catch {
5199
+ return [trimmed];
5200
+ }
5201
+ }, exports_external.array(exports_external.string()).optional().superRefine((routes, ctx) => {
5202
+ if (!routes)
5203
+ return;
5204
+ for (const route of routes) {
5205
+ if (route.trim().startsWith("[")) {
5206
+ ctx.addIssue({
5207
+ code: exports_external.ZodIssueCode.custom,
5208
+ message: "Invalid background_routes JSON array. Use a valid JSON array or comma-separated routes like /queue/*,/render/*."
5209
+ });
5210
+ return;
5211
+ }
5212
+ }
5213
+ }));
5031
5214
  function registerAdvancedTools(server, http) {
5032
5215
  server.tool("edge_functions", `Edge Function management (Deno/Bun serverless). Server auto-bundles dependencies.
5033
- Actions: list, deploy, deploy_bundle, source, delete, check`, {
5034
- action: exports_external.enum(["list", "deploy", "deploy_bundle", "source", "delete", "check"]).describe("Action"),
5216
+ Actions: list, deploy, deploy_bundle, config, source, delete, check`, {
5217
+ action: exports_external.enum(["list", "deploy", "deploy_bundle", "config", "source", "delete", "check"]).describe("Action"),
5035
5218
  ref: exports_external.string().describe("Project ref"),
5036
- slug: exports_external.string().optional().describe("[deploy/deploy_bundle/source/delete/check] Function name"),
5219
+ slug: exports_external.string().optional().describe("[deploy/deploy_bundle/config/source/delete/check] Function name"),
5037
5220
  code: exports_external.string().optional().describe("[deploy/check] Function source code (TypeScript)"),
5038
5221
  path: exports_external.string().optional().describe("[deploy/check] Local file path to read code from (alternative to code)"),
5039
5222
  files: exports_external.record(exports_external.string()).optional().describe("[deploy_bundle] File map: { 'index.ts': '...', '_shared/x.ts': '...' }"),
5040
5223
  entrypoint: exports_external.string().optional().describe("[deploy_bundle] Entrypoint file (default: index.ts)"),
5041
- minify: exports_external.boolean().optional().describe("[deploy/deploy_bundle] Minify bundle")
5224
+ minify: exports_external.boolean().optional().describe("[deploy/deploy_bundle] Minify bundle"),
5225
+ verify_jwt: exports_external.boolean().optional().describe("[deploy/deploy_bundle/config] Set JWT verification for this function"),
5226
+ background_routes: backgroundRoutesSchema.describe("[deploy/deploy_bundle/config] Background route paths; pass comma-separated or JSON array in CLI")
5042
5227
  }, async (args) => {
5043
- const { action, ref, slug, path: pathArg, files, entrypoint, minify } = args;
5228
+ const { action, ref, slug, path: pathArg, files, entrypoint, minify, verify_jwt, background_routes } = args;
5044
5229
  let code = args.code;
5045
5230
  const need = (f, v) => {
5046
5231
  if (!v)
5047
5232
  throw new Error(`'${f}' required for '${action}'`);
5048
5233
  };
5049
5234
  let text;
5235
+ const functionConfig = () => ({
5236
+ ...typeof verify_jwt === "boolean" ? { verify_jwt } : {},
5237
+ ...Array.isArray(background_routes) ? { background_routes } : {}
5238
+ });
5239
+ const hasFunctionConfig = () => Object.keys(functionConfig()).length > 0;
5240
+ const updateFunctionConfig = async () => {
5241
+ need("slug", slug);
5242
+ if (!hasFunctionConfig()) {
5243
+ throw new Error("'verify_jwt' or 'background_routes' required for 'config'");
5244
+ }
5245
+ const cr = await http.patch(`/v1/projects/${ref}/functions/${slug}/config`, functionConfig());
5246
+ return cr.ok ? `✅ Function ${slug} config updated
5247
+ ${JSON.stringify(cr.data, null, 2)}` : `❌ Config update failed (${cr.status}): ${JSON.stringify(cr.data)}`;
5248
+ };
5050
5249
  const checkSyntax = async (sourceCode) => {
5051
- const fs = __require("fs");
5052
- const os = __require("os");
5053
- const { promisify } = __require("util");
5054
- const execAsync = promisify(__require("child_process").exec);
5055
- const tmpFile = `${os.tmpdir()}/supacloud_edge_${Date.now()}.ts`;
5056
- fs.writeFileSync(tmpFile, sourceCode);
5250
+ const tmpDir = mkdtempSync(join2(tmpdir(), "supacloud-edge-check-"));
5251
+ const tmpFile = join2(tmpDir, "index.ts");
5252
+ writeFileSync(tmpFile, sourceCode);
5057
5253
  try {
5058
- await execAsync(`bun build ${tmpFile} --external='*'`);
5254
+ await runBunBuild([tmpFile, "--external", "*"]);
5059
5255
  return { ok: true };
5060
5256
  } catch (e) {
5061
- return { ok: false, err: e.stdout + `
5062
- ` + (e.stderr || e.message) };
5257
+ return { ok: false, err: `${e.stdout || ""}
5258
+ ${e.stderr || e.message}` };
5063
5259
  } finally {
5064
- try {
5065
- fs.unlinkSync(tmpFile);
5066
- } catch (e) {}
5260
+ rmSync(tmpDir, { recursive: true, force: true });
5067
5261
  }
5068
5262
  };
5069
5263
  if (pathArg && !code) {
5070
5264
  try {
5071
- const fs = __require("fs");
5072
- const os = __require("os");
5073
- const { promisify } = __require("util");
5074
- const execAsync = promisify(__require("child_process").exec);
5075
- const stat = fs.statSync(pathArg);
5076
- let entrypoint2 = pathArg;
5077
- if (stat.isDirectory()) {
5078
- entrypoint2 = `${pathArg}/index.ts`;
5079
- if (!fs.existsSync(entrypoint2)) {
5080
- throw new Error(`Directory provided but no index.ts found at ${entrypoint2}`);
5081
- }
5082
- }
5083
- const tmpOut = `${os.tmpdir()}/supacloud_bundled_${Date.now()}.js`;
5084
- try {
5085
- const { stderr } = await execAsync(`bun build ${entrypoint2} --target bun --outfile ${tmpOut}`);
5086
- if (!fs.existsSync(tmpOut))
5087
- throw new Error(`Bundle failed: ${stderr}`);
5088
- code = fs.readFileSync(tmpOut, "utf-8");
5089
- } finally {
5090
- try {
5091
- fs.unlinkSync(tmpOut);
5092
- } catch (e) {}
5093
- }
5265
+ code = await bundleEdgeFunctionPath(pathArg);
5094
5266
  } catch (e) {
5095
5267
  throw new Error(`Failed to bundle/read path ${pathArg}: ${e.message}`);
5096
5268
  }
@@ -5119,13 +5291,26 @@ ${deployCheck.err}`;
5119
5291
  break;
5120
5292
  }
5121
5293
  const dr = await http.post(`/v1/projects/${ref}/functions/${slug}`, { code, minify });
5122
- text = dr.ok ? `✅ Function ${slug} deployed` : `❌ Failed (${dr.status}): ${JSON.stringify(dr.data)}`;
5294
+ if (!dr.ok) {
5295
+ text = `❌ Failed (${dr.status}): ${JSON.stringify(dr.data)}`;
5296
+ break;
5297
+ }
5298
+ text = hasFunctionConfig() ? `✅ Function ${slug} deployed
5299
+ ${await updateFunctionConfig()}` : `✅ Function ${slug} deployed`;
5123
5300
  break;
5124
5301
  case "deploy_bundle":
5125
5302
  need("slug", slug);
5126
5303
  need("files", files);
5127
5304
  const br = await http.post(`/v1/projects/${ref}/functions/${slug}/bundle`, { files, entrypoint, minify });
5128
- text = br.ok ? `✅ Function ${slug} bundle deployed (${Object.keys(files).length} files)` : `❌ Failed (${br.status}): ${JSON.stringify(br.data)}`;
5305
+ if (!br.ok) {
5306
+ text = `❌ Failed (${br.status}): ${JSON.stringify(br.data)}`;
5307
+ break;
5308
+ }
5309
+ text = hasFunctionConfig() ? `✅ Function ${slug} bundle deployed (${Object.keys(files).length} files)
5310
+ ${await updateFunctionConfig()}` : `✅ Function ${slug} bundle deployed (${Object.keys(files).length} files)`;
5311
+ break;
5312
+ case "config":
5313
+ text = await updateFunctionConfig();
5129
5314
  break;
5130
5315
  case "source":
5131
5316
  need("slug", slug);
@@ -5230,7 +5415,8 @@ ${JSON.stringify(r.data, null, 2)}` : `❌ Failed (${r.status})`;
5230
5415
  }
5231
5416
 
5232
5417
  // src/shared/tools/frontend-tools.ts
5233
- import { basename } from "node:path";
5418
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
5419
+ import { basename as basename3 } from "node:path";
5234
5420
  function registerFrontendTools(server, http) {
5235
5421
  server.tool("frontend", `Frontend hosting (static sites & SSR). Supports: static, react, vue, svelte, sveltekit, nextjs, nuxt, astro.
5236
5422
  Actions: list, get, create, update, delete, deploy_git, deploy_upload, redeploy, build_logs, add_domain, remove_domain, set_env, list_frameworks, list_records`, {
@@ -5324,14 +5510,12 @@ Actions: list, get, create, update, delete, deploy_git, deploy_upload, redeploy,
5324
5510
  need("ref", ref);
5325
5511
  need("id", id);
5326
5512
  need("zip_path", zip_path);
5327
- const file = Bun.file(zip_path);
5328
- if (!await file.exists()) {
5513
+ if (!existsSync4(zip_path)) {
5329
5514
  throw new Error(`Zip file not found: ${zip_path}`);
5330
5515
  }
5516
+ const zipBuffer = readFileSync4(zip_path);
5331
5517
  const form = new FormData;
5332
- form.append("file", new File([await file.arrayBuffer()], basename(zip_path), {
5333
- type: "application/zip"
5334
- }));
5518
+ form.append("file", new Blob([zipBuffer], { type: "application/zip" }), basename3(zip_path));
5335
5519
  text = ok(await http.postMultipart(`/v1/projects/${ref}/frontend/deployments/${id}/deploy/upload`, form));
5336
5520
  break;
5337
5521
  case "redeploy":
@@ -5462,6 +5646,15 @@ function registerUserProjectCliTools(server, http, options = {}) {
5462
5646
  // src/index.ts
5463
5647
  var projectActionSchema = exports_external.enum(["get", "health", "logs", "api_keys", "settings", "tasks"]);
5464
5648
  var genericActionSchema = exports_external.string();
5649
+ function unwrapMcpSchema(schema) {
5650
+ if (schema && typeof schema === "object" && !Array.isArray(schema) && "args" in schema) {
5651
+ const argsSchema = schema.args;
5652
+ if (argsSchema && typeof argsSchema === "object" && !Array.isArray(argsSchema)) {
5653
+ return argsSchema;
5654
+ }
5655
+ }
5656
+ return schema;
5657
+ }
5465
5658
  function captureTools(register) {
5466
5659
  const tools = {};
5467
5660
  const server = {
@@ -5469,7 +5662,7 @@ function captureTools(register) {
5469
5662
  if (typeof schemaOrCallback === "function") {
5470
5663
  tools[name] = { schema: {}, callback: schemaOrCallback };
5471
5664
  } else {
5472
- tools[name] = { schema: schemaOrCallback, callback };
5665
+ tools[name] = { schema: unwrapMcpSchema(schemaOrCallback), callback };
5473
5666
  }
5474
5667
  }
5475
5668
  };
@@ -5506,7 +5699,10 @@ EXAMPLES
5506
5699
  supacloud project logs --log_type database
5507
5700
  supacloud frontend list --ref abc123
5508
5701
  supacloud database query --sql "select now()"
5702
+ supacloud database query --ref abc123 --file ./queries/vector-search.sql
5703
+ supacloud database push_migrations --ref abc123 --dir supabase/migrations --dry_run
5509
5704
  supacloud edge_functions deploy --ref abc123 --slug hello --path ./supabase/functions/hello
5705
+ supacloud edge_functions config --ref abc123 --slug hello --verify_jwt false --background_routes "/queue/*,/render/*"
5510
5706
 
5511
5707
  SEPARATE ADMIN CLI
5512
5708
 
@@ -5615,10 +5811,11 @@ function createCliTools() {
5615
5811
  assign(captureTools((server) => registerUserProjectCliTools(server, http, {
5616
5812
  projectRef: context.projectRef || undefined
5617
5813
  })));
5618
- assign(captureTools((server) => registerDatabaseTools(server, http, {
5814
+ const databaseTools = captureTools((server) => registerDatabaseTools(server, http, {
5619
5815
  projectRef: context.projectRef || undefined,
5620
5816
  readOnly: context.readOnly
5621
- })));
5817
+ }));
5818
+ assign(databaseTools);
5622
5819
  assign(captureTools((server) => registerAuthTools(server, http)));
5623
5820
  assign(captureTools((server) => registerStorageTools(server, http)));
5624
5821
  assign(captureTools((server) => registerAdvancedTools(server, http)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/cli",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Project-scoped CLI for SupaCloud users",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",