@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 +134 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +26 -0
- package/dist/core/prompts.d.ts +3 -0
- package/dist/core/prompts.js +26 -0
- package/dist/core/resources.d.ts +3 -0
- package/dist/core/resources.js +45 -0
- package/dist/core/tools.d.ts +3 -0
- package/dist/core/tools.js +69 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +25 -0
- package/dist/loaders/agents.d.ts +2 -0
- package/dist/loaders/agents.js +20 -0
- package/dist/loaders/cache.d.ts +3 -0
- package/dist/loaders/cache.js +39 -0
- package/dist/loaders/embedded.d.ts +7 -0
- package/dist/loaders/embedded.js +50 -0
- package/dist/loaders/frontmatter.d.ts +17 -0
- package/dist/loaders/frontmatter.js +71 -0
- package/dist/loaders/skills.d.ts +2 -0
- package/dist/loaders/skills.js +55 -0
- package/dist/loaders/workflows.d.ts +2 -0
- package/dist/loaders/workflows.js +20 -0
- package/dist/paths.d.ts +8 -0
- package/dist/paths.js +16 -0
- package/dist/registry.d.ts +9 -0
- package/dist/registry.js +344 -0
- package/dist/types.d.ts +74 -0
- package/dist/types.js +2 -0
- package/dist/utils/routing.d.ts +2 -0
- package/dist/utils/routing.js +32 -0
- package/dist/utils/search.d.ts +4 -0
- package/dist/utils/search.js +42 -0
- package/dist/worker.d.ts +4 -0
- package/dist/worker.js +117 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# @joelbonito/mcp-server
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@joelbonito/mcp-server)
|
|
4
|
+
[](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,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,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,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,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
|
package/dist/index.d.ts
ADDED
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,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,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,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,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
|