ali-skills 0.1.3 → 0.1.4

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,7 +2,7 @@ import { i as __toESM } from "./rolldown-runtime.mjs";
2
2
  import { l as pD, u as require_picocolors } from "./libs/@clack/core.mjs";
3
3
  import { a as Y, n as M, s as fe } from "./libs/@clack/prompts.mjs";
4
4
  import { i as SKILLS_API_BASE_URL, n as findPluginPackageRoots, t as validatePluginPackage } from "./plugin-manifest-validate.mjs";
5
- import { A as getDisplaySource, D as redactCliSnippet, F as resolveSourceInput, I as toInternalHttpsCloneUrl, P as parseSource, T as cloneRepo, b as AGENTS_DIR, f as saveSelectedAgents, k as embedInternalGitCredentials, s as getLastSelectedAgents, v as trackPluginCommand, x as PLUGINS_SUBDIR, y as trackPluginInstall } from "./skill-lock.mjs";
5
+ import { A as getDisplaySource, D as redactCliSnippet, F as resolveSourceInput, I as toInternalHttpsCloneUrl, P as parseSource, _ as trackPluginCommand, b as PLUGINS_SUBDIR, d as saveSelectedAgents, k as embedInternalGitCredentials, o as getLastSelectedAgents, v as trackPluginInstall, w as cloneRepo, y as AGENTS_DIR } from "./skill-lock.mjs";
6
6
  import "./libs/@kwsites/file-exists.mjs";
7
7
  import "./libs/@kwsites/promise-deferred.mjs";
8
8
  import "./libs/simple-git.mjs";
@@ -741,4 +741,4 @@ async function runPluginPublish(_options = {}) {
741
741
  spinner.stop(`${import_picocolors.default.green("✓")} Registered ${items.length} plugin(s)`);
742
742
  Se(import_picocolors.default.green("插件录入成功,已可在平台检索安装。"));
743
743
  }
744
- export { addSkillToLocalLock as a, discoverSkills as c, isValidSkillName as d, parseSkillMd as f, logSkillParseFailures as i, filterSkills as l, runPluginPublish as n, computeSkillFolderHash as o, runPublish as r, readLocalLock as s, parsePublishOptions as t, getSkillDisplayName as u };
744
+ export { addSkillToLocalLock as a, writeLocalLock as c, getSkillDisplayName as d, isValidSkillName as f, logSkillParseFailures as i, discoverSkills as l, runPluginPublish as n, computeSkillFolderHash as o, parseSkillMd as p, runPublish as r, readLocalLock as s, parsePublishOptions as t, filterSkills as u };
@@ -64,7 +64,15 @@ function parseOwnerRepo(ownerRepo) {
64
64
  return null;
65
65
  }
66
66
  async function isRepoPrivate(owner, repo) {
67
- return false;
67
+ try {
68
+ const source = encodeURIComponent(`${owner}/${repo}`);
69
+ const res = await fetch(`${SKILLS_API_BASE_URL}/github/repository?source=${source}`);
70
+ if (!res.ok) return null;
71
+ const data = await res.json();
72
+ return data.success && typeof data.data?.private === "boolean" ? data.data.private : null;
73
+ } catch {
74
+ return null;
75
+ }
68
76
  }
69
77
  function sanitizeSubpath(subpath) {
70
78
  const segments = subpath.replace(/\\/g, "/").split("/");
@@ -360,6 +368,9 @@ async function cloneRepo(url, ref) {
360
368
  throw new GitCloneError(`Failed to clone ${safeUrl}: ${safeErrorMessage}`, safeUrl, false, false);
361
369
  }
362
370
  }
