@onezhao/skill-sync 1.0.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 -45
- 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,25 +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 skillChoices = skills.map((s) => ({
|
|
549
|
-
value: s,
|
|
550
|
-
label: s.name,
|
|
551
|
-
hint: s.description.length > 50 ? s.description.slice(0, 47) + "..." : s.description
|
|
552
|
-
}));
|
|
553
622
|
let selectedSkills;
|
|
554
623
|
if (options.yes) {
|
|
555
624
|
selectedSkills = skills;
|
|
556
625
|
p.log.info(`Selected all ${skills.length} skills`);
|
|
557
626
|
} else {
|
|
558
|
-
|
|
627
|
+
const picked = await multiselectAll({
|
|
559
628
|
message: "Select skills to sync",
|
|
560
|
-
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
|
+
})),
|
|
561
634
|
required: true
|
|
562
635
|
});
|
|
563
|
-
if (p.isCancel(
|
|
636
|
+
if (p.isCancel(picked)) {
|
|
564
637
|
p.cancel("Cancelled");
|
|
565
638
|
process.exit(0);
|
|
566
639
|
}
|
|
640
|
+
selectedSkills = picked;
|
|
567
641
|
}
|
|
568
642
|
if (selectedSkills.length === 0) {
|
|
569
643
|
p.cancel("No skills selected");
|
|
@@ -592,21 +666,20 @@ async function runSkillSync(args = []) {
|
|
|
592
666
|
agentsToLink = filteredAgents;
|
|
593
667
|
p.log.info(`Selected all ${filteredAgents.length} available agents`);
|
|
594
668
|
} else {
|
|
595
|
-
const
|
|
596
|
-
value: a,
|
|
597
|
-
label: a.displayName,
|
|
598
|
-
hint: a.isInstalled ? `${a.skillsDir} ${pc.green("(installed)")}` : `${a.skillsDir} ${pc.yellow("(not detected)")}`
|
|
599
|
-
}));
|
|
600
|
-
const selectedAgents = await p.multiselect({
|
|
669
|
+
const picked = await multiselectAll({
|
|
601
670
|
message: "Select target agents",
|
|
602
|
-
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
|
+
})),
|
|
603
676
|
required: true
|
|
604
677
|
});
|
|
605
|
-
if (p.isCancel(
|
|
678
|
+
if (p.isCancel(picked)) {
|
|
606
679
|
p.cancel("Cancelled");
|
|
607
680
|
process.exit(0);
|
|
608
681
|
}
|
|
609
|
-
agentsToLink =
|
|
682
|
+
agentsToLink = picked;
|
|
610
683
|
if (agentsToLink.length === 0) {
|
|
611
684
|
p.cancel("No agents selected");
|
|
612
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",
|