skills-ws 1.0.1 → 1.0.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.
- package/bin/cli.mjs +207 -28
- package/package.json +1 -1
package/bin/cli.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { readdir, readFile, copyFile, mkdir, stat } from "node:fs/promises";
|
|
|
4
4
|
import { join, resolve, dirname } from "node:path";
|
|
5
5
|
import { createInterface } from "node:readline";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
+
import readline from "node:readline";
|
|
7
8
|
|
|
8
9
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
10
|
const SKILLS_DIR = join(__dirname, "..", "skills");
|
|
@@ -15,11 +16,155 @@ const GREEN = "\x1b[32m";
|
|
|
15
16
|
const CYAN = "\x1b[36m";
|
|
16
17
|
const YELLOW = "\x1b[33m";
|
|
17
18
|
const WHITE = "\x1b[37m";
|
|
19
|
+
const GRAY = "\x1b[90m";
|
|
20
|
+
const MAGENTA = "\x1b[35m";
|
|
18
21
|
|
|
19
22
|
function print(msg = "") {
|
|
20
23
|
process.stdout.write(msg + "\n");
|
|
21
24
|
}
|
|
22
25
|
|
|
26
|
+
// ── ASCII Animation ──────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
const BANNER_FRAMES = [
|
|
29
|
+
// Frame 1: empty
|
|
30
|
+
``,
|
|
31
|
+
// Frame 2: dots forming
|
|
32
|
+
`
|
|
33
|
+
. . .
|
|
34
|
+
`,
|
|
35
|
+
// Frame 3: lines emerging
|
|
36
|
+
`
|
|
37
|
+
_______________
|
|
38
|
+
| |
|
|
39
|
+
|_______________|
|
|
40
|
+
`,
|
|
41
|
+
// Frame 4: box growing
|
|
42
|
+
`
|
|
43
|
+
_____________________________
|
|
44
|
+
| |
|
|
45
|
+
| |
|
|
46
|
+
| |
|
|
47
|
+
|_____________________________|
|
|
48
|
+
`,
|
|
49
|
+
// Frame 5: text appearing
|
|
50
|
+
`
|
|
51
|
+
_____________________________
|
|
52
|
+
| |
|
|
53
|
+
| s k i l l s |
|
|
54
|
+
| |
|
|
55
|
+
|_____________________________|
|
|
56
|
+
`,
|
|
57
|
+
// Frame 6: full banner
|
|
58
|
+
`
|
|
59
|
+
_____________________________
|
|
60
|
+
| |
|
|
61
|
+
| s k i l l s - w s |
|
|
62
|
+
| |
|
|
63
|
+
|_____________________________|
|
|
64
|
+
`,
|
|
65
|
+
// Frame 7: with underline detail
|
|
66
|
+
`
|
|
67
|
+
_____________________________
|
|
68
|
+
| |
|
|
69
|
+
| s k i l l s - w s |
|
|
70
|
+
| ___________________ |
|
|
71
|
+
|_____________________________|
|
|
72
|
+
`,
|
|
73
|
+
// Frame 8: with tagline
|
|
74
|
+
`
|
|
75
|
+
_____________________________
|
|
76
|
+
| |
|
|
77
|
+
| s k i l l s - w s |
|
|
78
|
+
| ___________________ |
|
|
79
|
+
| agent skills for AI |
|
|
80
|
+
|_____________________________|
|
|
81
|
+
`,
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
const INSTALL_FRAMES = [
|
|
85
|
+
` [ ] preparing`,
|
|
86
|
+
` [= ] loading skills`,
|
|
87
|
+
` [== ] loading skills`,
|
|
88
|
+
` [=== ] reading manifests`,
|
|
89
|
+
` [==== ] reading manifests`,
|
|
90
|
+
` [===== ] copying files`,
|
|
91
|
+
` [====== ] copying files`,
|
|
92
|
+
` [======= ] copying files`,
|
|
93
|
+
` [======== ] writing to disk`,
|
|
94
|
+
` [========= ] writing to disk`,
|
|
95
|
+
` [==========] done`,
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
function hideCursor() {
|
|
99
|
+
process.stdout.write("\x1b[?25l");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function showCursor() {
|
|
103
|
+
process.stdout.write("\x1b[?25h");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function sleep(ms) {
|
|
107
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function playBanner() {
|
|
111
|
+
// Skip animation if not a TTY or NO_COLOR is set
|
|
112
|
+
if (!process.stdout.isTTY || process.env.NO_COLOR) {
|
|
113
|
+
print(`\n${BOLD}${WHITE} skills-ws${RESET} ${DIM}v1.0.1${RESET}\n`);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
hideCursor();
|
|
118
|
+
|
|
119
|
+
for (const frame of BANNER_FRAMES) {
|
|
120
|
+
readline.cursorTo(process.stdout, 0, 0);
|
|
121
|
+
readline.clearScreenDown(process.stdout);
|
|
122
|
+
process.stdout.write(`${CYAN}${frame}${RESET}\n`);
|
|
123
|
+
await sleep(90);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Hold final frame, then fade to colored version
|
|
127
|
+
await sleep(200);
|
|
128
|
+
readline.cursorTo(process.stdout, 0, 0);
|
|
129
|
+
readline.clearScreenDown(process.stdout);
|
|
130
|
+
|
|
131
|
+
print();
|
|
132
|
+
print(`${GRAY} _____________________________${RESET}`);
|
|
133
|
+
print(`${GRAY} | |${RESET}`);
|
|
134
|
+
print(`${GRAY} | ${WHITE}${BOLD}s k i l l s - w s${RESET}${GRAY} |${RESET}`);
|
|
135
|
+
print(`${GRAY} | ${DIM}___________________${RESET}${GRAY} |${RESET}`);
|
|
136
|
+
print(`${GRAY} | ${DIM}agent skills for AI${RESET}${GRAY} |${RESET}`);
|
|
137
|
+
print(`${GRAY} |_____________________________|${RESET}`);
|
|
138
|
+
print();
|
|
139
|
+
|
|
140
|
+
await sleep(300);
|
|
141
|
+
showCursor();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function playInstallProgress(skillCount) {
|
|
145
|
+
if (!process.stdout.isTTY || process.env.NO_COLOR) return;
|
|
146
|
+
|
|
147
|
+
hideCursor();
|
|
148
|
+
const startLine = process.stdout.rows || 20;
|
|
149
|
+
|
|
150
|
+
for (const frame of INSTALL_FRAMES) {
|
|
151
|
+
readline.cursorTo(process.stdout, 0);
|
|
152
|
+
readline.clearLine(process.stdout, 0);
|
|
153
|
+
process.stdout.write(`${GREEN}${frame}${RESET}`);
|
|
154
|
+
await sleep(80);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
readline.cursorTo(process.stdout, 0);
|
|
158
|
+
readline.clearLine(process.stdout, 0);
|
|
159
|
+
process.stdout.write(
|
|
160
|
+
`${GREEN} [==========]${RESET} ${BOLD}${skillCount} skill${skillCount !== 1 ? "s" : ""} installed${RESET}\n`
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
showCursor();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ── Skill Operations ─────────────────────────────────────────
|
|
167
|
+
|
|
23
168
|
async function getSkills() {
|
|
24
169
|
const entries = await readdir(SKILLS_DIR, { withFileTypes: true });
|
|
25
170
|
const skills = [];
|
|
@@ -30,8 +175,8 @@ async function getSkills() {
|
|
|
30
175
|
try {
|
|
31
176
|
await stat(skillMd);
|
|
32
177
|
const content = await readFile(skillMd, "utf8");
|
|
33
|
-
const descMatch = content.match(/^description:\s*(
|
|
34
|
-
const desc = descMatch ? descMatch[1]
|
|
178
|
+
const descMatch = content.match(/^description:\s*["']?(.+?)["']?\s*$/m);
|
|
179
|
+
const desc = descMatch ? descMatch[1] : "";
|
|
35
180
|
skills.push({ name: e.name, desc, dir: skillDir });
|
|
36
181
|
} catch {
|
|
37
182
|
// skip dirs without SKILL.md
|
|
@@ -55,7 +200,6 @@ async function copyDir(src, dest) {
|
|
|
55
200
|
}
|
|
56
201
|
|
|
57
202
|
async function detectTarget() {
|
|
58
|
-
// Check for common skill directories
|
|
59
203
|
const candidates = [
|
|
60
204
|
join(process.cwd(), ".claude", "skills"),
|
|
61
205
|
join(process.cwd(), "skills"),
|
|
@@ -71,46 +215,57 @@ async function detectTarget() {
|
|
|
71
215
|
return join(process.cwd(), "skills");
|
|
72
216
|
}
|
|
73
217
|
|
|
218
|
+
// ── Main ─────────────────────────────────────────────────────
|
|
219
|
+
|
|
74
220
|
async function main() {
|
|
75
221
|
const args = process.argv.slice(2);
|
|
76
222
|
const skills = await getSkills();
|
|
77
223
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
print(`${DIM}https://skills-ws.vercel.app${RESET}`);
|
|
81
|
-
print();
|
|
224
|
+
// Always play banner
|
|
225
|
+
await playBanner();
|
|
82
226
|
|
|
83
227
|
if (args[0] === "list" || args[0] === "ls") {
|
|
84
|
-
print(`${BOLD}Available skills:${RESET}`);
|
|
228
|
+
print(`${BOLD} Available skills:${RESET}`);
|
|
85
229
|
print();
|
|
86
230
|
for (const s of skills) {
|
|
87
|
-
print(`
|
|
231
|
+
print(` ${GREEN}${s.name}${RESET} ${DIM}${s.desc.slice(0, 70)}${RESET}`);
|
|
88
232
|
}
|
|
89
233
|
print();
|
|
90
|
-
print(
|
|
234
|
+
print(` ${DIM}${skills.length} skills available. Run: npx skills-ws install <name>${RESET}`);
|
|
235
|
+
print();
|
|
91
236
|
return;
|
|
92
237
|
}
|
|
93
238
|
|
|
94
239
|
if (args[0] === "install" || args[0] === "add") {
|
|
95
240
|
const names = args.slice(1);
|
|
241
|
+
|
|
96
242
|
if (names.length === 0) {
|
|
97
|
-
|
|
98
|
-
print(`${BOLD}Available skills:${RESET}`);
|
|
243
|
+
print(`${BOLD} Available skills:${RESET}`);
|
|
99
244
|
print();
|
|
100
245
|
for (let i = 0; i < skills.length; i++) {
|
|
101
|
-
print(
|
|
246
|
+
print(
|
|
247
|
+
` ${DIM}${String(i + 1).padStart(2)}.${RESET} ${GREEN}${skills[i].name}${RESET} ${DIM}${skills[i].desc.slice(0, 55)}${RESET}`
|
|
248
|
+
);
|
|
102
249
|
}
|
|
103
250
|
print();
|
|
104
|
-
print(
|
|
251
|
+
print(` ${YELLOW}Enter skill numbers or names (comma-separated), or 'all':${RESET}`);
|
|
105
252
|
|
|
106
|
-
const rl = createInterface({
|
|
107
|
-
|
|
253
|
+
const rl = createInterface({
|
|
254
|
+
input: process.stdin,
|
|
255
|
+
output: process.stdout,
|
|
256
|
+
});
|
|
257
|
+
const answer = await new Promise((resolve) =>
|
|
258
|
+
rl.question(` ${CYAN}> ${RESET}`, resolve)
|
|
259
|
+
);
|
|
108
260
|
rl.close();
|
|
109
261
|
|
|
110
262
|
if (answer.trim().toLowerCase() === "all") {
|
|
111
263
|
names.push(...skills.map((s) => s.name));
|
|
112
264
|
} else {
|
|
113
|
-
for (const part of answer
|
|
265
|
+
for (const part of answer
|
|
266
|
+
.split(",")
|
|
267
|
+
.map((s) => s.trim())
|
|
268
|
+
.filter(Boolean)) {
|
|
114
269
|
const num = parseInt(part);
|
|
115
270
|
if (!isNaN(num) && num >= 1 && num <= skills.length) {
|
|
116
271
|
names.push(skills[num - 1].name);
|
|
@@ -122,45 +277,69 @@ async function main() {
|
|
|
122
277
|
}
|
|
123
278
|
|
|
124
279
|
if (names.length === 0) {
|
|
125
|
-
print(
|
|
280
|
+
print(` ${DIM}Nothing selected.${RESET}`);
|
|
126
281
|
return;
|
|
127
282
|
}
|
|
128
283
|
|
|
129
284
|
const target = await detectTarget();
|
|
130
285
|
print();
|
|
131
|
-
print(
|
|
286
|
+
print(` ${DIM}target: ${target}${RESET}`);
|
|
132
287
|
print();
|
|
133
288
|
|
|
134
289
|
let installed = 0;
|
|
135
290
|
for (const name of names) {
|
|
136
291
|
const skill = skills.find((s) => s.name === name);
|
|
137
292
|
if (!skill) {
|
|
138
|
-
print(`
|
|
293
|
+
print(` ${YELLOW}skip${RESET} ${name} ${DIM}(not found)${RESET}`);
|
|
139
294
|
continue;
|
|
140
295
|
}
|
|
141
296
|
const dest = join(target, name);
|
|
142
297
|
await copyDir(skill.dir, dest);
|
|
143
|
-
print(` ${GREEN}done${RESET} ${name}`);
|
|
144
298
|
installed++;
|
|
145
299
|
}
|
|
146
300
|
|
|
301
|
+
// Play install animation
|
|
302
|
+
await playInstallProgress(installed);
|
|
303
|
+
print();
|
|
304
|
+
|
|
305
|
+
// Summary
|
|
306
|
+
for (const name of names) {
|
|
307
|
+
const skill = skills.find((s) => s.name === name);
|
|
308
|
+
if (skill) {
|
|
309
|
+
print(` ${GREEN}+${RESET} ${name}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
print();
|
|
314
|
+
print(` ${DIM}https://skills-ws.vercel.app${RESET}`);
|
|
147
315
|
print();
|
|
148
|
-
print(`${BOLD}${installed} skill${installed !== 1 ? "s" : ""} installed.${RESET}`);
|
|
149
316
|
return;
|
|
150
317
|
}
|
|
151
318
|
|
|
152
319
|
// Default: show help
|
|
153
|
-
print(`${BOLD}Usage:${RESET}`);
|
|
320
|
+
print(`${BOLD} Usage:${RESET}`);
|
|
154
321
|
print();
|
|
155
|
-
print(`
|
|
156
|
-
print(`
|
|
157
|
-
print(`
|
|
158
|
-
print(`
|
|
322
|
+
print(` ${CYAN}npx skills-ws list${RESET} List available skills`);
|
|
323
|
+
print(` ${CYAN}npx skills-ws install${RESET} Interactive install`);
|
|
324
|
+
print(` ${CYAN}npx skills-ws install <name>${RESET} Install specific skill(s)`);
|
|
325
|
+
print(` ${CYAN}npx skills-ws install all${RESET} Install everything`);
|
|
326
|
+
print();
|
|
327
|
+
print(` ${DIM}${skills.length} skills | https://skills-ws.vercel.app${RESET}`);
|
|
159
328
|
print();
|
|
160
|
-
print(`${DIM}${skills.length} skills available. Browse: https://skills-ws.vercel.app${RESET}`);
|
|
161
329
|
}
|
|
162
330
|
|
|
331
|
+
// Clean up cursor on exit
|
|
332
|
+
process.on("SIGINT", () => {
|
|
333
|
+
showCursor();
|
|
334
|
+
process.exit(0);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
process.on("exit", () => {
|
|
338
|
+
showCursor();
|
|
339
|
+
});
|
|
340
|
+
|
|
163
341
|
main().catch((err) => {
|
|
342
|
+
showCursor();
|
|
164
343
|
console.error(err);
|
|
165
344
|
process.exit(1);
|
|
166
345
|
});
|