ali-skills 0.0.15 → 0.0.17

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.
@@ -94,9 +94,32 @@ function parseSource(input) {
94
94
  const githubRepoMatch = input.match(/github\.com\/([^/]+)\/([^/]+)/);
95
95
  if (githubRepoMatch) {
96
96
  const [, owner, repo] = githubRepoMatch;
97
+ const cleanRepo = repo.replace(/\.git$/, "");
98
+ const credentialMatch = input.match(/^(https?:\/\/)(?:([^@]+)@)?github\.com/);
99
+ const protocol = credentialMatch?.[1] ?? "https://";
100
+ const credential = credentialMatch?.[2];
97
101
  return {
98
102
  type: "github",
99
- url: `https://github.com/${owner}/${repo.replace(/\.git$/, "")}.git`
103
+ url: credential ? `${protocol}${credential}@github.com/${owner}/${cleanRepo}.git` : `${protocol}github.com/${owner}/${cleanRepo}.git`
104
+ };
105
+ }
106
+ const internalTreeWithPathMatch = input.match(/^(https?:\/\/(?:[^@/]+@)?(?:code|gitlab)\.alibaba-inc\.com)\/(.+?)(?:\/-)?\/tree\/([^/]+)\/(.+)/);
107
+ if (internalTreeWithPathMatch) {
108
+ const [, host, repoPath, ref, subpath] = internalTreeWithPathMatch;
109
+ if (repoPath && repoPath.includes("/")) return {
110
+ type: "internal",
111
+ url: `${host.replace("gitlab.alibaba-inc.com", "code.alibaba-inc.com")}/${repoPath.replace(/\.git$/, "")}.git`,
112
+ ref,
113
+ subpath: subpath ? sanitizeSubpath(subpath) : subpath
114
+ };
115
+ }
116
+ const internalTreeMatch = input.match(/^(https?:\/\/(?:[^@/]+@)?(?:code|gitlab)\.alibaba-inc\.com)\/(.+?)(?:\/-)?\/tree\/([^/]+)$/);
117
+ if (internalTreeMatch) {
118
+ const [, host, repoPath, ref] = internalTreeMatch;
119
+ if (repoPath && repoPath.includes("/")) return {
120
+ type: "internal",
121
+ url: `${host.replace("gitlab.alibaba-inc.com", "code.alibaba-inc.com")}/${repoPath.replace(/\.git$/, "")}.git`,
122
+ ref
100
123
  };
101
124
  }
102
125
  const gitlabTreeWithPathMatch = input.match(/^(https?):\/\/([^/]+)\/(.+?)\/-\/tree\/([^/]+)\/(.+)/);
@@ -144,25 +167,6 @@ function parseSource(input) {
144
167
  subpath: subpath ? sanitizeSubpath(subpath) : subpath
145
168
  };
146
169
  }
147
- const internalTreeWithPathMatch = input.match(/^(https?:\/\/(?:[^@/]+@)?(?:code|gitlab)\.alibaba-inc\.com)\/(.+?)(?:\/-)?\/tree\/([^/]+)\/(.+)/);
148
- if (internalTreeWithPathMatch) {
149
- const [, host, repoPath, ref, subpath] = internalTreeWithPathMatch;
150
- if (repoPath && repoPath.includes("/")) return {
151
- type: "internal",
152
- url: `${host.replace("gitlab.alibaba-inc.com", "code.alibaba-inc.com")}/${repoPath.replace(/\.git$/, "")}.git`,
153
- ref,
154
- subpath: subpath ? sanitizeSubpath(subpath) : subpath
155
- };
156
- }
157
- const internalTreeMatch = input.match(/^(https?:\/\/(?:[^@/]+@)?(?:code|gitlab)\.alibaba-inc\.com)\/(.+?)(?:\/-)?\/tree\/([^/]+)$/);
158
- if (internalTreeMatch) {
159
- const [, host, repoPath, ref] = internalTreeMatch;
160
- if (repoPath && repoPath.includes("/")) return {
161
- type: "internal",
162
- url: `${host.replace("gitlab.alibaba-inc.com", "code.alibaba-inc.com")}/${repoPath.replace(/\.git$/, "")}.git`,
163
- ref
164
- };
165
- }
166
170
  const internalRepoWithSubpathMatch = input.match(/^(https?:\/\/(?:[^@/]+@)?(?:code|gitlab)\.alibaba-inc\.com)\/([^/]+)\/([^/]+)\/(.+)$/);
167
171
  if (internalRepoWithSubpathMatch) {
168
172
  const [, host, owner, repo, subpath] = internalRepoWithSubpathMatch;
@@ -220,6 +224,27 @@ function isWellKnownUrl(input) {
220
224
  return false;
221
225
  }
222
226
  }
