| | |
| | """Kiro Proxy CLI - 轻量命令行工具""" |
| | import argparse |
| | import asyncio |
| | import json |
| | import sys |
| | from pathlib import Path |
| |
|
| | from . import __version__ |
| |
|
| |
|
| | def cmd_serve(args): |
| | """启动代理服务""" |
| | from .main import run |
| | run(port=args.port) |
| |
|
| |
|
| | def cmd_accounts_list(args): |
| | """列出所有账号""" |
| | from .core import state |
| | accounts = state.get_accounts_status() |
| | if not accounts: |
| | print("暂无账号") |
| | return |
| | print(f"{'ID':<10} {'名称':<20} {'状态':<10} {'请求数':<8}") |
| | print("-" * 50) |
| | for acc in accounts: |
| | print(f"{acc['id']:<10} {acc['name']:<20} {acc['status']:<10} {acc['request_count']:<8}") |
| |
|
| |
|
| | def cmd_accounts_export(args): |
| | """导出账号配置""" |
| | from .core import state |
| | accounts_data = [] |
| | for acc in state.accounts: |
| | creds = acc.get_credentials() |
| | if creds: |
| | accounts_data.append({ |
| | "name": acc.name, |
| | "enabled": acc.enabled, |
| | "credentials": { |
| | "accessToken": creds.access_token, |
| | "refreshToken": creds.refresh_token, |
| | "expiresAt": creds.expires_at, |
| | "region": creds.region, |
| | "authMethod": creds.auth_method, |
| | } |
| | }) |
| | |
| | output = {"accounts": accounts_data, "version": "1.0"} |
| | |
| | if args.output: |
| | Path(args.output).write_text(json.dumps(output, indent=2, ensure_ascii=False)) |
| | print(f"已导出 {len(accounts_data)} 个账号到 {args.output}") |
| | else: |
| | print(json.dumps(output, indent=2, ensure_ascii=False)) |
| |
|
| |
|
| | def cmd_accounts_import(args): |
| | """导入账号配置""" |
| | import uuid |
| | from .core import state, Account |
| | from .auth import save_credentials_to_file |
| | |
| | data = json.loads(Path(args.file).read_text()) |
| | accounts_data = data.get("accounts", []) |
| | imported = 0 |
| | |
| | for acc_data in accounts_data: |
| | creds = acc_data.get("credentials", {}) |
| | if not creds.get("accessToken"): |
| | print(f"跳过 {acc_data.get('name', '未知')}: 缺少 accessToken") |
| | continue |
| | |
| | |
| | file_path = asyncio.run(save_credentials_to_file({ |
| | "accessToken": creds.get("accessToken"), |
| | "refreshToken": creds.get("refreshToken"), |
| | "expiresAt": creds.get("expiresAt"), |
| | "region": creds.get("region", "us-east-1"), |
| | "authMethod": creds.get("authMethod", "social"), |
| | }, f"imported-{uuid.uuid4().hex[:8]}")) |
| | |
| | account = Account( |
| | id=uuid.uuid4().hex[:8], |
| | name=acc_data.get("name", "导入账号"), |
| | token_path=file_path, |
| | enabled=acc_data.get("enabled", True) |
| | ) |
| | state.accounts.append(account) |
| | account.load_credentials() |
| | imported += 1 |
| | print(f"已导入: {account.name}") |
| | |
| | state._save_accounts() |
| | print(f"\n共导入 {imported} 个账号") |
| |
|
| |
|
| | def cmd_accounts_add(args): |
| | """手动添加 Token""" |
| | import uuid |
| | from .core import state, Account |
| | from .auth import save_credentials_to_file |
| | |
| | print("手动添加 Kiro 账号") |
| | print("-" * 40) |
| | |
| | name = input("账号名称 [我的账号]: ").strip() or "我的账号" |
| | print("\n请粘贴 Access Token:") |
| | access_token = input().strip() |
| | |
| | if not access_token: |
| | print("错误: Access Token 不能为空") |
| | return |
| | |
| | print("\n请粘贴 Refresh Token (可选,直接回车跳过):") |
| | refresh_token = input().strip() or None |
| | |
| | |
| | file_path = asyncio.run(save_credentials_to_file({ |
| | "accessToken": access_token, |
| | "refreshToken": refresh_token, |
| | "region": "us-east-1", |
| | "authMethod": "social", |
| | }, f"manual-{uuid.uuid4().hex[:8]}")) |
| | |
| | account = Account( |
| | id=uuid.uuid4().hex[:8], |
| | name=name, |
| | token_path=file_path |
| | ) |
| | state.accounts.append(account) |
| | account.load_credentials() |
| | state._save_accounts() |
| | |
| | print(f"\n✅ 账号已添加: {name} (ID: {account.id})") |
| |
|
| |
|
| | def cmd_accounts_scan(args): |
| | """扫描本地 Token""" |
| | import uuid |
| | from .core import state, Account |
| | from .config import TOKEN_DIR |
| | |
| | |
| | found = [] |
| | if TOKEN_DIR.exists(): |
| | for f in TOKEN_DIR.glob("*.json"): |
| | try: |
| | data = json.loads(f.read_text()) |
| | if "accessToken" in data: |
| | already = any(a.token_path == str(f) for a in state.accounts) |
| | found.append({"path": str(f), "name": f.stem, "already": already}) |
| | except: |
| | pass |
| | |
| | |
| | sso_cache = Path.home() / ".aws/sso/cache" |
| | if sso_cache.exists(): |
| | for f in sso_cache.glob("*.json"): |
| | try: |
| | data = json.loads(f.read_text()) |
| | if "accessToken" in data: |
| | already = any(a.token_path == str(f) for a in state.accounts) |
| | found.append({"path": str(f), "name": f.stem + " (旧目录)", "already": already}) |
| | except: |
| | pass |
| | |
| | if not found: |
| | print("未找到 Token 文件") |
| | print(f"Token 目录: {TOKEN_DIR}") |
| | return |
| | |
| | print(f"找到 {len(found)} 个 Token:\n") |
| | for i, t in enumerate(found): |
| | status = "[已添加]" if t["already"] else "" |
| | print(f" {i+1}. {t['name']} {status}") |
| | |
| | if args.auto: |
| | |
| | added = 0 |
| | for t in found: |
| | if not t["already"]: |
| | account = Account( |
| | id=uuid.uuid4().hex[:8], |
| | name=t["name"], |
| | token_path=t["path"] |
| | ) |
| | state.accounts.append(account) |
| | account.load_credentials() |
| | added += 1 |
| | state._save_accounts() |
| | print(f"\n已添加 {added} 个账号") |
| | else: |
| | print("\n使用 --auto 自动添加所有未添加的账号") |
| |
|
| |
|
| | def cmd_login_remote(args): |
| | """生成远程登录链接""" |
| | import uuid |
| | import time |
| | |
| | session_id = uuid.uuid4().hex |
| | host = args.host or "localhost:8080" |
| | scheme = "https" if args.https else "http" |
| | |
| | print("远程登录链接") |
| | print("-" * 40) |
| | print(f"\n将以下链接发送到有浏览器的机器上完成登录:\n") |
| | print(f" {scheme}://{host}/remote-login/{session_id}") |
| | print(f"\n链接有效期 10 分钟") |
| | print("\n登录完成后,在那台机器上导出账号,然后在这里导入:") |
| | print(f" python -m kiro_proxy accounts import xxx.json") |
| |
|
| |
|
| | def cmd_login_social(args): |
| | """Social 登录 (Google/GitHub)""" |
| | from .auth import start_social_auth |
| | |
| | provider = args.provider |
| | print(f"启动 {provider.title()} 登录...") |
| | |
| | success, result = asyncio.run(start_social_auth(provider)) |
| | if not success: |
| | print(f"错误: {result.get('error', '未知错误')}") |
| | return |
| | |
| | print(f"\n请在浏览器中打开以下链接完成授权:\n") |
| | print(f" {result['login_url']}") |
| | print(f"\n授权完成后,将浏览器地址栏中的完整 URL 粘贴到这里:") |
| | callback_url = input().strip() |
| | |
| | if not callback_url: |
| | print("已取消") |
| | return |
| | |
| | try: |
| | from urllib.parse import urlparse, parse_qs |
| | parsed = urlparse(callback_url) |
| | params = parse_qs(parsed.query) |
| | code = params.get("code", [None])[0] |
| | oauth_state = params.get("state", [None])[0] |
| | |
| | if not code or not oauth_state: |
| | print("错误: 无效的回调 URL") |
| | return |
| | |
| | from .auth import exchange_social_auth_token |
| | success, result = asyncio.run(exchange_social_auth_token(code, oauth_state)) |
| | |
| | if success and result.get("completed"): |
| | import uuid |
| | from .core import state, Account |
| | from .auth import save_credentials_to_file |
| | |
| | credentials = result["credentials"] |
| | file_path = asyncio.run(save_credentials_to_file( |
| | credentials, f"cli-{provider}" |
| | )) |
| | |
| | account = Account( |
| | id=uuid.uuid4().hex[:8], |
| | name=f"{provider.title()} 登录", |
| | token_path=file_path |
| | ) |
| | state.accounts.append(account) |
| | account.load_credentials() |
| | state._save_accounts() |
| | |
| | print(f"\n✅ 登录成功! 账号已添加: {account.name}") |
| | else: |
| | print(f"错误: {result.get('error', '登录失败')}") |
| | except Exception as e: |
| | print(f"错误: {e}") |
| |
|
| |
|
| | def cmd_status(args): |
| | """查看服务状态""" |
| | from .core import state |
| | stats = state.get_stats() |
| | |
| | print("Kiro Proxy 状态") |
| | print("-" * 40) |
| | print(f"运行时间: {stats['uptime_seconds']} 秒") |
| | print(f"总请求数: {stats['total_requests']}") |
| | print(f"错误数: {stats['total_errors']}") |
| | print(f"错误率: {stats['error_rate']}") |
| | print(f"账号总数: {stats['accounts_total']}") |
| | print(f"可用账号: {stats['accounts_available']}") |
| | print(f"冷却中: {stats['accounts_cooldown']}") |
| |
|
| |
|
| | def main(): |
| | parser = argparse.ArgumentParser( |
| | prog="kiro-proxy", |
| | description="Kiro API Proxy CLI" |
| | ) |
| | parser.add_argument("-v", "--version", action="version", version=__version__) |
| | |
| | subparsers = parser.add_subparsers(dest="command", help="命令") |
| | |
| | |
| | serve_parser = subparsers.add_parser("serve", help="启动代理服务") |
| | serve_parser.add_argument("-p", "--port", type=int, default=8080, help="端口号") |
| | serve_parser.set_defaults(func=cmd_serve) |
| | |
| | |
| | status_parser = subparsers.add_parser("status", help="查看状态") |
| | status_parser.set_defaults(func=cmd_status) |
| | |
| | |
| | accounts_parser = subparsers.add_parser("accounts", help="账号管理") |
| | accounts_sub = accounts_parser.add_subparsers(dest="accounts_cmd") |
| | |
| | |
| | list_parser = accounts_sub.add_parser("list", help="列出账号") |
| | list_parser.set_defaults(func=cmd_accounts_list) |
| | |
| | |
| | export_parser = accounts_sub.add_parser("export", help="导出账号") |
| | export_parser.add_argument("-o", "--output", help="输出文件") |
| | export_parser.set_defaults(func=cmd_accounts_export) |
| | |
| | |
| | import_parser = accounts_sub.add_parser("import", help="导入账号") |
| | import_parser.add_argument("file", help="JSON 文件路径") |
| | import_parser.set_defaults(func=cmd_accounts_import) |
| | |
| | |
| | add_parser = accounts_sub.add_parser("add", help="手动添加 Token") |
| | add_parser.set_defaults(func=cmd_accounts_add) |
| | |
| | |
| | scan_parser = accounts_sub.add_parser("scan", help="扫描本地 Token") |
| | scan_parser.add_argument("--auto", action="store_true", help="自动添加") |
| | scan_parser.set_defaults(func=cmd_accounts_scan) |
| | |
| | |
| | login_parser = subparsers.add_parser("login", help="登录") |
| | login_sub = login_parser.add_subparsers(dest="login_cmd") |
| | |
| | |
| | remote_parser = login_sub.add_parser("remote", help="生成远程登录链接") |
| | remote_parser.add_argument("--host", help="服务器地址 (如 example.com:8080)") |
| | remote_parser.add_argument("--https", action="store_true", help="使用 HTTPS") |
| | remote_parser.set_defaults(func=cmd_login_remote) |
| | |
| | |
| | google_parser = login_sub.add_parser("google", help="Google 登录") |
| | google_parser.set_defaults(func=cmd_login_social, provider="google") |
| | |
| | |
| | github_parser = login_sub.add_parser("github", help="GitHub 登录") |
| | github_parser.set_defaults(func=cmd_login_social, provider="github") |
| | |
| | args = parser.parse_args() |
| | |
| | if not args.command: |
| | parser.print_help() |
| | return |
| | |
| | if args.command == "accounts" and not args.accounts_cmd: |
| | accounts_parser.print_help() |
| | return |
| | |
| | if args.command == "login" and not args.login_cmd: |
| | login_parser.print_help() |
| | return |
| | |
| | if hasattr(args, "func"): |
| | args.func(args) |
| |
|
| |
|
| | if __name__ == "__main__": |
| | main() |
| |
|