@gscdump/cli 0.32.12 → 0.33.3

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.
Files changed (2) hide show
  1. package/dist/index.mjs +106 -51
  2. package/package.json +5 -5
package/dist/index.mjs CHANGED
@@ -10,7 +10,7 @@ import { err, ok, unwrapResult } from "gscdump/result";
10
10
  import { decodeSiteId, normalizeSiteUrl } from "gscdump/tenant";
11
11
  import os from "node:os";
12
12
  import { cancel, confirm, isCancel, multiselect, select, text } from "@clack/prompts";
13
- import { addSite, batchInspectUrls, batchRequestIndexing, createAuth, deleteSite, discoverSitemap, fetchSitemap, fetchSitemapUrls, fetchSitesWithSitemaps, formatErrorForCli, getIndexingMetadata, getVerificationToken, getVerifiedSite, googleSearchConsole, listVerifiedSites, requestIndexing, runSequentialBatch, siteUrlToVerificationSite, unverifySite, verificationMethodsFor, verifySite } from "gscdump/api";
13
+ import { addSite, batchInspectUrls, batchRequestIndexing, createAuth, deleteSite, discoverSitemap, fetchSitemap, fetchSitemapUrls, fetchSitesWithSitemaps, formatErrorForCli, getIndexingMetadata, getVerificationToken, getVerifiedSite, googleSearchConsole, hasGscWriteScope, hasIndexingScope, listVerifiedSites, requestIndexing, runSequentialBatch, siteUrlToVerificationSite, unverifySite, verificationMethodsFor, verifySite } from "gscdump/api";
14
14
  import { createServer } from "node:http";
15
15
  import { JWT, OAuth2Client } from "google-auth-library";
16
16
  import { ofetch } from "ofetch";
@@ -136,7 +136,7 @@ function loadEnvFromCwd() {
136
136
  }
137
137
  return applied;
138
138
  }
139
- var version = "0.32.12";
139
+ var version = "0.33.3";
140
140
  const ALL_SEARCH_TYPES$1 = Object.values(SearchTypes);
141
141
  const VERSION = version;
