agentlink-sh 0.30.1 → 0.32.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 +2 -2
  2. package/dist/index.js +88 -63
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -377,7 +377,7 @@ The scaffold runs a multi-step process:
377
377
  7. **Verify setup** — Runs verification queries to confirm all components
378
378
  8. **Setup frontend** — Writes React + Vite or NextJS files (if selected)
379
379
  9. **Install frontend deps** — Runs `npm install` for the frontend
380
- 10. **Configure Claude Code** — Sets up `CLAUDE.md` and Claude settings
380
+ 10. **Configure Claude Code** — Sets up `AGENTS.md` and Claude settings
381
381
  11. **Install MCP** — Installs Supabase MCP server (local mode only)
382
382
  12. **Install plugin** — Installs Agent Link plugin and companion skills
383
383
  13. **Finalize** — Creates git repo, writes `agentlink.json` manifest
@@ -425,7 +425,7 @@ The CLI installs all recommended companion skills for Claude Code automatically.
425
425
  ```
426
426
  my-app/
427
427
  ├── agentlink.json # Project manifest
428
- ├── CLAUDE.md # AI agent instructions
428
+ ├── AGENTS.md # AI agent instructions
429
429
  ├── .env.local # Environment variables
430
430
  ├── .env.example
431
431
  ├── .claude/
package/dist/index.js CHANGED
@@ -1123,6 +1123,7 @@ function formatBytes(n) {
1123
1123
  import fs4 from "fs";
1124
1124
  import path4 from "path";
1125
1125
  import { input as input2, select } from "@inquirer/prompts";
1126
+ import open from "open";
1126
1127
  var promptTheme = {
1127
1128
  prefix: { idle: blue("?"), done: blue("\u2714") },
1128
1129
  style: {
@@ -1447,7 +1448,30 @@ async function resendSetup(cwd, opts = {}) {
1447
1448
  apiKey = globalDefaults.api_key;
1448
1449
  fromEmail = globalDefaults.from_email;
1449
1450
  } else {
1450
- console.log(` ${dim("Get your API key at:")} ${blue("https://resend.com/api-keys")}`);
1451
+ const apiKeysUrl = "https://resend.com/api-keys";
1452
+ if (!hasExistingApiKey) {
1453
+ const hasKeyAtHand = await selectYesNo({
1454
+ message: "Do you have a Resend API key at hand?",
1455
+ default: true,
1456
+ yesLabel: "Yes \u2014 I'll paste it in the next step",
1457
+ noLabel: "No, I need to create one \u2014 open the Resend dashboard",
1458
+ theme: promptTheme
1459
+ });
1460
+ console.log();
1461
+ if (hasKeyAtHand) {
1462
+ console.log(` ${dim("Get your API key at:")} ${blue(apiKeysUrl)}`);
1463
+ } else {
1464
+ try {
1465
+ await open(apiKeysUrl);
1466
+ console.log(` ${blue("\u25CF")} Opened ${blue(apiKeysUrl)}`);
1467
+ } catch {
1468
+ console.log(` ${amber("Could not open browser.")} Visit: ${blue(apiKeysUrl)}`);
1469
+ }
1470
+ console.log(` ${dim("Create a key in the dashboard, then paste it below.")}`);
1471
+ }
1472
+ } else {
1473
+ console.log(` ${dim("Get your API key at:")} ${blue(apiKeysUrl)}`);
1474
+ }
1451
1475
  const rawKey = (await input2({
1452
1476
  message: hasExistingApiKey ? "Resend API key (press Enter to keep current):" : "Resend API key:",
1453
1477
  theme: apiKeyTheme,
@@ -5455,6 +5479,37 @@ DROP TRIGGER IF EXISTS trg_auth_users_new_user ON auth.users;
5455
5479
  CREATE TRIGGER trg_auth_users_new_user
5456
5480
  AFTER INSERT ON auth.users
5457
5481
  FOR EACH ROW EXECUTE FUNCTION public._internal_admin_handle_new_user();
5482
+
5483
+ -- supabase_auth_admin (the GoTrue auth service role) needs USAGE on the
5484
+ -- public schema to execute the _hook_* functions it calls during signup and
5485
+ -- email flows. Supabase pre-grants public USAGE to postgres/anon/
5486
+ -- authenticated/service_role but NOT to supabase_auth_admin, and the regen
5487
+ -- script's platform-default strip drops it from pg-delta's plan \u2014 so
5488
+ -- re-assert it here. Without it a migration-built DB diverges from a
5489
+ -- db-apply-built one. Idempotent.
5490
+ GRANT USAGE ON SCHEMA public TO supabase_auth_admin;
5491
+
5492
+ -- Lock down the api._admin_* SECURITY DEFINER functions to service_role.
5493
+ --
5494
+ -- These bypass RLS and have no auth_verify_access guard \u2014 they're meant to
5495
+ -- be called only by trusted server-side code (edge functions via the
5496
+ -- service role). They live in the api schema, which IS exposed via
5497
+ -- PostgREST, so a stray client grant is directly reachable.
5498
+ --
5499
+ -- WHY THIS IS HERE AND NOT IN THE pg-delta PLAN: Postgres grants EXECUTE to
5500
+ -- PUBLIC by default on every new function. The declarative schemas
5501
+ -- (_admin_queues.sql) revoke that with an explicit
5502
+ -- "REVOKE ALL ... FROM PUBLIC, anon, authenticated", but pg-delta's plan
5503
+ -- does NOT model the built-in PUBLIC default grant \u2014 it only emits
5504
+ -- "REVOKE ... FROM authenticated" and leaves the implicit PUBLIC grant in
5505
+ -- place. anon/authenticated are members of PUBLIC, so without this block a
5506
+ -- migration-built DB (fresh prod via db push) would leave these privileged
5507
+ -- functions anon-callable, diverging from a db-apply-built dev DB. Re-assert
5508
+ -- the full revoke here so both paths converge. REVOKE is idempotent.
5509
+ REVOKE ALL ON FUNCTION api._admin_enqueue_task(text, jsonb, integer) FROM PUBLIC, anon, authenticated;
5510
+ REVOKE ALL ON FUNCTION api._admin_queue_read(integer, integer) FROM PUBLIC, anon, authenticated;
5511
+ REVOKE ALL ON FUNCTION api._admin_queue_delete(bigint) FROM PUBLIC, anon, authenticated;
5512
+ REVOKE ALL ON FUNCTION api._admin_queue_archive(bigint) FROM PUBLIC, anon, authenticated;
5458
5513
  `;
