@supacloud/cli 0.32.0 → 0.34.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 +55 -0
  2. package/dist/index.js +820 -63
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -106,6 +106,32 @@ loopback development origins, with the default `:80` likewise omitted. Use
106
106
  `SUPACLOUD_PROJECT_REF` when it cannot be inferred from a managed
107
107
  `<ref>.api.*` application hostname.
108
108
 
109
+ ### Immutable frontend releases
110
+
111
+ The `frontend` command keeps the existing deployment, Git, and legacy ZIP
112
+ actions and also exposes the immutable prebuilt release workflow:
113
+
114
+ ```bash
115
+ supacloud-cli frontend list_releases --ref abc123 --id web
116
+ supacloud-cli frontend get_release --ref abc123 --id web --release_id <sha256>
117
+ supacloud-cli frontend upload_release --ref abc123 --id web --zip_path ./dist.zip
118
+ supacloud-cli frontend activate_release --ref abc123 --id web \
119
+ --release_id <sha256> \
120
+ --expected_active_release_id absent \
121
+ --expected_activation_id absent \
122
+ --mutation_id <retry-stable-uuid-v4>
123
+ ```
124
+
125
+ `upload_release` hashes and streams an existing regular ZIP file without
126
+ buffering the full archive. The Management API binds the upload to that SHA-256,
127
+ and the CLI reads the immutable release back before reporting success.
128
+ `activate_release` uses both the observed active release and activation IDs as
129
+ optimistic concurrency tokens, then verifies the authoritative active release.
130
+ Use the values returned by `list_releases`; `absent` is valid only when no
131
+ release has been activated. Production uploads and activations require the
132
+ normal exact `--confirm-production <ref>` value, and
133
+ `SUPACLOUD_READ_ONLY=true` blocks both mutations.
134
+
109
135
  ### Verified release controls
110
136
 
111
137
  `release` is an official CLI entry point for verified Management API controls
@@ -440,6 +466,35 @@ Use `--data_mode full_clone` only for an explicitly approved non-sensitive or
440
466
  masked debugging dataset. Whole-database replacement is an administrator-only
441
467
  break-glass API mode and is intentionally not exposed by this project CLI.
442
468
 
469
+ ## SupaCloud Lite CLI adapter
470
+
471
+ Lite can be used from its standalone `supacloud-lite` CLI and from the main
472
+ `supacloud-cli` through the local-only `lite` module. The adapter never calls
473
+ the Management API, never invokes the official Supabase CLI, and never treats a
474
+ PGlite data directory as a Postgres DSN.
475
+
476
+ ```bash
477
+ supacloud-cli lite migrate --project_dir .
478
+ supacloud-cli lite status --project_dir .
479
+ supacloud-cli lite db_diff --project_dir . --file add_accounts
480
+ supacloud-cli lite db_pull --project_dir . --file remote_schema
481
+ supacloud-cli lite gen_types --project_dir . --output src/database.types.ts
482
+ supacloud-cli lite snapshot_create --project_dir . --output backups/lite.tar.gz
483
+ supacloud-cli lite doctor --project_dir . --json
484
+ supacloud-cli lite start --project_dir . --port 54321
485
+ ```
486
+
487
+ The adapter resolves the executable in this order:
488
+
489
+ 1. `SUPACLOUD_LITE_CLI_BIN`
490
+ 2. `<workdir>/node_modules/@supacloud/lite/dist/launcher.cjs`
491
+ 3. `supacloud-lite` on `PATH`
492
+
493
+ Install `@supacloud/lite` or provide an explicit binary before using the
494
+ adapter. Lite actions are local-only, so Management API context and project
495
+ refs are not required. The `supabase` module remains the official CLI adapter;
496
+ use it for upstream Supabase CLI actions and Management-backed remote pushes.
497
+
443
498
  ## Official Supabase CLI adapter
444
499
 
445
500
  The `supabase` command group is a thin, allowlisted adapter around the official
package/dist/index.js CHANGED
@@ -6471,6 +6471,24 @@ var ACTION_POLICY = {
6471
6471
  local: ["version", "migration_new", "db_diff", "db_reset", "db_pull", "db_dump", "migration_list", "gen_types"],
6472
6472
  write: ["push"]
6473
6473
  },
