10 tea "github.com/charmbracelet/bubbletea"
11 "github.com/opencode-ai/opencode/internal/app"
12 "github.com/opencode-ai/opencode/internal/config"
13 "github.com/opencode-ai/opencode/internal/db"
14 "github.com/opencode-ai/opencode/internal/llm/agent"
15 "github.com/opencode-ai/opencode/internal/logging"
16 "github.com/opencode-ai/opencode/internal/pubsub"
17 "github.com/opencode-ai/opencode/internal/tui"
18 "github.com/opencode-ai/opencode/internal/version"
19 zone "github.com/lrstanley/bubblezone"
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(
82 tea.WithMouseCellMotion(),
85 // Initialize MCP tools in the background
86 initMCPTools(ctx, app)
88 // Setup the subscriptions, this will send services events to the TUI
89 ch, cancelSubs := setupSubscriptions(app, ctx)
91 // Create a context for the TUI message handler
92 tuiCtx, tuiCancel := context.WithCancel(ctx)
93 var tuiWg sync.WaitGroup
96 // Set up message handling for the TUI
99 defer logging.RecoverPanic("TUI-message-handler", func() {
100 attemptTUIRecovery(program)
105 case <-tuiCtx.Done():
106 logging.Info("TUI message handler shutting down")
108 case msg, ok := <-ch:
110 logging.Info("TUI message channel closed")
118 // Cleanup function for when the program exits
123 // Cancel subscriptions first
126 // Then cancel TUI message handler
129 // Wait for TUI message handler to finish
132 logging.Info("All goroutines cleaned up")
136 result, err := program.Run()
140 logging.Error("TUI error: %v", err)
141 return fmt.Errorf("TUI error: %v", err)
144 logging.Info("TUI exited with result: %v", result)
149// attemptTUIRecovery tries to recover the TUI after a panic
150func attemptTUIRecovery(program *tea.Program) {
151 logging.Info("Attempting to recover TUI after panic")
153 // We could try to restart the TUI or gracefully exit
154 // For now, we'll just quit the program to avoid further issues
158func initMCPTools(ctx context.Context, app *app.App) {
160 defer logging.RecoverPanic("MCP-goroutine", nil)
162 // Create a context with timeout for the initial MCP tools fetch
163 ctxWithTimeout, cancel := context.WithTimeout(ctx, 30*time.Second)
166 // Set this up once with proper error handling
167 agent.GetMcpTools(ctxWithTimeout, app.Permissions)
168 logging.Info("MCP message handling goroutine exiting")
172func setupSubscriber[T any](
176 subscriber func(context.Context) <-chan pubsub.Event[T],
177 outputCh chan<- tea.Msg,
182 defer logging.RecoverPanic(fmt.Sprintf("subscription-%s", name), nil)
184 subCh := subscriber(ctx)
188 case event, ok := <-subCh:
190 logging.Info("subscription channel closed", "name", name)
194 var msg tea.Msg = event
197 case outputCh <- msg:
198 case <-time.After(2 * time.Second):
199 logging.Warn("message dropped due to slow consumer", "name", name)
201 logging.Info("subscription cancelled", "name", name)
205 logging.Info("subscription cancelled", "name", name)
212func setupSubscriptions(app *app.App, parentCtx context.Context) (chan tea.Msg, func()) {
213 ch := make(chan tea.Msg, 100)
215 wg := sync.WaitGroup{}
216 ctx, cancel := context.WithCancel(parentCtx) // Inherit from parent context
218 setupSubscriber(ctx, &wg, "logging", logging.Subscribe, ch)
219 setupSubscriber(ctx, &wg, "sessions", app.Sessions.Subscribe, ch)
220 setupSubscriber(ctx, &wg, "messages", app.Messages.Subscribe, ch)
221 setupSubscriber(ctx, &wg, "permissions", app.Permissions.Subscribe, ch)
223 cleanupFunc := func() {
224 logging.Info("Cancelling all subscriptions")
225 cancel() // Signal all goroutines to stop
227 waitCh := make(chan struct{})
229 defer logging.RecoverPanic("subscription-cleanup", nil)
236 logging.Info("All subscription goroutines completed successfully")
237 close(ch) // Only close after all writers are confirmed done
238 case <-time.After(5 * time.Second):
239 logging.Warn("Timed out waiting for some subscription goroutines to complete")
243 return ch, cleanupFunc
247 err := rootCmd.Execute()
254 rootCmd.Flags().BoolP("help", "h", false, "Help")
255 rootCmd.Flags().BoolP("version", "v", false, "Version")
256 rootCmd.Flags().BoolP("debug", "d", false, "Debug")
257 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