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,663 @@
1
+ """Utility helpers for the Claude Code OAuth plugin."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import base64
6
+ import hashlib
7
+ import json
8
+ import logging
9
+ import re
10
+ import secrets
11
+ import time
12
+ from dataclasses import dataclass
13
+ from typing import Any, Dict, List, Optional, Tuple, Union
14
+ from urllib.parse import urlencode
15
+
16
+ import requests
17
+
18
+ from code_puppy_core_plugins.oauth_pasteback import parse_oauth_callback_input
19
+
20
+ from .config import (
21
+ CLAUDE_CODE_OAUTH_CONFIG,
22
+ get_claude_models_path,
23
+ get_token_storage_path,
24
+ )
25
+
26
+ # Proactive refresh buffer default (seconds). Actual buffer is dynamic
27
+ # based on expires_in to avoid overly aggressive refreshes.
28
+ TOKEN_REFRESH_BUFFER_SECONDS = 300
29
+ MIN_REFRESH_BUFFER_SECONDS = 30
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+
34
+ @dataclass
35
+ class OAuthContext:
36
+ """Runtime state for an in-progress OAuth flow."""
37
+
38
+ state: str
39
+ code_verifier: str
40
+ code_challenge: str
41
+ created_at: float
42
+ redirect_uri: Optional[str] = None
43
+
44
+
45
+ _oauth_context: Optional[OAuthContext] = None
46
+
47
+
48
+ def _urlsafe_b64encode(data: bytes) -> str:
49
+ return base64.urlsafe_b64encode(data).decode("utf-8").rstrip("=")
50
+
51
+
52
+ def _generate_code_verifier() -> str:
53
+ return _urlsafe_b64encode(secrets.token_bytes(64))
54
+
55
+
56
+ def _compute_code_challenge(code_verifier: str) -> str:
57
+ digest = hashlib.sha256(code_verifier.encode("utf-8")).digest()
58
+ return _urlsafe_b64encode(digest)
59
+
60
+
61
+ def prepare_oauth_context() -> OAuthContext:
62
+ """Create and cache a new OAuth PKCE context."""
63
+ global _oauth_context
64
+ state = secrets.token_urlsafe(32)
65
+ code_verifier = _generate_code_verifier()
66
+ code_challenge = _compute_code_challenge(code_verifier)
67
+ _oauth_context = OAuthContext(
68
+ state=state,
69
+ code_verifier=code_verifier,
70
+ code_challenge=code_challenge,
71
+ created_at=time.time(),
72
+ )
73
+ return _oauth_context
74
+
75
+
76
+ def get_oauth_context() -> Optional[OAuthContext]:
77
+ return _oauth_context
78
+
79
+
80
+ def clear_oauth_context() -> None:
81
+ global _oauth_context
82
+ _oauth_context = None
83
+
84
+
85
+ def assign_redirect_uri(context: OAuthContext, port: int) -> str:
86
+ """Assign redirect URI for the given OAuth context."""
87
+ if context is None:
88
+ raise RuntimeError("OAuth context cannot be None")
89
+
90
+ host = CLAUDE_CODE_OAUTH_CONFIG["redirect_host"].rstrip("/")
91
+ path = CLAUDE_CODE_OAUTH_CONFIG["redirect_path"].lstrip("/")
92
+ redirect_uri = f"{host}:{port}/{path}"
93
+ context.redirect_uri = redirect_uri
94
+ return redirect_uri
95
+
96
+
97
+ def build_authorization_url(context: OAuthContext) -> str:
98
+ """Return the Claude authorization URL with PKCE parameters."""
99
+ if not context.redirect_uri:
100
+ raise RuntimeError("Redirect URI has not been assigned for this OAuth context")
101
+
102
+ params = {
103
+ "response_type": "code",
104
+ "client_id": CLAUDE_CODE_OAUTH_CONFIG["client_id"],
105
+ "redirect_uri": context.redirect_uri,
106
+ "scope": CLAUDE_CODE_OAUTH_CONFIG["scope"],
107
+ "state": context.state,
108
+ "code": "true",
109
+ "code_challenge": context.code_challenge,
110
+ "code_challenge_method": "S256",
111
+ }
112
+ return f"{CLAUDE_CODE_OAUTH_CONFIG['auth_url']}?{urlencode(params)}"
113
+
114
+
115
+ def parse_authorization_code(raw_input: str) -> Tuple[str, Optional[str]]:
116
+ parsed = parse_oauth_callback_input(raw_input)
117
+ if parsed.error:
118
+ raise ValueError(parsed.error_message or parsed.error)
119
+ if not parsed.code:
120
+ raise ValueError("Authorization code cannot be empty")
121
+ return parsed.code, parsed.state
122
+
123
+
124
+ def load_stored_tokens() -> Optional[Dict[str, Any]]:
125
+ try:
126
+ token_path = get_token_storage_path()
127
+ if token_path.exists():
128
+ with open(token_path, "r", encoding="utf-8") as handle:
129
+ return json.load(handle)
130
+ except Exception as exc: # pragma: no cover - defensive logging
131
+ logger.error("Failed to load tokens: %s", exc)
132
+ return None
133
+
134
+
135
+ def _calculate_expires_at(expires_in: Optional[float]) -> Optional[float]:
136
+ if expires_in is None:
137
+ return None
138
+ try:
139
+ return time.time() + float(expires_in)
140
+ except (TypeError, ValueError):
141
+ return None
142
+
143
+
144
+ def _calculate_refresh_buffer(expires_in: Optional[float]) -> float:
145
+ default_buffer = float(TOKEN_REFRESH_BUFFER_SECONDS)
146
+ if expires_in is None:
147
+ return default_buffer
148
+ try:
149
+ expires_value = float(expires_in)
150
+ except (TypeError, ValueError):
151
+ return default_buffer
152
+ return min(default_buffer, max(MIN_REFRESH_BUFFER_SECONDS, expires_value * 0.1))
153
+
154
+
155
+ def _get_expires_at_value(tokens: Dict[str, Any]) -> Optional[float]:
156
+ expires_at = tokens.get("expires_at")
157
+ if expires_at is None:
158
+ return None
159
+ try:
160
+ return float(expires_at)
161
+ except (TypeError, ValueError):
162
+ return None
163
+
164
+
165
+ def _is_token_actually_expired(tokens: Dict[str, Any]) -> bool:
166
+ expires_at_value = _get_expires_at_value(tokens)
167
+ if expires_at_value is None:
168
+ return False
169
+ return time.time() >= expires_at_value
170
+
171
+
172
+ def is_token_expired(tokens: Dict[str, Any]) -> bool:
173
+ expires_at_value = _get_expires_at_value(tokens)
174
+ if expires_at_value is None:
175
+ return False
176
+ buffer_seconds = _calculate_refresh_buffer(tokens.get("expires_in"))
177
+ return time.time() >= expires_at_value - buffer_seconds
178
+
179
+
180
+ def update_claude_code_model_tokens(access_token: str) -> bool:
181
+ try:
182
+ claude_models = load_claude_models()
183
+ if not claude_models:
184
+ return False
185
+
186
+ updated = False
187
+ for config in claude_models.values():
188
+ if config.get("oauth_source") != "claude-code-plugin":
189
+ continue
190
+ custom_endpoint = config.get("custom_endpoint")
191
+ if not isinstance(custom_endpoint, dict):
192
+ continue
193
+ custom_endpoint["api_key"] = access_token
194
+ updated = True
195
+
196
+ if updated:
197
+ return save_claude_models(claude_models)
198
+ except Exception as exc: # pragma: no cover - defensive logging
199
+ logger.error("Failed to update Claude model tokens: %s", exc)
200
+ return False
201
+
202
+
203
+ def refresh_access_token(force: bool = False) -> Optional[str]:
204
+ tokens = load_stored_tokens()
205
+ if not tokens:
206
+ return None
207
+
208
+ if not force and not is_token_expired(tokens):
209
+ return tokens.get("access_token")
210
+
211
+ refresh_token = tokens.get("refresh_token")
212
+ if not refresh_token:
213
+ logger.debug("No refresh_token available")
214
+ return None
215
+
216
+ payload = {
217
+ "grant_type": "refresh_token",
218
+ "client_id": CLAUDE_CODE_OAUTH_CONFIG["client_id"],
219
+ "refresh_token": refresh_token,
220
+ }
221
+
222
+ headers = {
223
+ "Content-Type": "application/json",
224
+ "Accept": "application/json",
225
+ "anthropic-beta": "oauth-2025-04-20",
226
+ }
227
+
228
+ try:
229
+ response = requests.post(
230
+ CLAUDE_CODE_OAUTH_CONFIG["token_url"],
231
+ json=payload,
232
+ headers=headers,
233
+ timeout=30,
234
+ )
235
+ if response.status_code == 200:
236
+ content_type = response.headers.get("content-type", "")
237
+ if not content_type.startswith("application/json"):
238
+ logger.error(
239
+ "Token refresh returned non-JSON response (Content-Type: %s): %s",
240
+ content_type,
241
+ response.text[:500],
242
+ )
243
+ return None
244
+ try:
245
+ new_tokens = response.json()
246
+ except (ValueError, json.JSONDecodeError) as e:
247
+ logger.error("Failed to parse token refresh response as JSON: %s", e)
248
+ return None
249
+ tokens["access_token"] = new_tokens.get("access_token")
250
+ tokens["refresh_token"] = new_tokens.get("refresh_token", refresh_token)
251
+ expires_in_value = new_tokens.get("expires_in")
252
+ if expires_in_value is None:
253
+ expires_in_value = tokens.get("expires_in")
254
+ if expires_in_value is not None:
255
+ tokens["expires_in"] = expires_in_value
256
+ tokens["expires_at"] = _calculate_expires_at(expires_in_value)
257
+ if save_tokens(tokens):
258
+ update_claude_code_model_tokens(tokens["access_token"])
259
+ return tokens["access_token"]
260
+ else:
261
+ logger.error(
262
+ "Token refresh failed: %s - %s", response.status_code, response.text
263
+ )
264
+ except Exception as exc: # pragma: no cover - defensive logging
265
+ logger.error("Token refresh error: %s", exc)
266
+ return None
267
+
268
+
269
+ def get_valid_access_token() -> Optional[str]:
270
+ tokens = load_stored_tokens()
271
+ if not tokens:
272
+ logger.debug("No stored Claude Code OAuth tokens found")
273
+ return None
274
+
275
+ access_token = tokens.get("access_token")
276
+ if not access_token:
277
+ logger.debug("No access_token in stored tokens")
278
+ return None
279
+
280
+ if is_token_expired(tokens):
281
+ logger.info("Claude Code OAuth token expired, attempting refresh")
282
+ refreshed = refresh_access_token()
283
+ if refreshed:
284
+ return refreshed
285
+ if not _is_token_actually_expired(tokens):
286
+ logger.warning(
287
+ "Claude Code token refresh failed; using existing access token until expiry"
288
+ )
289
+ return access_token
290
+ logger.warning("Claude Code token refresh failed")
291
+ return None
292
+
293
+ return access_token
294
+
295
+
296
+ def save_tokens(tokens: Dict[str, Any]) -> bool:
297
+ try:
298
+ token_path = get_token_storage_path()
299
+ with open(token_path, "w", encoding="utf-8") as handle:
300
+ json.dump(tokens, handle, indent=2)
301
+ token_path.chmod(0o600)
302
+ return True
303
+ except Exception as exc: # pragma: no cover - defensive logging
304
+ logger.error("Failed to save tokens: %s", exc)
305
+ return False
306
+
307
+
308
+ def load_claude_models() -> Dict[str, Any]:
309
+ try:
310
+ models_path = get_claude_models_path()
311
+ if models_path.exists():
312
+ with open(models_path, "r", encoding="utf-8") as handle:
313
+ return json.load(handle)
314
+ except Exception as exc: # pragma: no cover - defensive logging
315
+ logger.error("Failed to load Claude models: %s", exc)
316
+ return {}
317
+
318
+
319
+ def load_claude_models_filtered() -> Dict[str, Any]:
320
+ """Load Claude models and filter to only the latest versions.
321
+
322
+ This loads the stored models and applies the same filtering logic
323
+ used during saving to ensure only the latest haiku, sonnet, and opus
324
+ models are returned.
325
+ """
326
+ try:
327
+ all_models = load_claude_models()
328
+ if not all_models:
329
+ return {}
330
+
331
+ # Extract model names from the configuration
332
+ model_names = []
333
+ for name, config in all_models.items():
334
+ if config.get("oauth_source") == "claude-code-plugin":
335
+ model_names.append(config.get("name", ""))
336
+ else:
337
+ # For non-OAuth models, use the full key
338
+ model_names.append(name)
339
+
340
+ # Filter to only latest models
341
+ latest_names = set(
342
+ filter_latest_claude_models(
343
+ model_names, max_per_family={"default": 1, "opus": 3}
344
+ )
345
+ )
346
+
347
+ # Return only the filtered models
348
+ filtered_models = {}
349
+ for name, config in all_models.items():
350
+ model_name = config.get("name", name)
351
+ if model_name in latest_names:
352
+ filtered_models[name] = config
353
+
354
+ logger.info(
355
+ "Loaded %d models, filtered to %d latest models",
356
+ len(all_models),
357
+ len(filtered_models),
358
+ )
359
+ return filtered_models
360
+
361
+ except Exception as exc: # pragma: no cover - defensive logging
362
+ logger.error("Failed to load and filter Claude models: %s", exc)
363
+ return {}
364
+
365
+
366
+ def save_claude_models(models: Dict[str, Any]) -> bool:
367
+ try:
368
+ models_path = get_claude_models_path()
369
+ with open(models_path, "w", encoding="utf-8") as handle:
370
+ json.dump(models, handle, indent=2)
371
+ return True
372
+ except Exception as exc: # pragma: no cover - defensive logging
373
+ logger.error("Failed to save Claude models: %s", exc)
374
+ return False
375
+
376
+
377
+ def exchange_code_for_tokens(
378
+ auth_code: str, context: OAuthContext
379
+ ) -> Optional[Dict[str, Any]]:
380
+ if not context.redirect_uri:
381
+ raise RuntimeError("Redirect URI missing from OAuth context")
382
+
383
+ payload = {
384
+ "grant_type": "authorization_code",
385
+ "client_id": CLAUDE_CODE_OAUTH_CONFIG["client_id"],
386
+ "code": auth_code,
387
+ "state": context.state,
388
+ "code_verifier": context.code_verifier,
389
+ "redirect_uri": context.redirect_uri,
390
+ }
391
+
392
+ headers = {
393
+ "Content-Type": "application/json",
394
+ "Accept": "application/json",
395
+ "anthropic-beta": "oauth-2025-04-20",
396
+ }
397
+
398
+ logger.info("Exchanging code for tokens: %s", CLAUDE_CODE_OAUTH_CONFIG["token_url"])
399
+ logger.debug("Payload keys: %s", list(payload.keys()))
400
+ logger.debug("Headers: %s", headers)
401
+ try:
402
+ response = requests.post(
403
+ CLAUDE_CODE_OAUTH_CONFIG["token_url"],
404
+ json=payload,
405
+ headers=headers,
406
+ timeout=30,
407
+ )
408
+ logger.info("Token exchange response: %s", response.status_code)
409
+ logger.debug("Response body: %s", response.text)
410
+ if response.status_code == 200:
411
+ content_type = response.headers.get("content-type", "")
412
+ if not content_type.startswith("application/json"):
413
+ logger.error(
414
+ "Token exchange returned non-JSON response (Content-Type: %s): %s",
415
+ content_type,
416
+ response.text[:500],
417
+ )
418
+ return None
419
+ try:
420
+ token_data = response.json()
421
+ except (ValueError, json.JSONDecodeError) as e:
422
+ logger.error("Failed to parse token exchange response as JSON: %s", e)
423
+ return None
424
+ token_data["expires_at"] = _calculate_expires_at(
425
+ token_data.get("expires_in")
426
+ )
427
+ return token_data
428
+ logger.error(
429
+ "Token exchange failed: %s - %s",
430
+ response.status_code,
431
+ response.text,
432
+ )
433
+ except Exception as exc: # pragma: no cover - defensive logging
434
+ logger.error("Token exchange error: %s", exc)
435
+ return None
436
+
437
+
438
+ def filter_latest_claude_models(
439
+ models: List[str], max_per_family: Union[int, Dict[str, int]] = 2
440
+ ) -> List[str]:
441
+ """Filter models to keep the top N latest haiku, sonnet, and opus.
442
+
443
+ Parses model names in the format claude-{family}-{major}-{minor}-{date}
444
+ and returns the top ``max_per_family`` versions of each family
445
+ (haiku, sonnet, opus), sorted newest-first.
446
+
447
+ Args:
448
+ models: List of model name strings to filter.
449
+ max_per_family: Either a single int applied to all families, or a dict
450
+ mapping family name to its limit (e.g. ``{"opus": 3}``). Families
451
+ not present in the dict fall back to ``"default"`` key, or ``2``.
452
+ """
453
+ # Collect all parsed models per family
454
+ # family -> list of (model_name, major, minor, date)
455
+ family_models: Dict[str, List[Tuple[str, int, int, int]]] = {}
456
+
457
+ for model_name in models:
458
+ if model_name == "claude-opus-5":
459
+ family_models.setdefault("opus", []).append((model_name, 5, 0, 0))
460
+ continue
461
+ if model_name == "claude-opus-4-8":
462
+ family_models.setdefault("opus", []).append((model_name, 4, 8, 20250301))
463
+ continue
464
+ if model_name == "claude-opus-4-7":
465
+ family_models.setdefault("opus", []).append((model_name, 4, 7, 20250219))
466
+ continue
467
+ if model_name == "claude-opus-4-6":
468
+ family_models.setdefault("opus", []).append((model_name, 4, 6, 20260205))
469
+ continue
470
+ if model_name == "claude-sonnet-4-6":
471
+ family_models.setdefault("sonnet", []).append((model_name, 4, 6, 20250610))
472
+ continue
473
+ if model_name == "claude-sonnet-5":
474
+ family_models.setdefault("sonnet", []).append((model_name, 5, 0, 0))
475
+ continue
476
+ if model_name == "claude-fable-5":
477
+ family_models.setdefault("fable", []).append((model_name, 5, 0, 0))
478
+ continue
479
+ # Match pattern: claude-{family}-{major}-{minor}-{date}
480
+ # Examples: claude-haiku-3-5-20241022, claude-sonnet-4-5-20250929
481
+ match = re.match(r"claude-(haiku|sonnet|opus)-(\d+)-(\d+)-(\d+)", model_name)
482
+ if not match:
483
+ # Also try pattern with dots: claude-{family}-{major}.{minor}-{date}
484
+ match = re.match(
485
+ r"claude-(haiku|sonnet|opus)-(\d+)\.(\d+)-(\d+)", model_name
486
+ )
487
+
488
+ if not match:
489
+ continue
490
+
491
+ family = match.group(1)
492
+ major = int(match.group(2))
493
+ minor = int(match.group(3))
494
+ date = int(match.group(4))
495
+
496
+ family_models.setdefault(family, []).append((model_name, major, minor, date))
497
+
498
+ # Sort each family descending and keep the top N
499
+ filtered: List[str] = []
500
+ for family, family_entries in family_models.items():
501
+ if isinstance(max_per_family, dict):
502
+ limit = max_per_family.get(family, max_per_family.get("default", 2))
503
+ else:
504
+ limit = max_per_family
505
+ family_entries.sort(key=lambda e: (e[1], e[2], e[3]), reverse=True)
506
+ for entry in family_entries[:limit]:
507
+ filtered.append(entry[0])
508
+
509
+ logger.info(
510
+ "Filtered %d models to %d latest models (max_per_family=%s): %s",
511
+ len(models),
512
+ len(filtered),
513
+ max_per_family,
514
+ filtered,
515
+ )
516
+ return filtered
517
+
518
+
519
+ def fetch_claude_code_models(access_token: str) -> Optional[List[str]]:
520
+ try:
521
+ api_url = f"{CLAUDE_CODE_OAUTH_CONFIG['api_base_url']}/v1/models"
522
+ headers = {
523
+ "Authorization": f"Bearer {access_token}",
524
+ "Content-Type": "application/json",
525
+ "anthropic-beta": "oauth-2025-04-20",
526
+ "anthropic-version": CLAUDE_CODE_OAUTH_CONFIG.get(
527
+ "anthropic_version", "2023-06-01"
528
+ ),
529
+ }
530
+ response = requests.get(api_url, headers=headers, timeout=30)
531
+ if response.status_code == 200:
532
+ content_type = response.headers.get("content-type", "")
533
+ if not content_type.startswith("application/json"):
534
+ logger.error(
535
+ "Models fetch returned non-JSON response (Content-Type: %s): %s",
536
+ content_type,
537
+ response.text[:500],
538
+ )
539
+ return None
540
+ try:
541
+ data = response.json()
542
+ except (ValueError, json.JSONDecodeError) as e:
543
+ logger.error("Failed to parse models response as JSON: %s", e)
544
+ return None
545
+ if isinstance(data.get("data"), list):
546
+ models: List[str] = []
547
+ for model in data["data"]:
548
+ name = model.get("id") or model.get("name")
549
+ if name:
550
+ models.append(name)
551
+ return models
552
+ else:
553
+ logger.error(
554
+ "Failed to fetch models: %s - %s",
555
+ response.status_code,
556
+ response.text,
557
+ )
558
+ except Exception as exc: # pragma: no cover - defensive logging
559
+ logger.error("Error fetching Claude Code models: %s", exc)
560
+ return None
561
+
562
+
563
+ def _build_model_entry(model_name: str, access_token: str, context_length: int) -> dict:
564
+ """Build a single model config entry for claude_models.json."""
565
+ supported_settings = [
566
+ "temperature",
567
+ "extended_thinking",
568
+ "budget_tokens",
569
+ "interleaved_thinking",
570
+ "fast",
571
+ ]
572
+
573
+ # Opus 4-6 models support the effort setting
574
+ lower = model_name.lower()
575
+ if (
576
+ "opus-4-6" in lower
577
+ or "4-6-opus" in lower
578
+ or "opus-4-7" in lower
579
+ or "4-7-opus" in lower
580
+ or "opus-4-8" in lower
581
+ or "4-8-opus" in lower
582
+ or "opus-5" in lower
583
+ or "5-opus" in lower
584
+ or "sonnet-5" in lower
585
+ or "5-sonnet" in lower
586
+ or "fable-5" in lower
587
+ ):
588
+ supported_settings.append("effort")
589
+
590
+ return {
591
+ "type": "claude_code",
592
+ "name": model_name,
593
+ "custom_endpoint": {
594
+ "url": CLAUDE_CODE_OAUTH_CONFIG["api_base_url"],
595
+ "api_key": access_token,
596
+ "headers": {
597
+ "anthropic-beta": "oauth-2025-04-20,interleaved-thinking-2025-05-14",
598
+ "x-app": "cli",
599
+ "User-Agent": "claude-cli/2.0.61 (external, cli)",
600
+ },
601
+ },
602
+ "context_length": context_length,
603
+ "oauth_source": "claude-code-plugin",
604
+ "supported_settings": supported_settings,
605
+ }
606
+
607
+
608
+ def add_models_to_extra_config(models: List[str]) -> bool:
609
+ try:
610
+ # Filter to only latest haiku, sonnet, and opus models
611
+ filtered_models = filter_latest_claude_models(
612
+ models, max_per_family={"default": 1, "opus": 3, "fable": 3}
613
+ )
614
+
615
+ # Start fresh - overwrite the file on every auth instead of loading existing
616
+ claude_models = {}
617
+ added = 0
618
+ access_token = get_valid_access_token() or ""
619
+ prefix = CLAUDE_CODE_OAUTH_CONFIG["prefix"]
620
+ default_ctx = CLAUDE_CODE_OAUTH_CONFIG["default_context_length"]
621
+ long_ctx = CLAUDE_CODE_OAUTH_CONFIG["long_context_length"]
622
+ long_ctx_models = CLAUDE_CODE_OAUTH_CONFIG["long_context_models"]
623
+
624
+ for model_name in filtered_models:
625
+ prefixed = f"{prefix}{model_name}"
626
+ claude_models[prefixed] = _build_model_entry(
627
+ model_name, access_token, default_ctx
628
+ )
629
+ added += 1
630
+
631
+ # Create a "-long" variant with extended context for eligible models
632
+ if model_name in long_ctx_models:
633
+ long_prefixed = f"{prefix}{model_name}-long"
634
+ claude_models[long_prefixed] = _build_model_entry(
635
+ model_name, access_token, long_ctx
636
+ )
637
+ added += 1
638
+
639
+ if save_claude_models(claude_models):
640
+ logger.info("Added %s Claude Code models", added)
641
+ return True
642
+ except Exception as exc: # pragma: no cover - defensive logging
643
+ logger.error("Error adding models to config: %s", exc)
644
+ return False
645
+
646
+
647
+ def remove_claude_code_models() -> int:
648
+ try:
649
+ claude_models = load_claude_models()
650
+ to_remove = [
651
+ name
652
+ for name, config in claude_models.items()
653
+ if config.get("oauth_source") == "claude-code-plugin"
654
+ ]
655
+ if not to_remove:
656
+ return 0
657
+ for model_name in to_remove:
658
+ claude_models.pop(model_name, None)
659
+ if save_claude_models(claude_models):
660
+ return len(to_remove)
661
+ except Exception as exc: # pragma: no cover - defensive logging
662
+ logger.error("Error removing Claude Code models: %s", exc)
663
+ return 0