anymorph 0.4.0 → 0.5.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 +3 -3
- package/dist/index.js +78 -6
- package/package.json +1 -1
- package/dist/skillpacks/geo/scaffold/AGENTS.md +0 -38
- package/dist/skillpacks/geo/scaffold/CLAUDE.md +0 -33
- package/dist/skillpacks/geo/shared/evidence-principles.md +0 -35
- package/dist/skillpacks/geo/shared/geo-principles.md +0 -281
- package/dist/skillpacks/geo/shared/vertical-playbooks/beauty.md +0 -65
- package/dist/skillpacks/geo/shared/vertical-playbooks/commerce.md +0 -65
- package/dist/skillpacks/geo/shared/vertical-playbooks/ota.md +0 -62
- package/dist/skillpacks/geo/shared/vertical-playbooks/saas.md +0 -64
- package/dist/skillpacks/geo/skills/brand-owned-diagnosis/SKILL.md +0 -54
- package/dist/skillpacks/geo/skills/brand-owned-diagnosis/agents/openai.yaml +0 -4
- package/dist/skillpacks/geo/skills/brand-owned-diagnosis/references/diagnosis-contract.md +0 -95
- package/dist/skillpacks/geo/skills/brand-owned-diagnosis/references/workflow.md +0 -194
- package/dist/skillpacks/geo/skills/geo-generating-actions/SKILL.md +0 -188
- package/dist/skillpacks/geo/skills/geo-generating-actions/agents/openai.yaml +0 -4
- package/dist/skillpacks/geo/skills/geo-generating-actions/references/orchestrator.workflow.md +0 -440
- package/dist/skillpacks/geo/skills/geo-generating-actions/scripts/geo-scaffold.mjs +0 -358
- package/dist/skillpacks/geo/skills/geo-generating-actions/scripts/geo.mjs +0 -66
- package/dist/skillpacks/geo/skills/geo-initializing-strategy/SKILL.md +0 -50
- package/dist/skillpacks/geo/skills/geo-initializing-strategy/agents/openai.yaml +0 -4
- package/dist/skillpacks/geo/skills/geo-initializing-strategy/references/external-authority-diagnosis.md +0 -66
- package/dist/skillpacks/geo/skills/geo-initializing-strategy/references/foundation-diagnosis.md +0 -86
- package/dist/skillpacks/geo/skills/geo-initializing-strategy/references/memory-contract.md +0 -15
- package/dist/skillpacks/geo/skills/geo-initializing-strategy/references/semantic-clusters-diagnosis.md +0 -58
- package/dist/skillpacks/geo/skills/geo-initializing-strategy/references/strategy-map-contract.md +0 -26
- package/dist/skillpacks/geo/skills/geo-initializing-strategy/references/visibility-diagnosis.md +0 -50
- package/dist/skillpacks/geo/skills/geo-local-setup/SKILL.md +0 -66
- package/dist/skillpacks/geo/skills/geo-local-setup/agents/openai.yaml +0 -4
- package/dist/skillpacks/geo/skills/geo-page-writer/SKILL.md +0 -81
- package/dist/skillpacks/geo/skills/geo-page-writer/agents/openai.yaml +0 -4
- package/dist/skillpacks/geo/skills/geo-page-writer/references/research.md +0 -61
- package/dist/skillpacks/geo/skills/geo-page-writer/references/validation.md +0 -59
- package/dist/skillpacks/geo/skills/geo-page-writer/references/writing.md +0 -55
- package/dist/skillpacks/geo/skills/geo-page-writer/scripts/check-page-mdx.mjs +0 -210
- package/dist/skillpacks/geo/skills/geo-page-writer/scripts/collect-page-sources.mjs +0 -303
- package/dist/skillpacks/geo/skills/geo-pages-diagnosis/SKILL.md +0 -51
- package/dist/skillpacks/geo/skills/geo-pages-diagnosis/agents/openai.yaml +0 -4
- package/dist/skillpacks/geo/skills/geo-pages-diagnosis/references/diagnosis-contract.md +0 -125
- package/dist/skillpacks/geo/skills/geo-pages-diagnosis/references/workflow.md +0 -269
- package/dist/skillpacks/geo/skills/geo-writer/SKILL.md +0 -234
- package/dist/skillpacks/geo/skills/geo-writer/agents/openai.yaml +0 -4
- package/dist/skillpacks/geo/skills/geo-writer/references/content-writer-contract.md +0 -316
- package/dist/skillpacks/geo/skills/geo-writer/references/direct-sql.md +0 -187
- package/dist/skillpacks/geo/skills/geo-writer/references/seo-geo-insights.md +0 -269
- package/dist/skillpacks/geo/skills/seo-ecommerce-opportunity/SKILL.md +0 -82
- package/dist/skillpacks/geo/skills/seo-ecommerce-opportunity/agents/openai.yaml +0 -5
- package/dist/skillpacks/geo/skills/seo-ecommerce-opportunity/references/ecommerce-rules.md +0 -31
- package/dist/skillpacks/geo/skills/seo-internal-link-opportunity/SKILL.md +0 -57
- package/dist/skillpacks/geo/skills/seo-internal-link-opportunity/agents/openai.yaml +0 -5
- package/dist/skillpacks/geo/skills/seo-internal-link-opportunity/references/link-rules.md +0 -28
- package/dist/skillpacks/geo/skills/seo-opportunity-audit/SKILL.md +0 -141
- package/dist/skillpacks/geo/skills/seo-opportunity-audit/agents/openai.yaml +0 -5
- package/dist/skillpacks/geo/skills/seo-opportunity-audit/references/action-contract.md +0 -62
- package/dist/skillpacks/geo/skills/seo-opportunity-audit/scripts/seo-toolkit.mjs +0 -248
- package/dist/skillpacks/geo/skills/seo-page-diagnosis/SKILL.md +0 -56
- package/dist/skillpacks/geo/skills/seo-page-diagnosis/agents/openai.yaml +0 -5
- package/dist/skillpacks/geo/skills/seo-page-diagnosis/references/page-checks.md +0 -38
- package/dist/skillpacks/geo/skills/seo-serp-opportunity-research/SKILL.md +0 -66
- package/dist/skillpacks/geo/skills/seo-serp-opportunity-research/agents/openai.yaml +0 -5
- package/dist/skillpacks/geo/skills/seo-serp-opportunity-research/references/page-type-taxonomy.md +0 -40
- package/dist/skillpacks/geo/skills/seo-serp-opportunity-research/references/serp-methodology.md +0 -38
- package/dist/skillpacks/geo/skills/seo-technical-diagnosis/SKILL.md +0 -64
- package/dist/skillpacks/geo/skills/seo-technical-diagnosis/agents/openai.yaml +0 -5
- package/dist/skillpacks/geo/skills/seo-technical-diagnosis/references/checks.md +0 -58
- package/dist/skillpacks/geo/skills/third-party-diagnosis/SKILL.md +0 -54
- package/dist/skillpacks/geo/skills/third-party-diagnosis/agents/openai.yaml +0 -4
- package/dist/skillpacks/geo/skills/third-party-diagnosis/references/diagnosis-contract.md +0 -111
- package/dist/skillpacks/geo/skills/third-party-diagnosis/references/workflow.md +0 -174
- package/dist/skillpacks/geo/skills/third-party-execution-planning/SKILL.md +0 -64
- package/dist/skillpacks/geo/skills/third-party-execution-planning/agents/openai.yaml +0 -4
- package/dist/skillpacks/geo/skills/third-party-execution-planning/references/execution-contract.md +0 -90
- package/dist/skillpacks/geo/skills/third-party-execution-planning/references/non-social-surface-playbooks.md +0 -123
- package/dist/skillpacks/geo/skills/third-party-execution-planning/references/reddit-rules.md +0 -69
- package/dist/skillpacks/geo/skills/third-party-execution-planning/references/social-platform-playbooks.md +0 -59
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
# SEO Action Contract
|
|
2
|
-
|
|
3
|
-
Use this contract for all SEO diagnosis and opportunity outputs.
|
|
4
|
-
|
|
5
|
-
## Finding
|
|
6
|
-
|
|
7
|
-
```json
|
|
8
|
-
{
|
|
9
|
-
"id": "stable-local-id",
|
|
10
|
-
"sourceSkill": "seo-technical-diagnosis",
|
|
11
|
-
"findingType": "crawlability",
|
|
12
|
-
"target": {
|
|
13
|
-
"url": "https://example.com/page",
|
|
14
|
-
"pageId": "optional",
|
|
15
|
-
"intentId": "optional",
|
|
16
|
-
"queryCluster": "optional"
|
|
17
|
-
},
|
|
18
|
-
"summary": "Concise finding",
|
|
19
|
-
"evidence": [
|
|
20
|
-
{
|
|
21
|
-
"type": "crawl|html|rendered_html|dfs|cms|serp|firecrawl|product",
|
|
22
|
-
"source": "tool, file, or URL",
|
|
23
|
-
"claim": "What this evidence proves"
|
|
24
|
-
}
|
|
25
|
-
],
|
|
26
|
-
"confidence": "confirmed|likely|hypothesis",
|
|
27
|
-
"severity": "critical|high|medium|low|info"
|
|
28
|
-
}
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## Action Candidate
|
|
32
|
-
|
|
33
|
-
```json
|
|
34
|
-
{
|
|
35
|
-
"sourceSkill": "seo-serp-opportunity-research",
|
|
36
|
-
"opportunityType": "missing_page|page_type_mismatch|content_gap|technical_fix|internal_link|ecommerce_enrichment",
|
|
37
|
-
"target": {},
|
|
38
|
-
"recommendedAction": "Specific action",
|
|
39
|
-
"expectedOutcome": "Observable improvement",
|
|
40
|
-
"impact": "high|medium|low",
|
|
41
|
-
"effort": "high|medium|low",
|
|
42
|
-
"priority": "critical|high|medium|low",
|
|
43
|
-
"confidence": "confirmed|likely|hypothesis",
|
|
44
|
-
"canAutoApply": false,
|
|
45
|
-
"requiresThirdPartyExecution": false,
|
|
46
|
-
"verificationMethod": "How to verify after apply",
|
|
47
|
-
"evidence": [],
|
|
48
|
-
"actionFingerprint": "sha256..."
|
|
49
|
-
}
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## Rules
|
|
53
|
-
|
|
54
|
-
- `confirmed`: direct evidence proves the condition.
|
|
55
|
-
- `likely`: multiple indirect signals support it.
|
|
56
|
-
- `hypothesis`: plausible but needs more evidence before execution.
|
|
57
|
-
- `canAutoApply=true` only when Anymorph can perform the mutation through CMS,
|
|
58
|
-
page generation, or managed action tooling.
|
|
59
|
-
- Third-party tasks must route through third-party execution planning.
|
|
60
|
-
- Duplicate actions share the same normalized target, operation, and payload
|
|
61
|
-
fingerprint and should be merged.
|
|
62
|
-
|
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
3
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
|
-
import { dirname, join, resolve } from "node:path";
|
|
5
|
-
|
|
6
|
-
const command = process.argv[2];
|
|
7
|
-
const args = parseArgs(process.argv.slice(3));
|
|
8
|
-
|
|
9
|
-
try {
|
|
10
|
-
const result = await run(command, args);
|
|
11
|
-
if (result !== undefined) console.log(JSON.stringify(result, null, 2));
|
|
12
|
-
} catch (err) {
|
|
13
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
14
|
-
console.error(message);
|
|
15
|
-
process.exit(1);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async function run(mode, input) {
|
|
19
|
-
if (mode === "init-artifact") return initArtifact(input);
|
|
20
|
-
if (mode === "write-json") return writeJson(input);
|
|
21
|
-
if (mode === "fingerprint") return fingerprint(input);
|
|
22
|
-
if (mode === "dfs-request") return dfsRequest(input);
|
|
23
|
-
if (mode === "dfs-call") return dfsCall(input);
|
|
24
|
-
if (mode === "dfs-normalize") return dfsNormalize(input);
|
|
25
|
-
if (mode === "validate-artifact") return validateArtifact(input);
|
|
26
|
-
throw new Error("Usage: seo-toolkit.mjs <init-artifact|write-json|fingerprint|dfs-request|dfs-normalize|validate-artifact>");
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function parseArgs(argv) {
|
|
30
|
-
const out = { repo: "." };
|
|
31
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
32
|
-
const arg = argv[i];
|
|
33
|
-
if (arg === "--skill") out.skill = requireValue(argv, ++i, arg);
|
|
34
|
-
else if (arg === "--run-id") out.runId = requireValue(argv, ++i, arg);
|
|
35
|
-
else if (arg === "--repo") out.repo = requireValue(argv, ++i, arg);
|
|
36
|
-
else if (arg === "--name") out.name = requireValue(argv, ++i, arg);
|
|
37
|
-
else if (arg === "--file") out.file = requireValue(argv, ++i, arg);
|
|
38
|
-
else if (arg === "--json") out.json = requireValue(argv, ++i, arg);
|
|
39
|
-
else if (arg === "--endpoint") out.endpoint = requireValue(argv, ++i, arg);
|
|
40
|
-
else if (arg === "--params") out.params = requireValue(argv, ++i, arg);
|
|
41
|
-
else if (arg === "--path") out.path = requireValue(argv, ++i, arg);
|
|
42
|
-
else if (arg === "--payload") out.payload = requireValue(argv, ++i, arg);
|
|
43
|
-
else throw new Error(`Unknown argument: ${arg}`);
|
|
44
|
-
}
|
|
45
|
-
return out;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function requireValue(argv, index, flag) {
|
|
49
|
-
const value = argv[index];
|
|
50
|
-
if (!value || value.startsWith("--")) throw new Error(`${flag} requires a value`);
|
|
51
|
-
return value;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
async function initArtifact(input) {
|
|
55
|
-
const skill = required(input.skill, "--skill");
|
|
56
|
-
const runId = required(input.runId, "--run-id");
|
|
57
|
-
const repo = resolve(input.repo ?? ".");
|
|
58
|
-
const dir = join(repo, "agent", "seo", skill, runId);
|
|
59
|
-
await mkdir(dir, { recursive: true });
|
|
60
|
-
const manifest = {
|
|
61
|
-
skill,
|
|
62
|
-
runId,
|
|
63
|
-
createdAt: new Date().toISOString(),
|
|
64
|
-
files: [],
|
|
65
|
-
};
|
|
66
|
-
await writeFile(join(dir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`);
|
|
67
|
-
return { ok: true, dir };
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async function writeJson(input) {
|
|
71
|
-
const skill = required(input.skill, "--skill");
|
|
72
|
-
const runId = required(input.runId, "--run-id");
|
|
73
|
-
const name = required(input.name, "--name");
|
|
74
|
-
const repo = resolve(input.repo ?? ".");
|
|
75
|
-
const dir = join(repo, "agent", "seo", skill, runId);
|
|
76
|
-
await mkdir(dir, { recursive: true });
|
|
77
|
-
const payload = input.json ? JSON.parse(input.json) : JSON.parse(await readStdin());
|
|
78
|
-
const path = join(dir, name.endsWith(".json") ? name : `${name}.json`);
|
|
79
|
-
await writeFile(path, `${JSON.stringify(payload, null, 2)}\n`);
|
|
80
|
-
return { ok: true, path };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function fingerprint(input) {
|
|
84
|
-
const payload = input.json ? JSON.parse(input.json) : input;
|
|
85
|
-
const canonical = stableStringify(payload);
|
|
86
|
-
return {
|
|
87
|
-
fingerprint: createHash("sha256").update(canonical).digest("hex"),
|
|
88
|
-
canonical,
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function dfsRequest(input) {
|
|
93
|
-
const endpoint = required(input.endpoint, "--endpoint");
|
|
94
|
-
const params = input.params ? JSON.parse(input.params) : {};
|
|
95
|
-
const cost = estimateDfsCost(endpoint, params);
|
|
96
|
-
return {
|
|
97
|
-
endpoint,
|
|
98
|
-
mcpTool: endpointToMcpTool(endpoint),
|
|
99
|
-
params,
|
|
100
|
-
estimatedCostUsd: cost,
|
|
101
|
-
approvalStatus: cost >= 0.25 ? "needs_approval" : "approved",
|
|
102
|
-
notes: [
|
|
103
|
-
"Save this request next to the skill artifact before calling DFS.",
|
|
104
|
-
"After the MCP/API call, pipe the raw response through dfs-normalize.",
|
|
105
|
-
],
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
async function dfsCall(input) {
|
|
110
|
-
const apiPath = required(input.path, "--path");
|
|
111
|
-
const payload = input.payload ? JSON.parse(input.payload) : JSON.parse(await readStdin());
|
|
112
|
-
const login = process.env.DATAFORSEO_LOGIN || process.env.DATAFORSEO_USERNAME;
|
|
113
|
-
const password = process.env.DATAFORSEO_PASSWORD;
|
|
114
|
-
if (!login || !password) {
|
|
115
|
-
throw new Error("DATAFORSEO_LOGIN/DATAFORSEO_USERNAME and DATAFORSEO_PASSWORD are required for dfs-call");
|
|
116
|
-
}
|
|
117
|
-
if (!apiPath.startsWith("/v3/")) throw new Error("--path must start with /v3/");
|
|
118
|
-
const auth = Buffer.from(`${login}:${password}`).toString("base64");
|
|
119
|
-
const response = await fetch(`https://api.dataforseo.com${apiPath}`, {
|
|
120
|
-
method: "POST",
|
|
121
|
-
headers: {
|
|
122
|
-
Authorization: `Basic ${auth}`,
|
|
123
|
-
"Content-Type": "application/json",
|
|
124
|
-
},
|
|
125
|
-
body: JSON.stringify(payload),
|
|
126
|
-
});
|
|
127
|
-
const body = await response.text();
|
|
128
|
-
if (!response.ok) {
|
|
129
|
-
throw new Error(`DataForSEO ${response.status}: ${body.slice(0, 1000)}`);
|
|
130
|
-
}
|
|
131
|
-
return JSON.parse(body);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
async function dfsNormalize(input) {
|
|
135
|
-
const endpoint = required(input.endpoint, "--endpoint");
|
|
136
|
-
const raw = JSON.parse(await readStdin());
|
|
137
|
-
return {
|
|
138
|
-
endpoint,
|
|
139
|
-
normalizedAt: new Date().toISOString(),
|
|
140
|
-
items: extractItems(raw).map((item) => normalizeDfsItem(endpoint, item)),
|
|
141
|
-
rawTaskStatus: extractStatus(raw),
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
async function validateArtifact(input) {
|
|
146
|
-
const file = required(input.file, "--file");
|
|
147
|
-
const parsed = JSON.parse(await readFile(file, "utf8"));
|
|
148
|
-
const problems = [];
|
|
149
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) problems.push("artifact must be an object");
|
|
150
|
-
if (!parsed.runId && !parsed.skill && !parsed.actions && !parsed.findings && !parsed.opportunities) {
|
|
151
|
-
problems.push("artifact should include runId/skill or findings/opportunities/actions");
|
|
152
|
-
}
|
|
153
|
-
return { ok: problems.length === 0, file, problems };
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function required(value, name) {
|
|
157
|
-
if (!value) throw new Error(`${name} is required`);
|
|
158
|
-
return value;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
async function readStdin() {
|
|
162
|
-
const chunks = [];
|
|
163
|
-
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
164
|
-
return Buffer.concat(chunks).toString("utf8");
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function stableStringify(value) {
|
|
168
|
-
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
169
|
-
if (value && typeof value === "object") {
|
|
170
|
-
return `{${Object.keys(value)
|
|
171
|
-
.sort()
|
|
172
|
-
.map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`)
|
|
173
|
-
.join(",")}}`;
|
|
174
|
-
}
|
|
175
|
-
return JSON.stringify(value);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function estimateDfsCost(endpoint, params) {
|
|
179
|
-
const count =
|
|
180
|
-
Number(params.count) ||
|
|
181
|
-
(params.keywords && Array.isArray(params.keywords) ? params.keywords.length : 1) ||
|
|
182
|
-
1;
|
|
183
|
-
if (endpoint.includes("serp")) return Math.ceil(count / 100) * 0.002;
|
|
184
|
-
if (endpoint.includes("keyword") || endpoint.includes("related")) return Math.max(0.005, count * 0.0001);
|
|
185
|
-
if (endpoint.includes("competitor") || endpoint.includes("intersection")) return 0.01;
|
|
186
|
-
if (endpoint.includes("on_page") || endpoint.includes("lighthouse")) return 0.01;
|
|
187
|
-
return 0.005;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function endpointToMcpTool(endpoint) {
|
|
191
|
-
const aliases = {
|
|
192
|
-
serp_organic_live_advanced: "mcp__dataforseo__serp_organic_live_advanced",
|
|
193
|
-
dataforseo_labs_google_related_keywords: "mcp__dataforseo__dataforseo_labs_google_related_keywords",
|
|
194
|
-
dataforseo_labs_google_keyword_overview: "mcp__dataforseo__dataforseo_labs_google_keyword_overview",
|
|
195
|
-
dataforseo_labs_google_competitors_domain: "mcp__dataforseo__dataforseo_labs_google_competitors_domain",
|
|
196
|
-
dataforseo_labs_google_domain_intersection: "mcp__dataforseo__dataforseo_labs_google_domain_intersection",
|
|
197
|
-
dataforseo_labs_google_page_intersection: "mcp__dataforseo__dataforseo_labs_google_page_intersection",
|
|
198
|
-
dataforseo_labs_google_serp_competitors: "mcp__dataforseo__dataforseo_labs_google_serp_competitors",
|
|
199
|
-
anymorph_dataforseo_research: "mcp__anymorph__dataforseo_research",
|
|
200
|
-
anymorph_search_keywords: "mcp__anymorph__search_keywords",
|
|
201
|
-
anymorph_serp_snapshot: "mcp__anymorph__serp_snapshot",
|
|
202
|
-
anymorph_list_seo_opportunities: "mcp__anymorph__list_seo_opportunities",
|
|
203
|
-
};
|
|
204
|
-
return aliases[endpoint] ?? `mcp__dataforseo__${endpoint}`;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function extractItems(raw) {
|
|
208
|
-
if (Array.isArray(raw)) return raw;
|
|
209
|
-
if (Array.isArray(raw.items)) return raw.items;
|
|
210
|
-
if (Array.isArray(raw.result)) return raw.result.flatMap((r) => r.items ?? r.result ?? r);
|
|
211
|
-
if (Array.isArray(raw.tasks)) return raw.tasks.flatMap((task) => task.result ?? task.items ?? []);
|
|
212
|
-
if (raw.data && Array.isArray(raw.data.items)) return raw.data.items;
|
|
213
|
-
return [raw];
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function extractStatus(raw) {
|
|
217
|
-
if (raw.status_message || raw.status_code) {
|
|
218
|
-
return { statusCode: raw.status_code, statusMessage: raw.status_message };
|
|
219
|
-
}
|
|
220
|
-
if (Array.isArray(raw.tasks)) {
|
|
221
|
-
return raw.tasks.map((task) => ({
|
|
222
|
-
statusCode: task.status_code,
|
|
223
|
-
statusMessage: task.status_message,
|
|
224
|
-
}));
|
|
225
|
-
}
|
|
226
|
-
return null;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function normalizeDfsItem(endpoint, item) {
|
|
230
|
-
const keyword = item.keyword ?? item.keyword_data?.keyword ?? item.keyword_info?.keyword ?? item.query;
|
|
231
|
-
const keywordInfo = item.keyword_info ?? item.keyword_data?.keyword_info ?? {};
|
|
232
|
-
const serp = item.serp_item ?? item.first_domain_serp_element ?? item.second_domain_serp_element ?? item;
|
|
233
|
-
return {
|
|
234
|
-
endpoint,
|
|
235
|
-
keyword,
|
|
236
|
-
searchVolume: keywordInfo.search_volume ?? item.search_volume ?? null,
|
|
237
|
-
cpc: keywordInfo.cpc ?? item.cpc ?? null,
|
|
238
|
-
competition: keywordInfo.competition ?? keywordInfo.competition_level ?? item.competition ?? null,
|
|
239
|
-
intent: item.search_intent_info?.main_intent ?? item.intent ?? null,
|
|
240
|
-
position: serp.rank_absolute ?? serp.position ?? serp.avg_position ?? item.avg_position ?? null,
|
|
241
|
-
url: serp.url ?? item.url ?? null,
|
|
242
|
-
domain: serp.domain ?? item.domain ?? null,
|
|
243
|
-
title: serp.title ?? item.title ?? null,
|
|
244
|
-
description: serp.description ?? item.description ?? null,
|
|
245
|
-
serpFeatures: item.serp_features ?? serp.serp_features ?? [],
|
|
246
|
-
raw: item,
|
|
247
|
-
};
|
|
248
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: seo-page-diagnosis
|
|
3
|
-
description: Use when diagnosing one or more pages for title, meta description, headings, content depth, page intent fit, schema, images, internal links, CTAs, or page-level SEO action candidates.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# SEO Page Diagnosis
|
|
7
|
-
|
|
8
|
-
Use this skill for page-level SEO diagnosis. It is not a site infrastructure
|
|
9
|
-
audit. It inspects the page as a search result candidate and conversion surface.
|
|
10
|
-
|
|
11
|
-
Follow the `claude-seo` single-page pattern: on-page SEO, content quality,
|
|
12
|
-
technical page elements, schema, images, and practical recommendations.
|
|
13
|
-
|
|
14
|
-
## Artifact
|
|
15
|
-
|
|
16
|
-
Write outputs under:
|
|
17
|
-
|
|
18
|
-
```text
|
|
19
|
-
agent/seo/seo-page-diagnosis/{runId}/
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
Initialize with the shared toolkit from `$seo-opportunity-audit`.
|
|
23
|
-
|
|
24
|
-
## References
|
|
25
|
-
|
|
26
|
-
- `references/page-checks.md` for page scoring dimensions.
|
|
27
|
-
- `../seo-opportunity-audit/references/action-contract.md` for output shape.
|
|
28
|
-
|
|
29
|
-
## Workflow
|
|
30
|
-
|
|
31
|
-
1. Select target pages from run context, candidate URLs, weak intents, or SERP
|
|
32
|
-
opportunity outputs.
|
|
33
|
-
2. Extract title, meta description, H1, heading hierarchy, canonical, robots,
|
|
34
|
-
schema, image alt/dimensions, internal links, external links, CTA structure,
|
|
35
|
-
word count, and freshness signals.
|
|
36
|
-
3. Compare page format with the intended query or intent if provided.
|
|
37
|
-
4. Produce page-specific actions such as title rewrite, meta rewrite, section
|
|
38
|
-
addition, FAQ-like answer block, proof block, image alt update, schema
|
|
39
|
-
completion, or internal link placement.
|
|
40
|
-
5. Separate auto-applicable CMS edits from third-party or source-code-only fixes.
|
|
41
|
-
|
|
42
|
-
## Output Files
|
|
43
|
-
|
|
44
|
-
- `pages.json`: page evidence and scorecards.
|
|
45
|
-
- `findings.json`: findings by page.
|
|
46
|
-
- `actions.json`: page-level action candidates.
|
|
47
|
-
- `rationale.md`: concise explanation.
|
|
48
|
-
|
|
49
|
-
## Guardrails
|
|
50
|
-
|
|
51
|
-
- Do not claim ranking opportunity without SERP/DFS evidence.
|
|
52
|
-
- Do not recommend keyword stuffing.
|
|
53
|
-
- Do not recommend FAQ schema for Google rich result value unless the site type
|
|
54
|
-
qualifies; FAQ-like content can still help AI answerability.
|
|
55
|
-
- If rendered content is unavailable, label findings as limited.
|
|
56
|
-
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# Page Checks
|
|
2
|
-
|
|
3
|
-
## On-Page SEO
|
|
4
|
-
|
|
5
|
-
- title is unique, specific, and aligned with primary intent.
|
|
6
|
-
- meta description summarizes value and supports click intent.
|
|
7
|
-
- exactly one clear H1 in most cases.
|
|
8
|
-
- headings form a logical outline without skipped meaning.
|
|
9
|
-
- URL is short, stable, and descriptive.
|
|
10
|
-
- canonical and robots directives match the desired index state.
|
|
11
|
-
|
|
12
|
-
## Content Quality
|
|
13
|
-
|
|
14
|
-
- page answers the primary question directly.
|
|
15
|
-
- content depth matches page type.
|
|
16
|
-
- claims include proof, examples, data, screenshots, customers, or source links.
|
|
17
|
-
- author, brand, or product expertise is visible where relevant.
|
|
18
|
-
- stale content has last-updated or freshness risk.
|
|
19
|
-
|
|
20
|
-
## AI Answerability
|
|
21
|
-
|
|
22
|
-
- include self-contained answer blocks.
|
|
23
|
-
- define entities clearly.
|
|
24
|
-
- state comparisons, criteria, pros/cons, and facts in extractable language.
|
|
25
|
-
- avoid vague marketing-only claims.
|
|
26
|
-
|
|
27
|
-
## Images and Media
|
|
28
|
-
|
|
29
|
-
- important images have descriptive alt text.
|
|
30
|
-
- large hero/product images have dimensions and modern formats where possible.
|
|
31
|
-
- visual evidence supports the page purpose.
|
|
32
|
-
|
|
33
|
-
## Links
|
|
34
|
-
|
|
35
|
-
- internal links point to next useful pages.
|
|
36
|
-
- anchors are descriptive and natural.
|
|
37
|
-
- external links support factual claims when needed.
|
|
38
|
-
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: seo-serp-opportunity-research
|
|
3
|
-
description: Use when researching SEO opportunities from DataForSEO SERPs, keyword gaps, SERP overlap, page-type mismatch, competitor pages, query clusters, or new page opportunities.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# SEO SERP Opportunity Research
|
|
7
|
-
|
|
8
|
-
Use this skill to find search opportunities that require live SERP or keyword
|
|
9
|
-
evidence. It combines `claude-seo` patterns from `seo-cluster`, `seo-sxo`,
|
|
10
|
-
`seo-dataforseo`, and competitor-page guidance.
|
|
11
|
-
|
|
12
|
-
DFS is the primary data source. Firecrawl is only for reading specific pages
|
|
13
|
-
after DFS identifies competitors or candidate URLs.
|
|
14
|
-
|
|
15
|
-
## Artifact
|
|
16
|
-
|
|
17
|
-
Write outputs under:
|
|
18
|
-
|
|
19
|
-
```text
|
|
20
|
-
agent/seo/seo-serp-opportunity-research/{runId}/
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
Use the shared toolkit for DFS request artifacts and normalization.
|
|
24
|
-
|
|
25
|
-
## References
|
|
26
|
-
|
|
27
|
-
- `references/serp-methodology.md` for DFS/SERP workflow.
|
|
28
|
-
- `references/page-type-taxonomy.md` for SXO classification.
|
|
29
|
-
- `../seo-opportunity-audit/references/action-contract.md` for output shape.
|
|
30
|
-
|
|
31
|
-
## Workflow
|
|
32
|
-
|
|
33
|
-
1. Gather seed terms from intents, workspace category, product catalog, existing
|
|
34
|
-
pages, GSC/SEO opportunity MCP results, and user input.
|
|
35
|
-
2. Use the SEO toolkit to create DFS request artifacts for related keywords,
|
|
36
|
-
keyword overview, SERP competitors, ranked/intersection/page intersection, or
|
|
37
|
-
organic SERP snapshots.
|
|
38
|
-
3. Normalize DFS responses before analysis.
|
|
39
|
-
4. Cluster queries by SERP overlap and intent.
|
|
40
|
-
5. Classify the dominant page type for each cluster.
|
|
41
|
-
6. Detect opportunities:
|
|
42
|
-
- missing page.
|
|
43
|
-
- page-type mismatch.
|
|
44
|
-
- content gap.
|
|
45
|
-
- comparison/alternatives/best-of opportunity.
|
|
46
|
-
- weak existing page needing refresh.
|
|
47
|
-
- internal-link support opportunity.
|
|
48
|
-
7. Use Firecrawl to inspect only the top competitor pages needed for evidence.
|
|
49
|
-
8. Emit opportunities and action candidates.
|
|
50
|
-
|
|
51
|
-
## Output Files
|
|
52
|
-
|
|
53
|
-
- `dfs-requests.json`: planned DFS calls and cost tiers.
|
|
54
|
-
- `dfs-normalized.json`: normalized DFS evidence.
|
|
55
|
-
- `clusters.json`: query clusters and page-type consensus.
|
|
56
|
-
- `opportunities.json`: ranked opportunities.
|
|
57
|
-
- `actions.json`: action candidates.
|
|
58
|
-
- `rationale.md`: concise decision record.
|
|
59
|
-
|
|
60
|
-
## Confidence Rules
|
|
61
|
-
|
|
62
|
-
- `confirmed`: DFS SERP/keyword evidence and page inventory agree.
|
|
63
|
-
- `likely`: DFS evidence is strong but page inventory or competitor scrape is
|
|
64
|
-
incomplete.
|
|
65
|
-
- `hypothesis`: keyword or SERP signal exists but needs validation before action.
|
|
66
|
-
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
interface:
|
|
2
|
-
display_name: "SEO SERP Opportunity Research"
|
|
3
|
-
short_description: "Find DFS-backed keyword, SERP, and page-type opportunities."
|
|
4
|
-
default_prompt: "Use $seo-serp-opportunity-research to find DFS-backed SERP, keyword, cluster, and competitor page opportunities."
|
|
5
|
-
|
package/dist/skillpacks/geo/skills/seo-serp-opportunity-research/references/page-type-taxonomy.md
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# Page-Type Taxonomy
|
|
2
|
-
|
|
3
|
-
Use this for SXO-style page-type mismatch detection.
|
|
4
|
-
|
|
5
|
-
## Common Types
|
|
6
|
-
|
|
7
|
-
- `homepage`
|
|
8
|
-
- `product`
|
|
9
|
-
- `pricing`
|
|
10
|
-
- `feature`
|
|
11
|
-
- `integration`
|
|
12
|
-
- `comparison`
|
|
13
|
-
- `alternatives`
|
|
14
|
-
- `best_of`
|
|
15
|
-
- `category`
|
|
16
|
-
- `collection`
|
|
17
|
-
- `product_detail`
|
|
18
|
-
- `guide`
|
|
19
|
-
- `how_to`
|
|
20
|
-
- `glossary`
|
|
21
|
-
- `case_study`
|
|
22
|
-
- `review`
|
|
23
|
-
- `tool`
|
|
24
|
-
- `calculator`
|
|
25
|
-
- `directory`
|
|
26
|
-
- `local_service`
|
|
27
|
-
|
|
28
|
-
## Mismatch Examples
|
|
29
|
-
|
|
30
|
-
- SERP shows product/category pages, target is a blog post: high mismatch.
|
|
31
|
-
- SERP shows comparison/listicle pages, target is a product page: high mismatch.
|
|
32
|
-
- SERP shows local pack/service pages, target lacks local signals: medium/high.
|
|
33
|
-
- SERP is fragmented: opportunity for differentiation, not a hard failure.
|
|
34
|
-
|
|
35
|
-
## Consensus
|
|
36
|
-
|
|
37
|
-
- Strong consensus: one type is above 60% of top organic results.
|
|
38
|
-
- Mixed consensus: top type is 40-60%.
|
|
39
|
-
- Fragmented: no type reaches 40%.
|
|
40
|
-
|
package/dist/skillpacks/geo/skills/seo-serp-opportunity-research/references/serp-methodology.md
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# SERP Opportunity Methodology
|
|
2
|
-
|
|
3
|
-
## DFS-First Data Flow
|
|
4
|
-
|
|
5
|
-
1. Expand seeds with related keywords and keyword overview.
|
|
6
|
-
2. Fetch SERP competitors or organic SERP snapshots for candidate clusters.
|
|
7
|
-
3. Use domain/page intersection to find competitor keywords the workspace lacks.
|
|
8
|
-
4. Normalize every DFS response before reasoning.
|
|
9
|
-
5. Scrape competitor pages only after a cluster is worth inspecting.
|
|
10
|
-
|
|
11
|
-
## SERP Overlap
|
|
12
|
-
|
|
13
|
-
Group keywords by shared top results:
|
|
14
|
-
|
|
15
|
-
- 7-10 shared URLs: same target page.
|
|
16
|
-
- 4-6 shared URLs: same cluster.
|
|
17
|
-
- 2-3 shared URLs: adjacent clusters with cross-links.
|
|
18
|
-
- 0-1 shared URLs: separate opportunity.
|
|
19
|
-
|
|
20
|
-
Use DFS SERP data when available. If DFS is unavailable, mark results as
|
|
21
|
-
`hypothesis` and explain the limitation.
|
|
22
|
-
|
|
23
|
-
## Opportunity Types
|
|
24
|
-
|
|
25
|
-
- `missing_page`: no matching owned/GEO page exists for a valuable cluster.
|
|
26
|
-
- `page_type_mismatch`: existing page type differs from SERP consensus.
|
|
27
|
-
- `content_gap`: page exists but lacks expected sections/proof/schema/media.
|
|
28
|
-
- `comparison_page_gap`: SERP expects vs/alternatives/best-of content.
|
|
29
|
-
- `refresh_existing_page`: page exists but freshness/depth/intent is weak.
|
|
30
|
-
- `internal_link_support`: page exists but needs stronger internal links.
|
|
31
|
-
|
|
32
|
-
## Competitor Page Rules
|
|
33
|
-
|
|
34
|
-
- Use public, verifiable competitor claims only.
|
|
35
|
-
- Separate fact tables from interpretation.
|
|
36
|
-
- Avoid unsupported negative competitor claims.
|
|
37
|
-
- Include last-updated recommendation for comparison pages.
|
|
38
|
-
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: seo-technical-diagnosis
|
|
3
|
-
description: Use when diagnosing technical SEO blockers, crawlability, indexability, robots, sitemap, canonicals, rendering, Core Web Vitals, security headers, AI crawler access, or technical action candidates.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# SEO Technical Diagnosis
|
|
7
|
-
|
|
8
|
-
Use this skill for site and URL-level technical SEO evidence.
|
|
9
|
-
|
|
10
|
-
This follows the `claude-seo` technical audit pattern: crawlability,
|
|
11
|
-
indexability, security, URL structure, mobile, CWV, structured data, JavaScript
|
|
12
|
-
rendering, IndexNow, and AI crawler management.
|
|
13
|
-
|
|
14
|
-
## Artifact
|
|
15
|
-
|
|
16
|
-
Write outputs under:
|
|
17
|
-
|
|
18
|
-
```text
|
|
19
|
-
agent/seo/seo-technical-diagnosis/{runId}/
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
Initialize with:
|
|
23
|
-
|
|
24
|
-
```bash
|
|
25
|
-
SEO_TOOLKIT=.agents/skills/seo-opportunity-audit/scripts/seo-toolkit.mjs
|
|
26
|
-
test -f "$SEO_TOOLKIT" || SEO_TOOLKIT=.claude/skills/seo-opportunity-audit/scripts/seo-toolkit.mjs
|
|
27
|
-
node "$SEO_TOOLKIT" init-artifact --skill seo-technical-diagnosis --run-id "$RUN_ID"
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## References
|
|
31
|
-
|
|
32
|
-
Read when needed:
|
|
33
|
-
|
|
34
|
-
- `references/checks.md` for technical check categories and severity rules.
|
|
35
|
-
- `../seo-opportunity-audit/references/action-contract.md` for output shape.
|
|
36
|
-
|
|
37
|
-
## Workflow
|
|
38
|
-
|
|
39
|
-
1. Start from known workspace URLs, sitemap URLs, crawl logs, and CMS inventory.
|
|
40
|
-
2. Check robots, sitemap, status codes, redirects, canonical, robots meta,
|
|
41
|
-
hreflang, title/meta availability, schema presence, JS rendering risk, mobile,
|
|
42
|
-
CWV/PSI/DFS on-page data if available, and AI crawler access.
|
|
43
|
-
3. Use DFS on-page/Lighthouse only through the SEO toolkit request/normalize flow.
|
|
44
|
-
4. Classify each issue by indexing risk, ranking risk, AI visibility risk, and
|
|
45
|
-
ability to auto-apply.
|
|
46
|
-
5. Produce `findings.json`, `actions.json`, `blocked.json`, and `rationale.md`.
|
|
47
|
-
|
|
48
|
-
## Output Rules
|
|
49
|
-
|
|
50
|
-
- `critical`: blocks indexing/crawling, severe canonical/noindex conflict, 5xx on
|
|
51
|
-
key pages, or AI/search crawler fully blocked when visibility is intended.
|
|
52
|
-
- `high`: major templates missing canonical/schema, important pages JS-only, bad
|
|
53
|
-
redirect chains, poor CWV with field/lab evidence.
|
|
54
|
-
- `medium`: optimization opportunity, partial crawler issue, incomplete sitemap,
|
|
55
|
-
missing schema on eligible pages.
|
|
56
|
-
- `low`: polish or monitoring.
|
|
57
|
-
|
|
58
|
-
Every action must include verification:
|
|
59
|
-
|
|
60
|
-
- recrawl URL.
|
|
61
|
-
- compare rendered and raw HTML.
|
|
62
|
-
- validate schema.
|
|
63
|
-
- rerun DFS/PSI/CWV check.
|
|
64
|
-
- inspect CMS/source diff when auto-applied.
|