@onezhao/skill-sync 1.1.0 → 1.2.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/dist/cli.mjs +118 -58
- package/package.json +2 -1
package/dist/cli.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { access, lstat, mkdir, readFile, readlink, rm, symlink } from "fs/promis
|
|
|
6
6
|
import { homedir, platform } from "os";
|
|
7
7
|
import matter from "gray-matter";
|
|
8
8
|
import { xdgConfig } from "xdg-basedir";
|
|
9
|
+
import { Prompt, isCancel } from "@clack/core";
|
|
9
10
|
//#region src/skills.ts
|
|
10
11
|
async function parseSkillMd(skillMdPath, options) {
|
|
11
12
|
try {
|
|
@@ -37,6 +38,34 @@ function getOpenClawGlobalSkillsDir(homeDir = home, pathExists = existsSync) {
|
|
|
37
38
|
return join(homeDir, ".openclaw/skills");
|
|
38
39
|
}
|
|
39
40
|
const agents = {
|
|
41
|
+
"claude-code": {
|
|
42
|
+
name: "claude-code",
|
|
43
|
+
displayName: "Claude Code",
|
|
44
|
+
skillsDir: ".claude/skills",
|
|
45
|
+
globalSkillsDir: join(claudeHome, "skills"),
|
|
46
|
+
detectInstalled: async () => existsSync(claudeHome)
|
|
47
|
+
},
|
|
48
|
+
"kiro-cli": {
|
|
49
|
+
name: "kiro-cli",
|
|
50
|
+
displayName: "Kiro CLI",
|
|
51
|
+
skillsDir: ".kiro/skills",
|
|
52
|
+
globalSkillsDir: join(home, ".kiro/skills"),
|
|
53
|
+
detectInstalled: async () => existsSync(join(home, ".kiro"))
|
|
54
|
+
},
|
|
55
|
+
trae: {
|
|
56
|
+
name: "trae",
|
|
57
|
+
displayName: "Trae",
|
|
58
|
+
skillsDir: ".trae/skills",
|
|
59
|
+
globalSkillsDir: join(home, ".trae/skills"),
|
|
60
|
+
detectInstalled: async () => existsSync(join(home, ".trae"))
|
|
61
|
+
},
|
|
62
|
+
"trae-cn": {
|
|
63
|
+
name: "trae-cn",
|
|
64
|
+
displayName: "Trae CN",
|
|
65
|
+
skillsDir: ".trae/skills",
|
|
66
|
+
globalSkillsDir: join(home, ".trae-cn/skills"),
|
|
67
|
+
detectInstalled: async () => existsSync(join(home, ".trae-cn"))
|
|
68
|
+
},
|
|
40
69
|
amp: {
|
|
41
70
|
name: "amp",
|
|
42
71
|
displayName: "Amp",
|
|
@@ -58,13 +87,6 @@ const agents = {
|
|
|
58
87
|
globalSkillsDir: join(home, ".augment/skills"),
|
|
59
88
|
detectInstalled: async () => existsSync(join(home, ".augment"))
|
|
60
89
|
},
|
|
61
|
-
"claude-code": {
|
|
62
|
-
name: "claude-code",
|
|
63
|
-
displayName: "Claude Code",
|
|
64
|
-
skillsDir: ".claude/skills",
|
|
65
|
-
globalSkillsDir: join(claudeHome, "skills"),
|
|
66
|
-
detectInstalled: async () => existsSync(claudeHome)
|
|
67
|
-
},
|
|
68
90
|
openclaw: {
|
|
69
91
|
name: "openclaw",
|
|
70
92
|
displayName: "OpenClaw",
|
|
@@ -184,13 +206,6 @@ const agents = {
|
|
|
184
206
|
globalSkillsDir: join(home, ".config/agents/skills"),
|
|
185
207
|
detectInstalled: async () => existsSync(join(home, ".kimi"))
|
|
186
208
|
},
|
|
187
|
-
"kiro-cli": {
|
|
188
|
-
name: "kiro-cli",
|
|
189
|
-
displayName: "Kiro CLI",
|
|
190
|
-
skillsDir: ".kiro/skills",
|
|
191
|
-
globalSkillsDir: join(home, ".kiro/skills"),
|
|
192
|
-
detectInstalled: async () => existsSync(join(home, ".kiro"))
|
|
193
|
-
},
|
|
194
209
|
kode: {
|
|
195
210
|
name: "kode",
|
|
196
211
|
displayName: "Kode",
|
|
@@ -269,20 +284,6 @@ const agents = {
|
|
|
269
284
|
globalSkillsDir: join(home, ".roo/skills"),
|
|
270
285
|
detectInstalled: async () => existsSync(join(home, ".roo"))
|
|
271
286
|
},
|
|
272
|
-
trae: {
|
|
273
|
-
name: "trae",
|
|
274
|
-
displayName: "Trae",
|
|
275
|
-
skillsDir: ".trae/skills",
|
|
276
|
-
globalSkillsDir: join(home, ".trae/skills"),
|
|
277
|
-
detectInstalled: async () => existsSync(join(home, ".trae"))
|
|
278
|
-
},
|
|
279
|
-
"trae-cn": {
|
|
280
|
-
name: "trae-cn",
|
|
281
|
-
displayName: "Trae CN",
|
|
282
|
-
skillsDir: ".trae/skills",
|
|
283
|
-
globalSkillsDir: join(home, ".trae-cn/skills"),
|
|
284
|
-
detectInstalled: async () => existsSync(join(home, ".trae-cn"))
|
|
285
|
-
},
|
|
286
287
|
windsurf: {
|
|
287
288
|
name: "windsurf",
|
|
288
289
|
displayName: "Windsurf",
|
|
@@ -334,6 +335,79 @@ async function detectInstalledAgents() {
|
|
|
334
335
|
})))).filter((r) => r.installed).map((r) => r.type);
|
|
335
336
|
}
|
|
336
337
|
//#endregion
|
|
338
|
+
//#region src/multiselect.ts
|
|
339
|
+
const S_STEP_SUBMIT = "◇";
|
|
340
|
+
const S_CHECKBOX_ACTIVE = pc.green("◼");
|
|
341
|
+
const S_CHECKBOX_INACTIVE = "◻";
|
|
342
|
+
const S_BAR = "│";
|
|
343
|
+
const S_BAR_END = "└";
|
|
344
|
+
async function multiselectAll(opts) {
|
|
345
|
+
const realOptions = opts.options;
|
|
346
|
+
const realValues = realOptions.map((o) => o.value);
|
|
347
|
+
const totalCount = realOptions.length + 1;
|
|
348
|
+
let cursor = 0;
|
|
349
|
+
const selected = /* @__PURE__ */ new Set();
|
|
350
|
+
let selectAllChecked = false;
|
|
351
|
+
const prompt = new Prompt({
|
|
352
|
+
validate(value) {
|
|
353
|
+
if (opts.required && (!value || value.length === 0)) return "Please select at least one option.";
|
|
354
|
+
},
|
|
355
|
+
render() {
|
|
356
|
+
const title = `${pc.gray(S_BAR)}\n${pc.cyan("●")} ${opts.message}\n`;
|
|
357
|
+
if (this.state === "submit") {
|
|
358
|
+
const labels = (this.value ?? []).map((v) => {
|
|
359
|
+
return realOptions.find((o) => o.value === v)?.label ?? String(v);
|
|
360
|
+
});
|
|
361
|
+
return `${pc.gray(S_STEP_SUBMIT)} ${opts.message}\n${pc.gray(S_BAR)} ${pc.dim(labels.join(", "))}`;
|
|
362
|
+
}
|
|
363
|
+
if (this.state === "cancel") return `${pc.gray(S_STEP_SUBMIT)} ${opts.message}\n${pc.gray(S_BAR)} ${pc.strikethrough(pc.dim("Cancelled"))}`;
|
|
364
|
+
const saActive = cursor === 0;
|
|
365
|
+
const saCheckbox = selectAllChecked ? S_CHECKBOX_ACTIVE : S_CHECKBOX_INACTIVE;
|
|
366
|
+
const saCursor = saActive ? pc.cyan("›") : " ";
|
|
367
|
+
const saLabel = saActive || selectAllChecked ? pc.bold("Select All") : pc.dim(pc.bold("Select All"));
|
|
368
|
+
const selectAllLine = `${pc.gray(S_BAR)} ${saCursor} ${saCheckbox} ${saLabel} ${pc.dim(`(${realValues.length} items)`)}`;
|
|
369
|
+
const lines = realOptions.map((opt, i) => {
|
|
370
|
+
const isActive = cursor === i + 1;
|
|
371
|
+
const isSelected = selected.has(i);
|
|
372
|
+
const checkbox = isSelected ? S_CHECKBOX_ACTIVE : S_CHECKBOX_INACTIVE;
|
|
373
|
+
const cur = isActive ? pc.cyan("›") : " ";
|
|
374
|
+
const label = isActive || isSelected ? opt.label : pc.dim(opt.label);
|
|
375
|
+
const hint = opt.hint ? pc.dim(` (${opt.hint})`) : "";
|
|
376
|
+
return `${pc.gray(S_BAR)} ${cur} ${checkbox} ${label}${hint}`;
|
|
377
|
+
});
|
|
378
|
+
const error = this.state === "error" && this.error ? `\n${pc.yellow("⚠")} ${this.error}` : "";
|
|
379
|
+
return `${title}${selectAllLine}\n${lines.join("\n")}\n${pc.gray(S_BAR)} ${pc.dim("(space: toggle, enter: confirm)")}${error}\n${pc.gray(S_BAR_END)}`;
|
|
380
|
+
}
|
|
381
|
+
}, false);
|
|
382
|
+
prompt.on("cursor", (action) => {
|
|
383
|
+
if (!action) return;
|
|
384
|
+
if (action === "up") cursor = cursor <= 0 ? totalCount - 1 : cursor - 1;
|
|
385
|
+
else if (action === "down") cursor = cursor >= totalCount - 1 ? 0 : cursor + 1;
|
|
386
|
+
else if (action === "space") {
|
|
387
|
+
if (cursor === 0) if (selectAllChecked) {
|
|
388
|
+
selected.clear();
|
|
389
|
+
selectAllChecked = false;
|
|
390
|
+
} else {
|
|
391
|
+
for (let i = 0; i < realOptions.length; i++) selected.add(i);
|
|
392
|
+
selectAllChecked = true;
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
const idx = cursor - 1;
|
|
396
|
+
if (selected.has(idx)) selected.delete(idx);
|
|
397
|
+
else selected.add(idx);
|
|
398
|
+
selectAllChecked = selected.size === realOptions.length;
|
|
399
|
+
}
|
|
400
|
+
prompt.value = realValues.filter((_, i) => selected.has(i));
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
prompt.on("key", (char, key) => {
|
|
404
|
+
if (key?.name === "return") prompt.value = realValues.filter((_, i) => selected.has(i));
|
|
405
|
+
});
|
|
406
|
+
const result = await prompt.prompt();
|
|
407
|
+
if (isCancel(result)) return result;
|
|
408
|
+
return result ?? [];
|
|
409
|
+
}
|
|
410
|
+
//#endregion
|
|
337
411
|
//#region src/cli.ts
|
|
338
412
|
const AGENTS_DIR = ".agents";
|
|
339
413
|
const SKILLS_SUBDIR = "skills";
|
|
@@ -545,32 +619,25 @@ async function runSkillSync(args = []) {
|
|
|
545
619
|
process.exit(1);
|
|
546
620
|
}
|
|
547
621
|
spinner.stop(`Found ${pc.green(skills.length)} skill${skills.length !== 1 ? "s" : ""}`);
|
|
548
|
-
const SELECT_ALL_SKILLS = Symbol("select-all-skills");
|
|
549
|
-
const skillChoices = [{
|
|
550
|
-
value: SELECT_ALL_SKILLS,
|
|
551
|
-
label: pc.bold("Select All"),
|
|
552
|
-
hint: `All ${skills.length} skills`
|
|
553
|
-
}, ...skills.map((s) => ({
|
|
554
|
-
value: s,
|
|
555
|
-
label: s.name,
|
|
556
|
-
hint: s.description.length > 50 ? s.description.slice(0, 47) + "..." : s.description
|
|
557
|
-
}))];
|
|
558
622
|
let selectedSkills;
|
|
559
623
|
if (options.yes) {
|
|
560
624
|
selectedSkills = skills;
|
|
561
625
|
p.log.info(`Selected all ${skills.length} skills`);
|
|
562
626
|
} else {
|
|
563
|
-
const
|
|
627
|
+
const picked = await multiselectAll({
|
|
564
628
|
message: "Select skills to sync",
|
|
565
|
-
options:
|
|
629
|
+
options: skills.map((s) => ({
|
|
630
|
+
value: s,
|
|
631
|
+
label: s.name,
|
|
632
|
+
hint: s.description.length > 50 ? s.description.slice(0, 47) + "..." : s.description
|
|
633
|
+
})),
|
|
566
634
|
required: true
|
|
567
635
|
});
|
|
568
|
-
if (p.isCancel(
|
|
636
|
+
if (p.isCancel(picked)) {
|
|
569
637
|
p.cancel("Cancelled");
|
|
570
638
|
process.exit(0);
|
|
571
639
|
}
|
|
572
|
-
|
|
573
|
-
selectedSkills = selection.includes(SELECT_ALL_SKILLS) ? skills : selection.filter((s) => s !== SELECT_ALL_SKILLS);
|
|
640
|
+
selectedSkills = picked;
|
|
574
641
|
}
|
|
575
642
|
if (selectedSkills.length === 0) {
|
|
576
643
|
p.cancel("No skills selected");
|
|
@@ -599,27 +666,20 @@ async function runSkillSync(args = []) {
|
|
|
599
666
|
agentsToLink = filteredAgents;
|
|
600
667
|
p.log.info(`Selected all ${filteredAgents.length} available agents`);
|
|
601
668
|
} else {
|
|
602
|
-
const
|
|
603
|
-
const agentChoices = [{
|
|
604
|
-
value: SELECT_ALL_AGENTS,
|
|
605
|
-
label: pc.bold("Select All"),
|
|
606
|
-
hint: `All ${filteredAgents.length} agents`
|
|
607
|
-
}, ...filteredAgents.map((a) => ({
|
|
608
|
-
value: a,
|
|
609
|
-
label: a.displayName,
|
|
610
|
-
hint: a.isInstalled ? `${a.skillsDir} ${pc.green("(installed)")}` : `${a.skillsDir} ${pc.yellow("(not detected)")}`
|
|
611
|
-
}))];
|
|
612
|
-
const rawAgents = await p.multiselect({
|
|
669
|
+
const picked = await multiselectAll({
|
|
613
670
|
message: "Select target agents",
|
|
614
|
-
options:
|
|
671
|
+
options: filteredAgents.map((a) => ({
|
|
672
|
+
value: a,
|
|
673
|
+
label: a.displayName,
|
|
674
|
+
hint: a.isInstalled ? `${a.skillsDir} ${pc.green("(installed)")}` : `${a.skillsDir} ${pc.yellow("(not detected)")}`
|
|
675
|
+
})),
|
|
615
676
|
required: true
|
|
616
677
|
});
|
|
617
|
-
if (p.isCancel(
|
|
678
|
+
if (p.isCancel(picked)) {
|
|
618
679
|
p.cancel("Cancelled");
|
|
619
680
|
process.exit(0);
|
|
620
681
|
}
|
|
621
|
-
|
|
622
|
-
agentsToLink = agentSelection.includes(SELECT_ALL_AGENTS) ? filteredAgents : agentSelection.filter((a) => a !== SELECT_ALL_AGENTS);
|
|
682
|
+
agentsToLink = picked;
|
|
623
683
|
if (agentsToLink.length === 0) {
|
|
624
684
|
p.cancel("No agents selected");
|
|
625
685
|
process.exit(0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onezhao/skill-sync",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Sync skills from .agents/skills to agent-specific directories",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"author": "Your Name <your@email.com>",
|
|
42
42
|
"license": "MIT",
|
|
43
43
|
"dependencies": {
|
|
44
|
+
"@clack/core": "^1.1.0",
|
|
44
45
|
"@clack/prompts": "^0.11.0",
|
|
45
46
|
"gray-matter": "^4.0.3",
|
|
46
47
|
"picocolors": "^1.1.1",
|