codepp 0.0.437__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. code_puppy/__init__.py +10 -0
  2. code_puppy/__main__.py +10 -0
  3. code_puppy/agents/__init__.py +31 -0
  4. code_puppy/agents/agent_c_reviewer.py +155 -0
  5. code_puppy/agents/agent_code_puppy.py +117 -0
  6. code_puppy/agents/agent_code_reviewer.py +90 -0
  7. code_puppy/agents/agent_cpp_reviewer.py +132 -0
  8. code_puppy/agents/agent_creator_agent.py +638 -0
  9. code_puppy/agents/agent_golang_reviewer.py +151 -0
  10. code_puppy/agents/agent_helios.py +124 -0
  11. code_puppy/agents/agent_javascript_reviewer.py +160 -0
  12. code_puppy/agents/agent_manager.py +742 -0
  13. code_puppy/agents/agent_pack_leader.py +385 -0
  14. code_puppy/agents/agent_planning.py +165 -0
  15. code_puppy/agents/agent_python_programmer.py +169 -0
  16. code_puppy/agents/agent_python_reviewer.py +90 -0
  17. code_puppy/agents/agent_qa_expert.py +163 -0
  18. code_puppy/agents/agent_qa_kitten.py +208 -0
  19. code_puppy/agents/agent_scheduler.py +121 -0
  20. code_puppy/agents/agent_security_auditor.py +181 -0
  21. code_puppy/agents/agent_terminal_qa.py +323 -0
  22. code_puppy/agents/agent_typescript_reviewer.py +166 -0
  23. code_puppy/agents/base_agent.py +2156 -0
  24. code_puppy/agents/event_stream_handler.py +348 -0
  25. code_puppy/agents/json_agent.py +202 -0
  26. code_puppy/agents/pack/__init__.py +34 -0
  27. code_puppy/agents/pack/bloodhound.py +304 -0
  28. code_puppy/agents/pack/husky.py +327 -0
  29. code_puppy/agents/pack/retriever.py +393 -0
  30. code_puppy/agents/pack/shepherd.py +348 -0
  31. code_puppy/agents/pack/terrier.py +287 -0
  32. code_puppy/agents/pack/watchdog.py +367 -0
  33. code_puppy/agents/prompt_reviewer.py +145 -0
  34. code_puppy/agents/subagent_stream_handler.py +276 -0
  35. code_puppy/api/__init__.py +13 -0
  36. code_puppy/api/app.py +169 -0
  37. code_puppy/api/main.py +21 -0
  38. code_puppy/api/pty_manager.py +453 -0
  39. code_puppy/api/routers/__init__.py +12 -0
  40. code_puppy/api/routers/agents.py +36 -0
  41. code_puppy/api/routers/commands.py +217 -0
  42. code_puppy/api/routers/config.py +75 -0
  43. code_puppy/api/routers/sessions.py +234 -0
  44. code_puppy/api/templates/terminal.html +361 -0
  45. code_puppy/api/websocket.py +154 -0
  46. code_puppy/callbacks.py +692 -0
  47. code_puppy/chatgpt_codex_client.py +338 -0
  48. code_puppy/claude_cache_client.py +672 -0
  49. code_puppy/cli_runner.py +1073 -0
  50. code_puppy/command_line/__init__.py +1 -0
  51. code_puppy/command_line/add_model_menu.py +1092 -0
  52. code_puppy/command_line/agent_menu.py +662 -0
  53. code_puppy/command_line/attachments.py +395 -0
  54. code_puppy/command_line/autosave_menu.py +704 -0
  55. code_puppy/command_line/clipboard.py +527 -0
  56. code_puppy/command_line/colors_menu.py +532 -0
  57. code_puppy/command_line/command_handler.py +293 -0
  58. code_puppy/command_line/command_registry.py +150 -0
  59. code_puppy/command_line/config_commands.py +719 -0
  60. code_puppy/command_line/core_commands.py +867 -0
  61. code_puppy/command_line/diff_menu.py +865 -0
  62. code_puppy/command_line/file_path_completion.py +73 -0
  63. code_puppy/command_line/load_context_completion.py +52 -0
  64. code_puppy/command_line/mcp/__init__.py +10 -0
  65. code_puppy/command_line/mcp/base.py +32 -0
  66. code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
  67. code_puppy/command_line/mcp/custom_server_form.py +688 -0
  68. code_puppy/command_line/mcp/custom_server_installer.py +195 -0
  69. code_puppy/command_line/mcp/edit_command.py +148 -0
  70. code_puppy/command_line/mcp/handler.py +138 -0
  71. code_puppy/command_line/mcp/help_command.py +147 -0
  72. code_puppy/command_line/mcp/install_command.py +214 -0
  73. code_puppy/command_line/mcp/install_menu.py +705 -0
  74. code_puppy/command_line/mcp/list_command.py +94 -0
  75. code_puppy/command_line/mcp/logs_command.py +235 -0
  76. code_puppy/command_line/mcp/remove_command.py +82 -0
  77. code_puppy/command_line/mcp/restart_command.py +100 -0
  78. code_puppy/command_line/mcp/search_command.py +123 -0
  79. code_puppy/command_line/mcp/start_all_command.py +135 -0
  80. code_puppy/command_line/mcp/start_command.py +117 -0
  81. code_puppy/command_line/mcp/status_command.py +184 -0
  82. code_puppy/command_line/mcp/stop_all_command.py +112 -0
  83. code_puppy/command_line/mcp/stop_command.py +80 -0
  84. code_puppy/command_line/mcp/test_command.py +107 -0
  85. code_puppy/command_line/mcp/utils.py +129 -0
  86. code_puppy/command_line/mcp/wizard_utils.py +334 -0
  87. code_puppy/command_line/mcp_completion.py +174 -0
  88. code_puppy/command_line/model_picker_completion.py +197 -0
  89. code_puppy/command_line/model_settings_menu.py +932 -0
  90. code_puppy/command_line/motd.py +96 -0
  91. code_puppy/command_line/onboarding_slides.py +179 -0
  92. code_puppy/command_line/onboarding_wizard.py +342 -0
  93. code_puppy/command_line/pin_command_completion.py +329 -0
  94. code_puppy/command_line/prompt_toolkit_completion.py +846 -0
  95. code_puppy/command_line/session_commands.py +302 -0
  96. code_puppy/command_line/shell_passthrough.py +145 -0
  97. code_puppy/command_line/skills_completion.py +160 -0
  98. code_puppy/command_line/uc_menu.py +893 -0
  99. code_puppy/command_line/utils.py +93 -0
  100. code_puppy/command_line/wiggum_state.py +78 -0
  101. code_puppy/config.py +1770 -0
  102. code_puppy/error_logging.py +134 -0
  103. code_puppy/gemini_code_assist.py +385 -0
  104. code_puppy/gemini_model.py +754 -0
  105. code_puppy/hook_engine/README.md +105 -0
  106. code_puppy/hook_engine/__init__.py +21 -0
  107. code_puppy/hook_engine/aliases.py +155 -0
  108. code_puppy/hook_engine/engine.py +221 -0
  109. code_puppy/hook_engine/executor.py +296 -0
  110. code_puppy/hook_engine/matcher.py +156 -0
  111. code_puppy/hook_engine/models.py +240 -0
  112. code_puppy/hook_engine/registry.py +106 -0
  113. code_puppy/hook_engine/validator.py +144 -0
  114. code_puppy/http_utils.py +361 -0
  115. code_puppy/keymap.py +128 -0
  116. code_puppy/main.py +10 -0
  117. code_puppy/mcp_/__init__.py +66 -0
  118. code_puppy/mcp_/async_lifecycle.py +286 -0
  119. code_puppy/mcp_/blocking_startup.py +469 -0
  120. code_puppy/mcp_/captured_stdio_server.py +275 -0
  121. code_puppy/mcp_/circuit_breaker.py +290 -0
  122. code_puppy/mcp_/config_wizard.py +507 -0
  123. code_puppy/mcp_/dashboard.py +308 -0
  124. code_puppy/mcp_/error_isolation.py +407 -0
  125. code_puppy/mcp_/examples/retry_example.py +226 -0
  126. code_puppy/mcp_/health_monitor.py +589 -0
  127. code_puppy/mcp_/managed_server.py +428 -0
  128. code_puppy/mcp_/manager.py +807 -0
  129. code_puppy/mcp_/mcp_logs.py +224 -0
  130. code_puppy/mcp_/registry.py +451 -0
  131. code_puppy/mcp_/retry_manager.py +337 -0
  132. code_puppy/mcp_/server_registry_catalog.py +1126 -0
  133. code_puppy/mcp_/status_tracker.py +355 -0
  134. code_puppy/mcp_/system_tools.py +209 -0
  135. code_puppy/mcp_prompts/__init__.py +1 -0
  136. code_puppy/mcp_prompts/hook_creator.py +103 -0
  137. code_puppy/messaging/__init__.py +255 -0
  138. code_puppy/messaging/bus.py +613 -0
  139. code_puppy/messaging/commands.py +167 -0
  140. code_puppy/messaging/markdown_patches.py +57 -0
  141. code_puppy/messaging/message_queue.py +361 -0
  142. code_puppy/messaging/messages.py +569 -0
  143. code_puppy/messaging/queue_console.py +271 -0
  144. code_puppy/messaging/renderers.py +311 -0
  145. code_puppy/messaging/rich_renderer.py +1158 -0
  146. code_puppy/messaging/spinner/__init__.py +83 -0
  147. code_puppy/messaging/spinner/console_spinner.py +240 -0
  148. code_puppy/messaging/spinner/spinner_base.py +95 -0
  149. code_puppy/messaging/subagent_console.py +460 -0
  150. code_puppy/model_factory.py +848 -0
  151. code_puppy/model_switching.py +63 -0
  152. code_puppy/model_utils.py +168 -0
  153. code_puppy/models.json +174 -0
  154. code_puppy/models_dev_api.json +1 -0
  155. code_puppy/models_dev_parser.py +592 -0
  156. code_puppy/plugins/__init__.py +186 -0
  157. code_puppy/plugins/agent_skills/__init__.py +22 -0
  158. code_puppy/plugins/agent_skills/config.py +175 -0
  159. code_puppy/plugins/agent_skills/discovery.py +136 -0
  160. code_puppy/plugins/agent_skills/downloader.py +392 -0
  161. code_puppy/plugins/agent_skills/installer.py +22 -0
  162. code_puppy/plugins/agent_skills/metadata.py +219 -0
  163. code_puppy/plugins/agent_skills/prompt_builder.py +60 -0
  164. code_puppy/plugins/agent_skills/register_callbacks.py +241 -0
  165. code_puppy/plugins/agent_skills/remote_catalog.py +322 -0
  166. code_puppy/plugins/agent_skills/skill_catalog.py +257 -0
  167. code_puppy/plugins/agent_skills/skills_install_menu.py +664 -0
  168. code_puppy/plugins/agent_skills/skills_menu.py +781 -0
  169. code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
  170. code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
  171. code_puppy/plugins/antigravity_oauth/antigravity_model.py +706 -0
  172. code_puppy/plugins/antigravity_oauth/config.py +42 -0
  173. code_puppy/plugins/antigravity_oauth/constants.py +133 -0
  174. code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
  175. code_puppy/plugins/antigravity_oauth/register_callbacks.py +518 -0
  176. code_puppy/plugins/antigravity_oauth/storage.py +288 -0
  177. code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
  178. code_puppy/plugins/antigravity_oauth/token.py +167 -0
  179. code_puppy/plugins/antigravity_oauth/transport.py +863 -0
  180. code_puppy/plugins/antigravity_oauth/utils.py +168 -0
  181. code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
  182. code_puppy/plugins/chatgpt_oauth/config.py +52 -0
  183. code_puppy/plugins/chatgpt_oauth/oauth_flow.py +329 -0
  184. code_puppy/plugins/chatgpt_oauth/register_callbacks.py +176 -0
  185. code_puppy/plugins/chatgpt_oauth/test_plugin.py +301 -0
  186. code_puppy/plugins/chatgpt_oauth/utils.py +523 -0
  187. code_puppy/plugins/claude_code_hooks/__init__.py +1 -0
  188. code_puppy/plugins/claude_code_hooks/config.py +137 -0
  189. code_puppy/plugins/claude_code_hooks/register_callbacks.py +175 -0
  190. code_puppy/plugins/claude_code_oauth/README.md +167 -0
  191. code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
  192. code_puppy/plugins/claude_code_oauth/__init__.py +25 -0
  193. code_puppy/plugins/claude_code_oauth/config.py +52 -0
  194. code_puppy/plugins/claude_code_oauth/register_callbacks.py +453 -0
  195. code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
  196. code_puppy/plugins/claude_code_oauth/token_refresh_heartbeat.py +241 -0
  197. code_puppy/plugins/claude_code_oauth/utils.py +640 -0
  198. code_puppy/plugins/customizable_commands/__init__.py +0 -0
  199. code_puppy/plugins/customizable_commands/register_callbacks.py +152 -0
  200. code_puppy/plugins/example_custom_command/README.md +280 -0
  201. code_puppy/plugins/example_custom_command/register_callbacks.py +51 -0
  202. code_puppy/plugins/file_permission_handler/__init__.py +4 -0
  203. code_puppy/plugins/file_permission_handler/register_callbacks.py +470 -0
  204. code_puppy/plugins/frontend_emitter/__init__.py +25 -0
  205. code_puppy/plugins/frontend_emitter/emitter.py +121 -0
  206. code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
  207. code_puppy/plugins/hook_creator/__init__.py +1 -0
  208. code_puppy/plugins/hook_creator/register_callbacks.py +33 -0
  209. code_puppy/plugins/hook_manager/__init__.py +1 -0
  210. code_puppy/plugins/hook_manager/config.py +290 -0
  211. code_puppy/plugins/hook_manager/hooks_menu.py +564 -0
  212. code_puppy/plugins/hook_manager/register_callbacks.py +227 -0
  213. code_puppy/plugins/oauth_puppy_html.py +228 -0
  214. code_puppy/plugins/scheduler/__init__.py +1 -0
  215. code_puppy/plugins/scheduler/register_callbacks.py +88 -0
  216. code_puppy/plugins/scheduler/scheduler_menu.py +522 -0
  217. code_puppy/plugins/scheduler/scheduler_wizard.py +341 -0
  218. code_puppy/plugins/shell_safety/__init__.py +6 -0
  219. code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
  220. code_puppy/plugins/shell_safety/command_cache.py +156 -0
  221. code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
  222. code_puppy/plugins/synthetic_status/__init__.py +1 -0
  223. code_puppy/plugins/synthetic_status/register_callbacks.py +132 -0
  224. code_puppy/plugins/synthetic_status/status_api.py +147 -0
  225. code_puppy/plugins/universal_constructor/__init__.py +13 -0
  226. code_puppy/plugins/universal_constructor/models.py +138 -0
  227. code_puppy/plugins/universal_constructor/register_callbacks.py +47 -0
  228. code_puppy/plugins/universal_constructor/registry.py +302 -0
  229. code_puppy/plugins/universal_constructor/sandbox.py +584 -0
  230. code_puppy/prompts/antigravity_system_prompt.md +1 -0
  231. code_puppy/pydantic_patches.py +356 -0
  232. code_puppy/reopenable_async_client.py +232 -0
  233. code_puppy/round_robin_model.py +150 -0
  234. code_puppy/scheduler/__init__.py +41 -0
  235. code_puppy/scheduler/__main__.py +9 -0
  236. code_puppy/scheduler/cli.py +118 -0
  237. code_puppy/scheduler/config.py +126 -0
  238. code_puppy/scheduler/daemon.py +280 -0
  239. code_puppy/scheduler/executor.py +155 -0
  240. code_puppy/scheduler/platform.py +19 -0
  241. code_puppy/scheduler/platform_unix.py +22 -0
  242. code_puppy/scheduler/platform_win.py +32 -0
  243. code_puppy/session_storage.py +338 -0
  244. code_puppy/status_display.py +257 -0
  245. code_puppy/summarization_agent.py +176 -0
  246. code_puppy/terminal_utils.py +418 -0
  247. code_puppy/tools/__init__.py +501 -0
  248. code_puppy/tools/agent_tools.py +603 -0
  249. code_puppy/tools/ask_user_question/__init__.py +26 -0
  250. code_puppy/tools/ask_user_question/constants.py +73 -0
  251. code_puppy/tools/ask_user_question/demo_tui.py +55 -0
  252. code_puppy/tools/ask_user_question/handler.py +232 -0
  253. code_puppy/tools/ask_user_question/models.py +304 -0
  254. code_puppy/tools/ask_user_question/registration.py +26 -0
  255. code_puppy/tools/ask_user_question/renderers.py +309 -0
  256. code_puppy/tools/ask_user_question/terminal_ui.py +329 -0
  257. code_puppy/tools/ask_user_question/theme.py +155 -0
  258. code_puppy/tools/ask_user_question/tui_loop.py +423 -0
  259. code_puppy/tools/browser/__init__.py +37 -0
  260. code_puppy/tools/browser/browser_control.py +289 -0
  261. code_puppy/tools/browser/browser_interactions.py +545 -0
  262. code_puppy/tools/browser/browser_locators.py +640 -0
  263. code_puppy/tools/browser/browser_manager.py +378 -0
  264. code_puppy/tools/browser/browser_navigation.py +251 -0
  265. code_puppy/tools/browser/browser_screenshot.py +179 -0
  266. code_puppy/tools/browser/browser_scripts.py +462 -0
  267. code_puppy/tools/browser/browser_workflows.py +221 -0
  268. code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
  269. code_puppy/tools/browser/terminal_command_tools.py +534 -0
  270. code_puppy/tools/browser/terminal_screenshot_tools.py +552 -0
  271. code_puppy/tools/browser/terminal_tools.py +525 -0
  272. code_puppy/tools/command_runner.py +1346 -0
  273. code_puppy/tools/common.py +1409 -0
  274. code_puppy/tools/display.py +84 -0
  275. code_puppy/tools/file_modifications.py +886 -0
  276. code_puppy/tools/file_operations.py +802 -0
  277. code_puppy/tools/scheduler_tools.py +412 -0
  278. code_puppy/tools/skills_tools.py +244 -0
  279. code_puppy/tools/subagent_context.py +158 -0
  280. code_puppy/tools/tools_content.py +51 -0
  281. code_puppy/tools/universal_constructor.py +889 -0
  282. code_puppy/uvx_detection.py +242 -0
  283. code_puppy/version_checker.py +82 -0
  284. codepp-0.0.437.dist-info/METADATA +766 -0
  285. codepp-0.0.437.dist-info/RECORD +288 -0
  286. codepp-0.0.437.dist-info/WHEEL +4 -0
  287. codepp-0.0.437.dist-info/entry_points.txt +3 -0
  288. codepp-0.0.437.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,518 @@
