@robosoft/skillhub-cli 0.1.0 → 0.1.1
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/CHANGELOG.md +11 -0
- package/README.md +62 -5
- package/bin/skillhub.mjs +144 -15
- package/docs/skillhub-cli-logic.md +23 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.1
|
|
4
|
+
|
|
5
|
+
- Added configurable install targets: `ai`, `cursor`, and `claude`.
|
|
6
|
+
- Added `skillhub target` command.
|
|
7
|
+
- Added `--target` / `--to` support for `init`, `install`, and `sync`.
|
|
8
|
+
- Cursor skills now install under `.cursor/skills` and update `.cursor/rules/skillhub.mdc`.
|
|
9
|
+
- Claude skills now install under `.claude/skills` and update `CLAUDE.md` plus `.claude/skillhub.md`.
|
|
10
|
+
- Added `cloude` alias for `claude`.
|
|
11
|
+
|
|
12
|
+
# Changelog
|
|
13
|
+
|
|
3
14
|
## 0.1.0
|
|
4
15
|
|
|
5
16
|
Initial publishable release of SkillHub CLI.
|
package/README.md
CHANGED
|
@@ -72,18 +72,46 @@ node ./bin/skillhub.mjs --help
|
|
|
72
72
|
|
|
73
73
|
## Use in Any Project
|
|
74
74
|
|
|
75
|
-
Create SkillHub config inside your target project:
|
|
75
|
+
Create SkillHub config inside your target project. The CLI can now ask where to place skill files:
|
|
76
76
|
|
|
77
77
|
```bash
|
|
78
78
|
skillhub init
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
+
Or pass the target directly:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
skillhub init --target cursor
|
|
85
|
+
skillhub init --target claude
|
|
86
|
+
skillhub init --target ai
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Targets:
|
|
90
|
+
|
|
91
|
+
```txt
|
|
92
|
+
ai -> .ai/skills + AGENTS.md
|
|
93
|
+
cursor -> .cursor/skills + .cursor/rules/skillhub.mdc
|
|
94
|
+
claude -> .claude/skills + CLAUDE.md + .claude/skillhub.md
|
|
95
|
+
```
|
|
96
|
+
|
|
81
97
|
Install a skill from a local registry:
|
|
82
98
|
|
|
83
99
|
```bash
|
|
84
100
|
skillhub install nextjs-clean-architecture --registry ./skillhub-registry
|
|
85
101
|
```
|
|
86
102
|
|
|
103
|
+
Install directly into Cursor skill location:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
skillhub install nextjs-clean-architecture --target cursor --registry ./skillhub-registry
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Install directly into Claude skill location:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
skillhub install nextjs-clean-architecture --target claude --registry ./skillhub-registry
|
|
113
|
+
```
|
|
114
|
+
|
|
87
115
|
Install a specific version:
|
|
88
116
|
|
|
89
117
|
```bash
|
|
@@ -96,6 +124,14 @@ List installed skills:
|
|
|
96
124
|
skillhub list
|
|
97
125
|
```
|
|
98
126
|
|
|
127
|
+
Change target later:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
skillhub target cursor
|
|
131
|
+
skillhub target claude
|
|
132
|
+
skillhub target ai
|
|
133
|
+
```
|
|
134
|
+
|
|
99
135
|
Sync all skills from `skillhub.json`:
|
|
100
136
|
|
|
101
137
|
```bash
|
|
@@ -149,12 +185,32 @@ npm run demo:validate
|
|
|
149
185
|
|
|
150
186
|
## Project Output
|
|
151
187
|
|
|
152
|
-
After installing skills, the target project gets files
|
|
188
|
+
After installing skills, the target project gets files based on the selected target.
|
|
189
|
+
|
|
190
|
+
For `--target cursor`:
|
|
191
|
+
|
|
192
|
+
```txt
|
|
193
|
+
.cursor/skills/<skill-name>/
|
|
194
|
+
.cursor/rules/skillhub.mdc
|
|
195
|
+
skillhub.json
|
|
196
|
+
skillhub.lock.json
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
For `--target claude`:
|
|
200
|
+
|
|
201
|
+
```txt
|
|
202
|
+
.claude/skills/<skill-name>/
|
|
203
|
+
.claude/skillhub.md
|
|
204
|
+
CLAUDE.md
|
|
205
|
+
skillhub.json
|
|
206
|
+
skillhub.lock.json
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
For `--target ai`:
|
|
153
210
|
|
|
154
211
|
```txt
|
|
155
212
|
.ai/skills/<skill-name>/
|
|
156
213
|
AGENTS.md
|
|
157
|
-
.cursor/rules/skillhub.mdc
|
|
158
214
|
skillhub.json
|
|
159
215
|
skillhub.lock.json
|
|
160
216
|
```
|
|
@@ -162,8 +218,9 @@ skillhub.lock.json
|
|
|
162
218
|
## Supported Commands
|
|
163
219
|
|
|
164
220
|
```txt
|
|
165
|
-
skillhub init
|
|
166
|
-
skillhub
|
|
221
|
+
skillhub init [--target ai|cursor|claude]
|
|
222
|
+
skillhub target [ai|cursor|claude]
|
|
223
|
+
skillhub install <skill[@version]> [--target ai|cursor|claude]
|
|
167
224
|
skillhub sync
|
|
168
225
|
skillhub list
|
|
169
226
|
skillhub remove <skill>
|
package/bin/skillhub.mjs
CHANGED
|
@@ -4,10 +4,29 @@ import fs from "node:fs/promises";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import crypto from "node:crypto";
|
|
6
6
|
import process from "node:process";
|
|
7
|
+
import readline from "node:readline/promises";
|
|
7
8
|
|
|
8
9
|
const CONFIG_FILE = "skillhub.json";
|
|
9
10
|
const LOCK_FILE = "skillhub.lock.json";
|
|
10
|
-
const
|
|
11
|
+
const DEFAULT_TARGET = "ai";
|
|
12
|
+
const TARGETS = {
|
|
13
|
+
ai: {
|
|
14
|
+
label: ".ai/skills",
|
|
15
|
+
skillsDir: ".ai/skills",
|
|
16
|
+
adapters: { agentsMd: true, cursorRules: false, claude: false, githubCopilot: false },
|
|
17
|
+
},
|
|
18
|
+
cursor: {
|
|
19
|
+
label: ".cursor/skills",
|
|
20
|
+
skillsDir: ".cursor/skills",
|
|
21
|
+
adapters: { agentsMd: false, cursorRules: true, claude: false, githubCopilot: false },
|
|
22
|
+
},
|
|
23
|
+
claude: {
|
|
24
|
+
label: ".claude/skills",
|
|
25
|
+
skillsDir: ".claude/skills",
|
|
26
|
+
adapters: { agentsMd: false, cursorRules: false, claude: true, githubCopilot: false },
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
const DEFAULT_SKILLS_DIR = TARGETS[DEFAULT_TARGET].skillsDir;
|
|
11
30
|
const GENERATED_START = "<!-- skillhub:start -->";
|
|
12
31
|
const GENERATED_END = "<!-- skillhub:end -->";
|
|
13
32
|
|
|
@@ -114,6 +133,75 @@ function parseFlags(argv) {
|
|
|
114
133
|
return { positional, flags };
|
|
115
134
|
}
|
|
116
135
|
|
|
136
|
+
function normalizeTarget(value) {
|
|
137
|
+
if (!value) return null;
|
|
138
|
+
|
|
139
|
+
const normalized = String(value).trim().toLowerCase().replace(/^\./, "");
|
|
140
|
+
const aliases = {
|
|
141
|
+
ai: "ai",
|
|
142
|
+
cursor: "cursor",
|
|
143
|
+
cursorrules: "cursor",
|
|
144
|
+
".cursor": "cursor",
|
|
145
|
+
claude: "claude",
|
|
146
|
+
cloude: "claude",
|
|
147
|
+
".claude": "claude",
|
|
148
|
+
".cloude": "claude",
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const target = aliases[normalized] || normalized;
|
|
152
|
+
if (!TARGETS[target]) {
|
|
153
|
+
throw new Error(`Invalid target "${value}". Use: ai, cursor, or claude.`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return target;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function askTarget(defaultTarget = DEFAULT_TARGET) {
|
|
160
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
161
|
+
return defaultTarget;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
165
|
+
try {
|
|
166
|
+
console.log(paint("bold", "Where should SkillHub install skill files?"));
|
|
167
|
+
console.log(` 1. ${TARGETS.ai.skillsDir} ${paint("dim", "(generic AI folder + AGENTS.md)")}`);
|
|
168
|
+
console.log(` 2. ${TARGETS.cursor.skillsDir} ${paint("dim", "(Cursor rules)")}`);
|
|
169
|
+
console.log(` 3. ${TARGETS.claude.skillsDir} ${paint("dim", "(Claude project instructions)")}`);
|
|
170
|
+
|
|
171
|
+
const answer = await rl.question(`Choose target [1/2/3 or ai/cursor/claude] (${defaultTarget}): `);
|
|
172
|
+
const raw = answer.trim();
|
|
173
|
+
if (!raw) return defaultTarget;
|
|
174
|
+
if (raw === "1") return "ai";
|
|
175
|
+
if (raw === "2") return "cursor";
|
|
176
|
+
if (raw === "3") return "claude";
|
|
177
|
+
return normalizeTarget(raw);
|
|
178
|
+
} finally {
|
|
179
|
+
rl.close();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function getConfigTarget(config) {
|
|
184
|
+
if (config?.target) return normalizeTarget(config.target);
|
|
185
|
+
|
|
186
|
+
const skillsDir = config?.skillsDir || DEFAULT_SKILLS_DIR;
|
|
187
|
+
if (skillsDir.startsWith(".cursor/")) return "cursor";
|
|
188
|
+
if (skillsDir.startsWith(".claude/") || skillsDir.startsWith(".cloude/")) return "claude";
|
|
189
|
+
return "ai";
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function applyTargetToConfig(config, targetValue, { preserveExistingAdapters = false } = {}) {
|
|
193
|
+
const target = normalizeTarget(targetValue) || DEFAULT_TARGET;
|
|
194
|
+
const targetConfig = TARGETS[target];
|
|
195
|
+
|
|
196
|
+
config.target = target;
|
|
197
|
+
config.skillsDir = targetConfig.skillsDir;
|
|
198
|
+
config.adapters = preserveExistingAdapters
|
|
199
|
+
? { ...targetConfig.adapters, ...(config.adapters || {}) }
|
|
200
|
+
: { ...targetConfig.adapters, ...(target === "ai" ? { agentsMd: true } : {}) };
|
|
201
|
+
|
|
202
|
+
return config;
|
|
203
|
+
}
|
|
204
|
+
|
|
117
205
|
function parseSkillSpec(spec) {
|
|
118
206
|
if (!spec || spec.trim().length === 0) {
|
|
119
207
|
throw new Error("Skill name is required.");
|
|
@@ -188,20 +276,18 @@ function checksumFiles(files) {
|
|
|
188
276
|
return `sha256-${hash.digest("hex")}`;
|
|
189
277
|
}
|
|
190
278
|
|
|
191
|
-
function defaultConfig(projectRoot) {
|
|
192
|
-
|
|
279
|
+
function defaultConfig(projectRoot, target = DEFAULT_TARGET) {
|
|
280
|
+
const config = {
|
|
193
281
|
$schema: "https://skillhub.local/schema/skillhub.schema.json",
|
|
194
282
|
project: path.basename(projectRoot),
|
|
195
283
|
registry: "./skillhub-registry",
|
|
284
|
+
target: DEFAULT_TARGET,
|
|
196
285
|
skillsDir: DEFAULT_SKILLS_DIR,
|
|
197
286
|
skills: {},
|
|
198
|
-
adapters: {
|
|
199
|
-
agentsMd: true,
|
|
200
|
-
cursorRules: true,
|
|
201
|
-
claude: false,
|
|
202
|
-
githubCopilot: false,
|
|
203
|
-
},
|
|
287
|
+
adapters: {},
|
|
204
288
|
};
|
|
289
|
+
|
|
290
|
+
return applyTargetToConfig(config, target);
|
|
205
291
|
}
|
|
206
292
|
|
|
207
293
|
function defaultLock() {
|
|
@@ -218,6 +304,10 @@ async function readProjectConfig(projectRoot) {
|
|
|
218
304
|
if (!config) {
|
|
219
305
|
throw new Error(`No ${CONFIG_FILE} found. Run: skillhub init`);
|
|
220
306
|
}
|
|
307
|
+
|
|
308
|
+
config.target = getConfigTarget(config);
|
|
309
|
+
config.skillsDir = config.skillsDir || TARGETS[config.target].skillsDir;
|
|
310
|
+
config.adapters = config.adapters || TARGETS[config.target].adapters;
|
|
221
311
|
return config;
|
|
222
312
|
}
|
|
223
313
|
|
|
@@ -403,8 +493,9 @@ async function updateCursorRules(projectRoot, skillsDir, installed) {
|
|
|
403
493
|
}
|
|
404
494
|
|
|
405
495
|
async function updateClaudeMd(projectRoot, skillsDir, installed) {
|
|
406
|
-
const body = `# SkillHub Instructions\n\nFollow the installed skills below:\n\n${generatedSkillList(skillsDir, installed)}`;
|
|
496
|
+
const body = `# SkillHub Instructions\n\nFollow the installed skills below before generating or changing code:\n\n${generatedSkillList(skillsDir, installed)}`;
|
|
407
497
|
await replaceGeneratedBlock(path.join(projectRoot, "CLAUDE.md"), body, "# Claude Project Instructions\n");
|
|
498
|
+
await replaceGeneratedBlock(path.join(projectRoot, ".claude/skillhub.md"), body, "# Claude SkillHub Instructions\n");
|
|
408
499
|
}
|
|
409
500
|
|
|
410
501
|
async function updateCopilotInstructions(projectRoot, skillsDir, installed) {
|
|
@@ -415,11 +506,13 @@ async function updateCopilotInstructions(projectRoot, skillsDir, installed) {
|
|
|
415
506
|
async function commandInit(projectRoot, flags) {
|
|
416
507
|
const configPath = path.join(projectRoot, CONFIG_FILE);
|
|
417
508
|
const lockPath = path.join(projectRoot, LOCK_FILE);
|
|
509
|
+
const target = normalizeTarget(flags.target || flags.to) || (flags.yes ? DEFAULT_TARGET : await askTarget(DEFAULT_TARGET));
|
|
418
510
|
|
|
419
511
|
if (await exists(configPath)) {
|
|
420
512
|
warn(`${CONFIG_FILE} already exists.`);
|
|
513
|
+
info(`To change target later: skillhub target ${target}`);
|
|
421
514
|
} else {
|
|
422
|
-
const config = defaultConfig(projectRoot);
|
|
515
|
+
const config = defaultConfig(projectRoot, target);
|
|
423
516
|
if (flags.registry) config.registry = flags.registry;
|
|
424
517
|
await writeJson(configPath, config);
|
|
425
518
|
ok(`Created ${CONFIG_FILE}`);
|
|
@@ -432,8 +525,9 @@ async function commandInit(projectRoot, flags) {
|
|
|
432
525
|
ok(`Created ${LOCK_FILE}`);
|
|
433
526
|
}
|
|
434
527
|
|
|
435
|
-
await
|
|
436
|
-
|
|
528
|
+
const config = await readProjectConfig(projectRoot);
|
|
529
|
+
await ensureDir(path.join(projectRoot, config.skillsDir));
|
|
530
|
+
ok(`Created ${config.skillsDir}`);
|
|
437
531
|
|
|
438
532
|
info("Next: skillhub install nextjs-clean-architecture");
|
|
439
533
|
}
|
|
@@ -442,6 +536,9 @@ async function commandInstall(projectRoot, spec, flags = {}) {
|
|
|
442
536
|
const configPath = path.join(projectRoot, CONFIG_FILE);
|
|
443
537
|
const lockPath = path.join(projectRoot, LOCK_FILE);
|
|
444
538
|
const config = await readProjectConfig(projectRoot);
|
|
539
|
+
if (flags.target || flags.to) {
|
|
540
|
+
applyTargetToConfig(config, flags.target || flags.to);
|
|
541
|
+
}
|
|
445
542
|
const lock = (await readJson(lockPath, null)) || defaultLock();
|
|
446
543
|
const registry = resolveRegistry(projectRoot, config, flags);
|
|
447
544
|
const { name, version } = parseSkillSpec(spec);
|
|
@@ -476,7 +573,12 @@ async function commandInstall(projectRoot, spec, flags = {}) {
|
|
|
476
573
|
}
|
|
477
574
|
|
|
478
575
|
async function commandSync(projectRoot, flags = {}) {
|
|
576
|
+
const configPath = path.join(projectRoot, CONFIG_FILE);
|
|
479
577
|
const config = await readProjectConfig(projectRoot);
|
|
578
|
+
if (flags.target || flags.to) {
|
|
579
|
+
applyTargetToConfig(config, flags.target || flags.to);
|
|
580
|
+
await writeJson(configPath, config);
|
|
581
|
+
}
|
|
480
582
|
const skills = Object.entries(config.skills || {});
|
|
481
583
|
|
|
482
584
|
if (skills.length === 0) {
|
|
@@ -513,12 +615,13 @@ async function commandRemove(projectRoot, skillName) {
|
|
|
513
615
|
const config = await readProjectConfig(projectRoot);
|
|
514
616
|
const lock = (await readJson(lockPath, null)) || defaultLock();
|
|
515
617
|
const skillsDir = config.skillsDir || DEFAULT_SKILLS_DIR;
|
|
618
|
+
const installedPath = lock.skills?.[skillName]?.installTo || `${skillsDir}/${skillName}`;
|
|
516
619
|
|
|
517
620
|
delete config.skills?.[skillName];
|
|
518
621
|
delete lock.skills?.[skillName];
|
|
519
622
|
lock.updatedAt = now();
|
|
520
623
|
|
|
521
|
-
await fs.rm(path.join(projectRoot,
|
|
624
|
+
await fs.rm(path.join(projectRoot, installedPath), { recursive: true, force: true });
|
|
522
625
|
await writeJson(configPath, config);
|
|
523
626
|
await writeJson(lockPath, lock);
|
|
524
627
|
await updateAdapters(projectRoot, config, lock);
|
|
@@ -526,6 +629,26 @@ async function commandRemove(projectRoot, skillName) {
|
|
|
526
629
|
ok(`Removed ${skillName}`);
|
|
527
630
|
}
|
|
528
631
|
|
|
632
|
+
async function commandTarget(projectRoot, targetValue, flags = {}) {
|
|
633
|
+
const configPath = path.join(projectRoot, CONFIG_FILE);
|
|
634
|
+
const lockPath = path.join(projectRoot, LOCK_FILE);
|
|
635
|
+
const config = await readProjectConfig(projectRoot);
|
|
636
|
+
const target = normalizeTarget(targetValue || flags.target || flags.to) || await askTarget(getConfigTarget(config));
|
|
637
|
+
|
|
638
|
+
applyTargetToConfig(config, target);
|
|
639
|
+
await writeJson(configPath, config);
|
|
640
|
+
await ensureDir(path.join(projectRoot, config.skillsDir));
|
|
641
|
+
|
|
642
|
+
const lock = (await readJson(lockPath, null)) || defaultLock();
|
|
643
|
+
await updateAdapters(projectRoot, config, lock);
|
|
644
|
+
|
|
645
|
+
ok(`SkillHub target set to ${target}`);
|
|
646
|
+
info(`Skill files will be installed to ${config.skillsDir}`);
|
|
647
|
+
if (Object.keys(lock.skills || {}).length > 0) {
|
|
648
|
+
info("Run: skillhub sync");
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
529
652
|
async function commandCreate(projectRoot, name, flags = {}) {
|
|
530
653
|
if (!name) throw new Error("Skill name is required.");
|
|
531
654
|
|
|
@@ -684,7 +807,7 @@ async function commandGenerate(projectRoot, skillName, templateName, resourceNam
|
|
|
684
807
|
}
|
|
685
808
|
|
|
686
809
|
function printHelp() {
|
|
687
|
-
console.log(`\n${paint("bold", "SkillHub CLI")}\n\nInstall reusable AI/project skills into any codebase.\n\n${paint("bold", "Usage")}\n skillhub <command> [options]\n\n${paint("bold", "Commands")}\n init Create skillhub.json, lockfile, and
|
|
810
|
+
console.log(`\n${paint("bold", "SkillHub CLI")}\n\nInstall reusable AI/project skills into any codebase.\n\n${paint("bold", "Usage")}\n skillhub <command> [options]\n\n${paint("bold", "Commands")}\n init Create skillhub.json, lockfile, and chosen skills folder\n target [ai|cursor|claude] Change where skills are installed\n install <skill[@version]> Install a skill from the registry\n sync Reinstall all skills from skillhub.json\n list Show installed skills\n remove <skill> Remove an installed skill\n create <skill> Scaffold a new local skill package\n validate <skill|path> Validate skill.json and SKILL.md\n generate <skill> <template> <name> Generate files from a skill template\n\n${paint("bold", "Targets")}\n ai Install to .ai/skills and update AGENTS.md\n cursor Install to .cursor/skills and update .cursor/rules/skillhub.mdc\n claude Install to .claude/skills and update CLAUDE.md + .claude/skillhub.md\n\n${paint("bold", "Options")}\n --target <ai|cursor|claude> Install target. Alias: --to\n --registry <path|url> Registry location. Default: ./skillhub-registry\n --version <version> Version used by create. Default: 1.0.0\n --category <name> Category used by create. Default: general\n --yes Use default init target without prompting\n\n${paint("bold", "Examples")}\n skillhub init --target cursor\n skillhub init --target claude\n skillhub install nextjs-clean-architecture --target cursor\n skillhub target claude\n skillhub sync\n skillhub create api-security-rules --category security\n`);
|
|
688
811
|
}
|
|
689
812
|
|
|
690
813
|
async function main() {
|
|
@@ -705,6 +828,10 @@ async function main() {
|
|
|
705
828
|
case "add":
|
|
706
829
|
await commandInstall(projectRoot, positional[0], flags);
|
|
707
830
|
break;
|
|
831
|
+
case "target":
|
|
832
|
+
case "configure":
|
|
833
|
+
await commandTarget(projectRoot, positional[0], flags);
|
|
834
|
+
break;
|
|
708
835
|
case "sync":
|
|
709
836
|
await commandSync(projectRoot, flags);
|
|
710
837
|
break;
|
|
@@ -742,6 +869,8 @@ export {
|
|
|
742
869
|
sortVersions,
|
|
743
870
|
validateSkillManifest,
|
|
744
871
|
defaultConfig,
|
|
872
|
+
normalizeTarget,
|
|
873
|
+
applyTargetToConfig,
|
|
745
874
|
};
|
|
746
875
|
|
|
747
876
|
// Let people run this file through `node packages/skillhub-cli/bin/skillhub.mjs`.
|
|
@@ -143,3 +143,26 @@ The enabled adapters are controlled in `skillhub.json`:
|
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
```
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
## Target folders
|
|
149
|
+
|
|
150
|
+
SkillHub can install skill packages into different AI-tool folders instead of only `.ai/skills`.
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
skillhub init --target cursor
|
|
154
|
+
skillhub init --target claude
|
|
155
|
+
skillhub install nextjs-clean-architecture --target cursor
|
|
156
|
+
skillhub target claude
|
|
157
|
+
skillhub sync
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Supported targets:
|
|
161
|
+
|
|
162
|
+
```txt
|
|
163
|
+
ai -> .ai/skills + AGENTS.md
|
|
164
|
+
cursor -> .cursor/skills + .cursor/rules/skillhub.mdc
|
|
165
|
+
claude -> .claude/skills + CLAUDE.md + .claude/skillhub.md
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The CLI also accepts `cloude` as an alias for `claude` to avoid failing on the common typo. Because apparently kindness can be implemented in 4 lines of code.
|