5459
5514
 
5460
5515
  // src/template/supabase/functions/_shared/responses.ts
@@ -6439,7 +6494,7 @@ async function countTargetUserTables(projectRef) {
6439
6494
  }
6440
6495
  }
6441
6496
 
6442
- // src/claude-md.ts
6497
+ // src/agents-md.ts
6443
6498
  import fs8 from "fs";
6444
6499
  import path8 from "path";
6445
6500
  var MARKER_START = "<!-- agentlink:config:start -->";
@@ -6555,8 +6610,8 @@ Run \`supabase status\` to get the local API URL, DB URL, publishable key, and s
6555
6610
  return `${MARKER_START}
6556
6611
  ${content}${MARKER_END}`;
6557
6612
  }
6558
- function writeClaudeMd(options) {
6559
- const filePath = path8.join(options.projectDir, "CLAUDE.md");
6613
+ function writeAgentsMd(options) {
6614
+ const filePath = path8.join(options.projectDir, "AGENTS.md");
6560
6615
  const section = generateSection(options);
6561
6616
  if (!fs8.existsSync(filePath)) {
6562
6617
  fs8.writeFileSync(filePath, section + "\n");
@@ -6741,7 +6796,7 @@ function writeBareManifest(cwd) {
6741
6796
  // `mode: "bare"` is diagnostic only — `setDefaultEnvironment` in
6742
6797
  // manifest.ts will overwrite it to "cloud" on the first `env add dev`.
6743
6798
  // The DURABLE flag is `bare: true` below: it's orthogonal to mode and
6744
- // survives every env mutation, so downstream checks (CLAUDE.md guards,
6799
+ // survives every env mutation, so downstream checks (AGENTS.md guards,
6745
6800
  // envDeploy sequence) can reliably detect "this project isn't
6746
6801
  // scaffolded" regardless of what `mode` says right now.
6747
6802
  mode: "bare",
@@ -6962,14 +7017,14 @@ async function envAdd(name, cwd, opts = {}) {
6962
7017
  setDefaultEnvironment(cwd, name);
6963
7018
  console.log(` ${blue("\u2714")} Default environment set to "${name}"`);
6964
7019
  if (!bare) {
6965
- writeClaudeMd({
7020
+ writeAgentsMd({
6966
7021
  projectDir: cwd,
6967
7022
  mode: "cloud",
6968
7023
  projectRef: env2.projectRef,
6969
7024
  region: env2.region,
6970
7025
  envName: name
6971
7026
  });
6972
- console.log(` ${blue("\u2714")} CLAUDE.md regenerated for "${name}"`);
7027
+ console.log(` ${blue("\u2714")} AGENTS.md regenerated for "${name}"`);
6973
7028
  }
6974
7029
  }
6975
7030
  let shouldDeploy;
@@ -7423,10 +7478,10 @@ async function envUse(name, cwd) {
7423
7478
  const updatedManifest = readManifest(cwd);
7424
7479
  if (!isBareManifest(updatedManifest)) {
7425
7480
  if (name === "local") {
7426
- writeClaudeMd({ projectDir: cwd, mode: "local" });
7481
+ writeAgentsMd({ projectDir: cwd, mode: "local" });
7427
7482
  } else {
7428
7483
  const env2 = updatedManifest.cloud.environments[name];
7429
- writeClaudeMd({
7484
+ writeAgentsMd({
7430
7485
  projectDir: cwd,
7431
7486
  mode: "cloud",
7432
7487
  projectRef: env2.projectRef,
@@ -7434,7 +7489,7 @@ async function envUse(name, cwd) {
7434
7489
  envName: name
7435
7490
  });
7436
7491
  }
7437
- console.log(` ${blue("\u2714")} CLAUDE.md regenerated for ${name} mode`);
7492
+ console.log(` ${blue("\u2714")} AGENTS.md regenerated for ${name} mode`);
7438
7493
  }
7439
7494
  console.log(`
