@nuxtseo/cli 0.5.3 → 0.5.4
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 +3 -1
- package/dist/commands.js +30 -4
- package/dist/pull.js +8 -5
- package/dist/render.d.ts +2 -1
- package/dist/render.js +21 -2
- package/package.json +3 -3
- package/skills/nuxtseo-cli/SKILL.md +4 -1
- package/skills/nuxtseo-cli/references/commands.md +2 -1
- package/skills/nuxtseo-cli/references/indexing.md +2 -1
package/README.md
CHANGED
|
@@ -256,7 +256,9 @@ reads retained rows and spends nothing.
|
|
|
256
256
|
`search status` reads stored connection state. It never waits for Google.
|
|
257
257
|
`search indexing summary` reads retained URL Inspection coverage. `asOf` and `oldestCheckAt` bound the age of its verdicts.
|
|
258
258
|
`search index-history` dates an indexing change against known releases.
|
|
259
|
-
`search inspect <url>` reads Google
|
|
259
|
+
`search inspect <url>` reads the latest retained Google URL Inspection for one URL.
|
|
260
|
+
`search inspect <url> --fresh --yes` requests Google URL Inspection through the Site connection and uses inspection quota.
|
|
261
|
+
Fresh inspection reports errors and skipped URLs with a nonzero exit code. If the request needs more time, add `--timeout-ms 60000`.
|
|
260
262
|
`page inspect <url>` reads the Nuxt SEO observation store for the same URL.
|
|
261
263
|
`research keywords`, `research serp`, and `research rankings` can use the Team
|
|
262
264
|
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 },
|
|
@@ -2016,8 +2039,11 @@ export function createRootCommand(runtime, globals, execution) {
|
|
|
2016
2039
|
}),
|
|
2017
2040
|
'inspect': defineCommand({
|
|
2018
2041
|
meta: { name: 'inspect', description: 'Read the latest URL Inspection result for one URL' },
|
|
2019
|
-
args: {
|
|
2020
|
-
|
|
2042
|
+
args: {
|
|
2043
|
+
url: positional('url', 'Page URL or path'),
|
|
2044
|
+
fresh: { type: 'boolean', description: 'Request Google URL Inspection using inspection quota', default: false },
|
|
2045
|
+
},
|
|
2046
|
+
run: ({ args }) => capture(execution, () => searchInspect(runtime, globals, args.url, args.fresh), args._, 1)(),
|
|
2021
2047
|
}),
|
|
2022
2048
|
},
|
|
2023
2049
|
});
|
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}.`;
|
|
@@ -604,7 +623,7 @@ export function renderFieldVitals(data) {
|
|
|
604
623
|
['As of', summary.asOf],
|
|
605
624
|
...['lcp', 'inp', 'cls'].map(metric => [
|
|
606
625
|
metric.toUpperCase(),
|
|
607
|
-
`${vitalValue(metric, summary[metric].p75)} ${summary[metric].status ?? 'unrated'} delta ${summary[metric].deltaOverPeriod ?? 'none'}`,
|
|
626
|
+
`${vitalValue(metric, summary[metric].p75)} ${summary[metric].status ?? 'unrated'} delta ${summary[metric].deltaOverPeriod ?? 'none'} As of ${summary[metric].asOf ?? 'unknown'}`,
|
|
608
627
|
]),
|
|
609
628
|
]),
|
|
610
629
|
].join('\n');
|
|
@@ -896,7 +915,7 @@ export function renderScanDetail(data) {
|
|
|
896
915
|
if (data.observedTrace?._tag === 'available') {
|
|
897
916
|
const trace = data.observedTrace;
|
|
898
917
|
blocks.push([
|
|
899
|
-
`Observed trace LCP ${trace.observedLcpMs}ms throttling ${trace.throttlingMethod}`,
|
|
918
|
+
`Observed trace LCP ${trace.observedLcpMs}ms throttling ${trace.throttlingMethod ?? 'not reported'}`,
|
|
900
919
|
...trace.phases.map(phase => ` ${phase.id} ${phase.durationMs}ms`),
|
|
901
920
|
'Trace phases describe observed LCP. Reported LCP can be simulated.',
|
|
902
921
|
].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.4",
|
|
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.4",
|
|
37
|
+
"@nuxtseo/sdk": "^0.5.4",
|
|
38
38
|
"citty": "^0.2.2",
|
|
39
39
|
"pathe": "^2.0.3"
|
|
40
40
|
},
|
|
@@ -277,7 +277,10 @@ Do not compare this population with a summary or cohort from another capture.
|
|
|
277
277
|
- `status` is the verdict, `performance` the lab score, `vitals` what real
|
|
278
278
|
users measured. Never substitute one for another.
|
|
279
279
|
- `page inspect` reads the Nuxt SEO observation store. `search inspect` reads
|
|
280
|
-
Google
|
|
280
|
+
the latest retained Google URL Inspection for the same URL.
|
|
281
|
+
`search inspect <url> --fresh --yes` requests Google URL Inspection through
|
|
282
|
+
the Site connection and uses inspection quota. Errors or skipped URLs mean
|
|
283
|
+
the fresh inspection did not complete. Never present them as fresh verdicts.
|
|
281
284
|
- `search status` reads the stored connection and never waits for Google.
|
|
282
285
|
`search analytics`, `search indexing`, `search index-history`, and
|
|
283
286
|
`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` |
|
|
@@ -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 |
|