@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.
- package/README.md +103 -207
- package/dist/index.js +239 -81
- package/dist/index.mjs +201 -43
- 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
|
-
|
|
313
|
+
function getSupabaseUrlLazy() {
|
|
314
314
|
if (!_supabaseUrl) _supabaseUrl = getSupabaseUrl();
|
|
315
315
|
return _supabaseUrl;
|
|
316
|
-
}
|
|
317
|
-
|
|
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
|
-
|
|
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)(
|
|
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(`${
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
1150
|
-
console.log((0,
|
|
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,
|
|
1155
|
-
console.log((0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
1207
|
-
console.log((0,
|
|
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
|
|
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(`${
|
|
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,
|
|
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,
|
|
1546
|
+
const result = await (0, import_agent_runtime4.discoverCompetitorKeywords)(
|
|
1390
1547
|
yourDomain,
|
|
1391
1548
|
seedKeywords,
|
|
1392
1549
|
competitors
|
|
1393
1550
|
);
|
|
1394
|
-
console.log((0,
|
|
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
|
|
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,
|
|
1424
|
-
console.log((0,
|
|
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,
|
|
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,
|
|
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,
|
|
1454
|
-
console.log((0,
|
|
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,
|
|
1458
|
-
console.log((0,
|
|
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,
|
|
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,
|
|
1626
|
+
console.log((0, import_agent_runtime5.formatKeywordDensityReport)(densityAnalysis));
|
|
1470
1627
|
}
|
|
1471
1628
|
if (options.keyword && (mode === "full" || mode === "snippet")) {
|
|
1472
|
-
const snippetAnalysis = (0,
|
|
1629
|
+
const snippetAnalysis = (0, import_agent_runtime5.analyzeFeaturedSnippetPotential)(
|
|
1473
1630
|
options.keyword,
|
|
1474
1631
|
bodyText,
|
|
1475
1632
|
headings
|
|
1476
1633
|
);
|
|
1477
|
-
console.log((0,
|
|
1634
|
+
console.log((0, import_agent_runtime5.formatFeaturedSnippetReport)(snippetAnalysis));
|
|
1478
1635
|
}
|
|
1479
1636
|
if (options.keyword) {
|
|
1480
|
-
const intentAnalysis = (0,
|
|
1481
|
-
console.log((0,
|
|
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,
|
|
1509
|
-
console.log((0,
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
1848
|
+
console.log((0, import_agent_runtime6.getGSCSetupInstructions)());
|
|
1692
1849
|
}
|
|
1693
1850
|
|
|
1694
1851
|
// src/commands/auth.ts
|
|
1695
|
-
var
|
|
1696
|
-
var
|
|
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(
|
|
1703
|
-
console.log(
|
|
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,
|
|
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(
|
|
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(
|
|
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(
|
|
1888
|
+
console.log(import_chalk3.default.dim(`
|
|
1732
1889
|
Email: ${result.user?.email}`));
|
|
1733
|
-
console.log(
|
|
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(
|
|
1738
|
-
console.log(
|
|
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(
|
|
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 ${
|
|
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(
|
|
1958
|
+
console.log(import_chalk3.default.red(`
|
|
1802
1959
|
${error.message}`));
|
|
1803
1960
|
if (error.message.includes("Invalid login")) {
|
|
1804
|
-
console.log(
|
|
1805
|
-
console.log(
|
|
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 ${
|
|
1972
|
+
spinner.succeed(`Logged in as ${import_chalk3.default.green(data.user?.email)}`);
|
|
1816
1973
|
const { planName, sitesLimit } = getUserInfo();
|
|
1817
|
-
console.log(
|
|
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(
|
|
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(
|
|
1984
|
+
console.log(import_chalk3.default.yellow("Not logged in"));
|
|
1828
1985
|
return;
|
|
1829
1986
|
}
|
|
1830
1987
|
const { email } = getUserInfo();
|
|
1831
|
-
const spinner = (0,
|
|
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 ${
|
|
1993
|
+
spinner.succeed(`Logged out from ${import_chalk3.default.dim(email)}`);
|
|
1837
1994
|
} catch (err) {
|
|
1838
1995
|
spinner.fail("Logout failed");
|
|
1839
|
-
console.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(
|
|
1845
|
-
console.log(
|
|
1846
|
-
console.log(
|
|
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,
|
|
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(
|
|
2026
|
+
console.log(import_chalk3.default.bold("\nRankCLI Account"));
|
|
1870
2027
|
console.log("\u2500".repeat(40));
|
|
1871
|
-
console.log(` Email: ${
|
|
1872
|
-
console.log(` Plan: ${
|
|
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: ${
|
|
2031
|
+
console.log(` Auth: ${import_chalk3.default.dim(authMethod)}`);
|
|
1875
2032
|
console.log("\u2500".repeat(40));
|
|
1876
|
-
console.log(
|
|
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(
|
|
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.
|
|
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, {
|