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,983 @@
1
+ """Interactive TUI for configuring per-model settings.
2
+
3
+ Provides a beautiful interface for viewing and modifying model-specific
4
+ settings like temperature and seed on a per-model basis.
5
+ """
6
+
7
+ import sys
8
+ import time
9
+
10
+ from prompt_toolkit import Application
11
+ from prompt_toolkit.key_binding import KeyBindings
12
+ from prompt_toolkit.layout import Dimension, Layout, VSplit, Window
13
+ from prompt_toolkit.layout.controls import FormattedTextControl
14
+ from prompt_toolkit.widgets import Frame
15
+
16
+ from code_muse.command_line.pagination import (
17
+ ensure_visible_page,
18
+ get_page_bounds,
19
+ get_page_for_index,
20
+ get_total_pages,
21
+ )
22
+ from code_muse.config import (
23
+ get_all_model_settings,
24
+ get_global_model_name,
25
+ get_openai_reasoning_effort,
26
+ get_openai_reasoning_summary,
27
+ get_openai_verbosity,
28
+ model_supports_setting,
29
+ set_model_setting,
30
+ set_openai_reasoning_effort,
31
+ set_openai_reasoning_summary,
32
+ set_openai_verbosity,
33
+ )
34
+ from code_muse.messaging import emit_info
35
+ from code_muse.model_factory import ModelFactory
36
+ from code_muse.tools.command_runner import set_awaiting_user_input
37
+
38
+ # Pagination config
39
+ MODELS_PER_PAGE = 15
40
+
41
+ # Setting definitions with metadata
42
+ # Numeric settings have min/max/step, choice settings have choices list
43
+ SETTING_DEFINITIONS: dict[str, dict] = {
44
+ "temperature": {
45
+ "name": "Temperature",
46
+ "description": "Controls randomness (0.0-1.0). Lower = more deterministic, higher = more creative.",
47
+ "type": "numeric",
48
+ "min": 0.0,
49
+ "max": 1.0,
50
+ "step": 0.05,
51
+ "default": None, # None means use model default
52
+ "format": "{:.2f}",
53
+ },
54
+ "seed": {
55
+ "name": "Seed",
56
+ "description": "Random seed for reproducible outputs. Set to same value for consistent results.",
57
+ "type": "numeric",
58
+ "min": 0,
59
+ "max": 999999,
60
+ "step": 1,
61
+ "default": None,
62
+ "format": "{:.0f}",
63
+ },
64
+ "top_p": {
65
+ "name": "Top-P (Nucleus Sampling)",
66
+ "description": "Controls token diversity. 0.0 = least random (only most likely tokens), 1.0 = most random (sample from all tokens).",
67
+ "type": "numeric",
68
+ "min": 0.0,
69
+ "max": 1.0,
70
+ "step": 0.05,
71
+ "default": None,
72
+ "format": "{:.2f}",
73
+ },
74
+ "reasoning_effort": {
75
+ "name": "Reasoning Effort",
76
+ "description": "Controls how much effort GPT-5 models spend on reasoning. Higher = more thorough but slower.",
77
+ "type": "choice",
78
+ "choices": ["minimal", "low", "medium", "high", "xhigh"],
79
+ "default": "medium",
80
+ },
81
+ "summary": {
82
+ "name": "Reasoning Summary",
83
+ "description": "Controls whether OpenAI Responses models return auto, concise, or detailed reasoning summaries.",
84
+ "type": "choice",
85
+ "choices": ["auto", "concise", "detailed"],
86
+ "default": "auto",
87
+ },
88
+ "verbosity": {
89
+ "name": "Verbosity",
90
+ "description": "Controls response length. Low = concise, Medium = balanced, High = verbose.",
91
+ "type": "choice",
92
+ "choices": ["low", "medium", "high"],
93
+ "default": "medium",
94
+ },
95
+ "extended_thinking": {
96
+ "name": "Extended Thinking",
97
+ "description": "Controls extended thinking mode. 'enabled' = classic thinking with budget_tokens, 'adaptive' = model decides when/how much to think (no budget), 'off' = disabled.",
98
+ "type": "choice",
99
+ "choices": ["enabled", "adaptive", "off"],
100
+ "default": "enabled",
101
+ },
102
+ "budget_tokens": {
103
+ "name": "Thinking Budget (tokens)",
104
+ "description": "Max tokens for extended thinking. Only used when extended_thinking is 'enabled'.",
105
+ "type": "numeric",
106
+ "min": 1024,
107
+ "max": 131072,
108
+ "step": 1024,
109
+ "default": 10000,
110
+ "format": "{:.0f}",
111
+ },
112
+ "interleaved_thinking": {
113
+ "name": "Interleaved Thinking",
114
+ "description": "Enable thinking between tool calls (Claude 4 only: Opus 4.5, Opus 4.1, Opus 4, Sonnet 4). Adds beta header. WARNING: On Vertex/Bedrock, this FAILS for non-Claude 4 models!",
115
+ "type": "boolean",
116
+ "default": False,
117
+ },
118
+ "clear_thinking": {
119
+ "name": "Clear Thinking",
120
+ "description": "False = Preserved Thinking (keep <think> blocks visible). True = strip thinking from responses.",
121
+ "type": "boolean",
122
+ "default": False,
123
+ },
124
+ "thinking_enabled": {
125
+ "name": "Thinking Enabled",
126
+ "description": "Enable thinking mode for Gemini 3 Pro models. When enabled, the model will show its reasoning process.",
127
+ "type": "boolean",
128
+ "default": True,
129
+ },
130
+ "thinking_level": {
131
+ "name": "Thinking Level",
132
+ "description": "Controls the depth of thinking for Gemini 3 Pro models. Low = faster responses, High = more thorough reasoning.",
133
+ "type": "choice",
134
+ "choices": ["low", "high"],
135
+ "default": "low",
136
+ },
137
+ "effort": {
138
+ "name": "Effort",
139
+ "description": "Controls how much effort the model spends on its response (Opus 4-6 only). Low = fast, Max = most thorough.",
140
+ "type": "choice",
141
+ "choices": ["low", "medium", "high", "max"],
142
+ "default": "high",
143
+ },
144
+ }
145
+
146
+
147
+ def _load_all_model_names() -> list[str]:
148
+ """Load all available model names from config."""
149
+ models_config = ModelFactory.load_config()
150
+ return list(models_config.keys())
151
+
152
+
153
+ def _get_model_display_settings(model_name: str) -> dict:
154
+ """Get model settings merged with global OpenAI controls for display."""
155
+ settings = get_all_model_settings(model_name)
156
+
157
+ if model_supports_setting(model_name, "reasoning_effort"):
158
+ settings["reasoning_effort"] = get_openai_reasoning_effort()
159
+ if model_supports_setting(model_name, "summary"):
160
+ settings["summary"] = get_openai_reasoning_summary()
161
+ if model_supports_setting(model_name, "verbosity"):
162
+ settings["verbosity"] = get_openai_verbosity()
163
+
164
+ return settings
165
+
166
+
167
+ def _get_setting_choices(setting_key: str, model_name: str | None = None) -> list[str]:
168
+ """Get the available choices for a setting, filtered by model capabilities.
169
+
170
+ For reasoning_effort, only codex models support 'xhigh' - regular GPT-5.2
171
+ models are capped at 'high'.
172
+
173
+ Args:
174
+ setting_key: The setting name (e.g., 'reasoning_effort', 'verbosity')
175
+ model_name: Optional model name to filter choices for
176
+
177
+ Returns:
178
+ list of valid choices for this setting and model combination.
179
+ """
180
+ setting_def = SETTING_DEFINITIONS.get(setting_key, {})
181
+ if setting_def.get("type") != "choice":
182
+ return []
183
+
184
+ base_choices = setting_def.get("choices", [])
185
+
186
+ # For reasoning_effort, filter 'xhigh' based on model support
187
+ if setting_key == "reasoning_effort" and model_name:
188
+ models_config = ModelFactory.load_config()
189
+ model_config = models_config.get(model_name, {})
190
+
191
+ # Check if model supports xhigh reasoning
192
+ supports_xhigh = model_config.get("supports_xhigh_reasoning", False)
193
+
194
+ if not supports_xhigh:
195
+ # Remove xhigh from choices for non-codex models
196
+ return [c for c in base_choices if c != "xhigh"]
197
+
198
+ return base_choices
199
+
200
+
201
+ def _get_setting_default(setting_key: str, model_name: str | None = None):
202
+ """Resolve the effective default for a setting, per-model when applicable.
203
+
204
+ Most settings have a static default declared in SETTING_DEFINITIONS, but
205
+ some (like ``extended_thinking``) have model-specific runtime defaults —
206
+ e.g. Opus 4.6/4.7 default to ``"adaptive"`` while other Claude models
207
+ default to ``"enabled"``. We defer to ``get_default_extended_thinking``
208
+ as the single source of truth so the UI and runtime never disagree.
209
+
210
+ Args:
211
+ setting_key: The setting name (e.g. ``"extended_thinking"``).
212
+ model_name: Optional model name for per-model defaults.
213
+
214
+ Returns:
215
+ The default value (may be ``None``).
216
+ """
217
+ if setting_key == "extended_thinking" and model_name:
218
+ # Import here to avoid a circular import at module load.
219
+ from code_muse.model_utils import get_default_extended_thinking
220
+
221
+ return get_default_extended_thinking(model_name)
222
+
223
+ setting_def = SETTING_DEFINITIONS.get(setting_key, {})
224
+ return setting_def.get("default")
225
+
226
+
227
+ class ModelSettingsMenu:
228
+ """Interactive TUI for model settings configuration.
229
+
230
+ Two-level navigation:
231
+ - Level 1: list of all available models (paginated)
232
+ - Level 2: Settings for the selected model
233
+ """
234
+
235
+ def __init__(self):
236
+ """Initialize the settings menu."""
237
+ self.all_models = _load_all_model_names()
238
+ self.current_model_name = get_global_model_name()
239
+
240
+ # Navigation state
241
+ self.view_mode = "models" # "models" or "settings"
242
+ self.model_index = 0 # Index in model list (absolute)
243
+ self.setting_index = 0 # Index in settings list
244
+
245
+ # Pagination state
246
+ self.page = 0
247
+ self.page_size = MODELS_PER_PAGE
248
+
249
+ # Try to pre-select the current model and set correct page
250
+ if self.current_model_name in self.all_models:
251
+ self.model_index = self.all_models.index(self.current_model_name)
252
+ self.page = get_page_for_index(self.model_index, self.page_size)
253
+
254
+ # Editing state
255
+ self.editing_mode = False
256
+ self.edit_value: float | None = None
257
+ self.result_changed = False
258
+
259
+ # Cache for selected model's settings
260
+ self.selected_model: str | None = None
261
+ self.supported_settings: list[str] = []
262
+ self.current_settings: dict = {}
263
+
264
+ @property
265
+ def total_pages(self) -> int:
266
+ """Calculate total number of pages."""
267
+ return get_total_pages(len(self.all_models), self.page_size)
268
+
269
+ @property
270
+ def page_start(self) -> int:
271
+ """Get the starting index for the current page."""
272
+ start, _ = get_page_bounds(self.page, len(self.all_models), self.page_size)
273
+ return start
274
+
275
+ @property
276
+ def page_end(self) -> int:
277
+ """Get the ending index (exclusive) for the current page."""
278
+ _, end = get_page_bounds(self.page, len(self.all_models), self.page_size)
279
+ return end
280
+
281
+ @property
282
+ def models_on_page(self) -> list[str]:
283
+ """Get the models visible on the current page."""
284
+ return self.all_models[self.page_start : self.page_end]
285
+
286
+ def _ensure_selection_visible(self):
287
+ """Ensure the current selection is on the visible page."""
288
+ self.page = ensure_visible_page(
289
+ self.model_index,
290
+ self.page,
291
+ len(self.all_models),
292
+ self.page_size,
293
+ )
294
+
295
+ def _get_supported_settings(self, model_name: str) -> list[str]:
296
+ """Get list of settings supported by a model."""
297
+ supported = []
298
+ for setting_key in SETTING_DEFINITIONS:
299
+ if model_supports_setting(model_name, setting_key):
300
+ supported.append(setting_key)
301
+ return supported
302
+
303
+ def _load_model_settings(self, model_name: str):
304
+ """Load settings for a specific model."""
305
+ self.selected_model = model_name
306
+ self.supported_settings = self._get_supported_settings(model_name)
307
+ self.current_settings = _get_model_display_settings(model_name)
308
+
309
+ self.setting_index = 0
310
+
311
+ def _get_current_value(self, setting: str):
312
+ """Get the current value for a setting."""
313
+ return self.current_settings.get(setting)
314
+
315
+ def _format_value(self, setting: str, value) -> str:
316
+ """Format a setting value for display."""
317
+ setting_def = SETTING_DEFINITIONS.get(setting)
318
+ if setting_def is None:
319
+ # Unknown/stale setting from saved config — just stringify it
320
+ return str(value) if value is not None else "(unknown)"
321
+
322
+ if value is None:
323
+ default = _get_setting_default(setting, self.selected_model)
324
+ if default is not None:
325
+ return f"(default: {default})"
326
+ return "(model default)"
327
+
328
+ if setting_def.get("type") == "choice":
329
+ return str(value)
330
+
331
+ if setting_def.get("type") == "boolean":
332
+ return "Enabled" if value else "Disabled"
333
+
334
+ fmt = setting_def.get("format", "{:.2f}")
335
+ return fmt.format(value)
336
+
337
+ def _render_main_list(self) -> list:
338
+ """Render the main list panel (models or settings)."""
339
+ lines = []
340
+
341
+ if self.view_mode == "models":
342
+ # Header with page indicator
343
+ lines.append(("bold cyan", "[Run] Select a Model to Configure"))
344
+ if self.total_pages > 1:
345
+ lines.append(
346
+ (
347
+ "fg:ansibrightblack",
348
+ f" (Page {self.page + 1}/{self.total_pages})",
349
+ )
350
+ )
351
+ lines.append(("", "\n\n"))
352
+
353
+ if not self.all_models:
354
+ lines.append(("fg:ansiyellow", " No models available."))
355
+ lines.append(("", "\n\n"))
356
+ self._add_model_nav_hints(lines)
357
+ return lines
358
+
359
+ # Only render models on the current page
360
+ for i, model_name in enumerate(self.models_on_page):
361
+ absolute_index = self.page_start + i
362
+ is_selected = absolute_index == self.model_index
363
+ is_current = model_name == self.current_model_name
364
+
365
+ prefix = " › " if is_selected else " "
366
+ style = "fg:ansiwhite bold" if is_selected else "fg:ansibrightblack"
367
+
368
+ # Check if model has any custom settings
369
+ model_settings = get_all_model_settings(model_name)
370
+ has_settings = len(model_settings) > 0
371
+
372
+ lines.append((style, f"{prefix}{model_name}"))
373
+
374
+ # Show indicators
375
+ if is_current:
376
+ lines.append(("fg:ansigreen", " (active)"))
377
+ if has_settings:
378
+ lines.append(("fg:ansicyan", " ⚙"))
379
+
380
+ lines.append(("", "\n"))
381
+
382
+ lines.append(("", "\n"))
383
+ self._add_model_nav_hints(lines)
384
+ else:
385
+ # Settings view
386
+ lines.append(("bold cyan", f" ⚙ Settings for {self.selected_model}"))
387
+ lines.append(("", "\n\n"))
388
+
389
+ if not self.supported_settings:
390
+ lines.append(
391
+ ("fg:ansiyellow", " No configurable settings for this model.")
392
+ )
393
+ lines.append(("", "\n\n"))
394
+ self._add_settings_nav_hints(lines)
395
+ return lines
396
+
397
+ for i, setting_key in enumerate(self.supported_settings):
398
+ setting_def = SETTING_DEFINITIONS[setting_key]
399
+ is_selected = i == self.setting_index
400
+ current_value = self._get_current_value(setting_key)
401
+
402
+ # Show editing state if in edit mode for this setting
403
+ if is_selected and self.editing_mode:
404
+ display_value = self._format_value(setting_key, self.edit_value)
405
+ prefix = " ✏️ "
406
+ style = "fg:ansigreen bold"
407
+ else:
408
+ display_value = self._format_value(setting_key, current_value)
409
+ prefix = " › " if is_selected else " "
410
+ style = "fg:ansiwhite" if is_selected else "fg:ansibrightblack"
411
+
412
+ # Setting name and value
413
+ lines.append((style, f"{prefix}{setting_def['name']}: "))
414
+ if current_value is not None or (is_selected and self.editing_mode):
415
+ lines.append(("fg:ansicyan", display_value))
416
+ else:
417
+ lines.append(("fg:ansibrightblack dim", display_value))
418
+ lines.append(("", "\n"))
419
+
420
+ lines.append(("", "\n"))
421
+ self._add_settings_nav_hints(lines)
422
+
423
+ return lines
424
+
425
+ def _add_model_nav_hints(self, lines: list):
426
+ """Add navigation hints for model list view."""
427
+ lines.append(("", "\n"))
428
+ lines.append(("fg:ansibrightblack", " ↑/↓ "))
429
+ lines.append(("", "Navigate models\n"))
430
+ if self.total_pages > 1:
431
+ lines.append(("fg:ansibrightblack", " PgUp/PgDn "))
432
+ lines.append(("", "Change page\n"))
433
+ lines.append(("fg:ansigreen", " Enter "))
434
+ lines.append(("", "Configure model\n"))
435
+ lines.append(("fg:ansiyellow", " Esc "))
436
+ lines.append(("", "Exit\n"))
437
+
438
+ def _add_settings_nav_hints(self, lines: list):
439
+ """Add navigation hints for settings view."""
440
+ lines.append(("", "\n"))
441
+
442
+ if self.editing_mode:
443
+ lines.append(("fg:ansibrightblack", " ←/→ "))
444
+ lines.append(("", "Adjust value\n"))
445
+ lines.append(("fg:ansigreen", " Enter "))
446
+ lines.append(("", "Save\n"))
447
+ lines.append(("fg:ansiyellow", " Esc "))
448
+ lines.append(("", "Cancel edit\n"))
449
+ lines.append(("fg:ansired", " d "))
450
+ lines.append(("", "Reset to default\n"))
451
+ else:
452
+ lines.append(("fg:ansibrightblack", " ↑/↓ "))
453
+ lines.append(("", "Navigate settings\n"))
454
+ lines.append(("fg:ansigreen", " Enter "))
455
+ lines.append(("", "Edit setting\n"))
456
+ lines.append(("fg:ansired", " d "))
457
+ lines.append(("", "Reset to default\n"))
458
+ lines.append(("fg:ansiyellow", " Esc "))
459
+ lines.append(("", "Back to models\n"))
460
+
461
+ def _render_details_panel(self) -> list:
462
+ """Render the details/help panel."""
463
+ lines = []
464
+
465
+ if self.view_mode == "models":
466
+ lines.append(("bold cyan", " Model Info"))
467
+ lines.append(("", "\n\n"))
468
+
469
+ if not self.all_models:
470
+ lines.append(("fg:ansibrightblack", " No models available."))
471
+ return lines
472
+
473
+ model_name = self.all_models[self.model_index]
474
+ is_current = model_name == self.current_model_name
475
+
476
+ lines.append(("bold", f" {model_name}"))
477
+ lines.append(("", "\n\n"))
478
+
479
+ if is_current:
480
+ lines.append(("fg:ansigreen", " ✓ Currently active model"))
481
+ lines.append(("", "\n\n"))
482
+
483
+ # Show current settings for this model
484
+ model_settings = _get_model_display_settings(model_name)
485
+ if model_settings:
486
+ lines.append(("bold", " Effective Settings:"))
487
+ lines.append(("", "\n"))
488
+ for setting_key, value in model_settings.items():
489
+ setting_def = SETTING_DEFINITIONS.get(setting_key, {})
490
+ name = setting_def.get("name", setting_key)
491
+ display = self._format_value(setting_key, value)
492
+ lines.append(("fg:ansicyan", f" {name}: {display}"))
493
+ lines.append(("", "\n"))
494
+ else:
495
+ lines.append(("fg:ansibrightblack", " Using all default settings"))
496
+ lines.append(("", "\n"))
497
+
498
+ # Show supported settings
499
+ supported = self._get_supported_settings(model_name)
500
+ lines.append(("", "\n"))
501
+ lines.append(("bold", " Configurable Settings:"))
502
+ lines.append(("", "\n"))
503
+ if supported:
504
+ for s in supported:
505
+ setting_def = SETTING_DEFINITIONS.get(s, {})
506
+ name = setting_def.get("name", s)
507
+ lines.append(("fg:ansibrightblack", f" • {name}"))
508
+ lines.append(("", "\n"))
509
+ else:
510
+ lines.append(("fg:ansibrightblack dim", " None"))
511
+ lines.append(("", "\n"))
512
+
513
+ # Show pagination info at the bottom of details
514
+ if self.total_pages > 1:
515
+ lines.append(("", "\n"))
516
+ lines.append(
517
+ (
518
+ "fg:ansibrightblack dim",
519
+ f" Model {self.model_index + 1} of {len(self.all_models)}",
520
+ )
521
+ )
522
+ lines.append(("", "\n"))
523
+
524
+ else:
525
+ # Settings detail view
526
+ lines.append(("bold cyan", " Setting Details"))
527
+ lines.append(("", "\n\n"))
528
+
529
+ if not self.supported_settings:
530
+ lines.append(
531
+ ("fg:ansibrightblack", " This model doesn't expose any settings.")
532
+ )
533
+ return lines
534
+
535
+ setting_key = self.supported_settings[self.setting_index]
536
+ setting_def = SETTING_DEFINITIONS[setting_key]
537
+ current_value = self._get_current_value(setting_key)
538
+
539
+ # Setting name
540
+ lines.append(("bold", f" {setting_def['name']}"))
541
+ lines.append(("", "\n"))
542
+
543
+ # Show if this is a global setting
544
+ if setting_key in ("reasoning_effort", "verbosity"):
545
+ lines.append(
546
+ (
547
+ "fg:ansiyellow",
548
+ " ⚠ Global setting (applies to all GPT-5 models)",
549
+ )
550
+ )
551
+ lines.append(("", "\n\n"))
552
+
553
+ # Description
554
+ lines.append(("fg:ansibrightblack", f" {setting_def['description']}"))
555
+ lines.append(("", "\n\n"))
556
+
557
+ # Range/choices info
558
+ if setting_def.get("type") == "choice":
559
+ lines.append(("bold", " Options:"))
560
+ lines.append(("", "\n"))
561
+ # Get filtered choices based on model capabilities
562
+ choices = _get_setting_choices(setting_key, self.selected_model)
563
+ lines.append(
564
+ (
565
+ "fg:ansibrightblack",
566
+ f" {' | '.join(choices)}",
567
+ )
568
+ )
569
+ elif setting_def.get("type") == "boolean":
570
+ lines.append(("bold", " Options:"))
571
+ lines.append(("", "\n"))
572
+ lines.append(
573
+ (
574
+ "fg:ansibrightblack",
575
+ " Enabled | Disabled",
576
+ )
577
+ )
578
+ else:
579
+ lines.append(("bold", " Range:"))
580
+ lines.append(("", "\n"))
581
+ lines.append(
582
+ (
583
+ "fg:ansibrightblack",
584
+ f" Min: {setting_def['min']} Max: {setting_def['max']} Step: {setting_def['step']}",
585
+ )
586
+ )
587
+ lines.append(("", "\n\n"))
588
+
589
+ # Current value
590
+ lines.append(("bold", " Current Value:"))
591
+ lines.append(("", "\n"))
592
+ if current_value is not None:
593
+ lines.append(
594
+ (
595
+ "fg:ansicyan",
596
+ f" {self._format_value(setting_key, current_value)}",
597
+ )
598
+ )
599
+ else:
600
+ lines.append(("fg:ansibrightblack dim", " (using model default)"))
601
+ lines.append(("", "\n\n"))
602
+
603
+ # Editing hint
604
+ if self.editing_mode:
605
+ lines.append(("fg:ansigreen bold", " ✏️ EDITING MODE"))
606
+ lines.append(("", "\n"))
607
+ if self.edit_value is not None:
608
+ lines.append(
609
+ (
610
+ "fg:ansicyan",
611
+ f" New value: {self._format_value(setting_key, self.edit_value)}",
612
+ )
613
+ )
614
+ else:
615
+ lines.append(
616
+ ("fg:ansibrightblack", " New value: (model default)")
617
+ )
618
+ lines.append(("", "\n"))
619
+
620
+ return lines
621
+
622
+ def _enter_settings_view(self):
623
+ """Enter settings view for the selected model."""
624
+ if not self.all_models:
625
+ return
626
+ model_name = self.all_models[self.model_index]
627
+ self._load_model_settings(model_name)
628
+ self.view_mode = "settings"
629
+
630
+ def _back_to_models(self):
631
+ """Go back to model list view."""
632
+ self.view_mode = "models"
633
+ self.editing_mode = False
634
+ self.edit_value = None
635
+
636
+ def _start_editing(self):
637
+ """Enter editing mode for the selected setting."""
638
+ if not self.supported_settings:
639
+ return
640
+
641
+ setting_key = self.supported_settings[self.setting_index]
642
+ setting_def = SETTING_DEFINITIONS[setting_key]
643
+ current = self._get_current_value(setting_key)
644
+
645
+ # Start with current value, or default if not set
646
+ if current is not None:
647
+ self.edit_value = current
648
+ elif setting_def.get("type") == "choice":
649
+ # For choice settings, start with the default (using filtered choices)
650
+ choices = _get_setting_choices(setting_key, self.selected_model)
651
+ resolved = _get_setting_default(setting_key, self.selected_model)
652
+ self.edit_value = resolved or (choices[0] if choices else None)
653
+ elif setting_def.get("type") == "boolean":
654
+ resolved = _get_setting_default(setting_key, self.selected_model)
655
+ self.edit_value = bool(resolved) if resolved is not None else False
656
+ else:
657
+ # Default to a sensible starting point for numeric
658
+ if setting_key == "temperature":
659
+ self.edit_value = 0.7
660
+ elif setting_key == "top_p":
661
+ self.edit_value = 0.9 # Common default for top_p
662
+ elif setting_key == "seed":
663
+ self.edit_value = 42
664
+ elif setting_key == "budget_tokens":
665
+ self.edit_value = 10000
666
+ else:
667
+ self.edit_value = (setting_def["min"] + setting_def["max"]) / 2
668
+
669
+ self.editing_mode = True
670
+
671
+ def _adjust_value(self, direction: int):
672
+ """Adjust the current edit value."""
673
+ if not self.editing_mode or self.edit_value is None:
674
+ return
675
+
676
+ setting_key = self.supported_settings[self.setting_index]
677
+ setting_def = SETTING_DEFINITIONS[setting_key]
678
+
679
+ if setting_def.get("type") == "choice":
680
+ # Cycle through filtered choices based on model capabilities
681
+ choices = _get_setting_choices(setting_key, self.selected_model)
682
+ current_idx = (
683
+ choices.index(self.edit_value) if self.edit_value in choices else 0
684
+ )
685
+ new_idx = (current_idx + direction) % len(choices)
686
+ self.edit_value = choices[new_idx]
687
+ elif setting_def.get("type") == "boolean":
688
+ # Toggle boolean
689
+ self.edit_value = not self.edit_value
690
+ else:
691
+ # Numeric adjustment
692
+ step = setting_def["step"]
693
+ new_value = self.edit_value + (direction * step)
694
+ # Clamp to range
695
+ new_value = max(setting_def["min"], min(setting_def["max"], new_value))
696
+ self.edit_value = new_value
697
+
698
+ def _save_edit(self):
699
+ """Save the current edit value."""
700
+ if not self.editing_mode or self.selected_model is None:
701
+ return
702
+
703
+ setting_key = self.supported_settings[self.setting_index]
704
+
705
+ # Handle global OpenAI settings specially
706
+ if setting_key == "reasoning_effort":
707
+ if self.edit_value is not None:
708
+ set_openai_reasoning_effort(self.edit_value)
709
+ elif setting_key == "summary":
710
+ if self.edit_value is not None:
711
+ set_openai_reasoning_summary(self.edit_value)
712
+ elif setting_key == "verbosity":
713
+ if self.edit_value is not None:
714
+ set_openai_verbosity(self.edit_value)
715
+ else:
716
+ # Standard per-model setting
717
+ set_model_setting(self.selected_model, setting_key, self.edit_value)
718
+
719
+ # Update local cache
720
+ if self.edit_value is not None:
721
+ self.current_settings[setting_key] = self.edit_value
722
+ elif setting_key in self.current_settings:
723
+ del self.current_settings[setting_key]
724
+
725
+ self.result_changed = True
726
+ self.editing_mode = False
727
+ self.edit_value = None
728
+
729
+ def _cancel_edit(self):
730
+ """Cancel the current edit."""
731
+ self.editing_mode = False
732
+ self.edit_value = None
733
+
734
+ def _reset_to_default(self):
735
+ """Reset the current setting to model default."""
736
+ if not self.supported_settings or self.selected_model is None:
737
+ return
738
+
739
+ setting_key = self.supported_settings[self.setting_index]
740
+
741
+ if self.editing_mode:
742
+ # Reset edit value to default
743
+ self.edit_value = _get_setting_default(setting_key, self.selected_model)
744
+ else:
745
+ # Handle global OpenAI settings - reset to their defaults
746
+ if setting_key == "reasoning_effort":
747
+ set_openai_reasoning_effort("medium") # Default
748
+ self.current_settings[setting_key] = "medium"
749
+ elif setting_key == "summary":
750
+ set_openai_reasoning_summary("auto") # Default
751
+ self.current_settings[setting_key] = "auto"
752
+ elif setting_key == "verbosity":
753
+ set_openai_verbosity("medium") # Default
754
+ self.current_settings[setting_key] = "medium"
755
+ else:
756
+ # Standard per-model setting
757
+ set_model_setting(self.selected_model, setting_key, None)
758
+ if setting_key in self.current_settings:
759
+ del self.current_settings[setting_key]
760
+ self.result_changed = True
761
+
762
+ def _page_up(self):
763
+ """Go to previous page."""
764
+ if self.page > 0:
765
+ self.page -= 1
766
+ # Move selection to first item on new page
767
+ self.model_index = self.page_start
768
+
769
+ def _page_down(self):
770
+ """Go to next page."""
771
+ if self.page < self.total_pages - 1:
772
+ self.page += 1
773
+ # Move selection to first item on new page
774
+ self.model_index = self.page_start
775
+
776
+ def update_display(self):
777
+ """Update the display."""
778
+ self.menu_control.text = self._render_main_list()
779
+ self.details_control.text = self._render_details_panel()
780
+
781
+ def run(self) -> bool:
782
+ """Run the interactive settings menu.
783
+
784
+ Returns:
785
+ True if settings were changed, False otherwise.
786
+ """
787
+ # Build UI
788
+ self.menu_control = FormattedTextControl(text="")
789
+ self.details_control = FormattedTextControl(text="")
790
+
791
+ menu_window = Window(
792
+ content=self.menu_control, wrap_lines=True, width=Dimension(weight=40)
793
+ )
794
+ details_window = Window(
795
+ content=self.details_control, wrap_lines=True, width=Dimension(weight=60)
796
+ )
797
+
798
+ menu_frame = Frame(menu_window, width=Dimension(weight=40), title="Models")
799
+ details_frame = Frame(
800
+ details_window, width=Dimension(weight=60), title="Details"
801
+ )
802
+
803
+ root_container = VSplit([menu_frame, details_frame])
804
+
805
+ # Key bindings
806
+ kb = KeyBindings()
807
+
808
+ @kb.add("up")
809
+ @kb.add("c-p") # Ctrl+P = previous (Emacs-style)
810
+ def _(event):
811
+ if self.view_mode == "models":
812
+ if self.model_index > 0:
813
+ self.model_index -= 1
814
+ self._ensure_selection_visible()
815
+ self.update_display()
816
+ else:
817
+ if not self.editing_mode and self.setting_index > 0:
818
+ self.setting_index -= 1
819
+ self.update_display()
820
+
821
+ @kb.add("down")
822
+ @kb.add("c-n") # Ctrl+N = next (Emacs-style)
823
+ def _(event):
824
+ if self.view_mode == "models":
825
+ if self.model_index < len(self.all_models) - 1:
826
+ self.model_index += 1
827
+ self._ensure_selection_visible()
828
+ self.update_display()
829
+ else:
830
+ if (
831
+ not self.editing_mode
832
+ and self.setting_index < len(self.supported_settings) - 1
833
+ ):
834
+ self.setting_index += 1
835
+ self.update_display()
836
+
837
+ @kb.add("pageup")
838
+ def _(event):
839
+ if self.view_mode == "models":
840
+ self._page_up()
841
+ self.update_display()
842
+
843
+ @kb.add("pagedown")
844
+ def _(event):
845
+ if self.view_mode == "models":
846
+ self._page_down()
847
+ self.update_display()
848
+
849
+ @kb.add("left")
850
+ def _(event):
851
+ if self.view_mode == "settings" and self.editing_mode:
852
+ self._adjust_value(-1)
853
+ self.update_display()
854
+ elif self.view_mode == "models":
855
+ # Left arrow also goes to previous page
856
+ self._page_up()
857
+ self.update_display()
858
+
859
+ @kb.add("right")
860
+ def _(event):
861
+ if self.view_mode == "settings" and self.editing_mode:
862
+ self._adjust_value(1)
863
+ self.update_display()
864
+ elif self.view_mode == "models":
865
+ # Right arrow also goes to next page
866
+ self._page_down()
867
+ self.update_display()
868
+
869
+ @kb.add("enter")
870
+ def _(event):
871
+ if self.view_mode == "models":
872
+ self._enter_settings_view()
873
+ self.update_display()
874
+ else:
875
+ if self.editing_mode:
876
+ self._save_edit()
877
+ else:
878
+ self._start_editing()
879
+ self.update_display()
880
+
881
+ @kb.add("escape")
882
+ def _(event):
883
+ if self.view_mode == "settings":
884
+ if self.editing_mode:
885
+ self._cancel_edit()
886
+ self.update_display()
887
+ else:
888
+ self._back_to_models()
889
+ self.update_display()
890
+ else:
891
+ # At model list level, ESC closes the TUI
892
+ event.app.exit()
893
+
894
+ @kb.add("d")
895
+ def _(event):
896
+ if self.view_mode == "settings":
897
+ self._reset_to_default()
898
+ self.update_display()
899
+
900
+ @kb.add("c-c")
901
+ def _(event):
902
+ if self.editing_mode:
903
+ self._cancel_edit()
904
+ event.app.exit()
905
+
906
+ layout = Layout(root_container)
907
+ app = Application(
908
+ layout=layout,
909
+ key_bindings=kb,
910
+ full_screen=False,
911
+ mouse_support=False,
912
+ )
913
+
914
+ set_awaiting_user_input(True)
915
+
916
+ # Enter alternate screen buffer
917
+ sys.stdout.write("\033[?1049h")
918
+ sys.stdout.write("\033[2J\033[H")
919
+ sys.stdout.flush()
920
+ time.sleep(0.05)
921
+
922
+ try:
923
+ self.update_display()
924
+ sys.stdout.write("\033[2J\033[H")
925
+ sys.stdout.flush()
926
+
927
+ app.run(in_thread=True)
928
+
929
+ finally:
930
+ sys.stdout.write("\033[?1049l")
931
+ sys.stdout.flush()
932
+ set_awaiting_user_input(False)
933
+
934
+ # Clear exit message
935
+ from code_muse.messaging import emit_info
936
+
937
+ emit_info("✓ Exited model settings")
938
+
939
+ return self.result_changed
940
+
941
+
942
+ def interactive_model_settings(model_name: str | None = None) -> bool:
943
+ """Show interactive TUI to configure model settings.
944
+
945
+ Args:
946
+ model_name: Deprecated - the TUI now shows all models.
947
+ This parameter is ignored.
948
+
949
+ Returns:
950
+ True if settings were changed, False otherwise.
951
+ """
952
+ menu = ModelSettingsMenu()
953
+ return menu.run()
954
+
955
+
956
+ def show_model_settings_summary(model_name: str | None = None) -> None:
957
+ """Print a summary of current model settings to the console.
958
+
959
+ Args:
960
+ model_name: Model to show settings for. If None, uses current global model.
961
+ """
962
+ model = model_name or get_global_model_name()
963
+ settings = _get_model_display_settings(model)
964
+
965
+ if not settings:
966
+ emit_info(f"No custom settings configured for {model} (using model defaults)")
967
+ return
968
+
969
+ emit_info(f"Settings for {model}:")
970
+ for setting_key, value in settings.items():
971
+ setting_def = SETTING_DEFINITIONS.get(setting_key, {})
972
+ name = setting_def.get("name", setting_key)
973
+ setting_type = setting_def.get("type")
974
+ if setting_type in ("choice", "boolean"):
975
+ display = (
976
+ str(value)
977
+ if setting_type == "choice"
978
+ else ("Enabled" if value else "Disabled")
979
+ )
980
+ else:
981
+ fmt = setting_def.get("format", "{:.2f}")
982
+ display = fmt.format(value)
983
+ emit_info(f" {name}: {display}")