@supacloud/cli 0.25.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -6458,8 +6458,9 @@ function resolveSupaCloudContext(env = process.env, cwd = process.cwd(), selecti
6458
6458
  // src/shared/execution-policy.ts
6459
6459
  var ACTION_POLICY = {
6460
6460
  project: {
6461
- read: ["get", "health", "logs", "api_keys", "settings", "tasks", "task_detail", "task_stats", "dlq", "background_settings"],
6462
- write: ["pause", "restore", "task_cancel", "task_retry", "update_background_settings"]
6461
+ read: ["get", "endpoints", "health", "logs", "api_keys", "settings", "tasks", "task_detail", "task_stats", "dlq", "background_settings"],
6462
+ write: ["pause", "restore", "task_cancel", "task_retry", "update_background_settings"],
6463
+ local: ["list"]
6463
6464
  },
6464
6465
  database: {
6465
6466
  read: ["list_tables", "describe_columns", "list_indexes", "list_constraints", "list_extensions", "rls_status", "rls_policies", "list_auth_users", "get_auth_user", "connections", "stats", "slow_queries", "list_migrations", "migration_inventory", "project_url", "generate_types"],
@@ -6473,6 +6474,10 @@ var ACTION_POLICY = {
6473
6474
  read: ["list_providers", "get_provider", "supported_providers", "get_settings", "get_config"],
6474
6475
  write: ["configure_provider", "update_provider", "disable_provider", "wechat_mini", "wechat_open", "update_settings", "update_config"]
6475
6476
  },
6477
+ oauth_clients: {
6478
+ read: ["list", "get"],
6479
+ write: ["create", "delete"]
6480
+ },
6476
6481
  storage: {
6477
6482
  read: ["status", "list_buckets", "get_bucket", "list_files"],
6478
6483
  write: ["create_bucket", "update_bucket", "delete_bucket", "upload_base64", "delete_file"]
@@ -8170,6 +8175,214 @@ ${JSON.stringify(r.data, null, 2)}` : `❌ Failed (${r.status}): ${JSON.stringif
8170
8175
  });
8171
8176
  }
8172
8177
 
8178
+ // src/shared/tools/oauth-client-tools.ts
8179
+ var RELEASE_CANARY_CLIENT_NAME = "supacloud-release-canary";
8180
+ var CLIENT_ID_PATTERN = /^[A-Za-z0-9._~-]{1,256}$/;
8181
+ var MAX_CLIENT_LIST_BYTES = 256 * 1024;
8182
+ var READ_TIMEOUT_MS = 5000;
8183
+ function isRecord(value) {
8184
+ return value !== null && typeof value === "object" && !Array.isArray(value);
8185
+ }
8186
+ function releaseCanaryCallbackUri(value) {
8187
+ if (typeof value !== "string" || !value.trim()) {
8188
+ throw new Error("'redirect_uri' is required");
8189
+ }
8190
+ let uri;
8191
+ try {
8192
+ uri = new URL(value);
8193
+ } catch {
8194
+ throw new Error("'redirect_uri' must be an absolute HTTPS or loopback HTTP URL");
8195
+ }
8196
+ const loopback = uri.hostname === "127.0.0.1" || uri.hostname === "[::1]";
8197
+ const isHttps = uri.protocol === "https:";
8198
+ const isPortBoundLoopback = uri.protocol === "http:" && loopback && Boolean(uri.port);
8199
+ if (!isHttps && !isPortBoundLoopback || !uri.hostname || uri.username || uri.password || uri.search || uri.hash) {
8200
+ throw new Error("'redirect_uri' must be an exact HTTPS callback or port-bound loopback HTTP callback without credentials, query, or fragment");
8201
+ }
8202
+ return uri.toString();
8203
+ }
8204
+ function createdClientId(value) {
8205
+ if (!isRecord(value))
8206
+ return null;
8207
+ try {
8208
+ return clientId(value.client_id);
8209
+ } catch {
8210
+ return null;
8211
+ }
8212
+ }
8213
+ function clientId(value) {
8214
+ if (typeof value !== "string" || !CLIENT_ID_PATTERN.test(value)) {
8215
+ throw new Error("'client_id' is invalid");
8216
+ }
8217
+ return value;
8218
+ }
8219
+ function projectRef(value) {
8220
+ if (typeof value !== "string" || !value.trim())
8221
+ throw new Error("'ref' is required");
8222
+ return projectRefPathSegment(value.trim(), "OAuth client");
8223
+ }
8224
+ function oauthClientsPath(ref) {
8225
+ return `/v1/projects/${encodeURIComponent(ref)}/auth/oauth-clients`;
8226
+ }
8227
+ function expectedClient(value, redirectUri) {
8228
+ if (!isRecord(value) || typeof value.client_id !== "string" || !CLIENT_ID_PATTERN.test(value.client_id) || value.client_name !== RELEASE_CANARY_CLIENT_NAME || value.client_type !== "public" || value.token_endpoint_auth_method !== "none" || !Array.isArray(value.redirect_uris) || value.redirect_uris.length !== 1 || !Array.isArray(value.grant_types) || value.grant_types.length !== 1 || value.grant_types[0] !== "authorization_code" || !Array.isArray(value.response_types) || value.response_types.length !== 1 || value.response_types[0] !== "code")
8229
+ return null;
8230
+ let callback;
8231
+ try {
8232
+ callback = releaseCanaryCallbackUri(value.redirect_uris[0]);
8233
+ } catch {
8234
+ return null;
8235
+ }
8236
+ if (redirectUri !== undefined && callback !== redirectUri)
8237
+ return null;
8238
+ return {
8239
+ client_id: value.client_id,
8240
+ client_name: RELEASE_CANARY_CLIENT_NAME,
8241
+ client_type: "public",
8242
+ token_endpoint_auth_method: "none",
8243
+ redirect_uris: [callback],
8244
+ grant_types: ["authorization_code"],
8245
+ response_types: ["code"]
8246
+ };
8247
+ }
8248
+ function clientInventory(value) {
8249
+ if (!isRecord(value) || !Array.isArray(value.clients))
8250
+ return null;
8251
+ const clients = value.clients.filter((client) => isRecord(client) && client.client_name === RELEASE_CANARY_CLIENT_NAME).map((client) => expectedClient(client));
8252
+ if (clients.some((client) => client === null))
8253
+ return null;
8254
+ const inventory = clients;
8255
+ return new Set(inventory.map((client) => client.client_id)).size === inventory.length ? inventory : null;
8256
+ }
8257
+ function readFailure(operation, response) {
8258
+ if (!response.ok) {
8259
+ return releaseControlFailure(operation, response.responseReadError ? "INVALID_RESPONSE" : "HTTP_ERROR", response.transportError ? null : response.status);
8260
+ }
8261
+ return null;
8262
+ }
8263
+ async function listClients(http, ref) {
8264
+ const response = await http.get(oauthClientsPath(ref), {
8265
+ maxJsonBytes: MAX_CLIENT_LIST_BYTES,
8266
+ responseTimeoutMs: READ_TIMEOUT_MS
8267
+ });
8268
+ return { response, clients: response.ok && response.status === 200 ? clientInventory(response.data) : null };
8269
+ }
8270
+ async function getClient(http, ref, id) {
8271
+ const response = await http.get(`${oauthClientsPath(ref)}/${encodeURIComponent(id)}`, {
8272
+ maxJsonBytes: MAX_CLIENT_LIST_BYTES,
8273
+ responseTimeoutMs: READ_TIMEOUT_MS
8274
+ });
8275
+ return { response, client: response.ok && response.status === 200 ? expectedClient(response.data) : null };
8276
+ }
8277
+ function exactSingleClient(inventory, redirectUri) {
8278
+ return inventory.length === 1 && inventory[0]?.redirect_uris[0] === redirectUri ? inventory[0] : null;
8279
+ }
8280
+ async function listReleaseCanaryClients(http, ref) {
8281
+ const read = await listClients(http, ref);
8282
+ const failure = readFailure("oauth_clients.list", read.response);
8283
+ if (failure)
8284
+ return failure;
8285
+ if (!read.clients)
8286
+ return releaseControlFailure("oauth_clients.list", "INVALID_RESPONSE", read.response.status);
8287
+ return releaseControlSuccess("oauth_clients.list", { project_ref: ref, clients: read.clients });
8288
+ }
8289
+ async function getReleaseCanaryClient(http, ref, id) {
8290
+ const read = await getClient(http, ref, id);
8291
+ const failure = readFailure("oauth_clients.get", read.response);
8292
+ if (failure)
8293
+ return failure;
8294
+ if (!read.client || read.client.client_id !== id) {
8295
+ return releaseControlFailure("oauth_clients.get", "INVALID_RESPONSE", read.response.status);
8296
+ }
8297
+ return releaseControlSuccess("oauth_clients.get", { project_ref: ref, client: read.client });
8298
+ }
8299
+ async function createReleaseCanaryClient(http, ref, redirectUri) {
8300
+ const before = await listClients(http, ref);
8301
+ const beforeFailure = readFailure("oauth_clients.create", before.response);
8302
+ if (beforeFailure)
8303
+ return beforeFailure;
8304
+ if (!before.clients)
8305
+ return releaseControlFailure("oauth_clients.create", "INVALID_RESPONSE", before.response.status);
8306
+ const existing = exactSingleClient(before.clients, redirectUri);
8307
+ if (existing) {
8308
+ return releaseControlSuccess("oauth_clients.create", {
8309
+ project_ref: ref,
8310
+ client: existing,
8311
+ reused: true
8312
+ });
8313
+ }
8314
+ if (before.clients.length > 0) {
8315
+ return releaseControlFailure("oauth_clients.create", "MUTATION_NOT_SUCCEEDED", null, { project_ref: ref });
8316
+ }
8317
+ const mutation = await http.postReleaseMutation(oauthClientsPath(ref), {
8318
+ client_type: "public",
8319
+ token_endpoint_auth_method: "none",
8320
+ redirect_uris: [redirectUri],
8321
+ grant_types: ["authorization_code"],
8322
+ client_name: RELEASE_CANARY_CLIENT_NAME
8323
+ });
8324
+ if (!mutation.ok) {
8325
+ return releaseControlMutationFailure("oauth_clients.create", mutation, { project_ref: ref });
8326
+ }
8327
+ const createdId = createdClientId(mutation.data);
8328
+ if (!createdId)
8329
+ return releaseControlFailure("oauth_clients.create", "OUTCOME_UNKNOWN", mutation.status, { project_ref: ref });
8330
+ const read = await getClient(http, ref, createdId);
8331
+ const readFailureResult = readFailure("oauth_clients.create", read.response);
8332
+ if (readFailureResult || !read.client || read.client.client_id !== createdId || read.client.redirect_uris[0] !== redirectUri) {
8333
+ return releaseControlFailure("oauth_clients.create", "OUTCOME_UNKNOWN", mutation.status, { project_ref: ref });
8334
+ }
8335
+ return releaseControlSuccess("oauth_clients.create", {
8336
+ project_ref: ref,
8337
+ client: read.client,
8338
+ reused: false
8339
+ });
8340
+ }
8341
+ async function deleteReleaseCanaryClient(http, ref, id, redirectUri) {
8342
+ const before = await getClient(http, ref, id);
8343
+ const beforeFailure = readFailure("oauth_clients.delete", before.response);
8344
+ if (beforeFailure)
8345
+ return beforeFailure;
8346
+ if (!before.client || before.client.client_id !== id || before.client.redirect_uris[0] !== redirectUri) {
8347
+ return releaseControlFailure("oauth_clients.delete", "MUTATION_NOT_SUCCEEDED", null, { project_ref: ref });
8348
+ }
8349
+ const mutation = await http.deleteReleaseMutation(`${oauthClientsPath(ref)}/${encodeURIComponent(id)}`);
8350
+ if (!mutation.ok || ![200, 204].includes(mutation.status)) {
8351
+ return releaseControlMutationFailure("oauth_clients.delete", mutation, { project_ref: ref });
8352
+ }
8353
+ const after = await listClients(http, ref);
8354
+ const afterFailure = readFailure("oauth_clients.delete", after.response);
8355
+ if (afterFailure || !after.clients || after.clients.some((client) => client.client_id === id)) {
8356
+ return releaseControlFailure("oauth_clients.delete", "OUTCOME_UNKNOWN", mutation.status, { project_ref: ref });
8357
+ }
8358
+ return releaseControlSuccess("oauth_clients.delete", {
8359
+ project_ref: ref,
8360
+ client_id: id,
8361
+ deleted: true
8362
+ });
8363
+ }
8364
+ function registerOAuthClientTools(server, http) {
8365
+ server.tool("oauth_clients", "Dedicated release-canary public OAuth client lifecycle. It only manages the exact supacloud-release-canary public authorization-code client and never returns client secrets.", {
8366
+ action: withDescription(stringEnum(["list", "get", "create", "delete"]), "OAuth client action"),
8367
+ ref: withDescription(Type.String(), "Central SupAuth project ref"),
8368
+ client_id: optional(Type.String(), "[get/delete] Exact release-canary public OAuth client ID"),
8369
+ redirect_uri: optional(Type.String(), "[create/delete] Exact HTTPS or port-bound RFC 8252 loopback callback")
8370
+ }, async ({ action, ref, client_id, redirect_uri }) => {
8371
+ const targetRef = projectRef(ref);
8372
+ if (action === "list")
8373
+ return listReleaseCanaryClients(http, targetRef);
8374
+ if (action === "get")
8375
+ return getReleaseCanaryClient(http, targetRef, clientId(client_id));
8376
+ if (action === "create") {
8377
+ return createReleaseCanaryClient(http, targetRef, releaseCanaryCallbackUri(redirect_uri));
8378
+ }
8379
+ if (action === "delete") {
8380
+ return deleteReleaseCanaryClient(http, targetRef, clientId(client_id), releaseCanaryCallbackUri(redirect_uri));
8381
+ }
8382
+ throw new Error("Unknown OAuth client action");
8383
+ });
8384
+ }
8385
+
8173
8386
  // src/shared/tools/storage-tools.ts
8174
8387
  var MAX_BUCKET_ID_LENGTH = 100;
8175
8388
  var MAX_MIME_TYPE_COUNT = 100;
@@ -9184,13 +9397,13 @@ async function readFunctionSource(http, request) {
9184
9397
  function mutationIdentityMatches2(receipt, expectation) {
9185
9398
  return receipt.success === true && receipt.project_ref === expectation.projectRef && receipt.slug === expectation.slug && receipt.previous_active_version === expectation.expectedActiveVersion && receipt.expected_activation_id === expectation.expectedActivationId;
9186
9399
  }
9187
- async function readFunctionIdentity(http, projectRef, slug) {
9188
- const resourcePath = edgeFunctionResourcePath(projectRef, slug);
9400
+ async function readFunctionIdentity(http, projectRef2, slug) {
9401
+ const resourcePath = edgeFunctionResourcePath(projectRef2, slug);
9189
9402
  const response = await http.get(`${resourcePath}/config`);
9190
9403
  if (!response.ok) {
9191
9404
  return releaseControlFailure("edge_functions.get_config", "HTTP_ERROR", response.status);
9192
9405
  }
9193
- const identity = projectedFunctionIdentity(response.data, projectRef, slug);
9406
+ const identity = projectedFunctionIdentity(response.data, projectRef2, slug);
9194
9407
  return identity ? { content: [{ type: "text", text: JSON.stringify(identity, null, 2) }] } : releaseControlFailure("edge_functions.get_config", "INVALID_RESPONSE", response.status);
9195
9408
  }
9196
9409
  async function updateFunctionConfiguration(http, request) {
@@ -9253,15 +9466,15 @@ function readOnlyActivationResult() {
9253
9466
  };
9254
9467
  }
9255
9468
  function functionActivationTarget(args) {
9256
- const projectRef = typeof args.ref === "string" ? args.ref.trim() : "";
9469
+ const projectRef2 = typeof args.ref === "string" ? args.ref.trim() : "";
9257
9470
  const functionSlug = typeof args.slug === "string" ? args.slug.trim() : "";
9258
9471
  const version = positiveFunctionVersion(args.version, "Function activation version");
9259
- projectRefPathSegment(projectRef, "Edge Function activation");
9472
+ projectRefPathSegment(projectRef2, "Edge Function activation");
9260
9473
  if (!SAFE_FUNCTION_SLUG_PATTERN.test(functionSlug))
9261
9474
  throw new Error("'slug' is invalid for 'activate'");
9262
9475
  const expectedActiveVersion = requiredExpectedActiveVersion(args, "activate");
9263
9476
  const expectedActivationId = requiredExpectedActivationId(args, "activate");
9264
- return { projectRef, functionSlug, version, expectedActiveVersion, expectedActivationId };
9477
+ return { projectRef: projectRef2, functionSlug, version, expectedActiveVersion, expectedActivationId };
9265
9478
  }
9266
9479
  function requiredExpectedActiveVersion(args, action) {
9267
9480
  const expected = args["expected-active-version"];
@@ -9289,11 +9502,11 @@ async function activateFunctionVersion(http, args, readOnly = false) {
9289
9502
  const unsupported = Object.keys(args).filter((name) => !FUNCTION_ACTIVATION_ARGUMENTS.has(name));
9290
9503
  if (unsupported.length > 0)
9291
9504
  throw new Error(`'${unsupported[0]}' is not supported for 'activate'`);
9292
- const { projectRef, functionSlug, version, expectedActiveVersion, expectedActivationId } = functionActivationTarget(args);
9293
- const endpoint = edgeFunctionResourcePath(projectRef, functionSlug) + `/versions/${encodeURIComponent(version)}/activate`;
9505
+ const { projectRef: projectRef2, functionSlug, version, expectedActiveVersion, expectedActivationId } = functionActivationTarget(args);
9506
+ const endpoint = edgeFunctionResourcePath(projectRef2, functionSlug) + `/versions/${encodeURIComponent(version)}/activate`;
9294
9507
  return functionMutationResponse({
9295
9508
  operation: "edge_functions.activate",
9296
- projectRef,
9509
+ projectRef: projectRef2,
9297
9510
  slug: functionSlug,
9298
9511
  expectedActiveVersion,
9299
9512
  expectedActivationId,
@@ -9979,6 +10192,115 @@ function projectGetRead(response, expectedRef) {
9979
10192
  return project ? successfulResult(project) : failedResult("Invalid project response");
9980
10193
  }
9981
10194
 
10195
+ // src/shared/tools/project-endpoint-read.ts
10196
+ var PROJECT_ENDPOINT_RESPONSE_MAX_BYTES = 256 * 1024;
10197
+ var PROJECT_ENDPOINT_LIST_RESPONSE_MAX_BYTES = 1024 * 1024;
10198
+ var PROJECT_REF_PATTERN4 = /^[a-z0-9-]{1,20}$/;
10199
+ var PROJECT_ENDPOINTS_SCHEMA = "supacloud.project-endpoints.v1";
10200
+ var PROJECT_ENDPOINT_SOURCES = new Set([
10201
+ "explicit_api_domain",
10202
+ "explicit_auth_domain",
10203
+ "explicit_studio_domain",
10204
+ "custom_domain",
10205
+ "derived_api_domain",
10206
+ "generated"
10207
+ ]);
10208
+ var ROOT_KEYS = new Set(["schema", "project_ref", "endpoints"]);
10209
+ var ENDPOINTS_KEYS = new Set(["api", "auth", "studio"]);
10210
+ var ENDPOINT_KEYS = new Set(["origin", "host", "scheme", "source", "aliases"]);
10211
+ var MAX_ALIASES = 64;
10212
+ function plainRecord2(candidate) {
10213
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate))
10214
+ return null;
10215
+ const prototype = Object.getPrototypeOf(candidate);
10216
+ return prototype === Object.prototype || prototype === null ? candidate : null;
10217
+ }
10218
+ function hasOnlyKeys2(record, allowedKeys) {
10219
+ return Object.keys(record).every((key) => allowedKeys.has(key));
10220
+ }
10221
+ function boundedText2(candidate, maxLength) {
10222
+ return typeof candidate === "string" && candidate.length > 0 && candidate.length <= maxLength && !/[\u0000-\u001f\u007f]/u.test(candidate) ? candidate : null;
10223
+ }
10224
+ function canonicalHost(candidate, scheme) {
10225
+ const host = boundedText2(candidate, 255);
10226
+ if (!host)
10227
+ return null;
10228
+ try {
10229
+ const parsed = new URL(`${scheme}://${host}`);
10230
+ return parsed.username || parsed.password || parsed.pathname !== "/" || parsed.search || parsed.hash || parsed.host !== host ? null : host;
10231
+ } catch {
10232
+ return null;
10233
+ }
10234
+ }
10235
+ function projectEndpoint2(candidate) {
10236
+ const endpoint = plainRecord2(candidate);
10237
+ if (!endpoint || !hasOnlyKeys2(endpoint, ENDPOINT_KEYS))
10238
+ return null;
10239
+ const scheme = endpoint.scheme === "http" || endpoint.scheme === "https" ? endpoint.scheme : null;
10240
+ const origin = boundedText2(endpoint.origin, 2048);
10241
+ const source = boundedText2(endpoint.source, 64);
10242
+ if (!scheme || !origin || !source || !PROJECT_ENDPOINT_SOURCES.has(source))
10243
+ return null;
10244
+ let parsedOrigin;
10245
+ try {
10246
+ parsedOrigin = new URL(origin);
10247
+ } catch {
10248
+ return null;
10249
+ }
10250
+ if (parsedOrigin.protocol !== `${scheme}:` || parsedOrigin.origin !== origin || parsedOrigin.username || parsedOrigin.password || parsedOrigin.pathname !== "/" || parsedOrigin.search || parsedOrigin.hash)
10251
+ return null;
10252
+ const host = canonicalHost(endpoint.host, scheme);
10253
+ if (!host || host !== parsedOrigin.host || !Array.isArray(endpoint.aliases) || endpoint.aliases.length > MAX_ALIASES)
10254
+ return null;
10255
+ const aliases = [];
10256
+ const seenAliases = new Set;
10257
+ for (const aliasCandidate of endpoint.aliases) {
10258
+ const alias = canonicalHost(aliasCandidate, scheme);
10259
+ if (!alias || alias === host || seenAliases.has(alias))
10260
+ return null;
10261
+ seenAliases.add(alias);
10262
+ aliases.push(alias);
10263
+ }
10264
+ return { origin, host, scheme, source, aliases };
10265
+ }
10266
+ function projectEndpointProjection(candidate) {
10267
+ const projection = plainRecord2(candidate);
10268
+ if (!projection || !hasOnlyKeys2(projection, ROOT_KEYS) || projection.schema !== PROJECT_ENDPOINTS_SCHEMA || typeof projection.project_ref !== "string" || !PROJECT_REF_PATTERN4.test(projection.project_ref))
10269
+ return null;
10270
+ const endpoints = plainRecord2(projection.endpoints);
10271
+ if (!endpoints || !hasOnlyKeys2(endpoints, ENDPOINTS_KEYS))
10272
+ return null;
10273
+ const api = projectEndpoint2(endpoints.api);
10274
+ const auth = projectEndpoint2(endpoints.auth);
10275
+ const studio = projectEndpoint2(endpoints.studio);
10276
+ return api && auth && studio ? {
10277
+ schema: PROJECT_ENDPOINTS_SCHEMA,
10278
+ project_ref: projection.project_ref,
10279
+ endpoints: { api, auth, studio }
10280
+ } : null;
10281
+ }
10282
+ function validHttpStatus2(status) {
10283
+ return Number.isSafeInteger(status) && status >= 100 && status <= 599;
10284
+ }
10285
+ function successfulResponse2(response) {
10286
+ return response.ok === true && validHttpStatus2(response.status) && response.status >= 200 && response.status <= 299;
10287
+ }
10288
+ function failedResult2(message) {
10289
+ return { text: `❌ ${message}`, isError: true };
10290
+ }
10291
+ function failedHttpResult2(label, status) {
10292
+ return failedResult2(validHttpStatus2(status) ? `${label} request failed (${status})` : `${label} request failed`);
10293
+ }
10294
+ function successfulResult2(payload) {
10295
+ return { text: JSON.stringify(payload, null, 2), isError: false };
10296
+ }
10297
+ function projectEndpointRead(response, expectedRef) {
10298
+ if (!successfulResponse2(response))
10299
+ return failedHttpResult2("Project endpoints", response.status);
10300
+ const projection = projectEndpointProjection(response.data);
10301
+ return projection && projection.project_ref === expectedRef ? successfulResult2(projection) : failedResult2("Invalid project endpoint response");
10302
+ }
10303
+
9982
10304
  // src/shared/tools/project-cli-tools.ts
9983
10305
  function projectReadResponse(readResult) {
9984
10306
  return {
@@ -10112,12 +10434,17 @@ function resolveRef(refFromArgs, defaultRef) {
10112
10434
  throw new Error("'ref' is required for this action");
10113
10435
  return ref;
10114
10436
  }
10437
+ function projectEndpointProjectionPath(ref) {
10438
+ return `/v1/projects/${encodeURIComponent(ref)}/endpoint/projection`;
10439
+ }
10115
10440
  function registerUserProjectCliTools(server, http, options = {}) {
10116
- const { projectRef } = options;
10441
+ const { projectRef: projectRef2 } = options;
10117
10442
  server.tool("project", `Project-scoped inspection and developer operations.
10118
- Actions: get, pause, restore, health, logs, api_keys, settings, tasks, task_detail, task_cancel, task_retry, task_stats, dlq, background_settings, update_background_settings`, {
10443
+ Actions: list (Admin guidance), get, endpoints, pause, restore, health, logs, api_keys, settings, tasks, task_detail, task_cancel, task_retry, task_stats, dlq, background_settings, update_background_settings`, {
10119
10444
  action: withDescription(stringEnum([
10445
+ "list",
10120
10446
  "get",
10447
+ "endpoints",
10121
10448
  "pause",
10122
10449
  "restore",
10123
10450
  "health",
@@ -10133,20 +10460,37 @@ Actions: get, pause, restore, health, logs, api_keys, settings, tasks, task_deta
10133
10460
  "background_settings",
10134
10461
  "update_background_settings"
10135
10462
  ]), "Action to perform"),
10136
- ref: optional(Type.String(), projectRef ? "Optional override when not auto-linked" : "Project ref"),
10463
+ ref: optional(Type.String(), projectRef2 ? "Optional override when not auto-linked" : "Project ref"),
10137
10464
  log_type: optional(stringEnum(["all", "auth", "database", "api"]), "[logs] Filter by service"),
10138
10465
  task_id: optional(Type.String(), "[task_detail/task_cancel/task_retry] Task ID"),
10139
10466
  limit: optional(Type.Number(), "[tasks/dlq] Max items to return"),
10140
10467
  concurrency: optional(Type.Number(), "[update_background_settings] Max concurrent background tasks"),
10141
10468
  max_attempts: optional(Type.Number(), "[update_background_settings] Max attempts for background tasks")
10142
10469
  }, async ({ action, ref, log_type, task_id, limit, concurrency, max_attempts }) => {
10143
- const resolvedRef = resolveRef(ref, projectRef);
10470
+ if (action === "list") {
10471
+ return {
10472
+ isError: true,
10473
+ content: [{
10474
+ type: "text",
10475
+ text: [
10476
+ "⚠️ Project enumeration is a platform administration operation.",
10477
+ "Use `supacloud-admin project list` with an admin Management API context."
10478
+ ].join(`
10479
+ `)
10480
+ }]
10481
+ };
10482
+ }
10483
+ const resolvedRef = resolveRef(ref, projectRef2);
10144
10484
  let text;
10145
10485
  switch (action) {
10146
10486
  case "get":
10147
10487
  return projectReadResponse(projectGetRead(await http.get(`/v1/projects/${resolvedRef}`, {
10148
10488
  maxResponseBytes: PROJECT_READ_RESPONSE_MAX_BYTES
10149
10489
  }), resolvedRef));
10490
+ case "endpoints":
10491
+ return projectReadResponse(projectEndpointRead(await http.get(projectEndpointProjectionPath(resolvedRef), {
10492
+ maxResponseBytes: PROJECT_ENDPOINT_RESPONSE_MAX_BYTES
10493
+ }), resolvedRef));
10150
10494
  case "pause":
10151
10495
  text = simple(await http.post(`/v1/projects/${resolvedRef}/pause`), `Project ${resolvedRef} paused`);
10152
10496
  break;
@@ -10311,7 +10655,7 @@ function resolveRef2(refFromArgs, defaultRef) {
10311
10655
  return ref;
10312
10656
  }
10313
10657
  function registerQueueTools(server, http, options = {}) {
10314
- const { projectRef } = options;
10658
+ const { projectRef: projectRef2 } = options;
10315
10659
  server.tool("queue", `Message queue operations for task-based messaging.
10316
10660
  Actions: list, stats, list_messages, dlq, get_message, send, receive, ack, release, fail, retry, delete_message, get_settings, update_settings`, {
10317
10661
  action: withDescription(stringEnum([
@@ -10351,7 +10695,7 @@ Actions: list, stats, list_messages, dlq, get_message, send, receive, ack, relea
10351
10695
  max_attempts_setting: optional(Type.Number(), "[update_settings] Max delivery attempts"),
10352
10696
  rate_limit: optional(Type.Number(), "[update_settings] Rate limit per minute")
10353
10697
  }, async (args) => {
10354
- const resolvedRef = resolveRef2(args.ref, projectRef);
10698
+ const resolvedRef = resolveRef2(args.ref, projectRef2);
10355
10699
  const q = args.queue;
10356
10700
  const need = (fields) => {
10357
10701
  for (const f of fields) {
@@ -10551,7 +10895,7 @@ var redirectStatus = Type.Optional(Type.Union([
10551
10895
  var ok2 = (res) => res.ok ? JSON.stringify(res.data, null, 2) : `❌ Failed (${res.status}): ${JSON.stringify(res.data)}`;
10552
10896
  var simple2 = (res, msg) => res.ok ? `✅ ${msg}` : `❌ Failed (${res.status}): ${JSON.stringify(res.data)}`;
10553
10897
  function registerGatewayTools(server, http, options = {}) {
10554
- const { projectRef } = options;
10898
+ const { projectRef: projectRef2 } = options;
10555
10899
  server.tool("gateway", `Gateway / Caddy 配置(通过 JSON Admin API 注入)。要求 admin 权限。
10556
10900
  Actions: routes, upsert_route, update_route, delete_route, config, get_certificate, update_certificate, issue_certificate, deploy_certificate, rebuild, custom_hostname, set_custom_hostname, delete_custom_hostname, verify_custom_hostname`, {
10557
10901
  action: withDescription(stringEnum([
@@ -10570,7 +10914,7 @@ Actions: routes, upsert_route, update_route, delete_route, config, get_certifica
10570
10914
  "delete_custom_hostname",
10571
10915
  "verify_custom_hostname"
10572
10916
  ]), "Action"),
10573
- ref: optional(Type.String(), projectRef ? "可选:覆盖自动关联的项目 ref" : "项目 ref"),
10917
+ ref: optional(Type.String(), projectRef2 ? "可选:覆盖自动关联的项目 ref" : "项目 ref"),
10574
10918
  route_id: optional(Type.String(), "[upsert_route/update_route/delete_route] 路由 ID(字母/数字/_/-,1-64)"),
10575
10919
  hosts: withDescription(stringArray, "[upsert_route/update_route] 主机名列表,逗号分隔或 JSON 数组(1-20)"),
10576
10920
  paths: withDescription(stringArray, "[upsert_route/update_route] 路径列表,逗号分隔或 JSON 数组(1-32)"),
@@ -10605,7 +10949,7 @@ Actions: routes, upsert_route, update_route, delete_route, config, get_certifica
10605
10949
  custom_hostname: optional(Type.String(), "[set_custom_hostname] 自定义域名")
10606
10950
  }, async (args) => {
10607
10951
  const resolveRef3 = (override) => {
10608
- const ref2 = override || projectRef;
10952
+ const ref2 = override || projectRef2;
10609
10953
  if (!ref2)
10610
10954
  throw new Error("'ref' is required for this action");
10611
10955
  return ref2;
@@ -10828,8 +11172,8 @@ Actions: routes, upsert_route, update_route, delete_route, config, get_certifica
10828
11172
  }
10829
11173
 
10830
11174
  // src/shared/tools/branch-tools.ts
10831
- function resolveProjectRef(ref, projectRef) {
10832
- const resolved = typeof ref === "string" && ref.trim() ? ref.trim() : projectRef || "";
11175
+ function resolveProjectRef(ref, projectRef2) {
11176
+ const resolved = typeof ref === "string" && ref.trim() ? ref.trim() : projectRef2 || "";
10833
11177
  if (!resolved)
10834
11178
  throw new Error("'ref' is required for this action");
10835
11179
  return resolved;
@@ -11281,14 +11625,14 @@ async function executeMigrationPush(request, runtime) {
11281
11625
  const pushMigrations = runtime.getPushMigrations?.();
11282
11626
  if (!pushMigrations)
11283
11627
  return missingMigrationContextResult();
11284
- const projectRef = request.ref || runtime.projectRef;
11285
- if (!projectRef)
11628
+ const projectRef2 = request.ref || runtime.projectRef;
11629
+ if (!projectRef2)
11286
11630
  return missingProjectRefResult();
11287
11631
  const workdir = resolveExistingWorkdir(request.workdir, runtime.fallbackWorkdir);
11288
11632
  const migrationDirectory = resolve3(workdir, request.dir || "supabase/migrations");
11289
11633
  const migrationResponse = await pushMigrations({
11290
11634
  action: "push_migrations",
11291
- ref: projectRef,
11635
+ ref: projectRef2,
11292
11636
  dir: migrationDirectory,
11293
11637
  dry_run: request.dry_run
11294
11638
  });
@@ -12223,7 +12567,7 @@ var INVENTORY_MAX_BYTES = 1024 * 1024;
12223
12567
  var MUTATION_MAX_BYTES = 64 * 1024;
12224
12568
  var BACKUP_TIMEOUT_MS = 36 * 60000;
12225
12569
  var RELEASE_READ_RESPONSE_TIMEOUT_MS = 5000;
12226
- function isRecord(value) {
12570
+ function isRecord2(value) {
12227
12571
  return value !== null && typeof value === "object" && !Array.isArray(value);
12228
12572
  }
12229
12573
  function canonicalTimestamp3(value) {
@@ -12235,15 +12579,15 @@ function canonicalTimestamp3(value) {
12235
12579
  function validProjectRef(ref) {
12236
12580
  return SAFE_PROJECT_REF.test(ref);
12237
12581
  }
12238
- function backupBelongsToProject(backupId, projectRef) {
12239
- return BACKUP_ID.test(backupId) && backupId.startsWith(`logical-full_${projectRef}_`);
12582
+ function backupBelongsToProject(backupId, projectRef2) {
12583
+ return BACKUP_ID.test(backupId) && backupId.startsWith(`logical-full_${projectRef2}_`);
12240
12584
  }
12241
- function verifiedBackup(value, projectRef) {
12242
- if (!isRecord(value) || typeof value.backup_id !== "string" || !backupBelongsToProject(value.backup_id, projectRef) || value.project_ref !== projectRef || 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))
12585
+ function verifiedBackup(value, projectRef2) {
12586
+ if (!isRecord2(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))
12243
12587
  return null;
12244
12588
  return {
12245
12589
  backup_id: value.backup_id,
12246
- project_ref: projectRef,
12590
+ project_ref: projectRef2,
12247
12591
  database: value.database,
12248
12592
  kind: "logical-full",
12249
12593
  created_at: value.created_at,
@@ -12252,10 +12596,10 @@ function verifiedBackup(value, projectRef) {
12252
12596
  sha256: value.sha256
12253
12597
  };
12254
12598
  }
12255
- function backupInventory(value, projectRef) {
12256
- if (!isRecord(value) || !Array.isArray(value.backups))
12599
+ function backupInventory(value, projectRef2) {
12600
+ if (!isRecord2(value) || !Array.isArray(value.backups))
12257
12601
  return null;
12258
- const backups = value.backups.map((backup) => verifiedBackup(backup, projectRef));
12602
+ const backups = value.backups.map((backup) => verifiedBackup(backup, projectRef2));
12259
12603
  if (backups.some((backup) => backup === null))
12260
12604
  return null;
12261
12605
  const inventory = backups;
@@ -12286,23 +12630,23 @@ function newlyCreatedBackup(before, after) {
12286
12630
  const additions = after.filter((backup) => !known.has(backup.backup_id));
12287
12631
  return additions.length === 1 ? additions[0] : null;
12288
12632
  }
12289
- function restoreRequest(projectRef, backupId, expectedSha256, restoreConfirmation) {
12290
- if (typeof backupId !== "string" || !backupBelongsToProject(backupId, projectRef)) {
12633
+ function restoreRequest(projectRef2, backupId, expectedSha256, restoreConfirmation) {
12634
+ if (typeof backupId !== "string" || !backupBelongsToProject(backupId, projectRef2)) {
12291
12635
  throw new Error("'backup_id' must identify a logical-full backup for 'ref'");
12292
12636
  }
12293
12637
  if (typeof expectedSha256 !== "string" || !SHA256.test(expectedSha256)) {
12294
12638
  throw new Error("'expected_sha256' must be a lowercase SHA-256 digest");
12295
12639
  }
12296
- const confirmation = `RESTORE_PROJECT:${projectRef}:${backupId}:${expectedSha256}`;
12640
+ const confirmation = `RESTORE_PROJECT:${projectRef2}:${backupId}:${expectedSha256}`;
12297
12641
  if (restoreConfirmation !== confirmation) {
12298
12642
  throw new Error("'restore_confirmation' must exactly confirm the selected logical backup restore");
12299
12643
  }
12300
12644
  return { backup_id: backupId, expected_sha256: expectedSha256, confirmation };
12301
12645
  }
12302
- function endpoint(projectRef) {
12303
- if (!validProjectRef(projectRef))
12646
+ function endpoint(projectRef2) {
12647
+ if (!validProjectRef(projectRef2))
12304
12648
  throw new Error("'ref' is invalid for release controls");
12305
- return `/v1/projects/${encodeURIComponent(projectRef)}`;
12649
+ return `/v1/projects/${encodeURIComponent(projectRef2)}`;
12306
12650
  }
12307
12651
  function httpFailure(operation, response) {
12308
12652
  if (response.responseReadError) {
@@ -12316,12 +12660,12 @@ function mutationFailure(operation, response) {
12316
12660
  }
12317
12661
  return releaseControlFailure(operation, "HTTP_ERROR", response.status);
12318
12662
  }
12319
- async function readInventory(http, projectRef) {
12320
- const response = await http.get(`${endpoint(projectRef)}/database/backups/logical`, {
12663
+ async function readInventory(http, projectRef2) {
12664
+ const response = await http.get(`${endpoint(projectRef2)}/database/backups/logical`, {
12321
12665
  maxJsonBytes: INVENTORY_MAX_BYTES,
12322
12666
  responseTimeoutMs: RELEASE_READ_RESPONSE_TIMEOUT_MS
12323
12667
  });
12324
- return { response, inventory: response.ok && response.status === 200 ? backupInventory(response.data, projectRef) : null };
12668
+ return { response, inventory: response.ok && response.status === 200 ? backupInventory(response.data, projectRef2) : null };
12325
12669
  }
12326
12670
  function readInventoryFailure(operation, read) {
12327
12671
  if (!read.response.ok)
@@ -12332,7 +12676,7 @@ function readInventoryFailure(operation, read) {
12332
12676
  return null;
12333
12677
  }
12334
12678
  function postgrestStatus(value) {
12335
- if (!isRecord(value) || value.component !== "postgrest" || !["running", "stopped"].includes(String(value.desired)) || !["running", "stopped", "starting", "error"].includes(String(value.actual)) || !["healthy", "unhealthy", "unknown"].includes(String(value.health)))
12679
+ if (!isRecord2(value) || value.component !== "postgrest" || !["running", "stopped"].includes(String(value.desired)) || !["running", "stopped", "starting", "error"].includes(String(value.actual)) || !["healthy", "unhealthy", "unknown"].includes(String(value.health)))
12336
12680
  return null;
12337
12681
  return {
12338
12682
  desired: value.desired,
@@ -12340,8 +12684,8 @@ function postgrestStatus(value) {
12340
12684
  health: value.health
12341
12685
  };
12342
12686
  }
12343
- async function readPostgrestStatus(http, projectRef) {
12344
- const response = await http.get(`${endpoint(projectRef)}/services/postgrest/status`, {
12687
+ async function readPostgrestStatus(http, projectRef2) {
12688
+ const response = await http.get(`${endpoint(projectRef2)}/services/postgrest/status`, {
12345
12689
  maxJsonBytes: MUTATION_MAX_BYTES,
12346
12690
  responseTimeoutMs: RELEASE_READ_RESPONSE_TIMEOUT_MS
12347
12691
  });
@@ -12353,7 +12697,7 @@ function readPostgrestFailure(operation, read) {
12353
12697
  return read.response.status === 200 && read.status ? null : releaseControlFailure(operation, "INVALID_RESPONSE", read.response.status);
12354
12698
  }
12355
12699
  function isRestartReceipt(value) {
12356
- return isRecord(value) && value.service === "postgrest" && value.action === "restart" && value.success === true;
12700
+ return isRecord2(value) && value.service === "postgrest" && value.action === "restart" && value.success === true;
12357
12701
  }
12358
12702
  function registerReleaseTools(server, http, options = {}) {
12359
12703
  server.tool("release", "Verified release controls using a Management API credential. Actions: logical_backup_list, logical_backup_create, logical_backup_restore, postgrest_status, postgrest_restart", {
@@ -12369,45 +12713,45 @@ function registerReleaseTools(server, http, options = {}) {
12369
12713
  expected_sha256: optional(Type.String(), "[logical_backup_restore] Exact lowercase SHA-256 from the selected project inventory"),
12370
12714
  restore_confirmation: optional(Type.String(), "[logical_backup_restore] Exact RESTORE_PROJECT:<ref>:<backup_id>:<sha256> confirmation")
12371
12715
  }, async ({ action, ref, backup_id, expected_sha256, restore_confirmation }) => {
12372
- const projectRef = typeof ref === "string" && ref || options.projectRef;
12373
- if (!projectRef)
12716
+ const projectRef2 = typeof ref === "string" && ref || options.projectRef;
12717
+ if (!projectRef2)
12374
12718
  throw new Error("'ref' is required for release controls");
12375
- if (!validProjectRef(projectRef))
12719
+ if (!validProjectRef(projectRef2))
12376
12720
  throw new Error("'ref' is invalid for release controls");
12377
12721
  if (action === "logical_backup_list") {
12378
- const read2 = await readInventory(http, projectRef);
12722
+ const read2 = await readInventory(http, projectRef2);
12379
12723
  const failure = readInventoryFailure("release.logical_backup.list", read2);
12380
12724
  return failure ?? releaseControlSuccess("release.logical_backup.list", {
12381
- project_ref: projectRef,
12725
+ project_ref: projectRef2,
12382
12726
  backups: read2.inventory.map(publicBackup)
12383
12727
  });
12384
12728
  }
12385
12729
  if (action === "logical_backup_create") {
12386
- const before = await readInventory(http, projectRef);
12730
+ const before = await readInventory(http, projectRef2);
12387
12731
  const beforeFailure = readInventoryFailure("release.logical_backup.create", before);
12388
12732
  if (beforeFailure)
12389
12733
  return beforeFailure;
12390
- const mutation2 = await http.postReleaseMutation(`${endpoint(projectRef)}/database/backups/logical`, {}, {
12734
+ const mutation2 = await http.postReleaseMutation(`${endpoint(projectRef2)}/database/backups/logical`, {}, {
12391
12735
  timeoutMs: BACKUP_TIMEOUT_MS
12392
12736
  });
12393
- const after = await readInventory(http, projectRef);
12737
+ const after = await readInventory(http, projectRef2);
12394
12738
  if (!mutation2.ok || mutation2.status !== 200) {
12395
12739
  return mutationFailure("release.logical_backup.create", mutation2);
12396
12740
  }
12397
- const responseBackup = isRecord(mutation2.data) ? verifiedBackup(mutation2.data.backup, projectRef) : null;
12741
+ const responseBackup = isRecord2(mutation2.data) ? verifiedBackup(mutation2.data.backup, projectRef2) : null;
12398
12742
  const afterFailure = readInventoryFailure("release.logical_backup.create", after);
12399
12743
  const addedBackup = after.inventory && newlyCreatedBackup(before.inventory, after.inventory);
12400
12744
  if (!responseBackup || afterFailure || !addedBackup || !equalBackup(responseBackup, addedBackup)) {
12401
12745
  return releaseControlFailure("release.logical_backup.create", "OUTCOME_UNKNOWN", mutation2.status);
12402
12746
  }
12403
12747
  return releaseControlSuccess("release.logical_backup.create", {
12404
- project_ref: projectRef,
12748
+ project_ref: projectRef2,
12405
12749
  backup: publicBackup(addedBackup)
12406
12750
  });
12407
12751
  }
12408
12752
  if (action === "logical_backup_restore") {
12409
- const request = restoreRequest(projectRef, backup_id, expected_sha256, restore_confirmation);
12410
- const before = await readInventory(http, projectRef);
12753
+ const request = restoreRequest(projectRef2, backup_id, expected_sha256, restore_confirmation);
12754
+ const before = await readInventory(http, projectRef2);
12411
12755
  const beforeFailure = readInventoryFailure("release.logical_backup.restore", before);
12412
12756
  if (beforeFailure)
12413
12757
  return beforeFailure;
@@ -12415,43 +12759,43 @@ function registerReleaseTools(server, http, options = {}) {
12415
12759
  if (!selectedBackup) {
12416
12760
  return releaseControlFailure("release.logical_backup.restore", "MUTATION_NOT_SUCCEEDED", null);
12417
12761
  }
12418
- const mutation2 = await http.postReleaseMutation(`${endpoint(projectRef)}/database/backups/logical/restore`, request, { timeoutMs: BACKUP_TIMEOUT_MS });
12762
+ const mutation2 = await http.postReleaseMutation(`${endpoint(projectRef2)}/database/backups/logical/restore`, request, { timeoutMs: BACKUP_TIMEOUT_MS });
12419
12763
  if (!mutation2.ok || mutation2.status !== 200) {
12420
12764
  return mutationFailure("release.logical_backup.restore", mutation2);
12421
12765
  }
12422
- const responseBackup = isRecord(mutation2.data) ? verifiedBackup(mutation2.data.restored_backup, projectRef) : null;
12423
- const after = await readInventory(http, projectRef);
12766
+ const responseBackup = isRecord2(mutation2.data) ? verifiedBackup(mutation2.data.restored_backup, projectRef2) : null;
12767
+ const after = await readInventory(http, projectRef2);
12424
12768
  const afterFailure = readInventoryFailure("release.logical_backup.restore", after);
12425
12769
  const restoredInventoryBackup = after.inventory?.find((backup) => backup.backup_id === request.backup_id);
12426
12770
  if (!responseBackup || !equalBackup(responseBackup, selectedBackup) || afterFailure || !restoredInventoryBackup || !equalBackup(restoredInventoryBackup, selectedBackup)) {
12427
12771
  return releaseControlFailure("release.logical_backup.restore", "OUTCOME_UNKNOWN", mutation2.status);
12428
12772
  }
12429
12773
  return releaseControlSuccess("release.logical_backup.restore", {
12430
- project_ref: projectRef,
12774
+ project_ref: projectRef2,
12431
12775
  backup: publicBackup(selectedBackup)
12432
12776
  });
12433
12777
  }
12434
12778
  if (action === "postgrest_status") {
12435
- const read2 = await readPostgrestStatus(http, projectRef);
12779
+ const read2 = await readPostgrestStatus(http, projectRef2);
12436
12780
  const failure = readPostgrestFailure("release.postgrest.status", read2);
12437
12781
  return failure ?? releaseControlSuccess("release.postgrest.status", {
12438
- project_ref: projectRef,
12782
+ project_ref: projectRef2,
12439
12783
  postgrest: read2.status
12440
12784
  });
12441
12785
  }
12442
12786
  if (action !== "postgrest_restart")
12443
12787
  throw new Error("Unknown release control action");
12444
- const mutation = await http.postReleaseMutation(`${endpoint(projectRef)}/services/postgrest/restart`);
12445
- const read = await readPostgrestStatus(http, projectRef);
12788
+ const mutation = await http.postReleaseMutation(`${endpoint(projectRef2)}/services/postgrest/restart`);
12789
+ const read = await readPostgrestStatus(http, projectRef2);
12446
12790
  if (!mutation.ok || mutation.status !== 200) {
12447
12791
  return mutationFailure("release.postgrest.restart", mutation);
12448
12792
  }
12449
- const readFailure = readPostgrestFailure("release.postgrest.restart", read);
12450
- if (!isRestartReceipt(mutation.data) || readFailure || read.status.desired !== "running" || read.status.actual !== "running" || read.status.health !== "healthy") {
12793
+ const readFailure2 = readPostgrestFailure("release.postgrest.restart", read);
12794
+ if (!isRestartReceipt(mutation.data) || readFailure2 || read.status.desired !== "running" || read.status.actual !== "running" || read.status.health !== "healthy") {
12451
12795
  return releaseControlFailure("release.postgrest.restart", "OUTCOME_UNKNOWN", mutation.status);
12452
12796
  }
12453
12797
  return releaseControlSuccess("release.postgrest.restart", {
12454
- project_ref: projectRef,
12798
+ project_ref: projectRef2,
12455
12799
  postgrest: read.status
12456
12800
  });
12457
12801
  });
@@ -12459,7 +12803,7 @@ function registerReleaseTools(server, http, options = {}) {
12459
12803
  // package.json
12460
12804
  var package_default = {
12461
12805
  name: "@supacloud/cli",
12462
- version: "0.25.0",
12806
+ version: "0.27.0",
12463
12807
  description: "Project-scoped CLI for SupaCloud users",
12464
12808
  type: "module",
12465
12809
  main: "./dist/index.js",
@@ -12801,7 +13145,7 @@ function createCliTools(context, confirmProduction) {
12801
13145
  ]
12802
13146
  })
12803
13147
  };
12804
- for (const name of ["database", "auth", "storage", "edge_functions", "secrets", "frontend", "queue", "task_events", "scheduled_functions", "mutations", "diagnostics", "gateway", "branch", "release"]) {
13148
+ for (const name of ["database", "auth", "oauth_clients", "storage", "edge_functions", "secrets", "frontend", "queue", "task_events", "scheduled_functions", "mutations", "diagnostics", "gateway", "branch", "release"]) {
12805
13149
  tools[name] = {
12806
13150
  schema: { action: genericActionSchema },
12807
13151
  callback: async () => ({
@@ -12878,6 +13222,7 @@ function createCliTools(context, confirmProduction) {
12878
13222
  pushMigrations = databaseTools.database?.callback;
12879
13223
  assign(databaseTools);
12880
13224
  assign(captureTools((server) => registerAuthTools(server, http)));
13225
+ assign(captureTools((server) => registerOAuthClientTools(server, http)));
12881
13226
  assign(captureTools((server) => registerStorageTools(server, http)));
12882
13227
  assign(captureTools((server) => registerAdvancedTools(server, http, process.env, {
12883
13228
  readOnly: context.readOnly
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/cli",
3
- "version": "0.25.0",
3
+ "version": "0.27.0",
4
4
  "description": "Project-scoped CLI for SupaCloud users",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: supacloud-cli
3
- description: Use when operating, implementing, diagnosing, deploying, or documenting a SupaCloud project through supacloud-cli, especially database schema, functions/RPC, triggers, RLS, indexes, grants, extensions, migrations, backups, auth, storage, Edge Functions, frontend, queues, task events, diagnostics, or gateway work. Also use when an AI might otherwise call SQL, psql, a database API, or the Management API directly.
3
+ description: Use when operating, implementing, diagnosing, deploying, or documenting a SupaCloud project through supacloud-cli, especially project endpoint discovery, database schema, functions/RPC, triggers, RLS, indexes, grants, extensions, migrations, backups, auth, storage, Edge Functions, frontend, queues, task events, diagnostics, or gateway work. Also use when an AI might otherwise call SQL, psql, a database API, or the Management API directly.
4
4
  ---
5
5
 
6
6
  # SupaCloud CLI
@@ -24,6 +24,8 @@ Use `supacloud-cli` as the project-level control surface and keep durable change
24
24
  5. Run a remote migration dry-run before apply. Production apply requires explicit user approval in the current task.
25
25
  6. Do not edit `supabase_migrations.schema_migrations` through ordinary SQL. Migration history is an application ledger, not a schema backup or source of truth. For a proven-equivalent historical baseline, use the controlled `database baseline_migrations` action with dry-run and explicit approval.
26
26
  7. Service-role credentials authenticate the SupaCloud Management API. Never reinterpret them as PostgreSQL passwords or forward them to the official Supabase CLI.
27
+ 8. Read project domains through `supacloud-cli project endpoints`. Do not infer API, Auth, or Studio origins by concatenating project refs and base domains. The projection reports configuration, not DNS/certificate/runtime readiness.
28
+ 9. Cross-project enumeration is an Admin boundary. Use `supacloud-admin project list` or `supacloud-admin project list_endpoints`; do not attempt to widen project credentials.
27
29
 
28
30
  ## Workflow
29
31
 
@@ -36,6 +38,20 @@ Use `supacloud-cli` as the project-level control surface and keep durable change
36
38
  7. Apply only within the user-authorized environment and scope.
37
39
  8. Read back migration history and affected resources; report exact evidence and any remaining drift.
38
40
 
41
+ ## Project endpoint inspection
42
+
43
+ For the selected project:
44
+
45
+ ```bash
46
+ supacloud-cli status
47
+ supacloud-cli project endpoints
48
+ ```
49
+
50
+ The fixed projection contains credential-free API/Auth/Studio origins, hosts,
51
+ schemes, source classifications, and API aliases. Follow it with the relevant
52
+ health or gateway command before claiming that DNS, TLS, routing, or a runtime is
53
+ ready. Project-wide or fleet-wide inventories belong to `supacloud-admin`.
54
+
39
55
  ## Database default
40
56
 
41
57
  For a new database change:
@@ -66,8 +82,8 @@ supacloud-cli database baseline_migrations \
66
82
 
67
83
  ## CLI boundaries
68
84
 
69
- - `supacloud-cli`: project status, database, migrations, auth, storage, Edge Functions, frontend, queues, task events, diagnostics, and project gateway configuration.
70
- - `supacloud-admin`: installation, upgrades, SSH diagnostics, platform-wide project lifecycle, tenant runtime, and server operations.
85
+ - `supacloud-cli`: selected-project status and endpoint projection, database, migrations, auth, storage, Edge Functions, frontend, queues, task events, diagnostics, and project gateway configuration.
86
+ - `supacloud-admin`: installation, upgrades, SSH diagnostics, platform-wide project/endpoint inventory, project lifecycle, tenant runtime, and server operations.
71
87
  - Official `supabase` CLI: invoked only through the allowlisted `supacloud-cli supabase` adapter for supported local authoring or explicit-DSN inspection commands.
72
88
  - Direct HTTP/SQL: read-only diagnosis or an explicitly approved break-glass path; never the default implementation path.
73
89
 
@@ -7,6 +7,8 @@ Load this reference when selecting a command surface or when a user asks an AI t
7
7
  | Intent | Use | Guardrail |
8
8
  | --- | --- | --- |
9
9
  | Inspect current project binding | `supacloud-cli status` | Read-only; run first |
10
+ | Inspect selected project API/Auth/Studio origins | `supacloud-cli project endpoints` | Uses the Management API's authoritative projection; do not reconstruct domains locally |
11
+ | Enumerate projects or endpoint projections | `supacloud-admin project list` / `project list_endpoints` | Platform-wide read; never promote a project credential to Admin authority |
10
12
  | Inspect project health/logs/tasks | `project`, `queue`, `task_events`, `diagnostics` | Prefer bounded reads |
11
13
  | Read database rows or metadata | `database query` and database inspection actions | `SELECT`/read-only by default |
12
14
  | Create schema/function/RPC/trigger/RLS/index/grant/extension | `supabase migration_new`, then edit SQL | Never direct remote DDL |
@@ -26,7 +28,7 @@ until a project-scoped context is resolved.
26
28
  ## Command groups
27
29
 
28
30
  - `status`: resolved context, Management API connectivity, authentication, and project reachability.
29
- - `project`: project metadata, health, logs, API keys/settings, background tasks, retry/cancel, DLQ, and background settings.
31
+ - `project`: selected-project metadata, authoritative endpoint projection, health, logs, API keys/settings, background tasks, retry/cancel, DLQ, and background settings. `project list` deliberately redirects to `supacloud-admin`; cross-project enumeration is not a project CLI capability.
30
32
  - `database`: read/query, schema inspection, extensions, indexes, RLS, stats, migration push, controlled historical baseline, and SQL-file execution.
31
33
  - `supabase`: allowlisted official CLI adapter for migration authoring, local reset/diff, explicit-DSN inspection/backup/type generation, and SupaCloud-controlled migration push.
32
34
  - `auth`: provider and authentication configuration.
@@ -43,8 +45,14 @@ until a project-scoped context is resolved.
43
45
  ```bash
44
46
  supacloud-cli status
45
47
  supacloud-cli project get
48
+ supacloud-cli project endpoints
46
49
  supacloud-cli project health
47
50
  supacloud-cli supabase migration_list --db_url "$SUPACLOUD_DB_URL"
48
51
  ```
49
52
 
53
+ The endpoint projection returns bounded, credential-free API/Auth/Studio origins,
54
+ canonical hosts, URL schemes, configuration sources, and API aliases. It does
55
+ not assert DNS, certificate, or runtime readiness; use the relevant health and
56
+ gateway inspection commands for those checks.
57
+
50
58
  Do not paste the DSN value into chat or commit it to shell scripts. Prefer an environment variable supplied outside the repository.