mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:10:44 +00:00
* feat: add migration providers * feat: offer Hermes migration during onboarding * feat(hermes): map imported config surfaces * feat(onboard): require fresh migration imports * docs(cli): clarify Hermes import coverage * chore(migrations): rename Hermes importer package * chore(migrations): rewire Hermes importer id * fix(migrations): redact migration JSON details * fix(hermes): use provider runtime for config imports * test(hermes): cover missing source planning --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
71 lines
2.1 KiB
TypeScript
71 lines
2.1 KiB
TypeScript
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { createMigrationItem, MIGRATION_REASON_TARGET_EXISTS } from "openclaw/plugin-sdk/migration";
|
|
import type { MigrationItem } from "openclaw/plugin-sdk/plugin-entry";
|
|
import { exists, sanitizeName } from "./helpers.js";
|
|
import type { HermesSource } from "./source.js";
|
|
import type { PlannedTargets } from "./targets.js";
|
|
|
|
type PlannedSkill = {
|
|
name: string;
|
|
source: string;
|
|
target: string;
|
|
};
|
|
|
|
export async function buildSkillItems(params: {
|
|
source: HermesSource;
|
|
targets: PlannedTargets;
|
|
overwrite?: boolean;
|
|
}): Promise<MigrationItem[]> {
|
|
if (!params.source.skillsDir) {
|
|
return [];
|
|
}
|
|
const entries = await fs
|
|
.readdir(params.source.skillsDir, { withFileTypes: true })
|
|
.catch(() => []);
|
|
const plannedSkills: PlannedSkill[] = [];
|
|
for (const entry of entries) {
|
|
if (!entry.isDirectory()) {
|
|
continue;
|
|
}
|
|
const name = sanitizeName(entry.name);
|
|
if (!name) {
|
|
continue;
|
|
}
|
|
const source = path.join(params.source.skillsDir, entry.name);
|
|
if (!(await exists(path.join(source, "SKILL.md")))) {
|
|
continue;
|
|
}
|
|
plannedSkills.push({
|
|
name,
|
|
source,
|
|
target: path.join(params.targets.workspaceDir, "skills", name),
|
|
});
|
|
}
|
|
const counts = new Map<string, number>();
|
|
for (const skill of plannedSkills) {
|
|
counts.set(skill.name, (counts.get(skill.name) ?? 0) + 1);
|
|
}
|
|
const items: MigrationItem[] = [];
|
|
for (const skill of plannedSkills) {
|
|
const collides = (counts.get(skill.name) ?? 0) > 1;
|
|
const targetExists = await exists(skill.target);
|
|
items.push(
|
|
createMigrationItem({
|
|
id: `skill:${skill.name}`,
|
|
kind: "skill",
|
|
action: "copy",
|
|
source: skill.source,
|
|
target: skill.target,
|
|
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
|
|
: undefined,
|
|
}),
|
|
);
|
|
}
|
|
return items;
|
|
}
|