227
+ function getDisplaySource(sourceUrl, options = {}) {
228
+ const format = options.format ?? "compact";
229
+ if (sourceUrl.startsWith("/") || sourceUrl.startsWith(".") || sourceUrl.startsWith("~")) return sourceUrl;
230
+ const sshMatch = sourceUrl.match(/^git@([^:]+):(.+)$/);
231
+ if (sshMatch) {
232
+ const [, host, path] = sshMatch;
233
+ if (!host || !path) return sourceUrl;
234
+ const cleanPath = path.replace(/\.git$/, "");
235
+ if (format === "compact" && (host === "code.alibaba-inc.com" || host === "gitlab.alibaba-inc.com")) return cleanPath;
236
+ return `https://${host}/${cleanPath}`;
237
+ }
238
+ if (sourceUrl.startsWith("http://") || sourceUrl.startsWith("https://")) try {
239
+ const url = new URL(sourceUrl);
240
+ const cleanPath = url.pathname.replace(/\.git$/, "").replace(/^\//, "");
241
+ if (format === "compact" && (url.hostname === "code.alibaba-inc.com" || url.hostname === "gitlab.alibaba-inc.com")) return cleanPath;
242
+ return `https://${url.hostname}/${cleanPath}`;
243
+ } catch {
244
+ return sourceUrl.replace(/^https?:\/\/[^@]+@/, "https://");
245
+ }
246
+ return sourceUrl;
247
+ }
223
248
  const silentOutput = new Writable({ write(_chunk, _encoding, callback) {
224
249
  callback();
225
250
  } });
@@ -384,6 +409,9 @@ async function searchMultiselect(options) {
384
409
  });
385
410
  }
386
411
  const CLONE_TIMEOUT_MS = 6e4;
412
+ function redactSensitiveText$1(text) {
413
+ return text.replace(/(https?:\/\/)([^/\s:@]+)(?::([^@/\s]*))?@/gi, "$1").replace(/([?&](?:private_token|access_token|token|api_key|access_key|secret|password)=)[^&\s]+/gi, "$1***");
414
+ }
387
415
  function getGitUserConfig() {
388
416
  try {
389
417
  const name = execSync("git config user.name", {
@@ -451,11 +479,13 @@ async function cloneRepo(url, ref) {
451
479
  force: true
452
480
  }).catch(() => {});
453
481
  const errorMessage = error instanceof Error ? error.message : String(error);
482
+ const safeUrl = getDisplaySource(url);
483
+ const safeErrorMessage = redactSensitiveText$1(errorMessage);
454
484
  const isTimeout = errorMessage.includes("block timeout") || errorMessage.includes("timed out");
455
485
  const isAuthError = errorMessage.includes("Authentication failed") || errorMessage.includes("could not read Username") || errorMessage.includes("Permission denied") || errorMessage.includes("Repository not found") || errorMessage.includes("HTTP Basic: Access denied") || errorMessage.includes("could not be found or you don") || errorMessage.toLowerCase().includes("you don't have permission") || /repository .+ not found/i.test(errorMessage);
456
- if (isTimeout) throw new GitCloneError("Clone timed out after 60s. This often happens with private repos that require authentication.\n Ensure you have access and your SSH keys or credentials are configured:\n - For SSH: ssh-add -l (to check loaded keys)\n - For HTTPS: gh auth status (if using GitHub CLI)", url, true, false);
457
- if (isAuthError) throw new GitCloneError(`Authentication failed for ${url}.\n - For private repos, ensure you have access\n - For SSH: Check your keys with 'ssh -T git@github.com'\n - For HTTPS: Run 'gh auth login' or configure git credentials`, url, false, true);
458
- throw new GitCloneError(`Failed to clone ${url}: ${errorMessage}`, url, false, false);
486
+ if (isTimeout) throw new GitCloneError("Clone timed out after 60s. This often happens with private repos that require authentication.\n Ensure you have access and your SSH keys or credentials are configured:\n - For SSH: ssh-add -l (to check loaded keys)\n - For HTTPS: gh auth status (if using GitHub CLI)", safeUrl, true, false);
487
+ if (isAuthError) throw new GitCloneError(`Authentication failed for ${safeUrl}.\n - For private repos, ensure you have access\n - For SSH: Check your keys with 'ssh -T git@github.com'\n - For HTTPS: Run 'gh auth login' or configure git credentials`, safeUrl, false, true);
488
+ throw new GitCloneError(`Failed to clone ${safeUrl}: ${safeErrorMessage}`, safeUrl, false, false);
459
489
  }
460
490
  }
461
491
  async function listRemoteBranches(url) {
@@ -2056,6 +2086,25 @@ async function saveSelectedAgents(agents) {
2056
2086
  lock.lastSelectedAgents = agents;
2057
2087
  await writeSkillLock(lock);
2058
2088
  }
2089
+ function sanitizeSourceUrl(url) {
2090
+ const sshMatch = url.match(/^git@([^:]+):(.+)$/);
2091
+ if (sshMatch) {
2092
+ const [, host, path] = sshMatch;
2093
+ if (!host || !path) return url;
2094
+ return `https://${host}/${path.replace(/\.git$/, "")}.git`;
2095
+ }
2096
+ if (url.startsWith("http://") || url.startsWith("https://")) try {
2097
+ const parsed = new URL(url);
2098
+ parsed.username = "";
2099
+ parsed.password = "";
2100
+ let cleanUrl = parsed.toString();
2101
+ if (!cleanUrl.endsWith(".git")) cleanUrl += ".git";
2102
+ return cleanUrl;
2103
+ } catch {
2104
+ return url;
2105
+ }
2106
+ return url;
2107
+ }
2059
2108
  const LOCAL_LOCK_FILE = "skills-lock.json";
2060
2109
  const CURRENT_VERSION = 1;
2061
2110
  function getLocalLockPath(cwd) {
@@ -2066,11 +2115,27 @@ async function readLocalLock(cwd) {
2066
2115
  try {
2067
2116
  const content = await readFile(lockPath, "utf-8");
2068
2117
  const parsed = JSON.parse(content);
2069
- if (typeof parsed.version !== "number" || !parsed.skills) return createEmptyLocalLock();
2070
- if (parsed.version < CURRENT_VERSION) return createEmptyLocalLock();
2071
- return parsed;
2118
+ if (typeof parsed.version !== "number") return {
2119
+ lock: parsed,
2120
+ isOutdated: true
2121
+ };
2122
+ if (parsed.version < CURRENT_VERSION) return {
2123
+ lock: parsed,
2124
+ isOutdated: true
2125
+ };
2126
+ if (!parsed.skills) return {
2127
+ lock: createEmptyLocalLock(),
2128
+ isOutdated: false
2129
+ };
2130
+ return {
2131
+ lock: parsed,
2132
+ isOutdated: false
2133
+ };
2072
2134
  } catch {
2073
- return createEmptyLocalLock();
2135
+ return {
2136
+ lock: createEmptyLocalLock(),
2137
+ isOutdated: false
2138
+ };
2074
2139
  }
2075
2140
  }
2076
2141
  async function writeLocalLock(lock, cwd) {
@@ -2112,7 +2177,7 @@ async function collectFiles(baseDir, currentDir, results) {
2112
2177
  }));
2113
2178
  }
2114
2179
  async function addSkillToLocalLock(skillName, entry, cwd) {
2115
- const lock = await readLocalLock(cwd);
2180
+ const { lock } = await readLocalLock(cwd);
2116
2181
  lock.skills[skillName] = entry;
2117
2182
  await writeLocalLock(lock, cwd);
2118
2183
  }
@@ -2132,8 +2197,11 @@ function logSkillParseFailures(failures) {
2132
2197
  if (f.hint) M.message(import_picocolors.default.yellow(` 提示: ${f.hint}`));
2133
2198
  }
2134
2199
  }
