feat: partners.json single source + sync script

partners.json is now the canonical partner registry. scripts/sync-partners.mjs
regenerates the README Partners section and the tools/REGISTRY.md Verified
Partners table (between PARTNERS:START/END markers) from it — run after editing
partners.json, or 'node scripts/sync-partners.mjs --check' in CI. The
marketing-skills.com site reads the same partners.json directly (raw GitHub), so
one edit updates every surface. Deactivate a lapsed partner with active:false.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Corey Haines
2026-08-24 12:19:23 -07:00
parent 997ab1a351
commit b6fc21ef94
4 changed files with 102 additions and 1 deletions
+3 -1
View File
@@ -14,9 +14,11 @@ Run into a problem or have a question? [Open an issue](https://github.com/coreyh
The library is free and MIT-licensed. [Verified Partners](tools/REGISTRY.md#verified-partners) fund the work — vetted, disclosed tool integrations, listed alongside the neutral options and never influencing what the core skills recommend. [Become a partner →](https://marketing-skills.com/sponsorship)
<!-- PARTNERS:START -->
> ◆ **[Converly](https://converly.io?ref=marketingskills)** — *Conversion tracking / attribution.* Server-side conversion tracking that fires when someone submits a form, books a meeting, or starts a chat — passing click IDs and identifiers for Enhanced Conversions (Google) and high EMQ match rates (Meta), across 100+ tools. CLI + MCP so your agent sets it up in minutes. → [Integration guide](tools/integrations/converly.md)
<!-- PARTNERS:END -->
<!-- Featured logo goes here once Converly provides an SVG/PNG asset. -->
<!-- The Partners block above is generated from partners.json — run `node scripts/sync-partners.mjs` after editing it. -->
## What are Skills?
+18
View File
@@ -0,0 +1,18 @@
{
"_comment": "Canonical partner registry — single source of truth. Fill one object per partner from their onboarding packet, then run `node scripts/sync-partners.mjs` to regenerate the README Partners section and the Verified Partners table in tools/REGISTRY.md. The marketing-skills.com site fetches this file directly (raw GitHub). Deactivate a lapsed partner with active:false (keeps history, removes it everywhere).",
"partners": [
{
"id": "converly",
"name": "Converly",
"url": "https://converly.io",
"category": "Marketing analytics & attribution",
"categoryShort": "Conversion tracking / attribution",
"tier": "skill-partner",
"tagline": "Server-side conversion tracking",
"blurb": "Server-side conversion tracking that fires when someone submits a form, books a meeting, or starts a chat — passing click IDs and identifiers for Enhanced Conversions (Google) and high EMQ match rates (Meta), across 100+ tools. CLI + MCP so your agent sets it up in minutes.",
"logoUrl": "",
"integration": "tools/integrations/converly.md",
"active": true
}
]
}
+77
View File
@@ -0,0 +1,77 @@
#!/usr/bin/env node
// Regenerate every partner surface from the canonical partners.json.
//
// node scripts/sync-partners.mjs # write the generated blocks
// node scripts/sync-partners.mjs --check # fail if anything is out of date (CI)
//
// Owns the marked blocks in:
// - README.md → the Partners section list
// - tools/REGISTRY.md → the Verified Partners table
// Everything outside the <!-- PARTNERS:START --> / <!-- PARTNERS:END --> markers
// is left untouched. The marketing-skills.com site reads partners.json directly.
import { readFileSync, writeFileSync } from "node:fs";
import { resolve, dirname } from "node:path";
import { fileURLToPath } from "node:url";
const HERE = dirname(fileURLToPath(import.meta.url));
const ROOT = resolve(HERE, "..");
const REPO = "https://github.com/coreyhaines31/marketingskills/blob/main";
const REF = "?ref=marketingskills";
const START = "<!-- PARTNERS:START -->";
const END = "<!-- PARTNERS:END -->";
const check = process.argv.includes("--check");
const { partners } = JSON.parse(readFileSync(resolve(ROOT, "partners.json"), "utf8"));
const active = partners.filter((p) => p.active);
const readmeBlock = active.length
? active
.map(
(p) =>
`> ◆ **[${p.name}](${p.url}${REF})** — *${p.categoryShort || p.category}.* ${p.blurb} → [Integration guide](${p.integration})`
)
.join("\n\n")
: "_No active partners yet. [Become a partner →](https://marketing-skills.com/sponsorship)_";
const registryBlock = [
"| Partner | Category | Guide |",
"|---------|----------|-------|",
...active.map(
(p) =>
`| ◆ ${p.name} | ${p.categoryShort || p.category} | [${p.integration.split("/").pop()}](${p.integration.replace(/^tools\//, "")}) |`
),
].join("\n");
const targets = [
{ file: "README.md", block: readmeBlock },
{ file: "tools/REGISTRY.md", block: registryBlock },
];
let stale = false;
for (const { file, block } of targets) {
const path = resolve(ROOT, file);
const src = readFileSync(path, "utf8");
const i = src.indexOf(START);
const j = src.indexOf(END);
if (i === -1 || j === -1 || j < i) {
console.error(`${file}: missing ${START} / ${END} markers`);
process.exitCode = 1;
continue;
}
const next = src.slice(0, i + START.length) + "\n" + block + "\n" + src.slice(j);
if (next === src) {
console.log(`${file} up to date`);
continue;
}
if (check) {
console.error(`${file} is out of date — run: node scripts/sync-partners.mjs`);
stale = true;
continue;
}
writeFileSync(path, next);
console.log(`${file} updated (${active.length} partner${active.length === 1 ? "" : "s"})`);
}
if (check && stale) process.exitCode = 1;
+4
View File
@@ -17,9 +17,13 @@ Quick reference for AI agents to discover tool capabilities and integration meth
- **Disclosed + vetted for fit.** Each carries a disclosure header in its integration guide and is marked ◆ in the index below.
- **Additive, never biasing.** A partner is listed *alongside* the neutral options for the same job, never instead of them. Partner status never removes or demotes another tool and **never changes what any skill recommends** — if a non-partner is the right answer, that's the answer. The badge means "paid, disclosed, vetted for fit," not "best in category."
<!-- PARTNERS:START -->
| Partner | Category | Guide |
|---------|----------|-------|
| ◆ Converly | Conversion tracking / attribution | [converly.md](integrations/converly.md) |
<!-- PARTNERS:END -->
<!-- The table above is generated from partners.json — run `node scripts/sync-partners.mjs`. -->
---