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,303 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
|
-
import { homedir } from "node:os";
|
|
5
|
-
import { join, resolve } from "node:path";
|
|
6
|
-
|
|
7
|
-
const CONFIG_DIR = join(homedir(), ".anymorph");
|
|
8
|
-
const CRED_FILE = join(CONFIG_DIR, "credentials.json");
|
|
9
|
-
|
|
10
|
-
const command = process.argv[2];
|
|
11
|
-
const args = parseArgs(process.argv.slice(3));
|
|
12
|
-
|
|
13
|
-
try {
|
|
14
|
-
const result = await run(command, args);
|
|
15
|
-
if (result !== undefined) console.log(JSON.stringify(result, null, 2));
|
|
16
|
-
if (result && result.ok === false) process.exitCode = 1;
|
|
17
|
-
} catch (err) {
|
|
18
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
19
|
-
console.error(message);
|
|
20
|
-
process.exit(1);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async function run(mode, input) {
|
|
24
|
-
if (mode === "exa") return collectExa(input);
|
|
25
|
-
if (mode === "kb") return collectKb(input);
|
|
26
|
-
if (mode === "images") return collectImages(input);
|
|
27
|
-
if (mode === "all") return collectAll(input);
|
|
28
|
-
throw new Error("Usage: collect-page-sources.mjs <exa|kb|images|all> --run-id <id> --action-id <id> --workspace <id-or-domain> [--repo .] [--query text]");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function parseArgs(argv) {
|
|
32
|
-
const out = { repo: ".", limit: "10", imageLimit: "10" };
|
|
33
|
-
for (let i = 0; i < argv.length; i += 1) {
|
|
34
|
-
const arg = argv[i];
|
|
35
|
-
if (arg === "--repo") out.repo = requireValue(argv, ++i, arg);
|
|
36
|
-
else if (arg === "--run-id") out.runId = requireValue(argv, ++i, arg);
|
|
37
|
-
else if (arg === "--action-id") out.actionId = requireValue(argv, ++i, arg);
|
|
38
|
-
else if (arg === "--workspace") out.workspace = requireValue(argv, ++i, arg);
|
|
39
|
-
else if (arg === "--query") out.query = requireValue(argv, ++i, arg);
|
|
40
|
-
else if (arg === "--limit") out.limit = requireValue(argv, ++i, arg);
|
|
41
|
-
else if (arg === "--image-limit") out.imageLimit = requireValue(argv, ++i, arg);
|
|
42
|
-
else if (arg === "--type") out.type = 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 collectAll(input) {
|
|
55
|
-
const results = { exa: null, kb: null, images: null, failures: [] };
|
|
56
|
-
for (const [key, fn] of [
|
|
57
|
-
["exa", collectExa],
|
|
58
|
-
["kb", collectKb],
|
|
59
|
-
["images", collectImages],
|
|
60
|
-
]) {
|
|
61
|
-
try {
|
|
62
|
-
results[key] = await fn({ ...input, quiet: true });
|
|
63
|
-
} catch (err) {
|
|
64
|
-
results.failures.push({ source: key, error: err instanceof Error ? err.message : String(err) });
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
const ctx = await loadContext(input);
|
|
68
|
-
const sourcesDir = await ensureSourcesDir(ctx);
|
|
69
|
-
const sourcePack = {
|
|
70
|
-
schemaVersion: 1,
|
|
71
|
-
runId: ctx.runId,
|
|
72
|
-
actionId: ctx.actionId,
|
|
73
|
-
workspace: ctx.workspace,
|
|
74
|
-
query: ctx.query,
|
|
75
|
-
action: ctx.action,
|
|
76
|
-
generatedAt: new Date().toISOString(),
|
|
77
|
-
exa: results.exa?.summary ?? null,
|
|
78
|
-
exaSources: results.exa?.sources ?? [],
|
|
79
|
-
kb: results.kb?.summary ?? null,
|
|
80
|
-
kbResults: results.kb?.results ?? [],
|
|
81
|
-
images: results.images?.summary ?? null,
|
|
82
|
-
imageResults: results.images?.results ?? [],
|
|
83
|
-
failures: results.failures,
|
|
84
|
-
};
|
|
85
|
-
await writeJson(join(sourcesDir, "source-pack.json"), sourcePack);
|
|
86
|
-
return { ok: results.failures.length === 0, dir: sourcesDir, sourcePack: join(sourcesDir, "source-pack.json"), failures: results.failures };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async function collectExa(input) {
|
|
90
|
-
const apiKey = process.env.EXA_API_KEY;
|
|
91
|
-
if (!apiKey) throw new Error("EXA_API_KEY is required for Exa Deep Search");
|
|
92
|
-
const ctx = await loadContext(input);
|
|
93
|
-
const sourcesDir = await ensureSourcesDir(ctx);
|
|
94
|
-
const body = {
|
|
95
|
-
query: buildResearchPrompt(ctx),
|
|
96
|
-
type: input.type ?? "deep-reasoning",
|
|
97
|
-
contents: { highlights: true, summary: true },
|
|
98
|
-
};
|
|
99
|
-
const res = await fetch("https://api.exa.ai/search", {
|
|
100
|
-
method: "POST",
|
|
101
|
-
headers: {
|
|
102
|
-
"Content-Type": "application/json",
|
|
103
|
-
"x-api-key": apiKey,
|
|
104
|
-
},
|
|
105
|
-
body: JSON.stringify(body),
|
|
106
|
-
});
|
|
107
|
-
const text = await res.text();
|
|
108
|
-
if (!res.ok) throw new Error(`Exa search failed (${res.status}): ${text.slice(0, 1000)}`);
|
|
109
|
-
const data = JSON.parse(text);
|
|
110
|
-
const markdown = renderExaMarkdown(ctx, data);
|
|
111
|
-
await writeFile(join(sourcesDir, "exa-research.md"), markdown);
|
|
112
|
-
await writeJson(join(sourcesDir, "exa-response.json"), data);
|
|
113
|
-
const summary = {
|
|
114
|
-
type: body.type,
|
|
115
|
-
resultCount: Array.isArray(data.results) ? data.results.length : 0,
|
|
116
|
-
hasOutput: Boolean(data.output),
|
|
117
|
-
costDollars: data.costDollars ?? null,
|
|
118
|
-
};
|
|
119
|
-
return {
|
|
120
|
-
ok: true,
|
|
121
|
-
dir: sourcesDir,
|
|
122
|
-
summary,
|
|
123
|
-
sources: (data.results ?? []).map((item) => ({
|
|
124
|
-
title: item.title ?? null,
|
|
125
|
-
url: item.url ?? null,
|
|
126
|
-
publishedDate: item.publishedDate ?? null,
|
|
127
|
-
})),
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
async function collectKb(input) {
|
|
132
|
-
const ctx = await loadContext(input);
|
|
133
|
-
const workspaceId = await resolveWorkspaceId(ctx.workspace);
|
|
134
|
-
const sourcesDir = await ensureSourcesDir(ctx);
|
|
135
|
-
const params = new URLSearchParams({ query: ctx.query });
|
|
136
|
-
const data = await backendJson("GET", `/api/workspaces/${encodeURIComponent(workspaceId)}/knowledge-base/search?${params}`);
|
|
137
|
-
await writeJson(join(sourcesDir, "kb-results.json"), data);
|
|
138
|
-
const summary = {
|
|
139
|
-
workspaceId,
|
|
140
|
-
resultCount: Array.isArray(data.results) ? data.results.length : 0,
|
|
141
|
-
};
|
|
142
|
-
return { ok: true, dir: sourcesDir, summary, results: data.results ?? [] };
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
async function collectImages(input) {
|
|
146
|
-
const ctx = await loadContext(input);
|
|
147
|
-
const workspaceId = await resolveWorkspaceId(ctx.workspace);
|
|
148
|
-
const sourcesDir = await ensureSourcesDir(ctx);
|
|
149
|
-
const params = new URLSearchParams({
|
|
150
|
-
q: ctx.query,
|
|
151
|
-
topK: String(Number(input.imageLimit ?? input.limit ?? 10)),
|
|
152
|
-
});
|
|
153
|
-
const data = await backendJson("GET", `/api/workspaces/${encodeURIComponent(workspaceId)}/image-assets/search?${params}`);
|
|
154
|
-
await writeJson(join(sourcesDir, "image-results.json"), data);
|
|
155
|
-
const rows = Array.isArray(data.results) ? data.results : Array.isArray(data.items) ? data.items : [];
|
|
156
|
-
const summary = { workspaceId, resultCount: rows.length };
|
|
157
|
-
return { ok: true, dir: sourcesDir, summary, results: rows };
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
async function loadContext(input) {
|
|
161
|
-
const repo = resolve(input.repo ?? ".");
|
|
162
|
-
const runId = required(input.runId, "--run-id");
|
|
163
|
-
const actionId = required(input.actionId, "--action-id");
|
|
164
|
-
const runDir = join(repo, "agent", "runs", runId);
|
|
165
|
-
const actions = await readJson(join(runDir, "actions.json"));
|
|
166
|
-
const context = await readJson(join(runDir, "context.json")).catch(() => null);
|
|
167
|
-
const action = actions.actions?.find((item) => item.id === actionId);
|
|
168
|
-
if (!action) throw new Error(`Action ${actionId} not found in agent/runs/${runId}/actions.json`);
|
|
169
|
-
if (action.assetType !== "geo_page") throw new Error(`Action ${actionId} is ${action.assetType}; geo-page-writer only supports geo_page actions`);
|
|
170
|
-
const workspace = input.workspace ?? context?.workspaceId ?? context?.workspaceDomain;
|
|
171
|
-
if (!workspace) throw new Error("--workspace is required when run context does not include workspaceId/workspaceDomain");
|
|
172
|
-
const query = input.query ?? buildDefaultQuery(action, context);
|
|
173
|
-
return { repo, runId, actionId, runDir, action, context, workspace, query };
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function buildDefaultQuery(action, context) {
|
|
177
|
-
const target = action.target ?? {};
|
|
178
|
-
return [
|
|
179
|
-
target.title,
|
|
180
|
-
action.objective,
|
|
181
|
-
action.changeBrief,
|
|
182
|
-
action.reason,
|
|
183
|
-
Array.isArray(target.queryCluster) ? target.queryCluster.join(", ") : "",
|
|
184
|
-
context?.workspaceDomain ? `brand domain: ${context.workspaceDomain}` : "",
|
|
185
|
-
]
|
|
186
|
-
.filter(Boolean)
|
|
187
|
-
.join("\n");
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function buildResearchPrompt(ctx) {
|
|
191
|
-
return [
|
|
192
|
-
"Research this GEO page before MDX writing.",
|
|
193
|
-
"Return concise findings, source URLs, caveats, and claims that are safe to cite.",
|
|
194
|
-
"",
|
|
195
|
-
`Workspace: ${ctx.workspace}`,
|
|
196
|
-
`Run: ${ctx.runId}`,
|
|
197
|
-
`Action: ${ctx.actionId}`,
|
|
198
|
-
`Objective: ${ctx.action.objective}`,
|
|
199
|
-
`Reason: ${ctx.action.reason}`,
|
|
200
|
-
`Brief: ${ctx.action.changeBrief}`,
|
|
201
|
-
`Expected outcome: ${ctx.action.expectedOutcome}`,
|
|
202
|
-
"",
|
|
203
|
-
`Research query:\n${ctx.query}`,
|
|
204
|
-
].join("\n");
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function renderExaMarkdown(ctx, data) {
|
|
208
|
-
const lines = [
|
|
209
|
-
`# Exa Research: ${ctx.action.target?.title ?? ctx.action.objective}`,
|
|
210
|
-
"",
|
|
211
|
-
`Run: ${ctx.runId}`,
|
|
212
|
-
`Action: ${ctx.actionId}`,
|
|
213
|
-
`Generated: ${new Date().toISOString()}`,
|
|
214
|
-
"",
|
|
215
|
-
"## Synthesized Output",
|
|
216
|
-
"",
|
|
217
|
-
renderOutputContent(data.output?.content),
|
|
218
|
-
"",
|
|
219
|
-
"## Grounding",
|
|
220
|
-
"",
|
|
221
|
-
...(Array.isArray(data.output?.grounding)
|
|
222
|
-
? data.output.grounding.map((item) => `- ${item.field ?? "output"}: ${(item.citations ?? []).map((c) => `${c.title ?? c.url} (${c.url})`).join("; ")}${item.confidence ? ` [${item.confidence}]` : ""}`)
|
|
223
|
-
: ["- none"]),
|
|
224
|
-
"",
|
|
225
|
-
"## Results",
|
|
226
|
-
"",
|
|
227
|
-
...(Array.isArray(data.results)
|
|
228
|
-
? data.results.map((item) => `- ${item.title ?? item.url}\n ${item.url}\n ${(item.highlights ?? []).slice(0, 3).join("\n ") || item.summary || ""}`)
|
|
229
|
-
: ["- none"]),
|
|
230
|
-
];
|
|
231
|
-
return `${lines.join("\n")}\n`;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function renderOutputContent(value) {
|
|
235
|
-
if (value == null) return "_No synthesized output returned._";
|
|
236
|
-
if (typeof value === "string") return value;
|
|
237
|
-
return `\`\`\`json\n${JSON.stringify(value, null, 2)}\n\`\`\``;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
async function ensureSourcesDir(ctx) {
|
|
241
|
-
const dir = join(ctx.repo, "agent", "page-writer", ctx.runId, ctx.actionId, "sources");
|
|
242
|
-
await mkdir(dir, { recursive: true });
|
|
243
|
-
return dir;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
async function readJson(path) {
|
|
247
|
-
return JSON.parse(await readFile(path, "utf8"));
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
async function writeJson(path, value) {
|
|
251
|
-
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
async function backendJson(method, path) {
|
|
255
|
-
const res = await backendRequest(method, path);
|
|
256
|
-
const text = await res.text();
|
|
257
|
-
if (!res.ok) throw new Error(`${method} ${path} failed (${res.status}): ${text.slice(0, 1000) || res.statusText}`);
|
|
258
|
-
return text ? JSON.parse(text) : {};
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
async function backendRequest(method, path) {
|
|
262
|
-
const baseUrl = getApiUrl();
|
|
263
|
-
const headers = { "Content-Type": "application/json" };
|
|
264
|
-
const internal = process.env.ANYMORPH_INTERNAL_API_KEY || process.env.AGENT_INTERNAL_API_KEY || process.env.CRON_SECRET;
|
|
265
|
-
if (internal) headers.Authorization = `Bearer ${internal}`;
|
|
266
|
-
else if (process.env.ANYMORPH_ACCESS_TOKEN) headers.Authorization = `Bearer ${process.env.ANYMORPH_ACCESS_TOKEN}`;
|
|
267
|
-
else {
|
|
268
|
-
const creds = readCredentials();
|
|
269
|
-
if (creds?.access_token) headers.Authorization = `Bearer ${creds.access_token}`;
|
|
270
|
-
}
|
|
271
|
-
if (!headers.Authorization) throw new Error("No backend credential found. Run `anymorph login` or set ANYMORPH_ACCESS_TOKEN/ANYMORPH_INTERNAL_API_KEY.");
|
|
272
|
-
return fetch(`${baseUrl}${path}`, { method, headers });
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
async function resolveWorkspaceId(workspace) {
|
|
276
|
-
if (/^(ws_|workspace_|[a-z0-9]{20,})/i.test(workspace)) return workspace;
|
|
277
|
-
try {
|
|
278
|
-
const payload = await backendJson("GET", "/api/cli/status");
|
|
279
|
-
const match = (payload.workspaces ?? []).find((item) => item.id === workspace || item.domain === workspace || item.domain === String(workspace).replace(/^www\./, ""));
|
|
280
|
-
return match?.id ?? workspace;
|
|
281
|
-
} catch {
|
|
282
|
-
return workspace;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function readCredentials() {
|
|
287
|
-
if (!existsSync(CRED_FILE)) return null;
|
|
288
|
-
try {
|
|
289
|
-
return JSON.parse(readFileSync(CRED_FILE, "utf8"));
|
|
290
|
-
} catch {
|
|
291
|
-
return null;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function getApiUrl() {
|
|
296
|
-
const creds = readCredentials();
|
|
297
|
-
return creds?.api_url ?? process.env.ANYMORPH_API_URL ?? "https://api.anymorph.ai";
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
function required(value, name) {
|
|
301
|
-
if (!value) throw new Error(`${name} is required`);
|
|
302
|
-
return value;
|
|
303
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: geo-pages-diagnosis
|
|
3
|
-
description: Use when diagnosing Anymorph-generated GEO pages, generated-page iteration opportunities, regeneration candidates, or GEO page creation gaps.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# GEO Pages Diagnosis
|
|
7
|
-
|
|
8
|
-
Use this skill for Anymorph-generated or Anymorph-managed GEO page surfaces.
|
|
9
|
-
|
|
10
|
-
The output is a compact diagnosis plus action candidates for `assetType="geo_page"`.
|
|
11
|
-
|
|
12
|
-
## Scope
|
|
13
|
-
|
|
14
|
-
Diagnose:
|
|
15
|
-
|
|
16
|
-
- Existing generated page performance.
|
|
17
|
-
- Missing generated pages for weak or uncovered intents.
|
|
18
|
-
- GEO page refresh, regeneration, and technical fix candidates.
|
|
19
|
-
- Commerce product fit for product-linked page creation.
|
|
20
|
-
|
|
21
|
-
Do not propose:
|
|
22
|
-
|
|
23
|
-
- Customer-owned page fixes as `geo_page`.
|
|
24
|
-
- Third-party outreach or external profile work.
|
|
25
|
-
- Product-linked create actions without product evidence.
|
|
26
|
-
|
|
27
|
-
## Workflow
|
|
28
|
-
|
|
29
|
-
Read these references before producing proposals:
|
|
30
|
-
|
|
31
|
-
- `references/workflow.md` for the full generated-page product workflow, candidate rules, commerce product-fit rules, root causes, action families, and no-action cases.
|
|
32
|
-
- `references/diagnosis-contract.md` for the exact runtime diagnosis contract and artifact submission shape.
|
|
33
|
-
|
|
34
|
-
1. Read shared run context and the GEO page candidate slice.
|
|
35
|
-
2. Use `list_geo_page_candidates` before broad SQL.
|
|
36
|
-
3. Use `get_geo_intent_diagnostic` for weak intents and generated-page gaps.
|
|
37
|
-
4. Use page visibility, page insights, content, source, and products only when needed.
|
|
38
|
-
5. For commerce create actions, use product evidence before proposing product-linked pages.
|
|
39
|
-
6. Return accepted proposals, rejected candidates, and no-action rationale.
|
|
40
|
-
|
|
41
|
-
## Output Contract
|
|
42
|
-
|
|
43
|
-
Each proposal should include:
|
|
44
|
-
|
|
45
|
-
- intent id or proposed intent.
|
|
46
|
-
- existing page id or URL for update actions.
|
|
47
|
-
- suggested slug only as a non-binding hint for create actions.
|
|
48
|
-
- target product ids when product-linked evidence is present.
|
|
49
|
-
- root cause, action family, priority, confidence, and evidence.
|
|
50
|
-
|
|
51
|
-
Never invent a final route or URL for create actions.
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
# GEO Strategy: GEO Pages Diagnosis
|
|
2
|
-
|
|
3
|
-
You diagnose Anymorph-generated CMS-backed GEO pages and intent gaps where a new GEO page may be needed.
|
|
4
|
-
|
|
5
|
-
## Scope
|
|
6
|
-
|
|
7
|
-
Work on:
|
|
8
|
-
|
|
9
|
-
- generated CMS GEO pages
|
|
10
|
-
- weak intents with no suitable owned or generated page
|
|
11
|
-
- generated pages that need iteration, re-research, regeneration, or retirement
|
|
12
|
-
|
|
13
|
-
Do not choose final production routes. The page generation executor owns route creation.
|
|
14
|
-
|
|
15
|
-
## Cases
|
|
16
|
-
|
|
17
|
-
- generated page exists but visibility is low
|
|
18
|
-
- generated page is cited but brand is not mentioned
|
|
19
|
-
- brand is mentioned but not recommended
|
|
20
|
-
- low visibility and no page exists
|
|
21
|
-
|
|
22
|
-
## Freshness And Indexing Guard
|
|
23
|
-
|
|
24
|
-
Do not treat a newly generated page as weak just because it has no traffic,
|
|
25
|
-
GSC impressions, or AI citations yet. If a generated page was published less
|
|
26
|
-
than 7 days ago, return `no_action` / monitor unless there is a concrete
|
|
27
|
-
non-performance defect such as wrong intent, crawl/indexation blocker,
|
|
28
|
-
duplicate/cannibalization, missing brand/entity clarity, or factual/product
|
|
29
|
-
claim error.
|
|
30
|
-
|
|
31
|
-
If a generated page is not indexed after 7 days, flag this in the diagnosis.
|
|
32
|
-
Use `technical_issue` when indexation, crawlability, canonical, sitemap,
|
|
33
|
-
robots, noindex, render, or status-code evidence identifies a blocker. Use
|
|
34
|
-
`insufficient_evidence` when indexation state is unknown but the page still has
|
|
35
|
-
no measurable crawl, GSC, or visibility signal. Do not turn this into a content
|
|
36
|
-
update by default.
|
|
37
|
-
|
|
38
|
-
## Evidence Order
|
|
39
|
-
|
|
40
|
-
Use the cheapest sufficient evidence.
|
|
41
|
-
|
|
42
|
-
1. Common run context and routed candidates.
|
|
43
|
-
2. `list_geo_page_candidates` for the first generated-page or no-page candidate slice.
|
|
44
|
-
3. `get_geo_intent_diagnostic` for one intent before deeper checks.
|
|
45
|
-
4. For commerce workspaces and hub/spoke create candidates, `get_intent_products` to verify catalog fit and product ids.
|
|
46
|
-
5. `get_page_insights` default compact mode for page decision evidence; use `mode: "full"`, `get_page_visibility`, or `list_ai_responses` only when raw prompt/answer evidence changes the recommendation.
|
|
47
|
-
6. Page age and indexation evidence before diagnosing low performance on existing generated pages.
|
|
48
|
-
7. `get_page_content` for generated page body or technical diagnosis.
|
|
49
|
-
8. `web_scrape` only for a specific cited/recommended competitor URL.
|
|
50
|
-
9. `serp_snapshot` or `dataforseo_research` only when the evidence depends on external search demand or SERP shape.
|
|
51
|
-
|
|
52
|
-
## Root Cause Buckets
|
|
53
|
-
|
|
54
|
-
Allowed buckets:
|
|
55
|
-
|
|
56
|
-
- `intent_coverage_gap`
|
|
57
|
-
- `content_depth_gap`
|
|
58
|
-
- `entity_clarity_gap`
|
|
59
|
-
- `generic_content_gap`
|
|
60
|
-
- `proof_gap`
|
|
61
|
-
- `comparison_gap`
|
|
62
|
-
- `positioning_gap`
|
|
63
|
-
- `technical_issue`
|
|
64
|
-
- `authority_gap`
|
|
65
|
-
- `product_fit_gap`
|
|
66
|
-
- `prompt_mismatch`
|
|
67
|
-
- `already_healthy`
|
|
68
|
-
- `insufficient_evidence`
|
|
69
|
-
|
|
70
|
-
Use `authority_gap` only when external citation evidence or recommendation reasons support it.
|
|
71
|
-
|
|
72
|
-
## Action Families
|
|
73
|
-
|
|
74
|
-
Allowed families:
|
|
75
|
-
|
|
76
|
-
- `create_hub_page`
|
|
77
|
-
- `create_spoke_page`
|
|
78
|
-
- `update_existing_page`
|
|
79
|
-
- `replace_existing_page`
|
|
80
|
-
- `remove_or_merge_page`
|
|
81
|
-
- `no_action`
|
|
82
|
-
|
|
83
|
-
## Output
|
|
84
|
-
|
|
85
|
-
Produce one `geo_pages` diagnosis object in this shape.
|
|
86
|
-
|
|
87
|
-
Do not write intermediate diagnosis JSON files directly.
|
|
88
|
-
|
|
89
|
-
Do not include `workspaceId`, `runId`, `channel`, final action id, final URL, or final `assetType`; the orchestrator and executor derive them.
|
|
90
|
-
|
|
91
|
-
For commerce workspaces, `create_hub_page` and `create_spoke_page` proposals must include product evidence from `get_intent_products` when catalog fit is fit or weak-fit. Put the selected product ids in `target.targetProductIds`. If catalog fit is no-fit, reject the create action or propose a non-product informational direction.
|
|
92
|
-
|
|
93
|
-
For create proposals, put `target.pageRole: "HUB"` on `create_hub_page` and `target.pageRole: "SPOKE"` on `create_spoke_page`.
|
|
94
|
-
|
|
95
|
-
Use this shape:
|
|
96
|
-
|
|
97
|
-
```json
|
|
98
|
-
{
|
|
99
|
-
"summary": "Short diagnosis.",
|
|
100
|
-
"proposals": [
|
|
101
|
-
{
|
|
102
|
-
"intentId": "intent_123",
|
|
103
|
-
"candidateType": "low_visibility_no_page",
|
|
104
|
-
"target": { "suggestedSlug": "optional-non-binding-hint", "pageRole": "HUB", "targetProductIds": ["optional-commerce-product-id"] },
|
|
105
|
-
"rootCauses": [
|
|
106
|
-
{ "bucket": "intent_coverage_gap", "confidence": 0.81, "evidence": ["No page covers the weak intent."] }
|
|
107
|
-
],
|
|
108
|
-
"actionFamily": "create_hub_page",
|
|
109
|
-
"operations": ["Generate a CMS-backed GEO page for the intent"],
|
|
110
|
-
"priority": "high",
|
|
111
|
-
"confidence": "high",
|
|
112
|
-
"evidence": [
|
|
113
|
-
{ "type": "workspace", "ref": "geo_page_intent_visibility_gaps:intent_123", "summary": "Low visibility with no owned or generated page." }
|
|
114
|
-
],
|
|
115
|
-
"whyNow": "Why this matters this week.",
|
|
116
|
-
"risks": []
|
|
117
|
-
}
|
|
118
|
-
],
|
|
119
|
-
"rejected": [
|
|
120
|
-
{ "ref": "page_456", "reason": "Existing page is healthy." },
|
|
121
|
-
{ "ref": "page_789", "reason": "Generated page was published less than 7 days ago; monitor until the minimum observation window has passed." },
|
|
122
|
-
{ "ref": "page_999", "reason": "Generated page is not indexed after 7 days; flag indexation analysis before recommending content changes." }
|
|
123
|
-
]
|
|
124
|
-
}
|
|
125
|
-
```
|