2135
- var version$1 = "2.0.15";
2200
+ var version$1 = "2.0.17";
2136
2201
  const isCancelled$1 = (value) => typeof value === "symbol";
2202
+ function redactSensitiveText(text) {
2203
+ return text.replace(/(https?:\/\/)([^/\s:@]+)(?::([^@/\s]*))?@/gi, "$1").replace(/([?&](?:private_token|access_token|token|api_key|access_key|secret|password)=)[^&\s]+/gi, "$1***");
2204
+ }
2137
2205
  async function isSourcePrivate(source) {
2138
2206
  const ownerRepo = parseOwnerRepo(source);
2139
2207
  if (!ownerRepo) return false;
@@ -2386,7 +2454,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2386
2454
  value: key,
2387
2455
  label: config.displayName
2388
2456
  })));
2389
- if (pD(selected)) {
2457
+ if (isCancelled$1(selected)) {
2390
2458
  xe("Installation cancelled");
2391
2459
  process.exit(0);
2392
2460
  }
@@ -2400,7 +2468,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2400
2468
  } else M.info(`Installing to: ${installedAgents.map((a) => import_picocolors.default.cyan(agents[a].displayName)).join(", ")}`);
2401
2469
  } else {
2402
2470
  const selected = await selectAgentsInteractive({ global: options.global });
2403
- if (pD(selected)) {
2471
+ if (isCancelled$1(selected)) {
2404
2472
  xe("Installation cancelled");
2405
2473
  process.exit(0);
2406
2474
  }
@@ -2524,8 +2592,7 @@ async function handleWellKnownSkills(source, url, options, spinner) {
2524
2592
  await addSkillToLock(skill.installName, {
2525
2593
  source: sourceIdentifier,
2526
2594
  sourceType: "well-known",
2527
- sourceUrl: skill.sourceUrl,
2528
- skillFolderHash: ""
2595
+ sourceUrl: sanitizeSourceUrl(skill.sourceUrl)
2529
2596
  });
2530
2597
  } catch {}
2531
2598
  }
@@ -2653,7 +2720,8 @@ async function runAdd(args, options = {}) {
2653
2720
  }
2654
2721
  } catch {}
2655
2722
  }
