Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@
"install:all": "pnpm install",
"build:all": "pnpm -r build",
"clean:all": "pnpm -r clean",
"test": "vitest run",
"test:watch": "vitest",
"cli": "node packages/cli/dist/index.js",
"setup": "pnpm install:all && pnpm build:all && pnpm --filter cli global",
"demo": "pnpm cli index ~/.openclaw/skills && pnpm cli route \"create a GitHub issue\""
"demo": "pnpm cli index test_openclaw_python/skills && pnpm cli route \"create a GitHub issue\""
},
"devDependencies": {
"vitest": "^2.0.0"
}
}
208 changes: 208 additions & 0 deletions packages/core/src/clawhub/ClawHubClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/**
* ClawHub Client - Skill Registry Integration
*
* Automatically discovers and installs skills from ClawHub
* when no local skill matches the query.
*/

import * as https from 'https';
import { SkillFingerprint } from '../fingerprint/Fingerprinter.js';

/**
* ClawHub skill search result
*/
export interface ClawHubSkill {
id: string;
name: string;
description: string;
author: string;
version: string;
downloads: number;
rating: number;
tags: string[];
install_url: string;
}

/**
* Search options for ClawHub queries
*/
export interface ClawHubSearchOptions {
limit: number;
minRating: number;
}

/**
* ClawHub API Client
*/
export class ClawHubClient {
private baseUrl = 'https://clawhub.ai/api/v1';
private cache = new Map<string, ClawHubSkill[]>();

/**
* Search ClawHub for skills matching the query
*/
async searchSkills(
query: string,
options: ClawHubSearchOptions = { limit: 5, minRating: 3.5 }
): Promise<ClawHubSkill[]> {
// Check cache first
const cacheKey = `${query}:${options.limit}:${options.minRating}`;
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey)!;
}

try {
// For now, return mock data since ClawHub API structure is known
// but we don't need actual network calls for development
const results = this.mockSearch(query, options);

// Cache for 5 minutes
this.cache.set(cacheKey, results);
setTimeout(() => this.cache.delete(cacheKey), 5 * 60 * 1000);

return results;
} catch (error) {
console.warn('ClawHub search failed:', error);
return [];
}
}

/**
* Mock search results for development
* Maps common queries to known ClawHub skills
*/
private mockSearch(query: string, options: ClawHubSearchOptions): ClawHubSkill[] {
const knownSkills: ClawHubSkill[] = [
{
id: 'browser-automation',
name: 'Browser Automation',
description: 'Automate web browsers - click, type, navigate, screenshot',
author: 'openclaw',
version: '1.2.0',
downloads: 12500,
rating: 4.8,
tags: ['browser', 'web', 'screenshot', 'automation'],
install_url: 'https://clawhub.ai/skills/browser-automation'
},
{
id: 'auto-coding',
name: 'Auto Coding',
description: 'AI-powered code generation, refactoring, and debugging',
author: 'openclaw',
version: '2.1.0',
downloads: 28000,
rating: 4.9,
tags: ['code', 'coding', 'development', 'ai'],
install_url: 'https://clawhub.ai/skills/auto-coding'
},
{
id: 'roundtable',
name: 'RoundTable',
description: 'Multi-agent discussion and brainstorming with AI experts',
author: 'openclaw',
version: '1.5.0',
downloads: 9500,
rating: 4.7,
tags: ['discussion', 'brainstorm', 'multi-agent', 'expert'],
install_url: 'https://clawhub.ai/skills/roundtable'
},
{
id: 'project-manager',
name: 'Project Manager',
description: 'Track project progress, manage tasks, generate reports',
author: 'openclaw',
version: '1.3.0',
downloads: 7200,
rating: 4.6,
tags: ['project', 'task', 'management', 'tracking'],
install_url: 'https://clawhub.ai/skills/project-manager'
},
{
id: 'weather',
name: 'Weather',
description: 'Get weather forecasts and current conditions',
author: 'openclaw',
version: '1.0.0',
downloads: 5800,
rating: 4.5,
tags: ['weather', 'forecast', 'location'],
install_url: 'https://clawhub.ai/skills/weather'
}
];

const queryLower = query.toLowerCase();

// Simple relevance scoring
const scored = knownSkills.map(skill => {
let score = 0;

// Name match
if (skill.name.toLowerCase().includes(queryLower)) score += 10;

// Description match
if (skill.description.toLowerCase().includes(queryLower)) score += 5;

// Tag match
for (const tag of skill.tags) {
if (queryLower.includes(tag)) score += 8;
}

// Rating boost
score += skill.rating;

// Popularity boost (normalized)
score += Math.min(skill.downloads / 1000, 5);

return { skill, score };
});

// Filter by min rating and sort by score
return scored
.filter(s => s.skill.rating >= options.minRating)
.sort((a, b) => b.score - a.score)
.slice(0, options.limit)
.map(s => s.skill);
}

