@agentlink.sh/cli 0.10.0 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@ Teach AI agents the right way to build on Supabase.
4
4
 
5
5
  [Agent Link](https://agentlink.sh) is an opinionated skill set for Claude Code — and this CLI is how you start a project with it. One command scaffolds a Supabase backend with schema isolation, RPC-first data access, row-level security, multi-tenancy, and edge functions already wired up — plus a frontend (React + Vite or Next.js) with auth, theming, and shadcn/ui ready to go. Your agent doesn't have to figure out the architecture — it's already there. From that foundation, five domain skills give it one clear path for every decision: how to structure tables, write RLS policies, expose APIs, handle background work, and connect a frontend. No ambiguity, no wrong turns. First-try results.
6
6
 
7
- ## Install
7
+ ## Install & Run
8
8
 
9
9
  ### macOS
10
10
 
@@ -24,16 +24,16 @@ wget -qO- https://agentlink.sh/install | sh
24
24
  irm https://agentlink.sh/install.ps1 | iex
25
25
  ```
26
26
 
27
- ### npm
27
+ ### npx (any platform with Node.js 18+)
28
28
 
29
29
  ```bash
30
- npm install -g @agentlinksh/cli
30
+ npx @agentlink.sh/cli@latest
31
31
  ```
32
32
 
33
33
  Or run directly with npx (requires Node.js 18+):
34
34
 
35
35
  ```bash
36
- npx @agentlinksh/cli@latest
36
+ npx @agentlink.sh/cli@latest
37
37
  ```
38
38
 
39
39
  The install scripts automatically detect your OS, install Node.js if needed, and set up the CLI globally.
@@ -181,7 +181,7 @@ async function ensureAccessToken(nonInteractive, projectDir) {
181
181
  }
182
182
  if (creds.oauth.refresh_token) {
183
183
  try {
184
- const { refreshOAuthToken } = await import("./oauth-J4X62CY4.js");
184
+ const { refreshOAuthToken } = await import("./oauth-VTSNCG7U.js");
185
185
  const tokens = await refreshOAuthToken(creds.oauth.refresh_token);
186
186
  const updated = {
187
187
  ...creds,
@@ -230,7 +230,7 @@ async function ensureAccessToken(nonInteractive, projectDir) {
230
230
  ]
231
231
  });
232
232
  if (method === "oauth") {
233
- const { oauthLogin } = await import("./oauth-J4X62CY4.js");
233
+ const { oauthLogin } = await import("./oauth-VTSNCG7U.js");
234
234
  const tokens = await oauthLogin();
235
235
  process.env.SUPABASE_ACCESS_TOKEN = tokens.access_token;
236
236
  saveCredentials({
@@ -20,7 +20,7 @@ import {
20
20
  updateAuthConfig,
21
21
  updatePostgrestConfig,
22
22
  waitForProjectReady
23
- } from "./chunk-DOWH47I5.js";
23
+ } from "./chunk-2CNK2BQY.js";
24
24
  import "./chunk-G3HVUCWY.js";
25
25
  export {
26
26
  createProject,
package/dist/index.js CHANGED
@@ -26,7 +26,7 @@ import {
26
26
  updatePostgrestConfig,
27
27
  validateProjectName,
28
28
  waitForProjectReady
29
- } from "./chunk-DOWH47I5.js";
29
+ } from "./chunk-2CNK2BQY.js";
30
30
  import {
31
31
  SKILLS_VERSION,
32
32
  SUPPORTED_SUPABASE_CLI,
@@ -1671,7 +1671,7 @@ async function dbApply(options) {
1671
1671
  console.warn(`Warning: type generation failed \u2014 ${err.message}`);
1672
1672
  }
1673
1673
  }
1674
- console.log("\nDone. Run 'npx @agentlinksh/cli check' to verify.");
1674
+ console.log("\nDone. Run 'npx @agentlink.sh/cli check' to verify.");
1675
1675
  }
1676
1676
  async function dbMigrate(name, options) {
1677
1677
  const dbUrl = await resolveDbUrl(options.cwd, options.dbUrl);
@@ -1928,7 +1928,7 @@ async function resolveEnvironments(cwd, opts) {
1928
1928
  if (!manifest.cloud?.environments?.[targetName]) {
1929
1929
  throw new Error(
1930
1930
  `Target environment "${targetName}" not found.
1931
- Run: npx @agentlinksh/cli@latest env add ${targetName}`
1931
+ Run: npx @agentlink.sh/cli@latest env add ${targetName}`
1932
1932
  );
