@basetisia/skill-manager 0.1.1 → 0.2.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/bin/index.js CHANGED
@@ -9,6 +9,7 @@ import { detect } from "../src/commands/detect.js";
9
9
  import { login } from "../src/commands/login.js";
10
10
  import { logout } from "../src/commands/logout.js";
11
11
  import { whoami } from "../src/commands/whoami.js";
12
+ import { sync } from "../src/commands/sync.js";
12
13
 
13
14
  const program = new Command();
14
15
 
@@ -80,6 +81,18 @@ program
80
81
  }
81
82
  });
82
83
 
84
+ program
85
+ .command("sync")
86
+ .description("Actualiza el find-skill con tus permisos más recientes")
87
+ .action(async () => {
88
+ try {
89
+ await sync();
90
+ } catch (err) {
91
+ console.error(err.message);
92
+ process.exit(1);
93
+ }
94
+ });
95
+
83
96
  program
84
97
  .command("login")
85
98
  .description("Inicia sesión con tu cuenta de Basetis (Google)")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@basetisia/skill-manager",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "CLI to manage AI coding skills across editors",
5
5
  "type": "module",
6
6
  "engines": {
@@ -2,16 +2,28 @@ import inquirer from "inquirer";
2
2
  import chalk from "chalk";
3
3
  import ora from "ora";
4
4
  import { loadConfig, saveConfig, getConfigPath } from "../utils/config.js";
5
- import { verifyConnection } from "../utils/gitlab.js";
5
+ import { loadCredentials } from "../utils/auth.js";
6
+ import { apiRequest } from "../utils/api.js";
7
+ import { generateFindSkill } from "../utils/find-skill.js";
8
+ import { getAdapter } from "../adapters/index.js";
6
9
 
7
10
  export async function init() {
11
+ // Check auth
12
+ const creds = await loadCredentials();
13
+ if (!creds) {
14
+ console.log(
15
+ chalk.red("No estás autenticado. Ejecuta primero: skill-manager login")
16
+ );
17
+ process.exit(1);
18
+ }
19
+
8
20
  const existing = await loadConfig();
9
- if (existing) {
21
+ if (existing?.companyId) {
10
22
  const { overwrite } = await inquirer.prompt([
11
23
  {
12
24
  type: "confirm",
13
25
  name: "overwrite",
14
- message: "Ya existe una configuración. ¿Deseas sobrescribirla?",
26
+ message: `Ya estás configurado para ${existing.companyName || "una empresa"}. ¿Reconfigurar?`,
15
27
  default: false,
16
28
  },
17
29
  ]);
@@ -21,110 +33,101 @@ export async function init() {
21
33
  }
22
34
  }
23
35
 
24
- const answers = await inquirer.prompt([
25
- {
26
- type: "list",
27
- name: "type",
28
- message: "Tipo de repositorio de skills:",
29
- choices: [
30
- { name: "GitLab", value: "gitlab" },
31
- { name: "GitHub", value: "github" },
32
- { name: "Local (carpeta en disco)", value: "local" },
33
- ],
34
- },
35
- {
36
- type: "input",
37
- name: "url",
38
- message: "URL base del servidor:",
39
- default: (prev) =>
40
- prev.type === "github"
41
- ? "https://github.com"
42
- : "https://git.basetis.com",
43
- when: (prev) => prev.type !== "local",
44
- },
45
- {
46
- type: "input",
47
- name: "projectId",
48
- message: (prev) =>
49
- prev.type === "gitlab"
50
- ? "Project ID (numérico) o ruta (grupo/repo):"
51
- : "Repositorio (owner/repo):",
52
- default: (prev) =>
53
- prev.type === "gitlab" ? "okr-ia/basetisskills" : "",
54
- when: (prev) => prev.type !== "local",
55
- validate: (val) => (val.trim() ? true : "Este campo es obligatorio."),
56
- },
57
- {
58
- type: "input",
59
- name: "localPath",
60
- message: "Ruta absoluta a la carpeta de skills:",
61
- when: (prev) => prev.type === "local",
62
- validate: (val) => (val.trim() ? true : "Este campo es obligatorio."),
63
- },
64
- {
65
- type: "password",
66
- name: "token",
67
- message: "Token de acceso personal (se guardará en ~/.skill-manager/config.json):",
68
- when: (prev) => prev.type !== "local",
69
- validate: (val) => (val.trim() ? true : "El token es obligatorio para repos remotos."),
70
- },
71
- {
72
- type: "input",
73
- name: "branch",
74
- message: "Rama del repositorio:",
75
- default: "main",
76
- when: (prev) => prev.type !== "local",
77
- },
36
+ // Fetch user info from API
37
+ const spinner = ora("Conectando con Skillsmanager...").start();
38
+ let me;
39
+ try {
40
+ me = await apiRequest("/api/me");
41
+ spinner.succeed(`Conectado como ${chalk.bold(me.name)} (${me.email})`);
42
+ } catch (err) {
43
+ spinner.fail("No se pudo conectar con la API.");
44
+ console.error(chalk.red(` ${err.message}`));
45
+ process.exit(1);
46
+ }
47
+
48
+ if (!me.companies || me.companies.length === 0) {
49
+ console.log(
50
+ chalk.red(
51
+ "\nNo perteneces a ninguna empresa. Contacta con un administrador."
52
+ )
53
+ );
54
+ process.exit(1);
55
+ }
56
+
57
+ // Select company (if more than one)
58
+ let company;
59
+ if (me.companies.length === 1) {
60
+ company = me.companies[0];
61
+ console.log(chalk.dim(` Empresa: ${company.name} (${company.role})`));
62
+ } else {
63
+ const { companyId } = await inquirer.prompt([
64
+ {
65
+ type: "list",
66
+ name: "companyId",
67
+ message: "Selecciona tu empresa:",
68
+ choices: me.companies.map((c) => ({
69
+ name: `${c.name} (${c.role})`,
70
+ value: c.id,
71
+ })),
72
+ },
73
+ ]);
74
+ company = me.companies.find((c) => c.id === companyId);
75
+ }
76
+
77
+ // Select default tool
78
+ const { defaultTool } = await inquirer.prompt([
78
79
  {
79
80
  type: "list",
80
81
  name: "defaultTool",
81
82
  message: "Herramienta de IA por defecto:",
82
83
  choices: [
83
84
  { name: "Claude Code", value: "claude" },
84
- { name: "Gemini CLI", value: "gemini" },
85
85
  { name: "Cursor", value: "cursor" },
86
86
  { name: "Windsurf", value: "windsurf" },
87
- { name: "Preguntar siempre", value: null },
87
+ { name: "Gemini CLI", value: "gemini" },
88
+ { name: "Codex", value: "codex" },
88
89
  ],
90
+ default: existing?.defaultTool || "claude",
89
91
  },
90
92
  ]);
91
93
 
92
94
  const config = {
93
- registry: {
94
- type: answers.type,
95
- url: answers.url || null,
96
- projectId: answers.projectId || answers.localPath || "",
97
- token: answers.token || "",
98
- branch: answers.branch || "main",
99
- },
100
- defaultTool: answers.defaultTool,
95
+ companyId: company.id,
96
+ companyName: company.name,
97
+ apiUrl: existing?.apiUrl || "http://localhost:3001",
98
+ defaultTool,
101
99
  };
102
100
 
103
- // Verify remote connection
104
- if (config.registry.type !== "local") {
105
- const spinner = ora("Verificando conexión con el repositorio...").start();
106
- try {
107
- await verifyConnection(config.registry);
108
- spinner.succeed("Conexión verificada correctamente.");
109
- } catch (err) {
110
- spinner.fail("No se pudo conectar al repositorio.");
111
- console.error(chalk.red(` Error: ${err.message}`));
112
- const { saveAnyway } = await inquirer.prompt([
113
- {
114
- type: "confirm",
115
- name: "saveAnyway",
116
- message: "¿Guardar la configuración de todas formas?",
117
- default: false,
118
- },
119
- ]);
120
- if (!saveAnyway) {
121
- console.log(chalk.yellow("Operación cancelada."));
122
- return;
123
- }
124
- }
101
+ await saveConfig(config);
102
+
103
+ // Generate and install find-skill
104
+ const findSpinner = ora("Generando find-skill personalizado...").start();
105
+ try {
106
+ const findSkillContent = await generateFindSkill(config);
107
+ const adapter = getAdapter(defaultTool);
108
+ await adapter.install("find-skill", findSkillContent, "personal");
109
+ findSpinner.succeed("Find-skill instalado");
110
+ } catch (err) {
111
+ findSpinner.fail("No se pudo instalar el find-skill");
112
+ console.error(chalk.dim(` ${err.message}`));
113
+ console.log(
114
+ chalk.dim(" Puedes intentarlo más tarde con: skill-manager sync")
115
+ );
125
116
  }
126
117
 
127
- await saveConfig(config);
128
- console.log(chalk.green("✔ Configuración guardada correctamente."));
118
+ console.log(chalk.green("\n✔ Configuración completa."));
129
119
  console.log(chalk.dim(` → ${getConfigPath()}`));
120
+ console.log(
121
+ chalk.dim(
122
+ ` Empresa: ${company.name} | Herramienta: ${defaultTool}`
123
+ )
124
+ );
125
+ console.log(
126
+ chalk.dim(
127
+ "\n El find-skill te recomendará skills cuando trabajes en tu editor."
128
+ )
129
+ );
130
+ console.log(
131
+ chalk.dim(" Para ver skills disponibles: skill-manager list")
132
+ );
130
133
  }
@@ -1,29 +1,101 @@
1
1
  import chalk from "chalk";
2
2
  import ora from "ora";
3
3
  import inquirer from "inquirer";
4
- import { resolveChain } from "../manifest.js";
5
- import { fetchFileContent } from "../utils/gitlab.js";
4
+ import { requireConfig } from "../utils/config.js";
5
+ import { apiRequest } from "../utils/api.js";
6
6
  import { getAdapter, detectInstalledTools } from "../adapters/index.js";
7
7
 
8
8
  export async function install(skillPath, options = {}) {
9
- // Resolve the full dependency chain
10
- const spinner = ora("Resolviendo dependencias...").start();
11
- let chain;
9
+ const config = await requireConfig();
10
+
11
+ // Fetch all published skills
12
+ const spinner = ora("Cargando skills...").start();
13
+ let skills;
12
14
  try {
13
- chain = await resolveChain(skillPath);
14
- spinner.succeed(`Cadena resuelta: ${chain.length} skill(s).`);
15
+ skills = await apiRequest(`/api/companies/${config.companyId}/skills`);
16
+ skills = skills.filter((s) => s.status === "published");
15
17
  } catch (err) {
16
- spinner.fail("Error resolviendo dependencias.");
17
- console.error(chalk.red(err.message));
18
+ spinner.fail("No se pudieron cargar los skills.");
19
+ throw err;
20
+ }
21
+
22
+ // Find the target skill
23
+ const target = skills.find((s) => s.path === skillPath);
24
+ if (!target) {
25
+ spinner.fail(`Skill no encontrado: ${skillPath}`);
26
+ console.log(
27
+ chalk.dim(" Usa 'skill-manager list' para ver los skills disponibles.")
28
+ );
18
29
  process.exit(1);
19
30
  }
20
31
 
32
+ // Find children (skills whose path starts with target path)
33
+ const children = skills.filter(
34
+ (s) =>
35
+ s.path !== target.path &&
36
+ s.path.startsWith(target.path + "/") &&
37
+ !s.path.endsWith("/_index")
38
+ );
39
+
40
+ spinner.stop();
41
+
42
+ // Build install list
43
+ const toInstall = [target];
44
+
45
+ if (children.length > 0) {
46
+ console.log(
47
+ chalk.blue.bold(
48
+ `\n ${target.name} tiene ${children.length} skill(s) hijo(s):\n`
49
+ )
50
+ );
51
+ for (const child of children) {
52
+ const desc = child.description ? chalk.dim(` — ${child.description}`) : "";
53
+ console.log(` ${chalk.cyan(child.path)}${desc}`);
54
+ }
55
+ console.log();
56
+
57
+ const { childAction } = await inquirer.prompt([
58
+ {
59
+ type: "list",
60
+ name: "childAction",
61
+ message: "¿Qué quieres hacer?",
62
+ choices: [
63
+ { name: "Instalar solo el padre", value: "parent" },
64
+ { name: "Instalar padre + todos los hijos", value: "all" },
65
+ { name: "Seleccionar cuáles instalar", value: "select" },
66
+ ],
67
+ },
68
+ ]);
69
+
70
+ if (childAction === "all") {
71
+ toInstall.push(...children);
72
+ } else if (childAction === "select") {
73
+ const { selected } = await inquirer.prompt([
74
+ {
75
+ type: "checkbox",
76
+ name: "selected",
77
+ message: "Selecciona los skills a instalar:",
78
+ choices: children.map((c) => ({
79
+ name: `${c.path} ${chalk.dim(c.description || "")}`,
80
+ value: c.path,
81
+ checked: false,
82
+ })),
83
+ },
84
+ ]);
85
+ const selectedSkills = children.filter((c) =>
86
+ selected.includes(c.path)
87
+ );
88
+ toInstall.push(...selectedSkills);
89
+ }
90
+ }
91
+
21
92
  // Show what will be installed
22
- console.log();
23
- console.log(chalk.blue.bold(" Skills a instalar (en orden):"));
24
- for (const [i, skill] of chain.entries()) {
25
- const arrow = i < chain.length - 1 ? "├─" : "└─";
26
- console.log(` ${arrow} ${skill.path} ${chalk.gray(`v${skill.version}`)}`);
93
+ console.log(chalk.blue.bold("\n Skills a instalar:\n"));
94
+ for (const [i, skill] of toInstall.entries()) {
95
+ const arrow = i < toInstall.length - 1 ? "├─" : "└─";
96
+ console.log(
97
+ ` ${arrow} ${skill.path} ${chalk.gray(`v${skill.version}`)}`
98
+ );
27
99
  }
28
100
  console.log();
29
101
 
@@ -31,6 +103,8 @@ export async function install(skillPath, options = {}) {
31
103
  let toolNames = [];
32
104
  if (options.tool) {
33
105
  toolNames = options.tool.split(",").map((t) => t.trim());
106
+ } else if (config.defaultTool) {
107
+ toolNames = [config.defaultTool];
34
108
  } else {
35
109
  const detected = await detectInstalledTools();
36
110
  if (detected.length === 0) {
@@ -66,25 +140,22 @@ export async function install(skillPath, options = {}) {
66
140
  message: "¿Dónde instalar las skills?",
67
141
  choices: [
68
142
  { name: "Global (solo para ti, en ~/)", value: "personal" },
69
- { name: "Proyecto (commitear al repo)", value: "project" },
143
+ { name: "Proyecto (en el repo actual)", value: "project" },
70
144
  ],
71
145
  },
72
146
  ]);
73
147
  scope = chosen;
74
148
  }
75
- // Normalize "global" to "personal"
76
149
  if (scope === "global") scope = "personal";
77
150
 
78
- // Install each skill in each tool
151
+ // Install
79
152
  const adapters = toolNames.map((name) => getAdapter(name));
80
153
  let installed = 0;
81
154
 
82
- for (const skill of chain) {
155
+ for (const skill of toInstall) {
83
156
  const installSpinner = ora(`Instalando ${skill.path}...`).start();
84
157
  try {
85
- const skillFile = `${skill.path}/SKILL.md`;
86
- const content = await fetchFileContent(skillFile);
87
-
158
+ const content = skill.content || "";
88
159
  for (const adapter of adapters) {
89
160
  await adapter.install(skill.path, content, scope);
90
161
  }
@@ -100,6 +171,8 @@ export async function install(skillPath, options = {}) {
100
171
  console.log();
101
172
  const toolList = adapters.map((a) => a.displayName).join(" y ");
102
173
  console.log(
103
- chalk.green(`✔ ${installed} skill(s) instalada(s) en ${toolList} (${scope})`)
174
+ chalk.green(
175
+ `✔ ${installed} skill(s) instalada(s) en ${toolList} (${scope})`
176
+ )
104
177
  );
105
178
  }
@@ -1,29 +1,49 @@
1
1
  import chalk from "chalk";
2
2
  import ora from "ora";
3
- import { getAllSkills } from "../manifest.js";
3
+ import { requireConfig } from "../utils/config.js";
4
+ import { apiRequest } from "../utils/api.js";
4
5
 
5
- const SECTIONS = [
6
- { prefix: "user/", label: "User Skills" },
7
- { prefix: "internal/", label: "Internal Skills" },
8
- { prefix: "external/", label: "External Skills" },
9
- ];
6
+ const LEVEL_ICONS = {
7
+ root: "🏢",
8
+ user: "👤",
9
+ habilidades: "🛠",
10
+ area: "📂",
11
+ team: "👥",
12
+ sector: "🏭",
13
+ proyecto: "📦",
14
+ };
15
+
16
+ const TYPE_LABELS = {
17
+ contexto: chalk.blue("contexto"),
18
+ habilidad: chalk.magenta("habilidad"),
19
+ agente: chalk.green("agente"),
20
+ };
10
21
 
11
22
  export async function list(options = {}) {
12
- const spinner = ora("Cargando catálogo de skills...").start();
23
+ const config = await requireConfig();
13
24
 
25
+ const spinner = ora("Cargando skills...").start();
14
26
  let skills;
15
27
  try {
16
- skills = await getAllSkills();
17
- spinner.succeed("Catálogo cargado.");
28
+ skills = await apiRequest(`/api/companies/${config.companyId}/skills`);
29
+ spinner.stop();
18
30
  } catch (err) {
19
- spinner.fail("Error cargando el catálogo.");
20
- console.error(chalk.red(err.message));
21
- process.exit(1);
31
+ spinner.fail("No se pudieron cargar los skills.");
32
+ throw err;
22
33
  }
23
34
 
35
+ // Filter published only
36
+ skills = skills.filter((s) => s.status === "published");
37
+
38
+ // Apply text filter if provided
24
39
  if (options.filter) {
25
- const term = options.filter.toLowerCase();
26
- skills = skills.filter((s) => s.path.toLowerCase().includes(term));
40
+ const q = options.filter.toLowerCase();
41
+ skills = skills.filter(
42
+ (s) =>
43
+ s.name.toLowerCase().includes(q) ||
44
+ s.path.toLowerCase().includes(q) ||
45
+ (s.description || "").toLowerCase().includes(q)
46
+ );
27
47
  }
28
48
 
29
49
  if (skills.length === 0) {
@@ -31,39 +51,39 @@ export async function list(options = {}) {
31
51
  return;
32
52
  }
33
53
 
34
- for (const section of SECTIONS) {
35
- const group = skills.filter((s) => s.path.startsWith(section.prefix));
36
- if (group.length === 0) continue;
54
+ // Group by level
55
+ const groups = {};
56
+ for (const s of skills) {
57
+ const level = s.level || "otro";
58
+ if (!groups[level]) groups[level] = [];
59
+ groups[level].push(s);
60
+ }
37
61
 
38
- console.log();
39
- console.log(chalk.blue.bold(` ${section.label}`));
40
- console.log(chalk.blue(" " + "─".repeat(40)));
62
+ console.log(chalk.bold(`\n ${skills.length} skills disponibles\n`));
41
63
 
42
- for (const skill of group) {
43
- const parent = skill.parent
44
- ? chalk.dim(` ← ${skill.parent}`)
45
- : "";
46
- console.log(
47
- ` ${chalk.white(skill.path)} ${chalk.gray(`v${skill.version}`)}${parent}`
48
- );
49
- }
50
- }
64
+ const levelOrder = [
65
+ "root",
66
+ "habilidades",
67
+ "area",
68
+ "team",
69
+ "sector",
70
+ "proyecto",
71
+ "user",
72
+ ];
51
73
 
52
- // Skills that don't match any section
53
- const uncategorized = skills.filter(
54
- (s) => !SECTIONS.some((sec) => s.path.startsWith(sec.prefix))
55
- );
56
- if (uncategorized.length > 0) {
57
- console.log();
58
- console.log(chalk.blue.bold(" Other"));
59
- console.log(chalk.blue(" " + "─".repeat(40)));
60
- for (const skill of uncategorized) {
61
- console.log(
62
- ` ${chalk.white(skill.path)} ${chalk.gray(`v${skill.version}`)}`
63
- );
74
+ for (const level of levelOrder) {
75
+ const items = groups[level];
76
+ if (!items) continue;
77
+
78
+ const icon = LEVEL_ICONS[level] || "•";
79
+ console.log(chalk.bold(` ${icon} ${level.toUpperCase()}`));
80
+
81
+ for (const s of items) {
82
+ const type = TYPE_LABELS[s.skill_type] || s.skill_type;
83
+ const path = chalk.cyan(s.path);
84
+ const desc = s.description ? chalk.dim(`${s.description}`) : "";
85
+ console.log(` ${path} ${type}${desc}`);
64
86
  }
87
+ console.log();
65
88
  }
66
-
67
- console.log();
68
- console.log(chalk.dim(` Total: ${skills.length} skills`));
69
89
  }
@@ -0,0 +1,20 @@
1
+ import chalk from "chalk";
2
+ import ora from "ora";
3
+ import { requireConfig } from "../utils/config.js";
4
+ import { generateFindSkill } from "../utils/find-skill.js";
5
+ import { getAdapter } from "../adapters/index.js";
6
+
7
+ export async function sync() {
8
+ const config = await requireConfig();
9
+
10
+ const spinner = ora("Actualizando find-skill...").start();
11
+ try {
12
+ const findSkillContent = await generateFindSkill(config);
13
+ const adapter = getAdapter(config.defaultTool);
14
+ await adapter.install("find-skill", findSkillContent, "personal");
15
+ spinner.succeed("Find-skill actualizado con tus permisos más recientes");
16
+ } catch (err) {
17
+ spinner.fail("No se pudo actualizar el find-skill");
18
+ throw err;
19
+ }
20
+ }
package/src/utils/api.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { getValidToken, clearCredentials } from "./auth.js";
2
2
  import { loadConfig } from "./config.js";
3
3
 
4
- const DEFAULT_API_URL = "https://skillsmanager.basetis.com";
4
+ const DEFAULT_API_URL = "http://localhost:3001";
5
5
 
6
6
  async function getApiUrl() {
7
7
  const config = await loadConfig();
@@ -0,0 +1,123 @@
1
+ import { apiRequest } from "./api.js";
2
+
3
+ export async function generateFindSkill(config) {
4
+ const permissions = await apiRequest("/api/me/permissions");
5
+
6
+ if (!permissions.company) {
7
+ throw new Error("No perteneces a ninguna empresa.");
8
+ }
9
+
10
+ const { company, entities, projects } = permissions;
11
+ const tool = config.defaultTool || "tu herramienta";
12
+ const toolLabel = {
13
+ claude: "Claude Code",
14
+ cursor: "Cursor",
15
+ windsurf: "Windsurf",
16
+ gemini: "Gemini CLI",
17
+ codex: "Codex",
18
+ }[config.defaultTool] || config.defaultTool;
19
+
20
+ const me = await apiRequest("/api/me");
21
+
22
+ let md = `# Skill Finder — ${company.name}
23
+
24
+ Eres un asistente de descubrimiento de skills para ${me.name} (${me.email}).
25
+ Tu función es ayudar al usuario a encontrar e instalar los skills relevantes
26
+ para su proyecto usando el CLI de Basetis Skill Manager.
27
+
28
+ ## Tu usuario
29
+ - Nombre: ${me.name}
30
+ - Empresa: ${company.name}
31
+ - Rol: ${company.role}
32
+ - Herramienta: ${toolLabel}
33
+
34
+ ## Instrucciones
35
+
36
+ ### Cuándo recomendar
37
+ - Cuando el usuario empiece a trabajar en un proyecto nuevo
38
+ - Cuando detectes tecnologías en el proyecto para las que hay skills disponibles
39
+ - Cuando el usuario pregunte por buenas prácticas o configuración
40
+ - Cuando el usuario haga algo que un skill podría mejorar
41
+
42
+ ### Cómo recomendar
43
+ - Analiza los ficheros del proyecto (package.json, tsconfig.json, Dockerfile, etc.)
44
+ - Menciona el skill y qué aporta en UNA línea
45
+ - Da el comando exacto para instalar
46
+ - No insistas si el usuario no quiere — menciona una vez y ya
47
+
48
+ ### Qué puede instalar este usuario
49
+
50
+ #### Habilidades (cross-cutting, para todos en ${company.name})
51
+ Skills de lenguajes, frameworks, herramientas y buenas prácticas compartidas.
52
+
53
+ \`\`\`bash
54
+ skill-manager list --filter habilidades
55
+ \`\`\`
56
+
57
+ Ejemplos de cuándo recomendar:
58
+ - Si detectas TypeScript → \`skill-manager install habilidades/typescript\`
59
+ - Si detectas React → \`skill-manager install habilidades/react\`
60
+ - Si detectas Express → \`skill-manager install habilidades/express-api\`
61
+ - Si detectas PostgreSQL → \`skill-manager install habilidades/postgresql\`
62
+ `;
63
+
64
+ // Areas
65
+ const areas = entities.filter((e) => e.type === "area");
66
+ if (areas.length > 0) {
67
+ md += `
68
+ #### Áreas: ${areas.map((a) => a.name).join(", ")}
69
+ Skills específicos de las áreas donde eres miembro.
70
+
71
+ ${areas.map((a) => `\`\`\`bash\nskill-manager list --filter area/${a.slug}\n\`\`\``).join("\n")}
72
+ `;
73
+ }
74
+
75
+ // Teams
76
+ const teams = entities.filter((e) => e.type === "team");
77
+ if (teams.length > 0) {
78
+ md += `
79
+ #### Equipos: ${teams.map((t) => t.name).join(", ")}
80
+ Skills específicos de tus equipos.
81
+
82
+ ${teams.map((t) => `\`\`\`bash\nskill-manager list --filter team/${t.slug}\n\`\`\``).join("\n")}
83
+ `;
84
+ }
85
+
86
+ // Sectors
87
+ const sectors = entities.filter((e) => e.type === "sector");
88
+ if (sectors.length > 0) {
89
+ md += `
90
+ #### Sectores: ${sectors.map((s) => s.name).join(", ")}
91
+
92
+ ${sectors.map((s) => `\`\`\`bash\nskill-manager list --filter sector/${s.slug}\n\`\`\``).join("\n")}
93
+ `;
94
+ }
95
+
96
+ // Projects
97
+ if (projects.length > 0) {
98
+ md += `
99
+ #### Proyectos: ${projects.map((p) => p.name).join(", ")}
100
+ Skills específicos de tus proyectos asignados.
101
+
102
+ ${projects.map((p) => `\`\`\`bash\nskill-manager list --filter proyecto/${p.slug}\n\`\`\``).join("\n")}
103
+ `;
104
+ }
105
+
106
+ md += `
107
+ ### Qué NO hacer
108
+ - No menciones skills de áreas, equipos o proyectos que no aparecen en las secciones anteriores
109
+ - No muestres el catálogo completo — recomienda solo lo relevante al contexto actual
110
+ - No expongas emails, tokens ni información interna del sistema
111
+ - No intentes modificar archivos de configuración del skill manager directamente
112
+ - No instales skills tú mismo — siempre recomienda el comando CLI al usuario
113
+
114
+ ### Comandos útiles
115
+ - Ver todo lo disponible: \`skill-manager list\`
116
+ - Filtrar: \`skill-manager list --filter <texto>\`
117
+ - Instalar: \`skill-manager install <path>\`
118
+ - Ver quién eres: \`skill-manager whoami\`
119
+ - Actualizar este skill: \`skill-manager sync\`
120
+ `;
121
+
122
+ return md;
123
+ }