@gscdump/cli 5.2.0 → 5.4.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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Google Search Console CLI and MCP server.
4
4
 
5
- Choose [Local or Hosted mode](../../docs/gscdump-cli/guides/1.start/2.choose-access.md) before login. Local mode calls Google with your own credentials: a service account or an OAuth client. Hosted mode opens gscdump.com, saves a CLI session, and reads your hosted record. It never calls Google.
5
+ Choose [Local or Hosted mode](../../docs/gscdump-cli/guides/1.start/2.choose-access.md) before login. Local mode calls Google with your own credentials: a service account or an OAuth client. Hosted mode opens gscdump.com, saves a CLI session, and reads your hosted record. Explicit `indexing inspect --yes` requests spend URL Inspections through the platform.
6
6
 
7
7
  ```bash
8
8
  npm install -g @gscdump/cli
@@ -2,6 +2,7 @@ import { toCSV } from "./utils.mjs";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { z } from "zod";
5
+ import { BING_THROTTLE_IP_ERROR_CODE } from "gscdump/bing";
5
6
  const BING_DATASETS = [
6
7
  "traffic",
7
8
  "pages",
@@ -37,7 +38,7 @@ function parseBingDumpOptions(args) {
37
38
  function unwrapBing(result) {
38
39
  if (result.ok) return result.value;
39
40
  const error = result.error;
40
- const help = error._tag === "AuthenticationRequired" ? " Run `gscdump bing login` again." : error._tag === "Throttled" && error.retryAfterMs !== void 0 ? ` Retry after ${Math.ceil(error.retryAfterMs / 1e3)} seconds.` : "";
41
+ const help = error._tag === "AuthenticationRequired" ? " Run `gscdump bing login` again." : error._tag === "Throttled" && error.errorCode === BING_THROTTLE_IP_ERROR_CODE ? " Bing refused requests from this IP address. Waiting does not lift the block. Retry from another network." : error._tag === "Throttled" && error.retryAfterMs !== void 0 ? ` Retry after ${Math.ceil(error.retryAfterMs / 1e3)} seconds.` : "";
41
42
  throw new Error(`Bing request failed: ${error._tag}.${help}`);
42
43
  }
43
44
  function siteIdentity(value) {
@@ -32,7 +32,7 @@ const inspectCommandMeta = {
32
32
  };
33
33
  const indexingCommandMeta = {
34
34
  name: "indexing",
35
- description: "Notify Google about URL changes (Indexing API); list hosted URL Inspection results"
35
+ description: "Notify Google about URL changes (Indexing API); read and refresh hosted URL Inspection results"
36
36
  };
37
37
  const entitiesCommandMeta = {
38
38
  name: "entities",
@@ -40,6 +40,7 @@ const HOSTED_COMMANDS = [
40
40
  "sitemaps lastmod",
41
41
  "sitemaps export",
42
42
  "indexing urls",
43
+ "indexing inspect",
43
44
  "indexing summary",
44
45
  "indexing watch",
45
46
  "bing login",
@@ -0,0 +1,73 @@
1
+ import { commandLineError, stopError } from "../stop.mjs";
2
+ import { OUTPUT_ARGS, applyOutputMode, logger, readUrlList } from "../utils.mjs";
3
+ import { HOSTED_ARGS, resolveHostedSite } from "../hosted-site.mjs";
4
+ import { defineCommand } from "citty";
5
+ import { isGscdumpV1Error } from "@gscdump/sdk/v1";
6
+ const indexingInspectCommand = defineCommand({
7
+ meta: {
8
+ name: "inspect",
9
+ description: "Refresh Google URL Inspection results through gscdump.com (hosted; spends URL Inspections)"
10
+ },
11
+ args: {
12
+ ...HOSTED_ARGS,
13
+ ...OUTPUT_ARGS,
14
+ urls: {
15
+ type: "positional",
16
+ required: false,
17
+ description: "Up to 10 URLs on the Site host"
18
+ },
19
+ file: {
20
+ type: "string",
21
+ alias: "f",
22
+ description: "File with URLs (one per line), or use stdin"
23
+ },
24
+ yes: {
25
+ type: "boolean",
26
+ alias: "y",
27
+ default: false,
28
+ description: "Allow this request to spend URL Inspections"
29
+ }
30
+ },
31
+ async run({ args }) {
32
+ const { json } = applyOutputMode(args);
33
+ if (!args.yes) throw commandLineError("This request spends URL Inspections. Pass --yes to proceed.");
34
+ const urls = [...new Set(await readUrlList({
35
+ file: args.file,
36
+ positionals: args._
37
+ }))];
38
+ if (urls.length === 0 || urls.length > 10) throw commandLineError("Pass 1 to 10 URLs. This command sends one request and does not split batches.");
39
+ for (const value of urls) {
40
+ const url = URL.parse(value);
41
+ if (!url || !["https:", "http:"].includes(url.protocol) || url.username || url.password || url.hash) throw commandLineError(`Invalid URL: ${value}. Pass an HTTP URL without credentials or a fragment.`);
42
+ }
43
+ const { client, site } = await resolveHostedSite({
44
+ ...args,
45
+ _: []
46
+ }, {
47
+ name: "indexing inspect",
48
+ localAlternative: "run `gscdump inspect --site <site> <url...>`"
49
+ });
50
+ const { data } = await client.inspectSiteUrls({
51
+ params: { siteId: site.siteId },
52
+ body: { urls }
53
+ }).catch((error) => {
54
+ if (!isGscdumpV1Error(error) || error.code !== "rate_limited") throw error;
55
+ const seconds = error.details.retryAfterSeconds;
56
+ const wait = typeof seconds === "number" && Number.isFinite(seconds) && seconds > 0 ? ` Wait ${Math.ceil(seconds)} seconds before trying again.` : " Wait for the quota reset before trying again.";
57
+ throw stopError({
58
+ code: "QUOTA_USED_UP",
59
+ message: `${error.message}${wait}`,
60
+ nextCommand: null
61
+ });
62
+ });
63
+ if (json) console.log(JSON.stringify(data, null, 2));
64
+ else {
65
+ for (const result of data.results) console.log(`${result.url}: ${result.coverageState ?? result.verdict ?? "unknown"}`);
66
+ for (const error of data.errors) console.log(`${error.url}: ${error.error}`);
67
+ for (const skipped of data.skipped) console.log(`${skipped.url}: skipped (${skipped.reason})`);
68
+ logger.info(`${data.rateLimit.remaining} of ${data.rateLimit.limit} daily URL Inspections remain.`);
69
+ }
70
+ if (data.errors.length || data.skipped.length) throw new Error("Some URLs were not inspected. Read the errors and skipped URLs before trying again.");
71
+ }
72
+ });
73
+ export { indexingInspectCommand };
@@ -3,6 +3,7 @@ import { indexingCommandMeta } from "../command-meta.mjs";
3
3
  import { OUTPUT_ARGS, applyOutputMode, logger, parseIntegerOption, readUrlList } from "../utils.mjs";
4
4
  import { createCommandContext } from "../context.mjs";
5
5
  import { loadSitemapUrls } from "../sitemap.mjs";
6
+ import { indexingInspectCommand } from "./indexing-inspect.mjs";
6
7
  import { indexingSummaryCommand } from "./indexing-summary.mjs";
7
8
  import { indexingUrlsCommand } from "./indexing-urls.mjs";
8
9
  import { indexingWatchCommand } from "./indexing-watch.mjs";
@@ -306,6 +307,7 @@ const indexingCommand = defineCommand({
306
307
  "quota": quotaCommand,
307
308
  "urls": indexingUrlsCommand,
308
309
  "summary": indexingSummaryCommand,
310
+ "inspect": indexingInspectCommand,
309
311
  "watch": indexingWatchCommand
310
312
  }
311
313
  });
package/dist/package.mjs CHANGED
@@ -1,2 +1,2 @@
1
- var version = "5.2.0";
1
+ var version = "5.4.0";
2
2
  export { version };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gscdump/cli",
3
3
  "type": "module",
4
- "version": "5.2.0",
4
+ "version": "5.4.0",
5
5
  "description": "CLI for Google Search Console and Bing with hosted or local authentication, data exports, and an MCP server",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",
@@ -44,16 +44,16 @@
44
44
  "dependencies": {
45
45
  "@clack/prompts": "^1.8.1",
46
46
  "@duckdb/node-api": "1.5.5-r.5",
47
- "@gscdump/analysis": "^5.2.0",
48
- "@gscdump/contracts": "^5.2.0",
49
- "@gscdump/engine": "^5.2.0",
50
- "@gscdump/engine-gsc-api": "^5.2.0",
51
- "@gscdump/sdk": "^5.2.0",
47
+ "@gscdump/analysis": "^5.3.0",
48
+ "@gscdump/contracts": "^5.3.0",
49
+ "@gscdump/engine": "^5.3.0",
50
+ "@gscdump/engine-gsc-api": "^5.3.0",
51
+ "@gscdump/sdk": "^5.3.1",
52
52
  "@modelcontextprotocol/sdk": "^1.30.1",
53
53
  "citty": "^0.2.2",
54
54
  "consola": "^3.4.2",
55
55
  "google-auth-library": "^11.1.0",
56
- "gscdump": "^5.2.0",
56
+ "gscdump": "^5.3.1",
57
57
  "ofetch": "^1.5.1",
58
58
  "open": "^11.0.4",
59
59
  "proper-lockfile": "^4.1.2",
@@ -38,7 +38,7 @@ gscdump has 2 access modes. Check `gscdump auth status --json` before queries. R
38
38
  | Mode | Credentials | What it reads |
39
39
  | --- | --- | --- |
40
40
  | `local` | The user's own Google credentials: a service account (recommended) or an OAuth client. Bing API key or OAuth | Calls Google and Bing directly. Keeps a local Store |
41
- | `hosted` | A browser-approved gscdump.com CLI session, or a gscdump user API key (`GSCDUMP_API_KEY`) | Reads the hosted record on gscdump.com. Never calls Google |
41
+ | `hosted` | A browser-approved gscdump.com CLI session, or a gscdump user API key (`GSCDUMP_API_KEY`) | Reads the hosted record on gscdump.com. Explicit `indexing inspect --yes` requests spend URL Inspections |
42
42
 
43
43
  In Local mode, `googleAuthenticated: true` means Google accepted the credentials. When it is false, `googleError` says why.
44
44
  In Hosted mode, `hostedSync` lists each hosted Site with `syncStatus` and `syncProgress`, and `commands` lists what Hosted mode can run.
@@ -72,9 +72,9 @@ gscdump query --site example.com -d page -f json
72
72
  ```
