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.
@@ -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 [,, command, ...args] = process.argv;
5
+ const [,, rawCommand, ...args] = process.argv;
6
6
 
7
- const commands = { install, list, search, info, uninstall };
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.0.0');
22
+ console.log('mkt-skills 1.1.0');
16
23
  process.exit(0);
17
24
  }
18
25
 
19
- if (!commands[command]) {
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 commands[command](args);
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> Install a skill to ~/.claude/skills/
46
- list List all available skills
47
- search <keyword> Search skills by keyword
48
- info <slug> Show details about a skill
49
- uninstall <slug> Remove an installed skill
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 Show this help
53
- --version, -v Show version
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.0.0' },
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 fetchInstall(slug) {
26
- const res = await request(`/install/${encodeURIComponent(slug)}`);
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 = args.includes('--force') || args.includes('-f');
11
- const slug = args.find(a => !a.startsWith('-'));
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mkt-skills",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Install Claude Code marketing skills from marketingskills.directory",
5
5
  "type": "module",
6
6
  "bin": {