sync: malware page, security alerts, review diagnostics, skill detail improvements

- Add malware security advisory page and SecurityAlertBanner component
- Add review diagnostics API and reviewed stats page
- Improve skill detail page with better scoring display and metadata
- Update review API endpoints with enhanced filtering and stats
- Add skill file serving improvements and cache enhancements
- Remove legacy curation scripts (moved to internal tooling)
- Update CLI search with score filtering support

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-31 16:08:12 +02:00
parent 3e22d2e238
commit 16ee9e3beb
33 changed files with 1231 additions and 3404 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "skillhub",
"version": "0.2.9",
"version": "0.2.11",
"description": "CLI tool for managing AI Agent skills - search, install, and update skills for Claude, Codex, Copilot, and more",
"author": "SkillHub Contributors",
"license": "MIT",

View File

@@ -39,6 +39,14 @@ export async function install(skillId: string, options: InstallOptions): Promise
try {
skillInfo = await getSkill(skillId);
if (skillInfo) {
// Block installation of malicious skills
if (skillInfo.isMalicious) {
spinner.fail(chalk.red('This skill has been flagged as malicious (contains malware).'));
console.log(chalk.red('Installation blocked for your safety.'));
console.log(chalk.dim(`See: https://skills.palebluedot.live/en/skill/${skillId}`));
process.exit(1);
}
const reviewBadge = skillInfo.aiScore && skillInfo.reviewStatus === 'ai-reviewed'
? chalk.dim(` | AI: ${skillInfo.aiScore}/100`)
: skillInfo.reviewStatus === 'verified'

View File

@@ -57,14 +57,15 @@ export async function search(query: string, options: SearchOptions): Promise<voi
`${num} ${verified} ${chalk.cyan(skill.id.padEnd(38))} ${security}`
);
// Second line: AI score (if reviewed) + downloads + stars + description
// Second line: AI score + downloads + stars + description
const aiScore = skill.aiScore;
const hasAiScore = aiScore != null && aiScore > 0 && skill.reviewStatus && skill.reviewStatus !== 'unreviewed' && skill.reviewStatus !== 'auto-scored';
const aiPrefix = hasAiScore
? `${chalk.magenta('AI')} ${(aiScore >= 75 ? chalk.green : aiScore >= 50 ? chalk.yellow : chalk.dim)(String(aiScore))} `
const isAiReviewed = aiScore != null && aiScore > 0 && skill.reviewStatus && skill.reviewStatus !== 'unreviewed' && skill.reviewStatus !== 'auto-scored';
const showAiScore = isAiReviewed;
const aiPrefix = showAiScore
? `${chalk.magenta('AI')} ${(aiScore >= 75 ? chalk.green : aiScore >= 50 ? chalk.yellow : chalk.dim)(String(aiScore).padStart(2))} `
: '';
console.log(
` ${aiPrefix}${formatNumber(skill.downloadCount).padStart(6)}${formatNumber(skill.githubStars).padStart(6)} ${chalk.dim(skill.description.slice(0, hasAiScore ? 45 : 55))}${skill.description.length > (hasAiScore ? 45 : 55) ? '...' : ''}`
` ${aiPrefix}${formatNumber(skill.downloadCount).padStart(6)}${formatNumber(skill.githubStars).padStart(6)} ${chalk.dim(skill.description.slice(0, showAiScore ? 45 : 55))}${skill.description.length > (showAiScore ? 45 : 55) ? '...' : ''}`
);
// Third line: Rating (only if ratingCount >= 3)

View File

@@ -128,6 +128,7 @@ export interface SkillInfo {
ratingCount?: number | null;
reviewStatus?: string | null;
aiScore?: number | null;
isMalicious?: boolean;
isVerified: boolean;
compatibility: {
platforms: string[];