@sysvv/ai-skill 1.1.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,18 +19,34 @@ 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';
21
- // ── ASCII Art Letters (5 lines) ─────────────────────────────────────────
22
+ const YELLOW = '\x1b[38;2;255;200;50m';
23
+ const WHITE = '\x1b[97m';
24
+ // ── Inquirer Theme ──────────────────────────────────────────────────────
25
+ const sysTheme = {
26
+ icon: {
27
+ cursor: PINK + '❯' + ANSI.reset,
28
+ checked: PINK + '◉' + ANSI.reset,
29
+ unchecked: ANSI.dim + '○' + ANSI.reset,
30
+ },
31
+ style: {
32
+ highlight: (text) => PINK + ANSI.bold + text + ANSI.reset,
33
+ answer: (text) => BLUE + ANSI.bold + text + ANSI.reset,
34
+ message: (text) => WHITE + ANSI.bold + text + ANSI.reset,
35
+ },
36
+ helpMode: 'always',
37
+ };
38
+ // ── ASCII Art Letters (7 lines, bigger) ─────────────────────────────────
22
39
  const LETTERS = {
23
- A: [' █████ ', '██ ██', '███████', '██ ██', '██ ██'],
24
- I: ['██', '██', '██', '██', '██'],
25
- K: ['██ ██', '██ ██ ', '████ ', '██ ██ ', '██ ██'],
26
- L: ['██ ', '██ ', '██ ', '██ ', '███████'],
27
- S: ['███████', '██ ', '███████', ' ██', '███████'],
28
- Y: ['██ ██', ' ██ ██ ', ' ███ ', ' ███ ', ' ███ '],
40
+ A: [' ██████ ', ' ██ ██ ', '██ ██', '██████████', '██ ██', '██ ██', '██ ██'],
41
+ I: ['████', ' ██ ', ' ██ ', ' ██ ', ' ██ ', ' ██ ', '████'],
42
+ K: ['██ ██', '██ ██ ', '██ ██ ', '█████ ', '██ ██ ', '██ ██ ', '██ ██'],
43
+ L: ['██ ', '██ ', '██ ', '██ ', '██ ', '██ ', '█████████'],
44
+ S: [' ████████ ', '██ ', '██ ', ' ████████ ', ' ██ ', ' ██ ', ' ████████ '],
45
+ Y: ['██ ██', ' ██ ██ ', ' ██ ██ ', ' ███ ', ' ███ ', ' ███ ', ' ███ '],
29
46
  };
