@safeskill/cli 0.1.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/bin/cli.mjs +0 -0
  2. package/dist/cli.mjs +91 -34
  3. package/package.json +15 -13
package/bin/cli.mjs CHANGED
File without changes
package/dist/cli.mjs CHANGED
@@ -12,6 +12,22 @@ import { access, cp, lstat, mkdir, mkdtemp, readFile, readdir, readlink, realpat
12
12
  import { gunzipSync } from "zlib";
13
13
  import { createHash } from "crypto";
14
14
  var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
15
+ const packageJsonPath = join(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
16
+ let version$2;
17
+ try {
18
+ version$2 = JSON.parse(readFileSync(packageJsonPath, "utf-8")).version || "0.0.0";
19
+ } catch {
20
+ version$2 = "0.0.0";
21
+ }
22
+ function getUserAgent() {
23
+ return `safeskill-cli/${version$2}`;
24
+ }
25
+ function getHeaders(additionalHeaders = {}) {
26
+ return {
27
+ "User-Agent": getUserAgent(),
28
+ ...additionalHeaders
29
+ };
30
+ }
15
31
  function getOwnerRepo(parsed) {
16
32
  if (parsed.type === "local") return null;
17
33
  const sshMatch = parsed.url.match(/^git@[^:]+:(.+)$/);
@@ -39,7 +55,7 @@ function parseOwnerRepo(ownerRepo) {
39
55
  }
40
56
  async function isRepoPrivate(owner, repo) {
41
57
  try {
42
- const res = await fetch(`https://api.github.com/repos/${owner}/${repo}`);
58
+ const res = await fetch(`https://api.github.com/repos/${owner}/${repo}`, { headers: getHeaders() });
43
59
  if (!res.ok) return null;
44
60
  return (await res.json()).private === true;
45
61
  } catch {
@@ -404,7 +420,10 @@ function withTimeout(timeoutMs) {
404
420
  }
405
421
  async function fetchText(url, timeoutMs) {
406
422
  try {
407
- const response = await fetch(url, { signal: withTimeout(timeoutMs) });
423
+ const response = await fetch(url, {
424
+ signal: withTimeout(timeoutMs),
425
+ headers: getHeaders()
426
+ });
408
427
  if (!response.ok) return null;
409
428
  return (await response.text()).trim();
410
429
  } catch {
@@ -460,7 +479,10 @@ async function ensureRegionAvailable(region, hubBaseUrl) {
460
479
  const availabilityPromise = (async () => {
461
480
  const statusUrl = new URL("/api/v1/status", hubBaseUrl).toString();
462
481
  try {
463
- if ((await fetch(statusUrl, { signal: withTimeout(STATUS_TIMEOUT_MS) })).ok) return;
482
+ if ((await fetch(statusUrl, {
483
+ signal: withTimeout(STATUS_TIMEOUT_MS),
484
+ headers: getHeaders()
485
+ })).ok) return;
464
486
  } catch {}
465
487
  const alternateRegion = region === "cn" ? "global" : "cn";
466
488
  REGION_DOMAINS[alternateRegion];
@@ -670,13 +692,12 @@ async function extractDownloadedArtifact(destination, fileName, bytes) {
670
692
  }
671
693
  async function searchSkillsCatalog(query, limit = 10) {
672
694
  try {
673
- const hubBaseUrl = await getSafeSkillHubBaseUrl();
674
- const searchBaseUrl = await getSafeSkillSearchUrlWithHub(hubBaseUrl);
695
+ const searchBaseUrl = await getSafeSkillSearchUrlWithHub(await getSafeSkillHubBaseUrl());
675
696
  const searchUrl = new URL(searchBaseUrl);
676
697
  searchUrl.searchParams.set("query", query);
677
698
  searchUrl.searchParams.set("page", "1");
678
699
  searchUrl.searchParams.set("page_size", String(limit));
679
- const response = await fetch(searchUrl.toString());
700
+ const response = await fetch(searchUrl.toString(), { headers: getHeaders() });
680
701
  if (!response.ok) return [];
681
702
  const payload = await response.json();
682
703
  const data = "data" in payload ? payload.data : null;
@@ -684,11 +705,15 @@ async function searchSkillsCatalog(query, limit = 10) {
684
705
  return data.items.map((item) => ({
685
706
  name: item.name,
686
707
  label: item.display_name || item.name,
687
- description: item.description,
708
+ description: item.summary || item.description,
688
709
  slug: `${item.namespace}/${item.name}`,
689
710
  source: buildHubSource(item.namespace, item.name, item.latest_version),
690
- installs: 0,
691
- detailUrl: `${buildApiUrl(hubBaseUrl, `/skills/${item.namespace}/${item.name}`)}`
711
+ installs: item.stats?.total_installs ?? 0,
712
+ totalStars: item.stats?.total_stars ?? 0,
713
+ trustScore: item.trust_score,
714
+ updatedAt: item.updated_at,
715
+ sha256: item.latest_sha256,
716
+ detailUrl: item.latest_sha256 ? `https://safeskill.cn/report/${item.latest_sha256}` : void 0
692
717
  }));
693
718
  } catch {
694
719
  return [];
@@ -698,7 +723,7 @@ async function resolveHubSkill(source, agent) {
698
723
  const baseUrl = await getSafeSkillHubBaseUrl();
699
724
  const response = await fetch(buildApiUrl(baseUrl, "/resolve"), {
700
725
  method: "POST",
701
- headers: { "content-type": "application/json" },
726
+ headers: getHeaders({ "content-type": "application/json" }),
702
727
  body: JSON.stringify({
703
728
  source,
704
729
  ...agent ? { agent } : {}
@@ -714,13 +739,19 @@ async function downloadHubSource(source, agent) {
714
739
  if (!downloadUrl) throw new Error("hub resolve did not return a downloadable artifact");
715
740
  let response;
716
741
  try {
717
- response = await fetch(downloadUrl, { redirect: "follow" });
742
+ response = await fetch(downloadUrl, {
743
+ redirect: "follow",
744
+ headers: getHeaders()
745
+ });
718
746
  if (!response.ok && response.status === 502 && resolved.artifact_digest) {
719
747
  console.warn(`Primary download URL failed with 502, trying fallback...`);
720
748
  const fallbackUrl = buildFallbackDownloadUrl(resolved.artifact_digest);
721
749
  if (fallbackUrl && fallbackUrl !== downloadUrl) {
722
750
  console.warn(`Trying fallback URL: ${fallbackUrl}`);
723
- const fallbackResponse = await fetch(fallbackUrl, { redirect: "follow" });
751
+ const fallbackResponse = await fetch(fallbackUrl, {
752
+ redirect: "follow",
753
+ headers: getHeaders()
754
+ });
724
755
  if (fallbackResponse.ok) response = fallbackResponse;
725
756
  else throw new Error(`hub artifact download failed: ${response.status} (primary) and ${fallbackResponse.status} (fallback)`);
726
757
  } else throw new Error(`hub artifact download failed: ${response.status}`);
@@ -750,7 +781,7 @@ async function checkHubUpdates(items) {
750
781
  const baseUrl = await getSafeSkillHubBaseUrl();
751
782
  const response = await fetch(buildApiUrl(baseUrl, "/check-update"), {
752
783
  method: "POST",
753
- headers: { "content-type": "application/json" },
784
+ headers: getHeaders({ "content-type": "application/json" }),
754
785
  body: JSON.stringify({ items })
755
786
  });
756
787
  if (!response.ok) throw new Error(`hub check-update failed: ${response.status}`);
@@ -1919,7 +1950,7 @@ var WellKnownProvider = class {
1919
1950
  });
1920
1951
  }
1921
1952
  for (const { indexUrl, baseUrl: resolvedBase, wellKnownPath } of urlsToTry) try {
1922
- const response = await fetch(indexUrl);
1953
+ const response = await fetch(indexUrl, { headers: getHeaders() });
1923
1954
  if (!response.ok) continue;
1924
1955
  const index = await response.json();
1925
1956
  if (!index.skills || !Array.isArray(index.skills)) continue;
@@ -1980,7 +2011,7 @@ var WellKnownProvider = class {
1980
2011
  const resolvedPath = wellKnownPath ?? this.WELL_KNOWN_PATHS[0];
1981
2012
  const skillBaseUrl = `${baseUrl.replace(/\/$/, "")}/${resolvedPath}/${entry.name}`;
1982
2013
  const skillMdUrl = `${skillBaseUrl}/SKILL.md`;
1983
- const response = await fetch(skillMdUrl);
2014
+ const response = await fetch(skillMdUrl, { headers: getHeaders() });
1984
2015
  if (!response.ok) return null;
1985
2016
  const content = await response.text();
1986
2017
  const { data } = (0, import_gray_matter.default)(content);
@@ -1990,7 +2021,7 @@ var WellKnownProvider = class {
1990
2021
  const filePromises = entry.files.filter((f) => f.toLowerCase() !== "skill.md").map(async (filePath) => {
1991
2022
  try {
1992
2023
  const fileUrl = `${skillBaseUrl}/${filePath}`;
1993
- const fileResponse = await fetch(fileUrl);
2024
+ const fileResponse = await fetch(fileUrl, { headers: getHeaders() });
1994
2025
  if (fileResponse.ok) return {
1995
2026
  path: filePath,
1996
2027
  content: await fileResponse.text()
@@ -2101,10 +2132,7 @@ async function fetchSkillFolderHash(ownerRepo, skillPath, token) {
2101
2132
  if (folderPath.endsWith("/")) folderPath = folderPath.slice(0, -1);
2102
2133
  for (const branch of ["main", "master"]) try {
2103
2134
  const url = `https://api.github.com/repos/${ownerRepo}/git/trees/${branch}?recursive=1`;
2104
- const headers = {
2105
- Accept: "application/vnd.github.v3+json",
2106
- "User-Agent": "skills-cli"
2107
- };
2135
+ const headers = getHeaders({ Accept: "application/vnd.github.v3+json" });
2108
2136
  if (token) headers["Authorization"] = `Bearer ${token}`;
2109
2137
  const response = await fetch(url, { headers });
2110
2138
  if (!response.ok) continue;
@@ -2231,7 +2259,7 @@ function createEmptyLocalLock() {
2231
2259
  skills: {}
2232
2260
  };
2233
2261
  }
2234
- var version$1 = "0.1.8";
2262
+ var version$1 = "0.2.0";
2235
2263
  const isCancelled$1 = (value) => typeof value === "symbol";
2236
2264
  async function isSourcePrivate(source) {
2237
2265
  const ownerRepo = parseOwnerRepo(source);
@@ -3267,11 +3295,34 @@ const BOLD$2 = "\x1B[1m";
3267
3295
  const DIM$2 = "\x1B[38;5;102m";
3268
3296
  const TEXT$1 = "\x1B[38;5;145m";
3269
3297
  const CYAN$1 = "\x1B[36m";
3270
- function formatInstalls(count) {
3271
- if (!count || count <= 0) return "";
3272
- if (count >= 1e6) return `${(count / 1e6).toFixed(1).replace(/\.0$/, "")}M installs`;
3273
- if (count >= 1e3) return `${(count / 1e3).toFixed(1).replace(/\.0$/, "")}K installs`;
3274
- return `${count} install${count === 1 ? "" : "s"}`;
3298
+ const GREEN = "\x1B[32m";
3299
+ const YELLOW$1 = "\x1B[33m";
3300
+ const RED = "\x1B[31m";
3301
+ function formatCount(count) {
3302
+ if (count >= 1e6) return `${(count / 1e6).toFixed(1).replace(/\.0$/, "")}M`;
3303
+ if (count >= 1e3) return `${(count / 1e3).toFixed(1).replace(/\.0$/, "")}K`;
3304
+ return `${count}`;
3305
+ }
3306
+ function formatTrustScore(score) {
3307
+ if (score === void 0 || score === null) return "";
3308
+ return `${score >= 80 ? GREEN : score >= 60 ? YELLOW$1 : RED}TrustScore:${score}${RESET$2}`;
3309
+ }
3310
+ function formatRelativeDate(ts) {
3311
+ if (!ts) return "";
3312
+ const diff = Math.floor(Date.now() / 1e3) - ts;
3313
+ if (diff < 0) return "";
3314
+ const days = Math.floor(diff / 86400);
3315
+ if (days < 1) return "today";
3316
+ if (days < 30) return `${days}d ago`;
3317
+ const months = Math.floor(days / 30);
3318
+ if (months < 12) return `${months}mo ago`;
3319
+ return `${Math.floor(months / 12)}y ago`;
3320
+ }
3321
+ function formatStatsBadges(installs, stars) {
3322
+ const parts = [];
3323
+ if (installs > 0) parts.push(`${CYAN$1}↓ ${formatCount(installs)}${RESET$2}`);
3324
+ if (stars > 0) parts.push(`${YELLOW$1}★ ${formatCount(stars)}${RESET$2}`);
3325
+ return parts.join(" ");
3275
3326
  }
3276
3327
  async function searchSkillsAPI(query) {
3277
3328
  return searchSkillsCatalog(query, 10);
@@ -3311,10 +3362,10 @@ async function runSearchPrompt(initialQuery = "") {
3311
3362
  const displayName = skill.label || skill.name;
3312
3363
  const name = isSelected ? `${BOLD$2}${displayName}${RESET$2}` : `${TEXT$1}${displayName}${RESET$2}`;
3313
3364
  const source = skill.source ? ` ${DIM$2}${skill.source}${RESET$2}` : "";
3314
- const installs = formatInstalls(skill.installs);
3315
- const installsBadge = installs ? ` ${CYAN$1}${installs}${RESET$2}` : "";
3365
+ const statsBadge = formatStatsBadges(skill.installs, skill.totalStars);
3366
+ const trustBadge = skill.trustScore !== void 0 ? ` ${skill.trustScore >= 80 ? GREEN : skill.trustScore >= 60 ? YELLOW$1 : RED}TrustScore:${skill.trustScore}${RESET$2}` : "";
3316
3367
  const loadingIndicator = loading && i === 0 ? ` ${DIM$2}...${RESET$2}` : "";
3317
- lines.push(` ${arrow} ${name}${source}${installsBadge}${loadingIndicator}`);
3368
+ lines.push(` ${arrow}${trustBadge} ${name}${source}${statsBadge ? ` ${statsBadge}` : ""}${loadingIndicator}`);
3318
3369
  }
3319
3370
  }
3320
3371
  lines.push("");
@@ -3432,12 +3483,18 @@ ${DIM$2} 2) npx safeskill add <owner/repo@skill>${RESET$2}`;
3432
3483
  console.log();
3433
3484
  for (const skill of results.slice(0, 6)) {
3434
3485
  const pkg = skill.source || skill.slug;
3435
- const installs = formatInstalls(skill.installs);
3436
- const displayName = skill.label || skill.name;
3437
3486
  const installRef = pkg.startsWith("safeskill://") ? pkg : `${pkg}@${skill.name}`;
3438
- console.log(`${TEXT$1}${installRef}${RESET$2}${installs ? ` ${CYAN$1}${installs}${RESET$2}` : ""}`);
3439
- if (skill.description) console.log(`${DIM$2} ${skill.description}${RESET$2}`);
3440
- else if (displayName !== skill.name) console.log(`${DIM$2} ${displayName}${RESET$2}`);
3487
+ console.log(`${TEXT$1}${installRef}${RESET$2}`);
3488
+ const statsBadge = formatStatsBadges(skill.installs, skill.totalStars);
3489
+ const badges = [
3490
+ formatTrustScore(skill.trustScore),
3491
+ statsBadge,
3492
+ skill.updatedAt ? `${DIM$2}${formatRelativeDate(skill.updatedAt)}${RESET$2}` : ""
3493
+ ].filter(Boolean).join(` `);
3494
+ if (badges) console.log(` ${badges}`);
3495
+ const displayName = skill.label || skill.name;
3496
+ const desc = skill.description || (displayName !== skill.name ? displayName : "");
3497
+ if (desc) console.log(`${DIM$2} ${desc}${RESET$2}`);
3441
3498
  if (skill.detailUrl) console.log(`${DIM$2}└ ${skill.detailUrl}${RESET$2}`);
3442
3499
  console.log();
3443
3500
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@safeskill/cli",
3
- "version": "0.1.8",
3
+ "version": "0.2.0",
4
4
  "description": "SafeSkill CLI, for safe your skills",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,6 +13,18 @@
13
13
  "CHANGELOG.md",
14
14
  "ThirdPartyNoticeText.txt"
15
15
  ],
16
+ "scripts": {
17
+ "build": "node scripts/generate-licenses.js && obuild",
18
+ "generate-licenses": "node scripts/generate-licenses.js",
19
+ "dev": "node src/cli.ts",
20
+ "exec:test": "node scripts/execute-tests.ts",
21
+ "prepublishOnly": "npm run build",
22
+ "format": "prettier --write \"src/**/*.ts\" \"scripts/**/*.ts\"",
23
+ "format:check": "prettier --check \"src/**/*.ts\" \"scripts/**/*.ts\"",
24
+ "test": "vitest",
25
+ "type-check": "tsc --noEmit",
26
+ "publish:snapshot": "npm version prerelease --preid=snapshot --no-git-tag-version && npm publish --tag snapshot"
27
+ },
16
28
  "lint-staged": {
17
29
  "src/**/*.ts": "prettier --write",
18
30
  "scripts/**/*.ts": "prettier --write",
@@ -95,21 +107,11 @@
95
107
  "engines": {
96
108
  "node": ">=18"
97
109
  },
110
+ "packageManager": "pnpm@10.17.1",
98
111
  "dependencies": {
99
112
  "@types/yauzl": "^2.10.3",
100
113
  "jschardet": "^3.1.4",
101
114
  "node-stream-zip": "^1.15.0",
102
115
  "yauzl": "^3.3.0"
103
- },
104
- "scripts": {
105
- "build": "node scripts/generate-licenses.js && obuild",
106
- "generate-licenses": "node scripts/generate-licenses.js",
107
- "dev": "node src/cli.ts",
108
- "exec:test": "node scripts/execute-tests.ts",
109
- "format": "prettier --write \"src/**/*.ts\" \"scripts/**/*.ts\"",
110
- "format:check": "prettier --check \"src/**/*.ts\" \"scripts/**/*.ts\"",
111
- "test": "vitest",
112
- "type-check": "tsc --noEmit",
113
- "publish:snapshot": "npm version prerelease --preid=snapshot --no-git-tag-version && npm publish --tag snapshot"
114
116
  }
115
- }
117
+ }