114 lines
3.2 KiB
JavaScript
114 lines
3.2 KiB
JavaScript
/**
|
|
* ClawdHub skill scanning
|
|
*
|
|
* Fetches all available skills from the ClawdHub registry
|
|
* and transforms them into SkillsCatalogItem format.
|
|
*/
|
|
|
|
import { fetchClawdHubSkills } from './api.js';
|
|
|
|
const MAX_PAGES = 20; // Safety limit to prevent infinite loops
|
|
const CLAWDHUB_PAGE_LIMIT = 25;
|
|
|
|
const mapClawdHubItem = (item) => {
|
|
const latestVersion = item.tags?.latest || item.latestVersion?.version || '1.0.0';
|
|
|
|
return {
|
|
sourceId: 'clawdhub',
|
|
repoSource: 'clawdhub:registry',
|
|
repoSubpath: null,
|
|
gitIdentityId: null,
|
|
skillDir: item.slug,
|
|
skillName: item.slug,
|
|
frontmatterName: item.displayName || item.slug,
|
|
description: item.summary || null,
|
|
installable: true,
|
|
warnings: [],
|
|
// ClawdHub-specific metadata
|
|
clawdhub: {
|
|
slug: item.slug,
|
|
version: latestVersion,
|
|
displayName: item.displayName,
|
|
owner: item.owner?.handle || null,
|
|
downloads: item.stats?.downloads || 0,
|
|
stars: item.stats?.stars || 0,
|
|
versionsCount: item.stats?.versions || 1,
|
|
createdAt: item.createdAt,
|
|
updatedAt: item.updatedAt,
|
|
},
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Scan ClawdHub registry for all available skills
|
|
* @returns {Promise<{ ok: boolean, items?: Array, error?: Object }>}
|
|
*/
|
|
export async function scanClawdHub() {
|
|
try {
|
|
const allItems = [];
|
|
let cursor = null;
|
|
|
|
for (let page = 0; page < MAX_PAGES; page++) {
|
|
let items = [];
|
|
let nextCursor = null;
|
|
|
|
try {
|
|
const pageResult = await fetchClawdHubSkills({ cursor });
|
|
items = pageResult.items || [];
|
|
nextCursor = pageResult.nextCursor || null;
|
|
} catch (error) {
|
|
if (page > 0 && allItems.length > 0) {
|
|
console.warn('ClawdHub pagination failed; returning partial results.');
|
|
break;
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
for (const item of items) {
|
|
allItems.push(mapClawdHubItem(item));
|
|
}
|
|
|
|
if (!nextCursor) {
|
|
break;
|
|
}
|
|
cursor = nextCursor;
|
|
}
|
|
|
|
// Sort by downloads (most popular first)
|
|
allItems.sort((a, b) => (b.clawdhub?.downloads || 0) - (a.clawdhub?.downloads || 0));
|
|
|
|
return { ok: true, items: allItems };
|
|
} catch (error) {
|
|
console.error('ClawdHub scan error:', error);
|
|
return {
|
|
ok: false,
|
|
error: {
|
|
kind: 'networkError',
|
|
message: error instanceof Error ? error.message : 'Failed to fetch skills from ClawdHub',
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Scan a single ClawdHub page (cursor-based)
|
|
* @returns {Promise<{ ok: boolean, items?: Array, nextCursor?: string | null, error?: Object }>}
|
|
*/
|
|
export async function scanClawdHubPage({ cursor } = {}) {
|
|
try {
|
|
const { items, nextCursor } = await fetchClawdHubSkills({ cursor });
|
|
const mapped = (items || []).map(mapClawdHubItem).slice(0, CLAWDHUB_PAGE_LIMIT);
|
|
mapped.sort((a, b) => (b.clawdhub?.downloads || 0) - (a.clawdhub?.downloads || 0));
|
|
return { ok: true, items: mapped, nextCursor: nextCursor || null };
|
|
} catch (error) {
|
|
console.error('ClawdHub page scan error:', error);
|
|
return {
|
|
ok: false,
|
|
error: {
|
|
kind: 'networkError',
|
|
message: error instanceof Error ? error.message : 'Failed to fetch skills from ClawdHub',
|
|
},
|
|
};
|
|
}
|
|
}
|