@gscdump/cli 0.3.0 → 0.4.0

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