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 | * Skills Loader |
| #3 | * |
| #4 | * Discovers and loads SKILL.md files from ~/.automaton/skills/ |
| #5 | * Each skill is a directory containing a SKILL.md file with |
| #6 | * YAML frontmatter + Markdown instructions. |
| #7 | */ |
| #8 | |
| #9 | import fs from "fs"; |
| #10 | import path from "path"; |
| #11 | import type { Skill, AutomatonDatabase } from "../types.js"; |
| #12 | import { parseSkillMd } from "./format.js"; |
| #13 | |
| #14 | /** |
| #15 | * Scan the skills directory and load all valid SKILL.md files. |
| #16 | * Returns loaded skills and syncs them to the database. |
| #17 | */ |
| #18 | export function loadSkills( |
| #19 | skillsDir: string, |
| #20 | db: AutomatonDatabase, |
| #21 | ): Skill[] { |
| #22 | const resolvedDir = resolveHome(skillsDir); |
| #23 | |
| #24 | if (!fs.existsSync(resolvedDir)) { |
| #25 | return db.getSkills(true); |
| #26 | } |
| #27 | |
| #28 | const entries = fs.readdirSync(resolvedDir, { withFileTypes: true }); |
| #29 | const loaded: Skill[] = []; |
| #30 | |
| #31 | for (const entry of entries) { |
| #32 | if (!entry.isDirectory()) continue; |
| #33 | |
| #34 | const skillMdPath = path.join(resolvedDir, entry.name, "SKILL.md"); |
| #35 | if (!fs.existsSync(skillMdPath)) continue; |
| #36 | |
| #37 | try { |
| #38 | const content = fs.readFileSync(skillMdPath, "utf-8"); |
| #39 | const skill = parseSkillMd(content, skillMdPath); |
| #40 | if (!skill) continue; |
| #41 | |
| #42 | // Check requirements |
| #43 | if (!checkRequirements(skill)) { |
| #44 | continue; |
| #45 | } |
| #46 | |
| #47 | // Check if already in DB and preserve enabled state |
| #48 | const existing = db.getSkillByName(skill.name); |
| #49 | if (existing) { |
| #50 | skill.enabled = existing.enabled; |
| #51 | skill.installedAt = existing.installedAt; |
| #52 | } |
| #53 | |
| #54 | db.upsertSkill(skill); |
| #55 | loaded.push(skill); |
| #56 | } catch { |
| #57 | // Skip invalid skill files |
| #58 | } |
| #59 | } |
| #60 | |
| #61 | // Return all enabled skills (includes DB-only skills not on disk) |
| #62 | return db.getSkills(true); |
| #63 | } |
| #64 | |
| #65 | /** |
| #66 | * Check if a skill's requirements are met. |
| #67 | */ |
| #68 | function checkRequirements(skill: Skill): boolean { |
| #69 | if (!skill.requires) return true; |
| #70 | |
| #71 | // Check required binaries |
| #72 | if (skill.requires.bins) { |
| #73 | for (const bin of skill.requires.bins) { |
| #74 | try { |
| #75 | const { execSync } = require("child_process"); |
| #76 | execSync(`which ${bin}`, { stdio: "ignore" }); |
| #77 | } catch { |
| #78 | return false; |
| #79 | } |
| #80 | } |
| #81 | } |
| #82 | |
| #83 | // Check required environment variables |
| #84 | if (skill.requires.env) { |
| #85 | for (const envVar of skill.requires.env) { |
| #86 | if (!process.env[envVar]) { |
| #87 | return false; |
| #88 | } |
| #89 | } |
| #90 | } |
| #91 | |
| #92 | return true; |
| #93 | } |
| #94 | |
| #95 | /** |
| #96 | * Get the active skill instructions to inject into the system prompt. |
| #97 | * Only returns instructions from auto-activate skills that are enabled. |
| #98 | */ |
| #99 | export function getActiveSkillInstructions(skills: Skill[]): string { |
| #100 | const active = skills.filter((s) => s.enabled && s.autoActivate); |
| #101 | if (active.length === 0) return ""; |
| #102 | |
| #103 | const sections = active.map( |
| #104 | (s) => |
| #105 | `--- SKILL: ${s.name} ---\n${s.description ? `${s.description}\n\n` : ""}${s.instructions}\n--- END SKILL: ${s.name} ---`, |
| #106 | ); |
| #107 | |
| #108 | return sections.join("\n\n"); |
| #109 | } |
| #110 | |
| #111 | function resolveHome(p: string): string { |
| #112 | if (p.startsWith("~")) { |
| #113 | return path.join(process.env.HOME || "/root", p.slice(1)); |
| #114 | } |
| #115 | return p; |
| #116 | } |
| #117 |