@gscdump/cli 3.8.0 → 4.0.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 (73) hide show
  1. package/README.md +113 -30
  2. package/bin/gscdump.mjs +21 -1
  3. package/dist/analysis-local.mjs +172 -53
  4. package/dist/auth-state.mjs +21 -6
  5. package/dist/auth.mjs +27 -18
  6. package/dist/bing-data.mjs +6 -2
  7. package/dist/bing-hosted.mjs +1 -1
  8. package/dist/cli-args.mjs +8 -0
  9. package/dist/cli.d.mts +5 -1
  10. package/dist/cli.mjs +41 -10
  11. package/dist/cloud-google.mjs +8 -1
  12. package/dist/command-meta.mjs +4 -4
  13. package/dist/command-registry.mjs +86 -10
  14. package/dist/commands/analyze.mjs +84 -43
  15. package/dist/commands/auth.mjs +46 -37
  16. package/dist/commands/bing.mjs +3 -3
  17. package/dist/commands/compact.mjs +12 -8
  18. package/dist/commands/config.mjs +14 -14
  19. package/dist/commands/doctor.mjs +26 -26
  20. package/dist/commands/dump.mjs +203 -105
  21. package/dist/commands/entities.mjs +10 -113
  22. package/dist/commands/gc.mjs +4 -3
  23. package/dist/commands/indexing-urls.mjs +185 -0
  24. package/dist/commands/indexing.mjs +12 -8
  25. package/dist/commands/init.mjs +77 -18
  26. package/dist/commands/inspect.mjs +97 -110
  27. package/dist/commands/mcp.mjs +37 -57
  28. package/dist/commands/papercut.mjs +1 -1
  29. package/dist/commands/profile.mjs +9 -17
  30. package/dist/commands/query.mjs +219 -210
  31. package/dist/commands/report.mjs +68 -85
  32. package/dist/commands/rollups.mjs +9 -6
  33. package/dist/commands/sitemaps.mjs +51 -104
  34. package/dist/commands/sites.mjs +40 -24
  35. package/dist/commands/stats.mjs +14 -17
  36. package/dist/commands/store-purge.mjs +9 -5
  37. package/dist/commands/store.mjs +0 -12
  38. package/dist/commands/sync.mjs +774 -296
  39. package/dist/config.mjs +7 -4
  40. package/dist/context.mjs +51 -21
  41. package/dist/coverage.mjs +251 -0
  42. package/dist/dump-bing.mjs +71 -0
  43. package/dist/dump-writers.mjs +246 -0
  44. package/dist/error-handler.mjs +108 -38
  45. package/dist/filters.mjs +125 -0
  46. package/dist/hosted-site.mjs +91 -0
  47. package/dist/inspect-urls.mjs +76 -0
  48. package/dist/inspection-record.mjs +93 -0
  49. package/dist/local-entities.mjs +658 -0
  50. package/dist/mcp/errors.mjs +71 -18
  51. package/dist/mcp/handlers/diagnostics.mjs +15 -12
  52. package/dist/mcp/handlers/reports.mjs +33 -42
  53. package/dist/mcp/server/index.mjs +80 -38
  54. package/dist/mcp/types.mjs +7 -7
  55. package/dist/package.mjs +1 -1
  56. package/dist/quota-ledger.mjs +370 -0
  57. package/dist/render/analysis.mjs +8 -1
  58. package/dist/render/report.mjs +8 -1
  59. package/dist/request-pacer.mjs +29 -0
  60. package/dist/route.mjs +295 -0
  61. package/dist/sitemap.mjs +4 -0
  62. package/dist/sql-views.mjs +117 -0
  63. package/dist/store-sites.mjs +134 -0
  64. package/dist/sync-plan.mjs +129 -0
  65. package/dist/sync-run.mjs +96 -0
  66. package/dist/table-sources.mjs +57 -0
  67. package/dist/token-info.mjs +54 -0
  68. package/dist/utils.mjs +28 -19
  69. package/dist/window.mjs +159 -0
  70. package/package.json +17 -14
  71. package/skills/gscdump/SKILL.md +167 -37
  72. package/dist/commands/export.mjs +0 -76
  73. package/dist/native-duckdb.mjs +0 -46
