ali-skills 0.0.11 → 0.0.13

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.
@@ -2,6 +2,18 @@
2
2
 
3
3
  import module from 'node:module';
4
4
 
5
+ // Check Node.js version (require >= 18 for native fetch)
6
+ const nodeVersion = process.versions.node;
7
+ const majorVersion = parseInt(nodeVersion.split('.')[0], 10);
8
+
9
+ if (majorVersion < 18) {
10
+ console.error(`\n❌ Error: Node.js version ${nodeVersion} is not supported.`);
11
+ console.error(`\n This CLI requires Node.js 18 or higher due to native fetch API usage.`);
12
+ console.error(` Please upgrade to Node.js 20 or later (recommended).\n`);
13
+ console.error(` Current version: ${nodeVersion}\n`);
14
+ process.exit(1);
15
+ }
16
+
5
17
  // https://nodejs.org/api/module.html#module-compile-cache
6
18
  if (module.enableCompileCache && !process.env.NODE_DISABLE_COMPILE_CACHE) {
7
19
  try {
@@ -166,6 +166,14 @@ function parseSource(input) {
166
166
  type: "internal",
167
167
  url: input.replace(/\bgitlab\.alibaba-inc\.com\b/g, "code.alibaba-inc.com")
168
168
  };
169
+ const internalSshMatch = input.match(/^git@(?:code|gitlab)\.alibaba-inc\.com:(.+?)(?:\.git)?$/);
170
+ if (internalSshMatch) {
171
+ const repoPath = internalSshMatch[1];
172
+ if (repoPath.includes("/")) return {
173
+ type: "internal",
174
+ url: `git@code.alibaba-inc.com:${repoPath}.git`
175
+ };
176
+ }
169
177
  if (isWellKnownUrl(input)) return {
170
178
  type: "well-known",
171
179
  url: input
@@ -365,6 +373,37 @@ async function searchMultiselect(options) {
365
373
  });
366
374
  }
367
375
  const CLONE_TIMEOUT_MS = 6e4;
376
+ function getGitUserConfig() {
377
+ try {
378
+ const name = execSync("git config user.name", {
379
+ encoding: "utf-8",
380
+ stdio: [
381
+ "pipe",
382
+ "pipe",
383
+ "ignore"
384
+ ]
385
+ }).trim();
386
+ const email = execSync("git config user.email", {
387
+ encoding: "utf-8",
388
+ stdio: [
389
+ "pipe",
390
+ "pipe",
391
+ "ignore"
392
+ ]
393
+ }).trim();
394
+ if (!name || !email) return null;
395
+ const emailMatch = email.match(/^([a-z0-9._-]+)@/i);
396
+ return {
397
+ username: (name || (emailMatch ? emailMatch[1] : "")) ?? "",
398
+ email
399
+ };
400
+ } catch {
401
+ return null;
402
+ }
403
+ }
404
+ function getCurrentUser() {
405
+ return getGitUserConfig();
406
+ }
368
407
  var GitCloneError = class extends Error {
369
408
  url;
370
409
  isTimeout;
@@ -677,12 +716,24 @@ async function parseSkillMd(skillMdPath, options) {
677
716
  });
678
717
  return null;
679
718
  }
719
+ const extractAuthorInfo = (data) => {
720
+ const authorObj = data.author && typeof data.author === "object" && !Array.isArray(data.author) ? data.author : {};
721
+ return {
722
+ authorName: typeof authorObj.nickname === "string" && authorObj.nickname || typeof authorObj.name === "string" && authorObj.name || void 0,
723
+ authorEmpId: typeof authorObj.empId === "string" && authorObj.empId || void 0,
724
+ authorAvatar: typeof authorObj.avatar === "string" && authorObj.avatar || void 0
725
+ };
726
+ };
727
+ const { authorName, authorEmpId, authorAvatar } = extractAuthorInfo(data);
680
728
  return {
681
729
  name: data.name,
682
730
  description: data.description,
683
731
  path: dirname(skillMdPath),
684
732
  rawContent: content,
685
- metadata: data.metadata
733
+ metadata: data.metadata,
734
+ authorName,
735
+ authorEmpId,
736
+ authorAvatar
686
737
  };