73
73
 
74
74
  Hosted mode can run only these commands: `sites`, `query`, `sitemaps current`, `sitemaps history`, `sitemaps membership`,
75
- `sitemaps lastmod`, `sitemaps export`, `indexing urls`, `indexing summary`, `indexing watch list|add|remove`,
75
+ `sitemaps lastmod`, `sitemaps export`, `indexing urls`, `indexing inspect --yes`, `indexing summary`, `indexing watch list|add|remove`,
76
76
  and the `bing` commands `login --site`, `sites`, `status`, `dump`, `inspect`, and `verify`.
77
- Every other command calls Google, so it needs Local mode: `sync`, `inspect`, `query --live`, `analyze --live`, `report --live`,
77
+ Direct Google commands need Local mode: `sync`, `inspect`, `query --live`, `analyze --live`, `report --live`,
78
78
  `sites add|get|delete|verify*`, `sitemaps list|get|submit|delete`, `indexing submit|remove|status|batch`, `entities`, and `mcp`.
79
79
  In Hosted mode these commands stop with this error: `This command calls Google, so it needs Local mode.`
80
80
  Tell the user. Do not switch modes for them.
@@ -247,7 +247,7 @@ A routing stop also has `siteUrl`. `STORE_RANGE_NOT_COVERED` adds `missingDates`
247
247
  | `LIVE_ONLY` | Only `--live` can answer the read |
