Skip to content

Commit 6aba495

Browse files
author
Test User
committed
feat: Complete Phase 1 - IDE Integration Setup Wizard
- Created internal/wizard/integration.go with full TUI wizard - Implemented IDE selection menu with 8 options - Integrated with SetupIDE and VerifyIDE functions - Regenerates agents and rules after setup - Updated TUI app.go to call integration wizard - Updated executor.go to implement SetupIntegration - Added integration_setup recommendation to workflow guidance - All 15 TUI menu items now fully functional
1 parent df31eb6 commit 6aba495

File tree

4 files changed

+285
-5
lines changed

4 files changed

+285
-5
lines changed

internal/commands/executor.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package commands
22

33
import (
4+
"os"
5+
46
"github.com/DoPlan-dev/CLI/internal/tui"
7+
"github.com/DoPlan-dev/CLI/internal/wizard"
58
)
69

710
// TUICommandExecutor implements the CommandExecutor interface for the TUI
@@ -65,7 +68,10 @@ func (e *TUICommandExecutor) ApplyDesign() error {
6568
}
6669

6770
func (e *TUICommandExecutor) SetupIntegration() error {
68-
// TODO: Implement integration command
69-
return nil
71+
projectRoot, err := os.Getwd()
72+
if err != nil {
73+
return err
74+
}
75+
return wizard.RunIntegrationWizard(projectRoot)
7076
}
7177

internal/tui/app.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,11 +178,12 @@ func (a *App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
178178
},
179179
)
180180
case "integration":
181-
// This will be implemented in later phases
182181
return a, tea.Sequence(
183-
tea.Printf("Action '%s' - Coming soon in v0.0.19-beta!\n", msg.Action),
184182
func() tea.Msg {
185-
return screens.MenuActionMsg{Action: "menu"}
183+
if err := a.executor.SetupIntegration(); err != nil {
184+
return screens.ErrorMsg{Error: err}
185+
}
186+
return screens.SuccessMsg{Message: "IDE integration configured", Action: "integration_setup"}
186187
},
187188
)
188189
case "menu":

