@rankcli/agent-runtime 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +242 -0
- package/dist/analyzer-2CSWIQGD.mjs +6 -0
- package/dist/chunk-YNZYHEYM.mjs +774 -0
- package/dist/index.d.mts +4012 -0
- package/dist/index.d.ts +4012 -0
- package/dist/index.js +29672 -0
- package/dist/index.mjs +28602 -0
- package/package.json +53 -0
- package/scripts/build-deno.ts +134 -0
- package/src/audit/ai/analyzer.ts +347 -0
- package/src/audit/ai/index.ts +29 -0
- package/src/audit/ai/prompts/content-analysis.ts +271 -0
- package/src/audit/ai/types.ts +179 -0
- package/src/audit/checks/additional-checks.ts +439 -0
- package/src/audit/checks/ai-citation-worthiness.ts +399 -0
- package/src/audit/checks/ai-content-structure.ts +325 -0
- package/src/audit/checks/ai-readiness.ts +339 -0
- package/src/audit/checks/anchor-text.ts +179 -0
- package/src/audit/checks/answer-conciseness.ts +322 -0
- package/src/audit/checks/asset-minification.ts +270 -0
- package/src/audit/checks/bing-optimization.ts +206 -0
- package/src/audit/checks/brand-mention-optimization.ts +349 -0
- package/src/audit/checks/caching-headers.ts +305 -0
- package/src/audit/checks/canonical-advanced.ts +150 -0
- package/src/audit/checks/canonical-domain.ts +196 -0
- package/src/audit/checks/citation-quality.ts +358 -0
- package/src/audit/checks/client-rendering.ts +542 -0
- package/src/audit/checks/color-contrast.ts +342 -0
- package/src/audit/checks/content-freshness.ts +170 -0
- package/src/audit/checks/content-science.ts +589 -0
- package/src/audit/checks/conversion-elements.ts +526 -0
- package/src/audit/checks/crawlability.ts +220 -0
- package/src/audit/checks/directory-listing.ts +172 -0
- package/src/audit/checks/dom-analysis.ts +191 -0
- package/src/audit/checks/dom-size.ts +246 -0
- package/src/audit/checks/duplicate-content.ts +194 -0
- package/src/audit/checks/eeat-signals.ts +990 -0
- package/src/audit/checks/entity-seo.ts +396 -0
- package/src/audit/checks/featured-snippet.ts +473 -0
- package/src/audit/checks/freshness-signals.ts +443 -0
- package/src/audit/checks/funnel-intent.ts +463 -0
- package/src/audit/checks/hreflang.ts +174 -0
- package/src/audit/checks/html-compliance.ts +302 -0
- package/src/audit/checks/image-dimensions.ts +167 -0
- package/src/audit/checks/images.ts +160 -0
- package/src/audit/checks/indexnow.ts +275 -0
- package/src/audit/checks/interactive-tools.ts +475 -0
- package/src/audit/checks/internal-link-graph.ts +436 -0
- package/src/audit/checks/keyword-analysis.ts +239 -0
- package/src/audit/checks/keyword-cannibalization.ts +385 -0
- package/src/audit/checks/keyword-placement.ts +471 -0
- package/src/audit/checks/links.ts +203 -0
- package/src/audit/checks/llms-txt.ts +224 -0
- package/src/audit/checks/local-seo.ts +296 -0
- package/src/audit/checks/mobile.ts +167 -0
- package/src/audit/checks/modern-images.ts +226 -0
- package/src/audit/checks/navboost-signals.ts +395 -0
- package/src/audit/checks/on-page.ts +209 -0
- package/src/audit/checks/page-resources.ts +285 -0
- package/src/audit/checks/pagination.ts +180 -0
- package/src/audit/checks/performance.ts +153 -0
- package/src/audit/checks/platform-presence.ts +580 -0
- package/src/audit/checks/redirect-analysis.ts +153 -0
- package/src/audit/checks/redirect-chain.ts +389 -0
- package/src/audit/checks/resource-hints.ts +420 -0
- package/src/audit/checks/responsive-css.ts +247 -0
- package/src/audit/checks/responsive-images.ts +396 -0
- package/src/audit/checks/review-ecosystem.ts +415 -0
- package/src/audit/checks/robots-validation.ts +373 -0
- package/src/audit/checks/security-headers.ts +172 -0
- package/src/audit/checks/security.ts +144 -0
- package/src/audit/checks/serp-preview.ts +251 -0
- package/src/audit/checks/site-maturity.ts +444 -0
- package/src/audit/checks/social-meta.test.ts +275 -0
- package/src/audit/checks/social-meta.ts +134 -0
- package/src/audit/checks/soft-404.ts +151 -0
- package/src/audit/checks/structured-data.ts +238 -0
- package/src/audit/checks/tech-detection.ts +496 -0
- package/src/audit/checks/topical-clusters.ts +435 -0
- package/src/audit/checks/tracker-bloat.ts +462 -0
- package/src/audit/checks/tracking-verification.test.ts +371 -0
- package/src/audit/checks/tracking-verification.ts +636 -0
- package/src/audit/checks/url-safety.ts +682 -0
- package/src/audit/deno-entry.ts +66 -0
- package/src/audit/discovery/index.ts +15 -0
- package/src/audit/discovery/link-crawler.ts +232 -0
- package/src/audit/discovery/repo-routes.ts +347 -0
- package/src/audit/engine.ts +620 -0
- package/src/audit/fixes/index.ts +209 -0
- package/src/audit/fixes/social-meta-fixes.test.ts +329 -0
- package/src/audit/fixes/social-meta-fixes.ts +463 -0
- package/src/audit/index.ts +74 -0
- package/src/audit/runner.test.ts +299 -0
- package/src/audit/runner.ts +130 -0
- package/src/audit/types.ts +1953 -0
- package/src/content/featured-snippet.ts +367 -0
- package/src/content/generator.test.ts +534 -0
- package/src/content/generator.ts +501 -0
- package/src/content/headline.ts +317 -0
- package/src/content/index.ts +62 -0
- package/src/content/intent.ts +258 -0
- package/src/content/keyword-density.ts +349 -0
- package/src/content/readability.ts +262 -0
- package/src/executor.ts +336 -0
- package/src/fixer.ts +416 -0
- package/src/frameworks/detector.test.ts +248 -0
- package/src/frameworks/detector.ts +371 -0
- package/src/frameworks/index.ts +68 -0
- package/src/frameworks/recipes/angular.yaml +171 -0
- package/src/frameworks/recipes/astro.yaml +206 -0
- package/src/frameworks/recipes/django.yaml +180 -0
- package/src/frameworks/recipes/laravel.yaml +137 -0
- package/src/frameworks/recipes/nextjs.yaml +268 -0
- package/src/frameworks/recipes/nuxt.yaml +175 -0
- package/src/frameworks/recipes/rails.yaml +188 -0
- package/src/frameworks/recipes/react.yaml +202 -0
- package/src/frameworks/recipes/sveltekit.yaml +154 -0
- package/src/frameworks/recipes/vue.yaml +137 -0
- package/src/frameworks/recipes/wordpress.yaml +209 -0
- package/src/frameworks/suggestion-engine.ts +320 -0
- package/src/geo/geo-content.test.ts +305 -0
- package/src/geo/geo-content.ts +266 -0
- package/src/geo/geo-history.test.ts +473 -0
- package/src/geo/geo-history.ts +433 -0
- package/src/geo/geo-tracker.test.ts +359 -0
- package/src/geo/geo-tracker.ts +411 -0
- package/src/geo/index.ts +10 -0
- package/src/git/commit-helper.test.ts +261 -0
- package/src/git/commit-helper.ts +329 -0
- package/src/git/index.ts +12 -0
- package/src/git/pr-helper.test.ts +284 -0
- package/src/git/pr-helper.ts +307 -0
- package/src/index.ts +66 -0
- package/src/keywords/ai-keyword-engine.ts +1062 -0
- package/src/keywords/ai-summarizer.ts +387 -0
- package/src/keywords/ci-mode.ts +555 -0
- package/src/keywords/engine.ts +359 -0
- package/src/keywords/index.ts +151 -0
- package/src/keywords/llm-judge.ts +357 -0
- package/src/keywords/nlp-analysis.ts +706 -0
- package/src/keywords/prioritizer.ts +295 -0
- package/src/keywords/site-crawler.ts +342 -0
- package/src/keywords/sources/autocomplete.ts +139 -0
- package/src/keywords/sources/competitive-search.ts +450 -0
- package/src/keywords/sources/competitor-analysis.ts +374 -0
- package/src/keywords/sources/dataforseo.ts +206 -0
- package/src/keywords/sources/free-sources.ts +294 -0
- package/src/keywords/sources/gsc.ts +123 -0
- package/src/keywords/topic-grouping.ts +327 -0
- package/src/keywords/types.ts +144 -0
- package/src/keywords/wizard.ts +457 -0
- package/src/loader.ts +40 -0
- package/src/reports/index.ts +7 -0
- package/src/reports/report-generator.test.ts +293 -0
- package/src/reports/report-generator.ts +713 -0
- package/src/scheduler/alerts.test.ts +458 -0
- package/src/scheduler/alerts.ts +328 -0
- package/src/scheduler/index.ts +8 -0
- package/src/scheduler/scheduled-audit.test.ts +377 -0
- package/src/scheduler/scheduled-audit.ts +149 -0
- package/src/test/integration-test.ts +325 -0
- package/src/tools/analyzer.ts +373 -0
- package/src/tools/crawl.ts +293 -0
- package/src/tools/files.ts +301 -0
- package/src/tools/h1-fixer.ts +249 -0
- package/src/tools/index.ts +67 -0
- package/src/tracking/github-action.ts +326 -0
- package/src/tracking/google-analytics.ts +265 -0
- package/src/tracking/index.ts +45 -0
- package/src/tracking/report-generator.ts +386 -0
- package/src/tracking/search-console.ts +335 -0
- package/src/types.ts +134 -0
- package/src/utils/http.ts +302 -0
- package/src/wasm-adapter.ts +297 -0
- package/src/wasm-entry.ts +14 -0
- package/tsconfig.json +17 -0
- package/tsup.wasm.config.ts +26 -0
- package/vitest.config.ts +15 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
// Google Search Console Integration
|
|
2
|
+
// Verification injection + API data fetching
|
|
3
|
+
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
|
|
7
|
+
export interface GSCConfig {
|
|
8
|
+
verificationCode?: string; // For meta tag verification
|
|
9
|
+
siteUrl: string; // https://example.com or sc-domain:example.com
|
|
10
|
+
credentials?: GSCCredentials;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface GSCCredentials {
|
|
14
|
+
type: 'service_account' | 'oauth';
|
|
15
|
+
// Service account JSON or OAuth tokens
|
|
16
|
+
clientEmail?: string;
|
|
17
|
+
privateKey?: string;
|
|
18
|
+
accessToken?: string;
|
|
19
|
+
refreshToken?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface GSCQueryResult {
|
|
23
|
+
query: string;
|
|
24
|
+
clicks: number;
|
|
25
|
+
impressions: number;
|
|
26
|
+
ctr: number;
|
|
27
|
+
position: number;
|
|
28
|
+
page?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface GSCPerformanceData {
|
|
32
|
+
startDate: string;
|
|
33
|
+
endDate: string;
|
|
34
|
+
queries: GSCQueryResult[];
|
|
35
|
+
totalClicks: number;
|
|
36
|
+
totalImpressions: number;
|
|
37
|
+
avgCtr: number;
|
|
38
|
+
avgPosition: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface QuickWin {
|
|
42
|
+
query: string;
|
|
43
|
+
currentPosition: number;
|
|
44
|
+
impressions: number;
|
|
45
|
+
clicks: number;
|
|
46
|
+
ctr: number;
|
|
47
|
+
opportunity: 'page-1-close' | 'low-ctr' | 'high-impression';
|
|
48
|
+
suggestedAction: string;
|
|
49
|
+
potentialGain: number; // Estimated additional clicks
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Generate Search Console verification meta tag
|
|
54
|
+
*/
|
|
55
|
+
export function generateGSCVerificationTag(verificationCode: string): string {
|
|
56
|
+
return `<meta name="google-site-verification" content="${verificationCode}" />`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Inject GSC verification into project
|
|
61
|
+
*/
|
|
62
|
+
export async function injectGSCVerification(
|
|
63
|
+
projectPath: string,
|
|
64
|
+
verificationCode: string
|
|
65
|
+
): Promise<{ success: boolean; file?: string; message: string }> {
|
|
66
|
+
// Find index.html or equivalent
|
|
67
|
+
const indexPaths = [
|
|
68
|
+
path.join(projectPath, 'index.html'),
|
|
69
|
+
path.join(projectPath, 'public', 'index.html'),
|
|
70
|
+
path.join(projectPath, 'app', 'layout.tsx'),
|
|
71
|
+
path.join(projectPath, 'src', 'app', 'layout.tsx'),
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
for (const indexPath of indexPaths) {
|
|
75
|
+
if (fs.existsSync(indexPath)) {
|
|
76
|
+
let content = fs.readFileSync(indexPath, 'utf-8');
|
|
77
|
+
|
|
78
|
+
// Check if already verified
|
|
79
|
+
if (content.includes('google-site-verification')) {
|
|
80
|
+
return { success: true, file: indexPath, message: 'GSC verification already present' };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const verificationTag = generateGSCVerificationTag(verificationCode);
|
|
84
|
+
|
|
85
|
+
// For HTML files
|
|
86
|
+
if (indexPath.endsWith('.html')) {
|
|
87
|
+
content = content.replace('<head>', `<head>\n ${verificationTag}`);
|
|
88
|
+
fs.writeFileSync(indexPath, content);
|
|
89
|
+
return { success: true, file: indexPath, message: 'GSC verification tag added' };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// For React/Next.js layout files
|
|
93
|
+
if (indexPath.endsWith('.tsx') || indexPath.endsWith('.jsx')) {
|
|
94
|
+
// Add to <head> section
|
|
95
|
+
if (content.includes('<head>')) {
|
|
96
|
+
content = content.replace(
|
|
97
|
+
'<head>',
|
|
98
|
+
`<head>\n <meta name="google-site-verification" content="${verificationCode}" />`
|
|
99
|
+
);
|
|
100
|
+
fs.writeFileSync(indexPath, content);
|
|
101
|
+
return { success: true, file: indexPath, message: 'GSC verification tag added to layout' };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
success: false,
|
|
109
|
+
message: 'Could not find index.html or layout file. Add manually: ' + generateGSCVerificationTag(verificationCode),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Build GSC API request (for use with Google APIs client)
|
|
115
|
+
* User needs to set up OAuth or service account credentials
|
|
116
|
+
*/
|
|
117
|
+
export function buildGSCApiRequest(
|
|
118
|
+
siteUrl: string,
|
|
119
|
+
startDate: string,
|
|
120
|
+
endDate: string,
|
|
121
|
+
options: {
|
|
122
|
+
dimensions?: ('query' | 'page' | 'country' | 'device' | 'date')[];
|
|
123
|
+
rowLimit?: number;
|
|
124
|
+
startRow?: number;
|
|
125
|
+
} = {}
|
|
126
|
+
): object {
|
|
127
|
+
return {
|
|
128
|
+
siteUrl,
|
|
129
|
+
requestBody: {
|
|
130
|
+
startDate,
|
|
131
|
+
endDate,
|
|
132
|
+
dimensions: options.dimensions || ['query'],
|
|
133
|
+
rowLimit: options.rowLimit || 1000,
|
|
134
|
+
startRow: options.startRow || 0,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Parse GSC API response into our format
|
|
141
|
+
*/
|
|
142
|
+
export function parseGSCResponse(response: any): GSCPerformanceData {
|
|
143
|
+
const rows = response.rows || [];
|
|
144
|
+
|
|
145
|
+
const queries: GSCQueryResult[] = rows.map((row: any) => ({
|
|
146
|
+
query: row.keys?.[0] || '',
|
|
147
|
+
page: row.keys?.[1],
|
|
148
|
+
clicks: row.clicks || 0,
|
|
149
|
+
impressions: row.impressions || 0,
|
|
150
|
+
ctr: row.ctr || 0,
|
|
151
|
+
position: row.position || 0,
|
|
152
|
+
}));
|
|
153
|
+
|
|
154
|
+
const totalClicks = queries.reduce((sum, q) => sum + q.clicks, 0);
|
|
155
|
+
const totalImpressions = queries.reduce((sum, q) => sum + q.impressions, 0);
|
|
156
|
+
const avgCtr = totalImpressions > 0 ? totalClicks / totalImpressions : 0;
|
|
157
|
+
const avgPosition = queries.length > 0
|
|
158
|
+
? queries.reduce((sum, q) => sum + q.position, 0) / queries.length
|
|
159
|
+
: 0;
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
startDate: response.startDate || '',
|
|
163
|
+
endDate: response.endDate || '',
|
|
164
|
+
queries,
|
|
165
|
+
totalClicks,
|
|
166
|
+
totalImpressions,
|
|
167
|
+
avgCtr,
|
|
168
|
+
avgPosition,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Identify quick wins from GSC data
|
|
174
|
+
*/
|
|
175
|
+
export function identifyQuickWins(data: GSCPerformanceData): QuickWin[] {
|
|
176
|
+
const quickWins: QuickWin[] = [];
|
|
177
|
+
|
|
178
|
+
for (const query of data.queries) {
|
|
179
|
+
// Skip very low impression queries
|
|
180
|
+
if (query.impressions < 10) continue;
|
|
181
|
+
|
|
182
|
+
// Quick Win 1: Position 11-20 (almost page 1)
|
|
183
|
+
if (query.position >= 11 && query.position <= 20) {
|
|
184
|
+
const potentialGain = Math.round(query.impressions * 0.1); // ~10% CTR if on page 1
|
|
185
|
+
quickWins.push({
|
|
186
|
+
...query,
|
|
187
|
+
currentPosition: query.position,
|
|
188
|
+
opportunity: 'page-1-close',
|
|
189
|
+
suggestedAction: `Improve content for "${query.query}" - you're at position ${Math.round(query.position)}, just off page 1!`,
|
|
190
|
+
potentialGain,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Quick Win 2: High impressions but low CTR (position 1-10)
|
|
195
|
+
if (query.position <= 10 && query.impressions > 100 && query.ctr < 0.02) {
|
|
196
|
+
const expectedCtr = getExpectedCtr(query.position);
|
|
197
|
+
const potentialGain = Math.round(query.impressions * (expectedCtr - query.ctr));
|
|
198
|
+
if (potentialGain > 5) {
|
|
199
|
+
quickWins.push({
|
|
200
|
+
...query,
|
|
201
|
+
currentPosition: query.position,
|
|
202
|
+
opportunity: 'low-ctr',
|
|
203
|
+
suggestedAction: `Improve title/description for "${query.query}" - CTR is ${(query.ctr * 100).toFixed(1)}% vs expected ${(expectedCtr * 100).toFixed(1)}%`,
|
|
204
|
+
potentialGain,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Quick Win 3: High impressions, position 4-10 (could be #1)
|
|
210
|
+
if (query.position >= 4 && query.position <= 10 && query.impressions > 500) {
|
|
211
|
+
const potentialGain = Math.round(query.impressions * 0.15); // Gain from reaching #1
|
|
212
|
+
quickWins.push({
|
|
213
|
+
...query,
|
|
214
|
+
currentPosition: query.position,
|
|
215
|
+
opportunity: 'high-impression',
|
|
216
|
+
suggestedAction: `Push "${query.query}" to top 3 - high traffic potential at ${query.impressions} impressions/month`,
|
|
217
|
+
potentialGain,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Sort by potential gain
|
|
223
|
+
quickWins.sort((a, b) => b.potentialGain - a.potentialGain);
|
|
224
|
+
|
|
225
|
+
return quickWins.slice(0, 20); // Top 20 quick wins
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Expected CTR by position (industry average)
|
|
230
|
+
*/
|
|
231
|
+
function getExpectedCtr(position: number): number {
|
|
232
|
+
const ctrByPosition: Record<number, number> = {
|
|
233
|
+
1: 0.28,
|
|
234
|
+
2: 0.15,
|
|
235
|
+
3: 0.11,
|
|
236
|
+
4: 0.08,
|
|
237
|
+
5: 0.06,
|
|
238
|
+
6: 0.05,
|
|
239
|
+
7: 0.04,
|
|
240
|
+
8: 0.03,
|
|
241
|
+
9: 0.03,
|
|
242
|
+
10: 0.02,
|
|
243
|
+
};
|
|
244
|
+
return ctrByPosition[Math.round(position)] || 0.01;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Compare two periods to identify trends
|
|
249
|
+
*/
|
|
250
|
+
export function comparePeriods(
|
|
251
|
+
current: GSCPerformanceData,
|
|
252
|
+
previous: GSCPerformanceData
|
|
253
|
+
): {
|
|
254
|
+
improved: GSCQueryResult[];
|
|
255
|
+
declined: GSCQueryResult[];
|
|
256
|
+
newKeywords: GSCQueryResult[];
|
|
257
|
+
lostKeywords: GSCQueryResult[];
|
|
258
|
+
summary: {
|
|
259
|
+
clicksChange: number;
|
|
260
|
+
impressionsChange: number;
|
|
261
|
+
avgPositionChange: number;
|
|
262
|
+
};
|
|
263
|
+
} {
|
|
264
|
+
const previousMap = new Map(previous.queries.map(q => [q.query, q]));
|
|
265
|
+
const currentMap = new Map(current.queries.map(q => [q.query, q]));
|
|
266
|
+
|
|
267
|
+
const improved: GSCQueryResult[] = [];
|
|
268
|
+
const declined: GSCQueryResult[] = [];
|
|
269
|
+
const newKeywords: GSCQueryResult[] = [];
|
|
270
|
+
const lostKeywords: GSCQueryResult[] = [];
|
|
271
|
+
|
|
272
|
+
// Check current queries
|
|
273
|
+
for (const query of current.queries) {
|
|
274
|
+
const prev = previousMap.get(query.query);
|
|
275
|
+
if (!prev) {
|
|
276
|
+
newKeywords.push(query);
|
|
277
|
+
} else if (query.position < prev.position - 2) {
|
|
278
|
+
improved.push(query);
|
|
279
|
+
} else if (query.position > prev.position + 2) {
|
|
280
|
+
declined.push(query);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Check for lost keywords
|
|
285
|
+
for (const query of previous.queries) {
|
|
286
|
+
if (!currentMap.has(query.query) && query.impressions > 50) {
|
|
287
|
+
lostKeywords.push(query);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
improved: improved.slice(0, 10),
|
|
293
|
+
declined: declined.slice(0, 10),
|
|
294
|
+
newKeywords: newKeywords.slice(0, 10),
|
|
295
|
+
lostKeywords: lostKeywords.slice(0, 10),
|
|
296
|
+
summary: {
|
|
297
|
+
clicksChange: current.totalClicks - previous.totalClicks,
|
|
298
|
+
impressionsChange: current.totalImpressions - previous.totalImpressions,
|
|
299
|
+
avgPositionChange: current.avgPosition - previous.avgPosition,
|
|
300
|
+
},
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Generate credentials setup instructions
|
|
306
|
+
*/
|
|
307
|
+
export function getGSCSetupInstructions(): string {
|
|
308
|
+
return `
|
|
309
|
+
# Google Search Console API Setup
|
|
310
|
+
|
|
311
|
+
## Option 1: Service Account (Recommended for GitHub Actions)
|
|
312
|
+
|
|
313
|
+
1. Go to Google Cloud Console: https://console.cloud.google.com
|
|
314
|
+
2. Create a new project or select existing
|
|
315
|
+
3. Enable "Search Console API"
|
|
316
|
+
4. Go to "IAM & Admin" > "Service Accounts"
|
|
317
|
+
5. Create a service account
|
|
318
|
+
6. Create a JSON key and download it
|
|
319
|
+
7. In Search Console, add the service account email as a user
|
|
320
|
+
|
|
321
|
+
Add to GitHub Secrets:
|
|
322
|
+
- GSC_SERVICE_ACCOUNT_EMAIL: your-service-account@project.iam.gserviceaccount.com
|
|
323
|
+
- GSC_PRIVATE_KEY: (the private_key from JSON, including -----BEGIN/END-----)
|
|
324
|
+
|
|
325
|
+
## Option 2: OAuth (For user-specific access)
|
|
326
|
+
|
|
327
|
+
1. Go to Google Cloud Console
|
|
328
|
+
2. Enable "Search Console API"
|
|
329
|
+
3. Create OAuth 2.0 credentials
|
|
330
|
+
4. Download client_secret.json
|
|
331
|
+
5. Run: seo auth --google
|
|
332
|
+
|
|
333
|
+
This will open a browser for OAuth consent and save tokens locally.
|
|
334
|
+
`;
|
|
335
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
export interface AgentDefinition {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
model: string;
|
|
5
|
+
temperature: number;
|
|
6
|
+
max_tokens: number;
|
|
7
|
+
tools: AgentTool[];
|
|
8
|
+
system: string;
|
|
9
|
+
prompt: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface AgentTool {
|
|
13
|
+
name: string;
|
|
14
|
+
description: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface SEOIssue {
|
|
18
|
+
severity: 'critical' | 'warning' | 'info';
|
|
19
|
+
category: 'meta' | 'schema' | 'content' | 'technical' | 'performance' | 'social' | 'accessibility';
|
|
20
|
+
code: string;
|
|
21
|
+
message: string;
|
|
22
|
+
impact: string;
|
|
23
|
+
element?: string;
|
|
24
|
+
fix?: {
|
|
25
|
+
file: string;
|
|
26
|
+
before: string | null;
|
|
27
|
+
after: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface SEOAnalysisResult {
|
|
32
|
+
url: string;
|
|
33
|
+
score: number;
|
|
34
|
+
core_web_vitals?: {
|
|
35
|
+
lcp?: { value: string; status: 'good' | 'needs-improvement' | 'poor' };
|
|
36
|
+
fid?: { value: string; status: 'good' | 'needs-improvement' | 'poor' };
|
|
37
|
+
cls?: { value: string; status: 'good' | 'needs-improvement' | 'poor' };
|
|
38
|
+
};
|
|
39
|
+
issues: SEOIssue[];
|
|
40
|
+
recommendations: SEORecommendation[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface SEORecommendation {
|
|
44
|
+
priority: 'high' | 'medium' | 'low';
|
|
45
|
+
category: string;
|
|
46
|
+
message: string;
|
|
47
|
+
impact: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface CrawlResult {
|
|
51
|
+
url: string;
|
|
52
|
+
html: string;
|
|
53
|
+
statusCode: number;
|
|
54
|
+
headers: Record<string, string>;
|
|
55
|
+
loadTime: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface MetaData {
|
|
59
|
+
title?: string;
|
|
60
|
+
description?: string;
|
|
61
|
+
canonical?: string;
|
|
62
|
+
robots?: string;
|
|
63
|
+
viewport?: string;
|
|
64
|
+
charset?: string;
|
|
65
|
+
openGraph: {
|
|
66
|
+
title?: string;
|
|
67
|
+
description?: string;
|
|
68
|
+
image?: string;
|
|
69
|
+
url?: string;
|
|
70
|
+
type?: string;
|
|
71
|
+
siteName?: string;
|
|
72
|
+
};
|
|
73
|
+
twitter: {
|
|
74
|
+
card?: string;
|
|
75
|
+
title?: string;
|
|
76
|
+
description?: string;
|
|
77
|
+
image?: string;
|
|
78
|
+
site?: string;
|
|
79
|
+
};
|
|
80
|
+
other: Record<string, string>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface HeadingStructure {
|
|
84
|
+
tag: string;
|
|
85
|
+
text: string;
|
|
86
|
+
level: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface ImageInfo {
|
|
90
|
+
src: string;
|
|
91
|
+
alt: string | null;
|
|
92
|
+
width?: string;
|
|
93
|
+
height?: string;
|
|
94
|
+
loading?: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface LinkInfo {
|
|
98
|
+
href: string;
|
|
99
|
+
text: string;
|
|
100
|
+
isInternal: boolean;
|
|
101
|
+
isNofollow: boolean;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface SchemaData {
|
|
105
|
+
type: string;
|
|
106
|
+
data: Record<string, unknown>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface FrameworkInfo {
|
|
110
|
+
name: string;
|
|
111
|
+
version?: string;
|
|
112
|
+
router?: 'app' | 'pages' | 'file-based' | 'custom';
|
|
113
|
+
metaPattern: 'metadata-export' | 'head-component' | 'frontmatter' | 'meta-function' | 'html-head';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface Fix {
|
|
117
|
+
issue: {
|
|
118
|
+
code: string;
|
|
119
|
+
message: string;
|
|
120
|
+
severity: string;
|
|
121
|
+
};
|
|
122
|
+
file: string;
|
|
123
|
+
before: string | null;
|
|
124
|
+
after: string;
|
|
125
|
+
explanation: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface ToolResult {
|
|
129
|
+
success: boolean;
|
|
130
|
+
data?: unknown;
|
|
131
|
+
error?: string;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export type ToolFunction = (params: Record<string, unknown>) => Promise<ToolResult>;
|