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 +204 -0
- package/package.json +23 -0
- package/src/index.ts +251 -0
- package/tsconfig.json +13 -0
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