From d1f00842fe4430664affb5844312e1aa0e81ec48 Mon Sep 17 00:00:00 2001 From: Amir Deris Date: Mon, 8 Sep 2025 19:57:04 -0700 Subject: [PATCH 1/5] modified gromit to have more conversation with AI --- gromit.go | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/gromit.go b/gromit.go index f14a326..4d19952 100644 --- a/gromit.go +++ b/gromit.go @@ -8,6 +8,7 @@ import ( "io" "os" "os/exec" + "regexp" "runtime" "sort" "strings" @@ -18,8 +19,9 @@ import ( const systemPrompt = `You are an assistant providing terminal commands based on user's questions. You will be given a question about how to do something in the CLI environment. You will then find out what command to execute and provide the command. - Do not provide any additional information, explanation or context, just the linux command. - For example, if question is about listing all files in a directory for linux, respond with "ls".` + Do not provide any additional information, explanation or context, just the linux command inside *** marker. + For example, if question is about listing all files in a directory for linux, respond with "***ls***". + If no question is asked by user, continue the conversation.` type Gromit struct { cli.Command @@ -131,10 +133,6 @@ func WithAskForConfirmation(confirm bool) ConfigurationModifier { func (g *Gromit) actionGromit(ctx context.Context, command *cli.Command) error { commandArgs := command.Args().Slice() query := strings.Join(commandArgs, " ") - if query == "" { - g.print("Please run ./gromit --help to see usage") - return nil - } prompt := g.String("systemPrompt") if prompt == "" { prompt = systemPrompt @@ -178,12 +176,25 @@ func (g *Gromit) handleUserQuery(ctx context.Context, query string) error { if err != nil { return err } - exeCommand, err := assister.GetTerminalCommand(ctx, query) + if query == "" { + query = "Can you please introduce yourself?" + } + response, err := assister.GetTerminalCommand(ctx, query) if err != nil { return err } + //command is enclosed in *** marker + regexp := regexp.MustCompile(`\*\*\*(.*?)\*\*\*`) + commands := regexp.FindStringSubmatch(response) + var command string + if len(commands) > 0 { + command = commands[1] + } else { + g.print(response) + return nil + } g.print("In order to do that, you need to run:") - g.print(exeCommand) + g.print(command) confirmation, err := g.askConfirmation("Would you like to run this command?") if err != nil { @@ -191,7 +202,7 @@ func (g *Gromit) handleUserQuery(ctx context.Context, query string) error { return err } if confirmation.confirmed { - err = g.executeCommand(exeCommand) + err = g.executeCommand(command) if err != nil { return err } From f192b109ac4c94030e62929eb84d0a5896e76d8b Mon Sep 17 00:00:00 2001 From: Amir Deris Date: Mon, 8 Sep 2025 20:40:04 -0700 Subject: [PATCH 2/5] separated conversation from command execution --- gromit.go | 45 ++++++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/gromit.go b/gromit.go index 4d19952..3f38797 100644 --- a/gromit.go +++ b/gromit.go @@ -145,43 +145,44 @@ func (g *Gromit) actionGromit(ctx context.Context, command *cli.Command) error { model: g.String("model"), systemPrompt: prompt, } - err := g.handleUserQuery(ctx, query) + terminalCommand, err := g.extractCommandForQuery(ctx, query) if err != nil { return err } + if terminalCommand != "" { + g.handleTerminalCommand(ctx, terminalCommand) + } for ctx.Err() == nil { - confirmation, err := g.askConfirmation("Can I help you with anything else?") + //read the user input, pass it to AI + reader := bufio.NewReader(os.Stdin) + query, err := reader.ReadString('\n') if err != nil { return err } - if confirmation.confirmed { - g.print("How can I help?") - reader := bufio.NewReader(os.Stdin) - query, err := reader.ReadString('\n') - if err != nil { - return err - } - if err = g.handleUserQuery(ctx, query); err != nil { - return err - } + terminalCommand, err := g.extractCommandForQuery(ctx, query) + if err != nil { + return err + } + if terminalCommand != "" { + g.handleTerminalCommand(ctx, terminalCommand) } else { - break + return nil } } return nil } -func (g *Gromit) handleUserQuery(ctx context.Context, query string) error { +func (g *Gromit) extractCommandForQuery(ctx context.Context, query string) (string, error) { assister, err := g.AssisterCreator.GetAssister(g.configuration.AiParameters) if err != nil { - return err + return "", err } if query == "" { - query = "Can you please introduce yourself?" + query = "Can you please introduce yourself or continue the conversation?" } response, err := assister.GetTerminalCommand(ctx, query) if err != nil { - return err + return "", err } //command is enclosed in *** marker regexp := regexp.MustCompile(`\*\*\*(.*?)\*\*\*`) @@ -191,18 +192,20 @@ func (g *Gromit) handleUserQuery(ctx context.Context, query string) error { command = commands[1] } else { g.print(response) - return nil } - g.print("In order to do that, you need to run:") - g.print(command) + return command, nil +} +func (g *Gromit) handleTerminalCommand(ctx context.Context, terminalCommand string) error { + g.print("In order to do that, you need to run:") + g.print(terminalCommand) confirmation, err := g.askConfirmation("Would you like to run this command?") if err != nil { g.print("Error reading your response") return err } if confirmation.confirmed { - err = g.executeCommand(command) + err = g.executeCommand(terminalCommand) if err != nil { return err } From f6b4e96d14685b97dab6c0e3c4690b5d930fadb1 Mon Sep 17 00:00:00 2001 From: Amir Deris Date: Wed, 10 Sep 2025 19:56:54 -0700 Subject: [PATCH 3/5] Made conversation more continuous --- gromit.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/gromit.go b/gromit.go index 3f38797..b0f0a30 100644 --- a/gromit.go +++ b/gromit.go @@ -19,9 +19,9 @@ import ( const systemPrompt = `You are an assistant providing terminal commands based on user's questions. You will be given a question about how to do something in the CLI environment. You will then find out what command to execute and provide the command. - Do not provide any additional information, explanation or context, just the linux command inside *** marker. + Make sure to enclose the actual command inside *** marker. For example, if question is about listing all files in a directory for linux, respond with "***ls***". - If no question is asked by user, continue the conversation.` + If no question is asked by user, continue the conversation. If they want to exit, respond with "***exit***".` type Gromit struct { cli.Command @@ -150,7 +150,10 @@ func (g *Gromit) actionGromit(ctx context.Context, command *cli.Command) error { return err } if terminalCommand != "" { - g.handleTerminalCommand(ctx, terminalCommand) + err = g.handleTerminalCommand(ctx, terminalCommand) + if err != nil { + return err + } } for ctx.Err() == nil { //read the user input, pass it to AI @@ -163,10 +166,14 @@ func (g *Gromit) actionGromit(ctx context.Context, command *cli.Command) error { if err != nil { return err } + if terminalCommand == "exit" { + break + } if terminalCommand != "" { - g.handleTerminalCommand(ctx, terminalCommand) - } else { - return nil + err = g.handleTerminalCommand(ctx, terminalCommand) + if err != nil { + return err + } } } return nil From 4638623c4e205839dafc5bf7b4f90b96a5a088b2 Mon Sep 17 00:00:00 2001 From: Amir Deris Date: Wed, 10 Sep 2025 20:23:26 -0700 Subject: [PATCH 4/5] Fixing tests --- gromit.go | 2 +- gromit_test.go | 19 +++++-------------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/gromit.go b/gromit.go index b0f0a30..6bd426b 100644 --- a/gromit.go +++ b/gromit.go @@ -157,7 +157,7 @@ func (g *Gromit) actionGromit(ctx context.Context, command *cli.Command) error { } for ctx.Err() == nil { //read the user input, pass it to AI - reader := bufio.NewReader(os.Stdin) + reader := bufio.NewReader(g.Reader) query, err := reader.ReadString('\n') if err != nil { return err diff --git a/gromit_test.go b/gromit_test.go index d8f7a8c..b2d9934 100644 --- a/gromit_test.go +++ b/gromit_test.go @@ -54,21 +54,13 @@ func TestMessagePrinter(t *testing.T) { require.Equal(t, "✌️ hello \r\n", buff.String()) } -func TestConfigurationPromptPrefix(t *testing.T) { - var buff bytes.Buffer - g, err := NewGromit(&mockAIProvider{}, WithPromptPrefix("🏝️"), WithWriter(&buff)) - require.NoError(t, err) - g.Run(t.Context(), []string{}) - require.Contains(t, buff.String(), "🏝️ Please run ./gromit --help to see usage") -} - func TestWhenAIProviderFailsToCreateAssister(t *testing.T) { m := &mockAIProvider{ assisterError: errors.New("Unable to create assister"), } g, err := NewGromit(m) require.NoError(t, err) - err = g.handleUserQuery(t.Context(), "some query") + _, err = g.extractCommandForQuery(t.Context(), "some query") require.EqualError(t, err, "Unable to create assister") } @@ -85,19 +77,18 @@ func TestWhenAIProviderFailsToFindTheCommand(t *testing.T) { func TestAIAssisterFindingCorrectCommand(t *testing.T) { var buff bytes.Buffer m := &mockAIProvider{ - commandResult: "ls", + commandResult: "***ls***", } g, err := NewGromit(m, WithWriter(&buff), WithPromptPrefix("🐶"), WithAskForConfirmation(false)) require.NoError(t, err) - + g.Reader = strings.NewReader("I want to list all files in current directory\n") g.Run(t.Context(), []string{"gromit", "--model", "myModel", "--agent", "myAgent", "--apiKey=key1234", "--maxTokens=2000", - "--systemPrompt", "myPrompt", "I", "want", "to", "list", "all", "files", "in", "current", "directory"}) + "--systemPrompt", "myPrompt", "hello", "my", "ai", "friend!"}) result := buff.String() require.Contains(t, result, "🐶 In order to do that, you need to run") require.Contains(t, result, "🐶 ls") require.Contains(t, result, "README.md") - require.Contains(t, result, "🐶 How can I help?") require.Equal(t, "myAgent", m.actualAiParameters.agent) require.Equal(t, "myModel", m.actualAiParameters.model) @@ -107,7 +98,7 @@ func TestAIAssisterFindingCorrectCommand(t *testing.T) { require.Contains(t, m.actualAiParameters.systemPrompt, "User's operating system is") require.Contains(t, m.actualAiParameters.systemPrompt, "User's current shell is") require.Contains(t, m.actualAiParameters.systemPrompt, "User's available path commands are") - require.Equal(t, "I want to list all files in current directory", m.actualUserMessage) + require.Contains(t, m.actualUserMessage, "I want to list all files in current directory") } type mockAIProvider struct { From 7c9c1006ea80b3de2d9660f6e79e6b197958b3fc Mon Sep 17 00:00:00 2001 From: Amir Deris Date: Wed, 10 Sep 2025 20:29:50 -0700 Subject: [PATCH 5/5] Updated github workflow action --- .github/workflows/build.yaml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 8e3a8ad..c5d8261 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -61,12 +61,4 @@ jobs: with: go-version: '1.24' - name: Build the code - run: go build -o gromitai . - - name: Run cli with no commands and check the usage output - run: | - output=$(./gromitai || true) - echo "$output" - if [[ "$output" != *"⚡️🤖 Please run ./gromit --help to see usage"* ]]; then - echo "❌ Usage message does not match the expected value" - exit 1 - fi \ No newline at end of file + run: go build -o gromitai . \ No newline at end of file