Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e31da79
feat: agent skills module
KartikJha May 19, 2026
7a496b7
fix: added test cases
KartikJha May 19, 2026
219c035
fix: lint fixes
KartikJha May 19, 2026
c9e6662
feat: download plugin module
KartikJha May 20, 2026
7598b75
feat: add skills lock file support
KartikJha May 20, 2026
0f4adf5
feat: skills internal download and lock
KartikJha May 21, 2026
639f648
feat: skills directory meta parsing for interactive display while ins…
KartikJha May 22, 2026
92461d0
fix: added review fixes for agent.go
KartikJha Jun 1, 2026
efb3e49
fix: added agents review fixes
KartikJha Jun 2, 2026
46a5cb1
fix: download internal review fixes
KartikJha Jun 2, 2026
9ace39d
fix: targetDir clone and ref tags vs branches prefix
KartikJha Jun 3, 2026
b6f0883
fix: download module, error handling, symlink corruption in mergeDir,…
KartikJha Jun 4, 2026
405d370
fix: setupLocalGit test fix
KartikJha Jun 4, 2026
5f4e490
fix: naming, scope enum, concurrent lockfile access inter process
KartikJha Jun 4, 2026
af2ba03
fix: skills meta parsing fix, available skills nil fix, skills.md fil…
KartikJha Jun 4, 2026
a0d4321
fix: removed quickstart related file changes
KartikJha Jun 4, 2026
8f5dc89
fix: lint fixes
KartikJha Jun 4, 2026
adff89f
fix: removed quickstart comment and gitignore update
KartikJha Jun 4, 2026
1ca39ec
feat: symlink internal init
KartikJha Jun 4, 2026
612bf8c
fix: symlink review agent fixes
KartikJha Jun 7, 2026
cf0f5b5
fix: use os.samefile for symlink check, remove tmo file in copy dir
KartikJha Jun 7, 2026
7976d15
feat: validate skills installation
KartikJha Jun 7, 2026
ae759dc
fix: lint fixes
KartikJha Jun 7, 2026
ef3b043
fix: add code fix for failing local github setupLocalGitRepo
KartikJha Jun 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,5 @@ out/
.github/copilot-instructions.md

# LLM files
.remember/
.remember/

316 changes: 316 additions & 0 deletions internal/ai/skills/agent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@
package skills

import (
"errors"
"os"
"os/exec"
"path/filepath"
"sync"
)

type AgentConfig struct {
ID string
DisplayName string
GlobalSkillsDir string
GlobalSkillsDirEnvVar string
ProjectSkillsDir string
DetectMarkers []string
DetectMarkerEnvVars []string
DetectBinaries []string
}

func (a AgentConfig) ResolvedGlobalSkillsDir() (string, error) {
if a.GlobalSkillsDirEnvVar != "" {
if v := os.Getenv(a.GlobalSkillsDirEnvVar); v != "" {
return filepath.Join(v, "skills"), nil
}
}
if a.GlobalSkillsDir == "" {
return "", errors.New("GlobalSkillsDirEnvVar must be set for: " + a.ID)
}
return a.GlobalSkillsDir, nil
}

func (a AgentConfig) IsInstalled() bool {
for _, marker := range a.DetectMarkers {
if marker == "" {
continue
}
if _, err := os.Stat(marker); err == nil {
return true
}
}
for _, envVar := range a.DetectMarkerEnvVars {
if envVar == "" {
continue
}
if v := os.Getenv(envVar); v != "" {
if _, err := os.Stat(v); err == nil {
return true
}
}
}
for _, binary := range a.DetectBinaries {
if binary == "" {
continue
}
if _, err := exec.LookPath(binary); err == nil {
return true
}
}
return false
}

var SupportedAgents []AgentConfig