1933
1933
  }
1934
1934
  await ensureAccessToken(opts.ci, cwd);
@@ -2138,16 +2138,16 @@ This is a **cloud** Supabase project.
2138
2138
 
2139
2139
  | Task | Command |
2140
2140
  |------|---------|
2141
- | Apply schemas | \`npx @agentlinksh/cli@latest db apply\` |
2142
- | Generate migration | \`npx @agentlinksh/cli@latest db migrate name\` then \`supabase db push\` |
2141
+ | Apply schemas | \`npx @agentlink.sh/cli@latest db apply\` |
2142
+ | Generate migration | \`npx @agentlink.sh/cli@latest db migrate name\` then \`supabase db push\` |
2143
2143
  | Push migrations | \`supabase db push\` |
2144
- | Generate types | \`npx @agentlinksh/cli@latest db types\` |
2145
- | Run SQL | \`npx @agentlinksh/cli@latest db sql "SELECT ..."\` |
2144
+ | Generate types | \`npx @agentlink.sh/cli@latest db types\` |
2145
+ | Run SQL | \`npx @agentlink.sh/cli@latest db sql "SELECT ..."\` |
2146
2146
  | Deploy edge functions | \`supabase functions deploy\` |
2147
2147
  | Set secrets | \`supabase secrets set KEY=value\` |
2148
- | Deploy to production | \`npx @agentlinksh/cli@latest deploy\` |
2149
- | Switch environment | \`npx @agentlinksh/cli@latest env use <name>\` |
2150
- | List environments | \`npx @agentlinksh/cli@latest env list\` |
2148
+ | Deploy to production | \`npx @agentlink.sh/cli@latest deploy\` |
2149
+ | Switch environment | \`npx @agentlink.sh/cli@latest env use <name>\` |
2150
+ | List environments | \`npx @agentlink.sh/cli@latest env list\` |
2151
2151
 
2152
2152
  ## Important
2153
2153
 
@@ -2169,14 +2169,14 @@ This is a **local** Supabase project running in Docker.
2169
2169
  | Task | Command |
2170
2170
  |------|---------|
2171
2171
  | Start Supabase | \`supabase start\` |
2172
- | Apply schemas | \`npx @agentlinksh/cli@latest db apply\` |
2173
- | Generate migration | \`npx @agentlinksh/cli@latest db migrate name\` |
2174
- | Generate types | \`npx @agentlinksh/cli@latest db types\` |
2175
- | Run SQL | \`npx @agentlinksh/cli@latest db sql "SELECT ..."\` |
2172
+ | Apply schemas | \`npx @agentlink.sh/cli@latest db apply\` |
2173
+ | Generate migration | \`npx @agentlink.sh/cli@latest db migrate name\` |
2174
+ | Generate types | \`npx @agentlink.sh/cli@latest db types\` |
2175
+ | Run SQL | \`npx @agentlink.sh/cli@latest db sql "SELECT ..."\` |
2176
2176
  | Serve edge functions | \`supabase functions serve\` |
2177
- | Deploy to production | \`npx @agentlinksh/cli@latest deploy\` |
2178
- | Switch environment | \`npx @agentlinksh/cli@latest env use <name>\` |
2179
- | List environments | \`npx @agentlinksh/cli@latest env list\` |
2177
+ | Deploy to production | \`npx @agentlink.sh/cli@latest deploy\` |
2178
+ | Switch environment | \`npx @agentlink.sh/cli@latest env use <name>\` |
2179
+ | List environments | \`npx @agentlink.sh/cli@latest env list\` |
2180
2180
 
2181
2181
  ## Finding connection details
2182
2182
 
@@ -2315,7 +2315,7 @@ async function envAdd(name, cwd, opts = {}) {
2315
2315
  console.log(`
