ali-skills 0.0.7-beta.0 → 0.0.8

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.
@@ -450,14 +450,12 @@ async function getGitLabProject(ownerRepo, token) {
450
450
  return null;
451
451
  }
452
452
  }
453
- async function getSkillBlameAuthor(projectId, filePath, ref, token) {
453
+ async function getSkillLastCommitAuthor(projectId, filePath, token) {
454
454
  try {
455
- const url = `${CODE_HOST}/api/v3/projects/${projectId}/repository/files/blame?file_path=${encodeURIComponent(filePath)}&ref=${encodeURIComponent(ref)}&private_token=${token}&projectId=${projectId}`;
455
+ const url = `${CODE_HOST}/api/v3/projects/${projectId}/repository/files/last_commit?filepath=${encodeURIComponent(filePath)}&sha=HEAD&private_token=${token}&projectId=${projectId}`;
456
456
  const res = await fetch(url);
457
457
  if (!res.ok) return null;
458
- const entries = await res.json();
459
- if (!Array.isArray(entries) || entries.length === 0) return null;
460
- const author = entries[0]?.commit?.user;
458
+ const author = (await res.json())?.user;
461
459
  if (!author) return null;
462
460
  return {
463
461
  employeeId: author.extern_uid ?? "",
@@ -467,7 +465,7 @@ async function getSkillBlameAuthor(projectId, filePath, ref, token) {
467
465
  username: author.username ?? ""
468
466
  };
469
467
  } catch (error) {
470
- console.error("[GitLab] Failed to get skill blame author:", error);
468
+ console.error("[GitLab] Failed to get skill last commit author:", error);
471
469
  return null;
472
470
  }
473
471
  }
@@ -618,27 +616,74 @@ async function hasSkillMd(dir) {
618
616
  return false;
619
617
  }
620
618
  }
619
+ function emitParseFailure(sink, failure) {
620
+ sink?.(failure);
621
+ }
621
622
  async function parseSkillMd(skillMdPath, options) {
623
+ const onFailure = options?.onParseFailure;
624
+ let content;
622
625
  try {
623
- const content = await readFile(skillMdPath, "utf-8");
624
- const { data } = (0, import_gray_matter.default)(content);
625
- if (!data.name || !data.description) return null;
626
- if (typeof data.name !== "string" || typeof data.description !== "string") return null;
627
- if (!isValidSkillName(data.name)) {
628
- const suggested = data.name.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^[^a-z]+/, "").replace(/-+$/g, "");
629
- console.warn(`⚠ Skill "${data.name}" 的名称不符合命名规范,将无法在技能市场中展示。\n 规范要求:以英文字母开头,仅允许小写字母(a-z)、数字(0-9)、连字符(-)、下划线(_)。\n 建议修改为:${suggested || "my-skill"}\n 请联系 Skill 开发者修改 SKILL.md 中的 name 字段。`);
630
- }
631
- if (data.metadata?.internal === true && !shouldInstallInternalSkills() && !options?.includeInternal) return null;
632
- return {
633
- name: data.name,
634
- description: data.description,
635
- path: dirname(skillMdPath),
636
- rawContent: content,
637
- metadata: data.metadata
638
- };
639
- } catch {
626
+ content = await readFile(skillMdPath, "utf-8");
627
+ } catch (err) {
628
+ emitParseFailure(onFailure, {
629
+ path: skillMdPath,
630
+ code: "read_error",
631
+ message: err instanceof Error ? err.message : String(err)
632
+ });
633
+ return null;
634
+ }
635
+ let data;
636
+ try {
637
+ ({data} = (0, import_gray_matter.default)(content));
638
+ } catch (err) {
639
+ emitParseFailure(onFailure, {
640
+ path: skillMdPath,
641
+ code: "yaml_parse",
642
+ message: err instanceof Error ? err.message : String(err),
643
+ hint: "YAML frontmatter 语法无效。值里含 []、:、#、引号等时请用单引号或双引号把整个标量包起来"
644
+ });
640
645
  return null;
641
646
  }
647
+ if (!data.name || !data.description) {
648
+ const missing = [];
649
+ if (!data.name) missing.push("name");
650
+ if (!data.description) missing.push("description");
651
+ emitParseFailure(onFailure, {
652
+ path: skillMdPath,
653
+ code: "missing_frontmatter_fields",
654
+ message: `缺少必填 frontmatter 字段: ${missing.join(", ")}`
655
+ });
656
+ return null;
657
+ }
658
+ if (typeof data.name !== "string" || typeof data.description !== "string") {
659
+ emitParseFailure(onFailure, {
660
+ path: skillMdPath,
661
+ code: "wrong_field_types",
662
+ message: `name 与 description 必须为字符串(当前 name 为 ${typeof data.name},description 为 ${typeof data.description})`,
663
+ hint: "在 YAML 里给值加引号,例如 name: \"my-skill\" 或 description: \"说明文字\""
664
+ });
665
+ return null;
666
+ }
667
+ if (!isValidSkillName(data.name)) {
668
+ const suggested = data.name.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^[^a-z]+/, "").replace(/-+$/g, "");
669
+ console.warn(`⚠ Skill "${data.name}" 的名称不符合命名规范,将无法在技能市场中展示。\n 规范要求:以英文字母开头,仅允许小写字母(a-z)、数字(0-9)、连字符(-)、下划线(_)。\n 建议修改为:${suggested || "my-skill"}\n 请联系 Skill 开发者修改 SKILL.md 中的 name 字段。`);
670
+ }
671
+ if (data.metadata?.internal === true && !shouldInstallInternalSkills() && !options?.includeInternal) {
672
+ emitParseFailure(onFailure, {
673
+ path: skillMdPath,
674
+ code: "skipped_internal",
675
+ message: "该 skill 在 frontmatter 中标记为 internal,默认不参与安装",
676
+ hint: "设置环境变量 INSTALL_INTERNAL_SKILLS=1 或使用 --skill 显式指定要安装的技能名"
677
+ });
678
+ return null;
679
+ }
680
+ return {
681
+ name: data.name,
682
+ description: data.description,
683
+ path: dirname(skillMdPath),
684
+ rawContent: content,
685
+ metadata: data.metadata
686
+ };
642
687
  }
