package main import ( "context" "fmt" "os" "path/filepath" "strings" "time" "github.com/YspCoder/clawgo/pkg/config" "github.com/YspCoder/clawgo/pkg/skills" ) func skillsCmd() { if len(os.Args) < 3 { skillsHelp() return } subcommand := os.Args[2] cfg, err := loadConfig() if err != nil { fmt.Printf("Error loading config: %v\n", err) os.Exit(1) } workspace := cfg.WorkspacePath() installer := skills.NewSkillInstaller(workspace) globalDir := filepath.Dir(getConfigPath()) globalSkillsDir := filepath.Join(globalDir, "skills") builtinSkillsDir := filepath.Join(globalDir, "clawgo", "skills") skillsLoader := skills.NewSkillsLoader(workspace, globalSkillsDir, builtinSkillsDir) switch subcommand { case "list": skillsListCmd(skillsLoader) case "install": skillsInstallCmd(installer) case "remove", "uninstall": if len(os.Args) < 4 { fmt.Println("Usage: clawgo skills remove ") return } skillsRemoveCmd(installer, os.Args[3]) case "install-builtin": skillsInstallBuiltinCmd(workspace) case "list-builtin": skillsListBuiltinCmd() case "search": skillsSearchCmd(installer) case "show": if len(os.Args) < 4 { fmt.Println("Usage: clawgo skills show ") return } skillsShowCmd(skillsLoader, os.Args[3]) default: fmt.Printf("Unknown skills command: %s\n", subcommand) skillsHelp() } } func skillsHelp() { fmt.Println("\nSkills commands:") fmt.Println(" list List installed skills") fmt.Println(" install Install skill from GitHub") fmt.Println(" install-builtin Install all builtin skills to workspace") fmt.Println(" list-builtin List available builtin skills") fmt.Println(" remove Remove installed skill") fmt.Println(" search Search available skills") fmt.Println(" show Show skill details") fmt.Println() fmt.Println("Examples:") fmt.Println(" clawgo skills list") fmt.Println(" clawgo skills install YspCoder/clawgo-skills/weather") fmt.Println(" clawgo skills install-builtin") fmt.Println(" clawgo skills list-builtin") fmt.Println(" clawgo skills remove weather") } func skillsListCmd(loader *skills.SkillsLoader) { allSkills := loader.ListSkills() if len(allSkills) == 0 { fmt.Println("No skills installed.") return } fmt.Println("\nInstalled Skills:") fmt.Println("------------------") for _, skill := range allSkills { fmt.Printf(" ✓ %s (%s)\n", skill.Name, skill.Source) if skill.Description != "" { fmt.Printf(" %s\n", skill.Description) } } } func skillsInstallCmd(installer *skills.SkillInstaller) { if len(os.Args) < 4 { fmt.Println("Usage: clawgo skills install ") fmt.Println("Example: clawgo skills install YspCoder/clawgo-skills/weather") return } repo := os.Args[3] fmt.Printf("Installing skill from %s...\n", repo) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := installer.InstallFromGitHub(ctx, repo); err != nil { fmt.Printf("✗ Failed to install skill: %v\n", err) os.Exit(1) } fmt.Printf("✓ Skill '%s' installed successfully!\n", filepath.Base(repo)) } func skillsRemoveCmd(installer *skills.SkillInstaller, skillName string) { fmt.Printf("Removing skill '%s'...\n", skillName) if err := installer.Uninstall(skillName); err != nil { fmt.Printf("✗ Failed to remove skill: %v\n", err) os.Exit(1) } fmt.Printf("✓ Skill '%s' removed successfully!\n", skillName) } func skillsInstallBuiltinCmd(workspace string) { builtinSkillsDir := detectBuiltinSkillsDir(workspace) workspaceSkillsDir := filepath.Join(workspace, "skills") fmt.Printf("Copying builtin skills to workspace...\n") skillsToInstall := []string{"weather", "news", "stock", "calculator"} for _, skillName := range skillsToInstall { builtinPath := filepath.Join(builtinSkillsDir, skillName) workspacePath := filepath.Join(workspaceSkillsDir, skillName) if _, err := os.Stat(builtinPath); err != nil { fmt.Printf("⊘ Builtin skill '%s' not found: %v\n", skillName, err) continue } if err := os.MkdirAll(workspacePath, 0755); err != nil { fmt.Printf("✗ Failed to create directory for %s: %v\n", skillName, err) continue } if err := copyDirectory(builtinPath, workspacePath); err != nil { fmt.Printf("✗ Failed to copy %s: %v\n", skillName, err) } } fmt.Println("\n✓ All builtin skills installed!") fmt.Println("Now you can use them in your workspace.") } func skillsListBuiltinCmd() { cfg, err := loadConfig() if err != nil { fmt.Printf("Error loading config: %v\n", err) return } builtinSkillsDir := detectBuiltinSkillsDir(cfg.WorkspacePath()) fmt.Println("\nAvailable Builtin Skills:") fmt.Println("-----------------------") entries, err := os.ReadDir(builtinSkillsDir) if err != nil { fmt.Printf("Error reading builtin skills: %v\n", err) return } if len(entries) == 0 { fmt.Println("No builtin skills available.") return } for _, entry := range entries { if entry.IsDir() { skillName := entry.Name() skillFile := filepath.Join(builtinSkillsDir, skillName, "SKILL.md") description := "No description" if _, err := os.Stat(skillFile); err == nil { data, err := os.ReadFile(skillFile) if err == nil { content := string(data) if idx := strings.Index(content, "\n"); idx > 0 { firstLine := content[:idx] if strings.Contains(firstLine, "description:") { descLine := strings.Index(content[idx:], "\n") if descLine > 0 { description = strings.TrimSpace(content[idx+descLine : idx+descLine]) } } } } } status := "✓" fmt.Printf(" %s %s\n", status, entry.Name()) if description != "" { fmt.Printf(" %s\n", description) } } } } func detectBuiltinSkillsDir(workspace string) string { candidates := []string{ filepath.Join(".", "skills"), filepath.Join(filepath.Dir(workspace), "clawgo", "skills"), filepath.Join(config.GetConfigDir(), "clawgo", "skills"), } for _, dir := range candidates { if info, err := os.Stat(dir); err == nil && info.IsDir() { return dir } } return filepath.Join(".", "skills") } func skillsSearchCmd(installer *skills.SkillInstaller) { fmt.Println("Searching for available skills...") ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() availableSkills, err := installer.ListAvailableSkills(ctx) if err != nil { fmt.Printf("✗ Failed to fetch skills list: %v\n", err) return } if len(availableSkills) == 0 { fmt.Println("No skills available.") return } fmt.Printf("\nAvailable Skills (%d):\n", len(availableSkills)) fmt.Println("--------------------") for _, skill := range availableSkills { fmt.Printf(" 📦 %s\n", skill.Name) fmt.Printf(" %s\n", skill.Description) fmt.Printf(" Repo: %s\n", skill.Repository) if skill.Author != "" { fmt.Printf(" Author: %s\n", skill.Author) } if len(skill.Tags) > 0 { fmt.Printf(" Tags: %v\n", skill.Tags) } fmt.Println() } } func skillsShowCmd(loader *skills.SkillsLoader, skillName string) { content, ok := loader.LoadSkill(skillName) if !ok { fmt.Printf("✗ Skill '%s' not found\n", skillName) return } fmt.Printf("\n📦 Skill: %s\n", skillName) fmt.Println("----------------------") fmt.Println(content) }