6474
+ lite: {
6475
+ local: [
6476
+ "version",
6477
+ "start",
6478
+ "migrate",
6479
+ "status",
6480
+ "keys",
6481
+ "gen_types",
6482
+ "db_reset",
6483
+ "db_diff",
6484
+ "db_pull",
6485
+ "snapshot_create",
6486
+ "snapshot_restore",
6487
+ "upgrade",
6488
+ "inspect",
6489
+ "doctor"
6490
+ ]
6491
+ },
6474
6492
  auth: {
6475
6493
  read: ["list_users", "get_user", "list_providers", "get_provider", "supported_providers", "get_settings", "get_config", "get_oauth_server"],
6476
6494
  write: ["generate_link", "configure_provider", "update_provider", "disable_provider", "wechat_mini", "wechat_open", "update_settings", "update_config", "migrate_oauth_server"]
@@ -6496,8 +6514,8 @@ var ACTION_POLICY = {
6496
6514
  },
6497
6515
  secrets: { read: ["list"], write: ["upsert", "delete"] },
6498
6516
  frontend: {
6499
- read: ["list", "get", "build_logs", "list_frameworks", "list_records"],
6500
- write: ["create", "update", "delete", "deploy_git", "deploy_upload", "redeploy", "add_domain", "remove_domain", "set_env"]
6517
+ read: ["list", "get", "build_logs", "list_frameworks", "list_records", "list_releases", "get_release"],
6518
+ write: ["create", "update", "delete", "deploy_git", "deploy_upload", "redeploy", "add_domain", "remove_domain", "set_env", "upload_release", "activate_release"]
6501
6519
  },
6502
6520
  task_events: { read: ["inspect_webhook"], write: ["register_webhook", "unregister_webhook"] },
6503
6521
  diagnostics: { read: ["list_checks", "get_run"], write: ["run_checks", "repair"] },
@@ -6857,6 +6875,7 @@ class HttpTransport {
6857
6875
  async post(path, body, options) {
6858
6876
  const timeoutMs = validatedPostTimeout(options);
6859
6877
  const maxJsonBytes = validatedJsonResponseLimit(options?.maxJsonBytes);
6878
+ const responseTimeoutMs = validatedResponseTimeout(options?.responseTimeoutMs);
6860
6879
  try {
6861
6880
  if (maxJsonBytes === undefined) {
6862
6881
  return await this.mutationWithResponseReader("POST", path, serializedRequestBody(body), responseJsonOrNull, timeoutMs);
@@ -6866,7 +6885,7 @@ class HttpTransport {
6866
6885
  headers: this.headers(),
6867
6886
  body: serializedRequestBody(body)
6868
6887
  }, timeoutMs);
6869
- const data = await boundedResponseJson(response, maxJsonBytes);
6888
+ const data = await boundedResponseJson(response, maxJsonBytes, responseTimeoutMs);
6870
6889
  return data === null ? responseReadFailure(response.status) : { ok: response.ok, status: response.status, data };
6871
6890
  } catch (error) {
6872
6891
  return transportFailure(error);
@@ -6876,6 +6895,39 @@ class HttpTransport {
6876
6895
  const timeoutMs = validatedPostTimeout(options);
6877
6896
  return this.mutationWithResponseReader("POST", path, serializedRequestBody(body), releaseMutationResponseJson, timeoutMs);
6878
6897
  }
6898
+ async postBinary(path, body, options) {
6899
+ if (options.contentType !== "application/zip") {
6900
+ throw new Error("Binary HTTP content type is invalid");
6901
+ }
6902
+ if (!Number.isSafeInteger(options.contentLength) || options.contentLength < 1 || options.contentLength !== body.byteLength) {
6903
+ throw new RangeError("Binary HTTP body length is invalid");
6904
+ }
6905
+ if (!/^[0-9a-f]{64}$/u.test(options.contentSha256)) {
6906
+ throw new Error("Binary HTTP body SHA-256 is invalid");
6907
+ }
6908
+ const maxJsonBytes = validatedJsonResponseLimit(options.maxJsonBytes);
6909
+ const timeoutMs = validatedPostTimeout(options);
6910
+ const responseTimeoutMs = validatedResponseTimeout(options.responseTimeoutMs);
6911
+ try {
6912
+ const request = {
6913
+ method: "POST",
6914
+ headers: {
6915
+ Authorization: `Bearer ${this.token}`,
6916
+ "Content-Type": options.contentType,
6917
+ "Content-Length": String(options.contentLength),
6918
+ "x-supacloud-content-sha256": options.contentSha256,
6919
+ ...this.apiKey ? { apikey: this.apiKey } : {}
6920
+ },
6921
+ body: body.stream,
6922
+ duplex: "half"
6923
+ };
6924
+ const response = await fetchWithRetry(`${this.baseUrl}${path}`, request, timeoutMs);
6925
+ const data = await boundedResponseJson(response, maxJsonBytes, responseTimeoutMs);
6926
+ return data === null ? responseReadFailure(response.status) : { ok: response.ok, status: response.status, data };
6927
+ } catch (error) {
6928
+ return transportFailure(error);
6929
+ }
6930
+ }
6879
6931
  async patchReleaseMutation(path, body) {
6880
6932
  return this.mutationWithResponseReader("PATCH", path, serializedRequestBody(body), releaseMutationResponseJson);
6881
6933
  }
@@ -11016,9 +11068,418 @@ ${JSON.stringify(r.data, null, 2)}` : `❌ Failed (${r.status}): ${JSON.stringif
11016
11068
  // src/shared/tools/frontend-tools.ts
11017
11069
  import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
11018
11070
  import { basename as basename3 } from "node:path";
11071
+
11072
+ // src/shared/tools/frontend-release-control.ts
11073
+ import { createHash as createHash3 } from "node:crypto";
11074
+ import { constants as fsConstants2 } from "node:fs";
11075
+ import { open } from "node:fs/promises";
11076
+ import { resolve as resolve3 } from "node:path";
11077
+ var PROJECT_REF_PATTERN3 = /^[A-Za-z0-9_-]{1,20}$/u;
11078
+ var DEPLOYMENT_ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/u;
11079
+ var RELEASE_ID_PATTERN = /^[0-9a-f]{64}$/u;
11080
+ var MUTATION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
11081
+ var TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u;
11082
+ var ARCHIVE_MAX_BYTES = 100 * 1024 * 1024;
11083
+ var ARCHIVE_CHUNK_BYTES = 64 * 1024;
11084
+ var RESPONSE_MAX_BYTES = 1024 * 1024;
11085
+ var UPLOAD_REQUEST_TIMEOUT_MS = 10 * 60000;
11086
+ var MUTATION_RESPONSE_TIMEOUT_MS = 5000;
11087
+ var RELEASE_LIST_LIMIT_MAX = 100;
11088
+ function toolResponse(payload) {
11089
+ return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
11090
+ }
11091
+ function releaseFailure(operation, code, status) {
11092
+ return {
11093
+ isError: true,
11094
+ content: [{
11095
+ type: "text",
11096
+ text: JSON.stringify({ ok: false, operation, error: { code, http_status: status } })
11097
+ }]
11098
+ };
11099
+ }
11100
+ function exactKeys(candidate, keys) {
11101
+ const actual = Object.keys(candidate).sort();
11102
+ const expected = [...keys].sort();
11103
+ return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
11104
+ }
11105
+ function releaseRecord(candidate) {
11106
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate))
11107
+ return null;
11108
+ const record = candidate;
11109
+ const keys = [
11110
+ "schema",
11111
+ "project_ref",
11112
+ "deployment_id",
11113
+ "release_id",
11114
+ "sha256",
11115
+ "tree_sha256",
11116
+ "size_bytes",
11117
+ "file_count",
11118
+ "created_at",
11119
+ "kind"
11120
+ ];
11121
+ if (!exactKeys(record, keys) || record.schema !== "supacloud.frontend-release.v1" || typeof record.project_ref !== "string" || !PROJECT_REF_PATTERN3.test(record.project_ref) || typeof record.deployment_id !== "string" || !DEPLOYMENT_ID_PATTERN.test(record.deployment_id) || typeof record.release_id !== "string" || !RELEASE_ID_PATTERN.test(record.release_id) || record.sha256 !== record.release_id || typeof record.tree_sha256 !== "string" || !RELEASE_ID_PATTERN.test(record.tree_sha256) || !Number.isSafeInteger(record.size_bytes) || Number(record.size_bytes) < 1 || !Number.isSafeInteger(record.file_count) || Number(record.file_count) < 1 || !canonicalTimestamp(record.created_at) || record.kind !== "prebuilt_static")
11122
+ return null;
11123
+ return {
11124
+ project_ref: record.project_ref,
11125
+ deployment_id: record.deployment_id,
11126
+ release_id: record.release_id,
11127
+ sha256: record.release_id,
11128
+ tree_sha256: record.tree_sha256,
11129
+ size_bytes: Number(record.size_bytes),
11130
+ file_count: Number(record.file_count),
11131
+ created_at: record.created_at,
11132
+ kind: "prebuilt_static"
11133
+ };
11134
+ }
11135
+ function releaseEnvelope(candidate, expected) {
11136
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate))
11137
+ return null;
11138
+ const envelope = candidate;
11139
+ const release = exactKeys(envelope, ["project_ref", "deployment_id", "release"]) ? releaseRecord(envelope.release) : null;
11140
+ if (!release || envelope.project_ref !== expected.projectRef || envelope.deployment_id !== expected.deploymentId || release.project_ref !== expected.projectRef || release.deployment_id !== expected.deploymentId || release.release_id !== expected.releaseId)
11141
+ return null;
11142
+ return release;
11143
+ }
11144
+ function canonicalTimestamp(candidate) {
11145
+ if (typeof candidate !== "string" || !TIMESTAMP_PATTERN.test(candidate))
11146
+ return false;
11147
+ const milliseconds = Date.parse(candidate);
11148
+ return Number.isFinite(milliseconds) && new Date(milliseconds).toISOString() === candidate;
11149
+ }
11150
+ function nullableIdentity(candidate, pattern) {
11151
+ if (candidate === null)
11152
+ return null;
11153
+ return typeof candidate === "string" && pattern.test(candidate) ? candidate : undefined;
11154
+ }
11155
+ function releaseInventory(candidate, expected) {
11156
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate))
11157
+ return null;
11158
+ const record = candidate;
11159
+ const keys = [
11160
+ "project_ref",
11161
+ "deployment_id",
11162
+ "active_release_id",
11163
+ "active_activation_id",
11164
+ "releases",
11165
+ "next_cursor"
11166
+ ];
11167
+ if (!exactKeys(record, keys) || typeof record.project_ref !== "string" || !PROJECT_REF_PATTERN3.test(record.project_ref) || typeof record.deployment_id !== "string" || !DEPLOYMENT_ID_PATTERN.test(record.deployment_id) || !Array.isArray(record.releases))
11168
+ return null;
11169
+ const activeReleaseId = nullableIdentity(record.active_release_id, RELEASE_ID_PATTERN);
11170
+ const activeActivationId = nullableIdentity(record.active_activation_id, MUTATION_ID_PATTERN);
11171
+ const nextCursor = nullableIdentity(record.next_cursor, RELEASE_ID_PATTERN);
11172
+ const releases = record.releases.map(releaseRecord);
11173
+ if (activeReleaseId === undefined || activeActivationId === undefined || nextCursor === undefined || activeReleaseId === null !== (activeActivationId === null) || releases.some((release) => release === null))
11174
+ return null;
11175
+ const verified = releases;
11176
+ if (new Set(verified.map((release) => release.release_id)).size !== verified.length || verified.some((release) => release.project_ref !== record.project_ref || release.deployment_id !== record.deployment_id) || expected && (record.project_ref !== expected.projectRef || record.deployment_id !== expected.deploymentId))
11177
+ return null;
11178
+ return {
11179
+ project_ref: record.project_ref,
11180
+ deployment_id: record.deployment_id,
11181
+ active_release_id: activeReleaseId,
11182
+ active_activation_id: activeActivationId,
11183
+ releases: verified,
11184
+ next_cursor: nextCursor
11185
+ };
11186
+ }
11187
+ function releaseEndpoint(projectRef2, deploymentId) {
11188
+ if (!PROJECT_REF_PATTERN3.test(projectRef2))
11189
+ throw new Error("'ref' is invalid for frontend releases");
11190
+ if (!DEPLOYMENT_ID_PATTERN.test(deploymentId))
11191
+ throw new Error("'id' is invalid for frontend releases");
11192
+ return `/v1/projects/${encodeURIComponent(projectRef2)}/frontend/deployments/${encodeURIComponent(deploymentId)}/releases`;
11193
+ }
11194
+ function releasePath(projectRef2, deploymentId, releaseId) {
11195
+ if (!RELEASE_ID_PATTERN.test(releaseId))
11196
+ throw new Error("'release_id' must be a SHA-256 digest");
11197
+ return `${releaseEndpoint(projectRef2, deploymentId)}/${releaseId}`;
11198
+ }
11199
+ function sameArchiveIdentity(left, right) {
11200
+ return left.dev === right.dev && left.ino === right.ino && left.size === right.size && left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs;
11201
+ }
11202
+ async function archiveSha256(handle, sizeBytes) {
11203
+ const hash2 = createHash3("sha256");
11204
+ const chunk = new Uint8Array(Math.min(sizeBytes, ARCHIVE_CHUNK_BYTES));
11205
+ for (let offset = 0;offset < sizeBytes; ) {
11206
+ const requested = Math.min(chunk.byteLength, sizeBytes - offset);
11207
+ const { bytesRead } = await handle.read(chunk, 0, requested, offset);
11208
+ if (bytesRead < 1)
11209
+ throw new Error("Frontend release archive changed while it was hashed");
11210
+ hash2.update(chunk.subarray(0, bytesRead));
11211
+ offset += bytesRead;
11212
+ }
11213
+ return hash2.digest("hex");
11214
+ }
11215
+ async function verifiedArchive(path) {
11216
+ const archivePath = resolve3(path);
11217
+ const handle = await open(archivePath, fsConstants2.O_RDONLY | fsConstants2.O_NOFOLLOW);
11218
+ try {
11219
+ const before = await handle.stat({ bigint: true });
11220
+ const sizeBytes = Number(before.size);
11221
+ if (!before.isFile() || sizeBytes < 1 || sizeBytes > ARCHIVE_MAX_BYTES) {
11222
+ throw new Error(`Frontend release archive must be a 1-${ARCHIVE_MAX_BYTES} byte regular file`);
11223
+ }
11224
+ const sha256 = await archiveSha256(handle, sizeBytes);
11225
+ const after = await handle.stat({ bigint: true });
11226
+ if (!sameArchiveIdentity(before, after)) {
11227
+ throw new Error("Frontend release archive identity changed while it was hashed");
11228
+ }
11229
+ return { handle, sizeBytes, sha256 };
11230
+ } catch (error) {
11231
+ await handle.close();
11232
+ throw error;
11233
+ }
11234
+ }
11235
+ function archiveStream(archive) {
11236
+ let offset = 0;
11237
+ return new ReadableStream({
11238
+ async pull(controller) {
11239
+ if (offset === archive.sizeBytes) {
11240
+ controller.close();
11241
+ return;
11242
+ }
11243
+ const length = Math.min(ARCHIVE_CHUNK_BYTES, archive.sizeBytes - offset);
11244
+ const chunk = new Uint8Array(length);
11245
+ const { bytesRead } = await archive.handle.read(chunk, 0, length, offset);
11246
+ if (bytesRead < 1)
11247
+ throw new Error("Frontend release archive changed while it was uploaded");
11248
+ offset += bytesRead;
11249
+ controller.enqueue(bytesRead === length ? chunk : chunk.subarray(0, bytesRead));
11250
+ }
11251
+ });
11252
+ }
11253
+ function releaseReadFailure(operation, response) {
11254
+ return releaseFailure(operation, response.ok ? "INVALID_RESPONSE" : "HTTP_ERROR", response.status);
11255
+ }
11256
+ async function listFrontendReleases(http, projectRef2, deploymentId, cursor, limit = 50) {
11257
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > RELEASE_LIST_LIMIT_MAX) {
11258
+ throw new Error("'limit' must be 1-100");
11259
+ }
11260
+ if (cursor !== undefined && !RELEASE_ID_PATTERN.test(cursor))
11261
+ throw new Error("'cursor' is invalid");
11262
+ const query = new URLSearchParams({ limit: String(limit) });
11263
+ if (cursor)
11264
+ query.set("cursor", cursor);
11265
+ const response = await http.get(`${releaseEndpoint(projectRef2, deploymentId)}?${query}`, {
11266
+ maxJsonBytes: RESPONSE_MAX_BYTES
11267
+ });
11268
+ const inventory = response.ok ? releaseInventory(response.data, { projectRef: projectRef2, deploymentId }) : null;
11269
+ if (!inventory || inventory.releases.length > limit) {
11270
+ return releaseReadFailure("frontend.list_releases", response);
11271
+ }
11272
+ return toolResponse(inventory);
11273
+ }
11274
+ async function getFrontendRelease(http, projectRef2, deploymentId, releaseId) {
11275
+ const response = await http.get(releasePath(projectRef2, deploymentId, releaseId), {
11276
+ maxJsonBytes: RESPONSE_MAX_BYTES
11277
+ });
11278
+ const release = response.ok ? releaseEnvelope(response.data, { projectRef: projectRef2, deploymentId, releaseId }) : null;
11279
+ if (!release) {
11280
+ return releaseReadFailure("frontend.get_release", response);
11281
+ }
11282
+ return toolResponse({ project_ref: projectRef2, deployment_id: deploymentId, release });
11283
+ }
11284
+ async function uploadFrontendRelease(http, projectRef2, deploymentId, archivePath) {
11285
+ const endpoint = releaseEndpoint(projectRef2, deploymentId);
11286
+ const archive = await verifiedArchive(archivePath);
11287
+ let response;
11288
+ try {
11289
+ response = await http.postBinary(endpoint, {
11290
+ stream: archiveStream(archive),
11291
+ byteLength: archive.sizeBytes
11292
+ }, {
11293
+ contentType: "application/zip",
11294
+ contentLength: archive.sizeBytes,
11295
+ contentSha256: archive.sha256,
11296
+ maxJsonBytes: RESPONSE_MAX_BYTES,
11297
+ timeoutMs: UPLOAD_REQUEST_TIMEOUT_MS,
11298
+ responseTimeoutMs: MUTATION_RESPONSE_TIMEOUT_MS
11299
+ });
11300
+ } finally {
11301
+ await archive.handle.close();
11302
+ }
11303
+ const release = response.ok ? releaseEnvelope(response.data, { projectRef: projectRef2, deploymentId, releaseId: archive.sha256 }) : null;
11304
+ if (!release) {
11305
+ if (response.status >= 400 && response.status < 500 && response.status !== 408 && !response.transportError) {
11306
+ return releaseFailure("frontend.upload_release", "HTTP_ERROR", response.status);
11307
+ }
11308
+ return uploadReadback(http, projectRef2, deploymentId, archive.sha256, response.status);
11309
+ }
11310
+ const readback = await http.get(`${endpoint}/${release.release_id}`, { maxJsonBytes: RESPONSE_MAX_BYTES });
11311
+ const verified = readback.ok ? releaseEnvelope(readback.data, { projectRef: projectRef2, deploymentId, releaseId: release.release_id }) : null;
11312
+ if (!verified || verified.tree_sha256 !== release.tree_sha256) {
11313
+ return releaseFailure("frontend.upload_release", "OUTCOME_UNKNOWN", readback.status);
11314
+ }
11315
+ return toolResponse({ project_ref: projectRef2, deployment_id: deploymentId, release: verified });
11316
+ }
11317
+ async function uploadReadback(http, projectRef2, deploymentId, releaseId, uploadStatus) {
11318
+ const response = await http.get(releasePath(projectRef2, deploymentId, releaseId), {
11319
+ maxJsonBytes: RESPONSE_MAX_BYTES
11320
+ });
11321
+ const release = response.ok ? releaseEnvelope(response.data, { projectRef: projectRef2, deploymentId, releaseId }) : null;
11322
+ if (!release) {
11323
+ return releaseFailure("frontend.upload_release", "OUTCOME_UNKNOWN", uploadStatus);
11324
+ }
11325
+ return toolResponse({ project_ref: projectRef2, deployment_id: deploymentId, release });
11326
+ }
11327
+ function stableJson(candidate) {
11328
+ if (candidate === null || typeof candidate !== "object")
11329
+ return JSON.stringify(candidate);
11330
+ if (Array.isArray(candidate))
11331
+ return `[${candidate.map(stableJson).join(",")}]`;
11332
+ const record = candidate;
11333
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`).join(",")}}`;
11334
+ }
11335
+ function activationFingerprint(identity) {
11336
+ return createHash3("sha256").update(stableJson({
11337
+ project_ref: identity.projectRef,
11338
+ deployment_id: identity.deploymentId,
11339
+ release_id: identity.releaseId,
11340
+ expected_active_release_id: identity.expectedActiveReleaseId,
11341
+ activation_id: identity.mutationId,
11342
+ expected_activation_id: identity.expectedActivationId
11343
+ })).digest("hex");
11344
+ }
11345
+ function activationResourceKey(deploymentId) {
11346
+ return `v1/frontend_release/${Buffer.from(deploymentId, "utf8").toString("base64url")}`;
11347
+ }
11348
+ function publicMutationReceipt(candidate, expected) {
11349
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate))
11350
+ return null;
11351
+ const envelope = candidate;
11352
+ const mutation = envelope.mutation;
11353
+ const expectedKeys = [
11354
+ "project_ref",
11355
+ "mutation_id",
11356
+ "operation",
11357
+ "resource_key",
11358
+ "request_fingerprint",
11359
+ "principal",
11360
+ "status",
11361
+ "checkpoint",
11362
+ "receipt",
11363
+ "response_status",
11364
+ "failure_code",
11365
+ "lease",
11366
+ "completed_at",
11367
+ "created_at",
11368
+ "updated_at"
11369
+ ];
11370
+ if (!mutation || !exactKeys(envelope, ["project_ref", "mutation"]) || !exactKeys(mutation, expectedKeys) || envelope.project_ref !== expected.projectRef || mutation.project_ref !== expected.projectRef || mutation.mutation_id !== expected.mutationId || mutation.resource_key !== expected.resourceKey || mutation.request_fingerprint !== expected.requestFingerprint || typeof mutation.operation !== "string" || typeof mutation.status !== "string" || !(mutation.response_status === null || Number.isSafeInteger(mutation.response_status)) || !(mutation.failure_code === null || typeof mutation.failure_code === "string"))
11371
+ return null;
11372
+ return {
11373
+ operation: mutation.operation,
11374
+ status: mutation.status,
11375
+ responseStatus: mutation.response_status,
11376
+ failureCode: mutation.failure_code
11377
+ };
11378
+ }
11379
+ function activationReceipt(candidate, input) {
11380
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate))
11381
+ return null;
11382
+ const record = candidate;
11383
+ const release = releaseRecord(record.release);
11384
+ const mutation = record.mutation;
11385
+ if (!release || !mutation || !exactKeys(record, [
11386
+ "project_ref",
11387
+ "deployment_id",
11388
+ "active_release_id",
11389
+ "activation_id",
11390
+ "release",
11391
+ "mutation"
11392
+ ]) || !exactKeys(mutation, ["mutation_id", "status", "replayed"]) || record.project_ref !== input.projectRef || record.deployment_id !== input.deploymentId || record.active_release_id !== input.releaseId || record.activation_id !== input.mutationId || release.project_ref !== input.projectRef || release.deployment_id !== input.deploymentId || release.release_id !== input.releaseId || mutation.mutation_id !== input.mutationId || mutation.status !== "succeeded" || typeof mutation.replayed !== "boolean")
11393
+ return null;
11394
+ return release;
11395
+ }
11396
+ async function activeReleaseReadback(http, identity) {
11397
+ const endpoint = releaseEndpoint(identity.projectRef, identity.deploymentId);
11398
+ const inventoryRead = await http.get(`${endpoint}?limit=${RELEASE_LIST_LIMIT_MAX}`, {
11399
+ maxJsonBytes: RESPONSE_MAX_BYTES
11400
+ });
11401
+ const inventory = inventoryRead.ok ? releaseInventory(inventoryRead.data, {
11402
+ projectRef: identity.projectRef,
11403
+ deploymentId: identity.deploymentId
11404
+ }) : null;
11405
+ if (!inventory || inventory.releases.length > RELEASE_LIST_LIMIT_MAX || inventory.active_release_id !== identity.releaseId || inventory.active_activation_id !== identity.mutationId) {
11406
+ return { release: null, status: inventoryRead.status };
11407
+ }
11408
+ const releaseRead = await http.get(releasePath(identity.projectRef, identity.deploymentId, identity.releaseId), { maxJsonBytes: RESPONSE_MAX_BYTES });
11409
+ const release = releaseRead.ok ? releaseEnvelope(releaseRead.data, {
11410
+ projectRef: identity.projectRef,
11411
+ deploymentId: identity.deploymentId,
11412
+ releaseId: identity.releaseId
11413
+ }) : null;
11414
+ return { release, status: releaseRead.status };
11415
+ }
11416
+ async function activateFrontendRelease(http, input) {
11417
+ const mutationId = input.mutationId;
11418
+ if (!MUTATION_ID_PATTERN.test(mutationId))
11419
+ throw new Error("'mutation_id' must be a UUIDv4");
11420
+ if (input.expectedActiveReleaseId !== "absent" && !RELEASE_ID_PATTERN.test(input.expectedActiveReleaseId)) {
11421
+ throw new Error("'expected_active_release_id' is invalid");
11422
+ }
11423
+ if (input.expectedActivationId !== "absent" && !MUTATION_ID_PATTERN.test(input.expectedActivationId)) {
11424
+ throw new Error("'expected_activation_id' is invalid");
11425
+ }
11426
+ const endpoint = `${releasePath(input.projectRef, input.deploymentId, input.releaseId)}/activate`;
11427
+ const response = await http.post(endpoint, {
11428
+ expected_active_release_id: input.expectedActiveReleaseId,
11429
+ expected_activation_id: input.expectedActivationId,
11430
+ mutation_id: mutationId
11431
+ }, {
11432
+ maxJsonBytes: RESPONSE_MAX_BYTES,
11433
+ responseTimeoutMs: MUTATION_RESPONSE_TIMEOUT_MS
11434
+ });
11435
+ const release = activationReceipt(response.data, { ...input, mutationId });
11436
+ if (!response.ok || !release) {
11437
+ if (response.status >= 400 && response.status < 500 && response.status !== 408 && !response.transportError) {
11438
+ return releaseFailure("frontend.activate_release", "HTTP_ERROR", response.status);
11439
+ }
11440
+ return activationReadback(http, input, mutationId, response.status);
11441
+ }
11442
+ const readback = await activeReleaseReadback(http, { ...input, mutationId });
11443
+ if (!readback.release || readback.release.tree_sha256 !== release.tree_sha256) {
11444
+ return releaseFailure("frontend.activate_release", "OUTCOME_UNKNOWN", readback.status);
11445
+ }
11446
+ return toolResponse({
11447
+ project_ref: input.projectRef,
11448
+ deployment_id: input.deploymentId,
11449
+ active_release_id: input.releaseId,
11450
+ activation_id: mutationId,
11451
+ release: readback.release
11452
+ });
11453
+ }
11454
+ async function activationReadback(http, input, mutationId, activationStatus) {
11455
+ const mutationRead = await http.get(`/v1/projects/${encodeURIComponent(input.projectRef)}/mutations/${mutationId}`, { maxJsonBytes: RESPONSE_MAX_BYTES });
11456
+ const mutationEnvelope = mutationRead.data;
11457
+ const mutation = mutationRead.ok ? publicMutationReceipt(mutationRead.data, {
11458
+ projectRef: input.projectRef,
11459
+ mutationId,
11460
+ resourceKey: activationResourceKey(input.deploymentId),
11461
+ requestFingerprint: activationFingerprint({ ...input, mutationId })
11462
+ }) : null;
11463
+ if (!mutation || mutationEnvelope?.project_ref !== input.projectRef || mutation.operation !== "frontend.release.activate" || mutation.status !== "succeeded" || mutation.responseStatus !== 200 || mutation.failureCode !== null) {
11464
+ return releaseFailure("frontend.activate_release", "OUTCOME_UNKNOWN", activationStatus);
11465
+ }
11466
+ const readback = await activeReleaseReadback(http, { ...input, mutationId });
11467
+ if (!readback.release) {
11468
+ return releaseFailure("frontend.activate_release", "OUTCOME_UNKNOWN", activationStatus);
11469
+ }
11470
+ return toolResponse({
11471
+ project_ref: input.projectRef,
11472
+ deployment_id: input.deploymentId,
11473
+ active_release_id: input.releaseId,
11474
+ activation_id: mutationId,
11475
+ release: readback.release
11476
+ });
11477
+ }
11478
+
11479
+ // src/shared/tools/frontend-tools.ts
11019
11480
  function registerFrontendTools(server, http) {
11020
- server.tool("frontend", `Frontend hosting (static sites & SSR). Supports: static, react, vue, svelte, sveltekit, sveltekit-static, nextjs, nuxt, astro.
11021
- Actions: list, get, create, update, delete, deploy_git, deploy_upload, redeploy, build_logs, add_domain, remove_domain, set_env, list_frameworks, list_records`, {
11481
+ server.tool("frontend", `Frontend hosting and immutable prebuilt releases. Supports: static, react, vue, svelte, sveltekit, sveltekit-static, nextjs, nuxt, astro.
11482
+ Actions: list, get, create, update, delete, deploy_git, deploy_upload, redeploy, build_logs, add_domain, remove_domain, set_env, list_frameworks, list_records, list_releases, get_release, upload_release, activate_release`, {
11022
11483
  action: withDescription(stringEnum([
11023
11484
  "list",
11024
11485
  "get",
@@ -11033,7 +11494,11 @@ Actions: list, get, create, update, delete, deploy_git, deploy_upload, redeploy,
11033
11494
  "remove_domain",
11034
11495
  "set_env",
11035
11496
  "list_frameworks",
11036
- "list_records"
11497
+ "list_records",
11498
+ "list_releases",
11499
+ "get_release",
11500
+ "upload_release",
11501
+ "activate_release"
11037
11502
  ]), "Action"),
11038
11503
  ref: optional(Type.String(), "Project ref"),
11039
11504
  id: optional(Type.String(), "Deployment ID"),
@@ -11048,9 +11513,37 @@ Actions: list, get, create, update, delete, deploy_git, deploy_upload, redeploy,
11048
11513
  env_vars: optional(Type.Record(Type.String(), Type.String()), "[create/update/set_env] Environment variables"),
11049
11514
  git_url: optional(Type.String(), "[deploy_git] Git repository URL"),
11050
11515
  branch: optional(Type.String(), "[deploy_git] Branch (default: main)"),
11051
- zip_path: optional(Type.String(), "[deploy_upload] Local zip file path")
11516
+ zip_path: optional(Type.String(), "[deploy_upload/upload_release] Local ZIP file path"),
11517
+ release_id: optional(Type.String(), "[get_release/activate_release] SHA-256 release ID"),
11518
+ expected_active_release_id: optional(Type.String(), "[activate_release] Current release SHA-256 or absent"),
11519
+ expected_activation_id: optional(Type.String(), "[activate_release] Current activation UUIDv4 or absent"),
11520
+ mutation_id: optional(Type.String(), "[activate_release] Required retry-stable UUIDv4"),
11521
+ cursor: optional(Type.String(), "[list_releases] Last release SHA-256 cursor"),
11522
+ limit: optional(Type.Number(), "[list_releases] Page size, 1-100 (default 50)")
11052
11523
  }, async (args) => {
11053
- const { action, ref, id, name, framework, domain, build_command, output_dir, install_command, node_version, health_check_path, env_vars, git_url, branch, zip_path } = args;
11524
+ const {
11525
+ action,
11526
+ ref,
11527
+ id,
11528
+ name,
11529
+ framework,
11530
+ domain,
11531
+ build_command,
11532
+ output_dir,
11533
+ install_command,
11534
+ node_version,
11535
+ health_check_path,
11536
+ env_vars,
11537
+ git_url,
11538
+ branch,
11539
+ zip_path,
11540
+ release_id,
11541
+ expected_active_release_id,
11542
+ expected_activation_id,
11543
+ mutation_id,
11544
+ cursor,
11545
+ limit
11546
+ } = args;
11054
11547
  const need = (f, v) => {
11055
11548
  if (!v)
11056
11549
  throw new Error(`'${f}' required for '${action}'`);
@@ -11157,6 +11650,35 @@ Actions: list, get, create, update, delete, deploy_git, deploy_upload, redeploy,
11157
11650
  need("id", id);
11158
11651
  text = ok(await http.get(`/v1/projects/${ref}/frontend/deployments/${id}/records`));
11159
11652
  break;
11653
+ case "list_releases":
11654
+ need("ref", ref);
11655
+ need("id", id);
11656
+ return listFrontendReleases(http, ref, id, cursor, limit);
11657
+ case "get_release":
11658
+ need("ref", ref);
11659
+ need("id", id);
11660
+ need("release_id", release_id);
11661
+ return getFrontendRelease(http, ref, id, release_id);
11662
+ case "upload_release":
11663
+ need("ref", ref);
11664
+ need("id", id);
11665
+ need("zip_path", zip_path);
11666
+ return uploadFrontendRelease(http, ref, id, zip_path);
11667
+ case "activate_release":
11668
+ need("ref", ref);
11669
+ need("id", id);
11670
+ need("release_id", release_id);
11671
+ need("expected_active_release_id", expected_active_release_id);
11672
+ need("expected_activation_id", expected_activation_id);
11673
+ need("mutation_id", mutation_id);
11674
+ return activateFrontendRelease(http, {
11675
+ projectRef: ref,
11676
+ deploymentId: id,
11677
+ releaseId: release_id,
11678
+ expectedActiveReleaseId: expected_active_release_id,
11679
+ expectedActivationId: expected_activation_id,
11680
+ mutationId: mutation_id
11681
+ });
11160
11682
  default:
11161
11683
  text = `❌ Unknown action`;
11162
11684
  }
@@ -11166,7 +11688,7 @@ Actions: list, get, create, update, delete, deploy_git, deploy_upload, redeploy,
11166
11688
 
11167
11689
  // src/shared/tools/project-read-projection.ts
11168
11690
  var PROJECT_READ_RESPONSE_MAX_BYTES = 1048576;
11169
- var PROJECT_REF_PATTERN3 = /^[a-z0-9-]{1,20}$/;
11691
+ var PROJECT_REF_PATTERN4 = /^[a-z0-9-]{1,20}$/;
11170
11692
  var SAFE_IDENTIFIER_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
11171
11693
  var REGION_PATTERN = /^[A-Za-z0-9._-]{1,64}$/;
11172
11694
  var STATUS_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
@@ -11230,7 +11752,7 @@ function matchingText(candidate, maxLength, pattern) {
11230
11752
  const candidateText = boundedText2(candidate, maxLength);
11231
11753
  return candidateText && pattern.test(candidateText) ? candidateText : null;
11232
11754
  }
11233
- function canonicalTimestamp(candidate) {
11755
+ function canonicalTimestamp2(candidate) {
11234
11756
  const timestamp = boundedText2(candidate, 64);
11235
11757
  if (!timestamp)
11236
11758
  return null;
@@ -11240,12 +11762,12 @@ function canonicalTimestamp(candidate) {
11240
11762
  function projectedSummary(project) {
11241
11763
  const summary = {
11242
11764
  id: matchingText(project.id, 128, SAFE_IDENTIFIER_PATTERN),
11243
- ref: matchingText(project.ref, 20, PROJECT_REF_PATTERN3),
11765
+ ref: matchingText(project.ref, 20, PROJECT_REF_PATTERN4),
11244
11766
  organization_id: matchingText(project.organization_id, 128, SAFE_IDENTIFIER_PATTERN),
11245
11767
  organization_slug: matchingText(project.organization_slug, 128, SAFE_IDENTIFIER_PATTERN),
11246
11768
  name: boundedText2(project.name, 100),
11247
11769
  region: matchingText(project.region, 64, REGION_PATTERN),
11248
- created_at: canonicalTimestamp(project.created_at),
11770
+ created_at: canonicalTimestamp2(project.created_at),
11249
11771
  status: matchingText(project.status, 64, STATUS_PATTERN)
11250
11772
  };
11251
11773
  return Object.values(summary).every((field) => field !== null) ? summary : null;
@@ -11364,7 +11886,7 @@ function projectGetRead(response, expectedRef) {
11364
11886
  // src/shared/tools/project-endpoint-read.ts
11365
11887
  var PROJECT_ENDPOINT_RESPONSE_MAX_BYTES = 256 * 1024;
11366
11888
  var PROJECT_ENDPOINT_LIST_RESPONSE_MAX_BYTES = 1024 * 1024;
11367
- var PROJECT_REF_PATTERN4 = /^[a-z0-9-]{1,20}$/;
11889
+ var PROJECT_REF_PATTERN5 = /^[a-z0-9-]{1,20}$/;
11368
11890
  var PROJECT_ENDPOINTS_SCHEMA = "supacloud.project-endpoints.v1";
11369
11891
  var PROJECT_ENDPOINT_SOURCES = new Set([
11370
11892
  "explicit_api_domain",
@@ -11434,7 +11956,7 @@ function projectEndpoint2(candidate) {
11434
11956
  }
11435
11957
  function projectEndpointProjection(candidate) {
11436
11958
  const projection = plainRecord2(candidate);
11437
- if (!projection || !hasOnlyKeys2(projection, ROOT_KEYS) || projection.schema !== PROJECT_ENDPOINTS_SCHEMA || typeof projection.project_ref !== "string" || !PROJECT_REF_PATTERN4.test(projection.project_ref))
11959
+ if (!projection || !hasOnlyKeys2(projection, ROOT_KEYS) || projection.schema !== PROJECT_ENDPOINTS_SCHEMA || typeof projection.project_ref !== "string" || !PROJECT_REF_PATTERN5.test(projection.project_ref))
11438
11960
  return null;
11439
11961
  const endpoints = plainRecord2(projection.endpoints);
11440
11962
  if (!endpoints || !hasOnlyKeys2(endpoints, ENDPOINTS_KEYS))
@@ -12513,7 +13035,7 @@ function registerBranchTools(server, http, options = {}) {
12513
13035
  // src/shared/tools/supabase-cli-tools.ts
12514
13036
  import { spawn } from "node:child_process";
12515
13037
  import { chmodSync, existsSync as existsSync5, mkdirSync, statSync as statSync3, writeFileSync as writeFileSync2 } from "node:fs";
12516
- import { dirname, isAbsolute, join as join3, resolve as resolve3 } from "node:path";
13038
+ import { dirname, isAbsolute, join as join3, resolve as resolve4 } from "node:path";
12517
13039
  var SENSITIVE_ENV_KEY = /(?:^|_)(?:PASSWORD|PASS|SECRET|TOKEN|KEY|CREDENTIALS?|AUTHORIZATION|AUTH|SESSION|COOKIE|BEARER|DB_URI|DB_URL|DSN|DATABASE_URL|DATABASE_URI|CONNECTION_STRING|CONNECTION_URI)(?:_|$)/i;
12518
13040
  var VALID_VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
12519
13041
  var VALID_MIGRATION_NAME = /^[A-Za-z0-9][A-Za-z0-9_-]{0,100}$/;
@@ -12557,12 +13079,12 @@ function schemaArguments(schema) {
12557
13079
  function workdirArguments(workdir) {
12558
13080
  if (!workdir)
12559
13081
  throw new Error("A workdir is required");
12560
- return ["--workdir", resolve3(workdir)];
13082
+ return ["--workdir", resolve4(workdir)];
12561
13083
  }
12562
13084
  function resolveOutputPath(workdir, file, label) {
12563
13085
  if (!file || /[\r\n\0]/.test(file))
12564
13086
  throw new Error(`${label} file is required`);
12565
- return isAbsolute(file) ? resolve3(file) : resolve3(workdir, file);
13087
+ return isAbsolute(file) ? resolve4(file) : resolve4(workdir, file);
12566
13088
  }
12567
13089
  function databaseTargetArguments(databaseUrl) {
12568
13090
  return databaseUrl ? ["--db-url", requirePostgresUrl(databaseUrl)] : ["--local"];
@@ -12647,7 +13169,7 @@ function actionArguments(request, workdir) {
12647
13169
  function buildOfficialSupabaseArgs(request) {
12648
13170
  if (request.action === "version")
12649
13171
  return ["--version"];
12650
- const workdir = request.workdir ? resolve3(request.workdir) : undefined;
13172
+ const workdir = request.workdir ? resolve4(request.workdir) : undefined;
12651
13173
  if (!workdir)
12652
13174
  throw new Error("A workdir is required");
12653
13175
  return [...actionArguments(request, workdir), ...workdirArguments(workdir)];
@@ -12693,13 +13215,13 @@ function resolveOfficialSupabaseCommand(workdir, environment = process.env) {
12693
13215
  }
12694
13216
  return ["npx", "--yes", `supabase@${version}`];
12695
13217
  }
12696
- const localPackageEntry = join3(resolve3(workdir), "node_modules", "supabase", "dist", "supabase.js");
13218
+ const localPackageEntry = join3(resolve4(workdir), "node_modules", "supabase", "dist", "supabase.js");
12697
13219
  if (existsSync5(localPackageEntry))
12698
13220
  return [process.execPath, localPackageEntry];
12699
13221
  return ["supabase"];
12700
13222
  }
12701
13223
  function resolveExistingWorkdir(workdirInput, fallback) {
12702
- const workdir = resolve3(workdirInput || fallback);
13224
+ const workdir = resolve4(workdirInput || fallback);
12703
13225
  if (!existsSync5(workdir) || !statSync3(workdir).isDirectory()) {
12704
13226
  throw new Error(`Supabase workdir not found: ${workdir}`);
12705
13227
  }
@@ -12813,7 +13335,7 @@ async function executeMigrationPush(request, runtime) {
12813
13335
  if (!projectRef2)
12814
13336
  return missingProjectRefResult();
12815
13337
  const workdir = resolveExistingWorkdir(request.workdir, runtime.fallbackWorkdir);
12816
- const migrationDirectory = resolve3(workdir, request.dir || "supabase/migrations");
13338
+ const migrationDirectory = resolve4(workdir, request.dir || "supabase/migrations");
12817
13339
  const migrationResponse = await pushMigrations({
12818
13340
  action: "push_migrations",
12819
13341
  ref: projectRef2,
@@ -12894,10 +13416,236 @@ function registerSupabaseCliTools(server, options = {}) {
12894
13416
  }, (request) => executeSupabaseAction(request, runtime));
12895
13417
  }
12896
13418
 
13419
+ // src/shared/tools/lite-cli-tools.ts
13420
+ import { spawn as spawn2 } from "node:child_process";
13421
+ import { existsSync as existsSync6, statSync as statSync4 } from "node:fs";
13422
+ import { join as join4, resolve as resolve5 } from "node:path";
13423
+ function requireWorkdir(workdir, fallback) {
13424
+ const resolved = resolve5(workdir || fallback);
13425
+ if (!existsSync6(resolved) || !statSync4(resolved).isDirectory()) {
13426
+ throw new Error(`Lite workdir not found: ${resolved}`);
13427
+ }
13428
+ return resolved;
13429
+ }
13430
+ function optionalFlag(args, flag, value) {
13431
+ if (value !== undefined)
13432
+ args.push(flag, String(value));
13433
+ }
13434
+ function booleanFlag(args, flag, value) {
13435
+ if (value === true)
13436
+ args.push(flag);
13437
+ }
13438
+ function buildLiteArgs(request) {
13439
+ const args = [];
13440
+ if (request.action === "version")
13441
+ return ["--version"];
13442
+ switch (request.action) {
13443
+ case "start":
13444
+ case "migrate":
13445
+ case "status":
13446
+ case "keys":
13447
+ case "upgrade":
13448
+ case "inspect":
13449
+ case "doctor":
13450
+ args.push(request.action);
13451
+ break;
13452
+ case "gen_types":
13453
+ args.push("gen", "types");
13454
+ break;
13455
+ case "db_reset":
13456
+ args.push("db", "reset");
13457
+ break;
13458
+ case "db_diff":
13459
+ args.push("db", "diff");
13460
+ break;
13461
+ case "db_pull":
13462
+ args.push("db", "pull");
13463
+ if (request.file)
13464
+ args.push(request.file);
13465
+ break;
13466
+ case "snapshot_create":
13467
+ args.push("snapshot", "create");
13468
+ break;
13469
+ case "snapshot_restore":
13470
+ if (!request.snapshot_file)
13471
+ throw new Error("snapshot_restore requires --snapshot_file");
13472
+ args.push("snapshot", "restore", request.snapshot_file);
13473
+ break;
13474
+ default:
13475
+ throw new Error(`Unsupported Lite CLI action: ${String(request.action)}`);
13476
+ }
13477
+ optionalFlag(args, "--project-dir", request.project_dir);
13478
+ optionalFlag(args, "--state-dir", request.state_dir);
13479
+ optionalFlag(args, "--data-dir", request.data_dir);
13480
+ optionalFlag(args, "--storage-dir", request.storage_dir);
13481
+ optionalFlag(args, "--storage-backend", request.storage_backend);
13482
+ optionalFlag(args, "--s3-prefix", request.s3_prefix);
13483
+ optionalFlag(args, "--engine", request.engine);
13484
+ optionalFlag(args, "--host", request.host);
13485
+ optionalFlag(args, "--port", request.port);
13486
+ optionalFlag(args, "--api-url", request.api_url);
13487
+ optionalFlag(args, "--site-url", request.site_url);
13488
+ optionalFlag(args, "--replication-profile", request.replication_profile);
13489
+ optionalFlag(args, "--replication-host", request.replication_host);
13490
+ optionalFlag(args, "--replication-port", request.replication_port);
13491
+ optionalFlag(args, "--replication-allow-cidrs", request.replication_allow_cidrs);
13492
+ optionalFlag(args, "--powersync-tables", request.powersync_tables);
13493
+ optionalFlag(args, "--replication-tls-cert", request.replication_tls_cert);
13494
+ optionalFlag(args, "--replication-tls-key", request.replication_tls_key);
13495
+ optionalFlag(args, "--output", request.output);
13496
+ optionalFlag(args, "--file", request.action === "db_diff" ? request.file : undefined);
13497
+ booleanFlag(args, "--service-role", request.service_role);
13498
+ booleanFlag(args, "--force", request.force);
13499
+ booleanFlag(args, "--memory", request.memory);
13500
+ booleanFlag(args, "--json", request.json);
13501
+ return args;
13502
+ }
13503
+ function resolveLiteCommand(workdir, environment = process.env) {
13504
+ const explicitBinary = environment.SUPACLOUD_LITE_CLI_BIN?.trim();
13505
+ if (explicitBinary) {
13506
+ if (explicitBinary.includes("\x00"))
13507
+ throw new Error("Invalid SUPACLOUD_LITE_CLI_BIN");
13508
+ return [explicitBinary];
13509
+ }
13510
+ const localPackageEntry = join4(resolve5(workdir), "node_modules", "@supacloud", "lite", "dist", "launcher.cjs");
13511
+ if (existsSync6(localPackageEntry))
13512
+ return [process.execPath, localPackageEntry];
13513
+ return ["supacloud-lite"];
13514
+ }
13515
+ function spawnLiteCommand(command, workdir, environment, inheritOutput) {
13516
+ const [executable, ...commandArguments] = command;
13517
+ return new Promise((resolveExecution, rejectExecution) => {
13518
+ const child = spawn2(executable, commandArguments, {
13519
+ cwd: workdir,
13520
+ env: { ...environment, NO_COLOR: "1" },
13521
+ shell: false,
13522
+ stdio: inheritOutput ? ["inherit", "inherit", "inherit"] : ["ignore", "pipe", "pipe"],
13523
+ windowsHide: true
13524
+ });
13525
+ const forwardSignal = (signal) => child.kill(signal);
13526
+ process.once("SIGINT", forwardSignal);
13527
+ process.once("SIGTERM", forwardSignal);
13528
+ const cleanup = () => {
13529
+ process.off("SIGINT", forwardSignal);
13530
+ process.off("SIGTERM", forwardSignal);
13531
+ };
13532
+ if (inheritOutput) {
13533
+ child.once("error", (error) => {
13534
+ cleanup();
13535
+ rejectExecution(error);
13536
+ });
13537
+ child.once("close", (exitCode) => {
13538
+ cleanup();
13539
+ resolveExecution({ exitCode: exitCode ?? 1, stdout: "", stderr: "" });
13540
+ });
13541
+ return;
13542
+ }
13543
+ if (!child.stdout || !child.stderr) {
13544
+ cleanup();
13545
+ rejectExecution(new Error("Lite CLI child process did not expose piped output"));
13546
+ return;
13547
+ }
13548
+ let standardOutput = "";
13549
+ let standardError = "";
13550
+ child.stdout.setEncoding("utf8");
13551
+ child.stderr.setEncoding("utf8");
13552
+ child.stdout.on("data", (chunk) => {
13553
+ standardOutput += chunk;
13554
+ });
13555
+ child.stderr.on("data", (chunk) => {
13556
+ standardError += chunk;
13557
+ });
13558
+ child.once("error", (error) => {
13559
+ cleanup();
13560
+ rejectExecution(error);
13561
+ });
13562
+ child.once("close", (exitCode) => {
13563
+ cleanup();
13564
+ resolveExecution({ exitCode: exitCode ?? 1, stdout: standardOutput, stderr: standardError });
13565
+ });
13566
+ });
13567
+ }
13568
+ async function executeLiteCli(request, environment, fallbackWorkdir) {
13569
+ const workdir = requireWorkdir(request.workdir, fallbackWorkdir);
13570
+ const command = [...resolveLiteCommand(workdir, environment), ...buildLiteArgs({ ...request, workdir })];
13571
+ try {
13572
+ return await spawnLiteCommand(command, workdir, environment, request.action === "start");
13573
+ } catch (error) {
13574
+ const failureMessage = error instanceof Error ? error.message : String(error);
13575
+ throw new Error([
13576
+ "SupaCloud Lite CLI could not be started.",
13577
+ "Install @supacloud/lite, put supacloud-lite on PATH, or set SUPACLOUD_LITE_CLI_BIN.",
13578
+ failureMessage
13579
+ ].join(" "));
13580
+ }
13581
+ }
13582
+ function formatExecutionText2(action, execution) {
13583
+ const combinedOutput = [execution.stdout.trim(), execution.stderr.trim()].filter(Boolean).join(`
13584
+ `);
13585
+ const heading = execution.exitCode === 0 ? `✅ SupaCloud Lite ${action} completed` : `❌ SupaCloud Lite ${action} failed (exit ${execution.exitCode})`;
13586
+ return combinedOutput ? `${heading}
13587
+ ${combinedOutput}` : heading;
13588
+ }
13589
+ function registerLiteCliTools(server, options = {}) {
13590
+ const environment = options.environment || process.env;
13591
+ const fallbackWorkdir = options.currentWorkingDirectory || process.cwd();
13592
+ const execute = options.executeLiteCli || ((request) => executeLiteCli(request, environment, fallbackWorkdir));
13593
+ server.tool("lite", "Controlled adapter for the local SupaCloud Lite CLI. Lite actions are local-only and never use the Management API or official Supabase CLI.", {
13594
+ action: withDescription(stringEnum([
13595
+ "version",
13596
+ "start",
13597
+ "migrate",
13598
+ "status",
13599
+ "keys",
13600
+ "gen_types",
13601
+ "db_reset",
13602
+ "db_diff",
13603
+ "db_pull",
13604
+ "snapshot_create",
13605
+ "snapshot_restore",
13606
+ "upgrade",
13607
+ "inspect",
13608
+ "doctor"
13609
+ ]), "Lite CLI action"),
13610
+ workdir: optional(Type.String(), "[*] Process working directory (default: current directory)"),
13611
+ project_dir: optional(Type.String(), "[*] Project containing supabase/"),
13612
+ state_dir: optional(Type.String(), "[*] Lite state root"),
13613
+ data_dir: optional(Type.String(), "[*] PGlite/native data directory"),
13614
+ storage_dir: optional(Type.String(), "[*] Object storage directory"),
13615
+ storage_backend: optional(stringEnum(["fs", "memory", "s3"]), "[*] Storage backend"),
13616
+ s3_prefix: optional(Type.String(), "[*] S3 object key prefix"),
13617
+ engine: optional(stringEnum(["pglite", "native"]), "[*] Database engine"),
13618
+ host: optional(Type.String(), "[start] Listen host"),
13619
+ port: optional(Type.Number(), "[start] Listen port"),
13620
+ api_url: optional(Type.String(), "[start] Public API URL"),
13621
+ site_url: optional(Type.String(), "[start] Auth site URL"),
13622
+ replication_profile: optional(stringEnum(["powersync"]), "[start/doctor] Replication profile"),
13623
+ replication_host: optional(Type.String(), "[start] Replication listener host"),
13624
+ replication_port: optional(Type.Number(), "[start] Replication listener port"),
13625
+ replication_allow_cidrs: optional(Type.String(), "[start] Replication client CIDRs"),
13626
+ powersync_tables: optional(Type.String(), "[start] PowerSync publication tables"),
13627
+ replication_tls_cert: optional(Type.String(), "[start] Replication TLS certificate"),
13628
+ replication_tls_key: optional(Type.String(), "[start] Replication TLS private key"),
13629
+ output: optional(Type.String(), "[gen_types/snapshot_create/upgrade] Output path"),
13630
+ file: optional(Type.String(), "[db_diff/db_pull] Migration suffix or name"),
13631
+ snapshot_file: optional(Type.String(), "[snapshot_restore] Snapshot archive"),
13632
+ service_role: optional(Type.Boolean(), "[keys] Also print the service_role key"),
13633
+ force: optional(Type.Boolean(), "[snapshot_restore] Replace non-empty restore targets"),
13634
+ memory: optional(Type.Boolean(), "[*] Use an in-memory PGlite database"),
13635
+ json: optional(Type.Boolean(), "[doctor] Emit machine-readable output")
13636
+ }, async (request) => {
13637
+ const execution = await execute(request);
13638
+ return {
13639
+ isError: execution.exitCode !== 0,
13640
+ content: [{ type: "text", text: formatExecutionText2(request.action, execution) }]
13641
+ };
13642
+ });
13643
+ }
13644
+
12897
13645
  // src/shared/tools/ai-tools.ts
12898
13646
  import {
12899
13647
  cpSync,
12900
- existsSync as existsSync6,
13648
+ existsSync as existsSync7,
12901
13649
  lstatSync as lstatSync2,
12902
13650
  mkdirSync as mkdirSync2,
12903
13651
  mkdtempSync as mkdtempSync2,
@@ -12907,13 +13655,13 @@ import {
12907
13655
  rmSync as rmSync2
12908
13656
  } from "node:fs";
12909
13657
  import { homedir as homedir2 } from "node:os";
12910
- import { dirname as dirname2, join as join4, relative as relative2, resolve as resolve4, sep as sep2 } from "node:path";
13658
+ import { dirname as dirname2, join as join5, relative as relative2, resolve as resolve6, sep as sep2 } from "node:path";
12911
13659
  import { fileURLToPath } from "node:url";
12912
13660
  var SKILL_NAME = "supacloud-cli";
12913
13661
  function regularFiles(rootDirectory, currentDirectory = rootDirectory) {
12914
13662
  const files = [];
12915
13663
  for (const directoryEntry of readdirSync3(currentDirectory, { withFileTypes: true })) {
12916
- const entryPath = join4(currentDirectory, directoryEntry.name);
13664
+ const entryPath = join5(currentDirectory, directoryEntry.name);
12917
13665
  if (directoryEntry.isSymbolicLink())
12918
13666
  throw new Error(`Skill directories cannot contain symlinks: ${entryPath}`);
12919
13667
  if (directoryEntry.isDirectory())
@@ -12924,13 +13672,13 @@ function regularFiles(rootDirectory, currentDirectory = rootDirectory) {
12924
13672
  return files.sort();
12925
13673
  }
12926
13674
  function directoriesMatch(sourceDirectory, destinationDirectory) {
12927
- if (!existsSync6(destinationDirectory) || !lstatSync2(destinationDirectory).isDirectory())
13675
+ if (!existsSync7(destinationDirectory) || !lstatSync2(destinationDirectory).isDirectory())
12928
13676
  return false;
12929
13677
  const sourceFiles = regularFiles(sourceDirectory);
12930
13678
  const destinationFiles = regularFiles(destinationDirectory);
12931
13679
  if (sourceFiles.join("\x00") !== destinationFiles.join("\x00"))
12932
13680
  return false;
12933
- return sourceFiles.every((file) => readFileSync5(join4(sourceDirectory, file)).equals(readFileSync5(join4(destinationDirectory, file))));
13681
+ return sourceFiles.every((file) => readFileSync5(join5(sourceDirectory, file)).equals(readFileSync5(join5(destinationDirectory, file))));
12934
13682
  }
12935
13683
  function backupTimestamp(now) {
12936
13684
  return now.toISOString().replace(/[-:.]/g, "");
@@ -12939,7 +13687,7 @@ function availableBackupDirectory(destinationDirectory, now) {
12939
13687
  const baseDirectory = `${destinationDirectory}.backup-${backupTimestamp(now)}`;
12940
13688
  let candidate = baseDirectory;
12941
13689
  let suffix = 2;
12942
- while (existsSync6(candidate)) {
13690
+ while (existsSync7(candidate)) {
12943
13691
  candidate = `${baseDirectory}-${suffix}`;
12944
13692
  suffix += 1;
12945
13693
  }
@@ -12947,8 +13695,8 @@ function availableBackupDirectory(destinationDirectory, now) {
12947
13695
  }
12948
13696
  function stagedSkill(sourceDirectory, targetRoot) {
12949
13697
  mkdirSync2(targetRoot, { recursive: true });
12950
- const stagingRoot = mkdtempSync2(join4(targetRoot, ".supacloud-cli-install-"));
12951
- const stagingSkill = join4(stagingRoot, SKILL_NAME);
13698
+ const stagingRoot = mkdtempSync2(join5(targetRoot, ".supacloud-cli-install-"));
13699
+ const stagingSkill = join5(stagingRoot, SKILL_NAME);
12952
13700
  try {
12953
13701
  cpSync(sourceDirectory, stagingSkill, { recursive: true, errorOnExist: true });
12954
13702
  } catch (error) {
@@ -12980,13 +13728,13 @@ function replaceSkill(sourceDirectory, targetRoot, destinationDirectory, backupD
12980
13728
  }
12981
13729
  }
12982
13730
  function skillSummary(request, action, files, backupDirectory) {
12983
- const sourceDirectory = resolve4(request.sourceDirectory);
12984
- const targetRoot = resolve4(request.targetRoot);
13731
+ const sourceDirectory = resolve6(request.sourceDirectory);
13732
+ const targetRoot = resolve6(request.targetRoot);
12985
13733
  return {
12986
13734
  name: SKILL_NAME,
12987
13735
  sourceDirectory,
12988
13736
  targetRoot,
12989
- destinationDirectory: join4(targetRoot, SKILL_NAME),
13737
+ destinationDirectory: join5(targetRoot, SKILL_NAME),
12990
13738
  action,
12991
13739
  mode: request.mode,
12992
13740
  changed: action !== "none",
@@ -12995,14 +13743,14 @@ function skillSummary(request, action, files, backupDirectory) {
12995
13743
  };
12996
13744
  }
12997
13745
  function installSkill(request) {
12998
- const sourceDirectory = resolve4(request.sourceDirectory);
12999
- const targetRoot = resolve4(request.targetRoot);
13000
- const destinationDirectory = join4(targetRoot, SKILL_NAME);
13001
- if (!existsSync6(join4(sourceDirectory, "SKILL.md"))) {
13746
+ const sourceDirectory = resolve6(request.sourceDirectory);
13747
+ const targetRoot = resolve6(request.targetRoot);
13748
+ const destinationDirectory = join5(targetRoot, SKILL_NAME);
13749
+ if (!existsSync7(join5(sourceDirectory, "SKILL.md"))) {
13002
13750
  throw new Error(`Bundled SupaCloud CLI skill not found: ${sourceDirectory}`);
13003
13751
  }
13004
13752
  const files = regularFiles(sourceDirectory);
13005
- if (!existsSync6(destinationDirectory))
13753
+ if (!existsSync7(destinationDirectory))
13006
13754
  return installNewSkill(request, files);
13007
13755
  if (directoriesMatch(sourceDirectory, destinationDirectory)) {
13008
13756
  return skillSummary(request, "none", files, null);
@@ -13013,17 +13761,17 @@ function installSkill(request) {
13013
13761
  return installReplacementSkill(request, files);
13014
13762
  }
13015
13763
  function installNewSkill(request, files) {
13016
- const sourceDirectory = resolve4(request.sourceDirectory);
13017
- const targetRoot = resolve4(request.targetRoot);
13764
+ const sourceDirectory = resolve6(request.sourceDirectory);
13765
+ const targetRoot = resolve6(request.targetRoot);
13018
13766
  if (request.mode === "write") {
13019
- createSkill(sourceDirectory, targetRoot, join4(targetRoot, SKILL_NAME));
13767
+ createSkill(sourceDirectory, targetRoot, join5(targetRoot, SKILL_NAME));
13020
13768
  }
13021
13769
  return skillSummary(request, "create", files, null);
13022
13770
  }
13023
13771
  function installReplacementSkill(request, files) {
13024
- const sourceDirectory = resolve4(request.sourceDirectory);
13025
- const targetRoot = resolve4(request.targetRoot);
13026
- const destinationDirectory = join4(targetRoot, SKILL_NAME);
13772
+ const sourceDirectory = resolve6(request.sourceDirectory);
13773
+ const targetRoot = resolve6(request.targetRoot);
13774
+ const destinationDirectory = join5(targetRoot, SKILL_NAME);
13027
13775
  const backupDirectory = availableBackupDirectory(destinationDirectory, request.now);
13028
13776
  if (request.mode === "write") {
13029
13777
  replaceSkill(sourceDirectory, targetRoot, destinationDirectory, backupDirectory);
@@ -13032,15 +13780,15 @@ function installReplacementSkill(request, files) {
13032
13780
  }
13033
13781
  function resolveDefaultCodexSkillRoot(environment = process.env, homeDirectory = homedir2()) {
13034
13782
  const codexHome = environment.CODEX_HOME?.trim();
13035
- return join4(resolve4(codexHome || join4(homeDirectory, ".codex")), "skills");
13783
+ return join5(resolve6(codexHome || join5(homeDirectory, ".codex")), "skills");
13036
13784
  }
13037
13785
  function resolveBundledSkillDirectory(moduleUrl = import.meta.url) {
13038
13786
  const moduleDirectory = dirname2(fileURLToPath(moduleUrl));
13039
13787
  const candidates = [
13040
- resolve4(moduleDirectory, "../../../skills", SKILL_NAME),
13041
- resolve4(moduleDirectory, "../skills", SKILL_NAME)
13788
+ resolve6(moduleDirectory, "../../../skills", SKILL_NAME),
13789
+ resolve6(moduleDirectory, "../skills", SKILL_NAME)
13042
13790
  ];
13043
- const skillDirectory = candidates.find((candidate) => existsSync6(join4(candidate, "SKILL.md")));
13791
+ const skillDirectory = candidates.find((candidate) => existsSync7(join5(candidate, "SKILL.md")));
13044
13792
  if (!skillDirectory)
13045
13793
  throw new Error("Bundled SupaCloud CLI skill is missing from this installation");
13046
13794
  return skillDirectory;
@@ -13062,7 +13810,7 @@ function registerAiTools(server) {
13062
13810
  name: SKILL_NAME,
13063
13811
  sourceDirectory,
13064
13812
  defaultTargetRoot,
13065
- defaultDestination: join4(defaultTargetRoot, SKILL_NAME)
13813
+ defaultDestination: join5(defaultTargetRoot, SKILL_NAME)
13066
13814
  });
13067
13815
  }
13068
13816
  return textResponse(installSkill({
@@ -13077,8 +13825,8 @@ function registerAiTools(server) {
13077
13825
 
13078
13826
  // src/shared/tools/scheduled-function-tools.ts
13079
13827
  import { randomUUID } from "node:crypto";
13080
- import { readFileSync as readFileSync6, statSync as statSync4 } from "node:fs";
13081
- import { resolve as resolve5 } from "node:path";
13828
+ import { readFileSync as readFileSync6, statSync as statSync5 } from "node:fs";
13829
+ import { resolve as resolve7 } from "node:path";
13082
13830
  import { isDeepStrictEqual } from "node:util";
13083
13831
  var HEADER_NAME_PATTERN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,128}$/;
13084
13832
  var ENVIRONMENT_NAME_PATTERN2 = /^[A-Za-z_][A-Za-z0-9_]{0,255}$/;
@@ -13162,8 +13910,8 @@ function validScheduledFunctionCron(expression) {
13162
13910
  function readScheduleBodyFile(bodyPathInput) {
13163
13911
  if (!bodyPathInput.trim())
13164
13912
  throw new Error("'body_file' must be a path");
13165
- const bodyPath = resolve5(bodyPathInput);
13166
- const bodyStat = statSync4(bodyPath);
13913
+ const bodyPath = resolve7(bodyPathInput);
13914
+ const bodyStat = statSync5(bodyPath);
13167
13915
  if (!bodyStat.isFile() || bodyStat.size > MAX_BODY_FILE_BYTES) {
13168
13916
  throw new Error("Scheduled Function body file must be a regular file no larger than 1 MiB");
13169
13917
  }
@@ -13508,14 +14256,14 @@ var SCHEDULE_TOOL_SCHEMA = {
13508
14256
  };
13509
14257
 
13510
14258
  // src/shared/mutation-protocol.ts
13511
- var MUTATION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
14259
+ var MUTATION_ID_PATTERN2 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
13512
14260
  var FINGERPRINT_PATTERN = /^[0-9a-f]{64}$/;
13513
14261
  var OPERATION_PATTERN = /^[a-z0-9][a-z0-9._:-]{0,127}$/;
13514
14262
  var RESOURCE_KEY_PATTERN = /^v1\/(?:[a-z0-9][a-z0-9._-]{0,63})\/([A-Za-z0-9_-]{2,171})$/;
13515
14263
  var RESOURCE_ID_CONTROL_PATTERN = /[\u0000-\u001f\u007f-\u009f]/u;
13516
14264
  var FAILURE_CODE_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
13517
14265
  var LEASE_OWNER_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:@/-]{0,254}$/;
13518
- var TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
14266
+ var TIMESTAMP_PATTERN2 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
13519
14267
  var MAX_STATUS_RESPONSE_BYTES = 196608;
13520
14268
  var MAX_RESOURCE_ID_BYTES = 128;
13521
14269
  var FATAL_UTF8_DECODER = new TextDecoder("utf-8", { fatal: true });
@@ -13548,7 +14296,7 @@ var MUTATION_KEYS = [
13548
14296
  var PRINCIPAL_KEYS = ["type", "id"];
13549
14297
  var LEASE_KEYS = ["owner", "expires_at", "fencing_epoch"];
13550
14298
  function isMutationId(candidate) {
13551
- return typeof candidate === "string" && MUTATION_ID_PATTERN.test(candidate);
14299
+ return typeof candidate === "string" && MUTATION_ID_PATTERN2.test(candidate);
13552
14300
  }
13553
14301
  function objectRecord4(candidate) {
13554
14302
  return candidate && typeof candidate === "object" && !Array.isArray(candidate) ? candidate : null;
@@ -13563,14 +14311,14 @@ function emptyProjection(candidate) {
13563
14311
  const record = objectRecord4(candidate);
13564
14312
  return record && Object.keys(record).length === 0 ? record : null;
13565
14313
  }
13566
- function canonicalTimestamp2(candidate) {
13567
- if (typeof candidate !== "string" || !TIMESTAMP_PATTERN.test(candidate))
14314
+ function canonicalTimestamp3(candidate) {
14315
+ if (typeof candidate !== "string" || !TIMESTAMP_PATTERN2.test(candidate))
13568
14316
  return false;
13569
14317
  const milliseconds = Date.parse(candidate);
13570
14318
  return Number.isFinite(milliseconds) && new Date(milliseconds).toISOString() === candidate;
13571
14319
  }
13572
14320
  function nullableTimestamp(candidate) {
13573
- return candidate === null || canonicalTimestamp2(candidate);
14321
+ return candidate === null || canonicalTimestamp3(candidate);
13574
14322
  }
13575
14323
  function safePrincipal(candidate) {
13576
14324
  const principal = exactRecord(candidate, PRINCIPAL_KEYS);
@@ -13653,7 +14401,7 @@ function validMutationTerminalFields(mutation) {
13653
14401
  return false;
13654
14402
  if (mutation.failure_code !== null && (typeof mutation.failure_code !== "string" || !FAILURE_CODE_PATTERN.test(mutation.failure_code)))
13655
14403
  return false;
13656
- return nullableTimestamp(mutation.completed_at) && canonicalTimestamp2(mutation.created_at) && canonicalTimestamp2(mutation.updated_at);
14404
+ return nullableTimestamp(mutation.completed_at) && canonicalTimestamp3(mutation.created_at) && canonicalTimestamp3(mutation.updated_at);
13657
14405
  }
13658
14406
  function safeMutationStatus(candidate) {
13659
14407
  const mutation = exactRecord(candidate, MUTATION_KEYS);
@@ -13761,7 +14509,7 @@ var RELEASE_CANARY_CLAIM_MAX_LENGTH = 2048;
13761
14509
  function isRecord3(value) {
13762
14510
  return value !== null && typeof value === "object" && !Array.isArray(value);
13763
14511
  }
13764
- function canonicalTimestamp3(value) {
14512
+ function canonicalTimestamp4(value) {
13765
14513
  if (typeof value !== "string")
13766
14514
  return false;
13767
14515
  const parsed = new Date(value);
@@ -13774,7 +14522,7 @@ function backupBelongsToProject(backupId, projectRef2) {
13774
14522
  return BACKUP_ID.test(backupId) && backupId.startsWith(`logical-full_${projectRef2}_`);
13775
14523
  }
13776
14524
  function verifiedBackup(value, projectRef2) {
13777
- if (!isRecord3(value) || typeof value.backup_id !== "string" || !backupBelongsToProject(value.backup_id, projectRef2) || value.project_ref !== projectRef2 || typeof value.database !== "string" || !SAFE_DATABASE.test(value.database) || value.kind !== "logical-full" || !canonicalTimestamp3(value.created_at) || !canonicalTimestamp3(value.completed_at) || new Date(value.completed_at).valueOf() < new Date(value.created_at).valueOf() || typeof value.bytes !== "number" || !Number.isSafeInteger(value.bytes) || value.bytes <= 0 || typeof value.sha256 !== "string" || !SHA256.test(value.sha256))
14525
+ if (!isRecord3(value) || typeof value.backup_id !== "string" || !backupBelongsToProject(value.backup_id, projectRef2) || value.project_ref !== projectRef2 || typeof value.database !== "string" || !SAFE_DATABASE.test(value.database) || value.kind !== "logical-full" || !canonicalTimestamp4(value.created_at) || !canonicalTimestamp4(value.completed_at) || new Date(value.completed_at).valueOf() < new Date(value.created_at).valueOf() || typeof value.bytes !== "number" || !Number.isSafeInteger(value.bytes) || value.bytes <= 0 || typeof value.sha256 !== "string" || !SHA256.test(value.sha256))
13778
14526
  return null;
13779
14527
  return {
13780
14528
  backup_id: value.backup_id,
@@ -14121,7 +14869,7 @@ function registerReleaseTools(server, http, options = {}) {
14121
14869
  // package.json
14122
14870
  var package_default = {
14123
14871
  name: "@supacloud/cli",
14124
- version: "0.32.0",
14872
+ version: "0.34.0",
14125
14873
  description: "Project-scoped CLI for SupaCloud users",
14126
14874
  type: "module",
14127
14875
  main: "./dist/index.js",
@@ -14386,6 +15134,9 @@ EXAMPLES
14386
15134
  ${preferredCommand} supabase db_diff --schema public --name add_accounts
14387
15135
  ${preferredCommand} supabase push --ref abc123 --dir supabase/migrations --dry_run
14388
15136
  ${preferredCommand} supabase db_dump --db_url "postgresql://..." --file backups/schema.sql
15137
+ ${preferredCommand} lite migrate --project_dir .
15138
+ ${preferredCommand} lite start --project_dir . --port 54321
15139
+ ${preferredCommand} lite doctor --project_dir . --json
14389
15140
  ${preferredCommand} branch create --name feature-auth --data_mode schema_only
14390
15141
  ${preferredCommand} branch promotion_plan --branch_ref preview123
14391
15142
  ${preferredCommand} branch promote --branch_ref preview123 --plan_checksum <sha256>
@@ -14438,6 +15189,7 @@ function createCliTools(context, confirmProduction) {
14438
15189
  projectRef: context.projectRef || undefined,
14439
15190
  readOnly: context.readOnly
14440
15191
  })));
15192
+ Object.assign(tools, captureTools((server) => registerLiteCliTools(server)));
14441
15193
  Object.assign(tools, captureTools((server) => registerAiTools(server)));
14442
15194
  const registerContextAwareHelp = () => {
14443
15195
  tools.project = {
@@ -14494,6 +15246,11 @@ function createCliTools(context, confirmProduction) {
14494
15246
  if (branchHelpTool) {
14495
15247
  tools.branch = { schema: branchHelpTool.schema, callback: branchContextCallback };
14496
15248
  }
15249
+ const frontendContextCallback = tools.frontend.callback;
15250
+ const frontendHelpTool = captureTools((server) => registerFrontendTools(server, {})).frontend;
15251
+ if (frontendHelpTool) {
15252
+ tools.frontend = { schema: frontendHelpTool.schema, callback: frontendContextCallback };
15253
+ }
14497
15254
  };
14498
15255
  if (context.credentialScope !== "management" || !context.apiUrl || !context.apiToken) {
14499
15256
  registerContextAwareHelp();
@@ -14599,7 +15356,7 @@ async function main() {
14599
15356
  return;
14600
15357
  }
14601
15358
  const cliTools = createCliTools(context, globalOptions.confirmProduction);
14602
- if (args.length === 1 && !["ai", "supabase"].includes(args[0]) && cliTools[args[0]]) {
15359
+ if (args.length === 1 && !["ai", "supabase", "lite"].includes(args[0]) && cliTools[args[0]]) {
14603
15360
  const result = await cliTools[args[0]].callback({});
14604
15361
  if (result?.content && Array.isArray(result.content)) {
14605
15362
  for (const chunk of result.content) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/cli",
3
- "version": "0.32.0",
3
+ "version": "0.34.0",
4
4
  "description": "Project-scoped CLI for SupaCloud users",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",