internal/wizard/integration.go

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
package wizard
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
8+
"github.com/DoPlan-dev/CLI/internal/config"
9+
"github.com/DoPlan-dev/CLI/internal/generators"
10+
"github.com/DoPlan-dev/CLI/internal/integration"
11+
"github.com/charmbracelet/bubbles/list"
12+
tea "github.com/charmbracelet/bubbletea"
13+
"github.com/charmbracelet/lipgloss"
14+
)
15+
16+
var (
17+
titleStyle = lipgloss.NewStyle().
18+
Foreground(lipgloss.Color("#667eea")).
19+
Bold(true).
20+
Padding(0, 1)
21+
22+
helpStyle = lipgloss.NewStyle().
23+
Foreground(lipgloss.Color("#666666"))
24+
)
25+
26+
type integrationModel struct {
27+
width int
28+
height int
29+
list list.Model
30+
projectRoot string
31+
selectedIDE string
32+
status string
33+
err error
34+
}
35+
36+
type integrationItem struct {
37+
id string
38+
title string
39+
description string
40+
}
41+
42+
func (i integrationItem) FilterValue() string { return i.title }
43+
func (i integrationItem) Title() string { return i.title }
44+
func (i integrationItem) Description() string { return i.description }
45+
46+
func RunIntegrationWizard(projectRoot string) error {
47+
items := []list.Item{
48+
integrationItem{
49+
id: "cursor",
50+
title: "Cursor",
51+
description: "AI-powered code editor with built-in chat",
52+
},
53+
integrationItem{
54+
id: "vscode",
55+
title: "VS Code + Copilot",
56+
description: "Visual Studio Code with GitHub Copilot",
57+
},
58+
integrationItem{
59+
id: "gemini",
60+
title: "Gemini CLI",
61+
description: "Google's Gemini CLI tool",
62+
},
63+
integrationItem{
64+
id: "claude",
65+
title: "Claude Code",
66+
description: "Anthropic's Claude Code",
67+
},
68+
integrationItem{
69+
id: "codex",
70+
title: "Codex CLI",
71+
description: "Codex command-line interface",
72+
},
73+
integrationItem{
74+
id: "opencode",
75+
title: "OpenCode",
76+
description: "OpenCode IDE integration",
77+
},
78+
integrationItem{
79+
id: "qwen",
80+
title: "Qwen Code",
81+
description: "Qwen Code IDE",
82+
},
83+
integrationItem{
84+
id: "other",
85+
title: "Other / Manual Setup",
86+
description: "View setup guide for other IDEs",
87+
},
88+
}
89+
90+
l := list.New(items, list.NewDefaultDelegate(), 0, 0)
91+
l.Title = "Select IDE for Integration"
92+
l.SetShowStatusBar(false)
93+
l.SetFilteringEnabled(true)
94+
l.Styles.Title = titleStyle
95+
l.Styles.HelpStyle = helpStyle
96+
97+
m := integrationModel{
98+
list: l,
99+
projectRoot: projectRoot,
100+
status: "Select an IDE to set up integration...",
101+
}
102+
103+
p := tea.NewProgram(m, tea.WithAltScreen())
104+
_, err := p.Run()
105+
return err
106+
}
107+
108+
func (m integrationModel) Init() tea.Cmd {
109+
return nil
110+
}
111+
112+
func (m integrationModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
113+
switch msg := msg.(type) {
114+
case tea.WindowSizeMsg:
115+
m.width = msg.Width
116+
m.height = msg.Height
117+
m.list.SetWidth(msg.Width - 4)
118+
m.list.SetHeight(msg.Height - 6)
119+
return m, nil
120+
121+
case tea.KeyMsg:
122+
switch msg.String() {
123+
case "ctrl+c", "q":
124+
return m, tea.Quit
125+
case "enter":
126+
selected := m.list.SelectedItem()
127+
if item, ok := selected.(integrationItem); ok {
128+
m.selectedIDE = item.id
129+
if item.id == "other" {
130+
m.status = "Opening setup guide..."
131+
return m, tea.Sequence(
132+
func() tea.Msg {
133+
// Show guide
134+
showOtherIDEGuide(m.projectRoot)
135+
return tea.Quit()
136+
},
137+
)
138+
}
139+
// Setup integration
140+
m.status = fmt.Sprintf("Setting up %s integration...", item.title)
141+
return m, tea.Sequence(
142+
func() tea.Msg {
143+
if err := setupIDEIntegration(m.projectRoot, item.id); err != nil {
144+
return errorMsg{err: err}
145+
}
146+
return successMsg{message: fmt.Sprintf("✅ %s integration set up successfully!", item.title)}
147+
},
148+
)
149+
}
150+
}
151+
152+
case errorMsg:
153+
m.err = msg.err
154+
m.status = fmt.Sprintf("❌ Error: %v", msg.err)
155+
return m, tea.Sequence(
156+
tea.Printf("Press any key to continue..."),
157+
func() tea.Msg {
158+
return tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune(" ")}
159+
},
160+
)
161+
162+
case successMsg:
163+
m.status = msg.message
164+
return m, tea.Sequence(
165+
tea.Printf("\n%s\n\nPress any key to continue...", msg.message),
166+
func() tea.Msg {
167+
return tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune(" ")}
168+
},
169+
)
170+
}
171+
172+
var cmd tea.Cmd
173+
m.list, cmd = m.list.Update(msg)
174+
return m, cmd
175+
}
176+
177+
func (m integrationModel) View() string {
178+
if m.width == 0 {
179+
return "Loading..."
180+
}
181+
182+
header := titleStyle.Render("⚙️ Setup AI/IDE Integration")
183+
help := helpStyle.Render("\n↑/↓: Navigate Enter: Select q: Quit")
184+
185+
var status string
186+
if m.status != "" {
187+
status = "\n" + m.status + "\n"
188+
}
189+
190+
var errorMsg string
191+
if m.err != nil {
192+
errorStyle := lipgloss.NewStyle().
193+
Foreground(lipgloss.Color("#ef4444")).
194+
Bold(true)
195+
errorMsg = "\n" + errorStyle.Render(fmt.Sprintf("Error: %v", m.err)) + "\n"
196+
}
197+
198+
return lipgloss.JoinVertical(
199+
lipgloss.Left,
200+
header,
201+
status,
202+
m.list.View(),
203+
errorMsg,
204+
help,
205+
)
206+
}
207+
208+
type errorMsg struct {
209+
err error
210+
}
211+
212+
type successMsg struct {
213+
message string
214+
}
215+
216+
func setupIDEIntegration(projectRoot, ide string) error {
217+
// Check if already installed
218+
if !config.IsInstalled(projectRoot) {
219+
return fmt.Errorf("DoPlan is not installed in this project. Run 'doplan install' first")
220+
}
221+
222+
// Setup IDE integration
223+
if err := integration.SetupIDE(projectRoot, ide); err != nil {
224+
return fmt.Errorf("failed to setup %s integration: %w", ide, err)
225+
}
226+
227+
// Regenerate agents and rules if needed
228+
agentsGen := generators.NewAgentsGenerator(projectRoot)
229+
if err := agentsGen.Generate(); err != nil {
230+
return fmt.Errorf("failed to regenerate agents: %w", err)
231+
}
232+
233+
rulesGen := generators.NewRulesGenerator(projectRoot)
234+
if err := rulesGen.Generate(); err != nil {
235+
return fmt.Errorf("failed to regenerate rules: %w", err)
236+
}
237+
238+
// Verify integration
239+
if err := integration.VerifyIDE(projectRoot, ide); err != nil {
240+
return fmt.Errorf("verification failed: %w", err)
241+
}
242+
243+
return nil
244+
}
245+
246+
func showOtherIDEGuide(projectRoot string) {
247+
guidePath := filepath.Join(projectRoot, ".doplan", "guides", "IDE_INTEGRATION.md")
248+
249+
// Read guide if it exists
250+
if content, err := os.ReadFile(guidePath); err == nil {
251+
fmt.Println(string(content))
252+
} else {
253+
// Show basic instructions
254+
fmt.Println("\n📚 IDE Integration Guide")
255+
fmt.Println("========================")
256+
fmt.Println("\nFor IDEs not listed, you can manually set up DoPlan integration:")
257+
fmt.Println("\n1. Create the following directories:")
258+
fmt.Println(" - .doplan/ai/agents/")
259+
fmt.Println(" - .doplan/ai/rules/")
260+
fmt.Println(" - .doplan/ai/commands/")
261+
fmt.Println("\n2. Run 'doplan install' to generate agents, rules, and commands")
262+
fmt.Println("\n3. Configure your IDE to reference these directories")
263+
fmt.Println("\n4. See .doplan/guides/IDE_INTEGRATION.md for detailed instructions")
264+
fmt.Println()
265+
}
266+
}
267+

internal/workflow/recommender.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,12 @@ func GetNextStep(lastAction string) (title string, description string) {
158158
description: "Action undone! Review what was changed and decide on next steps. Check dashboard: [d]ashboard",
159159
},
160160

161+
// Integration setup
162+
"integration_setup": {
163+
title: "Start Planning",
164+
description: "IDE integration configured! Start planning your project with @planner /Plan, or use the TUI menu: [p]lan",
165+
},
166+
161167
// Default fallback
162168
"": {
163169
title: "Get Started",

0 commit comments

Comments
 (0)