687
738
  }
688
739
  async function findSkillDirs(dir, depth = 0, maxDepth = 5) {
@@ -1623,6 +1674,16 @@ async function listInstalledSkills(options = {}) {
1623
1674
  const TELEMETRY_URL = `${SKILLS_API_BASE_URL}/telemetry`;
1624
1675
  const AUDIT_URL = `${SKILLS_API_BASE_URL}/audit`;
1625
1676
  let cliVersion = null;
1677
+ const pendingPromises = [];
1678
+ let pendingCount = 0;
1679
+ function getPendingTelemetryCount() {
1680
+ return pendingCount;
1681
+ }
1682
+ async function flushTelemetry() {
1683
+ if (pendingPromises.length === 0) return;
1684
+ await Promise.allSettled(pendingPromises);
1685
+ pendingCount = 0;
1686
+ }
1626
1687
  function isCI() {
1627
1688
  return !!(process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITLAB_CI || process.env.CIRCLECI || process.env.TRAVIS || process.env.BUILDKITE || process.env.JENKINS_URL || process.env.TEAMCITY_VERSION);
1628
1689
  }
@@ -1655,11 +1716,19 @@ function track(data) {
1655
1716
  if (!isEnabled()) return;
1656
1717
  try {
1657
1718
  const params = new URLSearchParams();
1719
+ pendingCount++;
1658
1720
  if (cliVersion) params.set("v", cliVersion);
1659
1721
  if (isCI()) params.set("ci", "1");
1660
1722
  for (const [key, value] of Object.entries(data)) if (value !== void 0 && value !== null) params.set(key, String(value));
1661
- fetch(`${TELEMETRY_URL}?${params.toString()}`).catch((err) => {
1662
- if (process.env.DEBUG) console.error("[Telemetry] Failed to send telemetry:", err);
1723
+ const requestUrl = `${TELEMETRY_URL}?${params.toString()}`;
1724
+ if (process.env.DEBUG) console.log("[Telemetry] request:", requestUrl);
1725
+ const promise = fetch(requestUrl).catch((err) => {
1726
+ console.error("[Telemetry] Failed to send telemetry:", err);
1727
+ });
1728
+ pendingPromises.push(promise);
1729
+ promise.finally(() => {
1730
+ const index = pendingPromises.indexOf(promise);
1731
+ if (index > -1) pendingPromises.splice(index, 1);
1663
1732
  });
1664
1733
  } catch {}
1665
1734
  }
@@ -2032,7 +2101,7 @@ function logSkillParseFailures(failures) {
2032
2101
  if (f.hint) M.message(import_picocolors.default.yellow(` 提示: ${f.hint}`));
2033
2102
  }
2034
2103
  }
2035
- var version$1 = "2.0.10";
2104
+ var version$1 = "2.0.13";
2036
2105
  const isCancelled$1 = (value) => typeof value === "symbol";
2037
2106
  async function isSourcePrivate(source) {
2038
2107
  const ownerRepo = parseOwnerRepo(source);
@@ -2271,7 +2340,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2271
2340
  M.info(`Valid agents: ${validAgents.join(", ")}`);
2272
2341
  process.exit(1);
2273
2342
  }
2274
- targetAgents = options.agent;
2343
+ targetAgents = ensureUniversalAgents(options.agent);
2275
2344
  } else {
2276
2345
  spinner.start("Loading agents...");
2277
2346
  const installedAgents = await detectInstalledAgents();
@@ -2329,8 +2398,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2329
2398
  installGlobally = scope;
2330
2399
  }
2331
2400
  let installMode = options.copy ? "copy" : "symlink";
2332
- const uniqueDirs = new Set(targetAgents.map((a) => agents[a].skillsDir));
2333
- if (!options.copy && !options.yes && uniqueDirs.size > 1) {
2401
+ if (!options.copy && !options.yes) {
2334
2402
  const modeChoice = await ve({
2335
2403
  message: "Installation method",
2336
2404
  options: [{
@@ -2348,7 +2416,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2348
2416
  process.exit(0);
2349
2417
  }
2350
2418
  installMode = modeChoice;
2351
- } else if (uniqueDirs.size <= 1) installMode = "copy";
2419
+ }
2352
2420
  const cwd = process.cwd();
2353
2421
  const summaryLines = [];
2354
2422
  const overwriteChecks = await Promise.all(selectedSkills.flatMap((skill) => targetAgents.map(async (agent) => ({
@@ -2405,16 +2473,20 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2405
2473
  skillFiles[skill.installName] = skill.sourceUrl;
2406
2474
  if (skill.description) skillDescriptions[skill.installName] = skill.description;
2407
2475
  }
2408
- if (await isSourcePrivate(sourceIdentifier) !== true) track({
2409
- event: "install",
2410
- source: sourceIdentifier,
2411
- skills: selectedSkills.map((s) => s.installName).join(","),
2412
- agents: targetAgents.join(","),
2413
- ...installGlobally && { global: "1" },
2414
- skillFiles: JSON.stringify(skillFiles),
2415
- sourceType: "well-known",
2416
- descriptions: JSON.stringify(skillDescriptions)
2417
- });
2476
+ if (await isSourcePrivate(sourceIdentifier) !== true) {
2477
+ const currentUser = getCurrentUser();
2478
+ track({
2479
+ event: "install",
2480
+ source: sourceIdentifier,
2481
+ skills: selectedSkills.map((s) => s.installName).join(","),
2482
+ agents: targetAgents.join(","),
2483
+ ...installGlobally && { global: "1" },
2484
+ skillFiles: JSON.stringify(skillFiles),
2485
+ sourceType: "well-known",
2486
+ descriptions: JSON.stringify(skillDescriptions),
2487
+ ...currentUser && { user: JSON.stringify(currentUser) }
2488
+ });
2489
+ }
2418
2490
  if (successful.length > 0 && installGlobally) {
2419
2491
  const successfulSkillNames = new Set(successful.map((r) => r.skill));
2420
2492
  for (const skill of selectedSkills) if (successfulSkillNames.has(skill.installName)) try {
@@ -2523,6 +2595,13 @@ async function runAdd(args, options = {}) {
2523
2595
  const spinner = Y();
2524
2596
  spinner.start("Parsing source...");
2525
2597
  const parsed = parseSource(resolvedSource);
2598
+ if (parsed.type === "internal" && parsed.url.startsWith("git@")) {
2599
+ const sshMatch = parsed.url.match(/^git@code\.alibaba-inc\.com:(.+)$/);
2600
+ if (sshMatch) {
2601
+ const repoPath = sshMatch[1].replace(/\.git$/, "");
2602
+ parsed.url = `https://${GITLAB_ACCOUNT.user}:${GITLAB_ACCOUNT.token}@code.alibaba-inc.com/${repoPath}.git`;
2603
+ }
2604
+ }
2526
2605
  if (options.branch) {
2527
2606
  parsed.ref = options.branch;
2528
2607
  parsed.subpath = void 0;
@@ -2760,7 +2839,7 @@ async function runAdd(args, options = {}) {
2760
2839
  await cleanup(tempDir);
2761
2840
  process.exit(1);
2762
2841
  }
2763
- targetAgents = options.agent;
2842
+ targetAgents = ensureUniversalAgents(options.agent);
2764
2843
  } else {
2765
2844
  spinner.start("Loading agents...");
2766
2845
  const installedAgents = await detectInstalledAgents();
@@ -2821,8 +2900,7 @@ async function runAdd(args, options = {}) {
2821
2900
  installGlobally = scope;
2822
2901
  }
2823
2902
  let installMode = options.copy ? "copy" : "symlink";
2824
- const uniqueDirs = new Set(targetAgents.map((a) => agents[a].skillsDir));
2825
- if (!options.copy && !options.yes && uniqueDirs.size > 1) {
2903
+ if (!options.copy && !options.yes) {
2826
2904
  const modeChoice = await ve({
2827
2905
  message: "Installation method",
2828
2906
  options: [{
@@ -2841,7 +2919,7 @@ async function runAdd(args, options = {}) {
2841
2919
  process.exit(0);
2842
2920
  }
2843
2921
  installMode = modeChoice;
2844
- } else if (uniqueDirs.size <= 1) installMode = "copy";
2922
+ }
2845
2923
  const cwd = process.cwd();
2846
2924
  const summaryLines = [];
2847
2925
  const overwriteChecks = await Promise.all(selectedSkills.flatMap((skill) => targetAgents.map(async (agent) => ({
@@ -2927,6 +3005,7 @@ async function runAdd(args, options = {}) {
2927
3005
  const failed = results.filter((r) => !r.success);
2928
3006
  const skillFiles = {};
2929
3007
  const skillDescriptions = {};
3008
+ const skillMdAuthors = {};
2930
3009
  for (const skill of selectedSkills) {
2931
3010
  let relativePath;
2932
3011
  if (tempDir && skill.path === tempDir) relativePath = "SKILL.md";
@@ -2934,29 +3013,59 @@ async function runAdd(args, options = {}) {
2934
3013
  else continue;
2935
3014
  skillFiles[skill.name] = relativePath;
2936
3015
  if (skill.description) skillDescriptions[skill.name] = skill.description;
3016
+ if (skill.authorName || skill.authorEmpId || skill.authorAvatar) skillMdAuthors[skill.name] = {
3017
+ name: skill.authorName,
3018
+ empId: skill.authorEmpId,
3019
+ avatar: skill.authorAvatar
3020
+ };
2937
3021
  }
2938
3022
  const normalizedSource = getOwnerRepo(parsed);
2939
3023
  const effectiveSourceType = parsed.type;
2940
3024
  const lockSource = parsed.url.startsWith("git@") ? parsed.url : normalizedSource;
2941
- let skillAuthor;
3025
+ const trackableSkillNames = selectedSkills.filter((s) => isValidSkillName(s.name)).map((s) => s.name);
3026
+ const skillMdLastCommitAuthors = {};
2942
3027
  if (parsed.type === "internal" && normalizedSource) {
2943
3028
  const gitlabToken = GITLAB_ACCOUNT.token;
2944
3029
  if (gitlabToken) {
2945
3030
  const project = await getGitLabProject(normalizedSource, gitlabToken);
2946
- if (project) {
2947
- const firstSkillPath = Object.values(skillFiles)[0];
2948
- if (firstSkillPath) {
2949
- const author = await getSkillLastCommitAuthor(project.id, firstSkillPath, gitlabToken);
2950
- if (author) skillAuthor = author;
3031
+ if (project) for (const skillName of trackableSkillNames) {
3032
+ const skillFilePath = skillFiles[skillName];
3033
+ if (skillFilePath) {
3034
+ const author = await getSkillLastCommitAuthor(project.id, skillFilePath, gitlabToken);
3035
+ if (author) {
3036
+ const formattedName = author.username ? `${author.name}/${author.username}` : author.name;
3037
+ skillMdLastCommitAuthors[skillName] = {
3038
+ employeeId: author.employeeId,
3039
+ email: author.email,
3040
+ name: formattedName
3041
+ };
3042
+ }
2951
3043
  }
2952
3044
  }
2953
3045
  }
2954
3046
  }
2955
- const trackableSkillNames = selectedSkills.filter((s) => isValidSkillName(s.name)).map((s) => s.name);
2956
3047
  if (normalizedSource && trackableSkillNames.length > 0) {
2957
3048
  const ownerRepo = parseOwnerRepo(normalizedSource);
2958
3049
  if (ownerRepo) {
2959
- if (await isRepoPrivate(ownerRepo.owner, ownerRepo.repo) === false) track({
3050
+ if (await isRepoPrivate(ownerRepo.owner, ownerRepo.repo) === false) {
3051
+ const currentUser = getCurrentUser();
3052
+ track({
3053
+ event: "install",
3054
+ source: normalizedSource,
3055
+ skills: trackableSkillNames.join(","),
3056
+ agents: targetAgents.join(","),
3057
+ ...installGlobally && { global: "1" },
3058
+ skillFiles: JSON.stringify(skillFiles),
3059
+ sourceType: effectiveSourceType,
3060
+ skillMdLastCommitAuthors: JSON.stringify(skillMdLastCommitAuthors),
3061
+ skillMdConfigAuthors: JSON.stringify(skillMdAuthors),
3062
+ descriptions: JSON.stringify(skillDescriptions),
3063
+ ...currentUser && { user: JSON.stringify(currentUser) }
3064
+ });
3065
+ }
3066
+ } else {
3067
+ const currentUser = getCurrentUser();
3068
+ track({
2960
3069
  event: "install",
2961
3070
  source: normalizedSource,
2962
3071
  skills: trackableSkillNames.join(","),
@@ -2964,20 +3073,12 @@ async function runAdd(args, options = {}) {
2964
3073
  ...installGlobally && { global: "1" },
2965
3074
  skillFiles: JSON.stringify(skillFiles),
2966
3075
  sourceType: effectiveSourceType,
2967
- author: JSON.stringify(skillAuthor),
2968
- descriptions: JSON.stringify(skillDescriptions)
3076
+ skillMdLastCommitAuthors: JSON.stringify(skillMdLastCommitAuthors),
3077
+ skillMdConfigAuthors: JSON.stringify(skillMdAuthors),
3078
+ descriptions: JSON.stringify(skillDescriptions),
3079
+ ...currentUser && { user: JSON.stringify(currentUser) }
2969
3080
  });
2970
- } else track({
2971
- event: "install",
2972
- source: normalizedSource,
2973
- skills: trackableSkillNames.join(","),
2974
- agents: targetAgents.join(","),
2975
- ...installGlobally && { global: "1" },
2976
- skillFiles: JSON.stringify(skillFiles),
2977
- sourceType: effectiveSourceType,
2978
- author: JSON.stringify(skillAuthor),
2979
- descriptions: JSON.stringify(skillDescriptions)
2980
- });
3081
+ }
2981
3082
  }
2982
3083
  if (successful.length > 0 && installGlobally && normalizedSource) {
2983
3084
  const successfulSkillNames = new Set(successful.map((r) => r.skill));
@@ -4026,13 +4127,19 @@ function detectRepoInfo() {
4026
4127
  }).trim();
4027
4128
  if (!remoteUrl) return null;
4028
4129
  if (remoteUrl.includes("code.alibaba-inc.com") || remoteUrl.includes("gitlab.alibaba-inc.com")) {
4029
- const match = remoteUrl.match(/(?:code|gitlab)\.alibaba-inc\.com[/:]([^/]+)\/([^/.]+)/);
4030
- if (match) return {
4031
- sourceType: "internal",
4032
- owner: match[1],
4033
- repo: match[2],
4034
- remoteUrl
4035
- };
4130
+ const match = remoteUrl.match(/(?:code|gitlab)\.alibaba-inc\.com[\/:](.+?)(?:\.git)?$/);
4131
+ if (match) {
4132
+ const pathParts = match[1].split("/");
4133
+ if (pathParts.length >= 2) {
4134
+ const repo = pathParts.pop();
4135
+ return {
4136
+ sourceType: "internal",
4137
+ owner: pathParts.join("/"),
4138
+ repo,
4139
+ remoteUrl
4140
+ };
4141
+ }
4142
+ }
4036
4143
  }
4037
4144
  if (remoteUrl.includes("github.com")) {
4038
4145
  const match = remoteUrl.match(/github\.com[/:]([^/]+)\/([^/.]+)/);
@@ -4233,12 +4340,18 @@ async function runPublish(args, options = {}) {
4233
4340
  const registerResult = await registerSkill({
4234
4341
  source,
4235
4342
  sourceType: repoInfo.sourceType,
4236
- skills: successful.map((r) => ({
4237
- name: r.skill,
4238
- zipUrl: r.zipUrl,
4239
- zipHash: r.hash,
4240
- description: selectedSkills.find((s) => getSkillDisplayName(s) === r.skill)?.description ?? ""
4241
- }))
4343
+ skills: successful.map((r) => {
4344
+ const skill = selectedSkills.find((s) => getSkillDisplayName(s) === r.skill);
4345
+ return {
4346
+ name: r.skill,
4347
+ zipUrl: r.zipUrl,
4348
+ zipHash: r.hash,
4349
+ description: skill?.description ?? "",
4350
+ authorName: skill?.authorName,
4351
+ authorEmpId: skill?.authorEmpId,
4352
+ authorAvatar: skill?.authorAvatar
4353
+ };
4354
+ })
4242
4355
  });
4243
4356
  if (registerResult.success) spinner.stop("Skills registered");
4244
4357
  else spinner.stop(import_picocolors.default.yellow(`Registration warning: ${registerResult.error}`));
@@ -4804,5 +4917,16 @@ async function main() {
4804
4917
  console.log(`Run ${BOLD}cli-skills --help${RESET} for usage.`);
4805
4918
  }
4806
4919
  }
4807
- main();
4920
+ main().then(async () => {
4921
+ if (getPendingTelemetryCount() > 0) {
4922
+ console.log();
4923
+ console.log(`${DIM}Syncing installation data...${RESET}`);
4924
+ }
4925
+ await flushTelemetry();
4926
+ process.exit(0);
4927
+ }).catch(async (error) => {
4928
+ console.error(error);
4929
+ await flushTelemetry();
4930
+ process.exit(1);
4931
+ });
4808
4932
  export {};
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ali/cli-skills",
3
- "version": "2.0.10",
3
+ "version": "2.0.13",
4
4
  "description": "The open agent skills ecosystem",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,7 +16,8 @@
16
16
  "scripts": {
17
17
  "build": "node scripts/generate-licenses.ts && obuild",
18
18
  "generate-licenses": "node scripts/generate-licenses.ts",
19
- "dev": "node --env-file=.env src/cli.ts",
19
+ "dev": "node src/cli.ts",
20
+ "dev:env": "node --env-file=.env src/cli.ts",
20
21
  "exec:test": "node scripts/execute-tests.ts",
21
22
  "prepublishOnly": "npm run build",
22
23
  "format": "prettier --write 'src/**/*.ts' 'scripts/**/*.ts'",
@@ -25,7 +26,7 @@
25
26
  "test": "vitest",
26
27
  "type-check": "tsc --noEmit",
27
28
  "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"
29
+ "publish:latest:patch": "tnpm version patch && tnpm publish && git push --follow-tags --no-verify"
29
30
  },
30
31
  "lint-staged": {
31
32
  "src/**/*.ts": "prettier --write",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ali-skills",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
4
4
  "description": "The open agent skills ecosystem",
5
5
  "type": "module",
6
6
  "bin": {
@@ -31,7 +31,7 @@
31
31
  "author": "",
32
32
  "license": "MIT",
33
33
  "dependencies": {
34
- "@ali/cli-skills": "^2.0.10"
34
+ "@ali/cli-skills": "^2.0.13"
35
35
  },
36
36
  "bundleDependencies": [
37
37
  "@ali/cli-skills"