Skip to content

Commit 2ba872c

Browse files
authored
[codex] add codemap plugin install flow (#60)
* add codemap plugin install flow * address plugin review feedback
1 parent 0bca36f commit 2ba872c

22 files changed

Lines changed: 891 additions & 20 deletions

File tree

.agents/plugins/marketplace.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "codemap-local",
3+
"interface": {
4+
"displayName": "Codemap Local"
5+
},
6+
"plugins": [
7+
{
8+
"name": "codemap",
9+
"source": {
10+
"source": "local",
11+
"path": "./plugins/codemap"
12+
},
13+
"policy": {
14+
"installation": "AVAILABLE",
15+
"authentication": "ON_INSTALL"
16+
},
17+
"category": "Coding"
18+
}
19+
]
20+
}

.gitignore

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@ venv/
22
__pycache__/
33

44
# Built binaries
5-
codemap
6-
codemap-mcp
7-
codemap-test
8-
mcp-test
9-
hub-check
5+
/codemap
6+
/codemap-mcp
7+
/codemap-test
8+
/mcp-test
9+
/hub-check
1010

1111
# Runtime state
1212
.codemap/
13-
codemap-dev
14-
dev_codemap
13+
/codemap-dev
14+
/dev_codemap
1515
firebase-debug.log
1616
firebalse-debug.log
1717
coverage.out

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ build:
66
go build -o codemap .
77

88
build-mcp:
9-
go build -o codemap-mcp ./mcp/
9+
go build -o codemap-mcp ./cmd/codemap-mcp/
1010