30
- function joinLetters(keys, gap = ' ') {
47
+ function joinLetters(keys, gap = ' ') {
31
48
  const letters = keys.map(k => LETTERS[k]);
32
- return Array.from({ length: 5 }, (_, i) => letters.map(l => l[i]).join(gap));
49
+ return Array.from({ length: 7 }, (_, i) => letters.map(l => l[i]).join(gap));
33
50
  }
34
51
  function centerPad(line, totalWidth) {
35
52
  const spaces = Math.max(0, Math.floor((totalWidth - line.length) / 2));
@@ -46,28 +63,35 @@ function showBanner() {
46
63
  const aiLines = joinLetters(['A', 'I']);
47
64
  const skillsLines = joinLetters(['S', 'K', 'I', 'L', 'L', 'S']);
48
65
  const maxWidth = Math.max(sysLines[0].length, aiLines[0].length, skillsLines[0].length);
49
- const indent = ' ';
66
+ const indent = ' ';
67
+ const separator = ANSI.dim + indent + centerPad('─'.repeat(maxWidth), maxWidth) + ANSI.reset;
68
+ console.log('');
50
69
  console.log('');
51
70
  for (const line of sysLines) {
52
71
  console.log(indent + PINK + ANSI.bold + centerPad(line, maxWidth) + ANSI.reset);
53
72
  }
54
73
  console.log('');
55
74
  for (const line of aiLines) {
56
- console.log(indent + BLUE + centerPad(line, maxWidth) + ANSI.reset);
75
+ console.log(indent + BLUE + ANSI.bold + centerPad(line, maxWidth) + ANSI.reset);
57
76
  }
77
+ console.log('');
58
78
  for (const line of skillsLines) {
59
79
  console.log(indent + BLUE + centerPad(line, maxWidth) + ANSI.reset);
60
80
  }
61
81
  console.log('');
62
- console.log(indent + ANSI.dim + centerPad(`VERSION ${version}`, maxWidth) + ANSI.reset);
82
+ console.log(separator);
83
+ console.log('');
84
+ console.log(indent + WHITE + ANSI.bold + centerPad(`v${version}`, maxWidth) + ANSI.reset);
63
85
  console.log('');
64
86
  const desc = 'Skills para turbinar seus agentes de codigo';
65
- const boxInner = desc.length + 2;
87
+ const boxInner = desc.length + 4;
66
88
  console.log(indent + ANSI.dim + centerPad('┌' + '─'.repeat(boxInner) + '┐', maxWidth) + ANSI.reset);
67
- console.log(indent + ANSI.dim + centerPad('│ ' + desc + ' │', maxWidth) + ANSI.reset);
89
+ console.log(indent + ANSI.dim + centerPad('│ ' + WHITE + desc + ANSI.reset + ANSI.dim + ' │', maxWidth) + ANSI.reset);
68
90
  console.log(indent + ANSI.dim + centerPad('└' + '─'.repeat(boxInner) + '┘', maxWidth) + ANSI.reset);
69
91
  console.log('');
70
- console.log(indent + ANSI.dim + ' ℹ Tip: npx @sysvv/ai-skill --claude | --codex | --gemini | --clear' + ANSI.reset);
92
+ console.log(indent + ANSI.dim + centerPad('npx @sysvv/ai-skill --claude | --codex | --gemini | --copilot | --clear', maxWidth) + ANSI.reset);
93
+ console.log('');
94
+ console.log(separator);
71
95
  console.log('');
72
96
  }
73
97
  // ── Config ──────────────────────────────────────────────────────────────
@@ -75,6 +99,7 @@ const AGENT_DIRS = {
75
99
  claude: ".claude/skills",
76
100
  codex: ".codex/skills",
77
101
  gemini: ".gemini/skills",
102
+ copilot: ".github/skills",
78
103
  };
79
104
  // ── Parse args ──────────────────────────────────────────────────────────
80
105
  function parseArgs() {
@@ -87,7 +112,7 @@ function parseArgs() {
87
112
  }
88
113
  return {};
89
114
  }
90
- // ── Install skills for agent ────────────────────────────────────────────
115
+ // ── Install skills + MCPs for agent ─────────────────────────────────────
91
116
  async function installSkills(agent) {
92
117
  const skillFiles = await findSkillFiles();
93
118
  const available = [];
@@ -98,71 +123,113 @@ async function installSkills(agent) {
98
123
  file,
99
124
  name: metadata.name,
100
125
  title: metadata.title ?? metadata.name,
101
- description: metadata.description
126
+ description: metadata.description,
127
+ mcps: metadata.mcps ?? []
102
128
  });
103
129
  }
104
130
  if (available.length === 0) {
105
- console.log("Nenhuma skill encontrada.");
131
+ console.log(" Nenhuma skill encontrada.");
106
132
  process.exit(1);
107
133
  }
108
134
  const { selected } = await inquirer.prompt([
109
135
  {
110
136
  type: "checkbox",
111
137
  name: "selected",
112
- message: "Escolha as skills que deseja instalar:",
138
+ message: "Escolha as skills:",
113
139
  choices: available.map((s) => ({
114
- name: `${s.title} — ${s.description}`,
140
+ name: `${s.title} ${ANSI.dim}— ${s.description}${s.mcps.length > 0 ? ` ${YELLOW}[MCP]${ANSI.reset}${ANSI.dim}` : ''}${ANSI.reset}`,
115
141
  value: s.file
116
142
  })),
117
- validate: (answer) => answer.length > 0 ? true : "Selecione pelo menos uma skill."
143
+ validate: (answer) => answer.length > 0 ? true : "Selecione pelo menos uma skill.",
144
+ theme: sysTheme
118
145
  }
119
146
  ]);
120
147
  const destBase = path.resolve(AGENT_DIRS[agent]);
148
+ const mcpsToInstall = new Set();
149
+ // Instala skills
150
+ console.log('');
121
151
  for (const file of selected) {
122
152
  const skill = await loadSkill(file, agent);
123
153
  const skillDir = path.join(destBase, skill.metadata.name);
124
154
  await fs.ensureDir(skillDir);
125
155
  await fs.writeFile(path.join(skillDir, "SKILL.md"), skill.renderedBody, "utf8");
126
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
+ }
127
176
  }