/**
* Install a skill from ClawHub to the local skill directory
*/
async installSkill(skillId: string, targetDir: string): Promise<boolean> {
// In real implementation, this would:
// 1. Download skill from ClawHub
// 2. Extract to target directory
// 3. Run any install scripts
// 4. Return success/failure

console.log(`[ClawHub] Installing skill '${skillId}' to ${targetDir}`);

// Mock success for now
return true;
}

/**
* Convert ClawHub skill to fingerprint for matching
*/
toFingerprint(skill: ClawHubSkill): Partial<SkillFingerprint> {
return {
id: skill.id,
name: skill.name,
description: skill.description,
keywords: skill.tags,
intentPatterns: [],
semanticVector: [],
sideEffects: 'read-only',
preconditions: {
env: [],
bins: []
},
conflictScore: 0,
manualTriggers: [],
priority: 0,
feedbackWeight: 0.7 // Default for new remote skills
};
}
}

// Export singleton
export const clawHubClient = new ClawHubClient();
134 changes: 134 additions & 0 deletions packages/core/src/clawhub/ClawHubRouter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/**
* ClawHub Router Extension
*
* Extends SkillRouter with ClawHub auto-discovery:
* - Low confidence local match → search ClawHub
* - Return remote skill options for user consideration
* - Auto-install option with one click
*/

import { SkillRouter, RouteResult } from '../router/SkillRouter.js';
import { SkillIndex } from '../index/SkillIndex.js';
import { ClawHubClient, ClawHubSkill, clawHubClient } from './ClawHubClient.js';
import { SkillFingerprint } from '../fingerprint/Fingerprinter.js';
import { GradualAdoptionManager, adoptionManager } from './GradualAdoption.js';

/**
* Extended route result with ClawHub options
*/
export interface ClawHubRouteResult extends RouteResult {
clawHubResults?: {
matched: boolean;
skills: Array<{
skill: ClawHubSkill;
confidence: number;
installable: boolean;
}>;
};
}

/**
* ClawHub-aware skill router
*/
export class ClawHubAwareRouter extends SkillRouter {
private clawHubClient: ClawHubClient;
private localIndex: SkillIndex;
protected adoptionManager: GradualAdoptionManager;

constructor(
index: SkillIndex,
embed: any,
config: any,
clawHub?: ClawHubClient,
adoptionMgr?: GradualAdoptionManager
) {
super(index, embed, config);
this.localIndex = index;
this.clawHubClient = clawHub || clawHubClient;
this.adoptionManager = adoptionMgr || adoptionManager;
}

/**
* Route with ClawHub fallback
*/
async routeWithClawHub(query: string, context?: any): Promise<ClawHubRouteResult> {
// First: try local routing
const localResult = await this.route(query, context);

// If high confidence local match, return immediately
if (localResult.confidence >= 0.5 && localResult.skill) {
return localResult;
}

// Low confidence local match → search ClawHub
const clawHubSkills = await this.clawHubClient.searchSkills(query, {
limit: 5,
minRating: 3.5
});

// If no ClawHub results either, return original
if (clawHubSkills.length === 0) {
return localResult;
}

// Calculate confidence for ClawHub skills
const scoredClawHubSkills = clawHubSkills.map(skill => {
const confidence = this.calculateRemoteSkillConfidence(query, skill);
return {
skill,
confidence,
installable: true
};
}).filter(s => s.confidence >= 0.3);

// Return extended result
return {
...localResult,
clawHubResults: {
matched: scoredClawHubSkills.length > 0,
skills: scoredClawHubSkills
}
};
}

/**
* Calculate confidence score for a remote skill
*/
private calculateRemoteSkillConfidence(query: string, skill: ClawHubSkill): number {
let score = 0;
const queryLower = query.toLowerCase();

// Name match
if (skill.name.toLowerCase().includes(queryLower)) score += 0.3;

// Description match
if (skill.description.toLowerCase().includes(queryLower)) score += 0.25;

// Tag match
for (const tag of skill.tags) {
if (queryLower.includes(tag.toLowerCase())) score += 0.2;
}

// Rating boost (0.1 - 0.2)
score += (skill.rating / 10);

// Popularity boost (0 - 0.1)
score += Math.min(skill.downloads / 50000, 0.1);

return Math.min(score, 1.0);
}

/**
* Install a ClawHub skill and add it to the local index
*/
async installAndIndexSkill(skillId: string, targetDir: string): Promise<boolean> {
const success = await this.clawHubClient.installSkill(skillId, targetDir);

if (success) {
// Register new skill for gradual adoption
this.adoptionManager?.registerNewSkill(skillId);
}

return success;
}
}
Loading