2316
2316
  ${blue("Done.")} Environment "${name}" is ready.`);
2317
2317
  if (!name.startsWith("dev") && name !== "staging" && name !== "local") {
2318
- console.log(` Deploy with: ${dim("npx @agentlinksh/cli@latest deploy --env " + name)}`);
2318
+ console.log(` Deploy with: ${dim("npx @agentlink.sh/cli@latest deploy --env " + name)}`);
2319
2319
  }
2320
2320
  }
2321
2321
  async function promptForPassword(nonInteractive) {
@@ -2361,7 +2361,7 @@ async function connectExistingProject() {
2361
2361
  };
2362
2362
  }
2363
2363
  async function createNewProject(envName, opts) {
2364
- const { listOrganizations: listOrganizations2, listRegions: listRegions2 } = await import("./cloud-N7CILHLD.js");
2364
+ const { listOrganizations: listOrganizations2, listRegions: listRegions2 } = await import("./cloud-2AWCJAG5.js");
2365
2365
  const orgs = await listOrganizations2();
2366
2366
  if (orgs.length === 0) {
2367
2367
  throw new Error("No Supabase organizations found. Create one at https://supabase.com/dashboard.");
@@ -2541,7 +2541,7 @@ jobs:
2541
2541
  - run: npm ci
2542
2542
 
2543
2543
  - name: Deploy to ${envName}
2544
- run: npx @agentlinksh/cli@latest deploy --ci --env ${envName}
2544
+ run: npx @agentlink.sh/cli@latest deploy --ci --env ${envName}
2545
2545
  env:
2546
2546
  SUPABASE_ACCESS_TOKEN: \${{ secrets.SUPABASE_ACCESS_TOKEN }}
2547
2547
  SUPABASE_DB_PASSWORD: \${{ secrets.SUPABASE_DB_PASSWORD }}
@@ -3664,12 +3664,14 @@ async function scaffold(options) {
3664
3664
  "pgdelta binary not found. Run `npm install` in the CLI package first."
3665
3665
  );
3666
3666
  }
3667
- if (checkCommand("psql")) {
3668
- spinner.text = "Checking prerequisites \u2014 PostgreSQL client installed";
3669
- } else {
3670
- throw new Error(
3671
- "psql is required but not installed.\n macOS: brew install libpq && brew link --force libpq\n Ubuntu: sudo apt install postgresql-client\n See: https://www.postgresql.org/download/"
3672
- );
3667
+ if (!options.cloud) {
3668
+ if (checkCommand("psql")) {
3669
+ spinner.text = "Checking prerequisites \u2014 PostgreSQL client installed";
3670
+ } else {
3671
+ throw new Error(
3672
+ "psql is required for local mode but not installed.\n macOS: brew install libpq && brew link --force libpq\n Ubuntu: sudo apt install postgresql-client\n Or remove --local to use Supabase Cloud (no psql needed).\n See: https://www.postgresql.org/download/"
3673
+ );
3674
+ }
3673
3675
  }
3674
3676
  });
3675
3677
  console.log();
@@ -4286,6 +4288,60 @@ ${red("Error:")} Supabase is not running.
4286
4288
  // src/wizard.ts
4287
4289
  var __dirname2 = path15.dirname(fileURLToPath2(import.meta.url));
4288
4290
  var pkg2 = JSON.parse(fs15.readFileSync(path15.join(__dirname2, "..", "package.json"), "utf-8"));
4291
+ async function checkForCliUpdate(autoYes) {
4292
+ try {
4293
+ const latest = execSync(`npm view @agentlink.sh/cli version 2>/dev/null`, { encoding: "utf-8" }).trim();
4294
+ if (!latest || latest === pkg2.version) return;
4295
+ const current = pkg2.version.split(".").map(Number);
4296
+ const remote = latest.split(".").map(Number);
4297
+ const isNewer = remote[0] > current[0] || remote[0] === current[0] && remote[1] > current[1] || remote[0] === current[0] && remote[1] === current[1] && remote[2] > current[2];
4298
+ if (!isNewer) return;
4299
+ console.log(` ${amber("\u25B2")} New version available: ${dim(pkg2.version)} \u2192 ${bold(latest)}`);
4300
+ const shouldUpdate = autoYes || await confirm5({
4301
+ message: "Update now?",
4302
+ default: true,
4303
+ theme: theme5
4304
+ });
4305
+ if (!shouldUpdate) {
4306
+ console.log();
4307
+ return;
4308
+ }
4309
+ console.log(` ${blue("\u25CF")} Updating...`);
4310
+ execSync("npm install -g @agentlink.sh/cli@latest", { stdio: "ignore" });
4311
+ console.log(` ${blue("\u2714")} Updated to v${latest}`);
4312
+ console.log();
4313
+ const args = process.argv.slice(2).map((a) => JSON.stringify(a)).join(" ");
4314
+ execSync(`agentlink ${args}`, { stdio: "inherit", cwd: process.cwd() });
4315
+ process.exit(0);
4316
+ } catch {
4317
+ }
4318
+ }
4319
+ async function checkForPluginUpdate(autoYes) {
4320
+ try {
4321
+ const listOutput = execSync("claude plugin list --json 2>/dev/null", { encoding: "utf-8" });
4322
+ const plugins = JSON.parse(listOutput);
4323
+ const installed = plugins.find((p) => p.id === "link@agentlink");
4324
+ if (!installed) return;
4325
+ const availableOutput = execSync("claude plugin list --json --available 2>/dev/null", { encoding: "utf-8" });
4326
+ const available = JSON.parse(availableOutput);
4327
+ const latest = available.find((p) => p.id === "link@agentlink");
4328
+ if (!latest || latest.version === installed.version) return;
4329
+ console.log(` ${amber("\u25B2")} Plugin update available: ${dim(installed.version)} \u2192 ${bold(latest.version)}`);
4330
+ const shouldUpdate = autoYes || await confirm5({
4331
+ message: "Update Agent Link plugin?",
4332
+ default: true,
4333
+ theme: theme5
4334
+ });
4335
+ if (!shouldUpdate) {
4336
+ console.log();
4337
+ return;
4338
+ }
4339
+ execSync(`claude plugin update link@agentlink --scope ${installed.scope}`, { stdio: "ignore" });
4340
+ console.log(` ${blue("\u2714")} Plugin updated to v${latest.version}`);
4341
+ console.log();
4342
+ } catch {
4343
+ }
4344
+ }
4289
4345
  var theme5 = {
4290
4346
  prefix: { idle: blue("?"), done: blue("\u2714") },
4291
4347
  style: {
@@ -4404,6 +4460,10 @@ async function wizard(initialName, initialPrompt, opts = {}) {
4404
4460
  printBanner();
4405
4461
  console.log(dim(` v${pkg2.version}`));
4406
4462
  console.log();
4463
+ if (!nonInteractive) {
4464
+ await checkForCliUpdate(autoYes);
4465
+ await checkForPluginUpdate(autoYes);
4466
+ }
4407
4467
  if (!opts.resume) {
4408
4468
  let sessions;
4409
4469
  if (initialName && initialName !== ".") {
@@ -4561,14 +4621,34 @@ Run without --resume to start fresh.`
4561
4621
  }