@@ -1,10 +1,11 @@
1
1
  import { sitesCommandMeta } from "../command-meta.mjs";
2
+ import { formatHostedSync, getCloudSites } from "../auth-state.mjs";
2
3
  import { OUTPUT_ARGS, applyOutputMode, logger } from "../utils.mjs";
3
- import { createCommandContext } from "../context.mjs";
4
- import { gscErrorHandler } from "../error-handler.mjs";
4
+ import { createCommandContext, formatSiteResolution } from "../context.mjs";
5
5
  import process from "node:process";
6
6
  import { defineCommand } from "citty";
7
7
  import { confirm, isCancel } from "@clack/prompts";
8
+ import { resolveSiteInput } from "gscdump";
8
9
  import { addSite, deleteSite, fetchSitesWithSitemaps, getVerificationToken, getVerifiedSite, listVerifiedSites, siteUrlToVerificationSite, unverifySite, verificationMethodsFor, verifySite } from "gscdump/sites";
9
10
  const ALL_METHODS = [
10
11
  "META",
@@ -101,7 +102,7 @@ const addCommand = defineCommand({
101
102
  async run({ args }) {
102
103
  applyOutputMode(args);
103
104
  const ctx = await createCommandContext({ needsAuth: true });
104
- await addSite(ctx.client, args.url).catch(gscErrorHandler);
105
+ await addSite(ctx.client, args.url);
105
106
  if (!args.verify) {
106
107
  if (args.json) {
107
108
  console.log(JSON.stringify({
@@ -118,7 +119,7 @@ const addCommand = defineCommand({
118
119
  return;
119
120
  }
120
121
  const method = validateMethod(args.url, args.method ?? pickDefaultMethod(args.url));
121
- const tokenResult = await getVerificationToken(ctx.client, args.url, method).catch(gscErrorHandler);
122
+ const tokenResult = await getVerificationToken(ctx.client, args.url, method);
122
123
  if (args.json) {
123
124
  console.log(JSON.stringify({
124
125
  siteUrl: args.url,
@@ -141,7 +142,7 @@ const addCommand = defineCommand({
141
142
  logger.info("Skipped verification. Run `gscdump sites verify` once the token is live.");
142
143
  return;
143
144
  }
144
- const resource = await verifySite(ctx.client, args.url, method).catch(gscErrorHandler);
145
+ const resource = await verifySite(ctx.client, args.url, method);
145
146
  logger.success(`Verified: ${args.url}`);
146
147
  if (resource.owners?.length) {
147
148
  console.log();
@@ -182,7 +183,7 @@ const deleteCommand = defineCommand({
182
183
  }
183
184
  }
184
185
  const ctx = await createCommandContext({ needsAuth: true });
185
- await deleteSite(ctx.client, args.url).catch(gscErrorHandler);
186
+ await deleteSite(ctx.client, args.url);
186
187
  if (args.json) {
187
188
  console.log(JSON.stringify({
188
189
  siteUrl: args.url,
@@ -202,7 +203,7 @@ const verifyTokenCommand = defineCommand({
202
203
  url: {
203
204
  type: "positional",
204
205
  required: true,
205
- description: "Property URL"
206
+ description: "Site, for example example.com"
206
207
  },
207
208
  method: {
208
209
  type: "string",
@@ -215,7 +216,7 @@ const verifyTokenCommand = defineCommand({
215
216
  applyOutputMode(args);
216
217
  const method = validateMethod(args.url, args.method ?? pickDefaultMethod(args.url));
217
218
  const ctx = await createCommandContext({ needsAuth: true });
218
- const result = await getVerificationToken(ctx.client, args.url, method).catch(gscErrorHandler);
219
+ const result = await getVerificationToken(ctx.client, args.url, method);
219
220
  if (args.json) {
220
221
  console.log(JSON.stringify({
221
222
  siteUrl: args.url,
@@ -237,7 +238,7 @@ const verifyCommand = defineCommand({
237
238
  url: {
238
239
  type: "positional",
239
240
  required: true,
240
- description: "Property URL"
241
+ description: "Site, for example example.com"
241
242
  },
242
243
  method: {
243
244
  type: "string",
@@ -250,7 +251,7 @@ const verifyCommand = defineCommand({
250
251
  applyOutputMode(args);
251
252
  const method = validateMethod(args.url, args.method ?? pickDefaultMethod(args.url));
252
253
  const ctx = await createCommandContext({ needsAuth: true });
253
- const resource = await verifySite(ctx.client, args.url, method).catch(gscErrorHandler);
254
+ const resource = await verifySite(ctx.client, args.url, method);
254
255
  if (args.json) {
255
256
  console.log(JSON.stringify({
256
257
  siteUrl: args.url,
@@ -283,7 +284,7 @@ const verifyGetCommand = defineCommand({
283
284
  async run({ args }) {
284
285
  applyOutputMode(args);
285
286
  const ctx = await createCommandContext({ needsAuth: true });
286
- const resource = await getVerifiedSite(ctx.client, args.id).catch(gscErrorHandler);
287
+ const resource = await getVerifiedSite(ctx.client, args.id);
287
288
  if (args.json) {
288
289
  console.log(JSON.stringify(resource, null, 2));
289
290
  return;
@@ -332,7 +333,7 @@ const unverifyCommand = defineCommand({
332
333
  }
333
334
  }
334
335
  const ctx = await createCommandContext({ needsAuth: true });
335
- await unverifySite(ctx.client, args.id).catch(gscErrorHandler);
336
+ await unverifySite(ctx.client, args.id);
336
337
  if (args.json) {
337
338
  console.log(JSON.stringify({
338
339
  id: args.id,
@@ -352,7 +353,7 @@ const verifyListCommand = defineCommand({
352
353
  async run({ args }) {
353
354
  applyOutputMode(args);
354
355
  const ctx = await createCommandContext({ needsAuth: true });
355
- const resources = await listVerifiedSites(ctx.client).catch(gscErrorHandler);
356
+ const resources = await listVerifiedSites(ctx.client);
356
357
  if (args.json) {
357
358
  console.log(JSON.stringify(resources, null, 2));
358
359
  return;
@@ -382,21 +383,20 @@ const getCommand = defineCommand({
382
383
  url: {
383
384
  type: "positional",
384
385
  required: true,
385
- description: "Property URL"
386
+ description: "Site, for example example.com"
386
387
  },
387
388
  ...OUTPUT_ARGS
388
389
  },
389
390
  async run({ args }) {
390
391
  applyOutputMode(args);
391
- const site = (await (await createCommandContext({ needsAuth: true })).loadSites()).find((s) => s.siteUrl === args.url);
392
- if (!site) {
393
- if (args.json) {
394
- console.log(JSON.stringify(null));
395
- process.exit(1);
396
- }
397
- logger.error(`Not found: ${args.url}`);
392
+ const all = await (await createCommandContext({ needsAuth: true })).loadSites();
393
+ const resolution = resolveSiteInput(String(args.url), all);
394
+ if (resolution.kind !== "resolved") {
395
+ if (args.json) console.log(JSON.stringify(null));
396
+ logger.error(formatSiteResolution(resolution, "account"));
398
397
  process.exit(1);
399
398
  }
399
+ const site = all.find((s) => s.siteUrl === resolution.siteUrl);
400
400
  if (args.json) {
401
401
  console.log(JSON.stringify(site, null, 2));
402
402
  return;
@@ -426,7 +426,7 @@ async function runListSites(args) {
426
426
  const ctx = await createCommandContext({ needsAuth: true });
427
427
  const ownerOnly = Boolean(args["owner-only"]);
428
428
  if (args["with-sitemaps"]) {
429
- const all = await fetchSitesWithSitemaps(ctx.client).catch(gscErrorHandler);
429
+ const all = await fetchSitesWithSitemaps(ctx.client);
430
430
  const sites = ownerOnly ? all.filter((s) => s.permissionLevel === "siteOwner") : all;
431
431
  if (args.json) {
432
432
  const enriched = sites.map((s) => ({
@@ -458,8 +458,22 @@ async function runListSites(args) {
458
458
  }
459
459
  const all = await ctx.loadSites();
460
460
  const sites = ownerOnly ? all.filter((s) => s.permissionLevel === "siteOwner") : all;
461
+ const hosted = ctx.authentication._tag === "Cloud" ? await getCloudSites(ctx.authentication).then((list) => new Map(list.map((site) => [site.siteUrl, site])), (error) => {
462
+ logger.warn(`Hosted sync status is not available: ${error.message}`);
463
+ }) : void 0;
461
464
  if (args.json) {
462
- console.log(JSON.stringify(sites, null, 2));
465
+ console.log(JSON.stringify(hosted ? sites.map((site) => {
466
+ const entry = hosted.get(site.siteUrl);
467
+ return {
468
+ ...site,
469
+ hostedSync: entry?.registered ? {
470
+ syncStatus: entry.syncStatus ?? null,
471
+ syncProgress: entry.syncProgress ?? null,
472
+ oldestDateSynced: entry.oldestDateSynced ?? null,
473
+ newestDateSynced: entry.newestDateSynced ?? null
474
+ } : null
475
+ };
476
+ }) : sites, null, 2));
463
477
  return;
464
478
  }
465
479
  if (sites.length === 0) {
@@ -470,7 +484,9 @@ async function runListSites(args) {
470
484
  console.log();
471
485
  for (const site of sites) {
472
486
  const perm = site.permissionLevel === "siteOwner" ? "\x1B[32m" : "\x1B[90m";
473
- console.log(` ${site.siteUrl} ${perm}(${site.permissionLevel})\x1B[0m`);
487
+ const entry = hosted?.get(site.siteUrl);
488
+ const sync = entry ? formatHostedSync(entry) : void 0;
489
+ console.log(` ${site.siteUrl} ${perm}(${site.permissionLevel})\x1B[0m${sync ? ` hosted sync ${sync}` : ""}`);
474
490
  }
475
491
  }
476
492
  const listCommand = defineCommand({
@@ -1,14 +1,13 @@
1
1
  import { terminalOutputOptions } from "../render/terminal.mjs";
2
- import { OUTPUT_ARGS, applyOutputMode, displayPath, formatAge, logger } from "../utils.mjs";
2
+ import { OUTPUT_ARGS, applyOutputMode, displayPath, formatAge } from "../utils.mjs";
3
3
  import { allTables, tableDimensions } from "../local-store.mjs";
4
- import { createCommandContext } from "../context.mjs";
4
+ import { readSiteMap, siteUrlForId } from "../store-sites.mjs";
5
+ import { createCommandContext, siteArg } from "../context.mjs";
5
6
  import { renderTable, textLines } from "../render/layout.mjs";
6
7
  import { formatMetric } from "../render/metrics.mjs";
7
8
  import { barColumn } from "../render/charts.mjs";
8
9
  import { columnsFor } from "../render/analysis.mjs";
9
- import process from "node:process";
10
10
  import { defineCommand } from "citty";
11
- import { decodeSiteId, parseGscSiteUrl } from "gscdump";
12
11
  import { filesystemStats } from "@gscdump/engine/filesystem";
13
12
  const statsCommand = defineCommand({
14
13
  meta: {
@@ -19,19 +18,17 @@ const statsCommand = defineCommand({
19
18
  ...OUTPUT_ARGS,
20
19
  site: {
21
20
  type: "string",
22
- description: "Limit to one site URL (sc-domain:example.com, https://example.com/, ...)"
21
+ description: "Limit to one Site, for example example.com"
23
22
  }
24
23
  },
25
24
  async run({ args }) {
26
25
  const { json } = applyOutputMode(args);
27
- const store = (await createCommandContext({ needsStore: true })).store;
28
- const allEntries = await store.engine.listAll({ userId: store.userId });
29
- const knownSites = [...new Set(allEntries.filter((entry) => entry.retiredAt === void 0 && entry.siteId !== void 0).map((entry) => entry.siteId))];
30
- const siteId = args.site ? store.siteIdFor(args.site) : void 0;
31
- if (args.site && !json && !knownSites.includes(siteId)) {
32
- logger.error(`No local data for --site=${args.site}. Known site IDs: ${knownSites.length === 0 ? "(none — run `gscdump sync` first)" : knownSites.join(", ")}`);
33
- process.exit(1);
34
- }
26
+ const ctx = await createCommandContext({ needsStore: true });
27
+ const store = ctx.store;
28
+ const siteUrl = args.site ? await ctx.resolveSite(String(args.site), { scope: "store" }) : void 0;
29
+ const siteId = siteUrl ? store.siteIdFor(siteUrl) : void 0;
30
+ const [allEntries, siteMap] = await Promise.all([store.engine.listAll({ userId: store.userId }), readSiteMap(store.dataDir, store.userId)]);
31
+ const knownSites = [...new Set(allEntries.filter((entry) => entry.retiredAt === void 0 && entry.siteId !== void 0).map((entry) => siteUrlForId(siteMap, entry.siteId)))];
35
32
  const selectedEntries = siteId === void 0 ? allEntries : allEntries.filter((entry) => entry.siteId === siteId);
36
33
  const perTable = allTables().map((table) => {
37
34
  const all = selectedEntries.filter((entry) => entry.table === table);
@@ -48,9 +45,9 @@ const statsCommand = defineCommand({
48
45
  if (json) {
49
46
  const payload = {
50
47
  dataDir: store.dataDir,
51
- siteId: siteId ?? null,
48
+ siteUrl: siteUrl ?? null,
52
49
  knownSites,
53
- nextCommand: `gscdump sync${args.site ? ` --site '${String(args.site).replaceAll("'", "'\\''")}'` : ""} --status --json`,
50
+ nextCommand: `gscdump sync${siteUrl ? ` --site ${siteArg(siteUrl)}` : ""} --status --json`,
54
51
  disk,
55
52
  tables: perTable.map(({ table, live, retired }) => ({
56
53
  table,
@@ -61,7 +58,7 @@ const statsCommand = defineCommand({
61
58
  retiredFiles: retired.length,
62
59
  retiredBytes: sumBytes(retired),
63
60
  watermarks: watermarks.filter((w) => w.table === table).map((w) => ({
64
- siteId: w.siteId ?? null,
61
+ siteUrl: w.siteId ? siteUrlForId(siteMap, w.siteId) : null,
65
62
  searchType: w.searchType ?? "web",
66
63
  newestDateSynced: w.newestDateSynced,
67
64
  oldestDateSynced: w.oldestDateSynced,
@@ -107,7 +104,7 @@ const statsCommand = defineCommand({
107
104
  ];
108
105
  if (rows.length > 1) lines.push(...textLines(`Total ${formatMetric("bytes", rows.reduce((sum, row) => sum + row.liveBytes, 0))} live`, options, "muted"));
109
106
  if (watermarks.length) lines.push("", ...renderTable(sortWatermarks(watermarks).map((w) => ({
110
- scope: w.siteId ? `${w.table}@${parseGscSiteUrl(decodeSiteId(w.siteId)).hostname}` : w.table,
107
+ scope: w.siteId ? `${w.table}@${siteArg(siteUrlForId(siteMap, w.siteId))}` : w.table,
111
108
  dates: `${w.oldestDateSynced} to ${w.newestDateSynced}`,
112
109
  synced: formatAge(w.lastSyncAt)
113
110
  })), [
@@ -1,4 +1,5 @@
1
1
  import { OUTPUT_ARGS, applyOutputMode, logger } from "../utils.mjs";
2
+ import { removeStoreSite } from "../store-sites.mjs";
2
3
  import { createCommandContext } from "../context.mjs";
3
4
  import process from "node:process";
4
5
  import { defineCommand } from "citty";
@@ -12,7 +13,7 @@ const rmSiteCommand = defineCommand({
12
13
  site: {
13
14
  type: "positional",
14
15
  required: true,
15
- description: "Site URL (e.g. sc-domain:example.com)"
16
+ description: "Site, for example example.com"
16
17
  },
17
18
  yes: {
18
19
  type: "boolean",
@@ -24,11 +25,13 @@ const rmSiteCommand = defineCommand({
24
25
  },
25
26
  async run({ args }) {
26
27
  const { json } = applyOutputMode(args);
27
- const store = (await createCommandContext({ needsStore: true })).store;
28
- const siteId = store.siteIdFor(String(args.site));
28
+ const ctx = await createCommandContext({ needsStore: true });
29
+ const store = ctx.store;
30
+ const siteUrl = await ctx.resolveSite(String(args.site), { scope: "store" });
31
+ const siteId = store.siteIdFor(siteUrl);
29
32
  if (!args.yes && !json) {
30
33
  const ok = await confirm({
31
- message: `Delete ALL local data for ${args.site}? This is irreversible.`,
34
+ message: `Delete ALL local data for ${siteUrl}? This is irreversible.`,
32
35
  initialValue: false
33
36
  });
34
37
  if (isCancel(ok) || !ok) {
@@ -40,11 +43,12 @@ const rmSiteCommand = defineCommand({
40
43
  userId: store.userId,
41
44
  siteId
42
45
  });
46
+ await removeStoreSite(store.dataDir, siteId, store.userId);
43
47
  if (json) {
44
48
  console.log(JSON.stringify(result, null, 2));
45
49
  return;
46
50
  }
47
- logger.success(`Removed local data for ${args.site}`);
51
+ logger.success(`Removed local data for ${siteUrl}`);
48
52
  console.log(` Objects deleted: ${result.objectsDeleted}`);
49
53
  console.log(` Manifest entries: ${result.entriesRemoved}`);
50
54
  console.log(` Watermarks: ${result.watermarksRemoved}`);
@@ -1,7 +1,5 @@
1
1
  import { storeCommandMeta } from "../command-meta.mjs";
2
- import { noSubcommandSelected } from "../utils.mjs";
3
2
  import { compactCommand } from "./compact.mjs";
4
- import { exportCommand } from "./export.mjs";
5
3
  import { gcCommand } from "./gc.mjs";
6
4
  import { rollupsCommand } from "./rollups.mjs";
7
5
  import { statsCommand } from "./stats.mjs";
@@ -14,21 +12,11 @@ const storeCommand = defineCommand({
14
12
  "stats": statsCommand,
15
13
  "compact": compactCommand,
16
14
  "gc": gcCommand,
17
- "export": exportCommand,
18
15
  "rollups": rollupsCommand,
19
16
  "rm-site": rmSiteCommand,
20
17
  "reset": resetCommand
21
18
  },
22
19
  async run({ args }) {
23
- if (!noSubcommandSelected("store", [
24
- "stats",
25
- "compact",
26
- "gc",
27
- "export",
28
- "rollups",
29
- "rm-site",
30
- "reset"
31
- ])) return;
32
20
  await statsCommand.run?.({
33
21
  args,
34
22
  cmd: statsCommand,