func init() {
home, _ := os.UserHomeDir()
if home == "" {
SupportedAgents = []AgentConfig{
{ID: "universal", DisplayName: "Universal", ProjectSkillsDir: filepath.Join(".agents", "skills")},
}
return
}

SupportedAgents = []AgentConfig{
{
ID: "claude-code",
DisplayName: "Claude Code",
GlobalSkillsDir: filepath.Join(home, ".claude", "skills"),
ProjectSkillsDir: filepath.Join(".claude", "skills"),
DetectMarkers: []string{filepath.Join(home, ".claude")},
DetectBinaries: []string{"claude"},
},
{
ID: "cursor",
DisplayName: "Cursor",
GlobalSkillsDir: filepath.Join(home, ".cursor", "skills"),
ProjectSkillsDir: filepath.Join(".agents", "skills"),
DetectMarkers: []string{filepath.Join(home, ".cursor")},
DetectBinaries: []string{"cursor"},
},
{
ID: "github-copilot",
DisplayName: "GitHub Copilot",
GlobalSkillsDir: filepath.Join(home, ".copilot", "skills"),
ProjectSkillsDir: filepath.Join(".agents", "skills"),
DetectMarkers: []string{
filepath.Join(home, ".copilot"),
filepath.Join(home, ".config", "github-copilot"),
},
},
{
ID: "gemini-cli",
DisplayName: "Gemini CLI",
GlobalSkillsDir: filepath.Join(home, ".gemini", "skills"),
ProjectSkillsDir: filepath.Join(".agents", "skills"),
DetectMarkers: []string{filepath.Join(home, ".gemini")},
DetectBinaries: []string{"gemini"},
},
{
ID: "antigravity",
DisplayName: "Antigravity",
GlobalSkillsDir: filepath.Join(home, ".gemini", "antigravity", "skills"),
ProjectSkillsDir: filepath.Join(".agents", "skills"),
DetectMarkers: []string{filepath.Join(home, ".gemini", "antigravity")},
},
{
ID: "roo",
DisplayName: "Roo Code",
GlobalSkillsDir: filepath.Join(home, ".roo", "skills"),
ProjectSkillsDir: filepath.Join(".roo", "skills"),
DetectMarkers: []string{filepath.Join(home, ".roo")},
},
{
ID: "goose",
DisplayName: "Goose",
GlobalSkillsDir: filepath.Join(home, ".config", "goose", "skills"),
ProjectSkillsDir: filepath.Join(".goose", "skills"),
DetectMarkers: []string{filepath.Join(home, ".config", "goose")},
},
{
ID: "opencode",
DisplayName: "OpenCode",
GlobalSkillsDir: filepath.Join(home, ".config", "opencode", "skills"),
ProjectSkillsDir: filepath.Join(".agents", "skills"),
DetectMarkers: []string{filepath.Join(home, ".config", "opencode")},
},
{
ID: "codex",
DisplayName: "Codex (OpenAI)",
GlobalSkillsDir: filepath.Join(home, ".codex", "skills"),
GlobalSkillsDirEnvVar: "CODEX_HOME",
ProjectSkillsDir: filepath.Join(".agents", "skills"),
DetectMarkers: []string{"/etc/codex"},
DetectMarkerEnvVars: []string{"CODEX_HOME"},
},
{
ID: "windsurf",
DisplayName: "Windsurf",
GlobalSkillsDir: filepath.Join(home, ".windsurf", "skills"),
ProjectSkillsDir: filepath.Join(".windsurf", "skills"),
DetectMarkers: []string{filepath.Join(home, ".windsurf")},
},
{
ID: "continue",
DisplayName: "Continue",
GlobalSkillsDir: filepath.Join(home, ".continue", "skills"),
ProjectSkillsDir: filepath.Join(".continue", "skills"),
DetectMarkers: []string{filepath.Join(home, ".continue")},
},
{
ID: "amp",
DisplayName: "Amp",
GlobalSkillsDir: filepath.Join(home, ".config", "agents", "skills"),
ProjectSkillsDir: filepath.Join(".agents", "skills"),
DetectMarkers: []string{filepath.Join(home, ".config", "amp")},
},
{
ID: "junie",
DisplayName: "Junie",
GlobalSkillsDir: filepath.Join(home, ".junie", "skills"),
ProjectSkillsDir: filepath.Join(".junie", "skills"),
DetectMarkers: []string{filepath.Join(home, ".junie")},
},
{
ID: "kiro-cli",
DisplayName: "Kiro CLI",
GlobalSkillsDir: filepath.Join(home, ".kiro", "skills"),
ProjectSkillsDir: filepath.Join(".kiro", "skills"),
DetectMarkers: []string{filepath.Join(home, ".kiro")},
},
{
ID: "cline",
DisplayName: "Cline",
GlobalSkillsDir: filepath.Join(home, ".agents", "skills"),
ProjectSkillsDir: filepath.Join(".agents", "skills"),
DetectMarkers: []string{filepath.Join(home, ".cline")},
},
{
ID: "augment",
DisplayName: "Augment",
GlobalSkillsDir: filepath.Join(home, ".augment", "skills"),
ProjectSkillsDir: filepath.Join(".augment", "skills"),
DetectMarkers: []string{filepath.Join(home, ".augment")},
},
{
ID: "aider-desk",
DisplayName: "AiderDesk",
GlobalSkillsDir: filepath.Join(home, ".aider-desk", "skills"),
ProjectSkillsDir: filepath.Join(".aider-desk", "skills"),
DetectMarkers: []string{filepath.Join(home, ".aider-desk")},
},
{
ID: "warp",
DisplayName: "Warp",
GlobalSkillsDir: filepath.Join(home, ".config", "agents", "skills"),
ProjectSkillsDir: filepath.Join(".agents", "skills"),
DetectMarkers: []string{filepath.Join(home, ".warp")},
},
{
ID: "devin",
DisplayName: "Devin",
GlobalSkillsDir: filepath.Join(home, ".config", "devin", "skills"),
ProjectSkillsDir: filepath.Join(".agents", "skills"),
DetectMarkers: []string{filepath.Join(home, ".config", "devin")},
},
{
ID: "mistral-vibe",
DisplayName: "Mistral Vibe",
GlobalSkillsDirEnvVar: "VIBE_HOME",
ProjectSkillsDir: filepath.Join(".agents", "skills"),
DetectMarkerEnvVars: []string{"VIBE_HOME"},
},
{
ID: "openhands",
DisplayName: "OpenHands",
GlobalSkillsDir: filepath.Join(home, ".openhands", "skills"),
ProjectSkillsDir: filepath.Join(".openhands", "skills"),
},
{
ID: "trae",
DisplayName: "Trae",
GlobalSkillsDir: filepath.Join(home, ".trae", "skills"),
ProjectSkillsDir: filepath.Join(".trae", "skills"),
},
{
ID: "mux",
DisplayName: "Mux",
GlobalSkillsDir: filepath.Join(home, ".mux", "skills"),
ProjectSkillsDir: filepath.Join(".mux", "skills"),
},
{
ID: "universal",
DisplayName: "Universal",
GlobalSkillsDir: filepath.Join(home, ".agents", "skills"),
ProjectSkillsDir: filepath.Join(".agents", "skills"),
},
}
}

