@supacloud/cli 0.24.0 → 0.26.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 +16 -0
  2. package/dist/index.js +298 -74
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -113,6 +113,22 @@ logical-backup and PostgREST lifecycle capabilities. It requires the Management
113
113
  API context above; it does not promote an application `service_role` key to
114
114
  Management authority.
115
115
 
116
+ Project pause and restore are explicit lifecycle commands. A logical backup
117
+ restore requires an operator to pause the selected project first and then use
118
+ `project get` to confirm that its status is `paused`. `project restore` resumes
119
+ the paused project lifecycle when its database exists; if the database is
120
+ missing, the platform begins project re-provisioning instead. It does not
121
+ restore a database backup and is never invoked automatically by the CLI.
122
+
123
+ ```bash
124
+ supacloud-cli project pause --ref abc123
125
+ supacloud-cli release logical_backup_restore --ref abc123 \
126
+ --backup_id logical-full_abc123_<backup-id-suffix> \
127
+ --expected_sha256 <64-lowercase-hex> \
128
+ --restore_confirmation RESTORE_PROJECT:abc123:logical-full_abc123_<backup-id-suffix>:<64-lowercase-hex>
129
+ supacloud-cli project restore --ref abc123
130
+ ```
131
+
116
132
  ```bash
117
133
  supacloud-cli release logical_backup_list --ref abc123
118
134
  supacloud-cli release logical_backup_create --ref abc123
package/dist/index.js CHANGED
@@ -6459,7 +6459,7 @@ function resolveSupaCloudContext(env = process.env, cwd = process.cwd(), selecti
6459
6459
  var ACTION_POLICY = {
6460
6460
  project: {
6461
6461
  read: ["get", "health", "logs", "api_keys", "settings", "tasks", "task_detail", "task_stats", "dlq", "background_settings"],
6462
- write: ["task_cancel", "task_retry", "update_background_settings"]
6462
+ write: ["pause", "restore", "task_cancel", "task_retry", "update_background_settings"]
6463
6463
  },
6464
6464
  database: {
6465
6465
  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 +6473,10 @@ var ACTION_POLICY = {
6473
6473
  read: ["list_providers", "get_provider", "supported_providers", "get_settings", "get_config"],
6474
6474
  write: ["configure_provider", "update_provider", "disable_provider", "wechat_mini", "wechat_open", "update_settings", "update_config"]
6475
6475
  },
6476
+ oauth_clients: {
6477
+ read: ["list", "get"],
6478
+ write: ["create", "delete"]
6479
+ },
6476
6480
  storage: {
6477
6481
  read: ["status", "list_buckets", "get_bucket", "list_files"],
6478
6482
  write: ["create_bucket", "update_bucket", "delete_bucket", "upload_base64", "delete_file"]
@@ -8170,6 +8174,214 @@ ${JSON.stringify(r.data, null, 2)}` : `❌ Failed (${r.status}): ${JSON.stringif
8170
8174
  });
8171
8175
  }
8172
8176
 
8177
+ // src/shared/tools/oauth-client-tools.ts
8178
+ var RELEASE_CANARY_CLIENT_NAME = "supacloud-release-canary";
8179
+ var CLIENT_ID_PATTERN = /^[A-Za-z0-9._~-]{1,256}$/;
8180
+ var MAX_CLIENT_LIST_BYTES = 256 * 1024;
8181
+ var READ_TIMEOUT_MS = 5000;
8182
+ function isRecord(value) {
8183
+ return value !== null && typeof value === "object" && !Array.isArray(value);
8184
+ }
8185
+ function releaseCanaryCallbackUri(value) {
8186
+ if (typeof value !== "string" || !value.trim()) {
8187
+ throw new Error("'redirect_uri' is required");
8188
+ }
8189
+ let uri;
8190
+ try {
8191
+ uri = new URL(value);
8192
+ } catch {
8193
+ throw new Error("'redirect_uri' must be an absolute HTTPS or loopback HTTP URL");
8194
+ }
8195
+ const loopback = uri.hostname === "127.0.0.1" || uri.hostname === "[::1]";
8196
+ const isHttps = uri.protocol === "https:";
8197
+ const isPortBoundLoopback = uri.protocol === "http:" && loopback && Boolean(uri.port);
8198
+ if (!isHttps && !isPortBoundLoopback || !uri.hostname || uri.username || uri.password || uri.search || uri.hash) {
8199
+ throw new Error("'redirect_uri' must be an exact HTTPS callback or port-bound loopback HTTP callback without credentials, query, or fragment");
8200
+ }
8201
+ return uri.toString();
8202
+ }
8203
+ function createdClientId(value) {
8204
+ if (!isRecord(value))
8205
+ return null;
8206
+ try {
8207
+ return clientId(value.client_id);
8208
+ } catch {
8209
+ return null;
8210
+ }
8211
+ }
8212
+ function clientId(value) {
8213
+ if (typeof value !== "string" || !CLIENT_ID_PATTERN.test(value)) {
8214
+ throw new Error("'client_id' is invalid");
8215
+ }
8216
+ return value;
8217
+ }
8218
+ function projectRef(value) {
8219
+ if (typeof value !== "string" || !value.trim())
8220
+ throw new Error("'ref' is required");
8221
+ return projectRefPathSegment(value.trim(), "OAuth client");
8222
+ }
8223
+ function oauthClientsPath(ref) {
8224
+ return `/v1/projects/${encodeURIComponent(ref)}/auth/oauth-clients`;
8225
+ }
8226
+ function expectedClient(value, redirectUri) {
8227
+ 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")
8228
+ return null;
8229
+ let callback;
8230
+ try {
8231
+ callback = releaseCanaryCallbackUri(value.redirect_uris[0]);
8232
+ } catch {
8233
+ return null;
8234
+ }
8235
+ if (redirectUri !== undefined && callback !== redirectUri)
8236
+ return null;
8237
+ return {
8238
+ client_id: value.client_id,
8239
+ client_name: RELEASE_CANARY_CLIENT_NAME,
8240
+ client_type: "public",
8241
+ token_endpoint_auth_method: "none",
8242
+ redirect_uris: [callback],
8243
+ grant_types: ["authorization_code"],
8244
+ response_types: ["code"]
8245
+ };
8246
+ }
8247
+ function clientInventory(value) {
8248
+ if (!isRecord(value) || !Array.isArray(value.clients))
8249
+ return null;
8250
+ const clients = value.clients.filter((client) => isRecord(client) && client.client_name === RELEASE_CANARY_CLIENT_NAME).map((client) => expectedClient(client));
8251
+ if (clients.some((client) => client === null))
8252
+ return null;
8253
+ const inventory = clients;
8254
+ return new Set(inventory.map((client) => client.client_id)).size === inventory.length ? inventory : null;
8255
+ }
8256
+ function readFailure(operation, response) {
8257
+ if (!response.ok) {
8258
+ return releaseControlFailure(operation, response.responseReadError ? "INVALID_RESPONSE" : "HTTP_ERROR", response.transportError ? null : response.status);
8259
+ }
8260
+ return null;
8261
+ }
8262
+ async function listClients(http, ref) {
8263
+ const response = await http.get(oauthClientsPath(ref), {
8264
+ maxJsonBytes: MAX_CLIENT_LIST_BYTES,
8265
+ responseTimeoutMs: READ_TIMEOUT_MS
8266
+ });
8267
+ return { response, clients: response.ok && response.status === 200 ? clientInventory(response.data) : null };
8268
+ }
8269
+ async function getClient(http, ref, id) {
8270
+ const response = await http.get(`${oauthClientsPath(ref)}/${encodeURIComponent(id)}`, {
8271
+ maxJsonBytes: MAX_CLIENT_LIST_BYTES,
8272
+ responseTimeoutMs: READ_TIMEOUT_MS
8273
+ });
8274
+ return { response, client: response.ok && response.status === 200 ? expectedClient(response.data) : null };
8275
+ }
8276
+ function exactSingleClient(inventory, redirectUri) {
8277
+ return inventory.length === 1 && inventory[0]?.redirect_uris[0] === redirectUri ? inventory[0] : null;
8278
+ }
8279
+ async function listReleaseCanaryClients(http, ref) {
8280
+ const read = await listClients(http, ref);
8281
+ const failure = readFailure("oauth_clients.list", read.response);
8282
+ if (failure)
8283
+ return failure;
8284
+ if (!read.clients)
8285
+ return releaseControlFailure("oauth_clients.list", "INVALID_RESPONSE", read.response.status);
8286
+ return releaseControlSuccess("oauth_clients.list", { project_ref: ref, clients: read.clients });
8287
+ }
8288
+ async function getReleaseCanaryClient(http, ref, id) {
8289
+ const read = await getClient(http, ref, id);
8290
+ const failure = readFailure("oauth_clients.get", read.response);
8291
+ if (failure)
8292
+ return failure;
8293
+ if (!read.client || read.client.client_id !== id) {
8294
+ return releaseControlFailure("oauth_clients.get", "INVALID_RESPONSE", read.response.status);
8295
+ }
8296
+ return releaseControlSuccess("oauth_clients.get", { project_ref: ref, client: read.client });
8297
+ }
8298
+ async function createReleaseCanaryClient(http, ref, redirectUri) {
8299
+ const before = await listClients(http, ref);
8300
+ const beforeFailure = readFailure("oauth_clients.create", before.response);
8301
+ if (beforeFailure)
8302
+ return beforeFailure;
8303
+ if (!before.clients)
8304
+ return releaseControlFailure("oauth_clients.create", "INVALID_RESPONSE", before.response.status);
8305
+ const existing = exactSingleClient(before.clients, redirectUri);
8306
+ if (existing) {
8307
+ return releaseControlSuccess("oauth_clients.create", {
8308
+ project_ref: ref,
8309
+ client: existing,
8310
+ reused: true
8311
+ });
8312
+ }
8313
+ if (before.clients.length > 0) {
8314
+ return releaseControlFailure("oauth_clients.create", "MUTATION_NOT_SUCCEEDED", null, { project_ref: ref });
8315
+ }
8316
+ const mutation = await http.postReleaseMutation(oauthClientsPath(ref), {
8317
+ client_type: "public",
8318
+ token_endpoint_auth_method: "none",
8319
+ redirect_uris: [redirectUri],
8320
+ grant_types: ["authorization_code"],
8321
+ client_name: RELEASE_CANARY_CLIENT_NAME
8322
+ });
8323
+ if (!mutation.ok) {
8324
+ return releaseControlMutationFailure("oauth_clients.create", mutation, { project_ref: ref });
8325
+ }
8326
+ const createdId = createdClientId(mutation.data);
8327
+ if (!createdId)
8328
+ return releaseControlFailure("oauth_clients.create", "OUTCOME_UNKNOWN", mutation.status, { project_ref: ref });
8329
+ const read = await getClient(http, ref, createdId);
8330
+ const readFailureResult = readFailure("oauth_clients.create", read.response);
8331
+ if (readFailureResult || !read.client || read.client.client_id !== createdId || read.client.redirect_uris[0] !== redirectUri) {
8332
+ return releaseControlFailure("oauth_clients.create", "OUTCOME_UNKNOWN", mutation.status, { project_ref: ref });
8333
+ }
8334
+ return releaseControlSuccess("oauth_clients.create", {
8335
+ project_ref: ref,
8336
+ client: read.client,
8337
+ reused: false
8338
+ });
8339
+ }
8340
+ async function deleteReleaseCanaryClient(http, ref, id, redirectUri) {
8341
+ const before = await getClient(http, ref, id);
8342
+ const beforeFailure = readFailure("oauth_clients.delete", before.response);
8343
+ if (beforeFailure)
8344
+ return beforeFailure;
8345
+ if (!before.client || before.client.client_id !== id || before.client.redirect_uris[0] !== redirectUri) {
8346
+ return releaseControlFailure("oauth_clients.delete", "MUTATION_NOT_SUCCEEDED", null, { project_ref: ref });
8347
+ }
8348
+ const mutation = await http.deleteReleaseMutation(`${oauthClientsPath(ref)}/${encodeURIComponent(id)}`);
8349
+ if (!mutation.ok || ![200, 204].includes(mutation.status)) {
8350
+ return releaseControlMutationFailure("oauth_clients.delete", mutation, { project_ref: ref });
8351
+ }
8352
+ const after = await listClients(http, ref);
8353
+ const afterFailure = readFailure("oauth_clients.delete", after.response);
8354
+ if (afterFailure || !after.clients || after.clients.some((client) => client.client_id === id)) {
8355
+ return releaseControlFailure("oauth_clients.delete", "OUTCOME_UNKNOWN", mutation.status, { project_ref: ref });
8356
+ }
8357
+ return releaseControlSuccess("oauth_clients.delete", {
8358
+ project_ref: ref,
8359
+ client_id: id,
8360
+ deleted: true
8361
+ });
8362
+ }
8363
+ function registerOAuthClientTools(server, http) {
8364
+ 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.", {
8365
+ action: withDescription(stringEnum(["list", "get", "create", "delete"]), "OAuth client action"),
8366
+ ref: withDescription(Type.String(), "Central SupAuth project ref"),
8367
+ client_id: optional(Type.String(), "[get/delete] Exact release-canary public OAuth client ID"),
8368
+ redirect_uri: optional(Type.String(), "[create/delete] Exact HTTPS or port-bound RFC 8252 loopback callback")
8369
+ }, async ({ action, ref, client_id, redirect_uri }) => {
8370
+ const targetRef = projectRef(ref);
8371
+ if (action === "list")
8372
+ return listReleaseCanaryClients(http, targetRef);
8373
+ if (action === "get")
8374
+ return getReleaseCanaryClient(http, targetRef, clientId(client_id));
8375
+ if (action === "create") {
8376
+ return createReleaseCanaryClient(http, targetRef, releaseCanaryCallbackUri(redirect_uri));
8377
+ }
8378
+ if (action === "delete") {
8379
+ return deleteReleaseCanaryClient(http, targetRef, clientId(client_id), releaseCanaryCallbackUri(redirect_uri));
8380
+ }
8381
+ throw new Error("Unknown OAuth client action");
8382
+ });
8383
+ }
8384
+
8173
8385
  // src/shared/tools/storage-tools.ts
8174
8386
  var MAX_BUCKET_ID_LENGTH = 100;
8175
8387
  var MAX_MIME_TYPE_COUNT = 100;
@@ -9184,13 +9396,13 @@ async function readFunctionSource(http, request) {
9184
9396
  function mutationIdentityMatches2(receipt, expectation) {
9185
9397
  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
9398
  }
9187
- async function readFunctionIdentity(http, projectRef, slug) {
9188
- const resourcePath = edgeFunctionResourcePath(projectRef, slug);
9399
+ async function readFunctionIdentity(http, projectRef2, slug) {
9400
+ const resourcePath = edgeFunctionResourcePath(projectRef2, slug);
9189
9401
  const response = await http.get(`${resourcePath}/config`);
9190
9402
  if (!response.ok) {
9191
9403
  return releaseControlFailure("edge_functions.get_config", "HTTP_ERROR", response.status);
9192
9404
  }
9193
- const identity = projectedFunctionIdentity(response.data, projectRef, slug);
9405
+ const identity = projectedFunctionIdentity(response.data, projectRef2, slug);
9194
9406
  return identity ? { content: [{ type: "text", text: JSON.stringify(identity, null, 2) }] } : releaseControlFailure("edge_functions.get_config", "INVALID_RESPONSE", response.status);
9195
9407
  }
9196
9408
  async function updateFunctionConfiguration(http, request) {
@@ -9253,15 +9465,15 @@ function readOnlyActivationResult() {
9253
9465
  };
9254
9466
  }
9255
9467
  function functionActivationTarget(args) {
9256
- const projectRef = typeof args.ref === "string" ? args.ref.trim() : "";
9468
+ const projectRef2 = typeof args.ref === "string" ? args.ref.trim() : "";
9257
9469
  const functionSlug = typeof args.slug === "string" ? args.slug.trim() : "";
9258
9470
  const version = positiveFunctionVersion(args.version, "Function activation version");
9259
- projectRefPathSegment(projectRef, "Edge Function activation");
9471
+ projectRefPathSegment(projectRef2, "Edge Function activation");
9260
9472
  if (!SAFE_FUNCTION_SLUG_PATTERN.test(functionSlug))
9261
9473
  throw new Error("'slug' is invalid for 'activate'");
9262
9474
  const expectedActiveVersion = requiredExpectedActiveVersion(args, "activate");
9263
9475
  const expectedActivationId = requiredExpectedActivationId(args, "activate");
9264
- return { projectRef, functionSlug, version, expectedActiveVersion, expectedActivationId };
9476
+ return { projectRef: projectRef2, functionSlug, version, expectedActiveVersion, expectedActivationId };
9265
9477
  }
9266
9478
  function requiredExpectedActiveVersion(args, action) {
9267
9479
  const expected = args["expected-active-version"];
@@ -9289,11 +9501,11 @@ async function activateFunctionVersion(http, args, readOnly = false) {
9289
9501
  const unsupported = Object.keys(args).filter((name) => !FUNCTION_ACTIVATION_ARGUMENTS.has(name));
9290
9502
  if (unsupported.length > 0)
9291
9503
  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`;
