evalstate's picture
download
raw
3.93 kB
import fs from 'node:fs/promises';
import path from 'node:path';
import { logger } from '../utils/logger.js';
import { mimeFor } from './skill-uri.js';
import type { SkillCatalog, SkillResource, SkillResourceType } from './skill-types.js';
const INDEX_FILE = 'index.json';
interface IndexEntry {
name: unknown;
type: unknown;
description: unknown;
url: unknown;
digest?: unknown;
}
interface IndexJson {
skills?: unknown;
}
function emptyCatalog(indexPath: string, indexText = ''): SkillCatalog {
return { indexPath, indexText, skills: [], compatibilityFiles: [] };
}
function isSkillResourceType(value: unknown): value is SkillResourceType {
return value === 'skill-md' || value === 'archive';
}
function resolveSkillUrl(rootDir: string, url: string): { absPath: string; relPath: string } | null {
const prefix = 'skill://';
if (!url.startsWith(prefix) || url.includes('?') || url.includes('#')) {
return null;
}
const encodedPath = url.slice(prefix.length);
if (!encodedPath) return null;
let parts: string[];
try {
parts = encodedPath.split('/').map((part) => decodeURIComponent(part));
} catch {
return null;
}
if (parts.some((part) => !part || part === '.' || part === '..' || path.isAbsolute(part))) {
return null;
}
const absRoot = path.resolve(rootDir);
const absPath = path.resolve(absRoot, ...parts);
const relative = path.relative(absRoot, absPath);
if (relative.startsWith('..') || path.isAbsolute(relative)) {
return null;
}
return { absPath, relPath: parts.join('/') };
}
async function loadIndexEntry(rootDir: string, entry: IndexEntry): Promise<SkillResource | null> {
if (
typeof entry.name !== 'string' ||
!isSkillResourceType(entry.type) ||
typeof entry.description !== 'string' ||
typeof entry.url !== 'string'
) {
logger.warn({ entry }, 'invalid skill index entry, skipping');
return null;
}
const resolved = resolveSkillUrl(rootDir, entry.url);
if (!resolved) {
logger.warn({ url: entry.url }, 'invalid skill resource URL, skipping');
return null;
}
try {
const stat = await fs.lstat(resolved.absPath);
if (!stat.isFile() || stat.isSymbolicLink()) {
logger.warn({ path: resolved.absPath }, 'skill resource is not a regular file, skipping');
return null;
}
} catch (err) {
logger.warn({ path: resolved.absPath, err }, 'skill resource missing, skipping');
return null;
}
const { mimeType, isText } = mimeFor(resolved.relPath);
return {
name: entry.name,
type: entry.type,
description: entry.description,
url: entry.url,
digest: typeof entry.digest === 'string' ? entry.digest : undefined,
absPath: resolved.absPath,
mimeType,
isText,
};
}
export async function loadSkills(rootDir: string): Promise<SkillCatalog> {
const indexPath = path.join(rootDir, INDEX_FILE);
let indexText: string;
try {
indexText = await fs.readFile(indexPath, 'utf8');
} catch (err) {
logger.warn({ indexPath, err }, 'skills index not found, skills disabled');
return emptyCatalog(indexPath);
}
let parsed: IndexJson;
try {
parsed = JSON.parse(indexText) as IndexJson;
} catch (err) {
logger.warn({ indexPath, err }, 'skills index is invalid JSON, skills disabled');
return emptyCatalog(indexPath, indexText);
}
if (!Array.isArray(parsed.skills)) {
logger.warn({ indexPath }, 'skills index missing skills array, skills disabled');
return emptyCatalog(indexPath, indexText);
}
const skills: SkillResource[] = [];
const seenUrls = new Set<string>();
for (const entry of parsed.skills as IndexEntry[]) {
const skill = await loadIndexEntry(rootDir, entry);
if (!skill) continue;
if (seenUrls.has(skill.url)) {
logger.warn({ url: skill.url }, 'duplicate skill resource URL, skipping');
continue;
}
seenUrls.add(skill.url);
skills.push(skill);
}
logger.info({ rootDir, count: skills.length }, 'loaded skills');
return { indexPath, indexText, skills, compatibilityFiles: [] };
}

Xet Storage Details

Size:
3.93 kB
·
Xet hash:
a42914031a1edb62b6a41fec922d84236d531bc1660376f0bcacd0f56aa9d9a5

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.