@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/index.mjs CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  httpGet,
15
15
  httpHead,
16
16
  httpPost
17
- } from "./chunk-PJLNXOLN.mjs";
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
- runCrawlabilityChecks(url).catch((err) => {
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
- import OpenAI from "openai";
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 openai = new OpenAI({ apiKey: options.openaiApiKey });
16818
- const model = options.model || "gpt-4o-mini";
16867
+ const llm = summarizerLLM(options);
16868
+ const maxContent = llm.maxContentChars ?? 15e3;
16819
16869
  let content = crawlResult.aggregatedContent;
16820
- if (content.length > 15e3) {
16821
- content = content.substring(0, 15e3) + "\n...[truncated]";
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 response = await openai.chat.completions.create({
16828
- model,
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
- max_tokens: options.maxTokens || 2e3
16880
+ maxTokens: options.maxTokens || 2e3
16841
16881
  });
16842
- const responseText = response.choices[0]?.message?.content || "";
16843
- const jsonMatch = responseText.match(/\{[\s\S]*\}/);
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 = JSON.parse(jsonMatch[0]);
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 openai = new OpenAI({ apiKey: options.openaiApiKey });
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 response = await openai.chat.completions.create({
16929
- model: options.model || "gpt-4o-mini",
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
- max_tokens: 1e3
16970
+ maxTokens: 1e3
16936
16971
  });
16937
- const responseText = response.choices[0]?.message?.content || "";
16938
- const jsonMatch = responseText.match(/\{[\s\S]*\}/);
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
- const response = await openai.embeddings.create({
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
- if (options.openaiApiKey && keywordsToCluster.length > 0) {
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
- openai,
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
- import OpenAI3 from "openai";
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 openai = new OpenAI3({ apiKey: options.openaiApiKey });
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 response = await openai.chat.completions.create({
17906
- model: "gpt-4o-mini",
17907
- messages: [{ role: "user", content: prompt }],
17908
- response_format: { type: "json_object" },
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 openai = new OpenAI3({ apiKey: options.openaiApiKey });
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 response = await openai.chat.completions.create({
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, openaiApiKey, maxPages = 20 } = options;
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
- if (!openaiApiKey) {
18038
- throw new Error("OpenAI API key required for paid tier keyword research");
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 { openaiApiKey, userContext, wizardResponses, ciMode, competitiveSearchKeys } = options;
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
- openaiApiKey,
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 && openaiApiKey) {
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
- openaiApiKey,
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
- { openaiApiKey: options.openaiApiKey }
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
- if (options.openaiApiKey) {
18602
- siteSummary = await summarizeSite(crawlResult, {
18603
- openaiApiKey: options.openaiApiKey
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-GMURJADU.mjs");
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
- * Get ranking history for a keyword
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, _days = 30) {
31526
- const { data, error } = await this.supabase.from("keywords").select("current_position, target_url, last_checked").eq("id", keywordId).single();
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: data.current_position,
31532
- url: data.target_url,
31562
+ return data.map((row) => ({
31563
+ position: row.position,
31564
+ url: row.url,
31533
31565
  serpFeatures: [],
31534
- recordedAt: data.last_checked ? new Date(data.last_checked) : /* @__PURE__ */ new Date()
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rankcli/agent-runtime",
3
- "version": "0.0.22",
3
+ "version": "0.0.24",
4
4
  "description": "RankCLI agent runtime - executes SEO audits and fixes with AI",
5
5
  "homepage": "https://rankcli.dev",
6
6
  "main": "dist/index.js",
@@ -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 = {