371
+ async function getGitHeadHash(repoDir) {
372
+ return (await esm_default(repoDir).revparse(["HEAD"])).trim();
373
+ }
363
374
  async function listRemoteBranches(url) {
364
375
  const git = esm_default({
365
376
  timeout: { block: 15e3 },
@@ -555,72 +566,63 @@ async function writeSkillLock(lock) {
555
566
  await mkdir(dirname(lockPath), { recursive: true });
556
567
  await writeFile(lockPath, JSON.stringify(lock, null, 2), "utf-8");
557
568
  }
558
- function getGitHubToken() {
559
- if (process.env.GITHUB_TOKEN) return process.env.GITHUB_TOKEN;
560
- if (process.env.GH_TOKEN) return process.env.GH_TOKEN;
561
- try {
562
- const token = execSync("gh auth token", {
563
- encoding: "utf-8",
564
- stdio: [
565
- "pipe",
566
- "pipe",
567
- "pipe"
568
- ]
569
- }).trim();
570
- if (token) return token;
571
- } catch {}
572
- return null;
573
- }
574
- async function fetchSkillFolderHash(ownerRepo, skillPath, token) {
575
- let folderPath = skillPath.replace(/\\/g, "/");
576
- if (folderPath.endsWith("/SKILL.md")) folderPath = folderPath.slice(0, -9);
577
- else if (folderPath.endsWith("SKILL.md")) folderPath = folderPath.slice(0, -8);
578
- if (folderPath.endsWith("/")) folderPath = folderPath.slice(0, -1);
579
- for (const ref of [
580
- "HEAD",
581
- "master",
582
- "main"
583
- ]) try {
584
- const url = `https://api.github.com/repos/${ownerRepo}/git/trees/${ref}?recursive=1`;
585
- const headers = {
586
- Accept: "application/vnd.github.v3+json",
587
- "User-Agent": "skills-cli"
588
- };
589
- if (token) headers["Authorization"] = `Bearer ${token}`;
590
- const response = await fetch(url, { headers });
591
- if (!response.ok) continue;
592
- const data = await response.json();
593
- if (!folderPath) return data.sha;
594
- const folderEntry = data.tree.find((entry) => entry.type === "tree" && entry.path === folderPath);
595
- if (folderEntry) return folderEntry.sha;
596
- } catch {
597
- continue;
598
- }
599
- return null;
600
- }
601
- async function fetchAliInternalCommitHash(source, skillPath = "", token) {
569
+ async function fetchAliInternalSkillVersion(source, skillPath = "", token) {
602
570
  const refs = [
603
571
  "HEAD",
604
572
  "master",
605
573
  "main"
606
574
  ];
607
575
  const privateToken = token || process.env.GITLAB_PRIVATE_TOKEN || process.env.CODE_ALIBABA_TOKEN || GITLAB_ACCOUNT.token;
608
- for (const ref of refs) try {
609
- let url = `https://code.alibaba-inc.com/api/v4/projects/${encodeURIComponent(source)}/repository/commits?ref_name=${ref}&per_page=1`;
610
- if (skillPath) url += `&path=${encodeURIComponent(skillPath)}`;
611
- const headers = {
612
- Accept: "application/json",
613
- "User-Agent": "skills-cli",
614
- "Private-Token": privateToken
615
- };
616
- const response = await fetch(url, { headers });
617
- if (!response.ok) continue;
618
- const latestCommit = (await response.json())[0];
619
- if (latestCommit?.id) return latestCommit.id;
620
- } catch {
621
- continue;
576
+ const encodedSource = encodeURIComponent(source);
577
+ const normalizedPath = skillPath.trim().replace(/\\/g, "/").replace(/^\/+|\/+$/g, "").replace(/\/?SKILL\.md$/i, "").replace(/\/+$/g, "");
578
+ const skillFile = normalizedPath ? `${normalizedPath}/SKILL.md` : "SKILL.md";
579
+ const headers = {
580
+ Accept: "application/json",
581
+ "User-Agent": "skills-cli",
582
+ "Private-Token": privateToken
583
+ };
584
+ let sawNotFound = false;
585
+ for (const ref of refs) {
586
+ const controller = new AbortController();
587
+ const timer = setTimeout(() => controller.abort(), 8e3);
588
+ try {
589
+ const fileUrl = `https://code.alibaba-inc.com/api/v4/projects/${encodedSource}/repository/files/${encodeURIComponent(skillFile)}?ref=${encodeURIComponent(ref)}`;
590
+ const fileResponse = await fetch(fileUrl, {
591
+ headers,
592
+ signal: controller.signal
593
+ });
594
+ if (fileResponse.status === 404) {
595
+ sawNotFound = true;
596
+ continue;
597
+ }
598
+ if (fileResponse.status === 401 || fileResponse.status === 403) return { status: "unauthorized" };
599
+ if (fileResponse.status === 429) return { status: "rate-limited" };
600
+ if (!fileResponse.ok) return { status: "upstream-error" };
601
+ let commitUrl = `https://code.alibaba-inc.com/api/v4/projects/${encodedSource}/repository/commits?ref_name=${encodeURIComponent(ref)}&per_page=1`;
602
+ if (normalizedPath) commitUrl += `&path=${encodeURIComponent(normalizedPath)}`;
603
+ const commitResponse = await fetch(commitUrl, {
604
+ headers,
605
+ signal: controller.signal
606
+ });
607
+ if (commitResponse.status === 401 || commitResponse.status === 403) return { status: "unauthorized" };
608
+ if (commitResponse.status === 429) return { status: "rate-limited" };
609
+ if (!commitResponse.ok) return { status: "upstream-error" };
610
+ const commits = await commitResponse.json();
611
+ return commits[0]?.id ? {
612
+ status: "ok",
613
+ latestHash: commits[0].id
614
+ } : { status: "upstream-error" };
615
+ } catch (error) {
616
+ return { status: error instanceof Error && error.name === "AbortError" ? "timeout" : "upstream-error" };
617
+ } finally {
618
+ clearTimeout(timer);
619
+ }
622
620
  }
623
- return null;
621
+ return { status: sawNotFound ? "not-found" : "upstream-error" };
622
+ }
623
+ async function fetchAliInternalCommitHash(source, skillPath = "", token) {
624
+ const result = await fetchAliInternalSkillVersion(source, skillPath, token);
625
+ return result.status === "ok" ? result.latestHash ?? null : null;
624
626
  }
625
627
  async function addSkillToLock(skillName, entry) {
626
628
  const lock = await readSkillLock();
@@ -689,4 +691,4 @@ function sanitizeSourceUrl(url) {
689
691
  }
690
692
  return url;
691
693
  }
692
- export { getDisplaySource as A, GitCloneError as C, redactCliSnippet as D, listRemoteBranches as E, resolveSourceInput as F, toInternalHttpsCloneUrl as I, isRepoPrivate as M, parseOwnerRepo as N, redactSensitiveText as O, parseSource as P, SKILLS_SUBDIR as S, cloneRepo as T, track as _, getAllLockedSkills as a, AGENTS_DIR$1 as b, getSkillFromLock as c, sanitizeSourceUrl as d, saveSelectedAgents as f, setVersion as g, getPendingTelemetryCount as h, fetchSkillFolderHash as i, getOwnerRepo as j, embedInternalGitCredentials as k, isPromptDismissed as l, flushTelemetry as m, dismissPrompt as n, getGitHubToken as o, fetchAuditData as p, fetchAliInternalCommitHash as r, getLastSelectedAgents as s, addSkillToLock as t, removeSkillFromLock as u, trackPluginCommand as v, cleanupTempDir as w, PLUGINS_SUBDIR as x, trackPluginInstall as y };
694
+ export { getDisplaySource as A, cleanupTempDir as C, redactCliSnippet as D, listRemoteBranches as E, resolveSourceInput as F, toInternalHttpsCloneUrl as I, isRepoPrivate as M, parseOwnerRepo as N, redactSensitiveText as O, parseSource as P, GitCloneError as S, getGitHeadHash as T, trackPluginCommand as _, getAllLockedSkills as a, PLUGINS_SUBDIR as b, isPromptDismissed as c, saveSelectedAgents as d, fetchAuditData as f, track as g, setVersion as h, fetchAliInternalSkillVersion as i, getOwnerRepo as j, embedInternalGitCredentials as k, removeSkillFromLock as l, getPendingTelemetryCount as m, dismissPrompt as n, getLastSelectedAgents as o, flushTelemetry as p, fetchAliInternalCommitHash as r, getSkillFromLock as s, addSkillToLock as t, sanitizeSourceUrl as u, trackPluginInstall as v, cloneRepo as w, SKILLS_SUBDIR as x, AGENTS_DIR$1 as y };
@@ -3,24 +3,24 @@ import { i as __toESM, n as __exportAll, r as __require } from "./_chunks/rolldo
3
3
  import { l as pD, u as require_picocolors } from "./_chunks/libs/@clack/core.mjs";
4
4
  import { a as Y, c as ge, d as ye, i as Se, l as ve, n as M, o as be, r as Me, s as fe, t as Ie, u as xe } from "./_chunks/libs/@clack/prompts.mjs";
5
5
  import { i as SKILLS_API_BASE_URL, r as GITLAB_ACCOUNT } from "./_chunks/plugin-manifest-validate.mjs";
6
- import { A as getDisplaySource, C as GitCloneError, E as listRemoteBranches, F as resolveSourceInput, I as toInternalHttpsCloneUrl, M as isRepoPrivate, N as parseOwnerRepo, O as redactSensitiveText, P as parseSource, S as SKILLS_SUBDIR, T as cloneRepo, _ as track, a as getAllLockedSkills, b as AGENTS_DIR$1, c as getSkillFromLock, d as sanitizeSourceUrl, f as saveSelectedAgents, g as setVersion, h as getPendingTelemetryCount, i as fetchSkillFolderHash, j as getOwnerRepo, l as isPromptDismissed, m as flushTelemetry, n as dismissPrompt, o as getGitHubToken, p as fetchAuditData, r as fetchAliInternalCommitHash, s as getLastSelectedAgents, t as addSkillToLock, u as removeSkillFromLock, w as cleanupTempDir } from "./_chunks/skill-lock.mjs";
6
+ import { A as getDisplaySource, C as cleanupTempDir, E as listRemoteBranches, F as resolveSourceInput, I as toInternalHttpsCloneUrl, M as isRepoPrivate, N as parseOwnerRepo, O as redactSensitiveText, P as parseSource, S as GitCloneError, T as getGitHeadHash, a as getAllLockedSkills, c as isPromptDismissed, d as saveSelectedAgents, f as fetchAuditData, g as track, h as setVersion, i as fetchAliInternalSkillVersion, j as getOwnerRepo, l as removeSkillFromLock, m as getPendingTelemetryCount, n as dismissPrompt, o as getLastSelectedAgents, p as flushTelemetry, r as fetchAliInternalCommitHash, s as getSkillFromLock, t as addSkillToLock, u as sanitizeSourceUrl, w as cloneRepo, x as SKILLS_SUBDIR, y as AGENTS_DIR$1 } from "./_chunks/skill-lock.mjs";
7
7
  import "./_chunks/libs/@kwsites/file-exists.mjs";
8
8
  import "./_chunks/libs/@kwsites/promise-deferred.mjs";
9
9
  import "./_chunks/libs/simple-git.mjs";
10
10
  import { t as require_gray_matter } from "./_chunks/libs/gray-matter.mjs";
11
11
  import "./_chunks/libs/extend-shallow.mjs";
12
12
  import "./_chunks/libs/esprima.mjs";
13
- import { a as addSkillToLocalLock, c as discoverSkills, d as isValidSkillName, f as parseSkillMd, i as logSkillParseFailures, l as filterSkills, o as computeSkillFolderHash, r as runPublish, s as readLocalLock, t as parsePublishOptions, u as getSkillDisplayName } from "./_chunks/publish.mjs";
13
+ import { a as addSkillToLocalLock, c as writeLocalLock, d as getSkillDisplayName, f as isValidSkillName, i as logSkillParseFailures, l as discoverSkills, o as computeSkillFolderHash, p as parseSkillMd, r as runPublish, s as readLocalLock, t as parsePublishOptions, u as filterSkills } from "./_chunks/publish.mjs";
14
14
  import { t as xdgConfig } from "./_chunks/libs/xdg-basedir.mjs";
15
- import { execSync, spawn } from "child_process";
15
+ import { spawn } from "child_process";
16
16
  import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "fs";
17
17
  import { basename, dirname, join, normalize, relative, resolve, sep } from "path";
18
- import { homedir, platform as platform$1, tmpdir } from "os";
18
+ import { homedir, platform as platform$1 } from "os";
19
19
  import { createHash } from "crypto";
20
20
  import { URL as URL$1, fileURLToPath } from "url";
21
21
  import * as readline from "readline";
22
22
  import { Writable } from "stream";
23
- import { access, cp, lstat, mkdir, mkdtemp, readdir, readlink, realpath, rm, stat, symlink, writeFile } from "fs/promises";
23
+ import { access, cp, lstat, mkdir, readdir, readlink, realpath, rm, stat, symlink, writeFile } from "fs/promises";
24
24
  import * as http from "http";
25
25
  var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
26
26
  const silentOutput = new Writable({ write(_chunk, _encoding, callback) {
@@ -202,67 +202,32 @@ async function getGitLabProject(ownerRepo, token) {
202
202
  return null;
203
203
  }
204
204
  }
205
- async function getSkillLastCommitAuthor(projectId, filePath, token) {
205
+ async function getSkillLastCommitInfo(projectId, filePath, token) {
206
206
  try {
207
207
  const url = `${CODE_HOST}/api/v3/projects/${projectId}/repository/files/last_commit?filepath=${encodeURIComponent(filePath)}&sha=HEAD&private_token=${token}&projectId=${projectId}`;
208
208
  const res = await fetch(url);
209
209
  if (!res.ok) return null;
210
- const author = (await res.json())?.user;
211
- if (!author) return null;
210
+ const commit = await res.json();
211
+ const hash = commit?.id ?? commit?.short_id ?? "";
212
+ const committedAt = commit?.committed_date ?? commit?.authored_date ?? commit?.created_at ?? "";
213
+ if (!hash && !committedAt) return null;
214
+ const author = commit?.user;
212
215
  return {
213
- employeeId: author.extern_uid ?? "",
214
- email: author.email ?? "",
215
- name: author.name ?? "",
216
- avatarUrl: author.avatar_url ?? "",
217
- username: author.username ?? ""
216
+ hash,
217
+ committedAt,
218
+ ...author && { author: {
219
+ employeeId: author.extern_uid ?? "",
220
+ email: author.email ?? "",
221
+ name: author.name ?? "",
222
+ avatarUrl: author.avatar_url ?? "",
223
+ username: author.username ?? ""
224
+ } }
218
225
  };
219
226
  } catch (error) {
220
- console.error("[GitLab] Failed to get skill last commit author:", error);
227
+ console.error("[GitLab] Failed to get skill last commit info:", error);
221
228
  return null;
222
229
  }
223
230
  }
224
- const RESOLVE_URL = `${SKILLS_API_BASE_URL}/resolve`;
225
- async function resolveFromOss(source, sourceType, skillName) {
226
- try {
227
- const params = new URLSearchParams({
228
- source,
229
- sourceType
230
- });
231
- if (skillName) params.set("skillName", skillName);
232
- const controller = new AbortController();
233
- const timeout = setTimeout(() => controller.abort(), 5e3);
234
- const res = await fetch(`${RESOLVE_URL}?${params.toString()}`, { signal: controller.signal });
235
- clearTimeout(timeout);
236
- if (!res.ok) return null;
237
- const json = await res.json();
238
- if (!json.success || !json.data) return null;
239
- return json.data;
240
- } catch (err) {
241
- console.error("[OSS Fallback] Failed to resolve from OSS:", err);
242
- return null;
243
- }
244
- }
245
- async function downloadAndExtractZip(zipUrl) {
246
- const tempDir = await mkdtemp(join(tmpdir(), "skills-oss-"));
247
- const zipPath = join(tempDir, "skill.zip");
248
- try {
249
- const controller = new AbortController();
250
- const timeout = setTimeout(() => controller.abort(), 6e4);
251
- const res = await fetch(zipUrl, { signal: controller.signal });
252
- clearTimeout(timeout);
253
- if (!res.ok) throw new Error(`Download failed: ${res.status}`);
254
- await writeFile(zipPath, Buffer.from(await res.arrayBuffer()));
255
- execSync(`unzip -q -o "${zipPath}" -d "${tempDir}"`, { stdio: "pipe" });
256
- await rm(zipPath, { force: true }).catch(() => {});
257
- return tempDir;
258
- } catch (error) {
259
- await rm(tempDir, {
260
- recursive: true,
261
- force: true
262
- }).catch(() => {});
263
- throw error;
264
- }
265
- }
266
231
  const home = homedir();
267
232
  const configHome = xdgConfig ?? join(home, ".config");
268
233
  const codexHome = process.env.CODEX_HOME?.trim() || join(home, ".codex");
@@ -947,6 +912,33 @@ async function copyDirectory(src, dest) {
947
912
  }
948
913
  }));
949
914
  }
915
+ async function installSkillCanonical(skill, options = {}) {
916
+ const canonicalBase = getCanonicalSkillsDir(options.global ?? false, options.cwd);
917
+ const canonicalDir = join(canonicalBase, sanitizeName(skill.name || basename(skill.path)));
918
+ if (!isPathSafe(canonicalBase, canonicalDir)) return {
919
+ success: false,
920
+ path: canonicalDir,
921
+ mode: "symlink",
922
+ error: "Invalid skill name: potential path traversal detected"
923
+ };
924
+ try {
925
+ await cleanAndCreateDirectory(canonicalDir);
926
+ await copyDirectory(skill.path, canonicalDir);
927
+ return {
928
+ success: true,
929
+ path: canonicalDir,
930
+ canonicalPath: canonicalDir,
931
+ mode: "symlink"
932
+ };
933
+ } catch (error) {
934
+ return {
935
+ success: false,
936
+ path: canonicalDir,
937
+ mode: "symlink",
938
+ error: error instanceof Error ? error.message : "Unknown error"
939
+ };
940
+ }
941
+ }
950
942
  async function isSkillInstalled(skillName, agentType, options = {}) {
951
943
  const agent = agents[agentType];
952
944
  const sanitized = sanitizeName(skillName);
@@ -1394,7 +1386,178 @@ var WellKnownProvider = class {
1394
1386
  }
1395
1387
  };
1396
1388
  const wellKnownProvider = new WellKnownProvider();
1397
- var version$1 = "2.1.3";
1389
+ var version$1 = "2.1.5";
1390
+ async function checkGithubVersions$1(requests, debugLog = () => {}) {
1391
+ const results = [];
1392
+ for (let offset = 0; offset < requests.length; offset += 100) results.push(...await checkGithubVersionBatch(requests.slice(offset, offset + 100), debugLog));
1393
+ return results;
1394
+ }
1395
+ async function checkGithubVersionBatch(requests, debugLog) {
1396
+ const startedAt = Date.now();
1397
+ const endpoint = `${SKILLS_API_BASE_URL}/check-updates`;
1398
+ debugLog(`POST ${endpoint} skills=${requests.length}`);
1399
+ try {
1400
+ const response = await fetch(endpoint, {
1401
+ method: "POST",
1402
+ headers: { "Content-Type": "application/json" },
1403
+ body: JSON.stringify({ skills: requests })
1404
+ });
1405
+ debugLog(`POST ${endpoint} status=${response.status} durationMs=${Date.now() - startedAt}`);
1406
+ if (!response.ok) return requests.map((request) => ({
1407
+ ...request,
1408
+ status: "upstream-error"
1409
+ }));
1410
+ const payload = await response.json();
1411
+ return payload.success && Array.isArray(payload.data?.results) ? payload.data.results : requests.map((request) => ({
1412
+ ...request,
1413
+ status: "upstream-error"
1414
+ }));
1415
+ } catch (error) {
1416
+ debugLog(`POST ${endpoint} failed durationMs=${Date.now() - startedAt} error=${error instanceof Error ? error.name : "unknown"}`);
1417
+ return requests.map((request) => ({
1418
+ ...request,
1419
+ status: "upstream-error"
1420
+ }));
1421
+ }
1422
+ }
1423
+ function normalizeSkillPath(skillPath) {
1424
+ return (skillPath ?? "").trim().replace(/\\/g, "/").replace(/^\/+|\/+$/g, "").replace(/\/?SKILL\.md$/i, "").replace(/\/+$/g, "");
1425
+ }
1426
+ function remoteKey(request) {
1427
+ return `${request.name}\0${request.source}\0${request.skillPath}`;
1428
+ }
1429
+ function fromRemote(entry, skillPath, remote, localHash) {
1430
+ const base = {
1431
+ name: entry.name,
1432
+ source: entry.source,
1433
+ entry,
1434
+ skillPath
1435
+ };
1436
+ if (!remote) return {
1437
+ ...base,
1438
+ status: "check-failed",
1439
+ reason: "missing-response"
1440
+ };
1441
+ if (remote.status === "not-found") return {
1442
+ ...base,
1443
+ status: "invalid-skill-path",
1444
+ reason: "SKILL.md not found"
1445
+ };
1446
+ if (remote.status !== "ok" || !remote.latestHash) return {
1447
+ ...base,
1448
+ status: "check-failed",
1449
+ reason: remote.status
1450
+ };
1451
+ if (!localHash) return {
1452
+ ...base,
1453
+ status: "update-required",
1454
+ reason: "missing-version",
1455
+ latestHash: remote.latestHash
1456
+ };
1457
+ if (localHash !== remote.latestHash) return {
1458
+ ...base,
1459
+ status: "update-required",
1460
+ reason: "version-changed",
1461
+ latestHash: remote.latestHash
1462
+ };
1463
+ return {
1464
+ ...base,
1465
+ status: "up-to-date",
1466
+ latestHash: remote.latestHash
1467
+ };
1468
+ }
1469
+ function fromGithubRemote(entry, skillPath, remote) {
1470
+ const base = {
1471
+ name: entry.name,
1472
+ source: entry.source,
1473
+ entry,
1474
+ skillPath
1475
+ };
1476
+ if (!remote) return {
1477
+ ...base,
1478
+ status: "check-failed",
1479
+ reason: "missing-response"
1480
+ };
1481
+ if (remote.status === "not-found") return {
1482
+ ...base,
1483
+ status: "invalid-skill-path",
1484
+ reason: "SKILL.md not found"
1485
+ };
1486
+ if (remote.status !== "ok" || !remote.latestHash) return {
1487
+ ...base,
1488
+ status: "check-failed",
1489
+ reason: remote.status
1490
+ };
1491
+ if (entry.commitHash) return fromRemote(entry, skillPath, remote, entry.commitHash);
1492
+ if (entry.skillFolderHash) {
1493
+ if (!remote.latestTreeHash) return {
1494
+ ...base,
1495
+ status: "check-failed",
1496
+ reason: "missing-legacy-tree"
1497
+ };
1498
+ if (entry.skillFolderHash !== remote.latestTreeHash) return {
1499
+ ...base,
1500
+ status: "update-required",
1501
+ reason: "version-changed",
1502
+ latestHash: remote.latestHash
1503
+ };
1504
+ return {
1505
+ ...base,
1506
+ status: "up-to-date",
1507
+ latestHash: remote.latestHash,
1508
+ migrationCommitHash: remote.latestHash
1509
+ };
1510
+ }
1511
+ return {
1512
+ ...base,
1513
+ status: "update-required",
1514
+ reason: "missing-version",
1515
+ latestHash: remote.latestHash
1516
+ };
1517
+ }
1518
+ async function checkSkillUpdates(entries, dependencies) {
1519
+ const results = /* @__PURE__ */ new Map();
1520
+ const githubEntries = entries.filter((entry) => entry.sourceType === "github");
1521
+ const githubRequests = githubEntries.map((entry) => ({
1522
+ name: entry.name,
1523
+ source: entry.checkSource ?? entry.source,
1524
+ skillPath: normalizeSkillPath(entry.skillPath),
1525
+ ...!entry.commitHash && entry.skillFolderHash ? { legacySkillFolderHash: entry.skillFolderHash } : {}
1526
+ }));
1527
+ const githubResults = githubRequests.length ? await dependencies.checkGithub(githubRequests) : [];
1528
+ const githubByKey = new Map(githubResults.map((result) => [remoteKey(result), result]));
1529
+ for (const entry of githubEntries) {
1530
+ const skillPath = normalizeSkillPath(entry.skillPath);
1531
+ const request = {
1532
+ name: entry.name,
1533
+ source: entry.checkSource ?? entry.source,
1534
+ skillPath
1535
+ };
1536
+ results.set(entry.name, fromGithubRemote(entry, skillPath, githubByKey.get(remoteKey(request))));
1537
+ }
1538
+ await Promise.all(entries.filter((entry) => entry.sourceType === "internal").map(async (entry) => {
1539
+ const skillPath = normalizeSkillPath(entry.skillPath);
1540
+ const remote = await dependencies.checkInternal({
1541
+ name: entry.name,
1542
+ source: entry.source,
1543
+ skillPath
1544
+ }).catch(() => void 0);
1545
+ results.set(entry.name, fromRemote(entry, skillPath, remote, entry.commitHash));
1546
+ }));
1547
+ for (const entry of entries) {
1548
+ if (results.has(entry.name)) continue;
1549
+ const skillPath = normalizeSkillPath(entry.skillPath);
1550
+ results.set(entry.name, {
1551
+ name: entry.name,
1552
+ source: entry.source,
1553
+ entry,
1554
+ skillPath,
1555
+ status: "unsupported",
1556
+ reason: entry.sourceType === "local" ? "Local path" : "Version tracking not supported"
1557
+ });
1558
+ }
1559
+ return entries.map((entry) => results.get(entry.name));
1560
+ }
1398
1561
  const isCancelled$1 = (value) => typeof value === "symbol";
1399
1562
  async function isSourcePrivate(source) {
1400
1563
  const ownerRepo = parseOwnerRepo(source);
@@ -1939,7 +2102,6 @@ async function runAdd(args, options = {}) {
1939
2102
  return;
1940
2103
  }
1941
2104
  let skillsDir;
1942
- let usedOssFallback = false;
1943
2105
  if (parsed.type === "local") {
1944
2106
  spinner.start("Validating local path...");
1945
2107
  if (!existsSync(parsed.localPath)) {
@@ -1951,47 +2113,10 @@ async function runAdd(args, options = {}) {
1951
2113
  spinner.stop("Local path validated");
1952
2114
  } else {
1953
2115
  spinner.start("Cloning repository...");
1954
- try {
1955
- const ref = parsed.ref === "HEAD" ? void 0 : parsed.ref;
1956
- tempDir = await cloneRepo(parsed.url, ref);
1957
- skillsDir = tempDir;
1958
- spinner.stop("Repository cloned");
1959
- } catch (cloneError) {
1960
- if (![
1961
- "github",
1962
- "internal",
1963
- "git"
1964
- ].includes(parsed.type)) throw cloneError;
1965
- if (cloneError instanceof GitCloneError && cloneError.isAuthError && cloneError.url.includes("code.alibaba-inc.com")) {
1966
- spinner.stop(import_picocolors.default.yellow("Git clone failed: 无访问权限"));
1967
- const gitlabUser = GITLAB_ACCOUNT.user;
1968
- console.log();
1969
- M.warn(import_picocolors.default.yellow(`仓库未授权给 ${import_picocolors.default.bold(gitlabUser)} 账号,请按以下步骤授权:`));
1970
- M.message([
1971
- ` ${import_picocolors.default.cyan("推荐")} 在 ${import_picocolors.default.bold("Group")} 层级将 ${import_picocolors.default.cyan(gitlabUser)} 添加为 ${import_picocolors.default.bold("Owner")} 角色`,
1972
- ` 路径:GitLab Group → Manage → Members → Invite members`,
1973
- ` ${import_picocolors.default.dim("最低")} 在 ${import_picocolors.default.bold("Project")} 层级将 ${import_picocolors.default.cyan(gitlabUser)} 添加为 ${import_picocolors.default.bold("Reporter")} 及以上角色(可下载代码)`,
1974
- ` 路径:GitLab Project → Manage → Members → Invite members`
1975
- ].join("\n"));
1976
- console.log();
1977
- M.info("正在尝试 OSS 备用下载...");
1978
- } else spinner.stop(import_picocolors.default.yellow("Git clone failed, trying OSS fallback..."));
1979
- const normalizedSource = getOwnerRepo(parsed);
1980
- if (!normalizedSource) throw cloneError;
1981
- const singleSkillForOss = parsed.skillFilter ?? (options.skill?.length === 1 && !options.skill.includes("*") ? options.skill[0] : void 0);
1982
- try {
1983
- const resolved = await resolveFromOss(normalizedSource, parsed.type, singleSkillForOss);
1984
- if (!resolved) throw cloneError;
1985
- spinner.start("Downloading skill package from OSS...");
1986
- const extractedDir = await downloadAndExtractZip(resolved.zipUrl);
1987
- tempDir = extractedDir;
1988
- skillsDir = extractedDir;
1989
- usedOssFallback = true;
1990
- spinner.stop("Skill package downloaded");
1991
- } catch {
1992
- throw cloneError;
1993
- }
1994
- }
2116
+ const ref = parsed.ref === "HEAD" ? void 0 : parsed.ref;
2117
+ tempDir = await cloneRepo(parsed.url, ref);
2118
+ skillsDir = tempDir;
2119
+ spinner.stop("Repository cloned");
1995
2120
  }
1996
2121
  if (parsed.skillFilter) {
1997
2122
  options.skill = options.skill || [];
@@ -2005,33 +2130,7 @@ async function runAdd(args, options = {}) {
2005
2130
  parseFailures: skillParseFailures
2006
2131
  };
2007
2132
  spinner.start("Discovering skills...");
2008
- let skills = await discoverSkills(skillsDir, parsed.subpath, discoverOpts);
2009
- if (skills.length === 0 && !usedOssFallback && [
2010
- "github",
2011
- "internal",
2012
- "git"
2013
- ].includes(parsed.type)) {
2014
- const normalizedSource = getOwnerRepo(parsed);
2015
- if (normalizedSource) {
2016
- spinner.stop(import_picocolors.default.yellow("默认分支未找到 skill,检查 OSS 已发布版本..."));
2017
- const singleSkillForOss = options.skill?.length === 1 && !options.skill.includes("*") ? options.skill[0] : void 0;
2018
- try {
2019
- const resolved = await resolveFromOss(normalizedSource, parsed.type, singleSkillForOss);
2020
- if (resolved) {
2021
- spinner.start("Downloading skill package from OSS...");
2022
- const ossDir = await downloadAndExtractZip(resolved.zipUrl);
2023
- await cleanup(tempDir);
2024
- tempDir = ossDir;
2025
- skillsDir = ossDir;
2026
- usedOssFallback = true;
2027
- spinner.stop("Skill package downloaded");
2028
- spinner.start("Discovering skills...");
2029
- skillParseFailures.length = 0;
2030
- skills = await discoverSkills(skillsDir, parsed.subpath, discoverOpts);
2031
- }
2032
- } catch {}
2033
- }
2034
- }
2133
+ const skills = await discoverSkills(skillsDir, parsed.subpath, discoverOpts);
2035
2134
  if (skills.length === 0) {
2036
2135
  spinner.stop(import_picocolors.default.red("No skills found"));
2037
2136
  logSkillParseFailures(skillParseFailures);
@@ -2039,7 +2138,7 @@ async function runAdd(args, options = {}) {
2039
2138
  await cleanup(tempDir);
2040
2139
  process.exit(1);
2041
2140
  }
2042
- spinner.stop(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}${usedOssFallback ? import_picocolors.default.dim(" (OSS)") : ""}`);
2141
+ spinner.stop(`Found ${import_picocolors.default.green(skills.length)} skill${skills.length > 1 ? "s" : ""}`);
2043
2142
  if (options.list) {
2044
2143
  console.log();
2045
2144
  M.step(import_picocolors.default.bold("Available Skills"));
@@ -2140,7 +2239,8 @@ async function runAdd(args, options = {}) {
2140
2239
  const auditPromise = ownerRepoForAudit ? fetchAuditData(ownerRepoForAudit, selectedSkills.map((s) => getSkillDisplayName(s)), parsed.type) : Promise.resolve(null);
2141
2240
  let targetAgents;
2142
2241
  const validAgents = Object.keys(agents);
2143
- if (options.agent?.includes("*")) {
2242
+ if (options.canonicalOnly) targetAgents = [];
2243
+ else if (options.agent?.includes("*")) {
2144
2244
  targetAgents = validAgents;
2145
2245
  M.info(`Installing to all ${targetAgents.length} agents`);
2146
2246
  } else if (options.agent && options.agent.length > 0) {
@@ -2189,7 +2289,7 @@ async function runAdd(args, options = {}) {
2189
2289
  targetAgents = selected;
2190
2290
  }
2191
2291
  }
2192
- let installGlobally = options.global ?? false;
2292
+ let installGlobally = options.canonicalOnly ? true : options.global ?? false;
2193
2293
  const supportsGlobal = targetAgents.some((a) => agents[a].globalSkillsDir !== void 0);
2194
2294
  if (options.global === void 0 && !options.yes && supportsGlobal) {
2195
2295
  const scope = await ve({
@@ -2248,7 +2348,8 @@ async function runAdd(args, options = {}) {
2248
2348
  if (summaryLines.length > 0) summaryLines.push("");
2249
2349
  const shortCanonical = shortenPath$2(getEntityPath(skill.name, installMode, installGlobally, cwd), cwd);
2250
2350
  summaryLines.push(`${import_picocolors.default.cyan(shortCanonical)}`);
2251
- summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
2351
+ if (options.canonicalOnly) summaryLines.push(` ${import_picocolors.default.dim("shared entity only (not linked)")}`);
2352
+ else summaryLines.push(...buildAgentSummaryLines(targetAgents, installMode));
2252
2353
  const skillOverwrites = overwriteStatus.get(skill.name);
2253
2354
  const overwriteAgents = targetAgents.filter((a) => skillOverwrites?.get(a)).map((a) => agents[a].displayName);
2254
2355
  if (overwriteAgents.length > 0) summaryLines.push(` ${import_picocolors.default.yellow("overwrites:")} ${formatList$1(overwriteAgents)}`);
@@ -2291,17 +2392,29 @@ async function runAdd(args, options = {}) {
2291
2392
  spinner.start("Installing skills...");
2292
2393
  const results = [];
2293
2394
  const effectiveAgents = installMode === "copy" || installMode === "copy-isolated" ? targetAgents.filter((a, i, arr) => arr.findIndex((b) => agents[b].skillsDir === agents[a].skillsDir) === i) : targetAgents;
2294
- for (const skill of selectedSkills) for (const agent of effectiveAgents) {
2295
- const result = await installSkillForAgent(skill, agent, {
2296
- global: installGlobally,
2297
- mode: installMode
2298
- });
2299
- results.push({
2300
- skill: getSkillDisplayName(skill),
2301
- agent: agents[agent].displayName,
2302
- pluginName: skill.pluginName,
2303
- ...result
2304
- });
2395
+ for (const skill of selectedSkills) {
2396
+ if (options.canonicalOnly) {
2397
+ const result = await installSkillCanonical(skill, { global: true });
2398
+ results.push({
2399
+ skill: getSkillDisplayName(skill),
2400
+ agent: "Shared",
2401
+ pluginName: skill.pluginName,
2402
+ ...result
2403
+ });
2404
+ continue;
2405
+ }
2406
+ for (const agent of effectiveAgents) {
2407
+ const result = await installSkillForAgent(skill, agent, {
2408
+ global: installGlobally,
2409
+ mode: installMode
2410
+ });
2411
+ results.push({
2412
+ skill: getSkillDisplayName(skill),
2413
+ agent: agents[agent].displayName,
2414
+ pluginName: skill.pluginName,
2415
+ ...result
2416
+ });
2417
+ }
2305
2418
  }
2306
2419
  spinner.stop("Installation complete");
2307
2420
  console.log();
@@ -2328,6 +2441,7 @@ async function runAdd(args, options = {}) {
2328
2441
  const lockSource = parsed.url.startsWith("git@") ? parsed.url : normalizedSource;
2329
2442
  const trackableSkillNames = selectedSkills.filter((s) => isValidSkillName(s.name)).map((s) => s.name);
2330
2443
  const skillMdLastCommitAuthors = {};
2444
+ const skillMdLatestVersions = {};
2331
2445
  if (parsed.type === "internal" && normalizedSource) {
2332
2446
  const gitlabToken = GITLAB_ACCOUNT.token;
2333
2447
  if (gitlabToken) {
@@ -2335,7 +2449,12 @@ async function runAdd(args, options = {}) {
2335
2449
  if (project) for (const skillName of trackableSkillNames) {
2336
2450
  const skillFilePath = skillFiles[skillName];
2337
2451
  if (skillFilePath) {
2338
- const author = await getSkillLastCommitAuthor(project.id, skillFilePath, gitlabToken);
2452
+ const commitInfo = await getSkillLastCommitInfo(project.id, skillFilePath, gitlabToken);
2453
+ if (commitInfo?.hash || commitInfo?.committedAt) skillMdLatestVersions[skillName] = {
2454
+ hash: commitInfo.hash,
2455
+ committedAt: commitInfo.committedAt
2456
+ };
2457
+ const author = commitInfo?.author;
2339
2458
  if (author) {
2340
2459
  const formattedName = author.username ? `${author.name}/${author.username}` : author.name;
2341
2460
  skillMdLastCommitAuthors[skillName] = {
@@ -2360,6 +2479,7 @@ async function runAdd(args, options = {}) {
2360
2479
  skillFiles: JSON.stringify(skillFiles),
2361
2480
  sourceType: effectiveSourceType,
2362
2481
  skillMdLastCommitAuthors: JSON.stringify(skillMdLastCommitAuthors),
2482
+ skillMdLatestVersions: JSON.stringify(skillMdLatestVersions),
2363
2483
  skillMdConfigAuthors: JSON.stringify(skillMdAuthors),
2364
2484
  descriptions: JSON.stringify(skillDescriptions)
2365
2485
  });
@@ -2372,27 +2492,37 @@ async function runAdd(args, options = {}) {
2372
2492
  skillFiles: JSON.stringify(skillFiles),
2373
2493
  sourceType: effectiveSourceType,
2374
2494
  skillMdLastCommitAuthors: JSON.stringify(skillMdLastCommitAuthors),
2495
+ skillMdLatestVersions: JSON.stringify(skillMdLatestVersions),
2375
2496
  skillMdConfigAuthors: JSON.stringify(skillMdAuthors),
2376
2497
  descriptions: JSON.stringify(skillDescriptions)
2377
2498
  });
2378
2499
  }
2500
+ const githubCommitHashes = /* @__PURE__ */ new Map();
2501
+ if (successful.length > 0 && effectiveSourceType === "github" && tempDir) try {
2502
+ const successfulSkillNames = new Set(successful.map((result) => result.skill));
2503
+ const source = normalizedSource || getOwnerRepo(parsed);
2504
+ if (source) {
2505
+ const ref = await getGitHeadHash(tempDir);
2506
+ (await checkGithubVersions$1(selectedSkills.filter((skill) => successfulSkillNames.has(getSkillDisplayName(skill))).map((skill) => ({
2507
+ name: skill.name,
2508
+ source,
2509
+ skillPath: normalizeSkillPath(skillFiles[skill.name]),
2510
+ ref
2511
+ })))).forEach((version) => {
2512
+ if (version.status === "ok" && version.latestHash) githubCommitHashes.set(version.name, version.latestHash);
2513
+ });
2514
+ }
2515
+ } catch {}
2379
2516
  if (successful.length > 0 && installGlobally && normalizedSource) {
2380
2517
  const successfulSkillNames = new Set(successful.map((r) => r.skill));
2381
2518
  for (const skill of selectedSkills) {
2382
2519
  const skillDisplayName = getSkillDisplayName(skill);
2383
2520
  if (successfulSkillNames.has(skillDisplayName)) try {
2384
- let skillFolderHash;
2385
2521
  let commitHash;
2386
2522
  const skillPathValue = skillFiles[skill.name];
2387
- let skillPath = skillPathValue;
2388
- if (skillPath) {
2389
- if (skillPath.endsWith("/SKILL.md")) skillPath = skillPath.slice(0, -9);
2390
- else if (skillPath.endsWith("SKILL.md")) skillPath = skillPath.slice(0, -8);
2391
- }
2392
- if (effectiveSourceType === "github" && skillPathValue) {
2393
- const hash = await fetchSkillFolderHash(normalizedSource, skillPathValue, getGitHubToken());
2394
- if (hash) skillFolderHash = hash;
2395
- } else if (effectiveSourceType === "internal" && skillPath !== void 0) {
2523
+ const skillPath = normalizeSkillPath(skillPathValue);
2524
+ if (effectiveSourceType === "github") commitHash = githubCommitHashes.get(skill.name);
2525
+ else if (effectiveSourceType === "internal") {
2396
2526
  const hash = await fetchAliInternalCommitHash(normalizedSource, skillPath, null);
2397
2527
  if (hash) commitHash = hash;
2398
2528
  }
@@ -2401,7 +2531,6 @@ async function runAdd(args, options = {}) {
2401
2531
  sourceType: effectiveSourceType,
2402
2532
  sourceUrl: sanitizeSourceUrl(parsed.url),
2403
2533
  skillPath,
2404
- ...skillFolderHash && { skillFolderHash },
2405
2534
  ...commitHash && { commitHash },
2406
2535
  pluginName: skill.pluginName
2407
2536
  });
@@ -2421,10 +2550,10 @@ async function runAdd(args, options = {}) {
2421
2550
  sourceType: effectiveSourceType,
2422
2551
  computedHash
2423
2552
  };
2424
- if (effectiveSourceType === "github" && skillFilePath && normalizedSource) {
2425
- const folderHash = await fetchSkillFolderHash(normalizedSource, skillFilePath, getGitHubToken());
2426
- if (folderHash) lockEntry.skillFolderHash = folderHash;
2427
- lockEntry.skillPath = skillFilePath.replace(/\/?SKILL\.md$/i, "").replace(/\/+$/, "");
2553
+ if (effectiveSourceType === "github") {
2554
+ const commitHash = githubCommitHashes.get(skill.name);
2555
+ if (commitHash) lockEntry.commitHash = commitHash;
2556
+ lockEntry.skillPath = normalizeSkillPath(skillFilePath);
2428
2557
  }
2429
2558
  if (effectiveSourceType === "internal") {
2430
2559
  const source = normalizedSource || getOwnerRepo(parsed) || parsed.url;
@@ -5740,16 +5869,6 @@ async function runPlatformLogin(platformArgs) {
5740
5869
  }
5741
5870
  await configurePlatform(platform, platform === "yuque" ? extraArgs[0] : void 0);
5742
5871
  }
5743
- function getUpdateComparisonStrategy(entry) {
5744
- if (entry.sourceType === "internal") return {
5745
- field: "commitHash",
5746
- value: entry.commitHash
5747
- };
5748
- return {
5749
- field: "skillFolderHash",
5750
- value: entry.skillFolderHash
5751
- };
5752
- }
5753
5872
  function parseScopeFromArgs(args) {
5754
5873
  let scopeGlobal;
5755
5874
  let isScopeSpecified = false;
@@ -5768,6 +5887,20 @@ function parseScopeFromArgs(args) {
5768
5887
  positionalArgs
5769
5888
  };
5770
5889
  }
5890
+ function resolveUpdateInstallTarget(installedAgents, global) {
5891
+ if (!installedAgents) return { mode: "skip" };
5892
+ if (installedAgents.length > 0) return {
5893
+ mode: "agents",
5894
+ agents: installedAgents
5895
+ };
5896
+ return global ? { mode: "canonical" } : { mode: "skip" };
5897
+ }
5898
+ function migrateGithubTreeToCommit(entry, expectedTreeHash, commitHash) {
5899
+ if (entry?.sourceType !== "github" || entry.commitHash || entry.skillFolderHash !== expectedTreeHash) return false;
5900
+ entry.commitHash = commitHash;
5901
+ delete entry.skillFolderHash;
5902
+ return true;
5903
+ }
5771
5904
  const __dirname = dirname(fileURLToPath(import.meta.url));
5772
5905
  function getVersion() {
5773
5906
  try {
@@ -5793,6 +5926,13 @@ function isTruthyEnv(value) {
5793
5926
  function isUpdateDebugEnabled() {
5794
5927
  return isTruthyEnv(process.env.ALI_SKILLS_DEBUG_UPDATE) || isTruthyEnv(process.env.DEBUG);
5795
5928
  }
5929
+ function isCheckDebugEnabled() {
5930
+ return isTruthyEnv(process.env.ALI_SKILLS_DEBUG_CHECK) || isTruthyEnv(process.env.DEBUG);
5931
+ }
5932
+ function checkDebugLog(message) {
5933
+ if (!isCheckDebugEnabled() && !isUpdateDebugEnabled()) return;
5934
+ console.log(`${DIM}[check-debug]${RESET} ${message}`);
5935
+ }
5796
5936
  function updateDebugLog(enabled, message) {
5797
5937
  if (!enabled) return;
5798
5938
  console.log(`${DIM}[update-debug]${RESET} ${message}`);
@@ -6465,88 +6605,83 @@ function readSkillLock() {
6465
6605
  };
6466
6606
  }
6467
6607
  }
6468
- function getSkipReason(entry) {
6469
- if (entry.sourceType === "local") return "Local path";
6470
- if (entry.sourceType === "git") return "Git URL (hash tracking not supported)";
6471
- if (!getUpdateComparisonStrategy(entry).value) return "No version hash available";
6472
- if (!entry.skillPath) return "No skill path recorded";
6473
- return "No version tracking";
6474
- }
6475
- async function checkSkillsForUpdates(skills, token) {
6476
- const updates = [];
6477
- const errors = [];
6478
- const skipped = [];
6479
- for (const [skillName, entry] of Object.entries(skills)) {
6480
- if (entry.sourceType === "internal") {
6481
- if (!entry.commitHash || !entry.skillPath) {
6482
- skipped.push({
6483
- name: skillName,
6484
- reason: getSkipReason(entry),
6485
- sourceUrl: entry.sourceUrl
6486
- });
6487
- continue;
6488
- }
6489
- try {
6490
- const latestCommit = await fetchAliInternalCommitHash(entry.source, entry.skillPath, null);
6491
- if (!latestCommit) {
6492
- errors.push({
6493
- name: skillName,
6494
- source: entry.source,
6495
- error: "Could not fetch from GitLab"
6496
- });
6497
- continue;
6498
- }
6499
- if (latestCommit !== entry.commitHash) updates.push({
6500
- name: skillName,
6501
- source: entry.source,
6502
- entry
6503
- });
6504
- } catch (err) {
6505
- errors.push({
6506
- name: skillName,
6507
- source: entry.source,
6508
- error: err instanceof Error ? err.message : "Unknown error"
6509
- });
6510
- }
6511
- continue;
6512
- }
6513
- if (!entry.skillFolderHash || !entry.skillPath) {
6514
- skipped.push({
6515
- name: skillName,
6516
- reason: getSkipReason(entry),
6517
- sourceUrl: entry.sourceUrl
6518
- });
6519
- continue;
6520
- }
6521
- try {
6522
- const latestHash = await fetchSkillFolderHash(entry.source, entry.skillPath, token);
6523
- if (!latestHash) {
6524
- errors.push({
6525
- name: skillName,
6526
- source: entry.source,
6527
- error: "Could not fetch from GitHub"
6528
- });
6529
- continue;
6530
- }
6531
- if (latestHash !== entry.skillFolderHash) updates.push({
6532
- name: skillName,
6533
- source: entry.source,
6534
- entry
6535
- });
6536
- } catch (err) {
6537
- errors.push({
6538
- name: skillName,
6539
- source: entry.source,
6540
- error: err instanceof Error ? err.message : "Unknown error"
6541
- });
6542
- }
6608
+ function printCheckErrors(errors) {
6609
+ if (errors.length === 0) return;
6610
+ console.log();
6611
+ console.log(`${YELLOW}${errors.length} skill(s) failed during update checking:${RESET}`);
6612
+ for (const error of errors) console.log(` ${YELLOW}⚠${RESET} ${error.name} ${DIM}(${error.source}: ${error.error})${RESET}`);
6613
+ console.log(`${DIM}Re-run with ALI_SKILLS_DEBUG_CHECK=1 for sanitized request diagnostics.${RESET}`);
6614
+ }
6615
+ async function checkGithubVersions(requests) {
6616
+ return checkGithubVersions$1(requests, checkDebugLog);
6617
+ }
6618
+ async function checkInternalVersion(request) {
6619
+ try {
6620
+ const result = await fetchAliInternalSkillVersion(request.source, request.skillPath, null);
6621
+ return {
6622
+ ...request,
6623
+ ...result
6624
+ };
6625
+ } catch {
6626
+ return {
6627
+ ...request,
6628
+ status: "upstream-error"
6629
+ };
6543
6630
  }
6631
+ }
6632
+ async function checkSkillsForUpdates(skills) {
6633
+ const results = await checkSkillUpdates(Object.entries(skills).map(([name, entry]) => ({
6634
+ ...entry,
6635
+ name,
6636
+ ...entry.sourceType === "github" ? { checkSource: getOwnerRepo(parseSource(entry.source)) ?? getOwnerRepo(parseSource(entry.sourceUrl)) ?? entry.source } : {}
6637
+ })), {
6638
+ checkGithub: checkGithubVersions,
6639
+ checkInternal: checkInternalVersion
6640
+ });
6544
6641
  return {
6545
- updates,
6546
- errors,
6547
- skipped
6642
+ updates: results.filter((result) => result.status === "update-required").map((result) => ({
6643
+ name: result.name,
6644
+ source: result.source,
6645
+ entry: result.entry
6646
+ })),
6647
+ errors: results.filter((result) => result.status === "check-failed").map((result) => ({
6648
+ name: result.name,
6649
+ source: result.source,
6650
+ error: result.reason ?? "Unknown error"
6651
+ })),
6652
+ skipped: results.filter((result) => result.status === "unsupported" || result.status === "invalid-skill-path").map((result) => ({
6653
+ name: result.name,
6654
+ reason: result.reason ?? "No version tracking",
6655
+ sourceUrl: result.entry.sourceUrl
6656
+ })),
6657
+ migrations: results.filter((result) => result.status === "up-to-date" && Boolean(result.migrationCommitHash) && Boolean(result.entry.skillFolderHash)).map((result) => ({
6658
+ name: result.name,
6659
+ expectedTreeHash: result.entry.skillFolderHash,
6660
+ commitHash: result.migrationCommitHash
6661
+ }))
6548
6662
  };
6549
6663
  }
6664
+ async function applySafeGithubLockMigrations(migrations, isGlobal) {
6665
+ if (migrations.length === 0) return 0;
6666
+ if (isGlobal) {
6667
+ const { lock } = readSkillLock();
6668
+ let changed = 0;
6669
+ for (const migration of migrations) {
6670
+ const entry = lock.skills[migration.name];
6671
+ if (migrateGithubTreeToCommit(entry, migration.expectedTreeHash, migration.commitHash)) changed++;
6672
+ }
6673
+ if (changed > 0) writeFileSync(getSkillLockPath(), JSON.stringify(lock, null, 2), "utf-8");
6674
+ return changed;
6675
+ }
6676
+ const { lock } = await readLocalLock();
6677
+ let changed = 0;
6678
+ for (const migration of migrations) {
6679
+ const entry = lock.skills[migration.name];
6680
+ if (migrateGithubTreeToCommit(entry, migration.expectedTreeHash, migration.commitHash)) changed++;
6681
+ }
6682
+ if (changed > 0) await writeLocalLock(lock);
6683
+ return changed;
6684
+ }
6550
6685
  async function runCheck(args = []) {
6551
6686
  const { isGlobal, isScopeSpecified, positionalArgs: skillArgs } = parseScopeFromArgs(args);
6552
6687
  console.log(`${TEXT}Checking for skill updates...${RESET}`);
@@ -6555,8 +6690,8 @@ async function runCheck(args = []) {
6555
6690
  console.log(`${DIM}提示: 当前为项目级检查(skills-lock.json)。如需检查全局技能,请执行: ${TEXT}npx ali-skills check -g${RESET}`);
6556
6691
  console.log();
6557
6692
  }
6558
- const token = getGitHubToken();
6559
6693
  let updates = [];
6694
+ let errors = [];
6560
6695
  let skipped = [];
6561
6696
  let skillCount = 0;
6562
6697
  if (isGlobal) {
@@ -6582,11 +6717,12 @@ async function runCheck(args = []) {
6582
6717
  skillCount = selectedGlobalSkillNames.length;
6583
6718
  console.log(`${BOLD}Global skills${RESET}`);
6584
6719
  console.log(`${DIM}Checking ${selectedGlobalSkillNames.length} skill(s)...${RESET}`);
6585
- const result = await checkSkillsForUpdates(globalSkillsToCheck, token);
6720
+ const result = await checkSkillsForUpdates(globalSkillsToCheck);
6586
6721
  updates = result.updates;
6587
- result.errors;
6722
+ errors = result.errors;
6588
6723
  skipped = result.skipped;
6589
- if (updates.length === 0) console.log(`${TEXT} ✓ ${skillArgs.length > 0 ? "All selected global skills are up to date" : "All global skills are up to date"}${RESET}`);
6724
+ if (updates.length === 0 && errors.length === 0) console.log(`${TEXT} ✓ ${skillArgs.length > 0 ? "All selected global skills are up to date" : "All global skills are up to date"}${RESET}`);
6725
+ else if (updates.length === 0) console.log(`${YELLOW} No updates confirmed; some skills could not be checked.${RESET}`);
6590
6726
  else {
6591
6727
  console.log(`${TEXT} ${updates.length} update(s) available:${RESET}`);
6592
6728
  for (const update of updates) console.log(` ${TEXT}↑${RESET} ${update.name} ${DIM}(${update.source})${RESET}`);
@@ -6601,6 +6737,7 @@ async function runCheck(args = []) {
6601
6737
  console.log(` ${DIM}To update: ${TEXT}npx ali-skills add ${displaySource} -g -y${RESET}`);
6602
6738
  }
6603
6739
  }
6740
+ printCheckErrors(errors);
6604
6741
  console.log();
6605
6742
  if (updates.length > 0) {
6606
6743
  console.log(`${TEXT}${updates.length} update(s) available${RESET}`);
@@ -6630,82 +6767,21 @@ async function runCheck(args = []) {
6630
6767
  skillCount = selectedProjectSkillNames.length;
6631
6768
  console.log(`${BOLD}Project-level skills${RESET}`);
6632
6769
  console.log(`${DIM}Checking ${selectedProjectSkillNames.length} skill(s)...${RESET}`);
6633
- for (const [skillName, entry] of Object.entries(projectSkillsToCheck)) {
6634
- if (entry.sourceType === "internal") {
6635
- if (!entry.commitHash) {
6636
- skipped.push({
6637
- name: skillName,
6638
- reason: "Reinstall required to enable update checking (missing commit hash)",
6639
- sourceUrl: `https://code.alibaba-inc.com/${entry.source}`
6640
- });
6641
- continue;
6642
- }
6643
- const skillPath = (entry.skillPath || "").replace(/\/?SKILL\.md$/i, "");
6644
- const latestCommit = await fetchAliInternalCommitHash(entry.source, skillPath, null);
6645
- if (latestCommit === null) skipped.push({
6646
- name: skillName,
6647
- reason: "Ali internal repository (API authentication failed)",
6648
- sourceUrl: `https://code.alibaba-inc.com/${entry.source}`
6649
- });
6650
- else if (latestCommit !== entry.commitHash) updates.push({
6651
- name: skillName,
6652
- source: entry.source,
6653
- entry: {
6654
- source: entry.source,
6655
- sourceType: entry.sourceType,
6656
- sourceUrl: `https://code.alibaba-inc.com/${entry.source}`,
6657
- skillPath,
6658
- skillFolderHash: entry.computedHash,
6659
- installedAt: (/* @__PURE__ */ new Date()).toISOString(),
6660
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
6661
- }
6662
- });
6663
- continue;
6664
- }
6665
- if (entry.sourceType === "github") {
6666
- if (!entry.skillFolderHash) {
6667
- skipped.push({
6668
- name: skillName,
6669
- reason: "Reinstall required to enable update checking (missing folder hash)",
6670
- sourceUrl: `https://github.com/${entry.source}`
6671
- });
6672
- continue;
6673
- }
6674
- const possiblePaths = entry.skillPath ? [entry.skillPath] : [`skills/${skillName}`, `skills/${skillName}/SKILL.md`];
6675
- let foundMatch = false;
6676
- for (const skillPath of possiblePaths) try {
6677
- const latestHash = await fetchSkillFolderHash(entry.source, skillPath, token);
6678
- if (latestHash === null) continue;
6679
- foundMatch = true;
6680
- if (latestHash !== entry.skillFolderHash) updates.push({
6681
- name: skillName,
6682
- source: entry.source,
6683
- entry: {
6684
- source: entry.source,
6685
- sourceType: entry.sourceType,
6686
- sourceUrl: `https://github.com/${entry.source}`,
6687
- skillPath,
6688
- skillFolderHash: entry.skillFolderHash,
6689
- installedAt: (/* @__PURE__ */ new Date()).toISOString(),
6690
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
6691
- }
6692
- });
6693
- break;
6694
- } catch {
6695
- continue;
6696
- }
6697
- if (!foundMatch) skipped.push({
6698
- name: skillName,
6699
- reason: "Unable to verify (API failed for all possible paths)",
6700
- sourceUrl: `https://github.com/${entry.source}`
6701
- });
6702
- } else skipped.push({
6703
- name: skillName,
6704
- reason: `${entry.sourceType} source type not supported for update checking`,
6705
- sourceUrl: entry.source
6706
- });
6707
- }
6708
- if (updates.length === 0) console.log(`${TEXT} ✓ ${skillArgs.length > 0 ? "All selected project skills are up to date" : "All project skills are up to date"}${RESET}`);
6770
+ const result = await checkSkillsForUpdates(Object.fromEntries(Object.entries(projectSkillsToCheck).map(([name, entry]) => [name, {
6771
+ source: entry.source,
6772
+ sourceType: entry.sourceType,
6773
+ sourceUrl: entry.sourceType === "github" ? `https://github.com/${entry.source}` : entry.sourceType === "internal" ? `https://code.alibaba-inc.com/${entry.source}` : entry.source,
6774
+ skillPath: entry.skillPath ?? "",
6775
+ skillFolderHash: entry.skillFolderHash,
6776
+ commitHash: entry.commitHash,
6777
+ installedAt: "",
6778
+ updatedAt: ""
6779
+ }])));
6780
+ updates = result.updates;
6781
+ errors = result.errors;
6782
+ skipped = result.skipped;
6783
+ if (updates.length === 0 && errors.length === 0) console.log(`${TEXT} ✓ ${skillArgs.length > 0 ? "All selected project skills are up to date" : "All project skills are up to date"}${RESET}`);
6784
+ else if (updates.length === 0) console.log(`${YELLOW} No updates confirmed; some skills could not be checked.${RESET}`);
6709
6785
  else {
6710
6786
  console.log(`${TEXT} ${updates.length} update(s) available:${RESET}`);
6711
6787
  for (const update of updates) console.log(` ${TEXT}↑${RESET} ${update.name} ${DIM}(${update.source})${RESET}`);
@@ -6716,6 +6792,7 @@ async function runCheck(args = []) {
6716
6792
  if (isLocalLockOutdated) console.log(`${YELLOW} Note: Your lock file may be outdated. Re-adding these skills may fix this.${RESET}`);
6717
6793
  for (const skill of skipped) console.log(` ${TEXT}•${RESET} ${skill.name} ${DIM}(${skill.reason})${RESET}`);
6718
6794
  }
6795
+ printCheckErrors(errors);
6719
6796
  console.log();
6720
6797
  if (updates.length > 0) {
6721
6798
  console.log(`${TEXT}${updates.length} update(s) available${RESET}`);
@@ -6742,7 +6819,6 @@ async function runUpdate(args = []) {
6742
6819
  console.log();
6743
6820
  }
6744
6821
  updateDebugLog(debugUpdate, `scope=${isGlobal ? "global" : "project"}, yes=${yesFlag}, requestedSkills=${skillArgs.length > 0 ? skillArgs.join(",") : "(all)"}`);
6745
- const token = getGitHubToken();
6746
6822
  let skillsToCheck = {};
6747
6823
  let scopeLabel = "project";
6748
6824
  if (isGlobal) {
@@ -6760,9 +6836,9 @@ async function runUpdate(args = []) {
6760
6836
  source: entry.source,
6761
6837
  sourceType: entry.sourceType,
6762
6838
  sourceUrl,
6763
- skillPath: entry.skillPath || "",
6839
+ skillPath: entry.skillPath ?? "",
6764
6840
  commitHash: entry.commitHash,
6765
- skillFolderHash: entry.skillFolderHash || entry.computedHash,
6841
+ skillFolderHash: entry.skillFolderHash,
6766
6842
  installedAt: (/* @__PURE__ */ new Date()).toISOString(),
6767
6843
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
6768
6844
  };
@@ -6775,81 +6851,17 @@ async function runUpdate(args = []) {
6775
6851
  console.log();
6776
6852
  return;
6777
6853
  }
6778
- const availableUpdates = [];
6779
- const skipped = [];
6780
- const projectLock = (!isGlobal ? await readLocalLock() : null)?.lock ?? null;
6781
- for (const skillName of skillNames) {
6782
- const entry = skillsToCheck[skillName];
6783
- if (!entry) continue;
6784
- if (entry.sourceType === "internal") {
6785
- const localEntry = projectLock?.skills[skillName];
6786
- const commitHash = getUpdateComparisonStrategy(entry).value || getUpdateComparisonStrategy(localEntry || {}).value;
6787
- const storedSkillPath = entry.skillPath || localEntry?.skillPath || "";
6788
- updateDebugLog(debugUpdate, `checking internal skill=${skillName}, repo=${entry.source}, skillPath=${storedSkillPath || "(root)"}, hasCommitHash=${commitHash ? "yes" : "no"}`);
6789
- if (!commitHash) {
6790
- skipped.push({
6791
- name: skillName,
6792
- reason: "Reinstall required to enable update checking (missing commit hash)",
6793
- sourceUrl: entry.sourceUrl
6794
- });
6795
- continue;
6796
- }
6797
- try {
6798
- const skillPath = storedSkillPath.replace(/\/?SKILL\.md$/i, "");
6799
- const latestCommit = await fetchAliInternalCommitHash(entry.source, skillPath, null);
6800
- if (latestCommit && latestCommit !== commitHash) {
6801
- availableUpdates.push({
6802
- name: skillName,
6803
- source: entry.source,
6804
- entry
6805
- });
6806
- updateDebugLog(debugUpdate, `update available for ${skillName}: local=${commitHash?.slice(0, 12)} remote=${latestCommit.slice(0, 12)}`);
6807
- } else updateDebugLog(debugUpdate, `no update for ${skillName}: local=${commitHash?.slice(0, 12)} remote=${latestCommit?.slice(0, 12) || "(empty)"}`);
6808
- } catch {
6809
- updateDebugLog(debugUpdate, `failed to check internal skill=${skillName}`);
6810
- skipped.push({
6811
- name: skillName,
6812
- reason: "Ali internal repository (API check failed)",
6813
- sourceUrl: entry.sourceUrl
6814
- });
6815
- }
6816
- continue;
6817
- }
6818
- if (!entry.skillFolderHash) {
6819
- skipped.push({
6820
- name: skillName,
6821
- reason: getSkipReason(entry),
6822
- sourceUrl: entry.sourceUrl
6823
- });
6824
- continue;
6825
- }
6826
- if (entry.sourceType === "github") {
6827
- const possiblePaths = entry.skillPath ? [entry.skillPath] : [`skills/${skillName}`, `skills/${skillName}/SKILL.md`];
6828
- for (const skillPath of possiblePaths) try {
6829
- const latestHash = await fetchSkillFolderHash(entry.source, skillPath, token);
6830
- if (latestHash === null) continue;
6831
- if (latestHash !== entry.skillFolderHash) availableUpdates.push({
6832
- name: skillName,
6833
- source: entry.source,
6834
- entry
6835
- });
6836
- break;
6837
- } catch {
6838
- continue;
6839
- }
6840
- continue;
6841
- }
6842
- try {
6843
- const latestHash = await fetchSkillFolderHash(entry.source, entry.skillPath || "", token);
6844
- if (latestHash && latestHash !== entry.skillFolderHash) availableUpdates.push({
6845
- name: skillName,
6846
- source: entry.source,
6847
- entry
6848
- });
6849
- } catch {}
6854
+ const updateCheck = await checkSkillsForUpdates(skillsToCheck);
6855
+ const migratedCount = await applySafeGithubLockMigrations(updateCheck.migrations, isGlobal);
6856
+ if (migratedCount > 0) console.log(`${DIM}Migrated ${migratedCount} GitHub skill version(s) to commit hashes.${RESET}`);
6857
+ const availableUpdates = updateCheck.updates;
6858
+ for (const error of updateCheck.errors) {
6859
+ updateDebugLog(debugUpdate, `failed to check skill=${error.name}, source=${error.source}, reason=${error.error}`);
6860
+ console.log(`${DIM}⚠ Could not check ${error.name} (${error.source}): ${error.error}${RESET}`);
6850
6861
  }
6851
6862
  if (availableUpdates.length === 0) {
6852
- console.log(`${TEXT} All ${scopeLabel} skills are up to date${RESET}`);
6863
+ if (updateCheck.errors.length > 0) console.log(`${YELLOW}No updates confirmed; ${updateCheck.errors.length} skill(s) could not be checked.${RESET}`);
6864
+ else console.log(`${TEXT}✓ All ${scopeLabel} skills are up to date${RESET}`);
6853
6865
  console.log();
6854
6866
  return;
6855
6867
  }
@@ -6920,8 +6932,8 @@ async function runUpdate(args = []) {
6920
6932
  installUrl = `${installUrl}/tree/HEAD/${skillFolder}`;
6921
6933
  } else installUrl = sourceUrl;
6922
6934
  try {
6923
- const installedAgents = installedAgentsBySkillName.get(update.name.toLowerCase());
6924
- if (!installedAgents || installedAgents.length === 0) {
6935
+ const installTarget = resolveUpdateInstallTarget(installedAgentsBySkillName.get(update.name.toLowerCase()), isGlobal);
6936
+ if (installTarget.mode === "skip") {
6925
6937
  failCount++;
6926
6938
  safeSkipCount++;
6927
6939
  const nearMatches = installedSkills.filter((s) => s.name.toLowerCase().includes(update.name.toLowerCase())).map((s) => s.name);
@@ -6936,9 +6948,10 @@ async function runUpdate(args = []) {
6936
6948
  await runAdd([installUrl], {
6937
6949
  yes: true,
6938
6950
  global: isGlobal,
6939
- agent: installedAgents
6951
+ ...installTarget.mode === "agents" ? { agent: installTarget.agents } : {},
6952
+ canonicalOnly: installTarget.mode === "canonical"
6940
6953
  });
6941
- updateDebugLog(debugUpdate, `updated ${update.name} with installUrl=${installUrl} targetAgents=${installedAgents.join(",")}`);
6954
+ updateDebugLog(debugUpdate, `updated ${update.name} with installUrl=${installUrl} targetAgents=${installTarget.mode === "agents" ? installTarget.agents.join(",") : "(canonical-only)"}`);
6942
6955
  successCount++;
6943
6956
  console.log(` ${TEXT}✓${RESET} Updated ${update.name}`);
6944
6957
  } catch {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ali/cli-skills",
3
- "version": "2.1.3",
3
+ "version": "2.1.5",
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.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "The open agent skills ecosystem",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,7 +32,7 @@
32
32
  "author": "",
33
33
  "license": "MIT",
34
34
  "dependencies": {
35
- "@ali/cli-skills": "^2.1.3"
35
+ "@ali/cli-skills": "^2.1.5"
36
36
  },
37
37
  "bundleDependencies": [
38
38
  "@ali/cli-skills"