claude-flow
Version:
Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration
204 lines (177 loc) • 4.6 kB
text/typescript
/**
* Registry API Client
* Secure integration with Claude Flow Cloud Functions
*
* Security:
* - HTTPS only
* - No credentials stored in code
* - Rate limiting respected
* - Input validation
*/
const REGISTRY_API_URL = 'https://us-central1-claude-flow.cloudfunctions.net/publish-registry';
export interface RatingResponse {
success: boolean;
itemId: string;
average: number;
count: number;
error?: string;
}
export interface BulkRatingsResponse {
[itemId: string]: {
average: number;
count: number;
};
}
export interface AnalyticsResponse {
downloads: Record<string, number>;
exports: number;
imports: number;
publishes: number;
}
/**
* Validate item ID to prevent injection
*/
function validateItemId(itemId: string): boolean {
// Only allow alphanumeric, @, /, -, _
return /^[@a-zA-Z0-9\/_-]+$/.test(itemId) && itemId.length < 100;
}
/**
* Validate rating value
*/
function validateRating(rating: number): boolean {
return Number.isInteger(rating) && rating >= 1 && rating <= 5;
}
/**
* Rate a plugin or model
*/
export async function rateItem(
itemId: string,
rating: number,
itemType: 'plugin' | 'model' = 'plugin',
userId?: string
): Promise<RatingResponse> {
if (!validateItemId(itemId)) {
throw new Error('Invalid item ID');
}
if (!validateRating(rating)) {
throw new Error('Rating must be integer 1-5');
}
const response = await fetch(`${REGISTRY_API_URL}?action=rate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
itemId,
rating,
itemType,
...(userId && { userId }),
}),
signal: AbortSignal.timeout(10000),
});
if (!response.ok) {
const error = await response.text();
throw new Error(`Rating failed: ${error}`);
}
return response.json() as Promise<RatingResponse>;
}
/**
* Get ratings for a single item
*/
export async function getRating(
itemId: string,
itemType: 'plugin' | 'model' = 'plugin'
): Promise<RatingResponse> {
if (!validateItemId(itemId)) {
throw new Error('Invalid item ID');
}
const params = new URLSearchParams({
action: 'get-ratings',
itemId,
itemType,
});
const response = await fetch(`${REGISTRY_API_URL}?${params}`, {
signal: AbortSignal.timeout(10000),
});
if (!response.ok) {
throw new Error('Failed to get ratings');
}
return response.json() as Promise<RatingResponse>;
}
/**
* Get ratings for multiple items (batch)
*/
export async function getBulkRatings(
itemIds: string[],
itemType: 'plugin' | 'model' = 'plugin'
): Promise<BulkRatingsResponse> {
// Validate all IDs
for (const id of itemIds) {
if (!validateItemId(id)) {
throw new Error(`Invalid item ID: ${id}`);
}
}
// Limit batch size
const limitedIds = itemIds.slice(0, 50);
const response = await fetch(`${REGISTRY_API_URL}?action=bulk-ratings`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
itemIds: limitedIds,
itemType,
}),
signal: AbortSignal.timeout(15000),
});
if (!response.ok) {
throw new Error('Failed to get bulk ratings');
}
return response.json() as Promise<BulkRatingsResponse>;
}
/**
* Get analytics data
*/
export async function getAnalytics(): Promise<AnalyticsResponse> {
const response = await fetch(`${REGISTRY_API_URL}?action=analytics`, {
signal: AbortSignal.timeout(10000),
});
if (!response.ok) {
throw new Error('Failed to get analytics');
}
return response.json() as Promise<AnalyticsResponse>;
}
/**
* Track a download event
*/
export async function trackDownload(pluginId: string): Promise<void> {
if (!validateItemId(pluginId)) {
return; // Silently fail for invalid IDs
}
try {
await fetch(`${REGISTRY_API_URL}?action=track-download`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pluginId }),
signal: AbortSignal.timeout(5000),
});
} catch {
// Non-critical, don't throw
}
}
/**
* Check API health
*/
export async function checkHealth(): Promise<{
healthy: boolean;
latestCid?: string;
error?: string;
}> {
try {
const response = await fetch(`${REGISTRY_API_URL}?action=status`, {
signal: AbortSignal.timeout(5000),
});
return response.json() as Promise<{ healthy: boolean; latestCid?: string; error?: string }>;
} catch (error) {
return {
healthy: false,
error: error instanceof Error ? error.message : 'Unknown error',
};
}
}