feat: content-pipeline/ → agents/ — formalize the agent system in the seed
Adopt the agents/ architecture proven on medellin.co (reference impl):
- Move the content engine to a top-level agents/ dir: orchestrators, prompts,
config, run.sh, admin console, shared libs. All content-pipeline literals
repointed (config paths, scripts, admin, LLM-facing prompts/image.md string,
configure.mjs, new-site.sh, astroagent tokenFile, .gitignore runtime block).
- Every script carries a parseable @agent-manifest header: name, title, class
(content|operational|runtime|plumbing), trigger, model, prompts, skills (MCP),
tools, reads/writes tables. 5 content agents + 3 plumbing scripts.
- New agents/catalog.mjs generates the catalog from the headers:
agents/AGENTS.md (human, grouped by class) + agents/agents.json (machine
manifest — a clone diffs it against a source to find missing tools/tables/MCP
before running). configure.mjs regenerates the catalog on every identity
stamp. No DB table, no watcher.
- config.json gains paths.stateDir/newsDir; publish-tick, write-daily, and
news-radar read them instead of hardcoding.
- Full cut: content-pipeline/ deleted (the seed has no live crons, so no
hybrid period needed). Docs updated (AGENTS.md structure + pipeline section,
README paths).
Clones migrating from content-pipeline/: see medellin.co's
.memory/handoffs/agents-directory-migration.md for the cutover playbook
(one cron set active at a time; migrate drafts/state after repointing cron).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMQeUnUrAeexcZ7P2Hxa6G
2026-07-11 20:33:48 +00:00
|
|
|
#!/usr/bin/env node
|
|
|
|
|
// catalog.mjs — generate the agent catalog from @agent-manifest headers.
|
|
|
|
|
//
|
|
|
|
|
// Scans agents/scripts/**/*.mjs plus the runtime PHP agents listed below, extracts each
|
|
|
|
|
// fenced `@agent-manifest … @end` block, and emits:
|
|
|
|
|
// agents/AGENTS.md — human catalog, one table per class (GENERATED — do not edit)
|
|
|
|
|
// agents/agents.json — machine manifest; clones diff this to find missing deps
|
|
|
|
|
//
|
|
|
|
|
// Run after adding/editing an agent: node agents/catalog.mjs
|
|
|
|
|
// (also runs automatically at the end of scripts/configure.mjs)
|
|
|
|
|
import { readFileSync, writeFileSync, readdirSync } from "node:fs";
|
|
|
|
|
import { dirname, join, relative } from "node:path";
|
2026-07-24 12:03:00 +00:00
|
|
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
feat: content-pipeline/ → agents/ — formalize the agent system in the seed
Adopt the agents/ architecture proven on medellin.co (reference impl):
- Move the content engine to a top-level agents/ dir: orchestrators, prompts,
config, run.sh, admin console, shared libs. All content-pipeline literals
repointed (config paths, scripts, admin, LLM-facing prompts/image.md string,
configure.mjs, new-site.sh, astroagent tokenFile, .gitignore runtime block).
- Every script carries a parseable @agent-manifest header: name, title, class
(content|operational|runtime|plumbing), trigger, model, prompts, skills (MCP),
tools, reads/writes tables. 5 content agents + 3 plumbing scripts.
- New agents/catalog.mjs generates the catalog from the headers:
agents/AGENTS.md (human, grouped by class) + agents/agents.json (machine
manifest — a clone diffs it against a source to find missing tools/tables/MCP
before running). configure.mjs regenerates the catalog on every identity
stamp. No DB table, no watcher.
- config.json gains paths.stateDir/newsDir; publish-tick, write-daily, and
news-radar read them instead of hardcoding.
- Full cut: content-pipeline/ deleted (the seed has no live crons, so no
hybrid period needed). Docs updated (AGENTS.md structure + pipeline section,
README paths).
Clones migrating from content-pipeline/: see medellin.co's
.memory/handoffs/agents-directory-migration.md for the cutover playbook
(one cron set active at a time; migrate drafts/state after repointing cron).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMQeUnUrAeexcZ7P2Hxa6G
2026-07-11 20:33:48 +00:00
|
|
|
|
|
|
|
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
|
|
|
const ROOT = join(HERE, "..");
|
|
|
|
|
|
2026-07-24 12:03:00 +00:00
|
|
|
// Runtime agents living outside agents/scripts (the console runner's pluggable
|
|
|
|
|
// modules) — discovered from their JS `manifest` export (see the loop below).
|
|
|
|
|
const RUNTIME_FILES = []; // legacy seam for PHP runtime agents; console agents handled below
|
feat: content-pipeline/ → agents/ — formalize the agent system in the seed
Adopt the agents/ architecture proven on medellin.co (reference impl):
- Move the content engine to a top-level agents/ dir: orchestrators, prompts,
config, run.sh, admin console, shared libs. All content-pipeline literals
repointed (config paths, scripts, admin, LLM-facing prompts/image.md string,
configure.mjs, new-site.sh, astroagent tokenFile, .gitignore runtime block).
- Every script carries a parseable @agent-manifest header: name, title, class
(content|operational|runtime|plumbing), trigger, model, prompts, skills (MCP),
tools, reads/writes tables. 5 content agents + 3 plumbing scripts.
- New agents/catalog.mjs generates the catalog from the headers:
agents/AGENTS.md (human, grouped by class) + agents/agents.json (machine
manifest — a clone diffs it against a source to find missing tools/tables/MCP
before running). configure.mjs regenerates the catalog on every identity
stamp. No DB table, no watcher.
- config.json gains paths.stateDir/newsDir; publish-tick, write-daily, and
news-radar read them instead of hardcoding.
- Full cut: content-pipeline/ deleted (the seed has no live crons, so no
hybrid period needed). Docs updated (AGENTS.md structure + pipeline section,
README paths).
Clones migrating from content-pipeline/: see medellin.co's
.memory/handoffs/agents-directory-migration.md for the cutover playbook
(one cron set active at a time; migrate drafts/state after repointing cron).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMQeUnUrAeexcZ7P2Hxa6G
2026-07-11 20:33:48 +00:00
|
|
|
|
|
|
|
|
const CLASSES = ["content", "operational", "runtime", "plumbing"];
|
|
|
|
|
const LIST_KEYS = ["prompt", "skills", "tools", "reads", "writes"];
|
|
|
|
|
|
|
|
|
|
function parseManifest(src) {
|
|
|
|
|
const m = src.match(/@agent-manifest\s*\n([\s\S]*?)\n\s*\*?\s*@end/);
|
|
|
|
|
if (!m) return null;
|
|
|
|
|
const out = {};
|
|
|
|
|
for (const raw of m[1].split("\n")) {
|
|
|
|
|
const line = raw.replace(/^\s*\*\s?/, ""); // strip comment leader
|
|
|
|
|
const kv = line.match(/^([a-z-]+):\s*(.*)$/);
|
|
|
|
|
if (kv) out[kv[1]] = kv[2].trim();
|
|
|
|
|
}
|
|
|
|
|
for (const k of LIST_KEYS) {
|
|
|
|
|
const v = out[k];
|
|
|
|
|
out[k] = !v || v === "-" ? [] : v.split(",").map((s) => s.trim()).filter(Boolean);
|
|
|
|
|
}
|
|
|
|
|
return out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const files = readdirSync(join(HERE, "scripts"))
|
|
|
|
|
.filter((f) => f.endsWith(".mjs"))
|
|
|
|
|
.map((f) => join("agents", "scripts", f))
|
|
|
|
|
.concat(RUNTIME_FILES);
|
|
|
|
|
|
|
|
|
|
const agents = [];
|
|
|
|
|
for (const rel of files.sort()) {
|
|
|
|
|
const manifest = parseManifest(readFileSync(join(ROOT, rel), "utf8"));
|
|
|
|
|
if (!manifest) continue; // no manifest → not an agent (e.g. future helpers)
|
|
|
|
|
if (!manifest.name || !manifest.class) {
|
|
|
|
|
console.error(`[catalog] ${rel}: manifest missing name/class — skipped`);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (!CLASSES.includes(manifest.class)) {
|
|
|
|
|
console.error(`[catalog] ${rel}: unknown class "${manifest.class}" — skipped`);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
agents.push({ file: rel, ...manifest });
|
|
|
|
|
}
|
2026-07-24 12:03:00 +00:00
|
|
|
|
|
|
|
|
// ---- runtime (console) agents: JS `manifest` exports ----
|
|
|
|
|
// Each agents/console/agents/*.mjs exports a `manifest` object. Importing a
|
|
|
|
|
// module only defines it (register()/listen() aren't called), so this is safe.
|
|
|
|
|
const consoleDir = join(HERE, "console", "agents");
|
|
|
|
|
let aiModel = "-";
|
|
|
|
|
try { aiModel = JSON.parse(readFileSync(join(ROOT, "astroagent.config.json"), "utf8")).ai?.model || "-"; } catch {}
|
|
|
|
|
let consoleFiles = [];
|
|
|
|
|
try { consoleFiles = readdirSync(consoleDir).filter((f) => f.endsWith(".mjs")); } catch {}
|
|
|
|
|
for (const f of consoleFiles.sort()) {
|
|
|
|
|
const rel = ["agents", "console", "agents", f].join("/");
|
|
|
|
|
let mod;
|
|
|
|
|
try { mod = await import(pathToFileURL(join(consoleDir, f)).href); }
|
|
|
|
|
catch (e) { console.error(`[catalog] ${rel}: import failed — ${e.message}`); continue; }
|
|
|
|
|
const m = mod.manifest;
|
|
|
|
|
if (!m || !m.name || !m.class) continue;
|
|
|
|
|
const trigger = (m.triggers || []).map((t) =>
|
|
|
|
|
t.type === "endpoint" ? `${t.method} ${t.path}` : t.type === "schedule" ? `schedule(${t.settingKey})` : t.type
|
|
|
|
|
).join("; ");
|
|
|
|
|
const toolsArr = typeof m.tools === "string" ? m.tools.split(/\s+/).filter(Boolean) : (m.tools || []);
|
|
|
|
|
agents.push({
|
|
|
|
|
file: rel, name: m.name, title: m.title || m.name, class: m.class,
|
|
|
|
|
trigger, model: aiModel, description: m.description || "",
|
|
|
|
|
prompt: [], skills: m.skills || [],
|
|
|
|
|
tools: [...toolsArr, ...(m.cli || []).map((c) => `api/cli/${c}.php`)],
|
|
|
|
|
reads: m.tables || [], writes: m.tables || [], created: "",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
feat: content-pipeline/ → agents/ — formalize the agent system in the seed
Adopt the agents/ architecture proven on medellin.co (reference impl):
- Move the content engine to a top-level agents/ dir: orchestrators, prompts,
config, run.sh, admin console, shared libs. All content-pipeline literals
repointed (config paths, scripts, admin, LLM-facing prompts/image.md string,
configure.mjs, new-site.sh, astroagent tokenFile, .gitignore runtime block).
- Every script carries a parseable @agent-manifest header: name, title, class
(content|operational|runtime|plumbing), trigger, model, prompts, skills (MCP),
tools, reads/writes tables. 5 content agents + 3 plumbing scripts.
- New agents/catalog.mjs generates the catalog from the headers:
agents/AGENTS.md (human, grouped by class) + agents/agents.json (machine
manifest — a clone diffs it against a source to find missing tools/tables/MCP
before running). configure.mjs regenerates the catalog on every identity
stamp. No DB table, no watcher.
- config.json gains paths.stateDir/newsDir; publish-tick, write-daily, and
news-radar read them instead of hardcoding.
- Full cut: content-pipeline/ deleted (the seed has no live crons, so no
hybrid period needed). Docs updated (AGENTS.md structure + pipeline section,
README paths).
Clones migrating from content-pipeline/: see medellin.co's
.memory/handoffs/agents-directory-migration.md for the cutover playbook
(one cron set active at a time; migrate drafts/state after repointing cron).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMQeUnUrAeexcZ7P2Hxa6G
2026-07-11 20:33:48 +00:00
|
|
|
agents.sort((a, b) => a.name.localeCompare(b.name));
|
|
|
|
|
|
|
|
|
|
// ---- agents.json (machine manifest) ----
|
|
|
|
|
writeFileSync(join(HERE, "agents.json"), JSON.stringify({ agents }, null, 2) + "\n");
|
|
|
|
|
|
|
|
|
|
// ---- AGENTS.md (human catalog) ----
|
|
|
|
|
const esc = (s) => String(s).replace(/\|/g, "\\|");
|
|
|
|
|
const classBlurb = {
|
|
|
|
|
content: "Cron/manual-triggered producers of gated drafts (events auto-publishes).",
|
|
|
|
|
operational: "Event/queue-triggered reviewers and responders (gated + logged).",
|
|
|
|
|
runtime: "HTTP-triggered agents answering live user requests.",
|
|
|
|
|
plumbing: "Deterministic helpers — not agents (no LLM), catalogued for completeness.",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let md = `# Agent catalog
|
|
|
|
|
|
|
|
|
|
> **GENERATED — do not edit.** Regenerate with \`node agents/catalog.mjs\`
|
|
|
|
|
> (source of truth: the \`@agent-manifest\` header in each script).
|
|
|
|
|
|
|
|
|
|
An **agent** = an LLM orchestrator script + its role prompt (\`prompt:\`) + its skills
|
|
|
|
|
(MCP servers, \`skills:\`) + its tools (\`api/cli/*.php\` bridges and \`lib/*.mjs\`, \`tools:\`).
|
|
|
|
|
To port an agent to another SeedProject clone, satisfy its manifest: copy its prompts, ensure
|
|
|
|
|
its tools exist, migrate each table in \`reads:\`/\`writes:\`, configure its MCP servers.
|
|
|
|
|
\`agents.json\` is the machine-readable version — diff it against a clone to find gaps.
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
for (const cls of CLASSES) {
|
|
|
|
|
const group = agents.filter((a) => a.class === cls);
|
|
|
|
|
if (!group.length) continue;
|
|
|
|
|
md += `\n## ${cls}\n\n${classBlurb[cls]}\n\n`;
|
|
|
|
|
md += `| agent | trigger | model | skills | tools | reads → writes | description |\n`;
|
|
|
|
|
md += `|---|---|---|---|---|---|---|\n`;
|
|
|
|
|
for (const a of group) {
|
|
|
|
|
const rw = `${a.reads.join(", ") || "—"} → ${a.writes.join(", ") || "—"}`;
|
|
|
|
|
md += `| **${esc(a.name)}** ([${esc(a.file)}](../${a.file})) | ${esc(a.trigger || "—")} | ${esc(a.model || "—")} | ${esc(a.skills.join(", ") || "—")} | ${esc(a.tools.join(", ") || "—")} | ${esc(rw)} | ${esc(a.description || "")} |\n`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
writeFileSync(join(HERE, "AGENTS.md"), md);
|
|
|
|
|
console.log(`[catalog] ${agents.length} manifests → agents/AGENTS.md + agents/agents.json`);
|