@supacloud/cli 0.27.0 → 0.28.1

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,23 @@ 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 = ["magiclink", "recovery", "invite"];
7975
+ var SAFE_USER_FIELDS = [
7976
+ "email",
7977
+ "phone",
7978
+ "created_at",
7979
+ "last_sign_in_at"
7980
+ ];
7981
+ var MAX_AUTH_SEARCH_LENGTH = 256;
7982
+ var MAX_AUTH_EMAIL_LENGTH = 320;
7983
+ var MAX_AUTH_PHONE_LENGTH = 64;
7984
+ var MAX_AUTH_TIMESTAMP_LENGTH = 64;
7985
+ var MAX_AUTH_REDIRECT_LENGTH = 4096;
7986
+ var MAX_AUTH_ACTION_LINK_LENGTH = 8192;
7987
+ var INVALID_FIELD = Symbol("invalid-auth-field");
7971
7988
  function parseAuthConfig(input) {
7972
7989
  if (typeof input !== "string")
7973
7990
  return input;
@@ -8019,6 +8036,234 @@ function authMutationResult(response, successMessage) {
8019
8036
  }]
8020
8037
  };
8021
8038
  }
8039
+ function requiredRef(candidate) {
8040
+ if (typeof candidate !== "string" || !candidate.trim())
8041
+ throw new Error("'ref' is required");
8042
+ return projectRefPathSegment(candidate.trim(), "Auth");
8043
+ }
8044
+ function requiredUserId(candidate) {
8045
+ if (typeof candidate !== "string" || !USER_ID_PATTERN.test(candidate.trim())) {
8046
+ throw new Error("'user_id' must be a UUID");
8047
+ }
8048
+ return candidate.trim().toLowerCase();
8049
+ }
8050
+ function boundedPage(candidate) {
8051
+ if (candidate === undefined)
8052
+ return 1;
8053
+ if (typeof candidate !== "number" || !Number.isSafeInteger(candidate) || candidate < 1) {
8054
+ throw new Error("'page' must be a positive integer");
8055
+ }
8056
+ return candidate;
8057
+ }
8058
+ function boundedPerPage(candidate) {
8059
+ if (candidate === undefined)
8060
+ return 50;
8061
+ if (typeof candidate !== "number" || !Number.isSafeInteger(candidate) || candidate < 1 || candidate > 100) {
8062
+ throw new Error("'per_page' must be an integer between 1 and 100");
8063
+ }
8064
+ return candidate;
8065
+ }
8066
+ function boundedText(candidate, field, maxLength) {
8067
+ if (typeof candidate !== "string")
8068
+ throw new Error(`'${field}' must be a string`);
8069
+ const value = candidate.trim();
8070
+ if (!value || value.length > maxLength || /[\u0000-\u001f\u007f]/u.test(value)) {
8071
+ throw new Error(`'${field}' is invalid or exceeds ${maxLength} characters`);
8072
+ }
8073
+ return value;
8074
+ }
8075
+ function boundedSearch(candidate, field) {
8076
+ return boundedText(candidate, field, MAX_AUTH_SEARCH_LENGTH);
8077
+ }
8078
+ function requiredEmail(candidate) {
8079
+ return boundedText(candidate, "email", MAX_AUTH_EMAIL_LENGTH);
8080
+ }
8081
+ function safeRedirectTo(candidate) {
8082
+ if (candidate === undefined)
8083
+ return;
8084
+ const value = boundedText(candidate, "redirect_to", MAX_AUTH_REDIRECT_LENGTH);
8085
+ let uri;
8086
+ try {
8087
+ uri = new URL(value);
8088
+ } catch {
8089
+ throw new Error("'redirect_to' must be an absolute HTTPS or loopback HTTP URL");
8090
+ }
8091
+ const loopback = uri.hostname === "localhost" || uri.hostname.endsWith(".localhost") || uri.hostname === "127.0.0.1" || uri.hostname === "[::1]";
8092
+ const validProtocol = uri.protocol === "https:" || uri.protocol === "http:" && loopback && Boolean(uri.port);
8093
+ if (!validProtocol || uri.username || uri.password || uri.hash) {
8094
+ throw new Error("'redirect_to' must be an absolute HTTPS or loopback HTTP URL without credentials or fragment");
8095
+ }
8096
+ return uri.toString();
8097
+ }
8098
+ function isRecord(candidate) {
8099
+ return candidate !== null && typeof candidate === "object" && !Array.isArray(candidate);
8100
+ }
8101
+ function safeOptionalUserField(candidate, field, maxLength) {
8102
+ if (!(field in candidate))
8103
+ return;
8104
+ const value = candidate[field];
8105
+ if (value === null)
8106
+ return null;
8107
+ if (typeof value !== "string" || value.length > maxLength || /[\u0000-\u001f\u007f]/u.test(value))
8108
+ return INVALID_FIELD;
8109
+ return value;
8110
+ }
8111
+ function projectUser(candidate) {
8112
+ if (!isRecord(candidate) || typeof candidate.id !== "string" || !USER_ID_PATTERN.test(candidate.id))
8113
+ return null;
8114
+ const projectedUser = { id: candidate.id.toLowerCase() };
8115
+ const fieldLimits = {
8116
+ email: MAX_AUTH_EMAIL_LENGTH,
8117
+ phone: MAX_AUTH_PHONE_LENGTH,
8118
+ created_at: MAX_AUTH_TIMESTAMP_LENGTH,
8119
+ last_sign_in_at: MAX_AUTH_TIMESTAMP_LENGTH
8120
+ };
8121
+ for (const field of SAFE_USER_FIELDS) {
8122
+ const value = safeOptionalUserField(candidate, field, fieldLimits[field]);
8123
+ if (value === INVALID_FIELD)
8124
+ return null;
8125
+ if (value !== undefined)
8126
+ projectedUser[field] = value;
8127
+ }
8128
+ return projectedUser;
8129
+ }
8130
+ function safePaginationField(candidate) {
8131
+ if (candidate === undefined)
8132
+ return;
8133
+ if (candidate === null)
8134
+ return null;
8135
+ return typeof candidate === "number" && Number.isSafeInteger(candidate) && candidate >= 0 ? candidate : INVALID_FIELD;
8136
+ }
8137
+ function projectUserList(candidate, expectedPage, expectedPerPage) {
8138
+ if (!isRecord(candidate) || !Array.isArray(candidate.users) || candidate.users.length > expectedPerPage)
8139
+ return null;
8140
+ const users = candidate.users.map(projectUser);
8141
+ if (users.some((user) => user === null))
8142
+ return null;
8143
+ const projectedUsers = users;
8144
+ const userIds = projectedUsers.map((user) => user.id);
8145
+ if (new Set(userIds).size !== userIds.length)
8146
+ return null;
8147
+ const projectedFields = { users: projectedUsers };
8148
+ for (const field of ["total", "page", "per_page", "next_page", "last_page"]) {
8149
+ const value = safePaginationField(candidate[field]);
8150
+ if (value === INVALID_FIELD)
8151
+ return null;
8152
+ if (value !== undefined)
8153
+ projectedFields[field] = value;
8154
+ }
8155
+ if (projectedFields.page !== undefined && projectedFields.page !== expectedPage)
8156
+ return null;
8157
+ if (projectedFields.per_page !== undefined && projectedFields.per_page !== expectedPerPage)
8158
+ return null;
8159
+ if (typeof projectedFields.total === "number" && projectedFields.total < projectedUsers.length)
8160
+ return null;
8161
+ return projectedFields;
8162
+ }
8163
+ function actionLink(candidate) {
8164
+ const candidates = [candidate];
8165
+ if (isRecord(candidate)) {
8166
+ candidates.push(candidate.data, candidate.properties);
8167
+ if (isRecord(candidate.data))
8168
+ candidates.push(candidate.data.properties);
8169
+ }
8170
+ for (const nested of candidates) {
8171
+ if (!isRecord(nested) || typeof nested.action_link !== "string")
8172
+ continue;
8173
+ const link = nested.action_link;
8174
+ if (!link || link.length > MAX_AUTH_ACTION_LINK_LENGTH || /[\u0000-\u001f\u007f]/u.test(link))
8175
+ continue;
8176
+ try {
8177
+ const uri = new URL(link);
8178
+ if ((uri.protocol === "https:" || uri.protocol === "http:") && !uri.username && !uri.password && !uri.hash && uri.toString() === link)
8179
+ return link;
8180
+ } catch {}
8181
+ }
8182
+ return null;
8183
+ }
8184
+ function safeAuthReadFailure(operation, response) {
8185
+ return {
8186
+ isError: true,
8187
+ content: [{
8188
+ type: "text",
8189
+ text: JSON.stringify({
8190
+ ok: false,
8191
+ operation,
8192
+ http_status: response.transportError || response.responseReadError ? null : response.status,
8193
+ error: response.responseReadError ? "INVALID_RESPONSE" : response.transportError ? "NETWORK_ERROR" : "HTTP_ERROR"
8194
+ }, null, 2)
8195
+ }]
8196
+ };
8197
+ }
8198
+ async function listUsers(http, args) {
8199
+ const ref = requiredRef(args.ref);
8200
+ const page = boundedPage(args.page);
8201
+ const perPage = boundedPerPage(args.per_page);
8202
+ const params = new URLSearchParams({ page: String(page), per_page: String(perPage) });
8203
+ for (const key of ["search", "email_like"]) {
8204
+ if (args[key] !== undefined)
8205
+ params.set(key, boundedSearch(args[key], key));
8206
+ }
8207
+ const response = await http.get(`/v1/projects/${ref}/auth/users?${params.toString()}`, {
8208
+ maxJsonBytes: MAX_AUTH_READ_BYTES,
8209
+ responseTimeoutMs: AUTH_READ_TIMEOUT_MS
8210
+ });
8211
+ if (!response.ok)
8212
+ return safeAuthReadFailure("auth.list_users", response);
8213
+ const users = projectUserList(response.data, page, perPage);
8214
+ if (!users)
8215
+ return safeAuthReadFailure("auth.list_users", { ...response, responseReadError: true });
8216
+ return { content: [{ type: "text", text: JSON.stringify({ ok: true, operation: "auth.list_users", project_ref: ref, ...users }, null, 2) }] };
8217
+ }
8218
+ async function getUser(http, args) {
8219
+ const ref = requiredRef(args.ref);
8220
+ const userId = requiredUserId(args.user_id);
8221
+ const response = await http.get(`/v1/projects/${ref}/auth/users/${encodeURIComponent(userId)}`, {
8222
+ maxJsonBytes: MAX_AUTH_READ_BYTES,
8223
+ responseTimeoutMs: AUTH_READ_TIMEOUT_MS
8224
+ });
8225
+ if (!response.ok)
8226
+ return safeAuthReadFailure("auth.get_user", response);
8227
+ const user = projectUser(response.data);
8228
+ if (!user || user.id !== userId) {
8229
+ return safeAuthReadFailure("auth.get_user", { ...response, responseReadError: true });
8230
+ }
8231
+ return { content: [{ type: "text", text: JSON.stringify({ ok: true, operation: "auth.get_user", project_ref: ref, user }, null, 2) }] };
8232
+ }
8233
+ function generateLinkFailure(response) {
8234
+ const outcomeUnknown = response.responseReadError || response.transportError || response.status === 408 || response.status >= 500;
8235
+ return {
8236
+ isError: true,
8237
+ content: [{
8238
+ type: "text",
8239
+ text: JSON.stringify({
8240
+ ok: false,
8241
+ operation: "auth.generate_link",
8242
+ error: {
8243
+ code: outcomeUnknown ? "OUTCOME_UNKNOWN" : "HTTP_ERROR",
8244
+ http_status: response.transportError ? null : response.status
8245
+ }
8246
+ }, null, 2)
8247
+ }]
8248
+ };
8249
+ }
8250
+ async function generateLink(http, args) {
8251
+ const ref = requiredRef(args.ref);
8252
+ if (typeof args.type !== "string" || !AUTH_LINK_TYPES.includes(args.type)) {
8253
+ throw new Error("'type' must be one of magiclink, recovery, or invite for 'generate_link'");
8254
+ }
8255
+ const body = { type: args.type, email: requiredEmail(args.email) };
8256
+ const redirectTo = safeRedirectTo(args.redirect_to);
8257
+ if (redirectTo)
8258
+ body.redirect_to = redirectTo;
8259
+ const response = await http.postReleaseMutation(`/v1/projects/${ref}/auth/generate_link`, body);
8260
+ if (!response.ok)
8261
+ return generateLinkFailure(response);
8262
+ const link = actionLink(response.data);
8263
+ if (!link)
8264
+ return generateLinkFailure({ ...response, responseReadError: true });
8265
+ return { content: [{ type: "text", text: JSON.stringify({ ok: true, operation: "auth.generate_link", action_link: link }, null, 2) }] };
8266
+ }
8022
8267
  function formatProviders(data) {
8023
8268
  if (!data || typeof data !== "object")
8024
8269
  return JSON.stringify(data, null, 2);
@@ -8051,9 +8296,12 @@ function formatProviders(data) {
8051
8296
  return out;
8052
8297
  }
8053
8298
  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`, {
8299
+ server.tool("auth", `Auth & OAuth provider management, controlled user lookup, and login-link generation.
8300
+ 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
8301
  action: withDescription(stringEnum([
8302
+ "list_users",
8303
+ "get_user",
8304
+ "generate_link",
8057
8305
  "list_providers",
8058
8306
  "get_provider",
8059
8307
  "configure_provider",
@@ -8068,6 +8316,14 @@ Actions: list_providers, get_provider, configure_provider, update_provider, disa
8068
8316
  "update_config"
8069
8317
  ]), "Action to perform"),
8070
8318
  ref: optional(Type.String(), "Project ref (required for most actions)"),
8319
+ user_id: optional(Type.String(), "[get_user] Exact auth user UUID"),
8320
+ page: optional(Type.Integer({ minimum: 1 }), "[list_users] 1-based page"),
8321
+ per_page: optional(Type.Integer({ minimum: 1, maximum: 100 }), "[list_users] Users per page (1-100)"),
8322
+ search: optional(Type.String(), "[list_users] Search user email, phone, or UUID"),
8323
+ email_like: optional(Type.String(), "[list_users] Search user email or phone"),
8324
+ type: optional(stringEnum(AUTH_LINK_TYPES), "[generate_link] magiclink, recovery, or invite"),
8325
+ email: optional(Type.String(), "[generate_link] User email"),
8326
+ redirect_to: optional(Type.String(), "[generate_link] Absolute HTTPS or loopback callback"),
8071
8327
  provider: optional(Type.String(), "[*_provider] Provider name (github, google, wechat, etc.)"),
8072
8328
  client_id: optional(Type.String(), "[configure/update] OAuth Client ID"),
8073
8329
  client_secret: optional(Type.String(), "[configure/update] OAuth Client Secret"),
@@ -8085,6 +8341,12 @@ Actions: list_providers, get_provider, configure_provider, update_provider, disa
8085
8341
  const ok = (res) => res.ok ? JSON.stringify(res.data, null, 2) : `❌ Failed (${res.status}): ${JSON.stringify(res.data)}`;
8086
8342
  let text;
8087
8343
  switch (action) {
8344
+ case "list_users":
8345
+ return listUsers(http, args);
8346
+ case "get_user":
8347
+ return getUser(http, args);
8348
+ case "generate_link":
8349
+ return generateLink(http, args);
8088
8350
  case "list_providers":
8089
8351
  need("ref");
8090
8352
  const lp = await http.get(`/v1/projects/${ref}/auth/providers`);
@@ -8180,7 +8442,7 @@ var RELEASE_CANARY_CLIENT_NAME = "supacloud-release-canary";
8180
8442
  var CLIENT_ID_PATTERN = /^[A-Za-z0-9._~-]{1,256}$/;
8181
8443
  var MAX_CLIENT_LIST_BYTES = 256 * 1024;
8182
8444
  var READ_TIMEOUT_MS = 5000;
8183
- function isRecord(value) {
8445
+ function isRecord2(value) {
8184
8446
  return value !== null && typeof value === "object" && !Array.isArray(value);
8185
8447
  }
8186
8448
  function releaseCanaryCallbackUri(value) {
@@ -8202,7 +8464,7 @@ function releaseCanaryCallbackUri(value) {
8202
8464
  return uri.toString();
8203
8465
  }
8204
8466
  function createdClientId(value) {
8205
- if (!isRecord(value))
8467
+ if (!isRecord2(value))
8206
8468
  return null;
8207
8469
  try {
8208
8470
  return clientId(value.client_id);
@@ -8225,7 +8487,7 @@ function oauthClientsPath(ref) {
8225
8487
  return `/v1/projects/${encodeURIComponent(ref)}/auth/oauth-clients`;
8226
8488
  }
8227
8489
  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")
8490
+ 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
8491
  return null;
8230
8492
  let callback;
8231
8493
  try {
@@ -8246,9 +8508,9 @@ function expectedClient(value, redirectUri) {
8246
8508
  };
8247
8509
  }
8248
8510
  function clientInventory(value) {
8249
- if (!isRecord(value) || !Array.isArray(value.clients))
8511
+ if (!isRecord2(value) || !Array.isArray(value.clients))
8250
8512
  return null;
8251
- const clients = value.clients.filter((client) => isRecord(client) && client.client_name === RELEASE_CANARY_CLIENT_NAME).map((client) => expectedClient(client));
8513
+ const clients = value.clients.filter((client) => isRecord2(client) && client.client_name === RELEASE_CANARY_CLIENT_NAME).map((client) => expectedClient(client));
8252
8514
  if (clients.some((client) => client === null))
8253
8515
  return null;
8254
8516
  const inventory = clients;
@@ -10054,15 +10316,15 @@ function hasWellFormedUnicode(text) {
10054
10316
  }
10055
10317
  return true;
10056
10318
  }
10057
- function boundedText(candidate, maxLength) {
10319
+ function boundedText2(candidate, maxLength) {
10058
10320
  return typeof candidate === "string" && candidate.length > 0 && candidate.length <= maxLength && !/[\u0000-\u001f\u007f]/u.test(candidate) && hasWellFormedUnicode(candidate) ? candidate : null;
10059
10321
  }
10060
10322
  function matchingText(candidate, maxLength, pattern) {
10061
- const candidateText = boundedText(candidate, maxLength);
10323
+ const candidateText = boundedText2(candidate, maxLength);
10062
10324
  return candidateText && pattern.test(candidateText) ? candidateText : null;
10063
10325
  }
10064
10326
  function canonicalTimestamp(candidate) {
10065
- const timestamp = boundedText(candidate, 64);
10327
+ const timestamp = boundedText2(candidate, 64);
10066
10328
  if (!timestamp)
10067
10329
  return null;
10068
10330
  const milliseconds = Date.parse(timestamp);
@@ -10074,7 +10336,7 @@ function projectedSummary(project) {
10074
10336
  ref: matchingText(project.ref, 20, PROJECT_REF_PATTERN3),
10075
10337
  organization_id: matchingText(project.organization_id, 128, SAFE_IDENTIFIER_PATTERN),
10076
10338
  organization_slug: matchingText(project.organization_slug, 128, SAFE_IDENTIFIER_PATTERN),
10077
- name: boundedText(project.name, 100),
10339
+ name: boundedText2(project.name, 100),
10078
10340
  region: matchingText(project.region, 64, REGION_PATTERN),
10079
10341
  created_at: canonicalTimestamp(project.created_at),
10080
10342
  status: matchingText(project.status, 64, STATUS_PATTERN)
@@ -10082,7 +10344,7 @@ function projectedSummary(project) {
10082
10344
  return Object.values(summary).every((field) => field !== null) ? summary : null;
10083
10345
  }
10084
10346
  function databaseHost(candidate) {
10085
- const host = boundedText(candidate, 255);
10347
+ const host = boundedText2(candidate, 255);
10086
10348
  if (!host)
10087
10349
  return null;
10088
10350
  if (host.startsWith("[") && host.endsWith("]")) {
@@ -10122,7 +10384,7 @@ function projectEndpoint(candidate) {
10122
10384
  const endpoint = plainRecord(candidate);
10123
10385
  if (!endpoint || !hasOnlyKeys(endpoint, PROJECT_ENDPOINT_KEYS))
10124
10386
  return null;
10125
- const endpointUrl = boundedText(endpoint.url, 2048);
10387
+ const endpointUrl = boundedText2(endpoint.url, 2048);
10126
10388
  if (!endpointUrl || !rawUrlHasNoPath(endpointUrl))
10127
10389
  return null;
10128
10390
  try {
@@ -10139,7 +10401,7 @@ function projectEndpoint(candidate) {
10139
10401
  function discardedDetailFieldsAreValid(project) {
10140
10402
  if (project.config !== undefined && plainRecord(project.config) === null)
10141
10403
  return false;
10142
- if (project.anon_key !== undefined && boundedText(project.anon_key, 16384) === null)
10404
+ if (project.anon_key !== undefined && boundedText2(project.anon_key, 16384) === null)
10143
10405
  return false;
10144
10406
  return project.services === undefined || Array.isArray(project.services);
10145
10407
  }
@@ -10218,11 +10480,11 @@ function plainRecord2(candidate) {
10218
10480
  function hasOnlyKeys2(record, allowedKeys) {
10219
10481
  return Object.keys(record).every((key) => allowedKeys.has(key));
10220
10482
  }
10221
- function boundedText2(candidate, maxLength) {
10483
+ function boundedText3(candidate, maxLength) {
10222
10484
  return typeof candidate === "string" && candidate.length > 0 && candidate.length <= maxLength && !/[\u0000-\u001f\u007f]/u.test(candidate) ? candidate : null;
10223
10485
  }
10224
10486
  function canonicalHost(candidate, scheme) {
10225
- const host = boundedText2(candidate, 255);
10487
+ const host = boundedText3(candidate, 255);
10226
10488
  if (!host)
10227
10489
  return null;
10228
10490
  try {
@@ -10237,8 +10499,8 @@ function projectEndpoint2(candidate) {
10237
10499
  if (!endpoint || !hasOnlyKeys2(endpoint, ENDPOINT_KEYS))
10238
10500
  return null;
10239
10501
  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);
10502
+ const origin = boundedText3(endpoint.origin, 2048);
10503
+ const source = boundedText3(endpoint.source, 64);
10242
10504
  if (!scheme || !origin || !source || !PROJECT_ENDPOINT_SOURCES.has(source))
10243
10505
  return null;
10244
10506
  let parsedOrigin;
@@ -12567,7 +12829,7 @@ var INVENTORY_MAX_BYTES = 1024 * 1024;
12567
12829
  var MUTATION_MAX_BYTES = 64 * 1024;
12568
12830
  var BACKUP_TIMEOUT_MS = 36 * 60000;
12569
12831
  var RELEASE_READ_RESPONSE_TIMEOUT_MS = 5000;
12570
- function isRecord2(value) {
12832
+ function isRecord3(value) {
12571
12833
  return value !== null && typeof value === "object" && !Array.isArray(value);
12572
12834
  }
12573
12835
  function canonicalTimestamp3(value) {
@@ -12583,7 +12845,7 @@ function backupBelongsToProject(backupId, projectRef2) {
12583
12845
  return BACKUP_ID.test(backupId) && backupId.startsWith(`logical-full_${projectRef2}_`);
12584
12846
  }
12585
12847
  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))
12848
+ 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
12849
  return null;
12588
12850
  return {
12589
12851
  backup_id: value.backup_id,
@@ -12597,7 +12859,7 @@ function verifiedBackup(value, projectRef2) {
12597
12859
  };
12598
12860
  }
12599
12861
  function backupInventory(value, projectRef2) {
12600
- if (!isRecord2(value) || !Array.isArray(value.backups))
12862
+ if (!isRecord3(value) || !Array.isArray(value.backups))
12601
12863
  return null;
12602
12864
  const backups = value.backups.map((backup) => verifiedBackup(backup, projectRef2));
12603
12865
  if (backups.some((backup) => backup === null))
@@ -12676,7 +12938,7 @@ function readInventoryFailure(operation, read) {
12676
12938
  return null;
12677
12939
  }
12678
12940
  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)))
12941
+ 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
12942
  return null;
12681
12943
  return {
12682
12944
  desired: value.desired,
@@ -12697,7 +12959,7 @@ function readPostgrestFailure(operation, read) {
12697
12959
  return read.response.status === 200 && read.status ? null : releaseControlFailure(operation, "INVALID_RESPONSE", read.response.status);
12698
12960
  }
12699
12961
  function isRestartReceipt(value) {
12700
- return isRecord2(value) && value.service === "postgrest" && value.action === "restart" && value.success === true;
12962
+ return isRecord3(value) && value.service === "postgrest" && value.action === "restart" && value.success === true;
12701
12963
  }
12702
12964
  function registerReleaseTools(server, http, options = {}) {
12703
12965
  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 +13000,7 @@ function registerReleaseTools(server, http, options = {}) {
12738
13000
  if (!mutation2.ok || mutation2.status !== 200) {
12739
13001
  return mutationFailure("release.logical_backup.create", mutation2);
12740
13002
  }
12741
- const responseBackup = isRecord2(mutation2.data) ? verifiedBackup(mutation2.data.backup, projectRef2) : null;
13003
+ const responseBackup = isRecord3(mutation2.data) ? verifiedBackup(mutation2.data.backup, projectRef2) : null;
12742
13004
  const afterFailure = readInventoryFailure("release.logical_backup.create", after);
12743
13005
  const addedBackup = after.inventory && newlyCreatedBackup(before.inventory, after.inventory);
12744
13006
  if (!responseBackup || afterFailure || !addedBackup || !equalBackup(responseBackup, addedBackup)) {
@@ -12763,7 +13025,7 @@ function registerReleaseTools(server, http, options = {}) {
12763
13025
  if (!mutation2.ok || mutation2.status !== 200) {
12764
13026
  return mutationFailure("release.logical_backup.restore", mutation2);
12765
13027
  }
12766
- const responseBackup = isRecord2(mutation2.data) ? verifiedBackup(mutation2.data.restored_backup, projectRef2) : null;
13028
+ const responseBackup = isRecord3(mutation2.data) ? verifiedBackup(mutation2.data.restored_backup, projectRef2) : null;
12767
13029
  const after = await readInventory(http, projectRef2);
12768
13030
  const afterFailure = readInventoryFailure("release.logical_backup.restore", after);
12769
13031
  const restoredInventoryBackup = after.inventory?.find((backup) => backup.backup_id === request.backup_id);
@@ -12803,7 +13065,7 @@ function registerReleaseTools(server, http, options = {}) {
12803
13065
  // package.json
12804
13066
  var package_default = {
12805
13067
  name: "@supacloud/cli",
12806
- version: "0.27.0",
13068
+ version: "0.28.1",
12807
13069
  description: "Project-scoped CLI for SupaCloud users",
12808
13070
  type: "module",
12809
13071
  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.1",
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; generation supports only `magiclink`, `recovery`, and `invite`, requires production confirmation, and returns only a validated action URL |
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 `magiclink`, `recovery`, or `invite` generation. Search/email/redirect inputs and returned action URLs are bounded and validated before use.
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.