repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
public Clawd ADK gateway launch mirror
stars
latest
clone command
git clone gitlawb://did:key:z6Mkq5mY...iFZ5/my-project-publ...git clone gitlawb://did:key:z6Mkq5mY.../my-project-publ...2fa351d6docs: add automaton and perps launch sources16d ago| #1 | /** |
| #2 | * SKILL.md Parser |
| #3 | * |
| #4 | * Parses SKILL.md files with YAML frontmatter + Markdown body |
| #5 | * into structured skill definitions. |
| #6 | * Follows the SKILL.md convention (OpenClaw/Anthropic format). |
| #7 | */ |
| #8 | /** |
| #9 | * Parse a SKILL.md file content into frontmatter + body. |
| #10 | * Handles YAML frontmatter delimited by --- markers. |
| #11 | */ |
| #12 | export function parseSkillMd(content, filePath, source = "builtin") { |
| #13 | const trimmed = content.trim(); |
| #14 | if (!trimmed.startsWith("---")) { |
| #15 | // No frontmatter -- treat entire content as instructions |
| #16 | // with a name derived from the directory |
| #17 | const name = extractNameFromPath(filePath); |
| #18 | return { |
| #19 | name, |
| #20 | description: "", |
| #21 | autoActivate: true, |
| #22 | instructions: trimmed, |
| #23 | source, |
| #24 | path: filePath, |
| #25 | enabled: true, |
| #26 | installedAt: new Date().toISOString(), |
| #27 | }; |
| #28 | } |
| #29 | // Find the closing --- |
| #30 | const endIndex = trimmed.indexOf("---", 3); |
| #31 | if (endIndex === -1) { |
| #32 | return null; |
| #33 | } |
| #34 | const frontmatterRaw = trimmed.slice(3, endIndex).trim(); |
| #35 | const body = trimmed.slice(endIndex + 3).trim(); |
| #36 | // Parse YAML frontmatter manually (avoid requiring gray-matter at runtime) |
| #37 | const frontmatter = parseYamlFrontmatter(frontmatterRaw); |
| #38 | if (!frontmatter) { |
| #39 | return null; |
| #40 | } |
| #41 | return { |
| #42 | name: frontmatter.name || extractNameFromPath(filePath), |
| #43 | description: frontmatter.description || "", |
| #44 | autoActivate: frontmatter["auto-activate"] !== false, |
| #45 | requires: frontmatter.requires, |
| #46 | instructions: body, |
| #47 | source, |
| #48 | path: filePath, |
| #49 | enabled: true, |
| #50 | installedAt: new Date().toISOString(), |
| #51 | }; |
| #52 | } |
| #53 | /** |
| #54 | * Parse simple YAML frontmatter without a full YAML parser. |
| #55 | * Handles the subset used by SKILL.md files. |
| #56 | */ |
| #57 | function parseYamlFrontmatter(raw) { |
| #58 | try { |
| #59 | const result = {}; |
| #60 | const lines = raw.split("\n"); |
| #61 | let currentKey = ""; |
| #62 | let inList = false; |
| #63 | let listKey = ""; |
| #64 | for (const line of lines) { |
| #65 | const trimmedLine = line.trim(); |
| #66 | if (!trimmedLine || trimmedLine.startsWith("#")) |
| #67 | continue; |
| #68 | // Check for list items |
| #69 | if (trimmedLine.startsWith("- ") && inList) { |
| #70 | const value = trimmedLine.slice(2).trim().replace(/^["']|["']$/g, ""); |
| #71 | if (!result[listKey]) |
| #72 | result[listKey] = []; |
| #73 | if (Array.isArray(result[listKey])) { |
| #74 | result[listKey].push(value); |
| #75 | } |
| #76 | continue; |
| #77 | } |
| #78 | // Check for key: value |
| #79 | const colonIndex = trimmedLine.indexOf(":"); |
| #80 | if (colonIndex === -1) |
| #81 | continue; |
| #82 | const key = trimmedLine.slice(0, colonIndex).trim(); |
| #83 | const value = trimmedLine.slice(colonIndex + 1).trim(); |
| #84 | if (key === "requires") { |
| #85 | result.requires = {}; |
| #86 | currentKey = "requires"; |
| #87 | inList = false; |
| #88 | continue; |
| #89 | } |
| #90 | if (currentKey === "requires" && line.startsWith(" ")) { |
| #91 | // Nested under requires |
| #92 | const nestedKey = key.trim(); |
| #93 | if (!value || value === "") { |
| #94 | // Start of list |
| #95 | inList = true; |
| #96 | listKey = `requires.${nestedKey}`; |
| #97 | if (!result.requires) |
| #98 | result.requires = {}; |
| #99 | result.requires[nestedKey] = []; |
| #100 | } |
| #101 | else { |
| #102 | // Inline list: [item1, item2] |
| #103 | if (value.startsWith("[") && value.endsWith("]")) { |
| #104 | const items = value |
| #105 | .slice(1, -1) |
| #106 | .split(",") |
| #107 | .map((s) => s.trim().replace(/^["']|["']$/g, "")); |
| #108 | if (!result.requires) |
| #109 | result.requires = {}; |
| #110 | result.requires[nestedKey] = items; |
| #111 | } |
| #112 | } |
| #113 | continue; |
| #114 | } |
| #115 | inList = false; |
| #116 | currentKey = key; |
| #117 | if (!value) |
| #118 | continue; |
| #119 | // Parse value |
| #120 | if (value === "true") { |
| #121 | result[key] = true; |
| #122 | } |
| #123 | else if (value === "false") { |
| #124 | result[key] = false; |
| #125 | } |
| #126 | else { |
| #127 | result[key] = value.replace(/^["']|["']$/g, ""); |
| #128 | } |
| #129 | } |
| #130 | return result; |
| #131 | } |
| #132 | catch { |
| #133 | return null; |
| #134 | } |
| #135 | } |
| #136 | function extractNameFromPath(filePath) { |
| #137 | // Extract skill name from path like ~/.automaton/skills/web-scraper/SKILL.md |
| #138 | const parts = filePath.split("/"); |
| #139 | const skillMdIndex = parts.findIndex((p) => p.toLowerCase() === "skill.md"); |
| #140 | if (skillMdIndex > 0) { |
| #141 | return parts[skillMdIndex - 1]; |
| #142 | } |
| #143 | return parts[parts.length - 1].replace(/\.md$/i, ""); |
| #144 | } |
| #145 | //# sourceMappingURL=format.js.map |