Skip to content

Conversation

@baxen
Copy link
Collaborator

@baxen baxen commented Nov 22, 2025

Term command is a new way to use goose without the builtin REPL

  • instead the session is tied right into your terminal

Type of Change

  • Feature

Term command is a new way to use goose without the builtin REPL
- instead the session is tied right into your terminal
Copilot AI review requested due to automatic review settings November 22, 2025 20:19
@baxen baxen requested a review from a team as a code owner November 22, 2025 20:19
@github-actions
Copy link
Contributor

github-actions bot commented Nov 22, 2025

PR Preview Action v1.6.0
Preview removed because the pull request was closed.
2025-11-27 01:29 UTC

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new goose term command that enables terminal-integrated sessions, allowing users to interact with goose directly from their shell prompt without entering a separate REPL. Each terminal maintains its own persistent session with shell command history awareness, so goose automatically sees what commands you've run when you ask questions.

Key changes:

  • New terminal integration command with shell-specific initialization scripts for bash, zsh, fish, and PowerShell
  • Database schema migration to v7 adding a shell_commands table to track command history
  • New CLI handlers for term init, term log, and term run subcommands

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
documentation/docs/guides/terminal-integration.md New documentation explaining terminal integration setup and usage
crates/goose/src/session/session_manager.rs Database schema bump to v7, new shell_commands table with migration logic and query methods
crates/goose-cli/src/commands/term.rs New command handlers for term init/log/run with shell script generation
crates/goose-cli/src/commands/mod.rs Module declaration for new term command
crates/goose-cli/src/cli.rs CLI integration with new Term command enum and shell type enum

