code-puppy 0.0.330__tar.gz → 0.0.331__tar.gz

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 (166) hide show
  1. {code_puppy-0.0.330 → code_puppy-0.0.331}/PKG-INFO +1 -1
  2. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/cli_runner.py +3 -0
  3. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/plugins/claude_code_oauth/register_callbacks.py +15 -0
  4. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/plugins/claude_code_oauth/utils.py +202 -0
  5. {code_puppy-0.0.330 → code_puppy-0.0.331}/pyproject.toml +1 -1
  6. {code_puppy-0.0.330 → code_puppy-0.0.331}/.gitignore +0 -0
  7. {code_puppy-0.0.330 → code_puppy-0.0.331}/LICENSE +0 -0
  8. {code_puppy-0.0.330 → code_puppy-0.0.331}/README.md +0 -0
  9. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/__init__.py +0 -0
  10. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/__main__.py +0 -0
  11. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/agents/__init__.py +0 -0
  12. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/agents/agent_c_reviewer.py +0 -0
  13. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/agents/agent_code_puppy.py +0 -0
  14. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/agents/agent_code_reviewer.py +0 -0
  15. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/agents/agent_cpp_reviewer.py +0 -0
  16. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/agents/agent_creator_agent.py +0 -0
  17. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/agents/agent_golang_reviewer.py +0 -0
  18. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/agents/agent_javascript_reviewer.py +0 -0
  19. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/agents/agent_manager.py +0 -0
  20. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/agents/agent_planning.py +0 -0
  21. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/agents/agent_python_programmer.py +0 -0
  22. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/agents/agent_python_reviewer.py +0 -0
  23. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/agents/agent_qa_expert.py +0 -0
  24. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/agents/agent_qa_kitten.py +0 -0
  25. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/agents/agent_security_auditor.py +0 -0
  26. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/agents/agent_typescript_reviewer.py +0 -0
  27. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/agents/base_agent.py +0 -0
  28. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/agents/json_agent.py +0 -0
  29. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/agents/prompt_reviewer.py +0 -0
  30. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/callbacks.py +0 -0
  31. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/chatgpt_codex_client.py +0 -0
  32. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/claude_cache_client.py +0 -0
  33. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/__init__.py +0 -0
  34. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/add_model_menu.py +0 -0
  35. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/attachments.py +0 -0
  36. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/autosave_menu.py +0 -0
  37. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/colors_menu.py +0 -0
  38. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/command_handler.py +0 -0
  39. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/command_registry.py +0 -0
  40. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/config_commands.py +0 -0
  41. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/core_commands.py +0 -0
  42. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/diff_menu.py +0 -0
  43. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/file_path_completion.py +0 -0
  44. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/load_context_completion.py +0 -0
  45. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/mcp/__init__.py +0 -0
  46. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/mcp/add_command.py +0 -0
  47. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/mcp/base.py +0 -0
  48. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/mcp/catalog_server_installer.py +0 -0
  49. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/mcp/custom_server_form.py +0 -0
  50. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/mcp/custom_server_installer.py +0 -0
  51. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/mcp/edit_command.py +0 -0
  52. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/mcp/handler.py +0 -0
  53. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/mcp/help_command.py +0 -0
  54. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/mcp/install_command.py +0 -0
  55. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/mcp/install_menu.py +0 -0
  56. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/mcp/list_command.py +0 -0
  57. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/mcp/logs_command.py +0 -0
  58. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/mcp/remove_command.py +0 -0
  59. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/mcp/restart_command.py +0 -0
  60. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/mcp/search_command.py +0 -0
  61. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/mcp/start_all_command.py +0 -0
  62. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/mcp/start_command.py +0 -0
  63. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/mcp/status_command.py +0 -0
  64. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
  65. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/mcp/stop_command.py +0 -0
  66. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/mcp/test_command.py +0 -0
  67. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/mcp/utils.py +0 -0
  68. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
  69. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/mcp_completion.py +0 -0
  70. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/model_picker_completion.py +0 -0
  71. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/model_settings_menu.py +0 -0
  72. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/motd.py +0 -0
  73. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/pin_command_completion.py +0 -0
  74. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
  75. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/session_commands.py +0 -0
  76. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/command_line/utils.py +0 -0
  77. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/config.py +0 -0
  78. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/error_logging.py +0 -0
  79. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/gemini_code_assist.py +0 -0
  80. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/http_utils.py +0 -0
  81. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/keymap.py +0 -0
  82. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/main.py +0 -0
  83. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/mcp_/__init__.py +0 -0
  84. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/mcp_/async_lifecycle.py +0 -0
  85. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/mcp_/blocking_startup.py +0 -0
  86. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/mcp_/captured_stdio_server.py +0 -0
  87. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/mcp_/circuit_breaker.py +0 -0
  88. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/mcp_/config_wizard.py +0 -0
  89. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/mcp_/dashboard.py +0 -0
  90. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/mcp_/error_isolation.py +0 -0
  91. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/mcp_/examples/retry_example.py +0 -0
  92. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/mcp_/health_monitor.py +0 -0
  93. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/mcp_/managed_server.py +0 -0
  94. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/mcp_/manager.py +0 -0
  95. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/mcp_/mcp_logs.py +0 -0
  96. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/mcp_/registry.py +0 -0
  97. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/mcp_/retry_manager.py +0 -0
  98. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/mcp_/server_registry_catalog.py +0 -0
  99. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/mcp_/status_tracker.py +0 -0
  100. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/mcp_/system_tools.py +0 -0
  101. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/messaging/__init__.py +0 -0
  102. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/messaging/bus.py +0 -0
  103. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/messaging/commands.py +0 -0
  104. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/messaging/markdown_patches.py +0 -0
  105. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/messaging/message_queue.py +0 -0
  106. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/messaging/messages.py +0 -0
  107. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/messaging/queue_console.py +0 -0
  108. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/messaging/renderers.py +0 -0
  109. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/messaging/rich_renderer.py +0 -0
  110. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/messaging/spinner/__init__.py +0 -0
  111. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/messaging/spinner/console_spinner.py +0 -0
  112. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/messaging/spinner/spinner_base.py +0 -0
  113. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/model_factory.py +0 -0
  114. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/model_utils.py +0 -0
  115. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/models.json +0 -0
  116. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/models_dev_api.json +0 -0
  117. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/models_dev_parser.py +0 -0
  118. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/plugins/__init__.py +0 -0
  119. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/plugins/chatgpt_oauth/__init__.py +0 -0
  120. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/plugins/chatgpt_oauth/config.py +0 -0
  121. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/plugins/chatgpt_oauth/oauth_flow.py +0 -0
  122. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/plugins/chatgpt_oauth/register_callbacks.py +0 -0
  123. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/plugins/chatgpt_oauth/test_plugin.py +0 -0
  124. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/plugins/chatgpt_oauth/utils.py +0 -0
  125. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/plugins/claude_code_oauth/README.md +0 -0
  126. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/plugins/claude_code_oauth/SETUP.md +0 -0
  127. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/plugins/claude_code_oauth/__init__.py +0 -0
  128. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/plugins/claude_code_oauth/config.py +0 -0
  129. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/plugins/claude_code_oauth/test_plugin.py +0 -0
  130. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/plugins/customizable_commands/__init__.py +0 -0
  131. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/plugins/customizable_commands/register_callbacks.py +0 -0
  132. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/plugins/example_custom_command/README.md +0 -0
  133. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/plugins/example_custom_command/register_callbacks.py +0 -0
  134. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/plugins/file_permission_handler/__init__.py +0 -0
  135. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/plugins/file_permission_handler/register_callbacks.py +0 -0
  136. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/plugins/oauth_puppy_html.py +0 -0
  137. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/plugins/shell_safety/__init__.py +0 -0
  138. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/plugins/shell_safety/agent_shell_safety.py +0 -0
  139. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/plugins/shell_safety/command_cache.py +0 -0
  140. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/plugins/shell_safety/register_callbacks.py +0 -0
  141. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/prompts/codex_system_prompt.md +0 -0
  142. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/pydantic_patches.py +0 -0
  143. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/reopenable_async_client.py +0 -0
  144. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/round_robin_model.py +0 -0
  145. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/session_storage.py +0 -0
  146. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/status_display.py +0 -0
  147. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/summarization_agent.py +0 -0
  148. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/terminal_utils.py +0 -0
  149. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/tools/__init__.py +0 -0
  150. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/tools/agent_tools.py +0 -0
  151. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/tools/browser/__init__.py +0 -0
  152. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/tools/browser/browser_control.py +0 -0
  153. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/tools/browser/browser_interactions.py +0 -0
  154. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/tools/browser/browser_locators.py +0 -0
  155. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/tools/browser/browser_navigation.py +0 -0
  156. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/tools/browser/browser_screenshot.py +0 -0
  157. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/tools/browser/browser_scripts.py +0 -0
  158. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/tools/browser/browser_workflows.py +0 -0
  159. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/tools/browser/camoufox_manager.py +0 -0
  160. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/tools/browser/vqa_agent.py +0 -0
  161. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/tools/command_runner.py +0 -0
  162. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/tools/common.py +0 -0
  163. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/tools/file_modifications.py +0 -0
  164. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/tools/file_operations.py +0 -0
  165. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/tools/tools_content.py +0 -0
  166. {code_puppy-0.0.330 → code_puppy-0.0.331}/code_puppy/version_checker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-puppy