7440
7495
  ${blue(isRefresh ? "Refreshed" : "Switched to")} ${bold(name)}`);
@@ -7646,17 +7701,17 @@ async function envRelink(name, cwd, opts = {}) {
7646
7701
  });
7647
7702
  console.log(` ${blue("\u2714")} .env.local updated (pooler URL from API)`);
7648
7703
  if (!isBareManifest(manifest)) {
7649
- writeClaudeMd({
7704
+ writeAgentsMd({
7650
7705
  projectDir: cwd,
7651
7706
  mode: "cloud",
7652
7707
  projectRef: env2.projectRef,
7653
7708
  region: env2.region,
7654
7709
  envName: name
7655
7710
  });
7656
- console.log(` ${blue("\u2714")} CLAUDE.md updated`);
7711
+ console.log(` ${blue("\u2714")} AGENTS.md updated`);
7657
7712
  }
7658
7713
  } else {
7659
- console.log(` ${dim(`.env.local and CLAUDE.md left on the active env \u2014 prod never auto-activates (use \`env use prod\` to switch).`)}`);
7714
+ console.log(` ${dim(`.env.local and AGENTS.md left on the active env \u2014 prod never auto-activates (use \`env use prod\` to switch).`)}`);
7660
7715
  }
7661
7716
  if (opts.deploy === false) {
7662
7717
  const deployCmd = `npx agentlink-sh@latest env deploy ${name}`;
@@ -11008,7 +11063,7 @@ import fs18 from "fs";
11008
11063
  import path18 from "path";
11009
11064
  import { fileURLToPath as fileURLToPath3 } from "url";
11010
11065
  import { input as input6, select as select5 } from "@inquirer/prompts";
11011
- import open from "open";
11066
+ import open2 from "open";
11012
11067
 
11013
11068
  // src/banner.ts
11014
11069
  var TITLE = [
@@ -11237,40 +11292,6 @@ function ensureClaudeSettings(projectDir, isCloud) {
11237
11292
  fs14.writeFileSync(filePath, JSON.stringify(merged, null, 2) + "\n");
11238
11293
  }
11239
11294
 
11240
- // src/migrate.ts
11241
- var BASELINE_MIGRATIONS = [
11242
- {
11243
- version: "20200101000000",
11244
- name: "initial_agentlink_bootstrap"
11245
- },
11246
- {
11247
- version: "20200101000001",
11248
- name: "initial_agentlink_scaffold"
11249
- }
11250
- ];
11251
- var BASELINE_MIGRATION = BASELINE_MIGRATIONS[1];
11252
- var MARK_BASELINE_MIGRATIONS_APPLIED_SQL = `
11253
- -- Ensure the schema_migrations ledger exists. On a freshly-created
11254
- -- Supabase project this is usually pre-created by the platform, but
11255
- -- we don't depend on that \u2014 IF NOT EXISTS makes both lines safe to
11256
- -- re-run.
11257
- CREATE SCHEMA IF NOT EXISTS supabase_migrations;
11258
-
11259
- CREATE TABLE IF NOT EXISTS supabase_migrations.schema_migrations (
11260
- version text PRIMARY KEY,
11261
- statements text[],
11262
- name text
11263
- );
11264
-
11265
- -- Mark the agentlink baselines as applied. The actual SQL has already
11266
- -- been run via pg-delta declarative apply (in scaffold) or will be run
11267
- -- via \`supabase db push\` (in env add prod). These rows only exist to
11268
- -- tell future \`db push\` calls "you don't need to apply these again."
11269
- INSERT INTO supabase_migrations.schema_migrations (version, name) VALUES
11270
- ${BASELINE_MIGRATIONS.map((m) => ` ('${m.version}', '${m.name}')`).join(",\n")}
11271
- ON CONFLICT (version) DO NOTHING;
11272
- `;
11273
-
11274
11295
  // src/scaffold.ts
