@vibe-agent-toolkit/cli 0.1.0-rc.9 → 0.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 +67 -4
- package/dist/bin/vat.d.ts +13 -2
- package/dist/bin/vat.d.ts.map +1 -1
- package/dist/bin/vat.js +147 -25
- package/dist/bin/vat.js.map +1 -1
- package/dist/bin.js +22 -14
- package/dist/bin.js.map +1 -1
- package/dist/commands/agent/index.d.ts.map +1 -1
- package/dist/commands/agent/index.js +0 -37
- package/dist/commands/agent/index.js.map +1 -1
- package/dist/commands/audit/cache-detector.d.ts +32 -0
- package/dist/commands/audit/cache-detector.d.ts.map +1 -0
- package/dist/commands/audit/cache-detector.js +68 -0
- package/dist/commands/audit/cache-detector.js.map +1 -0
- package/dist/commands/audit/hierarchical-output.d.ts +41 -0
- package/dist/commands/audit/hierarchical-output.d.ts.map +1 -0
- package/dist/commands/audit/hierarchical-output.js +267 -0
- package/dist/commands/audit/hierarchical-output.js.map +1 -0
- package/dist/commands/audit.d.ts +18 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +386 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/doctor.d.ts +106 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +499 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/resources/index.js +1 -1
- package/dist/commands/resources/index.js.map +1 -1
- package/dist/commands/resources/scan.d.ts.map +1 -1
- package/dist/commands/resources/scan.js +8 -0
- package/dist/commands/resources/scan.js.map +1 -1
- package/dist/utils/config-loader.d.ts +6 -0
- package/dist/utils/config-loader.d.ts.map +1 -1
- package/dist/utils/config-loader.js +18 -0
- package/dist/utils/config-loader.js.map +1 -1
- package/docs/audit.md +446 -0
- package/docs/doctor.md +268 -0
- package/docs/index.md +302 -26
- package/package.json +11 -8
- package/dist/commands/agent/audit.d.ts +0 -9
- package/dist/commands/agent/audit.d.ts.map +0 -1
- package/dist/commands/agent/audit.js +0 -139
- package/dist/commands/agent/audit.js.map +0 -1
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import * as os from 'node:os';
|
|
2
|
+
import { toForwardSlash } from '@vibe-agent-toolkit/utils';
|
|
3
|
+
/**
|
|
4
|
+
* Replace home directory with ~ for cleaner paths
|
|
5
|
+
* Normalizes paths for cross-platform comparison (handles Windows backslashes)
|
|
6
|
+
*/
|
|
7
|
+
function replaceHomeDir(filePath) {
|
|
8
|
+
const homeDir = os.homedir();
|
|
9
|
+
// Normalize both paths to forward slashes for comparison
|
|
10
|
+
const normalizedFilePath = toForwardSlash(filePath);
|
|
11
|
+
const normalizedHomeDir = toForwardSlash(homeDir);
|
|
12
|
+
if (normalizedFilePath.startsWith(normalizedHomeDir)) {
|
|
13
|
+
// Replace using original paths to preserve platform separators in output
|
|
14
|
+
return filePath.replace(homeDir, '~');
|
|
15
|
+
}
|
|
16
|
+
return filePath;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Parse path structure to extract marketplace, plugin, and skill names
|
|
20
|
+
*
|
|
21
|
+
* Expected patterns:
|
|
22
|
+
* - Marketplace plugin skill: .../marketplaces/{marketplace}/{plugin}/skills/{skill}/SKILL.md
|
|
23
|
+
* - Standalone plugin skill: .../plugins/{plugin}/skills/{skill}/SKILL.md (no marketplaces/)
|
|
24
|
+
* - Standalone skill: .../plugins/{skill}/SKILL.md (no skills/)
|
|
25
|
+
*/
|
|
26
|
+
function parsePathStructure(filePath) {
|
|
27
|
+
// Normalize to forward slashes for cross-platform parsing
|
|
28
|
+
const normalizedPath = toForwardSlash(filePath);
|
|
29
|
+
const parts = normalizedPath.split('/');
|
|
30
|
+
// Detect if this is a cached resource
|
|
31
|
+
const isCached = parts.includes('cache');
|
|
32
|
+
// Find key indices
|
|
33
|
+
const marketplacesIdx = parts.indexOf('marketplaces');
|
|
34
|
+
const pluginsIdx = parts.indexOf('plugins');
|
|
35
|
+
const skillsIdx = parts.indexOf('skills');
|
|
36
|
+
// Marketplace plugin skill: .../marketplaces/{marketplace}/{plugin}/skills/{skill}/SKILL.md
|
|
37
|
+
if (marketplacesIdx >= 0 && skillsIdx >= 0) {
|
|
38
|
+
const marketplace = parts[marketplacesIdx + 1];
|
|
39
|
+
const plugin = parts[marketplacesIdx + 2];
|
|
40
|
+
const skill = parts[skillsIdx + 1];
|
|
41
|
+
if (marketplace !== undefined && plugin !== undefined && skill !== undefined) {
|
|
42
|
+
return { marketplace, plugin, skill, isCached };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Standalone plugin skill: .../plugins/{plugin}/skills/{skill}/SKILL.md
|
|
46
|
+
if (pluginsIdx >= 0 && skillsIdx >= 0) {
|
|
47
|
+
const plugin = parts[skillsIdx - 1]; // Plugin name is before /skills/
|
|
48
|
+
const skill = parts[skillsIdx + 1];
|
|
49
|
+
if (plugin !== undefined && skill !== undefined) {
|
|
50
|
+
return { plugin, skill, isCached };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Standalone skill: .../plugins/{skill}/SKILL.md
|
|
54
|
+
if (pluginsIdx >= 0) {
|
|
55
|
+
const skill = parts[pluginsIdx + 1];
|
|
56
|
+
if (skill !== undefined) {
|
|
57
|
+
return { skill, isCached };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Fallback: use directory name before SKILL.md
|
|
61
|
+
const skill = parts.at(-2) ?? 'unknown';
|
|
62
|
+
return { skill, isCached };
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Add skill entry to marketplace map
|
|
66
|
+
*/
|
|
67
|
+
function addToMarketplaceMap(marketplacesMap, marketplace, plugin, entry) {
|
|
68
|
+
if (!marketplacesMap.has(marketplace)) {
|
|
69
|
+
marketplacesMap.set(marketplace, new Map());
|
|
70
|
+
}
|
|
71
|
+
const pluginsMap = marketplacesMap.get(marketplace);
|
|
72
|
+
if (pluginsMap === undefined) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (!pluginsMap.has(plugin)) {
|
|
76
|
+
pluginsMap.set(plugin, []);
|
|
77
|
+
}
|
|
78
|
+
pluginsMap.get(plugin)?.push(entry);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Add skill entry to cached plugin map
|
|
82
|
+
*/
|
|
83
|
+
function addToCachedPluginMap(cachedPluginsMap, plugin, entry) {
|
|
84
|
+
if (!cachedPluginsMap.has(plugin)) {
|
|
85
|
+
cachedPluginsMap.set(plugin, []);
|
|
86
|
+
}
|
|
87
|
+
cachedPluginsMap.get(plugin)?.push(entry);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Add skill entry to standalone plugin map
|
|
91
|
+
*/
|
|
92
|
+
function addToStandalonePluginMap(standalonePluginsMap, plugin, entry) {
|
|
93
|
+
if (!standalonePluginsMap.has(plugin)) {
|
|
94
|
+
standalonePluginsMap.set(plugin, []);
|
|
95
|
+
}
|
|
96
|
+
standalonePluginsMap.get(plugin)?.push(entry);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Filter out duplicate cache results that match their source
|
|
100
|
+
*
|
|
101
|
+
* Suppresses cache entries when:
|
|
102
|
+
* - A matching source (marketplace/plugin) exists
|
|
103
|
+
* - Same validation status (success/warning/error)
|
|
104
|
+
* - Same issues (count and content)
|
|
105
|
+
*
|
|
106
|
+
* Keeps cache entries when:
|
|
107
|
+
* - No matching source found (orphaned cache)
|
|
108
|
+
* - Different validation status or issues (stale/different)
|
|
109
|
+
*
|
|
110
|
+
* @returns Filtered results and cache status map
|
|
111
|
+
*/
|
|
112
|
+
function filterCacheDuplicates(results) {
|
|
113
|
+
const sourceBySkillName = new Map();
|
|
114
|
+
const cacheResults = [];
|
|
115
|
+
const nonCacheResults = [];
|
|
116
|
+
const cacheStatusMap = new Map();
|
|
117
|
+
// First pass: categorize results and build source index
|
|
118
|
+
for (const result of results) {
|
|
119
|
+
const { skill, isCached } = parsePathStructure(result.path);
|
|
120
|
+
if (isCached) {
|
|
121
|
+
cacheResults.push(result);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
nonCacheResults.push(result);
|
|
125
|
+
// Index source results by skill name for matching
|
|
126
|
+
sourceBySkillName.set(skill, result);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Second pass: filter cache results and track status
|
|
130
|
+
const filteredCache = [];
|
|
131
|
+
for (const cacheResult of cacheResults) {
|
|
132
|
+
const { skill } = parsePathStructure(cacheResult.path);
|
|
133
|
+
const sourceResult = sourceBySkillName.get(skill);
|
|
134
|
+
if (!sourceResult) {
|
|
135
|
+
// Orphaned cache - no matching source, keep it
|
|
136
|
+
filteredCache.push(cacheResult);
|
|
137
|
+
cacheStatusMap.set(cacheResult.path, 'orphaned');
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
// Check if cache and source have identical validation results
|
|
141
|
+
const statusMatches = cacheResult.status === sourceResult.status;
|
|
142
|
+
const issuesMatch = cacheResult.issues.length === sourceResult.issues.length &&
|
|
143
|
+
cacheResult.issues.every((issue, idx) => issue.code === sourceResult.issues[idx]?.code &&
|
|
144
|
+
issue.severity === sourceResult.issues[idx]?.severity);
|
|
145
|
+
if (!statusMatches || !issuesMatch) {
|
|
146
|
+
// Different validation results - keep both (stale or different)
|
|
147
|
+
filteredCache.push(cacheResult);
|
|
148
|
+
cacheStatusMap.set(cacheResult.path, 'stale');
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
// Fresh cache - matches source, will be suppressed
|
|
152
|
+
cacheStatusMap.set(cacheResult.path, 'fresh');
|
|
153
|
+
}
|
|
154
|
+
// If they match exactly, suppress the cache copy (don't add to filteredCache)
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
filtered: [...nonCacheResults, ...filteredCache],
|
|
158
|
+
cacheStatusMap,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Create skill entry from validation result
|
|
163
|
+
*/
|
|
164
|
+
function createSkillEntry(result, cacheStatusMap) {
|
|
165
|
+
const { skill, isCached } = parsePathStructure(result.path);
|
|
166
|
+
const entry = {
|
|
167
|
+
name: skill,
|
|
168
|
+
path: replaceHomeDir(result.path),
|
|
169
|
+
status: result.status,
|
|
170
|
+
issues: result.issues,
|
|
171
|
+
};
|
|
172
|
+
// Add cache status if this is a cached resource
|
|
173
|
+
if (isCached) {
|
|
174
|
+
const cacheStatus = cacheStatusMap.get(result.path);
|
|
175
|
+
if (cacheStatus !== undefined) {
|
|
176
|
+
entry.cacheStatus = cacheStatus;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return entry;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Categorize entry into appropriate map
|
|
183
|
+
*/
|
|
184
|
+
function categorizeEntry(entry, marketplace, plugin, isCached, maps) {
|
|
185
|
+
if (marketplace !== undefined && plugin !== undefined) {
|
|
186
|
+
addToMarketplaceMap(maps.marketplacesMap, marketplace, plugin, entry);
|
|
187
|
+
}
|
|
188
|
+
else if (plugin === undefined) {
|
|
189
|
+
maps.standaloneSkills.push(entry);
|
|
190
|
+
}
|
|
191
|
+
else if (isCached) {
|
|
192
|
+
addToCachedPluginMap(maps.cachedPluginsMap, plugin, entry);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
addToStandalonePluginMap(maps.standalonePluginsMap, plugin, entry);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Convert marketplace map to array structure
|
|
200
|
+
*/
|
|
201
|
+
function convertMarketplacesMapToArray(marketplacesMap) {
|
|
202
|
+
const marketplaces = [];
|
|
203
|
+
for (const [marketplaceName, pluginsMap] of marketplacesMap) {
|
|
204
|
+
const plugins = [];
|
|
205
|
+
for (const [pluginName, skills] of pluginsMap) {
|
|
206
|
+
plugins.push({ name: pluginName, skills });
|
|
207
|
+
}
|
|
208
|
+
marketplaces.push({ name: marketplaceName, plugins });
|
|
209
|
+
}
|
|
210
|
+
return marketplaces;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Convert plugin map to array structure
|
|
214
|
+
*/
|
|
215
|
+
function convertPluginMapToArray(pluginMap) {
|
|
216
|
+
const plugins = [];
|
|
217
|
+
for (const [pluginName, skills] of pluginMap) {
|
|
218
|
+
plugins.push({ name: pluginName, skills });
|
|
219
|
+
}
|
|
220
|
+
return plugins;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Build hierarchical output structure from validation results.
|
|
224
|
+
*
|
|
225
|
+
* Groups skills by:
|
|
226
|
+
* 1. Marketplace -> Plugin -> Skills (for marketplace-installed plugins)
|
|
227
|
+
* 2. Standalone Plugins -> Skills (for non-marketplace plugins)
|
|
228
|
+
* 3. Standalone Skills (for skills without plugins)
|
|
229
|
+
*
|
|
230
|
+
* By default, only includes skills with issues (terse principle).
|
|
231
|
+
* With verbose=true, includes all scanned skills regardless of status.
|
|
232
|
+
* Replaces home directory with ~ for cleaner display.
|
|
233
|
+
*
|
|
234
|
+
* @param results - Validation results from audit command
|
|
235
|
+
* @param verbose - If true, include all results; if false, only show results with issues
|
|
236
|
+
* @returns Hierarchical structure for display
|
|
237
|
+
*/
|
|
238
|
+
export function buildHierarchicalOutput(results, verbose = false) {
|
|
239
|
+
// Filter out cache duplicates that match their source
|
|
240
|
+
const { filtered: filteredResults, cacheStatusMap } = filterCacheDuplicates(results);
|
|
241
|
+
const marketplacesMap = new Map();
|
|
242
|
+
const cachedPluginsMap = new Map();
|
|
243
|
+
const standalonePluginsMap = new Map();
|
|
244
|
+
const standaloneSkills = [];
|
|
245
|
+
const maps = {
|
|
246
|
+
marketplacesMap,
|
|
247
|
+
cachedPluginsMap,
|
|
248
|
+
standalonePluginsMap,
|
|
249
|
+
standaloneSkills,
|
|
250
|
+
};
|
|
251
|
+
for (const result of filteredResults) {
|
|
252
|
+
// Only include results with issues (terse principle), unless verbose mode
|
|
253
|
+
if (!verbose && result.status === 'success') {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
const { marketplace, plugin, isCached } = parsePathStructure(result.path);
|
|
257
|
+
const entry = createSkillEntry(result, cacheStatusMap);
|
|
258
|
+
categorizeEntry(entry, marketplace, plugin, isCached, maps);
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
marketplaces: convertMarketplacesMapToArray(marketplacesMap),
|
|
262
|
+
cachedPlugins: convertPluginMapToArray(cachedPluginsMap),
|
|
263
|
+
standalonePlugins: convertPluginMapToArray(standalonePluginsMap),
|
|
264
|
+
standaloneSkills,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
//# sourceMappingURL=hierarchical-output.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hierarchical-output.js","sourceRoot":"","sources":["../../../src/commands/audit/hierarchical-output.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit command - audits plugins, marketplaces, registries, and Claude Skills
|
|
3
|
+
* Top-level command: vat audit [path]
|
|
4
|
+
*/
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
export interface AuditCommandOptions {
|
|
7
|
+
debug?: boolean;
|
|
8
|
+
recursive?: boolean;
|
|
9
|
+
user?: boolean;
|
|
10
|
+
verbose?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Create audit command
|
|
14
|
+
* Top-level command: vat audit [path]
|
|
15
|
+
*/
|
|
16
|
+
export declare function createAuditCommand(): Command;
|
|
17
|
+
export declare function auditCommand(targetPath: string | undefined, options: AuditCommandOptions): Promise<void>;
|
|
18
|
+
//# sourceMappingURL=audit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/commands/audit.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAaH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,OAAO,CAyD5C;AAWD,wBAAsB,YAAY,CAChC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,IAAI,CAAC,CA6Cf"}
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit command - audits plugins, marketplaces, registries, and Claude Skills
|
|
3
|
+
* Top-level command: vat audit [path]
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from 'node:fs';
|
|
6
|
+
import * as os from 'node:os';
|
|
7
|
+
import * as path from 'node:path';
|
|
8
|
+
import { detectFormat } from '@vibe-agent-toolkit/discovery';
|
|
9
|
+
import { detectResourceFormat, validate, validateSkill, } from '@vibe-agent-toolkit/runtime-claude-skills';
|
|
10
|
+
import { Command } from 'commander';
|
|
11
|
+
import { handleCommandError } from '../utils/command-error.js';
|
|
12
|
+
import { createLogger } from '../utils/logger.js';
|
|
13
|
+
import { writeYamlOutput } from '../utils/output.js';
|
|
14
|
+
import { buildHierarchicalOutput } from './audit/hierarchical-output.js';
|
|
15
|
+
/**
|
|
16
|
+
* Create audit command
|
|
17
|
+
* Top-level command: vat audit [path]
|
|
18
|
+
*/
|
|
19
|
+
export function createAuditCommand() {
|
|
20
|
+
const audit = new Command('audit');
|
|
21
|
+
audit
|
|
22
|
+
.description('Audit Claude plugins, marketplaces, registries, and skills')
|
|
23
|
+
.argument('[path]', 'Path to audit (default: current directory)')
|
|
24
|
+
.option('-r, --recursive', 'Scan directories recursively for all resource types')
|
|
25
|
+
.option('--user', 'Audit user-level Claude plugins installation (~/.claude/plugins)')
|
|
26
|
+
.option('--verbose', 'Show all scanned resources, including those without issues')
|
|
27
|
+
.option('--debug', 'Enable debug logging')
|
|
28
|
+
.action(auditCommand)
|
|
29
|
+
.addHelpText('after', `
|
|
30
|
+
Description:
|
|
31
|
+
Audits Claude plugins, marketplaces, registries, and Claude Skills for
|
|
32
|
+
quality, correctness, and compatibility. Automatically detects resource
|
|
33
|
+
type and validates accordingly. Outputs YAML report to stdout,
|
|
34
|
+
errors/warnings to stderr.
|
|
35
|
+
|
|
36
|
+
Supported resource types:
|
|
37
|
+
- Plugin directories (.claude-plugin/plugin.json)
|
|
38
|
+
- Marketplace directories (.claude-plugin/marketplace.json)
|
|
39
|
+
- Registry files (installed_plugins.json, known_marketplaces.json)
|
|
40
|
+
- Claude Skills (SKILL.md files)
|
|
41
|
+
- VAT agents (agent.yaml + SKILL.md)
|
|
42
|
+
|
|
43
|
+
Path can be: resource directory, registry file, SKILL.md file, or scan directory
|
|
44
|
+
Default: current directory
|
|
45
|
+
Use --user to audit user-level installation (~/.claude/plugins) automatically
|
|
46
|
+
|
|
47
|
+
Validation Checks:
|
|
48
|
+
Errors (must fix):
|
|
49
|
+
- Missing or invalid manifests/frontmatter
|
|
50
|
+
- Schema validation failures
|
|
51
|
+
- Broken links to other files (Skills only)
|
|
52
|
+
- Reserved words in names (Skills only)
|
|
53
|
+
- XML tags in frontmatter fields (Skills only)
|
|
54
|
+
- Windows-style backslashes in paths (Skills only)
|
|
55
|
+
|
|
56
|
+
Warnings (should fix):
|
|
57
|
+
- Skill exceeds recommended length (>5000 lines)
|
|
58
|
+
- References console-incompatible tools (Skills only)
|
|
59
|
+
|
|
60
|
+
Exit Codes:
|
|
61
|
+
0 - Success | 1 - Errors found | 2 - System error
|
|
62
|
+
|
|
63
|
+
Examples:
|
|
64
|
+
$ vat audit --user # Audit user-level plugins installation
|
|
65
|
+
$ vat audit # Audit current directory
|
|
66
|
+
$ vat audit ./my-plugin # Audit plugin directory
|
|
67
|
+
$ vat audit installed_plugins.json # Audit registry file
|
|
68
|
+
$ vat audit ./resources --recursive # Audit all resources recursively
|
|
69
|
+
`);
|
|
70
|
+
return audit;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Get the user-level Claude plugins directory
|
|
74
|
+
* Cross-platform: ~/.claude/plugins on macOS/Linux, %USERPROFILE%\.claude\plugins on Windows
|
|
75
|
+
*/
|
|
76
|
+
function getUserPluginsDir() {
|
|
77
|
+
const homeDir = os.homedir();
|
|
78
|
+
return path.join(homeDir, '.claude', 'plugins');
|
|
79
|
+
}
|
|
80
|
+
export async function auditCommand(targetPath, options) {
|
|
81
|
+
const logger = createLogger(options.debug ? { debug: true } : {});
|
|
82
|
+
const startTime = Date.now();
|
|
83
|
+
try {
|
|
84
|
+
let scanPath;
|
|
85
|
+
let recursive = options.recursive ?? false;
|
|
86
|
+
// Handle --user flag
|
|
87
|
+
if (options.user) {
|
|
88
|
+
const userPluginsDir = getUserPluginsDir();
|
|
89
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- safe: path constructed from os.homedir()
|
|
90
|
+
if (!fs.existsSync(userPluginsDir)) {
|
|
91
|
+
logger.error(`User plugins directory not found: ${userPluginsDir}`);
|
|
92
|
+
logger.error('Claude plugins have not been installed yet.');
|
|
93
|
+
process.exit(2);
|
|
94
|
+
}
|
|
95
|
+
scanPath = userPluginsDir;
|
|
96
|
+
recursive = true; // Always recursive for user-level audit
|
|
97
|
+
logger.debug(`Auditing user-level plugins at: ${scanPath}`);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
scanPath = targetPath ? path.resolve(targetPath) : process.cwd();
|
|
101
|
+
logger.debug(`Auditing resources at: ${scanPath}`);
|
|
102
|
+
}
|
|
103
|
+
// Get validation results
|
|
104
|
+
const results = await getValidationResults(scanPath, recursive, logger);
|
|
105
|
+
// Use hierarchical output for --user flag, flat output otherwise
|
|
106
|
+
if (options.user) {
|
|
107
|
+
// Filter to only include claude-skill type for hierarchical output
|
|
108
|
+
const skillResults = results.filter((r) => r.type === 'claude-skill');
|
|
109
|
+
const hierarchical = buildHierarchicalOutput(skillResults, options.verbose ?? false);
|
|
110
|
+
const summary = calculateHierarchicalSummary(results, hierarchical, startTime);
|
|
111
|
+
writeYamlOutput(summary);
|
|
112
|
+
logHierarchicalSummary(results, hierarchical, logger);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
// Standard flat output
|
|
116
|
+
const summary = calculateSummary(results, startTime);
|
|
117
|
+
writeYamlOutput(summary);
|
|
118
|
+
handleAuditResults(results, summary, logger);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
handleCommandError(error, logger, startTime, 'AgentAudit');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async function getValidationResults(scanPath, recursive, logger) {
|
|
126
|
+
const format = detectFormat(scanPath);
|
|
127
|
+
// Special handling for direct SKILL.md file
|
|
128
|
+
if (format === 'claude-skill') {
|
|
129
|
+
logger.debug('Detected single Claude Skill');
|
|
130
|
+
const result = await validateSkill({ skillPath: scanPath });
|
|
131
|
+
return [result];
|
|
132
|
+
}
|
|
133
|
+
// Special handling for VAT agent: validate its SKILL.md
|
|
134
|
+
if (format === 'vat-agent') {
|
|
135
|
+
const skillPath = path.join(scanPath, 'SKILL.md');
|
|
136
|
+
logger.debug('Detected VAT agent, validating SKILL.md');
|
|
137
|
+
const result = await validateSkill({ skillPath, isVATGenerated: true });
|
|
138
|
+
return [result];
|
|
139
|
+
}
|
|
140
|
+
// For plugin/marketplace directories or registry files, use unified validator
|
|
141
|
+
const resourceFormat = await detectResourceFormat(scanPath);
|
|
142
|
+
if (resourceFormat.type !== 'unknown') {
|
|
143
|
+
logger.debug(`Detected ${resourceFormat.type} at: ${scanPath}`);
|
|
144
|
+
const result = await validate(scanPath);
|
|
145
|
+
return [result];
|
|
146
|
+
}
|
|
147
|
+
// If unknown format, check if it's a directory we can scan
|
|
148
|
+
const fs = await import('node:fs/promises');
|
|
149
|
+
try {
|
|
150
|
+
const stat = await fs.stat(scanPath);
|
|
151
|
+
if (stat.isDirectory()) {
|
|
152
|
+
logger.debug('Scanning directory for resources');
|
|
153
|
+
return scanDirectory(scanPath, recursive, logger);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// Path doesn't exist or not accessible, let validate() handle it
|
|
158
|
+
}
|
|
159
|
+
// Unknown resource type - use unified validator which will return appropriate error
|
|
160
|
+
logger.debug(`Unknown resource type at: ${scanPath}`);
|
|
161
|
+
const result = await validate(scanPath);
|
|
162
|
+
return [result];
|
|
163
|
+
}
|
|
164
|
+
function calculateSummary(results, startTime) {
|
|
165
|
+
const base = buildBaseSummary(results, startTime);
|
|
166
|
+
return {
|
|
167
|
+
...base,
|
|
168
|
+
files: results,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function handleAuditResults(results, summary, logger) {
|
|
172
|
+
const { errors: errorCount, warnings: warningCount, success: successCount } = summary.summary;
|
|
173
|
+
if (errorCount > 0) {
|
|
174
|
+
logErrors(results, errorCount, logger);
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
if (warningCount > 0) {
|
|
178
|
+
logWarnings(results, warningCount, logger);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
logger.info(`Audit successful: ${successCount} file(s) passed`);
|
|
182
|
+
}
|
|
183
|
+
process.exit(0);
|
|
184
|
+
}
|
|
185
|
+
function logErrors(results, errorCount, logger) {
|
|
186
|
+
logger.error(`Audit failed: ${errorCount} file(s) with errors`);
|
|
187
|
+
const errorResults = results.filter((r) => r.status === 'error');
|
|
188
|
+
for (const result of errorResults) {
|
|
189
|
+
logger.error(`\n${result.path}:`);
|
|
190
|
+
const errorIssues = result.issues.filter((i) => i.severity === 'error');
|
|
191
|
+
logIssues(errorIssues, logger.error.bind(logger));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function logWarnings(results, warningCount, logger) {
|
|
195
|
+
logger.info(`Audit passed with warnings: ${warningCount} file(s)`);
|
|
196
|
+
const warningResults = results.filter((r) => r.status === 'warning');
|
|
197
|
+
for (const result of warningResults) {
|
|
198
|
+
logger.info(`\n${result.path}:`);
|
|
199
|
+
const warningIssues = result.issues.filter((i) => i.severity === 'warning');
|
|
200
|
+
logIssues(warningIssues, logger.info.bind(logger));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function logIssues(issues, logFn) {
|
|
204
|
+
for (const issue of issues) {
|
|
205
|
+
logFn(` [${issue.code}] ${issue.message}`);
|
|
206
|
+
if (issue.location) {
|
|
207
|
+
logFn(` at: ${issue.location}`);
|
|
208
|
+
}
|
|
209
|
+
if (issue.fix) {
|
|
210
|
+
logFn(` fix: ${issue.fix}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Handle file entry during directory scan
|
|
216
|
+
*/
|
|
217
|
+
async function handleFileEntry(entry, fullPath, logger) {
|
|
218
|
+
// Check for registry files
|
|
219
|
+
if (entry.name === 'installed_plugins.json' || entry.name === 'known_marketplaces.json') {
|
|
220
|
+
logger.debug(`Validating registry: ${fullPath}`);
|
|
221
|
+
return validate(fullPath);
|
|
222
|
+
}
|
|
223
|
+
// Check for SKILL.md
|
|
224
|
+
if (entry.name === 'SKILL.md') {
|
|
225
|
+
logger.debug(`Validating Claude Skill: ${fullPath}`);
|
|
226
|
+
return validateSkill({ skillPath: fullPath });
|
|
227
|
+
}
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Handle directory entry during directory scan
|
|
232
|
+
*/
|
|
233
|
+
async function handleDirectoryEntry(fullPath, recursive, logger) {
|
|
234
|
+
const fs = await import('node:fs/promises');
|
|
235
|
+
const results = [];
|
|
236
|
+
// Check if directory contains a plugin or marketplace
|
|
237
|
+
const claudePluginDir = path.join(fullPath, '.claude-plugin');
|
|
238
|
+
const hasClaudePlugin = await fs.access(claudePluginDir).then(() => true).catch(() => false);
|
|
239
|
+
if (hasClaudePlugin) {
|
|
240
|
+
logger.debug(`Validating resource directory: ${fullPath}`);
|
|
241
|
+
const result = await validate(fullPath);
|
|
242
|
+
results.push(result);
|
|
243
|
+
}
|
|
244
|
+
// Recurse into subdirectories (both plugin/marketplace dirs and regular dirs)
|
|
245
|
+
if (recursive) {
|
|
246
|
+
const subResults = await scanDirectory(fullPath, recursive, logger);
|
|
247
|
+
results.push(...subResults);
|
|
248
|
+
}
|
|
249
|
+
return results;
|
|
250
|
+
}
|
|
251
|
+
async function scanDirectory(dirPath, recursive, logger) {
|
|
252
|
+
const fs = await import('node:fs/promises');
|
|
253
|
+
const results = [];
|
|
254
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
255
|
+
for (const entry of entries) {
|
|
256
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
257
|
+
if (entry.isFile()) {
|
|
258
|
+
const result = await handleFileEntry(entry, fullPath, logger);
|
|
259
|
+
if (result !== null) {
|
|
260
|
+
results.push(result);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
else if (entry.isDirectory()) {
|
|
264
|
+
const dirResults = await handleDirectoryEntry(fullPath, recursive, logger);
|
|
265
|
+
results.push(...dirResults);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return results;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Calculate overall status from validation results
|
|
272
|
+
*/
|
|
273
|
+
function calculateOverallStatus(results) {
|
|
274
|
+
const errorCount = results.filter((r) => r.status === 'error').length;
|
|
275
|
+
const warningCount = results.filter((r) => r.status === 'warning').length;
|
|
276
|
+
if (errorCount > 0) {
|
|
277
|
+
return 'error';
|
|
278
|
+
}
|
|
279
|
+
if (warningCount > 0) {
|
|
280
|
+
return 'warning';
|
|
281
|
+
}
|
|
282
|
+
return 'success';
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Count all skills in hierarchical output
|
|
286
|
+
*/
|
|
287
|
+
function countAllSkills(hierarchical) {
|
|
288
|
+
let total = 0;
|
|
289
|
+
// Count marketplace skills
|
|
290
|
+
for (const marketplace of hierarchical.marketplaces) {
|
|
291
|
+
for (const plugin of marketplace.plugins) {
|
|
292
|
+
total += plugin.skills.length;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
// Count cached plugin skills
|
|
296
|
+
for (const plugin of hierarchical.cachedPlugins) {
|
|
297
|
+
total += plugin.skills.length;
|
|
298
|
+
}
|
|
299
|
+
// Count standalone plugin skills
|
|
300
|
+
for (const plugin of hierarchical.standalonePlugins) {
|
|
301
|
+
total += plugin.skills.length;
|
|
302
|
+
}
|
|
303
|
+
// Count standalone skills
|
|
304
|
+
total += hierarchical.standaloneSkills.length;
|
|
305
|
+
return total;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Calculate issue counts from validation results
|
|
309
|
+
*/
|
|
310
|
+
function calculateIssueCounts(results) {
|
|
311
|
+
const successCount = results.filter((r) => r.status === 'success').length;
|
|
312
|
+
const warningCount = results.filter((r) => r.status === 'warning').length;
|
|
313
|
+
const errorCount = results.filter((r) => r.status === 'error').length;
|
|
314
|
+
const totalErrors = results.reduce((sum, r) => sum + r.issues.filter(i => i.severity === 'error').length, 0);
|
|
315
|
+
const totalWarnings = results.reduce((sum, r) => sum + r.issues.filter(i => i.severity === 'warning').length, 0);
|
|
316
|
+
const totalInfo = results.reduce((sum, r) => sum + r.issues.filter(i => i.severity === 'info').length, 0);
|
|
317
|
+
return {
|
|
318
|
+
successCount,
|
|
319
|
+
warningCount,
|
|
320
|
+
errorCount,
|
|
321
|
+
totalErrors,
|
|
322
|
+
totalWarnings,
|
|
323
|
+
totalInfo,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Build base summary structure (used by both flat and hierarchical)
|
|
328
|
+
*/
|
|
329
|
+
function buildBaseSummary(results, startTime) {
|
|
330
|
+
const counts = calculateIssueCounts(results);
|
|
331
|
+
const status = calculateOverallStatus(results);
|
|
332
|
+
return {
|
|
333
|
+
status,
|
|
334
|
+
summary: {
|
|
335
|
+
filesScanned: results.length,
|
|
336
|
+
success: counts.successCount,
|
|
337
|
+
warnings: counts.warningCount,
|
|
338
|
+
errors: counts.errorCount,
|
|
339
|
+
},
|
|
340
|
+
issues: {
|
|
341
|
+
errors: counts.totalErrors,
|
|
342
|
+
warnings: counts.totalWarnings,
|
|
343
|
+
info: counts.totalInfo,
|
|
344
|
+
},
|
|
345
|
+
duration: `${Date.now() - startTime}ms`,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Calculate summary for hierarchical output
|
|
350
|
+
*/
|
|
351
|
+
function calculateHierarchicalSummary(results, hierarchical, startTime) {
|
|
352
|
+
const base = buildBaseSummary(results, startTime);
|
|
353
|
+
return {
|
|
354
|
+
...base,
|
|
355
|
+
summary: {
|
|
356
|
+
...base.summary,
|
|
357
|
+
marketplaces: hierarchical.marketplaces.length,
|
|
358
|
+
cachedPlugins: hierarchical.cachedPlugins.length,
|
|
359
|
+
standalonePlugins: hierarchical.standalonePlugins.length,
|
|
360
|
+
standaloneSkills: hierarchical.standaloneSkills.length,
|
|
361
|
+
},
|
|
362
|
+
hierarchical,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Log hierarchical summary to stderr
|
|
367
|
+
*/
|
|
368
|
+
function logHierarchicalSummary(results, hierarchical, logger) {
|
|
369
|
+
const status = calculateOverallStatus(results);
|
|
370
|
+
const skillsWithIssues = countAllSkills(hierarchical);
|
|
371
|
+
const totalSkills = results.length;
|
|
372
|
+
if (status === 'error') {
|
|
373
|
+
const errorCount = results.filter((r) => r.status === 'error').length;
|
|
374
|
+
logger.error(`Audit failed: ${errorCount} skill(s) with errors (${totalSkills} scanned, ${skillsWithIssues} with issues)`);
|
|
375
|
+
process.exit(1);
|
|
376
|
+
}
|
|
377
|
+
if (status === 'warning') {
|
|
378
|
+
const warningCount = results.filter((r) => r.status === 'warning').length;
|
|
379
|
+
logger.info(`Audit passed with warnings: ${warningCount} skill(s) (${totalSkills} scanned, ${skillsWithIssues} with issues)`);
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
logger.info(`Audit successful: ${totalSkills} skill(s) passed`);
|
|
383
|
+
}
|
|
384
|
+
process.exit(0);
|
|
385
|
+
}
|
|
386
|
+
//# sourceMappingURL=audit.js.map
|