@joelbonito/mcp-server 5.0.1

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 ADDED
@@ -0,0 +1,134 @@
1
+ # @joelbonito/mcp-server
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@joelbonito/mcp-server)](https://www.npmjs.com/package/@joelbonito/mcp-server)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/JoelBonito/inove-ai-framework/blob/main/LICENSE)
5
+
6
+ MCP server for **Inove AI Framework** — 21 agents, 41 skills, and 22 workflows served via the Model Context Protocol.
7
+
8
+ Zero disk usage per project. Auto-updates on every run. Setup in 30 seconds.
9
+
10
+ ## Quick Start
11
+
12
+ ### Claude Code
13
+
14
+ ```bash
15
+ claude mcp add inove-ai -- npx -y @joelbonito/mcp-server
16
+ ```
17
+
18
+ ### Cursor / VS Code
19
+
20
+ Add to `.cursor/mcp.json` or `.vscode/mcp.json`:
21
+
22
+ ```json
23
+ {
24
+ "mcpServers": {
25
+ "inove-ai": {
26
+ "command": "npx",
27
+ "args": ["-y", "@joelbonito/mcp-server"]
28
+ }
29
+ }
30
+ }
31
+ ```
32
+
33
+ ### Windsurf / Cline
34
+
35
+ Any MCP-compatible tool works — point it to `npx -y @joelbonito/mcp-server` as a stdio server.
36
+
37
+ ## What You Get
38
+
39
+ ### 8 Tools
40
+
41
+ | Tool | Description |
42
+ |------|-------------|
43
+ | `list_agents` | List all 21 specialized agents |
44
+ | `list_skills` | List all 41 modular skills |
45
+ | `list_workflows` | List all 22 workflows |
46
+ | `get_agent` | Get full agent content by name |
47
+ | `get_skill` | Get skill content (SKILL.md + sub-files) |
48
+ | `get_workflow` | Get workflow content by name |
49
+ | `route_task` | Recommend agents + skills for a task description |
50
+ | `search_content` | Full-text search across all content |
51
+
52
+ ### 6 Resources
53
+
54
+ | URI | Description |
55
+ |-----|-------------|
56
+ | `inove://agents` | JSON list of all agents |
57
+ | `inove://agents/{name}` | Full agent markdown |
58
+ | `inove://skills/{name}` | Full skill markdown |
59
+ | `inove://workflows/{name}` | Full workflow markdown |
60
+ | `inove://architecture` | Architecture documentation |
61
+ | `inove://instructions` | Framework instructions |
62
+
63
+ ### 22 Prompts
64
+
65
+ One prompt per workflow: `define`, `debug`, `create`, `brainstorm`, `enhance`, `deploy`, `test`, `track`, `status`, `log`, `finish`, `orchestrate`, `plan`, `preview`, `ui-ux-pro-max`, `review`, `test-book`, `release`, `squad`, `context`, `readiness`, `journeys`.
66
+
67
+ ## Agents
68
+
69
+ | Agent | Domain |
70
+ |-------|--------|
71
+ | `orchestrator` | Multi-agent coordination |
72
+ | `project-planner` | Architecture, system design |
73
+ | `product-manager` | Requirements, discovery |
74
+ | `product-owner` | User stories, backlog, MVP |
75
+ | `frontend-specialist` | React, UI/UX, Tailwind |
76
+ | `backend-specialist` | APIs, Node.js, server |
77
+ | `database-architect` | Schemas, queries, migrations |
78
+ | `mobile-developer` | iOS, Android, React Native |
79
+ | `security-auditor` | Auth, OWASP, compliance |
80
+ | `penetration-tester` | Offensive security testing |
81
+ | `debugger` | Root cause analysis |
82
+ | `devops-engineer` | CI/CD, Docker, infra |
83
+ | `test-engineer` | Test strategies, TDD |
84
+ | `qa-automation-engineer` | E2E, Playwright, automation |
85
+ | `performance-optimizer` | Speed, Core Web Vitals |
86
+ | `seo-specialist` | SEO, visibility, ranking |
87
+ | `documentation-writer` | Technical documentation |
88
+ | `code-archaeologist` | Legacy code, refactoring |
89
+ | `game-developer` | Game logic, engines |
90
+ | `ux-researcher` | User flows, wireframes, usability |
91
+ | `explorer-agent` | Codebase analysis |
92
+
93
+ ## Usage Examples
94
+
95
+ ```
96
+ User: "Help me debug this React authentication bug"
97
+ AI: [calls route_task] → recommends debugger + security-auditor
98
+ [calls get_agent("debugger")] → loads persona + protocols
99
+ → Starts systematic debugging with root cause analysis
100
+ ```
101
+
102
+ ```
103
+ User: "List available workflows"
104
+ AI: [calls list_workflows] → 22 workflows with descriptions
105
+ → Shows /define, /debug, /create, /deploy, etc.
106
+ ```
107
+
108
+ ## Requirements
109
+
110
+ - Node.js >= 22
111
+ - Any MCP-compatible AI tool
112
+
113
+ ## Development
114
+
115
+ ```bash
116
+ git clone https://github.com/JoelBonito/inove-ai-framework.git
117
+ cd inove-ai-framework/mcp-server
118
+ npm install
119
+ npm run dev # Reads from .agents/ filesystem (hot reload)
120
+ npm test # 91 tests
121
+ npm run build # Bundles content + compiles TypeScript
122
+ ```
123
+
124
+ ## Architecture
125
+
126
+ - **stdio transport** for local use via `npx`
127
+ - **Cloudflare Workers** for remote SSE access
128
+ - Content from `.agents/` is embedded at build-time into `registry.ts`
129
+ - In dev mode, reads directly from the filesystem for instant feedback
130
+ - Only 3 runtime dependencies: `@modelcontextprotocol/sdk`, `yaml`, `zod`
131
+
132
+ ## License
133
+
134
+ MIT
@@ -0,0 +1,3 @@
1
+ export declare const SERVER_NAME = "inove-ai";
2
+ export declare const SERVER_VERSION = "5.0.1";
3
+ export declare const ROUTING_KEYWORDS: Record<string, string[]>;
@@ -0,0 +1,26 @@
1
+ export const SERVER_NAME = "inove-ai";
2
+ export const SERVER_VERSION = "5.0.1";
3
+ export const ROUTING_KEYWORDS = {
4
+ "frontend-specialist": ["ui", "componente", "pagina", "frontend", "react", "tailwind", "css", "layout"],
5
+ "backend-specialist": ["api", "endpoint", "backend", "servidor", "server", "node", "express", "fastapi"],
6
+ "database-architect": ["database", "schema", "query", "migracao", "sql", "prisma", "drizzle"],
7
+ "mobile-developer": ["mobile", "ios", "android", "react native", "flutter", "app"],
8
+ "security-auditor": ["auth", "seguranca", "vulnerabilidade", "owasp", "security", "jwt"],
9
+ "debugger": ["bug", "erro", "nao funciona", "debug", "error", "crash"],
10
+ "qa-automation-engineer": ["teste", "e2e", "ci/cd", "test", "playwright", "jest"],
11
+ "devops-engineer": ["deploy", "docker", "infraestrutura", "ci", "cd", "kubernetes"],
12
+ "product-owner": ["requisitos", "user story", "backlog", "mvp", "product"],
13
+ "ux-researcher": ["ux", "user flow", "wireframe", "jornada", "usabilidade"],
14
+ "performance-optimizer": ["performance", "lento", "slow", "otimizar", "optimize", "lighthouse"],
15
+ "seo-specialist": ["seo", "meta tags", "sitemap", "ranking", "google"],
16
+ "documentation-writer": ["documentacao", "docs", "readme", "manual"],
17
+ "code-archaeologist": ["legacy", "refatorar", "refactor", "divida tecnica", "tech debt"],
18
+ "orchestrator": ["orquestrar", "multi-agente", "coordenar", "complexo"],
19
+ "project-planner": ["arquitetura", "architecture", "sistema", "design system"],
20
+ "product-manager": ["brief", "discovery", "requisito"],
21
+ "penetration-tester": ["pentest", "invasao", "exploit", "security test"],
22
+ "game-developer": ["game", "jogo", "unity", "godot"],
23
+ "test-engineer": ["unit test", "integration test", "tdd", "test strategy"],
24
+ "explorer-agent": ["analise", "codebase", "overview", "explore"],
25
+ };
26
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { ContentCache } from "../types.js";
3
+ export declare function registerPrompts(server: McpServer, cache: ContentCache): void;
@@ -0,0 +1,26 @@
1
+ import { z } from "zod";
2
+ export function registerPrompts(server, cache) {
3
+ for (const [name, workflow] of cache.workflows) {
4
+ server.prompt(name, workflow.meta.description || `Execute the /${name} workflow`, { topic: z.string().optional().describe("Topic or context for this workflow") }, ({ topic }) => ({
5
+ messages: [
6
+ {
7
+ role: "assistant",
8
+ content: {
9
+ type: "text",
10
+ text: workflow.raw,
11
+ },
12
+ },
13
+ {
14
+ role: "user",
15
+ content: {
16
+ type: "text",
17
+ text: topic
18
+ ? `Execute /${name} for: ${topic}`
19
+ : `Execute /${name}`,
20
+ },
21
+ },
22
+ ],
23
+ }));
24
+ }
25
+ }
26
+ //# sourceMappingURL=prompts.js.map
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { ContentCache } from "../types.js";
3
+ export declare function registerResources(server: McpServer, cache: ContentCache): void;
@@ -0,0 +1,45 @@
1
+ import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export function registerResources(server, cache) {
3
+ // Static: list of all agents as JSON
4
+ server.resource("agents-list", "inove://agents", { mimeType: "application/json", description: "List of all agents with name, description and skills" }, () => {
5
+ const list = [...cache.agents.values()].map((a) => ({
6
+ name: a.meta.name,
7
+ description: a.meta.description,
8
+ skills: a.meta.skills,
9
+ }));
10
+ return { contents: [{ uri: "inove://agents", text: JSON.stringify(list, null, 2), mimeType: "application/json" }] };
11
+ });
12
+ // Static: architecture doc
13
+ server.resource("architecture", "inove://architecture", { mimeType: "text/markdown", description: "Framework architecture documentation" }, () => ({
14
+ contents: [{ uri: "inove://architecture", text: cache.architecture, mimeType: "text/markdown" }],
15
+ }));
16
+ // Static: instructions doc
17
+ server.resource("instructions", "inove://instructions", { mimeType: "text/markdown", description: "Framework usage instructions" }, () => ({
18
+ contents: [{ uri: "inove://instructions", text: cache.instructions, mimeType: "text/markdown" }],
19
+ }));
20
+ // Template: individual agent
21
+ server.resource("agent", new ResourceTemplate("inove://agents/{name}", { list: undefined }), { mimeType: "text/markdown", description: "Full content of a specific agent" }, (uri, { name }) => {
22
+ const agent = cache.agents.get(name);
23
+ if (!agent) {
24
+ return { contents: [{ uri: uri.href, text: `Agent "${name}" not found`, mimeType: "text/plain" }] };
25
+ }
26
+ return { contents: [{ uri: uri.href, text: agent.raw, mimeType: "text/markdown" }] };
27
+ });
28
+ // Template: individual skill
29
+ server.resource("skill", new ResourceTemplate("inove://skills/{name}", { list: undefined }), { mimeType: "text/markdown", description: "Full content of a specific skill (SKILL.md + sub-files)" }, (uri, { name }) => {
30
+ const skill = cache.skills.get(name);
31
+ if (!skill) {
32
+ return { contents: [{ uri: uri.href, text: `Skill "${name}" not found`, mimeType: "text/plain" }] };
33
+ }
34
+ return { contents: [{ uri: uri.href, text: skill.raw, mimeType: "text/markdown" }] };
35
+ });
36
+ // Template: individual workflow
37
+ server.resource("workflow", new ResourceTemplate("inove://workflows/{name}", { list: undefined }), { mimeType: "text/markdown", description: "Full content of a specific workflow" }, (uri, { name }) => {
38
+ const workflow = cache.workflows.get(name);
39
+ if (!workflow) {
40
+ return { contents: [{ uri: uri.href, text: `Workflow "${name}" not found`, mimeType: "text/plain" }] };
41
+ }
42
+ return { contents: [{ uri: uri.href, text: workflow.raw, mimeType: "text/markdown" }] };
43
+ });
44
+ }
45
+ //# sourceMappingURL=resources.js.map
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { ContentCache } from "../types.js";
3
+ export declare function registerTools(server: McpServer, cache: ContentCache): void;
@@ -0,0 +1,69 @@
1
+ import { z } from "zod";
2
+ import { routeTask } from "../utils/routing.js";
3
+ import { searchContent } from "../utils/search.js";
4
+ export function registerTools(server, cache) {
5
+ // list_agents
6
+ server.tool("list_agents", "List all available agents with name, description and skills", {}, () => {
7
+ const list = [...cache.agents.values()].map((a) => ({
8
+ name: a.meta.name,
9
+ description: a.meta.description,
10
+ skills: a.meta.skills,
11
+ }));
12
+ return { content: [{ type: "text", text: JSON.stringify(list, null, 2) }] };
13
+ });
14
+ // list_skills
15
+ server.tool("list_skills", "List all available skills with name and description", {}, () => {
16
+ const list = [...cache.skills.values()].map((s) => ({
17
+ name: s.meta.name,
18
+ description: s.meta.description,
19
+ }));
20
+ return { content: [{ type: "text", text: JSON.stringify(list, null, 2) }] };
21
+ });
22
+ // list_workflows
23
+ server.tool("list_workflows", "List all available workflows (slash commands) with name and description", {}, () => {
24
+ const list = [...cache.workflows.entries()].map(([name, w]) => ({
25
+ name,
26
+ description: w.meta.description,
27
+ }));
28
+ return { content: [{ type: "text", text: JSON.stringify(list, null, 2) }] };
29
+ });
30
+ // get_agent
31
+ server.tool("get_agent", "Get full content of a specific agent by name", { name: z.string().min(1).max(100).describe("Agent name (e.g. 'debugger', 'frontend-specialist')") }, ({ name }) => {
32
+ const agent = cache.agents.get(name);
33
+ if (!agent) {
34
+ return { content: [{ type: "text", text: `Agent "${name}" not found. Use list_agents to see available agents.` }], isError: true };
35
+ }
36
+ return { content: [{ type: "text", text: agent.raw }] };
37
+ });
38
+ // get_skill
39
+ server.tool("get_skill", "Get full content of a specific skill (SKILL.md + sub-files concatenated)", { name: z.string().min(1).max(100).describe("Skill name (e.g. 'clean-code', 'mcp-builder')") }, ({ name }) => {
40
+ const skill = cache.skills.get(name);
41
+ if (!skill) {
42
+ return { content: [{ type: "text", text: `Skill "${name}" not found. Use list_skills to see available skills.` }], isError: true };
43
+ }
44
+ return { content: [{ type: "text", text: skill.raw }] };
45
+ });
46
+ // get_workflow
47
+ server.tool("get_workflow", "Get full content of a specific workflow (slash command)", { name: z.string().min(1).max(100).describe("Workflow name (e.g. 'define', 'debug', 'create')") }, ({ name }) => {
48
+ const workflow = cache.workflows.get(name);
49
+ if (!workflow) {
50
+ return { content: [{ type: "text", text: `Workflow "${name}" not found. Use list_workflows to see available workflows.` }], isError: true };
51
+ }
52
+ return { content: [{ type: "text", text: workflow.raw }] };
53
+ });
54
+ // route_task
55
+ server.tool("route_task", "Recommend the best agents and skills for a given task based on keyword analysis", { request: z.string().min(1).max(10000).describe("Description of the task (e.g. 'fix authentication bug', 'build a dashboard')") }, ({ request }) => {
56
+ const result = routeTask(request, cache);
57
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
58
+ });
59
+ // search_content
60
+ server.tool("search_content", "Full-text search across all framework content (agents, skills, workflows)", {
61
+ query: z.string().min(1).max(5000).describe("Search query"),
62
+ scope: z.enum(["all", "agents", "skills", "workflows"]).default("all").describe("Scope to search in"),
63
+ max_results: z.number().int().min(1).max(50).default(10).describe("Maximum results to return"),
64
+ }, ({ query, scope, max_results }) => {
65
+ const results = searchContent(query, scope, max_results, cache);
66
+ return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
67
+ });
68
+ }
69
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { SERVER_NAME, SERVER_VERSION } from "./constants.js";
5
+ import { getCache } from "./loaders/cache.js";
6
+ import { registerResources } from "./core/resources.js";
7
+ import { registerTools } from "./core/tools.js";
8
+ import { registerPrompts } from "./core/prompts.js";
9
+ async function main() {
10
+ const cache = await getCache();
11
+ const server = new McpServer({
12
+ name: SERVER_NAME,
13
+ version: SERVER_VERSION,
14
+ });
15
+ registerResources(server, cache);
16
+ registerTools(server, cache);
17
+ registerPrompts(server, cache);
18
+ const transport = new StdioServerTransport();
19
+ await server.connect(transport);
20
+ }
21
+ main().catch((err) => {
22
+ console.error("Fatal error:", err);
23
+ process.exit(1);
24
+ });
25
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,2 @@
1
+ import type { Agent } from "../types.js";
2
+ export declare function loadAllAgents(): Promise<Map<string, Agent>>;
@@ -0,0 +1,20 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { PATHS } from "../paths.js";
4
+ import { parseAgentFrontmatter } from "./frontmatter.js";
5
+ export async function loadAllAgents() {
6
+ const agents = new Map();
7
+ const files = await readdir(PATHS.agents);
8
+ const mdFiles = files.filter((f) => f.endsWith(".md"));
9
+ const results = await Promise.all(mdFiles.map(async (file) => {
10
+ const raw = await readFile(join(PATHS.agents, file), "utf-8");
11
+ const { meta, body } = parseAgentFrontmatter(raw);
12
+ const name = meta.name || file.replace(/\.md$/, "");
13
+ return [name, { meta: { ...meta, name }, body, raw }];
14
+ }));
15
+ for (const [name, agent] of results) {
16
+ agents.set(name, agent);
17
+ }
18
+ return agents;
19
+ }
20
+ //# sourceMappingURL=agents.js.map
@@ -0,0 +1,3 @@
1
+ import type { ContentCache } from "../types.js";
2
+ export declare function getCache(): Promise<ContentCache>;
3
+ export declare function resetCache(): void;
@@ -0,0 +1,39 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { IS_DEV, PATHS } from "../paths.js";
3
+ import { loadAllAgents } from "./agents.js";
4
+ import { loadAllSkills } from "./skills.js";
5
+ import { loadAllWorkflows } from "./workflows.js";
6
+ import { EmbeddedLoader } from "./embedded.js";
7
+ // Store the Promise (not the result) to prevent concurrent load race conditions
8
+ let cachePromise = null;
9
+ async function loadFromFilesystem() {
10
+ const [agents, skills, workflows, architecture, instructions] = await Promise.all([
11
+ loadAllAgents(),
12
+ loadAllSkills(),
13
+ loadAllWorkflows(),
14
+ readFile(PATHS.architecture, "utf-8"),
15
+ readFile(PATHS.instructions, "utf-8"),
16
+ ]);
17
+ return { agents, skills, workflows, architecture, instructions };
18
+ }
19
+ async function loadFromEmbedded() {
20
+ const loader = new EmbeddedLoader();
21
+ const [agents, skills, workflows, architecture, instructions] = await Promise.all([
22
+ loader.loadAgents(),
23
+ loader.loadSkills(),
24
+ loader.loadWorkflows(),
25
+ loader.loadFile("ARCHITECTURE"),
26
+ loader.loadFile("INSTRUCTIONS"),
27
+ ]);
28
+ return { agents, skills, workflows, architecture, instructions };
29
+ }
30
+ export function getCache() {
31
+ if (!cachePromise) {
32
+ cachePromise = IS_DEV ? loadFromFilesystem() : loadFromEmbedded();
33
+ }
34
+ return cachePromise;
35
+ }
36
+ export function resetCache() {
37
+ cachePromise = null;
38
+ }
39
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1,7 @@
1
+ import type { Agent, ContentLoader, Skill, Workflow } from "../types.js";
2
+ export declare class EmbeddedLoader implements ContentLoader {
3
+ loadAgents(): Promise<Map<string, Agent>>;
4
+ loadSkills(): Promise<Map<string, Skill>>;
5
+ loadWorkflows(): Promise<Map<string, Workflow>>;
6
+ loadFile(path: string): Promise<string>;
7
+ }
@@ -0,0 +1,50 @@
1
+ import { parseAgentFrontmatter, parseSkillFrontmatter, parseWorkflowFrontmatter } from "./frontmatter.js";
2
+ // Dynamic import — registry.ts only exists after prebuild
3
+ async function loadRegistry() {
4
+ return import("../registry.js");
5
+ }
6
+ export class EmbeddedLoader {
7
+ async loadAgents() {
8
+ const { EMBEDDED_AGENTS } = await loadRegistry();
9
+ const agents = new Map();
10
+ for (const [key, raw] of Object.entries(EMBEDDED_AGENTS)) {
11
+ const { meta, body } = parseAgentFrontmatter(raw);
12
+ const name = meta.name || key;
13
+ agents.set(name, { meta: { ...meta, name }, body, raw });
14
+ }
15
+ return agents;
16
+ }
17
+ async loadSkills() {
18
+ const { EMBEDDED_SKILLS } = await loadRegistry();
19
+ const skills = new Map();
20
+ for (const [key, data] of Object.entries(EMBEDDED_SKILLS)) {
21
+ const { meta, body } = parseSkillFrontmatter(data.skill);
22
+ const name = meta.name || key;
23
+ const subFiles = Object.entries(data.subFiles).map(([filename, content]) => ({ filename, content }));
24
+ let raw = data.skill;
25
+ for (const sub of subFiles) {
26
+ raw += `\n\n---\n\n# ${sub.filename}\n\n${sub.content}`;
27
+ }
28
+ skills.set(name, { meta: { ...meta, name }, body, subFiles, hasScripts: data.hasScripts, raw });
29
+ }
30
+ return skills;
31
+ }
32
+ async loadWorkflows() {
33
+ const { EMBEDDED_WORKFLOWS } = await loadRegistry();
34
+ const workflows = new Map();
35
+ for (const [name, raw] of Object.entries(EMBEDDED_WORKFLOWS)) {
36
+ const { meta, body } = parseWorkflowFrontmatter(raw);
37
+ workflows.set(name, { meta, body, raw });
38
+ }
39
+ return workflows;
40
+ }
41
+ async loadFile(path) {
42
+ const registry = await loadRegistry();
43
+ if (path === "ARCHITECTURE")
44
+ return registry.EMBEDDED_ARCHITECTURE;
45
+ if (path === "INSTRUCTIONS")
46
+ return registry.EMBEDDED_INSTRUCTIONS;
47
+ throw new Error(`Unknown embedded file: ${path}`);
48
+ }
49
+ }
50
+ //# sourceMappingURL=embedded.js.map
@@ -0,0 +1,17 @@
1
+ import type { AgentMeta, SkillMeta, WorkflowMeta } from "../types.js";
2
+ export declare function parseFrontmatter<T>(content: string): {
3
+ meta: T;
4
+ body: string;
5
+ };
6
+ export declare function parseAgentFrontmatter(content: string): {
7
+ meta: AgentMeta;
8
+ body: string;
9
+ };
10
+ export declare function parseSkillFrontmatter(content: string): {
11
+ meta: SkillMeta;
12
+ body: string;
13
+ };
14
+ export declare function parseWorkflowFrontmatter(content: string): {
15
+ meta: WorkflowMeta;
16
+ body: string;
17
+ };
@@ -0,0 +1,71 @@
1
+ import YAML from "yaml";
2
+ const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
3
+ export function parseFrontmatter(content) {
4
+ const match = content.match(FRONTMATTER_RE);
5
+ if (!match) {
6
+ return { meta: {}, body: content };
7
+ }
8
+ let meta;
9
+ try {
10
+ meta = YAML.parse(match[1]);
11
+ }
12
+ catch {
13
+ // Fallback: parse key-value pairs manually for YAML with unquoted special chars
14
+ const result = Object.create(null);
15
+ for (const line of match[1].split("\n")) {
16
+ const idx = line.indexOf(":");
17
+ if (idx > 0) {
18
+ const key = line.slice(0, idx).trim();
19
+ const val = line.slice(idx + 1).trim();
20
+ result[key] = val;
21
+ }
22
+ }
23
+ meta = result;
24
+ }
25
+ const body = match[2].trim();
26
+ return { meta, body };
27
+ }
28
+ function splitCsv(value) {
29
+ if (Array.isArray(value))
30
+ return value.map(String);
31
+ if (typeof value === "string") {
32
+ return value.split(",").map((s) => s.trim()).filter(Boolean);
33
+ }
34
+ return [];
35
+ }
36
+ export function parseAgentFrontmatter(content) {
37
+ const { meta, body } = parseFrontmatter(content);
38
+ return {
39
+ meta: {
40
+ name: String(meta.name ?? ""),
41
+ description: String(meta.description ?? ""),
42
+ tools: splitCsv(meta.tools),
43
+ model: String(meta.model ?? "inherit"),
44
+ skills: splitCsv(meta.skills),
45
+ },
46
+ body,
47
+ };
48
+ }
49
+ export function parseSkillFrontmatter(content) {
50
+ const { meta, body } = parseFrontmatter(content);
51
+ return {
52
+ meta: {
53
+ name: String(meta.name ?? ""),
54
+ description: String(meta.description ?? ""),
55
+ allowedTools: meta["allowed-tools"] ? splitCsv(meta["allowed-tools"]) : undefined,
56
+ version: meta.version != null ? String(meta.version) : undefined,
57
+ priority: meta.priority != null ? String(meta.priority) : undefined,
58
+ },
59
+ body,
60
+ };
61
+ }
62
+ export function parseWorkflowFrontmatter(content) {
63
+ const { meta, body } = parseFrontmatter(content);
64
+ return {
65
+ meta: {
66
+ description: String(meta.description ?? ""),
67
+ },
68
+ body,
69
+ };
70
+ }
71
+ //# sourceMappingURL=frontmatter.js.map
@@ -0,0 +1,2 @@
1
+ import type { Skill } from "../types.js";
2
+ export declare function loadAllSkills(): Promise<Map<string, Skill>>;
@@ -0,0 +1,55 @@
1
+ import { readdir, readFile, stat } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { PATHS } from "../paths.js";
4
+ import { parseSkillFrontmatter } from "./frontmatter.js";
5
+ export async function loadAllSkills() {
6
+ const skills = new Map();
7
+ const entries = await readdir(PATHS.skills);
8
+ const results = await Promise.all(entries.map(async (entry) => {
9
+ const skillDir = join(PATHS.skills, entry);
10
+ const info = await stat(skillDir);
11
+ if (!info.isDirectory())
12
+ return null;
13
+ const skillFile = join(skillDir, "SKILL.md");
14
+ let skillContent;
15
+ try {
16
+ skillContent = await readFile(skillFile, "utf-8");
17
+ }
18
+ catch {
19
+ return null; // No SKILL.md — skip
20
+ }
21
+ const { meta, body } = parseSkillFrontmatter(skillContent);
22
+ const name = meta.name || entry;
23
+ // Load sub-files (other .md files in the directory)
24
+ const subFiles = [];
25
+ const dirFiles = await readdir(skillDir);
26
+ for (const f of dirFiles) {
27
+ if (f === "SKILL.md" || !f.endsWith(".md"))
28
+ continue;
29
+ const content = await readFile(join(skillDir, f), "utf-8");
30
+ subFiles.push({ filename: f, content });
31
+ }
32
+ // Check for scripts/ directory
33
+ let hasScripts = false;
34
+ try {
35
+ const scriptsDir = join(skillDir, "scripts");
36
+ const scriptsInfo = await stat(scriptsDir);
37
+ hasScripts = scriptsInfo.isDirectory();
38
+ }
39
+ catch {
40
+ // No scripts directory
41
+ }
42
+ // Build concatenated raw content
43
+ let raw = skillContent;
44
+ for (const sub of subFiles) {
45
+ raw += `\n\n---\n\n# ${sub.filename}\n\n${sub.content}`;
46
+ }
47
+ return [name, { meta: { ...meta, name }, body, subFiles, hasScripts, raw }];
48
+ }));
49
+ for (const result of results) {
50
+ if (result)
51
+ skills.set(result[0], result[1]);
52
+ }
53
+ return skills;
54
+ }
55
+ //# sourceMappingURL=skills.js.map
@@ -0,0 +1,2 @@
1
+ import type { Workflow } from "../types.js";
2
+ export declare function loadAllWorkflows(): Promise<Map<string, Workflow>>;
@@ -0,0 +1,20 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { PATHS } from "../paths.js";
4
+ import { parseWorkflowFrontmatter } from "./frontmatter.js";
5
+ export async function loadAllWorkflows() {
6
+ const workflows = new Map();
7
+ const files = await readdir(PATHS.workflows);
8
+ const mdFiles = files.filter((f) => f.endsWith(".md"));
9
+ const results = await Promise.all(mdFiles.map(async (file) => {
10
+ const raw = await readFile(join(PATHS.workflows, file), "utf-8");
11
+ const { meta, body } = parseWorkflowFrontmatter(raw);
12
+ const name = file.replace(/\.md$/, "");
13
+ return [name, { meta, body, raw }];
14
+ }));
15
+ for (const [name, workflow] of results) {
16
+ workflows.set(name, workflow);
17
+ }
18
+ return workflows;
19
+ }
20
+ //# sourceMappingURL=workflows.js.map