@aiwg/cli 2026.7.20 → 2026.7.21
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 +4 -4
- package/dist/src/api/index.d.ts +1 -0
- package/dist/src/api/index.js +1 -0
- package/dist/src/artifacts/browser-export.js +7 -0
- package/dist/src/artifacts/citation-parser.js +96 -35
- package/dist/src/artifacts/index-builder.js +54 -17
- package/dist/src/artifacts/state-transfer.js +27 -0
- package/dist/src/artifacts/stats.js +8 -0
- package/dist/src/cli/cli-extension-loader.js +73 -0
- package/dist/src/cli/handlers/index.js +3 -1
- package/dist/src/cli/handlers/sessions.js +966 -0
- package/dist/src/cli/handlers/skill-lint.js +49 -45
- package/dist/src/cli/handlers/use.js +143 -60
- package/dist/src/cli/handlers/utilities.js +22 -8
- package/dist/src/cli/skill-usage.js +146 -24
- package/dist/src/extensions/commands/definitions.js +29 -0
- package/dist/src/extensions/manifest.js +29 -0
- package/dist/src/sessions/adapters/claude.js +357 -0
- package/dist/src/sessions/adapters/codex.js +521 -0
- package/dist/src/sessions/adapters/copilot.js +226 -0
- package/dist/src/sessions/adapters/cursor.js +372 -0
- package/dist/src/sessions/adapters/factory.js +345 -0
- package/dist/src/sessions/adapters/generic.js +225 -0
- package/dist/src/sessions/adapters/hermes.js +341 -0
- package/dist/src/sessions/adapters/openclaw.js +381 -0
- package/dist/src/sessions/adapters/opencode.js +454 -0
- package/dist/src/sessions/adapters/openhuman.js +315 -0
- package/dist/src/sessions/adapters/warp.js +160 -0
- package/dist/src/sessions/adapters/windsurf.js +212 -0
- package/dist/src/sessions/candidates.js +210 -0
- package/dist/src/sessions/contracts.js +310 -0
- package/dist/src/sessions/discovery.js +51 -0
- package/dist/src/sessions/fixtures.js +12 -0
- package/dist/src/sessions/importer.js +315 -0
- package/dist/src/sessions/index.js +25 -0
- package/dist/src/sessions/knowledge-shard.js +61 -0
- package/dist/src/sessions/optional-backends.js +238 -0
- package/dist/src/sessions/policy.js +192 -0
- package/dist/src/sessions/ports.js +2 -0
- package/dist/src/sessions/promotion.js +367 -0
- package/dist/src/sessions/readers.js +176 -0
- package/dist/src/sessions/repository.js +1551 -0
- package/dist/src/skills/adapters/agent-skills.js +59 -0
- package/dist/src/skills/adapters/local.js +19 -1
- package/dist/src/skills/agent-skills.js +249 -0
- package/dist/src/skills/cli.js +463 -7
- package/dist/src/skills/deployer.js +554 -0
- package/dist/src/skills/doctor.js +105 -0
- package/dist/src/skills/exporter.js +382 -0
- package/dist/src/skills/importer.js +921 -0
- package/dist/src/skills/registry.js +19 -0
- package/dist/src/skills/validator.js +323 -0
- package/package.json +2 -2
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @implements #539
|
|
8
8
|
*/
|
|
9
|
+
import { AgentSkillsAdapter } from './adapters/agent-skills.js';
|
|
9
10
|
import { LocalAdapter } from './adapters/local.js';
|
|
10
11
|
import { ClawHubAdapter } from './adapters/clawhub.js';
|
|
11
12
|
import { OpenClawAdapter } from './adapters/openclaw.js';
|
|
@@ -14,9 +15,27 @@ import { OpenClawAdapter } from './adapters/openclaw.js';
|
|
|
14
15
|
*/
|
|
15
16
|
const ALL_ADAPTERS = [
|
|
16
17
|
new LocalAdapter(),
|
|
18
|
+
new AgentSkillsAdapter(),
|
|
17
19
|
new ClawHubAdapter(),
|
|
18
20
|
new OpenClawAdapter(),
|
|
19
21
|
];
|
|
22
|
+
/**
|
|
23
|
+
* Import a standard Agent Skills directory or pinned Git source.
|
|
24
|
+
*/
|
|
25
|
+
export async function importSkillSource(source, options, providerId = 'agentskills') {
|
|
26
|
+
const adapter = getAdapter(providerId);
|
|
27
|
+
if (!adapter) {
|
|
28
|
+
throw new Error(`Unknown registry: ${providerId}`);
|
|
29
|
+
}
|
|
30
|
+
if (!adapter.importSource) {
|
|
31
|
+
throw new Error(`Registry '${providerId}' does not support source import`);
|
|
32
|
+
}
|
|
33
|
+
const available = await adapter.isAvailable();
|
|
34
|
+
if (!available) {
|
|
35
|
+
throw new Error(`Registry '${providerId}' is not available`);
|
|
36
|
+
}
|
|
37
|
+
return adapter.importSource(source, options);
|
|
38
|
+
}
|
|
20
39
|
/**
|
|
21
40
|
* Get adapter by ID
|
|
22
41
|
*/
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Agent Skills parser and conformance validator.
|
|
3
|
+
*
|
|
4
|
+
* This module is the only place that parses SKILL.md for Agent Skills
|
|
5
|
+
* conformance. Consumers may add their own quality or deployment policy, but
|
|
6
|
+
* must retain these diagnostics unchanged.
|
|
7
|
+
*
|
|
8
|
+
* @implements #1878
|
|
9
|
+
*/
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { parseDocument } from 'yaml';
|
|
13
|
+
import { AGENT_SKILLS_BASELINE, AGENT_SKILL_VALIDATION_PROFILES, AIWG_SKILL_CONTROL_FIELDS, STANDARD_SKILL_FIELDS, validateCompatibleAgentSkillMetadata, } from './agent-skills.js';
|
|
14
|
+
const FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
|
|
15
|
+
const MARKDOWN_LINK = /!?\[[^\]]*]\(\s*(?:<([^>]+)>|([^\s)]+))(?:\s+["'][^)]*["'])?\s*\)/g;
|
|
16
|
+
const STANDARD_FIELDS = new Set(STANDARD_SKILL_FIELDS);
|
|
17
|
+
const AIWG_FIELDS = new Set(AIWG_SKILL_CONTROL_FIELDS);
|
|
18
|
+
function diagnostic(code, severity, file, yamlPath, message, remediation) {
|
|
19
|
+
return {
|
|
20
|
+
code,
|
|
21
|
+
severity,
|
|
22
|
+
file,
|
|
23
|
+
yamlPath,
|
|
24
|
+
message,
|
|
25
|
+
upstreamBaseline: AGENT_SKILLS_BASELINE.revision,
|
|
26
|
+
remediation,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function sortDiagnostics(diagnostics) {
|
|
30
|
+
return diagnostics.sort((left, right) => (left.file.localeCompare(right.file)
|
|
31
|
+
|| left.code.localeCompare(right.code)
|
|
32
|
+
|| left.yamlPath.localeCompare(right.yamlPath)
|
|
33
|
+
|| left.message.localeCompare(right.message)));
|
|
34
|
+
}
|
|
35
|
+
function normalizeMetadataDiagnostics(diagnostics, frontmatter, profile, file) {
|
|
36
|
+
const policy = AGENT_SKILL_VALIDATION_PROFILES[profile];
|
|
37
|
+
const normalized = diagnostics.flatMap((item) => {
|
|
38
|
+
if (item.code === 'AS_FIELD_UNKNOWN') {
|
|
39
|
+
return [{
|
|
40
|
+
...item,
|
|
41
|
+
severity: policy.unknownField,
|
|
42
|
+
}];
|
|
43
|
+
}
|
|
44
|
+
if (profile === 'discovery'
|
|
45
|
+
&& (item.code === 'AS_NAME_FORMAT' || item.code === 'AS_NAME_DIRECTORY')) {
|
|
46
|
+
return [{ ...item, severity: policy.cosmeticNameDefect }];
|
|
47
|
+
}
|
|
48
|
+
return [item];
|
|
49
|
+
});
|
|
50
|
+
if (!policy.recognizedAiwgFields) {
|
|
51
|
+
for (const key of Object.keys(frontmatter).sort()) {
|
|
52
|
+
if (!AIWG_FIELDS.has(key))
|
|
53
|
+
continue;
|
|
54
|
+
normalized.push(diagnostic('AS_FIELD_EXTENSION', 'error', file, `$.${key}`, `AIWG extension field "${key}" is not allowed by the strict profile`, 'Remove the extension field or validate with the compatible profile.'));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return normalized;
|
|
58
|
+
}
|
|
59
|
+
function resourceDiagnostics(content, file, skillRoot, checkResources) {
|
|
60
|
+
const diagnostics = [];
|
|
61
|
+
const references = new Set();
|
|
62
|
+
for (const match of content.matchAll(MARKDOWN_LINK)) {
|
|
63
|
+
const raw = (match[1] ?? match[2] ?? '').trim();
|
|
64
|
+
if (raw.length === 0
|
|
65
|
+
|| raw.startsWith('#')
|
|
66
|
+
|| /^(?:https?|mailto|data):/i.test(raw)) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const withoutFragment = raw.split('#', 1)[0] ?? '';
|
|
70
|
+
const decoded = (() => {
|
|
71
|
+
try {
|
|
72
|
+
return decodeURIComponent(withoutFragment);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return withoutFragment;
|
|
76
|
+
}
|
|
77
|
+
})();
|
|
78
|
+
references.add(decoded);
|
|
79
|
+
}
|
|
80
|
+
for (const reference of [...references].sort()) {
|
|
81
|
+
const normalized = reference.replaceAll('\\', '/');
|
|
82
|
+
if (path.isAbsolute(reference)
|
|
83
|
+
|| normalized === '..'
|
|
84
|
+
|| normalized.startsWith('../')
|
|
85
|
+
|| normalized.includes('/../')) {
|
|
86
|
+
diagnostics.push(diagnostic('AS_RESOURCE_PATH', 'warning', file, '$.body', `resource reference "${reference}" is not an in-skill relative path`, 'Reference a file relative to SKILL.md without parent traversal.'));
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const segments = normalized.split('/').filter(Boolean);
|
|
90
|
+
if (segments.length > 2) {
|
|
91
|
+
diagnostics.push(diagnostic('AS_ADVISORY_RESOURCE_DEPTH', 'warning', file, '$.body', `resource reference "${reference}" is deeper than one resource-directory level`, 'Prefer resources such as references/topic.md, scripts/run.sh, or assets/example.json.'));
|
|
92
|
+
}
|
|
93
|
+
if (!checkResources || !skillRoot)
|
|
94
|
+
continue;
|
|
95
|
+
const resolved = path.resolve(skillRoot, reference);
|
|
96
|
+
const relative = path.relative(skillRoot, resolved);
|
|
97
|
+
if (relative.startsWith('..')
|
|
98
|
+
|| path.isAbsolute(relative)
|
|
99
|
+
|| !fs.existsSync(resolved)) {
|
|
100
|
+
diagnostics.push(diagnostic('AS_RESOURCE_MISSING', 'warning', file, '$.body', `referenced resource "${reference}" does not exist`, 'Add the referenced file or correct the relative resource path.'));
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const stat = fs.lstatSync(resolved);
|
|
104
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
105
|
+
diagnostics.push(diagnostic('AS_RESOURCE_TYPE', 'warning', file, '$.body', `referenced resource "${reference}" is not a regular file`, 'Replace the reference target with an in-tree regular file.'));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return diagnostics;
|
|
109
|
+
}
|
|
110
|
+
function resultState(diagnostics, profile) {
|
|
111
|
+
const errors = diagnostics.filter((item) => item.severity === 'error');
|
|
112
|
+
if (errors.length > 0) {
|
|
113
|
+
if (profile === 'discovery'
|
|
114
|
+
&& errors.some((item) => (item.code === 'AS_DESCRIPTION_REQUIRED'
|
|
115
|
+
|| item.code === 'AS_FRONTMATTER_REQUIRED'
|
|
116
|
+
|| item.code === 'AS_YAML_PARSE'
|
|
117
|
+
|| item.code === 'AS_YAML_TYPE'))) {
|
|
118
|
+
return 'skipped';
|
|
119
|
+
}
|
|
120
|
+
return 'invalid';
|
|
121
|
+
}
|
|
122
|
+
return diagnostics.some((item) => item.severity === 'warning')
|
|
123
|
+
? 'warning'
|
|
124
|
+
: 'valid';
|
|
125
|
+
}
|
|
126
|
+
export function validateAgentSkillContent(content, options = {}) {
|
|
127
|
+
const profile = options.profile ?? 'compatible';
|
|
128
|
+
const file = options.file ?? 'SKILL.md';
|
|
129
|
+
const directoryName = options.directoryName
|
|
130
|
+
?? (options.skillRoot ? path.basename(options.skillRoot) : undefined)
|
|
131
|
+
?? (path.dirname(file) !== '.' ? path.basename(path.dirname(file)) : undefined);
|
|
132
|
+
const lines = content.split(/\r?\n/).length;
|
|
133
|
+
const metrics = {
|
|
134
|
+
lines,
|
|
135
|
+
// The upstream guidance is advisory. A deterministic UTF-16/4 estimate
|
|
136
|
+
// avoids a runtime tokenizer dependency while keeping CI snapshots stable.
|
|
137
|
+
estimatedTokens: Math.ceil(content.length / 4),
|
|
138
|
+
};
|
|
139
|
+
const diagnostics = [];
|
|
140
|
+
const match = FRONTMATTER.exec(content);
|
|
141
|
+
if (!match) {
|
|
142
|
+
diagnostics.push(diagnostic('AS_FRONTMATTER_REQUIRED', 'error', file, '$', 'SKILL.md must begin with YAML frontmatter', 'Add a leading YAML mapping delimited by `---` lines.'));
|
|
143
|
+
const state = resultState(diagnostics, profile);
|
|
144
|
+
return {
|
|
145
|
+
schemaVersion: 1,
|
|
146
|
+
profile,
|
|
147
|
+
file,
|
|
148
|
+
state,
|
|
149
|
+
valid: false,
|
|
150
|
+
discoverable: false,
|
|
151
|
+
body: content,
|
|
152
|
+
diagnostics,
|
|
153
|
+
metrics,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const document = parseDocument(match[1] ?? '', {
|
|
157
|
+
prettyErrors: false,
|
|
158
|
+
strict: true,
|
|
159
|
+
uniqueKeys: true,
|
|
160
|
+
});
|
|
161
|
+
if (document.errors.length > 0) {
|
|
162
|
+
for (const error of document.errors) {
|
|
163
|
+
diagnostics.push(diagnostic('AS_YAML_PARSE', 'error', file, '$', `SKILL.md frontmatter is invalid YAML: ${error.message}`, 'Correct the YAML syntax before validation or discovery.'));
|
|
164
|
+
}
|
|
165
|
+
const state = resultState(diagnostics, profile);
|
|
166
|
+
return {
|
|
167
|
+
schemaVersion: 1,
|
|
168
|
+
profile,
|
|
169
|
+
file,
|
|
170
|
+
state,
|
|
171
|
+
valid: false,
|
|
172
|
+
discoverable: false,
|
|
173
|
+
body: content.slice(match[0].length),
|
|
174
|
+
diagnostics: sortDiagnostics(diagnostics),
|
|
175
|
+
metrics,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
const parsed = document.toJS({ maxAliasCount: 100 });
|
|
179
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
180
|
+
diagnostics.push(diagnostic('AS_YAML_TYPE', 'error', file, '$', 'SKILL.md frontmatter must be a YAML mapping', 'Use top-level key/value fields.'));
|
|
181
|
+
const state = resultState(diagnostics, profile);
|
|
182
|
+
return {
|
|
183
|
+
schemaVersion: 1,
|
|
184
|
+
profile,
|
|
185
|
+
file,
|
|
186
|
+
state,
|
|
187
|
+
valid: false,
|
|
188
|
+
discoverable: false,
|
|
189
|
+
body: content.slice(match[0].length),
|
|
190
|
+
diagnostics,
|
|
191
|
+
metrics,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
const frontmatter = parsed;
|
|
195
|
+
diagnostics.push(...normalizeMetadataDiagnostics(validateCompatibleAgentSkillMetadata(frontmatter, directoryName
|
|
196
|
+
?? (typeof frontmatter['name'] === 'string' ? frontmatter['name'] : ''), file, lines), frontmatter, profile, file));
|
|
197
|
+
if (Object.prototype.hasOwnProperty.call(frontmatter, 'allowed-tools')) {
|
|
198
|
+
const value = frontmatter['allowed-tools'];
|
|
199
|
+
if (typeof value === 'string'
|
|
200
|
+
&& (value.trim() !== value || value.length === 0 || /\s{2,}|\t|\r|\n/.test(value))) {
|
|
201
|
+
diagnostics.push(diagnostic('AS_ALLOWED_TOOLS_FORMAT', 'error', file, '$.allowed-tools', 'allowed-tools must contain non-empty tool identifiers separated by single spaces', 'Normalize allowed-tools to a single space-delimited string.'));
|
|
202
|
+
}
|
|
203
|
+
diagnostics.push(diagnostic('AS_ALLOWED_TOOLS_EXPERIMENTAL', 'warning', file, '$.allowed-tools', 'allowed-tools is experimental in the pinned Agent Skills baseline', 'Treat allowed-tools as advisory unless the target provider documents enforcement.'));
|
|
204
|
+
}
|
|
205
|
+
const body = content.slice(match[0].length);
|
|
206
|
+
if (body.trim().length === 0) {
|
|
207
|
+
diagnostics.push(diagnostic('AS_BODY_REQUIRED', 'error', file, '$.body', 'SKILL.md must contain Markdown instructions after frontmatter', 'Add the skill instructions below the closing frontmatter delimiter.'));
|
|
208
|
+
}
|
|
209
|
+
if (metrics.estimatedTokens > 5_000) {
|
|
210
|
+
diagnostics.push(diagnostic('AS_ADVISORY_TOKENS', 'warning', file, '$', `SKILL.md is approximately ${metrics.estimatedTokens} tokens; the recommendation is at most 5,000`, 'Move detailed material to referenced resources.'));
|
|
211
|
+
}
|
|
212
|
+
diagnostics.push(...resourceDiagnostics(body, file, options.skillRoot, options.checkResources ?? Boolean(options.skillRoot)));
|
|
213
|
+
// Defensive policy check: a future field added to one allow-list must not
|
|
214
|
+
// silently escape classification in this parser.
|
|
215
|
+
for (const key of Object.keys(frontmatter).sort()) {
|
|
216
|
+
if (STANDARD_FIELDS.has(key) || AIWG_FIELDS.has(key))
|
|
217
|
+
continue;
|
|
218
|
+
if (!diagnostics.some((item) => item.yamlPath === `$.${key}`)) {
|
|
219
|
+
diagnostics.push(diagnostic('AS_FIELD_UNKNOWN', AGENT_SKILL_VALIDATION_PROFILES[profile].unknownField, file, `$.${key}`, `unrecognized top-level field "${key}"`, 'Remove the field or map it explicitly before granting it policy meaning.'));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
sortDiagnostics(diagnostics);
|
|
223
|
+
const state = resultState(diagnostics, profile);
|
|
224
|
+
return {
|
|
225
|
+
schemaVersion: 1,
|
|
226
|
+
profile,
|
|
227
|
+
file,
|
|
228
|
+
state,
|
|
229
|
+
valid: state === 'valid' || state === 'warning',
|
|
230
|
+
discoverable: state !== 'skipped',
|
|
231
|
+
frontmatter,
|
|
232
|
+
body,
|
|
233
|
+
diagnostics,
|
|
234
|
+
metrics,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
export function validateAgentSkillFile(filePath, options = {}) {
|
|
238
|
+
const resolved = path.resolve(filePath);
|
|
239
|
+
const stat = fs.lstatSync(resolved);
|
|
240
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
241
|
+
const content = '';
|
|
242
|
+
const result = validateAgentSkillContent(content, {
|
|
243
|
+
...options,
|
|
244
|
+
file: resolved,
|
|
245
|
+
skillRoot: path.dirname(resolved),
|
|
246
|
+
});
|
|
247
|
+
result.diagnostics = [diagnostic('AS_SKILL_FILE_TYPE', 'error', resolved, '$', 'SKILL.md must be a regular file, not a symbolic link or special file', 'Replace SKILL.md with an in-tree regular file.')];
|
|
248
|
+
result.state = options.profile === 'discovery' ? 'skipped' : 'invalid';
|
|
249
|
+
result.valid = false;
|
|
250
|
+
result.discoverable = false;
|
|
251
|
+
return result;
|
|
252
|
+
}
|
|
253
|
+
const result = validateAgentSkillContent(fs.readFileSync(resolved, 'utf8'), {
|
|
254
|
+
...options,
|
|
255
|
+
file: resolved,
|
|
256
|
+
directoryName: options.directoryName ?? path.basename(path.dirname(resolved)),
|
|
257
|
+
skillRoot: path.dirname(resolved),
|
|
258
|
+
checkResources: true,
|
|
259
|
+
});
|
|
260
|
+
if (path.basename(resolved) !== 'SKILL.md') {
|
|
261
|
+
result.diagnostics.push(diagnostic('AS_SKILL_FILENAME', 'error', resolved, '$', 'the skill entrypoint must be named SKILL.md', 'Rename the entrypoint to SKILL.md.'));
|
|
262
|
+
sortDiagnostics(result.diagnostics);
|
|
263
|
+
result.state = resultState(result.diagnostics, result.profile);
|
|
264
|
+
result.valid = false;
|
|
265
|
+
}
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
268
|
+
function collectSkillFiles(targetPath, recursive, files) {
|
|
269
|
+
const resolved = path.resolve(targetPath);
|
|
270
|
+
if (!fs.existsSync(resolved))
|
|
271
|
+
return;
|
|
272
|
+
const stat = fs.lstatSync(resolved);
|
|
273
|
+
if (stat.isSymbolicLink())
|
|
274
|
+
return;
|
|
275
|
+
if (stat.isFile()) {
|
|
276
|
+
if (path.basename(resolved) === 'SKILL.md')
|
|
277
|
+
files.add(resolved);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (!stat.isDirectory())
|
|
281
|
+
return;
|
|
282
|
+
const direct = path.join(resolved, 'SKILL.md');
|
|
283
|
+
if (fs.existsSync(direct) && !fs.lstatSync(direct).isSymbolicLink()) {
|
|
284
|
+
files.add(direct);
|
|
285
|
+
}
|
|
286
|
+
if (!recursive)
|
|
287
|
+
return;
|
|
288
|
+
for (const entry of fs.readdirSync(resolved, { withFileTypes: true })
|
|
289
|
+
.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
290
|
+
if (!entry.isDirectory()
|
|
291
|
+
|| entry.isSymbolicLink()
|
|
292
|
+
|| entry.name.startsWith('.')
|
|
293
|
+
|| entry.name === 'node_modules') {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
collectSkillFiles(path.join(resolved, entry.name), true, files);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
export function scanAgentSkillPaths(targetPaths, options = {}) {
|
|
300
|
+
const profile = options.profile ?? 'compatible';
|
|
301
|
+
const files = new Set();
|
|
302
|
+
for (const target of typeof targetPaths === 'string' ? [targetPaths] : targetPaths) {
|
|
303
|
+
collectSkillFiles(target, options.recursive ?? true, files);
|
|
304
|
+
}
|
|
305
|
+
const results = [...files]
|
|
306
|
+
.sort((left, right) => left.localeCompare(right))
|
|
307
|
+
.map((file) => validateAgentSkillFile(file, { profile }));
|
|
308
|
+
return {
|
|
309
|
+
schemaVersion: 1,
|
|
310
|
+
profile,
|
|
311
|
+
files: results,
|
|
312
|
+
summary: {
|
|
313
|
+
scanned: results.length,
|
|
314
|
+
valid: results.filter((result) => result.state === 'valid').length,
|
|
315
|
+
warnings: results.filter((result) => result.state === 'warning').length,
|
|
316
|
+
invalid: results.filter((result) => result.state === 'invalid').length,
|
|
317
|
+
skipped: results.filter((result) => result.state === 'skipped').length,
|
|
318
|
+
errors: results.reduce((count, result) => count
|
|
319
|
+
+ result.diagnostics.filter((item) => item.severity === 'error').length, 0),
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
//# sourceMappingURL=validator.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiwg/cli",
|
|
3
|
-
"version": "2026.7.
|
|
3
|
+
"version": "2026.7.21",
|
|
4
4
|
"description": "Lightweight AIWG CLI for signed, versioned web-backed resources.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"node": ">=20.0.0"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@fortemi/core": "2026.7.
|
|
59
|
+
"@fortemi/core": "2026.7.14",
|
|
60
60
|
"@modelcontextprotocol/sdk": "^1.24.0",
|
|
61
61
|
"chalk": "^4.1.2",
|
|
62
62
|
"chokidar": "^3.6.0",
|