Ctrl K
ComunidadGuías…

shell-plugin/lib/dispatcher.zsh

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/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
}