@gscdump/cli 5.0.0 → 5.2.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/dist/analysis-local.mjs +2 -2
- package/dist/auth-state.mjs +60 -7
- package/dist/bing-hosted.mjs +26 -5
- package/dist/cli.d.mts +6 -0
- package/dist/cli.mjs +47 -6
- package/dist/command-meta.mjs +2 -2
- package/dist/commands/analyze.mjs +2 -2
- package/dist/commands/auth.mjs +13 -14
- package/dist/commands/bing.mjs +7 -2
- package/dist/commands/config.mjs +5 -15
- package/dist/commands/doctor.mjs +2 -2
- package/dist/commands/dump.mjs +2 -4
- package/dist/commands/entities.mjs +3 -9
- package/dist/commands/indexing-urls.mjs +2 -2
- package/dist/commands/indexing-watch.mjs +10 -7
- package/dist/commands/indexing.mjs +6 -22
- package/dist/commands/init.mjs +3 -6
- package/dist/commands/mcp.mjs +1 -1
- package/dist/commands/papercut.mjs +2 -5
- package/dist/commands/profile.mjs +5 -14
- package/dist/commands/query.mjs +16 -40
- package/dist/commands/report.mjs +2 -2
- package/dist/commands/sitemaps.mjs +3 -10
- package/dist/commands/sites.mjs +7 -12
- package/dist/commands/skill.mjs +1 -4
- package/dist/commands/sync.mjs +2 -8
- package/dist/config.mjs +1 -1
- package/dist/context.mjs +3 -3
- package/dist/error-handler.mjs +72 -16
- package/dist/hosted-auth.mjs +9 -1
- package/dist/hosted-session.mjs +17 -0
- package/dist/hosted-site.mjs +41 -18
- package/dist/mcp/errors.mjs +1 -1
- package/dist/package.mjs +1 -1
- package/dist/route.mjs +5 -9
- package/dist/runtime.d.mts +2 -0
- package/dist/runtime.mjs +1 -0
- package/dist/stop.mjs +23 -0
- package/dist/utils.mjs +13 -14
- package/package.json +7 -7
- package/skills/gscdump/SKILL.md +43 -4
package/dist/hosted-site.mjs
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { stopError } from "./stop.mjs";
|
|
2
|
+
import { connectSiteStep, getHostedAccount, hostedCredential, parseAuthentication, resolveAuthentication, siteManagerOf } from "./auth-state.mjs";
|
|
2
3
|
import { loadConfig } from "./config.mjs";
|
|
3
4
|
import { resolveCliEnvironment } from "./environment.mjs";
|
|
4
|
-
import { logger } from "./utils.mjs";
|
|
5
|
-
import process from "node:process";
|
|
6
5
|
import { createGscdumpV1Client } from "@gscdump/sdk/v1";
|
|
7
6
|
const HOSTED_ARGS = {
|
|
8
7
|
"site": {
|
|
@@ -57,35 +56,59 @@ function matchHostedSite(sites, target) {
|
|
|
57
56
|
sites: [...sites]
|
|
58
57
|
};
|
|
59
58
|
}
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
function noSitesStop(manager) {
|
|
60
|
+
return {
|
|
61
|
+
code: "NO_SITES",
|
|
62
|
+
message: `Your hosted record has no Sites. ${connectSiteStep(manager)} Then run the command again.`,
|
|
63
|
+
nextCommand: null
|
|
64
|
+
};
|
|
65
65
|
}
|
|
66
|
-
function
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
function siteMatchStop(match, manager) {
|
|
67
|
+
const nextCommand = "gscdump sites --json";
|
|
68
|
+
if (match.kind === "ambiguous") return {
|
|
69
|
+
code: "SITE_AMBIGUOUS",
|
|
70
|
+
message: `Multiple hosted Sites match "${match.target}": ${match.matches.map((site) => site.siteUrl).join(", ")}. Pass the exact Site URL with --site.`,
|
|
71
|
+
nextCommand
|
|
72
|
+
};
|
|
73
|
+
if (match.sites.length === 0) return noSitesStop(manager);
|
|
74
|
+
const available = `Hosted Sites: ${match.sites.map((site) => site.siteUrl).join(", ")}.`;
|
|
75
|
+
return match.target ? {
|
|
76
|
+
code: "SITE_NOT_FOUND",
|
|
77
|
+
message: `No hosted Site matches "${match.target}". ${available}`,
|
|
78
|
+
nextCommand
|
|
79
|
+
} : {
|
|
80
|
+
code: "SITE_REQUIRED",
|
|
81
|
+
message: `Pass --site. ${available}`,
|
|
82
|
+
nextCommand
|
|
83
|
+
};
|
|
69
84
|
}
|
|
70
85
|
async function resolveHostedSite(args, command) {
|
|
71
|
-
if (args._?.length)
|
|
86
|
+
if (args._?.length) throw new Error(`\`gscdump ${command.name}\` does not accept a positional Site ID. Pass --site instead.`);
|
|
72
87
|
const environment = resolveCliEnvironment().values;
|
|
73
88
|
const authentication = args["api-key"] ? parseAuthentication({
|
|
74
89
|
_tag: "Hosted",
|
|
75
90
|
apiKey: args["api-key"],
|
|
76
91
|
apiRoot: String(args["api-root"] || environment.GSCDUMP_API_ROOT || "https://gscdump.com/api")
|
|
77
92
|
}) : await resolveAuthentication();
|
|
78
|
-
if (authentication._tag !== "Hosted")
|
|
79
|
-
|
|
93
|
+
if (authentication._tag !== "Hosted") throw stopError({
|
|
94
|
+
code: "HOSTED_MODE_REQUIRED",
|
|
95
|
+
message: `\`gscdump ${command.name}\` reads the hosted record, so it needs Hosted mode. Run \`gscdump auth login --mode hosted\`, or pass --api-key. In Local mode, ${command.localAlternative}.`,
|
|
96
|
+
nextCommand: "gscdump auth login --mode hosted"
|
|
97
|
+
});
|
|
98
|
+
if (args["api-root"] && String(args["api-root"]).replace(/\/+$/, "") !== authentication.apiRoot) throw new Error("The API root changed. Pass --api-key for the new API root.");
|
|
80
99
|
const target = args.site ? String(args.site) : (await loadConfig()).defaultSite;
|
|
81
|
-
const
|
|
82
|
-
|
|
100
|
+
const account = await getHostedAccount(authentication);
|
|
101
|
+
const siteManager = siteManagerOf(authentication, account.issuer);
|
|
102
|
+
const match = matchHostedSite(account.sites, target);
|
|
103
|
+
if (match.kind !== "found") throw stopError(siteMatchStop(match, siteManager));
|
|
83
104
|
return {
|
|
84
105
|
client: createGscdumpV1Client({
|
|
85
106
|
apiRoot: authentication.apiRoot,
|
|
86
107
|
credential: hostedCredential(authentication)
|
|
87
108
|
}),
|
|
88
|
-
site: match.site
|
|
109
|
+
site: match.site,
|
|
110
|
+
authentication,
|
|
111
|
+
siteManager
|
|
89
112
|
};
|
|
90
113
|
}
|
|
91
|
-
export { HOSTED_ARGS, matchHostedSite, resolveHostedSite };
|
|
114
|
+
export { HOSTED_ARGS, matchHostedSite, noSitesStop, resolveHostedSite, siteMatchStop };
|
package/dist/mcp/errors.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { resolveBYOK, resolveServiceAccount } from "../auth.mjs";
|
|
2
2
|
import { COMPARISON_FLAGS, PERIOD_FLAGS } from "../window.mjs";
|
|
3
|
+
import { isQueryError } from "gscdump/query";
|
|
3
4
|
import { isAnalysisError } from "@gscdump/analysis/errors";
|
|
4
5
|
import { isEngineError } from "@gscdump/engine/errors";
|
|
5
6
|
import { classifyError } from "gscdump/errors";
|
|
6
|
-
import { isQueryError } from "gscdump/query";
|
|
7
7
|
const PERIODS = PERIOD_FLAGS;
|
|
8
8
|
const COMPARISONS = COMPARISON_FLAGS;
|
|
9
9
|
const mcpHandlerErrors = {
|
package/dist/package.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var version = "5.
|
|
1
|
+
var version = "5.2.0";
|
|
2
2
|
export { version };
|
package/dist/route.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { stopError } from "./stop.mjs";
|
|
1
2
|
import { probeAuth } from "./auth.mjs";
|
|
2
3
|
import { listStoreSites } from "./store-sites.mjs";
|
|
3
4
|
import { formatSiteResolution, siteArg } from "./context.mjs";
|
|
@@ -199,11 +200,8 @@ function stopCode(route) {
|
|
|
199
200
|
case "partial": return "STORE_RANGE_NOT_COVERED";
|
|
200
201
|
}
|
|
201
202
|
}
|
|
202
|
-
function routeStopError(route,
|
|
203
|
-
return Object.assign(
|
|
204
|
-
name: "RouteStopError",
|
|
205
|
-
routeStop: route
|
|
206
|
-
});
|
|
203
|
+
function routeStopError(route, details) {
|
|
204
|
+
return Object.assign(stopError(details, "RouteStopError"), { routeStop: route });
|
|
207
205
|
}
|
|
208
206
|
async function needCoverage(needs, states) {
|
|
209
207
|
const reader = syncStateCoverageReader(states);
|
|
@@ -299,9 +297,7 @@ function describeStop(route, req, auth) {
|
|
|
299
297
|
} } : {}
|
|
300
298
|
};
|
|
301
299
|
}
|
|
302
|
-
function stopAtRoute(route, req, auth
|
|
303
|
-
|
|
304
|
-
if (opts.json) console.log(JSON.stringify({ error: details }, null, 2));
|
|
305
|
-
throw routeStopError(route, details.message);
|
|
300
|
+
function stopAtRoute(route, req, auth) {
|
|
301
|
+
throw routeStopError(route, describeStop(route, req, auth));
|
|
306
302
|
}
|
|
307
303
|
export { decideRoute, describeStop, liveCommand, liveNote, needCoverage, readRouteState, readSiteStates, resolveReadSite, routeMessage, routeStopError, stopAtRoute, stopCode, syncCommandFor };
|
package/dist/runtime.d.mts
CHANGED
|
@@ -7,6 +7,8 @@ export interface CliRuntime {
|
|
|
7
7
|
configDir: string;
|
|
8
8
|
configDirOverridden: boolean;
|
|
9
9
|
environment: CliEnvironmentSource;
|
|
10
|
+
/** The command writes JSON to stdout. A stop then prints its JSON form there too. */
|
|
11
|
+
jsonOutput: boolean;
|
|
10
12
|
logger: ConsolaInstance;
|
|
11
13
|
quiet: boolean;
|
|
12
14
|
rawArgs: string[];
|
package/dist/runtime.mjs
CHANGED
|
@@ -21,6 +21,7 @@ function createCliRuntime(opts = {}) {
|
|
|
21
21
|
configDir: opts.configDir ?? path.join(os.homedir(), ".config", "gscdump"),
|
|
22
22
|
configDirOverridden: false,
|
|
23
23
|
environment: opts.environment ?? process.env,
|
|
24
|
+
jsonOutput: false,
|
|
24
25
|
logger: baseLogger.withTag("gscdump"),
|
|
25
26
|
quiet: false,
|
|
26
27
|
rawArgs: [...opts.rawArgs ?? process.argv.slice(2)]
|
package/dist/stop.mjs
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
function stopError(details, name = "StopError") {
|
|
2
|
+
return Object.assign(new Error(details.message), {
|
|
3
|
+
name,
|
|
4
|
+
stopDetails: details
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
function stopDetailsOf(error) {
|
|
8
|
+
return error instanceof Error ? error.stopDetails : void 0;
|
|
9
|
+
}
|
|
10
|
+
function userStopError(details) {
|
|
11
|
+
return Object.assign(stopError(details, "UserStopError"), { userStop: true });
|
|
12
|
+
}
|
|
13
|
+
function isUserStop(error) {
|
|
14
|
+
return error instanceof Error && error.userStop === true;
|
|
15
|
+
}
|
|
16
|
+
function commandLineError(message) {
|
|
17
|
+
return stopError({
|
|
18
|
+
code: "USAGE",
|
|
19
|
+
message,
|
|
20
|
+
nextCommand: null
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
export { commandLineError, isUserStop, stopDetailsOf, stopError, userStopError };
|
package/dist/utils.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { useCliRuntime } from "./runtime.mjs";
|
|
2
|
+
import { commandLineError } from "./stop.mjs";
|
|
2
3
|
import { version } from "./package.mjs";
|
|
3
4
|
import process from "node:process";
|
|
4
5
|
import fs from "node:fs/promises";
|
|
5
|
-
import { SearchTypes } from "gscdump/query";
|
|
6
6
|
import os from "node:os";
|
|
7
|
+
import { SearchTypes } from "gscdump/query";
|
|
7
8
|
import { Buffer } from "node:buffer";
|
|
8
9
|
import { MAX_FETCH_BUDGET } from "@gscdump/engine/analysis-types";
|
|
9
10
|
const ALL_SEARCH_TYPES = Object.values(SearchTypes);
|
|
@@ -40,10 +41,14 @@ const OUTPUT_ARGS = {
|
|
|
40
41
|
description: "Suppress info/success output"
|
|
41
42
|
}
|
|
42
43
|
};
|
|
44
|
+
function setJsonOutput(json) {
|
|
45
|
+
useCliRuntime().jsonOutput = json;
|
|
46
|
+
}
|
|
43
47
|
function applyOutputMode(args) {
|
|
44
48
|
const json = Boolean(args.json);
|
|
45
49
|
const quiet = json || Boolean(args.quiet);
|
|
46
50
|
setQuiet(quiet);
|
|
51
|
+
setJsonOutput(json);
|
|
47
52
|
return {
|
|
48
53
|
json,
|
|
49
54
|
quiet
|
|
@@ -52,29 +57,26 @@ function applyOutputMode(args) {
|
|
|
52
57
|
function parseSearchType(value, flag = "--search-type") {
|
|
53
58
|
if (!value) return void 0;
|
|
54
59
|
const v = String(value);
|
|
55
|
-
if (!ALL_SEARCH_TYPES.includes(v)) {
|
|
56
|
-
logger.error(`Invalid ${flag}: ${v}. Allowed: ${ALL_SEARCH_TYPES.join(", ")}`);
|
|
57
|
-
process.exit(1);
|
|
58
|
-
}
|
|
60
|
+
if (!ALL_SEARCH_TYPES.includes(v)) throw commandLineError(`Invalid ${flag}: ${v}. Allowed: ${ALL_SEARCH_TYPES.join(", ")}`);
|
|
59
61
|
return v;
|
|
60
62
|
}
|
|
61
63
|
function parseNameList(value, known, flag) {
|
|
62
64
|
const names = String(value ?? "").split(",").map((name) => name.trim()).filter(Boolean);
|
|
63
65
|
const unknown = names.filter((name) => !known.includes(name));
|
|
64
|
-
if (unknown.length > 0) throw
|
|
65
|
-
if (names.length === 0) throw
|
|
66
|
+
if (unknown.length > 0) throw commandLineError(`Unknown ${flag} name: ${unknown.join(", ")}. Valid names: ${known.join(", ")}.`);
|
|
67
|
+
if (names.length === 0) throw commandLineError(`${flag} needs at least one name. Valid names: ${known.join(", ")}.`);
|
|
66
68
|
return names;
|
|
67
69
|
}
|
|
68
70
|
function parseIntegerOption(value, flag, minimum = 1) {
|
|
69
71
|
if (value === void 0 || value === null) return void 0;
|
|
70
72
|
const text = typeof value === "number" || typeof value === "string" ? String(value).trim() : "";
|
|
71
73
|
const parsed = Number(text);
|
|
72
|
-
if (!/^\d+$/.test(text) || !Number.isSafeInteger(parsed) || parsed < minimum) throw
|
|
74
|
+
if (!/^\d+$/.test(text) || !Number.isSafeInteger(parsed) || parsed < minimum) throw commandLineError(`${flag} must be ${minimum === 0 ? "a non-negative integer" : "a positive integer"}.`);
|
|
73
75
|
return parsed;
|
|
74
76
|
}
|
|
75
77
|
function parseFetchBudget(value) {
|
|
76
78
|
const budget = parseIntegerOption(value, "--fetch-budget");
|
|
77
|
-
if (budget !== void 0 && budget > MAX_FETCH_BUDGET) throw
|
|
79
|
+
if (budget !== void 0 && budget > MAX_FETCH_BUDGET) throw commandLineError(`--fetch-budget must be at most ${MAX_FETCH_BUDGET}. Larger fetches use up the GSC quota.`);
|
|
78
80
|
return budget;
|
|
79
81
|
}
|
|
80
82
|
const ANSI_RE = /\x1B\[[0-9;]*m/g;
|
|
@@ -141,17 +143,14 @@ function applyGradient(text) {
|
|
|
141
143
|
}
|
|
142
144
|
function showSplash() {
|
|
143
145
|
console.log();
|
|
144
|
-
console.log(` ${applyGradient("
|
|
146
|
+
console.log(` ${applyGradient("gscdump")} v${VERSION}`);
|
|
145
147
|
console.log();
|
|
146
148
|
}
|
|
147
149
|
function clearLine() {
|
|
148
150
|
process.stdout.write("\r\x1B[K");
|
|
149
151
|
}
|
|
150
152
|
function displayPath(absPath) {
|
|
151
|
-
const cwd = process.cwd();
|
|
152
153
|
const home = os.homedir();
|
|
153
|
-
if (absPath === cwd) return ".";
|
|
154
|
-
if (absPath.startsWith(`${cwd}/`)) return absPath.slice(cwd.length + 1);
|
|
155
154
|
if (absPath === home) return "~";
|
|
156
155
|
if (absPath.startsWith(`${home}/`)) return `~/${absPath.slice(home.length + 1)}`;
|
|
157
156
|
return absPath;
|
|
@@ -196,4 +195,4 @@ async function readUrlList(input) {
|
|
|
196
195
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
197
196
|
return parseUrlLines(Buffer.concat(chunks).toString("utf-8"));
|
|
198
197
|
}
|
|
199
|
-
export { ALL_SEARCH_TYPES, OUTPUT_ARGS, VERSION, applyOutputMode, clearLine, color, cyan, dim, displayPath, formatAge, isColorEnabled, logger, parseFetchBudget, parseIntegerOption, parseNameList, parseSearchType, plural, progressBar, readUrlList, red, runWithConcurrency, setNoColor, setQuiet, showSplash, toCSV, withConfiguredOutput };
|
|
198
|
+
export { ALL_SEARCH_TYPES, OUTPUT_ARGS, VERSION, applyOutputMode, clearLine, color, cyan, dim, displayPath, formatAge, isColorEnabled, logger, parseFetchBudget, parseIntegerOption, parseNameList, parseSearchType, plural, progressBar, readUrlList, red, runWithConcurrency, setJsonOutput, setNoColor, setQuiet, showSplash, toCSV, withConfiguredOutput };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gscdump/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.2.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.
|
|
48
|
-
"@gscdump/contracts": "^5.
|
|
49
|
-
"@gscdump/engine": "^5.
|
|
50
|
-
"@gscdump/engine-gsc-api": "^5.
|
|
51
|
-
"@gscdump/sdk": "^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",
|
|
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.
|
|
56
|
+
"gscdump": "^5.2.0",
|
|
57
57
|
"ofetch": "^1.5.1",
|
|
58
58
|
"open": "^11.0.4",
|
|
59
59
|
"proper-lockfile": "^4.1.2",
|
package/skills/gscdump/SKILL.md
CHANGED
|
@@ -11,7 +11,7 @@ For `query`, `-s` means `--site`, `-d` means `--dimensions`, and `-f` means `--f
|
|
|
11
11
|
Use `--start` and `--end` for dates. `--site=SITE` also works.
|
|
12
12
|
Use each option once, with either its short or long spelling.
|
|
13
13
|
Put options after the subcommand name: `gscdump store stats --json`, not `gscdump store --json stats`.
|
|
14
|
-
A failed command prints one `Error:` line to stderr and exits 1.
|
|
14
|
+
A failed command prints one `Error:` line to stderr and exits 1. With JSON output it also prints `{ "error" }` on stdout. See [Stops](#stops).
|
|
15
15
|
Example: `gscdump query --site=SITE --start=DATE --end=DATE -d page -f json`.
|
|
16
16
|
|
|
17
17
|
## Start each task
|
|
@@ -43,6 +43,11 @@ gscdump has 2 access modes. Check `gscdump auth status --json` before queries. R
|
|
|
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.
|
|
45
45
|
`gscdump sites` shows the same Sites and progress.
|
|
46
|
+
An empty `sites` list means the hosted record has no Sites. Commands that need a Site then stop with `NO_SITES`.
|
|
47
|
+
Tell the user to connect a Site in the place the `NO_SITES` message names.
|
|
48
|
+
A browser login connects Sites on gscdump.com.
|
|
49
|
+
An API key connects Sites in the app that issued the key. That app can be a partner app, not gscdump.com.
|
|
50
|
+
When gscdump.com names the app, the message names it too, for example `Connect a Site in Request Indexing.`
|
|
46
51
|
|
|
47
52
|
`--mode local|hosted` overrides one invocation. `GSCDUMP_AUTH_MODE` also overrides the saved mode.
|
|
48
53
|
A successful login saves the mode per profile.
|
|
@@ -128,8 +133,7 @@ mixes Store rows and live rows.
|
|
|
128
133
|
- `--live` always asks Search Console. It needs Local mode with Google credentials.
|
|
129
134
|
- `query --sql` reads the Store only. It stops when no table it names has data.
|
|
130
135
|
- JSON output carries `meta.source`: `local`, `live`, or `hosted`.
|
|
131
|
-
- A
|
|
132
|
-
Codes: `NOT_CONNECTED`, `STORE_RANGE_NOT_COVERED` (with `missingDates`), `SYNC_RUNNING` (with `sync.done` and `sync.total`), `NO_SYNCED_DATA`, `STORE_ONLY`, `LIVE_ONLY`.
|
|
136
|
+
- A failure with JSON output prints `{ "error" }` on stdout and exits 1. See [Stops](#stops).
|
|
133
137
|
Partial coverage and a running sync are normal progress. Run `nextCommand`, or tell the user to.
|
|
134
138
|
- A sync is running only while its heartbeat is recent. A killed sync does not block reads.
|
|
135
139
|
- Every Search Analytics, URL Inspection, and Indexing API call spends the shared quota ledger in the data dir.
|
|
@@ -153,7 +157,8 @@ gscdump skill install --agent claude # Codex: --agent codex
|
|
|
153
157
|
```
|
|
154
158
|
|
|
155
159
|
After upgrading the CLI, run this command again to update the installed skill.
|
|
156
|
-
The command prints where it wrote the skill
|
|
160
|
+
The command prints where it wrote the skill, as a path that works from any directory, such as `~/.claude/skills/gscdump`.
|
|
161
|
+
With `--json`, `destination` is the absolute path. Clients without a skill
|
|
157
162
|
directory can read `gscdump --help` and `gscdump <command> --help` instead.
|
|
158
163
|
|
|
159
164
|
## Local mode setup
|
|
@@ -220,6 +225,40 @@ Parse JSON. Never scrape human output.
|
|
|
220
225
|
If the user requests JSON, return the CLI JSON unchanged. Do not replace it with a table.
|
|
221
226
|
Do not rewrite rows, estimate metrics, or add manually calculated totals.
|
|
222
227
|
|
|
228
|
+
## Stops
|
|
229
|
+
|
|
230
|
+
A stop ends a command at a known condition and exits 1. stderr gets the message.
|
|
231
|
+
If the command writes JSON, every failure prints `{ "error": { "code", "message", "nextCommand" } }` on stdout. A stop has a code from the table below.
|
|
232
|
+
Any other failure has the code `FAILED`, such as a network or API error, or `UNEXPECTED`, a defect in the CLI.
|
|
233
|
+
A command writes JSON with `--json`, with `--format json`, and for `query` with its default format.
|
|
234
|
+
If a command printed its JSON result before it failed, such as `inspect` after a quota stop, stdout holds only that result.
|
|
235
|
+
Read `code`, not the message. If `nextCommand` is not null, run it, or tell the user to run it.
|
|
236
|
+
A `gscdump auth login` command needs the user. Tell the user to run it. If `nextCommand` is null, tell the user the `message`.
|
|
237
|
+
A routing stop also has `siteUrl`. `STORE_RANGE_NOT_COVERED` adds `missingDates`, and `SYNC_RUNNING` adds `sync.done` and `sync.total`.
|
|
238
|
+
|
|
239
|
+
| Code | Meaning |
|
|
240
|
+
| --- | --- |
|
|
241
|
+
| `USAGE` | The command line is wrong: an unknown command or option, or a value the option does not accept. Fix it with the command's `--help` |
|
|
242
|
+
| `NOT_CONNECTED` | The Store cannot answer, and Google is not connected |
|
|
243
|
+
| `STORE_RANGE_NOT_COVERED` | The Store does not have `missingDates` |
|
|
244
|
+
| `SYNC_RUNNING` | A sync is still filling the dates |
|
|
245
|
+
| `NO_SYNCED_DATA` | A Store-only read found no synced data |
|
|
246
|
+
| `STORE_ONLY` | The read needs the Store, so `--live` cannot run it |
|
|
247
|
+
| `LIVE_ONLY` | Only `--live` can answer the read |
|
|
248
|
+
| `LOGIN_CANCELLED` | The user selected Cancel on the gscdump.com login page. Ask before you run `nextCommand` |
|
|
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 |
|
|
251
|
+
| `HOSTED_CREDENTIALS_MISSING` | Hosted mode has no CLI session and no API key |
|
|
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
|
+
| `NO_SITES` | The hosted record has no Sites. The message says where the user connects one |
|
|
254
|
+
| `SITE_NOT_FOUND` | No hosted Site matches `--site` |
|
|
255
|
+
| `SITE_AMBIGUOUS` | More than one hosted Site matches `--site` |
|
|
256
|
+
| `SITE_REQUIRED` | The hosted record has more than one Site. Pass `--site` |
|
|
257
|
+
| `BING_NOT_CONNECTED` | The Site has no hosted Bing connection |
|
|
258
|
+
| `RECORD_NOT_READY` | gscdump has not prepared the Site's record for reads yet |
|
|
259
|
+
| `RANGE_NOT_SYNCED` | The hosted record does not hold the requested dates |
|
|
260
|
+
| `QUOTA_USED_UP` | A Google API quota is used up. The message says when it resets |
|
|
261
|
+
|
|
223
262
|
## Commands
|
|
224
263
|
|
|
225
264
|
| Command | Use it for |
|