4562
4622
  if (!checkCommand("claude")) {
4563
4623
  console.log(amber(" Installing Claude Code..."));
4564
- await runCommand("npm install -g @anthropic-ai/claude-code");
4624
+ try {
4625
+ await runCommand("npm install -g @anthropic-ai/claude-code");
4626
+ } catch {
4627
+ await runCommand("sudo npm install -g @anthropic-ai/claude-code");
4628
+ }
4565
4629
  console.log(blue(" \u2714 Claude Code installed."));
4566
4630
  }
4567
4631
  const authStatus = await runCommand("claude auth status").catch(() => "");
4568
4632
  const isLoggedIn = authStatus.includes('"loggedIn": true') || authStatus.includes('"loggedIn":true');
4569
4633
  if (!isLoggedIn) {
4570
4634
  console.log(amber(" Claude Code requires authentication. Opening login..."));
4571
- execSync("claude auth login", { stdio: "inherit" });
4635
+ try {
4636
+ execSync("claude auth login", { stdio: "inherit" });
4637
+ } catch {
4638
+ throw new Error(
4639
+ `Claude Code login failed.
4640
+ Run ${dim("claude auth login")} manually, then retry with:
4641
+ ${dim("npx @agentlink.sh/cli@latest")}`
4642
+ );
4643
+ }
4644
+ const recheck = await runCommand("claude auth status").catch(() => "");
4645
+ if (!recheck.includes('"loggedIn": true') && !recheck.includes('"loggedIn":true')) {
4646
+ throw new Error(
4647
+ `Claude Code authentication did not complete.
4648
+ Run ${dim("claude auth login")} manually, then retry with:
4649
+ ${dim("npx @agentlink.sh/cli@latest")}`
4650
+ );
4651
+ }
4572
4652
  }
4573
4653
  const hasSupabase = fs15.existsSync(path15.join(process.cwd(), "supabase", "config.toml"));
4574
4654
  const hasPackageJson = fs15.existsSync(path15.join(process.cwd(), "package.json"));
@@ -4819,8 +4899,8 @@ program.command("info [name]").description("Show documentation about AgentLink c
4819
4899
  });
