Skip to content

Commit 4c27aa1

Browse files
committed
refactor: use baseClient composition for DRY client implementation
- Created baseClient struct with common Client interface methods - Refactored claudeCodeClient and cursorClient to embed baseClient - Added constructor functions (newClaudeCodeClient, newCursorClient) - Reduced code duplication by using composition pattern - Updated example template to show new pattern - Each client now only implements Configure() method
1 parent 24d6e20 commit 4c27aa1

File tree

1 file changed

+97
-33
lines changed

1 file changed

+97
-33
lines changed

internal/mcp/init/init.go

Lines changed: 97 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,41 @@ type Client interface {
3030
Configure(ctx context.Context, fsys afero.Fs) error
3131
}
3232

33+
34+
// baseClient provides default implementations for the Client interface
35+
type baseClient struct {
36+
name string
37+
displayName string
38+
installInstructions string
39+
checkInstalled func() bool
40+
}
41+
42+
func (b *baseClient) Name() string {
43+
return b.name
44+
}
45+
46+
func (b *baseClient) DisplayName() string {
47+
return b.displayName
48+
}
49+
50+
func (b *baseClient) IsInstalled() bool {
51+
if b.checkInstalled != nil {
52+
return b.checkInstalled()
53+
}
54+
return false
55+
}
56+
57+
func (b *baseClient) InstallInstructions() string {
58+
return b.installInstructions
59+
}
60+
3361
// clientRegistry holds all supported clients
3462
var clientRegistry = []Client{
35-
&claudeCodeClient{},
36-
&cursorClient{},
63+
newClaudeCodeClient(),
64+
newCursorClient(),
3765
// Add new clients here in the future:
38-
// &vscodeClient{},
39-
// &claudeDesktopClient{},
66+
// newVSCodeClient(),
67+
// newClaudeDesktopClient(),
4068
}
4169

4270
func Run(ctx context.Context, fsys afero.Fs, clientFlag string) error {
@@ -122,22 +150,21 @@ func configureSpecificClient(ctx context.Context, fsys afero.Fs, clientName stri
122150
}
123151

124152
// claudeCodeClient implements the Client interface for Claude Code
125-
type claudeCodeClient struct{}
126-
127-
func (c *claudeCodeClient) Name() string {
128-
return "claude-code"
129-
}
130-
131-
func (c *claudeCodeClient) DisplayName() string {
132-
return "Claude Code"
153+
type claudeCodeClient struct {
154+
baseClient
133155
}
134156

135-
func (c *claudeCodeClient) IsInstalled() bool {
136-
return commandExists("claude")
137-
}
138-
139-
func (c *claudeCodeClient) InstallInstructions() string {
140-
return "npm install -g @anthropic-ai/claude-cli"
157+
func newClaudeCodeClient() *claudeCodeClient {
158+
return &claudeCodeClient{
159+
baseClient: baseClient{
160+
name: "claude-code",
161+
displayName: "Claude Code",
162+
installInstructions: "npm install -g @anthropic-ai/claude-cli",
163+
checkInstalled: func() bool {
164+
return commandExists("claude")
165+
},
166+
},
167+
}
141168
}
142169

143170
func (c *claudeCodeClient) Configure(ctx context.Context, fsys afero.Fs) error {
@@ -169,23 +196,21 @@ func commandExists(command string) bool {
169196
}
170197

171198
// cursorClient implements the Client interface for Cursor
172-
type cursorClient struct{}
173-
174-
func (c *cursorClient) Name() string {
175-
return "cursor"
199+
type cursorClient struct {
200+
baseClient
176201
}
177202

178-
func (c *cursorClient) DisplayName() string {
179-
return "Cursor"
180-
}
181-
182-
func (c *cursorClient) IsInstalled() bool {
183-
// Check if cursor command exists or app is installed
184-
return commandExists("cursor") || appExists("Cursor")
185-
}
186-
187-
func (c *cursorClient) InstallInstructions() string {
188-
return "Download from https://cursor.sh"
203+
func newCursorClient() *cursorClient {
204+
return &cursorClient{
205+
baseClient: baseClient{
206+
name: "cursor",
207+
displayName: "Cursor",
208+
installInstructions: "Download from https://cursor.sh",
209+
checkInstalled: func() bool {
210+
return commandExists("cursor") || appExists("Cursor")
211+
},
212+
},
213+
}
189214
}
190215

191216
func (c *cursorClient) Configure(ctx context.Context, fsys afero.Fs) error {
@@ -292,4 +317,43 @@ func appExists(appName string) bool {
292317
return false
293318
}
294319

320+
// Example: Adding a new client
321+
//
322+
// 1. Create a struct that embeds baseClient:
323+
//
324+
// type myNewClient struct {
325+
// baseClient
326+
// }
327+
//
328+
// 2. Create a constructor function:
329+
//
330+
// func newMyNewClient() *myNewClient {
331+
// return &myNewClient{
332+
// baseClient: baseClient{
333+
// name: "my-client",
334+
// displayName: "My Client",
335+
// installInstructions: "Installation command or URL",
336+
// checkInstalled: func() bool {
337+
// return commandExists("my-cli") || appExists("MyApp")
338+
// },
339+
// },
340+
// }
341+
// }
342+
//
343+
// 3. Implement the Configure method:
344+
//
345+
// func (c *myNewClient) Configure(ctx context.Context, fsys afero.Fs) error {
346+
// // Your configuration logic here
347+
// // See claudeCodeClient or cursorClient for examples
348+
// return nil
349+
// }
350+
//
351+
// 4. Add to clientRegistry:
352+
//
353+
// var clientRegistry = []Client{
354+
// newClaudeCodeClient(),
355+
// newCursorClient(),
356+
// newMyNewClient(), // Add here
357+
// }
358+
295359

0 commit comments

Comments
 (0)