feat: enhance ClawHub integration with new CLI paths and IPC handlers for config and skills directories (#41)

This commit is contained in:
Felix
2026-02-11 11:32:03 +08:00
committed by GitHub
Unverified
parent 177cf4c1ea
commit a0505490cd
6 changed files with 129 additions and 25 deletions

View File

@@ -2,7 +2,7 @@
* Skills Page
* Browse and manage AI skills
*/
import { useEffect, useState, useCallback } from 'react';
import { useEffect, useState, useCallback, useRef } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import {
Search,
@@ -26,6 +26,7 @@ import {
Save,
Key,
ChevronDown,
FolderOpen,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
@@ -528,6 +529,7 @@ export function Skills() {
installSkill,
uninstallSkill,
searching,
searchError,
installing
} = useSkillsStore();
const gatewayStatus = useGatewayStore((state) => state.status);
@@ -536,6 +538,7 @@ export function Skills() {
const [selectedSkill, setSelectedSkill] = useState<Skill | null>(null);
const [activeTab, setActiveTab] = useState('all');
const [selectedSource, setSelectedSource] = useState<'all' | 'built-in' | 'marketplace'>('all');
const marketplaceDiscoveryAttemptedRef = useRef(false);
const isGatewayRunning = gatewayStatus.state === 'running';
const [showGatewayWarning, setShowGatewayWarning] = useState(false);
@@ -609,6 +612,21 @@ export function Skills() {
}
}, [enableSkill, disableSkill]);
const handleOpenSkillsFolder = useCallback(async () => {
try {
const skillsDir = await window.electron.ipcRenderer.invoke('openclaw:getSkillsDir') as string;
if (!skillsDir) {
throw new Error('Skills directory not available');
}
const result = await window.electron.ipcRenderer.invoke('shell:openPath', skillsDir) as string;
if (result) {
throw new Error(result);
}
} catch (err) {
toast.error('Failed to open skills folder: ' + String(err));
}
}, []);
// Handle marketplace search
const handleMarketplaceSearch = useCallback((e: React.FormEvent) => {
e.preventDefault();
@@ -632,10 +650,21 @@ export function Skills() {
// Initial marketplace load (Discovery)
useEffect(() => {
if (activeTab === 'marketplace' && searchResults.length === 0 && !searching) {
searchSkills('');
if (activeTab !== 'marketplace') {
return;
}
}, [activeTab, searchResults.length, searching, searchSkills]);
if (marketplaceQuery.trim()) {
return;
}
if (searching) {
return;
}
if (marketplaceDiscoveryAttemptedRef.current) {
return;
}
marketplaceDiscoveryAttemptedRef.current = true;
searchSkills('');
}, [activeTab, marketplaceQuery, searching, searchSkills]);
// Handle uninstall
const handleUninstall = useCallback(async (slug: string) => {
@@ -665,10 +694,16 @@ export function Skills() {
Browse and manage AI capabilities
</p>
</div>
<Button variant="outline" onClick={fetchSkills} disabled={!isGatewayRunning}>
<RefreshCw className="h-4 w-4 mr-2" />
Refresh
</Button>
<div className="flex items-center gap-2">
<Button variant="outline" onClick={fetchSkills} disabled={!isGatewayRunning}>
<RefreshCw className="h-4 w-4 mr-2" />
Refresh
</Button>
<Button variant="outline" onClick={handleOpenSkillsFolder}>
<FolderOpen className="h-4 w-4 mr-2" />
Open Skills Folder
</Button>
</div>
</div>
{/* Gateway Warning */}
@@ -907,6 +942,15 @@ export function Skills() {
</form>
</div>
{searchError && (
<Card className="border-destructive/50 bg-destructive/5">
<CardContent className="py-3 text-sm text-destructive flex items-center gap-2">
<AlertCircle className="h-4 w-4" />
<span>ClawHub search failed. Check your connection or installation.</span>
</CardContent>
</Card>
)}
{searchResults.length > 0 ? (
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
{searchResults.map((skill) => {

View File

@@ -39,6 +39,7 @@ interface SkillsState {
searchResults: MarketplaceSkill[];
loading: boolean;
searching: boolean;
searchError: string | null;
installing: Record<string, boolean>; // slug -> boolean
error: string | null;
@@ -58,6 +59,7 @@ export const useSkillsStore = create<SkillsState>((set, get) => ({
searchResults: [],
loading: false,
searching: false,
searchError: null,
installing: {},
error: null,
@@ -145,7 +147,7 @@ export const useSkillsStore = create<SkillsState>((set, get) => ({
},
searchSkills: async (query: string) => {
set({ searching: true, error: null });
set({ searching: true, searchError: null });
try {
const result = await window.electron.ipcRenderer.invoke('clawhub:search', { query }) as { success: boolean; results?: MarketplaceSkill[]; error?: string };
if (result.success) {
@@ -154,7 +156,7 @@ export const useSkillsStore = create<SkillsState>((set, get) => ({
throw new Error(result.error || 'Search failed');
}
} catch (error) {
set({ error: String(error) });
set({ searchError: String(error) });
} finally {
set({ searching: false });
}