@rankcli/cli 0.0.25 → 0.0.27

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 +1159 -1001
  2. package/dist/index.mjs +1118 -960
  3. package/package.json +2 -2
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"));
@@ -486,15 +239,807 @@ async function getAuthenticatedClient() {
486
239
  return supabase;
487
240
  }
488
241
 
489
- // src/lib/upsell.ts
490
- var import_chalk = __toESM(require("chalk"));
491
- var WEB_APP_URL2 = "https://rankcli.dev";
492
- var TIER_LIMITS = {
493
- free_anon: {
494
- checks: 50,
495
- sites: 0,
496
- domains_per_month: 1,
497
- features: { geo: false, auto_fix: false, github: false, ai: false }
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,
1042
+ features: { geo: false, auto_fix: false, github: false, ai: false }
498
1043
  },
499
1044
  free: {
500
1045
  checks: 100,
@@ -526,133 +1071,133 @@ var UPSELL_MESSAGES = {
526
1071
  // After free audit completes
527
1072
  free_audit_complete: () => [
528
1073
  "",
529
- 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"),
530
- import_chalk.default.yellow.bold(" \u{1F680} Get 2x more checks + audit history"),
531
- 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."),
532
1077
  "",
533
- import_chalk.default.cyan(` \u2192 rankcli login`),
534
- 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"),
535
1080
  ""
536
1081
  ],
537
1082
  // When checks limit is reached
538
1083
  checks_limit: (ctx) => [
539
1084
  "",
540
- import_chalk.default.yellow(`\u26A0\uFE0F Running ${ctx.current || 50} of ${TIER_LIMITS.solo.checks} available checks`),
541
- import_chalk.default.dim(` Unlock all ${TIER_LIMITS.solo.checks}+ checks with Solo ($9/mo)`),
542
- 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`),
543
1088
  ""
544
1089
  ],
545
1090
  // When domain limit is reached
546
1091
  domain_limit: (ctx) => [
547
1092
  "",
548
- 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"),
549
- import_chalk.default.red.bold(" \u2717 Domain limit reached"),
550
- 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.`),
551
1096
  "",
552
- import_chalk.default.dim(" Upgrade to audit more sites:"),
553
- import_chalk.default.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=cli_domain_limit`),
554
- 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"),
555
1100
  ""
556
1101
  ],
557
1102
  // When GEO features are limited
558
1103
  geo_limited: (ctx) => [
559
1104
  "",
560
- 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"),
561
- import_chalk.default.yellow.bold(" \u{1F916} Full GEO analysis requires Solo"),
562
- import_chalk.default.dim(" You're seeing basic AI crawler access checks."),
563
- 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"),
564
1109
  "",
565
- import_chalk.default.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=cli_geo`),
566
- 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"),
567
1112
  ""
568
1113
  ],
569
1114
  // When auto-fix is attempted without access
570
1115
  auto_fix_blocked: () => [
571
1116
  "",
572
- 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"),
573
- import_chalk.default.red.bold(" \u2717 Auto-fix requires Solo plan"),
574
- 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."),
575
1120
  "",
576
- import_chalk.default.dim(" Solo ($9/mo) includes:"),
577
- import_chalk.default.dim(" \u2022 Local auto-fix via CLI"),
578
- import_chalk.default.dim(" \u2022 25+ framework support"),
579
- 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"),
580
1125
  "",
581
- import_chalk.default.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=cli_autofix`),
582
- 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"),
583
1128
  ""
584
1129
  ],
585
1130
  // When GitHub integration is attempted without access
586
1131
  github_blocked: () => [
587
1132
  "",
588
- 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"),
589
- import_chalk.default.red.bold(" \u2717 GitHub auto-fix PRs require Pro"),
590
- 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."),
591
1136
  "",
592
- import_chalk.default.dim(" Pro ($29/mo) includes:"),
593
- import_chalk.default.dim(" \u2022 GitHub App integration"),
594
- import_chalk.default.dim(" \u2022 Auto-fix PRs"),
595
- import_chalk.default.dim(" \u2022 Daily scheduled audits"),
596
- 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"),
597
1142
  "",
598
- import_chalk.default.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=cli_github`),
599
- 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"),
600
1145
  ""
601
1146
  ],
602
1147
  // Keyword tracking limit reached
603
1148
  keyword_limit: (ctx) => [
604
1149
  "",
605
- 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"),
606
- import_chalk.default.red.bold(" \u2717 Keyword tracking limit reached"),
607
- import_chalk.default.dim(` Your ${ctx.tier} plan allows ${ctx.limit || ctx.max} tracked keywords.`),
608
- 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}`),
609
1154
  "",
610
- import_chalk.default.dim(" Upgrade for more keywords:"),
611
- import_chalk.default.dim(" \u2022 Solo ($9/mo): 100 keywords"),
612
- import_chalk.default.dim(" \u2022 Pro ($29/mo): 500 keywords"),
613
- 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"),
614
1159
  "",
615
- import_chalk.default.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=cli_keyword_limit`),
616
- 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"),
617
1162
  ""
618
1163
  ],
619
1164
  // Rate limit exceeded
620
1165
  rate_limit: (ctx) => [
621
1166
  "",
622
- 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"),
623
- import_chalk.default.red.bold(" \u2717 API rate limit exceeded"),
624
- 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.`),
625
1170
  "",
626
- import_chalk.default.dim(" Need more? Upgrade for higher limits:"),
627
- import_chalk.default.dim(" \u2022 Solo: 100 req/day"),
628
- import_chalk.default.dim(" \u2022 Pro: 1,000 req/day"),
629
- 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"),
630
1175
  "",
631
- import_chalk.default.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=cli_rate_limit`),
632
- 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"),
633
1178
  ""
634
1179
  ],
635
1180
  // Schedule exceeded (for GitHub Action)
636
1181
  schedule_exceeded: (ctx) => [
637
1182
  "",
638
- 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"),
639
- import_chalk.default.yellow.bold(" \u23F0 Scheduled audit skipped"),
640
- 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.`),
641
1186
  "",
642
- import_chalk.default.dim(" Upgrade for more frequent audits:"),
643
- import_chalk.default.dim(" \u2022 Solo: Weekly"),
644
- import_chalk.default.dim(" \u2022 Pro: Daily"),
645
- 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"),
646
1191
  "",
647
- import_chalk.default.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=schedule_exceeded`),
648
- 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"),
649
1194
  ""
650
1195
  ],
651
1196
  // Login prompt (soft)
652
1197
  login_prompt: () => [
653
1198
  "",
654
- import_chalk.default.dim(" \u{1F4A1} Tip: Log in to sync results and track progress over time:"),
655
- 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"),
656
1201
  ""
657
1202
  ]
658
1203
  };
@@ -673,8 +1218,8 @@ function hasFeature(tier, feature) {
673
1218
 
674
1219
  // src/wasm-engine.ts
675
1220
  var vm = __toESM(require("vm"));
676
- var path = __toESM(require("path"));
677
- var fs = __toESM(require("fs"));
1221
+ var path2 = __toESM(require("path"));
1222
+ var fs2 = __toESM(require("fs"));
678
1223
  var os = __toESM(require("os"));
679
1224
  var PREMIUM_CHECK_NAMES = [
680
1225
  "analyzeFeaturedSnippet",
@@ -711,12 +1256,12 @@ async function loadWasm() {
711
1256
  return wasmModule;
712
1257
  }
713
1258
  const possiblePaths = [
714
- path.join(__dirname, "../native/pkg/rankcli_audit_engine.js"),
715
- path.join(__dirname, "../wasm/rankcli_audit_engine.js"),
716
- 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")
717
1262
  ];
718
1263
  for (const wasmPath of possiblePaths) {
719
- if (fs.existsSync(wasmPath)) {
1264
+ if (fs2.existsSync(wasmPath)) {
720
1265
  wasmModule = require(wasmPath);
721
1266
  return wasmModule;
722
1267
  }
@@ -728,8 +1273,8 @@ Searched: ${possiblePaths.join(", ")}`
728
1273
  }
729
1274
  function extractSeaWasmAssets() {
730
1275
  const sea = require("sea");
731
- const dir = path.join(os.tmpdir(), "rankcli-wasm-sea");
732
- fs.mkdirSync(dir, { recursive: true });
1276
+ const dir = path2.join(os.tmpdir(), "rankcli-wasm-sea");
1277
+ fs2.mkdirSync(dir, { recursive: true });
733
1278
  const glueAsset = sea.getRawAsset("rankcli_audit_engine.js");
734
1279
  const wasmAsset = sea.getRawAsset("rankcli_audit_engine_bg.wasm");
735
1280
  if (typeof glueAsset === "string" || typeof wasmAsset === "string") {
@@ -737,12 +1282,12 @@ function extractSeaWasmAssets() {
737
1282
  }
738
1283
  const glueBuffer = Buffer.from(glueAsset);
739
1284
  const wasmBuffer = Buffer.from(wasmAsset);
740
- const gluePath = path.join(dir, "rankcli_audit_engine.js");
741
- const wasmPath = path.join(dir, "rankcli_audit_engine_bg.wasm");
742
- 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;
743
1288
  if (existingSize !== wasmBuffer.length) {
744
- fs.writeFileSync(gluePath, glueBuffer);
745
- fs.writeFileSync(wasmPath, wasmBuffer);
1289
+ fs2.writeFileSync(gluePath, glueBuffer);
1290
+ fs2.writeFileSync(wasmPath, wasmBuffer);
746
1291
  }
747
1292
  return gluePath;
748
1293
  }
@@ -837,7 +1382,7 @@ async function getPremiumChecks() {
837
1382
  }
838
1383
 
839
1384
  // src/commands/audit.ts
840
- var { detectFramework: detectFramework2, getFrameworkDisplayName } = import_agent_runtime.frameworks;
1385
+ var { detectFramework: detectFramework2, getFrameworkDisplayName } = import_agent_runtime2.frameworks;
841
1386
  async function fetchHtml(url, timeout = 1e4) {
842
1387
  try {
843
1388
  const controller = new AbortController();
@@ -858,9 +1403,16 @@ async function fetchHtml(url, timeout = 1e4) {
858
1403
  return null;
859
1404
  }
860
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
+ };
861
1412
  async function runAIAnalysis(url, issues, provider, apiKey) {
862
1413
  const isAnthropic = provider === "anthropic";
863
- 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`;
864
1416
  const prompt = `Analyze these SEO audit results and provide prioritized recommendations:
865
1417
 
866
1418
  URL: ${url}
@@ -891,19 +1443,26 @@ Return JSON with:
891
1443
  });
892
1444
  } else {
893
1445
  headers["Authorization"] = `Bearer ${apiKey}`;
1446
+ if (provider === "openrouter") {
1447
+ headers["HTTP-Referer"] = "https://rankcli.dev";
1448
+ headers["X-Title"] = "RankCLI";
1449
+ }
894
1450
  body = JSON.stringify({
895
- model: "gpt-4o-mini",
1451
+ model: providerConfig.model,
896
1452
  messages: [
897
1453
  { role: "system", content: "You are an SEO expert. Respond with valid JSON only." },
898
1454
  { role: "user", content: prompt }
899
1455
  ],
900
1456
  max_tokens: 1500,
901
- 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" } } : {}
902
1461
  });
903
1462
  }
904
1463
  const response = await fetch(apiUrl, { method: "POST", headers, body });
905
1464
  if (!response.ok) {
906
- console.error(import_chalk2.default.yellow(`AI analysis failed: ${response.status}`));
1465
+ console.error(import_chalk4.default.yellow(`AI analysis failed: ${response.status}`));
907
1466
  return null;
908
1467
  }
909
1468
  const data = await response.json();
@@ -913,7 +1472,7 @@ Return JSON with:
913
1472
  if (!jsonMatch) return null;
914
1473
  return JSON.parse(jsonMatch[0]);
915
1474
  } catch (error) {
916
- console.error(import_chalk2.default.yellow(`AI analysis error: ${error}`));
1475
+ console.error(import_chalk4.default.yellow(`AI analysis error: ${error}`));
917
1476
  return null;
918
1477
  }
919
1478
  }
@@ -939,7 +1498,7 @@ async function syncToCloud(url, report, mergedIssues, pagesAudited, aiAnalysis,
939
1498
  if (!userId) return null;
940
1499
  try {
941
1500
  const parsedUrl = new URL(url);
942
- const domain = parsedUrl.hostname;
1501
+ const domain = parsedUrl.port ? `${parsedUrl.hostname}:${parsedUrl.port}` : parsedUrl.hostname;
943
1502
  const { data: existingProject } = await supabase.from("projects").select("id").eq("user_id", userId).eq("domain", domain).eq("is_active", true).single();
944
1503
  let projectId;
945
1504
  if (existingProject) {
@@ -948,9 +1507,9 @@ async function syncToCloud(url, report, mergedIssues, pagesAudited, aiAnalysis,
948
1507
  const { count } = await supabase.from("projects").select("id", { count: "exact", head: true }).eq("user_id", userId).eq("is_active", true);
949
1508
  const limit = sitesLimit ?? 1;
950
1509
  if (count !== null && count >= limit) {
951
- console.log(import_chalk2.default.yellow(`
1510
+ console.log(import_chalk4.default.yellow(`
952
1511
  \u26A0\uFE0F Project limit reached (${count}/${limit})`));
953
- 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`));
954
1513
  return null;
955
1514
  }
956
1515
  const { data: newProject, error: projectError } = await supabase.from("projects").insert({
@@ -960,7 +1519,7 @@ async function syncToCloud(url, report, mergedIssues, pagesAudited, aiAnalysis,
960
1519
  domain
961
1520
  }).select("id").single();
962
1521
  if (projectError || !newProject) {
963
- console.error(import_chalk2.default.dim("Failed to create project"));
1522
+ console.error(import_chalk4.default.dim("Failed to create project"));
964
1523
  return null;
965
1524
  }
966
1525
  projectId = newProject.id;
@@ -1001,7 +1560,7 @@ async function syncToCloud(url, report, mergedIssues, pagesAudited, aiAnalysis,
1001
1560
  ai_analysis: aiAnalysis
1002
1561
  }).select("id").single();
1003
1562
  if (auditError || !audit2) {
1004
- console.error(import_chalk2.default.dim("Failed to save audit"));
1563
+ console.error(import_chalk4.default.dim("Failed to save audit"));
1005
1564
  return null;
1006
1565
  }
1007
1566
  if (mergedIssues.length > 0) {
@@ -1029,7 +1588,7 @@ async function syncToCloud(url, report, mergedIssues, pagesAudited, aiAnalysis,
1029
1588
  }).eq("id", projectId);
1030
1589
  return { projectId, auditId: audit2.id };
1031
1590
  } catch (error) {
1032
- console.error(import_chalk2.default.dim("Cloud sync failed"));
1591
+ console.error(import_chalk4.default.dim("Cloud sync failed"));
1033
1592
  return null;
1034
1593
  }
1035
1594
  }
@@ -1037,8 +1596,16 @@ async function audit(options) {
1037
1596
  const url = options.url;
1038
1597
  const maxPages = parseInt(options.maxPages || "5", 10);
1039
1598
  const enableAI = options.ai || false;
1040
- const aiProvider = options.aiProvider || "openai";
1041
- 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] || "";
1042
1609
  const loggedIn = isLoggedIn();
1043
1610
  const { planId } = getUserInfo();
1044
1611
  const tierId = planId && planId in TIER_LIMITS ? planId : loggedIn ? "free" : "free_anon";
@@ -1051,7 +1618,7 @@ async function audit(options) {
1051
1618
  await initEngine(passphrase);
1052
1619
  premiumChecks = await getPremiumChecks();
1053
1620
  } catch (error) {
1054
- 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})`));
1055
1622
  }
1056
1623
  }
