@framers/agentos-skills-registry 0.2.0 → 0.2.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/CONTRIBUTING.md +225 -0
- package/README.md +120 -49
- package/dist/catalog.d.ts +5 -6
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +36 -206
- package/dist/catalog.js.map +1 -1
- package/dist/index.d.ts +12 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -14
- package/dist/index.js.map +1 -1
- package/dist/schema-types.d.ts +79 -0
- package/dist/schema-types.d.ts.map +1 -0
- package/dist/schema-types.js +11 -0
- package/dist/schema-types.js.map +1 -0
- package/dist/workspace-discovery.d.ts +84 -0
- package/dist/workspace-discovery.d.ts.map +1 -0
- package/dist/workspace-discovery.js +211 -0
- package/dist/workspace-discovery.js.map +1 -0
- package/package.json +18 -8
- package/registry/community/.gitkeep +0 -0
- package/registry/curated/1password/SKILL.md +53 -0
- package/registry/curated/account-manager/SKILL.md +60 -0
- package/registry/curated/apple-notes/SKILL.md +45 -0
- package/registry/curated/apple-reminders/SKILL.md +46 -0
- package/registry/curated/blog-publisher/SKILL.md +110 -0
- package/registry/curated/bluesky-bot/SKILL.md +93 -0
- package/registry/curated/cloud-ops/SKILL.md +124 -0
- package/registry/curated/coding-agent/SKILL.md +40 -0
- package/registry/curated/content-creator/SKILL.md +53 -0
- package/registry/curated/deep-research/SKILL.md +56 -0
- package/registry/curated/discord-helper/SKILL.md +43 -0
- package/registry/curated/facebook-bot/SKILL.md +94 -0
- package/registry/curated/git/SKILL.md +39 -0
- package/registry/curated/github/SKILL.md +54 -0
- package/registry/curated/healthcheck/SKILL.md +43 -0
- package/registry/curated/image-gen/SKILL.md +44 -0
- package/registry/curated/instagram-bot/SKILL.md +60 -0
- package/registry/curated/linkedin-bot/SKILL.md +86 -0
- package/registry/curated/mastodon-bot/SKILL.md +104 -0
- package/registry/curated/notion/SKILL.md +43 -0
- package/registry/curated/obsidian/SKILL.md +42 -0
- package/registry/curated/pinterest-bot/SKILL.md +45 -0
- package/registry/curated/reddit-bot/SKILL.md +62 -0
- package/registry/curated/seo-campaign/SKILL.md +51 -0
- package/registry/curated/site-deploy/SKILL.md +119 -0
- package/registry/curated/slack-helper/SKILL.md +43 -0
- package/registry/curated/social-broadcast/SKILL.md +145 -0
- package/registry/curated/spotify-player/SKILL.md +45 -0
- package/registry/curated/summarize/SKILL.md +40 -0
- package/registry/curated/threads-bot/SKILL.md +82 -0
- package/registry/curated/tiktok-bot/SKILL.md +104 -0
- package/registry/curated/trello/SKILL.md +44 -0
- package/registry/curated/twitter-bot/SKILL.md +63 -0
- package/registry/curated/weather/SKILL.md +37 -0
- package/registry/curated/web-scraper/SKILL.md +60 -0
- package/registry/curated/web-search/SKILL.md +49 -0
- package/registry/curated/whisper-transcribe/SKILL.md +58 -0
- package/registry/curated/youtube-bot/SKILL.md +104 -0
- package/registry.json +1426 -0
- package/scripts/update-registry.mjs +126 -0
- package/scripts/validate-skill.mjs +298 -0
- package/types.d.ts +77 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Regenerate skills registry.json from SKILL.md files.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import YAML from 'yaml';
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = path.dirname(__filename);
|
|
14
|
+
const packageRoot = path.join(__dirname, '..');
|
|
15
|
+
const registryPath = path.join(packageRoot, 'registry.json');
|
|
16
|
+
const curatedRoot = path.join(packageRoot, 'registry', 'curated');
|
|
17
|
+
const communityRoot = path.join(packageRoot, 'registry', 'community');
|
|
18
|
+
|
|
19
|
+
function readFrontmatter(filePath) {
|
|
20
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
21
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
22
|
+
if (!match) return {};
|
|
23
|
+
try {
|
|
24
|
+
const parsed = YAML.parse(match[1]);
|
|
25
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
26
|
+
} catch {
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function listSkillDirs(root) {
|
|
32
|
+
if (!fs.existsSync(root)) return [];
|
|
33
|
+
return fs
|
|
34
|
+
.readdirSync(root, { withFileTypes: true })
|
|
35
|
+
.filter((d) => d.isDirectory() && fs.existsSync(path.join(root, d.name, 'SKILL.md')))
|
|
36
|
+
.map((d) => d.name)
|
|
37
|
+
.sort();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function cleanObject(obj) {
|
|
41
|
+
const out = {};
|
|
42
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
43
|
+
if (v === undefined) continue;
|
|
44
|
+
if (Array.isArray(v) && v.length === 0) continue;
|
|
45
|
+
if (v && typeof v === 'object' && !Array.isArray(v) && Object.keys(v).length === 0) continue;
|
|
46
|
+
out[k] = v;
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function toStringArray(value) {
|
|
52
|
+
if (!Array.isArray(value)) return [];
|
|
53
|
+
return value.filter((v) => typeof v === 'string' && v.trim().length > 0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function slugToDisplayName(slug) {
|
|
57
|
+
return slug
|
|
58
|
+
.split(/[-_]/g)
|
|
59
|
+
.filter(Boolean)
|
|
60
|
+
.map((part) => part[0].toUpperCase() + part.slice(1))
|
|
61
|
+
.join(' ');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function buildEntry(name, source, updatedAt) {
|
|
65
|
+
const skillPath = path.join(packageRoot, 'registry', source, name, 'SKILL.md');
|
|
66
|
+
const fm = readFrontmatter(skillPath);
|
|
67
|
+
const metadata = fm?.metadata?.agentos && typeof fm.metadata.agentos === 'object'
|
|
68
|
+
? fm.metadata.agentos
|
|
69
|
+
: undefined;
|
|
70
|
+
|
|
71
|
+
return cleanObject({
|
|
72
|
+
id: source === 'curated' ? `com.framers.skill.${name}` : `com.community.skill.${name}`,
|
|
73
|
+
name,
|
|
74
|
+
displayName:
|
|
75
|
+
typeof fm.name === 'string' && fm.name.trim().length > 0
|
|
76
|
+
? fm.name
|
|
77
|
+
: slugToDisplayName(name),
|
|
78
|
+
version: typeof fm.version === 'string' && fm.version ? fm.version : '1.0.0',
|
|
79
|
+
path: `registry/${source}/${name}`,
|
|
80
|
+
description: typeof fm.description === 'string' ? fm.description : '',
|
|
81
|
+
category: typeof fm.category === 'string' && fm.category ? fm.category : 'uncategorized',
|
|
82
|
+
namespace: typeof fm.namespace === 'string' && fm.namespace ? fm.namespace : 'wunderland',
|
|
83
|
+
verified: source === 'curated',
|
|
84
|
+
source,
|
|
85
|
+
verifiedAt: source === 'curated' ? updatedAt : undefined,
|
|
86
|
+
keywords: toStringArray(fm.tags),
|
|
87
|
+
requiredSecrets: toStringArray(fm.requires_secrets),
|
|
88
|
+
requiredTools: toStringArray(fm.requires_tools),
|
|
89
|
+
metadata: metadata && typeof metadata === 'object' ? metadata : undefined,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function generateRegistry() {
|
|
94
|
+
const updated = new Date().toISOString();
|
|
95
|
+
const curatedNames = listSkillDirs(curatedRoot);
|
|
96
|
+
const communityNames = listSkillDirs(communityRoot);
|
|
97
|
+
|
|
98
|
+
const curated = curatedNames.map((name) => buildEntry(name, 'curated', updated));
|
|
99
|
+
const community = communityNames.map((name) => buildEntry(name, 'community', updated));
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
version: '1.0.0',
|
|
103
|
+
updated,
|
|
104
|
+
categories: {
|
|
105
|
+
curated: curatedNames,
|
|
106
|
+
community: communityNames,
|
|
107
|
+
},
|
|
108
|
+
skills: {
|
|
109
|
+
curated,
|
|
110
|
+
community,
|
|
111
|
+
},
|
|
112
|
+
stats: {
|
|
113
|
+
totalSkills: curated.length + community.length,
|
|
114
|
+
curatedCount: curated.length,
|
|
115
|
+
communityCount: community.length,
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const registry = generateRegistry();
|
|
121
|
+
fs.writeFileSync(registryPath, `${JSON.stringify(registry, null, 2)}\n`);
|
|
122
|
+
|
|
123
|
+
console.log('✅ Skills registry updated');
|
|
124
|
+
console.log(` curated: ${registry.stats.curatedCount}`);
|
|
125
|
+
console.log(` community: ${registry.stats.communityCount}`);
|
|
126
|
+
console.log(` total: ${registry.stats.totalSkills}`);
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SKILL.md validator for @framers/agentos-skills
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* node scripts/validate-skill.mjs registry/community/my-skill/SKILL.md
|
|
8
|
+
* node scripts/validate-skill.mjs # validates ALL skills
|
|
9
|
+
*
|
|
10
|
+
* Exit code 0 on pass, 1 on fail.
|
|
11
|
+
* Zero external dependencies — uses only Node.js built-ins.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs';
|
|
15
|
+
import { resolve, basename, dirname, join } from 'node:path';
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Constants
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
const VALID_CATEGORIES = new Set([
|
|
22
|
+
'information',
|
|
23
|
+
'developer-tools',
|
|
24
|
+
'communication',
|
|
25
|
+
'productivity',
|
|
26
|
+
'devops',
|
|
27
|
+
'media',
|
|
28
|
+
'security',
|
|
29
|
+
'creative',
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
const VALID_NAMESPACES = new Set(['wunderland', 'community']);
|
|
33
|
+
|
|
34
|
+
const REQUIRED_FIELDS = ['name', 'version', 'description', 'author', 'namespace', 'category', 'tags'];
|
|
35
|
+
|
|
36
|
+
const MAX_DESCRIPTION_LENGTH = 200;
|
|
37
|
+
|
|
38
|
+
/** Patterns that suggest accidentally-committed secrets. */
|
|
39
|
+
const SECRET_PATTERNS = [
|
|
40
|
+
/(?:api[_-]?key|apikey)\s*[:=]\s*["']?[A-Za-z0-9_\-]{16,}/i,
|
|
41
|
+
/(?:secret|token|password|passwd|pwd)\s*[:=]\s*["']?[A-Za-z0-9_\-]{16,}/i,
|
|
42
|
+
/sk-[A-Za-z0-9]{20,}/, // OpenAI-style keys
|
|
43
|
+
/ghp_[A-Za-z0-9]{36,}/, // GitHub personal access tokens
|
|
44
|
+
/gho_[A-Za-z0-9]{36,}/, // GitHub OAuth tokens
|
|
45
|
+
/xox[bpors]-[A-Za-z0-9\-]{10,}/, // Slack tokens
|
|
46
|
+
/AKIA[0-9A-Z]{16}/, // AWS access key IDs
|
|
47
|
+
/-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY-----/, // Private keys
|
|
48
|
+
/Bearer\s+[A-Za-z0-9_\-\.]{20,}/, // Bearer tokens
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Simple YAML frontmatter parser (no dependencies)
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Parse basic YAML key-value pairs from frontmatter lines.
|
|
57
|
+
* Handles:
|
|
58
|
+
* key: value -> string
|
|
59
|
+
* key: 'value' -> string (strip quotes)
|
|
60
|
+
* key: "value" -> string (strip quotes)
|
|
61
|
+
* key: [a, b, c] -> string[]
|
|
62
|
+
* key: [] -> string[] (empty)
|
|
63
|
+
*
|
|
64
|
+
* Nested keys (like metadata.agentos.emoji) are ignored — only top-level
|
|
65
|
+
* keys matter for required-field validation.
|
|
66
|
+
*/
|
|
67
|
+
function parseSimpleYaml(yamlText) {
|
|
68
|
+
const result = {};
|
|
69
|
+
const lines = yamlText.split('\n');
|
|
70
|
+
|
|
71
|
+
for (const line of lines) {
|
|
72
|
+
// Skip blank lines and comments
|
|
73
|
+
const trimmed = line.trimStart();
|
|
74
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
75
|
+
|
|
76
|
+
// Only parse top-level keys (no leading whitespace)
|
|
77
|
+
if (line[0] === ' ' || line[0] === '\t') continue;
|
|
78
|
+
|
|
79
|
+
const colonIndex = trimmed.indexOf(':');
|
|
80
|
+
if (colonIndex === -1) continue;
|
|
81
|
+
|
|
82
|
+
const key = trimmed.slice(0, colonIndex).trim();
|
|
83
|
+
let value = trimmed.slice(colonIndex + 1).trim();
|
|
84
|
+
|
|
85
|
+
if (!key) continue;
|
|
86
|
+
|
|
87
|
+
// Inline array: [a, b, c]
|
|
88
|
+
if (value.startsWith('[') && value.endsWith(']')) {
|
|
89
|
+
const inner = value.slice(1, -1).trim();
|
|
90
|
+
if (inner === '') {
|
|
91
|
+
result[key] = [];
|
|
92
|
+
} else {
|
|
93
|
+
result[key] = inner.split(',').map((s) => s.trim().replace(/^['"]|['"]$/g, ''));
|
|
94
|
+
}
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Strip surrounding quotes
|
|
99
|
+
if ((value.startsWith("'") && value.endsWith("'")) || (value.startsWith('"') && value.endsWith('"'))) {
|
|
100
|
+
value = value.slice(1, -1);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
result[key] = value;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// Validation
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Validate a single SKILL.md file. Returns an object with:
|
|
115
|
+
* { path, passed, errors[] }
|
|
116
|
+
*/
|
|
117
|
+
function validateSkill(filePath) {
|
|
118
|
+
const errors = [];
|
|
119
|
+
const absolutePath = resolve(filePath);
|
|
120
|
+
|
|
121
|
+
// --- Read file --------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
let content;
|
|
124
|
+
try {
|
|
125
|
+
content = readFileSync(absolutePath, 'utf-8');
|
|
126
|
+
} catch (err) {
|
|
127
|
+
return { path: absolutePath, passed: false, errors: [`Cannot read file: ${err.message}`] };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// --- Split frontmatter / body ----------------------------------------
|
|
131
|
+
|
|
132
|
+
const fmRegex = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
133
|
+
const match = content.match(fmRegex);
|
|
134
|
+
|
|
135
|
+
if (!match) {
|
|
136
|
+
return {
|
|
137
|
+
path: absolutePath,
|
|
138
|
+
passed: false,
|
|
139
|
+
errors: ['File does not contain valid YAML frontmatter (expected --- delimiters).'],
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const yamlText = match[1];
|
|
144
|
+
const markdownBody = match[2];
|
|
145
|
+
|
|
146
|
+
// --- Parse YAML -------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
const meta = parseSimpleYaml(yamlText);
|
|
149
|
+
|
|
150
|
+
// --- Required fields --------------------------------------------------
|
|
151
|
+
|
|
152
|
+
for (const field of REQUIRED_FIELDS) {
|
|
153
|
+
if (meta[field] === undefined || meta[field] === '') {
|
|
154
|
+
errors.push(`Missing required field: ${field}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// --- Category ---------------------------------------------------------
|
|
159
|
+
|
|
160
|
+
if (meta.category && !VALID_CATEGORIES.has(meta.category)) {
|
|
161
|
+
errors.push(
|
|
162
|
+
`Invalid category "${meta.category}". Must be one of: ${[...VALID_CATEGORIES].join(', ')}`,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// --- Namespace --------------------------------------------------------
|
|
167
|
+
|
|
168
|
+
if (meta.namespace && !VALID_NAMESPACES.has(meta.namespace)) {
|
|
169
|
+
errors.push(
|
|
170
|
+
`Invalid namespace "${meta.namespace}". Must be one of: ${[...VALID_NAMESPACES].join(', ')}`,
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// --- Description length -----------------------------------------------
|
|
175
|
+
|
|
176
|
+
if (meta.description && typeof meta.description === 'string' && meta.description.length > MAX_DESCRIPTION_LENGTH) {
|
|
177
|
+
errors.push(
|
|
178
|
+
`Description is ${meta.description.length} characters (max ${MAX_DESCRIPTION_LENGTH}).`,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// --- Tags -------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
if (meta.tags !== undefined) {
|
|
185
|
+
if (!Array.isArray(meta.tags)) {
|
|
186
|
+
errors.push('tags must be an array.');
|
|
187
|
+
} else if (meta.tags.length === 0) {
|
|
188
|
+
errors.push('tags array must contain at least 1 tag.');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// --- Name matches directory -------------------------------------------
|
|
193
|
+
|
|
194
|
+
const dirName = basename(dirname(absolutePath));
|
|
195
|
+
if (meta.name && meta.name !== dirName) {
|
|
196
|
+
errors.push(
|
|
197
|
+
`Skill name "${meta.name}" does not match directory name "${dirName}".`,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// --- Markdown body not empty ------------------------------------------
|
|
202
|
+
|
|
203
|
+
if (!markdownBody || markdownBody.trim().length === 0) {
|
|
204
|
+
errors.push('Markdown body is empty. Include usage instructions, Examples, and Constraints.');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// --- Secret pattern scan ----------------------------------------------
|
|
208
|
+
|
|
209
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
210
|
+
if (pattern.test(content)) {
|
|
211
|
+
errors.push(`Possible secret or credential detected (pattern: ${pattern.source}). Remove it before submitting.`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return { path: absolutePath, passed: errors.length === 0, errors };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
// Discover all SKILL.md files
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
|
|
222
|
+
function discoverSkills(rootDir) {
|
|
223
|
+
const skills = [];
|
|
224
|
+
const tiers = ['curated', 'community'];
|
|
225
|
+
|
|
226
|
+
for (const tier of tiers) {
|
|
227
|
+
const tierDir = join(rootDir, 'registry', tier);
|
|
228
|
+
if (!existsSync(tierDir)) continue;
|
|
229
|
+
|
|
230
|
+
for (const entry of readdirSync(tierDir)) {
|
|
231
|
+
const skillDir = join(tierDir, entry);
|
|
232
|
+
if (!statSync(skillDir).isDirectory()) continue;
|
|
233
|
+
|
|
234
|
+
const skillFile = join(skillDir, 'SKILL.md');
|
|
235
|
+
if (existsSync(skillFile)) {
|
|
236
|
+
skills.push(skillFile);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return skills;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
// Main
|
|
246
|
+
// ---------------------------------------------------------------------------
|
|
247
|
+
|
|
248
|
+
function main() {
|
|
249
|
+
const args = process.argv.slice(2);
|
|
250
|
+
|
|
251
|
+
let filesToValidate;
|
|
252
|
+
|
|
253
|
+
if (args.length === 0) {
|
|
254
|
+
// Validate all skills
|
|
255
|
+
const rootDir = resolve(dirname(new URL(import.meta.url).pathname), '..');
|
|
256
|
+
filesToValidate = discoverSkills(rootDir);
|
|
257
|
+
if (filesToValidate.length === 0) {
|
|
258
|
+
console.log('No SKILL.md files found in registry/curated/ or registry/community/.');
|
|
259
|
+
process.exit(0);
|
|
260
|
+
}
|
|
261
|
+
console.log(`Validating ${filesToValidate.length} skill(s)...\n`);
|
|
262
|
+
} else {
|
|
263
|
+
filesToValidate = [resolve(args[0])];
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
let allPassed = true;
|
|
267
|
+
|
|
268
|
+
for (const filePath of filesToValidate) {
|
|
269
|
+
const result = validateSkill(filePath);
|
|
270
|
+
|
|
271
|
+
if (result.passed) {
|
|
272
|
+
console.log(` PASS ${result.path}`);
|
|
273
|
+
} else {
|
|
274
|
+
allPassed = false;
|
|
275
|
+
console.log(` FAIL ${result.path}`);
|
|
276
|
+
for (const err of result.errors) {
|
|
277
|
+
console.log(` - ${err}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
console.log('');
|
|
283
|
+
|
|
284
|
+
if (allPassed) {
|
|
285
|
+
console.log(`All ${filesToValidate.length} skill(s) passed validation.`);
|
|
286
|
+
process.exit(0);
|
|
287
|
+
} else {
|
|
288
|
+
const failCount = filesToValidate.length - filesToValidate.filter((_, i) => {
|
|
289
|
+
// Re-validate to count (cheap for small set)
|
|
290
|
+
return validateSkill(filesToValidate[i]).passed;
|
|
291
|
+
}).length;
|
|
292
|
+
// Simpler: just fail
|
|
293
|
+
console.log('Validation failed. Fix the errors above and re-run.');
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
main();
|
package/types.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type declarations for @framers/agentos-skills-registry catalog.
|
|
3
|
+
* @module @framers/agentos-skills-registry
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type SkillInstallKind = "brew" | "apt" | "node" | "go" | "uv" | "download";
|
|
7
|
+
|
|
8
|
+
export interface SkillInstallSpec {
|
|
9
|
+
id?: string;
|
|
10
|
+
kind: SkillInstallKind;
|
|
11
|
+
label?: string;
|
|
12
|
+
bins?: string[];
|
|
13
|
+
os?: readonly string[];
|
|
14
|
+
formula?: string;
|
|
15
|
+
package?: string;
|
|
16
|
+
module?: string;
|
|
17
|
+
url?: string;
|
|
18
|
+
archive?: string;
|
|
19
|
+
extract?: boolean;
|
|
20
|
+
stripComponents?: number;
|
|
21
|
+
targetDir?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface SkillRequirements {
|
|
25
|
+
bins?: string[];
|
|
26
|
+
anyBins?: string[];
|
|
27
|
+
env?: string[];
|
|
28
|
+
config?: string[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface SkillMetadata {
|
|
32
|
+
always?: boolean;
|
|
33
|
+
skillKey?: string;
|
|
34
|
+
primaryEnv?: string;
|
|
35
|
+
emoji?: string;
|
|
36
|
+
homepage?: string;
|
|
37
|
+
os?: readonly string[];
|
|
38
|
+
requires?: SkillRequirements;
|
|
39
|
+
install?: SkillInstallSpec[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface SkillCatalogEntry {
|
|
43
|
+
id: string;
|
|
44
|
+
name: string;
|
|
45
|
+
version: string;
|
|
46
|
+
path: string;
|
|
47
|
+
description: string;
|
|
48
|
+
verified: boolean;
|
|
49
|
+
source?: 'curated' | 'community';
|
|
50
|
+
verifiedAt?: string;
|
|
51
|
+
keywords?: string[];
|
|
52
|
+
metadata?: SkillMetadata;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface SkillsRegistryStats {
|
|
56
|
+
totalSkills: number;
|
|
57
|
+
curatedCount: number;
|
|
58
|
+
communityCount: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface SkillsRegistry {
|
|
62
|
+
version: string;
|
|
63
|
+
updated: string;
|
|
64
|
+
categories: {
|
|
65
|
+
curated: string[];
|
|
66
|
+
community: string[];
|
|
67
|
+
};
|
|
68
|
+
skills: {
|
|
69
|
+
curated: SkillCatalogEntry[];
|
|
70
|
+
community: SkillCatalogEntry[];
|
|
71
|
+
};
|
|
72
|
+
stats: SkillsRegistryStats;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
declare const registry: SkillsRegistry;
|
|
76
|
+
export default registry;
|
|
77
|
+
|