248
248
  | `LOGIN_CANCELLED` | The user selected Cancel on the gscdump.com login page. Ask before you run `nextCommand` |
249
249
  | `LOCAL_MODE_REQUIRED` | The command calls Google, and Hosted mode is selected |
250
- | `HOSTED_MODE_REQUIRED` | The command reads the hosted record, and Local mode is selected |
250
+ | `HOSTED_MODE_REQUIRED` | The command needs Hosted mode, and Local mode is selected |
251
251
  | `HOSTED_CREDENTIALS_MISSING` | Hosted mode has no CLI session and no API key |
252
252
  | `HOSTED_CREDENTIALS_REJECTED` | gscdump.com rejected the CLI session or API key. For an API key, `nextCommand` is null: the user needs a new key from the app that issued it |
253
253
  | `NO_SITES` | The hosted record has no Sites. The message says where the user connects one |
@@ -276,6 +276,7 @@ A routing stop also has `siteUrl`. `STORE_RANGE_NOT_COVERED` adds `missingDates`
276
276
  | `gscdump inspect <url...>` | URL Inspection with Indexing Evidence, saved to the Store |
277
277
  | `gscdump sitemaps` | List, submit, delete, and probe sitemaps |
278
278
  | `gscdump indexing` | Indexing API notifications and quota; hosted URL Inspection results |
