@gscdump/cli 0.0.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -0
- package/dist/index.mjs +56 -60
- package/package.json +25 -10
package/README.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# @gscdump/cli
|
|
2
|
+
|
|
3
|
+
[](https://npmjs.com/package/@gscdump/cli)
|
|
4
|
+
[](https://npm.chart.dev/@gscdump/cli)
|
|
5
|
+
[](https://github.com/harlan-zw/gscdump/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
> CLI for Google Search Console - dump, sync, compare, analyze, and run MCP server.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Data Export** - Dump analytics to stdout, file, or SQLite database
|
|
12
|
+
- **Period Comparison** - Compare metrics across time periods
|
|
13
|
+
- **SEO Analysis** - Striking distance, movers & shakers, decay detection
|
|
14
|
+
- **Indexing Tools** - Check status, request indexing, batch operations
|
|
15
|
+
- **MCP Server** - Let AI agents query your search data directly
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g @gscdump/cli
|
|
21
|
+
# or run with npx
|
|
22
|
+
npx @gscdump/cli
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# First-run setup (choose cloud or local auth)
|
|
29
|
+
gscdump init
|
|
30
|
+
|
|
31
|
+
# List your sites
|
|
32
|
+
gscdump sites
|
|
33
|
+
|
|
34
|
+
# Dump last 7 days to stdout
|
|
35
|
+
gscdump dump --site https://example.com --period 7d
|
|
36
|
+
|
|
37
|
+
# Sync to SQLite database
|
|
38
|
+
gscdump sync --site https://example.com --db ./gsc.db
|
|
39
|
+
|
|
40
|
+
# Compare periods
|
|
41
|
+
gscdump compare --site https://example.com --period 28d
|
|
42
|
+
|
|
43
|
+
# Run SEO analysis
|
|
44
|
+
gscdump analyze striking-distance --site https://example.com
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Commands
|
|
48
|
+
|
|
49
|
+
| Command | Description |
|
|
50
|
+
|---------|-------------|
|
|
51
|
+
| `init` | First-run setup (choose cloud/local mode) |
|
|
52
|
+
| `auth` | OAuth2 login with Google |
|
|
53
|
+
| `sites` | List GSC properties |
|
|
54
|
+
| `dump` | Export analytics to stdout/file |
|
|
55
|
+
| `sync` | Persist to SQLite database |
|
|
56
|
+
| `compare` | Period-over-period comparison |
|
|
57
|
+
| `analyze` | Run SEO analysis (striking-distance, movers, decay, etc.) |
|
|
58
|
+
| `sitemaps` | List/manage sitemaps for a site |
|
|
59
|
+
| `index` | URL indexing (status, inspect, request) |
|
|
60
|
+
| `inspect` | Quick URL inspection |
|
|
61
|
+
| `config` | Manage CLI configuration |
|
|
62
|
+
| `mcp` | Start MCP server for AI assistants |
|
|
63
|
+
|
|
64
|
+
## MCP Server
|
|
65
|
+
|
|
66
|
+
Start the MCP server for AI assistants:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
gscdump mcp
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Or add to your Claude config (`~/.claude.json` or VS Code settings):
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"mcpServers": {
|
|
77
|
+
"gscdump": {
|
|
78
|
+
"command": "npx",
|
|
79
|
+
"args": ["@gscdump/mcp"]
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Then ask Claude:
|
|
86
|
+
- "What pages lost traffic this week?"
|
|
87
|
+
- "Find keywords in striking distance (position 4-20)"
|
|
88
|
+
- "Which queries have keyword cannibalization?"
|
|
89
|
+
- "Compare this month vs last month"
|
|
90
|
+
|
|
91
|
+
## Auth Setup
|
|
92
|
+
|
|
93
|
+
**Cloud mode** (recommended):
|
|
94
|
+
```bash
|
|
95
|
+
gscdump init # Select "cloud"
|
|
96
|
+
```
|
|
97
|
+
Easy setup via cloud.gscdump.com - no API keys needed.
|
|
98
|
+
|
|
99
|
+
**Local mode** (bring your own credentials):
|
|
100
|
+
1. Create a Google Cloud project
|
|
101
|
+
2. Enable "Search Console API" and "Web Search Indexing API"
|
|
102
|
+
3. Create OAuth2 credentials (Desktop app)
|
|
103
|
+
4. Run `gscdump init` and select "local"
|
|
104
|
+
|
|
105
|
+
## Related Packages
|
|
106
|
+
|
|
107
|
+
- [`gscdump`](../gscdump) - Core library
|
|
108
|
+
- [`@gscdump/mcp`](../mcp) - MCP server
|
|
109
|
+
- [`@gscdump/db`](../db) - SQLite persistence
|
|
110
|
+
- [`@gscdump/query`](../query) - Unified data provider
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
|
|
114
|
+
[MIT](../../LICENSE)
|
package/dist/index.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import process from "node:process";
|
|
|
5
5
|
import { defineCommand, runMain } from "citty";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { batchInspectUrls, batchRequestIndexingForPaths, createGscDb, getIndexingStats, getLastSyncedDate, getSiteByProperty, setupSchema, sitePathDateAnalytics, syncCountries, syncDevices, syncKeywordPaths, syncKeywords, syncPages, syncSites, updateLastSynced } from "@gscdump/db";
|
|
8
|
-
import { createProvider, fetchCannibalizationAnalysis, fetchDecayAnalysis, fetchMoversAnalysis, fetchOpportunityAnalysis, fetchStrikingDistanceAnalysis, fetchZeroClickAnalysis } from "@gscdump/query";
|
|
8
|
+
import { createProvider, daysAgo, fetchCannibalizationAnalysis, fetchDecayAnalysis, fetchMoversAnalysis, fetchOpportunityAnalysis, fetchStrikingDistanceAnalysis, fetchZeroClickAnalysis } from "@gscdump/query";
|
|
9
9
|
import dayjs from "dayjs";
|
|
10
10
|
import betterSqlite3 from "db0/connectors/better-sqlite3";
|
|
11
11
|
import fs from "node:fs/promises";
|
|
@@ -14,7 +14,7 @@ import { cancel, confirm, isCancel, multiselect, select, text } from "@clack/pro
|
|
|
14
14
|
import { OAuth2Client } from "google-auth-library";
|
|
15
15
|
import os from "node:os";
|
|
16
16
|
import { consola } from "consola";
|
|
17
|
-
import { deleteSitemap, fetchSitemap, fetchSitemaps, fetchSites, formatErrorForCli, googleSearchConsole, submitSitemap
|
|
17
|
+
import { deleteSitemap, fetchSitemap, fetchSitemaps, fetchSites, formatErrorForCli, googleSearchConsole, submitSitemap } from "gscdump";
|
|
18
18
|
import { createGscMcpServer } from "@gscdump/mcp/server";
|
|
19
19
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
20
20
|
|
|
@@ -423,6 +423,17 @@ async function getAuth(opts = {}) {
|
|
|
423
423
|
|
|
424
424
|
//#endregion
|
|
425
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
|
+
}
|
|
426
437
|
const ANALYSIS_TYPES = [
|
|
427
438
|
"striking-distance",
|
|
428
439
|
"opportunity",
|
|
@@ -532,13 +543,7 @@ const analyzeCommand = defineCommand({
|
|
|
532
543
|
end: prevEndDate.format("YYYY-MM-DD")
|
|
533
544
|
}
|
|
534
545
|
};
|
|
535
|
-
const provider =
|
|
536
|
-
auth,
|
|
537
|
-
db: dbPath ? createGscDb(betterSqlite3({ name: path.resolve(dbPath) })).db : null,
|
|
538
|
-
source: args.source,
|
|
539
|
-
siteUrls: [siteArg],
|
|
540
|
-
range
|
|
541
|
-
});
|
|
546
|
+
const provider = getProviderForSource$2(auth, dbPath ? createGscDb(betterSqlite3({ name: path.resolve(dbPath) })).db : null, args.source);
|
|
542
547
|
const limit = Number.parseInt(args.limit, 10) || 20;
|
|
543
548
|
if (!args.json) {
|
|
544
549
|
console.log();
|
|
@@ -717,6 +722,17 @@ const authCommand = defineCommand({
|
|
|
717
722
|
|
|
718
723
|
//#endregion
|
|
719
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
|
+
}
|
|
720
736
|
const SEARCH_TYPES = [
|
|
721
737
|
"web",
|
|
722
738
|
"image",
|
|
@@ -876,13 +892,7 @@ const compareCommand = defineCommand({
|
|
|
876
892
|
const searchType = SEARCH_TYPES.includes(args.searchType) ? args.searchType : "web";
|
|
877
893
|
const effectiveSource = searchType !== "web" ? "api" : args.source || "auto";
|
|
878
894
|
if (searchType !== "web" && args.source === "db") logger.warn(`Search type '${searchType}' requires API. Ignoring --source db.`);
|
|
879
|
-
const provider =
|
|
880
|
-
auth,
|
|
881
|
-
db: dbPath ? createGscDb(betterSqlite3({ name: path.resolve(dbPath) })).db : null,
|
|
882
|
-
source: effectiveSource,
|
|
883
|
-
siteUrls: [siteArg],
|
|
884
|
-
range
|
|
885
|
-
});
|
|
895
|
+
const provider = getProviderForSource$1(auth, dbPath ? createGscDb(betterSqlite3({ name: path.resolve(dbPath) })).db : null, effectiveSource);
|
|
886
896
|
if (args.json) {
|
|
887
897
|
const [dates, pages, keywords] = await Promise.all([
|
|
888
898
|
provider.getDatesWithComparison(siteArg, range),
|
|
@@ -1054,6 +1064,23 @@ const configCommand = defineCommand({
|
|
|
1054
1064
|
|
|
1055
1065
|
//#endregion
|
|
1056
1066
|
//#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
|
+
}
|
|
1057
1084
|
const DUMP_DATA_TYPES = [
|
|
1058
1085
|
"pages",
|
|
1059
1086
|
"keywords",
|
|
@@ -1252,29 +1279,11 @@ async function interactiveMode(auth, dbPath, source) {
|
|
|
1252
1279
|
return;
|
|
1253
1280
|
}
|
|
1254
1281
|
console.log();
|
|
1255
|
-
const endDate = dayjs().subtract(3, "days").format("YYYY-MM-DD");
|
|
1256
|
-
const startDate = dayjs().subtract(period.amount, period.unit).subtract(3, "days").format("YYYY-MM-DD");
|
|
1257
|
-
const range = {
|
|
1258
|
-
period: {
|
|
1259
|
-
start: startDate,
|
|
1260
|
-
end: endDate
|
|
1261
|
-
},
|
|
1262
|
-
prevPeriod: {
|
|
1263
|
-
start: dayjs(startDate).subtract(period.amount, period.unit).format("YYYY-MM-DD"),
|
|
1264
|
-
end: dayjs(endDate).subtract(period.amount, period.unit).format("YYYY-MM-DD")
|
|
1265
|
-
}
|
|
1266
|
-
};
|
|
1267
1282
|
let db = null;
|
|
1268
1283
|
if (dbPath) {
|
|
1269
1284
|
if (await fs.access(dbPath).then(() => true).catch(() => false)) db = createGscDb(betterSqlite3({ name: path.resolve(dbPath) })).db;
|
|
1270
1285
|
}
|
|
1271
|
-
const provider =
|
|
1272
|
-
auth,
|
|
1273
|
-
db,
|
|
1274
|
-
source: finalSource,
|
|
1275
|
-
siteUrls: selectedSites,
|
|
1276
|
-
range
|
|
1277
|
-
});
|
|
1286
|
+
const provider = getProviderForSource(auth, db, finalSource);
|
|
1278
1287
|
logger.info(`Using ${provider.source.toUpperCase()} as data source`);
|
|
1279
1288
|
await runDump(provider, selectedSites, dataTypes, period, format, null);
|
|
1280
1289
|
}
|
|
@@ -1314,19 +1323,6 @@ async function nonInteractiveMode(auth, site, data, periodStr, format, output, d
|
|
|
1314
1323
|
}
|
|
1315
1324
|
normalizedSites.push(match);
|
|
1316
1325
|
}
|
|
1317
|
-
const daysOffset = options.fresh ? 1 : 3;
|
|
1318
|
-
const endDate = dayjs().subtract(daysOffset, "days").format("YYYY-MM-DD");
|
|
1319
|
-
const startDate = dayjs().subtract(period.amount, period.unit).subtract(daysOffset, "days").format("YYYY-MM-DD");
|
|
1320
|
-
const range = {
|
|
1321
|
-
period: {
|
|
1322
|
-
start: startDate,
|
|
1323
|
-
end: endDate
|
|
1324
|
-
},
|
|
1325
|
-
prevPeriod: {
|
|
1326
|
-
start: dayjs(startDate).subtract(period.amount, period.unit).format("YYYY-MM-DD"),
|
|
1327
|
-
end: dayjs(endDate).subtract(period.amount, period.unit).format("YYYY-MM-DD")
|
|
1328
|
-
}
|
|
1329
|
-
};
|
|
1330
1326
|
let db = null;
|
|
1331
1327
|
if (dbPath) {
|
|
1332
1328
|
if (await fs.access(dbPath).then(() => true).catch(() => false)) db = createGscDb(betterSqlite3({ name: path.resolve(dbPath) })).db;
|
|
@@ -1336,13 +1332,7 @@ async function nonInteractiveMode(auth, site, data, periodStr, format, output, d
|
|
|
1336
1332
|
process.exit(1);
|
|
1337
1333
|
}
|
|
1338
1334
|
}
|
|
1339
|
-
const provider =
|
|
1340
|
-
auth,
|
|
1341
|
-
db,
|
|
1342
|
-
source,
|
|
1343
|
-
siteUrls: normalizedSites,
|
|
1344
|
-
range
|
|
1345
|
-
}).catch(gscErrorHandler);
|
|
1335
|
+
const provider = getProviderForSource(auth, db, source);
|
|
1346
1336
|
const extras = [];
|
|
1347
1337
|
if (options.fresh) extras.push("fresh");
|
|
1348
1338
|
if (options.searchType && options.searchType !== "web") extras.push(options.searchType);
|
|
@@ -2158,6 +2148,12 @@ const sitesCommand = defineCommand({
|
|
|
2158
2148
|
|
|
2159
2149
|
//#endregion
|
|
2160
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
|
+
}
|
|
2161
2157
|
async function runSync(client, dbPath, siteArg, period, granular, options) {
|
|
2162
2158
|
const resolvedPath = path.resolve(dbPath);
|
|
2163
2159
|
const report = {
|
|
@@ -2195,12 +2191,12 @@ async function runSync(client, dbPath, siteArg, period, granular, options) {
|
|
|
2195
2191
|
siteId: s.siteId,
|
|
2196
2192
|
siteUrl: s.property
|
|
2197
2193
|
}));
|
|
2198
|
-
const
|
|
2194
|
+
const periodDays = parsePeriodDays(period);
|
|
2199
2195
|
const daysOffset = options.fresh ? 1 : 3;
|
|
2200
|
-
const adjustedEndDate =
|
|
2201
|
-
const baseStartDate =
|
|
2202
|
-
const adjustedPrevEndDate =
|
|
2203
|
-
const adjustedPrevStartDate =
|
|
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);
|
|
2204
2200
|
const getRangeForSite = async (siteId) => {
|
|
2205
2201
|
let startDate = baseStartDate;
|
|
2206
2202
|
if (options.since) startDate = options.since;
|
package/package.json
CHANGED
|
@@ -1,19 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gscdump/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
5
|
-
"description": "CLI for
|
|
4
|
+
"version": "0.1.2",
|
|
5
|
+
"description": "CLI for Google Search Console - dump, sync, compare, analyze, and run MCP server",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
8
8
|
"email": "harlan@harlanzw.com",
|
|
9
9
|
"url": "https://harlanzw.com/"
|
|
10
10
|
},
|
|
11
11
|
"license": "MIT",
|
|
12
|
+
"funding": "https://github.com/sponsors/harlan-zw",
|
|
13
|
+
"homepage": "https://github.com/harlan-zw/gscdump/tree/main/packages/cli#readme",
|
|
12
14
|
"repository": {
|
|
13
15
|
"type": "git",
|
|
14
16
|
"url": "git+https://github.com/harlan-zw/gscdump.git",
|
|
15
17
|
"directory": "packages/cli"
|
|
16
18
|
},
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/harlan-zw/gscdump/issues"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"google-search-console",
|
|
24
|
+
"gsc",
|
|
25
|
+
"seo",
|
|
26
|
+
"cli",
|
|
27
|
+
"mcp",
|
|
28
|
+
"search-analytics",
|
|
29
|
+
"data-export",
|
|
30
|
+
"sqlite"
|
|
31
|
+
],
|
|
17
32
|
"bin": {
|
|
18
33
|
"gscdump": "./dist/index.mjs"
|
|
19
34
|
},
|
|
@@ -22,20 +37,20 @@
|
|
|
22
37
|
],
|
|
23
38
|
"dependencies": {
|
|
24
39
|
"@clack/prompts": "^0.11.0",
|
|
25
|
-
"@modelcontextprotocol/sdk": "^1.25.
|
|
40
|
+
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
26
41
|
"citty": "^0.1.6",
|
|
27
42
|
"consola": "^3.4.2",
|
|
28
|
-
"dayjs": "^1.11.
|
|
43
|
+
"dayjs": "^1.11.19",
|
|
29
44
|
"db0": "^0.3.4",
|
|
30
45
|
"google-auth-library": "^10.5.0",
|
|
31
|
-
"open": "^
|
|
32
|
-
"@gscdump/
|
|
33
|
-
"@gscdump/
|
|
34
|
-
"gscdump": "0.
|
|
35
|
-
"
|
|
46
|
+
"open": "^11.0.0",
|
|
47
|
+
"@gscdump/db": "0.1.2",
|
|
48
|
+
"@gscdump/mcp": "0.1.2",
|
|
49
|
+
"@gscdump/query": "0.1.2",
|
|
50
|
+
"gscdump": "0.1.2"
|
|
36
51
|
},
|
|
37
52
|
"devDependencies": {
|
|
38
|
-
"vitest": "^4.0.
|
|
53
|
+
"vitest": "^4.0.17"
|
|
39
54
|
},
|
|
40
55
|
"scripts": {
|
|
41
56
|
"build": "obuild",
|