@supacloud/cli 0.14.6 → 0.15.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 +148 -5
  2. package/dist/index.js +1133 -121
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6101,6 +6101,39 @@ function coerceCliValue(value) {
6101
6101
  return Number(value);
6102
6102
  return value;
6103
6103
  }
6104
+ var DUPLICATE_CLI_FLAG_MESSAGE = "Duplicate CLI flag is not allowed";
6105
+ function cliFlagAt(args, index) {
6106
+ const argument = args[index];
6107
+ if (!argument.startsWith("--") || argument.length <= 2)
6108
+ return null;
6109
+ const rawFlag = argument.slice(2);
6110
+ const equalsIndex = rawFlag.indexOf("=");
6111
+ if (equalsIndex >= 0) {
6112
+ return { name: rawFlag.slice(0, equalsIndex), rawValue: rawFlag.slice(equalsIndex + 1), nextIndex: index };
6113
+ }
6114
+ const nextArgument = args[index + 1];
6115
+ if (nextArgument !== undefined && !nextArgument.startsWith("--")) {
6116
+ return { name: rawFlag, rawValue: nextArgument, nextIndex: index + 1 };
6117
+ }
6118
+ return { name: rawFlag, nextIndex: index };
6119
+ }
6120
+ function schemaCompatibleCliValue(field, rawValue) {
6121
+ return field && exports_value2.Check(field, rawValue) ? rawValue : coerceCliValue(rawValue);
6122
+ }
6123
+ function parseCliFlags(args, startIndex, schema) {
6124
+ const parsedFlags = Object.create(null);
6125
+ const fields = schemaProperties(schema);
6126
+ for (let index = startIndex;index < args.length; index++) {
6127
+ const flag = cliFlagAt(args, index);
6128
+ if (!flag)
6129
+ continue;
6130
+ index = flag.nextIndex;
6131
+ if (Object.hasOwn(parsedFlags, flag.name))
6132
+ throw new Error(DUPLICATE_CLI_FLAG_MESSAGE);
6133
+ parsedFlags[flag.name] = flag.rawValue === undefined ? true : schemaCompatibleCliValue(fields[flag.name], flag.rawValue);
6134
+ }
6135
+ return parsedFlags;
6136
+ }
6104
6137
  async function runCli(cliTools, args, options = {}) {
6105
6138
  const commandName = options.commandName || "supacloud";
6106
6139
  const formatAvailableCommands = () => Object.keys(cliTools).filter((k) => !["setup_help", "deploy_web_console"].includes(k)).join(`
@@ -6168,28 +6201,14 @@ async function runCli(cliTools, args, options = {}) {
6168
6201
  process.exitCode = 0;
6169
6202
  return;
6170
6203
  }
6171
- const parsedArgs = {};
6172
6204
  let startIdx = 1;
6173
6205
  if (args.length > 1 && !args[1].startsWith("--")) {
6174
- parsedArgs.action = args[1];
6175
6206
  startIdx = 2;
6176
6207
  }
6177
- for (let i = startIdx;i < args.length; i++) {
6178
- const arg = args[i];
6179
- if (arg.startsWith("--") && arg.length > 2) {
6180
- const rawFlag = arg.slice(2);
6181
- const equalsIndex = rawFlag.indexOf("=");
6182
- const key = equalsIndex >= 0 ? rawFlag.slice(0, equalsIndex) : rawFlag;
6183
- let val = true;
6184
- if (equalsIndex >= 0) {
6185
- val = coerceCliValue(rawFlag.slice(equalsIndex + 1));
6186
- } else if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
6187
- val = coerceCliValue(args[++i]);
6188
- }
6189
- parsedArgs[key] = val;
6190
- }
6191
- }
6192
6208
  try {
6209
+ const parsedArgs = parseCliFlags(args, startIdx, tool.schema);
6210
+ if (startIdx === 2)
6211
+ parsedArgs.action = args[1];
6193
6212
  const validatedArgs = parseToolArguments(tool.schema, parsedArgs);
6194
6213
  const result = await tool.callback(validatedArgs);
6195
6214
  if (result && result.content && Array.isArray(result.content)) {
@@ -6213,50 +6232,106 @@ async function runCli(cliTools, args, options = {}) {
6213
6232
  }
6214
6233
 
6215
6234
  // src/shared/context.ts
6235
+ import { existsSync, readFileSync } from "node:fs";
6216
6236
  import { homedir } from "node:os";
6217
6237
  import { resolve } from "node:path";
6218
- import { existsSync, readFileSync } from "node:fs";
6219
- function readDotEnvFile(cwd) {
6220
- const envPath = resolve(cwd, ".env");
6221
- if (!existsSync(envPath))
6222
- return {};
6238
+
6239
+ // src/shared/global-options.ts
6240
+ var GLOBAL_FLAGS = {
6241
+ "--env": "environmentName",
6242
+ "--env-file": "envFile",
6243
+ "--confirm-production": "confirmProduction"
6244
+ };
6245
+ var ENVIRONMENT_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
6246
+ function globalFlag(arg) {
6247
+ return Object.keys(GLOBAL_FLAGS).find((flag) => arg === flag || arg.startsWith(`${flag}=`)) ?? null;
6248
+ }
6249
+ function globalFlagValue(args, index, flag) {
6250
+ const inlineValue = args[index].slice(flag.length + 1);
6251
+ if (args[index].startsWith(`${flag}=`)) {
6252
+ if (!inlineValue)
6253
+ throw new Error(`${flag} requires a value`);
6254
+ return { value: inlineValue, consumed: 1 };
6255
+ }
6256
+ const value = args[index + 1];
6257
+ if (!value || value.startsWith("--"))
6258
+ throw new Error(`${flag} requires a value`);
6259
+ return { value, consumed: 2 };
6260
+ }
6261
+ function normalizeEnvironmentName(name) {
6262
+ if (!ENVIRONMENT_NAME_PATTERN.test(name)) {
6263
+ throw new Error("--env and SUPACLOUD_ENV must match ^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$");
6264
+ }
6265
+ const normalized = name.toLowerCase();
6266
+ return normalized === "prod" || normalized === "production" ? "production" : normalized;
6267
+ }
6268
+ function parseGlobalOptions(args) {
6223
6269
  const values = {};
6224
- try {
6225
- const envContent = readFileSync(envPath, "utf-8");
6226
- for (const line of envContent.split(`
6227
- `)) {
6228
- const match = line.trim().match(/^([^=]+)=(.*)$/);
6229
- if (!match)
6230
- continue;
6231
- const key = match[1].trim();
6232
- const value = match[2].trim().replace(/^["']|["']$/g, "");
6233
- values[key] = value;
6270
+ const remainingArgs = [];
6271
+ for (let index = 0;index < args.length; ) {
6272
+ const flag = globalFlag(args[index]);
6273
+ if (!flag) {
6274
+ remainingArgs.push(args[index]);
6275
+ index += 1;
6276
+ continue;
6234
6277
  }
6235
- } catch {
6236
- return {};
6278
+ const key = GLOBAL_FLAGS[flag];
6279
+ if (values[key] !== undefined)
6280
+ throw new Error(`${flag} may be provided only once`);
6281
+ const parsed = globalFlagValue(args, index, flag);
6282
+ values[key] = parsed.value;
6283
+ index += parsed.consumed;
6284
+ }
6285
+ if (values.environmentName && values.envFile) {
6286
+ throw new Error("--env and --env-file are mutually exclusive");
6287
+ }
6288
+ if (values.environmentName)
6289
+ normalizeEnvironmentName(values.environmentName);
6290
+ return { ...values, args: remainingArgs };
6291
+ }
6292
+
6293
+ // src/shared/context.ts
6294
+ var CORE_CONTEXT_KEYS = [
6295
+ "SUPABASE_URL",
6296
+ "SUPABASE_SERVICE_ROLE_KEY",
6297
+ "SUPACLOUD_API_URL",
6298
+ "SUPACLOUD_MANAGEMENT_API_URL",
6299
+ "MANAGEMENT_API_URL",
6300
+ "SUPACLOUD_API_TOKEN",
6301
+ "SUPACLOUD_PROJECT_REF",
6302
+ "X_PROJECT_REF",
6303
+ "SUPACLOUD_HOST"
6304
+ ];
6305
+ function unquotedEnvValue(rawValue) {
6306
+ const value = rawValue.trim();
6307
+ const quote = value[0];
6308
+ return quote && (quote === '"' || quote === "'") && value.endsWith(quote) ? value.slice(1, -1) : value;
6309
+ }
6310
+ function parseEnvFile(contents) {
6311
+ const values = {};
6312
+ for (const rawLine of contents.split(/\r?\n/)) {
6313
+ const line = rawLine.trim();
6314
+ if (!line || line.startsWith("#"))
6315
+ continue;
6316
+ const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
6317
+ if (!match)
6318
+ continue;
6319
+ values[match[1]] = unquotedEnvValue(match[2]);
6237
6320
  }
6238
6321
  return values;
6239
6322
  }
6240
- function pickValue(env, dotenv, keys) {
6241
- for (const key of keys) {
6242
- const envValue = env[key];
6243
- if (envValue)
6244
- return { value: envValue, source: "env" };
6323
+ function readEnvFile(path, required) {
6324
+ if (!existsSync(path)) {
6325
+ if (required)
6326
+ throw new Error(`SupaCloud environment file not found: ${path}`);
6327
+ return {};
6245
6328
  }
6246
- for (const key of keys) {
6247
- const dotenvValue = dotenv[key];
6248
- if (dotenvValue)
6249
- return { value: dotenvValue, source: "dotenv" };
6329
+ try {
6330
+ return parseEnvFile(readFileSync(path, "utf8"));
6331
+ } catch (error) {
6332
+ const message = error instanceof Error ? error.message : String(error);
6333
+ throw new Error(`Failed to read SupaCloud environment file ${path}: ${message}`);
6250
6334
  }
6251
- return { value: "", source: "env" };
6252
- }
6253
- function detectSource(sources) {
6254
- const present = new Set(sources.filter((value) => value !== "none"));
6255
- if (present.size === 0)
6256
- return "none";
6257
- if (present.size === 1)
6258
- return present.has("env") ? "env" : "dotenv";
6259
- return "mixed";
6260
6335
  }
6261
6336
  function normalizeUrl(value) {
6262
6337
  const trimmed = value.trim().replace(/\/+$/, "");
@@ -6279,8 +6354,7 @@ function inferProjectRefFromSupabaseUrl(value) {
6279
6354
  const normalized = normalizeUrl(value);
6280
6355
  if (!normalized)
6281
6356
  return "";
6282
- const match = new URL(normalized).hostname.match(/^([a-z0-9-]+)\.api\./i);
6283
- return match?.[1] ?? "";
6357
+ return new URL(normalized).hostname.match(/^([a-z0-9-]+)\.api\./i)?.[1] ?? "";
6284
6358
  }
6285
6359
  function inferManagementApiUrlFromSupabaseUrl(value, projectRef = "") {
6286
6360
  const normalized = normalizeUrl(value);
@@ -6297,43 +6371,209 @@ function inferManagementApiUrlFromSupabaseUrl(value, projectRef = "") {
6297
6371
  url.hostname = `studio-${ref}.${host.slice(`${ref}.api.`.length)}`;
6298
6372
  return url.toString().replace(/\/+$/, "");
6299
6373
  }
6300
- const managedHostMatch = host.match(/^([a-z0-9-]+)\.api\.(.+)$/i);
6301
- if (managedHostMatch) {
6302
- url.hostname = `studio-${managedHostMatch[1]}.${managedHostMatch[2]}`;
6374
+ const managedHost = host.match(/^([a-z0-9-]+)\.api\.(.+)$/i);
6375
+ if (managedHost) {
6376
+ url.hostname = `studio-${managedHost[1]}.${managedHost[2]}`;
6303
6377
  return url.toString().replace(/\/+$/, "");
6304
6378
  }
6305
6379
  return normalized;
6306
6380
  }
6307
- function resolveSupaCloudContext(env = process.env, cwd = process.cwd()) {
6308
- const dotenv = readDotEnvFile(cwd);
6309
- const supabaseUrl = pickValue(env, dotenv, ["SUPABASE_URL"]);
6310
- const explicitApiUrl = pickValue(env, dotenv, ["SUPACLOUD_API_URL", "SUPACLOUD_MANAGEMENT_API_URL", "MANAGEMENT_API_URL"]);
6311
- const inferredToken = pickValue(env, dotenv, ["SUPABASE_SERVICE_ROLE_KEY", "SUPACLOUD_API_TOKEN"]);
6312
- const explicitProjectRef = pickValue(env, dotenv, ["SUPACLOUD_PROJECT_REF", "X_PROJECT_REF"]).value;
6313
- const hostFromEnv = env.SUPACLOUD_HOST;
6314
- const normalizedSupabaseUrl = normalizeUrl(supabaseUrl.value);
6315
- const projectRef = explicitProjectRef || inferProjectRefFromSupabaseUrl(normalizedSupabaseUrl);
6316
- const apiUrl = normalizeUrl(explicitApiUrl.value) || inferManagementApiUrlFromSupabaseUrl(normalizedSupabaseUrl, projectRef) || (hostFromEnv ? `http://${hostFromEnv}:9090` : "");
6317
- const resolvedHostFromUrl = hostFromUrl(apiUrl || normalizedSupabaseUrl);
6381
+ function sourceProjectCore(values) {
6382
+ const supabaseUrl = normalizeUrl(values.SUPABASE_URL || "");
6383
+ const projectRef = (values.SUPACLOUD_PROJECT_REF || values.X_PROJECT_REF || "").trim() || inferProjectRefFromSupabaseUrl(supabaseUrl);
6384
+ const explicitApiUrl = values.SUPACLOUD_API_URL || values.SUPACLOUD_MANAGEMENT_API_URL || values.MANAGEMENT_API_URL || "";
6385
+ const apiUrl = normalizeUrl(explicitApiUrl) || inferManagementApiUrlFromSupabaseUrl(supabaseUrl, projectRef) || (values.SUPACLOUD_HOST ? `http://${values.SUPACLOUD_HOST}:9090` : "");
6386
+ const apiToken = values.SUPACLOUD_API_TOKEN || values.SUPABASE_SERVICE_ROLE_KEY || "";
6387
+ return { apiUrl, apiToken, projectRef, supabaseUrl };
6388
+ }
6389
+ function processValues(env) {
6390
+ return Object.fromEntries(Object.entries(env).filter((entry) => entry[1] !== undefined));
6391
+ }
6392
+ function hasProcessContext(env) {
6393
+ return CORE_CONTEXT_KEYS.some((key) => Object.hasOwn(env, key));
6394
+ }
6395
+ function completeProjectContext(values) {
6396
+ const core = sourceProjectCore(values);
6397
+ return Boolean(core.apiUrl && core.apiToken && core.projectRef);
6398
+ }
6399
+ function namedEnvironmentSource(cwd, selector) {
6400
+ const environment = normalizeEnvironmentName(selector);
6401
+ const path = resolve(cwd, `.env.supacloud.${selector}`);
6402
+ const values = readEnvFile(path, true);
6403
+ if (values.SUPACLOUD_ENV && normalizeEnvironmentName(values.SUPACLOUD_ENV) !== environment) {
6404
+ throw new Error(`SUPACLOUD_ENV in ${path} does not match selector ${selector}`);
6405
+ }
6406
+ return { values, kind: "named_env_file", path, environment };
6407
+ }
6408
+ function explicitEnvironmentSource(cwd, envFile) {
6409
+ const path = resolve(cwd, envFile);
6410
+ const values = readEnvFile(path, true);
6411
+ if (!values.SUPACLOUD_ENV)
6412
+ throw new Error(`SUPACLOUD_ENV is required in ${path}`);
6318
6413
  return {
6319
- host: hostFromEnv ?? resolvedHostFromUrl,
6414
+ values,
6415
+ kind: "explicit_env_file",
6416
+ path,
6417
+ environment: normalizeEnvironmentName(values.SUPACLOUD_ENV)
6418
+ };
6419
+ }
6420
+ function contextSource(env, cwd, selection) {
6421
+ if (selection.environmentName && selection.envFile)
6422
+ throw new Error("Environment selectors are mutually exclusive");
6423
+ if (selection.environmentName)
6424
+ return namedEnvironmentSource(cwd, selection.environmentName);
6425
+ if (selection.envFile)
6426
+ return explicitEnvironmentSource(cwd, selection.envFile);
6427
+ if (env.SUPACLOUD_ENV) {
6428
+ const environment2 = normalizeEnvironmentName(env.SUPACLOUD_ENV);
6429
+ const values2 = processValues(env);
6430
+ if (completeProjectContext(values2)) {
6431
+ return { values: values2, kind: "process_env", path: null, environment: environment2 };
6432
+ }
6433
+ return namedEnvironmentSource(cwd, env.SUPACLOUD_ENV);
6434
+ }
6435
+ if (hasProcessContext(env)) {
6436
+ return { values: processValues(env), kind: "process_env", path: null, environment: "" };
6437
+ }
6438
+ const path = resolve(cwd, ".env");
6439
+ const values = readEnvFile(path, false);
6440
+ const environment = values.SUPACLOUD_ENV ? normalizeEnvironmentName(values.SUPACLOUD_ENV) : "";
6441
+ return { values, kind: Object.keys(values).length ? "legacy_dotenv" : "none", path: existsSync(path) ? path : null, environment };
6442
+ }
6443
+ function resolveSupaCloudContext(env = process.env, cwd = process.cwd(), selection = {}) {
6444
+ const source = contextSource(env, cwd, selection);
6445
+ const core = sourceProjectCore(source.values);
6446
+ const host = source.values.SUPACLOUD_HOST || hostFromUrl(core.apiUrl || core.supabaseUrl);
6447
+ const readOnly = env.SUPACLOUD_READ_ONLY === "true" || source.values.SUPACLOUD_READ_ONLY === "true";
6448
+ return {
6449
+ host,
6320
6450
  sshUser: env.SUPACLOUD_SSH_USER ?? "root",
6321
6451
  sshPort: parseInt(env.SUPACLOUD_SSH_PORT ?? "22", 10),
6322
6452
  sshKey: env.SUPACLOUD_SSH_KEY ?? resolve(homedir(), ".ssh", "id_rsa"),
6323
6453
  sshPass: env.SUPACLOUD_SSH_PASS ?? "",
6324
- apiUrl,
6325
- apiToken: env.SUPACLOUD_API_TOKEN ?? inferredToken.value,
6326
- projectRef,
6327
- readOnly: env.SUPACLOUD_READ_ONLY === "true",
6328
- inferredSupabaseUrl: normalizedSupabaseUrl,
6329
- inferredServiceRoleKey: inferredToken.value,
6330
- source: detectSource([
6331
- supabaseUrl.value || explicitApiUrl.value ? supabaseUrl.value ? supabaseUrl.source : explicitApiUrl.source : "none",
6332
- inferredToken.value ? inferredToken.source : "none"
6333
- ])
6454
+ apiUrl: core.apiUrl,
6455
+ apiToken: core.apiToken,
6456
+ projectRef: core.projectRef,
6457
+ readOnly,
6458
+ environment: source.environment,
6459
+ production: source.environment === "prod" || source.environment === "production",
6460
+ inferredSupabaseUrl: core.supabaseUrl,
6461
+ inferredServiceRoleKey: source.values.SUPABASE_SERVICE_ROLE_KEY || "",
6462
+ source: source.kind,
6463
+ sourcePath: source.path
6334
6464
  };
6335
6465
  }
6336
6466
 
6467
+ // src/shared/execution-policy.ts
6468
+ var ACTION_POLICY = {
6469
+ project: {
6470
+ read: ["get", "health", "logs", "api_keys", "settings", "tasks", "task_detail", "task_stats", "dlq", "background_settings"],
6471
+ write: ["task_cancel", "task_retry", "update_background_settings"]
6472
+ },
6473
+ database: {
6474
+ 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", "project_url", "generate_types"],
6475
+ write: ["query", "apply_migration", "push_migrations", "baseline_migrations", "create_table_rls"]
6476
+ },
6477
+ supabase: {
6478
+ local: ["version", "migration_new", "db_diff", "db_reset", "db_pull", "db_dump", "migration_list", "gen_types"],
6479
+ write: ["push"]
6480
+ },
6481
+ auth: {
6482
+ read: ["list_providers", "get_provider", "supported_providers", "get_settings", "get_config"],
6483
+ write: ["configure_provider", "update_provider", "disable_provider", "wechat_mini", "wechat_open", "update_settings", "update_config"]
6484
+ },
6485
+ storage: {
6486
+ read: ["status", "list_buckets", "list_files"],
6487
+ write: ["upload_base64", "delete_file"]
6488
+ },
6489
+ edge_functions: {
6490
+ read: ["list", "source"],
6491
+ local: ["check"],
6492
+ write: ["deploy", "deploy_bundle", "config", "activate", "delete"]
6493
+ },
6494
+ scheduled_functions: { read: ["list"], write: ["create", "update", "delete"] },
6495
+ secrets: { read: ["list"], write: ["upsert", "delete"] },
6496
+ frontend: {
6497
+ read: ["list", "get", "build_logs", "list_frameworks", "list_records"],
6498
+ write: ["create", "update", "delete", "deploy_git", "deploy_upload", "redeploy", "add_domain", "remove_domain", "set_env"]
6499
+ },
6500
+ task_events: { read: ["inspect_webhook"], write: ["register_webhook", "unregister_webhook"] },
6501
+ diagnostics: { read: ["list_checks", "get_run"], write: ["run_checks", "repair"] },
6502
+ gateway: {
6503
+ read: ["routes", "get_certificate", "custom_hostname"],
6504
+ write: ["upsert_route", "update_route", "delete_route", "config", "update_certificate", "issue_certificate", "deploy_certificate", "rebuild", "set_custom_hostname", "delete_custom_hostname", "verify_custom_hostname"]
6505
+ },
6506
+ branch: { read: ["list", "promotion_plan"], write: ["create", "delete", "promote"] },
6507
+ queue: {
6508
+ read: ["list", "stats", "list_messages", "dlq", "get_message", "get_settings"],
6509
+ write: ["send", "receive", "ack", "release", "fail", "retry", "delete_message", "update_settings"]
6510
+ },
6511
+ ai: { local: ["show_skill", "install_skill"] }
6512
+ };
6513
+ function declaredMode(moduleName, action) {
6514
+ const policy = ACTION_POLICY[moduleName];
6515
+ if (!policy)
6516
+ return;
6517
+ for (const mode of ["read", "write", "local"]) {
6518
+ if (policy[mode]?.includes(action))
6519
+ return mode;
6520
+ }
6521
+ return;
6522
+ }
6523
+ function executionMode(moduleName, action, args) {
6524
+ if (moduleName === "database" && ["push_migrations", "baseline_migrations"].includes(action) && args.dry_run === true)
6525
+ return "read";
6526
+ if (moduleName === "supabase" && action === "push" && args.dry_run === true)
6527
+ return "read";
6528
+ return declaredMode(moduleName, action);
6529
+ }
6530
+ function authorizeExecution(moduleName, args, authorization) {
6531
+ const action = typeof args.action === "string" ? args.action : "";
6532
+ if (!action)
6533
+ return;
6534
+ const mode = executionMode(moduleName, action, args);
6535
+ const { context, confirmProduction } = authorization;
6536
+ if (!mode && (context.production || context.readOnly)) {
6537
+ throw new Error(`Execution policy has no classification for ${moduleName}.${action}`);
6538
+ }
6539
+ if (context.production && mode === "read" && typeof args.ref === "string" && args.ref && args.ref !== context.projectRef) {
6540
+ throw new Error("Production profiles cannot target a different project with --ref");
6541
+ }
6542
+ if (mode !== "write")
6543
+ return;
6544
+ if (context.production && moduleName === "diagnostics" && action === "repair") {
6545
+ throw new Error("diagnostics repair is forbidden in production environments");
6546
+ }
6547
+ if (context.readOnly) {
6548
+ throw new Error(`Remote write ${moduleName}.${action} is blocked in read-only mode (SUPACLOUD_READ_ONLY=true)`);
6549
+ }
6550
+ if (!context.production)
6551
+ return;
6552
+ const requestedRef = args.ref ?? context.projectRef;
6553
+ if (typeof requestedRef !== "string" || !requestedRef) {
6554
+ throw new Error(`Production write ${moduleName}.${action} requires a project ref`);
6555
+ }
6556
+ if (requestedRef !== context.projectRef) {
6557
+ throw new Error("Production profiles cannot target a different project with --ref");
6558
+ }
6559
+ if (confirmProduction !== context.projectRef || confirmProduction !== requestedRef) {
6560
+ throw new Error(`Production write requires --confirm-production ${context.projectRef}`);
6561
+ }
6562
+ }
6563
+ function validateExecutionPolicyCoverage(tools) {
6564
+ for (const [moduleName, tool] of Object.entries(tools)) {
6565
+ const actionSchema = schemaProperties(tool.schema).action;
6566
+ if (!actionSchema)
6567
+ continue;
6568
+ const actions = schemaEnumValues(actionSchema);
6569
+ for (const action of actions) {
6570
+ if (!declaredMode(moduleName, String(action))) {
6571
+ throw new Error(`Execution policy has no classification for ${moduleName}.${String(action)}`);
6572
+ }
6573
+ }
6574
+ }
6575
+ }
6576
+
6337
6577
  // src/shared/transports/http.ts
6338
6578
  var DEFAULT_TIMEOUT = 30000;
6339
6579
  var MAX_RETRIES = 2;
@@ -6348,6 +6588,16 @@ function isRetryableError(error) {
6348
6588
  const networkError = error;
6349
6589
  return networkError.name === "AbortError" || networkError.code === "ECONNREFUSED" || networkError.code === "ECONNRESET";
6350
6590
  }
6591
+ function transportFailure(error) {
6592
+ const networkError = error instanceof Error ? error : null;
6593
+ const code = networkError?.name === "AbortError" ? "TIMEOUT" : networkError?.code === "ECONNRESET" ? "CONNECTION_RESET" : "NETWORK_ERROR";
6594
+ return {
6595
+ ok: false,
6596
+ status: 500,
6597
+ data: { error: "Network Error", code },
6598
+ transportError: true
6599
+ };
6600
+ }
6351
6601
  async function fetchWithTimeout(url, options) {
6352
6602
  const controller = new AbortController;
6353
6603
  const timeout = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT);
@@ -6382,6 +6632,57 @@ async function fetchWithRetry(url, options) {
6382
6632
  }
6383
6633
  throw new Error("Unreachable");
6384
6634
  }
6635
+ function declaredResponseTooLarge(response, maxBytes) {
6636
+ const contentLength = response.headers.get("content-length");
6637
+ if (contentLength === null || !/^\d+$/.test(contentLength))
6638
+ return false;
6639
+ return Number(contentLength) > maxBytes;
6640
+ }
6641
+ function joinedResponseBytes(chunks, totalBytes) {
6642
+ const responseBytes = new Uint8Array(totalBytes);
6643
+ let offset = 0;
6644
+ for (const chunk of chunks) {
6645
+ responseBytes.set(chunk, offset);
6646
+ offset += chunk.byteLength;
6647
+ }
6648
+ return responseBytes;
6649
+ }
6650
+ async function responseBytesWithinLimit(response, maxBytes) {
6651
+ if (declaredResponseTooLarge(response, maxBytes)) {
6652
+ await response.body?.cancel();
6653
+ return null;
6654
+ }
6655
+ if (!response.body)
6656
+ return new Uint8Array;
6657
+ const reader = response.body.getReader();
6658
+ const chunks = [];
6659
+ let totalBytes = 0;
6660
+ while (true) {
6661
+ const { done, value } = await reader.read();
6662
+ if (done)
6663
+ return joinedResponseBytes(chunks, totalBytes);
6664
+ totalBytes += value.byteLength;
6665
+ if (totalBytes > maxBytes) {
6666
+ await reader.cancel();
6667
+ return null;
6668
+ }
6669
+ chunks.push(value);
6670
+ }
6671
+ }
6672
+ function parsedUtf8Json(responseBytes) {
6673
+ try {
6674
+ const responseText = new TextDecoder("utf-8", { fatal: true }).decode(responseBytes);
6675
+ return JSON.parse(responseText);
6676
+ } catch (error) {
6677
+ if (error instanceof SyntaxError || error instanceof TypeError)
6678
+ return null;
6679
+ throw error;
6680
+ }
6681
+ }
6682
+ async function boundedResponseJson(response, maxBytes) {
6683
+ const responseBytes = await responseBytesWithinLimit(response, maxBytes);
6684
+ return responseBytes === null ? null : parsedUtf8Json(responseBytes);
6685
+ }
6385
6686
 
6386
6687
  class HttpTransport {
6387
6688
  baseUrl;
@@ -6396,16 +6697,16 @@ class HttpTransport {
6396
6697
  "Content-Type": "application/json"
6397
6698
  };
6398
6699
  }
6399
- async get(path) {
6700
+ async get(path, options = {}) {
6400
6701
  try {
6401
6702
  const res = await fetchWithRetry(`${this.baseUrl}${path}`, {
6402
6703
  method: "GET",
6403
6704
  headers: this.headers()
6404
6705
  });
6405
- const data = await res.json().catch(() => null);
6706
+ const data = options.maxResponseBytes === undefined ? await res.json().catch(() => null) : await boundedResponseJson(res, options.maxResponseBytes);
6406
6707
  return { ok: res.ok, status: res.status, data };
6407
6708
  } catch (error) {
6408
- return { ok: false, status: 500, data: { error: "Network Error", details: error.message } };
6709
+ return transportFailure(error);
6409
6710
  }
6410
6711
  }
6411
6712
  async post(path, body) {
@@ -6418,7 +6719,7 @@ class HttpTransport {
6418
6719
  const data = await res.json().catch(() => null);
6419
6720
  return { ok: res.ok, status: res.status, data };
6420
6721
  } catch (error) {
6421
- return { ok: false, status: 500, data: { error: "Network Error", details: error.message } };
6722
+ return transportFailure(error);
6422
6723
  }
6423
6724
  }
6424
6725
  async postMultipart(path, formData) {
@@ -6432,7 +6733,7 @@ class HttpTransport {
6432
6733
  const data = await res.json().catch(() => null);
6433
6734
  return { ok: res.ok, status: res.status, data };
6434
6735
  } catch (error) {
6435
- return { ok: false, status: 500, data: { error: "Network Error", details: error.message } };
6736
+ return transportFailure(error);
6436
6737
  }
6437
6738
  }
6438
6739
  async patch(path, body) {
@@ -6445,7 +6746,7 @@ class HttpTransport {
6445
6746
  const data = await res.json().catch(() => null);
6446
6747
  return { ok: res.ok, status: res.status, data };
6447
6748
  } catch (error) {
6448
- return { ok: false, status: 500, data: { error: "Network Error", details: error.message } };
6749
+ return transportFailure(error);
6449
6750
  }
6450
6751
  }
6451
6752
  async put(path, body) {
@@ -6458,7 +6759,7 @@ class HttpTransport {
6458
6759
  const data = await res.json().catch(() => null);
6459
6760
  return { ok: res.ok, status: res.status, data };
6460
6761
  } catch (error) {
6461
- return { ok: false, status: 500, data: { error: "Network Error", details: error.message } };
6762
+ return transportFailure(error);
6462
6763
  }
6463
6764
  }
6464
6765
  async delete(path) {
@@ -6470,7 +6771,7 @@ class HttpTransport {
6470
6771
  const data = await res.json().catch(() => null);
6471
6772
  return { ok: res.ok, status: res.status, data };
6472
6773
  } catch (error) {
6473
- return { ok: false, status: 500, data: { error: "Network Error", details: error.message } };
6774
+ return transportFailure(error);
6474
6775
  }
6475
6776
  }
6476
6777
  async ping() {
@@ -6662,7 +6963,7 @@ Actions: ${allActions.join(", ")}${readOnly ? " (read-only mode)" : ""}`, {
6662
6963
  owner_column: optional(Type.String(), "[create_table_rls owner] UUID owner column matched to auth.uid()")
6663
6964
  }, async (args) => {
6664
6965
  const { action } = args;
6665
- const ref = projectRef || args.ref;
6966
+ const ref = args.ref || projectRef;
6666
6967
  const schema = args.schema || "public";
6667
6968
  const schemas = args.schemas || ["public"];
6668
6969
  if (args.file && !args.sql) {
@@ -7216,6 +7517,62 @@ function pgToTs(t) {
7216
7517
  }
7217
7518
 
7218
7519
  // src/shared/tools/auth-tools.ts
7520
+ var authConfigRecord = Type.Record(Type.String(), Type.Unknown());
7521
+ var safeAuthMutationCodes = new Set([
7522
+ "AUTH_RUNTIME_APPLY_FAILED",
7523
+ "SUPAUTH_DEPENDENT_REFRESH_FAILED"
7524
+ ]);
7525
+ function parseAuthConfig(input) {
7526
+ if (typeof input !== "string")
7527
+ return input;
7528
+ try {
7529
+ return JSON.parse(input);
7530
+ } catch (error) {
7531
+ if (!(error instanceof SyntaxError))
7532
+ throw error;
7533
+ throw new Error("Invalid auth config JSON object");
7534
+ }
7535
+ }
7536
+ var authConfigSchema = decodedSchema(Type.Union([Type.String(), authConfigRecord]), authConfigRecord, parseAuthConfig);
7537
+ function safeAuthFailureFields(payload) {
7538
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
7539
+ return {};
7540
+ const body = payload;
7541
+ const fields = {};
7542
+ if (typeof body.code === "string" && safeAuthMutationCodes.has(body.code)) {
7543
+ fields.code = body.code;
7544
+ }
7545
+ for (const field of ["persisted", "runtime_applied", "dependents_applied"]) {
7546
+ if (typeof body[field] === "boolean")
7547
+ fields[field] = body[field];
7548
+ }
7549
+ if (body.dependent_status === "failed" || body.dependent_status === "unknown") {
7550
+ fields.dependent_status = body.dependent_status;
7551
+ }
7552
+ if (body.runtime_mode === "local" || body.runtime_mode === "owner" || body.runtime_mode === "shared") {
7553
+ fields.runtime_mode = body.runtime_mode;
7554
+ }
7555
+ return fields;
7556
+ }
7557
+ function safeAuthMutationFailure(response) {
7558
+ return {
7559
+ ok: false,
7560
+ http_status: response.status,
7561
+ ...safeAuthFailureFields(response.data)
7562
+ };
7563
+ }
7564
+ function authMutationResult(response, successMessage) {
7565
+ if (response.ok) {
7566
+ return { content: [{ type: "text", text: successMessage }] };
7567
+ }
7568
+ return {
7569
+ isError: true,
7570
+ content: [{
7571
+ type: "text",
7572
+ text: JSON.stringify(safeAuthMutationFailure(response), null, 2)
7573
+ }]
7574
+ };
7575
+ }
7219
7576
  function formatProviders(data) {
7220
7577
  if (!data || typeof data !== "object")
7221
7578
  return JSON.stringify(data, null, 2);
@@ -7272,7 +7629,7 @@ Actions: list_providers, get_provider, configure_provider, update_provider, disa
7272
7629
  url: optional(Type.String(), "[configure] Custom OAuth URL"),
7273
7630
  app_id: optional(Type.String(), "[wechat_*] WeChat App ID"),
7274
7631
  app_secret: optional(Type.String(), "[wechat_*] WeChat App Secret"),
7275
- config: optional(Type.Record(Type.String(), Type.Unknown()), "[update_settings/update_config] Config fields")
7632
+ config: optional(authConfigSchema, "[update_settings/update_config] Config fields as a JSON object")
7276
7633
  }, async (args) => {
7277
7634
  const { action, ref, provider, client_id, client_secret, redirect_uri, url, app_id, app_secret, config } = args;
7278
7635
  const need = (f) => {
@@ -7355,8 +7712,7 @@ ${JSON.stringify(r.data, null, 2)}` : `❌ Failed (${r.status}): ${JSON.stringif
7355
7712
  need("ref");
7356
7713
  if (!config)
7357
7714
  throw new Error("'config' required");
7358
- text = (await http.patch(`/v1/projects/${ref}/auth/config`, config)).ok ? `✅ Auth settings updated` : `❌ Failed`;
7359
- break;
7715
+ return authMutationResult(await http.patch(`/v1/projects/${ref}/auth/config`, config), "✅ Auth settings updated");
7360
7716
  case "get_config":
7361
7717
  need("ref");
7362
7718
  text = ok(await http.get(`/v1/projects/${ref}/config/auth`));
@@ -7365,8 +7721,7 @@ ${JSON.stringify(r.data, null, 2)}` : `❌ Failed (${r.status}): ${JSON.stringif
7365
7721
  need("ref");
7366
7722
  if (!config)
7367
7723
  throw new Error("'config' required");
7368
- text = (await http.patch(`/v1/projects/${ref}/config/auth`, config)).ok ? `✅ Auth config updated` : `❌ Failed`;
7369
- break;
7724
+ return authMutationResult(await http.patch(`/v1/projects/${ref}/config/auth`, config), "✅ Auth config updated");
7370
7725
  default:
7371
7726
  text = `❌ Unknown action: ${action}`;
7372
7727
  }
@@ -7469,6 +7824,39 @@ import { tmpdir } from "node:os";
7469
7824
  import { basename as basename2, join as join2, resolve as resolve2 } from "node:path";
7470
7825
  import { promisify } from "node:util";
7471
7826
  import { execFile } from "node:child_process";
7827
+
7828
+ // src/shared/tools/release-control-response.ts
7829
+ var RELEASE_CONTROL_RESPONSE_SCHEMA = "supacloud.cli.release-control.v1";
7830
+ function releaseControlSuccess(operation, payload) {
7831
+ return releaseControlResponse({
7832
+ schema: RELEASE_CONTROL_RESPONSE_SCHEMA,
7833
+ ok: true,
7834
+ operation,
7835
+ ...payload
7836
+ });
7837
+ }
7838
+ function releaseControlFailure(operation, code, httpStatus) {
7839
+ return releaseControlErrorResponse({
7840
+ schema: RELEASE_CONTROL_RESPONSE_SCHEMA,
7841
+ ok: false,
7842
+ operation,
7843
+ error: { code, http_status: httpStatus }
7844
+ });
7845
+ }
7846
+ function releaseControlMutationFailure(operation, response) {
7847
+ const outcomeUnknown = response.transportError || response.status === 408 || response.status >= 500;
7848
+ return outcomeUnknown ? releaseControlFailure(operation, "OUTCOME_UNKNOWN", response.transportError ? null : response.status) : releaseControlFailure(operation, "HTTP_ERROR", response.status);
7849
+ }
7850
+ function releaseControlResponse(payload) {
7851
+ return {
7852
+ content: [{ type: "text", text: JSON.stringify(payload, null, 2) }]
7853
+ };
7854
+ }
7855
+ function releaseControlErrorResponse(payload) {
7856
+ return { ...releaseControlResponse(payload), isError: true };
7857
+ }
7858
+
7859
+ // src/shared/tools/advanced-tools.ts
7472
7860
  var execFileAsync = promisify(execFile);
7473
7861
  async function runBunBuild(args) {
7474
7862
  try {
@@ -7540,7 +7928,29 @@ function parseFunctionFiles(input) {
7540
7928
  }
7541
7929
  }
7542
7930
  var functionFilesSchema = decodedSchema(Type.Union([Type.String(), functionFilesRecordSchema]), functionFilesRecordSchema, parseFunctionFiles);
7931
+ function parseFunctionVersion(input) {
7932
+ const version = String(input);
7933
+ if (!CANONICAL_FUNCTION_VERSION_PATTERN.test(version) || !Number.isSafeInteger(Number(version))) {
7934
+ throw new Error("Function version must be a canonical safe integer");
7935
+ }
7936
+ return version;
7937
+ }
7938
+ var CANONICAL_FUNCTION_VERSION_PATTERN = /^(?:0|[1-9][0-9]*)$/;
7939
+ var SAFE_FUNCTION_REF_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
7940
+ var SAFE_FUNCTION_SLUG_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
7941
+ var FUNCTION_ACTIVATION_ARGUMENTS = new Set(["action", "ref", "slug", "version"]);
7942
+ var functionVersionSchema = Type.Optional(decodedSchema(Type.Union([
7943
+ Type.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER }),
7944
+ Type.String({ pattern: CANONICAL_FUNCTION_VERSION_PATTERN.source, maxLength: 16 })
7945
+ ]), Type.String({ pattern: CANONICAL_FUNCTION_VERSION_PATTERN.source, maxLength: 16 }), parseFunctionVersion));
7543
7946
  var secretListSchema = Type.Array(Type.Object({ name: Type.String(), value: Type.String() }));
7947
+ var ENVIRONMENT_SECRET_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]{0,255}$/;
7948
+ var MAX_SECRET_COUNT = 1024;
7949
+ var MAX_ENVIRONMENT_SECRET_BYTES = 24 * 1024;
7950
+ var MAX_SECRET_JSON_BYTES = 1024 * 1024;
7951
+ var INVALID_ENVIRONMENT_SECRET_NAMES_MESSAGE = "Environment secret names are invalid";
7952
+ var INVALID_ENVIRONMENT_SECRET_VALUES_MESSAGE = "Environment secret values are missing or exceed safe limits";
7953
+ var INVALID_SECRET_LIST_RESPONSE = "❌ Project secret list response is invalid";
7544
7954
  function parseSecrets(value) {
7545
7955
  if (Array.isArray(value))
7546
7956
  return value;
@@ -7567,6 +7977,89 @@ function parseSecrets(value) {
7567
7977
  }).filter((entry) => entry.name);
7568
7978
  }
7569
7979
  var secretsSchema = Type.Optional(decodedSchema(Type.Union([Type.String(), secretListSchema]), secretListSchema, parseSecrets));
7980
+ function validEnvironmentSecretNames(names) {
7981
+ if (names.length === 0 || names.length > MAX_SECRET_COUNT)
7982
+ return false;
7983
+ const uniqueNames = new Set;
7984
+ for (const name of names) {
7985
+ if (!ENVIRONMENT_SECRET_NAME_PATTERN.test(name) || uniqueNames.has(name))
7986
+ return false;
7987
+ uniqueNames.add(name);
7988
+ }
7989
+ return true;
7990
+ }
7991
+ function parseEnvironmentSecretNames(input) {
7992
+ const names = input.split(",", MAX_SECRET_COUNT + 1).map((name) => name.trim());
7993
+ if (!validEnvironmentSecretNames(names)) {
7994
+ throw new Error(INVALID_ENVIRONMENT_SECRET_NAMES_MESSAGE);
7995
+ }
7996
+ return names;
7997
+ }
7998
+ var environmentSecretNamesSchema = Type.Optional(decodedSchema(Type.String(), Type.Array(Type.String({ pattern: ENVIRONMENT_SECRET_NAME_PATTERN.source }), {
7999
+ minItems: 1,
8000
+ maxItems: MAX_SECRET_COUNT,
8001
+ uniqueItems: true
8002
+ }), parseEnvironmentSecretNames));
8003
+ function jsonWithinSecretLimit(payload) {
8004
+ try {
8005
+ const serializedPayload = JSON.stringify(payload);
8006
+ return serializedPayload !== undefined && Buffer.byteLength(serializedPayload) <= MAX_SECRET_JSON_BYTES;
8007
+ } catch (error) {
8008
+ if (error instanceof TypeError)
8009
+ return false;
8010
+ throw error;
8011
+ }
8012
+ }
8013
+ function maskedSecretEntry(candidate) {
8014
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate))
8015
+ return null;
8016
+ const { name, value } = candidate;
8017
+ if (typeof name !== "string" || !ENVIRONMENT_SECRET_NAME_PATTERN.test(name))
8018
+ return null;
8019
+ if (value !== "********")
8020
+ return null;
8021
+ return { name, value: "********" };
8022
+ }
8023
+ function projectedMaskedSecrets(payload) {
8024
+ if (!Array.isArray(payload) || payload.length > MAX_SECRET_COUNT)
8025
+ return null;
8026
+ if (!jsonWithinSecretLimit(payload))
8027
+ return null;
8028
+ const secretNames = new Set;
8029
+ const projectedSecrets = [];
8030
+ for (const candidate of payload) {
8031
+ const secret = maskedSecretEntry(candidate);
8032
+ if (!secret || secretNames.has(secret.name))
8033
+ return null;
8034
+ secretNames.add(secret.name);
8035
+ projectedSecrets.push(secret);
8036
+ }
8037
+ return projectedSecrets;
8038
+ }
8039
+ function secretsFromEnvironment(names, environment) {
8040
+ const secrets = names.map((name) => {
8041
+ const secretValue = environment[name];
8042
+ if (typeof secretValue !== "string" || secretValue.length === 0 || Buffer.byteLength(secretValue) > MAX_ENVIRONMENT_SECRET_BYTES) {
8043
+ throw new Error(INVALID_ENVIRONMENT_SECRET_VALUES_MESSAGE);
8044
+ }
8045
+ return { name, value: secretValue };
8046
+ });
8047
+ if (!jsonWithinSecretLimit(secrets)) {
8048
+ throw new Error(INVALID_ENVIRONMENT_SECRET_VALUES_MESSAGE);
8049
+ }
8050
+ return secrets;
8051
+ }
8052
+ function secretsForUpsert(inlineSecrets, environmentNames, environment) {
8053
+ if (inlineSecrets !== undefined && environmentNames !== undefined) {
8054
+ throw new Error("'--from-env' cannot be combined with '--secrets'");
8055
+ }
8056
+ if (environmentNames !== undefined) {
8057
+ return secretsFromEnvironment(environmentNames, environment);
8058
+ }
8059
+ if (!inlineSecrets?.length)
8060
+ throw new Error("'secrets' array required");
8061
+ return inlineSecrets;
8062
+ }
7570
8063
  function confirmedFunctionConfig(payload, expected) {
7571
8064
  if (!payload || typeof payload !== "object" || Array.isArray(payload))
7572
8065
  return false;
@@ -7587,12 +8080,60 @@ function functionSourceCode(payload) {
7587
8080
  const code = payload.code;
7588
8081
  return typeof code === "string" ? code : null;
7589
8082
  }
7590
- function registerAdvancedTools(server, http) {
8083
+ function objectRecord(candidate) {
8084
+ return candidate && typeof candidate === "object" && !Array.isArray(candidate) ? candidate : null;
8085
+ }
8086
+ function activationResponse(slug, version, response) {
8087
+ const operation = "edge_functions.activate";
8088
+ if (!response.ok)
8089
+ return releaseControlMutationFailure(operation, response);
8090
+ const receipt = objectRecord(response.data);
8091
+ const config = objectRecord(receipt?.config);
8092
+ if (receipt?.success !== true || receipt.version !== version || config?.version !== version || typeof config.verify_jwt !== "boolean") {
8093
+ return releaseControlFailure(operation, "OUTCOME_UNKNOWN", response.status);
8094
+ }
8095
+ return releaseControlSuccess(operation, {
8096
+ slug,
8097
+ version,
8098
+ verify_jwt: config.verify_jwt
8099
+ });
8100
+ }
8101
+ function readOnlyActivationResult() {
8102
+ return {
8103
+ isError: true,
8104
+ content: [{ type: "text", text: "⚠️ Edge Function activation blocked in read-only mode." }]
8105
+ };
8106
+ }
8107
+ function functionActivationTarget(args) {
8108
+ const projectRef = typeof args.ref === "string" ? args.ref.trim() : "";
8109
+ const functionSlug = typeof args.slug === "string" ? args.slug.trim() : "";
8110
+ const version = args.version;
8111
+ if (!SAFE_FUNCTION_REF_PATTERN.test(projectRef))
8112
+ throw new Error("'ref' is invalid for 'activate'");
8113
+ if (!SAFE_FUNCTION_SLUG_PATTERN.test(functionSlug))
8114
+ throw new Error("'slug' is invalid for 'activate'");
8115
+ if (typeof version !== "string" || !CANONICAL_FUNCTION_VERSION_PATTERN.test(version) || !Number.isSafeInteger(Number(version))) {
8116
+ throw new Error("'version' is invalid for 'activate'");
8117
+ }
8118
+ return { projectRef, functionSlug, version };
8119
+ }
8120
+ async function activateFunctionVersion(http, args, readOnly = false) {
8121
+ if (readOnly)
8122
+ return readOnlyActivationResult();
8123
+ const unsupported = Object.keys(args).filter((name) => !FUNCTION_ACTIVATION_ARGUMENTS.has(name));
8124
+ if (unsupported.length > 0)
8125
+ throw new Error(`'${unsupported[0]}' is not supported for 'activate'`);
8126
+ const { projectRef, functionSlug, version } = functionActivationTarget(args);
8127
+ const endpoint = `/v1/projects/${encodeURIComponent(projectRef)}/functions/${encodeURIComponent(functionSlug)}` + `/versions/${encodeURIComponent(version)}/activate`;
8128
+ return activationResponse(functionSlug, version, await http.post(endpoint));
8129
+ }
8130
+ function registerAdvancedTools(server, http, environment = process.env, options = {}) {
7591
8131
  server.tool("edge_functions", `Edge Function management (Deno/Bun serverless). Server auto-bundles dependencies.
7592
- Actions: list, deploy, deploy_bundle, config, source, delete, check`, {
7593
- action: withDescription(stringEnum(["list", "deploy", "deploy_bundle", "config", "source", "delete", "check"]), "Action"),
8132
+ Actions: list, deploy, deploy_bundle, config, source, activate, delete, check`, {
8133
+ action: withDescription(stringEnum(["list", "deploy", "deploy_bundle", "config", "source", "activate", "delete", "check"]), "Action"),
7594
8134
  ref: withDescription(Type.String(), "Project ref"),
7595
- slug: optional(Type.String(), "[deploy/deploy_bundle/config/source/delete/check] Function name"),
8135
+ slug: optional(Type.String(), "[deploy/deploy_bundle/config/source/activate/delete/check] Function name"),
8136
+ version: withDescription(functionVersionSchema, "[activate] Existing Function version"),
7596
8137
  code: optional(Type.String(), "[deploy/check] Function source code (TypeScript)"),
7597
8138
  path: optional(Type.String(), "[deploy/check] Local file path to read code from (alternative to code)"),
7598
8139
  output: optional(Type.String(), "[source] Write source to this local file instead of stdout; the file must not already exist"),
@@ -7602,6 +8143,8 @@ Actions: list, deploy, deploy_bundle, config, source, delete, check`, {
7602
8143
  verify_jwt: optional(Type.Boolean(), "[deploy/deploy_bundle/config] Set JWT verification for this function"),
7603
8144
  background_routes: withDescription(backgroundRoutesSchema, "[deploy/deploy_bundle/config] Background route paths; pass comma-separated or JSON array in CLI")
7604
8145
  }, async (args) => {
8146
+ if (args.action === "activate")
8147
+ return activateFunctionVersion(http, args, options.readOnly);
7605
8148
  const { action, ref, slug, path: pathArg, output, files, entrypoint, minify, verify_jwt, background_routes } = args;
7606
8149
  let code = args.code;
7607
8150
  const need = (f, v) => {
@@ -7737,18 +8280,28 @@ Actions: list, upsert, delete`, {
7737
8280
  action: withDescription(stringEnum(["list", "upsert", "delete"]), "Action"),
7738
8281
  ref: withDescription(Type.String(), "Project ref"),
7739
8282
  secrets: withDescription(secretsSchema, "[upsert] Secret list as JSON array or KEY=VALUE,KEY2=VALUE2"),
8283
+ "from-env": withDescription(environmentSecretNamesSchema, "[upsert] Comma-separated environment variable names; values are read from this CLI process"),
7740
8284
  name: optional(Type.String(), "[delete] Secret name to delete")
7741
8285
  }, async (args) => {
7742
8286
  const { action, ref, secrets, name } = args;
8287
+ const environmentNames = args["from-env"];
7743
8288
  let text;
7744
8289
  switch (action) {
7745
- case "list":
7746
- text = JSON.stringify((await http.get(`/v1/projects/${ref}/secrets`)).data, null, 2);
8290
+ case "list": {
8291
+ const response = await http.get(`/v1/projects/${ref}/secrets`, {
8292
+ maxResponseBytes: MAX_SECRET_JSON_BYTES
8293
+ });
8294
+ if (!response.ok) {
8295
+ text = `❌ Failed (${response.status})`;
8296
+ break;
8297
+ }
8298
+ const maskedSecrets = projectedMaskedSecrets(response.data);
8299
+ text = maskedSecrets === null ? INVALID_SECRET_LIST_RESPONSE : JSON.stringify(maskedSecrets, null, 2);
7747
8300
  break;
8301
+ }
7748
8302
  case "upsert":
7749
- if (!secrets?.length)
7750
- throw new Error("'secrets' array required");
7751
- text = (await http.post(`/v1/projects/${ref}/secrets`, secrets)).ok ? `✅ Updated ${secrets.length} secrets` : `❌ Failed`;
8303
+ const secretsToUpsert = secretsForUpsert(secrets, environmentNames, environment);
8304
+ text = (await http.post(`/v1/projects/${ref}/secrets`, secretsToUpsert)).ok ? `✅ Updated ${secretsToUpsert.length} secrets` : `❌ Failed`;
7752
8305
  break;
7753
8306
  case "delete":
7754
8307
  if (!name)
@@ -8171,7 +8724,7 @@ function buildProjectLogsPath(ref, logType) {
8171
8724
  return `/v1/projects/${ref}/logs?${params.toString()}`;
8172
8725
  }
8173
8726
  function resolveRef(refFromArgs, defaultRef) {
8174
- const ref = defaultRef || refFromArgs;
8727
+ const ref = refFromArgs || defaultRef;
8175
8728
  if (!ref)
8176
8729
  throw new Error("'ref' is required for this action");
8177
8730
  return ref;
@@ -8360,7 +8913,7 @@ function formatMessages(data, label = "Messages") {
8360
8913
  return out;
8361
8914
  }
8362
8915
  function resolveRef2(refFromArgs, defaultRef) {
8363
- const ref = defaultRef || refFromArgs;
8916
+ const ref = refFromArgs || defaultRef;
8364
8917
  if (!ref)
8365
8918
  throw new Error("'ref' is required for this action");
8366
8919
  return ref;
@@ -8660,7 +9213,7 @@ Actions: routes, upsert_route, update_route, delete_route, config, get_certifica
8660
9213
  custom_hostname: optional(Type.String(), "[set_custom_hostname] 自定义域名")
8661
9214
  }, async (args) => {
8662
9215
  const resolveRef3 = (override) => {
8663
- const ref2 = projectRef || override;
9216
+ const ref2 = override || projectRef;
8664
9217
  if (!ref2)
8665
9218
  throw new Error("'ref' is required for this action");
8666
9219
  return ref2;
@@ -9602,6 +10155,416 @@ function registerAiTools(server) {
9602
10155
  });
9603
10156
  }
9604
10157
 
10158
+ // src/shared/tools/scheduled-function-tools.ts
10159
+ import { randomUUID } from "node:crypto";
10160
+ import { readFileSync as readFileSync6, statSync as statSync4 } from "node:fs";
10161
+ import { resolve as resolve5 } from "node:path";
10162
+ import { isDeepStrictEqual } from "node:util";
10163
+ var HEADER_NAME_PATTERN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]{1,128}$/;
10164
+ var ENVIRONMENT_NAME_PATTERN2 = /^[A-Za-z_][A-Za-z0-9_]{0,255}$/;
10165
+ var FORBIDDEN_HEADER_NAMES = new Set([
10166
+ "apikey",
10167
+ "authorization",
10168
+ "connection",
10169
+ "content-length",
10170
+ "forwarded",
10171
+ "host",
10172
+ "keep-alive",
10173
+ "proxy-authenticate",
10174
+ "proxy-authorization",
10175
+ "proxy-connection",
10176
+ "te",
10177
+ "trailer",
10178
+ "transfer-encoding",
10179
+ "upgrade",
10180
+ "via",
10181
+ "x-project-ref"
10182
+ ]);
10183
+ var PROJECT_REF_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
10184
+ var SCHEDULE_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
10185
+ var SAFE_SLUG_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
10186
+ var CRON_PART_PATTERN = /^(\*|([0-9]+)(?:-([0-9]+))?)(?:\/([0-9]+))?$/;
10187
+ var CRON_FIELD_BOUNDS = [[0, 59], [0, 23], [1, 31], [1, 12], [0, 7]];
10188
+ var MAX_CRON_EXPRESSION_LENGTH = 256;
10189
+ var MAX_BODY_FILE_BYTES = 1048576;
10190
+ var MAX_HEADER_COUNT = 64;
10191
+ var MAX_HEADER_VALUE_LENGTH = 8192;
10192
+ var MAX_SCHEDULE_NAME_LENGTH = 120;
10193
+ var headerEnvironmentRecord = Type.Record(Type.String(), Type.String());
10194
+ var ACTION_ARGUMENTS = {
10195
+ list: new Set(["action", "ref"]),
10196
+ create: new Set(["action", "ref", "name", "slug", "cron", "method", "body_file", "header_env"]),
10197
+ update: new Set(["action", "ref", "schedule_id", "name", "cron", "method", "enabled", "body_file", "header_env"]),
10198
+ delete: new Set(["action", "ref", "schedule_id"])
10199
+ };
10200
+ function parseHeaderEnvironment(input) {
10201
+ if (typeof input !== "string")
10202
+ return input;
10203
+ try {
10204
+ return JSON.parse(input);
10205
+ } catch (error) {
10206
+ if (!(error instanceof SyntaxError))
10207
+ throw error;
10208
+ throw new Error("Invalid header_env JSON object");
10209
+ }
10210
+ }
10211
+ var headerEnvironmentSchema = Type.Optional(decodedSchema(Type.Union([Type.String(), headerEnvironmentRecord]), headerEnvironmentRecord, parseHeaderEnvironment));
10212
+ function objectRecord2(candidate) {
10213
+ return candidate && typeof candidate === "object" && !Array.isArray(candidate) ? candidate : null;
10214
+ }
10215
+ function boundedCronInteger(input, minimum, maximum) {
10216
+ const parsed = Number(input);
10217
+ return Number.isSafeInteger(parsed) && parsed >= minimum && parsed <= maximum;
10218
+ }
10219
+ function validCronPart(part, minimum, maximum) {
10220
+ const match = CRON_PART_PATTERN.exec(part);
10221
+ if (!match)
10222
+ return false;
10223
+ const start = match[1] === "*" ? minimum : Number(match[2]);
10224
+ const end = match[1] === "*" ? maximum : Number(match[3] ?? match[2]);
10225
+ const step = Number(match[4] ?? "1");
10226
+ return boundedCronInteger(String(start), minimum, maximum) && boundedCronInteger(String(end), minimum, maximum) && start <= end && boundedCronInteger(String(step), 1, maximum - minimum + 1);
10227
+ }
10228
+ function validCronField(field, minimum, maximum) {
10229
+ const parts = field.split(",");
10230
+ return parts.length <= maximum - minimum + 1 && parts.every((part) => part.length > 0 && validCronPart(part, minimum, maximum));
10231
+ }
10232
+ function validScheduledFunctionCron(expression) {
10233
+ if (!expression || expression.length > MAX_CRON_EXPRESSION_LENGTH)
10234
+ return false;
10235
+ const fields = expression.trim().split(/\s+/);
10236
+ return fields.length === CRON_FIELD_BOUNDS.length && fields.every((field, index) => {
10237
+ const [minimum, maximum] = CRON_FIELD_BOUNDS[index];
10238
+ return validCronField(field, minimum, maximum);
10239
+ });
10240
+ }
10241
+ function readScheduleBodyFile(bodyPathInput) {
10242
+ if (!bodyPathInput.trim())
10243
+ throw new Error("'body_file' must be a path");
10244
+ const bodyPath = resolve5(bodyPathInput);
10245
+ const bodyStat = statSync4(bodyPath);
10246
+ if (!bodyStat.isFile() || bodyStat.size > MAX_BODY_FILE_BYTES) {
10247
+ throw new Error("Scheduled Function body file must be a regular file no larger than 1 MiB");
10248
+ }
10249
+ let payload;
10250
+ try {
10251
+ payload = JSON.parse(readFileSync6(bodyPath, "utf8"));
10252
+ } catch (error) {
10253
+ if (!(error instanceof SyntaxError))
10254
+ throw error;
10255
+ throw new Error("Scheduled Function body file must contain exact JSON");
10256
+ }
10257
+ const body = objectRecord2(payload);
10258
+ if (!body)
10259
+ throw new Error("Scheduled Function body file must contain a JSON object");
10260
+ return body;
10261
+ }
10262
+ function scheduleBody(bodyFile) {
10263
+ if (bodyFile === undefined)
10264
+ return;
10265
+ if (typeof bodyFile !== "string")
10266
+ throw new Error("'body_file' must be a path");
10267
+ return readScheduleBodyFile(bodyFile);
10268
+ }
10269
+ function resolvedHeaderEntry(headerName, environmentName, environment) {
10270
+ const normalizedName = headerName.toLowerCase();
10271
+ if (!HEADER_NAME_PATTERN.test(headerName) || forbiddenHeaderName(normalizedName) || typeof environmentName !== "string" || !ENVIRONMENT_NAME_PATTERN2.test(environmentName)) {
10272
+ throw new Error("SCHEDULE_HEADER_MAPPING_INVALID");
10273
+ }
10274
+ const headerValue = environment[environmentName];
10275
+ if (!headerValue)
10276
+ throw new Error("SCHEDULE_HEADER_ENV_MISSING");
10277
+ if (!headerValueIsStable(normalizedName, headerValue))
10278
+ throw new Error("SCHEDULE_HEADER_INVALID");
10279
+ return [normalizedName, headerValue];
10280
+ }
10281
+ function forbiddenHeaderName(name) {
10282
+ return FORBIDDEN_HEADER_NAMES.has(name) || name.startsWith("x-forwarded-");
10283
+ }
10284
+ function headerValueIsStable(name, value) {
10285
+ if (!value || value.length > MAX_HEADER_VALUE_LENGTH)
10286
+ return false;
10287
+ try {
10288
+ const headers = new Headers;
10289
+ headers.set(name, value);
10290
+ return headers.get(name) === value;
10291
+ } catch {
10292
+ return false;
10293
+ }
10294
+ }
10295
+ function scheduleHeaders(mapping, environment) {
10296
+ if (mapping === undefined)
10297
+ return;
10298
+ const headerEnvironment = objectRecord2(mapping);
10299
+ if (!headerEnvironment)
10300
+ throw new Error("'header_env' must be a JSON object");
10301
+ const entries = Object.entries(headerEnvironment).map(([headerName, environmentName]) => resolvedHeaderEntry(headerName, environmentName, environment));
10302
+ const names = entries.map(([name]) => name);
10303
+ if (entries.length > MAX_HEADER_COUNT || new Set(names).size !== names.length) {
10304
+ throw new Error("SCHEDULE_HEADER_INVALID");
10305
+ }
10306
+ return Object.fromEntries(entries);
10307
+ }
10308
+ function validSafeSchedule(schedule) {
10309
+ return validScheduleIdentity(schedule) && validScheduleDefinition(schedule) && validScheduleMetadata(schedule);
10310
+ }
10311
+ function validScheduleIdentity(schedule) {
10312
+ return typeof schedule.id === "string" && SCHEDULE_ID_PATTERN.test(schedule.id) && typeof schedule.name === "string" && schedule.name.trim().length > 0 && schedule.name.length <= MAX_SCHEDULE_NAME_LENGTH && typeof schedule.slug === "string" && SAFE_SLUG_PATTERN.test(schedule.slug);
10313
+ }
10314
+ function validScheduleDefinition(schedule) {
10315
+ return typeof schedule.cron === "string" && validScheduledFunctionCron(schedule.cron) && (schedule.method === "GET" || schedule.method === "POST") && typeof schedule.enabled === "boolean";
10316
+ }
10317
+ function validScheduleMetadata(schedule) {
10318
+ return typeof schedule.created_at === "string" && typeof schedule.updated_at === "string";
10319
+ }
10320
+ function safeSchedulePayload(schedule) {
10321
+ const headerNames = safeHeaderNames(schedule.header_names);
10322
+ if (typeof schedule.body_empty !== "boolean" || !headerNames)
10323
+ return null;
10324
+ return { body_empty: schedule.body_empty, header_names: headerNames };
10325
+ }
10326
+ function safeHeaderNames(candidate) {
10327
+ if (!Array.isArray(candidate) || candidate.length > MAX_HEADER_COUNT)
10328
+ return null;
10329
+ const names = candidate.map((name) => typeof name === "string" ? name.toLowerCase() : "");
10330
+ const valid = candidate.every((name, index) => typeof name === "string" && HEADER_NAME_PATTERN.test(name) && !forbiddenHeaderName(names[index]));
10331
+ return valid && new Set(names).size === names.length ? names.sort() : null;
10332
+ }
10333
+ function safeSchedule(candidate) {
10334
+ const schedule = objectRecord2(candidate);
10335
+ if (!schedule || !validSafeSchedule(schedule))
10336
+ return null;
10337
+ const safePayload = safeSchedulePayload(schedule);
10338
+ if (!safePayload)
10339
+ return null;
10340
+ return {
10341
+ id: schedule.id,
10342
+ name: schedule.name,
10343
+ slug: schedule.slug,
10344
+ cron: schedule.cron,
10345
+ method: schedule.method,
10346
+ enabled: schedule.enabled,
10347
+ ...safePayload,
10348
+ created_at: schedule.created_at,
10349
+ updated_at: schedule.updated_at
10350
+ };
10351
+ }
10352
+ function requiredText(args, name, action) {
10353
+ const candidate = args[name];
10354
+ if (typeof candidate !== "string" || !candidate.trim()) {
10355
+ throw new Error(`'${name}' is required for '${action}'`);
10356
+ }
10357
+ return candidate.trim();
10358
+ }
10359
+ function assertActionArguments(action, args) {
10360
+ const unsupported = Object.keys(args).filter((name) => !ACTION_ARGUMENTS[action].has(name));
10361
+ if (unsupported.length > 0) {
10362
+ throw new Error(`'${unsupported[0]}' is not supported for '${action}'`);
10363
+ }
10364
+ }
10365
+ function schedulePath(ref, scheduleId) {
10366
+ if (!PROJECT_REF_PATTERN.test(ref))
10367
+ throw new Error("'ref' is invalid for Scheduled Functions");
10368
+ if (scheduleId !== undefined && !SCHEDULE_ID_PATTERN.test(scheduleId)) {
10369
+ throw new Error("'schedule_id' is invalid");
10370
+ }
10371
+ const root = `/v1/projects/${encodeURIComponent(ref)}/scheduled-functions`;
10372
+ return scheduleId ? `${root}/${encodeURIComponent(scheduleId)}` : root;
10373
+ }
10374
+ function scheduleFailure(operation, response) {
10375
+ return releaseControlFailure(operation, "HTTP_ERROR", response.transportError ? null : response.status);
10376
+ }
10377
+ function listResponse(ref, response) {
10378
+ const operation = "scheduled_functions.list";
10379
+ if (!response.ok)
10380
+ return scheduleFailure(operation, response);
10381
+ const payload = objectRecord2(response.data);
10382
+ const rawSchedules = payload?.schedules;
10383
+ const schedules = Array.isArray(rawSchedules) ? rawSchedules.map(safeSchedule) : null;
10384
+ if (payload?.project_ref !== ref || !schedules || schedules.some((schedule) => !schedule)) {
10385
+ return releaseControlFailure(operation, "INVALID_RESPONSE", null);
10386
+ }
10387
+ const ids = schedules.map((schedule) => schedule.id);
10388
+ if (new Set(ids).size !== ids.length)
10389
+ return releaseControlFailure(operation, "INVALID_RESPONSE", null);
10390
+ return releaseControlSuccess(operation, { project_ref: ref, schedules });
10391
+ }
10392
+ function mutationResponse(expectation, response) {
10393
+ const { action, ref, requestId, expectedFields } = expectation;
10394
+ const scheduleId = action === "update" ? expectation.scheduleId : undefined;
10395
+ const operation = `scheduled_functions.${action}`;
10396
+ if (!response.ok)
10397
+ return releaseControlMutationFailure(operation, response);
10398
+ const payload = objectRecord2(response.data);
10399
+ const schedule = safeSchedule(payload?.schedule);
10400
+ const confirmsRequest = schedule && Object.entries(expectedFields).every(([field, expected]) => isDeepStrictEqual(schedule[field], expected));
10401
+ if (payload?.project_ref !== ref || payload.request_id !== requestId || payload?.[action === "create" ? "created" : "updated"] !== true || !schedule || !confirmsRequest || scheduleId !== undefined && schedule.id !== scheduleId) {
10402
+ return releaseControlFailure(operation, "OUTCOME_UNKNOWN", response.status);
10403
+ }
10404
+ return releaseControlSuccess(operation, { project_ref: ref, request_id: requestId, schedule });
10405
+ }
10406
+ function deleteResponse(ref, scheduleId, response) {
10407
+ const operation = "scheduled_functions.delete";
10408
+ if (!response.ok)
10409
+ return releaseControlMutationFailure(operation, response);
10410
+ const payload = objectRecord2(response.data);
10411
+ if (payload?.deleted !== true || payload.project_ref !== ref || payload.schedule_id !== scheduleId) {
10412
+ return releaseControlFailure(operation, "OUTCOME_UNKNOWN", response.status);
10413
+ }
10414
+ return releaseControlSuccess(operation, {
10415
+ project_ref: ref,
10416
+ schedule_id: scheduleId,
10417
+ deleted: true
10418
+ });
10419
+ }
10420
+ function readOnlyResult2() {
10421
+ return {
10422
+ isError: true,
10423
+ content: [{ type: "text", text: "⚠️ Scheduled Function write blocked in read-only mode." }]
10424
+ };
10425
+ }
10426
+ function createRequest(args, environment) {
10427
+ return {
10428
+ request_id: randomUUID(),
10429
+ name: requiredName(args, "create"),
10430
+ slug: requiredSlug(args, "create"),
10431
+ cron: requiredCron(args, "create"),
10432
+ method: requiredText(args, "method", "create"),
10433
+ body: scheduleBody(args.body_file) ?? {},
10434
+ headers: scheduleHeaders(args.header_env, environment) ?? {}
10435
+ };
10436
+ }
10437
+ function safeMutationFields(request) {
10438
+ const safeFields = Object.fromEntries(["name", "slug", "cron", "method", "enabled"].filter((field) => request[field] !== undefined).map((field) => [field, request[field]]));
10439
+ if (request.body !== undefined) {
10440
+ safeFields.body_empty = Object.keys(request.body).length === 0;
10441
+ }
10442
+ if (request.headers !== undefined) {
10443
+ safeFields.header_names = Object.keys(request.headers).sort();
10444
+ }
10445
+ return safeFields;
10446
+ }
10447
+ function requiredName(args, action) {
10448
+ const name = requiredText(args, "name", action);
10449
+ if (name.length > MAX_SCHEDULE_NAME_LENGTH)
10450
+ throw new Error(`'name' is too long for '${action}'`);
10451
+ return name;
10452
+ }
10453
+ function requiredSlug(args, action) {
10454
+ const slug = requiredText(args, "slug", action);
10455
+ if (!SAFE_SLUG_PATTERN.test(slug))
10456
+ throw new Error(`'slug' is invalid for '${action}'`);
10457
+ return slug;
10458
+ }
10459
+ function requiredCron(args, action) {
10460
+ const cron = requiredText(args, "cron", action);
10461
+ if (!validScheduledFunctionCron(cron))
10462
+ throw new Error(`'cron' is invalid for '${action}'`);
10463
+ return cron;
10464
+ }
10465
+ function updateRequest(args, environment) {
10466
+ const body = scheduleBody(args.body_file);
10467
+ const headers = scheduleHeaders(args.header_env, environment);
10468
+ const cron = args.cron === undefined ? undefined : requiredCron(args, "update");
10469
+ const name = args.name === undefined ? undefined : requiredName(args, "update");
10470
+ const mutationFields = Object.fromEntries([
10471
+ ["name", name],
10472
+ ["cron", cron],
10473
+ ["method", args.method],
10474
+ ["enabled", args.enabled],
10475
+ ["body", body],
10476
+ ["headers", headers]
10477
+ ].filter((entry) => entry[1] !== undefined));
10478
+ if (Object.keys(mutationFields).length === 0) {
10479
+ throw new Error("Scheduled Function update requires at least one field");
10480
+ }
10481
+ return { request_id: randomUUID(), ...mutationFields };
10482
+ }
10483
+ async function executeScheduleAction(http, environment, args, readOnly = false) {
10484
+ const action = args.action;
10485
+ if (readOnly && action !== "list")
10486
+ return readOnlyResult2();
10487
+ assertActionArguments(action, args);
10488
+ const ref = requiredText(args, "ref", action);
10489
+ if (action === "list")
10490
+ return listResponse(ref, await http.get(schedulePath(ref)));
10491
+ if (action === "create") {
10492
+ const request = createRequest(args, environment);
10493
+ const requestId = request.request_id;
10494
+ return mutationResponse({ action, ref, requestId, expectedFields: safeMutationFields(request) }, await http.post(schedulePath(ref), request));
10495
+ }
10496
+ const scheduleId = requiredText(args, "schedule_id", action);
10497
+ if (action === "update") {
10498
+ const request = updateRequest(args, environment);
10499
+ const requestId = request.request_id;
10500
+ return mutationResponse({
10501
+ action,
10502
+ ref,
10503
+ scheduleId,
10504
+ requestId,
10505
+ expectedFields: safeMutationFields(request)
10506
+ }, await http.patch(schedulePath(ref, scheduleId), request));
10507
+ }
10508
+ return deleteResponse(ref, scheduleId, await http.delete(schedulePath(ref, scheduleId)));
10509
+ }
10510
+ function registerScheduledFunctionTools(server, http, environment = process.env, options = {}) {
10511
+ server.tool("scheduled_functions", SCHEDULE_TOOL_DESCRIPTION, SCHEDULE_TOOL_SCHEMA, (args) => executeScheduleAction(http, environment, args, options.readOnly));
10512
+ }
10513
+ var SCHEDULE_TOOL_DESCRIPTION = "Scheduled Edge Function lifecycle. Actions: list, create, update, delete";
10514
+ var SCHEDULE_TOOL_SCHEMA = {
10515
+ action: withDescription(stringEnum(["list", "create", "update", "delete"]), "Action"),
10516
+ ref: withDescription(Type.String(), "Project ref"),
10517
+ schedule_id: optional(Type.String(), "[update/delete] Schedule ID"),
10518
+ name: optional(Type.String(), "[create/update] Display name"),
10519
+ slug: optional(Type.String(), "[create] Edge Function slug"),
10520
+ cron: optional(Type.String(), "[create/update] Five-field cron expression"),
10521
+ method: optional(stringEnum(["GET", "POST"]), "[create/update] HTTP method"),
10522
+ enabled: optional(Type.Boolean(), "[update] Enabled state"),
10523
+ body_file: optional(Type.String(), "[create/update] Local JSON object file; content is never printed"),
10524
+ header_env: withDescription(headerEnvironmentSchema, "[create/update] JSON map of HTTP header names to environment variable names")
10525
+ };
10526
+ // package.json
10527
+ var package_default = {
10528
+ name: "@supacloud/cli",
10529
+ version: "0.15.0",
10530
+ description: "Project-scoped CLI for SupaCloud users",
10531
+ type: "module",
10532
+ main: "./dist/index.js",
10533
+ bin: {
10534
+ "supacloud-cli": "dist/index.js"
10535
+ },
10536
+ files: [
10537
+ "dist",
10538
+ "README.md",
10539
+ "skills"
10540
+ ],
10541
+ scripts: {
10542
+ dev: "bun run --watch src/index.ts",
10543
+ build: "bun build src/index.ts --outdir dist --target node",
10544
+ prepublishOnly: "bun run build",
10545
+ typecheck: "tsc --noEmit"
10546
+ },
10547
+ keywords: [
10548
+ "supacloud",
10549
+ "cli",
10550
+ "deploy",
10551
+ "supabase"
10552
+ ],
10553
+ license: "MIT",
10554
+ repository: {
10555
+ type: "git",
10556
+ url: "https://github.com/zuohuadong/supacloud.git",
10557
+ directory: "packages/cli"
10558
+ },
10559
+ dependencies: {
10560
+ "@sinclair/typebox": "^0.34.52"
10561
+ },
10562
+ devDependencies: {
10563
+ "@types/bun": "^1.3.14",
10564
+ typescript: "^7.0.2"
10565
+ }
10566
+ };
10567
+
9605
10568
  // src/index.ts
9606
10569
  var commandName = "supacloud-cli";
9607
10570
  var preferredCommand = commandName;
@@ -9679,9 +10642,12 @@ async function createProjectStatusResult(context) {
9679
10642
  const checks = await collectProjectStatusChecks(context);
9680
10643
  const statusPayload = {
9681
10644
  mode: "project",
9682
- source: context.source,
10645
+ environment: context.environment || null,
10646
+ source: { kind: context.source, path: context.sourcePath },
9683
10647
  projectRef: context.projectRef || null,
9684
10648
  apiUrl: context.apiUrl || null,
10649
+ readOnly: context.readOnly,
10650
+ production: context.production,
9685
10651
  autoLinked: Boolean(context.inferredSupabaseUrl && context.inferredServiceRoleKey),
9686
10652
  hasApiToken: Boolean(context.apiToken),
9687
10653
  checks
@@ -9714,7 +10680,7 @@ function captureTools(register) {
9714
10680
  register(server);
9715
10681
  return tools;
9716
10682
  }
9717
- function printHelp(context = resolveSupaCloudContext()) {
10683
+ function printHelp(context) {
9718
10684
  const autoLink = context.inferredSupabaseUrl ? `Project context: ${context.inferredSupabaseUrl} (${context.source})` : "Project context: not detected";
9719
10685
  console.error(`
9720
10686
  ╔═══════════════════════════════════════════════════════════╗
@@ -9724,18 +10690,31 @@ function printHelp(context = resolveSupaCloudContext()) {
9724
10690
 
9725
10691
  USAGE
9726
10692
 
9727
- ${preferredCommand} <module> <action> [--flags]
9728
- ${preferredCommand} status
10693
+ ${preferredCommand} [global flags] <module> <action> [--flags]
10694
+ ${preferredCommand} [global flags] status
9729
10695
  ${preferredCommand} --help
10696
+ ${preferredCommand} --version
10697
+
10698
+ GLOBAL FLAGS
10699
+
10700
+ --env <name> Load .env.supacloud.<name> from the current directory.
10701
+ --env-file <path> Load an exact file that declares SUPACLOUD_ENV.
10702
+ --confirm-production <ref> Confirm a write to the selected production project.
10703
+
10704
+ Global flags may appear before or after the command. --env and --env-file are
10705
+ mutually exclusive, and a selected source is never mixed with another source.
9730
10706
 
9731
10707
  DEFAULT CONTEXT
9732
10708
 
9733
- Unauthenticated runs default to the current project's .env.
10709
+ Without a selector or project variables, runs use the current project's legacy .env.
9734
10710
  Supported auto-link variables:
9735
10711
  SUPABASE_URL / SUPACLOUD_API_URL
9736
10712
  SUPABASE_SERVICE_ROLE_KEY / SUPACLOUD_API_TOKEN
9737
10713
  SUPACLOUD_PROJECT_REF (when it cannot be inferred from <ref>.api.*)
9738
10714
 
10715
+ SUPACLOUD_READ_ONLY=true blocks remote writes. Production writes require an
10716
+ exact --confirm-production value, and cannot override the selected project ref.
10717
+
9739
10718
  status checks configuration, Management API connectivity, and authentication.
9740
10719
  It exits non-zero when a required check fails.
9741
10720
 
@@ -9763,7 +10742,10 @@ EXAMPLES
9763
10742
  ${preferredCommand} ai show_skill
9764
10743
  ${preferredCommand} ai install_skill --dry_run
9765
10744
  ${preferredCommand} edge_functions deploy --ref abc123 --slug hello --path ./supabase/functions/hello
10745
+ ${preferredCommand} edge_functions activate --ref abc123 --slug hello --version 3
10746
+ ${preferredCommand} scheduled_functions list --ref abc123
9766
10747
  ${preferredCommand} edge_functions config --ref abc123 --slug hello --verify_jwt false --background_routes "/queue/*,/render/*"
10748
+ ${preferredCommand} secrets upsert --ref abc123 --from-env API_KEY,WEBHOOK_SECRET
9767
10749
  ${preferredCommand} gateway routes --ref abc123
9768
10750
  ${preferredCommand} gateway upsert_route --ref abc123 --route_id webhook --hosts "api.example.com" --paths "/webhook/*" --upstream 10.0.0.5:8080
9769
10751
  ${preferredCommand} gateway config --ref abc123 --rate_limit_tier pro
@@ -9777,8 +10759,18 @@ SEPARATE ADMIN CLI
9777
10759
  npx @supacloud/admin --help
9778
10760
  `);
9779
10761
  }
9780
- function createCliTools() {
9781
- const context = resolveSupaCloudContext();
10762
+ function authorizedToolMap(tools, context, confirmProduction) {
10763
+ validateExecutionPolicyCoverage(tools);
10764
+ for (const [moduleName, tool] of Object.entries(tools)) {
10765
+ const callback = tool.callback;
10766
+ tool.callback = async (args) => {
10767
+ authorizeExecution(moduleName, args, { context, confirmProduction });
10768
+ return callback(args);
10769
+ };
10770
+ }
10771
+ return tools;
10772
+ }
10773
+ function createCliTools(context, confirmProduction) {
9782
10774
  let pushMigrations;
9783
10775
  const tools = {
9784
10776
  status: {
@@ -9804,6 +10796,8 @@ function createCliTools() {
9804
10796
  "⚠️ Project commands need a project-scoped API context.",
9805
10797
  "",
9806
10798
  "Provide one of these sources:",
10799
+ " - --env <name> for .env.supacloud.<name>",
10800
+ " - --env-file <path> for a file declaring SUPACLOUD_ENV",
9807
10801
  " - .env with SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY",
9808
10802
  " - SUPACLOUD_API_URL + SUPACLOUD_API_TOKEN",
9809
10803
  "",
@@ -9816,7 +10810,7 @@ function createCliTools() {
9816
10810
  ]
9817
10811
  })
9818
10812
  };
9819
- for (const name of ["database", "auth", "storage", "edge_functions", "secrets", "frontend", "queue", "task_events", "diagnostics", "gateway", "branch"]) {
10813
+ for (const name of ["database", "auth", "storage", "edge_functions", "secrets", "frontend", "queue", "task_events", "scheduled_functions", "diagnostics", "gateway", "branch"]) {
9820
10814
  tools[name] = {
9821
10815
  schema: { action: genericActionSchema },
9822
10816
  callback: async () => ({
@@ -9853,11 +10847,14 @@ function createCliTools() {
9853
10847
  `${preferredCommand} expects project-scoped credentials by default.`,
9854
10848
  "Provide one of these sources:",
9855
10849
  "",
9856
- " 1. Current workspace .env",
10850
+ " 1. Named environment file",
10851
+ " supacloud-cli --env test status",
10852
+ "",
10853
+ " 2. Current workspace .env",
9857
10854
  " SUPABASE_URL=https://your-project.example.com",
9858
10855
  " SUPABASE_SERVICE_ROLE_KEY=...",
9859
10856
  "",
9860
- " 2. Explicit environment variables",
10857
+ " 3. Explicit environment variables",
9861
10858
  " SUPACLOUD_API_URL=https://your-project.example.com",
9862
10859
  " SUPACLOUD_API_TOKEN=...",
9863
10860
  "",
@@ -9869,7 +10866,7 @@ function createCliTools() {
9869
10866
  ]
9870
10867
  })
9871
10868
  };
9872
- return tools;
10869
+ return authorizedToolMap(tools, context, confirmProduction);
9873
10870
  }
9874
10871
  const http = new HttpTransport({
9875
10872
  baseUrl: context.apiUrl,
@@ -9887,7 +10884,12 @@ function createCliTools() {
9887
10884
  assign(databaseTools);
9888
10885
  assign(captureTools((server) => registerAuthTools(server, http)));
9889
10886
  assign(captureTools((server) => registerStorageTools(server, http)));
9890
- assign(captureTools((server) => registerAdvancedTools(server, http)));
10887
+ assign(captureTools((server) => registerAdvancedTools(server, http, process.env, {
10888
+ readOnly: context.readOnly
10889
+ })));
10890
+ assign(captureTools((server) => registerScheduledFunctionTools(server, http, process.env, {
10891
+ readOnly: context.readOnly
10892
+ })));
9891
10893
  assign(captureTools((server) => registerFrontendTools(server, http)));
9892
10894
  assign(captureTools((server) => registerGatewayTools(server, http, {
9893
10895
  projectRef: context.projectRef || undefined
@@ -9900,16 +10902,26 @@ function createCliTools() {
9900
10902
  projectRef: context.projectRef || undefined
9901
10903
  })));
9902
10904
  delete tools.platform;
9903
- return tools;
10905
+ return authorizedToolMap(tools, context, confirmProduction);
9904
10906
  }
9905
10907
  async function main() {
9906
- const args = process.argv.slice(2);
10908
+ const rawArgs = process.argv.slice(2);
10909
+ if (rawArgs.length === 1 && rawArgs[0] === "--version") {
10910
+ console.log(package_default.version);
10911
+ return;
10912
+ }
10913
+ const globalOptions = parseGlobalOptions(rawArgs);
10914
+ const args = globalOptions.args;
10915
+ const context = resolveSupaCloudContext(process.env, process.cwd(), {
10916
+ environmentName: globalOptions.environmentName,
10917
+ envFile: globalOptions.envFile
10918
+ });
9907
10919
  if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
9908
- printHelp();
10920
+ printHelp(context);
9909
10921
  process.exitCode = 0;
9910
10922
  return;
9911
10923
  }
9912
- const cliTools = createCliTools();
10924
+ const cliTools = createCliTools(context, globalOptions.confirmProduction);
9913
10925
  if (args.length === 1 && !["ai", "supabase"].includes(args[0]) && cliTools[args[0]]) {
9914
10926
  const result = await cliTools[args[0]].callback({});
9915
10927
  if (result?.content && Array.isArray(result.content)) {