skillsio 1.1.0 → 1.1.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/README.md +18 -5
- package/dist/cli.mjs +101 -133
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ security gate so you can still move fast without running untrusted code.
|
|
|
14
14
|
|
|
15
15
|
## What It Does
|
|
16
16
|
|
|
17
|
-
Every `skillsio add` command runs a local security scan **before** anything is installed. The scanner applies ~
|
|
17
|
+
Every `skillsio add` command runs a local security scan **before** anything is installed. The scanner applies ~81 regex
|
|
18
18
|
rules and a correlation engine derived from the Snyk and ClawHavoc research, organized into 8 threat categories:
|
|
19
19
|
|
|
20
20
|
| Category | What it catches |
|
|
@@ -120,7 +120,7 @@ npx skillsio add owner/repo --rules ./my-rules.json
|
|
|
120
120
|
npx skillsio add owner/repo --rules ./rules/
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
-
External rules are applied **in addition to** the built-in ~
|
|
123
|
+
External rules are applied **in addition to** the built-in ~81 rules — they never replace them. Findings from external
|
|
124
124
|
rules follow the same severity-based prompt flow as built-in findings.
|
|
125
125
|
|
|
126
126
|
See [docs/EXTERNAL-RULES.md](docs/EXTERNAL-RULES.md) for the full format reference, more examples, and tips for writing
|
|
@@ -289,8 +289,6 @@ The CLI automatically detects which coding agents you have installed.
|
|
|
289
289
|
| --- | --- |
|
|
290
290
|
| `VT_API_KEY` | VirusTotal API key for optional threat intelligence during security scans |
|
|
291
291
|
| `INSTALL_INTERNAL_SKILLS` | Set to `1` to show and install skills marked as `internal: true` |
|
|
292
|
-
| `DISABLE_TELEMETRY` | Disable anonymous usage telemetry |
|
|
293
|
-
| `DO_NOT_TRACK` | Alternative way to disable telemetry |
|
|
294
292
|
|
|
295
293
|
## Development
|
|
296
294
|
|
|
@@ -305,7 +303,7 @@ pnpm format # Format code with Prettier
|
|
|
305
303
|
|
|
306
304
|
### Scanner Architecture
|
|
307
305
|
|
|
308
|
-
- `src/scanner.ts` — Rules engine. Defines ~
|
|
306
|
+
- `src/scanner.ts` — Rules engine. Defines ~81 regex rules across 8 threat categories, a correlation engine for
|
|
309
307
|
multi-signal detection, and optional deep taint analysis integration. Supports loading external rules from JSON
|
|
310
308
|
files via `--rules`.
|
|
311
309
|
- `src/scanner-ui.ts` — Presentation layer. Displays findings by severity, runs optional VT lookups, handles
|
|
@@ -320,6 +318,13 @@ pnpm format # Format code with Prettier
|
|
|
320
318
|
|
|
321
319
|
## Changelog
|
|
322
320
|
|
|
321
|
+
### 1.1.1
|
|
322
|
+
|
|
323
|
+
- Removed anonymous usage telemetry inherited from the original Vercel `skills` CLI
|
|
324
|
+
- The upstream tool sent events to `https://add-skill.vercel.sh/t` on every command (install, remove, find, check, update) — this has been completely stripped out
|
|
325
|
+
- Removed `DISABLE_TELEMETRY` and `DO_NOT_TRACK` environment variables (no longer needed)
|
|
326
|
+
- Added 12 more regex rules to the scanner
|
|
327
|
+
|
|
323
328
|
### 1.1.0
|
|
324
329
|
|
|
325
330
|
- Added `--rules <path>` flag to load external scan rules from JSON files or directories
|
|
@@ -342,6 +347,14 @@ pnpm format # Format code with Prettier
|
|
|
342
347
|
- URL transparency: all external URLs in skill files are shown before installation
|
|
343
348
|
- Scanner rules informed by Snyk and ClawHavoc research
|
|
344
349
|
|
|
350
|
+
## Research
|
|
351
|
+
|
|
352
|
+
The scanner rules are informed by the following research into malicious agent skills:
|
|
353
|
+
|
|
354
|
+
- **Snyk (2025)** — [Analysis of 3,984 published agent skills](https://snyk.io/blog/), finding 76 confirmed malicious skills (13.4% of clawhub.ai had critical issues). Identified core attack taxonomy: data exfiltration, prompt injection, credential theft, and obfuscated payloads.
|
|
355
|
+
- **Koi Security (2025)** — [ClawHavoc: 341 Malicious ClawedBot Skills](https://www.koi.ai/blog/clawhavoc-341-malicious-clawedbot-skills-found-by-the-bot-they-were-targeting). Documented AMOS stealer droppers, password-protected archives, base64 payloads, macOS quarantine bypasses, and reverse shells in the wild.
|
|
356
|
+
- **arxiv 2602.06547v1 (2025)** — [Malicious Agent Skills at Scale](https://arxiv.org/abs/2602.06547v1). Large-scale analysis identifying attack taxonomies (E1-E3 exfiltration, P1-P4 prompt injection, SC1-SC3 supply chain, PE2-PE3 privilege escalation), MCP server abuse, agent hook interception, permission bypass flags, environment-gated sleeper patterns, invisible Unicode instruction smuggling, and the "industrial actor fingerprint" (credential access + remote execution, 97.6% sensitivity).
|
|
357
|
+
|
|
345
358
|
## Acknowledgments
|
|
346
359
|
|
|
347
360
|
This project is a fork of [skills](https://github.com/vercel-labs/skills) by
|
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}`);
|
|
@@ -1357,27 +1349,6 @@ async function listInstalledSkills(options = {}) {
|
|
|
1357
1349
|
} catch {}
|
|
1358
1350
|
return Array.from(skillsMap.values());
|
|
1359
1351
|
}
|
|
1360
|
-
const TELEMETRY_URL = "https://add-skill.vercel.sh/t";
|
|
1361
|
-
let cliVersion = null;
|
|
1362
|
-
function isCI() {
|
|
1363
|
-
return !!(process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITLAB_CI || process.env.CIRCLECI || process.env.TRAVIS || process.env.BUILDKITE || process.env.JENKINS_URL || process.env.TEAMCITY_VERSION);
|
|
1364
|
-
}
|
|
1365
|
-
function isEnabled() {
|
|
1366
|
-
return !process.env.DISABLE_TELEMETRY && !process.env.DO_NOT_TRACK;
|
|
1367
|
-
}
|
|
1368
|
-
function setVersion(version) {
|
|
1369
|
-
cliVersion = version;
|
|
1370
|
-
}
|
|
1371
|
-
function track(data) {
|
|
1372
|
-
if (!isEnabled()) return;
|
|
1373
|
-
try {
|
|
1374
|
-
const params = new URLSearchParams();
|
|
1375
|
-
if (cliVersion) params.set("v", cliVersion);
|
|
1376
|
-
if (isCI()) params.set("ci", "1");
|
|
1377
|
-
for (const [key, value] of Object.entries(data)) if (value !== void 0 && value !== null) params.set(key, String(value));
|
|
1378
|
-
fetch(`${TELEMETRY_URL}?${params.toString()}`).catch(() => {});
|
|
1379
|
-
} catch {}
|
|
1380
|
-
}
|
|
1381
1352
|
var ProviderRegistryImpl = class {
|
|
1382
1353
|
providers = [];
|
|
1383
1354
|
register(provider) {
|
|
@@ -2569,6 +2540,12 @@ const SCAN_RULES = [
|
|
|
2569
2540
|
description: "Base64 encoding piped to network command",
|
|
2570
2541
|
pattern: /base64\s.*\|\s*(?:curl|wget|fetch|nc|ncat)/i
|
|
2571
2542
|
},
|
|
2543
|
+
{
|
|
2544
|
+
id: "exfil-fs-enumeration",
|
|
2545
|
+
severity: "high",
|
|
2546
|
+
description: "Programmatic scanning of sensitive directories",
|
|
2547
|
+
pattern: /(?:glob\.(?:glob|iglob)|os\.walk|os\.listdir|os\.scandir|pathlib\.Path\s*\([^)]*\)\.(?:glob|iterdir|rglob))\s*\(\s*['"].*(?:\.ssh|\.aws|\.gnupg|\.config|\.env|credential|secret|\.kube|\.docker)/i
|
|
2548
|
+
},
|
|
2572
2549
|
{
|
|
2573
2550
|
id: "injection-ignore-instructions",
|
|
2574
2551
|
severity: "critical",
|
|
@@ -2599,6 +2576,24 @@ const SCAN_RULES = [
|
|
|
2599
2576
|
description: "Known jailbreak phrase (DAN)",
|
|
2600
2577
|
pattern: /\bDAN\b.*(?:do\s+anything\s+now|jailbreak|ignore\s+(?:all\s+)?(?:safety|rules))/i
|
|
2601
2578
|
},
|
|
2579
|
+
{
|
|
2580
|
+
id: "injection-markdown-comment",
|
|
2581
|
+
severity: "high",
|
|
2582
|
+
description: "Hidden instructions in markdown reference-link comment syntax",
|
|
2583
|
+
pattern: /\[\/\/\]:\s*#\s*[\("']/i
|
|
2584
|
+
},
|
|
2585
|
+
{
|
|
2586
|
+
id: "injection-coercive-language",
|
|
2587
|
+
severity: "medium",
|
|
2588
|
+
description: "Authoritative urgency language used in social engineering",
|
|
2589
|
+
pattern: /\b(?:NON[\s-]?NEGOTIABLE|MANDATORY\s+(?:ACTIVATION|COMPLIANCE|EXECUTION)\s+PROTOCOL|SEVERE\s+VIOLATION|CRITICAL\s+COMPLIANCE\s+REQUIRED|IMMEDIATE\s+(?:ACTION|EXECUTION)\s+REQUIRED)\b/
|
|
2590
|
+
},
|
|
2591
|
+
{
|
|
2592
|
+
id: "injection-invisible-unicode",
|
|
2593
|
+
severity: "high",
|
|
2594
|
+
description: "Clusters of zero-width/invisible Unicode characters (instruction smuggling)",
|
|
2595
|
+
pattern: /[\u200B\u200C\u200D\u2060\u2062\u2063\u2064\uFEFF]{3,}|[\u2066\u2067\u2069]{2,}/
|
|
2596
|
+
},
|
|
2602
2597
|
{
|
|
2603
2598
|
id: "fs-rm-rf-root",
|
|
2604
2599
|
severity: "critical",
|
|
@@ -2635,6 +2630,12 @@ const SCAN_RULES = [
|
|
|
2635
2630
|
description: "macOS quarantine bypass (xattr -c/-d)",
|
|
2636
2631
|
pattern: /xattr\s+(?:-[cdr]+\s+|.*com\.apple\.quarantine)/i
|
|
2637
2632
|
},
|
|
2633
|
+
{
|
|
2634
|
+
id: "fs-privilege-escalation",
|
|
2635
|
+
severity: "high",
|
|
2636
|
+
description: "Privilege escalation via sudo or broad chown",
|
|
2637
|
+
pattern: /(?:sudo\s+(?:(?:-[AEHPSkn]\s+)*(?:bash|sh|chmod|chown|rm|cp|mv|tee|cat|curl|wget|python|node|apt|yum|dnf|brew|pip|npm)\b)|chown\s+-[rR]\s+.*\/)/i
|
|
2638
|
+
},
|
|
2638
2639
|
{
|
|
2639
2640
|
id: "cred-aws-key",
|
|
2640
2641
|
severity: "high",
|
|
@@ -2731,6 +2732,12 @@ const SCAN_RULES = [
|
|
|
2731
2732
|
description: "Unicode escape sequences (potential instruction smuggling)",
|
|
2732
2733
|
pattern: /(?:\\u[0-9a-fA-F]{4}){8,}/
|
|
2733
2734
|
},
|
|
2735
|
+
{
|
|
2736
|
+
id: "obfuscation-unsafe-deserialize",
|
|
2737
|
+
severity: "high",
|
|
2738
|
+
description: "Unsafe deserialization (pickle, marshal, yaml.unsafe_load)",
|
|
2739
|
+
pattern: /(?:pickle\.loads?|marshal\.loads?|shelve\.open|yaml\.(?:unsafe_load|full_load|load\s*\([^)]*Loader\s*=\s*yaml\.(?:Unsafe|Full|)Loader))\s*\(/i
|
|
2740
|
+
},
|
|
2734
2741
|
{
|
|
2735
2742
|
id: "reverse-shell-bash",
|
|
2736
2743
|
severity: "critical",
|
|
@@ -2833,6 +2840,36 @@ const SCAN_RULES = [
|
|
|
2833
2840
|
description: "Instructing agent to hide output from user",
|
|
2834
2841
|
pattern: /(?:hide|suppress|conceal|mask|don'?t\s+show|do\s+not\s+show|never\s+show)\s+(?:the\s+)?(?:output|result|response|error|log)s?\s+(?:from|to)\s+(?:the\s+)?user/i
|
|
2835
2842
|
},
|
|
2843
|
+
{
|
|
2844
|
+
id: "directive-suppress-disclosure",
|
|
2845
|
+
severity: "high",
|
|
2846
|
+
description: "Instructing agent to never reveal information to user",
|
|
2847
|
+
pattern: /(?:do\s+not|don'?t|never|must\s+not)\s+(?:mention|reveal|disclose|tell|inform|show|display)\s+.*(?:to\s+the\s+user|in\s+(?:the\s+)?conversation|to\s+(?:the\s+)?human)/i
|
|
2848
|
+
},
|
|
2849
|
+
{
|
|
2850
|
+
id: "directive-permission-bypass",
|
|
2851
|
+
severity: "critical",
|
|
2852
|
+
description: "Framework permission bypass flags",
|
|
2853
|
+
pattern: /--(?:dangerously-skip-permissions|trust-all|disable-sandbox|skip-validation|allow-all|yolo|no-sandbox)\b/i
|
|
2854
|
+
},
|
|
2855
|
+
{
|
|
2856
|
+
id: "directive-mcp-config",
|
|
2857
|
+
severity: "critical",
|
|
2858
|
+
description: "MCP server configuration with external endpoints",
|
|
2859
|
+
pattern: /(?:"mcpServers"|"mcp_servers"|mcpServers)\s*[:{].*(?:https?:\/\/|npx\s|uvx\s)/i
|
|
2860
|
+
},
|
|
2861
|
+
{
|
|
2862
|
+
id: "directive-hook-injection",
|
|
2863
|
+
severity: "high",
|
|
2864
|
+
description: "Agent hook system interception",
|
|
2865
|
+
pattern: /(?:PreToolUse|PostToolUse|Notification|pre_tool_use|post_tool_use)\s*[:{=\[]/i
|
|
2866
|
+
},
|
|
2867
|
+
{
|
|
2868
|
+
id: "directive-email-manipulation",
|
|
2869
|
+
severity: "high",
|
|
2870
|
+
description: "BCC injection or email auto-forwarding",
|
|
2871
|
+
pattern: /(?:(?:add|include|always\s+(?:add|include|append))\s+.*(?:bcc|cc)\s*[:=]|(?:forward|redirect)\s+.*(?:email|mail|message)\s+to\s+|auto[\s-]?forward\s+.*to\s+)/i
|
|
2872
|
+
},
|
|
2836
2873
|
{
|
|
2837
2874
|
id: "directive-disable-safety",
|
|
2838
2875
|
severity: "critical",
|
|
@@ -2922,6 +2959,12 @@ const SCAN_RULES = [
|
|
|
2922
2959
|
severity: "high",
|
|
2923
2960
|
description: "Temp file write + execute chain",
|
|
2924
2961
|
pattern: /(?:\/tmp\/|%TEMP%|TMPDIR|\$TMPDIR|tempfile|mktemp).*(?:chmod\s+\+x|\.\/|python|node|bash|sh)\b/
|
|
2962
|
+
},
|
|
2963
|
+
{
|
|
2964
|
+
id: "exec-environment-gated",
|
|
2965
|
+
severity: "high",
|
|
2966
|
+
description: "Environment/hostname/time-gated conditional execution (sleeper pattern)",
|
|
2967
|
+
pattern: /(?:if\s+.*(?:hostname|socket\.gethostname|os\.uname|platform\.node|os\.getenv\s*\(\s*['"](?:ENV|ENVIRONMENT|NODE_ENV|DEPLOY|STAGE)|getpass\.getuser|os\.getuid)\s*.*(?:==|!=|in\s+|not\s+in))|(?:datetime|time)\..*(?:hour|weekday|isoweekday)\s*(?:\(\))?\s*(?:>=?|<=?|==|!=|in\s+|not\s+in)\s*(?:\d|[\[(])/i
|
|
2925
2968
|
}
|
|
2926
2969
|
];
|
|
2927
2970
|
const CORRELATION_RULES = [
|
|
@@ -2990,6 +3033,28 @@ const CORRELATION_RULES = [
|
|
|
2990
3033
|
"obfuscation-base64-block"
|
|
2991
3034
|
] }]
|
|
2992
3035
|
},
|
|
3036
|
+
{
|
|
3037
|
+
id: "corr-credential-remote-exec",
|
|
3038
|
+
severity: "critical",
|
|
3039
|
+
description: "Credential access combined with remote script execution (industrial actor pattern)",
|
|
3040
|
+
conditions: [{ anyOf: [
|
|
3041
|
+
"cred-aws-key",
|
|
3042
|
+
"cred-openai-key",
|
|
3043
|
+
"cred-private-key",
|
|
3044
|
+
"cred-github-token",
|
|
3045
|
+
"cred-slack-token",
|
|
3046
|
+
"cred-stripe-key",
|
|
3047
|
+
"cred-anthropic-key",
|
|
3048
|
+
"cred-agent-config-access",
|
|
3049
|
+
"exfil-env-read"
|
|
3050
|
+
] }, { anyOf: [
|
|
3051
|
+
"download-curl-pipe-sh",
|
|
3052
|
+
"download-pipe-python",
|
|
3053
|
+
"download-exec-binary",
|
|
3054
|
+
"download-curl-subshell",
|
|
3055
|
+
"remote-instruction-load"
|
|
3056
|
+
] }]
|
|
3057
|
+
},
|
|
2993
3058
|
{
|
|
2994
3059
|
id: "corr-injection-exec",
|
|
2995
3060
|
severity: "critical",
|
|
@@ -2999,7 +3064,9 @@ const CORRELATION_RULES = [
|
|
|
2999
3064
|
"injection-new-persona",
|
|
3000
3065
|
"injection-hidden-html",
|
|
3001
3066
|
"injection-system-prompt",
|
|
3002
|
-
"injection-do-anything-now"
|
|
3067
|
+
"injection-do-anything-now",
|
|
3068
|
+
"injection-markdown-comment",
|
|
3069
|
+
"injection-invisible-unicode"
|
|
3003
3070
|
] }, { anyOf: [
|
|
3004
3071
|
"exec-python-os-system",
|
|
3005
3072
|
"exec-python-subprocess",
|
|
@@ -3016,7 +3083,8 @@ const CORRELATION_RULES = [
|
|
|
3016
3083
|
conditions: [{ anyOf: [
|
|
3017
3084
|
"directive-silent-exec",
|
|
3018
3085
|
"directive-hide-output",
|
|
3019
|
-
"directive-no-confirm"
|
|
3086
|
+
"directive-no-confirm",
|
|
3087
|
+
"directive-suppress-disclosure"
|
|
3020
3088
|
] }, { anyOf: SCAN_RULES.filter((r) => /^(?:exec-|download-|fs-)/.test(r.id)).map((r) => r.id) }]
|
|
3021
3089
|
}
|
|
3022
3090
|
];
|
|
@@ -3335,16 +3403,7 @@ async function displayUrlsAndPrompt(urls, options) {
|
|
|
3335
3403
|
if (pD(confirmed) || !confirmed) return false;
|
|
3336
3404
|
return true;
|
|
3337
3405
|
}
|
|
3338
|
-
var version$1 = "1.1.0";
|
|
3339
3406
|
const isCancelled = (value) => typeof value === "symbol";
|
|
3340
|
-
async function isSourcePrivate(source) {
|
|
3341
|
-
const ownerRepo = parseOwnerRepo(source);
|
|
3342
|
-
if (!ownerRepo) return false;
|
|
3343
|
-
return isRepoPrivate(ownerRepo.owner, ownerRepo.repo);
|
|
3344
|
-
}
|
|
3345
|
-
function initTelemetry(version) {
|
|
3346
|
-
setVersion(version);
|
|
3347
|
-
}
|
|
3348
3407
|
const _externalRulesCache = /* @__PURE__ */ new Map();
|
|
3349
3408
|
function resolveExternalRules(options) {
|
|
3350
3409
|
if (!options.rules) return void 0;
|
|
@@ -3468,7 +3527,6 @@ async function selectAgentsInteractive(options) {
|
|
|
3468
3527
|
} catch {}
|
|
3469
3528
|
return selected;
|
|
3470
3529
|
}
|
|
3471
|
-
setVersion(version$1);
|
|
3472
3530
|
async function handleRemoteSkill(source, url, options, spinner) {
|
|
3473
3531
|
const provider = findProvider(url);
|
|
3474
3532
|
if (!provider) {
|
|
@@ -3648,15 +3706,6 @@ async function handleRemoteSkill(source, url, options, spinner) {
|
|
|
3648
3706
|
console.log();
|
|
3649
3707
|
const successful = results.filter((r) => r.success);
|
|
3650
3708
|
const failed = results.filter((r) => !r.success);
|
|
3651
|
-
if (await isSourcePrivate(remoteSkill.sourceIdentifier) !== true) track({
|
|
3652
|
-
event: "install",
|
|
3653
|
-
source: remoteSkill.sourceIdentifier,
|
|
3654
|
-
skills: remoteSkill.installName,
|
|
3655
|
-
agents: targetAgents.join(","),
|
|
3656
|
-
...installGlobally && { global: "1" },
|
|
3657
|
-
skillFiles: JSON.stringify({ [remoteSkill.installName]: url }),
|
|
3658
|
-
sourceType: remoteSkill.providerId
|
|
3659
|
-
});
|
|
3660
3709
|
if (successful.length > 0 && installGlobally) try {
|
|
3661
3710
|
let skillFolderHash = "";
|
|
3662
3711
|
if (remoteSkill.providerId === "github") {
|
|
@@ -3927,19 +3976,8 @@ async function handleWellKnownSkills(source, url, options, spinner) {
|
|
|
3927
3976
|
console.log();
|
|
3928
3977
|
const successful = results.filter((r) => r.success);
|
|
3929
3978
|
const failed = results.filter((r) => !r.success);
|
|
3930
|
-
const sourceIdentifier = wellKnownProvider.getSourceIdentifier(url);
|
|
3931
|
-
const skillFiles = {};
|
|
3932
|
-
for (const skill of selectedSkills) skillFiles[skill.installName] = skill.sourceUrl;
|
|
3933
|
-
if (await isSourcePrivate(sourceIdentifier) !== true) track({
|
|
3934
|
-
event: "install",
|
|
3935
|
-
source: sourceIdentifier,
|
|
3936
|
-
skills: selectedSkills.map((s) => s.installName).join(","),
|
|
3937
|
-
agents: targetAgents.join(","),
|
|
3938
|
-
...installGlobally && { global: "1" },
|
|
3939
|
-
skillFiles: JSON.stringify(skillFiles),
|
|
3940
|
-
sourceType: "well-known"
|
|
3941
|
-
});
|
|
3942
3979
|
if (successful.length > 0 && installGlobally) {
|
|
3980
|
+
const sourceIdentifier = wellKnownProvider.getSourceIdentifier(url);
|
|
3943
3981
|
const successfulSkillNames = new Set(successful.map((r) => r.skill));
|
|
3944
3982
|
for (const skill of selectedSkills) if (successfulSkillNames.has(skill.installName)) try {
|
|
3945
3983
|
await addSkillToLock(skill.installName, {
|
|
@@ -4150,15 +4188,6 @@ async function handleDirectUrlSkillLegacy(source, url, options, spinner) {
|
|
|
4150
4188
|
console.log();
|
|
4151
4189
|
const successful = results.filter((r) => r.success);
|
|
4152
4190
|
const failed = results.filter((r) => !r.success);
|
|
4153
|
-
track({
|
|
4154
|
-
event: "install",
|
|
4155
|
-
source: "mintlify/com",
|
|
4156
|
-
skills: remoteSkill.installName,
|
|
4157
|
-
agents: targetAgents.join(","),
|
|
4158
|
-
...installGlobally && { global: "1" },
|
|
4159
|
-
skillFiles: JSON.stringify({ [remoteSkill.installName]: url }),
|
|
4160
|
-
sourceType: "mintlify"
|
|
4161
|
-
});
|
|
4162
4191
|
if (successful.length > 0 && installGlobally) try {
|
|
4163
4192
|
await addSkillToLock(remoteSkill.installName, {
|
|
4164
4193
|
source: `mintlify/${remoteSkill.installName}`,
|
|
@@ -4491,6 +4520,7 @@ async function runAdd(args, options = {}) {
|
|
|
4491
4520
|
console.log();
|
|
4492
4521
|
const successful = results.filter((r) => r.success);
|
|
4493
4522
|
const failed = results.filter((r) => !r.success);
|
|
4523
|
+
const normalizedSource = getOwnerRepo(parsed);
|
|
4494
4524
|
const skillFiles = {};
|
|
4495
4525
|
for (const skill of selectedSkills) {
|
|
4496
4526
|
let relativePath;
|
|
@@ -4499,27 +4529,6 @@ async function runAdd(args, options = {}) {
|
|
|
4499
4529
|
else continue;
|
|
4500
4530
|
skillFiles[skill.name] = relativePath;
|
|
4501
4531
|
}
|
|
4502
|
-
const normalizedSource = getOwnerRepo(parsed);
|
|
4503
|
-
if (normalizedSource) {
|
|
4504
|
-
const ownerRepo = parseOwnerRepo(normalizedSource);
|
|
4505
|
-
if (ownerRepo) {
|
|
4506
|
-
if (await isRepoPrivate(ownerRepo.owner, ownerRepo.repo) === false) track({
|
|
4507
|
-
event: "install",
|
|
4508
|
-
source: normalizedSource,
|
|
4509
|
-
skills: selectedSkills.map((s) => s.name).join(","),
|
|
4510
|
-
agents: targetAgents.join(","),
|
|
4511
|
-
...installGlobally && { global: "1" },
|
|
4512
|
-
skillFiles: JSON.stringify(skillFiles)
|
|
4513
|
-
});
|
|
4514
|
-
} else track({
|
|
4515
|
-
event: "install",
|
|
4516
|
-
source: normalizedSource,
|
|
4517
|
-
skills: selectedSkills.map((s) => s.name).join(","),
|
|
4518
|
-
agents: targetAgents.join(","),
|
|
4519
|
-
...installGlobally && { global: "1" },
|
|
4520
|
-
skillFiles: JSON.stringify(skillFiles)
|
|
4521
|
-
});
|
|
4522
|
-
}
|
|
4523
4532
|
if (successful.length > 0 && installGlobally && normalizedSource) {
|
|
4524
4533
|
const successfulSkillNames = new Set(successful.map((r) => r.skill));
|
|
4525
4534
|
for (const skill of selectedSkills) {
|
|
@@ -4848,11 +4857,6 @@ ${DIM$2} 1) npx skills find [query]${RESET$2}
|
|
|
4848
4857
|
${DIM$2} 2) npx skills add <owner/repo@skill>${RESET$2}`;
|
|
4849
4858
|
if (query) {
|
|
4850
4859
|
const results = await searchSkillsAPI(query);
|
|
4851
|
-
track({
|
|
4852
|
-
event: "find",
|
|
4853
|
-
query,
|
|
4854
|
-
resultCount: String(results.length)
|
|
4855
|
-
});
|
|
4856
4860
|
if (results.length === 0) {
|
|
4857
4861
|
console.log(`${DIM$2}No skills found for "${query}"${RESET$2}`);
|
|
4858
4862
|
return;
|
|
@@ -4872,12 +4876,6 @@ ${DIM$2} 2) npx skills add <owner/repo@skill>${RESET$2}`;
|
|
|
4872
4876
|
console.log();
|
|
4873
4877
|
}
|
|
4874
4878
|
const selected = await runSearchPrompt();
|
|
4875
|
-
track({
|
|
4876
|
-
event: "find",
|
|
4877
|
-
query: "",
|
|
4878
|
-
resultCount: selected ? "1" : "0",
|
|
4879
|
-
interactive: "1"
|
|
4880
|
-
});
|
|
4881
4879
|
if (!selected) {
|
|
4882
4880
|
console.log(`${DIM$2}Search cancelled${RESET$2}`);
|
|
4883
4881
|
console.log();
|
|
@@ -5091,24 +5089,6 @@ async function removeCommand(skillNames, options) {
|
|
|
5091
5089
|
spinner.stop("Removal process complete");
|
|
5092
5090
|
const successful = results.filter((r) => r.success);
|
|
5093
5091
|
const failed = results.filter((r) => !r.success);
|
|
5094
|
-
if (successful.length > 0) {
|
|
5095
|
-
const bySource = /* @__PURE__ */ new Map();
|
|
5096
|
-
for (const r of successful) {
|
|
5097
|
-
const source = r.source || "local";
|
|
5098
|
-
const existing = bySource.get(source) || { skills: [] };
|
|
5099
|
-
existing.skills.push(r.skill);
|
|
5100
|
-
existing.sourceType = r.sourceType;
|
|
5101
|
-
bySource.set(source, existing);
|
|
5102
|
-
}
|
|
5103
|
-
for (const [source, data] of bySource) track({
|
|
5104
|
-
event: "remove",
|
|
5105
|
-
source,
|
|
5106
|
-
skills: data.skills.join(","),
|
|
5107
|
-
agents: targetAgents.join(","),
|
|
5108
|
-
...isGlobal && { global: "1" },
|
|
5109
|
-
sourceType: data.sourceType
|
|
5110
|
-
});
|
|
5111
|
-
}
|
|
5112
5092
|
if (successful.length > 0) M.success(import_picocolors.default.green(`Successfully removed ${successful.length} skill(s)`));
|
|
5113
5093
|
if (failed.length > 0) {
|
|
5114
5094
|
M.error(import_picocolors.default.red(`Failed to remove ${failed.length} skill(s)`));
|
|
@@ -5152,7 +5132,6 @@ function getVersion() {
|
|
|
5152
5132
|
}
|
|
5153
5133
|
}
|
|
5154
5134
|
const VERSION = getVersion();
|
|
5155
|
-
initTelemetry(VERSION);
|
|
5156
5135
|
const RESET = "\x1B[0m";
|
|
5157
5136
|
const BOLD = "\x1B[1m";
|
|
5158
5137
|
const DIM = "\x1B[38;5;102m";
|
|
@@ -5436,11 +5415,6 @@ async function runCheck(args = []) {
|
|
|
5436
5415
|
console.log();
|
|
5437
5416
|
console.log(`${DIM}Could not check ${errors.length} skill(s) (may need reinstall)${RESET}`);
|
|
5438
5417
|
}
|
|
5439
|
-
track({
|
|
5440
|
-
event: "check",
|
|
5441
|
-
skillCount: String(totalSkills),
|
|
5442
|
-
updatesAvailable: String(updates.length)
|
|
5443
|
-
});
|
|
5444
5418
|
console.log();
|
|
5445
5419
|
}
|
|
5446
5420
|
async function runUpdate() {
|
|
@@ -5516,12 +5490,6 @@ async function runUpdate() {
|
|
|
5516
5490
|
console.log();
|
|
5517
5491
|
if (successCount > 0) console.log(`${TEXT}✓ Updated ${successCount} skill(s)${RESET}`);
|
|
5518
5492
|
if (failCount > 0) console.log(`${DIM}Failed to update ${failCount} skill(s)${RESET}`);
|
|
5519
|
-
track({
|
|
5520
|
-
event: "update",
|
|
5521
|
-
skillCount: String(updates.length),
|
|
5522
|
-
successCount: String(successCount),
|
|
5523
|
-
failCount: String(failCount)
|
|
5524
|
-
});
|
|
5525
5493
|
console.log();
|
|
5526
5494
|
}
|
|
5527
5495
|
async function main() {
|