diff --git a/internal/cmd/root.go b/internal/cmd/root.go index c6c24d5963c57981b1e91911146c1893728ffe37..85c815ea37a687383f979bba68740200be54c7cb 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -43,6 +43,9 @@ to assist developers in writing, debugging, and understanding code directly from # Run a single non-interactive prompt with JSON output format crush -p "Explain the use of context in Go" -f json + # Start interactive session with initial prompt + crush -i "Explain the use of context in Go" + # Run in dangerous mode (auto-accept all permissions) crush -y `, @@ -52,6 +55,7 @@ to assist developers in writing, debugging, and understanding code directly from debug, _ := cmd.Flags().GetBool("debug") cwd, _ := cmd.Flags().GetString("cwd") prompt, _ := cmd.Flags().GetString("prompt") + initial, _ := cmd.Flags().GetString("initial") quiet, _ := cmd.Flags().GetBool("quiet") yolo, _ := cmd.Flags().GetBool("yolo") @@ -107,7 +111,7 @@ to assist developers in writing, debugging, and understanding code directly from // Set up the TUI. program := tea.NewProgram( - tui.New(app), + tui.New(app, initial), tea.WithAltScreen(), tea.WithContext(ctx), tea.WithMouseCellMotion(), // Use cell motion instead of all motion to reduce event flooding @@ -141,6 +145,7 @@ func init() { rootCmd.Flags().BoolP("help", "h", false, "Help") rootCmd.Flags().BoolP("debug", "d", false, "Debug") rootCmd.Flags().StringP("prompt", "p", "", "Prompt to run in non-interactive mode") + rootCmd.Flags().StringP("initial", "i", "", "Initial prompt to start interactive session with") rootCmd.Flags().BoolP("yolo", "y", false, "Automatically accept all permissions (dangerous mode)") // Add quiet flag to hide spinner in non-interactive mode diff --git a/internal/tui/components/chat/editor/editor.go b/internal/tui/components/chat/editor/editor.go index 4e5f0bc431eb466cea5c6c7d436234c7a5e8531b..33150955f36e8f4f86dad140bad8e53f46d08fe8 100644 --- a/internal/tui/components/chat/editor/editor.go +++ b/internal/tui/components/chat/editor/editor.go @@ -37,6 +37,7 @@ type Editor interface { SetSession(session session.Session) tea.Cmd IsCompletionsOpen() bool Cursor() *tea.Cursor + SendMessage() tea.Cmd } type FileCompletionItem struct { @@ -442,7 +443,11 @@ func (c *editorCmp) IsCompletionsOpen() bool { return c.isCompletionsOpen } -func New(app *app.App) Editor { +func (c *editorCmp) SendMessage() tea.Cmd { + return c.send() +} + +func New(app *app.App, initialPrompt string) Editor { t := styles.CurrentTheme() ta := textarea.New() ta.SetStyles(t.S().TextArea) @@ -462,6 +467,12 @@ func New(app *app.App) Editor { ta.SetVirtualCursor(false) ta.Focus() + // Set initial prompt if provided + if initialPrompt != "" { + ta.SetValue(initialPrompt) + ta.MoveToEnd() + } + return &editorCmp{ // TODO: remove the app instance from here app: app, diff --git a/internal/tui/page/chat/chat.go b/internal/tui/page/chat/chat.go index c03a369ae371c0b9fadb9cd170e35618aa5e5b40..80a3b8586f4bfd634cf9d0f5b8dd58b5428b878b 100644 --- a/internal/tui/page/chat/chat.go +++ b/internal/tui/page/chat/chat.go @@ -110,19 +110,32 @@ type chatPage struct { splashFullScreen bool isOnboarding bool isProjectInit bool + + // Initial prompt to send automatically + initialPrompt string } -func New(app *app.App) ChatPage { - return &chatPage{ - app: app, - keyMap: DefaultKeyMap(), - header: header.New(app.LSPClients), - sidebar: sidebar.New(app.History, app.LSPClients, false), - chat: chat.New(app), - editor: editor.New(app), - splash: splash.New(), - focusedPane: PanelTypeSplash, +func New(app *app.App, initialPrompt string) ChatPage { + chatPage := &chatPage{ + app: app, + keyMap: DefaultKeyMap(), + header: header.New(app.LSPClients), + sidebar: sidebar.New(app.History, app.LSPClients, false), + chat: chat.New(app), + editor: editor.New(app, initialPrompt), + splash: splash.New(), + focusedPane: PanelTypeSplash, + initialPrompt: initialPrompt, } + + // If we have an initial prompt and the app is configured, focus the editor + if initialPrompt != "" && config.HasInitialDataConfig() { + if b, _ := config.ProjectNeedsInitialization(); !b { + chatPage.focusedPane = PanelTypeEditor + } + } + + return chatPage } func (p *chatPage) Init() tea.Cmd { @@ -132,6 +145,8 @@ func (p *chatPage) Init() tea.Cmd { p.forceCompact = compact p.sidebar.SetCompactMode(p.compact) + var cmds []tea.Cmd + // Set splash state based on config if !config.HasInitialDataConfig() { // First-time setup: show model selection @@ -147,15 +162,22 @@ func (p *chatPage) Init() tea.Cmd { // Ready to chat: focus editor, splash in background p.focusedPane = PanelTypeEditor p.splashFullScreen = false + + // If we have an initial prompt, automatically send it + if p.initialPrompt != "" { + cmds = append(cmds, p.editor.SendMessage()) + } } - return tea.Batch( + cmds = append(cmds, p.header.Init(), p.sidebar.Init(), p.chat.Init(), p.editor.Init(), p.splash.Init(), ) + + return tea.Batch(cmds...) } func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { @@ -270,6 +292,12 @@ func (p *chatPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) { p.isOnboarding = false p.isProjectInit = false p.focusedPane = PanelTypeEditor + + // If we have an initial prompt, automatically send it after onboarding completes + if p.initialPrompt != "" { + return p, tea.Batch(p.SetSize(p.width, p.height), p.editor.SendMessage()) + } + return p, p.SetSize(p.width, p.height) case tea.KeyPressMsg: switch { diff --git a/internal/tui/tui.go b/internal/tui/tui.go index 0b448f1afe26a9c9ae60a5c335271630ca7468ec..44d071a8b2a70199e5bb8867b79521e796555e57 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -493,8 +493,8 @@ func (a *appModel) View() tea.View { } // New creates and initializes a new TUI application model. -func New(app *app.App) tea.Model { - chatPage := chat.New(app) +func New(app *app.App, initialPrompt string) tea.Model { + chatPage := chat.New(app, initialPrompt) keyMap := DefaultKeyMap() keyMap.pageBindings = chatPage.Bindings()