@rankcli/agent-runtime 0.0.23 → 0.0.25
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.d.mts +53 -9
- package/dist/index.d.ts +53 -9
- package/dist/index.js +136 -93
- package/dist/index.mjs +133 -93
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1631,6 +1631,39 @@ declare function runKeywordResearch(options: KeywordResearchOptions): Promise<Ke
|
|
|
1631
1631
|
declare function formatKeywordReport(result: KeywordResearchResult): string;
|
|
1632
1632
|
declare function extractSeedKeywords(url: string): Promise<string[]>;
|
|
1633
1633
|
|
|
1634
|
+
interface KeywordLLM {
|
|
1635
|
+
/**
|
|
1636
|
+
* A reply to one prompt that asks for a JSON object. `best` asks for the provider's stronger
|
|
1637
|
+
* model where it has one. Replies are parsed with parseJsonReply, never trusted to be clean.
|
|
1638
|
+
*/
|
|
1639
|
+
complete(prompt: string, opts?: {
|
|
1640
|
+
system?: string;
|
|
1641
|
+
maxTokens?: number;
|
|
1642
|
+
temperature?: number;
|
|
1643
|
+
best?: boolean;
|
|
1644
|
+
}): Promise<string>;
|
|
1645
|
+
/** Embeddings for keyword clustering. Without it, clustering falls back to term vectors. */
|
|
1646
|
+
embed?(texts: string[]): Promise<number[][]>;
|
|
1647
|
+
/** Most page text worth sending in one prompt: small-context models get less. */
|
|
1648
|
+
maxContentChars?: number;
|
|
1649
|
+
/** How many tool ideas to judge (two calls each): slow local models get fewer. */
|
|
1650
|
+
maxToolIdeas?: number;
|
|
1651
|
+
}
|
|
1652
|
+
/** The engine's original backend: gpt-4o-mini, gpt-4o for `best`, JSON mode, text-embedding-3-small. */
|
|
1653
|
+
declare function openAIKeywordLLM(apiKey: string, opts?: {
|
|
1654
|
+
model?: string;
|
|
1655
|
+
}): KeywordLLM;
|
|
1656
|
+
/** The model to use: an explicit one, else OpenAI when there's a key, else none. */
|
|
1657
|
+
declare function keywordLLM(options: {
|
|
1658
|
+
llm?: KeywordLLM;
|
|
1659
|
+
openaiApiKey?: string;
|
|
1660
|
+
}): KeywordLLM | undefined;
|
|
1661
|
+
/**
|
|
1662
|
+
* The JSON object in a model reply. Local and small models wrap it in ``` fences, prepend prose
|
|
1663
|
+
* or emit <think> blocks; this takes the outermost {...} and tolerates trailing commas.
|
|
1664
|
+
*/
|
|
1665
|
+
declare function parseJsonReply(text: string): Record<string, any> | null;
|
|
1666
|
+
|
|
1634
1667
|
/**
|
|
1635
1668
|
* Site Crawler for Keyword Research
|
|
1636
1669
|
*
|
|
@@ -1687,7 +1720,7 @@ declare function extractKeyPhrases(crawlResult: SiteCrawlResult): string[];
|
|
|
1687
1720
|
/**
|
|
1688
1721
|
* AI-Powered Site Summarizer
|
|
1689
1722
|
*
|
|
1690
|
-
* Uses
|
|
1723
|
+
* Uses the configured AI provider (llm.ts) to understand what a site does from crawled content.
|
|
1691
1724
|
* Extracts: product, audience, features, industry, positioning.
|
|
1692
1725
|
*/
|
|
1693
1726
|
|
|
@@ -1724,7 +1757,10 @@ interface SiteSummary {
|
|
|
1724
1757
|
uncertainties: string[];
|
|
1725
1758
|
}
|
|
1726
1759
|
interface SummarizerOptions {
|
|
1727
|
-
|
|
1760
|
+
/** The model to ask (see llm.ts); or an OpenAI key, as before. */
|
|
1761
|
+
llm?: KeywordLLM;
|
|
1762
|
+
openaiApiKey?: string;
|
|
1763
|
+
/** OpenAI model override, with openaiApiKey. */
|
|
1728
1764
|
model?: string;
|
|
1729
1765
|
maxTokens?: number;
|
|
1730
1766
|
}
|
|
@@ -1831,10 +1867,12 @@ declare function calculateBM25(documents: string[], query: string, k1?: number,
|
|
|
1831
1867
|
docIndex: number;
|
|
1832
1868
|
score: number;
|
|
1833
1869
|
}>;
|
|
1870
|
+
/** An OpenAI client, or any function returning one embedding per text (llm.ts). */
|
|
1871
|
+
type Embedder = OpenAI | ((texts: string[]) => Promise<number[][]>);
|
|
1834
1872
|
/**
|
|
1835
1873
|
* Cluster keywords using embeddings
|
|
1836
1874
|
*/
|
|
1837
|
-
declare function clusterKeywordsByEmbedding(keywords: string[], openai:
|
|
1875
|
+
declare function clusterKeywordsByEmbedding(keywords: string[], openai: Embedder, numClusters?: number): Promise<KeywordCluster[]>;
|
|
1838
1876
|
/**
|
|
1839
1877
|
* Extract entity-like phrases (capitalized sequences, proper nouns)
|
|
1840
1878
|
*/
|
|
@@ -1848,6 +1886,8 @@ declare function extractTopics(documents: string[], numTopics?: number): TopicMo
|
|
|
1848
1886
|
* Run full NLP analysis on content
|
|
1849
1887
|
*/
|
|
1850
1888
|
declare function runNLPAnalysis(content: string | string[], options?: {
|
|
1889
|
+
/** Embeddings come from here when it has them (see llm.ts); or an OpenAI key, as before. */
|
|
1890
|
+
llm?: KeywordLLM;
|
|
1851
1891
|
openaiApiKey?: string;
|
|
1852
1892
|
numClusters?: number;
|
|
1853
1893
|
numTopics?: number;
|
|
@@ -1871,6 +1911,8 @@ type PlanTier = 'free' | 'solo' | 'pro' | 'agency';
|
|
|
1871
1911
|
interface AIKeywordResearchOptions {
|
|
1872
1912
|
url: string;
|
|
1873
1913
|
tier: PlanTier;
|
|
1914
|
+
/** The model to ask for paid-tier research (see llm.ts); or an OpenAI key, as before. */
|
|
1915
|
+
llm?: KeywordLLM;
|
|
1874
1916
|
openaiApiKey?: string;
|
|
1875
1917
|
maxPages?: number;
|
|
1876
1918
|
userContext?: {
|
|
@@ -2066,6 +2108,8 @@ interface WizardResult {
|
|
|
2066
2108
|
* Start a new wizard session
|
|
2067
2109
|
*/
|
|
2068
2110
|
declare function startWizardSession(url: string, options?: {
|
|
2111
|
+
/** The model for the site summary (see llm.ts); or an OpenAI key, as before. */
|
|
2112
|
+
llm?: KeywordLLM;
|
|
2069
2113
|
openaiApiKey?: string;
|
|
2070
2114
|
existingResponses?: WizardResponse[];
|
|
2071
2115
|
}): Promise<WizardSession>;
|
|
@@ -2370,7 +2414,9 @@ interface EnhancedToolIdea {
|
|
|
2370
2414
|
priority: number;
|
|
2371
2415
|
}
|
|
2372
2416
|
interface LLMJudgeOptions {
|
|
2373
|
-
|
|
2417
|
+
/** The model to ask (see llm.ts); or an OpenAI key, as before. */
|
|
2418
|
+
llm?: KeywordLLM;
|
|
2419
|
+
openaiApiKey?: string;
|
|
2374
2420
|
minFeasibilityScore?: number;
|
|
2375
2421
|
minSpecificityScore?: number;
|
|
2376
2422
|
minProductFitScore?: number;
|
|
@@ -4691,11 +4737,9 @@ declare class RankTracker {
|
|
|
4691
4737
|
*/
|
|
4692
4738
|
private saveRanking;
|
|
4693
4739
|
/**
|
|
4694
|
-
*
|
|
4695
|
-
* Note: Full history requires keyword_ranking_history table with keyword_ranking_id
|
|
4696
|
-
* For now, returns current state as single history entry
|
|
4740
|
+
* Ranking history for a keyword: one entry per check in the last `days`, newest first.
|
|
4697
4741
|
*/
|
|
4698
|
-
getHistory(keywordId: string,
|
|
4742
|
+
getHistory(keywordId: string, days?: number): Promise<RankHistory[]>;
|
|
4699
4743
|
/**
|
|
4700
4744
|
* Get keyword trends for a project
|
|
4701
4745
|
*/
|
|
@@ -5498,4 +5542,4 @@ declare namespace index {
|
|
|
5498
5542
|
export { type index_Framework as Framework, type index_FrameworkCheck as FrameworkCheck, type index_FrameworkInfo as FrameworkInfo, type index_FrameworkRecipe as FrameworkRecipe, index_SUPPORTED_FRAMEWORKS as SUPPORTED_FRAMEWORKS, index_detectFramework as detectFramework, index_detectFromHeaders as detectFromHeaders, index_detectFromHtml as detectFromHtml, index_detectFromPackageJson as detectFromPackageJson, index_embedRecipe as embedRecipe, index_getFrameworkChecks as getFrameworkChecks, index_getFrameworkDisplayName as getFrameworkDisplayName, index_getFrameworkIssues as getFrameworkIssues, index_getFrameworkSuggestions as getFrameworkSuggestions, index_getFrameworkSummary as getFrameworkSummary, index_getMigrationPaths as getMigrationPaths, index_getPerformanceTips as getPerformanceTips, index_getSeoChallenges as getSeoChallenges, index_getTotalFrameworkChecks as getTotalFrameworkChecks, index_loadRecipe as loadRecipe };
|
|
5499
5543
|
}
|
|
5500
5544
|
|
|
5501
|
-
export { type AIContentConfig, type AIKeywordResearchOptions, type AgentDefinition, type AgentTool, type AlertChannel, type AlertConfig, type AlertMessage, type AlertResult, type AlertSeverity, type AlertType, type AuditAlertPayload, type AuditIssue, type AuditIssueData, type AuditOptions, type AuditPRResult, type AuditReport, type AuditReportData, type AuditRunnerOptions, type AuditRunnerResult, type AuditSchedule, type BlogPostConfig, type BrandConfig, type CIAction, type CIKeywordOptions, type CIKeywordResult, COMMIT_TYPES, type ChangelogConfig, type ChangelogResult, type CitationCheckOptions, type CommitConfig, type ComparisonConfig, type ComparisonRow, type CompetitiveInsight, type CompetitiveSearchOptions, type CompetitiveSearchResult, type CompetitorComparison, type CompetitorKeywordResult, type CompetitorOverlap, type CompetitorTool, type CompetitorUrl, type ContentElements, type ContentGeneratorOptions, type ContentRecommendation, type ConventionalCommit, type CrawlResult, type CrawlStats, type CrawledPage, type CreateAuditPROptions, type CreatePROptions, type DataForSEOCredentials, type DiscoveredPage, type EnhancedToolIdea, type ExecOptions, type ExecuteOptions, type ExecutionResult, type FAQItem, type FAQSchemaOptions, type FeaturedSnippetAnalysis, type Fix, type FixFile, type FixGeneratorOptions, type FixResult, type FrameworkInfo$1 as FrameworkInfo, type FreeKeywordResult, type FreeToolIdea, type GA4Config, type GEOAlert, type GEOAlertType, type GEOHistory, type GEOHistoryOptions, type GEOQuery, type GEOReport, type GEOResult, type GEOTrend, type GSCConfig, type GSCCredentials, type GSCPerformanceData, type GSCQueryData, type GSCQueryResult, type GeneratedCode, type GeneratedContent, type GeneratedFix, type HeadingStructure, type HeadlineAnalysis, type HealthScore, ISSUE_DEFINITIONS, type ImageInfo$2 as ImageInfo, type InjectionResult, type IntentAnalysis, type IssueCategory, type IssueDefinition, type IssueSeverity, type KeyFact, type KeyFactsOptions, type KeywordAction, type KeywordCluster, type KeywordData, type KeywordDensityAnalysis, type KeywordOpportunity, type KeywordRecommendation, type KeywordResearchOptions, type KeywordResearchResult, type KeywordStats, type KeywordTopic, type KeywordTrend, type LLMCitationResult, type LLMJudgeOptions, type LLMProvider, LOCATION_CODES, type LSIKeyword, type LinkInfo$1 as LinkInfo, type Mention, type MentionType, type MetaData, type MetaFixOptions, type NGram, type NLPAnalysisResult, OG_IMAGE_SPECS, type OptimizeOptions, type PRConfig, type PRDescription, PRIORITY_WEIGHTS, type PRResult, type PageAudit, type PageData, type PageMeta, type PaidKeywordResult, type ParsedResponse, type PlanTier, type ProviderStats, type QuickWin, type RankCheckOptions, type RankCheckResult, type RankHistory, RankTracker, type RankingResult, type RankingTierLimits, type ReadabilityResult, type ReadmeConfig, type ReadmeResult, type ReportBranding, type ReportConfig, type ReportData, type ReportPageData, type RouteInfo, type SEOAnalysisResult, type SEOFixCommit, type SEOFixSummary, type SEOIssue, type SEORecommendation, type SEOScore, SEO_SCOPES, SITE_PROFILE_QUESTIONS, type ScheduledAuditConfig, type ScheduledAuditResult, type SchemaData, Schemas, type SearchIntent, type Sentiment, type SerpApiConfig, SerpClient, type SerpClientConfig, type SerpFeature, type SiteCrawlResult, type SiteProfile, type SiteSummary, type SnippetRecommendation, type SnippetType, type SocialMetaConfig, type SocialMetaFix, type SummarizerOptions, type TFIDFResult, TIER_LIMITS, type ToolFeasibilityScore, type ToolFunction, type ToolResult, type TopicClusterResult, type TopicModel, type TrackedKeyword, type TrackerConfig, type TrackingOptions, type TrendDirection, type UncertaintyAssessment, type WizardQuestion, type WizardResponse, type WizardResult, type WizardSession, type WorkflowConfig, addTrackingResult, analyzeAnchorText, analyzeCanonicalAdvanced, analyzeClientRendering, analyzeContentFreshness, analyzeDOMStructure, analyzeFeaturedSnippetPotential, analyzeHeadings, analyzeHeadline, analyzeHreflang, analyzeImages$1 as analyzeImages, analyzeKeywordDensity, analyzeKeywords, analyzeLinks, analyzeLocalSEO, analyzeMobile, analyzeModernImages, analyzeOnPage, analyzePagination, analyzePerformance, analyzeReadability, analyzeRedirectChain, analyzeRedirects, analyzeResponsiveImages, analyzeSERPPreview, analyzeSecurity, analyzeSecurityHeaders$1 as analyzeSecurityHeaders, analyzeSocialMeta, analyzeStructuredData$1 as analyzeStructuredData, analyzeTrackerBloat, analyzeUrl, analyzeUrlSafety, index$1 as analyzers, applyFixes, buildGSCApiRequest, buildGSCRequest, calculateAIVisibilityScore, calculateBM25, calculateTFIDF$1 as calculateKeywordTFIDF, calculateNextRun, calculateTFIDF, checkAIBotBlocking, checkAMP, checkAdsTxt, checkAppleTouchIcon, checkBalance, checkCertificate, checkDMARC, checkGitHubCLI, checkInternalRedirects, checkJSRenderingRatio, checkLLMCitations, checkLlmsTxt, checkMobileResources, checkPlaintextEmails, checkRedirects, checkRobots, checkRobotsTxt, checkSPF, checkSitemap, classifyIntent, classifyIntents, clusterKeywordsByEmbedding, compareCompetitorVisibility, comparePeriods, completeWizard, crawlSite, crawlUrl, createAuditPR, createFallbackSummary, createGEOHistory, createPullRequest, createSEOCommit, createSEOCommits, detectDuplicates, detectFramework$1 as detectFramework, detectGitHubPages, detectMentions, detectSoft404, detectTechnologies, detectVisibilityChanges, discoverCompetitorKeywords, discoverPagesFromLinks, discoverRoutesFromRepo, enhanceSummaryWithCompetitors, enhanceToolIdea, enrichKeywordsWithEstimates, ensureGitRepo, evaluateAndEnhanceToolIdeas, evaluateToolFeasibility, executeAgent, extractContentHash, extractEntityPhrases, extractImages, extractKeyPhrases, extractLinks, extractMeta, extractNgrams, extractSchema, extractSeedKeywords, extractTopics, fetchCoreWebVitals, findAlmostPage1Keywords, findCTROpportunities, findHtmlEntry, findLSIKeywords, findPageFiles, findTopPerformers, formatAlertMessage, formatCIResult, formatCompetitorReport, formatConventionalCommit, formatFeaturedSnippetReport, formatHeadlineReport, formatIntentReport, formatKeywordDensityReport, formatKeywordReport, formatPRBody, formatPRTitle, formatReadabilityReport, formatReport, formatSEOCommitMessage, formatTopicReport, formatWizardProgress, index as frameworks, generateAICitableContent, generateAllFixes, generateAngularSEOService, generateAspNetCoreSEO, generateAstroBaseHead, generateAstroMeta, generateBlogPost, generateBranchName, generateChangelog, generateCommitSummary, generateComparisonTable, generateCompleteSocialMetaSetup, generateDjangoSEOHelper, generateDuplicateIssues, generateEleventySEO, generateFAQSchema, generateFixes, generateGA4EnvTemplate, generateGA4ReactComponent, generateGA4Script, generateGA4ViteScript, generateGEOReport, generateGSCVerificationTag, generateGitHubActionSetup, generateGoSEO, generateHTMLReport, generateHTMLSocialMeta, generateHTMXSEO, generateHeadlineVariations, generateHugoSEO, generateJekyllSEO, generateJsonReport, generateKeyFacts, generateLaravelSEOHelper, generateMarkdownReport, generateNextAppMetadata, generateNextJsAppRouterMetadata, generateNextJsPagesRouterHead, generateNextPagesHead, generateNuxtSEOHead, generatePDFReport, generatePRDescription, generatePhoenixSEO, generateRailsSEOHelper, generateReactHelmetSocialMeta, generateReactSEOHead, generateRecommendationQueries, generateRemixMeta, generateRemixSEO, generateSecretsDoc, generateSocialMetaFix, generateSpringBootSEO, generateSvelteKitMeta, generateSvelteKitSEOHead, generateUncertaintyQuestions, generateVueSEOHead, generateWizardQuestions, generateWorkflow, getAIVisibilitySummary, getAutocompleteSuggestions, getDateRange, getExpandedSuggestions, getFrameworkSpecificFix, getFrameworkSpecificFixExtended, getGSCSetupInstructions, getGitUser, getKeywordData, getKeywordSuggestions, getMaxKdThreshold, getNextQuestion, getRelatedKeywords, getVisibilityTrend, groupKeywordsByTopic, identifyQuickWins, injectGA4, injectGSCVerification, interpolatePrompt, isGitRepo, listFiles, loadAgent, loadAgentByName, mergePages, optimizeForAI, optimizeReadme, parseGEOResponse, parseGSCResponse, parseSitemap, prioritizeKeywords, processWizardResponse, readFile, routesToUrls, runAIKeywordResearch, runAIReadinessChecks, runAdditionalChecks, runAuditWithFixes, runCIKeywordResearch, runCrawlabilityChecks, runDirectAnalysis, runFullAudit, runKeywordResearch, runNLPAnalysis, runScheduledAudit, scoreContentSEO, scoreMention, searchCompetitors, searchFormatConverters, searchHackerNews, sendAlert, sendAlerts, sendDiscordAlert, sendSlackAlert, shouldRunAudit, shouldSendAlert, startWizardSession, suggestSchemaTypes, summarizeSite, tokenize, tools, trackLLMVisibility, transformGSCData, urlSafetyDatabase, wizardResponsesToContext, writeFile, writeGitHubActionFiles };
|
|
5545
|
+
export { type AIContentConfig, type AIKeywordResearchOptions, type AgentDefinition, type AgentTool, type AlertChannel, type AlertConfig, type AlertMessage, type AlertResult, type AlertSeverity, type AlertType, type AuditAlertPayload, type AuditIssue, type AuditIssueData, type AuditOptions, type AuditPRResult, type AuditReport, type AuditReportData, type AuditRunnerOptions, type AuditRunnerResult, type AuditSchedule, type BlogPostConfig, type BrandConfig, type CIAction, type CIKeywordOptions, type CIKeywordResult, COMMIT_TYPES, type ChangelogConfig, type ChangelogResult, type CitationCheckOptions, type CommitConfig, type ComparisonConfig, type ComparisonRow, type CompetitiveInsight, type CompetitiveSearchOptions, type CompetitiveSearchResult, type CompetitorComparison, type CompetitorKeywordResult, type CompetitorOverlap, type CompetitorTool, type CompetitorUrl, type ContentElements, type ContentGeneratorOptions, type ContentRecommendation, type ConventionalCommit, type CrawlResult, type CrawlStats, type CrawledPage, type CreateAuditPROptions, type CreatePROptions, type DataForSEOCredentials, type DiscoveredPage, type EnhancedToolIdea, type ExecOptions, type ExecuteOptions, type ExecutionResult, type FAQItem, type FAQSchemaOptions, type FeaturedSnippetAnalysis, type Fix, type FixFile, type FixGeneratorOptions, type FixResult, type FrameworkInfo$1 as FrameworkInfo, type FreeKeywordResult, type FreeToolIdea, type GA4Config, type GEOAlert, type GEOAlertType, type GEOHistory, type GEOHistoryOptions, type GEOQuery, type GEOReport, type GEOResult, type GEOTrend, type GSCConfig, type GSCCredentials, type GSCPerformanceData, type GSCQueryData, type GSCQueryResult, type GeneratedCode, type GeneratedContent, type GeneratedFix, type HeadingStructure, type HeadlineAnalysis, type HealthScore, ISSUE_DEFINITIONS, type ImageInfo$2 as ImageInfo, type InjectionResult, type IntentAnalysis, type IssueCategory, type IssueDefinition, type IssueSeverity, type KeyFact, type KeyFactsOptions, type KeywordAction, type KeywordCluster, type KeywordData, type KeywordDensityAnalysis, type KeywordLLM, type KeywordOpportunity, type KeywordRecommendation, type KeywordResearchOptions, type KeywordResearchResult, type KeywordStats, type KeywordTopic, type KeywordTrend, type LLMCitationResult, type LLMJudgeOptions, type LLMProvider, LOCATION_CODES, type LSIKeyword, type LinkInfo$1 as LinkInfo, type Mention, type MentionType, type MetaData, type MetaFixOptions, type NGram, type NLPAnalysisResult, OG_IMAGE_SPECS, type OptimizeOptions, type PRConfig, type PRDescription, PRIORITY_WEIGHTS, type PRResult, type PageAudit, type PageData, type PageMeta, type PaidKeywordResult, type ParsedResponse, type PlanTier, type ProviderStats, type QuickWin, type RankCheckOptions, type RankCheckResult, type RankHistory, RankTracker, type RankingResult, type RankingTierLimits, type ReadabilityResult, type ReadmeConfig, type ReadmeResult, type ReportBranding, type ReportConfig, type ReportData, type ReportPageData, type RouteInfo, type SEOAnalysisResult, type SEOFixCommit, type SEOFixSummary, type SEOIssue, type SEORecommendation, type SEOScore, SEO_SCOPES, SITE_PROFILE_QUESTIONS, type ScheduledAuditConfig, type ScheduledAuditResult, type SchemaData, Schemas, type SearchIntent, type Sentiment, type SerpApiConfig, SerpClient, type SerpClientConfig, type SerpFeature, type SiteCrawlResult, type SiteProfile, type SiteSummary, type SnippetRecommendation, type SnippetType, type SocialMetaConfig, type SocialMetaFix, type SummarizerOptions, type TFIDFResult, TIER_LIMITS, type ToolFeasibilityScore, type ToolFunction, type ToolResult, type TopicClusterResult, type TopicModel, type TrackedKeyword, type TrackerConfig, type TrackingOptions, type TrendDirection, type UncertaintyAssessment, type WizardQuestion, type WizardResponse, type WizardResult, type WizardSession, type WorkflowConfig, addTrackingResult, analyzeAnchorText, analyzeCanonicalAdvanced, analyzeClientRendering, analyzeContentFreshness, analyzeDOMStructure, analyzeFeaturedSnippetPotential, analyzeHeadings, analyzeHeadline, analyzeHreflang, analyzeImages$1 as analyzeImages, analyzeKeywordDensity, analyzeKeywords, analyzeLinks, analyzeLocalSEO, analyzeMobile, analyzeModernImages, analyzeOnPage, analyzePagination, analyzePerformance, analyzeReadability, analyzeRedirectChain, analyzeRedirects, analyzeResponsiveImages, analyzeSERPPreview, analyzeSecurity, analyzeSecurityHeaders$1 as analyzeSecurityHeaders, analyzeSocialMeta, analyzeStructuredData$1 as analyzeStructuredData, analyzeTrackerBloat, analyzeUrl, analyzeUrlSafety, index$1 as analyzers, applyFixes, buildGSCApiRequest, buildGSCRequest, calculateAIVisibilityScore, calculateBM25, calculateTFIDF$1 as calculateKeywordTFIDF, calculateNextRun, calculateTFIDF, checkAIBotBlocking, checkAMP, checkAdsTxt, checkAppleTouchIcon, checkBalance, checkCertificate, checkDMARC, checkGitHubCLI, checkInternalRedirects, checkJSRenderingRatio, checkLLMCitations, checkLlmsTxt, checkMobileResources, checkPlaintextEmails, checkRedirects, checkRobots, checkRobotsTxt, checkSPF, checkSitemap, classifyIntent, classifyIntents, clusterKeywordsByEmbedding, compareCompetitorVisibility, comparePeriods, completeWizard, crawlSite, crawlUrl, createAuditPR, createFallbackSummary, createGEOHistory, createPullRequest, createSEOCommit, createSEOCommits, detectDuplicates, detectFramework$1 as detectFramework, detectGitHubPages, detectMentions, detectSoft404, detectTechnologies, detectVisibilityChanges, discoverCompetitorKeywords, discoverPagesFromLinks, discoverRoutesFromRepo, enhanceSummaryWithCompetitors, enhanceToolIdea, enrichKeywordsWithEstimates, ensureGitRepo, evaluateAndEnhanceToolIdeas, evaluateToolFeasibility, executeAgent, extractContentHash, extractEntityPhrases, extractImages, extractKeyPhrases, extractLinks, extractMeta, extractNgrams, extractSchema, extractSeedKeywords, extractTopics, fetchCoreWebVitals, findAlmostPage1Keywords, findCTROpportunities, findHtmlEntry, findLSIKeywords, findPageFiles, findTopPerformers, formatAlertMessage, formatCIResult, formatCompetitorReport, formatConventionalCommit, formatFeaturedSnippetReport, formatHeadlineReport, formatIntentReport, formatKeywordDensityReport, formatKeywordReport, formatPRBody, formatPRTitle, formatReadabilityReport, formatReport, formatSEOCommitMessage, formatTopicReport, formatWizardProgress, index as frameworks, generateAICitableContent, generateAllFixes, generateAngularSEOService, generateAspNetCoreSEO, generateAstroBaseHead, generateAstroMeta, generateBlogPost, generateBranchName, generateChangelog, generateCommitSummary, generateComparisonTable, generateCompleteSocialMetaSetup, generateDjangoSEOHelper, generateDuplicateIssues, generateEleventySEO, generateFAQSchema, generateFixes, generateGA4EnvTemplate, generateGA4ReactComponent, generateGA4Script, generateGA4ViteScript, generateGEOReport, generateGSCVerificationTag, generateGitHubActionSetup, generateGoSEO, generateHTMLReport, generateHTMLSocialMeta, generateHTMXSEO, generateHeadlineVariations, generateHugoSEO, generateJekyllSEO, generateJsonReport, generateKeyFacts, generateLaravelSEOHelper, generateMarkdownReport, generateNextAppMetadata, generateNextJsAppRouterMetadata, generateNextJsPagesRouterHead, generateNextPagesHead, generateNuxtSEOHead, generatePDFReport, generatePRDescription, generatePhoenixSEO, generateRailsSEOHelper, generateReactHelmetSocialMeta, generateReactSEOHead, generateRecommendationQueries, generateRemixMeta, generateRemixSEO, generateSecretsDoc, generateSocialMetaFix, generateSpringBootSEO, generateSvelteKitMeta, generateSvelteKitSEOHead, generateUncertaintyQuestions, generateVueSEOHead, generateWizardQuestions, generateWorkflow, getAIVisibilitySummary, getAutocompleteSuggestions, getDateRange, getExpandedSuggestions, getFrameworkSpecificFix, getFrameworkSpecificFixExtended, getGSCSetupInstructions, getGitUser, getKeywordData, getKeywordSuggestions, getMaxKdThreshold, getNextQuestion, getRelatedKeywords, getVisibilityTrend, groupKeywordsByTopic, identifyQuickWins, injectGA4, injectGSCVerification, interpolatePrompt, isGitRepo, keywordLLM, listFiles, loadAgent, loadAgentByName, mergePages, openAIKeywordLLM, optimizeForAI, optimizeReadme, parseGEOResponse, parseGSCResponse, parseJsonReply, parseSitemap, prioritizeKeywords, processWizardResponse, readFile, routesToUrls, runAIKeywordResearch, runAIReadinessChecks, runAdditionalChecks, runAuditWithFixes, runCIKeywordResearch, runCrawlabilityChecks, runDirectAnalysis, runFullAudit, runKeywordResearch, runNLPAnalysis, runScheduledAudit, scoreContentSEO, scoreMention, searchCompetitors, searchFormatConverters, searchHackerNews, sendAlert, sendAlerts, sendDiscordAlert, sendSlackAlert, shouldRunAudit, shouldSendAlert, startWizardSession, suggestSchemaTypes, summarizeSite, tokenize, tools, trackLLMVisibility, transformGSCData, urlSafetyDatabase, wizardResponsesToContext, writeFile, writeGitHubActionFiles };
|
package/dist/index.d.ts
CHANGED
|
@@ -1631,6 +1631,39 @@ declare function runKeywordResearch(options: KeywordResearchOptions): Promise<Ke
|
|
|
1631
1631
|
declare function formatKeywordReport(result: KeywordResearchResult): string;
|
|
1632
1632
|
declare function extractSeedKeywords(url: string): Promise<string[]>;
|
|
1633
1633
|
|
|
1634
|
+
interface KeywordLLM {
|
|
1635
|
+
/**
|
|
1636
|
+
* A reply to one prompt that asks for a JSON object. `best` asks for the provider's stronger
|
|
1637
|
+
* model where it has one. Replies are parsed with parseJsonReply, never trusted to be clean.
|
|
1638
|
+
*/
|
|
1639
|
+
complete(prompt: string, opts?: {
|
|
1640
|
+
system?: string;
|
|
1641
|
+
maxTokens?: number;
|
|
1642
|
+
temperature?: number;
|
|
1643
|
+
best?: boolean;
|
|
1644
|
+
}): Promise<string>;
|
|
1645
|
+
/** Embeddings for keyword clustering. Without it, clustering falls back to term vectors. */
|
|
1646
|
+
embed?(texts: string[]): Promise<number[][]>;
|
|
1647
|
+
/** Most page text worth sending in one prompt: small-context models get less. */
|
|
1648
|
+
maxContentChars?: number;
|
|
1649
|
+
/** How many tool ideas to judge (two calls each): slow local models get fewer. */
|
|
1650
|
+
maxToolIdeas?: number;
|
|
1651
|
+
}
|
|
1652
|
+
/** The engine's original backend: gpt-4o-mini, gpt-4o for `best`, JSON mode, text-embedding-3-small. */
|
|
1653
|
+
declare function openAIKeywordLLM(apiKey: string, opts?: {
|
|
1654
|
+
model?: string;
|
|
1655
|
+
}): KeywordLLM;
|
|
1656
|
+
/** The model to use: an explicit one, else OpenAI when there's a key, else none. */
|
|
1657
|
+
declare function keywordLLM(options: {
|
|
1658
|
+
llm?: KeywordLLM;
|
|
1659
|
+
openaiApiKey?: string;
|
|
1660
|
+
}): KeywordLLM | undefined;
|
|
1661
|
+
/**
|
|
1662
|
+
* The JSON object in a model reply. Local and small models wrap it in ``` fences, prepend prose
|
|
1663
|
+
* or emit <think> blocks; this takes the outermost {...} and tolerates trailing commas.
|
|
1664
|
+
*/
|
|
1665
|
+
declare function parseJsonReply(text: string): Record<string, any> | null;
|
|
1666
|
+
|
|
1634
1667
|
/**
|
|
1635
1668
|
* Site Crawler for Keyword Research
|
|
1636
1669
|
*
|
|
@@ -1687,7 +1720,7 @@ declare function extractKeyPhrases(crawlResult: SiteCrawlResult): string[];
|
|
|
1687
1720
|
/**
|
|
1688
1721
|
* AI-Powered Site Summarizer
|
|
1689
1722
|
*
|
|
1690
|
-
* Uses
|
|
1723
|
+
* Uses the configured AI provider (llm.ts) to understand what a site does from crawled content.
|
|
1691
1724
|
* Extracts: product, audience, features, industry, positioning.
|
|
1692
1725
|
*/
|
|
1693
1726
|
|
|
@@ -1724,7 +1757,10 @@ interface SiteSummary {
|
|
|
1724
1757
|
uncertainties: string[];
|
|
1725
1758
|
}
|
|
1726
1759
|
interface SummarizerOptions {
|
|
1727
|
-
|
|
1760
|
+
/** The model to ask (see llm.ts); or an OpenAI key, as before. */
|
|
1761
|
+
llm?: KeywordLLM;
|
|
1762
|
+
openaiApiKey?: string;
|
|
1763
|
+
/** OpenAI model override, with openaiApiKey. */
|
|
1728
1764
|
model?: string;
|
|
1729
1765
|
maxTokens?: number;
|
|
1730
1766
|
}
|
|
@@ -1831,10 +1867,12 @@ declare function calculateBM25(documents: string[], query: string, k1?: number,
|
|
|
1831
1867
|
docIndex: number;
|
|
1832
1868
|
score: number;
|
|
1833
1869
|
}>;
|
|
1870
|
+
/** An OpenAI client, or any function returning one embedding per text (llm.ts). */
|
|
1871
|
+
type Embedder = OpenAI | ((texts: string[]) => Promise<number[][]>);
|
|
1834
1872
|
/**
|
|
1835
1873
|
* Cluster keywords using embeddings
|
|
1836
1874
|
*/
|
|
1837
|
-
declare function clusterKeywordsByEmbedding(keywords: string[], openai:
|
|
1875
|
+
declare function clusterKeywordsByEmbedding(keywords: string[], openai: Embedder, numClusters?: number): Promise<KeywordCluster[]>;
|
|
1838
1876
|
/**
|
|
1839
1877
|
* Extract entity-like phrases (capitalized sequences, proper nouns)
|
|
1840
1878
|
*/
|
|
@@ -1848,6 +1886,8 @@ declare function extractTopics(documents: string[], numTopics?: number): TopicMo
|
|
|
1848
1886
|
* Run full NLP analysis on content
|
|
1849
1887
|
*/
|
|
1850
1888
|
declare function runNLPAnalysis(content: string | string[], options?: {
|
|
1889
|
+
/** Embeddings come from here when it has them (see llm.ts); or an OpenAI key, as before. */
|
|
1890
|
+
llm?: KeywordLLM;
|
|
1851
1891
|
openaiApiKey?: string;
|
|
1852
1892
|
numClusters?: number;
|
|
1853
1893
|
numTopics?: number;
|
|
@@ -1871,6 +1911,8 @@ type PlanTier = 'free' | 'solo' | 'pro' | 'agency';
|
|
|
1871
1911
|
interface AIKeywordResearchOptions {
|
|
1872
1912
|
url: string;
|
|
1873
1913
|
tier: PlanTier;
|
|
1914
|
+
/** The model to ask for paid-tier research (see llm.ts); or an OpenAI key, as before. */
|
|
1915
|
+
llm?: KeywordLLM;
|
|
1874
1916
|
openaiApiKey?: string;
|
|
1875
1917
|
maxPages?: number;
|
|
1876
1918
|
userContext?: {
|
|
@@ -2066,6 +2108,8 @@ interface WizardResult {
|
|
|
2066
2108
|
* Start a new wizard session
|
|
2067
2109
|
*/
|
|
2068
2110
|
declare function startWizardSession(url: string, options?: {
|
|
2111
|
+
/** The model for the site summary (see llm.ts); or an OpenAI key, as before. */
|
|
2112
|
+
llm?: KeywordLLM;
|
|
2069
2113
|
openaiApiKey?: string;
|
|
2070
2114
|
existingResponses?: WizardResponse[];
|
|
2071
2115
|
}): Promise<WizardSession>;
|
|
@@ -2370,7 +2414,9 @@ interface EnhancedToolIdea {
|
|
|
2370
2414
|
priority: number;
|
|
2371
2415
|
}
|
|
2372
2416
|
interface LLMJudgeOptions {
|
|
2373
|
-
|
|
2417
|
+
/** The model to ask (see llm.ts); or an OpenAI key, as before. */
|
|
2418
|
+
llm?: KeywordLLM;
|
|
2419
|
+
openaiApiKey?: string;
|
|
2374
2420
|
minFeasibilityScore?: number;
|
|
2375
2421
|
minSpecificityScore?: number;
|
|
2376
2422
|
minProductFitScore?: number;
|
|
@@ -4691,11 +4737,9 @@ declare class RankTracker {
|
|
|
4691
4737
|
*/
|
|
4692
4738
|
private saveRanking;
|
|
4693
4739
|
/**
|
|
4694
|
-
*
|
|
4695
|
-
* Note: Full history requires keyword_ranking_history table with keyword_ranking_id
|
|
4696
|
-
* For now, returns current state as single history entry
|
|
4740
|
+
* Ranking history for a keyword: one entry per check in the last `days`, newest first.
|
|
4697
4741
|
*/
|
|
4698
|
-
getHistory(keywordId: string,
|
|
4742
|
+
getHistory(keywordId: string, days?: number): Promise<RankHistory[]>;
|
|
4699
4743
|
/**
|
|
4700
4744
|
* Get keyword trends for a project
|
|
4701
4745
|
*/
|
|
@@ -5498,4 +5542,4 @@ declare namespace index {
|
|
|
5498
5542
|
export { type index_Framework as Framework, type index_FrameworkCheck as FrameworkCheck, type index_FrameworkInfo as FrameworkInfo, type index_FrameworkRecipe as FrameworkRecipe, index_SUPPORTED_FRAMEWORKS as SUPPORTED_FRAMEWORKS, index_detectFramework as detectFramework, index_detectFromHeaders as detectFromHeaders, index_detectFromHtml as detectFromHtml, index_detectFromPackageJson as detectFromPackageJson, index_embedRecipe as embedRecipe, index_getFrameworkChecks as getFrameworkChecks, index_getFrameworkDisplayName as getFrameworkDisplayName, index_getFrameworkIssues as getFrameworkIssues, index_getFrameworkSuggestions as getFrameworkSuggestions, index_getFrameworkSummary as getFrameworkSummary, index_getMigrationPaths as getMigrationPaths, index_getPerformanceTips as getPerformanceTips, index_getSeoChallenges as getSeoChallenges, index_getTotalFrameworkChecks as getTotalFrameworkChecks, index_loadRecipe as loadRecipe };
|
|
5499
5543
|
}
|
|
5500
5544
|
|
|
5501
|
-
export { type AIContentConfig, type AIKeywordResearchOptions, type AgentDefinition, type AgentTool, type AlertChannel, type AlertConfig, type AlertMessage, type AlertResult, type AlertSeverity, type AlertType, type AuditAlertPayload, type AuditIssue, type AuditIssueData, type AuditOptions, type AuditPRResult, type AuditReport, type AuditReportData, type AuditRunnerOptions, type AuditRunnerResult, type AuditSchedule, type BlogPostConfig, type BrandConfig, type CIAction, type CIKeywordOptions, type CIKeywordResult, COMMIT_TYPES, type ChangelogConfig, type ChangelogResult, type CitationCheckOptions, type CommitConfig, type ComparisonConfig, type ComparisonRow, type CompetitiveInsight, type CompetitiveSearchOptions, type CompetitiveSearchResult, type CompetitorComparison, type CompetitorKeywordResult, type CompetitorOverlap, type CompetitorTool, type CompetitorUrl, type ContentElements, type ContentGeneratorOptions, type ContentRecommendation, type ConventionalCommit, type CrawlResult, type CrawlStats, type CrawledPage, type CreateAuditPROptions, type CreatePROptions, type DataForSEOCredentials, type DiscoveredPage, type EnhancedToolIdea, type ExecOptions, type ExecuteOptions, type ExecutionResult, type FAQItem, type FAQSchemaOptions, type FeaturedSnippetAnalysis, type Fix, type FixFile, type FixGeneratorOptions, type FixResult, type FrameworkInfo$1 as FrameworkInfo, type FreeKeywordResult, type FreeToolIdea, type GA4Config, type GEOAlert, type GEOAlertType, type GEOHistory, type GEOHistoryOptions, type GEOQuery, type GEOReport, type GEOResult, type GEOTrend, type GSCConfig, type GSCCredentials, type GSCPerformanceData, type GSCQueryData, type GSCQueryResult, type GeneratedCode, type GeneratedContent, type GeneratedFix, type HeadingStructure, type HeadlineAnalysis, type HealthScore, ISSUE_DEFINITIONS, type ImageInfo$2 as ImageInfo, type InjectionResult, type IntentAnalysis, type IssueCategory, type IssueDefinition, type IssueSeverity, type KeyFact, type KeyFactsOptions, type KeywordAction, type KeywordCluster, type KeywordData, type KeywordDensityAnalysis, type KeywordOpportunity, type KeywordRecommendation, type KeywordResearchOptions, type KeywordResearchResult, type KeywordStats, type KeywordTopic, type KeywordTrend, type LLMCitationResult, type LLMJudgeOptions, type LLMProvider, LOCATION_CODES, type LSIKeyword, type LinkInfo$1 as LinkInfo, type Mention, type MentionType, type MetaData, type MetaFixOptions, type NGram, type NLPAnalysisResult, OG_IMAGE_SPECS, type OptimizeOptions, type PRConfig, type PRDescription, PRIORITY_WEIGHTS, type PRResult, type PageAudit, type PageData, type PageMeta, type PaidKeywordResult, type ParsedResponse, type PlanTier, type ProviderStats, type QuickWin, type RankCheckOptions, type RankCheckResult, type RankHistory, RankTracker, type RankingResult, type RankingTierLimits, type ReadabilityResult, type ReadmeConfig, type ReadmeResult, type ReportBranding, type ReportConfig, type ReportData, type ReportPageData, type RouteInfo, type SEOAnalysisResult, type SEOFixCommit, type SEOFixSummary, type SEOIssue, type SEORecommendation, type SEOScore, SEO_SCOPES, SITE_PROFILE_QUESTIONS, type ScheduledAuditConfig, type ScheduledAuditResult, type SchemaData, Schemas, type SearchIntent, type Sentiment, type SerpApiConfig, SerpClient, type SerpClientConfig, type SerpFeature, type SiteCrawlResult, type SiteProfile, type SiteSummary, type SnippetRecommendation, type SnippetType, type SocialMetaConfig, type SocialMetaFix, type SummarizerOptions, type TFIDFResult, TIER_LIMITS, type ToolFeasibilityScore, type ToolFunction, type ToolResult, type TopicClusterResult, type TopicModel, type TrackedKeyword, type TrackerConfig, type TrackingOptions, type TrendDirection, type UncertaintyAssessment, type WizardQuestion, type WizardResponse, type WizardResult, type WizardSession, type WorkflowConfig, addTrackingResult, analyzeAnchorText, analyzeCanonicalAdvanced, analyzeClientRendering, analyzeContentFreshness, analyzeDOMStructure, analyzeFeaturedSnippetPotential, analyzeHeadings, analyzeHeadline, analyzeHreflang, analyzeImages$1 as analyzeImages, analyzeKeywordDensity, analyzeKeywords, analyzeLinks, analyzeLocalSEO, analyzeMobile, analyzeModernImages, analyzeOnPage, analyzePagination, analyzePerformance, analyzeReadability, analyzeRedirectChain, analyzeRedirects, analyzeResponsiveImages, analyzeSERPPreview, analyzeSecurity, analyzeSecurityHeaders$1 as analyzeSecurityHeaders, analyzeSocialMeta, analyzeStructuredData$1 as analyzeStructuredData, analyzeTrackerBloat, analyzeUrl, analyzeUrlSafety, index$1 as analyzers, applyFixes, buildGSCApiRequest, buildGSCRequest, calculateAIVisibilityScore, calculateBM25, calculateTFIDF$1 as calculateKeywordTFIDF, calculateNextRun, calculateTFIDF, checkAIBotBlocking, checkAMP, checkAdsTxt, checkAppleTouchIcon, checkBalance, checkCertificate, checkDMARC, checkGitHubCLI, checkInternalRedirects, checkJSRenderingRatio, checkLLMCitations, checkLlmsTxt, checkMobileResources, checkPlaintextEmails, checkRedirects, checkRobots, checkRobotsTxt, checkSPF, checkSitemap, classifyIntent, classifyIntents, clusterKeywordsByEmbedding, compareCompetitorVisibility, comparePeriods, completeWizard, crawlSite, crawlUrl, createAuditPR, createFallbackSummary, createGEOHistory, createPullRequest, createSEOCommit, createSEOCommits, detectDuplicates, detectFramework$1 as detectFramework, detectGitHubPages, detectMentions, detectSoft404, detectTechnologies, detectVisibilityChanges, discoverCompetitorKeywords, discoverPagesFromLinks, discoverRoutesFromRepo, enhanceSummaryWithCompetitors, enhanceToolIdea, enrichKeywordsWithEstimates, ensureGitRepo, evaluateAndEnhanceToolIdeas, evaluateToolFeasibility, executeAgent, extractContentHash, extractEntityPhrases, extractImages, extractKeyPhrases, extractLinks, extractMeta, extractNgrams, extractSchema, extractSeedKeywords, extractTopics, fetchCoreWebVitals, findAlmostPage1Keywords, findCTROpportunities, findHtmlEntry, findLSIKeywords, findPageFiles, findTopPerformers, formatAlertMessage, formatCIResult, formatCompetitorReport, formatConventionalCommit, formatFeaturedSnippetReport, formatHeadlineReport, formatIntentReport, formatKeywordDensityReport, formatKeywordReport, formatPRBody, formatPRTitle, formatReadabilityReport, formatReport, formatSEOCommitMessage, formatTopicReport, formatWizardProgress, index as frameworks, generateAICitableContent, generateAllFixes, generateAngularSEOService, generateAspNetCoreSEO, generateAstroBaseHead, generateAstroMeta, generateBlogPost, generateBranchName, generateChangelog, generateCommitSummary, generateComparisonTable, generateCompleteSocialMetaSetup, generateDjangoSEOHelper, generateDuplicateIssues, generateEleventySEO, generateFAQSchema, generateFixes, generateGA4EnvTemplate, generateGA4ReactComponent, generateGA4Script, generateGA4ViteScript, generateGEOReport, generateGSCVerificationTag, generateGitHubActionSetup, generateGoSEO, generateHTMLReport, generateHTMLSocialMeta, generateHTMXSEO, generateHeadlineVariations, generateHugoSEO, generateJekyllSEO, generateJsonReport, generateKeyFacts, generateLaravelSEOHelper, generateMarkdownReport, generateNextAppMetadata, generateNextJsAppRouterMetadata, generateNextJsPagesRouterHead, generateNextPagesHead, generateNuxtSEOHead, generatePDFReport, generatePRDescription, generatePhoenixSEO, generateRailsSEOHelper, generateReactHelmetSocialMeta, generateReactSEOHead, generateRecommendationQueries, generateRemixMeta, generateRemixSEO, generateSecretsDoc, generateSocialMetaFix, generateSpringBootSEO, generateSvelteKitMeta, generateSvelteKitSEOHead, generateUncertaintyQuestions, generateVueSEOHead, generateWizardQuestions, generateWorkflow, getAIVisibilitySummary, getAutocompleteSuggestions, getDateRange, getExpandedSuggestions, getFrameworkSpecificFix, getFrameworkSpecificFixExtended, getGSCSetupInstructions, getGitUser, getKeywordData, getKeywordSuggestions, getMaxKdThreshold, getNextQuestion, getRelatedKeywords, getVisibilityTrend, groupKeywordsByTopic, identifyQuickWins, injectGA4, injectGSCVerification, interpolatePrompt, isGitRepo, listFiles, loadAgent, loadAgentByName, mergePages, optimizeForAI, optimizeReadme, parseGEOResponse, parseGSCResponse, parseSitemap, prioritizeKeywords, processWizardResponse, readFile, routesToUrls, runAIKeywordResearch, runAIReadinessChecks, runAdditionalChecks, runAuditWithFixes, runCIKeywordResearch, runCrawlabilityChecks, runDirectAnalysis, runFullAudit, runKeywordResearch, runNLPAnalysis, runScheduledAudit, scoreContentSEO, scoreMention, searchCompetitors, searchFormatConverters, searchHackerNews, sendAlert, sendAlerts, sendDiscordAlert, sendSlackAlert, shouldRunAudit, shouldSendAlert, startWizardSession, suggestSchemaTypes, summarizeSite, tokenize, tools, trackLLMVisibility, transformGSCData, urlSafetyDatabase, wizardResponsesToContext, writeFile, writeGitHubActionFiles };
|
|
5545
|
+
export { type AIContentConfig, type AIKeywordResearchOptions, type AgentDefinition, type AgentTool, type AlertChannel, type AlertConfig, type AlertMessage, type AlertResult, type AlertSeverity, type AlertType, type AuditAlertPayload, type AuditIssue, type AuditIssueData, type AuditOptions, type AuditPRResult, type AuditReport, type AuditReportData, type AuditRunnerOptions, type AuditRunnerResult, type AuditSchedule, type BlogPostConfig, type BrandConfig, type CIAction, type CIKeywordOptions, type CIKeywordResult, COMMIT_TYPES, type ChangelogConfig, type ChangelogResult, type CitationCheckOptions, type CommitConfig, type ComparisonConfig, type ComparisonRow, type CompetitiveInsight, type CompetitiveSearchOptions, type CompetitiveSearchResult, type CompetitorComparison, type CompetitorKeywordResult, type CompetitorOverlap, type CompetitorTool, type CompetitorUrl, type ContentElements, type ContentGeneratorOptions, type ContentRecommendation, type ConventionalCommit, type CrawlResult, type CrawlStats, type CrawledPage, type CreateAuditPROptions, type CreatePROptions, type DataForSEOCredentials, type DiscoveredPage, type EnhancedToolIdea, type ExecOptions, type ExecuteOptions, type ExecutionResult, type FAQItem, type FAQSchemaOptions, type FeaturedSnippetAnalysis, type Fix, type FixFile, type FixGeneratorOptions, type FixResult, type FrameworkInfo$1 as FrameworkInfo, type FreeKeywordResult, type FreeToolIdea, type GA4Config, type GEOAlert, type GEOAlertType, type GEOHistory, type GEOHistoryOptions, type GEOQuery, type GEOReport, type GEOResult, type GEOTrend, type GSCConfig, type GSCCredentials, type GSCPerformanceData, type GSCQueryData, type GSCQueryResult, type GeneratedCode, type GeneratedContent, type GeneratedFix, type HeadingStructure, type HeadlineAnalysis, type HealthScore, ISSUE_DEFINITIONS, type ImageInfo$2 as ImageInfo, type InjectionResult, type IntentAnalysis, type IssueCategory, type IssueDefinition, type IssueSeverity, type KeyFact, type KeyFactsOptions, type KeywordAction, type KeywordCluster, type KeywordData, type KeywordDensityAnalysis, type KeywordLLM, type KeywordOpportunity, type KeywordRecommendation, type KeywordResearchOptions, type KeywordResearchResult, type KeywordStats, type KeywordTopic, type KeywordTrend, type LLMCitationResult, type LLMJudgeOptions, type LLMProvider, LOCATION_CODES, type LSIKeyword, type LinkInfo$1 as LinkInfo, type Mention, type MentionType, type MetaData, type MetaFixOptions, type NGram, type NLPAnalysisResult, OG_IMAGE_SPECS, type OptimizeOptions, type PRConfig, type PRDescription, PRIORITY_WEIGHTS, type PRResult, type PageAudit, type PageData, type PageMeta, type PaidKeywordResult, type ParsedResponse, type PlanTier, type ProviderStats, type QuickWin, type RankCheckOptions, type RankCheckResult, type RankHistory, RankTracker, type RankingResult, type RankingTierLimits, type ReadabilityResult, type ReadmeConfig, type ReadmeResult, type ReportBranding, type ReportConfig, type ReportData, type ReportPageData, type RouteInfo, type SEOAnalysisResult, type SEOFixCommit, type SEOFixSummary, type SEOIssue, type SEORecommendation, type SEOScore, SEO_SCOPES, SITE_PROFILE_QUESTIONS, type ScheduledAuditConfig, type ScheduledAuditResult, type SchemaData, Schemas, type SearchIntent, type Sentiment, type SerpApiConfig, SerpClient, type SerpClientConfig, type SerpFeature, type SiteCrawlResult, type SiteProfile, type SiteSummary, type SnippetRecommendation, type SnippetType, type SocialMetaConfig, type SocialMetaFix, type SummarizerOptions, type TFIDFResult, TIER_LIMITS, type ToolFeasibilityScore, type ToolFunction, type ToolResult, type TopicClusterResult, type TopicModel, type TrackedKeyword, type TrackerConfig, type TrackingOptions, type TrendDirection, type UncertaintyAssessment, type WizardQuestion, type WizardResponse, type WizardResult, type WizardSession, type WorkflowConfig, addTrackingResult, analyzeAnchorText, analyzeCanonicalAdvanced, analyzeClientRendering, analyzeContentFreshness, analyzeDOMStructure, analyzeFeaturedSnippetPotential, analyzeHeadings, analyzeHeadline, analyzeHreflang, analyzeImages$1 as analyzeImages, analyzeKeywordDensity, analyzeKeywords, analyzeLinks, analyzeLocalSEO, analyzeMobile, analyzeModernImages, analyzeOnPage, analyzePagination, analyzePerformance, analyzeReadability, analyzeRedirectChain, analyzeRedirects, analyzeResponsiveImages, analyzeSERPPreview, analyzeSecurity, analyzeSecurityHeaders$1 as analyzeSecurityHeaders, analyzeSocialMeta, analyzeStructuredData$1 as analyzeStructuredData, analyzeTrackerBloat, analyzeUrl, analyzeUrlSafety, index$1 as analyzers, applyFixes, buildGSCApiRequest, buildGSCRequest, calculateAIVisibilityScore, calculateBM25, calculateTFIDF$1 as calculateKeywordTFIDF, calculateNextRun, calculateTFIDF, checkAIBotBlocking, checkAMP, checkAdsTxt, checkAppleTouchIcon, checkBalance, checkCertificate, checkDMARC, checkGitHubCLI, checkInternalRedirects, checkJSRenderingRatio, checkLLMCitations, checkLlmsTxt, checkMobileResources, checkPlaintextEmails, checkRedirects, checkRobots, checkRobotsTxt, checkSPF, checkSitemap, classifyIntent, classifyIntents, clusterKeywordsByEmbedding, compareCompetitorVisibility, comparePeriods, completeWizard, crawlSite, crawlUrl, createAuditPR, createFallbackSummary, createGEOHistory, createPullRequest, createSEOCommit, createSEOCommits, detectDuplicates, detectFramework$1 as detectFramework, detectGitHubPages, detectMentions, detectSoft404, detectTechnologies, detectVisibilityChanges, discoverCompetitorKeywords, discoverPagesFromLinks, discoverRoutesFromRepo, enhanceSummaryWithCompetitors, enhanceToolIdea, enrichKeywordsWithEstimates, ensureGitRepo, evaluateAndEnhanceToolIdeas, evaluateToolFeasibility, executeAgent, extractContentHash, extractEntityPhrases, extractImages, extractKeyPhrases, extractLinks, extractMeta, extractNgrams, extractSchema, extractSeedKeywords, extractTopics, fetchCoreWebVitals, findAlmostPage1Keywords, findCTROpportunities, findHtmlEntry, findLSIKeywords, findPageFiles, findTopPerformers, formatAlertMessage, formatCIResult, formatCompetitorReport, formatConventionalCommit, formatFeaturedSnippetReport, formatHeadlineReport, formatIntentReport, formatKeywordDensityReport, formatKeywordReport, formatPRBody, formatPRTitle, formatReadabilityReport, formatReport, formatSEOCommitMessage, formatTopicReport, formatWizardProgress, index as frameworks, generateAICitableContent, generateAllFixes, generateAngularSEOService, generateAspNetCoreSEO, generateAstroBaseHead, generateAstroMeta, generateBlogPost, generateBranchName, generateChangelog, generateCommitSummary, generateComparisonTable, generateCompleteSocialMetaSetup, generateDjangoSEOHelper, generateDuplicateIssues, generateEleventySEO, generateFAQSchema, generateFixes, generateGA4EnvTemplate, generateGA4ReactComponent, generateGA4Script, generateGA4ViteScript, generateGEOReport, generateGSCVerificationTag, generateGitHubActionSetup, generateGoSEO, generateHTMLReport, generateHTMLSocialMeta, generateHTMXSEO, generateHeadlineVariations, generateHugoSEO, generateJekyllSEO, generateJsonReport, generateKeyFacts, generateLaravelSEOHelper, generateMarkdownReport, generateNextAppMetadata, generateNextJsAppRouterMetadata, generateNextJsPagesRouterHead, generateNextPagesHead, generateNuxtSEOHead, generatePDFReport, generatePRDescription, generatePhoenixSEO, generateRailsSEOHelper, generateReactHelmetSocialMeta, generateReactSEOHead, generateRecommendationQueries, generateRemixMeta, generateRemixSEO, generateSecretsDoc, generateSocialMetaFix, generateSpringBootSEO, generateSvelteKitMeta, generateSvelteKitSEOHead, generateUncertaintyQuestions, generateVueSEOHead, generateWizardQuestions, generateWorkflow, getAIVisibilitySummary, getAutocompleteSuggestions, getDateRange, getExpandedSuggestions, getFrameworkSpecificFix, getFrameworkSpecificFixExtended, getGSCSetupInstructions, getGitUser, getKeywordData, getKeywordSuggestions, getMaxKdThreshold, getNextQuestion, getRelatedKeywords, getVisibilityTrend, groupKeywordsByTopic, identifyQuickWins, injectGA4, injectGSCVerification, interpolatePrompt, isGitRepo, keywordLLM, listFiles, loadAgent, loadAgentByName, mergePages, openAIKeywordLLM, optimizeForAI, optimizeReadme, parseGEOResponse, parseGSCResponse, parseJsonReply, parseSitemap, prioritizeKeywords, processWizardResponse, readFile, routesToUrls, runAIKeywordResearch, runAIReadinessChecks, runAdditionalChecks, runAuditWithFixes, runCIKeywordResearch, runCrawlabilityChecks, runDirectAnalysis, runFullAudit, runKeywordResearch, runNLPAnalysis, runScheduledAudit, scoreContentSEO, scoreMention, searchCompetitors, searchFormatConverters, searchHackerNews, sendAlert, sendAlerts, sendDiscordAlert, sendSlackAlert, shouldRunAudit, shouldSendAlert, startWizardSession, suggestSchemaTypes, summarizeSite, tokenize, tools, trackLLMVisibility, transformGSCData, urlSafetyDatabase, wizardResponsesToContext, writeFile, writeGitHubActionFiles };
|
package/dist/index.js
CHANGED
|
@@ -3515,14 +3515,17 @@ __export(index_exports, {
|
|
|
3515
3515
|
injectGSCVerification: () => injectGSCVerification,
|
|
3516
3516
|
interpolatePrompt: () => interpolatePrompt,
|
|
3517
3517
|
isGitRepo: () => isGitRepo,
|
|
3518
|
+
keywordLLM: () => keywordLLM,
|
|
3518
3519
|
listFiles: () => listFiles,
|
|
3519
3520
|
loadAgent: () => loadAgent,
|
|
3520
3521
|
loadAgentByName: () => loadAgentByName,
|
|
3521
3522
|
mergePages: () => mergePages,
|
|
3523
|
+
openAIKeywordLLM: () => openAIKeywordLLM,
|
|
3522
3524
|
optimizeForAI: () => optimizeForAI,
|
|
3523
3525
|
optimizeReadme: () => optimizeReadme,
|
|
3524
3526
|
parseGEOResponse: () => parseGEOResponse,
|
|
3525
3527
|
parseGSCResponse: () => parseGSCResponse,
|
|
3528
|
+
parseJsonReply: () => parseJsonReply,
|
|
3526
3529
|
parseSitemap: () => parseSitemap,
|
|
3527
3530
|
prioritizeKeywords: () => prioritizeKeywords,
|
|
3528
3531
|
processWizardResponse: () => processWizardResponse,
|
|
@@ -7844,21 +7847,28 @@ var HTML_SIGNATURES = [
|
|
|
7844
7847
|
{
|
|
7845
7848
|
framework: "wordpress",
|
|
7846
7849
|
language: "php",
|
|
7847
|
-
patterns
|
|
7850
|
+
// Technical signatures only. These patterns run over the whole page, so the old
|
|
7851
|
+
// /wordpress/i matched the word anywhere in the copy: rankcli.dev's own "detects
|
|
7852
|
+
// Rails, Django, Laravel, and WordPress" line was enough to detect its React SPA as
|
|
7853
|
+
// WordPress and then tell it to install an SEO plugin. A generator meta tag is the
|
|
7854
|
+
// one place the page says it about itself.
|
|
7855
|
+
patterns: [/wp-content/, /wp-includes/, /wp-json/, /name=["']generator["'][^>]*content=["']WordPress/i],
|
|
7848
7856
|
confidence: "high"
|
|
7849
7857
|
},
|
|
7850
7858
|
// Laravel
|
|
7851
7859
|
{
|
|
7852
7860
|
framework: "laravel",
|
|
7853
7861
|
language: "php",
|
|
7854
|
-
|
|
7862
|
+
// /laravel/i had the same problem as WordPress above - it matched the word in prose.
|
|
7863
|
+
patterns: [/laravel_session/, /XSRF-TOKEN/, /@csrf/],
|
|
7855
7864
|
confidence: "medium"
|
|
7856
7865
|
},
|
|
7857
7866
|
// Symfony
|
|
7858
7867
|
{
|
|
7859
7868
|
framework: "symfony",
|
|
7860
7869
|
language: "php",
|
|
7861
|
-
|
|
7870
|
+
// Likewise /symfony/, which matched any lowercase mention.
|
|
7871
|
+
patterns: [/sf2/, /_sf_/, /profiler\.css/],
|
|
7862
7872
|
confidence: "medium"
|
|
7863
7873
|
},
|
|
7864
7874
|
// ============ Ruby Frameworks ============
|
|
@@ -20111,6 +20121,50 @@ async function extractSeedKeywords(url) {
|
|
|
20111
20121
|
}
|
|
20112
20122
|
}
|
|
20113
20123
|
|
|
20124
|
+
// src/keywords/llm.ts
|
|
20125
|
+
var import_openai = __toESM(require("openai"));
|
|
20126
|
+
function openAIKeywordLLM(apiKey, opts = {}) {
|
|
20127
|
+
const openai = new import_openai.default({ apiKey });
|
|
20128
|
+
const openAIModel = opts.model;
|
|
20129
|
+
return {
|
|
20130
|
+
async complete(prompt, opts2 = {}) {
|
|
20131
|
+
const response = await openai.chat.completions.create({
|
|
20132
|
+
model: opts2.best ? "gpt-4o" : openAIModel ?? "gpt-4o-mini",
|
|
20133
|
+
messages: [
|
|
20134
|
+
...opts2.system ? [{ role: "system", content: opts2.system }] : [],
|
|
20135
|
+
{ role: "user", content: prompt }
|
|
20136
|
+
],
|
|
20137
|
+
response_format: { type: "json_object" },
|
|
20138
|
+
temperature: opts2.temperature ?? 0.3,
|
|
20139
|
+
...opts2.maxTokens ? { max_tokens: opts2.maxTokens } : {}
|
|
20140
|
+
});
|
|
20141
|
+
return response.choices[0]?.message?.content || "";
|
|
20142
|
+
},
|
|
20143
|
+
async embed(texts) {
|
|
20144
|
+
const response = await openai.embeddings.create({ model: "text-embedding-3-small", input: texts });
|
|
20145
|
+
return response.data.map((item) => item.embedding);
|
|
20146
|
+
}
|
|
20147
|
+
};
|
|
20148
|
+
}
|
|
20149
|
+
function keywordLLM(options) {
|
|
20150
|
+
return options.llm ?? (options.openaiApiKey ? openAIKeywordLLM(options.openaiApiKey) : void 0);
|
|
20151
|
+
}
|
|
20152
|
+
function parseJsonReply(text) {
|
|
20153
|
+
const cleaned = text.replace(/<think>[\s\S]*?<\/think>/gi, "");
|
|
20154
|
+
const start = cleaned.indexOf("{");
|
|
20155
|
+
const end = cleaned.lastIndexOf("}");
|
|
20156
|
+
if (start === -1 || end <= start) return null;
|
|
20157
|
+
const candidate = cleaned.slice(start, end + 1);
|
|
20158
|
+
for (const attempt of [candidate, candidate.replace(/,\s*([}\]])/g, "$1")]) {
|
|
20159
|
+
try {
|
|
20160
|
+
const parsed = JSON.parse(attempt);
|
|
20161
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
20162
|
+
} catch {
|
|
20163
|
+
}
|
|
20164
|
+
}
|
|
20165
|
+
return null;
|
|
20166
|
+
}
|
|
20167
|
+
|
|
20114
20168
|
// src/keywords/site-crawler.ts
|
|
20115
20169
|
var cheerio40 = __toESM(require("cheerio"));
|
|
20116
20170
|
init_http();
|
|
@@ -20321,7 +20375,11 @@ function extractKeyPhrases(crawlResult) {
|
|
|
20321
20375
|
}
|
|
20322
20376
|
|
|
20323
20377
|
// src/keywords/ai-summarizer.ts
|
|
20324
|
-
|
|
20378
|
+
function summarizerLLM(options) {
|
|
20379
|
+
const llm = options.llm ?? (options.openaiApiKey ? openAIKeywordLLM(options.openaiApiKey, { model: options.model }) : void 0);
|
|
20380
|
+
if (!llm) throw new Error("An AI provider is required to summarize the site");
|
|
20381
|
+
return llm;
|
|
20382
|
+
}
|
|
20325
20383
|
var SUMMARIZER_PROMPT = `You are an expert SEO analyst. Analyze the following website content and extract key information for keyword research.
|
|
20326
20384
|
|
|
20327
20385
|
WEBSITE CONTENT:
|
|
@@ -20364,37 +20422,26 @@ IMPORTANT:
|
|
|
20364
20422
|
|
|
20365
20423
|
Respond ONLY with the JSON object, no other text.`;
|
|
20366
20424
|
async function summarizeSite(crawlResult, options) {
|
|
20367
|
-
const
|
|
20368
|
-
const
|
|
20425
|
+
const llm = summarizerLLM(options);
|
|
20426
|
+
const maxContent = llm.maxContentChars ?? 15e3;
|
|
20369
20427
|
let content = crawlResult.aggregatedContent;
|
|
20370
|
-
if (content.length >
|
|
20371
|
-
content = content.substring(0,
|
|
20428
|
+
if (content.length > maxContent) {
|
|
20429
|
+
content = content.substring(0, maxContent) + "\n...[truncated]";
|
|
20372
20430
|
}
|
|
20373
20431
|
const headings = crawlResult.uniqueHeadings.slice(0, 30).join("\n");
|
|
20374
20432
|
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));
|
|
20375
20433
|
console.log("\u{1F916} Analyzing site content with AI...");
|
|
20376
20434
|
try {
|
|
20377
|
-
const
|
|
20378
|
-
|
|
20379
|
-
messages: [
|
|
20380
|
-
{
|
|
20381
|
-
role: "system",
|
|
20382
|
-
content: "You are an expert SEO analyst. Always respond with valid JSON only."
|
|
20383
|
-
},
|
|
20384
|
-
{
|
|
20385
|
-
role: "user",
|
|
20386
|
-
content: prompt
|
|
20387
|
-
}
|
|
20388
|
-
],
|
|
20435
|
+
const responseText = await llm.complete(prompt, {
|
|
20436
|
+
system: "You are an expert SEO analyst. Always respond with valid JSON only.",
|
|
20389
20437
|
temperature: 0.3,
|
|
20390
|
-
|
|
20438
|
+
maxTokens: options.maxTokens || 2e3
|
|
20391
20439
|
});
|
|
20392
|
-
const
|
|
20393
|
-
|
|
20394
|
-
if (!jsonMatch) {
|
|
20440
|
+
const parsed = parseJsonReply(responseText);
|
|
20441
|
+
if (!parsed) {
|
|
20395
20442
|
throw new Error("No JSON found in response");
|
|
20396
20443
|
}
|
|
20397
|
-
const summary =
|
|
20444
|
+
const summary = parsed;
|
|
20398
20445
|
return {
|
|
20399
20446
|
productName: summary.productName || "Unknown Product",
|
|
20400
20447
|
productDescription: summary.productDescription || "No description available",
|
|
@@ -20456,7 +20503,7 @@ function createFallbackSummary(crawlResult) {
|
|
|
20456
20503
|
};
|
|
20457
20504
|
}
|
|
20458
20505
|
async function enhanceSummaryWithCompetitors(summary, competitors, options) {
|
|
20459
|
-
const
|
|
20506
|
+
const llm = summarizerLLM(options);
|
|
20460
20507
|
const prompt = `Given this product summary:
|
|
20461
20508
|
Product: ${summary.productName}
|
|
20462
20509
|
Description: ${summary.productDescription}
|
|
@@ -20475,19 +20522,13 @@ Analyze the competitive positioning. If this product focuses on a niche segment
|
|
|
20475
20522
|
"updatedUncertainties": ["uncertainty1", "uncertainty2"]
|
|
20476
20523
|
}`;
|
|
20477
20524
|
try {
|
|
20478
|
-
const
|
|
20479
|
-
|
|
20480
|
-
messages: [
|
|
20481
|
-
{ role: "system", content: "Respond with valid JSON only." },
|
|
20482
|
-
{ role: "user", content: prompt }
|
|
20483
|
-
],
|
|
20525
|
+
const responseText = await llm.complete(prompt, {
|
|
20526
|
+
system: "Respond with valid JSON only.",
|
|
20484
20527
|
temperature: 0.3,
|
|
20485
|
-
|
|
20528
|
+
maxTokens: 1e3
|
|
20486
20529
|
});
|
|
20487
|
-
const
|
|
20488
|
-
|
|
20489
|
-
if (jsonMatch) {
|
|
20490
|
-
const analysis = JSON.parse(jsonMatch[0]);
|
|
20530
|
+
const analysis = parseJsonReply(responseText);
|
|
20531
|
+
if (analysis) {
|
|
20491
20532
|
return {
|
|
20492
20533
|
...summary,
|
|
20493
20534
|
competitivePositioning: analysis.competitivePositioning || "",
|
|
@@ -20574,7 +20615,6 @@ function generateUncertaintyQuestions(summary) {
|
|
|
20574
20615
|
}
|
|
20575
20616
|
|
|
20576
20617
|
// src/keywords/nlp-analysis.ts
|
|
20577
|
-
var import_openai2 = __toESM(require("openai"));
|
|
20578
20618
|
var STOP_WORDS2 = /* @__PURE__ */ new Set([
|
|
20579
20619
|
"a",
|
|
20580
20620
|
"an",
|
|
@@ -20859,19 +20899,18 @@ function createTermVectors(keywords, vocabulary) {
|
|
|
20859
20899
|
return vector;
|
|
20860
20900
|
});
|
|
20861
20901
|
}
|
|
20902
|
+
async function embedBatch(embedder, batch) {
|
|
20903
|
+
if (typeof embedder === "function") return embedder(batch);
|
|
20904
|
+
const response = await embedder.embeddings.create({ model: "text-embedding-3-small", input: batch });
|
|
20905
|
+
return response.data.map((item) => item.embedding);
|
|
20906
|
+
}
|
|
20862
20907
|
async function getEmbeddings(texts, openai) {
|
|
20863
20908
|
const embeddings = [];
|
|
20864
20909
|
const batchSize = 100;
|
|
20865
20910
|
for (let i = 0; i < texts.length; i += batchSize) {
|
|
20866
20911
|
const batch = texts.slice(i, i + batchSize);
|
|
20867
20912
|
try {
|
|
20868
|
-
|
|
20869
|
-
model: "text-embedding-3-small",
|
|
20870
|
-
input: batch
|
|
20871
|
-
});
|
|
20872
|
-
for (const item of response.data) {
|
|
20873
|
-
embeddings.push(item.embedding);
|
|
20874
|
-
}
|
|
20913
|
+
embeddings.push(...await embedBatch(openai, batch));
|
|
20875
20914
|
} catch (error) {
|
|
20876
20915
|
console.warn("Embedding API failed, using fallback BOW vectors");
|
|
20877
20916
|
for (const text of batch) {
|
|
@@ -21026,12 +21065,12 @@ async function runNLPAnalysis(content, options = {}) {
|
|
|
21026
21065
|
const entityPhrases = extractEntityPhrases(combinedText);
|
|
21027
21066
|
let clusters = [];
|
|
21028
21067
|
const keywordsToCluster = tfidfKeywords.slice(0, 50).map((k) => k.term);
|
|
21029
|
-
|
|
21068
|
+
const embed = keywordLLM(options)?.embed;
|
|
21069
|
+
if (embed && keywordsToCluster.length > 0) {
|
|
21030
21070
|
try {
|
|
21031
|
-
const openai = new import_openai2.default({ apiKey: options.openaiApiKey });
|
|
21032
21071
|
clusters = await clusterKeywordsByEmbedding(
|
|
21033
21072
|
keywordsToCluster,
|
|
21034
|
-
|
|
21073
|
+
(texts) => embed(texts),
|
|
21035
21074
|
options.numClusters
|
|
21036
21075
|
);
|
|
21037
21076
|
} catch (error) {
|
|
@@ -21374,9 +21413,13 @@ async function searchHackerNews(query, maxResults = 10) {
|
|
|
21374
21413
|
}
|
|
21375
21414
|
|
|
21376
21415
|
// src/keywords/llm-judge.ts
|
|
21377
|
-
|
|
21416
|
+
function judgeLLM(options) {
|
|
21417
|
+
const llm = keywordLLM(options);
|
|
21418
|
+
if (!llm) throw new Error("An AI provider is required to judge tool ideas");
|
|
21419
|
+
return llm;
|
|
21420
|
+
}
|
|
21378
21421
|
async function evaluateToolFeasibility(toolIdea, siteSummary, competitiveData, options) {
|
|
21379
|
-
const
|
|
21422
|
+
const llm = judgeLLM(options);
|
|
21380
21423
|
const minFeasibility = options.minFeasibilityScore ?? 3;
|
|
21381
21424
|
const minSpecificity = options.minSpecificityScore ?? 3;
|
|
21382
21425
|
const minProductFit = options.minProductFitScore ?? 2;
|
|
@@ -21452,13 +21495,10 @@ Respond in JSON:
|
|
|
21452
21495
|
"blockers": ["<issues that prevent building>"],
|
|
21453
21496
|
"improvements": ["<suggestions to make it more specific/buildable>"]
|
|
21454
21497
|
}`;
|
|
21455
|
-
const
|
|
21456
|
-
|
|
21457
|
-
|
|
21458
|
-
|
|
21459
|
-
temperature: 0.3
|
|
21460
|
-
});
|
|
21461
|
-
const result = JSON.parse(response.choices[0].message.content || "{}");
|
|
21498
|
+
const result = parseJsonReply(await llm.complete(prompt, { temperature: 0.3 })) ?? {};
|
|
21499
|
+
for (const key of ["feasibility", "specificity", "productFit", "marketOpportunity"]) {
|
|
21500
|
+
result[key] = Number(result[key]) || 0;
|
|
21501
|
+
}
|
|
21462
21502
|
const overallScore = result.feasibility * 0.3 + result.specificity * 0.3 + result.productFit * 0.2 + result.marketOpportunity * 0.2;
|
|
21463
21503
|
const passes = result.feasibility >= minFeasibility && result.specificity >= minSpecificity && result.productFit >= minProductFit;
|
|
21464
21504
|
return {
|
|
@@ -21474,7 +21514,7 @@ Respond in JSON:
|
|
|
21474
21514
|
};
|
|
21475
21515
|
}
|
|
21476
21516
|
async function enhanceToolIdea(toolIdea, siteSummary, competitiveData, options) {
|
|
21477
|
-
const
|
|
21517
|
+
const llm = judgeLLM(options);
|
|
21478
21518
|
const feasibilityScore = await evaluateToolFeasibility(
|
|
21479
21519
|
toolIdea,
|
|
21480
21520
|
siteSummary,
|
|
@@ -21519,13 +21559,7 @@ ${competitiveData?.openSourceOptions.slice(0, 3).map((t) => `- OSS: ${t.name} ($
|
|
|
21519
21559
|
"canUseExistingOSS": <true if an OSS library does most of the work>,
|
|
21520
21560
|
"ossLibrary": "<name of OSS library to use, or null>"
|
|
21521
21561
|
}`;
|
|
21522
|
-
const
|
|
21523
|
-
model: "gpt-4o",
|
|
21524
|
-
messages: [{ role: "user", content: prompt }],
|
|
21525
|
-
response_format: { type: "json_object" },
|
|
21526
|
-
temperature: 0.5
|
|
21527
|
-
});
|
|
21528
|
-
const enhanced = JSON.parse(response.choices[0].message.content || "{}");
|
|
21562
|
+
const enhanced = parseJsonReply(await llm.complete(prompt, { temperature: 0.5, best: true })) ?? {};
|
|
21529
21563
|
const priority = Math.round(
|
|
21530
21564
|
feasibilityScore.overallScore * 20 + toolIdea.volume / 1e3 * 10 + (100 - toolIdea.difficulty) / 100 * 20
|
|
21531
21565
|
);
|
|
@@ -21576,7 +21610,7 @@ async function evaluateAndEnhanceToolIdeas(toolIdeas, siteSummary, getCompetitiv
|
|
|
21576
21610
|
|
|
21577
21611
|
// src/keywords/ai-keyword-engine.ts
|
|
21578
21612
|
async function runAIKeywordResearch(options) {
|
|
21579
|
-
const { url, tier,
|
|
21613
|
+
const { url, tier, maxPages = 20 } = options;
|
|
21580
21614
|
console.log(`
|
|
21581
21615
|
\u{1F52C} Starting AI Keyword Research (${tier} tier)...
|
|
21582
21616
|
`);
|
|
@@ -21584,10 +21618,11 @@ async function runAIKeywordResearch(options) {
|
|
|
21584
21618
|
if (tier === "free") {
|
|
21585
21619
|
return runFreeKeywordResearch(crawlResult, options);
|
|
21586
21620
|
} else {
|
|
21587
|
-
|
|
21588
|
-
|
|
21621
|
+
const llm = keywordLLM(options);
|
|
21622
|
+
if (!llm) {
|
|
21623
|
+
throw new Error("An AI provider (or an OpenAI API key) is required for paid tier keyword research");
|
|
21589
21624
|
}
|
|
21590
|
-
return runPaidKeywordResearch(crawlResult, options);
|
|
21625
|
+
return runPaidKeywordResearch(crawlResult, options, llm);
|
|
21591
21626
|
}
|
|
21592
21627
|
}
|
|
21593
21628
|
async function runFreeKeywordResearch(crawlResult, options) {
|
|
@@ -21641,13 +21676,11 @@ async function runFreeKeywordResearch(crawlResult, options) {
|
|
|
21641
21676
|
]
|
|
21642
21677
|
};
|
|
21643
21678
|
}
|
|
21644
|
-
async function runPaidKeywordResearch(crawlResult, options) {
|
|
21645
|
-
const {
|
|
21679
|
+
async function runPaidKeywordResearch(crawlResult, options, llm) {
|
|
21680
|
+
const { userContext, wizardResponses, ciMode, competitiveSearchKeys } = options;
|
|
21646
21681
|
const enableEnhancedToolIdeas = options.enableEnhancedToolIdeas !== false;
|
|
21647
21682
|
console.log("\u{1F680} Running full AI analysis...");
|
|
21648
|
-
let siteSummary = await summarizeSite(crawlResult, {
|
|
21649
|
-
openaiApiKey
|
|
21650
|
-
});
|
|
21683
|
+
let siteSummary = await summarizeSite(crawlResult, { llm });
|
|
21651
21684
|
if (userContext) {
|
|
21652
21685
|
siteSummary = applyUserContext(siteSummary, userContext);
|
|
21653
21686
|
}
|
|
@@ -21656,7 +21689,7 @@ async function runPaidKeywordResearch(crawlResult, options) {
|
|
|
21656
21689
|
}
|
|
21657
21690
|
const documents = crawlResult.pages.map((p) => p.mainContent);
|
|
21658
21691
|
const nlpAnalysis = await runNLPAnalysis(documents, {
|
|
21659
|
-
|
|
21692
|
+
llm,
|
|
21660
21693
|
numClusters: 8,
|
|
21661
21694
|
numTopics: 6
|
|
21662
21695
|
});
|
|
@@ -21683,13 +21716,13 @@ async function runPaidKeywordResearch(crawlResult, options) {
|
|
|
21683
21716
|
nlpAnalysis
|
|
21684
21717
|
);
|
|
21685
21718
|
let freeToolIdeas;
|
|
21686
|
-
if (enableEnhancedToolIdeas
|
|
21719
|
+
if (enableEnhancedToolIdeas) {
|
|
21687
21720
|
console.log("\u{1F52C} Running enhanced tool idea analysis with competitive research...");
|
|
21688
21721
|
freeToolIdeas = await generateEnhancedFreeToolIdeas(
|
|
21689
21722
|
opportunities,
|
|
21690
21723
|
siteSummary,
|
|
21691
21724
|
{
|
|
21692
|
-
|
|
21725
|
+
llm,
|
|
21693
21726
|
...competitiveSearchKeys
|
|
21694
21727
|
}
|
|
21695
21728
|
);
|
|
@@ -21787,7 +21820,7 @@ async function generateEnhancedFreeToolIdeas(opportunities, summary, options) {
|
|
|
21787
21820
|
kw.volume >= 300 && // Slightly lower threshold
|
|
21788
21821
|
(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")
|
|
21789
21822
|
);
|
|
21790
|
-
const basicIdeas = toolKeywords.slice(0, 15).map((kw) => ({
|
|
21823
|
+
const basicIdeas = toolKeywords.slice(0, options.llm.maxToolIdeas ?? 15).map((kw) => ({
|
|
21791
21824
|
keyword: kw.keyword,
|
|
21792
21825
|
toolName: generateToolName(kw.keyword),
|
|
21793
21826
|
toolDescription: generateToolDescription(kw.keyword, summary),
|
|
@@ -21817,7 +21850,7 @@ async function generateEnhancedFreeToolIdeas(opportunities, summary, options) {
|
|
|
21817
21850
|
basicIdeas,
|
|
21818
21851
|
summary,
|
|
21819
21852
|
getCompetitiveData,
|
|
21820
|
-
{
|
|
21853
|
+
{ llm: options.llm }
|
|
21821
21854
|
);
|
|
21822
21855
|
return enhancedIdeas.map((enhanced) => ({
|
|
21823
21856
|
keyword: enhanced.keyword,
|
|
@@ -22148,10 +22181,9 @@ async function startWizardSession(url, options = {}) {
|
|
|
22148
22181
|
console.log("\u{1F50D} Analyzing your site...");
|
|
22149
22182
|
const crawlResult = await crawlSite(url, { maxPages: 10 });
|
|
22150
22183
|
let siteSummary;
|
|
22151
|
-
|
|
22152
|
-
|
|
22153
|
-
|
|
22154
|
-
});
|
|
22184
|
+
const llm = keywordLLM(options);
|
|
22185
|
+
if (llm) {
|
|
22186
|
+
siteSummary = await summarizeSite(crawlResult, { llm });
|
|
22155
22187
|
} else {
|
|
22156
22188
|
siteSummary = {
|
|
22157
22189
|
productName: crawlResult.domain,
|
|
@@ -35072,24 +35104,32 @@ var RankTracker = class {
|
|
|
35072
35104
|
}).eq("id", keywordId);
|
|
35073
35105
|
if (updateError) {
|
|
35074
35106
|
console.error(`Failed to save ranking for ${result.keyword}:`, updateError);
|
|
35107
|
+
return;
|
|
35108
|
+
}
|
|
35109
|
+
const { error: historyError } = await this.supabase.from("keyword_position_history").insert({
|
|
35110
|
+
keyword_id: keywordId,
|
|
35111
|
+
position: result.position,
|
|
35112
|
+
url: result.url,
|
|
35113
|
+
checked_at: result.checkedAt.toISOString()
|
|
35114
|
+
});
|
|
35115
|
+
if (historyError) {
|
|
35116
|
+
console.error(`Failed to record history for ${result.keyword}:`, historyError);
|
|
35075
35117
|
}
|
|
35076
35118
|
}
|
|
35077
35119
|
/**
|
|
35078
|
-
*
|
|
35079
|
-
* Note: Full history requires keyword_ranking_history table with keyword_ranking_id
|
|
35080
|
-
* For now, returns current state as single history entry
|
|
35120
|
+
* Ranking history for a keyword: one entry per check in the last `days`, newest first.
|
|
35081
35121
|
*/
|
|
35082
|
-
async getHistory(keywordId,
|
|
35083
|
-
const { data, error } = await this.supabase.from("
|
|
35122
|
+
async getHistory(keywordId, days = 30) {
|
|
35123
|
+
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 });
|
|
35084
35124
|
if (error || !data) {
|
|
35085
35125
|
return [];
|
|
35086
35126
|
}
|
|
35087
|
-
return
|
|
35088
|
-
position:
|
|
35089
|
-
url:
|
|
35127
|
+
return data.map((row) => ({
|
|
35128
|
+
position: row.position,
|
|
35129
|
+
url: row.url,
|
|
35090
35130
|
serpFeatures: [],
|
|
35091
|
-
recordedAt:
|
|
35092
|
-
}
|
|
35131
|
+
recordedAt: new Date(row.checked_at)
|
|
35132
|
+
}));
|
|
35093
35133
|
}
|
|
35094
35134
|
/**
|
|
35095
35135
|
* Get keyword trends for a project
|
|
@@ -35594,14 +35634,17 @@ var SUPPORTED_FRAMEWORKS = [
|
|
|
35594
35634
|
injectGSCVerification,
|
|
35595
35635
|
interpolatePrompt,
|
|
35596
35636
|
isGitRepo,
|
|
35637
|
+
keywordLLM,
|
|
35597
35638
|
listFiles,
|
|
35598
35639
|
loadAgent,
|
|
35599
35640
|
loadAgentByName,
|
|
35600
35641
|
mergePages,
|
|
35642
|
+
openAIKeywordLLM,
|
|
35601
35643
|
optimizeForAI,
|
|
35602
35644
|
optimizeReadme,
|
|
35603
35645
|
parseGEOResponse,
|
|
35604
35646
|
parseGSCResponse,
|
|
35647
|
+
parseJsonReply,
|
|
35605
35648
|
parseSitemap,
|
|
35606
35649
|
prioritizeKeywords,
|
|
35607
35650
|
processWizardResponse,
|
package/dist/index.mjs
CHANGED
|
@@ -4326,21 +4326,28 @@ var HTML_SIGNATURES = [
|
|
|
4326
4326
|
{
|
|
4327
4327
|
framework: "wordpress",
|
|
4328
4328
|
language: "php",
|
|
4329
|
-
patterns
|
|
4329
|
+
// Technical signatures only. These patterns run over the whole page, so the old
|
|
4330
|
+
// /wordpress/i matched the word anywhere in the copy: rankcli.dev's own "detects
|
|
4331
|
+
// Rails, Django, Laravel, and WordPress" line was enough to detect its React SPA as
|
|
4332
|
+
// WordPress and then tell it to install an SEO plugin. A generator meta tag is the
|
|
4333
|
+
// one place the page says it about itself.
|
|
4334
|
+
patterns: [/wp-content/, /wp-includes/, /wp-json/, /name=["']generator["'][^>]*content=["']WordPress/i],
|
|
4330
4335
|
confidence: "high"
|
|
4331
4336
|
},
|
|
4332
4337
|
// Laravel
|
|
4333
4338
|
{
|
|
4334
4339
|
framework: "laravel",
|
|
4335
4340
|
language: "php",
|
|
4336
|
-
|
|
4341
|
+
// /laravel/i had the same problem as WordPress above - it matched the word in prose.
|
|
4342
|
+
patterns: [/laravel_session/, /XSRF-TOKEN/, /@csrf/],
|
|
4337
4343
|
confidence: "medium"
|
|
4338
4344
|
},
|
|
4339
4345
|
// Symfony
|
|
4340
4346
|
{
|
|
4341
4347
|
framework: "symfony",
|
|
4342
4348
|
language: "php",
|
|
4343
|
-
|
|
4349
|
+
// Likewise /symfony/, which matched any lowercase mention.
|
|
4350
|
+
patterns: [/sf2/, /_sf_/, /profiler\.css/],
|
|
4344
4351
|
confidence: "medium"
|
|
4345
4352
|
},
|
|
4346
4353
|
// ============ Ruby Frameworks ============
|
|
@@ -16564,6 +16571,50 @@ async function extractSeedKeywords(url) {
|
|
|
16564
16571
|
}
|
|
16565
16572
|
}
|
|
16566
16573
|
|
|
16574
|
+
// src/keywords/llm.ts
|
|
16575
|
+
import OpenAI from "openai";
|
|
16576
|
+
function openAIKeywordLLM(apiKey, opts = {}) {
|
|
16577
|
+
const openai = new OpenAI({ apiKey });
|
|
16578
|
+
const openAIModel = opts.model;
|
|
16579
|
+
return {
|
|
16580
|
+
async complete(prompt, opts2 = {}) {
|
|
16581
|
+
const response = await openai.chat.completions.create({
|
|
16582
|
+
model: opts2.best ? "gpt-4o" : openAIModel ?? "gpt-4o-mini",
|
|
16583
|
+
messages: [
|
|
16584
|
+
...opts2.system ? [{ role: "system", content: opts2.system }] : [],
|
|
16585
|
+
{ role: "user", content: prompt }
|
|
16586
|
+
],
|
|
16587
|
+
response_format: { type: "json_object" },
|
|
16588
|
+
temperature: opts2.temperature ?? 0.3,
|
|
16589
|
+
...opts2.maxTokens ? { max_tokens: opts2.maxTokens } : {}
|
|
16590
|
+
});
|
|
16591
|
+
return response.choices[0]?.message?.content || "";
|
|
16592
|
+
},
|
|
16593
|
+
async embed(texts) {
|
|
16594
|
+
const response = await openai.embeddings.create({ model: "text-embedding-3-small", input: texts });
|
|
16595
|
+
return response.data.map((item) => item.embedding);
|
|
16596
|
+
}
|
|
16597
|
+
};
|
|
16598
|
+
}
|
|
16599
|
+
function keywordLLM(options) {
|
|
16600
|
+
return options.llm ?? (options.openaiApiKey ? openAIKeywordLLM(options.openaiApiKey) : void 0);
|
|
16601
|
+
}
|
|
16602
|
+
function parseJsonReply(text) {
|
|
16603
|
+
const cleaned = text.replace(/<think>[\s\S]*?<\/think>/gi, "");
|
|
16604
|
+
const start = cleaned.indexOf("{");
|
|
16605
|
+
const end = cleaned.lastIndexOf("}");
|
|
16606
|
+
if (start === -1 || end <= start) return null;
|
|
16607
|
+
const candidate = cleaned.slice(start, end + 1);
|
|
16608
|
+
for (const attempt of [candidate, candidate.replace(/,\s*([}\]])/g, "$1")]) {
|
|
16609
|
+
try {
|
|
16610
|
+
const parsed = JSON.parse(attempt);
|
|
16611
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
16612
|
+
} catch {
|
|
16613
|
+
}
|
|
16614
|
+
}
|
|
16615
|
+
return null;
|
|
16616
|
+
}
|
|
16617
|
+
|
|
16567
16618
|
// src/keywords/site-crawler.ts
|
|
16568
16619
|
import * as cheerio40 from "cheerio";
|
|
16569
16620
|
var EXCLUDED_PATHS = [
|
|
@@ -16773,7 +16824,11 @@ function extractKeyPhrases(crawlResult) {
|
|
|
16773
16824
|
}
|
|
16774
16825
|
|
|
16775
16826
|
// src/keywords/ai-summarizer.ts
|
|
16776
|
-
|
|
16827
|
+
function summarizerLLM(options) {
|
|
16828
|
+
const llm = options.llm ?? (options.openaiApiKey ? openAIKeywordLLM(options.openaiApiKey, { model: options.model }) : void 0);
|
|
16829
|
+
if (!llm) throw new Error("An AI provider is required to summarize the site");
|
|
16830
|
+
return llm;
|
|
16831
|
+
}
|
|
16777
16832
|
var SUMMARIZER_PROMPT = `You are an expert SEO analyst. Analyze the following website content and extract key information for keyword research.
|
|
16778
16833
|
|
|
16779
16834
|
WEBSITE CONTENT:
|
|
@@ -16816,37 +16871,26 @@ IMPORTANT:
|
|
|
16816
16871
|
|
|
16817
16872
|
Respond ONLY with the JSON object, no other text.`;
|
|
16818
16873
|
async function summarizeSite(crawlResult, options) {
|
|
16819
|
-
const
|
|
16820
|
-
const
|
|
16874
|
+
const llm = summarizerLLM(options);
|
|
16875
|
+
const maxContent = llm.maxContentChars ?? 15e3;
|
|
16821
16876
|
let content = crawlResult.aggregatedContent;
|
|
16822
|
-
if (content.length >
|
|
16823
|
-
content = content.substring(0,
|
|
16877
|
+
if (content.length > maxContent) {
|
|
16878
|
+
content = content.substring(0, maxContent) + "\n...[truncated]";
|
|
16824
16879
|
}
|
|
16825
16880
|
const headings = crawlResult.uniqueHeadings.slice(0, 30).join("\n");
|
|
16826
16881
|
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));
|
|
16827
16882
|
console.log("\u{1F916} Analyzing site content with AI...");
|
|
16828
16883
|
try {
|
|
16829
|
-
const
|
|
16830
|
-
|
|
16831
|
-
messages: [
|
|
16832
|
-
{
|
|
16833
|
-
role: "system",
|
|
16834
|
-
content: "You are an expert SEO analyst. Always respond with valid JSON only."
|
|
16835
|
-
},
|
|
16836
|
-
{
|
|
16837
|
-
role: "user",
|
|
16838
|
-
content: prompt
|
|
16839
|
-
}
|
|
16840
|
-
],
|
|
16884
|
+
const responseText = await llm.complete(prompt, {
|
|
16885
|
+
system: "You are an expert SEO analyst. Always respond with valid JSON only.",
|
|
16841
16886
|
temperature: 0.3,
|
|
16842
|
-
|
|
16887
|
+
maxTokens: options.maxTokens || 2e3
|
|
16843
16888
|
});
|
|
16844
|
-
const
|
|
16845
|
-
|
|
16846
|
-
if (!jsonMatch) {
|
|
16889
|
+
const parsed = parseJsonReply(responseText);
|
|
16890
|
+
if (!parsed) {
|
|
16847
16891
|
throw new Error("No JSON found in response");
|
|
16848
16892
|
}
|
|
16849
|
-
const summary =
|
|
16893
|
+
const summary = parsed;
|
|
16850
16894
|
return {
|
|
16851
16895
|
productName: summary.productName || "Unknown Product",
|
|
16852
16896
|
productDescription: summary.productDescription || "No description available",
|
|
@@ -16908,7 +16952,7 @@ function createFallbackSummary(crawlResult) {
|
|
|
16908
16952
|
};
|
|
16909
16953
|
}
|
|
16910
16954
|
async function enhanceSummaryWithCompetitors(summary, competitors, options) {
|
|
16911
|
-
const
|
|
16955
|
+
const llm = summarizerLLM(options);
|
|
16912
16956
|
const prompt = `Given this product summary:
|
|
16913
16957
|
Product: ${summary.productName}
|
|
16914
16958
|
Description: ${summary.productDescription}
|
|
@@ -16927,19 +16971,13 @@ Analyze the competitive positioning. If this product focuses on a niche segment
|
|
|
16927
16971
|
"updatedUncertainties": ["uncertainty1", "uncertainty2"]
|
|
16928
16972
|
}`;
|
|
16929
16973
|
try {
|
|
16930
|
-
const
|
|
16931
|
-
|
|
16932
|
-
messages: [
|
|
16933
|
-
{ role: "system", content: "Respond with valid JSON only." },
|
|
16934
|
-
{ role: "user", content: prompt }
|
|
16935
|
-
],
|
|
16974
|
+
const responseText = await llm.complete(prompt, {
|
|
16975
|
+
system: "Respond with valid JSON only.",
|
|
16936
16976
|
temperature: 0.3,
|
|
16937
|
-
|
|
16977
|
+
maxTokens: 1e3
|
|
16938
16978
|
});
|
|
16939
|
-
const
|
|
16940
|
-
|
|
16941
|
-
if (jsonMatch) {
|
|
16942
|
-
const analysis = JSON.parse(jsonMatch[0]);
|
|
16979
|
+
const analysis = parseJsonReply(responseText);
|
|
16980
|
+
if (analysis) {
|
|
16943
16981
|
return {
|
|
16944
16982
|
...summary,
|
|
16945
16983
|
competitivePositioning: analysis.competitivePositioning || "",
|
|
@@ -17026,7 +17064,6 @@ function generateUncertaintyQuestions(summary) {
|
|
|
17026
17064
|
}
|
|
17027
17065
|
|
|
17028
17066
|
// src/keywords/nlp-analysis.ts
|
|
17029
|
-
import OpenAI2 from "openai";
|
|
17030
17067
|
var STOP_WORDS2 = /* @__PURE__ */ new Set([
|
|
17031
17068
|
"a",
|
|
17032
17069
|
"an",
|
|
@@ -17311,19 +17348,18 @@ function createTermVectors(keywords, vocabulary) {
|
|
|
17311
17348
|
return vector;
|
|
17312
17349
|
});
|
|
17313
17350
|
}
|
|
17351
|
+
async function embedBatch(embedder, batch) {
|
|
17352
|
+
if (typeof embedder === "function") return embedder(batch);
|
|
17353
|
+
const response = await embedder.embeddings.create({ model: "text-embedding-3-small", input: batch });
|
|
17354
|
+
return response.data.map((item) => item.embedding);
|
|
17355
|
+
}
|
|
17314
17356
|
async function getEmbeddings(texts, openai) {
|
|
17315
17357
|
const embeddings = [];
|
|
17316
17358
|
const batchSize = 100;
|
|
17317
17359
|
for (let i = 0; i < texts.length; i += batchSize) {
|
|
17318
17360
|
const batch = texts.slice(i, i + batchSize);
|
|
17319
17361
|
try {
|
|
17320
|
-
|
|
17321
|
-
model: "text-embedding-3-small",
|
|
17322
|
-
input: batch
|
|
17323
|
-
});
|
|
17324
|
-
for (const item of response.data) {
|
|
17325
|
-
embeddings.push(item.embedding);
|
|
17326
|
-
}
|
|
17362
|
+
embeddings.push(...await embedBatch(openai, batch));
|
|
17327
17363
|
} catch (error) {
|
|
17328
17364
|
console.warn("Embedding API failed, using fallback BOW vectors");
|
|
17329
17365
|
for (const text of batch) {
|
|
@@ -17478,12 +17514,12 @@ async function runNLPAnalysis(content, options = {}) {
|
|
|
17478
17514
|
const entityPhrases = extractEntityPhrases(combinedText);
|
|
17479
17515
|
let clusters = [];
|
|
17480
17516
|
const keywordsToCluster = tfidfKeywords.slice(0, 50).map((k) => k.term);
|
|
17481
|
-
|
|
17517
|
+
const embed = keywordLLM(options)?.embed;
|
|
17518
|
+
if (embed && keywordsToCluster.length > 0) {
|
|
17482
17519
|
try {
|
|
17483
|
-
const openai = new OpenAI2({ apiKey: options.openaiApiKey });
|
|
17484
17520
|
clusters = await clusterKeywordsByEmbedding(
|
|
17485
17521
|
keywordsToCluster,
|
|
17486
|
-
|
|
17522
|
+
(texts) => embed(texts),
|
|
17487
17523
|
options.numClusters
|
|
17488
17524
|
);
|
|
17489
17525
|
} catch (error) {
|
|
@@ -17826,9 +17862,13 @@ async function searchHackerNews(query, maxResults = 10) {
|
|
|
17826
17862
|
}
|
|
17827
17863
|
|
|
17828
17864
|
// src/keywords/llm-judge.ts
|
|
17829
|
-
|
|
17865
|
+
function judgeLLM(options) {
|
|
17866
|
+
const llm = keywordLLM(options);
|
|
17867
|
+
if (!llm) throw new Error("An AI provider is required to judge tool ideas");
|
|
17868
|
+
return llm;
|
|
17869
|
+
}
|
|
17830
17870
|
async function evaluateToolFeasibility(toolIdea, siteSummary, competitiveData, options) {
|
|
17831
|
-
const
|
|
17871
|
+
const llm = judgeLLM(options);
|
|
17832
17872
|
const minFeasibility = options.minFeasibilityScore ?? 3;
|
|
17833
17873
|
const minSpecificity = options.minSpecificityScore ?? 3;
|
|
17834
17874
|
const minProductFit = options.minProductFitScore ?? 2;
|
|
@@ -17904,13 +17944,10 @@ Respond in JSON:
|
|
|
17904
17944
|
"blockers": ["<issues that prevent building>"],
|
|
17905
17945
|
"improvements": ["<suggestions to make it more specific/buildable>"]
|
|
17906
17946
|
}`;
|
|
17907
|
-
const
|
|
17908
|
-
|
|
17909
|
-
|
|
17910
|
-
|
|
17911
|
-
temperature: 0.3
|
|
17912
|
-
});
|
|
17913
|
-
const result = JSON.parse(response.choices[0].message.content || "{}");
|
|
17947
|
+
const result = parseJsonReply(await llm.complete(prompt, { temperature: 0.3 })) ?? {};
|
|
17948
|
+
for (const key of ["feasibility", "specificity", "productFit", "marketOpportunity"]) {
|
|
17949
|
+
result[key] = Number(result[key]) || 0;
|
|
17950
|
+
}
|
|
17914
17951
|
const overallScore = result.feasibility * 0.3 + result.specificity * 0.3 + result.productFit * 0.2 + result.marketOpportunity * 0.2;
|
|
17915
17952
|
const passes = result.feasibility >= minFeasibility && result.specificity >= minSpecificity && result.productFit >= minProductFit;
|
|
17916
17953
|
return {
|
|
@@ -17926,7 +17963,7 @@ Respond in JSON:
|
|
|
17926
17963
|
};
|
|
17927
17964
|
}
|
|
17928
17965
|
async function enhanceToolIdea(toolIdea, siteSummary, competitiveData, options) {
|
|
17929
|
-
const
|
|
17966
|
+
const llm = judgeLLM(options);
|
|
17930
17967
|
const feasibilityScore = await evaluateToolFeasibility(
|
|
17931
17968
|
toolIdea,
|
|
17932
17969
|
siteSummary,
|
|
@@ -17971,13 +18008,7 @@ ${competitiveData?.openSourceOptions.slice(0, 3).map((t) => `- OSS: ${t.name} ($
|
|
|
17971
18008
|
"canUseExistingOSS": <true if an OSS library does most of the work>,
|
|
17972
18009
|
"ossLibrary": "<name of OSS library to use, or null>"
|
|
17973
18010
|
}`;
|
|
17974
|
-
const
|
|
17975
|
-
model: "gpt-4o",
|
|
17976
|
-
messages: [{ role: "user", content: prompt }],
|
|
17977
|
-
response_format: { type: "json_object" },
|
|
17978
|
-
temperature: 0.5
|
|
17979
|
-
});
|
|
17980
|
-
const enhanced = JSON.parse(response.choices[0].message.content || "{}");
|
|
18011
|
+
const enhanced = parseJsonReply(await llm.complete(prompt, { temperature: 0.5, best: true })) ?? {};
|
|
17981
18012
|
const priority = Math.round(
|
|
17982
18013
|
feasibilityScore.overallScore * 20 + toolIdea.volume / 1e3 * 10 + (100 - toolIdea.difficulty) / 100 * 20
|
|
17983
18014
|
);
|
|
@@ -18028,7 +18059,7 @@ async function evaluateAndEnhanceToolIdeas(toolIdeas, siteSummary, getCompetitiv
|
|
|
18028
18059
|
|
|
18029
18060
|
// src/keywords/ai-keyword-engine.ts
|
|
18030
18061
|
async function runAIKeywordResearch(options) {
|
|
18031
|
-
const { url, tier,
|
|
18062
|
+
const { url, tier, maxPages = 20 } = options;
|
|
18032
18063
|
console.log(`
|
|
18033
18064
|
\u{1F52C} Starting AI Keyword Research (${tier} tier)...
|
|
18034
18065
|
`);
|
|
@@ -18036,10 +18067,11 @@ async function runAIKeywordResearch(options) {
|
|
|
18036
18067
|
if (tier === "free") {
|
|
18037
18068
|
return runFreeKeywordResearch(crawlResult, options);
|
|
18038
18069
|
} else {
|
|
18039
|
-
|
|
18040
|
-
|
|
18070
|
+
const llm = keywordLLM(options);
|
|
18071
|
+
if (!llm) {
|
|
18072
|
+
throw new Error("An AI provider (or an OpenAI API key) is required for paid tier keyword research");
|
|
18041
18073
|
}
|
|
18042
|
-
return runPaidKeywordResearch(crawlResult, options);
|
|
18074
|
+
return runPaidKeywordResearch(crawlResult, options, llm);
|
|
18043
18075
|
}
|
|
18044
18076
|
}
|
|
18045
18077
|
async function runFreeKeywordResearch(crawlResult, options) {
|
|
@@ -18093,13 +18125,11 @@ async function runFreeKeywordResearch(crawlResult, options) {
|
|
|
18093
18125
|
]
|
|
18094
18126
|
};
|
|
18095
18127
|
}
|
|
18096
|
-
async function runPaidKeywordResearch(crawlResult, options) {
|
|
18097
|
-
const {
|
|
18128
|
+
async function runPaidKeywordResearch(crawlResult, options, llm) {
|
|
18129
|
+
const { userContext, wizardResponses, ciMode, competitiveSearchKeys } = options;
|
|
18098
18130
|
const enableEnhancedToolIdeas = options.enableEnhancedToolIdeas !== false;
|
|
18099
18131
|
console.log("\u{1F680} Running full AI analysis...");
|
|
18100
|
-
let siteSummary = await summarizeSite(crawlResult, {
|
|
18101
|
-
openaiApiKey
|
|
18102
|
-
});
|
|
18132
|
+
let siteSummary = await summarizeSite(crawlResult, { llm });
|
|
18103
18133
|
if (userContext) {
|
|
18104
18134
|
siteSummary = applyUserContext(siteSummary, userContext);
|
|
18105
18135
|
}
|
|
@@ -18108,7 +18138,7 @@ async function runPaidKeywordResearch(crawlResult, options) {
|
|
|
18108
18138
|
}
|
|
18109
18139
|
const documents = crawlResult.pages.map((p) => p.mainContent);
|
|
18110
18140
|
const nlpAnalysis = await runNLPAnalysis(documents, {
|
|
18111
|
-
|
|
18141
|
+
llm,
|
|
18112
18142
|
numClusters: 8,
|
|
18113
18143
|
numTopics: 6
|
|
18114
18144
|
});
|
|
@@ -18135,13 +18165,13 @@ async function runPaidKeywordResearch(crawlResult, options) {
|
|
|
18135
18165
|
nlpAnalysis
|
|
18136
18166
|
);
|
|
18137
18167
|
let freeToolIdeas;
|
|
18138
|
-
if (enableEnhancedToolIdeas
|
|
18168
|
+
if (enableEnhancedToolIdeas) {
|
|
18139
18169
|
console.log("\u{1F52C} Running enhanced tool idea analysis with competitive research...");
|
|
18140
18170
|
freeToolIdeas = await generateEnhancedFreeToolIdeas(
|
|
18141
18171
|
opportunities,
|
|
18142
18172
|
siteSummary,
|
|
18143
18173
|
{
|
|
18144
|
-
|
|
18174
|
+
llm,
|
|
18145
18175
|
...competitiveSearchKeys
|
|
18146
18176
|
}
|
|
18147
18177
|
);
|
|
@@ -18239,7 +18269,7 @@ async function generateEnhancedFreeToolIdeas(opportunities, summary, options) {
|
|
|
18239
18269
|
kw.volume >= 300 && // Slightly lower threshold
|
|
18240
18270
|
(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")
|
|
18241
18271
|
);
|
|
18242
|
-
const basicIdeas = toolKeywords.slice(0, 15).map((kw) => ({
|
|
18272
|
+
const basicIdeas = toolKeywords.slice(0, options.llm.maxToolIdeas ?? 15).map((kw) => ({
|
|
18243
18273
|
keyword: kw.keyword,
|
|
18244
18274
|
toolName: generateToolName(kw.keyword),
|
|
18245
18275
|
toolDescription: generateToolDescription(kw.keyword, summary),
|
|
@@ -18269,7 +18299,7 @@ async function generateEnhancedFreeToolIdeas(opportunities, summary, options) {
|
|
|
18269
18299
|
basicIdeas,
|
|
18270
18300
|
summary,
|
|
18271
18301
|
getCompetitiveData,
|
|
18272
|
-
{
|
|
18302
|
+
{ llm: options.llm }
|
|
18273
18303
|
);
|
|
18274
18304
|
return enhancedIdeas.map((enhanced) => ({
|
|
18275
18305
|
keyword: enhanced.keyword,
|
|
@@ -18600,10 +18630,9 @@ async function startWizardSession(url, options = {}) {
|
|
|
18600
18630
|
console.log("\u{1F50D} Analyzing your site...");
|
|
18601
18631
|
const crawlResult = await crawlSite(url, { maxPages: 10 });
|
|
18602
18632
|
let siteSummary;
|
|
18603
|
-
|
|
18604
|
-
|
|
18605
|
-
|
|
18606
|
-
});
|
|
18633
|
+
const llm = keywordLLM(options);
|
|
18634
|
+
if (llm) {
|
|
18635
|
+
siteSummary = await summarizeSite(crawlResult, { llm });
|
|
18607
18636
|
} else {
|
|
18608
18637
|
siteSummary = {
|
|
18609
18638
|
productName: crawlResult.domain,
|
|
@@ -31517,24 +31546,32 @@ var RankTracker = class {
|
|
|
31517
31546
|
}).eq("id", keywordId);
|
|
31518
31547
|
if (updateError) {
|
|
31519
31548
|
console.error(`Failed to save ranking for ${result.keyword}:`, updateError);
|
|
31549
|
+
return;
|
|
31550
|
+
}
|
|
31551
|
+
const { error: historyError } = await this.supabase.from("keyword_position_history").insert({
|
|
31552
|
+
keyword_id: keywordId,
|
|
31553
|
+
position: result.position,
|
|
31554
|
+
url: result.url,
|
|
31555
|
+
checked_at: result.checkedAt.toISOString()
|
|
31556
|
+
});
|
|
31557
|
+
if (historyError) {
|
|
31558
|
+
console.error(`Failed to record history for ${result.keyword}:`, historyError);
|
|
31520
31559
|
}
|
|
31521
31560
|
}
|
|
31522
31561
|
/**
|
|
31523
|
-
*
|
|
31524
|
-
* Note: Full history requires keyword_ranking_history table with keyword_ranking_id
|
|
31525
|
-
* For now, returns current state as single history entry
|
|
31562
|
+
* Ranking history for a keyword: one entry per check in the last `days`, newest first.
|
|
31526
31563
|
*/
|
|
31527
|
-
async getHistory(keywordId,
|
|
31528
|
-
const { data, error } = await this.supabase.from("
|
|
31564
|
+
async getHistory(keywordId, days = 30) {
|
|
31565
|
+
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 });
|
|
31529
31566
|
if (error || !data) {
|
|
31530
31567
|
return [];
|
|
31531
31568
|
}
|
|
31532
|
-
return
|
|
31533
|
-
position:
|
|
31534
|
-
url:
|
|
31569
|
+
return data.map((row) => ({
|
|
31570
|
+
position: row.position,
|
|
31571
|
+
url: row.url,
|
|
31535
31572
|
serpFeatures: [],
|
|
31536
|
-
recordedAt:
|
|
31537
|
-
}
|
|
31573
|
+
recordedAt: new Date(row.checked_at)
|
|
31574
|
+
}));
|
|
31538
31575
|
}
|
|
31539
31576
|
/**
|
|
31540
31577
|
* Get keyword trends for a project
|
|
@@ -32031,14 +32068,17 @@ export {
|
|
|
32031
32068
|
injectGSCVerification,
|
|
32032
32069
|
interpolatePrompt,
|
|
32033
32070
|
isGitRepo,
|
|
32071
|
+
keywordLLM,
|
|
32034
32072
|
listFiles,
|
|
32035
32073
|
loadAgent,
|
|
32036
32074
|
loadAgentByName,
|
|
32037
32075
|
mergePages,
|
|
32076
|
+
openAIKeywordLLM,
|
|
32038
32077
|
optimizeForAI,
|
|
32039
32078
|
optimizeReadme,
|
|
32040
32079
|
parseGEOResponse,
|
|
32041
32080
|
parseGSCResponse,
|
|
32081
|
+
parseJsonReply,
|
|
32042
32082
|
parseSitemap,
|
|
32043
32083
|
prioritizeKeywords,
|
|
32044
32084
|
processWizardResponse,
|