From acbc349b13183af7d548524c04bdd165ebdd3ee7 Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Sun, 28 Sep 2025 19:34:26 +0530 Subject: [PATCH 01/10] chore: remove unused function --- shell-plugin/forge.plugin.zsh | 38 ----------------------------------- 1 file changed, 38 deletions(-) diff --git a/shell-plugin/forge.plugin.zsh b/shell-plugin/forge.plugin.zsh index 9a0c59037b..5aec97d432 100644 --- a/shell-plugin/forge.plugin.zsh +++ b/shell-plugin/forge.plugin.zsh @@ -32,44 +32,6 @@ typeset -h _FORGE_CONVERSATION_ID="" # Store the last command for reuse typeset -h _FORGE_USER_ACTION="" - -# Helper function for buffer parsing (kept for potential future use) -# Note: This function is currently not used but kept for backward compatibility -function _forge_transform_buffer() { - local user_action="" - local input_text="" - - # Check if the line starts with any of the supported patterns - if [[ "$BUFFER" =~ "^:([a-zA-Z][a-zA-Z0-9_-]*)( (.*))?$" ]]; then - # Action with or without parameters: :foo or :foo bar baz - user_action="${match[1]}" - input_text="${match[3]:-}" # Use empty string if no parameters - elif [[ "$BUFFER" =~ "^: (.*)$" ]]; then - # Default action with parameters: : something - user_action="" - input_text="${match[1]}" - else - return 1 # No transformation needed - fi - - # Handle reset as a special case - if [[ "$user_action" == "$_FORGE_RESET_COMMAND" ]]; then - return 1 # No transformation needed - fi - - # Note: Cannot generate conversation ID here as this runs in subshell - # This would need to be passed as parameter or handled in parent - local conversation_id="${1:-$_FORGE_CONVERSATION_ID}" - - # Build the forge command with the appropriate command - local forge_cmd="$_FORGE_BIN --resume $conversation_id --agent ${user_action:-forge}" - - # Return the transformed command - echo "$forge_cmd -p $(printf %q "$input_text")" - - return 0 # Successfully transformed -} - # Custom completion widget that handles both :commands and @ completion function forge-completion() { local current_word="${LBUFFER##* }" From c3877a3c5cdc36dfbb5a0e40486c6881349c1190 Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Sun, 28 Sep 2025 20:28:17 +0530 Subject: [PATCH 02/10] fix(zsh): correct regex handling and improve command execution flow --- shell-plugin/forge.plugin.zsh | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/shell-plugin/forge.plugin.zsh b/shell-plugin/forge.plugin.zsh index 5aec97d432..3d84e507db 100644 --- a/shell-plugin/forge.plugin.zsh +++ b/shell-plugin/forge.plugin.zsh @@ -1,6 +1,6 @@ #!/usr/bin/env zsh -# Forge ZSH Plugin - ZLE Widget Version +# Forge ZSH Plugin - ZLE Widget Version # Converts command-tagged commands to resume conversations using ZLE widgets # Supports :plan/:p (muse), :ask/:a (sage), :new (start new conversation), :command_name (custom command), : (forge default) # Features: Auto-resume existing conversations or start new ones, @ tab completion support, banner display for new conversations @@ -105,7 +105,7 @@ function forge-accept-line() { # Action with or without parameters: :foo or :foo bar baz user_action="${match[1]}" input_text="${match[3]:-}" # Use empty string if no parameters - elif [[ "$BUFFER" =~ "^: (.*)$" ]]; then + elif [[ "$BUFFER" =~ "^: (.*)$" ]]; then # Default action with parameters: : something user_action="" input_text="${match[1]}" @@ -143,15 +143,15 @@ function forge-accept-line() { # Build and execute the forge command local forge_cmd="$_FORGE_BIN --resume $_FORGE_CONVERSATION_ID --agent ${user_action:-forge}" - local full_command="$forge_cmd -p $(printf %q "$input_text")" + local full_command="$forge_cmd -p \"$input_text\"" - echo # Add a newline before execution for better UX - eval "$full_command" + # Use push-line to clear the current line before execution + zle push-line - # Clear the buffer for next command - BUFFER="" - CURSOR=${#BUFFER} - zle reset-prompt + # Set buffer to the transformed command and execute + BUFFER="$full_command" + zle accept-line + return } # Register ZLE widgets From d9423ed1a78f2bb9412aa9365d910236473b3679 Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Sun, 28 Sep 2025 20:36:30 +0530 Subject: [PATCH 03/10] refactor: use env variables for ConversationId and ActiveAgent --- shell-plugin/forge.plugin.zsh | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/shell-plugin/forge.plugin.zsh b/shell-plugin/forge.plugin.zsh index 3d84e507db..0689a5efc9 100644 --- a/shell-plugin/forge.plugin.zsh +++ b/shell-plugin/forge.plugin.zsh @@ -27,7 +27,8 @@ ZSH_HIGHLIGHT_PATTERNS+=('(#s):[a-zA-Z]# *(*|[[:graph:]]*)' 'fg=white,bold') # Store conversation ID in a temporary variable (local to plugin) -typeset -h _FORGE_CONVERSATION_ID="" +export FORGE_CONVERSATION_ID="" +export FORGE_ACTIVE_AGENT="" # Store the last command for reuse typeset -h _FORGE_USER_ACTION="" @@ -121,11 +122,11 @@ function forge-accept-line() { # Handle reset command specially if [[ "$user_action" == "$_FORGE_RESET_COMMAND" ]]; then echo - if [[ -n "$_FORGE_CONVERSATION_ID" ]]; then - echo "\033[36m⏺\033[0m \033[90m[$(date '+%H:%M:%S')] Reset ${_FORGE_CONVERSATION_ID}\033[0m" + if [[ -n "$FORGE_CONVERSATION_ID" ]]; then + echo "\033[36m⏺\033[0m \033[90m[$(date '+%H:%M:%S')] Reset ${FORGE_CONVERSATION_ID}\033[0m" fi - _FORGE_CONVERSATION_ID="" + FORGE_CONVERSATION_ID="" _FORGE_USER_ACTION="" BUFFER="" CURSOR=${#BUFFER} @@ -137,12 +138,15 @@ function forge-accept-line() { _FORGE_USER_ACTION="$user_action" # Generate conversation ID if needed (in parent shell context) - if [[ -z "$_FORGE_CONVERSATION_ID" ]]; then - _FORGE_CONVERSATION_ID=$($_FORGE_BIN --generate-conversation-id) + if [[ -z "$FORGE_CONVERSATION_ID" ]]; then + FORGE_CONVERSATION_ID=$($_FORGE_BIN --generate-conversation-id) fi + # Set the active agent for this execution + FORGE_ACTIVE_AGENT="${user_action:-forge}" + # Build and execute the forge command - local forge_cmd="$_FORGE_BIN --resume $_FORGE_CONVERSATION_ID --agent ${user_action:-forge}" + local forge_cmd="$_FORGE_BIN" local full_command="$forge_cmd -p \"$input_text\"" # Use push-line to clear the current line before execution From 42fcf0d1d9f821fee85855c98c4588a90107727d Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Mon, 29 Sep 2025 11:02:39 +0530 Subject: [PATCH 04/10] feat: add environment variable support for conversation ID and active agent --- crates/forge_main/src/cli.rs | 24 +-------- crates/forge_main/src/env.rs | 96 ++++++++++++++++++++++++++++++++++++ crates/forge_main/src/lib.rs | 1 + crates/forge_main/src/ui.rs | 9 ++-- 4 files changed, 103 insertions(+), 27 deletions(-) create mode 100644 crates/forge_main/src/env.rs diff --git a/crates/forge_main/src/cli.rs b/crates/forge_main/src/cli.rs index 45af4cd6c4..a2f7b37160 100644 --- a/crates/forge_main/src/cli.rs +++ b/crates/forge_main/src/cli.rs @@ -1,7 +1,6 @@ use std::path::PathBuf; use clap::{Parser, Subcommand, ValueEnum}; -use forge_domain::ConversationId; #[derive(Parser)] #[command(version = env!("CARGO_PKG_VERSION"))] @@ -60,19 +59,12 @@ pub struct Cli { /// Generate a new conversation ID and exit. /// /// When enabled, generates a new unique conversation ID and prints it to - /// stdout. This ID can be used with --resume --conversation-id to - /// manage multiple terminal sessions with separate conversation + /// stdout. This ID can be used with the FORGE_CONVERSATION_ID environment + /// variable to manage multiple terminal sessions with separate conversation /// contexts. #[arg(long, default_value_t = false)] pub generate_conversation_id: bool, - /// Resume an existing conversation by providing its conversation ID. - /// - /// The conversation ID must be a valid UUID format. You can generate a new - /// conversation ID using --generate-conversation-id. - #[arg(long, value_parser = parse_conversation_id)] - pub resume: Option, - /// Top-level subcommands #[command(subcommand)] pub subcommands: Option, @@ -89,18 +81,6 @@ pub struct Cli { /// The worktree name will be used as the branch name. #[arg(long)] pub sandbox: Option, - /// Specify an agent to execute tasks with. - /// - /// Allows selecting a specific agent to handle the conversation and task - /// execution. Agents provide specialized capabilities and knowledge - /// domains. Example: --agent sage, --agent planner, --agent - /// "code-reviewer" - #[arg(long, short = 'a')] - pub agent: Option, -} - -fn parse_conversation_id(s: &str) -> std::result::Result { - ConversationId::parse(s).map_err(|e| e.to_string()) } impl Cli { diff --git a/crates/forge_main/src/env.rs b/crates/forge_main/src/env.rs new file mode 100644 index 0000000000..bbda7b55c4 --- /dev/null +++ b/crates/forge_main/src/env.rs @@ -0,0 +1,96 @@ +use forge_api::ConversationId; + +// Environment variable names +pub const FORGE_CONVERSATION_ID: &str = "FORGE_CONVERSATION_ID"; +pub const FORGE_ACTIVE_AGENT: &str = "FORGE_ACTIVE_AGENT"; + +/// Get conversation ID from FORGE_CONVERSATION_ID environment variable +pub fn get_conversation_id_from_env() -> Option { + std::env::var(FORGE_CONVERSATION_ID) + .ok() + .and_then(|env_id| forge_domain::ConversationId::parse(&env_id).ok()) +} + +/// Get agent ID from FORGE_ACTIVE_AGENT environment variable +pub fn get_agent_from_env() -> Option { + std::env::var(FORGE_ACTIVE_AGENT).ok() +} + +#[cfg(test)] +mod tests { + use pretty_assertions::assert_eq; + + use super::*; + + #[test] + fn test_get_conversation_id_from_env_with_valid_id() { + let fixture_env_value = "01234567-89ab-cdef-0123-456789abcdef"; + unsafe { + std::env::set_var(FORGE_CONVERSATION_ID, fixture_env_value); + } + + let actual = get_conversation_id_from_env(); + let expected = forge_domain::ConversationId::parse(fixture_env_value).ok(); + + assert_eq!(actual, expected); + unsafe { + std::env::remove_var(FORGE_CONVERSATION_ID); + } + } + + #[test] + fn test_get_conversation_id_from_env_with_invalid_id() { + let fixture_env_value = "invalid-uuid"; + unsafe { + std::env::set_var(FORGE_CONVERSATION_ID, fixture_env_value); + } + + let actual = get_conversation_id_from_env(); + let expected = None; + + assert_eq!(actual, expected); + unsafe { + std::env::remove_var(FORGE_CONVERSATION_ID); + } + } + + #[test] + fn test_get_conversation_id_from_env_not_set() { + unsafe { + std::env::remove_var(FORGE_CONVERSATION_ID); + } + + let actual = get_conversation_id_from_env(); + let expected = None; + + assert_eq!(actual, expected); + } + + #[test] + fn test_get_agent_from_env_with_value() { + let fixture_env_value = "sage"; + unsafe { + std::env::set_var(FORGE_ACTIVE_AGENT, fixture_env_value); + } + + let actual = get_agent_from_env(); + let expected = Some("sage".to_string()); + + assert_eq!(actual, expected); + unsafe { + std::env::remove_var(FORGE_ACTIVE_AGENT); + } + } + + #[test] + fn test_get_agent_from_env_not_set() { + unsafe { + std::env::remove_var(FORGE_ACTIVE_AGENT); + } + + let actual = get_agent_from_env(); + let expected = None; + + assert_eq!(actual, expected); + } +} diff --git a/crates/forge_main/src/lib.rs b/crates/forge_main/src/lib.rs index 802f56405c..c907685481 100644 --- a/crates/forge_main/src/lib.rs +++ b/crates/forge_main/src/lib.rs @@ -3,6 +3,7 @@ mod cli; mod completer; mod conversation_selector; mod editor; +mod env; mod info; mod input; mod model; diff --git a/crates/forge_main/src/ui.rs b/crates/forge_main/src/ui.rs index f64d45734f..cbb59af60a 100644 --- a/crates/forge_main/src/ui.rs +++ b/crates/forge_main/src/ui.rs @@ -22,6 +22,7 @@ use tokio_stream::StreamExt; use crate::cli::{Cli, McpCommand, TopLevelCommand, Transport}; use crate::conversation_selector::ConversationSelector; +use crate::env::{get_agent_from_env, get_conversation_id_from_env}; use crate::info::{Info, get_usage}; use crate::input::Console; use crate::model::{Command, ForgeCommandManager}; @@ -100,7 +101,6 @@ impl A> UI { // Reset previously set CLI parameters by the user self.cli.conversation = None; - self.cli.resume = None; self.display_banner()?; self.trace_user(); @@ -741,8 +741,7 @@ impl A> UI { serde_json::from_str(ForgeFS::read_utf8(path.as_os_str()).await?.as_str()) .context("Failed to parse Conversation")?; conversation - } else if let Some(conversation_id) = self.cli.resume { - // Use the explicitly provided conversation ID + } else if let Some(conversation_id) = get_conversation_id_from_env() { // Check if conversation with this ID already exists if let Some(conversation) = self.api.conversation(&conversation_id).await? { conversation @@ -765,7 +764,7 @@ impl A> UI { self.spinner.stop(None)?; let mut sub_title = conversation.id.into_string(); - if let Some(ref agent) = self.cli.agent { + if let Some(ref agent) = get_agent_from_env() { sub_title.push_str(format!(" [via {agent}]").as_str()); } @@ -782,7 +781,7 @@ impl A> UI { } }; - if let Some(ref agent) = self.cli.agent { + if let Some(ref agent) = get_agent_from_env() { self.state.operating_agent = AgentId::new(agent) } From 5a1fee011ef17f3f8216d4bafb22e8b9485e051f Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Mon, 29 Sep 2025 11:02:48 +0530 Subject: [PATCH 05/10] fix: improve command execution by properly quoting input text --- shell-plugin/forge.plugin.zsh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shell-plugin/forge.plugin.zsh b/shell-plugin/forge.plugin.zsh index 0689a5efc9..9b96e93fcb 100644 --- a/shell-plugin/forge.plugin.zsh +++ b/shell-plugin/forge.plugin.zsh @@ -147,14 +147,14 @@ function forge-accept-line() { # Build and execute the forge command local forge_cmd="$_FORGE_BIN" - local full_command="$forge_cmd -p \"$input_text\"" + local full_command="$forge_cmd -p $(printf %q "$input_text")" # Use push-line to clear the current line before execution zle push-line # Set buffer to the transformed command and execute BUFFER="$full_command" - zle accept-line + zle accept-line return } From aca85bfd8627c754e87e924e47afa9edce50ea33 Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Mon, 29 Sep 2025 13:15:44 +0530 Subject: [PATCH 06/10] feat: add README documentation and enhance plugin description --- shell-plugin/README.md | 174 ++++++++++++++++++++++++++++++++++ shell-plugin/forge.plugin.zsh | 8 +- 2 files changed, 177 insertions(+), 5 deletions(-) create mode 100644 shell-plugin/README.md diff --git a/shell-plugin/README.md b/shell-plugin/README.md new file mode 100644 index 0000000000..bc3f483aab --- /dev/null +++ b/shell-plugin/README.md @@ -0,0 +1,174 @@ +# Forge ZSH Plugin + +A powerful ZSH plugin that provides intelligent command transformation, file tagging, and conversation management for the Forge AI assistant. + +## Features + +- **Smart Command Transformation**: Convert `:command` syntax into forge executions +- **Agent Selection**: Tab completion for available agents using `:agent_name` +- **File Tagging**: Interactive file selection with `@[filename]` syntax +- **Syntax Highlighting**: Visual feedback for commands and tagged files +- **Conversation Continuity**: Automatic session management across commands +- **Interactive Completion**: Fuzzy finding for files and agents + +## Prerequisites + +Before using this plugin, ensure you have the following tools installed: + +- **fzf** - Command-line fuzzy finder +- **fd** - Fast file finder (alternative to find) +- **forge** - The Forge CLI tool + +### Installation of Prerequisites + +```bash +# macOS (using Homebrew) +brew install fzf fd + +# Ubuntu/Debian +sudo apt install fzf fd-find + +# Arch Linux +sudo pacman -S fzf fd +``` + +## Usage + +### Starting a Conversation + +Begin any command with `:` followed by your prompt: + +```bash +: Get the current time +``` + +This automatically starts a new conversation with the default Forge agent. + +### Using Specific Agents + +Specify an agent by name after the colon: + +```bash +:sage How does caching work in this system? +:muse Create a deployment strategy for my app +``` + +**Tab Completion**: Type `:` followed by partial agent name and press `TAB` for interactive selection. + +### File Tagging + +Tag files in your commands using the `@[filename]` syntax: + +```bash +: Review this code @[src/main.rs] +: Explain the configuration in @[config.yaml] +``` + +**Interactive Selection**: Type `@` and press `TAB` to search and select files interactively using fuzzy finder. + +### Conversation Continuity + +Commands within the same session maintain context: + +```bash +# First command +: My project uses React and TypeScript + +# Second command (remembers previous context) +: How can I optimize the build process? +``` + +The plugin automatically manages conversation IDs to maintain context across related commands. + +### Session Management + +#### Resetting Sessions + +Clear the current conversation context: + +```bash +:reset +``` + +This will: + +- Clear the current conversation ID +- Reset the session state +- Display a confirmation message with timestamp + +#### Session Status + +The plugin automatically displays session information including: + +- Conversation ID when starting new sessions +- Active agent information +- Reset confirmations with timestamps + +## Syntax Highlighting + +The plugin provides visual feedback through syntax highlighting: + +- **Tagged Files** (`@[filename]`): Displayed in **green bold** +- **Agent Commands** (`:agent`): Agent names in **yellow bold** +- **Command Text**: Remaining text in **white bold** + +## Configuration + +Customize the plugin behavior by setting these variables before loading the plugin: + +```bash +# Custom forge binary location +export FORGE_BIN="/path/to/custom/forge" + +# Custom reset command (default: "reset") +export FORGE_RESET_COMMAND="clear" +``` + +### Available Configuration Variables + +- `FORGE_BIN`: Path to the forge executable (default: `forge`) +- Internal pattern matching for conversation syntax (`:`) +- Reset command keyword (default: `reset`) + +## Advanced Features + +### Command History + +All transformed commands are properly saved to ZSH history, allowing you to: + +- Navigate command history with arrow keys +- Search previous forge commands with `Ctrl+R` +- Reuse complex commands with file tags + +### Keyboard Shortcuts + +- **Tab**: Interactive completion for files (`@`) and agents (`:`) +- **Enter**: Transform and execute `:commands` +- **Ctrl+C**: Interrupt running forge commands + +## Examples + +### Basic Usage + +```bash +: What's the weather like? +:sage Explain the MVC pattern +:planner Help me structure this project +``` + +### With File Tagging + +```bash +: Review this implementation @[src/auth.rs] +: Debug the issue in @[logs/error.log] @[config/app.yml] +``` + +### Session Flow + +```bash +: I'm working on a Rust web API +: What are the best practices for error handling? +: Show me an example with @[src/errors.rs] +:reset +: New conversation starts here +``` diff --git a/shell-plugin/forge.plugin.zsh b/shell-plugin/forge.plugin.zsh index 9b96e93fcb..28d53419d4 100644 --- a/shell-plugin/forge.plugin.zsh +++ b/shell-plugin/forge.plugin.zsh @@ -1,9 +1,7 @@ #!/usr/bin/env zsh -# Forge ZSH Plugin - ZLE Widget Version -# Converts command-tagged commands to resume conversations using ZLE widgets -# Supports :plan/:p (muse), :ask/:a (sage), :new (start new conversation), :command_name (custom command), : (forge default) -# Features: Auto-resume existing conversations or start new ones, @ tab completion support, banner display for new conversations +# Documentation in [README.md](./README.md) + # Configuration: Change these variables to customize the forge command and special characters # Using typeset to keep variables local to plugin scope and prevent public exposure @@ -154,7 +152,7 @@ function forge-accept-line() { # Set buffer to the transformed command and execute BUFFER="$full_command" - zle accept-line + zle accept-line return } From 606f68a89d9c0338ca5ee0eeff0ab86ab23febbd Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Mon, 29 Sep 2025 15:35:08 +0530 Subject: [PATCH 07/10] fix: remove unnecessary line clearing before command execution --- shell-plugin/forge.plugin.zsh | 3 --- 1 file changed, 3 deletions(-) diff --git a/shell-plugin/forge.plugin.zsh b/shell-plugin/forge.plugin.zsh index 28d53419d4..2cffea8eb0 100644 --- a/shell-plugin/forge.plugin.zsh +++ b/shell-plugin/forge.plugin.zsh @@ -147,9 +147,6 @@ function forge-accept-line() { local forge_cmd="$_FORGE_BIN" local full_command="$forge_cmd -p $(printf %q "$input_text")" - # Use push-line to clear the current line before execution - zle push-line - # Set buffer to the transformed command and execute BUFFER="$full_command" zle accept-line From f880a6d81c845955f667deac73fab59145b6e139 Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Mon, 29 Sep 2025 16:07:48 +0530 Subject: [PATCH 08/10] fix: properly quote input text in command execution --- shell-plugin/forge.plugin.zsh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shell-plugin/forge.plugin.zsh b/shell-plugin/forge.plugin.zsh index 2cffea8eb0..3f725dacf6 100644 --- a/shell-plugin/forge.plugin.zsh +++ b/shell-plugin/forge.plugin.zsh @@ -145,7 +145,8 @@ function forge-accept-line() { # Build and execute the forge command local forge_cmd="$_FORGE_BIN" - local full_command="$forge_cmd -p $(printf %q "$input_text")" + local quoted_input=${input_text//\'/\'\\\'\'} + local full_command="$forge_cmd -p '$quoted_input'" # Set buffer to the transformed command and execute BUFFER="$full_command" From e395aee6158f0e6c192c9af572e714739b4a7d7a Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Mon, 29 Sep 2025 16:18:07 +0530 Subject: [PATCH 09/10] fix: enhance fzf selection with cycle option and simplify reset command handling --- shell-plugin/forge.plugin.zsh | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/shell-plugin/forge.plugin.zsh b/shell-plugin/forge.plugin.zsh index 3f725dacf6..38cc9b1f19 100644 --- a/shell-plugin/forge.plugin.zsh +++ b/shell-plugin/forge.plugin.zsh @@ -7,7 +7,6 @@ # Using typeset to keep variables local to plugin scope and prevent public exposure typeset -h _FORGE_BIN="${FORGE_BIN:-forge}" typeset -h _FORGE_CONVERSATION_PATTERN=":" -typeset -h _FORGE_RESET_COMMAND="reset" # Style tagged files to be in green ZSH_HIGHLIGHT_PATTERNS+=('@\[[^]]#\]' 'fg=green,bold') @@ -40,9 +39,9 @@ function forge-completion() { local filter_text="${current_word#@}" local selected if [[ -n "$filter_text" ]]; then - selected=$(fd --type f --hidden --exclude .git | fzf --select-1 --query "$filter_text" --height 40% --reverse) + selected=$(fd --type f --hidden --exclude .git | fzf --cycle --select-1 --query "$filter_text" --height 40% --reverse) else - selected=$(fd --type f --hidden --exclude .git | fzf --select-1 --height 40% --reverse) + selected=$(fd --type f --hidden --exclude .git | fzf --cycle --select-1 --height 40% --reverse) fi if [[ -n "$selected" ]]; then @@ -66,12 +65,12 @@ function forge-completion() { command_output=$($_FORGE_BIN show-agents 2>/dev/null) if [[ $? -eq 0 && -n "$command_output" ]]; then - # Use fzf for interactive selection with prefilled filter + # Use fzf --cycle for interactive selection with prefilled filter local selected if [[ -n "$filter_text" ]]; then - selected=$(echo "$command_output" | fzf --select-1 --nth=1 --query "$filter_text" --height 40% --reverse --prompt="Agent ❯ ") + selected=$(echo "$command_output" | fzf --cycle --select-1 --nth=1 --query "$filter_text" --height 40% --reverse --prompt="Agent ❯ ") else - selected=$(echo "$command_output" | fzf --select-1 --nth=1 --height 40% --reverse --prompt="Agent ❯ ") + selected=$(echo "$command_output" | fzf --cycle --select-1 --nth=1 --height 40% --reverse --prompt="Agent ❯ ") fi if [[ -n "$selected" ]]; then @@ -118,7 +117,7 @@ function forge-accept-line() { print -s -- "$original_buffer" # Handle reset command specially - if [[ "$user_action" == "$_FORGE_RESET_COMMAND" ]]; then + if [[ "$user_action" == "reset" || "$user_action" == "r" ]]; then echo if [[ -n "$FORGE_CONVERSATION_ID" ]]; then echo "\033[36m⏺\033[0m \033[90m[$(date '+%H:%M:%S')] Reset ${FORGE_CONVERSATION_ID}\033[0m" From 0d798d1fe1609789a9368795b69838b374ef12ec Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Mon, 29 Sep 2025 16:32:45 +0530 Subject: [PATCH 10/10] fix: update fzf selection to use a private function for consistency and change tagged file color to cyan --- shell-plugin/forge.plugin.zsh | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/shell-plugin/forge.plugin.zsh b/shell-plugin/forge.plugin.zsh index 38cc9b1f19..c03cf4cd3b 100644 --- a/shell-plugin/forge.plugin.zsh +++ b/shell-plugin/forge.plugin.zsh @@ -8,8 +8,8 @@ typeset -h _FORGE_BIN="${FORGE_BIN:-forge}" typeset -h _FORGE_CONVERSATION_PATTERN=":" -# Style tagged files to be in green -ZSH_HIGHLIGHT_PATTERNS+=('@\[[^]]#\]' 'fg=green,bold') +# Style tagged files +ZSH_HIGHLIGHT_PATTERNS+=('@\[[^]]#\]' 'fg=cyan,bold') ZSH_HIGHLIGHT_HIGHLIGHTERS+=(pattern) # Style the conversation pattern with appropriate highlighting @@ -21,6 +21,11 @@ ZSH_HIGHLIGHT_PATTERNS+=('(#s):[a-zA-Z]#' 'fg=yellow,bold') # Highlight everything after that word + space in white ZSH_HIGHLIGHT_PATTERNS+=('(#s):[a-zA-Z]# *(*|[[:graph:]]*)' 'fg=white,bold') +# Private fzf function with common options for consistent UX +function _forge_fzf() { + fzf --cycle --select-1 --height 40% --reverse "$@" +} + # Store conversation ID in a temporary variable (local to plugin) @@ -39,9 +44,9 @@ function forge-completion() { local filter_text="${current_word#@}" local selected if [[ -n "$filter_text" ]]; then - selected=$(fd --type f --hidden --exclude .git | fzf --cycle --select-1 --query "$filter_text" --height 40% --reverse) + selected=$(fd --type f --hidden --exclude .git | _forge_fzf --query "$filter_text") else - selected=$(fd --type f --hidden --exclude .git | fzf --cycle --select-1 --height 40% --reverse) + selected=$(fd --type f --hidden --exclude .git | _forge_fzf) fi if [[ -n "$selected" ]]; then @@ -65,12 +70,12 @@ function forge-completion() { command_output=$($_FORGE_BIN show-agents 2>/dev/null) if [[ $? -eq 0 && -n "$command_output" ]]; then - # Use fzf --cycle for interactive selection with prefilled filter + # Use fzf for interactive selection with prefilled filter local selected if [[ -n "$filter_text" ]]; then - selected=$(echo "$command_output" | fzf --cycle --select-1 --nth=1 --query "$filter_text" --height 40% --reverse --prompt="Agent ❯ ") + selected=$(echo "$command_output" | _forge_fzf --nth=1 --query "$filter_text" --prompt="Agent ❯ ") else - selected=$(echo "$command_output" | fzf --cycle --select-1 --nth=1 --height 40% --reverse --prompt="Agent ❯ ") + selected=$(echo "$command_output" | _forge_fzf --nth=1 --prompt="Agent ❯ ") fi if [[ -n "$selected" ]]; then @@ -140,7 +145,7 @@ function forge-accept-line() { fi # Set the active agent for this execution - FORGE_ACTIVE_AGENT="${user_action:-forge}" + FORGE_ACTIVE_AGENT="${user_action:-${FORGE_ACTIVE_AGENT:-forge}}" # Build and execute the forge command local forge_cmd="$_FORGE_BIN"