142
142
  function noSubcommandSelected(parent, subNames) {
@@ -1769,6 +1769,17 @@ const REQUIRED_SCOPES$2 = [
1769
1769
  "https://www.googleapis.com/auth/indexing",
1770
1770
  "https://www.googleapis.com/auth/siteverification"
1771
1771
  ];
1772
+ function hasGoogleScope$2(scopes, scope) {
1773
+ const suffix = scope.replace("https://www.googleapis.com/auth/", "");
1774
+ return scopes.includes(scope) || scopes.includes(suffix);
1775
+ }
1776
+ function missingRequiredScopes$2(scopes) {
1777
+ return REQUIRED_SCOPES$2.filter((scope) => {
1778
+ if (scope.endsWith("/webmasters")) return !hasGscWriteScope(scopes);
1779
+ if (scope.endsWith("/indexing")) return !hasIndexingScope(scopes);
1780
+ return !hasGoogleScope$2(scopes, scope);
1781
+ });
1782
+ }
1772
1783
  async function fetchTokenInfo(accessToken) {
1773
1784
  return ofetch("https://oauth2.googleapis.com/tokeninfo", { query: { access_token: accessToken } }).catch(() => null);
1774
1785
  }
@@ -1781,8 +1792,7 @@ async function resolveLiveAuthState() {
1781
1792
  else if (tokens?.access_token) liveToken = tokens.access_token;
1782
1793
  const tokenInfo = liveToken ? await fetchTokenInfo(liveToken) : null;
1783
1794
  const scopes = tokenInfo?.scope ? tokenInfo.scope.split(/\s+/).filter(Boolean) : [];
1784
- const has = (s) => scopes.includes(s) || scopes.includes(s.replace(".readonly", ""));
1785
- const missing = REQUIRED_SCOPES$2.filter((s) => !has(s));
1795
+ const missing = missingRequiredScopes$2(scopes);
1786
1796
  return {
1787
1797
  byok,
1788
1798
  tokens,
@@ -2277,6 +2287,17 @@ function redact(v) {
2277
2287
  if (v.length <= 6) return "***";
2278
2288
  return `***${v.slice(-6)}`;
2279
2289
  }
2290
+ function hasGoogleScope$1(scopes, scope) {
2291
+ const suffix = scope.replace("https://www.googleapis.com/auth/", "");
2292
+ return scopes.includes(scope) || scopes.includes(suffix);
2293
+ }
2294
+ function missingRequiredScopes$1(scopes) {
2295
+ return REQUIRED_SCOPES$1.filter((scope) => {
2296
+ if (scope.endsWith("/webmasters")) return !hasGscWriteScope(scopes);
2297
+ if (scope.endsWith("/indexing")) return !hasIndexingScope(scopes);
2298
+ return !hasGoogleScope$1(scopes, scope);
2299
+ });
2300
+ }
2280
2301
  async function checkEnv() {
2281
2302
  const envPath = path.join(process.cwd(), ".env");
2282
2303
  const parsed = parseEnvFile(envPath);
@@ -2384,7 +2405,7 @@ async function checkAuth$1(envKeys) {
2384
2405
  detail: info.email
2385
2406
  });
2386
2407
  const scopes = info.scope ? info.scope.split(/\s+/) : [];
2387
- const missing = REQUIRED_SCOPES$1.filter((s) => !scopes.includes(s) && !scopes.includes(s.replace(".readonly", "")));
2408
+ const missing = missingRequiredScopes$1(scopes);
2388
2409
  if (missing.length > 0) checks.push({
2389
2410
  name: "auth.scopes",
2390
2411
  status: "warn",
@@ -3740,6 +3761,17 @@ const REQUIRED_SCOPES = [
3740
3761
  ];
3741
3762
  const FETCH_TIMEOUT_MS = 5e3;
3742
3763
  const TIME_SKEW_WARN_MS = 5 * 6e4;
3764
+ function hasGoogleScope(scopes, scope) {
3765
+ const suffix = scope.replace("https://www.googleapis.com/auth/", "");
3766
+ return scopes.includes(scope) || scopes.includes(suffix);
3767
+ }
3768
+ function missingRequiredScopes(scopes) {
3769
+ return REQUIRED_SCOPES.filter((scope) => {
3770
+ if (scope.endsWith("/webmasters")) return !hasGscWriteScope(scopes);
3771
+ if (scope.endsWith("/indexing")) return !hasIndexingScope(scopes);
3772
+ return !hasGoogleScope(scopes, scope);
3773
+ });
3774
+ }
3743
3775
  async function diagnostics(_input, ctx) {
3744
3776
  const checks = [];
3745
3777
  const token = await resolveAccessToken(ctx.auth);
@@ -3773,7 +3805,7 @@ async function diagnostics(_input, ctx) {
3773
3805
  detail: tokenInfo.email ?? "token valid"
3774
3806
  });
3775
3807
  const scopes = tokenInfo.scope ? tokenInfo.scope.split(/\s+/) : [];
3776
- const missing = REQUIRED_SCOPES.filter((s) => !scopes.includes(s));
3808
+ const missing = missingRequiredScopes(scopes);
3777
3809
  checks.push(missing.length > 0 ? {
3778
3810
  name: "auth.scopes",
3779
3811
  status: "warn",
@@ -4114,6 +4146,50 @@ const runReportInput = z.object({
4114
4146
  prevEnd: z.string().optional().describe("Override comparison-window end."),
4115
4147
  maxFindings: z.number().optional().describe("Cap findings per section (per-report default ~5).")
4116
4148
  });
4149
+ async function runMcpSearchAnalyticsQuery(client, args) {
4150
+ const totalLimit = Math.max(0, args.rowLimit ?? 25e3);
4151
+ const pageSize = Math.min(totalLimit, 25e3);
4152
+ const allRows = [];
4153
+ let startRow = 0;
4154
+ while (allRows.length < totalLimit) {
4155
+ const remaining = totalLimit - allRows.length;
4156
+ const currentLimit = Math.min(pageSize, remaining);
4157
+ if (currentLimit <= 0) break;
4158
+ const rows = ((await client._rawQuery(args.siteUrl, {
4159
+ startDate: args.startDate,
4160
+ endDate: args.endDate,
4161
+ dimensions: args.dimensions,
4162
+ rowLimit: currentLimit,
4163
+ startRow,
4164
+ ...args.type ? { type: args.type } : {},
4165
+ ...args.dataState ? { dataState: args.dataState } : {},
4166
+ ...args.aggregationType ? { aggregationType: args.aggregationType } : {},
4167
+ ...args.dimensionFilterGroups ? { dimensionFilterGroups: args.dimensionFilterGroups.map((g) => ({
4168
+ groupType: g.groupType ?? "and",
4169
+ filters: g.filters
4170
+ })) } : {}
4171
+ })).rows || []).map((row) => {
4172
+ const result = {
4173
+ clicks: row.clicks ?? 0,
4174
+ impressions: row.impressions ?? 0,
4175
+ ctr: row.ctr ?? 0,
4176
+ position: row.position ?? 0
4177
+ };
4178
+ args.dimensions.forEach((dim, i) => {
4179
+ result[dim] = row.keys?.[i];
4180
+ });
4181
+ return result;
4182
+ });
4183
+ if (rows.length === 0) break;
4184
+ allRows.push(...rows);
4185
+ startRow += rows.length;
4186
+ }
4187
+ return {
4188
+ siteUrl: args.siteUrl,
4189
+ rowCount: allRows.length,
4190
+ rows: allRows
4191
+ };
4192
+ }
4117
4193
  function createGscMcpServer(options) {
4118
4194
  const { name = "gscdump", version = "1.0.0", getAuth } = options;
4119
4195
  const server = new McpServer({
@@ -4264,47 +4340,20 @@ function createGscMcpServer(options) {
4264
4340
  dimensionFilterGroups: z.array(filterGroupSchema).optional().describe("Filter groups (each \"and\"-ed internally; multiple groups are OR-ed)")
4265
4341
  }).shape
4266
4342
  }, async ({ siteUrl, startDate, endDate, dimensions, rowLimit, type, dataState, aggregationType, dimensionFilterGroups }) => {
4267
- const client = await getClient();
4268
- const limit = rowLimit ?? 25e3;
4269
- const allRows = [];
4270
- let startRow = 0;
4271
- while (true) {
4272
- const rows = ((await client._rawQuery(siteUrl, {
4273
- startDate,
4274
- endDate,
4275
- dimensions,
4276
- rowLimit: limit,
4277
- startRow,
4278
- ...type ? { type } : {},
4279
- ...dataState ? { dataState } : {},
4280
- ...aggregationType ? { aggregationType } : {},
4281
- ...dimensionFilterGroups ? { dimensionFilterGroups: dimensionFilterGroups.map((g) => ({
4282
- groupType: g.groupType ?? "and",
4283
- filters: g.filters
4284
- })) } : {}
4285
- })).rows || []).map((row) => {
4286
- const result = {
4287
- clicks: row.clicks ?? 0,
4288
- impressions: row.impressions ?? 0,
4289
- ctr: row.ctr ?? 0,
4290
- position: row.position ?? 0
4291
- };
4292
- dimensions.forEach((dim, i) => {
4293
- result[dim] = row.keys?.[i];
4294
- });
4295
- return result;
4296
- });
4297
- allRows.push(...rows);
4298
- if (rows.length < limit) break;
4299
- startRow += rows.length;
4300
- }
4343
+ const result = await runMcpSearchAnalyticsQuery(await getClient(), {
4344
+ siteUrl,
4345
+ startDate,
4346
+ endDate,
4347
+ dimensions,
4348
+ rowLimit,
4349
+ type,
4350
+ dataState,
4351
+ aggregationType,
4352
+ dimensionFilterGroups
4353
+ });
4301
4354
  return { content: [{
4302
4355
  type: "text",
4303
- text: JSON.stringify({
4304
- siteUrl,
4305
- rowCount: allRows.length,
4306
- rows: allRows
4307
- }, null, 2)
4356
+ text: JSON.stringify(result, null, 2)
4308
4357
  }] };
4309
4358
  });
4310
4359
  server.registerTool("inspect-url", {
@@ -4505,6 +4554,7 @@ function createGscMcpServer(options) {
4505
4554
  return server;
4506
4555
  }
4507
4556
  async function checkAuth() {
4557
+ if (await resolveServiceAccount().then(Boolean).catch(() => false)) return { ok: true };
4508
4558
  if (resolveBYOK()) return { ok: true };
4509
4559
  const config = await loadConfig();
4510
4560
  if (!config.clientId && !config.clientSecret) return {
@@ -4546,7 +4596,7 @@ const mcpCommand = defineCommand({
4546
4596
  const server = createGscMcpServer({
4547
4597
  name: "gscdump",
4548
4598
  version: VERSION,
4549
- getAuth: () => getAuth({ interactive: false })
4599
+ getAuth: () => resolveAuth({ interactive: false })
4550
4600
  });
4551
4601
  const transport = new StdioServerTransport();
4552
4602
  await server.connect(transport);
@@ -4616,12 +4666,13 @@ function makeLeaf(col, operator, value) {
4616
4666
  }
4617
4667
  async function runLiveQuery(client, siteUrl, opts) {
4618
4668
  const allRows = [];
4669
+ const totalLimit = Math.max(0, opts.rowLimit);
4670
+ const pageSize = Math.min(totalLimit, 25e3);
4619
4671
  let startRow = 0;
4620
4672
  const baseBody = {
4621
4673
  startDate: opts.startDate,
4622
4674
  endDate: opts.endDate,
4623
- dimensions: opts.dimensions,
4624
- rowLimit: opts.rowLimit
4675
+ dimensions: opts.dimensions
4625
4676
  };
4626
4677
  if (opts.searchType) baseBody.type = opts.searchType;
4627
4678
  if (opts.dataState) baseBody.dataState = opts.dataState;
@@ -4630,9 +4681,13 @@ async function runLiveQuery(client, siteUrl, opts) {
4630
4681
  const groups = filterToGroups(opts.dimensionFilter);
4631
4682
  if (groups.length > 0) baseBody.dimensionFilterGroups = groups;
4632
4683
  }
4633
- while (true) {
4684
+ while (allRows.length < totalLimit) {
4685
+ const remaining = totalLimit - allRows.length;
4686
+ const rowLimit = Math.min(pageSize, remaining);
4687
+ if (rowLimit <= 0) break;
4634
4688
  const rows = ((await client._rawQuery(siteUrl, {
4635
4689
  ...baseBody,
4690
+ rowLimit,
4636
4691
  startRow
4637
4692
  })).rows || []).map((row) => {
4638
4693
  const result = {
@@ -4646,8 +4701,8 @@ async function runLiveQuery(client, siteUrl, opts) {
4646
4701
  });
4647
4702
  return result;
4648
4703
  });
4704
+ if (rows.length === 0) break;
4649
4705
  allRows.push(...rows);
4650
- if (rows.length < opts.rowLimit) break;
4651
4706
  startRow += rows.length;
4652
4707
  }
4653
4708
  return { rows: allRows };
@@ -6829,7 +6884,7 @@ async function runOneDate(store, client, siteUrl, table, searchType, dims, date)
6829
6884
  });
6830
6885
  if (transformed) rows.push(transformed.row);
6831
6886
  }
6832
- if (batch.length < rowLimit) break;
6887
+ if (batch.length === 0) break;
6833
6888
  startRow += batch.length;
6834
6889
  }
6835
6890
  const writeCtx = {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gscdump/cli",
3
3
  "type": "module",
4
- "version": "0.32.12",
4
+ "version": "0.33.3",
5
5
  "description": "CLI for Google Search Console - dump, query, and run MCP server",
6
6
  "author": {
7
7
  "name": "Harlan Wilton",
@@ -43,10 +43,10 @@
43
43
  "ofetch": "^1.5.1",
44
44
  "open": "^11.0.0",
45
45
  "zod": "^4.4.3",
46
- "@gscdump/engine": "0.32.12",
47
- "@gscdump/engine-gsc-api": "0.32.12",
48
- "gscdump": "0.32.12",
49
- "@gscdump/analysis": "0.32.12"
46
+ "@gscdump/analysis": "0.33.3",
47
+ "@gscdump/engine-gsc-api": "0.33.3",
48
+ "gscdump": "0.33.3",
49
+ "@gscdump/engine": "0.33.3"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@duckdb/node-api": "1.5.1-r.2",