code-muse 0.0.1__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 (394) hide show
  1. code_muse/__init__.py +26 -0
  2. code_muse/__main__.py +10 -0
  3. code_muse/agents/__init__.py +31 -0
  4. code_muse/agents/_builder.py +214 -0
  5. code_muse/agents/_compaction.py +506 -0
  6. code_muse/agents/_diagnostics.py +171 -0
  7. code_muse/agents/_history.py +382 -0
  8. code_muse/agents/_key_listeners.py +148 -0
  9. code_muse/agents/_non_streaming_render.py +148 -0
  10. code_muse/agents/_runtime.py +596 -0
  11. code_muse/agents/agent_creator_agent.py +603 -0
  12. code_muse/agents/agent_helios.py +47 -0
  13. code_muse/agents/agent_manager.py +740 -0
  14. code_muse/agents/agent_muse.py +78 -0
  15. code_muse/agents/agent_planning.py +44 -0
  16. code_muse/agents/agent_qa_melpomene.py +207 -0
  17. code_muse/agents/base_agent.py +194 -0
  18. code_muse/agents/event_stream_handler.py +361 -0
  19. code_muse/agents/json_agent.py +201 -0
  20. code_muse/agents/prompt_v3.py +521 -0
  21. code_muse/agents/subagent_stream_handler.py +273 -0
  22. code_muse/callbacks.py +941 -0
  23. code_muse/chatgpt_codex_client.py +333 -0
  24. code_muse/claude_cache_client.py +853 -0
  25. code_muse/cli_runner/__init__.py +319 -0
  26. code_muse/cli_runner/args.py +63 -0
  27. code_muse/cli_runner/loop.py +510 -0
  28. code_muse/cli_runner/resume.py +72 -0
  29. code_muse/cli_runner/runner.py +161 -0
  30. code_muse/command_line/__init__.py +1 -0
  31. code_muse/command_line/add_model_menu.py +1331 -0
  32. code_muse/command_line/agent_menu.py +674 -0
  33. code_muse/command_line/attachments.py +397 -0
  34. code_muse/command_line/autosave_menu.py +709 -0
  35. code_muse/command_line/clipboard.py +528 -0
  36. code_muse/command_line/colors_menu.py +530 -0
  37. code_muse/command_line/command_handler.py +262 -0
  38. code_muse/command_line/command_registry.py +150 -0
  39. code_muse/command_line/config_commands.py +711 -0
  40. code_muse/command_line/core_commands.py +740 -0
  41. code_muse/command_line/diff_menu.py +865 -0
  42. code_muse/command_line/file_path_completion.py +73 -0
  43. code_muse/command_line/load_context_completion.py +57 -0
  44. code_muse/command_line/model_picker_completion.py +512 -0
  45. code_muse/command_line/model_settings_menu.py +983 -0
  46. code_muse/command_line/onboarding_slides.py +162 -0
  47. code_muse/command_line/onboarding_wizard.py +337 -0
  48. code_muse/command_line/pagination.py +41 -0
  49. code_muse/command_line/pin_command_completion.py +329 -0
  50. code_muse/command_line/prompt_toolkit_completion.py +886 -0
  51. code_muse/command_line/session_commands.py +304 -0
  52. code_muse/command_line/shell_passthrough.py +145 -0
  53. code_muse/command_line/skills_completion.py +158 -0
  54. code_muse/command_line/types.py +18 -0
  55. code_muse/command_line/uc_menu.py +908 -0
  56. code_muse/command_line/utils.py +105 -0
  57. code_muse/command_line/wiggum_state.py +77 -0
  58. code_muse/config.py +1138 -0
  59. code_muse/config_agent.py +168 -0
  60. code_muse/config_appearance.py +241 -0
  61. code_muse/config_model.py +357 -0
  62. code_muse/config_security.py +73 -0
  63. code_muse/error_logging.py +132 -0
  64. code_muse/evals/__init__.py +35 -0
  65. code_muse/evals/eval_helpers.py +81 -0
  66. code_muse/evals/eval_runner.py +299 -0
  67. code_muse/evals/sample_evals/__init__.py +1 -0
  68. code_muse/evals/sample_evals/eval_frugal_reads.py +59 -0
  69. code_muse/evals/sample_evals/eval_memory_planning.py +31 -0
  70. code_muse/evals/sample_evals/eval_shell_efficiency.py +39 -0
  71. code_muse/evals/sample_evals/eval_tool_masking.py +33 -0
  72. code_muse/fs_scan_cache/__init__.py +31 -0
  73. code_muse/fs_scan_cache/invalidation_hooks.py +89 -0
  74. code_muse/fs_scan_cache/scan_cache_core.cpython-314-darwin.so +0 -0
  75. code_muse/fs_scan_cache/scan_cache_core.pyx +203 -0
  76. code_muse/fs_scan_cache/tool_integration.py +309 -0
  77. code_muse/fs_scan_cache/ttl_policy.py +44 -0
  78. code_muse/gemini_code_assist.py +383 -0
  79. code_muse/gemini_model.py +838 -0
  80. code_muse/hook_engine/README.md +105 -0
  81. code_muse/hook_engine/__init__.py +21 -0
  82. code_muse/hook_engine/aliases.py +153 -0
  83. code_muse/hook_engine/engine.py +221 -0
  84. code_muse/hook_engine/executor.py +347 -0
  85. code_muse/hook_engine/matcher.py +154 -0
  86. code_muse/hook_engine/models.py +245 -0
  87. code_muse/hook_engine/registry.py +114 -0
  88. code_muse/hook_engine/trust.py +268 -0
  89. code_muse/hook_engine/validator.py +144 -0
  90. code_muse/http_utils.py +360 -0
  91. code_muse/keymap.py +128 -0
  92. code_muse/list_filtering.py +26 -0
  93. code_muse/main.py +10 -0
  94. code_muse/messaging/__init__.py +259 -0
  95. code_muse/messaging/bus.py +621 -0
  96. code_muse/messaging/commands.py +166 -0
  97. code_muse/messaging/markdown_patches.py +57 -0
  98. code_muse/messaging/message_queue.py +397 -0
  99. code_muse/messaging/messages.py +591 -0
  100. code_muse/messaging/queue_console.py +269 -0
  101. code_muse/messaging/renderers.py +308 -0
  102. code_muse/messaging/rich_renderer.py +1158 -0
  103. code_muse/messaging/shimmer.py +154 -0
  104. code_muse/messaging/spinner/__init__.py +87 -0
  105. code_muse/messaging/spinner/console_spinner.py +250 -0
  106. code_muse/messaging/spinner/spinner_base.py +82 -0
  107. code_muse/messaging/subagent_console.py +458 -0
  108. code_muse/model_factory.py +1203 -0
  109. code_muse/model_switching.py +59 -0
  110. code_muse/model_utils.py +156 -0
  111. code_muse/models.json +66 -0
  112. code_muse/models_cache/__init__.py +26 -0
  113. code_muse/models_cache/blocking_lru_cache.py +98 -0
  114. code_muse/models_cache/cache_writer.py +86 -0
  115. code_muse/models_cache/sha256_hash.cpython-314-darwin.so +0 -0
  116. code_muse/models_cache/sha256_hash.pyx +34 -0
  117. code_muse/models_cache/startup_integration.py +75 -0
  118. code_muse/models_dev_api.json +1 -0
  119. code_muse/models_dev_parser.py +590 -0
  120. code_muse/motion.py +126 -0
  121. code_muse/plugins/__init__.py +471 -0
  122. code_muse/plugins/agent_skills/__init__.py +32 -0
  123. code_muse/plugins/agent_skills/config.py +176 -0
  124. code_muse/plugins/agent_skills/discovery.py +309 -0
  125. code_muse/plugins/agent_skills/downloader.py +389 -0
  126. code_muse/plugins/agent_skills/installer.py +19 -0
  127. code_muse/plugins/agent_skills/metadata.py +293 -0
  128. code_muse/plugins/agent_skills/prompt_builder.py +66 -0
  129. code_muse/plugins/agent_skills/register_callbacks.py +298 -0
  130. code_muse/plugins/agent_skills/remote_catalog.py +320 -0
  131. code_muse/plugins/agent_skills/skill_catalog.py +254 -0
  132. code_muse/plugins/agent_skills/skills_install_menu.py +690 -0
  133. code_muse/plugins/agent_skills/skills_menu.py +791 -0
  134. code_muse/plugins/autonomous_memory/__init__.py +39 -0
  135. code_muse/plugins/autonomous_memory/bm25_scorer.cpython-314-darwin.so +0 -0
  136. code_muse/plugins/autonomous_memory/bm25_scorer.cpython-314-x86_64-linux-gnu.so +0 -0
  137. code_muse/plugins/autonomous_memory/bm25_scorer.pyx +291 -0
  138. code_muse/plugins/autonomous_memory/consolidation.py +82 -0
  139. code_muse/plugins/autonomous_memory/extraction.py +382 -0
  140. code_muse/plugins/autonomous_memory/lease_lock.py +105 -0
  141. code_muse/plugins/autonomous_memory/memory_injection.py +59 -0
  142. code_muse/plugins/autonomous_memory/register_callbacks.py +268 -0
  143. code_muse/plugins/autonomous_memory/secret_scanner.py +62 -0
  144. code_muse/plugins/autonomous_memory/session_scanner.py +163 -0
  145. code_muse/plugins/aws_bedrock/__init__.py +14 -0
  146. code_muse/plugins/aws_bedrock/config.py +99 -0
  147. code_muse/plugins/aws_bedrock/register_callbacks.py +241 -0
  148. code_muse/plugins/aws_bedrock/utils.py +153 -0
  149. code_muse/plugins/azure_foundry/README.md +238 -0
  150. code_muse/plugins/azure_foundry/__init__.py +15 -0
  151. code_muse/plugins/azure_foundry/config.py +125 -0
  152. code_muse/plugins/azure_foundry/discovery.py +187 -0
  153. code_muse/plugins/azure_foundry/register_callbacks.py +495 -0
  154. code_muse/plugins/azure_foundry/token.py +180 -0
  155. code_muse/plugins/azure_foundry/utils.py +345 -0
  156. code_muse/plugins/build_filter/__init__.py +1 -0
  157. code_muse/plugins/build_filter/register_callbacks.py +201 -0
  158. code_muse/plugins/build_filter/strategies/__init__.py +1 -0
  159. code_muse/plugins/build_filter/strategies/build.py +397 -0
  160. code_muse/plugins/chatgpt_oauth/__init__.py +6 -0
  161. code_muse/plugins/chatgpt_oauth/config.py +52 -0
  162. code_muse/plugins/chatgpt_oauth/oauth_flow.py +338 -0
  163. code_muse/plugins/chatgpt_oauth/register_callbacks.py +172 -0
  164. code_muse/plugins/chatgpt_oauth/test_plugin.py +301 -0
  165. code_muse/plugins/chatgpt_oauth/utils.py +538 -0
  166. code_muse/plugins/checkpointing/__init__.py +29 -0
  167. code_muse/plugins/checkpointing/checkpoint_hook.py +51 -0
  168. code_muse/plugins/checkpointing/conversation_snapshots.py +117 -0
  169. code_muse/plugins/checkpointing/register_callbacks.py +51 -0
  170. code_muse/plugins/checkpointing/restore_command.py +263 -0
  171. code_muse/plugins/checkpointing/rewind_shortcut.py +88 -0
  172. code_muse/plugins/checkpointing/shadow_git.py +90 -0
  173. code_muse/plugins/claude_code_hooks/__init__.py +1 -0
  174. code_muse/plugins/claude_code_hooks/config.py +188 -0
  175. code_muse/plugins/claude_code_hooks/register_callbacks.py +208 -0
  176. code_muse/plugins/claude_code_oauth/README.md +167 -0
  177. code_muse/plugins/claude_code_oauth/SETUP.md +93 -0
  178. code_muse/plugins/claude_code_oauth/__init__.py +25 -0
  179. code_muse/plugins/claude_code_oauth/config.py +52 -0
  180. code_muse/plugins/claude_code_oauth/fast_mode.py +124 -0
  181. code_muse/plugins/claude_code_oauth/prompt_handler.py +63 -0
  182. code_muse/plugins/claude_code_oauth/register_callbacks.py +547 -0
  183. code_muse/plugins/claude_code_oauth/test_fast_mode.py +165 -0
  184. code_muse/plugins/claude_code_oauth/test_plugin.py +283 -0
  185. code_muse/plugins/claude_code_oauth/token_refresh_heartbeat.py +237 -0
  186. code_muse/plugins/claude_code_oauth/utils.py +664 -0
  187. code_muse/plugins/copilot_auth/__init__.py +11 -0
  188. code_muse/plugins/copilot_auth/config.py +91 -0
  189. code_muse/plugins/copilot_auth/reasoning_client.py +409 -0
  190. code_muse/plugins/copilot_auth/register_callbacks.py +461 -0
  191. code_muse/plugins/copilot_auth/utils.py +584 -0
  192. code_muse/plugins/custom_commands/__init__.py +14 -0
  193. code_muse/plugins/custom_commands/args_injection.py +82 -0
  194. code_muse/plugins/custom_commands/command_discovery.py +89 -0
  195. code_muse/plugins/custom_commands/command_toml_schema.py +71 -0
  196. code_muse/plugins/custom_commands/register_callbacks.py +176 -0
  197. code_muse/plugins/customizable_commands/__init__.py +0 -0
  198. code_muse/plugins/customizable_commands/register_callbacks.py +136 -0
  199. code_muse/plugins/destructive_command_guard/__init__.py +14 -0
  200. code_muse/plugins/destructive_command_guard/detector.py +375 -0
  201. code_muse/plugins/destructive_command_guard/register_callbacks.py +148 -0
  202. code_muse/plugins/example_custom_command/README.md +280 -0
  203. code_muse/plugins/example_custom_command/register_callbacks.py +51 -0
  204. code_muse/plugins/file_permission_handler/__init__.py +4 -0
  205. code_muse/plugins/file_permission_handler/register_callbacks.py +441 -0
  206. code_muse/plugins/filter_engine/__init__.py +30 -0
  207. code_muse/plugins/filter_engine/classifier.py +153 -0
  208. code_muse/plugins/filter_engine/content_detector.py +184 -0
  209. code_muse/plugins/filter_engine/dispatcher.py +244 -0
  210. code_muse/plugins/filter_engine/register_callbacks.py +188 -0
  211. code_muse/plugins/filter_engine/registry.py +279 -0
  212. code_muse/plugins/filter_engine/strategies/__init__.py +8 -0
  213. code_muse/plugins/filter_engine/strategies/ast_compressor.cpython-314-darwin.so +0 -0
  214. code_muse/plugins/filter_engine/strategies/ast_compressor.cpython-314-x86_64-linux-gnu.so +0 -0
  215. code_muse/plugins/filter_engine/strategies/ast_compressor.pyx +348 -0
  216. code_muse/plugins/filter_engine/strategies/ast_parser.py +167 -0
  217. code_muse/plugins/filter_engine/strategies/code.cpython-314-darwin.so +0 -0
  218. code_muse/plugins/filter_engine/strategies/code.cpython-314-x86_64-linux-gnu.so +0 -0
  219. code_muse/plugins/filter_engine/strategies/code.pyx +584 -0
  220. code_muse/plugins/filter_engine/strategies/git.cpython-314-darwin.so +0 -0
  221. code_muse/plugins/filter_engine/strategies/git.cpython-314-x86_64-linux-gnu.so +0 -0
  222. code_muse/plugins/filter_engine/strategies/git.pyx +438 -0
  223. code_muse/plugins/filter_engine/strategies/json_compressor.cpython-314-darwin.so +0 -0
  224. code_muse/plugins/filter_engine/strategies/json_compressor.pyx +253 -0
  225. code_muse/plugins/filter_engine/strategies/json_patterns.cpython-314-darwin.so +0 -0
  226. code_muse/plugins/filter_engine/strategies/json_patterns.pyx +178 -0
  227. code_muse/plugins/filter_engine/strategies/lint.cpython-314-darwin.so +0 -0
  228. code_muse/plugins/filter_engine/strategies/lint.cpython-314-x86_64-linux-gnu.so +0 -0
  229. code_muse/plugins/filter_engine/strategies/lint.pyx +626 -0
  230. code_muse/plugins/filter_engine/strategies/test.cpython-314-darwin.so +0 -0
  231. code_muse/plugins/filter_engine/strategies/test.cpython-314-x86_64-linux-gnu.so +0 -0
  232. code_muse/plugins/filter_engine/strategies/test.pyx +431 -0
  233. code_muse/plugins/filter_engine/verbosity.py +63 -0
  234. code_muse/plugins/force_push_guard/__init__.py +5 -0
  235. code_muse/plugins/force_push_guard/detector.py +96 -0
  236. code_muse/plugins/force_push_guard/register_callbacks.py +144 -0
  237. code_muse/plugins/force_push_guard/test_detector.py +143 -0
  238. code_muse/plugins/frontend_emitter/__init__.py +25 -0
  239. code_muse/plugins/frontend_emitter/emitter.py +121 -0
  240. code_muse/plugins/frontend_emitter/register_callbacks.py +259 -0
  241. code_muse/plugins/gac/__init__.py +4 -0
  242. code_muse/plugins/gac/git_ops.py +136 -0
  243. code_muse/plugins/gac/prompt.py +191 -0
  244. code_muse/plugins/gac/register_callbacks.py +82 -0
  245. code_muse/plugins/hook_creator/__init__.py +1 -0
  246. code_muse/plugins/hook_creator/register_callbacks.py +34 -0
  247. code_muse/plugins/hook_manager/__init__.py +1 -0
  248. code_muse/plugins/hook_manager/config.py +289 -0
  249. code_muse/plugins/hook_manager/hooks_menu.py +563 -0
  250. code_muse/plugins/hook_manager/register_callbacks.py +227 -0
  251. code_muse/plugins/hook_monitor/register_callbacks.py +36 -0
  252. code_muse/plugins/mindpack/__init__.py +0 -0
  253. code_muse/plugins/mindpack/factory.py +930 -0
  254. code_muse/plugins/mindpack/judge.py +573 -0
  255. code_muse/plugins/mindpack/memory.py +100 -0
  256. code_muse/plugins/mindpack/mindpack_menu.py +1552 -0
  257. code_muse/plugins/mindpack/orchestration.py +605 -0
  258. code_muse/plugins/mindpack/register_callbacks.py +175 -0
  259. code_muse/plugins/mindpack/schemas.py +358 -0
  260. code_muse/plugins/mindpack/tools.py +387 -0
  261. code_muse/plugins/oauth_muse_html.py +226 -0
  262. code_muse/plugins/ollama_setup/__init__.py +5 -0
  263. code_muse/plugins/ollama_setup/completer.py +36 -0
  264. code_muse/plugins/ollama_setup/register_callbacks.py +410 -0
  265. code_muse/plugins/plan_command/__init__.py +0 -0
  266. code_muse/plugins/plan_command/register_callbacks.py +206 -0
  267. code_muse/plugins/plan_mode/__init__.py +37 -0
  268. code_muse/plugins/plan_mode/mode_cycling.py +40 -0
  269. code_muse/plugins/plan_mode/plan_generation.py +68 -0
  270. code_muse/plugins/plan_mode/plan_hooks.py +74 -0
  271. code_muse/plugins/plan_mode/plan_mode_tools.py +138 -0
  272. code_muse/plugins/plan_mode/register_callbacks.py +121 -0
  273. code_muse/plugins/plugin_trust/register_callbacks.py +140 -0
  274. code_muse/plugins/policy_engine/__init__.py +46 -0
  275. code_muse/plugins/policy_engine/approval_flow_integration.py +59 -0
  276. code_muse/plugins/policy_engine/policy_evaluator.py +75 -0
  277. code_muse/plugins/policy_engine/policy_file_discovery.py +90 -0
  278. code_muse/plugins/policy_engine/policy_toml_schema.py +115 -0
  279. code_muse/plugins/policy_engine/register_callbacks.py +112 -0
  280. code_muse/plugins/pop_command/__init__.py +1 -0
  281. code_muse/plugins/pop_command/register_callbacks.py +189 -0
  282. code_muse/plugins/prompt_newline/__init__.py +13 -0
  283. code_muse/plugins/prompt_newline/config.py +19 -0
  284. code_muse/plugins/prompt_newline/register_callbacks.py +159 -0
  285. code_muse/plugins/safety_status/__init__.py +0 -0
  286. code_muse/plugins/safety_status/register_callbacks.py +113 -0
  287. code_muse/plugins/semantic_compression/__init__.py +6 -0
  288. code_muse/plugins/semantic_compression/compressor.py +295 -0
  289. code_muse/plugins/semantic_compression/config.py +123 -0
  290. code_muse/plugins/semantic_compression/register_callbacks.py +320 -0
  291. code_muse/plugins/shell_minimizer/__init__.py +50 -0
  292. code_muse/plugins/shell_minimizer/builtin_filters.toml +393 -0
  293. code_muse/plugins/shell_minimizer/pipeline.py +556 -0
  294. code_muse/plugins/shell_minimizer/primitives.py +482 -0
  295. code_muse/plugins/shell_minimizer/register_callbacks.py +276 -0
  296. code_muse/plugins/shell_safety/__init__.py +6 -0
  297. code_muse/plugins/shell_safety/agent_shell_safety.py +69 -0
  298. code_muse/plugins/shell_safety/command_cache.py +149 -0
  299. code_muse/plugins/shell_safety/register_callbacks.py +202 -0
  300. code_muse/plugins/synthetic_status/__init__.py +1 -0
  301. code_muse/plugins/synthetic_status/register_callbacks.py +128 -0
  302. code_muse/plugins/synthetic_status/status_api.py +145 -0
  303. code_muse/plugins/token_caching/__init__.py +21 -0
  304. code_muse/plugins/token_caching/cache_hit_tracking.py +128 -0
  305. code_muse/plugins/token_caching/cacheable_prefix_detection.py +28 -0
  306. code_muse/plugins/token_caching/register_callbacks.py +54 -0
  307. code_muse/plugins/token_caching/stats_display.py +35 -0
  308. code_muse/plugins/token_tracking/__init__.py +26 -0
  309. code_muse/plugins/token_tracking/database.py +381 -0
  310. code_muse/plugins/token_tracking/edit_analyzer.py +97 -0
  311. code_muse/plugins/token_tracking/record.py +55 -0
  312. code_muse/plugins/token_tracking/register_callbacks.py +277 -0
  313. code_muse/plugins/token_tracking/reports.py +329 -0
  314. code_muse/plugins/universal_constructor/__init__.py +13 -0
  315. code_muse/plugins/universal_constructor/models.py +136 -0
  316. code_muse/plugins/universal_constructor/register_callbacks.py +47 -0
  317. code_muse/plugins/universal_constructor/registry.py +390 -0
  318. code_muse/plugins/universal_constructor/runner.py +474 -0
  319. code_muse/plugins/universal_constructor/safety.py +440 -0
  320. code_muse/plugins/universal_constructor/sandbox.py +584 -0
  321. code_muse/provider_identity.py +105 -0
  322. code_muse/pydantic_patches.py +410 -0
  323. code_muse/reopenable_async_client.py +233 -0
  324. code_muse/round_robin_model.py +151 -0
  325. code_muse/secret_storage.py +74 -0
  326. code_muse/security/__init__.py +1 -0
  327. code_muse/security/redaction.cpython-314-darwin.so +0 -0
  328. code_muse/security/redaction.cpython-314-x86_64-linux-gnu.so +0 -0
  329. code_muse/security/redaction.pyx +135 -0
  330. code_muse/session_storage.py +565 -0
  331. code_muse/status_display.py +261 -0
  332. code_muse/stream_parser/__init__.py +76 -0
  333. code_muse/stream_parser/assistant_text_parser.py +90 -0
  334. code_muse/stream_parser/citation_parser.py +76 -0
  335. code_muse/stream_parser/inline_hidden_tag_parser.py +236 -0
  336. code_muse/stream_parser/proposed_plan_parser.py +158 -0
  337. code_muse/stream_parser/stream_text_chunk.py +23 -0
  338. code_muse/stream_parser/stream_text_parser.py +27 -0
  339. code_muse/stream_parser/tagged_line_parser.cpython-314-darwin.so +0 -0
  340. code_muse/stream_parser/tagged_line_parser.pyx +251 -0
  341. code_muse/stream_parser/utf8_stream_parser.cpython-314-darwin.so +0 -0
  342. code_muse/stream_parser/utf8_stream_parser.pyx +206 -0
  343. code_muse/summarization_agent.py +308 -0
  344. code_muse/terminal_utils.cpython-314-darwin.so +0 -0
  345. code_muse/terminal_utils.cpython-314-x86_64-linux-gnu.so +0 -0
  346. code_muse/terminal_utils.pyx +483 -0
  347. code_muse/tools/__init__.py +459 -0
  348. code_muse/tools/agent_tools.py +613 -0
  349. code_muse/tools/ask_user_question/__init__.py +26 -0
  350. code_muse/tools/ask_user_question/constants.py +73 -0
  351. code_muse/tools/ask_user_question/demo_tui.py +55 -0
  352. code_muse/tools/ask_user_question/handler.py +232 -0
  353. code_muse/tools/ask_user_question/models.py +302 -0
  354. code_muse/tools/ask_user_question/registration.py +37 -0
  355. code_muse/tools/ask_user_question/renderers.py +336 -0
  356. code_muse/tools/ask_user_question/terminal_ui.py +327 -0
  357. code_muse/tools/ask_user_question/theme.py +156 -0
  358. code_muse/tools/ask_user_question/tui_loop.py +422 -0
  359. code_muse/tools/background_jobs.py +99 -0
  360. code_muse/tools/browser/__init__.py +37 -0
  361. code_muse/tools/browser/browser_control.py +289 -0
  362. code_muse/tools/browser/browser_interactions.py +545 -0
  363. code_muse/tools/browser/browser_locators.py +640 -0
  364. code_muse/tools/browser/browser_manager.py +376 -0
  365. code_muse/tools/browser/browser_navigation.py +251 -0
  366. code_muse/tools/browser/browser_screenshot.py +180 -0
  367. code_muse/tools/browser/browser_scripts.py +462 -0
  368. code_muse/tools/browser/browser_workflows.py +222 -0
  369. code_muse/tools/chrome_cdp/__init__.py +1070 -0
  370. code_muse/tools/chrome_cdp/register_callbacks.py +61 -0
  371. code_muse/tools/command_runner.py +1401 -0
  372. code_muse/tools/common.py +1407 -0
  373. code_muse/tools/display.py +87 -0
  374. code_muse/tools/file_modifications.py +1099 -0
  375. code_muse/tools/file_operations.py +860 -0
  376. code_muse/tools/image_tools.py +185 -0
  377. code_muse/tools/meetin_proxy/__init__.py +243 -0
  378. code_muse/tools/meetin_proxy/capture_addon.py +82 -0
  379. code_muse/tools/meetin_proxy/proxy_manager.py +326 -0
  380. code_muse/tools/meetin_proxy/register_callbacks.py +45 -0
  381. code_muse/tools/path_policy.py +219 -0
  382. code_muse/tools/skills_tools.py +586 -0
  383. code_muse/tools/subagent_context.py +158 -0
  384. code_muse/tools/tools_content.py +50 -0
  385. code_muse/tools/universal_constructor.py +965 -0
  386. code_muse/uvx_detection.py +241 -0
  387. code_muse/version_checker.py +86 -0
  388. code_muse-0.0.1.data/data/code_muse/models.json +66 -0
  389. code_muse-0.0.1.data/data/code_muse/models_dev_api.json +1 -0
  390. code_muse-0.0.1.dist-info/METADATA +845 -0
  391. code_muse-0.0.1.dist-info/RECORD +394 -0
  392. code_muse-0.0.1.dist-info/WHEEL +4 -0
  393. code_muse-0.0.1.dist-info/entry_points.txt +2 -0
  394. code_muse-0.0.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,664 @@
