diff --git a/desktop/src/main.ts b/desktop/src/main.ts index 478e0bd..6b42793 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -19,6 +19,7 @@ import { t as mainT } from "./i18n"; import { getOpenClawStateDir, loadStateDirEnv, + resolveBuiltinSkillsDir, resolveNodePath, resolveOpenClawEntry, } from "./path-resolver"; @@ -2460,17 +2461,7 @@ function registerIpcHandlers(): void { // --- Skills --- ipcMain.handle("skills:list", () => { const homeDir = app.getPath("home"); - // Check both classic and lib/ npm global layouts for builtin skills - const classicDir = path.join(homeDir, ".openclaw-node", "node_modules", "openclaw", "skills"); - const libDir = path.join( - homeDir, - ".openclaw-node", - "lib", - "node_modules", - "openclaw", - "skills", - ); - const builtinDir = fs.existsSync(classicDir) ? classicDir : libDir; + const builtinDir = resolveBuiltinSkillsDir(); const customDir = path.join(homeDir, ".agents", "skills"); const managedDir = path.join(homeDir, ".openclaw", "skills"); diff --git a/desktop/src/path-resolver.ts b/desktop/src/path-resolver.ts index 3dd53c4..9024cc1 100644 --- a/desktop/src/path-resolver.ts +++ b/desktop/src/path-resolver.ts @@ -110,3 +110,41 @@ export function resolveOpenClawEntry(): string { } return candidates[0]; } + +/** + * Resolve the directory containing the built-in (bundled with the `openclaw` + * npm package) skills. + * + * Mirrors the layouts probed by {@link resolveOpenClawEntry}: + * 1. Bundled in packaged app resources (`resources/openclaw/skills`) + * 2. Deployer-installed under `~/.openclaw-node` (classic & `lib/` npm layouts) + * 3. Global npm install under `%APPDATA%/npm` (per-user, default `npm i -g`) + * 4. Per-machine Node at `%ProgramFiles%/nodejs` + * 5. Per-user Node at `%LocalAppData%/Programs/nodejs` + * + * Returns the first existing path. If none exist, returns the first candidate + * (legacy deployer layout) so callers can still surface a useful path. + */ +export function resolveBuiltinSkillsDir(): string { + if (app.isPackaged) { + const bundled = path.join(process.resourcesPath, "openclaw", "skills"); + if (fs.existsSync(bundled)) return bundled; + } + const home = process.env.USERPROFILE || ""; + const appData = process.env.APPDATA || ""; + const programFiles = process.env.ProgramFiles || ""; + const localAppData = process.env.LOCALAPPDATA || ""; + const candidates = [ + home ? path.join(home, ".openclaw-node", "node_modules", "openclaw", "skills") : "", + home ? path.join(home, ".openclaw-node", "lib", "node_modules", "openclaw", "skills") : "", + appData ? path.join(appData, "npm", "node_modules", "openclaw", "skills") : "", + programFiles ? path.join(programFiles, "nodejs", "node_modules", "openclaw", "skills") : "", + localAppData + ? path.join(localAppData, "Programs", "nodejs", "node_modules", "openclaw", "skills") + : "", + ]; + for (const p of candidates) { + if (p && fs.existsSync(p)) return p; + } + return candidates[0]; +} diff --git a/desktop/src/skill-integrity.test.ts b/desktop/src/skill-integrity.test.ts index ec2a863..92c924d 100644 --- a/desktop/src/skill-integrity.test.ts +++ b/desktop/src/skill-integrity.test.ts @@ -19,6 +19,18 @@ vi.mock("electron", () => ({ vi.mock("./path-resolver", () => ({ getOpenClawStateDir: () => tmpStateDir, + resolveBuiltinSkillsDir: () => { + const classic = path.join(tmpHome, ".openclaw-node", "node_modules", "openclaw", "skills"); + const lib = path.join( + tmpHome, + ".openclaw-node", + "lib", + "node_modules", + "openclaw", + "skills", + ); + return fs.existsSync(classic) ? classic : lib; + }, })); import { diff --git a/desktop/src/skill-integrity.ts b/desktop/src/skill-integrity.ts index 1fe7a30..5424434 100644 --- a/desktop/src/skill-integrity.ts +++ b/desktop/src/skill-integrity.ts @@ -18,7 +18,7 @@ import { import fs from "fs"; import path from "path"; import { app } from "electron"; -import { getOpenClawStateDir } from "./path-resolver"; +import { getOpenClawStateDir, resolveBuiltinSkillsDir } from "./path-resolver"; // --------------------------------------------------------------------------- // Public types @@ -87,21 +87,10 @@ const EXCLUDED_NAMES = new Set([ export function getSkillSourceDirs(): Array<{ source: string; baseDir: string }> { const homeDir = app.getPath("home"); - // Check both classic and lib/ npm global layouts - const classicSkills = path.join(homeDir, ".openclaw-node", "node_modules", "openclaw", "skills"); - const libSkills = path.join( - homeDir, - ".openclaw-node", - "lib", - "node_modules", - "openclaw", - "skills", - ); - const builtinDir = fs.existsSync(classicSkills) ? classicSkills : libSkills; return [ { source: "builtin", - baseDir: builtinDir, + baseDir: resolveBuiltinSkillsDir(), }, { source: "managed",