11275
11296
  import { fileURLToPath as fileURLToPath2 } from "url";
11276
11297
  var __dirname = path15.dirname(fileURLToPath2(import.meta.url));
@@ -11608,8 +11629,6 @@ SUPABASE_DB_PASSWORD=${ctx.dbPass}
11608
11629
  }
11609
11630
  spinner.text = "Setting up database \u2014 Waiting for Postgres to accept connections";
11610
11631
  await waitForDatabaseReady(ctx.projectRef, spinner);
11611
- spinner.text = "Setting up database \u2014 Applying schemas (extensions, isolation, auth hooks, triggers)";
11612
- await dbApply({ cwd: projectDir, dbUrl: ctx.dbUrl, skipTypes: true, quiet: true });
11613
11632
  for (const [relPath, content] of Object.entries(TEMPLATE_FILES)) {
11614
11633
  if (!relPath.startsWith("migrations/")) continue;
11615
11634
  const full = path15.join(projectDir, "supabase", relPath);
@@ -11618,8 +11637,13 @@ SUPABASE_DB_PASSWORD=${ctx.dbPass}
11618
11637
  fs15.writeFileSync(full, content);
11619
11638
  }
11620
11639
  }
11621
- spinner.text = "Setting up database \u2014 Marking baseline migrations applied";
11622
- await runCloudSQL(MARK_BASELINE_MIGRATIONS_APPLIED_SQL, ctx.projectRef);
11640
+ spinner.text = "Setting up database \u2014 Applying baseline migrations";
11641
+ await runCommand(
11642
+ `${sb()} db push --db-url ${JSON.stringify(ctx.dbUrl)}`,
11643
+ projectDir,
11644
+ void 0,
11645
+ cloudEnv
11646
+ );
11623
11647
  spinner.text = "Setting up database \u2014 Storing API keys in Vault";
11624
11648
  await runCloudSQL(cloudSeedSQL(ctx.apiUrl, ctx.publishableKey, ctx.secretKey), ctx.projectRef);
11625
11649
  spinner.text = "Setting up database \u2014 Deploying edge functions (internal-send-auth-email, internal-queue-worker, internal-invite-member)";
@@ -11634,8 +11658,6 @@ SUPABASE_DB_PASSWORD=${ctx.dbPass}
11634
11658
  spinner.text = "Setting up database \u2014 Enabling auth hooks and signup settings";
11635
11659
  await updateAuthConfig(ctx.projectRef, { ...AUTH_CONFIG, ...AUTH_CONFIG_DEV_OVERRIDES });
11636
11660
  } else {
11637
- spinner.text = "Setting up database \u2014 Applying schemas (extensions, isolation, auth hooks, triggers)";
11638
- await dbApply({ cwd: projectDir, dbUrl: ctx.dbUrl, skipTypes: true, quiet: true });
11639
11661
  for (const [relPath, content] of Object.entries(TEMPLATE_FILES)) {
11640
11662
  if (!relPath.startsWith("migrations/")) continue;
11641
11663
  const full = path15.join(projectDir, "supabase", relPath);
@@ -11644,8 +11666,11 @@ SUPABASE_DB_PASSWORD=${ctx.dbPass}
11644
11666
  fs15.writeFileSync(full, content);
11645
11667
  }
11646
11668
  }
11647
- spinner.text = "Setting up database \u2014 Marking baseline migrations applied";
11648
- await runSQL(MARK_BASELINE_MIGRATIONS_APPLIED_SQL, ctx.dbUrl);
11669
+ spinner.text = "Setting up database \u2014 Applying baseline migrations";
11670
+ await runCommand(
11671
+ `${sb()} db push --db-url ${JSON.stringify(ctx.dbUrl)}`,
11672
+ projectDir
11673
+ );
11649
11674
  spinner.text = "Setting up database \u2014 Storing API keys in Vault";
11650
11675
  await runSQL(seedSQL(ctx.publishableKey, ctx.secretKey), ctx.dbUrl);
11651
11676
  }
@@ -11756,7 +11781,7 @@ SUPABASE_DB_PASSWORD=${ctx.dbPass}
11756
11781
  await trackedStep("configure-claude", "Configuring Claude Code", async () => {
11757
11782
  ensureClaudeSettings(projectDir, !!options.cloud || !!options.skipEnv);
11758
11783
  const mode2 = options.skipEnv ? "pending-env" : options.cloud ? "cloud" : "local";
11759
- writeClaudeMd({
11784
+ writeAgentsMd({
11760
11785
  projectDir,
11761
11786
  mode: mode2,
11762
11787
  projectRef: ctx.projectRef,
@@ -12061,8 +12086,8 @@ ${red("Error:")} Supabase is not running.
12061
12086
  projectDir
12062
12087
  );
12063
12088
  });
12064
- await step("Updating CLAUDE.md", async () => {
12065
- writeClaudeMd({
12089
+ await step("Updating AGENTS.md", async () => {
12090
+ writeAgentsMd({
12066
12091
  projectDir,
12067
12092
  mode: isCloud ? "cloud" : "local",
12068
12093
  projectRef: env2?.projectRef,
@@ -12259,7 +12284,7 @@ async function runDryRun(projectDir, isCloud) {
12259
12284
  console.log(`
12260
12285
  ${bold("Side effects")} ${dim("(skipped in dry run)")}`);
12261
12286
  console.log(` ${dim("\u2022")} update agent plugin + companion skills`);
12262
- console.log(` ${dim("\u2022")} rewrite .claude/CLAUDE.md, .claude/settings.json`);
12287
+ console.log(` ${dim("\u2022")} rewrite AGENTS.md, .claude/settings.json`);
12263
12288
  console.log(` ${dim("\u2022")} apply ${sqlCount} SQL schema file(s) to ${isCloud ? "cloud DB" : "local DB"}`);
12264
12289
  if (isCloud) {
12265
12290
  console.log(` ${dim("\u2022")} update PostgREST config + auth config`);
@@ -12293,7 +12318,7 @@ async function printUpdateSummary(projectDir, allowDirty) {
12293
12318
  // config.toml, .gitignore
12294
12319
  ".claude/": [],
12295
12320
  "root": []
12296
- // agentlink.json, CLAUDE.md, .env.local, etc.
12321
+ // agentlink.json, AGENTS.md, .env.local, etc.
12297
12322
  };
12298
12323
  for (const file of changed) {
12299
12324
  if (file.startsWith("supabase/schemas/")) groups["supabase/schemas/"].push(file);
@@ -12565,7 +12590,7 @@ async function runFinalActionPicker(summary, relPath) {
12565
12590
  if (action === "exit") return;
12566
12591
  if (action === "dashboard") {
12567
12592
  try {
12568
- await open(dashboardUrl);
12593
+ await open2(dashboardUrl);
12569
12594
  console.log();
12570
12595
  console.log(` ${dim("Opened")} ${blue(dashboardUrl)}`);
12571
12596
  console.log();
@@ -13515,7 +13540,7 @@ env.command("add [name]").description("Add or re-add a cloud environment (dev or
13515
13540
  console.log();
13516
13541
  console.log(` ${bold("To use local development:")}`);
13517
13542
  console.log(` ${dim("supabase start")} ${dim("# bring up the local Docker stack")}`);
13518
- console.log(` ${dim("npx agentlink-sh@latest env use local")} ${dim("# point .env.local + CLAUDE.md at it")}`);
13543
+ console.log(` ${dim("npx agentlink-sh@latest env use local")} ${dim("# point .env.local + AGENTS.md at it")}`);
13519
13544
  console.log();
13520
13545
  console.log(` ${dim("Or scaffold a new project against local Docker:")} ${dim("npx agentlink-sh@latest <name> --local")}`);
13521
13546
  console.log();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentlink-sh",
3
- "version": "0.30.1",
3
+ "version": "0.32.0",
4
4
  "description": "CLI for building Supabase apps with AI agents",
5
5
  "bin": {
6
6
  "agentlink": "dist/index.js"
@@ -13,7 +13,8 @@
13
13
  "build": "tsup",
14
14
  "dev": "tsup --watch",
15
15
  "lint": "eslint src/",
16
- "regen-migrations": "tsx scripts/regen-baseline-migration.ts"
16
+ "regen-migrations": "tsx scripts/regen-baseline-migration.ts",
17
+ "check-migrations": "tsx scripts/check-migrations.ts"
17
18
  },
18
19
  "dependencies": {
19
20
  "@inquirer/prompts": "^8.3.0",