skilld 0.13.3 → 0.13.4

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.
@@ -10,8 +10,9 @@ import { gt } from "semver";
10
10
  import { ofetch } from "ofetch";
11
11
  import { crawlAndGenerate } from "@mdream/crawl";
12
12
  import { globby } from "globby";
13
- import pLimit from "p-limit";
13
+ import { downloadTemplate } from "giget";
14
14
  import { fileURLToPath, pathToFileURL } from "node:url";
15
+ import pLimit from "p-limit";
15
16
  import { Writable } from "node:stream";
16
17
  import { resolvePathSync } from "mlly";
17
18
  const BOT_USERS = new Set([
@@ -1119,11 +1120,20 @@ function parseSkillFrontmatterName(content) {
1119
1120
  description: fm.description
1120
1121
  };
1121
1122
  }
1122
- const SUPPORTING_DIRS = [
1123
- "scripts",
1124
- "references",
1125
- "assets"
1126
- ];
1123
+ function collectFiles(dir, prefix = "") {
1124
+ const files = [];
1125
+ if (!existsSync(dir)) return files;
1126
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
1127
+ const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
1128
+ const fullPath = resolve(dir, entry.name);
1129
+ if (entry.isDirectory()) files.push(...collectFiles(fullPath, relPath));
1130
+ else if (entry.isFile()) files.push({
1131
+ path: relPath,
1132
+ content: readFileSync(fullPath, "utf-8")
1133
+ });
1134
+ }
1135
+ return files;
1136
+ }
1127
1137
  async function fetchGitSkills(source, onProgress) {
1128
1138
  if (source.type === "local") return fetchLocalSkills(source);
1129
1139
  if (source.type === "github") return fetchGitHubSkills(source, onProgress);
@@ -1153,18 +1163,7 @@ function readLocalSkill(dir, repoPath) {
1153
1163
  const frontmatter = parseSkillFrontmatterName(content);
1154
1164
  const dirName = dir.split("/").pop();
1155
1165
  const name = frontmatter.name || dirName;
1156
- const files = [];
1157
- for (const subdir of SUPPORTING_DIRS) {
1158
- const subdirPath = resolve(dir, subdir);
1159
- if (!existsSync(subdirPath)) continue;
1160
- for (const file of readdirSync(subdirPath, { withFileTypes: true })) {
1161
- if (!file.isFile()) continue;
1162
- files.push({
1163
- path: `${subdir}/${file.name}`,
1164
- content: readFileSync(resolve(subdirPath, file.name), "utf-8")
1165
- });
1166
- }
1167
- }
1166
+ const files = collectFiles(dir).filter((f) => f.path !== "SKILL.md");
1168
1167
  return {
1169
1168
  name,
1170
1169
  description: frontmatter.description || "",
@@ -1177,120 +1176,110 @@ async function fetchGitHubSkills(source, onProgress) {
1177
1176
  const { owner, repo } = source;
1178
1177
  if (!owner || !repo) return { skills: [] };
1179
1178
  const ref = source.ref || "main";
1180
- onProgress?.(`Listing files at ${owner}/${repo}@${ref}`);
1181
- const data = await $fetch(`https://ungh.cc/repos/${owner}/${repo}/files/${ref}`).catch(() => null);
1182
- if (!data?.files?.length) {
1183
- if (ref === "main") {
1184
- const fallback = await $fetch(`https://ungh.cc/repos/${owner}/${repo}/files/master`).catch(() => null);
1185
- if (fallback?.files?.length) return extractGitHubSkills(owner, repo, "master", fallback, source.skillPath, onProgress);
1186
- }
1187
- return { skills: [] };
1179
+ const refs = ref === "main" ? ["main", "master"] : [ref];
1180
+ for (const tryRef of refs) {
1181
+ const skills = await downloadGitHubSkills(owner, repo, tryRef, source.skillPath, onProgress);
1182
+ if (skills.length > 0) return { skills };
1188
1183
  }
1189
- return extractGitHubSkills(owner, repo, ref, data, source.skillPath, onProgress);
1190
- }
1191
- async function extractGitHubSkills(owner, repo, ref, data, skillPath, onProgress) {
1192
- const allFiles = data.files.map((f) => f.path);
1193
- const commitSha = data.meta?.sha;
1194
- let skillMdPaths;
1195
- if (skillPath) {
1196
- const candidates = [`${skillPath}/SKILL.md`, skillPath.endsWith("/SKILL.md") ? skillPath : null].filter(Boolean);
1197
- skillMdPaths = allFiles.filter((f) => candidates.includes(f));
1198
- } else skillMdPaths = allFiles.filter((f) => f.match(/^skills\/[^/]+\/SKILL\.md$/) || f === "SKILL.md");
1199
- if (skillMdPaths.length === 0) return {
1200
- skills: [],
1201
- commitSha
1202
- };
1203
- const limit = pLimit(5);
1204
- const skills = [];
1205
- onProgress?.(`Found ${skillMdPaths.length} skill(s), downloading...`);
1206
- await Promise.all(skillMdPaths.map((mdPath) => limit(async () => {
1207
- const skillDir = mdPath === "SKILL.md" ? "" : mdPath.replace(/\/SKILL\.md$/, "");
1208
- const content = await fetchRawGitHub(owner, repo, ref, mdPath);
1209
- if (!content) return;
1210
- const frontmatter = parseSkillFrontmatterName(content);
1211
- const dirName = skillDir ? skillDir.split("/").pop() : repo;
1212
- const name = frontmatter.name || dirName;
1213
- const supportingFiles = [];
1214
- const prefix = skillDir ? `${skillDir}/` : "";
1215
- for (const subdir of SUPPORTING_DIRS) {
1216
- const subdirPrefix = `${prefix}${subdir}/`;
1217
- const matching = allFiles.filter((f) => f.startsWith(subdirPrefix));
1218
- for (const filePath of matching) {
1219
- const fileContent = await fetchRawGitHub(owner, repo, ref, filePath);
1220
- if (fileContent) {
1221
- const relativePath = filePath.slice(prefix.length);
1222
- supportingFiles.push({
1223
- path: relativePath,
1224
- content: fileContent
1225
- });
1226
- }
1184
+ return { skills: [] };
1185
+ }
1186
+ async function downloadGitHubSkills(owner, repo, ref, skillPath, onProgress) {
1187
+ const tempDir = join(tmpdir(), `skilld-${Date.now()}`);
1188
+ try {
1189
+ if (skillPath) {
1190
+ onProgress?.(`Downloading ${owner}/${repo}/${skillPath}@${ref}`);
1191
+ const { dir } = await downloadTemplate(`github:${owner}/${repo}/${skillPath}#${ref}`, {
1192
+ dir: tempDir,
1193
+ force: true
1194
+ });
1195
+ const skill = readLocalSkill(dir, skillPath);
1196
+ return skill ? [skill] : [];
1197
+ }
1198
+ onProgress?.(`Downloading ${owner}/${repo}/skills@${ref}`);
1199
+ try {
1200
+ const { dir } = await downloadTemplate(`github:${owner}/${repo}/skills#${ref}`, {
1201
+ dir: tempDir,
1202
+ force: true
1203
+ });
1204
+ const skills = [];
1205
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
1206
+ if (!entry.isDirectory()) continue;
1207
+ const skill = readLocalSkill(resolve(dir, entry.name), `skills/${entry.name}`);
1208
+ if (skill) skills.push(skill);
1209
+ }
1210
+ if (skills.length > 0) {
1211
+ onProgress?.(`Found ${skills.length} skill(s)`);
1212
+ return skills;
1227
1213
  }
1214
+ } catch {}
1215
+ const content = await $fetch(`https://raw.githubusercontent.com/${owner}/${repo}/${ref}/SKILL.md`, { responseType: "text" }).catch(() => null);
1216
+ if (content) {
1217
+ const fm = parseSkillFrontmatterName(content);
1218
+ onProgress?.("Found 1 skill");
1219
+ return [{
1220
+ name: fm.name || repo,
1221
+ description: fm.description || "",
1222
+ path: "",
1223
+ content,
1224
+ files: []
1225
+ }];
1228
1226
  }
1229
- skills.push({
1230
- name,
1231
- description: frontmatter.description || "",
1232
- path: skillDir,
1233
- content,
1234
- files: supportingFiles
1227
+ return [];
1228
+ } catch {
1229
+ return [];
1230
+ } finally {
1231
+ rmSync(tempDir, {
1232
+ recursive: true,
1233
+ force: true
1235
1234
  });
1236
- })));
1237
- return {
1238
- skills,
1239
- commitSha
1240
- };
1241
- }
1242
- async function fetchRawGitHub(owner, repo, ref, path) {
1243
- return $fetch(`https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${path}`, { responseType: "text" }).catch(() => null);
1235
+ }
1244
1236
  }
1245
1237
  async function fetchGitLabSkills(source, onProgress) {
1246
1238
  const { owner, repo } = source;
1247
1239
  if (!owner || !repo) return { skills: [] };
1248
1240
  const ref = source.ref || "main";
1249
- const projectId = encodeURIComponent(`${owner}/${repo}`);
1250
- onProgress?.(`Listing files at ${owner}/${repo}@${ref}`);
1251
- const tree = await $fetch(`https://gitlab.com/api/v4/projects/${projectId}/repository/tree?ref=${ref}&recursive=true&per_page=100`).catch(() => null);
1252
- if (!tree?.length) return { skills: [] };
1253
- const allFiles = tree.filter((e) => e.type === "blob").map((e) => e.path);
1254
- const skillMdPaths = source.skillPath ? allFiles.filter((f) => f === `${source.skillPath}/SKILL.md`) : allFiles.filter((f) => f.match(/^skills\/[^/]+\/SKILL\.md$/) || f === "SKILL.md");
1255
- if (skillMdPaths.length === 0) return { skills: [] };
1256
- const limit = pLimit(5);
1257
- const skills = [];
1258
- onProgress?.(`Found ${skillMdPaths.length} skill(s), downloading...`);
1259
- await Promise.all(skillMdPaths.map((mdPath) => limit(async () => {
1260
- const skillDir = mdPath === "SKILL.md" ? "" : mdPath.replace(/\/SKILL\.md$/, "");
1261
- const content = await fetchRawGitLab(owner, repo, ref, mdPath);
1262
- if (!content) return;
1263
- const frontmatter = parseSkillFrontmatterName(content);
1264
- const dirName = skillDir ? skillDir.split("/").pop() : repo;
1265
- const name = frontmatter.name || dirName;
1266
- const supportingFiles = [];
1267
- const prefix = skillDir ? `${skillDir}/` : "";
1268
- for (const subdir of SUPPORTING_DIRS) {
1269
- const subdirPrefix = `${prefix}${subdir}/`;
1270
- const matching = allFiles.filter((f) => f.startsWith(subdirPrefix));
1271
- for (const filePath of matching) {
1272
- const fileContent = await fetchRawGitLab(owner, repo, ref, filePath);
1273
- if (fileContent) {
1274
- const relativePath = filePath.slice(prefix.length);
1275
- supportingFiles.push({
1276
- path: relativePath,
1277
- content: fileContent
1278
- });
1279
- }
1280
- }
1241
+ const tempDir = join(tmpdir(), `skilld-gitlab-${Date.now()}`);
1242
+ try {
1243
+ const subdir = source.skillPath || "skills";
1244
+ onProgress?.(`Downloading ${owner}/${repo}/${subdir}@${ref}`);
1245
+ const { dir } = await downloadTemplate(`gitlab:${owner}/${repo}/${subdir}#${ref}`, {
1246
+ dir: tempDir,
1247
+ force: true
1248
+ });
1249
+ if (source.skillPath) {
1250
+ const skill = readLocalSkill(dir, source.skillPath);
1251
+ return { skills: skill ? [skill] : [] };
1281
1252
  }
1282
- skills.push({
1283
- name,
1284
- description: frontmatter.description || "",
1285
- path: skillDir,
1286
- content,
1287
- files: supportingFiles
1253
+ const skills = [];
1254
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
1255
+ if (!entry.isDirectory()) continue;
1256
+ const skill = readLocalSkill(resolve(dir, entry.name), `skills/${entry.name}`);
1257
+ if (skill) skills.push(skill);
1258
+ }
1259
+ if (skills.length > 0) {
1260
+ onProgress?.(`Found ${skills.length} skill(s)`);
1261
+ return { skills };
1262
+ }
1263
+ const content = await $fetch(`https://gitlab.com/${owner}/${repo}/-/raw/${ref}/SKILL.md`, { responseType: "text" }).catch(() => null);
1264
+ if (content) {
1265
+ const fm = parseSkillFrontmatterName(content);
1266
+ return { skills: [{
1267
+ name: fm.name || repo,
1268
+ description: fm.description || "",
1269
+ path: "",
1270
+ content,
1271
+ files: []
1272
+ }] };
1273
+ }
1274
+ return { skills: [] };
1275
+ } catch {
1276
+ return { skills: [] };
1277
+ } finally {
1278
+ rmSync(tempDir, {
1279
+ recursive: true,
1280
+ force: true
1288
1281
  });
1289
- })));
1290
- return { skills };
1291
- }
1292
- async function fetchRawGitLab(owner, repo, ref, path) {
1293
- return $fetch(`https://gitlab.com/${owner}/${repo}/-/raw/${ref}/${path}`, { responseType: "text" }).catch(() => null);
1282
+ }
1294
1283
  }
1295
1284
  async function fetchLlmsUrl(docsUrl) {
1296
1285
  const llmsUrl = `${new URL(docsUrl).origin}/llms.txt`;