feat: ensure OpenClaw skills directory exists and add a clear button to the marketplace search input (#65)
This commit is contained in:
@@ -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 = '';
|
||||||
|
|||||||
@@ -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) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user