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
9 changes: 9 additions & 0 deletions crates/forge_main/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,15 @@ pub struct Cli {
/// Event to dispatch to the workflow in JSON format.
#[arg(long, short = 'e')]
pub event: Option<String>,

/// Shell context from the terminal environment.
///
/// When provided by the shell plugin, this contains information about
/// the last command executed in the terminal and its exit code. This
/// helps the AI understand what the user was doing before asking for
/// help. Format: "command\nexit_code"
#[arg(long)]
pub shell_context: Option<String>,
}

impl Cli {
Expand Down
19 changes: 19 additions & 0 deletions crates/forge_main/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,25 @@ mod tests {
assert_eq!(cli_with_flags.restricted, true);
}

#[test]
fn test_cli_parsing_shell_context() {
let cli = Cli::parse_from([
"forge",
"-p",
"fix it",
"--shell-context",
"rm test\n1",
]);
assert_eq!(cli.prompt, Some("fix it".to_string()));
assert_eq!(cli.shell_context, Some("rm test\n1".to_string()));
}

#[test]
fn test_cli_shell_context_not_required() {
let cli = Cli::parse_from(["forge", "-p", "hello"]);
assert_eq!(cli.shell_context, None);
}

#[test]
fn test_commit_command_diff_field_initially_none() {
// Test that the diff field in CommitCommandGroup starts as None
Expand Down
33 changes: 33 additions & 0 deletions crates/forge_main/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2955,6 +2955,39 @@ impl<A: API + ConsoleWriter + 'static, F: Fn() -> A + Send + Sync> UI<A, F> {
event = event.additional_context(piped);
}

// Add shell context from the terminal environment if provided.
// The shell plugin captures the last command and its exit code,
// passing them via --shell-context so the AI knows what happened
// before the user's request.
if let Some(ref shell_ctx) = self.cli.shell_context {
let mut parts = shell_ctx.splitn(2, '\n');
let last_command = parts.next().unwrap_or_default();
let exit_code = parts.next().unwrap_or_default();

if !last_command.is_empty() {
let context_text = if exit_code == "0" {
format!(
"Terminal context — the user just ran this command successfully:\n$ {}",
last_command,
)
} else {
format!(
"Terminal context — the user just ran this command and it failed (exit code {}):\n$ {}",
exit_code,
last_command,
)
};

// If additional_context is already set (from piped input),
// prepend the shell context to it
if let Some(existing) = event.additional_context.take() {
event = event.additional_context(format!("{}\n\n{}", context_text, existing));
} else {
event = event.additional_context(context_text);
}
}
}

// Create the chat request with the event
let chat = ChatRequest::new(event, conversation_id);

Expand Down
3 changes: 3 additions & 0 deletions shell-plugin/forge.plugin.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ source "${0:A:h}/lib/highlight.zsh"
# Core utilities (includes logging)
source "${0:A:h}/lib/helpers.zsh"

# Shell context tracking (preexec/precmd hooks)
source "${0:A:h}/lib/shell_context.zsh"

# Completion widget
source "${0:A:h}/lib/completion.zsh"

Expand Down
4 changes: 4 additions & 0 deletions shell-plugin/lib/helpers.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ function _forge_exec_interactive() {
cmd=($_FORGE_BIN --agent "$agent_id")
[[ -n "$_FORGE_SESSION_MODEL" ]] && cmd+=(--model "$_FORGE_SESSION_MODEL")
[[ -n "$_FORGE_SESSION_PROVIDER" ]] && cmd+=(--provider "$_FORGE_SESSION_PROVIDER")
# Pass shell context (last command + exit code) when available
if [[ -n "$_FORGE_LAST_COMMAND" ]]; then
cmd+=(--shell-context "${_FORGE_LAST_COMMAND}"$'\n'"${_FORGE_LAST_EXIT_CODE}")
fi
cmd+=("$@")
"${cmd[@]}" </dev/tty >/dev/tty
}
Expand Down
31 changes: 31 additions & 0 deletions shell-plugin/lib/shell_context.zsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env zsh

# Shell context tracking for forge plugin
# Captures the last command and its exit code so that forge can understand
# what the user was doing before asking for help.
#
# Uses ZSH's preexec/precmd hook arrays so that existing user hooks are
# not overwritten.

# Variables to store shell context
typeset -g _FORGE_LAST_COMMAND=""
typeset -g _FORGE_LAST_EXIT_CODE=""

# preexec runs just before a command is executed.
# We capture the command string here.
function _forge_preexec() {
# $1 is the command string as typed by the user
_FORGE_LAST_COMMAND="$1"
}

# precmd runs just before the prompt is drawn (i.e., after the previous
# command finishes). We capture the exit code here.
function _forge_precmd() {
# $? is the exit code of the command that just finished
_FORGE_LAST_EXIT_CODE="$?"
}

# Register hooks using ZSH hook arrays (non-destructive)
autoload -Uz add-zsh-hook
add-zsh-hook preexec _forge_preexec
add-zsh-hook precmd _forge_precmd
Loading