diff --git a/g4f/Provider/github/GithubCopilot.py b/g4f/Provider/github/GithubCopilot.py index 6e18dd3c..c578f88a 100644 --- a/g4f/Provider/github/GithubCopilot.py +++ b/g4f/Provider/github/GithubCopilot.py @@ -25,7 +25,7 @@ class GithubCopilot(OpenaiTemplate): allowing users to authenticate via browser without sharing credentials. Usage: - 1. Run `g4f-github-copilot login` to authenticate + 1. Run `g4f auth github-copilot` to authenticate 2. Use the provider normally after authentication Example: @@ -270,7 +270,7 @@ class GithubCopilot(OpenaiTemplate): usage = await resp.json() return usage -async def main(): +async def main(args: Optional[List[str]] = None): """CLI entry point for GitHub Copilot OAuth authentication.""" import argparse @@ -296,7 +296,7 @@ Examples: # Logout command subparsers.add_parser("logout", help="Remove saved credentials") - args = parser.parse_args() + args = parser.parse_args(args) if args.command == "login": try: @@ -334,7 +334,7 @@ Examples: print(f" (Could not read credential details: {e})") else: print("✗ No credentials found") - print(f"\nRun 'g4f-github-copilot login' to authenticate.") + print(f"\nRun 'g4f auth github-copilot' to authenticate.") print() @@ -370,9 +370,9 @@ Examples: parser.print_help() -def cli_main(): +def cli_main(args: Optional[List[str]] = None): """Synchronous CLI entry point for setup.py console_scripts.""" - asyncio.run(main()) + asyncio.run(main(args)) if __name__ == "__main__": diff --git a/g4f/Provider/needs_auth/Antigravity.py b/g4f/Provider/needs_auth/Antigravity.py index 5b01916a..e20d7956 100644 --- a/g4f/Provider/needs_auth/Antigravity.py +++ b/g4f/Provider/needs_auth/Antigravity.py @@ -1499,7 +1499,7 @@ class Antigravity(AsyncGeneratorProvider, ProviderModelMixin): return cache_path -async def main(): +async def main(args: Optional[List[str]] = None): """CLI entry point for Antigravity authentication.""" import argparse @@ -1537,7 +1537,7 @@ Examples: # Logout command subparsers.add_parser("logout", help="Remove saved credentials") - args = parser.parse_args() + args = parser.parse_args(args) if args.command == "login": try: @@ -1616,9 +1616,9 @@ Examples: parser.print_help() -def cli_main(): +def cli_main(args: Optional[List[str]] = None): """Synchronous CLI entry point for setup.py console_scripts.""" - asyncio.run(main()) + asyncio.run(main(args)) if __name__ == "__main__": diff --git a/g4f/Provider/needs_auth/GeminiCLI.py b/g4f/Provider/needs_auth/GeminiCLI.py index a29b0fd8..626027fd 100644 --- a/g4f/Provider/needs_auth/GeminiCLI.py +++ b/g4f/Provider/needs_auth/GeminiCLI.py @@ -1171,7 +1171,7 @@ class GeminiCLI(AsyncGeneratorProvider, ProviderModelMixin): return None -async def main(): +async def main(args: Optional[List[str]] = None): """CLI entry point for GeminiCLI authentication.""" import argparse @@ -1203,7 +1203,7 @@ Examples: # Logout command subparsers.add_parser("logout", help="Remove saved credentials") - args = parser.parse_args() + args = parser.parse_args(args) if args.command == "login": try: @@ -1241,7 +1241,7 @@ Examples: print(f" (Could not read credential details: {e})") else: print("✗ No credentials found") - print(f"\nRun 'g4f-geminicli login' to authenticate.") + print(f"\nRun 'g4f auth gemini-cli login' to authenticate.") print() @@ -1274,9 +1274,9 @@ Examples: parser.print_help() -def cli_main(): +def cli_main(args: Optional[List[str]] = None): """Synchronous CLI entry point for setup.py console_scripts.""" - asyncio.run(main()) + asyncio.run(main(args)) if __name__ == "__main__": diff --git a/g4f/Provider/qwen/QwenCode.py b/g4f/Provider/qwen/QwenCode.py index f8369ffb..89927f11 100644 --- a/g4f/Provider/qwen/QwenCode.py +++ b/g4f/Provider/qwen/QwenCode.py @@ -124,7 +124,7 @@ class QwenCode(OpenaiTemplate): return None -async def main(): +async def main(args: Optional[list[str]] = None): """CLI entry point for QwenCode authentication.""" import argparse @@ -150,7 +150,7 @@ Examples: # Logout command subparsers.add_parser("logout", help="Remove saved credentials") - args = parser.parse_args() + args = parser.parse_args(args) if args.command == "login": try: @@ -188,7 +188,7 @@ Examples: print(f" (Could not read credential details: {e})") else: print("✗ No credentials found") - print(f"\nRun 'g4f-qwencode login' to authenticate.") + print(f"\nRun 'g4f auth qwencode' to authenticate.") print() @@ -224,9 +224,9 @@ Examples: parser.print_help() -def cli_main(): +def cli_main(args: Optional[list[str]] = None): """Synchronous CLI entry point for setup.py console_scripts.""" - asyncio.run(main()) + asyncio.run(main(args)) if __name__ == "__main__": diff --git a/g4f/cli/__init__.py b/g4f/cli/__init__.py index 9eb0740f..c5ead7bf 100644 --- a/g4f/cli/__init__.py +++ b/g4f/cli/__init__.py @@ -13,6 +13,8 @@ runtime function depending on the CLI arguments. """ import argparse +import os +import sys from argparse import ArgumentParser # Local imports (within g4f package) @@ -20,6 +22,10 @@ from .client import get_parser, run_client_args from ..requests import BrowserConfig from ..gui.run import gui_parser, run_gui_args from ..config import DEFAULT_PORT, DEFAULT_TIMEOUT, DEFAULT_STREAM_TIMEOUT +from ..Provider.needs_auth.Antigravity import cli_main as antigravity_cli_main +from ..Provider.qwen.QwenCode import cli_main as qwen_cli_main +from ..Provider.github.GithubCopilot import cli_main as github_cli_main +from g4f.Provider.needs_auth.GeminiCLI import cli_main as gemini_cli_main from .. import Provider from .. import cookies @@ -27,12 +33,12 @@ from .. import cookies # -------------------------------------------------------------- # API PARSER # -------------------------------------------------------------- -def get_api_parser() -> ArgumentParser: +def get_api_parser(exit_on_error: bool = True) -> ArgumentParser: """ Creates and returns the argument parser used for: g4f api ... """ - api_parser = ArgumentParser(description="Run the API and GUI") + api_parser = ArgumentParser(description="Run the API and GUI", exit_on_error=exit_on_error) api_parser.add_argument( "--bind", @@ -228,12 +234,12 @@ def run_api_args(args): # -------------------------------------------------------------- # MCP PARSER # -------------------------------------------------------------- -def get_mcp_parser() -> ArgumentParser: +def get_mcp_parser(exit_on_error: bool = True) -> ArgumentParser: """ Parser for: g4f mcp ... """ - mcp_parser = ArgumentParser(description="Run the MCP (Model Context Protocol) server") + mcp_parser = ArgumentParser(description="Run the MCP (Model Context Protocol) server", exit_on_error=exit_on_error) mcp_parser.add_argument("--debug", "-d", action="store_true", help="Enable verbose logging.") mcp_parser.add_argument("--http", action="store_true", help="Use HTTP instead of stdio.") mcp_parser.add_argument("--host", default="0.0.0.0", help="HTTP server host.") @@ -254,6 +260,11 @@ def run_mcp_args(args): origin=args.origin ) +def get_auth_parser(exit_on_error: bool = True) -> ArgumentParser: + auth_parser = ArgumentParser(description="Manage authentication for providers", exit_on_error=exit_on_error) + auth_parser.add_argument("provider", choices=["gemini-cli", "antigravity", "qwencode", "github-copilot"], help="The provider to authenticate with") + auth_parser.add_argument("action", nargs="?", choices=["status", "login", "logout"], default="login", help="Action to perform (default: login)") + return auth_parser # -------------------------------------------------------------- # MAIN ENTRYPOINT @@ -264,31 +275,45 @@ def main(): Handles selecting: api / gui / client / mcp """ parser = argparse.ArgumentParser(description="Run gpt4free", exit_on_error=False) + parser.add_argument("--install-autocomplete", action="store_true", help="Install Bash autocompletion for g4f CLI.") + args, remaining = parser.parse_known_args() + if args.install_autocomplete: + generate_autocomplete() + return + - # Create sub-commands - subparsers = parser.add_subparsers(dest="mode", help="Mode to run g4f in.") - subparsers.add_parser("api", parents=[get_api_parser()], add_help=False) - subparsers.add_parser("gui", parents=[gui_parser()], add_help=False) - subparsers.add_parser("client", parents=[get_parser()], add_help=False) - subparsers.add_parser("mcp", parents=[get_mcp_parser()], add_help=False) - + mode_parser = ArgumentParser(description="Select mode to run g4f in.", exit_on_error=False) + mode_parser.add_argument("mode", nargs="?", choices=["api", "gui", "client", "mcp", "auth"], default="api", help="Mode to run g4f in (default: api).") + + args, remaining = mode_parser.parse_known_args(remaining) try: - args = parser.parse_args() - - # Mode routing - if args.mode == "api": + if args.mode == "auth": + parser = get_auth_parser() + args, remaining = parser.parse_known_args(remaining) + print(f"Handling auth for provider: {args.provider}, action: {args.action}") + handle_auth(args.provider, args.action, remaining) + return + elif args.mode == "api": + parser = get_api_parser() + args = parser.parse_args(remaining) run_api_args(args) elif args.mode == "gui": + parser = gui_parser() + args = parser.parse_args(remaining) run_gui_args(args) elif args.mode == "client": + parser = get_parser() + args = parser.parse_args(remaining) run_client_args(args) elif args.mode == "mcp": + parser = get_mcp_parser() + args = parser.parse_args(remaining) run_mcp_args(args) else: # No mode provided raise argparse.ArgumentError( None, - "No valid mode specified. Use 'api', 'gui', 'client', or 'mcp'." + "No valid mode specified. Use 'api', 'gui', 'client', 'mcp', or 'auth'." ) except argparse.ArgumentError: @@ -302,3 +327,58 @@ def main(): except argparse.ArgumentError: # 2. Try API mode with default arguments run_api_args(get_api_parser().parse_args()) + +def generate_autocomplete(): + # Top-level commands and their subcommands/options + commands = ["api", "gui", "client", "mcp", "auth"] + auth_providers = ["gemini-cli", "antigravity", "qwencode", "github-copilot"] + auth_subcommands = ["status", "login"] + # Options for each command + api_args = ["--bind", "--port", "--debug", "--gui", "--no-gui", "--model", "--provider", "--media-provider", "--proxy", "--workers", "--disable-colors", "--ignore-cookie-files", "--g4f-api-key", "--ignored-providers", "--cookie-browsers", "--reload", "--demo", "--timeout", "--stream-timeout", "--ssl-keyfile", "--ssl-certfile", "--log-config", "--access-log", "--no-access-log", "--browser-port", "--browser-host"] + gui_args = ["--debug"] + client_args = ["--debug"] + mcp_args = ["--debug", "--http", "--host", "--port", "--origin"] + global_args = ["--install-autocomplete"] + bash_completion_script = f""" +_g4f_completions() {{ + local cur prev words cword + _get_comp_words_by_ref -n : cur prev words cword + if [[ $cword -eq 1 ]]; then + COMPREPLY=($(compgen -W '{' '.join(commands + global_args)}' -- "$cur")) + elif [[ $prev == auth && $cword -eq 2 ]]; then + COMPREPLY=($(compgen -W '{' '.join(auth_providers)}' -- "$cur")) + elif [[ $prev =~ ^(gemini-cli|antigravity|qwencode|github-copilot)$ && $cword -eq 3 ]]; then + COMPREPLY=($(compgen -W '{' '.join(auth_subcommands)}' -- "$cur")) + elif [[ $words[1] == api ]]; then + local opts="{' '.join(api_args)}" + COMPREPLY=($(compgen -W "$opts" -- "$cur")) + elif [[ $words[1] == gui ]]; then + local opts="{' '.join(gui_args)}" + COMPREPLY=($(compgen -W "$opts" -- "$cur")) + elif [[ $words[1] == client ]]; then + local opts="{' '.join(client_args)}" + COMPREPLY=($(compgen -W "$opts" -- "$cur")) + elif [[ $words[1] == mcp ]]; then + local opts="{' '.join(mcp_args)}" + COMPREPLY=($(compgen -W "$opts" -- "$cur")) + fi +}} +complete -F _g4f_completions g4f +""" + completion_file = os.path.expanduser("~/.g4f_bash_completion") + with open(completion_file, "w") as f: + f.write(bash_completion_script) + print(f"Bash completion script written to {completion_file}. Source it in your .bashrc or .bash_profile.") + + +def handle_auth(provider, action, remaining): + if provider == "gemini-cli": + sys.exit(gemini_cli_main([action] + remaining)) + elif provider == "antigravity": + sys.exit(antigravity_cli_main([action] + remaining)) + elif provider == "qwencode": + sys.exit(qwen_cli_main([action] + remaining)) + elif provider == "github-copilot": + sys.exit(github_cli_main([action] + remaining)) + else: + print(f"Provider {provider} not supported yet.") \ No newline at end of file diff --git a/g4f/gui/gui_parser.py b/g4f/gui/gui_parser.py index bf006677..43e9e5cf 100644 --- a/g4f/gui/gui_parser.py +++ b/g4f/gui/gui_parser.py @@ -3,8 +3,8 @@ from argparse import ArgumentParser from ..cookies import BROWSERS from .. import Provider -def gui_parser(): - parser = ArgumentParser(description="Run the GUI") +def gui_parser(exit_on_error: bool = True): + parser = ArgumentParser(description="Run the GUI", exit_on_error=exit_on_error) parser.add_argument("--host", type=str, default="0.0.0.0", help="hostname") parser.add_argument("--port", "-p", type=int, default=8080, help="port") parser.add_argument("--debug", "-d", "-debug", action="store_true", help="debug mode") diff --git a/g4f_cli.py b/g4f_cli.py index e999399d..4685fd47 100644 --- a/g4f_cli.py +++ b/g4f_cli.py @@ -7,25 +7,6 @@ This file is used as the main entry point for building executables with Nuitka import g4f.debug g4f.debug.enable_logging() -from g4f.client import Client -from g4f.errors import ModelNotFoundError - -import g4f.Provider - -try: - client = Client(provider=g4f.Provider.PollinationsAI) - response = client.chat.completions.create( - model="openai", - messages=[{"role": "user", "content": "Hello!"}], - stream=True, - raw=True - ) - for r in response: - print(r) -except ModelNotFoundError as e: - print(f"Successfully") -exit(0) - import g4f.cli if __name__ == "__main__": diff --git a/setup.py b/setup.py index eb759f8a..40d15bed 100644 --- a/setup.py +++ b/setup.py @@ -121,10 +121,6 @@ setup( 'console_scripts': [ 'g4f=g4f.cli:main', 'g4f-mcp=g4f.mcp.server:main', - 'g4f-antigravity=g4f.Provider.needs_auth.Antigravity:cli_main', - 'g4f-geminicli=g4f.Provider.needs_auth.GeminiCLI:cli_main', - 'g4f-qwencode=g4f.Provider.qwen.QwenCode:cli_main', - 'g4f-github-copilot=g4f.Provider.github.GithubCopilot:cli_main', ], }, url='https://github.com/xtekky/gpt4free', # Link to your GitHub repository