9504
+ const { projectRef: projectRef2, functionSlug, version, expectedActiveVersion, expectedActivationId } = functionActivationTarget(args);
9505
+ const endpoint = edgeFunctionResourcePath(projectRef2, functionSlug) + `/versions/${encodeURIComponent(version)}/activate`;
9294
9506
  return functionMutationResponse({
9295
9507
  operation: "edge_functions.activate",
9296
- projectRef,
9508
+ projectRef: projectRef2,
9297
9509
  slug: functionSlug,
9298
9510
  expectedActiveVersion,
9299
9511
  expectedActivationId,
@@ -10098,6 +10310,7 @@ var formatTaskStats = (data) => {
10098
10310
  `);
10099
10311
  };
10100
10312
  var ok = (res) => res.ok ? JSON.stringify(res.data, null, 2) : `❌ Failed (${res.status}): ${JSON.stringify(res.data)}`;
10313
+ var simple = (res, msg) => res.ok ? `✅ ${msg}` : `❌ Failed (${res.status})`;
10101
10314
  function buildProjectLogsPath(ref, logType) {
10102
10315
  const params = new URLSearchParams({ limit: "200" });
10103
10316
  if (logType && logType !== "all") {
@@ -10112,11 +10325,13 @@ function resolveRef(refFromArgs, defaultRef) {
10112
10325
  return ref;
10113
10326
  }
10114
10327
  function registerUserProjectCliTools(server, http, options = {}) {
10115
- const { projectRef } = options;
10328
+ const { projectRef: projectRef2 } = options;
10116
10329
  server.tool("project", `Project-scoped inspection and developer operations.
10117
- Actions: get, health, logs, api_keys, settings, tasks, task_detail, task_cancel, task_retry, task_stats, dlq, background_settings, update_background_settings`, {
10330
+ Actions: get, pause, restore, health, logs, api_keys, settings, tasks, task_detail, task_cancel, task_retry, task_stats, dlq, background_settings, update_background_settings`, {
10118
10331
  action: withDescription(stringEnum([
10119
10332
  "get",
10333
+ "pause",
10334
+ "restore",
10120
10335
  "health",
10121
10336
  "logs",
10122
10337
  "api_keys",
@@ -10130,20 +10345,26 @@ Actions: get, health, logs, api_keys, settings, tasks, task_detail, task_cancel,
10130
10345
  "background_settings",
10131
10346
  "update_background_settings"
10132
10347
  ]), "Action to perform"),
10133
- ref: optional(Type.String(), projectRef ? "Optional override when not auto-linked" : "Project ref"),
10348
+ ref: optional(Type.String(), projectRef2 ? "Optional override when not auto-linked" : "Project ref"),
10134
10349
  log_type: optional(stringEnum(["all", "auth", "database", "api"]), "[logs] Filter by service"),
10135
10350
  task_id: optional(Type.String(), "[task_detail/task_cancel/task_retry] Task ID"),
10136
10351
  limit: optional(Type.Number(), "[tasks/dlq] Max items to return"),
10137
10352
  concurrency: optional(Type.Number(), "[update_background_settings] Max concurrent background tasks"),
10138
10353
  max_attempts: optional(Type.Number(), "[update_background_settings] Max attempts for background tasks")
10139
10354
  }, async ({ action, ref, log_type, task_id, limit, concurrency, max_attempts }) => {
10140
- const resolvedRef = resolveRef(ref, projectRef);
10355
+ const resolvedRef = resolveRef(ref, projectRef2);
10141
10356
  let text;
10142
10357
  switch (action) {
10143
10358
  case "get":
10144
10359
  return projectReadResponse(projectGetRead(await http.get(`/v1/projects/${resolvedRef}`, {
10145
10360
  maxResponseBytes: PROJECT_READ_RESPONSE_MAX_BYTES
10146
10361
  }), resolvedRef));
10362
+ case "pause":
10363
+ text = simple(await http.post(`/v1/projects/${resolvedRef}/pause`), `Project ${resolvedRef} paused`);
10364
+ break;
10365
+ case "restore":
10366
+ text = simple(await http.post(`/v1/projects/${resolvedRef}/restore`), `Project ${resolvedRef} restored`);
10367
+ break;
10147
10368
  case "health":
10148
10369
  text = ok(await http.get(`/v1/projects/${resolvedRef}/health`));
10149
10370
  break;
@@ -10302,7 +10523,7 @@ function resolveRef2(refFromArgs, defaultRef) {
10302
10523
  return ref;
10303
10524
  }
10304
10525
  function registerQueueTools(server, http, options = {}) {
10305
- const { projectRef } = options;
10526
+ const { projectRef: projectRef2 } = options;
10306
10527
  server.tool("queue", `Message queue operations for task-based messaging.
10307
10528
  Actions: list, stats, list_messages, dlq, get_message, send, receive, ack, release, fail, retry, delete_message, get_settings, update_settings`, {
10308
10529
  action: withDescription(stringEnum([
@@ -10342,7 +10563,7 @@ Actions: list, stats, list_messages, dlq, get_message, send, receive, ack, relea
10342
10563
  max_attempts_setting: optional(Type.Number(), "[update_settings] Max delivery attempts"),
10343
10564
  rate_limit: optional(Type.Number(), "[update_settings] Rate limit per minute")
10344
10565
  }, async (args) => {
10345
- const resolvedRef = resolveRef2(args.ref, projectRef);
10566
+ const resolvedRef = resolveRef2(args.ref, projectRef2);
10346
10567
  const q = args.queue;
10347
10568
  const need = (fields) => {
10348
10569
  for (const f of fields) {
@@ -10540,9 +10761,9 @@ var redirectStatus = Type.Optional(Type.Union([
10540
10761
  Type.Literal(308)
10541
10762
  ]));
10542
10763
  var ok2 = (res) => res.ok ? JSON.stringify(res.data, null, 2) : `❌ Failed (${res.status}): ${JSON.stringify(res.data)}`;
10543
- var simple = (res, msg) => res.ok ? `✅ ${msg}` : `❌ Failed (${res.status}): ${JSON.stringify(res.data)}`;
10764
+ var simple2 = (res, msg) => res.ok ? `✅ ${msg}` : `❌ Failed (${res.status}): ${JSON.stringify(res.data)}`;
10544
10765
  function registerGatewayTools(server, http, options = {}) {
10545
- const { projectRef } = options;
10766
+ const { projectRef: projectRef2 } = options;
10546
10767
  server.tool("gateway", `Gateway / Caddy 配置(通过 JSON Admin API 注入)。要求 admin 权限。
10547
10768
  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`, {
10548
10769
  action: withDescription(stringEnum([
@@ -10561,7 +10782,7 @@ Actions: routes, upsert_route, update_route, delete_route, config, get_certifica
10561
10782
  "delete_custom_hostname",
10562
10783
  "verify_custom_hostname"
10563
10784
  ]), "Action"),
10564
- ref: optional(Type.String(), projectRef ? "可选:覆盖自动关联的项目 ref" : "项目 ref"),
10785
+ ref: optional(Type.String(), projectRef2 ? "可选:覆盖自动关联的项目 ref" : "项目 ref"),
10565
10786
  route_id: optional(Type.String(), "[upsert_route/update_route/delete_route] 路由 ID(字母/数字/_/-,1-64)"),
10566
10787
  hosts: withDescription(stringArray, "[upsert_route/update_route] 主机名列表,逗号分隔或 JSON 数组(1-20)"),
10567
10788
  paths: withDescription(stringArray, "[upsert_route/update_route] 路径列表,逗号分隔或 JSON 数组(1-32)"),
@@ -10596,7 +10817,7 @@ Actions: routes, upsert_route, update_route, delete_route, config, get_certifica
10596
10817
  custom_hostname: optional(Type.String(), "[set_custom_hostname] 自定义域名")
10597
10818
  }, async (args) => {
10598
10819
  const resolveRef3 = (override) => {
10599
- const ref2 = override || projectRef;
10820
+ const ref2 = override || projectRef2;
10600
10821
  if (!ref2)
10601
10822
  throw new Error("'ref' is required for this action");
10602
10823
  return ref2;
@@ -10802,11 +11023,11 @@ Actions: routes, upsert_route, update_route, delete_route, config, get_certifica
10802
11023
  break;
10803
11024
  case "set_custom_hostname": {
10804
11025
  need("custom_hostname", custom_hostname);
10805
- text = simple(await http.post(`/v1/projects/${projectRefValue}/custom-hostname`, { custom_hostname }), `Custom hostname ${custom_hostname} requested`);
11026
+ text = simple2(await http.post(`/v1/projects/${projectRefValue}/custom-hostname`, { custom_hostname }), `Custom hostname ${custom_hostname} requested`);
10806
11027
  break;
10807
11028
  }
10808
11029
  case "delete_custom_hostname":
10809
- text = simple(await http.delete(`/v1/projects/${projectRefValue}/custom-hostname`), "Custom hostname removed");
11030
+ text = simple2(await http.delete(`/v1/projects/${projectRefValue}/custom-hostname`), "Custom hostname removed");
10810
11031
  break;
10811
11032
  case "verify_custom_hostname":
10812
11033
  text = ok2(await http.post(`/v1/projects/${projectRefValue}/custom-hostname/verify`));
@@ -10819,8 +11040,8 @@ Actions: routes, upsert_route, update_route, delete_route, config, get_certifica
10819
11040
  }
10820
11041
 
10821
11042
  // src/shared/tools/branch-tools.ts
10822
- function resolveProjectRef(ref, projectRef) {
10823
- const resolved = typeof ref === "string" && ref.trim() ? ref.trim() : projectRef || "";
11043
+ function resolveProjectRef(ref, projectRef2) {
11044
+ const resolved = typeof ref === "string" && ref.trim() ? ref.trim() : projectRef2 || "";
10824
11045
  if (!resolved)
10825
11046
  throw new Error("'ref' is required for this action");
10826
11047
  return resolved;
@@ -11272,14 +11493,14 @@ async function executeMigrationPush(request, runtime) {
11272
11493
  const pushMigrations = runtime.getPushMigrations?.();
11273
11494
  if (!pushMigrations)
11274
11495
  return missingMigrationContextResult();
11275
- const projectRef = request.ref || runtime.projectRef;
11276
- if (!projectRef)
11496
+ const projectRef2 = request.ref || runtime.projectRef;
11497
+ if (!projectRef2)
11277
11498
  return missingProjectRefResult();
11278
11499
  const workdir = resolveExistingWorkdir(request.workdir, runtime.fallbackWorkdir);
11279
11500
  const migrationDirectory = resolve3(workdir, request.dir || "supabase/migrations");
11280
11501
  const migrationResponse = await pushMigrations({
11281
11502
  action: "push_migrations",
11282
- ref: projectRef,
11503
+ ref: projectRef2,
11283
11504
  dir: migrationDirectory,
11284
11505
  dry_run: request.dry_run
11285
11506
  });
@@ -12214,7 +12435,7 @@ var INVENTORY_MAX_BYTES = 1024 * 1024;
12214
12435
  var MUTATION_MAX_BYTES = 64 * 1024;
12215
12436
  var BACKUP_TIMEOUT_MS = 36 * 60000;
12216
12437
  var RELEASE_READ_RESPONSE_TIMEOUT_MS = 5000;
12217
- function isRecord(value) {
12438
+ function isRecord2(value) {
12218
12439
  return value !== null && typeof value === "object" && !Array.isArray(value);
12219
12440
  }
12220
12441
  function canonicalTimestamp3(value) {
@@ -12226,15 +12447,15 @@ function canonicalTimestamp3(value) {
12226
12447
  function validProjectRef(ref) {
12227
12448
  return SAFE_PROJECT_REF.test(ref);
12228
12449
  }
12229
- function backupBelongsToProject(backupId, projectRef) {
12230
- return BACKUP_ID.test(backupId) && backupId.startsWith(`logical-full_${projectRef}_`);
12450
+ function backupBelongsToProject(backupId, projectRef2) {
12451
+ return BACKUP_ID.test(backupId) && backupId.startsWith(`logical-full_${projectRef2}_`);
12231
12452
  }
12232
- function verifiedBackup(value, projectRef) {
12233
- 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))
12453
+ function verifiedBackup(value, projectRef2) {
12454
+ 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))
12234
12455
  return null;
12235
12456
  return {
12236
12457
  backup_id: value.backup_id,
12237
- project_ref: projectRef,
12458
+ project_ref: projectRef2,
12238
12459
  database: value.database,
12239
12460
  kind: "logical-full",
12240
12461
  created_at: value.created_at,
@@ -12243,10 +12464,10 @@ function verifiedBackup(value, projectRef) {
12243
12464
  sha256: value.sha256
12244
12465
  };
12245
12466
  }
12246
- function backupInventory(value, projectRef) {
12247
- if (!isRecord(value) || !Array.isArray(value.backups))
12467
+ function backupInventory(value, projectRef2) {
12468
+ if (!isRecord2(value) || !Array.isArray(value.backups))
12248
12469
  return null;
12249
- const backups = value.backups.map((backup) => verifiedBackup(backup, projectRef));
12470
+ const backups = value.backups.map((backup) => verifiedBackup(backup, projectRef2));
12250
12471
  if (backups.some((backup) => backup === null))
12251
12472
  return null;
12252
12473
  const inventory = backups;
@@ -12277,23 +12498,23 @@ function newlyCreatedBackup(before, after) {
12277
12498
  const additions = after.filter((backup) => !known.has(backup.backup_id));
12278
12499
  return additions.length === 1 ? additions[0] : null;
12279
12500
  }
12280
- function restoreRequest(projectRef, backupId, expectedSha256, restoreConfirmation) {
12281
- if (typeof backupId !== "string" || !backupBelongsToProject(backupId, projectRef)) {
12501
+ function restoreRequest(projectRef2, backupId, expectedSha256, restoreConfirmation) {
12502
+ if (typeof backupId !== "string" || !backupBelongsToProject(backupId, projectRef2)) {
12282
12503
  throw new Error("'backup_id' must identify a logical-full backup for 'ref'");
12283
12504
  }
12284
12505
  if (typeof expectedSha256 !== "string" || !SHA256.test(expectedSha256)) {
12285
12506
  throw new Error("'expected_sha256' must be a lowercase SHA-256 digest");
12286
12507
  }
12287
- const confirmation = `RESTORE_PROJECT:${projectRef}:${backupId}:${expectedSha256}`;
12508
+ const confirmation = `RESTORE_PROJECT:${projectRef2}:${backupId}:${expectedSha256}`;
12288
12509
  if (restoreConfirmation !== confirmation) {
12289
12510
  throw new Error("'restore_confirmation' must exactly confirm the selected logical backup restore");
12290
12511
  }
12291
12512
  return { backup_id: backupId, expected_sha256: expectedSha256, confirmation };
12292
12513
  }
12293
- function endpoint(projectRef) {
12294
- if (!validProjectRef(projectRef))
12514
+ function endpoint(projectRef2) {
12515
+ if (!validProjectRef(projectRef2))
12295
12516
  throw new Error("'ref' is invalid for release controls");
12296
- return `/v1/projects/${encodeURIComponent(projectRef)}`;
12517
+ return `/v1/projects/${encodeURIComponent(projectRef2)}`;
12297
12518
  }
12298
12519
  function httpFailure(operation, response) {
12299
12520
  if (response.responseReadError) {
@@ -12307,12 +12528,12 @@ function mutationFailure(operation, response) {
12307
12528
  }
12308
12529
  return releaseControlFailure(operation, "HTTP_ERROR", response.status);
12309
12530
  }
12310
- async function readInventory(http, projectRef) {
12311
- const response = await http.get(`${endpoint(projectRef)}/database/backups/logical`, {
12531
+ async function readInventory(http, projectRef2) {
12532
+ const response = await http.get(`${endpoint(projectRef2)}/database/backups/logical`, {
12312
12533
  maxJsonBytes: INVENTORY_MAX_BYTES,
12313
12534
  responseTimeoutMs: RELEASE_READ_RESPONSE_TIMEOUT_MS
12314
12535
  });
12315
- return { response, inventory: response.ok && response.status === 200 ? backupInventory(response.data, projectRef) : null };
12536
+ return { response, inventory: response.ok && response.status === 200 ? backupInventory(response.data, projectRef2) : null };
12316
12537
  }
12317
12538
  function readInventoryFailure(operation, read) {
12318
12539
  if (!read.response.ok)
@@ -12323,7 +12544,7 @@ function readInventoryFailure(operation, read) {
12323
12544
  return null;
12324
12545
  }
12325
12546
  function postgrestStatus(value) {
12326
- 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)))
12547
+ 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)))
12327
12548
  return null;
12328
12549
  return {
12329
12550
  desired: value.desired,
@@ -12331,8 +12552,8 @@ function postgrestStatus(value) {
12331
12552
  health: value.health
12332
12553
  };
12333
12554
  }
12334
- async function readPostgrestStatus(http, projectRef) {
12335
- const response = await http.get(`${endpoint(projectRef)}/services/postgrest/status`, {
12555
+ async function readPostgrestStatus(http, projectRef2) {
12556
+ const response = await http.get(`${endpoint(projectRef2)}/services/postgrest/status`, {
12336
12557
  maxJsonBytes: MUTATION_MAX_BYTES,
12337
12558
  responseTimeoutMs: RELEASE_READ_RESPONSE_TIMEOUT_MS
12338
12559
  });
@@ -12344,7 +12565,7 @@ function readPostgrestFailure(operation, read) {
12344
12565
  return read.response.status === 200 && read.status ? null : releaseControlFailure(operation, "INVALID_RESPONSE", read.response.status);
12345
12566
  }
12346
12567
  function isRestartReceipt(value) {
12347
- return isRecord(value) && value.service === "postgrest" && value.action === "restart" && value.success === true;
12568
+ return isRecord2(value) && value.service === "postgrest" && value.action === "restart" && value.success === true;
12348
12569
  }
12349
12570
  function registerReleaseTools(server, http, options = {}) {
12350
12571
  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", {
@@ -12360,45 +12581,45 @@ function registerReleaseTools(server, http, options = {}) {
12360
12581
  expected_sha256: optional(Type.String(), "[logical_backup_restore] Exact lowercase SHA-256 from the selected project inventory"),
12361
12582
  restore_confirmation: optional(Type.String(), "[logical_backup_restore] Exact RESTORE_PROJECT:<ref>:<backup_id>:<sha256> confirmation")
12362
12583
  }, async ({ action, ref, backup_id, expected_sha256, restore_confirmation }) => {
12363
- const projectRef = typeof ref === "string" && ref || options.projectRef;
12364
- if (!projectRef)
12584
+ const projectRef2 = typeof ref === "string" && ref || options.projectRef;
12585
+ if (!projectRef2)
12365
12586
  throw new Error("'ref' is required for release controls");
12366
- if (!validProjectRef(projectRef))
12587
+ if (!validProjectRef(projectRef2))
12367
12588
  throw new Error("'ref' is invalid for release controls");
12368
12589
  if (action === "logical_backup_list") {
12369
- const read2 = await readInventory(http, projectRef);
12590
+ const read2 = await readInventory(http, projectRef2);
12370
12591
  const failure = readInventoryFailure("release.logical_backup.list", read2);
12371
12592
  return failure ?? releaseControlSuccess("release.logical_backup.list", {
12372
- project_ref: projectRef,
12593
+ project_ref: projectRef2,
12373
12594
  backups: read2.inventory.map(publicBackup)
12374
12595
  });
12375
12596
  }
12376
12597
  if (action === "logical_backup_create") {
12377
- const before = await readInventory(http, projectRef);
12598
+ const before = await readInventory(http, projectRef2);
12378
12599
  const beforeFailure = readInventoryFailure("release.logical_backup.create", before);
12379
12600
  if (beforeFailure)
12380
12601
  return beforeFailure;
12381
- const mutation2 = await http.postReleaseMutation(`${endpoint(projectRef)}/database/backups/logical`, {}, {
12602
+ const mutation2 = await http.postReleaseMutation(`${endpoint(projectRef2)}/database/backups/logical`, {}, {
12382
12603
  timeoutMs: BACKUP_TIMEOUT_MS
12383
12604
  });
12384
- const after = await readInventory(http, projectRef);
12605
+ const after = await readInventory(http, projectRef2);
12385
12606
  if (!mutation2.ok || mutation2.status !== 200) {
12386
12607
  return mutationFailure("release.logical_backup.create", mutation2);
12387
12608
  }
12388
- const responseBackup = isRecord(mutation2.data) ? verifiedBackup(mutation2.data.backup, projectRef) : null;
12609
+ const responseBackup = isRecord2(mutation2.data) ? verifiedBackup(mutation2.data.backup, projectRef2) : null;
12389
12610
  const afterFailure = readInventoryFailure("release.logical_backup.create", after);
12390
12611
  const addedBackup = after.inventory && newlyCreatedBackup(before.inventory, after.inventory);
12391
12612
  if (!responseBackup || afterFailure || !addedBackup || !equalBackup(responseBackup, addedBackup)) {
12392
12613
  return releaseControlFailure("release.logical_backup.create", "OUTCOME_UNKNOWN", mutation2.status);
12393
12614
  }
12394
12615
  return releaseControlSuccess("release.logical_backup.create", {
12395
- project_ref: projectRef,
12616
+ project_ref: projectRef2,
12396
12617
  backup: publicBackup(addedBackup)
12397
12618
  });
12398
12619
  }
12399
12620
  if (action === "logical_backup_restore") {
12400
- const request = restoreRequest(projectRef, backup_id, expected_sha256, restore_confirmation);
12401
- const before = await readInventory(http, projectRef);
12621
+ const request = restoreRequest(projectRef2, backup_id, expected_sha256, restore_confirmation);
12622
+ const before = await readInventory(http, projectRef2);
12402
12623
  const beforeFailure = readInventoryFailure("release.logical_backup.restore", before);
12403
12624
  if (beforeFailure)
12404
12625
  return beforeFailure;
@@ -12406,43 +12627,43 @@ function registerReleaseTools(server, http, options = {}) {
12406
12627
  if (!selectedBackup) {
12407
12628
  return releaseControlFailure("release.logical_backup.restore", "MUTATION_NOT_SUCCEEDED", null);
12408
12629
  }
12409
- const mutation2 = await http.postReleaseMutation(`${endpoint(projectRef)}/database/backups/logical/restore`, request, { timeoutMs: BACKUP_TIMEOUT_MS });
12630
+ const mutation2 = await http.postReleaseMutation(`${endpoint(projectRef2)}/database/backups/logical/restore`, request, { timeoutMs: BACKUP_TIMEOUT_MS });
12410
12631
  if (!mutation2.ok || mutation2.status !== 200) {
12411
12632
  return mutationFailure("release.logical_backup.restore", mutation2);
12412
12633
  }
12413
- const responseBackup = isRecord(mutation2.data) ? verifiedBackup(mutation2.data.restored_backup, projectRef) : null;
12414
- const after = await readInventory(http, projectRef);
12634
+ const responseBackup = isRecord2(mutation2.data) ? verifiedBackup(mutation2.data.restored_backup, projectRef2) : null;
12635
+ const after = await readInventory(http, projectRef2);
12415
12636
  const afterFailure = readInventoryFailure("release.logical_backup.restore", after);
12416
12637
  const restoredInventoryBackup = after.inventory?.find((backup) => backup.backup_id === request.backup_id);
12417
12638
  if (!responseBackup || !equalBackup(responseBackup, selectedBackup) || afterFailure || !restoredInventoryBackup || !equalBackup(restoredInventoryBackup, selectedBackup)) {
12418
12639
  return releaseControlFailure("release.logical_backup.restore", "OUTCOME_UNKNOWN", mutation2.status);
12419
12640
  }
12420
12641
  return releaseControlSuccess("release.logical_backup.restore", {
12421
- project_ref: projectRef,
12642
+ project_ref: projectRef2,
12422
12643
  backup: publicBackup(selectedBackup)
12423
12644
  });
12424
12645
  }
12425
12646
  if (action === "postgrest_status") {
12426
- const read2 = await readPostgrestStatus(http, projectRef);
12647
+ const read2 = await readPostgrestStatus(http, projectRef2);
12427
12648
  const failure = readPostgrestFailure("release.postgrest.status", read2);
12428
12649
  return failure ?? releaseControlSuccess("release.postgrest.status", {
12429
- project_ref: projectRef,
12650
+ project_ref: projectRef2,
12430
12651
  postgrest: read2.status
12431
12652
  });
12432
12653
  }
12433
12654
  if (action !== "postgrest_restart")
12434
12655
  throw new Error("Unknown release control action");
12435
- const mutation = await http.postReleaseMutation(`${endpoint(projectRef)}/services/postgrest/restart`);
12436
- const read = await readPostgrestStatus(http, projectRef);
12656
+ const mutation = await http.postReleaseMutation(`${endpoint(projectRef2)}/services/postgrest/restart`);
12657
+ const read = await readPostgrestStatus(http, projectRef2);
12437
12658
  if (!mutation.ok || mutation.status !== 200) {
12438
12659
  return mutationFailure("release.postgrest.restart", mutation);
12439
12660
  }
12440
- const readFailure = readPostgrestFailure("release.postgrest.restart", read);
12441
- if (!isRestartReceipt(mutation.data) || readFailure || read.status.desired !== "running" || read.status.actual !== "running" || read.status.health !== "healthy") {
12661
+ const readFailure2 = readPostgrestFailure("release.postgrest.restart", read);
12662
+ if (!isRestartReceipt(mutation.data) || readFailure2 || read.status.desired !== "running" || read.status.actual !== "running" || read.status.health !== "healthy") {
12442
12663
  return releaseControlFailure("release.postgrest.restart", "OUTCOME_UNKNOWN", mutation.status);
12443
12664
  }
12444
12665
  return releaseControlSuccess("release.postgrest.restart", {
12445
- project_ref: projectRef,
12666
+ project_ref: projectRef2,
12446
12667
  postgrest: read.status
12447
12668
  });
12448
12669
  });
@@ -12450,7 +12671,7 @@ function registerReleaseTools(server, http, options = {}) {
12450
12671
  // package.json
12451
12672
  var package_default = {
12452
12673
  name: "@supacloud/cli",
12453
- version: "0.24.0",
12674
+ version: "0.26.0",
12454
12675
  description: "Project-scoped CLI for SupaCloud users",
12455
12676
  type: "module",
12456
12677
  main: "./dist/index.js",
@@ -12494,6 +12715,8 @@ var commandName = "supacloud-cli";
12494
12715
  var preferredCommand = commandName;
12495
12716
  var projectActionSchema = stringEnum([
12496
12717
  "get",
12718
+ "pause",
12719
+ "restore",
12497
12720
  "health",
12498
12721
  "logs",
12499
12722
  "api_keys",
@@ -12790,7 +13013,7 @@ function createCliTools(context, confirmProduction) {
12790
13013
  ]
12791
13014
  })
12792
13015
  };
12793
- for (const name of ["database", "auth", "storage", "edge_functions", "secrets", "frontend", "queue", "task_events", "scheduled_functions", "mutations", "diagnostics", "gateway", "branch", "release"]) {
13016
+ for (const name of ["database", "auth", "oauth_clients", "storage", "edge_functions", "secrets", "frontend", "queue", "task_events", "scheduled_functions", "mutations", "diagnostics", "gateway", "branch", "release"]) {
12794
13017
  tools[name] = {
12795
13018
  schema: { action: genericActionSchema },
12796
13019
  callback: async () => ({
@@ -12867,6 +13090,7 @@ function createCliTools(context, confirmProduction) {
12867
13090
  pushMigrations = databaseTools.database?.callback;
12868
13091
  assign(databaseTools);
12869
13092
  assign(captureTools((server) => registerAuthTools(server, http)));
13093
+ assign(captureTools((server) => registerOAuthClientTools(server, http)));
12870
13094
  assign(captureTools((server) => registerStorageTools(server, http)));
12871
13095
  assign(captureTools((server) => registerAdvancedTools(server, http, process.env, {
12872
13096
  readOnly: context.readOnly
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/cli",
3
- "version": "0.24.0",
3
+ "version": "0.26.0",
4
4
  "description": "Project-scoped CLI for SupaCloud users",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",