mkt-skills 1.0.0 → 1.1.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/mkt-skills.mjs +28 -13
- package/lib/api.mjs +22 -3
- package/lib/commands.mjs +159 -5
- package/package.json +1 -1
package/bin/mkt-skills.mjs
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { install, list, search, info, uninstall } from '../lib/commands.mjs';
|
|
3
|
+
import { install, list, search, info, uninstall, sets, installSet, setInfo } from '../lib/commands.mjs';
|
|
4
4
|
|
|
5
|
-
const [,,
|
|
5
|
+
const [,, rawCommand, ...args] = process.argv;
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
// Normalize hyphenated commands: "install-set" -> lookup key
|
|
8
|
+
const commandMap = {
|
|
9
|
+
install, list, search, info, uninstall, sets,
|
|
10
|
+
'install-set': installSet,
|
|
11
|
+
'set-info': setInfo,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const command = rawCommand;
|
|
8
15
|
|
|
9
16
|
if (!command || command === '--help' || command === '-h') {
|
|
10
17
|
showHelp();
|
|
@@ -12,17 +19,17 @@ if (!command || command === '--help' || command === '-h') {
|
|
|
12
19
|
}
|
|
13
20
|
|
|
14
21
|
if (command === '--version' || command === '-v') {
|
|
15
|
-
console.log('mkt-skills 1.
|
|
22
|
+
console.log('mkt-skills 1.1.0');
|
|
16
23
|
process.exit(0);
|
|
17
24
|
}
|
|
18
25
|
|
|
19
|
-
if (!
|
|
26
|
+
if (!commandMap[command]) {
|
|
20
27
|
console.error(`Unknown command: ${command}\nRun "mkt-skills --help" for usage.`);
|
|
21
28
|
process.exit(1);
|
|
22
29
|
}
|
|
23
30
|
|
|
24
31
|
try {
|
|
25
|
-
await
|
|
32
|
+
await commandMap[command](args);
|
|
26
33
|
} catch (err) {
|
|
27
34
|
if (err.name === 'AbortError') {
|
|
28
35
|
console.error('Request timed out. Check your internet connection.');
|
|
@@ -42,21 +49,29 @@ Usage:
|
|
|
42
49
|
npx mkt-skills <command> [args]
|
|
43
50
|
|
|
44
51
|
Commands:
|
|
45
|
-
install <slug>
|
|
46
|
-
list
|
|
47
|
-
search <keyword>
|
|
48
|
-
info <slug>
|
|
49
|
-
uninstall <slug>
|
|
52
|
+
install <slug> Install a skill to ~/.claude/skills/
|
|
53
|
+
list List all available skills
|
|
54
|
+
search <keyword> Search skills by keyword
|
|
55
|
+
info <slug> Show details about a skill
|
|
56
|
+
uninstall <slug> Remove an installed skill
|
|
57
|
+
sets List available skill sets
|
|
58
|
+
install-set <slug> Install all skills in a set
|
|
59
|
+
set-info <slug> Show details about a skill set
|
|
50
60
|
|
|
51
61
|
Options:
|
|
52
|
-
--help, -h
|
|
53
|
-
--version, -v
|
|
62
|
+
--help, -h Show this help
|
|
63
|
+
--version, -v Show version
|
|
64
|
+
--force, -f Overwrite existing skills
|
|
65
|
+
--code CODE Access code to unlock premium skills
|
|
54
66
|
|
|
55
67
|
Examples:
|
|
56
68
|
npx mkt-skills install seo-content
|
|
57
69
|
npx mkt-skills search "email automation"
|
|
58
70
|
npx mkt-skills list --category=seo
|
|
59
71
|
npx mkt-skills info marketing-audit
|
|
72
|
+
npx mkt-skills install-set thrivemattic-marketing-suite
|
|
73
|
+
npx mkt-skills install premium-skill --code LAUNCH2026
|
|
74
|
+
npx mkt-skills install-set my-set --code LAUNCH2026
|
|
60
75
|
|
|
61
76
|
https://marketingskills.directory
|
|
62
77
|
`);
|
package/lib/api.mjs
CHANGED
|
@@ -8,7 +8,7 @@ async function request(path) {
|
|
|
8
8
|
try {
|
|
9
9
|
const res = await fetch(`${BASE_URL}${path}`, {
|
|
10
10
|
signal: controller.signal,
|
|
11
|
-
headers: { 'User-Agent': 'mkt-skills-cli/1.
|
|
11
|
+
headers: { 'User-Agent': 'mkt-skills-cli/1.1.0' },
|
|
12
12
|
});
|
|
13
13
|
return res;
|
|
14
14
|
} finally {
|
|
@@ -22,8 +22,27 @@ export async function fetchCatalog() {
|
|
|
22
22
|
return res.json();
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
export async function
|
|
26
|
-
const res = await request(
|
|
25
|
+
export async function fetchSets() {
|
|
26
|
+
const res = await request('/sets');
|
|
27
|
+
if (!res.ok) throw new Error(`Failed to fetch sets (HTTP ${res.status})`);
|
|
28
|
+
return res.json();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function fetchSet(slug) {
|
|
32
|
+
const res = await request(`/sets/${encodeURIComponent(slug)}`);
|
|
33
|
+
|
|
34
|
+
if (res.status === 404) {
|
|
35
|
+
const data = await res.json();
|
|
36
|
+
throw Object.assign(new Error(data.message), { code: 'NOT_FOUND' });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!res.ok) throw new Error(`Failed to fetch set (HTTP ${res.status})`);
|
|
40
|
+
return res.json();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function fetchInstall(slug, code) {
|
|
44
|
+
const path = `/install/${encodeURIComponent(slug)}` + (code ? `?code=${encodeURIComponent(code)}` : '');
|
|
45
|
+
const res = await request(path);
|
|
27
46
|
|
|
28
47
|
if (res.status === 403) {
|
|
29
48
|
const data = await res.json();
|
package/lib/commands.mjs
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { mkdir, rm, access, writeFile } from 'fs/promises';
|
|
2
2
|
import { createInterface } from 'readline';
|
|
3
|
-
import { fetchCatalog, fetchInstall } from './api.mjs';
|
|
3
|
+
import { fetchCatalog, fetchInstall, fetchSets, fetchSet } from './api.mjs';
|
|
4
4
|
import { skillDir, skillFile, installedSlugs } from './paths.mjs';
|
|
5
5
|
import { bold, dim, green, red, yellow, cyan } from './ui.mjs';
|
|
6
6
|
|
|
7
7
|
// ── install ─────────────────────────────────────────────────
|
|
8
8
|
|
|
9
9
|
export async function install(args) {
|
|
10
|
-
const force
|
|
11
|
-
const
|
|
10
|
+
const force = args.includes('--force') || args.includes('-f');
|
|
11
|
+
const codeFlag = args.find(a => a.startsWith('--code=')) || args.find((a, i, arr) => a === '--code' && arr[i + 1]);
|
|
12
|
+
const code = codeFlag?.startsWith('--code=') ? codeFlag.split('=')[1] : (args.includes('--code') ? args[args.indexOf('--code') + 1] : null);
|
|
13
|
+
const slug = args.find(a => !a.startsWith('-') && a !== code);
|
|
12
14
|
|
|
13
15
|
if (!slug) {
|
|
14
|
-
console.error(`Usage: mkt-skills install <slug> [--force]\n`);
|
|
16
|
+
console.error(`Usage: mkt-skills install <slug> [--force] [--code CODE]\n`);
|
|
15
17
|
process.exit(1);
|
|
16
18
|
}
|
|
17
19
|
|
|
@@ -30,10 +32,13 @@ export async function install(args) {
|
|
|
30
32
|
|
|
31
33
|
let data;
|
|
32
34
|
try {
|
|
33
|
-
data = await fetchInstall(slug);
|
|
35
|
+
data = await fetchInstall(slug, code);
|
|
34
36
|
} catch (err) {
|
|
35
37
|
if (err.code === 'PREMIUM') {
|
|
36
38
|
console.log(`\n${red('x')} ${bold(`"${slug}"`)} is a premium skill.`);
|
|
39
|
+
if (code) {
|
|
40
|
+
console.log(` ${dim('->')} Access code was invalid or not applicable.`);
|
|
41
|
+
}
|
|
37
42
|
console.log(` ${dim('->')} Purchase at: ${cyan(err.url)}\n`);
|
|
38
43
|
process.exit(1);
|
|
39
44
|
}
|
|
@@ -211,6 +216,155 @@ export async function uninstall(args) {
|
|
|
211
216
|
console.log(` ${dim('->')} Removed ${dir}\n`);
|
|
212
217
|
}
|
|
213
218
|
|
|
219
|
+
// ── sets ───────────────────────────────────────────────────
|
|
220
|
+
|
|
221
|
+
export async function sets(args) {
|
|
222
|
+
const allSets = await fetchSets();
|
|
223
|
+
|
|
224
|
+
if (allSets.length === 0) {
|
|
225
|
+
console.log('\nNo skill sets available yet.\n');
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
console.log(`\n${bold('Skill Sets')}\n`);
|
|
230
|
+
|
|
231
|
+
const slugW = Math.max(4, ...allSets.map(s => s.slug.length));
|
|
232
|
+
const nameW = Math.max(4, ...allSets.map(s => s.name.length));
|
|
233
|
+
|
|
234
|
+
const header = [
|
|
235
|
+
'SLUG'.padEnd(slugW),
|
|
236
|
+
'NAME'.padEnd(nameW),
|
|
237
|
+
'SKILLS',
|
|
238
|
+
].join(' ');
|
|
239
|
+
|
|
240
|
+
console.log(dim(header));
|
|
241
|
+
console.log(dim('-'.repeat(header.length)));
|
|
242
|
+
|
|
243
|
+
for (const s of allSets) {
|
|
244
|
+
console.log([
|
|
245
|
+
s.slug.padEnd(slugW),
|
|
246
|
+
s.name.padEnd(nameW),
|
|
247
|
+
`${s.count} skills`,
|
|
248
|
+
].join(' '));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
console.log(`\n${dim('Install a set:')} npx mkt-skills install-set <slug>\n`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ── install-set ────────────────────────────────────────────
|
|
255
|
+
|
|
256
|
+
export async function installSet(args) {
|
|
257
|
+
const force = args.includes('--force') || args.includes('-f');
|
|
258
|
+
const codeFlag = args.find(a => a.startsWith('--code=')) || args.find((a, i, arr) => a === '--code' && arr[i + 1]);
|
|
259
|
+
const code = codeFlag?.startsWith('--code=') ? codeFlag.split('=')[1] : (args.includes('--code') ? args[args.indexOf('--code') + 1] : null);
|
|
260
|
+
const slug = args.find(a => !a.startsWith('-') && a !== code);
|
|
261
|
+
|
|
262
|
+
if (!slug) {
|
|
263
|
+
console.error('Usage: mkt-skills install-set <slug> [--force] [--code CODE]\n');
|
|
264
|
+
process.exit(1);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
let set;
|
|
268
|
+
try {
|
|
269
|
+
set = await fetchSet(slug);
|
|
270
|
+
} catch (err) {
|
|
271
|
+
if (err.code === 'NOT_FOUND') {
|
|
272
|
+
console.log(`\n${red('x')} Skill set "${slug}" not found.`);
|
|
273
|
+
console.log(` ${dim('->')} Browse sets: ${cyan('npx mkt-skills sets')}\n`);
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
throw err;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
console.log(`\n${bold(`Installing "${set.name}"`)} (${set.count} skills)\n`);
|
|
280
|
+
|
|
281
|
+
const installed = new Set(await installedSlugs());
|
|
282
|
+
let ok = 0, skipped = 0, premium = 0;
|
|
283
|
+
|
|
284
|
+
for (const skill of set.skills) {
|
|
285
|
+
const skillSlug = skill.slug;
|
|
286
|
+
|
|
287
|
+
if (skill.price !== 'free' && !code) {
|
|
288
|
+
console.log(` ${dim('-')} ${skillSlug} — ${yellow('premium, skipped')}`);
|
|
289
|
+
premium++;
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (!force && installed.has(skillSlug)) {
|
|
294
|
+
console.log(` ${dim('-')} ${skillSlug} ${dim('[already installed]')}`);
|
|
295
|
+
skipped++;
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
try {
|
|
300
|
+
const data = await fetchInstall(skillSlug, code);
|
|
301
|
+
await mkdir(skillDir(skillSlug), { recursive: true });
|
|
302
|
+
await writeFile(skillFile(skillSlug), data.content, 'utf-8');
|
|
303
|
+
console.log(` ${green('+')} ${skillSlug} (${data.lines} lines)`);
|
|
304
|
+
ok++;
|
|
305
|
+
} catch (err) {
|
|
306
|
+
if (err.code === 'PREMIUM') {
|
|
307
|
+
console.log(` ${dim('-')} ${skillSlug} — ${yellow('premium, skipped')}`);
|
|
308
|
+
premium++;
|
|
309
|
+
} else {
|
|
310
|
+
console.log(` ${red('x')} ${skillSlug} — ${err.message}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const total = set.skills.length;
|
|
316
|
+
const parts = [`${ok} installed`];
|
|
317
|
+
if (skipped) parts.push(`${skipped} already installed`);
|
|
318
|
+
if (premium) parts.push(`${premium} premium skipped`);
|
|
319
|
+
|
|
320
|
+
console.log(`\n${bold(`Done.`)} ${parts.join(', ')} (${total} total)`);
|
|
321
|
+
console.log(` ${dim('->')} ~/.claude/skills/\n`);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ── set-info ───────────────────────────────────────────────
|
|
325
|
+
|
|
326
|
+
export async function setInfo(args) {
|
|
327
|
+
const slug = args.find(a => !a.startsWith('-'));
|
|
328
|
+
|
|
329
|
+
if (!slug) {
|
|
330
|
+
console.error('Usage: mkt-skills set-info <slug>\n');
|
|
331
|
+
process.exit(1);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
let set;
|
|
335
|
+
try {
|
|
336
|
+
set = await fetchSet(slug);
|
|
337
|
+
} catch (err) {
|
|
338
|
+
if (err.code === 'NOT_FOUND') {
|
|
339
|
+
console.log(`\n${red('x')} Skill set "${slug}" not found.`);
|
|
340
|
+
console.log(` ${dim('->')} Browse sets: ${cyan('npx mkt-skills sets')}\n`);
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
343
|
+
throw err;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const installed = new Set(await installedSlugs());
|
|
347
|
+
|
|
348
|
+
console.log(`\n${bold(set.name)}`);
|
|
349
|
+
console.log(dim('-'.repeat(set.name.length)));
|
|
350
|
+
if (set.description) {
|
|
351
|
+
console.log(set.description);
|
|
352
|
+
}
|
|
353
|
+
console.log(`\n${set.count} skills:\n`);
|
|
354
|
+
|
|
355
|
+
const slugW = Math.max(4, ...set.skills.map(s => s.slug.length));
|
|
356
|
+
const titleW = Math.min(30, Math.max(5, ...set.skills.map(s => s.title.length)));
|
|
357
|
+
|
|
358
|
+
for (const s of set.skills) {
|
|
359
|
+
const price = s.price === 'paid' ? yellow('paid') : green('free');
|
|
360
|
+
const inst = installed.has(s.slug) ? green(' [installed]') : '';
|
|
361
|
+
const title = s.title.length > titleW ? s.title.slice(0, titleW - 1) + '\u2026' : s.title;
|
|
362
|
+
console.log(` ${s.slug.padEnd(slugW)} ${title.padEnd(titleW)} ${String(s.lines).padStart(5)} lines ${price}${inst}`);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
console.log(`\n${dim('Install:')} npx mkt-skills install-set ${set.slug}\n`);
|
|
366
|
+
}
|
|
367
|
+
|
|
214
368
|
// ── helpers ─────────────────────────────────────────────────
|
|
215
369
|
|
|
216
370
|
function confirm(message) {
|