@bud-fe/skills 0.0.2 → 0.0.3

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.
Files changed (3) hide show
  1. package/README.md +18 -9
  2. package/dist/cli.mjs +207 -83
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,19 +1,28 @@
1
1
  # skills-x
2
2
 
3
- The CLI for the open agent skills, commands ecosystem.
3
+ Forked from [vercel-labs/skills](https://github.com/vercel-labs/skills).
4
+
5
+ ## Changes based on upstream
6
+
7
+ ### ADDED
4
8
 
5
- Forked from [vercel-labs/skills](https://github.com/vercel-labs/skills), **recent spec changes**:
9
+ - `add-c` 命令:支持按名称从默认源的 `commands/` 目录安装 slash command 文件
6
10
 
7
- - `skills add` now supports implicit default-registry source resolution when flags are provided without a source (for example `--list` and `--all`).
8
- - `skills add` maps simple identifiers (for example `code-review`) to the curated default registry, while explicit sources (`owner/repo`, URLs, local paths) keep their existing behavior.
9
- - `skills add-c` installs command markdown files from the default registry `commands/` directory by command name.
10
- - `skills find` is provider-scoped:
11
- - default mode searches the curated registry
12
- - `-g` / `--global` searches [skills.sh](https://skills.sh)
13
- - the CLI does not silently fall back to another provider if the selected provider fails
11
+ ### MODIFIED
12
+
13
+ - `add` 命令
14
+ - 支持在未提供源但携带标志(如 `--list` `--all`)时,自动解析至默认源
15
+ - 将简单标识符(如 `code-review`)映射到默认源,而显式源(`owner/repo`、URL、本地路径)保持原有行为
16
+ - `find` 命令
17
+ - 无参数时默认从默认源搜索
18
+ - `-g` / `--global` 搜索 [skills.sh](https://skills.sh)(即原逻辑)
19
+ - `check` 和 `update`
20
+ - 支持非 GitHub 源(如 GitLab、通用 Git),采用基于内容的内容哈希(SHA-256)进行更新检测
14
21
 
15
22
  ---
16
23
 
24
+ The CLI for the open agent skills, commands ecosystem.
25
+
17
26
  <!-- agent-list:start -->
18
27
 
19
28
  Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [37 more](#available-agents).
package/dist/cli.mjs CHANGED
@@ -19,7 +19,7 @@ import { access, cp, lstat, mkdir, mkdtemp, readFile, readdir, readlink, realpat
19
19
  import * as readline from "readline";
20
20
  import { Writable } from "stream";
21
21
  var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
22
- var version$1 = "0.0.2";
22
+ var version$1 = "0.0.3";
23
23
  const home = homedir();
24
24
  const configHome = xdgConfig ?? join(home, ".config");
25
25
  const codexHome = process.env.CODEX_HOME?.trim() || join(home, ".codex");
@@ -104,6 +104,8 @@ const agents = {
104
104
  displayName: "Codex",
105
105
  skillsDir: ".agents/skills",
106
106
  globalSkillsDir: join(codexHome, "skills"),
107
+ commandsDir: ".codex/prompts",
108
+ globalCommandsDir: join(codexHome, "prompts"),
107
109
  detectInstalled: async () => {
108
110
  return existsSync(codexHome) || existsSync("/etc/codex");
109
111
  }
@@ -2322,6 +2324,33 @@ async function selectAgentsInteractive(options) {
2322
2324
  return selected;
2323
2325
  }
2324
2326
  setVersion(version$1);
2327
+ async function resolveGlobalLockHashData(params, deps = {
2328
+ fetchGitHubHash: fetchSkillFolderHash,
2329
+ computeFolderHash: computeSkillFolderHash
2330
+ }) {
2331
+ if (params.sourceType === "github") {
2332
+ let skillFolderHash = "";
2333
+ if (params.normalizedSource && params.skillPathValue) try {
2334
+ const hash = await deps.fetchGitHubHash(params.normalizedSource, params.skillPathValue);
2335
+ if (hash) skillFolderHash = hash;
2336
+ } catch {}
2337
+ return {
2338
+ skillFolderHash,
2339
+ hashMethod: "github-tree-sha"
2340
+ };
2341
+ }
2342
+ if (params.sourceType === "git" || params.sourceType === "gitlab") {
2343
+ let skillFolderHash = "";
2344
+ if (params.installDir) try {
2345
+ skillFolderHash = await deps.computeFolderHash(params.installDir);
2346
+ } catch {}
2347
+ return {
2348
+ skillFolderHash,
2349
+ hashMethod: "content-sha256"
2350
+ };
2351
+ }
2352
+ return { skillFolderHash: "" };
2353
+ }
2325
2354
  async function handleRemoteSkill(source, url, options, spinner) {
2326
2355
  const provider = findProvider(url);
2327
2356
  if (!provider) {
@@ -2494,7 +2523,9 @@ async function handleRemoteSkill(source, url, options, spinner) {
2494
2523
  });
2495
2524
  if (successful.length > 0 && installGlobally) try {
2496
2525
  let skillFolderHash = "";
2526
+ let hashMethod;
2497
2527
  if (remoteSkill.providerId === "github") {
2528
+ hashMethod = "github-tree-sha";
2498
2529
  const hash = await fetchSkillFolderHash(remoteSkill.sourceIdentifier, url);
2499
2530
  if (hash) skillFolderHash = hash;
2500
2531
  }
@@ -2502,7 +2533,8 @@ async function handleRemoteSkill(source, url, options, spinner) {
2502
2533
  source: remoteSkill.sourceIdentifier,
2503
2534
  sourceType: remoteSkill.providerId,
2504
2535
  sourceUrl: url,
2505
- skillFolderHash
2536
+ skillFolderHash,
2537
+ hashMethod
2506
2538
  });
2507
2539
  } catch {}
2508
2540
  if (successful.length > 0 && !installGlobally) try {
@@ -3329,18 +3361,22 @@ async function runAdd(args, options = {}) {
3329
3361
  for (const skill of selectedSkills) {
3330
3362
  const skillDisplayName = getSkillDisplayName(skill);
3331
3363
  if (successfulSkillNames.has(skillDisplayName)) try {
3332
- let skillFolderHash = "";
3333
3364
  const skillPathValue = skillFiles[skill.name];
3334
- if (parsed.type === "github" && skillPathValue) {
3335
- const hash = await fetchSkillFolderHash(normalizedSource, skillPathValue);
3336
- if (hash) skillFolderHash = hash;
3337
- }
3365
+ const matchingResult = successful.find((r) => r.skill === skillDisplayName);
3366
+ const installDir = matchingResult?.canonicalPath || matchingResult?.path;
3367
+ const { skillFolderHash, hashMethod } = await resolveGlobalLockHashData({
3368
+ sourceType: parsed.type,
3369
+ normalizedSource,
3370
+ skillPathValue,
3371
+ installDir
3372
+ });
3338
3373
  await addSkillToLock(skill.name, {
3339
3374
  source: normalizedSource,
3340
3375
  sourceType: parsed.type,
3341
3376
  sourceUrl: parsed.url,
3342
3377
  skillPath: skillPathValue,
3343
- skillFolderHash
3378
+ skillFolderHash,
3379
+ hashMethod
3344
3380
  });
3345
3381
  } catch {}
3346
3382
  }
@@ -4826,6 +4862,142 @@ function parseRemoveOptions(args) {
4826
4862
  options
4827
4863
  };
4828
4864
  }
4865
+ const DEFAULT_DEPS = {
4866
+ fetchGitHubHash: fetchSkillFolderHash,
4867
+ cloneRepo,
4868
+ cleanupTempDir,
4869
+ computeFolderHash: computeSkillFolderHash,
4870
+ pathExists: existsSync
4871
+ };
4872
+ function normalizeSkillFolderPath(skillPath) {
4873
+ let folderPath = skillPath.replace(/\\/g, "/");
4874
+ if (folderPath.endsWith("/SKILL.md")) folderPath = folderPath.slice(0, -9);
4875
+ else if (folderPath.endsWith("SKILL.md")) folderPath = folderPath.slice(0, -8);
4876
+ if (folderPath.endsWith("/")) folderPath = folderPath.slice(0, -1);
4877
+ return folderPath;
4878
+ }
4879
+ function resolveHashMethod(entry) {
4880
+ if (entry.hashMethod === "github-tree-sha" || entry.hashMethod === "content-sha256") return entry.hashMethod;
4881
+ if (entry.sourceType === "github") return "github-tree-sha";
4882
+ if ((entry.sourceType === "git" || entry.sourceType === "gitlab") && !!entry.skillFolderHash) return "content-sha256";
4883
+ return null;
4884
+ }
4885
+ async function detectSkillUpdates(lock, options = {}) {
4886
+ const deps = {
4887
+ ...DEFAULT_DEPS,
4888
+ ...options.deps || {}
4889
+ };
4890
+ const githubSkillsBySource = /* @__PURE__ */ new Map();
4891
+ const contentSkillsBySourceUrl = /* @__PURE__ */ new Map();
4892
+ let skippedMissingMetadata = 0;
4893
+ for (const [name, entry] of Object.entries(lock.skills)) {
4894
+ const hashMethod = resolveHashMethod(entry);
4895
+ if (!hashMethod || !entry.skillPath || !entry.skillFolderHash) {
4896
+ skippedMissingMetadata++;
4897
+ continue;
4898
+ }
4899
+ if (hashMethod === "github-tree-sha") {
4900
+ const bucket = githubSkillsBySource.get(entry.source) || [];
4901
+ bucket.push({
4902
+ name,
4903
+ entry
4904
+ });
4905
+ githubSkillsBySource.set(entry.source, bucket);
4906
+ continue;
4907
+ }
4908
+ if (!entry.sourceUrl) {
4909
+ skippedMissingMetadata++;
4910
+ continue;
4911
+ }
4912
+ const bucket = contentSkillsBySourceUrl.get(entry.sourceUrl) || [];
4913
+ bucket.push({
4914
+ name,
4915
+ entry
4916
+ });
4917
+ contentSkillsBySourceUrl.set(entry.sourceUrl, bucket);
4918
+ }
4919
+ const updates = [];
4920
+ const errors = [];
4921
+ let checkedCount = 0;
4922
+ for (const [source, skills] of githubSkillsBySource) for (const { name, entry } of skills) {
4923
+ checkedCount++;
4924
+ try {
4925
+ const latestHash = await deps.fetchGitHubHash(source, entry.skillPath, options.githubToken);
4926
+ if (!latestHash) {
4927
+ errors.push({
4928
+ name,
4929
+ source,
4930
+ error: "Could not fetch from GitHub"
4931
+ });
4932
+ continue;
4933
+ }
4934
+ if (latestHash !== entry.skillFolderHash) updates.push({
4935
+ name,
4936
+ source,
4937
+ entry
4938
+ });
4939
+ } catch (err) {
4940
+ errors.push({
4941
+ name,
4942
+ source,
4943
+ error: err instanceof Error ? err.message : "Unknown error"
4944
+ });
4945
+ }
4946
+ }
4947
+ for (const [sourceUrl, skills] of contentSkillsBySourceUrl) {
4948
+ let tempDir = null;
4949
+ try {
4950
+ tempDir = await deps.cloneRepo(sourceUrl);
4951
+ } catch (err) {
4952
+ const message = err instanceof Error ? err.message : "Failed to clone source repository";
4953
+ for (const { name, entry } of skills) {
4954
+ checkedCount++;
4955
+ errors.push({
4956
+ name,
4957
+ source: entry.source,
4958
+ error: message
4959
+ });
4960
+ }
4961
+ continue;
4962
+ }
4963
+ try {
4964
+ for (const { name, entry } of skills) {
4965
+ checkedCount++;
4966
+ try {
4967
+ const folderPath = normalizeSkillFolderPath(entry.skillPath);
4968
+ const targetDir = folderPath ? join(tempDir, folderPath) : tempDir;
4969
+ if (!deps.pathExists(targetDir)) {
4970
+ errors.push({
4971
+ name,
4972
+ source: entry.source,
4973
+ error: `Skill path not found: ${folderPath}`
4974
+ });
4975
+ continue;
4976
+ }
4977
+ if (await deps.computeFolderHash(targetDir) !== entry.skillFolderHash) updates.push({
4978
+ name,
4979
+ source: entry.source,
4980
+ entry
4981
+ });
4982
+ } catch (err) {
4983
+ errors.push({
4984
+ name,
4985
+ source: entry.source,
4986
+ error: err instanceof Error ? err.message : "Unknown error"
4987
+ });
4988
+ }
4989
+ }
4990
+ } finally {
4991
+ await deps.cleanupTempDir(tempDir).catch(() => {});
4992
+ }
4993
+ }
4994
+ return {
4995
+ checkedCount,
4996
+ skippedMissingMetadata,
4997
+ updates,
4998
+ errors
4999
+ };
5000
+ }
4829
5001
  const __dirname = dirname(fileURLToPath(import.meta.url));
4830
5002
  function getVersion() {
4831
5003
  try {
@@ -5080,58 +5252,21 @@ async function runCheck(args = []) {
5080
5252
  console.log(`${TEXT}Checking for skill updates...${RESET}`);
5081
5253
  console.log();
5082
5254
  const lock = readSkillLock();
5083
- const skillNames = Object.keys(lock.skills);
5084
- if (skillNames.length === 0) {
5255
+ if (Object.keys(lock.skills).length === 0) {
5085
5256
  console.log(`${DIM}No skills tracked in lock file.${RESET}`);
5086
5257
  console.log(`${DIM}Install skills with${RESET} ${TEXT}npx skills add <package>${RESET}`);
5087
5258
  return;
5088
5259
  }
5089
- const token = getGitHubToken();
5090
- const skillsBySource = /* @__PURE__ */ new Map();
5091
- let skippedCount = 0;
5092
- for (const skillName of skillNames) {
5093
- const entry = lock.skills[skillName];
5094
- if (!entry) continue;
5095
- if (entry.sourceType !== "github" || !entry.skillFolderHash || !entry.skillPath) {
5096
- skippedCount++;
5097
- continue;
5098
- }
5099
- const existing = skillsBySource.get(entry.source) || [];
5100
- existing.push({
5101
- name: skillName,
5102
- entry
5103
- });
5104
- skillsBySource.set(entry.source, existing);
5105
- }
5106
- const totalSkills = skillNames.length - skippedCount;
5260
+ const result = await detectSkillUpdates(lock, { githubToken: getGitHubToken() });
5261
+ const totalSkills = result.checkedCount;
5107
5262
  if (totalSkills === 0) {
5108
- console.log(`${DIM}No GitHub skills to check.${RESET}`);
5263
+ console.log(`${DIM}No skills with update metadata to check.${RESET}`);
5264
+ if (result.skippedMissingMetadata > 0) console.log(`${DIM}${result.skippedMissingMetadata} skill(s) are missing hash metadata (run${RESET} ${TEXT}npx skills add <source> -g${RESET} ${DIM}to reinstall and track updates).${RESET}`);
5109
5265
  return;
5110
5266
  }
5111
5267
  console.log(`${DIM}Checking ${totalSkills} skill(s) for updates...${RESET}`);
5112
- const updates = [];
5113
- const errors = [];
5114
- for (const [source, skills] of skillsBySource) for (const { name, entry } of skills) try {
5115
- const latestHash = await fetchSkillFolderHash(source, entry.skillPath, token);
5116
- if (!latestHash) {
5117
- errors.push({
5118
- name,
5119
- source,
5120
- error: "Could not fetch from GitHub"
5121
- });
5122
- continue;
5123
- }
5124
- if (latestHash !== entry.skillFolderHash) updates.push({
5125
- name,
5126
- source
5127
- });
5128
- } catch (err) {
5129
- errors.push({
5130
- name,
5131
- source,
5132
- error: err instanceof Error ? err.message : "Unknown error"
5133
- });
5134
- }
5268
+ const updates = result.updates;
5269
+ const errors = result.errors;
5135
5270
  console.log();
5136
5271
  if (updates.length === 0) console.log(`${TEXT}✓ All skills are up to date${RESET}`);
5137
5272
  else {
@@ -5148,6 +5283,10 @@ async function runCheck(args = []) {
5148
5283
  console.log();
5149
5284
  console.log(`${DIM}Could not check ${errors.length} skill(s) (may need reinstall)${RESET}`);
5150
5285
  }
5286
+ if (result.skippedMissingMetadata > 0) {
5287
+ console.log();
5288
+ console.log(`${DIM}Skipped ${result.skippedMissingMetadata} skill(s) missing hash metadata (reinstall to enable update tracking).${RESET}`);
5289
+ }
5151
5290
  track({
5152
5291
  event: "check",
5153
5292
  skillCount: String(totalSkills),
@@ -5159,31 +5298,16 @@ async function runUpdate() {
5159
5298
  console.log(`${TEXT}Checking for skill updates...${RESET}`);
5160
5299
  console.log();
5161
5300
  const lock = readSkillLock();
5162
- const skillNames = Object.keys(lock.skills);
5163
- if (skillNames.length === 0) {
5301
+ if (Object.keys(lock.skills).length === 0) {
5164
5302
  console.log(`${DIM}No skills tracked in lock file.${RESET}`);
5165
5303
  console.log(`${DIM}Install skills with${RESET} ${TEXT}npx skills add <package>${RESET}`);
5166
5304
  return;
5167
5305
  }
5168
- const token = getGitHubToken();
5169
- const updates = [];
5170
- let checkedCount = 0;
5171
- for (const skillName of skillNames) {
5172
- const entry = lock.skills[skillName];
5173
- if (!entry) continue;
5174
- if (entry.sourceType !== "github" || !entry.skillFolderHash || !entry.skillPath) continue;
5175
- checkedCount++;
5176
- try {
5177
- const latestHash = await fetchSkillFolderHash(entry.source, entry.skillPath, token);
5178
- if (latestHash && latestHash !== entry.skillFolderHash) updates.push({
5179
- name: skillName,
5180
- source: entry.source,
5181
- entry
5182
- });
5183
- } catch {}
5184
- }
5185
- if (checkedCount === 0) {
5186
- console.log(`${DIM}No skills to check.${RESET}`);
5306
+ const result = await detectSkillUpdates(lock, { githubToken: getGitHubToken() });
5307
+ const updates = result.updates;
5308
+ if (result.checkedCount === 0) {
5309
+ console.log(`${DIM}No skills with update metadata to check.${RESET}`);
5310
+ if (result.skippedMissingMetadata > 0) console.log(`${DIM}${result.skippedMissingMetadata} skill(s) are missing hash metadata (reinstall to enable update tracking).${RESET}`);
5187
5311
  return;
5188
5312
  }
5189
5313
  if (updates.length === 0) {
@@ -5198,22 +5322,21 @@ async function runUpdate() {
5198
5322
  for (const update of updates) {
5199
5323
  console.log(`${TEXT}Updating ${update.name}...${RESET}`);
5200
5324
  let installUrl = update.entry.sourceUrl;
5201
- if (update.entry.skillPath) {
5325
+ const addArgs = [
5326
+ "-y",
5327
+ "skills",
5328
+ "add"
5329
+ ];
5330
+ if (update.entry.sourceType === "github" && update.entry.skillPath) {
5202
5331
  let skillFolder = update.entry.skillPath;
5203
5332
  if (skillFolder.endsWith("/SKILL.md")) skillFolder = skillFolder.slice(0, -9);
5204
5333
  else if (skillFolder.endsWith("SKILL.md")) skillFolder = skillFolder.slice(0, -8);
5205
5334
  if (skillFolder.endsWith("/")) skillFolder = skillFolder.slice(0, -1);
5206
5335
  installUrl = update.entry.sourceUrl.replace(/\.git$/, "").replace(/\/$/, "");
5207
5336
  installUrl = `${installUrl}/tree/main/${skillFolder}`;
5208
- }
5209
- if (spawnSync("npx", [
5210
- "-y",
5211
- "skills",
5212
- "add",
5213
- installUrl,
5214
- "-g",
5215
- "-y"
5216
- ], { stdio: [
5337
+ addArgs.push(installUrl, "-g", "-y");
5338
+ } else addArgs.push(installUrl, "--skill", update.name, "-g", "-y");
5339
+ if (spawnSync("npx", addArgs, { stdio: [
5217
5340
  "inherit",
5218
5341
  "pipe",
5219
5342
  "pipe"
@@ -5228,6 +5351,7 @@ async function runUpdate() {
5228
5351
  console.log();
5229
5352
  if (successCount > 0) console.log(`${TEXT}✓ Updated ${successCount} skill(s)${RESET}`);
5230
5353
  if (failCount > 0) console.log(`${DIM}Failed to update ${failCount} skill(s)${RESET}`);
5354
+ if (result.skippedMissingMetadata > 0) console.log(`${DIM}Skipped ${result.skippedMissingMetadata} skill(s) missing hash metadata (reinstall to enable update tracking).${RESET}`);
5231
5355
  track({
5232
5356
  event: "update",
5233
5357
  skillCount: String(updates.length),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bud-fe/skills",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "The open agent skills ecosystem",
5
5
  "type": "module",
6
6
  "bin": {