@agentlink.sh/cli 0.11.1 → 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,
@@ -1530,6 +1531,7 @@ async function check(envFlag) {
1530
1531
  import fs5 from "fs";
1531
1532
  import os from "os";
1532
1533
  import path5 from "path";
1534
+ import { input } from "@inquirer/prompts";
1533
1535
  function stripQuotes(val) {
1534
1536
  if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
1535
1537
  return val.slice(1, -1);
@@ -1734,12 +1736,130 @@ Migration created: supabase/migrations/${filename}`);
1734
1736
  fs5.rmSync(tmpDir, { recursive: true, force: true });
1735
1737
  }
1736
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
+ }
1737
1857
 
1738
1858
  // src/deploy.ts
1739
1859
  import fs6 from "fs";
1740
1860
  import os2 from "os";
1741
1861
  import path6 from "path";
1742
- import { confirm, input } from "@inquirer/prompts";
1862
+ import { confirm, input as input2 } from "@inquirer/prompts";
1743
1863
 
1744
1864
  // src/migration-analysis.ts
1745
1865
  var PATTERNS = [
@@ -1946,8 +2066,8 @@ async function resolveEnvironments(cwd, opts) {
1946
2066
  }
1947
2067
  await ensureAccessToken(opts.ci, cwd);
1948
2068
  const targetEnv = resolveCloudEnv(manifest.cloud, targetName);
1949
- const dbPassword = resolveTargetPassword(targetEnv.projectRef);
1950
- const targetDbUrl = resolveDbUrlForEnv(targetEnv, dbPassword);
2069
+ const dbPassword2 = resolveTargetPassword(targetEnv.projectRef);
2070
+ const targetDbUrl = resolveDbUrlForEnv(targetEnv, dbPassword2);
1951
2071
  const devDbUrl = await resolveDbUrl(cwd);
1952
2072
  return { devDbUrl, targetEnv, targetDbUrl, targetName };
1953
2073
  }
@@ -2112,7 +2232,7 @@ async function syncSecrets(cwd, resolved, opts) {
2112
2232
  `);
2113
2233
  const toSet = {};
2114
2234
  for (const key of missingKeys) {
2115
- const value = await input({
2235
+ const value = await input2({
2116
2236
  message: `${key}:`,
2117
2237
  theme,
2118
2238
  validate: (val) => val.trim().length > 0 || "Value is required (press Ctrl+C to skip all)"
@@ -2128,7 +2248,7 @@ async function syncSecrets(cwd, resolved, opts) {
2128
2248
  // src/env.ts
2129
2249
  import fs8 from "fs";
2130
2250
  import path8 from "path";
2131
- import { confirm as confirm2, input as input2, select } from "@inquirer/prompts";
2251
+ import { confirm as confirm2, input as input3, select } from "@inquirer/prompts";
2132
2252
 
2133
2253
  // src/claude-md.ts
2134
2254
  import fs7 from "fs";
@@ -2272,14 +2392,14 @@ async function envAdd(name, cwd, opts = {}) {
2272
2392
  }
2273
2393
  await ensureAccessToken(opts.nonInteractive, cwd);
2274
2394
  let env2;
2275
- let dbPassword;
2395
+ let dbPassword2;
2276
2396
  if (opts.projectRef) {
2277
2397
  const projects = await listProjects();
2278
2398
  const project = projects.find((p) => p.id === opts.projectRef);
2279
2399
  if (!project) {
2280
2400
  throw new Error(`Project ${opts.projectRef} not found.`);
2281
2401
  }
2282
- dbPassword = await promptForPassword(opts.nonInteractive);
2402
+ dbPassword2 = await promptForPassword(opts.nonInteractive, project.id);
2283
2403
  env2 = {
2284
2404
  projectRef: project.id,
2285
2405
  region: project.region,
@@ -2299,17 +2419,17 @@ async function envAdd(name, cwd, opts = {}) {
2299
2419
  if (method === "existing") {
2300
2420
  const result = await connectExistingProject();
2301
2421
  env2 = result.env;
2302
- dbPassword = result.dbPassword;
2422
+ dbPassword2 = result.dbPassword;
2303
2423
  } else {
2304
2424
  const result = await createNewProject(name, opts);
2305
2425
  env2 = result.env;
2306
- dbPassword = result.dbPassword;
2426
+ dbPassword2 = result.dbPassword;
2307
2427
  }
2308
2428
  }
2309
2429
  addCloudEnvironment(cwd, name, env2);
2310
2430
  console.log(`
2311
2431
  ${blue("\u2714")} Environment "${name}" added to agentlink.json`);
2312
- saveProjectCredential(env2.projectRef, dbPassword);
2432
+ saveProjectCredential(env2.projectRef, dbPassword2);
2313
2433
  console.log(` ${blue("\u2714")} Credentials stored securely`);
2314
2434
  if (name.startsWith("dev") || name === "staging") {
2315
2435
  const shouldSync = opts.nonInteractive ? false : await confirm2({
@@ -2331,7 +2451,7 @@ async function envAdd(name, cwd, opts = {}) {
2331
2451
  console.log(` Deploy with: ${dim("npx @agentlink.sh/cli@latest deploy --env " + name)}`);
2332
2452
  }
2333
2453
  }
2334
- async function promptForPassword(nonInteractive) {
2454
+ async function promptForPassword(nonInteractive, projectRef) {
2335
2455
  if (nonInteractive) {
2336
2456
  const envPass = process.env.SUPABASE_DB_PASSWORD;
2337
2457
  if (!envPass) {
@@ -2339,7 +2459,13 @@ async function promptForPassword(nonInteractive) {
2339
2459
  }
2340
2460
  return envPass;
2341
2461
  }
2342
- 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({
2343
2469
  message: "Database password:",
2344
2470
  theme: theme2,
2345
2471
  transformer: (value, { isFinal }) => {
@@ -2363,18 +2489,18 @@ async function connectExistingProject() {
2363
2489
  }))
2364
2490
  });
2365
2491
  const project = projects.find((p) => p.id === projectRef);
2366
- const dbPassword = await promptForPassword();
2492
+ const dbPassword2 = await promptForPassword(false, project.id);
2367
2493
  return {
2368
2494
  env: {
2369
2495
  projectRef: project.id,
2370
2496
  region: project.region,
2371
2497
  apiUrl: `https://${project.id}.supabase.co`
2372
2498
  },
2373
- dbPassword
2499
+ dbPassword: dbPassword2
2374
2500
  };
2375
2501
  }
2376
2502
  async function createNewProject(envName, opts) {
2377
- const { listOrganizations: listOrganizations2, listRegions: listRegions2 } = await import("./cloud-2AWCJAG5.js");
2503
+ const { listOrganizations: listOrganizations2, listRegions: listRegions2 } = await import("./cloud-QNZQNIS5.js");
2378
2504
  const orgs = await listOrganizations2();
2379
2505
  if (orgs.length === 0) {
2380
2506
  throw new Error("No Supabase organizations found. Create one at https://supabase.com/dashboard.");
@@ -2390,13 +2516,13 @@ async function createNewProject(envName, opts) {
2390
2516
  theme: theme2,
2391
2517
  choices: regions.map((r) => ({ name: `${r.name} (${r.id})`, value: r.id }))
2392
2518
  });
2393
- const projectName = await input2({
2519
+ const projectName = await input3({
2394
2520
  message: "Project name:",
2395
2521
  default: `agentlink-${envName}`,
2396
2522
  theme: theme2
2397
2523
  });
2398
2524
  const { randomBytes } = await import("crypto");
2399
- const dbPassword = randomBytes(24).toString("base64url");
2525
+ const dbPassword2 = randomBytes(24).toString("base64url");
2400
2526
  console.log(`
2401
2527
  Creating project "${projectName}" in ${region}...
2402
2528
  `);
@@ -2404,7 +2530,7 @@ async function createNewProject(envName, opts) {
2404
2530
  orgId,
2405
2531
  name: projectName,
2406
2532
  region,
2407
- dbPass: dbPassword
2533
+ dbPass: dbPassword2
2408
2534
  });
2409
2535
  await waitForProjectReady(project.id);
2410
2536
  console.log(` ${blue("\u2714")} Project created: ${project.id}`);
@@ -2414,7 +2540,7 @@ async function createNewProject(envName, opts) {
2414
2540
  region: project.region,
2415
2541
  apiUrl: `https://${project.id}.supabase.co`
2416
2542
  },
2417
- dbPassword
2543
+ dbPassword: dbPassword2
2418
2544
  };
2419
2545
  }
2420
2546
  async function envUse(name, cwd) {
@@ -2463,7 +2589,7 @@ async function envUse(name, cwd) {
2463
2589
  await ensureAccessToken(true, cwd);
2464
2590
  const env2 = manifest.cloud.environments[name];
2465
2591
  const keys = await getApiKeys(env2.projectRef);
2466
- const dbPassword = loadProjectCredential(env2.projectRef);
2592
+ const dbPassword2 = loadProjectCredential(env2.projectRef);
2467
2593
  const frontend = manifest.frontend;
2468
2594
  const urlKey = frontend === "nextjs" ? "NEXT_PUBLIC_SUPABASE_URL" : "VITE_SUPABASE_URL";
2469
2595
  const keyKey = frontend === "nextjs" ? "NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY" : "VITE_SUPABASE_PUBLISHABLE_KEY";
@@ -2473,8 +2599,8 @@ async function envUse(name, cwd) {
2473
2599
  SB_PUBLISHABLE_KEY: keys.publishableKey,
2474
2600
  SB_SECRET_KEY: keys.secretKey
2475
2601
  };
2476
- if (dbPassword) {
2477
- vars.SUPABASE_DB_URL = resolveDbUrlForEnv(env2, dbPassword);
2602
+ if (dbPassword2) {
2603
+ vars.SUPABASE_DB_URL = resolveDbUrlForEnv(env2, dbPassword2);
2478
2604
  }
2479
2605
  }
2480
2606
  writeManagedEnv(cwd, name, vars);
@@ -2529,6 +2655,120 @@ async function envRemove(name, cwd, opts = {}) {
2529
2655
  removeCloudEnvironment(cwd, name);
2530
2656
  console.log(` ${blue("\u2714")} Environment "${name}" removed.`);
2531
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
+ }
2532
2772
  async function scaffoldCiTemplate(cwd, envName) {
2533
2773
  const workflowDir = path8.join(cwd, ".github", "workflows");
2534
2774
  fs8.mkdirSync(workflowDir, { recursive: true });
@@ -3395,7 +3635,7 @@ import { execSync } from "child_process";
3395
3635
  import fs15 from "fs";
3396
3636
  import path15 from "path";
3397
3637
  import { fileURLToPath as fileURLToPath2 } from "url";
3398
- 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";
3399
3639
 
3400
3640
  // src/banner.ts
3401
3641
  var TITLE = [
@@ -3517,7 +3757,7 @@ function clearSession(name) {
3517
3757
  // src/scaffold.ts
3518
3758
  import fs13 from "fs";
3519
3759
  import path13 from "path";
3520
- import { confirm as confirm3, input as input3 } from "@inquirer/prompts";
3760
+ import { confirm as confirm3, input as input4 } from "@inquirer/prompts";
3521
3761
 
3522
3762
  // src/claude-settings.ts
3523
3763
  import fs12 from "fs";
@@ -3761,7 +4001,7 @@ async function scaffold(options) {
3761
4001
  console.log(dim(` If you don't know this password you can reset it from your Supabase dashboard:`));
3762
4002
  console.log(dim(` ${link(resetUrl)}`));
3763
4003
  console.log();
3764
- const dbPass2 = await input3({
4004
+ const dbPass2 = await input4({
3765
4005
  message: "Database password for existing project",
3766
4006
  theme: theme3,
3767
4007
  validate: (val) => val.trim().length > 0 || "Password is required"
@@ -3849,16 +4089,19 @@ SUPABASE_DB_PASSWORD=${ctx.dbPass}
3849
4089
  }
3850
4090
  spinner.text = "Setting up database \u2014 Pushing infrastructure migrations";
3851
4091
  await runCommand(`${sb()} db push`, projectDir, void 0, cloudEnv);
3852
- spinner.text = "Setting up database \u2014 Writing application migration";
3853
- const setupBase = /* @__PURE__ */ new Date();
3854
- const setupContent = getTemplateSQLEntries().join("\n\n");
3855
4092
  const migrationsDir = path13.join(projectDir, "supabase", "migrations");
3856
- const setupVersion = setupBase.toISOString().replace(/\D/g, "").slice(0, 14);
3857
- const setupFilename = `${setupVersion}_agentlink_setup.sql`;
3858
- fs13.writeFileSync(path13.join(migrationsDir, setupFilename), setupContent);
3859
- spinner.text = "Setting up database \u2014 Writing post-setup migrations";
3860
- const postBase = new Date(setupBase.getTime() + 2e3);
3861
- 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
+ }
3862
4105
  spinner.text = "Setting up database \u2014 Pushing migrations";
3863
4106
  await runCommand(`${sb()} db push`, projectDir, void 0, cloudEnv);
3864
4107
  spinner.text = "Setting up database \u2014 Storing vault secrets";
@@ -3903,19 +4146,22 @@ SUPABASE_DB_PASSWORD=${ctx.dbPass}
3903
4146
  for (const sql of getTemplateSQLEntries()) {
3904
4147
  await runSQL(sql, ctx.dbUrl);
3905
4148
  }
3906
- spinner.text = "Setting up database \u2014 Writing application migration";
3907
- const setupBase = /* @__PURE__ */ new Date();
3908
- const setupContent = getTemplateSQLEntries().join("\n\n");
3909
4149
  const migrationsDir = path13.join(projectDir, "supabase", "migrations");
3910
- const setupVersion = setupBase.toISOString().replace(/\D/g, "").slice(0, 14);
3911
- const setupFilename = `${setupVersion}_agentlink_setup.sql`;
3912
- fs13.writeFileSync(path13.join(migrationsDir, setupFilename), setupContent);
3913
- await repairMigrations(projectDir, [setupVersion]);
3914
- spinner.text = "Setting up database \u2014 Writing post-setup migrations";
3915
- const postBase = new Date(setupBase.getTime() + 2e3);
3916
- const postVersions = writeMigrationTemplates(projectDir, POST_SETUP_MIGRATIONS, postBase);
3917
- if (postVersions.length > 0) {
3918
- 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
+ }
3919
4165
  }
3920
4166
  spinner.text = "Setting up database \u2014 Storing vault secrets";
3921
4167
  await runSQL(seedSQL(ctx.publishableKey, ctx.secretKey), ctx.dbUrl);
@@ -4733,7 +4979,7 @@ Run without --resume to start fresh.`
4733
4979
  if (nonInteractive) {
4734
4980
  throw new Error("Project name required in non-interactive mode. Provide a name as the first argument.");
4735
4981
  }
4736
- name = await input4({
4982
+ name = await input5({
4737
4983
  message: "Project name",
4738
4984
  default: "my-app",
4739
4985
  theme: theme5,
@@ -4757,7 +5003,7 @@ ${red("Error:")} ${error}`);
4757
5003
  if (nonInteractive) {
4758
5004
  throw new Error("Project name required in non-interactive mode. Provide a name as the first argument.");
4759
5005
  }
4760
- name = await input4({
5006
+ name = await input5({
4761
5007
  message: "Project name",
4762
5008
  default: "my-app",
4763
5009
  theme: theme5,
@@ -4972,7 +5218,7 @@ program.command("info [name]").description("Show documentation about AgentLink c
4972
5218
  program.command("login").description("Authenticate with Supabase via OAuth (opens browser)").action(async () => {
4973
5219
  try {
4974
5220
  const { oauthLogin } = await import("./oauth-VTSNCG7U.js");
4975
- 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");
4976
5222
  console.log();
4977
5223
  const tokens = await oauthLogin();
4978
5224
  saveCredentials2({
@@ -5086,6 +5332,35 @@ ${red("Error:")} ${err.message}`);
5086
5332
  process.exit(1);
5087
5333
  }
5088
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
+ });
5089
5364
  program.addCommand(db);
5090
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) => {
5091
5366
  if (opts.debug) initLog(true);
@@ -5127,6 +5402,16 @@ ${red("Error:")} ${err.message}`);
5127
5402
  process.exit(1);
5128
5403
  }
5129
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
+ });
5130
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) => {
5131
5416
  if (opts.debug) initLog(true);
5132
5417
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentlink.sh/cli",
3
- "version": "0.11.1",
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"