mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-14 01:50:39 +09:00
Namespace company skill identities
Persist canonical namespaced skill keys, split adapter runtime names from skill keys, and update portability/import flows to carry the canonical identity end-to-end. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
bb46423969
commit
5890b318c4
39 changed files with 9902 additions and 309 deletions
|
|
@ -49,10 +49,10 @@ async function buildSkillsDir(config: Record<string, unknown>): Promise<string>
|
|||
),
|
||||
);
|
||||
for (const entry of availableEntries) {
|
||||
if (!desiredNames.has(entry.name)) continue;
|
||||
if (!desiredNames.has(entry.key)) continue;
|
||||
await fs.symlink(
|
||||
entry.source,
|
||||
path.join(target, entry.name),
|
||||
path.join(target, entry.runtimeName),
|
||||
);
|
||||
}
|
||||
return tmp;
|
||||
|
|
|
|||
|
|
@ -14,17 +14,18 @@ const __moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|||
|
||||
async function buildClaudeSkillSnapshot(config: Record<string, unknown>): Promise<AdapterSkillSnapshot> {
|
||||
const availableEntries = await readPaperclipRuntimeSkillEntries(config, __moduleDir);
|
||||
const availableByName = new Map(availableEntries.map((entry) => [entry.name, entry]));
|
||||
const availableByKey = new Map(availableEntries.map((entry) => [entry.key, entry]));
|
||||
const desiredSkills = resolvePaperclipDesiredSkillNames(config, availableEntries);
|
||||
const desiredSet = new Set(desiredSkills);
|
||||
const entries: AdapterSkillEntry[] = availableEntries.map((entry) => ({
|
||||
name: entry.name,
|
||||
desired: desiredSet.has(entry.name),
|
||||
key: entry.key,
|
||||
runtimeName: entry.runtimeName,
|
||||
desired: desiredSet.has(entry.key),
|
||||
managed: true,
|
||||
state: desiredSet.has(entry.name) ? "configured" : "available",
|
||||
state: desiredSet.has(entry.key) ? "configured" : "available",
|
||||
sourcePath: entry.source,
|
||||
targetPath: null,
|
||||
detail: desiredSet.has(entry.name)
|
||||
detail: desiredSet.has(entry.key)
|
||||
? "Will be mounted into the ephemeral Claude skill directory on the next run."
|
||||
: null,
|
||||
required: Boolean(entry.required),
|
||||
|
|
@ -33,10 +34,11 @@ async function buildClaudeSkillSnapshot(config: Record<string, unknown>): Promis
|
|||
const warnings: string[] = [];
|
||||
|
||||
for (const desiredSkill of desiredSkills) {
|
||||
if (availableByName.has(desiredSkill)) continue;
|
||||
if (availableByKey.has(desiredSkill)) continue;
|
||||
warnings.push(`Desired skill "${desiredSkill}" is not available from the Paperclip skills directory.`);
|
||||
entries.push({
|
||||
name: desiredSkill,
|
||||
key: desiredSkill,
|
||||
runtimeName: null,
|
||||
desired: true,
|
||||
managed: true,
|
||||
state: "missing",
|
||||
|
|
@ -46,7 +48,7 @@ async function buildClaudeSkillSnapshot(config: Record<string, unknown>): Promis
|
|||
});
|
||||
}
|
||||
|
||||
entries.sort((left, right) => left.name.localeCompare(right.name));
|
||||
entries.sort((left, right) => left.key.localeCompare(right.key));
|
||||
|
||||
return {
|
||||
adapterType: "claude_local",
|
||||
|
|
@ -71,7 +73,7 @@ export async function syncClaudeSkills(
|
|||
|
||||
export function resolveClaudeDesiredSkillNames(
|
||||
config: Record<string, unknown>,
|
||||
availableEntries: Array<{ name: string; required?: boolean }>,
|
||||
availableEntries: Array<{ key: string; required?: boolean }>,
|
||||
) {
|
||||
return resolvePaperclipDesiredSkillNames(config, availableEntries);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ async function isLikelyPaperclipRuntimeSkillSource(candidate: string, skillName:
|
|||
|
||||
type EnsureCodexSkillsInjectedOptions = {
|
||||
skillsHome?: string;
|
||||
skillsEntries?: Array<{ name: string; source: string }>;
|
||||
skillsEntries?: Array<{ key: string; runtimeName: string; source: string }>;
|
||||
desiredSkillNames?: string[];
|
||||
linkSkill?: (source: string, target: string) => Promise<void>;
|
||||
};
|
||||
|
|
@ -110,16 +110,16 @@ export async function ensureCodexSkillsInjected(
|
|||
) {
|
||||
const allSkillsEntries = options.skillsEntries ?? await readPaperclipRuntimeSkillEntries({}, __moduleDir);
|
||||
const desiredSkillNames =
|
||||
options.desiredSkillNames ?? allSkillsEntries.map((entry) => entry.name);
|
||||
options.desiredSkillNames ?? allSkillsEntries.map((entry) => entry.key);
|
||||
const desiredSet = new Set(desiredSkillNames);
|
||||
const skillsEntries = allSkillsEntries.filter((entry) => desiredSet.has(entry.name));
|
||||
const skillsEntries = allSkillsEntries.filter((entry) => desiredSet.has(entry.key));
|
||||
if (skillsEntries.length === 0) return;
|
||||
|
||||
const skillsHome = options.skillsHome ?? path.join(resolveCodexHomeDir(process.env), "skills");
|
||||
await fs.mkdir(skillsHome, { recursive: true });
|
||||
const removedSkills = await removeMaintainerOnlySkillSymlinks(
|
||||
skillsHome,
|
||||
skillsEntries.map((entry) => entry.name),
|
||||
skillsEntries.map((entry) => entry.runtimeName),
|
||||
);
|
||||
for (const skillName of removedSkills) {
|
||||
await onLog(
|
||||
|
|
@ -129,7 +129,7 @@ export async function ensureCodexSkillsInjected(
|
|||
}
|
||||
const linkSkill = options.linkSkill;
|
||||
for (const entry of skillsEntries) {
|
||||
const target = path.join(skillsHome, entry.name);
|
||||
const target = path.join(skillsHome, entry.runtimeName);
|
||||
|
||||
try {
|
||||
const existing = await fs.lstat(target).catch(() => null);
|
||||
|
|
@ -141,7 +141,7 @@ export async function ensureCodexSkillsInjected(
|
|||
if (
|
||||
resolvedLinkedPath &&
|
||||
resolvedLinkedPath !== entry.source &&
|
||||
(await isLikelyPaperclipRuntimeSkillSource(resolvedLinkedPath, entry.name))
|
||||
(await isLikelyPaperclipRuntimeSkillSource(resolvedLinkedPath, entry.runtimeName))
|
||||
) {
|
||||
await fs.unlink(target);
|
||||
if (linkSkill) {
|
||||
|
|
@ -151,7 +151,7 @@ export async function ensureCodexSkillsInjected(
|
|||
}
|
||||
await onLog(
|
||||
"stderr",
|
||||
`[paperclip] Repaired Codex skill "${entry.name}" into ${skillsHome}\n`,
|
||||
`[paperclip] Repaired Codex skill "${entry.key}" into ${skillsHome}\n`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
|
@ -162,12 +162,12 @@ export async function ensureCodexSkillsInjected(
|
|||
|
||||
await onLog(
|
||||
"stderr",
|
||||
`[paperclip] ${result === "repaired" ? "Repaired" : "Injected"} Codex skill "${entry.name}" into ${skillsHome}\n`,
|
||||
`[paperclip] ${result === "repaired" ? "Repaired" : "Injected"} Codex skill "${entry.key}" into ${skillsHome}\n`,
|
||||
);
|
||||
} catch (err) {
|
||||
await onLog(
|
||||
"stderr",
|
||||
`[paperclip] Failed to inject Codex skill "${entry.name}" into ${skillsHome}: ${err instanceof Error ? err.message : String(err)}\n`,
|
||||
`[paperclip] Failed to inject Codex skill "${entry.key}" into ${skillsHome}: ${err instanceof Error ? err.message : String(err)}\n`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ async function readInstalledSkillTargets(skillsHome: string) {
|
|||
|
||||
async function buildCodexSkillSnapshot(config: Record<string, unknown>): Promise<AdapterSkillSnapshot> {
|
||||
const availableEntries = await readPaperclipRuntimeSkillEntries(config, __moduleDir);
|
||||
const availableByName = new Map(availableEntries.map((entry) => [entry.name, entry]));
|
||||
const availableByKey = new Map(availableEntries.map((entry) => [entry.key, entry]));
|
||||
const desiredSkills = resolvePaperclipDesiredSkillNames(config, availableEntries);
|
||||
const desiredSet = new Set(desiredSkills);
|
||||
const skillsHome = resolveCodexSkillsHome(config);
|
||||
|
|
@ -62,8 +62,8 @@ async function buildCodexSkillSnapshot(config: Record<string, unknown>): Promise
|
|||
const warnings: string[] = [];
|
||||
|
||||
for (const available of availableEntries) {
|
||||
const installedEntry = installed.get(available.name) ?? null;
|
||||
const desired = desiredSet.has(available.name);
|
||||
const installedEntry = installed.get(available.runtimeName) ?? null;
|
||||
const desired = desiredSet.has(available.key);
|
||||
let state: AdapterSkillEntry["state"] = "available";
|
||||
let managed = false;
|
||||
let detail: string | null = null;
|
||||
|
|
@ -82,12 +82,13 @@ async function buildCodexSkillSnapshot(config: Record<string, unknown>): Promise
|
|||
}
|
||||
|
||||
entries.push({
|
||||
name: available.name,
|
||||
key: available.key,
|
||||
runtimeName: available.runtimeName,
|
||||
desired,
|
||||
managed,
|
||||
state,
|
||||
sourcePath: available.source,
|
||||
targetPath: path.join(skillsHome, available.name),
|
||||
targetPath: path.join(skillsHome, available.runtimeName),
|
||||
detail,
|
||||
required: Boolean(available.required),
|
||||
requiredReason: available.requiredReason ?? null,
|
||||
|
|
@ -95,23 +96,25 @@ async function buildCodexSkillSnapshot(config: Record<string, unknown>): Promise
|
|||
}
|
||||
|
||||
for (const desiredSkill of desiredSkills) {
|
||||
if (availableByName.has(desiredSkill)) continue;
|
||||
if (availableByKey.has(desiredSkill)) continue;
|
||||
warnings.push(`Desired skill "${desiredSkill}" is not available from the Paperclip skills directory.`);
|
||||
entries.push({
|
||||
name: desiredSkill,
|
||||
key: desiredSkill,
|
||||
runtimeName: null,
|
||||
desired: true,
|
||||
managed: true,
|
||||
state: "missing",
|
||||
sourcePath: null,
|
||||
targetPath: path.join(skillsHome, desiredSkill),
|
||||
targetPath: null,
|
||||
detail: "Paperclip cannot find this skill in the local runtime skills directory.",
|
||||
});
|
||||
}
|
||||
|
||||
for (const [name, installedEntry] of installed.entries()) {
|
||||
if (availableByName.has(name)) continue;
|
||||
if (availableEntries.some((entry) => entry.runtimeName === name)) continue;
|
||||
entries.push({
|
||||
name,
|
||||
key: name,
|
||||
runtimeName: name,
|
||||
desired: false,
|
||||
managed: false,
|
||||
state: "external",
|
||||
|
|
@ -121,7 +124,7 @@ async function buildCodexSkillSnapshot(config: Record<string, unknown>): Promise
|
|||
});
|
||||
}
|
||||
|
||||
entries.sort((left, right) => left.name.localeCompare(right.name));
|
||||
entries.sort((left, right) => left.key.localeCompare(right.key));
|
||||
|
||||
return {
|
||||
adapterType: "codex_local",
|
||||
|
|
@ -144,23 +147,23 @@ export async function syncCodexSkills(
|
|||
const availableEntries = await readPaperclipRuntimeSkillEntries(ctx.config, __moduleDir);
|
||||
const desiredSet = new Set([
|
||||
...desiredSkills,
|
||||
...availableEntries.filter((entry) => entry.required).map((entry) => entry.name),
|
||||
...availableEntries.filter((entry) => entry.required).map((entry) => entry.key),
|
||||
]);
|
||||
const skillsHome = resolveCodexSkillsHome(ctx.config);
|
||||
await fs.mkdir(skillsHome, { recursive: true });
|
||||
const installed = await readInstalledSkillTargets(skillsHome);
|
||||
const availableByName = new Map(availableEntries.map((entry) => [entry.name, entry]));
|
||||
const availableByRuntimeName = new Map(availableEntries.map((entry) => [entry.runtimeName, entry]));
|
||||
|
||||
for (const available of availableEntries) {
|
||||
if (!desiredSet.has(available.name)) continue;
|
||||
const target = path.join(skillsHome, available.name);
|
||||
if (!desiredSet.has(available.key)) continue;
|
||||
const target = path.join(skillsHome, available.runtimeName);
|
||||
await ensurePaperclipSkillSymlink(available.source, target);
|
||||
}
|
||||
|
||||
for (const [name, installedEntry] of installed.entries()) {
|
||||
const available = availableByName.get(name);
|
||||
const available = availableByRuntimeName.get(name);
|
||||
if (!available) continue;
|
||||
if (desiredSet.has(name)) continue;
|
||||
if (desiredSet.has(available.key)) continue;
|
||||
if (installedEntry.targetPath !== available.source) continue;
|
||||
await fs.unlink(path.join(skillsHome, name)).catch(() => {});
|
||||
}
|
||||
|
|
@ -170,7 +173,7 @@ export async function syncCodexSkills(
|
|||
|
||||
export function resolveCodexDesiredSkillNames(
|
||||
config: Record<string, unknown>,
|
||||
availableEntries: Array<{ name: string; required?: boolean }>,
|
||||
availableEntries: Array<{ key: string; required?: boolean }>,
|
||||
) {
|
||||
return resolvePaperclipDesiredSkillNames(config, availableEntries);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ function cursorSkillsHome(): string {
|
|||
|
||||
type EnsureCursorSkillsInjectedOptions = {
|
||||
skillsDir?: string | null;
|
||||
skillsEntries?: Array<{ name: string; source: string }>;
|
||||
skillsEntries?: Array<{ key: string; runtimeName: string; source: string }>;
|
||||
skillsHome?: string;
|
||||
linkSkill?: (source: string, target: string) => Promise<void>;
|
||||
};
|
||||
|
|
@ -108,7 +108,11 @@ export async function ensureCursorSkillsInjected(
|
|||
?? (options.skillsDir
|
||||
? (await fs.readdir(options.skillsDir, { withFileTypes: true }))
|
||||
.filter((entry) => entry.isDirectory())
|
||||
.map((entry) => ({ name: entry.name, source: path.join(options.skillsDir!, entry.name) }))
|
||||
.map((entry) => ({
|
||||
key: entry.name,
|
||||
runtimeName: entry.name,
|
||||
source: path.join(options.skillsDir!, entry.name),
|
||||
}))
|
||||
: await readPaperclipRuntimeSkillEntries({}, __moduleDir));
|
||||
if (skillsEntries.length === 0) return;
|
||||
|
||||
|
|
@ -124,7 +128,7 @@ export async function ensureCursorSkillsInjected(
|
|||
}
|
||||
const removedSkills = await removeMaintainerOnlySkillSymlinks(
|
||||
skillsHome,
|
||||
skillsEntries.map((entry) => entry.name),
|
||||
skillsEntries.map((entry) => entry.runtimeName),
|
||||
);
|
||||
for (const skillName of removedSkills) {
|
||||
await onLog(
|
||||
|
|
@ -134,19 +138,19 @@ export async function ensureCursorSkillsInjected(
|
|||
}
|
||||
const linkSkill = options.linkSkill ?? ((source: string, target: string) => fs.symlink(source, target));
|
||||
for (const entry of skillsEntries) {
|
||||
const target = path.join(skillsHome, entry.name);
|
||||
const target = path.join(skillsHome, entry.runtimeName);
|
||||
try {
|
||||
const result = await ensurePaperclipSkillSymlink(entry.source, target, linkSkill);
|
||||
if (result === "skipped") continue;
|
||||
|
||||
await onLog(
|
||||
"stderr",
|
||||
`[paperclip] ${result === "repaired" ? "Repaired" : "Injected"} Cursor skill "${entry.name}" into ${skillsHome}\n`,
|
||||
`[paperclip] ${result === "repaired" ? "Repaired" : "Injected"} Cursor skill "${entry.key}" into ${skillsHome}\n`,
|
||||
);
|
||||
} catch (err) {
|
||||
await onLog(
|
||||
"stderr",
|
||||
`[paperclip] Failed to inject Cursor skill "${entry.name}" into ${skillsHome}: ${err instanceof Error ? err.message : String(err)}\n`,
|
||||
`[paperclip] Failed to inject Cursor skill "${entry.key}" into ${skillsHome}: ${err instanceof Error ? err.message : String(err)}\n`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -183,7 +187,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
|
|||
const cursorSkillEntries = await readPaperclipRuntimeSkillEntries(config, __moduleDir);
|
||||
const desiredCursorSkillNames = resolvePaperclipDesiredSkillNames(config, cursorSkillEntries);
|
||||
await ensureCursorSkillsInjected(onLog, {
|
||||
skillsEntries: cursorSkillEntries.filter((entry) => desiredCursorSkillNames.includes(entry.name)),
|
||||
skillsEntries: cursorSkillEntries.filter((entry) => desiredCursorSkillNames.includes(entry.key)),
|
||||
});
|
||||
|
||||
const envConfig = parseObject(config.env);
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ async function readInstalledSkillTargets(skillsHome: string) {
|
|||
|
||||
async function buildCursorSkillSnapshot(config: Record<string, unknown>): Promise<AdapterSkillSnapshot> {
|
||||
const availableEntries = await readPaperclipRuntimeSkillEntries(config, __moduleDir);
|
||||
const availableByName = new Map(availableEntries.map((entry) => [entry.name, entry]));
|
||||
const availableByKey = new Map(availableEntries.map((entry) => [entry.key, entry]));
|
||||
const desiredSkills = resolvePaperclipDesiredSkillNames(config, availableEntries);
|
||||
const desiredSet = new Set(desiredSkills);
|
||||
const skillsHome = resolveCursorSkillsHome(config);
|
||||
|
|
@ -62,8 +62,8 @@ async function buildCursorSkillSnapshot(config: Record<string, unknown>): Promis
|
|||
const warnings: string[] = [];
|
||||
|
||||
for (const available of availableEntries) {
|
||||
const installedEntry = installed.get(available.name) ?? null;
|
||||
const desired = desiredSet.has(available.name);
|
||||
const installedEntry = installed.get(available.runtimeName) ?? null;
|
||||
const desired = desiredSet.has(available.key);
|
||||
let state: AdapterSkillEntry["state"] = "available";
|
||||
let managed = false;
|
||||
let detail: string | null = null;
|
||||
|
|
@ -82,12 +82,13 @@ async function buildCursorSkillSnapshot(config: Record<string, unknown>): Promis
|
|||
}
|
||||
|
||||
entries.push({
|
||||
name: available.name,
|
||||
key: available.key,
|
||||
runtimeName: available.runtimeName,
|
||||
desired,
|
||||
managed,
|
||||
state,
|
||||
sourcePath: available.source,
|
||||
targetPath: path.join(skillsHome, available.name),
|
||||
targetPath: path.join(skillsHome, available.runtimeName),
|
||||
detail,
|
||||
required: Boolean(available.required),
|
||||
requiredReason: available.requiredReason ?? null,
|
||||
|
|
@ -95,23 +96,25 @@ async function buildCursorSkillSnapshot(config: Record<string, unknown>): Promis
|
|||
}
|
||||
|
||||
for (const desiredSkill of desiredSkills) {
|
||||
if (availableByName.has(desiredSkill)) continue;
|
||||
if (availableByKey.has(desiredSkill)) continue;
|
||||
warnings.push(`Desired skill "${desiredSkill}" is not available from the Paperclip skills directory.`);
|
||||
entries.push({
|
||||
name: desiredSkill,
|
||||
key: desiredSkill,
|
||||
runtimeName: null,
|
||||
desired: true,
|
||||
managed: true,
|
||||
state: "missing",
|
||||
sourcePath: null,
|
||||
targetPath: path.join(skillsHome, desiredSkill),
|
||||
targetPath: null,
|
||||
detail: "Paperclip cannot find this skill in the local runtime skills directory.",
|
||||
});
|
||||
}
|
||||
|
||||
for (const [name, installedEntry] of installed.entries()) {
|
||||
if (availableByName.has(name)) continue;
|
||||
if (availableEntries.some((entry) => entry.runtimeName === name)) continue;
|
||||
entries.push({
|
||||
name,
|
||||
key: name,
|
||||
runtimeName: name,
|
||||
desired: false,
|
||||
managed: false,
|
||||
state: "external",
|
||||
|
|
@ -121,7 +124,7 @@ async function buildCursorSkillSnapshot(config: Record<string, unknown>): Promis
|
|||
});
|
||||
}
|
||||
|
||||
entries.sort((left, right) => left.name.localeCompare(right.name));
|
||||
entries.sort((left, right) => left.key.localeCompare(right.key));
|
||||
|
||||
return {
|
||||
adapterType: "cursor",
|
||||
|
|
@ -144,23 +147,23 @@ export async function syncCursorSkills(
|
|||
const availableEntries = await readPaperclipRuntimeSkillEntries(ctx.config, __moduleDir);
|
||||
const desiredSet = new Set([
|
||||
...desiredSkills,
|
||||
...availableEntries.filter((entry) => entry.required).map((entry) => entry.name),
|
||||
...availableEntries.filter((entry) => entry.required).map((entry) => entry.key),
|
||||
]);
|
||||
const skillsHome = resolveCursorSkillsHome(ctx.config);
|
||||
await fs.mkdir(skillsHome, { recursive: true });
|
||||
const installed = await readInstalledSkillTargets(skillsHome);
|
||||
const availableByName = new Map(availableEntries.map((entry) => [entry.name, entry]));
|
||||
const availableByRuntimeName = new Map(availableEntries.map((entry) => [entry.runtimeName, entry]));
|
||||
|
||||
for (const available of availableEntries) {
|
||||
if (!desiredSet.has(available.name)) continue;
|
||||
const target = path.join(skillsHome, available.name);
|
||||
if (!desiredSet.has(available.key)) continue;
|
||||
const target = path.join(skillsHome, available.runtimeName);
|
||||
await ensurePaperclipSkillSymlink(available.source, target);
|
||||
}
|
||||
|
||||
for (const [name, installedEntry] of installed.entries()) {
|
||||
const available = availableByName.get(name);
|
||||
const available = availableByRuntimeName.get(name);
|
||||
if (!available) continue;
|
||||
if (desiredSet.has(name)) continue;
|
||||
if (desiredSet.has(available.key)) continue;
|
||||
if (installedEntry.targetPath !== available.source) continue;
|
||||
await fs.unlink(path.join(skillsHome, name)).catch(() => {});
|
||||
}
|
||||
|
|
@ -170,7 +173,7 @@ export async function syncCursorSkills(
|
|||
|
||||
export function resolveCursorDesiredSkillNames(
|
||||
config: Record<string, unknown>,
|
||||
availableEntries: Array<{ name: string; required?: boolean }>,
|
||||
availableEntries: Array<{ key: string; required?: boolean }>,
|
||||
) {
|
||||
return resolvePaperclipDesiredSkillNames(config, availableEntries);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,11 +85,11 @@ function geminiSkillsHome(): string {
|
|||
*/
|
||||
async function ensureGeminiSkillsInjected(
|
||||
onLog: AdapterExecutionContext["onLog"],
|
||||
skillsEntries: Array<{ name: string; source: string }>,
|
||||
skillsEntries: Array<{ key: string; runtimeName: string; source: string }>,
|
||||
desiredSkillNames?: string[],
|
||||
): Promise<void> {
|
||||
const desiredSet = new Set(desiredSkillNames ?? skillsEntries.map((entry) => entry.name));
|
||||
const selectedEntries = skillsEntries.filter((entry) => desiredSet.has(entry.name));
|
||||
const desiredSet = new Set(desiredSkillNames ?? skillsEntries.map((entry) => entry.key));
|
||||
const selectedEntries = skillsEntries.filter((entry) => desiredSet.has(entry.key));
|
||||
if (selectedEntries.length === 0) return;
|
||||
|
||||
const skillsHome = geminiSkillsHome();
|
||||
|
|
@ -104,7 +104,7 @@ async function ensureGeminiSkillsInjected(
|
|||
}
|
||||
const removedSkills = await removeMaintainerOnlySkillSymlinks(
|
||||
skillsHome,
|
||||
selectedEntries.map((entry) => entry.name),
|
||||
selectedEntries.map((entry) => entry.runtimeName),
|
||||
);
|
||||
for (const skillName of removedSkills) {
|
||||
await onLog(
|
||||
|
|
@ -114,19 +114,19 @@ async function ensureGeminiSkillsInjected(
|
|||
}
|
||||
|
||||
for (const entry of selectedEntries) {
|
||||
const target = path.join(skillsHome, entry.name);
|
||||
const target = path.join(skillsHome, entry.runtimeName);
|
||||
|
||||
try {
|
||||
const result = await ensurePaperclipSkillSymlink(entry.source, target);
|
||||
if (result === "skipped") continue;
|
||||
await onLog(
|
||||
"stderr",
|
||||
`[paperclip] ${result === "repaired" ? "Repaired" : "Linked"} Gemini skill: ${entry.name}\n`,
|
||||
`[paperclip] ${result === "repaired" ? "Repaired" : "Linked"} Gemini skill: ${entry.key}\n`,
|
||||
);
|
||||
} catch (err) {
|
||||
await onLog(
|
||||
"stderr",
|
||||
`[paperclip] Failed to link Gemini skill "${entry.name}": ${err instanceof Error ? err.message : String(err)}\n`,
|
||||
`[paperclip] Failed to link Gemini skill "${entry.key}": ${err instanceof Error ? err.message : String(err)}\n`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ async function readInstalledSkillTargets(skillsHome: string) {
|
|||
|
||||
async function buildGeminiSkillSnapshot(config: Record<string, unknown>): Promise<AdapterSkillSnapshot> {
|
||||
const availableEntries = await readPaperclipRuntimeSkillEntries(config, __moduleDir);
|
||||
const availableByName = new Map(availableEntries.map((entry) => [entry.name, entry]));
|
||||
const availableByKey = new Map(availableEntries.map((entry) => [entry.key, entry]));
|
||||
const desiredSkills = resolvePaperclipDesiredSkillNames(config, availableEntries);
|
||||
const desiredSet = new Set(desiredSkills);
|
||||
const skillsHome = resolveGeminiSkillsHome(config);
|
||||
|
|
@ -62,8 +62,8 @@ async function buildGeminiSkillSnapshot(config: Record<string, unknown>): Promis
|
|||
const warnings: string[] = [];
|
||||
|
||||
for (const available of availableEntries) {
|
||||
const installedEntry = installed.get(available.name) ?? null;
|
||||
const desired = desiredSet.has(available.name);
|
||||
const installedEntry = installed.get(available.runtimeName) ?? null;
|
||||
const desired = desiredSet.has(available.key);
|
||||
let state: AdapterSkillEntry["state"] = "available";
|
||||
let managed = false;
|
||||
let detail: string | null = null;
|
||||
|
|
@ -82,12 +82,13 @@ async function buildGeminiSkillSnapshot(config: Record<string, unknown>): Promis
|
|||
}
|
||||
|
||||
entries.push({
|
||||
name: available.name,
|
||||
key: available.key,
|
||||
runtimeName: available.runtimeName,
|
||||
desired,
|
||||
managed,
|
||||
state,
|
||||
sourcePath: available.source,
|
||||
targetPath: path.join(skillsHome, available.name),
|
||||
targetPath: path.join(skillsHome, available.runtimeName),
|
||||
detail,
|
||||
required: Boolean(available.required),
|
||||
requiredReason: available.requiredReason ?? null,
|
||||
|
|
@ -95,23 +96,25 @@ async function buildGeminiSkillSnapshot(config: Record<string, unknown>): Promis
|
|||
}
|
||||
|
||||
for (const desiredSkill of desiredSkills) {
|
||||
if (availableByName.has(desiredSkill)) continue;
|
||||
if (availableByKey.has(desiredSkill)) continue;
|
||||
warnings.push(`Desired skill "${desiredSkill}" is not available from the Paperclip skills directory.`);
|
||||
entries.push({
|
||||
name: desiredSkill,
|
||||
key: desiredSkill,
|
||||
runtimeName: null,
|
||||
desired: true,
|
||||
managed: true,
|
||||
state: "missing",
|
||||
sourcePath: null,
|
||||
targetPath: path.join(skillsHome, desiredSkill),
|
||||
targetPath: null,
|
||||
detail: "Paperclip cannot find this skill in the local runtime skills directory.",
|
||||
});
|
||||
}
|
||||
|
||||
for (const [name, installedEntry] of installed.entries()) {
|
||||
if (availableByName.has(name)) continue;
|
||||
if (availableEntries.some((entry) => entry.runtimeName === name)) continue;
|
||||
entries.push({
|
||||
name,
|
||||
key: name,
|
||||
runtimeName: name,
|
||||
desired: false,
|
||||
managed: false,
|
||||
state: "external",
|
||||
|
|
@ -121,7 +124,7 @@ async function buildGeminiSkillSnapshot(config: Record<string, unknown>): Promis
|
|||
});
|
||||
}
|
||||
|
||||
entries.sort((left, right) => left.name.localeCompare(right.name));
|
||||
entries.sort((left, right) => left.key.localeCompare(right.key));
|
||||
|
||||
return {
|
||||
adapterType: "gemini_local",
|
||||
|
|
@ -144,23 +147,23 @@ export async function syncGeminiSkills(
|
|||
const availableEntries = await readPaperclipRuntimeSkillEntries(ctx.config, __moduleDir);
|
||||
const desiredSet = new Set([
|
||||
...desiredSkills,
|
||||
...availableEntries.filter((entry) => entry.required).map((entry) => entry.name),
|
||||
...availableEntries.filter((entry) => entry.required).map((entry) => entry.key),
|
||||
]);
|
||||
const skillsHome = resolveGeminiSkillsHome(ctx.config);
|
||||
await fs.mkdir(skillsHome, { recursive: true });
|
||||
const installed = await readInstalledSkillTargets(skillsHome);
|
||||
const availableByName = new Map(availableEntries.map((entry) => [entry.name, entry]));
|
||||
const availableByRuntimeName = new Map(availableEntries.map((entry) => [entry.runtimeName, entry]));
|
||||
|
||||
for (const available of availableEntries) {
|
||||
if (!desiredSet.has(available.name)) continue;
|
||||
const target = path.join(skillsHome, available.name);
|
||||
if (!desiredSet.has(available.key)) continue;
|
||||
const target = path.join(skillsHome, available.runtimeName);
|
||||
await ensurePaperclipSkillSymlink(available.source, target);
|
||||
}
|
||||
|
||||
for (const [name, installedEntry] of installed.entries()) {
|
||||
const available = availableByName.get(name);
|
||||
const available = availableByRuntimeName.get(name);
|
||||
if (!available) continue;
|
||||
if (desiredSet.has(name)) continue;
|
||||
if (desiredSet.has(available.key)) continue;
|
||||
if (installedEntry.targetPath !== available.source) continue;
|
||||
await fs.unlink(path.join(skillsHome, name)).catch(() => {});
|
||||
}
|
||||
|
|
@ -170,7 +173,7 @@ export async function syncGeminiSkills(
|
|||
|
||||
export function resolveGeminiDesiredSkillNames(
|
||||
config: Record<string, unknown>,
|
||||
availableEntries: Array<{ name: string; required?: boolean }>,
|
||||
availableEntries: Array<{ key: string; required?: boolean }>,
|
||||
) {
|
||||
return resolvePaperclipDesiredSkillNames(config, availableEntries);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,16 +52,16 @@ function claudeSkillsHome(): string {
|
|||
|
||||
async function ensureOpenCodeSkillsInjected(
|
||||
onLog: AdapterExecutionContext["onLog"],
|
||||
skillsEntries: Array<{ name: string; source: string }>,
|
||||
skillsEntries: Array<{ key: string; runtimeName: string; source: string }>,
|
||||
desiredSkillNames?: string[],
|
||||
) {
|
||||
const skillsHome = claudeSkillsHome();
|
||||
await fs.mkdir(skillsHome, { recursive: true });
|
||||
const desiredSet = new Set(desiredSkillNames ?? skillsEntries.map((entry) => entry.name));
|
||||
const selectedEntries = skillsEntries.filter((entry) => desiredSet.has(entry.name));
|
||||
const desiredSet = new Set(desiredSkillNames ?? skillsEntries.map((entry) => entry.key));
|
||||
const selectedEntries = skillsEntries.filter((entry) => desiredSet.has(entry.key));
|
||||
const removedSkills = await removeMaintainerOnlySkillSymlinks(
|
||||
skillsHome,
|
||||
selectedEntries.map((entry) => entry.name),
|
||||
selectedEntries.map((entry) => entry.runtimeName),
|
||||
);
|
||||
for (const skillName of removedSkills) {
|
||||
await onLog(
|
||||
|
|
@ -70,19 +70,19 @@ async function ensureOpenCodeSkillsInjected(
|
|||
);
|
||||
}
|
||||
for (const entry of selectedEntries) {
|
||||
const target = path.join(skillsHome, entry.name);
|
||||
const target = path.join(skillsHome, entry.runtimeName);
|
||||
|
||||
try {
|
||||
const result = await ensurePaperclipSkillSymlink(entry.source, target);
|
||||
if (result === "skipped") continue;
|
||||
await onLog(
|
||||
"stderr",
|
||||
`[paperclip] ${result === "repaired" ? "Repaired" : "Injected"} OpenCode skill "${entry.name}" into ${skillsHome}\n`,
|
||||
`[paperclip] ${result === "repaired" ? "Repaired" : "Injected"} OpenCode skill "${entry.key}" into ${skillsHome}\n`,
|
||||
);
|
||||
} catch (err) {
|
||||
await onLog(
|
||||
"stderr",
|
||||
`[paperclip] Failed to inject OpenCode skill "${entry.name}" into ${skillsHome}: ${err instanceof Error ? err.message : String(err)}\n`,
|
||||
`[paperclip] Failed to inject OpenCode skill "${entry.key}" into ${skillsHome}: ${err instanceof Error ? err.message : String(err)}\n`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ async function readInstalledSkillTargets(skillsHome: string) {
|
|||
|
||||
async function buildOpenCodeSkillSnapshot(config: Record<string, unknown>): Promise<AdapterSkillSnapshot> {
|
||||
const availableEntries = await readPaperclipRuntimeSkillEntries(config, __moduleDir);
|
||||
const availableByName = new Map(availableEntries.map((entry) => [entry.name, entry]));
|
||||
const availableByKey = new Map(availableEntries.map((entry) => [entry.key, entry]));
|
||||
const desiredSkills = resolvePaperclipDesiredSkillNames(config, availableEntries);
|
||||
const desiredSet = new Set(desiredSkills);
|
||||
const skillsHome = resolveOpenCodeSkillsHome(config);
|
||||
|
|
@ -64,8 +64,8 @@ async function buildOpenCodeSkillSnapshot(config: Record<string, unknown>): Prom
|
|||
];
|
||||
|
||||
for (const available of availableEntries) {
|
||||
const installedEntry = installed.get(available.name) ?? null;
|
||||
const desired = desiredSet.has(available.name);
|
||||
const installedEntry = installed.get(available.runtimeName) ?? null;
|
||||
const desired = desiredSet.has(available.key);
|
||||
let state: AdapterSkillEntry["state"] = "available";
|
||||
let managed = false;
|
||||
let detail: string | null = null;
|
||||
|
|
@ -85,12 +85,13 @@ async function buildOpenCodeSkillSnapshot(config: Record<string, unknown>): Prom
|
|||
}
|
||||
|
||||
entries.push({
|
||||
name: available.name,
|
||||
key: available.key,
|
||||
runtimeName: available.runtimeName,
|
||||
desired,
|
||||
managed,
|
||||
state,
|
||||
sourcePath: available.source,
|
||||
targetPath: path.join(skillsHome, available.name),
|
||||
targetPath: path.join(skillsHome, available.runtimeName),
|
||||
detail,
|
||||
required: Boolean(available.required),
|
||||
requiredReason: available.requiredReason ?? null,
|
||||
|
|
@ -98,23 +99,25 @@ async function buildOpenCodeSkillSnapshot(config: Record<string, unknown>): Prom
|
|||
}
|
||||
|
||||
for (const desiredSkill of desiredSkills) {
|
||||
if (availableByName.has(desiredSkill)) continue;
|
||||
if (availableByKey.has(desiredSkill)) continue;
|
||||
warnings.push(`Desired skill "${desiredSkill}" is not available from the Paperclip skills directory.`);
|
||||
entries.push({
|
||||
name: desiredSkill,
|
||||
key: desiredSkill,
|
||||
runtimeName: null,
|
||||
desired: true,
|
||||
managed: true,
|
||||
state: "missing",
|
||||
sourcePath: null,
|
||||
targetPath: path.join(skillsHome, desiredSkill),
|
||||
targetPath: null,
|
||||
detail: "Paperclip cannot find this skill in the local runtime skills directory.",
|
||||
});
|
||||
}
|
||||
|
||||
for (const [name, installedEntry] of installed.entries()) {
|
||||
if (availableByName.has(name)) continue;
|
||||
if (availableEntries.some((entry) => entry.runtimeName === name)) continue;
|
||||
entries.push({
|
||||
name,
|
||||
key: name,
|
||||
runtimeName: name,
|
||||
desired: false,
|
||||
managed: false,
|
||||
state: "external",
|
||||
|
|
@ -124,7 +127,7 @@ async function buildOpenCodeSkillSnapshot(config: Record<string, unknown>): Prom
|
|||
});
|
||||
}
|
||||
|
||||
entries.sort((left, right) => left.name.localeCompare(right.name));
|
||||
entries.sort((left, right) => left.key.localeCompare(right.key));
|
||||
|
||||
return {
|
||||
adapterType: "opencode_local",
|
||||
|
|
@ -147,23 +150,23 @@ export async function syncOpenCodeSkills(
|
|||
const availableEntries = await readPaperclipRuntimeSkillEntries(ctx.config, __moduleDir);
|
||||
const desiredSet = new Set([
|
||||
...desiredSkills,
|
||||
...availableEntries.filter((entry) => entry.required).map((entry) => entry.name),
|
||||
...availableEntries.filter((entry) => entry.required).map((entry) => entry.key),
|
||||
]);
|
||||
const skillsHome = resolveOpenCodeSkillsHome(ctx.config);
|
||||
await fs.mkdir(skillsHome, { recursive: true });
|
||||
const installed = await readInstalledSkillTargets(skillsHome);
|
||||
const availableByName = new Map(availableEntries.map((entry) => [entry.name, entry]));
|
||||
const availableByRuntimeName = new Map(availableEntries.map((entry) => [entry.runtimeName, entry]));
|
||||
|
||||
for (const available of availableEntries) {
|
||||
if (!desiredSet.has(available.name)) continue;
|
||||
const target = path.join(skillsHome, available.name);
|
||||
if (!desiredSet.has(available.key)) continue;
|
||||
const target = path.join(skillsHome, available.runtimeName);
|
||||
await ensurePaperclipSkillSymlink(available.source, target);
|
||||
}
|
||||
|
||||
for (const [name, installedEntry] of installed.entries()) {
|
||||
const available = availableByName.get(name);
|
||||
const available = availableByRuntimeName.get(name);
|
||||
if (!available) continue;
|
||||
if (desiredSet.has(name)) continue;
|
||||
if (desiredSet.has(available.key)) continue;
|
||||
if (installedEntry.targetPath !== available.source) continue;
|
||||
await fs.unlink(path.join(skillsHome, name)).catch(() => {});
|
||||
}
|
||||
|
|
@ -173,7 +176,7 @@ export async function syncOpenCodeSkills(
|
|||
|
||||
export function resolveOpenCodeDesiredSkillNames(
|
||||
config: Record<string, unknown>,
|
||||
availableEntries: Array<{ name: string; required?: boolean }>,
|
||||
availableEntries: Array<{ key: string; required?: boolean }>,
|
||||
) {
|
||||
return resolvePaperclipDesiredSkillNames(config, availableEntries);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,17 +53,17 @@ function parseModelId(model: string | null): string | null {
|
|||
|
||||
async function ensurePiSkillsInjected(
|
||||
onLog: AdapterExecutionContext["onLog"],
|
||||
skillsEntries: Array<{ name: string; source: string }>,
|
||||
skillsEntries: Array<{ key: string; runtimeName: string; source: string }>,
|
||||
desiredSkillNames?: string[],
|
||||
) {
|
||||
const desiredSet = new Set(desiredSkillNames ?? skillsEntries.map((entry) => entry.name));
|
||||
const selectedEntries = skillsEntries.filter((entry) => desiredSet.has(entry.name));
|
||||
const desiredSet = new Set(desiredSkillNames ?? skillsEntries.map((entry) => entry.key));
|
||||
const selectedEntries = skillsEntries.filter((entry) => desiredSet.has(entry.key));
|
||||
if (selectedEntries.length === 0) return;
|
||||
const piSkillsHome = path.join(os.homedir(), ".pi", "agent", "skills");
|
||||
await fs.mkdir(piSkillsHome, { recursive: true });
|
||||
const removedSkills = await removeMaintainerOnlySkillSymlinks(
|
||||
piSkillsHome,
|
||||
selectedEntries.map((entry) => entry.name),
|
||||
selectedEntries.map((entry) => entry.runtimeName),
|
||||
);
|
||||
for (const skillName of removedSkills) {
|
||||
await onLog(
|
||||
|
|
@ -73,19 +73,19 @@ async function ensurePiSkillsInjected(
|
|||
}
|
||||
|
||||
for (const entry of selectedEntries) {
|
||||
const target = path.join(piSkillsHome, entry.name);
|
||||
const target = path.join(piSkillsHome, entry.runtimeName);
|
||||
|
||||
try {
|
||||
const result = await ensurePaperclipSkillSymlink(entry.source, target);
|
||||
if (result === "skipped") continue;
|
||||
await onLog(
|
||||
"stderr",
|
||||
`[paperclip] ${result === "repaired" ? "Repaired" : "Injected"} Pi skill "${entry.name}" into ${piSkillsHome}\n`,
|
||||
`[paperclip] ${result === "repaired" ? "Repaired" : "Injected"} Pi skill "${entry.key}" into ${piSkillsHome}\n`,
|
||||
);
|
||||
} catch (err) {
|
||||
await onLog(
|
||||
"stderr",
|
||||
`[paperclip] Failed to inject Pi skill "${entry.name}" into ${piSkillsHome}: ${err instanceof Error ? err.message : String(err)}\n`,
|
||||
`[paperclip] Failed to inject Pi skill "${entry.key}" into ${piSkillsHome}: ${err instanceof Error ? err.message : String(err)}\n`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ async function readInstalledSkillTargets(skillsHome: string) {
|
|||
|
||||
async function buildPiSkillSnapshot(config: Record<string, unknown>): Promise<AdapterSkillSnapshot> {
|
||||
const availableEntries = await readPaperclipRuntimeSkillEntries(config, __moduleDir);
|
||||
const availableByName = new Map(availableEntries.map((entry) => [entry.name, entry]));
|
||||
const availableByKey = new Map(availableEntries.map((entry) => [entry.key, entry]));
|
||||
const desiredSkills = resolvePaperclipDesiredSkillNames(config, availableEntries);
|
||||
const desiredSet = new Set(desiredSkills);
|
||||
const skillsHome = resolvePiSkillsHome(config);
|
||||
|
|
@ -62,8 +62,8 @@ async function buildPiSkillSnapshot(config: Record<string, unknown>): Promise<Ad
|
|||
const warnings: string[] = [];
|
||||
|
||||
for (const available of availableEntries) {
|
||||
const installedEntry = installed.get(available.name) ?? null;
|
||||
const desired = desiredSet.has(available.name);
|
||||
const installedEntry = installed.get(available.runtimeName) ?? null;
|
||||
const desired = desiredSet.has(available.key);
|
||||
let state: AdapterSkillEntry["state"] = "available";
|
||||
let managed = false;
|
||||
let detail: string | null = null;
|
||||
|
|
@ -82,12 +82,13 @@ async function buildPiSkillSnapshot(config: Record<string, unknown>): Promise<Ad
|
|||
}
|
||||
|
||||
entries.push({
|
||||
name: available.name,
|
||||
key: available.key,
|
||||
runtimeName: available.runtimeName,
|
||||
desired,
|
||||
managed,
|
||||
state,
|
||||
sourcePath: available.source,
|
||||
targetPath: path.join(skillsHome, available.name),
|
||||
targetPath: path.join(skillsHome, available.runtimeName),
|
||||
detail,
|
||||
required: Boolean(available.required),
|
||||
requiredReason: available.requiredReason ?? null,
|
||||
|
|
@ -95,23 +96,25 @@ async function buildPiSkillSnapshot(config: Record<string, unknown>): Promise<Ad
|
|||
}
|
||||
|
||||
for (const desiredSkill of desiredSkills) {
|
||||
if (availableByName.has(desiredSkill)) continue;
|
||||
if (availableByKey.has(desiredSkill)) continue;
|
||||
warnings.push(`Desired skill "${desiredSkill}" is not available from the Paperclip skills directory.`);
|
||||
entries.push({
|
||||
name: desiredSkill,
|
||||
key: desiredSkill,
|
||||
runtimeName: null,
|
||||
desired: true,
|
||||
managed: true,
|
||||
state: "missing",
|
||||
sourcePath: null,
|
||||
targetPath: path.join(skillsHome, desiredSkill),
|
||||
targetPath: null,
|
||||
detail: "Paperclip cannot find this skill in the local runtime skills directory.",
|
||||
});
|
||||
}
|
||||
|
||||
for (const [name, installedEntry] of installed.entries()) {
|
||||
if (availableByName.has(name)) continue;
|
||||
if (availableEntries.some((entry) => entry.runtimeName === name)) continue;
|
||||
entries.push({
|
||||
name,
|
||||
key: name,
|
||||
runtimeName: name,
|
||||
desired: false,
|
||||
managed: false,
|
||||
state: "external",
|
||||
|
|
@ -121,7 +124,7 @@ async function buildPiSkillSnapshot(config: Record<string, unknown>): Promise<Ad
|
|||
});
|
||||
}
|
||||
|
||||
entries.sort((left, right) => left.name.localeCompare(right.name));
|
||||
entries.sort((left, right) => left.key.localeCompare(right.key));
|
||||
|
||||
return {
|
||||
adapterType: "pi_local",
|
||||
|
|
@ -144,23 +147,23 @@ export async function syncPiSkills(
|
|||
const availableEntries = await readPaperclipRuntimeSkillEntries(ctx.config, __moduleDir);
|
||||
const desiredSet = new Set([
|
||||
...desiredSkills,
|
||||
...availableEntries.filter((entry) => entry.required).map((entry) => entry.name),
|
||||
...availableEntries.filter((entry) => entry.required).map((entry) => entry.key),
|
||||
]);
|
||||
const skillsHome = resolvePiSkillsHome(ctx.config);
|
||||
await fs.mkdir(skillsHome, { recursive: true });
|
||||
const installed = await readInstalledSkillTargets(skillsHome);
|
||||
const availableByName = new Map(availableEntries.map((entry) => [entry.name, entry]));
|
||||
const availableByRuntimeName = new Map(availableEntries.map((entry) => [entry.runtimeName, entry]));
|
||||
|
||||
for (const available of availableEntries) {
|
||||
if (!desiredSet.has(available.name)) continue;
|
||||
const target = path.join(skillsHome, available.name);
|
||||
if (!desiredSet.has(available.key)) continue;
|
||||
const target = path.join(skillsHome, available.runtimeName);
|
||||
await ensurePaperclipSkillSymlink(available.source, target);
|
||||
}
|
||||
|
||||
for (const [name, installedEntry] of installed.entries()) {
|
||||
const available = availableByName.get(name);
|
||||
const available = availableByRuntimeName.get(name);
|
||||
if (!available) continue;
|
||||
if (desiredSet.has(name)) continue;
|
||||
if (desiredSet.has(available.key)) continue;
|
||||
if (installedEntry.targetPath !== available.source) continue;
|
||||
await fs.unlink(path.join(skillsHome, name)).catch(() => {});
|
||||
}
|
||||
|
|
@ -170,7 +173,7 @@ export async function syncPiSkills(
|
|||
|
||||
export function resolvePiDesiredSkillNames(
|
||||
config: Record<string, unknown>,
|
||||
availableEntries: Array<{ name: string; required?: boolean }>,
|
||||
availableEntries: Array<{ key: string; required?: boolean }>,
|
||||
) {
|
||||
return resolvePaperclipDesiredSkillNames(config, availableEntries);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue