@supacloud/cli 0.27.0 → 0.28.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
@@ -6471,8 +6471,8 @@ var ACTION_POLICY = {
6471
6471
  write: ["push"]
6472
6472
  },
6473
6473
  auth: {
6474
- read: ["list_providers", "get_provider", "supported_providers", "get_settings", "get_config"],
6475
- write: ["configure_provider", "update_provider", "disable_provider", "wechat_mini", "wechat_open", "update_settings", "update_config"]
6474
+ read: ["list_users", "get_user", "list_providers", "get_provider", "supported_providers", "get_settings", "get_config"],
6475
+ write: ["generate_link", "configure_provider", "update_provider", "disable_provider", "wechat_mini", "wechat_open", "update_settings", "update_config"]
6476
6476
  },
6477
6477
  oauth_clients: {
6478
6478
  read: ["list", "get"],
@@ -7968,6 +7968,25 @@ var safeAuthMutationCodes = new Set([
7968
7968
  "AUTH_RUNTIME_APPLY_FAILED",
7969
7969
  "SUPAUTH_DEPENDENT_REFRESH_FAILED"
7970
7970
  ]);
7971
+ var MAX_AUTH_READ_BYTES = 64 * 1024;
7972
+ var AUTH_READ_TIMEOUT_MS = 5000;
7973
+ var USER_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
7974
+ var AUTH_LINK_TYPES = [
7975
+ "signup",
7976
+ "magiclink",
7977
+ "recovery",
7978
+ "invite",
7979
+ "email_change",
7980
+ "email_change_current",
7981
+ "email_change_new"
7982
+ ];
7983
+ var SAFE_USER_FIELDS = [
7984
+ "id",
7985
+ "email",
7986
+ "phone",
7987
+ "created_at",
7988
+ "last_sign_in_at"
7989
+ ];
7971
7990
  function parseAuthConfig(input) {
7972
7991
  if (typeof input !== "string")
7973
7992
  return input;
@@ -8019,6 +8038,162 @@ function authMutationResult(response, successMessage) {
8019
8038
  }]
8020
8039
  };
8021
8040
  }
8041
+ function requiredRef(candidate) {
8042
+ if (typeof candidate !== "string" || !candidate.trim())
8043
+ throw new Error("'ref' is required");
8044
+ return projectRefPathSegment(candidate.trim(), "Auth");
8045
+ }
8046
+ function requiredUserId(candidate) {
8047
+ if (typeof candidate !== "string" || !USER_ID_PATTERN.test(candidate.trim())) {
8048
+ throw new Error("'user_id' must be a UUID");
8049
+ }
8050
+ return candidate.trim().toLowerCase();
8051
+ }
8052
+ function boundedPage(candidate) {
8053
+ if (candidate === undefined)
8054
+ return 1;
8055
+ if (typeof candidate !== "number" || !Number.isSafeInteger(candidate) || candidate < 1) {
8056
+ throw new Error("'page' must be a positive integer");
8057
+ }
8058
+ return candidate;
8059
+ }
8060
+ function boundedPerPage(candidate) {
8061
+ if (candidate === undefined)
8062
+ return 50;
8063
+ if (typeof candidate !== "number" || !Number.isSafeInteger(candidate) || candidate < 1 || candidate > 100) {
8064
+ throw new Error("'per_page' must be an integer between 1 and 100");
8065
+ }
8066
+ return candidate;
8067
+ }
8068
+ function safeRedirectTo(candidate) {
8069
+ if (candidate === undefined)
8070
+ return;
8071
+ if (typeof candidate !== "string" || !candidate.trim())
8072
+ throw new Error("'redirect_to' must be an absolute HTTPS or loopback HTTP URL");
8073
+ let uri;
8074
+ try {
8075
+ uri = new URL(candidate.trim());
8076
+ } catch {
8077
+ throw new Error("'redirect_to' must be an absolute HTTPS or loopback HTTP URL");
8078
+ }
8079
+ const loopback = uri.hostname === "127.0.0.1" || uri.hostname === "[::1]";
8080
+ const validProtocol = uri.protocol === "https:" || uri.protocol === "http:" && loopback && Boolean(uri.port);
8081
+ if (!validProtocol || uri.username || uri.password || uri.hash) {
8082
+ throw new Error("'redirect_to' must be an absolute HTTPS or loopback HTTP URL without credentials or fragment");
8083
+ }
8084
+ return uri.toString();
8085
+ }
8086
+ function isRecord(candidate) {
8087
+ return candidate !== null && typeof candidate === "object" && !Array.isArray(candidate);
8088
+ }
8089
+ function projectUser(candidate) {
8090
+ if (!isRecord(candidate) || typeof candidate.id !== "string")
8091
+ return null;
8092
+ const projectedUser = {};
8093
+ for (const field of SAFE_USER_FIELDS) {
8094
+ if (field in candidate)
8095
+ projectedUser[field] = candidate[field];
8096
+ }
8097
+ return projectedUser;
8098
+ }
8099
+ function projectUserList(candidate) {
8100
+ if (!isRecord(candidate) || !Array.isArray(candidate.users))
8101
+ return null;
8102
+ const users = candidate.users.map(projectUser);
8103
+ if (users.some((user) => user === null))
8104
+ return null;
8105
+ const projectedFields = { users };
8106
+ for (const field of ["total", "page", "per_page", "next_page", "last_page"]) {
8107
+ if (field in candidate && (typeof candidate[field] === "number" || candidate[field] === null)) {
8108
+ projectedFields[field] = candidate[field];
8109
+ }
8110
+ }
8111
+ return projectedFields;
8112
+ }
8113
+ function actionLink(candidate) {
8114
+ const candidates = [candidate];
8115
+ if (isRecord(candidate)) {
8116
+ candidates.push(candidate.data, candidate.properties);
8117
+ if (isRecord(candidate.data))
8118
+ candidates.push(candidate.data.properties);
8119
+ }
8120
+ for (const candidate2 of candidates) {
8121
+ if (isRecord(candidate2) && typeof candidate2.action_link === "string" && candidate2.action_link.length > 0) {
8122
+ return candidate2.action_link;
8123
+ }
8124
+ }
8125
+ return null;
8126
+ }
8127
+ function safeAuthReadFailure(operation, response) {
8128
+ return {
8129
+ isError: true,
8130
+ content: [{
8131
+ type: "text",
8132
+ text: JSON.stringify({
8133
+ ok: false,
8134
+ operation,
8135
+ http_status: response.transportError || response.responseReadError ? null : response.status,
8136
+ error: response.responseReadError ? "INVALID_RESPONSE" : response.transportError ? "NETWORK_ERROR" : "HTTP_ERROR"
8137
+ }, null, 2)
8138
+ }]
8139
+ };
8140
+ }
8141
+ async function listUsers(http, args) {
8142
+ const ref = requiredRef(args.ref);
8143
+ const page = boundedPage(args.page);
8144
+ const perPage = boundedPerPage(args.per_page);
8145
+ const params = new URLSearchParams({ page: String(page), per_page: String(perPage) });
8146
+ for (const key of ["search", "email_like"]) {
8147
+ if (args[key] !== undefined) {
8148
+ if (typeof args[key] !== "string" || !args[key].trim())
8149
+ throw new Error(`'${key}' must be a non-empty string`);
8150
+ params.set(key, args[key].trim());
8151
+ }
8152
+ }
8153
+ const response = await http.get(`/v1/projects/${ref}/auth/users?${params.toString()}`, {
8154
+ maxJsonBytes: MAX_AUTH_READ_BYTES,
8155
+ responseTimeoutMs: AUTH_READ_TIMEOUT_MS
8156
+ });
8157
+ if (!response.ok)
8158
+ return safeAuthReadFailure("auth.list_users", response);
8159
+ const users = projectUserList(response.data);
8160
+ if (!users)
8161
+ return safeAuthReadFailure("auth.list_users", { ...response, responseReadError: true });
8162
+ return { content: [{ type: "text", text: JSON.stringify({ ok: true, operation: "auth.list_users", project_ref: ref, ...users }, null, 2) }] };
8163
+ }
8164
+ async function getUser(http, args) {
8165
+ const ref = requiredRef(args.ref);
8166
+ const userId = requiredUserId(args.user_id);
8167
+ const response = await http.get(`/v1/projects/${ref}/auth/users/${encodeURIComponent(userId)}`, {
8168
+ maxJsonBytes: MAX_AUTH_READ_BYTES,
8169
+ responseTimeoutMs: AUTH_READ_TIMEOUT_MS
8170
+ });
8171
+ if (!response.ok)
8172
+ return safeAuthReadFailure("auth.get_user", response);
8173
+ const user = projectUser(response.data);
8174
+ if (!user)
8175
+ return safeAuthReadFailure("auth.get_user", { ...response, responseReadError: true });
8176
+ return { content: [{ type: "text", text: JSON.stringify({ ok: true, operation: "auth.get_user", project_ref: ref, user }, null, 2) }] };
8177
+ }
8178
+ async function generateLink(http, args) {
8179
+ const ref = requiredRef(args.ref);
8180
+ if (typeof args.type !== "string" || !AUTH_LINK_TYPES.includes(args.type)) {
8181
+ throw new Error("'type' is invalid for 'generate_link'");
8182
+ }
8183
+ if (typeof args.email !== "string" || !args.email.trim())
8184
+ throw new Error("'email' is required for 'generate_link'");
8185
+ const body = { type: args.type, email: args.email.trim() };
8186
+ const redirectTo = safeRedirectTo(args.redirect_to);
8187
+ if (redirectTo)
8188
+ body.redirect_to = redirectTo;
8189
+ const response = await http.postReleaseMutation(`/v1/projects/${ref}/auth/generate_link`, body);
8190
+ if (!response.ok)
8191
+ return safeAuthReadFailure("auth.generate_link", response);
8192
+ const link = actionLink(response.data);
8193
+ if (!link)
8194
+ return safeAuthReadFailure("auth.generate_link", { ...response, responseReadError: true });
8195
+ return { content: [{ type: "text", text: JSON.stringify({ ok: true, operation: "auth.generate_link", action_link: link }, null, 2) }] };
8196
+ }
8022
8197
  function formatProviders(data) {
8023
8198
  if (!data || typeof data !== "object")
8024
8199
  return JSON.stringify(data, null, 2);
@@ -8051,9 +8226,12 @@ function formatProviders(data) {
8051
8226
  return out;
8052
8227
  }
8053
8228
  function registerAuthTools(server, http) {
8054
- server.tool("auth", `Auth & OAuth provider management.
8055
- Actions: list_providers, get_provider, configure_provider, update_provider, disable_provider, supported_providers, wechat_mini, wechat_open, get_settings, update_settings, get_config, update_config`, {
8229
+ server.tool("auth", `Auth & OAuth provider management, controlled user lookup, and login-link generation.
8230
+ Actions: list_users, get_user, generate_link, list_providers, get_provider, configure_provider, update_provider, disable_provider, supported_providers, wechat_mini, wechat_open, get_settings, update_settings, get_config, update_config`, {
8056
8231
  action: withDescription(stringEnum([
8232
+ "list_users",
8233
+ "get_user",
8234
+ "generate_link",
8057
8235
  "list_providers",
8058
8236
  "get_provider",
8059
8237
  "configure_provider",
@@ -8068,6 +8246,14 @@ Actions: list_providers, get_provider, configure_provider, update_provider, disa
8068
8246
  "update_config"
8069
8247
  ]), "Action to perform"),
8070
8248
  ref: optional(Type.String(), "Project ref (required for most actions)"),
8249
+ user_id: optional(Type.String(), "[get_user] Exact auth user UUID"),
8250
+ page: optional(Type.Integer({ minimum: 1 }), "[list_users] 1-based page"),
8251
+ per_page: optional(Type.Integer({ minimum: 1, maximum: 100 }), "[list_users] Users per page (1-100)"),
8252
+ search: optional(Type.String(), "[list_users] Search user email, phone, or UUID"),
8253
+ email_like: optional(Type.String(), "[list_users] Search user email or phone"),
8254
+ type: optional(stringEnum(AUTH_LINK_TYPES), "[generate_link] GoTrue link type"),
8255
+ email: optional(Type.String(), "[generate_link] User email"),
8256
+ redirect_to: optional(Type.String(), "[generate_link] Absolute HTTPS or loopback callback"),
8071
8257
  provider: optional(Type.String(), "[*_provider] Provider name (github, google, wechat, etc.)"),
8072
8258
  client_id: optional(Type.String(), "[configure/update] OAuth Client ID"),
8073
8259
  client_secret: optional(Type.String(), "[configure/update] OAuth Client Secret"),
@@ -8085,6 +8271,12 @@ Actions: list_providers, get_provider, configure_provider, update_provider, disa
8085
8271
  const ok = (res) => res.ok ? JSON.stringify(res.data, null, 2) : `❌ Failed (${res.status}): ${JSON.stringify(res.data)}`;
8086
8272
  let text;
8087
8273
  switch (action) {
8274
+ case "list_users":
8275
+ return listUsers(http, args);
8276
+ case "get_user":
8277
+ return getUser(http, args);
8278
+ case "generate_link":
8279
+ return generateLink(http, args);
8088
8280
  case "list_providers":
8089
8281
  need("ref");
8090
8282
  const lp = await http.get(`/v1/projects/${ref}/auth/providers`);
@@ -8180,7 +8372,7 @@ var RELEASE_CANARY_CLIENT_NAME = "supacloud-release-canary";
8180
8372
  var CLIENT_ID_PATTERN = /^[A-Za-z0-9._~-]{1,256}$/;
8181
8373
  var MAX_CLIENT_LIST_BYTES = 256 * 1024;
8182
8374
  var READ_TIMEOUT_MS = 5000;
8183
- function isRecord(value) {
8375
+ function isRecord2(value) {
8184
8376
  return value !== null && typeof value === "object" && !Array.isArray(value);
8185
8377
  }
8186
8378
  function releaseCanaryCallbackUri(value) {
@@ -8202,7 +8394,7 @@ function releaseCanaryCallbackUri(value) {
8202
8394
  return uri.toString();
8203
8395
  }
8204
8396
  function createdClientId(value) {
8205
- if (!isRecord(value))
8397
+ if (!isRecord2(value))
8206
8398
  return null;
8207
8399
  try {
8208
8400
  return clientId(value.client_id);
@@ -8225,7 +8417,7 @@ function oauthClientsPath(ref) {
8225
8417
  return `/v1/projects/${encodeURIComponent(ref)}/auth/oauth-clients`;
8226
8418
  }
8227
8419
  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")
8420
+ if (!isRecord2(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
8421
  return null;
8230
8422
  let callback;
8231
8423
  try {
@@ -8246,9 +8438,9 @@ function expectedClient(value, redirectUri) {
8246
8438
  };
8247
8439
  }
8248
8440
  function clientInventory(value) {
8249
- if (!isRecord(value) || !Array.isArray(value.clients))
8441
+ if (!isRecord2(value) || !Array.isArray(value.clients))
8250
8442
  return null;
8251
- const clients = value.clients.filter((client) => isRecord(client) && client.client_name === RELEASE_CANARY_CLIENT_NAME).map((client) => expectedClient(client));
8443
+ const clients = value.clients.filter((client) => isRecord2(client) && client.client_name === RELEASE_CANARY_CLIENT_NAME).map((client) => expectedClient(client));
8252
8444
  if (clients.some((client) => client === null))
8253
8445
  return null;
8254
8446
  const inventory = clients;
@@ -12567,7 +12759,7 @@ var INVENTORY_MAX_BYTES = 1024 * 1024;
12567
12759
  var MUTATION_MAX_BYTES = 64 * 1024;
12568
12760
  var BACKUP_TIMEOUT_MS = 36 * 60000;
12569
12761
  var RELEASE_READ_RESPONSE_TIMEOUT_MS = 5000;
12570
- function isRecord2(value) {
12762
+ function isRecord3(value) {
12571
12763
  return value !== null && typeof value === "object" && !Array.isArray(value);
12572
12764
  }
12573
12765
  function canonicalTimestamp3(value) {
@@ -12583,7 +12775,7 @@ function backupBelongsToProject(backupId, projectRef2) {
12583
12775
  return BACKUP_ID.test(backupId) && backupId.startsWith(`logical-full_${projectRef2}_`);
12584
12776
  }
12585
12777
  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))
12778
+ if (!isRecord3(value) || typeof value.backup_id !== "string" || !backupBelongsToProject(value.backup_id, projectRef2) || value.project_ref !== projectRef2 || typeof value.database !== "string" || !SAFE_DATABASE.test(value.database) || value.kind !== "logical-full" || !canonicalTimestamp3(value.created_at) || !canonicalTimestamp3(value.completed_at) || new Date(value.completed_at).valueOf() < new Date(value.created_at).valueOf() || typeof value.bytes !== "number" || !Number.isSafeInteger(value.bytes) || value.bytes <= 0 || typeof value.sha256 !== "string" || !SHA256.test(value.sha256))
12587
12779
  return null;
12588
12780
  return {
12589
12781
  backup_id: value.backup_id,
@@ -12597,7 +12789,7 @@ function verifiedBackup(value, projectRef2) {
12597
12789
  };
12598
12790
  }
12599
12791
  function backupInventory(value, projectRef2) {
12600
- if (!isRecord2(value) || !Array.isArray(value.backups))
12792
+ if (!isRecord3(value) || !Array.isArray(value.backups))
12601
12793
  return null;
12602
12794
  const backups = value.backups.map((backup) => verifiedBackup(backup, projectRef2));
12603
12795
  if (backups.some((backup) => backup === null))
@@ -12676,7 +12868,7 @@ function readInventoryFailure(operation, read) {
12676
12868
  return null;
12677
12869
  }
12678
12870
  function postgrestStatus(value) {
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)))
12871
+ if (!isRecord3(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)))
12680
12872
  return null;
12681
12873
  return {
12682
12874
  desired: value.desired,
@@ -12697,7 +12889,7 @@ function readPostgrestFailure(operation, read) {
12697
12889
  return read.response.status === 200 && read.status ? null : releaseControlFailure(operation, "INVALID_RESPONSE", read.response.status);
12698
12890
  }
12699
12891
  function isRestartReceipt(value) {
12700
- return isRecord2(value) && value.service === "postgrest" && value.action === "restart" && value.success === true;
12892
+ return isRecord3(value) && value.service === "postgrest" && value.action === "restart" && value.success === true;
12701
12893
  }
12702
12894
  function registerReleaseTools(server, http, options = {}) {
12703
12895
  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", {
@@ -12738,7 +12930,7 @@ function registerReleaseTools(server, http, options = {}) {
12738
12930
  if (!mutation2.ok || mutation2.status !== 200) {
12739
12931
  return mutationFailure("release.logical_backup.create", mutation2);
12740
12932
  }
12741
- const responseBackup = isRecord2(mutation2.data) ? verifiedBackup(mutation2.data.backup, projectRef2) : null;
12933
+ const responseBackup = isRecord3(mutation2.data) ? verifiedBackup(mutation2.data.backup, projectRef2) : null;
12742
12934
  const afterFailure = readInventoryFailure("release.logical_backup.create", after);
12743
12935
  const addedBackup = after.inventory && newlyCreatedBackup(before.inventory, after.inventory);
12744
12936
  if (!responseBackup || afterFailure || !addedBackup || !equalBackup(responseBackup, addedBackup)) {
@@ -12763,7 +12955,7 @@ function registerReleaseTools(server, http, options = {}) {
12763
12955
  if (!mutation2.ok || mutation2.status !== 200) {
12764
12956
  return mutationFailure("release.logical_backup.restore", mutation2);
12765
12957
  }
12766
- const responseBackup = isRecord2(mutation2.data) ? verifiedBackup(mutation2.data.restored_backup, projectRef2) : null;
12958
+ const responseBackup = isRecord3(mutation2.data) ? verifiedBackup(mutation2.data.restored_backup, projectRef2) : null;
12767
12959
  const after = await readInventory(http, projectRef2);
12768
12960
  const afterFailure = readInventoryFailure("release.logical_backup.restore", after);
12769
12961
  const restoredInventoryBackup = after.inventory?.find((backup) => backup.backup_id === request.backup_id);
@@ -12803,7 +12995,7 @@ function registerReleaseTools(server, http, options = {}) {
12803
12995
  // package.json
12804
12996
  var package_default = {
12805
12997
  name: "@supacloud/cli",
12806
- version: "0.27.0",
12998
+ version: "0.28.0",
12807
12999
  description: "Project-scoped CLI for SupaCloud users",
12808
13000
  type: "module",
12809
13001
  main: "./dist/index.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/cli",
3
- "version": "0.27.0",
3
+ "version": "0.28.0",
4
4
  "description": "Project-scoped CLI for SupaCloud users",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -17,6 +17,7 @@ Load this reference when selecting a command surface or when a user asks an AI t
17
17
  | Inspect or back up a remote database | `supabase db_pull`, `migration_list`, `db_dump`, `gen_types` | Requires explicit PostgreSQL DSN; redact it |
18
18
  | Preview/apply migrations remotely | `supabase push` | Always dry-run first; production needs explicit approval |
19
19
  | Mark proven-equivalent historical migrations as applied | `database baseline_migrations` | Dry-run, schema-equivalence proof, backup, explicit approval |
20
+ | Inspect auth users or generate a controlled login link | `auth list_users`, `auth get_user`, `auth generate_link` | User reads are bounded; login-link generation is a production-confirmed write and action links stay in the calling process |
20
21
  | Manage Auth/Storage/Edge Functions/frontend/secrets | Corresponding project module | Keep deployable config/code in version control |
21
22
  | Configure project gateway routes | `gateway` | Requires an admin-capable project token; inspect before write |
22
23
  | Install/upgrade/debug SupaCloud servers | `supacloud-admin` | Platform boundary; not a project CLI action |
@@ -31,7 +32,7 @@ until a project-scoped context is resolved.
31
32
  - `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.
32
33
  - `database`: read/query, schema inspection, extensions, indexes, RLS, stats, migration push, controlled historical baseline, and SQL-file execution.
33
34
  - `supabase`: allowlisted official CLI adapter for migration authoring, local reset/diff, explicit-DSN inspection/backup/type generation, and SupaCloud-controlled migration push.
34
- - `auth`: provider and authentication configuration.
35
+ - `auth`: provider/configuration plus bounded user lookup and production-confirmed login-link generation.
35
36
  - `storage`: buckets and object-management workflows.
36
37
  - `edge_functions`: list, atomically read one active or deleted identity with `get_config`, read immutable source, deploy, activate, configure, and delete Edge Functions. For every mutation, pass the `activation_id` read from the same `list` or `get_config` snapshot as `--expected-activation-id`; use `legacy` only for a never-created or listed legacy Function, not for a deleted slug with a tombstone UUID. Deploy and activate actions also require the non-negative observed version as `--expected-active-version`; use `absent` for a never-created slug or a `get_config` tombstone. Version `0` is a legacy version token and cannot be used as a source or activation target.
37
38
  - `frontend`: list, build/deploy, domain, and deployment workflows.