Refactor CLI authentication commands and improve argument parsing

This commit is contained in:
hlohaus
2026-02-22 13:12:34 +01:00
parent 21380803a5
commit 5100c19c4a
8 changed files with 118 additions and 61 deletions
+6 -6
View File
@@ -25,7 +25,7 @@ class GithubCopilot(OpenaiTemplate):
allowing users to authenticate via browser without sharing credentials. allowing users to authenticate via browser without sharing credentials.
Usage: Usage:
1. Run `g4f-github-copilot login` to authenticate 1. Run `g4f auth github-copilot` to authenticate
2. Use the provider normally after authentication 2. Use the provider normally after authentication
Example: Example:
@@ -270,7 +270,7 @@ class GithubCopilot(OpenaiTemplate):
usage = await resp.json() usage = await resp.json()
return usage return usage
async def main(): async def main(args: Optional[List[str]] = None):
"""CLI entry point for GitHub Copilot OAuth authentication.""" """CLI entry point for GitHub Copilot OAuth authentication."""
import argparse import argparse
@@ -296,7 +296,7 @@ Examples:
# Logout command # Logout command
subparsers.add_parser("logout", help="Remove saved credentials") subparsers.add_parser("logout", help="Remove saved credentials")
args = parser.parse_args() args = parser.parse_args(args)
if args.command == "login": if args.command == "login":
try: try:
@@ -334,7 +334,7 @@ Examples:
print(f" (Could not read credential details: {e})") print(f" (Could not read credential details: {e})")
else: else:
print("✗ No credentials found") print("✗ No credentials found")
print(f"\nRun 'g4f-github-copilot login' to authenticate.") print(f"\nRun 'g4f auth github-copilot' to authenticate.")
print() print()
@@ -370,9 +370,9 @@ Examples:
parser.print_help() parser.print_help()
def cli_main(): def cli_main(args: Optional[List[str]] = None):
"""Synchronous CLI entry point for setup.py console_scripts.""" """Synchronous CLI entry point for setup.py console_scripts."""
asyncio.run(main()) asyncio.run(main(args))
if __name__ == "__main__": if __name__ == "__main__":
+4 -4
View File
@@ -1499,7 +1499,7 @@ class Antigravity(AsyncGeneratorProvider, ProviderModelMixin):
return cache_path return cache_path
async def main(): async def main(args: Optional[List[str]] = None):
"""CLI entry point for Antigravity authentication.""" """CLI entry point for Antigravity authentication."""
import argparse import argparse
@@ -1537,7 +1537,7 @@ Examples:
# Logout command # Logout command
subparsers.add_parser("logout", help="Remove saved credentials") subparsers.add_parser("logout", help="Remove saved credentials")
args = parser.parse_args() args = parser.parse_args(args)
if args.command == "login": if args.command == "login":
try: try:
@@ -1616,9 +1616,9 @@ Examples:
parser.print_help() parser.print_help()
def cli_main(): def cli_main(args: Optional[List[str]] = None):
"""Synchronous CLI entry point for setup.py console_scripts.""" """Synchronous CLI entry point for setup.py console_scripts."""
asyncio.run(main()) asyncio.run(main(args))
if __name__ == "__main__": if __name__ == "__main__":
+5 -5
View File
@@ -1171,7 +1171,7 @@ class GeminiCLI(AsyncGeneratorProvider, ProviderModelMixin):
return None return None
async def main(): async def main(args: Optional[List[str]] = None):
"""CLI entry point for GeminiCLI authentication.""" """CLI entry point for GeminiCLI authentication."""
import argparse import argparse
@@ -1203,7 +1203,7 @@ Examples:
# Logout command # Logout command
subparsers.add_parser("logout", help="Remove saved credentials") subparsers.add_parser("logout", help="Remove saved credentials")
args = parser.parse_args() args = parser.parse_args(args)
if args.command == "login": if args.command == "login":
try: try:
@@ -1241,7 +1241,7 @@ Examples:
print(f" (Could not read credential details: {e})") print(f" (Could not read credential details: {e})")
else: else:
print("✗ No credentials found") print("✗ No credentials found")
print(f"\nRun 'g4f-geminicli login' to authenticate.") print(f"\nRun 'g4f auth gemini-cli login' to authenticate.")
print() print()
@@ -1274,9 +1274,9 @@ Examples:
parser.print_help() parser.print_help()
def cli_main(): def cli_main(args: Optional[List[str]] = None):
"""Synchronous CLI entry point for setup.py console_scripts.""" """Synchronous CLI entry point for setup.py console_scripts."""
asyncio.run(main()) asyncio.run(main(args))
if __name__ == "__main__": if __name__ == "__main__":
+5 -5
View File
@@ -124,7 +124,7 @@ class QwenCode(OpenaiTemplate):
return None return None
async def main(): async def main(args: Optional[list[str]] = None):
"""CLI entry point for QwenCode authentication.""" """CLI entry point for QwenCode authentication."""
import argparse import argparse
@@ -150,7 +150,7 @@ Examples:
# Logout command # Logout command
subparsers.add_parser("logout", help="Remove saved credentials") subparsers.add_parser("logout", help="Remove saved credentials")
args = parser.parse_args() args = parser.parse_args(args)
if args.command == "login": if args.command == "login":
try: try:
@@ -188,7 +188,7 @@ Examples:
print(f" (Could not read credential details: {e})") print(f" (Could not read credential details: {e})")
else: else:
print("✗ No credentials found") print("✗ No credentials found")
print(f"\nRun 'g4f-qwencode login' to authenticate.") print(f"\nRun 'g4f auth qwencode' to authenticate.")
print() print()
@@ -224,9 +224,9 @@ Examples:
parser.print_help() parser.print_help()
def cli_main(): def cli_main(args: Optional[list[str]] = None):
"""Synchronous CLI entry point for setup.py console_scripts.""" """Synchronous CLI entry point for setup.py console_scripts."""
asyncio.run(main()) asyncio.run(main(args))
if __name__ == "__main__": if __name__ == "__main__":
+96 -16
View File
@@ -13,6 +13,8 @@ runtime function depending on the CLI arguments.
""" """
import argparse import argparse
import os
import sys
from argparse import ArgumentParser from argparse import ArgumentParser
# Local imports (within g4f package) # Local imports (within g4f package)
@@ -20,6 +22,10 @@ from .client import get_parser, run_client_args
from ..requests import BrowserConfig from ..requests import BrowserConfig
from ..gui.run import gui_parser, run_gui_args from ..gui.run import gui_parser, run_gui_args
from ..config import DEFAULT_PORT, DEFAULT_TIMEOUT, DEFAULT_STREAM_TIMEOUT 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 Provider
from .. import cookies from .. import cookies
@@ -27,12 +33,12 @@ from .. import cookies
# -------------------------------------------------------------- # --------------------------------------------------------------
# API PARSER # 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: Creates and returns the argument parser used for:
g4f api ... 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( api_parser.add_argument(
"--bind", "--bind",
@@ -228,12 +234,12 @@ def run_api_args(args):
# -------------------------------------------------------------- # --------------------------------------------------------------
# MCP PARSER # MCP PARSER
# -------------------------------------------------------------- # --------------------------------------------------------------
def get_mcp_parser() -> ArgumentParser: def get_mcp_parser(exit_on_error: bool = True) -> ArgumentParser:
""" """
Parser for: Parser for:
g4f mcp ... 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("--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("--http", action="store_true", help="Use HTTP instead of stdio.")
mcp_parser.add_argument("--host", default="0.0.0.0", help="HTTP server host.") 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 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 # MAIN ENTRYPOINT
@@ -264,31 +275,45 @@ def main():
Handles selecting: api / gui / client / mcp Handles selecting: api / gui / client / mcp
""" """
parser = argparse.ArgumentParser(description="Run gpt4free", exit_on_error=False) 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 mode_parser = ArgumentParser(description="Select mode to run g4f in.", exit_on_error=False)
subparsers = parser.add_subparsers(dest="mode", help="Mode to run g4f in.") mode_parser.add_argument("mode", nargs="?", choices=["api", "gui", "client", "mcp", "auth"], default="api", help="Mode to run g4f in (default: api).")
subparsers.add_parser("api", parents=[get_api_parser()], add_help=False)
subparsers.add_parser("gui", parents=[gui_parser()], add_help=False) args, remaining = mode_parser.parse_known_args(remaining)
subparsers.add_parser("client", parents=[get_parser()], add_help=False)
subparsers.add_parser("mcp", parents=[get_mcp_parser()], add_help=False)
try: try:
args = parser.parse_args() if args.mode == "auth":
parser = get_auth_parser()
# Mode routing args, remaining = parser.parse_known_args(remaining)
if args.mode == "api": 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) run_api_args(args)
elif args.mode == "gui": elif args.mode == "gui":
parser = gui_parser()
args = parser.parse_args(remaining)
run_gui_args(args) run_gui_args(args)
elif args.mode == "client": elif args.mode == "client":
parser = get_parser()
args = parser.parse_args(remaining)
run_client_args(args) run_client_args(args)
elif args.mode == "mcp": elif args.mode == "mcp":
parser = get_mcp_parser()
args = parser.parse_args(remaining)
run_mcp_args(args) run_mcp_args(args)
else: else:
# No mode provided # No mode provided
raise argparse.ArgumentError( raise argparse.ArgumentError(
None, 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: except argparse.ArgumentError:
@@ -302,3 +327,58 @@ def main():
except argparse.ArgumentError: except argparse.ArgumentError:
# 2. Try API mode with default arguments # 2. Try API mode with default arguments
run_api_args(get_api_parser().parse_args()) 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.")
+2 -2
View File
@@ -3,8 +3,8 @@ from argparse import ArgumentParser
from ..cookies import BROWSERS from ..cookies import BROWSERS
from .. import Provider from .. import Provider
def gui_parser(): def gui_parser(exit_on_error: bool = True):
parser = ArgumentParser(description="Run the GUI") 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("--host", type=str, default="0.0.0.0", help="hostname")
parser.add_argument("--port", "-p", type=int, default=8080, help="port") parser.add_argument("--port", "-p", type=int, default=8080, help="port")
parser.add_argument("--debug", "-d", "-debug", action="store_true", help="debug mode") parser.add_argument("--debug", "-d", "-debug", action="store_true", help="debug mode")
-19
View File
@@ -7,25 +7,6 @@ This file is used as the main entry point for building executables with Nuitka
import g4f.debug import g4f.debug
g4f.debug.enable_logging() 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 import g4f.cli
if __name__ == "__main__": if __name__ == "__main__":
-4
View File
@@ -121,10 +121,6 @@ setup(
'console_scripts': [ 'console_scripts': [
'g4f=g4f.cli:main', 'g4f=g4f.cli:main',
'g4f-mcp=g4f.mcp.server: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 url='https://github.com/xtekky/gpt4free', # Link to your GitHub repository