mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-01 21:02:12 +03:00
Adds /skills/omniroute*/SKILL.md following the Anthropic skill manifest spec (frontmatter name/description + self-contained body): chat, image, tts, stt, embeddings, web-search, web-fetch, mcp, a2a + entry point. External agents (Claude Desktop, ChatGPT, Cursor, Cline) can fetch one raw GitHub URL to learn how to call OmniRoute — zero-friction onboarding. OmniRoute-specific differentiators (mcp + a2a skills) extend the 9router pattern with 2 extra manifests not present in the reference. Adds structural lint test (tests/unit/docs/skillManifestsLint.test.ts) enforcing frontmatter, env-var references, and trigger-phrase quality. Adds "AI Agent Skills" section to README.md root. Ref: 9router/skills/ pattern (adapted).
44 lines
1.7 KiB
TypeScript
44 lines
1.7 KiB
TypeScript
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import { readdir, readFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
|
|
const SKILLS_DIR = join(process.cwd(), "skills");
|
|
const REQUIRED_FRONTMATTER = ["name:", "description:"];
|
|
|
|
async function listSkillDirs(): Promise<string[]> {
|
|
const entries = await readdir(SKILLS_DIR, { withFileTypes: true });
|
|
return entries
|
|
.filter((e) => e.isDirectory() && e.name.startsWith("omniroute"))
|
|
.map((e) => e.name);
|
|
}
|
|
|
|
test("each skill dir has SKILL.md with frontmatter", async () => {
|
|
const dirs = await listSkillDirs();
|
|
assert.ok(dirs.length >= 9, `Expected ≥9 skill dirs, got ${dirs.length}`);
|
|
for (const dir of dirs) {
|
|
const path = join(SKILLS_DIR, dir, "SKILL.md");
|
|
const content = await readFile(path, "utf-8");
|
|
assert.ok(content.startsWith("---\n"), `${dir}: missing opening frontmatter`);
|
|
for (const key of REQUIRED_FRONTMATTER) {
|
|
assert.ok(content.includes(key), `${dir}: missing frontmatter key ${key}`);
|
|
}
|
|
assert.ok(
|
|
content.includes("$OMNIROUTE_URL") || content.includes("OMNIROUTE_KEY"),
|
|
`${dir}: missing env-var references`
|
|
);
|
|
}
|
|
});
|
|
|
|
test("description field is meaningful (≥50 chars, has 'Use when')", async () => {
|
|
const dirs = await listSkillDirs();
|
|
for (const dir of dirs) {
|
|
const content = await readFile(join(SKILLS_DIR, dir, "SKILL.md"), "utf-8");
|
|
const match = content.match(/^description:\s*(.+?)$/m);
|
|
assert.ok(match, `${dir}: no description field`);
|
|
const desc = match![1];
|
|
assert.ok(desc.length >= 50, `${dir}: description too short (${desc.length})`);
|
|
assert.ok(/use when/i.test(desc), `${dir}: description missing "Use when" trigger phrase`);
|
|
}
|
|
});
|