@rankcli/cli 0.0.13 → 0.0.15

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 (4) hide show
  1. package/README.md +103 -207
  2. package/dist/index.js +239 -81
  3. package/dist/index.mjs +201 -43
  4. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -310,16 +310,18 @@ function getSupabaseAnonKey() {
310
310
  }
311
311
  var _supabaseUrl = null;
312
312
  var _supabaseAnonKey = null;
313
- var SUPABASE_URL = (() => {
313
+ function getSupabaseUrlLazy() {
314
314
  if (!_supabaseUrl) _supabaseUrl = getSupabaseUrl();
315
315
  return _supabaseUrl;
316
- })();
317
- var SUPABASE_ANON_KEY = (() => {
316
+ }
317
+ function getSupabaseAnonKeyLazy() {
318
318
  if (!_supabaseAnonKey) _supabaseAnonKey = getSupabaseAnonKey();
319
319
  return _supabaseAnonKey;
320
- })();
320
+ }
321
321
  var WEB_APP_URL = process.env.RANKCLI_WEB_URL || INJECTED_CONFIG?.webAppUrl || "https://rankcli.dev";
322
- var API_URL = process.env.RANKCLI_API_URL || SUPABASE_URL;
322
+ function getApiUrl() {
323
+ return process.env.RANKCLI_API_URL || getSupabaseUrlLazy();
324
+ }
323
325
  var config = new import_conf.default({
324
326
  projectName: "rankcli",
325
327
  schema: {
@@ -338,7 +340,7 @@ var config = new import_conf.default({
338
340
  var supabaseClient = null;
339
341
  function getSupabaseClient() {
340
342
  if (!supabaseClient) {
341
- supabaseClient = (0, import_supabase_js.createClient)(SUPABASE_URL, SUPABASE_ANON_KEY, {
343
+ supabaseClient = (0, import_supabase_js.createClient)(getSupabaseUrlLazy(), getSupabaseAnonKeyLazy(), {
342
344
  auth: {
343
345
  persistSession: false,
344
346
  // We manage session ourselves via conf
@@ -376,7 +378,7 @@ function saveApiKey(apiKey, keyName) {
376
378
  }
377
379
  async function validateApiKey(apiKey) {
378
380
  try {
379
- const response = await fetch(`${API_URL}/functions/v1/validate-api-key`, {
381
+ const response = await fetch(`${getApiUrl()}/functions/v1/validate-api-key`, {
380
382
  method: "POST",
381
383
  headers: {
382
384
  "Content-Type": "application/json",
@@ -1049,9 +1051,164 @@ async function apply(options) {
1049
1051
  console.log("");
1050
1052
  }
1051
1053
 
1054
+ // src/commands/geo.ts
1055
+ var import_agent_runtime3 = require("@rankcli/agent-runtime");
1056
+ var import_ora2 = __toESM(require("ora"));
1057
+ var import_chalk2 = __toESM(require("chalk"));
1058
+ async function fetchResources(url) {
1059
+ const headers = {};
1060
+ let html = null;
1061
+ let robotsTxt = null;
1062
+ try {
1063
+ const response = await fetch(url, {
1064
+ headers: {
1065
+ "User-Agent": "RankCLI-Bot/1.0 (+https://rankcli.dev/bot)",
1066
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
1067
+ }
1068
+ });
1069
+ if (response.ok) {
1070
+ html = await response.text();
1071
+ response.headers.forEach((value, key) => {
1072
+ headers[key.toLowerCase()] = value;
1073
+ });
1074
+ }
1075
+ } catch {
1076
+ }
1077
+ try {
1078
+ const robotsUrl = new URL("/robots.txt", url).href;
1079
+ const robotsResponse = await fetch(robotsUrl);
1080
+ if (robotsResponse.ok) {
1081
+ robotsTxt = await robotsResponse.text();
1082
+ }
1083
+ } catch {
1084
+ }
1085
+ return { html, robotsTxt, headers };
1086
+ }
1087
+ async function geo(options) {
1088
+ const url = options.url;
1089
+ if (!url) {
1090
+ console.log("\n\u26A0\uFE0F Please provide a URL to analyze:");
1091
+ console.log(" rankcli geo --url https://your-site.com");
1092
+ console.log("\nOptions:");
1093
+ console.log(" --output json Output as JSON");
1094
+ console.log("");
1095
+ return;
1096
+ }
1097
+ try {
1098
+ new URL(url);
1099
+ } catch {
1100
+ console.log("\n\u274C Invalid URL format. Please provide a valid URL.\n");
1101
+ return;
1102
+ }
1103
+ const spinner = (0, import_ora2.default)("Fetching page and robots.txt...").start();
1104
+ const { html, robotsTxt, headers } = await fetchResources(url);
1105
+ if (!html) {
1106
+ spinner.fail("Could not fetch page content");
1107
+ return;
1108
+ }
1109
+ spinner.text = "Running GEO analysis...";
1110
+ try {
1111
+ const geoResult = await import_agent_runtime3.analyzers.analyzeGEO(html, url, robotsTxt || void 0);
1112
+ const cwvResult = import_agent_runtime3.analyzers.analyzeCoreWebVitals(html, url, headers);
1113
+ const securityResult = import_agent_runtime3.analyzers.analyzeSecurityHeaders(headers, url);
1114
+ const schemaResult = import_agent_runtime3.analyzers.analyzeStructuredData(html, url);
1115
+ const mobileResult = import_agent_runtime3.analyzers.analyzeMobileSEO(html, url);
1116
+ spinner.succeed("GEO analysis complete");
1117
+ if (options.output === "json") {
1118
+ const jsonOutput = {
1119
+ url,
1120
+ geo: geoResult,
1121
+ coreWebVitals: cwvResult,
1122
+ security: securityResult,
1123
+ structuredData: schemaResult,
1124
+ mobile: mobileResult
1125
+ };
1126
+ console.log(JSON.stringify(jsonOutput, null, 2));
1127
+ return;
1128
+ }
1129
+ console.log("\n" + import_chalk2.default.bold("\u2550".repeat(60)));
1130
+ console.log(import_chalk2.default.bold.cyan(" GEO ANALYSIS: AI Search Optimization"));
1131
+ console.log(import_chalk2.default.bold("\u2550".repeat(60)));
1132
+ const geoScore = geoResult.score;
1133
+ const scoreColor = geoScore >= 80 ? import_chalk2.default.green : geoScore >= 60 ? import_chalk2.default.yellow : import_chalk2.default.red;
1134
+ console.log(`
1135
+ ${import_chalk2.default.bold("GEO Score:")} ${scoreColor.bold(geoScore + "/100")}`);
1136
+ console.log("\n" + import_chalk2.default.bold(" AI Crawler Access:"));
1137
+ const { aiCrawlerAccess } = geoResult;
1138
+ if (aiCrawlerAccess.blockedCrawlers.length > 0) {
1139
+ console.log(import_chalk2.default.red(` \u274C Blocked: ${aiCrawlerAccess.blockedCrawlers.join(", ")}`));
1140
+ }
1141
+ if (aiCrawlerAccess.allowedCrawlers.length > 0) {
1142
+ const topAllowed = aiCrawlerAccess.allowedCrawlers.slice(0, 5);
1143
+ console.log(import_chalk2.default.green(` \u2705 Allowed: ${topAllowed.join(", ")}${aiCrawlerAccess.allowedCrawlers.length > 5 ? "..." : ""}`));
1144
+ }
1145
+ console.log("\n" + import_chalk2.default.bold(" Rendering:"));
1146
+ if (aiCrawlerAccess.serverSideRendered) {
1147
+ console.log(import_chalk2.default.green(" \u2705 Server-side rendered (AI crawlers can see content)"));
1148
+ } else if (aiCrawlerAccess.jsRenderingRequired) {
1149
+ console.log(import_chalk2.default.red(" \u274C JavaScript required (AI crawlers may see blank page)"));
1150
+ }
1151
+ console.log("\n" + import_chalk2.default.bold(" LLM Friendliness:"));
1152
+ const { llmSignals } = geoResult;
1153
+ const bar = (score) => {
1154
+ const filled = Math.round(score / 5);
1155
+ const empty = 20 - filled;
1156
+ const color = score >= 70 ? import_chalk2.default.green : score >= 40 ? import_chalk2.default.yellow : import_chalk2.default.red;
1157
+ return color("\u2588".repeat(filled)) + import_chalk2.default.gray("\u2591".repeat(empty));
1158
+ };
1159
+ console.log(` Content Clarity: ${bar(llmSignals.contentClarity)} ${llmSignals.contentClarity}%`);
1160
+ console.log(` Fact Density: ${bar(llmSignals.factDensity)} ${llmSignals.factDensity}%`);
1161
+ console.log(` Structure: ${bar(llmSignals.structureQuality)} ${llmSignals.structureQuality}%`);
1162
+ console.log(` Citations: ${bar(llmSignals.citationQuality)} ${llmSignals.citationQuality}%`);
1163
+ console.log("\n" + import_chalk2.default.bold(" Content Structure:"));
1164
+ const { contentStructure } = geoResult;
1165
+ console.log(` ${contentStructure.hasStructuredData ? import_chalk2.default.green("\u2705") : import_chalk2.default.red("\u274C")} JSON-LD Structured Data`);
1166
+ console.log(` ${contentStructure.hasFAQSchema ? import_chalk2.default.green("\u2705") : import_chalk2.default.yellow("\u25CB")} FAQ Schema`);
1167
+ console.log(` ${contentStructure.hasArticleSchema ? import_chalk2.default.green("\u2705") : import_chalk2.default.yellow("\u25CB")} Article Schema`);
1168
+ console.log(` ${contentStructure.hasBreadcrumbs ? import_chalk2.default.green("\u2705") : import_chalk2.default.yellow("\u25CB")} Breadcrumbs`);
1169
+ console.log(` Heading Hierarchy: ${contentStructure.headingHierarchy === "good" ? import_chalk2.default.green("Good") : contentStructure.headingHierarchy === "needs-work" ? import_chalk2.default.yellow("Needs Work") : import_chalk2.default.red("Poor")}`);
1170
+ console.log("\n" + import_chalk2.default.bold(" Citation Readiness:"));
1171
+ const { citationReadiness } = geoResult;
1172
+ if (citationReadiness.trustSignals.length > 0) {
1173
+ console.log(import_chalk2.default.green(` \u2705 ${citationReadiness.trustSignals.join(", ")}`));
1174
+ } else {
1175
+ console.log(import_chalk2.default.yellow(" \u25CB No trust signals detected"));
1176
+ }
1177
+ console.log("\n" + import_chalk2.default.bold(" Other Scores:"));
1178
+ console.log(` Core Web Vitals: ${bar(cwvResult.overallScore)} ${cwvResult.overallScore}%`);
1179
+ console.log(` Security Headers: ${securityResult.grade} (${securityResult.score}%)`);
1180
+ console.log(` Structured Data: ${bar(schemaResult.score)} ${schemaResult.score}%`);
1181
+ console.log(` Mobile SEO: ${bar(mobileResult.score)} ${mobileResult.score}%`);
1182
+ const allIssues = [
1183
+ ...geoResult.issues,
1184
+ ...cwvResult.issues,
1185
+ ...securityResult.issues,
1186
+ ...schemaResult.issues,
1187
+ ...mobileResult.issues
1188
+ ];
1189
+ const criticalIssues = allIssues.filter((i) => i.severity === "critical" || i.severity === "error");
1190
+ if (criticalIssues.length > 0) {
1191
+ console.log("\n" + import_chalk2.default.bold.red(" Critical Issues:"));
1192
+ criticalIssues.slice(0, 5).forEach((issue, i) => {
1193
+ console.log(import_chalk2.default.red(` ${i + 1}. ${issue.title}`));
1194
+ });
1195
+ }
1196
+ if (geoResult.recommendations.length > 0) {
1197
+ console.log("\n" + import_chalk2.default.bold(" Recommendations:"));
1198
+ geoResult.recommendations.slice(0, 5).forEach((rec, i) => {
1199
+ console.log(` ${i + 1}. ${rec}`);
1200
+ });
1201
+ }
1202
+ console.log("\n" + import_chalk2.default.bold("\u2550".repeat(60)));
1203
+ console.log("");
1204
+ } catch (error) {
1205
+ spinner.fail(`Analysis failed: ${error instanceof Error ? error.message : "Unknown error"}`);
1206
+ }
1207
+ }
1208
+
1052
1209
  // src/commands/keywords.ts
1053
1210
  var readline = __toESM(require("readline"));
1054
- var import_agent_runtime3 = require("@rankcli/agent-runtime");
1211
+ var import_agent_runtime4 = require("@rankcli/agent-runtime");
1055
1212
  async function keywords(options) {
1056
1213
  console.log("\n\u{1F511} SEO Autopilot - Keyword Research\n");
1057
1214
  console.log("This tool helps you find keywords you can actually rank for,");
@@ -1102,7 +1259,7 @@ ${question}`);
1102
1259
  seedKeywords = options.seed.split(",").map((s) => s.trim());
1103
1260
  } else if (options.auto) {
1104
1261
  console.log("\n\u{1F50D} Auto-extracting seed keywords from your page...");
1105
- seedKeywords = await (0, import_agent_runtime3.extractSeedKeywords)(url);
1262
+ seedKeywords = await (0, import_agent_runtime4.extractSeedKeywords)(url);
1106
1263
  if (seedKeywords.length > 0) {
1107
1264
  console.log(`Found: ${seedKeywords.join(", ")}`);
1108
1265
  }
@@ -1127,7 +1284,7 @@ ${question}`);
1127
1284
  contentCapacity: "medium",
1128
1285
  targetGeo: "us"
1129
1286
  };
1130
- for (const q of import_agent_runtime3.SITE_PROFILE_QUESTIONS) {
1287
+ for (const q of import_agent_runtime4.SITE_PROFILE_QUESTIONS) {
1131
1288
  const answer = await askChoice(q.question, q.options);
1132
1289
  siteProfile[q.id] = answer;
1133
1290
  }
@@ -1146,13 +1303,13 @@ ${question}`);
1146
1303
  url,
1147
1304
  maxKeywords: 100
1148
1305
  };
1149
- const result = await (0, import_agent_runtime3.runKeywordResearch)(researchOptions);
1150
- console.log((0, import_agent_runtime3.formatKeywordReport)(result));
1306
+ const result = await (0, import_agent_runtime4.runKeywordResearch)(researchOptions);
1307
+ console.log((0, import_agent_runtime4.formatKeywordReport)(result));
1151
1308
  const allKeywords = [...result.quickWins, ...result.mediumTerm, ...result.longTerm];
1152
1309
  if (allKeywords.length > 5) {
1153
1310
  console.log("\n\u{1F4DA} Grouping keywords by topic...\n");
1154
- const topicResult = (0, import_agent_runtime3.groupKeywordsByTopic)(allKeywords);
1155
- console.log((0, import_agent_runtime3.formatTopicReport)(topicResult));
1311
+ const topicResult = (0, import_agent_runtime4.groupKeywordsByTopic)(allKeywords);
1312
+ console.log((0, import_agent_runtime4.formatTopicReport)(topicResult));
1156
1313
  }
1157
1314
  if (result.quickWins.length > 0) {
1158
1315
  console.log('\n\u{1F4A1} TIP: Run "seo apply --url ' + url + '" to apply keyword optimizations');
@@ -1177,7 +1334,7 @@ async function keywordsQuick(url, providedSeeds) {
1177
1334
  `);
1178
1335
  } else {
1179
1336
  console.log("\u{1F50D} Extracting keywords from your page...");
1180
- seedKeywords = await (0, import_agent_runtime3.extractSeedKeywords)(url);
1337
+ seedKeywords = await (0, import_agent_runtime4.extractSeedKeywords)(url);
1181
1338
  if (seedKeywords.length === 0) {
1182
1339
  console.log("\u274C Could not extract keywords. Please use interactive mode.\n");
1183
1340
  return;
@@ -1193,18 +1350,18 @@ async function keywordsQuick(url, providedSeeds) {
1193
1350
  contentCapacity: "medium",
1194
1351
  targetGeo: "us"
1195
1352
  };
1196
- const result = await (0, import_agent_runtime3.runKeywordResearch)({
1353
+ const result = await (0, import_agent_runtime4.runKeywordResearch)({
1197
1354
  seedKeywords,
1198
1355
  siteProfile,
1199
1356
  url,
1200
1357
  maxKeywords: 50
1201
1358
  });
1202
- console.log((0, import_agent_runtime3.formatKeywordReport)(result));
1359
+ console.log((0, import_agent_runtime4.formatKeywordReport)(result));
1203
1360
  const allKeywords = [...result.quickWins, ...result.mediumTerm, ...result.longTerm];
1204
1361
  if (allKeywords.length > 5) {
1205
1362
  console.log("\n\u{1F4DA} Grouping keywords by topic...\n");
1206
- const topicResult = (0, import_agent_runtime3.groupKeywordsByTopic)(allKeywords);
1207
- console.log((0, import_agent_runtime3.formatTopicReport)(topicResult));
1363
+ const topicResult = (0, import_agent_runtime4.groupKeywordsByTopic)(allKeywords);
1364
+ console.log((0, import_agent_runtime4.formatTopicReport)(topicResult));
1208
1365
  }
1209
1366
  } catch (error) {
1210
1367
  console.log(`
@@ -1212,7 +1369,7 @@ async function keywordsQuick(url, providedSeeds) {
1212
1369
  `);
1213
1370
  }
1214
1371
  }
1215
- var SUPABASE_URL2 = "https://eqzlmjbvrtrglknphdai.supabase.co";
1372
+ var SUPABASE_URL = "https://eqzlmjbvrtrglknphdai.supabase.co";
1216
1373
  async function keywordsAI(url, options = {}) {
1217
1374
  console.log("\n\u{1F916} AI-Powered Keyword Research\n");
1218
1375
  console.log("This uses GPT-4 to analyze your site and generate keyword recommendations");
@@ -1226,7 +1383,7 @@ async function keywordsAI(url, options = {}) {
1226
1383
  if (!options.forceLocal) {
1227
1384
  console.log("\u{1F517} Connecting to RankCLI cloud...");
1228
1385
  try {
1229
- const response = await fetch(`${SUPABASE_URL2}/functions/v1/ai-keywords`, {
1386
+ const response = await fetch(`${SUPABASE_URL}/functions/v1/ai-keywords`, {
1230
1387
  method: "POST",
1231
1388
  headers: {
1232
1389
  "Content-Type": "application/json"
@@ -1267,7 +1424,7 @@ ${result2.teaser.upgradeMessage}`);
1267
1424
  return;
1268
1425
  }
1269
1426
  console.log("\u{1F52C} Running local AI analysis (this may take a minute)...\n");
1270
- const result = await (0, import_agent_runtime3.runAIKeywordResearch)({
1427
+ const result = await (0, import_agent_runtime4.runAIKeywordResearch)({
1271
1428
  url,
1272
1429
  tier: "solo",
1273
1430
  openaiApiKey,
@@ -1386,12 +1543,12 @@ async function keywordsCompetitor(yourUrl, seedKeywords, competitors) {
1386
1543
  }
1387
1544
  console.log("");
1388
1545
  console.log("Analyzing competitor keywords (this may take a moment)...\n");
1389
- const result = await (0, import_agent_runtime3.discoverCompetitorKeywords)(
1546
+ const result = await (0, import_agent_runtime4.discoverCompetitorKeywords)(
1390
1547
  yourDomain,
1391
1548
  seedKeywords,
1392
1549
  competitors
1393
1550
  );
1394
- console.log((0, import_agent_runtime3.formatCompetitorReport)(result));
1551
+ console.log((0, import_agent_runtime4.formatCompetitorReport)(result));
1395
1552
  if (result.missingKeywords.length > 0) {
1396
1553
  console.log("\n\u{1F3AF} ACTION ITEMS:");
1397
1554
  console.log("\u2500".repeat(60));
@@ -1410,7 +1567,7 @@ async function keywordsCompetitor(yourUrl, seedKeywords, competitors) {
1410
1567
  }
1411
1568
 
1412
1569
  // src/commands/content.ts
1413
- var import_agent_runtime4 = require("@rankcli/agent-runtime");
1570
+ var import_agent_runtime5 = require("@rankcli/agent-runtime");
1414
1571
  async function analyzeContent(options) {
1415
1572
  console.log("\n\u{1F4DD} SEO Autopilot - Content Analysis\n");
1416
1573
  const mode = options.mode || "full";
@@ -1420,12 +1577,12 @@ async function analyzeContent(options) {
1420
1577
  console.log('\u274C Please provide a headline with --headline "Your Headline Here"');
1421
1578
  return;
1422
1579
  }
1423
- const analysis = (0, import_agent_runtime4.analyzeHeadline)(headline);
1424
- console.log((0, import_agent_runtime4.formatHeadlineReport)(analysis));
1580
+ const analysis = (0, import_agent_runtime5.analyzeHeadline)(headline);
1581
+ console.log((0, import_agent_runtime5.formatHeadlineReport)(analysis));
1425
1582
  if (options.keyword) {
1426
1583
  console.log("\n\u{1F4CB} HEADLINE VARIATIONS");
1427
1584
  console.log("\u2500".repeat(60));
1428
- const variations = (0, import_agent_runtime4.generateHeadlineVariations)(options.keyword, [options.keyword]);
1585
+ const variations = (0, import_agent_runtime5.generateHeadlineVariations)(options.keyword, [options.keyword]);
1429
1586
  for (const v of variations.slice(0, 5)) {
1430
1587
  console.log(` \u2022 ${v}`);
1431
1588
  }
@@ -1436,7 +1593,7 @@ async function analyzeContent(options) {
1436
1593
  if (options.url) {
1437
1594
  console.log(`Fetching content from ${options.url}...`);
1438
1595
  try {
1439
- const result = await (0, import_agent_runtime4.crawlUrl)(options.url);
1596
+ const result = await (0, import_agent_runtime5.crawlUrl)(options.url);
1440
1597
  if (!result.html) {
1441
1598
  console.log("\u274C Could not fetch content from URL");
1442
1599
  return;
@@ -1450,15 +1607,15 @@ async function analyzeContent(options) {
1450
1607
  Analyzing: "${title || options.url}"
1451
1608
  `);
1452
1609
  if (mode === "full" || mode === "readability") {
1453
- const readability = (0, import_agent_runtime4.analyzeReadability)(bodyText);
1454
- console.log((0, import_agent_runtime4.formatReadabilityReport)(readability));
1610
+ const readability = (0, import_agent_runtime5.analyzeReadability)(bodyText);
1611
+ console.log((0, import_agent_runtime5.formatReadabilityReport)(readability));
1455
1612
  }
1456
1613
  if (mode === "full" && title) {
1457
- const headlineAnalysis = (0, import_agent_runtime4.analyzeHeadline)(title);
1458
- console.log((0, import_agent_runtime4.formatHeadlineReport)(headlineAnalysis));
1614
+ const headlineAnalysis = (0, import_agent_runtime5.analyzeHeadline)(title);
1615
+ console.log((0, import_agent_runtime5.formatHeadlineReport)(headlineAnalysis));
1459
1616
  }
1460
1617
  if (options.keyword && (mode === "full" || mode === "density")) {
1461
- const densityAnalysis = (0, import_agent_runtime4.analyzeKeywordDensity)(options.keyword, {
1618
+ const densityAnalysis = (0, import_agent_runtime5.analyzeKeywordDensity)(options.keyword, {
1462
1619
  title,
1463
1620
  h1,
1464
1621
  headings,
@@ -1466,19 +1623,19 @@ Analyzing: "${title || options.url}"
1466
1623
  firstParagraph: paragraphs[0],
1467
1624
  lastParagraph: paragraphs[paragraphs.length - 1]
1468
1625
  });
1469
- console.log((0, import_agent_runtime4.formatKeywordDensityReport)(densityAnalysis));
1626
+ console.log((0, import_agent_runtime5.formatKeywordDensityReport)(densityAnalysis));
1470
1627
  }
1471
1628
  if (options.keyword && (mode === "full" || mode === "snippet")) {
1472
- const snippetAnalysis = (0, import_agent_runtime4.analyzeFeaturedSnippetPotential)(
1629
+ const snippetAnalysis = (0, import_agent_runtime5.analyzeFeaturedSnippetPotential)(
1473
1630
  options.keyword,
1474
1631
  bodyText,
1475
1632
  headings
1476
1633
  );
1477
- console.log((0, import_agent_runtime4.formatFeaturedSnippetReport)(snippetAnalysis));
1634
+ console.log((0, import_agent_runtime5.formatFeaturedSnippetReport)(snippetAnalysis));
1478
1635
  }
1479
1636
  if (options.keyword) {
1480
- const intentAnalysis = (0, import_agent_runtime4.classifyIntent)(options.keyword);
1481
- console.log((0, import_agent_runtime4.formatIntentReport)(options.keyword, intentAnalysis));
1637
+ const intentAnalysis = (0, import_agent_runtime5.classifyIntent)(options.keyword);
1638
+ console.log((0, import_agent_runtime5.formatIntentReport)(options.keyword, intentAnalysis));
1482
1639
  }
1483
1640
  console.log("\n\u{1F4CA} CONTENT SUMMARY");
1484
1641
  console.log("\u2500".repeat(60));
@@ -1505,8 +1662,8 @@ Analyzing: "${title || options.url}"
1505
1662
  }
1506
1663
  async function analyzeIntent(keyword) {
1507
1664
  console.log("\n\u{1F50D} Search Intent Analysis\n");
1508
- const analysis = (0, import_agent_runtime4.classifyIntent)(keyword);
1509
- console.log((0, import_agent_runtime4.formatIntentReport)(keyword, analysis));
1665
+ const analysis = (0, import_agent_runtime5.classifyIntent)(keyword);
1666
+ console.log((0, import_agent_runtime5.formatIntentReport)(keyword, analysis));
1510
1667
  }
1511
1668
  function extractBodyText(html) {
1512
1669
  let text = html.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "");
@@ -1525,7 +1682,7 @@ function extractBodyText(html) {
1525
1682
  var readline2 = __toESM(require("readline"));
1526
1683
  var fs = __toESM(require("fs"));
1527
1684
  var path = __toESM(require("path"));
1528
- var import_agent_runtime5 = require("@rankcli/agent-runtime");
1685
+ var import_agent_runtime6 = require("@rankcli/agent-runtime");
1529
1686
  async function setup(options) {
1530
1687
  console.log("\n\u{1F527} SEO Autopilot - Setup\n");
1531
1688
  const projectPath = process.cwd();
@@ -1627,7 +1784,7 @@ async function interactiveSetup(projectPath) {
1627
1784
  }
1628
1785
  async function setupGA4(projectPath, measurementId) {
1629
1786
  console.log(`Setting up Google Analytics 4 (${measurementId})...`);
1630
- const result = await (0, import_agent_runtime5.injectGA4)(projectPath, { measurementId });
1787
+ const result = await (0, import_agent_runtime6.injectGA4)(projectPath, { measurementId });
1631
1788
  if (result.success) {
1632
1789
  console.log(`\u2705 ${result.message}`);
1633
1790
  if (result.file) {
@@ -1641,7 +1798,7 @@ async function setupGA4(projectPath, measurementId) {
1641
1798
  }
1642
1799
  }
1643
1800
  const envExamplePath = path.join(projectPath, ".env.example");
1644
- const envContent = (0, import_agent_runtime5.generateGA4EnvTemplate)();
1801
+ const envContent = (0, import_agent_runtime6.generateGA4EnvTemplate)();
1645
1802
  if (fs.existsSync(envExamplePath)) {
1646
1803
  const existing = fs.readFileSync(envExamplePath, "utf-8");
1647
1804
  if (!existing.includes("GA4_MEASUREMENT_ID")) {
@@ -1656,7 +1813,7 @@ async function setupGA4(projectPath, measurementId) {
1656
1813
  }
1657
1814
  async function setupGSC(projectPath, verificationCode) {
1658
1815
  console.log("Setting up Google Search Console verification...");
1659
- const result = await (0, import_agent_runtime5.injectGSCVerification)(projectPath, verificationCode);
1816
+ const result = await (0, import_agent_runtime6.injectGSCVerification)(projectPath, verificationCode);
1660
1817
  if (result.success) {
1661
1818
  console.log(`\u2705 ${result.message}`);
1662
1819
  if (result.file) {
@@ -1680,7 +1837,7 @@ async function setupGitHubAction(projectPath, schedule, siteUrl) {
1680
1837
  createPRs: true
1681
1838
  }
1682
1839
  };
1683
- const { files, instructions } = (0, import_agent_runtime5.writeGitHubActionFiles)(projectPath, config2);
1840
+ const { files, instructions } = (0, import_agent_runtime6.writeGitHubActionFiles)(projectPath, config2);
1684
1841
  console.log("\u2705 GitHub Action files created:");
1685
1842
  for (const file of files) {
1686
1843
  console.log(` - ${path.relative(projectPath, file)}`);
@@ -1688,34 +1845,34 @@ async function setupGitHubAction(projectPath, schedule, siteUrl) {
1688
1845
  console.log(instructions);
1689
1846
  }
1690
1847
  function showGSCInstructions() {
1691
- console.log((0, import_agent_runtime5.getGSCSetupInstructions)());
1848
+ console.log((0, import_agent_runtime6.getGSCSetupInstructions)());
1692
1849
  }
1693
1850
 
1694
1851
  // src/commands/auth.ts
1695
- var import_chalk2 = __toESM(require("chalk"));
1696
- var import_ora2 = __toESM(require("ora"));
1852
+ var import_chalk3 = __toESM(require("chalk"));
1853
+ var import_ora3 = __toESM(require("ora"));
1697
1854
  var import_inquirer = __toESM(require("inquirer"));
1698
1855
  var import_open = __toESM(require("open"));
1699
1856
  async function login(options) {
1700
1857
  if (isLoggedIn()) {
1701
1858
  const { email } = getUserInfo();
1702
- console.log(import_chalk2.default.yellow(`Already logged in as ${email}`));
1703
- console.log(import_chalk2.default.dim('Run "rankcli logout" to sign out first.'));
1859
+ console.log(import_chalk3.default.yellow(`Already logged in as ${email}`));
1860
+ console.log(import_chalk3.default.dim('Run "rankcli logout" to sign out first.'));
1704
1861
  return;
1705
1862
  }
1706
- const spinner = (0, import_ora2.default)();
1863
+ const spinner = (0, import_ora3.default)();
1707
1864
  try {
1708
1865
  if (options.token) {
1709
1866
  const apiKey = options.token;
1710
1867
  if (!apiKey.startsWith("rankcli_")) {
1711
- console.log(import_chalk2.default.red('Invalid API key format. Keys should start with "rankcli_"'));
1868
+ console.log(import_chalk3.default.red('Invalid API key format. Keys should start with "rankcli_"'));
1712
1869
  return;
1713
1870
  }
1714
1871
  spinner.start("Validating API key...");
1715
1872
  const result = await validateApiKey(apiKey);
1716
1873
  if (!result.valid) {
1717
1874
  spinner.fail("Invalid API key");
1718
- console.log(import_chalk2.default.red(`
1875
+ console.log(import_chalk3.default.red(`
1719
1876
  ${result.error}`));
1720
1877
  return;
1721
1878
  }
@@ -1728,14 +1885,14 @@ ${result.error}`));
1728
1885
  });
1729
1886
  }
1730
1887
  spinner.succeed(`Authenticated via API key`);
1731
- console.log(import_chalk2.default.dim(`
1888
+ console.log(import_chalk3.default.dim(`
1732
1889
  Email: ${result.user?.email}`));
1733
- console.log(import_chalk2.default.dim(`Plan: ${result.subscription?.planName || "Free"}`));
1890
+ console.log(import_chalk3.default.dim(`Plan: ${result.subscription?.planName || "Free"}`));
1734
1891
  return;
1735
1892
  }
1736
1893
  if (options.browser) {
1737
- console.log(import_chalk2.default.cyan("\nOpening browser for login..."));
1738
- console.log(import_chalk2.default.dim("Complete the login in your browser, then return here.\n"));
1894
+ console.log(import_chalk3.default.cyan("\nOpening browser for login..."));
1895
+ console.log(import_chalk3.default.dim("Complete the login in your browser, then return here.\n"));
1739
1896
  const loginUrl = `${WEB_APP_URL}/login?cli=true`;
1740
1897
  await (0, import_open.default)(loginUrl);
1741
1898
  const { token } = await import_inquirer.default.prompt([
@@ -1751,7 +1908,7 @@ Email: ${result.user?.email}`));
1751
1908
  const { data, error } = await supabase.auth.getUser(token);
1752
1909
  if (error || !data.user) {
1753
1910
  spinner.fail("Invalid token");
1754
- console.log(import_chalk2.default.red("The token is invalid or expired. Please try again."));
1911
+ console.log(import_chalk3.default.red("The token is invalid or expired. Please try again."));
1755
1912
  return;
1756
1913
  }
1757
1914
  const { data: sessionData, error: sessionError } = await supabase.auth.setSession({
@@ -1765,7 +1922,7 @@ Email: ${result.user?.email}`));
1765
1922
  }
1766
1923
  saveSession(sessionData.session);
1767
1924
  await fetchAndSaveSubscription();
1768
- spinner.succeed(`Logged in as ${import_chalk2.default.green(data.user.email)}`);
1925
+ spinner.succeed(`Logged in as ${import_chalk3.default.green(data.user.email)}`);
1769
1926
  } else {
1770
1927
  let email = options.email;
1771
1928
  if (!email) {
@@ -1798,11 +1955,11 @@ Email: ${result.user?.email}`));
1798
1955
  });
1799
1956
  if (error) {
1800
1957
  spinner.fail("Login failed");
1801
- console.log(import_chalk2.default.red(`
1958
+ console.log(import_chalk3.default.red(`
1802
1959
  ${error.message}`));
1803
1960
  if (error.message.includes("Invalid login")) {
1804
- console.log(import_chalk2.default.dim("\nDon't have an account? Sign up at:"));
1805
- console.log(import_chalk2.default.cyan(` ${WEB_APP_URL}/signup`));
1961
+ console.log(import_chalk3.default.dim("\nDon't have an account? Sign up at:"));
1962
+ console.log(import_chalk3.default.cyan(` ${WEB_APP_URL}/signup`));
1806
1963
  }
1807
1964
  return;
1808
1965
  }
@@ -1812,41 +1969,41 @@ ${error.message}`));
1812
1969
  }
1813
1970
  saveSession(data.session);
1814
1971
  await fetchAndSaveSubscription();
1815
- spinner.succeed(`Logged in as ${import_chalk2.default.green(data.user?.email)}`);
1972
+ spinner.succeed(`Logged in as ${import_chalk3.default.green(data.user?.email)}`);
1816
1973
  const { planName, sitesLimit } = getUserInfo();
1817
- console.log(import_chalk2.default.dim(`
1974
+ console.log(import_chalk3.default.dim(`
1818
1975
  Plan: ${planName} (${sitesLimit} site${sitesLimit !== 1 ? "s" : ""})`));
1819
1976
  }
1820
1977
  } catch (err) {
1821
1978
  spinner.fail("Login failed");
1822
- console.error(import_chalk2.default.red(err instanceof Error ? err.message : "Unknown error"));
1979
+ console.error(import_chalk3.default.red(err instanceof Error ? err.message : "Unknown error"));
1823
1980
  }
1824
1981
  }
1825
1982
  async function logout() {
1826
1983
  if (!isLoggedIn()) {
1827
- console.log(import_chalk2.default.yellow("Not logged in"));
1984
+ console.log(import_chalk3.default.yellow("Not logged in"));
1828
1985
  return;
1829
1986
  }
1830
1987
  const { email } = getUserInfo();
1831
- const spinner = (0, import_ora2.default)("Signing out...").start();
1988
+ const spinner = (0, import_ora3.default)("Signing out...").start();
1832
1989
  try {
1833
1990
  const supabase = getSupabaseClient();
1834
1991
  await supabase.auth.signOut();
1835
1992
  clearCredentials();
1836
- spinner.succeed(`Logged out from ${import_chalk2.default.dim(email)}`);
1993
+ spinner.succeed(`Logged out from ${import_chalk3.default.dim(email)}`);
1837
1994
  } catch (err) {
1838
1995
  spinner.fail("Logout failed");
1839
- console.error(import_chalk2.default.red(err instanceof Error ? err.message : "Unknown error"));
1996
+ console.error(import_chalk3.default.red(err instanceof Error ? err.message : "Unknown error"));
1840
1997
  }
1841
1998
  }
1842
1999
  async function whoami() {
1843
2000
  if (!isLoggedIn()) {
1844
- console.log(import_chalk2.default.yellow("Not logged in"));
1845
- console.log(import_chalk2.default.dim('\nRun "rankcli login" to sign in.'));
1846
- console.log(import_chalk2.default.dim(`Or sign up at: ${WEB_APP_URL}/signup`));
2001
+ console.log(import_chalk3.default.yellow("Not logged in"));
2002
+ console.log(import_chalk3.default.dim('\nRun "rankcli login" to sign in.'));
2003
+ console.log(import_chalk3.default.dim(`Or sign up at: ${WEB_APP_URL}/signup`));
1847
2004
  return;
1848
2005
  }
1849
- const spinner = (0, import_ora2.default)("Checking account...").start();
2006
+ const spinner = (0, import_ora3.default)("Checking account...").start();
1850
2007
  try {
1851
2008
  if (isApiKeyAuth()) {
1852
2009
  const apiKey = getApiKey();
@@ -1866,18 +2023,18 @@ async function whoami() {
1866
2023
  const { email, planName, sitesLimit } = getUserInfo();
1867
2024
  const authMethod = isApiKeyAuth() ? "API Key" : "Session";
1868
2025
  spinner.stop();
1869
- console.log(import_chalk2.default.bold("\nRankCLI Account"));
2026
+ console.log(import_chalk3.default.bold("\nRankCLI Account"));
1870
2027
  console.log("\u2500".repeat(40));
1871
- console.log(` Email: ${import_chalk2.default.cyan(email || "N/A")}`);
1872
- console.log(` Plan: ${import_chalk2.default.green(planName)}`);
2028
+ console.log(` Email: ${import_chalk3.default.cyan(email || "N/A")}`);
2029
+ console.log(` Plan: ${import_chalk3.default.green(planName)}`);
1873
2030
  console.log(` Sites: ${sitesLimit} allowed`);
1874
- console.log(` Auth: ${import_chalk2.default.dim(authMethod)}`);
2031
+ console.log(` Auth: ${import_chalk3.default.dim(authMethod)}`);
1875
2032
  console.log("\u2500".repeat(40));
1876
- console.log(import_chalk2.default.dim(`
2033
+ console.log(import_chalk3.default.dim(`
1877
2034
  Manage at: ${WEB_APP_URL}/account`));
1878
2035
  } catch (err) {
1879
2036
  spinner.fail("Failed to fetch account info");
1880
- console.error(import_chalk2.default.red(err instanceof Error ? err.message : "Unknown error"));
2037
+ console.error(import_chalk3.default.red(err instanceof Error ? err.message : "Unknown error"));
1881
2038
  }
1882
2039
  }
1883
2040
  async function fetchAndSaveSubscription() {
@@ -1909,7 +2066,7 @@ async function fetchAndSaveSubscription() {
1909
2066
  }
1910
2067
 
1911
2068
  // src/version.ts
1912
- var VERSION = "0.0.13";
2069
+ var VERSION = "0.0.15";
1913
2070
 
1914
2071
  // src/index.ts
1915
2072
  var program = new import_commander.Command();
@@ -1921,6 +2078,7 @@ program.command("init").description("Initialize SEO Autopilot in your project").
1921
2078
  program.command("analyze").description("AI analyzes your codebase for SEO opportunities").option("-v, --verbose", "Show detailed output").action(analyze);
1922
2079
  program.command("audit").description("Run a comprehensive SEO audit (170+ checks, Ahrefs-level)").option("-u, --url <url>", "URL to audit").option("-o, --output <format>", "Output format (json, console)", "console").option("--check-links", "Check for broken internal/external links (slower)").option("--max-pages <n>", "Max pages to crawl (default: 5)").option("--ai", "Enable AI analysis").option("--ai-provider <provider>", "AI provider: openai or anthropic").option("--ai-key <key>", "API key (or set OPENAI_API_KEY/ANTHROPIC_API_KEY)").action(audit);
1923
2080
  program.command("apply").description("Analyze a live URL and apply SEO fixes to your codebase").option("-u, --url <url>", "URL to analyze").option("--auto", "Auto-apply fixes without confirmation").option("--dry-run", "Preview changes without applying").action(apply);
2081
+ program.command("geo").description("GEO analysis - Check AI search visibility (ChatGPT, Perplexity, Claude)").option("-u, --url <url>", "URL to analyze").option("-o, --output <format>", "Output format (json, console)", "console").action(geo);
1924
2082
  program.command("keywords").description("AI-powered keyword research with actionable suggestions").option("-u, --url <url>", "Your website URL").option("-s, --seed <keywords>", "Seed keywords (comma-separated)").option("--auto", "Auto-extract seed keywords from your page").option("--quick", "Quick mode - skip questions, use defaults").option("--ai", "Use GPT-4 for enhanced analysis with free tool ideas").option("--local", "Force local processing (skip cloud worker)").option("--competitor", "Competitor gap analysis mode (SpyFu Kombat-style)").option("-c, --competitors <domains>", "Competitor domains (comma-separated)").action(async (options) => {
1925
2083
  if (options.ai && options.url) {
1926
2084
  await keywordsAI(options.url, {