fix(skills): distinguish external skill install paths and open real location (#463)

This commit is contained in:
Felix
2026-03-13 14:40:46 +08:00
committed by GitHub
Unverified
parent 995a7f070d
commit 4cfb552b1d
11 changed files with 238 additions and 30 deletions

View File

@@ -33,6 +33,13 @@ export interface ClawHubSkillResult {
stars?: number;
}
export interface ClawHubInstalledSkillResult {
slug: string;
version: string;
source?: string;
baseDir?: string;
}
export class ClawHubService {
private workDir: string;
private cliPath: string;
@@ -339,7 +346,7 @@ export class ClawHubService {
/**
* List installed skills
*/
async listInstalled(): Promise<Array<{ slug: string; version: string }>> {
async listInstalled(): Promise<ClawHubInstalledSkillResult[]> {
try {
const output = await this.runCommand(['list']);
if (!output || output.includes('No installed skills')) {
@@ -351,31 +358,41 @@ export class ClawHubService {
const cleanLine = this.stripAnsi(line);
const match = cleanLine.match(/^(\S+)\s+v?(\d+\.\S+)/);
if (match) {
const slug = match[1];
return {
slug: match[1],
slug,
version: match[2],
source: 'openclaw-managed',
baseDir: path.join(this.workDir, 'skills', slug),
};
}
return null;
}).filter((s): s is { slug: string; version: string } => s !== null);
}).filter((s): s is ClawHubInstalledSkillResult => s !== null);
} catch (error) {
console.error('ClawHub list error:', error);
return [];
}
}
/**
* Open skill README/manual in default editor
*/
async openSkillReadme(skillKeyOrSlug: string, fallbackSlug?: string): Promise<boolean> {
private resolveSkillDir(skillKeyOrSlug: string, fallbackSlug?: string, preferredBaseDir?: string): string | null {
const candidates = [skillKeyOrSlug, fallbackSlug]
.filter((v): v is string => typeof v === 'string' && v.trim().length > 0)
.map(v => v.trim());
const uniqueCandidates = [...new Set(candidates)];
if (preferredBaseDir && preferredBaseDir.trim() && fs.existsSync(preferredBaseDir.trim())) {
return preferredBaseDir.trim();
}
const directSkillDir = uniqueCandidates
.map((id) => path.join(this.workDir, 'skills', id))
.find((dir) => fs.existsSync(dir));
const skillDir = directSkillDir || this.resolveSkillDirByManifestName(uniqueCandidates);
return directSkillDir || this.resolveSkillDirByManifestName(uniqueCandidates);
}
/**
* Open skill README/manual in default editor
*/
async openSkillReadme(skillKeyOrSlug: string, fallbackSlug?: string, preferredBaseDir?: string): Promise<boolean> {
const skillDir = this.resolveSkillDir(skillKeyOrSlug, fallbackSlug, preferredBaseDir);
// Try to find documentation file
const possibleFiles = ['SKILL.md', 'README.md', 'skill.md', 'readme.md'];
@@ -409,4 +426,19 @@ export class ClawHubService {
throw error;
}
}
/**
* Open skill path in file explorer
*/
async openSkillPath(skillKeyOrSlug: string, fallbackSlug?: string, preferredBaseDir?: string): Promise<boolean> {
const skillDir = this.resolveSkillDir(skillKeyOrSlug, fallbackSlug, preferredBaseDir);
if (!skillDir) {
throw new Error('Skill directory not found');
}
const openResult = await shell.openPath(skillDir);
if (openResult) {
throw new Error(openResult);
}
return true;
}
}