@skillsmanagerbasetis/skill-manager-local 0.4.2

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.
Files changed (41) hide show
  1. package/bin/index.js +257 -0
  2. package/package.json +23 -0
  3. package/src/adapters/adapters.test.js +175 -0
  4. package/src/adapters/aider.js +41 -0
  5. package/src/adapters/claude.js +26 -0
  6. package/src/adapters/codex.js +31 -0
  7. package/src/adapters/copilot.js +42 -0
  8. package/src/adapters/cursor.js +54 -0
  9. package/src/adapters/gemini.js +26 -0
  10. package/src/adapters/index.js +57 -0
  11. package/src/adapters/opencode.js +26 -0
  12. package/src/adapters/qwen.js +26 -0
  13. package/src/adapters/roocode.js +41 -0
  14. package/src/adapters/shared.js +48 -0
  15. package/src/adapters/windsurf.js +93 -0
  16. package/src/adapters/zed.js +41 -0
  17. package/src/commands/detect.js +135 -0
  18. package/src/commands/init.js +139 -0
  19. package/src/commands/install-bulk.js +170 -0
  20. package/src/commands/install.js +216 -0
  21. package/src/commands/list.js +116 -0
  22. package/src/commands/login.js +29 -0
  23. package/src/commands/logout.js +13 -0
  24. package/src/commands/push.js +104 -0
  25. package/src/commands/read.js +139 -0
  26. package/src/commands/remove.js +115 -0
  27. package/src/commands/search.js +107 -0
  28. package/src/commands/status.js +87 -0
  29. package/src/commands/sync.js +20 -0
  30. package/src/commands/update.js +140 -0
  31. package/src/commands/whoami.js +29 -0
  32. package/src/env.js +3 -0
  33. package/src/manifest.js +84 -0
  34. package/src/manifest.test.js +183 -0
  35. package/src/utils/api.js +40 -0
  36. package/src/utils/auth.js +289 -0
  37. package/src/utils/config.js +47 -0
  38. package/src/utils/find-skill.js +129 -0
  39. package/src/utils/fs.js +39 -0
  40. package/src/utils/gitlab.js +78 -0
  41. package/src/utils/installed.js +88 -0
