@yonpark/skillhub-cli 0.3.0 → 0.3.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/README.md +1 -18
- package/dist/service/skillsService.js +76 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -70,24 +70,6 @@ Mode behavior:
|
|
|
70
70
|
- `merge`: union local + remote, installs missing local skills, uploads when remote differs.
|
|
71
71
|
- `auto`: compare `remote.updatedAt` and local `lastSyncAt`; install from remote when remote is newer, otherwise upload local when needed.
|
|
72
72
|
|
|
73
|
-
## Migration (Breaking Changes in 0.3.0)
|
|
74
|
-
|
|
75
|
-
Removed commands:
|
|
76
|
-
|
|
77
|
-
- `skillhub login`
|
|
78
|
-
- `skillhub status`
|
|
79
|
-
- `skillhub logout`
|
|
80
|
-
- `skillhub sync --strategy ...`
|
|
81
|
-
- `skillhub sync` (without a subcommand)
|
|
82
|
-
|
|
83
|
-
New mapping:
|
|
84
|
-
|
|
85
|
-
- `skillhub login` -> `skillhub auth login`
|
|
86
|
-
- `skillhub status` -> `skillhub auth status`
|
|
87
|
-
- `skillhub logout` -> `skillhub auth logout`
|
|
88
|
-
- `skillhub sync --strategy union` -> `skillhub sync merge`
|
|
89
|
-
- `skillhub sync --strategy latest` -> `skillhub sync auto`
|
|
90
|
-
|
|
91
73
|
## Payload Format
|
|
92
74
|
|
|
93
75
|
`skillhub.json` in Gist:
|
|
@@ -134,3 +116,4 @@ CI release workflow publishes to GitHub Packages only:
|
|
|
134
116
|
- GitHub Packages target package is `@yw9142/skillhub-cli`.
|
|
135
117
|
|
|
136
118
|
npmjs publish is manual.
|
|
119
|
+
|
|
@@ -15,7 +15,7 @@ const node_util_1 = require("node:util");
|
|
|
15
15
|
const syncCore_1 = require("../core/syncCore");
|
|
16
16
|
const retry_1 = require("../utils/retry");
|
|
17
17
|
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
18
|
-
const
|
|
18
|
+
const SKILLS_LOCK_FILENAMES = ["skills-lock.json", ".skill-lock.json"];
|
|
19
19
|
const DEFAULT_SKILL_SOURCE_REPO = "vercel-labs/agent-skills";
|
|
20
20
|
const NPX_COMMAND = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
21
21
|
const SOURCE_PATTERN = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
@@ -36,26 +36,31 @@ async function runSkillsCommand(args, label) {
|
|
|
36
36
|
function isValidSource(source) {
|
|
37
37
|
return SOURCE_PATTERN.test(source);
|
|
38
38
|
}
|
|
39
|
-
function getCandidateSkillsLockPaths() {
|
|
40
|
-
const cwdPath = node_path_1.default.resolve(process.cwd(),
|
|
41
|
-
const homePath = node_path_1.default.resolve(node_os_1.default.homedir(),
|
|
39
|
+
function getCandidateSkillsLockPaths(filename) {
|
|
40
|
+
const cwdPath = node_path_1.default.resolve(process.cwd(), filename);
|
|
41
|
+
const homePath = node_path_1.default.resolve(node_os_1.default.homedir(), filename);
|
|
42
42
|
const homeConfigPaths = [
|
|
43
|
-
node_path_1.default.resolve(node_os_1.default.homedir(), ".config", "skills",
|
|
44
|
-
node_path_1.default.resolve(node_os_1.default.homedir(), ".config", "skillhub",
|
|
45
|
-
node_path_1.default.resolve(node_os_1.default.homedir(), ".skills",
|
|
43
|
+
node_path_1.default.resolve(node_os_1.default.homedir(), ".config", "skills", filename),
|
|
44
|
+
node_path_1.default.resolve(node_os_1.default.homedir(), ".config", "skillhub", filename),
|
|
45
|
+
node_path_1.default.resolve(node_os_1.default.homedir(), ".skills", filename),
|
|
46
|
+
node_path_1.default.resolve(node_os_1.default.homedir(), ".agents", filename),
|
|
46
47
|
];
|
|
47
48
|
const winAppData = process.env.APPDATA;
|
|
48
49
|
const winLocalAppData = process.env.LOCALAPPDATA;
|
|
49
50
|
const windowsConfigPaths = [
|
|
50
51
|
...(winAppData
|
|
51
|
-
? [node_path_1.default.resolve(winAppData, "skills",
|
|
52
|
+
? [node_path_1.default.resolve(winAppData, "skills", filename)]
|
|
52
53
|
: []),
|
|
53
54
|
...(winLocalAppData
|
|
54
|
-
? [node_path_1.default.resolve(winLocalAppData, "skills",
|
|
55
|
+
? [node_path_1.default.resolve(winLocalAppData, "skills", filename)]
|
|
55
56
|
: []),
|
|
56
57
|
];
|
|
57
58
|
return [cwdPath, homePath, ...homeConfigPaths, ...windowsConfigPaths];
|
|
58
59
|
}
|
|
60
|
+
function getAllCandidateSkillsLockPaths() {
|
|
61
|
+
const paths = SKILLS_LOCK_FILENAMES.flatMap((filename) => getCandidateSkillsLockPaths(filename));
|
|
62
|
+
return [...new Set(paths)];
|
|
63
|
+
}
|
|
59
64
|
function parseSkillsLock(raw) {
|
|
60
65
|
const parsed = JSON.parse(raw);
|
|
61
66
|
const extractSkills = (items) => {
|
|
@@ -83,6 +88,21 @@ function parseSkillsLock(raw) {
|
|
|
83
88
|
if (Array.isArray(parsed?.skills)) {
|
|
84
89
|
return extractSkills(parsed.skills);
|
|
85
90
|
}
|
|
91
|
+
if (parsed?.skills && typeof parsed.skills === "object") {
|
|
92
|
+
const objectSkills = parsed.skills;
|
|
93
|
+
return Object.entries(objectSkills).map(([name, metadata]) => {
|
|
94
|
+
if (typeof metadata === "object" && metadata !== null) {
|
|
95
|
+
const objectItem = metadata;
|
|
96
|
+
const source = typeof objectItem.source === "string"
|
|
97
|
+
? objectItem.source
|
|
98
|
+
: typeof objectItem.repo === "string"
|
|
99
|
+
? objectItem.repo
|
|
100
|
+
: DEFAULT_SKILL_SOURCE_REPO;
|
|
101
|
+
return { name, source };
|
|
102
|
+
}
|
|
103
|
+
return { name, source: DEFAULT_SKILL_SOURCE_REPO };
|
|
104
|
+
});
|
|
105
|
+
}
|
|
86
106
|
if (Array.isArray(parsed)) {
|
|
87
107
|
return extractSkills(parsed);
|
|
88
108
|
}
|
|
@@ -128,6 +148,45 @@ async function tryReadSkillsLock(lockPath) {
|
|
|
128
148
|
return null;
|
|
129
149
|
}
|
|
130
150
|
}
|
|
151
|
+
async function readFirstAvailableSkillsLock() {
|
|
152
|
+
const candidatePaths = getAllCandidateSkillsLockPaths();
|
|
153
|
+
for (const lockPath of candidatePaths) {
|
|
154
|
+
const parsed = await tryReadSkillsLock(lockPath);
|
|
155
|
+
if (parsed && parsed.length > 0) {
|
|
156
|
+
return {
|
|
157
|
+
skills: (0, syncCore_1.normalizeSkills)(parsed),
|
|
158
|
+
candidatePaths,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
skills: [],
|
|
164
|
+
candidatePaths,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function hydrateSourcesFromLock(listSkills, lockSkills) {
|
|
168
|
+
const lockByName = new Map();
|
|
169
|
+
for (const skill of lockSkills) {
|
|
170
|
+
const existing = lockByName.get(skill.name);
|
|
171
|
+
if (existing) {
|
|
172
|
+
existing.push(skill);
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
lockByName.set(skill.name, [skill]);
|
|
176
|
+
}
|
|
177
|
+
const hydrated = listSkills.map((skill) => {
|
|
178
|
+
const matches = lockByName.get(skill.name);
|
|
179
|
+
if (!matches || matches.length === 0) {
|
|
180
|
+
return skill;
|
|
181
|
+
}
|
|
182
|
+
const preferred = matches.find((item) => isValidSource(item.source)) ?? matches[0];
|
|
183
|
+
return {
|
|
184
|
+
name: skill.name,
|
|
185
|
+
source: preferred.source,
|
|
186
|
+
};
|
|
187
|
+
});
|
|
188
|
+
return (0, syncCore_1.normalizeSkills)(hydrated);
|
|
189
|
+
}
|
|
131
190
|
async function getLocalSkills() {
|
|
132
191
|
const listResult = await runSkillsCommand(["list", "-g"], "skills list -g");
|
|
133
192
|
const listOutput = stringifyCommandResult(listResult);
|
|
@@ -136,32 +195,23 @@ async function getLocalSkills() {
|
|
|
136
195
|
return [];
|
|
137
196
|
}
|
|
138
197
|
const fromList = parseSkillsListOutput(listOutput);
|
|
198
|
+
const fromLock = await readFirstAvailableSkillsLock();
|
|
139
199
|
if (fromList.length > 0) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const lockResult = await runSkillsCommand(["generate-lock"], "skills generate-lock");
|
|
143
|
-
const lockOutput = stringifyCommandResult(lockResult);
|
|
144
|
-
const candidatePaths = getCandidateSkillsLockPaths();
|
|
145
|
-
for (const lockPath of candidatePaths) {
|
|
146
|
-
const parsed = await tryReadSkillsLock(lockPath);
|
|
147
|
-
if (parsed) {
|
|
148
|
-
return (0, syncCore_1.normalizeSkills)(parsed);
|
|
200
|
+
if (fromLock.skills.length > 0) {
|
|
201
|
+
return hydrateSourcesFromLock(fromList, fromLock.skills);
|
|
149
202
|
}
|
|
203
|
+
return fromList;
|
|
150
204
|
}
|
|
151
|
-
if (
|
|
152
|
-
|
|
153
|
-
return [];
|
|
205
|
+
if (fromLock.skills.length > 0) {
|
|
206
|
+
return fromLock.skills;
|
|
154
207
|
}
|
|
155
208
|
throw new Error([
|
|
156
209
|
"Unable to construct local skills list.",
|
|
157
210
|
"- skills list -g output:",
|
|
158
211
|
listOutput,
|
|
159
212
|
"",
|
|
160
|
-
|
|
161
|
-
...candidatePaths.map((p) => ` - ${p}`),
|
|
162
|
-
"",
|
|
163
|
-
"- npx skills generate-lock output:",
|
|
164
|
-
lockOutput,
|
|
213
|
+
"- Searched lock paths:",
|
|
214
|
+
...fromLock.candidatePaths.map((p) => ` - ${p}`),
|
|
165
215
|
].join("\n"));
|
|
166
216
|
}
|
|
167
217
|
async function installSkills(skills, options = {}) {
|