code-puppy-core-plugins 0.0.2__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 (313) hide show
  1. code_puppy_core_plugins/__init__.py +5 -0
  2. code_puppy_core_plugins/acp/README.md +266 -0
  3. code_puppy_core_plugins/acp/__init__.py +8 -0
  4. code_puppy_core_plugins/acp/agent.py +494 -0
  5. code_puppy_core_plugins/acp/bridge.py +309 -0
  6. code_puppy_core_plugins/acp/capabilities.py +73 -0
  7. code_puppy_core_plugins/acp/commands.py +116 -0
  8. code_puppy_core_plugins/acp/content.py +115 -0
  9. code_puppy_core_plugins/acp/io_delegation.py +231 -0
  10. code_puppy_core_plugins/acp/mcp_config.py +66 -0
  11. code_puppy_core_plugins/acp/permissions.py +159 -0
  12. code_puppy_core_plugins/acp/persistence.py +197 -0
  13. code_puppy_core_plugins/acp/register_callbacks.py +110 -0
  14. code_puppy_core_plugins/acp/replay.py +114 -0
  15. code_puppy_core_plugins/acp/session.py +329 -0
  16. code_puppy_core_plugins/acp/session_config.py +179 -0
  17. code_puppy_core_plugins/acp/state.py +135 -0
  18. code_puppy_core_plugins/agent_skills/__init__.py +22 -0
  19. code_puppy_core_plugins/agent_skills/config.py +208 -0
  20. code_puppy_core_plugins/agent_skills/discovery.py +337 -0
  21. code_puppy_core_plugins/agent_skills/downloader.py +392 -0
  22. code_puppy_core_plugins/agent_skills/enabled_skills.py +82 -0
  23. code_puppy_core_plugins/agent_skills/installer.py +22 -0
  24. code_puppy_core_plugins/agent_skills/metadata.py +219 -0
  25. code_puppy_core_plugins/agent_skills/prompt_builder.py +49 -0
  26. code_puppy_core_plugins/agent_skills/provider.py +52 -0
  27. code_puppy_core_plugins/agent_skills/register_callbacks.py +309 -0
  28. code_puppy_core_plugins/agent_skills/remote_catalog.py +322 -0
  29. code_puppy_core_plugins/agent_skills/skill_catalog.py +257 -0
  30. code_puppy_core_plugins/agent_skills/skill_commands.py +102 -0
  31. code_puppy_core_plugins/agent_skills/skills_install_menu.py +698 -0
  32. code_puppy_core_plugins/agent_skills/skills_menu.py +794 -0
  33. code_puppy_core_plugins/aws_bedrock/__init__.py +14 -0
  34. code_puppy_core_plugins/aws_bedrock/config.py +101 -0
  35. code_puppy_core_plugins/aws_bedrock/register_callbacks.py +243 -0
  36. code_puppy_core_plugins/aws_bedrock/utils.py +186 -0
  37. code_puppy_core_plugins/azure_foundry/README.md +238 -0
  38. code_puppy_core_plugins/azure_foundry/__init__.py +15 -0
  39. code_puppy_core_plugins/azure_foundry/config.py +126 -0
  40. code_puppy_core_plugins/azure_foundry/discovery.py +189 -0
  41. code_puppy_core_plugins/azure_foundry/register_callbacks.py +506 -0
  42. code_puppy_core_plugins/azure_foundry/token.py +182 -0
  43. code_puppy_core_plugins/azure_foundry/utils.py +375 -0
  44. code_puppy_core_plugins/btw/__init__.py +1 -0
  45. code_puppy_core_plugins/btw/inline_view.py +135 -0
  46. code_puppy_core_plugins/btw/register_callbacks.py +106 -0
  47. code_puppy_core_plugins/btw/side_query.py +91 -0
  48. code_puppy_core_plugins/chatgpt_oauth/IMAGEGEN_SKILL.md +83 -0
  49. code_puppy_core_plugins/chatgpt_oauth/__init__.py +8 -0
  50. code_puppy_core_plugins/chatgpt_oauth/config.py +52 -0
  51. code_puppy_core_plugins/chatgpt_oauth/image_generation.py +253 -0
  52. code_puppy_core_plugins/chatgpt_oauth/image_tool.py +70 -0
  53. code_puppy_core_plugins/chatgpt_oauth/oauth_flow.py +461 -0
  54. code_puppy_core_plugins/chatgpt_oauth/register_callbacks.py +288 -0
  55. code_puppy_core_plugins/chatgpt_oauth/test_plugin.py +663 -0
  56. code_puppy_core_plugins/chatgpt_oauth/usage.py +113 -0
  57. code_puppy_core_plugins/chatgpt_oauth/utils.py +534 -0
  58. code_puppy_core_plugins/claude_code_hooks/__init__.py +1 -0
  59. code_puppy_core_plugins/claude_code_hooks/config.py +137 -0
  60. code_puppy_core_plugins/claude_code_hooks/register_callbacks.py +383 -0
  61. code_puppy_core_plugins/claude_code_oauth/README.md +167 -0
  62. code_puppy_core_plugins/claude_code_oauth/SETUP.md +93 -0
  63. code_puppy_core_plugins/claude_code_oauth/__init__.py +25 -0
  64. code_puppy_core_plugins/claude_code_oauth/config.py +59 -0
  65. code_puppy_core_plugins/claude_code_oauth/fast_mode.py +122 -0
  66. code_puppy_core_plugins/claude_code_oauth/prompt_handler.py +64 -0
  67. code_puppy_core_plugins/claude_code_oauth/register_callbacks.py +719 -0
  68. code_puppy_core_plugins/claude_code_oauth/test_plugin.py +285 -0
  69. code_puppy_core_plugins/claude_code_oauth/token_refresh_heartbeat.py +240 -0
  70. code_puppy_core_plugins/claude_code_oauth/utils.py +663 -0
  71. code_puppy_core_plugins/code_puppy_agent/AGENTS_AND_TOOLS.md +155 -0
  72. code_puppy_core_plugins/code_puppy_agent/MODELS_AND_MCP.md +104 -0
  73. code_puppy_core_plugins/code_puppy_agent/PLUGINS_AND_CALLBACKS.md +147 -0
  74. code_puppy_core_plugins/code_puppy_agent/SESSIONS_AND_HISTORY.md +39 -0
  75. code_puppy_core_plugins/code_puppy_agent/SKILL.md +180 -0
  76. code_puppy_core_plugins/code_puppy_agent/SKILLS_SYSTEM.md +136 -0
  77. code_puppy_core_plugins/code_puppy_agent/SYSTEM_PROMPT_CONFIG_AND_I18N.md +160 -0
  78. code_puppy_core_plugins/code_puppy_agent/__init__.py +5 -0
  79. code_puppy_core_plugins/code_puppy_agent/register_callbacks.py +46 -0
  80. code_puppy_core_plugins/computer_use/NativeCapture.swift +123 -0
  81. code_puppy_core_plugins/computer_use/README.md +109 -0
  82. code_puppy_core_plugins/computer_use/__init__.py +1 -0
  83. code_puppy_core_plugins/computer_use/accessibility.py +163 -0
  84. code_puppy_core_plugins/computer_use/activation.py +75 -0
  85. code_puppy_core_plugins/computer_use/backend.py +584 -0
  86. code_puppy_core_plugins/computer_use/backend_types.py +5 -0
  87. code_puppy_core_plugins/computer_use/capture.py +110 -0
  88. code_puppy_core_plugins/computer_use/commands.py +62 -0
  89. code_puppy_core_plugins/computer_use/geometry.py +81 -0
  90. code_puppy_core_plugins/computer_use/inline_image.py +56 -0
  91. code_puppy_core_plugins/computer_use/keycodes.py +47 -0
  92. code_puppy_core_plugins/computer_use/policy.py +144 -0
  93. code_puppy_core_plugins/computer_use/register_callbacks.py +96 -0
  94. code_puppy_core_plugins/computer_use/safety.py +18 -0
  95. code_puppy_core_plugins/computer_use/settle.py +64 -0
  96. code_puppy_core_plugins/computer_use/state.py +86 -0
  97. code_puppy_core_plugins/computer_use/tools.py +469 -0
  98. code_puppy_core_plugins/context_indicator/__init__.py +14 -0
  99. code_puppy_core_plugins/context_indicator/register_callbacks.py +249 -0
  100. code_puppy_core_plugins/context_indicator/usage.py +40 -0
  101. code_puppy_core_plugins/copilot_auth/__init__.py +11 -0
  102. code_puppy_core_plugins/copilot_auth/config.py +93 -0
  103. code_puppy_core_plugins/copilot_auth/reasoning_client.py +409 -0
  104. code_puppy_core_plugins/copilot_auth/register_callbacks.py +460 -0
  105. code_puppy_core_plugins/copilot_auth/utils.py +587 -0
  106. code_puppy_core_plugins/customizable_commands/__init__.py +0 -0
  107. code_puppy_core_plugins/customizable_commands/register_callbacks.py +464 -0
  108. code_puppy_core_plugins/dbos_durable_exec/__init__.py +1 -0
  109. code_puppy_core_plugins/dbos_durable_exec/cancel.py +15 -0
  110. code_puppy_core_plugins/dbos_durable_exec/commands.py +31 -0
  111. code_puppy_core_plugins/dbos_durable_exec/config.py +31 -0
  112. code_puppy_core_plugins/dbos_durable_exec/lifecycle.py +83 -0
  113. code_puppy_core_plugins/dbos_durable_exec/register_callbacks.py +45 -0
  114. code_puppy_core_plugins/dbos_durable_exec/runtime.py +65 -0
  115. code_puppy_core_plugins/dbos_durable_exec/startup_lock.py +155 -0
  116. code_puppy_core_plugins/dbos_durable_exec/workflow_ids.py +16 -0
  117. code_puppy_core_plugins/dbos_durable_exec/wrapper.py +58 -0
  118. code_puppy_core_plugins/destructive_command_guard/AGENTS.md +79 -0
  119. code_puppy_core_plugins/destructive_command_guard/__init__.py +14 -0
  120. code_puppy_core_plugins/destructive_command_guard/detector.py +374 -0
  121. code_puppy_core_plugins/destructive_command_guard/register_callbacks.py +165 -0
  122. code_puppy_core_plugins/emoji_filter/__init__.py +12 -0
  123. code_puppy_core_plugins/emoji_filter/config.py +26 -0
  124. code_puppy_core_plugins/emoji_filter/register_callbacks.py +238 -0
  125. code_puppy_core_plugins/emoji_filter/stripper.py +45 -0
  126. code_puppy_core_plugins/empty_html_comment_filter/register_callbacks.py +116 -0
  127. code_puppy_core_plugins/example_custom_command/README.md +328 -0
  128. code_puppy_core_plugins/example_custom_command/register_callbacks.py +66 -0
  129. code_puppy_core_plugins/file_permission_handler/__init__.py +4 -0
  130. code_puppy_core_plugins/file_permission_handler/register_callbacks.py +575 -0
  131. code_puppy_core_plugins/flux_bootstrap/__init__.py +5 -0
  132. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/_docs/README.md +173 -0
  133. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/_docs/cheatsheet.md +86 -0
  134. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/_docs/pipeline.md +167 -0
  135. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/_docs/synopsis.md +392 -0
  136. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/about.md +51 -0
  137. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/address-feedback.md +129 -0
  138. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/ask.md +211 -0
  139. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/aug.md +181 -0
  140. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/auto-pilot.md +71 -0
  141. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/cheatsheet.md +16 -0
  142. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/commit.md +103 -0
  143. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/config.md +108 -0
  144. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/create-pr.md +195 -0
  145. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/exec.md +221 -0
  146. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/new.md +151 -0
  147. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/qa.md +208 -0
  148. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/rebase.md +180 -0
  149. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/review.md +304 -0
  150. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/split.md +120 -0
  151. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/squash-commits.md +179 -0
  152. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/status.md +46 -0
  153. code_puppy_core_plugins/flux_bootstrap/bundled/commands/flux/tests.md +155 -0
  154. code_puppy_core_plugins/flux_bootstrap/bundled/scripts/flux_about.py +154 -0
  155. code_puppy_core_plugins/flux_bootstrap/bundled/scripts/flux_cheatsheet.py +248 -0
  156. code_puppy_core_plugins/flux_bootstrap/bundled/scripts/flux_status.py +345 -0
  157. code_puppy_core_plugins/flux_bootstrap/installer.py +256 -0
  158. code_puppy_core_plugins/flux_bootstrap/register_callbacks.py +71 -0
  159. code_puppy_core_plugins/force_push_guard/__init__.py +5 -0
  160. code_puppy_core_plugins/force_push_guard/detector.py +96 -0
  161. code_puppy_core_plugins/force_push_guard/register_callbacks.py +161 -0
  162. code_puppy_core_plugins/fork/__init__.py +7 -0
  163. code_puppy_core_plugins/fork/register_callbacks.py +445 -0
  164. code_puppy_core_plugins/frontend_emitter/__init__.py +25 -0
  165. code_puppy_core_plugins/frontend_emitter/emitter.py +249 -0
  166. code_puppy_core_plugins/frontend_emitter/register_callbacks.py +412 -0
  167. code_puppy_core_plugins/frontend_emitter/session_context.py +50 -0
  168. code_puppy_core_plugins/grok_oauth/__init__.py +6 -0
  169. code_puppy_core_plugins/grok_oauth/config.py +49 -0
  170. code_puppy_core_plugins/grok_oauth/oauth_flow.py +213 -0
  171. code_puppy_core_plugins/grok_oauth/register_callbacks.py +132 -0
  172. code_puppy_core_plugins/grok_oauth/utils.py +195 -0
  173. code_puppy_core_plugins/herdr/README.md +168 -0
  174. code_puppy_core_plugins/herdr/__init__.py +9 -0
  175. code_puppy_core_plugins/herdr/client.py +339 -0
  176. code_puppy_core_plugins/herdr/register_callbacks.py +140 -0
  177. code_puppy_core_plugins/herdr/reporter.py +212 -0
  178. code_puppy_core_plugins/herdr/smoke.py +143 -0
  179. code_puppy_core_plugins/herdr/sources.py +133 -0
  180. code_puppy_core_plugins/hook_creator/__init__.py +1 -0
  181. code_puppy_core_plugins/hook_creator/register_callbacks.py +33 -0
  182. code_puppy_core_plugins/hook_manager/__init__.py +1 -0
  183. code_puppy_core_plugins/hook_manager/config.py +290 -0
  184. code_puppy_core_plugins/hook_manager/hooks_menu.py +571 -0
  185. code_puppy_core_plugins/hook_manager/register_callbacks.py +227 -0
  186. code_puppy_core_plugins/meta_oauth/__init__.py +1 -0
  187. code_puppy_core_plugins/meta_oauth/config.py +55 -0
  188. code_puppy_core_plugins/meta_oauth/oauth_flow.py +109 -0
  189. code_puppy_core_plugins/meta_oauth/register_callbacks.py +154 -0
  190. code_puppy_core_plugins/meta_oauth/utils.py +252 -0
  191. code_puppy_core_plugins/namespace_skill_search/README.md +118 -0
  192. code_puppy_core_plugins/namespace_skill_search/__init__.py +15 -0
  193. code_puppy_core_plugins/namespace_skill_search/namespaces.py +125 -0
  194. code_puppy_core_plugins/namespace_skill_search/register_callbacks.py +99 -0
  195. code_puppy_core_plugins/namespace_skill_search/search_tool.py +158 -0
  196. code_puppy_core_plugins/no_tools/__init__.py +0 -0
  197. code_puppy_core_plugins/no_tools/register_callbacks.py +42 -0
  198. code_puppy_core_plugins/oauth_pasteback.py +110 -0
  199. code_puppy_core_plugins/oauth_puppy_html.py +194 -0
  200. code_puppy_core_plugins/obsidian_agent/README.md +52 -0
  201. code_puppy_core_plugins/obsidian_agent/__init__.py +8 -0
  202. code_puppy_core_plugins/obsidian_agent/agent_obsidian.py +147 -0
  203. code_puppy_core_plugins/obsidian_agent/register_callbacks.py +13 -0
  204. code_puppy_core_plugins/ollama/__init__.py +1 -0
  205. code_puppy_core_plugins/ollama/register_callbacks.py +127 -0
  206. code_puppy_core_plugins/ollama_setup/__init__.py +5 -0
  207. code_puppy_core_plugins/ollama_setup/completer.py +38 -0
  208. code_puppy_core_plugins/ollama_setup/register_callbacks.py +419 -0
  209. code_puppy_core_plugins/plugin_list/__init__.py +0 -0
  210. code_puppy_core_plugins/plugin_list/plugin_contributions.py +348 -0
  211. code_puppy_core_plugins/plugin_list/plugin_meta.py +67 -0
  212. code_puppy_core_plugins/plugin_list/plugin_text_utils.py +173 -0
  213. code_puppy_core_plugins/plugin_list/plugins_menu.py +455 -0
  214. code_puppy_core_plugins/plugin_list/plugins_menu_layout.py +194 -0
  215. code_puppy_core_plugins/plugin_list/plugins_menu_render.py +431 -0
  216. code_puppy_core_plugins/plugin_list/project_trust_flow.py +162 -0
  217. code_puppy_core_plugins/plugin_list/register_callbacks.py +281 -0
  218. code_puppy_core_plugins/pop_command/__init__.py +1 -0
  219. code_puppy_core_plugins/pop_command/register_callbacks.py +191 -0
  220. code_puppy_core_plugins/prompt_newline/__init__.py +13 -0
  221. code_puppy_core_plugins/prompt_newline/config.py +21 -0
  222. code_puppy_core_plugins/prompt_newline/register_callbacks.py +163 -0
  223. code_puppy_core_plugins/prune/__init__.py +1 -0
  224. code_puppy_core_plugins/prune/prune_menu.py +308 -0
  225. code_puppy_core_plugins/prune/prune_model.py +517 -0
  226. code_puppy_core_plugins/prune/prune_render.py +354 -0
  227. code_puppy_core_plugins/prune/register_callbacks.py +388 -0
  228. code_puppy_core_plugins/puppy_kennel/README.md +203 -0
  229. code_puppy_core_plugins/puppy_kennel/__init__.py +6 -0
  230. code_puppy_core_plugins/puppy_kennel/commands.py +206 -0
  231. code_puppy_core_plugins/puppy_kennel/config.py +36 -0
  232. code_puppy_core_plugins/puppy_kennel/kennel.py +368 -0
  233. code_puppy_core_plugins/puppy_kennel/packer.py +152 -0
  234. code_puppy_core_plugins/puppy_kennel/recorder.py +83 -0
  235. code_puppy_core_plugins/puppy_kennel/register_callbacks.py +109 -0
  236. code_puppy_core_plugins/puppy_kennel/retriever.py +28 -0
  237. code_puppy_core_plugins/puppy_kennel/schema.py +78 -0
  238. code_puppy_core_plugins/puppy_kennel/state.py +68 -0
  239. code_puppy_core_plugins/puppy_kennel/tools.py +449 -0
  240. code_puppy_core_plugins/puppy_kennel/wings.py +110 -0
  241. code_puppy_core_plugins/puppy_spinner/README.md +93 -0
  242. code_puppy_core_plugins/puppy_spinner/__init__.py +11 -0
  243. code_puppy_core_plugins/puppy_spinner/builtin_frames.py +172 -0
  244. code_puppy_core_plugins/puppy_spinner/commands.py +116 -0
  245. code_puppy_core_plugins/puppy_spinner/picker.py +379 -0
  246. code_puppy_core_plugins/puppy_spinner/register_callbacks.py +193 -0
  247. code_puppy_core_plugins/puppy_spinner/spinners.py +444 -0
  248. code_puppy_core_plugins/quick_resume/__init__.py +0 -0
  249. code_puppy_core_plugins/quick_resume/register_callbacks.py +77 -0
  250. code_puppy_core_plugins/review_pr/__init__.py +1 -0
  251. code_puppy_core_plugins/review_pr/register_callbacks.py +162 -0
  252. code_puppy_core_plugins/shell_safety/__init__.py +6 -0
  253. code_puppy_core_plugins/shell_safety/agent_shell_safety.py +69 -0
  254. code_puppy_core_plugins/shell_safety/command_cache.py +156 -0
  255. code_puppy_core_plugins/shell_safety/register_callbacks.py +203 -0
  256. code_puppy_core_plugins/stack_dump/__init__.py +1 -0
  257. code_puppy_core_plugins/stack_dump/register_callbacks.py +79 -0
  258. code_puppy_core_plugins/statusline/__init__.py +14 -0
  259. code_puppy_core_plugins/statusline/config.py +68 -0
  260. code_puppy_core_plugins/statusline/payload.py +138 -0
  261. code_puppy_core_plugins/statusline/prompt_patch.py +76 -0
  262. code_puppy_core_plugins/statusline/register_callbacks.py +45 -0
  263. code_puppy_core_plugins/statusline/runner.py +122 -0
  264. code_puppy_core_plugins/statusline/statusline_command.py +197 -0
  265. code_puppy_core_plugins/steer_queue/__init__.py +10 -0
  266. code_puppy_core_plugins/steer_queue/queue_menu.py +444 -0
  267. code_puppy_core_plugins/steer_queue/register_callbacks.py +138 -0
  268. code_puppy_core_plugins/subagent_panel/README.md +102 -0
  269. code_puppy_core_plugins/subagent_panel/__init__.py +24 -0
  270. code_puppy_core_plugins/subagent_panel/coalesce_patch.py +242 -0
  271. code_puppy_core_plugins/subagent_panel/panel_render.py +145 -0
  272. code_puppy_core_plugins/subagent_panel/register_callbacks.py +535 -0
  273. code_puppy_core_plugins/subagent_panel/resume_repaint.py +112 -0
  274. code_puppy_core_plugins/subagent_panel/state.py +231 -0
  275. code_puppy_core_plugins/switch_agent_resume/__init__.py +5 -0
  276. code_puppy_core_plugins/switch_agent_resume/register_callbacks.py +314 -0
  277. code_puppy_core_plugins/theme/README.md +86 -0
  278. code_puppy_core_plugins/theme/__init__.py +14 -0
  279. code_puppy_core_plugins/theme/bundled_palettes.py +346 -0
  280. code_puppy_core_plugins/theme/content_styles.py +146 -0
  281. code_puppy_core_plugins/theme/osc_palette.py +178 -0
  282. code_puppy_core_plugins/theme/picker.py +358 -0
  283. code_puppy_core_plugins/theme/prompt_toolkit_theme.py +113 -0
  284. code_puppy_core_plugins/theme/register_callbacks.py +348 -0
  285. code_puppy_core_plugins/theme/rich_themes.py +202 -0
  286. code_puppy_core_plugins/theme/themes.py +720 -0
  287. code_puppy_core_plugins/timestamp_heartbeat/__init__.py +1 -0
  288. code_puppy_core_plugins/timestamp_heartbeat/register_callbacks.py +114 -0
  289. code_puppy_core_plugins/token_ratio_learner/__init__.py +5 -0
  290. code_puppy_core_plugins/token_ratio_learner/ratios.py +208 -0
  291. code_puppy_core_plugins/token_ratio_learner/register_callbacks.py +213 -0
  292. code_puppy_core_plugins/universal_constructor/__init__.py +13 -0
  293. code_puppy_core_plugins/universal_constructor/models.py +138 -0
  294. code_puppy_core_plugins/universal_constructor/provider.py +62 -0
  295. code_puppy_core_plugins/universal_constructor/register_callbacks.py +75 -0
  296. code_puppy_core_plugins/universal_constructor/registry.py +302 -0
  297. code_puppy_core_plugins/universal_constructor/sandbox.py +584 -0
  298. code_puppy_core_plugins/wide_completion_menu/__init__.py +6 -0
  299. code_puppy_core_plugins/wide_completion_menu/register_callbacks.py +165 -0
  300. code_puppy_core_plugins/wiggum/__init__.py +1 -0
  301. code_puppy_core_plugins/wiggum/goal_runs.py +91 -0
  302. code_puppy_core_plugins/wiggum/judge.py +299 -0
  303. code_puppy_core_plugins/wiggum/judge_config.py +249 -0
  304. code_puppy_core_plugins/wiggum/judges_menu.py +814 -0
  305. code_puppy_core_plugins/wiggum/register_callbacks.py +569 -0
  306. code_puppy_core_plugins/wiggum/state.py +89 -0
  307. code_puppy_core_plugins/yolo_cli/__init__.py +1 -0
  308. code_puppy_core_plugins/yolo_cli/register_callbacks.py +41 -0
  309. code_puppy_core_plugins-0.0.2.dist-info/METADATA +64 -0
  310. code_puppy_core_plugins-0.0.2.dist-info/RECORD +313 -0
  311. code_puppy_core_plugins-0.0.2.dist-info/WHEEL +4 -0
  312. code_puppy_core_plugins-0.0.2.dist-info/entry_points.txt +55 -0
  313. code_puppy_core_plugins-0.0.2.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,50 @@
