@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 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
+ }