@rankcli/agent-runtime 0.0.17 → 0.0.19
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 +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +104 -142
- package/dist/index.mjs +104 -142
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -644,6 +644,7 @@ interface AIBotBlockingResult {
|
|
|
644
644
|
blockedBots: string[];
|
|
645
645
|
allowedBots: string[];
|
|
646
646
|
allBlocked: boolean;
|
|
647
|
+
contentSignal: string | null;
|
|
647
648
|
}
|
|
648
649
|
interface CloudflareAICrawlerGateResult {
|
|
649
650
|
behindCloudflare: boolean;
|
|
@@ -2751,12 +2752,11 @@ interface WorkflowConfig {
|
|
|
2751
2752
|
siteUrl: string;
|
|
2752
2753
|
features: {
|
|
2753
2754
|
audit: boolean;
|
|
2754
|
-
tracking: boolean;
|
|
2755
2755
|
autoFix: boolean;
|
|
2756
2756
|
createIssues: boolean;
|
|
2757
2757
|
createPRs: boolean;
|
|
2758
2758
|
};
|
|
2759
|
-
|
|
2759
|
+
syncToDashboard?: boolean;
|
|
2760
2760
|
}
|
|
2761
2761
|
/**
|
|
2762
2762
|
* Generate GitHub Action workflow YAML
|
package/dist/index.d.ts
CHANGED
|
@@ -644,6 +644,7 @@ interface AIBotBlockingResult {
|
|
|
644
644
|
blockedBots: string[];
|
|
645
645
|
allowedBots: string[];
|
|
646
646
|
allBlocked: boolean;
|
|
647
|
+
contentSignal: string | null;
|
|
647
648
|
}
|
|
648
649
|
interface CloudflareAICrawlerGateResult {
|
|
649
650
|
behindCloudflare: boolean;
|
|
@@ -2751,12 +2752,11 @@ interface WorkflowConfig {
|
|
|
2751
2752
|
siteUrl: string;
|
|
2752
2753
|
features: {
|
|
2753
2754
|
audit: boolean;
|
|
2754
|
-
tracking: boolean;
|
|
2755
2755
|
autoFix: boolean;
|
|
2756
2756
|
createIssues: boolean;
|
|
2757
2757
|
createPRs: boolean;
|
|
2758
2758
|
};
|
|
2759
|
-
|
|
2759
|
+
syncToDashboard?: boolean;
|
|
2760
2760
|
}
|
|
2761
2761
|
/**
|
|
2762
2762
|
* Generate GitHub Action workflow YAML
|
package/dist/index.js
CHANGED
|
@@ -6739,7 +6739,7 @@ var ISSUE_DEFINITIONS = {
|
|
|
6739
6739
|
title: "No explicit AI crawler rules on a Cloudflare-fronted site",
|
|
6740
6740
|
description: 'This site appears to be served through Cloudflare but robots.txt has no explicit Allow/Disallow rules for AI crawlers. Cloudflare blocks "mixed-use" AI crawlers by default on ad-hosting zones starting September 15, 2026, and is rolling out Pay Per Crawl / Pay Per Use gating beyond that.',
|
|
6741
6741
|
impact: "Without an explicit rule, whether AI crawlers (and future citation opportunities in ChatGPT, Claude, Perplexity, etc.) can reach this site now depends on Cloudflare account-level bot-management defaults, not on this codebase \u2014 a silent, invisible-to-git failure mode.",
|
|
6742
|
-
howToFix: "
|
|
6742
|
+
howToFix: `Two ways to make your stance explicit, either works: (1) add a Content-Signal line to robots.txt - e.g. "Content-Signal: search=yes,ai-train=no,use=reference" - the format Cloudflare's AI Crawl Control update actually reads, or (2) add explicit User-agent rules for GPTBot, Claude-Web/ClaudeBot, PerplexityBot, and Google-Extended. Either way, confirm the matching allow/block posture in the Cloudflare dashboard under Bot Management \u2192 AI Crawl Control. If you'd rather charge than block, Cloudflare's Pay Per Crawl (AI Crawl Control \u2192 Payments tab) lets you set a per-crawl price instead of a flat allow/deny \u2014 worth a look if you get meaningful AI-crawler traffic.`
|
|
6743
6743
|
},
|
|
6744
6744
|
NO_AGENT_EXPERIENCE_SURFACE: {
|
|
6745
6745
|
code: "NO_AGENT_EXPERIENCE_SURFACE",
|
|
@@ -10467,6 +10467,15 @@ async function checkLlmsTxt(baseUrl) {
|
|
|
10467
10467
|
};
|
|
10468
10468
|
}
|
|
10469
10469
|
}
|
|
10470
|
+
function parseContentSignal(robotsTxtContent) {
|
|
10471
|
+
for (const line of robotsTxtContent.split("\n")) {
|
|
10472
|
+
const trimmed = line.trim();
|
|
10473
|
+
if (trimmed.toLowerCase().startsWith("content-signal:")) {
|
|
10474
|
+
return trimmed.substring(trimmed.indexOf(":") + 1).trim();
|
|
10475
|
+
}
|
|
10476
|
+
}
|
|
10477
|
+
return null;
|
|
10478
|
+
}
|
|
10470
10479
|
async function checkAIBotBlocking(baseUrl) {
|
|
10471
10480
|
const issues = [];
|
|
10472
10481
|
const url = new URL("/robots.txt", baseUrl).href;
|
|
@@ -10484,17 +10493,21 @@ async function checkAIBotBlocking(baseUrl) {
|
|
|
10484
10493
|
robotsExists: false,
|
|
10485
10494
|
blockedBots: [],
|
|
10486
10495
|
allowedBots: Object.keys(AI_BOTS),
|
|
10487
|
-
allBlocked: false
|
|
10496
|
+
allBlocked: false,
|
|
10497
|
+
contentSignal: null
|
|
10488
10498
|
}
|
|
10489
10499
|
};
|
|
10490
10500
|
}
|
|
10491
10501
|
const content = response.data;
|
|
10492
10502
|
const lines = content.split("\n");
|
|
10503
|
+
const contentSignal = parseContentSignal(content);
|
|
10493
10504
|
let currentUserAgent = "";
|
|
10494
10505
|
const botRules = {};
|
|
10495
10506
|
for (const line of lines) {
|
|
10496
10507
|
const trimmed = line.trim().toLowerCase();
|
|
10497
|
-
if (trimmed.startsWith("
|
|
10508
|
+
if (trimmed.startsWith("content-signal:")) {
|
|
10509
|
+
continue;
|
|
10510
|
+
} else if (trimmed.startsWith("user-agent:")) {
|
|
10498
10511
|
currentUserAgent = trimmed.split(":")[1].trim();
|
|
10499
10512
|
} else if (trimmed.startsWith("disallow:")) {
|
|
10500
10513
|
const path3 = trimmed.split(":")[1]?.trim() || "";
|
|
@@ -10567,7 +10580,8 @@ async function checkAIBotBlocking(baseUrl) {
|
|
|
10567
10580
|
robotsExists: true,
|
|
10568
10581
|
blockedBots,
|
|
10569
10582
|
allowedBots,
|
|
10570
|
-
allBlocked: blockedBots.length === Object.keys(AI_BOTS).length
|
|
10583
|
+
allBlocked: blockedBots.length === Object.keys(AI_BOTS).length,
|
|
10584
|
+
contentSignal
|
|
10571
10585
|
}
|
|
10572
10586
|
};
|
|
10573
10587
|
} catch (error) {
|
|
@@ -10577,7 +10591,8 @@ async function checkAIBotBlocking(baseUrl) {
|
|
|
10577
10591
|
robotsExists: false,
|
|
10578
10592
|
blockedBots: [],
|
|
10579
10593
|
allowedBots: Object.keys(AI_BOTS),
|
|
10580
|
-
allBlocked: false
|
|
10594
|
+
allBlocked: false,
|
|
10595
|
+
contentSignal: null
|
|
10581
10596
|
}
|
|
10582
10597
|
};
|
|
10583
10598
|
}
|
|
@@ -10631,7 +10646,7 @@ async function checkCloudflareAICrawlerGate(baseUrl, botBlocking) {
|
|
|
10631
10646
|
} catch {
|
|
10632
10647
|
behindCloudflare = false;
|
|
10633
10648
|
}
|
|
10634
|
-
const hasExplicitAIRules = botBlocking.robotsExists && botBlocking.blockedBots.length > 0;
|
|
10649
|
+
const hasExplicitAIRules = botBlocking.robotsExists && (botBlocking.blockedBots.length > 0 || !!botBlocking.contentSignal);
|
|
10635
10650
|
const ambiguous = behindCloudflare && !hasExplicitAIRules;
|
|
10636
10651
|
if (ambiguous) {
|
|
10637
10652
|
issues.push({
|
|
@@ -17434,7 +17449,7 @@ async function runFullAudit(options) {
|
|
|
17434
17449
|
});
|
|
17435
17450
|
return createReport(url, domain, allIssues, pages);
|
|
17436
17451
|
}
|
|
17437
|
-
const html = fetchResult.data;
|
|
17452
|
+
const html = fetchResult.data.replace(/<\/?noscript[^>]*>/gi, "");
|
|
17438
17453
|
const headers = fetchResult.headers;
|
|
17439
17454
|
console.error(`\u{1F4DD} Phase 2: Running synchronous HTML checks (tier: ${tier}, limit: ${checksLimit})...`);
|
|
17440
17455
|
const onPageResult = analyzeOnPage(html, url);
|
|
@@ -25149,32 +25164,16 @@ This will open a browser for OAuth consent and save tokens locally.
|
|
|
25149
25164
|
// src/tracking/github-action.ts
|
|
25150
25165
|
function generateWorkflow(config) {
|
|
25151
25166
|
const cronSchedule = getCronSchedule(config.schedule);
|
|
25152
|
-
return `#
|
|
25153
|
-
#
|
|
25167
|
+
return `# RankCLI - Automated SEO Monitoring
|
|
25168
|
+
# https://rankcli.dev - generated by \`rankcli setup --github-action\`
|
|
25154
25169
|
|
|
25155
|
-
name: SEO
|
|
25170
|
+
name: RankCLI SEO Check
|
|
25156
25171
|
|
|
25157
25172
|
on:
|
|
25158
|
-
# Scheduled runs
|
|
25159
25173
|
schedule:
|
|
25160
25174
|
- cron: '${cronSchedule}'
|
|
25161
25175
|
|
|
25162
|
-
# Manual trigger
|
|
25163
25176
|
workflow_dispatch:
|
|
25164
|
-
inputs:
|
|
25165
|
-
mode:
|
|
25166
|
-
description: 'Run mode'
|
|
25167
|
-
required: true
|
|
25168
|
-
default: 'full'
|
|
25169
|
-
type: choice
|
|
25170
|
-
options:
|
|
25171
|
-
- full
|
|
25172
|
-
- audit-only
|
|
25173
|
-
- track-only
|
|
25174
|
-
|
|
25175
|
-
# Run on main branch pushes (optional)
|
|
25176
|
-
# push:
|
|
25177
|
-
# branches: [main]
|
|
25178
25177
|
|
|
25179
25178
|
env:
|
|
25180
25179
|
SITE_URL: '${config.siteUrl}'
|
|
@@ -25196,94 +25195,81 @@ jobs:
|
|
|
25196
25195
|
with:
|
|
25197
25196
|
node-version: '20'
|
|
25198
25197
|
|
|
25199
|
-
- name: Install
|
|
25200
|
-
run: npm install -g @
|
|
25201
|
-
|
|
25202
|
-
|
|
25203
|
-
- name: Pull GSC Data
|
|
25204
|
-
if: \${{ inputs.mode != 'audit-only' }}
|
|
25198
|
+
- name: Install RankCLI
|
|
25199
|
+
run: npm install -g @rankcli/cli
|
|
25200
|
+
${config.syncToDashboard ? `
|
|
25201
|
+
- name: Log in to RankCLI
|
|
25205
25202
|
env:
|
|
25206
|
-
|
|
25207
|
-
|
|
25203
|
+
RANKCLI_API_KEY: \${{ secrets.RANKCLI_API_KEY }}
|
|
25204
|
+
run: rankcli login --token "$RANKCLI_API_KEY"
|
|
25205
|
+
` : ""}
|
|
25206
|
+
- name: Run SEO audit
|
|
25208
25207
|
run: |
|
|
25209
|
-
|
|
25210
|
-
|
|
25211
|
-
|
|
25212
|
-
|
|
25213
|
-
${config.features.audit ? `
|
|
25214
|
-
- name: Run SEO Audit
|
|
25215
|
-
if: \${{ inputs.mode != 'track-only' }}
|
|
25216
|
-
run: |
|
|
25217
|
-
seo audit --url "$SITE_URL" --output json > audit-report.json
|
|
25218
|
-
` : ""}
|
|
25219
|
-
|
|
25220
|
-
- name: Generate Report
|
|
25221
|
-
id: report
|
|
25222
|
-
run: |
|
|
25223
|
-
seo report --combine \\
|
|
25224
|
-
${config.features.tracking ? "--tracking tracking-report.json \\" : ""}
|
|
25225
|
-
${config.features.audit ? "--audit audit-report.json \\" : ""}
|
|
25226
|
-
--format markdown > seo-report.md
|
|
25227
|
-
|
|
25228
|
-
# Set output for issue creation
|
|
25229
|
-
echo "report<<EOF" >> $GITHUB_OUTPUT
|
|
25230
|
-
cat seo-report.md >> $GITHUB_OUTPUT
|
|
25231
|
-
echo "EOF" >> $GITHUB_OUTPUT
|
|
25232
|
-
|
|
25233
|
-
${config.features.createIssues ? `
|
|
25234
|
-
- name: Create/Update Issue
|
|
25208
|
+
rankcli audit --url "$SITE_URL" --output json > audit-report.json
|
|
25209
|
+
${config.features.createIssues ? `
|
|
25210
|
+
- name: Create/update SEO report issue
|
|
25235
25211
|
uses: actions/github-script@v7
|
|
25236
25212
|
with:
|
|
25237
25213
|
script: |
|
|
25214
|
+
const fs = require('fs');
|
|
25215
|
+
const report = JSON.parse(fs.readFileSync('audit-report.json', 'utf8'));
|
|
25216
|
+
const errors = report.issues.filter(i => i.severity === 'error');
|
|
25217
|
+
const warnings = report.issues.filter(i => i.severity === 'warning');
|
|
25218
|
+
const notices = report.issues.filter(i => i.severity === 'notice');
|
|
25219
|
+
|
|
25220
|
+
const lines = [
|
|
25221
|
+
\`## RankCLI SEO Report\`,
|
|
25222
|
+
'',
|
|
25223
|
+
\`**Score:** \${report.score}/100 \xB7 **Errors:** \${errors.length} \xB7 **Warnings:** \${warnings.length} \xB7 **Notices:** \${notices.length}\`,
|
|
25224
|
+
'',
|
|
25225
|
+
];
|
|
25226
|
+
if (errors.length > 0) {
|
|
25227
|
+
lines.push('### Errors', ...errors.slice(0, 15).map(i => \`- \${i.title}\`), '');
|
|
25228
|
+
}
|
|
25229
|
+
if (warnings.length > 0) {
|
|
25230
|
+
lines.push('### Warnings', ...warnings.slice(0, 15).map(i => \`- \${i.title}\`), '');
|
|
25231
|
+
}
|
|
25232
|
+
if (report.dashboardUrl) {
|
|
25233
|
+
lines.push(\`[View full report on the RankCLI dashboard](\${report.dashboardUrl})\`);
|
|
25234
|
+
}
|
|
25235
|
+
const body = lines.join('\\n');
|
|
25238
25236
|
const title = 'SEO Report - ' + new Date().toISOString().split('T')[0];
|
|
25239
|
-
const body = \`\${{ steps.report.outputs.report }}\`;
|
|
25240
25237
|
|
|
25241
|
-
// Find existing issue
|
|
25242
25238
|
const issues = await github.rest.issues.listForRepo({
|
|
25243
25239
|
owner: context.repo.owner,
|
|
25244
25240
|
repo: context.repo.repo,
|
|
25245
25241
|
labels: 'seo-report',
|
|
25246
|
-
state: 'open'
|
|
25242
|
+
state: 'open',
|
|
25247
25243
|
});
|
|
25248
25244
|
|
|
25249
25245
|
if (issues.data.length > 0) {
|
|
25250
|
-
// Update existing issue
|
|
25251
25246
|
await github.rest.issues.update({
|
|
25252
25247
|
owner: context.repo.owner,
|
|
25253
25248
|
repo: context.repo.repo,
|
|
25254
25249
|
issue_number: issues.data[0].number,
|
|
25255
|
-
body
|
|
25250
|
+
body,
|
|
25256
25251
|
});
|
|
25257
|
-
console.log('Updated issue #' + issues.data[0].number);
|
|
25258
25252
|
} else {
|
|
25259
|
-
// Create new issue
|
|
25260
25253
|
await github.rest.issues.create({
|
|
25261
25254
|
owner: context.repo.owner,
|
|
25262
25255
|
repo: context.repo.repo,
|
|
25263
|
-
title
|
|
25264
|
-
body
|
|
25265
|
-
labels: ['seo-report', 'automated']
|
|
25256
|
+
title,
|
|
25257
|
+
body,
|
|
25258
|
+
labels: ['seo-report', 'automated'],
|
|
25266
25259
|
});
|
|
25267
|
-
console.log('Created new SEO report issue');
|
|
25268
25260
|
}
|
|
25269
|
-
|
|
25261
|
+
` : ""}${config.features.autoFix && config.features.createPRs ? `
|
|
25262
|
+
- name: Auto-fix issues
|
|
25263
|
+
run: rankcli fix --url "$SITE_URL" --auto --output json > fixes.json
|
|
25270
25264
|
|
|
25271
|
-
|
|
25272
|
-
|
|
25273
|
-
id: autofix
|
|
25265
|
+
- name: Check if any fixes were applied
|
|
25266
|
+
id: fixes
|
|
25274
25267
|
run: |
|
|
25275
|
-
|
|
25276
|
-
|
|
25277
|
-
|
|
25278
|
-
|
|
25279
|
-
|
|
25280
|
-
echo "fixes_made=true" >> $GITHUB_OUTPUT
|
|
25281
|
-
else
|
|
25282
|
-
echo "fixes_made=false" >> $GITHUB_OUTPUT
|
|
25283
|
-
fi
|
|
25284
|
-
|
|
25285
|
-
- name: Create PR with Fixes
|
|
25286
|
-
if: steps.autofix.outputs.fixes_made == 'true'
|
|
25268
|
+
APPLIED=$(node -p "JSON.parse(require('fs').readFileSync('fixes.json','utf8')).appliedCount || 0")
|
|
25269
|
+
echo "applied=$APPLIED" >> $GITHUB_OUTPUT
|
|
25270
|
+
|
|
25271
|
+
- name: Create PR with fixes
|
|
25272
|
+
if: steps.fixes.outputs.applied != '0'
|
|
25287
25273
|
uses: peter-evans/create-pull-request@v6
|
|
25288
25274
|
with:
|
|
25289
25275
|
token: \${{ secrets.GITHUB_TOKEN }}
|
|
@@ -25292,43 +25278,23 @@ jobs:
|
|
|
25292
25278
|
body: |
|
|
25293
25279
|
## Automated SEO Fixes
|
|
25294
25280
|
|
|
25295
|
-
This PR was automatically generated by
|
|
25296
|
-
|
|
25297
|
-
### Changes Made
|
|
25298
|
-
\`\`\`
|
|
25299
|
-
$(cat fixes.json | jq -r '.fixes[] | "- " + .description')
|
|
25300
|
-
\`\`\`
|
|
25281
|
+
This PR was automatically generated by [RankCLI](https://rankcli.dev).
|
|
25301
25282
|
|
|
25302
25283
|
### Review Checklist
|
|
25303
25284
|
- [ ] Changes look correct
|
|
25304
25285
|
- [ ] No unintended modifications
|
|
25305
25286
|
- [ ] Tests pass
|
|
25306
|
-
|
|
25307
|
-
---
|
|
25308
|
-
\u{1F916} Generated by [SEO Autopilot](https://github.com/seo-autopilot)
|
|
25309
|
-
branch: seo-autofix-\${{ github.run_number }}
|
|
25287
|
+
branch: rankcli-autofix-\${{ github.run_number }}
|
|
25310
25288
|
labels: seo, automated
|
|
25311
|
-
|
|
25312
|
-
|
|
25313
|
-
- name: Save Report Artifact
|
|
25289
|
+
` : ""}
|
|
25290
|
+
- name: Save report artifact
|
|
25314
25291
|
uses: actions/upload-artifact@v4
|
|
25315
25292
|
with:
|
|
25316
|
-
name:
|
|
25293
|
+
name: rankcli-report-\${{ github.run_number }}
|
|
25317
25294
|
path: |
|
|
25318
|
-
|
|
25319
|
-
${config.features.
|
|
25320
|
-
${config.features.tracking ? "tracking-report.json" : ""}
|
|
25295
|
+
audit-report.json
|
|
25296
|
+
${config.features.autoFix ? "fixes.json" : ""}
|
|
25321
25297
|
retention-days: 90
|
|
25322
|
-
|
|
25323
|
-
${config.apiKey ? `
|
|
25324
|
-
- name: Send to SEO Autopilot Dashboard
|
|
25325
|
-
env:
|
|
25326
|
-
SEO_AUTOPILOT_API_KEY: \${{ secrets.SEO_AUTOPILOT_API_KEY }}
|
|
25327
|
-
run: |
|
|
25328
|
-
seo sync --api-key "$SEO_AUTOPILOT_API_KEY" \\
|
|
25329
|
-
${config.features.audit ? "--audit audit-report.json \\" : ""}
|
|
25330
|
-
${config.features.tracking ? "--tracking tracking-report.json" : ""}
|
|
25331
|
-
` : ""}
|
|
25332
25298
|
`;
|
|
25333
25299
|
}
|
|
25334
25300
|
function getCronSchedule(schedule) {
|
|
@@ -25355,34 +25321,24 @@ function generateSecretsDoc(config) {
|
|
|
25355
25321
|
Go to your repository Settings > Secrets and variables > Actions
|
|
25356
25322
|
|
|
25357
25323
|
`;
|
|
25358
|
-
if (config.
|
|
25324
|
+
if (config.syncToDashboard) {
|
|
25359
25325
|
secrets += `
|
|
25360
|
-
##
|
|
25361
|
-
|
|
25362
|
-
1. \`GSC_SERVICE_ACCOUNT_EMAIL\`
|
|
25363
|
-
- Your Google Cloud service account email
|
|
25364
|
-
- Example: seo-bot@my-project.iam.gserviceaccount.com
|
|
25326
|
+
## RankCLI (required - sends audits to your dashboard)
|
|
25365
25327
|
|
|
25366
|
-
|
|
25367
|
-
-
|
|
25368
|
-
-
|
|
25369
|
-
|
|
25370
|
-
|
|
25371
|
-
1. Go to https://console.cloud.google.com
|
|
25372
|
-
2. Create a project and enable "Search Console API"
|
|
25373
|
-
3. Create a service account and download JSON key
|
|
25374
|
-
4. In Search Console, add the service account email as a user
|
|
25375
|
-
5. Copy email and private_key to GitHub Secrets
|
|
25328
|
+
1. \`RANKCLI_API_KEY\`
|
|
25329
|
+
- Get a free API key at https://rankcli.dev/account?tab=api-keys
|
|
25330
|
+
- Without this secret, remove the "Log in to RankCLI" step from
|
|
25331
|
+
.github/workflows/seo.yml - the audit still runs, it just won't
|
|
25332
|
+
appear on your dashboard.
|
|
25376
25333
|
|
|
25377
25334
|
`;
|
|
25378
|
-
}
|
|
25379
|
-
if (config.apiKey) {
|
|
25335
|
+
} else {
|
|
25380
25336
|
secrets += `
|
|
25381
|
-
|
|
25382
|
-
|
|
25383
|
-
|
|
25384
|
-
|
|
25385
|
-
|
|
25337
|
+
No secrets are required for this workflow as configured - it runs a local
|
|
25338
|
+
audit and, if enabled, opens auto-fix PRs using the repository's built-in
|
|
25339
|
+
GITHUB_TOKEN. To also sync results to your RankCLI dashboard, re-run
|
|
25340
|
+
\`rankcli setup --github-action\` and opt in when asked, or add a
|
|
25341
|
+
\`RANKCLI_API_KEY\` secret and a \`rankcli login --token\` step yourself.
|
|
25386
25342
|
|
|
25387
25343
|
`;
|
|
25388
25344
|
}
|
|
@@ -25410,18 +25366,24 @@ function writeGitHubActionFiles(projectPath, config) {
|
|
|
25410
25366
|
const secretsDocPath = path3.join(docsDir, "SEO_SETUP.md");
|
|
25411
25367
|
fs3.writeFileSync(secretsDocPath, generateSecretsDoc(config));
|
|
25412
25368
|
files.push(secretsDocPath);
|
|
25413
|
-
const instructions = `
|
|
25369
|
+
const instructions = config.syncToDashboard ? `
|
|
25414
25370
|
GitHub Action created! Next steps:
|
|
25415
25371
|
|
|
25416
|
-
1. Add
|
|
25372
|
+
1. Add the RANKCLI_API_KEY secret to your repository:
|
|
25417
25373
|
Settings > Secrets and variables > Actions
|
|
25374
|
+
Get a key at https://rankcli.dev/account?tab=api-keys
|
|
25418
25375
|
See .github/SEO_SETUP.md for details
|
|
25419
25376
|
|
|
25420
25377
|
2. The workflow will run ${config.schedule}
|
|
25421
|
-
Or trigger manually: Actions > SEO
|
|
25378
|
+
Or trigger manually: Actions > RankCLI SEO Check > Run workflow
|
|
25379
|
+
` : `
|
|
25380
|
+
GitHub Action created! No secrets required to run as-is.
|
|
25422
25381
|
|
|
25423
|
-
|
|
25424
|
-
|
|
25382
|
+
1. Commit and push .github/workflows/seo.yml
|
|
25383
|
+
2. The workflow will run ${config.schedule}
|
|
25384
|
+
Or trigger manually: Actions > RankCLI SEO Check > Run workflow
|
|
25385
|
+
3. Want audits on your RankCLI dashboard too? Re-run
|
|
25386
|
+
\`rankcli setup --github-action\` and opt in - see .github/SEO_SETUP.md
|
|
25425
25387
|
`;
|
|
25426
25388
|
return { files, instructions };
|
|
25427
25389
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -3224,7 +3224,7 @@ var ISSUE_DEFINITIONS = {
|
|
|
3224
3224
|
title: "No explicit AI crawler rules on a Cloudflare-fronted site",
|
|
3225
3225
|
description: 'This site appears to be served through Cloudflare but robots.txt has no explicit Allow/Disallow rules for AI crawlers. Cloudflare blocks "mixed-use" AI crawlers by default on ad-hosting zones starting September 15, 2026, and is rolling out Pay Per Crawl / Pay Per Use gating beyond that.',
|
|
3226
3226
|
impact: "Without an explicit rule, whether AI crawlers (and future citation opportunities in ChatGPT, Claude, Perplexity, etc.) can reach this site now depends on Cloudflare account-level bot-management defaults, not on this codebase \u2014 a silent, invisible-to-git failure mode.",
|
|
3227
|
-
howToFix: "
|
|
3227
|
+
howToFix: `Two ways to make your stance explicit, either works: (1) add a Content-Signal line to robots.txt - e.g. "Content-Signal: search=yes,ai-train=no,use=reference" - the format Cloudflare's AI Crawl Control update actually reads, or (2) add explicit User-agent rules for GPTBot, Claude-Web/ClaudeBot, PerplexityBot, and Google-Extended. Either way, confirm the matching allow/block posture in the Cloudflare dashboard under Bot Management \u2192 AI Crawl Control. If you'd rather charge than block, Cloudflare's Pay Per Crawl (AI Crawl Control \u2192 Payments tab) lets you set a per-crawl price instead of a flat allow/deny \u2014 worth a look if you get meaningful AI-crawler traffic.`
|
|
3228
3228
|
},
|
|
3229
3229
|
NO_AGENT_EXPERIENCE_SURFACE: {
|
|
3230
3230
|
code: "NO_AGENT_EXPERIENCE_SURFACE",
|
|
@@ -6940,6 +6940,15 @@ async function checkLlmsTxt(baseUrl) {
|
|
|
6940
6940
|
};
|
|
6941
6941
|
}
|
|
6942
6942
|
}
|
|
6943
|
+
function parseContentSignal(robotsTxtContent) {
|
|
6944
|
+
for (const line of robotsTxtContent.split("\n")) {
|
|
6945
|
+
const trimmed = line.trim();
|
|
6946
|
+
if (trimmed.toLowerCase().startsWith("content-signal:")) {
|
|
6947
|
+
return trimmed.substring(trimmed.indexOf(":") + 1).trim();
|
|
6948
|
+
}
|
|
6949
|
+
}
|
|
6950
|
+
return null;
|
|
6951
|
+
}
|
|
6943
6952
|
async function checkAIBotBlocking(baseUrl) {
|
|
6944
6953
|
const issues = [];
|
|
6945
6954
|
const url = new URL("/robots.txt", baseUrl).href;
|
|
@@ -6957,17 +6966,21 @@ async function checkAIBotBlocking(baseUrl) {
|
|
|
6957
6966
|
robotsExists: false,
|
|
6958
6967
|
blockedBots: [],
|
|
6959
6968
|
allowedBots: Object.keys(AI_BOTS),
|
|
6960
|
-
allBlocked: false
|
|
6969
|
+
allBlocked: false,
|
|
6970
|
+
contentSignal: null
|
|
6961
6971
|
}
|
|
6962
6972
|
};
|
|
6963
6973
|
}
|
|
6964
6974
|
const content = response.data;
|
|
6965
6975
|
const lines = content.split("\n");
|
|
6976
|
+
const contentSignal = parseContentSignal(content);
|
|
6966
6977
|
let currentUserAgent = "";
|
|
6967
6978
|
const botRules = {};
|
|
6968
6979
|
for (const line of lines) {
|
|
6969
6980
|
const trimmed = line.trim().toLowerCase();
|
|
6970
|
-
if (trimmed.startsWith("
|
|
6981
|
+
if (trimmed.startsWith("content-signal:")) {
|
|
6982
|
+
continue;
|
|
6983
|
+
} else if (trimmed.startsWith("user-agent:")) {
|
|
6971
6984
|
currentUserAgent = trimmed.split(":")[1].trim();
|
|
6972
6985
|
} else if (trimmed.startsWith("disallow:")) {
|
|
6973
6986
|
const path3 = trimmed.split(":")[1]?.trim() || "";
|
|
@@ -7040,7 +7053,8 @@ async function checkAIBotBlocking(baseUrl) {
|
|
|
7040
7053
|
robotsExists: true,
|
|
7041
7054
|
blockedBots,
|
|
7042
7055
|
allowedBots,
|
|
7043
|
-
allBlocked: blockedBots.length === Object.keys(AI_BOTS).length
|
|
7056
|
+
allBlocked: blockedBots.length === Object.keys(AI_BOTS).length,
|
|
7057
|
+
contentSignal
|
|
7044
7058
|
}
|
|
7045
7059
|
};
|
|
7046
7060
|
} catch (error) {
|
|
@@ -7050,7 +7064,8 @@ async function checkAIBotBlocking(baseUrl) {
|
|
|
7050
7064
|
robotsExists: false,
|
|
7051
7065
|
blockedBots: [],
|
|
7052
7066
|
allowedBots: Object.keys(AI_BOTS),
|
|
7053
|
-
allBlocked: false
|
|
7067
|
+
allBlocked: false,
|
|
7068
|
+
contentSignal: null
|
|
7054
7069
|
}
|
|
7055
7070
|
};
|
|
7056
7071
|
}
|
|
@@ -7104,7 +7119,7 @@ async function checkCloudflareAICrawlerGate(baseUrl, botBlocking) {
|
|
|
7104
7119
|
} catch {
|
|
7105
7120
|
behindCloudflare = false;
|
|
7106
7121
|
}
|
|
7107
|
-
const hasExplicitAIRules = botBlocking.robotsExists && botBlocking.blockedBots.length > 0;
|
|
7122
|
+
const hasExplicitAIRules = botBlocking.robotsExists && (botBlocking.blockedBots.length > 0 || !!botBlocking.contentSignal);
|
|
7108
7123
|
const ambiguous = behindCloudflare && !hasExplicitAIRules;
|
|
7109
7124
|
if (ambiguous) {
|
|
7110
7125
|
issues.push({
|
|
@@ -13891,7 +13906,7 @@ async function runFullAudit(options) {
|
|
|
13891
13906
|
});
|
|
13892
13907
|
return createReport(url, domain, allIssues, pages);
|
|
13893
13908
|
}
|
|
13894
|
-
const html = fetchResult.data;
|
|
13909
|
+
const html = fetchResult.data.replace(/<\/?noscript[^>]*>/gi, "");
|
|
13895
13910
|
const headers = fetchResult.headers;
|
|
13896
13911
|
console.error(`\u{1F4DD} Phase 2: Running synchronous HTML checks (tier: ${tier}, limit: ${checksLimit})...`);
|
|
13897
13912
|
const onPageResult = analyzeOnPage(html, url);
|
|
@@ -21600,32 +21615,16 @@ This will open a browser for OAuth consent and save tokens locally.
|
|
|
21600
21615
|
// src/tracking/github-action.ts
|
|
21601
21616
|
function generateWorkflow(config) {
|
|
21602
21617
|
const cronSchedule = getCronSchedule(config.schedule);
|
|
21603
|
-
return `#
|
|
21604
|
-
#
|
|
21618
|
+
return `# RankCLI - Automated SEO Monitoring
|
|
21619
|
+
# https://rankcli.dev - generated by \`rankcli setup --github-action\`
|
|
21605
21620
|
|
|
21606
|
-
name: SEO
|
|
21621
|
+
name: RankCLI SEO Check
|
|
21607
21622
|
|
|
21608
21623
|
on:
|
|
21609
|
-
# Scheduled runs
|
|
21610
21624
|
schedule:
|
|
21611
21625
|
- cron: '${cronSchedule}'
|
|
21612
21626
|
|
|
21613
|
-
# Manual trigger
|
|
21614
21627
|
workflow_dispatch:
|
|
21615
|
-
inputs:
|
|
21616
|
-
mode:
|
|
21617
|
-
description: 'Run mode'
|
|
21618
|
-
required: true
|
|
21619
|
-
default: 'full'
|
|
21620
|
-
type: choice
|
|
21621
|
-
options:
|
|
21622
|
-
- full
|
|
21623
|
-
- audit-only
|
|
21624
|
-
- track-only
|
|
21625
|
-
|
|
21626
|
-
# Run on main branch pushes (optional)
|
|
21627
|
-
# push:
|
|
21628
|
-
# branches: [main]
|
|
21629
21628
|
|
|
21630
21629
|
env:
|
|
21631
21630
|
SITE_URL: '${config.siteUrl}'
|
|
@@ -21647,94 +21646,81 @@ jobs:
|
|
|
21647
21646
|
with:
|
|
21648
21647
|
node-version: '20'
|
|
21649
21648
|
|
|
21650
|
-
- name: Install
|
|
21651
|
-
run: npm install -g @
|
|
21652
|
-
|
|
21653
|
-
|
|
21654
|
-
- name: Pull GSC Data
|
|
21655
|
-
if: \${{ inputs.mode != 'audit-only' }}
|
|
21649
|
+
- name: Install RankCLI
|
|
21650
|
+
run: npm install -g @rankcli/cli
|
|
21651
|
+
${config.syncToDashboard ? `
|
|
21652
|
+
- name: Log in to RankCLI
|
|
21656
21653
|
env:
|
|
21657
|
-
|
|
21658
|
-
|
|
21654
|
+
RANKCLI_API_KEY: \${{ secrets.RANKCLI_API_KEY }}
|
|
21655
|
+
run: rankcli login --token "$RANKCLI_API_KEY"
|
|
21656
|
+
` : ""}
|
|
21657
|
+
- name: Run SEO audit
|
|
21659
21658
|
run: |
|
|
21660
|
-
|
|
21661
|
-
|
|
21662
|
-
|
|
21663
|
-
|
|
21664
|
-
${config.features.audit ? `
|
|
21665
|
-
- name: Run SEO Audit
|
|
21666
|
-
if: \${{ inputs.mode != 'track-only' }}
|
|
21667
|
-
run: |
|
|
21668
|
-
seo audit --url "$SITE_URL" --output json > audit-report.json
|
|
21669
|
-
` : ""}
|
|
21670
|
-
|
|
21671
|
-
- name: Generate Report
|
|
21672
|
-
id: report
|
|
21673
|
-
run: |
|
|
21674
|
-
seo report --combine \\
|
|
21675
|
-
${config.features.tracking ? "--tracking tracking-report.json \\" : ""}
|
|
21676
|
-
${config.features.audit ? "--audit audit-report.json \\" : ""}
|
|
21677
|
-
--format markdown > seo-report.md
|
|
21678
|
-
|
|
21679
|
-
# Set output for issue creation
|
|
21680
|
-
echo "report<<EOF" >> $GITHUB_OUTPUT
|
|
21681
|
-
cat seo-report.md >> $GITHUB_OUTPUT
|
|
21682
|
-
echo "EOF" >> $GITHUB_OUTPUT
|
|
21683
|
-
|
|
21684
|
-
${config.features.createIssues ? `
|
|
21685
|
-
- name: Create/Update Issue
|
|
21659
|
+
rankcli audit --url "$SITE_URL" --output json > audit-report.json
|
|
21660
|
+
${config.features.createIssues ? `
|
|
21661
|
+
- name: Create/update SEO report issue
|
|
21686
21662
|
uses: actions/github-script@v7
|
|
21687
21663
|
with:
|
|
21688
21664
|
script: |
|
|
21665
|
+
const fs = require('fs');
|
|
21666
|
+
const report = JSON.parse(fs.readFileSync('audit-report.json', 'utf8'));
|
|
21667
|
+
const errors = report.issues.filter(i => i.severity === 'error');
|
|
21668
|
+
const warnings = report.issues.filter(i => i.severity === 'warning');
|
|
21669
|
+
const notices = report.issues.filter(i => i.severity === 'notice');
|
|
21670
|
+
|
|
21671
|
+
const lines = [
|
|
21672
|
+
\`## RankCLI SEO Report\`,
|
|
21673
|
+
'',
|
|
21674
|
+
\`**Score:** \${report.score}/100 \xB7 **Errors:** \${errors.length} \xB7 **Warnings:** \${warnings.length} \xB7 **Notices:** \${notices.length}\`,
|
|
21675
|
+
'',
|
|
21676
|
+
];
|
|
21677
|
+
if (errors.length > 0) {
|
|
21678
|
+
lines.push('### Errors', ...errors.slice(0, 15).map(i => \`- \${i.title}\`), '');
|
|
21679
|
+
}
|
|
21680
|
+
if (warnings.length > 0) {
|
|
21681
|
+
lines.push('### Warnings', ...warnings.slice(0, 15).map(i => \`- \${i.title}\`), '');
|
|
21682
|
+
}
|
|
21683
|
+
if (report.dashboardUrl) {
|
|
21684
|
+
lines.push(\`[View full report on the RankCLI dashboard](\${report.dashboardUrl})\`);
|
|
21685
|
+
}
|
|
21686
|
+
const body = lines.join('\\n');
|
|
21689
21687
|
const title = 'SEO Report - ' + new Date().toISOString().split('T')[0];
|
|
21690
|
-
const body = \`\${{ steps.report.outputs.report }}\`;
|
|
21691
21688
|
|
|
21692
|
-
// Find existing issue
|
|
21693
21689
|
const issues = await github.rest.issues.listForRepo({
|
|
21694
21690
|
owner: context.repo.owner,
|
|
21695
21691
|
repo: context.repo.repo,
|
|
21696
21692
|
labels: 'seo-report',
|
|
21697
|
-
state: 'open'
|
|
21693
|
+
state: 'open',
|
|
21698
21694
|
});
|
|
21699
21695
|
|
|
21700
21696
|
if (issues.data.length > 0) {
|
|
21701
|
-
// Update existing issue
|
|
21702
21697
|
await github.rest.issues.update({
|
|
21703
21698
|
owner: context.repo.owner,
|
|
21704
21699
|
repo: context.repo.repo,
|
|
21705
21700
|
issue_number: issues.data[0].number,
|
|
21706
|
-
body
|
|
21701
|
+
body,
|
|
21707
21702
|
});
|
|
21708
|
-
console.log('Updated issue #' + issues.data[0].number);
|
|
21709
21703
|
} else {
|
|
21710
|
-
// Create new issue
|
|
21711
21704
|
await github.rest.issues.create({
|
|
21712
21705
|
owner: context.repo.owner,
|
|
21713
21706
|
repo: context.repo.repo,
|
|
21714
|
-
title
|
|
21715
|
-
body
|
|
21716
|
-
labels: ['seo-report', 'automated']
|
|
21707
|
+
title,
|
|
21708
|
+
body,
|
|
21709
|
+
labels: ['seo-report', 'automated'],
|
|
21717
21710
|
});
|
|
21718
|
-
console.log('Created new SEO report issue');
|
|
21719
21711
|
}
|
|
21720
|
-
|
|
21712
|
+
` : ""}${config.features.autoFix && config.features.createPRs ? `
|
|
21713
|
+
- name: Auto-fix issues
|
|
21714
|
+
run: rankcli fix --url "$SITE_URL" --auto --output json > fixes.json
|
|
21721
21715
|
|
|
21722
|
-
|
|
21723
|
-
|
|
21724
|
-
id: autofix
|
|
21716
|
+
- name: Check if any fixes were applied
|
|
21717
|
+
id: fixes
|
|
21725
21718
|
run: |
|
|
21726
|
-
|
|
21727
|
-
|
|
21728
|
-
|
|
21729
|
-
|
|
21730
|
-
|
|
21731
|
-
echo "fixes_made=true" >> $GITHUB_OUTPUT
|
|
21732
|
-
else
|
|
21733
|
-
echo "fixes_made=false" >> $GITHUB_OUTPUT
|
|
21734
|
-
fi
|
|
21735
|
-
|
|
21736
|
-
- name: Create PR with Fixes
|
|
21737
|
-
if: steps.autofix.outputs.fixes_made == 'true'
|
|
21719
|
+
APPLIED=$(node -p "JSON.parse(require('fs').readFileSync('fixes.json','utf8')).appliedCount || 0")
|
|
21720
|
+
echo "applied=$APPLIED" >> $GITHUB_OUTPUT
|
|
21721
|
+
|
|
21722
|
+
- name: Create PR with fixes
|
|
21723
|
+
if: steps.fixes.outputs.applied != '0'
|
|
21738
21724
|
uses: peter-evans/create-pull-request@v6
|
|
21739
21725
|
with:
|
|
21740
21726
|
token: \${{ secrets.GITHUB_TOKEN }}
|
|
@@ -21743,43 +21729,23 @@ jobs:
|
|
|
21743
21729
|
body: |
|
|
21744
21730
|
## Automated SEO Fixes
|
|
21745
21731
|
|
|
21746
|
-
This PR was automatically generated by
|
|
21747
|
-
|
|
21748
|
-
### Changes Made
|
|
21749
|
-
\`\`\`
|
|
21750
|
-
$(cat fixes.json | jq -r '.fixes[] | "- " + .description')
|
|
21751
|
-
\`\`\`
|
|
21732
|
+
This PR was automatically generated by [RankCLI](https://rankcli.dev).
|
|
21752
21733
|
|
|
21753
21734
|
### Review Checklist
|
|
21754
21735
|
- [ ] Changes look correct
|
|
21755
21736
|
- [ ] No unintended modifications
|
|
21756
21737
|
- [ ] Tests pass
|
|
21757
|
-
|
|
21758
|
-
---
|
|
21759
|
-
\u{1F916} Generated by [SEO Autopilot](https://github.com/seo-autopilot)
|
|
21760
|
-
branch: seo-autofix-\${{ github.run_number }}
|
|
21738
|
+
branch: rankcli-autofix-\${{ github.run_number }}
|
|
21761
21739
|
labels: seo, automated
|
|
21762
|
-
|
|
21763
|
-
|
|
21764
|
-
- name: Save Report Artifact
|
|
21740
|
+
` : ""}
|
|
21741
|
+
- name: Save report artifact
|
|
21765
21742
|
uses: actions/upload-artifact@v4
|
|
21766
21743
|
with:
|
|
21767
|
-
name:
|
|
21744
|
+
name: rankcli-report-\${{ github.run_number }}
|
|
21768
21745
|
path: |
|
|
21769
|
-
|
|
21770
|
-
${config.features.
|
|
21771
|
-
${config.features.tracking ? "tracking-report.json" : ""}
|
|
21746
|
+
audit-report.json
|
|
21747
|
+
${config.features.autoFix ? "fixes.json" : ""}
|
|
21772
21748
|
retention-days: 90
|
|
21773
|
-
|
|
21774
|
-
${config.apiKey ? `
|
|
21775
|
-
- name: Send to SEO Autopilot Dashboard
|
|
21776
|
-
env:
|
|
21777
|
-
SEO_AUTOPILOT_API_KEY: \${{ secrets.SEO_AUTOPILOT_API_KEY }}
|
|
21778
|
-
run: |
|
|
21779
|
-
seo sync --api-key "$SEO_AUTOPILOT_API_KEY" \\
|
|
21780
|
-
${config.features.audit ? "--audit audit-report.json \\" : ""}
|
|
21781
|
-
${config.features.tracking ? "--tracking tracking-report.json" : ""}
|
|
21782
|
-
` : ""}
|
|
21783
21749
|
`;
|
|
21784
21750
|
}
|
|
21785
21751
|
function getCronSchedule(schedule) {
|
|
@@ -21806,34 +21772,24 @@ function generateSecretsDoc(config) {
|
|
|
21806
21772
|
Go to your repository Settings > Secrets and variables > Actions
|
|
21807
21773
|
|
|
21808
21774
|
`;
|
|
21809
|
-
if (config.
|
|
21775
|
+
if (config.syncToDashboard) {
|
|
21810
21776
|
secrets += `
|
|
21811
|
-
##
|
|
21812
|
-
|
|
21813
|
-
1. \`GSC_SERVICE_ACCOUNT_EMAIL\`
|
|
21814
|
-
- Your Google Cloud service account email
|
|
21815
|
-
- Example: seo-bot@my-project.iam.gserviceaccount.com
|
|
21777
|
+
## RankCLI (required - sends audits to your dashboard)
|
|
21816
21778
|
|
|
21817
|
-
|
|
21818
|
-
-
|
|
21819
|
-
-
|
|
21820
|
-
|
|
21821
|
-
|
|
21822
|
-
1. Go to https://console.cloud.google.com
|
|
21823
|
-
2. Create a project and enable "Search Console API"
|
|
21824
|
-
3. Create a service account and download JSON key
|
|
21825
|
-
4. In Search Console, add the service account email as a user
|
|
21826
|
-
5. Copy email and private_key to GitHub Secrets
|
|
21779
|
+
1. \`RANKCLI_API_KEY\`
|
|
21780
|
+
- Get a free API key at https://rankcli.dev/account?tab=api-keys
|
|
21781
|
+
- Without this secret, remove the "Log in to RankCLI" step from
|
|
21782
|
+
.github/workflows/seo.yml - the audit still runs, it just won't
|
|
21783
|
+
appear on your dashboard.
|
|
21827
21784
|
|
|
21828
21785
|
`;
|
|
21829
|
-
}
|
|
21830
|
-
if (config.apiKey) {
|
|
21786
|
+
} else {
|
|
21831
21787
|
secrets += `
|
|
21832
|
-
|
|
21833
|
-
|
|
21834
|
-
|
|
21835
|
-
|
|
21836
|
-
|
|
21788
|
+
No secrets are required for this workflow as configured - it runs a local
|
|
21789
|
+
audit and, if enabled, opens auto-fix PRs using the repository's built-in
|
|
21790
|
+
GITHUB_TOKEN. To also sync results to your RankCLI dashboard, re-run
|
|
21791
|
+
\`rankcli setup --github-action\` and opt in when asked, or add a
|
|
21792
|
+
\`RANKCLI_API_KEY\` secret and a \`rankcli login --token\` step yourself.
|
|
21837
21793
|
|
|
21838
21794
|
`;
|
|
21839
21795
|
}
|
|
@@ -21861,18 +21817,24 @@ function writeGitHubActionFiles(projectPath, config) {
|
|
|
21861
21817
|
const secretsDocPath = path3.join(docsDir, "SEO_SETUP.md");
|
|
21862
21818
|
fs3.writeFileSync(secretsDocPath, generateSecretsDoc(config));
|
|
21863
21819
|
files.push(secretsDocPath);
|
|
21864
|
-
const instructions = `
|
|
21820
|
+
const instructions = config.syncToDashboard ? `
|
|
21865
21821
|
GitHub Action created! Next steps:
|
|
21866
21822
|
|
|
21867
|
-
1. Add
|
|
21823
|
+
1. Add the RANKCLI_API_KEY secret to your repository:
|
|
21868
21824
|
Settings > Secrets and variables > Actions
|
|
21825
|
+
Get a key at https://rankcli.dev/account?tab=api-keys
|
|
21869
21826
|
See .github/SEO_SETUP.md for details
|
|
21870
21827
|
|
|
21871
21828
|
2. The workflow will run ${config.schedule}
|
|
21872
|
-
Or trigger manually: Actions > SEO
|
|
21829
|
+
Or trigger manually: Actions > RankCLI SEO Check > Run workflow
|
|
21830
|
+
` : `
|
|
21831
|
+
GitHub Action created! No secrets required to run as-is.
|
|
21873
21832
|
|
|
21874
|
-
|
|
21875
|
-
|
|
21833
|
+
1. Commit and push .github/workflows/seo.yml
|
|
21834
|
+
2. The workflow will run ${config.schedule}
|
|
21835
|
+
Or trigger manually: Actions > RankCLI SEO Check > Run workflow
|
|
21836
|
+
3. Want audits on your RankCLI dashboard too? Re-run
|
|
21837
|
+
\`rankcli setup --github-action\` and opt in - see .github/SEO_SETUP.md
|
|
21876
21838
|
`;
|
|
21877
21839
|
return { files, instructions };
|
|
21878
21840
|
}
|