@sysvv/ai-skill 1.2.0 → 1.3.0
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/dist/core/mcp.js +72 -0
- package/dist/index.js +62 -20
- package/mcps/azure-devops.mcp.json +16 -0
- package/package.json +4 -2
- package/schemas/mcp.schema.json +93 -0
- package/skills/azure-devops/SKILL.md +37 -0
package/dist/core/mcp.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
/** Onde cada agent espera o arquivo de MCP */
|
|
6
|
+
const MCP_TARGETS = {
|
|
7
|
+
claude: ".mcp.json",
|
|
8
|
+
codex: ".codex/mcp.json",
|
|
9
|
+
gemini: ".gemini/mcp.json",
|
|
10
|
+
copilot: ".github/copilot/mcp.json",
|
|
11
|
+
};
|
|
12
|
+
/** Resolve o caminho do .mcp.json dentro de /mcps/ */
|
|
13
|
+
function getMcpFilePath(name) {
|
|
14
|
+
return path.resolve(__dirname, "../../mcps", `${name}.mcp.json`);
|
|
15
|
+
}
|
|
16
|
+
/** Carrega a definição de um MCP pelo nome */
|
|
17
|
+
export async function loadMcp(name) {
|
|
18
|
+
const mcpFile = getMcpFilePath(name);
|
|
19
|
+
if (!(await fs.pathExists(mcpFile))) {
|
|
20
|
+
throw new Error(`MCP "${name}" não encontrado em mcps/${name}.mcp.json`);
|
|
21
|
+
}
|
|
22
|
+
return fs.readJson(mcpFile);
|
|
23
|
+
}
|
|
24
|
+
/** Extrai apenas os campos de server config (sem metadata) */
|
|
25
|
+
function extractServerConfig(mcp) {
|
|
26
|
+
if (mcp.transport === "stdio") {
|
|
27
|
+
const config = { command: mcp.command };
|
|
28
|
+
if (mcp.args?.length)
|
|
29
|
+
config.args = mcp.args;
|
|
30
|
+
if (mcp.env && Object.keys(mcp.env).length)
|
|
31
|
+
config.env = mcp.env;
|
|
32
|
+
return config;
|
|
33
|
+
}
|
|
34
|
+
// sse | streamable-http
|
|
35
|
+
const config = { url: mcp.url };
|
|
36
|
+
if (mcp.headers && Object.keys(mcp.headers).length)
|
|
37
|
+
config.headers = mcp.headers;
|
|
38
|
+
if (mcp.env && Object.keys(mcp.env).length)
|
|
39
|
+
config.env = mcp.env;
|
|
40
|
+
return config;
|
|
41
|
+
}
|
|
42
|
+
/** Escreve o MCP no arquivo alvo do agent, fazendo merge com configs existentes */
|
|
43
|
+
export async function writeMcp(agent, mcp) {
|
|
44
|
+
const targetFile = MCP_TARGETS[agent];
|
|
45
|
+
if (!targetFile)
|
|
46
|
+
throw new Error(`Agent "${agent}" não tem target de MCP configurado.`);
|
|
47
|
+
const targetPath = path.resolve(targetFile);
|
|
48
|
+
let existing = {};
|
|
49
|
+
if (await fs.pathExists(targetPath)) {
|
|
50
|
+
existing = await fs.readJson(targetPath);
|
|
51
|
+
}
|
|
52
|
+
const mcpServers = existing.mcpServers ?? {};
|
|
53
|
+
mcpServers[mcp.name] = extractServerConfig(mcp);
|
|
54
|
+
await fs.ensureDir(path.dirname(targetPath));
|
|
55
|
+
await fs.writeJson(targetPath, { ...existing, mcpServers }, { spaces: 2 });
|
|
56
|
+
return targetPath;
|
|
57
|
+
}
|
|
58
|
+
/** Lista todos os MCPs disponíveis */
|
|
59
|
+
export async function listMcps() {
|
|
60
|
+
const mcpsDir = path.resolve(__dirname, "../../mcps");
|
|
61
|
+
if (!(await fs.pathExists(mcpsDir)))
|
|
62
|
+
return [];
|
|
63
|
+
const files = await fs.readdir(mcpsDir);
|
|
64
|
+
const results = [];
|
|
65
|
+
for (const file of files) {
|
|
66
|
+
if (file.endsWith(".mcp.json")) {
|
|
67
|
+
const mcp = await fs.readJson(path.join(mcpsDir, file));
|
|
68
|
+
results.push(mcp);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return results;
|
|
72
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { fileURLToPath } from "node:url";
|
|
|
7
7
|
import { findSkillFiles } from "./core/registry.js";
|
|
8
8
|
import { parseSkillFile } from "./core/frontmatter.js";
|
|
9
9
|
import { loadSkill } from "./core/skill.js";
|
|
10
|
+
import { loadMcp, writeMcp } from "./core/mcp.js";
|
|
10
11
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
12
|
// ── ANSI ────────────────────────────────────────────────────────────────
|
|
12
13
|
const ANSI = {
|
|
@@ -18,6 +19,7 @@ const PINK = '\x1b[38;2;238;54;141m';
|
|
|
18
19
|
const BLUE = '\x1b[38;2;14;93;171m';
|
|
19
20
|
const RED = '\x1b[38;2;255;70;70m';
|
|
20
21
|
const GREEN = '\x1b[38;2;80;200;120m';
|
|
22
|
+
const YELLOW = '\x1b[38;2;255;200;50m';
|
|
21
23
|
const WHITE = '\x1b[97m';
|
|
22
24
|
// ── Inquirer Theme ──────────────────────────────────────────────────────
|
|
23
25
|
const sysTheme = {
|
|
@@ -87,7 +89,7 @@ function showBanner() {
|
|
|
87
89
|
console.log(indent + ANSI.dim + centerPad('│ ' + WHITE + desc + ANSI.reset + ANSI.dim + ' │', maxWidth) + ANSI.reset);
|
|
88
90
|
console.log(indent + ANSI.dim + centerPad('└' + '─'.repeat(boxInner) + '┘', maxWidth) + ANSI.reset);
|
|
89
91
|
console.log('');
|
|
90
|
-
console.log(indent + ANSI.dim + centerPad('npx @sysvv/ai-skill --claude | --codex | --gemini | --clear', maxWidth) + ANSI.reset);
|
|
92
|
+
console.log(indent + ANSI.dim + centerPad('npx @sysvv/ai-skill --claude | --codex | --gemini | --copilot | --clear', maxWidth) + ANSI.reset);
|
|
91
93
|
console.log('');
|
|
92
94
|
console.log(separator);
|
|
93
95
|
console.log('');
|
|
@@ -97,6 +99,7 @@ const AGENT_DIRS = {
|
|
|
97
99
|
claude: ".claude/skills",
|
|
98
100
|
codex: ".codex/skills",
|
|
99
101
|
gemini: ".gemini/skills",
|
|
102
|
+
copilot: ".github/skills",
|
|
100
103
|
};
|
|
101
104
|
// ── Parse args ──────────────────────────────────────────────────────────
|
|
102
105
|
function parseArgs() {
|
|
@@ -109,7 +112,7 @@ function parseArgs() {
|
|
|
109
112
|
}
|
|
110
113
|
return {};
|
|
111
114
|
}
|
|
112
|
-
// ── Install skills for agent
|
|
115
|
+
// ── Install skills + MCPs for agent ─────────────────────────────────────
|
|
113
116
|
async function installSkills(agent) {
|
|
114
117
|
const skillFiles = await findSkillFiles();
|
|
115
118
|
const available = [];
|
|
@@ -120,7 +123,8 @@ async function installSkills(agent) {
|
|
|
120
123
|
file,
|
|
121
124
|
name: metadata.name,
|
|
122
125
|
title: metadata.title ?? metadata.name,
|
|
123
|
-
description: metadata.description
|
|
126
|
+
description: metadata.description,
|
|
127
|
+
mcps: metadata.mcps ?? []
|
|
124
128
|
});
|
|
125
129
|
}
|
|
126
130
|
if (available.length === 0) {
|
|
@@ -133,7 +137,7 @@ async function installSkills(agent) {
|
|
|
133
137
|
name: "selected",
|
|
134
138
|
message: "Escolha as skills:",
|
|
135
139
|
choices: available.map((s) => ({
|
|
136
|
-
name: `${s.title} ${ANSI.dim}— ${s.description}${ANSI.reset}`,
|
|
140
|
+
name: `${s.title} ${ANSI.dim}— ${s.description}${s.mcps.length > 0 ? ` ${YELLOW}[MCP]${ANSI.reset}${ANSI.dim}` : ''}${ANSI.reset}`,
|
|
137
141
|
value: s.file
|
|
138
142
|
})),
|
|
139
143
|
validate: (answer) => answer.length > 0 ? true : "Selecione pelo menos uma skill.",
|
|
@@ -141,6 +145,8 @@ async function installSkills(agent) {
|
|
|
141
145
|
}
|
|
142
146
|
]);
|
|
143
147
|
const destBase = path.resolve(AGENT_DIRS[agent]);
|
|
148
|
+
const mcpsToInstall = new Set();
|
|
149
|
+
// Instala skills
|
|
144
150
|
console.log('');
|
|
145
151
|
for (const file of selected) {
|
|
146
152
|
const skill = await loadSkill(file, agent);
|
|
@@ -148,32 +154,63 @@ async function installSkills(agent) {
|
|
|
148
154
|
await fs.ensureDir(skillDir);
|
|
149
155
|
await fs.writeFile(path.join(skillDir, "SKILL.md"), skill.renderedBody, "utf8");
|
|
150
156
|
console.log(` ${GREEN}✔${ANSI.reset} ${skill.metadata.name}`);
|
|
157
|
+
// Coleta MCPs necessários
|
|
158
|
+
for (const mcpName of skill.metadata.mcps ?? []) {
|
|
159
|
+
mcpsToInstall.add(mcpName);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Instala MCPs automaticamente
|
|
163
|
+
if (mcpsToInstall.size > 0) {
|
|
164
|
+
console.log('');
|
|
165
|
+
let mcpTarget = '';
|
|
166
|
+
for (const mcpName of mcpsToInstall) {
|
|
167
|
+
try {
|
|
168
|
+
const mcp = await loadMcp(mcpName);
|
|
169
|
+
mcpTarget = await writeMcp(agent, mcp);
|
|
170
|
+
console.log(` ${GREEN}✔${ANSI.reset} MCP ${ANSI.bold}${mcpName}${ANSI.reset} ${ANSI.dim}→ ${mcpTarget}${ANSI.reset}`);
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
console.log(` ${RED}✖${ANSI.reset} MCP ${mcpName}: ${err.message}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
151
176
|
}
|
|
152
177
|
console.log(`\n Skills instaladas em ${ANSI.bold}${destBase}/${ANSI.reset}\n`);
|
|
153
178
|
}
|
|
154
|
-
// ── Clear all skills
|
|
179
|
+
// ── Clear all skills + MCPs ─────────────────────────────────────────────
|
|
155
180
|
async function clearSkills() {
|
|
156
|
-
const
|
|
181
|
+
const MCP_TARGETS = {
|
|
182
|
+
claude: ".mcp.json",
|
|
183
|
+
codex: ".codex/mcp.json",
|
|
184
|
+
gemini: ".gemini/mcp.json",
|
|
185
|
+
copilot: ".github/copilot/mcp.json",
|
|
186
|
+
};
|
|
187
|
+
const found = [];
|
|
157
188
|
for (const [agent, dir] of Object.entries(AGENT_DIRS)) {
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
189
|
+
const skillsFull = path.resolve(dir);
|
|
190
|
+
const mcpFull = path.resolve(MCP_TARGETS[agent]);
|
|
191
|
+
const hasSkills = await fs.pathExists(skillsFull);
|
|
192
|
+
const hasMcp = await fs.pathExists(mcpFull);
|
|
193
|
+
if (hasSkills || hasMcp) {
|
|
194
|
+
found.push({ agent, skillsDir: skillsFull, mcpFile: mcpFull, hasSkills, hasMcp });
|
|
161
195
|
}
|
|
162
196
|
}
|
|
163
|
-
if (
|
|
164
|
-
console.log(" Nenhuma pasta de skills encontrada.\n");
|
|
197
|
+
if (found.length === 0) {
|
|
198
|
+
console.log(" Nenhuma pasta de skills ou MCP encontrada.\n");
|
|
165
199
|
return;
|
|
166
200
|
}
|
|
167
|
-
console.log(` ${RED}⚠${ANSI.reset}
|
|
168
|
-
for (const
|
|
169
|
-
|
|
201
|
+
console.log(` ${RED}⚠${ANSI.reset} Encontrado:\n`);
|
|
202
|
+
for (const f of found) {
|
|
203
|
+
if (f.hasSkills)
|
|
204
|
+
console.log(` ${ANSI.dim}${f.skillsDir}${ANSI.reset}`);
|
|
205
|
+
if (f.hasMcp)
|
|
206
|
+
console.log(` ${ANSI.dim}${f.mcpFile}${ANSI.reset}`);
|
|
170
207
|
}
|
|
171
208
|
console.log('');
|
|
172
209
|
const { confirm } = await inquirer.prompt([
|
|
173
210
|
{
|
|
174
211
|
type: "confirm",
|
|
175
212
|
name: "confirm",
|
|
176
|
-
message: "Tem certeza que deseja apagar
|
|
213
|
+
message: "Tem certeza que deseja apagar TUDO (skills + MCPs)?",
|
|
177
214
|
default: false,
|
|
178
215
|
theme: sysTheme
|
|
179
216
|
}
|
|
@@ -182,12 +219,17 @@ async function clearSkills() {
|
|
|
182
219
|
console.log("\n Operação cancelada.\n");
|
|
183
220
|
return;
|
|
184
221
|
}
|
|
185
|
-
for (const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
222
|
+
for (const f of found) {
|
|
223
|
+
if (f.hasSkills) {
|
|
224
|
+
await fs.remove(f.skillsDir);
|
|
225
|
+
console.log(` ${RED}✖${ANSI.reset} ${f.skillsDir}`);
|
|
226
|
+
}
|
|
227
|
+
if (f.hasMcp) {
|
|
228
|
+
await fs.remove(f.mcpFile);
|
|
229
|
+
console.log(` ${RED}✖${ANSI.reset} ${f.mcpFile}`);
|
|
230
|
+
}
|
|
189
231
|
}
|
|
190
|
-
console.log("\n
|
|
232
|
+
console.log("\n Tudo removido.\n");
|
|
191
233
|
}
|
|
192
234
|
// ── Main ────────────────────────────────────────────────────────────────
|
|
193
235
|
async function main() {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../schemas/mcp.schema.json",
|
|
3
|
+
"name": "azure-devops",
|
|
4
|
+
"description": "Integração com Azure DevOps para work items, repos e pipelines.",
|
|
5
|
+
"transport": "stdio",
|
|
6
|
+
"command": "npx",
|
|
7
|
+
"args": [
|
|
8
|
+
"-y",
|
|
9
|
+
"@azure-devops/mcp",
|
|
10
|
+
"sysmanagerdevops",
|
|
11
|
+
"-d", "core", "work", "work-items"
|
|
12
|
+
],
|
|
13
|
+
"env": {
|
|
14
|
+
"AZURE_DEVOPS_PAT": "${AZURE_DEVOPS_PAT}"
|
|
15
|
+
}
|
|
16
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sysvv/ai-skill",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Instale skills de IA direto no seu projeto. Escolha o agent, escolha as skills.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
|
-
"skills"
|
|
11
|
+
"skills",
|
|
12
|
+
"mcps",
|
|
13
|
+
"schemas"
|
|
12
14
|
],
|
|
13
15
|
"scripts": {
|
|
14
16
|
"build": "tsc",
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://sysvv.dev/schemas/mcp.schema.json",
|
|
4
|
+
"title": "MCP Server Configuration",
|
|
5
|
+
"description": "Schema para configuração de servidores MCP (Model Context Protocol). Suporta os transportes stdio, sse e streamable-http.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"$schema": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "Referência ao JSON Schema para validação e autocomplete."
|
|
11
|
+
},
|
|
12
|
+
"name": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$",
|
|
15
|
+
"description": "Identificador único do MCP. Deve ser slug (lowercase, hífens). Ex: azure-devops"
|
|
16
|
+
},
|
|
17
|
+
"description": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "Descrição curta do que o MCP faz."
|
|
20
|
+
},
|
|
21
|
+
"transport": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"enum": ["stdio", "sse", "streamable-http"],
|
|
24
|
+
"description": "Tipo de transporte do MCP."
|
|
25
|
+
},
|
|
26
|
+
"command": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"description": "[stdio] Comando para iniciar o servidor MCP."
|
|
29
|
+
},
|
|
30
|
+
"args": {
|
|
31
|
+
"type": "array",
|
|
32
|
+
"items": { "type": "string" },
|
|
33
|
+
"description": "[stdio] Argumentos passados ao comando."
|
|
34
|
+
},
|
|
35
|
+
"url": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"format": "uri",
|
|
38
|
+
"description": "[sse | streamable-http] URL do servidor MCP."
|
|
39
|
+
},
|
|
40
|
+
"headers": {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"additionalProperties": { "type": "string" },
|
|
43
|
+
"description": "[sse | streamable-http] Headers HTTP enviados nas requisições."
|
|
44
|
+
},
|
|
45
|
+
"env": {
|
|
46
|
+
"type": "object",
|
|
47
|
+
"additionalProperties": { "type": "string" },
|
|
48
|
+
"description": "Variáveis de ambiente. Use ${VAR} para referências dinâmicas."
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"required": ["name", "description", "transport"],
|
|
52
|
+
"allOf": [
|
|
53
|
+
{
|
|
54
|
+
"if": {
|
|
55
|
+
"properties": { "transport": { "const": "stdio" } },
|
|
56
|
+
"required": ["transport"]
|
|
57
|
+
},
|
|
58
|
+
"then": {
|
|
59
|
+
"required": ["command"],
|
|
60
|
+
"properties": {
|
|
61
|
+
"url": false,
|
|
62
|
+
"headers": false
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"if": {
|
|
68
|
+
"properties": { "transport": { "const": "sse" } },
|
|
69
|
+
"required": ["transport"]
|
|
70
|
+
},
|
|
71
|
+
"then": {
|
|
72
|
+
"required": ["url"],
|
|
73
|
+
"properties": {
|
|
74
|
+
"command": false,
|
|
75
|
+
"args": false
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"if": {
|
|
81
|
+
"properties": { "transport": { "const": "streamable-http" } },
|
|
82
|
+
"required": ["transport"]
|
|
83
|
+
},
|
|
84
|
+
"then": {
|
|
85
|
+
"required": ["url"],
|
|
86
|
+
"properties": {
|
|
87
|
+
"command": false,
|
|
88
|
+
"args": false
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: azure-devops
|
|
3
|
+
title: Azure DevOps
|
|
4
|
+
description: gerenciar work items, repos e pipelines no Azure DevOps.
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
providers:
|
|
7
|
+
- claude
|
|
8
|
+
- codex
|
|
9
|
+
- gemini
|
|
10
|
+
mcps:
|
|
11
|
+
- azure-devops
|
|
12
|
+
variables:
|
|
13
|
+
language: pt-BR
|
|
14
|
+
provider_overrides:
|
|
15
|
+
claude:
|
|
16
|
+
style: "retornar bullets curtos e objetivos"
|
|
17
|
+
codex:
|
|
18
|
+
style: "retornar estrutura operacional"
|
|
19
|
+
gemini:
|
|
20
|
+
style: "retornar texto claro e organizado"
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
Você é um assistente especialista em Azure DevOps.
|
|
24
|
+
|
|
25
|
+
Objetivo:
|
|
26
|
+
Ajudar a gerenciar work items, repositórios e pipelines usando a integração com Azure DevOps.
|
|
27
|
+
|
|
28
|
+
Capacidades:
|
|
29
|
+
- Criar e atualizar work items
|
|
30
|
+
- Consultar status de pipelines
|
|
31
|
+
- Listar repositórios e branches
|
|
32
|
+
- Buscar PRs e reviews
|
|
33
|
+
|
|
34
|
+
Idioma: {{language}}
|
|
35
|
+
|
|
36
|
+
Instrução específica do provider:
|
|
37
|
+
{{provider_style}}
|