4820
4900
  program.command("login").description("Authenticate with Supabase via OAuth (opens browser)").action(async () => {
4821
4901
  try {
4822
- const { oauthLogin } = await import("./oauth-J4X62CY4.js");
4823
- const { saveCredentials: saveCredentials2, loadCredentials: loadCredentials2, credentialsPath: credentialsPath2 } = await import("./cloud-N7CILHLD.js");
4902
+ const { oauthLogin } = await import("./oauth-VTSNCG7U.js");
4903
+ const { saveCredentials: saveCredentials2, loadCredentials: loadCredentials2, credentialsPath: credentialsPath2 } = await import("./cloud-2AWCJAG5.js");
4824
4904
  console.log();
4825
4905
  const tokens = await oauthLogin();
4826
4906
  saveCredentials2({
@@ -4,6 +4,7 @@ import {
4
4
  OAUTH_CLIENT_SECRET,
5
5
  amber,
6
6
  blue,
7
+ bold,
7
8
  dim
8
9
  } from "./chunk-G3HVUCWY.js";
9
10
 
@@ -12,11 +13,21 @@ import { createHash, randomBytes } from "crypto";
12
13
  import { createServer } from "http";
13
14
  import { URL } from "url";
14
15
  import { execSync } from "child_process";
16
+ import { select } from "@inquirer/prompts";
17
+ var theme = {
18
+ prefix: { idle: blue("?"), done: blue("\u2714") },
19
+ style: {
20
+ answer: (text) => amber(text),
21
+ message: (text) => bold(text),
22
+ highlight: (text) => blue(text),
23
+ key: (text) => blue(bold(`<${text}>`))
24
+ }
25
+ };
15
26
  var AUTHORIZE_URL = "https://api.supabase.com/v1/oauth/authorize";
16
27
  var TOKEN_URL = "https://api.supabase.com/v1/oauth/token";
17
28
  var PORT_RANGE_START = 54320;
18
29
  var PORT_RANGE_END = 54330;
19
- var TIMEOUT_MS = 12e4;
30
+ var CHECK_IN_MS = 3e4;
20
31
  function generateCodeVerifier() {
21
32
  return randomBytes(64).toString("base64url");
22
33
  }
@@ -87,25 +98,15 @@ function openBrowser(url) {
87
98
  console.log(` ${dim(url)}`);
88
99
  }
89
100
  }
90
- async function oauthLogin() {
91
- const codeVerifier = generateCodeVerifier();
92
- const codeChallenge = generateCodeChallenge(codeVerifier);
93
- const state = randomBytes(16).toString("hex");
94
- const port = await findOpenPort();
95
- const redirectUri = `http://localhost:${port}/callback`;
96
- const authorizeUrl = new URL(AUTHORIZE_URL);
97
- authorizeUrl.searchParams.set("client_id", OAUTH_CLIENT_ID);
98
- authorizeUrl.searchParams.set("redirect_uri", redirectUri);
99
- authorizeUrl.searchParams.set("response_type", "code");
100
- authorizeUrl.searchParams.set("code_challenge", codeChallenge);
101
- authorizeUrl.searchParams.set("code_challenge_method", "S256");
102
- authorizeUrl.searchParams.set("state", state);
103
- const code = await new Promise((resolve, reject) => {
104
- const timeout = setTimeout(() => {
105
- server.close();
106
- reject(new Error("OAuth login timed out. Try again."));
107
- }, TIMEOUT_MS);
108
- const server = createServer((req, res) => {
101
+ function waitForCallback(port, state, timeoutMs) {
102
+ let server;
103
+ let timer;
104
+ const promise = new Promise((resolve) => {
105
+ timer = setTimeout(() => {
106
+ server?.close();
107
+ resolve(null);
108
+ }, timeoutMs);
109
+ server = createServer((req, res) => {
109
110
  const url = new URL(req.url, `http://localhost:${port}`);
110
111
  if (url.pathname !== "/callback") {
111
112
  res.writeHead(404);
@@ -117,43 +118,93 @@ async function oauthLogin() {
117
118
  const desc = url.searchParams.get("error_description") ?? error;
118
119
  res.writeHead(400, { "Content-Type": "text/html" });
119
120
  res.end(ERROR_HTML(desc));
120
- clearTimeout(timeout);
121
+ clearTimeout(timer);
121
122
  server.close();
122
- reject(new Error(`OAuth error: ${desc}`));
123
+ console.log(` ${amber("\u25B2")} OAuth error: ${desc}`);
124
+ resolve(null);
123
125
  return;
124
126
  }
125
127
  const returnedState = url.searchParams.get("state");
126
128
  if (returnedState !== state) {
127
129
  res.writeHead(400, { "Content-Type": "text/html" });
128
130
  res.end(ERROR_HTML("State mismatch \u2014 possible CSRF attack."));
129
- clearTimeout(timeout);
131
+ clearTimeout(timer);
130
132
  server.close();
131
- reject(new Error("OAuth state mismatch."));
133
+ resolve(null);
132
134
  return;
133
135
  }
134
136
  const authCode = url.searchParams.get("code");
135
137
  if (!authCode) {
136
138
  res.writeHead(400, { "Content-Type": "text/html" });
137
139
  res.end(ERROR_HTML("No authorization code received."));
138
- clearTimeout(timeout);
140
+ clearTimeout(timer);
139
141
  server.close();
140
- reject(new Error("No authorization code in callback."));
142
+ resolve(null);
141
143
  return;
142
144
  }
143
145
  res.writeHead(200, { "Content-Type": "text/html" });
144
146
  res.end(SUCCESS_HTML);
145
- clearTimeout(timeout);
147
+ clearTimeout(timer);
146
148
  server.close();
147
149
  resolve(authCode);
148
150
  });
149
- server.listen(port, "127.0.0.1", () => {
150
- console.log(` ${blue("\u25CF")} Opening browser for Supabase login...`);
151
- openBrowser(authorizeUrl.toString());
152
- console.log(` ${dim("Waiting for authorization...")}`);
153
- console.log();
154
- });
151
+ server.listen(port, "127.0.0.1");
155
152
  });
156
- return exchangeCode(code, redirectUri, codeVerifier);
153
+ return {
154
+ promise,
155
+ cleanup: () => {
156
+ clearTimeout(timer);
157
+ server?.close();
158
+ }
159
+ };
160
+ }
161
+ async function oauthLogin() {
162
+ while (true) {
163
+ const codeVerifier = generateCodeVerifier();
164
+ const codeChallenge = generateCodeChallenge(codeVerifier);
165
+ const state = randomBytes(16).toString("hex");
166
+ const port = await findOpenPort();
167
+ const redirectUri = `http://localhost:${port}/callback`;
168
+ const authorizeUrl = new URL(AUTHORIZE_URL);
169
+ authorizeUrl.searchParams.set("client_id", OAUTH_CLIENT_ID);
170
+ authorizeUrl.searchParams.set("redirect_uri", redirectUri);
171
+ authorizeUrl.searchParams.set("response_type", "code");
172
+ authorizeUrl.searchParams.set("code_challenge", codeChallenge);
173
+ authorizeUrl.searchParams.set("code_challenge_method", "S256");
174
+ authorizeUrl.searchParams.set("state", state);
175
+ console.log(` ${blue("\u25CF")} Opening browser for Supabase login...`);
176
+ openBrowser(authorizeUrl.toString());
177
+ console.log(` ${dim("Waiting for authorization...")}`);
178
+ console.log();
179
+ let code = null;
180
+ const first = waitForCallback(port, state, CHECK_IN_MS);
181
+ code = await first.promise;
182
+ if (code) {
183
+ return exchangeCode(code, redirectUri, codeVerifier);
184
+ }
185
+ while (true) {
186
+ const action = await select({
187
+ message: "No response yet. What would you like to do?",
188
+ theme,
189
+ choices: [
190
+ { name: "Keep waiting (30s more)", value: "wait" },
191
+ { name: "Retry (open browser again)", value: "retry" },
192
+ { name: "Cancel", value: "cancel" }
193
+ ]
194
+ });
195
+ if (action === "cancel") {
196
+ throw new Error("OAuth login cancelled.");
197
+ }
198
+ if (action === "retry") {
199
+ break;
200
+ }
201
+ const next = waitForCallback(port, state, CHECK_IN_MS);
202
+ code = await next.promise;
203
+ if (code) {
204
+ return exchangeCode(code, redirectUri, codeVerifier);
205
+ }
206
+ }
207
+ }
157
208
  }
158
209
  async function exchangeCode(code, redirectUri, codeVerifier) {
159
210
  const res = await fetch(TOKEN_URL, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentlink.sh/cli",
3
- "version": "0.10.0",
3
+ "version": "0.10.2",
4
4
  "description": "CLI for scaffolding Supabase apps with AI agents",
5
5
  "bin": {
6
6
  "agentlink": "dist/index.js"