128
177
  console.log(`\n Skills instaladas em ${ANSI.bold}${destBase}/${ANSI.reset}\n`);
129
178
  }
130
- // ── Clear all skills ────────────────────────────────────────────────────
179
+ // ── Clear all skills + MCPs ─────────────────────────────────────────────
131
180
  async function clearSkills() {
132
- 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 = [];
133
188
  for (const [agent, dir] of Object.entries(AGENT_DIRS)) {
134
- const full = path.resolve(dir);
135
- if (await fs.pathExists(full)) {
136
- 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 });
137
195
  }
138
196
  }
139
- if (existing.length === 0) {
140
- console.log(" Nenhuma pasta de skills encontrada.\n");
197
+ if (found.length === 0) {
198
+ console.log(" Nenhuma pasta de skills ou MCP encontrada.\n");
141
199
  return;
142
200
  }
143
- console.log(` ${RED}⚠${ANSI.reset} Pastas encontradas:\n`);
144
- for (const agent of existing) {
145
- 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}`);
146
207
  }
147
208
  console.log('');
148
209
  const { confirm } = await inquirer.prompt([
149
210
  {
150
211
  type: "confirm",
151
212
  name: "confirm",
152
- message: "Tem certeza que deseja apagar TODAS as pastas de skills?",
153
- default: false
213
+ message: "Tem certeza que deseja apagar TUDO (skills + MCPs)?",
214
+ default: false,
215
+ theme: sysTheme
154
216
  }
155
217
  ]);
156
218
  if (!confirm) {
157
219
  console.log("\n Operação cancelada.\n");
158
220
  return;
159
221
  }
160
- for (const agent of existing) {
161
- const full = path.resolve(AGENT_DIRS[agent]);
162
- await fs.remove(full);
163
- 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
+ }
164
231
  }
165
- console.log("\n Todas as skills foram removidas.\n");
232
+ console.log("\n Tudo removido.\n");
166
233
  }
167
234
  // ── Main ────────────────────────────────────────────────────────────────
168
235
  async function main() {
@@ -176,13 +243,16 @@ async function main() {
176
243
  await installSkills(agent);
177
244
  return;
178
245
  }
179
- // Modo interativo
180
246
  const { chosenAgent } = await inquirer.prompt([
181
247
  {
182
248
  type: "list",
183
249
  name: "chosenAgent",
184
250
  message: "Qual IA você usa?",
185
- choices: Object.keys(AGENT_DIRS)
251
+ choices: Object.keys(AGENT_DIRS).map((key) => ({
252
+ name: `${ANSI.bold}${key.charAt(0).toUpperCase() + key.slice(1)}${ANSI.reset}`,
253
+ value: key
254
+ })),
255
+ theme: sysTheme
186
256
  }
187
257
  ]);
188
258
  await installSkills(chosenAgent);
@@ -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.1.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}}