diff options
| author | Ed Zynda <[email protected]> | 2025-05-16 14:06:28 +0300 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-05-16 06:06:28 -0500 |
| commit | 623d132772b9c69dd6d99ed4004b26c46dbe43a4 (patch) | |
| tree | 1547bc8b7f7b8487dbdc34c76998416a37db7618 /cmd | |
| parent | d127a1c4ebe326344dc77fe3d136c033da6031fd (diff) | |
| download | opencode-623d132772b9c69dd6d99ed4004b26c46dbe43a4.tar.gz opencode-623d132772b9c69dd6d99ed4004b26c46dbe43a4.zip | |
feat: Add non-interactive mode (#18)
Diffstat (limited to 'cmd')
| -rw-r--r-- | cmd/root.go | 111 |
1 files changed, 111 insertions, 0 deletions
diff --git a/cmd/root.go b/cmd/root.go index 1e96e20c4..65da66e69 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -15,11 +15,15 @@ import ( "github.com/sst/opencode/internal/app" "github.com/sst/opencode/internal/config" "github.com/sst/opencode/internal/db" + "github.com/sst/opencode/internal/format" "github.com/sst/opencode/internal/llm/agent" "github.com/sst/opencode/internal/logging" "github.com/sst/opencode/internal/lsp/discovery" + "github.com/sst/opencode/internal/message" + "github.com/sst/opencode/internal/permission" "github.com/sst/opencode/internal/pubsub" "github.com/sst/opencode/internal/tui" + "github.com/sst/opencode/internal/tui/components/spinner" "github.com/sst/opencode/internal/version" ) @@ -88,6 +92,19 @@ to assist developers in writing, debugging, and understanding code directly from return err } + // Check if we're in non-interactive mode + prompt, _ := cmd.Flags().GetString("prompt") + if prompt != "" { + outputFormatStr, _ := cmd.Flags().GetString("output-format") + outputFormat := format.OutputFormat(outputFormatStr) + if !outputFormat.IsValid() { + return fmt.Errorf("invalid output format: %s", outputFormatStr) + } + + quiet, _ := cmd.Flags().GetBool("quiet") + return handleNonInteractiveMode(cmd.Context(), prompt, outputFormat, quiet) + } + // Run LSP auto-discovery if err := discovery.IntegrateLSPServers(cwd); err != nil { slog.Warn("Failed to auto-discover LSP servers", "error", err) @@ -205,6 +222,97 @@ func initMCPTools(ctx context.Context, app *app.App) { }() } +// handleNonInteractiveMode processes a single prompt in non-interactive mode +func handleNonInteractiveMode(ctx context.Context, prompt string, outputFormat format.OutputFormat, quiet bool) error { + slog.Info("Running in non-interactive mode", "prompt", prompt, "format", outputFormat, "quiet", quiet) + + // Start spinner if not in quiet mode + var s *spinner.Spinner + if !quiet { + s = spinner.NewSpinner("Thinking...") + s.Start() + defer s.Stop() + } + + // Connect DB, this will also run migrations + conn, err := db.Connect() + if err != nil { + return err + } + + // Create a context with cancellation + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + // Create the app + app, err := app.New(ctx, conn) + if err != nil { + slog.Error("Failed to create app", "error", err) + return err + } + + // Auto-approve all permissions for non-interactive mode + permission.AutoApproveSession(ctx, "non-interactive") + + // Create a new session for this prompt + session, err := app.Sessions.Create(ctx, "Non-interactive prompt") + if err != nil { + return fmt.Errorf("failed to create session: %w", err) + } + + // Set the session as current + app.CurrentSession = &session + + // Create the user message + _, err = app.Messages.Create(ctx, session.ID, message.CreateMessageParams{ + Role: message.User, + Parts: []message.ContentPart{message.TextContent{Text: prompt}}, + }) + if err != nil { + return fmt.Errorf("failed to create message: %w", err) + } + + // Run the agent to get a response + eventCh, err := app.PrimaryAgent.Run(ctx, session.ID, prompt) + if err != nil { + return fmt.Errorf("failed to run agent: %w", err) + } + + // Wait for the response + var response message.Message + for event := range eventCh { + if event.Err() != nil { + return fmt.Errorf("agent error: %w", event.Err()) + } + response = event.Response() + } + + // Get the text content from the response + content := "" + if textContent := response.Content(); textContent != nil { + content = textContent.Text + } + + // Format the output according to the specified format + formattedOutput, err := format.FormatOutput(content, outputFormat) + if err != nil { + return fmt.Errorf("failed to format output: %w", err) + } + + // Stop spinner before printing output + if !quiet && s != nil { + s.Stop() + } + + // Print the formatted output to stdout + fmt.Println(formattedOutput) + + // Shutdown the app + app.Shutdown() + + return nil +} + func setupSubscriber[T any]( ctx context.Context, wg *sync.WaitGroup, @@ -296,4 +404,7 @@ func init() { rootCmd.Flags().BoolP("version", "v", false, "Version") rootCmd.Flags().BoolP("debug", "d", false, "Debug") rootCmd.Flags().StringP("cwd", "c", "", "Current working directory") + rootCmd.Flags().StringP("prompt", "p", "", "Run a single prompt in non-interactive mode") + rootCmd.Flags().StringP("output-format", "f", "text", "Output format for non-interactive mode (text, json)") + rootCmd.Flags().BoolP("quiet", "q", false, "Hide spinner in non-interactive mode") } |
