ali-skills 0.0.32 → 0.1.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.
@@ -0,0 +1,309 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync } from "fs";
2
+ import { join, normalize, resolve, sep } from "path";
3
+ const ENV_URLS = {
4
+ local: "http://localhost:3000/api/skills",
5
+ daily: "https://next-ai-base.fn.taobao.net/api/skills",
6
+ pre: "https://pre-ali-skills.alibaba-inc.com/api/skills",
7
+ prod: "https://ali-skills.alibaba-inc.com/api/skills"
8
+ };
9
+ function resolveSkillsApiBaseUrl() {
10
+ const env = process.env.SKILLS_API_URL?.trim();
11
+ if (!env) return ENV_URLS.prod;
12
+ if (env in ENV_URLS) return ENV_URLS[env];
13
+ return env.replace(/\/+$/, "");
14
+ }
15
+ const SKILLS_API_BASE_URL = resolveSkillsApiBaseUrl();
16
+ const GITLAB_ACCOUNT = {
17
+ user: process.env.GITLAB_USER ?? "fliggy-edith",
18
+ token: process.env.GITLAB_TOKEN ?? "rXn7iEvs1JYtKkE44fzj"
19
+ };
20
+ const MANIFEST_DIR_TO_AGENT = {
21
+ ".claude-plugin": "claude-code",
22
+ ".codex-plugin": "codex",
23
+ ".cursor-plugin": "cursor"
24
+ };
25
+ const MANIFEST_DIR_NAMES = Object.keys(MANIFEST_DIR_TO_AGENT);
26
+ const MAX_SCAN_DEPTH = 3;
27
+ const SCAN_SKIP_DIRS = new Set([
28
+ ".git",
29
+ "node_modules",
30
+ "__pycache__",
31
+ ".agents"
32
+ ]);
33
+ function isContainedIn(target, base) {
34
+ const nb = normalize(resolve(base));
35
+ const nt = normalize(resolve(target));
36
+ return nt === nb || nt.startsWith(nb + sep);
37
+ }
38
+ function detectDeclaredAgents(dir) {
39
+ const agents = [];
40
+ for (const [manifestDir, agent] of Object.entries(MANIFEST_DIR_TO_AGENT)) if (existsSync(join(dir, manifestDir, "plugin.json"))) agents.push(agent);
41
+ return agents;
42
+ }
43
+ function scanForFirstPackageRoot(baseDir, depth) {
44
+ if (depth > MAX_SCAN_DEPTH) return null;
45
+ if (detectDeclaredAgents(baseDir).length > 0) return baseDir;
46
+ let entries;
47
+ try {
48
+ entries = readdirSync(baseDir).sort();
49
+ } catch {
50
+ return null;
51
+ }
52
+ for (const name of entries) {
53
+ if (SCAN_SKIP_DIRS.has(name) || MANIFEST_DIR_NAMES.includes(name)) continue;
54
+ const child = join(baseDir, name);
55
+ try {
56
+ if (statSync(child).isDirectory()) {
57
+ const hit = scanForFirstPackageRoot(child, depth + 1);
58
+ if (hit) return hit;
59
+ }
60
+ } catch {}
61
+ }
62
+ return null;
63
+ }
64
+ function findPluginPackageRoots(baseDir, opts = {}) {
65
+ const base = resolve(baseDir);
66
+ if (opts.subpath) {
67
+ const root = join(base, opts.subpath);
68
+ if (!isContainedIn(root, base)) throw new Error(`Unsafe subpath escapes repo root: ${opts.subpath}`);
69
+ const declaredAgents = detectDeclaredAgents(root);
70
+ if (declaredAgents.length === 0) throw new Error(`No plugin manifest (.X-plugin/plugin.json) found at subpath: ${opts.subpath}`);
71
+ return [{
72
+ root,
73
+ declaredAgents
74
+ }];
75
+ }
76
+ const rootAgents = detectDeclaredAgents(base);
77
+ if (rootAgents.length > 0) return [{
78
+ root: base,
79
+ declaredAgents: rootAgents
80
+ }];
81
+ const root = scanForFirstPackageRoot(base, 0);
82
+ if (root) return [{
83
+ root,
84
+ declaredAgents: detectDeclaredAgents(root)
85
+ }];
86
+ return [];
87
+ }
88
+ const AGENT_REFERENCE_FIELDS = {
89
+ "claude-code": [
90
+ {
91
+ field: "skills",
92
+ kind: "dir"
93
+ },
94
+ {
95
+ field: "commands",
96
+ kind: "file"
97
+ },
98
+ {
99
+ field: "agents",
100
+ kind: "file"
101
+ },
102
+ {
103
+ field: "hooks",
104
+ kind: "file"
105
+ },
106
+ {
107
+ field: "mcpServers",
108
+ kind: "mcp"
109
+ },
110
+ {
111
+ field: "lspServers",
112
+ kind: "file"
113
+ }
114
+ ],
115
+ codex: [
116
+ {
117
+ field: "skills",
118
+ kind: "dir"
119
+ },
120
+ {
121
+ field: "hooks",
122
+ kind: "file"
123
+ },
124
+ {
125
+ field: "mcpServers",
126
+ kind: "mcp"
127
+ },
128
+ {
129
+ field: "apps",
130
+ kind: "file"
131
+ }
132
+ ],
133
+ cursor: [
134
+ {
135
+ field: "rules",
136
+ kind: "dir"
137
+ },
138
+ {
139
+ field: "skills",
140
+ kind: "dir"
141
+ },
142
+ {
143
+ field: "agents",
144
+ kind: "file"
145
+ },
146
+ {
147
+ field: "commands",
148
+ kind: "file"
149
+ },
150
+ {
151
+ field: "hooks",
152
+ kind: "file"
153
+ },
154
+ {
155
+ field: "mcpServers",
156
+ kind: "mcp"
157
+ }
158
+ ]
159
+ };
160
+ const AGENT_MCP_FILENAME = {
161
+ "claude-code": ".mcp.json",
162
+ codex: ".mcp.json",
163
+ cursor: "mcp.json"
164
+ };
165
+ const AGENT_TO_MANIFEST_DIR = Object.fromEntries(Object.entries(MANIFEST_DIR_TO_AGENT).map(([dir, agent]) => [agent, dir]));
166
+ const OPTIONAL_MISSING_REF_FIELDS = new Set([
167
+ "agents",
168
+ "commands",
169
+ "rules",
170
+ "apps",
171
+ "lspServers"
172
+ ]);
173
+ function isWithin(target, base) {
174
+ const nb = normalize(resolve(base));
175
+ const nt = normalize(resolve(target));
176
+ return nt === nb || nt.startsWith(nb + sep);
177
+ }
178
+ function toArray(v) {
179
+ if (typeof v === "string") return [v];
180
+ if (Array.isArray(v)) return v.filter((x) => typeof x === "string");
181
+ return [];
182
+ }
183
+ function validateRef(pkgRoot, agent, ref, manifest, errors) {
184
+ const raw = manifest[ref.field];
185
+ if (raw === void 0) return;
186
+ if (ref.kind === "mcp") {
187
+ if (typeof raw === "object" && !Array.isArray(raw)) return;
188
+ for (const p of toArray(raw)) {
189
+ const full = join(pkgRoot, p);
190
+ if (!isWithin(full, pkgRoot)) {
191
+ errors.push(`${agent}.${ref.field} 路径越界包根: ${p}`);
192
+ continue;
193
+ }
194
+ if (!existsSync(full)) {
195
+ if (OPTIONAL_MISSING_REF_FIELDS.has(ref.field)) continue;
196
+ errors.push(`${agent}.${ref.field} 引用文件不存在: ${p}`);
197
+ continue;
198
+ }
199
+ const base = p.split("/").pop() ?? "";
200
+ const expected = AGENT_MCP_FILENAME[agent];
201
+ if (expected && base !== expected && (base === ".mcp.json" || base === "mcp.json")) errors.push(`${agent}.${ref.field} mcp 文件名应为 ${expected},实际 ${base}`);
202
+ }
203
+ return;
204
+ }
205
+ for (const p of toArray(raw)) {
206
+ const full = join(pkgRoot, p);
207
+ if (!isWithin(full, pkgRoot)) {
208
+ errors.push(`${agent}.${ref.field} 路径越界包根: ${p}`);
209
+ continue;
210
+ }
211
+ if (!existsSync(full)) {
212
+ if (OPTIONAL_MISSING_REF_FIELDS.has(ref.field)) continue;
213
+ errors.push(`${agent}.${ref.field} 引用路径不存在: ${p}`);
214
+ continue;
215
+ }
216
+ const st = statSync(full);
217
+ if (ref.kind === "dir" && !st.isDirectory()) errors.push(`${agent}.${ref.field} 期望目录,实际非目录: ${p}`);
218
+ }
219
+ }
220
+ function validateAgentManifest(pkgRoot, agent) {
221
+ const errors = [];
222
+ const manifestDir = AGENT_TO_MANIFEST_DIR[agent];
223
+ if (!manifestDir) return {
224
+ agent,
225
+ valid: false,
226
+ errors: [`未知 agent: ${agent}`]
227
+ };
228
+ const manifestPath = join(pkgRoot, manifestDir, "plugin.json");
229
+ if (!existsSync(manifestPath)) return {
230
+ agent,
231
+ valid: false,
232
+ errors: [`缺少 ${manifestDir}/plugin.json`]
233
+ };
234
+ let manifest;
235
+ try {
236
+ manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
237
+ } catch (err) {
238
+ return {
239
+ agent,
240
+ valid: false,
241
+ errors: [`plugin.json JSON 解析失败: ${err.message}`]
242
+ };
243
+ }
244
+ if (typeof manifest.name !== "string" || manifest.name.trim() === "") errors.push(`${agent} plugin.json 缺少必填字段 name`);
245
+ for (const ref of AGENT_REFERENCE_FIELDS[agent] ?? []) validateRef(pkgRoot, agent, ref, manifest, errors);
246
+ return {
247
+ agent,
248
+ valid: errors.length === 0,
249
+ errors,
250
+ manifest
251
+ };
252
+ }
253
+ function listSkillNames(pkgRoot) {
254
+ const dir = join(pkgRoot, "skills");
255
+ if (!existsSync(dir)) return [];
256
+ const out = [];
257
+ for (const name of readdirSync(dir)) try {
258
+ if (statSync(join(dir, name)).isDirectory() && existsSync(join(dir, name, "SKILL.md"))) out.push(name);
259
+ } catch {}
260
+ return out;
261
+ }
262
+ function readMcpServerNames(pkgRoot) {
263
+ for (const fn of [".mcp.json", "mcp.json"]) {
264
+ const f = join(pkgRoot, fn);
265
+ if (!existsSync(f)) continue;
266
+ try {
267
+ const data = JSON.parse(readFileSync(f, "utf-8"));
268
+ const servers = data.mcpServers ?? data.mcp_servers ?? data;
269
+ if (servers && typeof servers === "object") return Object.keys(servers);
270
+ } catch {}
271
+ }
272
+ return [];
273
+ }
274
+ function summarizeComponents(pkgRoot) {
275
+ return {
276
+ skills: listSkillNames(pkgRoot),
277
+ agents: listFlatNames(join(pkgRoot, "agents")),
278
+ commands: listFlatNames(join(pkgRoot, "commands")),
279
+ hooks: existsSync(join(pkgRoot, "hooks", "hooks.json")),
280
+ mcpServers: readMcpServerNames(pkgRoot),
281
+ rules: listFlatNames(join(pkgRoot, "rules")),
282
+ apps: existsSync(join(pkgRoot, ".app.json")) ? ["(.app.json)"] : [],
283
+ lsp: existsSync(join(pkgRoot, ".lsp.json")) ? ["(.lsp.json)"] : []
284
+ };
285
+ }
286
+ function listFlatNames(dir) {
287
+ if (!existsSync(dir)) return [];
288
+ try {
289
+ return readdirSync(dir).filter((n) => n.endsWith(".md") || n.endsWith(".mdc")).map((n) => n.replace(/\.(md|mdc)$/, ""));
290
+ } catch {
291
+ return [];
292
+ }
293
+ }
294
+ function validatePluginPackage(pkgRoot, declaredAgents) {
295
+ const perAgent = [];
296
+ const manifests = {};
297
+ for (const agent of declaredAgents) {
298
+ const v = validateAgentManifest(pkgRoot, agent);
299
+ perAgent.push(v);
300
+ if (v.valid && v.manifest) manifests[agent] = v.manifest;
301
+ }
302
+ return {
303
+ targetAgents: perAgent.filter((v) => v.valid).map((v) => v.agent),
304
+ perAgent,
305
+ manifests,
306
+ components: summarizeComponents(pkgRoot)
307
+ };
308
+ }
309
+ export { SKILLS_API_BASE_URL as i, findPluginPackageRoots as n, GITLAB_ACCOUNT as r, validatePluginPackage as t };