2656
- spinner.stop(`Source: ${parsed.type === "local" ? parsed.localPath : parsed.url}${parsed.ref ? ` @ ${import_picocolors.default.yellow(parsed.ref)}` : ""}${parsed.subpath ? ` (${parsed.subpath})` : ""}${parsed.skillFilter ? ` ${import_picocolors.default.dim("@")}${import_picocolors.default.cyan(parsed.skillFilter)}` : ""}`);
2723
+ const displaySource = parsed.type === "local" ? parsed.localPath : getDisplaySource(parsed.url, { format: "absolute" });
2724
+ spinner.stop(`Source: ${displaySource}${parsed.ref ? ` @ ${import_picocolors.default.yellow(parsed.ref)}` : ""}${parsed.subpath ? ` (${parsed.subpath})` : ""}${parsed.skillFilter ? ` ${import_picocolors.default.dim("@")}${import_picocolors.default.cyan(parsed.skillFilter)}` : ""}`);
2657
2725
  if (parsed.type === "well-known") {
2658
2726
  await handleWellKnownSkills(source, parsed.url, options, spinner);
2659
2727
  return;
@@ -2886,7 +2954,7 @@ async function runAdd(args, options = {}) {
2886
2954
  value: key,
2887
2955
  label: config.displayName
2888
2956
  })));
