@rankcli/cli 0.0.32 → 0.0.34

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
@@ -211,7 +211,7 @@ rankcli apply --url https://mysite.com --auto # Auto-apply
211
211
 
212
212
  ### `rankcli keywords`
213
213
 
214
- AI-powered keyword research.
214
+ AI-powered keyword research. `--ai` runs on the same provider as `audit --ai` (Apple Intelligence on a Mac that has it, else your key, else a local Ollama) and takes the same `--ai-provider` / `--ai-key` / `--ai-model` / `--ai-base-url` flags. Keyword clustering uses embeddings on OpenAI and term vectors everywhere else; local models judge 5 tool ideas instead of 15.
215
215
 
216
216
  ```bash
217
217
  rankcli keywords --url https://mysite.com --ai
package/dist/index.js CHANGED
@@ -1922,19 +1922,27 @@ Respond with only this JSON object - no prose, no code fences:
1922
1922
  }`;
1923
1923
  }
1924
1924
  async function runAIAnalysis(url, issues, ai) {
1925
- const system = "You are an SEO expert. Respond with valid JSON only.";
1925
+ const reply = await completeWithAI(ai, {
1926
+ system: "You are an SEO expert. Respond with valid JSON only.",
1927
+ prompt: buildPrompt(url, issues, ai.provider === "apple" ? APPLE_MAX_ISSUES : MAX_ISSUES_IN_PROMPT),
1928
+ json: true
1929
+ });
1930
+ if ("error" in reply) return reply;
1931
+ return parseAnalysis(reply.text, ai);
1932
+ }
1933
+ async function completeWithAI(ai, request) {
1934
+ const { system, prompt } = request;
1926
1935
  if (ai.provider === "apple" && ai.helper) {
1927
1936
  const reply = await generateWithAppleAI(ai.helper, {
1928
1937
  system,
1929
- prompt: buildPrompt(url, issues, APPLE_MAX_ISSUES),
1930
- maxTokens: APPLE_MAX_TOKENS
1938
+ prompt,
1939
+ maxTokens: Math.min(request.maxTokens ?? APPLE_MAX_TOKENS, APPLE_MAX_TOKENS)
1931
1940
  });
1932
1941
  if ("error" in reply) return { error: `${ai.label} request failed: ${reply.error}` };
1933
- return parseAnalysis(reply.text, ai);
1942
+ return reply;
1934
1943
  }
1935
- const prompt = buildPrompt(url, issues);
1936
1944
  const timeoutMs = ai.local ? 3e5 : 9e4;
1937
- const maxTokens = 4096;
1945
+ const maxTokens = Math.max(request.maxTokens ?? 4096, 4096);
1938
1946
  const headers = { "Content-Type": "application/json" };
1939
1947
  let endpoint;
1940
1948
  let body;
@@ -1942,7 +1950,7 @@ async function runAIAnalysis(url, issues, ai) {
1942
1950
  endpoint = `${ai.baseUrl}/messages`;
1943
1951
  headers["x-api-key"] = ai.apiKey || "";
1944
1952
  headers["anthropic-version"] = "2023-06-01";
1945
- body = { model: ai.model, max_tokens: maxTokens, system, messages: [{ role: "user", content: prompt }] };
1953
+ body = { model: ai.model, max_tokens: maxTokens, ...system ? { system } : {}, messages: [{ role: "user", content: prompt }] };
1946
1954
  } else {
1947
1955
  endpoint = `${ai.baseUrl}/chat/completions`;
1948
1956
  if (ai.apiKey) headers["Authorization"] = `Bearer ${ai.apiKey}`;
@@ -1953,12 +1961,12 @@ async function runAIAnalysis(url, issues, ai) {
1953
1961
  body = {
1954
1962
  model: ai.model,
1955
1963
  messages: [
1956
- { role: "system", content: system },
1964
+ ...system ? [{ role: "system", content: system }] : [],
1957
1965
  { role: "user", content: prompt }
1958
1966
  ],
1959
1967
  // OpenAI's GPT-5.6 rejects max_tokens (and temperature); everyone else expects max_tokens.
1960
1968
  ...ai.provider === "openai" && !ai.local ? { max_completion_tokens: maxTokens } : { max_tokens: maxTokens },
1961
- ...ai.jsonMode ? { response_format: { type: "json_object" } } : {}
1969
+ ...ai.jsonMode && request.json ? { response_format: { type: "json_object" } } : {}
1962
1970
  };
1963
1971
  }
1964
1972
  let response;
@@ -1982,7 +1990,7 @@ async function runAIAnalysis(url, issues, ai) {
1982
1990
  if (!content) {
1983
1991
  return { error: `${ai.label} returned no content (a reasoning model may have used its whole token budget - try another --ai-model)` };
1984
1992
  }
1985
- return parseAnalysis(content, ai);
1993
+ return { text: content };
1986
1994
  }
1987
1995
  function parseAnalysis(content, ai) {
1988
1996
  const parsed = extractJsonObject(content);
@@ -3077,8 +3085,7 @@ async function trackAdd(options) {
3077
3085
  spinner.fail("Could not find or create project");
3078
3086
  return { success: false, error: "Project not found" };
3079
3087
  }
3080
- const { planName } = getUserInfo();
3081
- const tier = planName?.toLowerCase() || "free";
3088
+ const tier = tierFromPlanId(getUserInfo().planId) ?? "free";
3082
3089
  const { maxKeywords } = getTierLimits(tier);
3083
3090
  const supabase = await getAuthenticatedClient();
3084
3091
  if (!supabase) {
@@ -3238,7 +3245,7 @@ async function trackHistory(options) {
3238
3245
  }
3239
3246
  const days = options.days || 30;
3240
3247
  const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
3241
- const { data: historyData, error } = await supabase.from("rankings").select("position, url, recorded_at").eq("keyword_id", keywordData.id).gte("recorded_at", cutoff.toISOString()).order("recorded_at", { ascending: false });
3248
+ const { data: historyData, error } = await supabase.from("keyword_position_history").select("position, url, checked_at").eq("keyword_id", keywordData.id).gte("checked_at", cutoff.toISOString()).order("checked_at", { ascending: false });
3242
3249
  if (error) {
3243
3250
  spinner.fail(`Failed to load history: ${error.message}`);
3244
3251
  return { success: false, error: error.message };
@@ -3247,7 +3254,7 @@ async function trackHistory(options) {
3247
3254
  const history = (historyData || []).map((row) => ({
3248
3255
  position: row.position,
3249
3256
  url: row.url,
3250
- recordedAt: new Date(row.recorded_at)
3257
+ recordedAt: new Date(row.checked_at)
3251
3258
  }));
3252
3259
  if (options.output === "json") {
3253
3260
  console.log(JSON.stringify({ keyword: options.keyword, history }, null, 2));
@@ -3255,7 +3262,9 @@ async function trackHistory(options) {
3255
3262
  console.log("\n" + import_chalk6.default.bold(`\u{1F4C8} History: "${options.keyword}"`));
3256
3263
  console.log("\u2550".repeat(50));
3257
3264
  if (history.length === 0) {
3258
- console.log(import_chalk6.default.dim("\nNo history available yet.\n"));
3265
+ console.log(import_chalk6.default.dim(`
3266
+ No checks recorded yet. Run 'rankcli track check --url <your site>' to record one.
3267
+ `));
3259
3268
  } else {
3260
3269
  const positions = history.slice(0, 30).reverse().map((h) => h.position || 101);
3261
3270
  const sparkline = positions.map((p) => {
@@ -3266,7 +3275,7 @@ async function trackHistory(options) {
3266
3275
  }).join("");
3267
3276
  console.log(`
3268
3277
  ${sparkline} (last ${positions.length} checks)`);
3269
- console.log(import_chalk6.default.dim(" \u2190 newer older \u2192\n"));
3278
+ console.log(import_chalk6.default.dim(" \u2190 older newer \u2192\n"));
3270
3279
  const recent = history.slice(0, 10);
3271
3280
  for (const entry of recent) {
3272
3281
  console.log(` ${entry.recordedAt.toLocaleDateString()} ${formatPosition(entry.position)}`);
@@ -3357,8 +3366,7 @@ async function trackCheck(options) {
3357
3366
  if (!project) {
3358
3367
  return { success: false, error: "Project not found" };
3359
3368
  }
3360
- const { planName } = getUserInfo();
3361
- const tier = planName?.toLowerCase() || "free";
3369
+ const tier = tierFromPlanId(getUserInfo().planId) ?? "free";
3362
3370
  const { checksPerDay } = getTierLimits(tier);
3363
3371
  if (!options.force && project.lastRankCheckAt) {
3364
3372
  const hoursSinceCheck = (Date.now() - project.lastRankCheckAt.getTime()) / (1e3 * 60 * 60);
@@ -3580,6 +3588,40 @@ async function geo(options) {
3580
3588
  // src/commands/keywords.ts
3581
3589
  var readline2 = __toESM(require("readline"));
3582
3590
  var import_agent_runtime7 = require("@rankcli/agent-runtime");
3591
+
3592
+ // src/lib/keyword-llm.ts
3593
+ async function openAIEmbeddings(ai, texts) {
3594
+ const response = await fetch(`${ai.baseUrl}/embeddings`, {
3595
+ method: "POST",
3596
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${ai.apiKey}` },
3597
+ body: JSON.stringify({ model: "text-embedding-3-small", input: texts }),
3598
+ signal: AbortSignal.timeout(6e4)
3599
+ });
3600
+ if (!response.ok) throw new Error(`OpenAI embeddings returned HTTP ${response.status}`);
3601
+ const data = await response.json();
3602
+ return data.data.map((item) => item.embedding);
3603
+ }
3604
+ function keywordLLMFromAI(ai) {
3605
+ const llm = {
3606
+ async complete(prompt, opts = {}) {
3607
+ const reply = await completeWithAI(ai, { system: opts.system, prompt, maxTokens: opts.maxTokens, json: true });
3608
+ if ("error" in reply) throw new Error(reply.error);
3609
+ return reply.text;
3610
+ }
3611
+ };
3612
+ if (ai.provider === "openai" && ai.apiKey && ai.jsonMode) {
3613
+ llm.embed = (texts) => openAIEmbeddings(ai, texts);
3614
+ }
3615
+ if (ai.provider === "apple") {
3616
+ llm.maxContentChars = 5e3;
3617
+ llm.maxToolIdeas = 5;
3618
+ } else if (ai.local) {
3619
+ llm.maxToolIdeas = 5;
3620
+ }
3621
+ return llm;
3622
+ }
3623
+
3624
+ // src/commands/keywords.ts
3583
3625
  async function keywords(options) {
3584
3626
  console.log("\n\u{1F511} RankCLI - Keyword Research\n");
3585
3627
  console.log("This tool helps you find keywords you can actually rank for,");
@@ -3740,65 +3782,37 @@ async function keywordsQuick(url, providedSeeds) {
3740
3782
  `);
3741
3783
  }
3742
3784
  }
3743
- var SUPABASE_URL = "https://eqzlmjbvrtrglknphdai.supabase.co";
3744
3785
  async function keywordsAI(url, options = {}) {
3745
3786
  console.log("\n\u{1F916} AI-Powered Keyword Research\n");
3746
- console.log("This uses GPT-4 to analyze your site and generate keyword recommendations");
3747
- console.log("with competitive analysis and free tool ideas.\n");
3748
- const openaiApiKey = options.openaiApiKey || process.env.OPENAI_API_KEY;
3749
3787
  try {
3750
3788
  const parsed = new URL(url.startsWith("http") ? url : `https://${url}`);
3751
3789
  url = parsed.href;
3752
- console.log(`\u{1F4CD} Analyzing: ${url}`);
3753
- console.log("");
3754
- if (!options.forceLocal) {
3755
- console.log("\u{1F517} Connecting to RankCLI cloud...");
3756
- try {
3757
- const response = await fetch(`${SUPABASE_URL}/functions/v1/ai-keywords`, {
3758
- method: "POST",
3759
- headers: {
3760
- "Content-Type": "application/json"
3761
- // Auth header would go here if user is logged in
3762
- },
3763
- body: JSON.stringify({
3764
- url,
3765
- mode: "analyze"
3766
- })
3767
- });
3768
- if (response.ok) {
3769
- const result2 = await response.json();
3770
- if (result2.tier === "free" && result2.teaser) {
3771
- console.log("\n" + "\u2500".repeat(60));
3772
- console.log("\u{1F512} FREE TIER - Limited Preview");
3773
- console.log("\u2500".repeat(60));
3774
- console.log(`Found ${result2.teaser.totalKeywordsFound} keywords`);
3775
- console.log(`Top keywords: ${result2.teaser.topKeywords.join(", ")}`);
3776
- console.log(`
3777
- ${result2.teaser.upgradeMessage}`);
3778
- console.log('\n\u{1F4A1} Run "rankcli login" to access your paid plan features.\n');
3779
- return;
3780
- }
3781
- displayAIKeywordResults(result2);
3782
- return;
3783
- } else {
3784
- const errorText = await response.text();
3785
- console.log(`\u26A0\uFE0F Cloud request failed, running locally...
3786
- `);
3787
- }
3788
- } catch (err) {
3789
- console.log("\u26A0\uFE0F Cloud unavailable, running locally...\n");
3790
- }
3790
+ const resolution = await resolveAIProvider(options);
3791
+ if (!resolution.ok) {
3792
+ console.log(`\u274C ${resolution.error}`);
3793
+ for (const hint of resolution.hints) console.log(` ${hint}`);
3794
+ console.log("");
3795
+ process.exitCode = 1;
3796
+ return;
3791
3797
  }
3792
- if (!openaiApiKey) {
3793
- console.log("\u274C OpenAI API key required for local processing.");
3794
- console.log(' Set OPENAI_API_KEY environment variable, or use "rankcli login" for cloud processing.\n');
3798
+ const { ai } = resolution;
3799
+ if (ai.notice) console.log(ai.notice);
3800
+ const check = await completeWithAI(ai, { prompt: 'Reply with the JSON object {"ok": true} and nothing else.', maxTokens: 50, json: true });
3801
+ if ("error" in check) {
3802
+ console.log(`\u274C ${check.error}`);
3803
+ console.log(" Choose another provider with --ai-provider, or fix the key or model and try again.\n");
3804
+ process.exitCode = 1;
3795
3805
  return;
3796
3806
  }
3797
- console.log("\u{1F52C} Running local AI analysis (this may take a minute)...\n");
3807
+ console.log(`\u{1F4CD} Analyzing: ${url}`);
3808
+ console.log(`\u{1F9E0} Model: ${ai.label} (${ai.model})
3809
+ `);
3810
+ console.log(`\u{1F52C} Running AI analysis (${ai.local ? "a few minutes on a local model" : "this may take a minute"})...
3811
+ `);
3798
3812
  const result = await (0, import_agent_runtime7.runAIKeywordResearch)({
3799
3813
  url,
3800
3814
  tier: "solo",
3801
- openaiApiKey,
3815
+ llm: keywordLLMFromAI(ai),
3802
3816
  enableEnhancedToolIdeas: true,
3803
3817
  competitiveSearchKeys: {
3804
3818
  braveApiKey: process.env.BRAVE_API_KEY,
@@ -3811,6 +3825,7 @@ ${result2.teaser.upgradeMessage}`);
3811
3825
  console.log(`
3812
3826
  \u274C Error: ${error instanceof Error ? error.message : "Unknown error"}
3813
3827
  `);
3828
+ process.exitCode = 1;
3814
3829
  }
3815
3830
  }
3816
3831
  function displayAIKeywordResults(result) {
@@ -4222,7 +4237,7 @@ async function testWebhook(channel, webhookUrl) {
4222
4237
  }
4223
4238
 
4224
4239
  // src/version.ts
4225
- var VERSION = "0.0.32";
4240
+ var VERSION = "0.0.34";
4226
4241
 
4227
4242
  // src/index.ts
4228
4243
  var program = new import_commander.Command();
@@ -4253,11 +4268,13 @@ program.command("fix").description("Agentic SEO fixer - detect, fix, commit, and
4253
4268
  }
4254
4269
  });
4255
4270
  program.command("geo").description("GEO analysis - Check AI search visibility (ChatGPT, Perplexity, Claude)").option("-u, --url <url>", "URL to analyze").option("-o, --output <format>", "Output format (json, console)", "console").action(geo);
4256
- program.command("keywords").description("AI-powered keyword research with actionable suggestions").option("-u, --url <url>", "Your website URL").option("-s, --seed <keywords>", "Seed keywords (comma-separated)").option("--auto", "Auto-extract seed keywords from your page").option("--quick", "Quick mode - skip questions, use defaults").option("--ai", "Use GPT-4 for enhanced analysis with free tool ideas").option("--local", "Force local processing (skip cloud worker)").option("--competitor", "Competitor gap analysis mode (SpyFu Kombat-style)").option("-c, --competitors <domains>", "Competitor domains (comma-separated)").action(async (options) => {
4271
+ program.command("keywords").description("AI-powered keyword research with actionable suggestions").option("-u, --url <url>", "Your website URL").option("-s, --seed <keywords>", "Seed keywords (comma-separated)").option("--auto", "Auto-extract seed keywords from your page").option("--quick", "Quick mode - skip questions, use defaults").option("--ai", "AI keyword research with free tool ideas, on your own provider (Apple Intelligence on a Mac that has it)").option("--ai-provider <provider>", "AI provider for --ai (auto-detected if omitted, or set RANKCLI_AI_PROVIDER)").option("--ai-key <key>", "AI provider API key (or the provider's env var)").option("--ai-model <model>", "Override the provider's default model").option("--ai-base-url <url>", "Any OpenAI-compatible server (implies --ai-provider custom)").option("--local", "No effect; --ai always runs locally on your provider (kept for old scripts)").option("--competitor", "Competitor gap analysis mode (SpyFu Kombat-style)").option("-c, --competitors <domains>", "Competitor domains (comma-separated)").action(async (options) => {
4257
4272
  if (options.ai && options.url) {
4258
4273
  await keywordsAI(options.url, {
4259
- forceLocal: options.local,
4260
- openaiApiKey: process.env.OPENAI_API_KEY
4274
+ aiProvider: options.aiProvider,
4275
+ aiKey: options.aiKey,
4276
+ aiModel: options.aiModel,
4277
+ aiBaseUrl: options.aiBaseUrl
4261
4278
  });
4262
4279
  } else if (options.competitor && options.url && options.seed) {
4263
4280
  const seeds = options.seed.split(",").map((s) => s.trim());
package/dist/index.mjs CHANGED
@@ -1743,19 +1743,27 @@ Respond with only this JSON object - no prose, no code fences:
1743
1743
  }`;
1744
1744
  }
1745
1745
  async function runAIAnalysis(url, issues, ai) {
1746
- const system = "You are an SEO expert. Respond with valid JSON only.";
1746
+ const reply = await completeWithAI(ai, {
1747
+ system: "You are an SEO expert. Respond with valid JSON only.",
1748
+ prompt: buildPrompt(url, issues, ai.provider === "apple" ? APPLE_MAX_ISSUES : MAX_ISSUES_IN_PROMPT),
1749
+ json: true
1750
+ });
1751
+ if ("error" in reply) return reply;
1752
+ return parseAnalysis(reply.text, ai);
1753
+ }
1754
+ async function completeWithAI(ai, request) {
1755
+ const { system, prompt } = request;
1747
1756
  if (ai.provider === "apple" && ai.helper) {
1748
1757
  const reply = await generateWithAppleAI(ai.helper, {
1749
1758
  system,
1750
- prompt: buildPrompt(url, issues, APPLE_MAX_ISSUES),
1751
- maxTokens: APPLE_MAX_TOKENS
1759
+ prompt,
1760
+ maxTokens: Math.min(request.maxTokens ?? APPLE_MAX_TOKENS, APPLE_MAX_TOKENS)
1752
1761
  });
1753
1762
  if ("error" in reply) return { error: `${ai.label} request failed: ${reply.error}` };
1754
- return parseAnalysis(reply.text, ai);
1763
+ return reply;
1755
1764
  }
1756
- const prompt = buildPrompt(url, issues);
1757
1765
  const timeoutMs = ai.local ? 3e5 : 9e4;
1758
- const maxTokens = 4096;
1766
+ const maxTokens = Math.max(request.maxTokens ?? 4096, 4096);
1759
1767
  const headers = { "Content-Type": "application/json" };
1760
1768
  let endpoint;
1761
1769
  let body;
@@ -1763,7 +1771,7 @@ async function runAIAnalysis(url, issues, ai) {
1763
1771
  endpoint = `${ai.baseUrl}/messages`;
1764
1772
  headers["x-api-key"] = ai.apiKey || "";
1765
1773
  headers["anthropic-version"] = "2023-06-01";
1766
- body = { model: ai.model, max_tokens: maxTokens, system, messages: [{ role: "user", content: prompt }] };
1774
+ body = { model: ai.model, max_tokens: maxTokens, ...system ? { system } : {}, messages: [{ role: "user", content: prompt }] };
1767
1775
  } else {
1768
1776
  endpoint = `${ai.baseUrl}/chat/completions`;
1769
1777
  if (ai.apiKey) headers["Authorization"] = `Bearer ${ai.apiKey}`;
@@ -1774,12 +1782,12 @@ async function runAIAnalysis(url, issues, ai) {
1774
1782
  body = {
1775
1783
  model: ai.model,
1776
1784
  messages: [
1777
- { role: "system", content: system },
1785
+ ...system ? [{ role: "system", content: system }] : [],
1778
1786
  { role: "user", content: prompt }
1779
1787
  ],
1780
1788
  // OpenAI's GPT-5.6 rejects max_tokens (and temperature); everyone else expects max_tokens.
1781
1789
  ...ai.provider === "openai" && !ai.local ? { max_completion_tokens: maxTokens } : { max_tokens: maxTokens },
1782
- ...ai.jsonMode ? { response_format: { type: "json_object" } } : {}
1790
+ ...ai.jsonMode && request.json ? { response_format: { type: "json_object" } } : {}
1783
1791
  };
1784
1792
  }
1785
1793
  let response;
@@ -1803,7 +1811,7 @@ async function runAIAnalysis(url, issues, ai) {
1803
1811
  if (!content) {
1804
1812
  return { error: `${ai.label} returned no content (a reasoning model may have used its whole token budget - try another --ai-model)` };
1805
1813
  }
1806
- return parseAnalysis(content, ai);
1814
+ return { text: content };
1807
1815
  }
1808
1816
  function parseAnalysis(content, ai) {
1809
1817
  const parsed = extractJsonObject(content);
@@ -2908,8 +2916,7 @@ async function trackAdd(options) {
2908
2916
  spinner.fail("Could not find or create project");
2909
2917
  return { success: false, error: "Project not found" };
2910
2918
  }
2911
- const { planName } = getUserInfo();
2912
- const tier = planName?.toLowerCase() || "free";
2919
+ const tier = tierFromPlanId(getUserInfo().planId) ?? "free";
2913
2920
  const { maxKeywords } = getTierLimits(tier);
2914
2921
  const supabase = await getAuthenticatedClient();
2915
2922
  if (!supabase) {
@@ -3069,7 +3076,7 @@ async function trackHistory(options) {
3069
3076
  }
3070
3077
  const days = options.days || 30;
3071
3078
  const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
3072
- const { data: historyData, error } = await supabase.from("rankings").select("position, url, recorded_at").eq("keyword_id", keywordData.id).gte("recorded_at", cutoff.toISOString()).order("recorded_at", { ascending: false });
3079
+ const { data: historyData, error } = await supabase.from("keyword_position_history").select("position, url, checked_at").eq("keyword_id", keywordData.id).gte("checked_at", cutoff.toISOString()).order("checked_at", { ascending: false });
3073
3080
  if (error) {
3074
3081
  spinner.fail(`Failed to load history: ${error.message}`);
3075
3082
  return { success: false, error: error.message };
@@ -3078,7 +3085,7 @@ async function trackHistory(options) {
3078
3085
  const history = (historyData || []).map((row) => ({
3079
3086
  position: row.position,
3080
3087
  url: row.url,
3081
- recordedAt: new Date(row.recorded_at)
3088
+ recordedAt: new Date(row.checked_at)
3082
3089
  }));
3083
3090
  if (options.output === "json") {
3084
3091
  console.log(JSON.stringify({ keyword: options.keyword, history }, null, 2));
@@ -3086,7 +3093,9 @@ async function trackHistory(options) {
3086
3093
  console.log("\n" + chalk6.bold(`\u{1F4C8} History: "${options.keyword}"`));
3087
3094
  console.log("\u2550".repeat(50));
3088
3095
  if (history.length === 0) {
3089
- console.log(chalk6.dim("\nNo history available yet.\n"));
3096
+ console.log(chalk6.dim(`
3097
+ No checks recorded yet. Run 'rankcli track check --url <your site>' to record one.
3098
+ `));
3090
3099
  } else {
3091
3100
  const positions = history.slice(0, 30).reverse().map((h) => h.position || 101);
3092
3101
  const sparkline = positions.map((p) => {
@@ -3097,7 +3106,7 @@ async function trackHistory(options) {
3097
3106
  }).join("");
3098
3107
  console.log(`
3099
3108
  ${sparkline} (last ${positions.length} checks)`);
3100
- console.log(chalk6.dim(" \u2190 newer older \u2192\n"));
3109
+ console.log(chalk6.dim(" \u2190 older newer \u2192\n"));
3101
3110
  const recent = history.slice(0, 10);
3102
3111
  for (const entry of recent) {
3103
3112
  console.log(` ${entry.recordedAt.toLocaleDateString()} ${formatPosition(entry.position)}`);
@@ -3188,8 +3197,7 @@ async function trackCheck(options) {
3188
3197
  if (!project) {
3189
3198
  return { success: false, error: "Project not found" };
3190
3199
  }
3191
- const { planName } = getUserInfo();
3192
- const tier = planName?.toLowerCase() || "free";
3200
+ const tier = tierFromPlanId(getUserInfo().planId) ?? "free";
3193
3201
  const { checksPerDay } = getTierLimits(tier);
3194
3202
  if (!options.force && project.lastRankCheckAt) {
3195
3203
  const hoursSinceCheck = (Date.now() - project.lastRankCheckAt.getTime()) / (1e3 * 60 * 60);
@@ -3421,6 +3429,40 @@ import {
3421
3429
  formatTopicReport,
3422
3430
  runAIKeywordResearch
3423
3431
  } from "@rankcli/agent-runtime";
3432
+
3433
+ // src/lib/keyword-llm.ts
3434
+ async function openAIEmbeddings(ai, texts) {
3435
+ const response = await fetch(`${ai.baseUrl}/embeddings`, {
3436
+ method: "POST",
3437
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${ai.apiKey}` },
3438
+ body: JSON.stringify({ model: "text-embedding-3-small", input: texts }),
3439
+ signal: AbortSignal.timeout(6e4)
3440
+ });
3441
+ if (!response.ok) throw new Error(`OpenAI embeddings returned HTTP ${response.status}`);
3442
+ const data = await response.json();
3443
+ return data.data.map((item) => item.embedding);
3444
+ }
3445
+ function keywordLLMFromAI(ai) {
3446
+ const llm = {
3447
+ async complete(prompt, opts = {}) {
3448
+ const reply = await completeWithAI(ai, { system: opts.system, prompt, maxTokens: opts.maxTokens, json: true });
3449
+ if ("error" in reply) throw new Error(reply.error);
3450
+ return reply.text;
3451
+ }
3452
+ };
3453
+ if (ai.provider === "openai" && ai.apiKey && ai.jsonMode) {
3454
+ llm.embed = (texts) => openAIEmbeddings(ai, texts);
3455
+ }
3456
+ if (ai.provider === "apple") {
3457
+ llm.maxContentChars = 5e3;
3458
+ llm.maxToolIdeas = 5;
3459
+ } else if (ai.local) {
3460
+ llm.maxToolIdeas = 5;
3461
+ }
3462
+ return llm;
3463
+ }
3464
+
3465
+ // src/commands/keywords.ts
3424
3466
  async function keywords(options) {
3425
3467
  console.log("\n\u{1F511} RankCLI - Keyword Research\n");
3426
3468
  console.log("This tool helps you find keywords you can actually rank for,");
@@ -3581,65 +3623,37 @@ async function keywordsQuick(url, providedSeeds) {
3581
3623
  `);
3582
3624
  }
3583
3625
  }
3584
- var SUPABASE_URL = "https://eqzlmjbvrtrglknphdai.supabase.co";
3585
3626
  async function keywordsAI(url, options = {}) {
3586
3627
  console.log("\n\u{1F916} AI-Powered Keyword Research\n");
3587
- console.log("This uses GPT-4 to analyze your site and generate keyword recommendations");
3588
- console.log("with competitive analysis and free tool ideas.\n");
3589
- const openaiApiKey = options.openaiApiKey || process.env.OPENAI_API_KEY;
3590
3628
  try {
3591
3629
  const parsed = new URL(url.startsWith("http") ? url : `https://${url}`);
3592
3630
  url = parsed.href;
3593
- console.log(`\u{1F4CD} Analyzing: ${url}`);
3594
- console.log("");
3595
- if (!options.forceLocal) {
3596
- console.log("\u{1F517} Connecting to RankCLI cloud...");
3597
- try {
3598
- const response = await fetch(`${SUPABASE_URL}/functions/v1/ai-keywords`, {
3599
- method: "POST",
3600
- headers: {
3601
- "Content-Type": "application/json"
3602
- // Auth header would go here if user is logged in
3603
- },
3604
- body: JSON.stringify({
3605
- url,
3606
- mode: "analyze"
3607
- })
3608
- });
3609
- if (response.ok) {
3610
- const result2 = await response.json();
3611
- if (result2.tier === "free" && result2.teaser) {
3612
- console.log("\n" + "\u2500".repeat(60));
3613
- console.log("\u{1F512} FREE TIER - Limited Preview");
3614
- console.log("\u2500".repeat(60));
3615
- console.log(`Found ${result2.teaser.totalKeywordsFound} keywords`);
3616
- console.log(`Top keywords: ${result2.teaser.topKeywords.join(", ")}`);
3617
- console.log(`
3618
- ${result2.teaser.upgradeMessage}`);
3619
- console.log('\n\u{1F4A1} Run "rankcli login" to access your paid plan features.\n');
3620
- return;
3621
- }
3622
- displayAIKeywordResults(result2);
3623
- return;
3624
- } else {
3625
- const errorText = await response.text();
3626
- console.log(`\u26A0\uFE0F Cloud request failed, running locally...
3627
- `);
3628
- }
3629
- } catch (err) {
3630
- console.log("\u26A0\uFE0F Cloud unavailable, running locally...\n");
3631
- }
3631
+ const resolution = await resolveAIProvider(options);
3632
+ if (!resolution.ok) {
3633
+ console.log(`\u274C ${resolution.error}`);
3634
+ for (const hint of resolution.hints) console.log(` ${hint}`);
3635
+ console.log("");
3636
+ process.exitCode = 1;
3637
+ return;
3632
3638
  }
3633
- if (!openaiApiKey) {
3634
- console.log("\u274C OpenAI API key required for local processing.");
3635
- console.log(' Set OPENAI_API_KEY environment variable, or use "rankcli login" for cloud processing.\n');
3639
+ const { ai } = resolution;
3640
+ if (ai.notice) console.log(ai.notice);
3641
+ const check = await completeWithAI(ai, { prompt: 'Reply with the JSON object {"ok": true} and nothing else.', maxTokens: 50, json: true });
3642
+ if ("error" in check) {
3643
+ console.log(`\u274C ${check.error}`);
3644
+ console.log(" Choose another provider with --ai-provider, or fix the key or model and try again.\n");
3645
+ process.exitCode = 1;
3636
3646
  return;
3637
3647
  }
3638
- console.log("\u{1F52C} Running local AI analysis (this may take a minute)...\n");
3648
+ console.log(`\u{1F4CD} Analyzing: ${url}`);
3649
+ console.log(`\u{1F9E0} Model: ${ai.label} (${ai.model})
3650
+ `);
3651
+ console.log(`\u{1F52C} Running AI analysis (${ai.local ? "a few minutes on a local model" : "this may take a minute"})...
3652
+ `);
3639
3653
  const result = await runAIKeywordResearch({
3640
3654
  url,
3641
3655
  tier: "solo",
3642
- openaiApiKey,
3656
+ llm: keywordLLMFromAI(ai),
3643
3657
  enableEnhancedToolIdeas: true,
3644
3658
  competitiveSearchKeys: {
3645
3659
  braveApiKey: process.env.BRAVE_API_KEY,
@@ -3652,6 +3666,7 @@ ${result2.teaser.upgradeMessage}`);
3652
3666
  console.log(`
3653
3667
  \u274C Error: ${error instanceof Error ? error.message : "Unknown error"}
3654
3668
  `);
3669
+ process.exitCode = 1;
3655
3670
  }
3656
3671
  }
3657
3672
  function displayAIKeywordResults(result) {
@@ -4078,7 +4093,7 @@ async function testWebhook(channel, webhookUrl) {
4078
4093
  }
4079
4094
 
4080
4095
  // src/version.ts
4081
- var VERSION = "0.0.32";
4096
+ var VERSION = "0.0.34";
4082
4097
 
4083
4098
  // src/index.ts
4084
4099
  var program = new Command();
@@ -4109,11 +4124,13 @@ program.command("fix").description("Agentic SEO fixer - detect, fix, commit, and
4109
4124
  }
4110
4125
  });
4111
4126
  program.command("geo").description("GEO analysis - Check AI search visibility (ChatGPT, Perplexity, Claude)").option("-u, --url <url>", "URL to analyze").option("-o, --output <format>", "Output format (json, console)", "console").action(geo);
4112
- program.command("keywords").description("AI-powered keyword research with actionable suggestions").option("-u, --url <url>", "Your website URL").option("-s, --seed <keywords>", "Seed keywords (comma-separated)").option("--auto", "Auto-extract seed keywords from your page").option("--quick", "Quick mode - skip questions, use defaults").option("--ai", "Use GPT-4 for enhanced analysis with free tool ideas").option("--local", "Force local processing (skip cloud worker)").option("--competitor", "Competitor gap analysis mode (SpyFu Kombat-style)").option("-c, --competitors <domains>", "Competitor domains (comma-separated)").action(async (options) => {
4127
+ program.command("keywords").description("AI-powered keyword research with actionable suggestions").option("-u, --url <url>", "Your website URL").option("-s, --seed <keywords>", "Seed keywords (comma-separated)").option("--auto", "Auto-extract seed keywords from your page").option("--quick", "Quick mode - skip questions, use defaults").option("--ai", "AI keyword research with free tool ideas, on your own provider (Apple Intelligence on a Mac that has it)").option("--ai-provider <provider>", "AI provider for --ai (auto-detected if omitted, or set RANKCLI_AI_PROVIDER)").option("--ai-key <key>", "AI provider API key (or the provider's env var)").option("--ai-model <model>", "Override the provider's default model").option("--ai-base-url <url>", "Any OpenAI-compatible server (implies --ai-provider custom)").option("--local", "No effect; --ai always runs locally on your provider (kept for old scripts)").option("--competitor", "Competitor gap analysis mode (SpyFu Kombat-style)").option("-c, --competitors <domains>", "Competitor domains (comma-separated)").action(async (options) => {
4113
4128
  if (options.ai && options.url) {
4114
4129
  await keywordsAI(options.url, {
4115
- forceLocal: options.local,
4116
- openaiApiKey: process.env.OPENAI_API_KEY
4130
+ aiProvider: options.aiProvider,
4131
+ aiKey: options.aiKey,
4132
+ aiModel: options.aiModel,
4133
+ aiBaseUrl: options.aiBaseUrl
4117
4134
  });
4118
4135
  } else if (options.competitor && options.url && options.seed) {
4119
4136
  const seeds = options.seed.split(",").map((s) => s.trim());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rankcli/cli",
3
- "version": "0.0.32",
3
+ "version": "0.0.34",
4
4
  "description": "RankCLI - Ship code, get ranked. SEO meets CI/CD.",
5
5
  "homepage": "https://rankcli.dev",
6
6
  "main": "dist/index.js",
@@ -33,7 +33,7 @@
33
33
  "build": "npm run build:checks && npm run build:wasm && npm run build:js",
34
34
  "build:js": "npm run update-version && tsup src/index.ts src/wasm-engine.ts --format cjs,esm --dts",
35
35
  "build:checks": "test -d ../agent-runtime && (cd ../agent-runtime && pnpm build:wasm) || echo 'agent-runtime not present as a monorepo sibling - build.rs will fall back to the node_modules-installed bundle'",
36
- "build:wasm": "cd native && wasm-pack build --target nodejs --release && cp -r pkg ../wasm",
36
+ "build:wasm": "cd native && wasm-pack build --target nodejs --release && mkdir -p ../wasm && cp -r pkg/* ../wasm/ && node ../scripts/verify-wasm.js",
37
37
  "build:wasm:dev": "cd native && wasm-pack build --target nodejs --dev && mkdir -p ../wasm && cp -r pkg/* ../wasm/",
38
38
  "prebuild:wasm": "mkdir -p wasm",
39
39
  "inject-config": "node scripts/inject-config.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.23",
48
+ "@rankcli/agent-runtime": "^0.0.24",
49
49
  "@supabase/supabase-js": "^2.98.0",
50
50
  "chalk": "^5.3.0",
51
51
  "commander": "^12.0.0",
@@ -24,8 +24,21 @@ export function get_encrypted_size(): number;
24
24
  export function get_tier(): string;
25
25
 
26
26
  /**
27
- * Initialize the engine with a license key.
27
+ * Initialize the engine with the build-time decrypt passphrase.
28
28
  * Decrypts the embedded checks and stores them for execution.
29
+ *
30
+ * The `license_key` parameter is misleadingly named for what it does
31
+ * now: entitlement (is this user actually paid?) is decided in
32
+ * TypeScript, from the real Supabase-backed apiKey/plan the CLI already
33
+ * validates on login - not by anything checkable here. The caller only
34
+ * invokes this at all once it has confirmed a paid apiKey, always
35
+ * passing the single build-embedded passphrase (see
36
+ * scripts/inject-config.js), never a user-supplied string. This
37
+ * decryption step exists purely to keep the premium check source out of
38
+ * static analysis of the shipped binary; get_tier()/get_checks_limit()/
39
+ * validate_license() below are legacy from an earlier signed-license
40
+ * design that was never finished and should not be trusted as an
41
+ * entitlement source.
29
42
  */
30
43
  export function init_engine(license_key: string): boolean;
31
44
 
@@ -1,7 +1,5 @@
1
1
  /* @ts-self-types="./rankcli_audit_engine.d.ts" */
2
2
 
3
- //#region exports
4
-
5
3
  /**
6
4
  * Get checks limit for current tier
7
5
  * @returns {number}
@@ -22,18 +20,24 @@ function get_decrypted_code() {
22
20
  let deferred2_0;
23
21
  let deferred2_1;
24
22
  try {
25
- const ret = wasm.get_decrypted_code();
26
- var ptr1 = ret[0];
27
- var len1 = ret[1];
28
- if (ret[3]) {
23
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
24
+ wasm.get_decrypted_code(retptr);
25
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
26
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
27
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
28
+ var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true);
29
+ var ptr1 = r0;
30
+ var len1 = r1;
31
+ if (r3) {
29
32
  ptr1 = 0; len1 = 0;
30
- throw takeFromExternrefTable0(ret[2]);
33
+ throw takeObject(r2);
31
34
  }
32
35
  deferred2_0 = ptr1;
33
36
  deferred2_1 = len1;
34
37
  return getStringFromWasm0(ptr1, len1);
35
38
  } finally {
36
- wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
39
+ wasm.__wbindgen_add_to_stack_pointer(16);
40
+ wasm.__wbindgen_export(deferred2_0, deferred2_1, 1);
37
41
  }
38
42
  }
39
43
  exports.get_decrypted_code = get_decrypted_code;
@@ -56,30 +60,55 @@ function get_tier() {
56
60
  let deferred1_0;
57
61
  let deferred1_1;
58
62
  try {
59
- const ret = wasm.get_tier();
60
- deferred1_0 = ret[0];
61
- deferred1_1 = ret[1];
62
- return getStringFromWasm0(ret[0], ret[1]);
63
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
64
+ wasm.get_tier(retptr);
65
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
66
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
67
+ deferred1_0 = r0;
68
+ deferred1_1 = r1;
69
+ return getStringFromWasm0(r0, r1);
63
70
  } finally {
64
- wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
71
+ wasm.__wbindgen_add_to_stack_pointer(16);
72
+ wasm.__wbindgen_export(deferred1_0, deferred1_1, 1);
65
73
  }
66
74
  }
67
75
  exports.get_tier = get_tier;
68
76
 
69
77
  /**
70
- * Initialize the engine with a license key.
78
+ * Initialize the engine with the build-time decrypt passphrase.
71
79
  * Decrypts the embedded checks and stores them for execution.
80
+ *
81
+ * The `license_key` parameter is misleadingly named for what it does
82
+ * now: entitlement (is this user actually paid?) is decided in
83
+ * TypeScript, from the real Supabase-backed apiKey/plan the CLI already
84
+ * validates on login - not by anything checkable here. The caller only
85
+ * invokes this at all once it has confirmed a paid apiKey, always
86
+ * passing the single build-embedded passphrase (see
87
+ * scripts/inject-config.js), never a user-supplied string. This
88
+ * decryption step exists purely to keep the premium check source out of
89
+ * static analysis of the shipped binary; get_tier()/get_checks_limit()/
90
+ * validate_license() below are legacy from an earlier signed-license
91
+ * design that was never finished and should not be trusted as an
92
+ * entitlement source.
72
93
  * @param {string} license_key
73
94
  * @returns {boolean}
74
95
  */
75
96
  function init_engine(license_key) {
76
- const ptr0 = passStringToWasm0(license_key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
77
- const len0 = WASM_VECTOR_LEN;
78
- const ret = wasm.init_engine(ptr0, len0);
79
- if (ret[2]) {
80
- throw takeFromExternrefTable0(ret[1]);
97
+ try {
98
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
99
+ const ptr0 = passStringToWasm0(license_key, wasm.__wbindgen_export2, wasm.__wbindgen_export3);
100
+ const len0 = WASM_VECTOR_LEN;
101
+ wasm.init_engine(retptr, ptr0, len0);
102
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
103
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
104
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
105
+ if (r2) {
106
+ throw takeObject(r1);
107
+ }
108
+ return r0 !== 0;
109
+ } finally {
110
+ wasm.__wbindgen_add_to_stack_pointer(16);
81
111
  }
82
- return ret[0] !== 0;
83
112
  }
84
113
  exports.init_engine = init_engine;
85
114
 
@@ -102,14 +131,18 @@ function validate_license(license_key) {
102
131
  let deferred2_0;
103
132
  let deferred2_1;
104
133
  try {
105
- const ptr0 = passStringToWasm0(license_key, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
134
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
135
+ const ptr0 = passStringToWasm0(license_key, wasm.__wbindgen_export2, wasm.__wbindgen_export3);
106
136
  const len0 = WASM_VECTOR_LEN;
107
- const ret = wasm.validate_license(ptr0, len0);
108
- deferred2_0 = ret[0];
109
- deferred2_1 = ret[1];
110
- return getStringFromWasm0(ret[0], ret[1]);
137
+ wasm.validate_license(retptr, ptr0, len0);
138
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
139
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
140
+ deferred2_0 = r0;
141
+ deferred2_1 = r1;
142
+ return getStringFromWasm0(r0, r1);
111
143
  } finally {
112
- wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
144
+ wasm.__wbindgen_add_to_stack_pointer(16);
145
+ wasm.__wbindgen_export(deferred2_0, deferred2_1, 1);
113
146
  }
114
147
  }
115
148
  exports.validate_license = validate_license;
@@ -122,39 +155,30 @@ function version() {
122
155
  let deferred1_0;
123
156
  let deferred1_1;
124
157
  try {
125
- const ret = wasm.version();
126
- deferred1_0 = ret[0];
127
- deferred1_1 = ret[1];
128
- return getStringFromWasm0(ret[0], ret[1]);
158
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
159
+ wasm.version(retptr);
160
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
161
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
162
+ deferred1_0 = r0;
163
+ deferred1_1 = r1;
164
+ return getStringFromWasm0(r0, r1);
129
165
  } finally {
130
- wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
166
+ wasm.__wbindgen_add_to_stack_pointer(16);
167
+ wasm.__wbindgen_export(deferred1_0, deferred1_1, 1);
131
168
  }
132
169
  }
133
170
  exports.version = version;
134
171
 
135
- //#endregion
136
-
137
- //#region wasm imports
138
-
139
172
  function __wbg_get_imports() {
140
173
  const import0 = {
141
174
  __proto__: null,
142
- __wbg_Error_8c4e43fe74559d73: function() { return logError(function (arg0, arg1) {
175
+ __wbg_Error_8c4e43fe74559d73: function(arg0, arg1) {
143
176
  const ret = Error(getStringFromWasm0(arg0, arg1));
144
- return ret;
145
- }, arguments); },
177
+ return addHeapObject(ret);
178
+ },
146
179
  __wbg___wbindgen_throw_be289d5034ed271b: function(arg0, arg1) {
147
180
  throw new Error(getStringFromWasm0(arg0, arg1));
148
181
  },
149
- __wbindgen_init_externref_table: function() {
150
- const table = wasm.__wbindgen_externrefs;
151
- const offset = table.grow(4);
152
- table.set(0, undefined);
153
- table.set(offset + 0, undefined);
154
- table.set(offset + 1, null);
155
- table.set(offset + 2, true);
156
- table.set(offset + 3, false);
157
- },
158
182
  };
159
183
  return {
160
184
  __proto__: null,
@@ -162,10 +186,29 @@ function __wbg_get_imports() {
162
186
  };
163
187
  }
164
188
 
189
+ function addHeapObject(obj) {
190
+ if (heap_next === heap.length) heap.push(heap.length + 1);
191
+ const idx = heap_next;
192
+ heap_next = heap[idx];
193
+
194
+ heap[idx] = obj;
195
+ return idx;
196
+ }
197
+
198
+ function dropObject(idx) {
199
+ if (idx < 132) return;
200
+ heap[idx] = heap_next;
201
+ heap_next = idx;
202
+ }
165
203
 
166
- //#endregion
204
+ let cachedDataViewMemory0 = null;
205
+ function getDataViewMemory0() {
206
+ if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
207
+ cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
208
+ }
209
+ return cachedDataViewMemory0;
210
+ }
167
211
 
168
- //#region intrinsics
169
212
  function getStringFromWasm0(ptr, len) {
170
213
  ptr = ptr >>> 0;
171
214
  return decodeText(ptr, len);
@@ -179,24 +222,14 @@ function getUint8ArrayMemory0() {
179
222
  return cachedUint8ArrayMemory0;
180
223
  }
181
224
 
182
- function logError(f, args) {
183
- try {
184
- return f.apply(this, args);
185
- } catch (e) {
186
- let error = (function () {
187
- try {
188
- return e instanceof Error ? `${e.message}\n\nStack:\n${e.stack}` : e.toString();
189
- } catch(_) {
190
- return "<failed to stringify thrown value>";
191
- }
192
- }());
193
- console.error("wasm-bindgen: imported JS function that was not marked as `catch` threw an error:", error);
194
- throw e;
195
- }
196
- }
225
+ function getObject(idx) { return heap[idx]; }
226
+
227
+ let heap = new Array(128).fill(undefined);
228
+ heap.push(undefined, null, true, false);
229
+
230
+ let heap_next = heap.length;
197
231
 
198
232
  function passStringToWasm0(arg, malloc, realloc) {
199
- if (typeof(arg) !== 'string') throw new Error(`expected a string argument, found ${typeof(arg)}`);
200
233
  if (realloc === undefined) {
201
234
  const buf = cachedTextEncoder.encode(arg);
202
235
  const ptr = malloc(buf.length, 1) >>> 0;
@@ -224,7 +257,7 @@ function passStringToWasm0(arg, malloc, realloc) {
224
257
  ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
225
258
  const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
226
259
  const ret = cachedTextEncoder.encodeInto(arg, view);
227
- if (ret.read !== arg.length) throw new Error('failed to pass whole string');
260
+
228
261
  offset += ret.written;
229
262
  ptr = realloc(ptr, len, offset, 1) >>> 0;
230
263
  }
@@ -233,10 +266,10 @@ function passStringToWasm0(arg, malloc, realloc) {
233
266
  return ptr;
234
267
  }
235
268
 
236
- function takeFromExternrefTable0(idx) {
237
- const value = wasm.__wbindgen_externrefs.get(idx);
238
- wasm.__externref_table_dealloc(idx);
239
- return value;
269
+ function takeObject(idx) {
270
+ const ret = getObject(idx);
271
+ dropObject(idx);
272
+ return ret;
240
273
  }
241
274
 
242
275
  let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
@@ -260,15 +293,7 @@ if (!('encodeInto' in cachedTextEncoder)) {
260
293
 
261
294
  let WASM_VECTOR_LEN = 0;
262
295
 
263
-
264
- //#endregion
265
-
266
- //#region wasm loading
267
296
  const wasmPath = `${__dirname}/rankcli_audit_engine_bg.wasm`;
268
297
  const wasmBytes = require('fs').readFileSync(wasmPath);
269
298
  const wasmModule = new WebAssembly.Module(wasmBytes);
270
299
  const wasm = new WebAssembly.Instance(wasmModule, __wbg_get_imports()).exports;
271
- wasm.__wbindgen_start();
272
-
273
- //#endregion
274
- exports.__wasm = wasm;
Binary file
@@ -2,16 +2,14 @@
2
2
  /* eslint-disable */
3
3
  export const memory: WebAssembly.Memory;
4
4
  export const get_checks_limit: () => number;
5
- export const get_decrypted_code: () => [number, number, number, number];
5
+ export const get_decrypted_code: (a: number) => void;
6
6
  export const get_encrypted_size: () => number;
7
- export const get_tier: () => [number, number];
8
- export const init_engine: (a: number, b: number) => [number, number, number];
7
+ export const get_tier: (a: number) => void;
8
+ export const init_engine: (a: number, b: number, c: number) => void;
9
9
  export const is_initialized: () => number;
10
- export const validate_license: (a: number, b: number) => [number, number];
11
- export const version: () => [number, number];
12
- export const __wbindgen_externrefs: WebAssembly.Table;
13
- export const __externref_table_dealloc: (a: number) => void;
14
- export const __wbindgen_free: (a: number, b: number, c: number) => void;
15
- export const __wbindgen_malloc: (a: number, b: number) => number;
16
- export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
17
- export const __wbindgen_start: () => void;
10
+ export const validate_license: (a: number, b: number, c: number) => void;
11
+ export const version: (a: number) => void;
12
+ export const __wbindgen_add_to_stack_pointer: (a: number) => number;
13
+ export const __wbindgen_export: (a: number, b: number, c: number) => void;
14
+ export const __wbindgen_export2: (a: number, b: number) => number;
15
+ export const __wbindgen_export3: (a: number, b: number, c: number, d: number) => number;