@nuxtseo/cli 0.5.3 → 0.5.5
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 +6 -1
- package/dist/commands.js +83 -4
- package/dist/pull.js +8 -5
- package/dist/render.d.ts +2 -1
- package/dist/render.js +32 -3
- package/package.json +3 -3
- package/skills/nuxtseo-cli/SKILL.md +5 -1
- package/skills/nuxtseo-cli/references/commands.md +13 -1
- package/skills/nuxtseo-cli/references/indexing.md +2 -1
package/README.md
CHANGED
|
@@ -206,6 +206,9 @@ nuxtseo sitemaps urls
|
|
|
206
206
|
nuxtseo sitemaps submit <sitemap-url>
|
|
207
207
|
nuxtseo sitemaps delete <sitemap-url>
|
|
208
208
|
nuxtseo research overview
|
|
209
|
+
nuxtseo research competitors list
|
|
210
|
+
nuxtseo research competitors add <domain> --yes
|
|
211
|
+
nuxtseo research competitors remove <competitor-id> --yes
|
|
209
212
|
nuxtseo research domain-traffic <domain>
|
|
210
213
|
nuxtseo research domain-availability <domains>
|
|
211
214
|
nuxtseo research keywords <topic>
|
|
@@ -256,7 +259,9 @@ reads retained rows and spends nothing.
|
|
|
256
259
|
`search status` reads stored connection state. It never waits for Google.
|
|
257
260
|
`search indexing summary` reads retained URL Inspection coverage. `asOf` and `oldestCheckAt` bound the age of its verdicts.
|
|
258
261
|
`search index-history` dates an indexing change against known releases.
|
|
259
|
-
`search inspect <url>` reads Google
|
|
262
|
+
`search inspect <url>` reads the latest retained Google URL Inspection for one URL.
|
|
263
|
+
`search inspect <url> --fresh --yes` requests Google URL Inspection through the Site connection and uses inspection quota.
|
|
264
|
+
Fresh inspection reports errors and skipped URLs with a nonzero exit code. If the request needs more time, add `--timeout-ms 60000`.
|
|
260
265
|
`page inspect <url>` reads the Nuxt SEO observation store for the same URL.
|
|
261
266
|
`research keywords`, `research serp`, and `research rankings` can use the Team
|
|
262
267
|
research limit. Keyword responses report cache use in `evidence`. SERP and
|
package/dist/commands.js
CHANGED
|
@@ -9,7 +9,7 @@ import { EXIT_CODE, fail, fromSdkFailure, ok } from './failures.js';
|
|
|
9
9
|
import { awaitPairingApproval, createCliPairingClient, startPairing } from './pairing.js';
|
|
10
10
|
import { parseAbsolutePageUrl, parseChoice, parseInteger, parsePageUrlOrPath, parseRatio } from './parse.js';
|
|
11
11
|
import { PULL_PERIODS, runPull } from './pull.js';
|
|
12
|
-
import { renderAccountToken, renderAction, renderActionDismissal, renderActionResolution, renderActions, renderAnalytics, renderAnnotation, renderAnnotationDeleted, renderAnnotations, renderAuditChanges, renderBacklinkAnchors, renderBacklinksHistory, renderBacklinksSummary, renderContentDecay, renderDomainAvailability, renderDomainTraffic, renderDuplicateClusters, renderFieldVitalFindings, renderFieldVitals, renderIndexCohorts, renderIndexingDiagnostics, renderIndexingHistory, renderKeywordResearch, renderLinkOpportunities, renderLinkStructure, renderMentions, renderMonitoredPages, renderPageInspection, renderPageIssues, renderPageScan, renderPerformance, renderRankingsResearch, renderRecoverableBacklinks, renderReferringDomains, renderResearchOverview, renderScanDetail, renderScans, renderSearchAnalytics, renderSearchStatus, renderSerpResearch, renderSitemapAction, renderSitemaps, renderSitemapUrls, renderSites, renderSiteStatus, renderTimeline, renderUrlInspection, renderUsage, } from './render.js';
|
|
12
|
+
import { renderAccountToken, renderAction, renderActionDismissal, renderActionResolution, renderActions, renderAnalytics, renderAnnotation, renderAnnotationDeleted, renderAnnotations, renderAuditChanges, renderBacklinkAnchors, renderBacklinksHistory, renderBacklinksSummary, renderContentDecay, renderDomainAvailability, renderDomainTraffic, renderDuplicateClusters, renderFieldVitalFindings, renderFieldVitals, renderIndexCohorts, renderIndexingDiagnostics, renderIndexingHistory, renderKeywordResearch, renderLinkOpportunities, renderLinkStructure, renderMentions, renderMonitoredPages, renderPageInspection, renderPageIssues, renderPageScan, renderPerformance, renderRankingsResearch, renderRecoverableBacklinks, renderReferringDomains, renderResearchOverview, renderScanDetail, renderScans, renderSearchAnalytics, renderSearchStatus, renderSerpResearch, renderSitemapAction, renderSitemaps, renderSitemapUrls, renderSites, renderSiteStatus, renderTimeline, renderUrlInspection, renderUrlInspectionRefresh, renderUsage, } from './render.js';
|
|
13
13
|
import { writeCliResponse, writeDiagnostic, writeOutput, writeProtocolResponse } from './runtime.js';
|
|
14
14
|
import { resolveSite } from './site.js';
|
|
15
15
|
import { installSkill, SKILL_AGENTS } from './skill.js';
|
|
@@ -717,13 +717,36 @@ async function searchIndexHistory(runtime, globals, args) {
|
|
|
717
717
|
}, { signal: runtime.requestSignal }));
|
|
718
718
|
return present(runtime, globals, response, value => renderIndexingHistory(value.data));
|
|
719
719
|
}
|
|
720
|
-
async function searchInspect(runtime, globals, rawUrl) {
|
|
720
|
+
async function searchInspect(runtime, globals, rawUrl, fresh = false) {
|
|
721
721
|
const url = parsePageUrlOrPath(rawUrl);
|
|
722
722
|
if (url._tag === 'Err')
|
|
723
723
|
return url;
|
|
724
|
+
if (fresh) {
|
|
725
|
+
const confirmed = await confirmMutation(runtime, globals, `Request a fresh URL Inspection for ${url.value}? This uses inspection quota.`);
|
|
726
|
+
if (confirmed._tag === 'Err')
|
|
727
|
+
return confirmed;
|
|
728
|
+
if (!confirmed.value)
|
|
729
|
+
return ok(undefined);
|
|
730
|
+
}
|
|
724
731
|
const resolved = await apiAndSite(runtime, globals);
|
|
725
732
|
if (resolved._tag === 'Err')
|
|
726
733
|
return resolved;
|
|
734
|
+
if (fresh) {
|
|
735
|
+
const response = await withSpinner(runtime, 'Requesting URL Inspection', () => resolved.value.api.client.search.refreshUrlInspection({
|
|
736
|
+
params: { siteId: resolved.value.siteId },
|
|
737
|
+
body: { url: url.value },
|
|
738
|
+
}, { signal: runtime.requestSignal }));
|
|
739
|
+
if (response._tag === 'Err')
|
|
740
|
+
return fromSdkFailure(response.error);
|
|
741
|
+
if (response.value.data.errors.length || response.value.data.skipped.length || response.value.data.results.length !== 1) {
|
|
742
|
+
const incomplete = fail(EXIT_CODE.conflict, 'Fresh URL Inspection did not complete. Read the errors or skipped URLs in the response.');
|
|
743
|
+
if (globals.json)
|
|
744
|
+
return { _tag: 'Err', error: { ...incomplete.error, protocolResponse: response.value } };
|
|
745
|
+
writeOutput(runtime, renderUrlInspectionRefresh(response.value.data));
|
|
746
|
+
return incomplete;
|
|
747
|
+
}
|
|
748
|
+
return present(runtime, globals, response, value => renderUrlInspectionRefresh(value.data));
|
|
749
|
+
}
|
|
727
750
|
const response = await withSpinner(runtime, 'Loading URL Inspection', () => resolved.value.api.client.search.readUrlInspection({
|
|
728
751
|
params: { siteId: resolved.value.siteId },
|
|
729
752
|
query: { url: url.value },
|
|
@@ -739,6 +762,40 @@ async function researchOverview(runtime, globals) {
|
|
|
739
762
|
}, { signal: runtime.requestSignal }));
|
|
740
763
|
return present(runtime, globals, response, value => renderResearchOverview(value.data));
|
|
741
764
|
}
|
|
765
|
+
async function competitorsList(runtime, globals) {
|
|
766
|
+
const resolved = await apiAndSite(runtime, globals);
|
|
767
|
+
if (resolved._tag === 'Err')
|
|
768
|
+
return resolved;
|
|
769
|
+
const response = await withSpinner(runtime, 'Loading competitors', () => resolved.value.api.client.research.listCompetitors({
|
|
770
|
+
params: { siteId: resolved.value.siteId },
|
|
771
|
+
}, { signal: runtime.requestSignal }));
|
|
772
|
+
return present(runtime, globals, response, value => value.data.competitors.map(competitor => `${competitor.id}\t${competitor.domain}`).join('\n') || '0 tracked competitors');
|
|
773
|
+
}
|
|
774
|
+
async function competitorWrite(runtime, globals, action) {
|
|
775
|
+
const confirmed = await confirmMutation(runtime, globals, action._tag === 'Add'
|
|
776
|
+
? `Track ${action.domain}? This uses the research limit.`
|
|
777
|
+
: `Remove competitor ${action.competitorId}? This cannot be undone.`);
|
|
778
|
+
if (confirmed._tag === 'Err')
|
|
779
|
+
return confirmed;
|
|
780
|
+
if (!confirmed.value) {
|
|
781
|
+
writeDiagnostic(runtime, 'Mutation cancelled.');
|
|
782
|
+
return ok(undefined);
|
|
783
|
+
}
|
|
784
|
+
const resolved = await apiAndSite(runtime, globals);
|
|
785
|
+
if (resolved._tag === 'Err')
|
|
786
|
+
return resolved;
|
|
787
|
+
if (action._tag === 'Add') {
|
|
788
|
+
const response = await withSpinner(runtime, 'Adding competitor', () => resolved.value.api.client.research.addCompetitor({
|
|
789
|
+
params: { siteId: resolved.value.siteId },
|
|
790
|
+
body: { domain: action.domain },
|
|
791
|
+
}, { signal: runtime.requestSignal }));
|
|
792
|
+
return present(runtime, globals, response, value => `${value.data.competitor.id}\t${value.data.competitor.domain}\tAnalysis queued`);
|
|
793
|
+
}
|
|
794
|
+
const response = await withSpinner(runtime, 'Removing competitor', () => resolved.value.api.client.research.removeCompetitor({
|
|
795
|
+
params: { siteId: resolved.value.siteId, competitorId: action.competitorId },
|
|
796
|
+
}, { signal: runtime.requestSignal }));
|
|
797
|
+
return present(runtime, globals, response, value => `Removed competitor ${value.data.competitorId}`);
|
|
798
|
+
}
|
|
742
799
|
/**
|
|
743
800
|
* DataForSEO refuses these characters, so the research provider removes them
|
|
744
801
|
* before it counts the words of a seed. Mirrored from
|
|
@@ -1810,6 +1867,25 @@ export function createRootCommand(runtime, globals, execution) {
|
|
|
1810
1867
|
const research = defineCommand({
|
|
1811
1868
|
meta: { name: 'research', description: 'Read stored and live search research' },
|
|
1812
1869
|
subCommands: {
|
|
1870
|
+
'competitors': defineCommand({
|
|
1871
|
+
meta: { name: 'competitors', description: 'Manage tracked competitors for the Site' },
|
|
1872
|
+
subCommands: {
|
|
1873
|
+
list: defineCommand({
|
|
1874
|
+
meta: { name: 'list', description: 'List tracked competitor domains and ids' },
|
|
1875
|
+
run: ({ args }) => capture(execution, () => competitorsList(runtime, globals), args._, 0)(),
|
|
1876
|
+
}),
|
|
1877
|
+
add: defineCommand({
|
|
1878
|
+
meta: { name: 'add', description: 'Track one competitor and queue its analysis' },
|
|
1879
|
+
args: { domain: positional('domain', 'Public competitor domain') },
|
|
1880
|
+
run: ({ args }) => capture(execution, () => competitorWrite(runtime, globals, { _tag: 'Add', domain: args.domain }), args._, 1)(),
|
|
1881
|
+
}),
|
|
1882
|
+
remove: defineCommand({
|
|
1883
|
+
meta: { name: 'remove', description: 'Remove one competitor using its listed id' },
|
|
1884
|
+
args: { competitorId: positional('competitorId', 'Competitor id from research competitors list') },
|
|
1885
|
+
run: ({ args }) => capture(execution, () => competitorWrite(runtime, globals, { _tag: 'Remove', competitorId: args.competitorId }), args._, 1)(),
|
|
1886
|
+
}),
|
|
1887
|
+
},
|
|
1888
|
+
}),
|
|
1813
1889
|
'overview': defineCommand({
|
|
1814
1890
|
meta: { name: 'overview', description: 'Read stored Site metrics and competitor research' },
|
|
1815
1891
|
run: ({ args }) => capture(execution, () => researchOverview(runtime, globals), args._, 0)(),
|
|
@@ -2016,8 +2092,11 @@ export function createRootCommand(runtime, globals, execution) {
|
|
|
2016
2092
|
}),
|
|
2017
2093
|
'inspect': defineCommand({
|
|
2018
2094
|
meta: { name: 'inspect', description: 'Read the latest URL Inspection result for one URL' },
|
|
2019
|
-
args: {
|
|
2020
|
-
|
|
2095
|
+
args: {
|
|
2096
|
+
url: positional('url', 'Page URL or path'),
|
|
2097
|
+
fresh: { type: 'boolean', description: 'Request Google URL Inspection using inspection quota', default: false },
|
|
2098
|
+
},
|
|
2099
|
+
run: ({ args }) => capture(execution, () => searchInspect(runtime, globals, args.url, args.fresh), args._, 1)(),
|
|
2021
2100
|
}),
|
|
2022
2101
|
},
|
|
2023
2102
|
});
|
package/dist/pull.js
CHANGED
|
@@ -319,22 +319,25 @@ export async function runPull(runtime, globals, args, resolve, catalogue = PULL_
|
|
|
319
319
|
});
|
|
320
320
|
const outcome = pullLine(candidate.name, result);
|
|
321
321
|
writeProtocolResponse(runtime, outcome.line);
|
|
322
|
-
|
|
323
|
-
&& candidate.name.startsWith('analytics ')
|
|
322
|
+
const analyticsUnconnected = candidate.name.startsWith('analytics ')
|
|
324
323
|
&& result._tag === 'Err'
|
|
325
324
|
&& result.error._tag === 'ApiFailure'
|
|
326
325
|
&& result.error.code === 'conflict'
|
|
327
|
-
&& result.error.details?.action === 'connect_web_analytics'
|
|
326
|
+
&& result.error.details?.action === 'connect_web_analytics';
|
|
327
|
+
if (!explainedAnalyticsConnection && analyticsUnconnected) {
|
|
328
328
|
explainedAnalyticsConnection = true;
|
|
329
329
|
writeDiagnostic(runtime, 'Web Analytics is not connected. Connect a property to read these views. For a full SEO pull, use --exclude analytics.');
|
|
330
330
|
}
|
|
331
|
-
|
|
331
|
+
// Default pulls include optional integrations. An explicit include asks
|
|
332
|
+
// for those reads, so their missing connection still fails that request.
|
|
333
|
+
const optionalUnavailable = analyticsUnconnected && !args.include;
|
|
334
|
+
exitCodes.push(optionalUnavailable ? EXIT_CODE.success : outcome.exitCode);
|
|
332
335
|
completed++;
|
|
333
336
|
// An expired or rejected credential fails every later read the same way.
|
|
334
337
|
// Stopping there saves the requests and keeps the reason on one line.
|
|
335
338
|
if (outcome.exitCode === EXIT_CODE.authentication || outcome.exitCode === EXIT_CODE.interrupted)
|
|
336
339
|
stopped = true;
|
|
337
|
-
writeDiagnostic(runtime, `[${completed}/${operations.length}] ${candidate.name} ${outcome.failure ? `failed: ${outcome.failure.code}` : 'ok'}`);
|
|
340
|
+
writeDiagnostic(runtime, `[${completed}/${operations.length}] ${candidate.name} ${optionalUnavailable ? 'unavailable' : outcome.failure ? `failed: ${outcome.failure.code}` : 'ok'}`);
|
|
338
341
|
if (outcome.failure?.code === 'request_timeout' && timeoutMs < 300_000) {
|
|
339
342
|
const retryTimeout = Math.min(timeoutMs * 2, 300_000);
|
|
340
343
|
// Retry only this read. Repeating a full paid pull can spend twice.
|
package/dist/render.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { ActionDismiss, ActionList, ActionResolve, ActionShow } from '@nuxt
|
|
|
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
6
|
import type { BacklinkAnchorsData, BacklinksHistory, BacklinksSummaryData, MentionsData, RecoverableBacklinksData, ReferringDomainsData } from '@nuxtseo/protocol/v1/backlinks';
|
|
7
|
-
import type { IndexCohorts, IndexingDiagnosticsData, IndexingHistoryData, SearchAnalyticsData, SearchStatusData, UrlInspectionData } from '@nuxtseo/protocol/v1/gsc';
|
|
7
|
+
import type { IndexCohorts, IndexingDiagnosticsData, IndexingHistoryData, SearchAnalyticsData, SearchStatusData, UrlInspectionData, UrlInspectionRefreshData } 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';
|
|
10
10
|
import type { DomainAvailability, DomainTraffic, KeywordResearchData, RankingsResearchData, SerpResearchData, StoredResearchOverview } from '@nuxtseo/protocol/v1/research';
|
|
@@ -21,6 +21,7 @@ export declare function renderPerformance(data: SitePerformanceOverview): string
|
|
|
21
21
|
export declare function renderSearchStatus(data: SearchStatusData): string;
|
|
22
22
|
export declare function renderSearchAnalytics(data: SearchAnalyticsData): string;
|
|
23
23
|
export declare function renderIndexingHistory(data: IndexingHistoryData): string;
|
|
24
|
+
export declare function renderUrlInspectionRefresh(data: UrlInspectionRefreshData): string;
|
|
24
25
|
export declare function renderUrlInspection(data: UrlInspectionData): string;
|
|
25
26
|
export declare function renderAuditChanges(data: AuditChanges): string;
|
|
26
27
|
export declare function renderLinkStructure(data: AuditLinkStructure): string;
|
package/dist/render.js
CHANGED
|
@@ -232,6 +232,25 @@ export function renderIndexingHistory(data) {
|
|
|
232
232
|
}),
|
|
233
233
|
].join('\n');
|
|
234
234
|
}
|
|
235
|
+
export function renderUrlInspectionRefresh(data) {
|
|
236
|
+
return [
|
|
237
|
+
`URL Inspection request completed at ${data.checkedAt}.`,
|
|
238
|
+
...data.results.map(result => fields([
|
|
239
|
+
['URL', result.url],
|
|
240
|
+
['Verdict', result.verdict],
|
|
241
|
+
['Coverage', result.coverageState],
|
|
242
|
+
['Indexing', result.indexingState],
|
|
243
|
+
['robots.txt', result.robotsTxtState],
|
|
244
|
+
['Page fetch', result.pageFetchState],
|
|
245
|
+
['User canonical', result.userCanonical],
|
|
246
|
+
['Google canonical', result.googleCanonical],
|
|
247
|
+
['Last crawl', result.lastCrawlTime],
|
|
248
|
+
])),
|
|
249
|
+
...data.errors.map(result => `Inspection failed for ${result.url}: ${result.error}`),
|
|
250
|
+
...data.skipped.map(result => `Inspection skipped for ${result.url}: ${result.reason}`),
|
|
251
|
+
`Inspection quota remaining: ${data.rateLimit.remaining} of ${data.rateLimit.limit}.`,
|
|
252
|
+
].join('\n');
|
|
253
|
+
}
|
|
235
254
|
export function renderUrlInspection(data) {
|
|
236
255
|
if (data._tag === 'no-inspection-data')
|
|
237
256
|
return `No retained URL Inspection for ${data.requestedUrl}.`;
|
|
@@ -465,9 +484,19 @@ export function renderResearchOverview(data) {
|
|
|
465
484
|
return [subject, `Competitors: ${data.summary.competitorCount}`, competitors].join('\n');
|
|
466
485
|
}
|
|
467
486
|
export function renderKeywordResearch(data) {
|
|
487
|
+
const measured = data.topicMetrics.filter(row => row._tag === 'measured').length;
|
|
488
|
+
const topics = data.topicMetrics.length
|
|
489
|
+
? [
|
|
490
|
+
`Requested topics (${measured}/${data.topicMetrics.length} measured)`,
|
|
491
|
+
...data.topicMetrics.map(row => row._tag === 'measured'
|
|
492
|
+
? `${row.metrics.volume ?? 'unknown'} volume ${row.metrics.difficulty ?? 'unknown'} difficulty ${row.metrics.intent ?? 'unknown intent'}\n ${row.topic}`
|
|
493
|
+
: `No data returned\n ${row.topic}`),
|
|
494
|
+
]
|
|
495
|
+
: [];
|
|
468
496
|
if (data.keywords.length === 0)
|
|
469
|
-
return data.message ?? `No keyword ideas found for ${data.topic}
|
|
497
|
+
return [...topics, data.message ?? `No keyword ideas found for ${data.topic}.`, ...(data.tip ? [`Tip: ${data.tip}`] : [])].join('\n');
|
|
470
498
|
return [
|
|
499
|
+
...topics,
|
|
471
500
|
`Keyword ideas for ${data.topic} (${data.keywords.length} of ${data.totalFound})${data.evidence._tag === 'cache' ? ' cached' : ''}`,
|
|
472
501
|
...data.keywords.map(row => `${row.volume ?? 'unknown'} volume ${row.difficulty ?? 'unknown'} difficulty ${row.intent ?? 'unknown intent'}\n ${row.keyword}`),
|
|
473
502
|
...(data.tip ? [`Tip: ${data.tip}`] : []),
|
|
@@ -604,7 +633,7 @@ export function renderFieldVitals(data) {
|
|
|
604
633
|
['As of', summary.asOf],
|
|
605
634
|
...['lcp', 'inp', 'cls'].map(metric => [
|
|
606
635
|
metric.toUpperCase(),
|
|
607
|
-
`${vitalValue(metric, summary[metric].p75)} ${summary[metric].status ?? 'unrated'} delta ${summary[metric].deltaOverPeriod ?? 'none'}`,
|
|
636
|
+
`${vitalValue(metric, summary[metric].p75)} ${summary[metric].status ?? 'unrated'} delta ${summary[metric].deltaOverPeriod ?? 'none'} As of ${summary[metric].asOf ?? 'unknown'}`,
|
|
608
637
|
]),
|
|
609
638
|
]),
|
|
610
639
|
].join('\n');
|
|
@@ -896,7 +925,7 @@ export function renderScanDetail(data) {
|
|
|
896
925
|
if (data.observedTrace?._tag === 'available') {
|
|
897
926
|
const trace = data.observedTrace;
|
|
898
927
|
blocks.push([
|
|
899
|
-
`Observed trace LCP ${trace.observedLcpMs}ms throttling ${trace.throttlingMethod}`,
|
|
928
|
+
`Observed trace LCP ${trace.observedLcpMs}ms throttling ${trace.throttlingMethod ?? 'not reported'}`,
|
|
900
929
|
...trace.phases.map(phase => ` ${phase.id} ${phase.durationMs}ms`),
|
|
901
930
|
'Trace phases describe observed LCP. Reported LCP can be simulated.',
|
|
902
931
|
].join('\n'));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuxtseo/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.5",
|
|
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.5.
|
|
37
|
-
"@nuxtseo/sdk": "^0.5.
|
|
36
|
+
"@nuxtseo/protocol": "^0.5.5",
|
|
37
|
+
"@nuxtseo/sdk": "^0.5.5",
|
|
38
38
|
"citty": "^0.2.2",
|
|
39
39
|
"pathe": "^2.0.3"
|
|
40
40
|
},
|
|
@@ -64,6 +64,7 @@ Read the matching file before you act on its subject:
|
|
|
64
64
|
| `page scan` | `page:write` |
|
|
65
65
|
| `actions resolve`, `actions dismiss` | `actions:write` |
|
|
66
66
|
| `annotations` writes | `timeline:write` |
|
|
67
|
+
| `research competitors add`, `research competitors remove` | `research:write` |
|
|
67
68
|
| `sitemaps submit`, `sitemaps delete` | `sites:write` |
|
|
68
69
|
|
|
69
70
|
If a write scope is missing, do the reads, then hand the user the exact
|
|
@@ -277,7 +278,10 @@ Do not compare this population with a summary or cohort from another capture.
|
|
|
277
278
|
- `status` is the verdict, `performance` the lab score, `vitals` what real
|
|
278
279
|
users measured. Never substitute one for another.
|
|
279
280
|
- `page inspect` reads the Nuxt SEO observation store. `search inspect` reads
|
|
280
|
-
Google
|
|
281
|
+
the latest retained Google URL Inspection for the same URL.
|
|
282
|
+
`search inspect <url> --fresh --yes` requests Google URL Inspection through
|
|
283
|
+
the Site connection and uses inspection quota. Errors or skipped URLs mean
|
|
284
|
+
the fresh inspection did not complete. Never present them as fresh verdicts.
|
|
281
285
|
- `search status` reads the stored connection and never waits for Google.
|
|
282
286
|
`search analytics`, `search indexing`, `search index-history`, and
|
|
283
287
|
`search inspect` read retained Search Console evidence.
|
|
@@ -141,6 +141,7 @@ lines.
|
|
|
141
141
|
`scans show` can include `observedTrace` from the retained Lighthouse projection.
|
|
142
142
|
If its tag is `available`, its phases describe `observedLcpMs` from the browser trace.
|
|
143
143
|
`throttlingMethod` names how Lighthouse computed the reported metrics.
|
|
144
|
+
If it is `null`, the provider omitted the method. Do not infer a method from the timings.
|
|
144
145
|
With `simulate`, reported `lcp` is modeled. Observed trace phases do not partition that modeled value.
|
|
145
146
|
With `devtools` or `provided`, Lighthouse uses observed metrics.
|
|
146
147
|
Neither value is real-user field data.
|
|
@@ -194,7 +195,7 @@ The monitored Page then follows its own recurring schedule.
|
|
|
194
195
|
| `search analytics <view>` | Search Console rows, trend, detail, or analysis. Views: `pages`, `keywords`, `countries`, `devices`, `timeseries`, `page-detail`, `keyword-detail`, `analysis`. `--period 7d\|28d\|3m\|6m\|12m` (default `28d`). No other period value is accepted; `90d` exits `2`, and `3m` is the three-month window. `--limit 1..100` (default 25), `--page` (default 1), `--search`, `--sort clicks\|impressions\|ctr\|position` (default `clicks`), `--sort-dir asc\|desc` (default `desc`), `--filter top-level\|new\|lost\|improving\|declining`, `--min-clicks`, `--max-clicks`, `--min-impressions`, `--max-impressions`, `--min-position 1..100`, `--max-position 1..100`, `--max-ctr 0..1`. The `page-detail` view needs `--page-url`, `keyword-detail` needs `--keyword`, and `analysis` needs `--preset`. The brand presets also need `--brand-terms` |
|
|
195
196
|
| `search indexing <summary\|urls>` | Retained URL Inspection coverage. `summary` returns indexed counts. `urls` supports `--issue`, `--status`, `--limit 1..500` (default 50), `--offset` |
|
|
196
197
|
| `search index-history` | Retained indexing changes and candidate causes. `--url`, `--field`, `--days 1..730`. Dates a regression against a release |
|
|
197
|
-
| `search inspect <url>` | Google
|
|
198
|
+
| `search inspect <url>` | Latest retained Google URL Inspection for one URL. Takes an absolute URL or a site-relative path. `--fresh --yes` requests Google URL Inspection through the Site connection and uses inspection quota. Errors or skipped URLs produce a nonzero exit code |
|
|
198
199
|
| `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` |
|
|
199
200
|
| `sitemaps list` | Search Console sitemap snapshot: errors, warnings, URL counts, sync status |
|
|
200
201
|
| `sitemaps urls` | Sitemap URL membership. `--generation-id`, `--feedpath`, `--cursor`, `--limit 1..1000` |
|
|
@@ -251,12 +252,16 @@ A missing query row is never evidence of no demand.
|
|
|
251
252
|
## Research, backlinks, and mentions
|
|
252
253
|
|
|
253
254
|
Every command here can start live research, except `research overview`,
|
|
255
|
+
`research competitors list`, `research competitors remove`,
|
|
254
256
|
`backlinks recoverable` and `mentions list`, which read retained rows. Read the
|
|
255
257
|
research boundary in SKILL.md first.
|
|
256
258
|
|
|
257
259
|
| Command | What it returns, and its flags |
|
|
258
260
|
| --- | --- |
|
|
259
261
|
| `research overview` | Stored Site and competitor research: metrics, history, gaps, quick wins. Takes no flags. Returns `data.competitors` and `data.summary` |
|
|
262
|
+
| `research competitors list` | Tracked competitor domains and ids for this Site. Suggested competitors stay excluded. Reads start no provider work. |
|
|
263
|
+
| `research competitors add <domain>` | Mutation, requires `research:write` and `--yes`. Accepts a public domain or URL. Queues analysis within existing Site and Team limits. Returns `data.competitor.id` and `data.status: queued`. |
|
|
264
|
+
| `research competitors remove <competitor-id>` | Mutation, requires `research:write` and `--yes`. Use an id from the list. Removes that Site's competitor without provider work. |
|
|
260
265
|
| `research keywords <topic>` | Live keyword ideas, with volume, difficulty, intent, and cost data. The seed must be 1 to 3 words. The server refuses a longer seed. The refusal arrives as exit `0` with an empty `data.keywords`, so read `data.message` first. Comma-separate several short seeds instead. Special characters are stripped before the lookup. If a seed is under 2 characters after stripping, the server refuses it the same way. The server applies default filters and echoes them in `data.filters`: `--min-volume` (default 10), `--max-volume` (default 10000), `--min-difficulty 0..100` (default 0), `--max-difficulty 0..100` (default 60). So a default run hides keywords over difficulty 60 or under 10 searches. Also `--limit 1..100` (default 20), `--intent`, `--location-code` (default 2840), `--no-related` to drop related keywords, and `--verbose` for full evidence. If `--max-volume` is under `--min-volume`, the run exits `2`. The same holds for `--max-difficulty` under `--min-difficulty` |
|
|
261
266
|
| `research serp <keyword>` | A live SERP snapshot: results, features, fetch time. `--depth 1..20` (default 10) sets how many organic results return. `--location-code` (default 2840). The keyword must be 2 to 200 characters. `data.cached` reports cache use |
|
|
262
267
|
| `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 |
|
|
@@ -269,6 +274,13 @@ research boundary in SKILL.md first.
|
|
|
269
274
|
| `backlinks recoverable` | Stored recoverable backlinks. `--limit 1..200`, `--offset`. Reads retained rows |
|
|
270
275
|
| `mentions list` | Stored mentions. `--limit 1..200` (default 100), `--include-filtered` to keep the Mentions AI triage marked a false positive. Reads retained rows |
|
|
271
276
|
|
|
277
|
+
Keyword research returns `data.topicMetrics` for every accepted comma-separated topic.
|
|
278
|
+
Read it separately from filtered `data.keywords`, which contains keyword ideas.
|
|
279
|
+
A `measured` row preserves exact metrics, including zero volume and values outside idea filters.
|
|
280
|
+
A `no-data` row means the provider returned no exact metrics. It does not mean zero volume.
|
|
281
|
+
If any topic becomes shorter than two characters after sanitization, the server refuses the whole request.
|
|
282
|
+
Cached reads preserve all topic metrics without another provider call.
|
|
283
|
+
|
|
272
284
|
## Crawl audit
|
|
273
285
|
|
|
274
286
|
| Command | What it returns, and its flags |
|
|
@@ -63,5 +63,6 @@ Keep these reads separate:
|
|
|
63
63
|
| What is the indexed count for retained URLs? | `search indexing summary` | `totalUrls`, `indexed`, `oldestCheckAt`, `asOf` |
|
|
64
64
|
| Which retained URLs are indexed or not indexed? | `search indexing urls` | `total`, then the complete `urls` stream |
|
|
65
65
|
| Which URLs appeared in the current period? | `search analytics pages` | Rows with `impressions > 0`, then each `url` |
|
|
66
|
-
| What
|
|
66
|
+
| What did Google say at the last inspection? | `search inspect <url>` | The retained verdict and inspection date |
|
|
67
|
+
| What does Google say about one URL now? | `search inspect <url>` | Add `--fresh --yes`. Report fresh results, quota, and explicit errors or skipped URLs |
|
|
67
68
|
| Which URLs did the Site submit? | `sitemaps list`, then `sitemaps urls` | Sitemap totals and membership |
|