@rankcli/cli 0.0.22 → 0.0.24

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 CHANGED
@@ -16,6 +16,17 @@ Developer-first SEO. CLI-native. Optimizes for Google *and* ChatGPT, Perplexity,
16
16
 
17
17
  ## Installation
18
18
 
19
+ Standalone binary, no Node required:
20
+
21
+ ```bash
22
+ brew install integrallis/tap/rankcli
23
+ # or on Windows:
24
+ scoop bucket add integrallis https://github.com/integrallis/scoop-bucket
25
+ scoop install rankcli
26
+ ```
27
+
28
+ Or via npm (requires Node 18+):
29
+
19
30
  ```bash
20
31
  npm install -g @rankcli/cli
21
32
  # or
@@ -59,23 +70,22 @@ Analyzes:
59
70
 
60
71
  ### 🔌 MCP Server (For AI Assistants)
61
72
 
62
- Let Claude, Cursor, or any MCP-compatible AI run SEO audits for you. **No installation required** — just add your API key:
73
+ Let Claude, Cursor, or any MCP-compatible AI run SEO audits for you. **Free, local, no signup**:
63
74
 
64
75
  **Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
65
76
  ```json
66
77
  {
67
78
  "mcpServers": {
68
79
  "rankcli": {
69
- "url": "https://rankcli-audit-worker.fly.dev/mcp",
70
- "headers": {
71
- "Authorization": "Bearer YOUR_API_KEY"
72
- }
80
+ "command": "npx",
81
+ "args": ["@rankcli/mcp-server"]
73
82
  }
74
83
  }
75
84
  }
76
85
  ```
77
86
 
78
- Get your API key at [rankcli.dev/dashboard](https://rankcli.dev/dashboard).
87
+ Nothing is sent to RankCLI's servers — see [`@rankcli/mcp-server`](https://www.npmjs.com/package/@rankcli/mcp-server).
88
+ A hosted endpoint (with your [API key](https://rankcli.dev/dashboard)) is also available for teams that want dashboard sync.
79
89
 
80
90
  **Available tools:**
81
91
  - `seo_audit` — Full 280+ check audit on any URL
@@ -207,12 +217,12 @@ jobs:
207
217
  | GEO Analysis | Basic | Full |
208
218
  | Framework Fixes | - | ✅ 25+ |
209
219
  | Auto-Fix PRs | - | ✅ |
210
- | Sites | 1 | 10 |
220
+ | Sites | 1 | 3+ |
211
221
 
212
222
  ## Links
213
223
 
214
224
  - **Docs**: [rankcli.dev/docs](https://rankcli.dev/docs)
215
- - **GitHub**: [github.com/rankcli/rankcli](https://github.com/rankcli/rankcli)
225
+ - **GitHub**: [github.com/integrallis/rankcli-cli](https://github.com/integrallis/rankcli-cli)
216
226
  - **Support**: support@rankcli.dev
217
227
 
218
228
  ## License
@@ -9,6 +9,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
9
9
  import * as vm from "vm";
10
10
  import * as path from "path";
11
11
  import * as fs from "fs";
12
+ import * as os from "os";
12
13
  var PREMIUM_CHECK_NAMES = [
13
14
  "analyzeFeaturedSnippet",
14
15
  "analyzeInternalLinkGraph",
@@ -36,6 +37,13 @@ var wasmModule = null;
36
37
  var auditContext = null;
37
38
  async function loadWasm() {
38
39
  if (wasmModule) return wasmModule;
40
+ const sea = __require("sea");
41
+ if (sea.isSea()) {
42
+ const gluePath = extractSeaWasmAssets();
43
+ const dynamicRequire = __require("module").createRequire(gluePath);
44
+ wasmModule = dynamicRequire(gluePath);
45
+ return wasmModule;
46
+ }
39
47
  const possiblePaths = [
40
48
  path.join(__dirname, "../native/pkg/rankcli_audit_engine.js"),
41
49
  path.join(__dirname, "../wasm/rankcli_audit_engine.js"),
@@ -52,6 +60,26 @@ async function loadWasm() {
52
60
  Searched: ${possiblePaths.join(", ")}`
53
61
  );
54
62
  }
