mkt-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/README.md +63 -0
- package/bin/mkt-skills.mjs +63 -0
- package/lib/api.mjs +43 -0
- package/lib/commands.mjs +224 -0
- package/lib/paths.mjs +22 -0
- package/lib/ui.mjs +14 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# mkt-skills
|
|
2
|
+
|
|
3
|
+
Install Claude Code marketing skills from [marketingskills.directory](https://marketingskills.directory).
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx mkt-skills install seo-content
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This downloads the skill and saves it to `~/.claude/skills/seo-content/SKILL.md`, ready for Claude Code to use.
|
|
12
|
+
|
|
13
|
+
## Commands
|
|
14
|
+
|
|
15
|
+
### Install a skill
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx mkt-skills install <slug>
|
|
19
|
+
npx mkt-skills install seo-content --force # overwrite existing
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### List available skills
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx mkt-skills list
|
|
26
|
+
npx mkt-skills list --category=seo
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Search skills
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx mkt-skills search "email automation"
|
|
33
|
+
npx mkt-skills search content
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Skill details
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx mkt-skills info marketing-audit
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Uninstall a skill
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npx mkt-skills uninstall seo-content
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Requirements
|
|
49
|
+
|
|
50
|
+
- Node.js 18 or later
|
|
51
|
+
- Claude Code (skills are installed to `~/.claude/skills/`)
|
|
52
|
+
|
|
53
|
+
## Local Development
|
|
54
|
+
|
|
55
|
+
Override the API URL for testing against a local WordPress instance:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
MKT_API_URL=http://localhost:8080/wp-json/mkt/v1 npx mkt-skills list
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## License
|
|
62
|
+
|
|
63
|
+
MIT
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { install, list, search, info, uninstall } from '../lib/commands.mjs';
|
|
4
|
+
|
|
5
|
+
const [,, command, ...args] = process.argv;
|
|
6
|
+
|
|
7
|
+
const commands = { install, list, search, info, uninstall };
|
|
8
|
+
|
|
9
|
+
if (!command || command === '--help' || command === '-h') {
|
|
10
|
+
showHelp();
|
|
11
|
+
process.exit(0);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (command === '--version' || command === '-v') {
|
|
15
|
+
console.log('mkt-skills 1.0.0');
|
|
16
|
+
process.exit(0);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!commands[command]) {
|
|
20
|
+
console.error(`Unknown command: ${command}\nRun "mkt-skills --help" for usage.`);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
await commands[command](args);
|
|
26
|
+
} catch (err) {
|
|
27
|
+
if (err.name === 'AbortError') {
|
|
28
|
+
console.error('Request timed out. Check your internet connection.');
|
|
29
|
+
} else if (err.cause?.code === 'ENOTFOUND' || err.cause?.code === 'ECONNREFUSED') {
|
|
30
|
+
console.error('Could not connect to marketingskills.directory. Check your internet connection.');
|
|
31
|
+
} else {
|
|
32
|
+
console.error(`Error: ${err.message}`);
|
|
33
|
+
}
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function showHelp() {
|
|
38
|
+
console.log(`
|
|
39
|
+
Marketing Skills Directory CLI
|
|
40
|
+
|
|
41
|
+
Usage:
|
|
42
|
+
npx mkt-skills <command> [args]
|
|
43
|
+
|
|
44
|
+
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
|
|
50
|
+
|
|
51
|
+
Options:
|
|
52
|
+
--help, -h Show this help
|
|
53
|
+
--version, -v Show version
|
|
54
|
+
|
|
55
|
+
Examples:
|
|
56
|
+
npx mkt-skills install seo-content
|
|
57
|
+
npx mkt-skills search "email automation"
|
|
58
|
+
npx mkt-skills list --category=seo
|
|
59
|
+
npx mkt-skills info marketing-audit
|
|
60
|
+
|
|
61
|
+
https://marketingskills.directory
|
|
62
|
+
`);
|
|
63
|
+
}
|
package/lib/api.mjs
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const BASE_URL = process.env.MKT_API_URL || 'https://marketingskills.directory/wp-json/mkt/v1';
|
|
2
|
+
const TIMEOUT = 30_000;
|
|
3
|
+
|
|
4
|
+
async function request(path) {
|
|
5
|
+
const controller = new AbortController();
|
|
6
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT);
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
const res = await fetch(`${BASE_URL}${path}`, {
|
|
10
|
+
signal: controller.signal,
|
|
11
|
+
headers: { 'User-Agent': 'mkt-skills-cli/1.0.0' },
|
|
12
|
+
});
|
|
13
|
+
return res;
|
|
14
|
+
} finally {
|
|
15
|
+
clearTimeout(timer);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function fetchCatalog() {
|
|
20
|
+
const res = await request('/catalog');
|
|
21
|
+
if (!res.ok) throw new Error(`Failed to fetch catalog (HTTP ${res.status})`);
|
|
22
|
+
return res.json();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function fetchInstall(slug) {
|
|
26
|
+
const res = await request(`/install/${encodeURIComponent(slug)}`);
|
|
27
|
+
|
|
28
|
+
if (res.status === 403) {
|
|
29
|
+
const data = await res.json();
|
|
30
|
+
const err = new Error(data.message);
|
|
31
|
+
err.code = 'PREMIUM';
|
|
32
|
+
err.url = data.purchase_url;
|
|
33
|
+
throw err;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (res.status === 404) {
|
|
37
|
+
const data = await res.json();
|
|
38
|
+
throw Object.assign(new Error(data.message), { code: 'NOT_FOUND' });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!res.ok) throw new Error(`Install failed (HTTP ${res.status})`);
|
|
42
|
+
return res.json();
|
|
43
|
+
}
|
package/lib/commands.mjs
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { mkdir, rm, access, writeFile } from 'fs/promises';
|
|
2
|
+
import { createInterface } from 'readline';
|
|
3
|
+
import { fetchCatalog, fetchInstall } from './api.mjs';
|
|
4
|
+
import { skillDir, skillFile, installedSlugs } from './paths.mjs';
|
|
5
|
+
import { bold, dim, green, red, yellow, cyan } from './ui.mjs';
|
|
6
|
+
|
|
7
|
+
// ── install ─────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
export async function install(args) {
|
|
10
|
+
const force = args.includes('--force') || args.includes('-f');
|
|
11
|
+
const slug = args.find(a => !a.startsWith('-'));
|
|
12
|
+
|
|
13
|
+
if (!slug) {
|
|
14
|
+
console.error(`Usage: mkt-skills install <slug> [--force]\n`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const dest = skillFile(slug);
|
|
19
|
+
|
|
20
|
+
if (!force) {
|
|
21
|
+
try {
|
|
22
|
+
await access(dest);
|
|
23
|
+
const ok = await confirm(`Skill "${slug}" is already installed. Overwrite?`);
|
|
24
|
+
if (!ok) {
|
|
25
|
+
console.log('Aborted.');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
} catch { /* not installed — proceed */ }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let data;
|
|
32
|
+
try {
|
|
33
|
+
data = await fetchInstall(slug);
|
|
34
|
+
} catch (err) {
|
|
35
|
+
if (err.code === 'PREMIUM') {
|
|
36
|
+
console.log(`\n${red('x')} ${bold(`"${slug}"`)} is a premium skill.`);
|
|
37
|
+
console.log(` ${dim('->')} Purchase at: ${cyan(err.url)}\n`);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
if (err.code === 'NOT_FOUND') {
|
|
41
|
+
console.log(`\n${red('x')} Skill "${slug}" not found.`);
|
|
42
|
+
console.log(` ${dim('->')} Browse available skills: ${cyan('npx mkt-skills list')}\n`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
await mkdir(skillDir(slug), { recursive: true });
|
|
49
|
+
await writeFile(dest, data.content, 'utf-8');
|
|
50
|
+
|
|
51
|
+
console.log(`\n${green('+')} Installed ${bold(`"${data.title}"`)} (${data.lines} lines)`);
|
|
52
|
+
console.log(` ${dim('->')} ${dest}\n`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── list ────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
export async function list(args) {
|
|
58
|
+
const catFlag = args.find(a => a.startsWith('--category='));
|
|
59
|
+
const catFilter = catFlag ? catFlag.split('=')[1].toLowerCase() : null;
|
|
60
|
+
|
|
61
|
+
const catalog = await fetchCatalog();
|
|
62
|
+
const installed = new Set(await installedSlugs());
|
|
63
|
+
|
|
64
|
+
let items = catalog;
|
|
65
|
+
if (catFilter) {
|
|
66
|
+
items = items.filter(s => s.categories.some(c => c.toLowerCase().includes(catFilter)));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (items.length === 0) {
|
|
70
|
+
console.log(catFilter
|
|
71
|
+
? `No skills found in category "${catFilter}".`
|
|
72
|
+
: 'No skills available.');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
console.log(`\n${bold('Marketing Skills Directory')} (${items.length} skills)\n`);
|
|
77
|
+
|
|
78
|
+
const slugW = Math.min(30, Math.max(4, ...items.map(s => s.slug.length)));
|
|
79
|
+
const titleW = Math.min(35, Math.max(5, ...items.map(s => s.title.length)));
|
|
80
|
+
|
|
81
|
+
const header = [
|
|
82
|
+
'SLUG'.padEnd(slugW),
|
|
83
|
+
'TITLE'.padEnd(titleW),
|
|
84
|
+
'LINES'.padStart(5),
|
|
85
|
+
'PRICE'.padEnd(7),
|
|
86
|
+
'CATEGORY',
|
|
87
|
+
].join(' ');
|
|
88
|
+
|
|
89
|
+
console.log(dim(header));
|
|
90
|
+
console.log(dim('-'.repeat(header.length)));
|
|
91
|
+
|
|
92
|
+
for (const s of items) {
|
|
93
|
+
const inst = installed.has(s.slug) ? green(' [installed]') : '';
|
|
94
|
+
const price = s.price === 'paid' ? yellow('paid') : green('free');
|
|
95
|
+
const cats = s.categories.join(', ') || '-';
|
|
96
|
+
console.log([
|
|
97
|
+
s.slug.padEnd(slugW),
|
|
98
|
+
(s.title.length > titleW ? s.title.slice(0, titleW - 1) + '\u2026' : s.title).padEnd(titleW),
|
|
99
|
+
String(s.lines).padStart(5),
|
|
100
|
+
price.padEnd(7 + (price.length - (s.price === 'paid' ? 4 : 4))),
|
|
101
|
+
cats,
|
|
102
|
+
].join(' ') + inst);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log(`\n${dim('Install:')} npx mkt-skills install <slug>\n`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ── search ──────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
export async function search(args) {
|
|
111
|
+
const query = args.filter(a => !a.startsWith('-')).join(' ').toLowerCase();
|
|
112
|
+
|
|
113
|
+
if (!query) {
|
|
114
|
+
console.error('Usage: mkt-skills search <keyword>\n');
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const catalog = await fetchCatalog();
|
|
119
|
+
const results = catalog.filter(s =>
|
|
120
|
+
s.slug.includes(query) ||
|
|
121
|
+
s.title.toLowerCase().includes(query) ||
|
|
122
|
+
s.description.toLowerCase().includes(query) ||
|
|
123
|
+
s.categories.some(c => c.toLowerCase().includes(query))
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
if (results.length === 0) {
|
|
127
|
+
console.log(`\nNo skills found matching "${query}".`);
|
|
128
|
+
console.log(`${dim('->')} Browse all: ${cyan('npx mkt-skills list')}\n`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
console.log(`\n${bold(`Search: "${query}"`)} (${results.length} results)\n`);
|
|
133
|
+
|
|
134
|
+
for (const s of results) {
|
|
135
|
+
const price = s.price === 'paid' ? yellow(' [premium]') : '';
|
|
136
|
+
const cats = s.categories.length ? dim(` (${s.categories.join(', ')})`) : '';
|
|
137
|
+
console.log(` ${bold(s.slug)}${price}${cats}`);
|
|
138
|
+
console.log(` ${s.description}`);
|
|
139
|
+
console.log('');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
console.log(`${dim('Install:')} npx mkt-skills install <slug>\n`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ── info ────────────────────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
export async function info(args) {
|
|
148
|
+
const slug = args.find(a => !a.startsWith('-'));
|
|
149
|
+
|
|
150
|
+
if (!slug) {
|
|
151
|
+
console.error('Usage: mkt-skills info <slug>\n');
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const catalog = await fetchCatalog();
|
|
156
|
+
const skill = catalog.find(s => s.slug === slug);
|
|
157
|
+
|
|
158
|
+
if (!skill) {
|
|
159
|
+
console.log(`\n${red('x')} Skill "${slug}" not found.`);
|
|
160
|
+
// suggest close matches
|
|
161
|
+
const close = catalog.filter(s => s.slug.includes(slug) || slug.includes(s.slug)).slice(0, 3);
|
|
162
|
+
if (close.length) {
|
|
163
|
+
console.log(`\n Did you mean?`);
|
|
164
|
+
close.forEach(s => console.log(` ${cyan(s.slug)} ${s.title}`));
|
|
165
|
+
}
|
|
166
|
+
console.log('');
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const installed = (await installedSlugs()).includes(slug);
|
|
171
|
+
const priceLabel = skill.price === 'paid' ? yellow('Premium') : green('Free');
|
|
172
|
+
const secLabel = skill.security_level === 'safe' ? green('safe') : (skill.security_level || dim('unknown'));
|
|
173
|
+
|
|
174
|
+
console.log(`\n${bold(skill.title)}\n`);
|
|
175
|
+
console.log(` Slug: ${skill.slug}`);
|
|
176
|
+
console.log(` Price: ${priceLabel}`);
|
|
177
|
+
console.log(` Lines: ${skill.lines}`);
|
|
178
|
+
console.log(` Categories: ${skill.categories.join(', ') || '-'}`);
|
|
179
|
+
console.log(` Security: ${secLabel}`);
|
|
180
|
+
console.log(` Downloads: ${skill.downloads}`);
|
|
181
|
+
console.log(` Installed: ${installed ? green('yes') : 'no'}`);
|
|
182
|
+
console.log(`\n ${dim('Description:')}`);
|
|
183
|
+
console.log(` ${skill.description}`);
|
|
184
|
+
console.log(`\n ${dim('Install:')}`);
|
|
185
|
+
console.log(` npx mkt-skills install ${skill.slug}`);
|
|
186
|
+
console.log(`\n ${dim('Web:')}`);
|
|
187
|
+
console.log(` https://marketingskills.directory/skill/${skill.slug}/\n`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ── uninstall ───────────────────────────────────────────────
|
|
191
|
+
|
|
192
|
+
export async function uninstall(args) {
|
|
193
|
+
const slug = args.find(a => !a.startsWith('-'));
|
|
194
|
+
|
|
195
|
+
if (!slug) {
|
|
196
|
+
console.error('Usage: mkt-skills uninstall <slug>\n');
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const dir = skillDir(slug);
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
await access(dir);
|
|
204
|
+
} catch {
|
|
205
|
+
console.log(`\n${red('x')} Skill "${slug}" is not installed.\n`);
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
await rm(dir, { recursive: true, force: true });
|
|
210
|
+
console.log(`\n${green('+')} Uninstalled "${slug}"`);
|
|
211
|
+
console.log(` ${dim('->')} Removed ${dir}\n`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ── helpers ─────────────────────────────────────────────────
|
|
215
|
+
|
|
216
|
+
function confirm(message) {
|
|
217
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
218
|
+
return new Promise(resolve => {
|
|
219
|
+
rl.question(`${message} (y/N) `, answer => {
|
|
220
|
+
rl.close();
|
|
221
|
+
resolve(answer.trim().toLowerCase() === 'y');
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
}
|
package/lib/paths.mjs
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { homedir } from 'os';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { readdir } from 'fs/promises';
|
|
4
|
+
|
|
5
|
+
const SKILLS_ROOT = join(homedir(), '.claude', 'skills');
|
|
6
|
+
|
|
7
|
+
export function skillDir(slug) {
|
|
8
|
+
return join(SKILLS_ROOT, slug);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function skillFile(slug) {
|
|
12
|
+
return join(skillDir(slug), 'SKILL.md');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function installedSlugs() {
|
|
16
|
+
try {
|
|
17
|
+
const entries = await readdir(SKILLS_ROOT, { withFileTypes: true });
|
|
18
|
+
return entries.filter(e => e.isDirectory()).map(e => e.name);
|
|
19
|
+
} catch {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
}
|
package/lib/ui.mjs
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// ANSI color helpers — zero dependencies.
|
|
2
|
+
|
|
3
|
+
const enabled = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
4
|
+
|
|
5
|
+
const wrap = (code, reset) => enabled
|
|
6
|
+
? s => `\x1b[${code}m${s}\x1b[${reset}m`
|
|
7
|
+
: s => s;
|
|
8
|
+
|
|
9
|
+
export const bold = wrap('1', '22');
|
|
10
|
+
export const dim = wrap('2', '22');
|
|
11
|
+
export const green = wrap('32', '39');
|
|
12
|
+
export const red = wrap('31', '39');
|
|
13
|
+
export const yellow = wrap('33', '39');
|
|
14
|
+
export const cyan = wrap('36', '39');
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mkt-skills",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Install Claude Code marketing skills from marketingskills.directory",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mkt-skills": "./bin/mkt-skills.mjs"
|
|
8
|
+
},
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=18.0.0"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin/",
|
|
14
|
+
"lib/",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"claude",
|
|
20
|
+
"claude-code",
|
|
21
|
+
"skills",
|
|
22
|
+
"marketing",
|
|
23
|
+
"seo",
|
|
24
|
+
"ai",
|
|
25
|
+
"cli"
|
|
26
|
+
],
|
|
27
|
+
"author": "Thrivemattic",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/sandeepkelvadi/mkt-skills-dir"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://marketingskills.directory"
|
|
34
|
+
}
|