@rankcli/cli 0.0.24 → 0.0.25

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/dist/index.js CHANGED
@@ -373,11 +373,14 @@ function isApiKeyAuth() {
373
373
  function getApiKey() {
374
374
  return process.env.RANKCLI_API_KEY || config.get("apiKey") || null;
375
375
  }
376
- function saveApiKey(apiKey, keyName) {
376
+ function saveApiKey(apiKey, keyName, email) {
377
377
  config.set("apiKey", apiKey);
378
378
  if (keyName) {
379
379
  config.set("apiKeyName", keyName);
380
380
  }
381
+ if (email) {
382
+ config.set("email", email);
383
+ }
381
384
  }
382
385
  async function validateApiKey(apiKey) {
383
386
  try {
@@ -1354,11 +1357,18 @@ async function apply(options) {
1354
1357
  generateFixes: true
1355
1358
  });
1356
1359
  issues = auditResult.report.issues.map((issue) => ({
1357
- severity: issue.severity,
1360
+ // Squash the audit engine's 5-level severity into generateFixes's
1361
+ // 3-level SEOIssue scheme - matches how audit.ts already treats
1362
+ // 'error' as critical-weight (red ✗) and 'notice' as info-weight.
1363
+ severity: issue.severity === "error" ? "critical" : issue.severity === "notice" ? "info" : issue.severity,
1358
1364
  category: issue.category,
1359
1365
  code: issue.code,
1360
1366
  message: issue.title,
1361
1367
  impact: issue.description || void 0
1368
+ // Cast covers `category`: the audit engine's categories (17 values,
1369
+ // e.g. 'crawlability', 'links') don't map cleanly onto SEOIssue's
1370
+ // narrower set - generateFixes never reads category off its input
1371
+ // (each fixer hardcodes its own), so this is safe, not just silenced.
1362
1372
  }));
1363
1373
  score = auditResult.score;
1364
1374
  } catch (error) {
@@ -1471,8 +1481,7 @@ function generateCommitMessage(fixes, url) {
1471
1481
  (f) => f.issue.code.includes("MISSING") || f.issue.message.toLowerCase().includes("missing") || f.issue.message.toLowerCase().includes("add")
1472
1482
  );
1473
1483
  const type = isAddingNew ? "feat" : "fix";
1474
- const categories = new Set(applied.map((f) => f.issue.category || "seo"));
1475
- const categoryStr = categories.size === 1 ? Array.from(categories)[0] : "seo";
1484
+ const categoryStr = "seo";
1476
1485
  const issueCount = applied.length;
1477
1486
  const title = `${type}(${categoryStr}): fix ${issueCount} SEO issue${issueCount > 1 ? "s" : ""}`;
1478
1487
  const fixList = applied.map((f) => `- ${f.issue.code}: ${f.issue.message}`).join("\n");
@@ -1585,7 +1594,7 @@ async function fix(options) {
1585
1594
  let issues;
1586
1595
  let score;
1587
1596
  try {
1588
- const analysis = await (0, import_agent_runtime3.runDirectAnalysis)({ url });
1597
+ const analysis = await (0, import_agent_runtime3.runDirectAnalysis)(url);
1589
1598
  issues = analysis.issues;
1590
1599
  score = analysis.score;
1591
1600
  } catch (error) {
@@ -1894,7 +1903,7 @@ async function trackAdd(options) {
1894
1903
  const newCount = options.keywords.length;
1895
1904
  if (existingCount + newCount > maxKeywords) {
1896
1905
  spinner.fail(`Keyword limit reached (${existingCount}/${maxKeywords})`);
1897
- showUpsell("keyword_limit", { tier, current: existingCount, limit: maxKeywords });
1906
+ showUpsell("keyword_limit", { tier, current: existingCount, max: maxKeywords });
1898
1907
  return { success: false, error: `Keyword limit reached. Upgrade to track more keywords.` };
1899
1908
  }
1900
1909
  const keywordRecords = options.keywords.map((keyword) => ({
@@ -2768,15 +2777,22 @@ async function analyzeContent(options) {
2768
2777
  if (options.url) {
2769
2778
  console.log(`Fetching content from ${options.url}...`);
2770
2779
  try {
2771
- const result = await (0, import_agent_runtime7.crawlUrl)(options.url);
2772
- if (!result.html) {
2780
+ const crawlResult = await (0, import_agent_runtime7.crawlUrl)({ url: options.url });
2781
+ if (!crawlResult.success || !crawlResult.data) {
2773
2782
  console.log("\u274C Could not fetch content from URL");
2774
2783
  return;
2775
2784
  }
2776
- const title = result.meta?.title || "";
2777
- const h1 = result.headings?.find((h) => h.level === 1)?.text || "";
2778
- const headings = result.headings?.map((h) => h.text) || [];
2779
- const bodyText = extractBodyText(result.html);
2785
+ const { html } = crawlResult.data;
2786
+ const [metaResult, headingsResult] = await Promise.all([
2787
+ (0, import_agent_runtime7.extractMeta)({ html, url: options.url }),
2788
+ (0, import_agent_runtime7.analyzeHeadings)({ html })
2789
+ ]);
2790
+ const meta = metaResult.success ? metaResult.data : void 0;
2791
+ const headingStructure = headingsResult.success ? headingsResult.data : [];
2792
+ const title = meta?.title || "";
2793
+ const h1 = headingStructure.find((h) => h.level === 1)?.text || "";
2794
+ const headings = headingStructure.map((h) => h.text);
2795
+ const bodyText = extractBodyText(html);
2780
2796
  const paragraphs = bodyText.split(/\n\n+/);
2781
2797
  console.log(`
2782
2798
  Analyzing: "${title || options.url}"
@@ -2937,7 +2953,9 @@ async function interactiveSetup(projectPath) {
2937
2953
  const schedule = scheduleMap[scheduleChoice] || "weekly";
2938
2954
  const siteUrl = await ask2("Enter your site URL (https://example.com): ");
2939
2955
  if (siteUrl) {
2940
- await setupGitHubAction(projectPath, schedule, siteUrl);
2956
+ const syncChoice = await ask2("Send scheduled audits to your RankCLI dashboard? (y/n): ");
2957
+ const syncToDashboard = syncChoice.toLowerCase() === "y" || syncChoice.toLowerCase() === "yes";
2958
+ await setupGitHubAction(projectPath, schedule, siteUrl, syncToDashboard);
2941
2959
  }
2942
2960
  } else {
2943
2961
  console.log("Skipping GitHub Action setup.\n");
@@ -2999,18 +3017,18 @@ async function setupGSC(projectPath, verificationCode) {
2999
3017
  }
3000
3018
  console.log("");
3001
3019
  }
3002
- async function setupGitHubAction(projectPath, schedule, siteUrl) {
3020
+ async function setupGitHubAction(projectPath, schedule, siteUrl, syncToDashboard) {
3003
3021
  console.log(`Setting up GitHub Action (${schedule} schedule)...`);
3004
3022
  const config2 = {
3005
3023
  schedule,
3006
3024
  siteUrl: siteUrl || "https://example.com",
3007
3025
  features: {
3008
3026
  audit: true,
3009
- tracking: true,
3010
3027
  autoFix: true,
3011
3028
  createIssues: true,
3012
3029
  createPRs: true
3013
- }
3030
+ },
3031
+ syncToDashboard
3014
3032
  };
3015
3033
  const { files, instructions } = (0, import_agent_runtime8.writeGitHubActionFiles)(projectPath, config2);
3016
3034
  console.log("\u2705 GitHub Action files created:");
@@ -3193,6 +3211,57 @@ var import_chalk6 = __toESM(require("chalk"));
3193
3211
  var import_ora5 = __toESM(require("ora"));
3194
3212
  var import_inquirer = __toESM(require("inquirer"));
3195
3213
  var import_open = __toESM(require("open"));
3214
+ async function browserLogin(spinner) {
3215
+ spinner.start("Starting login...");
3216
+ let start;
3217
+ try {
3218
+ const res = await fetch(`${getApiUrl()}/functions/v1/mcp-connect-start`, { method: "POST" });
3219
+ if (!res.ok) throw new Error(`status ${res.status}`);
3220
+ start = await res.json();
3221
+ } catch (err) {
3222
+ spinner.fail("Could not start login");
3223
+ console.log(import_chalk6.default.red(err instanceof Error ? err.message : "Network error"));
3224
+ return;
3225
+ }
3226
+ spinner.text = "Opening browser to approve...";
3227
+ try {
3228
+ await (0, import_open.default)(start.url);
3229
+ } catch {
3230
+ }
3231
+ console.log(import_chalk6.default.dim(`
3232
+ If a browser didn't open, visit:
3233
+ ${start.url}
3234
+ `));
3235
+ const deadline = Date.now() + start.expiresInSeconds * 1e3;
3236
+ spinner.text = "Waiting for approval in your browser...";
3237
+ while (Date.now() < deadline) {
3238
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
3239
+ let status;
3240
+ try {
3241
+ const res = await fetch(`${getApiUrl()}/functions/v1/mcp-connect-status`, {
3242
+ method: "POST",
3243
+ headers: { "Content-Type": "application/json" },
3244
+ body: JSON.stringify({ connectId: start.connectId })
3245
+ });
3246
+ status = await res.json();
3247
+ } catch {
3248
+ continue;
3249
+ }
3250
+ if (status.status === "claimed" && status.apiKey) {
3251
+ saveApiKey(status.apiKey, status.apiKeyName, status.email);
3252
+ await fetchAndSaveSubscription();
3253
+ spinner.succeed(`Logged in as ${import_chalk6.default.green(status.email)}`);
3254
+ return;
3255
+ }
3256
+ if (status.status === "expired" || status.status === "not_found") {
3257
+ spinner.fail("Login link expired");
3258
+ console.log(import_chalk6.default.dim('Run "rankcli login" again.'));
3259
+ return;
3260
+ }
3261
+ }
3262
+ spinner.fail("Timed out waiting for approval");
3263
+ console.log(import_chalk6.default.dim(`Run "rankcli login" again when you're ready.`));
3264
+ }
3196
3265
  async function login(options) {
3197
3266
  if (isLoggedIn()) {
3198
3267
  const { email } = getUserInfo();
@@ -3230,39 +3299,8 @@ Email: ${result.user?.email}`));
3230
3299
  console.log(import_chalk6.default.dim(`Plan: ${result.subscription?.planName || "Free"}`));
3231
3300
  return;
3232
3301
  }
3233
- if (options.browser) {
3234
- console.log(import_chalk6.default.cyan("\nOpening browser for login..."));
3235
- console.log(import_chalk6.default.dim("Complete the login in your browser, then return here.\n"));
3236
- const loginUrl = `${WEB_APP_URL}/login?cli=true`;
3237
- await (0, import_open.default)(loginUrl);
3238
- const { token } = await import_inquirer.default.prompt([
3239
- {
3240
- type: "password",
3241
- name: "token",
3242
- message: "Paste your CLI token from the browser:",
3243
- mask: "*"
3244
- }
3245
- ]);
3246
- spinner.start("Verifying token...");
3247
- const supabase = getSupabaseClient();
3248
- const { data, error } = await supabase.auth.getUser(token);
3249
- if (error || !data.user) {
3250
- spinner.fail("Invalid token");
3251
- console.log(import_chalk6.default.red("The token is invalid or expired. Please try again."));
3252
- return;
3253
- }
3254
- const { data: sessionData, error: sessionError } = await supabase.auth.setSession({
3255
- access_token: token,
3256
- refresh_token: ""
3257
- // Will need to handle this differently
3258
- });
3259
- if (sessionError || !sessionData.session) {
3260
- spinner.fail("Failed to establish session");
3261
- return;
3262
- }
3263
- saveSession(sessionData.session);
3264
- await fetchAndSaveSubscription();
3265
- spinner.succeed(`Logged in as ${import_chalk6.default.green(data.user.email)}`);
3302
+ if (options.browser !== false && !options.email) {
3303
+ await browserLogin(spinner);
3266
3304
  } else {
3267
3305
  let email = options.email;
3268
3306
  if (!email) {
@@ -3406,12 +3444,12 @@ async function fetchAndSaveSubscription() {
3406
3444
  }
3407
3445
 
3408
3446
  // src/version.ts
3409
- var VERSION = "0.0.23";
3447
+ var VERSION = "0.0.24";
3410
3448
 
3411
3449
  // src/index.ts
3412
3450
  var program = new import_commander.Command();
3413
3451
  program.name("rankcli").description("RankCLI - Ship code, get ranked. SEO meets CI/CD.").version(VERSION);
3414
- program.command("login").description("Log in to your RankCLI account").option("-e, --email <email>", "Email address").option("-b, --browser", "Open browser for login").option("-t, --token <apiKey>", "API key for non-interactive authentication").action(login);
3452
+ program.command("login").description("Log in to your RankCLI account (opens your browser by default)").option("-e, --email <email>", "Log in with email/password instead of the browser").option("--no-browser", "Skip the browser and log in with email/password interactively").option("-t, --token <apiKey>", "API key for non-interactive authentication").action(login);
3415
3453
  program.command("logout").description("Log out of your RankCLI account").action(logout);
3416
3454
  program.command("whoami").description("Show current logged in user").action(whoami);
3417
3455
  program.command("init").description("Initialize SEO Autopilot in your project").option("-y, --yes", "Skip prompts and use defaults").action(init);
package/dist/index.mjs CHANGED
@@ -354,11 +354,14 @@ function isApiKeyAuth() {
354
354
  function getApiKey() {
355
355
  return process.env.RANKCLI_API_KEY || config.get("apiKey") || null;
356
356
  }
357
- function saveApiKey(apiKey, keyName) {
357
+ function saveApiKey(apiKey, keyName, email) {
358
358
  config.set("apiKey", apiKey);
359
359
  if (keyName) {
360
360
  config.set("apiKeyName", keyName);
361
361
  }
362
+ if (email) {
363
+ config.set("email", email);
364
+ }
362
365
  }
363
366
  async function validateApiKey(apiKey) {
364
367
  try {
@@ -1175,11 +1178,18 @@ async function apply(options) {
1175
1178
  generateFixes: true
1176
1179
  });
1177
1180
  issues = auditResult.report.issues.map((issue) => ({
1178
- severity: issue.severity,
1181
+ // Squash the audit engine's 5-level severity into generateFixes's
1182
+ // 3-level SEOIssue scheme - matches how audit.ts already treats
1183
+ // 'error' as critical-weight (red ✗) and 'notice' as info-weight.
1184
+ severity: issue.severity === "error" ? "critical" : issue.severity === "notice" ? "info" : issue.severity,
1179
1185
  category: issue.category,
1180
1186
  code: issue.code,
1181
1187
  message: issue.title,
1182
1188
  impact: issue.description || void 0
1189
+ // Cast covers `category`: the audit engine's categories (17 values,
1190
+ // e.g. 'crawlability', 'links') don't map cleanly onto SEOIssue's
1191
+ // narrower set - generateFixes never reads category off its input
1192
+ // (each fixer hardcodes its own), so this is safe, not just silenced.
1183
1193
  }));
1184
1194
  score = auditResult.score;
1185
1195
  } catch (error) {
@@ -1297,8 +1307,7 @@ function generateCommitMessage(fixes, url) {
1297
1307
  (f) => f.issue.code.includes("MISSING") || f.issue.message.toLowerCase().includes("missing") || f.issue.message.toLowerCase().includes("add")
1298
1308
  );
1299
1309
  const type = isAddingNew ? "feat" : "fix";
1300
- const categories = new Set(applied.map((f) => f.issue.category || "seo"));
1301
- const categoryStr = categories.size === 1 ? Array.from(categories)[0] : "seo";
1310
+ const categoryStr = "seo";
1302
1311
  const issueCount = applied.length;
1303
1312
  const title = `${type}(${categoryStr}): fix ${issueCount} SEO issue${issueCount > 1 ? "s" : ""}`;
1304
1313
  const fixList = applied.map((f) => `- ${f.issue.code}: ${f.issue.message}`).join("\n");
@@ -1411,7 +1420,7 @@ async function fix(options) {
1411
1420
  let issues;
1412
1421
  let score;
1413
1422
  try {
1414
- const analysis = await runDirectAnalysis2({ url });
1423
+ const analysis = await runDirectAnalysis2(url);
1415
1424
  issues = analysis.issues;
1416
1425
  score = analysis.score;
1417
1426
  } catch (error) {
@@ -1720,7 +1729,7 @@ async function trackAdd(options) {
1720
1729
  const newCount = options.keywords.length;
1721
1730
  if (existingCount + newCount > maxKeywords) {
1722
1731
  spinner.fail(`Keyword limit reached (${existingCount}/${maxKeywords})`);
1723
- showUpsell("keyword_limit", { tier, current: existingCount, limit: maxKeywords });
1732
+ showUpsell("keyword_limit", { tier, current: existingCount, max: maxKeywords });
1724
1733
  return { success: false, error: `Keyword limit reached. Upgrade to track more keywords.` };
1725
1734
  }
1726
1735
  const keywordRecords = options.keywords.map((keyword) => ({
@@ -2590,7 +2599,9 @@ import {
2590
2599
  formatFeaturedSnippetReport,
2591
2600
  analyzeKeywordDensity,
2592
2601
  formatKeywordDensityReport,
2593
- crawlUrl
2602
+ crawlUrl,
2603
+ extractMeta,
2604
+ analyzeHeadings
2594
2605
  } from "@rankcli/agent-runtime";
2595
2606
  async function analyzeContent(options) {
2596
2607
  console.log("\n\u{1F4DD} SEO Autopilot - Content Analysis\n");
@@ -2617,15 +2628,22 @@ async function analyzeContent(options) {
2617
2628
  if (options.url) {
2618
2629
  console.log(`Fetching content from ${options.url}...`);
2619
2630
  try {
2620
- const result = await crawlUrl(options.url);
2621
- if (!result.html) {
2631
+ const crawlResult = await crawlUrl({ url: options.url });
2632
+ if (!crawlResult.success || !crawlResult.data) {
2622
2633
  console.log("\u274C Could not fetch content from URL");
2623
2634
  return;
2624
2635
  }
2625
- const title = result.meta?.title || "";
2626
- const h1 = result.headings?.find((h) => h.level === 1)?.text || "";
2627
- const headings = result.headings?.map((h) => h.text) || [];
2628
- const bodyText = extractBodyText(result.html);
2636
+ const { html } = crawlResult.data;
2637
+ const [metaResult, headingsResult] = await Promise.all([
2638
+ extractMeta({ html, url: options.url }),
2639
+ analyzeHeadings({ html })
2640
+ ]);
2641
+ const meta = metaResult.success ? metaResult.data : void 0;
2642
+ const headingStructure = headingsResult.success ? headingsResult.data : [];
2643
+ const title = meta?.title || "";
2644
+ const h1 = headingStructure.find((h) => h.level === 1)?.text || "";
2645
+ const headings = headingStructure.map((h) => h.text);
2646
+ const bodyText = extractBodyText(html);
2629
2647
  const paragraphs = bodyText.split(/\n\n+/);
2630
2648
  console.log(`
2631
2649
  Analyzing: "${title || options.url}"
@@ -2792,7 +2810,9 @@ async function interactiveSetup(projectPath) {
2792
2810
  const schedule = scheduleMap[scheduleChoice] || "weekly";
2793
2811
  const siteUrl = await ask2("Enter your site URL (https://example.com): ");
2794
2812
  if (siteUrl) {
2795
- await setupGitHubAction(projectPath, schedule, siteUrl);
2813
+ const syncChoice = await ask2("Send scheduled audits to your RankCLI dashboard? (y/n): ");
2814
+ const syncToDashboard = syncChoice.toLowerCase() === "y" || syncChoice.toLowerCase() === "yes";
2815
+ await setupGitHubAction(projectPath, schedule, siteUrl, syncToDashboard);
2796
2816
  }
2797
2817
  } else {
2798
2818
  console.log("Skipping GitHub Action setup.\n");
@@ -2854,18 +2874,18 @@ async function setupGSC(projectPath, verificationCode) {
2854
2874
  }
2855
2875
  console.log("");
2856
2876
  }
2857
- async function setupGitHubAction(projectPath, schedule, siteUrl) {
2877
+ async function setupGitHubAction(projectPath, schedule, siteUrl, syncToDashboard) {
2858
2878
  console.log(`Setting up GitHub Action (${schedule} schedule)...`);
2859
2879
  const config2 = {
2860
2880
  schedule,
2861
2881
  siteUrl: siteUrl || "https://example.com",
2862
2882
  features: {
2863
2883
  audit: true,
2864
- tracking: true,
2865
2884
  autoFix: true,
2866
2885
  createIssues: true,
2867
2886
  createPRs: true
2868
- }
2887
+ },
2888
+ syncToDashboard
2869
2889
  };
2870
2890
  const { files, instructions } = writeGitHubActionFiles(projectPath, config2);
2871
2891
  console.log("\u2705 GitHub Action files created:");
@@ -3048,6 +3068,57 @@ import chalk6 from "chalk";
3048
3068
  import ora5 from "ora";
3049
3069
  import inquirer from "inquirer";
3050
3070
  import open from "open";
3071
+ async function browserLogin(spinner) {
3072
+ spinner.start("Starting login...");
3073
+ let start;
3074
+ try {
3075
+ const res = await fetch(`${getApiUrl()}/functions/v1/mcp-connect-start`, { method: "POST" });
3076
+ if (!res.ok) throw new Error(`status ${res.status}`);
3077
+ start = await res.json();
3078
+ } catch (err) {
3079
+ spinner.fail("Could not start login");
3080
+ console.log(chalk6.red(err instanceof Error ? err.message : "Network error"));
3081
+ return;
3082
+ }
3083
+ spinner.text = "Opening browser to approve...";
3084
+ try {
3085
+ await open(start.url);
3086
+ } catch {
3087
+ }
3088
+ console.log(chalk6.dim(`
3089
+ If a browser didn't open, visit:
3090
+ ${start.url}
3091
+ `));
3092
+ const deadline = Date.now() + start.expiresInSeconds * 1e3;
3093
+ spinner.text = "Waiting for approval in your browser...";
3094
+ while (Date.now() < deadline) {
3095
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
3096
+ let status;
3097
+ try {
3098
+ const res = await fetch(`${getApiUrl()}/functions/v1/mcp-connect-status`, {
3099
+ method: "POST",
3100
+ headers: { "Content-Type": "application/json" },
3101
+ body: JSON.stringify({ connectId: start.connectId })
3102
+ });
3103
+ status = await res.json();
3104
+ } catch {
3105
+ continue;
3106
+ }
3107
+ if (status.status === "claimed" && status.apiKey) {
3108
+ saveApiKey(status.apiKey, status.apiKeyName, status.email);
3109
+ await fetchAndSaveSubscription();
3110
+ spinner.succeed(`Logged in as ${chalk6.green(status.email)}`);
3111
+ return;
3112
+ }
3113
+ if (status.status === "expired" || status.status === "not_found") {
3114
+ spinner.fail("Login link expired");
3115
+ console.log(chalk6.dim('Run "rankcli login" again.'));
3116
+ return;
3117
+ }
3118
+ }
3119
+ spinner.fail("Timed out waiting for approval");
3120
+ console.log(chalk6.dim(`Run "rankcli login" again when you're ready.`));
3121
+ }
3051
3122
  async function login(options) {
3052
3123
  if (isLoggedIn()) {
3053
3124
  const { email } = getUserInfo();
@@ -3085,39 +3156,8 @@ Email: ${result.user?.email}`));
3085
3156
  console.log(chalk6.dim(`Plan: ${result.subscription?.planName || "Free"}`));
3086
3157
  return;
3087
3158
  }
3088
- if (options.browser) {
3089
- console.log(chalk6.cyan("\nOpening browser for login..."));
3090
- console.log(chalk6.dim("Complete the login in your browser, then return here.\n"));
3091
- const loginUrl = `${WEB_APP_URL}/login?cli=true`;
3092
- await open(loginUrl);
3093
- const { token } = await inquirer.prompt([
3094
- {
3095
- type: "password",
3096
- name: "token",
3097
- message: "Paste your CLI token from the browser:",
3098
- mask: "*"
3099
- }
3100
- ]);
3101
- spinner.start("Verifying token...");
3102
- const supabase = getSupabaseClient();
3103
- const { data, error } = await supabase.auth.getUser(token);
3104
- if (error || !data.user) {
3105
- spinner.fail("Invalid token");
3106
- console.log(chalk6.red("The token is invalid or expired. Please try again."));
3107
- return;
3108
- }
3109
- const { data: sessionData, error: sessionError } = await supabase.auth.setSession({
3110
- access_token: token,
3111
- refresh_token: ""
3112
- // Will need to handle this differently
3113
- });
3114
- if (sessionError || !sessionData.session) {
3115
- spinner.fail("Failed to establish session");
3116
- return;
3117
- }
3118
- saveSession(sessionData.session);
3119
- await fetchAndSaveSubscription();
3120
- spinner.succeed(`Logged in as ${chalk6.green(data.user.email)}`);
3159
+ if (options.browser !== false && !options.email) {
3160
+ await browserLogin(spinner);
3121
3161
  } else {
3122
3162
  let email = options.email;
3123
3163
  if (!email) {
@@ -3261,12 +3301,12 @@ async function fetchAndSaveSubscription() {
3261
3301
  }
3262
3302
 
3263
3303
  // src/version.ts
3264
- var VERSION = "0.0.23";
3304
+ var VERSION = "0.0.24";
3265
3305
 
3266
3306
  // src/index.ts
3267
3307
  var program = new Command();
3268
3308
  program.name("rankcli").description("RankCLI - Ship code, get ranked. SEO meets CI/CD.").version(VERSION);
3269
- program.command("login").description("Log in to your RankCLI account").option("-e, --email <email>", "Email address").option("-b, --browser", "Open browser for login").option("-t, --token <apiKey>", "API key for non-interactive authentication").action(login);
3309
+ program.command("login").description("Log in to your RankCLI account (opens your browser by default)").option("-e, --email <email>", "Log in with email/password instead of the browser").option("--no-browser", "Skip the browser and log in with email/password interactively").option("-t, --token <apiKey>", "API key for non-interactive authentication").action(login);
3270
3310
  program.command("logout").description("Log out of your RankCLI account").action(logout);
3271
3311
  program.command("whoami").description("Show current logged in user").action(whoami);
3272
3312
  program.command("init").description("Initialize SEO Autopilot in your project").option("-y, --yes", "Skip prompts and use defaults").action(init);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rankcli/cli",
3
- "version": "0.0.24",
3
+ "version": "0.0.25",
4
4
  "description": "RankCLI - Ship code, get ranked. SEO meets CI/CD.",
5
5
  "homepage": "https://rankcli.dev",
6
6
  "main": "dist/index.js",
@@ -45,8 +45,8 @@
45
45
  "update-version": "node scripts/update-version.js"
46
46
  },
47
47
  "dependencies": {
48
- "@rankcli/agent-runtime": "^0.0.17",
49
- "@supabase/supabase-js": "^2.45.0",
48
+ "@rankcli/agent-runtime": "^0.0.18",
49
+ "@supabase/supabase-js": "^2.98.0",
50
50
  "chalk": "^5.3.0",
51
51
  "commander": "^12.0.0",
52
52
  "conf": "^12.0.0",