Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 2 additions & 11 deletions desktop/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { t as mainT } from "./i18n";
import {
getOpenClawStateDir,
loadStateDirEnv,
resolveBuiltinSkillsDir,
resolveNodePath,
resolveOpenClawEntry,
} from "./path-resolver";
Expand Down Expand Up @@ -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");

Expand Down
38 changes: 38 additions & 0 deletions desktop/src/path-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}
12 changes: 12 additions & 0 deletions desktop/src/skill-integrity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
15 changes: 2 additions & 13 deletions desktop/src/skill-integrity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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",
Expand Down
Loading