643
688
  async function findSkillDirs(dir, depth = 0, maxDepth = 5) {
644
689
  if (depth > maxDepth) return [];
@@ -659,6 +704,19 @@ function isSubpathSafe(basePath, subpath) {
659
704
  async function discoverSkills(basePath, subpath, options) {
660
705
  const skills = [];
661
706
  const seenNames = /* @__PURE__ */ new Set();
707
+ const failureSink = options?.parseFailures;
708
+ const seenFailureKeys = /* @__PURE__ */ new Set();
709
+ const onParseFailure = (failure) => {
710
+ if (!failureSink) return;
711
+ const key = `${failure.code}\0${failure.path}`;
712
+ if (seenFailureKeys.has(key)) return;
713
+ seenFailureKeys.add(key);
714
+ failureSink.push(failure);
715
+ };
716
+ const mdOptions = {
717
+ includeInternal: options?.includeInternal,
718
+ onParseFailure
719
+ };
662
720
  if (subpath && !isSubpathSafe(basePath, subpath)) throw new Error(`Invalid subpath: "${subpath}" resolves outside the repository directory. Subpath must not contain ".." segments that escape the base path.`);
663
721
  const searchPath = subpath ? join(basePath, subpath) : basePath;
664
722
  const pluginGroupings = await getPluginGroupings(searchPath);
@@ -668,7 +726,7 @@ async function discoverSkills(basePath, subpath, options) {
668
726
  return skill;
669
727
  };
670
728
  if (await hasSkillMd(searchPath)) {
671
- let skill = await parseSkillMd(join(searchPath, "SKILL.md"), options);
729
+ let skill = await parseSkillMd(join(searchPath, "SKILL.md"), mdOptions);
672
730
  if (skill) {
673
731
  skill = enhanceSkill(skill);
674
732
  skills.push(skill);
@@ -713,7 +771,7 @@ async function discoverSkills(basePath, subpath, options) {
713
771
  for (const entry of entries) if (entry.isDirectory()) {
714
772
  const skillDir = join(dir, entry.name);
715
773
  if (await hasSkillMd(skillDir)) {
716
- let skill = await parseSkillMd(join(skillDir, "SKILL.md"), options);
774
+ let skill = await parseSkillMd(join(skillDir, "SKILL.md"), mdOptions);
717
775
  if (skill && !seenNames.has(skill.name)) {
718
776
  skill = enhanceSkill(skill);
719
777
  skills.push(skill);
@@ -725,7 +783,7 @@ async function discoverSkills(basePath, subpath, options) {
725
783
  if (skills.length === 0 || options?.fullDepth) {
726
784
  const allSkillDirs = await findSkillDirs(searchPath);
727
785
  for (const skillDir of allSkillDirs) {
728
- let skill = await parseSkillMd(join(skillDir, "SKILL.md"), options);
786
+ let skill = await parseSkillMd(join(skillDir, "SKILL.md"), mdOptions);
729
787
  if (skill && !seenNames.has(skill.name)) {
730
788
  skill = enhanceSkill(skill);
731
789
  skills.push(skill);
@@ -1946,7 +2004,17 @@ function createEmptyLocalLock() {
1946
2004
  skills: {}
1947
2005
  };
1948
2006
  }
1949
- var version$1 = "2.0.4";
2007
+ function logSkillParseFailures(failures) {
2008
+ if (failures.length === 0) return;
2009
+ console.log();
2010
+ M.error(import_picocolors.default.bold("以下 SKILL.md 解析未通过(请按提示修复)"));
2011
+ for (const f of failures) {
2012
+ M.message(import_picocolors.default.dim(f.path));
2013
+ M.message(import_picocolors.default.red(` [${f.code}] ${f.message}`));
2014
+ if (f.hint) M.message(import_picocolors.default.yellow(` 提示: ${f.hint}`));
2015
+ }
2016
+ }
2017
+ var version$1 = "2.0.6";
1950
2018
  const isCancelled$1 = (value) => typeof value === "symbol";
1951
2019
  async function isSourcePrivate(source) {
1952
2020
  const ownerRepo = parseOwnerRepo(source);
@@ -2521,11 +2589,14 @@ async function runAdd(args, options = {}) {
2521
2589
  if (!options.skill.includes(parsed.skillFilter)) options.skill.push(parsed.skillFilter);
2522
2590
  }
2523
2591
  const includeInternal = !!(options.skill && options.skill.length > 0);
2524
- spinner.start("Discovering skills...");
2525
- let skills = await discoverSkills(skillsDir, parsed.subpath, {
2592
+ const skillParseFailures = [];
2593
+ const discoverOpts = {
2526
2594
  includeInternal,
2527
- fullDepth: options.fullDepth
2528
- });
2595
+ fullDepth: options.fullDepth,
2596
+ parseFailures: skillParseFailures
2597
+ };
2598
+ spinner.start("Discovering skills...");
2599
+ let skills = await discoverSkills(skillsDir, parsed.subpath, discoverOpts);
2529
2600
  if (skills.length === 0 && !usedOssFallback && [
2530
2601
  "github",
2531
2602
  "internal",
@@ -2546,16 +2617,15 @@ async function runAdd(args, options = {}) {
2546
2617
  usedOssFallback = true;
2547
2618
  spinner.stop("Skill package downloaded");
2548
2619
  spinner.start("Discovering skills...");
2549
- skills = await discoverSkills(skillsDir, parsed.subpath, {
2550
- includeInternal,
2551
- fullDepth: options.fullDepth
2552
- });
2620
+ skillParseFailures.length = 0;
2621
+ skills = await discoverSkills(skillsDir, parsed.subpath, discoverOpts);
2553
2622
  }
2554
2623
  } catch {}
2555
2624
  }
2556
2625
  }
2557
2626
  if (skills.length === 0) {
2558
2627
  spinner.stop(import_picocolors.default.red("No skills found"));
2628
+ logSkillParseFailures(skillParseFailures);
2559
2629
  Se(import_picocolors.default.red("No valid skills found. Skills require a SKILL.md with name and description."));
2560
2630
  await cleanup(tempDir);
2561
2631
  process.exit(1);
@@ -2858,7 +2928,7 @@ async function runAdd(args, options = {}) {
2858
2928
  if (project) {
2859
2929
  const firstSkillPath = Object.values(skillFiles)[0];
2860
2930
  if (firstSkillPath) {
2861
- const author = await getSkillBlameAuthor(project.id, firstSkillPath, parsed.ref || project.defaultBranch, gitlabToken);
2931
+ const author = await getSkillLastCommitAuthor(project.id, firstSkillPath, gitlabToken);
2862
2932
  if (author) skillAuthor = author;
2863
2933
  }
2864
2934
  }
@@ -3069,6 +3139,7 @@ const BOLD$2 = "\x1B[1m";
3069
3139
  const DIM$2 = "\x1B[38;5;102m";
3070
3140
  const TEXT$1 = "\x1B[38;5;145m";
3071
3141
  const CYAN$1 = "\x1B[36m";
3142
+ const YELLOW$1 = "\x1B[33m";
3072
3143
  const SKILLS_API_URL = process.env.SKILLS_API_URL;
3073
3144
  const SEARCH_ENDPOINT = SKILLS_API_URL ? `${SKILLS_API_URL.replace(/\/+$/, "")}/search` : "https://next-ai-base.pre-fn.alibaba-inc.com/api/skills/search";
3074
3145
  function formatInstalls(count) {
@@ -3077,6 +3148,9 @@ function formatInstalls(count) {
3077
3148
  if (count >= 1e3) return `${(count / 1e3).toFixed(1).replace(/\.0$/, "")}K installs`;
3078
3149
  return `${count} install${count === 1 ? "" : "s"}`;
3079
3150
  }
3151
+ function buildSkillUrl(skill) {
3152
+ return `https://ali-skills.alibaba-inc.com/skills/${skill.source}/${skill.name}`;
3153
+ }
3080
3154
  async function searchSkillsAPI(query) {
3081
3155
  try {
3082
3156
  const url = `${SEARCH_ENDPOINT}?q=${encodeURIComponent(query)}&limit=10`;
@@ -3087,8 +3161,14 @@ async function searchSkillsAPI(query) {
3087
3161
  name: skill.name,
3088
3162
  slug: skill.id,
3089
3163
  source: skill.source || "",
3090
- installs: skill.installs
3091
- })).sort((a, b) => (b.installs || 0) - (a.installs || 0));
3164
+ installs: skill.installs,
3165
+ sourceType: skill.sourceType ?? "github"
3166
+ })).sort((a, b) => {
3167
+ const aIsInternal = a.sourceType === "internal" ? 1 : 0;
3168
+ const bIsInternal = b.sourceType === "internal" ? 1 : 0;
3169
+ if (bIsInternal !== aIsInternal) return bIsInternal - aIsInternal;
3170
+ return (b.installs || 0) - (a.installs || 0);
3171
+ });
3092
3172
  } catch {
3093
3173
  return [];
3094
3174
  }
@@ -3129,8 +3209,9 @@ async function runSearchPrompt(initialQuery = "") {
3129
3209
  const source = skill.source ? ` ${DIM$2}${skill.source}${RESET$2}` : "";
3130
3210
  const installs = formatInstalls(skill.installs);
3131
3211
  const installsBadge = installs ? ` ${CYAN$1}${installs}${RESET$2}` : "";
3212
+ const githubBadge = skill.sourceType === "github" ? ` ${YELLOW$1}GitHub${RESET$2}` : "";
3132
3213
  const loadingIndicator = loading && i === 0 ? ` ${DIM$2}...${RESET$2}` : "";
3133
- lines.push(` ${arrow} ${name}${source}${installsBadge}${loadingIndicator}`);
3214
+ lines.push(` ${arrow} ${name}${source}${githubBadge}${installsBadge}${loadingIndicator}`);
3134
3215
  }
3135
3216
  }
3136
3217
  lines.push("");
@@ -3215,18 +3296,6 @@ async function runSearchPrompt(initialQuery = "") {
3215
3296
  process.stdin.on("keypress", handleKeypress);
3216
3297
  });
3217
3298
  }
3218
- function getOwnerRepoFromString(pkg) {
3219
- const atIndex = pkg.lastIndexOf("@");
3220
- const match = (atIndex > 0 ? pkg.slice(0, atIndex) : pkg).match(/^([^/]+)\/([^/]+)$/);
3221
- if (match) return {
3222
- owner: match[1],
3223
- repo: match[2]
3224
- };
3225
- return null;
3226
- }
3227
- async function isRepoPublic(owner, repo) {
3228
- return await isRepoPrivate(owner, repo) === false;
3229
- }
3230
3299
  async function runFind(args) {
3231
3300
  const query = args.join(" ");
3232
3301
  const isNonInteractive = !process.stdin.isTTY;
@@ -3249,8 +3318,10 @@ ${DIM$2} 2) npx @ali/cli-skills add <owner/repo@skill>${RESET$2}`;
3249
3318
  for (const skill of results.slice(0, 6)) {
3250
3319
  const pkg = skill.source || skill.slug;
3251
3320
  const installs = formatInstalls(skill.installs);
3252
- console.log(`${TEXT$1}${pkg}@${skill.name}${RESET$2}${installs ? ` ${CYAN$1}${installs}${RESET$2}` : ""}`);
3253
- console.log(`${DIM$2}└ https://skills.sh/${skill.slug}${RESET$2}`);
3321
+ const githubBadge = skill.sourceType === "github" ? ` ${YELLOW$1}GitHub${RESET$2}` : "";
3322
+ console.log(`${TEXT$1}${pkg}@${skill.name}${RESET$2}${githubBadge}${installs ? ` ${CYAN$1}${installs}${RESET$2}` : ""}`);
3323
+ const skillUrl = buildSkillUrl(skill);
3324
+ console.log(`${DIM$2}└ ${skillUrl}${RESET$2}`);
3254
3325
  console.log();
3255
3326
  }
3256
3327
  return;
@@ -3283,9 +3354,8 @@ ${DIM$2} 2) npx @ali/cli-skills add <owner/repo@skill>${RESET$2}`;
3283
3354
  ]);
3284
3355
  await runAdd(source, options);
3285
3356
  console.log();
3286
- const info = getOwnerRepoFromString(pkg);
3287
- if (info && await isRepoPublic(info.owner, info.repo)) console.log(`${DIM$2}View the skill at${RESET$2} ${TEXT$1}https://skills.sh/${selected.slug}${RESET$2}`);
3288
- else console.log(`${DIM$2}Discover more skills at${RESET$2} ${TEXT$1}https://skills.sh${RESET$2}`);
3357
+ const skillUrl = buildSkillUrl(selected);
3358
+ console.log(`${DIM$2}View the skill at${RESET$2} ${TEXT$1}${skillUrl}${RESET$2}`);
3289
3359
  console.log();
3290
3360
  }
3291
3361
  const isCancelled = (value) => typeof value === "symbol";
@@ -4040,9 +4110,15 @@ async function runPublish(args, options = {}) {
4040
4110
  const source = `${repoInfo.owner}/${repoInfo.repo}`;
4041
4111
  spinner.stop(`Repository: ${import_picocolors.default.cyan(source)} ${import_picocolors.default.dim(`(${repoInfo.sourceType})`)}`);
4042
4112
  spinner.start("Discovering skills...");
4043
- const skills = await discoverSkills(process.cwd(), void 0, { includeInternal: true });
4113
+ const cwd = process.cwd();
4114
+ const skillParseFailures = [];
4115
+ const skills = await discoverSkills(cwd, void 0, {
4116
+ includeInternal: true,
4117
+ parseFailures: skillParseFailures
4118
+ });
4044
4119
  if (skills.length === 0) {
4045
4120
  spinner.stop(import_picocolors.default.red("No skills found"));
4121
+ logSkillParseFailures(skillParseFailures);
4046
4122
  Se(import_picocolors.default.red("No valid skills found. Skills require a SKILL.md with name and description."));
4047
4123
  process.exit(1);
4048
4124
  }
@@ -4205,12 +4281,12 @@ const BOLD = "\x1B[1m";
4205
4281
  const DIM = "\x1B[38;5;102m";
4206
4282
  const TEXT = "\x1B[38;5;145m";
4207
4283
  const LOGO_LINES = [
4208
- "███████╗██╗ ██╗██╗██╗ ██╗ ███████╗",
4209
- "██╔════╝██║ ██╔╝██║██║ ██║ ██╔════╝",
4210
- "███████╗█████╔╝ ██║██║ ██║ ███████╗",
4211
- "╚════██║██╔═██╗ ██║██║ ██║ ╚════██║",
4212
- "███████║██║ ██╗██║███████╗███████╗███████║",
4213
- "╚══════╝╚═╝ ╚═╝╚═╝╚══════╝╚══════╝╚══════╝"
4284
+ " █████╗ ██╗ ██╗ ███████╗██╗ ██╗██╗██╗ ██╗ ███████╗",
4285
+ "██╔══██╗██║ ██║ ██╔════╝██║ ██╔╝██║██║ ██║ ██╔════╝",
4286
+ "███████║██║ ██║ ███████╗█████╔╝ ██║██║ ██║ ███████╗",
4287
+ "██╔══██║██║ ██║ ╚════██║██╔═██╗ ██║██║ ██║ ╚════██║",
4288
+ "██║ ██║███████╗██║ ███████║██║ ██╗██║███████╗███████╗███████║",
4289
+ "╚═╝ ╚═╝╚══════╝╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝╚══════╝╚══════╝╚══════╝"
4214
4290
  ];
4215
4291
  const GRAYS = [
4216
4292
  "\x1B[38;5;250m",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ali/cli-skills",
3
- "version": "2.0.4",
3
+ "version": "2.0.6",
4
4
  "description": "The open agent skills ecosystem",
5
5
  "type": "module",
6
6
  "bin": {
@@ -24,7 +24,8 @@
24
24
  "prepare": "husky",
25
25
  "test": "vitest",
26
26
  "type-check": "tsc --noEmit",
27
- "publish:snapshot": "tnpm version prerelease --preid=snapshot --no-git-tag-version && tnpm publish --tag snapshot"
27
+ "publish:snapshot": "tnpm version prerelease --preid=snapshot --no-git-tag-version && tnpm publish --tag snapshot",
28
+ "publish:latest:patch": "tnpm version patch && tnpm publish && git push --follow-tags"
28
29
  },
29
30
  "lint-staged": {
30
31
  "src/**/*.ts": "prettier --write",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ali-skills",
3
- "version": "0.0.7-beta.0",
3
+ "version": "0.0.8",
4
4
  "description": "The open agent skills ecosystem",
5
5
  "type": "module",
6
6
  "bin": {
@@ -25,12 +25,13 @@
25
25
  "url": "git+http://gitlab.alibaba-inc.com/trip-tools/ali-skills.git"
26
26
  },
27
27
  "scripts": {
28
- "publish:beta": "npm version prerelease --preid=beta --no-git-tag-version && npm publish --tag beta"
28
+ "publish:beta": "npm version prerelease --preid=beta --no-git-tag-version && npm publish --tag beta",
29
+ "publish:latest:patch": "npm version patch && npm publish && git push --follow-tags"
29
30
  },
30
31
  "author": "",
31
32
  "license": "MIT",
32
33
  "dependencies": {
33
- "@ali/cli-skills": "^2.0.4"
34
+ "@ali/cli-skills": "^2.0.6"
34
35
  },
35
36
  "bundleDependencies": [
36
37
  "@ali/cli-skills"