@gscdump/cli 4.1.0 → 4.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/README.md +5 -3
- package/dist/auth-state.mjs +26 -10
- package/dist/auth.mjs +1 -1
- package/dist/bing-hosted.mjs +2 -2
- package/dist/cloud-google.mjs +2 -2
- package/dist/commands/auth.mjs +33 -9
- package/dist/commands/init.mjs +1 -1
- package/dist/commands/mcp.mjs +1 -1
- package/dist/hosted-auth.mjs +23 -3
- package/dist/hosted-site.mjs +2 -2
- package/dist/mcp/errors.mjs +4 -2
- package/dist/package.mjs +1 -1
- package/package.json +7 -7
- package/skills/gscdump/SKILL.md +11 -5
package/README.md
CHANGED
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
Google Search Console CLI and MCP server.
|
|
4
4
|
|
|
5
|
+
Choose [cloud or local access](../../docs/gscdump-cli/guides/1.start/2.choose-access.md) before login. Cloud login opens gscdump.com and saves a CLI session. Local login calls Google directly and can use your own Google credentials.
|
|
6
|
+
|
|
5
7
|
```bash
|
|
6
8
|
npm install -g @gscdump/cli
|
|
7
9
|
```
|
|
8
10
|
|
|
9
|
-
- [Getting started](../../docs/gscdump-cli/guides/1.
|
|
10
|
-
- [Authentication](../../docs/gscdump-cli/guides/2.
|
|
11
|
+
- [Getting started](../../docs/gscdump-cli/guides/1.start/1.first-result.md)
|
|
12
|
+
- [Authentication](../../docs/gscdump-cli/guides/1.start/2.choose-access.md)
|
|
11
13
|
- [Command reference](../../docs/gscdump-cli/api/1.commands.md)
|
|
12
|
-
- [AI integration and MCP](../../docs/gscdump-cli/guides/3.
|
|
14
|
+
- [AI integration and MCP](../../docs/gscdump-cli/guides/1.start/3.connect-an-agent.md)
|
|
13
15
|
|
|
14
16
|
## License
|
|
15
17
|
|
package/dist/auth-state.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import fs from "node:fs/promises";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { gscdumpAvailableSiteSchema } from "@gscdump/contracts";
|
|
7
7
|
import { z } from "zod";
|
|
8
|
+
const HOSTED_SESSION_REJECTED = "gscdump.com rejected the CLI session. Run `gscdump auth login --mode cloud` again.";
|
|
8
9
|
const apiRootSchema = z.url().transform((value) => value.replace(/\/+$/, "")).refine((value) => {
|
|
9
10
|
const url = new URL(value);
|
|
10
11
|
return !url.username && !url.password && !url.search && !url.hash && (url.protocol === "https:" || url.protocol === "http:" && [
|
|
@@ -13,11 +14,19 @@ const apiRootSchema = z.url().transform((value) => value.replace(/\/+$/, "")).re
|
|
|
13
14
|
"[::1]"
|
|
14
15
|
].includes(url.hostname));
|
|
15
16
|
}, "Use HTTPS, or HTTP on loopback, for the API root");
|
|
16
|
-
const stateSchema = z.
|
|
17
|
-
_tag: z.literal("
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
const stateSchema = z.union([
|
|
18
|
+
z.object({ _tag: z.literal("Local") }),
|
|
19
|
+
z.object({
|
|
20
|
+
_tag: z.literal("Cloud"),
|
|
21
|
+
apiRoot: apiRootSchema,
|
|
22
|
+
apiKey: z.string().trim().regex(/^gsd_user_\S+$/)
|
|
23
|
+
}),
|
|
24
|
+
z.object({
|
|
25
|
+
_tag: z.literal("Cloud"),
|
|
26
|
+
apiRoot: apiRootSchema,
|
|
27
|
+
sessionId: z.string().regex(/^[a-f0-9]{64}$/)
|
|
28
|
+
})
|
|
29
|
+
]);
|
|
21
30
|
function parseAuthMode(value) {
|
|
22
31
|
if (value === void 0 || value === "") return void 0;
|
|
23
32
|
if (value === "cloud" || value === "local") return value;
|
|
@@ -25,7 +34,7 @@ function parseAuthMode(value) {
|
|
|
25
34
|
}
|
|
26
35
|
function parseAuthentication(value) {
|
|
27
36
|
const parsed = stateSchema.safeParse(value);
|
|
28
|
-
if (!parsed.success) throw new Error("Invalid authentication. Use a gscdump user API key
|
|
37
|
+
if (!parsed.success) throw new Error("Invalid authentication. Use a CLI session or gscdump user API key with a trusted API root.");
|
|
29
38
|
return parsed.data;
|
|
30
39
|
}
|
|
31
40
|
async function saveAuthentication(state) {
|
|
@@ -50,6 +59,10 @@ async function saveAuthentication(state) {
|
|
|
50
59
|
async function clearAuthentication() {
|
|
51
60
|
await fs.rm(path.join(useCliRuntime().configDir, "authentication.json"), { force: true });
|
|
52
61
|
}
|
|
62
|
+
async function revokeCloudSession(state) {
|
|
63
|
+
if (!("sessionId" in state)) return;
|
|
64
|
+
await cloudRequest(state, "/cli/auth/logout", { method: "POST" });
|
|
65
|
+
}
|
|
53
66
|
async function resolveAuthentication() {
|
|
54
67
|
const runtime = useCliRuntime();
|
|
55
68
|
const env = runtime.environment;
|
|
@@ -73,7 +86,7 @@ async function resolveAuthentication() {
|
|
|
73
86
|
return parsed.data;
|
|
74
87
|
}
|
|
75
88
|
if (state?._tag === "Cloud") {
|
|
76
|
-
if (env.GSCDUMP_API_ROOT && env.GSCDUMP_API_ROOT.replace(/\/+$/, "") !== state.apiRoot) throw new Error("The API root changed.
|
|
89
|
+
if (env.GSCDUMP_API_ROOT && env.GSCDUMP_API_ROOT.replace(/\/+$/, "") !== state.apiRoot) throw new Error("The API root changed. Run `gscdump auth login --mode cloud` for the new API root.");
|
|
77
90
|
return state;
|
|
78
91
|
}
|
|
79
92
|
if (mode === "cloud") throw new Error("Hosted credentials are missing. Run `gscdump auth login --mode cloud`.");
|
|
@@ -94,13 +107,13 @@ async function cloudRequest(state, route, options = {}) {
|
|
|
94
107
|
...options,
|
|
95
108
|
headers: {
|
|
96
109
|
"Content-Type": "application/json",
|
|
97
|
-
"x-api-key": state.apiKey
|
|
110
|
+
..."sessionId" in state ? { "x-cli-session": state.sessionId } : { "x-api-key": state.apiKey }
|
|
98
111
|
},
|
|
99
112
|
signal: options.signal ?? AbortSignal.timeout(3e4),
|
|
100
113
|
redirect: "error"
|
|
101
114
|
});
|
|
102
115
|
if (!response.ok) {
|
|
103
|
-
const message = response.status === 401 ? HOSTED_KEY_REJECTED : `Hosted request failed (${response.status}) for ${route.split("?")[0]}. Check \`gscdump auth status\`.`;
|
|
116
|
+
const message = response.status === 401 ? "sessionId" in state ? HOSTED_SESSION_REJECTED : HOSTED_KEY_REJECTED : `Hosted request failed (${response.status}) for ${route.split("?")[0]}. Check \`gscdump auth status\`.`;
|
|
104
117
|
throw Object.assign(new Error(message), {
|
|
105
118
|
statusCode: response.status,
|
|
106
119
|
retryAfter: response.headers.get("retry-after"),
|
|
@@ -109,6 +122,9 @@ async function cloudRequest(state, route, options = {}) {
|
|
|
109
122
|
}
|
|
110
123
|
return response.status === 204 ? void 0 : response.json();
|
|
111
124
|
}
|
|
125
|
+
function cloudCredential(state) {
|
|
126
|
+
return "sessionId" in state ? state.sessionId : state.apiKey;
|
|
127
|
+
}
|
|
112
128
|
async function getCloudAccount(state) {
|
|
113
129
|
const result = accountSchema.safeParse(await cloudRequest(state, "/cli/me"));
|
|
114
130
|
if (!result.success) throw new Error("The hosted API returned invalid account data.");
|
|
@@ -124,4 +140,4 @@ function formatHostedSync(site) {
|
|
|
124
140
|
const status = site.syncStatus ?? "pending";
|
|
125
141
|
return `${status}${site.syncProgress && site.syncProgress.total > 0 && status !== "synced" ? `: ${site.syncProgress.completed.toLocaleString("en-US")} of ${site.syncProgress.total.toLocaleString("en-US")} days (${Math.round(site.syncProgress.percent)}%)` : ""}${status === "synced" && site.oldestDateSynced && site.newestDateSynced ? `: ${site.oldestDateSynced} to ${site.newestDateSynced}` : ""}`;
|
|
126
142
|
}
|
|
127
|
-
export { clearAuthentication, cloudRequest, formatHostedSync, getCloudAccount, getCloudSites, parseAuthMode, parseAuthentication, resolveAuthentication, saveAuthentication };
|
|
143
|
+
export { HOSTED_SESSION_REJECTED, clearAuthentication, cloudCredential, cloudRequest, formatHostedSync, getCloudAccount, getCloudSites, parseAuthMode, parseAuthentication, resolveAuthentication, revokeCloudSession, saveAuthentication };
|
package/dist/auth.mjs
CHANGED
|
@@ -70,7 +70,7 @@ function getTokensPath() {
|
|
|
70
70
|
const GOOGLE_NOT_CONNECTED = [
|
|
71
71
|
"Google is not connected. Use one of these:",
|
|
72
72
|
" Local: gscdump auth login",
|
|
73
|
-
"
|
|
73
|
+
" Cloud: gscdump auth login --mode cloud (opens a browser)",
|
|
74
74
|
" BYOK: set GSC_ACCESS_TOKEN, or GSC_CLIENT_ID, GSC_CLIENT_SECRET and GSC_REFRESH_TOKEN"
|
|
75
75
|
].join("\n");
|
|
76
76
|
async function loadTokens() {
|
package/dist/bing-hosted.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { getCloudAccount } from "./auth-state.mjs";
|
|
1
|
+
import { cloudCredential, getCloudAccount } from "./auth-state.mjs";
|
|
2
2
|
import { logger } from "./utils.mjs";
|
|
3
3
|
import { writeBingDump } from "./bing-data.mjs";
|
|
4
4
|
import { createGscdumpV1Client } from "@gscdump/sdk/v1";
|
|
5
5
|
function hostedBingClient(state) {
|
|
6
6
|
return createGscdumpV1Client({
|
|
7
7
|
apiRoot: state.apiRoot,
|
|
8
|
-
credential: state
|
|
8
|
+
credential: cloudCredential(state)
|
|
9
9
|
});
|
|
10
10
|
}
|
|
11
11
|
async function listHostedBingSites(state) {
|
package/dist/cloud-google.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HOSTED_KEY_REJECTED } from "./error-handler.mjs";
|
|
2
|
-
import { cloudRequest } from "./auth-state.mjs";
|
|
2
|
+
import { HOSTED_SESSION_REJECTED, cloudRequest } from "./auth-state.mjs";
|
|
3
3
|
import { googleSearchConsole } from "gscdump/client";
|
|
4
4
|
import { ofetch } from "ofetch";
|
|
5
5
|
function createCloudGoogleClient(state, fetchOptions) {
|
|
@@ -69,7 +69,7 @@ function createCloudGoogleClient(state, fetchOptions) {
|
|
|
69
69
|
if (error && typeof error === "object" && "response" in error && error.response instanceof Response) {
|
|
70
70
|
if (error.response.status === 401) return Response.json({ error: {
|
|
71
71
|
code: 401,
|
|
72
|
-
message: HOSTED_KEY_REJECTED
|
|
72
|
+
message: "sessionId" in state ? HOSTED_SESSION_REJECTED : HOSTED_KEY_REJECTED
|
|
73
73
|
} }, { status: 401 });
|
|
74
74
|
return error.response;
|
|
75
75
|
}
|
package/dist/commands/auth.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { authCommandMeta } from "../command-meta.mjs";
|
|
2
2
|
import { useCliRuntime } from "../runtime.mjs";
|
|
3
|
-
import { clearAuthentication, formatHostedSync, getCloudAccount, getCloudSites, parseAuthMode, parseAuthentication, resolveAuthentication, saveAuthentication } from "../auth-state.mjs";
|
|
3
|
+
import { clearAuthentication, formatHostedSync, getCloudAccount, getCloudSites, parseAuthMode, parseAuthentication, resolveAuthentication, revokeCloudSession, saveAuthentication } from "../auth-state.mjs";
|
|
4
4
|
import { loadConfig, saveConfig } from "../config.mjs";
|
|
5
5
|
import { OUTPUT_ARGS, applyOutputMode, logger } from "../utils.mjs";
|
|
6
|
+
import { loginWithCloudSession } from "../hosted-auth.mjs";
|
|
6
7
|
import { GOOGLE_NOT_CONNECTED, clearTokens, formatAuthProvenance, getAuth, loadServiceAccount, loadTokens, resolveBYOK, saveTokens } from "../auth.mjs";
|
|
7
8
|
import { missingRequiredScopes } from "../auth-scopes.mjs";
|
|
8
9
|
import { clearBingCredentials, getBingClient, inspectBingCredentials } from "../bing-auth.mjs";
|
|
@@ -12,7 +13,8 @@ import { adoptCurrentConfigAsProfile, profileNameFromEmail, resolveActiveProfile
|
|
|
12
13
|
import process from "node:process";
|
|
13
14
|
import { defineCommand } from "citty";
|
|
14
15
|
import path from "node:path";
|
|
15
|
-
import {
|
|
16
|
+
import { setTimeout } from "node:timers/promises";
|
|
17
|
+
import open from "open";
|
|
16
18
|
const MODE_ARG = {
|
|
17
19
|
type: "string",
|
|
18
20
|
description: "Authentication mode: cloud or local"
|
|
@@ -23,21 +25,39 @@ function applyAuthMode(args) {
|
|
|
23
25
|
}
|
|
24
26
|
async function requireLocalAuth(args) {
|
|
25
27
|
applyAuthMode(args);
|
|
26
|
-
if ((await resolveAuthentication())._tag === "Cloud") throw new Error("Cloud authentication uses
|
|
28
|
+
if ((await resolveAuthentication())._tag === "Cloud") throw new Error("Cloud authentication uses a CLI session. Google OAuth scopes and token refresh require --mode local.");
|
|
27
29
|
}
|
|
28
30
|
async function loginCloud(args) {
|
|
29
31
|
const env = useCliRuntime().environment;
|
|
30
|
-
|
|
32
|
+
const apiKey = String(args["api-key"] ?? env.GSCDUMP_API_KEY ?? "");
|
|
33
|
+
const apiRoot = String(args["api-root"] ?? env.GSCDUMP_API_ROOT ?? "https://gscdump.com/api");
|
|
31
34
|
if (!apiKey) {
|
|
32
|
-
if (
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
if (apiRoot.replace(/\/+$/, "") !== "https://gscdump.com/api") throw new Error("Browser login uses gscdump.com. Supply --api-key for a custom API root.");
|
|
36
|
+
const sessionId = await loginWithCloudSession({
|
|
37
|
+
request: fetch,
|
|
38
|
+
now: Date.now,
|
|
39
|
+
wait: setTimeout,
|
|
40
|
+
authorize: async (url) => {
|
|
41
|
+
logger.info(`Open this URL to connect cloud access:\n${url}`);
|
|
42
|
+
if (args.browser !== false) await open(url).catch((error) => logger.warn(`Browser could not open: ${error.message}. Open the URL above.`));
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
const state = parseAuthentication({
|
|
46
|
+
_tag: "Cloud",
|
|
47
|
+
apiRoot,
|
|
48
|
+
sessionId
|
|
49
|
+
});
|
|
50
|
+
if (state._tag !== "Cloud") throw new Error("Cloud login did not return a CLI session.");
|
|
51
|
+
const account = await getCloudAccount(state);
|
|
52
|
+
await saveAuthentication(state);
|
|
53
|
+
logger.success(`Cloud authentication saved for ${account.user.email}`);
|
|
54
|
+
logger.info("Google and Bing commands use connections saved on gscdump.com.");
|
|
55
|
+
return;
|
|
36
56
|
}
|
|
37
57
|
const state = parseAuthentication({
|
|
38
58
|
_tag: "Cloud",
|
|
39
59
|
apiKey,
|
|
40
|
-
apiRoot
|
|
60
|
+
apiRoot
|
|
41
61
|
});
|
|
42
62
|
if (state._tag !== "Cloud") throw new Error("Cloud login requires a gscdump user API key.");
|
|
43
63
|
const account = await getCloudAccount(state);
|
|
@@ -365,6 +385,10 @@ const logoutCommand = defineCommand({
|
|
|
365
385
|
args: { ...OUTPUT_ARGS },
|
|
366
386
|
async run({ args }) {
|
|
367
387
|
applyOutputMode(args);
|
|
388
|
+
await resolveAuthentication().then((authentication) => authentication._tag === "Cloud" ? revokeCloudSession(authentication) : void 0).catch((error) => {
|
|
389
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
390
|
+
logger.warn(`Cloud session revocation failed (${message}). Local credentials are still cleared.`);
|
|
391
|
+
});
|
|
368
392
|
await clearTokens();
|
|
369
393
|
await clearBingCredentials();
|
|
370
394
|
await clearAuthentication();
|
package/dist/commands/init.mjs
CHANGED
|
@@ -148,7 +148,7 @@ const initCommand = defineCommand({
|
|
|
148
148
|
logger.error([
|
|
149
149
|
"No Google credentials found. Init cannot prompt without a terminal.",
|
|
150
150
|
"Run `gscdump auth login` in a terminal, or set GSC_CLIENT_ID, GSC_CLIENT_SECRET and GSC_REFRESH_TOKEN.",
|
|
151
|
-
"To use gscdump.com, run `gscdump
|
|
151
|
+
"To use gscdump.com, run `gscdump auth login --mode cloud`."
|
|
152
152
|
].join("\n"));
|
|
153
153
|
process.exit(1);
|
|
154
154
|
}
|
package/dist/commands/mcp.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
9
9
|
const MCP_NO_AUTH_MESSAGE = [
|
|
10
10
|
"gscdump has no Google authentication.",
|
|
11
11
|
"Run `gscdump auth login` in a terminal, then call this tool again.",
|
|
12
|
-
"For cloud mode,
|
|
12
|
+
"For cloud mode, run `gscdump auth login --mode cloud` in a terminal.",
|
|
13
13
|
"For local mode, set GSC_SERVICE_ACCOUNT_JSON or GOOGLE_APPLICATION_CREDENTIALS to a service-account key file, set GSC_ACCESS_TOKEN, or set GSC_CLIENT_ID, GSC_CLIENT_SECRET, and GSC_REFRESH_TOKEN.",
|
|
14
14
|
"If you set environment variables, set them in the MCP server configuration and restart the MCP client.",
|
|
15
15
|
"If the gscdump command is missing, run `npm install -g @gscdump/cli`."
|
package/dist/hosted-auth.mjs
CHANGED
|
@@ -8,7 +8,11 @@ const pollSchema = z.discriminatedUnion("status", [z.object({ status: z.literal(
|
|
|
8
8
|
status: z.literal("complete"),
|
|
9
9
|
tokens: accessSchema.extend({ refreshToken: z.string().min(1) })
|
|
10
10
|
})]);
|
|
11
|
-
|
|
11
|
+
const cloudPollSchema = z.discriminatedUnion("status", [z.object({ status: z.literal("pending") }), z.object({
|
|
12
|
+
status: z.literal("complete"),
|
|
13
|
+
sessionId: z.string().regex(/^[a-f0-9]{64}$/)
|
|
14
|
+
})]);
|
|
15
|
+
async function requestJson(request, route, init = {}, failureMessage) {
|
|
12
16
|
const response = await request(`${ORIGIN}/api/cli/auth/${route}`, {
|
|
13
17
|
...init,
|
|
14
18
|
redirect: "error",
|
|
@@ -16,7 +20,7 @@ async function requestJson(request, route, init = {}) {
|
|
|
16
20
|
});
|
|
17
21
|
if (!response.ok) {
|
|
18
22
|
if (response.status === 429 || response.status >= 500) throw new Error("Google authorization is temporarily unavailable. Try again later.");
|
|
19
|
-
throw new Error("Google authorization failed. Run `gscdump auth login --mode local --force` to reconnect.");
|
|
23
|
+
throw new Error(failureMessage ?? "Google authorization failed. Run `gscdump auth login --mode local --force` to reconnect.");
|
|
20
24
|
}
|
|
21
25
|
return response.json();
|
|
22
26
|
}
|
|
@@ -52,4 +56,20 @@ async function loginWithPlatform(deps) {
|
|
|
52
56
|
}
|
|
53
57
|
throw new Error("Authorization expired. Run `gscdump auth login` to try again.");
|
|
54
58
|
}
|
|
55
|
-
|
|
59
|
+
const CLOUD_SESSION_FAILURE = "Cloud authorization failed. Run `gscdump auth login --mode cloud` to try again.";
|
|
60
|
+
async function loginWithCloudSession(deps) {
|
|
61
|
+
const init = z.object({
|
|
62
|
+
code: z.string().regex(/^S-[A-F0-9]{20}$/),
|
|
63
|
+
expiresIn: z.number().int().positive().max(600)
|
|
64
|
+
}).parse(await requestJson(deps.request, "init?mode=cloud", { method: "POST" }, CLOUD_SESSION_FAILURE));
|
|
65
|
+
const deadline = deps.now() + init.expiresIn * 1e3;
|
|
66
|
+
const url = `${ORIGIN}/app/cli/auth?code=${init.code}`;
|
|
67
|
+
await deps.authorize(url);
|
|
68
|
+
while (deps.now() < deadline) {
|
|
69
|
+
const result = cloudPollSchema.parse(await requestJson(deps.request, `poll?code=${init.code}`, {}, CLOUD_SESSION_FAILURE));
|
|
70
|
+
if (result.status === "complete") return result.sessionId;
|
|
71
|
+
await deps.wait(2e3);
|
|
72
|
+
}
|
|
73
|
+
throw new Error("Authorization expired. Run `gscdump auth login --mode cloud` to try again.");
|
|
74
|
+
}
|
|
75
|
+
export { loginWithCloudSession, loginWithPlatform, refreshWithPlatform };
|
package/dist/hosted-site.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getCloudAccount, parseAuthentication, resolveAuthentication } from "./auth-state.mjs";
|
|
1
|
+
import { cloudCredential, getCloudAccount, parseAuthentication, resolveAuthentication } from "./auth-state.mjs";
|
|
2
2
|
import { loadConfig } from "./config.mjs";
|
|
3
3
|
import { resolveCliEnvironment } from "./environment.mjs";
|
|
4
4
|
import { logger } from "./utils.mjs";
|
|
@@ -83,7 +83,7 @@ async function resolveHostedSite(args, command) {
|
|
|
83
83
|
return {
|
|
84
84
|
client: createGscdumpV1Client({
|
|
85
85
|
apiRoot: authentication.apiRoot,
|
|
86
|
-
credential: authentication
|
|
86
|
+
credential: cloudCredential(authentication)
|
|
87
87
|
}),
|
|
88
88
|
site: match.site
|
|
89
89
|
};
|
package/dist/mcp/errors.mjs
CHANGED
|
@@ -114,7 +114,7 @@ function nextStep(error, status, mode) {
|
|
|
114
114
|
return "The signed-in account cannot open this Site. Check its Search Console permissions, or run `gscdump auth status` to see the account.";
|
|
115
115
|
}
|
|
116
116
|
if (status === 401 || error.kind === "auth-expired") {
|
|
117
|
-
if (mode === "cloud") return "
|
|
117
|
+
if (mode === "cloud") return "Run `gscdump auth login --mode cloud` in a terminal, then restart the MCP client.";
|
|
118
118
|
if (mode === "service-account") return "Fix the service-account key (GSC_SERVICE_ACCOUNT_JSON or GOOGLE_APPLICATION_CREDENTIALS) in the MCP server configuration and restart the MCP client, or run `gscdump auth status`.";
|
|
119
119
|
if (mode === "byok") return "Refresh GSC_ACCESS_TOKEN (or GSC_CLIENT_ID, GSC_CLIENT_SECRET, and GSC_REFRESH_TOKEN) in the MCP server configuration and restart the MCP client.";
|
|
120
120
|
return "Run `gscdump auth login` in a terminal to connect again.";
|
|
@@ -128,7 +128,9 @@ function describeApiError(error, mode = "local") {
|
|
|
128
128
|
if (status === void 0) return null;
|
|
129
129
|
const classified = classifyGoogleError(error);
|
|
130
130
|
const message = googleMessage(error) ?? classified.message;
|
|
131
|
-
return `API error ${status}: ${
|
|
131
|
+
if (message === HOSTED_KEY_REJECTED) return `API error ${status}: ${HOSTED_KEY_REJECTED_REASON}. Replace GSCDUMP_API_KEY with a key from gscdump.com Agent setup, then restart the MCP client.`;
|
|
132
|
+
if (message === "gscdump.com rejected the CLI session. Run `gscdump auth login --mode cloud` again.") return `API error ${status}: gscdump.com rejected the CLI session. Run \`gscdump auth login --mode cloud\` again, then restart the MCP client.`;
|
|
133
|
+
return `API error ${status}: ${message.replace(/\s+/g, " ").trim().replace(/\.$/, "")}. ${nextStep(classified, status, mode)}`.trim();
|
|
132
134
|
}
|
|
133
135
|
function googleMessage(error) {
|
|
134
136
|
const bodies = [error?.data, error?.response?.data];
|
package/dist/package.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var version = "4.
|
|
1
|
+
var version = "4.2.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": "4.
|
|
4
|
+
"version": "4.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": "^4.
|
|
48
|
-
"@gscdump/contracts": "^4.
|
|
49
|
-
"@gscdump/engine": "^4.
|
|
50
|
-
"@gscdump/engine-gsc-api": "^4.
|
|
51
|
-
"@gscdump/sdk": "^4.
|
|
47
|
+
"@gscdump/analysis": "^4.2.0",
|
|
48
|
+
"@gscdump/contracts": "^4.2.0",
|
|
49
|
+
"@gscdump/engine": "^4.2.0",
|
|
50
|
+
"@gscdump/engine-gsc-api": "^4.2.0",
|
|
51
|
+
"@gscdump/sdk": "^4.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": "^4.
|
|
56
|
+
"gscdump": "^4.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
|
@@ -38,7 +38,7 @@ In cloud mode, `hostedSync` lists each gscdump.com Site with `syncStatus` and `s
|
|
|
38
38
|
|
|
39
39
|
| Mode | Credentials | Query path |
|
|
40
40
|
| --- | --- | --- |
|
|
41
|
-
| `cloud` |
|
|
41
|
+
| `cloud` | Browser-approved CLI session; optional user API key for automation | `https://gscdump.com/api` uses saved Search Engine connections |
|
|
42
42
|
| `local` | Google OAuth/service account or Bing API key/OAuth | Calls the Search Engine directly |
|
|
43
43
|
|
|
44
44
|
`--mode cloud|local` overrides one invocation. `GSCDUMP_AUTH_MODE` also overrides the saved mode.
|
|
@@ -47,28 +47,32 @@ If no mode is saved, `GSCDUMP_API_KEY` selects cloud mode.
|
|
|
47
47
|
With neither source, the CLI defaults to local mode.
|
|
48
48
|
When a saved mode exists, it remains selected unless an explicit override applies.
|
|
49
49
|
Never switch modes to bypass an authentication failure.
|
|
50
|
-
`GSCDUMP_API_ROOT` defaults to `https://gscdump.com/api`. Supply
|
|
50
|
+
`GSCDUMP_API_ROOT` defaults to `https://gscdump.com/api`. Browser login uses this root. Supply an API key explicitly for a custom root.
|
|
51
51
|
|
|
52
52
|
```sh
|
|
53
|
-
#
|
|
53
|
+
# Open gscdump.com in a browser and approve the CLI session.
|
|
54
54
|
gscdump auth login --mode cloud
|
|
55
55
|
gscdump bing sites --json
|
|
56
56
|
gscdump bing login --site s_SITE_ID
|
|
57
57
|
gscdump bing dump --site s_SITE_ID --out ./bing-export --format json
|
|
58
58
|
|
|
59
|
-
# Local Google
|
|
59
|
+
# Local Google login uses gscdump.com for OAuth by default.
|
|
60
|
+
# Google data requests go directly from the CLI to Google.
|
|
60
61
|
gscdump auth login --mode local
|
|
61
62
|
gscdump bing login --mode local
|
|
62
63
|
gscdump bing dump --site https://example.com/ --out ./bing-export
|
|
63
64
|
```
|
|
64
65
|
|
|
66
|
+
For a fully local Google login, set `GSC_CLIENT_ID` and `GSC_CLIENT_SECRET` before `auth login --mode local`.
|
|
67
|
+
The CLI then opens Google directly. A service account can use `--service-account` instead.
|
|
68
|
+
|
|
65
69
|
Hosted Bing login opens the existing connection flow on gscdump.com.
|
|
66
70
|
Local Bing login uses `BING_API_KEY` or a password prompt.
|
|
67
71
|
Local `--oauth` uses `BING_CLIENT_ID`, `BING_CLIENT_SECRET`, and a registered loopback callback.
|
|
68
72
|
The default callback is `http://127.0.0.1:53683/oauth/bing`. `BING_ACCESS_TOKEN` accepts an existing OAuth access token.
|
|
69
73
|
After cloud Bing login opens a browser, use `bing status --site s_SITE_ID` to confirm the connection.
|
|
70
74
|
|
|
71
|
-
`auth logout`
|
|
75
|
+
`auth logout` revokes a saved cloud CLI session and removes saved mode and Google and Bing credentials.
|
|
72
76
|
`bing logout --mode local` removes only saved Bing credentials. Environment credentials remain active until unset.
|
|
73
77
|
|
|
74
78
|
Hosted Bing commands use the API's plan and preview access rules.
|
|
@@ -154,6 +158,8 @@ If local Google credentials are missing, use one of these paths:
|
|
|
154
158
|
|
|
155
159
|
Default local login opens gscdump.com for free Google login and token refresh.
|
|
156
160
|
Data queries call Google directly. No Google Cloud project or hosted activation is required.
|
|
161
|
+
For a fully local connection, set `GSC_CLIENT_ID` and `GSC_CLIENT_SECRET` before login.
|
|
162
|
+
The CLI then opens Google directly and uses a loopback callback.
|
|
157
163
|
Use `gscdump auth login --mode local --no-browser` when a browser runs on another host.
|
|
158
164
|
The default grant is read-only Search Console access.
|
|
159
165
|
For Google write operations, use your own OAuth client with the required scopes.
|