@nuxtseo/cli 0.4.0 → 0.5.0
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 +25 -3
- package/dist/cli-entry.js +1 -1
- package/dist/commands.js +5 -4
- package/dist/failures.js +9 -3
- package/dist/render.d.ts +9 -4
- package/dist/render.js +87 -9
- package/dist/skill.js +16 -0
- package/package.json +3 -3
- package/skills/nuxtseo-cli/SKILL.md +31 -14
- package/skills/nuxtseo-cli/references/commands.md +24 -6
- package/skills/nuxtseo-cli/references/indexing.md +12 -6
- package/skills/nuxtseo-cli/references/protocol.md +1 -1
package/README.md
CHANGED
|
@@ -239,10 +239,14 @@ a different dataset: `vitals summary` and `vitals trend` read CrUX field p75,
|
|
|
239
239
|
and `vitals findings` names the DOM element behind each failing vital.
|
|
240
240
|
`status` reads the Site assessment: the verdict, the one ranked Next Action, and
|
|
241
241
|
what changed. Start there. `status`, `page issues` and `search cohorts` refuse
|
|
242
|
-
with exit `4` or `5` on
|
|
242
|
+
with exit `4` or `5` on a paused Site. Removed Sites return `not_found` with exit `8`.
|
|
243
243
|
The four live `backlinks` reads need the Site to have a URL. Without one they
|
|
244
244
|
exit `2`, and retrying will not help; set the Site URL first.
|
|
245
|
-
`
|
|
245
|
+
`status` reports `assessment.scope`: `site` when the headline covers the whole
|
|
246
|
+
Site, `route-family` when one family leads. `assessment.aggregate` always holds
|
|
247
|
+
the whole-Site verdict.
|
|
248
|
+
`search cohorts` returns `analysed: false` with `reason: sampled-index-state`
|
|
249
|
+
when the not-indexed list is a sample. Otherwise it returns two lists. Only `established` is proven; `ranked` is
|
|
246
250
|
ordered by raw rate with no significance test and is leads only.
|
|
247
251
|
`backlinks summary`, `backlinks referring-domains`, `backlinks anchors`,
|
|
248
252
|
`backlinks history`, `research domain-traffic` and `research domain-availability`
|
|
@@ -250,7 +254,7 @@ can use the Team research limit. Each response reports
|
|
|
250
254
|
cache use in `evidence`; a cached response spends nothing. `backlinks recoverable`
|
|
251
255
|
reads retained rows and spends nothing.
|
|
252
256
|
`search status` reads stored connection state. It never waits for Google.
|
|
253
|
-
`search indexing summary` reads retained URL Inspection coverage.
|
|
257
|
+
`search indexing summary` reads retained URL Inspection coverage. `asOf` and `oldestCheckAt` bound the age of its verdicts.
|
|
254
258
|
`search index-history` dates an indexing change against known releases.
|
|
255
259
|
`search inspect <url>` reads Google's own verdict for one URL.
|
|
256
260
|
`page inspect <url>` reads the Nuxt SEO observation store for the same URL.
|
|
@@ -331,3 +335,21 @@ and what to do for each exit code.
|
|
|
331
335
|
The CLI prints the exact server error code. When available, stderr also includes
|
|
332
336
|
the request ID, retry delay, rate limit, reset time, and structured details.
|
|
333
337
|
The CLI never falls back to MCP, a private route, or cached feature data.
|
|
338
|
+
|
|
339
|
+
### Claim response
|
|
340
|
+
|
|
341
|
+
`actions resolve` saves your Claim. It returns before a requested scan finishes starting.
|
|
342
|
+
`status: "open"` stays unchanged until the engine verifies the Action.
|
|
343
|
+
Human output shows `Claimed, verifying` and the suggested check-back time.
|
|
344
|
+
Claimed Actions leave `actions list`. Use `actions show <action-id>` to check them.
|
|
345
|
+
|
|
346
|
+
Both `resolve` and `show` return these additive fields:
|
|
347
|
+
|
|
348
|
+
| Field | Meaning |
|
|
349
|
+
| --- | --- |
|
|
350
|
+
| `markedAt` | ISO timestamp when your Claim was saved, or `null` before a Claim. |
|
|
351
|
+
| `checkBackAt` | Suggested ISO timestamp to check again, or `null` when no automated check applies. |
|
|
352
|
+
|
|
353
|
+
A check-back time does not promise a completed check.
|
|
354
|
+
`scan-queued` can mean a re-assessment using scheduled crawl results, without a new scan.
|
|
355
|
+
A self-resolved Claim returns `status: "verified"` and `verification: "user-resolved"`.
|
package/dist/cli-entry.js
CHANGED
|
@@ -12,6 +12,7 @@ function outputError(cause) {
|
|
|
12
12
|
}
|
|
13
13
|
process.once('SIGINT', abort);
|
|
14
14
|
process.once('SIGTERM', abort);
|
|
15
|
+
// stdout writes can finish after runCli returns. Keep this handler for the process lifetime.
|
|
15
16
|
process.stdout.once('error', outputError);
|
|
16
17
|
async function readStdin() {
|
|
17
18
|
process.stdin.setEncoding('utf8');
|
|
@@ -38,4 +39,3 @@ process.exitCode = await runCli(process.argv.slice(2), {
|
|
|
38
39
|
});
|
|
39
40
|
process.removeListener('SIGINT', abort);
|
|
40
41
|
process.removeListener('SIGTERM', abort);
|
|
41
|
-
process.stdout.removeListener('error', outputError);
|
package/dist/commands.js
CHANGED
|
@@ -175,10 +175,11 @@ async function login(runtime, globals, options) {
|
|
|
175
175
|
const apiUrl = await resolveApiUrl({ apiUrl: globals.apiUrl, env: runtime.env, paths: runtime.paths });
|
|
176
176
|
if (apiUrl._tag === 'Err')
|
|
177
177
|
return fromStateError(apiUrl.error);
|
|
178
|
-
// Pairing needs a
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
|
|
178
|
+
// Pairing needs a person to approve it, and `--no-browser` says that person
|
|
179
|
+
// will open the printed URL themselves, so it is available on a headless
|
|
180
|
+
// machine too. Without the flag a non-interactive terminal keeps the paste
|
|
181
|
+
// path, so CI keeps working unchanged.
|
|
182
|
+
const token = options.withToken || (!runtime.interactive && !options.noBrowser)
|
|
182
183
|
? await readLoginToken(runtime)
|
|
183
184
|
: await pairInBrowser(runtime, apiUrl.value.apiUrl, { openBrowser: !options.noBrowser });
|
|
184
185
|
if (token._tag === 'Err')
|
package/dist/failures.js
CHANGED
|
@@ -41,7 +41,7 @@ function defaultFailureCode(exitCode) {
|
|
|
41
41
|
export function fail(exitCode, message, cause, code = defaultFailureCode(exitCode)) {
|
|
42
42
|
return { _tag: 'Err', error: { _tag: 'CliFailure', code, exitCode, message, cause } };
|
|
43
43
|
}
|
|
44
|
-
function apiExitCode(code, retryable) {
|
|
44
|
+
function apiExitCode(code, retryable, details = {}) {
|
|
45
45
|
switch (code) {
|
|
46
46
|
case 'invalid_request':
|
|
47
47
|
return EXIT_CODE.invalidInput;
|
|
@@ -57,8 +57,14 @@ function apiExitCode(code, retryable) {
|
|
|
57
57
|
return EXIT_CODE.conflict;
|
|
58
58
|
case 'rate_limited':
|
|
59
59
|
case 'quota_exhausted':
|
|
60
|
-
case 'provider_unavailable':
|
|
61
60
|
return EXIT_CODE.retryable;
|
|
61
|
+
case 'provider_unavailable':
|
|
62
|
+
// A provider that refuses to serve until its own billing is settled
|
|
63
|
+
// cannot be retried into working, so the caller must not treat it as a
|
|
64
|
+
// transient outage.
|
|
65
|
+
return details.providerCode === 'provider_payment_required'
|
|
66
|
+
? EXIT_CODE.authorization
|
|
67
|
+
: EXIT_CODE.retryable;
|
|
62
68
|
case 'not_found':
|
|
63
69
|
return EXIT_CODE.notFound;
|
|
64
70
|
case 'contract_violation':
|
|
@@ -201,7 +207,7 @@ export function fromSdkFailure(error) {
|
|
|
201
207
|
error: {
|
|
202
208
|
_tag: 'CliFailure',
|
|
203
209
|
code: error.code,
|
|
204
|
-
exitCode: apiExitCode(error.code, error.retryable),
|
|
210
|
+
exitCode: apiExitCode(error.code, error.retryable, error.details),
|
|
205
211
|
message: [
|
|
206
212
|
`${error.code}: ${error.message}`,
|
|
207
213
|
...metadataLines(error),
|
package/dist/render.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { AccountTokenData } from '@nuxtseo/protocol/v1/account';
|
|
|
3
3
|
import type { ActionDismiss, ActionList, ActionResolve, ActionShow } from '@nuxtseo/protocol/v1/actions';
|
|
4
4
|
import type { AnalyticsQueryData, AnalyticsView } from '@nuxtseo/protocol/v1/analytics';
|
|
5
5
|
import type { AuditChanges, AuditContentDecay, AuditDuplicateClusters, AuditLinkOpportunities, AuditLinkStructure } from '@nuxtseo/protocol/v1/audit';
|
|
6
|
-
import type {
|
|
6
|
+
import type { BacklinkAnchorsData, BacklinksHistory, BacklinksSummaryData, MentionsData, RecoverableBacklinksData, ReferringDomainsData } from '@nuxtseo/protocol/v1/backlinks';
|
|
7
7
|
import type { IndexCohorts, IndexingDiagnosticsData, IndexingHistoryData, SearchAnalyticsData, SearchStatusData, UrlInspectionData } from '@nuxtseo/protocol/v1/gsc';
|
|
8
8
|
import type { PageInspectData, PageIssues, PageScan } from '@nuxtseo/protocol/v1/pages';
|
|
9
9
|
import type { FieldVitalFindings, FieldVitals, MonitoredPages, PerformanceScanDetail, PerformanceScans, SitePerformanceOverview } from '@nuxtseo/protocol/v1/performance';
|
|
@@ -46,9 +46,14 @@ export declare function renderRecoverableBacklinks(data: RecoverableBacklinksDat
|
|
|
46
46
|
export declare function renderMentions(data: MentionsData): string;
|
|
47
47
|
export declare function renderFieldVitals(data: FieldVitals): string;
|
|
48
48
|
export declare function renderFieldVitalFindings(data: FieldVitalFindings): string;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
/**
|
|
50
|
+
* With Link Spam Networks, the two profile counts print without them, and each
|
|
51
|
+
* count says how many more the networks hold. `linkSpamNetworks` is absent from
|
|
52
|
+
* an API that predates it, and the summary then prints as it always did.
|
|
53
|
+
*/
|
|
54
|
+
export declare function renderBacklinksSummary(data: BacklinksSummaryData): string;
|
|
55
|
+
export declare function renderReferringDomains(data: ReferringDomainsData): string;
|
|
56
|
+
export declare function renderBacklinkAnchors(data: BacklinkAnchorsData): string;
|
|
52
57
|
export declare function renderBacklinksHistory(data: BacklinksHistory): string;
|
|
53
58
|
export declare function renderSiteStatus(data: SiteStatus): string;
|
|
54
59
|
export declare function renderActionDismissal(data: ActionDismiss): string;
|
package/dist/render.js
CHANGED
|
@@ -58,19 +58,23 @@ export function renderAction(data) {
|
|
|
58
58
|
return [
|
|
59
59
|
fields([
|
|
60
60
|
['Action', data.id],
|
|
61
|
-
['Status', data.status],
|
|
61
|
+
['Status', data.markedAt && data.status === 'open' ? 'Claimed, verifying' : data.status],
|
|
62
|
+
['Claimed at', data.markedAt ?? ''],
|
|
63
|
+
['Check back at', data.checkBackAt ?? ''],
|
|
62
64
|
['Diagnosis', data.diagnosis],
|
|
63
65
|
['Do', data.action],
|
|
64
66
|
['Finding', data.findingType],
|
|
65
67
|
['As of', data.asOf],
|
|
66
68
|
]),
|
|
67
|
-
`Evidence groups: ${data.evidenceGroups.length}`,
|
|
69
|
+
data.workPrompt ?? `Evidence groups: ${data.evidenceGroups.length}`,
|
|
68
70
|
].join('\n');
|
|
69
71
|
}
|
|
70
72
|
export function renderActionResolution(data) {
|
|
71
73
|
return fields([
|
|
72
74
|
['Action', data.actionId],
|
|
73
|
-
['Status', data.status],
|
|
75
|
+
['Status', data.markedAt && data.status === 'open' ? 'Claimed, verifying' : data.status],
|
|
76
|
+
['Claimed at', data.markedAt ?? ''],
|
|
77
|
+
['Check back at', data.checkBackAt ?? ''],
|
|
74
78
|
['Verification', data.verification],
|
|
75
79
|
]);
|
|
76
80
|
}
|
|
@@ -429,7 +433,11 @@ export function renderIndexingDiagnostics(data) {
|
|
|
429
433
|
['Indexed', data.indexed],
|
|
430
434
|
['Not indexed', Math.max(0, data.totalUrls - data.indexed)],
|
|
431
435
|
['Indexed rate', indexedPercent],
|
|
436
|
+
['Oldest check', data.oldestCheckAt],
|
|
432
437
|
['As of', data.asOf],
|
|
438
|
+
['As of source', data.asOfSource],
|
|
439
|
+
['Verdicts older than 7d', data.verdictFreshness ? `${data.verdictFreshness.olderThan7dPercent}%` : null],
|
|
440
|
+
['Verdicts older than 30d', data.verdictFreshness ? `${data.verdictFreshness.olderThan30dPercent}%` : null],
|
|
433
441
|
]),
|
|
434
442
|
...(issues.length > 0 ? ['Issues', ...issues] : []),
|
|
435
443
|
].join('\n');
|
|
@@ -618,12 +626,53 @@ export function renderFieldVitalFindings(data) {
|
|
|
618
626
|
function spendLine(evidence) {
|
|
619
627
|
return ['Source', `${evidence._tag} (${evidence.spend.units} unit)`];
|
|
620
628
|
}
|
|
629
|
+
function counted(count, one, many) {
|
|
630
|
+
return `${count} ${count === 1 ? one : many}`;
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* The anchor is third-party text. JSON quoting keeps a network on one line and
|
|
634
|
+
* prints a control character, such as a terminal escape, as an escape code.
|
|
635
|
+
*/
|
|
636
|
+
function linkSpamNetworkLine(network) {
|
|
637
|
+
return [
|
|
638
|
+
counted(network.referringDomains, 'domain', 'domains'),
|
|
639
|
+
JSON.stringify(network.anchor),
|
|
640
|
+
network.linkingPath === null ? '' : `from ${network.linkingPath}`,
|
|
641
|
+
network.otherSites > 0 ? `on ${counted(network.otherSites, 'other Site', 'other Sites')}` : '',
|
|
642
|
+
].filter(Boolean).join(' ');
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* The two profile counts without the networks, each followed by the part of the
|
|
646
|
+
* total the networks hold, so the two numbers add up to the total. The server
|
|
647
|
+
* never subtracts below the stored domains outside every network, so that part
|
|
648
|
+
* can be smaller than the networks' own counts.
|
|
649
|
+
*/
|
|
650
|
+
function countsWithoutNetworks(data, without, networks) {
|
|
651
|
+
const width = Math.max(String(without.backlinks).length, String(without.referringDomains).length);
|
|
652
|
+
const inNetworks = counted(networks.length, 'link spam network', 'link spam networks');
|
|
653
|
+
const count = (total, own, networkCount) => {
|
|
654
|
+
const held = total === null ? networkCount : Math.max(total - own, 0);
|
|
655
|
+
return `${String(own).padEnd(width)} (${held} more in ${inNetworks})`;
|
|
656
|
+
};
|
|
657
|
+
return {
|
|
658
|
+
backlinks: count(data.backlinks, without.backlinks, networks.reduce((sum, network) => sum + network.backlinks, 0)),
|
|
659
|
+
referringDomains: count(data.referringDomains, without.referringDomains, networks.reduce((sum, network) => sum + network.referringDomains, 0)),
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* With Link Spam Networks, the two profile counts print without them, and each
|
|
664
|
+
* count says how many more the networks hold. `linkSpamNetworks` is absent from
|
|
665
|
+
* an API that predates it, and the summary then prints as it always did.
|
|
666
|
+
*/
|
|
621
667
|
export function renderBacklinksSummary(data) {
|
|
622
|
-
|
|
668
|
+
const networks = data.linkSpamNetworks ?? [];
|
|
669
|
+
const without = networks.length > 0 ? data.withoutLinkSpamNetworks ?? null : null;
|
|
670
|
+
const counts = without ? countsWithoutNetworks(data, without, networks) : data;
|
|
671
|
+
const totals = fields([
|
|
623
672
|
['Target', data.target],
|
|
624
673
|
['Domain rank', data.rank],
|
|
625
|
-
['Backlinks',
|
|
626
|
-
['Referring domains',
|
|
674
|
+
['Backlinks', counts.backlinks],
|
|
675
|
+
['Referring domains', counts.referringDomains],
|
|
627
676
|
['Referring main domains', data.referringMainDomains],
|
|
628
677
|
['Referring pages', data.referringPages],
|
|
629
678
|
['Broken backlinks', data.brokenBacklinks],
|
|
@@ -631,6 +680,30 @@ export function renderBacklinksSummary(data) {
|
|
|
631
680
|
['Spam score', data.spamScore],
|
|
632
681
|
spendLine(data.evidence),
|
|
633
682
|
]);
|
|
683
|
+
if (networks.length === 0)
|
|
684
|
+
return totals;
|
|
685
|
+
return [
|
|
686
|
+
totals,
|
|
687
|
+
[`Link spam networks (${networks.length})`, ...networks.map(network => ` ${linkSpamNetworkLine(network)}`)].join('\n'),
|
|
688
|
+
].join('\n\n');
|
|
689
|
+
}
|
|
690
|
+
const LINK_SPAM_NETWORK_MARK = ' (link spam network)';
|
|
691
|
+
/**
|
|
692
|
+
* A row the server tagged as a Link Spam Network member ends with a marker, and
|
|
693
|
+
* one line under the list counts them. An untagged row is not known to be in a
|
|
694
|
+
* network, which is never "verified clean". An API that predates the field
|
|
695
|
+
* tags nothing, and the list prints as it always did.
|
|
696
|
+
*/
|
|
697
|
+
function networkMark(item) {
|
|
698
|
+
return item.linkSpamNetworkId ? LINK_SPAM_NETWORK_MARK : '';
|
|
699
|
+
}
|
|
700
|
+
function linkSpamNetworkFooter(items) {
|
|
701
|
+
const tagged = items.filter(item => item.linkSpamNetworkId).length;
|
|
702
|
+
if (tagged === 0)
|
|
703
|
+
return [];
|
|
704
|
+
return [tagged === 1
|
|
705
|
+
? '1 row is in a link spam network. Google ignores most links like these.'
|
|
706
|
+
: `${tagged} rows are in link spam networks. Google ignores most links like these.`];
|
|
634
707
|
}
|
|
635
708
|
export function renderReferringDomains(data) {
|
|
636
709
|
if (data.items.length === 0)
|
|
@@ -638,17 +711,17 @@ export function renderReferringDomains(data) {
|
|
|
638
711
|
return [
|
|
639
712
|
`Referring domains for ${data.target} (${data.items.length} of ${data.total})`,
|
|
640
713
|
...data.items.map(item => [
|
|
641
|
-
`${item.domain}`,
|
|
714
|
+
`${item.domain}${networkMark(item)}`,
|
|
642
715
|
` ${fields([
|
|
643
716
|
['rank', item.rank],
|
|
644
717
|
['backlinks', item.backlinks],
|
|
645
718
|
['referring pages', item.referringPages],
|
|
646
719
|
['broken backlinks', item.brokenBacklinks],
|
|
647
|
-
['spam score', item.spamScore],
|
|
648
720
|
['sessions (90d)', item.sessions],
|
|
649
721
|
['first seen', item.firstSeen],
|
|
650
722
|
]).split('\n').join('\n ')}`,
|
|
651
723
|
].join('\n')),
|
|
724
|
+
...linkSpamNetworkFooter(data.items),
|
|
652
725
|
fields([spendLine(data.evidence)]),
|
|
653
726
|
].join('\n');
|
|
654
727
|
}
|
|
@@ -658,7 +731,7 @@ export function renderBacklinkAnchors(data) {
|
|
|
658
731
|
return [
|
|
659
732
|
`Backlink anchors for ${data.target} (${data.items.length} of ${data.total})`,
|
|
660
733
|
...data.items.map(item => [
|
|
661
|
-
`${item.anchor || '(empty anchor)'}`,
|
|
734
|
+
`${item.anchor || '(empty anchor)'}${networkMark(item)}`,
|
|
662
735
|
` ${fields([
|
|
663
736
|
['backlinks', item.backlinks],
|
|
664
737
|
['referring domains', item.referringDomains],
|
|
@@ -667,6 +740,7 @@ export function renderBacklinkAnchors(data) {
|
|
|
667
740
|
['first seen', item.firstSeen],
|
|
668
741
|
]).split('\n').join('\n ')}`,
|
|
669
742
|
].join('\n')),
|
|
743
|
+
...linkSpamNetworkFooter(data.items),
|
|
670
744
|
fields([spendLine(data.evidence)]),
|
|
671
745
|
].join('\n');
|
|
672
746
|
}
|
|
@@ -687,12 +761,16 @@ export function renderSiteStatus(data) {
|
|
|
687
761
|
blocks.push([
|
|
688
762
|
data.assessment.summary,
|
|
689
763
|
` ${data.assessment.recommendation}`,
|
|
764
|
+
...(data.assessment.scope === 'route-family' && data.assessment.aggregate
|
|
765
|
+
? [`Whole Site: ${data.assessment.aggregate.summary}`]
|
|
766
|
+
: []),
|
|
690
767
|
].join('\n'));
|
|
691
768
|
}
|
|
692
769
|
blocks.push(fields([
|
|
693
770
|
['Site', data.site],
|
|
694
771
|
['Reach', data.assessment?.reachStage],
|
|
695
772
|
['Health', data.assessment?.healthStage],
|
|
773
|
+
['Headline scope', data.assessment?.scope],
|
|
696
774
|
['Assessed', data.assessment?.assessedAt],
|
|
697
775
|
['Data quality', data.dataQuality.status === 'complete'
|
|
698
776
|
? 'complete'
|
package/dist/skill.js
CHANGED
|
@@ -211,6 +211,22 @@ export function skillNoticeFor(installed, current, agent) {
|
|
|
211
211
|
}
|
|
212
212
|
export function skillNoticeLine(notice) {
|
|
213
213
|
const installed = notice.installed === null ? 'skill version unknown' : `skill ${notice.installed} installed`;
|
|
214
|
+
if (notice.installed !== null) {
|
|
215
|
+
const installedParts = /^(\d+)\.(\d+)\.(\d+)$/.exec(notice.installed);
|
|
216
|
+
const currentParts = /^(\d+)\.(\d+)\.(\d+)$/.exec(notice.current);
|
|
217
|
+
if (installedParts && currentParts) {
|
|
218
|
+
for (let part = 1; part <= 3; part++) {
|
|
219
|
+
const difference = Number(installedParts[part]) - Number(currentParts[part]);
|
|
220
|
+
if (difference > 0)
|
|
221
|
+
return `${installed}, CLI ${notice.current}. Update CLI before refreshing the skill: pnpm add -g @nuxtseo/cli`;
|
|
222
|
+
if (difference < 0)
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
return `${installed}, CLI ${notice.current}. Check which version is newer before refreshing the skill.`;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
214
230
|
return `${installed}, CLI ${notice.current}. Refresh with: nuxtseo skill install --agent ${notice.agent}`;
|
|
215
231
|
}
|
|
216
232
|
export function parseSkillCheckCache(content) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuxtseo/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"description": "Command line interface for the Nuxt SEO public API.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://nuxtseo.com/pro",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@clack/prompts": "^1.8.0",
|
|
36
|
-
"@nuxtseo/protocol": "^0.
|
|
37
|
-
"@nuxtseo/sdk": "^0.
|
|
36
|
+
"@nuxtseo/protocol": "^0.5.0",
|
|
37
|
+
"@nuxtseo/sdk": "^0.5.0",
|
|
38
38
|
"citty": "^0.2.2",
|
|
39
39
|
"pathe": "^2.0.3"
|
|
40
40
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: nuxtseo-cli
|
|
3
|
-
description:
|
|
3
|
+
description: Required before you interpret any `nuxtseo` CLI output, exit code, or error, including pasted output, because several fields read like findings and are not. Drives the `nuxtseo` CLI for Site status, ranked actions, Search Console and indexing, Core Web Vitals, Lighthouse Scans, research, and backlinks. Use whenever the user mentions NuxtSEO, the `nuxtseo` command, or Nuxt SEO work.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# `nuxtseo` CLI
|
|
@@ -31,10 +31,10 @@ Read the matching file before you act on its subject:
|
|
|
31
31
|
Inside the Nuxt SEO monorepo, run `node packages/cli/dist/cli-entry.js`
|
|
32
32
|
instead.
|
|
33
33
|
|
|
34
|
-
2. **Skill version.** Run `nuxtseo --version --json`.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
2. **Skill version.** Run `nuxtseo --version --json`. Compare the CLI version
|
|
35
|
+
with this file's frontmatter version. If the CLI is older, update the CLI
|
|
36
|
+
first. If the skill is older, run `nuxtseo skill install --agent claude` and
|
|
37
|
+
re-read this file. An older skill hides commands. If
|
|
38
38
|
`NUXTSEO_NO_UPDATE_CHECK` is set, the line never appears; compare this
|
|
39
39
|
file's frontmatter `version` with the binary instead.
|
|
40
40
|
|
|
@@ -107,6 +107,11 @@ nuxtseo status --site <site-id> --json
|
|
|
107
107
|
```
|
|
108
108
|
|
|
109
109
|
- `available: false` means the first assessment has not run.
|
|
110
|
+
- Read `assessment.scope` before you quote `assessment.summary`. `site` means
|
|
111
|
+
the headline describes the whole Site. `route-family` means it describes one
|
|
112
|
+
Route family: a lead to check, never the Site verdict. `assessment.aggregate`
|
|
113
|
+
is always the whole-Site verdict. If `healthStage` is `quality_rejection`,
|
|
114
|
+
the whole Site is affected, whatever a route family shows.
|
|
110
115
|
- Read `dataQuality.status` before you quote the verdict. `degraded` means
|
|
111
116
|
proofs were missing, so the verdict is Provisional. `unavailable` means it
|
|
112
117
|
has not run.
|
|
@@ -135,8 +140,9 @@ Site may have changed since the observation.
|
|
|
135
140
|
nuxtseo actions show <action-id> --site <site-id> --json
|
|
136
141
|
```
|
|
137
142
|
|
|
138
|
-
It names the pages, the finding type, and when it was observed.
|
|
139
|
-
|
|
143
|
+
It names the pages, the finding type, and when it was observed. If
|
|
144
|
+
`data.workPrompt` is not null, it is the Work Prompt: the task for this action,
|
|
145
|
+
grounded in its evidence. Start the fix from it. Two reads go deeper:
|
|
140
146
|
|
|
141
147
|
- `page issues --action-id <action-id>`: the raw observations, with URLs,
|
|
142
148
|
status codes, redirect targets, and Lighthouse selectors. This is the
|
|
@@ -189,7 +195,7 @@ you got:
|
|
|
189
195
|
| --- | --- |
|
|
190
196
|
| `page inspect` | `observations.coverage` |
|
|
191
197
|
| `page issues` | `availableKeys` |
|
|
192
|
-
| `search cohorts` | `analysed: false`: no completed crawl,
|
|
198
|
+
| `search cohorts` | `analysed: false`: no completed crawl, no Google index state, or `reason: "sampled-index-state"` |
|
|
193
199
|
| `vitals summary` | `available: false` |
|
|
194
200
|
| `research keywords`, `research rankings`, `research domain-*`, `page issues`, `search cohorts`, `vitals findings` | `data.message`, and `data.tip` for the repair |
|
|
195
201
|
|
|
@@ -199,9 +205,10 @@ a seed over three words, still exits `0`, and returns `keywords: []` with
|
|
|
199
205
|
|
|
200
206
|
**A refusal is not an empty Site.** Exit `4` is a plan, scope, or entitlement
|
|
201
207
|
blocker, for example `entitlement_required`. Exit `5` on `status`, `page
|
|
202
|
-
issues`, or `search cohorts` can mean
|
|
203
|
-
|
|
204
|
-
retry unchanged
|
|
208
|
+
issues`, or `search cohorts` can mean a paused Site.
|
|
209
|
+
Removed Sites return `not_found` with exit `8`. Report the blocker and stop.
|
|
210
|
+
Do not read it as "no data" or "nothing wrong". Do not retry unchanged,
|
|
211
|
+
and do not route around it with `pull` or another read.
|
|
205
212
|
|
|
206
213
|
**Quote the count the result ships.** Never quote the length of a list. Use
|
|
207
214
|
`data.total` from `page issues`, and `coverage.accessibilityChecksTotal` and
|
|
@@ -221,15 +228,25 @@ true` marks the list as a floor.
|
|
|
221
228
|
- `search status` reads the stored connection and never waits for Google.
|
|
222
229
|
`search analytics`, `search indexing`, `search index-history`, and
|
|
223
230
|
`search inspect` read retained Search Console evidence.
|
|
224
|
-
- An indexed count comes from `search indexing`, never from analytics
|
|
225
|
-
|
|
231
|
+
- An indexed count comes from `search indexing summary`, never from analytics
|
|
232
|
+
rows. It counts retained URL Inspection evidence as of `data.asOf`, not
|
|
233
|
+
Google's live index. Name that source, quote `asOf` with `oldestCheckAt`
|
|
234
|
+
(the evidence range), and never label the figure current or "right now",
|
|
235
|
+
even in a summary line. See
|
|
236
|
+
[references/indexing.md](references/indexing.md).
|
|
226
237
|
|
|
227
238
|
**Route families: `established` versus `ranked`.** Read `search cohorts`
|
|
228
|
-
before you list single not-indexed URLs.
|
|
239
|
+
before you list single not-indexed URLs. If it returns `analysed: false` with
|
|
240
|
+
`reason: "sampled-index-state"`, the not-indexed list is a capped sample of the
|
|
241
|
+
count Search Console reports. No family rate exists. Read `search indexing
|
|
242
|
+
summary` instead, and never infer that unlisted URLs are indexed. Never merge
|
|
243
|
+
its two lists:
|
|
229
244
|
|
|
230
245
|
- `established` holds only families whose not-indexed rate is significant
|
|
231
246
|
against the rest of the Site, after a Bonferroni correction. Only these rows
|
|
232
247
|
are a finding or a cause.
|
|
248
|
+
- `coverage.notIndexedNotInCrawl` counts not-indexed URLs that the Nuxt SEO
|
|
249
|
+
crawl did not reach. It does not count pages Google never crawled.
|
|
233
250
|
- `ranked` orders every family by raw rate, with no significance test. A row
|
|
234
251
|
with `statisticallyEstablished: false` is a lead to verify, for example by
|
|
235
252
|
inspecting its URLs. Presenting it as a cause is a hard failure.
|
|
@@ -120,7 +120,7 @@ lines.
|
|
|
120
120
|
|
|
121
121
|
| Command | What it returns, and its flags |
|
|
122
122
|
| --- | --- |
|
|
123
|
-
| `status` | The Site assessment in one read: verdict, the single ranked Next Action, open and verified counts, degraded pipeline steps, recent changes, Receipts. `available: false` means the first assessment has not run.
|
|
123
|
+
| `status` | The Site assessment in one read: verdict, the single ranked Next Action, open and verified counts, degraded pipeline steps, recent changes, Receipts. `assessment.scope` says whether the headline covers the whole Site (`site`) or one route family (`route-family`); `assessment.aggregate` is always the whole-Site verdict. `available: false` means the first assessment has not run. Paused Sites exit `4` or `5`. Removed Sites return `not_found` with exit `8` |
|
|
124
124
|
| `actions list` | Server-ranked issues and opportunities. `--limit 1..25` (default 10), `--offset`. Each row carries `evidence.freshness`; `verdict: "aged"` means the observation is over a day old, so live-check before fixing |
|
|
125
125
|
| `actions show <action-id>` | One issue or opportunity, plus its evidence. `--limit 1..100` (default 50), `--group-id`, `--cursor` |
|
|
126
126
|
| `actions resolve <action-id>` | Mutation. Claims the issue or opportunity and starts server verification. Reads the action first and sends `artifactVersion` for you |
|
|
@@ -131,7 +131,7 @@ lines.
|
|
|
131
131
|
| Command | What it returns, and its flags |
|
|
132
132
|
| --- | --- |
|
|
133
133
|
| `page inspect <url>` | Stored observations, Lighthouse rows and keywords for one Page. `--limit 1..200`, `--offset`, `--include-resolved`. Carries `observations.coverage`: `never-scanned` means no source recorded this Page, `scanned-clear` means recorded and all clear |
|
|
134
|
-
| `page issues` | Raw Page Issue rows. Pass `--action-id`, or pass both `--source` and `--issue`. Passing both selectors exits `2`. `--url`, `--path-prefix`, `--include-resolved`, `--limit 1..200`, `--offset`. When the selector matches nothing, `availableKeys` lists the pairs this Site does have.
|
|
134
|
+
| `page issues` | Raw Page Issue rows. Pass `--action-id`, or pass both `--source` and `--issue`. Passing both selectors exits `2`. `--url`, `--path-prefix`, `--include-resolved`, `--limit 1..200`, `--offset`. When the selector matches nothing, `availableKeys` lists the pairs this Site does have. Paused Sites exit `4` or `5`. Removed Sites return `not_found` with exit `8` |
|
|
135
135
|
| `page scan <url>` | Mutation. Starts a mobile Scan and a desktop Scan |
|
|
136
136
|
| `performance` | Stored Lighthouse lab overview for the Site: medians for perf, a11y, SEO, LCP, TBT, CLS. Lab Scans, never real-user field data |
|
|
137
137
|
| `scans list` | Retained Lighthouse Scans, newest first, with scores, lab vitals, and the error on a failed Scan. `--limit 1..100` (default 25) |
|
|
@@ -158,7 +158,7 @@ lines.
|
|
|
158
158
|
| `search indexing <summary\|urls>` | Retained URL Inspection coverage. `summary` returns indexed counts. `urls` supports `--issue`, `--status`, `--limit 1..500` (default 50), `--offset` |
|
|
159
159
|
| `search index-history` | Retained indexing changes and candidate causes. `--url`, `--field`, `--days 1..730`. Dates a regression against a release |
|
|
160
160
|
| `search inspect <url>` | Google's own verdict for one URL. Takes an absolute URL or a site-relative path |
|
|
161
|
-
| `search cohorts` | Not-indexed rate per route family. `--limit 1..50` (default 12), `--min-pages 1..500` (default 5).
|
|
161
|
+
| `search cohorts` | Not-indexed rate per route family. Returns `analysed: false` when the not-indexed list is a sample (`reason: sampled-index-state`). `--limit 1..50` (default 12), `--min-pages 1..500` (default 5). Paused Sites exit `4` or `5`. Removed Sites return `not_found` with exit `8` |
|
|
162
162
|
| `sitemaps list` | Search Console sitemap snapshot: errors, warnings, URL counts, sync status |
|
|
163
163
|
| `sitemaps urls` | Sitemap URL membership. `--generation-id`, `--feedpath`, `--cursor`, `--limit 1..1000` |
|
|
164
164
|
| `sitemaps submit <sitemap-url>` | Mutation. Submits one sitemap through the Site's stored Search Console credential |
|
|
@@ -215,9 +215,9 @@ research boundary in SKILL.md first.
|
|
|
215
215
|
| `research rankings <domain>` | Live domain rankings: current keywords and domain metrics. `--limit 1..100` (default 50), `--min-position 1..100` (default 1), `--max-position 1..100` (default 20), `--location-code` (default 2840), `--order position\|traffic` (default `position`). So a default run returns only the top 20 positions. If `--max-position` is under `--min-position`, the run exits `2`. An unusable domain also exits `2`, with `invalid_request`. An empty `data.keywords` with exit `0` carries the reason in `data.message`. `data.cached` reports cache use |
|
|
216
216
|
| `research domain-traffic <domain>` | A live organic traffic estimate for any domain: trend, top pages, top countries. The domain does not have to be one of your Sites |
|
|
217
217
|
| `research domain-availability <domains>` | Registration status. One comma-separated positional, up to 10 domains |
|
|
218
|
-
| `backlinks summary` | Whole-domain inbound link totals for the Site's own domain: `backlinks`, `referringDomains`, `referringPages`, `brokenBacklinks`, `brokenPages`, `crawledPages`, `spamScore`, `rank`. Takes no flags; it always reads the registered Site URL, so use `research domain-traffic` for any other domain. Every count is nullable. A `null` count means the provider returned no value. Never read it as zero. `evidence` reports cache use. Needs a Site URL |
|
|
219
|
-
| `backlinks referring-domains` | Domains that link to the Site. `--limit 1..1000` (default 100). `sessions` is 90-day referral traffic, or null when no Web Analytics property is linked. Needs a Site URL |
|
|
220
|
-
| `backlinks anchors` | Anchor text distribution of inbound links. `--limit 1..1000` (default 100). Needs a Site URL |
|
|
218
|
+
| `backlinks summary` | Whole-domain inbound link totals for the Site's own domain: `backlinks`, `referringDomains`, `referringPages`, `brokenBacklinks`, `brokenPages`, `crawledPages`, `spamScore`, `rank`. Takes no flags; it always reads the registered Site URL, so use `research domain-traffic` for any other domain. Every count is nullable. A `null` count means the provider returned no value. Never read it as zero. `linkSpamNetworks` groups referring domains that each place one generated anchor with the Site's host in it. Google ignores most links like these. `withoutLinkSpamNetworks` gives `referringDomains` and `backlinks` without them, or `null` when there is no network or no total. `evidence` reports cache use. Needs a Site URL |
|
|
219
|
+
| `backlinks referring-domains` | Domains that link to the Site. `--limit 1..1000` (default 100). `sessions` is 90-day referral traffic, or null when no Web Analytics property is linked. `linkSpamNetworkId` names the link spam network of a known member domain; null means not known, never verified clean. Needs a Site URL |
|
|
220
|
+
| `backlinks anchors` | Anchor text distribution of inbound links. `--limit 1..1000` (default 100). `linkSpamNetworkId` names the link spam network whose generated anchor the row is; null means not known, never verified clean. Needs a Site URL |
|
|
221
221
|
| `backlinks history` | A monthly inbound link series. `--from YYYY-MM-DD`. Defaults to about twelve months. Needs a Site URL |
|
|
222
222
|
| `backlinks recoverable` | Stored recoverable backlinks. `--limit 1..200`, `--offset`. Reads retained rows |
|
|
223
223
|
| `mentions list` | Stored mentions. `--limit 1..200` (default 100), `--include-filtered` to keep the Mentions AI triage marked a false positive. Reads retained rows |
|
|
@@ -254,3 +254,21 @@ nuxtseo actions list --help --json
|
|
|
254
254
|
|
|
255
255
|
Use it instead of guessing a flag. Prefer the tables above for anything they
|
|
256
256
|
already answer.
|
|
257
|
+
|
|
258
|
+
### Claim response
|
|
259
|
+
|
|
260
|
+
`actions resolve` saves your Claim. It returns before a requested scan finishes starting.
|
|
261
|
+
`status: "open"` stays unchanged until the engine verifies the Action.
|
|
262
|
+
Human output shows `Claimed, verifying` and the suggested check-back time.
|
|
263
|
+
Claimed Actions leave `actions list`. Use `actions show <action-id>` to check them.
|
|
264
|
+
|
|
265
|
+
Both `resolve` and `show` return these additive fields:
|
|
266
|
+
|
|
267
|
+
| Field | Meaning |
|
|
268
|
+
| --- | --- |
|
|
269
|
+
| `markedAt` | ISO timestamp when your Claim was saved, or `null` before a Claim. |
|
|
270
|
+
| `checkBackAt` | Suggested ISO timestamp to check again, or `null` when no automated check applies. |
|
|
271
|
+
|
|
272
|
+
A check-back time does not promise a completed check.
|
|
273
|
+
`scan-queued` can mean a re-assessment using scheduled crawl results, without a new scan.
|
|
274
|
+
A self-resolved Claim returns `status: "verified"` and `verification: "user-resolved"`.
|
|
@@ -11,11 +11,17 @@ Start with the retained URL Inspection summary:
|
|
|
11
11
|
nuxtseo search indexing summary --site <site-id> --json
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
Report `data.totalUrls`, `data.indexed`,
|
|
15
|
-
the retained URL Inspection evidence
|
|
16
|
-
|
|
17
|
-
`data.
|
|
18
|
-
|
|
14
|
+
Report `data.totalUrls`, `data.indexed`, `data.oldestCheckAt`, and
|
|
15
|
+
`data.asOf`. These are exact for the retained URL Inspection evidence in that
|
|
16
|
+
date range. `data.asOf` is when gscdump counted the verdicts, or the newest
|
|
17
|
+
check (`data.asOfSource` names it: `capture`, `rollup`, or `newest-check`). The
|
|
18
|
+
verdicts run back to `data.oldestCheckAt`, so quote both. When
|
|
19
|
+
`data.verdictFreshness` is set, quote it too: a fresh count can rest on old
|
|
20
|
+
verdicts, and `olderThan30dPercent` says how many. They are not a live
|
|
21
|
+
count of Google's whole index. The retained set can be far smaller than the
|
|
22
|
+
Search Console UI totals. State that boundary with the result. If `data.asOf`
|
|
23
|
+
is null, say the evidence age is unknown. Never call that result current or
|
|
24
|
+
live.
|
|
19
25
|
|
|
20
26
|
Use the URL view when the user asks which URLs hold a verdict:
|
|
21
27
|
|
|
@@ -54,7 +60,7 @@ Keep these reads separate:
|
|
|
54
60
|
|
|
55
61
|
| Question | Command | Report |
|
|
56
62
|
| --- | --- | --- |
|
|
57
|
-
| What is the indexed count for retained URLs? | `search indexing summary` | `totalUrls`, `indexed`, `asOf` |
|
|
63
|
+
| What is the indexed count for retained URLs? | `search indexing summary` | `totalUrls`, `indexed`, `oldestCheckAt`, `asOf` |
|
|
58
64
|
| Which retained URLs are indexed or not indexed? | `search indexing urls` | `total`, then the complete `urls` stream |
|
|
59
65
|
| Which URLs appeared in the current period? | `search analytics pages` | Rows with `impressions > 0`, then each `url` |
|
|
60
66
|
| What does Google say about one URL now? | `search inspect <url>` | The returned URL Inspection verdict |
|
|
@@ -34,7 +34,7 @@ These fields carry the reason:
|
|
|
34
34
|
| `research domain-traffic` | `data.message` |
|
|
35
35
|
| `research domain-availability` | `data.message` |
|
|
36
36
|
| `page issues` | `data.message`, plus `availableKeys` |
|
|
37
|
-
| `search cohorts` | `data.message`, plus `reason` |
|
|
37
|
+
| `search cohorts` | `data.message`, plus `reason` (`no-completed-crawl`, `no-inspection-join`, or `sampled-index-state`) |
|
|
38
38
|
| `vitals findings` | `data.message`, plus `source` |
|
|
39
39
|
|
|
40
40
|
`evidence._tag: "no-provider"` does not prove the data is absent. On `research
|