Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 3 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@ make clean

## Architecture

AgentX is a CLI tool for managing MCP servers, skills, and plugins across multiple AI coding agents (Claude Code, Cursor, Gemini CLI, OpenCode).
AgentX is a CLI tool for managing MCP servers, skills, and plugins across multiple AI coding agents (Claude Code, Codex, Cursor, Gemini CLI, OpenCode).

### Core Layers

**CLI Layer** (`cmd/`): Cobra-based commands. `root.go` launches the TUI when run without arguments, or delegates to subcommands (`install`, `check`, `list`, `remove`, `skills`, `plugins`).

**Agent Abstraction** (`internal/agent/`): The `Agent` interface defines operations for all supported agents. Each agent (Claude, Cursor, Gemini, OpenCode) implements this interface with its own config file location and format:
**Agent Abstraction** (`internal/agent/`): The `Agent` interface defines operations for all supported agents. Each agent (Claude, Codex, Cursor, Gemini, OpenCode) implements this interface with its own config file location and format:
- Claude Code: `~/.claude.json`
- Codex: `~/.codex/config.toml`
- Cursor: `~/.cursor/mcp.json`
- Gemini CLI: `~/.gemini/settings.json`
- OpenCode: `~/.opencode/config.json`
Expand Down
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ AgentX stands for Agent eXtension: A unified CLI tool for managing **MCP (Model
AgentX simplifies the installation, management, and monitoring of MCP servers and skills across popular AI coding tools:

- **Claude Code**
- **Codex**
- **Cursor**
- **Gemini CLI**
- **OpenCode**
Expand All @@ -23,9 +24,9 @@ It provides both a command-line interface and an interactive terminal UI (TUI) f
- **Playwright** - Browser automation capabilities
- **Context7** - Library documentation access

### Claude Code Skills Management
### Claude Code & Codex Skills Management
- Install skills from local paths or Git repositories
- Support for skill directories (with `SKILL.md`) and command files (`.md`)
- Support for skill directories (with `SKILL.md`); Claude Code also supports command files (`.md`)
- Install from GitHub URLs with tree fragments
- Personal and project scope management
- Skills health checking and validation
Expand Down Expand Up @@ -105,24 +106,34 @@ The tool responds to: `agentx`, `agents`, or `ax`
| Agent | Config Path |
|-------|-------------|
| Claude Code | `~/.claude.json` |
| Codex | `~/.codex/config.toml` |
| Cursor | `~/.cursor/mcp.json` |
| Gemini CLI | `~/.gemini/settings.json` |
| OpenCode | `~/.opencode/config.json` |

### Skills Storage

Claude Code:

| Scope | Skills Directory | Commands Directory |
|-------|------------------|-------------------|
| Personal | `~/.claude/skills/` | `~/.claude/commands/` |
| Project | `.claude/skills/` | `.claude/commands/` |

Codex:

| Scope | Skills Directory |
|-------|------------------|
| Personal | `$CODEX_HOME/skills/` (default `~/.codex/skills/`) |
| Project | `.codex/skills/` |

## Project Structure

```
agentx/
├── cmd/ # CLI commands
├── internal/
│ ├── agent/ # Agent implementations (Claude, Cursor, Gemini, OpenCode)
│ ├── agent/ # Agent implementations (Claude, Codex, Cursor, Gemini, OpenCode)
│ ├── config/ # Configuration management
│ ├── skills/ # Skills management
│ ├── mcp/ # MCP-specific logic
Expand Down
4 changes: 2 additions & 2 deletions cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package cmd
import (
"fmt"

"github.com/spf13/cobra"
"github.com/agentsdance/agentx/internal/agent"
"github.com/spf13/cobra"
)

var agentFlag string
Expand Down Expand Up @@ -54,5 +54,5 @@ var installCmd = &cobra.Command{
}

func init() {
installCmd.Flags().StringVarP(&agentFlag, "agent", "a", "", "Target agent (claude, gemini, opencode)")
installCmd.Flags().StringVarP(&agentFlag, "agent", "a", "", "Target agent (claude, codex, cursor, gemini, opencode)")
}
4 changes: 2 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ import (
"fmt"
"os"

"github.com/spf13/cobra"
"github.com/agentsdance/agentx/internal/version"
"github.com/agentsdance/agentx/ui"
"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
Use: "agentx",
Aliases: []string{"agents", "ax"},
Short: "Unified MCP Servers & Agent Skills Manager for AI coding agents",
Long: `agentx is a CLI tool for managing MCP servers and skills across AI coding agents
(Claude Code, Cursor, Gemini cli, opencode).
(Claude Code, Codex, Cursor, Gemini cli, opencode).

Run without arguments to launch the TUI interface.

Expand Down
63 changes: 54 additions & 9 deletions cmd/skills.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,45 @@ package cmd
import (
"fmt"
"os"
"strings"
"text/tabwriter"

"github.com/spf13/cobra"
"github.com/agentsdance/agentx/internal/skills"
"github.com/spf13/cobra"
)

var skillsScope string
var skillsAgent string

var skillsCmd = &cobra.Command{
Use: "skills",
Short: "Manage Claude Code skills",
Long: `Manage Claude Code skills and slash commands.
Short: "Manage Claude Code and Codex skills",
Long: `Manage Claude Code and Codex skills and slash commands.

Use --agent to switch between agents (default: Claude Code). Codex does not
support command files.

Skills are stored in:
Personal: ~/.claude/skills/ and ~/.claude/commands/
Project: .claude/skills/ and .claude/commands/`,
Project: .claude/skills/ and .claude/commands/

Codex skills are stored in:
Personal: $CODEX_HOME/skills/ (default ~/.codex/skills/)
Project: .codex/skills/`,
}

var skillsListCmd = &cobra.Command{
Use: "list",
Short: "List installed skills",
Long: `List all installed skills and commands.`,
Run: func(cmd *cobra.Command, args []string) {
mgr := skills.NewSkillManager()
mgr, err := resolveSkillsManager()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}

var skillList []skills.Skill
var err error

if skillsScope != "" {
scope := skills.SkillScope(skillsScope)
Expand Down Expand Up @@ -90,7 +102,11 @@ The source can be:
scope = skills.ScopeProject
}

mgr := skills.NewSkillManager()
mgr, err := resolveSkillsManager()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
skill, err := mgr.Install(source, scope)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
Expand All @@ -117,7 +133,11 @@ var skillsRemoveCmd = &cobra.Command{
scope = skills.ScopeProject
}

mgr := skills.NewSkillManager()
mgr, err := resolveSkillsManager()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
if err := mgr.Remove(name, scope); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
Expand All @@ -132,7 +152,11 @@ var skillsCheckCmd = &cobra.Command{
Short: "Check skills installation status",
Long: `Verify that all installed skills are valid and properly configured.`,
Run: func(cmd *cobra.Command, args []string) {
mgr := skills.NewSkillManager()
mgr, err := resolveSkillsManager()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
statuses, err := mgr.Check()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
Expand Down Expand Up @@ -186,9 +210,30 @@ var skillsCheckCmd = &cobra.Command{
func init() {
skillsCmd.PersistentFlags().StringVarP(&skillsScope, "scope", "s", "",
"Scope for the operation (personal, project)")
skillsCmd.PersistentFlags().StringVarP(&skillsAgent, "agent", "a", "claude",
"Target agent for skills (claude, codex)")

skillsCmd.AddCommand(skillsListCmd)
skillsCmd.AddCommand(skillsInstallCmd)
skillsCmd.AddCommand(skillsRemoveCmd)
skillsCmd.AddCommand(skillsCheckCmd)
}

func resolveSkillsManager() (*skills.DefaultSkillManager, error) {
switch normalizeSkillsAgent(skillsAgent) {
case "", "claude", "claudecode":
return skills.NewSkillManager(), nil
case "codex":
return skills.NewCodexSkillManager(), nil
default:
return nil, fmt.Errorf("unknown agent: %s (use 'claude' or 'codex')", skillsAgent)
}
}

func normalizeSkillsAgent(name string) string {
normalized := strings.ToLower(strings.TrimSpace(name))
normalized = strings.ReplaceAll(normalized, "-", "")
normalized = strings.ReplaceAll(normalized, "_", "")
normalized = strings.ReplaceAll(normalized, " ", "")
return normalized
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.25.1
require (
github.com/charmbracelet/bubbletea v1.3.10
github.com/charmbracelet/lipgloss v1.1.0
github.com/pelletier/go-toml/v2 v2.2.4
github.com/spf13/cobra v1.10.2
gopkg.in/yaml.v3 v3.0.1
)
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
Expand Down
3 changes: 3 additions & 0 deletions internal/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type Agent interface {
func GetAllAgents() []Agent {
return []Agent{
NewClaudeAgent(),
NewCodexAgent(),
NewCursorAgent(),
NewGeminiAgent(),
NewOpenCodeAgent(),
Expand All @@ -64,6 +65,8 @@ func matchAgentName(agentName, input string) bool {
switch input {
case "claude", "claudecode", "claude-code", "claude_code":
return agentName == "Claude Code"
case "codex", "codexcli", "codex-cli", "codex_cli":
return agentName == "Codex"
case "cursor":
return agentName == "Cursor"
case "gemini", "geminicli", "gemini-cli", "gemini_cli":
Expand Down
Loading