Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 37 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/goose-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ is-terminal = "0.4.16"
anstream = "0.6.18"
url = "2.5.7"
open = "5.3.2"
dirs = "6.0.0"
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

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

The dirs dependency is added to Cargo.toml but is not used anywhere in the codebase. Consider removing it if it's not needed.

Suggested change
dirs = "6.0.0"

Copilot uses AI. Check for mistakes.

[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3", features = ["wincred"] }
Expand Down
108 changes: 108 additions & 0 deletions crates/goose-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::commands::configure::handle_configure;
use crate::commands::info::handle_info;
use crate::commands::project::{handle_project_default, handle_projects_interactive};
use crate::commands::recipe::{handle_deeplink, handle_list, handle_open, handle_validate};
use crate::commands::term::{handle_term_info, handle_term_init, handle_term_log, handle_term_run};

use crate::commands::schedule::{
handle_schedule_add, handle_schedule_cron_help, handle_schedule_list, handle_schedule_remove,
Expand Down Expand Up @@ -829,6 +830,81 @@ enum Command {
#[arg(long, help = "Authentication token to secure the web interface")]
auth_token: Option<String>,
},

/// Terminal-integrated session (one session per terminal)
#[command(
about = "Terminal-integrated goose session",
long_about = "Runs a goose session tied to your terminal window.\n\
Each terminal maintains its own persistent session that resumes automatically.\n\n\
Setup:\n \
eval \"$(goose term init zsh)\" # Add to ~/.zshrc\n\n\
Usage:\n \
goose term run \"list files in this directory\"\n \
@goose \"create a python script\" # using alias\n \
@g \"quick question\" # short alias"
)]
Term {
#[command(subcommand)]
command: TermCommand,
},
}

#[derive(Subcommand)]
enum TermCommand {
/// Print shell initialization script
#[command(
about = "Print shell initialization script",
long_about = "Prints shell configuration to set up terminal-integrated sessions.\n\
Each terminal gets a persistent goose session that automatically resumes.\n\n\
Setup:\n \
echo 'eval \"$(goose term init zsh)\"' >> ~/.zshrc\n \
source ~/.zshrc\n\n\
With --default (anything typed that isn't a command goes to goose):\n \
echo 'eval \"$(goose term init zsh --default)\"' >> ~/.zshrc"
)]
Init {
/// Shell type (bash, zsh, fish, powershell)
#[arg(value_enum)]
shell: Shell,

/// Make goose the default handler for unknown commands
#[arg(
long = "default",
help = "Make goose the default handler for unknown commands",
long_help = "When enabled, anything you type that isn't a valid command will be sent to goose. Only supported for zsh and bash."
)]
default: bool,
},

/// Log a shell command (called by shell hook)
#[command(about = "Log a shell command to the session", hide = true)]
Log {
/// The command that was executed
command: String,
},

/// Run a prompt in the terminal session
#[command(
about = "Run a prompt in the terminal session",
long_about = "Run a prompt in the terminal-integrated session.\n\n\
Examples:\n \
goose term run list files in this directory\n \
@goose list files # using alias\n \
@g why did that fail # short alias"
)]
Run {
/// The prompt to send to goose (multiple words allowed without quotes)
#[arg(required = true, num_args = 1..)]
prompt: Vec<String>,
},

/// Print session info for prompt integration
#[command(
about = "Print session info for prompt integration",
long_about = "Prints compact session info (token usage, model) for shell prompt integration.\n\
Example output: ●○○○○ sonnet"
)]
Info,
}

#[derive(clap::ValueEnum, Clone, Debug)]
Expand All @@ -838,6 +914,14 @@ enum CliProviderVariant {
Ollama,
}

#[derive(clap::ValueEnum, Clone, Debug)]
enum Shell {
Bash,
Zsh,
Fish,
Powershell,
}

#[derive(Debug)]
pub struct InputConfig {
pub contents: Option<String>,
Expand Down Expand Up @@ -874,6 +958,7 @@ pub async fn cli() -> anyhow::Result<()> {
Some(Command::Bench { .. }) => "bench",
Some(Command::Recipe { .. }) => "recipe",
Some(Command::Web { .. }) => "web",
Some(Command::Term { .. }) => "term",
None => "default_session",
};

Expand Down Expand Up @@ -1387,6 +1472,29 @@ pub async fn cli() -> anyhow::Result<()> {
crate::commands::web::handle_web(port, host, open, auth_token).await?;
return Ok(());
}
Some(Command::Term { command }) => {
match command {
TermCommand::Init { shell, default } => {
let shell_str = match shell {
Shell::Bash => "bash",
Shell::Zsh => "zsh",
Shell::Fish => "fish",
Shell::Powershell => "powershell",
};
handle_term_init(shell_str, default).await?;
}
TermCommand::Log { command } => {
handle_term_log(command).await?;
}
TermCommand::Run { prompt } => {
handle_term_run(prompt).await?;
}
TermCommand::Info => {
handle_term_info().await?;
}
}
return Ok(());
}
None => {
return if !Config::global().exists() {
handle_configure().await?;
Expand Down
1 change: 1 addition & 0 deletions crates/goose-cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ pub mod project;
pub mod recipe;
pub mod schedule;
pub mod session;
pub mod term;
pub mod update;
pub mod web;
Loading