reeeliance-skills 1.0.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/dist/index.js ADDED
@@ -0,0 +1,204 @@
1
+ #!/usr/bin/env node
2
+ import { checkbox } from "@inquirer/prompts";
3
+ import chalk from "chalk";
4
+ import { existsSync, mkdirSync, writeFileSync } from "fs";
5
+ import { join, resolve, dirname } from "path";
6
+ const GITLAB_PROJECT = "eee-main/eee-projects/skills";
7
+ const GITLAB_BRANCH = "main";
8
+ const GITLAB_RAW = `https://gitlab.com/${GITLAB_PROJECT}/-/raw/${GITLAB_BRANCH}`;
9
+ const GITLAB_API = `https://gitlab.com/api/v4/projects/${encodeURIComponent(GITLAB_PROJECT)}/repository`;
10
+ function getToken() {
11
+ const token = process.env.GITLAB_TOKEN;
12
+ if (!token) {
13
+ console.error(chalk.red("\nGITLAB_TOKEN environment variable is not set."));
14
+ console.error(chalk.dim("Create a token at: https://gitlab.com/-/user_settings/personal_access_tokens"));
15
+ console.error(chalk.dim("Required scope: read_api\n"));
16
+ console.error(chalk.dim("Then run:"));
17
+ console.error(chalk.dim(" export GITLAB_TOKEN=glpat-xxxxxxxxxxxx\n"));
18
+ process.exit(1);
19
+ }
20
+ return token;
21
+ }
22
+ // ── Source: local filesystem ──────────────────────────────────────────────────
23
+ import { readFileSync, cpSync } from "fs";
24
+ function loadLocalManifest(repoPath) {
25
+ const manifestPath = join(repoPath, "skills.json");
26
+ if (!existsSync(manifestPath)) {
27
+ console.error(chalk.red(`No skills.json found at ${manifestPath}`));
28
+ process.exit(1);
29
+ }
30
+ return JSON.parse(readFileSync(manifestPath, "utf8"));
31
+ }
32
+ function installLocalSkill(skill, repoPath, targetDir) {
33
+ const srcDir = join(repoPath, skill.path);
34
+ const destDir = join(targetDir, skill.name);
35
+ if (!existsSync(srcDir)) {
36
+ console.warn(chalk.yellow(` Warning: source not found: ${srcDir}`));
37
+ return;
38
+ }
39
+ mkdirSync(destDir, { recursive: true });
40
+ cpSync(srcDir, destDir, { recursive: true });
41
+ console.log(chalk.green(` ✓ ${skill.name}`) + chalk.dim(` → ${destDir}`));
42
+ }
43
+ // ── Source: GitLab (remote) ───────────────────────────────────────────────────
44
+ async function fetchText(url, token) {
45
+ const res = await fetch(url, { headers: { "PRIVATE-TOKEN": token } });
46
+ if (!res.ok)
47
+ throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`);
48
+ return res.text();
49
+ }
50
+ async function loadRemoteManifest(token) {
51
+ process.stdout.write(chalk.dim("Fetching skill list from GitLab…"));
52
+ const text = await fetchText(`${GITLAB_RAW}/skills.json`, token);
53
+ process.stdout.write(chalk.dim(" done\n"));
54
+ return JSON.parse(text);
55
+ }
56
+ async function listRemoteTree(skillPath, token) {
57
+ const url = `${GITLAB_API}/tree?path=${encodeURIComponent(skillPath)}&recursive=true&per_page=100`;
58
+ const text = await fetchText(url, token);
59
+ return JSON.parse(text);
60
+ }
61
+ async function installRemoteSkill(skill, targetDir, token) {
62
+ const destDir = join(targetDir, skill.name);
63
+ mkdirSync(destDir, { recursive: true });
64
+ const entries = await listRemoteTree(skill.path, token);
65
+ const files = entries.filter((e) => e.type === "blob");
66
+ for (const file of files) {
67
+ const rawUrl = `${GITLAB_RAW}/${file.path}`;
68
+ const content = await fetchText(rawUrl, token);
69
+ const relative = file.path.slice(skill.path.length + 1);
70
+ const destFile = join(destDir, relative);
71
+ mkdirSync(dirname(destFile), { recursive: true });
72
+ writeFileSync(destFile, content, "utf8");
73
+ }
74
+ console.log(chalk.green(` ✓ ${skill.name}`) + chalk.dim(` → ${destDir}`));
75
+ }
76
+ // ── Shared helpers ────────────────────────────────────────────────────────────
77
+ function groupByCategory(skills) {
78
+ return skills.reduce((acc, skill) => {
79
+ const cat = skill.category;
80
+ if (!acc[cat])
81
+ acc[cat] = [];
82
+ acc[cat].push(skill);
83
+ return acc;
84
+ }, {});
85
+ }
86
+ // ── Main ──────────────────────────────────────────────────────────────────────
87
+ async function main() {
88
+ const args = process.argv.slice(2);
89
+ const command = args[0] ?? "add";
90
+ // Local repo path: auto-detected (two levels above dist/) or --repo flag
91
+ const repoFlagIdx = args.indexOf("--repo");
92
+ const localRepoPath = repoFlagIdx !== -1
93
+ ? resolve(args[repoFlagIdx + 1])
94
+ : resolve(join(dirname(new URL(import.meta.url).pathname), "../.."));
95
+ // Use local source when running from inside the repo, remote otherwise
96
+ const isLocal = existsSync(join(localRepoPath, "skills.json"));
97
+ const token = isLocal ? "" : getToken();
98
+ // Target directory
99
+ const isGlobal = args.includes("--global");
100
+ const targetFlagIdx = args.indexOf("--target");
101
+ const targetDir = isGlobal
102
+ ? resolve(join(process.env.HOME ?? "~", ".claude", "skills"))
103
+ : targetFlagIdx !== -1
104
+ ? resolve(args[targetFlagIdx + 1])
105
+ : resolve(process.cwd());
106
+ // ── list ──
107
+ if (command === "list") {
108
+ const manifest = isLocal
109
+ ? loadLocalManifest(localRepoPath)
110
+ : await loadRemoteManifest(token);
111
+ console.log(chalk.bold(`\n${manifest.name} — ${manifest.description}\n`));
112
+ const groups = groupByCategory(manifest.skills);
113
+ for (const [cat, skills] of Object.entries(groups)) {
114
+ console.log(chalk.cyan.bold(` ${cat}`));
115
+ for (const s of skills) {
116
+ const badge = s.source ? chalk.dim(` [${s.source}]`) : "";
117
+ console.log(` ${chalk.bold(s.name)}${badge} — ${s.description}`);
118
+ }
119
+ }
120
+ console.log();
121
+ return;
122
+ }
123
+ // ── add ──
124
+ if (command === "add") {
125
+ const manifest = isLocal
126
+ ? loadLocalManifest(localRepoPath)
127
+ : await loadRemoteManifest(token);
128
+ const install = isLocal
129
+ ? (skill) => installLocalSkill(skill, localRepoPath, targetDir)
130
+ : (skill) => installRemoteSkill(skill, targetDir, token);
131
+ // --all
132
+ if (args.includes("--all")) {
133
+ console.log(chalk.bold(`\nInstalling all skills to ${targetDir}\n`));
134
+ for (const skill of manifest.skills)
135
+ await install(skill);
136
+ console.log(chalk.bold("\nDone. Restart your AI agent to load the new skills.\n"));
137
+ return;
138
+ }
139
+ // --skill <name>
140
+ const skillFlagIdx = args.indexOf("--skill");
141
+ if (skillFlagIdx !== -1) {
142
+ const skillName = args[skillFlagIdx + 1];
143
+ const skill = manifest.skills.find((s) => s.name === skillName);
144
+ if (!skill) {
145
+ console.error(chalk.red(`Skill not found: ${skillName}`));
146
+ console.log("Run `skills list` to see available skills.");
147
+ process.exit(1);
148
+ }
149
+ console.log(chalk.bold(`\nInstalling ${skillName} to ${targetDir}\n`));
150
+ await install(skill);
151
+ console.log(chalk.bold("\nDone. Restart your AI agent to load the new skills.\n"));
152
+ return;
153
+ }
154
+ // Interactive
155
+ console.log(chalk.bold("\nreeeliance Skills Installer\n"));
156
+ console.log(chalk.dim(`Source: ${isLocal ? localRepoPath : `GitLab (${GITLAB_PROJECT})`}`));
157
+ console.log(chalk.dim(`Target: ${targetDir}\n`));
158
+ const groups = groupByCategory(manifest.skills);
159
+ const choices = Object.values(groups).flat().map((s) => ({
160
+ name: `${s.name}${s.source ? ` [${s.source}]` : ""} — ${s.description}`,
161
+ value: s.name,
162
+ }));
163
+ const selected = await checkbox({
164
+ message: "Select skills to install (space to toggle, enter to confirm):",
165
+ choices,
166
+ pageSize: 20,
167
+ });
168
+ if (selected.length === 0) {
169
+ console.log(chalk.yellow("\nNo skills selected. Nothing installed.\n"));
170
+ return;
171
+ }
172
+ console.log(chalk.bold(`\nInstalling ${selected.length} skill(s) to ${targetDir}\n`));
173
+ for (const name of selected) {
174
+ const skill = manifest.skills.find((s) => s.name === name);
175
+ await install(skill);
176
+ }
177
+ console.log(chalk.bold("\nDone. Restart your AI agent to load the new skills.\n"));
178
+ return;
179
+ }
180
+ console.log(`
181
+ ${chalk.bold("reeeliance Skills CLI")}
182
+
183
+ ${chalk.dim("Usage:")}
184
+ npx reeeliance-skills list List all available skills
185
+ npx reeeliance-skills add Interactive skill selection
186
+ npx reeeliance-skills add --global Install to ~/.claude/skills (all projects)
187
+ npx reeeliance-skills add --all Install all skills
188
+ npx reeeliance-skills add --skill <name> Install a single skill
189
+
190
+ ${chalk.dim("Options:")}
191
+ --global Install to ~/.claude/skills so all Claude Code projects see the skills
192
+ --repo <path> Use a local skills repo instead of fetching from GitLab
193
+ --target <path> Custom destination directory
194
+
195
+ ${chalk.dim("Authentication (required for remote use):")}
196
+ export GITLAB_TOKEN=glpat-xxxxxxxxxxxx
197
+ Create a token at: https://gitlab.com/-/user_settings/personal_access_tokens
198
+ Required scope: read_api
199
+ `);
200
+ }
201
+ main().catch((err) => {
202
+ console.error(chalk.red(err.message));
203
+ process.exit(1);
204
+ });
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "reeeliance-skills",
3
+ "version": "1.0.0",
4
+ "description": "reeeliance GmbH AI skill installer — select and install skills from the library into any AI agent",
5
+ "type": "module",
6
+ "bin": {
7
+ "skills": "dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "tsx src/index.ts",
12
+ "start": "node dist/index.js"
13
+ },
14
+ "dependencies": {
15
+ "@inquirer/prompts": "^7.0.0",
16
+ "chalk": "^5.6.0"
17
+ },
18
+ "devDependencies": {
19
+ "@types/node": "^22.0.0",
20
+ "tsx": "^4.0.0",
21
+ "typescript": "^5.0.0"
22
+ }
23
+ }
package/src/index.ts ADDED
@@ -0,0 +1,251 @@
1
+ #!/usr/bin/env node
2
+ import { checkbox } from "@inquirer/prompts";
3
+ import chalk from "chalk";
4
+ import { existsSync, mkdirSync, writeFileSync } from "fs";
5
+ import { join, resolve, dirname, basename } from "path";
6
+
7
+ const GITLAB_PROJECT = "eee-main/eee-projects/skills";
8
+ const GITLAB_BRANCH = "main";
9
+ const GITLAB_RAW = `https://gitlab.com/${GITLAB_PROJECT}/-/raw/${GITLAB_BRANCH}`;
10
+ const GITLAB_API = `https://gitlab.com/api/v4/projects/${encodeURIComponent(GITLAB_PROJECT)}/repository`;
11
+
12
+ function getToken(): string {
13
+ const token = process.env.GITLAB_TOKEN;
14
+ if (!token) {
15
+ console.error(chalk.red("\nGITLAB_TOKEN environment variable is not set."));
16
+ console.error(chalk.dim("Create a token at: https://gitlab.com/-/user_settings/personal_access_tokens"));
17
+ console.error(chalk.dim("Required scope: read_api\n"));
18
+ console.error(chalk.dim("Then run:"));
19
+ console.error(chalk.dim(" export GITLAB_TOKEN=glpat-xxxxxxxxxxxx\n"));
20
+ process.exit(1);
21
+ }
22
+ return token;
23
+ }
24
+
25
+ interface Skill {
26
+ name: string;
27
+ path: string;
28
+ description: string;
29
+ category: string;
30
+ tags: string[];
31
+ parent?: string;
32
+ source?: string;
33
+ }
34
+
35
+ interface Manifest {
36
+ name: string;
37
+ description: string;
38
+ skills: Skill[];
39
+ }
40
+
41
+ // ── Source: local filesystem ──────────────────────────────────────────────────
42
+
43
+ import { readFileSync, cpSync } from "fs";
44
+
45
+ function loadLocalManifest(repoPath: string): Manifest {
46
+ const manifestPath = join(repoPath, "skills.json");
47
+ if (!existsSync(manifestPath)) {
48
+ console.error(chalk.red(`No skills.json found at ${manifestPath}`));
49
+ process.exit(1);
50
+ }
51
+ return JSON.parse(readFileSync(manifestPath, "utf8"));
52
+ }
53
+
54
+ function installLocalSkill(skill: Skill, repoPath: string, targetDir: string) {
55
+ const srcDir = join(repoPath, skill.path);
56
+ const destDir = join(targetDir, skill.name);
57
+ if (!existsSync(srcDir)) {
58
+ console.warn(chalk.yellow(` Warning: source not found: ${srcDir}`));
59
+ return;
60
+ }
61
+ mkdirSync(destDir, { recursive: true });
62
+ cpSync(srcDir, destDir, { recursive: true });
63
+ console.log(chalk.green(` ✓ ${skill.name}`) + chalk.dim(` → ${destDir}`));
64
+ }
65
+
66
+ // ── Source: GitLab (remote) ───────────────────────────────────────────────────
67
+
68
+ async function fetchText(url: string, token: string): Promise<string> {
69
+ const res = await fetch(url, { headers: { "PRIVATE-TOKEN": token } });
70
+ if (!res.ok) throw new Error(`Failed to fetch ${url}: ${res.status} ${res.statusText}`);
71
+ return res.text();
72
+ }
73
+
74
+ async function loadRemoteManifest(token: string): Promise<Manifest> {
75
+ process.stdout.write(chalk.dim("Fetching skill list from GitLab…"));
76
+ const text = await fetchText(`${GITLAB_RAW}/skills.json`, token);
77
+ process.stdout.write(chalk.dim(" done\n"));
78
+ return JSON.parse(text);
79
+ }
80
+
81
+ async function listRemoteTree(skillPath: string, token: string): Promise<{ path: string; type: string }[]> {
82
+ const url = `${GITLAB_API}/tree?path=${encodeURIComponent(skillPath)}&recursive=true&per_page=100`;
83
+ const text = await fetchText(url, token);
84
+ return JSON.parse(text);
85
+ }
86
+
87
+ async function installRemoteSkill(skill: Skill, targetDir: string, token: string) {
88
+ const destDir = join(targetDir, skill.name);
89
+ mkdirSync(destDir, { recursive: true });
90
+
91
+ const entries = await listRemoteTree(skill.path, token);
92
+ const files = entries.filter((e) => e.type === "blob");
93
+
94
+ for (const file of files) {
95
+ const rawUrl = `${GITLAB_RAW}/${file.path}`;
96
+ const content = await fetchText(rawUrl, token);
97
+ const relative = file.path.slice(skill.path.length + 1);
98
+ const destFile = join(destDir, relative);
99
+ mkdirSync(dirname(destFile), { recursive: true });
100
+ writeFileSync(destFile, content, "utf8");
101
+ }
102
+
103
+ console.log(chalk.green(` ✓ ${skill.name}`) + chalk.dim(` → ${destDir}`));
104
+ }
105
+
106
+ // ── Shared helpers ────────────────────────────────────────────────────────────
107
+
108
+ function groupByCategory(skills: Skill[]): Record<string, Skill[]> {
109
+ return skills.reduce((acc, skill) => {
110
+ const cat = skill.category;
111
+ if (!acc[cat]) acc[cat] = [];
112
+ acc[cat].push(skill);
113
+ return acc;
114
+ }, {} as Record<string, Skill[]>);
115
+ }
116
+
117
+ // ── Main ──────────────────────────────────────────────────────────────────────
118
+
119
+ async function main() {
120
+ const args = process.argv.slice(2);
121
+ const command = args[0] ?? "add";
122
+
123
+ // Local repo path: auto-detected (two levels above dist/) or --repo flag
124
+ const repoFlagIdx = args.indexOf("--repo");
125
+ const localRepoPath = repoFlagIdx !== -1
126
+ ? resolve(args[repoFlagIdx + 1])
127
+ : resolve(join(dirname(new URL(import.meta.url).pathname), "../.."));
128
+
129
+ // Use local source when running from inside the repo, remote otherwise
130
+ const isLocal = existsSync(join(localRepoPath, "skills.json"));
131
+ const token = isLocal ? "" : getToken();
132
+
133
+ // Target directory
134
+ const isGlobal = args.includes("--global");
135
+ const targetFlagIdx = args.indexOf("--target");
136
+ const targetDir = isGlobal
137
+ ? resolve(join(process.env.HOME ?? "~", ".claude", "skills"))
138
+ : targetFlagIdx !== -1
139
+ ? resolve(args[targetFlagIdx + 1])
140
+ : resolve(process.cwd());
141
+
142
+ // ── list ──
143
+ if (command === "list") {
144
+ const manifest = isLocal
145
+ ? loadLocalManifest(localRepoPath)
146
+ : await loadRemoteManifest(token);
147
+
148
+ console.log(chalk.bold(`\n${manifest.name} — ${manifest.description}\n`));
149
+ const groups = groupByCategory(manifest.skills);
150
+ for (const [cat, skills] of Object.entries(groups)) {
151
+ console.log(chalk.cyan.bold(` ${cat}`));
152
+ for (const s of skills) {
153
+ const badge = s.source ? chalk.dim(` [${s.source}]`) : "";
154
+ console.log(` ${chalk.bold(s.name)}${badge} — ${s.description}`);
155
+ }
156
+ }
157
+ console.log();
158
+ return;
159
+ }
160
+
161
+ // ── add ──
162
+ if (command === "add") {
163
+ const manifest = isLocal
164
+ ? loadLocalManifest(localRepoPath)
165
+ : await loadRemoteManifest(token);
166
+
167
+ const install = isLocal
168
+ ? (skill: Skill) => installLocalSkill(skill, localRepoPath, targetDir)
169
+ : (skill: Skill) => installRemoteSkill(skill, targetDir, token);
170
+
171
+ // --all
172
+ if (args.includes("--all")) {
173
+ console.log(chalk.bold(`\nInstalling all skills to ${targetDir}\n`));
174
+ for (const skill of manifest.skills) await install(skill);
175
+ console.log(chalk.bold("\nDone. Restart your AI agent to load the new skills.\n"));
176
+ return;
177
+ }
178
+
179
+ // --skill <name>
180
+ const skillFlagIdx = args.indexOf("--skill");
181
+ if (skillFlagIdx !== -1) {
182
+ const skillName = args[skillFlagIdx + 1];
183
+ const skill = manifest.skills.find((s) => s.name === skillName);
184
+ if (!skill) {
185
+ console.error(chalk.red(`Skill not found: ${skillName}`));
186
+ console.log("Run `skills list` to see available skills.");
187
+ process.exit(1);
188
+ }
189
+ console.log(chalk.bold(`\nInstalling ${skillName} to ${targetDir}\n`));
190
+ await install(skill);
191
+ console.log(chalk.bold("\nDone. Restart your AI agent to load the new skills.\n"));
192
+ return;
193
+ }
194
+
195
+ // Interactive
196
+ console.log(chalk.bold("\nreeeliance Skills Installer\n"));
197
+ console.log(chalk.dim(`Source: ${isLocal ? localRepoPath : `GitLab (${GITLAB_PROJECT})`}`));
198
+ console.log(chalk.dim(`Target: ${targetDir}\n`));
199
+
200
+ const groups = groupByCategory(manifest.skills);
201
+ const choices = Object.values(groups).flat().map((s) => ({
202
+ name: `${s.name}${s.source ? ` [${s.source}]` : ""} — ${s.description}`,
203
+ value: s.name,
204
+ }));
205
+
206
+ const selected = await checkbox({
207
+ message: "Select skills to install (space to toggle, enter to confirm):",
208
+ choices,
209
+ pageSize: 20,
210
+ });
211
+
212
+ if (selected.length === 0) {
213
+ console.log(chalk.yellow("\nNo skills selected. Nothing installed.\n"));
214
+ return;
215
+ }
216
+
217
+ console.log(chalk.bold(`\nInstalling ${selected.length} skill(s) to ${targetDir}\n`));
218
+ for (const name of selected) {
219
+ const skill = manifest.skills.find((s) => s.name === name)!;
220
+ await install(skill);
221
+ }
222
+ console.log(chalk.bold("\nDone. Restart your AI agent to load the new skills.\n"));
223
+ return;
224
+ }
225
+
226
+ console.log(`
227
+ ${chalk.bold("reeeliance Skills CLI")}
228
+
229
+ ${chalk.dim("Usage:")}
230
+ npx reeeliance-skills list List all available skills
231
+ npx reeeliance-skills add Interactive skill selection
232
+ npx reeeliance-skills add --global Install to ~/.claude/skills (all projects)
233
+ npx reeeliance-skills add --all Install all skills
234
+ npx reeeliance-skills add --skill <name> Install a single skill
235
+
236
+ ${chalk.dim("Options:")}
237
+ --global Install to ~/.claude/skills so all Claude Code projects see the skills
238
+ --repo <path> Use a local skills repo instead of fetching from GitLab
239
+ --target <path> Custom destination directory
240
+
241
+ ${chalk.dim("Authentication (required for remote use):")}
242
+ export GITLAB_TOKEN=glpat-xxxxxxxxxxxx
243
+ Create a token at: https://gitlab.com/-/user_settings/personal_access_tokens
244
+ Required scope: read_api
245
+ `);
246
+ }
247
+
248
+ main().catch((err) => {
249
+ console.error(chalk.red(err.message));
250
+ process.exit(1);
251
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "outDir": "dist",
7
+ "rootDir": "src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true
11
+ },
12
+ "include": ["src"]
13
+ }