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,144 @@
1
+ """
2
+ Configuration validation for hooks.
3
+
4
+ Validates hook configuration dictionaries and provides actionable error messages.
5
+ """
6
+
7
+ import logging
8
+ from typing import Any
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ VALID_EVENT_TYPES = [
13
+ "PreToolUse",
14
+ "PostToolUse",
15
+ "SessionStart",
16
+ "SessionEnd",
17
+ "PreCompact",
18
+ "UserPromptSubmit",
19
+ "Notification",
20
+ "Stop",
21
+ "SubagentStop",
22
+ ]
23
+
24
+ VALID_HOOK_TYPES = ["command", "prompt"]
25
+
26
+
27
+ def validate_hooks_config(config: dict[str, Any]) -> tuple[bool, list[str]]:
28
+ """
29
+ Validate a hooks configuration dictionary.
30
+
31
+ Returns:
32
+ Tuple of (is_valid, list_of_error_messages)
33
+ """
34
+ errors: list[str] = []
35
+
36
+ if not isinstance(config, dict):
37
+ return False, ["Configuration must be a dictionary"]
38
+
39
+ for event_type, hook_groups in config.items():
40
+ if event_type.startswith("_"):
41
+ continue # skip comment keys
42
+
43
+ if event_type not in VALID_EVENT_TYPES:
44
+ errors.append(
45
+ f"Unknown event type '{event_type}'. "
46
+ f"Valid types: {', '.join(VALID_EVENT_TYPES)}"
47
+ )
48
+ continue
49
+
50
+ if not isinstance(hook_groups, list):
51
+ errors.append(f"'{event_type}' must be a list of hook groups")
52
+ continue
53
+
54
+ for i, group in enumerate(hook_groups):
55
+ if not isinstance(group, dict):
56
+ errors.append(
57
+ f"'{event_type}[{i}]' must be a dict with 'matcher' and 'hooks'"
58
+ )
59
+ continue
60
+
61
+ if "matcher" not in group:
62
+ errors.append(f"'{event_type}[{i}]' missing required field 'matcher'")
63
+
64
+ if "hooks" not in group:
65
+ errors.append(f"'{event_type}[{i}]' missing required field 'hooks'")
66
+ continue
67
+
68
+ if not isinstance(group["hooks"], list):
69
+ errors.append(f"'{event_type}[{i}].hooks' must be a list")
70
+ continue
71
+
72
+ for j, hook in enumerate(group["hooks"]):
73
+ hook_errors = _validate_hook(event_type, i, j, hook)
74
+ errors.extend(hook_errors)
75
+
76
+ return len(errors) == 0, errors
77
+
78
+
79
+ def _validate_hook(
80
+ event_type: str, group_idx: int, hook_idx: int, hook: Any
81
+ ) -> list[str]:
82
+ errors: list[str] = []
83
+ prefix = f"'{event_type}[{group_idx}].hooks[{hook_idx}]'"
84
+
85
+ if not isinstance(hook, dict):
86
+ return [f"{prefix} must be a dict"]
87
+
88
+ hook_type = hook.get("type")
89
+ if not hook_type:
90
+ errors.append(f"{prefix} missing required field 'type'")
91
+ elif hook_type not in VALID_HOOK_TYPES:
92
+ errors.append(
93
+ f"{prefix} invalid type '{hook_type}'. Must be one of: {', '.join(VALID_HOOK_TYPES)}"
94
+ )
95
+
96
+ if hook_type == "command" and not hook.get("command"):
97
+ errors.append(f"{prefix} missing required field 'command' for type 'command'")
98
+ elif hook_type == "prompt" and not hook.get("prompt") and not hook.get("command"):
99
+ errors.append(
100
+ f"{prefix} missing required field 'prompt' (or 'command') for type 'prompt'"
101
+ )
102
+
103
+ timeout = hook.get("timeout")
104
+ if timeout is not None:
105
+ if not isinstance(timeout, (int, float)) or timeout < 100:
106
+ errors.append(f"{prefix} 'timeout' must be >= 100ms, got: {timeout}")
107
+
108
+ return errors
109
+
110
+
111
+ def format_validation_report(
112
+ is_valid: bool, errors: list[str], suggestions: list[str | None] = None
113
+ ) -> str:
114
+ lines = []
115
+ if is_valid:
116
+ lines.append("✓ Configuration is valid")
117
+ else:
118
+ lines.append(f"✗ Configuration has {len(errors)} error(s):")
119
+ for error in errors:
120
+ lines.append(f" • {error}")
121
+
122
+ if suggestions:
123
+ lines.append("\nSuggestions:")
124
+ for suggestion in suggestions:
125
+ lines.append(f" → {suggestion}")
126
+
127
+ return "\n".join(lines)
128
+
129
+
130
+ def get_config_suggestions(config: dict[str, Any], errors: list[str]) -> list[str]:
131
+ suggestions: list[str] = []
132
+
133
+ for error in errors:
134
+ if "Unknown event type" in error:
135
+ suggestions.append("Valid event types are: " + ", ".join(VALID_EVENT_TYPES))
136
+ break
137
+
138
+ if any("missing required field 'command'" in e for e in errors):
139
+ suggestions.append(
140
+ "Hook commands should be shell commands like: "
141
+ "'bash .claude/hooks/my-hook.sh'"
142
+ )
143
+
144
+ return suggestions
@@ -0,0 +1,360 @@
1
+ """
2
+ HTTP utilities module for Muse.
3
+
4
+ This module provides functions for creating properly configured HTTP clients.
5
+ """
6
+
7
+ import asyncio
8
+ import os
9
+ import socket
10
+ import time
11
+ from dataclasses import dataclass
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ import httpx
16
+
17
+ from code_muse.config import get_http2
18
+
19
+
20
+ @dataclass
21
+ class ProxyConfig:
22
+ """Configuration for proxy and SSL settings."""
23
+
24
+ verify: bool | str | None
25
+ trust_env: bool
26
+ proxy_url: str | None
27
+ disable_retry: bool
28
+ http2_enabled: bool
29
+
30
+
31
+ def _detect_proxy_url() -> str | None:
32
+ """Return the first proxy URL found in environment variables."""
33
+ return (
34
+ os.environ.get("HTTPS_PROXY")
35
+ or os.environ.get("https_proxy")
36
+ or os.environ.get("HTTP_PROXY")
37
+ or os.environ.get("http_proxy")
38
+ or None
39
+ )
40
+
41
+
42
+ def _resolve_proxy_config(verify: bool | str | None = None) -> ProxyConfig:
43
+ """Resolve proxy, SSL, and retry settings from environment.
44
+
45
+ This centralizes the logic for detecting proxies, determining SSL verification,
46
+ and checking if retry transport should be disabled.
47
+ """
48
+ if verify is None:
49
+ verify = get_cert_bundle_path()
50
+
51
+ http2_enabled = get_http2()
52
+
53
+ disable_retry = os.environ.get(
54
+ "MUSE_DISABLE_RETRY_TRANSPORT", ""
55
+ ).lower() in ("1", "true", "yes")
56
+
57
+ # When retry transport is disabled and no cert bundle is configured,
58
+ # also disable TLS verification (legacy compatibility). If a cert
59
+ # bundle is available, keep it — only bare disable_retry sets False.
60
+ if disable_retry and verify is None:
61
+ verify = False
62
+
63
+ # Explicit TLS disable — independent from retry logic
64
+ disable_tls_verify = os.environ.get(
65
+ "MUSE_DISABLE_TLS_VERIFY", ""
66
+ ).lower() in ("1", "true", "yes")
67
+ if disable_tls_verify:
68
+ verify = False
69
+
70
+ proxy_url = _detect_proxy_url()
71
+ has_proxy = proxy_url is not None
72
+
73
+ # trust_env lets httpx read proxy settings from the environment.
74
+ # It should not affect SSL verification.
75
+ trust_env = has_proxy
76
+
77
+ return ProxyConfig(
78
+ verify=verify,
79
+ trust_env=trust_env,
80
+ proxy_url=proxy_url,
81
+ disable_retry=disable_retry,
82
+ http2_enabled=http2_enabled,
83
+ )
84
+
85
+
86
+ try:
87
+ from .reopenable_async_client import ReopenableAsyncClient
88
+ except ImportError:
89
+ ReopenableAsyncClient = None
90
+
91
+ try:
92
+ from .messaging import emit_info, emit_warning
93
+ except ImportError:
94
+ # Fallback if messaging system is not available
95
+ def emit_info(content: str, **metadata):
96
+ pass # No-op if messaging system is not available
97
+
98
+ def emit_warning(content: str, **metadata):
99
+ pass
100
+
101
+
102
+ class RetryingAsyncClient(httpx.AsyncClient):
103
+ """AsyncClient with built-in rate limit handling (429) and retries.
104
+
105
+ This replaces the Tenacity transport with a more direct subclass implementation,
106
+ which plays nicer with proxies and custom transports.
107
+
108
+ Special handling for Cerebras: Their Retry-After headers are absurdly aggressive
109
+ (often 60s), so we ignore them and use a 3s base backoff instead.
110
+ """
111
+
112
+ def __init__(
113
+ self,
114
+ retry_status_codes: tuple = (429, 502, 503, 504),
115
+ max_retries: int = 5,
116
+ model_name: str = "",
117
+ **kwargs,
118
+ ):
119
+ super().__init__(**kwargs)
120
+ self.retry_status_codes = retry_status_codes
121
+ self.max_retries = max_retries
122
+ self.model_name = model_name.lower() if model_name else ""
123
+ # Cerebras sends crazy aggressive Retry-After headers (60s), ignore them
124
+ self._ignore_retry_headers = "cerebras" in self.model_name
125
+
126
+ async def send(self, request: httpx.Request, **kwargs: Any) -> httpx.Response:
127
+ """Send request with automatic retries for rate limits and server errors."""
128
+ last_response = None
129
+ last_exception = None
130
+
131
+ for attempt in range(self.max_retries + 1):
132
+ try:
133
+ response = await super().send(request, **kwargs)
134
+ last_response = response
135
+
136
+ # Check for retryable status
137
+ if response.status_code not in self.retry_status_codes:
138
+ return response
139
+
140
+ # Close response if we're going to retry
141
+ await response.aclose()
142
+
143
+ # Determine wait time - Cerebras gets special treatment
144
+ if self._ignore_retry_headers:
145
+ # Cerebras: 3s base with exponential backoff (3s, 6s, 12s...)
146
+ wait_time = 3.0 * (2**attempt)
147
+ else:
148
+ # Default exponential backoff: 1s, 2s, 4s...
149
+ wait_time = 1.0 * (2**attempt)
150
+
151
+ # Check Retry-After header (only for non-Cerebras)
152
+ retry_after = response.headers.get("Retry-After")
153
+ if retry_after:
154
+ try:
155
+ wait_time = float(retry_after)
156
+ except ValueError:
157
+ # Try parsing http-date
158
+ from email.utils import parsedate_to_datetime
159
+
160
+ try:
161
+ date = parsedate_to_datetime(retry_after)
162
+ wait_time = date.timestamp() - time.time()
163
+ except Exception:
164
+ pass
165
+
166
+ # Cap wait time
167
+ wait_time = max(0.5, min(wait_time, 60.0))
168
+
169
+ if attempt < self.max_retries:
170
+ provider_note = (
171
+ " (ignoring header)" if self._ignore_retry_headers else ""
172
+ )
173
+ emit_info(
174
+ f"HTTP retry: {response.status_code} received{provider_note}. "
175
+ f"Waiting {wait_time:.1f}s (attempt {attempt + 1}/{self.max_retries})"
176
+ )
177
+ await asyncio.sleep(wait_time)
178
+
179
+ except (httpx.ConnectError, httpx.ReadTimeout, httpx.PoolTimeout) as e:
180
+ last_exception = e
181
+ wait_time = 1.0 * (2**attempt)
182
+ if attempt < self.max_retries:
183
+ emit_warning(
184
+ f"HTTP connection error: {e}. Retrying in {wait_time}s..."
185
+ )
186
+ await asyncio.sleep(wait_time)
187
+ else:
188
+ raise
189
+ except Exception:
190
+ raise
191
+
192
+ # Return last response (even if it's an error status)
193
+ if last_response:
194
+ return last_response
195
+
196
+ # Should catch this in loop, but just in case
197
+ if last_exception:
198
+ raise last_exception
199
+
200
+ return last_response
201
+
202
+
203
+ def get_cert_bundle_path() -> str | None:
204
+ # First check if SSL_CERT_FILE environment variable is set
205
+ ssl_cert_file = os.environ.get("SSL_CERT_FILE")
206
+ if ssl_cert_file and Path(ssl_cert_file).exists():
207
+ return ssl_cert_file
208
+
209
+
210
+ def create_client(
211
+ timeout: int = 180,
212
+ verify: bool | str = None,
213
+ headers: dict[str, str | None] = None,
214
+ retry_status_codes: tuple = (429, 502, 503, 504),
215
+ ) -> httpx.Client:
216
+ if verify is None:
217
+ verify = get_cert_bundle_path()
218
+
219
+ # Check if HTTP/2 is enabled in config
220
+ http2_enabled = get_http2()
221
+
222
+ # If retry components are available, create a client with retry transport
223
+ # Note: TenacityTransport was removed. For now we just return a standard client.
224
+ # Future TODO: Implement RetryingClient(httpx.Client) if needed.
225
+ return httpx.Client(
226
+ verify=verify,
227
+ headers=headers or {},
228
+ timeout=timeout,
229
+ http2=http2_enabled,
230
+ )
231
+
232
+
233
+ def create_async_client(
234
+ timeout: int = 180,
235
+ verify: bool | str = None,
236
+ headers: dict[str, str | None] = None,
237
+ retry_status_codes: tuple = (429, 502, 503, 504),
238
+ model_name: str = "",
239
+ ) -> httpx.AsyncClient:
240
+ config = _resolve_proxy_config(verify)
241
+
242
+ if not config.disable_retry:
243
+ return RetryingAsyncClient(
244
+ retry_status_codes=retry_status_codes,
245
+ model_name=model_name,
246
+ proxy=config.proxy_url,
247
+ verify=config.verify,
248
+ headers=headers or {},
249
+ timeout=timeout,
250
+ http2=config.http2_enabled,
251
+ trust_env=config.trust_env,
252
+ )
253
+ else:
254
+ return httpx.AsyncClient(
255
+ proxy=config.proxy_url,
256
+ verify=config.verify,
257
+ headers=headers or {},
258
+ timeout=timeout,
259
+ http2=config.http2_enabled,
260
+ trust_env=config.trust_env,
261
+ )
262
+
263
+
264
+ def create_httpx_client(
265
+ timeout: float = 5.0,
266
+ verify: bool | str = None,
267
+ headers: dict[str, str | None] = None,
268
+ ) -> httpx.Client:
269
+ if verify is None:
270
+ verify = get_cert_bundle_path()
271
+
272
+ client = httpx.Client(verify=verify)
273
+
274
+ if headers:
275
+ client.headers.update(headers or {})
276
+
277
+ return client
278
+
279
+
280
+ def create_auth_headers(
281
+ api_key: str, header_name: str = "Authorization"
282
+ ) -> dict[str, str]:
283
+ return {header_name: f"Bearer {api_key}"}
284
+
285
+
286
+ def resolve_env_var_in_header(headers: dict[str, str]) -> dict[str, str]:
287
+ resolved_headers = {}
288
+
289
+ for key, value in headers.items():
290
+ if isinstance(value, str):
291
+ try:
292
+ expanded = os.path.expandvars(value)
293
+ resolved_headers[key] = expanded
294
+ except Exception:
295
+ resolved_headers[key] = value
296
+ else:
297
+ resolved_headers[key] = value
298
+
299
+ return resolved_headers
300
+
301
+
302
+ def create_reopenable_async_client(
303
+ timeout: int = 180,
304
+ verify: bool | str = None,
305
+ headers: dict[str, str | None] = None,
306
+ retry_status_codes: tuple = (429, 502, 503, 504),
307
+ model_name: str = "",
308
+ ) -> ReopenableAsyncClient | httpx.AsyncClient:
309
+ config = _resolve_proxy_config(verify)
310
+
311
+ base_kwargs = {
312
+ "proxy": config.proxy_url,
313
+ "verify": config.verify,
314
+ "headers": headers or {},
315
+ "timeout": timeout,
316
+ "http2": config.http2_enabled,
317
+ "trust_env": config.trust_env,
318
+ }
319
+
320
+ if ReopenableAsyncClient is not None:
321
+ client_class = (
322
+ RetryingAsyncClient if not config.disable_retry else httpx.AsyncClient
323
+ )
324
+ kwargs = {**base_kwargs, "client_class": client_class}
325
+ if not config.disable_retry:
326
+ kwargs["retry_status_codes"] = retry_status_codes
327
+ kwargs["model_name"] = model_name
328
+ return ReopenableAsyncClient(**kwargs)
329
+ else:
330
+ # Fallback to RetryingAsyncClient or plain AsyncClient
331
+ if not config.disable_retry:
332
+ return RetryingAsyncClient(
333
+ retry_status_codes=retry_status_codes,
334
+ model_name=model_name,
335
+ **base_kwargs,
336
+ )
337
+ else:
338
+ return httpx.AsyncClient(**base_kwargs)
339
+
340
+
341
+ def is_cert_bundle_available() -> bool:
342
+ cert_path = get_cert_bundle_path()
343
+ if cert_path is None:
344
+ return False
345
+ return Path(cert_path).exists() and Path(cert_path).is_file()
346
+
347
+
348
+ def find_available_port(start_port=8090, end_port=9010, host="127.0.0.1"):
349
+ for port in range(start_port, end_port + 1):
350
+ try:
351
+ # Try to bind to the port to check if it's available.
352
+ # Do NOT set SO_REUSEADDR here — it would allow binding to
353
+ # ports already in use, defeating the availability check.
354
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
355
+ sock.bind((host, port))
356
+ return port
357
+ except OSError:
358
+ # Port is in use, try the next one
359
+ continue
360
+ return None
code_muse/keymap.py ADDED
@@ -0,0 +1,128 @@
1
+ """Keymap configuration for Muse.
2
+
3
+ This module handles configurable keyboard shortcuts, starting with the
4
+ cancel_agent_key feature that allows users to override Ctrl+C with a
5
+ different key for cancelling agent tasks.
6
+ """
7
+
8
+ # Character codes for Ctrl+letter combinations (Ctrl+A = 0x01, Ctrl+Z = 0x1A)
9
+ KEY_CODES: dict[str, str] = {
10
+ "ctrl+a": "\x01",
11
+ "ctrl+b": "\x02",
12
+ "ctrl+c": "\x03",
13
+ "ctrl+d": "\x04",
14
+ "ctrl+e": "\x05",
15
+ "ctrl+f": "\x06",
16
+ "ctrl+g": "\x07",
17
+ "ctrl+h": "\x08",
18
+ "ctrl+i": "\x09",
19
+ "ctrl+j": "\x0a",
20
+ "ctrl+k": "\x0b",
21
+ "ctrl+l": "\x0c",
22
+ "ctrl+m": "\x0d",
23
+ "ctrl+n": "\x0e",
24
+ "ctrl+o": "\x0f",
25
+ "ctrl+p": "\x10",
26
+ "ctrl+q": "\x11",
27
+ "ctrl+r": "\x12",
28
+ "ctrl+s": "\x13",
29
+ "ctrl+t": "\x14",
30
+ "ctrl+u": "\x15",
31
+ "ctrl+v": "\x16",
32
+ "ctrl+w": "\x17",
33
+ "ctrl+x": "\x18",
34
+ "ctrl+y": "\x19",
35
+ "ctrl+z": "\x1a",
36
+ "escape": "\x1b",
37
+ }
38
+
39
+ # Valid keys for cancel_agent_key configuration
40
+ # NOTE: "escape" is excluded because it conflicts with ANSI escape sequences
41
+ # (arrow keys, F-keys, etc. all start with \x1b)
42
+ VALID_CANCEL_KEYS: set[str] = {
43
+ "ctrl+c",
44
+ "ctrl+k",
45
+ "ctrl+q",
46
+ }
47
+
48
+ DEFAULT_CANCEL_AGENT_KEY: str = "ctrl+c"
49
+
50
+
51
+ class KeymapError(Exception):
52
+ """Exception raised for keymap configuration errors."""
53
+
54
+
55
+ def get_cancel_agent_key() -> str:
56
+ """Get the configured cancel agent key from config.
57
+
58
+ On Windows when launched via uvx, this automatically returns "ctrl+k"
59
+ to work around uvx capturing Ctrl+C before it reaches Python.
60
+
61
+ Returns:
62
+ The key name (e.g., "ctrl+c", "ctrl+k") from config,
63
+ or the default if not configured.
64
+ """
65
+ from code_muse.config import get_value
66
+ from code_muse.uvx_detection import should_use_alternate_cancel_key
67
+
68
+ # On Windows + uvx, force ctrl+k to bypass uvx's SIGINT capture
69
+ if should_use_alternate_cancel_key():
70
+ return "ctrl+k"
71
+
72
+ key = get_value("cancel_agent_key")
73
+ if key is None or key.strip() == "":
74
+ return DEFAULT_CANCEL_AGENT_KEY
75
+ return key.strip().lower()
76
+
77
+
78
+ def validate_cancel_agent_key() -> None:
79
+ """Validate the configured cancel agent key.
80
+
81
+ Raises:
82
+ KeymapError: If the configured key is invalid.
83
+ """
84
+ key = get_cancel_agent_key()
85
+ if key not in VALID_CANCEL_KEYS:
86
+ valid_keys_str = ", ".join(sorted(VALID_CANCEL_KEYS))
87
+ raise KeymapError(
88
+ f"Invalid cancel_agent_key '{key}' in muse.cfg. "
89
+ f"Valid options are: {valid_keys_str}"
90
+ )
91
+
92
+
93
+ def cancel_agent_uses_signal() -> bool:
94
+ """Check if the cancel agent key uses SIGINT (Ctrl+C).
95
+
96
+ Returns:
97
+ True if the cancel key is ctrl+c (uses SIGINT handler),
98
+ False if it uses keyboard listener approach.
99
+ """
100
+ return get_cancel_agent_key() == "ctrl+c"
101
+
102
+
103
+ def get_cancel_agent_char_code() -> str:
104
+ """Get the character code for the cancel agent key.
105
+
106
+ Returns:
107
+ The character code (e.g., "\x0b" for ctrl+k).
108
+
109
+ Raises:
110
+ KeymapError: If the key is not found in KEY_CODES.
111
+ """
112
+ key = get_cancel_agent_key()
113
+ if key not in KEY_CODES:
114
+ raise KeymapError(f"Unknown key '{key}' - no character code mapping found.")
115
+ return KEY_CODES[key]
116
+
117
+
118
+ def get_cancel_agent_display_name() -> str:
119
+ """Get a human-readable display name for the cancel agent key.
120
+
121
+ Returns:
122
+ A formatted display name like "Ctrl+K".
123
+ """
124
+ key = get_cancel_agent_key()
125
+ if key.startswith("ctrl+"):
126
+ letter = key.split("+")[1].upper()
127
+ return f"Ctrl+{letter}"
128
+ return key.upper()
@@ -0,0 +1,26 @@
1
+ """Helpers for lightweight case-insensitive list filtering in TUIs."""
2
+
3
+ import re
4
+
5
+ _NON_ALNUM_RE = re.compile(r"[^0-9a-z]+")
6
+
7
+
8
+ def normalize_filter_text(text: str) -> str:
9
+ """Normalize text for forgiving case-insensitive substring matching."""
10
+ normalized = _NON_ALNUM_RE.sub(" ", str(text).casefold()).strip()
11
+ return " ".join(normalized.split())
12
+
13
+
14
+ def query_matches_text(query: str, *candidates: str) -> bool:
15
+ """Return True when every query term appears in the candidate text."""
16
+ terms = normalize_filter_text(query).split()
17
+ if not terms:
18
+ return True
19
+
20
+ haystack = " ".join(
21
+ normalize_filter_text(candidate) for candidate in candidates if candidate
22
+ ).strip()
23
+ if not haystack:
24
+ return False
25
+
26
+ return all(term in haystack for term in terms)
code_muse/main.py ADDED
@@ -0,0 +1,10 @@
1
+ """Main entry point for Muse CLI.
2
+
3
+ This module re-exports the main_entry function from cli_runner for backwards compatibility.
4
+ All the actual logic lives in cli_runner.py.
5
+ """
6
+
7
+ from code_muse.cli_runner import main_entry
8
+
9
+ if __name__ == "__main__":
10
+ main_entry()