var (
detectedAgentsMu sync.RWMutex
detectedAgentsDone bool
detectedAgentsCache []AgentConfig
)

func DetectedAgents() []AgentConfig {
detectedAgentsMu.RLock()
if detectedAgentsDone {
result := detectedAgentsCache
detectedAgentsMu.RUnlock()
return result
}
detectedAgentsMu.RUnlock()

detectedAgentsMu.Lock()
defer detectedAgentsMu.Unlock()
if !detectedAgentsDone {
for _, a := range SupportedAgents {
if a.ID == "universal" || a.IsInstalled() {
detectedAgentsCache = append(detectedAgentsCache, a)
}
}
detectedAgentsDone = true
}
return detectedAgentsCache
}

func ResetDetectedAgentsCache() {
detectedAgentsMu.Lock()
defer detectedAgentsMu.Unlock()
detectedAgentsDone = false
detectedAgentsCache = nil
}

func FastPriorityAgents() []AgentConfig {
detected := DetectedAgents()

priority := []string{"claude-code", "cursor", "github-copilot", "gemini-cli"}
byID := make(map[string]AgentConfig, len(detected))
for _, a := range detected {
byID[a.ID] = a
}

var result []AgentConfig
added := make(map[string]bool)

for _, id := range priority {
if a, ok := byID[id]; ok && id != "universal" {
result = append(result, a)
added[id] = true
}
}

for _, a := range detected {
if !added[a.ID] && a.ID != "universal" {
result = append(result, a)
}
}

if a, ok := byID["universal"]; ok {
result = append(result, a)
}

return result
}
Loading
Loading