10 tea "github.com/charmbracelet/bubbletea"
11 zone "github.com/lrstanley/bubblezone"
12 "github.com/opencode-ai/opencode/internal/app"
13 "github.com/opencode-ai/opencode/internal/config"
14 "github.com/opencode-ai/opencode/internal/db"
15 "github.com/opencode-ai/opencode/internal/llm/agent"
16 "github.com/opencode-ai/opencode/internal/logging"
17 "github.com/opencode-ai/opencode/internal/pubsub"
18 "github.com/opencode-ai/opencode/internal/tui"
19 "github.com/opencode-ai/opencode/internal/version"
20 "github.com/spf13/cobra"
23var rootCmd = &cobra.Command{
25 Short: "A terminal AI assistant for software development",
26 Long: `OpenCode is a powerful terminal-based AI assistant that helps with software development tasks.
27It provides an interactive chat interface with AI capabilities, code analysis, and LSP integration
28to assist developers in writing, debugging, and understanding code directly from the terminal.`,
29 RunE: func(cmd *cobra.Command, args []string) error {
30 // If the help flag is set, show the help message
31 if cmd.Flag("help").Changed {
35 if cmd.Flag("version").Changed {
36 fmt.Println(version.Version)
41 debug, _ := cmd.Flags().GetBool("debug")
42 cwd, _ := cmd.Flags().GetString("cwd")
46 return fmt.Errorf("failed to change directory: %v", err)
52 return fmt.Errorf("failed to get current working directory: %v", err)
56 _, err := config.Load(cwd, debug)
61 // Connect DB, this will also run migrations
62 conn, err := db.Connect()
67 // Create main context for the application
68 ctx, cancel := context.WithCancel(context.Background())
71 app, err := app.New(ctx, conn)
73 logging.Error("Failed to create app: %v", err)
79 program := tea.NewProgram(
84 // Initialize MCP tools in the background
85 initMCPTools(ctx, app)
87 // Setup the subscriptions, this will send services events to the TUI
88 ch, cancelSubs := setupSubscriptions(app, ctx)
90 // Create a context for the TUI message handler
91 tuiCtx, tuiCancel := context.WithCancel(ctx)
92 var tuiWg sync.WaitGroup
95 // Set up message handling for the TUI
98 defer logging.RecoverPanic("TUI-message-handler", func() {
99 attemptTUIRecovery(program)
104 case <-tuiCtx.Done():
105 logging.Info("TUI message handler shutting down")
107 case msg, ok := <-ch:
109 logging.Info("TUI message channel closed")
117 // Cleanup function for when the program exits
122 // Cancel subscriptions first
125 // Then cancel TUI message handler
128 // Wait for TUI message handler to finish
131 logging.Info("All goroutines cleaned up")
135 result, err := program.Run()
139 logging.Error("TUI error: %v", err)
140 return fmt.Errorf("TUI error: %v", err)
143 logging.Info("TUI exited with result: %v", result)
148// attemptTUIRecovery tries to recover the TUI after a panic
149func attemptTUIRecovery(program *tea.Program) {
150 logging.Info("Attempting to recover TUI after panic")
152 // We could try to restart the TUI or gracefully exit
153 // For now, we'll just quit the program to avoid further issues
157func initMCPTools(ctx context.Context, app *app.App) {
159 defer logging.RecoverPanic("MCP-goroutine", nil)
161 // Create a context with timeout for the initial MCP tools fetch
162 ctxWithTimeout, cancel := context.WithTimeout(ctx, 30*time.Second)
165 // Set this up once with proper error handling
166 agent.GetMcpTools(ctxWithTimeout, app.Permissions)
167 logging.Info("MCP message handling goroutine exiting")
171func setupSubscriber[T any](
175 subscriber func(context.Context) <-chan pubsub.Event[T],
176 outputCh chan<- tea.Msg,
181 defer logging.RecoverPanic(fmt.Sprintf("subscription-%s", name), nil)
183 subCh := subscriber(ctx)
187 case event, ok := <-subCh:
189 logging.Info("subscription channel closed", "name", name)
193 var msg tea.Msg = event
196 case outputCh <- msg:
197 case <-time.After(2 * time.Second):
198 logging.Warn("message dropped due to slow consumer", "name", name)
200 logging.Info("subscription cancelled", "name", name)
204 logging.Info("subscription cancelled", "name", name)
211func setupSubscriptions(app *app.App, parentCtx context.Context) (chan tea.Msg, func()) {
212 ch := make(chan tea.Msg, 100)
214 wg := sync.WaitGroup{}
215 ctx, cancel := context.WithCancel(parentCtx) // Inherit from parent context
217 setupSubscriber(ctx, &wg, "logging", logging.Subscribe, ch)
218 setupSubscriber(ctx, &wg, "sessions", app.Sessions.Subscribe, ch)
219 setupSubscriber(ctx, &wg, "messages", app.Messages.Subscribe, ch)
220 setupSubscriber(ctx, &wg, "permissions", app.Permissions.Subscribe, ch)
222 cleanupFunc := func() {
223 logging.Info("Cancelling all subscriptions")
224 cancel() // Signal all goroutines to stop
226 waitCh := make(chan struct{})
228 defer logging.RecoverPanic("subscription-cleanup", nil)
235 logging.Info("All subscription goroutines completed successfully")
236 close(ch) // Only close after all writers are confirmed done
237 case <-time.After(5 * time.Second):
238 logging.Warn("Timed out waiting for some subscription goroutines to complete")
242 return ch, cleanupFunc
246 err := rootCmd.Execute()
253 rootCmd.Flags().BoolP("help", "h", false, "Help")
254 rootCmd.Flags().BoolP("version", "v", false, "Version")
255 rootCmd.Flags().BoolP("debug", "d", false, "Debug")
256 rootCmd.Flags().StringP("cwd", "c", "", "Current working directory")
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list