Skip to content

ccproxy.utils.binary_resolver

ccproxy.utils.binary_resolver

Binary resolution with package manager fallback support.

BinaryCommand

Bases: NamedTuple

Represents a resolved binary command.

PackageManagerConfig

Bases: TypedDict

Configuration for a package manager.

CLIInfo

Bases: TypedDict

Common structure for CLI information.

BinaryResolver

BinaryResolver(
    fallback_enabled=True,
    package_manager_only=False,
    preferred_package_manager=None,
    package_manager_priority=None,
)

Resolves binaries with fallback to package managers.

Parameters:

Name Type Description Default
fallback_enabled bool

Whether to use package manager fallback

True
package_manager_only bool

Skip direct binary lookup and use package managers exclusively

False
preferred_package_manager str | None

Preferred package manager (bunx, pnpm, npx)

None
package_manager_priority list[str] | None

Custom priority order for package managers

None
Source code in ccproxy/utils/binary_resolver.py
def __init__(
    self,
    fallback_enabled: bool = True,
    package_manager_only: bool = False,
    preferred_package_manager: str | None = None,
    package_manager_priority: list[str] | None = None,
):
    """Initialize the binary resolver.

    Args:
        fallback_enabled: Whether to use package manager fallback
        package_manager_only: Skip direct binary lookup and use package managers exclusively
        preferred_package_manager: Preferred package manager (bunx, pnpm, npx)
        package_manager_priority: Custom priority order for package managers
    """
    self.fallback_enabled = fallback_enabled
    self.package_manager_only = package_manager_only
    self.preferred_package_manager = preferred_package_manager
    self.package_manager_priority = package_manager_priority or [
        "bunx",
        "pnpm",
        "npx",
    ]
    self._available_managers: dict[str, bool] | None = None

find_binary

find_binary(
    binary_name,
    package_name=None,
    package_manager_only=None,
    fallback_enabled=None,
)

Find a binary with optional package manager fallback.

Parameters:

Name Type Description Default
binary_name str

Name of the binary to find. Can be: - Simple binary name (e.g., "claude") - Full package name (e.g., "anthropic-ai/claude-code")

required
package_name str | None

NPM package name if different from binary name

None

Returns:

Type Description
BinaryCommand | None

BinaryCommand with resolved command or None if not found

Source code in ccproxy/utils/binary_resolver.py
@ttl_cache(maxsize=32, ttl=300.0)
def find_binary(
    self,
    binary_name: str,
    package_name: str | None = None,
    package_manager_only: bool | None = None,
    fallback_enabled: bool | None = None,
) -> BinaryCommand | None:
    """Find a binary with optional package manager fallback.

    Args:
        binary_name: Name of the binary to find. Can be:
            - Simple binary name (e.g., "claude")
            - Full package name (e.g., "@anthropic-ai/claude-code")
        package_name: NPM package name if different from binary name

    Returns:
        BinaryCommand with resolved command or None if not found
    """
    if package_manager_only is None:
        package_manager_only = self.package_manager_only
    if fallback_enabled is None:
        fallback_enabled = self.fallback_enabled

    # Determine if binary_name is a full package name (contains @ or /)
    is_full_package = "@" in binary_name or "/" in binary_name

    if is_full_package and package_name is None:
        # If binary_name is a full package name, use it as the package
        # and extract the binary name from it
        package_name = binary_name
        # Extract binary name from package (last part after /)
        binary_name = binary_name.split("/")[-1]

    # If package_manager_only mode, skip direct binary lookup
    if package_manager_only:
        package_name = package_name or self.KNOWN_PACKAGES.get(
            binary_name, binary_name
        )
        result = self._find_via_package_manager(binary_name, package_name)
        if result:
            logger.trace(
                "binary_resolved",
                binary=binary_name,
                manager=result.package_manager,
                command=result.command,
                source="package_manager",
            )
        else:
            logger.trace(
                "binary_resolution_failed",
                binary=binary_name,
                source="package_manager",
            )
        return result

    # First, try direct binary lookup in PATH
    direct_path = shutil.which(binary_name)
    if direct_path:
        return BinaryCommand(command=[direct_path], is_direct=True, is_in_path=True)

    # Check common installation locations
    common_paths = self._get_common_paths(binary_name)
    for path in common_paths:
        if path.exists() and path.is_file():
            logger.debug(
                "binary_found_in_common_path", binary=binary_name, path=str(path)
            )
            return BinaryCommand(
                command=[str(path)], is_direct=True, is_in_path=False
            )

    # If fallback is disabled, stop here
    if not fallback_enabled:
        logger.debug("binary_fallback_disabled", binary=binary_name)
        return None

    # Try package manager fallback
    package_name = package_name or self.KNOWN_PACKAGES.get(binary_name, binary_name)
    return self._find_via_package_manager(binary_name, package_name)

get_available_package_managers

get_available_package_managers()

Get list of available package managers on the system.

Returns:

Type Description
list[str]

List of package manager names that are available (e.g., ['bunx', 'pnpm'])

Source code in ccproxy/utils/binary_resolver.py
def get_available_package_managers(self) -> list[str]:
    """Get list of available package managers on the system.

    Returns:
        List of package manager names that are available (e.g., ['bunx', 'pnpm'])
    """
    available = self._get_available_managers()
    return [name for name, is_available in available.items() if is_available]

get_package_manager_info

get_package_manager_info()

Get detailed information about package managers.

Returns:

Type Description
dict[str, dict[str, str | bool | int]]

Dictionary with package manager info including availability and priority

