@gscdump/cli 4.8.0 → 5.1.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/dist/analysis-local.mjs +2 -2
- package/dist/auth-state.mjs +41 -6
- package/dist/auth.mjs +1 -7
- package/dist/bing-hosted.mjs +26 -5
- package/dist/cli.d.mts +0 -1
- package/dist/cli.mjs +4 -4
- package/dist/commands/analyze.mjs +2 -2
- package/dist/commands/auth.mjs +1 -1
- package/dist/commands/bing.mjs +11 -6
- package/dist/commands/doctor.mjs +12 -69
- package/dist/commands/indexing-urls.mjs +2 -2
- package/dist/commands/indexing-watch.mjs +10 -7
- package/dist/commands/init.mjs +4 -89
- package/dist/commands/mcp.mjs +1 -1
- package/dist/commands/papercut.mjs +1 -1
- package/dist/commands/profile.mjs +1 -1
- package/dist/commands/query.mjs +16 -9
- package/dist/commands/report.mjs +2 -2
- package/dist/commands/sitemaps.mjs +5 -5
- package/dist/commands/sites.mjs +10 -10
- package/dist/config.mjs +1 -1
- package/dist/context.mjs +3 -3
- package/dist/coverage.mjs +1 -3
- package/dist/environment.mjs +1 -5
- package/dist/error-handler.mjs +37 -11
- package/dist/hosted-query.mjs +23 -1
- package/dist/hosted-site.mjs +37 -17
- package/dist/mcp/errors.mjs +1 -1
- package/dist/mcp/types.mjs +1 -0
- package/dist/package.mjs +1 -1
- package/dist/render/query.mjs +1 -1
- package/dist/route.mjs +5 -9
- package/dist/runtime.d.mts +2 -0
- package/dist/runtime.mjs +1 -0
- package/dist/stop.mjs +10 -0
- package/dist/sync-plan.mjs +1 -1
- package/dist/utils.mjs +9 -5
- package/package.json +7 -7
- package/skills/gscdump/SKILL.md +36 -4
- package/dist/env-file.mjs +0 -53
package/dist/analysis-local.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { LocalStoreUnsupportedError } from "./error-handler.mjs";
|
|
2
1
|
import { useCliRuntime } from "./runtime.mjs";
|
|
2
|
+
import { LocalStoreUnsupportedError } from "./error-handler.mjs";
|
|
3
3
|
import { logger } from "./utils.mjs";
|
|
4
4
|
import { inferTable } from "./local-store.mjs";
|
|
5
5
|
import { createCommandContext } from "./context.mjs";
|
|
@@ -174,7 +174,7 @@ async function resolveAnalysisSource(args) {
|
|
|
174
174
|
auth
|
|
175
175
|
});
|
|
176
176
|
const route = decideRoute(req, state);
|
|
177
|
-
if (route.kind === "syncing" || route.kind === "prompt") stopAtRoute(route, req, state.auth
|
|
177
|
+
if (route.kind === "syncing" || route.kind === "prompt") stopAtRoute(route, req, state.auth);
|
|
178
178
|
if (route.kind === "local") {
|
|
179
179
|
const source = createEngineQuerySource({
|
|
180
180
|
engine: store.engine,
|
package/dist/auth-state.mjs
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
|
-
import { HOSTED_KEY_REJECTED } from "./error-handler.mjs";
|
|
2
1
|
import { useCliRuntime } from "./runtime.mjs";
|
|
2
|
+
import { stopError } from "./stop.mjs";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import fs from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
const HOSTED_SESSION_REJECTED = "gscdump.com rejected the CLI session. Run `gscdump auth login --mode hosted` again.";
|
|
8
|
+
const HOSTED_KEY_REJECTED = "gscdump.com rejected the API key. Run `gscdump auth login --mode hosted --api-key KEY` with a valid key.";
|
|
8
9
|
const LOCAL_MODE_REQUIRED = [
|
|
9
10
|
"This command calls Google, so it needs Local mode.",
|
|
10
11
|
"Hosted mode reads your gscdump.com record and never calls Google.",
|
|
11
12
|
"If you want Local mode, run `gscdump auth login --mode local`."
|
|
12
13
|
].join("\n");
|
|
14
|
+
function localModeRequired(detail) {
|
|
15
|
+
return stopError({
|
|
16
|
+
code: "LOCAL_MODE_REQUIRED",
|
|
17
|
+
message: detail ? `${detail}\n${LOCAL_MODE_REQUIRED}` : LOCAL_MODE_REQUIRED,
|
|
18
|
+
nextCommand: "gscdump auth login --mode local"
|
|
19
|
+
});
|
|
20
|
+
}
|
|
13
21
|
const HOSTED_NOT_SET_UP = "Hosted credentials are missing. Run `gscdump auth login --mode hosted`, or set GSCDUMP_API_KEY.";
|
|
14
22
|
const SAVED_STATE_INVALID = "The saved access mode is invalid. Run `gscdump auth login --mode local` or `gscdump auth login --mode hosted`.";
|
|
15
23
|
const apiRootSchema = z.url().transform((value) => value.replace(/\/+$/, "")).refine((value) => {
|
|
@@ -129,7 +137,11 @@ async function resolveAuthentication() {
|
|
|
129
137
|
if (env.GSCDUMP_API_ROOT && env.GSCDUMP_API_ROOT.replace(/\/+$/, "") !== state.apiRoot) throw new Error("The API root changed. Run `gscdump auth login --mode hosted` for the new API root.");
|
|
130
138
|
return state;
|
|
131
139
|
}
|
|
132
|
-
throw
|
|
140
|
+
throw stopError({
|
|
141
|
+
code: "HOSTED_CREDENTIALS_MISSING",
|
|
142
|
+
message: HOSTED_NOT_SET_UP,
|
|
143
|
+
nextCommand: "gscdump auth login --mode hosted"
|
|
144
|
+
});
|
|
133
145
|
}
|
|
134
146
|
const hostedSiteSchema = z.object({
|
|
135
147
|
siteId: z.string(),
|
|
@@ -162,18 +174,41 @@ async function hostedRequest(state, route, options = {}) {
|
|
|
162
174
|
redirect: "error"
|
|
163
175
|
});
|
|
164
176
|
if (!response.ok) {
|
|
165
|
-
const
|
|
166
|
-
throw Object.assign(new Error(message), {
|
|
177
|
+
const details = {
|
|
167
178
|
statusCode: response.status,
|
|
168
179
|
retryAfter: response.headers.get("retry-after"),
|
|
169
180
|
response
|
|
170
|
-
}
|
|
181
|
+
};
|
|
182
|
+
if (response.status === 401) {
|
|
183
|
+
const session = "sessionId" in state;
|
|
184
|
+
throw Object.assign(stopError({
|
|
185
|
+
code: "HOSTED_CREDENTIALS_REJECTED",
|
|
186
|
+
message: session ? HOSTED_SESSION_REJECTED : HOSTED_KEY_REJECTED,
|
|
187
|
+
nextCommand: session ? "gscdump auth login --mode hosted" : null
|
|
188
|
+
}), details);
|
|
189
|
+
}
|
|
190
|
+
throw Object.assign(/* @__PURE__ */ new Error(`Hosted request failed (${response.status}) for ${route.split("?")[0]}. Check \`gscdump auth status\`.`), details);
|
|
171
191
|
}
|
|
172
192
|
return response.status === 204 ? void 0 : response.json();
|
|
173
193
|
}
|
|
174
194
|
function hostedCredential(state) {
|
|
175
195
|
return "sessionId" in state ? state.sessionId : state.apiKey;
|
|
176
196
|
}
|
|
197
|
+
function siteManagerOf(state) {
|
|
198
|
+
return "sessionId" in state ? { _tag: "gscdump" } : { _tag: "key_issuer" };
|
|
199
|
+
}
|
|
200
|
+
function connectSiteStep(manager) {
|
|
201
|
+
switch (manager._tag) {
|
|
202
|
+
case "gscdump": return "Connect a Site at https://gscdump.com/app/onboarding?step=connect-sites.";
|
|
203
|
+
case "key_issuer": return "Connect a Site in the app that issued this API key.";
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function siteSettingsPlace(manager) {
|
|
207
|
+
switch (manager._tag) {
|
|
208
|
+
case "gscdump": return "in the Site settings on gscdump.com";
|
|
209
|
+
case "key_issuer": return "in the Site settings of the app that issued this API key";
|
|
210
|
+
}
|
|
211
|
+
}
|
|
177
212
|
async function getHostedAccount(state) {
|
|
178
213
|
const result = accountSchema.safeParse(await hostedRequest(state, "/cli/me"));
|
|
179
214
|
if (!result.success) throw new Error("gscdump.com returned invalid account data.");
|
|
@@ -183,4 +218,4 @@ function formatHostedSync(site) {
|
|
|
183
218
|
const status = site.syncStatus ?? "pending";
|
|
184
219
|
return `${status}${site.syncProgress && site.syncProgress.total > 0 && status !== "synced" ? `: ${site.syncProgress.completed.toLocaleString("en-US")} of ${site.syncProgress.total.toLocaleString("en-US")} days (${Math.round(site.syncProgress.percent)}%)` : ""}${status === "synced" && site.oldestDateSynced && site.newestDateSynced ? `: ${site.oldestDateSynced} to ${site.newestDateSynced}` : ""}`;
|
|
185
220
|
}
|
|
186
|
-
export { HOSTED_NOT_SET_UP, HOSTED_SESSION_REJECTED, LOCAL_MODE_REQUIRED, clearAuthentication, formatHostedSync, getHostedAccount, hostedCredential, hostedRequest, parseAuthMode, parseAuthentication, resolveAccessMode, resolveAuthentication, revokeHostedSession, saveAuthentication };
|
|
221
|
+
export { HOSTED_KEY_REJECTED, HOSTED_NOT_SET_UP, HOSTED_SESSION_REJECTED, LOCAL_MODE_REQUIRED, clearAuthentication, connectSiteStep, formatHostedSync, getHostedAccount, hostedCredential, hostedRequest, localModeRequired, parseAuthMode, parseAuthentication, resolveAccessMode, resolveAuthentication, revokeHostedSession, saveAuthentication, siteManagerOf, siteSettingsPlace };
|
package/dist/auth.mjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { resolveAuthentication } from "./auth-state.mjs";
|
|
2
2
|
import { getConfigDir, loadConfig, saveConfig } from "./config.mjs";
|
|
3
3
|
import { pickCliEnvironmentValue, resolveCliEnvironment } from "./environment.mjs";
|
|
4
|
-
import { getAppliedEnvKeys, getLoadedEnvPath } from "./env-file.mjs";
|
|
5
4
|
import { displayPath, logger } from "./utils.mjs";
|
|
6
5
|
import process from "node:process";
|
|
7
6
|
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
@@ -361,7 +360,7 @@ async function resolveAuth(opts = {}) {
|
|
|
361
360
|
return getAuth(opts);
|
|
362
361
|
}
|
|
363
362
|
function envSourceLabel(envVar) {
|
|
364
|
-
return
|
|
363
|
+
return `environment (${envVar})`;
|
|
365
364
|
}
|
|
366
365
|
function pickEnvSource(...envVars) {
|
|
367
366
|
return pickCliEnvironmentValue(envVars);
|
|
@@ -451,11 +450,6 @@ async function describeAuthProvenance() {
|
|
|
451
450
|
else if (tokens) effective = "saved-tokens";
|
|
452
451
|
else effective = "none";
|
|
453
452
|
if ((effective === "byok-refresh-token" || effective === "byok-access-token") && tokens) warnings.push("Environment credentials take priority over saved tokens. If you just ran `auth login --force`, unset or update the environment tokens.");
|
|
454
|
-
if (effective === "byok-refresh-token" && clientId && refreshTok) {
|
|
455
|
-
if (getAppliedEnvKeys().has(clientId.envVar) !== getAppliedEnvKeys().has(refreshTok.envVar)) warnings.push(`client_id and refresh_token come from different sources (${envSourceLabel(clientId.envVar)} vs ${envSourceLabel(refreshTok.envVar)}); they may not match.`);
|
|
456
|
-
}
|
|
457
|
-
const envFile = getLoadedEnvPath();
|
|
458
|
-
if (envFile && getAppliedEnvKeys().size > 0) warnings.push(`Loaded ${getAppliedEnvKeys().size} var(s) from ${displayPath(envFile)}.`);
|
|
459
453
|
return {
|
|
460
454
|
rows,
|
|
461
455
|
effective,
|
package/dist/bing-hosted.mjs
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { stopError } from "./stop.mjs";
|
|
2
|
+
import { getHostedAccount, hostedCredential, siteManagerOf } from "./auth-state.mjs";
|
|
2
3
|
import { logger } from "./utils.mjs";
|
|
3
4
|
import { writeBingDump } from "./bing-data.mjs";
|
|
5
|
+
import { noSitesStop } from "./hosted-site.mjs";
|
|
4
6
|
import { createGscdumpV1Client } from "@gscdump/sdk/v1";
|
|
7
|
+
const BING_SITES_COMMAND = "gscdump bing sites --json";
|
|
5
8
|
function hostedBingClient(state) {
|
|
6
9
|
return createGscdumpV1Client({
|
|
7
10
|
apiRoot: state.apiRoot,
|
|
@@ -30,15 +33,33 @@ async function loadHostedBingConnections(state, sites, options = {}) {
|
|
|
30
33
|
async function resolveHostedBingSites(state, input) {
|
|
31
34
|
if (Boolean(input.site) === Boolean(input.allSites)) throw new Error("Choose --site or --all-sites.");
|
|
32
35
|
const account = await getHostedAccount(state);
|
|
36
|
+
if (account.sites.length === 0) throw stopError(noSitesStop(siteManagerOf(state)));
|
|
33
37
|
const exact = account.sites.filter((site) => site.siteId === input.site || site.siteUrl === input.site);
|
|
34
38
|
const normalize = (value) => URL.parse(value.startsWith("sc-domain:") ? `https://${value.slice(10)}/` : value)?.toString();
|
|
35
39
|
const requested = input.allSites ? account.sites : exact.length ? exact : account.sites.filter((site) => normalize(site.siteUrl) === normalize(input.site) && normalize(input.site) !== void 0);
|
|
36
|
-
if (requested.length === 0) throw
|
|
37
|
-
|
|
40
|
+
if (requested.length === 0) throw stopError({
|
|
41
|
+
code: "SITE_NOT_FOUND",
|
|
42
|
+
message: "No matching Bing site. Run `gscdump bing sites`.",
|
|
43
|
+
nextCommand: BING_SITES_COMMAND
|
|
44
|
+
});
|
|
45
|
+
if (!input.allSites && requested.length > 1) throw stopError({
|
|
46
|
+
code: "SITE_AMBIGUOUS",
|
|
47
|
+
message: "Multiple Bing sites match. Use a site ID from `gscdump bing sites`.",
|
|
48
|
+
nextCommand: BING_SITES_COMMAND
|
|
49
|
+
});
|
|
38
50
|
const connections = await loadHostedBingConnections(state, requested, { tolerateFailures: Boolean(input.allSites) });
|
|
39
51
|
const selected = input.allSites ? connections.filter((site) => site.connection._tag === "connected") : connections;
|
|
40
|
-
if (selected.length === 0) throw
|
|
41
|
-
|
|
52
|
+
if (selected.length === 0) throw stopError({
|
|
53
|
+
code: "SITE_NOT_FOUND",
|
|
54
|
+
message: "No matching Bing site. Run `gscdump bing sites`.",
|
|
55
|
+
nextCommand: BING_SITES_COMMAND
|
|
56
|
+
});
|
|
57
|
+
const unconnected = input.requireConnected === false ? void 0 : selected.find((site) => site.connection._tag !== "connected");
|
|
58
|
+
if (unconnected) throw stopError({
|
|
59
|
+
code: "BING_NOT_CONNECTED",
|
|
60
|
+
message: `Bing is not connected for ${unconnected.siteUrl}. Run \`gscdump bing login --site ${unconnected.siteId}\`.`,
|
|
61
|
+
nextCommand: `gscdump bing login --site ${unconnected.siteId}`
|
|
62
|
+
});
|
|
42
63
|
return selected;
|
|
43
64
|
}
|
|
44
65
|
async function dumpHostedBingSite(state, site, outDir, options) {
|
package/dist/cli.d.mts
CHANGED
|
@@ -2,7 +2,6 @@ import { CliRuntime, CreateCliRuntimeOptions, createCliRuntime } from "./runtime
|
|
|
2
2
|
export declare const main: import("citty").CommandDef<import("citty").ArgsDef>;
|
|
3
3
|
export interface RunCliOptions {
|
|
4
4
|
rawArgs?: string[];
|
|
5
|
-
loadEnv?: boolean;
|
|
6
5
|
environment?: Record<string, string | undefined>;
|
|
7
6
|
runtime?: CliRuntime;
|
|
8
7
|
}
|
package/dist/cli.mjs
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { CLI_SUBCOMMANDS, resolveUsageTarget } from "./command-registry.mjs";
|
|
2
|
-
import { reportCliError } from "./error-handler.mjs";
|
|
3
1
|
import { createCliRuntime, runWithCliRuntime, useCliRuntime } from "./runtime.mjs";
|
|
4
2
|
import { parseAuthMode } from "./auth-state.mjs";
|
|
5
3
|
import { checkCliArgs } from "./cli-args.mjs";
|
|
4
|
+
import { CLI_SUBCOMMANDS, resolveUsageTarget } from "./command-registry.mjs";
|
|
6
5
|
import { resolveCliEnvironment } from "./environment.mjs";
|
|
7
6
|
import { applyProfileFromCli } from "./commands/profile-selection.mjs";
|
|
8
|
-
import {
|
|
7
|
+
import { reportCliError } from "./error-handler.mjs";
|
|
9
8
|
import { resolveOutputOptions, terminalOutputOptions } from "./render/terminal.mjs";
|
|
10
9
|
import { VERSION, setNoColor, showSplash, withConfiguredOutput } from "./utils.mjs";
|
|
11
10
|
import process from "node:process";
|
|
@@ -97,8 +96,8 @@ async function runCli(opts = {}) {
|
|
|
97
96
|
runtime.rawArgs = [...input];
|
|
98
97
|
return runWithCliRuntime(runtime, async () => {
|
|
99
98
|
let rawArgs = [...input];
|
|
99
|
+
runtime.jsonOutput = false;
|
|
100
100
|
try {
|
|
101
|
-
if (opts.loadEnv !== false) loadEnvFromCwd();
|
|
102
101
|
rawArgs = prepareCliArgs(input);
|
|
103
102
|
runtime.rawArgs = [...rawArgs];
|
|
104
103
|
const argumentError = await checkCliArgs(main, rawArgs);
|
|
@@ -108,6 +107,7 @@ async function runCli(opts = {}) {
|
|
|
108
107
|
} catch (error) {
|
|
109
108
|
await reportCliError(error, {
|
|
110
109
|
color: stderrColor(runtime, rawArgs),
|
|
110
|
+
json: runtime.jsonOutput,
|
|
111
111
|
usage: async () => renderUsage(...await resolveUsageTarget(main, rawArgs)),
|
|
112
112
|
authSources: async () => (await import("./auth.mjs")).formatAuthProvenance()
|
|
113
113
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { analyzeCommandMeta } from "../command-meta.mjs";
|
|
2
2
|
import { terminalOutputOptions } from "../render/terminal.mjs";
|
|
3
|
-
import { logger, parseFetchBudget, parseIntegerOption, toCSV } from "../utils.mjs";
|
|
3
|
+
import { logger, parseFetchBudget, parseIntegerOption, setJsonOutput, toCSV } from "../utils.mjs";
|
|
4
4
|
import { DEFAULT_WINDOW, PERIOD_FLAGS, parseWindowFlags, windowFlagErrorToException } from "../window.mjs";
|
|
5
5
|
import { analysisNeeds, analyzerTables, resolveAnalysisSource } from "../analysis-local.mjs";
|
|
6
6
|
import { coverageWarning, renderAnalysis } from "../render/analysis.mjs";
|
|
@@ -176,10 +176,10 @@ function makeToolCommand(tool) {
|
|
|
176
176
|
].includes(args.format ?? "table")) throw new Error("Invalid --format. Use table, json, or csv.");
|
|
177
177
|
const baseParams = buildParams(tool, args);
|
|
178
178
|
const format = args.json ? "json" : String(args.format ?? "table");
|
|
179
|
+
setJsonOutput(format === "json");
|
|
179
180
|
const { runAnalysis, siteUrl, anchor } = await resolveAnalysisSource({
|
|
180
181
|
site: args.site,
|
|
181
182
|
live: !!args.live,
|
|
182
|
-
json: format === "json",
|
|
183
183
|
label: `analyze ${tool}`,
|
|
184
184
|
types: [tool],
|
|
185
185
|
anchorTables: analyzerTables(baseParams),
|
package/dist/commands/auth.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { authCommandMeta } from "../command-meta.mjs";
|
|
2
1
|
import { useCliRuntime } from "../runtime.mjs";
|
|
3
2
|
import { clearAuthentication, formatHostedSync, getHostedAccount, parseAuthMode, parseAuthentication, resolveAuthentication, revokeHostedSession, saveAuthentication } from "../auth-state.mjs";
|
|
3
|
+
import { authCommandMeta } from "../command-meta.mjs";
|
|
4
4
|
import { loadConfig, saveConfig } from "../config.mjs";
|
|
5
5
|
import { OUTPUT_ARGS, applyOutputMode, logger } from "../utils.mjs";
|
|
6
6
|
import { ACCESS_NOT_SET_UP, clearTokens, formatAuthProvenance, getAuth, loadServiceAccount, loadTokens, resolveBYOK, saveTokens } from "../auth.mjs";
|
package/dist/commands/bing.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { bingCommandMeta } from "../command-meta.mjs";
|
|
2
1
|
import { useCliRuntime } from "../runtime.mjs";
|
|
2
|
+
import { stopError } from "../stop.mjs";
|
|
3
3
|
import { resolveAuthentication, saveAuthentication } from "../auth-state.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { bingCommandMeta } from "../command-meta.mjs";
|
|
5
|
+
import { OUTPUT_ARGS, applyOutputMode, logger, plural } from "../utils.mjs";
|
|
5
6
|
import { BING_REDIRECT_URI, clearBingCredentials, getBingClient, loginBingOAuth, saveBingCredentials } from "../bing-auth.mjs";
|
|
6
7
|
import { BING_DATASETS, dumpBingSite, parseBingDumpOptions, resolveBingSites, unwrapBing } from "../bing-data.mjs";
|
|
7
8
|
import { dumpHostedBingSite, hostedBingClient, inspectHostedBingUrl, listHostedBingSites, resolveHostedBingSites } from "../bing-hosted.mjs";
|
|
@@ -171,7 +172,7 @@ const bingCommand = defineCommand({
|
|
|
171
172
|
authenticated: true,
|
|
172
173
|
verifiedSites: sites.filter((site) => site.isVerified).length
|
|
173
174
|
}));
|
|
174
|
-
else logger.success(`Bing authenticated. ${sites.filter((site) => site.isVerified).length
|
|
175
|
+
else logger.success(`Bing authenticated. ${plural(sites.filter((site) => site.isVerified).length, "verified site", "verified sites")}.`);
|
|
175
176
|
}
|
|
176
177
|
}),
|
|
177
178
|
sites: defineCommand({
|
|
@@ -250,7 +251,7 @@ const bingCommand = defineCommand({
|
|
|
250
251
|
for (const site of sites) {
|
|
251
252
|
const summary = await dumpHostedBingSite(authentication, site, args.out, options);
|
|
252
253
|
summaries.push(summary);
|
|
253
|
-
if (!json) logger.success(`Bing ${site.siteUrl}: ${summary.files.length
|
|
254
|
+
if (!json) logger.success(`Bing ${site.siteUrl}: ${plural(summary.files.length, "file", "files")} exported to ${args.out}.`);
|
|
254
255
|
}
|
|
255
256
|
if (json) console.log(JSON.stringify(args["all-sites"] ? {
|
|
256
257
|
searchEngine: "bing",
|
|
@@ -267,7 +268,7 @@ const bingCommand = defineCommand({
|
|
|
267
268
|
for (const site of sites) {
|
|
268
269
|
const summary = await dumpBingSite(client, site, args.out, options);
|
|
269
270
|
summaries.push(summary);
|
|
270
|
-
if (!json) logger.success(`Bing ${site}: ${summary.files.length
|
|
271
|
+
if (!json) logger.success(`Bing ${site}: ${plural(summary.files.length, "file", "files")} exported to ${args.out}.`);
|
|
271
272
|
}
|
|
272
273
|
if (json) console.log(JSON.stringify(args["all-sites"] ? {
|
|
273
274
|
searchEngine: "bing",
|
|
@@ -323,7 +324,11 @@ const bingCommand = defineCommand({
|
|
|
323
324
|
async run({ args }) {
|
|
324
325
|
applyOutputMode(args);
|
|
325
326
|
const authentication = await resolveAuthentication();
|
|
326
|
-
if (authentication._tag !== "Hosted") throw
|
|
327
|
+
if (authentication._tag !== "Hosted") throw stopError({
|
|
328
|
+
code: "HOSTED_MODE_REQUIRED",
|
|
329
|
+
message: "Hosted Bing connection verification needs Hosted mode. Pass --mode hosted.",
|
|
330
|
+
nextCommand: "gscdump auth login --mode hosted"
|
|
331
|
+
});
|
|
327
332
|
const [site] = await resolveHostedBingSites(authentication, {
|
|
328
333
|
site: args.site,
|
|
329
334
|
requireConnected: false
|
package/dist/commands/doctor.mjs
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
+
import { connectSiteStep, getHostedAccount, resolveAuthentication, siteManagerOf } from "../auth-state.mjs";
|
|
1
2
|
import { doctorCommandMeta } from "../command-meta.mjs";
|
|
2
|
-
import { getHostedAccount, resolveAuthentication } from "../auth-state.mjs";
|
|
3
3
|
import { loadConfig } from "../config.mjs";
|
|
4
|
-
import { resolveCliEnvironment } from "../environment.mjs";
|
|
5
|
-
import { parseEnvFile } from "../env-file.mjs";
|
|
4
|
+
import { pickCliEnvironmentValue, resolveCliEnvironment } from "../environment.mjs";
|
|
6
5
|
import { OUTPUT_ARGS, applyOutputMode, displayPath, logger } from "../utils.mjs";
|
|
7
6
|
import { getAuth, isStaleServiceAccountPointer, loadTokens, resolveAuth, resolveBYOK, resolveServiceAccount } from "../auth.mjs";
|
|
8
7
|
import { createLocalStore } from "../local-store.mjs";
|
|
@@ -12,73 +11,19 @@ import { currentAccessToken, fetchTokenInfo, redactTokens } from "../token-info.
|
|
|
12
11
|
import process from "node:process";
|
|
13
12
|
import { defineCommand } from "citty";
|
|
14
13
|
import fs from "node:fs/promises";
|
|
15
|
-
import path from "node:path";
|
|
16
14
|
import { googleSearchConsole } from "gscdump/client";
|
|
17
15
|
import { resolveSiteInput } from "gscdump";
|
|
18
16
|
import { ofetch } from "ofetch";
|
|
19
17
|
const FETCH_TIMEOUT_MS = 5e3;
|
|
20
18
|
const TIME_SKEW_WARN_MS = 3e5;
|
|
21
19
|
const WATERMARK_STALE_DAYS_WARN = 7;
|
|
22
|
-
|
|
23
|
-
"GSC_ACCESS_TOKEN",
|
|
24
|
-
"GSC_CLIENT_ID",
|
|
25
|
-
"GSC_CLIENT_SECRET",
|
|
26
|
-
"GSC_REFRESH_TOKEN",
|
|
27
|
-
"GOOGLE_ACCESS_TOKEN",
|
|
28
|
-
"GOOGLE_CLIENT_ID",
|
|
29
|
-
"GOOGLE_CLIENT_SECRET",
|
|
30
|
-
"GOOGLE_REFRESH_TOKEN",
|
|
31
|
-
"GOOGLE_APPLICATION_CREDENTIALS",
|
|
32
|
-
"GSC_SERVICE_ACCOUNT_JSON"
|
|
33
|
-
];
|
|
34
|
-
function redact(v) {
|
|
35
|
-
if (!v) return "<missing>";
|
|
36
|
-
if (v.length <= 6) return "***";
|
|
37
|
-
return `***${v.slice(-6)}`;
|
|
38
|
-
}
|
|
39
|
-
async function checkEnv() {
|
|
40
|
-
const envPath = path.join(process.cwd(), ".env");
|
|
41
|
-
const parsed = parseEnvFile(envPath);
|
|
42
|
-
if (!parsed) return {
|
|
43
|
-
checks: [{
|
|
44
|
-
name: "env",
|
|
45
|
-
status: "info",
|
|
46
|
-
detail: "no .env (using shell env / saved tokens)"
|
|
47
|
-
}],
|
|
48
|
-
envKeys: /* @__PURE__ */ new Set()
|
|
49
|
-
};
|
|
50
|
-
const relevant = RELEVANT_ENV_KEYS.filter((k) => parsed[k] !== void 0);
|
|
51
|
-
const envKeys = new Set(relevant);
|
|
52
|
-
if (relevant.length === 0) return {
|
|
53
|
-
checks: [{
|
|
54
|
-
name: "env",
|
|
55
|
-
status: "info",
|
|
56
|
-
detail: `${displayPath(envPath)} found, no auth vars`
|
|
57
|
-
}],
|
|
58
|
-
envKeys
|
|
59
|
-
};
|
|
60
|
-
const inventory = relevant.map((k) => {
|
|
61
|
-
const v = parsed[k];
|
|
62
|
-
if (k.endsWith("CLIENT_ID")) return `${k}=${v}`;
|
|
63
|
-
return `${k}=${redact(v)}`;
|
|
64
|
-
});
|
|
65
|
-
return {
|
|
66
|
-
checks: [{
|
|
67
|
-
name: "env",
|
|
68
|
-
status: "info",
|
|
69
|
-
detail: `${displayPath(envPath)} → ${inventory.join(", ")} (not validated — see auth)`
|
|
70
|
-
}],
|
|
71
|
-
envKeys
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
function describeAuthSource(envKeys, byok) {
|
|
20
|
+
function describeAuthSource(byok) {
|
|
75
21
|
if (!byok) return "saved tokens";
|
|
76
22
|
const isAccessToken = typeof byok === "string";
|
|
77
|
-
const driver = isAccessToken ? "GSC_ACCESS_TOKEN" : "GSC_REFRESH_TOKEN";
|
|
78
|
-
|
|
79
|
-
return `environment credentials ${isAccessToken ? "(access-token)" : "(refresh-token)"} from ${source} via ${driver}`;
|
|
23
|
+
const driver = pickCliEnvironmentValue(isAccessToken ? ["GSC_ACCESS_TOKEN", "GOOGLE_ACCESS_TOKEN"] : ["GSC_REFRESH_TOKEN", "GOOGLE_REFRESH_TOKEN"])?.envVar ?? (isAccessToken ? "GSC_ACCESS_TOKEN" : "GSC_REFRESH_TOKEN");
|
|
24
|
+
return `environment credentials ${isAccessToken ? "(access-token)" : "(refresh-token)"} via ${driver}`;
|
|
80
25
|
}
|
|
81
|
-
async function checkAuth(
|
|
26
|
+
async function checkAuth() {
|
|
82
27
|
const checks = [];
|
|
83
28
|
const serviceAccount = await resolveServiceAccount().catch((error) => {
|
|
84
29
|
if (isStaleServiceAccountPointer(error)) {
|
|
@@ -153,7 +98,7 @@ async function checkAuth(envKeys) {
|
|
|
153
98
|
checks.push({
|
|
154
99
|
name: "auth",
|
|
155
100
|
status: "fail",
|
|
156
|
-
detail: `${describeAuthSource(
|
|
101
|
+
detail: `${describeAuthSource(byok)}: refresh failed: ${current.detail}. Run \`gscdump auth login --force\` and update the source above.`
|
|
157
102
|
});
|
|
158
103
|
return {
|
|
159
104
|
checks,
|
|
@@ -177,7 +122,7 @@ async function checkAuth(envKeys) {
|
|
|
177
122
|
checks.push({
|
|
178
123
|
name: "auth",
|
|
179
124
|
status: "pass",
|
|
180
|
-
detail: describeAuthSource(
|
|
125
|
+
detail: describeAuthSource(byok)
|
|
181
126
|
});
|
|
182
127
|
if (info.email) checks.push({
|
|
183
128
|
name: "auth.account",
|
|
@@ -394,7 +339,7 @@ async function checkHostedConnection(authentication) {
|
|
|
394
339
|
} : {
|
|
395
340
|
name: "hosted.sites",
|
|
396
341
|
status: "warn",
|
|
397
|
-
detail:
|
|
342
|
+
detail: `no hosted Sites. ${connectSiteStep(siteManagerOf(authentication))}`
|
|
398
343
|
}];
|
|
399
344
|
return [
|
|
400
345
|
{
|
|
@@ -410,9 +355,9 @@ async function checkHostedConnection(authentication) {
|
|
|
410
355
|
...sitesChecks
|
|
411
356
|
];
|
|
412
357
|
}
|
|
413
|
-
async function checkLocalConnection(
|
|
358
|
+
async function checkLocalConnection() {
|
|
414
359
|
const [authResult, timeChecks, gscApi, indexingApi, siteVerificationApi] = await Promise.all([
|
|
415
|
-
checkAuth(
|
|
360
|
+
checkAuth(),
|
|
416
361
|
checkTimeSkew(),
|
|
417
362
|
checkApiReachable("gsc.api", "https://searchconsole.googleapis.com/$discovery/rest?version=v1"),
|
|
418
363
|
checkApiReachable("indexing.api", "https://indexing.googleapis.com/$discovery/rest?version=v3"),
|
|
@@ -439,14 +384,12 @@ const doctorCommand = defineCommand({
|
|
|
439
384
|
const { json } = applyOutputMode(args);
|
|
440
385
|
const { dataDir } = await createCommandContext();
|
|
441
386
|
const authentication = await resolveAuthentication();
|
|
442
|
-
const envResult = await checkEnv();
|
|
443
387
|
const [connectionChecks, dataDirChecks, watermarkChecks] = await Promise.all([
|
|
444
|
-
authentication._tag === "Hosted" ? checkHostedConnection(authentication) : checkLocalConnection(
|
|
388
|
+
authentication._tag === "Hosted" ? checkHostedConnection(authentication) : checkLocalConnection(),
|
|
445
389
|
checkDataDir(dataDir),
|
|
446
390
|
checkStoreWatermarks(dataDir)
|
|
447
391
|
]);
|
|
448
392
|
const all = [
|
|
449
|
-
...envResult.checks,
|
|
450
393
|
...connectionChecks,
|
|
451
394
|
...dataDirChecks,
|
|
452
395
|
...watermarkChecks
|
|
@@ -121,8 +121,8 @@ const indexingUrlsCommand = defineCommand({
|
|
|
121
121
|
if (limit > MAX_PAGE_SIZE) throw new Error(`--limit must be ${MAX_PAGE_SIZE} or less. Use --all to read every page.`);
|
|
122
122
|
const search = args.search ? String(args.search) : void 0;
|
|
123
123
|
applyOutputMode({
|
|
124
|
-
json: format
|
|
125
|
-
quiet: args.quiet
|
|
124
|
+
json: format === "json",
|
|
125
|
+
quiet: Boolean(args.quiet) || format === "csv"
|
|
126
126
|
});
|
|
127
127
|
const filters = {
|
|
128
128
|
...status ? { status } : {},
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { siteManagerOf, siteSettingsPlace } from "../auth-state.mjs";
|
|
1
2
|
import { terminalOutputOptions } from "../render/terminal.mjs";
|
|
2
3
|
import { applyOutputMode, logger, readUrlList } from "../utils.mjs";
|
|
3
4
|
import { renderTable } from "../render/layout.mjs";
|
|
@@ -39,12 +40,14 @@ function watchedRow(entry) {
|
|
|
39
40
|
dueAt: entry.dueAt.slice(0, 10)
|
|
40
41
|
};
|
|
41
42
|
}
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
function skipHint(reason, manager) {
|
|
44
|
+
return reason === "inspection_disabled" ? `gscdump never inspects this Site, so a Watched URL gets no Checkpoint. To use Watched URLs, turn on URL Inspection ${siteSettingsPlace(manager)}.` : void 0;
|
|
45
|
+
}
|
|
46
|
+
function printChange(verb, result, siteUrl, manager) {
|
|
44
47
|
for (const url of result.changed) console.log(`${verb}: ${url}`);
|
|
45
48
|
for (const url of result.unchanged) console.log(`${verb === "Added" ? "Already watched" : "Not watched"}: ${url}`);
|
|
46
49
|
for (const { url, reason } of result.skipped) console.log(`Skipped (${reason}): ${url}`);
|
|
47
|
-
for (const hint of new Set(result.skipped.map(({ reason }) =>
|
|
50
|
+
for (const hint of new Set(result.skipped.map(({ reason }) => skipHint(reason, manager)).filter(Boolean))) console.log(hint);
|
|
48
51
|
logger.info(`${siteUrl} has ${result.total} of ${result.limit} Watched URLs.`);
|
|
49
52
|
}
|
|
50
53
|
async function readUrls(args) {
|
|
@@ -133,7 +136,7 @@ const addCommand = defineCommand({
|
|
|
133
136
|
async run({ args }) {
|
|
134
137
|
const { json } = applyOutputMode(args);
|
|
135
138
|
const urls = await readUrls(args);
|
|
136
|
-
const { client, site } = await resolveHostedSite({
|
|
139
|
+
const { client, site, authentication } = await resolveHostedSite({
|
|
137
140
|
...args,
|
|
138
141
|
_: []
|
|
139
142
|
}, {
|
|
@@ -148,7 +151,7 @@ const addCommand = defineCommand({
|
|
|
148
151
|
console.log(JSON.stringify(data, null, 2));
|
|
149
152
|
return;
|
|
150
153
|
}
|
|
151
|
-
printChange("Added", data, site.siteUrl);
|
|
154
|
+
printChange("Added", data, site.siteUrl, siteManagerOf(authentication));
|
|
152
155
|
}
|
|
153
156
|
});
|
|
154
157
|
const removeCommand = defineCommand({
|
|
@@ -164,7 +167,7 @@ const removeCommand = defineCommand({
|
|
|
164
167
|
async run({ args }) {
|
|
165
168
|
const { json } = applyOutputMode(args);
|
|
166
169
|
const urls = await readUrls(args);
|
|
167
|
-
const { client, site } = await resolveHostedSite({
|
|
170
|
+
const { client, site, authentication } = await resolveHostedSite({
|
|
168
171
|
...args,
|
|
169
172
|
_: []
|
|
170
173
|
}, {
|
|
@@ -179,7 +182,7 @@ const removeCommand = defineCommand({
|
|
|
179
182
|
console.log(JSON.stringify(data, null, 2));
|
|
180
183
|
return;
|
|
181
184
|
}
|
|
182
|
-
printChange("Removed", data, site.siteUrl);
|
|
185
|
+
printChange("Removed", data, site.siteUrl, siteManagerOf(authentication));
|
|
183
186
|
}
|
|
184
187
|
});
|
|
185
188
|
const indexingWatchCommand = defineCommand({
|
package/dist/commands/init.mjs
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
import { initCommandMeta } from "../command-meta.mjs";
|
|
2
1
|
import { useCliRuntime } from "../runtime.mjs";
|
|
3
2
|
import { saveAuthentication } from "../auth-state.mjs";
|
|
3
|
+
import { initCommandMeta } from "../command-meta.mjs";
|
|
4
4
|
import { defaultDataDir, loadConfig, saveConfig } from "../config.mjs";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { ACCESS_NOT_SET_UP, authenticate, getAuth, loadTokens, resolveBYOK, resolveServiceAccount, saveTokens } from "../auth.mjs";
|
|
5
|
+
import { OUTPUT_ARGS, applyOutputMode, logger } from "../utils.mjs";
|
|
6
|
+
import { ACCESS_NOT_SET_UP, getAuth, loadTokens, resolveBYOK, resolveServiceAccount } from "../auth.mjs";
|
|
8
7
|
import { loginHosted } from "./auth.mjs";
|
|
9
8
|
import process from "node:process";
|
|
10
9
|
import { defineCommand } from "citty";
|
|
11
|
-
import
|
|
12
|
-
import path from "node:path";
|
|
13
|
-
import { confirm, isCancel, text } from "@clack/prompts";
|
|
10
|
+
import { isCancel, text } from "@clack/prompts";
|
|
14
11
|
import { googleSearchConsole } from "gscdump/client";
|
|
15
|
-
const ENV_LINE_RE = /^([^=]+)=(.*)$/;
|
|
16
12
|
function canPrompt() {
|
|
17
13
|
return process.stdin.isTTY === true;
|
|
18
14
|
}
|
|
@@ -27,24 +23,6 @@ async function promptDataDir(existing) {
|
|
|
27
23
|
if (isCancel(answer)) process.exit(1);
|
|
28
24
|
return String(answer) || fallback;
|
|
29
25
|
}
|
|
30
|
-
async function loadEnvFile() {
|
|
31
|
-
const envPath = path.join(process.cwd(), ".env");
|
|
32
|
-
const content = await fs.readFile(envPath, "utf-8").catch(() => null);
|
|
33
|
-
if (!content) return null;
|
|
34
|
-
const env = {};
|
|
35
|
-
for (const line of content.split("\n")) {
|
|
36
|
-
const trimmed = line.trim();
|
|
37
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
38
|
-
const match = trimmed.match(ENV_LINE_RE);
|
|
39
|
-
if (match) {
|
|
40
|
-
const key = match[1].trim();
|
|
41
|
-
let value = match[2].trim();
|
|
42
|
-
if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
43
|
-
env[key] = value;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return env;
|
|
47
|
-
}
|
|
48
26
|
const initCommand = defineCommand({
|
|
49
27
|
meta: initCommandMeta,
|
|
50
28
|
args: {
|
|
@@ -121,40 +99,6 @@ const initCommand = defineCommand({
|
|
|
121
99
|
printNextSteps();
|
|
122
100
|
return;
|
|
123
101
|
}
|
|
124
|
-
const envFile = await loadEnvFile();
|
|
125
|
-
const envCid = envFile?.GSC_CLIENT_ID ?? envFile?.GOOGLE_CLIENT_ID;
|
|
126
|
-
const envSec = envFile?.GSC_CLIENT_SECRET ?? envFile?.GOOGLE_CLIENT_SECRET;
|
|
127
|
-
const envRef = envFile?.GSC_REFRESH_TOKEN ?? envFile?.GOOGLE_REFRESH_TOKEN;
|
|
128
|
-
const envAcc = envFile?.GSC_ACCESS_TOKEN ?? envFile?.GOOGLE_ACCESS_TOKEN;
|
|
129
|
-
if (envCid && envSec && envRef) {
|
|
130
|
-
logger.info("Found .env file with credentials");
|
|
131
|
-
applyCliEnvironment({
|
|
132
|
-
GOOGLE_ACCESS_TOKEN: envAcc,
|
|
133
|
-
GOOGLE_CLIENT_ID: envCid,
|
|
134
|
-
GOOGLE_CLIENT_SECRET: envSec,
|
|
135
|
-
GOOGLE_REFRESH_TOKEN: envRef
|
|
136
|
-
});
|
|
137
|
-
await saveConfig({
|
|
138
|
-
...config,
|
|
139
|
-
clientId: envCid,
|
|
140
|
-
clientSecret: envSec,
|
|
141
|
-
dataDir: config.dataDir ?? defaultDataDir()
|
|
142
|
-
});
|
|
143
|
-
const creds = (await authenticate({
|
|
144
|
-
clientId: envCid,
|
|
145
|
-
clientSecret: envSec
|
|
146
|
-
}, false)).credentials;
|
|
147
|
-
if (creds.access_token) await saveTokens({
|
|
148
|
-
access_token: creds.access_token,
|
|
149
|
-
refresh_token: creds.refresh_token || envRef,
|
|
150
|
-
expiry_date: creds.expiry_date
|
|
151
|
-
});
|
|
152
|
-
await saveAuthentication({ _tag: "Local" });
|
|
153
|
-
console.log();
|
|
154
|
-
logger.success("Setup complete using .env credentials.");
|
|
155
|
-
printNextSteps();
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
102
|
if (!canPrompt()) {
|
|
159
103
|
if (!await loadTokens()) {
|
|
160
104
|
logger.error(`Init cannot prompt without a terminal.\n${ACCESS_NOT_SET_UP}`);
|
|
@@ -180,7 +124,6 @@ const initCommand = defineCommand({
|
|
|
180
124
|
});
|
|
181
125
|
const sites = await runSmokeTest(await getAuth({ interactive: true }));
|
|
182
126
|
await saveAuthentication({ _tag: "Local" });
|
|
183
|
-
if (config.clientId && config.clientSecret) await maybeWriteEnvFile(config.clientId, config.clientSecret);
|
|
184
127
|
console.log();
|
|
185
128
|
logger.success("Setup complete.");
|
|
186
129
|
printNextSteps(sites);
|
|
@@ -229,32 +172,4 @@ async function runSmokeTest(oauth) {
|
|
|
229
172
|
logger.success(`Verified: ${sites.length} GSC site(s) accessible`);
|
|
230
173
|
return sites.flatMap((site) => site.siteUrl ? [site.siteUrl] : []);
|
|
231
174
|
}
|
|
232
|
-
async function maybeWriteEnvFile(clientId, clientSecret) {
|
|
233
|
-
const tokens = await loadTokens();
|
|
234
|
-
if (!tokens?.refresh_token) return;
|
|
235
|
-
const wants = await confirm({
|
|
236
|
-
message: "Write a `.env` file with these credentials? (handy for CI / other machines)",
|
|
237
|
-
initialValue: false
|
|
238
|
-
});
|
|
239
|
-
if (isCancel(wants) || !wants) return;
|
|
240
|
-
const envPath = path.join(process.cwd(), ".env");
|
|
241
|
-
if (await fs.stat(envPath).then(() => true).catch(() => false)) {
|
|
242
|
-
const overwrite = await confirm({
|
|
243
|
-
message: `${displayPath(envPath)} exists. Overwrite?`,
|
|
244
|
-
initialValue: false
|
|
245
|
-
});
|
|
246
|
-
if (isCancel(overwrite) || !overwrite) {
|
|
247
|
-
logger.info(`Skipped — keep credentials at ${displayPath(envPath)} manually if needed`);
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
const content = [
|
|
252
|
-
`GSC_CLIENT_ID=${clientId}`,
|
|
253
|
-
`GSC_CLIENT_SECRET=${clientSecret}`,
|
|
254
|
-
`GSC_REFRESH_TOKEN=${tokens.refresh_token}`,
|
|
255
|
-
""
|
|
256
|
-
].join("\n");
|
|
257
|
-
await fs.writeFile(envPath, content, { mode: 384 });
|
|
258
|
-
logger.success(`Wrote ${displayPath(envPath)}`);
|
|
259
|
-
}
|
|
260
175
|
export { initCommand, printHostedNextSteps, printNextSteps, runSmokeTest };
|
package/dist/commands/mcp.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { mcpCommandMeta } from "../command-meta.mjs";
|
|
2
1
|
import { runWithCliRuntime, useCliRuntime } from "../runtime.mjs";
|
|
2
|
+
import { mcpCommandMeta } from "../command-meta.mjs";
|
|
3
3
|
import { VERSION } from "../utils.mjs";
|
|
4
4
|
import { probeAuth } from "../auth.mjs";
|
|
5
5
|
import { createCommandContext } from "../context.mjs";
|