@getvetai/cli 0.1.0 โ†’ 0.3.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/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @getvetai/cli
2
2
 
3
- Security audit CLI for AI skills and MCP servers. Scan, audit, and score tools before you install them.
3
+ Security audit CLI for AI skills and MCP servers. Scan, audit, and discover tools before you install them.
4
4
 
5
- ๐ŸŒ **Registry:** [getvet.ai](https://getvet.ai) โ€” 12,000+ AI tools cataloged and scored
5
+ ๐ŸŒ **Registry:** [getvet.ai](https://getvet.ai) โ€” 20,000+ AI tools cataloged and scored
6
6
 
7
7
  ## Install
8
8
 
@@ -10,31 +10,49 @@ Security audit CLI for AI skills and MCP servers. Scan, audit, and score tools b
10
10
  npm install -g @getvetai/cli
11
11
  ```
12
12
 
13
+ Or run without installing:
14
+
15
+ ```bash
16
+ npx @getvetai/cli scan .
17
+ ```
18
+
19
+ ## What's New in v0.3.0
20
+
21
+ - **`vet find --limit <n>`** โ€” control how many results to return (default: 10, max: 48)
22
+ - **`vet find --type <type>`** โ€” filter by `skill`, `mcp`, or `all`
23
+ - **20,000+ tools** in the registry (up from 12K) โ€” now indexing 10 sources including Smithery, mcp.so, MCP Registry, PyPI, npm, GitHub, and more
24
+
13
25
  ## Commands
14
26
 
15
27
  ### `vet scan <target>`
16
28
 
17
- Scan a single tool for security issues. Accepts file paths, URLs, npm packages, or GitHub repos.
29
+ Scan a tool for security issues. Checks the [getvet.ai](https://getvet.ai) registry first for instant results.
18
30
 
19
31
  ```bash
20
- # Scan a local SKILL.md
21
- vet scan ./my-skill/SKILL.md
22
-
23
- # Scan an npm package
32
+ # Scan an npm package (checks registry first)
24
33
  vet scan @modelcontextprotocol/server-filesystem
25
34
 
35
+ # Local analysis only (skip registry)
36
+ vet scan @modelcontextprotocol/server-filesystem --offline
37
+
38
+ # Request a deep scan from registry
39
+ vet scan @modelcontextprotocol/server-filesystem --deep
40
+
41
+ # Scan a local project
42
+ vet scan ./my-mcp-server
43
+
26
44
  # Scan a GitHub repo
27
45
  vet scan https://github.com/modelcontextprotocol/servers
28
46
 
29
- # Output JSON
47
+ # JSON output
30
48
  vet scan ./SKILL.md --json
31
49
  ```
32
50
 
33
- **Output includes:** trust score, badge (certified/reviewed/unverified/flagged), detected permissions, security issues, risk factors, and tools list.
34
-
35
51
  ### `vet audit [path]`
36
52
 
37
- Audit all AI tools in a project. Discovers tools from `package.json`, MCP configs (`.cursor/mcp.json`, Claude Desktop config), OpenClaw configs, and `SKILL.md` files.
53
+ Audit all AI tools in a project. Auto-discovers MCP configurations from:
54
+
55
+ **Claude Desktop** ยท **Cursor** ยท **VS Code** ยท **Windsurf** ยท **Cline** ยท **Zed** ยท **Continue** ยท **OpenClaw**
38
56
 
39
57
  ```bash
40
58
  # Audit current directory
@@ -43,7 +61,7 @@ vet audit
43
61
  # Audit a specific project
44
62
  vet audit ./my-project
45
63
 
46
- # Strict mode โ€” exit code 1 if any tool is unverified or flagged
64
+ # Strict mode โ€” exit 1 if any tool is unverified/flagged
47
65
  vet audit --strict
48
66
 
49
67
  # JSON output
@@ -52,33 +70,33 @@ vet audit --json
52
70
 
53
71
  ### `vet find <query>`
54
72
 
55
- Search the getvet.ai registry by description.
73
+ Search the getvet.ai registry for tools by description.
56
74
 
57
75
  ```bash
58
76
  # Search for tools
59
- vet find "file management"
60
- vet find "database query tool"
77
+ vet find "web scraping"
78
+ vet find "database access"
79
+
80
+ # Limit results
81
+ vet find "browser automation" --limit 20
82
+
83
+ # Filter by type
84
+ vet find "file management" --type mcp
61
85
 
62
86
  # JSON output
63
87
  vet find "weather" --json
64
-
65
- # Use local API
66
- vet find "search" --api http://localhost:3300
67
88
  ```
68
89
 
69
90
  ### `vet install <package>`
70
91
 
71
- Install a package with a pre-install security audit. Shows the security report and asks for confirmation if the tool is flagged.
92
+ Install a package with a pre-install security audit.
72
93
 
73
94
  ```bash
74
- # Audit + install npm package
95
+ # Audit + install
75
96
  vet install @modelcontextprotocol/server-github
76
97
 
77
98
  # Install globally
78
99
  vet install -g some-mcp-server
79
-
80
- # Install as OpenClaw skill
81
- vet install --skill weather
82
100
  ```
83
101
 
84
102
  ## Trust Scores
@@ -86,22 +104,21 @@ vet install --skill weather
86
104
  | Score | Badge | Meaning |
87
105
  |-------|-------|---------|
88
106
  | 75+ | โœ… Certified | No critical issues, good practices |
89
- | 50-74 | ๐Ÿ” Reviewed | Some concerns, use with caution |
90
- | 25-49 | โš ๏ธ Unverified | Not yet reviewed or limited info |
91
- | 0-24 | ๐Ÿšซ Flagged | Critical security issues found |
107
+ | 50โ€“74 | ๐Ÿ” Reviewed | Some concerns, use with caution |
108
+ | 25โ€“49 | โš ๏ธ Unverified | Not yet reviewed or limited info |
109
+ | 0โ€“24 | ๐Ÿšซ Flagged | Critical security issues found |
92
110
 
93
111
  ## What It Detects
94
112
 
95
- **Permissions:** shell execution, file read/write, network access, browser control, message sending, device access (camera, screen, location), database queries, crypto operations.
96
-
97
- **Security Issues:** destructive commands (`rm -rf`), remote code execution (`curl | bash`), dynamic code eval, credential patterns, elevated privileges (`sudo`), permissive file permissions.
98
-
99
- **MCP-specific:** tool parameter analysis, transport detection (stdio/http/sse), runtime detection, environment variable scanning.
113
+ - **Permissions:** shell execution, file I/O, network access, browser control, database queries, crypto operations
114
+ - **Security issues:** destructive commands, remote code execution, dynamic eval, credential patterns, elevated privileges
115
+ - **MCP-specific:** tool parameter analysis, transport detection (stdio/http/sse), runtime detection
100
116
 
101
- ## API
117
+ ## Links
102
118
 
103
- By default, `vet find` uses the public API at `https://getvet.ai`. Use `--api` to point to a different instance.
119
+ - ๐ŸŒ [getvet.ai](https://getvet.ai) โ€” Browse the registry
120
+ - ๐Ÿ“ฆ [npm](https://www.npmjs.com/package/@getvetai/cli) โ€” Package page
104
121
 
105
122
  ## License
106
123
 
107
- MIT โ€” [getvet.ai](https://getvet.ai)
124
+ MIT
@@ -1,4 +1,5 @@
1
1
  export declare function findCommand(query: string, options: {
2
2
  json?: boolean;
3
- api?: string;
3
+ limit?: string;
4
+ type?: string;
4
5
  }): Promise<void>;
@@ -1,16 +1,11 @@
1
1
  import ora from 'ora';
2
+ import { searchTools } from '../utils/api.js';
2
3
  import { displayFindResults } from '../utils/display.js';
3
4
  export async function findCommand(query, options) {
4
5
  const spinner = ora(`Searching "${query}"...`).start();
5
- const api = options.api || 'https://getvet.ai';
6
6
  try {
7
- const r = await fetch(`${api}/api/skills/search?q=${encodeURIComponent(query)}`);
8
- if (!r.ok)
9
- throw new Error(`API returned ${r.status}`);
10
- const data = await r.json();
11
- // Handle both array response and { results: [...] } response
12
- const items = Array.isArray(data) ? data : (data.results || []);
13
- const results = items.map(x => ({
7
+ const items = await searchTools(query, { limit: Number(options.limit) || 10, type: options.type });
8
+ const results = items.map((x) => ({
14
9
  name: x.name,
15
10
  slug: x.slug,
16
11
  description: x.description,
@@ -28,7 +23,6 @@ export async function findCommand(query, options) {
28
23
  }
29
24
  catch (err) {
30
25
  spinner.fail(`Search failed: ${err.message}`);
31
- console.log(' Tip: use --api http://localhost:3300 for local dev');
32
26
  process.exitCode = 1;
33
27
  }
34
28
  }
@@ -1,4 +1,6 @@
1
1
  export declare function scanCommand(target: string, options: {
2
2
  json?: boolean;
3
3
  overview?: boolean;
4
+ offline?: boolean;
5
+ deep?: boolean;
4
6
  }): Promise<void>;
@@ -1,8 +1,10 @@
1
1
  import { readFileSync, existsSync } from 'fs';
2
+ import chalk from 'chalk';
2
3
  import ora from 'ora';
3
4
  import { analyzeSkill } from '../utils/analyzer.js';
4
5
  import { analyzeMcp } from '../utils/mcp-analyzer.js';
5
6
  import { displayScanReport } from '../utils/display.js';
7
+ import { lookupTool, requestDeepScan } from '../utils/api.js';
6
8
  function detect(t) {
7
9
  if (/^https?:\/\/github\.com\//.test(t))
8
10
  return 'github';
@@ -17,6 +19,59 @@ export async function scanCommand(target, options) {
17
19
  try {
18
20
  const tt = detect(target);
19
21
  let result;
22
+ let registrySlug;
23
+ // Try registry lookup first for npm packages (unless --offline)
24
+ if (tt === 'npm' && !options.offline) {
25
+ spinner.text = `Checking Vet registry for ${target}...`;
26
+ const registryData = await lookupTool(target);
27
+ if (registryData && registryData.scanTier === 'deep') {
28
+ spinner.succeed(chalk.green('โœ“ Found in Vet registry (deep scan available)'));
29
+ registrySlug = registryData.slug || target;
30
+ result = {
31
+ name: registryData.name || target,
32
+ description: registryData.description,
33
+ type: registryData.type || 'mcp',
34
+ transport: registryData.transport,
35
+ runtime: registryData.runtime,
36
+ tools: registryData.tools || [],
37
+ permissions: registryData.permissions || [],
38
+ issues: registryData.issues || [],
39
+ trustScore: registryData.trustScore ?? registryData.trust_score ?? 50,
40
+ badge: registryData.badge || 'unverified',
41
+ codeQuality: registryData.codeQuality || { hasTests: false, hasDocs: false, linesOfCode: 0 },
42
+ riskFactors: registryData.riskFactors || [],
43
+ overview: registryData.overview,
44
+ envVars: registryData.envVars,
45
+ };
46
+ if (options.json)
47
+ console.log(JSON.stringify(result, null, 2));
48
+ else
49
+ displayScanReport(result, registrySlug);
50
+ process.exitCode = result.badge === 'flagged' ? 1 : 0;
51
+ return;
52
+ }
53
+ if (registryData) {
54
+ registrySlug = registryData.slug || target;
55
+ console.log(chalk.cyan(` โ„น Found in Vet registry (indexed) โ€” running local analysis too`));
56
+ console.log(chalk.gray(` View: https://getvet.ai/catalog/${registrySlug}`));
57
+ console.log();
58
+ }
59
+ }
60
+ // Deep scan request
61
+ if (options.deep && tt === 'npm') {
62
+ spinner.text = `Requesting deep scan for ${target}...`;
63
+ const deepResult = await requestDeepScan(target);
64
+ if (deepResult) {
65
+ spinner.succeed(chalk.green('โœ“ Deep scan requested'));
66
+ if (deepResult.status === 'queued') {
67
+ console.log(chalk.gray(` Deep scan queued. Check back soon at https://getvet.ai/catalog/${target}`));
68
+ }
69
+ }
70
+ else {
71
+ spinner.info('Deep scan request failed โ€” proceeding with local analysis');
72
+ }
73
+ }
74
+ // Local analysis (current behavior)
20
75
  if (tt === 'npm') {
21
76
  spinner.text = `Fetching npm: ${target}`;
22
77
  result = await analyzeMcp({ npmPackage: target });
@@ -43,7 +98,7 @@ export async function scanCommand(target, options) {
43
98
  if (options.json)
44
99
  console.log(JSON.stringify(result, null, 2));
45
100
  else
46
- displayScanReport(result);
101
+ displayScanReport(result, registrySlug);
47
102
  process.exitCode = result.badge === 'flagged' ? 1 : 0;
48
103
  }
49
104
  catch (err) {
package/dist/index.js CHANGED
@@ -8,13 +8,15 @@ const program = new Command();
8
8
  program
9
9
  .name('vet')
10
10
  .description('Security audit CLI for AI skills & MCP servers')
11
- .version('0.1.0');
11
+ .version('0.2.0');
12
12
  program
13
13
  .command('scan')
14
14
  .description('Scan a single tool for security issues')
15
15
  .argument('<target>', 'URL, npm package, file path, or GitHub repo')
16
16
  .option('--json', 'Output JSON instead of formatted report')
17
17
  .option('--overview', 'Include AI-generated overview')
18
+ .option('--offline', 'Skip registry lookup')
19
+ .option('--deep', 'Request deep scan from registry')
18
20
  .action(scanCommand);
19
21
  program
20
22
  .command('audit')
@@ -28,8 +30,9 @@ program
28
30
  .command('find')
29
31
  .description('Search for tools by description')
30
32
  .argument('<query>', 'Natural language search query')
33
+ .option('--limit <n>', 'Max results to return (default: 10, max: 48)', '10')
34
+ .option('--type <type>', 'Filter by type: skill, mcp, or all (default: all)')
31
35
  .option('--json', 'Output JSON')
32
- .option('--api <url>', 'API endpoint (default: https://getvet.ai)')
33
36
  .action(findCommand);
34
37
  program
35
38
  .command('install')
@@ -0,0 +1,6 @@
1
+ export declare function lookupTool(slug: string): Promise<any | null>;
2
+ export declare function searchTools(query: string, options?: {
3
+ limit?: number;
4
+ type?: string;
5
+ }): Promise<any[]>;
6
+ export declare function requestDeepScan(slug: string): Promise<any | null>;
@@ -0,0 +1,50 @@
1
+ const API_BASE = 'https://getvet.ai';
2
+ export async function lookupTool(slug) {
3
+ try {
4
+ const resp = await fetch(`${API_BASE}/api/skills/${encodeURIComponent(slug)}`, {
5
+ headers: { 'User-Agent': 'vet-cli/0.3.0' },
6
+ signal: AbortSignal.timeout(5000),
7
+ });
8
+ if (resp.ok)
9
+ return await resp.json();
10
+ return null;
11
+ }
12
+ catch {
13
+ return null;
14
+ }
15
+ }
16
+ export async function searchTools(query, options) {
17
+ try {
18
+ const limit = Math.min(Math.max(Number(options?.limit) || 10, 1), 48);
19
+ const params = new URLSearchParams({ q: query, limit: String(limit) });
20
+ if (options?.type && options.type !== 'all')
21
+ params.set('type', options.type);
22
+ const resp = await fetch(`${API_BASE}/api/skills/search?${params}`, {
23
+ headers: { 'User-Agent': 'vet-cli/0.3.0' },
24
+ signal: AbortSignal.timeout(5000),
25
+ });
26
+ if (resp.ok) {
27
+ const data = await resp.json();
28
+ return Array.isArray(data) ? data : data.results || data.items || [];
29
+ }
30
+ return [];
31
+ }
32
+ catch {
33
+ return [];
34
+ }
35
+ }
36
+ export async function requestDeepScan(slug) {
37
+ try {
38
+ const resp = await fetch(`${API_BASE}/api/tools/${encodeURIComponent(slug)}/deep-scan`, {
39
+ method: 'POST',
40
+ headers: { 'User-Agent': 'vet-cli/0.3.0', 'Content-Type': 'application/json' },
41
+ signal: AbortSignal.timeout(10000),
42
+ });
43
+ if (resp.ok)
44
+ return await resp.json();
45
+ return null;
46
+ }
47
+ catch {
48
+ return null;
49
+ }
50
+ }
@@ -21,11 +21,15 @@ export function discoverTools(projectPath) {
21
21
  }
22
22
  catch { /* skip */ }
23
23
  }
24
+ // Standard MCP config files (mcpServers or servers key at top level)
24
25
  const mcpPaths = [
25
26
  join(projectPath, '.cursor', 'mcp.json'),
26
27
  join(projectPath, 'mcp.json'),
27
28
  join(homedir(), '.config', 'claude', 'claude_desktop_config.json'),
28
29
  join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'),
30
+ join(homedir(), '.windsurf', 'mcp.json'),
31
+ join(homedir(), '.config', 'windsurf', 'mcp.json'),
32
+ join(homedir(), '.config', 'cline', 'mcp_settings.json'),
29
33
  ];
30
34
  for (const cp of mcpPaths) {
31
35
  if (!existsSync(cp))
@@ -43,6 +47,56 @@ export function discoverTools(projectPath) {
43
47
  }
44
48
  catch { /* skip */ }
45
49
  }
50
+ // VS Code settings.json โ€” look for mcp.servers key
51
+ const vscodePath = join(homedir(), '.config', 'Code', 'User', 'settings.json');
52
+ if (existsSync(vscodePath)) {
53
+ try {
54
+ const cfg = JSON.parse(readFileSync(vscodePath, 'utf-8'));
55
+ const svrs = cfg['mcp.servers'] || cfg?.mcp?.servers || {};
56
+ let c = 0;
57
+ for (const [n, v] of Object.entries(svrs)) {
58
+ const target = (v.command === 'npx' ? v.args?.[0] : undefined) || n;
59
+ tools.push({ name: n, source: vscodePath.replace(homedir(), '~'), type: 'mcp-config', target });
60
+ c++;
61
+ }
62
+ add(vscodePath.replace(homedir(), '~'), c);
63
+ }
64
+ catch { /* skip */ }
65
+ }
66
+ // Zed settings.json โ€” look for mcp key
67
+ const zedPath = join(homedir(), '.config', 'zed', 'settings.json');
68
+ if (existsSync(zedPath)) {
69
+ try {
70
+ const cfg = JSON.parse(readFileSync(zedPath, 'utf-8'));
71
+ const svrs = cfg?.mcp?.servers || cfg?.mcp || {};
72
+ let c = 0;
73
+ for (const [n, v] of Object.entries(svrs)) {
74
+ if (typeof v !== 'object' || v === null)
75
+ continue;
76
+ const target = (v.command === 'npx' ? v.args?.[0] : undefined) || n;
77
+ tools.push({ name: n, source: zedPath.replace(homedir(), '~'), type: 'mcp-config', target });
78
+ c++;
79
+ }
80
+ add(zedPath.replace(homedir(), '~'), c);
81
+ }
82
+ catch { /* skip */ }
83
+ }
84
+ // Continue config.json โ€” look for mcpServers key
85
+ const continuePath = join(homedir(), '.continue', 'config.json');
86
+ if (existsSync(continuePath)) {
87
+ try {
88
+ const cfg = JSON.parse(readFileSync(continuePath, 'utf-8'));
89
+ const svrs = cfg.mcpServers || {};
90
+ let c = 0;
91
+ for (const [n, v] of Object.entries(svrs)) {
92
+ const target = (v.command === 'npx' ? v.args?.[0] : undefined) || n;
93
+ tools.push({ name: n, source: continuePath.replace(homedir(), '~'), type: 'mcp-config', target });
94
+ c++;
95
+ }
96
+ add(continuePath.replace(homedir(), '~'), c);
97
+ }
98
+ catch { /* skip */ }
99
+ }
46
100
  for (const op of [join(projectPath, 'openclaw.json'), join(homedir(), '.openclaw', 'openclaw.json')]) {
47
101
  if (!existsSync(op))
48
102
  continue;
@@ -1,12 +1,16 @@
1
1
  import type { AnalysisResult, BadgeType } from '../types.js';
2
- export declare function displayScanReport(r: AnalysisResult): void;
2
+ export declare function displayScanReport(r: AnalysisResult, registrySlug?: string): void;
3
3
  export declare function displayAuditReport(results: AnalysisResult[], sources: {
4
4
  source: string;
5
5
  count: number;
6
6
  }[]): void;
7
7
  export declare function displayFindResults(results: Array<{
8
8
  name: string;
9
+ slug?: string;
9
10
  description?: string;
10
11
  trustScore?: number;
11
12
  badge?: BadgeType;
13
+ author?: string;
14
+ version?: string;
15
+ installs?: number;
12
16
  }>): void;
@@ -23,7 +23,7 @@ function overallRisk(r) {
23
23
  return 'medium';
24
24
  return 'low';
25
25
  }
26
- export function displayScanReport(r) {
26
+ export function displayScanReport(r, registrySlug) {
27
27
  const b = BADGE[r.badge], rk = overallRisk(r);
28
28
  console.log();
29
29
  console.log(chalk.bold(' ๐Ÿ” Vet Security Report'));
@@ -86,6 +86,10 @@ export function displayScanReport(r) {
86
86
  for (const l of r.overview.split('\n'))
87
87
  console.log(` ${chalk.gray(l)}`);
88
88
  }
89
+ if (registrySlug) {
90
+ console.log();
91
+ console.log(` ${chalk.gray('View full report:')} ${chalk.cyan(`https://getvet.ai/catalog/${registrySlug}`)}`);
92
+ }
89
93
  console.log();
90
94
  console.log(chalk.gray(' โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”'));
91
95
  console.log();
@@ -128,10 +132,14 @@ export function displayFindResults(results) {
128
132
  for (const r of results) {
129
133
  const b = r.badge ? BADGE[r.badge] : BADGE.unverified;
130
134
  console.log();
131
- console.log(` ${b.emoji} ${chalk.bold(r.name)}`);
135
+ console.log(` ${b.emoji} ${chalk.bold(r.name)}${r.version ? chalk.gray(` v${r.version}`) : ''}${r.author ? chalk.gray(` by ${r.author}`) : ''}`);
132
136
  if (r.description)
133
137
  console.log(` ${chalk.gray(r.description.slice(0, 80))}`);
134
- console.log(` Score: ${r.trustScore != null ? bar(r.trustScore) : chalk.gray('N/A')}`);
138
+ console.log(` Score: ${r.trustScore != null ? bar(r.trustScore) : chalk.gray('N/A')} Badge: ${b.emoji} ${b.color(b.label)}`);
139
+ if (r.installs != null)
140
+ console.log(` ${chalk.gray(`${r.installs.toLocaleString()} installs`)}`);
141
+ if (r.slug)
142
+ console.log(` ${chalk.cyan(`https://getvet.ai/catalog/${r.slug}`)}`);
135
143
  }
136
144
  console.log();
137
145
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getvetai/cli",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Security audit CLI for AI skills and MCP servers โ€” scan, audit, and score tools before you install them",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -12,13 +12,7 @@
12
12
  "README.md",
13
13
  "LICENSE"
14
14
  ],
15
- "repository": {
16
- "type": "git",
17
- "url": "https://github.com/getvetai/vet.git",
18
- "directory": "cli"
19
- },
20
15
  "homepage": "https://getvet.ai",
21
- "bugs": "https://github.com/getvetai/vet/issues",
22
16
  "keywords": [
23
17
  "ai",
24
18
  "security",