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 Registry |
| #3 | * |
| #4 | * Install skills from remote sources: |
| #5 | * - Git repos: git clone <url> ~/.automaton/skills/<name> |
| #6 | * - URLs: fetch a SKILL.md from any URL |
| #7 | * - Self-created: the automaton writes its own SKILL.md files |
| #8 | */ |
| #9 | |
| #10 | import fs from "fs"; |
| #11 | import path from "path"; |
| #12 | import type { |
| #13 | Skill, |
| #14 | SkillSource, |
| #15 | AutomatonDatabase, |
| #16 | ConwayClient, |
| #17 | } from "../types.js"; |
| #18 | import { parseSkillMd } from "./format.js"; |
| #19 | |
| #20 | /** |
| #21 | * Install a skill from a git repository. |
| #22 | * Clones the repo into ~/.automaton/skills/<name>/ |
| #23 | */ |
| #24 | export async function installSkillFromGit( |
| #25 | repoUrl: string, |
| #26 | name: string, |
| #27 | skillsDir: string, |
| #28 | db: AutomatonDatabase, |
| #29 | conway: ConwayClient, |
| #30 | ): Promise<Skill | null> { |
| #31 | const resolvedDir = resolveHome(skillsDir); |
| #32 | const targetDir = path.join(resolvedDir, name); |
| #33 | |
| #34 | // Clone via sandbox exec |
| #35 | const result = await conway.exec( |
| #36 | `git clone --depth 1 ${repoUrl} ${targetDir}`, |
| #37 | 60000, |
| #38 | ); |
| #39 | |
| #40 | if (result.exitCode !== 0) { |
| #41 | throw new Error(`Failed to clone skill repo: ${result.stderr}`); |
| #42 | } |
| #43 | |
| #44 | // Look for SKILL.md |
| #45 | const skillMdPath = path.join(targetDir, "SKILL.md"); |
| #46 | const checkResult = await conway.exec(`cat ${skillMdPath}`, 5000); |
| #47 | |
| #48 | if (checkResult.exitCode !== 0) { |
| #49 | throw new Error(`No SKILL.md found in cloned repo at ${skillMdPath}`); |
| #50 | } |
| #51 | |
| #52 | const skill = parseSkillMd(checkResult.stdout, skillMdPath, "git"); |
| #53 | if (!skill) { |
| #54 | throw new Error("Failed to parse SKILL.md from cloned repo"); |
| #55 | } |
| #56 | |
| #57 | db.upsertSkill(skill); |
| #58 | return skill; |
| #59 | } |
| #60 | |
| #61 | /** |
| #62 | * Install a skill from a URL (fetches a single SKILL.md). |
| #63 | */ |
| #64 | export async function installSkillFromUrl( |
| #65 | url: string, |
| #66 | name: string, |
| #67 | skillsDir: string, |
| #68 | db: AutomatonDatabase, |
| #69 | conway: ConwayClient, |
| #70 | ): Promise<Skill | null> { |
| #71 | const resolvedDir = resolveHome(skillsDir); |
| #72 | const targetDir = path.join(resolvedDir, name); |
| #73 | |
| #74 | // Create directory |
| #75 | await conway.exec(`mkdir -p ${targetDir}`, 5000); |
| #76 | |
| #77 | // Fetch SKILL.md |
| #78 | const result = await conway.exec( |
| #79 | `curl -fsSL "${url}" -o ${targetDir}/SKILL.md`, |
| #80 | 30000, |
| #81 | ); |
| #82 | |
| #83 | if (result.exitCode !== 0) { |
| #84 | throw new Error(`Failed to fetch SKILL.md from URL: ${result.stderr}`); |
| #85 | } |
| #86 | |
| #87 | const content = await conway.exec( |
| #88 | `cat ${targetDir}/SKILL.md`, |
| #89 | 5000, |
| #90 | ); |
| #91 | |
| #92 | const skillMdPath = path.join(targetDir, "SKILL.md"); |
| #93 | const skill = parseSkillMd(content.stdout, skillMdPath, "url"); |
| #94 | if (!skill) { |
| #95 | throw new Error("Failed to parse fetched SKILL.md"); |
| #96 | } |
| #97 | |
| #98 | db.upsertSkill(skill); |
| #99 | return skill; |
| #100 | } |
| #101 | |
| #102 | /** |
| #103 | * Create a new skill authored by the automaton itself. |
| #104 | */ |
| #105 | export async function createSkill( |
| #106 | name: string, |
| #107 | description: string, |
| #108 | instructions: string, |
| #109 | skillsDir: string, |
| #110 | db: AutomatonDatabase, |
| #111 | conway: ConwayClient, |
| #112 | ): Promise<Skill> { |
| #113 | const resolvedDir = resolveHome(skillsDir); |
| #114 | const targetDir = path.join(resolvedDir, name); |
| #115 | |
| #116 | // Create directory |
| #117 | await conway.exec(`mkdir -p ${targetDir}`, 5000); |
| #118 | |
| #119 | // Write SKILL.md |
| #120 | const content = `--- |
| #121 | name: ${name} |
| #122 | description: "${description}" |
| #123 | auto-activate: true |
| #124 | --- |
| #125 | ${instructions}`; |
| #126 | |
| #127 | const skillMdPath = path.join(targetDir, "SKILL.md"); |
| #128 | await conway.writeFile(skillMdPath, content); |
| #129 | |
| #130 | const skill: Skill = { |
| #131 | name, |
| #132 | description, |
| #133 | autoActivate: true, |
| #134 | instructions, |
| #135 | source: "self", |
| #136 | path: skillMdPath, |
| #137 | enabled: true, |
| #138 | installedAt: new Date().toISOString(), |
| #139 | }; |
| #140 | |
| #141 | db.upsertSkill(skill); |
| #142 | return skill; |
| #143 | } |
| #144 | |
| #145 | /** |
| #146 | * Remove a skill (disable in DB and optionally delete from disk). |
| #147 | */ |
| #148 | export async function removeSkill( |
| #149 | name: string, |
| #150 | db: AutomatonDatabase, |
| #151 | conway: ConwayClient, |
| #152 | skillsDir: string, |
| #153 | deleteFiles: boolean = false, |
| #154 | ): Promise<void> { |
| #155 | db.removeSkill(name); |
| #156 | |
| #157 | if (deleteFiles) { |
| #158 | const resolvedDir = resolveHome(skillsDir); |
| #159 | const targetDir = path.join(resolvedDir, name); |
| #160 | await conway.exec(`rm -rf ${targetDir}`, 5000); |
| #161 | } |
| #162 | } |
| #163 | |
| #164 | /** |
| #165 | * List all installed skills. |
| #166 | */ |
| #167 | export function listSkills(db: AutomatonDatabase): Skill[] { |
| #168 | return db.getSkills(); |
| #169 | } |
| #170 | |
| #171 | function resolveHome(p: string): string { |
| #172 | if (p.startsWith("~")) { |
| #173 | return path.join(process.env.HOME || "/root", p.slice(1)); |
| #174 | } |
| #175 | return p; |
| #176 | } |
| #177 |