Skip to content

Commit 44f8dda

Browse files
zmotsoSergK
authored andcommitted
feat: Add sync-ide functionality for IDE integration
Introduced a new command-line flag `--sync-ide` for install command to sync IDE integration files from installed agents.
1 parent 5e93b74 commit 44f8dda

File tree

3 files changed

+177
-25
lines changed

3 files changed

+177
-25
lines changed

.vscode/launch.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,17 @@
4848
"KRCI_AI_PROJECT_DIR": "${workspaceFolder}"
4949
}
5050
},
51+
{
52+
"name": "install sync ide",
53+
"type": "go",
54+
"request": "launch",
55+
"mode": "auto",
56+
"program": "${workspaceFolder}/cmd/krci-ai/main.go",
57+
"args": ["install", "--sync-ide"],
58+
"env": {
59+
"KRCI_AI_PROJECT_DIR": "${workspaceFolder}"
60+
}
61+
},
5162
{
5263
"name": "install krci-ai go-dev",
5364
"type": "go",

cmd/krci-ai/cmd/install.go

Lines changed: 94 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,11 @@ Examples:
7474
7575
# Force operations
7676
krci-ai install --ide cursor --force # Add IDE integration to existing install
77-
krci-ai install --all --force # Force reinstall everything`,
77+
krci-ai install --all --force # Force reinstall everything
78+
79+
# Sync IDE files from installed agents (instead of embedded assets)
80+
krci-ai install --sync-ide # Sync all existing IDE integrations from installed agents
81+
krci-ai install --agent dev --sync-ide # Install dev agent + sync IDE files`,
7882
Run: func(cmd *cobra.Command, args []string) {
7983
errorHandler := cli.NewErrorHandler()
8084

@@ -111,23 +115,31 @@ Examples:
111115

112116
// Check aliases flag if main flag is empty
113117
if agentFlag == "" {
114-
if agentsFlag, err := cmd.Flags().GetString("agents"); err == nil && agentsFlag != "" {
118+
agentsFlag, flagErr := cmd.Flags().GetString("agents")
119+
if flagErr == nil && agentsFlag != "" {
115120
agentFlag = agentsFlag
116121
}
117122
}
118123

124+
// Check sync-ide flag
125+
syncIDEFlag, err := cmd.Flags().GetBool("sync-ide")
126+
if err != nil {
127+
errorHandler.HandleError(err, "Failed to read sync-ide flag")
128+
return
129+
}
130+
119131
if agentFlag != "" {
120-
runSelectiveInstallation(cmd, agentFlag, ideFlag, output, errorHandler)
132+
runSelectiveInstallation(cmd, agentFlag, ideFlag, syncIDEFlag, output, errorHandler)
121133
return
122134
}
123135

124136
// Standard full installation
125-
runFullInstallation(cmd, ideFlag, output, errorHandler)
137+
runFullInstallation(cmd, ideFlag, syncIDEFlag, output, errorHandler)
126138
},
127139
}
128140

129141
// runSelectiveInstallation handles installation of specific agents only
130-
func runSelectiveInstallation(_ *cobra.Command, agentFlag string, ideFlag string, output *cli.OutputHandler, errorHandler *cli.ErrorHandler) {
142+
func runSelectiveInstallation(_ *cobra.Command, agentFlag string, ideFlag string, syncIDEFlag bool, output *cli.OutputHandler, errorHandler *cli.ErrorHandler) {
131143
// Parse agent list using existing bundle logic
132144
agentNames := ParseAgentList(agentFlag)
133145
if len(agentNames) == 0 {
@@ -161,11 +173,17 @@ func runSelectiveInstallation(_ *cobra.Command, agentFlag string, ideFlag string
161173
handleIDEIntegration(installer, ideFlag, output, errorHandler)
162174
}
163175

176+
// Handle sync-ide flag at the end
177+
if syncIDEFlag {
178+
output.PrintInfo("Syncing IDE integration files from installed agents...")
179+
handleIDESync(installer, output, errorHandler)
180+
}
181+
164182
output.PrintSuccess(fmt.Sprintf("Selected agents installed successfully: %v", agentNames))
165183
}
166184

167185
// runFullInstallation handles standard full framework installation
168-
func runFullInstallation(cmd *cobra.Command, ideFlag string, output *cli.OutputHandler, errorHandler *cli.ErrorHandler) {
186+
func runFullInstallation(cmd *cobra.Command, ideFlag string, syncIDEFlag bool, output *cli.OutputHandler, errorHandler *cli.ErrorHandler) {
169187
output.PrintProgress("Installing KubeRocketAI framework components...")
170188

171189
// Get force flag (other flags already processed upfront)
@@ -193,37 +211,45 @@ func runFullInstallation(cmd *cobra.Command, ideFlag string, output *cli.OutputH
193211
assets.NewEmbeddedDiscovery(GetEmbeddedAssets(), assets.EmbeddedPrefix),
194212
)
195213

196-
// Check installation status and force flag
197-
if !handleInstallationCheck(installer, forceFlag, output) {
198-
return
199-
}
214+
// Check installation status
215+
isAlreadyInstalled := installer.IsInstalled()
200216

201-
// Perform core installation
202-
if err := performCoreInstallation(installer, output, errorHandler); err != nil {
217+
// Special case: If already installed and only syncing IDE files (no reinstall needed)
218+
if isAlreadyInstalled && syncIDEFlag && !forceFlag {
219+
output.PrintInfo("Framework already installed. Syncing IDE integration files from installed agents...")
220+
handleIDESync(installer, output, errorHandler)
221+
output.PrintSuccess("IDE integration files synced successfully!")
203222
return
204223
}
205224

206-
// Handle IDE integration using existing function
207-
handleIDEIntegration(installer, ideFlag, output, errorHandler)
208-
209-
// Show success and next steps
210-
showInstallationSuccess(installer, ideFlag, output)
211-
}
212-
213-
// handleInstallationCheck checks if installation should proceed
214-
func handleInstallationCheck(installer *assets.Installer, forceFlag bool, output *cli.OutputHandler) bool {
215-
if installer.IsInstalled() && !forceFlag {
225+
// Check if installation should proceed
226+
if isAlreadyInstalled && !forceFlag {
216227
output.PrintWarning("Framework already installed in current directory")
217228
output.PrintInfo("If you want to proceed with installation, use the --force flag:")
218229
output.PrintInfo(" krci-ai install --force")
219-
return false
230+
return
220231
}
221232

222-
if installer.IsInstalled() && forceFlag {
233+
if isAlreadyInstalled && forceFlag {
223234
output.PrintWarning("Framework already installed - proceeding with forced installation")
224235
}
225236

226-
return true
237+
// Perform core installation
238+
if err := performCoreInstallation(installer, output, errorHandler); err != nil {
239+
return
240+
}
241+
242+
// Handle IDE integration
243+
// If sync-ide is set, use installed agents; otherwise use embedded assets
244+
if syncIDEFlag {
245+
output.PrintInfo("Setting up IDE integration from installed agents...")
246+
handleIDESync(installer, output, errorHandler)
247+
} else if ideFlag != "" {
248+
handleIDEIntegration(installer, ideFlag, output, errorHandler)
249+
}
250+
251+
// Show success and next steps
252+
showInstallationSuccess(installer, ideFlag, output)
227253
}
228254

229255
// performCoreInstallation handles the core framework installation
@@ -284,6 +310,9 @@ func init() {
284310
// Add selective installation flags (following bundle command patterns)
285311
installCmd.Flags().String("agent", "", "Install specific agents (comma or space separated: 'pm,architect' or 'pm architect')")
286312
installCmd.Flags().String("agents", "", "Alias for --agent flag")
313+
314+
// Add sync-ide flag
315+
installCmd.Flags().Bool("sync-ide", false, "Sync IDE integration files from installed agents")
287316
}
288317

289318
// validateIDEFlag validates the IDE flag value and returns error if invalid
@@ -335,3 +364,43 @@ func handleIDEIntegration(installer *assets.Installer, ideFlag string, output *c
335364
output.PrintSuccess("Windsurf IDE integration installed successfully!")
336365
}
337366
}
367+
368+
// handleIDESync syncs IDE integration files from installed agents
369+
func handleIDESync(installer *assets.Installer, output *cli.OutputHandler, errorHandler *cli.ErrorHandler) {
370+
// Check which IDE directories exist and sync them
371+
if installer.HasCursorIntegration() {
372+
output.PrintInfo("Syncing Cursor IDE integration...")
373+
if err := installer.SyncCursorIntegration(); err != nil {
374+
errorHandler.HandleError(err, "Failed to sync Cursor IDE integration")
375+
return
376+
}
377+
output.PrintSuccess("Cursor IDE integration synced successfully!")
378+
}
379+
380+
if installer.HasClaudeIntegration() {
381+
output.PrintInfo("Syncing Claude Code integration...")
382+
if err := installer.SyncClaudeIntegration(); err != nil {
383+
errorHandler.HandleError(err, "Failed to sync Claude Code integration")
384+
return
385+
}
386+
output.PrintSuccess("Claude Code integration synced successfully!")
387+
}
388+
389+
if installer.HasVSCodeIntegration() {
390+
output.PrintInfo("Syncing VS Code integration...")
391+
if err := installer.SyncVSCodeIntegration(); err != nil {
392+
errorHandler.HandleError(err, "Failed to sync VS Code integration")
393+
return
394+
}
395+
output.PrintSuccess("VS Code integration synced successfully!")
396+
}
397+
398+
if installer.HasWindsurfIntegration() {
399+
output.PrintInfo("Syncing Windsurf IDE integration...")
400+
if err := installer.SyncWindsurfIntegration(); err != nil {
401+
errorHandler.HandleError(err, "Failed to sync Windsurf IDE integration")
402+
return
403+
}
404+
output.PrintSuccess("Windsurf IDE integration synced successfully!")
405+
}
406+
}

internal/assets/installer_ide.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,3 +253,75 @@ func (i *Installer) GetTemplatesPath() string {
253253
func (i *Installer) GetDataPath() string {
254254
return GetDataPath(i.krciPath)
255255
}
256+
257+
// HasCursorIntegration checks if Cursor IDE integration directory exists
258+
func (i *Installer) HasCursorIntegration() bool {
259+
cursorPath := i.GetCursorRulesPath()
260+
info, err := os.Stat(cursorPath)
261+
return err == nil && info.IsDir()
262+
}
263+
264+
// HasClaudeIntegration checks if Claude Code integration directory exists
265+
func (i *Installer) HasClaudeIntegration() bool {
266+
claudePath := i.GetClaudeCommandsPath()
267+
info, err := os.Stat(claudePath)
268+
return err == nil && info.IsDir()
269+
}
270+
271+
// HasVSCodeIntegration checks if VS Code integration directory exists
272+
func (i *Installer) HasVSCodeIntegration() bool {
273+
vscodePath := i.GetVSCodeChatmodesPath()
274+
info, err := os.Stat(vscodePath)
275+
return err == nil && info.IsDir()
276+
}
277+
278+
// HasWindsurfIntegration checks if Windsurf IDE integration directory exists
279+
func (i *Installer) HasWindsurfIntegration() bool {
280+
windsurfPath := i.GetWindsurfRulesPath()
281+
info, err := os.Stat(windsurfPath)
282+
return err == nil && info.IsDir()
283+
}
284+
285+
// SyncCursorIntegration syncs Cursor IDE integration from installed agents
286+
func (i *Installer) SyncCursorIntegration() error {
287+
integration := &CursorIntegration{projectDir: i.projectDir}
288+
return i.syncIDEIntegration(integration, "Cursor IDE")
289+
}
290+
291+
// SyncClaudeIntegration syncs Claude Code integration from installed agents
292+
func (i *Installer) SyncClaudeIntegration() error {
293+
integration := &ClaudeIntegration{projectDir: i.projectDir}
294+
return i.syncIDEIntegration(integration, "Claude Code")
295+
}
296+
297+
// SyncVSCodeIntegration syncs VS Code integration from installed agents
298+
func (i *Installer) SyncVSCodeIntegration() error {
299+
integration := &VSCodeIntegration{targetDir: i.projectDir}
300+
return i.syncIDEIntegration(integration, "VS Code")
301+
}
302+
303+
// SyncWindsurfIntegration syncs Windsurf IDE integration from installed agents
304+
func (i *Installer) SyncWindsurfIntegration() error {
305+
integration := &WindsurfIntegration{targetDir: i.projectDir}
306+
return i.syncIDEIntegration(integration, "Windsurf IDE")
307+
}
308+
309+
// syncIDEIntegration is a generic method for syncing IDE integrations from installed agents
310+
func (i *Installer) syncIDEIntegration(integration IDEIntegration, ideName string) error {
311+
// Get list of agent files from installed location (not embedded)
312+
agentsPath := i.GetAgentsPath()
313+
agentFiles, err := filepath.Glob(filepath.Join(agentsPath, "*.yaml"))
314+
if err != nil {
315+
return fmt.Errorf("failed to find agent files: %w", err)
316+
}
317+
318+
// Generate IDE-specific files for each agent
319+
for _, agentFile := range agentFiles {
320+
if err := i.generateIDEFile(agentFile, integration); err != nil {
321+
agentName := strings.TrimSuffix(filepath.Base(agentFile), ".yaml")
322+
return fmt.Errorf("failed to generate %s file for %s: %w", ideName, agentName, err)
323+
}
324+
}
325+
326+
return nil
327+
}

0 commit comments

Comments
 (0)