skillsio 1.1.2 → 1.1.3
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/README.md +204 -6
- package/dist/cli.mjs +164 -141
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
A security-hardened fork of the [skills](https://github.com/vercel-labs/skills) CLI that scans agent skills for
|
|
4
4
|
malicious content before installation.
|
|
5
5
|
|
|
6
|
+
<!-- agent-list:start -->
|
|
7
|
+
Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [37 more](#available-agents).
|
|
8
|
+
<!-- agent-list:end -->
|
|
9
|
+
|
|
6
10
|
The open agent skills ecosystem makes it trivial to install third-party instruction sets into coding agents — but that
|
|
7
11
|
same ease of installation is a vector for prompt injection, data exfiltration, and credential theft.
|
|
8
12
|
[Snyk's analysis](https://snyk.io/blog/) of 3,984 published skills found that **13.4% had critical security issues** and
|
|
@@ -259,18 +263,19 @@ Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [35 more](#su
|
|
|
259
263
|
<!-- supported-agents:start -->
|
|
260
264
|
| Agent | `--agent` | Project Path | Global Path |
|
|
261
265
|
|-------|-----------|--------------|-------------|
|
|
262
|
-
| Amp, Kimi Code CLI, Replit | `amp`, `kimi-cli`, `replit` | `.agents/skills/` | `~/.config/agents/skills/` |
|
|
266
|
+
| Amp, Kimi Code CLI, Replit, Universal | `amp`, `kimi-cli`, `replit`, `universal` | `.agents/skills/` | `~/.config/agents/skills/` |
|
|
263
267
|
| Antigravity | `antigravity` | `.agent/skills/` | `~/.gemini/antigravity/skills/` |
|
|
264
268
|
| Augment | `augment` | `.augment/skills/` | `~/.augment/skills/` |
|
|
265
269
|
| Claude Code | `claude-code` | `.claude/skills/` | `~/.claude/skills/` |
|
|
266
|
-
| OpenClaw | `openclaw` | `skills/` | `~/.
|
|
270
|
+
| OpenClaw | `openclaw` | `skills/` | `~/.openclaw/skills/` |
|
|
267
271
|
| Cline | `cline` | `.cline/skills/` | `~/.cline/skills/` |
|
|
268
272
|
| CodeBuddy | `codebuddy` | `.codebuddy/skills/` | `~/.codebuddy/skills/` |
|
|
269
273
|
| Codex | `codex` | `.agents/skills/` | `~/.codex/skills/` |
|
|
270
274
|
| Command Code | `command-code` | `.commandcode/skills/` | `~/.commandcode/skills/` |
|
|
271
275
|
| Continue | `continue` | `.continue/skills/` | `~/.continue/skills/` |
|
|
276
|
+
| Cortex Code | `cortex` | `.cortex/skills/` | `~/.snowflake/cortex/skills/` |
|
|
272
277
|
| Crush | `crush` | `.crush/skills/` | `~/.config/crush/skills/` |
|
|
273
|
-
| Cursor | `cursor` | `.
|
|
278
|
+
| Cursor | `cursor` | `.agents/skills/` | `~/.cursor/skills/` |
|
|
274
279
|
| Droid | `droid` | `.factory/skills/` | `~/.factory/skills/` |
|
|
275
280
|
| Gemini CLI | `gemini-cli` | `.agents/skills/` | `~/.gemini/skills/` |
|
|
276
281
|
| GitHub Copilot | `github-copilot` | `.agents/skills/` | `~/.copilot/skills/` |
|
|
@@ -298,7 +303,154 @@ Supports **OpenCode**, **Claude Code**, **Codex**, **Cursor**, and [35 more](#su
|
|
|
298
303
|
| AdaL | `adal` | `.adal/skills/` | `~/.adal/skills/` |
|
|
299
304
|
<!-- supported-agents:end -->
|
|
300
305
|
|
|
301
|
-
|
|
306
|
+
> [!NOTE]
|
|
307
|
+
> **Kiro CLI users:** After installing skills, manually add them to your custom agent's `resources` in
|
|
308
|
+
> `.kiro/agents/<agent>.json`:
|
|
309
|
+
>
|
|
310
|
+
> ```json
|
|
311
|
+
> {
|
|
312
|
+
> "resources": ["skill://.kiro/skills/**/SKILL.md"]
|
|
313
|
+
> }
|
|
314
|
+
> ```
|
|
315
|
+
|
|
316
|
+
The CLI automatically detects which coding agents you have installed. If none are detected, you'll be prompted to select
|
|
317
|
+
which agents to install to.
|
|
318
|
+
|
|
319
|
+
## Creating Skills
|
|
320
|
+
|
|
321
|
+
Skills are directories containing a `SKILL.md` file with YAML frontmatter:
|
|
322
|
+
|
|
323
|
+
```markdown
|
|
324
|
+
---
|
|
325
|
+
name: my-skill
|
|
326
|
+
description: What this skill does and when to use it
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
# My Skill
|
|
330
|
+
|
|
331
|
+
Instructions for the agent to follow when this skill is activated.
|
|
332
|
+
|
|
333
|
+
## When to Use
|
|
334
|
+
|
|
335
|
+
Describe the scenarios where this skill should be used.
|
|
336
|
+
|
|
337
|
+
## Steps
|
|
338
|
+
|
|
339
|
+
1. First, do this
|
|
340
|
+
2. Then, do that
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Required Fields
|
|
344
|
+
|
|
345
|
+
- `name`: Unique identifier (lowercase, hyphens allowed)
|
|
346
|
+
- `description`: Brief explanation of what the skill does
|
|
347
|
+
|
|
348
|
+
### Optional Fields
|
|
349
|
+
|
|
350
|
+
- `metadata.internal`: Set to `true` to hide the skill from normal discovery. Internal skills are only visible and
|
|
351
|
+
installable when `INSTALL_INTERNAL_SKILLS=1` is set. Useful for work-in-progress skills or skills meant only for
|
|
352
|
+
internal tooling.
|
|
353
|
+
|
|
354
|
+
```markdown
|
|
355
|
+
---
|
|
356
|
+
name: my-internal-skill
|
|
357
|
+
description: An internal skill not shown by default
|
|
358
|
+
metadata:
|
|
359
|
+
internal: true
|
|
360
|
+
---
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Skill Discovery
|
|
364
|
+
|
|
365
|
+
The CLI searches for skills in these locations within a repository:
|
|
366
|
+
|
|
367
|
+
<!-- skill-discovery:start -->
|
|
368
|
+
- Root directory (if it contains `SKILL.md`)
|
|
369
|
+
- `skills/`
|
|
370
|
+
- `skills/.curated/`
|
|
371
|
+
- `skills/.experimental/`
|
|
372
|
+
- `skills/.system/`
|
|
373
|
+
- `.agents/skills/`
|
|
374
|
+
- `.agent/skills/`
|
|
375
|
+
- `.augment/skills/`
|
|
376
|
+
- `.claude/skills/`
|
|
377
|
+
- `./skills/`
|
|
378
|
+
- `.cline/skills/`
|
|
379
|
+
- `.codebuddy/skills/`
|
|
380
|
+
- `.commandcode/skills/`
|
|
381
|
+
- `.continue/skills/`
|
|
382
|
+
- `.cortex/skills/`
|
|
383
|
+
- `.crush/skills/`
|
|
384
|
+
- `.factory/skills/`
|
|
385
|
+
- `.goose/skills/`
|
|
386
|
+
- `.junie/skills/`
|
|
387
|
+
- `.iflow/skills/`
|
|
388
|
+
- `.kilocode/skills/`
|
|
389
|
+
- `.kiro/skills/`
|
|
390
|
+
- `.kode/skills/`
|
|
391
|
+
- `.mcpjam/skills/`
|
|
392
|
+
- `.vibe/skills/`
|
|
393
|
+
- `.mux/skills/`
|
|
394
|
+
- `.openhands/skills/`
|
|
395
|
+
- `.pi/skills/`
|
|
396
|
+
- `.qoder/skills/`
|
|
397
|
+
- `.qwen/skills/`
|
|
398
|
+
- `.roo/skills/`
|
|
399
|
+
- `.trae/skills/`
|
|
400
|
+
- `.windsurf/skills/`
|
|
401
|
+
- `.zencoder/skills/`
|
|
402
|
+
- `.neovate/skills/`
|
|
403
|
+
- `.pochi/skills/`
|
|
404
|
+
- `.adal/skills/`
|
|
405
|
+
<!-- skill-discovery:end -->
|
|
406
|
+
|
|
407
|
+
### Plugin Manifest Discovery
|
|
408
|
+
|
|
409
|
+
If `.claude-plugin/marketplace.json` or `.claude-plugin/plugin.json` exists, skills declared in those files are also discovered:
|
|
410
|
+
|
|
411
|
+
```json
|
|
412
|
+
// .claude-plugin/marketplace.json
|
|
413
|
+
{
|
|
414
|
+
"metadata": { "pluginRoot": "./plugins" },
|
|
415
|
+
"plugins": [{
|
|
416
|
+
"name": "my-plugin",
|
|
417
|
+
"source": "my-plugin",
|
|
418
|
+
"skills": ["./skills/review", "./skills/test"]
|
|
419
|
+
}]
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
This enables compatibility with the [Claude Code plugin marketplace](https://code.claude.com/docs/en/plugin-marketplaces) ecosystem.
|
|
424
|
+
|
|
425
|
+
If no skills are found in standard locations, a recursive search is performed.
|
|
426
|
+
|
|
427
|
+
## Compatibility
|
|
428
|
+
|
|
429
|
+
Skills are generally compatible across agents since they follow a
|
|
430
|
+
shared [Agent Skills specification](https://agentskills.io). However, some features may be agent-specific:
|
|
431
|
+
|
|
432
|
+
| Feature | OpenCode | OpenHands | Claude Code | Cline | CodeBuddy | Codex | Command Code | Kiro CLI | Cursor | Antigravity | Roo Code | Github Copilot | Amp | OpenClaw | Neovate | Pi | Qoder | Zencoder |
|
|
433
|
+
| --------------- | -------- | --------- | ----------- | ----- | --------- | ----- | ------------ | -------- | ------ | ----------- | -------- | -------------- | --- | -------- | ------- | --- | ----- | -------- |
|
|
434
|
+
| Basic skills | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
|
|
435
|
+
| `allowed-tools` | Yes | Yes | Yes | Yes | Yes | Yes | Yes | No | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | No |
|
|
436
|
+
| `context: fork` | No | No | Yes | No | No | No | No | No | No | No | No | No | No | No | No | No | No | No |
|
|
437
|
+
| Hooks | No | No | Yes | Yes | No | No | No | No | No | No | No | No | No | No | No | No | No | No |
|
|
438
|
+
|
|
439
|
+
## Troubleshooting
|
|
440
|
+
|
|
441
|
+
### "No skills found"
|
|
442
|
+
|
|
443
|
+
Ensure the repository contains valid `SKILL.md` files with both `name` and `description` in the frontmatter.
|
|
444
|
+
|
|
445
|
+
### Skill not loading in agent
|
|
446
|
+
|
|
447
|
+
- Verify the skill was installed to the correct path
|
|
448
|
+
- Check the agent's documentation for skill loading requirements
|
|
449
|
+
- Ensure the `SKILL.md` frontmatter is valid YAML
|
|
450
|
+
|
|
451
|
+
### Permission errors
|
|
452
|
+
|
|
453
|
+
Ensure you have write access to the target directory.
|
|
302
454
|
|
|
303
455
|
## Environment Variables
|
|
304
456
|
|
|
@@ -337,12 +489,27 @@ pnpm format # Format code with Prettier
|
|
|
337
489
|
|
|
338
490
|
## Changelog
|
|
339
491
|
|
|
492
|
+
### 1.1.3
|
|
493
|
+
|
|
494
|
+
- **Synced with upstream** ([vercel-labs/skills](https://github.com/vercel-labs/skills)): universal agent support
|
|
495
|
+
(`.agents/skills/` as a single install target symlinked across agents), new agents (Cortex Code, and others), Kiro CLI
|
|
496
|
+
note, Creating Skills docs, Compatibility table, Troubleshooting section, agent-list badge
|
|
497
|
+
- **Replaced HTML scraping with structured API**: third-party audit now uses the `add-skill.vercel.sh/audit` JSON
|
|
498
|
+
endpoint instead of scraping `skills.sh` HTML — more reliable and richer data (risk levels + alert counts per auditor)
|
|
499
|
+
- **Stronger blocking**: critical and high risk from the API both always prompt for confirmation; `--yes` is ignored for
|
|
500
|
+
both (previously high was bypassed by `--yes` and critical only prompted rather than blocked)
|
|
501
|
+
- **Audit failure warning**: if the skills.sh API is unreachable, a yellow warning is shown rather than silently
|
|
502
|
+
skipping — local scan still runs regardless
|
|
503
|
+
- Removed duplicate audit display (previously showed both an inline scan note and a separate "Security Risk Assessments"
|
|
504
|
+
panel for the same data)
|
|
505
|
+
|
|
340
506
|
### 1.1.2
|
|
341
507
|
|
|
342
508
|
- **skills.sh audit integration**: for GitHub-sourced skills, the CLI now fetches third-party audit results from
|
|
343
509
|
[skills.sh](https://skills.sh) (Snyk, Socket, Gen Agent Trust Hub) and displays them alongside local scan output
|
|
344
|
-
-
|
|
345
|
-
|
|
510
|
+
- Critical or High risk from any auditor escalates the install gate — critical always prompts even with `--yes`, high
|
|
511
|
+
blocks unless `--yes` is set; uses the structured skills.sh JSON API instead of HTML scraping
|
|
512
|
+
- Audit runs in parallel with VirusTotal and fails silently on any network or parse error
|
|
346
513
|
|
|
347
514
|
### 1.1.1
|
|
348
515
|
|
|
@@ -373,6 +540,37 @@ pnpm format # Format code with Prettier
|
|
|
373
540
|
- URL transparency: all external URLs in skill files are shown before installation
|
|
374
541
|
- Scanner rules informed by Snyk and ClawHavoc research
|
|
375
542
|
|
|
543
|
+
## Links
|
|
544
|
+
|
|
545
|
+
- [Agent Skills Specification](https://agentskills.io)
|
|
546
|
+
- [Skills Directory](https://skills.sh)
|
|
547
|
+
- [Amp Skills Documentation](https://ampcode.com/manual#agent-skills)
|
|
548
|
+
- [Antigravity Skills Documentation](https://antigravity.google/docs/skills)
|
|
549
|
+
- [Factory AI / Droid Skills Documentation](https://docs.factory.ai/cli/configuration/skills)
|
|
550
|
+
- [Claude Code Skills Documentation](https://code.claude.com/docs/en/skills)
|
|
551
|
+
- [OpenClaw Skills Documentation](https://docs.openclaw.ai/tools/skills)
|
|
552
|
+
- [Cline Skills Documentation](https://docs.cline.bot/features/skills)
|
|
553
|
+
- [CodeBuddy Skills Documentation](https://www.codebuddy.ai/docs/ide/Features/Skills)
|
|
554
|
+
- [Codex Skills Documentation](https://developers.openai.com/codex/skills)
|
|
555
|
+
- [Command Code Skills Documentation](https://commandcode.ai/docs/skills)
|
|
556
|
+
- [Crush Skills Documentation](https://github.com/charmbracelet/crush?tab=readme-ov-file#agent-skills)
|
|
557
|
+
- [Cursor Skills Documentation](https://cursor.com/docs/context/skills)
|
|
558
|
+
- [Gemini CLI Skills Documentation](https://geminicli.com/docs/cli/skills/)
|
|
559
|
+
- [GitHub Copilot Agent Skills](https://docs.github.com/en/copilot/concepts/agents/about-agent-skills)
|
|
560
|
+
- [iFlow CLI Skills Documentation](https://platform.iflow.cn/en/cli/examples/skill)
|
|
561
|
+
- [Kimi Code CLI Skills Documentation](https://moonshotai.github.io/kimi-cli/en/customization/skills.html)
|
|
562
|
+
- [Kiro CLI Skills Documentation](https://kiro.dev/docs/cli/custom-agents/configuration-reference/#skill-resources)
|
|
563
|
+
- [Kode Skills Documentation](https://github.com/shareAI-lab/kode/blob/main/docs/skills.md)
|
|
564
|
+
- [OpenCode Skills Documentation](https://opencode.ai/docs/skills)
|
|
565
|
+
- [Qwen Code Skills Documentation](https://qwenlm.github.io/qwen-code-docs/en/users/features/skills/)
|
|
566
|
+
- [OpenHands Skills Documentation](https://docs.openhands.ai/modules/usage/how-to/using-skills)
|
|
567
|
+
- [Pi Skills Documentation](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/skills.md)
|
|
568
|
+
- [Qoder Skills Documentation](https://docs.qoder.com/cli/Skills)
|
|
569
|
+
- [Replit Skills Documentation](https://docs.replit.com/replitai/skills)
|
|
570
|
+
- [Roo Code Skills Documentation](https://docs.roocode.com/features/skills)
|
|
571
|
+
- [Trae Skills Documentation](https://docs.trae.ai/ide/skills)
|
|
572
|
+
- [Vercel Agent Skills Repository](https://github.com/vercel-labs/agent-skills)
|
|
573
|
+
|
|
376
574
|
## Research
|
|
377
575
|
|
|
378
576
|
The scanner rules are informed by the following research into malicious agent skills:
|
package/dist/cli.mjs
CHANGED
|
@@ -29,14 +29,6 @@ function getOwnerRepo(parsed) {
|
|
|
29
29
|
} catch {}
|
|
30
30
|
return null;
|
|
31
31
|
}
|
|
32
|
-
function parseOwnerRepo(ownerRepo) {
|
|
33
|
-
const match = ownerRepo.match(/^([^/]+)\/([^/]+)$/);
|
|
34
|
-
if (match) return {
|
|
35
|
-
owner: match[1],
|
|
36
|
-
repo: match[2]
|
|
37
|
-
};
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
32
|
async function isRepoPrivate(owner, repo) {
|
|
41
33
|
try {
|
|
42
34
|
const res = await fetch(`https://api.github.com/repos/${owner}/${repo}`);
|
|
@@ -58,7 +50,10 @@ function isDirectSkillUrl(input) {
|
|
|
58
50
|
if (input.includes("gitlab.com/") && !input.includes("/-/raw/")) return false;
|
|
59
51
|
return true;
|
|
60
52
|
}
|
|
53
|
+
const SOURCE_ALIASES = { "coinbase/agentWallet": "coinbase/agentic-wallet-skills" };
|
|
61
54
|
function parseSource(input) {
|
|
55
|
+
const alias = SOURCE_ALIASES[input];
|
|
56
|
+
if (alias) input = alias;
|
|
62
57
|
if (isLocalPath(input)) {
|
|
63
58
|
const resolvedPath = resolve(input);
|
|
64
59
|
return {
|
|
@@ -177,7 +172,8 @@ const S_STEP_CANCEL = import_picocolors.default.red("■");
|
|
|
177
172
|
const S_STEP_SUBMIT = import_picocolors.default.green("◇");
|
|
178
173
|
const S_RADIO_ACTIVE = import_picocolors.default.green("●");
|
|
179
174
|
const S_RADIO_INACTIVE = import_picocolors.default.dim("○");
|
|
180
|
-
|
|
175
|
+
import_picocolors.default.green("✓");
|
|
176
|
+
const S_BULLET = import_picocolors.default.green("•");
|
|
181
177
|
const S_BAR = import_picocolors.default.dim("│");
|
|
182
178
|
const S_BAR_H = import_picocolors.default.dim("─");
|
|
183
179
|
const cancelSymbol = Symbol("cancel");
|
|
@@ -220,10 +216,11 @@ async function searchMultiselect(options) {
|
|
|
220
216
|
if (state === "active") {
|
|
221
217
|
if (lockedSection && lockedSection.items.length > 0) {
|
|
222
218
|
lines.push(`${S_BAR}`);
|
|
223
|
-
|
|
224
|
-
|
|
219
|
+
const lockedTitle = `${import_picocolors.default.bold(lockedSection.title)} ${import_picocolors.default.dim("── always included")}`;
|
|
220
|
+
lines.push(`${S_BAR} ${S_BAR_H}${S_BAR_H} ${lockedTitle} ${S_BAR_H.repeat(12)}`);
|
|
221
|
+
for (const item of lockedSection.items) lines.push(`${S_BAR} ${S_BULLET} ${import_picocolors.default.bold(item.label)}`);
|
|
225
222
|
lines.push(`${S_BAR}`);
|
|
226
|
-
lines.push(`${S_BAR} ${S_BAR_H}${S_BAR_H} ${import_picocolors.default.bold("
|
|
223
|
+
lines.push(`${S_BAR} ${S_BAR_H}${S_BAR_H} ${import_picocolors.default.bold("Additional agents")} ${S_BAR_H.repeat(29)}`);
|
|
227
224
|
}
|
|
228
225
|
const searchLine = `${S_BAR} ${import_picocolors.default.dim("Search:")} ${query}${import_picocolors.default.inverse(" ")}`;
|
|
229
226
|
lines.push(searchLine);
|
|
@@ -436,6 +433,7 @@ async function parseSkillMd(skillMdPath, options) {
|
|
|
436
433
|
const content = await readFile(skillMdPath, "utf-8");
|
|
437
434
|
const { data } = (0, import_gray_matter.default)(content);
|
|
438
435
|
if (!data.name || !data.description) return null;
|
|
436
|
+
if (typeof data.name !== "string" || typeof data.description !== "string") return null;
|
|
439
437
|
if (data.metadata?.internal === true && !shouldInstallInternalSkills() && !options?.includeInternal) return null;
|
|
440
438
|
return {
|
|
441
439
|
name: data.name,
|
|
@@ -485,7 +483,6 @@ async function discoverSkills(basePath, subpath, options) {
|
|
|
485
483
|
join(searchPath, ".codex/skills"),
|
|
486
484
|
join(searchPath, ".commandcode/skills"),
|
|
487
485
|
join(searchPath, ".continue/skills"),
|
|
488
|
-
join(searchPath, ".cursor/skills"),
|
|
489
486
|
join(searchPath, ".github/skills"),
|
|
490
487
|
join(searchPath, ".goose/skills"),
|
|
491
488
|
join(searchPath, ".iflow/skills"),
|
|
@@ -544,6 +541,12 @@ const home = homedir();
|
|
|
544
541
|
const configHome = xdgConfig ?? join(home, ".config");
|
|
545
542
|
const codexHome = process.env.CODEX_HOME?.trim() || join(home, ".codex");
|
|
546
543
|
const claudeHome = process.env.CLAUDE_CONFIG_DIR?.trim() || join(home, ".claude");
|
|
544
|
+
function getOpenClawGlobalSkillsDir(homeDir = home, pathExists = existsSync) {
|
|
545
|
+
if (pathExists(join(homeDir, ".openclaw"))) return join(homeDir, ".openclaw/skills");
|
|
546
|
+
if (pathExists(join(homeDir, ".clawdbot"))) return join(homeDir, ".clawdbot/skills");
|
|
547
|
+
if (pathExists(join(homeDir, ".moltbot"))) return join(homeDir, ".moltbot/skills");
|
|
548
|
+
return join(homeDir, ".openclaw/skills");
|
|
549
|
+
}
|
|
547
550
|
const agents = {
|
|
548
551
|
amp: {
|
|
549
552
|
name: "amp",
|
|
@@ -585,7 +588,7 @@ const agents = {
|
|
|
585
588
|
name: "openclaw",
|
|
586
589
|
displayName: "OpenClaw",
|
|
587
590
|
skillsDir: "skills",
|
|
588
|
-
globalSkillsDir:
|
|
591
|
+
globalSkillsDir: getOpenClawGlobalSkillsDir(),
|
|
589
592
|
detectInstalled: async () => {
|
|
590
593
|
return existsSync(join(home, ".openclaw")) || existsSync(join(home, ".clawdbot")) || existsSync(join(home, ".moltbot"));
|
|
591
594
|
}
|
|
@@ -635,6 +638,15 @@ const agents = {
|
|
|
635
638
|
return existsSync(join(process.cwd(), ".continue")) || existsSync(join(home, ".continue"));
|
|
636
639
|
}
|
|
637
640
|
},
|
|
641
|
+
cortex: {
|
|
642
|
+
name: "cortex",
|
|
643
|
+
displayName: "Cortex Code",
|
|
644
|
+
skillsDir: ".cortex/skills",
|
|
645
|
+
globalSkillsDir: join(home, ".snowflake/cortex/skills"),
|
|
646
|
+
detectInstalled: async () => {
|
|
647
|
+
return existsSync(join(home, ".snowflake/cortex"));
|
|
648
|
+
}
|
|
649
|
+
},
|
|
638
650
|
crush: {
|
|
639
651
|
name: "crush",
|
|
640
652
|
displayName: "Crush",
|
|
@@ -647,7 +659,7 @@ const agents = {
|
|
|
647
659
|
cursor: {
|
|
648
660
|
name: "cursor",
|
|
649
661
|
displayName: "Cursor",
|
|
650
|
-
skillsDir: ".
|
|
662
|
+
skillsDir: ".agents/skills",
|
|
651
663
|
globalSkillsDir: join(home, ".cursor/skills"),
|
|
652
664
|
detectInstalled: async () => {
|
|
653
665
|
return existsSync(join(home, ".cursor"));
|
|
@@ -896,6 +908,14 @@ const agents = {
|
|
|
896
908
|
detectInstalled: async () => {
|
|
897
909
|
return existsSync(join(home, ".adal"));
|
|
898
910
|
}
|
|
911
|
+
},
|
|
912
|
+
universal: {
|
|
913
|
+
name: "universal",
|
|
914
|
+
displayName: "Universal",
|
|
915
|
+
skillsDir: ".agents/skills",
|
|
916
|
+
globalSkillsDir: join(configHome, "agents/skills"),
|
|
917
|
+
showInUniversalList: false,
|
|
918
|
+
detectInstalled: async () => false
|
|
899
919
|
}
|
|
900
920
|
};
|
|
901
921
|
async function detectInstalledAgents() {
|
|
@@ -3255,7 +3275,7 @@ function extractRemoteSkillFiles(remoteSkill) {
|
|
|
3255
3275
|
function extractWellKnownSkillFiles(skill) {
|
|
3256
3276
|
return skill.files;
|
|
3257
3277
|
}
|
|
3258
|
-
const NOT_FOUND
|
|
3278
|
+
const NOT_FOUND = {
|
|
3259
3279
|
found: false,
|
|
3260
3280
|
verdict: "unknown",
|
|
3261
3281
|
maliciousCount: 0,
|
|
@@ -3266,19 +3286,19 @@ async function lookupFileHash(sha256, apiKey) {
|
|
|
3266
3286
|
try {
|
|
3267
3287
|
response = await fetch(`https://www.virustotal.com/api/v3/files/${sha256}`, { headers: { "x-apikey": apiKey } });
|
|
3268
3288
|
} catch {
|
|
3269
|
-
return NOT_FOUND
|
|
3289
|
+
return NOT_FOUND;
|
|
3270
3290
|
}
|
|
3271
|
-
if (response.status === 404) return NOT_FOUND
|
|
3272
|
-
if (response.status === 429) return NOT_FOUND
|
|
3273
|
-
if (!response.ok) return NOT_FOUND
|
|
3291
|
+
if (response.status === 404) return NOT_FOUND;
|
|
3292
|
+
if (response.status === 429) return NOT_FOUND;
|
|
3293
|
+
if (!response.ok) return NOT_FOUND;
|
|
3274
3294
|
let body;
|
|
3275
3295
|
try {
|
|
3276
3296
|
body = await response.json();
|
|
3277
3297
|
} catch {
|
|
3278
|
-
return NOT_FOUND
|
|
3298
|
+
return NOT_FOUND;
|
|
3279
3299
|
}
|
|
3280
3300
|
const attrs = body.data?.attributes;
|
|
3281
|
-
if (!attrs) return NOT_FOUND
|
|
3301
|
+
if (!attrs) return NOT_FOUND;
|
|
3282
3302
|
const stats = attrs.last_analysis_stats;
|
|
3283
3303
|
const maliciousCount = stats?.malicious ?? 0;
|
|
3284
3304
|
const totalEngines = stats ? Object.values(stats).reduce((sum, n) => sum + n, 0) : 0;
|
|
@@ -3304,62 +3324,22 @@ async function lookupFileHash(sha256, apiKey) {
|
|
|
3304
3324
|
async function checkSkillOnVT(skillContent, apiKey) {
|
|
3305
3325
|
return lookupFileHash(createHash("sha256").update(skillContent).digest("hex"), apiKey);
|
|
3306
3326
|
}
|
|
3307
|
-
const
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
displayName: "Snyk"
|
|
3311
|
-
},
|
|
3312
|
-
{
|
|
3313
|
-
id: "socket",
|
|
3314
|
-
displayName: "Socket"
|
|
3315
|
-
},
|
|
3316
|
-
{
|
|
3317
|
-
id: "agent-trust-hub",
|
|
3318
|
-
displayName: "Gen Agent Trust Hub"
|
|
3319
|
-
}
|
|
3320
|
-
];
|
|
3321
|
-
const NOT_FOUND = {
|
|
3322
|
-
found: false,
|
|
3323
|
-
permalink: "",
|
|
3324
|
-
audits: [],
|
|
3325
|
-
anyFail: false
|
|
3326
|
-
};
|
|
3327
|
-
function parseAudits(html, baseUrl) {
|
|
3328
|
-
return AUDITORS.map(({ id, displayName }) => {
|
|
3329
|
-
const pattern = new RegExp(`\\/security\\/${id}[^]{0,400}?\\b(Pass|Fail)\\b`, "is");
|
|
3330
|
-
const match = html.match(pattern);
|
|
3331
|
-
let status = "unknown";
|
|
3332
|
-
if (match) status = match[1].toLowerCase() === "pass" ? "pass" : "fail";
|
|
3333
|
-
return {
|
|
3334
|
-
auditor: id,
|
|
3335
|
-
displayName,
|
|
3336
|
-
status,
|
|
3337
|
-
permalink: `${baseUrl}/security/${id}`
|
|
3338
|
-
};
|
|
3339
|
-
});
|
|
3340
|
-
}
|
|
3341
|
-
async function checkSkillOnSkillsSh(source) {
|
|
3342
|
-
const { owner, repo, skillFolder } = source;
|
|
3343
|
-
const permalink = `https://skills.sh/${owner}/${repo}/${skillFolder}`;
|
|
3327
|
+
const AUDIT_URL = "https://add-skill.vercel.sh/audit";
|
|
3328
|
+
async function fetchAuditData(source, skillSlugs, timeoutMs = 3e3) {
|
|
3329
|
+
if (skillSlugs.length === 0) return null;
|
|
3344
3330
|
try {
|
|
3331
|
+
const params = new URLSearchParams({
|
|
3332
|
+
source,
|
|
3333
|
+
skills: skillSlugs.join(",")
|
|
3334
|
+
});
|
|
3345
3335
|
const controller = new AbortController();
|
|
3346
|
-
const timeout = setTimeout(() => controller.abort(),
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
clearTimeout(timeout);
|
|
3352
|
-
}
|
|
3353
|
-
if (!response.ok) return NOT_FOUND;
|
|
3354
|
-
const audits = parseAudits(await response.text(), permalink);
|
|
3355
|
-
return {
|
|
3356
|
-
found: true,
|
|
3357
|
-
permalink,
|
|
3358
|
-
audits,
|
|
3359
|
-
anyFail: audits.some((a) => a.status === "fail")
|
|
3360
|
-
};
|
|
3336
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
3337
|
+
const response = await fetch(`${AUDIT_URL}?${params.toString()}`, { signal: controller.signal });
|
|
3338
|
+
clearTimeout(timeout);
|
|
3339
|
+
if (!response.ok) return null;
|
|
3340
|
+
return await response.json();
|
|
3361
3341
|
} catch {
|
|
3362
|
-
return
|
|
3342
|
+
return null;
|
|
3363
3343
|
}
|
|
3364
3344
|
}
|
|
3365
3345
|
const SEVERITY_LABELS = {
|
|
@@ -3376,6 +3356,14 @@ const SEVERITY_ORDER = {
|
|
|
3376
3356
|
high: 3,
|
|
3377
3357
|
critical: 4
|
|
3378
3358
|
};
|
|
3359
|
+
const AUDIT_RISK_ORDER = {
|
|
3360
|
+
unknown: 0,
|
|
3361
|
+
safe: 0,
|
|
3362
|
+
low: 1,
|
|
3363
|
+
medium: 2,
|
|
3364
|
+
high: 3,
|
|
3365
|
+
critical: 4
|
|
3366
|
+
};
|
|
3379
3367
|
function displayVTVerdict(verdict) {
|
|
3380
3368
|
if (!verdict.found) {
|
|
3381
3369
|
M.message(import_picocolors.default.dim(" VirusTotal: not found (local scan only)"));
|
|
@@ -3390,16 +3378,61 @@ function displayVTVerdict(verdict) {
|
|
|
3390
3378
|
}
|
|
3391
3379
|
if (verdict.permalink) M.message(import_picocolors.default.dim(` ${verdict.permalink}`));
|
|
3392
3380
|
}
|
|
3393
|
-
function
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
return `[${import_picocolors.default.
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3381
|
+
function auditRiskBadge(displayName, audit) {
|
|
3382
|
+
const alerts = audit.alerts != null && audit.alerts > 0 ? ` ${audit.alerts} alert${audit.alerts !== 1 ? "s" : ""}` : "";
|
|
3383
|
+
switch (audit.risk) {
|
|
3384
|
+
case "critical": return `[${import_picocolors.default.red(import_picocolors.default.bold(`${displayName} ✗ critical${alerts}`))}]`;
|
|
3385
|
+
case "high": return `[${import_picocolors.default.red(`${displayName} ✗ high${alerts}`)}]`;
|
|
3386
|
+
case "medium": return `[${import_picocolors.default.yellow(`${displayName} ⚠ medium${alerts}`)}]`;
|
|
3387
|
+
case "low": return `[${import_picocolors.default.green(`${displayName} ✓ low`)}]`;
|
|
3388
|
+
case "safe": return `[${import_picocolors.default.green(`${displayName} ✓`)}]`;
|
|
3389
|
+
default: return `[${import_picocolors.default.dim(`${displayName} ~`)}]`;
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
const AUDITORS = [
|
|
3393
|
+
{
|
|
3394
|
+
id: "ath",
|
|
3395
|
+
displayName: "Trust Hub"
|
|
3396
|
+
},
|
|
3397
|
+
{
|
|
3398
|
+
id: "socket",
|
|
3399
|
+
displayName: "Socket"
|
|
3400
|
+
},
|
|
3401
|
+
{
|
|
3402
|
+
id: "snyk",
|
|
3403
|
+
displayName: "Snyk"
|
|
3404
|
+
}
|
|
3405
|
+
];
|
|
3406
|
+
function displayAuditResults(skillNames, auditData, source) {
|
|
3407
|
+
if (!skillNames.some((name) => {
|
|
3408
|
+
const data = auditData[name];
|
|
3409
|
+
return data && Object.keys(data).length > 0;
|
|
3410
|
+
})) return;
|
|
3411
|
+
for (const skillName of skillNames) {
|
|
3412
|
+
const data = auditData[skillName];
|
|
3413
|
+
if (!data || Object.keys(data).length === 0) continue;
|
|
3414
|
+
const badges = AUDITORS.map(({ id, displayName }) => {
|
|
3415
|
+
const audit = data[id];
|
|
3416
|
+
return audit ? auditRiskBadge(displayName, audit) : `[${import_picocolors.default.dim(`${displayName} ~`)}]`;
|
|
3417
|
+
}).join(" ");
|
|
3418
|
+
const label = skillNames.length > 1 ? `${import_picocolors.default.cyan(skillName)}: ` : "";
|
|
3419
|
+
M.message(` ${import_picocolors.default.cyan("◆")} ${label}${badges}`);
|
|
3420
|
+
}
|
|
3421
|
+
M.message(import_picocolors.default.dim(` https://skills.sh/${source}`));
|
|
3422
|
+
}
|
|
3423
|
+
function maxAuditRisk(skillNames, auditData) {
|
|
3424
|
+
let max = 0;
|
|
3425
|
+
for (const skillName of skillNames) {
|
|
3426
|
+
const data = auditData[skillName];
|
|
3427
|
+
if (!data) continue;
|
|
3428
|
+
for (const audit of Object.values(data)) {
|
|
3429
|
+
const level = AUDIT_RISK_ORDER[audit.risk] ?? 0;
|
|
3430
|
+
if (level > max) max = level;
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
3433
|
+
if (max >= 4) return "critical";
|
|
3434
|
+
if (max >= 3) return "high";
|
|
3435
|
+
return null;
|
|
3403
3436
|
}
|
|
3404
3437
|
async function presentScanResults(results, options) {
|
|
3405
3438
|
const allFindings = results.flatMap((r) => r.findings.map((f) => ({
|
|
@@ -3407,10 +3440,12 @@ async function presentScanResults(results, options) {
|
|
|
3407
3440
|
skillName: r.skillName
|
|
3408
3441
|
})));
|
|
3409
3442
|
const allUrls = [...new Set(results.flatMap((r) => r.urls))];
|
|
3443
|
+
const skillNames = results.map((r) => r.skillName);
|
|
3410
3444
|
const vtVerdicts = /* @__PURE__ */ new Map();
|
|
3411
|
-
|
|
3445
|
+
let auditData = null;
|
|
3446
|
+
let auditFailed = false;
|
|
3412
3447
|
let vtEscalate = false;
|
|
3413
|
-
let
|
|
3448
|
+
let auditEscalate = null;
|
|
3414
3449
|
await Promise.all([(async () => {
|
|
3415
3450
|
if (options.vtKey && options.skillContents) for (const [skillName, content] of options.skillContents) try {
|
|
3416
3451
|
const verdict = await checkSkillOnVT(content, options.vtKey);
|
|
@@ -3418,16 +3453,20 @@ async function presentScanResults(results, options) {
|
|
|
3418
3453
|
if (verdict.found && verdict.verdict === "malicious") vtEscalate = true;
|
|
3419
3454
|
} catch {}
|
|
3420
3455
|
})(), (async () => {
|
|
3421
|
-
if (options.
|
|
3422
|
-
const
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3456
|
+
if (options.auditSource) {
|
|
3457
|
+
const data = await fetchAuditData(options.auditSource, skillNames);
|
|
3458
|
+
if (data) {
|
|
3459
|
+
auditData = data;
|
|
3460
|
+
auditEscalate = maxAuditRisk(skillNames, data);
|
|
3461
|
+
} else auditFailed = true;
|
|
3462
|
+
}
|
|
3426
3463
|
})()]);
|
|
3427
|
-
|
|
3464
|
+
const anyEscalation = vtEscalate || auditEscalate !== null;
|
|
3465
|
+
if (allFindings.length === 0 && !anyEscalation) {
|
|
3428
3466
|
M.success(import_picocolors.default.green("Security scan passed — no issues found"));
|
|
3429
3467
|
if (vtVerdicts.size > 0) for (const [, verdict] of vtVerdicts) displayVTVerdict(verdict);
|
|
3430
|
-
|
|
3468
|
+
if (auditData && options.auditSource) displayAuditResults(skillNames, auditData, options.auditSource);
|
|
3469
|
+
if (auditFailed) M.warn(import_picocolors.default.yellow("skills.sh audit unavailable — third-party risk data could not be fetched"));
|
|
3431
3470
|
if (allUrls.length > 0) return displayUrlsAndPrompt(allUrls, options);
|
|
3432
3471
|
return true;
|
|
3433
3472
|
}
|
|
@@ -3451,10 +3490,11 @@ async function presentScanResults(results, options) {
|
|
|
3451
3490
|
console.log();
|
|
3452
3491
|
for (const [, verdict] of vtVerdicts) displayVTVerdict(verdict);
|
|
3453
3492
|
}
|
|
3454
|
-
if (
|
|
3493
|
+
if (auditData && options.auditSource) {
|
|
3455
3494
|
console.log();
|
|
3456
|
-
|
|
3495
|
+
displayAuditResults(skillNames, auditData, options.auditSource);
|
|
3457
3496
|
}
|
|
3497
|
+
if (auditFailed) M.warn(import_picocolors.default.yellow("skills.sh audit unavailable — third-party risk data could not be fetched"));
|
|
3458
3498
|
if (allUrls.length > 0) {
|
|
3459
3499
|
console.log();
|
|
3460
3500
|
M.info(`External URLs found in skill files (${allUrls.length}):`);
|
|
@@ -3462,25 +3502,18 @@ async function presentScanResults(results, options) {
|
|
|
3462
3502
|
}
|
|
3463
3503
|
console.log();
|
|
3464
3504
|
if (vtEscalate) overallMax = "critical";
|
|
3465
|
-
if (
|
|
3505
|
+
if (auditEscalate === "critical") overallMax = "critical";
|
|
3506
|
+
else if (auditEscalate === "high" && SEVERITY_ORDER[overallMax] < SEVERITY_ORDER["high"]) overallMax = "high";
|
|
3466
3507
|
if (SEVERITY_ORDER[overallMax] <= SEVERITY_ORDER["medium"]) {
|
|
3467
3508
|
M.info(import_picocolors.default.dim("Low/medium severity findings — proceeding with installation"));
|
|
3468
3509
|
return true;
|
|
3469
3510
|
}
|
|
3470
|
-
if (overallMax === "critical")
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
if (pD(confirmed) || !confirmed) return false;
|
|
3477
|
-
return true;
|
|
3478
|
-
}
|
|
3479
|
-
if (options.yes) {
|
|
3480
|
-
M.warn(import_picocolors.default.yellow("High severity findings detected — proceeding (--yes flag set)"));
|
|
3481
|
-
return true;
|
|
3482
|
-
}
|
|
3483
|
-
const confirmed = await ye({ message: import_picocolors.default.yellow("Security warnings found. Continue with installation?") });
|
|
3511
|
+
if (overallMax === "critical") M.error(import_picocolors.default.red(import_picocolors.default.bold("Critical security issues detected. This skill may be malicious.")));
|
|
3512
|
+
else M.error(import_picocolors.default.red("High severity security issues detected."));
|
|
3513
|
+
const confirmed = await ye({
|
|
3514
|
+
message: import_picocolors.default.yellow("Install anyway?"),
|
|
3515
|
+
initialValue: false
|
|
3516
|
+
});
|
|
3484
3517
|
if (pD(confirmed) || !confirmed) return false;
|
|
3485
3518
|
return true;
|
|
3486
3519
|
}
|
|
@@ -3507,20 +3540,12 @@ function resolveExternalRules(options) {
|
|
|
3507
3540
|
_externalRulesCache.set(key, loaded);
|
|
3508
3541
|
return loaded;
|
|
3509
3542
|
}
|
|
3510
|
-
function
|
|
3511
|
-
|
|
3512
|
-
if (!sourceUrl || !sourceUrl.includes("github.com")) return void 0;
|
|
3543
|
+
function extractAuditSource(sourceUrl) {
|
|
3544
|
+
if (!sourceUrl?.includes("github.com")) return void 0;
|
|
3513
3545
|
try {
|
|
3514
3546
|
const parts = new URL(sourceUrl).pathname.slice(1).replace(/\.git$/, "").split("/").filter(Boolean);
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
const skillFolder = parts.length > 2 ? parts.at(-1) : remoteSkill.installName;
|
|
3518
|
-
const sources = /* @__PURE__ */ new Map();
|
|
3519
|
-
sources.set(remoteSkill.installName, {
|
|
3520
|
-
...ownerRepo,
|
|
3521
|
-
skillFolder
|
|
3522
|
-
});
|
|
3523
|
-
return sources;
|
|
3547
|
+
if (parts.length < 2) return void 0;
|
|
3548
|
+
return parts.slice(0, 2).join("/");
|
|
3524
3549
|
} catch {
|
|
3525
3550
|
return;
|
|
3526
3551
|
}
|
|
@@ -3784,12 +3809,12 @@ async function handleRemoteSkill(source, url, options, spinner) {
|
|
|
3784
3809
|
});
|
|
3785
3810
|
const vtKey = options.vtKey || process.env.VT_API_KEY;
|
|
3786
3811
|
const skillContents = vtKey ? new Map([[remoteSkill.installName, remoteSkill.content]]) : void 0;
|
|
3787
|
-
const
|
|
3812
|
+
const auditSource = extractAuditSource(remoteSkill.sourceUrl);
|
|
3788
3813
|
if (!await presentScanResults([scanResult], {
|
|
3789
3814
|
yes: options.yes,
|
|
3790
3815
|
vtKey,
|
|
3791
3816
|
skillContents,
|
|
3792
|
-
|
|
3817
|
+
auditSource
|
|
3793
3818
|
})) {
|
|
3794
3819
|
xe("Installation cancelled due to security concerns");
|
|
3795
3820
|
process.exit(0);
|
|
@@ -4461,6 +4486,7 @@ async function runAdd(args, options = {}) {
|
|
|
4461
4486
|
}
|
|
4462
4487
|
selectedSkills = selected;
|
|
4463
4488
|
}
|
|
4489
|
+
const auditSource = getOwnerRepo(parsed) ?? void 0;
|
|
4464
4490
|
let targetAgents;
|
|
4465
4491
|
const validAgents = Object.keys(agents);
|
|
4466
4492
|
if (options.agent?.includes("*")) {
|
|
@@ -4598,24 +4624,11 @@ async function runAdd(args, options = {}) {
|
|
|
4598
4624
|
}
|
|
4599
4625
|
}
|
|
4600
4626
|
spinner.stop("Security scan complete");
|
|
4601
|
-
const skillsShSources = /* @__PURE__ */ new Map();
|
|
4602
|
-
if (parsed.type === "github") {
|
|
4603
|
-
const ownerRepoStr = getOwnerRepo(parsed);
|
|
4604
|
-
const ownerRepo = ownerRepoStr ? parseOwnerRepo(ownerRepoStr) : null;
|
|
4605
|
-
if (ownerRepo) for (const skill of selectedSkills) {
|
|
4606
|
-
const displayName = getSkillDisplayName(skill);
|
|
4607
|
-
const skillFolder = skill.path.split("/").at(-1) ?? skill.name;
|
|
4608
|
-
skillsShSources.set(displayName, {
|
|
4609
|
-
...ownerRepo,
|
|
4610
|
-
skillFolder
|
|
4611
|
-
});
|
|
4612
|
-
}
|
|
4613
|
-
}
|
|
4614
4627
|
if (!await presentScanResults(scanResults, {
|
|
4615
4628
|
yes: options.yes,
|
|
4616
4629
|
vtKey,
|
|
4617
4630
|
skillContents,
|
|
4618
|
-
|
|
4631
|
+
auditSource
|
|
4619
4632
|
})) {
|
|
4620
4633
|
xe("Installation cancelled due to security concerns");
|
|
4621
4634
|
await cleanup(tempDir);
|
|
@@ -4828,7 +4841,14 @@ const RESET$2 = "\x1B[0m";
|
|
|
4828
4841
|
const BOLD$2 = "\x1B[1m";
|
|
4829
4842
|
const DIM$2 = "\x1B[38;5;102m";
|
|
4830
4843
|
const TEXT$1 = "\x1B[38;5;145m";
|
|
4844
|
+
const CYAN$1 = "\x1B[36m";
|
|
4831
4845
|
const SEARCH_API_BASE = process.env.SKILLS_API_URL || "https://skills.sh";
|
|
4846
|
+
function formatInstalls(count) {
|
|
4847
|
+
if (!count || count <= 0) return "";
|
|
4848
|
+
if (count >= 1e6) return `${(count / 1e6).toFixed(1).replace(/\.0$/, "")}M installs`;
|
|
4849
|
+
if (count >= 1e3) return `${(count / 1e3).toFixed(1).replace(/\.0$/, "")}K installs`;
|
|
4850
|
+
return `${count} install${count === 1 ? "" : "s"}`;
|
|
4851
|
+
}
|
|
4832
4852
|
async function searchSkillsAPI(query) {
|
|
4833
4853
|
try {
|
|
4834
4854
|
const url = `${SEARCH_API_BASE}/api/search?q=${encodeURIComponent(query)}&limit=10`;
|
|
@@ -4878,8 +4898,10 @@ async function runSearchPrompt(initialQuery = "") {
|
|
|
4878
4898
|
const arrow = isSelected ? `${BOLD$2}>${RESET$2}` : " ";
|
|
4879
4899
|
const name = isSelected ? `${BOLD$2}${skill.name}${RESET$2}` : `${TEXT$1}${skill.name}${RESET$2}`;
|
|
4880
4900
|
const source = skill.source ? ` ${DIM$2}${skill.source}${RESET$2}` : "";
|
|
4901
|
+
const installs = formatInstalls(skill.installs);
|
|
4902
|
+
const installsBadge = installs ? ` ${CYAN$1}${installs}${RESET$2}` : "";
|
|
4881
4903
|
const loadingIndicator = loading && i === 0 ? ` ${DIM$2}...${RESET$2}` : "";
|
|
4882
|
-
lines.push(` ${arrow} ${name}${source}${loadingIndicator}`);
|
|
4904
|
+
lines.push(` ${arrow} ${name}${source}${installsBadge}${loadingIndicator}`);
|
|
4883
4905
|
}
|
|
4884
4906
|
}
|
|
4885
4907
|
lines.push("");
|
|
@@ -4992,7 +5014,8 @@ ${DIM$2} 2) npx skills add <owner/repo@skill>${RESET$2}`;
|
|
|
4992
5014
|
console.log();
|
|
4993
5015
|
for (const skill of results.slice(0, 6)) {
|
|
4994
5016
|
const pkg = skill.source || skill.slug;
|
|
4995
|
-
|
|
5017
|
+
const installs = formatInstalls(skill.installs);
|
|
5018
|
+
console.log(`${TEXT$1}${pkg}@${skill.name}${RESET$2}${installs ? ` ${CYAN$1}${installs}${RESET$2}` : ""}`);
|
|
4996
5019
|
console.log(`${DIM$2}└ https://skills.sh/${skill.slug}${RESET$2}`);
|
|
4997
5020
|
console.log();
|
|
4998
5021
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillsio",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "The SECURE open agent skills ecosystem",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"codex",
|
|
47
47
|
"command-code",
|
|
48
48
|
"continue",
|
|
49
|
+
"cortex",
|
|
49
50
|
"crush",
|
|
50
51
|
"cursor",
|
|
51
52
|
"droid",
|
|
@@ -74,7 +75,8 @@
|
|
|
74
75
|
"zencoder",
|
|
75
76
|
"neovate",
|
|
76
77
|
"pochi",
|
|
77
|
-
"adal"
|
|
78
|
+
"adal",
|
|
79
|
+
"universal"
|
|
78
80
|
],
|
|
79
81
|
"repository": {
|
|
80
82
|
"type": "git",
|