anymorph 0.2.5 → 0.4.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 +50 -36
- package/dist/index.js +9231 -251
- package/dist/skillpacks/geo/scaffold/AGENTS.md +38 -0
- package/dist/skillpacks/geo/scaffold/CLAUDE.md +33 -0
- package/dist/skillpacks/geo/shared/evidence-principles.md +35 -0
- package/dist/skillpacks/geo/shared/geo-principles.md +281 -0
- package/dist/skillpacks/geo/shared/vertical-playbooks/beauty.md +65 -0
- package/dist/skillpacks/geo/shared/vertical-playbooks/commerce.md +65 -0
- package/dist/skillpacks/geo/shared/vertical-playbooks/ota.md +62 -0
- package/dist/skillpacks/geo/shared/vertical-playbooks/saas.md +64 -0
- package/dist/skillpacks/geo/skills/brand-owned-diagnosis/SKILL.md +1 -1
- package/dist/skillpacks/geo/skills/brand-owned-diagnosis/references/diagnosis-contract.md +8 -9
- package/dist/skillpacks/geo/skills/brand-owned-diagnosis/references/workflow.md +11 -8
- package/dist/skillpacks/geo/skills/geo-generating-actions/SKILL.md +110 -22
- package/dist/skillpacks/geo/skills/geo-generating-actions/references/orchestrator.workflow.md +171 -5
- package/dist/skillpacks/geo/skills/geo-generating-actions/scripts/geo-scaffold.mjs +358 -0
- package/dist/skillpacks/geo/skills/geo-generating-actions/scripts/geo.mjs +66 -0
- package/dist/skillpacks/geo/skills/geo-initializing-strategy/references/foundation-diagnosis.md +1 -1
- package/dist/skillpacks/geo/skills/geo-local-setup/SKILL.md +66 -0
- package/dist/skillpacks/geo/skills/geo-local-setup/agents/openai.yaml +4 -0
- package/dist/skillpacks/geo/skills/geo-page-writer/SKILL.md +81 -0
- package/dist/skillpacks/geo/skills/geo-page-writer/agents/openai.yaml +4 -0
- package/dist/skillpacks/geo/skills/geo-page-writer/references/research.md +61 -0
- package/dist/skillpacks/geo/skills/geo-page-writer/references/validation.md +59 -0
- package/dist/skillpacks/geo/skills/geo-page-writer/references/writing.md +55 -0
- package/dist/skillpacks/geo/skills/geo-page-writer/scripts/check-page-mdx.mjs +210 -0
- package/dist/skillpacks/geo/skills/geo-page-writer/scripts/collect-page-sources.mjs +303 -0
- package/dist/skillpacks/geo/skills/geo-pages-diagnosis/SKILL.md +1 -1
- package/dist/skillpacks/geo/skills/geo-pages-diagnosis/references/diagnosis-contract.md +36 -17
- package/dist/skillpacks/geo/skills/geo-pages-diagnosis/references/workflow.md +65 -15
- package/dist/skillpacks/geo/skills/seo-ecommerce-opportunity/SKILL.md +82 -0
- package/dist/skillpacks/geo/skills/seo-ecommerce-opportunity/agents/openai.yaml +5 -0
- package/dist/skillpacks/geo/skills/seo-ecommerce-opportunity/references/ecommerce-rules.md +31 -0
- package/dist/skillpacks/geo/skills/seo-internal-link-opportunity/SKILL.md +57 -0
- package/dist/skillpacks/geo/skills/seo-internal-link-opportunity/agents/openai.yaml +5 -0
- package/dist/skillpacks/geo/skills/seo-internal-link-opportunity/references/link-rules.md +28 -0
- package/dist/skillpacks/geo/skills/seo-opportunity-audit/SKILL.md +141 -0
- package/dist/skillpacks/geo/skills/seo-opportunity-audit/agents/openai.yaml +5 -0
- package/dist/skillpacks/geo/skills/seo-opportunity-audit/references/action-contract.md +62 -0
- package/dist/skillpacks/geo/skills/seo-opportunity-audit/scripts/seo-toolkit.mjs +248 -0
- package/dist/skillpacks/geo/skills/seo-page-diagnosis/SKILL.md +56 -0
- package/dist/skillpacks/geo/skills/seo-page-diagnosis/agents/openai.yaml +5 -0
- package/dist/skillpacks/geo/skills/seo-page-diagnosis/references/page-checks.md +38 -0
- package/dist/skillpacks/geo/skills/seo-serp-opportunity-research/SKILL.md +66 -0
- package/dist/skillpacks/geo/skills/seo-serp-opportunity-research/agents/openai.yaml +5 -0
- package/dist/skillpacks/geo/skills/seo-serp-opportunity-research/references/page-type-taxonomy.md +40 -0
- package/dist/skillpacks/geo/skills/seo-serp-opportunity-research/references/serp-methodology.md +38 -0
- package/dist/skillpacks/geo/skills/seo-technical-diagnosis/SKILL.md +64 -0
- package/dist/skillpacks/geo/skills/seo-technical-diagnosis/agents/openai.yaml +5 -0
- package/dist/skillpacks/geo/skills/seo-technical-diagnosis/references/checks.md +58 -0
- package/dist/skillpacks/geo/skills/third-party-diagnosis/SKILL.md +1 -1
- package/dist/skillpacks/geo/skills/third-party-diagnosis/references/diagnosis-contract.md +2 -2
- package/dist/skillpacks/geo/skills/third-party-diagnosis/references/workflow.md +1 -1
- package/dist/skillpacks/geo/skills/third-party-execution-planning/SKILL.md +64 -0
- package/dist/skillpacks/geo/skills/third-party-execution-planning/agents/openai.yaml +4 -0
- package/dist/skillpacks/geo/skills/third-party-execution-planning/references/execution-contract.md +90 -0
- package/dist/skillpacks/geo/skills/third-party-execution-planning/references/non-social-surface-playbooks.md +123 -0
- package/package.json +2 -1
- package/dist/skillpacks/geo/skills/social-execution-planning/SKILL.md +0 -53
- package/dist/skillpacks/geo/skills/social-execution-planning/agents/openai.yaml +0 -5
- package/dist/skillpacks/geo/skills/social-execution-planning/references/execution-contract.md +0 -68
- /package/dist/skillpacks/geo/skills/{social-execution-planning → third-party-execution-planning}/references/reddit-rules.md +0 -0
- /package/dist/skillpacks/geo/skills/{social-execution-planning/references/platform-playbooks.md → third-party-execution-planning/references/social-platform-playbooks.md} +0 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import {
|
|
5
|
+
cp,
|
|
6
|
+
mkdir,
|
|
7
|
+
readdir,
|
|
8
|
+
readFile,
|
|
9
|
+
rm,
|
|
10
|
+
stat,
|
|
11
|
+
writeFile,
|
|
12
|
+
} from "node:fs/promises";
|
|
13
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
import { promisify } from "node:util";
|
|
16
|
+
|
|
17
|
+
const execFileAsync = promisify(execFile);
|
|
18
|
+
const MANAGED_PATHS = [".claude/skills", ".agents/skills", "agent/contracts"];
|
|
19
|
+
const ACTIONS_SCHEMA = {
|
|
20
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
21
|
+
title: "Anymorph GEO actions artifact",
|
|
22
|
+
type: "object",
|
|
23
|
+
required: ["runId", "actions"],
|
|
24
|
+
properties: {
|
|
25
|
+
runId: { type: "string" },
|
|
26
|
+
actions: { type: "array", items: { type: "object" } },
|
|
27
|
+
artifactPaths: { type: "array", items: { type: "string" } },
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const MEMORY_ITEM_SCHEMA = {
|
|
32
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
33
|
+
title: "Anymorph GEO memory item",
|
|
34
|
+
type: "object",
|
|
35
|
+
required: ["summary"],
|
|
36
|
+
properties: {
|
|
37
|
+
summary: { type: "string" },
|
|
38
|
+
evidence: { type: "array", items: { type: "string" } },
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const mode = process.argv[2];
|
|
43
|
+
const args = parseArgs(process.argv.slice(3));
|
|
44
|
+
|
|
45
|
+
if (!["init", "sync", "doctor"].includes(mode)) {
|
|
46
|
+
console.error("Usage: geo-scaffold.mjs <init|sync|doctor> --repo <path> [--skills-source <path>] [--ref <ref>] [--json]");
|
|
47
|
+
process.exit(2);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const result =
|
|
52
|
+
mode === "doctor"
|
|
53
|
+
? await doctorGeoScaffold(args)
|
|
54
|
+
: await applyGeoScaffold(args, { createMemory: mode === "init" });
|
|
55
|
+
|
|
56
|
+
if (args.json) {
|
|
57
|
+
console.log(JSON.stringify(result, null, 2));
|
|
58
|
+
} else if (result.ok) {
|
|
59
|
+
console.log(`${mode} ok: ${result.repoPath}`);
|
|
60
|
+
for (const item of result.changed) console.log(`changed: ${item}`);
|
|
61
|
+
} else {
|
|
62
|
+
console.error(`${mode} failed: ${result.repoPath}`);
|
|
63
|
+
for (const problem of result.problems) console.error(`problem: ${problem}`);
|
|
64
|
+
}
|
|
65
|
+
process.exit(args.json ? 0 : result.ok ? 0 : 1);
|
|
66
|
+
} catch (err) {
|
|
67
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
68
|
+
if (args.json) {
|
|
69
|
+
console.log(
|
|
70
|
+
JSON.stringify(
|
|
71
|
+
{
|
|
72
|
+
ok: false,
|
|
73
|
+
repoPath: resolve(args.repo ?? "."),
|
|
74
|
+
skillsSourceDir: args.skillsSource ? resolve(args.skillsSource) : defaultSkillsSourceDir(),
|
|
75
|
+
changed: [],
|
|
76
|
+
problems: [message],
|
|
77
|
+
manifestHash: null,
|
|
78
|
+
},
|
|
79
|
+
null,
|
|
80
|
+
2,
|
|
81
|
+
),
|
|
82
|
+
);
|
|
83
|
+
} else {
|
|
84
|
+
console.error(message);
|
|
85
|
+
}
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function parseArgs(argv) {
|
|
90
|
+
const parsed = { repo: ".", ref: "local", json: false };
|
|
91
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
92
|
+
const arg = argv[i];
|
|
93
|
+
if (arg === "--json") parsed.json = true;
|
|
94
|
+
else if (arg === "--repo") parsed.repo = requireValue(argv, ++i, arg);
|
|
95
|
+
else if (arg === "--skills-source") parsed.skillsSource = requireValue(argv, ++i, arg);
|
|
96
|
+
else if (arg === "--ref") parsed.ref = requireValue(argv, ++i, arg);
|
|
97
|
+
else if (arg === "--script-version") parsed.scriptVersion = requireValue(argv, ++i, arg);
|
|
98
|
+
else if (arg === "--cli-version") parsed.scriptVersion = requireValue(argv, ++i, arg);
|
|
99
|
+
else throw new Error(`Unknown argument: ${arg}`);
|
|
100
|
+
}
|
|
101
|
+
return parsed;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function requireValue(argv, index, flag) {
|
|
105
|
+
const value = argv[index];
|
|
106
|
+
if (!value || value.startsWith("--")) throw new Error(`${flag} requires a value`);
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function applyGeoScaffold(input, options) {
|
|
111
|
+
const repoPath = resolve(input.repo ?? ".");
|
|
112
|
+
const skillsSourceDir = resolveSkillsSourceDir(input.skillsSource);
|
|
113
|
+
await ensureDirectory(skillsSourceDir);
|
|
114
|
+
await mkdir(repoPath, { recursive: true });
|
|
115
|
+
|
|
116
|
+
const changed = [];
|
|
117
|
+
const problems = [];
|
|
118
|
+
const manifestHash = await hashDirectory(skillsSourceDir);
|
|
119
|
+
|
|
120
|
+
await syncManagedDirectory(skillsSourceDir, join(repoPath, ".claude", "skills"), changed);
|
|
121
|
+
await syncManagedDirectory(skillsSourceDir, join(repoPath, ".agents", "skills"), changed);
|
|
122
|
+
await syncContracts(repoPath, changed);
|
|
123
|
+
await ensureRunDirs(repoPath, changed);
|
|
124
|
+
if (options.createMemory) await ensureMemoryFiles(repoPath, changed);
|
|
125
|
+
await writeSkillpackLock(
|
|
126
|
+
{
|
|
127
|
+
repoPath,
|
|
128
|
+
skillsSourceDir,
|
|
129
|
+
ref: input.ref ?? "local",
|
|
130
|
+
manifestHash,
|
|
131
|
+
scriptVersion: input.scriptVersion ?? null,
|
|
132
|
+
},
|
|
133
|
+
changed,
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
ok: problems.length === 0,
|
|
138
|
+
repoPath,
|
|
139
|
+
skillsSourceDir,
|
|
140
|
+
changed,
|
|
141
|
+
problems,
|
|
142
|
+
manifestHash,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function doctorGeoScaffold(input) {
|
|
147
|
+
const repoPath = resolve(input.repo ?? ".");
|
|
148
|
+
const skillsSourceDir = resolveSkillsSourceDir(input.skillsSource);
|
|
149
|
+
await ensureDirectory(skillsSourceDir);
|
|
150
|
+
const manifestHash = await hashDirectory(skillsSourceDir);
|
|
151
|
+
const problems = [];
|
|
152
|
+
|
|
153
|
+
for (const target of [".claude/skills", ".agents/skills"]) {
|
|
154
|
+
await compareDirectories(skillsSourceDir, join(repoPath, target), target, problems);
|
|
155
|
+
}
|
|
156
|
+
for (const path of [
|
|
157
|
+
"agent/BRAND.md",
|
|
158
|
+
"agent/STRATEGY.md",
|
|
159
|
+
"agent/LEARNINGS.md",
|
|
160
|
+
"agent/runs/.gitkeep",
|
|
161
|
+
"agent/archive/.gitkeep",
|
|
162
|
+
"agent/skillpack.json",
|
|
163
|
+
]) {
|
|
164
|
+
if (!(await exists(join(repoPath, path)))) problems.push(`Missing ${path}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const lock = await readJson(join(repoPath, "agent", "skillpack.json"));
|
|
168
|
+
if (lock && typeof lock === "object" && lock.manifestHash !== manifestHash) {
|
|
169
|
+
problems.push("agent/skillpack.json manifestHash does not match current skillpack");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
ok: problems.length === 0,
|
|
174
|
+
repoPath,
|
|
175
|
+
skillsSourceDir,
|
|
176
|
+
changed: [],
|
|
177
|
+
problems,
|
|
178
|
+
manifestHash,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function resolveSkillsSourceDir(explicit) {
|
|
183
|
+
if (explicit) return resolve(explicit);
|
|
184
|
+
return defaultSkillsSourceDir();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function defaultSkillsSourceDir() {
|
|
188
|
+
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
189
|
+
return resolve(scriptDir, "..", "..");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function syncManagedDirectory(sourceDir, targetDir, changed) {
|
|
193
|
+
const source = resolve(sourceDir);
|
|
194
|
+
const target = resolve(targetDir);
|
|
195
|
+
if (source === target) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (source.startsWith(`${target}/`)) {
|
|
199
|
+
throw new Error(`Refusing to sync ${source} into itself at ${target}`);
|
|
200
|
+
}
|
|
201
|
+
await rm(target, { recursive: true, force: true });
|
|
202
|
+
await mkdir(dirname(target), { recursive: true });
|
|
203
|
+
await cp(source, target, { recursive: true });
|
|
204
|
+
changed.push(relative(process.cwd(), target) || target);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async function syncContracts(repoPath, changed) {
|
|
208
|
+
const contractsDir = join(repoPath, "agent", "contracts");
|
|
209
|
+
await mkdir(contractsDir, { recursive: true });
|
|
210
|
+
await writeIfChanged(
|
|
211
|
+
join(contractsDir, "actions.schema.json"),
|
|
212
|
+
`${JSON.stringify(ACTIONS_SCHEMA, null, 2)}\n`,
|
|
213
|
+
changed,
|
|
214
|
+
);
|
|
215
|
+
await writeIfChanged(
|
|
216
|
+
join(contractsDir, "memory-item.schema.json"),
|
|
217
|
+
`${JSON.stringify(MEMORY_ITEM_SCHEMA, null, 2)}\n`,
|
|
218
|
+
changed,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function ensureRunDirs(repoPath, changed) {
|
|
223
|
+
for (const path of [join(repoPath, "agent", "runs"), join(repoPath, "agent", "archive")]) {
|
|
224
|
+
await mkdir(path, { recursive: true });
|
|
225
|
+
await writeIfMissing(join(path, ".gitkeep"), "", changed);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async function ensureMemoryFiles(repoPath, changed) {
|
|
230
|
+
await mkdir(join(repoPath, "agent"), { recursive: true });
|
|
231
|
+
await writeIfMissing(
|
|
232
|
+
join(repoPath, "agent", "BRAND.md"),
|
|
233
|
+
"# Brand\n\nInitial local scaffold. Replace with workspace brand context before serious runs.\n",
|
|
234
|
+
changed,
|
|
235
|
+
);
|
|
236
|
+
await writeIfMissing(
|
|
237
|
+
join(repoPath, "agent", "STRATEGY.md"),
|
|
238
|
+
"# GEO Strategy\n\n## Current Priorities\n\n- Initial local scaffold.\n",
|
|
239
|
+
changed,
|
|
240
|
+
);
|
|
241
|
+
await writeIfMissing(
|
|
242
|
+
join(repoPath, "agent", "LEARNINGS.md"),
|
|
243
|
+
"# Learnings\n\n## Stable Learnings\n\n- Initial local scaffold.\n",
|
|
244
|
+
changed,
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function writeSkillpackLock(input, changed) {
|
|
249
|
+
const sourceCommit = await gitOutput(input.skillsSourceDir, ["rev-parse", "HEAD"]);
|
|
250
|
+
const lock = {
|
|
251
|
+
name: "anymorph-geo",
|
|
252
|
+
source: input.skillsSourceDir,
|
|
253
|
+
sourceRepo: "opactor-dev/anymorph-geo-skills",
|
|
254
|
+
requestedRef: input.ref,
|
|
255
|
+
resolvedCommit: sourceCommit,
|
|
256
|
+
scriptVersion: input.scriptVersion,
|
|
257
|
+
manifestHash: input.manifestHash,
|
|
258
|
+
installedAt: new Date().toISOString(),
|
|
259
|
+
managedPaths: MANAGED_PATHS,
|
|
260
|
+
};
|
|
261
|
+
await writeIfChanged(
|
|
262
|
+
join(input.repoPath, "agent", "skillpack.json"),
|
|
263
|
+
`${JSON.stringify(lock, null, 2)}\n`,
|
|
264
|
+
changed,
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function compareDirectories(sourceDir, targetDir, displayPrefix, problems) {
|
|
269
|
+
const sourceFiles = await listFiles(sourceDir);
|
|
270
|
+
const targetFiles = await listFiles(targetDir);
|
|
271
|
+
const targetSet = new Set(targetFiles);
|
|
272
|
+
for (const file of sourceFiles) {
|
|
273
|
+
const source = await readFile(join(sourceDir, file), "utf8");
|
|
274
|
+
const targetPath = join(targetDir, file);
|
|
275
|
+
if (!(await exists(targetPath))) {
|
|
276
|
+
problems.push(`Missing ${displayPrefix}/${file}`);
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
const target = await readFile(targetPath, "utf8");
|
|
280
|
+
if (source !== target) problems.push(`Drifted ${displayPrefix}/${file}`);
|
|
281
|
+
targetSet.delete(file);
|
|
282
|
+
}
|
|
283
|
+
for (const extra of targetSet) problems.push(`Extra managed file ${displayPrefix}/${extra}`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async function hashDirectory(dir) {
|
|
287
|
+
const files = await listFiles(dir);
|
|
288
|
+
const hash = createHash("sha256");
|
|
289
|
+
for (const file of files) {
|
|
290
|
+
hash.update(file);
|
|
291
|
+
hash.update("\0");
|
|
292
|
+
hash.update(await readFile(join(dir, file)));
|
|
293
|
+
hash.update("\0");
|
|
294
|
+
}
|
|
295
|
+
return `sha256:${hash.digest("hex")}`;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function listFiles(dir, base = "") {
|
|
299
|
+
let entries;
|
|
300
|
+
try {
|
|
301
|
+
entries = await readdir(join(dir, base), { withFileTypes: true });
|
|
302
|
+
} catch {
|
|
303
|
+
return [];
|
|
304
|
+
}
|
|
305
|
+
const files = [];
|
|
306
|
+
for (const entry of entries) {
|
|
307
|
+
const name = String(entry.name);
|
|
308
|
+
const path = base ? `${base}/${name}` : name;
|
|
309
|
+
if (entry.isDirectory()) files.push(...(await listFiles(dir, path)));
|
|
310
|
+
else if (entry.isFile()) files.push(path);
|
|
311
|
+
}
|
|
312
|
+
return files.sort();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async function writeIfMissing(path, content, changed) {
|
|
316
|
+
if (await exists(path)) return;
|
|
317
|
+
await mkdir(dirname(path), { recursive: true });
|
|
318
|
+
await writeFile(path, content);
|
|
319
|
+
changed.push(relative(process.cwd(), path) || path);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async function writeIfChanged(path, content, changed) {
|
|
323
|
+
const current = await readFile(path, "utf8").catch(() => null);
|
|
324
|
+
if (current === content) return;
|
|
325
|
+
await mkdir(dirname(path), { recursive: true });
|
|
326
|
+
await writeFile(path, content);
|
|
327
|
+
changed.push(relative(process.cwd(), path) || path);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async function readJson(path) {
|
|
331
|
+
const raw = await readFile(path, "utf8").catch(() => null);
|
|
332
|
+
if (!raw) return null;
|
|
333
|
+
try {
|
|
334
|
+
return JSON.parse(raw);
|
|
335
|
+
} catch {
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async function gitOutput(cwd, args) {
|
|
341
|
+
try {
|
|
342
|
+
const { stdout } = await execFileAsync("git", args, { cwd });
|
|
343
|
+
return stdout.trim() || null;
|
|
344
|
+
} catch {
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async function ensureDirectory(path) {
|
|
350
|
+
const s = await stat(path);
|
|
351
|
+
if (!s.isDirectory()) throw new Error(`${path} is not a directory`);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async function exists(path) {
|
|
355
|
+
return stat(path)
|
|
356
|
+
.then(() => true)
|
|
357
|
+
.catch(() => false);
|
|
358
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
|
|
4
|
+
const legacyCommand = process.argv[2];
|
|
5
|
+
const legacyArgs = process.argv.slice(3);
|
|
6
|
+
|
|
7
|
+
const commandMap = new Map([
|
|
8
|
+
["materialize", "submit"],
|
|
9
|
+
["check", "doctor"],
|
|
10
|
+
["sync", "doctor"],
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
if (!legacyCommand || ["-h", "--help", "help"].includes(legacyCommand)) {
|
|
14
|
+
usage();
|
|
15
|
+
process.exit(0);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (legacyArgs.includes("--repo")) {
|
|
19
|
+
console.error(
|
|
20
|
+
"`--repo` is no longer supported. Run `anymorph` from the tenant repo root instead.",
|
|
21
|
+
);
|
|
22
|
+
process.exit(2);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const command = commandMap.get(legacyCommand) ?? legacyCommand;
|
|
26
|
+
const args = legacyArgs.filter((arg) => !arg.startsWith("--repo="));
|
|
27
|
+
if (legacyCommand === "sync") args.unshift("--fix");
|
|
28
|
+
|
|
29
|
+
console.error(
|
|
30
|
+
"`geo.mjs` is deprecated. Use the top-level Anymorph CLI: " +
|
|
31
|
+
`anymorph ${[command, ...args].join(" ")}`.trim(),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const child = spawn("anymorph", [command, ...args], {
|
|
35
|
+
stdio: "inherit",
|
|
36
|
+
env: process.env,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
child.on("exit", (code, signal) => {
|
|
40
|
+
if (signal) {
|
|
41
|
+
process.kill(process.pid, signal);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
process.exit(code ?? 1);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
child.on("error", (err) => {
|
|
48
|
+
console.error(`Could not run \`anymorph\`: ${err.message}`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
function usage() {
|
|
53
|
+
console.error(`Usage:
|
|
54
|
+
anymorph login
|
|
55
|
+
anymorph logout
|
|
56
|
+
anymorph status
|
|
57
|
+
anymorph workspaces
|
|
58
|
+
anymorph init
|
|
59
|
+
anymorph doctor
|
|
60
|
+
anymorph doctor --fix
|
|
61
|
+
anymorph prepare <workspaceId>
|
|
62
|
+
anymorph validate <runId>
|
|
63
|
+
anymorph submit <runId>
|
|
64
|
+
anymorph intents <runId>
|
|
65
|
+
anymorph run-status <runId>`);
|
|
66
|
+
}
|
package/dist/skillpacks/geo/skills/geo-initializing-strategy/references/foundation-diagnosis.md
CHANGED
|
@@ -37,7 +37,7 @@ Use these DFS fields:
|
|
|
37
37
|
|
|
38
38
|
Do not use DataForSEO Backlinks API as a substitute for owned-site internal-link evidence. Use DataForSEO On-Page only.
|
|
39
39
|
|
|
40
|
-
Do not claim a page is an in-content hub from DFS alone. DFS On-Page can show internal edges and target status, but not guaranteed nav/footer/content placement. For true in-content hub claims, corroborate with
|
|
40
|
+
Do not claim a page is an in-content hub from DFS alone. DFS On-Page can show internal edges and target status, but not guaranteed nav/footer/content placement. For true in-content hub claims, corroborate with page content or page HTML via `get_page_content` or summarized scrape.
|
|
41
41
|
|
|
42
42
|
Use Ahrefs only as optional enrichment for crawl and in-content internal-link evidence. For internal-link hubs, filter to in-content, dofollow links when the endpoint supports it. If Ahrefs returns "API units limit reached", times out, fails auth, or is unavailable, stop calling Ahrefs and continue with DataForSEO On-Page + owned-source evidence.
|
|
43
43
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: geo-local-setup
|
|
3
|
+
description: Use when setting up, checking, repairing, or preparing a tenant repo for local Anymorph GEO work.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# GEO Local Setup
|
|
7
|
+
|
|
8
|
+
Use this skill before local GEO action generation in a tenant repo.
|
|
9
|
+
|
|
10
|
+
## CLI
|
|
11
|
+
|
|
12
|
+
Use the top-level `anymorph` CLI. Run commands from the tenant repo root.
|
|
13
|
+
|
|
14
|
+
## Setup
|
|
15
|
+
|
|
16
|
+
Run these from the tenant repo root:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
anymorph status
|
|
20
|
+
anymorph login
|
|
21
|
+
anymorph init
|
|
22
|
+
anymorph doctor
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
If credentials are missing, authenticated commands start device login
|
|
26
|
+
automatically. Use `login` directly when you want to authenticate first.
|
|
27
|
+
For non-interactive runs, set `ANYMORPH_ACCESS_TOKEN` and optionally
|
|
28
|
+
`ANYMORPH_API_URL`.
|
|
29
|
+
|
|
30
|
+
`init` installs managed skills, contracts, run folders, and missing memory files.
|
|
31
|
+
`doctor` reports exact missing, extra, or drifted managed files. Use
|
|
32
|
+
`doctor --fix` to repair managed files without touching memory or run artifacts.
|
|
33
|
+
|
|
34
|
+
To compare against a known source checkout instead of the installed copy:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
anymorph doctor --skills-source /path/to/anymorph-geo-skills/skills
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Prepare
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
anymorph status
|
|
44
|
+
anymorph workspaces
|
|
45
|
+
anymorph prepare <workspaceId>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
After the agent writes `actions.json`:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
anymorph validate {runId}
|
|
52
|
+
git add agent/runs/{runId} agent/STRATEGY.md agent/LEARNINGS.md
|
|
53
|
+
git commit -m "chore: add geo strategy run {runId}"
|
|
54
|
+
git push origin main
|
|
55
|
+
anymorph submit {runId}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`submit` registers or updates the backend run and immediately materializes it
|
|
59
|
+
in Dashboard. There is no separate approve step.
|
|
60
|
+
A submitted run is immutable: once it is `executed`, re-submitting the same
|
|
61
|
+
`runId` (even with an edited `actions.json`) is a no-op. To add or change
|
|
62
|
+
actions, run a fresh `prepare` for a new `runId` and submit that new run.
|
|
63
|
+
For commerce workspaces, `validate` requires every `geo_page` create action to
|
|
64
|
+
include `target.targetProductIds` from the prepared product files.
|
|
65
|
+
|
|
66
|
+
The `anymorph` CLI owns local GEO setup, prepare, validate, and submit.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: geo-page-writer
|
|
3
|
+
description: Use when writing or updating the actual MDX for an Anymorph-managed GEO page in a CMS tenant repo after a geo_page action or generated-page opportunity has been selected.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# GEO Page Writer
|
|
7
|
+
|
|
8
|
+
Use this skill to write or revise a real CMS MDX page for a selected
|
|
9
|
+
`geo_page` action. This is an execution skill, not a strategy-planning skill.
|
|
10
|
+
|
|
11
|
+
## Scope
|
|
12
|
+
|
|
13
|
+
Write or update:
|
|
14
|
+
|
|
15
|
+
- A tenant repo MDX page for a generated GEO page.
|
|
16
|
+
- The supporting page-writer evidence artifacts under `agent/page-writer/`.
|
|
17
|
+
|
|
18
|
+
Do not:
|
|
19
|
+
|
|
20
|
+
- Write `actions.json`.
|
|
21
|
+
- Submit GEO strategy runs.
|
|
22
|
+
- Replace the backend CMS generation pipeline.
|
|
23
|
+
- Invent claims, product facts, prices, image URLs, testimonials, quotes, or
|
|
24
|
+
source names.
|
|
25
|
+
|
|
26
|
+
## References
|
|
27
|
+
|
|
28
|
+
Read these references before writing:
|
|
29
|
+
|
|
30
|
+
- `references/research.md` for source collection, Exa Deep Search, KB search,
|
|
31
|
+
image search, and source-pack rules.
|
|
32
|
+
- `references/writing.md` for MDX structure, frontmatter, components, images,
|
|
33
|
+
CTA, and GEO writing rules.
|
|
34
|
+
- `references/validation.md` for agent-owned brand and fact validation plus
|
|
35
|
+
script-backed MDX static checks.
|
|
36
|
+
|
|
37
|
+
## Workflow
|
|
38
|
+
|
|
39
|
+
1. Identify the selected `geo_page` action from `agent/runs/{runId}/actions.json`.
|
|
40
|
+
2. Collect page sources:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
node .agents/skills/geo-page-writer/scripts/collect-page-sources.mjs all \
|
|
44
|
+
--run-id <runId> \
|
|
45
|
+
--action-id <actionId> \
|
|
46
|
+
--workspace <workspace-id-or-domain>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Use `.claude/skills/...` instead of `.agents/skills/...` when running under
|
|
50
|
+
Claude Code.
|
|
51
|
+
|
|
52
|
+
3. Read `agent/page-writer/{runId}/{actionId}/sources/source-pack.json`.
|
|
53
|
+
4. Write or edit the MDX manually using the source pack and `references/writing.md`.
|
|
54
|
+
5. Self-review brand and fact claims with `references/validation.md`.
|
|
55
|
+
6. Run the static MDX checker:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
node .agents/skills/geo-page-writer/scripts/check-page-mdx.mjs \
|
|
59
|
+
--file <path/to/page.mdx> \
|
|
60
|
+
--sources agent/page-writer/<runId>/<actionId>/sources/source-pack.json
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
7. Fix every error. Review warnings before commit.
|
|
64
|
+
|
|
65
|
+
## Artifact Contract
|
|
66
|
+
|
|
67
|
+
Write page-writer artifacts here:
|
|
68
|
+
|
|
69
|
+
```text
|
|
70
|
+
agent/page-writer/{runId}/{actionId}/
|
|
71
|
+
sources/
|
|
72
|
+
exa-research.md
|
|
73
|
+
exa-response.json
|
|
74
|
+
kb-results.json
|
|
75
|
+
image-results.json
|
|
76
|
+
source-pack.json
|
|
77
|
+
checks/
|
|
78
|
+
mdx-static.json
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Commit the MDX and relevant `agent/page-writer/...` artifacts together.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Research
|
|
2
|
+
|
|
3
|
+
The research phase builds the source pack used to write the page. It does not
|
|
4
|
+
write the MDX.
|
|
5
|
+
|
|
6
|
+
## Source Order
|
|
7
|
+
|
|
8
|
+
Use sources in this order:
|
|
9
|
+
|
|
10
|
+
1. Selected `geo_page` action and run context.
|
|
11
|
+
2. Tenant workspace KB / living document.
|
|
12
|
+
3. Product catalog or action `targetProductIds`.
|
|
13
|
+
4. Exa Deep Search for external evidence.
|
|
14
|
+
5. Image search for existing brand assets.
|
|
15
|
+
|
|
16
|
+
Prefer first-party and workspace facts over external summaries. Use external
|
|
17
|
+
research to fill market, comparison, definition, and citation gaps.
|
|
18
|
+
|
|
19
|
+
## Exa Deep Search
|
|
20
|
+
|
|
21
|
+
`collect-page-sources.mjs exa` uses Exa `/search` with `type:
|
|
22
|
+
"deep-reasoning"`. Exa's older `/research/v1` API is deprecated, so do not use
|
|
23
|
+
it for new work.
|
|
24
|
+
|
|
25
|
+
The Exa result is a research input, not page copy. Treat its synthesized answer
|
|
26
|
+
as a draft finding that still needs source-aware judgment.
|
|
27
|
+
|
|
28
|
+
## KB Search
|
|
29
|
+
|
|
30
|
+
Use KB search for brand-specific facts:
|
|
31
|
+
|
|
32
|
+
- product positioning
|
|
33
|
+
- process and methodology
|
|
34
|
+
- pricing or packaging claims
|
|
35
|
+
- company history
|
|
36
|
+
- differentiators
|
|
37
|
+
- support, FAQ, and policy details
|
|
38
|
+
|
|
39
|
+
If KB search is unavailable, mark the missing query in the source pack and avoid
|
|
40
|
+
brand-specific factual claims that need that evidence.
|
|
41
|
+
|
|
42
|
+
## Image Search
|
|
43
|
+
|
|
44
|
+
Use image search only for existing workspace assets. Do not generate images in
|
|
45
|
+
this skill.
|
|
46
|
+
|
|
47
|
+
Every MDX image URL should come from:
|
|
48
|
+
|
|
49
|
+
- the source pack image results
|
|
50
|
+
- product catalog image inputs
|
|
51
|
+
- an existing tenant asset already present in the MDX/repo
|
|
52
|
+
|
|
53
|
+
## Source Pack Rules
|
|
54
|
+
|
|
55
|
+
The source pack must make writing decisions auditable:
|
|
56
|
+
|
|
57
|
+
- Keep source URLs and titles.
|
|
58
|
+
- Keep KB snippets separate from external web sources.
|
|
59
|
+
- Keep image results separate from factual sources.
|
|
60
|
+
- Record failures instead of silently omitting a source class.
|
|
61
|
+
- Do not promote a source into a claim unless the source actually supports it.
|