@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.
@@ -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 existing = [];
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 full = path.resolve(dir);
159
- if (await fs.pathExists(full)) {
160
- existing.push(agent);
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 (existing.length === 0) {
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} Pastas encontradas:\n`);
168
- for (const agent of existing) {
169
- console.log(` ${ANSI.dim}${path.resolve(AGENT_DIRS[agent])}${ANSI.reset}`);
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 TODAS as pastas de skills?",
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 agent of existing) {
186
- const full = path.resolve(AGENT_DIRS[agent]);
187
- await fs.remove(full);
188
- console.log(` ${RED}✖${ANSI.reset} ${full} removida`);
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 Todas as skills foram removidas.\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.2.0",
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}}