@basetisia/skill-manager 0.1.2 → 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 +13 -0
- package/package.json +1 -1
- package/src/commands/init.js +94 -91
- package/src/commands/install.js +95 -22
- package/src/commands/list.js +64 -44
- package/src/commands/sync.js +20 -0
- package/src/utils/api.js +1 -1
- package/src/utils/find-skill.js +123 -0
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
package/src/commands/init.js
CHANGED
|
@@ -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 {
|
|
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:
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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: "
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/commands/install.js
CHANGED
|
@@ -1,29 +1,101 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import ora from "ora";
|
|
3
3
|
import inquirer from "inquirer";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
+
skills = await apiRequest(`/api/companies/${config.companyId}/skills`);
|
|
16
|
+
skills = skills.filter((s) => s.status === "published");
|
|
15
17
|
} catch (err) {
|
|
16
|
-
spinner.fail("
|
|
17
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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 (
|
|
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
|
|
151
|
+
// Install
|
|
79
152
|
const adapters = toolNames.map((name) => getAdapter(name));
|
|
80
153
|
let installed = 0;
|
|
81
154
|
|
|
82
|
-
for (const skill of
|
|
155
|
+
for (const skill of toInstall) {
|
|
83
156
|
const installSpinner = ora(`Instalando ${skill.path}...`).start();
|
|
84
157
|
try {
|
|
85
|
-
const
|
|
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(
|
|
174
|
+
chalk.green(
|
|
175
|
+
`✔ ${installed} skill(s) instalada(s) en ${toolList} (${scope})`
|
|
176
|
+
)
|
|
104
177
|
);
|
|
105
178
|
}
|
package/src/commands/list.js
CHANGED
|
@@ -1,29 +1,49 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import ora from "ora";
|
|
3
|
-
import {
|
|
3
|
+
import { requireConfig } from "../utils/config.js";
|
|
4
|
+
import { apiRequest } from "../utils/api.js";
|
|
4
5
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
|
23
|
+
const config = await requireConfig();
|
|
13
24
|
|
|
25
|
+
const spinner = ora("Cargando skills...").start();
|
|
14
26
|
let skills;
|
|
15
27
|
try {
|
|
16
|
-
skills = await
|
|
17
|
-
spinner.
|
|
28
|
+
skills = await apiRequest(`/api/companies/${config.companyId}/skills`);
|
|
29
|
+
spinner.stop();
|
|
18
30
|
} catch (err) {
|
|
19
|
-
spinner.fail("
|
|
20
|
-
|
|
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
|
|
26
|
-
skills = skills.filter(
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
64
|
+
const levelOrder = [
|
|
65
|
+
"root",
|
|
66
|
+
"habilidades",
|
|
67
|
+
"area",
|
|
68
|
+
"team",
|
|
69
|
+
"sector",
|
|
70
|
+
"proyecto",
|
|
71
|
+
"user",
|
|
72
|
+
];
|
|
51
73
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
console.log();
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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 = "
|
|
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
|
+
}
|