forgecode / shell-plugin /lib /helpers.zsh
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
c981f27 verified
Raw
History Blame Contribute Delete
9.18 kB
#!/usr/bin/env zsh
# Core utility functions for forge plugin
# Lazy loader for commands cache
# Loads the commands list only when first needed, avoiding startup cost
function _forge_get_commands() {
if [[ -z "$_FORGE_COMMANDS" ]]; then
_FORGE_COMMANDS="$(CLICOLOR_FORCE=0 $_FORGE_BIN list commands --porcelain 2>/dev/null)"
fi
echo "$_FORGE_COMMANDS"
}
# Helper function to execute forge commands consistently
# This ensures proper handling of special characters and consistent output
function _forge_exec() {
local agent_id="${_FORGE_ACTIVE_AGENT:-forge}"
local -a cmd
cmd=($_FORGE_BIN --agent "$agent_id")
# Expose terminal context arrays as US-separated (\x1F) env vars so that
# the Rust TerminalContextService can read them via get_env_var.
# ASCII Unit Separator (\x1F) is used instead of `:` because commands
# can legitimately contain colons (URLs, port mappings, paths, etc.).
# Use `local -x` so the variables are exported only to the child forge
# process and do not leak into the caller's shell environment.
if [[ "$_FORGE_TERM" == "true" && ${#_FORGE_TERM_COMMANDS} -gt 0 ]]; then
# Join the ring-buffer arrays with the ASCII Unit Separator (\x1F).
# We use IFS-based joining ("${arr[*]}") rather than ${(j.SEP.)arr} because
# zsh does NOT expand $'...' ANSI-C escapes inside parameter expansion flags.
local _old_ifs="$IFS" _sep=$'\x1f'
IFS="$_sep"
local -x _FORGE_TERM_COMMANDS="${_FORGE_TERM_COMMANDS[*]}"
local -x _FORGE_TERM_EXIT_CODES="${_FORGE_TERM_EXIT_CODES[*]}"
local -x _FORGE_TERM_TIMESTAMPS="${_FORGE_TERM_TIMESTAMPS[*]}"
IFS="$_old_ifs"
fi
cmd+=("$@")
[[ -n "$_FORGE_SESSION_MODEL" ]] && local -x FORGE_SESSION__MODEL_ID="$_FORGE_SESSION_MODEL"
[[ -n "$_FORGE_SESSION_PROVIDER" ]] && local -x FORGE_SESSION__PROVIDER_ID="$_FORGE_SESSION_PROVIDER"
[[ -n "$_FORGE_SESSION_REASONING_EFFORT" ]] && local -x FORGE_REASONING__EFFORT="$_FORGE_SESSION_REASONING_EFFORT"
"${cmd[@]}"
}
# Like _forge_exec but connects stdin/stdout to /dev/tty so that interactive
# prompts (rustyline, nucleo-picker, etc.) work correctly when forge is launched as a
# child of a ZLE widget. ZLE owns the terminal and replaces the process's
# stdin/stdout with its own pipes, so without this redirect any readline
# library would see a non-tty stdin and return EOF immediately.
# Do NOT use inside $(...) command substitutions - use _forge_exec instead.
function _forge_exec_interactive() {
local agent_id="${_FORGE_ACTIVE_AGENT:-forge}"
local -a cmd
cmd=($_FORGE_BIN --agent "$agent_id")
# Expose terminal context arrays as US-separated (\x1F) env vars so that
# the Rust TerminalContextService can read them via get_env_var.
# ASCII Unit Separator (\x1F) is used instead of `:` because commands
# can legitimately contain colons (URLs, port mappings, paths, etc.).
# Use `local -x` so the variables are exported only for the duration of
# this function call (i.e. inherited by the child forge process) and do
# not leak into the caller's shell environment.
if [[ "$_FORGE_TERM" == "true" && ${#_FORGE_TERM_COMMANDS} -gt 0 ]]; then
local _old_ifs="$IFS" _sep=$'\x1f'
IFS="$_sep"
local -x _FORGE_TERM_COMMANDS="${_FORGE_TERM_COMMANDS[*]}"
local -x _FORGE_TERM_EXIT_CODES="${_FORGE_TERM_EXIT_CODES[*]}"
local -x _FORGE_TERM_TIMESTAMPS="${_FORGE_TERM_TIMESTAMPS[*]}"
IFS="$_old_ifs"
fi
cmd+=("$@")
[[ -n "$_FORGE_SESSION_MODEL" ]] && local -x FORGE_SESSION__MODEL_ID="$_FORGE_SESSION_MODEL"
[[ -n "$_FORGE_SESSION_PROVIDER" ]] && local -x FORGE_SESSION__PROVIDER_ID="$_FORGE_SESSION_PROVIDER"
[[ -n "$_FORGE_SESSION_REASONING_EFFORT" ]] && local -x FORGE_REASONING__EFFORT="$_FORGE_SESSION_REASONING_EFFORT"
"${cmd[@]}" </dev/tty >/dev/tty
}
function _forge_select() {
[[ -n "$_FORGE_SESSION_MODEL" ]] && local -x FORGE_SESSION__MODEL_ID="$_FORGE_SESSION_MODEL"
[[ -n "$_FORGE_SESSION_PROVIDER" ]] && local -x FORGE_SESSION__PROVIDER_ID="$_FORGE_SESSION_PROVIDER"
[[ -n "$_FORGE_SESSION_REASONING_EFFORT" ]] && local -x FORGE_REASONING__EFFORT="$_FORGE_SESSION_REASONING_EFFORT"
CLICOLOR_FORCE=0 $_FORGE_BIN select "$@" </dev/tty 2>/dev/tty
}
function _forge_select_global() {
CLICOLOR_FORCE=0 $_FORGE_BIN select "$@" </dev/tty 2>/dev/tty
}
function _forge_select_with_query() {
local query="$1"
shift
if [[ -n "$query" ]]; then
_forge_select "$@" --query "$query"
else
_forge_select "$@"
fi
}
function _forge_select_with_query_global() {
local query="$1"
shift
if [[ -n "$query" ]]; then
_forge_select_global "$@" --query "$query"
else
_forge_select_global "$@"
fi
}
function _forge_select_model_pair() {
local result
result=$(_forge_select_with_query "$1" model)
if [[ -z "$result" ]]; then
reply=()
return 1
fi
reply=("${(@f)result}")
[[ ${#reply[@]} -ge 2 ]]
}
function _forge_select_model_pair_global() {
local result
result=$(_forge_select_with_query_global "$1" model)
if [[ -z "$result" ]]; then
reply=()
return 1
fi
reply=("${(@f)result}")
[[ ${#reply[@]} -ge 2 ]]
}
function _forge_reset() {
# Print newlines equal to the current buffer display line count so that
# ZLE's zrefresh() clears these padding lines instead of conversation
# output. When BUFFER spans multiple display lines (newlines or terminal
# wrap), ZLE tracks the multi-line state internally (olnct). On
# reset-prompt, zrefresh() compares olnct (old multi-line count) against
# nlnct (new count after clearing) and clears the delta lines. By
# printing padding here, the cleared lines are blank ones we inserted
# rather than the forge conversation output that precedes them.
local pad="${BUFFERLINES:-1}" _i
for ((_i=1; _i<pad; _i++)); do print; done
# Clear buffer and reset cursor position
BUFFER=""
CURSOR=0
# Force widget redraw and prompt reset
zle -I
zle reset-prompt
}
# Helper function to print messages with consistent formatting based on log level
# Usage: _forge_log <level> <message>
# Levels: error, info, success, warning, debug
# Color scheme matches crates/forge_main/src/title_display.rs
function _forge_log() {
local level="$1"
local message="$2"
local timestamp="\033[90m[$(date '+%H:%M:%S')]\033[0m"
case "$level" in
error)
# Category::Error - Red ⏺
echo "\033[31m⏺\033[0m ${timestamp} \033[31m${message}\033[0m"
;;
info)
# Category::Info - White ⏺
echo "\033[37m⏺\033[0m ${timestamp} \033[37m${message}\033[0m"
;;
success)
# Category::Action/Completion - Yellow ⏺
echo "\033[33m⏺\033[0m ${timestamp} \033[37m${message}\033[0m"
;;
warning)
# Category::Warning - Bright yellow ⚠️
echo "\033[93m⚠️\033[0m ${timestamp} \033[93m${message}\033[0m"
;;
debug)
# Category::Debug - Cyan ⏺ with dimmed text
echo "\033[36m⏺\033[0m ${timestamp} \033[90m${message}\033[0m"
;;
*)
echo "${message}"
;;
esac
}
# Helper function to check if a workspace is indexed
# Usage: _forge_is_workspace_indexed <workspace_path>
# Returns: 0 if workspace is indexed, 1 otherwise
function _forge_is_workspace_indexed() {
local workspace_path="$1"
$_FORGE_BIN workspace info "$workspace_path" >/dev/null 2>&1
return $?
}
# Start background sync job for current workspace if not already running
# Uses canonical path hash to identify workspace
function _forge_start_background_sync() {
# Check if sync is enabled (default to true if not set)
local sync_enabled="${FORGE_SYNC_ENABLED:-true}"
if [[ "$sync_enabled" != "true" ]]; then
return 0
fi
# Get canonical workspace path
local workspace_path=$(pwd -P)
# Check if workspace is indexed before attempting sync
{
# Run sync once in background
# Close all output streams immediately to prevent any flashing
# Redirect stdin to /dev/null to prevent hanging if sync tries to read input
exec >/dev/null 2>&1 </dev/null
setopt NO_NOTIFY NO_MONITOR
if ! _forge_is_workspace_indexed "$workspace_path"; then
return 0
fi
# Should fail if sync-init or sync --init has not been performed even once
$_FORGE_BIN workspace sync "$workspace_path"
} &!
}
# Start background update check if not already running
# Mirrors the background sync pattern to silently check for and apply updates
function _forge_start_background_update() {
{
# Run update check in background
# Close all output streams immediately to prevent any flashing
# Redirect stdin to /dev/null to prevent hanging
exec >/dev/null 2>&1 </dev/null
setopt NO_NOTIFY NO_MONITOR
$_FORGE_BIN update --no-confirm
} &!
}