@gscdump/cli 0.3.1 → 0.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/dist/index.mjs +1624 -163
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,58 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "node:
|
|
3
|
-
import process from "node:process";
|
|
2
|
+
import process$1 from "node:process";
|
|
4
3
|
import { defineCommand, runMain } from "citty";
|
|
4
|
+
import { cancel, confirm, isCancel, multiselect, select, text } from "@clack/prompts";
|
|
5
5
|
import fs from "node:fs/promises";
|
|
6
6
|
import { createServer } from "node:http";
|
|
7
7
|
import path from "node:path";
|
|
8
|
-
import { cancel, isCancel, multiselect, select, text } from "@clack/prompts";
|
|
9
8
|
import { OAuth2Client } from "google-auth-library";
|
|
10
|
-
import os from "node:os";
|
|
11
9
|
import { consola } from "consola";
|
|
12
10
|
import { batchInspectUrls, batchRequestIndexing, deleteSitemap, fetchSitemap, fetchSitemaps, fetchSites, fetchSitesWithSitemaps, formatErrorForCli, getIndexingMetadata, googleSearchConsole, inspectUrl, requestIndexing, submitSitemap } from "gscdump";
|
|
11
|
+
import os from "node:os";
|
|
13
12
|
import { between, country, date, device, gsc, page, query, searchAppearance } from "gscdump/query";
|
|
14
13
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
15
14
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
16
15
|
import { z } from "zod";
|
|
17
16
|
|
|
18
|
-
//#region rolldown:runtime
|
|
19
|
-
var __defProp = Object.defineProperty;
|
|
20
|
-
var __exportAll = (all, symbols) => {
|
|
21
|
-
let target = {};
|
|
22
|
-
for (var name in all) {
|
|
23
|
-
__defProp(target, name, {
|
|
24
|
-
get: all[name],
|
|
25
|
-
enumerable: true
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
if (symbols) {
|
|
29
|
-
__defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
30
|
-
}
|
|
31
|
-
return target;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
//#endregion
|
|
35
|
-
//#region src/config.ts
|
|
36
|
-
let configDir = path.join(os.homedir(), ".config", "gscdump");
|
|
37
|
-
function getConfigDir() {
|
|
38
|
-
return configDir;
|
|
39
|
-
}
|
|
40
|
-
const DEFAULT_CLOUD_URL = "https://cloud.gscdump.com";
|
|
41
|
-
async function loadConfig() {
|
|
42
|
-
return fs.readFile(path.join(configDir, "config.json"), "utf-8").then((data) => JSON.parse(data)).catch(() => ({}));
|
|
43
|
-
}
|
|
44
|
-
async function saveConfig(config) {
|
|
45
|
-
await fs.mkdir(configDir, {
|
|
46
|
-
recursive: true,
|
|
47
|
-
mode: 448
|
|
48
|
-
});
|
|
49
|
-
await fs.writeFile(path.join(configDir, "config.json"), JSON.stringify(config, null, 2), { mode: 384 });
|
|
50
|
-
}
|
|
51
|
-
function getConfigPath() {
|
|
52
|
-
return path.join(configDir, "config.json");
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
//#endregion
|
|
56
17
|
//#region src/utils.ts
|
|
57
18
|
const VERSION = "1.0.0";
|
|
58
19
|
const logger = consola.withTag("gscdump");
|
|
@@ -64,7 +25,7 @@ function handleGscError(error) {
|
|
|
64
25
|
console.error();
|
|
65
26
|
console.error(formatErrorForCli(error));
|
|
66
27
|
console.error();
|
|
67
|
-
process.exit(1);
|
|
28
|
+
process$1.exit(1);
|
|
68
29
|
}
|
|
69
30
|
/**
|
|
70
31
|
* Creates a .catch() handler for GSC API errors.
|
|
@@ -98,7 +59,7 @@ function progressBar(current, total, label, width = 30) {
|
|
|
98
59
|
return ` ${`\x1B[36m${"█".repeat(filled)}\x1B[0m\x1B[90m${"░".repeat(empty)}\x1B[0m`} \x1B[90m${current}/${total}\x1B[0m ${label}`;
|
|
99
60
|
}
|
|
100
61
|
function clearLine() {
|
|
101
|
-
process.stdout.write("\r\x1B[K");
|
|
62
|
+
process$1.stdout.write("\r\x1B[K");
|
|
102
63
|
}
|
|
103
64
|
function toCSV(data, columns) {
|
|
104
65
|
return [columns.join(","), ...data.map((row) => columns.map((col) => {
|
|
@@ -141,20 +102,103 @@ function exportToCSV(output) {
|
|
|
141
102
|
return sections.join("\n\n");
|
|
142
103
|
}
|
|
143
104
|
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/cloud.ts
|
|
107
|
+
async function cloudFetch(url, options) {
|
|
108
|
+
const res = await fetch(url, options);
|
|
109
|
+
if (res.status === 401) {
|
|
110
|
+
logger.error("CLI session expired or revoked. Run gscdump init --force to re-authenticate.");
|
|
111
|
+
process$1.exit(1);
|
|
112
|
+
}
|
|
113
|
+
if (!res.ok) {
|
|
114
|
+
const body = await res.json().catch(() => ({ message: res.statusText }));
|
|
115
|
+
throw new Error(body.message || `HTTP ${res.status}: ${res.statusText}`);
|
|
116
|
+
}
|
|
117
|
+
return res.json();
|
|
118
|
+
}
|
|
119
|
+
function buildUrl(base, path$1, query$1) {
|
|
120
|
+
const url = new URL(path$1, base);
|
|
121
|
+
if (query$1) {
|
|
122
|
+
for (const [k, v] of Object.entries(query$1)) if (v !== void 0 && v !== "") url.searchParams.set(k, v);
|
|
123
|
+
}
|
|
124
|
+
return url.toString();
|
|
125
|
+
}
|
|
126
|
+
function createCloudClient(cloudUrl, sessionId) {
|
|
127
|
+
const headers = {
|
|
128
|
+
"x-cli-session": sessionId,
|
|
129
|
+
"content-type": "application/json"
|
|
130
|
+
};
|
|
131
|
+
return {
|
|
132
|
+
me: () => cloudFetch(buildUrl(cloudUrl, "/api/cli/me"), { headers }),
|
|
133
|
+
availableSites: () => cloudFetch(buildUrl(cloudUrl, "/api/cli/sites/available"), { headers }),
|
|
134
|
+
registerSite: (siteUrl) => cloudFetch(buildUrl(cloudUrl, "/api/sites/register"), {
|
|
135
|
+
method: "POST",
|
|
136
|
+
headers,
|
|
137
|
+
body: JSON.stringify({ siteUrl })
|
|
138
|
+
}),
|
|
139
|
+
syncStatus: (siteId) => cloudFetch(buildUrl(cloudUrl, `/api/sites/${siteId}/sync-status`), { headers }),
|
|
140
|
+
data: (siteId, params) => cloudFetch(buildUrl(cloudUrl, `/api/sites/${siteId}/data`, params), { headers }),
|
|
141
|
+
sitemaps: (siteId) => cloudFetch(buildUrl(cloudUrl, `/api/sites/${siteId}/sitemaps`), { headers }),
|
|
142
|
+
analysis: (siteId, tool, params) => cloudFetch(buildUrl(cloudUrl, `/api/sites/${siteId}/analysis/${tool}`, params), { headers }),
|
|
143
|
+
query: (siteId, params) => cloudFetch(buildUrl(cloudUrl, `/api/sites/${siteId}/query`, params), { headers }),
|
|
144
|
+
indexing: (siteId, params) => cloudFetch(buildUrl(cloudUrl, `/api/sites/${siteId}/indexing`, params), { headers }),
|
|
145
|
+
indexingDiagnostics: (siteId) => cloudFetch(buildUrl(cloudUrl, `/api/sites/${siteId}/indexing/diagnostics`), { headers }),
|
|
146
|
+
indexingUrls: (siteId, params) => cloudFetch(buildUrl(cloudUrl, `/api/sites/${siteId}/indexing/urls`, params), { headers }),
|
|
147
|
+
indexPercent: (siteId, params) => cloudFetch(buildUrl(cloudUrl, `/api/sites/${siteId}/index-percent`, params), { headers }),
|
|
148
|
+
sitemapAction: (siteId, body) => cloudFetch(buildUrl(cloudUrl, `/api/sites/${siteId}/sitemaps`), {
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers,
|
|
151
|
+
body: JSON.stringify(body)
|
|
152
|
+
}),
|
|
153
|
+
triggerSync: (siteId) => cloudFetch(buildUrl(cloudUrl, `/api/sites/${siteId}/sync`), {
|
|
154
|
+
method: "POST",
|
|
155
|
+
headers
|
|
156
|
+
}),
|
|
157
|
+
deleteSite: (siteId) => cloudFetch(buildUrl(cloudUrl, `/api/sites/${siteId}`), {
|
|
158
|
+
method: "DELETE",
|
|
159
|
+
headers
|
|
160
|
+
}),
|
|
161
|
+
bulkRegister: (siteUrls) => cloudFetch(buildUrl(cloudUrl, "/api/sites/bulk-register"), {
|
|
162
|
+
method: "POST",
|
|
163
|
+
headers,
|
|
164
|
+
body: JSON.stringify({ siteUrls })
|
|
165
|
+
}),
|
|
166
|
+
analysisPost: (siteId, body) => cloudFetch(buildUrl(cloudUrl, `/api/cli/sites/${siteId}/analysis`), {
|
|
167
|
+
method: "POST",
|
|
168
|
+
headers,
|
|
169
|
+
body: JSON.stringify(body)
|
|
170
|
+
}),
|
|
171
|
+
detail: (siteId, body) => cloudFetch(buildUrl(cloudUrl, `/api/cli/sites/${siteId}/detail`), {
|
|
172
|
+
method: "POST",
|
|
173
|
+
headers,
|
|
174
|
+
body: JSON.stringify(body)
|
|
175
|
+
})
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
//#endregion
|
|
180
|
+
//#region src/config.ts
|
|
181
|
+
let configDir = path.join(os.homedir(), ".config", "gscdump");
|
|
182
|
+
function getConfigDir() {
|
|
183
|
+
return configDir;
|
|
184
|
+
}
|
|
185
|
+
const DEFAULT_CLOUD_URL = "https://cloud.gscdump.com";
|
|
186
|
+
async function loadConfig() {
|
|
187
|
+
return fs.readFile(path.join(configDir, "config.json"), "utf-8").then((data) => JSON.parse(data)).catch(() => ({}));
|
|
188
|
+
}
|
|
189
|
+
async function saveConfig(config) {
|
|
190
|
+
await fs.mkdir(configDir, {
|
|
191
|
+
recursive: true,
|
|
192
|
+
mode: 448
|
|
193
|
+
});
|
|
194
|
+
await fs.writeFile(path.join(configDir, "config.json"), JSON.stringify(config, null, 2), { mode: 384 });
|
|
195
|
+
}
|
|
196
|
+
function getConfigPath() {
|
|
197
|
+
return path.join(configDir, "config.json");
|
|
198
|
+
}
|
|
199
|
+
|
|
144
200
|
//#endregion
|
|
145
201
|
//#region src/auth.ts
|
|
146
|
-
var auth_exports = /* @__PURE__ */ __exportAll({
|
|
147
|
-
authenticate: () => authenticate,
|
|
148
|
-
authenticateCloud: () => authenticateCloud,
|
|
149
|
-
clearCloudTokens: () => clearCloudTokens,
|
|
150
|
-
clearTokens: () => clearTokens,
|
|
151
|
-
getAuth: () => getAuth,
|
|
152
|
-
getAuthCredentials: () => getAuthCredentials,
|
|
153
|
-
loadCloudTokens: () => loadCloudTokens,
|
|
154
|
-
loadTokens: () => loadTokens,
|
|
155
|
-
saveCloudTokens: () => saveCloudTokens,
|
|
156
|
-
saveTokens: () => saveTokens
|
|
157
|
-
});
|
|
158
202
|
function getTokensPath() {
|
|
159
203
|
return path.join(getConfigDir(), "tokens.json");
|
|
160
204
|
}
|
|
@@ -173,8 +217,8 @@ async function clearTokens() {
|
|
|
173
217
|
logger.success("Logged out, tokens cleared");
|
|
174
218
|
}
|
|
175
219
|
async function getAuthCredentials(interactive) {
|
|
176
|
-
const envClientId = process.env.GOOGLE_CLIENT_ID;
|
|
177
|
-
const envClientSecret = process.env.GOOGLE_CLIENT_SECRET;
|
|
220
|
+
const envClientId = process$1.env.GOOGLE_CLIENT_ID;
|
|
221
|
+
const envClientSecret = process$1.env.GOOGLE_CLIENT_SECRET;
|
|
178
222
|
if (envClientId && envClientSecret) {
|
|
179
223
|
logger.success("Using OAuth2 credentials from environment");
|
|
180
224
|
return {
|
|
@@ -189,7 +233,7 @@ async function getAuthCredentials(interactive) {
|
|
|
189
233
|
};
|
|
190
234
|
if (!interactive) {
|
|
191
235
|
logger.error("GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET required for non-interactive mode");
|
|
192
|
-
process.exit(1);
|
|
236
|
+
process$1.exit(1);
|
|
193
237
|
}
|
|
194
238
|
console.log();
|
|
195
239
|
console.log(" \x1B[1mOAuth 2.0 Setup Required\x1B[0m");
|
|
@@ -206,12 +250,12 @@ async function getAuthCredentials(interactive) {
|
|
|
206
250
|
placeholder: "your-client-id.googleusercontent.com",
|
|
207
251
|
validate: (v) => v ? void 0 : "Required"
|
|
208
252
|
});
|
|
209
|
-
if (isCancel(clientIdResult)) process.exit(1);
|
|
253
|
+
if (isCancel(clientIdResult)) process$1.exit(1);
|
|
210
254
|
const clientSecretResult = await text({
|
|
211
255
|
message: "Enter your Google OAuth Client Secret:",
|
|
212
256
|
validate: (v) => v ? void 0 : "Required"
|
|
213
257
|
});
|
|
214
|
-
if (isCancel(clientSecretResult)) process.exit(1);
|
|
258
|
+
if (isCancel(clientSecretResult)) process$1.exit(1);
|
|
215
259
|
console.log();
|
|
216
260
|
logger.info("Tip: Set GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET env vars to skip prompts");
|
|
217
261
|
return {
|
|
@@ -272,8 +316,8 @@ async function getAuthCodeViaLoopback(authUrl) {
|
|
|
272
316
|
}
|
|
273
317
|
async function authenticate(credentials, interactive) {
|
|
274
318
|
const oauth2Client = new OAuth2Client(credentials.clientId, credentials.clientSecret, "http://127.0.0.1");
|
|
275
|
-
const envAccessToken = process.env.GOOGLE_ACCESS_TOKEN;
|
|
276
|
-
const envRefreshToken = process.env.GOOGLE_REFRESH_TOKEN;
|
|
319
|
+
const envAccessToken = process$1.env.GOOGLE_ACCESS_TOKEN;
|
|
320
|
+
const envRefreshToken = process$1.env.GOOGLE_REFRESH_TOKEN;
|
|
277
321
|
if (envAccessToken || envRefreshToken) {
|
|
278
322
|
oauth2Client.setCredentials({
|
|
279
323
|
access_token: envAccessToken,
|
|
@@ -303,7 +347,7 @@ async function authenticate(credentials, interactive) {
|
|
|
303
347
|
}
|
|
304
348
|
if (!interactive) {
|
|
305
349
|
logger.error("No saved tokens. Run interactively first to authenticate.");
|
|
306
|
-
process.exit(1);
|
|
350
|
+
process$1.exit(1);
|
|
307
351
|
}
|
|
308
352
|
const authUrl = oauth2Client.generateAuthUrl({
|
|
309
353
|
access_type: "offline",
|
|
@@ -349,11 +393,11 @@ async function authenticateCloud(cloudUrl, interactive) {
|
|
|
349
393
|
}
|
|
350
394
|
if (!interactive) {
|
|
351
395
|
logger.error("No cloud tokens. Run gscdump init to authenticate.");
|
|
352
|
-
process.exit(1);
|
|
396
|
+
process$1.exit(1);
|
|
353
397
|
}
|
|
354
398
|
const initRes = await fetch(`${cloudUrl}/api/cli/auth/init`, { method: "POST" }).then((r) => r.json()).catch((e) => {
|
|
355
399
|
logger.error(`Failed to connect to ${cloudUrl}: ${e.message}`);
|
|
356
|
-
process.exit(1);
|
|
400
|
+
process$1.exit(1);
|
|
357
401
|
});
|
|
358
402
|
console.log();
|
|
359
403
|
console.log(" \x1B[1mOpen this URL in your browser:\x1B[0m");
|
|
@@ -368,8 +412,13 @@ async function authenticateCloud(cloudUrl, interactive) {
|
|
|
368
412
|
await new Promise((r) => setTimeout(r, pollInterval));
|
|
369
413
|
const pollRes = await fetch(`${cloudUrl}/api/cli/auth/poll?code=${initRes.code}`).then((r) => r.json()).catch(() => ({ status: "error" }));
|
|
370
414
|
if (pollRes.status === "complete" && pollRes.tokens) {
|
|
371
|
-
await saveCloudTokens(
|
|
372
|
-
|
|
415
|
+
await saveCloudTokens({
|
|
416
|
+
...pollRes.tokens,
|
|
417
|
+
sessionId: pollRes.sessionId,
|
|
418
|
+
user: pollRes.user
|
|
419
|
+
});
|
|
420
|
+
if (pollRes.user?.email) logger.success(`Authenticated as ${pollRes.user.email}`);
|
|
421
|
+
else logger.success("Authenticated via cloud.gscdump.com");
|
|
373
422
|
const oauth2Client = new OAuth2Client();
|
|
374
423
|
oauth2Client.setCredentials({
|
|
375
424
|
access_token: pollRes.tokens.accessToken,
|
|
@@ -380,11 +429,11 @@ async function authenticateCloud(cloudUrl, interactive) {
|
|
|
380
429
|
}
|
|
381
430
|
if (pollRes.status === "error") {
|
|
382
431
|
logger.error("Authorization failed");
|
|
383
|
-
process.exit(1);
|
|
432
|
+
process$1.exit(1);
|
|
384
433
|
}
|
|
385
434
|
}
|
|
386
435
|
logger.error("Authorization timed out");
|
|
387
|
-
process.exit(1);
|
|
436
|
+
process$1.exit(1);
|
|
388
437
|
}
|
|
389
438
|
async function getAuth(opts = {}) {
|
|
390
439
|
const { interactive = true, config: providedConfig } = opts;
|
|
@@ -392,19 +441,269 @@ async function getAuth(opts = {}) {
|
|
|
392
441
|
if (!config.mode) {
|
|
393
442
|
if (!interactive) {
|
|
394
443
|
logger.error("Not configured. Run gscdump init first.");
|
|
395
|
-
process.exit(1);
|
|
444
|
+
process$1.exit(1);
|
|
396
445
|
}
|
|
397
446
|
logger.warn("GSCDump not configured");
|
|
398
447
|
logger.info("Run: gscdump init");
|
|
399
|
-
process.exit(1);
|
|
448
|
+
process$1.exit(1);
|
|
400
449
|
}
|
|
401
450
|
if (config.mode === "cloud") return authenticateCloud(config.cloudUrl || DEFAULT_CLOUD_URL, interactive);
|
|
402
451
|
return authenticate(await getAuthCredentials(interactive), interactive);
|
|
403
452
|
}
|
|
453
|
+
async function getCloudClient() {
|
|
454
|
+
const config = await loadConfig();
|
|
455
|
+
if (config.mode !== "cloud") return null;
|
|
456
|
+
const tokens = await loadCloudTokens();
|
|
457
|
+
if (!tokens?.sessionId) return null;
|
|
458
|
+
return createCloudClient(config.cloudUrl || DEFAULT_CLOUD_URL, tokens.sessionId);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
//#endregion
|
|
462
|
+
//#region src/commands/analysis.ts
|
|
463
|
+
const ANALYSIS_TOOLS = [
|
|
464
|
+
"striking-distance",
|
|
465
|
+
"opportunity",
|
|
466
|
+
"movers",
|
|
467
|
+
"decay",
|
|
468
|
+
"zero-click",
|
|
469
|
+
"brand",
|
|
470
|
+
"cannibalization",
|
|
471
|
+
"clustering",
|
|
472
|
+
"concentration",
|
|
473
|
+
"seasonality"
|
|
474
|
+
];
|
|
475
|
+
async function resolveSiteId(cloud, siteUrl) {
|
|
476
|
+
const config = await loadConfig();
|
|
477
|
+
const target = siteUrl || config.defaultSite;
|
|
478
|
+
const me = await cloud.me().catch((e) => {
|
|
479
|
+
logger.error(`Failed to fetch profile: ${e.message}`);
|
|
480
|
+
process$1.exit(1);
|
|
481
|
+
});
|
|
482
|
+
if (me.sites.length === 0) {
|
|
483
|
+
logger.error("No registered sites. Run gscdump register first.");
|
|
484
|
+
process$1.exit(1);
|
|
485
|
+
}
|
|
486
|
+
const match = target ? me.sites.find((s) => s.siteUrl === target || s.siteUrl.includes(target)) : void 0;
|
|
487
|
+
if (match) return match.siteId;
|
|
488
|
+
if (me.sites.length === 1) return me.sites[0].siteId;
|
|
489
|
+
const selected = await select({
|
|
490
|
+
message: "Select a site",
|
|
491
|
+
options: me.sites.map((s) => ({
|
|
492
|
+
value: s.siteId,
|
|
493
|
+
label: s.siteUrl
|
|
494
|
+
}))
|
|
495
|
+
});
|
|
496
|
+
if (isCancel(selected)) {
|
|
497
|
+
cancel("Cancelled");
|
|
498
|
+
process$1.exit(0);
|
|
499
|
+
}
|
|
500
|
+
return selected;
|
|
501
|
+
}
|
|
502
|
+
function extractResults(data) {
|
|
503
|
+
if (Array.isArray(data.results)) return {
|
|
504
|
+
results: data.results,
|
|
505
|
+
total: data.meta?.total ?? data.results.length
|
|
506
|
+
};
|
|
507
|
+
if (Array.isArray(data.keywords)) return {
|
|
508
|
+
results: data.keywords,
|
|
509
|
+
total: data.totalCount ?? data.keywords.length
|
|
510
|
+
};
|
|
511
|
+
if (Array.isArray(data.clusters)) {
|
|
512
|
+
const clusters = data.clusters;
|
|
513
|
+
return {
|
|
514
|
+
results: clusters,
|
|
515
|
+
total: data.meta?.totalClusters ?? clusters.length
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
if (Array.isArray(data.rising)) {
|
|
519
|
+
const rows = [...data.rising.map((r) => ({
|
|
520
|
+
...r,
|
|
521
|
+
direction: "rising"
|
|
522
|
+
})), ...(data.declining || []).map((r) => ({
|
|
523
|
+
...r,
|
|
524
|
+
direction: "declining"
|
|
525
|
+
}))];
|
|
526
|
+
return {
|
|
527
|
+
results: rows,
|
|
528
|
+
total: rows.length
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
if (Array.isArray(data.brand)) {
|
|
532
|
+
const rows = [...data.brand.map((r) => ({
|
|
533
|
+
...r,
|
|
534
|
+
segment: "brand"
|
|
535
|
+
})), ...(data.nonBrand || []).map((r) => ({
|
|
536
|
+
...r,
|
|
537
|
+
segment: "non-brand"
|
|
538
|
+
}))];
|
|
539
|
+
return {
|
|
540
|
+
results: rows,
|
|
541
|
+
total: rows.length
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
if (Array.isArray(data.monthlyBreakdown)) return {
|
|
545
|
+
results: data.monthlyBreakdown,
|
|
546
|
+
total: data.monthlyBreakdown.length
|
|
547
|
+
};
|
|
548
|
+
if (data.giniCoefficient !== void 0) {
|
|
549
|
+
const { meta: _m, ...rest } = data;
|
|
550
|
+
return {
|
|
551
|
+
results: [rest],
|
|
552
|
+
total: 1
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
return {
|
|
556
|
+
results: [],
|
|
557
|
+
total: 0
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
const TOOL_EXTRA_ARGS = {
|
|
561
|
+
"brand": { "brand-terms": {
|
|
562
|
+
type: "string",
|
|
563
|
+
description: "Comma-separated brand terms (required)"
|
|
564
|
+
} },
|
|
565
|
+
"movers": {
|
|
566
|
+
"prev-start": {
|
|
567
|
+
type: "string",
|
|
568
|
+
description: "Previous period start date (required)"
|
|
569
|
+
},
|
|
570
|
+
"prev-end": {
|
|
571
|
+
type: "string",
|
|
572
|
+
description: "Previous period end date (required)"
|
|
573
|
+
}
|
|
574
|
+
},
|
|
575
|
+
"decay": {
|
|
576
|
+
"prev-start": {
|
|
577
|
+
type: "string",
|
|
578
|
+
description: "Previous period start date (required)"
|
|
579
|
+
},
|
|
580
|
+
"prev-end": {
|
|
581
|
+
type: "string",
|
|
582
|
+
description: "Previous period end date (required)"
|
|
583
|
+
}
|
|
584
|
+
},
|
|
585
|
+
"concentration": { dimension: {
|
|
586
|
+
type: "string",
|
|
587
|
+
description: "Dimension: pages or keywords (default: pages)"
|
|
588
|
+
} },
|
|
589
|
+
"seasonality": { metric: {
|
|
590
|
+
type: "string",
|
|
591
|
+
description: "Metric: clicks or impressions (default: clicks)"
|
|
592
|
+
} },
|
|
593
|
+
"clustering": { "cluster-by": {
|
|
594
|
+
type: "string",
|
|
595
|
+
description: "Cluster by: prefix, intent, or both (default: both)"
|
|
596
|
+
} }
|
|
597
|
+
};
|
|
598
|
+
function buildBody(tool, args) {
|
|
599
|
+
const body = {
|
|
600
|
+
type: tool,
|
|
601
|
+
startDate: args.start ? String(args.start) : void 0,
|
|
602
|
+
endDate: args.end ? String(args.end) : void 0,
|
|
603
|
+
limit: args.limit ? Number(args.limit) : void 0
|
|
604
|
+
};
|
|
605
|
+
if (args["brand-terms"]) body.brandTerms = String(args["brand-terms"]).split(",").map((t) => t.trim()).filter(Boolean);
|
|
606
|
+
if (args["prev-start"]) body.prevStartDate = String(args["prev-start"]);
|
|
607
|
+
if (args["prev-end"]) body.prevEndDate = String(args["prev-end"]);
|
|
608
|
+
if (args.dimension) body.dimension = String(args.dimension);
|
|
609
|
+
if (args.metric) body.metric = String(args.metric);
|
|
610
|
+
if (args["cluster-by"]) body.clusterBy = String(args["cluster-by"]);
|
|
611
|
+
return body;
|
|
612
|
+
}
|
|
613
|
+
function makeToolCommand(tool) {
|
|
614
|
+
const extraArgs = TOOL_EXTRA_ARGS[tool] || {};
|
|
615
|
+
return defineCommand({
|
|
616
|
+
meta: {
|
|
617
|
+
name: tool,
|
|
618
|
+
description: `Run ${tool} analysis`
|
|
619
|
+
},
|
|
620
|
+
args: {
|
|
621
|
+
site: {
|
|
622
|
+
type: "string",
|
|
623
|
+
alias: "s",
|
|
624
|
+
description: "Site URL"
|
|
625
|
+
},
|
|
626
|
+
start: {
|
|
627
|
+
type: "string",
|
|
628
|
+
description: "Start date (YYYY-MM-DD)"
|
|
629
|
+
},
|
|
630
|
+
end: {
|
|
631
|
+
type: "string",
|
|
632
|
+
description: "End date (YYYY-MM-DD)"
|
|
633
|
+
},
|
|
634
|
+
limit: {
|
|
635
|
+
type: "string",
|
|
636
|
+
alias: "l",
|
|
637
|
+
default: "100",
|
|
638
|
+
description: "Max results"
|
|
639
|
+
},
|
|
640
|
+
format: {
|
|
641
|
+
type: "string",
|
|
642
|
+
alias: "f",
|
|
643
|
+
default: "table",
|
|
644
|
+
description: "Output: table, json, csv"
|
|
645
|
+
},
|
|
646
|
+
json: {
|
|
647
|
+
type: "boolean",
|
|
648
|
+
default: false,
|
|
649
|
+
description: "Output as JSON"
|
|
650
|
+
},
|
|
651
|
+
...extraArgs
|
|
652
|
+
},
|
|
653
|
+
async run({ args }) {
|
|
654
|
+
const cloud = await getCloudClient();
|
|
655
|
+
if (!cloud) {
|
|
656
|
+
logger.error("Analysis requires cloud mode. Run gscdump init to set up cloud mode.");
|
|
657
|
+
process$1.exit(1);
|
|
658
|
+
}
|
|
659
|
+
const siteId = await resolveSiteId(cloud, args.site);
|
|
660
|
+
logger.info(`Running ${tool} analysis...`);
|
|
661
|
+
const body = buildBody(tool, args);
|
|
662
|
+
const data = await cloud.analysisPost(siteId, body).catch((e) => {
|
|
663
|
+
logger.error(`Analysis failed: ${e.message}`);
|
|
664
|
+
process$1.exit(1);
|
|
665
|
+
});
|
|
666
|
+
const format = args.json ? "json" : String(args.format);
|
|
667
|
+
if (format === "json") {
|
|
668
|
+
console.log(JSON.stringify(data, null, 2));
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
const { results, total } = extractResults(data);
|
|
672
|
+
if (format === "csv" && results.length > 0) {
|
|
673
|
+
const cols$1 = Object.keys(results[0]);
|
|
674
|
+
console.log(toCSV(results, cols$1));
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
if (results.length === 0) {
|
|
678
|
+
logger.warn("No results found");
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
const cols = Object.keys(results[0]);
|
|
682
|
+
const widths = cols.map((c) => Math.max(c.length, ...results.map((r) => String(r[c] ?? "").length).slice(0, 20)));
|
|
683
|
+
console.log();
|
|
684
|
+
console.log(` ${cols.map((c, i) => c.padEnd(widths[i])).join(" ")}`);
|
|
685
|
+
console.log(` ${cols.map((_, i) => "─".repeat(widths[i])).join(" ")}`);
|
|
686
|
+
for (const row of results) console.log(` ${cols.map((c, i) => {
|
|
687
|
+
const val = row[c];
|
|
688
|
+
return (typeof val === "number" ? Number.isInteger(val) ? String(val) : val.toFixed(2) : String(val ?? "")).padEnd(widths[i]);
|
|
689
|
+
}).join(" ")}`);
|
|
690
|
+
console.log();
|
|
691
|
+
logger.success(`${results.length} results`);
|
|
692
|
+
if (total > results.length) logger.info(`Total: ${total} (showing ${results.length})`);
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
const analysisCommand = defineCommand({
|
|
697
|
+
meta: {
|
|
698
|
+
name: "analysis",
|
|
699
|
+
description: "SEO analysis tools (cloud mode only)"
|
|
700
|
+
},
|
|
701
|
+
subCommands: Object.fromEntries(ANALYSIS_TOOLS.map((tool) => [tool, makeToolCommand(tool)]))
|
|
702
|
+
});
|
|
404
703
|
|
|
405
704
|
//#endregion
|
|
406
705
|
//#region src/commands/auth.ts
|
|
407
|
-
const statusCommand = defineCommand({
|
|
706
|
+
const statusCommand$2 = defineCommand({
|
|
408
707
|
meta: {
|
|
409
708
|
name: "status",
|
|
410
709
|
description: "Show current authentication status"
|
|
@@ -425,12 +724,16 @@ const statusCommand = defineCommand({
|
|
|
425
724
|
logger.info("Run gscdump init --force to re-authenticate");
|
|
426
725
|
return;
|
|
427
726
|
}
|
|
727
|
+
const hasSession = !!tokens.sessionId;
|
|
428
728
|
const hasAccess = !!tokens.accessToken;
|
|
429
729
|
const hasRefresh = !!tokens.refreshToken;
|
|
430
730
|
const expiry = tokens.expiresAt ? new Date(tokens.expiresAt) : null;
|
|
431
731
|
const isExpired = expiry && expiry < /* @__PURE__ */ new Date();
|
|
432
732
|
logger.success("Authenticated");
|
|
433
733
|
console.log();
|
|
734
|
+
if (tokens.user?.email) console.log(` User: \x1B[36m${tokens.user.email}\x1B[0m`);
|
|
735
|
+
if (tokens.user?.publicId) console.log(` User ID: \x1B[90m${tokens.user.publicId}\x1B[0m`);
|
|
736
|
+
console.log(` Session: ${hasSession ? "\x1B[32mactive\x1B[0m" : "\x1B[31mmissing\x1B[0m"}`);
|
|
434
737
|
console.log(` Access token: ${hasAccess ? "\x1B[32mpresent\x1B[0m" : "\x1B[31mmissing\x1B[0m"}`);
|
|
435
738
|
console.log(` Refresh token: ${hasRefresh ? "\x1B[32mpresent\x1B[0m" : "\x1B[31mmissing\x1B[0m"}`);
|
|
436
739
|
if (expiry) {
|
|
@@ -475,7 +778,7 @@ const authCommand = defineCommand({
|
|
|
475
778
|
description: "Manage authentication"
|
|
476
779
|
},
|
|
477
780
|
subCommands: {
|
|
478
|
-
status: statusCommand,
|
|
781
|
+
status: statusCommand$2,
|
|
479
782
|
logout: logoutCommand
|
|
480
783
|
}
|
|
481
784
|
});
|
|
@@ -526,7 +829,7 @@ const setCommand = defineCommand({
|
|
|
526
829
|
if (!validKeys.includes(args.key)) {
|
|
527
830
|
logger.error(`Invalid key: ${args.key}`);
|
|
528
831
|
logger.info(`Valid keys: ${validKeys.join(", ")}`);
|
|
529
|
-
process.exit(1);
|
|
832
|
+
process$1.exit(1);
|
|
530
833
|
}
|
|
531
834
|
const config = await loadConfig();
|
|
532
835
|
config[args.key] = args.value;
|
|
@@ -589,6 +892,44 @@ function getDimensions(dataType) {
|
|
|
589
892
|
case "devices": return [device, date];
|
|
590
893
|
}
|
|
591
894
|
}
|
|
895
|
+
function getDimensionNames(dataType) {
|
|
896
|
+
switch (dataType) {
|
|
897
|
+
case "pages": return "page,date";
|
|
898
|
+
case "keywords": return "query,date";
|
|
899
|
+
case "countries": return "country,date";
|
|
900
|
+
case "devices": return "device,date";
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
async function resolveCloudSite$3(cloud, target) {
|
|
904
|
+
const me = await cloud.me().catch((e) => {
|
|
905
|
+
logger.error(`Failed to fetch sites: ${e.message}`);
|
|
906
|
+
process$1.exit(1);
|
|
907
|
+
});
|
|
908
|
+
if (me.sites.length === 0) {
|
|
909
|
+
logger.error("No registered sites. Run gscdump register first.");
|
|
910
|
+
process$1.exit(1);
|
|
911
|
+
}
|
|
912
|
+
let site = target ? me.sites.find((s) => s.siteUrl === target || s.siteUrl.includes(target)) : void 0;
|
|
913
|
+
if (!site) if (me.sites.length === 1) site = me.sites[0];
|
|
914
|
+
else {
|
|
915
|
+
const selected = await select({
|
|
916
|
+
message: "Select a site",
|
|
917
|
+
options: me.sites.map((s) => ({
|
|
918
|
+
value: s.siteId,
|
|
919
|
+
label: s.siteUrl
|
|
920
|
+
}))
|
|
921
|
+
});
|
|
922
|
+
if (isCancel(selected)) {
|
|
923
|
+
cancel("Cancelled");
|
|
924
|
+
process$1.exit(0);
|
|
925
|
+
}
|
|
926
|
+
site = me.sites.find((s) => s.siteId === selected);
|
|
927
|
+
}
|
|
928
|
+
return {
|
|
929
|
+
siteId: site.siteId,
|
|
930
|
+
siteUrl: site.siteUrl
|
|
931
|
+
};
|
|
932
|
+
}
|
|
592
933
|
const dumpCommand = defineCommand({
|
|
593
934
|
meta: {
|
|
594
935
|
name: "dump",
|
|
@@ -651,32 +992,6 @@ const dumpCommand = defineCommand({
|
|
|
651
992
|
},
|
|
652
993
|
async run({ args }) {
|
|
653
994
|
const config = await loadConfig();
|
|
654
|
-
const { getAuth: getAuth$1 } = await Promise.resolve().then(() => auth_exports);
|
|
655
|
-
const client = googleSearchConsole(await getAuth$1({
|
|
656
|
-
interactive: false,
|
|
657
|
-
config
|
|
658
|
-
}));
|
|
659
|
-
let siteUrl = String(args.site || config.defaultSite || "");
|
|
660
|
-
if (!siteUrl || args.interactive) {
|
|
661
|
-
const verified = (await client.sites()).filter((s) => s.permissionLevel !== "siteUnverifiedUser");
|
|
662
|
-
if (verified.length === 0) {
|
|
663
|
-
logger.error("No verified sites found");
|
|
664
|
-
process.exit(1);
|
|
665
|
-
}
|
|
666
|
-
const selected = await select({
|
|
667
|
-
message: "Select a site",
|
|
668
|
-
options: verified.map((s) => ({
|
|
669
|
-
value: s.siteUrl,
|
|
670
|
-
label: s.siteUrl
|
|
671
|
-
})),
|
|
672
|
-
initialValue: siteUrl || verified[0]?.siteUrl
|
|
673
|
-
});
|
|
674
|
-
if (isCancel(selected)) {
|
|
675
|
-
cancel("Cancelled");
|
|
676
|
-
process.exit(0);
|
|
677
|
-
}
|
|
678
|
-
siteUrl = selected;
|
|
679
|
-
}
|
|
680
995
|
let startDate;
|
|
681
996
|
let endDate;
|
|
682
997
|
if (args.start && args.end) {
|
|
@@ -689,7 +1004,7 @@ const dumpCommand = defineCommand({
|
|
|
689
1004
|
});
|
|
690
1005
|
if (isCancel(startInput)) {
|
|
691
1006
|
cancel("Cancelled");
|
|
692
|
-
process.exit(0);
|
|
1007
|
+
process$1.exit(0);
|
|
693
1008
|
}
|
|
694
1009
|
const endInput = await text({
|
|
695
1010
|
message: "End date (YYYY-MM-DD)",
|
|
@@ -697,7 +1012,7 @@ const dumpCommand = defineCommand({
|
|
|
697
1012
|
});
|
|
698
1013
|
if (isCancel(endInput)) {
|
|
699
1014
|
cancel("Cancelled");
|
|
700
|
-
process.exit(0);
|
|
1015
|
+
process$1.exit(0);
|
|
701
1016
|
}
|
|
702
1017
|
startDate = String(startInput) || (/* @__PURE__ */ new Date(Date.now() - Number(args.days) * 864e5)).toISOString().split("T")[0];
|
|
703
1018
|
endDate = String(endInput) || (/* @__PURE__ */ new Date(Date.now() - 3 * 864e5)).toISOString().split("T")[0];
|
|
@@ -719,12 +1034,82 @@ const dumpCommand = defineCommand({
|
|
|
719
1034
|
});
|
|
720
1035
|
if (isCancel(selected)) {
|
|
721
1036
|
cancel("Cancelled");
|
|
722
|
-
process.exit(0);
|
|
1037
|
+
process$1.exit(0);
|
|
723
1038
|
}
|
|
724
1039
|
dataTypes = selected;
|
|
725
1040
|
} else dataTypes = ["pages", "keywords"];
|
|
726
1041
|
const rowLimit = Number.parseInt(String(args.limit), 10);
|
|
727
1042
|
const format = String(args.format);
|
|
1043
|
+
const cloud = await getCloudClient();
|
|
1044
|
+
if (cloud) {
|
|
1045
|
+
const { siteId, siteUrl: siteUrl$1 } = await resolveCloudSite$3(cloud, args.site || config.defaultSite);
|
|
1046
|
+
const output$1 = {
|
|
1047
|
+
siteUrl: siteUrl$1,
|
|
1048
|
+
dateRange: {
|
|
1049
|
+
start: startDate,
|
|
1050
|
+
end: endDate
|
|
1051
|
+
},
|
|
1052
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1053
|
+
};
|
|
1054
|
+
const totalSteps$1 = dataTypes.length;
|
|
1055
|
+
let currentStep$1 = 0;
|
|
1056
|
+
for (const dataType of dataTypes) {
|
|
1057
|
+
currentStep$1++;
|
|
1058
|
+
if (!args.quiet) {
|
|
1059
|
+
clearLine();
|
|
1060
|
+
process$1.stdout.write(progressBar(currentStep$1, totalSteps$1, dataType));
|
|
1061
|
+
}
|
|
1062
|
+
const dimensions = getDimensionNames(dataType);
|
|
1063
|
+
const result = await cloud.query(siteId, {
|
|
1064
|
+
startDate,
|
|
1065
|
+
endDate,
|
|
1066
|
+
dimensions,
|
|
1067
|
+
rowLimit: String(rowLimit)
|
|
1068
|
+
}).catch((e) => {
|
|
1069
|
+
logger.error(`Query failed: ${e.message}`);
|
|
1070
|
+
process$1.exit(1);
|
|
1071
|
+
});
|
|
1072
|
+
output$1[dataType] = {
|
|
1073
|
+
total: result.rows.length,
|
|
1074
|
+
data: result.rows
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
if (!args.quiet) {
|
|
1078
|
+
clearLine();
|
|
1079
|
+
logger.success(`Exported ${dataTypes.join(", ")} for ${siteUrl$1}`);
|
|
1080
|
+
}
|
|
1081
|
+
const content$1 = format === "csv" ? exportToCSV(output$1) : JSON.stringify(output$1, null, 2);
|
|
1082
|
+
if (args.output) {
|
|
1083
|
+
await fs.writeFile(String(args.output), content$1);
|
|
1084
|
+
if (!args.quiet) logger.info(`Written to ${args.output}`);
|
|
1085
|
+
} else console.log(content$1);
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
const client = googleSearchConsole(await getAuth({
|
|
1089
|
+
interactive: false,
|
|
1090
|
+
config
|
|
1091
|
+
}));
|
|
1092
|
+
let siteUrl = String(args.site || config.defaultSite || "");
|
|
1093
|
+
if (!siteUrl || args.interactive) {
|
|
1094
|
+
const verified = (await client.sites()).filter((s) => s.permissionLevel !== "siteUnverifiedUser");
|
|
1095
|
+
if (verified.length === 0) {
|
|
1096
|
+
logger.error("No verified sites found");
|
|
1097
|
+
process$1.exit(1);
|
|
1098
|
+
}
|
|
1099
|
+
const selected = await select({
|
|
1100
|
+
message: "Select a site",
|
|
1101
|
+
options: verified.map((s) => ({
|
|
1102
|
+
value: s.siteUrl,
|
|
1103
|
+
label: s.siteUrl
|
|
1104
|
+
})),
|
|
1105
|
+
initialValue: siteUrl || verified[0]?.siteUrl
|
|
1106
|
+
});
|
|
1107
|
+
if (isCancel(selected)) {
|
|
1108
|
+
cancel("Cancelled");
|
|
1109
|
+
process$1.exit(0);
|
|
1110
|
+
}
|
|
1111
|
+
siteUrl = selected;
|
|
1112
|
+
}
|
|
728
1113
|
const output = {
|
|
729
1114
|
siteUrl,
|
|
730
1115
|
dateRange: {
|
|
@@ -739,7 +1124,7 @@ const dumpCommand = defineCommand({
|
|
|
739
1124
|
currentStep++;
|
|
740
1125
|
if (!args.quiet) {
|
|
741
1126
|
clearLine();
|
|
742
|
-
process.stdout.write(progressBar(currentStep, totalSteps, dataType));
|
|
1127
|
+
process$1.stdout.write(progressBar(currentStep, totalSteps, dataType));
|
|
743
1128
|
}
|
|
744
1129
|
const dimensions = getDimensions(dataType);
|
|
745
1130
|
const builder = gsc.select(...dimensions).where(between(date, startDate, endDate)).limit(rowLimit);
|
|
@@ -750,22 +1135,383 @@ const dumpCommand = defineCommand({
|
|
|
750
1135
|
data: rows
|
|
751
1136
|
};
|
|
752
1137
|
}
|
|
753
|
-
if (!args.quiet) {
|
|
754
|
-
clearLine();
|
|
755
|
-
logger.success(`Exported ${dataTypes.join(", ")} for ${siteUrl}`);
|
|
1138
|
+
if (!args.quiet) {
|
|
1139
|
+
clearLine();
|
|
1140
|
+
logger.success(`Exported ${dataTypes.join(", ")} for ${siteUrl}`);
|
|
1141
|
+
}
|
|
1142
|
+
const content = format === "csv" ? exportToCSV(output) : JSON.stringify(output, null, 2);
|
|
1143
|
+
if (args.output) {
|
|
1144
|
+
await fs.writeFile(String(args.output), content);
|
|
1145
|
+
if (!args.quiet) logger.info(`Written to ${args.output}`);
|
|
1146
|
+
} else console.log(content);
|
|
1147
|
+
}
|
|
1148
|
+
});
|
|
1149
|
+
|
|
1150
|
+
//#endregion
|
|
1151
|
+
//#region src/commands/indexing.ts
|
|
1152
|
+
async function resolveCloudSite$2(cloud, target) {
|
|
1153
|
+
const me = await cloud.me().catch((e) => {
|
|
1154
|
+
logger.error(`Failed to fetch sites: ${e.message}`);
|
|
1155
|
+
process$1.exit(1);
|
|
1156
|
+
});
|
|
1157
|
+
if (me.sites.length === 0) {
|
|
1158
|
+
logger.error("No registered sites. Run gscdump register first.");
|
|
1159
|
+
process$1.exit(1);
|
|
1160
|
+
}
|
|
1161
|
+
let site = target ? me.sites.find((s) => s.siteUrl === target || s.siteUrl.includes(target)) : void 0;
|
|
1162
|
+
if (!site) if (me.sites.length === 1) site = me.sites[0];
|
|
1163
|
+
else {
|
|
1164
|
+
const selected = await select({
|
|
1165
|
+
message: "Select a site",
|
|
1166
|
+
options: me.sites.map((s) => ({
|
|
1167
|
+
value: s.siteId,
|
|
1168
|
+
label: s.siteUrl
|
|
1169
|
+
}))
|
|
1170
|
+
});
|
|
1171
|
+
if (isCancel(selected)) {
|
|
1172
|
+
cancel("Cancelled");
|
|
1173
|
+
process$1.exit(0);
|
|
1174
|
+
}
|
|
1175
|
+
site = me.sites.find((s) => s.siteId === selected);
|
|
1176
|
+
}
|
|
1177
|
+
return {
|
|
1178
|
+
siteId: site.siteId,
|
|
1179
|
+
siteUrl: site.siteUrl
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
const statusCommand$1 = defineCommand({
|
|
1183
|
+
meta: {
|
|
1184
|
+
name: "status",
|
|
1185
|
+
description: "Show indexing status overview (cloud mode)"
|
|
1186
|
+
},
|
|
1187
|
+
args: {
|
|
1188
|
+
site: {
|
|
1189
|
+
type: "string",
|
|
1190
|
+
alias: "s",
|
|
1191
|
+
description: "Site URL"
|
|
1192
|
+
},
|
|
1193
|
+
days: {
|
|
1194
|
+
type: "string",
|
|
1195
|
+
alias: "d",
|
|
1196
|
+
default: "28",
|
|
1197
|
+
description: "Days of trend data (max 90)"
|
|
1198
|
+
},
|
|
1199
|
+
json: {
|
|
1200
|
+
type: "boolean",
|
|
1201
|
+
default: false,
|
|
1202
|
+
description: "Output as JSON"
|
|
1203
|
+
}
|
|
1204
|
+
},
|
|
1205
|
+
async run({ args }) {
|
|
1206
|
+
const cloud = await getCloudClient();
|
|
1207
|
+
if (!cloud) {
|
|
1208
|
+
logger.error("Indexing status requires cloud mode. Run gscdump init to set up.");
|
|
1209
|
+
process$1.exit(1);
|
|
1210
|
+
}
|
|
1211
|
+
const config = await loadConfig();
|
|
1212
|
+
const { siteId, siteUrl } = await resolveCloudSite$2(cloud, args.site || config.defaultSite);
|
|
1213
|
+
const data = await cloud.indexing(siteId, { days: String(args.days) }).catch((e) => {
|
|
1214
|
+
logger.error(`Failed to fetch indexing data: ${e.message}`);
|
|
1215
|
+
process$1.exit(1);
|
|
1216
|
+
});
|
|
1217
|
+
if (args.json) {
|
|
1218
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
const s = data.summary;
|
|
1222
|
+
console.log();
|
|
1223
|
+
console.log(` \x1B[1m${siteUrl}\x1B[0m — Indexing Status`);
|
|
1224
|
+
console.log();
|
|
1225
|
+
console.log(` Total URLs: \x1B[36m${s.totalUrls.toLocaleString()}\x1B[0m`);
|
|
1226
|
+
console.log(` Indexed: \x1B[32m${s.indexed.toLocaleString()}\x1B[0m (${s.indexedPercent}%)`);
|
|
1227
|
+
console.log(` Not Indexed: \x1B[31m${s.notIndexed.toLocaleString()}\x1B[0m`);
|
|
1228
|
+
if (s.pending > 0) console.log(` Pending: \x1B[33m${s.pending.toLocaleString()}\x1B[0m`);
|
|
1229
|
+
if (s.change7d !== null || s.change28d !== null) {
|
|
1230
|
+
console.log();
|
|
1231
|
+
if (s.change7d !== null) {
|
|
1232
|
+
const color = s.change7d > 0 ? "\x1B[32m+" : s.change7d < 0 ? "\x1B[31m" : "\x1B[90m";
|
|
1233
|
+
console.log(` 7d change: ${color}${s.change7d}%\x1B[0m`);
|
|
1234
|
+
}
|
|
1235
|
+
if (s.change28d !== null) {
|
|
1236
|
+
const color = s.change28d > 0 ? "\x1B[32m+" : s.change28d < 0 ? "\x1B[31m" : "\x1B[90m";
|
|
1237
|
+
console.log(` 28d change: ${color}${s.change28d}%\x1B[0m`);
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
if (data.trend.length > 1) {
|
|
1241
|
+
console.log();
|
|
1242
|
+
console.log(" \x1B[1mTrend (indexed %)\x1B[0m");
|
|
1243
|
+
const recent = data.trend.slice(-14);
|
|
1244
|
+
for (const t of recent) {
|
|
1245
|
+
const bar = "█".repeat(Math.round(t.indexedPercent / 5));
|
|
1246
|
+
console.log(` ${t.date} \x1B[36m${bar}\x1B[0m ${t.indexedPercent}%`);
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
console.log();
|
|
1250
|
+
}
|
|
1251
|
+
});
|
|
1252
|
+
const diagnosticsCommand = defineCommand({
|
|
1253
|
+
meta: {
|
|
1254
|
+
name: "diagnostics",
|
|
1255
|
+
description: "Show indexing issue diagnostics (cloud mode)"
|
|
1256
|
+
},
|
|
1257
|
+
args: {
|
|
1258
|
+
site: {
|
|
1259
|
+
type: "string",
|
|
1260
|
+
alias: "s",
|
|
1261
|
+
description: "Site URL"
|
|
1262
|
+
},
|
|
1263
|
+
json: {
|
|
1264
|
+
type: "boolean",
|
|
1265
|
+
default: false,
|
|
1266
|
+
description: "Output as JSON"
|
|
1267
|
+
}
|
|
1268
|
+
},
|
|
1269
|
+
async run({ args }) {
|
|
1270
|
+
const cloud = await getCloudClient();
|
|
1271
|
+
if (!cloud) {
|
|
1272
|
+
logger.error("Indexing diagnostics requires cloud mode. Run gscdump init to set up.");
|
|
1273
|
+
process$1.exit(1);
|
|
1274
|
+
}
|
|
1275
|
+
const config = await loadConfig();
|
|
1276
|
+
const { siteId, siteUrl } = await resolveCloudSite$2(cloud, args.site || config.defaultSite);
|
|
1277
|
+
const data = await cloud.indexingDiagnostics(siteId).catch((e) => {
|
|
1278
|
+
logger.error(`Failed to fetch diagnostics: ${e.message}`);
|
|
1279
|
+
process$1.exit(1);
|
|
1280
|
+
});
|
|
1281
|
+
if (args.json) {
|
|
1282
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
console.log();
|
|
1286
|
+
console.log(` \x1B[1m${siteUrl}\x1B[0m — Indexing Diagnostics`);
|
|
1287
|
+
console.log();
|
|
1288
|
+
console.log(` Total: \x1B[36m${data.summary.totalUrls.toLocaleString()}\x1B[0m URLs, \x1B[32m${data.summary.indexed.toLocaleString()}\x1B[0m indexed (${data.summary.indexedPercent}%)`);
|
|
1289
|
+
console.log();
|
|
1290
|
+
if (data.issues.length === 0) {
|
|
1291
|
+
logger.success("No indexing issues found!");
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
console.log(" \x1B[1mIssues\x1B[0m");
|
|
1295
|
+
for (const issue of data.issues) {
|
|
1296
|
+
const color = issue.severity === "error" ? "\x1B[31m" : issue.severity === "warning" ? "\x1B[33m" : "\x1B[90m";
|
|
1297
|
+
console.log(` ${color}${issue.severity.toUpperCase().padEnd(7)}\x1B[0m ${issue.label} — \x1B[36m${issue.count.toLocaleString()}\x1B[0m URLs`);
|
|
1298
|
+
}
|
|
1299
|
+
console.log();
|
|
1300
|
+
}
|
|
1301
|
+
});
|
|
1302
|
+
const urlsCommand = defineCommand({
|
|
1303
|
+
meta: {
|
|
1304
|
+
name: "urls",
|
|
1305
|
+
description: "List URLs with indexing status (cloud mode)"
|
|
1306
|
+
},
|
|
1307
|
+
args: {
|
|
1308
|
+
site: {
|
|
1309
|
+
type: "string",
|
|
1310
|
+
alias: "s",
|
|
1311
|
+
description: "Site URL"
|
|
1312
|
+
},
|
|
1313
|
+
status: {
|
|
1314
|
+
type: "string",
|
|
1315
|
+
description: "Filter: indexed, not_indexed, pending"
|
|
1316
|
+
},
|
|
1317
|
+
issue: {
|
|
1318
|
+
type: "string",
|
|
1319
|
+
description: "Filter by issue type"
|
|
1320
|
+
},
|
|
1321
|
+
search: {
|
|
1322
|
+
type: "string",
|
|
1323
|
+
description: "Search URLs"
|
|
1324
|
+
},
|
|
1325
|
+
limit: {
|
|
1326
|
+
type: "string",
|
|
1327
|
+
alias: "l",
|
|
1328
|
+
default: "50",
|
|
1329
|
+
description: "Max results"
|
|
1330
|
+
},
|
|
1331
|
+
offset: {
|
|
1332
|
+
type: "string",
|
|
1333
|
+
default: "0",
|
|
1334
|
+
description: "Offset for pagination"
|
|
1335
|
+
},
|
|
1336
|
+
json: {
|
|
1337
|
+
type: "boolean",
|
|
1338
|
+
default: false,
|
|
1339
|
+
description: "Output as JSON"
|
|
1340
|
+
}
|
|
1341
|
+
},
|
|
1342
|
+
async run({ args }) {
|
|
1343
|
+
const cloud = await getCloudClient();
|
|
1344
|
+
if (!cloud) {
|
|
1345
|
+
logger.error("Indexing URLs requires cloud mode. Run gscdump init to set up.");
|
|
1346
|
+
process$1.exit(1);
|
|
1347
|
+
}
|
|
1348
|
+
const config = await loadConfig();
|
|
1349
|
+
const { siteId, siteUrl } = await resolveCloudSite$2(cloud, args.site || config.defaultSite);
|
|
1350
|
+
const params = {
|
|
1351
|
+
limit: String(args.limit),
|
|
1352
|
+
offset: String(args.offset)
|
|
1353
|
+
};
|
|
1354
|
+
if (args.status) params.status = String(args.status);
|
|
1355
|
+
if (args.issue) params.issue = String(args.issue);
|
|
1356
|
+
if (args.search) params.search = String(args.search);
|
|
1357
|
+
const data = await cloud.indexingUrls(siteId, params).catch((e) => {
|
|
1358
|
+
logger.error(`Failed to fetch URLs: ${e.message}`);
|
|
1359
|
+
process$1.exit(1);
|
|
1360
|
+
});
|
|
1361
|
+
if (args.json) {
|
|
1362
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
console.log();
|
|
1366
|
+
console.log(` \x1B[1m${siteUrl}\x1B[0m — ${data.pagination.total.toLocaleString()} URLs (showing ${data.urls.length})`);
|
|
1367
|
+
console.log();
|
|
1368
|
+
for (const url of data.urls) {
|
|
1369
|
+
const verdictColor = url.verdict === "PASS" ? "\x1B[32m" : url.verdict ? "\x1B[31m" : "\x1B[33m";
|
|
1370
|
+
const verdictLabel = url.verdict === "PASS" ? "INDEXED" : url.verdict ? "NOT INDEXED" : "PENDING";
|
|
1371
|
+
console.log(` ${verdictColor}${verdictLabel.padEnd(12)}\x1B[0m ${url.url}`);
|
|
1372
|
+
if (url.coverageState && url.coverageState !== "Submitted and indexed") console.log(` \x1B[90m${url.coverageState}\x1B[0m`);
|
|
1373
|
+
}
|
|
1374
|
+
if (data.pagination.hasMore) console.log(`\n \x1B[90m... ${data.pagination.total - data.pagination.offset - data.urls.length} more (use --offset ${data.pagination.offset + data.urls.length})\x1B[0m`);
|
|
1375
|
+
console.log();
|
|
1376
|
+
}
|
|
1377
|
+
});
|
|
1378
|
+
const inspectCommand = defineCommand({
|
|
1379
|
+
meta: {
|
|
1380
|
+
name: "inspect",
|
|
1381
|
+
description: "Inspect a specific URL's indexing status (local mode)"
|
|
1382
|
+
},
|
|
1383
|
+
args: {
|
|
1384
|
+
site: {
|
|
1385
|
+
type: "string",
|
|
1386
|
+
alias: "s",
|
|
1387
|
+
required: true,
|
|
1388
|
+
description: "Site URL (e.g., sc-domain:example.com)"
|
|
1389
|
+
},
|
|
1390
|
+
url: {
|
|
1391
|
+
type: "positional",
|
|
1392
|
+
required: true,
|
|
1393
|
+
description: "URL to inspect"
|
|
1394
|
+
},
|
|
1395
|
+
json: {
|
|
1396
|
+
type: "boolean",
|
|
1397
|
+
default: false,
|
|
1398
|
+
description: "Output as JSON"
|
|
1399
|
+
}
|
|
1400
|
+
},
|
|
1401
|
+
async run({ args }) {
|
|
1402
|
+
const result = await inspectUrl(googleSearchConsole(await getAuth({ interactive: false })), args.site, args.url).catch((e) => {
|
|
1403
|
+
logger.error(`Inspection failed: ${e.message}`);
|
|
1404
|
+
process$1.exit(1);
|
|
1405
|
+
});
|
|
1406
|
+
if (args.json) {
|
|
1407
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1408
|
+
return;
|
|
1409
|
+
}
|
|
1410
|
+
const indexStatus = result.inspectionResult?.indexStatusResult;
|
|
1411
|
+
console.log();
|
|
1412
|
+
console.log(` \x1B[1mURL:\x1B[0m ${args.url}`);
|
|
1413
|
+
console.log();
|
|
1414
|
+
if (indexStatus) {
|
|
1415
|
+
const verdict = indexStatus.verdict;
|
|
1416
|
+
const verdictColor = verdict === "PASS" ? "\x1B[32m" : "\x1B[31m";
|
|
1417
|
+
console.log(` Verdict: ${verdictColor}${verdict}\x1B[0m`);
|
|
1418
|
+
if (indexStatus.coverageState) console.log(` Coverage: ${indexStatus.coverageState}`);
|
|
1419
|
+
if (indexStatus.robotsTxtState) console.log(` Robots.txt: ${indexStatus.robotsTxtState}`);
|
|
1420
|
+
if (indexStatus.indexingState) console.log(` Indexing: ${indexStatus.indexingState}`);
|
|
1421
|
+
if (indexStatus.lastCrawlTime) console.log(` Last Crawl: ${indexStatus.lastCrawlTime}`);
|
|
1422
|
+
if (indexStatus.pageFetchState) console.log(` Page Fetch: ${indexStatus.pageFetchState}`);
|
|
1423
|
+
if (indexStatus.googleCanonical) console.log(` Google Canon: ${indexStatus.googleCanonical}`);
|
|
1424
|
+
if (indexStatus.userCanonical) console.log(` User Canon: ${indexStatus.userCanonical}`);
|
|
1425
|
+
} else console.log(JSON.stringify(result, null, 2));
|
|
1426
|
+
console.log();
|
|
1427
|
+
}
|
|
1428
|
+
});
|
|
1429
|
+
const indexPercentCommand = defineCommand({
|
|
1430
|
+
meta: {
|
|
1431
|
+
name: "index-percent",
|
|
1432
|
+
description: "Show index percent, invisible URLs, and orphan pages (cloud mode)"
|
|
1433
|
+
},
|
|
1434
|
+
args: {
|
|
1435
|
+
site: {
|
|
1436
|
+
type: "string",
|
|
1437
|
+
alias: "s",
|
|
1438
|
+
description: "Site URL"
|
|
1439
|
+
},
|
|
1440
|
+
json: {
|
|
1441
|
+
type: "boolean",
|
|
1442
|
+
default: false,
|
|
1443
|
+
description: "Output as JSON"
|
|
1444
|
+
}
|
|
1445
|
+
},
|
|
1446
|
+
async run({ args }) {
|
|
1447
|
+
const cloud = await getCloudClient();
|
|
1448
|
+
if (!cloud) {
|
|
1449
|
+
logger.error("Index percent requires cloud mode. Run gscdump init to set up.");
|
|
1450
|
+
process$1.exit(1);
|
|
1451
|
+
}
|
|
1452
|
+
const config = await loadConfig();
|
|
1453
|
+
const { siteId, siteUrl } = await resolveCloudSite$2(cloud, args.site || config.defaultSite);
|
|
1454
|
+
const data = await cloud.indexPercent(siteId).catch((e) => {
|
|
1455
|
+
logger.error(`Failed to fetch index percent: ${e.message}`);
|
|
1456
|
+
process$1.exit(1);
|
|
1457
|
+
});
|
|
1458
|
+
if (args.json) {
|
|
1459
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
const s = data.summary;
|
|
1463
|
+
console.log();
|
|
1464
|
+
console.log(` \x1B[1m${siteUrl}\x1B[0m — Index Percent`);
|
|
1465
|
+
console.log();
|
|
1466
|
+
console.log(` Index Percent: \x1B[36m${s.currentPercent}%\x1B[0m`);
|
|
1467
|
+
console.log(` Sitemap URLs: ${s.totalSitemapUrls.toLocaleString()}`);
|
|
1468
|
+
console.log(` Visible in Search: \x1B[32m${s.visibleUrls.toLocaleString()}\x1B[0m`);
|
|
1469
|
+
if (s.change7d !== null) {
|
|
1470
|
+
const color = s.change7d > 0 ? "\x1B[32m+" : s.change7d < 0 ? "\x1B[31m" : "\x1B[90m";
|
|
1471
|
+
console.log(` 7d change: ${color}${s.change7d.toFixed(1)}%\x1B[0m`);
|
|
1472
|
+
}
|
|
1473
|
+
if (s.change28d !== null) {
|
|
1474
|
+
const color = s.change28d > 0 ? "\x1B[32m+" : s.change28d < 0 ? "\x1B[31m" : "\x1B[90m";
|
|
1475
|
+
console.log(` 28d change: ${color}${s.change28d.toFixed(1)}%\x1B[0m`);
|
|
1476
|
+
}
|
|
1477
|
+
if (data.invisibleCount > 0) {
|
|
1478
|
+
console.log();
|
|
1479
|
+
console.log(` \x1B[1mInvisible URLs\x1B[0m (\x1B[33m${data.invisibleCount}\x1B[0m — in sitemap but no search traffic)`);
|
|
1480
|
+
for (const u of data.invisibleUrls.slice(0, 10)) console.log(` ${u.url}`);
|
|
1481
|
+
if (data.invisibleCount > 10) console.log(` \x1B[90m... and ${data.invisibleCount - 10} more\x1B[0m`);
|
|
1482
|
+
}
|
|
1483
|
+
if (data.orphanCount > 0) {
|
|
1484
|
+
console.log();
|
|
1485
|
+
console.log(` \x1B[1mOrphan Pages\x1B[0m (\x1B[33m${data.orphanCount}\x1B[0m — has traffic but not in sitemap)`);
|
|
1486
|
+
for (const u of data.orphanPages.slice(0, 10)) console.log(` ${u.url} \x1B[90m(${u.clicks} clicks)\x1B[0m`);
|
|
1487
|
+
if (data.orphanCount > 10) console.log(` \x1B[90m... and ${data.orphanCount - 10} more\x1B[0m`);
|
|
1488
|
+
}
|
|
1489
|
+
if (data.sitemaps.length > 0) {
|
|
1490
|
+
console.log();
|
|
1491
|
+
console.log(" \x1B[1mSitemaps\x1B[0m");
|
|
1492
|
+
for (const sm of data.sitemaps) console.log(` ${sm.path} \x1B[90m(${sm.urlCount.toLocaleString()} URLs)\x1B[0m`);
|
|
756
1493
|
}
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
1494
|
+
console.log();
|
|
1495
|
+
}
|
|
1496
|
+
});
|
|
1497
|
+
const indexingCommand = defineCommand({
|
|
1498
|
+
meta: {
|
|
1499
|
+
name: "indexing",
|
|
1500
|
+
description: "Indexing status, diagnostics, and URL inspection"
|
|
1501
|
+
},
|
|
1502
|
+
subCommands: {
|
|
1503
|
+
"status": statusCommand$1,
|
|
1504
|
+
"diagnostics": diagnosticsCommand,
|
|
1505
|
+
"urls": urlsCommand,
|
|
1506
|
+
"inspect": inspectCommand,
|
|
1507
|
+
"index-percent": indexPercentCommand
|
|
762
1508
|
}
|
|
763
1509
|
});
|
|
764
1510
|
|
|
765
1511
|
//#endregion
|
|
766
1512
|
//#region src/commands/init.ts
|
|
767
1513
|
async function loadEnvFile() {
|
|
768
|
-
const envPath = path.join(process.cwd(), ".env");
|
|
1514
|
+
const envPath = path.join(process$1.cwd(), ".env");
|
|
769
1515
|
const content = await fs.readFile(envPath, "utf-8").catch(() => null);
|
|
770
1516
|
if (!content) return null;
|
|
771
1517
|
const env = {};
|
|
@@ -802,10 +1548,10 @@ const initCommand = defineCommand({
|
|
|
802
1548
|
const envFile = await loadEnvFile();
|
|
803
1549
|
if (envFile?.GOOGLE_CLIENT_ID && envFile?.GOOGLE_CLIENT_SECRET && envFile?.GOOGLE_REFRESH_TOKEN) {
|
|
804
1550
|
logger.info("Found .env file with Google credentials");
|
|
805
|
-
process.env.GOOGLE_CLIENT_ID = envFile.GOOGLE_CLIENT_ID;
|
|
806
|
-
process.env.GOOGLE_CLIENT_SECRET = envFile.GOOGLE_CLIENT_SECRET;
|
|
807
|
-
process.env.GOOGLE_REFRESH_TOKEN = envFile.GOOGLE_REFRESH_TOKEN;
|
|
808
|
-
if (envFile.GOOGLE_ACCESS_TOKEN) process.env.GOOGLE_ACCESS_TOKEN = envFile.GOOGLE_ACCESS_TOKEN;
|
|
1551
|
+
process$1.env.GOOGLE_CLIENT_ID = envFile.GOOGLE_CLIENT_ID;
|
|
1552
|
+
process$1.env.GOOGLE_CLIENT_SECRET = envFile.GOOGLE_CLIENT_SECRET;
|
|
1553
|
+
process$1.env.GOOGLE_REFRESH_TOKEN = envFile.GOOGLE_REFRESH_TOKEN;
|
|
1554
|
+
if (envFile.GOOGLE_ACCESS_TOKEN) process$1.env.GOOGLE_ACCESS_TOKEN = envFile.GOOGLE_ACCESS_TOKEN;
|
|
809
1555
|
await saveConfig({
|
|
810
1556
|
...config,
|
|
811
1557
|
mode: "local",
|
|
@@ -841,7 +1587,7 @@ const initCommand = defineCommand({
|
|
|
841
1587
|
hint: "Use your own Google OAuth credentials"
|
|
842
1588
|
}]
|
|
843
1589
|
});
|
|
844
|
-
if (isCancel(mode)) process.exit(1);
|
|
1590
|
+
if (isCancel(mode)) process$1.exit(1);
|
|
845
1591
|
if (mode === "cloud") {
|
|
846
1592
|
const cloudUrl = config.cloudUrl || DEFAULT_CLOUD_URL;
|
|
847
1593
|
await saveConfig({
|
|
@@ -1063,7 +1809,7 @@ const batchInspectUrlsInput = z.object({
|
|
|
1063
1809
|
//#endregion
|
|
1064
1810
|
//#region src/mcp/server/index.ts
|
|
1065
1811
|
function createGscMcpServer(options) {
|
|
1066
|
-
const { name = "gscdump", version = "1.0.0", getAuth: getAuth$1 } = options;
|
|
1812
|
+
const { name = "gscdump", version = "1.0.0", getAuth: getAuth$1, cloudClient } = options;
|
|
1067
1813
|
const server = new McpServer({
|
|
1068
1814
|
name,
|
|
1069
1815
|
version
|
|
@@ -1236,13 +1982,212 @@ function createGscMcpServer(options) {
|
|
|
1236
1982
|
text: JSON.stringify(result, null, 2)
|
|
1237
1983
|
}] };
|
|
1238
1984
|
});
|
|
1985
|
+
if (cloudClient) {
|
|
1986
|
+
const siteIdSchema = z.object({ siteId: z.string().describe("Site ID from gscdump platform (use cloud-list-sites to find)") });
|
|
1987
|
+
const analysisSchema = z.object({
|
|
1988
|
+
siteId: z.string().describe("Site ID from gscdump platform"),
|
|
1989
|
+
tool: z.enum([
|
|
1990
|
+
"striking-distance",
|
|
1991
|
+
"opportunity",
|
|
1992
|
+
"movers",
|
|
1993
|
+
"decay",
|
|
1994
|
+
"zero-click",
|
|
1995
|
+
"brand",
|
|
1996
|
+
"cannibalization",
|
|
1997
|
+
"clustering",
|
|
1998
|
+
"concentration",
|
|
1999
|
+
"seasonality"
|
|
2000
|
+
]).describe("Analysis tool to run"),
|
|
2001
|
+
startDate: z.string().optional().describe("Start date (YYYY-MM-DD)"),
|
|
2002
|
+
endDate: z.string().optional().describe("End date (YYYY-MM-DD)"),
|
|
2003
|
+
limit: z.number().optional().describe("Max results")
|
|
2004
|
+
});
|
|
2005
|
+
server.registerTool("cloud-list-sites", {
|
|
2006
|
+
description: "List registered sites on gscdump.com with sync status and progress",
|
|
2007
|
+
inputSchema: z.object({}).shape
|
|
2008
|
+
}, async () => {
|
|
2009
|
+
const result = await cloudClient.me();
|
|
2010
|
+
return { content: [{
|
|
2011
|
+
type: "text",
|
|
2012
|
+
text: JSON.stringify(result, null, 2)
|
|
2013
|
+
}] };
|
|
2014
|
+
});
|
|
2015
|
+
server.registerTool("cloud-sync-status", {
|
|
2016
|
+
description: "Get detailed sync status for a site on gscdump.com",
|
|
2017
|
+
inputSchema: siteIdSchema.shape
|
|
2018
|
+
}, async ({ siteId }) => {
|
|
2019
|
+
const result = await cloudClient.syncStatus(siteId);
|
|
2020
|
+
return { content: [{
|
|
2021
|
+
type: "text",
|
|
2022
|
+
text: JSON.stringify(result, null, 2)
|
|
2023
|
+
}] };
|
|
2024
|
+
});
|
|
2025
|
+
server.registerTool("cloud-sitemaps", {
|
|
2026
|
+
description: "Get sitemap health data for a site from gscdump.com (includes URL counts, error tracking, history)",
|
|
2027
|
+
inputSchema: siteIdSchema.shape
|
|
2028
|
+
}, async ({ siteId }) => {
|
|
2029
|
+
const result = await cloudClient.sitemaps(siteId);
|
|
2030
|
+
return { content: [{
|
|
2031
|
+
type: "text",
|
|
2032
|
+
text: JSON.stringify(result, null, 2)
|
|
2033
|
+
}] };
|
|
2034
|
+
});
|
|
2035
|
+
server.registerTool("cloud-analysis", {
|
|
2036
|
+
description: "Run SEO analysis on synced data (striking-distance, opportunity, movers, decay, zero-click, brand, cannibalization, clustering, concentration, seasonality)",
|
|
2037
|
+
inputSchema: analysisSchema.shape
|
|
2038
|
+
}, async ({ siteId, tool, startDate, endDate, limit }) => {
|
|
2039
|
+
const params = {};
|
|
2040
|
+
if (startDate) params.startDate = startDate;
|
|
2041
|
+
if (endDate) params.endDate = endDate;
|
|
2042
|
+
if (limit) params.limit = String(limit);
|
|
2043
|
+
const result = await cloudClient.analysis(siteId, tool, params);
|
|
2044
|
+
return { content: [{
|
|
2045
|
+
type: "text",
|
|
2046
|
+
text: JSON.stringify(result, null, 2)
|
|
2047
|
+
}] };
|
|
2048
|
+
});
|
|
2049
|
+
server.registerTool("cloud-register-site", {
|
|
2050
|
+
description: "Register a site for syncing on gscdump.com",
|
|
2051
|
+
inputSchema: z.object({ siteUrl: z.string().describe("Site URL to register (e.g., example.com)") }).shape
|
|
2052
|
+
}, async ({ siteUrl }) => {
|
|
2053
|
+
const result = await cloudClient.registerSite(siteUrl);
|
|
2054
|
+
return { content: [{
|
|
2055
|
+
type: "text",
|
|
2056
|
+
text: JSON.stringify(result, null, 2)
|
|
2057
|
+
}] };
|
|
2058
|
+
});
|
|
2059
|
+
server.registerTool("cloud-query", {
|
|
2060
|
+
description: "Live GSC query via gscdump.com platform (bypasses synced data, queries Google directly)",
|
|
2061
|
+
inputSchema: z.object({
|
|
2062
|
+
siteId: z.string().describe("Site ID from gscdump platform"),
|
|
2063
|
+
startDate: z.string().describe("Start date (YYYY-MM-DD)"),
|
|
2064
|
+
endDate: z.string().describe("End date (YYYY-MM-DD)"),
|
|
2065
|
+
dimensions: z.string().optional().describe("Comma-separated: page,query,country,device,date,searchAppearance"),
|
|
2066
|
+
rowLimit: z.number().optional().describe("Max rows (default 1000, max 25000)")
|
|
2067
|
+
}).shape
|
|
2068
|
+
}, async ({ siteId, startDate, endDate, dimensions, rowLimit }) => {
|
|
2069
|
+
const params = {
|
|
2070
|
+
startDate,
|
|
2071
|
+
endDate
|
|
2072
|
+
};
|
|
2073
|
+
if (dimensions) params.dimensions = dimensions;
|
|
2074
|
+
if (rowLimit) params.rowLimit = String(rowLimit);
|
|
2075
|
+
const result = await cloudClient.query(siteId, params);
|
|
2076
|
+
return { content: [{
|
|
2077
|
+
type: "text",
|
|
2078
|
+
text: JSON.stringify(result, null, 2)
|
|
2079
|
+
}] };
|
|
2080
|
+
});
|
|
2081
|
+
server.registerTool("cloud-indexing", {
|
|
2082
|
+
description: "Get indexing status trend and summary for a site on gscdump.com",
|
|
2083
|
+
inputSchema: z.object({
|
|
2084
|
+
siteId: z.string().describe("Site ID from gscdump platform"),
|
|
2085
|
+
days: z.number().optional().describe("Days of trend data (default 28, max 90)")
|
|
2086
|
+
}).shape
|
|
2087
|
+
}, async ({ siteId, days }) => {
|
|
2088
|
+
const params = {};
|
|
2089
|
+
if (days) params.days = String(days);
|
|
2090
|
+
const result = await cloudClient.indexing(siteId, params);
|
|
2091
|
+
return { content: [{
|
|
2092
|
+
type: "text",
|
|
2093
|
+
text: JSON.stringify(result, null, 2)
|
|
2094
|
+
}] };
|
|
2095
|
+
});
|
|
2096
|
+
server.registerTool("cloud-indexing-diagnostics", {
|
|
2097
|
+
description: "Get indexing issue diagnostics with counts and severity for a site",
|
|
2098
|
+
inputSchema: siteIdSchema.shape
|
|
2099
|
+
}, async ({ siteId }) => {
|
|
2100
|
+
const result = await cloudClient.indexingDiagnostics(siteId);
|
|
2101
|
+
return { content: [{
|
|
2102
|
+
type: "text",
|
|
2103
|
+
text: JSON.stringify(result, null, 2)
|
|
2104
|
+
}] };
|
|
2105
|
+
});
|
|
2106
|
+
server.registerTool("cloud-indexing-urls", {
|
|
2107
|
+
description: "Get paginated URL list with indexing status, verdict, and coverage details",
|
|
2108
|
+
inputSchema: z.object({
|
|
2109
|
+
siteId: z.string().describe("Site ID from gscdump platform"),
|
|
2110
|
+
status: z.enum([
|
|
2111
|
+
"indexed",
|
|
2112
|
+
"not_indexed",
|
|
2113
|
+
"pending"
|
|
2114
|
+
]).optional().describe("Filter by status"),
|
|
2115
|
+
issue: z.string().optional().describe("Filter by issue type"),
|
|
2116
|
+
search: z.string().optional().describe("Search URLs"),
|
|
2117
|
+
limit: z.number().optional().describe("Max results (default 100, max 500)"),
|
|
2118
|
+
offset: z.number().optional().describe("Pagination offset")
|
|
2119
|
+
}).shape
|
|
2120
|
+
}, async ({ siteId, status, issue, search, limit, offset }) => {
|
|
2121
|
+
const params = {};
|
|
2122
|
+
if (status) params.status = status;
|
|
2123
|
+
if (issue) params.issue = issue;
|
|
2124
|
+
if (search) params.search = search;
|
|
2125
|
+
if (limit) params.limit = String(limit);
|
|
2126
|
+
if (offset) params.offset = String(offset);
|
|
2127
|
+
const result = await cloudClient.indexingUrls(siteId, params);
|
|
2128
|
+
return { content: [{
|
|
2129
|
+
type: "text",
|
|
2130
|
+
text: JSON.stringify(result, null, 2)
|
|
2131
|
+
}] };
|
|
2132
|
+
});
|
|
2133
|
+
server.registerTool("cloud-index-percent", {
|
|
2134
|
+
description: "Get index percent trend, invisible URLs (in sitemap but no traffic), and orphan pages (traffic but not in sitemap)",
|
|
2135
|
+
inputSchema: siteIdSchema.shape
|
|
2136
|
+
}, async ({ siteId }) => {
|
|
2137
|
+
const result = await cloudClient.indexPercent(siteId);
|
|
2138
|
+
return { content: [{
|
|
2139
|
+
type: "text",
|
|
2140
|
+
text: JSON.stringify(result, null, 2)
|
|
2141
|
+
}] };
|
|
2142
|
+
});
|
|
2143
|
+
server.registerTool("cloud-trigger-sync", {
|
|
2144
|
+
description: "Trigger a fresh data sync for a site on gscdump.com",
|
|
2145
|
+
inputSchema: siteIdSchema.shape
|
|
2146
|
+
}, async ({ siteId }) => {
|
|
2147
|
+
const result = await cloudClient.triggerSync(siteId);
|
|
2148
|
+
return { content: [{
|
|
2149
|
+
type: "text",
|
|
2150
|
+
text: JSON.stringify(result, null, 2)
|
|
2151
|
+
}] };
|
|
2152
|
+
});
|
|
2153
|
+
server.registerTool("cloud-delete-site", {
|
|
2154
|
+
description: "Unregister a site from gscdump.com (stops syncing, removes pending jobs)",
|
|
2155
|
+
inputSchema: siteIdSchema.shape
|
|
2156
|
+
}, async ({ siteId }) => {
|
|
2157
|
+
const result = await cloudClient.deleteSite(siteId);
|
|
2158
|
+
return { content: [{
|
|
2159
|
+
type: "text",
|
|
2160
|
+
text: JSON.stringify(result, null, 2)
|
|
2161
|
+
}] };
|
|
2162
|
+
});
|
|
2163
|
+
server.registerTool("cloud-sitemap-action", {
|
|
2164
|
+
description: "Submit, delete, or refresh sitemaps via gscdump.com",
|
|
2165
|
+
inputSchema: z.object({
|
|
2166
|
+
siteId: z.string().describe("Site ID from gscdump platform"),
|
|
2167
|
+
action: z.enum([
|
|
2168
|
+
"submit",
|
|
2169
|
+
"delete",
|
|
2170
|
+
"refresh"
|
|
2171
|
+
]).describe("Action to perform"),
|
|
2172
|
+
sitemapUrl: z.string().optional().describe("Sitemap URL (required for submit/delete)")
|
|
2173
|
+
}).shape
|
|
2174
|
+
}, async ({ siteId, action, sitemapUrl }) => {
|
|
2175
|
+
const body = { action };
|
|
2176
|
+
if (sitemapUrl) body.sitemapUrl = sitemapUrl;
|
|
2177
|
+
const result = await cloudClient.sitemapAction(siteId, body);
|
|
2178
|
+
return { content: [{
|
|
2179
|
+
type: "text",
|
|
2180
|
+
text: JSON.stringify(result, null, 2)
|
|
2181
|
+
}] };
|
|
2182
|
+
});
|
|
2183
|
+
}
|
|
1239
2184
|
return server;
|
|
1240
2185
|
}
|
|
1241
2186
|
|
|
1242
2187
|
//#endregion
|
|
1243
2188
|
//#region src/commands/mcp.ts
|
|
1244
2189
|
async function checkAuth() {
|
|
1245
|
-
if ((process.env.GOOGLE_ACCESS_TOKEN || process.env.GOOGLE_REFRESH_TOKEN) && process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET) return { ok: true };
|
|
2190
|
+
if ((process$1.env.GOOGLE_ACCESS_TOKEN || process$1.env.GOOGLE_REFRESH_TOKEN) && process$1.env.GOOGLE_CLIENT_ID && process$1.env.GOOGLE_CLIENT_SECRET) return { ok: true };
|
|
1246
2191
|
const config = await loadConfig();
|
|
1247
2192
|
if (!config.mode) return {
|
|
1248
2193
|
ok: false,
|
|
@@ -1287,13 +2232,14 @@ const mcpCommand = defineCommand({
|
|
|
1287
2232
|
async run() {
|
|
1288
2233
|
const authCheck = await checkAuth();
|
|
1289
2234
|
if (!authCheck.ok) {
|
|
1290
|
-
process.stderr.write(`\n${authCheck.error}\n\n`);
|
|
1291
|
-
process.exit(1);
|
|
2235
|
+
process$1.stderr.write(`\n${authCheck.error}\n\n`);
|
|
2236
|
+
process$1.exit(1);
|
|
1292
2237
|
}
|
|
1293
2238
|
const server = createGscMcpServer({
|
|
1294
2239
|
name: "gscdump",
|
|
1295
2240
|
version: VERSION,
|
|
1296
|
-
getAuth: () => getAuth({ interactive: false })
|
|
2241
|
+
getAuth: () => getAuth({ interactive: false }),
|
|
2242
|
+
cloudClient: await getCloudClient()
|
|
1297
2243
|
});
|
|
1298
2244
|
const transport = new StdioServerTransport();
|
|
1299
2245
|
await server.connect(transport);
|
|
@@ -1310,6 +2256,36 @@ const DIMENSION_MAP = {
|
|
|
1310
2256
|
device,
|
|
1311
2257
|
searchAppearance
|
|
1312
2258
|
};
|
|
2259
|
+
async function resolveCloudSite$1(cloud, target) {
|
|
2260
|
+
const me = await cloud.me().catch((e) => {
|
|
2261
|
+
logger.error(`Failed to fetch sites: ${e.message}`);
|
|
2262
|
+
process$1.exit(1);
|
|
2263
|
+
});
|
|
2264
|
+
if (me.sites.length === 0) {
|
|
2265
|
+
logger.error("No registered sites. Run gscdump register first.");
|
|
2266
|
+
process$1.exit(1);
|
|
2267
|
+
}
|
|
2268
|
+
let site = target ? me.sites.find((s) => s.siteUrl === target || s.siteUrl.includes(target)) : void 0;
|
|
2269
|
+
if (!site) if (me.sites.length === 1) site = me.sites[0];
|
|
2270
|
+
else {
|
|
2271
|
+
const selected = await select({
|
|
2272
|
+
message: "Select a site",
|
|
2273
|
+
options: me.sites.map((s) => ({
|
|
2274
|
+
value: s.siteId,
|
|
2275
|
+
label: s.siteUrl
|
|
2276
|
+
}))
|
|
2277
|
+
});
|
|
2278
|
+
if (isCancel(selected)) {
|
|
2279
|
+
cancel("Cancelled");
|
|
2280
|
+
process$1.exit(0);
|
|
2281
|
+
}
|
|
2282
|
+
site = me.sites.find((s) => s.siteId === selected);
|
|
2283
|
+
}
|
|
2284
|
+
return {
|
|
2285
|
+
siteId: site.siteId,
|
|
2286
|
+
siteUrl: site.siteUrl
|
|
2287
|
+
};
|
|
2288
|
+
}
|
|
1313
2289
|
const queryCommand = defineCommand({
|
|
1314
2290
|
meta: {
|
|
1315
2291
|
name: "query",
|
|
@@ -1366,34 +2342,8 @@ const queryCommand = defineCommand({
|
|
|
1366
2342
|
},
|
|
1367
2343
|
async run({ args }) {
|
|
1368
2344
|
const config = await loadConfig();
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
interactive: false,
|
|
1372
|
-
config
|
|
1373
|
-
}));
|
|
1374
|
-
let siteUrl = String(args.site || config.defaultSite || "");
|
|
1375
|
-
if (!siteUrl || args.interactive) {
|
|
1376
|
-
const verified = (await client.sites()).filter((s) => s.permissionLevel !== "siteUnverifiedUser");
|
|
1377
|
-
if (verified.length === 0) {
|
|
1378
|
-
logger.error("No verified sites found");
|
|
1379
|
-
process.exit(1);
|
|
1380
|
-
}
|
|
1381
|
-
const selected = await select({
|
|
1382
|
-
message: "Select a site",
|
|
1383
|
-
options: verified.map((s) => ({
|
|
1384
|
-
value: s.siteUrl,
|
|
1385
|
-
label: s.siteUrl
|
|
1386
|
-
})),
|
|
1387
|
-
initialValue: siteUrl || verified[0]?.siteUrl
|
|
1388
|
-
});
|
|
1389
|
-
if (isCancel(selected)) {
|
|
1390
|
-
cancel("Cancelled");
|
|
1391
|
-
process.exit(0);
|
|
1392
|
-
}
|
|
1393
|
-
siteUrl = selected;
|
|
1394
|
-
}
|
|
1395
|
-
let dimensions;
|
|
1396
|
-
if (args.dimensions) dimensions = String(args.dimensions).split(",").filter((d) => d in DIMENSION_MAP).map((d) => DIMENSION_MAP[d]);
|
|
2345
|
+
let dimNames;
|
|
2346
|
+
if (args.dimensions) dimNames = String(args.dimensions).split(",").filter((d) => d in DIMENSION_MAP);
|
|
1397
2347
|
else if (args.interactive) {
|
|
1398
2348
|
const selected = await multiselect({
|
|
1399
2349
|
message: "Select dimensions",
|
|
@@ -1405,10 +2355,10 @@ const queryCommand = defineCommand({
|
|
|
1405
2355
|
});
|
|
1406
2356
|
if (isCancel(selected)) {
|
|
1407
2357
|
cancel("Cancelled");
|
|
1408
|
-
process.exit(0);
|
|
2358
|
+
process$1.exit(0);
|
|
1409
2359
|
}
|
|
1410
|
-
|
|
1411
|
-
} else
|
|
2360
|
+
dimNames = selected;
|
|
2361
|
+
} else dimNames = ["page", "query"];
|
|
1412
2362
|
let startDate;
|
|
1413
2363
|
let endDate;
|
|
1414
2364
|
if (args.start && args.end) {
|
|
@@ -1421,7 +2371,7 @@ const queryCommand = defineCommand({
|
|
|
1421
2371
|
});
|
|
1422
2372
|
if (isCancel(startInput)) {
|
|
1423
2373
|
cancel("Cancelled");
|
|
1424
|
-
process.exit(0);
|
|
2374
|
+
process$1.exit(0);
|
|
1425
2375
|
}
|
|
1426
2376
|
const endInput = await text({
|
|
1427
2377
|
message: "End date (YYYY-MM-DD)",
|
|
@@ -1429,7 +2379,7 @@ const queryCommand = defineCommand({
|
|
|
1429
2379
|
});
|
|
1430
2380
|
if (isCancel(endInput)) {
|
|
1431
2381
|
cancel("Cancelled");
|
|
1432
|
-
process.exit(0);
|
|
2382
|
+
process$1.exit(0);
|
|
1433
2383
|
}
|
|
1434
2384
|
startDate = String(startInput) || (/* @__PURE__ */ new Date(Date.now() - 28 * 864e5)).toISOString().split("T")[0];
|
|
1435
2385
|
endDate = String(endInput) || (/* @__PURE__ */ new Date(Date.now() - 3 * 864e5)).toISOString().split("T")[0];
|
|
@@ -1439,6 +2389,63 @@ const queryCommand = defineCommand({
|
|
|
1439
2389
|
}
|
|
1440
2390
|
const rowLimit = Number.parseInt(String(args.limit), 10);
|
|
1441
2391
|
const format = String(args.format);
|
|
2392
|
+
const cloud = await getCloudClient();
|
|
2393
|
+
if (cloud) {
|
|
2394
|
+
const { siteId, siteUrl: siteUrl$1 } = await resolveCloudSite$1(cloud, args.site || config.defaultSite);
|
|
2395
|
+
if (!args.quiet) logger.info(`Querying ${siteUrl$1}...`);
|
|
2396
|
+
const result = await cloud.query(siteId, {
|
|
2397
|
+
startDate,
|
|
2398
|
+
endDate,
|
|
2399
|
+
dimensions: dimNames.join(","),
|
|
2400
|
+
rowLimit: String(rowLimit)
|
|
2401
|
+
}).catch((e) => {
|
|
2402
|
+
logger.error(`Query failed: ${e.message}`);
|
|
2403
|
+
process$1.exit(1);
|
|
2404
|
+
});
|
|
2405
|
+
if (!args.quiet) logger.success(`Fetched ${result.rows.length} rows`);
|
|
2406
|
+
const output$1 = {
|
|
2407
|
+
siteUrl: siteUrl$1,
|
|
2408
|
+
dimensions: dimNames,
|
|
2409
|
+
dateRange: {
|
|
2410
|
+
start: startDate,
|
|
2411
|
+
end: endDate
|
|
2412
|
+
},
|
|
2413
|
+
total: result.rows.length,
|
|
2414
|
+
data: result.rows
|
|
2415
|
+
};
|
|
2416
|
+
const content$1 = format === "csv" ? exportToCSV(output$1) : JSON.stringify(output$1, null, 2);
|
|
2417
|
+
if (args.output) {
|
|
2418
|
+
await fs.writeFile(String(args.output), content$1);
|
|
2419
|
+
if (!args.quiet) logger.info(`Written to ${args.output}`);
|
|
2420
|
+
} else console.log(content$1);
|
|
2421
|
+
return;
|
|
2422
|
+
}
|
|
2423
|
+
const client = googleSearchConsole(await getAuth({
|
|
2424
|
+
interactive: false,
|
|
2425
|
+
config
|
|
2426
|
+
}));
|
|
2427
|
+
const dimensions = dimNames.map((d) => DIMENSION_MAP[d]);
|
|
2428
|
+
let siteUrl = String(args.site || config.defaultSite || "");
|
|
2429
|
+
if (!siteUrl || args.interactive) {
|
|
2430
|
+
const verified = (await client.sites()).filter((s) => s.permissionLevel !== "siteUnverifiedUser");
|
|
2431
|
+
if (verified.length === 0) {
|
|
2432
|
+
logger.error("No verified sites found");
|
|
2433
|
+
process$1.exit(1);
|
|
2434
|
+
}
|
|
2435
|
+
const selected = await select({
|
|
2436
|
+
message: "Select a site",
|
|
2437
|
+
options: verified.map((s) => ({
|
|
2438
|
+
value: s.siteUrl,
|
|
2439
|
+
label: s.siteUrl
|
|
2440
|
+
})),
|
|
2441
|
+
initialValue: siteUrl || verified[0]?.siteUrl
|
|
2442
|
+
});
|
|
2443
|
+
if (isCancel(selected)) {
|
|
2444
|
+
cancel("Cancelled");
|
|
2445
|
+
process$1.exit(0);
|
|
2446
|
+
}
|
|
2447
|
+
siteUrl = selected;
|
|
2448
|
+
}
|
|
1442
2449
|
const builder = gsc.select(...dimensions).where(between(date, startDate, endDate)).limit(rowLimit);
|
|
1443
2450
|
if (!args.quiet) logger.info(`Querying ${siteUrl}...`);
|
|
1444
2451
|
const rows = [];
|
|
@@ -1446,7 +2453,7 @@ const queryCommand = defineCommand({
|
|
|
1446
2453
|
rows.push(...batch);
|
|
1447
2454
|
if (!args.quiet) {
|
|
1448
2455
|
clearLine();
|
|
1449
|
-
process.stdout.write(progressBar(rows.length, rowLimit, `${rows.length} rows`));
|
|
2456
|
+
process$1.stdout.write(progressBar(rows.length, rowLimit, `${rows.length} rows`));
|
|
1450
2457
|
}
|
|
1451
2458
|
}
|
|
1452
2459
|
if (!args.quiet) {
|
|
@@ -1455,7 +2462,7 @@ const queryCommand = defineCommand({
|
|
|
1455
2462
|
}
|
|
1456
2463
|
const output = {
|
|
1457
2464
|
siteUrl,
|
|
1458
|
-
dimensions:
|
|
2465
|
+
dimensions: dimNames,
|
|
1459
2466
|
dateRange: {
|
|
1460
2467
|
start: startDate,
|
|
1461
2468
|
end: endDate
|
|
@@ -1471,6 +2478,84 @@ const queryCommand = defineCommand({
|
|
|
1471
2478
|
}
|
|
1472
2479
|
});
|
|
1473
2480
|
|
|
2481
|
+
//#endregion
|
|
2482
|
+
//#region src/commands/register.ts
|
|
2483
|
+
const registerCommand = defineCommand({
|
|
2484
|
+
meta: {
|
|
2485
|
+
name: "register",
|
|
2486
|
+
description: "Register site(s) for syncing (cloud mode only)"
|
|
2487
|
+
},
|
|
2488
|
+
args: { site: {
|
|
2489
|
+
type: "positional",
|
|
2490
|
+
description: "Site URL(s) to register (space-separated for bulk)",
|
|
2491
|
+
required: false
|
|
2492
|
+
} },
|
|
2493
|
+
async run({ args }) {
|
|
2494
|
+
const cloud = await getCloudClient();
|
|
2495
|
+
if (!cloud) {
|
|
2496
|
+
logger.error("Register requires cloud mode. Run gscdump init to set up cloud mode.");
|
|
2497
|
+
process$1.exit(1);
|
|
2498
|
+
}
|
|
2499
|
+
const rawArgs = process$1.argv.slice(3).filter((a) => !a.startsWith("-"));
|
|
2500
|
+
const siteUrls = rawArgs.length > 0 ? rawArgs : args.site ? [args.site] : [];
|
|
2501
|
+
if (siteUrls.length > 1) {
|
|
2502
|
+
logger.info(`Registering ${siteUrls.length} sites...`);
|
|
2503
|
+
const result$1 = await cloud.bulkRegister(siteUrls).catch((e) => {
|
|
2504
|
+
logger.error(`Bulk registration failed: ${e.message}`);
|
|
2505
|
+
process$1.exit(1);
|
|
2506
|
+
});
|
|
2507
|
+
console.log();
|
|
2508
|
+
for (const r of result$1.results) {
|
|
2509
|
+
const icon = r.status === "registered" ? "\x1B[32m✓\x1B[0m" : r.status === "already_exists" ? "\x1B[33m~\x1B[0m" : "\x1B[31m✗\x1B[0m";
|
|
2510
|
+
const detail = r.status === "registered" ? `ID: ${r.siteId}` : r.status === "already_exists" ? "already registered" : r.error || r.status;
|
|
2511
|
+
console.log(` ${icon} ${r.siteUrl} — ${detail}`);
|
|
2512
|
+
}
|
|
2513
|
+
console.log();
|
|
2514
|
+
const s = result$1.summary;
|
|
2515
|
+
logger.success(`${s.registered} registered, ${s.alreadyExists} existing, ${s.notFound} not found, ${s.errors} errors`);
|
|
2516
|
+
return;
|
|
2517
|
+
}
|
|
2518
|
+
let siteUrl = siteUrls[0];
|
|
2519
|
+
if (!siteUrl) {
|
|
2520
|
+
const available = await cloud.availableSites().catch((e) => {
|
|
2521
|
+
logger.error(`Failed to fetch available sites: ${e.message}`);
|
|
2522
|
+
process$1.exit(1);
|
|
2523
|
+
});
|
|
2524
|
+
const unregistered = available.filter((s) => !s.registered);
|
|
2525
|
+
if (unregistered.length === 0) {
|
|
2526
|
+
if (available.length > 0) logger.info("All available sites are already registered");
|
|
2527
|
+
else logger.warn("No GSC sites found for this account");
|
|
2528
|
+
return;
|
|
2529
|
+
}
|
|
2530
|
+
const selected = await select({
|
|
2531
|
+
message: "Select a site to register for syncing",
|
|
2532
|
+
options: unregistered.map((s) => ({
|
|
2533
|
+
value: s.siteUrl,
|
|
2534
|
+
label: s.siteUrl,
|
|
2535
|
+
hint: s.permissionLevel
|
|
2536
|
+
}))
|
|
2537
|
+
});
|
|
2538
|
+
if (isCancel(selected)) {
|
|
2539
|
+
cancel("Cancelled");
|
|
2540
|
+
process$1.exit(0);
|
|
2541
|
+
}
|
|
2542
|
+
siteUrl = selected;
|
|
2543
|
+
}
|
|
2544
|
+
logger.info(`Registering ${siteUrl}...`);
|
|
2545
|
+
const result = await cloud.registerSite(siteUrl).catch((e) => {
|
|
2546
|
+
logger.error(`Registration failed: ${e.message}`);
|
|
2547
|
+
process$1.exit(1);
|
|
2548
|
+
});
|
|
2549
|
+
if (result.existing) logger.info(`Site already registered (${result.status})`);
|
|
2550
|
+
else logger.success(`Site registered! Sync queued.`);
|
|
2551
|
+
console.log();
|
|
2552
|
+
console.log(` Site ID: \x1B[36m${result.siteId}\x1B[0m`);
|
|
2553
|
+
console.log(` Status: ${result.status}`);
|
|
2554
|
+
console.log();
|
|
2555
|
+
logger.info("Run gscdump sync status to check sync progress");
|
|
2556
|
+
}
|
|
2557
|
+
});
|
|
2558
|
+
|
|
1474
2559
|
//#endregion
|
|
1475
2560
|
//#region src/commands/sitemaps.ts
|
|
1476
2561
|
const listCommand = defineCommand({
|
|
@@ -1482,7 +2567,6 @@ const listCommand = defineCommand({
|
|
|
1482
2567
|
site: {
|
|
1483
2568
|
type: "string",
|
|
1484
2569
|
alias: "s",
|
|
1485
|
-
required: true,
|
|
1486
2570
|
description: "Site URL (e.g., sc-domain:example.com or https://example.com/)"
|
|
1487
2571
|
},
|
|
1488
2572
|
json: {
|
|
@@ -1492,6 +2576,69 @@ const listCommand = defineCommand({
|
|
|
1492
2576
|
}
|
|
1493
2577
|
},
|
|
1494
2578
|
async run({ args }) {
|
|
2579
|
+
const cloud = await getCloudClient();
|
|
2580
|
+
if (cloud) {
|
|
2581
|
+
const config = await loadConfig();
|
|
2582
|
+
const target = args.site || config.defaultSite;
|
|
2583
|
+
const me = await cloud.me().catch((e) => {
|
|
2584
|
+
logger.error(`Failed to fetch sites: ${e.message}`);
|
|
2585
|
+
process$1.exit(1);
|
|
2586
|
+
});
|
|
2587
|
+
if (me.sites.length === 0) {
|
|
2588
|
+
logger.warn("No registered sites. Run gscdump register first.");
|
|
2589
|
+
return;
|
|
2590
|
+
}
|
|
2591
|
+
let site = target ? me.sites.find((s) => s.siteUrl === target || s.siteUrl.includes(target)) : void 0;
|
|
2592
|
+
if (!site) if (me.sites.length === 1) site = me.sites[0];
|
|
2593
|
+
else {
|
|
2594
|
+
const selected = await select({
|
|
2595
|
+
message: "Select a site",
|
|
2596
|
+
options: me.sites.map((s) => ({
|
|
2597
|
+
value: s.siteId,
|
|
2598
|
+
label: s.siteUrl
|
|
2599
|
+
}))
|
|
2600
|
+
});
|
|
2601
|
+
if (isCancel(selected)) {
|
|
2602
|
+
cancel("Cancelled");
|
|
2603
|
+
process$1.exit(0);
|
|
2604
|
+
}
|
|
2605
|
+
site = me.sites.find((s) => s.siteId === selected);
|
|
2606
|
+
}
|
|
2607
|
+
const data = await cloud.sitemaps(site.siteId).catch((e) => {
|
|
2608
|
+
logger.error(`Failed to fetch sitemaps: ${e.message}`);
|
|
2609
|
+
process$1.exit(1);
|
|
2610
|
+
});
|
|
2611
|
+
if (args.json) {
|
|
2612
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2613
|
+
return;
|
|
2614
|
+
}
|
|
2615
|
+
if (data.sitemaps.length === 0) {
|
|
2616
|
+
logger.warn("No sitemaps found");
|
|
2617
|
+
return;
|
|
2618
|
+
}
|
|
2619
|
+
logger.success(`${data.sitemaps.length} sitemaps:`);
|
|
2620
|
+
console.log();
|
|
2621
|
+
for (const sm of data.sitemaps) {
|
|
2622
|
+
const pending = sm.isPending ? " \x1B[33m(pending)\x1B[0m" : "";
|
|
2623
|
+
const errors = sm.errors ? ` \x1B[31m${sm.errors} errors\x1B[0m` : "";
|
|
2624
|
+
const warnings = sm.warnings ? ` \x1B[33m${sm.warnings} warnings\x1B[0m` : "";
|
|
2625
|
+
const urls = sm.urlCount ? ` \x1B[36m${sm.urlCount.toLocaleString()} URLs\x1B[0m` : "";
|
|
2626
|
+
console.log(` ${sm.path}${urls}${pending}${errors}${warnings}`);
|
|
2627
|
+
}
|
|
2628
|
+
if (data.history.length > 0) {
|
|
2629
|
+
console.log();
|
|
2630
|
+
console.log(" \x1B[1mRecent History\x1B[0m");
|
|
2631
|
+
for (const h of data.history.slice(0, 7)) {
|
|
2632
|
+
const errStr = h.errors > 0 ? ` \x1B[31m${h.errors} err\x1B[0m` : "";
|
|
2633
|
+
console.log(` ${h.date}: ${h.urlCount.toLocaleString()} URLs${errStr}`);
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
return;
|
|
2637
|
+
}
|
|
2638
|
+
if (!args.site) {
|
|
2639
|
+
logger.error("Site URL required (-s)");
|
|
2640
|
+
process$1.exit(1);
|
|
2641
|
+
}
|
|
1495
2642
|
const sitemaps = await fetchSitemaps(googleSearchConsole(await getAuth({ interactive: false })), args.site).catch(gscErrorHandler);
|
|
1496
2643
|
if (args.json) {
|
|
1497
2644
|
console.log(JSON.stringify(sitemaps, null, 2));
|
|
@@ -1574,6 +2721,29 @@ const submitCommand = defineCommand({
|
|
|
1574
2721
|
}
|
|
1575
2722
|
},
|
|
1576
2723
|
async run({ args }) {
|
|
2724
|
+
const cloud = await getCloudClient();
|
|
2725
|
+
if (cloud) {
|
|
2726
|
+
const config = await loadConfig();
|
|
2727
|
+
const me = await cloud.me().catch((e) => {
|
|
2728
|
+
logger.error(`Failed to fetch sites: ${e.message}`);
|
|
2729
|
+
process$1.exit(1);
|
|
2730
|
+
});
|
|
2731
|
+
const target = args.site || config.defaultSite;
|
|
2732
|
+
const site = me.sites.find((s) => s.siteUrl === target || s.siteUrl.includes(target));
|
|
2733
|
+
if (!site) {
|
|
2734
|
+
logger.error(`Site not found: ${target}`);
|
|
2735
|
+
process$1.exit(1);
|
|
2736
|
+
}
|
|
2737
|
+
await cloud.sitemapAction(site.siteId, {
|
|
2738
|
+
action: "submit",
|
|
2739
|
+
sitemapUrl: args.url
|
|
2740
|
+
}).catch((e) => {
|
|
2741
|
+
logger.error(`Submit failed: ${e.message}`);
|
|
2742
|
+
process$1.exit(1);
|
|
2743
|
+
});
|
|
2744
|
+
logger.success(`Submitted sitemap: ${args.url}`);
|
|
2745
|
+
return;
|
|
2746
|
+
}
|
|
1577
2747
|
await submitSitemap(googleSearchConsole(await getAuth({ interactive: false })), args.site, args.url).catch(gscErrorHandler);
|
|
1578
2748
|
logger.success(`Submitted sitemap: ${args.url}`);
|
|
1579
2749
|
}
|
|
@@ -1597,10 +2767,77 @@ const deleteCommand = defineCommand({
|
|
|
1597
2767
|
}
|
|
1598
2768
|
},
|
|
1599
2769
|
async run({ args }) {
|
|
2770
|
+
const cloud = await getCloudClient();
|
|
2771
|
+
if (cloud) {
|
|
2772
|
+
const config = await loadConfig();
|
|
2773
|
+
const me = await cloud.me().catch((e) => {
|
|
2774
|
+
logger.error(`Failed to fetch sites: ${e.message}`);
|
|
2775
|
+
process$1.exit(1);
|
|
2776
|
+
});
|
|
2777
|
+
const target = args.site || config.defaultSite;
|
|
2778
|
+
const site = me.sites.find((s) => s.siteUrl === target || s.siteUrl.includes(target));
|
|
2779
|
+
if (!site) {
|
|
2780
|
+
logger.error(`Site not found: ${target}`);
|
|
2781
|
+
process$1.exit(1);
|
|
2782
|
+
}
|
|
2783
|
+
await cloud.sitemapAction(site.siteId, {
|
|
2784
|
+
action: "delete",
|
|
2785
|
+
sitemapUrl: args.url
|
|
2786
|
+
}).catch((e) => {
|
|
2787
|
+
logger.error(`Delete failed: ${e.message}`);
|
|
2788
|
+
process$1.exit(1);
|
|
2789
|
+
});
|
|
2790
|
+
logger.success(`Deleted sitemap: ${args.url}`);
|
|
2791
|
+
return;
|
|
2792
|
+
}
|
|
1600
2793
|
await deleteSitemap(googleSearchConsole(await getAuth({ interactive: false })), args.site, args.url).catch(gscErrorHandler);
|
|
1601
2794
|
logger.success(`Deleted sitemap: ${args.url}`);
|
|
1602
2795
|
}
|
|
1603
2796
|
});
|
|
2797
|
+
const refreshCommand = defineCommand({
|
|
2798
|
+
meta: {
|
|
2799
|
+
name: "refresh",
|
|
2800
|
+
description: "Refresh sitemap data from GSC (cloud mode)"
|
|
2801
|
+
},
|
|
2802
|
+
args: { site: {
|
|
2803
|
+
type: "string",
|
|
2804
|
+
alias: "s",
|
|
2805
|
+
description: "Site URL"
|
|
2806
|
+
} },
|
|
2807
|
+
async run({ args }) {
|
|
2808
|
+
const cloud = await getCloudClient();
|
|
2809
|
+
if (!cloud) {
|
|
2810
|
+
logger.error("Sitemap refresh requires cloud mode. Run gscdump init to set up.");
|
|
2811
|
+
process$1.exit(1);
|
|
2812
|
+
}
|
|
2813
|
+
const config = await loadConfig();
|
|
2814
|
+
const me = await cloud.me().catch((e) => {
|
|
2815
|
+
logger.error(`Failed to fetch sites: ${e.message}`);
|
|
2816
|
+
process$1.exit(1);
|
|
2817
|
+
});
|
|
2818
|
+
const target = args.site || config.defaultSite;
|
|
2819
|
+
let site = target ? me.sites.find((s) => s.siteUrl === target || s.siteUrl.includes(target)) : me.sites.length === 1 ? me.sites[0] : void 0;
|
|
2820
|
+
if (!site) {
|
|
2821
|
+
const selected = await select({
|
|
2822
|
+
message: "Select a site",
|
|
2823
|
+
options: me.sites.map((s) => ({
|
|
2824
|
+
value: s.siteId,
|
|
2825
|
+
label: s.siteUrl
|
|
2826
|
+
}))
|
|
2827
|
+
});
|
|
2828
|
+
if (isCancel(selected)) {
|
|
2829
|
+
cancel("Cancelled");
|
|
2830
|
+
process$1.exit(0);
|
|
2831
|
+
}
|
|
2832
|
+
site = me.sites.find((s) => s.siteId === selected);
|
|
2833
|
+
}
|
|
2834
|
+
const result = await cloud.sitemapAction(site.siteId, { action: "refresh" }).catch((e) => {
|
|
2835
|
+
logger.error(`Refresh failed: ${e.message}`);
|
|
2836
|
+
process$1.exit(1);
|
|
2837
|
+
});
|
|
2838
|
+
logger.success(`Refreshed sitemaps (${result.sitemapCount} found)`);
|
|
2839
|
+
}
|
|
2840
|
+
});
|
|
1604
2841
|
const sitemapsCommand = defineCommand({
|
|
1605
2842
|
meta: {
|
|
1606
2843
|
name: "sitemaps",
|
|
@@ -1610,7 +2847,8 @@ const sitemapsCommand = defineCommand({
|
|
|
1610
2847
|
list: listCommand,
|
|
1611
2848
|
get: getCommand,
|
|
1612
2849
|
submit: submitCommand,
|
|
1613
|
-
delete: deleteCommand
|
|
2850
|
+
delete: deleteCommand,
|
|
2851
|
+
refresh: refreshCommand
|
|
1614
2852
|
}
|
|
1615
2853
|
});
|
|
1616
2854
|
|
|
@@ -1627,6 +2865,30 @@ const sitesCommand = defineCommand({
|
|
|
1627
2865
|
description: "Output as JSON for scripting"
|
|
1628
2866
|
} },
|
|
1629
2867
|
async run({ args }) {
|
|
2868
|
+
const cloud = await getCloudClient();
|
|
2869
|
+
if (cloud) {
|
|
2870
|
+
const me = await cloud.me().catch((e) => {
|
|
2871
|
+
logger.error(`Failed to fetch sites: ${e.message}`);
|
|
2872
|
+
process.exit(1);
|
|
2873
|
+
});
|
|
2874
|
+
if (args.json) {
|
|
2875
|
+
console.log(JSON.stringify(me.sites, null, 2));
|
|
2876
|
+
return;
|
|
2877
|
+
}
|
|
2878
|
+
if (me.sites.length === 0) {
|
|
2879
|
+
logger.warn("No registered sites. Run gscdump register to add a site.");
|
|
2880
|
+
return;
|
|
2881
|
+
}
|
|
2882
|
+
logger.success(`${me.sites.length} registered sites:`);
|
|
2883
|
+
console.log();
|
|
2884
|
+
for (const site of me.sites) {
|
|
2885
|
+
const statusColor = site.syncStatus === "synced" ? "\x1B[32m" : site.syncStatus === "syncing" ? "\x1B[33m" : site.syncStatus === "error" ? "\x1B[31m" : "\x1B[90m";
|
|
2886
|
+
console.log(` ${site.siteUrl} ${statusColor}(${site.syncStatus || "pending"})\x1B[0m`);
|
|
2887
|
+
if (site.syncProgress.percent > 0 && site.syncProgress.percent < 100) console.log(` ${progressBar(site.syncProgress.percent, 100, `${site.syncProgress.percent}%`, 20)}`);
|
|
2888
|
+
if (site.oldestDateSynced && site.newestDateSynced) console.log(` \x1B[90m${site.oldestDateSynced} → ${site.newestDateSynced}\x1B[0m`);
|
|
2889
|
+
}
|
|
2890
|
+
return;
|
|
2891
|
+
}
|
|
1630
2892
|
const sites = (await fetchSites(googleSearchConsole(await getAuth({ interactive: false }))).catch(gscErrorHandler)).filter((site) => site.siteUrl && site.permissionLevel !== "siteUnverifiedUser").map((site) => ({
|
|
1631
2893
|
url: site.siteUrl,
|
|
1632
2894
|
permission: site.permissionLevel || "unknown"
|
|
@@ -1648,6 +2910,200 @@ const sitesCommand = defineCommand({
|
|
|
1648
2910
|
}
|
|
1649
2911
|
});
|
|
1650
2912
|
|
|
2913
|
+
//#endregion
|
|
2914
|
+
//#region src/commands/sync.ts
|
|
2915
|
+
async function resolveCloudSite(cloud, target) {
|
|
2916
|
+
const me = await cloud.me().catch((e) => {
|
|
2917
|
+
logger.error(`Failed to fetch sites: ${e.message}`);
|
|
2918
|
+
process$1.exit(1);
|
|
2919
|
+
});
|
|
2920
|
+
if (me.sites.length === 0) {
|
|
2921
|
+
logger.error("No registered sites. Run gscdump register first.");
|
|
2922
|
+
process$1.exit(1);
|
|
2923
|
+
}
|
|
2924
|
+
let site = target ? me.sites.find((s) => s.siteUrl === target || s.siteUrl.includes(target)) : void 0;
|
|
2925
|
+
if (!site) if (me.sites.length === 1) site = me.sites[0];
|
|
2926
|
+
else {
|
|
2927
|
+
const selected = await select({
|
|
2928
|
+
message: "Select a site",
|
|
2929
|
+
options: me.sites.map((s) => ({
|
|
2930
|
+
value: s.siteId,
|
|
2931
|
+
label: s.siteUrl,
|
|
2932
|
+
hint: s.syncStatus || "unknown"
|
|
2933
|
+
}))
|
|
2934
|
+
});
|
|
2935
|
+
if (isCancel(selected)) {
|
|
2936
|
+
cancel("Cancelled");
|
|
2937
|
+
process$1.exit(0);
|
|
2938
|
+
}
|
|
2939
|
+
site = me.sites.find((s) => s.siteId === selected);
|
|
2940
|
+
}
|
|
2941
|
+
return {
|
|
2942
|
+
siteId: site.siteId,
|
|
2943
|
+
siteUrl: site.siteUrl
|
|
2944
|
+
};
|
|
2945
|
+
}
|
|
2946
|
+
function requireCloud(cloud) {
|
|
2947
|
+
if (!cloud) {
|
|
2948
|
+
logger.error("Sync requires cloud mode. Run gscdump init to set up cloud mode.");
|
|
2949
|
+
process$1.exit(1);
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
const statusCommand = defineCommand({
|
|
2953
|
+
meta: {
|
|
2954
|
+
name: "status",
|
|
2955
|
+
description: "Check sync status for a site"
|
|
2956
|
+
},
|
|
2957
|
+
args: {
|
|
2958
|
+
site: {
|
|
2959
|
+
type: "string",
|
|
2960
|
+
alias: "s",
|
|
2961
|
+
description: "Site URL"
|
|
2962
|
+
},
|
|
2963
|
+
json: {
|
|
2964
|
+
type: "boolean",
|
|
2965
|
+
default: false,
|
|
2966
|
+
description: "Output as JSON"
|
|
2967
|
+
}
|
|
2968
|
+
},
|
|
2969
|
+
async run({ args }) {
|
|
2970
|
+
const cloud = await getCloudClient();
|
|
2971
|
+
requireCloud(cloud);
|
|
2972
|
+
const config = await loadConfig();
|
|
2973
|
+
const { siteId } = await resolveCloudSite(cloud, args.site || config.defaultSite);
|
|
2974
|
+
const status = await cloud.syncStatus(siteId).catch((e) => {
|
|
2975
|
+
logger.error(`Failed to fetch sync status: ${e.message}`);
|
|
2976
|
+
process$1.exit(1);
|
|
2977
|
+
});
|
|
2978
|
+
if (args.json) {
|
|
2979
|
+
console.log(JSON.stringify(status, null, 2));
|
|
2980
|
+
return;
|
|
2981
|
+
}
|
|
2982
|
+
console.log();
|
|
2983
|
+
console.log(` \x1B[1m${status.siteUrl}\x1B[0m`);
|
|
2984
|
+
console.log();
|
|
2985
|
+
const statusColor = status.syncStatus === "synced" ? "\x1B[32m" : status.isSyncing ? "\x1B[33m" : status.syncStatus === "error" ? "\x1B[31m" : "\x1B[90m";
|
|
2986
|
+
console.log(` Status: ${statusColor}${status.syncStatus}\x1B[0m`);
|
|
2987
|
+
console.log(` Progress: ${progressBar(status.progress, 100, `${status.progress}%`)}`);
|
|
2988
|
+
console.log(` Days: \x1B[36m${status.daysSynced}\x1B[0m / ${status.daysAvailable} synced`);
|
|
2989
|
+
if (status.oldestDateSynced) console.log(` Range: ${status.oldestDateSynced} \x1B[90m→\x1B[0m ${status.newestDateSynced}`);
|
|
2990
|
+
console.log();
|
|
2991
|
+
console.log(" \x1B[1mJobs\x1B[0m");
|
|
2992
|
+
console.log(` Queued: ${status.jobs.queued}`);
|
|
2993
|
+
console.log(` Processing: ${status.jobs.processing}`);
|
|
2994
|
+
console.log(` Completed: \x1B[32m${status.jobs.completed}\x1B[0m`);
|
|
2995
|
+
if (status.jobs.failed > 0) console.log(` Failed: \x1B[31m${status.jobs.failed}\x1B[0m`);
|
|
2996
|
+
const tableNames = Object.keys(status.tables);
|
|
2997
|
+
if (tableNames.length > 0) {
|
|
2998
|
+
console.log();
|
|
2999
|
+
console.log(" \x1B[1mTables\x1B[0m");
|
|
3000
|
+
for (const name of tableNames) {
|
|
3001
|
+
const t = status.tables[name];
|
|
3002
|
+
const rows = t.totalRows > 0 ? ` (${t.totalRows.toLocaleString()} rows)` : "";
|
|
3003
|
+
console.log(` ${name}: \x1B[32m${t.completed}\x1B[0m done, ${t.queued} queued${t.failed > 0 ? `, \x1B[31m${t.failed} failed\x1B[0m` : ""}${rows}`);
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
if (status.failedJobs.length > 0) {
|
|
3007
|
+
console.log();
|
|
3008
|
+
console.log(" \x1B[31mFailed Jobs\x1B[0m");
|
|
3009
|
+
for (const j of status.failedJobs.slice(0, 5)) console.log(` ${j.date} ${j.tableName}: ${j.error}`);
|
|
3010
|
+
if (status.failedJobs.length > 5) console.log(` \x1B[90m... and ${status.failedJobs.length - 5} more\x1B[0m`);
|
|
3011
|
+
}
|
|
3012
|
+
console.log();
|
|
3013
|
+
}
|
|
3014
|
+
});
|
|
3015
|
+
const triggerCommand = defineCommand({
|
|
3016
|
+
meta: {
|
|
3017
|
+
name: "trigger",
|
|
3018
|
+
description: "Trigger a fresh sync for a site"
|
|
3019
|
+
},
|
|
3020
|
+
args: { site: {
|
|
3021
|
+
type: "string",
|
|
3022
|
+
alias: "s",
|
|
3023
|
+
description: "Site URL"
|
|
3024
|
+
} },
|
|
3025
|
+
async run({ args }) {
|
|
3026
|
+
const cloud = await getCloudClient();
|
|
3027
|
+
requireCloud(cloud);
|
|
3028
|
+
const config = await loadConfig();
|
|
3029
|
+
const { siteId, siteUrl } = await resolveCloudSite(cloud, args.site || config.defaultSite);
|
|
3030
|
+
const result = await cloud.triggerSync(siteId).catch((e) => {
|
|
3031
|
+
logger.error(`Failed to trigger sync: ${e.message}`);
|
|
3032
|
+
process$1.exit(1);
|
|
3033
|
+
});
|
|
3034
|
+
logger.success(`Sync triggered for ${siteUrl}`);
|
|
3035
|
+
console.log(` ${result.message}`);
|
|
3036
|
+
console.log();
|
|
3037
|
+
}
|
|
3038
|
+
});
|
|
3039
|
+
const syncCommand = defineCommand({
|
|
3040
|
+
meta: {
|
|
3041
|
+
name: "sync",
|
|
3042
|
+
description: "Sync status and management (cloud mode only)"
|
|
3043
|
+
},
|
|
3044
|
+
subCommands: {
|
|
3045
|
+
status: statusCommand,
|
|
3046
|
+
trigger: triggerCommand
|
|
3047
|
+
}
|
|
3048
|
+
});
|
|
3049
|
+
|
|
3050
|
+
//#endregion
|
|
3051
|
+
//#region src/commands/unregister.ts
|
|
3052
|
+
const unregisterCommand = defineCommand({
|
|
3053
|
+
meta: {
|
|
3054
|
+
name: "unregister",
|
|
3055
|
+
description: "Unregister a site from syncing (cloud mode only)"
|
|
3056
|
+
},
|
|
3057
|
+
args: { site: {
|
|
3058
|
+
type: "positional",
|
|
3059
|
+
description: "Site URL to unregister",
|
|
3060
|
+
required: false
|
|
3061
|
+
} },
|
|
3062
|
+
async run({ args }) {
|
|
3063
|
+
const cloud = await getCloudClient();
|
|
3064
|
+
if (!cloud) {
|
|
3065
|
+
logger.error("Unregister requires cloud mode. Run gscdump init to set up.");
|
|
3066
|
+
process$1.exit(1);
|
|
3067
|
+
}
|
|
3068
|
+
const config = await loadConfig();
|
|
3069
|
+
const target = args.site || config.defaultSite;
|
|
3070
|
+
const me = await cloud.me().catch((e) => {
|
|
3071
|
+
logger.error(`Failed to fetch sites: ${e.message}`);
|
|
3072
|
+
process$1.exit(1);
|
|
3073
|
+
});
|
|
3074
|
+
if (me.sites.length === 0) {
|
|
3075
|
+
logger.warn("No registered sites.");
|
|
3076
|
+
return;
|
|
3077
|
+
}
|
|
3078
|
+
let site = target ? me.sites.find((s) => s.siteUrl === target || s.siteUrl.includes(target)) : void 0;
|
|
3079
|
+
if (!site) {
|
|
3080
|
+
const selected = await select({
|
|
3081
|
+
message: "Select a site to unregister",
|
|
3082
|
+
options: me.sites.map((s) => ({
|
|
3083
|
+
value: s.siteId,
|
|
3084
|
+
label: s.siteUrl,
|
|
3085
|
+
hint: s.syncStatus || "unknown"
|
|
3086
|
+
}))
|
|
3087
|
+
});
|
|
3088
|
+
if (isCancel(selected)) {
|
|
3089
|
+
cancel("Cancelled");
|
|
3090
|
+
process$1.exit(0);
|
|
3091
|
+
}
|
|
3092
|
+
site = me.sites.find((s) => s.siteId === selected);
|
|
3093
|
+
}
|
|
3094
|
+
const confirmed = await confirm({ message: `Unregister ${site.siteUrl}? This will stop syncing and delete pending jobs.` });
|
|
3095
|
+
if (isCancel(confirmed) || !confirmed) {
|
|
3096
|
+
cancel("Cancelled");
|
|
3097
|
+
process$1.exit(0);
|
|
3098
|
+
}
|
|
3099
|
+
const result = await cloud.deleteSite(site.siteId).catch((e) => {
|
|
3100
|
+
logger.error(`Failed to unregister: ${e.message}`);
|
|
3101
|
+
process$1.exit(1);
|
|
3102
|
+
});
|
|
3103
|
+
logger.success(`Unregistered ${result.siteUrl}`);
|
|
3104
|
+
}
|
|
3105
|
+
});
|
|
3106
|
+
|
|
1651
3107
|
//#endregion
|
|
1652
3108
|
//#region src/index.ts
|
|
1653
3109
|
runMain(defineCommand({
|
|
@@ -1662,14 +3118,19 @@ runMain(defineCommand({
|
|
|
1662
3118
|
query: queryCommand,
|
|
1663
3119
|
sites: sitesCommand,
|
|
1664
3120
|
sitemaps: sitemapsCommand,
|
|
3121
|
+
register: registerCommand,
|
|
3122
|
+
unregister: unregisterCommand,
|
|
3123
|
+
sync: syncCommand,
|
|
3124
|
+
indexing: indexingCommand,
|
|
3125
|
+
analysis: analysisCommand,
|
|
1665
3126
|
auth: authCommand,
|
|
1666
3127
|
config: configCommand,
|
|
1667
3128
|
mcp: mcpCommand
|
|
1668
3129
|
},
|
|
1669
3130
|
setup() {
|
|
1670
|
-
if (!process.argv.includes("mcp")) showSplash();
|
|
3131
|
+
if (!process$1.argv.includes("mcp")) showSplash();
|
|
1671
3132
|
}
|
|
1672
3133
|
}));
|
|
1673
3134
|
|
|
1674
3135
|
//#endregion
|
|
1675
|
-
export {
|
|
3136
|
+
export { };
|