3
- Version: 0.0.330
3
+ Version: 0.0.331
4
4
  Summary: Code generation agent
5
5
  Project-URL: repository, https://github.com/mpfaffenberger/code_puppy
6
6
  Project-URL: HomePage, https://github.com/mpfaffenberger/code_puppy
@@ -711,6 +711,9 @@ async def run_prompt_with_attachments(
711
711
  attachments = [attachment.content for attachment in processed_prompt.attachments]
712
712
  link_attachments = [link.url_part for link in processed_prompt.link_attachments]
713
713
 
714
+ # Trigger invoke_agent callbacks (e.g., Claude Code OAuth token refresh)
715
+ await callbacks.on_invoke_agent()
716
+
714
717
  # IMPORTANT: Set the shared console on the agent so that streaming output
715
718
  # uses the same console as the spinner. This prevents Live display conflicts
716
719
  # that cause line duplication during markdown streaming.
@@ -25,6 +25,7 @@ from .utils import (
25
25
  fetch_claude_code_models,
26
26
  load_claude_models_filtered,
27
27
  load_stored_tokens,
28
+ maybe_refresh_token,
28
29
  prepare_oauth_context,
29
30
  remove_claude_code_models,
30
31
  save_tokens,
@@ -302,5 +303,19 @@ def _handle_custom_command(command: str, name: str) -> Optional[bool]:
302
303
  return None
303
304
 
304
305
 
306
+ def _on_invoke_agent(*args, **kwargs) -> None:
307
+ """Called before each agent invocation.
308
+
309
+ Checks if 30 minutes have passed since last token refresh and
310
+ refreshes the Claude Code OAuth token if needed.
311
+ """
312
+ try:
313
+ if maybe_refresh_token():
314
+ logger.debug("Token refresh check completed")
315
+ except Exception as exc: # pragma: no cover - defensive
316
+ logger.warning("Token refresh check failed: %s", exc)
317
+
318
+
305
319
  register_callback("custom_command_help", _custom_help)
306
320
  register_callback("custom_command", _handle_custom_command)
321
+ register_callback("invoke_agent", _on_invoke_agent)
@@ -23,6 +23,10 @@ from .config import (
23
23
 
24
24
  logger = logging.getLogger(__name__)
25
25
 
26
+ # Token refresh tracking
27
+ _last_refresh_time: float = 0.0
28
+ REFRESH_INTERVAL_SECONDS: float = 30 * 60 # 30 minutes
29
+
26
30
 
27
31
  @dataclass
28
32
  class OAuthContext:
@@ -397,3 +401,201 @@ def remove_claude_code_models() -> int:
397
401
  except Exception as exc: # pragma: no cover - defensive logging
398
402
  logger.error("Error removing Claude Code models: %s", exc)
399
403
  return 0
404
+
405
+
406
+ def _update_model_tokens(new_access_token: str) -> bool:
407
+ """Update all Claude Code models with the new access token.
408
+
409
+ Args:
410
+ new_access_token: The new access token to set
411
+
412
+ Returns:
413
+ True if successful, False otherwise
414
+ """
415
+ try:
416
+ claude_models = load_claude_models()
417
+ if not claude_models:
418
+ logger.debug("No models to update")
419
+ return True
420
+
421
+ updated = 0
422
+ for _model_name, config in claude_models.items():
423
+ if config.get("oauth_source") == "claude-code-plugin":
424
+ if (
425
+ "custom_endpoint" in config
426
+ and "api_key" in config["custom_endpoint"]
427
+ ):
428
+ config["custom_endpoint"]["api_key"] = new_access_token
429
+ updated += 1
430
+
431
+ if updated > 0:
432
+ if save_claude_models(claude_models):
433
+ logger.info("Updated %s model configurations with new token", updated)
434
+ return True
435
+ else:
436
+ logger.error("Failed to save updated model configurations")
437
+ return False
438
+
439
+ return True
440
+
441
+ except Exception as exc: # pragma: no cover - defensive logging
442
+ logger.error("Error updating model tokens: %s", exc)
443
+ return False
444
+
445
+
446
+ def refresh_access_token() -> Optional[str]:
447
+ """Refresh the access token using the refresh token.
448
+
449
+ Returns:
450
+ New access token if successful, None otherwise
451
+ """
452
+ try:
453
+ tokens = load_stored_tokens()
454
+ if not tokens:
455
+ logger.debug("No stored tokens found for refresh")
456
+ return None
457
+
458
+ if "refresh_token" not in tokens:
459
+ logger.debug("No refresh token available")
460
+ return None
461
+
462
+ refresh_token = tokens["refresh_token"]
463
+
464
+ # Prepare refresh request
465
+ payload = {
466
+ "grant_type": "refresh_token",
467
+ "client_id": CLAUDE_CODE_OAUTH_CONFIG["client_id"],
468
+ "refresh_token": refresh_token,
469
+ }
470
+
471
+ headers = {
472
+ "Content-Type": "application/json",
473
+ "Accept": "application/json",
474
+ "anthropic-beta": "oauth-2025-04-20",
475
+ }
476
+
477
+ logger.info("Refreshing Claude Code access token...")
478
+ response = requests.post(
479
+ CLAUDE_CODE_OAUTH_CONFIG["token_url"],
480
+ json=payload,
481
+ headers=headers,
482
+ timeout=30,
483
+ )
484
+
485
+ if response.status_code == 200:
486
+ token_data = response.json()
487
+
488
+ # Update tokens with new access token and expiry
489
+ new_access_token = token_data.get("access_token")
490
+ if not new_access_token:
491
+ logger.error("No access_token in refresh response")
492
+ return None
493
+
494
+ # Update stored tokens
495
+ tokens["access_token"] = new_access_token
496
+
497
+ # Update expiry if provided
498
+ if "expires_in" in token_data:
499
+ tokens["expires_at"] = time.time() + token_data["expires_in"]
500
+
501
+ # Update refresh token if a new one was provided
502
+ if "refresh_token" in token_data:
503
+ tokens["refresh_token"] = token_data["refresh_token"]
504
+
505
+ # Save updated tokens
506
+ if save_tokens(tokens):
507
+ logger.info("Claude Code access token refreshed successfully")
508
+
509
+ # Update model configurations with new token
510
+ _update_model_tokens(new_access_token)
511
+
512
+ return new_access_token
513
+ else:
514
+ logger.error("Failed to save refreshed tokens")
515
+ return None
516
+ else:
517
+ logger.warning(
518
+ "Token refresh failed: %s - %s",
519
+ response.status_code,
520
+ response.text,
521
+ )
522
+ return None
523
+
524
+ except Exception as exc: # pragma: no cover - defensive logging
525
+ logger.error("Error refreshing access token: %s", exc)
526
+ return None
527
+
528
+
529
+ def _is_using_claude_code_model() -> bool:
530
+ """Check if the current agent is using a Claude Code OAuth model.
531
+
532
+ Returns:
533
+ True if currently using a claude-code-* model, False otherwise
534
+ """
535
+ try:
536
+ from code_puppy.agents import get_current_agent
537
+ from code_puppy.model_utils import is_claude_code_model
538
+
539
+ agent = get_current_agent()
540
+ if agent is None:
541
+ return False
542
+
543
+ model_name = agent.get_model_name()
544
+ if not model_name:
545
+ return False
546
+
547
+ return is_claude_code_model(model_name)
548
+ except Exception as exc:
549
+ logger.debug("Could not determine current model: %s", exc)
550
+ return False
551
+
552
+
553
+ def maybe_refresh_token() -> bool:
554
+ """Refresh the token if 30 minutes have passed since last refresh.
555
+
556
+ This function is designed to be called on every prompt, but will only
557
+ actually refresh the token if:
558
+ 1. The current model is a Claude Code OAuth model (starts with 'claude-code-')
559
+ 2. REFRESH_INTERVAL_SECONDS (30 min) has passed since last refresh
560
+
561
+ Returns:
562
+ True if refresh was attempted (regardless of success), False if skipped
563
+ """
564
+ global _last_refresh_time
565
+
566
+ # Only refresh if we're actually using a Claude Code model
567
+ if not _is_using_claude_code_model():
568
+ return False
569
+
570
+ # Check if we have tokens at all
571
+ tokens = load_stored_tokens()
572
+ if not tokens or "refresh_token" not in tokens:
573
+ return False
574
+
575
+ current_time = time.time()
576
+ time_since_last = current_time - _last_refresh_time
577
+
578
+ if time_since_last < REFRESH_INTERVAL_SECONDS:
579
+ logger.debug(
580
+ "Skipping token refresh, %.1f minutes until next refresh",
581
+ (REFRESH_INTERVAL_SECONDS - time_since_last) / 60,
582
+ )
583
+ return False
584
+
585
+ logger.info(
586
+ "Token refresh interval reached (%.1f min since last refresh)",
587
+ time_since_last / 60,
588
+ )
589
+
590
+ # Attempt refresh
591
+ result = refresh_access_token()
592
+ if result:
593
+ _last_refresh_time = current_time
594
+ logger.info("Token refresh successful, next refresh in 30 minutes")
595
+ else:
596
+ # Even on failure, update the timestamp to avoid hammering the API
597
+ # We'll retry on the next 30-min interval
598
+ _last_refresh_time = current_time
599
+ logger.warning("Token refresh failed, will retry in 30 minutes")
600
+
601
+ return True
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "code-puppy"
7
- version = "0.0.330"
7
+ version = "0.0.331"
8
8
  description = "Code generation agent"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11,<3.14"
File without changes
File without changes
File without changes