package/bin/index.js ADDED
@@ -0,0 +1,257 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from "commander";
4
+ import { init } from "../src/commands/init.js";
5
+ import { list } from "../src/commands/list.js";
6
+ import { install } from "../src/commands/install.js";
7
+ import { installBulk } from "../src/commands/install-bulk.js";
8
+ import { status } from "../src/commands/status.js";
9
+ import { detect } from "../src/commands/detect.js";
10
+ import { login } from "../src/commands/login.js";
11
+ import { logout } from "../src/commands/logout.js";
12
+ import { whoami } from "../src/commands/whoami.js";
13
+ import { sync } from "../src/commands/sync.js";
14
+ import { push } from "../src/commands/push.js";
15
+ import { update } from "../src/commands/update.js";
16
+ import { remove } from "../src/commands/remove.js";
17
+ import { read } from "../src/commands/read.js";
18
+ import { search } from "../src/commands/search.js";
19
+
20
+ const program = new Command();
21
+
22
+ program
23
+ .name("skill-manager")
24
+ .description("CLI para gestionar skills de IA en tu editor")
25
+ .version("0.4.0");
26
+
27
+ program
28
+ .command("init")
29
+ .description("Configura el repositorio de skills y la herramienta por defecto")
30
+ .action(async () => {
31
+ try {
32
+ await init();
33
+ } catch (err) {
34
+ console.error(err.message);
35
+ process.exit(1);
36
+ }
37
+ });
38
+
39
+ program
40
+ .command("list")
41
+ .description("Lista todas las skills disponibles en el catálogo")
42
+ .option("-f, --filter <texto>", "Filtra skills por nombre")
43
+ .option("-j, --json", "Salida en formato JSON")
44
+ .action(async (options) => {
45
+ try {
46
+ await list(options);
47
+ } catch (err) {
48
+ console.error(err.message);
49
+ process.exit(1);
50
+ }
51
+ });
52
+
53
+ program
54
+ .command("search [texto]")
55
+ .description("Busca skills por texto, tipo, nivel, entidad o proyecto (solo las que puedes ver)")
56
+ .option("-t, --type <tipo>", "Filtrar por tipo: contexto, habilidad, agente")
57
+ .option("-l, --level <nivel>", "Filtrar por nivel: root, user, habilidades, area, team, sector, proyecto")
58
+ .option("-e, --entity <slug>", "Filtrar por entidad (slug del area/team/sector)")
59
+ .option("-p, --project <slug>", "Filtrar por proyecto (slug)")
60
+ .option("-j, --json", "Salida en formato JSON")
61
+ .action(async (texto, options) => {
62
+ try {
63
+ await search(texto, options);
64
+ } catch (err) {
65
+ console.error(err.message);
66
+ process.exit(1);
67
+ }
68
+ });
69
+
70
+ program
71
+ .command("install <ruta>")
72
+ .description("Instala una skill y sus dependencias (cadena de parents)")
73
+ .option("-t, --tool <herramienta>", "Herramienta(s) destino, separadas por coma (claude,cursor,gemini,codex,windsurf,opencode,copilot,aider,zed,roocode,qwen)")
74
+ .option("-s, --scope <ámbito>", "Ámbito de instalación: global o project")
75
+ .option("-f, --force", "Sobreescribir sin confirmar si la skill ya existe")
76
+ .action(async (ruta, options) => {
77
+ try {
78
+ await install(ruta, options);
79
+ } catch (err) {
80
+ console.error(err.message);
81
+ process.exit(1);
82
+ }
83
+ });
84
+
85
+ // Bulk install commands: install-project / install-area / install-team / install-sector
86
+ const bulkOpts = (cmd) =>
87
+ cmd
88
+ .option("-t, --tool <herramienta>", "Herramienta(s) destino, separadas por coma")
89
+ .option("-s, --scope <ámbito>", "Ámbito de instalación: global o project")
90
+ .option("-y, --yes", "Asume sí en las confirmaciones");
91
+
92
+ bulkOpts(
93
+ program
94
+ .command("install-project <slug>")
95
+ .description("Instala todas las skills publicadas de un proyecto")
96
+ ).action(async (slug, options) => {
97
+ try { await installBulk("project", slug, options); }
98
+ catch (err) { console.error(err.message); process.exit(1); }
99
+ });
100
+
101
+ bulkOpts(
102
+ program
103
+ .command("install-area <slug>")
104
+ .description("Instala todas las skills publicadas de un área")
105
+ ).action(async (slug, options) => {
106
+ try { await installBulk("area", slug, options); }
107
+ catch (err) { console.error(err.message); process.exit(1); }
108
+ });
109
+
110
+ bulkOpts(
111
+ program
112
+ .command("install-team <slug>")
113
+ .description("Instala todas las skills publicadas de un team")
114
+ ).action(async (slug, options) => {
115
+ try { await installBulk("team", slug, options); }
116
+ catch (err) { console.error(err.message); process.exit(1); }
117
+ });
118
+
119
+ bulkOpts(
120
+ program
121
+ .command("install-sector <slug>")
122
+ .description("Instala todas las skills publicadas de un sector")
123
+ ).action(async (slug, options) => {
124
+ try { await installBulk("sector", slug, options); }
125
+ catch (err) { console.error(err.message); process.exit(1); }
126
+ });
127
+
128
+ program
129
+ .command("status")
130
+ .description("Muestra el estado de las skills instaladas vs el catálogo")
131
+ .option("-j, --json", "Salida en formato JSON")
132
+ .action(async (options) => {
133
+ try {
134
+ await status(options);
135
+ } catch (err) {
136
+ console.error(err.message);
137
+ process.exit(1);
138
+ }
139
+ });
140
+
141
+ program
142
+ .command("detect")
143
+ .description("Analiza el proyecto actual y recomienda skills relevantes")
144
+ .action(async () => {
145
+ try {
146
+ await detect();
147
+ } catch (err) {
148
+ console.error(err.message);
149
+ process.exit(1);
150
+ }
151
+ });
152
+
153
+ program
154
+ .command("sync")
155
+ .description("Actualiza el find-skill con tus permisos más recientes")
156
+ .action(async () => {
157
+ try {
158
+ await sync();
159
+ } catch (err) {
160
+ console.error(err.message);
161
+ process.exit(1);
162
+ }
163
+ });
164
+
165
+ program
166
+ .command("push <ruta>")
167
+ .description("Publica los cambios locales de un skill al servidor")
168
+ .action(async (ruta) => {
169
+ try {
170
+ await push(ruta);
171
+ } catch (err) {
172
+ console.error(err.message);
173
+ process.exit(1);
174
+ }
175
+ });
176
+
177
+ program
178
+ .command("update")
179
+ .description("Comprueba y aplica actualizaciones de skills instalados")
180
+ .option("-n, --dry-run", "Muestra qué se actualizaría sin aplicar cambios")
181
+ .action(async (options) => {
182
+ try {
183
+ await update(options);
184
+ } catch (err) {
185
+ console.error(err.message);
186
+ process.exit(1);
187
+ }
188
+ });
189
+
190
+ program
191
+ .command("remove <ruta>")
192
+ .description("Desinstala una skill del sistema de archivos local")
193
+ .option("-t, --tool <herramienta>", "Solo eliminar de esta herramienta (claude,cursor,gemini,codex,windsurf,opencode,copilot,aider,zed,roocode,qwen)")
194
+ .option("-s, --scope <ámbito>", "Solo eliminar de este ámbito: global o project")
195
+ .option("-y, --yes", "Asume sí en las confirmaciones")
196
+ .action(async (ruta, options) => {
197
+ try {
198
+ await remove(ruta, options);
199
+ } catch (err) {
200
+ console.error(err.message);
201
+ process.exit(1);
202
+ }
203
+ });
204
+
205
+ program
206
+ .command("read <ruta>")
207
+ .description("Muestra el contenido de una skill instalada")
208
+ .option("-t, --tool <herramienta>", "Leer de esta herramienta específica")
209
+ .option("-s, --scope <ámbito>", "Leer de este ámbito: global o project")
210
+ .option("-r, --raw", "Muestra el contenido sin formateo")
211
+ .option("-j, --json", "Salida en formato JSON")
212
+ .action(async (ruta, options) => {
213
+ try {
214
+ await read(ruta, options);
215
+ } catch (err) {
216
+ console.error(err.message);
217
+ process.exit(1);
218
+ }
219
+ });
220
+
221
+ program
222
+ .command("login")
223
+ .description("Inicia sesión con tu cuenta de Basetis (Google)")
224
+ .action(async () => {
225
+ try {
226
+ await login();
227
+ } catch (err) {
228
+ console.error(err.message);
229
+ process.exit(1);
230
+ }
231
+ });
232
+
233
+ program
234
+ .command("logout")
235
+ .description("Cierra la sesión actual")
236
+ .action(async () => {
237
+ try {
238
+ await logout();
239
+ } catch (err) {
240
+ console.error(err.message);
241
+ process.exit(1);
242
+ }
243
+ });
244
+
245
+ program
246
+ .command("whoami")
247
+ .description("Muestra el usuario autenticado")
248
+ .action(async () => {
249
+ try {
250
+ await whoami();
251
+ } catch (err) {
252
+ console.error(err.message);
253
+ process.exit(1);
254
+ }
255
+ });
256
+
257
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@skillsmanagerbasetis/skill-manager-local",
3
+ "version": "0.4.2",
4
+ "description": "CLI to manage AI coding skills across editors",
5
+ "type": "module",
6
+ "engines": {
7
+ "node": ">=20"
8
+ },
9
+ "bin": {
10
+ "skill-manager-local": "bin/index.js"
11
+ },
12
+ "publishConfig": {
13
+ "registry": "https://registry.npmjs.org/",
14
+ "access": "public"
15
+ },
16
+ "dependencies": {
17
+ "chalk": "^5",
18
+ "commander": "^13",
19
+ "inquirer": "^12",
20
+ "js-yaml": "^4",
21
+ "ora": "^8"
22
+ }
23
+ }
@@ -0,0 +1,175 @@
1
+ import { describe, it, beforeEach, afterEach } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdtemp, rm, readFile, realpath } from "node:fs/promises";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+
7
+ const SKILL_CONTENT = `---
8
+ name: test-skill
9
+ version: "1.0.0"
10
+ description: A test skill
11
+ ---
12
+ You are a test skill.
13
+ `;
14
+
15
+ let tmpHome, tmpCwd, origHome, origCwd;
16
+
17
+ async function setup() {
18
+ tmpHome = await realpath(await mkdtemp(join(tmpdir(), "sm-home-")));
19
+ tmpCwd = await realpath(await mkdtemp(join(tmpdir(), "sm-cwd-")));
20
+ origHome = process.env.HOME;
21
+ origCwd = process.cwd();
22
+ process.env.HOME = tmpHome;
23
+ process.chdir(tmpCwd);
24
+ }
25
+
26
+ async function teardown() {
27
+ process.env.HOME = origHome;
28
+ process.chdir(origCwd);
29
+ await rm(tmpHome, { recursive: true, force: true });
30
+ await rm(tmpCwd, { recursive: true, force: true });
31
+ }
32
+
33
+ // Re-import adapters fresh so homeDir/cwdDir pick up the new env
34
+ async function loadAdapter(name) {
35
+ const mod = await import(`./${name}.js?t=${Date.now()}`);
36
+ return mod;
37
+ }
38
+
39
+ describe("claude adapter", () => {
40
+ beforeEach(setup);
41
+ afterEach(teardown);
42
+
43
+ it("installs to personal scope at ~/.claude/skills/", async () => {
44
+ const claude = await loadAdapter("claude");
45
+ const dest = await claude.install("external/sector/pharma", SKILL_CONTENT, "personal");
46
+ assert.equal(dest, join(tmpHome, ".claude", "skills", "external", "sector", "pharma", "SKILL.md"));
47
+ const content = await readFile(dest, "utf-8");
48
+ assert.ok(content.includes("test skill"));
49
+ });
50
+
51
+ it("installs to project scope at .claude/skills/", async () => {
52
+ const claude = await loadAdapter("claude");
53
+ const dest = await claude.install("internal/tooling/eslint", SKILL_CONTENT, "project");
54
+ assert.equal(dest, join(tmpCwd, ".claude", "skills", "internal", "tooling", "eslint", "SKILL.md"));
55
+ });
56
+
57
+ it("getInstallPath returns correct path", async () => {
58
+ const claude = await loadAdapter("claude");
59
+ const p = claude.getInstallPath("a/b/c", "personal");
60
+ assert.ok(p.endsWith(join(".claude", "skills", "a", "b", "c", "SKILL.md")));
61
+ });
62
+
63
+ it("getInstalledSkills reads back installed skills", async () => {
64
+ const claude = await loadAdapter("claude");
65
+ await claude.install("sector/pharma", SKILL_CONTENT, "personal");
66
+ const skills = await claude.getInstalledSkills("personal");
67
+ assert.equal(skills.length, 1);
68
+ assert.equal(skills[0].path, "sector/pharma");
69
+ assert.equal(skills[0].version, "1.0.0");
70
+ });
71
+ });
72
+
73
+ describe("gemini adapter", () => {
74
+ beforeEach(setup);
75
+ afterEach(teardown);
76
+
77
+ it("installs to personal scope at ~/.gemini/skills/", async () => {
78
+ const gemini = await loadAdapter("gemini");
79
+ const dest = await gemini.install("sector/pharma", SKILL_CONTENT, "personal");
80
+ assert.equal(dest, join(tmpHome, ".gemini", "skills", "sector", "pharma", "SKILL.md"));
81
+ });
82
+
83
+ it("installs to project scope at .agents/skills/", async () => {
84
+ const gemini = await loadAdapter("gemini");
85
+ const dest = await gemini.install("sector/pharma", SKILL_CONTENT, "project");
86
+ assert.equal(dest, join(tmpCwd, ".agents", "skills", "sector", "pharma", "SKILL.md"));
87
+ });
88
+ });
89
+
90
+ describe("codex adapter", () => {
91
+ beforeEach(setup);
92
+ afterEach(teardown);
93
+
94
+ it("installs to personal scope at ~/.codex/{skillName}/", async () => {
95
+ const codex = await loadAdapter("codex");
96
+ const dest = await codex.install("external/sector/pharma", SKILL_CONTENT, "personal");
97
+ assert.equal(dest, join(tmpHome, ".codex", "pharma", "SKILL.md"));
98
+ });
99
+
100
+ it("installs to project scope at .agents/skills/", async () => {
101
+ const codex = await loadAdapter("codex");
102
+ const dest = await codex.install("external/sector/pharma", SKILL_CONTENT, "project");
103
+ assert.equal(dest, join(tmpCwd, ".agents", "skills", "external", "sector", "pharma", "SKILL.md"));
104
+ });
105
+ });
106
+
107
+ describe("cursor adapter", () => {
108
+ beforeEach(setup);
109
+ afterEach(teardown);
110
+
111
+ it("installs to personal scope as .mdc with cursor frontmatter", async () => {
112
+ const cursor = await loadAdapter("cursor");
113
+ const dest = await cursor.install("external/sector/pharma", SKILL_CONTENT, "personal");
114
+ assert.equal(dest, join(tmpHome, ".cursor", "rules", "pharma.mdc"));
115
+ const content = await readFile(dest, "utf-8");
116
+ assert.ok(content.startsWith("---\ndescription: A test skill\nalwaysApply: false\n---\n"));
117
+ });
118
+
119
+ it("installs to project scope at .cursor/rules/", async () => {
120
+ const cursor = await loadAdapter("cursor");
121
+ const dest = await cursor.install("sector/pharma", SKILL_CONTENT, "project");
122
+ assert.equal(dest, join(tmpCwd, ".cursor", "rules", "pharma.mdc"));
123
+ });
124
+ });
125
+
126
+ describe("windsurf adapter", () => {
127
+ beforeEach(setup);
128
+ afterEach(teardown);
129
+
130
+ it("installs to personal scope by appending to global_rules.md", async () => {
131
+ const windsurf = await loadAdapter("windsurf");
132
+ const dest = await windsurf.install("sector/pharma", SKILL_CONTENT, "personal");
133
+ assert.equal(dest, join(tmpHome, ".windsurf", "global_rules.md"));
134
+ const content = await readFile(dest, "utf-8");
135
+ assert.ok(content.includes("## pharma"));
136
+ assert.ok(content.includes("test skill"));
137
+ });
138
+
139
+ it("appends multiple skills without overwriting", async () => {
140
+ const windsurf = await loadAdapter("windsurf");
141
+ await windsurf.install("sector/pharma", SKILL_CONTENT, "personal");
142
+ await windsurf.install("sector/tech", "---\nname: tech\nversion: \"2.0.0\"\n---\nTech skill.", "personal");
143
+ const content = await readFile(join(tmpHome, ".windsurf", "global_rules.md"), "utf-8");
144
+ assert.ok(content.includes("## pharma"));
145
+ assert.ok(content.includes("## tech"));
146
+ });
147
+
148
+ it("installs to project scope at .windsurf/rules/", async () => {
149
+ const windsurf = await loadAdapter("windsurf");
150
+ const dest = await windsurf.install("sector/pharma", SKILL_CONTENT, "project");
151
+ assert.equal(dest, join(tmpCwd, ".windsurf", "rules", "pharma.md"));
152
+ });
153
+ });
154
+
155
+ describe("adapter index", () => {
156
+ it("getAdapter returns correct adapter", async () => {
157
+ const { getAdapter } = await import("./index.js");
158
+ const claude = getAdapter("claude");
159
+ assert.equal(claude.toolName, "claude");
160
+ assert.equal(claude.displayName, "Claude Code");
161
+ });
162
+
163
+ it("getAdapter throws for unknown tool", async () => {
164
+ const { getAdapter } = await import("./index.js");
165
+ assert.throws(() => getAdapter("unknown"), { message: /no soportada/ });
166
+ });
167
+
168
+ it("getAllAdapters returns 5 adapters", async () => {
169
+ const { getAllAdapters } = await import("./index.js");
170
+ const all = getAllAdapters();
171
+ assert.equal(all.length, 5);
172
+ const names = all.map((a) => a.toolName);
173
+ assert.deepEqual(names, ["claude", "gemini", "codex", "cursor", "windsurf"]);
174
+ });
175
+ });
@@ -0,0 +1,41 @@
1
+ import { join, basename } from "node:path";
2
+ import { cwdDir, writeFile, readFile } from "../utils/fs.js";
3
+ import { parseFrontmatter, scanDir } from "./shared.js";
4
+
5
+ export const toolName = "aider";
6
+ export const displayName = "Aider";
7
+
8
+ // Aider only supports project-level conventions files
9
+ function basePath() {
10
+ return join(cwdDir(), ".aider", "skills");
11
+ }
12
+
13
+ export function getInstallPath(skillPath, scope) {
14
+ if (scope === "personal") {
15
+ return join(basePath(), `${basename(skillPath)}.md`);
16
+ }
17
+ return join(basePath(), `${basename(skillPath)}.md`);
18
+ }
19
+
20
+ export async function install(skillPath, skillContent, scope, _metadata = {}) {
21
+ const dest = getInstallPath(skillPath, scope);
22
+ await writeFile(dest, skillContent);
23
+ return dest;
24
+ }
25
+
26
+ export async function getInstalledSkills(scope) {
27
+ if (scope === "personal") return [];
28
+ const dir = basePath();
29
+ const files = await scanDir(dir, ".md");
30
+ const skills = [];
31
+ for (const file of files) {
32
+ const content = await readFile(join(dir, file));
33
+ if (!content) continue;
34
+ const { meta } = parseFrontmatter(content);
35
+ skills.push({
36
+ path: file.replace(/\.md$/, ""),
37
+ version: meta.version || "unknown",
38
+ });
39
+ }
40
+ return skills;
41
+ }
@@ -0,0 +1,26 @@
1
+ import { join } from "node:path";
2
+ import { homeDir, cwdDir, writeFile } from "../utils/fs.js";
3
+ import { scanInstalledSkills } from "./shared.js";
4
+
5
+ export const toolName = "claude";
6
+ export const displayName = "Claude Code";
7
+
8
+ function basePath(scope) {
9
+ return scope === "personal"
10
+ ? join(homeDir(), ".claude", "skills")
11
+ : join(cwdDir(), ".claude", "skills");
12
+ }
13
+
14
+ export function getInstallPath(skillPath, scope) {
15
+ return join(basePath(scope), skillPath, "SKILL.md");
16
+ }
17
+
18
+ export async function install(skillPath, skillContent, scope, _metadata = {}) {
19
+ const dest = getInstallPath(skillPath, scope);
20
+ await writeFile(dest, skillContent);
21
+ return dest;
22
+ }
23
+
24
+ export async function getInstalledSkills(scope) {
25
+ return scanInstalledSkills(basePath(scope));
26
+ }
@@ -0,0 +1,31 @@
1
+ import { join, basename } from "node:path";
2
+ import { homeDir, cwdDir, writeFile } from "../utils/fs.js";
3
+ import { scanInstalledSkills } from "./shared.js";
4
+
5
+ export const toolName = "codex";
6
+ export const displayName = "Codex CLI";
7
+
8
+ function basePath(scope) {
9
+ if (scope === "personal") {
10
+ return join(homeDir(), ".codex");
11
+ }
12
+ return join(cwdDir(), ".agents", "skills");
13
+ }
14
+
15
+ export function getInstallPath(skillPath, scope) {
16
+ if (scope === "personal") {
17
+ const skillName = basename(skillPath);
18
+ return join(basePath(scope), skillName, "SKILL.md");
19
+ }
20
+ return join(basePath(scope), skillPath, "SKILL.md");
21
+ }
22
+
23
+ export async function install(skillPath, skillContent, scope, _metadata = {}) {
24
+ const dest = getInstallPath(skillPath, scope);
25
+ await writeFile(dest, skillContent);
26
+ return dest;
27
+ }
28
+
29
+ export async function getInstalledSkills(scope) {
30
+ return scanInstalledSkills(basePath(scope));
31
+ }
@@ -0,0 +1,42 @@
1
+ import { join, basename } from "node:path";
2
+ import { cwdDir, writeFile, readFile } from "../utils/fs.js";
3
+ import { parseFrontmatter, scanDir } from "./shared.js";
4
+
5
+ export const toolName = "copilot";
6
+ export const displayName = "GitHub Copilot";
7
+
8
+ // Copilot only supports project-level skills
9
+ function basePath() {
10
+ return join(cwdDir(), ".github", "copilot", "skills");
11
+ }
12
+
13
+ export function getInstallPath(skillPath, scope) {
14
+ if (scope === "personal") {
15
+ // Copilot doesn't have a global skills directory — install to project
16
+ return join(basePath(), `${basename(skillPath)}.md`);
17
+ }
18
+ return join(basePath(), `${basename(skillPath)}.md`);
19
+ }
20
+
21
+ export async function install(skillPath, skillContent, scope, _metadata = {}) {
22
+ const dest = getInstallPath(skillPath, scope);
23
+ await writeFile(dest, skillContent);
24
+ return dest;
25
+ }
26
+
27
+ export async function getInstalledSkills(scope) {
28
+ if (scope === "personal") return [];
29
+ const dir = basePath();
30
+ const files = await scanDir(dir, ".md");
31
+ const skills = [];
32
+ for (const file of files) {
33
+ const content = await readFile(join(dir, file));
34
+ if (!content) continue;
35
+ const { meta } = parseFrontmatter(content);
36
+ skills.push({
37
+ path: file.replace(/\.md$/, ""),
38
+ version: meta.version || "unknown",
39
+ });
40
+ }
41
+ return skills;
42
+ }
@@ -0,0 +1,54 @@
1
+ import { join, basename } from "node:path";
2
+ import { homeDir, cwdDir, writeFile, readFile } from "../utils/fs.js";
3
+ import { parseFrontmatter, scanDir } from "./shared.js";
4
+
5
+ export const toolName = "cursor";
6
+ export const displayName = "Cursor";
7
+
8
+ function basePath(scope) {
9
+ return scope === "personal"
10
+ ? join(homeDir(), ".cursor", "rules")
11
+ : join(cwdDir(), ".cursor", "rules");
12
+ }
13
+
14
+ export function getInstallPath(skillPath, scope) {
15
+ const skillName = basename(skillPath);
16
+ return join(basePath(scope), `${skillName}.mdc`);
17
+ }
18
+
19
+ function toCursorFormat(skillContent, metadata = {}) {
20
+ const { meta, body } = parseFrontmatter(skillContent);
21
+ const description = metadata.description || meta.description || meta.name || "Skill";
22
+ const alwaysApply = metadata.activationMode === "always";
23
+ const globs = metadata.cursorGlobs?.length
24
+ ? metadata.cursorGlobs.join(", ")
25
+ : "";
26
+
27
+ let header = `---\ndescription: ${description}\nalwaysApply: ${alwaysApply}`;
28
+ if (globs) header += `\nglobs: ${globs}`;
29
+ header += "\n---\n";
30
+ return header + body;
31
+ }
32
+
33
+ export async function install(skillPath, skillContent, scope, metadata = {}) {
34
+ const dest = getInstallPath(skillPath, scope);
35
+ const content = toCursorFormat(skillContent, metadata);
36
+ await writeFile(dest, content);
37
+ return dest;
38
+ }
39
+
40
+ export async function getInstalledSkills(scope) {
41
+ const dir = basePath(scope);
42
+ const files = await scanDir(dir, ".mdc");
43
+ const skills = [];
44
+ for (const file of files) {
45
+ const content = await readFile(join(dir, file));
46
+ if (!content) continue;
47
+ const { meta } = parseFrontmatter(content);
48
+ skills.push({
49
+ path: file.replace(/\.mdc$/, ""),
50
+ version: meta.version || "unknown",
51
+ });
52
+ }
53
+ return skills;
54
+ }
@@ -0,0 +1,26 @@
1
+ import { join } from "node:path";
2
+ import { homeDir, cwdDir, writeFile } from "../utils/fs.js";
3
+ import { scanInstalledSkills } from "./shared.js";
4
+
5
+ export const toolName = "gemini";
6
+ export const displayName = "Gemini CLI";
7
+
8
+ function basePath(scope) {
9
+ return scope === "personal"
10
+ ? join(homeDir(), ".gemini", "skills")
11
+ : join(cwdDir(), ".agents", "skills");
12
+ }
13
+
14
+ export function getInstallPath(skillPath, scope) {
15
+ return join(basePath(scope), skillPath, "SKILL.md");
16
+ }
17
+
18
+ export async function install(skillPath, skillContent, scope, _metadata = {}) {
19
+ const dest = getInstallPath(skillPath, scope);
20
+ await writeFile(dest, skillContent);
21
+ return dest;
22
+ }
23
+
24
+ export async function getInstalledSkills(scope) {
25
+ return scanInstalledSkills(basePath(scope));
26
+ }