1
+ """Session-id ContextVar used by the frontend_emitter plugin.
2
+
3
+ This module defines a single ``contextvars.ContextVar`` that carries
4
+ the *current agent invocation's* session identifier across the asyncio
5
+ call graph without requiring every callable to take an explicit kwarg.
6
+
7
+ The primary use case is letting embedders (e.g. a WebSocket backend
8
+ hosting multiple concurrent agent sessions) tag every event emitted
9
+ during an agent run with a session identifier, so subscribers can
10
+ filter events by session without monkey-patching the emitter.
11
+
12
+ Usage::
13
+
14
+ from code_puppy_core_plugins.frontend_emitter.session_context import (
15
+ current_emitter_session_id,
16
+ )
17
+
18
+ token = current_emitter_session_id.set("my-session-id")
19
+ try:
20
+ await some_agent_work() # any emit_event() inside picks up the sid
21
+ finally:
22
+ current_emitter_session_id.reset(token)
23
+
24
+ Because ``ContextVar`` values are copied into each new asyncio Task at
25
+ creation time, sessions are naturally isolated across concurrent tasks
26
+ without explicit propagation.
27
+
28
+ Scope note
29
+ ----------
30
+ This ContextVar is intentionally scoped to the ``frontend_emitter``
31
+ plugin. It is not a general-purpose ``code_puppy.context.session_id``
32
+ because the only consumer is the emitter's session-channel fan-out
33
+ logic. Other subsystems that need session-level metadata should
34
+ introduce their own ContextVar with a clearly-scoped name.
35
+ """
36
+
37
+ from __future__ import annotations
38
+
39
+ from contextvars import ContextVar
40
+ from typing import Optional
41
+
42
+ # session_id of the currently running agent invocation, if any. Set by embedders
43
+ # before agent work; read by emit_event()/register_callbacks to tag events. ``None``
44
+ # is the legitimate "no session context" value, not an error.
45
+ current_emitter_session_id: ContextVar[Optional[str]] = ContextVar(
46
+ "code_puppy_frontend_emitter_session_id", default=None
47
+ )
48
+
49
+
50
+ __all__ = ["current_emitter_session_id"]
@@ -0,0 +1,6 @@
1
+ """Grok (x.ai) OAuth plugin for Code Puppy.
2
+
3
+ Authenticates against xAI's OAuth service (the same flow used by the
4
+ official Grok CLI and the pi-xai-oauth package) and registers Grok
5
+ models that run on the resulting access token.
6
+ """
@@ -0,0 +1,49 @@
1
+ """Configuration for the Grok (x.ai) OAuth plugin.
2
+
3
+ Endpoints, client id, and scopes mirror the official Grok CLI OAuth flow
4
+ (as implemented by https://pi.dev/packages/pi-xai-oauth).
5
+ """
6
+
7
+ from pathlib import Path
8
+ from typing import Any, Dict
9
+
10
+ from code_puppy import config
11
+
12
+ GROK_OAUTH_CONFIG: Dict[str, Any] = {
13
+ # OIDC issuer + discovery. Authorization/token endpoints are resolved
14
+ # dynamically from the discovery document and validated against *.x.ai.
15
+ "issuer": "https://auth.x.ai",
16
+ "discovery_url": "https://auth.x.ai/.well-known/openid-configuration",
17
+ # Public OAuth client used by the official Grok CLI.
18
+ "client_id": "b1a00492-073a-47ea-816f-4c329264a828",
19
+ "scope": "openid profile email offline_access grok-cli:access api:access",
20
+ # Localhost callback. xAI allows an ephemeral-port fallback when the
21
+ # preferred port is busy.
22
+ "redirect_host": "127.0.0.1",
23
+ "redirect_port": 56121,
24
+ "redirect_path": "/callback",
25
+ "callback_timeout": 180,
26
+ # Refresh this many seconds before the token actually expires.
27
+ "refresh_skew_seconds": 120,
28
+ # Grok models are served from the regular xAI API with the OAuth
29
+ # access token as the bearer key.
30
+ "api_base_url": "https://api.x.ai/v1",
31
+ # Registered model names are prefixed to avoid catalogue collisions.
32
+ "prefix": "xai-",
33
+ "default_model": "grok-4.5",
34
+ }
35
+
36
+ # Grok models made available after authentication.
37
+ GROK_MODELS: Dict[str, Dict[str, Any]] = {
38
+ "grok-4.5": {
39
+ "context_length": 500_000,
40
+ "supported_settings": ["reasoning_effort"],
41
+ },
42
+ }
43
+
44
+
45
+ def get_token_storage_path() -> Path:
46
+ """Path for storing OAuth tokens (uses XDG_DATA_HOME)."""
47
+ data_dir = Path(config.DATA_DIR)
48
+ data_dir.mkdir(parents=True, exist_ok=True, mode=0o700)
49
+ return data_dir / "grok_oauth.json"
@@ -0,0 +1,213 @@
1
+ """Grok (x.ai) OAuth flow matching the official Grok CLI / pi-xai-oauth."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import secrets
6
+ import threading
7
+ import time
8
+ import urllib.parse
9
+ from http.server import BaseHTTPRequestHandler, HTTPServer
10
+ from typing import Any
11
+
12
+ from code_puppy.messaging import emit_error, emit_info, emit_success, emit_warning
13
+
14
+ from ..oauth_puppy_html import oauth_failure_html, oauth_success_html
15
+ from .config import GROK_OAUTH_CONFIG
16
+ from .utils import (
17
+ exchange_token,
18
+ fetch_discovery,
19
+ generate_pkce_pair,
20
+ load_stored_tokens,
21
+ save_tokens,
22
+ tokens_from_payload,
23
+ )
24
+
25
+ _ALLOWED_CORS_ORIGINS = ("https://accounts.x.ai", "https://auth.x.ai")
26
+
27
+
28
+ class _OAuthServer(HTTPServer):
29
+ """Localhost callback server holding the state for one OAuth attempt."""
30
+
31
+ def __init__(self) -> None:
32
+ host = GROK_OAUTH_CONFIG["redirect_host"]
33
+ preferred_port = GROK_OAUTH_CONFIG["redirect_port"]
34
+ try:
35
+ super().__init__((host, preferred_port), _CallbackHandler)
36
+ except OSError:
37
+ # Preferred port busy — xAI accepts ephemeral localhost ports.
38
+ super().__init__((host, 0), _CallbackHandler)
39
+
40
+ self.exit_code = 1
41
+ discovery = fetch_discovery()
42
+ self.authorization_endpoint = discovery["authorization_endpoint"]
43
+ self.token_endpoint = discovery["token_endpoint"]
44
+ self.state = secrets.token_hex(16)
45
+ self.nonce = secrets.token_hex(16)
46
+ self.code_verifier, self.code_challenge = generate_pkce_pair()
47
+ port = self.server_address[1]
48
+ self.redirect_uri = f"http://{host}:{port}{GROK_OAUTH_CONFIG['redirect_path']}"
49
+
50
+ def auth_url(self) -> str:
51
+ params = {
52
+ "response_type": "code",
53
+ "client_id": GROK_OAUTH_CONFIG["client_id"],
54
+ "redirect_uri": self.redirect_uri,
55
+ "scope": GROK_OAUTH_CONFIG["scope"],
56
+ "code_challenge": self.code_challenge,
57
+ "code_challenge_method": "S256",
58
+ "state": self.state,
59
+ "nonce": self.nonce,
60
+ }
61
+ return f"{self.authorization_endpoint}?{urllib.parse.urlencode(params)}"
62
+
63
+ def exchange_code(self, code: str) -> bool:
64
+ payload = exchange_token(
65
+ self.token_endpoint,
66
+ {
67
+ "grant_type": "authorization_code",
68
+ "code": code,
69
+ "redirect_uri": self.redirect_uri,
70
+ "client_id": GROK_OAUTH_CONFIG["client_id"],
71
+ "code_verifier": self.code_verifier,
72
+ },
73
+ )
74
+ return save_tokens(tokens_from_payload(payload, self.token_endpoint))
75
+
76
+
77
+ class _CallbackHandler(BaseHTTPRequestHandler):
78
+ server: "_OAuthServer"
79
+
80
+ def do_OPTIONS(self) -> None: # noqa: N802
81
+ self.send_response(204)
82
+ self._send_cors_headers()
83
+ self.end_headers()
84
+
85
+ def do_GET(self) -> None: # noqa: N802
86
+ parsed = urllib.parse.urlparse(self.path)
87
+ if parsed.path != GROK_OAUTH_CONFIG["redirect_path"]:
88
+ self._send_failure(404, "Callback endpoint not found for the puppy parade.")
89
+ return
90
+
91
+ params = urllib.parse.parse_qs(parsed.query)
92
+ error = params.get("error", [None])[0]
93
+ if error:
94
+ description = params.get("error_description", [error])[0]
95
+ self._send_failure(400, f"xAI authorization failed: {description}")
96
+ self._shutdown_after_delay()
97
+ return
98
+
99
+ if params.get("state", [None])[0] != self.server.state:
100
+ self._send_failure(
101
+ 400, "OAuth state mismatch — a squirrel may have tampered with it."
102
+ )
103
+ return
104
+
105
+ code = params.get("code", [None])[0]
106
+ if not code:
107
+ self._send_failure(400, "Missing auth code — the token treat rolled away.")
108
+ self._shutdown_after_delay()
109
+ return
110
+
111
+ try:
112
+ saved = self.server.exchange_code(code)
113
+ except Exception as exc: # noqa: BLE001
114
+ self._send_failure(500, f"Token exchange failed: {exc}")
115
+ self._shutdown_after_delay()
116
+ return
117
+
118
+ if saved:
119
+ self.server.exit_code = 0
120
+ self._send_html(
121
+ oauth_success_html(
122
+ "Grok",
123
+ "You can now close this window and return to Code Puppy.",
124
+ )
125
+ )
126
+ else:
127
+ self._send_failure(
128
+ 500, "Unable to persist auth file — a puppy probably chewed it."
129
+ )
130
+ self._shutdown_after_delay()
131
+
132
+ def log_message(self, fmt: str, *args: Any) -> None: # noqa: A003
133
+ pass
134
+
135
+ def _send_cors_headers(self) -> None:
136
+ origin = self.headers.get("Origin")
137
+ if origin not in _ALLOWED_CORS_ORIGINS:
138
+ return
139
+ self.send_header("Access-Control-Allow-Origin", origin)
140
+ self.send_header("Access-Control-Allow-Methods", "GET, OPTIONS")
141
+ self.send_header("Access-Control-Allow-Headers", "Content-Type")
142
+ self.send_header("Access-Control-Allow-Private-Network", "true")
143
+ self.send_header("Vary", "Origin")
144
+
145
+ def _send_html(self, body: str, status: int = 200) -> None:
146
+ encoded = body.encode("utf-8")
147
+ self.send_response(status)
148
+ self._send_cors_headers()
149
+ self.send_header("Content-Type", "text/html; charset=utf-8")
150
+ self.send_header("Content-Length", str(len(encoded)))
151
+ self.end_headers()
152
+ self.wfile.write(encoded)
153
+
154
+ def _send_failure(self, status: int, reason: str) -> None:
155
+ self._send_html(oauth_failure_html("Grok", reason), status)
156
+
157
+ def _shutdown_after_delay(self, seconds: float = 2.0) -> None:
158
+ def _later() -> None:
159
+ time.sleep(seconds)
160
+ self.server.shutdown()
161
+
162
+ threading.Thread(target=_later, daemon=True).start()
163
+
164
+
165
+ def run_oauth_flow() -> bool:
166
+ """Run the browser OAuth flow. Returns True when tokens were saved."""
167
+ existing_tokens = load_stored_tokens()
168
+ if existing_tokens and existing_tokens.get("access_token"):
169
+ emit_warning("Existing Grok tokens will be overwritten.")
170
+
171
+ try:
172
+ server = _OAuthServer()
173
+ except Exception as exc: # noqa: BLE001
174
+ emit_error(f"Could not start Grok OAuth flow: {exc}")
175
+ return False
176
+
177
+ auth_url = server.auth_url()
178
+ emit_info(f"Open this URL in your browser: {auth_url}")
179
+
180
+ server_thread = threading.Thread(target=server.serve_forever, daemon=True)
181
+ server_thread.start()
182
+
183
+ from code_puppy.tools.common import should_suppress_browser
184
+
185
+ if should_suppress_browser():
186
+ emit_info(f"[HEADLESS MODE] Would normally open: {auth_url}")
187
+ else:
188
+ try:
189
+ import webbrowser
190
+
191
+ if not webbrowser.open(auth_url):
192
+ emit_warning("Please open the URL manually — the browser stayed home.")
193
+ except Exception as exc: # noqa: BLE001
194
+ emit_warning(f"Could not open browser automatically: {exc}")
195
+
196
+ emit_info("Waiting for xAI authentication callback…")
197
+
198
+ elapsed = 0.0
199
+ timeout = GROK_OAUTH_CONFIG["callback_timeout"]
200
+ interval = 0.25
201
+ while elapsed < timeout and server.exit_code != 0:
202
+ time.sleep(interval)
203
+ elapsed += interval
204
+
205
+ server.shutdown()
206
+ server_thread.join(timeout=5)
207
+
208
+ if server.exit_code != 0:
209
+ emit_error("Grok authentication failed or timed out.")
210
+ return False
211
+
212
+ emit_success("Grok OAuth complete — access token saved.")
213
+ return True
@@ -0,0 +1,132 @@
1
+ """Grok (x.ai) OAuth plugin callbacks.
2
+
3
+ Provides OAuth authentication for Grok models (SuperGrok / Grok CLI flow)
4
+ and registers the 'grok_oauth' model type handler plus the Grok model
5
+ catalogue (headlined by grok-4.5).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any, Dict, List, Optional, Tuple
11
+
12
+ from code_puppy.callbacks import register_callback
13
+ from code_puppy.messaging import emit_info, emit_success, emit_warning
14
+ from code_puppy.model_switching import set_model_and_reload_agent
15
+
16
+ from .config import GROK_MODELS, GROK_OAUTH_CONFIG, get_token_storage_path
17
+ from .oauth_flow import run_oauth_flow
18
+ from .utils import get_valid_access_token, load_stored_tokens
19
+
20
+ _PREFIX = GROK_OAUTH_CONFIG["prefix"]
21
+ _DEFAULT_MODEL = f"{_PREFIX}{GROK_OAUTH_CONFIG['default_model']}"
22
+
23
+
24
+ def _custom_help() -> List[Tuple[str, str]]:
25
+ return [
26
+ (
27
+ "grok-auth",
28
+ "Authenticate with x.ai (Grok) via OAuth and register Grok models",
29
+ ),
30
+ ("grok-status", "Check Grok OAuth authentication status"),
31
+ ("grok-logout", "Remove Grok OAuth tokens and registered models"),
32
+ ]
33
+
34
+
35
+ def _handle_grok_status() -> None:
36
+ tokens = load_stored_tokens()
37
+ if tokens and tokens.get("access_token"):
38
+ emit_success(" Grok OAuth: Authenticated")
39
+ emit_info(f" Available Grok models: {', '.join(_grok_model_names())}")
40
+ else:
41
+ emit_warning(" Grok OAuth: Not authenticated")
42
+ emit_info(" Run /grok-auth to launch the browser sign-in flow.")
43
+
44
+
45
+ def _handle_grok_logout() -> None:
46
+ token_path = get_token_storage_path()
47
+ if token_path.exists():
48
+ token_path.unlink()
49
+ emit_info("Removed Grok OAuth tokens")
50
+ emit_success("Grok logout complete")
51
+
52
+
53
+ def _handle_custom_command(command: str, name: str) -> Optional[bool]:
54
+ if not name:
55
+ return None
56
+
57
+ if name == "grok-auth":
58
+ if run_oauth_flow():
59
+ emit_info(f"Registering Grok models: {', '.join(_grok_model_names())}")
60
+ set_model_and_reload_agent(_DEFAULT_MODEL)
61
+ return True
62
+
63
+ if name == "grok-status":
64
+ _handle_grok_status()
65
+ return True
66
+
67
+ if name == "grok-logout":
68
+ _handle_grok_logout()
69
+ return True
70
+
71
+ return None
72
+
73
+
74
+ def _grok_model_names() -> List[str]:
75
+ return [f"{_PREFIX}{model_id}" for model_id in GROK_MODELS]
76
+
77
+
78
+ def _load_grok_models() -> Dict[str, Any]:
79
+ """Inject Grok models into the catalogue once authenticated."""
80
+ tokens = load_stored_tokens()
81
+ if not (tokens and tokens.get("access_token")):
82
+ return {}
83
+
84
+ return {
85
+ f"{_PREFIX}{model_id}": {
86
+ "type": "grok_oauth",
87
+ "name": model_id,
88
+ "context_length": spec["context_length"],
89
+ "supported_settings": spec.get("supported_settings", []),
90
+ "oauth_source": "grok-oauth-plugin",
91
+ }
92
+ for model_id, spec in GROK_MODELS.items()
93
+ }
94
+
95
+
96
+ def _create_grok_oauth_model(model_name: str, model_config: Dict, config: Dict) -> Any:
97
+ """Create a Grok OAuth model instance.
98
+
99
+ This handler is registered via the 'register_model_type' callback to
100
+ handle models with type='grok_oauth'. Grok models speak the OpenAI
101
+ Responses API on api.x.ai with the OAuth access token as bearer key.
102
+ """
103
+ from pydantic_ai.models.openai import OpenAIResponsesModel
104
+ from pydantic_ai.providers.openai import OpenAIProvider
105
+
106
+ from code_puppy.http_utils import create_async_client
107
+
108
+ access_token = get_valid_access_token()
109
+ if not access_token:
110
+ emit_warning(
111
+ f"Failed to get valid Grok OAuth token; skipping model "
112
+ f"'{model_config.get('name')}'. Run /grok-auth to authenticate."
113
+ )
114
+ return None
115
+
116
+ provider = OpenAIProvider(
117
+ api_key=access_token,
118
+ base_url=GROK_OAUTH_CONFIG["api_base_url"],
119
+ http_client=create_async_client(model_name=model_name),
120
+ )
121
+ return OpenAIResponsesModel(model_name=model_config["name"], provider=provider)
122
+
123
+
124
+ def _register_model_types() -> List[Dict[str, Any]]:
125
+ """Register the grok_oauth model type handler."""
126
+ return [{"type": "grok_oauth", "handler": _create_grok_oauth_model}]
127
+
128
+
129
+ register_callback("custom_command_help", _custom_help)
130
+ register_callback("custom_command", _handle_custom_command)
131
+ register_callback("register_model_type", _register_model_types)
132
+ register_callback("load_models_config", _load_grok_models)
@@ -0,0 +1,195 @@
1
+ """Utility helpers for the Grok (x.ai) OAuth plugin."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import base64
6
+ import datetime
7
+ import hashlib
8
+ import json
9
+ import logging
10
+ import secrets
11
+ import time
12
+ from typing import Any, Dict, Optional, Tuple
13
+ from urllib.parse import urlparse
14
+
15
+ import requests
16
+
17
+ from .config import GROK_OAUTH_CONFIG, get_token_storage_path
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+ _discovery_cache: Optional[Dict[str, str]] = None
22
+
23
+
24
+ def _urlsafe_b64encode(data: bytes) -> str:
25
+ return base64.urlsafe_b64encode(data).decode("utf-8").rstrip("=")
26
+
27
+
28
+ def generate_pkce_pair() -> Tuple[str, str]:
29
+ """Return a (code_verifier, code_challenge) PKCE S256 pair."""
30
+ code_verifier = _urlsafe_b64encode(secrets.token_bytes(32))
31
+ digest = hashlib.sha256(code_verifier.encode("utf-8")).digest()
32
+ return code_verifier, _urlsafe_b64encode(digest)
33
+
34
+
35
+ def validate_xai_endpoint(url: str) -> str:
36
+ """Ensure a discovered endpoint is HTTPS on x.ai (or a subdomain)."""
37
+ parsed = urlparse(url)
38
+ host = (parsed.hostname or "").lower()
39
+ if parsed.scheme != "https" or (host != "x.ai" and not host.endswith(".x.ai")):
40
+ raise ValueError(f"xAI OAuth discovery returned an unexpected endpoint: {url}")
41
+ return url
42
+
43
+
44
+ def fetch_discovery() -> Dict[str, str]:
45
+ """Fetch (and cache) the xAI OIDC discovery document."""
46
+ global _discovery_cache
47
+ if _discovery_cache:
48
+ return _discovery_cache
49
+
50
+ response = requests.get(
51
+ GROK_OAUTH_CONFIG["discovery_url"],
52
+ headers={"Accept": "application/json"},
53
+ timeout=30,
54
+ )
55
+ response.raise_for_status()
56
+ data = response.json()
57
+
58
+ authorization_endpoint = data.get("authorization_endpoint")
59
+ token_endpoint = data.get("token_endpoint")
60
+ if not authorization_endpoint or not token_endpoint:
61
+ raise ValueError(
62
+ "xAI OAuth discovery response did not include authorization/token endpoints"
63
+ )
64
+
65
+ _discovery_cache = {
66
+ "authorization_endpoint": validate_xai_endpoint(authorization_endpoint),
67
+ "token_endpoint": validate_xai_endpoint(token_endpoint),
68
+ }
69
+ return _discovery_cache
70
+
71
+
72
+ def _utc_now_iso() -> str:
73
+ return (
74
+ datetime.datetime.now(datetime.timezone.utc).isoformat().replace("+00:00", "Z")
75
+ )
76
+
77
+
78
+ def tokens_from_payload(
79
+ payload: Dict[str, Any],
80
+ token_endpoint: str,
81
+ fallback_refresh: str = "",
82
+ ) -> Dict[str, Any]:
83
+ """Normalize an OAuth token response into our stored-token shape."""
84
+ access_token = payload.get("access_token")
85
+ if not access_token:
86
+ raise ValueError("xAI token response did not include an access token")
87
+
88
+ expires_in = payload.get("expires_in") or 3600
89
+ return {
90
+ "access_token": access_token,
91
+ "refresh_token": payload.get("refresh_token") or fallback_refresh,
92
+ "id_token": payload.get("id_token", ""),
93
+ "token_type": payload.get("token_type", "Bearer"),
94
+ "expires_at": time.time() + float(expires_in),
95
+ "token_endpoint": token_endpoint,
96
+ "last_refresh": _utc_now_iso(),
97
+ }
98
+
99
+
100
+ def load_stored_tokens() -> Optional[Dict[str, Any]]:
101
+ try:
102
+ token_path = get_token_storage_path()
103
+ if token_path.exists():
104
+ with open(token_path, "r", encoding="utf-8") as handle:
105
+ return json.load(handle)
106
+ except Exception as exc:
107
+ logger.error("Failed to load Grok OAuth tokens: %s", exc)
108
+ return None
109
+
110
+
111
+ def save_tokens(tokens: Dict[str, Any]) -> bool:
112
+ if tokens is None:
113
+ raise TypeError("tokens cannot be None")
114
+ try:
115
+ token_path = get_token_storage_path()
116
+ with open(token_path, "w", encoding="utf-8") as handle:
117
+ json.dump(tokens, handle, indent=2)
118
+ token_path.chmod(0o600)
119
+ return True
120
+ except Exception as exc:
121
+ logger.error("Failed to save Grok OAuth tokens: %s", exc)
122
+ return False
123
+
124
+
125
+ def exchange_token(token_endpoint: str, body: Dict[str, str]) -> Dict[str, Any]:
126
+ """POST a form-encoded token request and return the JSON payload."""
127
+ response = requests.post(
128
+ token_endpoint,
129
+ data=body,
130
+ headers={
131
+ "Accept": "application/json",
132
+ "Content-Type": "application/x-www-form-urlencoded",
133
+ },
134
+ timeout=30,
135
+ )
136
+ if response.status_code != 200:
137
+ raise RuntimeError(
138
+ f"xAI token request failed: {response.status_code} {response.text}"
139
+ )
140
+ return response.json()
141
+
142
+
143
+ def refresh_access_token() -> Optional[str]:
144
+ """Refresh the access token using the stored refresh token."""
145
+ tokens = load_stored_tokens()
146
+ if not tokens:
147
+ return None
148
+
149
+ refresh_token = tokens.get("refresh_token")
150
+ if not refresh_token:
151
+ logger.debug("No refresh_token available for Grok OAuth")
152
+ return None
153
+
154
+ try:
155
+ token_endpoint = tokens.get("token_endpoint") or ""
156
+ token_endpoint = (
157
+ validate_xai_endpoint(token_endpoint)
158
+ if token_endpoint
159
+ else fetch_discovery()["token_endpoint"]
160
+ )
161
+ payload = exchange_token(
162
+ token_endpoint,
163
+ {
164
+ "grant_type": "refresh_token",
165
+ "refresh_token": refresh_token,
166
+ "client_id": GROK_OAUTH_CONFIG["client_id"],
167
+ },
168
+ )
169
+ new_tokens = tokens_from_payload(payload, token_endpoint, refresh_token)
170
+ if save_tokens(new_tokens):
171
+ logger.info("Successfully refreshed Grok OAuth token")
172
+ return new_tokens["access_token"]
173
+ except Exception as exc:
174
+ logger.error("Grok OAuth token refresh failed: %s", exc)
175
+ return None
176
+
177
+
178
+ def get_valid_access_token() -> Optional[str]:
179
+ """Return a valid access token, refreshing if it is (nearly) expired."""
180
+ tokens = load_stored_tokens()
181
+ if not tokens:
182
+ logger.debug("No stored Grok OAuth tokens found")
183
+ return None
184
+
185
+ access_token = tokens.get("access_token")
186
+ if not access_token:
187
+ return None
188
+
189
+ expires_at = tokens.get("expires_at")
190
+ skew = GROK_OAUTH_CONFIG["refresh_skew_seconds"]
191
+ if isinstance(expires_at, (int, float)) and time.time() > expires_at - skew:
192
+ logger.info("Grok OAuth token expired, attempting refresh")
193
+ return refresh_access_token()
194
+
195
+ return access_token