@getvetai/cli 0.1.0 → 0.2.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
@@ -10,19 +10,35 @@ 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
+ ## What's New in v0.2.0
14
+
15
+ - **Registry integration** — instant results for known tools from the getvet.ai catalog
16
+ - **Expanded MCP config discovery** — Claude, Cursor, VS Code, Windsurf, Cline, Zed, Continue, OpenClaw
17
+ - **`--offline` flag** — skip registry lookup for air-gapped environments
18
+ - **`--deep` flag** — request a deep scan from the registry
19
+ - **Better display** — trust score bars, registry links, badge indicators
20
+
13
21
  ## Commands
14
22
 
15
23
  ### `vet scan <target>`
16
24
 
17
25
  Scan a single tool for security issues. Accepts file paths, URLs, npm packages, or GitHub repos.
18
26
 
27
+ For npm packages, the CLI first checks the getvet.ai registry for existing scan results. If a deep scan is available, it returns instantly without local analysis.
28
+
19
29
  ```bash
30
+ # Scan an npm package (checks registry first)
31
+ vet scan @modelcontextprotocol/server-filesystem
32
+
33
+ # Skip registry, local analysis only
34
+ vet scan @modelcontextprotocol/server-filesystem --offline
35
+
36
+ # Request a deep scan from registry
37
+ vet scan @modelcontextprotocol/server-filesystem --deep
38
+
20
39
  # Scan a local SKILL.md
21
40
  vet scan ./my-skill/SKILL.md
22
41
 
23
- # Scan an npm package
24
- vet scan @modelcontextprotocol/server-filesystem
25
-
26
42
  # Scan a GitHub repo
27
43
  vet scan https://github.com/modelcontextprotocol/servers
28
44
 
@@ -30,11 +46,22 @@ vet scan https://github.com/modelcontextprotocol/servers
30
46
  vet scan ./SKILL.md --json
31
47
  ```
32
48
 
33
- **Output includes:** trust score, badge (certified/reviewed/unverified/flagged), detected permissions, security issues, risk factors, and tools list.
49
+ **Output includes:** trust score, badge (certified/reviewed/unverified/flagged), detected permissions, security issues, risk factors, tools list, and registry link.
34
50
 
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. Discovers tools from:
54
+
55
+ - `package.json` (MCP dependencies)
56
+ - Cursor (`.cursor/mcp.json`)
57
+ - Claude Desktop (`claude_desktop_config.json`)
58
+ - VS Code (`settings.json` → `mcp.servers`)
59
+ - Windsurf (`mcp.json`)
60
+ - Cline (`mcp_settings.json`)
61
+ - Zed (`settings.json` → `mcp`)
62
+ - Continue (`config.json` → `mcpServers`)
63
+ - OpenClaw (`openclaw.json`)
64
+ - `SKILL.md` files
38
65
 
39
66
  ```bash
40
67
  # Audit current directory
@@ -61,9 +88,6 @@ vet find "database query tool"
61
88
 
62
89
  # JSON output
63
90
  vet find "weather" --json
64
-
65
- # Use local API
66
- vet find "search" --api http://localhost:3300
67
91
  ```
68
92
 
69
93
  ### `vet install <package>`
@@ -98,10 +122,6 @@ vet install --skill weather
98
122
 
99
123
  **MCP-specific:** tool parameter analysis, transport detection (stdio/http/sse), runtime detection, environment variable scanning.
100
124
 
101
- ## API
102
-
103
- By default, `vet find` uses the public API at `https://getvet.ai`. Use `--api` to point to a different instance.
104
-
105
125
  ## License
106
126
 
107
127
  MIT — [getvet.ai](https://getvet.ai)
@@ -1,4 +1,3 @@
1
1
  export declare function findCommand(query: string, options: {
2
2
  json?: boolean;
3
- api?: string;
4
3
  }): 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);
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')
@@ -29,7 +31,6 @@ program
29
31
  .description('Search for tools by description')
30
32
  .argument('<query>', 'Natural language search query')
31
33
  .option('--json', 'Output JSON')
32
- .option('--api <url>', 'API endpoint (default: https://getvet.ai)')
33
34
  .action(findCommand);
34
35
  program
35
36
  .command('install')
@@ -0,0 +1,3 @@
1
+ export declare function lookupTool(slug: string): Promise<any | null>;
2
+ export declare function searchTools(query: string): Promise<any[]>;
3
+ export declare function requestDeepScan(slug: string): Promise<any | null>;
@@ -0,0 +1,46 @@
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.2.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) {
17
+ try {
18
+ const resp = await fetch(`${API_BASE}/api/skills/search?q=${encodeURIComponent(query)}`, {
19
+ headers: { 'User-Agent': 'vet-cli/0.2.0' },
20
+ signal: AbortSignal.timeout(5000),
21
+ });
22
+ if (resp.ok) {
23
+ const data = await resp.json();
24
+ return Array.isArray(data) ? data : data.results || data.items || [];
25
+ }
26
+ return [];
27
+ }
28
+ catch {
29
+ return [];
30
+ }
31
+ }
32
+ export async function requestDeepScan(slug) {
33
+ try {
34
+ const resp = await fetch(`${API_BASE}/api/tools/${encodeURIComponent(slug)}/deep-scan`, {
35
+ method: 'POST',
36
+ headers: { 'User-Agent': 'vet-cli/0.2.0', 'Content-Type': 'application/json' },
37
+ signal: AbortSignal.timeout(10000),
38
+ });
39
+ if (resp.ok)
40
+ return await resp.json();
41
+ return null;
42
+ }
43
+ catch {
44
+ return null;
45
+ }
46
+ }
@@ -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.2.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",