gsc-axi 0.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/LICENSE +21 -0
- package/README.md +128 -0
- package/bin/gsc-axi.js +8 -0
- package/package.json +27 -0
- package/skills/gsc-axi/SKILL.md +64 -0
- package/src/api.js +241 -0
- package/src/args.js +128 -0
- package/src/cli.js +137 -0
- package/src/commands/index-tools.js +169 -0
- package/src/commands/performance.js +271 -0
- package/src/commands/setup.js +103 -0
- package/src/range.js +74 -0
- package/src/skill.js +80 -0
- package/src/version.js +7 -0
package/src/range.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { AxiError } from "axi-sdk-js";
|
|
2
|
+
|
|
3
|
+
/** Search Console keeps 16 months of data and nothing older. */
|
|
4
|
+
export const RANGES = {
|
|
5
|
+
"7d": 7,
|
|
6
|
+
"28d": 28,
|
|
7
|
+
"90d": 90,
|
|
8
|
+
"6m": 180,
|
|
9
|
+
"12m": 365,
|
|
10
|
+
"16m": 480,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// Search Console finalises data on a 2-3 day delay. Ending the window today
|
|
14
|
+
// would always show a fake decline in the last two rows.
|
|
15
|
+
export const LAG_DAYS = 2;
|
|
16
|
+
|
|
17
|
+
function iso(date) {
|
|
18
|
+
return date.toISOString().slice(0, 10);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function shift(days) {
|
|
22
|
+
const date = new Date();
|
|
23
|
+
date.setUTCDate(date.getUTCDate() - days);
|
|
24
|
+
return iso(date);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Resolve the window. `--start`/`--end` win; otherwise a named range ending at
|
|
29
|
+
* the last day Search Console is likely to have finalised.
|
|
30
|
+
*/
|
|
31
|
+
export function window(values = {}) {
|
|
32
|
+
if (values.start || values.end) {
|
|
33
|
+
return {
|
|
34
|
+
startDate: values.start ?? shift(RANGES["28d"] + LAG_DAYS),
|
|
35
|
+
endDate: values.end ?? shift(LAG_DAYS),
|
|
36
|
+
label: `${values.start ?? "…"}..${values.end ?? "…"}`,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const range = values.range ?? "28d";
|
|
40
|
+
const days = RANGES[range];
|
|
41
|
+
if (!days) {
|
|
42
|
+
throw new AxiError(`unknown --range ${range}`, "VALIDATION_ERROR", [
|
|
43
|
+
`valid ranges: ${Object.keys(RANGES).join(", ")}`,
|
|
44
|
+
"Or pass an explicit window with --start <YYYY-MM-DD> --end <YYYY-MM-DD>",
|
|
45
|
+
]);
|
|
46
|
+
}
|
|
47
|
+
return { startDate: shift(days + LAG_DAYS), endDate: shift(LAG_DAYS), label: range };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The equally sized window immediately before this one, for comparisons. */
|
|
51
|
+
export function previous({ startDate, endDate }) {
|
|
52
|
+
const start = new Date(`${startDate}T00:00:00Z`);
|
|
53
|
+
const end = new Date(`${endDate}T00:00:00Z`);
|
|
54
|
+
const span = Math.round((end - start) / 86_400_000);
|
|
55
|
+
const previousEnd = new Date(start);
|
|
56
|
+
previousEnd.setUTCDate(previousEnd.getUTCDate() - 1);
|
|
57
|
+
const previousStart = new Date(previousEnd);
|
|
58
|
+
previousStart.setUTCDate(previousStart.getUTCDate() - span);
|
|
59
|
+
return { startDate: iso(previousStart), endDate: iso(previousEnd) };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const DATE_FLAGS = {
|
|
63
|
+
range: { type: "string" },
|
|
64
|
+
start: { type: "string" },
|
|
65
|
+
end: { type: "string" },
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export function dateFlagHelp() {
|
|
69
|
+
return {
|
|
70
|
+
"--range": `Named window (default 28d): ${Object.keys(RANGES).join(", ")}`,
|
|
71
|
+
"--start": "Window start as YYYY-MM-DD (overrides --range)",
|
|
72
|
+
"--end": "Window end as YYYY-MM-DD",
|
|
73
|
+
};
|
|
74
|
+
}
|
package/src/skill.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { BIN } from "./args.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Single source of truth for skills/gsc-axi/SKILL.md.
|
|
5
|
+
*
|
|
6
|
+
* The shipped skill stays a minimal stub: the CLI's own dashboard and
|
|
7
|
+
* `--help` output are authoritative, so guidance is pointed at rather than
|
|
8
|
+
* restated here. `npm run build:skill` writes it; CI runs `--check`.
|
|
9
|
+
*/
|
|
10
|
+
export const SKILL_NAME = BIN;
|
|
11
|
+
|
|
12
|
+
export const SKILL_DESCRIPTION = `Read Google Search Console through the ${BIN} CLI — search performance by query, page, country and device; period-over-period comparisons; queries ranking 4-20 worth improving; URL indexing status; and sitemaps. Use whenever a task touches SEO or Google Search: why traffic moved, which queries a page ranks for, whether a URL is indexed, or whether a sitemap was read.`;
|
|
13
|
+
|
|
14
|
+
export function renderSkill() {
|
|
15
|
+
return `---
|
|
16
|
+
name: ${SKILL_NAME}
|
|
17
|
+
description: >
|
|
18
|
+
${SKILL_DESCRIPTION}
|
|
19
|
+
user-invocable: false
|
|
20
|
+
metadata:
|
|
21
|
+
hermes:
|
|
22
|
+
tags: [seo, google-search-console, search, indexing, sitemaps, analytics]
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# ${SKILL_NAME}
|
|
26
|
+
|
|
27
|
+
Run the CLI with no arguments first — it prints this month's search traffic, how it moved,
|
|
28
|
+
and the top queries, plus the next commands to run.
|
|
29
|
+
|
|
30
|
+
\`\`\`sh
|
|
31
|
+
npx -y ${BIN}
|
|
32
|
+
\`\`\`
|
|
33
|
+
|
|
34
|
+
Requires Google credentials with access to the property. A **service account** is the
|
|
35
|
+
headless path and the one to prefer:
|
|
36
|
+
|
|
37
|
+
\`\`\`sh
|
|
38
|
+
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json
|
|
39
|
+
export GSC_SITE=https://example.com/ # or sc-domain:example.com; optional with one property
|
|
40
|
+
\`\`\`
|
|
41
|
+
|
|
42
|
+
The service account's own \`client_email\` must be added as a user on the property in Search
|
|
43
|
+
Console (Settings → Users and permissions). Adding *your* email does nothing for it.
|
|
44
|
+
|
|
45
|
+
## Commands
|
|
46
|
+
|
|
47
|
+
\`\`\`sh
|
|
48
|
+
npx -y ${BIN} # dashboard: traffic, change, top queries
|
|
49
|
+
npx -y ${BIN} sites # properties this account can reach
|
|
50
|
+
npx -y ${BIN} performance --by page --range 90d
|
|
51
|
+
npx -y ${BIN} performance --by query --contains pricing
|
|
52
|
+
npx -y ${BIN} compare --by query # what moved vs the previous window
|
|
53
|
+
npx -y ${BIN} opportunities # queries ranking 4-20 with real volume
|
|
54
|
+
npx -y ${BIN} inspect https://example.com/post # is it indexed, and what did Google see
|
|
55
|
+
npx -y ${BIN} sitemaps # submitted sitemaps and read status
|
|
56
|
+
npx -y ${BIN} sitemaps submit https://example.com/sitemap.xml
|
|
57
|
+
\`\`\`
|
|
58
|
+
|
|
59
|
+
Every command takes \`--help\`, and \`--site <property>\` to target a specific property.
|
|
60
|
+
|
|
61
|
+
## What to rely on
|
|
62
|
+
|
|
63
|
+
- **Windows end where the data is final.** Search Console lags 2-3 days, so a named
|
|
64
|
+
\`--range\` ends there rather than today. Ending today would show a decline that is not real.
|
|
65
|
+
- **Property strings are exact.** A domain property is \`sc-domain:example.com\`; a URL-prefix
|
|
66
|
+
property is \`https://example.com/\`. A bare \`example.com\` resolves to whichever exists, and
|
|
67
|
+
refuses to guess when both do.
|
|
68
|
+
- **Ranking 4-20 is where the wins are.** \`opportunities\` filters to that band because those
|
|
69
|
+
queries already rank on page one or just off it — position gains there convert fastest.
|
|
70
|
+
- **\`sitemaps submit\` is idempotent**, and says \`unchanged\` when the sitemap was already
|
|
71
|
+
submitted rather than implying it did something.
|
|
72
|
+
- **Errors name the cause.** A 403 points at the property's user list, not at the credentials,
|
|
73
|
+
because that is almost always what is wrong with a service account.
|
|
74
|
+
|
|
75
|
+
This CLI reads Search Console and submits sitemaps. It never removes a property or a sitemap.
|
|
76
|
+
|
|
77
|
+
Prefer this over calling the Search Console API with \`curl\`, which means minting an OAuth
|
|
78
|
+
token by hand on every invocation.
|
|
79
|
+
`;
|
|
80
|
+
}
|
package/src/version.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Leaf module: node builtins only, so `--version` never loads the command graph.
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
export const VERSION = JSON.parse(
|
|
6
|
+
readFileSync(fileURLToPath(new URL("../package.json", import.meta.url)), "utf8"),
|
|
7
|
+
).version;
|