1
+ """Utility helpers for the Claude Code OAuth plugin."""
2
+
3
+ import base64
4
+ import hashlib
5
+ import json
6
+ import logging
7
+ import re
8
+ import secrets
9
+ import time
10
+ from dataclasses import dataclass
11
+ from typing import Any
12
+ from urllib.parse import urlencode
13
+
14
+ import httpx
15
+
16
+ from code_muse.secret_storage import (
17
+ atomic_write_private_json,
18
+ warn_or_fix_private_file_mode,
19
+ )
20
+ from code_muse.security.redaction import redact_secrets
21
+
22
+ from .config import (
23
+ CLAUDE_CODE_OAUTH_CONFIG,
24
+ get_claude_models_path,
25
+ get_token_storage_path,
26
+ )
27
+
28
+ # Proactive refresh buffer default (seconds). Actual buffer is dynamic
29
+ # based on expires_in to avoid overly aggressive refreshes.
30
+ TOKEN_REFRESH_BUFFER_SECONDS = 300
31
+ MIN_REFRESH_BUFFER_SECONDS = 30
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+
36
+ @dataclass
37
+ class OAuthContext:
38
+ """Runtime state for an in-progress OAuth flow."""
39
+
40
+ state: str
41
+ code_verifier: str
42
+ code_challenge: str
43
+ created_at: float
44
+ redirect_uri: str | None = None
45
+
46
+
47
+ _oauth_context: OAuthContext | None = None
48
+
49
+
50
+ def _urlsafe_b64encode(data: bytes) -> str:
51
+ return base64.urlsafe_b64encode(data).decode("utf-8").rstrip("=")
52
+
53
+
54
+ def _generate_code_verifier() -> str:
55
+ return _urlsafe_b64encode(secrets.token_bytes(64))
56
+
57
+
58
+ def _compute_code_challenge(code_verifier: str) -> str:
59
+ digest = hashlib.sha256(code_verifier.encode("utf-8")).digest()
60
+ return _urlsafe_b64encode(digest)
61
+
62
+
63
+ def prepare_oauth_context() -> OAuthContext:
64
+ """Create and cache a new OAuth PKCE context."""
65
+ global _oauth_context
66
+ state = secrets.token_urlsafe(32)
67
+ code_verifier = _generate_code_verifier()
68
+ code_challenge = _compute_code_challenge(code_verifier)
69
+ _oauth_context = OAuthContext(
70
+ state=state,
71
+ code_verifier=code_verifier,
72
+ code_challenge=code_challenge,
73
+ created_at=time.time(),
74
+ )
75
+ return _oauth_context
76
+
77
+
78
+ def get_oauth_context() -> OAuthContext | None:
79
+ return _oauth_context
80
+
81
+
82
+ def clear_oauth_context() -> None:
83
+ global _oauth_context
84
+ _oauth_context = None
85
+
86
+
87
+ def assign_redirect_uri(context: OAuthContext, port: int) -> str:
88
+ """Assign redirect URI for the given OAuth context."""
89
+ if context is None:
90
+ raise RuntimeError("OAuth context cannot be None")
91
+
92
+ host = CLAUDE_CODE_OAUTH_CONFIG["redirect_host"].rstrip("/")
93
+ path = CLAUDE_CODE_OAUTH_CONFIG["redirect_path"].lstrip("/")
94
+ # TODO: PEP 750 t-string — use templatelib when stable
95
+ redirect_uri = f"{host}:{port}/{path}"
96
+ context.redirect_uri = redirect_uri
97
+ return redirect_uri
98
+
99
+
100
+ def build_authorization_url(context: OAuthContext) -> str:
101
+ """Return the Claude authorization URL with PKCE parameters."""
102
+ if not context.redirect_uri:
103
+ raise RuntimeError("Redirect URI has not been assigned for this OAuth context")
104
+
105
+ params = {
106
+ "response_type": "code",
107
+ "client_id": CLAUDE_CODE_OAUTH_CONFIG["client_id"],
108
+ "redirect_uri": context.redirect_uri,
109
+ "scope": CLAUDE_CODE_OAUTH_CONFIG["scope"],
110
+ "state": context.state,
111
+ "code": "true",
112
+ "code_challenge": context.code_challenge,
113
+ "code_challenge_method": "S256",
114
+ }
115
+ # TODO: PEP 750 t-string — use templatelib when stable
116
+ return f"{CLAUDE_CODE_OAUTH_CONFIG['auth_url']}?{urlencode(params)}"
117
+
118
+
119
+ def parse_authorization_code(raw_input: str) -> tuple[str, str | None]:
120
+ value = raw_input.strip()
121
+ if not value:
122
+ raise ValueError("Authorization code cannot be empty")
123
+
124
+ if "#" in value:
125
+ code, state = value.split("#", 1)
126
+ return code.strip(), state.strip() or None
127
+
128
+ parts = value.split()
129
+ if len(parts) == 2:
130
+ return parts[0].strip(), parts[1].strip() or None
131
+
132
+ return value, None
133
+
134
+
135
+ def load_stored_tokens() -> dict[str, Any | None]:
136
+ try:
137
+ token_path = get_token_storage_path()
138
+ if token_path.exists():
139
+ warn_or_fix_private_file_mode(token_path)
140
+ with open(token_path, encoding="utf-8") as handle:
141
+ return json.load(handle)
142
+ except Exception as exc: # pragma: no cover - defensive logging
143
+ logger.error("Failed to load tokens: %s", exc)
144
+ return None
145
+
146
+
147
+ def _calculate_expires_at(expires_in: float | None) -> float | None:
148
+ if expires_in is None:
149
+ return None
150
+ try:
151
+ return time.time() + float(expires_in)
152
+ except TypeError, ValueError:
153
+ return None
154
+
155
+
156
+ def _calculate_refresh_buffer(expires_in: float | None) -> float:
157
+ default_buffer = float(TOKEN_REFRESH_BUFFER_SECONDS)
158
+ if expires_in is None:
159
+ return default_buffer
160
+ try:
161
+ expires_value = float(expires_in)
162
+ except TypeError, ValueError:
163
+ return default_buffer
164
+ return min(default_buffer, max(MIN_REFRESH_BUFFER_SECONDS, expires_value * 0.1))
165
+
166
+
167
+ def _get_expires_at_value(tokens: dict[str, Any]) -> float | None:
168
+ expires_at = tokens.get("expires_at")
169
+ if expires_at is None:
170
+ return None
171
+ try:
172
+ return float(expires_at)
173
+ except TypeError, ValueError:
174
+ return None
175
+
176
+
177
+ def _is_token_actually_expired(tokens: dict[str, Any]) -> bool:
178
+ expires_at_value = _get_expires_at_value(tokens)
179
+ if expires_at_value is None:
180
+ return False
181
+ return time.time() >= expires_at_value
182
+
183
+
184
+ def is_token_expired(tokens: dict[str, Any]) -> bool:
185
+ expires_at_value = _get_expires_at_value(tokens)
186
+ if expires_at_value is None:
187
+ return False
188
+ buffer_seconds = _calculate_refresh_buffer(tokens.get("expires_in"))
189
+ return time.time() >= expires_at_value - buffer_seconds
190
+
191
+
192
+ def update_claude_code_model_tokens(access_token: str) -> bool:
193
+ try:
194
+ claude_models = load_claude_models()
195
+ if not claude_models:
196
+ return False
197
+
198
+ updated = False
199
+ for config in claude_models.values():
200
+ if config.get("oauth_source") != "claude-code-plugin":
201
+ continue
202
+ custom_endpoint = config.get("custom_endpoint")
203
+ if not isinstance(custom_endpoint, dict):
204
+ continue
205
+ custom_endpoint["api_key"] = access_token
206
+ updated = True
207
+
208
+ if updated:
209
+ return save_claude_models(claude_models)
210
+ except Exception as exc: # pragma: no cover - defensive logging
211
+ logger.error("Failed to update Claude model tokens: %s", exc)
212
+ return False
213
+
214
+
215
+ def refresh_access_token(force: bool = False) -> str | None:
216
+ tokens = load_stored_tokens()
217
+ if not tokens:
218
+ return None
219
+
220
+ if not force and not is_token_expired(tokens):
221
+ return tokens.get("access_token")
222
+
223
+ refresh_token = tokens.get("refresh_token")
224
+ if not refresh_token:
225
+ logger.debug("No refresh_token available")
226
+ return None
227
+
228
+ payload = {
229
+ "grant_type": "refresh_token",
230
+ "client_id": CLAUDE_CODE_OAUTH_CONFIG["client_id"],
231
+ "refresh_token": refresh_token,
232
+ }
233
+
234
+ headers = {
235
+ "Content-Type": "application/json",
236
+ "Accept": "application/json",
237
+ "anthropic-beta": "oauth-2025-04-20",
238
+ }
239
+
240
+ try:
241
+ response = httpx.post(
242
+ CLAUDE_CODE_OAUTH_CONFIG["token_url"],
243
+ json=payload,
244
+ headers=headers,
245
+ timeout=30,
246
+ )
247
+ if response.status_code == 200:
248
+ content_type = response.headers.get("content-type", "")
249
+ if not content_type.startswith("application/json"):
250
+ safe_text = redact_secrets(response.text)
251
+ logger.error(
252
+ "Token refresh returned non-JSON response (Content-Type: %s): %s",
253
+ content_type,
254
+ safe_text[:200],
255
+ )
256
+ return None
257
+ try:
258
+ new_tokens = response.json()
259
+ except ValueError as e:
260
+ logger.error("Failed to parse token refresh response as JSON: %s", e)
261
+ return None
262
+ tokens["access_token"] = new_tokens.get("access_token")
263
+ tokens["refresh_token"] = new_tokens.get("refresh_token", refresh_token)
264
+ expires_in_value = new_tokens.get("expires_in")
265
+ if expires_in_value is None:
266
+ expires_in_value = tokens.get("expires_in")
267
+ if expires_in_value is not None:
268
+ tokens["expires_in"] = expires_in_value
269
+ tokens["expires_at"] = _calculate_expires_at(expires_in_value)
270
+ if save_tokens(tokens):
271
+ update_claude_code_model_tokens(tokens["access_token"])
272
+ return tokens["access_token"]
273
+ else:
274
+ safe_text = redact_secrets(response.text)
275
+ logger.error(
276
+ "Token refresh failed: status=%s content_type=%s error=%s",
277
+ response.status_code,
278
+ response.headers.get("content-type", "unknown"),
279
+ safe_text[:200],
280
+ )
281
+ except Exception as exc: # pragma: no cover - defensive logging
282
+ logger.error("Token refresh error: %s", exc)
283
+ return None
284
+
285
+
286
+ def get_valid_access_token() -> str | None:
287
+ tokens = load_stored_tokens()
288
+ if not tokens:
289
+ logger.debug("No stored Claude Code OAuth tokens found")
290
+ return None
291
+
292
+ access_token = tokens.get("access_token")
293
+ if not access_token:
294
+ logger.debug("No access_token in stored tokens")
295
+ return None
296
+
297
+ if is_token_expired(tokens):
298
+ logger.info("Claude Code OAuth token expired, attempting refresh")
299
+ refreshed = refresh_access_token()
300
+ if refreshed:
301
+ return refreshed
302
+ if not _is_token_actually_expired(tokens):
303
+ logger.warning(
304
+ "Claude Code token refresh failed; using existing access token until expiry"
305
+ )
306
+ return access_token
307
+ logger.warning("Claude Code token refresh failed")
308
+ return None
309
+
310
+ return access_token
311
+
312
+
313
+ def save_tokens(tokens: dict[str, Any]) -> bool:
314
+ try:
315
+ token_path = get_token_storage_path()
316
+ atomic_write_private_json(token_path, tokens)
317
+ return True
318
+ except Exception as exc: # pragma: no cover - defensive logging
319
+ logger.error("Failed to save tokens: %s", exc)
320
+ return False
321
+
322
+
323
+ def load_claude_models() -> dict[str, Any]:
324
+ try:
325
+ models_path = get_claude_models_path()
326
+ if models_path.exists():
327
+ with open(models_path, encoding="utf-8") as handle:
328
+ return json.load(handle)
329
+ except Exception as exc: # pragma: no cover - defensive logging
330
+ logger.error("Failed to load Claude models: %s", exc)
331
+ return {}
332
+
333
+
334
+ def load_claude_models_filtered() -> dict[str, Any]:
335
+ """Load Claude models and filter to only the latest versions.
336
+
337
+ This loads the stored models and applies the same filtering logic
338
+ used during saving to ensure only the latest haiku, sonnet, and opus
339
+ models are returned.
340
+ """
341
+ try:
342
+ all_models = load_claude_models()
343
+ if not all_models:
344
+ return {}
345
+
346
+ # Extract model names from the configuration
347
+ model_names = []
348
+ for name, config in all_models.items():
349
+ if config.get("oauth_source") == "claude-code-plugin":
350
+ model_names.append(config.get("name", ""))
351
+ else:
352
+ # For non-OAuth models, use the full key
353
+ model_names.append(name)
354
+
355
+ # Filter to only latest models
356
+ latest_names = set(
357
+ filter_latest_claude_models(
358
+ model_names, max_per_family={"default": 1, "opus": 3}
359
+ )
360
+ )
361
+
362
+ # Return only the filtered models
363
+ filtered_models = {}
364
+ for name, config in all_models.items():
365
+ model_name = config.get("name", name)
366
+ if model_name in latest_names:
367
+ filtered_models[name] = config
368
+
369
+ logger.info(
370
+ "Loaded %d models, filtered to %d latest models",
371
+ len(all_models),
372
+ len(filtered_models),
373
+ )
374
+ return filtered_models
375
+
376
+ except Exception as exc: # pragma: no cover - defensive logging
377
+ logger.error("Failed to load and filter Claude models: %s", exc)
378
+ return {}
379
+
380
+
381
+ def save_claude_models(models: dict[str, Any]) -> bool:
382
+ try:
383
+ models_path = get_claude_models_path()
384
+ atomic_write_private_json(models_path, models)
385
+ return True
386
+ except Exception as exc: # pragma: no cover - defensive logging
387
+ logger.error("Failed to save Claude models: %s", exc)
388
+ return False
389
+
390
+
391
+ def exchange_code_for_tokens(
392
+ auth_code: str, context: OAuthContext
393
+ ) -> dict[str, Any | None]:
394
+ if not context.redirect_uri:
395
+ raise RuntimeError("Redirect URI missing from OAuth context")
396
+
397
+ payload = {
398
+ "grant_type": "authorization_code",
399
+ "client_id": CLAUDE_CODE_OAUTH_CONFIG["client_id"],
400
+ "code": auth_code,
401
+ "state": context.state,
402
+ "code_verifier": context.code_verifier,
403
+ "redirect_uri": context.redirect_uri,
404
+ }
405
+
406
+ headers = {
407
+ "Content-Type": "application/json",
408
+ "Accept": "application/json",
409
+ "anthropic-beta": "oauth-2025-04-20",
410
+ }
411
+
412
+ logger.info("Exchanging code for tokens: %s", CLAUDE_CODE_OAUTH_CONFIG["token_url"])
413
+ logger.debug("Payload keys: %s", list(payload.keys()))
414
+ redacted_headers = {k: redact_secrets(v) for k, v in headers.items()}
415
+ logger.debug("Headers: %s", redacted_headers)
416
+ try:
417
+ response = httpx.post(
418
+ CLAUDE_CODE_OAUTH_CONFIG["token_url"],
419
+ json=payload,
420
+ headers=headers,
421
+ timeout=30,
422
+ )
423
+ logger.info("Token exchange response: %s", response.status_code)
424
+ if response.status_code == 200:
425
+ content_type = response.headers.get("content-type", "")
426
+ if not content_type.startswith("application/json"):
427
+ safe_text = redact_secrets(response.text)
428
+ logger.error(
429
+ "Token exchange returned non-JSON response (Content-Type: %s): %s",
430
+ content_type,
431
+ safe_text[:200],
432
+ )
433
+ return None
434
+ try:
435
+ token_data = response.json()
436
+ except ValueError as e:
437
+ logger.error("Failed to parse token exchange response as JSON: %s", e)
438
+ return None
439
+ token_data["expires_at"] = _calculate_expires_at(
440
+ token_data.get("expires_in")
441
+ )
442
+ return token_data
443
+ safe_text = redact_secrets(response.text)
444
+ logger.error(
445
+ "Token exchange failed: status=%s content_type=%s error=%s",
446
+ response.status_code,
447
+ response.headers.get("content-type", "unknown"),
448
+ safe_text[:200],
449
+ )
450
+ except Exception as exc: # pragma: no cover - defensive logging
451
+ logger.error("Token exchange error: %s", exc)
452
+ return None
453
+
454
+
455
+ def filter_latest_claude_models(
456
+ models: list[str], max_per_family: int | dict[str, int] = 2
457
+ ) -> list[str]:
458
+ """Filter models to keep the top N latest haiku, sonnet, and opus.
459
+
460
+ Parses model names in the format claude-{family}-{major}-{minor}-{date}
461
+ and returns the top ``max_per_family`` versions of each family
462
+ (haiku, sonnet, opus), sorted newest-first.
463
+
464
+ Args:
465
+ models: List of model name strings to filter.
466
+ max_per_family: Either a single int applied to all families, or a dict
467
+ mapping family name to its limit (e.g. ``{"opus": 3}``). Families
468
+ not present in the dict fall back to ``"default"`` key, or ``2``.
469
+ """
470
+ # Collect all parsed models per family
471
+ # family -> list of (model_name, major, minor, date)
472
+ family_models: dict[str, list[tuple[str, int, int, int]]] = {}
473
+
474
+ for model_name in models:
475
+ if model_name == "claude-opus-4-7":
476
+ family_models.setdefault("opus", []).append((model_name, 4, 7, 20250219))
477
+ continue
478
+ if model_name == "claude-opus-4-6":
479
+ family_models.setdefault("opus", []).append((model_name, 4, 6, 20260205))
480
+ continue
481
+ if model_name == "claude-sonnet-4-6":
482
+ family_models.setdefault("sonnet", []).append((model_name, 4, 6, 20250610))
483
+ continue
484
+ # Match pattern: claude-{family}-{major}-{minor}-{date}
485
+ # Examples: claude-haiku-3-5-20241022, claude-sonnet-4-5-20250929
486
+ match = re.match(r"claude-(haiku|sonnet|opus)-(\d+)-(\d+)-(\d+)", model_name)
487
+ if not match:
488
+ # Also try pattern with dots: claude-{family}-{major}.{minor}-{date}
489
+ match = re.match(
490
+ r"claude-(haiku|sonnet|opus)-(\d+)\.(\d+)-(\d+)", model_name
491
+ )
492
+
493
+ if not match:
494
+ continue
495
+
496
+ family = match.group(1)
497
+ major = int(match.group(2))
498
+ minor = int(match.group(3))
499
+ date = int(match.group(4))
500
+
501
+ family_models.setdefault(family, []).append((model_name, major, minor, date))
502
+
503
+ # Sort each family descending and keep the top N
504
+ filtered: list[str] = []
505
+ for family, family_entries in family_models.items():
506
+ if isinstance(max_per_family, dict):
507
+ limit = max_per_family.get(family, max_per_family.get("default", 2))
508
+ else:
509
+ limit = max_per_family
510
+ family_entries.sort(key=lambda e: (e[1], e[2], e[3]), reverse=True)
511
+ for entry in family_entries[:limit]:
512
+ filtered.append(entry[0])
513
+
514
+ logger.info(
515
+ "Filtered %d models to %d latest models (max_per_family=%s): %s",
516
+ len(models),
517
+ len(filtered),
518
+ max_per_family,
519
+ filtered,
520
+ )
521
+ return filtered
522
+
523
+
524
+ def fetch_claude_code_models(access_token: str) -> list[str | None]:
525
+ try:
526
+ api_url = f"{CLAUDE_CODE_OAUTH_CONFIG['api_base_url']}/v1/models"
527
+ headers = {
528
+ "Authorization": f"Bearer {access_token}",
529
+ "Content-Type": "application/json",
530
+ "anthropic-beta": "oauth-2025-04-20",
531
+ "anthropic-version": CLAUDE_CODE_OAUTH_CONFIG.get(
532
+ "anthropic_version", "2023-06-01"
533
+ ),
534
+ }
535
+ response = httpx.get(api_url, headers=headers, timeout=30)
536
+ if response.status_code == 200:
537
+ content_type = response.headers.get("content-type", "")
538
+ if not content_type.startswith("application/json"):
539
+ safe_text = redact_secrets(response.text)
540
+ logger.error(
541
+ "Models fetch returned non-JSON response (Content-Type: %s): %s",
542
+ content_type,
543
+ safe_text[:200],
544
+ )
545
+ return None
546
+ try:
547
+ data = response.json()
548
+ except ValueError as e:
549
+ logger.error("Failed to parse models response as JSON: %s", e)
550
+ return None
551
+ if isinstance(data.get("data"), list):
552
+ models: list[str] = []
553
+ for model in data["data"]:
554
+ name = model.get("id") or model.get("name")
555
+ if name:
556
+ models.append(name)
557
+ return models
558
+ else:
559
+ safe_text = redact_secrets(response.text)
560
+ logger.error(
561
+ "Failed to fetch models: status=%s content_type=%s error=%s",
562
+ response.status_code,
563
+ response.headers.get("content-type", "unknown"),
564
+ safe_text[:200],
565
+ )
566
+ except Exception as exc: # pragma: no cover - defensive logging
567
+ logger.error("Error fetching Claude Code models: %s", exc)
568
+ return None
569
+
570
+
571
+ def _build_model_entry(model_name: str, access_token: str, context_length: int) -> dict:
572
+ """Build a single model config entry for claude_models.json."""
573
+ supported_settings = [
574
+ "temperature",
575
+ "extended_thinking",
576
+ "budget_tokens",
577
+ "interleaved_thinking",
578
+ "fast",
579
+ ]
580
+
581
+ # Opus 4-6 models support the effort setting
582
+ lower = model_name.lower()
583
+ if (
584
+ "opus-4-6" in lower
585
+ or "4-6-opus" in lower
586
+ or "opus-4-7" in lower
587
+ or "4-7-opus" in lower
588
+ ):
589
+ supported_settings.append("effort")
590
+
591
+ return {
592
+ "type": "claude_code",
593
+ "name": model_name,
594
+ "custom_endpoint": {
595
+ "url": CLAUDE_CODE_OAUTH_CONFIG["api_base_url"],
596
+ "api_key": access_token,
597
+ "headers": {
598
+ "anthropic-beta": "oauth-2025-04-20,interleaved-thinking-2025-05-14",
599
+ "x-app": "cli",
600
+ "User-Agent": "claude-cli/2.0.61 (external, cli)",
601
+ },
602
+ },
603
+ "context_length": context_length,
604
+ "oauth_source": "claude-code-plugin",
605
+ "supported_settings": supported_settings,
606
+ }
607
+
608
+
609
+ def add_models_to_extra_config(models: list[str]) -> bool:
610
+ try:
611
+ # Filter to only latest haiku, sonnet, and opus models
612
+ filtered_models = filter_latest_claude_models(
613
+ models, max_per_family={"default": 1, "opus": 3}
614
+ )
615
+
616
+ # Start fresh - overwrite the file on every auth instead of loading existing
617
+ claude_models = {}
618
+ added = 0
619
+ access_token = get_valid_access_token() or ""
620
+ prefix = CLAUDE_CODE_OAUTH_CONFIG["prefix"]
621
+ default_ctx = CLAUDE_CODE_OAUTH_CONFIG["default_context_length"]
622
+ long_ctx = CLAUDE_CODE_OAUTH_CONFIG["long_context_length"]
623
+ long_ctx_models = CLAUDE_CODE_OAUTH_CONFIG["long_context_models"]
624
+
625
+ for model_name in filtered_models:
626
+ prefixed = f"{prefix}{model_name}"
627
+ claude_models[prefixed] = _build_model_entry(
628
+ model_name, access_token, default_ctx
629
+ )
630
+ added += 1
631
+
632
+ # Create a "-long" variant with extended context for eligible models
633
+ if model_name in long_ctx_models:
634
+ long_prefixed = f"{prefix}{model_name}-long"
635
+ claude_models[long_prefixed] = _build_model_entry(
636
+ model_name, access_token, long_ctx
637
+ )
638
+ added += 1
639
+
640
+ if save_claude_models(claude_models):
641
+ logger.info("Added %s Claude Code models", added)
642
+ return True
643
+ except Exception as exc: # pragma: no cover - defensive logging
644
+ logger.error("Error adding models to config: %s", exc)
645
+ return False
646
+
647
+
648
+ def remove_claude_code_models() -> int:
649
+ try:
650
+ claude_models = load_claude_models()
651
+ to_remove = [
652
+ name
653
+ for name, config in claude_models.items()
654
+ if config.get("oauth_source") == "claude-code-plugin"
655
+ ]
656
+ if not to_remove:
657
+ return 0
658
+ for model_name in to_remove:
659
+ claude_models.pop(model_name, None)
660
+ if save_claude_models(claude_models):
661
+ return len(to_remove)
662
+ except Exception as exc: # pragma: no cover - defensive logging
663
+ logger.error("Error removing Claude Code models: %s", exc)
664
+ return 0
@@ -0,0 +1,11 @@
1
+ """GitHub Copilot auth plugin for Muse.
2
+
3
+ Authenticates with GitHub (or GitHub Enterprise) via the browser-based
4
+ Device Flow and exchanges the resulting OAuth token for a short-lived
5
+ Copilot session token to access the Copilot API.
6
+
7
+ Commands:
8
+ - ``/copilot-login`` — authenticate via browser
9
+ - ``/copilot-status`` — show auth & model status
10
+ - ``/copilot-logout`` — remove tokens and registered models
11
+ """