1
+ """Antigravity OAuth Plugin callbacks for Code Puppy CLI.
2
+
3
+ Provides OAuth authentication for Antigravity models and registers
4
+ the 'antigravity' model type handler.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ import threading
11
+ import time
12
+ from http.server import BaseHTTPRequestHandler, HTTPServer
13
+ from typing import Any, Dict, List, Optional, Tuple
14
+ from urllib.parse import parse_qs, urlparse
15
+
16
+ from code_puppy.callbacks import register_callback
17
+ from code_puppy.messaging import emit_error, emit_info, emit_success, emit_warning
18
+ from code_puppy.model_switching import set_model_and_reload_agent
19
+
20
+ from ..oauth_puppy_html import oauth_failure_html, oauth_success_html
21
+ from .accounts import AccountManager
22
+ from .config import (
23
+ ANTIGRAVITY_OAUTH_CONFIG,
24
+ get_accounts_storage_path,
25
+ get_token_storage_path,
26
+ )
27
+ from .constants import ANTIGRAVITY_MODELS
28
+ from .oauth import (
29
+ TokenExchangeSuccess,
30
+ assign_redirect_uri,
31
+ build_authorization_url,
32
+ exchange_code_for_tokens,
33
+ fetch_antigravity_status,
34
+ prepare_oauth_context,
35
+ )
36
+ from .storage import clear_accounts
37
+ from .token import is_token_expired, refresh_access_token
38
+ from .transport import create_antigravity_client
39
+ from .utils import (
40
+ add_models_to_config,
41
+ load_antigravity_models,
42
+ load_stored_tokens,
43
+ reload_current_agent,
44
+ remove_antigravity_models,
45
+ save_tokens,
46
+ )
47
+
48
+ logger = logging.getLogger(__name__)
49
+
50
+
51
+ class _OAuthResult:
52
+ def __init__(self) -> None:
53
+ self.code: Optional[str] = None
54
+ self.state: Optional[str] = None
55
+ self.error: Optional[str] = None
56
+
57
+
58
+ class _CallbackHandler(BaseHTTPRequestHandler):
59
+ result: _OAuthResult
60
+ received_event: threading.Event
61
+ redirect_uri: str
62
+
63
+ def do_GET(self) -> None: # noqa: N802
64
+ logger.info("Callback received: path=%s", self.path)
65
+ parsed = urlparse(self.path)
66
+ params: Dict[str, List[str]] = parse_qs(parsed.query)
67
+
68
+ code = params.get("code", [None])[0]
69
+ state = params.get("state", [None])[0]
70
+
71
+ if code and state:
72
+ self.result.code = code
73
+ self.result.state = state
74
+ success_html = oauth_success_html(
75
+ "Antigravity",
76
+ "You're connected to Antigravity! 🚀 Gemini & Claude models are now available.",
77
+ )
78
+ self._write_response(200, success_html)
79
+ else:
80
+ self.result.error = "Missing code or state"
81
+ failure_html = oauth_failure_html(
82
+ "Antigravity",
83
+ "Missing code or state parameter 🥺",
84
+ )
85
+ self._write_response(400, failure_html)
86
+
87
+ self.received_event.set()
88
+
89
+ def log_message(self, log_format: str, *args: Any) -> None:
90
+ return
91
+
92
+ def _write_response(self, status: int, body: str) -> None:
93
+ self.send_response(status)
94
+ self.send_header("Content-Type", "text/html; charset=utf-8")
95
+ self.end_headers()
96
+ self.wfile.write(body.encode("utf-8"))
97
+
98
+
99
+ def _start_callback_server(
100
+ context: Any,
101
+ ) -> Optional[Tuple[HTTPServer, _OAuthResult, threading.Event, str]]:
102
+ """Start local HTTP server for OAuth callback."""
103
+ port_range = ANTIGRAVITY_OAUTH_CONFIG["callback_port_range"]
104
+
105
+ for port in range(port_range[0], port_range[1] + 1):
106
+ try:
107
+ server = HTTPServer(("localhost", port), _CallbackHandler)
108
+ redirect_uri = assign_redirect_uri(context, port)
109
+ result = _OAuthResult()
110
+ event = threading.Event()
111
+ _CallbackHandler.result = result
112
+ _CallbackHandler.received_event = event
113
+ _CallbackHandler.redirect_uri = redirect_uri
114
+
115
+ def run_server(server=server) -> None:
116
+ with server:
117
+ server.serve_forever()
118
+
119
+ threading.Thread(target=run_server, daemon=True).start()
120
+ return server, result, event, redirect_uri
121
+ except OSError:
122
+ continue
123
+
124
+ emit_error("Could not start OAuth callback server; all candidate ports are in use")
125
+ return None
126
+
127
+
128
+ def _await_callback(context: Any) -> Optional[Tuple[str, str, str]]:
129
+ """Wait for OAuth callback and return (code, state, redirect_uri)."""
130
+ timeout = ANTIGRAVITY_OAUTH_CONFIG["callback_timeout"]
131
+
132
+ started = _start_callback_server(context)
133
+ if not started:
134
+ return None
135
+
136
+ server, result, event, redirect_uri = started
137
+
138
+ auth_url = build_authorization_url(context)
139
+
140
+ try:
141
+ import webbrowser
142
+
143
+ from code_puppy.tools.common import should_suppress_browser
144
+
145
+ if should_suppress_browser():
146
+ emit_info(
147
+ "[HEADLESS MODE] Would normally open browser for Antigravity OAuth…"
148
+ )
149
+ emit_info(f"In normal mode, would visit: {auth_url}")
150
+ else:
151
+ emit_info("🌐 Opening browser for Google OAuth…")
152
+ webbrowser.open(auth_url)
153
+ emit_info(f"If it doesn't open automatically, visit:\n{auth_url}")
154
+ except Exception as exc:
155
+ emit_warning(f"Failed to open browser: {exc}")
156
+ emit_info(f"Please open manually: {auth_url}")
157
+
158
+ emit_info(f"⏳ Waiting for callback on {redirect_uri}")
159
+
160
+ if not event.wait(timeout=timeout):
161
+ emit_error("OAuth callback timed out. Please try again.")
162
+ server.shutdown()
163
+ return None
164
+
165
+ server.shutdown()
166
+
167
+ if result.error:
168
+ emit_error(f"OAuth callback error: {result.error}")
169
+ return None
170
+
171
+ return result.code, result.state, redirect_uri
172
+
173
+
174
+ def _perform_authentication(
175
+ add_account: bool = False,
176
+ reload_agent: bool = True,
177
+ ) -> bool:
178
+ """Run the OAuth authentication flow.
179
+
180
+ Args:
181
+ add_account: Whether to add a new account to the pool.
182
+ reload_agent: Whether to reload the current agent after auth.
183
+ """
184
+ context = prepare_oauth_context()
185
+ callback_result = _await_callback(context)
186
+
187
+ if not callback_result:
188
+ return False
189
+
190
+ code, state, redirect_uri = callback_result
191
+
192
+ emit_info("🔄 Exchanging authorization code for tokens…")
193
+ result = exchange_code_for_tokens(code, state, redirect_uri)
194
+
195
+ if not isinstance(result, TokenExchangeSuccess):
196
+ emit_error(f"Token exchange failed: {result.error}")
197
+ return False
198
+
199
+ # Save tokens
200
+ tokens = {
201
+ "access_token": result.access_token,
202
+ "refresh_token": result.refresh_token,
203
+ "expires_at": result.expires_at,
204
+ "email": result.email,
205
+ "project_id": result.project_id,
206
+ }
207
+
208
+ if not save_tokens(tokens):
209
+ emit_error("Failed to save tokens locally. Check file permissions.")
210
+ return False
211
+
212
+ # Handle multi-account
213
+ manager = AccountManager.load_from_disk(result.refresh_token)
214
+
215
+ if add_account or manager.account_count == 0:
216
+ manager.add_account(
217
+ refresh_token=result.refresh_token,
218
+ email=result.email,
219
+ project_id=result.project_id,
220
+ )
221
+ manager.save_to_disk()
222
+
223
+ if add_account:
224
+ emit_success(f"✅ Added account: {result.email or 'Unknown'}")
225
+ emit_info(f"📊 Total accounts: {manager.account_count}")
226
+
227
+ if result.email:
228
+ emit_success(f"🎉 Authenticated as {result.email}!")
229
+ else:
230
+ emit_success("🎉 Antigravity OAuth authentication successful!")
231
+
232
+ # Add models
233
+ emit_info("📦 Configuring available models…")
234
+ if add_models_to_config(result.access_token, result.project_id):
235
+ model_count = len(ANTIGRAVITY_MODELS)
236
+ emit_success(f"✅ {model_count} Antigravity models configured!")
237
+ emit_info(
238
+ " Use the `antigravity-` prefix (e.g., antigravity-gemini-3-pro-high)"
239
+ )
240
+ else:
241
+ emit_warning("Failed to configure models. Try running /antigravity-auth again.")
242
+
243
+ if reload_agent:
244
+ reload_current_agent()
245
+ return True
246
+
247
+
248
+ def _custom_help() -> List[Tuple[str, str]]:
249
+ """Return help entries for Antigravity commands."""
250
+ return [
251
+ (
252
+ "antigravity-auth",
253
+ "Authenticate with Google/Antigravity for Gemini & Claude models",
254
+ ),
255
+ (
256
+ "antigravity-add",
257
+ "Add another Google account for load balancing",
258
+ ),
259
+ (
260
+ "antigravity-status",
261
+ "Check authentication status and account pool",
262
+ ),
263
+ (
264
+ "antigravity-logout",
265
+ "Remove all Antigravity OAuth tokens and models",
266
+ ),
267
+ ]
268
+
269
+
270
+ def _handle_status() -> None:
271
+ """Handle /antigravity-status command."""
272
+ tokens = load_stored_tokens()
273
+
274
+ if not tokens or not tokens.get("access_token"):
275
+ emit_warning("🔓 Antigravity: Not authenticated")
276
+ emit_info("Run /antigravity-auth to sign in with Google")
277
+ return
278
+
279
+ emit_success("🔐 Antigravity: Authenticated")
280
+
281
+ # Show email if available
282
+ if tokens.get("email"):
283
+ emit_info(f" Primary account: {tokens['email']}")
284
+
285
+ # Show token expiry
286
+ expires_at = tokens.get("expires_at")
287
+ if expires_at:
288
+ remaining = max(0, int(expires_at - time.time()))
289
+ hours, remainder = divmod(remaining, 3600)
290
+ minutes = remainder // 60
291
+ emit_info(f" Token expires in: ~{hours}h {minutes}m")
292
+
293
+ # Fetch tier/quota status from API
294
+ emit_info("\n📊 Fetching tier status...")
295
+ status = fetch_antigravity_status(tokens.get("access_token", ""))
296
+
297
+ if status.error:
298
+ emit_warning(f" Could not fetch status: {status.error}")
299
+ else:
300
+ # Show tier info
301
+ tier_display = {
302
+ "free-tier": "Free Tier (limited)",
303
+ "standard-tier": "Standard Tier (full access)",
304
+ }
305
+ current = tier_display.get(
306
+ status.current_tier, status.current_tier or "Unknown"
307
+ )
308
+ emit_info(f" Current tier: {current}")
309
+
310
+ if status.project_id:
311
+ emit_info(f" Project ID: {status.project_id}")
312
+
313
+ if status.allowed_tiers:
314
+ available = ", ".join(status.allowed_tiers)
315
+ emit_info(f" Available tiers: {available}")
316
+
317
+ # Show account pool
318
+ manager = AccountManager.load_from_disk()
319
+ if manager.account_count > 1:
320
+ emit_info(f"\n📊 Account Pool: {manager.account_count} accounts")
321
+ for acc in manager.get_accounts_snapshot():
322
+ email_str = acc.email or "Unknown"
323
+ limits = []
324
+ if acc.rate_limit_reset_times:
325
+ for key, reset_time in acc.rate_limit_reset_times.items():
326
+ if reset_time > time.time() * 1000:
327
+ wait_sec = int((reset_time - time.time() * 1000) / 1000)
328
+ limits.append(f"{key}: {wait_sec}s")
329
+
330
+ status = f" • {email_str}"
331
+ if limits:
332
+ status += f" (rate-limited: {', '.join(limits)})"
333
+ emit_info(status)
334
+
335
+ # Show configured models
336
+ models = load_antigravity_models()
337
+ antigravity_models = [
338
+ name
339
+ for name, cfg in models.items()
340
+ if cfg.get("oauth_source") == "antigravity-plugin"
341
+ ]
342
+
343
+ if antigravity_models:
344
+ emit_info(f"\n🎯 Configured models: {len(antigravity_models)}")
345
+ # Group by family
346
+ gemini = [m for m in antigravity_models if "gemini" in m]
347
+ claude = [m for m in antigravity_models if "claude" in m]
348
+ other = [m for m in antigravity_models if m not in gemini and m not in claude]
349
+
350
+ if gemini:
351
+ emit_info(f" Gemini: {', '.join(sorted(gemini))}")
352
+ if claude:
353
+ emit_info(f" Claude: {', '.join(sorted(claude))}")
354
+ if other:
355
+ emit_info(f" Other: {', '.join(sorted(other))}")
356
+ else:
357
+ emit_warning("No Antigravity models configured")
358
+
359
+
360
+ def _handle_logout() -> None:
361
+ """Handle /antigravity-logout command."""
362
+ # Remove tokens
363
+ token_path = get_token_storage_path()
364
+ if token_path.exists():
365
+ token_path.unlink()
366
+ emit_info("✓ Removed OAuth tokens")
367
+
368
+ # Remove accounts
369
+ accounts_path = get_accounts_storage_path()
370
+ if accounts_path.exists():
371
+ clear_accounts()
372
+ emit_info("✓ Removed account pool")
373
+
374
+ # Remove models
375
+ removed = remove_antigravity_models()
376
+ if removed:
377
+ emit_info(f"✓ Removed {removed} Antigravity models")
378
+
379
+ emit_success("👋 Antigravity logout complete")
380
+
381
+
382
+ def _handle_custom_command(command: str, name: str) -> Optional[bool]:
383
+ """Handle Antigravity custom commands."""
384
+ if not name:
385
+ return None
386
+
387
+ if name == "antigravity-auth":
388
+ emit_info("🚀 Starting Antigravity OAuth authentication…")
389
+ tokens = load_stored_tokens()
390
+ if tokens and tokens.get("access_token"):
391
+ emit_warning(
392
+ "Existing tokens found. This will refresh your authentication."
393
+ )
394
+
395
+ if _perform_authentication(reload_agent=False):
396
+ set_model_and_reload_agent("antigravity-gemini-3-pro-high")
397
+ return True
398
+
399
+ if name == "antigravity-add":
400
+ emit_info("➕ Adding another Google account…")
401
+ manager = AccountManager.load_from_disk()
402
+ emit_info(f"Current accounts: {manager.account_count}")
403
+ _perform_authentication(add_account=True)
404
+ return True
405
+
406
+ if name == "antigravity-status":
407
+ _handle_status()
408
+ return True
409
+
410
+ if name == "antigravity-logout":
411
+ _handle_logout()
412
+ return True
413
+
414
+ return None
415
+
416
+
417
+ def _create_antigravity_model(model_name: str, model_config: Dict, config: Dict) -> Any:
418
+ """Create an Antigravity model instance.
419
+
420
+ This handler is registered via the 'register_model_type' callback to handle
421
+ models with type='antigravity'.
422
+ """
423
+ from code_puppy.gemini_model import GeminiModel
424
+ from code_puppy.model_factory import get_custom_config
425
+
426
+ # Try to import custom model for thinking signatures
427
+ try:
428
+ from .antigravity_model import AntigravityModel
429
+ except ImportError:
430
+ AntigravityModel = None # type: ignore
431
+
432
+ url, headers, verify, api_key = get_custom_config(model_config)
433
+ if not api_key:
434
+ emit_warning(
435
+ f"API key is not set for Antigravity endpoint; skipping model '{model_config.get('name')}'."
436
+ )
437
+ return None
438
+
439
+ # Get fresh access token (refresh if needed)
440
+ tokens = load_stored_tokens()
441
+ if not tokens:
442
+ emit_warning("Antigravity tokens not found; run /antigravity-auth first.")
443
+ return None
444
+
445
+ access_token = tokens.get("access_token", "")
446
+ refresh_token = tokens.get("refresh_token", "")
447
+ expires_at = tokens.get("expires_at")
448
+
449
+ # Refresh if expired or about to expire (initial check)
450
+ if is_token_expired(expires_at):
451
+ new_tokens = refresh_access_token(refresh_token)
452
+ if new_tokens:
453
+ access_token = new_tokens.access_token
454
+ refresh_token = new_tokens.refresh_token
455
+ expires_at = new_tokens.expires_at
456
+ tokens["access_token"] = new_tokens.access_token
457
+ tokens["refresh_token"] = new_tokens.refresh_token
458
+ tokens["expires_at"] = new_tokens.expires_at
459
+ save_tokens(tokens)
460
+ else:
461
+ emit_warning(
462
+ "Failed to refresh Antigravity token; run /antigravity-auth again."
463
+ )
464
+ return None
465
+
466
+ # Callback to persist tokens when proactively refreshed during session
467
+ def on_token_refreshed(new_tokens: Any) -> None:
468
+ """Persist new tokens when proactively refreshed."""
469
+ try:
470
+ updated_tokens = load_stored_tokens() or {}
471
+ updated_tokens["access_token"] = new_tokens.access_token
472
+ updated_tokens["refresh_token"] = new_tokens.refresh_token
473
+ updated_tokens["expires_at"] = new_tokens.expires_at
474
+ save_tokens(updated_tokens)
475
+ logger.debug("Persisted proactively refreshed Antigravity tokens")
476
+ except Exception as e:
477
+ logger.warning("Failed to persist refreshed tokens: %s", e)
478
+
479
+ project_id = tokens.get("project_id", model_config.get("project_id", ""))
480
+ client = create_antigravity_client(
481
+ access_token=access_token,
482
+ project_id=project_id,
483
+ model_name=model_config["name"],
484
+ base_url=url,
485
+ headers=headers,
486
+ refresh_token=refresh_token,
487
+ expires_at=expires_at,
488
+ on_token_refreshed=on_token_refreshed,
489
+ )
490
+
491
+ # Use custom model with direct httpx client
492
+ if AntigravityModel:
493
+ model = AntigravityModel(
494
+ model_name=model_config["name"],
495
+ api_key=api_key or "", # Antigravity uses OAuth, key may be empty
496
+ base_url=url,
497
+ http_client=client,
498
+ )
499
+ else:
500
+ model = GeminiModel(
501
+ model_name=model_config["name"],
502
+ api_key=api_key or "",
503
+ base_url=url,
504
+ http_client=client,
505
+ )
506
+
507
+ return model
508
+
509
+
510
+ def _register_model_types() -> List[Dict[str, Any]]:
511
+ """Register the antigravity model type handler."""
512
+ return [{"type": "antigravity", "handler": _create_antigravity_model}]
513
+
514
+
515
+ # Register callbacks
516
+ register_callback("custom_command_help", _custom_help)
517
+ register_callback("custom_command", _handle_custom_command)
518
+ register_callback("register_model_type", _register_model_types)