@gscdump/cli 0.1.3 → 0.3.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 +693 -1445
- package/package.json +5 -9
- package/dist/_chunks/libs/drizzle-orm.mjs +0 -1043
package/dist/index.mjs
CHANGED
|
@@ -1,22 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as eq } from "./_chunks/libs/drizzle-orm.mjs";
|
|
3
2
|
import "node:module";
|
|
4
3
|
import process from "node:process";
|
|
5
4
|
import { defineCommand, runMain } from "citty";
|
|
6
|
-
import path from "node:path";
|
|
7
|
-
import { batchInspectUrls, batchRequestIndexingForPaths, createGscDb, getIndexingStats, getLastSyncedDate, getSiteByProperty, setupSchema, sitePathDateAnalytics, syncCountries, syncDevices, syncKeywordPaths, syncKeywords, syncPages, syncSites, updateLastSynced } from "@gscdump/db";
|
|
8
|
-
import { createProvider, daysAgo, fetchCannibalizationAnalysis, fetchDecayAnalysis, fetchMoversAnalysis, fetchOpportunityAnalysis, fetchStrikingDistanceAnalysis, fetchZeroClickAnalysis } from "@gscdump/query";
|
|
9
|
-
import dayjs from "dayjs";
|
|
10
|
-
import betterSqlite3 from "db0/connectors/better-sqlite3";
|
|
11
5
|
import fs from "node:fs/promises";
|
|
12
6
|
import { createServer } from "node:http";
|
|
13
|
-
import
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { cancel, isCancel, multiselect, select, text } from "@clack/prompts";
|
|
14
9
|
import { OAuth2Client } from "google-auth-library";
|
|
15
10
|
import os from "node:os";
|
|
16
11
|
import { consola } from "consola";
|
|
17
|
-
import { deleteSitemap, fetchSitemap, fetchSitemaps, fetchSites, formatErrorForCli, googleSearchConsole, submitSitemap } from "gscdump";
|
|
18
|
-
import {
|
|
12
|
+
import { batchInspectUrls, batchRequestIndexing, deleteSitemap, fetchSitemap, fetchSitemaps, fetchSites, fetchSitesWithSitemaps, formatErrorForCli, getIndexingMetadata, googleSearchConsole, inspectUrl, requestIndexing, submitSitemap } from "gscdump";
|
|
13
|
+
import { between, country, date, device, gsc, page, query, searchAppearance } from "gscdump/query";
|
|
19
14
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
15
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
16
|
+
import { z } from "zod";
|
|
20
17
|
|
|
21
18
|
//#region rolldown:runtime
|
|
22
19
|
var __defProp = Object.defineProperty;
|
|
@@ -103,22 +100,6 @@ function progressBar(current, total, label, width = 30) {
|
|
|
103
100
|
function clearLine() {
|
|
104
101
|
process.stdout.write("\r\x1B[K");
|
|
105
102
|
}
|
|
106
|
-
function parsePeriod(periodStr) {
|
|
107
|
-
const match = periodStr.match(/^(\d+)([dmy])$/i);
|
|
108
|
-
if (!match) return null;
|
|
109
|
-
const amount = Number.parseInt(match[1], 10);
|
|
110
|
-
const unit = {
|
|
111
|
-
d: "days",
|
|
112
|
-
m: "months",
|
|
113
|
-
y: "years"
|
|
114
|
-
}[match[2].toLowerCase()];
|
|
115
|
-
if (!unit || amount <= 0) return null;
|
|
116
|
-
if (amount > 450 && unit === "days") return null;
|
|
117
|
-
return {
|
|
118
|
-
amount,
|
|
119
|
-
unit
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
103
|
function toCSV(data, columns) {
|
|
123
104
|
return [columns.join(","), ...data.map((row) => columns.map((col) => {
|
|
124
105
|
const val = row[col];
|
|
@@ -421,227 +402,6 @@ async function getAuth(opts = {}) {
|
|
|
421
402
|
return authenticate(await getAuthCredentials(interactive), interactive);
|
|
422
403
|
}
|
|
423
404
|
|
|
424
|
-
//#endregion
|
|
425
|
-
//#region src/commands/analyze.ts
|
|
426
|
-
function getProviderForSource$2(auth, db, source) {
|
|
427
|
-
if (source === "api") return createProvider({ auth });
|
|
428
|
-
if (source === "db") {
|
|
429
|
-
if (!db) throw new Error("Database required for db source");
|
|
430
|
-
return createProvider({ db });
|
|
431
|
-
}
|
|
432
|
-
return db ? createProvider({
|
|
433
|
-
auth,
|
|
434
|
-
db
|
|
435
|
-
}) : createProvider({ auth });
|
|
436
|
-
}
|
|
437
|
-
const ANALYSIS_TYPES = [
|
|
438
|
-
"striking-distance",
|
|
439
|
-
"opportunity",
|
|
440
|
-
"movers",
|
|
441
|
-
"decay",
|
|
442
|
-
"cannibalization",
|
|
443
|
-
"zero-click"
|
|
444
|
-
];
|
|
445
|
-
const analyzeCommand = defineCommand({
|
|
446
|
-
meta: {
|
|
447
|
-
name: "analyze",
|
|
448
|
-
description: "Run SEO analysis on site data"
|
|
449
|
-
},
|
|
450
|
-
args: {
|
|
451
|
-
type: {
|
|
452
|
-
type: "positional",
|
|
453
|
-
required: true,
|
|
454
|
-
description: `Analysis type: ${ANALYSIS_TYPES.join(", ")}`
|
|
455
|
-
},
|
|
456
|
-
site: {
|
|
457
|
-
type: "string",
|
|
458
|
-
alias: "s",
|
|
459
|
-
description: "Site URL (e.g., sc-domain:example.com)"
|
|
460
|
-
},
|
|
461
|
-
period: {
|
|
462
|
-
type: "string",
|
|
463
|
-
alias: "p",
|
|
464
|
-
default: "28d",
|
|
465
|
-
description: "Period to analyze (e.g., 7d, 28d, 3m)"
|
|
466
|
-
},
|
|
467
|
-
limit: {
|
|
468
|
-
type: "string",
|
|
469
|
-
alias: "l",
|
|
470
|
-
default: "20",
|
|
471
|
-
description: "Number of results to show"
|
|
472
|
-
},
|
|
473
|
-
json: {
|
|
474
|
-
type: "boolean",
|
|
475
|
-
default: false,
|
|
476
|
-
description: "Output as JSON"
|
|
477
|
-
},
|
|
478
|
-
db: {
|
|
479
|
-
type: "string",
|
|
480
|
-
alias: "d",
|
|
481
|
-
description: "Path to SQLite database"
|
|
482
|
-
},
|
|
483
|
-
source: {
|
|
484
|
-
type: "string",
|
|
485
|
-
default: "auto",
|
|
486
|
-
description: "Data source: api, db, auto"
|
|
487
|
-
},
|
|
488
|
-
minPosition: {
|
|
489
|
-
type: "string",
|
|
490
|
-
description: "Minimum position (striking-distance)"
|
|
491
|
-
},
|
|
492
|
-
maxPosition: {
|
|
493
|
-
type: "string",
|
|
494
|
-
description: "Maximum position (striking-distance, zero-click)"
|
|
495
|
-
},
|
|
496
|
-
minImpressions: {
|
|
497
|
-
type: "string",
|
|
498
|
-
description: "Minimum impressions"
|
|
499
|
-
},
|
|
500
|
-
minClicks: {
|
|
501
|
-
type: "string",
|
|
502
|
-
description: "Minimum clicks (decay)"
|
|
503
|
-
},
|
|
504
|
-
threshold: {
|
|
505
|
-
type: "string",
|
|
506
|
-
description: "Threshold percentage 0-1 (decay, movers)"
|
|
507
|
-
}
|
|
508
|
-
},
|
|
509
|
-
async run({ args }) {
|
|
510
|
-
const analysisType = args.type;
|
|
511
|
-
if (!ANALYSIS_TYPES.includes(analysisType)) {
|
|
512
|
-
logger.error(`Unknown analysis type: ${analysisType}`);
|
|
513
|
-
logger.info(`Available: ${ANALYSIS_TYPES.join(", ")}`);
|
|
514
|
-
process.exit(1);
|
|
515
|
-
}
|
|
516
|
-
const config = await loadConfig();
|
|
517
|
-
const siteArg = args.site || config.defaultSite;
|
|
518
|
-
if (!siteArg) {
|
|
519
|
-
logger.error("Site required. Use --site or set defaultSite in config.");
|
|
520
|
-
process.exit(1);
|
|
521
|
-
}
|
|
522
|
-
const dbPath = args.db || config.defaultDb;
|
|
523
|
-
const auth = await getAuth({
|
|
524
|
-
interactive: false,
|
|
525
|
-
config
|
|
526
|
-
});
|
|
527
|
-
const period = parsePeriod(args.period);
|
|
528
|
-
if (!period) {
|
|
529
|
-
logger.error(`Invalid period: ${args.period}`);
|
|
530
|
-
process.exit(1);
|
|
531
|
-
}
|
|
532
|
-
const endDate = dayjs().subtract(3, "day");
|
|
533
|
-
const startDate = endDate.subtract(period.amount, period.unit);
|
|
534
|
-
const prevEndDate = startDate.subtract(1, "day");
|
|
535
|
-
const prevStartDate = prevEndDate.subtract(period.amount, period.unit);
|
|
536
|
-
const range = {
|
|
537
|
-
period: {
|
|
538
|
-
start: startDate.format("YYYY-MM-DD"),
|
|
539
|
-
end: endDate.format("YYYY-MM-DD")
|
|
540
|
-
},
|
|
541
|
-
prevPeriod: {
|
|
542
|
-
start: prevStartDate.format("YYYY-MM-DD"),
|
|
543
|
-
end: prevEndDate.format("YYYY-MM-DD")
|
|
544
|
-
}
|
|
545
|
-
};
|
|
546
|
-
const provider = getProviderForSource$2(auth, dbPath ? createGscDb(betterSqlite3({ name: path.resolve(dbPath) })).db : null, args.source);
|
|
547
|
-
const limit = Number.parseInt(args.limit, 10) || 20;
|
|
548
|
-
if (!args.json) {
|
|
549
|
-
console.log();
|
|
550
|
-
console.log(` \x1B[1m${siteArg}\x1B[0m`);
|
|
551
|
-
console.log(` \x1B[90mSource: ${provider.source}\x1B[0m`);
|
|
552
|
-
console.log(` \x1B[90mPeriod: ${range.period.start} → ${range.period.end}\x1B[0m`);
|
|
553
|
-
console.log(` \x1B[90mAnalysis: ${analysisType}\x1B[0m`);
|
|
554
|
-
console.log();
|
|
555
|
-
}
|
|
556
|
-
let results;
|
|
557
|
-
switch (analysisType) {
|
|
558
|
-
case "striking-distance":
|
|
559
|
-
results = await fetchStrikingDistanceAnalysis(provider, siteArg, range, {
|
|
560
|
-
minPosition: args.minPosition ? Number(args.minPosition) : void 0,
|
|
561
|
-
maxPosition: args.maxPosition ? Number(args.maxPosition) : void 0,
|
|
562
|
-
minImpressions: args.minImpressions ? Number(args.minImpressions) : void 0
|
|
563
|
-
}).catch(gscErrorHandler);
|
|
564
|
-
break;
|
|
565
|
-
case "opportunity":
|
|
566
|
-
results = await fetchOpportunityAnalysis(provider, siteArg, range, { minImpressions: args.minImpressions ? Number(args.minImpressions) : void 0 }).catch(gscErrorHandler);
|
|
567
|
-
break;
|
|
568
|
-
case "movers":
|
|
569
|
-
results = await fetchMoversAnalysis(provider, siteArg, range, {
|
|
570
|
-
changeThreshold: args.threshold ? Number(args.threshold) : void 0,
|
|
571
|
-
minImpressions: args.minImpressions ? Number(args.minImpressions) : void 0
|
|
572
|
-
}).catch(gscErrorHandler);
|
|
573
|
-
break;
|
|
574
|
-
case "decay":
|
|
575
|
-
results = await fetchDecayAnalysis(provider, siteArg, range, {
|
|
576
|
-
minPreviousClicks: args.minClicks ? Number(args.minClicks) : void 0,
|
|
577
|
-
threshold: args.threshold ? Number(args.threshold) : void 0
|
|
578
|
-
}).catch(gscErrorHandler);
|
|
579
|
-
break;
|
|
580
|
-
case "cannibalization":
|
|
581
|
-
results = await fetchCannibalizationAnalysis(provider, siteArg, range, { minImpressions: args.minImpressions ? Number(args.minImpressions) : void 0 }).catch(gscErrorHandler);
|
|
582
|
-
break;
|
|
583
|
-
case "zero-click":
|
|
584
|
-
results = await fetchZeroClickAnalysis(provider, siteArg, range, {
|
|
585
|
-
minImpressions: args.minImpressions ? Number(args.minImpressions) : void 0,
|
|
586
|
-
maxPosition: args.maxPosition ? Number(args.maxPosition) : void 0
|
|
587
|
-
}).catch(gscErrorHandler);
|
|
588
|
-
break;
|
|
589
|
-
}
|
|
590
|
-
if (args.json) {
|
|
591
|
-
console.log(JSON.stringify({
|
|
592
|
-
site: siteArg,
|
|
593
|
-
source: provider.source,
|
|
594
|
-
analysis: analysisType,
|
|
595
|
-
range,
|
|
596
|
-
results
|
|
597
|
-
}, null, 2));
|
|
598
|
-
return;
|
|
599
|
-
}
|
|
600
|
-
const arr = Array.isArray(results) ? results : [];
|
|
601
|
-
const display = arr.slice(0, limit);
|
|
602
|
-
if (display.length === 0) {
|
|
603
|
-
logger.warn("No results found");
|
|
604
|
-
return;
|
|
605
|
-
}
|
|
606
|
-
console.log(` \x1B[1mResults\x1B[0m \x1B[90m(${arr.length} total, showing ${display.length})\x1B[0m`);
|
|
607
|
-
for (const item of display) formatResultItem(analysisType, item);
|
|
608
|
-
console.log();
|
|
609
|
-
logger.success("Analysis complete");
|
|
610
|
-
}
|
|
611
|
-
});
|
|
612
|
-
function formatResultItem(type, item) {
|
|
613
|
-
const keyword = item.keyword || item.query || "";
|
|
614
|
-
const page = item.page || "";
|
|
615
|
-
switch (type) {
|
|
616
|
-
case "striking-distance":
|
|
617
|
-
console.log(` ${keyword.slice(0, 40).padEnd(40)} pos ${String(item.position || 0).padStart(5)} ${formatNumber$1(item.impressions)} impr ${formatNumber$1(item.potentialClicks)} potential`);
|
|
618
|
-
break;
|
|
619
|
-
case "opportunity":
|
|
620
|
-
console.log(` ${keyword.slice(0, 40).padEnd(40)} score ${String((item.score || 0).toFixed(1)).padStart(6)} ${formatNumber$1(item.impressions)} impr`);
|
|
621
|
-
break;
|
|
622
|
-
case "movers":
|
|
623
|
-
console.log(` ${keyword.slice(0, 40).padEnd(40)} ${formatChange(item.clicksChange)} clicks ${formatChange(item.positionChange)} pos`);
|
|
624
|
-
break;
|
|
625
|
-
case "decay":
|
|
626
|
-
console.log(` ${page.replace(/^https?:\/\/[^/]+/, "").slice(0, 50).padEnd(50)} -${formatNumber$1(item.lostClicks)} clicks (${((item.declinePercent || 0) * 100).toFixed(0)}% decline)`);
|
|
627
|
-
break;
|
|
628
|
-
case "cannibalization":
|
|
629
|
-
console.log(` ${keyword.slice(0, 40).padEnd(40)} ${item.pageCount} pages ${formatNumber$1(item.clicks)} clicks spread ${item.positionSpread}`);
|
|
630
|
-
break;
|
|
631
|
-
case "zero-click":
|
|
632
|
-
console.log(` ${keyword.slice(0, 40).padEnd(40)} pos ${String(item.position || 0).padStart(5)} ${formatNumber$1(item.impressions)} impr ${((item.ctr || 0) * 100).toFixed(2)}% ctr`);
|
|
633
|
-
break;
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
function formatNumber$1(val) {
|
|
637
|
-
if (val === void 0 || val === null) return "-";
|
|
638
|
-
return val.toLocaleString();
|
|
639
|
-
}
|
|
640
|
-
function formatChange(val) {
|
|
641
|
-
if (val === void 0 || val === null || !Number.isFinite(val)) return "\x1B[90m-\x1B[0m";
|
|
642
|
-
return `${val > 0 ? "\x1B[32m" : val < 0 ? "\x1B[31m" : "\x1B[90m"}${val > 0 ? "+" : ""}${val.toFixed(1)}%\x1B[0m`;
|
|
643
|
-
}
|
|
644
|
-
|
|
645
405
|
//#endregion
|
|
646
406
|
//#region src/commands/auth.ts
|
|
647
407
|
const statusCommand = defineCommand({
|
|
@@ -720,255 +480,6 @@ const authCommand = defineCommand({
|
|
|
720
480
|
}
|
|
721
481
|
});
|
|
722
482
|
|
|
723
|
-
//#endregion
|
|
724
|
-
//#region src/commands/compare.ts
|
|
725
|
-
function getProviderForSource$1(auth, db, source) {
|
|
726
|
-
if (source === "api") return createProvider({ auth });
|
|
727
|
-
if (source === "db") {
|
|
728
|
-
if (!db) throw new Error("Database required for db source");
|
|
729
|
-
return createProvider({ db });
|
|
730
|
-
}
|
|
731
|
-
return db ? createProvider({
|
|
732
|
-
auth,
|
|
733
|
-
db
|
|
734
|
-
}) : createProvider({ auth });
|
|
735
|
-
}
|
|
736
|
-
const SEARCH_TYPES = [
|
|
737
|
-
"web",
|
|
738
|
-
"image",
|
|
739
|
-
"video",
|
|
740
|
-
"news",
|
|
741
|
-
"discover",
|
|
742
|
-
"googleNews"
|
|
743
|
-
];
|
|
744
|
-
function formatPercent(val) {
|
|
745
|
-
if (val === void 0 || val === null || !Number.isFinite(val)) return "\x1B[90m-\x1B[0m";
|
|
746
|
-
return `${val > 0 ? "\x1B[32m" : val < 0 ? "\x1B[31m" : "\x1B[90m"}${val > 0 ? "+" : ""}${val.toFixed(1)}%\x1B[0m`;
|
|
747
|
-
}
|
|
748
|
-
function formatNumber(val) {
|
|
749
|
-
if (val === void 0 || val === null) return "-";
|
|
750
|
-
return val.toLocaleString();
|
|
751
|
-
}
|
|
752
|
-
const compareCommand = defineCommand({
|
|
753
|
-
meta: {
|
|
754
|
-
name: "compare",
|
|
755
|
-
description: "Compare site performance across periods"
|
|
756
|
-
},
|
|
757
|
-
args: {
|
|
758
|
-
site: {
|
|
759
|
-
type: "string",
|
|
760
|
-
alias: "s",
|
|
761
|
-
description: "Site URL (e.g., sc-domain:example.com)"
|
|
762
|
-
},
|
|
763
|
-
period: {
|
|
764
|
-
type: "string",
|
|
765
|
-
alias: "p",
|
|
766
|
-
default: "7d",
|
|
767
|
-
description: "Period to compare (e.g., 7d, 30d, 3m)"
|
|
768
|
-
},
|
|
769
|
-
from: {
|
|
770
|
-
type: "string",
|
|
771
|
-
description: "Custom start date (YYYY-MM-DD)"
|
|
772
|
-
},
|
|
773
|
-
to: {
|
|
774
|
-
type: "string",
|
|
775
|
-
description: "Custom end date (YYYY-MM-DD)"
|
|
776
|
-
},
|
|
777
|
-
vs: {
|
|
778
|
-
type: "string",
|
|
779
|
-
description: "Comparison period start date (YYYY-MM-DD)"
|
|
780
|
-
},
|
|
781
|
-
json: {
|
|
782
|
-
type: "boolean",
|
|
783
|
-
default: false,
|
|
784
|
-
description: "Output as JSON"
|
|
785
|
-
},
|
|
786
|
-
type: {
|
|
787
|
-
type: "string",
|
|
788
|
-
alias: "t",
|
|
789
|
-
default: "summary",
|
|
790
|
-
description: "Output type: summary, pages, keywords"
|
|
791
|
-
},
|
|
792
|
-
limit: {
|
|
793
|
-
type: "string",
|
|
794
|
-
alias: "l",
|
|
795
|
-
default: "10",
|
|
796
|
-
description: "Number of items to show (for pages/keywords)"
|
|
797
|
-
},
|
|
798
|
-
fresh: {
|
|
799
|
-
type: "boolean",
|
|
800
|
-
default: false,
|
|
801
|
-
description: "Include fresh/unfinalized data (last 3 days)"
|
|
802
|
-
},
|
|
803
|
-
searchType: {
|
|
804
|
-
type: "string",
|
|
805
|
-
default: "web",
|
|
806
|
-
description: "Search type: web, image, video, news, discover, googleNews"
|
|
807
|
-
},
|
|
808
|
-
db: {
|
|
809
|
-
type: "string",
|
|
810
|
-
alias: "d",
|
|
811
|
-
description: "Path to SQLite database for local queries"
|
|
812
|
-
},
|
|
813
|
-
source: {
|
|
814
|
-
type: "string",
|
|
815
|
-
default: "auto",
|
|
816
|
-
description: "Data source: api, db, auto"
|
|
817
|
-
},
|
|
818
|
-
quiet: {
|
|
819
|
-
type: "boolean",
|
|
820
|
-
alias: "q",
|
|
821
|
-
default: false,
|
|
822
|
-
description: "Suppress output"
|
|
823
|
-
}
|
|
824
|
-
},
|
|
825
|
-
async run({ args }) {
|
|
826
|
-
const config = await loadConfig();
|
|
827
|
-
const siteArg = args.site || config.defaultSite;
|
|
828
|
-
if (!siteArg) {
|
|
829
|
-
logger.error("Site required. Use --site or set defaultSite in config.");
|
|
830
|
-
process.exit(1);
|
|
831
|
-
}
|
|
832
|
-
const dbPath = args.db || config.defaultDb;
|
|
833
|
-
const periodArg = args.period === "7d" && config.defaultPeriod ? config.defaultPeriod : args.period;
|
|
834
|
-
const auth = await getAuth({
|
|
835
|
-
interactive: false,
|
|
836
|
-
config
|
|
837
|
-
});
|
|
838
|
-
let range;
|
|
839
|
-
if (args.from && args.to) {
|
|
840
|
-
const start = dayjs(args.from);
|
|
841
|
-
const end = dayjs(args.to);
|
|
842
|
-
if (!start.isValid() || !end.isValid()) {
|
|
843
|
-
logger.error("Invalid date format. Use YYYY-MM-DD");
|
|
844
|
-
process.exit(1);
|
|
845
|
-
}
|
|
846
|
-
const periodDays = end.diff(start, "day");
|
|
847
|
-
let prevStart;
|
|
848
|
-
let prevEnd;
|
|
849
|
-
if (args.vs) {
|
|
850
|
-
prevStart = dayjs(args.vs);
|
|
851
|
-
if (!prevStart.isValid()) {
|
|
852
|
-
logger.error("Invalid --vs date format. Use YYYY-MM-DD");
|
|
853
|
-
process.exit(1);
|
|
854
|
-
}
|
|
855
|
-
prevEnd = prevStart.add(periodDays, "day");
|
|
856
|
-
} else {
|
|
857
|
-
prevEnd = start.subtract(1, "day");
|
|
858
|
-
prevStart = prevEnd.subtract(periodDays, "day");
|
|
859
|
-
}
|
|
860
|
-
range = {
|
|
861
|
-
period: {
|
|
862
|
-
start: start.format("YYYY-MM-DD"),
|
|
863
|
-
end: end.format("YYYY-MM-DD")
|
|
864
|
-
},
|
|
865
|
-
prevPeriod: {
|
|
866
|
-
start: prevStart.format("YYYY-MM-DD"),
|
|
867
|
-
end: prevEnd.format("YYYY-MM-DD")
|
|
868
|
-
}
|
|
869
|
-
};
|
|
870
|
-
} else {
|
|
871
|
-
const period = parsePeriod(periodArg);
|
|
872
|
-
if (!period) {
|
|
873
|
-
logger.error(`Invalid period: ${periodArg}`);
|
|
874
|
-
process.exit(1);
|
|
875
|
-
}
|
|
876
|
-
const daysOffset = args.fresh ? 1 : 3;
|
|
877
|
-
const endDate = dayjs().subtract(daysOffset, "day");
|
|
878
|
-
const startDate = endDate.subtract(period.amount, period.unit);
|
|
879
|
-
const prevEndDate = startDate.subtract(1, "day");
|
|
880
|
-
const prevStartDate = prevEndDate.subtract(period.amount, period.unit);
|
|
881
|
-
range = {
|
|
882
|
-
period: {
|
|
883
|
-
start: startDate.format("YYYY-MM-DD"),
|
|
884
|
-
end: endDate.format("YYYY-MM-DD")
|
|
885
|
-
},
|
|
886
|
-
prevPeriod: {
|
|
887
|
-
start: prevStartDate.format("YYYY-MM-DD"),
|
|
888
|
-
end: prevEndDate.format("YYYY-MM-DD")
|
|
889
|
-
}
|
|
890
|
-
};
|
|
891
|
-
}
|
|
892
|
-
const searchType = SEARCH_TYPES.includes(args.searchType) ? args.searchType : "web";
|
|
893
|
-
const effectiveSource = searchType !== "web" ? "api" : args.source || "auto";
|
|
894
|
-
if (searchType !== "web" && args.source === "db") logger.warn(`Search type '${searchType}' requires API. Ignoring --source db.`);
|
|
895
|
-
const provider = getProviderForSource$1(auth, dbPath ? createGscDb(betterSqlite3({ name: path.resolve(dbPath) })).db : null, effectiveSource);
|
|
896
|
-
if (args.json) {
|
|
897
|
-
const [dates, pages, keywords] = await Promise.all([
|
|
898
|
-
provider.getDatesWithComparison(siteArg, range),
|
|
899
|
-
provider.getPagesWithComparison(siteArg, range),
|
|
900
|
-
provider.getKeywordsWithComparison(siteArg, range)
|
|
901
|
-
]).catch(gscErrorHandler);
|
|
902
|
-
console.log(JSON.stringify({
|
|
903
|
-
site: siteArg,
|
|
904
|
-
source: provider.source,
|
|
905
|
-
range,
|
|
906
|
-
dates,
|
|
907
|
-
pages,
|
|
908
|
-
keywords
|
|
909
|
-
}, null, 2));
|
|
910
|
-
return;
|
|
911
|
-
}
|
|
912
|
-
if (args.quiet) return;
|
|
913
|
-
console.log();
|
|
914
|
-
console.log(` \x1B[1m${siteArg}\x1B[0m`);
|
|
915
|
-
console.log(` \x1B[90mSource: ${provider.source}\x1B[0m`);
|
|
916
|
-
console.log(` \x1B[90mCurrent: ${range.period.start} → ${range.period.end}\x1B[0m`);
|
|
917
|
-
console.log(` \x1B[90mPrevious: ${range.prevPeriod?.start} → ${range.prevPeriod?.end}\x1B[0m`);
|
|
918
|
-
console.log();
|
|
919
|
-
if (args.type === "summary" || args.type === "all") {
|
|
920
|
-
const dates = await provider.getDatesWithComparison(siteArg, range).catch(gscErrorHandler);
|
|
921
|
-
console.log(" \x1B[1mSummary\x1B[0m");
|
|
922
|
-
console.log(` ┌─────────────┬──────────────┬──────────────┬─────────────┐`);
|
|
923
|
-
console.log(` │ │ \x1B[1mCurrent\x1B[0m │ \x1B[1mPrevious\x1B[0m │ \x1B[1mChange\x1B[0m │`);
|
|
924
|
-
console.log(` ├─────────────┼──────────────┼──────────────┼─────────────┤`);
|
|
925
|
-
console.log(` │ Clicks │ ${formatNumber(dates.metadata.totals.current.clicks).padStart(12)} │ ${formatNumber(dates.metadata.totals.previous.clicks).padStart(12)} │ ${formatPercent(dates.metadata.totals.clicksPercent).padStart(19)} │`);
|
|
926
|
-
console.log(` │ Impressions │ ${formatNumber(dates.metadata.totals.current.impressions).padStart(12)} │ ${formatNumber(dates.metadata.totals.previous.impressions).padStart(12)} │ ${formatPercent(dates.metadata.totals.impressionsPercent).padStart(19)} │`);
|
|
927
|
-
console.log(` │ CTR │ ${(dates.metadata.totals.current.ctr * 100).toFixed(2).padStart(10)}% │ ${(dates.metadata.totals.previous.ctr * 100).toFixed(2).padStart(10)}% │ ${formatPercent(dates.metadata.totals.ctrPercent).padStart(19)} │`);
|
|
928
|
-
console.log(` │ Position │ ${dates.metadata.totals.current.position.toFixed(1).padStart(12)} │ ${dates.metadata.totals.previous.position.toFixed(1).padStart(12)} │ ${formatPercent(dates.metadata.totals.positionPercent).padStart(19)} │`);
|
|
929
|
-
console.log(` └─────────────┴──────────────┴──────────────┴─────────────┘`);
|
|
930
|
-
console.log();
|
|
931
|
-
}
|
|
932
|
-
const limit = Number.parseInt(args.limit, 10) || 10;
|
|
933
|
-
if (args.type === "pages" || args.type === "all") {
|
|
934
|
-
const pages = await provider.getPagesWithComparison(siteArg, range).catch(gscErrorHandler);
|
|
935
|
-
const topPages = pages.current.slice(0, limit);
|
|
936
|
-
const lostPages = pages.previous.filter((p) => p.lost).slice(0, 5);
|
|
937
|
-
console.log(` \x1B[1mTop Pages\x1B[0m \x1B[90m(${pages.current.length} total)\x1B[0m`);
|
|
938
|
-
for (const p of topPages) {
|
|
939
|
-
const pagePath = p.page.replace(/^https?:\/\/[^/]+/, "") || "/";
|
|
940
|
-
console.log(` ${pagePath.slice(0, 50).padEnd(50)} ${formatNumber(p.clicks).padStart(8)} clicks ${formatPercent(p.clicksPercent)}`);
|
|
941
|
-
}
|
|
942
|
-
if (lostPages.length > 0) {
|
|
943
|
-
console.log();
|
|
944
|
-
console.log(` \x1B[1mLost Pages\x1B[0m \x1B[90m(had traffic in previous period)\x1B[0m`);
|
|
945
|
-
for (const p of lostPages) {
|
|
946
|
-
const pagePath = p.page.replace(/^https?:\/\/[^/]+/, "") || "/";
|
|
947
|
-
console.log(` \x1B[31m${pagePath.slice(0, 50).padEnd(50)}\x1B[0m ${formatNumber(p.prevClicks).padStart(8)} prev clicks`);
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
console.log();
|
|
951
|
-
}
|
|
952
|
-
if (args.type === "keywords" || args.type === "all") {
|
|
953
|
-
const keywords = await provider.getKeywordsWithComparison(siteArg, range).catch(gscErrorHandler);
|
|
954
|
-
const topKeywords = keywords.current.slice(0, limit);
|
|
955
|
-
const lostKeywords = keywords.previous.filter((k) => k.lost).slice(0, 5);
|
|
956
|
-
console.log(` \x1B[1mTop Keywords\x1B[0m \x1B[90m(${keywords.current.length} total)\x1B[0m`);
|
|
957
|
-
for (const k of topKeywords) {
|
|
958
|
-
const posChange = k.prevPosition ? formatPercent(-((k.position || 0) - k.prevPosition) / k.prevPosition * 100) : "";
|
|
959
|
-
console.log(` ${k.keyword.slice(0, 40).padEnd(40)} pos ${(k.position || 0).toFixed(1).padStart(5)} ${posChange} ${formatNumber(k.clicks).padStart(6)} clicks`);
|
|
960
|
-
}
|
|
961
|
-
if (lostKeywords.length > 0) {
|
|
962
|
-
console.log();
|
|
963
|
-
console.log(` \x1B[1mLost Keywords\x1B[0m \x1B[90m(had traffic in previous period)\x1B[0m`);
|
|
964
|
-
for (const k of lostKeywords) console.log(` \x1B[31m${k.keyword.slice(0, 40).padEnd(40)}\x1B[0m pos ${(k.prevPosition || 0).toFixed(1).padStart(5)} ${formatNumber(k.prevClicks).padStart(6)} prev clicks`);
|
|
965
|
-
}
|
|
966
|
-
console.log();
|
|
967
|
-
}
|
|
968
|
-
logger.success("Comparison complete");
|
|
969
|
-
}
|
|
970
|
-
});
|
|
971
|
-
|
|
972
483
|
//#endregion
|
|
973
484
|
//#region src/commands/config.ts
|
|
974
485
|
const showCommand = defineCommand({
|
|
@@ -1064,747 +575,190 @@ const configCommand = defineCommand({
|
|
|
1064
575
|
|
|
1065
576
|
//#endregion
|
|
1066
577
|
//#region src/commands/dump.ts
|
|
1067
|
-
/**
|
|
1068
|
-
* Maps CLI source option to provider options
|
|
1069
|
-
* - 'api': API only (no db)
|
|
1070
|
-
* - 'db': DB only (no auth, errors if data missing)
|
|
1071
|
-
* - 'auto': Hybrid (both auth and db, syncs on cache miss)
|
|
1072
|
-
*/
|
|
1073
|
-
function getProviderForSource(auth, db, source) {
|
|
1074
|
-
if (source === "api") return createProvider({ auth });
|
|
1075
|
-
if (source === "db") {
|
|
1076
|
-
if (!db) throw new Error("Database required for db source");
|
|
1077
|
-
return createProvider({ db });
|
|
1078
|
-
}
|
|
1079
|
-
return db ? createProvider({
|
|
1080
|
-
auth,
|
|
1081
|
-
db
|
|
1082
|
-
}) : createProvider({ auth });
|
|
1083
|
-
}
|
|
1084
578
|
const DUMP_DATA_TYPES = [
|
|
1085
579
|
"pages",
|
|
1086
580
|
"keywords",
|
|
1087
581
|
"countries",
|
|
1088
582
|
"devices"
|
|
1089
583
|
];
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
const startDate = dayjs().subtract(period.amount, period.unit).subtract(daysOffset, "days").format("YYYY-MM-DD");
|
|
1097
|
-
const range = {
|
|
1098
|
-
period: {
|
|
1099
|
-
start: startDate,
|
|
1100
|
-
end: endDate
|
|
1101
|
-
},
|
|
1102
|
-
prevPeriod: {
|
|
1103
|
-
start: dayjs(startDate).subtract(period.amount, period.unit).format("YYYY-MM-DD"),
|
|
1104
|
-
end: dayjs(endDate).subtract(period.amount, period.unit).format("YYYY-MM-DD")
|
|
1105
|
-
}
|
|
1106
|
-
};
|
|
1107
|
-
for (const siteUrl of selectedSites) {
|
|
1108
|
-
const siteName = siteUrl.replace(/https?:\/\//, "").replace(/\/$/, "");
|
|
1109
|
-
const output = {
|
|
1110
|
-
siteUrl,
|
|
1111
|
-
source: provider.source,
|
|
1112
|
-
dateRange: {
|
|
1113
|
-
startDate,
|
|
1114
|
-
endDate
|
|
1115
|
-
},
|
|
1116
|
-
period: {
|
|
1117
|
-
amount: period.amount,
|
|
1118
|
-
unit: period.unit
|
|
1119
|
-
},
|
|
1120
|
-
dataTypes,
|
|
1121
|
-
fresh: options.fresh || false,
|
|
1122
|
-
searchType: options.searchType || "web"
|
|
1123
|
-
};
|
|
1124
|
-
const totalSteps = dataTypes.length;
|
|
1125
|
-
let currentStep = 0;
|
|
1126
|
-
for (const dataType of dataTypes) {
|
|
1127
|
-
currentStep++;
|
|
1128
|
-
clearLine();
|
|
1129
|
-
process.stdout.write(progressBar(currentStep, totalSteps, `${dataType} (${siteName})`));
|
|
1130
|
-
if (dataType === "pages") {
|
|
1131
|
-
const pages = await provider.getPages(siteUrl, range);
|
|
1132
|
-
output.pages = {
|
|
1133
|
-
total: pages.length,
|
|
1134
|
-
data: pages
|
|
1135
|
-
};
|
|
1136
|
-
} else if (dataType === "keywords") {
|
|
1137
|
-
const keywordsData = await provider.getKeywordsWithComparison(siteUrl, range);
|
|
1138
|
-
output.keywords = {
|
|
1139
|
-
total: keywordsData.current.length,
|
|
1140
|
-
current: keywordsData.current,
|
|
1141
|
-
previous: keywordsData.previous,
|
|
1142
|
-
metadata: keywordsData.metadata
|
|
1143
|
-
};
|
|
1144
|
-
} else if (dataType === "countries") {
|
|
1145
|
-
const countriesData = await provider.getCountriesWithComparison(siteUrl, range);
|
|
1146
|
-
output.countries = {
|
|
1147
|
-
current: countriesData.current,
|
|
1148
|
-
previous: countriesData.previous,
|
|
1149
|
-
metadata: countriesData.metadata
|
|
1150
|
-
};
|
|
1151
|
-
} else if (dataType === "devices") {
|
|
1152
|
-
const devicesData = await provider.getDevicesWithComparison(siteUrl, range);
|
|
1153
|
-
output.devices = {
|
|
1154
|
-
current: devicesData.current,
|
|
1155
|
-
previous: devicesData.previous,
|
|
1156
|
-
metadata: devicesData.metadata
|
|
1157
|
-
};
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
clearLine();
|
|
1161
|
-
const ext = format === "csv" ? "csv" : "json";
|
|
1162
|
-
const filename = outputFile || `gsc-${siteName.replace(/\//g, "_")}-${dataTypes.join("-")}-${period.amount}${period.unit[0]}-${endDate}.${ext}`;
|
|
1163
|
-
const content = format === "csv" ? exportToCSV(output) : JSON.stringify(output, null, 2);
|
|
1164
|
-
await fs.writeFile(filename, content);
|
|
1165
|
-
const totalItems = (output.pages?.total || 0) + (output.keywords?.total || 0) + (output.countries?.current?.length || 0) + (output.devices?.current?.length || 0);
|
|
1166
|
-
logger.success(`Saved ${totalItems} items to ${filename} (source: ${provider.source})`);
|
|
1167
|
-
}
|
|
1168
|
-
}
|
|
1169
|
-
async function interactiveMode(auth, dbPath, source) {
|
|
1170
|
-
process.stdout.write(" Fetching sites...");
|
|
1171
|
-
const sites = await getSites(googleSearchConsole(auth));
|
|
1172
|
-
clearLine();
|
|
1173
|
-
logger.success(`Found ${sites.length} sites`);
|
|
1174
|
-
if (sites.length === 0) {
|
|
1175
|
-
cancel("No sites found in your Google Search Console account.");
|
|
1176
|
-
return;
|
|
1177
|
-
}
|
|
1178
|
-
const selectedSites = await multiselect({
|
|
1179
|
-
message: "Select sites to dump data from:",
|
|
1180
|
-
options: sites.map((site) => ({
|
|
1181
|
-
value: site,
|
|
1182
|
-
label: site
|
|
1183
|
-
}))
|
|
1184
|
-
});
|
|
1185
|
-
if (isCancel(selectedSites) || selectedSites.length === 0) {
|
|
1186
|
-
cancel("Operation cancelled.");
|
|
1187
|
-
return;
|
|
1188
|
-
}
|
|
1189
|
-
const dataTypes = await multiselect({
|
|
1190
|
-
message: "What data would you like to dump?",
|
|
1191
|
-
options: [
|
|
1192
|
-
{
|
|
1193
|
-
value: "pages",
|
|
1194
|
-
label: "Pages (URLs and performance data)",
|
|
1195
|
-
hint: "recommended"
|
|
1196
|
-
},
|
|
1197
|
-
{
|
|
1198
|
-
value: "keywords",
|
|
1199
|
-
label: "Keywords (search queries and rankings)"
|
|
1200
|
-
},
|
|
1201
|
-
{
|
|
1202
|
-
value: "countries",
|
|
1203
|
-
label: "Countries (geographic performance)"
|
|
1204
|
-
},
|
|
1205
|
-
{
|
|
1206
|
-
value: "devices",
|
|
1207
|
-
label: "Devices (desktop, mobile, tablet)"
|
|
1208
|
-
}
|
|
1209
|
-
],
|
|
1210
|
-
required: true
|
|
1211
|
-
});
|
|
1212
|
-
if (isCancel(dataTypes) || dataTypes.length === 0) {
|
|
1213
|
-
cancel("Operation cancelled.");
|
|
1214
|
-
return;
|
|
1215
|
-
}
|
|
1216
|
-
const periodInput = await text({
|
|
1217
|
-
message: "Time period (e.g., 90d, 6m, 1y):",
|
|
1218
|
-
placeholder: "180d",
|
|
1219
|
-
defaultValue: "180d",
|
|
1220
|
-
validate: (value) => {
|
|
1221
|
-
if (!value) return void 0;
|
|
1222
|
-
return parsePeriod(value) ? void 0 : "Invalid format. Use: 90d, 6m, 1y";
|
|
1223
|
-
}
|
|
1224
|
-
});
|
|
1225
|
-
if (isCancel(periodInput)) {
|
|
1226
|
-
cancel("Operation cancelled.");
|
|
1227
|
-
return;
|
|
1228
|
-
}
|
|
1229
|
-
const formatResult = await multiselect({
|
|
1230
|
-
message: "Output format:",
|
|
1231
|
-
options: [{
|
|
1232
|
-
value: "json",
|
|
1233
|
-
label: "JSON",
|
|
1234
|
-
hint: "structured data"
|
|
1235
|
-
}, {
|
|
1236
|
-
value: "csv",
|
|
1237
|
-
label: "CSV",
|
|
1238
|
-
hint: "spreadsheet compatible"
|
|
1239
|
-
}],
|
|
1240
|
-
required: true
|
|
1241
|
-
});
|
|
1242
|
-
if (isCancel(formatResult)) {
|
|
1243
|
-
cancel("Operation cancelled.");
|
|
1244
|
-
return;
|
|
1245
|
-
}
|
|
1246
|
-
let finalSource = source;
|
|
1247
|
-
if (dbPath && source === "auto") {
|
|
1248
|
-
const sourceResult = await select({
|
|
1249
|
-
message: "Data source:",
|
|
1250
|
-
options: [
|
|
1251
|
-
{
|
|
1252
|
-
value: "auto",
|
|
1253
|
-
label: "Auto",
|
|
1254
|
-
hint: "use DB if data exists, else API"
|
|
1255
|
-
},
|
|
1256
|
-
{
|
|
1257
|
-
value: "api",
|
|
1258
|
-
label: "API",
|
|
1259
|
-
hint: "always fetch from Google"
|
|
1260
|
-
},
|
|
1261
|
-
{
|
|
1262
|
-
value: "db",
|
|
1263
|
-
label: "Database",
|
|
1264
|
-
hint: "use synced data"
|
|
1265
|
-
}
|
|
1266
|
-
]
|
|
1267
|
-
});
|
|
1268
|
-
if (isCancel(sourceResult)) {
|
|
1269
|
-
cancel("Operation cancelled.");
|
|
1270
|
-
return;
|
|
1271
|
-
}
|
|
1272
|
-
finalSource = sourceResult;
|
|
1273
|
-
}
|
|
1274
|
-
const format = formatResult[0];
|
|
1275
|
-
const period = parsePeriod(periodInput || "180d");
|
|
1276
|
-
const ready = await confirm({ message: `Dump ${dataTypes.join(", ")} for ${selectedSites.length} site(s), ${period.amount}${period.unit[0]}, as ${format.toUpperCase()}?` });
|
|
1277
|
-
if (isCancel(ready) || !ready) {
|
|
1278
|
-
cancel("Operation cancelled.");
|
|
1279
|
-
return;
|
|
1280
|
-
}
|
|
1281
|
-
console.log();
|
|
1282
|
-
let db = null;
|
|
1283
|
-
if (dbPath) {
|
|
1284
|
-
if (await fs.access(dbPath).then(() => true).catch(() => false)) db = createGscDb(betterSqlite3({ name: path.resolve(dbPath) })).db;
|
|
584
|
+
function getDimensions(dataType) {
|
|
585
|
+
switch (dataType) {
|
|
586
|
+
case "pages": return [page, date];
|
|
587
|
+
case "keywords": return [query, date];
|
|
588
|
+
case "countries": return [country, date];
|
|
589
|
+
case "devices": return [device, date];
|
|
1285
590
|
}
|
|
1286
|
-
const provider = getProviderForSource(auth, db, finalSource);
|
|
1287
|
-
logger.info(`Using ${provider.source.toUpperCase()} as data source`);
|
|
1288
|
-
await runDump(provider, selectedSites, dataTypes, period, format, null);
|
|
1289
|
-
}
|
|
1290
|
-
async function nonInteractiveMode(auth, site, data, periodStr, format, output, dbPath, source, options = {}) {
|
|
1291
|
-
if (site.length === 0) {
|
|
1292
|
-
logger.error("--site required in non-interactive mode");
|
|
1293
|
-
process.exit(1);
|
|
1294
|
-
}
|
|
1295
|
-
if (data.length === 0) {
|
|
1296
|
-
logger.error("--data required in non-interactive mode");
|
|
1297
|
-
process.exit(1);
|
|
1298
|
-
}
|
|
1299
|
-
const invalidData = data.filter((d) => !DUMP_DATA_TYPES.includes(d));
|
|
1300
|
-
if (invalidData.length > 0) {
|
|
1301
|
-
logger.error(`Invalid data types: ${invalidData.join(", ")}`);
|
|
1302
|
-
logger.info("Valid types: pages, keywords, countries, devices");
|
|
1303
|
-
process.exit(1);
|
|
1304
|
-
}
|
|
1305
|
-
const period = parsePeriod(periodStr);
|
|
1306
|
-
if (!period) {
|
|
1307
|
-
logger.error(`Invalid period: ${periodStr}`);
|
|
1308
|
-
logger.info("Examples: 90d, 6m, 1y");
|
|
1309
|
-
process.exit(1);
|
|
1310
|
-
}
|
|
1311
|
-
process.stdout.write(" Validating sites...");
|
|
1312
|
-
const availableSites = await getSites(googleSearchConsole(auth));
|
|
1313
|
-
clearLine();
|
|
1314
|
-
const normalizedSites = [];
|
|
1315
|
-
for (const s of site) {
|
|
1316
|
-
const match = availableSites.find((avail) => avail === s || avail === `https://${s}` || avail === `http://${s}` || avail === `sc-domain:${s}` || avail.replace(/https?:\/\//, "").replace(/\/$/, "") === s.replace(/\/$/, ""));
|
|
1317
|
-
if (!match) {
|
|
1318
|
-
logger.error(`Site not found: ${s}`);
|
|
1319
|
-
logger.info("Available sites:");
|
|
1320
|
-
availableSites.slice(0, 5).forEach((a) => console.log(` - ${a}`));
|
|
1321
|
-
if (availableSites.length > 5) console.log(` ... and ${availableSites.length - 5} more`);
|
|
1322
|
-
process.exit(1);
|
|
1323
|
-
}
|
|
1324
|
-
normalizedSites.push(match);
|
|
1325
|
-
}
|
|
1326
|
-
let db = null;
|
|
1327
|
-
if (dbPath) {
|
|
1328
|
-
if (await fs.access(dbPath).then(() => true).catch(() => false)) db = createGscDb(betterSqlite3({ name: path.resolve(dbPath) })).db;
|
|
1329
|
-
else if (source === "db") {
|
|
1330
|
-
logger.error(`Database not found: ${dbPath}`);
|
|
1331
|
-
logger.info("Run 'gscdump sync' first to create the database.");
|
|
1332
|
-
process.exit(1);
|
|
1333
|
-
}
|
|
1334
|
-
}
|
|
1335
|
-
const provider = getProviderForSource(auth, db, source);
|
|
1336
|
-
const extras = [];
|
|
1337
|
-
if (options.fresh) extras.push("fresh");
|
|
1338
|
-
if (options.searchType && options.searchType !== "web") extras.push(options.searchType);
|
|
1339
|
-
const extrasStr = extras.length ? ` [${extras.join(", ")}]` : "";
|
|
1340
|
-
logger.info(`${normalizedSites.length} site(s), ${data.join("+")} data, ${period.amount}${period.unit[0]}, ${format}, source: ${provider.source}${extrasStr}`);
|
|
1341
|
-
console.log();
|
|
1342
|
-
await runDump(provider, normalizedSites, data, period, format, output, options);
|
|
1343
591
|
}
|
|
1344
592
|
const dumpCommand = defineCommand({
|
|
1345
593
|
meta: {
|
|
1346
594
|
name: "dump",
|
|
1347
|
-
description: "Export
|
|
595
|
+
description: "Export search analytics data via GSC API"
|
|
1348
596
|
},
|
|
1349
597
|
args: {
|
|
1350
598
|
site: {
|
|
1351
599
|
type: "string",
|
|
1352
600
|
alias: "s",
|
|
1353
|
-
description: "Site URL (
|
|
1354
|
-
},
|
|
1355
|
-
data: {
|
|
1356
|
-
type: "string",
|
|
1357
|
-
alias: "d",
|
|
1358
|
-
description: "Data types: pages,keywords,countries,devices"
|
|
601
|
+
description: "Site URL (e.g., sc-domain:example.com)"
|
|
1359
602
|
},
|
|
1360
|
-
|
|
603
|
+
output: {
|
|
1361
604
|
type: "string",
|
|
1362
|
-
alias: "
|
|
1363
|
-
default: "
|
|
1364
|
-
description: "Time period: 90d, 6m, 1y"
|
|
605
|
+
alias: "o",
|
|
606
|
+
description: "Output file path (default: stdout)"
|
|
1365
607
|
},
|
|
1366
608
|
format: {
|
|
1367
609
|
type: "string",
|
|
1368
610
|
alias: "f",
|
|
1369
611
|
default: "json",
|
|
1370
|
-
description: "Output format: json
|
|
612
|
+
description: "Output format: json or csv"
|
|
1371
613
|
},
|
|
1372
|
-
|
|
614
|
+
start: {
|
|
1373
615
|
type: "string",
|
|
1374
|
-
|
|
1375
|
-
|
|
616
|
+
description: "Start date (YYYY-MM-DD)"
|
|
617
|
+
},
|
|
618
|
+
end: {
|
|
619
|
+
type: "string",
|
|
620
|
+
description: "End date (YYYY-MM-DD)"
|
|
621
|
+
},
|
|
622
|
+
days: {
|
|
623
|
+
type: "string",
|
|
624
|
+
alias: "d",
|
|
625
|
+
default: "28",
|
|
626
|
+
description: "Number of days to fetch (default: 28)"
|
|
1376
627
|
},
|
|
1377
|
-
|
|
628
|
+
types: {
|
|
1378
629
|
type: "string",
|
|
1379
|
-
|
|
1380
|
-
description: "Data
|
|
630
|
+
alias: "t",
|
|
631
|
+
description: "Data types: pages,keywords,countries,devices"
|
|
1381
632
|
},
|
|
1382
|
-
|
|
633
|
+
limit: {
|
|
1383
634
|
type: "string",
|
|
1384
|
-
|
|
635
|
+
alias: "l",
|
|
636
|
+
default: "25000",
|
|
637
|
+
description: "Max rows per data type"
|
|
1385
638
|
},
|
|
1386
|
-
|
|
639
|
+
quiet: {
|
|
1387
640
|
type: "boolean",
|
|
641
|
+
alias: "q",
|
|
1388
642
|
default: false,
|
|
1389
|
-
description: "
|
|
643
|
+
description: "Suppress progress output"
|
|
1390
644
|
},
|
|
1391
|
-
|
|
1392
|
-
type: "
|
|
1393
|
-
alias: "
|
|
1394
|
-
default:
|
|
1395
|
-
description: "
|
|
645
|
+
interactive: {
|
|
646
|
+
type: "boolean",
|
|
647
|
+
alias: "i",
|
|
648
|
+
default: false,
|
|
649
|
+
description: "Interactive mode - prompts for options"
|
|
1396
650
|
}
|
|
1397
651
|
},
|
|
1398
652
|
async run({ args }) {
|
|
1399
653
|
const config = await loadConfig();
|
|
1400
|
-
const siteArg = args.site || config.defaultSite;
|
|
1401
|
-
const sites = siteArg ? siteArg.split(",") : [];
|
|
1402
|
-
const data = args.data ? args.data.split(",") : [];
|
|
1403
|
-
const periodArg = args.period === "180d" && config.defaultPeriod ? config.defaultPeriod : args.period;
|
|
1404
|
-
const formatArg = args.format === "json" && config.defaultFormat ? config.defaultFormat : args.format;
|
|
1405
|
-
const dbPath = args.db || config.defaultDb || null;
|
|
1406
|
-
const source = [
|
|
1407
|
-
"auto",
|
|
1408
|
-
"api",
|
|
1409
|
-
"db"
|
|
1410
|
-
].includes(args.source) ? args.source : "auto";
|
|
1411
|
-
const isInteractive = sites.length === 0 && data.length === 0;
|
|
1412
654
|
const { getAuth: getAuth$1 } = await Promise.resolve().then(() => auth_exports);
|
|
1413
|
-
const
|
|
1414
|
-
interactive:
|
|
1415
|
-
config
|
|
1416
|
-
});
|
|
1417
|
-
const searchType = [
|
|
1418
|
-
"web",
|
|
1419
|
-
"image",
|
|
1420
|
-
"video",
|
|
1421
|
-
"news",
|
|
1422
|
-
"discover",
|
|
1423
|
-
"googleNews"
|
|
1424
|
-
].includes(args.type) ? args.type : "web";
|
|
1425
|
-
const options = {
|
|
1426
|
-
fresh: args.fresh,
|
|
1427
|
-
searchType
|
|
1428
|
-
};
|
|
1429
|
-
if (isInteractive) await interactiveMode(auth, dbPath, source);
|
|
1430
|
-
else await nonInteractiveMode(auth, sites, data, periodArg, formatArg === "csv" ? "csv" : "json", args.output || null, dbPath, source, options);
|
|
1431
|
-
logger.success("Done!");
|
|
1432
|
-
}
|
|
1433
|
-
});
|
|
1434
|
-
|
|
1435
|
-
//#endregion
|
|
1436
|
-
//#region src/commands/indexing.ts
|
|
1437
|
-
const inspectCommand = defineCommand({
|
|
1438
|
-
meta: {
|
|
1439
|
-
name: "inspect",
|
|
1440
|
-
description: "Inspect URLs to check their indexing status (shorthand for `gscdump index inspect`)"
|
|
1441
|
-
},
|
|
1442
|
-
args: {
|
|
1443
|
-
db: {
|
|
1444
|
-
type: "string",
|
|
1445
|
-
alias: "d",
|
|
1446
|
-
default: "./gscdump.db",
|
|
1447
|
-
description: "SQLite database path"
|
|
1448
|
-
},
|
|
1449
|
-
site: {
|
|
1450
|
-
type: "string",
|
|
1451
|
-
alias: "s",
|
|
1452
|
-
description: "Site URL (e.g., sc-domain:example.com)"
|
|
1453
|
-
},
|
|
1454
|
-
limit: {
|
|
1455
|
-
type: "string",
|
|
1456
|
-
alias: "l",
|
|
1457
|
-
default: "100",
|
|
1458
|
-
description: "Max URLs to inspect"
|
|
1459
|
-
},
|
|
1460
|
-
delay: {
|
|
1461
|
-
type: "string",
|
|
1462
|
-
default: "200",
|
|
1463
|
-
description: "Delay between requests (ms)"
|
|
1464
|
-
},
|
|
1465
|
-
quiet: {
|
|
1466
|
-
type: "boolean",
|
|
1467
|
-
alias: "q",
|
|
1468
|
-
default: false,
|
|
1469
|
-
description: "Suppress progress output"
|
|
1470
|
-
},
|
|
1471
|
-
json: {
|
|
1472
|
-
type: "boolean",
|
|
1473
|
-
default: false,
|
|
1474
|
-
description: "Output as JSON"
|
|
1475
|
-
}
|
|
1476
|
-
},
|
|
1477
|
-
async run({ args }) {
|
|
1478
|
-
const config = await loadConfig();
|
|
1479
|
-
const dbPath = String(args.db === "./gscdump.db" && config.defaultDb ? config.defaultDb : args.db);
|
|
1480
|
-
const siteArg = String(args.site || config.defaultSite || "");
|
|
1481
|
-
if (!siteArg) {
|
|
1482
|
-
logger.error("Site required. Use --site or set defaultSite in config.");
|
|
1483
|
-
process.exit(1);
|
|
1484
|
-
}
|
|
1485
|
-
const { getAuth: getAuth$1 } = await Promise.resolve().then(() => auth_exports);
|
|
1486
|
-
const client = googleSearchConsole(await getAuth$1({
|
|
1487
|
-
interactive: false,
|
|
655
|
+
const client = googleSearchConsole(await getAuth$1({
|
|
656
|
+
interactive: false,
|
|
1488
657
|
config
|
|
1489
658
|
}));
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
process.exit(1);
|
|
1497
|
-
}
|
|
1498
|
-
const siteId = siteRecord.site_id || siteRecord.siteId;
|
|
1499
|
-
const limit = Number.parseInt(String(args.limit), 10);
|
|
1500
|
-
const delayMs = Number.parseInt(String(args.delay), 10);
|
|
1501
|
-
const paths = await db.selectDistinct({ path: sitePathDateAnalytics.path }).from(sitePathDateAnalytics).where(eq(sitePathDateAnalytics.siteId, siteId)).limit(limit);
|
|
1502
|
-
if (paths.length === 0) {
|
|
1503
|
-
logger.warn("No URLs found. Run `gscdump sync` first.");
|
|
1504
|
-
return;
|
|
1505
|
-
}
|
|
1506
|
-
if (!args.quiet && !args.json) logger.info(`Inspecting ${paths.length} URLs...`);
|
|
1507
|
-
const results = [];
|
|
1508
|
-
const stats = await batchInspectUrls(db, client, siteId, siteArg, paths.map((p) => p.path), {
|
|
1509
|
-
delayMs,
|
|
1510
|
-
onProgress: (result, i, total) => {
|
|
1511
|
-
results.push(result);
|
|
1512
|
-
if (!args.quiet && !args.json) {
|
|
1513
|
-
clearLine();
|
|
1514
|
-
process.stdout.write(progressBar(i + 1, total, result.path.slice(0, 40)));
|
|
1515
|
-
}
|
|
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);
|
|
1516
665
|
}
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
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;
|
|
1530
679
|
}
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
args: {
|
|
1545
|
-
db: {
|
|
1546
|
-
type: "string",
|
|
1547
|
-
alias: "d",
|
|
1548
|
-
default: "./gscdump.db",
|
|
1549
|
-
description: "SQLite database path"
|
|
1550
|
-
},
|
|
1551
|
-
site: {
|
|
1552
|
-
type: "string",
|
|
1553
|
-
alias: "s",
|
|
1554
|
-
description: "Site URL (e.g., sc-domain:example.com)"
|
|
1555
|
-
},
|
|
1556
|
-
json: {
|
|
1557
|
-
type: "boolean",
|
|
1558
|
-
default: false,
|
|
1559
|
-
description: "Output as JSON"
|
|
1560
|
-
}
|
|
1561
|
-
},
|
|
1562
|
-
async run({ args }) {
|
|
1563
|
-
const config = await loadConfig();
|
|
1564
|
-
const dbPath = String(args.db === "./gscdump.db" && config.defaultDb ? config.defaultDb : args.db);
|
|
1565
|
-
const siteArg = String(args.site || config.defaultSite || "");
|
|
1566
|
-
if (!siteArg) {
|
|
1567
|
-
logger.error("Site required. Use --site or set defaultSite in config.");
|
|
1568
|
-
process.exit(1);
|
|
1569
|
-
}
|
|
1570
|
-
const { db, db0 } = createGscDb(betterSqlite3({ name: path.resolve(dbPath) }));
|
|
1571
|
-
await setupSchema(db0);
|
|
1572
|
-
const siteRecord = await getSiteByProperty(db, siteArg);
|
|
1573
|
-
if (!siteRecord) {
|
|
1574
|
-
logger.error(`Site not found: ${siteArg}`);
|
|
1575
|
-
process.exit(1);
|
|
1576
|
-
}
|
|
1577
|
-
const stats = await getIndexingStats(db, siteRecord.site_id || siteRecord.siteId);
|
|
1578
|
-
if (args.json) console.log(JSON.stringify(stats, null, 2));
|
|
1579
|
-
else {
|
|
1580
|
-
console.log();
|
|
1581
|
-
logger.info(`Indexing Status for ${siteArg}`);
|
|
1582
|
-
console.log();
|
|
1583
|
-
console.log(` Total URLs: ${stats.total}`);
|
|
1584
|
-
console.log(` Indexed: ${stats.indexed} (${stats.total ? Math.round(stats.indexed / stats.total * 100) : 0}%)`);
|
|
1585
|
-
console.log(` Not Indexed: ${stats.notIndexed}`);
|
|
1586
|
-
console.log(` Unknown: ${stats.unknown}`);
|
|
1587
|
-
console.log(` Requested: ${stats.requested}`);
|
|
1588
|
-
console.log();
|
|
1589
|
-
}
|
|
680
|
+
let startDate;
|
|
681
|
+
let endDate;
|
|
682
|
+
if (args.start && args.end) {
|
|
683
|
+
startDate = String(args.start);
|
|
684
|
+
endDate = String(args.end);
|
|
685
|
+
} else if (args.interactive) {
|
|
686
|
+
const startInput = await text({
|
|
687
|
+
message: "Start date (YYYY-MM-DD)",
|
|
688
|
+
placeholder: (/* @__PURE__ */ new Date(Date.now() - Number(args.days) * 864e5)).toISOString().split("T")[0]
|
|
689
|
+
});
|
|
690
|
+
if (isCancel(startInput)) {
|
|
691
|
+
cancel("Cancelled");
|
|
692
|
+
process.exit(0);
|
|
1590
693
|
}
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
db: {
|
|
1599
|
-
type: "string",
|
|
1600
|
-
alias: "d",
|
|
1601
|
-
default: "./gscdump.db",
|
|
1602
|
-
description: "SQLite database path"
|
|
1603
|
-
},
|
|
1604
|
-
site: {
|
|
1605
|
-
type: "string",
|
|
1606
|
-
alias: "s",
|
|
1607
|
-
description: "Site URL (e.g., sc-domain:example.com)"
|
|
1608
|
-
},
|
|
1609
|
-
limit: {
|
|
1610
|
-
type: "string",
|
|
1611
|
-
alias: "l",
|
|
1612
|
-
default: "100",
|
|
1613
|
-
description: "Max URLs to inspect"
|
|
1614
|
-
},
|
|
1615
|
-
delay: {
|
|
1616
|
-
type: "string",
|
|
1617
|
-
default: "200",
|
|
1618
|
-
description: "Delay between requests (ms)"
|
|
1619
|
-
},
|
|
1620
|
-
quiet: {
|
|
1621
|
-
type: "boolean",
|
|
1622
|
-
alias: "q",
|
|
1623
|
-
default: false,
|
|
1624
|
-
description: "Suppress progress output"
|
|
1625
|
-
},
|
|
1626
|
-
json: {
|
|
1627
|
-
type: "boolean",
|
|
1628
|
-
default: false,
|
|
1629
|
-
description: "Output as JSON"
|
|
1630
|
-
}
|
|
1631
|
-
},
|
|
1632
|
-
async run({ args }) {
|
|
1633
|
-
const config = await loadConfig();
|
|
1634
|
-
const dbPath = String(args.db === "./gscdump.db" && config.defaultDb ? config.defaultDb : args.db);
|
|
1635
|
-
const siteArg = String(args.site || config.defaultSite || "");
|
|
1636
|
-
if (!siteArg) {
|
|
1637
|
-
logger.error("Site required. Use --site or set defaultSite in config.");
|
|
1638
|
-
process.exit(1);
|
|
1639
|
-
}
|
|
1640
|
-
const { getAuth: getAuth$1 } = await Promise.resolve().then(() => auth_exports);
|
|
1641
|
-
const client = googleSearchConsole(await getAuth$1({
|
|
1642
|
-
interactive: false,
|
|
1643
|
-
config
|
|
1644
|
-
}));
|
|
1645
|
-
const { db, db0 } = createGscDb(betterSqlite3({ name: path.resolve(dbPath) }));
|
|
1646
|
-
await setupSchema(db0);
|
|
1647
|
-
await syncSites(db, client).catch(gscErrorHandler);
|
|
1648
|
-
const siteRecord = await getSiteByProperty(db, siteArg);
|
|
1649
|
-
if (!siteRecord) {
|
|
1650
|
-
logger.error(`Site not found: ${siteArg}`);
|
|
1651
|
-
process.exit(1);
|
|
1652
|
-
}
|
|
1653
|
-
const siteId = siteRecord.site_id || siteRecord.siteId;
|
|
1654
|
-
const limit = Number.parseInt(String(args.limit), 10);
|
|
1655
|
-
const delayMs = Number.parseInt(String(args.delay), 10);
|
|
1656
|
-
const paths = await db.selectDistinct({ path: sitePathDateAnalytics.path }).from(sitePathDateAnalytics).where(eq(sitePathDateAnalytics.siteId, siteId)).limit(limit);
|
|
1657
|
-
if (paths.length === 0) {
|
|
1658
|
-
logger.warn("No URLs found. Run `gscdump sync` first.");
|
|
1659
|
-
return;
|
|
1660
|
-
}
|
|
1661
|
-
if (!args.quiet && !args.json) logger.info(`Inspecting ${paths.length} URLs...`);
|
|
1662
|
-
const results = [];
|
|
1663
|
-
const stats = await batchInspectUrls(db, client, siteId, siteArg, paths.map((p) => p.path), {
|
|
1664
|
-
delayMs,
|
|
1665
|
-
onProgress: (result, i, total) => {
|
|
1666
|
-
results.push(result);
|
|
1667
|
-
if (!args.quiet && !args.json) {
|
|
1668
|
-
clearLine();
|
|
1669
|
-
process.stdout.write(progressBar(i + 1, total, result.path.slice(0, 40)));
|
|
1670
|
-
}
|
|
1671
|
-
}
|
|
1672
|
-
}).catch(gscErrorHandler);
|
|
1673
|
-
if (!args.quiet && !args.json) clearLine();
|
|
1674
|
-
if (args.json) console.log(JSON.stringify({
|
|
1675
|
-
stats,
|
|
1676
|
-
results
|
|
1677
|
-
}, null, 2));
|
|
1678
|
-
else {
|
|
1679
|
-
console.log();
|
|
1680
|
-
logger.success(`Inspected ${paths.length} URLs`);
|
|
1681
|
-
console.log(` Indexed: ${stats.indexed}`);
|
|
1682
|
-
console.log(` Not Indexed: ${stats.notIndexed}`);
|
|
1683
|
-
console.log(` Errors: ${stats.errors}`);
|
|
1684
|
-
console.log();
|
|
1685
|
-
}
|
|
694
|
+
const endInput = await text({
|
|
695
|
+
message: "End date (YYYY-MM-DD)",
|
|
696
|
+
placeholder: (/* @__PURE__ */ new Date(Date.now() - 3 * 864e5)).toISOString().split("T")[0]
|
|
697
|
+
});
|
|
698
|
+
if (isCancel(endInput)) {
|
|
699
|
+
cancel("Cancelled");
|
|
700
|
+
process.exit(0);
|
|
1686
701
|
}
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
alias: "t",
|
|
1719
|
-
default: "URL_UPDATED",
|
|
1720
|
-
description: "Notification type: URL_UPDATED or URL_DELETED"
|
|
1721
|
-
},
|
|
1722
|
-
"not-indexed": {
|
|
1723
|
-
type: "boolean",
|
|
1724
|
-
default: true,
|
|
1725
|
-
description: "Only request for non-indexed URLs"
|
|
1726
|
-
},
|
|
1727
|
-
"quiet": {
|
|
1728
|
-
type: "boolean",
|
|
1729
|
-
alias: "q",
|
|
1730
|
-
default: false,
|
|
1731
|
-
description: "Suppress progress output"
|
|
1732
|
-
},
|
|
1733
|
-
"json": {
|
|
1734
|
-
type: "boolean",
|
|
1735
|
-
default: false,
|
|
1736
|
-
description: "Output as JSON"
|
|
1737
|
-
}
|
|
702
|
+
startDate = String(startInput) || (/* @__PURE__ */ new Date(Date.now() - Number(args.days) * 864e5)).toISOString().split("T")[0];
|
|
703
|
+
endDate = String(endInput) || (/* @__PURE__ */ new Date(Date.now() - 3 * 864e5)).toISOString().split("T")[0];
|
|
704
|
+
} else {
|
|
705
|
+
const days = Number.parseInt(String(args.days), 10);
|
|
706
|
+
endDate = (/* @__PURE__ */ new Date(Date.now() - 3 * 864e5)).toISOString().split("T")[0];
|
|
707
|
+
startDate = (/* @__PURE__ */ new Date(Date.now() - (days + 3) * 864e5)).toISOString().split("T")[0];
|
|
708
|
+
}
|
|
709
|
+
let dataTypes;
|
|
710
|
+
if (args.types) dataTypes = String(args.types).split(",").filter((t) => DUMP_DATA_TYPES.includes(t));
|
|
711
|
+
else if (args.interactive) {
|
|
712
|
+
const selected = await multiselect({
|
|
713
|
+
message: "Select data types to export",
|
|
714
|
+
options: DUMP_DATA_TYPES.map((t) => ({
|
|
715
|
+
value: t,
|
|
716
|
+
label: t
|
|
717
|
+
})),
|
|
718
|
+
initialValues: ["pages", "keywords"]
|
|
719
|
+
});
|
|
720
|
+
if (isCancel(selected)) {
|
|
721
|
+
cancel("Cancelled");
|
|
722
|
+
process.exit(0);
|
|
723
|
+
}
|
|
724
|
+
dataTypes = selected;
|
|
725
|
+
} else dataTypes = ["pages", "keywords"];
|
|
726
|
+
const rowLimit = Number.parseInt(String(args.limit), 10);
|
|
727
|
+
const format = String(args.format);
|
|
728
|
+
const output = {
|
|
729
|
+
siteUrl,
|
|
730
|
+
dateRange: {
|
|
731
|
+
start: startDate,
|
|
732
|
+
end: endDate
|
|
1738
733
|
},
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
const client = googleSearchConsole(await getAuth$1({
|
|
1749
|
-
interactive: false,
|
|
1750
|
-
config
|
|
1751
|
-
}));
|
|
1752
|
-
const { db, db0 } = createGscDb(betterSqlite3({ name: path.resolve(dbPath) }));
|
|
1753
|
-
await setupSchema(db0);
|
|
1754
|
-
await syncSites(db, client).catch(gscErrorHandler);
|
|
1755
|
-
const siteRecord = await getSiteByProperty(db, siteArg);
|
|
1756
|
-
if (!siteRecord) {
|
|
1757
|
-
logger.error(`Site not found: ${siteArg}`);
|
|
1758
|
-
process.exit(1);
|
|
1759
|
-
}
|
|
1760
|
-
const siteId = siteRecord.site_id || siteRecord.siteId;
|
|
1761
|
-
const limit = Number.parseInt(String(args.limit), 10);
|
|
1762
|
-
const delayMs = Number.parseInt(String(args.delay), 10);
|
|
1763
|
-
const type = String(args.type);
|
|
1764
|
-
const paths = await db.selectDistinct({ path: sitePathDateAnalytics.path }).from(sitePathDateAnalytics).where(eq(sitePathDateAnalytics.siteId, siteId)).limit(limit);
|
|
1765
|
-
if (paths.length === 0) {
|
|
1766
|
-
logger.warn("No URLs found. Run `gscdump sync` first.");
|
|
1767
|
-
return;
|
|
1768
|
-
}
|
|
1769
|
-
if (!args.quiet && !args.json) {
|
|
1770
|
-
logger.info(`Requesting indexing for ${paths.length} URLs...`);
|
|
1771
|
-
logger.warn("Note: Indexing API quota is typically 200 requests/day");
|
|
1772
|
-
}
|
|
1773
|
-
const results = [];
|
|
1774
|
-
const stats = await batchRequestIndexingForPaths(db, client, siteId, siteArg, paths.map((p) => p.path), {
|
|
1775
|
-
type,
|
|
1776
|
-
delayMs,
|
|
1777
|
-
onProgress: (result, i, total) => {
|
|
1778
|
-
results.push(result);
|
|
1779
|
-
if (!args.quiet && !args.json) {
|
|
1780
|
-
clearLine();
|
|
1781
|
-
const status = result.error ? "ERR" : "OK";
|
|
1782
|
-
process.stdout.write(progressBar(i + 1, total, `[${status}] ${result.url.slice(-40)}`));
|
|
1783
|
-
}
|
|
1784
|
-
}
|
|
1785
|
-
}).catch(gscErrorHandler);
|
|
1786
|
-
if (!args.quiet && !args.json) clearLine();
|
|
1787
|
-
if (args.json) console.log(JSON.stringify({
|
|
1788
|
-
stats,
|
|
1789
|
-
results
|
|
1790
|
-
}, null, 2));
|
|
1791
|
-
else {
|
|
1792
|
-
console.log();
|
|
1793
|
-
logger.success(`Requested indexing for ${paths.length} URLs`);
|
|
1794
|
-
console.log(` Success: ${stats.success}`);
|
|
1795
|
-
console.log(` Errors: ${stats.errors}`);
|
|
1796
|
-
console.log();
|
|
1797
|
-
if (stats.errors > 0) {
|
|
1798
|
-
const errorResults = results.filter((r) => r.error);
|
|
1799
|
-
logger.warn("Errors:");
|
|
1800
|
-
errorResults.slice(0, 5).forEach((r) => {
|
|
1801
|
-
console.log(` ${r.url}: ${r.error}`);
|
|
1802
|
-
});
|
|
1803
|
-
if (errorResults.length > 5) console.log(` ... and ${errorResults.length - 5} more`);
|
|
1804
|
-
}
|
|
1805
|
-
}
|
|
734
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
735
|
+
};
|
|
736
|
+
const totalSteps = dataTypes.length;
|
|
737
|
+
let currentStep = 0;
|
|
738
|
+
for (const dataType of dataTypes) {
|
|
739
|
+
currentStep++;
|
|
740
|
+
if (!args.quiet) {
|
|
741
|
+
clearLine();
|
|
742
|
+
process.stdout.write(progressBar(currentStep, totalSteps, dataType));
|
|
1806
743
|
}
|
|
1807
|
-
|
|
744
|
+
const dimensions = getDimensions(dataType);
|
|
745
|
+
const builder = gsc.select(...dimensions).where(between(date, startDate, endDate)).limit(rowLimit);
|
|
746
|
+
const rows = [];
|
|
747
|
+
for await (const batch of client.query(siteUrl, builder)) rows.push(...batch);
|
|
748
|
+
output[dataType] = {
|
|
749
|
+
total: rows.length,
|
|
750
|
+
data: rows
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
if (!args.quiet) {
|
|
754
|
+
clearLine();
|
|
755
|
+
logger.success(`Exported ${dataTypes.join(", ")} for ${siteUrl}`);
|
|
756
|
+
}
|
|
757
|
+
const content = format === "csv" ? exportToCSV(output) : JSON.stringify(output, null, 2);
|
|
758
|
+
if (args.output) {
|
|
759
|
+
await fs.writeFile(String(args.output), content);
|
|
760
|
+
if (!args.quiet) logger.info(`Written to ${args.output}`);
|
|
761
|
+
} else console.log(content);
|
|
1808
762
|
}
|
|
1809
763
|
});
|
|
1810
764
|
|
|
@@ -1908,6 +862,383 @@ const initCommand = defineCommand({
|
|
|
1908
862
|
}
|
|
1909
863
|
});
|
|
1910
864
|
|
|
865
|
+
//#endregion
|
|
866
|
+
//#region src/mcp/handlers/analytics.ts
|
|
867
|
+
async function collectRows(ctx, siteUrl, builder) {
|
|
868
|
+
const rows = [];
|
|
869
|
+
for await (const batch of ctx.client.query(siteUrl, builder)) rows.push(...batch);
|
|
870
|
+
return rows;
|
|
871
|
+
}
|
|
872
|
+
async function fetchPages(input, ctx) {
|
|
873
|
+
const builder = gsc.select(page, date).where(between(date, input.period.start, input.period.end)).limit(25e3);
|
|
874
|
+
const rows = await collectRows(ctx, input.siteUrl, builder);
|
|
875
|
+
return {
|
|
876
|
+
total: rows.length,
|
|
877
|
+
data: rows
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
async function fetchKeywords(input, ctx) {
|
|
881
|
+
const builder = gsc.select(query, date).where(between(date, input.period.start, input.period.end)).limit(25e3);
|
|
882
|
+
const rows = await collectRows(ctx, input.siteUrl, builder);
|
|
883
|
+
return {
|
|
884
|
+
total: rows.length,
|
|
885
|
+
data: rows
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
async function fetchCountries(input, ctx) {
|
|
889
|
+
const builder = gsc.select(country, date).where(between(date, input.period.start, input.period.end)).limit(25e3);
|
|
890
|
+
const rows = await collectRows(ctx, input.siteUrl, builder);
|
|
891
|
+
return {
|
|
892
|
+
total: rows.length,
|
|
893
|
+
data: rows
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
async function fetchDevices(input, ctx) {
|
|
897
|
+
const builder = gsc.select(device, date).where(between(date, input.period.start, input.period.end)).limit(25e3);
|
|
898
|
+
const rows = await collectRows(ctx, input.siteUrl, builder);
|
|
899
|
+
return {
|
|
900
|
+
total: rows.length,
|
|
901
|
+
data: rows
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
//#endregion
|
|
906
|
+
//#region src/mcp/handlers/indexing.ts
|
|
907
|
+
async function inspectUrl$1(input, ctx) {
|
|
908
|
+
return inspectUrl(ctx.client, input.siteUrl, input.inspectionUrl);
|
|
909
|
+
}
|
|
910
|
+
async function requestIndexing$1(input, ctx) {
|
|
911
|
+
return requestIndexing(ctx.client, input.url, { type: input.type || "URL_UPDATED" }).catch((e) => ({
|
|
912
|
+
url: input.url,
|
|
913
|
+
type: input.type || "URL_UPDATED",
|
|
914
|
+
error: e.message
|
|
915
|
+
}));
|
|
916
|
+
}
|
|
917
|
+
async function getIndexingStatus(input, ctx) {
|
|
918
|
+
return getIndexingMetadata(ctx.client, input.url).catch((e) => ({
|
|
919
|
+
url: input.url,
|
|
920
|
+
error: e.message
|
|
921
|
+
}));
|
|
922
|
+
}
|
|
923
|
+
async function batchRequestIndexing$1(input, ctx) {
|
|
924
|
+
const results = await batchRequestIndexing(ctx.client, input.urls, {
|
|
925
|
+
type: input.type || "URL_UPDATED",
|
|
926
|
+
delayMs: input.delayMs || 100
|
|
927
|
+
});
|
|
928
|
+
return {
|
|
929
|
+
results,
|
|
930
|
+
success: results.length,
|
|
931
|
+
failed: 0
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
async function batchInspectUrls$1(input, ctx) {
|
|
935
|
+
const results = await batchInspectUrls(ctx.client, input.siteUrl, input.urls, { delayMs: input.delayMs || 200 });
|
|
936
|
+
return {
|
|
937
|
+
results,
|
|
938
|
+
indexed: results.filter((r) => r.isIndexed).length,
|
|
939
|
+
notIndexed: results.filter((r) => !r.isIndexed).length
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
//#endregion
|
|
944
|
+
//#region src/mcp/handlers/query.ts
|
|
945
|
+
const DIMENSION_MAP$1 = {
|
|
946
|
+
page,
|
|
947
|
+
query,
|
|
948
|
+
date,
|
|
949
|
+
country,
|
|
950
|
+
device,
|
|
951
|
+
searchAppearance
|
|
952
|
+
};
|
|
953
|
+
async function customQuery(input, ctx) {
|
|
954
|
+
const dimensions = input.dimensions.filter((d) => d in DIMENSION_MAP$1).map((d) => DIMENSION_MAP$1[d]);
|
|
955
|
+
if (dimensions.length === 0) throw new Error("At least one valid dimension required");
|
|
956
|
+
const builder = gsc.select(...dimensions).where(between(date, input.period.start, input.period.end)).limit(input.rowLimit || 25e3);
|
|
957
|
+
const rows = [];
|
|
958
|
+
for await (const batch of ctx.client.query(input.siteUrl, builder)) rows.push(...batch);
|
|
959
|
+
return {
|
|
960
|
+
total: rows.length,
|
|
961
|
+
data: rows
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
//#endregion
|
|
966
|
+
//#region src/mcp/handlers/sites.ts
|
|
967
|
+
async function listSites(_input, ctx) {
|
|
968
|
+
return fetchSites(ctx.client);
|
|
969
|
+
}
|
|
970
|
+
async function listSitesWithSitemaps(_input, ctx) {
|
|
971
|
+
return fetchSitesWithSitemaps(ctx.client);
|
|
972
|
+
}
|
|
973
|
+
async function listSitemaps(input, ctx) {
|
|
974
|
+
return fetchSitemaps(ctx.client, input.siteUrl);
|
|
975
|
+
}
|
|
976
|
+
async function getSitemap(input, ctx) {
|
|
977
|
+
return fetchSitemap(ctx.client, input.siteUrl, input.feedpath);
|
|
978
|
+
}
|
|
979
|
+
async function submitSitemap$1(input, ctx) {
|
|
980
|
+
await submitSitemap(ctx.client, input.siteUrl, input.feedpath);
|
|
981
|
+
return { success: true };
|
|
982
|
+
}
|
|
983
|
+
async function deleteSitemap$1(input, ctx) {
|
|
984
|
+
await deleteSitemap(ctx.client, input.siteUrl, input.feedpath);
|
|
985
|
+
return { success: true };
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
//#endregion
|
|
989
|
+
//#region src/mcp/types.ts
|
|
990
|
+
const periodSchema = z.object({
|
|
991
|
+
start: z.string().describe("Start date (YYYY-MM-DD)"),
|
|
992
|
+
end: z.string().describe("End date (YYYY-MM-DD)")
|
|
993
|
+
}).describe("Date range for the query");
|
|
994
|
+
const siteUrlSchema = z.string().describe("GSC property URL (e.g., sc-domain:example.com or https://example.com/)");
|
|
995
|
+
const queryOptionsSchema = z.object({
|
|
996
|
+
type: z.enum([
|
|
997
|
+
"web",
|
|
998
|
+
"image",
|
|
999
|
+
"video",
|
|
1000
|
+
"news",
|
|
1001
|
+
"discover",
|
|
1002
|
+
"googleNews"
|
|
1003
|
+
]).optional().describe("Data type"),
|
|
1004
|
+
dataState: z.enum(["final", "all"]).optional().describe("Data state: final (settled) or all (includes fresh)"),
|
|
1005
|
+
aggregationType: z.enum(["byPage", "byProperty"]).optional().describe("Aggregation: byPage or byProperty")
|
|
1006
|
+
}).optional();
|
|
1007
|
+
const listSitesInput = z.object({});
|
|
1008
|
+
const listSitemapsInput = z.object({ siteUrl: siteUrlSchema });
|
|
1009
|
+
const fetchAnalyticsInput = z.object({
|
|
1010
|
+
siteUrl: siteUrlSchema,
|
|
1011
|
+
period: periodSchema,
|
|
1012
|
+
comparePrevious: z.boolean().optional().describe("Include previous period comparison"),
|
|
1013
|
+
options: queryOptionsSchema
|
|
1014
|
+
});
|
|
1015
|
+
const fetchPageInput = z.object({
|
|
1016
|
+
siteUrl: siteUrlSchema,
|
|
1017
|
+
period: periodSchema,
|
|
1018
|
+
url: z.string().describe("Page URL to fetch details for")
|
|
1019
|
+
});
|
|
1020
|
+
const fetchKeywordInput = z.object({
|
|
1021
|
+
siteUrl: siteUrlSchema,
|
|
1022
|
+
period: periodSchema,
|
|
1023
|
+
keyword: z.string().describe("Keyword to fetch details for")
|
|
1024
|
+
});
|
|
1025
|
+
const inspectUrlInput = z.object({
|
|
1026
|
+
siteUrl: siteUrlSchema,
|
|
1027
|
+
inspectionUrl: z.string().describe("URL to inspect")
|
|
1028
|
+
});
|
|
1029
|
+
const requestIndexingInput = z.object({
|
|
1030
|
+
url: z.string().describe("URL to request indexing for"),
|
|
1031
|
+
type: z.enum(["URL_UPDATED", "URL_DELETED"]).optional().describe("Notification type")
|
|
1032
|
+
});
|
|
1033
|
+
const getIndexingStatusInput = z.object({ url: z.string().describe("URL to get indexing status for") });
|
|
1034
|
+
const customQueryInput = z.object({
|
|
1035
|
+
siteUrl: siteUrlSchema,
|
|
1036
|
+
period: periodSchema,
|
|
1037
|
+
dimensions: z.array(z.enum([
|
|
1038
|
+
"date",
|
|
1039
|
+
"query",
|
|
1040
|
+
"page",
|
|
1041
|
+
"country",
|
|
1042
|
+
"device",
|
|
1043
|
+
"searchAppearance"
|
|
1044
|
+
])).describe("Dimensions to group by"),
|
|
1045
|
+
rowLimit: z.number().optional().describe("Max rows (default 25000)"),
|
|
1046
|
+
options: queryOptionsSchema
|
|
1047
|
+
});
|
|
1048
|
+
const sitemapInput = z.object({
|
|
1049
|
+
siteUrl: siteUrlSchema,
|
|
1050
|
+
feedpath: z.string().describe("Sitemap URL (e.g., https://example.com/sitemap.xml)")
|
|
1051
|
+
});
|
|
1052
|
+
const batchRequestIndexingInput = z.object({
|
|
1053
|
+
urls: z.array(z.string()).describe("URLs to request indexing for"),
|
|
1054
|
+
type: z.enum(["URL_UPDATED", "URL_DELETED"]).optional().describe("Notification type"),
|
|
1055
|
+
delayMs: z.number().optional().describe("Delay between requests in ms (default 100)")
|
|
1056
|
+
});
|
|
1057
|
+
const batchInspectUrlsInput = z.object({
|
|
1058
|
+
siteUrl: siteUrlSchema,
|
|
1059
|
+
urls: z.array(z.string()).describe("URLs to inspect"),
|
|
1060
|
+
delayMs: z.number().optional().describe("Delay between requests in ms (default 200)")
|
|
1061
|
+
});
|
|
1062
|
+
|
|
1063
|
+
//#endregion
|
|
1064
|
+
//#region src/mcp/server/index.ts
|
|
1065
|
+
function createGscMcpServer(options) {
|
|
1066
|
+
const { name = "gscdump", version = "1.0.0", getAuth: getAuth$1 } = options;
|
|
1067
|
+
const server = new McpServer({
|
|
1068
|
+
name,
|
|
1069
|
+
version
|
|
1070
|
+
});
|
|
1071
|
+
const auth = async () => Promise.resolve(getAuth$1());
|
|
1072
|
+
const getContext = async () => {
|
|
1073
|
+
const a = await auth();
|
|
1074
|
+
return {
|
|
1075
|
+
auth: a,
|
|
1076
|
+
client: googleSearchConsole(a)
|
|
1077
|
+
};
|
|
1078
|
+
};
|
|
1079
|
+
server.registerTool("list-sites", {
|
|
1080
|
+
description: "List all Google Search Console sites the user has access to",
|
|
1081
|
+
inputSchema: listSitesInput.shape
|
|
1082
|
+
}, async (args) => {
|
|
1083
|
+
const result = await listSites(args, await getContext());
|
|
1084
|
+
return { content: [{
|
|
1085
|
+
type: "text",
|
|
1086
|
+
text: JSON.stringify(result, null, 2)
|
|
1087
|
+
}] };
|
|
1088
|
+
});
|
|
1089
|
+
server.registerTool("list-sites-with-sitemaps", {
|
|
1090
|
+
description: "List all GSC sites with their sitemaps",
|
|
1091
|
+
inputSchema: listSitesInput.shape
|
|
1092
|
+
}, async (args) => {
|
|
1093
|
+
const result = await listSitesWithSitemaps(args, await getContext());
|
|
1094
|
+
return { content: [{
|
|
1095
|
+
type: "text",
|
|
1096
|
+
text: JSON.stringify(result, null, 2)
|
|
1097
|
+
}] };
|
|
1098
|
+
});
|
|
1099
|
+
server.registerTool("list-sitemaps", {
|
|
1100
|
+
description: "List sitemaps for a specific site",
|
|
1101
|
+
inputSchema: listSitemapsInput.shape
|
|
1102
|
+
}, async (args) => {
|
|
1103
|
+
const result = await listSitemaps(args, await getContext());
|
|
1104
|
+
return { content: [{
|
|
1105
|
+
type: "text",
|
|
1106
|
+
text: JSON.stringify(result, null, 2)
|
|
1107
|
+
}] };
|
|
1108
|
+
});
|
|
1109
|
+
server.registerTool("get-sitemap", {
|
|
1110
|
+
description: "Get details for a specific sitemap",
|
|
1111
|
+
inputSchema: sitemapInput.shape
|
|
1112
|
+
}, async (args) => {
|
|
1113
|
+
const result = await getSitemap(args, await getContext());
|
|
1114
|
+
return { content: [{
|
|
1115
|
+
type: "text",
|
|
1116
|
+
text: JSON.stringify(result, null, 2)
|
|
1117
|
+
}] };
|
|
1118
|
+
});
|
|
1119
|
+
server.registerTool("submit-sitemap", {
|
|
1120
|
+
description: "Submit a sitemap to Google Search Console",
|
|
1121
|
+
inputSchema: sitemapInput.shape
|
|
1122
|
+
}, async (args) => {
|
|
1123
|
+
const result = await submitSitemap$1(args, await getContext());
|
|
1124
|
+
return { content: [{
|
|
1125
|
+
type: "text",
|
|
1126
|
+
text: JSON.stringify(result, null, 2)
|
|
1127
|
+
}] };
|
|
1128
|
+
});
|
|
1129
|
+
server.registerTool("delete-sitemap", {
|
|
1130
|
+
description: "Delete a sitemap from Google Search Console",
|
|
1131
|
+
inputSchema: sitemapInput.shape
|
|
1132
|
+
}, async (args) => {
|
|
1133
|
+
const result = await deleteSitemap$1(args, await getContext());
|
|
1134
|
+
return { content: [{
|
|
1135
|
+
type: "text",
|
|
1136
|
+
text: JSON.stringify(result, null, 2)
|
|
1137
|
+
}] };
|
|
1138
|
+
});
|
|
1139
|
+
server.registerTool("fetch-pages", {
|
|
1140
|
+
description: "Fetch page analytics data for a site",
|
|
1141
|
+
inputSchema: fetchAnalyticsInput.shape
|
|
1142
|
+
}, async (args) => {
|
|
1143
|
+
const result = await fetchPages(args, await getContext());
|
|
1144
|
+
return { content: [{
|
|
1145
|
+
type: "text",
|
|
1146
|
+
text: JSON.stringify(result, null, 2)
|
|
1147
|
+
}] };
|
|
1148
|
+
});
|
|
1149
|
+
server.registerTool("fetch-keywords", {
|
|
1150
|
+
description: "Fetch keyword/query analytics data for a site",
|
|
1151
|
+
inputSchema: fetchAnalyticsInput.shape
|
|
1152
|
+
}, async (args) => {
|
|
1153
|
+
const result = await fetchKeywords(args, await getContext());
|
|
1154
|
+
return { content: [{
|
|
1155
|
+
type: "text",
|
|
1156
|
+
text: JSON.stringify(result, null, 2)
|
|
1157
|
+
}] };
|
|
1158
|
+
});
|
|
1159
|
+
server.registerTool("fetch-countries", {
|
|
1160
|
+
description: "Fetch country analytics data for a site",
|
|
1161
|
+
inputSchema: fetchAnalyticsInput.shape
|
|
1162
|
+
}, async (args) => {
|
|
1163
|
+
const result = await fetchCountries(args, await getContext());
|
|
1164
|
+
return { content: [{
|
|
1165
|
+
type: "text",
|
|
1166
|
+
text: JSON.stringify(result, null, 2)
|
|
1167
|
+
}] };
|
|
1168
|
+
});
|
|
1169
|
+
server.registerTool("fetch-devices", {
|
|
1170
|
+
description: "Fetch device analytics data for a site",
|
|
1171
|
+
inputSchema: fetchAnalyticsInput.shape
|
|
1172
|
+
}, async (args) => {
|
|
1173
|
+
const result = await fetchDevices(args, await getContext());
|
|
1174
|
+
return { content: [{
|
|
1175
|
+
type: "text",
|
|
1176
|
+
text: JSON.stringify(result, null, 2)
|
|
1177
|
+
}] };
|
|
1178
|
+
});
|
|
1179
|
+
server.registerTool("custom-query", {
|
|
1180
|
+
description: "Run a custom search analytics query with specified dimensions",
|
|
1181
|
+
inputSchema: customQueryInput.shape
|
|
1182
|
+
}, async (args) => {
|
|
1183
|
+
const result = await customQuery(args, await getContext());
|
|
1184
|
+
return { content: [{
|
|
1185
|
+
type: "text",
|
|
1186
|
+
text: JSON.stringify(result, null, 2)
|
|
1187
|
+
}] };
|
|
1188
|
+
});
|
|
1189
|
+
server.registerTool("inspect-url", {
|
|
1190
|
+
description: "Inspect a URL in Google Search Console to check its indexing status",
|
|
1191
|
+
inputSchema: inspectUrlInput.shape
|
|
1192
|
+
}, async (args) => {
|
|
1193
|
+
const result = await inspectUrl$1(args, await getContext());
|
|
1194
|
+
return { content: [{
|
|
1195
|
+
type: "text",
|
|
1196
|
+
text: JSON.stringify(result, null, 2)
|
|
1197
|
+
}] };
|
|
1198
|
+
});
|
|
1199
|
+
server.registerTool("request-indexing", {
|
|
1200
|
+
description: "Request Google to index or remove a URL via the Indexing API",
|
|
1201
|
+
inputSchema: requestIndexingInput.shape
|
|
1202
|
+
}, async (args) => {
|
|
1203
|
+
const result = await requestIndexing$1(args, await getContext());
|
|
1204
|
+
return { content: [{
|
|
1205
|
+
type: "text",
|
|
1206
|
+
text: JSON.stringify(result, null, 2)
|
|
1207
|
+
}] };
|
|
1208
|
+
});
|
|
1209
|
+
server.registerTool("get-indexing-status", {
|
|
1210
|
+
description: "Get indexing status metadata for a URL",
|
|
1211
|
+
inputSchema: getIndexingStatusInput.shape
|
|
1212
|
+
}, async (args) => {
|
|
1213
|
+
const result = await getIndexingStatus(args, await getContext());
|
|
1214
|
+
return { content: [{
|
|
1215
|
+
type: "text",
|
|
1216
|
+
text: JSON.stringify(result, null, 2)
|
|
1217
|
+
}] };
|
|
1218
|
+
});
|
|
1219
|
+
server.registerTool("batch-request-indexing", {
|
|
1220
|
+
description: "Batch request indexing for multiple URLs with rate limiting",
|
|
1221
|
+
inputSchema: batchRequestIndexingInput.shape
|
|
1222
|
+
}, async (args) => {
|
|
1223
|
+
const result = await batchRequestIndexing$1(args, await getContext());
|
|
1224
|
+
return { content: [{
|
|
1225
|
+
type: "text",
|
|
1226
|
+
text: JSON.stringify(result, null, 2)
|
|
1227
|
+
}] };
|
|
1228
|
+
});
|
|
1229
|
+
server.registerTool("batch-inspect-urls", {
|
|
1230
|
+
description: "Batch inspect multiple URLs to check their indexing status",
|
|
1231
|
+
inputSchema: batchInspectUrlsInput.shape
|
|
1232
|
+
}, async (args) => {
|
|
1233
|
+
const result = await batchInspectUrls$1(args, await getContext());
|
|
1234
|
+
return { content: [{
|
|
1235
|
+
type: "text",
|
|
1236
|
+
text: JSON.stringify(result, null, 2)
|
|
1237
|
+
}] };
|
|
1238
|
+
});
|
|
1239
|
+
return server;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1911
1242
|
//#endregion
|
|
1912
1243
|
//#region src/commands/mcp.ts
|
|
1913
1244
|
async function checkAuth() {
|
|
@@ -1969,6 +1300,177 @@ const mcpCommand = defineCommand({
|
|
|
1969
1300
|
}
|
|
1970
1301
|
});
|
|
1971
1302
|
|
|
1303
|
+
//#endregion
|
|
1304
|
+
//#region src/commands/query.ts
|
|
1305
|
+
const DIMENSION_MAP = {
|
|
1306
|
+
page,
|
|
1307
|
+
query,
|
|
1308
|
+
date,
|
|
1309
|
+
country,
|
|
1310
|
+
device,
|
|
1311
|
+
searchAppearance
|
|
1312
|
+
};
|
|
1313
|
+
const queryCommand = defineCommand({
|
|
1314
|
+
meta: {
|
|
1315
|
+
name: "query",
|
|
1316
|
+
description: "Run custom search analytics queries"
|
|
1317
|
+
},
|
|
1318
|
+
args: {
|
|
1319
|
+
site: {
|
|
1320
|
+
type: "string",
|
|
1321
|
+
alias: "s",
|
|
1322
|
+
description: "Site URL (e.g., sc-domain:example.com)"
|
|
1323
|
+
},
|
|
1324
|
+
dimensions: {
|
|
1325
|
+
type: "string",
|
|
1326
|
+
alias: "d",
|
|
1327
|
+
description: "Dimensions: page,query,date,country,device,searchAppearance"
|
|
1328
|
+
},
|
|
1329
|
+
start: {
|
|
1330
|
+
type: "string",
|
|
1331
|
+
description: "Start date (YYYY-MM-DD)"
|
|
1332
|
+
},
|
|
1333
|
+
end: {
|
|
1334
|
+
type: "string",
|
|
1335
|
+
description: "End date (YYYY-MM-DD)"
|
|
1336
|
+
},
|
|
1337
|
+
limit: {
|
|
1338
|
+
type: "string",
|
|
1339
|
+
alias: "l",
|
|
1340
|
+
default: "1000",
|
|
1341
|
+
description: "Max rows (default: 1000)"
|
|
1342
|
+
},
|
|
1343
|
+
output: {
|
|
1344
|
+
type: "string",
|
|
1345
|
+
alias: "o",
|
|
1346
|
+
description: "Output file path (default: stdout)"
|
|
1347
|
+
},
|
|
1348
|
+
format: {
|
|
1349
|
+
type: "string",
|
|
1350
|
+
alias: "f",
|
|
1351
|
+
default: "json",
|
|
1352
|
+
description: "Output format: json or csv"
|
|
1353
|
+
},
|
|
1354
|
+
quiet: {
|
|
1355
|
+
type: "boolean",
|
|
1356
|
+
alias: "q",
|
|
1357
|
+
default: false,
|
|
1358
|
+
description: "Suppress progress output"
|
|
1359
|
+
},
|
|
1360
|
+
interactive: {
|
|
1361
|
+
type: "boolean",
|
|
1362
|
+
alias: "i",
|
|
1363
|
+
default: false,
|
|
1364
|
+
description: "Interactive mode"
|
|
1365
|
+
}
|
|
1366
|
+
},
|
|
1367
|
+
async run({ args }) {
|
|
1368
|
+
const config = await loadConfig();
|
|
1369
|
+
const { getAuth: getAuth$1 } = await Promise.resolve().then(() => auth_exports);
|
|
1370
|
+
const client = googleSearchConsole(await getAuth$1({
|
|
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]);
|
|
1397
|
+
else if (args.interactive) {
|
|
1398
|
+
const selected = await multiselect({
|
|
1399
|
+
message: "Select dimensions",
|
|
1400
|
+
options: Object.keys(DIMENSION_MAP).map((d) => ({
|
|
1401
|
+
value: d,
|
|
1402
|
+
label: d
|
|
1403
|
+
})),
|
|
1404
|
+
initialValues: ["page", "query"]
|
|
1405
|
+
});
|
|
1406
|
+
if (isCancel(selected)) {
|
|
1407
|
+
cancel("Cancelled");
|
|
1408
|
+
process.exit(0);
|
|
1409
|
+
}
|
|
1410
|
+
dimensions = selected.map((d) => DIMENSION_MAP[d]);
|
|
1411
|
+
} else dimensions = [page, query];
|
|
1412
|
+
let startDate;
|
|
1413
|
+
let endDate;
|
|
1414
|
+
if (args.start && args.end) {
|
|
1415
|
+
startDate = String(args.start);
|
|
1416
|
+
endDate = String(args.end);
|
|
1417
|
+
} else if (args.interactive) {
|
|
1418
|
+
const startInput = await text({
|
|
1419
|
+
message: "Start date (YYYY-MM-DD)",
|
|
1420
|
+
placeholder: (/* @__PURE__ */ new Date(Date.now() - 28 * 864e5)).toISOString().split("T")[0]
|
|
1421
|
+
});
|
|
1422
|
+
if (isCancel(startInput)) {
|
|
1423
|
+
cancel("Cancelled");
|
|
1424
|
+
process.exit(0);
|
|
1425
|
+
}
|
|
1426
|
+
const endInput = await text({
|
|
1427
|
+
message: "End date (YYYY-MM-DD)",
|
|
1428
|
+
placeholder: (/* @__PURE__ */ new Date(Date.now() - 3 * 864e5)).toISOString().split("T")[0]
|
|
1429
|
+
});
|
|
1430
|
+
if (isCancel(endInput)) {
|
|
1431
|
+
cancel("Cancelled");
|
|
1432
|
+
process.exit(0);
|
|
1433
|
+
}
|
|
1434
|
+
startDate = String(startInput) || (/* @__PURE__ */ new Date(Date.now() - 28 * 864e5)).toISOString().split("T")[0];
|
|
1435
|
+
endDate = String(endInput) || (/* @__PURE__ */ new Date(Date.now() - 3 * 864e5)).toISOString().split("T")[0];
|
|
1436
|
+
} else {
|
|
1437
|
+
endDate = (/* @__PURE__ */ new Date(Date.now() - 3 * 864e5)).toISOString().split("T")[0];
|
|
1438
|
+
startDate = (/* @__PURE__ */ new Date(Date.now() - 31 * 864e5)).toISOString().split("T")[0];
|
|
1439
|
+
}
|
|
1440
|
+
const rowLimit = Number.parseInt(String(args.limit), 10);
|
|
1441
|
+
const format = String(args.format);
|
|
1442
|
+
const builder = gsc.select(...dimensions).where(between(date, startDate, endDate)).limit(rowLimit);
|
|
1443
|
+
if (!args.quiet) logger.info(`Querying ${siteUrl}...`);
|
|
1444
|
+
const rows = [];
|
|
1445
|
+
for await (const batch of client.query(siteUrl, builder)) {
|
|
1446
|
+
rows.push(...batch);
|
|
1447
|
+
if (!args.quiet) {
|
|
1448
|
+
clearLine();
|
|
1449
|
+
process.stdout.write(progressBar(rows.length, rowLimit, `${rows.length} rows`));
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
if (!args.quiet) {
|
|
1453
|
+
clearLine();
|
|
1454
|
+
logger.success(`Fetched ${rows.length} rows`);
|
|
1455
|
+
}
|
|
1456
|
+
const output = {
|
|
1457
|
+
siteUrl,
|
|
1458
|
+
dimensions: dimensions.map((d) => String(d)),
|
|
1459
|
+
dateRange: {
|
|
1460
|
+
start: startDate,
|
|
1461
|
+
end: endDate
|
|
1462
|
+
},
|
|
1463
|
+
total: rows.length,
|
|
1464
|
+
data: rows
|
|
1465
|
+
};
|
|
1466
|
+
const content = format === "csv" ? exportToCSV(output) : JSON.stringify(output, null, 2);
|
|
1467
|
+
if (args.output) {
|
|
1468
|
+
await fs.writeFile(String(args.output), content);
|
|
1469
|
+
if (!args.quiet) logger.info(`Written to ${args.output}`);
|
|
1470
|
+
} else console.log(content);
|
|
1471
|
+
}
|
|
1472
|
+
});
|
|
1473
|
+
|
|
1972
1474
|
//#endregion
|
|
1973
1475
|
//#region src/commands/sitemaps.ts
|
|
1974
1476
|
const listCommand = defineCommand({
|
|
@@ -2146,249 +1648,6 @@ const sitesCommand = defineCommand({
|
|
|
2146
1648
|
}
|
|
2147
1649
|
});
|
|
2148
1650
|
|
|
2149
|
-
//#endregion
|
|
2150
|
-
//#region src/commands/sync.ts
|
|
2151
|
-
function parsePeriodDays(period) {
|
|
2152
|
-
if (period === "max") return 480;
|
|
2153
|
-
if (period.endsWith("y")) return Number.parseInt(period) * 365;
|
|
2154
|
-
if (period.endsWith("m") || period.endsWith("mo")) return Number.parseInt(period) * 30;
|
|
2155
|
-
return Number.parseInt(period.replace("d", ""));
|
|
2156
|
-
}
|
|
2157
|
-
async function runSync(client, dbPath, siteArg, period, granular, options) {
|
|
2158
|
-
const resolvedPath = path.resolve(dbPath);
|
|
2159
|
-
const report = {
|
|
2160
|
-
database: resolvedPath,
|
|
2161
|
-
period: {
|
|
2162
|
-
start: "",
|
|
2163
|
-
end: ""
|
|
2164
|
-
},
|
|
2165
|
-
sites: [],
|
|
2166
|
-
totalRows: 0
|
|
2167
|
-
};
|
|
2168
|
-
if (!options.quiet && !options.json) logger.info(`Database: ${resolvedPath}`);
|
|
2169
|
-
const { db, db0 } = createGscDb(betterSqlite3({ name: resolvedPath }));
|
|
2170
|
-
await setupSchema(db0);
|
|
2171
|
-
if (!options.quiet && !options.json) logger.start("Syncing sites...");
|
|
2172
|
-
const syncedSites = await syncSites(db, client);
|
|
2173
|
-
if (!options.quiet && !options.json) logger.success(`Synced ${syncedSites.length} sites`);
|
|
2174
|
-
let sitesToSync = [];
|
|
2175
|
-
if (siteArg) {
|
|
2176
|
-
const siteRecord = await getSiteByProperty(db, siteArg);
|
|
2177
|
-
if (!siteRecord) {
|
|
2178
|
-
if (options.json) console.log(JSON.stringify({ error: `Site not found: ${siteArg}` }));
|
|
2179
|
-
else {
|
|
2180
|
-
logger.error(`Site not found: ${siteArg}`);
|
|
2181
|
-
logger.info("Available sites:");
|
|
2182
|
-
syncedSites.slice(0, 5).forEach((s) => console.log(` - ${s.property}`));
|
|
2183
|
-
}
|
|
2184
|
-
process.exit(1);
|
|
2185
|
-
}
|
|
2186
|
-
sitesToSync = [{
|
|
2187
|
-
siteId: siteRecord.site_id || siteRecord.siteId,
|
|
2188
|
-
siteUrl: siteRecord.property
|
|
2189
|
-
}];
|
|
2190
|
-
} else sitesToSync = syncedSites.map((s) => ({
|
|
2191
|
-
siteId: s.siteId,
|
|
2192
|
-
siteUrl: s.property
|
|
2193
|
-
}));
|
|
2194
|
-
const periodDays = parsePeriodDays(period);
|
|
2195
|
-
const daysOffset = options.fresh ? 1 : 3;
|
|
2196
|
-
const adjustedEndDate = daysAgo(daysOffset);
|
|
2197
|
-
const baseStartDate = daysAgo(periodDays + daysOffset);
|
|
2198
|
-
const adjustedPrevEndDate = daysAgo(periodDays + daysOffset + 1);
|
|
2199
|
-
const adjustedPrevStartDate = daysAgo(periodDays * 2 + daysOffset);
|
|
2200
|
-
const getRangeForSite = async (siteId) => {
|
|
2201
|
-
let startDate = baseStartDate;
|
|
2202
|
-
if (options.since) startDate = options.since;
|
|
2203
|
-
else if (options.incremental) {
|
|
2204
|
-
const lastSynced = await getLastSyncedDate(db, siteId);
|
|
2205
|
-
if (lastSynced) {
|
|
2206
|
-
const incrementalStart = dayjs(lastSynced).add(1, "day").format("YYYY-MM-DD");
|
|
2207
|
-
startDate = incrementalStart > baseStartDate ? incrementalStart : baseStartDate;
|
|
2208
|
-
}
|
|
2209
|
-
}
|
|
2210
|
-
if (startDate > adjustedEndDate) return {
|
|
2211
|
-
range: {
|
|
2212
|
-
period: {
|
|
2213
|
-
start: startDate,
|
|
2214
|
-
end: adjustedEndDate
|
|
2215
|
-
},
|
|
2216
|
-
prevPeriod: {
|
|
2217
|
-
start: adjustedPrevStartDate,
|
|
2218
|
-
end: adjustedPrevEndDate
|
|
2219
|
-
}
|
|
2220
|
-
},
|
|
2221
|
-
startDate,
|
|
2222
|
-
skipped: true
|
|
2223
|
-
};
|
|
2224
|
-
return {
|
|
2225
|
-
range: {
|
|
2226
|
-
period: {
|
|
2227
|
-
start: startDate,
|
|
2228
|
-
end: adjustedEndDate
|
|
2229
|
-
},
|
|
2230
|
-
prevPeriod: {
|
|
2231
|
-
start: adjustedPrevStartDate,
|
|
2232
|
-
end: adjustedPrevEndDate
|
|
2233
|
-
}
|
|
2234
|
-
},
|
|
2235
|
-
startDate,
|
|
2236
|
-
skipped: false
|
|
2237
|
-
};
|
|
2238
|
-
};
|
|
2239
|
-
report.period = {
|
|
2240
|
-
start: options.since || baseStartDate,
|
|
2241
|
-
end: adjustedEndDate
|
|
2242
|
-
};
|
|
2243
|
-
if (!options.quiet && !options.json) {
|
|
2244
|
-
const modeNote = options.incremental ? " (incremental)" : options.since ? ` (since ${options.since})` : options.fresh ? " (fresh)" : "";
|
|
2245
|
-
logger.info(`Period: ${options.since || baseStartDate} to ${adjustedEndDate}${modeNote}`);
|
|
2246
|
-
console.log();
|
|
2247
|
-
}
|
|
2248
|
-
const dataTypes = granular ? [
|
|
2249
|
-
"pages",
|
|
2250
|
-
"keywords",
|
|
2251
|
-
"keyword-paths",
|
|
2252
|
-
"countries",
|
|
2253
|
-
"devices"
|
|
2254
|
-
] : [
|
|
2255
|
-
"pages",
|
|
2256
|
-
"keywords",
|
|
2257
|
-
"countries",
|
|
2258
|
-
"devices"
|
|
2259
|
-
];
|
|
2260
|
-
for (const { siteId, siteUrl } of sitesToSync) {
|
|
2261
|
-
const siteName = siteUrl.replace(/^(sc-domain:|https?:\/\/)/, "");
|
|
2262
|
-
const { range, startDate, skipped } = await getRangeForSite(siteId);
|
|
2263
|
-
if (skipped) {
|
|
2264
|
-
if (!options.quiet && !options.json) logger.info(`${siteName} is up to date (last synced: ${startDate})`);
|
|
2265
|
-
continue;
|
|
2266
|
-
}
|
|
2267
|
-
const siteReport = {
|
|
2268
|
-
site: siteUrl,
|
|
2269
|
-
rows: {
|
|
2270
|
-
pages: 0,
|
|
2271
|
-
keywords: 0,
|
|
2272
|
-
countries: 0,
|
|
2273
|
-
devices: 0,
|
|
2274
|
-
total: 0
|
|
2275
|
-
}
|
|
2276
|
-
};
|
|
2277
|
-
if (!options.quiet && !options.json && (options.incremental || options.since)) logger.info(`${siteName}: syncing ${startDate} to ${adjustedEndDate}`);
|
|
2278
|
-
for (let i = 0; i < dataTypes.length; i++) {
|
|
2279
|
-
const dataType = dataTypes[i];
|
|
2280
|
-
if (!options.quiet && !options.json) {
|
|
2281
|
-
clearLine();
|
|
2282
|
-
process.stdout.write(progressBar(i + 1, dataTypes.length, `${dataType} (${siteName})`));
|
|
2283
|
-
}
|
|
2284
|
-
let rows = [];
|
|
2285
|
-
if (dataType === "pages") {
|
|
2286
|
-
rows = await syncPages(db, client, siteId, siteUrl, range);
|
|
2287
|
-
siteReport.rows.pages = rows.length;
|
|
2288
|
-
} else if (dataType === "keywords") {
|
|
2289
|
-
rows = await syncKeywords(db, client, siteId, siteUrl, range);
|
|
2290
|
-
siteReport.rows.keywords = rows.length;
|
|
2291
|
-
} else if (dataType === "keyword-paths") {
|
|
2292
|
-
rows = await syncKeywordPaths(db, client, siteId, siteUrl, range);
|
|
2293
|
-
siteReport.rows.keywordPaths = rows.length;
|
|
2294
|
-
} else if (dataType === "countries") {
|
|
2295
|
-
rows = await syncCountries(db, client, siteId, siteUrl, range);
|
|
2296
|
-
siteReport.rows.countries = rows.length;
|
|
2297
|
-
} else if (dataType === "devices") {
|
|
2298
|
-
rows = await syncDevices(db, client, siteId, siteUrl, range);
|
|
2299
|
-
siteReport.rows.devices = rows.length;
|
|
2300
|
-
}
|
|
2301
|
-
siteReport.rows.total += rows.length;
|
|
2302
|
-
}
|
|
2303
|
-
if (!options.quiet && !options.json) clearLine();
|
|
2304
|
-
await updateLastSynced(db, siteId);
|
|
2305
|
-
report.sites.push(siteReport);
|
|
2306
|
-
report.totalRows += siteReport.rows.total;
|
|
2307
|
-
if (!options.quiet && !options.json) logger.success(`Synced ${siteName} (${siteReport.rows.total.toLocaleString()} rows)`);
|
|
2308
|
-
}
|
|
2309
|
-
if (!options.quiet && !options.json) {
|
|
2310
|
-
console.log();
|
|
2311
|
-
logger.success(`Database saved to ${resolvedPath}`);
|
|
2312
|
-
}
|
|
2313
|
-
return report;
|
|
2314
|
-
}
|
|
2315
|
-
const syncCommand = defineCommand({
|
|
2316
|
-
meta: {
|
|
2317
|
-
name: "sync",
|
|
2318
|
-
description: "Sync GSC data to SQLite database"
|
|
2319
|
-
},
|
|
2320
|
-
args: {
|
|
2321
|
-
db: {
|
|
2322
|
-
type: "string",
|
|
2323
|
-
alias: "d",
|
|
2324
|
-
default: "./gscdump.db",
|
|
2325
|
-
description: "SQLite database path"
|
|
2326
|
-
},
|
|
2327
|
-
site: {
|
|
2328
|
-
type: "string",
|
|
2329
|
-
alias: "s",
|
|
2330
|
-
description: "Site URL (e.g., sc-domain:example.com)"
|
|
2331
|
-
},
|
|
2332
|
-
period: {
|
|
2333
|
-
type: "string",
|
|
2334
|
-
alias: "p",
|
|
2335
|
-
default: "90d",
|
|
2336
|
-
description: "Time period: 90d, 6m, 1y, max (all GSC history ~16mo)"
|
|
2337
|
-
},
|
|
2338
|
-
granular: {
|
|
2339
|
-
type: "boolean",
|
|
2340
|
-
alias: "g",
|
|
2341
|
-
default: false,
|
|
2342
|
-
description: "Include keyword-per-page data (large dataset)"
|
|
2343
|
-
},
|
|
2344
|
-
quiet: {
|
|
2345
|
-
type: "boolean",
|
|
2346
|
-
alias: "q",
|
|
2347
|
-
default: false,
|
|
2348
|
-
description: "Suppress output"
|
|
2349
|
-
},
|
|
2350
|
-
json: {
|
|
2351
|
-
type: "boolean",
|
|
2352
|
-
default: false,
|
|
2353
|
-
description: "Output sync report as JSON"
|
|
2354
|
-
},
|
|
2355
|
-
fresh: {
|
|
2356
|
-
type: "boolean",
|
|
2357
|
-
default: false,
|
|
2358
|
-
description: "Include fresh/unfinalized data (last 3 days)"
|
|
2359
|
-
},
|
|
2360
|
-
incremental: {
|
|
2361
|
-
type: "boolean",
|
|
2362
|
-
alias: "i",
|
|
2363
|
-
default: false,
|
|
2364
|
-
description: "Only fetch data since last sync"
|
|
2365
|
-
},
|
|
2366
|
-
since: {
|
|
2367
|
-
type: "string",
|
|
2368
|
-
description: "Fetch data from specific date (YYYY-MM-DD)"
|
|
2369
|
-
}
|
|
2370
|
-
},
|
|
2371
|
-
async run({ args }) {
|
|
2372
|
-
const config = await loadConfig();
|
|
2373
|
-
const dbPath = args.db === "./gscdump.db" && config.defaultDb ? config.defaultDb : args.db;
|
|
2374
|
-
const siteArg = args.site || config.defaultSite || null;
|
|
2375
|
-
const periodArg = args.period === "90d" && config.defaultPeriod ? config.defaultPeriod : args.period;
|
|
2376
|
-
const { getAuth: getAuth$1 } = await Promise.resolve().then(() => auth_exports);
|
|
2377
|
-
const report = await runSync(googleSearchConsole(await getAuth$1({
|
|
2378
|
-
interactive: false,
|
|
2379
|
-
config
|
|
2380
|
-
})), dbPath, siteArg, periodArg, args.granular, {
|
|
2381
|
-
quiet: args.quiet,
|
|
2382
|
-
json: args.json,
|
|
2383
|
-
fresh: args.fresh,
|
|
2384
|
-
incremental: args.incremental,
|
|
2385
|
-
since: args.since
|
|
2386
|
-
}).catch(gscErrorHandler);
|
|
2387
|
-
if (args.json) console.log(JSON.stringify(report, null, 2));
|
|
2388
|
-
else if (!args.quiet) logger.success("Done!");
|
|
2389
|
-
}
|
|
2390
|
-
});
|
|
2391
|
-
|
|
2392
1651
|
//#endregion
|
|
2393
1652
|
//#region src/index.ts
|
|
2394
1653
|
runMain(defineCommand({
|
|
@@ -2400,26 +1659,15 @@ runMain(defineCommand({
|
|
|
2400
1659
|
subCommands: {
|
|
2401
1660
|
init: initCommand,
|
|
2402
1661
|
dump: dumpCommand,
|
|
2403
|
-
|
|
2404
|
-
compare: compareCommand,
|
|
2405
|
-
analyze: analyzeCommand,
|
|
1662
|
+
query: queryCommand,
|
|
2406
1663
|
sites: sitesCommand,
|
|
2407
1664
|
sitemaps: sitemapsCommand,
|
|
2408
|
-
index: indexingCommand,
|
|
2409
|
-
inspect: inspectCommand,
|
|
2410
1665
|
auth: authCommand,
|
|
2411
1666
|
config: configCommand,
|
|
2412
1667
|
mcp: mcpCommand
|
|
2413
1668
|
},
|
|
2414
1669
|
setup() {
|
|
2415
1670
|
if (!process.argv.includes("mcp")) showSplash();
|
|
2416
|
-
},
|
|
2417
|
-
async run({ args }) {
|
|
2418
|
-
if (!args._.length) await dumpCommand.run({
|
|
2419
|
-
args,
|
|
2420
|
-
rawArgs: [],
|
|
2421
|
-
cmd: dumpCommand
|
|
2422
|
-
});
|
|
2423
1671
|
}
|
|
2424
1672
|
}));
|
|
2425
1673
|
|