279
+ | `gscdump indexing inspect <url...> --yes` | Hosted: refresh up to 10 URLs through the platform, spending shared URL Inspections. Never retry automatically |
279
280
  | `gscdump indexing summary` | Hosted: the coverage ladder per day, with the time gscdump counted the verdicts |
280
281
  | `gscdump indexing watch` | Hosted: list, add, and remove Watched URLs and read their Checkpoints |
281
282
  | `gscdump dump` | Export Store tables, inspections, sitemaps, and Bing data as Parquet, CSV, JSON, NDJSON, SQLite, or DuckDB |
@@ -484,10 +485,10 @@ Read the 4 ladder states in `counts`, lowest rung first. Name each state with th
484
485
 
485
486
  | Key | Name | Meaning |
486
487
  | --- | --- | --- |
487
- | `unknown_to_google` | unknown | Google has no record of the URL |
488
- | `discovered_not_indexed` | discovered | Google queued the URL and has not crawled it |
489
- | `crawled_not_indexed` | crawled | Google crawled the URL and left it out of the index |
490
- | `indexed` | indexed | Google indexed the URL |
488
+ | `unknown_to_google` | unknown | URL Inspection reported the URL as unknown at its check time |
489
+ | `discovered_not_indexed` | discovered | URL Inspection reported discovery without a crawl at its check time |
490
+ | `crawled_not_indexed` | crawled | URL Inspection reported a crawl without indexing at its check time |
491
+ | `indexed` | indexed | URL Inspection reported the URL as indexed at its check time |
491
492
 
492
493
  The other keys in `counts` are exclusions, such as `noindex`, `not_found`, and `redirect`.
493
494
  `unrecognized` counts coverage text gscdump cannot map yet. `not_reported` counts results with no coverage text.
@@ -502,11 +503,15 @@ Rules for every answer:
502
503
  They are also not the Search Console Page indexing report. The two can differ a lot, and the Page indexing report can list many more URLs.
503
504
  - `olderThan7d` and `olderThan30d` count verdicts inspected more than 7 times 24 hours and 30 times 24 hours before `capturedAt`. They are not calendar days.
504
505
  - A verdict can be months older than its capture. Unchanged URLs are rechecked less often over time. Say when most verdicts are old.
506
+ - Unknown and discovered verdicts do not show Google's current crawl queue. They do not prove noindex or content rejection.
507
+ - An accepted indexing request does not confirm crawling or indexing. Compare its receipt date with the saved inspection date.
508
+ - If the saved inspection predates a request, it cannot show that request's outcome. A live indexability test also cannot confirm indexing.
505
509
  - A move between rungs shows in the trend. Compare two `counted` days and name the dates.
506
510
  - `capture.scope` says which URLs the counts cover. `sitemap_members` means every counted URL is in a live sitemap.
507
511
  `inspected_urls` means sitemap membership was not available, so the counts can include URLs outside every sitemap.
508
512
  - If `unknown_to_google` URLs are already in a sitemap, the sitemap is not the gap. Do not tell the owner to add them to it.
509
- Read the sitemap's last download with `gscdump sitemaps current --site SITE --json`. If Google downloaded it, say that Google read the sitemap and did not take the URLs.
513
+ Read the sitemap's last download with `gscdump sitemaps current --site SITE --json`. Compare that date with each saved inspection date.
514
+ A sitemap download confirms that Google read the feed. It does not confirm discovery, crawling, or indexing of every URL.
510
515
  Check one URL's membership with `gscdump sitemaps membership`.
511
516
  - Name no cause beyond what the evidence shows.
512
517
  - Cite the command behind each number.
@@ -590,3 +595,16 @@ Include that JSON in your final response. Tool output alone is not a final answe
590
595
  Include every returned row. Do not refer the user to results "above".
591
596
 
592
597
  For a deletion explanation, read metadata only if needed. Explain the scope and ask for consent, then stop.
598
+
599
+ ## Refresh hosted Google URL Inspection results
600
+
601
+ ```sh
602
+ gscdump indexing inspect https://example.com/page --site example.com --yes --json
603
+ ```
604
+
605
+ This asks Google for its current indexed-page verdict. It does not run a live-page test or request indexing.
606
+ Pass 1 to 10 unique URLs, or use `--file` or stdin. The platform enforces its shared daily quota.
607
+ The command sends one request. It never splits batches or retries automatically.
608
+ If the API returns 429, wait for the reported reset before another request.
609
+ If `errors` or `skipped` contains URLs, the command exits 1 and preserves its result on stdout.
610
+ Read `rateLimit.remaining` before deciding whether to inspect more URLs.