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 && (
+
+ )}
-