1057
1624
  }
@@ -1061,12 +1628,12 @@ async function audit(options) {
1061
1628
  console.log("\nOptions:");
1062
1629
  console.log(" --max-pages <n> Max pages to crawl (default: 5)");
1063
1630
  console.log(" --ai Enable AI analysis");
1064
- console.log(" --ai-provider <p> AI provider: openai or anthropic");
1065
- 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)");
1066
1633
  console.log(" --output json Output as JSON");
1067
1634
  if (!loggedIn) {
1068
- console.log(import_chalk2.default.dim("\nTip: Log in to sync results to the web dashboard:"));
1069
- 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"));
1070
1637
  }
1071
1638
  console.log("");
1072
1639
  return;
@@ -1079,13 +1646,13 @@ async function audit(options) {
1079
1646
  }
1080
1647
  const startTime = Date.now();
1081
1648
  try {
1082
- const discoverySpinner = (0, import_ora.default)("Discovering pages...").start();
1649
+ const discoverySpinner = (0, import_ora2.default)("Discovering pages...").start();
1083
1650
  let pagesToAudit = [url];
1084
1651
  const homepageHtml = await fetchHtml(url, 1e4);
1085
1652
  let detectedFramework = detectFramework2({});
1086
1653
  if (homepageHtml) {
1087
1654
  detectedFramework = detectFramework2({ html: homepageHtml });
1088
- const discovered = (0, import_agent_runtime.discoverPagesFromLinks)(url, homepageHtml, maxPages - 1);
1655
+ const discovered = (0, import_agent_runtime2.discoverPagesFromLinks)(url, homepageHtml, maxPages - 1);
1089
1656
  const additionalPages = discovered.map((p) => p.url).filter((u) => u !== url);
1090
1657
  pagesToAudit = [url, ...additionalPages];
1091
1658
  const frameworkLabel = detectedFramework.framework !== "unknown" ? ` (${getFrameworkDisplayName(detectedFramework.framework)})` : "";
@@ -1093,10 +1660,10 @@ async function audit(options) {
1093
1660
  } else {
1094
1661
  discoverySpinner.warn("Could not fetch homepage, auditing URL only");
1095
1662
  }
1096
- 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();
1097
1664
  const auditPage = async (pageUrl, index) => {
1098
1665
  try {
1099
- const report = await (0, import_agent_runtime.runFullAudit)({
1666
+ const report = await (0, import_agent_runtime2.runFullAudit)({
1100
1667
  url: pageUrl,
1101
1668
  maxPages: 1,
1102
1669
  checkBrokenLinks: options.checkLinks || false,
@@ -1140,7 +1707,7 @@ async function audit(options) {
1140
1707
  const mergedIssues = mergeIssues(allIssues);
1141
1708
  let aiAnalysis = null;
1142
1709
  if (enableAI && aiKey) {
1143
- const aiSpinner = (0, import_ora.default)(`Running AI analysis (${aiProvider})...`).start();
1710
+ const aiSpinner = (0, import_ora2.default)(`Running AI analysis (${aiProvider})...`).start();
1144
1711
  aiAnalysis = await runAIAnalysis(url, mergedIssues, aiProvider, aiKey);
1145
1712
  if (aiAnalysis) {
1146
1713
  aiSpinner.succeed("AI analysis complete");
@@ -1148,8 +1715,8 @@ async function audit(options) {
1148
1715
  aiSpinner.warn("AI analysis failed or returned no results");
1149
1716
  }
1150
1717
  } else if (enableAI && !aiKey) {
1151
- console.log(import_chalk2.default.yellow("\n\u26A0\uFE0F AI analysis requested but no API key provided"));
1152
- 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"));
1153
1720
  }
1154
1721
  const totalTime = Date.now() - startTime;
1155
1722
  const frameworkName = detectedFramework.framework !== "unknown" ? getFrameworkDisplayName(detectedFramework.framework) : null;
@@ -1177,27 +1744,27 @@ async function audit(options) {
1177
1744
  };
1178
1745
  console.log(JSON.stringify(jsonOutput, null, 2));
1179
1746
  } else {
1180
- console.log("\n" + import_chalk2.default.bold("\u2550".repeat(60)));
1181
- console.log(import_chalk2.default.bold.cyan(` SEO AUDIT REPORT: ${url}`));
1182
- 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)));
1183
1750
  const score = primaryReport.healthScore?.overall || 0;
1184
- 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;
1185
1752
  console.log(`
1186
- ${import_chalk2.default.bold("Score:")} ${scoreColor.bold(score + "/100")}`);
1187
- console.log(` ${import_chalk2.default.bold("Pages audited:")} ${successCount}`);
1188
- console.log(` ${import_chalk2.default.bold("Issues found:")} ${mergedIssues.length}`);
1189
- 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`);
1190
1757
  if (frameworkName) {
1191
- 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)}`);
1192
1759
  }
1193
1760
  if (primaryReport.healthScore) {
1194
- console.log("\n" + import_chalk2.default.bold(" Health Scores:"));
1761
+ console.log("\n" + import_chalk4.default.bold(" Health Scores:"));
1195
1762
  const hs = primaryReport.healthScore;
1196
1763
  const bar = (score2) => {
1197
1764
  const filled = Math.round(score2 / 5);
1198
1765
  const empty = 20 - filled;
1199
- const color = score2 >= 80 ? import_chalk2.default.green : score2 >= 60 ? import_chalk2.default.yellow : import_chalk2.default.red;
1200
- 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));
1201
1768
  };
1202
1769
  console.log(` Crawlability: ${bar(hs.crawlability || 0)} ${hs.crawlability || 0}%`);
1203
1770
  console.log(` On-Page: ${bar(hs.onPage || 0)} ${hs.onPage || 0}%`);
@@ -1210,35 +1777,35 @@ async function audit(options) {
1210
1777
  const errors = mergedIssues.filter((i) => i.severity === "error");
1211
1778
  const warnings = mergedIssues.filter((i) => i.severity === "warning");
1212
1779
  const notices = mergedIssues.filter((i) => i.severity === "notice");
1213
- console.log("\n" + import_chalk2.default.bold(" Issues by Severity:"));
1214
- console.log(` ${import_chalk2.default.red("\u25CF")} Errors: ${errors.length}`);
1215
- console.log(` ${import_chalk2.default.yellow("\u25CF")} Warnings: ${warnings.length}`);
1216
- console.log(` ${import_chalk2.default.blue("\u25CF")} Notices: ${notices.length}`);
1217
- 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:"));
1218
1785
  const topIssues = [...errors, ...warnings, ...notices].slice(0, 10);
1219
1786
  topIssues.forEach((issue, i) => {
1220
- 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");
1221
1788
  console.log(` ${i + 1}. ${icon} ${issue.title}`);
1222
1789
  if (issue.affectedUrls && issue.affectedUrls.length > 1) {
1223
- console.log(import_chalk2.default.dim(` Affects ${issue.affectedUrls.length} pages`));
1790
+ console.log(import_chalk4.default.dim(` Affects ${issue.affectedUrls.length} pages`));
1224
1791
  }
1225
1792
  });
1226
1793
  if (aiAnalysis) {
1227
- console.log("\n" + import_chalk2.default.bold(" AI Analysis:"));
1794
+ console.log("\n" + import_chalk4.default.bold(" AI Analysis:"));
1228
1795
  console.log(` Content Score: ${aiAnalysis.contentScore}/100`);
1229
- console.log("\n " + import_chalk2.default.bold("Suggestions:"));
1796
+ console.log("\n " + import_chalk4.default.bold("Suggestions:"));
1230
1797
  aiAnalysis.suggestions.forEach((s, i) => {
1231
1798
  console.log(` ${i + 1}. ${s}`);
1232
1799
  });
1233
1800
  if (aiAnalysis.prioritizedFixes?.length > 0) {
1234
- console.log("\n " + import_chalk2.default.bold("Priority Fixes:"));
1801
+ console.log("\n " + import_chalk4.default.bold("Priority Fixes:"));
1235
1802
  aiAnalysis.prioritizedFixes.slice(0, 5).forEach((fix2, i) => {
1236
1803
  console.log(` ${i + 1}. [P${fix2.priority}] ${fix2.code}`);
1237
- console.log(import_chalk2.default.dim(` ${fix2.rationale}`));
1804
+ console.log(import_chalk4.default.dim(` ${fix2.rationale}`));
1238
1805
  });
1239
1806
  }
1240
1807
  }
1241
- console.log("\n" + import_chalk2.default.bold("\u2550".repeat(60)));
1808
+ console.log("\n" + import_chalk4.default.bold("\u2550".repeat(60)));
1242
1809
  const userInfo = getUserInfo();
1243
1810
  const tier = userInfo.planName?.toLowerCase() || "free";
1244
1811
  const isFreeTier = !loggedIn || tier === "free";
@@ -1254,13 +1821,13 @@ async function audit(options) {
1254
1821
  showUpsell("geo_limited", { tier: loggedIn ? "free" : "free_anon" });
1255
1822
  }
1256
1823
  if (loggedIn) {
1257
- const syncSpinner = (0, import_ora.default)("Syncing to cloud...").start();
1824
+ const syncSpinner = (0, import_ora2.default)("Syncing to cloud...").start();
1258
1825
  const syncResult = await syncToCloud(url, primaryReport, mergedIssues, successCount, aiAnalysis, totalTime, actualChecksRun);
1259
1826
  if (syncResult) {
1260
1827
  syncSpinner.succeed("Results synced to cloud");
1261
- console.log(import_chalk2.default.cyan(`
1828
+ console.log(import_chalk4.default.cyan(`
1262
1829
  View and manage issues in the web dashboard:`));
1263
- 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}
1264
1831
  `));
1265
1832
  } else {
1266
1833
  syncSpinner.warn("Could not sync to cloud");
@@ -1279,7 +1846,7 @@ async function audit(options) {
1279
1846
  // src/commands/apply.ts
1280
1847
  var import_fs3 = require("fs");
1281
1848
  var import_path3 = require("path");
1282
- var import_agent_runtime2 = require("@rankcli/agent-runtime");
1849
+ var import_agent_runtime3 = require("@rankcli/agent-runtime");
1283
1850
  var SEVERITY_ICON = {
1284
1851
  critical: "\u274C",
1285
1852
  warning: "\u26A0\uFE0F",
@@ -1307,7 +1874,7 @@ function truncate(str, maxLen) {
1307
1874
  }
1308
1875
  async function apply(options) {
1309
1876
  const cwd = process.cwd();
1310
- const configPath = (0, import_path3.join)(cwd, "seo-autopilot.config.json");
1877
+ const configPath = (0, import_path3.join)(cwd, "rankcli.config.json");
1311
1878
  let config2 = null;
1312
1879
  if ((0, import_fs3.existsSync)(configPath)) {
1313
1880
  try {
@@ -1321,8 +1888,8 @@ async function apply(options) {
1321
1888
  }
1322
1889
  if (!url) {
1323
1890
  console.log("\n\u26A0\uFE0F Please provide a URL to analyze:");
1324
- console.log(" seo apply --url https://your-site.com");
1325
- 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");
1326
1893
  return;
1327
1894
  }
1328
1895
  try {
@@ -1331,7 +1898,7 @@ async function apply(options) {
1331
1898
  console.log("\n\u274C Invalid URL format. Please provide a valid URL.\n");
1332
1899
  return;
1333
1900
  }
1334
- console.log("\n\u{1F680} SEO Autopilot - Apply Fixes");
1901
+ console.log("\n\u{1F680} RankCLI - Apply Fixes");
1335
1902
  console.log("\u2550".repeat(50));
1336
1903
  const loggedIn = isLoggedIn();
1337
1904
  const userInfo = getUserInfo();
@@ -1342,7 +1909,7 @@ async function apply(options) {
1342
1909
  return;
1343
1910
  }
1344
1911
  console.log("\n\u{1F50D} Detecting framework...");
1345
- const frameworkResult = await (0, import_agent_runtime2.detectFramework)({ cwd });
1912
+ const frameworkResult = await (0, import_agent_runtime3.detectFramework)({ cwd });
1346
1913
  const framework = frameworkResult.success ? frameworkResult.data : { name: "Unknown", metaPattern: "html-head" };
1347
1914
  console.log(` Framework: ${framework.name}`);
1348
1915
  console.log(`
@@ -1352,7 +1919,7 @@ async function apply(options) {
1352
1919
  let score;
1353
1920
  let auditResult = null;
1354
1921
  try {
1355
- auditResult = await (0, import_agent_runtime2.runAuditWithFixes)({
1922
+ auditResult = await (0, import_agent_runtime3.runAuditWithFixes)({
1356
1923
  url,
1357
1924
  generateFixes: true
1358
1925
  });
@@ -1384,7 +1951,7 @@ async function apply(options) {
1384
1951
  return;
1385
1952
  }
1386
1953
  console.log("\n\u{1F527} Generating fixes...");
1387
- const fixes = await (0, import_agent_runtime2.generateFixes)(issues, {
1954
+ const fixes = await (0, import_agent_runtime3.generateFixes)(issues, {
1388
1955
  cwd,
1389
1956
  url,
1390
1957
  framework
@@ -1423,7 +1990,7 @@ async function apply(options) {
1423
1990
  }
1424
1991
  }
1425
1992
  console.log("\n\u{1F527} Applying fixes...");
1426
- 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 });
1427
1994
  console.log("\n" + "\u2550".repeat(50));
1428
1995
  console.log("RESULTS");
1429
1996
  console.log("\u2550".repeat(50));
@@ -1454,17 +2021,17 @@ async function apply(options) {
1454
2021
  console.log("\n2. Commit the SEO improvements:");
1455
2022
  console.log(' git add -A && git commit -m "feat(seo): add meta tags and structured data"');
1456
2023
  console.log("\n3. Re-run the audit to verify:");
1457
- console.log(` seo audit --url ${url}`);
2024
+ console.log(` rankcli audit --url ${url}`);
1458
2025
  console.log("");
1459
2026
  }
1460
2027
 
1461
2028
  // src/commands/fix.ts
1462
- var import_child_process = require("child_process");
2029
+ var import_child_process2 = require("child_process");
1463
2030
  var import_util = require("util");
1464
- var import_agent_runtime3 = require("@rankcli/agent-runtime");
1465
- var import_ora2 = __toESM(require("ora"));
1466
- var import_chalk3 = __toESM(require("chalk"));
1467
- 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);
1468
2035
  var SEVERITY_ICON2 = {
1469
2036
  critical: "\u274C",
1470
2037
  error: "\u274C",
@@ -1530,19 +2097,19 @@ function printFix2(fix2, index) {
1530
2097
  const icon = SEVERITY_ICON2[fix2.issue.severity] || "\u2022";
1531
2098
  console.log(`
1532
2099
  ${index + 1}. ${icon} ${fix2.issue.message}`);
1533
- console.log(` Code: ${import_chalk3.default.dim(fix2.issue.code)}`);
1534
- 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)}`);
1535
2102
  if (fix2.skipped) {
1536
- console.log(` Status: ${import_chalk3.default.yellow("SKIPPED")} - ${fix2.skipReason}`);
2103
+ console.log(` Status: ${import_chalk5.default.yellow("SKIPPED")} - ${fix2.skipReason}`);
1537
2104
  } else if (fix2.applied) {
1538
- console.log(` Status: ${import_chalk3.default.green("APPLIED")}`);
1539
- 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}`);
1540
2107
  } else {
1541
- console.log(` ${import_chalk3.default.dim("Explanation:")} ${fix2.explanation}`);
2108
+ console.log(` ${import_chalk5.default.dim("Explanation:")} ${fix2.explanation}`);
1542
2109
  if (fix2.before) {
1543
- console.log(` ${import_chalk3.default.red("Before:")} ${truncate2(fix2.before, 60)}`);
2110
+ console.log(` ${import_chalk5.default.red("Before:")} ${truncate2(fix2.before, 60)}`);
1544
2111
  }
1545
- console.log(` ${import_chalk3.default.green("After:")} ${truncate2(fix2.after, 100)}`);
2112
+ console.log(` ${import_chalk5.default.green("After:")} ${truncate2(fix2.after, 100)}`);
1546
2113
  }
1547
2114
  }
1548
2115
  async function fix(options) {
@@ -1586,15 +2153,15 @@ async function fix(options) {
1586
2153
  console.log("\n\u{1F527} RankCLI Fix - Agentic SEO Fixer");
1587
2154
  console.log("\u2550".repeat(50));
1588
2155
  }
1589
- const frameworkSpinner = options.output !== "json" ? (0, import_ora2.default)("Detecting framework...").start() : null;
1590
- 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 });
1591
2158
  const framework = frameworkResult.success ? frameworkResult.data : { name: "Unknown", metaPattern: "html-head" };
1592
2159
  frameworkSpinner?.succeed(`Framework: ${framework.name}`);
1593
- 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;
1594
2161
  let issues;
1595
2162
  let score;
1596
2163
  try {
1597
- const analysis = await (0, import_agent_runtime3.runDirectAnalysis)(url);
2164
+ const analysis = await (0, import_agent_runtime4.runDirectAnalysis)(url);
1598
2165
  issues = analysis.issues;
1599
2166
  score = analysis.score;
1600
2167
  } catch (error) {
@@ -1607,7 +2174,7 @@ async function fix(options) {
1607
2174
  const onlyCodes = new Set(options.only.map((c) => c.toUpperCase()));
1608
2175
  issues = issues.filter((i) => onlyCodes.has(i.code.toUpperCase()));
1609
2176
  if (options.output !== "json") {
1610
- 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(", ")}`));
1611
2178
  }
1612
2179
  }
1613
2180
  if (issues.length === 0) {
@@ -1624,8 +2191,8 @@ async function fix(options) {
1624
2191
  durationMs: Date.now() - startTime
1625
2192
  };
1626
2193
  }
1627
- const fixSpinner = options.output !== "json" ? (0, import_ora2.default)("Generating fixes...").start() : null;
1628
- 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, {
1629
2196
  cwd,
1630
2197
  url,
1631
2198
  framework
@@ -1636,13 +2203,13 @@ async function fix(options) {
1636
2203
  if (options.dryRun) {
1637
2204
  if (options.output !== "json") {
1638
2205
  console.log("\n" + "\u2500".repeat(50));
1639
- 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"));
1640
2207
  console.log("\u2500".repeat(50));
1641
2208
  for (let i = 0; i < fixes.length; i++) {
1642
2209
  printFix2(fixes[i], i);
1643
2210
  }
1644
2211
  console.log("\n" + "\u2500".repeat(50));
1645
- 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`);
1646
2213
  console.log(` Remove --dry-run to apply these fixes.
1647
2214
  `);
1648
2215
  }
@@ -1708,8 +2275,8 @@ async function fix(options) {
1708
2275
  durationMs: Date.now() - startTime
1709
2276
  };
1710
2277
  }
1711
- const applySpinner = options.output !== "json" ? (0, import_ora2.default)("Applying fixes...").start() : null;
1712
- 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 });
1713
2280
  for (const fix2 of applied) {
1714
2281
  fix2.applied = true;
1715
2282
  }
@@ -1720,7 +2287,7 @@ async function fix(options) {
1720
2287
  let commitError;
1721
2288
  let pushError;
1722
2289
  if (options.commit && applied.length > 0 && commitMessage) {
1723
- 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;
1724
2291
  const gitResult = await executeGitCommit(cwd, commitMessage, options.push || false);
1725
2292
  committed = gitResult.committed;
1726
2293
  pushed = gitResult.pushed;
@@ -1746,23 +2313,23 @@ async function fix(options) {
1746
2313
  console.log("\u2550".repeat(50));
1747
2314
  if (applied.length > 0) {
1748
2315
  console.log(`
1749
- ${import_chalk3.default.green("\u2705")} Applied ${applied.length} fix(es):`);
2316
+ ${import_chalk5.default.green("\u2705")} Applied ${applied.length} fix(es):`);
1750
2317
  for (const fix2 of applied) {
1751
2318
  console.log(` - ${fix2.file}: ${fix2.issue.message}`);
1752
2319
  }
1753
2320
  }
1754
2321
  if (skipped.length > 0) {
1755
2322
  console.log(`
1756
- ${import_chalk3.default.yellow("\u26A0\uFE0F")} Skipped ${skipped.length} fix(es):`);
2323
+ ${import_chalk5.default.yellow("\u26A0\uFE0F")} Skipped ${skipped.length} fix(es):`);
1757
2324
  for (const fix2 of skipped) {
1758
2325
  console.log(` - ${fix2.file}: ${fix2.skipReason || "Manual fix required"}`);
1759
2326
  }
1760
2327
  }
1761
2328
  if (committed) {
1762
2329
  console.log(`
1763
- ${import_chalk3.default.green("\u2705")} Changes committed`);
2330
+ ${import_chalk5.default.green("\u2705")} Changes committed`);
1764
2331
  if (pushed) {
1765
- console.log(`${import_chalk3.default.green("\u2705")} Changes pushed`);
2332
+ console.log(`${import_chalk5.default.green("\u2705")} Changes pushed`);
1766
2333
  }
1767
2334
  }
1768
2335
  if (!committed && applied.length > 0) {
@@ -1815,10 +2382,10 @@ ${import_chalk3.default.green("\u2705")} Changes committed`);
1815
2382
  }
1816
2383
 
1817
2384
  // src/commands/track.ts
1818
- var import_ora3 = __toESM(require("ora"));
1819
- var import_chalk4 = __toESM(require("chalk"));
2385
+ var import_ora4 = __toESM(require("ora"));
2386
+ var import_chalk6 = __toESM(require("chalk"));
1820
2387
  var import_table = require("table");
1821
- var import_agent_runtime4 = require("@rankcli/agent-runtime");
2388
+ var import_agent_runtime5 = require("@rankcli/agent-runtime");
1822
2389
  async function getProjectForUrl(url) {
1823
2390
  const supabase = await getAuthenticatedClient();
1824
2391
  if (!supabase) return null;
@@ -1848,42 +2415,42 @@ async function getProjectForUrl(url) {
1848
2415
  };
1849
2416
  }
1850
2417
  function getTierLimits(tier) {
1851
- 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;
1852
2419
  return {
1853
2420
  maxKeywords: limits.maxKeywords,
1854
2421
  checksPerDay: limits.checksPerDay
1855
2422
  };
1856
2423
  }
1857
2424
  function formatPosition(pos) {
1858
- if (pos === null) return import_chalk4.default.dim("N/A");
1859
- if (pos <= 3) return import_chalk4.default.green.bold(`#${pos}`);
1860
- if (pos <= 10) return import_chalk4.default.green(`#${pos}`);
1861
- if (pos <= 20) return import_chalk4.default.yellow(`#${pos}`);
1862
- if (pos <= 50) return import_chalk4.default.red(`#${pos}`);
1863
- 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}`);
1864
2431
  }
1865
2432
  function formatChange(change) {
1866
- if (change === 0) return import_chalk4.default.dim("\u2192");
1867
- if (change > 0) return import_chalk4.default.green(`\u2191${change}`);
1868
- 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)}`);
1869
2436
  }
1870
2437
  async function trackAdd(options) {
1871
2438
  if (!options.keywords || options.keywords.length === 0) {
1872
- 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:"));
1873
2440
  console.log(' rankcli track add "seo tool" "cli seo" --url https://your-site.com\n');
1874
2441
  return { success: false, error: "No keywords provided" };
1875
2442
  }
1876
2443
  if (!options.url) {
1877
- 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:"));
1878
2445
  console.log(' rankcli track add "keyword" --url https://your-site.com\n');
1879
2446
  return { success: false, error: "No URL provided" };
1880
2447
  }
1881
2448
  if (!isLoggedIn()) {
1882
- 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:"));
1883
2450
  console.log(" rankcli login\n");
1884
2451
  return { success: false, error: "Not logged in" };
1885
2452
  }
1886
- const spinner = (0, import_ora3.default)("Adding keywords to track...").start();
2453
+ const spinner = (0, import_ora4.default)("Adding keywords to track...").start();
1887
2454
  try {
1888
2455
  const project = await getProjectForUrl(options.url);
1889
2456
  if (!project) {
@@ -1924,10 +2491,10 @@ async function trackAdd(options) {
1924
2491
  }
1925
2492
  const addedCount = data?.length || 0;
1926
2493
  spinner.succeed(`Added ${addedCount} keyword(s) to track`);
1927
- console.log(import_chalk4.default.dim(`
2494
+ console.log(import_chalk6.default.dim(`
1928
2495
  Tracking: ${options.keywords.join(", ")}`));
1929
- console.log(import_chalk4.default.dim(`For: ${project.domain}`));
1930
- console.log(import_chalk4.default.cyan(`
2496
+ console.log(import_chalk6.default.dim(`For: ${project.domain}`));
2497
+ console.log(import_chalk6.default.cyan(`
1931
2498
  Run 'rankcli track check --url ${options.url}' to check current positions.
1932
2499
  `));
1933
2500
  return { success: true, addedCount };
@@ -1938,16 +2505,16 @@ Run 'rankcli track check --url ${options.url}' to check current positions.
1938
2505
  }
1939
2506
  async function trackStatus(options) {
1940
2507
  if (!options.url) {
1941
- 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:"));
1942
2509
  console.log(" rankcli track status --url https://your-site.com\n");
1943
2510
  return { success: false, error: "No URL provided" };
1944
2511
  }
1945
2512
  if (!isLoggedIn()) {
1946
- 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:"));
1947
2514
  console.log(" rankcli login\n");
1948
2515
  return { success: false, error: "Not logged in" };
1949
2516
  }
1950
- const spinner = (0, import_ora3.default)("Loading keyword rankings...").start();
2517
+ const spinner = (0, import_ora4.default)("Loading keyword rankings...").start();
1951
2518
  try {
1952
2519
  const project = await getProjectForUrl(options.url);
1953
2520
  if (!project) {
@@ -1966,8 +2533,8 @@ async function trackStatus(options) {
1966
2533
  }
1967
2534
  spinner.succeed(`Loaded ${data?.length || 0} tracked keywords`);
1968
2535
  if (!data || data.length === 0) {
1969
- console.log(import_chalk4.default.dim("\nNo keywords tracked yet."));
1970
- 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.
1971
2538
  `));
1972
2539
  return { success: true, keywords: [] };
1973
2540
  }
@@ -1982,7 +2549,7 @@ async function trackStatus(options) {
1982
2549
  if (options.output === "json") {
1983
2550
  console.log(JSON.stringify({ keywords: keywords2 }, null, 2));
1984
2551
  } else {
1985
- console.log("\n" + import_chalk4.default.bold("\u{1F4CA} Keyword Rankings"));
2552
+ console.log("\n" + import_chalk6.default.bold("\u{1F4CA} Keyword Rankings"));
1986
2553
  console.log("\u2550".repeat(60));
1987
2554
  const tableData = [
1988
2555
  ["Keyword", "Position", "Best", "Change", "Last Checked"],
@@ -2025,14 +2592,14 @@ async function trackHistory(options) {
2025
2592
  return { success: false, error: "No URL provided" };
2026
2593
  }
2027
2594
  if (!options.keyword) {
2028
- 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:"));
2029
2596
  console.log(' rankcli track history --keyword "seo tool" --url https://your-site.com\n');
2030
2597
  return { success: false, error: "No keyword provided" };
2031
2598
  }
2032
2599
  if (!isLoggedIn()) {
2033
2600
  return { success: false, error: "Not logged in" };
2034
2601
  }
2035
- 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();
2036
2603
  try {
2037
2604
  const project = await getProjectForUrl(options.url);
2038
2605
  if (!project) {
@@ -2065,21 +2632,21 @@ async function trackHistory(options) {
2065
2632
  if (options.output === "json") {
2066
2633
  console.log(JSON.stringify({ keyword: options.keyword, history }, null, 2));
2067
2634
  } else {
2068
- 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}"`));
2069
2636
  console.log("\u2550".repeat(50));
2070
2637
  if (history.length === 0) {
2071
- console.log(import_chalk4.default.dim("\nNo history available yet.\n"));
2638
+ console.log(import_chalk6.default.dim("\nNo history available yet.\n"));
2072
2639
  } else {
2073
2640
  const positions = history.slice(0, 30).reverse().map((h) => h.position || 101);
2074
2641
  const sparkline = positions.map((p) => {
2075
- if (p <= 10) return import_chalk4.default.green("\u2588");
2076
- if (p <= 20) return import_chalk4.default.yellow("\u2593");
2077
- if (p <= 50) return import_chalk4.default.red("\u2592");
2078
- 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");
2079
2646
  }).join("");
2080
2647
  console.log(`
2081
2648
  ${sparkline} (last ${positions.length} checks)`);
2082
- console.log(import_chalk4.default.dim(" \u2190 newer older \u2192\n"));
2649
+ console.log(import_chalk6.default.dim(" \u2190 newer older \u2192\n"));
2083
2650
  const recent = history.slice(0, 10);
2084
2651
  for (const entry of recent) {
2085
2652
  console.log(` ${entry.recordedAt.toLocaleDateString()} ${formatPosition(entry.position)}`);
@@ -2099,7 +2666,7 @@ async function trackExport(options) {
2099
2666
  if (!isLoggedIn()) {
2100
2667
  return { success: false, error: "Not logged in" };
2101
2668
  }
2102
- const spinner = (0, import_ora3.default)("Exporting data...").start();
2669
+ const spinner = (0, import_ora4.default)("Exporting data...").start();
2103
2670
  try {
2104
2671
  const project = await getProjectForUrl(options.url);
2105
2672
  if (!project) {
@@ -2156,12 +2723,12 @@ async function trackExport(options) {
2156
2723
  }
2157
2724
  async function trackCheck(options) {
2158
2725
  if (!options.url) {
2159
- 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:"));
2160
2727
  console.log(" rankcli track check --url https://your-site.com\n");
2161
2728
  return { success: false, error: "No URL provided" };
2162
2729
  }
2163
2730
  if (!isLoggedIn()) {
2164
- 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:"));
2165
2732
  console.log(" rankcli login\n");
2166
2733
  return { success: false, error: "Not logged in" };
2167
2734
  }
@@ -2178,14 +2745,14 @@ async function trackCheck(options) {
2178
2745
  const minHoursBetweenChecks = 24 / checksPerDay;
2179
2746
  if (hoursSinceCheck < minHoursBetweenChecks) {
2180
2747
  const nextCheck = new Date(project.lastRankCheckAt.getTime() + minHoursBetweenChecks * 60 * 60 * 1e3);
2181
- console.log(import_chalk4.default.yellow(`
2748
+ console.log(import_chalk6.default.yellow(`
2182
2749
  \u26A0\uFE0F Rate limited. Next check available: ${nextCheck.toLocaleString()}`));
2183
- 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.
2184
2751
  `));
2185
2752
  return { success: true, rateLimited: true };
2186
2753
  }
2187
2754
  }
2188
- const spinner = (0, import_ora3.default)("Checking current rankings...").start();
2755
+ const spinner = (0, import_ora4.default)("Checking current rankings...").start();
2189
2756
  const supabase = await getAuthenticatedClient();
2190
2757
  if (!supabase) {
2191
2758
  spinner.fail("Could not connect to database");
@@ -2199,11 +2766,11 @@ async function trackCheck(options) {
2199
2766
  const serpApiKey = process.env.VALUESERP_API_KEY || process.env.SERPAPI_KEY;
2200
2767
  if (!serpApiKey) {
2201
2768
  spinner.warn("No SERP API key configured");
2202
- console.log(import_chalk4.default.yellow("\n\u26A0\uFE0F Set VALUESERP_API_KEY or SERPAPI_KEY to check live rankings."));
2203
- 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"));
2204
2771
  return { success: false, error: "No SERP API key" };
2205
2772
  }
2206
- const tracker = new import_agent_runtime4.RankTracker({
2773
+ const tracker = new import_agent_runtime5.RankTracker({
2207
2774
  supabase,
2208
2775
  serpConfig: {
2209
2776
  provider: process.env.VALUESERP_API_KEY ? "valueserp" : "serpapi",
@@ -2212,13 +2779,13 @@ async function trackCheck(options) {
2212
2779
  });
2213
2780
  const results = await tracker.checkRankings(project.id, project.domain);
2214
2781
  spinner.succeed(`Checked ${results.length} keyword(s)`);
2215
- 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"));
2216
2783
  console.log("\u2550".repeat(50));
2217
2784
  for (const result of results) {
2218
2785
  const posStr = formatPosition(result.position);
2219
2786
  console.log(` ${result.keyword}: ${posStr}`);
2220
2787
  if (result.serpFeatures.length > 0) {
2221
- 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(", ")}`));
2222
2789
  }
2223
2790
  }
2224
2791
  console.log("");
@@ -2236,9 +2803,9 @@ async function trackCheck(options) {
2236
2803
  }
2237
2804
 
2238
2805
  // src/commands/geo.ts
2239
- var import_agent_runtime5 = require("@rankcli/agent-runtime");
2240
- var import_ora4 = __toESM(require("ora"));
2241
- 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"));
2242
2809
  async function fetchResources(url) {
2243
2810
  const headers = {};
2244
2811
  let html = null;
@@ -2284,7 +2851,7 @@ async function geo(options) {
2284
2851
  console.log("\n\u274C Invalid URL format. Please provide a valid URL.\n");
2285
2852
  return;
2286
2853
  }
2287
- 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();
2288
2855
  const { html, robotsTxt, headers } = await fetchResources(url);
2289
2856
  if (!html) {
2290
2857
  spinner.fail("Could not fetch page content");
@@ -2292,11 +2859,11 @@ async function geo(options) {
2292
2859
  }
2293
2860
  spinner.text = "Running GEO analysis...";
2294
2861
  try {
2295
- const geoResult = await import_agent_runtime5.analyzers.analyzeGEO(html, url, robotsTxt || void 0);
2296
- const cwvResult = import_agent_runtime5.analyzers.analyzeCoreWebVitals(html, url, headers);
2297
- const securityResult = import_agent_runtime5.analyzers.analyzeSecurityHeaders(headers, url);
2298
- const schemaResult = import_agent_runtime5.analyzers.analyzeStructuredData(html, url);
2299
- 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);
2300
2867
  spinner.succeed("GEO analysis complete");
2301
2868
  if (options.output === "json") {
2302
2869
  const jsonOutput = {
@@ -2310,55 +2877,55 @@ async function geo(options) {
2310
2877
  console.log(JSON.stringify(jsonOutput, null, 2));
2311
2878
  return;
2312
2879
  }
2313
- console.log("\n" + import_chalk5.default.bold("\u2550".repeat(60)));
2314
- console.log(import_chalk5.default.bold.cyan(" GEO ANALYSIS: AI Search Optimization"));
2315
- 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)));
2316
2883
  const geoScore = geoResult.score;
2317
- 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;
2318
2885
  console.log(`
2319
- ${import_chalk5.default.bold("GEO Score:")} ${scoreColor.bold(geoScore + "/100")}`);
2320
- 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:"));
2321
2888
  const { aiCrawlerAccess } = geoResult;
2322
2889
  if (aiCrawlerAccess.blockedCrawlers.length > 0) {
2323
- console.log(import_chalk5.default.red(` \u274C Blocked: ${aiCrawlerAccess.blockedCrawlers.join(", ")}`));
2890
+ console.log(import_chalk7.default.red(` \u274C Blocked: ${aiCrawlerAccess.blockedCrawlers.join(", ")}`));
2324
2891
  }
2325
2892
  if (aiCrawlerAccess.allowedCrawlers.length > 0) {
2326
2893
  const topAllowed = aiCrawlerAccess.allowedCrawlers.slice(0, 5);
2327
- 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 ? "..." : ""}`));
2328
2895
  }
2329
- console.log("\n" + import_chalk5.default.bold(" Rendering:"));
2896
+ console.log("\n" + import_chalk7.default.bold(" Rendering:"));
2330
2897
  if (aiCrawlerAccess.serverSideRendered) {
2331
- 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)"));
2332
2899
  } else if (aiCrawlerAccess.jsRenderingRequired) {
2333
- 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)"));
2334
2901
  }
2335
- console.log("\n" + import_chalk5.default.bold(" LLM Friendliness:"));
2902
+ console.log("\n" + import_chalk7.default.bold(" LLM Friendliness:"));
2336
2903
  const { llmSignals } = geoResult;
2337
2904
  const bar = (score) => {
2338
2905
  const filled = Math.round(score / 5);
2339
2906
  const empty = 20 - filled;
2340
- const color = score >= 70 ? import_chalk5.default.green : score >= 40 ? import_chalk5.default.yellow : import_chalk5.default.red;
2341
- 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));
2342
2909
  };
2343
2910
  console.log(` Content Clarity: ${bar(llmSignals.contentClarity)} ${llmSignals.contentClarity}%`);
2344
2911
  console.log(` Fact Density: ${bar(llmSignals.factDensity)} ${llmSignals.factDensity}%`);
2345
2912
  console.log(` Structure: ${bar(llmSignals.structureQuality)} ${llmSignals.structureQuality}%`);
2346
2913
  console.log(` Citations: ${bar(llmSignals.citationQuality)} ${llmSignals.citationQuality}%`);
2347
- console.log("\n" + import_chalk5.default.bold(" Content Structure:"));
2914
+ console.log("\n" + import_chalk7.default.bold(" Content Structure:"));
2348
2915
  const { contentStructure } = geoResult;
2349
- console.log(` ${contentStructure.hasStructuredData ? import_chalk5.default.green("\u2705") : import_chalk5.default.red("\u274C")} JSON-LD Structured Data`);
2350
- console.log(` ${contentStructure.hasFAQSchema ? import_chalk5.default.green("\u2705") : import_chalk5.default.yellow("\u25CB")} FAQ Schema`);
2351
- console.log(` ${contentStructure.hasArticleSchema ? import_chalk5.default.green("\u2705") : import_chalk5.default.yellow("\u25CB")} Article Schema`);
2352
- console.log(` ${contentStructure.hasBreadcrumbs ? import_chalk5.default.green("\u2705") : import_chalk5.default.yellow("\u25CB")} Breadcrumbs`);
2353
- 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")}`);
2354
- 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:"));
2355
2922
  const { citationReadiness } = geoResult;
2356
2923
  if (citationReadiness.trustSignals.length > 0) {
2357
- console.log(import_chalk5.default.green(` \u2705 ${citationReadiness.trustSignals.join(", ")}`));
2924
+ console.log(import_chalk7.default.green(` \u2705 ${citationReadiness.trustSignals.join(", ")}`));
2358
2925
  } else {
2359
- console.log(import_chalk5.default.yellow(" \u25CB No trust signals detected"));
2926
+ console.log(import_chalk7.default.yellow(" \u25CB No trust signals detected"));
2360
2927
  }
2361
- console.log("\n" + import_chalk5.default.bold(" Other Scores:"));
2928
+ console.log("\n" + import_chalk7.default.bold(" Other Scores:"));
2362
2929
  console.log(` Core Web Vitals: ${bar(cwvResult.overallScore)} ${cwvResult.overallScore}%`);
2363
2930
  console.log(` Security Headers: ${securityResult.grade} (${securityResult.score}%)`);
2364
2931
  console.log(` Structured Data: ${bar(schemaResult.score)} ${schemaResult.score}%`);
@@ -2372,18 +2939,18 @@ async function geo(options) {
2372
2939
  ];
2373
2940
  const criticalIssues = allIssues.filter((i) => i.severity === "critical" || i.severity === "error");
2374
2941
  if (criticalIssues.length > 0) {
2375
- console.log("\n" + import_chalk5.default.bold.red(" Critical Issues:"));
2942
+ console.log("\n" + import_chalk7.default.bold.red(" Critical Issues:"));
2376
2943
  criticalIssues.slice(0, 5).forEach((issue, i) => {
2377
- console.log(import_chalk5.default.red(` ${i + 1}. ${issue.title}`));
2944
+ console.log(import_chalk7.default.red(` ${i + 1}. ${issue.title}`));
2378
2945
  });
2379
2946
  }
2380
2947
  if (geoResult.recommendations.length > 0) {
2381
- console.log("\n" + import_chalk5.default.bold(" Recommendations:"));
2948
+ console.log("\n" + import_chalk7.default.bold(" Recommendations:"));
2382
2949
  geoResult.recommendations.slice(0, 5).forEach((rec, i) => {
2383
2950
  console.log(` ${i + 1}. ${rec}`);
2384
2951
  });
2385
2952
  }
2386
- console.log("\n" + import_chalk5.default.bold("\u2550".repeat(60)));
2953
+ console.log("\n" + import_chalk7.default.bold("\u2550".repeat(60)));
2387
2954
  console.log("");
2388
2955
  } catch (error) {
2389
2956
  spinner.fail(`Analysis failed: ${error instanceof Error ? error.message : "Unknown error"}`);
@@ -2391,13 +2958,13 @@ async function geo(options) {
2391
2958
  }
2392
2959
 
2393
2960
  // src/commands/keywords.ts
2394
- var readline = __toESM(require("readline"));
2395
- var import_agent_runtime6 = require("@rankcli/agent-runtime");
2961
+ var readline2 = __toESM(require("readline"));
2962
+ var import_agent_runtime7 = require("@rankcli/agent-runtime");
2396
2963
  async function keywords(options) {
2397
- console.log("\n\u{1F511} SEO Autopilot - Keyword Research\n");
2964
+ console.log("\n\u{1F511} RankCLI - Keyword Research\n");
2398
2965
  console.log("This tool helps you find keywords you can actually rank for,");
2399
2966
  console.log("based on your site's current authority and competition level.\n");
2400
- const rl = readline.createInterface({
2967
+ const rl = readline2.createInterface({
2401
2968
  input: process.stdin,
2402
2969
  output: process.stdout
2403
2970
  });
@@ -2443,7 +3010,7 @@ ${question}`);
2443
3010
  seedKeywords = options.seed.split(",").map((s) => s.trim());
2444
3011
  } else if (options.auto) {
2445
3012
  console.log("\n\u{1F50D} Auto-extracting seed keywords from your page...");
2446
- seedKeywords = await (0, import_agent_runtime6.extractSeedKeywords)(url);
3013
+ seedKeywords = await (0, import_agent_runtime7.extractSeedKeywords)(url);
2447
3014
  if (seedKeywords.length > 0) {
2448
3015
  console.log(`Found: ${seedKeywords.join(", ")}`);
2449
3016
  }
@@ -2468,7 +3035,7 @@ ${question}`);
2468
3035
  contentCapacity: "medium",
2469
3036
  targetGeo: "us"
2470
3037
  };
2471
- for (const q of import_agent_runtime6.SITE_PROFILE_QUESTIONS) {
3038
+ for (const q of import_agent_runtime7.SITE_PROFILE_QUESTIONS) {
2472
3039
  const answer = await askChoice(q.question, q.options);
2473
3040
  siteProfile[q.id] = answer;
2474
3041
  }
@@ -2487,13 +3054,13 @@ ${question}`);
2487
3054
  url,
2488
3055
  maxKeywords: 100
2489
3056
  };
2490
- const result = await (0, import_agent_runtime6.runKeywordResearch)(researchOptions);
2491
- 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));
2492
3059
  const allKeywords = [...result.quickWins, ...result.mediumTerm, ...result.longTerm];
2493
3060
  if (allKeywords.length > 5) {
2494
3061
  console.log("\n\u{1F4DA} Grouping keywords by topic...\n");
2495
- const topicResult = (0, import_agent_runtime6.groupKeywordsByTopic)(allKeywords);
2496
- 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));
2497
3064
  }
2498
3065
  if (result.quickWins.length > 0) {
2499
3066
  console.log('\n\u{1F4A1} TIP: Run "seo apply --url ' + url + '" to apply keyword optimizations');
@@ -2518,7 +3085,7 @@ async function keywordsQuick(url, providedSeeds) {
2518
3085
  `);
2519
3086
  } else {
2520
3087
  console.log("\u{1F50D} Extracting keywords from your page...");
2521
- seedKeywords = await (0, import_agent_runtime6.extractSeedKeywords)(url);
3088
+ seedKeywords = await (0, import_agent_runtime7.extractSeedKeywords)(url);
2522
3089
  if (seedKeywords.length === 0) {
2523
3090
  console.log("\u274C Could not extract keywords. Please use interactive mode.\n");
2524
3091
  return;
@@ -2534,18 +3101,18 @@ async function keywordsQuick(url, providedSeeds) {
2534
3101
  contentCapacity: "medium",
2535
3102
  targetGeo: "us"
2536
3103
  };
2537
- const result = await (0, import_agent_runtime6.runKeywordResearch)({
3104
+ const result = await (0, import_agent_runtime7.runKeywordResearch)({
2538
3105
  seedKeywords,
2539
3106
  siteProfile,
2540
3107
  url,
2541
3108
  maxKeywords: 50
2542
3109
  });
2543
- console.log((0, import_agent_runtime6.formatKeywordReport)(result));
3110
+ console.log((0, import_agent_runtime7.formatKeywordReport)(result));
2544
3111
  const allKeywords = [...result.quickWins, ...result.mediumTerm, ...result.longTerm];
2545
3112
  if (allKeywords.length > 5) {
2546
3113
  console.log("\n\u{1F4DA} Grouping keywords by topic...\n");
2547
- const topicResult = (0, import_agent_runtime6.groupKeywordsByTopic)(allKeywords);
2548
- 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));
2549
3116
  }
2550
3117
  } catch (error) {
2551
3118
  console.log(`
@@ -2608,7 +3175,7 @@ ${result2.teaser.upgradeMessage}`);
2608
3175
  return;
2609
3176
  }
2610
3177
  console.log("\u{1F52C} Running local AI analysis (this may take a minute)...\n");
2611
- const result = await (0, import_agent_runtime6.runAIKeywordResearch)({
3178
+ const result = await (0, import_agent_runtime7.runAIKeywordResearch)({
2612
3179
  url,
2613
3180
  tier: "solo",
2614
3181
  openaiApiKey,
@@ -2727,12 +3294,12 @@ async function keywordsCompetitor(yourUrl, seedKeywords, competitors) {
2727
3294
  }
2728
3295
  console.log("");
2729
3296
  console.log("Analyzing competitor keywords (this may take a moment)...\n");
2730
- const result = await (0, import_agent_runtime6.discoverCompetitorKeywords)(
3297
+ const result = await (0, import_agent_runtime7.discoverCompetitorKeywords)(
2731
3298
  yourDomain,
2732
3299
  seedKeywords,
2733
3300
  competitors
2734
3301
  );
2735
- console.log((0, import_agent_runtime6.formatCompetitorReport)(result));
3302
+ console.log((0, import_agent_runtime7.formatCompetitorReport)(result));
2736
3303
  if (result.missingKeywords.length > 0) {
2737
3304
  console.log("\n\u{1F3AF} ACTION ITEMS:");
2738
3305
  console.log("\u2500".repeat(60));
@@ -2751,9 +3318,9 @@ async function keywordsCompetitor(yourUrl, seedKeywords, competitors) {
2751
3318
  }
2752
3319
 
2753
3320
  // src/commands/content.ts
2754
- var import_agent_runtime7 = require("@rankcli/agent-runtime");
3321
+ var import_agent_runtime8 = require("@rankcli/agent-runtime");
2755
3322
  async function analyzeContent(options) {
2756
- console.log("\n\u{1F4DD} SEO Autopilot - Content Analysis\n");
3323
+ console.log("\n\u{1F4DD} RankCLI - Content Analysis\n");
2757
3324
  const mode = options.mode || "full";
2758
3325
  if (mode === "headline" || options.headline) {
2759
3326
  const headline = options.headline || "";
@@ -2761,12 +3328,12 @@ async function analyzeContent(options) {
2761
3328
  console.log('\u274C Please provide a headline with --headline "Your Headline Here"');
2762
3329
  return;
2763
3330
  }
2764
- const analysis = (0, import_agent_runtime7.analyzeHeadline)(headline);
2765
- 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));
2766
3333
  if (options.keyword) {
2767
3334
  console.log("\n\u{1F4CB} HEADLINE VARIATIONS");
2768
3335
  console.log("\u2500".repeat(60));
2769
- const variations = (0, import_agent_runtime7.generateHeadlineVariations)(options.keyword, [options.keyword]);
3336
+ const variations = (0, import_agent_runtime8.generateHeadlineVariations)(options.keyword, [options.keyword]);
2770
3337
  for (const v of variations.slice(0, 5)) {
2771
3338
  console.log(` \u2022 ${v}`);
2772
3339
  }
@@ -2777,15 +3344,15 @@ async function analyzeContent(options) {
2777
3344
  if (options.url) {
2778
3345
  console.log(`Fetching content from ${options.url}...`);
2779
3346
  try {
2780
- const crawlResult = await (0, import_agent_runtime7.crawlUrl)({ url: options.url });
3347
+ const crawlResult = await (0, import_agent_runtime8.crawlUrl)({ url: options.url });
2781
3348
  if (!crawlResult.success || !crawlResult.data) {
2782
3349
  console.log("\u274C Could not fetch content from URL");
2783
3350
  return;
2784
3351
  }
2785
3352
  const { html } = crawlResult.data;
2786
3353
  const [metaResult, headingsResult] = await Promise.all([
2787
- (0, import_agent_runtime7.extractMeta)({ html, url: options.url }),
2788
- (0, import_agent_runtime7.analyzeHeadings)({ html })
3354
+ (0, import_agent_runtime8.extractMeta)({ html, url: options.url }),
3355
+ (0, import_agent_runtime8.analyzeHeadings)({ html })
2789
3356
  ]);
2790
3357
  const meta = metaResult.success ? metaResult.data : void 0;
2791
3358
  const headingStructure = headingsResult.success ? headingsResult.data : [];
@@ -2798,15 +3365,15 @@ async function analyzeContent(options) {
2798
3365
  Analyzing: "${title || options.url}"
2799
3366
  `);
2800
3367
  if (mode === "full" || mode === "readability") {
2801
- const readability = (0, import_agent_runtime7.analyzeReadability)(bodyText);
2802
- 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));
2803
3370
  }
2804
3371
  if (mode === "full" && title) {
2805
- const headlineAnalysis = (0, import_agent_runtime7.analyzeHeadline)(title);
2806
- 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));
2807
3374
  }
2808
3375
  if (options.keyword && (mode === "full" || mode === "density")) {
2809
- const densityAnalysis = (0, import_agent_runtime7.analyzeKeywordDensity)(options.keyword, {
3376
+ const densityAnalysis = (0, import_agent_runtime8.analyzeKeywordDensity)(options.keyword, {
2810
3377
  title,
2811
3378
  h1,
2812
3379
  headings,
@@ -2814,231 +3381,59 @@ Analyzing: "${title || options.url}"
2814
3381
  firstParagraph: paragraphs[0],
2815
3382
  lastParagraph: paragraphs[paragraphs.length - 1]
2816
3383
  });
2817
- console.log((0, import_agent_runtime7.formatKeywordDensityReport)(densityAnalysis));
3384
+ console.log((0, import_agent_runtime8.formatKeywordDensityReport)(densityAnalysis));
2818
3385
  }
2819
3386
  if (options.keyword && (mode === "full" || mode === "snippet")) {
2820
- const snippetAnalysis = (0, import_agent_runtime7.analyzeFeaturedSnippetPotential)(
3387
+ const snippetAnalysis = (0, import_agent_runtime8.analyzeFeaturedSnippetPotential)(
2821
3388
  options.keyword,
2822
3389
  bodyText,
2823
3390
  headings
2824
3391
  );
2825
- console.log((0, import_agent_runtime7.formatFeaturedSnippetReport)(snippetAnalysis));
3392
+ console.log((0, import_agent_runtime8.formatFeaturedSnippetReport)(snippetAnalysis));
2826
3393
  }
2827
3394
  if (options.keyword) {
2828
- const intentAnalysis = (0, import_agent_runtime7.classifyIntent)(options.keyword);
2829
- console.log((0, import_agent_runtime7.formatIntentReport)(options.keyword, intentAnalysis));
3395
+ const intentAnalysis = (0, import_agent_runtime8.classifyIntent)(options.keyword);
3396
+ console.log((0, import_agent_runtime8.formatIntentReport)(options.keyword, intentAnalysis));
2830
3397
  }
2831
3398
  console.log("\n\u{1F4CA} CONTENT SUMMARY");
2832
3399
  console.log("\u2500".repeat(60));
2833
- console.log(` Title: ${title || "(none)"}`);
2834
- console.log(` H1: ${h1 || "(none)"}`);
2835
- console.log(` Word Count: ${bodyText.split(/\s+/).length}`);
2836
- console.log(` Headings: ${headings.length}`);
2837
- console.log("");
2838
- } catch (error) {
2839
- console.log(`
2840
- \u274C Error: ${error instanceof Error ? error.message : "Unknown error"}
2841
- `);
2842
- }
2843
- } else {
2844
- console.log("Usage:");
2845
- console.log(" seo content -u <url> Full content analysis");
2846
- console.log(' seo content -u <url> -k "keyword" Analyze with target keyword');
2847
- console.log(' seo content --headline "Your Title" Analyze headline only');
2848
- console.log(" seo content -u <url> --mode readability Readability only");
2849
- console.log(' seo content -u <url> -k "kw" --mode density Keyword density only');
2850
- console.log(' seo content -u <url> -k "kw" --mode snippet Featured snippet analysis');
2851
- console.log("");
2852
- }
2853
- }
2854
- async function analyzeIntent(keyword) {
2855
- console.log("\n\u{1F50D} Search Intent Analysis\n");
2856
- const analysis = (0, import_agent_runtime7.classifyIntent)(keyword);
2857
- console.log((0, import_agent_runtime7.formatIntentReport)(keyword, analysis));
2858
- }
2859
- function extractBodyText(html) {
2860
- let text = html.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "");
2861
- text = text.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
2862
- text = text.replace(/<[^>]+>/g, " ");
2863
- text = text.replace(/&nbsp;/g, " ");
2864
- text = text.replace(/&amp;/g, "&");
2865
- text = text.replace(/&lt;/g, "<");
2866
- text = text.replace(/&gt;/g, ">");
2867
- text = text.replace(/&quot;/g, '"');
2868
- text = text.replace(/\s+/g, " ").trim();
2869
- return text;
2870
- }
2871
-
2872
- // src/commands/setup.ts
2873
- var readline2 = __toESM(require("readline"));
2874
- var fs2 = __toESM(require("fs"));
2875
- var path2 = __toESM(require("path"));
2876
- var import_agent_runtime8 = require("@rankcli/agent-runtime");
2877
- async function setup(options) {
2878
- console.log("\n\u{1F527} SEO Autopilot - Setup\n");
2879
- const projectPath = process.cwd();
2880
- if (!fs2.existsSync(path2.join(projectPath, "package.json")) && !fs2.existsSync(path2.join(projectPath, "index.html"))) {
2881
- console.log("\u26A0\uFE0F No package.json or index.html found. Are you in a web project directory?\n");
2882
- }
2883
- if (options.all || !options.ga4 && !options.gsc && !options.githubAction) {
2884
- await interactiveSetup(projectPath);
2885
- return;
2886
- }
2887
- if (options.ga4) {
2888
- await setupGA4(projectPath, options.ga4);
2889
- }
2890
- if (options.gsc) {
2891
- await setupGSC(projectPath, options.gsc);
2892
- }
2893
- if (options.githubAction) {
2894
- await setupGitHubAction(projectPath, options.schedule || "weekly");
2895
- }
2896
- }
2897
- async function interactiveSetup(projectPath) {
2898
- const rl = readline2.createInterface({
2899
- input: process.stdin,
2900
- output: process.stdout
2901
- });
2902
- const ask2 = (question) => {
2903
- return new Promise((resolve) => {
2904
- rl.question(question, (answer) => {
2905
- resolve(answer.trim());
2906
- });
2907
- });
2908
- };
2909
- try {
2910
- console.log("This wizard will help you set up SEO tracking for your project.\n");
2911
- console.log("\u2500".repeat(60));
2912
- console.log("\u{1F4CA} STEP 1: Google Analytics 4\n");
2913
- console.log("GA4 tracks visitor behavior on your site.");
2914
- console.log("Get your Measurement ID from: https://analytics.google.com");
2915
- console.log("Go to: Admin > Data Streams > Your Stream > Measurement ID\n");
2916
- const ga4Id = await ask2("Enter GA4 Measurement ID (G-XXXXXXXXXX) or press Enter to skip: ");
2917
- if (ga4Id && ga4Id.startsWith("G-")) {
2918
- await setupGA4(projectPath, ga4Id);
2919
- } else if (ga4Id) {
2920
- console.log('\u26A0\uFE0F Invalid format. GA4 IDs start with "G-". Skipping.\n');
2921
- } else {
2922
- console.log("Skipping GA4 setup.\n");
2923
- }
2924
- console.log("\u2500".repeat(60));
2925
- console.log("\u{1F50D} STEP 2: Google Search Console\n");
2926
- console.log("GSC shows how Google sees your site and what queries you rank for.");
2927
- console.log("Get your verification code from: https://search.google.com/search-console");
2928
- console.log("Go to: Settings > Ownership verification > HTML tag\n");
2929
- const gscCode = await ask2("Enter GSC verification code or press Enter to skip: ");
2930
- if (gscCode && gscCode.length > 10) {
2931
- await setupGSC(projectPath, gscCode);
2932
- } else if (gscCode) {
2933
- console.log("\u26A0\uFE0F Code seems too short. Skipping.\n");
2934
- } else {
2935
- console.log("Skipping GSC verification.\n");
2936
- }
2937
- console.log("\u2500".repeat(60));
2938
- console.log("\u{1F916} STEP 3: Automated SEO Monitoring\n");
2939
- console.log("A GitHub Action can automatically check your site's SEO on a schedule.");
2940
- console.log("It creates issues with reports and can auto-fix some problems.\n");
2941
- const setupAction = await ask2("Set up GitHub Action? (y/n): ");
2942
- if (setupAction.toLowerCase() === "y" || setupAction.toLowerCase() === "yes") {
2943
- console.log("\nHow often should the check run?");
2944
- console.log(" 1. Weekly (recommended for most sites)");
2945
- console.log(" 2. Daily (for high-traffic sites)");
2946
- console.log(" 3. Monthly (for low-traffic sites)\n");
2947
- const scheduleChoice = await ask2("Enter choice (1-3): ");
2948
- const scheduleMap = {
2949
- "1": "weekly",
2950
- "2": "daily",
2951
- "3": "monthly"
2952
- };
2953
- const schedule = scheduleMap[scheduleChoice] || "weekly";
2954
- const siteUrl = await ask2("Enter your site URL (https://example.com): ");
2955
- if (siteUrl) {
2956
- const syncChoice = await ask2("Send scheduled audits to your RankCLI dashboard? (y/n): ");
2957
- const syncToDashboard = syncChoice.toLowerCase() === "y" || syncChoice.toLowerCase() === "yes";
2958
- await setupGitHubAction(projectPath, schedule, siteUrl, syncToDashboard);
2959
- }
2960
- } else {
2961
- console.log("Skipping GitHub Action setup.\n");
2962
- }
2963
- console.log("\u2500".repeat(60));
2964
- console.log("\u2705 Setup complete!\n");
2965
- console.log("Next steps:");
2966
- console.log(" 1. Commit the changes to your repository");
2967
- console.log(" 2. If using GitHub Actions, add required secrets (see .github/SEO_SETUP.md)");
2968
- console.log(" 3. Deploy your site to apply tracking code changes");
2969
- console.log(" 4. Verify GSC ownership and GA4 data collection\n");
2970
- rl.close();
2971
- } catch (error) {
2972
- console.log(`
2973
- \u274C Error: ${error instanceof Error ? error.message : "Unknown error"}
2974
- `);
2975
- rl.close();
2976
- }
2977
- }
2978
- async function setupGA4(projectPath, measurementId) {
2979
- console.log(`Setting up Google Analytics 4 (${measurementId})...`);
2980
- const result = await (0, import_agent_runtime8.injectGA4)(projectPath, { measurementId });
2981
- if (result.success) {
2982
- console.log(`\u2705 ${result.message}`);
2983
- if (result.file) {
2984
- console.log(` Modified: ${result.file}`);
2985
- }
2986
- } else {
2987
- console.log(`\u26A0\uFE0F ${result.message}`);
2988
- if (result.code) {
2989
- console.log("\nManually add this code to your HTML <head>:\n");
2990
- console.log(result.code);
2991
- }
2992
- }
2993
- const envExamplePath = path2.join(projectPath, ".env.example");
2994
- const envContent = (0, import_agent_runtime8.generateGA4EnvTemplate)();
2995
- if (fs2.existsSync(envExamplePath)) {
2996
- const existing = fs2.readFileSync(envExamplePath, "utf-8");
2997
- if (!existing.includes("GA4_MEASUREMENT_ID")) {
2998
- fs2.appendFileSync(envExamplePath, "\n" + envContent);
2999
- console.log(" Updated: .env.example");
3000
- }
3001
- } else {
3002
- fs2.writeFileSync(envExamplePath, envContent);
3003
- console.log(" Created: .env.example");
3004
- }
3005
- console.log("");
3006
- }
3007
- async function setupGSC(projectPath, verificationCode) {
3008
- console.log("Setting up Google Search Console verification...");
3009
- const result = await (0, import_agent_runtime8.injectGSCVerification)(projectPath, verificationCode);
3010
- if (result.success) {
3011
- console.log(`\u2705 ${result.message}`);
3012
- if (result.file) {
3013
- console.log(` Modified: ${result.file}`);
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(`
3407
+ \u274C Error: ${error instanceof Error ? error.message : "Unknown error"}
3408
+ `);
3014
3409
  }
3015
3410
  } else {
3016
- 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("");
3017
3419
  }
3018
- console.log("");
3019
3420
  }
3020
- async function setupGitHubAction(projectPath, schedule, siteUrl, syncToDashboard) {
3021
- console.log(`Setting up GitHub Action (${schedule} schedule)...`);
3022
- const config2 = {
3023
- schedule,
3024
- siteUrl: siteUrl || "https://example.com",
3025
- features: {
3026
- audit: true,
3027
- autoFix: true,
3028
- createIssues: true,
3029
- createPRs: true
3030
- },
3031
- syncToDashboard
3032
- };
3033
- const { files, instructions } = (0, import_agent_runtime8.writeGitHubActionFiles)(projectPath, config2);
3034
- console.log("\u2705 GitHub Action files created:");
3035
- for (const file of files) {
3036
- console.log(` - ${path2.relative(projectPath, file)}`);
3037
- }
3038
- 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));
3039
3425
  }
3040
- function showGSCInstructions() {
3041
- 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;
3042
3437
  }
3043
3438
 
3044
3439
  // src/commands/alerts.ts
@@ -3063,7 +3458,7 @@ async function askYesNo(rl, question, defaultYes = true) {
3063
3458
  return answer.toLowerCase().startsWith("y");
3064
3459
  }
3065
3460
  async function alerts(options) {
3066
- console.log("\n\u{1F514} SEO Autopilot - Alert Configuration\n");
3461
+ console.log("\n\u{1F514} RankCLI - Alert Configuration\n");
3067
3462
  const rl = readline3.createInterface({
3068
3463
  input: process.stdin,
3069
3464
  output: process.stdout
@@ -3206,245 +3601,8 @@ async function testWebhook(channel, webhookUrl) {
3206
3601
  }
3207
3602
  }
3208
3603
 
3209
- // src/commands/auth.ts
3210
- var import_chalk6 = __toESM(require("chalk"));
3211
- var import_ora5 = __toESM(require("ora"));
3212
- var import_inquirer = __toESM(require("inquirer"));
3213
- var import_open = __toESM(require("open"));
3214
- async function browserLogin(spinner) {
3215
- spinner.start("Starting login...");
3216
- let start;
3217
- try {
3218
- const res = await fetch(`${getApiUrl()}/functions/v1/mcp-connect-start`, { method: "POST" });
3219
- if (!res.ok) throw new Error(`status ${res.status}`);
3220
- start = await res.json();
3221
- } catch (err) {
3222
- spinner.fail("Could not start login");
3223
- console.log(import_chalk6.default.red(err instanceof Error ? err.message : "Network error"));
3224
- return;
3225
- }
3226
- spinner.text = "Opening browser to approve...";
3227
- try {
3228
- await (0, import_open.default)(start.url);
3229
- } catch {
3230
- }
3231
- console.log(import_chalk6.default.dim(`
3232
- If a browser didn't open, visit:
3233
- ${start.url}
3234
- `));
3235
- const deadline = Date.now() + start.expiresInSeconds * 1e3;
3236
- spinner.text = "Waiting for approval in your browser...";
3237
- while (Date.now() < deadline) {
3238
- await new Promise((resolve) => setTimeout(resolve, 2e3));
3239
- let status;
3240
- try {
3241
- const res = await fetch(`${getApiUrl()}/functions/v1/mcp-connect-status`, {
3242
- method: "POST",
3243
- headers: { "Content-Type": "application/json" },
3244
- body: JSON.stringify({ connectId: start.connectId })
3245
- });
3246
- status = await res.json();
3247
- } catch {
3248
- continue;
3249
- }
3250
- if (status.status === "claimed" && status.apiKey) {
3251
- saveApiKey(status.apiKey, status.apiKeyName, status.email);
3252
- await fetchAndSaveSubscription();
3253
- spinner.succeed(`Logged in as ${import_chalk6.default.green(status.email)}`);
3254
- return;
3255
- }
3256
- if (status.status === "expired" || status.status === "not_found") {
3257
- spinner.fail("Login link expired");
3258
- console.log(import_chalk6.default.dim('Run "rankcli login" again.'));
3259
- return;
3260
- }
3261
- }
3262
- spinner.fail("Timed out waiting for approval");
3263
- console.log(import_chalk6.default.dim(`Run "rankcli login" again when you're ready.`));
3264
- }
3265
- async function login(options) {
3266
- if (isLoggedIn()) {
3267
- const { email } = getUserInfo();
3268
- console.log(import_chalk6.default.yellow(`Already logged in as ${email}`));
3269
- console.log(import_chalk6.default.dim('Run "rankcli logout" to sign out first.'));
3270
- return;
3271
- }
3272
- const spinner = (0, import_ora5.default)();
3273
- try {
3274
- if (options.token) {
3275
- const apiKey = options.token;
3276
- if (!apiKey.startsWith("rankcli_")) {
3277
- console.log(import_chalk6.default.red('Invalid API key format. Keys should start with "rankcli_"'));
3278
- return;
3279
- }
3280
- spinner.start("Validating API key...");
3281
- const result = await validateApiKey(apiKey);
3282
- if (!result.valid) {
3283
- spinner.fail("Invalid API key");
3284
- console.log(import_chalk6.default.red(`
3285
- ${result.error}`));
3286
- return;
3287
- }
3288
- saveApiKey(apiKey);
3289
- if (result.subscription) {
3290
- saveSubscription({
3291
- planId: result.subscription.planId,
3292
- planName: result.subscription.planName,
3293
- sitesLimit: result.subscription.sitesLimit
3294
- });
3295
- }
3296
- spinner.succeed(`Authenticated via API key`);
3297
- console.log(import_chalk6.default.dim(`
3298
- Email: ${result.user?.email}`));
3299
- console.log(import_chalk6.default.dim(`Plan: ${result.subscription?.planName || "Free"}`));
3300
- return;
3301
- }
3302
- if (options.browser !== false && !options.email) {
3303
- await browserLogin(spinner);
3304
- } else {
3305
- let email = options.email;
3306
- if (!email) {
3307
- const answers = await import_inquirer.default.prompt([
3308
- {
3309
- type: "input",
3310
- name: "email",
3311
- message: "Email:",
3312
- validate: (input) => {
3313
- if (!input.includes("@")) return "Please enter a valid email";
3314
- return true;
3315
- }
3316
- }
3317
- ]);
3318
- email = answers.email;
3319
- }
3320
- const { password } = await import_inquirer.default.prompt([
3321
- {
3322
- type: "password",
3323
- name: "password",
3324
- message: "Password:",
3325
- mask: "*"
3326
- }
3327
- ]);
3328
- spinner.start("Signing in...");
3329
- const supabase = getSupabaseClient();
3330
- const { data, error } = await supabase.auth.signInWithPassword({
3331
- email,
3332
- password
3333
- });
3334
- if (error) {
3335
- spinner.fail("Login failed");
3336
- console.log(import_chalk6.default.red(`
3337
- ${error.message}`));
3338
- if (error.message.includes("Invalid login")) {
3339
- console.log(import_chalk6.default.dim("\nDon't have an account? Sign up at:"));
3340
- console.log(import_chalk6.default.cyan(` ${WEB_APP_URL}/signup`));
3341
- }
3342
- return;
3343
- }
3344
- if (!data.session) {
3345
- spinner.fail("No session returned");
3346
- return;
3347
- }
3348
- saveSession(data.session);
3349
- await fetchAndSaveSubscription();
3350
- spinner.succeed(`Logged in as ${import_chalk6.default.green(data.user?.email)}`);
3351
- const { planName, sitesLimit } = getUserInfo();
3352
- console.log(import_chalk6.default.dim(`
3353
- Plan: ${planName} (${sitesLimit} site${sitesLimit !== 1 ? "s" : ""})`));
3354
- }
3355
- } catch (err) {
3356
- spinner.fail("Login failed");
3357
- console.error(import_chalk6.default.red(err instanceof Error ? err.message : "Unknown error"));
3358
- }
3359
- }
3360
- async function logout() {
3361
- if (!isLoggedIn()) {
3362
- console.log(import_chalk6.default.yellow("Not logged in"));
3363
- return;
3364
- }
3365
- const { email } = getUserInfo();
3366
- const spinner = (0, import_ora5.default)("Signing out...").start();
3367
- try {
3368
- const supabase = getSupabaseClient();
3369
- await supabase.auth.signOut();
3370
- clearCredentials();
3371
- spinner.succeed(`Logged out from ${import_chalk6.default.dim(email)}`);
3372
- } catch (err) {
3373
- spinner.fail("Logout failed");
3374
- console.error(import_chalk6.default.red(err instanceof Error ? err.message : "Unknown error"));
3375
- }
3376
- }
3377
- async function whoami() {
3378
- if (!isLoggedIn()) {
3379
- console.log(import_chalk6.default.yellow("Not logged in"));
3380
- console.log(import_chalk6.default.dim('\nRun "rankcli login" to sign in.'));
3381
- console.log(import_chalk6.default.dim(`Or sign up at: ${WEB_APP_URL}/signup`));
3382
- return;
3383
- }
3384
- const spinner = (0, import_ora5.default)("Checking account...").start();
3385
- try {
3386
- if (isApiKeyAuth()) {
3387
- const apiKey = getApiKey();
3388
- if (apiKey) {
3389
- const result = await validateApiKey(apiKey);
3390
- if (result.valid && result.subscription) {
3391
- saveSubscription({
3392
- planId: result.subscription.planId,
3393
- planName: result.subscription.planName,
3394
- sitesLimit: result.subscription.sitesLimit
3395
- });
3396
- }
3397
- }
3398
- } else {
3399
- await fetchAndSaveSubscription();
3400
- }
3401
- const { email, planName, sitesLimit } = getUserInfo();
3402
- const authMethod = isApiKeyAuth() ? "API Key" : "Session";
3403
- spinner.stop();
3404
- console.log(import_chalk6.default.bold("\nRankCLI Account"));
3405
- console.log("\u2500".repeat(40));
3406
- console.log(` Email: ${import_chalk6.default.cyan(email || "N/A")}`);
3407
- console.log(` Plan: ${import_chalk6.default.green(planName)}`);
3408
- console.log(` Sites: ${sitesLimit} allowed`);
3409
- console.log(` Auth: ${import_chalk6.default.dim(authMethod)}`);
3410
- console.log("\u2500".repeat(40));
3411
- console.log(import_chalk6.default.dim(`
3412
- Manage at: ${WEB_APP_URL}/account`));
3413
- } catch (err) {
3414
- spinner.fail("Failed to fetch account info");
3415
- console.error(import_chalk6.default.red(err instanceof Error ? err.message : "Unknown error"));
3416
- }
3417
- }
3418
- async function fetchAndSaveSubscription() {
3419
- const supabase = await getAuthenticatedClient();
3420
- if (!supabase) return;
3421
- const { userId } = getUserInfo();
3422
- if (!userId) return;
3423
- const { data: subscription } = await supabase.from("user_subscriptions").select(`
3424
- plan:subscription_plans (
3425
- plan_id,
3426
- name,
3427
- sites_limit
3428
- )
3429
- `).eq("user_id", userId).single();
3430
- if (subscription?.plan) {
3431
- const plan = subscription.plan;
3432
- saveSubscription({
3433
- planId: plan.plan_id,
3434
- planName: plan.name,
3435
- sitesLimit: plan.sites_limit
3436
- });
3437
- } else {
3438
- saveSubscription({
3439
- planId: void 0,
3440
- planName: "Free",
3441
- sitesLimit: 1
3442
- });
3443
- }
3444
- }
3445
-
3446
3604
  // src/version.ts
3447
- var VERSION = "0.0.24";
3605
+ var VERSION = "0.0.26";
3448
3606
 
3449
3607
  // src/index.ts
3450
3608
  var program = new import_commander.Command();
@@ -3452,9 +3610,9 @@ program.name("rankcli").description("RankCLI - Ship code, get ranked. SEO meets
3452
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);
3453
3611
  program.command("logout").description("Log out of your RankCLI account").action(logout);
3454
3612
  program.command("whoami").description("Show current logged in user").action(whoami);
3455
- 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);
3456
3614
  program.command("analyze").description("AI analyzes your codebase for SEO opportunities").option("-v, --verbose", "Show detailed output").action(analyze);
3457
- 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);
3458
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);
3459
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) => {
3460
3618
  const result = await fix({