1111
DIR ?= .
1212
ABS_DIR := $(shell cd "$(DIR)" && pwd)

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ codemap handoff . # Save layered handoff for cross-agent continuation
7676
codemap --deps . # Dependency flow (requires ast-grep)
7777
codemap skill list # Show available skills
7878
codemap context # Universal JSON context for any AI tool
79+
codemap mcp # Run Codemap MCP server on stdio
80+
codemap plugin install # Install the Codemap Codex plugin
7981
codemap serve # HTTP API for non-MCP integrations
8082
```
8183

cmd/codemap-mcp/main.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
8+
codemapmcp "codemap/mcp"
9+
)
10+
11+
func main() {
12+
if err := codemapmcp.Run(context.Background()); err != nil {
13+
fmt.Fprintf(os.Stderr, "MCP server error: %v\n", err)
14+
os.Exit(1)
15+
}
16+
}

cmd/plugin.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package cmd
2+
3+
import (
4+
"errors"
5+
"flag"
6+
"fmt"
7+
"io"
8+
"os"
9+
"path/filepath"
10+
11+
pluginbundle "codemap/plugins"
12+
)
13+
14+
// RunPlugin handles the "codemap plugin" subcommand.
15+
func RunPlugin(args []string) {
16+
subCmd := ""
17+
if len(args) > 0 {
18+
subCmd = args[0]
19+
}
20+
21+
switch subCmd {
22+
case "install":
23+
runPluginInstall(args[1:])
24+
default:
25+
fmt.Println("Usage: codemap plugin install")
26+
fmt.Println()
27+
fmt.Println("Commands:")
28+
fmt.Println(" install Install the Codemap plugin into ~/.agents/plugins and ~/plugins")
29+
}
30+
}
31+
32+
func runPluginInstall(args []string) {
33+
fs := flag.NewFlagSet("plugin install", flag.ContinueOnError)
34+
fs.SetOutput(io.Discard)
35+
homeDir := fs.String("home", "", "Override the home directory used for plugin installation")
36+
pluginPath := fs.String("plugin-path", "", "Override the installed plugin path")
37+
marketplacePath := fs.String("marketplace-path", "", "Override the marketplace manifest path")
38+
if err := fs.Parse(args); err != nil {
39+
if errors.Is(err, flag.ErrHelp) {
40+
fmt.Println("Usage: codemap plugin install [--home <dir>] [--plugin-path <dir>] [--marketplace-path <file>]")
41+
return
42+
}
43+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
44+
fmt.Fprintln(os.Stderr, "Usage: codemap plugin install [--home <dir>] [--plugin-path <dir>] [--marketplace-path <file>]")
45+
os.Exit(2)
46+
}
47+
if fs.NArg() != 0 {
48+
fmt.Fprintln(os.Stderr, "Usage: codemap plugin install [--home <dir>] [--plugin-path <dir>] [--marketplace-path <file>]")
49+
os.Exit(1)
50+
}
51+
52+
result, err := pluginbundle.InstallCodemapPlugin(pluginbundle.InstallOptions{
53+
HomeDir: *homeDir,
54+
PluginPath: *pluginPath,
55+
MarketplacePath: *marketplacePath,
56+
})
57+
if err != nil {
58+
fmt.Fprintf(os.Stderr, "Plugin install failed: %v\n", err)
59+
os.Exit(1)
60+
}
61+
62+
absPluginPath, _ := filepath.Abs(result.PluginPath)
63+
absMarketplacePath, _ := filepath.Abs(result.MarketplacePath)
64+
65+
fmt.Println("codemap plugin install")
66+
fmt.Printf("Plugin: %s\n", absPluginPath)
67+
fmt.Printf("Marketplace: %s\n", absMarketplacePath)
68+
fmt.Printf("Files written: %d\n", result.FilesWritten)
69+
if result.FilesUnchanged > 0 {
70+
fmt.Printf("Files unchanged: %d\n", result.FilesUnchanged)
71+
}
72+
switch {
73+
case result.CreatedMarketplace:
74+
fmt.Println("Marketplace entry: created")
75+
case result.UpdatedMarketplace:
76+
fmt.Println("Marketplace entry: updated")
77+
default:
78+
fmt.Println("Marketplace entry: already configured")
79+
}
80+
fmt.Println()
81+
fmt.Println("Next:")
82+
fmt.Println(" 1. Restart Codex or reload plugins.")
83+
fmt.Println(" 2. Verify the Codemap plugin appears in your plugin list.")
84+
fmt.Println(" 3. If Codemap was installed before this release, update it so `codemap mcp` is available.")
85+
}

cmd/plugin_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package cmd
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"strings"
7+
"testing"
8+
)
9+
10+
func TestRunPluginInstall(t *testing.T) {
11+
home := t.TempDir()
12+
13+
out := captureOutput(func() {
14+
RunPlugin([]string{"install", "--home", home})
15+
})
16+
17+
checks := []string{
18+
"codemap plugin install",
19+
"Plugin:",
20+
"Marketplace:",
21+
"Marketplace entry: created",
22+
}
23+
for _, check := range checks {
24+
if !strings.Contains(out, check) {
25+
t.Fatalf("expected output to contain %q, got:\n%s", check, out)
26+
}
27+
}
28+
29+
pluginJSON := filepath.Join(home, "plugins", "codemap", ".codex-plugin", "plugin.json")
30+
if _, err := os.Stat(pluginJSON); err != nil {
31+
t.Fatalf("expected installed plugin manifest to exist: %v", err)
32+
}
33+
}

docs/MCP.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ Run codemap as an MCP (Model Context Protocol) server for deep Claude integratio
44

55
## Setup
66

7+
Preferred when `codemap` is already installed:
8+
9+
```bash
10+
claude mcp add --transport stdio codemap -- codemap mcp
11+
```
12+
713
### Build
814

915
```bash
@@ -22,8 +28,8 @@ Or add to your project's `.mcp.json`:
2228
{
2329
"mcpServers": {
2430
"codemap": {
25-
"command": "/path/to/codemap-mcp",
26-
"args": []
31+
"command": "codemap",
32+
"args": ["mcp"]
2733
}
2834
}
2935
}
@@ -39,12 +45,15 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
3945
{
4046
"mcpServers": {
4147
"codemap": {
42-
"command": "/path/to/codemap-mcp"
48+
"command": "codemap",
49+
"args": ["mcp"]
4350
}
4451
}
4552
}
4653
```
4754

55+
If you prefer a standalone MCP binary, keep using `/path/to/codemap-mcp`.
56+
4857
## Available Tools (17)
4958

5059
### Project Analysis

main.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"context"
45
"encoding/json"
56
"errors"
67
"flag"
@@ -17,6 +18,7 @@ import (
1718
"codemap/config"
1819
"codemap/handoff"
1920
"codemap/limits"
21+
codemapmcp "codemap/mcp"
2022
"codemap/render"
2123
"codemap/scanner"
2224
"codemap/watch"
@@ -104,13 +106,28 @@ func main() {
104106
return
105107
}
106108

109+
// Handle "mcp" subcommand before global flag parsing
110+
if len(os.Args) >= 2 && os.Args[1] == "mcp" {
111+
if err := codemapmcp.Run(context.Background()); err != nil {
112+
fmt.Fprintf(os.Stderr, "MCP server error: %v\n", err)
113+
os.Exit(1)
114+
}
115+
return
116+
}
117+
107118
// Handle "skill" subcommand before global flag parsing
108119
if len(os.Args) >= 2 && os.Args[1] == "skill" {
109120
root, _ := os.Getwd()
110121
cmd.RunSkill(os.Args[2:], root)
111122
return
112123
}
113124

125+
// Handle "plugin" subcommand before global flag parsing
126+
if len(os.Args) >= 2 && os.Args[1] == "plugin" {
127+
cmd.RunPlugin(os.Args[2:])
128+
return
129+
}
130+
114131
// Handle "context" subcommand before global flag parsing
115132
if len(os.Args) >= 2 && os.Args[1] == "context" {
116133
root, _ := os.Getwd()
@@ -197,6 +214,12 @@ func main() {
197214
fmt.Println(" codemap config init # Create .codemap/config.json (auto-detects extensions)")
198215
fmt.Println(" codemap config show # Show current project config")
199216
fmt.Println()
217+
fmt.Println("Plugin management:")
218+
fmt.Println(" codemap plugin install # Install the Codemap plugin into your home plugin marketplace")
219+
fmt.Println()
220+
fmt.Println("MCP server:")
221+
fmt.Println(" codemap mcp # Run Codemap MCP server on stdio")
222+
fmt.Println()
200223
fmt.Println("Recommended onboarding:")
201224
fmt.Println(" codemap setup # Configure project config + Claude hooks")
202225
fmt.Println(" codemap setup --global # Write hooks to ~/.claude/settings.json")

mcp/main.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
// MCP Server for codemap - provides codebase analysis tools to LLMs
2-
package main
2+
package codemapmcp
33

44
import (
55
"bytes"
66
"context"
77
"encoding/json"
88
"fmt"
9-
"log"
109
"os"
1110
"path/filepath"
1211
"regexp"
@@ -83,7 +82,7 @@ type HandoffInput struct {
8382
File string `json:"file,omitempty" jsonschema:"Load detailed context for one changed file path from handoff delta"`
8483
}
8584

86-
func main() {
85+
func NewServer() *mcp.Server {
8786
server := mcp.NewServer(&mcp.Implementation{
8887
Name: "codemap",
8988
Version: "2.0.0",
@@ -191,10 +190,11 @@ func main() {
191190
Description: "Load the full instructions for a specific skill by name. Returns the complete markdown body with step-by-step guidance. Use after list_skills to get detailed guidance for a task.",
192191
}, handleGetSkill)
193192

194-
// Run server on stdio
195-
if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
196-
log.Printf("Server error: %v", err)
197-
}
193+
return server
194+
}
195+
196+
func Run(ctx context.Context) error {
197+
return NewServer().Run(ctx, &mcp.StdioTransport{})
198198
}
199199

200200
func textResult(text string) *mcp.CallToolResult {

0 commit comments

Comments
 (0)