UNPKG

@workflow-manager/runner

Version:

CLI runner for in-memory and markdown workflow orchestration using ATEP-like envelopes

76 lines (75 loc) 2.96 kB
import fs from "node:fs"; import os from "node:os"; import path from "node:path"; function readFileSafe(filePath) { try { if (!fs.existsSync(filePath)) return null; return fs.readFileSync(filePath, "utf-8"); } catch { return null; } } function isSafeSkillName(name) { if (!name || name.includes("..") || name.includes("/") || name.includes("\\")) return false; return /^[a-zA-Z0-9_.-]+$/.test(name); } function isAllowedLocalSkillSourcePath(source) { if (!source || path.isAbsolute(source) || source.includes("\\") || source.includes("..")) return false; const normalized = path.posix.normalize(source); const withoutDot = normalized.startsWith("./") ? normalized.slice(2) : normalized; if (!withoutDot.startsWith("skills/")) return false; return withoutDot.endsWith("/SKILL.md"); } function resolveAllowedLocalSkillSourcePath(workflowDir, source) { if (!isAllowedLocalSkillSourcePath(source)) return null; const sourcePath = path.resolve(workflowDir, source); const allowedRoot = path.resolve(workflowDir, "skills"); if (!sourcePath.startsWith(`${allowedRoot}${path.sep}`)) return null; if (path.basename(sourcePath) !== "SKILL.md") return null; return sourcePath; } function tryRead(...candidates) { for (const candidate of candidates) { const content = readFileSafe(candidate); if (content && content.trim()) return content; } return null; } export function resolveSkill(name, workflow, workflowFilePath) { const entry = workflow.skills?.[name]; if (entry?.content && entry.content.trim()) { return { content: entry.content, origin: "embedded" }; } if (entry?.source) { const workflowDir = path.dirname(path.resolve(workflowFilePath)); const sourcePath = resolveAllowedLocalSkillSourcePath(workflowDir, entry.source); if (!sourcePath) return null; const content = readFileSafe(sourcePath); if (content && content.trim()) return { content, origin: "source" }; return null; } if (!isSafeSkillName(name)) return null; const workflowDir = path.dirname(path.resolve(workflowFilePath)); const projectLocal = tryRead(path.join(workflowDir, "skills", name, "SKILL.md")); if (projectLocal) return { content: projectLocal, origin: "project-local" }; const userGlobal = tryRead(path.join(os.homedir(), ".workflow-manager", "skills", name, "SKILL.md")); if (userGlobal) return { content: userGlobal, origin: "user-global" }; const packaged = tryRead(path.join(workflowDir, "node_modules", "workflow-manager", "skills", name, "SKILL.md"), path.join(workflowDir, "..", "node_modules", "workflow-manager", "skills", name, "SKILL.md")); if (packaged) return { content: packaged, origin: "npm" }; return null; }