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.
Files changed (63) hide show
  1. package/README.md +50 -36
  2. package/dist/index.js +9231 -251
  3. package/dist/skillpacks/geo/scaffold/AGENTS.md +38 -0
  4. package/dist/skillpacks/geo/scaffold/CLAUDE.md +33 -0
  5. package/dist/skillpacks/geo/shared/evidence-principles.md +35 -0
  6. package/dist/skillpacks/geo/shared/geo-principles.md +281 -0
  7. package/dist/skillpacks/geo/shared/vertical-playbooks/beauty.md +65 -0
  8. package/dist/skillpacks/geo/shared/vertical-playbooks/commerce.md +65 -0
  9. package/dist/skillpacks/geo/shared/vertical-playbooks/ota.md +62 -0
  10. package/dist/skillpacks/geo/shared/vertical-playbooks/saas.md +64 -0
  11. package/dist/skillpacks/geo/skills/brand-owned-diagnosis/SKILL.md +1 -1
  12. package/dist/skillpacks/geo/skills/brand-owned-diagnosis/references/diagnosis-contract.md +8 -9
  13. package/dist/skillpacks/geo/skills/brand-owned-diagnosis/references/workflow.md +11 -8
  14. package/dist/skillpacks/geo/skills/geo-generating-actions/SKILL.md +110 -22
  15. package/dist/skillpacks/geo/skills/geo-generating-actions/references/orchestrator.workflow.md +171 -5
  16. package/dist/skillpacks/geo/skills/geo-generating-actions/scripts/geo-scaffold.mjs +358 -0
  17. package/dist/skillpacks/geo/skills/geo-generating-actions/scripts/geo.mjs +66 -0
  18. package/dist/skillpacks/geo/skills/geo-initializing-strategy/references/foundation-diagnosis.md +1 -1
  19. package/dist/skillpacks/geo/skills/geo-local-setup/SKILL.md +66 -0
  20. package/dist/skillpacks/geo/skills/geo-local-setup/agents/openai.yaml +4 -0
  21. package/dist/skillpacks/geo/skills/geo-page-writer/SKILL.md +81 -0
  22. package/dist/skillpacks/geo/skills/geo-page-writer/agents/openai.yaml +4 -0
  23. package/dist/skillpacks/geo/skills/geo-page-writer/references/research.md +61 -0
  24. package/dist/skillpacks/geo/skills/geo-page-writer/references/validation.md +59 -0
  25. package/dist/skillpacks/geo/skills/geo-page-writer/references/writing.md +55 -0
  26. package/dist/skillpacks/geo/skills/geo-page-writer/scripts/check-page-mdx.mjs +210 -0
  27. package/dist/skillpacks/geo/skills/geo-page-writer/scripts/collect-page-sources.mjs +303 -0
  28. package/dist/skillpacks/geo/skills/geo-pages-diagnosis/SKILL.md +1 -1
  29. package/dist/skillpacks/geo/skills/geo-pages-diagnosis/references/diagnosis-contract.md +36 -17
  30. package/dist/skillpacks/geo/skills/geo-pages-diagnosis/references/workflow.md +65 -15
  31. package/dist/skillpacks/geo/skills/seo-ecommerce-opportunity/SKILL.md +82 -0
  32. package/dist/skillpacks/geo/skills/seo-ecommerce-opportunity/agents/openai.yaml +5 -0
  33. package/dist/skillpacks/geo/skills/seo-ecommerce-opportunity/references/ecommerce-rules.md +31 -0
  34. package/dist/skillpacks/geo/skills/seo-internal-link-opportunity/SKILL.md +57 -0
  35. package/dist/skillpacks/geo/skills/seo-internal-link-opportunity/agents/openai.yaml +5 -0
  36. package/dist/skillpacks/geo/skills/seo-internal-link-opportunity/references/link-rules.md +28 -0
  37. package/dist/skillpacks/geo/skills/seo-opportunity-audit/SKILL.md +141 -0
  38. package/dist/skillpacks/geo/skills/seo-opportunity-audit/agents/openai.yaml +5 -0
  39. package/dist/skillpacks/geo/skills/seo-opportunity-audit/references/action-contract.md +62 -0
  40. package/dist/skillpacks/geo/skills/seo-opportunity-audit/scripts/seo-toolkit.mjs +248 -0
  41. package/dist/skillpacks/geo/skills/seo-page-diagnosis/SKILL.md +56 -0
  42. package/dist/skillpacks/geo/skills/seo-page-diagnosis/agents/openai.yaml +5 -0
  43. package/dist/skillpacks/geo/skills/seo-page-diagnosis/references/page-checks.md +38 -0
  44. package/dist/skillpacks/geo/skills/seo-serp-opportunity-research/SKILL.md +66 -0
  45. package/dist/skillpacks/geo/skills/seo-serp-opportunity-research/agents/openai.yaml +5 -0
  46. package/dist/skillpacks/geo/skills/seo-serp-opportunity-research/references/page-type-taxonomy.md +40 -0
  47. package/dist/skillpacks/geo/skills/seo-serp-opportunity-research/references/serp-methodology.md +38 -0
  48. package/dist/skillpacks/geo/skills/seo-technical-diagnosis/SKILL.md +64 -0
  49. package/dist/skillpacks/geo/skills/seo-technical-diagnosis/agents/openai.yaml +5 -0
  50. package/dist/skillpacks/geo/skills/seo-technical-diagnosis/references/checks.md +58 -0
  51. package/dist/skillpacks/geo/skills/third-party-diagnosis/SKILL.md +1 -1
  52. package/dist/skillpacks/geo/skills/third-party-diagnosis/references/diagnosis-contract.md +2 -2
  53. package/dist/skillpacks/geo/skills/third-party-diagnosis/references/workflow.md +1 -1
  54. package/dist/skillpacks/geo/skills/third-party-execution-planning/SKILL.md +64 -0
  55. package/dist/skillpacks/geo/skills/third-party-execution-planning/agents/openai.yaml +4 -0
  56. package/dist/skillpacks/geo/skills/third-party-execution-planning/references/execution-contract.md +90 -0
  57. package/dist/skillpacks/geo/skills/third-party-execution-planning/references/non-social-surface-playbooks.md +123 -0
  58. package/package.json +2 -1
  59. package/dist/skillpacks/geo/skills/social-execution-planning/SKILL.md +0 -53
  60. package/dist/skillpacks/geo/skills/social-execution-planning/agents/openai.yaml +0 -5
  61. package/dist/skillpacks/geo/skills/social-execution-planning/references/execution-contract.md +0 -68
  62. /package/dist/skillpacks/geo/skills/{social-execution-planning → third-party-execution-planning}/references/reddit-rules.md +0 -0
  63. /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
+ }
@@ -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 CMS source or page HTML via `get_page_source_code`, `get_page_content`, or summarized scrape.
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,4 @@
1
+ interface:
2
+ display_name: "GEO Local Setup"
3
+ short_description: "Set up and verify a tenant repo for local GEO work."
4
+ default_prompt: "Use $geo-local-setup to set up or check this tenant repo for local GEO work."
@@ -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,4 @@
1
+ interface:
2
+ display_name: "GEO Page Writer"
3
+ short_description: "Write and validate real CMS MDX for selected GEO page actions."
4
+ default_prompt: "Use $geo-page-writer to collect sources, write the GEO page MDX, and run static MDX validation."
@@ -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.