"fish" => {
format!(
r#"set -gx GOOSE_TERMINAL_ID "{terminal_id}"
alias gt='{goose_bin} term run'
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fish shell aliases don't support arguments like bash/zsh. Use alias gt '{goose_bin} term run' (function-style without =) or create a proper function instead.

Suggested change
alias gt='{goose_bin} term run'
function gt; {goose_bin} term run $argv; end

Copilot uses AI. Check for mistakes.
function goose_preexec --on-event fish_preexec
string match -q -r '^goose term' -- $argv[1]; and return
string match -q -r '^gt ' -- $argv[1]; and return
{goose_bin} term log $argv[1] 2>/dev/null &
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing quotes around $argv[1] in Fish shell. Commands with spaces or special characters will fail. Use {goose_bin} term log \"$argv[1]\" 2>/dev/null &

Suggested change
{goose_bin} term log $argv[1] 2>/dev/null &
{goose_bin} term log "$argv[1]" 2>/dev/null &

Copilot uses AI. Check for mistakes.
"powershell" | "pwsh" => {
format!(
r#"$env:GOOSE_TERMINAL_ID = "{terminal_id}"
Set-Alias -Name gt -Value {{ {goose_bin} term run $args }}
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PowerShell Set-Alias -Value doesn't accept scriptblocks with parameters. Use a function instead: function gt {{ & {goose_bin} term run @args }}

Suggested change
Set-Alias -Name gt -Value {{ {goose_bin} term run $args }}
function gt { {goose_bin} term run $args }

Copilot uses AI. Check for mistakes.
Comment on lines 110 to 121
let session = SessionManager::create_session_with_id(
session_name.clone(),
working_dir.clone(),
session_name.clone(),
SessionType::User,
)
.await?;

SessionManager::update_session(&session.id)
.user_provided_name(session_name.clone())
.apply()
.await?;
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated session creation logic appears in both handle_term_log (lines 110-121) and handle_term_run (lines 151-163). Extract to a helper function like ensure_terminal_session to reduce duplication.

Copilot uses AI. Check for mistakes.
Comment on lines 27 to 28
[[ "$1" =~ ^goose\ term ]] && return
[[ "$1" =~ ^gt\ ]] && return
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regex pattern ^gt\ (line 28) requires gt followed by space but won't match standalone gt command. Should be ^gt(\s|$) or ^gt to handle both cases.

Copilot uses AI. Check for mistakes.
Comment on lines 46 to 47
[[ "$1" =~ ^goose\ term ]] && return
[[ "$1" =~ ^gt\ ]] && return
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as bash: regex pattern ^gt\ won't match standalone gt command. Should be ^gt(\s|$) or ^gt.

Copilot uses AI. Check for mistakes.
# Log commands to goose
function goose_preexec --on-event fish_preexec
string match -q -r '^goose term' -- $argv[1]; and return
string match -q -r '^gt ' -- $argv[1]; and return
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pattern ^gt requires space after gt but won't match standalone gt. Should be '^gt(\s|$)' or '^gt'.

Suggested change
string match -q -r '^gt ' -- $argv[1]; and return
string match -q -r '^gt(\\s|$)' -- $argv[1]; and return

Copilot uses AI. Check for mistakes.
Set-PSReadLineKeyHandler -Chord Enter -ScriptBlock {{
$line = $null
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$null)
if ($line -notmatch '^goose term' -and $line -notmatch '^gt ') {{
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pattern ^gt won't match standalone gt command. Should be ^gt(\s|$) or ^gt\b.

Copilot uses AI. Check for mistakes.
gt "what does this error mean?"
```

Goose responds, you read the answer, and you're back at your prompt. The conversation lives alongside your work, not in a separate window you have to manage.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Goose responds, you read the answer, and you're back at your prompt. The conversation lives alongside your work, not in a separate window you have to manage.
goose responds, you read the answer, and you're back at your prompt. The conversation lives alongside your work, not in a separate window you have to manage.

Copilot AI review requested due to automatic review settings November 23, 2025 00:00
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

$line = $null
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$null)
if ($line -notmatch '^goose term' -and $line -notmatch '^gt ') {{
Start-Job -ScriptBlock {{ {goose_bin} term log $using:line }} | Out-Null
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the goose binary path contains spaces, this command will fail. Quote the path in PowerShell: & '{goose_bin}' term log $using:line

Suggested change
Start-Job -ScriptBlock {{ {goose_bin} term log $using:line }} | Out-Null
Start-Job -ScriptBlock {{ '{goose_bin}' term log $using:line }} | Out-Null

Copilot uses AI. Check for mistakes.
goose_preexec() {{
[[ "$1" =~ ^goose\ term ]] && return
[[ "$1" =~ ^gt\ ]] && return
("{goose_bin}" term log "$1" &) 2>/dev/null
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the goose binary path contains spaces, this command will fail. Quote the path: ("'${goose_bin}'" term log "$1" &) or use escaped quotes within the path variable itself.

Copilot uses AI. Check for mistakes.
goose_preexec() {{
[[ "$1" =~ ^goose\ term ]] && return
[[ "$1" =~ ^gt\ ]] && return
("{goose_bin}" term log "$1" &) 2>/dev/null
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the goose binary path contains spaces, this command will fail. Quote the path: ("'${goose_bin}'" term log "$1" &)

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,75 @@
# Terminal Integration

The `goose term` commands let you talk to goose directly from your shell prompt. Instead of switching to a separate REPL session, you stay in your terminal and call goose when you need it.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is such a cool feature, but since it's not released yet, can you make the document unlisted? We can remove it when it gets released.

The way you do that..is in the frontmatter write "unlisted: true"

@@ -0,0 +1,75 @@
# Terminal Integration

The `goose term` commands let you talk to goose directly from your shell prompt. Instead of switching to a separate REPL session, you stay in your terminal and call goose when you need it.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is such a cool feature, but since it's not released yet, can you make the document unlisted? We can remove it when it gets released.

The way you do that..is in the frontmatter write "unlisted: true"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And noting to myself that we should have this mentioned in the CLI commands section

- Fix bash/zsh: properly quote goose binary path to handle spaces
- Extract session creation logic to ensure_terminal_session helper
- Reduces code duplication between handle_term_log and handle_term_run
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

@michaelneale
Copy link
Collaborator

michaelneale commented Nov 23, 2025

nice - I did note when I ran something which didn't involve a tool call, it didn't always return results... (ie ask "how are you") - if it uses a tool call seems all ok., but intermittent... digging in.

Also ...

command_not_found_handler() {
  local cmd="$*"
  gt "$cmd"
}

I added that to it and works well (even with the eval, automatically, if not too invasive!).

Very cool and innovative! (I also wonder if we could add a nice little oh my zsh like decoration to know goose is on)

I do think, somehow, dont' want each invocation to show up as a "term..." shell in the session history if possible?

use utoipa::ToSchema;

const CURRENT_SCHEMA_VERSION: i32 = 6;
const CURRENT_SCHEMA_VERSION: i32 = 7;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess if we are changing the schema - we can make the term sessions not show up the usual way? (wonder if there is a less invasive way).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

like having these term: specific sessions not show up in the history for things like the goose desktop? could do!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah - I have a PR that does that, Session::Hidden is fine.

.await
}

async fn add_shell_command(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused as to why we need to track these separately? as a special case?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is used by the preexec hook so that goose actually gets a record of the commands that you ran by hand in your terminal between interactions with goose. could make this optional but i find it very useful to not have to tell it what i was doing

Copy link
Collaborator

@DOsinga DOsinga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems pretty awesome!

I would prefer it if we can do this without making changes to the session manager. I don't think you need the create_with_session_id in this flow and I wonder if we can get the commands out of the sessions.

there's also a bunch of LLM-y comments in there that I could do without.

.await
.is_err()
{
let session = SessionManager::create_session_with_id(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need to create a session with a specific id, why not just use the name of the session here instead? or alternatively, we could create the session the first time and then write that session id to the config file; that way you wouldn't have to set the environment variable either


/// Handle `goose term init <shell>` - print shell initialization script
pub fn handle_term_init(shell: &str) -> Result<()> {
let terminal_id = Uuid::new_v4().to_string();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually you could just create the session here and then use the session id of that session as the terminal_id

let session = SessionManager::get_session(&session_name, false).await.ok();
let total_tokens = session.as_ref().and_then(|s| s.total_tokens).unwrap_or(0) as usize;

let model_name = Config::global()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we store the model and provider now in the session - should probably get it from there

}
};

let commands = SessionManager::get_shell_commands_since_last_message(&session_id).await?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not entirely sure I follow what we are fetching here. doesn't each session add at least one message to the conversation so isn't this then automatically empty?

in general though, adding shell commands to the session manager seems rather specific for this application. couldn't we just list the messages from session and exclude the <shell_history></shell_history> from that to get the same result? or if that doesn't work, maybe add a message content type?

@michaelneale
Copy link
Collaborator

my enhancements so far: #5852

@michaelneale michaelneale mentioned this pull request Nov 24, 2025
1 task
@michaelneale
Copy link
Collaborator

michaelneale commented Nov 24, 2025

ok @baxen @DOsinga #5852 - is my take, this doesn't change session manager, and makes sessions hidden type etc - but seems to work the same if you wan to try it? not sure if the approach for storing the terminal info is better/worse than putting it in sessions, but it is more isolated change.

@michaelneale
Copy link
Collaborator

all of this has been pulled over here: #5887

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants