From 541a85a5b0f9e505b706379f8053fd86d087ca33 Mon Sep 17 00:00:00 2001 From: Felix <24791380+vcfgv@users.noreply.github.com> Date: Thu, 12 Feb 2026 16:54:56 +0800 Subject: [PATCH] feat: ensure OpenClaw skills directory exists and add a clear button to the marketplace search input (#65) --- electron/gateway/clawhub.ts | 5 ++++- electron/main/ipc-handlers.ts | 38 ++++++++++++++++++----------------- src/pages/Skills/index.tsx | 24 +++++++++++++++++----- 3 files changed, 43 insertions(+), 24 deletions(-) diff --git a/electron/gateway/clawhub.ts b/electron/gateway/clawhub.ts index fc93e0853..2a40372ed 100644 --- a/electron/gateway/clawhub.ts +++ b/electron/gateway/clawhub.ts @@ -98,7 +98,10 @@ export class ClawHubService { const child = spawn(this.cliPath, commandArgs, { cwd: this.workDir, shell: isWin && !this.useNodeRunner, - env, + env: { + ...env, + CLAWHUB_WORKDIR: this.workDir, + }, }); let stdout = ''; diff --git a/electron/main/ipc-handlers.ts b/electron/main/ipc-handlers.ts index 6dd6bd11f..4a38fe391 100644 --- a/electron/main/ipc-handlers.ts +++ b/electron/main/ipc-handlers.ts @@ -19,7 +19,7 @@ import { getAllProvidersWithKeyInfo, type ProviderConfig, } 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 { getSetting } from '../utils/store'; import { @@ -517,7 +517,9 @@ function registerOpenClawHandlers(): void { // Get the OpenClaw skills directory (~/.openclaw/skills) ipcMain.handle('openclaw:getSkillsDir', () => { - return getOpenClawSkillsDir(); + const dir = getOpenClawSkillsDir(); + ensureDir(dir); + return dir; }); // Get a shell command to run OpenClaw CLI without modifying PATH @@ -911,24 +913,24 @@ function registerProviderHandlers(): void { apiKey: string, options?: { baseUrl?: string } ) => { - try { - // First try to get existing provider - const provider = await getProvider(providerId); + try { + // First try to get existing provider + const provider = await getProvider(providerId); - // Use provider.type if provider exists, otherwise use providerId as the type - // This allows validation during setup when provider hasn't been saved yet - const providerType = provider?.type || providerId; - const registryBaseUrl = getProviderConfig(providerType)?.baseUrl; - // Prefer caller-supplied baseUrl (live form value) over persisted config. - // This ensures Setup/Settings validation reflects unsaved edits immediately. - const resolvedBaseUrl = options?.baseUrl || provider?.baseUrl || registryBaseUrl; + // Use provider.type if provider exists, otherwise use providerId as the type + // This allows validation during setup when provider hasn't been saved yet + const providerType = provider?.type || providerId; + const registryBaseUrl = getProviderConfig(providerType)?.baseUrl; + // Prefer caller-supplied baseUrl (live form value) over persisted config. + // This ensures Setup/Settings validation reflects unsaved edits immediately. + const resolvedBaseUrl = options?.baseUrl || provider?.baseUrl || registryBaseUrl; - console.log(`[clawx-validate] validating provider type: ${providerType}`); - return await validateApiKeyWithProvider(providerType, apiKey, { baseUrl: resolvedBaseUrl }); - } catch (error) { - console.error('Validation error:', error); - return { valid: false, error: String(error) }; - } + console.log(`[clawx-validate] validating provider type: ${providerType}`); + return await validateApiKeyWithProvider(providerType, apiKey, { baseUrl: resolvedBaseUrl }); + } catch (error) { + console.error('Validation error:', error); + return { valid: false, error: String(error) }; + } } ); } diff --git a/src/pages/Skills/index.tsx b/src/pages/Skills/index.tsx index eada6b69e..46f719553 100644 --- a/src/pages/Skills/index.tsx +++ b/src/pages/Skills/index.tsx @@ -640,11 +640,16 @@ export function Skills() { // Handle marketplace search const handleMarketplaceSearch = useCallback((e: React.FormEvent) => { e.preventDefault(); - if (marketplaceQuery.trim()) { - searchSkills(marketplaceQuery); - } + searchSkills(marketplaceQuery); }, [marketplaceQuery, searchSkills]); + // Auto-reset when query is cleared + useEffect(() => { + if (activeTab === 'marketplace' && marketplaceQuery === '' && marketplaceDiscoveryAttemptedRef.current) { + searchSkills(''); + } + }, [marketplaceQuery, activeTab, searchSkills]); + // Handle install const handleInstall = useCallback(async (slug: string) => { try { @@ -908,10 +913,19 @@ export function Skills() { placeholder={t('searchMarketplace')} value={marketplaceQuery} onChange={(e) => setMarketplaceQuery(e.target.value)} - className="pl-9" + className="pl-9 pr-9" /> + {marketplaceQuery && ( + + )} -