@rankcli/cli 0.0.24 → 0.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +1189 -993
  2. package/dist/index.mjs +1147 -949
  3. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -29,262 +29,15 @@ var import_commander = require("commander");
29
29
  // src/commands/init.ts
30
30
  var import_fs = require("fs");
31
31
  var import_path = require("path");
32
- var FRAMEWORKS = [
33
- { pattern: "next.config", name: "Next.js" },
34
- { pattern: "astro.config", name: "Astro" },
35
- { pattern: "nuxt.config", name: "Nuxt" },
36
- { pattern: "svelte.config", name: "SvelteKit" },
37
- { pattern: "gatsby-config", name: "Gatsby" },
38
- { pattern: "vite.config", name: "Vite" },
39
- { pattern: "hugo.toml", name: "Hugo" },
40
- { pattern: "_config.yml", name: "Jekyll" },
41
- { pattern: "angular.json", name: "Angular" },
42
- { pattern: "vue.config", name: "Vue CLI" }
43
- ];
44
- function detectFramework() {
45
- const cwd = process.cwd();
46
- for (const fw of FRAMEWORKS) {
47
- const patterns = [
48
- `${fw.pattern}.js`,
49
- `${fw.pattern}.ts`,
50
- `${fw.pattern}.mjs`,
51
- `${fw.pattern}.cjs`,
52
- fw.pattern
53
- ];
54
- for (const p of patterns) {
55
- if ((0, import_fs.existsSync)((0, import_path.join)(cwd, p))) {
56
- return fw.name;
57
- }
58
- }
59
- }
60
- const pkgPath = (0, import_path.join)(cwd, "package.json");
61
- if ((0, import_fs.existsSync)(pkgPath)) {
62
- try {
63
- const pkg = JSON.parse((0, import_fs.readFileSync)(pkgPath, "utf-8"));
64
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
65
- if (deps["next"]) return "Next.js";
66
- if (deps["astro"]) return "Astro";
67
- if (deps["nuxt"]) return "Nuxt";
68
- if (deps["@sveltejs/kit"]) return "SvelteKit";
69
- if (deps["gatsby"]) return "Gatsby";
70
- if (deps["react"]) return "React";
71
- if (deps["vue"]) return "Vue";
72
- } catch {
73
- }
74
- }
75
- return null;
76
- }
77
- function getProjectName() {
78
- const cwd = process.cwd();
79
- const pkgPath = (0, import_path.join)(cwd, "package.json");
80
- if ((0, import_fs.existsSync)(pkgPath)) {
81
- try {
82
- const pkg = JSON.parse((0, import_fs.readFileSync)(pkgPath, "utf-8"));
83
- if (pkg.name) return pkg.name;
84
- } catch {
85
- }
86
- }
87
- return cwd.split("/").pop() || "unknown";
88
- }
89
- async function init(options) {
90
- console.log("\n\u{1F680} Initializing SEO Autopilot...\n");
91
- const configPath = (0, import_path.join)(process.cwd(), "seo-autopilot.config.json");
92
- if ((0, import_fs.existsSync)(configPath)) {
93
- console.log("\u26A0\uFE0F Configuration file already exists: seo-autopilot.config.json");
94
- console.log(' Run "seo analyze" to analyze your project.\n');
95
- return;
96
- }
97
- const framework = detectFramework();
98
- const projectName = getProjectName();
99
- console.log(`\u{1F4E6} Project: ${projectName}`);
100
- console.log(`\u{1F527} Framework: ${framework || "Unknown (static site?)"}`);
101
- const config2 = {
102
- name: projectName,
103
- framework,
104
- url: null,
105
- initialized: (/* @__PURE__ */ new Date()).toISOString()
106
- };
107
- (0, import_fs.writeFileSync)(configPath, JSON.stringify(config2, null, 2));
108
- console.log("\n\u2705 Created seo-autopilot.config.json");
109
- console.log("\n\u{1F4CB} Next steps:");
110
- console.log(' 1. Run "seo analyze" to analyze your project');
111
- console.log(' 2. Run "seo audit --url <your-site>" to run a technical audit');
112
- console.log(" 3. Connect Google Search Console at https://seo-autopilot.dev/connect\n");
113
- }
114
-
115
- // src/commands/analyze.ts
116
- var import_fs2 = require("fs");
117
- var import_path2 = require("path");
118
- var CONTENT_EXTENSIONS = [".md", ".mdx", ".html", ".tsx", ".jsx", ".vue", ".astro", ".svelte"];
119
- function findFiles(dir, extensions, maxDepth = 3, depth = 0) {
120
- if (depth > maxDepth) return [];
121
- const files = [];
122
- try {
123
- const items = (0, import_fs2.readdirSync)(dir);
124
- for (const item of items) {
125
- if (item.startsWith(".") || item === "node_modules" || item === "dist" || item === "build") {
126
- continue;
127
- }
128
- const fullPath = (0, import_path2.join)(dir, item);
129
- const stat = (0, import_fs2.statSync)(fullPath);
130
- if (stat.isDirectory()) {
131
- files.push(...findFiles(fullPath, extensions, maxDepth, depth + 1));
132
- } else if (extensions.includes((0, import_path2.extname)(item))) {
133
- files.push(fullPath);
134
- }
135
- }
136
- } catch {
137
- }
138
- return files;
139
- }
140
- function extractKeywords(text) {
141
- const words = text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 3);
142
- const frequency = {};
143
- for (const word of words) {
144
- frequency[word] = (frequency[word] || 0) + 1;
145
- }
146
- const stopWords = /* @__PURE__ */ new Set([
147
- "this",
148
- "that",
149
- "with",
150
- "from",
151
- "have",
152
- "been",
153
- "were",
154
- "will",
155
- "would",
156
- "could",
157
- "should",
158
- "their",
159
- "there",
160
- "about",
161
- "which",
162
- "when",
163
- "what",
164
- "your",
165
- "more",
166
- "some",
167
- "just",
168
- "into",
169
- "over",
170
- "such",
171
- "only",
172
- "than",
173
- "them",
174
- "then",
175
- "these",
176
- "most",
177
- "other",
178
- "also",
179
- "being",
180
- "after",
181
- "const",
182
- "function",
183
- "return",
184
- "import",
185
- "export",
186
- "default",
187
- "true",
188
- "false"
189
- ]);
190
- return Object.entries(frequency).filter(([word]) => !stopWords.has(word)).sort((a, b) => b[1] - a[1]).slice(0, 20).map(([word]) => word);
191
- }
192
- function loadConfig() {
193
- const configPath = (0, import_path2.join)(process.cwd(), "seo-autopilot.config.json");
194
- if ((0, import_fs2.existsSync)(configPath)) {
195
- try {
196
- return JSON.parse((0, import_fs2.readFileSync)(configPath, "utf-8"));
197
- } catch {
198
- return null;
199
- }
200
- }
201
- return null;
202
- }
203
- async function analyze(options) {
204
- console.log("\n\u{1F50D} Analyzing project for SEO opportunities...\n");
205
- const cwd = process.cwd();
206
- const config2 = loadConfig();
207
- const result = {
208
- projectName: config2?.name || cwd.split("/").pop() || "unknown",
209
- framework: config2?.framework || null,
210
- hasReadme: false,
211
- readmeSummary: null,
212
- pageCount: 0,
213
- hasMetaTags: false,
214
- hasSitemap: false,
215
- hasRobotsTxt: false,
216
- keywords: [],
217
- suggestions: []
218
- };
219
- const readmePaths = ["README.md", "readme.md", "Readme.md", "README.MD"];
220
- for (const rp of readmePaths) {
221
- if ((0, import_fs2.existsSync)((0, import_path2.join)(cwd, rp))) {
222
- result.hasReadme = true;
223
- const content = (0, import_fs2.readFileSync)((0, import_path2.join)(cwd, rp), "utf-8");
224
- result.readmeSummary = content.slice(0, 200).replace(/\n/g, " ").trim() + "...";
225
- result.keywords.push(...extractKeywords(content));
226
- break;
227
- }
228
- }
229
- result.hasSitemap = (0, import_fs2.existsSync)((0, import_path2.join)(cwd, "public", "sitemap.xml")) || (0, import_fs2.existsSync)((0, import_path2.join)(cwd, "sitemap.xml"));
230
- result.hasRobotsTxt = (0, import_fs2.existsSync)((0, import_path2.join)(cwd, "public", "robots.txt")) || (0, import_fs2.existsSync)((0, import_path2.join)(cwd, "robots.txt"));
231
- const contentFiles = findFiles(cwd, CONTENT_EXTENSIONS);
232
- result.pageCount = contentFiles.length;
233
- const htmlFiles = contentFiles.filter((f) => f.endsWith(".html") || f.includes("_document") || f.includes("layout"));
234
- for (const hf of htmlFiles.slice(0, 5)) {
235
- const content = (0, import_fs2.readFileSync)(hf, "utf-8");
236
- if (content.includes("<meta") || content.includes("metadata")) {
237
- result.hasMetaTags = true;
238
- break;
239
- }
240
- }
241
- for (const cf of contentFiles.slice(0, 10)) {
242
- const content = (0, import_fs2.readFileSync)(cf, "utf-8");
243
- result.keywords.push(...extractKeywords(content));
244
- }
245
- result.keywords = [...new Set(result.keywords)].slice(0, 20);
246
- if (!result.hasReadme) {
247
- result.suggestions.push("Add a README.md with clear project description");
248
- }
249
- if (!result.hasSitemap) {
250
- result.suggestions.push("Generate a sitemap.xml for better crawlability");
251
- }
252
- if (!result.hasRobotsTxt) {
253
- result.suggestions.push("Add robots.txt to guide search engine crawlers");
254
- }
255
- if (!result.hasMetaTags) {
256
- result.suggestions.push("Add meta tags (title, description, og:*) to pages");
257
- }
258
- if (result.pageCount < 5) {
259
- result.suggestions.push("Consider adding more content pages for SEO coverage");
260
- }
261
- console.log("\u{1F4CA} Analysis Results\n");
262
- console.log(`Project: ${result.projectName}`);
263
- console.log(`Framework: ${result.framework || "Unknown"}`);
264
- console.log(`Content files: ${result.pageCount}`);
265
- console.log(`Has README: ${result.hasReadme ? "\u2705" : "\u274C"}`);
266
- console.log(`Has sitemap.xml: ${result.hasSitemap ? "\u2705" : "\u274C"}`);
267
- console.log(`Has robots.txt: ${result.hasRobotsTxt ? "\u2705" : "\u274C"}`);
268
- console.log(`Has meta tags: ${result.hasMetaTags ? "\u2705" : "\u274C"}`);
269
- if (result.keywords.length > 0) {
270
- console.log("\n\u{1F511} Detected Keywords:");
271
- console.log(` ${result.keywords.join(", ")}`);
272
- }
273
- if (result.suggestions.length > 0) {
274
- console.log("\n\u{1F4A1} Suggestions:");
275
- for (const s of result.suggestions) {
276
- console.log(` \u2022 ${s}`);
277
- }
278
- } else {
279
- console.log("\n\u2705 No critical SEO issues found!");
280
- }
281
- console.log('\n\u{1F4C8} Next: Run "seo audit --url <your-deployed-site>" for a full technical audit\n');
282
- }
32
+ var import_child_process = require("child_process");
33
+ var import_chalk2 = __toESM(require("chalk"));
34
+ var import_inquirer2 = __toESM(require("inquirer"));
283
35
 
284
- // src/commands/audit.ts
285
- var import_agent_runtime = require("@rankcli/agent-runtime");
36
+ // src/commands/auth.ts
37
+ var import_chalk = __toESM(require("chalk"));
286
38
  var import_ora = __toESM(require("ora"));
287
- var import_chalk2 = __toESM(require("chalk"));
39
+ var import_inquirer = __toESM(require("inquirer"));
40
+ var import_open = __toESM(require("open"));
288
41
 
289
42
  // src/lib/config.ts
290
43
  var import_conf = __toESM(require("conf"));
