@rankcli/cli 0.0.18 → 0.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -10
- package/dist/index.js +325 -131
- package/dist/index.mjs +325 -132
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -262,7 +262,7 @@ async function analyze(options) {
|
|
|
262
262
|
// src/commands/audit.ts
|
|
263
263
|
import { runFullAudit, discoverPagesFromLinks, frameworks } from "@rankcli/agent-runtime";
|
|
264
264
|
import ora from "ora";
|
|
265
|
-
import
|
|
265
|
+
import chalk2 from "chalk";
|
|
266
266
|
|
|
267
267
|
// src/lib/config.ts
|
|
268
268
|
import Conf from "conf";
|
|
@@ -457,6 +457,174 @@ async function getAuthenticatedClient() {
|
|
|
457
457
|
return supabase;
|
|
458
458
|
}
|
|
459
459
|
|
|
460
|
+
// src/lib/upsell.ts
|
|
461
|
+
import chalk from "chalk";
|
|
462
|
+
var WEB_APP_URL2 = "https://rankcli.dev";
|
|
463
|
+
var TIER_LIMITS = {
|
|
464
|
+
free_anon: {
|
|
465
|
+
checks: 50,
|
|
466
|
+
sites: 0,
|
|
467
|
+
domains_per_month: 1,
|
|
468
|
+
features: { geo: false, auto_fix: false, github: false, ai: false }
|
|
469
|
+
},
|
|
470
|
+
free: {
|
|
471
|
+
checks: 100,
|
|
472
|
+
sites: 1,
|
|
473
|
+
domains_per_month: 2,
|
|
474
|
+
features: { geo: "basic", auto_fix: false, github: false, ai: false }
|
|
475
|
+
},
|
|
476
|
+
solo: {
|
|
477
|
+
checks: 280,
|
|
478
|
+
sites: 3,
|
|
479
|
+
domains_per_month: 5,
|
|
480
|
+
features: { geo: true, auto_fix: true, github: false, ai: true }
|
|
481
|
+
},
|
|
482
|
+
pro: {
|
|
483
|
+
checks: 280,
|
|
484
|
+
sites: 10,
|
|
485
|
+
domains_per_month: 15,
|
|
486
|
+
features: { geo: true, auto_fix: true, github: true, ai: true }
|
|
487
|
+
},
|
|
488
|
+
agency: {
|
|
489
|
+
checks: 280,
|
|
490
|
+
sites: 50,
|
|
491
|
+
domains_per_month: null,
|
|
492
|
+
// unlimited
|
|
493
|
+
features: { geo: true, auto_fix: true, github: true, ai: true }
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
var UPSELL_MESSAGES = {
|
|
497
|
+
// After free audit completes
|
|
498
|
+
free_audit_complete: () => [
|
|
499
|
+
"",
|
|
500
|
+
chalk.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"),
|
|
501
|
+
chalk.yellow.bold(" \u{1F680} Get 2x more checks + audit history"),
|
|
502
|
+
chalk.dim(" Create a free account to unlock 100 checks and save your results."),
|
|
503
|
+
"",
|
|
504
|
+
chalk.cyan(` \u2192 rankcli login`),
|
|
505
|
+
chalk.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"),
|
|
506
|
+
""
|
|
507
|
+
],
|
|
508
|
+
// When checks limit is reached
|
|
509
|
+
checks_limit: (ctx) => [
|
|
510
|
+
"",
|
|
511
|
+
chalk.yellow(`\u26A0\uFE0F Running ${ctx.current || 50} of ${TIER_LIMITS.solo.checks} available checks`),
|
|
512
|
+
chalk.dim(` Unlock all ${TIER_LIMITS.solo.checks}+ checks with Solo ($9/mo)`),
|
|
513
|
+
chalk.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=cli_checks`),
|
|
514
|
+
""
|
|
515
|
+
],
|
|
516
|
+
// When domain limit is reached
|
|
517
|
+
domain_limit: (ctx) => [
|
|
518
|
+
"",
|
|
519
|
+
chalk.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"),
|
|
520
|
+
chalk.red.bold(" \u2717 Domain limit reached"),
|
|
521
|
+
chalk.dim(` Your ${ctx.tier} plan allows ${ctx.max} domains per month.`),
|
|
522
|
+
"",
|
|
523
|
+
chalk.dim(" Upgrade to audit more sites:"),
|
|
524
|
+
chalk.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=cli_domain_limit`),
|
|
525
|
+
chalk.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"),
|
|
526
|
+
""
|
|
527
|
+
],
|
|
528
|
+
// When GEO features are limited
|
|
529
|
+
geo_limited: (ctx) => [
|
|
530
|
+
"",
|
|
531
|
+
chalk.yellow("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"),
|
|
532
|
+
chalk.yellow.bold(" \u{1F916} Full GEO analysis requires Solo"),
|
|
533
|
+
chalk.dim(" You're seeing basic AI crawler access checks."),
|
|
534
|
+
chalk.dim(" Full analysis includes: LLM signals, citation readiness, content structure"),
|
|
535
|
+
"",
|
|
536
|
+
chalk.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=cli_geo`),
|
|
537
|
+
chalk.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"),
|
|
538
|
+
""
|
|
539
|
+
],
|
|
540
|
+
// When auto-fix is attempted without access
|
|
541
|
+
auto_fix_blocked: () => [
|
|
542
|
+
"",
|
|
543
|
+
chalk.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"),
|
|
544
|
+
chalk.red.bold(" \u2717 Auto-fix requires Solo plan"),
|
|
545
|
+
chalk.dim(" Apply framework-specific SEO fixes directly to your codebase."),
|
|
546
|
+
"",
|
|
547
|
+
chalk.dim(" Solo ($9/mo) includes:"),
|
|
548
|
+
chalk.dim(" \u2022 Local auto-fix via CLI"),
|
|
549
|
+
chalk.dim(" \u2022 25+ framework support"),
|
|
550
|
+
chalk.dim(" \u2022 Full 280+ checks"),
|
|
551
|
+
"",
|
|
552
|
+
chalk.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=cli_autofix`),
|
|
553
|
+
chalk.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"),
|
|
554
|
+
""
|
|
555
|
+
],
|
|
556
|
+
// When GitHub integration is attempted without access
|
|
557
|
+
github_blocked: () => [
|
|
558
|
+
"",
|
|
559
|
+
chalk.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"),
|
|
560
|
+
chalk.red.bold(" \u2717 GitHub auto-fix PRs require Pro"),
|
|
561
|
+
chalk.dim(" Automatically create fix PRs when SEO issues are detected."),
|
|
562
|
+
"",
|
|
563
|
+
chalk.dim(" Pro ($29/mo) includes:"),
|
|
564
|
+
chalk.dim(" \u2022 GitHub App integration"),
|
|
565
|
+
chalk.dim(" \u2022 Auto-fix PRs"),
|
|
566
|
+
chalk.dim(" \u2022 Daily scheduled audits"),
|
|
567
|
+
chalk.dim(" \u2022 Slack/Discord alerts"),
|
|
568
|
+
"",
|
|
569
|
+
chalk.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=cli_github`),
|
|
570
|
+
chalk.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"),
|
|
571
|
+
""
|
|
572
|
+
],
|
|
573
|
+
// Rate limit exceeded
|
|
574
|
+
rate_limit: (ctx) => [
|
|
575
|
+
"",
|
|
576
|
+
chalk.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"),
|
|
577
|
+
chalk.red.bold(" \u2717 API rate limit exceeded"),
|
|
578
|
+
chalk.dim(` Your ${ctx.tier} plan allows ${ctx.max} requests/day.`),
|
|
579
|
+
"",
|
|
580
|
+
chalk.dim(" Need more? Upgrade for higher limits:"),
|
|
581
|
+
chalk.dim(" \u2022 Solo: 100 req/day"),
|
|
582
|
+
chalk.dim(" \u2022 Pro: 1,000 req/day"),
|
|
583
|
+
chalk.dim(" \u2022 Agency: 10,000 req/day"),
|
|
584
|
+
"",
|
|
585
|
+
chalk.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=cli_rate_limit`),
|
|
586
|
+
chalk.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"),
|
|
587
|
+
""
|
|
588
|
+
],
|
|
589
|
+
// Schedule exceeded (for GitHub Action)
|
|
590
|
+
schedule_exceeded: (ctx) => [
|
|
591
|
+
"",
|
|
592
|
+
chalk.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"),
|
|
593
|
+
chalk.yellow.bold(" \u23F0 Scheduled audit skipped"),
|
|
594
|
+
chalk.dim(` Your ${ctx.tier} plan allows ${ctx.limit} scheduled audits.`),
|
|
595
|
+
"",
|
|
596
|
+
chalk.dim(" Upgrade for more frequent audits:"),
|
|
597
|
+
chalk.dim(" \u2022 Solo: Weekly"),
|
|
598
|
+
chalk.dim(" \u2022 Pro: Daily"),
|
|
599
|
+
chalk.dim(" \u2022 Agency: Hourly"),
|
|
600
|
+
"",
|
|
601
|
+
chalk.cyan(` \u2192 ${WEB_APP_URL2}/pricing?from=schedule_exceeded`),
|
|
602
|
+
chalk.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"),
|
|
603
|
+
""
|
|
604
|
+
],
|
|
605
|
+
// Login prompt (soft)
|
|
606
|
+
login_prompt: () => [
|
|
607
|
+
"",
|
|
608
|
+
chalk.dim(" \u{1F4A1} Tip: Log in to sync results and track progress over time:"),
|
|
609
|
+
chalk.dim(" rankcli login"),
|
|
610
|
+
""
|
|
611
|
+
]
|
|
612
|
+
};
|
|
613
|
+
function showUpsell(messageKey, context) {
|
|
614
|
+
const messageFn = UPSELL_MESSAGES[messageKey];
|
|
615
|
+
if (!messageFn) return;
|
|
616
|
+
const ctx = {
|
|
617
|
+
tier: "free",
|
|
618
|
+
...context
|
|
619
|
+
};
|
|
620
|
+
const lines = messageFn(ctx);
|
|
621
|
+
lines.forEach((line) => console.log(line));
|
|
622
|
+
}
|
|
623
|
+
function hasFeature(tier, feature) {
|
|
624
|
+
const limits = TIER_LIMITS[tier];
|
|
625
|
+
return limits?.features?.[feature] ?? false;
|
|
626
|
+
}
|
|
627
|
+
|
|
460
628
|
// src/commands/audit.ts
|
|
461
629
|
var { detectFramework: detectFramework2, getFrameworkDisplayName, getFrameworkIssues } = frameworks;
|
|
462
630
|
async function fetchHtml(url, timeout = 1e4) {
|
|
@@ -524,7 +692,7 @@ Return JSON with:
|
|
|
524
692
|
}
|
|
525
693
|
const response = await fetch(apiUrl, { method: "POST", headers, body });
|
|
526
694
|
if (!response.ok) {
|
|
527
|
-
console.error(
|
|
695
|
+
console.error(chalk2.yellow(`AI analysis failed: ${response.status}`));
|
|
528
696
|
return null;
|
|
529
697
|
}
|
|
530
698
|
const data = await response.json();
|
|
@@ -534,7 +702,7 @@ Return JSON with:
|
|
|
534
702
|
if (!jsonMatch) return null;
|
|
535
703
|
return JSON.parse(jsonMatch[0]);
|
|
536
704
|
} catch (error) {
|
|
537
|
-
console.error(
|
|
705
|
+
console.error(chalk2.yellow(`AI analysis error: ${error}`));
|
|
538
706
|
return null;
|
|
539
707
|
}
|
|
540
708
|
}
|
|
@@ -553,7 +721,7 @@ function mergeIssues(issueArrays) {
|
|
|
553
721
|
}
|
|
554
722
|
return Array.from(issueMap.values());
|
|
555
723
|
}
|
|
556
|
-
async function syncToCloud(url, report, mergedIssues, pagesAudited, aiAnalysis) {
|
|
724
|
+
async function syncToCloud(url, report, mergedIssues, pagesAudited, aiAnalysis, durationMs, checksRun) {
|
|
557
725
|
const supabase = await getAuthenticatedClient();
|
|
558
726
|
if (!supabase) return null;
|
|
559
727
|
const { userId, sitesLimit } = getUserInfo();
|
|
@@ -569,9 +737,9 @@ async function syncToCloud(url, report, mergedIssues, pagesAudited, aiAnalysis)
|
|
|
569
737
|
const { count } = await supabase.from("projects").select("id", { count: "exact", head: true }).eq("user_id", userId).eq("is_active", true);
|
|
570
738
|
const limit = sitesLimit ?? 1;
|
|
571
739
|
if (count !== null && count >= limit) {
|
|
572
|
-
console.log(
|
|
740
|
+
console.log(chalk2.yellow(`
|
|
573
741
|
\u26A0\uFE0F Project limit reached (${count}/${limit})`));
|
|
574
|
-
console.log(
|
|
742
|
+
console.log(chalk2.dim(` Upgrade at: ${WEB_APP_URL}/account`));
|
|
575
743
|
return null;
|
|
576
744
|
}
|
|
577
745
|
const { data: newProject, error: projectError } = await supabase.from("projects").insert({
|
|
@@ -581,7 +749,7 @@ async function syncToCloud(url, report, mergedIssues, pagesAudited, aiAnalysis)
|
|
|
581
749
|
domain
|
|
582
750
|
}).select("id").single();
|
|
583
751
|
if (projectError || !newProject) {
|
|
584
|
-
console.error(
|
|
752
|
+
console.error(chalk2.dim("Failed to create project"));
|
|
585
753
|
return null;
|
|
586
754
|
}
|
|
587
755
|
projectId = newProject.id;
|
|
@@ -595,6 +763,7 @@ async function syncToCloud(url, report, mergedIssues, pagesAudited, aiAnalysis)
|
|
|
595
763
|
performance: report.healthScore?.performance || 0,
|
|
596
764
|
security: report.healthScore?.security || 0
|
|
597
765
|
};
|
|
766
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
598
767
|
const { data: audit2, error: auditError } = await supabase.from("audits").insert({
|
|
599
768
|
project_id: projectId,
|
|
600
769
|
triggered_by: "cli",
|
|
@@ -603,21 +772,23 @@ async function syncToCloud(url, report, mergedIssues, pagesAudited, aiAnalysis)
|
|
|
603
772
|
score,
|
|
604
773
|
pages_crawled: pagesAudited,
|
|
605
774
|
issues_found: mergedIssues.length,
|
|
606
|
-
checks_run:
|
|
607
|
-
// Approximate
|
|
775
|
+
checks_run: checksRun,
|
|
608
776
|
health_scores: healthScores,
|
|
609
|
-
|
|
777
|
+
started_at: new Date(Date.now() - durationMs).toISOString(),
|
|
778
|
+
completed_at: now,
|
|
779
|
+
duration_ms: durationMs,
|
|
610
780
|
summary: {
|
|
611
781
|
errors: mergedIssues.filter((i) => i.severity === "error").length,
|
|
612
782
|
warnings: mergedIssues.filter((i) => i.severity === "warning").length,
|
|
613
783
|
notices: mergedIssues.filter((i) => i.severity === "notice").length,
|
|
614
784
|
pagesAudited,
|
|
785
|
+
checksRun,
|
|
615
786
|
source: "cli"
|
|
616
787
|
},
|
|
617
788
|
ai_analysis: aiAnalysis
|
|
618
789
|
}).select("id").single();
|
|
619
790
|
if (auditError || !audit2) {
|
|
620
|
-
console.error(
|
|
791
|
+
console.error(chalk2.dim("Failed to save audit"));
|
|
621
792
|
return null;
|
|
622
793
|
}
|
|
623
794
|
if (mergedIssues.length > 0) {
|
|
@@ -638,12 +809,14 @@ async function syncToCloud(url, report, mergedIssues, pagesAudited, aiAnalysis)
|
|
|
638
809
|
await supabase.from("audit_issues").insert(issueRecords);
|
|
639
810
|
}
|
|
640
811
|
await supabase.from("projects").update({
|
|
641
|
-
last_audit_at:
|
|
642
|
-
last_audit_score: score
|
|
812
|
+
last_audit_at: now,
|
|
813
|
+
last_audit_score: score,
|
|
814
|
+
last_scheduled_audit_at: now,
|
|
815
|
+
scheduled_audit_source: "cli"
|
|
643
816
|
}).eq("id", projectId);
|
|
644
817
|
return { projectId, auditId: audit2.id };
|
|
645
818
|
} catch (error) {
|
|
646
|
-
console.error(
|
|
819
|
+
console.error(chalk2.dim("Cloud sync failed"));
|
|
647
820
|
return null;
|
|
648
821
|
}
|
|
649
822
|
}
|
|
@@ -664,8 +837,8 @@ async function audit(options) {
|
|
|
664
837
|
console.log(" --ai-key <key> API key (or set OPENAI_API_KEY/ANTHROPIC_API_KEY)");
|
|
665
838
|
console.log(" --output json Output as JSON");
|
|
666
839
|
if (!loggedIn) {
|
|
667
|
-
console.log(
|
|
668
|
-
console.log(
|
|
840
|
+
console.log(chalk2.dim("\nTip: Log in to sync results to the web dashboard:"));
|
|
841
|
+
console.log(chalk2.dim(" rankcli login"));
|
|
669
842
|
}
|
|
670
843
|
console.log("");
|
|
671
844
|
return;
|
|
@@ -749,12 +922,17 @@ async function audit(options) {
|
|
|
749
922
|
aiSpinner.warn("AI analysis failed or returned no results");
|
|
750
923
|
}
|
|
751
924
|
} else if (enableAI && !aiKey) {
|
|
752
|
-
console.log(
|
|
753
|
-
console.log(
|
|
925
|
+
console.log(chalk2.yellow("\n\u26A0\uFE0F AI analysis requested but no API key provided"));
|
|
926
|
+
console.log(chalk2.dim(" Set OPENAI_API_KEY or ANTHROPIC_API_KEY, or use --ai-key"));
|
|
754
927
|
}
|
|
755
928
|
const totalTime = Date.now() - startTime;
|
|
756
929
|
const frameworkName = detectedFramework.framework !== "unknown" ? getFrameworkDisplayName(detectedFramework.framework) : null;
|
|
930
|
+
const actualChecksRun = (primaryReport.summary?.passed || 0) + (primaryReport.summary?.errors || 0) + (primaryReport.summary?.warnings || 0) || 200;
|
|
757
931
|
if (options.output === "json") {
|
|
932
|
+
let syncResult = null;
|
|
933
|
+
if (loggedIn) {
|
|
934
|
+
syncResult = await syncToCloud(url, primaryReport, mergedIssues, successCount, aiAnalysis, totalTime, actualChecksRun);
|
|
935
|
+
}
|
|
758
936
|
const jsonOutput = {
|
|
759
937
|
url,
|
|
760
938
|
score: primaryReport.healthScore?.overall || 0,
|
|
@@ -763,31 +941,37 @@ async function audit(options) {
|
|
|
763
941
|
framework: frameworkName,
|
|
764
942
|
issues: mergedIssues,
|
|
765
943
|
healthScores: primaryReport.healthScore,
|
|
766
|
-
aiAnalysis
|
|
944
|
+
aiAnalysis,
|
|
945
|
+
// Include cloud sync info if synced
|
|
946
|
+
...syncResult && {
|
|
947
|
+
projectId: syncResult.projectId,
|
|
948
|
+
auditId: syncResult.auditId,
|
|
949
|
+
dashboardUrl: `${WEB_APP_URL}/dashboard/projects/${syncResult.projectId}`
|
|
950
|
+
}
|
|
767
951
|
};
|
|
768
952
|
console.log(JSON.stringify(jsonOutput, null, 2));
|
|
769
953
|
} else {
|
|
770
|
-
console.log("\n" +
|
|
771
|
-
console.log(
|
|
772
|
-
console.log(
|
|
954
|
+
console.log("\n" + chalk2.bold("\u2550".repeat(60)));
|
|
955
|
+
console.log(chalk2.bold.cyan(` SEO AUDIT REPORT: ${url}`));
|
|
956
|
+
console.log(chalk2.bold("\u2550".repeat(60)));
|
|
773
957
|
const score = primaryReport.healthScore?.overall || 0;
|
|
774
|
-
const scoreColor = score >= 80 ?
|
|
958
|
+
const scoreColor = score >= 80 ? chalk2.green : score >= 60 ? chalk2.yellow : chalk2.red;
|
|
775
959
|
console.log(`
|
|
776
|
-
${
|
|
777
|
-
console.log(` ${
|
|
778
|
-
console.log(` ${
|
|
779
|
-
console.log(` ${
|
|
960
|
+
${chalk2.bold("Score:")} ${scoreColor.bold(score + "/100")}`);
|
|
961
|
+
console.log(` ${chalk2.bold("Pages audited:")} ${successCount}`);
|
|
962
|
+
console.log(` ${chalk2.bold("Issues found:")} ${mergedIssues.length}`);
|
|
963
|
+
console.log(` ${chalk2.bold("Time:")} ${totalTime}ms`);
|
|
780
964
|
if (frameworkName) {
|
|
781
|
-
console.log(` ${
|
|
965
|
+
console.log(` ${chalk2.bold("Framework:")} ${chalk2.cyan(frameworkName)}`);
|
|
782
966
|
}
|
|
783
967
|
if (primaryReport.healthScore) {
|
|
784
|
-
console.log("\n" +
|
|
968
|
+
console.log("\n" + chalk2.bold(" Health Scores:"));
|
|
785
969
|
const hs = primaryReport.healthScore;
|
|
786
970
|
const bar = (score2) => {
|
|
787
971
|
const filled = Math.round(score2 / 5);
|
|
788
972
|
const empty = 20 - filled;
|
|
789
|
-
const color = score2 >= 80 ?
|
|
790
|
-
return color("\u2588".repeat(filled)) +
|
|
973
|
+
const color = score2 >= 80 ? chalk2.green : score2 >= 60 ? chalk2.yellow : chalk2.red;
|
|
974
|
+
return color("\u2588".repeat(filled)) + chalk2.gray("\u2591".repeat(empty));
|
|
791
975
|
};
|
|
792
976
|
console.log(` Crawlability: ${bar(hs.crawlability || 0)} ${hs.crawlability || 0}%`);
|
|
793
977
|
console.log(` On-Page: ${bar(hs.onPage || 0)} ${hs.onPage || 0}%`);
|
|
@@ -798,50 +982,63 @@ async function audit(options) {
|
|
|
798
982
|
const errors = mergedIssues.filter((i) => i.severity === "error");
|
|
799
983
|
const warnings = mergedIssues.filter((i) => i.severity === "warning");
|
|
800
984
|
const notices = mergedIssues.filter((i) => i.severity === "notice");
|
|
801
|
-
console.log("\n" +
|
|
802
|
-
console.log(` ${
|
|
803
|
-
console.log(` ${
|
|
804
|
-
console.log(` ${
|
|
805
|
-
console.log("\n" +
|
|
985
|
+
console.log("\n" + chalk2.bold(" Issues by Severity:"));
|
|
986
|
+
console.log(` ${chalk2.red("\u25CF")} Errors: ${errors.length}`);
|
|
987
|
+
console.log(` ${chalk2.yellow("\u25CF")} Warnings: ${warnings.length}`);
|
|
988
|
+
console.log(` ${chalk2.blue("\u25CF")} Notices: ${notices.length}`);
|
|
989
|
+
console.log("\n" + chalk2.bold(" Top Issues:"));
|
|
806
990
|
const topIssues = [...errors, ...warnings, ...notices].slice(0, 10);
|
|
807
991
|
topIssues.forEach((issue, i) => {
|
|
808
|
-
const icon = issue.severity === "error" ?
|
|
992
|
+
const icon = issue.severity === "error" ? chalk2.red("\u2717") : issue.severity === "warning" ? chalk2.yellow("!") : chalk2.blue("i");
|
|
809
993
|
console.log(` ${i + 1}. ${icon} ${issue.title}`);
|
|
810
994
|
if (issue.affectedUrls && issue.affectedUrls.length > 1) {
|
|
811
|
-
console.log(
|
|
995
|
+
console.log(chalk2.dim(` Affects ${issue.affectedUrls.length} pages`));
|
|
812
996
|
}
|
|
813
997
|
});
|
|
814
998
|
if (aiAnalysis) {
|
|
815
|
-
console.log("\n" +
|
|
999
|
+
console.log("\n" + chalk2.bold(" AI Analysis:"));
|
|
816
1000
|
console.log(` Content Score: ${aiAnalysis.contentScore}/100`);
|
|
817
|
-
console.log("\n " +
|
|
1001
|
+
console.log("\n " + chalk2.bold("Suggestions:"));
|
|
818
1002
|
aiAnalysis.suggestions.forEach((s, i) => {
|
|
819
1003
|
console.log(` ${i + 1}. ${s}`);
|
|
820
1004
|
});
|
|
821
1005
|
if (aiAnalysis.prioritizedFixes?.length > 0) {
|
|
822
|
-
console.log("\n " +
|
|
1006
|
+
console.log("\n " + chalk2.bold("Priority Fixes:"));
|
|
823
1007
|
aiAnalysis.prioritizedFixes.slice(0, 5).forEach((fix, i) => {
|
|
824
1008
|
console.log(` ${i + 1}. [P${fix.priority}] ${fix.code}`);
|
|
825
|
-
console.log(
|
|
1009
|
+
console.log(chalk2.dim(` ${fix.rationale}`));
|
|
826
1010
|
});
|
|
827
1011
|
}
|
|
828
1012
|
}
|
|
829
|
-
console.log("\n" +
|
|
1013
|
+
console.log("\n" + chalk2.bold("\u2550".repeat(60)));
|
|
1014
|
+
const userInfo = getUserInfo();
|
|
1015
|
+
const tier = userInfo.planName?.toLowerCase() || "free";
|
|
1016
|
+
const isFreeTier = !loggedIn || tier === "free";
|
|
1017
|
+
const isSoloOrBelow = isFreeTier || tier === "solo";
|
|
1018
|
+
if (isFreeTier) {
|
|
1019
|
+
const checksRun = mergedIssues.length > 0 ? loggedIn ? 100 : 50 : 0;
|
|
1020
|
+
showUpsell("checks_limit", {
|
|
1021
|
+
tier: loggedIn ? "free" : "free_anon",
|
|
1022
|
+
current: checksRun
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
if (options.geo && isFreeTier) {
|
|
1026
|
+
showUpsell("geo_limited", { tier: loggedIn ? "free" : "free_anon" });
|
|
1027
|
+
}
|
|
830
1028
|
if (loggedIn) {
|
|
831
1029
|
const syncSpinner = ora("Syncing to cloud...").start();
|
|
832
|
-
const syncResult = await syncToCloud(url, primaryReport, mergedIssues, successCount, aiAnalysis);
|
|
1030
|
+
const syncResult = await syncToCloud(url, primaryReport, mergedIssues, successCount, aiAnalysis, totalTime, actualChecksRun);
|
|
833
1031
|
if (syncResult) {
|
|
834
1032
|
syncSpinner.succeed("Results synced to cloud");
|
|
835
|
-
console.log(
|
|
1033
|
+
console.log(chalk2.cyan(`
|
|
836
1034
|
View and manage issues in the web dashboard:`));
|
|
837
|
-
console.log(
|
|
1035
|
+
console.log(chalk2.bold(` ${WEB_APP_URL}/dashboard/projects/${syncResult.projectId}
|
|
838
1036
|
`));
|
|
839
1037
|
} else {
|
|
840
1038
|
syncSpinner.warn("Could not sync to cloud");
|
|
841
1039
|
}
|
|
842
1040
|
} else {
|
|
843
|
-
|
|
844
|
-
console.log(chalk.dim(" rankcli login\n"));
|
|
1041
|
+
showUpsell("free_audit_complete");
|
|
845
1042
|
}
|
|
846
1043
|
}
|
|
847
1044
|
} catch (error) {
|
|
@@ -855,7 +1052,6 @@ async function audit(options) {
|
|
|
855
1052
|
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
856
1053
|
import { join as join3 } from "path";
|
|
857
1054
|
import {
|
|
858
|
-
runDirectAnalysis,
|
|
859
1055
|
runAuditWithFixes,
|
|
860
1056
|
generateFixes,
|
|
861
1057
|
applyFixes,
|
|
@@ -914,40 +1110,37 @@ async function apply(options) {
|
|
|
914
1110
|
}
|
|
915
1111
|
console.log("\n\u{1F680} SEO Autopilot - Apply Fixes");
|
|
916
1112
|
console.log("\u2550".repeat(50));
|
|
1113
|
+
const loggedIn = isLoggedIn();
|
|
1114
|
+
const userInfo = getUserInfo();
|
|
1115
|
+
const tier = userInfo.planName?.toLowerCase() || "free";
|
|
1116
|
+
const hasAutoFix = loggedIn && hasFeature(tier, "auto_fix");
|
|
1117
|
+
if (!hasAutoFix) {
|
|
1118
|
+
showUpsell("auto_fix_blocked");
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
917
1121
|
console.log("\n\u{1F50D} Detecting framework...");
|
|
918
1122
|
const frameworkResult = await detectFramework3({ cwd });
|
|
919
1123
|
const framework = frameworkResult.success ? frameworkResult.data : { name: "Unknown", metaPattern: "html-head" };
|
|
920
1124
|
console.log(` Framework: ${framework.name}`);
|
|
921
|
-
const isPaidUser = isLoggedIn();
|
|
922
1125
|
console.log(`
|
|
923
1126
|
\u{1F50D} Analyzing ${url}...`);
|
|
924
|
-
|
|
925
|
-
console.log(" Running full audit (200+ checks)...");
|
|
926
|
-
} else {
|
|
927
|
-
console.log(" Running basic analysis (login for full 200+ checks)");
|
|
928
|
-
}
|
|
1127
|
+
console.log(" Running full audit (200+ checks)...");
|
|
929
1128
|
let issues;
|
|
930
1129
|
let score;
|
|
931
1130
|
let auditResult = null;
|
|
932
1131
|
try {
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
score = auditResult.score;
|
|
946
|
-
} else {
|
|
947
|
-
const analysis = await runDirectAnalysis(url);
|
|
948
|
-
issues = analysis.issues;
|
|
949
|
-
score = analysis.score;
|
|
950
|
-
}
|
|
1132
|
+
auditResult = await runAuditWithFixes({
|
|
1133
|
+
url,
|
|
1134
|
+
generateFixes: true
|
|
1135
|
+
});
|
|
1136
|
+
issues = auditResult.report.issues.map((issue) => ({
|
|
1137
|
+
severity: issue.severity,
|
|
1138
|
+
category: issue.category,
|
|
1139
|
+
code: issue.code,
|
|
1140
|
+
message: issue.title,
|
|
1141
|
+
impact: issue.description || void 0
|
|
1142
|
+
}));
|
|
1143
|
+
score = auditResult.score;
|
|
951
1144
|
} catch (error) {
|
|
952
1145
|
console.log(`
|
|
953
1146
|
\u274C Analysis failed: ${error instanceof Error ? error.message : "Unknown error"}
|
|
@@ -1038,7 +1231,7 @@ async function apply(options) {
|
|
|
1038
1231
|
// src/commands/geo.ts
|
|
1039
1232
|
import { analyzers } from "@rankcli/agent-runtime";
|
|
1040
1233
|
import ora2 from "ora";
|
|
1041
|
-
import
|
|
1234
|
+
import chalk3 from "chalk";
|
|
1042
1235
|
async function fetchResources(url) {
|
|
1043
1236
|
const headers = {};
|
|
1044
1237
|
let html = null;
|
|
@@ -1110,55 +1303,55 @@ async function geo(options) {
|
|
|
1110
1303
|
console.log(JSON.stringify(jsonOutput, null, 2));
|
|
1111
1304
|
return;
|
|
1112
1305
|
}
|
|
1113
|
-
console.log("\n" +
|
|
1114
|
-
console.log(
|
|
1115
|
-
console.log(
|
|
1306
|
+
console.log("\n" + chalk3.bold("\u2550".repeat(60)));
|
|
1307
|
+
console.log(chalk3.bold.cyan(" GEO ANALYSIS: AI Search Optimization"));
|
|
1308
|
+
console.log(chalk3.bold("\u2550".repeat(60)));
|
|
1116
1309
|
const geoScore = geoResult.score;
|
|
1117
|
-
const scoreColor = geoScore >= 80 ?
|
|
1310
|
+
const scoreColor = geoScore >= 80 ? chalk3.green : geoScore >= 60 ? chalk3.yellow : chalk3.red;
|
|
1118
1311
|
console.log(`
|
|
1119
|
-
${
|
|
1120
|
-
console.log("\n" +
|
|
1312
|
+
${chalk3.bold("GEO Score:")} ${scoreColor.bold(geoScore + "/100")}`);
|
|
1313
|
+
console.log("\n" + chalk3.bold(" AI Crawler Access:"));
|
|
1121
1314
|
const { aiCrawlerAccess } = geoResult;
|
|
1122
1315
|
if (aiCrawlerAccess.blockedCrawlers.length > 0) {
|
|
1123
|
-
console.log(
|
|
1316
|
+
console.log(chalk3.red(` \u274C Blocked: ${aiCrawlerAccess.blockedCrawlers.join(", ")}`));
|
|
1124
1317
|
}
|
|
1125
1318
|
if (aiCrawlerAccess.allowedCrawlers.length > 0) {
|
|
1126
1319
|
const topAllowed = aiCrawlerAccess.allowedCrawlers.slice(0, 5);
|
|
1127
|
-
console.log(
|
|
1320
|
+
console.log(chalk3.green(` \u2705 Allowed: ${topAllowed.join(", ")}${aiCrawlerAccess.allowedCrawlers.length > 5 ? "..." : ""}`));
|
|
1128
1321
|
}
|
|
1129
|
-
console.log("\n" +
|
|
1322
|
+
console.log("\n" + chalk3.bold(" Rendering:"));
|
|
1130
1323
|
if (aiCrawlerAccess.serverSideRendered) {
|
|
1131
|
-
console.log(
|
|
1324
|
+
console.log(chalk3.green(" \u2705 Server-side rendered (AI crawlers can see content)"));
|
|
1132
1325
|
} else if (aiCrawlerAccess.jsRenderingRequired) {
|
|
1133
|
-
console.log(
|
|
1326
|
+
console.log(chalk3.red(" \u274C JavaScript required (AI crawlers may see blank page)"));
|
|
1134
1327
|
}
|
|
1135
|
-
console.log("\n" +
|
|
1328
|
+
console.log("\n" + chalk3.bold(" LLM Friendliness:"));
|
|
1136
1329
|
const { llmSignals } = geoResult;
|
|
1137
1330
|
const bar = (score) => {
|
|
1138
1331
|
const filled = Math.round(score / 5);
|
|
1139
1332
|
const empty = 20 - filled;
|
|
1140
|
-
const color = score >= 70 ?
|
|
1141
|
-
return color("\u2588".repeat(filled)) +
|
|
1333
|
+
const color = score >= 70 ? chalk3.green : score >= 40 ? chalk3.yellow : chalk3.red;
|
|
1334
|
+
return color("\u2588".repeat(filled)) + chalk3.gray("\u2591".repeat(empty));
|
|
1142
1335
|
};
|
|
1143
1336
|
console.log(` Content Clarity: ${bar(llmSignals.contentClarity)} ${llmSignals.contentClarity}%`);
|
|
1144
1337
|
console.log(` Fact Density: ${bar(llmSignals.factDensity)} ${llmSignals.factDensity}%`);
|
|
1145
1338
|
console.log(` Structure: ${bar(llmSignals.structureQuality)} ${llmSignals.structureQuality}%`);
|
|
1146
1339
|
console.log(` Citations: ${bar(llmSignals.citationQuality)} ${llmSignals.citationQuality}%`);
|
|
1147
|
-
console.log("\n" +
|
|
1340
|
+
console.log("\n" + chalk3.bold(" Content Structure:"));
|
|
1148
1341
|
const { contentStructure } = geoResult;
|
|
1149
|
-
console.log(` ${contentStructure.hasStructuredData ?
|
|
1150
|
-
console.log(` ${contentStructure.hasFAQSchema ?
|
|
1151
|
-
console.log(` ${contentStructure.hasArticleSchema ?
|
|
1152
|
-
console.log(` ${contentStructure.hasBreadcrumbs ?
|
|
1153
|
-
console.log(` Heading Hierarchy: ${contentStructure.headingHierarchy === "good" ?
|
|
1154
|
-
console.log("\n" +
|
|
1342
|
+
console.log(` ${contentStructure.hasStructuredData ? chalk3.green("\u2705") : chalk3.red("\u274C")} JSON-LD Structured Data`);
|
|
1343
|
+
console.log(` ${contentStructure.hasFAQSchema ? chalk3.green("\u2705") : chalk3.yellow("\u25CB")} FAQ Schema`);
|
|
1344
|
+
console.log(` ${contentStructure.hasArticleSchema ? chalk3.green("\u2705") : chalk3.yellow("\u25CB")} Article Schema`);
|
|
1345
|
+
console.log(` ${contentStructure.hasBreadcrumbs ? chalk3.green("\u2705") : chalk3.yellow("\u25CB")} Breadcrumbs`);
|
|
1346
|
+
console.log(` Heading Hierarchy: ${contentStructure.headingHierarchy === "good" ? chalk3.green("Good") : contentStructure.headingHierarchy === "needs-work" ? chalk3.yellow("Needs Work") : chalk3.red("Poor")}`);
|
|
1347
|
+
console.log("\n" + chalk3.bold(" Citation Readiness:"));
|
|
1155
1348
|
const { citationReadiness } = geoResult;
|
|
1156
1349
|
if (citationReadiness.trustSignals.length > 0) {
|
|
1157
|
-
console.log(
|
|
1350
|
+
console.log(chalk3.green(` \u2705 ${citationReadiness.trustSignals.join(", ")}`));
|
|
1158
1351
|
} else {
|
|
1159
|
-
console.log(
|
|
1352
|
+
console.log(chalk3.yellow(" \u25CB No trust signals detected"));
|
|
1160
1353
|
}
|
|
1161
|
-
console.log("\n" +
|
|
1354
|
+
console.log("\n" + chalk3.bold(" Other Scores:"));
|
|
1162
1355
|
console.log(` Core Web Vitals: ${bar(cwvResult.overallScore)} ${cwvResult.overallScore}%`);
|
|
1163
1356
|
console.log(` Security Headers: ${securityResult.grade} (${securityResult.score}%)`);
|
|
1164
1357
|
console.log(` Structured Data: ${bar(schemaResult.score)} ${schemaResult.score}%`);
|
|
@@ -1172,18 +1365,18 @@ async function geo(options) {
|
|
|
1172
1365
|
];
|
|
1173
1366
|
const criticalIssues = allIssues.filter((i) => i.severity === "critical" || i.severity === "error");
|
|
1174
1367
|
if (criticalIssues.length > 0) {
|
|
1175
|
-
console.log("\n" +
|
|
1368
|
+
console.log("\n" + chalk3.bold.red(" Critical Issues:"));
|
|
1176
1369
|
criticalIssues.slice(0, 5).forEach((issue, i) => {
|
|
1177
|
-
console.log(
|
|
1370
|
+
console.log(chalk3.red(` ${i + 1}. ${issue.title}`));
|
|
1178
1371
|
});
|
|
1179
1372
|
}
|
|
1180
1373
|
if (geoResult.recommendations.length > 0) {
|
|
1181
|
-
console.log("\n" +
|
|
1374
|
+
console.log("\n" + chalk3.bold(" Recommendations:"));
|
|
1182
1375
|
geoResult.recommendations.slice(0, 5).forEach((rec, i) => {
|
|
1183
1376
|
console.log(` ${i + 1}. ${rec}`);
|
|
1184
1377
|
});
|
|
1185
1378
|
}
|
|
1186
|
-
console.log("\n" +
|
|
1379
|
+
console.log("\n" + chalk3.bold("\u2550".repeat(60)));
|
|
1187
1380
|
console.log("");
|
|
1188
1381
|
} catch (error) {
|
|
1189
1382
|
spinner.fail(`Analysis failed: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
@@ -1862,15 +2055,15 @@ function showGSCInstructions() {
|
|
|
1862
2055
|
}
|
|
1863
2056
|
|
|
1864
2057
|
// src/commands/auth.ts
|
|
1865
|
-
import
|
|
2058
|
+
import chalk4 from "chalk";
|
|
1866
2059
|
import ora3 from "ora";
|
|
1867
2060
|
import inquirer from "inquirer";
|
|
1868
2061
|
import open from "open";
|
|
1869
2062
|
async function login(options) {
|
|
1870
2063
|
if (isLoggedIn()) {
|
|
1871
2064
|
const { email } = getUserInfo();
|
|
1872
|
-
console.log(
|
|
1873
|
-
console.log(
|
|
2065
|
+
console.log(chalk4.yellow(`Already logged in as ${email}`));
|
|
2066
|
+
console.log(chalk4.dim('Run "rankcli logout" to sign out first.'));
|
|
1874
2067
|
return;
|
|
1875
2068
|
}
|
|
1876
2069
|
const spinner = ora3();
|
|
@@ -1878,14 +2071,14 @@ async function login(options) {
|
|
|
1878
2071
|
if (options.token) {
|
|
1879
2072
|
const apiKey = options.token;
|
|
1880
2073
|
if (!apiKey.startsWith("rankcli_")) {
|
|
1881
|
-
console.log(
|
|
2074
|
+
console.log(chalk4.red('Invalid API key format. Keys should start with "rankcli_"'));
|
|
1882
2075
|
return;
|
|
1883
2076
|
}
|
|
1884
2077
|
spinner.start("Validating API key...");
|
|
1885
2078
|
const result = await validateApiKey(apiKey);
|
|
1886
2079
|
if (!result.valid) {
|
|
1887
2080
|
spinner.fail("Invalid API key");
|
|
1888
|
-
console.log(
|
|
2081
|
+
console.log(chalk4.red(`
|
|
1889
2082
|
${result.error}`));
|
|
1890
2083
|
return;
|
|
1891
2084
|
}
|
|
@@ -1898,14 +2091,14 @@ ${result.error}`));
|
|
|
1898
2091
|
});
|
|
1899
2092
|
}
|
|
1900
2093
|
spinner.succeed(`Authenticated via API key`);
|
|
1901
|
-
console.log(
|
|
2094
|
+
console.log(chalk4.dim(`
|
|
1902
2095
|
Email: ${result.user?.email}`));
|
|
1903
|
-
console.log(
|
|
2096
|
+
console.log(chalk4.dim(`Plan: ${result.subscription?.planName || "Free"}`));
|
|
1904
2097
|
return;
|
|
1905
2098
|
}
|
|
1906
2099
|
if (options.browser) {
|
|
1907
|
-
console.log(
|
|
1908
|
-
console.log(
|
|
2100
|
+
console.log(chalk4.cyan("\nOpening browser for login..."));
|
|
2101
|
+
console.log(chalk4.dim("Complete the login in your browser, then return here.\n"));
|
|
1909
2102
|
const loginUrl = `${WEB_APP_URL}/login?cli=true`;
|
|
1910
2103
|
await open(loginUrl);
|
|
1911
2104
|
const { token } = await inquirer.prompt([
|
|
@@ -1921,7 +2114,7 @@ Email: ${result.user?.email}`));
|
|
|
1921
2114
|
const { data, error } = await supabase.auth.getUser(token);
|
|
1922
2115
|
if (error || !data.user) {
|
|
1923
2116
|
spinner.fail("Invalid token");
|
|
1924
|
-
console.log(
|
|
2117
|
+
console.log(chalk4.red("The token is invalid or expired. Please try again."));
|
|
1925
2118
|
return;
|
|
1926
2119
|
}
|
|
1927
2120
|
const { data: sessionData, error: sessionError } = await supabase.auth.setSession({
|
|
@@ -1935,7 +2128,7 @@ Email: ${result.user?.email}`));
|
|
|
1935
2128
|
}
|
|
1936
2129
|
saveSession(sessionData.session);
|
|
1937
2130
|
await fetchAndSaveSubscription();
|
|
1938
|
-
spinner.succeed(`Logged in as ${
|
|
2131
|
+
spinner.succeed(`Logged in as ${chalk4.green(data.user.email)}`);
|
|
1939
2132
|
} else {
|
|
1940
2133
|
let email = options.email;
|
|
1941
2134
|
if (!email) {
|
|
@@ -1968,11 +2161,11 @@ Email: ${result.user?.email}`));
|
|
|
1968
2161
|
});
|
|
1969
2162
|
if (error) {
|
|
1970
2163
|
spinner.fail("Login failed");
|
|
1971
|
-
console.log(
|
|
2164
|
+
console.log(chalk4.red(`
|
|
1972
2165
|
${error.message}`));
|
|
1973
2166
|
if (error.message.includes("Invalid login")) {
|
|
1974
|
-
console.log(
|
|
1975
|
-
console.log(
|
|
2167
|
+
console.log(chalk4.dim("\nDon't have an account? Sign up at:"));
|
|
2168
|
+
console.log(chalk4.cyan(` ${WEB_APP_URL}/signup`));
|
|
1976
2169
|
}
|
|
1977
2170
|
return;
|
|
1978
2171
|
}
|
|
@@ -1982,19 +2175,19 @@ ${error.message}`));
|
|
|
1982
2175
|
}
|
|
1983
2176
|
saveSession(data.session);
|
|
1984
2177
|
await fetchAndSaveSubscription();
|
|
1985
|
-
spinner.succeed(`Logged in as ${
|
|
2178
|
+
spinner.succeed(`Logged in as ${chalk4.green(data.user?.email)}`);
|
|
1986
2179
|
const { planName, sitesLimit } = getUserInfo();
|
|
1987
|
-
console.log(
|
|
2180
|
+
console.log(chalk4.dim(`
|
|
1988
2181
|
Plan: ${planName} (${sitesLimit} site${sitesLimit !== 1 ? "s" : ""})`));
|
|
1989
2182
|
}
|
|
1990
2183
|
} catch (err) {
|
|
1991
2184
|
spinner.fail("Login failed");
|
|
1992
|
-
console.error(
|
|
2185
|
+
console.error(chalk4.red(err instanceof Error ? err.message : "Unknown error"));
|
|
1993
2186
|
}
|
|
1994
2187
|
}
|
|
1995
2188
|
async function logout() {
|
|
1996
2189
|
if (!isLoggedIn()) {
|
|
1997
|
-
console.log(
|
|
2190
|
+
console.log(chalk4.yellow("Not logged in"));
|
|
1998
2191
|
return;
|
|
1999
2192
|
}
|
|
2000
2193
|
const { email } = getUserInfo();
|
|
@@ -2003,17 +2196,17 @@ async function logout() {
|
|
|
2003
2196
|
const supabase = getSupabaseClient();
|
|
2004
2197
|
await supabase.auth.signOut();
|
|
2005
2198
|
clearCredentials();
|
|
2006
|
-
spinner.succeed(`Logged out from ${
|
|
2199
|
+
spinner.succeed(`Logged out from ${chalk4.dim(email)}`);
|
|
2007
2200
|
} catch (err) {
|
|
2008
2201
|
spinner.fail("Logout failed");
|
|
2009
|
-
console.error(
|
|
2202
|
+
console.error(chalk4.red(err instanceof Error ? err.message : "Unknown error"));
|
|
2010
2203
|
}
|
|
2011
2204
|
}
|
|
2012
2205
|
async function whoami() {
|
|
2013
2206
|
if (!isLoggedIn()) {
|
|
2014
|
-
console.log(
|
|
2015
|
-
console.log(
|
|
2016
|
-
console.log(
|
|
2207
|
+
console.log(chalk4.yellow("Not logged in"));
|
|
2208
|
+
console.log(chalk4.dim('\nRun "rankcli login" to sign in.'));
|
|
2209
|
+
console.log(chalk4.dim(`Or sign up at: ${WEB_APP_URL}/signup`));
|
|
2017
2210
|
return;
|
|
2018
2211
|
}
|
|
2019
2212
|
const spinner = ora3("Checking account...").start();
|
|
@@ -2036,18 +2229,18 @@ async function whoami() {
|
|
|
2036
2229
|
const { email, planName, sitesLimit } = getUserInfo();
|
|
2037
2230
|
const authMethod = isApiKeyAuth() ? "API Key" : "Session";
|
|
2038
2231
|
spinner.stop();
|
|
2039
|
-
console.log(
|
|
2232
|
+
console.log(chalk4.bold("\nRankCLI Account"));
|
|
2040
2233
|
console.log("\u2500".repeat(40));
|
|
2041
|
-
console.log(` Email: ${
|
|
2042
|
-
console.log(` Plan: ${
|
|
2234
|
+
console.log(` Email: ${chalk4.cyan(email || "N/A")}`);
|
|
2235
|
+
console.log(` Plan: ${chalk4.green(planName)}`);
|
|
2043
2236
|
console.log(` Sites: ${sitesLimit} allowed`);
|
|
2044
|
-
console.log(` Auth: ${
|
|
2237
|
+
console.log(` Auth: ${chalk4.dim(authMethod)}`);
|
|
2045
2238
|
console.log("\u2500".repeat(40));
|
|
2046
|
-
console.log(
|
|
2239
|
+
console.log(chalk4.dim(`
|
|
2047
2240
|
Manage at: ${WEB_APP_URL}/account`));
|
|
2048
2241
|
} catch (err) {
|
|
2049
2242
|
spinner.fail("Failed to fetch account info");
|
|
2050
|
-
console.error(
|
|
2243
|
+
console.error(chalk4.red(err instanceof Error ? err.message : "Unknown error"));
|
|
2051
2244
|
}
|
|
2052
2245
|
}
|
|
2053
2246
|
async function fetchAndSaveSubscription() {
|
|
@@ -2079,7 +2272,7 @@ async function fetchAndSaveSubscription() {
|
|
|
2079
2272
|
}
|
|
2080
2273
|
|
|
2081
2274
|
// src/version.ts
|
|
2082
|
-
var VERSION = "0.0.
|
|
2275
|
+
var VERSION = "0.0.19";
|
|
2083
2276
|
|
|
2084
2277
|
// src/index.ts
|
|
2085
2278
|
var program = new Command();
|