@rankcli/agent-runtime 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/dist/{analyzer-GMURJADU.mjs → analyzer-MCBRQHB7.mjs} +1 -1
- package/dist/index.d.mts +53 -9
- package/dist/index.d.ts +53 -9
- package/dist/index.js +143 -105
- package/dist/index.mjs +140 -105
- package/package.json +1 -1
- package/dist/{chunk-PJLNXOLN.mjs → chunk-NATXE6B3.mjs} +2 -2
package/dist/index.mjs
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
httpGet,
|
|
15
15
|
httpHead,
|
|
16
16
|
httpPost
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-NATXE6B3.mjs";
|
|
18
18
|
import {
|
|
19
19
|
AI_CRAWLERS_INFO,
|
|
20
20
|
analyzeCitationReadiness,
|
|
@@ -4924,7 +4924,7 @@ async function checkRobotsTxt(baseUrl) {
|
|
|
4924
4924
|
}
|
|
4925
4925
|
async function checkSitemap2(baseUrl, robotsData) {
|
|
4926
4926
|
const issues = [];
|
|
4927
|
-
const sitemapUrls = robotsData.sitemapUrls.length > 0 ? robotsData.sitemapUrls : [
|
|
4927
|
+
const sitemapUrls = robotsData.sitemapUrls.length > 0 ? robotsData.sitemapUrls.slice(0, 3) : [
|
|
4928
4928
|
new URL("/sitemap.xml", baseUrl).href,
|
|
4929
4929
|
new URL("/sitemap_index.xml", baseUrl).href
|
|
4930
4930
|
];
|
|
@@ -13989,9 +13989,22 @@ async function runFullAudit(options) {
|
|
|
13989
13989
|
console.error(`
|
|
13990
13990
|
\u{1F50D} Running comprehensive SEO audit on ${url}...
|
|
13991
13991
|
`);
|
|
13992
|
+
const withTimeout = async (name, promise, timeoutMs) => {
|
|
13993
|
+
let timer;
|
|
13994
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
13995
|
+
timer = setTimeout(() => reject(new Error(`${name} timed out`)), timeoutMs);
|
|
13996
|
+
});
|
|
13997
|
+
try {
|
|
13998
|
+
return await Promise.race([promise, timeoutPromise]);
|
|
13999
|
+
} finally {
|
|
14000
|
+
clearTimeout(timer);
|
|
14001
|
+
}
|
|
14002
|
+
};
|
|
13992
14003
|
console.error("\u{1F4CB} Phase 1: Crawlability checks + page fetch (parallel)...");
|
|
13993
14004
|
const [crawlabilityResult, fetchResult] = await Promise.all([
|
|
13994
|
-
|
|
14005
|
+
// robots.txt, sitemaps and the redirect chain run one after another; a site that is slow
|
|
14006
|
+
// on each could hold the page for minutes, so the whole group gets 45s.
|
|
14007
|
+
withTimeout("Crawlability checks", runCrawlabilityChecks(url), 45e3).catch((err) => {
|
|
13995
14008
|
console.error("Crawlability check failed:", err);
|
|
13996
14009
|
return [];
|
|
13997
14010
|
}),
|
|
@@ -14142,17 +14155,6 @@ async function runFullAudit(options) {
|
|
|
14142
14155
|
}
|
|
14143
14156
|
}
|
|
14144
14157
|
console.error("\u{1F517} Phase 3: Running async checks (parallel)...");
|
|
14145
|
-
const withTimeout = async (name, promise, timeoutMs) => {
|
|
14146
|
-
let timer;
|
|
14147
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
14148
|
-
timer = setTimeout(() => reject(new Error(`${name} timed out`)), timeoutMs);
|
|
14149
|
-
});
|
|
14150
|
-
try {
|
|
14151
|
-
return await Promise.race([promise, timeoutPromise]);
|
|
14152
|
-
} finally {
|
|
14153
|
-
clearTimeout(timer);
|
|
14154
|
-
}
|
|
14155
|
-
};
|
|
14156
14158
|
const safeAsync = async (name, fn, timeoutMs = 1e4) => {
|
|
14157
14159
|
try {
|
|
14158
14160
|
const result = await withTimeout(name, fn(), timeoutMs);
|
|
@@ -16562,6 +16564,50 @@ async function extractSeedKeywords(url) {
|
|
|
16562
16564
|
}
|
|
16563
16565
|
}
|
|
16564
16566
|
|
|
16567
|
+
// src/keywords/llm.ts
|
|
16568
|
+
import OpenAI from "openai";
|
|
16569
|
+
function openAIKeywordLLM(apiKey, opts = {}) {
|
|
16570
|
+
const openai = new OpenAI({ apiKey });
|
|
16571
|
+
const openAIModel = opts.model;
|
|
16572
|
+
return {
|
|
16573
|
+
async complete(prompt, opts2 = {}) {
|
|
16574
|
+
const response = await openai.chat.completions.create({
|
|
16575
|
+
model: opts2.best ? "gpt-4o" : openAIModel ?? "gpt-4o-mini",
|
|
16576
|
+
messages: [
|
|
16577
|
+
...opts2.system ? [{ role: "system", content: opts2.system }] : [],
|
|
16578
|
+
{ role: "user", content: prompt }
|
|
16579
|
+
],
|
|
16580
|
+
response_format: { type: "json_object" },
|
|
16581
|
+
temperature: opts2.temperature ?? 0.3,
|
|
16582
|
+
...opts2.maxTokens ? { max_tokens: opts2.maxTokens } : {}
|
|
16583
|
+
});
|
|
16584
|
+
return response.choices[0]?.message?.content || "";
|
|
16585
|
+
},
|
|
16586
|
+
async embed(texts) {
|
|
16587
|
+
const response = await openai.embeddings.create({ model: "text-embedding-3-small", input: texts });
|
|
16588
|
+
return response.data.map((item) => item.embedding);
|
|
16589
|
+
}
|
|
16590
|
+
};
|
|
16591
|
+
}
|
|
16592
|
+
function keywordLLM(options) {
|
|
16593
|
+
return options.llm ?? (options.openaiApiKey ? openAIKeywordLLM(options.openaiApiKey) : void 0);
|
|
16594
|
+
}
|
|
16595
|
+
function parseJsonReply(text) {
|
|
16596
|
+
const cleaned = text.replace(/<think>[\s\S]*?<\/think>/gi, "");
|
|
16597
|
+
const start = cleaned.indexOf("{");
|
|
16598
|
+
const end = cleaned.lastIndexOf("}");
|
|
16599
|
+
if (start === -1 || end <= start) return null;
|
|
16600
|
+
const candidate = cleaned.slice(start, end + 1);
|
|
16601
|
+
for (const attempt of [candidate, candidate.replace(/,\s*([}\]])/g, "$1")]) {
|
|
16602
|
+
try {
|
|
16603
|
+
const parsed = JSON.parse(attempt);
|
|
16604
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
16605
|
+
} catch {
|
|
16606
|
+
}
|
|
16607
|
+
}
|
|
16608
|
+
return null;
|
|
16609
|
+
}
|
|
16610
|
+
|
|
16565
16611
|
// src/keywords/site-crawler.ts
|
|
16566
16612
|
import * as cheerio40 from "cheerio";
|
|
16567
16613
|
var EXCLUDED_PATHS = [
|
|
@@ -16771,7 +16817,11 @@ function extractKeyPhrases(crawlResult) {
|
|
|
16771
16817
|
}
|
|
16772
16818
|
|
|
16773
16819
|
// src/keywords/ai-summarizer.ts
|
|
16774
|
-
|
|
16820
|
+
function summarizerLLM(options) {
|
|
16821
|
+
const llm = options.llm ?? (options.openaiApiKey ? openAIKeywordLLM(options.openaiApiKey, { model: options.model }) : void 0);
|
|
16822
|
+
if (!llm) throw new Error("An AI provider is required to summarize the site");
|
|
16823
|
+
return llm;
|
|
16824
|
+
}
|
|
16775
16825
|
var SUMMARIZER_PROMPT = `You are an expert SEO analyst. Analyze the following website content and extract key information for keyword research.
|
|
16776
16826
|
|
|
16777
16827
|
WEBSITE CONTENT:
|
|
@@ -16814,37 +16864,26 @@ IMPORTANT:
|
|
|
16814
16864
|
|
|
16815
16865
|
Respond ONLY with the JSON object, no other text.`;
|
|
16816
16866
|
async function summarizeSite(crawlResult, options) {
|
|
16817
|
-
const
|
|
16818
|
-
const
|
|
16867
|
+
const llm = summarizerLLM(options);
|
|
16868
|
+
const maxContent = llm.maxContentChars ?? 15e3;
|
|
16819
16869
|
let content = crawlResult.aggregatedContent;
|
|
16820
|
-
if (content.length >
|
|
16821
|
-
content = content.substring(0,
|
|
16870
|
+
if (content.length > maxContent) {
|
|
16871
|
+
content = content.substring(0, maxContent) + "\n...[truncated]";
|
|
16822
16872
|
}
|
|
16823
16873
|
const headings = crawlResult.uniqueHeadings.slice(0, 30).join("\n");
|
|
16824
16874
|
const prompt = SUMMARIZER_PROMPT.replace("{content}", content).replace("{headings}", headings).replace("{productPages}", String(crawlResult.detectedPageTypes.product)).replace("{pricingPages}", String(crawlResult.detectedPageTypes.pricing)).replace("{blogPages}", String(crawlResult.detectedPageTypes.blog)).replace("{featurePages}", String(crawlResult.detectedPageTypes.feature));
|
|
16825
16875
|
console.log("\u{1F916} Analyzing site content with AI...");
|
|
16826
16876
|
try {
|
|
16827
|
-
const
|
|
16828
|
-
|
|
16829
|
-
messages: [
|
|
16830
|
-
{
|
|
16831
|
-
role: "system",
|
|
16832
|
-
content: "You are an expert SEO analyst. Always respond with valid JSON only."
|
|
16833
|
-
},
|
|
16834
|
-
{
|
|
16835
|
-
role: "user",
|
|
16836
|
-
content: prompt
|
|
16837
|
-
}
|
|
16838
|
-
],
|
|
16877
|
+
const responseText = await llm.complete(prompt, {
|
|
16878
|
+
system: "You are an expert SEO analyst. Always respond with valid JSON only.",
|
|
16839
16879
|
temperature: 0.3,
|
|
16840
|
-
|
|
16880
|
+
maxTokens: options.maxTokens || 2e3
|
|
16841
16881
|
});
|
|
16842
|
-
const
|
|
16843
|
-
|
|
16844
|
-
if (!jsonMatch) {
|
|
16882
|
+
const parsed = parseJsonReply(responseText);
|
|
16883
|
+
if (!parsed) {
|
|
16845
16884
|
throw new Error("No JSON found in response");
|
|
16846
16885
|
}
|
|
16847
|
-
const summary =
|
|
16886
|
+
const summary = parsed;
|
|
16848
16887
|
return {
|
|
16849
16888
|
productName: summary.productName || "Unknown Product",
|
|
16850
16889
|
productDescription: summary.productDescription || "No description available",
|
|
@@ -16906,7 +16945,7 @@ function createFallbackSummary(crawlResult) {
|
|
|
16906
16945
|
};
|
|
16907
16946
|
}
|
|
16908
16947
|
async function enhanceSummaryWithCompetitors(summary, competitors, options) {
|
|
16909
|
-
const
|
|
16948
|
+
const llm = summarizerLLM(options);
|
|
16910
16949
|
const prompt = `Given this product summary:
|
|
16911
16950
|
Product: ${summary.productName}
|
|
16912
16951
|
Description: ${summary.productDescription}
|
|
@@ -16925,19 +16964,13 @@ Analyze the competitive positioning. If this product focuses on a niche segment
|
|
|
16925
16964
|
"updatedUncertainties": ["uncertainty1", "uncertainty2"]
|
|
16926
16965
|
}`;
|
|
16927
16966
|
try {
|
|
16928
|
-
const
|
|
16929
|
-
|
|
16930
|
-
messages: [
|
|
16931
|
-
{ role: "system", content: "Respond with valid JSON only." },
|
|
16932
|
-
{ role: "user", content: prompt }
|
|
16933
|
-
],
|
|
16967
|
+
const responseText = await llm.complete(prompt, {
|
|
16968
|
+
system: "Respond with valid JSON only.",
|
|
16934
16969
|
temperature: 0.3,
|
|
16935
|
-
|
|
16970
|
+
maxTokens: 1e3
|
|
16936
16971
|
});
|
|
16937
|
-
const
|
|
16938
|
-
|
|
16939
|
-
if (jsonMatch) {
|
|
16940
|
-
const analysis = JSON.parse(jsonMatch[0]);
|
|
16972
|
+
const analysis = parseJsonReply(responseText);
|
|
16973
|
+
if (analysis) {
|
|
16941
16974
|
return {
|
|
16942
16975
|
...summary,
|
|
16943
16976
|
competitivePositioning: analysis.competitivePositioning || "",
|
|
@@ -17024,7 +17057,6 @@ function generateUncertaintyQuestions(summary) {
|
|
|
17024
17057
|
}
|
|
17025
17058
|
|
|
17026
17059
|
// src/keywords/nlp-analysis.ts
|
|
17027
|
-
import OpenAI2 from "openai";
|
|
17028
17060
|
var STOP_WORDS2 = /* @__PURE__ */ new Set([
|
|
17029
17061
|
"a",
|
|
17030
17062
|
"an",
|
|
@@ -17309,19 +17341,18 @@ function createTermVectors(keywords, vocabulary) {
|
|
|
17309
17341
|
return vector;
|
|
17310
17342
|
});
|
|
17311
17343
|
}
|
|
17344
|
+
async function embedBatch(embedder, batch) {
|
|
17345
|
+
if (typeof embedder === "function") return embedder(batch);
|
|
17346
|
+
const response = await embedder.embeddings.create({ model: "text-embedding-3-small", input: batch });
|
|
17347
|
+
return response.data.map((item) => item.embedding);
|
|
17348
|
+
}
|
|
17312
17349
|
async function getEmbeddings(texts, openai) {
|
|
17313
17350
|
const embeddings = [];
|
|
17314
17351
|
const batchSize = 100;
|
|
17315
17352
|
for (let i = 0; i < texts.length; i += batchSize) {
|
|
17316
17353
|
const batch = texts.slice(i, i + batchSize);
|
|
17317
17354
|
try {
|
|
17318
|
-
|
|
17319
|
-
model: "text-embedding-3-small",
|
|
17320
|
-
input: batch
|
|
17321
|
-
});
|
|
17322
|
-
for (const item of response.data) {
|
|
17323
|
-
embeddings.push(item.embedding);
|
|
17324
|
-
}
|
|
17355
|
+
embeddings.push(...await embedBatch(openai, batch));
|
|
17325
17356
|
} catch (error) {
|
|
17326
17357
|
console.warn("Embedding API failed, using fallback BOW vectors");
|
|
17327
17358
|
for (const text of batch) {
|
|
@@ -17476,12 +17507,12 @@ async function runNLPAnalysis(content, options = {}) {
|
|
|
17476
17507
|
const entityPhrases = extractEntityPhrases(combinedText);
|
|
17477
17508
|
let clusters = [];
|
|
17478
17509
|
const keywordsToCluster = tfidfKeywords.slice(0, 50).map((k) => k.term);
|
|
17479
|
-
|
|
17510
|
+
const embed = keywordLLM(options)?.embed;
|
|
17511
|
+
if (embed && keywordsToCluster.length > 0) {
|
|
17480
17512
|
try {
|
|
17481
|
-
const openai = new OpenAI2({ apiKey: options.openaiApiKey });
|
|
17482
17513
|
clusters = await clusterKeywordsByEmbedding(
|
|
17483
17514
|
keywordsToCluster,
|
|
17484
|
-
|
|
17515
|
+
(texts) => embed(texts),
|
|
17485
17516
|
options.numClusters
|
|
17486
17517
|
);
|
|
17487
17518
|
} catch (error) {
|
|
@@ -17824,9 +17855,13 @@ async function searchHackerNews(query, maxResults = 10) {
|
|
|
17824
17855
|
}
|
|
17825
17856
|
|
|
17826
17857
|
// src/keywords/llm-judge.ts
|
|
17827
|
-
|
|
17858
|
+
function judgeLLM(options) {
|
|
17859
|
+
const llm = keywordLLM(options);
|
|
17860
|
+
if (!llm) throw new Error("An AI provider is required to judge tool ideas");
|
|
17861
|
+
return llm;
|
|
17862
|
+
}
|
|
17828
17863
|
async function evaluateToolFeasibility(toolIdea, siteSummary, competitiveData, options) {
|
|
17829
|
-
const
|
|
17864
|
+
const llm = judgeLLM(options);
|
|
17830
17865
|
const minFeasibility = options.minFeasibilityScore ?? 3;
|
|
17831
17866
|
const minSpecificity = options.minSpecificityScore ?? 3;
|
|
17832
17867
|
const minProductFit = options.minProductFitScore ?? 2;
|
|
@@ -17902,13 +17937,10 @@ Respond in JSON:
|
|
|
17902
17937
|
"blockers": ["<issues that prevent building>"],
|
|
17903
17938
|
"improvements": ["<suggestions to make it more specific/buildable>"]
|
|
17904
17939
|
}`;
|
|
17905
|
-
const
|
|
17906
|
-
|
|
17907
|
-
|
|
17908
|
-
|
|
17909
|
-
temperature: 0.3
|
|
17910
|
-
});
|
|
17911
|
-
const result = JSON.parse(response.choices[0].message.content || "{}");
|
|
17940
|
+
const result = parseJsonReply(await llm.complete(prompt, { temperature: 0.3 })) ?? {};
|
|
17941
|
+
for (const key of ["feasibility", "specificity", "productFit", "marketOpportunity"]) {
|
|
17942
|
+
result[key] = Number(result[key]) || 0;
|
|
17943
|
+
}
|
|
17912
17944
|
const overallScore = result.feasibility * 0.3 + result.specificity * 0.3 + result.productFit * 0.2 + result.marketOpportunity * 0.2;
|
|
17913
17945
|
const passes = result.feasibility >= minFeasibility && result.specificity >= minSpecificity && result.productFit >= minProductFit;
|
|
17914
17946
|
return {
|
|
@@ -17924,7 +17956,7 @@ Respond in JSON:
|
|
|
17924
17956
|
};
|
|
17925
17957
|
}
|
|
17926
17958
|
async function enhanceToolIdea(toolIdea, siteSummary, competitiveData, options) {
|
|
17927
|
-
const
|
|
17959
|
+
const llm = judgeLLM(options);
|
|
17928
17960
|
const feasibilityScore = await evaluateToolFeasibility(
|
|
17929
17961
|
toolIdea,
|
|
17930
17962
|
siteSummary,
|
|
@@ -17969,13 +18001,7 @@ ${competitiveData?.openSourceOptions.slice(0, 3).map((t) => `- OSS: ${t.name} ($
|
|
|
17969
18001
|
"canUseExistingOSS": <true if an OSS library does most of the work>,
|
|
17970
18002
|
"ossLibrary": "<name of OSS library to use, or null>"
|
|
17971
18003
|
}`;
|
|
17972
|
-
const
|
|
17973
|
-
model: "gpt-4o",
|
|
17974
|
-
messages: [{ role: "user", content: prompt }],
|
|
17975
|
-
response_format: { type: "json_object" },
|
|
17976
|
-
temperature: 0.5
|
|
17977
|
-
});
|
|
17978
|
-
const enhanced = JSON.parse(response.choices[0].message.content || "{}");
|
|
18004
|
+
const enhanced = parseJsonReply(await llm.complete(prompt, { temperature: 0.5, best: true })) ?? {};
|
|
17979
18005
|
const priority = Math.round(
|
|
17980
18006
|
feasibilityScore.overallScore * 20 + toolIdea.volume / 1e3 * 10 + (100 - toolIdea.difficulty) / 100 * 20
|
|
17981
18007
|
);
|
|
@@ -18026,7 +18052,7 @@ async function evaluateAndEnhanceToolIdeas(toolIdeas, siteSummary, getCompetitiv
|
|
|
18026
18052
|
|
|
18027
18053
|
// src/keywords/ai-keyword-engine.ts
|
|
18028
18054
|
async function runAIKeywordResearch(options) {
|
|
18029
|
-
const { url, tier,
|
|
18055
|
+
const { url, tier, maxPages = 20 } = options;
|
|
18030
18056
|
console.log(`
|
|
18031
18057
|
\u{1F52C} Starting AI Keyword Research (${tier} tier)...
|
|
18032
18058
|
`);
|
|
@@ -18034,10 +18060,11 @@ async function runAIKeywordResearch(options) {
|
|
|
18034
18060
|
if (tier === "free") {
|
|
18035
18061
|
return runFreeKeywordResearch(crawlResult, options);
|
|
18036
18062
|
} else {
|
|
18037
|
-
|
|
18038
|
-
|
|
18063
|
+
const llm = keywordLLM(options);
|
|
18064
|
+
if (!llm) {
|
|
18065
|
+
throw new Error("An AI provider (or an OpenAI API key) is required for paid tier keyword research");
|
|
18039
18066
|
}
|
|
18040
|
-
return runPaidKeywordResearch(crawlResult, options);
|
|
18067
|
+
return runPaidKeywordResearch(crawlResult, options, llm);
|
|
18041
18068
|
}
|
|
18042
18069
|
}
|
|
18043
18070
|
async function runFreeKeywordResearch(crawlResult, options) {
|
|
@@ -18091,13 +18118,11 @@ async function runFreeKeywordResearch(crawlResult, options) {
|
|
|
18091
18118
|
]
|
|
18092
18119
|
};
|
|
18093
18120
|
}
|
|
18094
|
-
async function runPaidKeywordResearch(crawlResult, options) {
|
|
18095
|
-
const {
|
|
18121
|
+
async function runPaidKeywordResearch(crawlResult, options, llm) {
|
|
18122
|
+
const { userContext, wizardResponses, ciMode, competitiveSearchKeys } = options;
|
|
18096
18123
|
const enableEnhancedToolIdeas = options.enableEnhancedToolIdeas !== false;
|
|
18097
18124
|
console.log("\u{1F680} Running full AI analysis...");
|
|
18098
|
-
let siteSummary = await summarizeSite(crawlResult, {
|
|
18099
|
-
openaiApiKey
|
|
18100
|
-
});
|
|
18125
|
+
let siteSummary = await summarizeSite(crawlResult, { llm });
|
|
18101
18126
|
if (userContext) {
|
|
18102
18127
|
siteSummary = applyUserContext(siteSummary, userContext);
|
|
18103
18128
|
}
|
|
@@ -18106,7 +18131,7 @@ async function runPaidKeywordResearch(crawlResult, options) {
|
|
|
18106
18131
|
}
|
|
18107
18132
|
const documents = crawlResult.pages.map((p) => p.mainContent);
|
|
18108
18133
|
const nlpAnalysis = await runNLPAnalysis(documents, {
|
|
18109
|
-
|
|
18134
|
+
llm,
|
|
18110
18135
|
numClusters: 8,
|
|
18111
18136
|
numTopics: 6
|
|
18112
18137
|
});
|
|
@@ -18133,13 +18158,13 @@ async function runPaidKeywordResearch(crawlResult, options) {
|
|
|
18133
18158
|
nlpAnalysis
|
|
18134
18159
|
);
|
|
18135
18160
|
let freeToolIdeas;
|
|
18136
|
-
if (enableEnhancedToolIdeas
|
|
18161
|
+
if (enableEnhancedToolIdeas) {
|
|
18137
18162
|
console.log("\u{1F52C} Running enhanced tool idea analysis with competitive research...");
|
|
18138
18163
|
freeToolIdeas = await generateEnhancedFreeToolIdeas(
|
|
18139
18164
|
opportunities,
|
|
18140
18165
|
siteSummary,
|
|
18141
18166
|
{
|
|
18142
|
-
|
|
18167
|
+
llm,
|
|
18143
18168
|
...competitiveSearchKeys
|
|
18144
18169
|
}
|
|
18145
18170
|
);
|
|
@@ -18237,7 +18262,7 @@ async function generateEnhancedFreeToolIdeas(opportunities, summary, options) {
|
|
|
18237
18262
|
kw.volume >= 300 && // Slightly lower threshold
|
|
18238
18263
|
(kw.keyword.includes("generator") || kw.keyword.includes("tool") || kw.keyword.includes("maker") || kw.keyword.includes("builder") || kw.keyword.includes("converter") || kw.keyword.includes("calculator") || kw.keyword.includes("checker") || kw.keyword.includes("validator") || kw.keyword.includes("formatter") || kw.keyword.includes("free") || kw.intent === "informational")
|
|
18239
18264
|
);
|
|
18240
|
-
const basicIdeas = toolKeywords.slice(0, 15).map((kw) => ({
|
|
18265
|
+
const basicIdeas = toolKeywords.slice(0, options.llm.maxToolIdeas ?? 15).map((kw) => ({
|
|
18241
18266
|
keyword: kw.keyword,
|
|
18242
18267
|
toolName: generateToolName(kw.keyword),
|
|
18243
18268
|
toolDescription: generateToolDescription(kw.keyword, summary),
|
|
@@ -18267,7 +18292,7 @@ async function generateEnhancedFreeToolIdeas(opportunities, summary, options) {
|
|
|
18267
18292
|
basicIdeas,
|
|
18268
18293
|
summary,
|
|
18269
18294
|
getCompetitiveData,
|
|
18270
|
-
{
|
|
18295
|
+
{ llm: options.llm }
|
|
18271
18296
|
);
|
|
18272
18297
|
return enhancedIdeas.map((enhanced) => ({
|
|
18273
18298
|
keyword: enhanced.keyword,
|
|
@@ -18598,10 +18623,9 @@ async function startWizardSession(url, options = {}) {
|
|
|
18598
18623
|
console.log("\u{1F50D} Analyzing your site...");
|
|
18599
18624
|
const crawlResult = await crawlSite(url, { maxPages: 10 });
|
|
18600
18625
|
let siteSummary;
|
|
18601
|
-
|
|
18602
|
-
|
|
18603
|
-
|
|
18604
|
-
});
|
|
18626
|
+
const llm = keywordLLM(options);
|
|
18627
|
+
if (llm) {
|
|
18628
|
+
siteSummary = await summarizeSite(crawlResult, { llm });
|
|
18605
18629
|
} else {
|
|
18606
18630
|
siteSummary = {
|
|
18607
18631
|
productName: crawlResult.domain,
|
|
@@ -23037,7 +23061,7 @@ async function executeAgent(agent, options = {}) {
|
|
|
23037
23061
|
}
|
|
23038
23062
|
}
|
|
23039
23063
|
async function runDirectAnalysis(url) {
|
|
23040
|
-
const { analyzeUrl: analyzeUrl2 } = await import("./analyzer-
|
|
23064
|
+
const { analyzeUrl: analyzeUrl2 } = await import("./analyzer-MCBRQHB7.mjs");
|
|
23041
23065
|
const result = await analyzeUrl2({ url });
|
|
23042
23066
|
if (!result.success || !result.data) {
|
|
23043
23067
|
throw new Error(result.error || "Analysis failed");
|
|
@@ -31515,24 +31539,32 @@ var RankTracker = class {
|
|
|
31515
31539
|
}).eq("id", keywordId);
|
|
31516
31540
|
if (updateError) {
|
|
31517
31541
|
console.error(`Failed to save ranking for ${result.keyword}:`, updateError);
|
|
31542
|
+
return;
|
|
31543
|
+
}
|
|
31544
|
+
const { error: historyError } = await this.supabase.from("keyword_position_history").insert({
|
|
31545
|
+
keyword_id: keywordId,
|
|
31546
|
+
position: result.position,
|
|
31547
|
+
url: result.url,
|
|
31548
|
+
checked_at: result.checkedAt.toISOString()
|
|
31549
|
+
});
|
|
31550
|
+
if (historyError) {
|
|
31551
|
+
console.error(`Failed to record history for ${result.keyword}:`, historyError);
|
|
31518
31552
|
}
|
|
31519
31553
|
}
|
|
31520
31554
|
/**
|
|
31521
|
-
*
|
|
31522
|
-
* Note: Full history requires keyword_ranking_history table with keyword_ranking_id
|
|
31523
|
-
* For now, returns current state as single history entry
|
|
31555
|
+
* Ranking history for a keyword: one entry per check in the last `days`, newest first.
|
|
31524
31556
|
*/
|
|
31525
|
-
async getHistory(keywordId,
|
|
31526
|
-
const { data, error } = await this.supabase.from("
|
|
31557
|
+
async getHistory(keywordId, days = 30) {
|
|
31558
|
+
const { data, error } = await this.supabase.from("keyword_position_history").select("position, url, checked_at").eq("keyword_id", keywordId).gte("checked_at", new Date(Date.now() - days * 24 * 60 * 60 * 1e3).toISOString()).order("checked_at", { ascending: false });
|
|
31527
31559
|
if (error || !data) {
|
|
31528
31560
|
return [];
|
|
31529
31561
|
}
|
|
31530
|
-
return
|
|
31531
|
-
position:
|
|
31532
|
-
url:
|
|
31562
|
+
return data.map((row) => ({
|
|
31563
|
+
position: row.position,
|
|
31564
|
+
url: row.url,
|
|
31533
31565
|
serpFeatures: [],
|
|
31534
|
-
recordedAt:
|
|
31535
|
-
}
|
|
31566
|
+
recordedAt: new Date(row.checked_at)
|
|
31567
|
+
}));
|
|
31536
31568
|
}
|
|
31537
31569
|
/**
|
|
31538
31570
|
* Get keyword trends for a project
|
|
@@ -32029,14 +32061,17 @@ export {
|
|
|
32029
32061
|
injectGSCVerification,
|
|
32030
32062
|
interpolatePrompt,
|
|
32031
32063
|
isGitRepo,
|
|
32064
|
+
keywordLLM,
|
|
32032
32065
|
listFiles,
|
|
32033
32066
|
loadAgent,
|
|
32034
32067
|
loadAgentByName,
|
|
32035
32068
|
mergePages,
|
|
32069
|
+
openAIKeywordLLM,
|
|
32036
32070
|
optimizeForAI,
|
|
32037
32071
|
optimizeReadme,
|
|
32038
32072
|
parseGEOResponse,
|
|
32039
32073
|
parseGSCResponse,
|
|
32074
|
+
parseJsonReply,
|
|
32040
32075
|
parseSitemap,
|
|
32041
32076
|
prioritizeKeywords,
|
|
32042
32077
|
processWizardResponse,
|
package/package.json
CHANGED
|
@@ -41,7 +41,6 @@ async function httpGet(url, config = {}) {
|
|
|
41
41
|
signal: controller.signal,
|
|
42
42
|
redirect: maxRedirects > 0 ? "follow" : "manual"
|
|
43
43
|
});
|
|
44
|
-
clearTimeout(timeoutId);
|
|
45
44
|
const contentType = response.headers.get("content-type") || "";
|
|
46
45
|
let data;
|
|
47
46
|
if (contentType.includes("application/json")) {
|
|
@@ -49,6 +48,7 @@ async function httpGet(url, config = {}) {
|
|
|
49
48
|
} else {
|
|
50
49
|
data = await response.text();
|
|
51
50
|
}
|
|
51
|
+
clearTimeout(timeoutId);
|
|
52
52
|
if (!validateStatus(response.status)) {
|
|
53
53
|
const error = new Error(`Request failed with status ${response.status}`);
|
|
54
54
|
error.response = {
|
|
@@ -140,7 +140,6 @@ async function httpPost(url, data, config = {}) {
|
|
|
140
140
|
body: data ? JSON.stringify(data) : void 0,
|
|
141
141
|
signal: controller.signal
|
|
142
142
|
});
|
|
143
|
-
clearTimeout(timeoutId);
|
|
144
143
|
const contentType = response.headers.get("content-type") || "";
|
|
145
144
|
let responseData;
|
|
146
145
|
if (contentType.includes("application/json")) {
|
|
@@ -148,6 +147,7 @@ async function httpPost(url, data, config = {}) {
|
|
|
148
147
|
} else {
|
|
149
148
|
responseData = await response.text();
|
|
150
149
|
}
|
|
150
|
+
clearTimeout(timeoutId);
|
|
151
151
|
if (!validateStatus(response.status)) {
|
|
152
152
|
const error = new Error(`Request failed with status ${response.status}`);
|
|
153
153
|
error.response = {
|