openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
87 lines (86 loc) • 3.27 kB
JavaScript
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js";
import "./string-coerce-runtime-GQa0ehRA.js";
import { n as MIGRATION_REASON_TARGET_EXISTS, o as createMigrationItem } from "./migration-q6QzXKht.js";
import { c as readText, o as parseHermesConfig, r as exists, u as sanitizeName } from "./helpers-DWNr52wp.js";
import path from "node:path";
import fs from "node:fs/promises";
//#region extensions/migrate-hermes/skills.ts
const EXCLUDED_SKILL_DIRS = /* @__PURE__ */ new Set([
".git",
".github",
".hub",
".archive",
".venv",
"venv",
"node_modules",
"site-packages",
"__pycache__",
".tox",
".nox",
".pytest_cache",
".mypy_cache",
".ruff_cache"
]);
const SKILL_SUPPORT_DIRS = /* @__PURE__ */ new Set([
"references",
"templates",
"assets",
"scripts"
]);
async function discoverSkillRoots(root) {
const orgRoot = path.join(root, "_org");
const activeOrg = (await readText(path.join(orgRoot, ".active_org")))?.trim();
async function visit(dir) {
const hasSkill = await exists(path.join(dir, "SKILL.md"));
const entries = await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
const roots = hasSkill ? [dir] : [];
for (const entry of entries.toSorted((left, right) => left.name.localeCompare(right.name))) {
const child = path.join(dir, entry.name);
const inactiveOrg = child === orgRoot && !activeOrg || dir === orgRoot && entry.name !== activeOrg;
if (!entry.isDirectory() || EXCLUDED_SKILL_DIRS.has(entry.name) || hasSkill && SKILL_SUPPORT_DIRS.has(entry.name) || inactiveOrg) continue;
roots.push(...await visit(child));
}
return roots;
}
return await visit(root);
}
async function buildSkillItems(params) {
if (!params.source.skillsDir) return [];
const plannedSkills = [];
for (const source of await discoverSkillRoots(params.source.skillsDir)) {
const name = sanitizeName(path.basename(source));
if (!name) continue;
const frontmatter = (await readText(path.join(source, "SKILL.md")))?.match(/^\uFEFF?---\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/u)?.[1];
let skillName = path.basename(source);
try {
skillName = normalizeOptionalString(parseHermesConfig(frontmatter).name) ?? skillName;
} catch {}
plannedSkills.push({
id: `skill:${path.relative(params.source.skillsDir, source).split(path.sep).map(sanitizeName).filter(Boolean).join(":")}`,
name,
skillName,
source,
target: path.join(params.targets.workspaceDir, "skills", name)
});
}
const counts = /* @__PURE__ */ new Map();
for (const skill of plannedSkills) counts.set(skill.name, (counts.get(skill.name) ?? 0) + 1);
const items = [];
for (const skill of plannedSkills) {
const collides = (counts.get(skill.name) ?? 0) > 1;
const targetExists = await exists(skill.target);
items.push(createMigrationItem({
id: skill.id,
kind: "skill",
action: "copy",
source: skill.source,
target: skill.target,
details: { skillName: skill.skillName },
status: collides ? "conflict" : targetExists && !params.overwrite ? "conflict" : "planned",
reason: collides ? `multiple Hermes skill directories normalize to "${skill.name}"` : targetExists && !params.overwrite ? MIGRATION_REASON_TARGET_EXISTS : void 0
}));
}
return items;
}
//#endregion
export { buildSkillItems as t };