@safeskill/cli 0.1.8 → 0.2.1

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.1
4
+
5
+ - Added Flocks agent support with `.flocks/plugins/skills` install paths.
6
+ - Added hub resolve fallback for new or unsupported agent-specific resolve requests.
7
+ - Fixed zip artifact extraction when packages contain explicit directory entries.
8
+
3
9
  ## 0.0.1
4
10
 
5
11
  - Renamed the published package and CLI command from `skills` to `safeskill`.
package/README.md CHANGED
@@ -6,7 +6,7 @@ The SafeSkill CLI for the open agent skills ecosystem.
6
6
 
7
7
  <!-- agent-list:start -->
8
8
 
9
- Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [40 more](#available-agents).
9
+ Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [41 more](#available-agents).
10
10
 
11
11
  <!-- agent-list:end -->
12
12
 
@@ -238,48 +238,49 @@ Skills can be installed to any of these agents:
238
238
 
239
239
  <!-- supported-agents:start -->
240
240
 
241
- | Agent | `--agent` | Project Path | Global Path |
242
- | ------------------------------------- | ---------------------------------------- | ---------------------- | ------------------------------- |
243
- | Amp, Kimi Code CLI, Replit, Universal | `amp`, `kimi-cli`, `replit`, `universal` | `.agents/skills/` | `~/.config/agents/skills/` |
244
- | Antigravity | `antigravity` | `.agents/skills/` | `~/.gemini/antigravity/skills/` |
245
- | Augment | `augment` | `.augment/skills/` | `~/.augment/skills/` |
246
- | Claude Code | `claude-code` | `.claude/skills/` | `~/.claude/skills/` |
247
- | OpenClaw | `openclaw` | `skills/` | `~/.openclaw/skills/` |
248
- | Cline, Warp | `cline`, `warp` | `.agents/skills/` | `~/.agents/skills/` |
249
- | CodeBuddy | `codebuddy` | `.codebuddy/skills/` | `~/.codebuddy/skills/` |
250
- | Codex | `codex` | `.agents/skills/` | `~/.codex/skills/` |
251
- | Command Code | `command-code` | `.commandcode/skills/` | `~/.commandcode/skills/` |
252
- | Continue | `continue` | `.continue/skills/` | `~/.continue/skills/` |
253
- | Cortex Code | `cortex` | `.cortex/skills/` | `~/.snowflake/cortex/skills/` |
254
- | Crush | `crush` | `.crush/skills/` | `~/.config/crush/skills/` |
255
- | Cursor | `cursor` | `.agents/skills/` | `~/.cursor/skills/` |
256
- | Deep Agents | `deepagents` | `.agents/skills/` | `~/.deepagents/agent/skills/` |
257
- | Droid | `droid` | `.factory/skills/` | `~/.factory/skills/` |
258
- | Firebender | `firebender` | `.agents/skills/` | `~/.firebender/skills/` |
259
- | Gemini CLI | `gemini-cli` | `.agents/skills/` | `~/.gemini/skills/` |
260
- | GitHub Copilot | `github-copilot` | `.agents/skills/` | `~/.copilot/skills/` |
261
- | Goose | `goose` | `.goose/skills/` | `~/.config/goose/skills/` |
262
- | Junie | `junie` | `.junie/skills/` | `~/.junie/skills/` |
263
- | iFlow CLI | `iflow-cli` | `.iflow/skills/` | `~/.iflow/skills/` |
264
- | Kilo Code | `kilo` | `.kilocode/skills/` | `~/.kilocode/skills/` |
265
- | Kiro CLI | `kiro-cli` | `.kiro/skills/` | `~/.kiro/skills/` |
266
- | Kode | `kode` | `.kode/skills/` | `~/.kode/skills/` |
267
- | MCPJam | `mcpjam` | `.mcpjam/skills/` | `~/.mcpjam/skills/` |
268
- | Mistral Vibe | `mistral-vibe` | `.vibe/skills/` | `~/.vibe/skills/` |
269
- | Mux | `mux` | `.mux/skills/` | `~/.mux/skills/` |
270
- | OpenCode | `opencode` | `.agents/skills/` | `~/.config/opencode/skills/` |
271
- | OpenHands | `openhands` | `.openhands/skills/` | `~/.openhands/skills/` |
272
- | Pi | `pi` | `.pi/skills/` | `~/.pi/agent/skills/` |
273
- | Qoder | `qoder` | `.qoder/skills/` | `~/.qoder/skills/` |
274
- | Qwen Code | `qwen-code` | `.qwen/skills/` | `~/.qwen/skills/` |
275
- | Roo Code | `roo` | `.roo/skills/` | `~/.roo/skills/` |
276
- | Trae | `trae` | `.trae/skills/` | `~/.trae/skills/` |
277
- | Trae CN | `trae-cn` | `.trae/skills/` | `~/.trae-cn/skills/` |
278
- | Windsurf | `windsurf` | `.windsurf/skills/` | `~/.codeium/windsurf/skills/` |
279
- | Zencoder | `zencoder` | `.zencoder/skills/` | `~/.zencoder/skills/` |
280
- | Neovate | `neovate` | `.neovate/skills/` | `~/.neovate/skills/` |
281
- | Pochi | `pochi` | `.pochi/skills/` | `~/.pochi/skills/` |
282
- | AdaL | `adal` | `.adal/skills/` | `~/.adal/skills/` |
241
+ | Agent | `--agent` | Project Path | Global Path |
242
+ | ------------------------------------- | ---------------------------------------- | ------------------------- | ---------------------------------- |
243
+ | Amp, Kimi Code CLI, Replit, Universal | `amp`, `kimi-cli`, `replit`, `universal` | `.agents/skills/` | `~/.config/agents/skills/` |
244
+ | Antigravity | `antigravity` | `.agents/skills/` | `~/.gemini/antigravity/skills/` |
245
+ | Augment | `augment` | `.augment/skills/` | `~/.augment/skills/` |
246
+ | Claude Code | `claude-code` | `.claude/skills/` | `~/.claude/skills/` |
247
+ | OpenClaw | `openclaw` | `skills/` | `~/.openclaw/skills/` |
248
+ | Cline, Warp | `cline`, `warp` | `.agents/skills/` | `~/.agents/skills/` |
249
+ | CodeBuddy | `codebuddy` | `.codebuddy/skills/` | `~/.codebuddy/skills/` |
250
+ | Codex | `codex` | `.agents/skills/` | `~/.codex/skills/` |
251
+ | Command Code | `command-code` | `.commandcode/skills/` | `~/.commandcode/skills/` |
252
+ | Continue | `continue` | `.continue/skills/` | `~/.continue/skills/` |
253
+ | Cortex Code | `cortex` | `.cortex/skills/` | `~/.snowflake/cortex/skills/` |
254
+ | Crush | `crush` | `.crush/skills/` | `~/.config/crush/skills/` |
255
+ | Cursor | `cursor` | `.agents/skills/` | `~/.cursor/skills/` |
256
+ | Deep Agents | `deepagents` | `.agents/skills/` | `~/.deepagents/agent/skills/` |
257
+ | Droid | `droid` | `.factory/skills/` | `~/.factory/skills/` |
258
+ | Firebender | `firebender` | `.agents/skills/` | `~/.firebender/skills/` |
259
+ | Flocks | `flocks` | `.flocks/plugins/skills/` | `~/.flocks/plugins/skills/` |
260
+ | Gemini CLI | `gemini-cli` | `.agents/skills/` | `~/.gemini/skills/` |
261
+ | GitHub Copilot | `github-copilot` | `.agents/skills/` | `~/.copilot/skills/` |
262
+ | Goose | `goose` | `.goose/skills/` | `~/.config/goose/skills/` |
263
+ | Junie | `junie` | `.junie/skills/` | `~/.junie/skills/` |
264
+ | iFlow CLI | `iflow-cli` | `.iflow/skills/` | `~/.iflow/skills/` |
265
+ | Kilo Code | `kilo` | `.kilocode/skills/` | `~/.kilocode/skills/` |
266
+ | Kiro CLI | `kiro-cli` | `.kiro/skills/` | `~/.kiro/skills/` |
267
+ | Kode | `kode` | `.kode/skills/` | `~/.kode/skills/` |
268
+ | MCPJam | `mcpjam` | `.mcpjam/skills/` | `~/.mcpjam/skills/` |
269
+ | Mistral Vibe | `mistral-vibe` | `.vibe/skills/` | `~/.vibe/skills/` |
270
+ | Mux | `mux` | `.mux/skills/` | `~/.mux/skills/` |
271
+ | OpenCode | `opencode` | `.agents/skills/` | `~/.config/opencode/skills/` |
272
+ | OpenHands | `openhands` | `.openhands/skills/` | `~/.openhands/skills/` |
273
+ | Pi | `pi` | `.pi/skills/` | `~/.pi/agent/skills/` |
274
+ | Qoder | `qoder` | `.qoder/skills/` | `~/.qoder/skills/` |
275
+ | Qwen Code | `qwen-code` | `.qwen/skills/` | `~/.qwen/skills/` |
276
+ | Roo Code | `roo` | `.roo/skills/` | `~/.roo/skills/` |
277
+ | Trae | `trae` | `.trae/skills/` | `~/.trae/skills/` |
278
+ | Trae CN | `trae-cn` | `.trae/skills/` | `~/.trae-cn/skills/` |
279
+ | Windsurf | `windsurf` | `.windsurf/skills/` | `~/.codeium/windsurf/skills/` |
280
+ | Zencoder | `zencoder` | `.zencoder/skills/` | `~/.zencoder/skills/` |
281
+ | Neovate | `neovate` | `.neovate/skills/` | `~/.neovate/skills/` |
282
+ | Pochi | `pochi` | `.pochi/skills/` | `~/.pochi/skills/` |
283
+ | AdaL | `adal` | `.adal/skills/` | `~/.adal/skills/` |
283
284
 
284
285
  <!-- supported-agents:end -->
285
286
 
@@ -361,6 +362,7 @@ The CLI searches for skills in these locations within a repository:
361
362
  - `.cortex/skills/`
362
363
  - `.crush/skills/`
363
364
  - `.factory/skills/`
365
+ - `.flocks/plugins/skills/`
364
366
  - `.goose/skills/`
365
367
  - `.junie/skills/`
366
368
  - `.iflow/skills/`
@@ -381,6 +383,7 @@ The CLI searches for skills in these locations within a repository:
381
383
  - `.neovate/skills/`
382
384
  - `.pochi/skills/`
383
385
  - `.adal/skills/`
386
+
384
387
  <!-- skill-discovery:end -->
385
388
 
386
389
  ### Plugin Manifest Discovery
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];
@@ -585,9 +607,7 @@ async function extractDownloadedArtifact(destination, fileName, bytes) {
585
607
  import_yauzl.open(tempZipPath, {
586
608
  lazyEntries: true,
587
609
  strictFileNames: false,
588
- decodeStrings: true,
589
- encoding,
590
- validateEntryNames: false
610
+ decodeStrings: true
591
611
  }, (err, zipfile) => {
592
612
  if (err) {
593
613
  reject(err);
@@ -595,16 +615,22 @@ async function extractDownloadedArtifact(destination, fileName, bytes) {
595
615
  }
596
616
  const processedFiles = /* @__PURE__ */ new Set();
597
617
  zipfile.on("entry", (entry) => {
598
- if (entry.isDirectory) {
599
- zipfile.readEntry();
600
- return;
601
- }
602
- let fileName = entry.fileName;
603
- fileName = fileName.replace(/[^\w\-._/\\]/g, (char) => {
618
+ let fileName = entry.fileName.replace(/\\/g, "/");
619
+ const isDirectoryEntry = fileName.endsWith("/");
620
+ fileName = fileName.replace(/[^\w\-._/]/g, (char) => {
604
621
  const code = char.charCodeAt(0);
605
622
  if (code < 32 || code > 126) return "_";
606
623
  return char;
607
624
  });
625
+ if (isDirectoryEntry) {
626
+ const directoryName = fileName.replace(/\/+$/, "");
627
+ if (!directoryName) {
628
+ zipfile.readEntry();
629
+ return;
630
+ }
631
+ mkdir(join(destination, directoryName), { recursive: true }).then(() => zipfile.readEntry()).catch(reject);
632
+ return;
633
+ }
608
634
  if (processedFiles.has(fileName)) {
609
635
  zipfile.readEntry();
610
636
  return;
@@ -670,13 +696,12 @@ async function extractDownloadedArtifact(destination, fileName, bytes) {
670
696
  }
671
697
  async function searchSkillsCatalog(query, limit = 10) {
672
698
  try {
673
- const hubBaseUrl = await getSafeSkillHubBaseUrl();
674
- const searchBaseUrl = await getSafeSkillSearchUrlWithHub(hubBaseUrl);
699
+ const searchBaseUrl = await getSafeSkillSearchUrlWithHub(await getSafeSkillHubBaseUrl());
675
700
  const searchUrl = new URL(searchBaseUrl);
676
701
  searchUrl.searchParams.set("query", query);
677
702
  searchUrl.searchParams.set("page", "1");
678
703
  searchUrl.searchParams.set("page_size", String(limit));
679
- const response = await fetch(searchUrl.toString());
704
+ const response = await fetch(searchUrl.toString(), { headers: getHeaders() });
680
705
  if (!response.ok) return [];
681
706
  const payload = await response.json();
682
707
  const data = "data" in payload ? payload.data : null;
@@ -684,25 +709,34 @@ async function searchSkillsCatalog(query, limit = 10) {
684
709
  return data.items.map((item) => ({
685
710
  name: item.name,
686
711
  label: item.display_name || item.name,
687
- description: item.description,
712
+ description: item.summary || item.description,
688
713
  slug: `${item.namespace}/${item.name}`,
689
714
  source: buildHubSource(item.namespace, item.name, item.latest_version),
690
- installs: 0,
691
- detailUrl: `${buildApiUrl(hubBaseUrl, `/skills/${item.namespace}/${item.name}`)}`
715
+ installs: item.stats?.total_installs ?? 0,
716
+ totalStars: item.stats?.total_stars ?? 0,
717
+ trustScore: item.trust_score,
718
+ updatedAt: item.updated_at,
719
+ sha256: item.latest_sha256,
720
+ detailUrl: item.latest_sha256 ? `https://safeskill.cn/report/${item.latest_sha256}` : void 0
692
721
  }));
693
722
  } catch {
694
723
  return [];
695
724
  }
696
725
  }
697
726
  async function resolveHubSkill(source, agent) {
698
- const baseUrl = await getSafeSkillHubBaseUrl();
699
- const response = await fetch(buildApiUrl(baseUrl, "/resolve"), {
727
+ const resolveUrl = buildApiUrl(await getSafeSkillHubBaseUrl(), "/resolve");
728
+ const resolveWithAgent = agent ? await fetch(resolveUrl, {
700
729
  method: "POST",
701
- headers: { "content-type": "application/json" },
730
+ headers: getHeaders({ "content-type": "application/json" }),
702
731
  body: JSON.stringify({
703
732
  source,
704
- ...agent ? { agent } : {}
733
+ agent
705
734
  })
735
+ }) : null;
736
+ const response = resolveWithAgent && resolveWithAgent.ok ? resolveWithAgent : await fetch(resolveUrl, {
737
+ method: "POST",
738
+ headers: getHeaders({ "content-type": "application/json" }),
739
+ body: JSON.stringify({ source })
706
740
  });
707
741
  if (!response.ok) throw new Error(`hub resolve failed: ${response.status}`);
708
742
  return readEnvelope(response);
@@ -714,13 +748,19 @@ async function downloadHubSource(source, agent) {
714
748
  if (!downloadUrl) throw new Error("hub resolve did not return a downloadable artifact");
715
749
  let response;
716
750
  try {
717
- response = await fetch(downloadUrl, { redirect: "follow" });
751
+ response = await fetch(downloadUrl, {
752
+ redirect: "follow",
753
+ headers: getHeaders()
754
+ });
718
755
  if (!response.ok && response.status === 502 && resolved.artifact_digest) {
719
756
  console.warn(`Primary download URL failed with 502, trying fallback...`);
720
757
  const fallbackUrl = buildFallbackDownloadUrl(resolved.artifact_digest);
721
758
  if (fallbackUrl && fallbackUrl !== downloadUrl) {
722
759
  console.warn(`Trying fallback URL: ${fallbackUrl}`);
723
- const fallbackResponse = await fetch(fallbackUrl, { redirect: "follow" });
760
+ const fallbackResponse = await fetch(fallbackUrl, {
761
+ redirect: "follow",
762
+ headers: getHeaders()
763
+ });
724
764
  if (fallbackResponse.ok) response = fallbackResponse;
725
765
  else throw new Error(`hub artifact download failed: ${response.status} (primary) and ${fallbackResponse.status} (fallback)`);
726
766
  } else throw new Error(`hub artifact download failed: ${response.status}`);
@@ -750,7 +790,7 @@ async function checkHubUpdates(items) {
750
790
  const baseUrl = await getSafeSkillHubBaseUrl();
751
791
  const response = await fetch(buildApiUrl(baseUrl, "/check-update"), {
752
792
  method: "POST",
753
- headers: { "content-type": "application/json" },
793
+ headers: getHeaders({ "content-type": "application/json" }),
754
794
  body: JSON.stringify({ items })
755
795
  });
756
796
  if (!response.ok) throw new Error(`hub check-update failed: ${response.status}`);
@@ -1136,6 +1176,15 @@ const agents = {
1136
1176
  return existsSync(join(home, ".firebender"));
1137
1177
  }
1138
1178
  },
1179
+ flocks: {
1180
+ name: "flocks",
1181
+ displayName: "Flocks",
1182
+ skillsDir: ".flocks/plugins/skills",
1183
+ globalSkillsDir: join(home, ".flocks/plugins/skills"),
1184
+ detectInstalled: async () => {
1185
+ return existsSync(join(home, ".flocks"));
1186
+ }
1187
+ },
1139
1188
  "gemini-cli": {
1140
1189
  name: "gemini-cli",
1141
1190
  displayName: "Gemini CLI",
@@ -1409,6 +1458,7 @@ const SKILLS_SUBDIR = "skills";
1409
1458
  function sanitizeName(name) {
1410
1459
  const asciiName = name.toLowerCase().replace(/[^a-z0-9._]+/g, "-").replace(/^[.\-]+|[.\-]+$/g, "");
1411
1460
  if (asciiName.length === 0) {
1461
+ if (!/[\p{L}\p{N}]/u.test(name)) return "unnamed-skill";
1412
1462
  let hash = 0;
1413
1463
  for (let i = 0; i < name.length; i++) {
1414
1464
  const char = name.charCodeAt(i);
@@ -1919,7 +1969,7 @@ var WellKnownProvider = class {
1919
1969
  });
1920
1970
  }
1921
1971
  for (const { indexUrl, baseUrl: resolvedBase, wellKnownPath } of urlsToTry) try {
1922
- const response = await fetch(indexUrl);
1972
+ const response = await fetch(indexUrl, { headers: getHeaders() });
1923
1973
  if (!response.ok) continue;
1924
1974
  const index = await response.json();
1925
1975
  if (!index.skills || !Array.isArray(index.skills)) continue;
@@ -1980,7 +2030,7 @@ var WellKnownProvider = class {
1980
2030
  const resolvedPath = wellKnownPath ?? this.WELL_KNOWN_PATHS[0];
1981
2031
  const skillBaseUrl = `${baseUrl.replace(/\/$/, "")}/${resolvedPath}/${entry.name}`;
1982
2032
  const skillMdUrl = `${skillBaseUrl}/SKILL.md`;
1983
- const response = await fetch(skillMdUrl);
2033
+ const response = await fetch(skillMdUrl, { headers: getHeaders() });
1984
2034
  if (!response.ok) return null;
1985
2035
  const content = await response.text();
1986
2036
  const { data } = (0, import_gray_matter.default)(content);
@@ -1990,7 +2040,7 @@ var WellKnownProvider = class {
1990
2040
  const filePromises = entry.files.filter((f) => f.toLowerCase() !== "skill.md").map(async (filePath) => {
1991
2041
  try {
1992
2042
  const fileUrl = `${skillBaseUrl}/${filePath}`;
1993
- const fileResponse = await fetch(fileUrl);
2043
+ const fileResponse = await fetch(fileUrl, { headers: getHeaders() });
1994
2044
  if (fileResponse.ok) return {
1995
2045
  path: filePath,
1996
2046
  content: await fileResponse.text()
@@ -2101,10 +2151,7 @@ async function fetchSkillFolderHash(ownerRepo, skillPath, token) {
2101
2151
  if (folderPath.endsWith("/")) folderPath = folderPath.slice(0, -1);
2102
2152
  for (const branch of ["main", "master"]) try {
2103
2153
  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
- };
2154
+ const headers = getHeaders({ Accept: "application/vnd.github.v3+json" });
2108
2155
  if (token) headers["Authorization"] = `Bearer ${token}`;
2109
2156
  const response = await fetch(url, { headers });
2110
2157
  if (!response.ok) continue;
@@ -2231,7 +2278,7 @@ function createEmptyLocalLock() {
2231
2278
  skills: {}
2232
2279
  };
2233
2280
  }
2234
- var version$1 = "0.1.8";
2281
+ var version$1 = "0.2.1";
2235
2282
  const isCancelled$1 = (value) => typeof value === "symbol";
2236
2283
  async function isSourcePrivate(source) {
2237
2284
  const ownerRepo = parseOwnerRepo(source);
@@ -3267,11 +3314,34 @@ const BOLD$2 = "\x1B[1m";
3267
3314
  const DIM$2 = "\x1B[38;5;102m";
3268
3315
  const TEXT$1 = "\x1B[38;5;145m";
3269
3316
  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"}`;
3317
+ const GREEN = "\x1B[32m";
3318
+ const YELLOW$1 = "\x1B[33m";
3319
+ const RED = "\x1B[31m";
3320
+ function formatCount(count) {
3321
+ if (count >= 1e6) return `${(count / 1e6).toFixed(1).replace(/\.0$/, "")}M`;
3322
+ if (count >= 1e3) return `${(count / 1e3).toFixed(1).replace(/\.0$/, "")}K`;
3323
+ return `${count}`;
3324
+ }
3325
+ function formatTrustScore(score) {
3326
+ if (score === void 0 || score === null) return "";
3327
+ return `${score >= 80 ? GREEN : score >= 60 ? YELLOW$1 : RED}TrustScore:${score}${RESET$2}`;
3328
+ }
3329
+ function formatRelativeDate(ts) {
3330
+ if (!ts) return "";
3331
+ const diff = Math.floor(Date.now() / 1e3) - ts;
3332
+ if (diff < 0) return "";
3333
+ const days = Math.floor(diff / 86400);
3334
+ if (days < 1) return "today";
3335
+ if (days < 30) return `${days}d ago`;
3336
+ const months = Math.floor(days / 30);
3337
+ if (months < 12) return `${months}mo ago`;
3338
+ return `${Math.floor(months / 12)}y ago`;
3339
+ }
3340
+ function formatStatsBadges(installs, stars) {
3341
+ const parts = [];
3342
+ if (installs > 0) parts.push(`${CYAN$1}↓ ${formatCount(installs)}${RESET$2}`);
3343
+ if (stars > 0) parts.push(`${YELLOW$1}★ ${formatCount(stars)}${RESET$2}`);
3344
+ return parts.join(" ");
3275
3345
  }
3276
3346
  async function searchSkillsAPI(query) {
3277
3347
  return searchSkillsCatalog(query, 10);
@@ -3311,10 +3381,10 @@ async function runSearchPrompt(initialQuery = "") {
3311
3381
  const displayName = skill.label || skill.name;
3312
3382
  const name = isSelected ? `${BOLD$2}${displayName}${RESET$2}` : `${TEXT$1}${displayName}${RESET$2}`;
3313
3383
  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}` : "";
3384
+ const statsBadge = formatStatsBadges(skill.installs, skill.totalStars);
3385
+ const trustBadge = skill.trustScore !== void 0 ? ` ${skill.trustScore >= 80 ? GREEN : skill.trustScore >= 60 ? YELLOW$1 : RED}TrustScore:${skill.trustScore}${RESET$2}` : "";
3316
3386
  const loadingIndicator = loading && i === 0 ? ` ${DIM$2}...${RESET$2}` : "";
3317
- lines.push(` ${arrow} ${name}${source}${installsBadge}${loadingIndicator}`);
3387
+ lines.push(` ${arrow}${trustBadge} ${name}${source}${statsBadge ? ` ${statsBadge}` : ""}${loadingIndicator}`);
3318
3388
  }
3319
3389
  }
3320
3390
  lines.push("");
@@ -3432,12 +3502,18 @@ ${DIM$2} 2) npx safeskill add <owner/repo@skill>${RESET$2}`;
3432
3502
  console.log();
3433
3503
  for (const skill of results.slice(0, 6)) {
3434
3504
  const pkg = skill.source || skill.slug;
3435
- const installs = formatInstalls(skill.installs);
3436
- const displayName = skill.label || skill.name;
3437
3505
  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}`);
3506
+ console.log(`${TEXT$1}${installRef}${RESET$2}`);
3507
+ const statsBadge = formatStatsBadges(skill.installs, skill.totalStars);
3508
+ const badges = [
3509
+ formatTrustScore(skill.trustScore),
3510
+ statsBadge,
3511
+ skill.updatedAt ? `${DIM$2}${formatRelativeDate(skill.updatedAt)}${RESET$2}` : ""
3512
+ ].filter(Boolean).join(` `);
3513
+ if (badges) console.log(` ${badges}`);
3514
+ const displayName = skill.label || skill.name;
3515
+ const desc = skill.description || (displayName !== skill.name ? displayName : "");
3516
+ if (desc) console.log(`${DIM$2} ${desc}${RESET$2}`);
3441
3517
  if (skill.detailUrl) console.log(`${DIM$2}└ ${skill.detailUrl}${RESET$2}`);
3442
3518
  console.log();
3443
3519
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@safeskill/cli",
3
- "version": "0.1.8",
3
+ "version": "0.2.1",
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",
@@ -39,6 +51,7 @@
39
51
  "deepagents",
40
52
  "droid",
41
53
  "firebender",
54
+ "flocks",
42
55
  "gemini-cli",
43
56
  "github-copilot",
44
57
  "goose",
@@ -95,21 +108,11 @@
95
108
  "engines": {
96
109
  "node": ">=18"
97
110
  },
111
+ "packageManager": "pnpm@10.17.1",
98
112
  "dependencies": {
99
113
  "@types/yauzl": "^2.10.3",
100
114
  "jschardet": "^3.1.4",
101
115
  "node-stream-zip": "^1.15.0",
102
116
  "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
117
  }
115
- }
118
+ }