63
+ function extractSeaWasmAssets() {
64
+ const sea = __require("sea");
65
+ const dir = path.join(os.tmpdir(), "rankcli-wasm-sea");
66
+ fs.mkdirSync(dir, { recursive: true });
67
+ const glueAsset = sea.getRawAsset("rankcli_audit_engine.js");
68
+ const wasmAsset = sea.getRawAsset("rankcli_audit_engine_bg.wasm");
69
+ if (typeof glueAsset === "string" || typeof wasmAsset === "string") {
70
+ throw new Error("SEA assets returned as string, expected binary ArrayBuffer");
71
+ }
72
+ const glueBuffer = Buffer.from(glueAsset);
73
+ const wasmBuffer = Buffer.from(wasmAsset);
74
+ const gluePath = path.join(dir, "rankcli_audit_engine.js");
75
+ const wasmPath = path.join(dir, "rankcli_audit_engine_bg.wasm");
76
+ const existingSize = fs.existsSync(wasmPath) ? fs.statSync(wasmPath).size : -1;
77
+ if (existingSize !== wasmBuffer.length) {
78
+ fs.writeFileSync(gluePath, glueBuffer);
79
+ fs.writeFileSync(wasmPath, wasmBuffer);
80
+ }
81
+ return gluePath;
82
+ }
55
83
  async function initEngine(decryptPassphrase) {
56
84
  const wasm = await loadWasm();
57
85
  try {
package/dist/index.js CHANGED
@@ -672,6 +672,7 @@ function hasFeature(tier, feature) {
672
672
  var vm = __toESM(require("vm"));
673
673
  var path = __toESM(require("path"));
674
674
  var fs = __toESM(require("fs"));
675
+ var os = __toESM(require("os"));
675
676
  var PREMIUM_CHECK_NAMES = [
676
677
  "analyzeFeaturedSnippet",
677
678
  "analyzeInternalLinkGraph",
@@ -699,6 +700,13 @@ var wasmModule = null;
699
700
  var auditContext = null;
700
701
  async function loadWasm() {
701
702
  if (wasmModule) return wasmModule;
703
+ const sea = require("sea");
704
+ if (sea.isSea()) {
705
+ const gluePath = extractSeaWasmAssets();
706
+ const dynamicRequire = require("module").createRequire(gluePath);
707
+ wasmModule = dynamicRequire(gluePath);
708
+ return wasmModule;
709
+ }
702
710
  const possiblePaths = [
703
711
  path.join(__dirname, "../native/pkg/rankcli_audit_engine.js"),
704
712
  path.join(__dirname, "../wasm/rankcli_audit_engine.js"),
@@ -715,6 +723,26 @@ async function loadWasm() {
715
723
  Searched: ${possiblePaths.join(", ")}`
716
724
  );
717
725
  }
726
+ function extractSeaWasmAssets() {
727
+ const sea = require("sea");
728
+ const dir = path.join(os.tmpdir(), "rankcli-wasm-sea");
729
+ fs.mkdirSync(dir, { recursive: true });
730
+ const glueAsset = sea.getRawAsset("rankcli_audit_engine.js");
731
+ const wasmAsset = sea.getRawAsset("rankcli_audit_engine_bg.wasm");
732
+ if (typeof glueAsset === "string" || typeof wasmAsset === "string") {
733
+ throw new Error("SEA assets returned as string, expected binary ArrayBuffer");
734
+ }
735
+ const glueBuffer = Buffer.from(glueAsset);
736
+ const wasmBuffer = Buffer.from(wasmAsset);
737
+ const gluePath = path.join(dir, "rankcli_audit_engine.js");
738
+ const wasmPath = path.join(dir, "rankcli_audit_engine_bg.wasm");
739
+ const existingSize = fs.existsSync(wasmPath) ? fs.statSync(wasmPath).size : -1;
740
+ if (existingSize !== wasmBuffer.length) {
741
+ fs.writeFileSync(gluePath, glueBuffer);
742
+ fs.writeFileSync(wasmPath, wasmBuffer);
743
+ }
744
+ return gluePath;
745
+ }
718
746
  async function initEngine(decryptPassphrase) {
719
747
  const wasm = await loadWasm();
720
748
  try {
@@ -806,7 +834,7 @@ async function getPremiumChecks() {
806
834
  }
807
835
 
808
836
  // src/commands/audit.ts
809
- var { detectFramework: detectFramework2, getFrameworkDisplayName, getFrameworkIssues } = import_agent_runtime.frameworks;
837
+ var { detectFramework: detectFramework2, getFrameworkDisplayName } = import_agent_runtime.frameworks;
810
838
  async function fetchHtml(url, timeout = 1e4) {
811
839
  try {
812
840
  const controller = new AbortController();
@@ -1069,6 +1097,11 @@ async function audit(options) {
1069
1097
  url: pageUrl,
1070
1098
  maxPages: 1,
1071
1099
  checkBrokenLinks: options.checkLinks || false,
1100
+ // Bundled with --check-links: both mean "verify actual link
1101
+ // targets, which costs real requests" - hreflang reciprocity and
1102
+ // broken-target checks were previously unreachable from any real
1103
+ // caller since nothing ever set this true.
1104
+ checkHreflangUrls: options.checkLinks || false,
1072
1105
  checksLimit,
1073
1106
  premiumChecks
1074
1107
  });
@@ -1101,16 +1134,7 @@ async function audit(options) {
1101
1134
  return;
1102
1135
  }
1103
1136
  auditSpinner.succeed(`Audited ${successCount}/${pagesToAudit.length} pages in ${parallelTime}ms`);
1104
- let mergedIssues = mergeIssues(allIssues);
1105
- if (detectedFramework.framework !== "unknown" && homepageHtml) {
1106
- try {
1107
- const frameworkIssues = await getFrameworkIssues(detectedFramework, homepageHtml, url);
1108
- if (frameworkIssues.length > 0) {
1109
- mergedIssues = [...mergedIssues, ...frameworkIssues];
1110
- }
1111
- } catch {
1112
- }
1113
- }
1137
+ const mergedIssues = mergeIssues(allIssues);
1114
1138
  let aiAnalysis = null;
1115
1139
  if (enableAI && aiKey) {
1116
1140
  const aiSpinner = (0, import_ora.default)(`Running AI analysis (${aiProvider})...`).start();
@@ -3382,7 +3406,7 @@ async function fetchAndSaveSubscription() {
3382
3406
  }
3383
3407
 
3384
3408
  // src/version.ts
3385
- var VERSION = "0.0.22";
3409
+ var VERSION = "0.0.23";
3386
3410
 
3387
3411
  // src/index.ts
3388
3412
  var program = new import_commander.Command();
package/dist/index.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  getPremiumChecks,
4
4
  initEngine
5
- } from "./chunk-YX67Q5F6.mjs";
5
+ } from "./chunk-NF755NKY.mjs";
6
6
 
7
7
  // src/index.ts
8
8
  import { Command } from "commander";
@@ -650,7 +650,7 @@ function hasFeature(tier, feature) {
650
650
  }
651
651
 
652
652
  // src/commands/audit.ts
653
- var { detectFramework: detectFramework2, getFrameworkDisplayName, getFrameworkIssues } = frameworks;
653
+ var { detectFramework: detectFramework2, getFrameworkDisplayName } = frameworks;
654
654
  async function fetchHtml(url, timeout = 1e4) {
655
655
  try {
656
656
  const controller = new AbortController();
@@ -913,6 +913,11 @@ async function audit(options) {
913
913
  url: pageUrl,
914
914
  maxPages: 1,
915
915
  checkBrokenLinks: options.checkLinks || false,
916
+ // Bundled with --check-links: both mean "verify actual link
917
+ // targets, which costs real requests" - hreflang reciprocity and
918
+ // broken-target checks were previously unreachable from any real
919
+ // caller since nothing ever set this true.
920
+ checkHreflangUrls: options.checkLinks || false,
916
921
  checksLimit,
917
922
  premiumChecks
918
923
  });
@@ -945,16 +950,7 @@ async function audit(options) {
945
950
  return;
946
951
  }
947
952
  auditSpinner.succeed(`Audited ${successCount}/${pagesToAudit.length} pages in ${parallelTime}ms`);
948
- let mergedIssues = mergeIssues(allIssues);
949
- if (detectedFramework.framework !== "unknown" && homepageHtml) {
950
- try {
951
- const frameworkIssues = await getFrameworkIssues(detectedFramework, homepageHtml, url);
952
- if (frameworkIssues.length > 0) {
953
- mergedIssues = [...mergedIssues, ...frameworkIssues];
954
- }
955
- } catch {
956
- }
957
- }
953
+ const mergedIssues = mergeIssues(allIssues);
958
954
  let aiAnalysis = null;
959
955
  if (enableAI && aiKey) {
960
956
  const aiSpinner = ora(`Running AI analysis (${aiProvider})...`).start();
@@ -3265,7 +3261,7 @@ async function fetchAndSaveSubscription() {
3265
3261
  }
3266
3262
 
3267
3263
  // src/version.ts
3268
- var VERSION = "0.0.22";
3264
+ var VERSION = "0.0.23";
3269
3265
 
3270
3266
  // src/index.ts
3271
3267
  var program = new Command();
@@ -39,6 +39,7 @@ module.exports = __toCommonJS(wasm_engine_exports);
39
39
  var vm = __toESM(require("vm"));
40
40
  var path = __toESM(require("path"));
41
41
  var fs = __toESM(require("fs"));
42
+ var os = __toESM(require("os"));
42
43
  var PREMIUM_CHECK_NAMES = [
43
44
  "analyzeFeaturedSnippet",
44
45
  "analyzeInternalLinkGraph",
@@ -66,6 +67,13 @@ var wasmModule = null;
66
67
  var auditContext = null;
67
68
  async function loadWasm() {
68
69
  if (wasmModule) return wasmModule;
70
+ const sea = require("sea");
71
+ if (sea.isSea()) {
72
+ const gluePath = extractSeaWasmAssets();
73
+ const dynamicRequire = require("module").createRequire(gluePath);
74
+ wasmModule = dynamicRequire(gluePath);
75
+ return wasmModule;
76
+ }
69
77
  const possiblePaths = [
70
78
  path.join(__dirname, "../native/pkg/rankcli_audit_engine.js"),
71
79
  path.join(__dirname, "../wasm/rankcli_audit_engine.js"),
@@ -82,6 +90,26 @@ async function loadWasm() {
82
90
  Searched: ${possiblePaths.join(", ")}`
83
91
  );
84
92
  }
93
+ function extractSeaWasmAssets() {
94
+ const sea = require("sea");
95
+ const dir = path.join(os.tmpdir(), "rankcli-wasm-sea");
96
+ fs.mkdirSync(dir, { recursive: true });
97
+ const glueAsset = sea.getRawAsset("rankcli_audit_engine.js");
98
+ const wasmAsset = sea.getRawAsset("rankcli_audit_engine_bg.wasm");
99
+ if (typeof glueAsset === "string" || typeof wasmAsset === "string") {
100
+ throw new Error("SEA assets returned as string, expected binary ArrayBuffer");
101
+ }
102
+ const glueBuffer = Buffer.from(glueAsset);
103
+ const wasmBuffer = Buffer.from(wasmAsset);
104
+ const gluePath = path.join(dir, "rankcli_audit_engine.js");
105
+ const wasmPath = path.join(dir, "rankcli_audit_engine_bg.wasm");
106
+ const existingSize = fs.existsSync(wasmPath) ? fs.statSync(wasmPath).size : -1;
107
+ if (existingSize !== wasmBuffer.length) {
108
+ fs.writeFileSync(gluePath, glueBuffer);
109
+ fs.writeFileSync(wasmPath, wasmBuffer);
110
+ }
111
+ return gluePath;
112
+ }
85
113
  async function initEngine(decryptPassphrase) {
86
114
  const wasm = await loadWasm();
87
115
  try {
@@ -3,7 +3,7 @@ import {
3
3
  getVersion,
4
4
  initEngine,
5
5
  isReady
6
- } from "./chunk-YX67Q5F6.mjs";
6
+ } from "./chunk-NF755NKY.mjs";
7
7
  export {
8
8
  getPremiumChecks,
9
9
  getVersion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rankcli/cli",
3
- "version": "0.0.22",
3
+ "version": "0.0.24",
4
4
  "description": "RankCLI - Ship code, get ranked. SEO meets CI/CD.",
5
5
  "homepage": "https://rankcli.dev",
6
6
  "main": "dist/index.js",
@@ -45,7 +45,7 @@
45
45
  "update-version": "node scripts/update-version.js"
46
46
  },
47
47
  "dependencies": {
48
- "@rankcli/agent-runtime": "^0.0.16",
48
+ "@rankcli/agent-runtime": "^0.0.17",
49
49
  "@supabase/supabase-js": "^2.45.0",
50
50
  "chalk": "^5.3.0",
51
51
  "commander": "^12.0.0",