@gscdump/cli 3.4.4 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +217 -54
- package/bin/gscdump.mjs +6 -1
- package/dist/analysis-local.mjs +1 -1
- package/dist/auth-state.mjs +112 -0
- package/dist/auth.mjs +109 -122
- package/dist/bing-auth.mjs +200 -0
- package/dist/bing-data.mjs +160 -0
- package/dist/bing-hosted.mjs +121 -0
- package/dist/cli.d.mts +4 -4
- package/dist/cli.mjs +20 -21
- package/dist/cloud-google.mjs +92 -0
- package/dist/command-meta.mjs +13 -1
- package/dist/command-registry.mjs +5 -2
- package/dist/commands/analyze.mjs +34 -177
- package/dist/commands/auth.mjs +161 -7
- package/dist/commands/bing.mjs +337 -0
- package/dist/commands/config.mjs +36 -43
- package/dist/commands/doctor.mjs +73 -24
- package/dist/commands/dump.mjs +1 -1
- package/dist/commands/entities.mjs +4 -4
- package/dist/commands/indexing.mjs +11 -14
- package/dist/commands/init.mjs +1 -1
- package/dist/commands/inspect.mjs +3 -3
- package/dist/commands/mcp.mjs +16 -2
- package/dist/commands/papercut.mjs +76 -0
- package/dist/commands/profile-selection.mjs +2 -2
- package/dist/commands/profile.mjs +8 -3
- package/dist/commands/query.mjs +86 -40
- package/dist/commands/report.mjs +5 -3
- package/dist/commands/sitemaps.mjs +24 -18
- package/dist/commands/skill.mjs +52 -0
- package/dist/commands/stats.mjs +58 -32
- package/dist/commands/sync.mjs +58 -25
- package/dist/config.mjs +39 -4
- package/dist/context.mjs +13 -8
- package/dist/env-file.mjs +1 -1
- package/dist/local-store.mjs +2 -2
- package/dist/mcp/errors.mjs +8 -0
- package/dist/mcp/handlers/diagnostics.mjs +31 -0
- package/dist/mcp/handlers/reports.mjs +38 -11
- package/dist/mcp/server/index.mjs +9 -10
- package/dist/mcp/types.mjs +8 -3
- package/dist/package.mjs +1 -1
- package/dist/papercut.mjs +99 -0
- package/dist/render/analysis.mjs +98 -0
- package/dist/render/charts.mjs +170 -0
- package/dist/render/layout.mjs +87 -0
- package/dist/render/metrics.mjs +163 -0
- package/dist/render/query.mjs +30 -0
- package/dist/render/report.mjs +69 -0
- package/dist/render/terminal.mjs +25 -0
- package/dist/runtime.d.mts +5 -5
- package/dist/runtime.mjs +1 -1
- package/dist/skill.mjs +45 -0
- package/dist/utils.mjs +10 -38
- package/package.json +14 -12
- package/skills/gscdump/SKILL.md +287 -0
package/dist/commands/config.mjs
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { configCommandMeta } from "../command-meta.mjs";
|
|
2
|
-
import { getConfigPath, loadConfig, saveConfig } from "../config.mjs";
|
|
2
|
+
import { getConfigPath, loadConfig, resolveDataDir, saveConfig } from "../config.mjs";
|
|
3
3
|
import { OUTPUT_ARGS, applyOutputMode, displayPath, logger, noSubcommandSelected } from "../utils.mjs";
|
|
4
|
+
import { createCommandContext } from "../context.mjs";
|
|
4
5
|
import process from "node:process";
|
|
5
6
|
import { defineCommand } from "citty";
|
|
7
|
+
import fs from "node:fs/promises";
|
|
8
|
+
import path from "node:path";
|
|
6
9
|
const showCommand = defineCommand({
|
|
7
10
|
meta: {
|
|
8
11
|
name: "show",
|
|
@@ -11,7 +14,8 @@ const showCommand = defineCommand({
|
|
|
11
14
|
args: { ...OUTPUT_ARGS },
|
|
12
15
|
async run({ args }) {
|
|
13
16
|
const { json } = applyOutputMode(args);
|
|
14
|
-
const config = await loadConfig();
|
|
17
|
+
const config = { ...await loadConfig() };
|
|
18
|
+
if (config.clientSecret) config.clientSecret = config.clientSecret.length <= 6 ? "***" : `***${config.clientSecret.slice(-6)}`;
|
|
15
19
|
const configPath = getConfigPath();
|
|
16
20
|
if (json) {
|
|
17
21
|
console.log(JSON.stringify({
|
|
@@ -120,82 +124,71 @@ const validateCommand = defineCommand({
|
|
|
120
124
|
args: { ...OUTPUT_ARGS },
|
|
121
125
|
async run({ args }) {
|
|
122
126
|
const { json } = applyOutputMode(args);
|
|
123
|
-
const { resolveDataDir } = await import("../config.mjs");
|
|
124
|
-
const fs = await import("node:fs/promises");
|
|
125
127
|
const config = await loadConfig();
|
|
126
128
|
const issues = [];
|
|
127
129
|
const dataDir = resolveDataDir(config);
|
|
128
130
|
const dataDirDisplay = displayPath(dataDir);
|
|
129
|
-
const stat = await fs.stat(dataDir).catch(() =>
|
|
131
|
+
const stat = await fs.stat(dataDir).catch((error) => {
|
|
132
|
+
if (error.code === "ENOENT") return void 0;
|
|
133
|
+
issues.push({
|
|
134
|
+
key: "dataDir",
|
|
135
|
+
level: "fail",
|
|
136
|
+
message: `Could not inspect ${dataDirDisplay}: ${error.message}`
|
|
137
|
+
});
|
|
138
|
+
return null;
|
|
139
|
+
});
|
|
130
140
|
if (stat && !stat.isDirectory()) issues.push({
|
|
131
141
|
key: "dataDir",
|
|
132
142
|
level: "fail",
|
|
133
143
|
message: `${dataDirDisplay} is not a directory`
|
|
134
144
|
});
|
|
135
145
|
else if (stat) {
|
|
136
|
-
|
|
137
|
-
if (!await fs.writeFile(probe, "").then(() => fs.rm(probe)).then(() => true).catch(() => false)) issues.push({
|
|
146
|
+
if (!await fs.mkdtemp(path.join(dataDir, ".gscdump-config-probe-")).then((probe) => fs.rmdir(probe)).then(() => true).catch(() => false)) issues.push({
|
|
138
147
|
key: "dataDir",
|
|
139
148
|
level: "fail",
|
|
140
149
|
message: `${dataDirDisplay} not writable`
|
|
141
150
|
});
|
|
142
|
-
} else issues.push({
|
|
151
|
+
} else if (stat === void 0) issues.push({
|
|
143
152
|
key: "dataDir",
|
|
144
153
|
level: "warn",
|
|
145
154
|
message: `${dataDirDisplay} does not exist (will be created on first sync)`
|
|
146
155
|
});
|
|
147
156
|
if (config.defaultSite) {
|
|
148
|
-
if (!!config.clientId && !!config.clientSecret) {
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
const sites = await ctx.loadSites().catch(() => null);
|
|
153
|
-
if (sites && !sites.some((s) => s.siteUrl === config.defaultSite || s.siteUrl.includes(String(config.defaultSite)))) issues.push({
|
|
157
|
+
if (!!config.clientId && !!config.clientSecret || !!config.serviceAccountPath) {
|
|
158
|
+
const sites = await createCommandContext({ needsAuth: true }).then((ctx) => ctx.loadSites()).catch((error) => {
|
|
159
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
160
|
+
issues.push({
|
|
154
161
|
key: "defaultSite",
|
|
155
|
-
level: "
|
|
156
|
-
message:
|
|
162
|
+
level: "warn",
|
|
163
|
+
message: `Could not verify Site: ${message}`
|
|
157
164
|
});
|
|
158
|
-
}
|
|
165
|
+
});
|
|
166
|
+
if (sites && !sites.some((s) => s.siteUrl === config.defaultSite || s.siteUrl.includes(String(config.defaultSite)))) issues.push({
|
|
167
|
+
key: "defaultSite",
|
|
168
|
+
level: "fail",
|
|
169
|
+
message: `${config.defaultSite} is not in the verified site list`
|
|
170
|
+
});
|
|
159
171
|
} else issues.push({
|
|
160
172
|
key: "defaultSite",
|
|
161
173
|
level: "warn",
|
|
162
|
-
message: "
|
|
174
|
+
message: "Set, but auth is not configured. Verification skipped."
|
|
163
175
|
});
|
|
164
176
|
}
|
|
165
|
-
if (config.defaultFormat && !["json", "csv"].includes(config.defaultFormat)) issues.push({
|
|
166
|
-
key: "defaultFormat",
|
|
167
|
-
level: "fail",
|
|
168
|
-
message: `unknown format: ${config.defaultFormat}`
|
|
169
|
-
});
|
|
170
|
-
const { SearchTypes } = await import("gscdump/query");
|
|
171
|
-
const allowedSearchTypes = Object.values(SearchTypes);
|
|
172
|
-
if (config.defaultSearchType && !allowedSearchTypes.includes(config.defaultSearchType)) issues.push({
|
|
173
|
-
key: "defaultSearchType",
|
|
174
|
-
level: "fail",
|
|
175
|
-
message: `unknown search type: ${config.defaultSearchType} (allowed: ${allowedSearchTypes.join(", ")})`
|
|
176
|
-
});
|
|
177
|
-
const allowedDataStates = [
|
|
178
|
-
"all",
|
|
179
|
-
"final",
|
|
180
|
-
"hourly_all"
|
|
181
|
-
];
|
|
182
|
-
if (config.defaultDataState && !allowedDataStates.includes(config.defaultDataState)) issues.push({
|
|
183
|
-
key: "defaultDataState",
|
|
184
|
-
level: "fail",
|
|
185
|
-
message: `unknown data state: ${config.defaultDataState} (allowed: ${allowedDataStates.join(", ")})`
|
|
186
|
-
});
|
|
187
177
|
if (config.serviceAccountPath) {
|
|
188
|
-
|
|
178
|
+
const saPath = config.serviceAccountPath;
|
|
179
|
+
if (!await fs.stat(saPath).then(async (stat) => stat.isFile() && await fs.access(saPath, fs.constants.R_OK).then(() => true)).catch(() => false)) issues.push({
|
|
189
180
|
key: "serviceAccountPath",
|
|
190
181
|
level: "fail",
|
|
191
|
-
message: `${displayPath(config.serviceAccountPath)}
|
|
182
|
+
message: `${displayPath(config.serviceAccountPath)} must point to a readable file`
|
|
192
183
|
});
|
|
193
184
|
}
|
|
194
185
|
if (json) {
|
|
186
|
+
const failed = issues.some((i) => i.level === "fail");
|
|
195
187
|
console.log(JSON.stringify({
|
|
196
|
-
ok: !
|
|
188
|
+
ok: !failed,
|
|
197
189
|
issues
|
|
198
190
|
}, null, 2));
|
|
191
|
+
if (failed) process.exit(1);
|
|
199
192
|
return;
|
|
200
193
|
}
|
|
201
194
|
if (issues.length === 0) {
|
package/dist/commands/doctor.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getCloudAccount, resolveAuthentication } from "../auth-state.mjs";
|
|
1
2
|
import { doctorCommandMeta } from "../command-meta.mjs";
|
|
2
3
|
import { loadConfig } from "../config.mjs";
|
|
3
4
|
import { resolveCliEnvironment } from "../environment.mjs";
|
|
@@ -9,8 +10,8 @@ import { createCommandContext } from "../context.mjs";
|
|
|
9
10
|
import { missingRequiredScopes } from "../auth-scopes.mjs";
|
|
10
11
|
import process from "node:process";
|
|
11
12
|
import { defineCommand } from "citty";
|
|
12
|
-
import path from "node:path";
|
|
13
13
|
import fs from "node:fs/promises";
|
|
14
|
+
import path from "node:path";
|
|
14
15
|
import { googleSearchConsole } from "gscdump/client";
|
|
15
16
|
import { ofetch } from "ofetch";
|
|
16
17
|
const FETCH_TIMEOUT_MS = 5e3;
|
|
@@ -299,6 +300,9 @@ async function checkGscSites() {
|
|
|
299
300
|
status: "fail",
|
|
300
301
|
detail: `sites() failed: ${sites.message}`
|
|
301
302
|
}];
|
|
303
|
+
return describeGscSites(sites, config.defaultSite);
|
|
304
|
+
}
|
|
305
|
+
function describeGscSites(sites, defaultSite) {
|
|
302
306
|
const checks = [];
|
|
303
307
|
const verified = sites.filter((s) => s.permissionLevel !== "siteUnverifiedUser").length;
|
|
304
308
|
checks.push({
|
|
@@ -306,51 +310,96 @@ async function checkGscSites() {
|
|
|
306
310
|
status: "pass",
|
|
307
311
|
detail: `${sites.length} site(s) accessible (${verified} verified)`
|
|
308
312
|
});
|
|
309
|
-
if (
|
|
310
|
-
const match = sites.find((s) => s.siteUrl ===
|
|
313
|
+
if (defaultSite) {
|
|
314
|
+
const match = sites.find((s) => s.siteUrl === defaultSite || (s.siteUrl ?? "").includes(defaultSite));
|
|
311
315
|
checks.push(match ? {
|
|
312
316
|
name: "config.defaultSite",
|
|
313
317
|
status: "pass",
|
|
314
|
-
detail: `${
|
|
318
|
+
detail: `${defaultSite} ✓`
|
|
315
319
|
} : {
|
|
316
320
|
name: "config.defaultSite",
|
|
317
321
|
status: "fail",
|
|
318
|
-
detail: `${
|
|
322
|
+
detail: `${defaultSite} not in verified site list`
|
|
319
323
|
});
|
|
320
324
|
}
|
|
321
325
|
return checks;
|
|
322
326
|
}
|
|
327
|
+
async function checkCloudConnection(authentication) {
|
|
328
|
+
const account = await getCloudAccount(authentication).then((value) => ({
|
|
329
|
+
_tag: "Ok",
|
|
330
|
+
value
|
|
331
|
+
}), (error) => ({
|
|
332
|
+
_tag: "Err",
|
|
333
|
+
detail: error instanceof Error ? error.message : "Hosted authentication failed."
|
|
334
|
+
}));
|
|
335
|
+
if (account._tag === "Err") return [{
|
|
336
|
+
name: "auth",
|
|
337
|
+
status: "fail",
|
|
338
|
+
detail: account.detail
|
|
339
|
+
}, {
|
|
340
|
+
name: "gsc.sites",
|
|
341
|
+
status: "warn",
|
|
342
|
+
detail: "skipped (auth failed)"
|
|
343
|
+
}];
|
|
344
|
+
const sitesChecks = await createCommandContext({ needsAuth: true }).then(async (context) => describeGscSites(await context.loadSites(), context.config.defaultSite)).catch((error) => [{
|
|
345
|
+
name: "gsc.sites",
|
|
346
|
+
status: "fail",
|
|
347
|
+
detail: error instanceof Error ? error.message : "Hosted Sites request failed."
|
|
348
|
+
}]);
|
|
349
|
+
return [
|
|
350
|
+
{
|
|
351
|
+
name: "auth",
|
|
352
|
+
status: "pass",
|
|
353
|
+
detail: `cloud via ${authentication.apiRoot}`
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
name: "auth.account",
|
|
357
|
+
status: "pass",
|
|
358
|
+
detail: account.value.user.email
|
|
359
|
+
},
|
|
360
|
+
...sitesChecks
|
|
361
|
+
];
|
|
362
|
+
}
|
|
363
|
+
async function checkLocalConnection(envKeys) {
|
|
364
|
+
const [authResult, timeChecks, gscApi, indexingApi, siteVerificationApi] = await Promise.all([
|
|
365
|
+
checkAuth(envKeys),
|
|
366
|
+
checkTimeSkew(),
|
|
367
|
+
checkApiReachable("gsc.api", "https://searchconsole.googleapis.com/$discovery/rest?version=v1"),
|
|
368
|
+
checkApiReachable("indexing.api", "https://indexing.googleapis.com/$discovery/rest?version=v3"),
|
|
369
|
+
checkApiReachable("siteverification.api", "https://www.googleapis.com/discovery/v1/apis/siteVerification/v1/rest")
|
|
370
|
+
]);
|
|
371
|
+
const sitesChecks = authResult.liveToken ? await checkGscSites() : [{
|
|
372
|
+
name: "gsc.sites",
|
|
373
|
+
status: "warn",
|
|
374
|
+
detail: "skipped (auth failed)"
|
|
375
|
+
}];
|
|
376
|
+
return [
|
|
377
|
+
...authResult.checks,
|
|
378
|
+
...timeChecks,
|
|
379
|
+
...gscApi,
|
|
380
|
+
...indexingApi,
|
|
381
|
+
...siteVerificationApi,
|
|
382
|
+
...sitesChecks
|
|
383
|
+
];
|
|
384
|
+
}
|
|
323
385
|
const doctorCommand = defineCommand({
|
|
324
386
|
meta: doctorCommandMeta,
|
|
325
387
|
args: { ...OUTPUT_ARGS },
|
|
326
388
|
async run({ args }) {
|
|
327
389
|
const { json } = applyOutputMode(args);
|
|
328
390
|
const { dataDir } = await createCommandContext();
|
|
391
|
+
const authentication = await resolveAuthentication();
|
|
329
392
|
const envResult = await checkEnv();
|
|
330
|
-
const [
|
|
331
|
-
|
|
332
|
-
checkTimeSkew(),
|
|
393
|
+
const [connectionChecks, dataDirChecks, watermarkChecks] = await Promise.all([
|
|
394
|
+
authentication._tag === "Cloud" ? checkCloudConnection(authentication) : checkLocalConnection(envResult.envKeys),
|
|
333
395
|
checkDataDir(dataDir),
|
|
334
|
-
checkStoreWatermarks(dataDir)
|
|
335
|
-
checkApiReachable("gsc.api", "https://searchconsole.googleapis.com/$discovery/rest?version=v1"),
|
|
336
|
-
checkApiReachable("indexing.api", "https://indexing.googleapis.com/$discovery/rest?version=v3"),
|
|
337
|
-
checkApiReachable("siteverification.api", "https://www.googleapis.com/discovery/v1/apis/siteVerification/v1/rest")
|
|
396
|
+
checkStoreWatermarks(dataDir)
|
|
338
397
|
]);
|
|
339
|
-
const sitesChecks = authResult.liveToken ? await checkGscSites() : [{
|
|
340
|
-
name: "gsc.sites",
|
|
341
|
-
status: "warn",
|
|
342
|
-
detail: "skipped (auth failed)"
|
|
343
|
-
}];
|
|
344
398
|
const all = [
|
|
345
399
|
...envResult.checks,
|
|
346
|
-
...
|
|
347
|
-
...timeChecks,
|
|
400
|
+
...connectionChecks,
|
|
348
401
|
...dataDirChecks,
|
|
349
|
-
...watermarkChecks
|
|
350
|
-
...gscApi,
|
|
351
|
-
...indexingApi,
|
|
352
|
-
...siteVerificationApi,
|
|
353
|
-
...sitesChecks
|
|
402
|
+
...watermarkChecks
|
|
354
403
|
];
|
|
355
404
|
if (json) {
|
|
356
405
|
console.log(JSON.stringify({
|
package/dist/commands/dump.mjs
CHANGED
|
@@ -5,8 +5,8 @@ import { createCommandContext } from "../context.mjs";
|
|
|
5
5
|
import { readParquetRows } from "../native-duckdb.mjs";
|
|
6
6
|
import process from "node:process";
|
|
7
7
|
import { defineCommand } from "citty";
|
|
8
|
-
import path from "node:path";
|
|
9
8
|
import fs from "node:fs/promises";
|
|
9
|
+
import path from "node:path";
|
|
10
10
|
const DEFAULT_OUT = "./gscdump-export";
|
|
11
11
|
const FORMATS = [
|
|
12
12
|
"parquet",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { entitiesCommandMeta } from "../command-meta.mjs";
|
|
2
|
-
import { OUTPUT_ARGS, applyOutputMode, logger, progressBar, runWithConcurrency } from "../utils.mjs";
|
|
2
|
+
import { OUTPUT_ARGS, applyOutputMode, logger, parseIntegerOption, progressBar, runWithConcurrency } from "../utils.mjs";
|
|
3
3
|
import { createCommandContext } from "../context.mjs";
|
|
4
4
|
import process from "node:process";
|
|
5
5
|
import { defineCommand } from "citty";
|
|
@@ -58,6 +58,8 @@ const inspectSubCommand = defineCommand({
|
|
|
58
58
|
},
|
|
59
59
|
async run({ args }) {
|
|
60
60
|
const { json, quiet } = applyOutputMode(args);
|
|
61
|
+
const limit = parseIntegerOption(args.limit, "--limit") ?? INSPECTION_QPD_PER_PROPERTY;
|
|
62
|
+
const concurrency = parseIntegerOption(args.concurrency, "--concurrency") ?? 4;
|
|
61
63
|
const ctx = await createCommandContext({
|
|
62
64
|
needsAuth: true,
|
|
63
65
|
needsStore: true
|
|
@@ -65,8 +67,6 @@ const inspectSubCommand = defineCommand({
|
|
|
65
67
|
const client = ctx.client;
|
|
66
68
|
const store = ctx.store;
|
|
67
69
|
const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
|
|
68
|
-
const limit = args.limit ? Number.parseInt(String(args.limit), 10) : INSPECTION_QPD_PER_PROPERTY;
|
|
69
|
-
const concurrency = Math.max(1, Number.parseInt(String(args.concurrency), 10) || 4);
|
|
70
70
|
const urls = (await readUrlList({ file: args.file ? String(args.file) : void 0 })).slice(0, limit);
|
|
71
71
|
if (urls.length === 0) {
|
|
72
72
|
logger.warn("No URLs to inspect.");
|
|
@@ -207,6 +207,7 @@ const indexingSnapshotSubCommand = defineCommand({
|
|
|
207
207
|
},
|
|
208
208
|
async run({ args }) {
|
|
209
209
|
const { quiet } = applyOutputMode(args);
|
|
210
|
+
const concurrency = parseIntegerOption(args.concurrency, "--concurrency") ?? 4;
|
|
210
211
|
const ctx = await createCommandContext({
|
|
211
212
|
needsAuth: true,
|
|
212
213
|
needsStore: true
|
|
@@ -214,7 +215,6 @@ const indexingSnapshotSubCommand = defineCommand({
|
|
|
214
215
|
const client = ctx.client;
|
|
215
216
|
const store = ctx.store;
|
|
216
217
|
const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
|
|
217
|
-
const concurrency = Math.max(1, Number.parseInt(String(args.concurrency), 10) || 4);
|
|
218
218
|
const urls = await readUrlList({ file: args.file ? String(args.file) : void 0 });
|
|
219
219
|
if (urls.length === 0) {
|
|
220
220
|
logger.warn("No URLs to fetch metadata for.");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { indexingCommandMeta } from "../command-meta.mjs";
|
|
2
|
-
import { OUTPUT_ARGS, applyOutputMode, logger, readUrlList } from "../utils.mjs";
|
|
2
|
+
import { OUTPUT_ARGS, applyOutputMode, logger, parseIntegerOption, readUrlList } from "../utils.mjs";
|
|
3
3
|
import { createCommandContext } from "../context.mjs";
|
|
4
4
|
import { gscErrorHandler } from "../error-handler.mjs";
|
|
5
5
|
import { loadSitemapUrls } from "../sitemap.mjs";
|
|
@@ -10,11 +10,6 @@ const RETRIES_ARG = { retries: {
|
|
|
10
10
|
type: "string",
|
|
11
11
|
description: "Override per-call retry count (default: 3)"
|
|
12
12
|
} };
|
|
13
|
-
function parseRetries(v) {
|
|
14
|
-
if (v == null || v === "") return void 0;
|
|
15
|
-
const n = Number.parseInt(String(v), 10);
|
|
16
|
-
return Number.isFinite(n) && n >= 0 ? n : void 0;
|
|
17
|
-
}
|
|
18
13
|
async function resolveUrlSource(args) {
|
|
19
14
|
const fromSitemap = args["from-sitemap"];
|
|
20
15
|
if (fromSitemap) {
|
|
@@ -46,7 +41,7 @@ const submitCommand = defineCommand({
|
|
|
46
41
|
applyOutputMode(args);
|
|
47
42
|
const ctx = await createCommandContext({
|
|
48
43
|
needsAuth: true,
|
|
49
|
-
fetchOptions: { retry:
|
|
44
|
+
fetchOptions: { retry: parseIntegerOption(args.retries, "--retries", 0) }
|
|
50
45
|
});
|
|
51
46
|
const result = await requestIndexing(ctx.client, args.url, { type: "URL_UPDATED" }).catch(gscErrorHandler);
|
|
52
47
|
if (args.json) {
|
|
@@ -75,7 +70,7 @@ const removeCommand = defineCommand({
|
|
|
75
70
|
applyOutputMode(args);
|
|
76
71
|
const ctx = await createCommandContext({
|
|
77
72
|
needsAuth: true,
|
|
78
|
-
fetchOptions: { retry:
|
|
73
|
+
fetchOptions: { retry: parseIntegerOption(args.retries, "--retries", 0) }
|
|
79
74
|
});
|
|
80
75
|
const result = await requestIndexing(ctx.client, args.url, { type: "URL_DELETED" }).catch(gscErrorHandler);
|
|
81
76
|
if (args.json) {
|
|
@@ -210,6 +205,9 @@ const batchCommand = defineCommand({
|
|
|
210
205
|
},
|
|
211
206
|
async run({ args }) {
|
|
212
207
|
applyOutputMode(args);
|
|
208
|
+
const retry = parseIntegerOption(args.retries, "--retries", 0);
|
|
209
|
+
const delayMs = parseIntegerOption(args["delay-ms"], "--delay-ms", 0) ?? 100;
|
|
210
|
+
const concurrency = parseIntegerOption(args.concurrency, "--concurrency") ?? 1;
|
|
213
211
|
const urls = await resolveUrlSource(args);
|
|
214
212
|
if (urls.length === 0) {
|
|
215
213
|
logger.error("No URLs provided. Pass URLs as args, --file, --from-sitemap, or stdin.");
|
|
@@ -228,10 +226,8 @@ const batchCommand = defineCommand({
|
|
|
228
226
|
if (urls.length > INDEXING_DAILY_QUOTA && !args.json && !args.quiet) logger.warn(`Proceeding with ${urls.length} URLs (over the ${INDEXING_DAILY_QUOTA}/day quota). Excess will fail.`);
|
|
229
227
|
const ctx = await createCommandContext({
|
|
230
228
|
needsAuth: true,
|
|
231
|
-
fetchOptions: { retry
|
|
229
|
+
fetchOptions: { retry }
|
|
232
230
|
});
|
|
233
|
-
const delayMs = Number.parseInt(String(args["delay-ms"]), 10);
|
|
234
|
-
const concurrency = Math.max(1, Number.parseInt(String(args.concurrency), 10) || 1);
|
|
235
231
|
if (!args.json && !args.quiet) logger.info(`Submitting ${urls.length} URLs (${type}) ...`);
|
|
236
232
|
const results = await batchRequestIndexing(ctx.client, urls, {
|
|
237
233
|
type,
|
|
@@ -285,6 +281,9 @@ const batchStatusCommand = defineCommand({
|
|
|
285
281
|
},
|
|
286
282
|
async run({ args }) {
|
|
287
283
|
applyOutputMode(args);
|
|
284
|
+
const retry = parseIntegerOption(args.retries, "--retries", 0);
|
|
285
|
+
const delayMs = parseIntegerOption(args["delay-ms"], "--delay-ms", 0) ?? 100;
|
|
286
|
+
const concurrency = parseIntegerOption(args.concurrency, "--concurrency") ?? 1;
|
|
288
287
|
const urls = await resolveUrlSource(args);
|
|
289
288
|
if (urls.length === 0) {
|
|
290
289
|
logger.error("No URLs provided. Pass URLs as args, --file, --from-sitemap, or stdin.");
|
|
@@ -292,10 +291,8 @@ const batchStatusCommand = defineCommand({
|
|
|
292
291
|
}
|
|
293
292
|
const ctx = await createCommandContext({
|
|
294
293
|
needsAuth: true,
|
|
295
|
-
fetchOptions: { retry
|
|
294
|
+
fetchOptions: { retry }
|
|
296
295
|
});
|
|
297
|
-
const delayMs = Number.parseInt(String(args["delay-ms"]), 10);
|
|
298
|
-
const concurrency = Math.max(1, Number.parseInt(String(args.concurrency), 10) || 1);
|
|
299
296
|
if (!args.json && !args.quiet) logger.info(`Fetching status for ${urls.length} URLs ...`);
|
|
300
297
|
const results = await runSequentialBatch(urls, (url) => getIndexingMetadata(ctx.client, url), {
|
|
301
298
|
delayMs,
|
package/dist/commands/init.mjs
CHANGED
|
@@ -5,8 +5,8 @@ import { OUTPUT_ARGS, applyOutputMode, displayPath, logger } from "../utils.mjs"
|
|
|
5
5
|
import { authenticate, getAuthCredentials, loadTokens, resolveBYOK, saveTokens } from "../auth.mjs";
|
|
6
6
|
import process from "node:process";
|
|
7
7
|
import { defineCommand } from "citty";
|
|
8
|
-
import path from "node:path";
|
|
9
8
|
import fs from "node:fs/promises";
|
|
9
|
+
import path from "node:path";
|
|
10
10
|
import { confirm, isCancel, text } from "@clack/prompts";
|
|
11
11
|
import { googleSearchConsole } from "gscdump/client";
|
|
12
12
|
const ENV_LINE_RE = /^([^=]+)=(.*)$/;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { inspectCommandMeta } from "../command-meta.mjs";
|
|
2
|
-
import { OUTPUT_ARGS, applyOutputMode, logger, readUrlList } from "../utils.mjs";
|
|
2
|
+
import { OUTPUT_ARGS, applyOutputMode, logger, parseIntegerOption, readUrlList } from "../utils.mjs";
|
|
3
3
|
import { createCommandContext } from "../context.mjs";
|
|
4
4
|
import { gscErrorHandler } from "../error-handler.mjs";
|
|
5
5
|
import { loadSitemapUrls } from "../sitemap.mjs";
|
|
@@ -116,6 +116,8 @@ const batchCommand = defineCommand({
|
|
|
116
116
|
},
|
|
117
117
|
async run({ args }) {
|
|
118
118
|
const { json, quiet } = applyOutputMode(args);
|
|
119
|
+
const delayMs = parseIntegerOption(args["delay-ms"], "--delay-ms", 0) ?? 200;
|
|
120
|
+
const concurrency = parseIntegerOption(args.concurrency, "--concurrency") ?? 1;
|
|
119
121
|
let urls;
|
|
120
122
|
if (args["from-sitemap"]) {
|
|
121
123
|
const result = await loadSitemapUrls(String(args["from-sitemap"]));
|
|
@@ -132,8 +134,6 @@ const batchCommand = defineCommand({
|
|
|
132
134
|
}
|
|
133
135
|
const ctx = await createCommandContext({ needsAuth: true });
|
|
134
136
|
const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
|
|
135
|
-
const delayMs = Number.parseInt(String(args["delay-ms"]), 10);
|
|
136
|
-
const concurrency = Math.max(1, Number.parseInt(String(args.concurrency), 10) || 1);
|
|
137
137
|
if (!quiet) logger.info(`Inspecting ${urls.length} URLs ...`);
|
|
138
138
|
const results = await batchInspectUrls(ctx.client, siteUrl, urls, {
|
|
139
139
|
delayMs,
|
package/dist/commands/mcp.mjs
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
|
+
import { getCloudAccount, resolveAuthentication } from "../auth-state.mjs";
|
|
1
2
|
import { mcpCommandMeta } from "../command-meta.mjs";
|
|
2
3
|
import { loadConfig } from "../config.mjs";
|
|
3
4
|
import { VERSION } from "../utils.mjs";
|
|
4
|
-
import { loadTokens,
|
|
5
|
+
import { loadTokens, resolveBYOK, resolveServiceAccount } from "../auth.mjs";
|
|
6
|
+
import { createCommandContext } from "../context.mjs";
|
|
5
7
|
import { createGscMcpServer } from "../mcp/server/index.mjs";
|
|
6
8
|
import process from "node:process";
|
|
7
9
|
import { defineCommand } from "citty";
|
|
8
10
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9
11
|
async function checkAuth() {
|
|
12
|
+
const authentication = await resolveAuthentication();
|
|
13
|
+
if (authentication._tag === "Cloud") {
|
|
14
|
+
await getCloudAccount(authentication);
|
|
15
|
+
return { ok: true };
|
|
16
|
+
}
|
|
10
17
|
if (await resolveServiceAccount().then(Boolean).catch(() => false)) return { ok: true };
|
|
11
18
|
if (resolveBYOK()) return { ok: true };
|
|
12
19
|
const config = await loadConfig();
|
|
@@ -46,7 +53,14 @@ const mcpCommand = defineCommand({
|
|
|
46
53
|
const server = createGscMcpServer({
|
|
47
54
|
name: "gscdump",
|
|
48
55
|
version: VERSION,
|
|
49
|
-
|
|
56
|
+
getContext: async () => {
|
|
57
|
+
const ctx = await createCommandContext({ needsAuth: true });
|
|
58
|
+
return {
|
|
59
|
+
authentication: ctx.authentication,
|
|
60
|
+
auth: ctx.auth,
|
|
61
|
+
client: ctx.client
|
|
62
|
+
};
|
|
63
|
+
}
|
|
50
64
|
});
|
|
51
65
|
const transport = new StdioServerTransport();
|
|
52
66
|
await server.connect(transport);
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { useCliRuntime } from "../runtime.mjs";
|
|
2
|
+
import { papercutCommandMeta } from "../command-meta.mjs";
|
|
3
|
+
import { OUTPUT_ARGS, applyOutputMode, logger } from "../utils.mjs";
|
|
4
|
+
import { buildPapercutBody, resolvePapercutUrl, submitPapercut } from "../papercut.mjs";
|
|
5
|
+
import process from "node:process";
|
|
6
|
+
import { defineCommand } from "citty";
|
|
7
|
+
import { confirm, isCancel } from "@clack/prompts";
|
|
8
|
+
const papercutCommand = defineCommand({
|
|
9
|
+
meta: papercutCommandMeta,
|
|
10
|
+
args: {
|
|
11
|
+
command: {
|
|
12
|
+
type: "string",
|
|
13
|
+
description: "The gscdump command that misbehaved, for example \"report triage\""
|
|
14
|
+
},
|
|
15
|
+
comment: {
|
|
16
|
+
type: "string",
|
|
17
|
+
description: "What you ran, what you expected, what happened, any workaround (sanitized)"
|
|
18
|
+
},
|
|
19
|
+
agent: {
|
|
20
|
+
type: "string",
|
|
21
|
+
description: "Reporter name, for example \"Claude Code\" or a person's handle"
|
|
22
|
+
},
|
|
23
|
+
intent: {
|
|
24
|
+
type: "string",
|
|
25
|
+
description: "bug (default) or improvement",
|
|
26
|
+
default: "bug"
|
|
27
|
+
},
|
|
28
|
+
yes: {
|
|
29
|
+
type: "boolean",
|
|
30
|
+
alias: "y",
|
|
31
|
+
default: false,
|
|
32
|
+
description: "Submit without confirmation"
|
|
33
|
+
},
|
|
34
|
+
...OUTPUT_ARGS
|
|
35
|
+
},
|
|
36
|
+
async run({ args }) {
|
|
37
|
+
const { json } = applyOutputMode(args);
|
|
38
|
+
const built = buildPapercutBody({
|
|
39
|
+
command: args.command,
|
|
40
|
+
comment: args.comment,
|
|
41
|
+
agent: args.agent,
|
|
42
|
+
intent: args.intent
|
|
43
|
+
});
|
|
44
|
+
if (built._tag === "Err") {
|
|
45
|
+
logger.error(`Invalid papercut: ${built.message}`);
|
|
46
|
+
logger.info("Pass --command, --comment, and --agent. Read `gscdump papercut --help` for limits.");
|
|
47
|
+
process.exit(2);
|
|
48
|
+
}
|
|
49
|
+
if (!args.yes) {
|
|
50
|
+
if (!process.stdin.isTTY || json) {
|
|
51
|
+
logger.error("Pass --yes to submit without a prompt.");
|
|
52
|
+
process.exit(2);
|
|
53
|
+
}
|
|
54
|
+
const ok = await confirm({ message: `Send this papercut about "${built.body.command}" to gscdump.com?` });
|
|
55
|
+
if (isCancel(ok) || !ok) {
|
|
56
|
+
logger.info("Papercut cancelled.");
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const url = resolvePapercutUrl(useCliRuntime().environment);
|
|
61
|
+
const result = await submitPapercut(built.body, {
|
|
62
|
+
fetch: globalThis.fetch,
|
|
63
|
+
url
|
|
64
|
+
});
|
|
65
|
+
if (result._tag === "Err") {
|
|
66
|
+
logger.error(result.message);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
if (json) {
|
|
70
|
+
console.log(JSON.stringify(result.receipt));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
logger.success(`Papercut saved: ${result.receipt.id} (${result.receipt.status})`);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
export { papercutCommand };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { useCliRuntime } from "../runtime.mjs";
|
|
2
2
|
import { setConfigDir } from "../config.mjs";
|
|
3
3
|
import { resolveCliEnvironment } from "../environment.mjs";
|
|
4
|
-
import fs from "node:fs";
|
|
5
|
-
import os from "node:os";
|
|
6
4
|
import path from "node:path";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import fs from "node:fs";
|
|
7
7
|
const ROOT_DIR = path.join(os.homedir(), ".config", "gscdump");
|
|
8
8
|
const PROFILES_DIR = path.join(ROOT_DIR, "profiles");
|
|
9
9
|
const ACTIVE_MARKER = path.join(ROOT_DIR, "active-profile");
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { profileCommandMeta } from "../command-meta.mjs";
|
|
2
1
|
import { useCliRuntime } from "../runtime.mjs";
|
|
2
|
+
import { profileCommandMeta } from "../command-meta.mjs";
|
|
3
3
|
import { getConfigDir, setConfigDir } from "../config.mjs";
|
|
4
4
|
import { resolveCliEnvironment } from "../environment.mjs";
|
|
5
5
|
import { ACTIVE_MARKER, PROFILES_DIR, ROOT_DIR, getProfileDir, readActiveMarkerSync } from "./profile-selection.mjs";
|
|
6
6
|
import { OUTPUT_ARGS, applyOutputMode, displayPath, logger, noSubcommandSelected } from "../utils.mjs";
|
|
7
7
|
import process from "node:process";
|
|
8
8
|
import { defineCommand } from "citty";
|
|
9
|
-
import path from "node:path";
|
|
10
9
|
import fs from "node:fs/promises";
|
|
10
|
+
import path from "node:path";
|
|
11
11
|
import { confirm, isCancel } from "@clack/prompts";
|
|
12
12
|
function resolveActiveProfile() {
|
|
13
13
|
return useCliRuntime().activeProfileOverride ?? resolveCliEnvironment().profile ?? readActiveMarkerSync();
|
|
@@ -48,7 +48,12 @@ async function adoptCurrentConfigAsProfile(name) {
|
|
|
48
48
|
recursive: true,
|
|
49
49
|
mode: 448
|
|
50
50
|
});
|
|
51
|
-
for (const f of [
|
|
51
|
+
for (const f of [
|
|
52
|
+
"tokens.json",
|
|
53
|
+
"config.json",
|
|
54
|
+
"authentication.json",
|
|
55
|
+
"bing-tokens.json"
|
|
56
|
+
]) {
|
|
52
57
|
const src = path.join(currentDir, f);
|
|
53
58
|
const dst = path.join(targetDir, f);
|
|
54
59
|
if (await fs.stat(src).then(() => true).catch(() => false)) await fs.rename(src, dst);
|