feat: ensure OpenClaw skills directory exists and add a clear button to the marketplace search input (#65)

This commit is contained in:
Felix
2026-02-12 16:54:56 +08:00
committed by GitHub
Unverified
parent 582287c74c
commit 541a85a5b0
3 changed files with 43 additions and 24 deletions

View File

@@ -98,7 +98,10 @@ export class ClawHubService {
const child = spawn(this.cliPath, commandArgs, { const child = spawn(this.cliPath, commandArgs, {
cwd: this.workDir, cwd: this.workDir,
shell: isWin && !this.useNodeRunner, shell: isWin && !this.useNodeRunner,
env, env: {
...env,
CLAWHUB_WORKDIR: this.workDir,
},
}); });
let stdout = ''; let stdout = '';

View File

@@ -19,7 +19,7 @@ import {
getAllProvidersWithKeyInfo, getAllProvidersWithKeyInfo,
type ProviderConfig, type ProviderConfig,
} from '../utils/secure-storage'; } from '../utils/secure-storage';
import { getOpenClawStatus, getOpenClawDir, getOpenClawConfigDir, getOpenClawSkillsDir } from '../utils/paths'; import { getOpenClawStatus, getOpenClawDir, getOpenClawConfigDir, getOpenClawSkillsDir, ensureDir } from '../utils/paths';
import { getOpenClawCliCommand, installOpenClawCliMac } from '../utils/openclaw-cli'; import { getOpenClawCliCommand, installOpenClawCliMac } from '../utils/openclaw-cli';
import { getSetting } from '../utils/store'; import { getSetting } from '../utils/store';
import { import {
@@ -517,7 +517,9 @@ function registerOpenClawHandlers(): void {
// Get the OpenClaw skills directory (~/.openclaw/skills) // Get the OpenClaw skills directory (~/.openclaw/skills)
ipcMain.handle('openclaw:getSkillsDir', () => { ipcMain.handle('openclaw:getSkillsDir', () => {
return getOpenClawSkillsDir(); const dir = getOpenClawSkillsDir();
ensureDir(dir);
return dir;
}); });
// Get a shell command to run OpenClaw CLI without modifying PATH // Get a shell command to run OpenClaw CLI without modifying PATH
@@ -911,24 +913,24 @@ function registerProviderHandlers(): void {
apiKey: string, apiKey: string,
options?: { baseUrl?: string } options?: { baseUrl?: string }
) => { ) => {
try { try {
// First try to get existing provider // First try to get existing provider
const provider = await getProvider(providerId); const provider = await getProvider(providerId);
// Use provider.type if provider exists, otherwise use providerId as the type // Use provider.type if provider exists, otherwise use providerId as the type
// This allows validation during setup when provider hasn't been saved yet // This allows validation during setup when provider hasn't been saved yet
const providerType = provider?.type || providerId; const providerType = provider?.type || providerId;
const registryBaseUrl = getProviderConfig(providerType)?.baseUrl; const registryBaseUrl = getProviderConfig(providerType)?.baseUrl;
// Prefer caller-supplied baseUrl (live form value) over persisted config. // Prefer caller-supplied baseUrl (live form value) over persisted config.
// This ensures Setup/Settings validation reflects unsaved edits immediately. // This ensures Setup/Settings validation reflects unsaved edits immediately.
const resolvedBaseUrl = options?.baseUrl || provider?.baseUrl || registryBaseUrl; const resolvedBaseUrl = options?.baseUrl || provider?.baseUrl || registryBaseUrl;
console.log(`[clawx-validate] validating provider type: ${providerType}`); console.log(`[clawx-validate] validating provider type: ${providerType}`);
return await validateApiKeyWithProvider(providerType, apiKey, { baseUrl: resolvedBaseUrl }); return await validateApiKeyWithProvider(providerType, apiKey, { baseUrl: resolvedBaseUrl });
} catch (error) { } catch (error) {
console.error('Validation error:', error); console.error('Validation error:', error);
return { valid: false, error: String(error) }; return { valid: false, error: String(error) };
} }
} }
); );
} }

View File

@@ -640,11 +640,16 @@ export function Skills() {
// Handle marketplace search // Handle marketplace search
const handleMarketplaceSearch = useCallback((e: React.FormEvent) => { const handleMarketplaceSearch = useCallback((e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
if (marketplaceQuery.trim()) { searchSkills(marketplaceQuery);
searchSkills(marketplaceQuery);
}
}, [marketplaceQuery, searchSkills]); }, [marketplaceQuery, searchSkills]);
// Auto-reset when query is cleared
useEffect(() => {
if (activeTab === 'marketplace' && marketplaceQuery === '' && marketplaceDiscoveryAttemptedRef.current) {
searchSkills('');
}
}, [marketplaceQuery, activeTab, searchSkills]);
// Handle install // Handle install
const handleInstall = useCallback(async (slug: string) => { const handleInstall = useCallback(async (slug: string) => {
try { try {
@@ -908,10 +913,19 @@ export function Skills() {
placeholder={t('searchMarketplace')} placeholder={t('searchMarketplace')}
value={marketplaceQuery} value={marketplaceQuery}
onChange={(e) => setMarketplaceQuery(e.target.value)} onChange={(e) => setMarketplaceQuery(e.target.value)}
className="pl-9" className="pl-9 pr-9"
/> />
{marketplaceQuery && (
<button
type="button"
className="absolute right-3 top-3 text-muted-foreground hover:text-foreground"
onClick={() => setMarketplaceQuery('')}
>
<X className="h-4 w-4" />
</button>
)}
</div> </div>
<Button type="submit" disabled={searching || !marketplaceQuery.trim()} className="min-w-[100px]" asChild> <Button type="submit" disabled={searching} className="min-w-[100px]" asChild>
<motion.button whileTap={{ scale: 0.98 }}> <motion.button whileTap={{ scale: 0.98 }}>
<AnimatePresence mode="wait" initial={false}> <AnimatePresence mode="wait" initial={false}>
{searching ? ( {searching ? (