forgecode / shell-plugin /lib /dispatcher.zsh
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
c981f27 verified
Raw
History Blame Contribute Delete
9.57 kB
#!/usr/bin/env zsh
# Main command dispatcher and widget registration
# Action handler: Set active agent or execute command
# Flow:
# 1. Check if user_action is a CUSTOM command -> execute with `cmd` subcommand
# 2. If no input_text -> switch to agent (for AGENT type commands)
# 3. If input_text -> execute command with active agent context
function _forge_action_default() {
local user_action="$1"
local input_text="$2"
local command_type=""
# Validate that the command exists in show-commands (if user_action is provided)
if [[ -n "$user_action" ]]; then
local commands_list=$(_forge_get_commands)
if [[ -n "$commands_list" ]]; then
# Check if the user_action is in the list of valid commands and extract the row
local command_row=$(echo "$commands_list" | grep "^${user_action}\b")
if [[ -z "$command_row" ]]; then
echo
_forge_log error "Command '\033[1m${user_action}\033[0m' not found"
return 0
fi
# Extract the command type from the second field (TYPE column)
# Format: "COMMAND_NAME TYPE DESCRIPTION"
command_type=$(echo "$command_row" | awk '{print $2}')
# Case-insensitive comparison using :l (lowercase) modifier
if [[ "${command_type:l}" == "custom" ]]; then
# Generate conversation ID if needed (don't track previous for auto-generation)
if [[ -z "$_FORGE_CONVERSATION_ID" ]]; then
local new_id=$($_FORGE_BIN conversation new)
# Use helper but don't track previous for auto-generation
_FORGE_CONVERSATION_ID="$new_id"
fi
echo
# Execute custom command with execute subcommand
if [[ -n "$input_text" ]]; then
_forge_exec cmd execute --cid "$_FORGE_CONVERSATION_ID" "$user_action" "$input_text"
else
_forge_exec cmd execute --cid "$_FORGE_CONVERSATION_ID" "$user_action"
fi
return 0
fi
fi
fi
# If input_text is empty, just set the active agent (only for AGENT type commands)
if [[ -z "$input_text" ]]; then
if [[ -n "$user_action" ]]; then
if [[ "${command_type:l}" != "agent" ]]; then
echo
_forge_log error "Command '\033[1m${user_action}\033[0m' not found"
return 0
fi
echo
# Set the agent in the local variable
_FORGE_ACTIVE_AGENT="$user_action"
_forge_log info "\033[1;37m${_FORGE_ACTIVE_AGENT:u}\033[0m \033[90mis now the active agent\033[0m"
fi
return 0
fi
# Generate conversation ID if needed (don't track previous for auto-generation)
if [[ -z "$_FORGE_CONVERSATION_ID" ]]; then
local new_id=$($_FORGE_BIN conversation new)
# Use direct assignment here - no previous to track for auto-generation
_FORGE_CONVERSATION_ID="$new_id"
fi
echo
# Only set the agent if user explicitly specified one
if [[ -n "$user_action" ]]; then
_FORGE_ACTIVE_AGENT="$user_action"
fi
# Execute the forge command directly with proper escaping
_forge_exec_interactive -p "$input_text" --cid "$_FORGE_CONVERSATION_ID"
# Start background sync job if enabled and not already running
_forge_start_background_sync
# Start background update check
_forge_start_background_update
}
function forge-accept-line() {
# Save the original command for history
local original_buffer="$BUFFER"
# Parse the buffer first in parent shell context to avoid subshell issues
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]}"
# Only use match[3] if the second group (space + params) was actually matched
if [[ -n "${match[2]}" ]]; then
input_text="${match[3]}"
else
input_text=""
fi
elif [[ "$BUFFER" =~ "^: (.*)$" ]]; then
# Default action with parameters: : something
user_action=""
input_text="${match[1]}"
else
# For non-:commands, use normal accept-line
zle accept-line
return
fi
# Add the original command to history before transformation
print -s -- "$original_buffer"
CURSOR=${#BUFFER}
zle redisplay
# Handle aliases - convert to their actual agent names
case "$user_action" in
ask)
user_action="sage"
;;
plan)
user_action="muse"
;;
esac
# ⚠️ IMPORTANT: When adding a new command here, you MUST also update:
# crates/forge_main/src/built_in_commands.json
# Add a new entry: {"command": "name", "description": "Description [alias: x]"}
#
# Naming convention: shell commands should follow Object-Action (e.g., provider-login).
#
# ZLE-dispatched Forge commands bypass zsh preexec/precmd hooks, so emit
# OSC 133 markers explicitly. Ghostty uses these markers to distinguish the
# prompt from command output during window resize/reflow.
_forge_osc133_emit "B"
_forge_osc133_emit "C"
# Dispatch to appropriate action handler using pattern matching
case "$user_action" in
new|n)
_forge_action_new "$input_text"
;;
info|i)
_forge_action_info
;;
dump|d)
_forge_action_dump "$input_text"
;;
compact)
_forge_action_compact
;;
retry|r)
_forge_action_retry
;;
help)
_forge_action_help
;;
agent|a)
_forge_action_agent "$input_text"
;;
conversation|c)
_forge_action_conversation "$input_text"
;;
conversation-tree|ct)
_forge_action_conversation_tree
;;
config-model|cm)
_forge_action_model "$input_text"
;;
model|m)
_forge_action_session_model "$input_text"
;;
config-reload|cr|model-reset|mr)
_forge_action_config_reload
;;
reasoning-effort|re)
_forge_action_reasoning_effort "$input_text"
;;
config-reasoning-effort|cre)
_forge_action_config_reasoning_effort "$input_text"
;;
config-commit-model|ccm)
_forge_action_commit_model "$input_text"
;;
config-suggest-model|csm)
_forge_action_suggest_model "$input_text"
;;
tools|t)
_forge_action_tools
;;
config|env|e)
_forge_action_config
;;
config-edit|ce)
_forge_action_config_edit
;;
skill)
_forge_action_skill
;;
edit|ed)
_forge_action_editor "$input_text"
local action_status=$?
_forge_osc133_emit "D;$action_status"
_forge_osc133_emit "A"
# Note: editor action intentionally modifies BUFFER and handles its own prompt reset
return $action_status
;;
commit)
_forge_action_commit "$input_text"
;;
commit-preview)
_forge_action_commit_preview "$input_text"
local action_status=$?
_forge_osc133_emit "D;$action_status"
_forge_osc133_emit "A"
# Note: commit action intentionally modifies BUFFER and handles its own prompt reset
return $action_status
;;
suggest|s)
_forge_action_suggest "$input_text"
local action_status=$?
_forge_osc133_emit "D;$action_status"
_forge_osc133_emit "A"
# Note: suggest action intentionally modifies BUFFER and handles its own prompt reset
return $action_status
;;
clone)
_forge_action_clone "$input_text"
;;
rename|rn)
_forge_action_rename "$input_text"
;;
conversation-rename)
_forge_action_conversation_rename "$input_text"
;;
copy)
_forge_action_copy
;;
workspace-sync|sync)
_forge_action_sync
;;
workspace-init|sync-init)
_forge_action_sync_init
;;
workspace-status|sync-status)
_forge_action_sync_status
;;
workspace-info|sync-info)
_forge_action_sync_info
;;
provider-login|login|provider)
_forge_action_login "$input_text"
;;
logout)
_forge_action_logout "$input_text"
;;
*)
_forge_action_default "$user_action" "$input_text"
;;
esac
local action_status=$?
_forge_osc133_emit "D;$action_status"
_forge_osc133_emit "A"
# Centralized reset after all actions complete
# This ensures consistent prompt state without requiring each action to call _forge_reset
# Exceptions: editor, commit-preview, and suggest actions return early as they intentionally modify BUFFER
_forge_reset
return $action_status
}