Source code in ccproxy/utils/binary_resolver.py
def get_package_manager_info(self) -> dict[str, dict[str, str | bool | int]]:
    """Get detailed information about package managers.

    Returns:
        Dictionary with package manager info including availability and priority
    """
    available = self._get_available_managers()
    info: dict[str, dict[str, str | bool | int]] = {}

    for name, config in self.PACKAGE_MANAGERS.items():
        exec_cmd = config.get("exec_cmd", name)
        info[name] = {
            "available": bool(available.get(name, False)),
            "priority": int(config["priority"]),
            "check_command": str(" ".join(config["check_cmd"])),
            "exec_command": str(exec_cmd if exec_cmd is not None else name),
        }

    return info

get_cli_info

get_cli_info(binary_name, package_name=None, version=None)

Get comprehensive CLI information in common format.

Parameters:

Name Type Description Default
binary_name str

Name of the binary to find

required
package_name str | None

NPM package name if different from binary name

None
version str | None

Optional version string (if known)

None

Returns:

Type Description
CLIInfo

CLIInfo dictionary with structured information

Source code in ccproxy/utils/binary_resolver.py
def get_cli_info(
    self,
    binary_name: str,
    package_name: str | None = None,
    version: str | None = None,
) -> CLIInfo:
    """Get comprehensive CLI information in common format.

    Args:
        binary_name: Name of the binary to find
        package_name: NPM package name if different from binary name
        version: Optional version string (if known)

    Returns:
        CLIInfo dictionary with structured information
    """
    result = self.find_binary(binary_name, package_name)

    if not result:
        return CLIInfo(
            name=binary_name,
            version=version,
            source="unknown",
            path=None,
            command=[],
            package_manager=None,
            is_available=False,
        )

    # Determine source and path
    if result.is_direct:
        source = "path"
        path = result.command[0] if result.command else None
    else:
        source = "package_manager"
        path = None

    return CLIInfo(
        name=binary_name,
        version=version,
        source=source,
        path=path,
        command=result.command,
        package_manager=result.package_manager,
        is_available=True,
    )

clear_cache

clear_cache()

Clear all caches.

Source code in ccproxy/utils/binary_resolver.py
def clear_cache(self) -> None:
    """Clear all caches."""
    # Reset the available managers cache
    self._available_managers = None

from_settings classmethod

from_settings(settings)

Create a BinaryResolver from application settings.

Parameters:

Name Type Description Default
settings Settings

Application settings

required

Returns:

Type Description
BinaryResolver

Configured BinaryResolver instance

Source code in ccproxy/utils/binary_resolver.py
@classmethod
def from_settings(cls, settings: "Settings") -> "BinaryResolver":
    """Create a BinaryResolver from application settings.

    Args:
        settings: Application settings

    Returns:
        Configured BinaryResolver instance
    """
    return cls(
        fallback_enabled=settings.binary.fallback_enabled,
        package_manager_only=settings.binary.package_manager_only,
        preferred_package_manager=settings.binary.preferred_package_manager,
        package_manager_priority=settings.binary.package_manager_priority,
    )

find_binary_with_fallback

find_binary_with_fallback(
    binary_name, package_name=None, fallback_enabled=True
)

Convenience function to find a binary with package manager fallback.

Parameters:

Name Type Description Default
binary_name str

Name of the binary to find. Can be: - Simple binary name (e.g., "claude") - Full package name (e.g., "anthropic-ai/claude-code")

required
package_name str | None

NPM package name if different from binary name

None
fallback_enabled bool

Whether to use package manager fallback

True

Returns:

Type Description
list[str] | None

Command list to execute the binary, or None if not found

Source code in ccproxy/utils/binary_resolver.py
def find_binary_with_fallback(
    binary_name: str,
    package_name: str | None = None,
    fallback_enabled: bool = True,
) -> list[str] | None:
    """Convenience function to find a binary with package manager fallback.

    Args:
        binary_name: Name of the binary to find. Can be:
            - Simple binary name (e.g., "claude")
            - Full package name (e.g., "@anthropic-ai/claude-code")
        package_name: NPM package name if different from binary name
        fallback_enabled: Whether to use package manager fallback

    Returns:
        Command list to execute the binary, or None if not found
    """
    resolver = BinaryResolver(fallback_enabled=fallback_enabled)
    result = resolver.find_binary(binary_name, package_name)
    return result.command if result else None

is_package_manager_command

is_package_manager_command(command)

Check if a command uses a package manager.

Parameters:

Name Type Description Default
command list[str]

Command list to check

required

Returns:

Type Description
bool

True if command uses a package manager

Source code in ccproxy/utils/binary_resolver.py
def is_package_manager_command(command: list[str]) -> bool:
    """Check if a command uses a package manager.

    Args:
        command: Command list to check

    Returns:
        True if command uses a package manager
    """
    if not command:
        return False
    first_cmd = Path(command[0]).name
    return first_cmd in ["npx", "bunx", "pnpm"]

get_available_package_managers

get_available_package_managers()

Convenience function to get available package managers using default resolver.

Returns:

Type Description
list[str]

List of package manager names that are available

Source code in ccproxy/utils/binary_resolver.py
def get_available_package_managers() -> list[str]:
    """Convenience function to get available package managers using default resolver.

    Returns:
        List of package manager names that are available
    """
    return _default_resolver.get_available_package_managers()

get_package_manager_info

get_package_manager_info()

Convenience function to get package manager info using default resolver.

Returns:

Type Description
dict[str, dict[str, str | bool | int]]

Dictionary with package manager info including availability and priority

Source code in ccproxy/utils/binary_resolver.py
def get_package_manager_info() -> dict[str, dict[str, str | bool | int]]:
    """Convenience function to get package manager info using default resolver.

    Returns:
        Dictionary with package manager info including availability and priority
    """
    return _default_resolver.get_package_manager_info()