@agentskillshub/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +59 -0
- package/SKILL.md +57 -0
- package/bin/ash.mjs +331 -0
- package/package.json +36 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AgentSkillsHub
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# `ash` β AgentSkillsHub CLI
|
|
2
|
+
|
|
3
|
+
Search, audit, and install open-source AI agent skills & MCP servers from your terminal. Every result is **security-graded** and **quality-scored** by [AgentSkillsHub](https://agentskillshub.top).
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx @agentskillshub/cli search "scrape a website" --safe
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Why
|
|
10
|
+
|
|
11
|
+
Discovering a skill is easy. Knowing whether it's safe to run against your credentials is not. `ash` puts the trust signal *before* the install:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
$ ash search postgres --category mcp-server --limit 2
|
|
15
|
+
|
|
16
|
+
call518/MCP-PostgreSQL-Ops 150β
π’ SAFE
|
|
17
|
+
Give AI assistants full PostgreSQL DBA superpowers β 30+ toolsβ¦
|
|
18
|
+
mcp-server Β· claude-code Β· mcp Β· python https://agentskillshub.top/skill/call518/MCP-PostgreSQL-Ops/
|
|
19
|
+
|
|
20
|
+
sgaunet/postgresql-mcp 5β
βͺ UNAUDITED ~23.0k tok
|
|
21
|
+
A Model Context Protocol (MCP) serverβ¦ read-only query executionβ¦
|
|
22
|
+
mcp-server Β· claude-code Β· go Β· mcp https://agentskillshub.top/skill/sgaunet/postgresql-mcp/
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Commands
|
|
26
|
+
|
|
27
|
+
| | |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `ash search <query> [filters]` | Find skills. Filters: `--category` `--platform` `--min-stars` `--safe` `--limit` |
|
|
30
|
+
| `ash audit <owner/repo>` | Free basic trust check: security grade + plain-English verdict |
|
|
31
|
+
| `ash install <owner/repo>` | Install commands + "check before you install" safety line |
|
|
32
|
+
| `ash update` | Force-refresh the cached index |
|
|
33
|
+
|
|
34
|
+
Add `--json` to any of `search` / `audit` / `install` for machine-readable output.
|
|
35
|
+
|
|
36
|
+
## How it works
|
|
37
|
+
|
|
38
|
+
The catalog (~20K quality skills, stars β₯ 5) is a single static file (~1.7MB gzipped) served from the CDN. `ash` downloads it once, caches it at `~/.cache/agentskillshub/`, and refreshes every ~8h. **All searching is local** β fast, works offline, and puts zero load on the backend.
|
|
39
|
+
|
|
40
|
+
## Security grades
|
|
41
|
+
|
|
42
|
+
π’ SAFE Β· π‘ CAUTION Β· π΄ UNSAFE Β· β REJECT Β· βͺ UNAUDITED
|
|
43
|
+
|
|
44
|
+
βͺ **UNAUDITED** is not "probably fine" β it means *no one has audited it*. Check the code, the credentials it asks for, and who maintains it before you trust it.
|
|
45
|
+
|
|
46
|
+
## Free vs. Pro
|
|
47
|
+
|
|
48
|
+
- **Free**: search Β· basic audit Β· install commands, for any catalogued skill.
|
|
49
|
+
- **Pro / Enterprise**: 5-dimension deep audit, any GitHub URL (incl. <5β
/ private), CI/batch auditing, compliance evidence β <https://agentskillshub.top/enterprise/>
|
|
50
|
+
|
|
51
|
+
## Env
|
|
52
|
+
|
|
53
|
+
| Var | Default |
|
|
54
|
+
|---|---|
|
|
55
|
+
| `AGENTSKILLSHUB_BASE` | `https://agentskillshub.top` |
|
|
56
|
+
| `AGENTSKILLSHUB_CACHE` | `~/.cache/agentskillshub` |
|
|
57
|
+
| `NO_COLOR` | unset (set to disable color) |
|
|
58
|
+
|
|
59
|
+
MIT Β© AgentSkillsHub
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agentskillshub
|
|
3
|
+
description: Use when the user wants to find, evaluate, audit, or install an open-source AI agent skill or MCP server β e.g. "find an MCP server for Postgres", "is this skill safe to install", "what should I use to scrape a website". Searches a quality-scored, security-graded catalog of ~20K skills locally (cached index, zero backend load) and returns each result's security grade, quality score, and install commands so you can check trust BEFORE installing.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# AgentSkillsHub
|
|
7
|
+
|
|
8
|
+
Discover β audit β install open-source AI agent skills and MCP servers without leaving the terminal. Backed by [AgentSkillsHub](https://agentskillshub.top): ~106K indexed skills, of which ~20K (stars β₯ 5) are in the searchable catalog, each carrying a **quality score** and a **security grade**.
|
|
9
|
+
|
|
10
|
+
The catalog is a static index downloaded once and cached locally (`~/.cache/agentskillshub/`, refreshed every ~8h). Every search after the first is **instant, offline, and puts zero load on the Hub backend**.
|
|
11
|
+
|
|
12
|
+
## When to use
|
|
13
|
+
|
|
14
|
+
- The user is looking for a skill / MCP server for a task ("find an MCP server for X", "what can scrape a website").
|
|
15
|
+
- The user wants to know if a skill is safe / trustworthy before installing it.
|
|
16
|
+
- The user wants the install command for a specific skill.
|
|
17
|
+
|
|
18
|
+
## How to use
|
|
19
|
+
|
|
20
|
+
The CLI is `bin/ash.mjs` (Node β₯ 18, zero dependencies). Run it with `node`:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Search (local fuzzy ranking over name/desc/tags; quality + popularity tiebreak)
|
|
24
|
+
node bin/ash.mjs search "scrape a website" --safe --limit 5
|
|
25
|
+
node bin/ash.mjs search postgres --category mcp-server
|
|
26
|
+
# filters: --category <c> --platform <p> --min-stars <n> --safe --limit <n>
|
|
27
|
+
|
|
28
|
+
# Audit β free basic trust check (security grade + plain-English verdict)
|
|
29
|
+
node bin/ash.mjs audit owner/repo
|
|
30
|
+
|
|
31
|
+
# Install β install commands + a "check before you install" safety line
|
|
32
|
+
node bin/ash.mjs install owner/repo
|
|
33
|
+
|
|
34
|
+
# Force-refresh the cached index
|
|
35
|
+
node bin/ash.mjs update
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Add `--json` to `search`, `audit`, or `install` for structured output to parse programmatically.
|
|
39
|
+
|
|
40
|
+
## Reading the output
|
|
41
|
+
|
|
42
|
+
- **Security grade**: π’ SAFE Β· π‘ CAUTION Β· π΄ UNSAFE Β· β REJECT Β· βͺ UNAUDITED.
|
|
43
|
+
βͺ UNAUDITED means *no one has ever audited it* β treat it as a black box, not as "probably fine". 97%+ of the wider catalog is unaudited; surface this honestly to the user.
|
|
44
|
+
- **Quality score** (0-100): documentation, maintenance, examples, structure β not stars.
|
|
45
|
+
- **`~Nk tok`**: rough context cost when loaded (omitted when the upstream estimate is implausible).
|
|
46
|
+
- **β official**: published by a verified official org account.
|
|
47
|
+
|
|
48
|
+
## What's free vs. paid
|
|
49
|
+
|
|
50
|
+
- **Free**: search, basic audit (grade + flags + verdict), install commands β for any skill in the catalog.
|
|
51
|
+
- **Pro / Enterprise**: 5-dimension deep audit (code Β· credentials Β· vendor Β· supply-chain Β· operational), auditing *any* GitHub URL (including < 5β
or private repos), CI/batch auditing, and compliance evidence. Point the user to <https://agentskillshub.top/enterprise/>.
|
|
52
|
+
|
|
53
|
+
## Guidance for the agent
|
|
54
|
+
|
|
55
|
+
- Default to `--safe` only when the user explicitly cares about production/brand safety; otherwise show the full ranked list so they see unaudited options too (and warn about them).
|
|
56
|
+
- Before suggesting the user install anything, run `audit` and relay the grade honestly. Never imply an UNAUDITED skill is safe.
|
|
57
|
+
- This skill **never runs install commands itself** β it returns the commands for the user/agent to run.
|
package/bin/ash.mjs
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ash β AgentSkillsHub CLI
|
|
4
|
+
*
|
|
5
|
+
* Discover, audit, and install open-source AI agent skills / MCP servers
|
|
6
|
+
* without leaving the terminal. Searches a quality-filtered catalog of ~20K
|
|
7
|
+
* skills (stars >= 5) that is downloaded once and cached locally, so every
|
|
8
|
+
* search after the first is instant and offline β and puts ZERO load on the
|
|
9
|
+
* Hub's backend (the index is a static file on the CDN).
|
|
10
|
+
*
|
|
11
|
+
* Commands:
|
|
12
|
+
* ash search <query> [filters] find skills (local fuzzy ranking)
|
|
13
|
+
* ash audit <owner/repo> free basic trust check (security grade + flags)
|
|
14
|
+
* ash install <owner/repo> install commands + "check before you install"
|
|
15
|
+
* ash update force-refresh the cached index
|
|
16
|
+
* ash --help
|
|
17
|
+
*
|
|
18
|
+
* Zero dependencies β Node >= 18 built-ins only (fetch, zlib, fs).
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { gunzipSync } from "node:zlib";
|
|
22
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, statSync } from "node:fs";
|
|
23
|
+
import { homedir } from "node:os";
|
|
24
|
+
import { join } from "node:path";
|
|
25
|
+
|
|
26
|
+
const BASE = process.env.AGENTSKILLSHUB_BASE || "https://agentskillshub.top";
|
|
27
|
+
const META_URL = `${BASE}/search-index-meta.json`;
|
|
28
|
+
const INDEX_URL = `${BASE}/search-index.json.gz`;
|
|
29
|
+
const HUB_SKILL = (full) => `${BASE}/skill/${full}/`;
|
|
30
|
+
|
|
31
|
+
const CACHE_DIR = join(process.env.AGENTSKILLSHUB_CACHE || join(homedir(), ".cache", "agentskillshub"));
|
|
32
|
+
const CACHE_INDEX = join(CACHE_DIR, "search-index.json");
|
|
33
|
+
const CACHE_META = join(CACHE_DIR, "search-index-meta.json");
|
|
34
|
+
const TTL_MS = 8 * 60 * 60 * 1000; // refresh at most every 8h (matches sync cadence)
|
|
35
|
+
|
|
36
|
+
// security_grade β display
|
|
37
|
+
const GRADE = {
|
|
38
|
+
safe: { label: "SAFE", mark: "π’" },
|
|
39
|
+
caution: { label: "CAUTION", mark: "π‘" },
|
|
40
|
+
unsafe: { label: "UNSAFE", mark: "π΄" },
|
|
41
|
+
reject: { label: "REJECT", mark: "β" },
|
|
42
|
+
unknown: { label: "UNAUDITED", mark: "βͺ" },
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// βββ tiny ANSI (auto-off when not a TTY / NO_COLOR) ββββββββββββββββββββββββββ
|
|
46
|
+
const tty = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
47
|
+
const c = (code, s) => (tty ? `\x1b[${code}m${s}\x1b[0m` : s);
|
|
48
|
+
const bold = (s) => c("1", s);
|
|
49
|
+
const dim = (s) => c("2", s);
|
|
50
|
+
const cyan = (s) => c("36", s);
|
|
51
|
+
const green = (s) => c("32", s);
|
|
52
|
+
const yellow = (s) => c("33", s);
|
|
53
|
+
|
|
54
|
+
// βββ cache / index loading βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
55
|
+
async function fetchJson(url) {
|
|
56
|
+
const res = await fetch(url, { headers: { "user-agent": "ash-cli" } });
|
|
57
|
+
if (!res.ok) throw new Error(`HTTP ${res.status} fetching ${url}`);
|
|
58
|
+
return res.json();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function readCachedIndex() {
|
|
62
|
+
if (!existsSync(CACHE_INDEX)) return null;
|
|
63
|
+
try {
|
|
64
|
+
return JSON.parse(readFileSync(CACHE_INDEX, "utf8"));
|
|
65
|
+
} catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function cacheFresh() {
|
|
71
|
+
if (!existsSync(CACHE_INDEX)) return false;
|
|
72
|
+
return Date.now() - statSync(CACHE_INDEX).mtimeMs < TTL_MS;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function downloadIndex() {
|
|
76
|
+
process.stderr.write(dim("β¬ downloading skill indexβ¦\n"));
|
|
77
|
+
const res = await fetch(INDEX_URL, { headers: { "user-agent": "ash-cli" } });
|
|
78
|
+
if (!res.ok) throw new Error(`HTTP ${res.status} fetching index`);
|
|
79
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
80
|
+
const json = gunzipSync(buf).toString("utf8");
|
|
81
|
+
mkdirSync(CACHE_DIR, { recursive: true });
|
|
82
|
+
writeFileSync(CACHE_INDEX, json);
|
|
83
|
+
const parsed = JSON.parse(json);
|
|
84
|
+
writeFileSync(CACHE_META, JSON.stringify({ v: parsed.v, generated_at: parsed.generated_at, count: parsed.count, min_stars: parsed.min_stars }));
|
|
85
|
+
process.stderr.write(dim(`β cached ${parsed.count} skills (${(buf.length / 1024 / 1024).toFixed(1)}MB)\n`));
|
|
86
|
+
return parsed;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Load index, refreshing if stale or if the CDN has a newer generation. */
|
|
90
|
+
async function loadIndex({ force = false } = {}) {
|
|
91
|
+
if (!force && cacheFresh()) {
|
|
92
|
+
const cached = readCachedIndex();
|
|
93
|
+
if (cached) return cached;
|
|
94
|
+
}
|
|
95
|
+
// Cheap freshness probe (77B) before pulling the full 1.7MB index.
|
|
96
|
+
if (!force && existsSync(CACHE_META) && existsSync(CACHE_INDEX)) {
|
|
97
|
+
try {
|
|
98
|
+
const [remote, local] = [await fetchJson(META_URL), JSON.parse(readFileSync(CACHE_META, "utf8"))];
|
|
99
|
+
if (remote.generated_at === local.generated_at) {
|
|
100
|
+
writeFileSync(CACHE_INDEX, readFileSync(CACHE_INDEX)); // bump mtime β reset TTL
|
|
101
|
+
return readCachedIndex();
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
const cached = readCachedIndex();
|
|
105
|
+
if (cached) return cached; // offline β serve stale rather than fail
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
return await downloadIndex();
|
|
110
|
+
} catch (err) {
|
|
111
|
+
const cached = readCachedIndex();
|
|
112
|
+
if (cached) {
|
|
113
|
+
process.stderr.write(yellow(`β refresh failed (${err.message}); using cached index\n`));
|
|
114
|
+
return cached;
|
|
115
|
+
}
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// βββ search ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
121
|
+
function parseFilters(args) {
|
|
122
|
+
const f = { limit: 10, json: false, minStars: 0, safe: false, category: null, platform: null };
|
|
123
|
+
const terms = [];
|
|
124
|
+
for (let i = 0; i < args.length; i++) {
|
|
125
|
+
const a = args[i];
|
|
126
|
+
if (a === "--json") f.json = true;
|
|
127
|
+
else if (a === "--safe" || a === "--safe-only") f.safe = true;
|
|
128
|
+
else if (a === "--limit") f.limit = Math.max(1, parseInt(args[++i], 10) || 10);
|
|
129
|
+
else if (a === "--min-stars") f.minStars = parseInt(args[++i], 10) || 0;
|
|
130
|
+
else if (a === "--category") f.category = (args[++i] || "").toLowerCase();
|
|
131
|
+
else if (a === "--platform") f.platform = (args[++i] || "").toLowerCase();
|
|
132
|
+
else if (a.startsWith("--")) {} // ignore unknown flags
|
|
133
|
+
else terms.push(a);
|
|
134
|
+
}
|
|
135
|
+
f.query = terms.join(" ").trim();
|
|
136
|
+
return f;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Score one skill row against the tokenized query. Higher = better match. */
|
|
140
|
+
function scoreRow(row, tokens) {
|
|
141
|
+
if (!tokens.length) return row.q || 0; // no query β quality-ranked browse
|
|
142
|
+
const name = (row.n || "").toLowerCase();
|
|
143
|
+
const full = (row.f || "").toLowerCase();
|
|
144
|
+
const desc = (row.d || "").toLowerCase();
|
|
145
|
+
const tags = (row.t || []).join(" ").toLowerCase();
|
|
146
|
+
let score = 0;
|
|
147
|
+
for (const tok of tokens) {
|
|
148
|
+
if (name === tok) score += 50;
|
|
149
|
+
else if (name.includes(tok)) score += 20;
|
|
150
|
+
if (full.includes(tok)) score += 8;
|
|
151
|
+
if (tags.includes(tok)) score += 10;
|
|
152
|
+
if (desc.includes(tok)) score += 5;
|
|
153
|
+
}
|
|
154
|
+
if (score === 0) return -1; // matched nothing
|
|
155
|
+
return score + (row.q || 0) / 20 + Math.min(row.s, 50000) / 25000; // quality + popularity tiebreak
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function applyFilters(skills, f) {
|
|
159
|
+
return skills.filter((r) => {
|
|
160
|
+
if (f.minStars && (r.s || 0) < f.minStars) return false;
|
|
161
|
+
if (f.safe && !(r.g === "safe")) return false;
|
|
162
|
+
if (f.category && (r.c || "").toLowerCase() !== f.category) return false;
|
|
163
|
+
if (f.platform && !(r.p || []).map((p) => p.toLowerCase()).includes(f.platform)) return false;
|
|
164
|
+
return true;
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function runSearch(index, args) {
|
|
169
|
+
const f = parseFilters(args);
|
|
170
|
+
const tokens = f.query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
171
|
+
const pool = applyFilters(index.skills, f);
|
|
172
|
+
const ranked = pool
|
|
173
|
+
.map((r) => ({ r, score: scoreRow(r, tokens) }))
|
|
174
|
+
.filter((x) => x.score >= 0)
|
|
175
|
+
.sort((a, b) => b.score - a.score)
|
|
176
|
+
.slice(0, f.limit)
|
|
177
|
+
.map((x) => x.r);
|
|
178
|
+
|
|
179
|
+
if (f.json) {
|
|
180
|
+
console.log(JSON.stringify(ranked.map(expand), null, 2));
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (!ranked.length) {
|
|
184
|
+
console.log(dim(`No skills matched "${f.query}".`) + " Try fewer/broader terms or drop --safe / --category.");
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
console.log(bold(`\n${ranked.length} result${ranked.length > 1 ? "s" : ""}`) + dim(` Β· catalog ${index.count} skills, generated ${index.generated_at.slice(0, 10)}\n`));
|
|
188
|
+
ranked.forEach(printResult);
|
|
189
|
+
console.log(dim(`\nNext: ash audit <owner/repo> Β· ash install <owner/repo>\n`));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// βββ display helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
193
|
+
function gradeBadge(g) {
|
|
194
|
+
const meta = GRADE[g] || GRADE.unknown;
|
|
195
|
+
return `${meta.mark} ${meta.label}`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** estimated_tokens is noisy upstream (some rows hold whole-repo counts).
|
|
199
|
+
* Only surface it when plausibly a context cost. */
|
|
200
|
+
function tokenHint(k) {
|
|
201
|
+
if (!k || k <= 0 || k > 200000) return "";
|
|
202
|
+
return dim(` ~${k >= 1000 ? (k / 1000).toFixed(1) + "k" : k} tok`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function starStr(s) {
|
|
206
|
+
return s >= 1000 ? `${(s / 1000).toFixed(1)}kβ
` : `${s}β
`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function printResult(r) {
|
|
210
|
+
const head = `${bold(cyan(r.f))} ${yellow(starStr(r.s))} ${gradeBadge(r.g)}`;
|
|
211
|
+
console.log(head + tokenHint(r.k) + (r.o ? green(" β official") : ""));
|
|
212
|
+
if (r.d) console.log(` ${r.d}`);
|
|
213
|
+
const meta = [r.c, ...(r.p || [])].filter(Boolean).join(" Β· ");
|
|
214
|
+
console.log(dim(` ${meta} ${HUB_SKILL(r.f)}`) + "\n");
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function expand(r) {
|
|
218
|
+
return {
|
|
219
|
+
repo_full_name: r.f, name: r.n, author: r.a, stars: r.s, description: r.d,
|
|
220
|
+
category: r.c, platforms: r.p, tags: r.t, quality_score: r.q,
|
|
221
|
+
security_grade: r.g, estimated_tokens: r.k, official: !!r.o, hub_url: HUB_SKILL(r.f),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// βββ audit (free basic tier) βββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
226
|
+
const VERDICT = {
|
|
227
|
+
safe: "Reviewed, no blocking issues β reasonable for general use. Still confirm credential handling for production.",
|
|
228
|
+
caution: "Has caution flags β fine for personal trials; review credentials/maintainer before brand or production use.",
|
|
229
|
+
unsafe: "Flagged unsafe β do NOT run against real credentials or production data.",
|
|
230
|
+
reject: "Rejected β known serious problems. Avoid.",
|
|
231
|
+
unknown: "Never audited by anyone. It's a black box: check the code, what credentials it asks for, and who maintains it before you trust it.",
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
function runAudit(index, args) {
|
|
235
|
+
const json = args.includes("--json");
|
|
236
|
+
const target = args.find((a) => !a.startsWith("--"));
|
|
237
|
+
if (!target) return fail('audit needs a target, e.g. `ash audit owner/repo`');
|
|
238
|
+
const row = index.skills.find((r) => r.f.toLowerCase() === target.toLowerCase());
|
|
239
|
+
if (!row) {
|
|
240
|
+
const msg = `"${target}" is not in the quality catalog (stars < 5, or not indexed).`;
|
|
241
|
+
if (json) return console.log(JSON.stringify({ target, in_catalog: false, note: msg + " Deep audit of any GitHub URL is a Pro feature." }, null, 2));
|
|
242
|
+
console.log(`\n${yellow("Not in the free catalog.")} ${msg}`);
|
|
243
|
+
console.log(dim(`Deep audit of any GitHub URL (incl. <5β
/ private) is a Pro feature β ${BASE}/enterprise/\n`));
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (json) {
|
|
247
|
+
return console.log(JSON.stringify({ ...expand(row), in_catalog: true, verdict: VERDICT[row.g] || VERDICT.unknown, tier_note: "Basic (free). 5-dimension deep audit + any GitHub URL β Pro." }, null, 2));
|
|
248
|
+
}
|
|
249
|
+
console.log(`\n${bold(cyan(row.f))} ${yellow(starStr(row.s))}${row.o ? green(" β official") : ""}`);
|
|
250
|
+
console.log(`Security: ${gradeBadge(row.g)} Quality: ${row.q}/100`);
|
|
251
|
+
if (row.d) console.log(dim(`\n ${row.d}`));
|
|
252
|
+
console.log(`\n${bold("Basic verdict")} ${dim("(free tier)")}`);
|
|
253
|
+
console.log(` ${VERDICT[row.g] || VERDICT.unknown}`);
|
|
254
|
+
console.log(dim(`\n Full 5-dimension audit (code Β· credentials Β· vendor Β· supply-chain Β· operational)`));
|
|
255
|
+
console.log(dim(` + any GitHub URL β ${BASE}/enterprise/`));
|
|
256
|
+
console.log(dim(` Report page: ${HUB_SKILL(row.f)}#audit\n`));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// βββ install βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
260
|
+
function installCommands(full) {
|
|
261
|
+
return {
|
|
262
|
+
"claude-code": `npx skills add ${full}`,
|
|
263
|
+
cursor: `npx skills add ${full}`,
|
|
264
|
+
manual: `git clone https://github.com/${full}.git`,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function runInstall(index, args) {
|
|
269
|
+
const json = args.includes("--json");
|
|
270
|
+
const target = args.find((a) => !a.startsWith("--"));
|
|
271
|
+
if (!target) return fail('install needs a target, e.g. `ash install owner/repo`');
|
|
272
|
+
const row = index.skills.find((r) => r.f.toLowerCase() === target.toLowerCase());
|
|
273
|
+
const cmds = installCommands(target);
|
|
274
|
+
if (json) {
|
|
275
|
+
return console.log(JSON.stringify({ repo_full_name: target, in_catalog: !!row, install_commands: cmds, pre_install_safety: row ? { security_grade: row.g, must_check: ["What credentials does it ask for, and where are they stored?", "Is the maintainer identifiable / the repo actively maintained?"] } : null, hub_url: HUB_SKILL(target) }, null, 2));
|
|
276
|
+
}
|
|
277
|
+
console.log(`\n${bold("Install ")}${cyan(target)}`);
|
|
278
|
+
console.log(` ${green(cmds["claude-code"])} ${dim("# Claude Code / Cursor")}`);
|
|
279
|
+
console.log(dim(` ${cmds.manual} # manual\n`));
|
|
280
|
+
if (row) {
|
|
281
|
+
console.log(`${bold("Before you install")} ${gradeBadge(row.g)}`);
|
|
282
|
+
if (row.g === "unknown") console.log(` ${yellow("Unaudited.")} Check the code, the credentials it requests, and who maintains it.`);
|
|
283
|
+
else console.log(` ${VERDICT[row.g]}`);
|
|
284
|
+
} else {
|
|
285
|
+
console.log(dim("Not in the quality catalog β audit it yourself before trusting it."));
|
|
286
|
+
}
|
|
287
|
+
console.log(dim(`\n Details: ${HUB_SKILL(target)}\n`));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// βββ plumbing ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
291
|
+
function fail(msg) {
|
|
292
|
+
console.error(`β ${msg}`);
|
|
293
|
+
process.exitCode = 1;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const HELP = `${bold("ash")} β AgentSkillsHub CLI ${dim("Β· search Β· audit Β· install Β· ~20K skills")}
|
|
297
|
+
|
|
298
|
+
${bold("Usage")}
|
|
299
|
+
ash search <query> [--category <c>] [--platform <p>] [--min-stars <n>] [--safe] [--limit <n>] [--json]
|
|
300
|
+
ash audit <owner/repo> [--json]
|
|
301
|
+
ash install <owner/repo> [--json]
|
|
302
|
+
ash update force-refresh the cached index
|
|
303
|
+
ash --help
|
|
304
|
+
|
|
305
|
+
${bold("Examples")}
|
|
306
|
+
ash search "scrape a website" --safe
|
|
307
|
+
ash search postgres --category mcp-server --limit 5
|
|
308
|
+
ash audit modelcontextprotocol/servers
|
|
309
|
+
ash install owner/repo
|
|
310
|
+
|
|
311
|
+
${dim("Index is a static CDN file, downloaded once and cached at")} ${CACHE_DIR}
|
|
312
|
+
${dim("Refreshes every 8h. Zero backend load. Set NO_COLOR=1 to disable color.")}`;
|
|
313
|
+
|
|
314
|
+
async function main() {
|
|
315
|
+
const [cmd, ...rest] = process.argv.slice(2);
|
|
316
|
+
if (!cmd || cmd === "--help" || cmd === "-h" || cmd === "help") return console.log(HELP);
|
|
317
|
+
|
|
318
|
+
if (cmd === "update") {
|
|
319
|
+
await loadIndex({ force: true });
|
|
320
|
+
return console.log(green("β index refreshed."));
|
|
321
|
+
}
|
|
322
|
+
const index = await loadIndex();
|
|
323
|
+
if (cmd === "search" || cmd === "s") return runSearch(index, rest);
|
|
324
|
+
if (cmd === "audit" || cmd === "a") return runAudit(index, rest);
|
|
325
|
+
if (cmd === "install" || cmd === "i") return runInstall(index, rest);
|
|
326
|
+
|
|
327
|
+
// bare `ash <query>` β treat as search
|
|
328
|
+
runSearch(index, [cmd, ...rest]);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
main().catch((err) => fail(err.message || String(err)));
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agentskillshub/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Search, audit, and install open-source AI agent skills & MCP servers from the terminal β security-graded, quality-scored.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ash": "bin/ash.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/ash.mjs",
|
|
11
|
+
"SKILL.md",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18"
|
|
17
|
+
},
|
|
18
|
+
"author": "Jason Zhu (AgentSkillsHub)",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/agentskillshub/cli.git"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/agentskillshub/cli/issues"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"mcp",
|
|
28
|
+
"ai-agent",
|
|
29
|
+
"claude",
|
|
30
|
+
"skills",
|
|
31
|
+
"security-audit",
|
|
32
|
+
"cli"
|
|
33
|
+
],
|
|
34
|
+
"homepage": "https://agentskillshub.top",
|
|
35
|
+
"license": "MIT"
|
|
36
|
+
}
|