2889
- if (pD(selected)) {
2957
+ if (isCancelled$1(selected)) {
2890
2958
  xe("Installation cancelled");
2891
2959
  await cleanup(tempDir);
2892
2960
  process.exit(0);
@@ -2901,7 +2969,7 @@ async function runAdd(args, options = {}) {
2901
2969
  } else M.info(`Installing to: ${installedAgents.map((a) => import_picocolors.default.cyan(agents[a].displayName)).join(", ")}`);
2902
2970
  } else {
2903
2971
  const selected = await selectAgentsInteractive({ global: options.global });
2904
- if (pD(selected)) {
2972
+ if (isCancelled$1(selected)) {
2905
2973
  xe("Installation cancelled");
2906
2974
  await cleanup(tempDir);
2907
2975
  process.exit(0);
@@ -3117,18 +3185,28 @@ async function runAdd(args, options = {}) {
3117
3185
  for (const skill of selectedSkills) {
3118
3186
  const skillDisplayName = getSkillDisplayName(skill);
3119
3187
  if (successfulSkillNames.has(skillDisplayName)) try {
3120
- let skillFolderHash = "";
3188
+ let skillFolderHash;
3189
+ let commitHash;
3121
3190
  const skillPathValue = skillFiles[skill.name];
3122
- if (parsed.type === "github" && skillPathValue) {
3191
+ let skillPath = skillPathValue;
3192
+ if (skillPath) {
3193
+ if (skillPath.endsWith("/SKILL.md")) skillPath = skillPath.slice(0, -9);
3194
+ else if (skillPath.endsWith("SKILL.md")) skillPath = skillPath.slice(0, -8);
3195
+ }
3196
+ if (effectiveSourceType === "github" && skillPathValue) {
3123
3197
  const hash = await fetchSkillFolderHash(normalizedSource, skillPathValue, getGitHubToken());
3124
3198
  if (hash) skillFolderHash = hash;
3199
+ } else if (effectiveSourceType === "internal" && skillPath !== void 0) {
3200
+ const hash = await fetchAliInternalCommitHash(normalizedSource, skillPath, null);
3201
+ if (hash) commitHash = hash;
3125
3202
  }
3126
3203
  await addSkillToLock(skill.name, {
3127
3204
  source: lockSource || normalizedSource,
3128
3205
  sourceType: effectiveSourceType,
3129
- sourceUrl: parsed.url,
3130
- skillPath: skillPathValue,
3131
- skillFolderHash,
3206
+ sourceUrl: sanitizeSourceUrl(parsed.url),
3207
+ skillPath,
3208
+ ...skillFolderHash && { skillFolderHash },
3209
+ ...commitHash && { commitHash },
3132
3210
  pluginName: skill.pluginName
3133
3211
  });
3134
3212
  } catch {}
@@ -3141,8 +3219,9 @@ async function runAdd(args, options = {}) {
3141
3219
  if (successfulSkillNames.has(skillDisplayName)) try {
3142
3220
  const computedHash = await computeSkillFolderHash(skill.path);
3143
3221
  const skillFilePath = skillFiles[skill.name];
3222
+ const rawSource = lockSource || parsed.url;
3144
3223
  const lockEntry = {
3145
- source: lockSource || parsed.url,
3224
+ source: effectiveSourceType === "internal" || effectiveSourceType === "github" ? rawSource.replace(/^https?:\/\/[^@]+@/, "https://") : rawSource,
3146
3225
  sourceType: effectiveSourceType,
3147
3226
  computedHash
3148
3227
  };
@@ -3231,8 +3310,8 @@ async function runAdd(args, options = {}) {
3231
3310
  } catch (error) {
3232
3311
  if (error instanceof GitCloneError) {
3233
3312
  M.error(import_picocolors.default.red("Failed to clone repository"));
3234
- for (const line of error.message.split("\n")) M.message(import_picocolors.default.dim(line));
3235
- } else M.error(error instanceof Error ? error.message : "Unknown error occurred");
3313
+ for (const line of error.message.split("\n")) M.message(import_picocolors.default.dim(redactSensitiveText(line)));
3314
+ } else M.error(redactSensitiveText(error instanceof Error ? error.message : "Unknown error occurred"));
3236
3315
  showInstallTip();
3237
3316
  Se(import_picocolors.default.red("Installation failed"));
3238
3317
  process.exit(1);
@@ -3305,7 +3384,7 @@ const BOLD$2 = "\x1B[1m";
3305
3384
  const DIM$5 = "\x1B[38;5;102m";
3306
3385
  const TEXT$4 = "\x1B[38;5;145m";
3307
3386
  const CYAN$1 = "\x1B[36m";
3308
- const YELLOW$4 = "\x1B[33m";
3387
+ const YELLOW$5 = "\x1B[33m";
3309
3388
  const SKILLS_API_URL = process.env.SKILLS_API_URL;
3310
3389
  const SEARCH_ENDPOINT = SKILLS_API_URL ? `${SKILLS_API_URL.replace(/\/+$/, "")}/search` : "https://next-ai-base.pre-fn.alibaba-inc.com/api/skills/search";
3311
3390
  function formatInstalls(count) {
@@ -3375,7 +3454,7 @@ async function runSearchPrompt(initialQuery = "") {
3375
3454
  const source = skill.source ? ` ${DIM$5}${skill.source}${RESET$5}` : "";
3376
3455
  const installs = formatInstalls(skill.installs);
3377
3456
  const installsBadge = installs ? ` ${CYAN$1}${installs}${RESET$5}` : "";
3378
- const githubBadge = skill.sourceType === "github" ? ` ${YELLOW$4}GitHub${RESET$5}` : "";
3457
+ const githubBadge = skill.sourceType === "github" ? ` ${YELLOW$5}GitHub${RESET$5}` : "";
3379
3458
  const loadingIndicator = loading && i === 0 ? ` ${DIM$5}...${RESET$5}` : "";
3380
3459
  lines.push(` ${arrow} ${name}${source}${githubBadge}${installsBadge}${loadingIndicator}`);
3381
3460
  }
@@ -3484,7 +3563,7 @@ ${DIM$5} 2) npx @ali/cli-skills add <owner/repo@skill>${RESET$5}`;
3484
3563
  for (const skill of results.slice(0, 6)) {
3485
3564
  const pkg = skill.source || skill.slug;
3486
3565
  const installs = formatInstalls(skill.installs);
3487
- const githubBadge = skill.sourceType === "github" ? ` ${YELLOW$4}GitHub${RESET$5}` : "";
3566
+ const githubBadge = skill.sourceType === "github" ? ` ${YELLOW$5}GitHub${RESET$5}` : "";
3488
3567
  console.log(`${TEXT$4}${pkg}@${skill.name}${RESET$5}${githubBadge}${installs ? ` ${CYAN$1}${installs}${RESET$5}` : ""}`);
3489
3568
  const skillUrl = buildSkillUrl(skill);
3490
3569
  console.log(`${DIM$5}└ ${skillUrl}${RESET$5}`);
@@ -3612,7 +3691,7 @@ async function runSync(args, options = {}) {
3612
3691
  M.info(`${import_picocolors.default.cyan(skill.name)} ${import_picocolors.default.dim(`from ${skill.packageName}`)}`);
3613
3692
  if (skill.description) M.message(import_picocolors.default.dim(` ${skill.description}`));
3614
3693
  }
3615
- const localLock = await readLocalLock(cwd);
3694
+ const { lock: localLock } = await readLocalLock(cwd);
3616
3695
  const toInstall = [];
3617
3696
  const upToDate = [];
3618
3697
  if (options.force) {
@@ -3812,7 +3891,8 @@ function parseSyncOptions(args) {
3812
3891
  return { options };
3813
3892
  }
3814
3893
  async function runInstallFromLock(args) {
3815
- const lock = await readLocalLock(process.cwd());
3894
+ const { lock, isOutdated } = await readLocalLock(process.cwd());
3895
+ if (isOutdated) M.warn("Project lock file may be outdated. Some skills might not install correctly.");
3816
3896
  const skillEntries = Object.entries(lock.skills);
3817
3897
  if (skillEntries.length === 0) {
3818
3898
  M.warn("No project skills found in skills-lock.json");
@@ -3863,7 +3943,7 @@ const RESET$4 = "\x1B[0m";
3863
3943
  const BOLD$1 = "\x1B[1m";
3864
3944
  const DIM$4 = "\x1B[38;5;102m";
3865
3945
  const CYAN = "\x1B[36m";
3866
- const YELLOW$3 = "\x1B[33m";
3946
+ const YELLOW$4 = "\x1B[33m";
3867
3947
  function shortenPath(fullPath, cwd) {
3868
3948
  const home = homedir();
3869
3949
  if (fullPath.startsWith(home)) return fullPath.replace(home, "~");
@@ -3897,7 +3977,7 @@ async function runList(args) {
3897
3977
  const validAgents = Object.keys(agents);
3898
3978
  const invalidAgents = options.agent.filter((a) => !validAgents.includes(a));
3899
3979
  if (invalidAgents.length > 0) {
3900
- console.log(`${YELLOW$3}Invalid agents: ${invalidAgents.join(", ")}${RESET$4}`);
3980
+ console.log(`${YELLOW$4}Invalid agents: ${invalidAgents.join(", ")}${RESET$4}`);
3901
3981
  console.log(`${DIM$4}Valid agents: ${validAgents.join(", ")}${RESET$4}`);
3902
3982
  process.exit(1);
3903
3983
  }
@@ -3934,7 +4014,7 @@ async function runList(args) {
3934
4014
  const prefix = indent ? " " : "";
3935
4015
  const shortPath = shortenPath(skill.canonicalPath, cwd);
3936
4016
  const agentNames = skill.agents.map((a) => agents[a].displayName);
3937
- const agentInfo = skill.agents.length > 0 ? formatList(agentNames) : `${YELLOW$3}not linked${RESET$4}`;
4017
+ const agentInfo = skill.agents.length > 0 ? formatList(agentNames) : `${YELLOW$4}not linked${RESET$4}`;
3938
4018
  console.log(`${prefix}${CYAN}${skill.name}${RESET$4} ${DIM$4}${shortPath}${RESET$4}`);
3939
4019
  console.log(`${prefix} ${DIM$4}Agents:${RESET$4} ${agentInfo}`);
3940
4020
  }
@@ -4537,7 +4617,7 @@ const DIM$3 = "\x1B[38;5;102m";
4537
4617
  const TEXT$3 = "\x1B[38;5;145m";
4538
4618
  const RESET$3 = "\x1B[0m";
4539
4619
  const GREEN$2 = "\x1B[32m";
4540
- const YELLOW$2 = "\x1B[33m";
4620
+ const YELLOW$3 = "\x1B[33m";
4541
4621
  function startLocalServer() {
4542
4622
  return new Promise((resolve) => {
4543
4623
  let resolveCallback;
@@ -4685,7 +4765,7 @@ async function status() {
4685
4765
  console.log(` 工号: ${auth.empId}`);
4686
4766
  console.log(` 网关: ${auth.gatewayUrl}`);
4687
4767
  if (expired) {
4688
- console.log(` Token: ${YELLOW$2}即将过期${RESET$3}`);
4768
+ console.log(` Token: ${YELLOW$3}即将过期${RESET$3}`);
4689
4769
  console.log(`${TEXT$3}建议重新登录: npx ali-skills login${RESET$3}`);
4690
4770
  } else console.log(` Token: ${GREEN$2}有效${RESET$3} (还剩 ${remainingDays} 天)`);
4691
4771
  }
@@ -4773,7 +4853,7 @@ const TEXT$2 = "\x1B[38;5;145m";
4773
4853
  const RESET$2 = "\x1B[0m";
4774
4854
  const GREEN$1 = "\x1B[32m";
4775
4855
  const RED$1 = "\x1B[31m";
4776
- const YELLOW$1 = "\x1B[33m";
4856
+ const YELLOW$2 = "\x1B[33m";
4777
4857
  function openBrowser$1(url) {
4778
4858
  const platform = process.platform;
4779
4859
  spawn(platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open", [url], {
@@ -4808,7 +4888,7 @@ function getPlatformPrivateToken(platformKey, explicitToken) {
4808
4888
  }
4809
4889
  async function promptConfigureToken(platformKey, platform) {
4810
4890
  console.log();
4811
- console.log(`${YELLOW$1}⚠️ ${platform.name} 需要配置 Private Token${RESET$2}`);
4891
+ console.log(`${YELLOW$2}⚠️ ${platform.name} 需要配置 Private Token${RESET$2}`);
4812
4892
  if (platform.tokenDocUrl) console.log(`${DIM$2}Token 获取地址: ${platform.tokenDocUrl}${RESET$2}`);
4813
4893
  if (!await ye({
4814
4894
  message: "是否现在配置?",
@@ -5038,7 +5118,7 @@ const DIM$1 = "\x1B[38;5;102m";
5038
5118
  const TEXT$1 = "\x1B[38;5;145m";
5039
5119
  const RESET$1 = "\x1B[0m";
5040
5120
  const GREEN = "\x1B[32m";
5041
- const YELLOW = "\x1B[33m";
5121
+ const YELLOW$1 = "\x1B[33m";
5042
5122
  const RED = "\x1B[31m";
5043
5123
  function showConfigHelp() {
5044
5124
  console.log(`
@@ -5108,12 +5188,12 @@ async function configurePlatform(platformKey) {
5108
5188
  console.log(`${GREEN}✓ ${platform.name} 使用内置凭证,无需配置${RESET$1}`);
5109
5189
  break;
5110
5190
  case "app_code+buc":
5111
- console.log(`${YELLOW}! ${platform.name} 需要 BUC 登录${RESET$1}`);
5191
+ console.log(`${YELLOW$1}! ${platform.name} 需要 BUC 登录${RESET$1}`);
5112
5192
  console.log(`${DIM$1}请运行: npx ali-skills login${RESET$1}`);
5113
5193
  break;
5114
5194
  case "buc_sso_refresh":
5115
5195
  case "buc_sso_ticket":
5116
- console.log(`${YELLOW}! ${platform.name} 需要 BUC 登录${RESET$1}`);
5196
+ console.log(`${YELLOW$1}! ${platform.name} 需要 BUC 登录${RESET$1}`);
5117
5197
  console.log(`${DIM$1}请运行: npx ali-skills login${RESET$1}`);
5118
5198
  break;
5119
5199
  default:
@@ -5210,6 +5290,7 @@ const RESET = "\x1B[0m";
5210
5290
  const BOLD = "\x1B[1m";
5211
5291
  const DIM = "\x1B[38;5;102m";
5212
5292
  const TEXT = "\x1B[38;5;145m";
5293
+ const YELLOW = "\x1B[33m";
5213
5294
  const LOGO_LINES = [
5214
5295
  " █████╗ ██╗ ██╗ ███████╗██╗ ██╗██╗██╗ ██╗ ███████╗",
5215
5296
  "██╔══██╗██║ ██║ ██╔════╝██║ ██╔╝██║██║ ██║ ██╔════╝",
@@ -5571,26 +5652,42 @@ function readSkillLock() {
5571
5652
  try {
5572
5653
  const content = readFileSync(lockPath, "utf-8");
5573
5654
  const parsed = JSON.parse(content);
5574
- if (typeof parsed.version !== "number" || !parsed.skills) return {
5575
- version: CURRENT_LOCK_VERSION,
5576
- skills: {}
5655
+ if (typeof parsed.version !== "number") return {
5656
+ lock: {
5657
+ ...parsed,
5658
+ version: CURRENT_LOCK_VERSION
5659
+ },
5660
+ isOutdated: true
5577
5661
  };
5578
5662
  if (parsed.version < CURRENT_LOCK_VERSION) return {
5579
- version: CURRENT_LOCK_VERSION,
5580
- skills: {}
5663
+ lock: parsed,
5664
+ isOutdated: true
5665
+ };
5666
+ if (!parsed.skills) return {
5667
+ lock: {
5668
+ version: CURRENT_LOCK_VERSION,
5669
+ skills: {}
5670
+ },
5671
+ isOutdated: false
5672
+ };
5673
+ return {
5674
+ lock: parsed,
5675
+ isOutdated: false
5581
5676
  };
5582
- return parsed;
5583
5677
  } catch {
5584
5678
  return {
5585
- version: CURRENT_LOCK_VERSION,
5586
- skills: {}
5679
+ lock: {
5680
+ version: CURRENT_LOCK_VERSION,
5681
+ skills: {}
5682
+ },
5683
+ isOutdated: false
5587
5684
  };
5588
5685
  }
5589
5686
  }
5590
5687
  function getSkipReason(entry) {
5591
5688
  if (entry.sourceType === "local") return "Local path";
5592
5689
  if (entry.sourceType === "git") return "Git URL (hash tracking not supported)";
5593
- if (!entry.skillFolderHash) return "No version hash available";
5690
+ if (!(entry.sourceType === "internal" ? entry.commitHash : entry.skillFolderHash)) return "No version hash available";
5594
5691
  if (!entry.skillPath) return "No skill path recorded";
5595
5692
  return "No version tracking";
5596
5693
  }
@@ -5599,6 +5696,39 @@ async function checkSkillsForUpdates(skills, token) {
5599
5696
  const errors = [];
5600
5697
  const skipped = [];
5601
5698
  for (const [skillName, entry] of Object.entries(skills)) {
5699
+ if (entry.sourceType === "internal") {
5700
+ if (!entry.commitHash || !entry.skillPath) {
5701
+ skipped.push({
5702
+ name: skillName,
5703
+ reason: getSkipReason(entry),
5704
+ sourceUrl: entry.sourceUrl
5705
+ });
5706
+ continue;
5707
+ }
5708
+ try {
5709
+ const latestCommit = await fetchAliInternalCommitHash(entry.source, entry.skillPath, null);
5710
+ if (!latestCommit) {
5711
+ errors.push({
5712
+ name: skillName,
5713
+ source: entry.source,
5714
+ error: "Could not fetch from GitLab"
5715
+ });
5716
+ continue;
5717
+ }
5718
+ if (latestCommit !== entry.commitHash) updates.push({
5719
+ name: skillName,
5720
+ source: entry.source,
5721
+ entry
5722
+ });
5723
+ } catch (err) {
5724
+ errors.push({
5725
+ name: skillName,
5726
+ source: entry.source,
5727
+ error: err instanceof Error ? err.message : "Unknown error"
5728
+ });
5729
+ }
5730
+ continue;
5731
+ }
5602
5732
  if (!entry.skillFolderHash || !entry.skillPath) {
5603
5733
  skipped.push({
5604
5734
  name: skillName,
@@ -5645,7 +5775,7 @@ async function runCheck(args = []) {
5645
5775
  let skipped = [];
5646
5776
  let skillCount = 0;
5647
5777
  if (isGlobal) {
5648
- const globalLock = readSkillLock();
5778
+ const { lock: globalLock, isOutdated: isGlobalLockOutdated } = readSkillLock();
5649
5779
  const globalSkillNames = Object.keys(globalLock.skills);
5650
5780
  skillCount = globalSkillNames.length;
5651
5781
  if (globalSkillNames.length === 0) {
@@ -5667,9 +5797,11 @@ async function runCheck(args = []) {
5667
5797
  if (skipped.length > 0) {
5668
5798
  console.log();
5669
5799
  console.log(`${DIM} ${skipped.length} skill(s) cannot be checked automatically:${RESET}`);
5800
+ if (isGlobalLockOutdated) console.log(`${YELLOW} Note: Your lock file may be outdated. Re-adding these skills may fix this.${RESET}`);
5670
5801
  for (const skill of skipped) {
5671
5802
  console.log(` ${TEXT}•${RESET} ${skill.name} ${DIM}(${skill.reason})${RESET}`);
5672
- console.log(` ${DIM}To update: ${TEXT}npx ali-skills add ${skill.sourceUrl} -g -y${RESET}`);
5803
+ const displaySource = getDisplaySource(skill.sourceUrl);
5804
+ console.log(` ${DIM}To update: ${TEXT}npx ali-skills add ${displaySource} -g -y${RESET}`);
5673
5805
  }
5674
5806
  }
5675
5807
  console.log();
@@ -5679,7 +5811,7 @@ async function runCheck(args = []) {
5679
5811
  console.log(`${DIM}Run${RESET} ${TEXT}npx ali-skills update -g${RESET} ${DIM}to update all global skills${RESET}`);
5680
5812
  }
5681
5813
  } else {
5682
- const projectLock = await readLocalLock();
5814
+ const { lock: projectLock, isOutdated: isLocalLockOutdated } = await readLocalLock();
5683
5815
  const projectSkillNames = Object.keys(projectLock.skills);
5684
5816
  skillCount = projectSkillNames.length;
5685
5817
  if (projectSkillNames.length === 0) {
@@ -5773,6 +5905,7 @@ async function runCheck(args = []) {
5773
5905
  if (skipped.length > 0) {
5774
5906
  console.log();
5775
5907
  console.log(`${DIM} ${skipped.length} skill(s) cannot be checked automatically:${RESET}`);
5908
+ if (isLocalLockOutdated) console.log(`${YELLOW} Note: Your lock file may be outdated. Re-adding these skills may fix this.${RESET}`);
5776
5909
  for (const skill of skipped) console.log(` ${TEXT}•${RESET} ${skill.name} ${DIM}(${skill.reason})${RESET}`);
5777
5910
  }
5778
5911
  console.log();
@@ -5800,10 +5933,11 @@ async function runUpdate(args = []) {
5800
5933
  let skillsToCheck = {};
5801
5934
  let scopeLabel = "project";
5802
5935
  if (isGlobal) {
5803
- skillsToCheck = readSkillLock().skills;
5936
+ const { lock: globalLock } = readSkillLock();
5937
+ skillsToCheck = globalLock.skills;
5804
5938
  scopeLabel = "global";
5805
5939
  } else {
5806
- const projectLock = await readLocalLock();
5940
+ const { lock: projectLock } = await readLocalLock();
5807
5941
  for (const [name, entry] of Object.entries(projectLock.skills)) {
5808
5942
  let sourceUrl;
5809
5943
  if (entry.sourceType === "github") sourceUrl = `https://github.com/${entry.source}`;
@@ -5829,7 +5963,7 @@ async function runUpdate(args = []) {
5829
5963
  }
5830
5964
  const availableUpdates = [];
5831
5965
  const skipped = [];
5832
- const projectLock = !isGlobal ? await readLocalLock() : null;
5966
+ const projectLock = (!isGlobal ? await readLocalLock() : null)?.lock ?? null;
5833
5967
  for (const skillName of skillNames) {
5834
5968
  const entry = skillsToCheck[skillName];
5835
5969
  if (!entry) continue;
@@ -6090,10 +6224,20 @@ async function main() {
6090
6224
  }
6091
6225
  main().then(async () => {
6092
6226
  if (getPendingTelemetryCount() > 0) {
6227
+ const syncStartAt = Date.now();
6228
+ const longSyncThresholdMs = 800;
6229
+ let didShowStartMessage = false;
6230
+ const startMessageTimer = setTimeout(() => {
6231
+ didShowStartMessage = true;
6232
+ console.log();
6233
+ console.log(`${DIM}Syncing telemetry data...${RESET}`);
6234
+ }, longSyncThresholdMs);
6235
+ await flushTelemetry();
6236
+ clearTimeout(startMessageTimer);
6093
6237
  console.log();
6094
- console.log(`${DIM}Syncing installation data...${RESET}`);
6095
- }
6096
- await flushTelemetry();
6238
+ if (didShowStartMessage || Date.now() - syncStartAt >= longSyncThresholdMs) console.log(`${DIM}Telemetry data sync complete${RESET}`);
6239
+ else console.log(`${DIM}Telemetry data synced${RESET}`);
6240
+ } else await flushTelemetry();
6097
6241
  process.exit(0);
6098
6242
  }).catch(async (error) => {
6099
6243
  console.error(error);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ali/cli-skills",
3
- "version": "2.0.15",
3
+ "version": "2.0.17",
4
4
  "description": "The open agent skills ecosystem",
5
5
  "type": "module",
6
6
  "bin": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ali-skills",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
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.15"
34
+ "@ali/cli-skills": "^2.0.17"
35
35
  },
36
36
  "bundleDependencies": [
37
37
  "@ali/cli-skills"