@agentlink.sh/cli 0.11.0 → 0.12.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.
@@ -505,6 +505,42 @@ async function waitForProjectReady(ref, spinner, timeoutMs = 3e5) {
505
505
  Check status at: https://supabase.com/dashboard/project/${ref}`
506
506
  );
507
507
  }
508
+ async function fetchPoolerUrl(projectRef) {
509
+ const token = process.env.SUPABASE_ACCESS_TOKEN;
510
+ if (!token) {
511
+ throw new Error("SUPABASE_ACCESS_TOKEN is required to fetch pooler config");
512
+ }
513
+ const res = await fetch(
514
+ `https://api.supabase.com/v1/projects/${projectRef}/config/database/pooler`,
515
+ {
516
+ headers: { Authorization: `Bearer ${token}` }
517
+ }
518
+ );
519
+ if (!res.ok) {
520
+ const body = await res.text();
521
+ throw new Error(`Failed to fetch pooler config (${res.status}): ${body}`);
522
+ }
523
+ const entries = await res.json();
524
+ const pooler = entries.find(
525
+ (e) => e.database_type === "PRIMARY" && e.pool_mode === "transaction"
526
+ ) ?? entries.find(
527
+ (e) => e.database_type === "PRIMARY"
528
+ ) ?? entries[0];
529
+ if (!pooler) {
530
+ throw new Error("No pooler configuration found for project");
531
+ }
532
+ return {
533
+ host: pooler.db_host,
534
+ port: pooler.db_port,
535
+ user: pooler.db_user,
536
+ dbName: pooler.db_name,
537
+ connectionString: pooler.connection_string
538
+ };
539
+ }
540
+ async function resolvePoolerDbUrl(projectRef, password) {
541
+ const pooler = await fetchPoolerUrl(projectRef);
542
+ return `postgresql://${pooler.user}:${encodeURIComponent(password)}@${pooler.host}:${pooler.port}/${pooler.dbName}`;
543
+ }
508
544
  function saveProjectCredential(projectRef, dbPassword) {
509
545
  const creds = loadCredentials();
510
546
  if (!creds.project_credentials) {
@@ -584,6 +620,8 @@ export {
584
620
  updatePostgrestConfig,
585
621
  updateAuthConfig,
586
622
  waitForProjectReady,
623
+ fetchPoolerUrl,
624
+ resolvePoolerDbUrl,
587
625
  saveProjectCredential,
588
626
  loadProjectCredential,
589
627
  resolveDbUrlForEnv,
@@ -4,6 +4,7 @@ import {
4
4
  credentialsPath,
5
5
  detectClosestRegion,
6
6
  ensureAccessToken,
7
+ fetchPoolerUrl,
7
8
  getApiKeys,
8
9
  getProject,
9
10
  listOrganizations,
@@ -13,6 +14,7 @@ import {
13
14
  loadCredentials,
14
15
  loadProjectCredential,
15
16
  resolveDbUrlForEnv,
17
+ resolvePoolerDbUrl,
16
18
  runCloudSQL,
17
19
  saveCredentials,
18
20
  saveProjectCredential,
@@ -20,13 +22,14 @@ import {
20
22
  updateAuthConfig,
21
23
  updatePostgrestConfig,
22
24
  waitForProjectReady
23
- } from "./chunk-2CNK2BQY.js";
25
+ } from "./chunk-4EUOAXWZ.js";
24
26
  import "./chunk-G3HVUCWY.js";
25
27
  export {
26
28
  createProject,
27
29
  credentialsPath,
28
30
  detectClosestRegion,
29
31
  ensureAccessToken,
32
+ fetchPoolerUrl,
30
33
  getApiKeys,
31
34
  getProject,
32
35
  listOrganizations,
@@ -36,6 +39,7 @@ export {
36
39
  loadCredentials,
37
40
  loadProjectCredential,
38
41
  resolveDbUrlForEnv,
42
+ resolvePoolerDbUrl,
39
43
  runCloudSQL,
40
44
  saveCredentials,
41
45
  saveProjectCredential,
package/dist/index.js CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  loadCredentials,
17
17
  loadProjectCredential,
18
18
  resolveDbUrlForEnv,
19
+ resolvePoolerDbUrl,
19
20
  runCloudSQL,
20
21
  runCommand,
21
22
  saveCredentials,
@@ -26,7 +27,7 @@ import {
26
27
  updatePostgrestConfig,
27
28
  validateProjectName,
28
29
  waitForProjectReady
29
- } from "./chunk-2CNK2BQY.js";
30
+ } from "./chunk-4EUOAXWZ.js";
30
31
  import {
31
32
  SKILLS_VERSION,
32
33
  SUPPORTED_SUPABASE_CLI,
@@ -186,20 +187,33 @@ function listEnvironments(cwd) {
186
187
  var check_setup_default = "SELECT jsonb_build_object(\n 'extensions', jsonb_build_object(\n 'pg_net', EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pg_net'),\n 'pg_cron', EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pg_cron'),\n 'vault', EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'supabase_vault'),\n 'pgmq', EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pgmq')\n ),\n 'queues', jsonb_build_object(\n 'agentlink_tasks', EXISTS (\n SELECT 1 FROM information_schema.tables\n WHERE table_schema = 'pgmq' AND table_name = 'q_agentlink_tasks'\n )\n ),\n 'tables', jsonb_build_object(\n 'profiles', EXISTS (\n SELECT 1 FROM information_schema.tables\n WHERE table_schema = 'public' AND table_name = 'profiles'\n ),\n 'tenants', EXISTS (\n SELECT 1 FROM information_schema.tables\n WHERE table_schema = 'public' AND table_name = 'tenants'\n ),\n 'memberships', EXISTS (\n SELECT 1 FROM information_schema.tables\n WHERE table_schema = 'public' AND table_name = 'memberships'\n ),\n 'invitations', EXISTS (\n SELECT 1 FROM information_schema.tables\n WHERE table_schema = 'public' AND table_name = 'invitations'\n )\n ),\n 'functions', jsonb_build_object(\n '_internal_admin_get_secret',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public'\n AND routine_name = '_internal_admin_get_secret'\n ),\n '_internal_admin_call_edge_function',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public'\n AND routine_name = '_internal_admin_call_edge_function'\n ),\n 'api._admin_enqueue_task',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api'\n AND routine_name = '_admin_enqueue_task'\n ),\n 'api._admin_queue_read',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api'\n AND routine_name = '_admin_queue_read'\n ),\n 'api._admin_queue_delete',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api'\n AND routine_name = '_admin_queue_delete'\n ),\n 'api._admin_queue_archive',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api'\n AND routine_name = '_admin_queue_archive'\n ),\n 'set_updated_at',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public'\n AND routine_name = 'set_updated_at'\n ),\n '_internal_admin_handle_new_user',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public'\n AND routine_name = '_internal_admin_handle_new_user'\n ),\n 'api.profile_get',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api'\n AND routine_name = 'profile_get'\n ),\n 'api.profile_update',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api'\n AND routine_name = 'profile_update'\n ),\n '_hook_before_user_created',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public'\n AND routine_name = '_hook_before_user_created'\n ),\n '_hook_send_email',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public'\n AND routine_name = '_hook_send_email'\n ),\n '_auth_tenant_id',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public'\n AND routine_name = '_auth_tenant_id'\n ),\n '_auth_tenant_role',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public'\n AND routine_name = '_auth_tenant_role'\n ),\n '_auth_has_role',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public'\n AND routine_name = '_auth_has_role'\n ),\n '_auth_is_tenant_member',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public'\n AND routine_name = '_auth_is_tenant_member'\n ),\n 'api.tenant_select',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api'\n AND routine_name = 'tenant_select'\n ),\n 'api.tenant_list',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api'\n AND routine_name = 'tenant_list'\n ),\n 'api.tenant_create',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api'\n AND routine_name = 'tenant_create'\n ),\n 'api.invitation_create',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api'\n AND routine_name = 'invitation_create'\n ),\n 'api.invitation_accept',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api'\n AND routine_name = 'invitation_accept'\n ),\n 'api.membership_list',\n EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api'\n AND routine_name = 'membership_list'\n )\n ),\n 'triggers', jsonb_build_object(\n 'trg_profiles_updated_at',\n EXISTS (\n SELECT 1 FROM pg_trigger WHERE tgname = 'trg_profiles_updated_at'\n ),\n 'trg_auth_users_new_user',\n EXISTS (\n SELECT 1 FROM pg_trigger WHERE tgname = 'trg_auth_users_new_user'\n ),\n 'trg_tenants_updated_at',\n EXISTS (\n SELECT 1 FROM pg_trigger WHERE tgname = 'trg_tenants_updated_at'\n )\n ),\n 'secrets', jsonb_build_object(\n 'SUPABASE_URL',\n EXISTS (SELECT 1 FROM vault.secrets WHERE name = 'SUPABASE_URL'),\n 'SB_PUBLISHABLE_KEY',\n EXISTS (SELECT 1 FROM vault.secrets WHERE name = 'SB_PUBLISHABLE_KEY'),\n 'SB_SECRET_KEY',\n EXISTS (SELECT 1 FROM vault.secrets WHERE name = 'SB_SECRET_KEY')\n ),\n 'api_schema', EXISTS (\n SELECT 1 FROM information_schema.schemata WHERE schema_name = 'api'\n ),\n 'ready', (\n EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pg_net')\n AND EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pg_cron')\n AND EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'supabase_vault')\n AND EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pgmq')\n AND EXISTS (SELECT 1 FROM information_schema.schemata WHERE schema_name = 'api')\n AND EXISTS (\n SELECT 1 FROM information_schema.tables\n WHERE table_schema = 'pgmq' AND table_name = 'q_agentlink_tasks'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public' AND routine_name = '_internal_admin_get_secret'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public' AND routine_name = '_internal_admin_call_edge_function'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api' AND routine_name = '_admin_enqueue_task'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api' AND routine_name = '_admin_queue_read'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api' AND routine_name = '_admin_queue_delete'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api' AND routine_name = '_admin_queue_archive'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.tables\n WHERE table_schema = 'public' AND table_name = 'profiles'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.tables\n WHERE table_schema = 'public' AND table_name = 'tenants'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.tables\n WHERE table_schema = 'public' AND table_name = 'memberships'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.tables\n WHERE table_schema = 'public' AND table_name = 'invitations'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public' AND routine_name = 'set_updated_at'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public' AND routine_name = '_internal_admin_handle_new_user'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api' AND routine_name = 'profile_get'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api' AND routine_name = 'profile_update'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public' AND routine_name = '_hook_before_user_created'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public' AND routine_name = '_hook_send_email'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public' AND routine_name = '_auth_tenant_id'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public' AND routine_name = '_auth_tenant_role'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public' AND routine_name = '_auth_has_role'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'public' AND routine_name = '_auth_is_tenant_member'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api' AND routine_name = 'tenant_select'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api' AND routine_name = 'tenant_list'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api' AND routine_name = 'tenant_create'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api' AND routine_name = 'invitation_create'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api' AND routine_name = 'invitation_accept'\n )\n AND EXISTS (\n SELECT 1 FROM information_schema.routines\n WHERE routine_schema = 'api' AND routine_name = 'membership_list'\n )\n AND EXISTS (\n SELECT 1 FROM pg_trigger WHERE tgname = 'trg_profiles_updated_at'\n )\n AND EXISTS (\n SELECT 1 FROM pg_trigger WHERE tgname = 'trg_auth_users_new_user'\n )\n AND EXISTS (\n SELECT 1 FROM pg_trigger WHERE tgname = 'trg_tenants_updated_at'\n )\n AND EXISTS (SELECT 1 FROM vault.secrets WHERE name = 'SUPABASE_URL')\n AND EXISTS (SELECT 1 FROM vault.secrets WHERE name = 'SB_PUBLISHABLE_KEY')\n AND EXISTS (SELECT 1 FROM vault.secrets WHERE name = 'SB_SECRET_KEY')\n )\n) AS setup_status;\n";
187
188
 
188
189
  // src/sql.ts
190
+ function upsertSecret(value, name) {
191
+ const safeValue = value.replace(/'/g, "''");
192
+ return `DO $$ DECLARE
193
+ _id uuid;
194
+ BEGIN
195
+ SELECT id INTO _id FROM vault.secrets WHERE name = '${name}';
196
+ IF _id IS NOT NULL THEN
197
+ PERFORM vault.update_secret(_id, '${safeValue}', '${name}');
198
+ ELSE
199
+ PERFORM vault.create_secret('${safeValue}', '${name}');
200
+ END IF;
201
+ END $$;`;
202
+ }
189
203
  function seedSQL(publishableKey, secretKey) {
190
204
  return [
191
- "select vault.create_secret('http://host.docker.internal:54321', 'SUPABASE_URL');",
192
- `select vault.create_secret('${publishableKey}', 'SB_PUBLISHABLE_KEY');`,
193
- `select vault.create_secret('${secretKey}', 'SB_SECRET_KEY');`,
194
- "select vault.create_secret('', 'ALLOWED_SIGNUP_DOMAINS');"
205
+ upsertSecret("http://host.docker.internal:54321", "SUPABASE_URL"),
206
+ upsertSecret(publishableKey, "SB_PUBLISHABLE_KEY"),
207
+ upsertSecret(secretKey, "SB_SECRET_KEY"),
208
+ upsertSecret("", "ALLOWED_SIGNUP_DOMAINS")
195
209
  ].join("\n");
196
210
  }
197
211
  function cloudSeedSQL(apiUrl, publishableKey, secretKey) {
198
212
  return [
199
- `select vault.create_secret('${apiUrl}', 'SUPABASE_URL');`,
200
- `select vault.create_secret('${publishableKey}', 'SB_PUBLISHABLE_KEY');`,
201
- `select vault.create_secret('${secretKey}', 'SB_SECRET_KEY');`,
202
- "select vault.create_secret('', 'ALLOWED_SIGNUP_DOMAINS');"
213
+ upsertSecret(apiUrl, "SUPABASE_URL"),
214
+ upsertSecret(publishableKey, "SB_PUBLISHABLE_KEY"),
215
+ upsertSecret(secretKey, "SB_SECRET_KEY"),
216
+ upsertSecret("", "ALLOWED_SIGNUP_DOMAINS")
203
217
  ].join("\n");
204
218
  }
205
219
 
@@ -1517,6 +1531,7 @@ async function check(envFlag) {
1517
1531
  import fs5 from "fs";
1518
1532
  import os from "os";
1519
1533
  import path5 from "path";
1534
+ import { input } from "@inquirer/prompts";
1520
1535
  function stripQuotes(val) {
1521
1536
  if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
1522
1537
  return val.slice(1, -1);
@@ -1721,12 +1736,130 @@ Migration created: supabase/migrations/${filename}`);
1721
1736
  fs5.rmSync(tmpDir, { recursive: true, force: true });
1722
1737
  }
1723
1738
  }
1739
+ async function dbRebuild(options) {
1740
+ const cwd = options.cwd;
1741
+ const migrationsDir = path5.join(cwd, "supabase", "migrations");
1742
+ if (fs5.existsSync(migrationsDir)) {
1743
+ const files = fs5.readdirSync(migrationsDir).filter((f) => f.endsWith(".sql"));
1744
+ for (const file of files) {
1745
+ fs5.unlinkSync(path5.join(migrationsDir, file));
1746
+ }
1747
+ console.log(`Deleted ${files.length} migration file(s)`);
1748
+ }
1749
+ const progressPath2 = path5.join(cwd, ".agentlink-progress.json");
1750
+ if (fs5.existsSync(progressPath2)) {
1751
+ fs5.unlinkSync(progressPath2);
1752
+ console.log("Cleared scaffold progress");
1753
+ }
1754
+ console.log("\nRe-applying schemas...");
1755
+ await dbApply({ cwd, dbUrl: options.dbUrl, env: options.env, skipTypes: true });
1756
+ console.log("\nRegenerating migration...");
1757
+ await dbMigrate("agentlink_setup", { cwd, dbUrl: options.dbUrl, env: options.env });
1758
+ console.log("\nGenerating types...");
1759
+ try {
1760
+ await dbTypes({ cwd, dbUrl: options.dbUrl, env: options.env });
1761
+ } catch (err) {
1762
+ console.warn(`Warning: type generation failed \u2014 ${err.message}`);
1763
+ }
1764
+ console.log("\nDone. Database rebuilt from schema files.");
1765
+ }
1766
+ async function dbUrlCheck(options) {
1767
+ const mode = await resolveDbMode(options.cwd, options.dbUrl, options.env);
1768
+ if (mode.kind !== "cloud" || !mode.projectRef) {
1769
+ const dbUrl = await resolveDbUrl(options.cwd, options.dbUrl);
1770
+ console.log(dbUrl);
1771
+ return;
1772
+ }
1773
+ await ensureAccessToken(true, options.cwd);
1774
+ const password = loadProjectCredential(mode.projectRef);
1775
+ if (!password) {
1776
+ throw new Error(
1777
+ `No stored password for project ${mode.projectRef}.
1778
+ Run: agentlink env relink dev`
1779
+ );
1780
+ }
1781
+ const poolerUrl = await resolvePoolerDbUrl(mode.projectRef, password);
1782
+ console.log(`Pooler URL: ${poolerUrl}`);
1783
+ const currentUrl = readEnvValue(options.cwd, "SUPABASE_DB_URL");
1784
+ if (currentUrl === poolerUrl) {
1785
+ console.log("\u2714 .env.local is up to date");
1786
+ return;
1787
+ }
1788
+ if (currentUrl) {
1789
+ console.log(`
1790
+ Current: ${currentUrl}`);
1791
+ console.log(`Expected: ${poolerUrl}`);
1792
+ }
1793
+ if (options.fix) {
1794
+ const envPath = path5.join(options.cwd, ".env.local");
1795
+ if (fs5.existsSync(envPath)) {
1796
+ let content = fs5.readFileSync(envPath, "utf-8");
1797
+ if (/^SUPABASE_DB_URL=/m.test(content)) {
1798
+ content = content.replace(/^SUPABASE_DB_URL=.*$/m, `SUPABASE_DB_URL=${poolerUrl}`);
1799
+ } else {
1800
+ content = content.trimEnd() + `
1801
+ SUPABASE_DB_URL=${poolerUrl}
1802
+ `;
1803
+ }
1804
+ fs5.writeFileSync(envPath, content);
1805
+ console.log("\n\u2714 .env.local updated with correct pooler URL");
1806
+ }
1807
+ } else if (currentUrl !== poolerUrl) {
1808
+ console.log("\nRun with --fix to update .env.local");
1809
+ }
1810
+ }
1811
+ var passwordTheme = {
1812
+ prefix: { idle: blue("?"), done: blue("\u2714") },
1813
+ style: {
1814
+ answer: (text) => amber(text),
1815
+ message: (text) => bold(text),
1816
+ help: (text) => dim(text)
1817
+ }
1818
+ };
1819
+ async function dbPassword(cwd, value) {
1820
+ const manifest = readManifest(cwd);
1821
+ if (!manifest) {
1822
+ throw new Error("No agentlink.json found. Run `agentlink` to scaffold a project first.");
1823
+ }
1824
+ const defaultEnv = manifest.cloud?.default;
1825
+ if (!defaultEnv || defaultEnv === "local" || !manifest.cloud?.environments?.[defaultEnv]) {
1826
+ throw new Error("No cloud environment configured. This command is for cloud projects.");
1827
+ }
1828
+ const env2 = manifest.cloud.environments[defaultEnv];
1829
+ const projectRef = env2.projectRef;
1830
+ if (value) {
1831
+ saveProjectCredential(projectRef, value);
1832
+ console.log(`Password saved for project ${dim(projectRef)}`);
1833
+ } else {
1834
+ const current = loadProjectCredential(projectRef);
1835
+ if (current) {
1836
+ const masked = current.length > 8 ? current.slice(0, 4) + "\u25CF".repeat(current.length - 8) + current.slice(-4) : "\u25CF".repeat(current.length);
1837
+ console.log(`Current password: ${dim(masked)} (project: ${projectRef})`);
1838
+ console.log();
1839
+ }
1840
+ const resetUrl = `https://supabase.com/dashboard/project/${projectRef}/settings/database`;
1841
+ console.log(` ${dim("Reset your password at:")}`);
1842
+ console.log(` ${dim(resetUrl)}`);
1843
+ console.log();
1844
+ const newPassword = await input({
1845
+ message: "Database password:",
1846
+ theme: passwordTheme,
1847
+ transformer: (v, { isFinal }) => {
1848
+ if (isFinal) return "\u25CF".repeat(Math.min(v.length, 8));
1849
+ return "\u25CF".repeat(v.length);
1850
+ },
1851
+ validate: (val) => val.trim().length > 0 || "Password is required"
1852
+ });
1853
+ saveProjectCredential(projectRef, newPassword.trim());
1854
+ console.log(`Password saved for project ${dim(projectRef)}`);
1855
+ }
1856
+ }
1724
1857
 
1725
1858
  // src/deploy.ts
1726
1859
  import fs6 from "fs";
1727
1860
  import os2 from "os";
1728
1861
  import path6 from "path";
1729
- import { confirm, input } from "@inquirer/prompts";
1862
+ import { confirm, input as input2 } from "@inquirer/prompts";
1730
1863
 
1731
1864
  // src/migration-analysis.ts
1732
1865
  var PATTERNS = [
@@ -1933,8 +2066,8 @@ async function resolveEnvironments(cwd, opts) {
1933
2066
  }
1934
2067
  await ensureAccessToken(opts.ci, cwd);
1935
2068
  const targetEnv = resolveCloudEnv(manifest.cloud, targetName);
1936
- const dbPassword = resolveTargetPassword(targetEnv.projectRef);
1937
- const targetDbUrl = resolveDbUrlForEnv(targetEnv, dbPassword);
2069
+ const dbPassword2 = resolveTargetPassword(targetEnv.projectRef);
2070
+ const targetDbUrl = resolveDbUrlForEnv(targetEnv, dbPassword2);
1938
2071
  const devDbUrl = await resolveDbUrl(cwd);
1939
2072
  return { devDbUrl, targetEnv, targetDbUrl, targetName };
1940
2073
  }
@@ -2099,7 +2232,7 @@ async function syncSecrets(cwd, resolved, opts) {
2099
2232
  `);
2100
2233
  const toSet = {};
2101
2234
  for (const key of missingKeys) {
2102
- const value = await input({
2235
+ const value = await input2({
2103
2236
  message: `${key}:`,
2104
2237
  theme,
2105
2238
  validate: (val) => val.trim().length > 0 || "Value is required (press Ctrl+C to skip all)"
@@ -2115,7 +2248,7 @@ async function syncSecrets(cwd, resolved, opts) {
2115
2248
  // src/env.ts
2116
2249
  import fs8 from "fs";
2117
2250
  import path8 from "path";
2118
- import { confirm as confirm2, input as input2, select } from "@inquirer/prompts";
2251
+ import { confirm as confirm2, input as input3, select } from "@inquirer/prompts";
2119
2252
 
2120
2253
  // src/claude-md.ts
2121
2254
  import fs7 from "fs";
@@ -2259,14 +2392,14 @@ async function envAdd(name, cwd, opts = {}) {
2259
2392
  }
2260
2393
  await ensureAccessToken(opts.nonInteractive, cwd);
2261
2394
  let env2;
2262
- let dbPassword;
2395
+ let dbPassword2;
2263
2396
  if (opts.projectRef) {
2264
2397
  const projects = await listProjects();
2265
2398
  const project = projects.find((p) => p.id === opts.projectRef);
2266
2399
  if (!project) {
2267
2400
  throw new Error(`Project ${opts.projectRef} not found.`);
2268
2401
  }
2269
- dbPassword = await promptForPassword(opts.nonInteractive);
2402
+ dbPassword2 = await promptForPassword(opts.nonInteractive, project.id);
2270
2403
  env2 = {
2271
2404
  projectRef: project.id,
2272
2405
  region: project.region,
@@ -2286,17 +2419,17 @@ async function envAdd(name, cwd, opts = {}) {
2286
2419
  if (method === "existing") {
2287
2420
  const result = await connectExistingProject();
2288
2421
  env2 = result.env;
2289
- dbPassword = result.dbPassword;
2422
+ dbPassword2 = result.dbPassword;
2290
2423
  } else {
2291
2424
  const result = await createNewProject(name, opts);
2292
2425
  env2 = result.env;
2293
- dbPassword = result.dbPassword;
2426
+ dbPassword2 = result.dbPassword;
2294
2427
  }
2295
2428
  }
2296
2429
  addCloudEnvironment(cwd, name, env2);
2297
2430
  console.log(`
2298
2431
  ${blue("\u2714")} Environment "${name}" added to agentlink.json`);
2299
- saveProjectCredential(env2.projectRef, dbPassword);
2432
+ saveProjectCredential(env2.projectRef, dbPassword2);
2300
2433
  console.log(` ${blue("\u2714")} Credentials stored securely`);
2301
2434
  if (name.startsWith("dev") || name === "staging") {
2302
2435
  const shouldSync = opts.nonInteractive ? false : await confirm2({
@@ -2318,7 +2451,7 @@ async function envAdd(name, cwd, opts = {}) {
2318
2451
  console.log(` Deploy with: ${dim("npx @agentlink.sh/cli@latest deploy --env " + name)}`);
2319
2452
  }
2320
2453
  }
2321
- async function promptForPassword(nonInteractive) {
2454
+ async function promptForPassword(nonInteractive, projectRef) {
2322
2455
  if (nonInteractive) {
2323
2456
  const envPass = process.env.SUPABASE_DB_PASSWORD;
2324
2457
  if (!envPass) {
@@ -2326,7 +2459,13 @@ async function promptForPassword(nonInteractive) {
2326
2459
  }
2327
2460
  return envPass;
2328
2461
  }
2329
- return await input2({
2462
+ if (projectRef) {
2463
+ const resetUrl = `https://supabase.com/dashboard/project/${projectRef}/settings/database`;
2464
+ console.log(` ${dim("Don't know the password? Reset it at:")}`);
2465
+ console.log(` ${dim(resetUrl)}`);
2466
+ console.log();
2467
+ }
2468
+ return await input3({
2330
2469
  message: "Database password:",
2331
2470
  theme: theme2,
2332
2471
  transformer: (value, { isFinal }) => {
@@ -2350,18 +2489,18 @@ async function connectExistingProject() {
2350
2489
  }))
2351
2490
  });
2352
2491
  const project = projects.find((p) => p.id === projectRef);
2353
- const dbPassword = await promptForPassword();
2492
+ const dbPassword2 = await promptForPassword(false, project.id);
2354
2493
  return {
2355
2494
  env: {
2356
2495
  projectRef: project.id,
2357
2496
  region: project.region,
2358
2497
  apiUrl: `https://${project.id}.supabase.co`
2359
2498
  },
2360
- dbPassword
2499
+ dbPassword: dbPassword2
2361
2500
  };
2362
2501
  }
2363
2502
  async function createNewProject(envName, opts) {
2364
- const { listOrganizations: listOrganizations2, listRegions: listRegions2 } = await import("./cloud-2AWCJAG5.js");
2503
+ const { listOrganizations: listOrganizations2, listRegions: listRegions2 } = await import("./cloud-QNZQNIS5.js");
2365
2504
  const orgs = await listOrganizations2();
2366
2505
  if (orgs.length === 0) {
2367
2506
  throw new Error("No Supabase organizations found. Create one at https://supabase.com/dashboard.");
@@ -2377,13 +2516,13 @@ async function createNewProject(envName, opts) {
2377
2516
  theme: theme2,
2378
2517
  choices: regions.map((r) => ({ name: `${r.name} (${r.id})`, value: r.id }))
2379
2518
  });
2380
- const projectName = await input2({
2519
+ const projectName = await input3({
2381
2520
  message: "Project name:",
2382
2521
  default: `agentlink-${envName}`,
2383
2522
  theme: theme2
2384
2523
  });
2385
2524
  const { randomBytes } = await import("crypto");
2386
- const dbPassword = randomBytes(24).toString("base64url");
2525
+ const dbPassword2 = randomBytes(24).toString("base64url");
2387
2526
  console.log(`
2388
2527
  Creating project "${projectName}" in ${region}...
2389
2528
  `);
@@ -2391,7 +2530,7 @@ async function createNewProject(envName, opts) {
2391
2530
  orgId,
2392
2531
  name: projectName,
2393
2532
  region,
2394
- dbPass: dbPassword
2533
+ dbPass: dbPassword2
2395
2534
  });
2396
2535
  await waitForProjectReady(project.id);
2397
2536
  console.log(` ${blue("\u2714")} Project created: ${project.id}`);
@@ -2401,7 +2540,7 @@ async function createNewProject(envName, opts) {
2401
2540
  region: project.region,
2402
2541
  apiUrl: `https://${project.id}.supabase.co`
2403
2542
  },
2404
- dbPassword
2543
+ dbPassword: dbPassword2
2405
2544
  };
2406
2545
  }
2407
2546
  async function envUse(name, cwd) {
@@ -2450,7 +2589,7 @@ async function envUse(name, cwd) {
2450
2589
  await ensureAccessToken(true, cwd);
2451
2590
  const env2 = manifest.cloud.environments[name];
2452
2591
  const keys = await getApiKeys(env2.projectRef);
2453
- const dbPassword = loadProjectCredential(env2.projectRef);
2592
+ const dbPassword2 = loadProjectCredential(env2.projectRef);
2454
2593
  const frontend = manifest.frontend;
2455
2594
  const urlKey = frontend === "nextjs" ? "NEXT_PUBLIC_SUPABASE_URL" : "VITE_SUPABASE_URL";
2456
2595
  const keyKey = frontend === "nextjs" ? "NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY" : "VITE_SUPABASE_PUBLISHABLE_KEY";
@@ -2460,8 +2599,8 @@ async function envUse(name, cwd) {
2460
2599
  SB_PUBLISHABLE_KEY: keys.publishableKey,
2461
2600
  SB_SECRET_KEY: keys.secretKey
2462
2601
  };
2463
- if (dbPassword) {
2464
- vars.SUPABASE_DB_URL = resolveDbUrlForEnv(env2, dbPassword);
2602
+ if (dbPassword2) {
2603
+ vars.SUPABASE_DB_URL = resolveDbUrlForEnv(env2, dbPassword2);
2465
2604
  }
2466
2605
  }
2467
2606
  writeManagedEnv(cwd, name, vars);
@@ -2516,6 +2655,120 @@ async function envRemove(name, cwd, opts = {}) {
2516
2655
  removeCloudEnvironment(cwd, name);
2517
2656
  console.log(` ${blue("\u2714")} Environment "${name}" removed.`);
2518
2657
  }
2658
+ async function envRelink(name, cwd, opts = {}) {
2659
+ const manifest = readManifest(cwd);
2660
+ if (!manifest) {
2661
+ throw new Error("No agentlink.json found. Run `agentlink` to scaffold a project first.");
2662
+ }
2663
+ if (opts.token) {
2664
+ process.env.SUPABASE_ACCESS_TOKEN = opts.token;
2665
+ }
2666
+ await ensureAccessToken(opts.nonInteractive, cwd);
2667
+ let env2;
2668
+ let dbPassword2;
2669
+ if (opts.projectRef) {
2670
+ const projects = await listProjects();
2671
+ const project = projects.find((p) => p.id === opts.projectRef);
2672
+ if (!project) {
2673
+ throw new Error(`Project ${opts.projectRef} not found.`);
2674
+ }
2675
+ dbPassword2 = await promptForPassword(opts.nonInteractive, project.id);
2676
+ env2 = {
2677
+ projectRef: project.id,
2678
+ region: project.region,
2679
+ apiUrl: `https://${project.id}.supabase.co`
2680
+ };
2681
+ } else if (opts.nonInteractive) {
2682
+ throw new Error("--project-ref is required in non-interactive mode.");
2683
+ } else {
2684
+ const method = await select({
2685
+ message: `How do you want to relink the "${name}" environment?`,
2686
+ theme: theme2,
2687
+ choices: [
2688
+ { name: "Connect an existing Supabase project", value: "existing" },
2689
+ { name: "Create a new Supabase project", value: "new" }
2690
+ ]
2691
+ });
2692
+ if (method === "existing") {
2693
+ const result = await connectExistingProject();
2694
+ env2 = result.env;
2695
+ dbPassword2 = result.dbPassword;
2696
+ } else {
2697
+ const result = await createNewProject(name, opts);
2698
+ env2 = result.env;
2699
+ dbPassword2 = result.dbPassword;
2700
+ }
2701
+ }
2702
+ if (!manifest.cloud) {
2703
+ manifest.cloud = { default: "local", environments: {} };
2704
+ }
2705
+ manifest.cloud.environments[name] = env2;
2706
+ writeManifest(cwd, manifest);
2707
+ console.log(`
2708
+ ${blue("\u2714")} Environment "${name}" relinked to ${env2.projectRef}`);
2709
+ saveProjectCredential(env2.projectRef, dbPassword2);
2710
+ console.log(` ${blue("\u2714")} Credentials stored securely`);
2711
+ const keys = await getApiKeys(env2.projectRef);
2712
+ let dbUrl;
2713
+ try {
2714
+ dbUrl = await resolvePoolerDbUrl(env2.projectRef, dbPassword2);
2715
+ } catch {
2716
+ dbUrl = resolveDbUrlForEnv(env2, dbPassword2);
2717
+ }
2718
+ const frontend = manifest.frontend;
2719
+ const urlKey = frontend === "nextjs" ? "NEXT_PUBLIC_SUPABASE_URL" : "VITE_SUPABASE_URL";
2720
+ const keyKey = frontend === "nextjs" ? "NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY" : "VITE_SUPABASE_PUBLISHABLE_KEY";
2721
+ writeManagedEnv(cwd, name, {
2722
+ [urlKey]: env2.apiUrl,
2723
+ [keyKey]: keys.publishableKey,
2724
+ SB_PUBLISHABLE_KEY: keys.publishableKey,
2725
+ SB_SECRET_KEY: keys.secretKey,
2726
+ SUPABASE_DB_URL: dbUrl
2727
+ });
2728
+ console.log(` ${blue("\u2714")} .env.local updated (pooler URL from API)`);
2729
+ writeClaudeMd({
2730
+ projectDir: cwd,
2731
+ mode: "cloud",
2732
+ projectRef: env2.projectRef,
2733
+ region: env2.region,
2734
+ envName: name
2735
+ });
2736
+ console.log(` ${blue("\u2714")} CLAUDE.md updated`);
2737
+ console.log(`
2738
+ ${blue("\u25CF")} Linking to Supabase project...`);
2739
+ await runCommand(
2740
+ `${sb()} link --project-ref ${env2.projectRef}`,
2741
+ cwd,
2742
+ void 0,
2743
+ { SUPABASE_DB_PASSWORD: dbPassword2 }
2744
+ );
2745
+ console.log(` ${blue("\u2714")} Linked`);
2746
+ const migrationsDir = path8.join(cwd, "supabase", "migrations");
2747
+ const hasMigrations = fs8.existsSync(migrationsDir) && fs8.readdirSync(migrationsDir).some((f) => f.endsWith(".sql"));
2748
+ if (hasMigrations) {
2749
+ console.log(`
2750
+ ${blue("\u25CF")} Pushing migrations to new project...`);
2751
+ await runCommand(
2752
+ `${sb()} db push`,
2753
+ cwd,
2754
+ void 0,
2755
+ { SUPABASE_DB_PASSWORD: dbPassword2 }
2756
+ );
2757
+ console.log(` ${blue("\u2714")} Migrations pushed`);
2758
+ }
2759
+ const functionsDir = path8.join(cwd, "supabase", "functions");
2760
+ if (fs8.existsSync(functionsDir)) {
2761
+ const hasFunctions = fs8.readdirSync(functionsDir, { withFileTypes: true }).some((e) => e.isDirectory() && !e.name.startsWith("_"));
2762
+ if (hasFunctions) {
2763
+ console.log(`
2764
+ ${blue("\u25CF")} Deploying edge functions...`);
2765
+ await runCommand(`${sb()} functions deploy --project-ref ${env2.projectRef}`, cwd);
2766
+ console.log(` ${blue("\u2714")} Edge functions deployed`);
2767
+ }
2768
+ }
2769
+ console.log(`
2770
+ ${blue("Done.")} Environment "${name}" relinked to ${dim(env2.projectRef)}`);
2771
+ }
2519
2772
  async function scaffoldCiTemplate(cwd, envName) {
2520
2773
  const workflowDir = path8.join(cwd, ".github", "workflows");
2521
2774
  fs8.mkdirSync(workflowDir, { recursive: true });
@@ -3382,7 +3635,7 @@ import { execSync } from "child_process";
3382
3635
  import fs15 from "fs";
3383
3636
  import path15 from "path";
3384
3637
  import { fileURLToPath as fileURLToPath2 } from "url";
3385
- import { confirm as confirm5, input as input4, select as select3 } from "@inquirer/prompts";
3638
+ import { confirm as confirm5, input as input5, select as select3 } from "@inquirer/prompts";
3386
3639
 
3387
3640
  // src/banner.ts
3388
3641
  var TITLE = [
@@ -3504,7 +3757,7 @@ function clearSession(name) {
3504
3757
  // src/scaffold.ts
3505
3758
  import fs13 from "fs";
3506
3759
  import path13 from "path";
3507
- import { confirm as confirm3, input as input3 } from "@inquirer/prompts";
3760
+ import { confirm as confirm3, input as input4 } from "@inquirer/prompts";
3508
3761
 
3509
3762
  // src/claude-settings.ts
3510
3763
  import fs12 from "fs";
@@ -3521,7 +3774,7 @@ var CLAUDE_SETTINGS = {
3521
3774
  "Bash(supabase functions:*)",
3522
3775
  "Bash(psql:*)",
3523
3776
  "Bash(npx create-next-app@latest:*)",
3524
- "Bash(npx agentlinksh/cli?@latest:*)",
3777
+ "Bash(npx @agentlink.sh/cli@latest:*)",
3525
3778
  "Bash(npm install:*)",
3526
3779
  "Bash(npx next:*)",
3527
3780
  "Bash(npx vite:*)",
@@ -3748,7 +4001,7 @@ async function scaffold(options) {
3748
4001
  console.log(dim(` If you don't know this password you can reset it from your Supabase dashboard:`));
3749
4002
  console.log(dim(` ${link(resetUrl)}`));
3750
4003
  console.log();
3751
- const dbPass2 = await input3({
4004
+ const dbPass2 = await input4({
3752
4005
  message: "Database password for existing project",
3753
4006
  theme: theme3,
3754
4007
  validate: (val) => val.trim().length > 0 || "Password is required"
@@ -3836,16 +4089,19 @@ SUPABASE_DB_PASSWORD=${ctx.dbPass}
3836
4089
  }
3837
4090
  spinner.text = "Setting up database \u2014 Pushing infrastructure migrations";
3838
4091
  await runCommand(`${sb()} db push`, projectDir, void 0, cloudEnv);
3839
- spinner.text = "Setting up database \u2014 Writing application migration";
3840
- const setupBase = /* @__PURE__ */ new Date();
3841
- const setupContent = getTemplateSQLEntries().join("\n\n");
3842
4092
  const migrationsDir = path13.join(projectDir, "supabase", "migrations");
3843
- const setupVersion = setupBase.toISOString().replace(/\D/g, "").slice(0, 14);
3844
- const setupFilename = `${setupVersion}_agentlink_setup.sql`;
3845
- fs13.writeFileSync(path13.join(migrationsDir, setupFilename), setupContent);
3846
- spinner.text = "Setting up database \u2014 Writing post-setup migrations";
3847
- const postBase = new Date(setupBase.getTime() + 2e3);
3848
- writeMigrationTemplates(projectDir, POST_SETUP_MIGRATIONS, postBase);
4093
+ const existingSetup = fs13.readdirSync(migrationsDir).find((f) => f.endsWith("_agentlink_setup.sql"));
4094
+ if (!existingSetup) {
4095
+ spinner.text = "Setting up database \u2014 Writing application migration";
4096
+ const setupBase = /* @__PURE__ */ new Date();
4097
+ const setupContent = getTemplateSQLEntries().join("\n\n");
4098
+ const setupVersion = setupBase.toISOString().replace(/\D/g, "").slice(0, 14);
4099
+ const setupFilename = `${setupVersion}_agentlink_setup.sql`;
4100
+ fs13.writeFileSync(path13.join(migrationsDir, setupFilename), setupContent);
4101
+ spinner.text = "Setting up database \u2014 Writing post-setup migrations";
4102
+ const postBase = new Date(setupBase.getTime() + 2e3);
4103
+ writeMigrationTemplates(projectDir, POST_SETUP_MIGRATIONS, postBase);
4104
+ }
3849
4105
  spinner.text = "Setting up database \u2014 Pushing migrations";
3850
4106
  await runCommand(`${sb()} db push`, projectDir, void 0, cloudEnv);
3851
4107
  spinner.text = "Setting up database \u2014 Storing vault secrets";
@@ -3890,19 +4146,22 @@ SUPABASE_DB_PASSWORD=${ctx.dbPass}
3890
4146
  for (const sql of getTemplateSQLEntries()) {
3891
4147
  await runSQL(sql, ctx.dbUrl);
3892
4148
  }
3893
- spinner.text = "Setting up database \u2014 Writing application migration";
3894
- const setupBase = /* @__PURE__ */ new Date();
3895
- const setupContent = getTemplateSQLEntries().join("\n\n");
3896
4149
  const migrationsDir = path13.join(projectDir, "supabase", "migrations");
3897
- const setupVersion = setupBase.toISOString().replace(/\D/g, "").slice(0, 14);
3898
- const setupFilename = `${setupVersion}_agentlink_setup.sql`;
3899
- fs13.writeFileSync(path13.join(migrationsDir, setupFilename), setupContent);
3900
- await repairMigrations(projectDir, [setupVersion]);
3901
- spinner.text = "Setting up database \u2014 Writing post-setup migrations";
3902
- const postBase = new Date(setupBase.getTime() + 2e3);
3903
- const postVersions = writeMigrationTemplates(projectDir, POST_SETUP_MIGRATIONS, postBase);
3904
- if (postVersions.length > 0) {
3905
- await repairMigrations(projectDir, postVersions);
4150
+ const existingSetup = fs13.readdirSync(migrationsDir).find((f) => f.endsWith("_agentlink_setup.sql"));
4151
+ if (!existingSetup) {
4152
+ spinner.text = "Setting up database \u2014 Writing application migration";
4153
+ const setupBase = /* @__PURE__ */ new Date();
4154
+ const setupContent = getTemplateSQLEntries().join("\n\n");
4155
+ const setupVersion = setupBase.toISOString().replace(/\D/g, "").slice(0, 14);
4156
+ const setupFilename = `${setupVersion}_agentlink_setup.sql`;
4157
+ fs13.writeFileSync(path13.join(migrationsDir, setupFilename), setupContent);
4158
+ await repairMigrations(projectDir, [setupVersion]);
4159
+ spinner.text = "Setting up database \u2014 Writing post-setup migrations";
4160
+ const postBase = new Date(setupBase.getTime() + 2e3);
4161
+ const postVersions = writeMigrationTemplates(projectDir, POST_SETUP_MIGRATIONS, postBase);
4162
+ if (postVersions.length > 0) {
4163
+ await repairMigrations(projectDir, postVersions);
4164
+ }
3906
4165
  }
3907
4166
  spinner.text = "Setting up database \u2014 Storing vault secrets";
3908
4167
  await runSQL(seedSQL(ctx.publishableKey, ctx.secretKey), ctx.dbUrl);
@@ -4720,7 +4979,7 @@ Run without --resume to start fresh.`
4720
4979
  if (nonInteractive) {
4721
4980
  throw new Error("Project name required in non-interactive mode. Provide a name as the first argument.");
4722
4981
  }
4723
- name = await input4({
4982
+ name = await input5({
4724
4983
  message: "Project name",
4725
4984
  default: "my-app",
4726
4985
  theme: theme5,
@@ -4744,7 +5003,7 @@ ${red("Error:")} ${error}`);
4744
5003
  if (nonInteractive) {
4745
5004
  throw new Error("Project name required in non-interactive mode. Provide a name as the first argument.");
4746
5005
  }
4747
- name = await input4({
5006
+ name = await input5({
4748
5007
  message: "Project name",
4749
5008
  default: "my-app",
4750
5009
  theme: theme5,
@@ -4959,7 +5218,7 @@ program.command("info [name]").description("Show documentation about AgentLink c
4959
5218
  program.command("login").description("Authenticate with Supabase via OAuth (opens browser)").action(async () => {
4960
5219
  try {
4961
5220
  const { oauthLogin } = await import("./oauth-VTSNCG7U.js");
4962
- const { saveCredentials: saveCredentials2, loadCredentials: loadCredentials2, credentialsPath: credentialsPath2 } = await import("./cloud-2AWCJAG5.js");
5221
+ const { saveCredentials: saveCredentials2, loadCredentials: loadCredentials2, credentialsPath: credentialsPath2 } = await import("./cloud-QNZQNIS5.js");
4963
5222
  console.log();
4964
5223
  const tokens = await oauthLogin();
4965
5224
  saveCredentials2({
@@ -5073,6 +5332,35 @@ ${red("Error:")} ${err.message}`);
5073
5332
  process.exit(1);
5074
5333
  }
5075
5334
  });
5335
+ db.command("rebuild").description("Nuke migrations, re-apply schemas, and regenerate migration files").option("--debug", "Write debug log to agentlink-debug.log").option("--db-url <url>", "Database URL (auto-detects from .env.local or supabase status)").option("--env <name>", "Target cloud environment").action(async (opts) => {
5336
+ if (opts.debug) initLog(true);
5337
+ try {
5338
+ await dbRebuild({ dbUrl: opts.dbUrl, cwd: process.cwd(), env: opts.env });
5339
+ } catch (err) {
5340
+ console.error(`
5341
+ ${red("Error:")} ${err.message}`);
5342
+ process.exit(1);
5343
+ }
5344
+ });
5345
+ db.command("url").description("Show the correct pooler DB URL from Supabase (--fix to update .env.local)").option("--fix", "Update .env.local with the correct URL").option("--debug", "Write debug log to agentlink-debug.log").option("--db-url <url>", "Database URL (auto-detects from .env.local or supabase status)").option("--env <name>", "Target cloud environment").action(async (opts) => {
5346
+ if (opts.debug) initLog(true);
5347
+ try {
5348
+ await dbUrlCheck({ dbUrl: opts.dbUrl, cwd: process.cwd(), env: opts.env, fix: opts.fix });
5349
+ } catch (err) {
5350
+ console.error(`
5351
+ ${red("Error:")} ${err.message}`);
5352
+ process.exit(1);
5353
+ }
5354
+ });
5355
+ db.command("password [value]").description("Show or set the database password for the active cloud project").action(async (value) => {
5356
+ try {
5357
+ await dbPassword(process.cwd(), value);
5358
+ } catch (err) {
5359
+ console.error(`
5360
+ ${red("Error:")} ${err.message}`);
5361
+ process.exit(1);
5362
+ }
5363
+ });
5076
5364
  program.addCommand(db);
5077
5365
  program.command("deploy").description("Deploy schema changes and edge functions to a target environment").option("--env <name>", "Target environment (default: prod)").option("--ci", "Non-interactive mode for CI/CD").option("--allow-warnings", "Proceed past data-risk warnings (CI only)").option("--dry-run", "Show what would be deployed without applying").option("--setup-ci", "Scaffold GitHub Actions workflow").option("--debug", "Write debug log to agentlink-debug.log").action(async (opts) => {
5078
5366
  if (opts.debug) initLog(true);
@@ -5114,6 +5402,16 @@ ${red("Error:")} ${err.message}`);
5114
5402
  process.exit(1);
5115
5403
  }
5116
5404
  });
5405
+ env.command("relink <name>").description("Relink an environment to a new Supabase project (keeps migrations)").option("--token <token>", "Supabase access token").option("--org <id>", "Supabase organization ID").option("--region <region>", "Supabase Cloud region").option("--project-ref <ref>", "Connect to an existing Supabase project").option("--non-interactive", "Error instead of prompting").option("--debug", "Write debug log to agentlink-debug.log").action(async (name, opts) => {
5406
+ if (opts.debug) initLog(true);
5407
+ try {
5408
+ await envRelink(name, process.cwd(), opts);
5409
+ } catch (err) {
5410
+ console.error(`
5411
+ ${red("Error:")} ${err.message}`);
5412
+ process.exit(1);
5413
+ }
5414
+ });
5117
5415
  env.command("remove <name>").description("Remove a cloud environment").option("-y, --yes", "Skip confirmation").option("--debug", "Write debug log to agentlink-debug.log").action(async (name, opts) => {
5118
5416
  if (opts.debug) initLog(true);
5119
5417
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentlink.sh/cli",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "CLI for scaffolding Supabase apps with AI agents",
5
5
  "bin": {
6
6
  "agentlink": "dist/index.js"