@@ -373,11 +126,14 @@ function isApiKeyAuth() {
373
126
  function getApiKey() {
374
127
  return process.env.RANKCLI_API_KEY || config.get("apiKey") || null;
375
128
  }
376
- function saveApiKey(apiKey, keyName) {
129
+ function saveApiKey(apiKey, keyName, email) {
377
130
  config.set("apiKey", apiKey);
378
131
  if (keyName) {
379
132
  config.set("apiKeyName", keyName);
380
133
  }
134
+ if (email) {
135
+ config.set("email", email);
136
+ }
381
137
  }
382
138
  async function validateApiKey(apiKey) {
383
139
  try {
@@ -483,14 +239,806 @@ async function getAuthenticatedClient() {
483
239
  return supabase;
484
240
  }
485
241
 
486
- // src/lib/upsell.ts
487
- var import_chalk = __toESM(require("chalk"));
488
- var WEB_APP_URL2 = "https://rankcli.dev";
489
- var TIER_LIMITS = {
490
- free_anon: {
491
- checks: 50,
492
- sites: 0,
493
- domains_per_month: 1,
242
+ // src/commands/auth.ts
243
+ async function browserLogin(spinner) {
244
+ spinner.start("Starting login...");
245
+ let start;
246
+ try {
247
+ const res = await fetch(`${getApiUrl()}/functions/v1/mcp-connect-start`, { method: "POST" });
248
+ if (!res.ok) throw new Error(`status ${res.status}`);
249
+ start = await res.json();
250
+ } catch (err) {
251
+ spinner.fail("Could not start login");
252
+ console.log(import_chalk.default.red(err instanceof Error ? err.message : "Network error"));
253
+ return;
254
+ }
255
+ spinner.text = "Opening browser to approve...";
256
+ try {
257
+ await (0, import_open.default)(start.url);
258
+ } catch {
259
+ }
260
+ console.log(import_chalk.default.dim(`
261
+ If a browser didn't open, visit:
262
+ ${start.url}
263
+ `));
264
+ const deadline = Date.now() + start.expiresInSeconds * 1e3;
265
+ spinner.text = "Waiting for approval in your browser...";
266
+ while (Date.now() < deadline) {
267
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
268
+ let status;
269
+ try {
270
+ const res = await fetch(`${getApiUrl()}/functions/v1/mcp-connect-status`, {
271
+ method: "POST",
272
+ headers: { "Content-Type": "application/json" },
273
+ body: JSON.stringify({ connectId: start.connectId })
274
+ });
275
+ status = await res.json();
276
+ } catch {
277
+ continue;
278
+ }
279
+ if (status.status === "claimed" && status.apiKey) {
280
+ saveApiKey(status.apiKey, status.apiKeyName, status.email);
281
+ await fetchAndSaveSubscription();
282
+ spinner.succeed(`Logged in as ${import_chalk.default.green(status.email)}`);
283
+ return;
284
+ }
285
+ if (status.status === "expired" || status.status === "not_found") {
286
+ spinner.fail("Login link expired");
287
+ console.log(import_chalk.default.dim('Run "rankcli login" again.'));
288
+ return;
289
+ }
290
+ }
291
+ spinner.fail("Timed out waiting for approval");
292
+ console.log(import_chalk.default.dim(`Run "rankcli login" again when you're ready.`));
293
+ }
294
+ async function login(options) {
295
+ if (isLoggedIn()) {
296
+ const { email } = getUserInfo();
297
+ console.log(import_chalk.default.yellow(`Already logged in as ${email}`));
298
+ console.log(import_chalk.default.dim('Run "rankcli logout" to sign out first.'));
299
+ return;
300
+ }
301
+ const spinner = (0, import_ora.default)();
302
+ try {
303
+ if (options.token) {
304
+ const apiKey = options.token;
305
+ if (!apiKey.startsWith("rankcli_")) {
306
+ console.log(import_chalk.default.red('Invalid API key format. Keys should start with "rankcli_"'));
307
+ return;
308
+ }
309
+ spinner.start("Validating API key...");
310
+ const result = await validateApiKey(apiKey);
311
+ if (!result.valid) {
312
+ spinner.fail("Invalid API key");
313
+ console.log(import_chalk.default.red(`
314
+ ${result.error}`));
315
+ return;
316
+ }
317
+ saveApiKey(apiKey);
318
+ if (result.subscription) {
319
+ saveSubscription({
320
+ planId: result.subscription.planId,
321
+ planName: result.subscription.planName,
322
+ sitesLimit: result.subscription.sitesLimit
323
+ });
324
+ }
325
+ spinner.succeed(`Authenticated via API key`);
326
+ console.log(import_chalk.default.dim(`
327
+ Email: ${result.user?.email}`));
328
+ console.log(import_chalk.default.dim(`Plan: ${result.subscription?.planName || "Free"}`));
329
+ return;
330
+ }
331
+ if (options.browser !== false && !options.email) {
332
+ await browserLogin(spinner);
333
+ } else {
334
+ let email = options.email;
335
+ if (!email) {
336
+ const answers = await import_inquirer.default.prompt([
337
+ {
338
+ type: "input",
339
+ name: "email",
340
+ message: "Email:",
341
+ validate: (input) => {
342
+ if (!input.includes("@")) return "Please enter a valid email";
343
+ return true;
344
+ }
345
+ }
346
+ ]);
347
+ email = answers.email;
348
+ }
349
+ const { password } = await import_inquirer.default.prompt([
350
+ {
351
+ type: "password",
352
+ name: "password",
353
+ message: "Password:",
354
+ mask: "*"
355
+ }
356
+ ]);
357
+ spinner.start("Signing in...");
358
+ const supabase = getSupabaseClient();
359
+ const { data, error } = await supabase.auth.signInWithPassword({
360
+ email,
361
+ password
362
+ });
363
+ if (error) {
364
+ spinner.fail("Login failed");
365
+ console.log(import_chalk.default.red(`
366
+ ${error.message}`));
367
+ if (error.message.includes("Invalid login")) {
368
+ console.log(import_chalk.default.dim("\nDon't have an account? Sign up at:"));
369
+ console.log(import_chalk.default.cyan(` ${WEB_APP_URL}/signup`));
370
+ }
371
+ return;
372
+ }
373
+ if (!data.session) {
374
+ spinner.fail("No session returned");
375
+ return;
376
+ }
377
+ saveSession(data.session);
378
+ await fetchAndSaveSubscription();
379
+ spinner.succeed(`Logged in as ${import_chalk.default.green(data.user?.email)}`);
380
+ const { planName, sitesLimit } = getUserInfo();
381
+ console.log(import_chalk.default.dim(`
382
+ Plan: ${planName} (${sitesLimit} site${sitesLimit !== 1 ? "s" : ""})`));
383
+ }
384
+ } catch (err) {
385
+ spinner.fail("Login failed");
386
+ console.error(import_chalk.default.red(err instanceof Error ? err.message : "Unknown error"));
387
+ }
388
+ }
389
+ async function logout() {
390
+ if (!isLoggedIn()) {
391
+ console.log(import_chalk.default.yellow("Not logged in"));
392
+ return;
393
+ }
394
+ const { email } = getUserInfo();
395
+ const spinner = (0, import_ora.default)("Signing out...").start();
396
+ try {
397
+ const supabase = getSupabaseClient();
398
+ await supabase.auth.signOut();
399
+ clearCredentials();
400
+ spinner.succeed(`Logged out from ${import_chalk.default.dim(email)}`);
401
+ } catch (err) {
402
+ spinner.fail("Logout failed");
403
+ console.error(import_chalk.default.red(err instanceof Error ? err.message : "Unknown error"));
404
+ }
405
+ }
406
+ async function whoami() {
407
+ if (!isLoggedIn()) {
408
+ console.log(import_chalk.default.yellow("Not logged in"));
409
+ console.log(import_chalk.default.dim('\nRun "rankcli login" to sign in.'));
410
+ console.log(import_chalk.default.dim(`Or sign up at: ${WEB_APP_URL}/signup`));
411
+ return;
412
+ }
413
+ const spinner = (0, import_ora.default)("Checking account...").start();
414
+ try {
415
+ if (isApiKeyAuth()) {
416
+ const apiKey = getApiKey();
417
+ if (apiKey) {
418
+ const result = await validateApiKey(apiKey);
419
+ if (result.valid && result.subscription) {
420
+ saveSubscription({
421
+ planId: result.subscription.planId,
422
+ planName: result.subscription.planName,
423
+ sitesLimit: result.subscription.sitesLimit
424
+ });
425
+ }
426
+ }
427
+ } else {
428
+ await fetchAndSaveSubscription();
429
+ }
430
+ const { email, planName, sitesLimit } = getUserInfo();
431
+ const authMethod = isApiKeyAuth() ? "API Key" : "Session";
432
+ spinner.stop();
433
+ console.log(import_chalk.default.bold("\nRankCLI Account"));
434
+ console.log("\u2500".repeat(40));
435
+ console.log(` Email: ${import_chalk.default.cyan(email || "N/A")}`);
436
+ console.log(` Plan: ${import_chalk.default.green(planName)}`);
437
+ console.log(` Sites: ${sitesLimit} allowed`);
438
+ console.log(` Auth: ${import_chalk.default.dim(authMethod)}`);
439
+ console.log("\u2500".repeat(40));
440
+ console.log(import_chalk.default.dim(`
441
+ Manage at: ${WEB_APP_URL}/account`));
442
+ } catch (err) {
443
+ spinner.fail("Failed to fetch account info");
444
+ console.error(import_chalk.default.red(err instanceof Error ? err.message : "Unknown error"));
445
+ }
446
+ }
447
+ async function fetchAndSaveSubscription() {
448
+ const supabase = await getAuthenticatedClient();
449
+ if (!supabase) return;
450
+ const { userId } = getUserInfo();
451
+ if (!userId) return;
452
+ const { data: subscription } = await supabase.from("user_subscriptions").select(`
453
+ plan:subscription_plans (
454
+ plan_id,
455
+ name,
456
+ sites_limit
457
+ )
458
+ `).eq("user_id", userId).single();
459
+ if (subscription?.plan) {
460
+ const plan = subscription.plan;
461
+ saveSubscription({
462
+ planId: plan.plan_id,
463
+ planName: plan.name,
464
+ sitesLimit: plan.sites_limit
465
+ });
466
+ } else {
467
+ saveSubscription({
468
+ planId: void 0,
469
+ planName: "Free",
470
+ sitesLimit: 1
471
+ });
472
+ }
473
+ }
474
+
475
+ // src/commands/setup.ts
476
+ var readline = __toESM(require("readline"));
477
+ var fs = __toESM(require("fs"));
478
+ var path = __toESM(require("path"));
479
+ var import_agent_runtime = require("@rankcli/agent-runtime");
480
+ async function setup(options) {
481
+ console.log("\n\u{1F527} RankCLI - Setup\n");
482
+ const projectPath = process.cwd();
483
+ if (!fs.existsSync(path.join(projectPath, "package.json")) && !fs.existsSync(path.join(projectPath, "index.html"))) {
484
+ console.log("\u26A0\uFE0F No package.json or index.html found. Are you in a web project directory?\n");
485
+ }
486
+ if (options.all || !options.ga4 && !options.gsc && !options.githubAction) {
487
+ await interactiveSetup(projectPath);
488
+ return;
489
+ }
490
+ if (options.ga4) {
491
+ await setupGA4(projectPath, options.ga4);
492
+ }
493
+ if (options.gsc) {
494
+ await setupGSC(projectPath, options.gsc);
495
+ }
496
+ if (options.githubAction) {
497
+ await setupGitHubAction(projectPath, options.schedule || "weekly");
498
+ }
499
+ }
500
+ async function interactiveSetup(projectPath) {
501
+ const rl = readline.createInterface({
502
+ input: process.stdin,
503
+ output: process.stdout
504
+ });
505
+ const ask2 = (question) => {
506
+ return new Promise((resolve) => {
507
+ rl.question(question, (answer) => {
508
+ resolve(answer.trim());
509
+ });
510
+ });
511
+ };
512
+ try {
513
+ console.log("This wizard will help you set up SEO tracking for your project.\n");
514
+ console.log("\u2500".repeat(60));
515
+ console.log("\u{1F4CA} STEP 1: Google Analytics 4\n");
516
+ console.log("GA4 tracks visitor behavior on your site.");
517
+ console.log("Get your Measurement ID from: https://analytics.google.com");
518
+ console.log("Go to: Admin > Data Streams > Your Stream > Measurement ID\n");
519
+ const ga4Id = await ask2("Enter GA4 Measurement ID (G-XXXXXXXXXX) or press Enter to skip: ");
520
+ if (ga4Id && ga4Id.startsWith("G-")) {
521
+ await setupGA4(projectPath, ga4Id);
522
+ } else if (ga4Id) {
523
+ console.log('\u26A0\uFE0F Invalid format. GA4 IDs start with "G-". Skipping.\n');
524
+ } else {
525
+ console.log("Skipping GA4 setup.\n");
526
+ }
527
+ console.log("\u2500".repeat(60));
528
+ console.log("\u{1F50D} STEP 2: Google Search Console\n");
529
+ console.log("GSC shows how Google sees your site and what queries you rank for.");
530
+ console.log("Get your verification code from: https://search.google.com/search-console");
531
+ console.log("Go to: Settings > Ownership verification > HTML tag\n");
532
+ const gscCode = await ask2("Enter GSC verification code or press Enter to skip: ");
533
+ if (gscCode && gscCode.length > 10) {
534
+ await setupGSC(projectPath, gscCode);
535
+ } else if (gscCode) {
536
+ console.log("\u26A0\uFE0F Code seems too short. Skipping.\n");
537
+ } else {
538
+ console.log("Skipping GSC verification.\n");
539
+ }
540
+ console.log("\u2500".repeat(60));
541
+ console.log("\u{1F916} STEP 3: Automated SEO Monitoring\n");
542
+ console.log("A GitHub Action can automatically check your site's SEO on a schedule.");
543
+ console.log("It creates issues with reports and can auto-fix some problems.\n");
544
+ const setupAction = await ask2("Set up GitHub Action? (y/n): ");
545
+ if (setupAction.toLowerCase() === "y" || setupAction.toLowerCase() === "yes") {
546
+ console.log("\nHow often should the check run?");
547
+ console.log(" 1. Weekly (recommended for most sites)");
548
+ console.log(" 2. Daily (for high-traffic sites)");
549
+ console.log(" 3. Monthly (for low-traffic sites)\n");
550
+ const scheduleChoice = await ask2("Enter choice (1-3): ");
551
+ const scheduleMap = {
552
+ "1": "weekly",
553
+ "2": "daily",
554
+ "3": "monthly"
555
+ };
556
+ const schedule = scheduleMap[scheduleChoice] || "weekly";
557
+ const siteUrl = await ask2("Enter your site URL (https://example.com): ");
558
+ if (siteUrl) {
559
+ const syncChoice = await ask2("Send scheduled audits to your RankCLI dashboard? (y/n): ");
560
+ const syncToDashboard = syncChoice.toLowerCase() === "y" || syncChoice.toLowerCase() === "yes";
561
+ await setupGitHubAction(projectPath, schedule, siteUrl, syncToDashboard);
562
+ }
563
+ } else {
564
+ console.log("Skipping GitHub Action setup.\n");
565
+ }
566
+ console.log("\u2500".repeat(60));
567
+ console.log("\u2705 Setup complete!\n");
568
+ console.log("Next steps:");
569
+ console.log(" 1. Commit the changes to your repository");
570
+ console.log(" 2. If using GitHub Actions, add required secrets (see .github/SEO_SETUP.md)");
571
+ console.log(" 3. Deploy your site to apply tracking code changes");
572
+ console.log(" 4. Verify GSC ownership and GA4 data collection\n");
573
+ rl.close();
574
+ } catch (error) {
575
+ console.log(`
576
+ \u274C Error: ${error instanceof Error ? error.message : "Unknown error"}
577
+ `);
578
+ rl.close();
579
+ }
580
+ }
581
+ async function setupGA4(projectPath, measurementId) {
582
+ console.log(`Setting up Google Analytics 4 (${measurementId})...`);
583
+ const result = await (0, import_agent_runtime.injectGA4)(projectPath, { measurementId });
584
+ if (result.success) {
585
+ console.log(`\u2705 ${result.message}`);
586
+ if (result.file) {
587
+ console.log(` Modified: ${result.file}`);
588
+ }
589
+ } else {
590
+ console.log(`\u26A0\uFE0F ${result.message}`);
591
+ if (result.code) {
592
+ console.log("\nManually add this code to your HTML <head>:\n");
593
+ console.log(result.code);
594
+ }
595
+ }
596
+ const envExamplePath = path.join(projectPath, ".env.example");
597
+ const envContent = (0, import_agent_runtime.generateGA4EnvTemplate)();
598
+ if (fs.existsSync(envExamplePath)) {
599
+ const existing = fs.readFileSync(envExamplePath, "utf-8");
600
+ if (!existing.includes("GA4_MEASUREMENT_ID")) {
601
+ fs.appendFileSync(envExamplePath, "\n" + envContent);
602
+ console.log(" Updated: .env.example");
603
+ }
604
+ } else {
605
+ fs.writeFileSync(envExamplePath, envContent);
606
+ console.log(" Created: .env.example");
607
+ }
608
+ console.log("");
609
+ }
610
+ async function setupGSC(projectPath, verificationCode) {
611
+ console.log("Setting up Google Search Console verification...");
612
+ const result = await (0, import_agent_runtime.injectGSCVerification)(projectPath, verificationCode);
613
+ if (result.success) {
614
+ console.log(`\u2705 ${result.message}`);
615
+ if (result.file) {
616
+ console.log(` Modified: ${result.file}`);
617
+ }
618
+ } else {
619
+ console.log(`\u26A0\uFE0F ${result.message}`);
620
+ }
621
+ console.log("");
622
+ }
623
+ async function setupGitHubAction(projectPath, schedule, siteUrl, syncToDashboard) {
624
+ console.log(`Setting up GitHub Action (${schedule} schedule)...`);
625
+ const config2 = {
626
+ schedule,
627
+ siteUrl: siteUrl || "https://example.com",
628
+ features: {
629
+ audit: true,
630
+ autoFix: true,
631
+ createIssues: true,
632
+ createPRs: true
633
+ },
634
+ syncToDashboard
635
+ };
636
+ const { files, instructions } = (0, import_agent_runtime.writeGitHubActionFiles)(projectPath, config2);
637
+ console.log("\u2705 GitHub Action files created:");
638
+ for (const file of files) {
639
+ console.log(` - ${path.relative(projectPath, file)}`);
640
+ }
641
+ console.log(instructions);
642
+ }
643
+ function showGSCInstructions() {
644
+ console.log((0, import_agent_runtime.getGSCSetupInstructions)());
645
+ }
646
+
647
+ // src/commands/init.ts
648
+ var FRAMEWORKS = [
649
+ { pattern: "next.config", name: "Next.js" },
650
+ { pattern: "astro.config", name: "Astro" },
651
+ { pattern: "nuxt.config", name: "Nuxt" },
652
+ { pattern: "svelte.config", name: "SvelteKit" },
653
+ { pattern: "gatsby-config", name: "Gatsby" },
654
+ { pattern: "vite.config", name: "Vite" },
655
+ { pattern: "hugo.toml", name: "Hugo" },
656
+ { pattern: "_config.yml", name: "Jekyll" },
657
+ { pattern: "angular.json", name: "Angular" },
658
+ { pattern: "vue.config", name: "Vue CLI" }
659
+ ];
660
+ var AI_KEY_ENV_VARS = [
661
+ { env: "OPENAI_API_KEY", label: "OpenAI" },
662
+ { env: "ANTHROPIC_API_KEY", label: "Anthropic" },
663
+ { env: "OPENROUTER_API_KEY", label: "OpenRouter" },
664
+ { env: "GROQ_API_KEY", label: "Groq" },
665
+ { env: "GEMINI_API_KEY", label: "Gemini" },
666
+ { env: "GOOGLE_API_KEY", label: "Gemini" }
667
+ ];
668
+ function detectFramework() {
669
+ const cwd = process.cwd();
670
+ for (const fw of FRAMEWORKS) {
671
+ const patterns = [
672
+ `${fw.pattern}.js`,
673
+ `${fw.pattern}.ts`,
674
+ `${fw.pattern}.mjs`,
675
+ `${fw.pattern}.cjs`,
676
+ fw.pattern
677
+ ];
678
+ for (const p of patterns) {
679
+ if ((0, import_fs.existsSync)((0, import_path.join)(cwd, p))) {
680
+ return fw.name;
681
+ }
682
+ }
683
+ }
684
+ const pkgPath = (0, import_path.join)(cwd, "package.json");
685
+ if ((0, import_fs.existsSync)(pkgPath)) {
686
+ try {
687
+ const pkg = JSON.parse((0, import_fs.readFileSync)(pkgPath, "utf-8"));
688
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
689
+ if (deps["next"]) return "Next.js";
690
+ if (deps["astro"]) return "Astro";
691
+ if (deps["nuxt"]) return "Nuxt";
692
+ if (deps["@sveltejs/kit"]) return "SvelteKit";
693
+ if (deps["gatsby"]) return "Gatsby";
694
+ if (deps["react"]) return "React";
695
+ if (deps["vue"]) return "Vue";
696
+ } catch {
697
+ }
698
+ }
699
+ return null;
700
+ }
701
+ function getProjectName() {
702
+ const cwd = process.cwd();
703
+ const pkgPath = (0, import_path.join)(cwd, "package.json");
704
+ if ((0, import_fs.existsSync)(pkgPath)) {
705
+ try {
706
+ const pkg = JSON.parse((0, import_fs.readFileSync)(pkgPath, "utf-8"));
707
+ if (pkg.name) return pkg.name;
708
+ } catch {
709
+ }
710
+ }
711
+ return cwd.split("/").pop() || "unknown";
712
+ }
713
+ function runGit(args) {
714
+ try {
715
+ return (0, import_child_process.execSync)(`git ${args}`, { cwd: process.cwd(), stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
716
+ } catch {
717
+ return null;
718
+ }
719
+ }
720
+ function parseGitHubRepo(remoteUrl) {
721
+ const match = remoteUrl.match(/github\.com[:/]([^/]+)\/(.+?)(?:\.git)?$/);
722
+ return match ? `${match[1]}/${match[2]}` : null;
723
+ }
724
+ function detectConnectedAiKeys() {
725
+ const found = /* @__PURE__ */ new Set();
726
+ for (const { env, label } of AI_KEY_ENV_VARS) {
727
+ if (process.env[env]) found.add(label);
728
+ }
729
+ return Array.from(found);
730
+ }
731
+ async function init(options) {
732
+ console.log(import_chalk2.default.bold("\n\u{1F680} Initializing RankCLI...\n"));
733
+ const configPath = (0, import_path.join)(process.cwd(), "rankcli.config.json");
734
+ if ((0, import_fs.existsSync)(configPath)) {
735
+ console.log(import_chalk2.default.yellow("\u26A0\uFE0F Configuration file already exists: rankcli.config.json"));
736
+ console.log(import_chalk2.default.dim(' Run "rankcli audit" to analyze your project.\n'));
737
+ return;
738
+ }
739
+ const framework = detectFramework();
740
+ const projectName = getProjectName();
741
+ const isGitRepo = runGit("rev-parse --is-inside-work-tree") === "true";
742
+ const remoteUrl = isGitRepo ? runGit("remote get-url origin") : null;
743
+ const githubRepo = remoteUrl ? parseGitHubRepo(remoteUrl) : null;
744
+ const connectedAiKeys = detectConnectedAiKeys();
745
+ console.log(`\u{1F4E6} Project: ${import_chalk2.default.cyan(projectName)}`);
746
+ console.log(`\u{1F527} Framework: ${import_chalk2.default.cyan(framework || "Unknown (static site?)")}`);
747
+ if (githubRepo) {
748
+ console.log(`\u{1F419} GitHub repo: ${import_chalk2.default.cyan(githubRepo)}`);
749
+ } else if (isGitRepo) {
750
+ console.log(import_chalk2.default.dim("\u{1F419} Git repo detected, but no GitHub remote found"));
751
+ }
752
+ if (connectedAiKeys.length > 0) {
753
+ console.log(`\u{1F511} AI key detected: ${import_chalk2.default.cyan(connectedAiKeys.join(", "))} (will be used automatically for --ai audits)`);
754
+ }
755
+ const config2 = {
756
+ name: projectName,
757
+ framework,
758
+ url: null,
759
+ githubRepo,
760
+ initialized: (/* @__PURE__ */ new Date()).toISOString()
761
+ };
762
+ (0, import_fs.writeFileSync)(configPath, JSON.stringify(config2, null, 2));
763
+ console.log(import_chalk2.default.green("\n\u2705 Created rankcli.config.json"));
764
+ if (options.yes) {
765
+ printNextSteps(githubRepo);
766
+ return;
767
+ }
768
+ if (!isLoggedIn()) {
769
+ console.log("");
770
+ const { shouldConnect } = await import_inquirer2.default.prompt([
771
+ {
772
+ type: "confirm",
773
+ name: "shouldConnect",
774
+ message: "Connect a RankCLI account? Unlocks a dashboard, audit history, and GitHub auto-fix PRs (opens your browser).",
775
+ default: true
776
+ }
777
+ ]);
778
+ if (shouldConnect) {
779
+ await login({});
780
+ }
781
+ } else {
782
+ const { email } = getUserInfo();
783
+ console.log(import_chalk2.default.dim(`
784
+ Already connected as ${email}.`));
785
+ }
786
+ const workflowPath = (0, import_path.join)(process.cwd(), ".github", "workflows", "seo.yml");
787
+ if (githubRepo && !(0, import_fs.existsSync)(workflowPath)) {
788
+ console.log("");
789
+ console.log(import_chalk2.default.bold("\u{1F916} Automate SEO/GEO checks in CI"));
790
+ console.log(import_chalk2.default.dim(" Privacy: by default nothing is sent to RankCLI - the audit runs"));
791
+ console.log(import_chalk2.default.dim(" entirely inside your own GitHub Actions runner. If you opt into"));
792
+ console.log(import_chalk2.default.dim(" dashboard sync below, audit results (the URL and issues found,"));
793
+ console.log(import_chalk2.default.dim(" not your source code) are sent to your RankCLI account.\n"));
794
+ const { shouldSetupAction } = await import_inquirer2.default.prompt([
795
+ {
796
+ type: "confirm",
797
+ name: "shouldSetupAction",
798
+ message: `Set up a GitHub Action for ${githubRepo}?`,
799
+ default: true
800
+ }
801
+ ]);
802
+ if (shouldSetupAction) {
803
+ const { siteUrl } = await import_inquirer2.default.prompt([
804
+ {
805
+ type: "input",
806
+ name: "siteUrl",
807
+ message: "Site URL to audit (https://example.com):",
808
+ validate: (input) => {
809
+ try {
810
+ new URL(input);
811
+ return true;
812
+ } catch {
813
+ return "Please enter a valid URL";
814
+ }
815
+ }
816
+ }
817
+ ]);
818
+ const { schedule } = await import_inquirer2.default.prompt([
819
+ {
820
+ type: "list",
821
+ name: "schedule",
822
+ message: "How often should it run?",
823
+ choices: [
824
+ { name: "Weekly (recommended)", value: "weekly" },
825
+ { name: "Daily", value: "daily" },
826
+ { name: "Monthly", value: "monthly" }
827
+ ],
828
+ default: "weekly"
829
+ }
830
+ ]);
831
+ let syncToDashboard = false;
832
+ if (isLoggedIn()) {
833
+ const answer = await import_inquirer2.default.prompt([
834
+ {
835
+ type: "confirm",
836
+ name: "syncToDashboard",
837
+ message: "Also send scheduled audits to your RankCLI dashboard?",
838
+ default: true
839
+ }
840
+ ]);
841
+ syncToDashboard = answer.syncToDashboard;
842
+ }
843
+ await setupGitHubAction(process.cwd(), schedule, siteUrl, syncToDashboard);
844
+ config2.url = siteUrl;
845
+ (0, import_fs.writeFileSync)(configPath, JSON.stringify(config2, null, 2));
846
+ }
847
+ }
848
+ printNextSteps(githubRepo);
849
+ }
850
+ function printNextSteps(githubRepo) {
851
+ console.log(import_chalk2.default.bold("\n\u{1F4CB} Next steps:"));
852
+ console.log(' 1. Run "rankcli audit --url <your-site>" for a full technical audit');
853
+ console.log(' 2. Run "rankcli apply --auto" to apply fixes locally');
854
+ if (!githubRepo) {
855
+ console.log(' 3. Push to GitHub, then re-run "rankcli init" to set up automated checks');
856
+ }
857
+ console.log("");
858
+ }
859
+
860
+ // src/commands/analyze.ts
861
+ var import_fs2 = require("fs");
862
+ var import_path2 = require("path");
863
+ var CONTENT_EXTENSIONS = [".md", ".mdx", ".html", ".tsx", ".jsx", ".vue", ".astro", ".svelte"];
864
+ function findFiles(dir, extensions, maxDepth = 3, depth = 0) {
865
+ if (depth > maxDepth) return [];
866
+ const files = [];
867
+ try {
868
+ const items = (0, import_fs2.readdirSync)(dir);
869
+ for (const item of items) {
870
+ if (item.startsWith(".") || item === "node_modules" || item === "dist" || item === "build") {
871
+ continue;
872
+ }
873
+ const fullPath = (0, import_path2.join)(dir, item);
874
+ const stat = (0, import_fs2.statSync)(fullPath);
875
+ if (stat.isDirectory()) {
876
+ files.push(...findFiles(fullPath, extensions, maxDepth, depth + 1));
877
+ } else if (extensions.includes((0, import_path2.extname)(item))) {
878
+ files.push(fullPath);
879
+ }
880
+ }
881
+ } catch {
882
+ }
883
+ return files;
884
+ }
885
+ function extractKeywords(text) {
886
+ const words = text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 3);
887
+ const frequency = {};
888
+ for (const word of words) {
889
+ frequency[word] = (frequency[word] || 0) + 1;
890
+ }
891
+ const stopWords = /* @__PURE__ */ new Set([
892
+ "this",
893
+ "that",
894
+ "with",
895
+ "from",
896
+ "have",
897
+ "been",
898
+ "were",
899
+ "will",
900
+ "would",
901
+ "could",
902
+ "should",
903
+ "their",
904
+ "there",
905
+ "about",
906
+ "which",
907
+ "when",
908
+ "what",
909
+ "your",
910
+ "more",
911
+ "some",
912
+ "just",
913
+ "into",
914
+ "over",
915
+ "such",
916
+ "only",
917
+ "than",
918
+ "them",
919
+ "then",
920
+ "these",
921
+ "most",
922
+ "other",
923
+ "also",
924
+ "being",
925
+ "after",
926
+ "const",
927
+ "function",
928
+ "return",
929
+ "import",
930
+ "export",
931
+ "default",
932
+ "true",
933
+ "false"
934
+ ]);
935
+ return Object.entries(frequency).filter(([word]) => !stopWords.has(word)).sort((a, b) => b[1] - a[1]).slice(0, 20).map(([word]) => word);
936
+ }
937
+ function loadConfig() {
938
+ const configPath = (0, import_path2.join)(process.cwd(), "rankcli.config.json");
939
+ if ((0, import_fs2.existsSync)(configPath)) {
940
+ try {
941
+ return JSON.parse((0, import_fs2.readFileSync)(configPath, "utf-8"));
942
+ } catch {
943
+ return null;
944
+ }
945
+ }
946
+ return null;
947
+ }
948
+ async function analyze(options) {
949
+ console.log("\n\u{1F50D} Analyzing project for SEO opportunities...\n");
950
+ const cwd = process.cwd();
951
+ const config2 = loadConfig();
952
+ const result = {
953
+ projectName: config2?.name || cwd.split("/").pop() || "unknown",
954
+ framework: config2?.framework || null,
955
+ hasReadme: false,
956
+ readmeSummary: null,
957
+ pageCount: 0,
958
+ hasMetaTags: false,
959
+ hasSitemap: false,
960
+ hasRobotsTxt: false,
961
+ keywords: [],
962
+ suggestions: []
963
+ };
964
+ const readmePaths = ["README.md", "readme.md", "Readme.md", "README.MD"];
965
+ for (const rp of readmePaths) {
966
+ if ((0, import_fs2.existsSync)((0, import_path2.join)(cwd, rp))) {
967
+ result.hasReadme = true;
968
+ const content = (0, import_fs2.readFileSync)((0, import_path2.join)(cwd, rp), "utf-8");
969
+ result.readmeSummary = content.slice(0, 200).replace(/\n/g, " ").trim() + "...";
970
+ result.keywords.push(...extractKeywords(content));
971
+ break;
972
+ }
973
+ }
974
+ result.hasSitemap = (0, import_fs2.existsSync)((0, import_path2.join)(cwd, "public", "sitemap.xml")) || (0, import_fs2.existsSync)((0, import_path2.join)(cwd, "sitemap.xml"));
975
+ result.hasRobotsTxt = (0, import_fs2.existsSync)((0, import_path2.join)(cwd, "public", "robots.txt")) || (0, import_fs2.existsSync)((0, import_path2.join)(cwd, "robots.txt"));
976
+ const contentFiles = findFiles(cwd, CONTENT_EXTENSIONS);
977
+ result.pageCount = contentFiles.length;
978
+ const htmlFiles = contentFiles.filter((f) => f.endsWith(".html") || f.includes("_document") || f.includes("layout"));
979
+ for (const hf of htmlFiles.slice(0, 5)) {
980
+ const content = (0, import_fs2.readFileSync)(hf, "utf-8");
981
+ if (content.includes("<meta") || content.includes("metadata")) {
982
+ result.hasMetaTags = true;
983
+ break;
984
+ }
985
+ }
986
+ for (const cf of contentFiles.slice(0, 10)) {
987
+ const content = (0, import_fs2.readFileSync)(cf, "utf-8");
988
+ result.keywords.push(...extractKeywords(content));
989
+ }
990
+ result.keywords = [...new Set(result.keywords)].slice(0, 20);
991
+ if (!result.hasReadme) {
992
+ result.suggestions.push("Add a README.md with clear project description");
993
+ }
994
+ if (!result.hasSitemap) {
995
+ result.suggestions.push("Generate a sitemap.xml for better crawlability");
996
+ }
997
+ if (!result.hasRobotsTxt) {
998
+ result.suggestions.push("Add robots.txt to guide search engine crawlers");
999
+ }
1000
+ if (!result.hasMetaTags) {
1001
+ result.suggestions.push("Add meta tags (title, description, og:*) to pages");
1002
+ }
1003
+ if (result.pageCount < 5) {
1004
+ result.suggestions.push("Consider adding more content pages for SEO coverage");
1005
+ }
1006
+ console.log("\u{1F4CA} Analysis Results\n");
1007
+ console.log(`Project: ${result.projectName}`);
1008
+ console.log(`Framework: ${result.framework || "Unknown"}`);
1009
+ console.log(`Content files: ${result.pageCount}`);
1010
+ console.log(`Has README: ${result.hasReadme ? "\u2705" : "\u274C"}`);
1011
+ console.log(`Has sitemap.xml: ${result.hasSitemap ? "\u2705" : "\u274C"}`);
1012
+ console.log(`Has robots.txt: ${result.hasRobotsTxt ? "\u2705" : "\u274C"}`);
1013
+ console.log(`Has meta tags: ${result.hasMetaTags ? "\u2705" : "\u274C"}`);
1014
+ if (result.keywords.length > 0) {
1015
+ console.log("\n\u{1F511} Detected Keywords:");
1016
+ console.log(` ${result.keywords.join(", ")}`);
1017
+ }
1018
+ if (result.suggestions.length > 0) {
1019
+ console.log("\n\u{1F4A1} Suggestions:");
1020
+ for (const s of result.suggestions) {
1021
+ console.log(` \u2022 ${s}`);
1022
+ }
1023
+ } else {
1024
+ console.log("\n\u2705 No critical SEO issues found!");
1025
+ }
1026
+ console.log('\n\u{1F4C8} Next: Run "rankcli audit --url <your-deployed-site>" for a full technical audit\n');
1027
+ }
1028
+
1029
+ // src/commands/audit.ts
1030
+ var import_agent_runtime2 = require("@rankcli/agent-runtime");
1031
+ var import_ora2 = __toESM(require("ora"));
1032
+ var import_chalk4 = __toESM(require("chalk"));
1033
+
1034
+ // src/lib/upsell.ts
1035
+ var import_chalk3 = __toESM(require("chalk"));
1036
+ var WEB_APP_URL2 = "https://rankcli.dev";
1037
+ var TIER_LIMITS = {
1038
+ free_anon: {
1039
+ checks: 50,
1040
+ sites: 0,
1041
+ domains_per_month: 1,
494
1042
  features: { geo: false, auto_fix: false, github: false, ai: false }
495
1043
  },
496
1044
  free: {
@@ -523,133 +1071,133 @@ var UPSELL_MESSAGES = {
523
1071
  // After free audit completes
524
1072
  free_audit_complete: () => [
525
1073
  "",
526
- import_chalk.default.yellow("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
527
- import_chalk.default.yellow.bold(" \u{1F680} Get 2x more checks + audit history"),
528
- import_chalk.default.dim(" Create a free account to unlock 100 checks and save your results."),
1074
+ import_chalk3.default.yellow("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
1075
+ import_chalk3.default.yellow.bold(" \u{1F680} Get 2x more checks + audit history"),
1076
+ import_chalk3.default.dim(" Create a free account to unlock 100 checks and save your results."),
529
1077
  "",
530
- import_chalk.default.cyan(` \u2192 rankcli login`),
531
- import_chalk.default.yellow("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
1078
+ import_chalk3.default.cyan(` \u2192 rankcli login`),
1079
+ import_chalk3.default.yellow("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
532
1080
  ""
533
1081
  ],
534
1082
  // When checks limit is reached
535
1083
  checks_limit: (ctx) => [
536
1084
  "",
537
- import_chalk.default.yellow(`\u26A0\uFE0F Running ${ctx.current || 50} of ${TIER_LIMITS.solo.checks} available checks`),
538
- import_chalk.default.dim(` Unlock all ${TIER_LIMITS.solo.checks}+ checks with Solo ($9/mo)`),
539
- import_chalk.default.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=cli_checks`),
1085
+ import_chalk3.default.yellow(`\u26A0\uFE0F Running ${ctx.current || 50} of ${TIER_LIMITS.solo.checks} available checks`),
1086
+ import_chalk3.default.dim(` Unlock all ${TIER_LIMITS.solo.checks}+ checks with Solo ($9/mo)`),
1087
+ import_chalk3.default.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=cli_checks`),
540
1088
  ""
541
1089
  ],
542
1090
  // When domain limit is reached
543
1091
  domain_limit: (ctx) => [
544
1092
  "",
545
- import_chalk.default.red("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
546
- import_chalk.default.red.bold(" \u2717 Domain limit reached"),
547
- import_chalk.default.dim(` Your ${ctx.tier} plan allows ${ctx.max} domains per month.`),
1093
+ import_chalk3.default.red("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
1094
+ import_chalk3.default.red.bold(" \u2717 Domain limit reached"),
1095
+ import_chalk3.default.dim(` Your ${ctx.tier} plan allows ${ctx.max} domains per month.`),
548
1096
  "",
549
- import_chalk.default.dim(" Upgrade to audit more sites:"),
550
- import_chalk.default.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=cli_domain_limit`),
551
- import_chalk.default.red("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
1097
+ import_chalk3.default.dim(" Upgrade to audit more sites:"),
1098
+ import_chalk3.default.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=cli_domain_limit`),
1099
+ import_chalk3.default.red("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
552
1100
  ""
553
1101
  ],
554
1102
  // When GEO features are limited
555
1103
  geo_limited: (ctx) => [
556
1104
  "",
557
- import_chalk.default.yellow("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
558
- import_chalk.default.yellow.bold(" \u{1F916} Full GEO analysis requires Solo"),
559
- import_chalk.default.dim(" You're seeing basic AI crawler access checks."),
560
- import_chalk.default.dim(" Full analysis includes: LLM signals, citation readiness, content structure"),
1105
+ import_chalk3.default.yellow("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
1106
+ import_chalk3.default.yellow.bold(" \u{1F916} Full GEO analysis requires Solo"),
1107
+ import_chalk3.default.dim(" You're seeing basic AI crawler access checks."),
1108
+ import_chalk3.default.dim(" Full analysis includes: LLM signals, citation readiness, content structure"),
561
1109
  "",
562
- import_chalk.default.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=cli_geo`),
563
- import_chalk.default.yellow("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
1110
+ import_chalk3.default.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=cli_geo`),
1111
+ import_chalk3.default.yellow("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
564
1112
  ""
565
1113
  ],
566
1114
  // When auto-fix is attempted without access
567
1115
  auto_fix_blocked: () => [
568
1116
  "",
569
- import_chalk.default.red("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
570
- import_chalk.default.red.bold(" \u2717 Auto-fix requires Solo plan"),
571
- import_chalk.default.dim(" Apply framework-specific SEO fixes directly to your codebase."),
1117
+ import_chalk3.default.red("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
1118
+ import_chalk3.default.red.bold(" \u2717 Auto-fix requires Solo plan"),
1119
+ import_chalk3.default.dim(" Apply framework-specific SEO fixes directly to your codebase."),
572
1120
  "",
573
- import_chalk.default.dim(" Solo ($9/mo) includes:"),
574
- import_chalk.default.dim(" \u2022 Local auto-fix via CLI"),
575
- import_chalk.default.dim(" \u2022 25+ framework support"),
576
- import_chalk.default.dim(" \u2022 Full 280+ checks"),
1121
+ import_chalk3.default.dim(" Solo ($9/mo) includes:"),
1122
+ import_chalk3.default.dim(" \u2022 Local auto-fix via CLI"),
1123
+ import_chalk3.default.dim(" \u2022 25+ framework support"),
1124
+ import_chalk3.default.dim(" \u2022 Full 280+ checks"),
577
1125
  "",
578
- import_chalk.default.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=cli_autofix`),
579
- import_chalk.default.red("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
1126
+ import_chalk3.default.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=cli_autofix`),
1127
+ import_chalk3.default.red("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
580
1128
  ""
581
1129
  ],
582
1130
  // When GitHub integration is attempted without access
583
1131
  github_blocked: () => [
584
1132
  "",
585
- import_chalk.default.red("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
586
- import_chalk.default.red.bold(" \u2717 GitHub auto-fix PRs require Pro"),
587
- import_chalk.default.dim(" Automatically create fix PRs when SEO issues are detected."),
1133
+ import_chalk3.default.red("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
1134
+ import_chalk3.default.red.bold(" \u2717 GitHub auto-fix PRs require Pro"),
1135
+ import_chalk3.default.dim(" Automatically create fix PRs when SEO issues are detected."),
588
1136
  "",
589
- import_chalk.default.dim(" Pro ($29/mo) includes:"),
590
- import_chalk.default.dim(" \u2022 GitHub App integration"),
591
- import_chalk.default.dim(" \u2022 Auto-fix PRs"),
592
- import_chalk.default.dim(" \u2022 Daily scheduled audits"),
593
- import_chalk.default.dim(" \u2022 Slack/Discord alerts"),
1137
+ import_chalk3.default.dim(" Pro ($29/mo) includes:"),
1138
+ import_chalk3.default.dim(" \u2022 GitHub App integration"),
1139
+ import_chalk3.default.dim(" \u2022 Auto-fix PRs"),
1140
+ import_chalk3.default.dim(" \u2022 Daily scheduled audits"),
1141
+ import_chalk3.default.dim(" \u2022 Slack/Discord alerts"),
594
1142
  "",
595
- import_chalk.default.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=cli_github`),
596
- import_chalk.default.red("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
1143
+ import_chalk3.default.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=cli_github`),
1144
+ import_chalk3.default.red("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
597
1145
  ""
598
1146
  ],
599
1147
  // Keyword tracking limit reached
600
1148
  keyword_limit: (ctx) => [
601
1149
  "",
602
- import_chalk.default.red("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
603
- import_chalk.default.red.bold(" \u2717 Keyword tracking limit reached"),
604
- import_chalk.default.dim(` Your ${ctx.tier} plan allows ${ctx.limit || ctx.max} tracked keywords.`),
605
- import_chalk.default.dim(` Currently tracking: ${ctx.current}`),
1150
+ import_chalk3.default.red("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
1151
+ import_chalk3.default.red.bold(" \u2717 Keyword tracking limit reached"),
1152
+ import_chalk3.default.dim(` Your ${ctx.tier} plan allows ${ctx.limit || ctx.max} tracked keywords.`),
1153
+ import_chalk3.default.dim(` Currently tracking: ${ctx.current}`),
606
1154
  "",
607
- import_chalk.default.dim(" Upgrade for more keywords:"),
608
- import_chalk.default.dim(" \u2022 Solo ($9/mo): 100 keywords"),
609
- import_chalk.default.dim(" \u2022 Pro ($29/mo): 500 keywords"),
610
- import_chalk.default.dim(" \u2022 Agency ($99/mo): 2,000 keywords"),
1155
+ import_chalk3.default.dim(" Upgrade for more keywords:"),
1156
+ import_chalk3.default.dim(" \u2022 Solo ($9/mo): 100 keywords"),
1157
+ import_chalk3.default.dim(" \u2022 Pro ($29/mo): 500 keywords"),
1158
+ import_chalk3.default.dim(" \u2022 Agency ($99/mo): 2,000 keywords"),
611
1159
  "",
612
- import_chalk.default.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=cli_keyword_limit`),
613
- import_chalk.default.red("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
1160
+ import_chalk3.default.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=cli_keyword_limit`),
1161
+ import_chalk3.default.red("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
614
1162
  ""
615
1163
  ],
616
1164
  // Rate limit exceeded
617
1165
  rate_limit: (ctx) => [
618
1166
  "",
619
- import_chalk.default.red("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
620
- import_chalk.default.red.bold(" \u2717 API rate limit exceeded"),
621
- import_chalk.default.dim(` Your ${ctx.tier} plan allows ${ctx.max} requests/day.`),
1167
+ import_chalk3.default.red("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
1168
+ import_chalk3.default.red.bold(" \u2717 API rate limit exceeded"),
1169
+ import_chalk3.default.dim(` Your ${ctx.tier} plan allows ${ctx.max} requests/day.`),
622
1170
  "",
623
- import_chalk.default.dim(" Need more? Upgrade for higher limits:"),
624
- import_chalk.default.dim(" \u2022 Solo: 100 req/day"),
625
- import_chalk.default.dim(" \u2022 Pro: 1,000 req/day"),
626
- import_chalk.default.dim(" \u2022 Agency: 10,000 req/day"),
1171
+ import_chalk3.default.dim(" Need more? Upgrade for higher limits:"),
1172
+ import_chalk3.default.dim(" \u2022 Solo: 100 req/day"),
1173
+ import_chalk3.default.dim(" \u2022 Pro: 1,000 req/day"),
1174
+ import_chalk3.default.dim(" \u2022 Agency: 10,000 req/day"),
627
1175
  "",
628
- import_chalk.default.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=cli_rate_limit`),
629
- import_chalk.default.red("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
1176
+ import_chalk3.default.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=cli_rate_limit`),
1177
+ import_chalk3.default.red("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
630
1178
  ""
631
1179
  ],
632
1180
  // Schedule exceeded (for GitHub Action)
633
1181
  schedule_exceeded: (ctx) => [
634
1182
  "",
635
- import_chalk.default.yellow("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
636
- import_chalk.default.yellow.bold(" \u23F0 Scheduled audit skipped"),
637
- import_chalk.default.dim(` Your ${ctx.tier} plan allows ${ctx.limit} scheduled audits.`),
1183
+ import_chalk3.default.yellow("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
1184
+ import_chalk3.default.yellow.bold(" \u23F0 Scheduled audit skipped"),
1185
+ import_chalk3.default.dim(` Your ${ctx.tier} plan allows ${ctx.limit} scheduled audits.`),
638
1186
  "",
639
- import_chalk.default.dim(" Upgrade for more frequent audits:"),
640
- import_chalk.default.dim(" \u2022 Solo: Weekly"),
641
- import_chalk.default.dim(" \u2022 Pro: Daily"),
642
- import_chalk.default.dim(" \u2022 Agency: Hourly"),
1187
+ import_chalk3.default.dim(" Upgrade for more frequent audits:"),
1188
+ import_chalk3.default.dim(" \u2022 Solo: Weekly"),
1189
+ import_chalk3.default.dim(" \u2022 Pro: Daily"),
1190
+ import_chalk3.default.dim(" \u2022 Agency: Hourly"),
643
1191
  "",
644
- import_chalk.default.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=schedule_exceeded`),
645
- import_chalk.default.yellow("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
1192
+ import_chalk3.default.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=schedule_exceeded`),
1193
+ import_chalk3.default.yellow("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
646
1194
  ""
647
1195
  ],
648
1196
  // Login prompt (soft)
649
1197
  login_prompt: () => [
650
1198
  "",
651
- import_chalk.default.dim(" \u{1F4A1} Tip: Log in to sync results and track progress over time:"),
652
- import_chalk.default.dim(" rankcli login"),
1199
+ import_chalk3.default.dim(" \u{1F4A1} Tip: Log in to sync results and track progress over time:"),
1200
+ import_chalk3.default.dim(" rankcli login"),
653
1201
  ""
654
1202
  ]
655
1203
  };
@@ -670,8 +1218,8 @@ function hasFeature(tier, feature) {
670
1218
 
671
1219
  // src/wasm-engine.ts
672
1220
  var vm = __toESM(require("vm"));
673
- var path = __toESM(require("path"));
674
- var fs = __toESM(require("fs"));
1221
+ var path2 = __toESM(require("path"));
1222
+ var fs2 = __toESM(require("fs"));
675
1223
  var os = __toESM(require("os"));
676
1224
  var PREMIUM_CHECK_NAMES = [
677
1225
  "analyzeFeaturedSnippet",
@@ -708,12 +1256,12 @@ async function loadWasm() {
708
1256
  return wasmModule;
709
1257
  }
710
1258
  const possiblePaths = [
711
- path.join(__dirname, "../native/pkg/rankcli_audit_engine.js"),
712
- path.join(__dirname, "../wasm/rankcli_audit_engine.js"),
713
- path.join(__dirname, "../../native/pkg/rankcli_audit_engine.js")
1259
+ path2.join(__dirname, "../native/pkg/rankcli_audit_engine.js"),
1260
+ path2.join(__dirname, "../wasm/rankcli_audit_engine.js"),
1261
+ path2.join(__dirname, "../../native/pkg/rankcli_audit_engine.js")
714
1262
  ];
715
1263
  for (const wasmPath of possiblePaths) {
716
- if (fs.existsSync(wasmPath)) {
1264
+ if (fs2.existsSync(wasmPath)) {
717
1265
  wasmModule = require(wasmPath);
718
1266
  return wasmModule;
719
1267
  }
@@ -725,8 +1273,8 @@ Searched: ${possiblePaths.join(", ")}`
725
1273
  }
726
1274
  function extractSeaWasmAssets() {
727
1275
  const sea = require("sea");
728
- const dir = path.join(os.tmpdir(), "rankcli-wasm-sea");
729
- fs.mkdirSync(dir, { recursive: true });
1276
+ const dir = path2.join(os.tmpdir(), "rankcli-wasm-sea");
1277
+ fs2.mkdirSync(dir, { recursive: true });
730
1278
  const glueAsset = sea.getRawAsset("rankcli_audit_engine.js");
731
1279
  const wasmAsset = sea.getRawAsset("rankcli_audit_engine_bg.wasm");
732
1280
  if (typeof glueAsset === "string" || typeof wasmAsset === "string") {
@@ -734,12 +1282,12 @@ function extractSeaWasmAssets() {
734
1282
  }
735
1283
  const glueBuffer = Buffer.from(glueAsset);
736
1284
  const wasmBuffer = Buffer.from(wasmAsset);
737
- const gluePath = path.join(dir, "rankcli_audit_engine.js");
738
- const wasmPath = path.join(dir, "rankcli_audit_engine_bg.wasm");
739
- const existingSize = fs.existsSync(wasmPath) ? fs.statSync(wasmPath).size : -1;
1285
+ const gluePath = path2.join(dir, "rankcli_audit_engine.js");
1286
+ const wasmPath = path2.join(dir, "rankcli_audit_engine_bg.wasm");
1287
+ const existingSize = fs2.existsSync(wasmPath) ? fs2.statSync(wasmPath).size : -1;
740
1288
  if (existingSize !== wasmBuffer.length) {
741
- fs.writeFileSync(gluePath, glueBuffer);
742
- fs.writeFileSync(wasmPath, wasmBuffer);
1289
+ fs2.writeFileSync(gluePath, glueBuffer);
1290
+ fs2.writeFileSync(wasmPath, wasmBuffer);
743
1291
  }
744
1292
  return gluePath;
745
1293
  }
@@ -834,7 +1382,7 @@ async function getPremiumChecks() {
834
1382
  }
835
1383
 
836
1384
  // src/commands/audit.ts
837
- var { detectFramework: detectFramework2, getFrameworkDisplayName } = import_agent_runtime.frameworks;
1385
+ var { detectFramework: detectFramework2, getFrameworkDisplayName } = import_agent_runtime2.frameworks;
838
1386
  async function fetchHtml(url, timeout = 1e4) {
839
1387
  try {
840
1388
  const controller = new AbortController();
@@ -855,9 +1403,16 @@ async function fetchHtml(url, timeout = 1e4) {
855
1403
  return null;
856
1404
  }
857
1405
  }
1406
+ var AI_PROVIDER_DEFAULTS = {
1407
+ openai: { baseUrl: "https://api.openai.com/v1", model: "gpt-4o-mini" },
1408
+ openrouter: { baseUrl: "https://openrouter.ai/api/v1", model: "deepseek/deepseek-r1:free" },
1409
+ groq: { baseUrl: "https://api.groq.com/openai/v1", model: "llama-3.3-70b-versatile" },
1410
+ gemini: { baseUrl: "https://generativelanguage.googleapis.com/v1beta/openai", model: "gemini-2.0-flash" }
1411
+ };
858
1412
  async function runAIAnalysis(url, issues, provider, apiKey) {
859
1413
  const isAnthropic = provider === "anthropic";
860
- const apiUrl = isAnthropic ? "https://api.anthropic.com/v1/messages" : "https://api.openai.com/v1/chat/completions";
1414
+ const providerConfig = AI_PROVIDER_DEFAULTS[provider] || AI_PROVIDER_DEFAULTS.openai;
1415
+ const apiUrl = isAnthropic ? "https://api.anthropic.com/v1/messages" : `${providerConfig.baseUrl}/chat/completions`;
861
1416
  const prompt = `Analyze these SEO audit results and provide prioritized recommendations:
862
1417
 
863
1418
  URL: ${url}
@@ -888,19 +1443,26 @@ Return JSON with:
888
1443
  });
889
1444
  } else {
890
1445
  headers["Authorization"] = `Bearer ${apiKey}`;
1446
+ if (provider === "openrouter") {
1447
+ headers["HTTP-Referer"] = "https://rankcli.dev";
1448
+ headers["X-Title"] = "RankCLI";
1449
+ }
891
1450
  body = JSON.stringify({
892
- model: "gpt-4o-mini",
1451
+ model: providerConfig.model,
893
1452
  messages: [
894
1453
  { role: "system", content: "You are an SEO expert. Respond with valid JSON only." },
895
1454
  { role: "user", content: prompt }
896
1455
  ],
897
1456
  max_tokens: 1500,
898
- response_format: { type: "json_object" }
1457
+ // json_object mode is an OpenAI-specific extension - Groq supports
1458
+ // it too, but not every OpenAI-compatible server does, so only
1459
+ // request it for providers confirmed to support it.
1460
+ ...provider === "openai" || provider === "groq" ? { response_format: { type: "json_object" } } : {}
899
1461
  });
900
1462
  }
901
1463
  const response = await fetch(apiUrl, { method: "POST", headers, body });
902
1464
  if (!response.ok) {
903
- console.error(import_chalk2.default.yellow(`AI analysis failed: ${response.status}`));
1465
+ console.error(import_chalk4.default.yellow(`AI analysis failed: ${response.status}`));
904
1466
  return null;
905
1467
  }
906
1468
  const data = await response.json();
@@ -910,7 +1472,7 @@ Return JSON with:
910
1472
  if (!jsonMatch) return null;
911
1473
  return JSON.parse(jsonMatch[0]);
912
1474
  } catch (error) {
913
- console.error(import_chalk2.default.yellow(`AI analysis error: ${error}`));
1475
+ console.error(import_chalk4.default.yellow(`AI analysis error: ${error}`));
914
1476
  return null;
915
1477
  }
916
1478
  }
@@ -945,9 +1507,9 @@ async function syncToCloud(url, report, mergedIssues, pagesAudited, aiAnalysis,
945
1507
  const { count } = await supabase.from("projects").select("id", { count: "exact", head: true }).eq("user_id", userId).eq("is_active", true);
946
1508
  const limit = sitesLimit ?? 1;
947
1509
  if (count !== null && count >= limit) {
948
- console.log(import_chalk2.default.yellow(`
1510
+ console.log(import_chalk4.default.yellow(`
949
1511
  \u26A0\uFE0F Project limit reached (${count}/${limit})`));
950
- console.log(import_chalk2.default.dim(` Upgrade at: ${WEB_APP_URL}/account`));
1512
+ console.log(import_chalk4.default.dim(` Upgrade at: ${WEB_APP_URL}/account`));
951
1513
  return null;
952
1514
  }
953
1515
  const { data: newProject, error: projectError } = await supabase.from("projects").insert({
@@ -957,7 +1519,7 @@ async function syncToCloud(url, report, mergedIssues, pagesAudited, aiAnalysis,
957
1519
  domain
958
1520
  }).select("id").single();
959
1521
  if (projectError || !newProject) {
960
- console.error(import_chalk2.default.dim("Failed to create project"));
1522
+ console.error(import_chalk4.default.dim("Failed to create project"));
961
1523
  return null;
962
1524
  }
963
1525
  projectId = newProject.id;
@@ -998,7 +1560,7 @@ async function syncToCloud(url, report, mergedIssues, pagesAudited, aiAnalysis,
998
1560
  ai_analysis: aiAnalysis
999
1561
  }).select("id").single();
1000
1562
  if (auditError || !audit2) {
1001
- console.error(import_chalk2.default.dim("Failed to save audit"));
1563
+ console.error(import_chalk4.default.dim("Failed to save audit"));
1002
1564
  return null;
1003
1565
  }
1004
1566
  if (mergedIssues.length > 0) {
@@ -1026,7 +1588,7 @@ async function syncToCloud(url, report, mergedIssues, pagesAudited, aiAnalysis,
1026
1588
  }).eq("id", projectId);
1027
1589
  return { projectId, auditId: audit2.id };
1028
1590
  } catch (error) {
1029
- console.error(import_chalk2.default.dim("Cloud sync failed"));
1591
+ console.error(import_chalk4.default.dim("Cloud sync failed"));
1030
1592
  return null;
1031
1593
  }
1032
1594
  }
@@ -1034,8 +1596,16 @@ async function audit(options) {
1034
1596
  const url = options.url;
1035
1597
  const maxPages = parseInt(options.maxPages || "5", 10);
1036
1598
  const enableAI = options.ai || false;
1037
- const aiProvider = options.aiProvider || "openai";
1038
- const aiKey = options.aiKey || (aiProvider === "anthropic" ? process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY : process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY) || "";
1599
+ const detectedProvider = options.aiProvider || (process.env.OPENROUTER_API_KEY ? "openrouter" : process.env.GROQ_API_KEY ? "groq" : process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY ? "gemini" : process.env.ANTHROPIC_API_KEY ? "anthropic" : "openai");
1600
+ const aiProvider = detectedProvider;
1601
+ const providerEnvKey = {
1602
+ openai: process.env.OPENAI_API_KEY,
1603
+ anthropic: process.env.ANTHROPIC_API_KEY,
1604
+ openrouter: process.env.OPENROUTER_API_KEY,
1605
+ groq: process.env.GROQ_API_KEY,
1606
+ gemini: process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY
1607
+ };
1608
+ const aiKey = options.aiKey || providerEnvKey[aiProvider] || "";
1039
1609
  const loggedIn = isLoggedIn();
1040
1610
  const { planId } = getUserInfo();
1041
1611
  const tierId = planId && planId in TIER_LIMITS ? planId : loggedIn ? "free" : "free_anon";
@@ -1048,7 +1618,7 @@ async function audit(options) {
1048
1618
  await initEngine(passphrase);
1049
1619
  premiumChecks = await getPremiumChecks();
1050
1620
  } catch (error) {
1051
- console.error(import_chalk2.default.dim(`(premium checks unavailable: ${error instanceof Error ? error.message : error})`));
1621
+ console.error(import_chalk4.default.dim(`(premium checks unavailable: ${error instanceof Error ? error.message : error})`));
1052
1622
  }
1053
1623
  }
1054
1624
  }
@@ -1058,12 +1628,12 @@ async function audit(options) {
1058
1628
  console.log("\nOptions:");
1059
1629
  console.log(" --max-pages <n> Max pages to crawl (default: 5)");
1060
1630
  console.log(" --ai Enable AI analysis");
1061
- console.log(" --ai-provider <p> AI provider: openai or anthropic");
1062
- console.log(" --ai-key <key> API key (or set OPENAI_API_KEY/ANTHROPIC_API_KEY)");
1631
+ console.log(" --ai-provider <p> AI provider: openai, anthropic, openrouter, groq, or gemini (auto-detected from env if omitted)");
1632
+ console.log(" --ai-key <key> API key (or set OPENAI_API_KEY, ANTHROPIC_API_KEY, OPENROUTER_API_KEY, GROQ_API_KEY, or GEMINI_API_KEY)");
1063
1633
  console.log(" --output json Output as JSON");
1064
1634
  if (!loggedIn) {
1065
- console.log(import_chalk2.default.dim("\nTip: Log in to sync results to the web dashboard:"));
1066
- console.log(import_chalk2.default.dim(" rankcli login"));
1635
+ console.log(import_chalk4.default.dim("\nTip: Log in to sync results to the web dashboard:"));
1636
+ console.log(import_chalk4.default.dim(" rankcli login"));
1067
1637
  }
1068
1638
  console.log("");
1069
1639
  return;
@@ -1076,13 +1646,13 @@ async function audit(options) {
1076
1646
  }
1077
1647
  const startTime = Date.now();
1078
1648
  try {
1079
- const discoverySpinner = (0, import_ora.default)("Discovering pages...").start();
1649
+ const discoverySpinner = (0, import_ora2.default)("Discovering pages...").start();
1080
1650
  let pagesToAudit = [url];
1081
1651
  const homepageHtml = await fetchHtml(url, 1e4);
1082
1652
  let detectedFramework = detectFramework2({});
1083
1653
  if (homepageHtml) {
1084
1654
  detectedFramework = detectFramework2({ html: homepageHtml });
1085
- const discovered = (0, import_agent_runtime.discoverPagesFromLinks)(url, homepageHtml, maxPages - 1);
1655
+ const discovered = (0, import_agent_runtime2.discoverPagesFromLinks)(url, homepageHtml, maxPages - 1);
1086
1656
  const additionalPages = discovered.map((p) => p.url).filter((u) => u !== url);
1087
1657
  pagesToAudit = [url, ...additionalPages];
1088
1658
  const frameworkLabel = detectedFramework.framework !== "unknown" ? ` (${getFrameworkDisplayName(detectedFramework.framework)})` : "";
@@ -1090,10 +1660,10 @@ async function audit(options) {
1090
1660
  } else {
1091
1661
  discoverySpinner.warn("Could not fetch homepage, auditing URL only");
1092
1662
  }
1093
- const auditSpinner = (0, import_ora.default)(`Auditing ${pagesToAudit.length} pages in parallel...`).start();
1663
+ const auditSpinner = (0, import_ora2.default)(`Auditing ${pagesToAudit.length} pages in parallel...`).start();
1094
1664
  const auditPage = async (pageUrl, index) => {
1095
1665
  try {
1096
- const report = await (0, import_agent_runtime.runFullAudit)({
1666
+ const report = await (0, import_agent_runtime2.runFullAudit)({
1097
1667
  url: pageUrl,
1098
1668
  maxPages: 1,
1099
1669
  checkBrokenLinks: options.checkLinks || false,
@@ -1137,7 +1707,7 @@ async function audit(options) {
1137
1707
  const mergedIssues = mergeIssues(allIssues);
1138
1708
  let aiAnalysis = null;
1139
1709
  if (enableAI && aiKey) {
1140
- const aiSpinner = (0, import_ora.default)(`Running AI analysis (${aiProvider})...`).start();
1710
+ const aiSpinner = (0, import_ora2.default)(`Running AI analysis (${aiProvider})...`).start();
1141
1711
  aiAnalysis = await runAIAnalysis(url, mergedIssues, aiProvider, aiKey);
1142
1712
  if (aiAnalysis) {
1143
1713
  aiSpinner.succeed("AI analysis complete");
@@ -1145,8 +1715,8 @@ async function audit(options) {
1145
1715
  aiSpinner.warn("AI analysis failed or returned no results");
1146
1716
  }
1147
1717
  } else if (enableAI && !aiKey) {
1148
- console.log(import_chalk2.default.yellow("\n\u26A0\uFE0F AI analysis requested but no API key provided"));
1149
- console.log(import_chalk2.default.dim(" Set OPENAI_API_KEY or ANTHROPIC_API_KEY, or use --ai-key"));
1718
+ console.log(import_chalk4.default.yellow("\n\u26A0\uFE0F AI analysis requested but no API key provided"));
1719
+ console.log(import_chalk4.default.dim(" Set OPENAI_API_KEY, ANTHROPIC_API_KEY, OPENROUTER_API_KEY, GROQ_API_KEY, or GEMINI_API_KEY, or use --ai-key"));
1150
1720
  }
1151
1721
  const totalTime = Date.now() - startTime;
1152
1722
  const frameworkName = detectedFramework.framework !== "unknown" ? getFrameworkDisplayName(detectedFramework.framework) : null;
@@ -1174,27 +1744,27 @@ async function audit(options) {
1174
1744
  };
1175
1745
  console.log(JSON.stringify(jsonOutput, null, 2));
1176
1746
  } else {
1177
- console.log("\n" + import_chalk2.default.bold("\u2550".repeat(60)));
1178
- console.log(import_chalk2.default.bold.cyan(` SEO AUDIT REPORT: ${url}`));
1179
- console.log(import_chalk2.default.bold("\u2550".repeat(60)));
1747
+ console.log("\n" + import_chalk4.default.bold("\u2550".repeat(60)));
1748
+ console.log(import_chalk4.default.bold.cyan(` SEO AUDIT REPORT: ${url}`));
1749
+ console.log(import_chalk4.default.bold("\u2550".repeat(60)));
1180
1750
  const score = primaryReport.healthScore?.overall || 0;
1181
- const scoreColor = score >= 80 ? import_chalk2.default.green : score >= 60 ? import_chalk2.default.yellow : import_chalk2.default.red;
1751
+ const scoreColor = score >= 80 ? import_chalk4.default.green : score >= 60 ? import_chalk4.default.yellow : import_chalk4.default.red;
1182
1752
  console.log(`
1183
- ${import_chalk2.default.bold("Score:")} ${scoreColor.bold(score + "/100")}`);
1184
- console.log(` ${import_chalk2.default.bold("Pages audited:")} ${successCount}`);
1185
- console.log(` ${import_chalk2.default.bold("Issues found:")} ${mergedIssues.length}`);
1186
- console.log(` ${import_chalk2.default.bold("Time:")} ${totalTime}ms`);
1753
+ ${import_chalk4.default.bold("Score:")} ${scoreColor.bold(score + "/100")}`);
1754
+ console.log(` ${import_chalk4.default.bold("Pages audited:")} ${successCount}`);
1755
+ console.log(` ${import_chalk4.default.bold("Issues found:")} ${mergedIssues.length}`);
1756
+ console.log(` ${import_chalk4.default.bold("Time:")} ${totalTime}ms`);
1187
1757
  if (frameworkName) {
1188
- console.log(` ${import_chalk2.default.bold("Framework:")} ${import_chalk2.default.cyan(frameworkName)}`);
1758
+ console.log(` ${import_chalk4.default.bold("Framework:")} ${import_chalk4.default.cyan(frameworkName)}`);
1189
1759
  }
1190
1760
  if (primaryReport.healthScore) {
1191
- console.log("\n" + import_chalk2.default.bold(" Health Scores:"));
1761
+ console.log("\n" + import_chalk4.default.bold(" Health Scores:"));
1192
1762
  const hs = primaryReport.healthScore;
1193
1763
  const bar = (score2) => {
1194
1764
  const filled = Math.round(score2 / 5);
1195
1765
  const empty = 20 - filled;
1196
- const color = score2 >= 80 ? import_chalk2.default.green : score2 >= 60 ? import_chalk2.default.yellow : import_chalk2.default.red;
1197
- return color("\u2588".repeat(filled)) + import_chalk2.default.gray("\u2591".repeat(empty));
1766
+ const color = score2 >= 80 ? import_chalk4.default.green : score2 >= 60 ? import_chalk4.default.yellow : import_chalk4.default.red;
1767
+ return color("\u2588".repeat(filled)) + import_chalk4.default.gray("\u2591".repeat(empty));
1198
1768
  };
1199
1769
  console.log(` Crawlability: ${bar(hs.crawlability || 0)} ${hs.crawlability || 0}%`);
1200
1770
  console.log(` On-Page: ${bar(hs.onPage || 0)} ${hs.onPage || 0}%`);
@@ -1207,35 +1777,35 @@ async function audit(options) {
1207
1777
  const errors = mergedIssues.filter((i) => i.severity === "error");
1208
1778
  const warnings = mergedIssues.filter((i) => i.severity === "warning");
1209
1779
  const notices = mergedIssues.filter((i) => i.severity === "notice");
1210
- console.log("\n" + import_chalk2.default.bold(" Issues by Severity:"));
1211
- console.log(` ${import_chalk2.default.red("\u25CF")} Errors: ${errors.length}`);
1212
- console.log(` ${import_chalk2.default.yellow("\u25CF")} Warnings: ${warnings.length}`);
1213
- console.log(` ${import_chalk2.default.blue("\u25CF")} Notices: ${notices.length}`);
1214
- console.log("\n" + import_chalk2.default.bold(" Top Issues:"));
1780
+ console.log("\n" + import_chalk4.default.bold(" Issues by Severity:"));
1781
+ console.log(` ${import_chalk4.default.red("\u25CF")} Errors: ${errors.length}`);
1782
+ console.log(` ${import_chalk4.default.yellow("\u25CF")} Warnings: ${warnings.length}`);
1783
+ console.log(` ${import_chalk4.default.blue("\u25CF")} Notices: ${notices.length}`);
1784
+ console.log("\n" + import_chalk4.default.bold(" Top Issues:"));
1215
1785
  const topIssues = [...errors, ...warnings, ...notices].slice(0, 10);
1216
1786
  topIssues.forEach((issue, i) => {
1217
- const icon = issue.severity === "error" ? import_chalk2.default.red("\u2717") : issue.severity === "warning" ? import_chalk2.default.yellow("!") : import_chalk2.default.blue("i");
1787
+ const icon = issue.severity === "error" ? import_chalk4.default.red("\u2717") : issue.severity === "warning" ? import_chalk4.default.yellow("!") : import_chalk4.default.blue("i");
1218
1788
  console.log(` ${i + 1}. ${icon} ${issue.title}`);
1219
1789
  if (issue.affectedUrls && issue.affectedUrls.length > 1) {
1220
- console.log(import_chalk2.default.dim(` Affects ${issue.affectedUrls.length} pages`));
1790
+ console.log(import_chalk4.default.dim(` Affects ${issue.affectedUrls.length} pages`));
1221
1791
  }
1222
1792
  });
1223
1793
  if (aiAnalysis) {
1224
- console.log("\n" + import_chalk2.default.bold(" AI Analysis:"));
1794
+ console.log("\n" + import_chalk4.default.bold(" AI Analysis:"));
1225
1795
  console.log(` Content Score: ${aiAnalysis.contentScore}/100`);
1226
- console.log("\n " + import_chalk2.default.bold("Suggestions:"));
1796
+ console.log("\n " + import_chalk4.default.bold("Suggestions:"));
1227
1797
  aiAnalysis.suggestions.forEach((s, i) => {
1228
1798
  console.log(` ${i + 1}. ${s}`);
1229
1799
  });
1230
1800
  if (aiAnalysis.prioritizedFixes?.length > 0) {
1231
- console.log("\n " + import_chalk2.default.bold("Priority Fixes:"));
1801
+ console.log("\n " + import_chalk4.default.bold("Priority Fixes:"));
1232
1802
  aiAnalysis.prioritizedFixes.slice(0, 5).forEach((fix2, i) => {
1233
1803
  console.log(` ${i + 1}. [P${fix2.priority}] ${fix2.code}`);
1234
- console.log(import_chalk2.default.dim(` ${fix2.rationale}`));
1804
+ console.log(import_chalk4.default.dim(` ${fix2.rationale}`));
1235
1805
  });
1236
1806
  }
1237
1807
  }
1238
- console.log("\n" + import_chalk2.default.bold("\u2550".repeat(60)));
1808
+ console.log("\n" + import_chalk4.default.bold("\u2550".repeat(60)));
1239
1809
  const userInfo = getUserInfo();
1240
1810
  const tier = userInfo.planName?.toLowerCase() || "free";
1241
1811
  const isFreeTier = !loggedIn || tier === "free";
@@ -1251,13 +1821,13 @@ async function audit(options) {
1251
1821
  showUpsell("geo_limited", { tier: loggedIn ? "free" : "free_anon" });
1252
1822
  }
1253
1823
  if (loggedIn) {
1254
- const syncSpinner = (0, import_ora.default)("Syncing to cloud...").start();
1824
+ const syncSpinner = (0, import_ora2.default)("Syncing to cloud...").start();
1255
1825
  const syncResult = await syncToCloud(url, primaryReport, mergedIssues, successCount, aiAnalysis, totalTime, actualChecksRun);
1256
1826
  if (syncResult) {
1257
1827
  syncSpinner.succeed("Results synced to cloud");
1258
- console.log(import_chalk2.default.cyan(`
1828
+ console.log(import_chalk4.default.cyan(`
1259
1829
  View and manage issues in the web dashboard:`));
1260
- console.log(import_chalk2.default.bold(` ${WEB_APP_URL}/dashboard/projects/${syncResult.projectId}
1830
+ console.log(import_chalk4.default.bold(` ${WEB_APP_URL}/dashboard/projects/${syncResult.projectId}
1261
1831
  `));
1262
1832
  } else {
1263
1833
  syncSpinner.warn("Could not sync to cloud");
@@ -1276,7 +1846,7 @@ async function audit(options) {
1276
1846
  // src/commands/apply.ts
1277
1847
  var import_fs3 = require("fs");
1278
1848
  var import_path3 = require("path");
1279
- var import_agent_runtime2 = require("@rankcli/agent-runtime");
1849
+ var import_agent_runtime3 = require("@rankcli/agent-runtime");
1280
1850
  var SEVERITY_ICON = {
1281
1851
  critical: "\u274C",
1282
1852
  warning: "\u26A0\uFE0F",
@@ -1304,7 +1874,7 @@ function truncate(str, maxLen) {
1304
1874
  }
1305
1875
  async function apply(options) {
1306
1876
  const cwd = process.cwd();
1307
- const configPath = (0, import_path3.join)(cwd, "seo-autopilot.config.json");
1877
+ const configPath = (0, import_path3.join)(cwd, "rankcli.config.json");
1308
1878
  let config2 = null;
1309
1879
  if ((0, import_fs3.existsSync)(configPath)) {
1310
1880
  try {
@@ -1318,8 +1888,8 @@ async function apply(options) {
1318
1888
  }
1319
1889
  if (!url) {
1320
1890
  console.log("\n\u26A0\uFE0F Please provide a URL to analyze:");
1321
- console.log(" seo apply --url https://your-site.com");
1322
- console.log("\n Or set a URL in your seo-autopilot.config.json\n");
1891
+ console.log(" rankcli apply --url https://your-site.com");
1892
+ console.log("\n Or set a URL in your rankcli.config.json\n");
1323
1893
  return;
1324
1894
  }
1325
1895
  try {
@@ -1328,7 +1898,7 @@ async function apply(options) {
1328
1898
  console.log("\n\u274C Invalid URL format. Please provide a valid URL.\n");
1329
1899
  return;
1330
1900
  }
1331
- console.log("\n\u{1F680} SEO Autopilot - Apply Fixes");
1901
+ console.log("\n\u{1F680} RankCLI - Apply Fixes");
1332
1902
  console.log("\u2550".repeat(50));
1333
1903
  const loggedIn = isLoggedIn();
1334
1904
  const userInfo = getUserInfo();
@@ -1339,7 +1909,7 @@ async function apply(options) {
1339
1909
  return;
1340
1910
  }
1341
1911
  console.log("\n\u{1F50D} Detecting framework...");
1342
- const frameworkResult = await (0, import_agent_runtime2.detectFramework)({ cwd });
1912
+ const frameworkResult = await (0, import_agent_runtime3.detectFramework)({ cwd });
1343
1913
  const framework = frameworkResult.success ? frameworkResult.data : { name: "Unknown", metaPattern: "html-head" };
1344
1914
  console.log(` Framework: ${framework.name}`);
1345
1915
  console.log(`
@@ -1349,16 +1919,23 @@ async function apply(options) {
1349
1919
  let score;
1350
1920
  let auditResult = null;
1351
1921
  try {
1352
- auditResult = await (0, import_agent_runtime2.runAuditWithFixes)({
1922
+ auditResult = await (0, import_agent_runtime3.runAuditWithFixes)({
1353
1923
  url,
1354
1924
  generateFixes: true
1355
1925
  });
1356
1926
  issues = auditResult.report.issues.map((issue) => ({
1357
- severity: issue.severity,
1927
+ // Squash the audit engine's 5-level severity into generateFixes's
1928
+ // 3-level SEOIssue scheme - matches how audit.ts already treats
1929
+ // 'error' as critical-weight (red ✗) and 'notice' as info-weight.
1930
+ severity: issue.severity === "error" ? "critical" : issue.severity === "notice" ? "info" : issue.severity,
1358
1931
  category: issue.category,
1359
1932
  code: issue.code,
1360
1933
  message: issue.title,
1361
1934
  impact: issue.description || void 0
1935
+ // Cast covers `category`: the audit engine's categories (17 values,
1936
+ // e.g. 'crawlability', 'links') don't map cleanly onto SEOIssue's
1937
+ // narrower set - generateFixes never reads category off its input
1938
+ // (each fixer hardcodes its own), so this is safe, not just silenced.
1362
1939
  }));
1363
1940
  score = auditResult.score;
1364
1941
  } catch (error) {
@@ -1374,7 +1951,7 @@ async function apply(options) {
1374
1951
  return;
1375
1952
  }
1376
1953
  console.log("\n\u{1F527} Generating fixes...");
1377
- const fixes = await (0, import_agent_runtime2.generateFixes)(issues, {
1954
+ const fixes = await (0, import_agent_runtime3.generateFixes)(issues, {
1378
1955
  cwd,
1379
1956
  url,
1380
1957
  framework
@@ -1413,7 +1990,7 @@ async function apply(options) {
1413
1990
  }
1414
1991
  }
1415
1992
  console.log("\n\u{1F527} Applying fixes...");
1416
- const { applied, skipped } = await (0, import_agent_runtime2.applyFixes)(fixes, { cwd, dryRun: false });
1993
+ const { applied, skipped } = await (0, import_agent_runtime3.applyFixes)(fixes, { cwd, dryRun: false });
1417
1994
  console.log("\n" + "\u2550".repeat(50));
1418
1995
  console.log("RESULTS");
1419
1996
  console.log("\u2550".repeat(50));
@@ -1444,17 +2021,17 @@ async function apply(options) {
1444
2021
  console.log("\n2. Commit the SEO improvements:");
1445
2022
  console.log(' git add -A && git commit -m "feat(seo): add meta tags and structured data"');
1446
2023
  console.log("\n3. Re-run the audit to verify:");
1447
- console.log(` seo audit --url ${url}`);
2024
+ console.log(` rankcli audit --url ${url}`);
1448
2025
  console.log("");
1449
2026
  }
1450
2027
 
1451
2028
  // src/commands/fix.ts
1452
- var import_child_process = require("child_process");
2029
+ var import_child_process2 = require("child_process");
1453
2030
  var import_util = require("util");
1454
- var import_agent_runtime3 = require("@rankcli/agent-runtime");
1455
- var import_ora2 = __toESM(require("ora"));
1456
- var import_chalk3 = __toESM(require("chalk"));
1457
- var execAsync = (0, import_util.promisify)(import_child_process.exec);
2031
+ var import_agent_runtime4 = require("@rankcli/agent-runtime");
2032
+ var import_ora3 = __toESM(require("ora"));
2033
+ var import_chalk5 = __toESM(require("chalk"));
2034
+ var execAsync = (0, import_util.promisify)(import_child_process2.exec);
1458
2035
  var SEVERITY_ICON2 = {
1459
2036
  critical: "\u274C",
1460
2037
  error: "\u274C",
@@ -1471,8 +2048,7 @@ function generateCommitMessage(fixes, url) {
1471
2048
  (f) => f.issue.code.includes("MISSING") || f.issue.message.toLowerCase().includes("missing") || f.issue.message.toLowerCase().includes("add")
1472
2049
  );
1473
2050
  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";
2051
+ const categoryStr = "seo";
1476
2052
  const issueCount = applied.length;
1477
2053
  const title = `${type}(${categoryStr}): fix ${issueCount} SEO issue${issueCount > 1 ? "s" : ""}`;
1478
2054
  const fixList = applied.map((f) => `- ${f.issue.code}: ${f.issue.message}`).join("\n");
@@ -1521,19 +2097,19 @@ function printFix2(fix2, index) {
1521
2097
  const icon = SEVERITY_ICON2[fix2.issue.severity] || "\u2022";
1522
2098
  console.log(`
1523
2099
  ${index + 1}. ${icon} ${fix2.issue.message}`);
1524
- console.log(` Code: ${import_chalk3.default.dim(fix2.issue.code)}`);
1525
- console.log(` File: ${import_chalk3.default.cyan(fix2.file)}`);
2100
+ console.log(` Code: ${import_chalk5.default.dim(fix2.issue.code)}`);
2101
+ console.log(` File: ${import_chalk5.default.cyan(fix2.file)}`);
1526
2102
  if (fix2.skipped) {
1527
- console.log(` Status: ${import_chalk3.default.yellow("SKIPPED")} - ${fix2.skipReason}`);
2103
+ console.log(` Status: ${import_chalk5.default.yellow("SKIPPED")} - ${fix2.skipReason}`);
1528
2104
  } else if (fix2.applied) {
1529
- console.log(` Status: ${import_chalk3.default.green("APPLIED")}`);
1530
- console.log(` ${import_chalk3.default.dim("Explanation:")} ${fix2.explanation}`);
2105
+ console.log(` Status: ${import_chalk5.default.green("APPLIED")}`);
2106
+ console.log(` ${import_chalk5.default.dim("Explanation:")} ${fix2.explanation}`);
1531
2107
  } else {
1532
- console.log(` ${import_chalk3.default.dim("Explanation:")} ${fix2.explanation}`);
2108
+ console.log(` ${import_chalk5.default.dim("Explanation:")} ${fix2.explanation}`);
1533
2109
  if (fix2.before) {
1534
- console.log(` ${import_chalk3.default.red("Before:")} ${truncate2(fix2.before, 60)}`);
2110
+ console.log(` ${import_chalk5.default.red("Before:")} ${truncate2(fix2.before, 60)}`);
1535
2111
  }
1536
- console.log(` ${import_chalk3.default.green("After:")} ${truncate2(fix2.after, 100)}`);
2112
+ console.log(` ${import_chalk5.default.green("After:")} ${truncate2(fix2.after, 100)}`);
1537
2113
  }
1538
2114
  }
1539
2115
  async function fix(options) {
@@ -1577,15 +2153,15 @@ async function fix(options) {
1577
2153
  console.log("\n\u{1F527} RankCLI Fix - Agentic SEO Fixer");
1578
2154
  console.log("\u2550".repeat(50));
1579
2155
  }
1580
- const frameworkSpinner = options.output !== "json" ? (0, import_ora2.default)("Detecting framework...").start() : null;
1581
- const frameworkResult = await (0, import_agent_runtime3.detectFramework)({ cwd });
2156
+ const frameworkSpinner = options.output !== "json" ? (0, import_ora3.default)("Detecting framework...").start() : null;
2157
+ const frameworkResult = await (0, import_agent_runtime4.detectFramework)({ cwd });
1582
2158
  const framework = frameworkResult.success ? frameworkResult.data : { name: "Unknown", metaPattern: "html-head" };
1583
2159
  frameworkSpinner?.succeed(`Framework: ${framework.name}`);
1584
- const analyzeSpinner = options.output !== "json" ? (0, import_ora2.default)(`Analyzing ${url}...`).start() : null;
2160
+ const analyzeSpinner = options.output !== "json" ? (0, import_ora3.default)(`Analyzing ${url}...`).start() : null;
1585
2161
  let issues;
1586
2162
  let score;
1587
2163
  try {
1588
- const analysis = await (0, import_agent_runtime3.runDirectAnalysis)({ url });
2164
+ const analysis = await (0, import_agent_runtime4.runDirectAnalysis)(url);
1589
2165
  issues = analysis.issues;
1590
2166
  score = analysis.score;
1591
2167
  } catch (error) {
@@ -1598,7 +2174,7 @@ async function fix(options) {
1598
2174
  const onlyCodes = new Set(options.only.map((c) => c.toUpperCase()));
1599
2175
  issues = issues.filter((i) => onlyCodes.has(i.code.toUpperCase()));
1600
2176
  if (options.output !== "json") {
1601
- console.log(import_chalk3.default.dim(` Filtered to ${issues.length} issue(s) matching: ${options.only.join(", ")}`));
2177
+ console.log(import_chalk5.default.dim(` Filtered to ${issues.length} issue(s) matching: ${options.only.join(", ")}`));
1602
2178
  }
1603
2179
  }
1604
2180
  if (issues.length === 0) {
@@ -1615,8 +2191,8 @@ async function fix(options) {
1615
2191
  durationMs: Date.now() - startTime
1616
2192
  };
1617
2193
  }
1618
- const fixSpinner = options.output !== "json" ? (0, import_ora2.default)("Generating fixes...").start() : null;
1619
- const fixes = await (0, import_agent_runtime3.generateFixes)(issues, {
2194
+ const fixSpinner = options.output !== "json" ? (0, import_ora3.default)("Generating fixes...").start() : null;
2195
+ const fixes = await (0, import_agent_runtime4.generateFixes)(issues, {
1620
2196
  cwd,
1621
2197
  url,
1622
2198
  framework
@@ -1627,13 +2203,13 @@ async function fix(options) {
1627
2203
  if (options.dryRun) {
1628
2204
  if (options.output !== "json") {
1629
2205
  console.log("\n" + "\u2500".repeat(50));
1630
- console.log(import_chalk3.default.yellow("DRY RUN - No changes will be made"));
2206
+ console.log(import_chalk5.default.yellow("DRY RUN - No changes will be made"));
1631
2207
  console.log("\u2500".repeat(50));
1632
2208
  for (let i = 0; i < fixes.length; i++) {
1633
2209
  printFix2(fixes[i], i);
1634
2210
  }
1635
2211
  console.log("\n" + "\u2500".repeat(50));
1636
- console.log(`\u{1F4CB} ${import_chalk3.default.bold("Summary:")} ${actionableFixes.length} fix(es) would be applied`);
2212
+ console.log(`\u{1F4CB} ${import_chalk5.default.bold("Summary:")} ${actionableFixes.length} fix(es) would be applied`);
1637
2213
  console.log(` Remove --dry-run to apply these fixes.
1638
2214
  `);
1639
2215
  }
@@ -1699,8 +2275,8 @@ async function fix(options) {
1699
2275
  durationMs: Date.now() - startTime
1700
2276
  };
1701
2277
  }
1702
- const applySpinner = options.output !== "json" ? (0, import_ora2.default)("Applying fixes...").start() : null;
1703
- const { applied, skipped } = await (0, import_agent_runtime3.applyFixes)(fixes, { cwd, dryRun: false });
2278
+ const applySpinner = options.output !== "json" ? (0, import_ora3.default)("Applying fixes...").start() : null;
2279
+ const { applied, skipped } = await (0, import_agent_runtime4.applyFixes)(fixes, { cwd, dryRun: false });
1704
2280
  for (const fix2 of applied) {
1705
2281
  fix2.applied = true;
1706
2282
  }
@@ -1711,7 +2287,7 @@ async function fix(options) {
1711
2287
  let commitError;
1712
2288
  let pushError;
1713
2289
  if (options.commit && applied.length > 0 && commitMessage) {
1714
- const gitSpinner = options.output !== "json" ? (0, import_ora2.default)("Committing changes...").start() : null;
2290
+ const gitSpinner = options.output !== "json" ? (0, import_ora3.default)("Committing changes...").start() : null;
1715
2291
  const gitResult = await executeGitCommit(cwd, commitMessage, options.push || false);
1716
2292
  committed = gitResult.committed;
1717
2293
  pushed = gitResult.pushed;
@@ -1737,23 +2313,23 @@ async function fix(options) {
1737
2313
  console.log("\u2550".repeat(50));
1738
2314
  if (applied.length > 0) {
1739
2315
  console.log(`
1740
- ${import_chalk3.default.green("\u2705")} Applied ${applied.length} fix(es):`);
2316
+ ${import_chalk5.default.green("\u2705")} Applied ${applied.length} fix(es):`);
1741
2317
  for (const fix2 of applied) {
1742
2318
  console.log(` - ${fix2.file}: ${fix2.issue.message}`);
1743
2319
  }
1744
2320
  }
1745
2321
  if (skipped.length > 0) {
1746
2322
  console.log(`
1747
- ${import_chalk3.default.yellow("\u26A0\uFE0F")} Skipped ${skipped.length} fix(es):`);
2323
+ ${import_chalk5.default.yellow("\u26A0\uFE0F")} Skipped ${skipped.length} fix(es):`);
1748
2324
  for (const fix2 of skipped) {
1749
2325
  console.log(` - ${fix2.file}: ${fix2.skipReason || "Manual fix required"}`);
1750
2326
  }
1751
2327
  }
1752
2328
  if (committed) {
1753
2329
  console.log(`
1754
- ${import_chalk3.default.green("\u2705")} Changes committed`);
2330
+ ${import_chalk5.default.green("\u2705")} Changes committed`);
1755
2331
  if (pushed) {
1756
- console.log(`${import_chalk3.default.green("\u2705")} Changes pushed`);
2332
+ console.log(`${import_chalk5.default.green("\u2705")} Changes pushed`);
1757
2333
  }
1758
2334
  }
1759
2335
  if (!committed && applied.length > 0) {
@@ -1806,10 +2382,10 @@ ${import_chalk3.default.green("\u2705")} Changes committed`);
1806
2382
  }
1807
2383
 
1808
2384
  // src/commands/track.ts
1809
- var import_ora3 = __toESM(require("ora"));
1810
- var import_chalk4 = __toESM(require("chalk"));
2385
+ var import_ora4 = __toESM(require("ora"));
2386
+ var import_chalk6 = __toESM(require("chalk"));
1811
2387
  var import_table = require("table");
1812
- var import_agent_runtime4 = require("@rankcli/agent-runtime");
2388
+ var import_agent_runtime5 = require("@rankcli/agent-runtime");
1813
2389
  async function getProjectForUrl(url) {
1814
2390
  const supabase = await getAuthenticatedClient();
1815
2391
  if (!supabase) return null;
@@ -1839,42 +2415,42 @@ async function getProjectForUrl(url) {
1839
2415
  };
1840
2416
  }
1841
2417
  function getTierLimits(tier) {
1842
- const limits = import_agent_runtime4.TIER_LIMITS[tier] || import_agent_runtime4.TIER_LIMITS.free;
2418
+ const limits = import_agent_runtime5.TIER_LIMITS[tier] || import_agent_runtime5.TIER_LIMITS.free;
1843
2419
  return {
1844
2420
  maxKeywords: limits.maxKeywords,
1845
2421
  checksPerDay: limits.checksPerDay
1846
2422
  };
1847
2423
  }
1848
2424
  function formatPosition(pos) {
1849
- if (pos === null) return import_chalk4.default.dim("N/A");
1850
- if (pos <= 3) return import_chalk4.default.green.bold(`#${pos}`);
1851
- if (pos <= 10) return import_chalk4.default.green(`#${pos}`);
1852
- if (pos <= 20) return import_chalk4.default.yellow(`#${pos}`);
1853
- if (pos <= 50) return import_chalk4.default.red(`#${pos}`);
1854
- return import_chalk4.default.red.dim(`#${pos}`);
2425
+ if (pos === null) return import_chalk6.default.dim("N/A");
2426
+ if (pos <= 3) return import_chalk6.default.green.bold(`#${pos}`);
2427
+ if (pos <= 10) return import_chalk6.default.green(`#${pos}`);
2428
+ if (pos <= 20) return import_chalk6.default.yellow(`#${pos}`);
2429
+ if (pos <= 50) return import_chalk6.default.red(`#${pos}`);
2430
+ return import_chalk6.default.red.dim(`#${pos}`);
1855
2431
  }
1856
2432
  function formatChange(change) {
1857
- if (change === 0) return import_chalk4.default.dim("\u2192");
1858
- if (change > 0) return import_chalk4.default.green(`\u2191${change}`);
1859
- return import_chalk4.default.red(`\u2193${Math.abs(change)}`);
2433
+ if (change === 0) return import_chalk6.default.dim("\u2192");
2434
+ if (change > 0) return import_chalk6.default.green(`\u2191${change}`);
2435
+ return import_chalk6.default.red(`\u2193${Math.abs(change)}`);
1860
2436
  }
1861
2437
  async function trackAdd(options) {
1862
2438
  if (!options.keywords || options.keywords.length === 0) {
1863
- console.log(import_chalk4.default.yellow("\n\u26A0\uFE0F Please provide keywords to track:"));
2439
+ console.log(import_chalk6.default.yellow("\n\u26A0\uFE0F Please provide keywords to track:"));
1864
2440
  console.log(' rankcli track add "seo tool" "cli seo" --url https://your-site.com\n');
1865
2441
  return { success: false, error: "No keywords provided" };
1866
2442
  }
1867
2443
  if (!options.url) {
1868
- console.log(import_chalk4.default.yellow("\n\u26A0\uFE0F Please provide a URL to track:"));
2444
+ console.log(import_chalk6.default.yellow("\n\u26A0\uFE0F Please provide a URL to track:"));
1869
2445
  console.log(' rankcli track add "keyword" --url https://your-site.com\n');
1870
2446
  return { success: false, error: "No URL provided" };
1871
2447
  }
1872
2448
  if (!isLoggedIn()) {
1873
- console.log(import_chalk4.default.yellow("\n\u26A0\uFE0F Please log in to track keywords:"));
2449
+ console.log(import_chalk6.default.yellow("\n\u26A0\uFE0F Please log in to track keywords:"));
1874
2450
  console.log(" rankcli login\n");
1875
2451
  return { success: false, error: "Not logged in" };
1876
2452
  }
1877
- const spinner = (0, import_ora3.default)("Adding keywords to track...").start();
2453
+ const spinner = (0, import_ora4.default)("Adding keywords to track...").start();
1878
2454
  try {
1879
2455
  const project = await getProjectForUrl(options.url);
1880
2456
  if (!project) {
@@ -1894,7 +2470,7 @@ async function trackAdd(options) {
1894
2470
  const newCount = options.keywords.length;
1895
2471
  if (existingCount + newCount > maxKeywords) {
1896
2472
  spinner.fail(`Keyword limit reached (${existingCount}/${maxKeywords})`);
1897
- showUpsell("keyword_limit", { tier, current: existingCount, limit: maxKeywords });
2473
+ showUpsell("keyword_limit", { tier, current: existingCount, max: maxKeywords });
1898
2474
  return { success: false, error: `Keyword limit reached. Upgrade to track more keywords.` };
1899
2475
  }
1900
2476
  const keywordRecords = options.keywords.map((keyword) => ({
@@ -1915,10 +2491,10 @@ async function trackAdd(options) {
1915
2491
  }
1916
2492
  const addedCount = data?.length || 0;
1917
2493
  spinner.succeed(`Added ${addedCount} keyword(s) to track`);
1918
- console.log(import_chalk4.default.dim(`
2494
+ console.log(import_chalk6.default.dim(`
1919
2495
  Tracking: ${options.keywords.join(", ")}`));
1920
- console.log(import_chalk4.default.dim(`For: ${project.domain}`));
1921
- console.log(import_chalk4.default.cyan(`
2496
+ console.log(import_chalk6.default.dim(`For: ${project.domain}`));
2497
+ console.log(import_chalk6.default.cyan(`
1922
2498
  Run 'rankcli track check --url ${options.url}' to check current positions.
1923
2499
  `));
1924
2500
  return { success: true, addedCount };
@@ -1929,16 +2505,16 @@ Run 'rankcli track check --url ${options.url}' to check current positions.
1929
2505
  }
1930
2506
  async function trackStatus(options) {
1931
2507
  if (!options.url) {
1932
- console.log(import_chalk4.default.yellow("\n\u26A0\uFE0F Please provide a URL:"));
2508
+ console.log(import_chalk6.default.yellow("\n\u26A0\uFE0F Please provide a URL:"));
1933
2509
  console.log(" rankcli track status --url https://your-site.com\n");
1934
2510
  return { success: false, error: "No URL provided" };
1935
2511
  }
1936
2512
  if (!isLoggedIn()) {
1937
- console.log(import_chalk4.default.yellow("\n\u26A0\uFE0F Please log in to view tracking:"));
2513
+ console.log(import_chalk6.default.yellow("\n\u26A0\uFE0F Please log in to view tracking:"));
1938
2514
  console.log(" rankcli login\n");
1939
2515
  return { success: false, error: "Not logged in" };
1940
2516
  }
1941
- const spinner = (0, import_ora3.default)("Loading keyword rankings...").start();
2517
+ const spinner = (0, import_ora4.default)("Loading keyword rankings...").start();
1942
2518
  try {
1943
2519
  const project = await getProjectForUrl(options.url);
1944
2520
  if (!project) {
@@ -1957,8 +2533,8 @@ async function trackStatus(options) {
1957
2533
  }
1958
2534
  spinner.succeed(`Loaded ${data?.length || 0} tracked keywords`);
1959
2535
  if (!data || data.length === 0) {
1960
- console.log(import_chalk4.default.dim("\nNo keywords tracked yet."));
1961
- console.log(import_chalk4.default.cyan(`Run 'rankcli track add "keyword" --url ${options.url}' to start tracking.
2536
+ console.log(import_chalk6.default.dim("\nNo keywords tracked yet."));
2537
+ console.log(import_chalk6.default.cyan(`Run 'rankcli track add "keyword" --url ${options.url}' to start tracking.
1962
2538
  `));
1963
2539
  return { success: true, keywords: [] };
1964
2540
  }
@@ -1973,7 +2549,7 @@ async function trackStatus(options) {
1973
2549
  if (options.output === "json") {
1974
2550
  console.log(JSON.stringify({ keywords: keywords2 }, null, 2));
1975
2551
  } else {
1976
- console.log("\n" + import_chalk4.default.bold("\u{1F4CA} Keyword Rankings"));
2552
+ console.log("\n" + import_chalk6.default.bold("\u{1F4CA} Keyword Rankings"));
1977
2553
  console.log("\u2550".repeat(60));
1978
2554
  const tableData = [
1979
2555
  ["Keyword", "Position", "Best", "Change", "Last Checked"],
@@ -2016,14 +2592,14 @@ async function trackHistory(options) {
2016
2592
  return { success: false, error: "No URL provided" };
2017
2593
  }
2018
2594
  if (!options.keyword) {
2019
- console.log(import_chalk4.default.yellow("\n\u26A0\uFE0F Please provide a keyword:"));
2595
+ console.log(import_chalk6.default.yellow("\n\u26A0\uFE0F Please provide a keyword:"));
2020
2596
  console.log(' rankcli track history --keyword "seo tool" --url https://your-site.com\n');
2021
2597
  return { success: false, error: "No keyword provided" };
2022
2598
  }
2023
2599
  if (!isLoggedIn()) {
2024
2600
  return { success: false, error: "Not logged in" };
2025
2601
  }
2026
- const spinner = (0, import_ora3.default)(`Loading history for "${options.keyword}"...`).start();
2602
+ const spinner = (0, import_ora4.default)(`Loading history for "${options.keyword}"...`).start();
2027
2603
  try {
2028
2604
  const project = await getProjectForUrl(options.url);
2029
2605
  if (!project) {
@@ -2056,21 +2632,21 @@ async function trackHistory(options) {
2056
2632
  if (options.output === "json") {
2057
2633
  console.log(JSON.stringify({ keyword: options.keyword, history }, null, 2));
2058
2634
  } else {
2059
- console.log("\n" + import_chalk4.default.bold(`\u{1F4C8} History: "${options.keyword}"`));
2635
+ console.log("\n" + import_chalk6.default.bold(`\u{1F4C8} History: "${options.keyword}"`));
2060
2636
  console.log("\u2550".repeat(50));
2061
2637
  if (history.length === 0) {
2062
- console.log(import_chalk4.default.dim("\nNo history available yet.\n"));
2638
+ console.log(import_chalk6.default.dim("\nNo history available yet.\n"));
2063
2639
  } else {
2064
2640
  const positions = history.slice(0, 30).reverse().map((h) => h.position || 101);
2065
2641
  const sparkline = positions.map((p) => {
2066
- if (p <= 10) return import_chalk4.default.green("\u2588");
2067
- if (p <= 20) return import_chalk4.default.yellow("\u2593");
2068
- if (p <= 50) return import_chalk4.default.red("\u2592");
2069
- return import_chalk4.default.dim("\u2591");
2642
+ if (p <= 10) return import_chalk6.default.green("\u2588");
2643
+ if (p <= 20) return import_chalk6.default.yellow("\u2593");
2644
+ if (p <= 50) return import_chalk6.default.red("\u2592");
2645
+ return import_chalk6.default.dim("\u2591");
2070
2646
  }).join("");
2071
2647
  console.log(`
2072
2648
  ${sparkline} (last ${positions.length} checks)`);
2073
- console.log(import_chalk4.default.dim(" \u2190 newer older \u2192\n"));
2649
+ console.log(import_chalk6.default.dim(" \u2190 newer older \u2192\n"));
2074
2650
  const recent = history.slice(0, 10);
2075
2651
  for (const entry of recent) {
2076
2652
  console.log(` ${entry.recordedAt.toLocaleDateString()} ${formatPosition(entry.position)}`);
@@ -2090,7 +2666,7 @@ async function trackExport(options) {
2090
2666
  if (!isLoggedIn()) {
2091
2667
  return { success: false, error: "Not logged in" };
2092
2668
  }
2093
- const spinner = (0, import_ora3.default)("Exporting data...").start();
2669
+ const spinner = (0, import_ora4.default)("Exporting data...").start();
2094
2670
  try {
2095
2671
  const project = await getProjectForUrl(options.url);
2096
2672
  if (!project) {
@@ -2147,12 +2723,12 @@ async function trackExport(options) {
2147
2723
  }
2148
2724
  async function trackCheck(options) {
2149
2725
  if (!options.url) {
2150
- console.log(import_chalk4.default.yellow("\n\u26A0\uFE0F Please provide a URL:"));
2726
+ console.log(import_chalk6.default.yellow("\n\u26A0\uFE0F Please provide a URL:"));
2151
2727
  console.log(" rankcli track check --url https://your-site.com\n");
2152
2728
  return { success: false, error: "No URL provided" };
2153
2729
  }
2154
2730
  if (!isLoggedIn()) {
2155
- console.log(import_chalk4.default.yellow("\n\u26A0\uFE0F Please log in to check rankings:"));
2731
+ console.log(import_chalk6.default.yellow("\n\u26A0\uFE0F Please log in to check rankings:"));
2156
2732
  console.log(" rankcli login\n");
2157
2733
  return { success: false, error: "Not logged in" };
2158
2734
  }
@@ -2169,14 +2745,14 @@ async function trackCheck(options) {
2169
2745
  const minHoursBetweenChecks = 24 / checksPerDay;
2170
2746
  if (hoursSinceCheck < minHoursBetweenChecks) {
2171
2747
  const nextCheck = new Date(project.lastRankCheckAt.getTime() + minHoursBetweenChecks * 60 * 60 * 1e3);
2172
- console.log(import_chalk4.default.yellow(`
2748
+ console.log(import_chalk6.default.yellow(`
2173
2749
  \u26A0\uFE0F Rate limited. Next check available: ${nextCheck.toLocaleString()}`));
2174
- console.log(import_chalk4.default.dim(` Your tier (${tier}) allows ${checksPerDay} check(s) per day.
2750
+ console.log(import_chalk6.default.dim(` Your tier (${tier}) allows ${checksPerDay} check(s) per day.
2175
2751
  `));
2176
2752
  return { success: true, rateLimited: true };
2177
2753
  }
2178
2754
  }
2179
- const spinner = (0, import_ora3.default)("Checking current rankings...").start();
2755
+ const spinner = (0, import_ora4.default)("Checking current rankings...").start();
2180
2756
  const supabase = await getAuthenticatedClient();
2181
2757
  if (!supabase) {
2182
2758
  spinner.fail("Could not connect to database");
@@ -2190,11 +2766,11 @@ async function trackCheck(options) {
2190
2766
  const serpApiKey = process.env.VALUESERP_API_KEY || process.env.SERPAPI_KEY;
2191
2767
  if (!serpApiKey) {
2192
2768
  spinner.warn("No SERP API key configured");
2193
- console.log(import_chalk4.default.yellow("\n\u26A0\uFE0F Set VALUESERP_API_KEY or SERPAPI_KEY to check live rankings."));
2194
- console.log(import_chalk4.default.dim(" Get a key at https://www.valueserp.com or https://serpapi.com\n"));
2769
+ console.log(import_chalk6.default.yellow("\n\u26A0\uFE0F Set VALUESERP_API_KEY or SERPAPI_KEY to check live rankings."));
2770
+ console.log(import_chalk6.default.dim(" Get a key at https://www.valueserp.com or https://serpapi.com\n"));
2195
2771
  return { success: false, error: "No SERP API key" };
2196
2772
  }
2197
- const tracker = new import_agent_runtime4.RankTracker({
2773
+ const tracker = new import_agent_runtime5.RankTracker({
2198
2774
  supabase,
2199
2775
  serpConfig: {
2200
2776
  provider: process.env.VALUESERP_API_KEY ? "valueserp" : "serpapi",
@@ -2203,13 +2779,13 @@ async function trackCheck(options) {
2203
2779
  });
2204
2780
  const results = await tracker.checkRankings(project.id, project.domain);
2205
2781
  spinner.succeed(`Checked ${results.length} keyword(s)`);
2206
- console.log("\n" + import_chalk4.default.bold("\u{1F50D} Rank Check Results"));
2782
+ console.log("\n" + import_chalk6.default.bold("\u{1F50D} Rank Check Results"));
2207
2783
  console.log("\u2550".repeat(50));
2208
2784
  for (const result of results) {
2209
2785
  const posStr = formatPosition(result.position);
2210
2786
  console.log(` ${result.keyword}: ${posStr}`);
2211
2787
  if (result.serpFeatures.length > 0) {
2212
- console.log(import_chalk4.default.dim(` SERP features: ${result.serpFeatures.map((f) => f.type).join(", ")}`));
2788
+ console.log(import_chalk6.default.dim(` SERP features: ${result.serpFeatures.map((f) => f.type).join(", ")}`));
2213
2789
  }
2214
2790
  }
2215
2791
  console.log("");
@@ -2227,9 +2803,9 @@ async function trackCheck(options) {
2227
2803
  }
2228
2804
 
2229
2805
  // src/commands/geo.ts
2230
- var import_agent_runtime5 = require("@rankcli/agent-runtime");
2231
- var import_ora4 = __toESM(require("ora"));
2232
- var import_chalk5 = __toESM(require("chalk"));
2806
+ var import_agent_runtime6 = require("@rankcli/agent-runtime");
2807
+ var import_ora5 = __toESM(require("ora"));
2808
+ var import_chalk7 = __toESM(require("chalk"));
2233
2809
  async function fetchResources(url) {
2234
2810
  const headers = {};
2235
2811
  let html = null;
@@ -2275,7 +2851,7 @@ async function geo(options) {
2275
2851
  console.log("\n\u274C Invalid URL format. Please provide a valid URL.\n");
2276
2852
  return;
2277
2853
  }
2278
- const spinner = (0, import_ora4.default)("Fetching page and robots.txt...").start();
2854
+ const spinner = (0, import_ora5.default)("Fetching page and robots.txt...").start();
2279
2855
  const { html, robotsTxt, headers } = await fetchResources(url);
2280
2856
  if (!html) {
2281
2857
  spinner.fail("Could not fetch page content");
@@ -2283,11 +2859,11 @@ async function geo(options) {
2283
2859
  }
2284
2860
  spinner.text = "Running GEO analysis...";
2285
2861
  try {
2286
- const geoResult = await import_agent_runtime5.analyzers.analyzeGEO(html, url, robotsTxt || void 0);
2287
- const cwvResult = import_agent_runtime5.analyzers.analyzeCoreWebVitals(html, url, headers);
2288
- const securityResult = import_agent_runtime5.analyzers.analyzeSecurityHeaders(headers, url);
2289
- const schemaResult = import_agent_runtime5.analyzers.analyzeStructuredData(html, url);
2290
- const mobileResult = import_agent_runtime5.analyzers.analyzeMobileSEO(html, url);
2862
+ const geoResult = await import_agent_runtime6.analyzers.analyzeGEO(html, url, robotsTxt || void 0);
2863
+ const cwvResult = import_agent_runtime6.analyzers.analyzeCoreWebVitals(html, url, headers);
2864
+ const securityResult = import_agent_runtime6.analyzers.analyzeSecurityHeaders(headers, url);
2865
+ const schemaResult = import_agent_runtime6.analyzers.analyzeStructuredData(html, url);
2866
+ const mobileResult = import_agent_runtime6.analyzers.analyzeMobileSEO(html, url);
2291
2867
  spinner.succeed("GEO analysis complete");
2292
2868
  if (options.output === "json") {
2293
2869
  const jsonOutput = {
@@ -2301,55 +2877,55 @@ async function geo(options) {
2301
2877
  console.log(JSON.stringify(jsonOutput, null, 2));
2302
2878
  return;
2303
2879
  }
2304
- console.log("\n" + import_chalk5.default.bold("\u2550".repeat(60)));
2305
- console.log(import_chalk5.default.bold.cyan(" GEO ANALYSIS: AI Search Optimization"));
2306
- console.log(import_chalk5.default.bold("\u2550".repeat(60)));
2880
+ console.log("\n" + import_chalk7.default.bold("\u2550".repeat(60)));
2881
+ console.log(import_chalk7.default.bold.cyan(" GEO ANALYSIS: AI Search Optimization"));
2882
+ console.log(import_chalk7.default.bold("\u2550".repeat(60)));
2307
2883
  const geoScore = geoResult.score;
2308
- const scoreColor = geoScore >= 80 ? import_chalk5.default.green : geoScore >= 60 ? import_chalk5.default.yellow : import_chalk5.default.red;
2884
+ const scoreColor = geoScore >= 80 ? import_chalk7.default.green : geoScore >= 60 ? import_chalk7.default.yellow : import_chalk7.default.red;
2309
2885
  console.log(`
2310
- ${import_chalk5.default.bold("GEO Score:")} ${scoreColor.bold(geoScore + "/100")}`);
2311
- console.log("\n" + import_chalk5.default.bold(" AI Crawler Access:"));
2886
+ ${import_chalk7.default.bold("GEO Score:")} ${scoreColor.bold(geoScore + "/100")}`);
2887
+ console.log("\n" + import_chalk7.default.bold(" AI Crawler Access:"));
2312
2888
  const { aiCrawlerAccess } = geoResult;
2313
2889
  if (aiCrawlerAccess.blockedCrawlers.length > 0) {
2314
- console.log(import_chalk5.default.red(` \u274C Blocked: ${aiCrawlerAccess.blockedCrawlers.join(", ")}`));
2890
+ console.log(import_chalk7.default.red(` \u274C Blocked: ${aiCrawlerAccess.blockedCrawlers.join(", ")}`));
2315
2891
  }
2316
2892
  if (aiCrawlerAccess.allowedCrawlers.length > 0) {
2317
2893
  const topAllowed = aiCrawlerAccess.allowedCrawlers.slice(0, 5);
2318
- console.log(import_chalk5.default.green(` \u2705 Allowed: ${topAllowed.join(", ")}${aiCrawlerAccess.allowedCrawlers.length > 5 ? "..." : ""}`));
2894
+ console.log(import_chalk7.default.green(` \u2705 Allowed: ${topAllowed.join(", ")}${aiCrawlerAccess.allowedCrawlers.length > 5 ? "..." : ""}`));
2319
2895
  }
2320
- console.log("\n" + import_chalk5.default.bold(" Rendering:"));
2896
+ console.log("\n" + import_chalk7.default.bold(" Rendering:"));
2321
2897
  if (aiCrawlerAccess.serverSideRendered) {
2322
- console.log(import_chalk5.default.green(" \u2705 Server-side rendered (AI crawlers can see content)"));
2898
+ console.log(import_chalk7.default.green(" \u2705 Server-side rendered (AI crawlers can see content)"));
2323
2899
  } else if (aiCrawlerAccess.jsRenderingRequired) {
2324
- console.log(import_chalk5.default.red(" \u274C JavaScript required (AI crawlers may see blank page)"));
2900
+ console.log(import_chalk7.default.red(" \u274C JavaScript required (AI crawlers may see blank page)"));
2325
2901
  }
2326
- console.log("\n" + import_chalk5.default.bold(" LLM Friendliness:"));
2902
+ console.log("\n" + import_chalk7.default.bold(" LLM Friendliness:"));
2327
2903
  const { llmSignals } = geoResult;
2328
2904
  const bar = (score) => {
2329
2905
  const filled = Math.round(score / 5);
2330
2906
  const empty = 20 - filled;
2331
- const color = score >= 70 ? import_chalk5.default.green : score >= 40 ? import_chalk5.default.yellow : import_chalk5.default.red;
2332
- return color("\u2588".repeat(filled)) + import_chalk5.default.gray("\u2591".repeat(empty));
2907
+ const color = score >= 70 ? import_chalk7.default.green : score >= 40 ? import_chalk7.default.yellow : import_chalk7.default.red;
2908
+ return color("\u2588".repeat(filled)) + import_chalk7.default.gray("\u2591".repeat(empty));
2333
2909
  };
2334
2910
  console.log(` Content Clarity: ${bar(llmSignals.contentClarity)} ${llmSignals.contentClarity}%`);
2335
2911
  console.log(` Fact Density: ${bar(llmSignals.factDensity)} ${llmSignals.factDensity}%`);
2336
2912
  console.log(` Structure: ${bar(llmSignals.structureQuality)} ${llmSignals.structureQuality}%`);
2337
2913
  console.log(` Citations: ${bar(llmSignals.citationQuality)} ${llmSignals.citationQuality}%`);
2338
- console.log("\n" + import_chalk5.default.bold(" Content Structure:"));
2914
+ console.log("\n" + import_chalk7.default.bold(" Content Structure:"));
2339
2915
  const { contentStructure } = geoResult;
2340
- console.log(` ${contentStructure.hasStructuredData ? import_chalk5.default.green("\u2705") : import_chalk5.default.red("\u274C")} JSON-LD Structured Data`);
2341
- console.log(` ${contentStructure.hasFAQSchema ? import_chalk5.default.green("\u2705") : import_chalk5.default.yellow("\u25CB")} FAQ Schema`);
2342
- console.log(` ${contentStructure.hasArticleSchema ? import_chalk5.default.green("\u2705") : import_chalk5.default.yellow("\u25CB")} Article Schema`);
2343
- console.log(` ${contentStructure.hasBreadcrumbs ? import_chalk5.default.green("\u2705") : import_chalk5.default.yellow("\u25CB")} Breadcrumbs`);
2344
- console.log(` Heading Hierarchy: ${contentStructure.headingHierarchy === "good" ? import_chalk5.default.green("Good") : contentStructure.headingHierarchy === "needs-work" ? import_chalk5.default.yellow("Needs Work") : import_chalk5.default.red("Poor")}`);
2345
- console.log("\n" + import_chalk5.default.bold(" Citation Readiness:"));
2916
+ console.log(` ${contentStructure.hasStructuredData ? import_chalk7.default.green("\u2705") : import_chalk7.default.red("\u274C")} JSON-LD Structured Data`);
2917
+ console.log(` ${contentStructure.hasFAQSchema ? import_chalk7.default.green("\u2705") : import_chalk7.default.yellow("\u25CB")} FAQ Schema`);
2918
+ console.log(` ${contentStructure.hasArticleSchema ? import_chalk7.default.green("\u2705") : import_chalk7.default.yellow("\u25CB")} Article Schema`);
2919
+ console.log(` ${contentStructure.hasBreadcrumbs ? import_chalk7.default.green("\u2705") : import_chalk7.default.yellow("\u25CB")} Breadcrumbs`);
2920
+ console.log(` Heading Hierarchy: ${contentStructure.headingHierarchy === "good" ? import_chalk7.default.green("Good") : contentStructure.headingHierarchy === "needs-work" ? import_chalk7.default.yellow("Needs Work") : import_chalk7.default.red("Poor")}`);
2921
+ console.log("\n" + import_chalk7.default.bold(" Citation Readiness:"));
2346
2922
  const { citationReadiness } = geoResult;
2347
2923
  if (citationReadiness.trustSignals.length > 0) {
2348
- console.log(import_chalk5.default.green(` \u2705 ${citationReadiness.trustSignals.join(", ")}`));
2924
+ console.log(import_chalk7.default.green(` \u2705 ${citationReadiness.trustSignals.join(", ")}`));
2349
2925
  } else {
2350
- console.log(import_chalk5.default.yellow(" \u25CB No trust signals detected"));
2926
+ console.log(import_chalk7.default.yellow(" \u25CB No trust signals detected"));
2351
2927
  }
2352
- console.log("\n" + import_chalk5.default.bold(" Other Scores:"));
2928
+ console.log("\n" + import_chalk7.default.bold(" Other Scores:"));
2353
2929
  console.log(` Core Web Vitals: ${bar(cwvResult.overallScore)} ${cwvResult.overallScore}%`);
2354
2930
  console.log(` Security Headers: ${securityResult.grade} (${securityResult.score}%)`);
2355
2931
  console.log(` Structured Data: ${bar(schemaResult.score)} ${schemaResult.score}%`);
@@ -2363,18 +2939,18 @@ async function geo(options) {
2363
2939
  ];
2364
2940
  const criticalIssues = allIssues.filter((i) => i.severity === "critical" || i.severity === "error");
2365
2941
  if (criticalIssues.length > 0) {
2366
- console.log("\n" + import_chalk5.default.bold.red(" Critical Issues:"));
2942
+ console.log("\n" + import_chalk7.default.bold.red(" Critical Issues:"));
2367
2943
  criticalIssues.slice(0, 5).forEach((issue, i) => {
2368
- console.log(import_chalk5.default.red(` ${i + 1}. ${issue.title}`));
2944
+ console.log(import_chalk7.default.red(` ${i + 1}. ${issue.title}`));
2369
2945
  });
2370
2946
  }
2371
2947
  if (geoResult.recommendations.length > 0) {
2372
- console.log("\n" + import_chalk5.default.bold(" Recommendations:"));
2948
+ console.log("\n" + import_chalk7.default.bold(" Recommendations:"));
2373
2949
  geoResult.recommendations.slice(0, 5).forEach((rec, i) => {
2374
2950
  console.log(` ${i + 1}. ${rec}`);
2375
2951
  });
2376
2952
  }
2377
- console.log("\n" + import_chalk5.default.bold("\u2550".repeat(60)));
2953
+ console.log("\n" + import_chalk7.default.bold("\u2550".repeat(60)));
2378
2954
  console.log("");
2379
2955
  } catch (error) {
2380
2956
  spinner.fail(`Analysis failed: ${error instanceof Error ? error.message : "Unknown error"}`);
@@ -2382,13 +2958,13 @@ async function geo(options) {
2382
2958
  }
2383
2959
 
2384
2960
  // src/commands/keywords.ts
2385
- var readline = __toESM(require("readline"));
2386
- var import_agent_runtime6 = require("@rankcli/agent-runtime");
2961
+ var readline2 = __toESM(require("readline"));
2962
+ var import_agent_runtime7 = require("@rankcli/agent-runtime");
2387
2963
  async function keywords(options) {
2388
- console.log("\n\u{1F511} SEO Autopilot - Keyword Research\n");
2964
+ console.log("\n\u{1F511} RankCLI - Keyword Research\n");
2389
2965
  console.log("This tool helps you find keywords you can actually rank for,");
2390
2966
  console.log("based on your site's current authority and competition level.\n");
2391
- const rl = readline.createInterface({
2967
+ const rl = readline2.createInterface({
2392
2968
  input: process.stdin,
2393
2969
  output: process.stdout
2394
2970
  });
@@ -2434,7 +3010,7 @@ ${question}`);
2434
3010
  seedKeywords = options.seed.split(",").map((s) => s.trim());
2435
3011
  } else if (options.auto) {
2436
3012
  console.log("\n\u{1F50D} Auto-extracting seed keywords from your page...");
2437
- seedKeywords = await (0, import_agent_runtime6.extractSeedKeywords)(url);
3013
+ seedKeywords = await (0, import_agent_runtime7.extractSeedKeywords)(url);
2438
3014
  if (seedKeywords.length > 0) {
2439
3015
  console.log(`Found: ${seedKeywords.join(", ")}`);
2440
3016
  }
@@ -2459,7 +3035,7 @@ ${question}`);
2459
3035
  contentCapacity: "medium",
2460
3036
  targetGeo: "us"
2461
3037
  };
2462
- for (const q of import_agent_runtime6.SITE_PROFILE_QUESTIONS) {
3038
+ for (const q of import_agent_runtime7.SITE_PROFILE_QUESTIONS) {
2463
3039
  const answer = await askChoice(q.question, q.options);
2464
3040
  siteProfile[q.id] = answer;
2465
3041
  }
@@ -2478,13 +3054,13 @@ ${question}`);
2478
3054
  url,
2479
3055
  maxKeywords: 100
2480
3056
  };
2481
- const result = await (0, import_agent_runtime6.runKeywordResearch)(researchOptions);
2482
- console.log((0, import_agent_runtime6.formatKeywordReport)(result));
3057
+ const result = await (0, import_agent_runtime7.runKeywordResearch)(researchOptions);
3058
+ console.log((0, import_agent_runtime7.formatKeywordReport)(result));
2483
3059
  const allKeywords = [...result.quickWins, ...result.mediumTerm, ...result.longTerm];
2484
3060
  if (allKeywords.length > 5) {
2485
3061
  console.log("\n\u{1F4DA} Grouping keywords by topic...\n");
2486
- const topicResult = (0, import_agent_runtime6.groupKeywordsByTopic)(allKeywords);
2487
- console.log((0, import_agent_runtime6.formatTopicReport)(topicResult));
3062
+ const topicResult = (0, import_agent_runtime7.groupKeywordsByTopic)(allKeywords);
3063
+ console.log((0, import_agent_runtime7.formatTopicReport)(topicResult));
2488
3064
  }
2489
3065
  if (result.quickWins.length > 0) {
2490
3066
  console.log('\n\u{1F4A1} TIP: Run "seo apply --url ' + url + '" to apply keyword optimizations');
@@ -2509,7 +3085,7 @@ async function keywordsQuick(url, providedSeeds) {
2509
3085
  `);
2510
3086
  } else {
2511
3087
  console.log("\u{1F50D} Extracting keywords from your page...");
2512
- seedKeywords = await (0, import_agent_runtime6.extractSeedKeywords)(url);
3088
+ seedKeywords = await (0, import_agent_runtime7.extractSeedKeywords)(url);
2513
3089
  if (seedKeywords.length === 0) {
2514
3090
  console.log("\u274C Could not extract keywords. Please use interactive mode.\n");
2515
3091
  return;
@@ -2525,18 +3101,18 @@ async function keywordsQuick(url, providedSeeds) {
2525
3101
  contentCapacity: "medium",
2526
3102
  targetGeo: "us"
2527
3103
  };
2528
- const result = await (0, import_agent_runtime6.runKeywordResearch)({
3104
+ const result = await (0, import_agent_runtime7.runKeywordResearch)({
2529
3105
  seedKeywords,
2530
3106
  siteProfile,
2531
3107
  url,
2532
3108
  maxKeywords: 50
2533
3109
  });
2534
- console.log((0, import_agent_runtime6.formatKeywordReport)(result));
3110
+ console.log((0, import_agent_runtime7.formatKeywordReport)(result));
2535
3111
  const allKeywords = [...result.quickWins, ...result.mediumTerm, ...result.longTerm];
2536
3112
  if (allKeywords.length > 5) {
2537
3113
  console.log("\n\u{1F4DA} Grouping keywords by topic...\n");
2538
- const topicResult = (0, import_agent_runtime6.groupKeywordsByTopic)(allKeywords);
2539
- console.log((0, import_agent_runtime6.formatTopicReport)(topicResult));
3114
+ const topicResult = (0, import_agent_runtime7.groupKeywordsByTopic)(allKeywords);
3115
+ console.log((0, import_agent_runtime7.formatTopicReport)(topicResult));
2540
3116
  }
2541
3117
  } catch (error) {
2542
3118
  console.log(`
@@ -2599,7 +3175,7 @@ ${result2.teaser.upgradeMessage}`);
2599
3175
  return;
2600
3176
  }
2601
3177
  console.log("\u{1F52C} Running local AI analysis (this may take a minute)...\n");
2602
- const result = await (0, import_agent_runtime6.runAIKeywordResearch)({
3178
+ const result = await (0, import_agent_runtime7.runAIKeywordResearch)({
2603
3179
  url,
2604
3180
  tier: "solo",
2605
3181
  openaiApiKey,
@@ -2718,12 +3294,12 @@ async function keywordsCompetitor(yourUrl, seedKeywords, competitors) {
2718
3294
  }
2719
3295
  console.log("");
2720
3296
  console.log("Analyzing competitor keywords (this may take a moment)...\n");
2721
- const result = await (0, import_agent_runtime6.discoverCompetitorKeywords)(
3297
+ const result = await (0, import_agent_runtime7.discoverCompetitorKeywords)(
2722
3298
  yourDomain,
2723
3299
  seedKeywords,
2724
3300
  competitors
2725
3301
  );
2726
- console.log((0, import_agent_runtime6.formatCompetitorReport)(result));
3302
+ console.log((0, import_agent_runtime7.formatCompetitorReport)(result));
2727
3303
  if (result.missingKeywords.length > 0) {
2728
3304
  console.log("\n\u{1F3AF} ACTION ITEMS:");
2729
3305
  console.log("\u2500".repeat(60));
@@ -2742,9 +3318,9 @@ async function keywordsCompetitor(yourUrl, seedKeywords, competitors) {
2742
3318
  }
2743
3319
 
2744
3320
  // src/commands/content.ts
2745
- var import_agent_runtime7 = require("@rankcli/agent-runtime");
3321
+ var import_agent_runtime8 = require("@rankcli/agent-runtime");
2746
3322
  async function analyzeContent(options) {
2747
- console.log("\n\u{1F4DD} SEO Autopilot - Content Analysis\n");
3323
+ console.log("\n\u{1F4DD} RankCLI - Content Analysis\n");
2748
3324
  const mode = options.mode || "full";
2749
3325
  if (mode === "headline" || options.headline) {
2750
3326
  const headline = options.headline || "";
@@ -2752,12 +3328,12 @@ async function analyzeContent(options) {
2752
3328
  console.log('\u274C Please provide a headline with --headline "Your Headline Here"');
2753
3329
  return;
2754
3330
  }
2755
- const analysis = (0, import_agent_runtime7.analyzeHeadline)(headline);
2756
- console.log((0, import_agent_runtime7.formatHeadlineReport)(analysis));
3331
+ const analysis = (0, import_agent_runtime8.analyzeHeadline)(headline);
3332
+ console.log((0, import_agent_runtime8.formatHeadlineReport)(analysis));
2757
3333
  if (options.keyword) {
2758
3334
  console.log("\n\u{1F4CB} HEADLINE VARIATIONS");
2759
3335
  console.log("\u2500".repeat(60));
2760
- const variations = (0, import_agent_runtime7.generateHeadlineVariations)(options.keyword, [options.keyword]);
3336
+ const variations = (0, import_agent_runtime8.generateHeadlineVariations)(options.keyword, [options.keyword]);
2761
3337
  for (const v of variations.slice(0, 5)) {
2762
3338
  console.log(` \u2022 ${v}`);
2763
3339
  }
@@ -2768,29 +3344,36 @@ async function analyzeContent(options) {
2768
3344
  if (options.url) {
2769
3345
  console.log(`Fetching content from ${options.url}...`);
2770
3346
  try {
2771
- const result = await (0, import_agent_runtime7.crawlUrl)(options.url);
2772
- if (!result.html) {
3347
+ const crawlResult = await (0, import_agent_runtime8.crawlUrl)({ url: options.url });
3348
+ if (!crawlResult.success || !crawlResult.data) {
2773
3349
  console.log("\u274C Could not fetch content from URL");
2774
3350
  return;
2775
3351
  }
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);
3352
+ const { html } = crawlResult.data;
3353
+ const [metaResult, headingsResult] = await Promise.all([
3354
+ (0, import_agent_runtime8.extractMeta)({ html, url: options.url }),
3355
+ (0, import_agent_runtime8.analyzeHeadings)({ html })
3356
+ ]);
3357
+ const meta = metaResult.success ? metaResult.data : void 0;
3358
+ const headingStructure = headingsResult.success ? headingsResult.data : [];
3359
+ const title = meta?.title || "";
3360
+ const h1 = headingStructure.find((h) => h.level === 1)?.text || "";
3361
+ const headings = headingStructure.map((h) => h.text);
3362
+ const bodyText = extractBodyText(html);
2780
3363
  const paragraphs = bodyText.split(/\n\n+/);
2781
3364
  console.log(`
2782
3365
  Analyzing: "${title || options.url}"
2783
3366
  `);
2784
3367
  if (mode === "full" || mode === "readability") {
2785
- const readability = (0, import_agent_runtime7.analyzeReadability)(bodyText);
2786
- console.log((0, import_agent_runtime7.formatReadabilityReport)(readability));
3368
+ const readability = (0, import_agent_runtime8.analyzeReadability)(bodyText);
3369
+ console.log((0, import_agent_runtime8.formatReadabilityReport)(readability));
2787
3370
  }
2788
3371
  if (mode === "full" && title) {
2789
- const headlineAnalysis = (0, import_agent_runtime7.analyzeHeadline)(title);
2790
- console.log((0, import_agent_runtime7.formatHeadlineReport)(headlineAnalysis));
3372
+ const headlineAnalysis = (0, import_agent_runtime8.analyzeHeadline)(title);
3373
+ console.log((0, import_agent_runtime8.formatHeadlineReport)(headlineAnalysis));
2791
3374
  }
2792
3375
  if (options.keyword && (mode === "full" || mode === "density")) {
2793
- const densityAnalysis = (0, import_agent_runtime7.analyzeKeywordDensity)(options.keyword, {
3376
+ const densityAnalysis = (0, import_agent_runtime8.analyzeKeywordDensity)(options.keyword, {
2794
3377
  title,
2795
3378
  h1,
2796
3379
  headings,
@@ -2798,229 +3381,59 @@ Analyzing: "${title || options.url}"
2798
3381
  firstParagraph: paragraphs[0],
2799
3382
  lastParagraph: paragraphs[paragraphs.length - 1]
2800
3383
  });
2801
- console.log((0, import_agent_runtime7.formatKeywordDensityReport)(densityAnalysis));
3384
+ console.log((0, import_agent_runtime8.formatKeywordDensityReport)(densityAnalysis));
2802
3385
  }
2803
3386
  if (options.keyword && (mode === "full" || mode === "snippet")) {
2804
- const snippetAnalysis = (0, import_agent_runtime7.analyzeFeaturedSnippetPotential)(
2805
- options.keyword,
2806
- bodyText,
2807
- headings
2808
- );
2809
- console.log((0, import_agent_runtime7.formatFeaturedSnippetReport)(snippetAnalysis));
2810
- }
2811
- if (options.keyword) {
2812
- const intentAnalysis = (0, import_agent_runtime7.classifyIntent)(options.keyword);
2813
- console.log((0, import_agent_runtime7.formatIntentReport)(options.keyword, intentAnalysis));
2814
- }
2815
- console.log("\n\u{1F4CA} CONTENT SUMMARY");
2816
- console.log("\u2500".repeat(60));
2817
- console.log(` Title: ${title || "(none)"}`);
2818
- console.log(` H1: ${h1 || "(none)"}`);
2819
- console.log(` Word Count: ${bodyText.split(/\s+/).length}`);
2820
- console.log(` Headings: ${headings.length}`);
2821
- console.log("");
2822
- } catch (error) {
2823
- console.log(`
2824
- \u274C Error: ${error instanceof Error ? error.message : "Unknown error"}
2825
- `);
2826
- }
2827
- } else {
2828
- console.log("Usage:");
2829
- console.log(" seo content -u <url> Full content analysis");
2830
- console.log(' seo content -u <url> -k "keyword" Analyze with target keyword');
2831
- console.log(' seo content --headline "Your Title" Analyze headline only');
2832
- console.log(" seo content -u <url> --mode readability Readability only");
2833
- console.log(' seo content -u <url> -k "kw" --mode density Keyword density only');
2834
- console.log(' seo content -u <url> -k "kw" --mode snippet Featured snippet analysis');
2835
- console.log("");
2836
- }
2837
- }
2838
- async function analyzeIntent(keyword) {
2839
- console.log("\n\u{1F50D} Search Intent Analysis\n");
2840
- const analysis = (0, import_agent_runtime7.classifyIntent)(keyword);
2841
- console.log((0, import_agent_runtime7.formatIntentReport)(keyword, analysis));
2842
- }
2843
- function extractBodyText(html) {
2844
- let text = html.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "");
2845
- text = text.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
2846
- text = text.replace(/<[^>]+>/g, " ");
2847
- text = text.replace(/&nbsp;/g, " ");
2848
- text = text.replace(/&amp;/g, "&");
2849
- text = text.replace(/&lt;/g, "<");
2850
- text = text.replace(/&gt;/g, ">");
2851
- text = text.replace(/&quot;/g, '"');
2852
- text = text.replace(/\s+/g, " ").trim();
2853
- return text;
2854
- }
2855
-
2856
- // src/commands/setup.ts
2857
- var readline2 = __toESM(require("readline"));
2858
- var fs2 = __toESM(require("fs"));
2859
- var path2 = __toESM(require("path"));
2860
- var import_agent_runtime8 = require("@rankcli/agent-runtime");
2861
- async function setup(options) {
2862
- console.log("\n\u{1F527} SEO Autopilot - Setup\n");
2863
- const projectPath = process.cwd();
2864
- if (!fs2.existsSync(path2.join(projectPath, "package.json")) && !fs2.existsSync(path2.join(projectPath, "index.html"))) {
2865
- console.log("\u26A0\uFE0F No package.json or index.html found. Are you in a web project directory?\n");
2866
- }
2867
- if (options.all || !options.ga4 && !options.gsc && !options.githubAction) {
2868
- await interactiveSetup(projectPath);
2869
- return;
2870
- }
2871
- if (options.ga4) {
2872
- await setupGA4(projectPath, options.ga4);
2873
- }
2874
- if (options.gsc) {
2875
- await setupGSC(projectPath, options.gsc);
2876
- }
2877
- if (options.githubAction) {
2878
- await setupGitHubAction(projectPath, options.schedule || "weekly");
2879
- }
2880
- }
2881
- async function interactiveSetup(projectPath) {
2882
- const rl = readline2.createInterface({
2883
- input: process.stdin,
2884
- output: process.stdout
2885
- });
2886
- const ask2 = (question) => {
2887
- return new Promise((resolve) => {
2888
- rl.question(question, (answer) => {
2889
- resolve(answer.trim());
2890
- });
2891
- });
2892
- };
2893
- try {
2894
- console.log("This wizard will help you set up SEO tracking for your project.\n");
2895
- console.log("\u2500".repeat(60));
2896
- console.log("\u{1F4CA} STEP 1: Google Analytics 4\n");
2897
- console.log("GA4 tracks visitor behavior on your site.");
2898
- console.log("Get your Measurement ID from: https://analytics.google.com");
2899
- console.log("Go to: Admin > Data Streams > Your Stream > Measurement ID\n");
2900
- const ga4Id = await ask2("Enter GA4 Measurement ID (G-XXXXXXXXXX) or press Enter to skip: ");
2901
- if (ga4Id && ga4Id.startsWith("G-")) {
2902
- await setupGA4(projectPath, ga4Id);
2903
- } else if (ga4Id) {
2904
- console.log('\u26A0\uFE0F Invalid format. GA4 IDs start with "G-". Skipping.\n');
2905
- } else {
2906
- console.log("Skipping GA4 setup.\n");
2907
- }
2908
- console.log("\u2500".repeat(60));
2909
- console.log("\u{1F50D} STEP 2: Google Search Console\n");
2910
- console.log("GSC shows how Google sees your site and what queries you rank for.");
2911
- console.log("Get your verification code from: https://search.google.com/search-console");
2912
- console.log("Go to: Settings > Ownership verification > HTML tag\n");
2913
- const gscCode = await ask2("Enter GSC verification code or press Enter to skip: ");
2914
- if (gscCode && gscCode.length > 10) {
2915
- await setupGSC(projectPath, gscCode);
2916
- } else if (gscCode) {
2917
- console.log("\u26A0\uFE0F Code seems too short. Skipping.\n");
2918
- } else {
2919
- console.log("Skipping GSC verification.\n");
2920
- }
2921
- console.log("\u2500".repeat(60));
2922
- console.log("\u{1F916} STEP 3: Automated SEO Monitoring\n");
2923
- console.log("A GitHub Action can automatically check your site's SEO on a schedule.");
2924
- console.log("It creates issues with reports and can auto-fix some problems.\n");
2925
- const setupAction = await ask2("Set up GitHub Action? (y/n): ");
2926
- if (setupAction.toLowerCase() === "y" || setupAction.toLowerCase() === "yes") {
2927
- console.log("\nHow often should the check run?");
2928
- console.log(" 1. Weekly (recommended for most sites)");
2929
- console.log(" 2. Daily (for high-traffic sites)");
2930
- console.log(" 3. Monthly (for low-traffic sites)\n");
2931
- const scheduleChoice = await ask2("Enter choice (1-3): ");
2932
- const scheduleMap = {
2933
- "1": "weekly",
2934
- "2": "daily",
2935
- "3": "monthly"
2936
- };
2937
- const schedule = scheduleMap[scheduleChoice] || "weekly";
2938
- const siteUrl = await ask2("Enter your site URL (https://example.com): ");
2939
- if (siteUrl) {
2940
- await setupGitHubAction(projectPath, schedule, siteUrl);
2941
- }
2942
- } else {
2943
- console.log("Skipping GitHub Action setup.\n");
2944
- }
2945
- console.log("\u2500".repeat(60));
2946
- console.log("\u2705 Setup complete!\n");
2947
- console.log("Next steps:");
2948
- console.log(" 1. Commit the changes to your repository");
2949
- console.log(" 2. If using GitHub Actions, add required secrets (see .github/SEO_SETUP.md)");
2950
- console.log(" 3. Deploy your site to apply tracking code changes");
2951
- console.log(" 4. Verify GSC ownership and GA4 data collection\n");
2952
- rl.close();
2953
- } catch (error) {
2954
- console.log(`
3387
+ const snippetAnalysis = (0, import_agent_runtime8.analyzeFeaturedSnippetPotential)(
3388
+ options.keyword,
3389
+ bodyText,
3390
+ headings
3391
+ );
3392
+ console.log((0, import_agent_runtime8.formatFeaturedSnippetReport)(snippetAnalysis));
3393
+ }
3394
+ if (options.keyword) {
3395
+ const intentAnalysis = (0, import_agent_runtime8.classifyIntent)(options.keyword);
3396
+ console.log((0, import_agent_runtime8.formatIntentReport)(options.keyword, intentAnalysis));
3397
+ }
3398
+ console.log("\n\u{1F4CA} CONTENT SUMMARY");
3399
+ console.log("\u2500".repeat(60));
3400
+ console.log(` Title: ${title || "(none)"}`);
3401
+ console.log(` H1: ${h1 || "(none)"}`);
3402
+ console.log(` Word Count: ${bodyText.split(/\s+/).length}`);
3403
+ console.log(` Headings: ${headings.length}`);
3404
+ console.log("");
3405
+ } catch (error) {
3406
+ console.log(`
2955
3407
  \u274C Error: ${error instanceof Error ? error.message : "Unknown error"}
2956
3408
  `);
2957
- rl.close();
2958
- }
2959
- }
2960
- async function setupGA4(projectPath, measurementId) {
2961
- console.log(`Setting up Google Analytics 4 (${measurementId})...`);
2962
- const result = await (0, import_agent_runtime8.injectGA4)(projectPath, { measurementId });
2963
- if (result.success) {
2964
- console.log(`\u2705 ${result.message}`);
2965
- if (result.file) {
2966
- console.log(` Modified: ${result.file}`);
2967
- }
2968
- } else {
2969
- console.log(`\u26A0\uFE0F ${result.message}`);
2970
- if (result.code) {
2971
- console.log("\nManually add this code to your HTML <head>:\n");
2972
- console.log(result.code);
2973
- }
2974
- }
2975
- const envExamplePath = path2.join(projectPath, ".env.example");
2976
- const envContent = (0, import_agent_runtime8.generateGA4EnvTemplate)();
2977
- if (fs2.existsSync(envExamplePath)) {
2978
- const existing = fs2.readFileSync(envExamplePath, "utf-8");
2979
- if (!existing.includes("GA4_MEASUREMENT_ID")) {
2980
- fs2.appendFileSync(envExamplePath, "\n" + envContent);
2981
- console.log(" Updated: .env.example");
2982
- }
2983
- } else {
2984
- fs2.writeFileSync(envExamplePath, envContent);
2985
- console.log(" Created: .env.example");
2986
- }
2987
- console.log("");
2988
- }
2989
- async function setupGSC(projectPath, verificationCode) {
2990
- console.log("Setting up Google Search Console verification...");
2991
- const result = await (0, import_agent_runtime8.injectGSCVerification)(projectPath, verificationCode);
2992
- if (result.success) {
2993
- console.log(`\u2705 ${result.message}`);
2994
- if (result.file) {
2995
- console.log(` Modified: ${result.file}`);
2996
3409
  }
2997
3410
  } else {
2998
- console.log(`\u26A0\uFE0F ${result.message}`);
3411
+ console.log("Usage:");
3412
+ console.log(" seo content -u <url> Full content analysis");
3413
+ console.log(' seo content -u <url> -k "keyword" Analyze with target keyword');
3414
+ console.log(' seo content --headline "Your Title" Analyze headline only');
3415
+ console.log(" seo content -u <url> --mode readability Readability only");
3416
+ console.log(' seo content -u <url> -k "kw" --mode density Keyword density only');
3417
+ console.log(' seo content -u <url> -k "kw" --mode snippet Featured snippet analysis');
3418
+ console.log("");
2999
3419
  }
3000
- console.log("");
3001
3420
  }
3002
- async function setupGitHubAction(projectPath, schedule, siteUrl) {
3003
- console.log(`Setting up GitHub Action (${schedule} schedule)...`);
3004
- const config2 = {
3005
- schedule,
3006
- siteUrl: siteUrl || "https://example.com",
3007
- features: {
3008
- audit: true,
3009
- tracking: true,
3010
- autoFix: true,
3011
- createIssues: true,
3012
- createPRs: true
3013
- }
3014
- };
3015
- const { files, instructions } = (0, import_agent_runtime8.writeGitHubActionFiles)(projectPath, config2);
3016
- console.log("\u2705 GitHub Action files created:");
3017
- for (const file of files) {
3018
- console.log(` - ${path2.relative(projectPath, file)}`);
3019
- }
3020
- console.log(instructions);
3421
+ async function analyzeIntent(keyword) {
3422
+ console.log("\n\u{1F50D} Search Intent Analysis\n");
3423
+ const analysis = (0, import_agent_runtime8.classifyIntent)(keyword);
3424
+ console.log((0, import_agent_runtime8.formatIntentReport)(keyword, analysis));
3021
3425
  }
3022
- function showGSCInstructions() {
3023
- console.log((0, import_agent_runtime8.getGSCSetupInstructions)());
3426
+ function extractBodyText(html) {
3427
+ let text = html.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "");
3428
+ text = text.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
3429
+ text = text.replace(/<[^>]+>/g, " ");
3430
+ text = text.replace(/&nbsp;/g, " ");
3431
+ text = text.replace(/&amp;/g, "&");
3432
+ text = text.replace(/&lt;/g, "<");
3433
+ text = text.replace(/&gt;/g, ">");
3434
+ text = text.replace(/&quot;/g, '"');
3435
+ text = text.replace(/\s+/g, " ").trim();
3436
+ return text;
3024
3437
  }
3025
3438
 
3026
3439
  // src/commands/alerts.ts
@@ -3045,7 +3458,7 @@ async function askYesNo(rl, question, defaultYes = true) {
3045
3458
  return answer.toLowerCase().startsWith("y");
3046
3459
  }
3047
3460
  async function alerts(options) {
3048
- console.log("\n\u{1F514} SEO Autopilot - Alert Configuration\n");
3461
+ console.log("\n\u{1F514} RankCLI - Alert Configuration\n");
3049
3462
  const rl = readline3.createInterface({
3050
3463
  input: process.stdin,
3051
3464
  output: process.stdout
@@ -3188,235 +3601,18 @@ async function testWebhook(channel, webhookUrl) {
3188
3601
  }
3189
3602
  }
3190
3603
 
3191
- // src/commands/auth.ts
3192
- var import_chalk6 = __toESM(require("chalk"));
3193
- var import_ora5 = __toESM(require("ora"));
3194
- var import_inquirer = __toESM(require("inquirer"));
3195
- var import_open = __toESM(require("open"));
3196
- async function login(options) {
3197
- if (isLoggedIn()) {
3198
- const { email } = getUserInfo();
3199
- console.log(import_chalk6.default.yellow(`Already logged in as ${email}`));
3200
- console.log(import_chalk6.default.dim('Run "rankcli logout" to sign out first.'));
3201
- return;
3202
- }
3203
- const spinner = (0, import_ora5.default)();
3204
- try {
3205
- if (options.token) {
3206
- const apiKey = options.token;
3207
- if (!apiKey.startsWith("rankcli_")) {
3208
- console.log(import_chalk6.default.red('Invalid API key format. Keys should start with "rankcli_"'));
3209
- return;
3210
- }
3211
- spinner.start("Validating API key...");
3212
- const result = await validateApiKey(apiKey);
3213
- if (!result.valid) {
3214
- spinner.fail("Invalid API key");
3215
- console.log(import_chalk6.default.red(`
3216
- ${result.error}`));
3217
- return;
3218
- }
3219
- saveApiKey(apiKey);
3220
- if (result.subscription) {
3221
- saveSubscription({
3222
- planId: result.subscription.planId,
3223
- planName: result.subscription.planName,
3224
- sitesLimit: result.subscription.sitesLimit
3225
- });
3226
- }
3227
- spinner.succeed(`Authenticated via API key`);
3228
- console.log(import_chalk6.default.dim(`
3229
- Email: ${result.user?.email}`));
3230
- console.log(import_chalk6.default.dim(`Plan: ${result.subscription?.planName || "Free"}`));
3231
- return;
3232
- }
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)}`);
3266
- } else {
3267
- let email = options.email;
3268
- if (!email) {
3269
- const answers = await import_inquirer.default.prompt([
3270
- {
3271
- type: "input",
3272
- name: "email",
3273
- message: "Email:",
3274
- validate: (input) => {
3275
- if (!input.includes("@")) return "Please enter a valid email";
3276
- return true;
3277
- }
3278
- }
3279
- ]);
3280
- email = answers.email;
3281
- }
3282
- const { password } = await import_inquirer.default.prompt([
3283
- {
3284
- type: "password",
3285
- name: "password",
3286
- message: "Password:",
3287
- mask: "*"
3288
- }
3289
- ]);
3290
- spinner.start("Signing in...");
3291
- const supabase = getSupabaseClient();
3292
- const { data, error } = await supabase.auth.signInWithPassword({
3293
- email,
3294
- password
3295
- });
3296
- if (error) {
3297
- spinner.fail("Login failed");
3298
- console.log(import_chalk6.default.red(`
3299
- ${error.message}`));
3300
- if (error.message.includes("Invalid login")) {
3301
- console.log(import_chalk6.default.dim("\nDon't have an account? Sign up at:"));
3302
- console.log(import_chalk6.default.cyan(` ${WEB_APP_URL}/signup`));
3303
- }
3304
- return;
3305
- }
3306
- if (!data.session) {
3307
- spinner.fail("No session returned");
3308
- return;
3309
- }
3310
- saveSession(data.session);
3311
- await fetchAndSaveSubscription();
3312
- spinner.succeed(`Logged in as ${import_chalk6.default.green(data.user?.email)}`);
3313
- const { planName, sitesLimit } = getUserInfo();
3314
- console.log(import_chalk6.default.dim(`
3315
- Plan: ${planName} (${sitesLimit} site${sitesLimit !== 1 ? "s" : ""})`));
3316
- }
3317
- } catch (err) {
3318
- spinner.fail("Login failed");
3319
- console.error(import_chalk6.default.red(err instanceof Error ? err.message : "Unknown error"));
3320
- }
3321
- }
3322
- async function logout() {
3323
- if (!isLoggedIn()) {
3324
- console.log(import_chalk6.default.yellow("Not logged in"));
3325
- return;
3326
- }
3327
- const { email } = getUserInfo();
3328
- const spinner = (0, import_ora5.default)("Signing out...").start();
3329
- try {
3330
- const supabase = getSupabaseClient();
3331
- await supabase.auth.signOut();
3332
- clearCredentials();
3333
- spinner.succeed(`Logged out from ${import_chalk6.default.dim(email)}`);
3334
- } catch (err) {
3335
- spinner.fail("Logout failed");
3336
- console.error(import_chalk6.default.red(err instanceof Error ? err.message : "Unknown error"));
3337
- }
3338
- }
3339
- async function whoami() {
3340
- if (!isLoggedIn()) {
3341
- console.log(import_chalk6.default.yellow("Not logged in"));
3342
- console.log(import_chalk6.default.dim('\nRun "rankcli login" to sign in.'));
3343
- console.log(import_chalk6.default.dim(`Or sign up at: ${WEB_APP_URL}/signup`));
3344
- return;
3345
- }
3346
- const spinner = (0, import_ora5.default)("Checking account...").start();
3347
- try {
3348
- if (isApiKeyAuth()) {
3349
- const apiKey = getApiKey();
3350
- if (apiKey) {
3351
- const result = await validateApiKey(apiKey);
3352
- if (result.valid && result.subscription) {
3353
- saveSubscription({
3354
- planId: result.subscription.planId,
3355
- planName: result.subscription.planName,
3356
- sitesLimit: result.subscription.sitesLimit
3357
- });
3358
- }
3359
- }
3360
- } else {
3361
- await fetchAndSaveSubscription();
3362
- }
3363
- const { email, planName, sitesLimit } = getUserInfo();
3364
- const authMethod = isApiKeyAuth() ? "API Key" : "Session";
3365
- spinner.stop();
3366
- console.log(import_chalk6.default.bold("\nRankCLI Account"));
3367
- console.log("\u2500".repeat(40));
3368
- console.log(` Email: ${import_chalk6.default.cyan(email || "N/A")}`);
3369
- console.log(` Plan: ${import_chalk6.default.green(planName)}`);
3370
- console.log(` Sites: ${sitesLimit} allowed`);
3371
- console.log(` Auth: ${import_chalk6.default.dim(authMethod)}`);
3372
- console.log("\u2500".repeat(40));
3373
- console.log(import_chalk6.default.dim(`
3374
- Manage at: ${WEB_APP_URL}/account`));
3375
- } catch (err) {
3376
- spinner.fail("Failed to fetch account info");
3377
- console.error(import_chalk6.default.red(err instanceof Error ? err.message : "Unknown error"));
3378
- }
3379
- }
3380
- async function fetchAndSaveSubscription() {
3381
- const supabase = await getAuthenticatedClient();
3382
- if (!supabase) return;
3383
- const { userId } = getUserInfo();
3384
- if (!userId) return;
3385
- const { data: subscription } = await supabase.from("user_subscriptions").select(`
3386
- plan:subscription_plans (
3387
- plan_id,
3388
- name,
3389
- sites_limit
3390
- )
3391
- `).eq("user_id", userId).single();
3392
- if (subscription?.plan) {
3393
- const plan = subscription.plan;
3394
- saveSubscription({
3395
- planId: plan.plan_id,
3396
- planName: plan.name,
3397
- sitesLimit: plan.sites_limit
3398
- });
3399
- } else {
3400
- saveSubscription({
3401
- planId: void 0,
3402
- planName: "Free",
3403
- sitesLimit: 1
3404
- });
3405
- }
3406
- }
3407
-
3408
3604
  // src/version.ts
3409
- var VERSION = "0.0.23";
3605
+ var VERSION = "0.0.25";
3410
3606
 
3411
3607
  // src/index.ts
3412
3608
  var program = new import_commander.Command();
3413
3609
  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);
3610
+ 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
3611
  program.command("logout").description("Log out of your RankCLI account").action(logout);
3416
3612
  program.command("whoami").description("Show current logged in user").action(whoami);
3417
- program.command("init").description("Initialize SEO Autopilot in your project").option("-y, --yes", "Skip prompts and use defaults").action(init);
3613
+ program.command("init").description("Set up RankCLI in your project (detects framework, git/GitHub, AI keys)").option("-y, --yes", "Skip prompts and use defaults").action(init);
3418
3614
  program.command("analyze").description("AI analyzes your codebase for SEO opportunities").option("-v, --verbose", "Show detailed output").action(analyze);
3419
- program.command("audit").description("Run a comprehensive SEO audit (200+ 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);
3615
+ program.command("audit").description("Run a comprehensive SEO audit (200+ 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, anthropic, openrouter, groq, or gemini (auto-detected from env if omitted)").option("--ai-key <key>", "API key (or set OPENAI_API_KEY, ANTHROPIC_API_KEY, OPENROUTER_API_KEY, GROQ_API_KEY, or GEMINI_API_KEY)").action(audit);
3420
3616
  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);
3421
3617
  program.command("fix").description("Agentic SEO fixer - detect, fix, commit, and push").option("-u, --url <url>", "URL to analyze").option("--auto", "Auto-apply fixes without confirmation").option("--commit", "Commit changes with conventional commit message").option("--push", "Push changes after committing (requires --commit)").option("--dry-run", "Preview changes without applying").option("--only <codes>", "Fix only specific issue codes (comma-separated)", (val) => val.split(",")).option("-o, --output <format>", "Output format (text, json)", "text").action(async (options) => {
3422
3618
  const result = await fix({