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,908 @@
1
+ """Universal Constructor (UC) interactive TUI menu.
2
+
3
+ Provides a split-panel interface for browsing and managing UC tools
4
+ with live preview of tool details and inline source code viewing.
5
+ """
6
+
7
+ import asyncio
8
+ import sys
9
+ import unicodedata
10
+ from pathlib import Path
11
+
12
+ from prompt_toolkit.application import Application
13
+ from prompt_toolkit.key_binding import KeyBindings
14
+ from prompt_toolkit.layout import Dimension, HSplit, Layout, VSplit, Window
15
+ from prompt_toolkit.layout.controls import FormattedTextControl
16
+ from prompt_toolkit.widgets import Frame
17
+
18
+ from code_muse.command_line.command_registry import register_command
19
+ from code_muse.command_line.pagination import (
20
+ ensure_visible_page,
21
+ get_page_bounds,
22
+ get_page_for_index,
23
+ get_total_pages,
24
+ )
25
+ from code_muse.messaging import emit_error, emit_info, emit_success
26
+ from code_muse.plugins.universal_constructor.models import UCToolInfo
27
+ from code_muse.plugins.universal_constructor.registry import get_registry
28
+ from code_muse.tools.command_runner import set_awaiting_user_input
29
+
30
+ PAGE_SIZE = 10 # Tools per page
31
+ SOURCE_PAGE_SIZE = 30 # Lines of source per page
32
+
33
+
34
+ def _sanitize_display_text(text: str) -> str:
35
+ """Remove or replace characters that cause terminal rendering issues.
36
+
37
+ Args:
38
+ text: Text that may contain emojis or wide characters
39
+
40
+ Returns:
41
+ Sanitized text safe for prompt_toolkit rendering
42
+ """
43
+ result = []
44
+ for char in text:
45
+ cat = unicodedata.category(char)
46
+ safe_categories = (
47
+ "Lu",
48
+ "Ll",
49
+ "Lt",
50
+ "Lm",
51
+ "Lo", # Letters
52
+ "Nd",
53
+ "Nl",
54
+ "No", # Numbers
55
+ "Pc",
56
+ "Pd",
57
+ "Ps",
58
+ "Pe",
59
+ "Pi",
60
+ "Pf",
61
+ "Po", # Punctuation
62
+ "Zs", # Space
63
+ "Sm",
64
+ "Sc",
65
+ "Sk", # Safe symbols
66
+ )
67
+ if cat in safe_categories:
68
+ result.append(char)
69
+
70
+ cleaned = " ".join("".join(result).split())
71
+ return cleaned
72
+
73
+
74
+ def _get_tool_entries() -> list[UCToolInfo]:
75
+ """Get all UC tools sorted by name.
76
+
77
+ Returns:
78
+ list of UCToolInfo sorted by full_name.
79
+ """
80
+ registry = get_registry()
81
+ registry.scan() # Force fresh scan
82
+ return registry.list_tools(include_disabled=True)
83
+
84
+
85
+ def _toggle_tool_enabled(tool: UCToolInfo) -> bool:
86
+ """Toggle a tool's enabled status by modifying its source file.
87
+
88
+ Args:
89
+ tool: The tool to toggle.
90
+
91
+ Returns:
92
+ True if successful, False otherwise.
93
+ """
94
+ try:
95
+ source_path = Path(tool.source_path)
96
+ content = source_path.read_text()
97
+
98
+ # Find and flip the enabled flag in TOOL_META
99
+ new_enabled = not tool.meta.enabled
100
+
101
+ # Try to find and replace the enabled line
102
+ import re
103
+
104
+ # Match 'enabled': True/False or "enabled": True/False
105
+ pattern = r'(["\']enabled["\']\s*:\s*)(True|False)'
106
+
107
+ def replacer(m):
108
+ return m.group(1) + str(new_enabled)
109
+
110
+ new_content, count = re.subn(pattern, replacer, content)
111
+
112
+ if count == 0:
113
+ # No explicit enabled field - add it to TOOL_META
114
+ # Find TOOL_META = { and add enabled after the opening brace
115
+ meta_pattern = r"(TOOL_META\s*=\s*\{)"
116
+ new_content, meta_count = re.subn(
117
+ meta_pattern, f'\\1\n "enabled": {new_enabled},', content
118
+ )
119
+ if meta_count == 0:
120
+ emit_error("TOOL_META not found; cannot toggle enabled flag.")
121
+ return False
122
+
123
+ source_path.write_text(new_content)
124
+
125
+ status = "enabled" if new_enabled else "disabled"
126
+ emit_success(f"Tool '{tool.full_name}' is now {status}")
127
+ return True
128
+
129
+ except Exception as e:
130
+ emit_error(f"Failed to toggle tool: {e}")
131
+ return False
132
+
133
+
134
+ def _delete_tool(tool: UCToolInfo) -> bool:
135
+ """Delete a UC tool by removing its source file.
136
+
137
+ Args:
138
+ tool: The tool to delete.
139
+
140
+ Returns:
141
+ True if successful, False otherwise.
142
+ """
143
+ try:
144
+ source_path = Path(tool.source_path)
145
+ if not source_path.exists():
146
+ emit_error(f"Tool file not found: {source_path}")
147
+ return False
148
+
149
+ # Delete the file
150
+ source_path.unlink()
151
+
152
+ # Try to clean up empty parent directories (namespace folders)
153
+ parent = source_path.parent
154
+ from code_muse.plugins.universal_constructor import USER_UC_DIR
155
+
156
+ while parent != USER_UC_DIR and parent.exists():
157
+ try:
158
+ if not any(parent.iterdir()):
159
+ parent.rmdir()
160
+ parent = parent.parent
161
+ else:
162
+ break
163
+ except OSError:
164
+ break
165
+
166
+ emit_success(f"Deleted tool '{tool.full_name}'")
167
+ return True
168
+
169
+ except Exception as e:
170
+ emit_error(f"Failed to delete tool: {e}")
171
+ return False
172
+
173
+
174
+ def _load_source_code(tool: UCToolInfo) -> tuple[list[str], str | None]:
175
+ """Load source code lines from a tool's file.
176
+
177
+ Args:
178
+ tool: The tool to load source for.
179
+
180
+ Returns:
181
+ Tuple of (lines list, error message or None)
182
+ """
183
+ try:
184
+ source_path = Path(tool.source_path)
185
+ content = source_path.read_text()
186
+ return content.splitlines(), None
187
+ except Exception as e:
188
+ return [], f"Could not read source: {e}"
189
+
190
+
191
+ def _render_menu_panel(
192
+ tools: list[UCToolInfo],
193
+ page: int,
194
+ selected_idx: int,
195
+ ) -> list:
196
+ """Render the left menu panel with pagination.
197
+
198
+ Args:
199
+ tools: list of UCToolInfo objects
200
+ page: Current page number (0-indexed)
201
+ selected_idx: Currently selected index (global)
202
+
203
+ Returns:
204
+ list of (style, text) tuples for FormattedTextControl
205
+ """
206
+ lines = []
207
+ total_pages = get_total_pages(len(tools), PAGE_SIZE)
208
+ start_idx, end_idx = get_page_bounds(page, len(tools), PAGE_SIZE)
209
+
210
+ lines.append(("bold", "UC Tools"))
211
+ lines.append(("fg:ansibrightblack", f" (Page {page + 1}/{total_pages})"))
212
+ lines.append(("", "\n\n"))
213
+
214
+ if not tools:
215
+ lines.append(("fg:yellow", " No UC tools found.\n"))
216
+ lines.append(("fg:ansibrightblack", " Ask the LLM to create one!\n"))
217
+ lines.append(("", "\n"))
218
+ else:
219
+ for i in range(start_idx, end_idx):
220
+ tool = tools[i]
221
+ is_selected = i == selected_idx
222
+
223
+ safe_name = _sanitize_display_text(tool.full_name)
224
+
225
+ # Selection indicator
226
+ if is_selected:
227
+ lines.append(("fg:ansigreen", "> "))
228
+ lines.append(("fg:ansigreen bold", safe_name))
229
+ else:
230
+ lines.append(("", " "))
231
+ lines.append(("", safe_name))
232
+
233
+ # Status indicator
234
+ if tool.meta.enabled:
235
+ lines.append(("fg:ansigreen", " [on]"))
236
+ else:
237
+ lines.append(("fg:ansired", " [off]"))
238
+
239
+ # Namespace tag if present
240
+ if tool.meta.namespace:
241
+ lines.append(("fg:ansiblue", f" ({tool.meta.namespace})"))
242
+
243
+ lines.append(("", "\n"))
244
+
245
+ # Navigation hints
246
+ lines.append(("", "\n"))
247
+ lines.append(("fg:ansibrightblack", " [up]/[down] "))
248
+ lines.append(("", "Navigate\n"))
249
+ lines.append(("fg:ansibrightblack", " [left]/[right] "))
250
+ lines.append(("", "Page\n"))
251
+ lines.append(("fg:green", " Enter "))
252
+ lines.append(("", "View source\n"))
253
+ lines.append(("fg:ansiyellow", " E "))
254
+ lines.append(("", "Toggle enabled\n"))
255
+ lines.append(("fg:ansired", " D "))
256
+ lines.append(("", "Delete tool\n"))
257
+ lines.append(("fg:ansibrightblack", " Esc "))
258
+ lines.append(("", "Exit"))
259
+
260
+ return lines
261
+
262
+
263
+ def _render_preview_panel(tool: UCToolInfo | None) -> list:
264
+ """Render the right preview panel with tool details.
265
+
266
+ Args:
267
+ tool: UCToolInfo or None
268
+
269
+ Returns:
270
+ list of (style, text) tuples for FormattedTextControl
271
+ """
272
+ lines = []
273
+
274
+ lines.append(("dim cyan", " TOOL DETAILS"))
275
+ lines.append(("", "\n\n"))
276
+
277
+ if not tool:
278
+ lines.append(("fg:yellow", " No tool selected.\n"))
279
+ lines.append(("fg:ansibrightblack", " Create some with the LLM!\n"))
280
+ return lines
281
+
282
+ safe_name = _sanitize_display_text(tool.meta.name)
283
+ safe_desc = _sanitize_display_text(tool.meta.description)
284
+
285
+ # Tool name
286
+ lines.append(("bold", "Name: "))
287
+ lines.append(("fg:ansicyan", safe_name))
288
+ lines.append(("", "\n\n"))
289
+
290
+ # Full name (with namespace)
291
+ if tool.meta.namespace:
292
+ lines.append(("bold", "Full Name: "))
293
+ lines.append(("", tool.full_name))
294
+ lines.append(("", "\n\n"))
295
+
296
+ # Status
297
+ lines.append(("bold", "Status: "))
298
+ if tool.meta.enabled:
299
+ lines.append(("fg:ansigreen bold", "ENABLED"))
300
+ else:
301
+ lines.append(("fg:ansired bold", "DISABLED"))
302
+ lines.append(("", "\n\n"))
303
+
304
+ # Version
305
+ lines.append(("bold", "Version: "))
306
+ lines.append(("", tool.meta.version))
307
+ lines.append(("", "\n\n"))
308
+
309
+ # Author (if present)
310
+ if tool.meta.author:
311
+ lines.append(("bold", "Author: "))
312
+ lines.append(("", tool.meta.author))
313
+ lines.append(("", "\n\n"))
314
+
315
+ # Signature
316
+ lines.append(("bold", "Signature: "))
317
+ lines.append(("fg:ansiyellow", tool.signature))
318
+ lines.append(("", "\n\n"))
319
+
320
+ # Description (word-wrapped)
321
+ lines.append(("bold", "Description:"))
322
+ lines.append(("", "\n"))
323
+
324
+ words = safe_desc.split()
325
+ current_line = ""
326
+ for word in words:
327
+ if len(current_line) + len(word) + 1 > 50:
328
+ lines.append(("fg:ansibrightblack", f" {current_line}"))
329
+ lines.append(("", "\n"))
330
+ current_line = word
331
+ else:
332
+ current_line = word if not current_line else current_line + " " + word
333
+ if current_line:
334
+ lines.append(("fg:ansibrightblack", f" {current_line}"))
335
+ lines.append(("", "\n"))
336
+
337
+ lines.append(("", "\n"))
338
+
339
+ # Docstring preview (if available)
340
+ if tool.docstring:
341
+ lines.append(("bold", "Docstring:"))
342
+ lines.append(("", "\n"))
343
+ doc_preview = tool.docstring[:150]
344
+ if len(tool.docstring) > 150:
345
+ doc_preview += "..."
346
+ lines.append(("fg:ansibrightblack", f" {doc_preview}"))
347
+ lines.append(("", "\n\n"))
348
+
349
+ # Source path
350
+ lines.append(("bold", "Source:"))
351
+ lines.append(("", "\n"))
352
+ lines.append(("fg:ansibrightblack", f" {tool.source_path}"))
353
+ lines.append(("", "\n"))
354
+
355
+ return lines
356
+
357
+
358
+ def _render_source_panel(
359
+ tool: UCToolInfo,
360
+ source_lines: list[str],
361
+ scroll_offset: int,
362
+ error: str | None = None,
363
+ ) -> list:
364
+ """Render source code panel with syntax highlighting.
365
+
366
+ Args:
367
+ tool: The tool being viewed
368
+ source_lines: list of source code lines
369
+ scroll_offset: Current scroll position (line number)
370
+ error: Error message if source couldn't be loaded
371
+
372
+ Returns:
373
+ list of (style, text) tuples for FormattedTextControl
374
+ """
375
+ lines = []
376
+
377
+ # Header
378
+ lines.append(("bold cyan", f" SOURCE: {tool.full_name}"))
379
+ lines.append(("", "\n"))
380
+ lines.append(("fg:ansibrightblack", f" {tool.source_path}"))
381
+ lines.append(("", "\n"))
382
+ lines.append(("fg:ansibrightblack", "─" * 70))
383
+ lines.append(("", "\n"))
384
+
385
+ if error:
386
+ lines.append(("fg:ansired", f" Error: {error}\n"))
387
+ return lines
388
+
389
+ if not source_lines:
390
+ lines.append(("fg:yellow", " (empty file)\n"))
391
+ return lines
392
+
393
+ # Calculate visible range
394
+ total_lines = len(source_lines)
395
+ visible_lines = SOURCE_PAGE_SIZE
396
+ end_offset = min(scroll_offset + visible_lines, total_lines)
397
+
398
+ # Line number width for padding
399
+ line_num_width = len(str(total_lines))
400
+
401
+ # Render visible source lines with basic syntax highlighting
402
+ for i in range(scroll_offset, end_offset):
403
+ line_num = i + 1
404
+ line_content = source_lines[i]
405
+
406
+ # Line number
407
+ lines.append(("fg:ansibrightblack", f" {line_num:>{line_num_width}} │ "))
408
+
409
+ # Basic syntax highlighting
410
+ highlighted = _highlight_python_line(line_content)
411
+ lines.extend(highlighted)
412
+ lines.append(("", "\n"))
413
+
414
+ # Footer with scroll info
415
+ lines.append(("fg:ansibrightblack", "─" * 70))
416
+ lines.append(("", "\n"))
417
+
418
+ # Scroll position indicator
419
+ current_page = scroll_offset // SOURCE_PAGE_SIZE + 1
420
+ total_pages = (total_lines + SOURCE_PAGE_SIZE - 1) // SOURCE_PAGE_SIZE
421
+ lines.append(
422
+ (
423
+ "fg:ansibrightblack",
424
+ f" Lines {scroll_offset + 1}-{end_offset} of {total_lines}",
425
+ )
426
+ )
427
+ lines.append(("fg:ansibrightblack", f" (Page {current_page}/{total_pages})"))
428
+ lines.append(("", "\n\n"))
429
+
430
+ # Navigation hints for source view
431
+ lines.append(("fg:ansibrightblack", " [up]/[down] "))
432
+ lines.append(("", "Scroll\n"))
433
+ lines.append(("fg:ansibrightblack", " [PgUp]/[PgDn] "))
434
+ lines.append(("", "Page\n"))
435
+ lines.append(("fg:ansiyellow", " Esc/Q "))
436
+ lines.append(("", "Back to list\n"))
437
+ lines.append(("fg:ansibrightred", " Ctrl+C "))
438
+ lines.append(("", "Exit"))
439
+
440
+ return lines
441
+
442
+
443
+ def _highlight_python_line(line: str) -> list[tuple[str, str]]:
444
+ """Apply basic Python syntax highlighting to a line.
445
+
446
+ Args:
447
+ line: A single line of Python code
448
+
449
+ Returns:
450
+ list of (style, text) tuples
451
+ """
452
+ result = []
453
+
454
+ # Keywords
455
+ keywords = {
456
+ "def",
457
+ "class",
458
+ "return",
459
+ "if",
460
+ "else",
461
+ "elif",
462
+ "for",
463
+ "while",
464
+ "try",
465
+ "except",
466
+ "finally",
467
+ "with",
468
+ "as",
469
+ "import",
470
+ "from",
471
+ "True",
472
+ "False",
473
+ "None",
474
+ "and",
475
+ "or",
476
+ "not",
477
+ "in",
478
+ "is",
479
+ "lambda",
480
+ "yield",
481
+ "raise",
482
+ "pass",
483
+ "break",
484
+ "continue",
485
+ "async",
486
+ "await",
487
+ }
488
+
489
+ # Simple tokenization
490
+ if not line.strip():
491
+ result.append(("", line))
492
+ return result
493
+
494
+ # Check for comments
495
+ if line.lstrip().startswith("#"):
496
+ result.append(("fg:ansibrightblack italic", line))
497
+ return result
498
+
499
+ # Check for strings (simplified)
500
+ stripped = line.lstrip()
501
+ if stripped.startswith('"""') or stripped.startswith("'''"):
502
+ result.append(("fg:ansigreen", line))
503
+ return result
504
+
505
+ # Word-by-word highlighting
506
+ import re
507
+
508
+ tokens = re.split(r"(\s+|[()\[\]{}:,=.])", line)
509
+
510
+ in_string = False
511
+ string_char = None
512
+
513
+ for token in tokens:
514
+ if not token:
515
+ continue
516
+
517
+ # Track string state
518
+ if not in_string and (token.startswith('"') or token.startswith("'")):
519
+ in_string = True
520
+ string_char = token[0]
521
+ result.append(("fg:ansigreen", token))
522
+ if (
523
+ len(token) > 1
524
+ and token.endswith(string_char)
525
+ and not token.endswith("\\" + string_char)
526
+ ):
527
+ in_string = False
528
+ continue
529
+
530
+ if in_string:
531
+ result.append(("fg:ansigreen", token))
532
+ if token.endswith(string_char) and not token.endswith("\\" + string_char):
533
+ in_string = False
534
+ continue
535
+
536
+ # Keywords
537
+ if token in keywords:
538
+ result.append(("fg:ansimagenta bold", token))
539
+ # Numbers
540
+ elif token.isdigit():
541
+ result.append(("fg:ansicyan", token))
542
+ # Function/class names (after def/class)
543
+ elif result and len(result) >= 1:
544
+ prev_text = result[-1][1].strip() if result[-1][1] else ""
545
+ if prev_text in ("def", "class"):
546
+ result.append(("fg:ansiyellow bold", token))
547
+ else:
548
+ result.append(("", token))
549
+ else:
550
+ result.append(("", token))
551
+
552
+ return result
553
+
554
+
555
+ def _show_source_code(tool: UCToolInfo) -> None:
556
+ """Display the full source code of a tool (legacy, for external use).
557
+
558
+ Args:
559
+ tool: The tool to show source for.
560
+ """
561
+ from rich.panel import Panel
562
+ from rich.syntax import Syntax
563
+
564
+ try:
565
+ source_code = Path(tool.source_path).read_text()
566
+ syntax = Syntax(
567
+ source_code,
568
+ "python",
569
+ theme="monokai",
570
+ line_numbers=True,
571
+ word_wrap=True,
572
+ )
573
+ panel = Panel(
574
+ syntax,
575
+ title=f"[bold cyan]{tool.full_name}[/bold cyan]",
576
+ border_style="cyan",
577
+ padding=(0, 1),
578
+ )
579
+ emit_info(panel)
580
+ except Exception as e:
581
+ emit_error(f"Could not read source: {e}")
582
+
583
+
584
+ async def interactive_uc_picker() -> str | None:
585
+ """Show interactive TUI to browse UC tools.
586
+
587
+ Returns:
588
+ Tool name that was selected for viewing, or None if cancelled.
589
+ """
590
+ tools = _get_tool_entries()
591
+
592
+ # State
593
+ selected_idx = [0]
594
+ current_page = [0]
595
+ result = [None] # Tool name to view
596
+ pending_action = [None] # 'toggle', 'view', or None
597
+ view_mode = ["list"] # 'list' or 'source'
598
+ source_scroll = [0] # Scroll offset in source view
599
+ source_lines = [[]] # Cached source lines
600
+ source_error = [None] # Error loading source
601
+
602
+ total_pages = [get_total_pages(len(tools), PAGE_SIZE)]
603
+
604
+ def get_current_tool() -> UCToolInfo | None:
605
+ if 0 <= selected_idx[0] < len(tools):
606
+ return tools[selected_idx[0]]
607
+ return None
608
+
609
+ def refresh_tools(selected_name: str | None = None) -> None:
610
+ nonlocal tools
611
+ tools = _get_tool_entries()
612
+ total_pages[0] = get_total_pages(len(tools), PAGE_SIZE)
613
+
614
+ if not tools:
615
+ selected_idx[0] = 0
616
+ current_page[0] = 0
617
+ return
618
+
619
+ if selected_name:
620
+ for idx, t in enumerate(tools):
621
+ if t.full_name == selected_name:
622
+ selected_idx[0] = idx
623
+ break
624
+ else:
625
+ selected_idx[0] = min(selected_idx[0], len(tools) - 1)
626
+ else:
627
+ selected_idx[0] = min(selected_idx[0], len(tools) - 1)
628
+
629
+ current_page[0] = get_page_for_index(selected_idx[0], PAGE_SIZE)
630
+
631
+ # Build UI controls
632
+ menu_control = FormattedTextControl(text="")
633
+ preview_control = FormattedTextControl(text="")
634
+ source_control = FormattedTextControl(text="")
635
+
636
+ def update_list_display():
637
+ """Update the list view panels."""
638
+ menu_control.text = _render_menu_panel(tools, current_page[0], selected_idx[0])
639
+ preview_control.text = _render_preview_panel(get_current_tool())
640
+
641
+ def update_source_display():
642
+ """Update the source view panel."""
643
+ tool = get_current_tool()
644
+ if tool:
645
+ source_control.text = _render_source_panel(
646
+ tool, source_lines[0], source_scroll[0], source_error[0]
647
+ )
648
+
649
+ # Windows for list view
650
+ menu_window = Window(
651
+ content=menu_control, wrap_lines=False, width=Dimension(weight=40)
652
+ )
653
+ preview_window = Window(
654
+ content=preview_control, wrap_lines=False, width=Dimension(weight=60)
655
+ )
656
+
657
+ # Window for source view (full width)
658
+ source_window = Window(
659
+ content=source_control, wrap_lines=True, width=Dimension(weight=100)
660
+ )
661
+
662
+ # Frames
663
+ menu_frame = Frame(menu_window, width=Dimension(weight=40), title="UC Tools")
664
+ preview_frame = Frame(preview_window, width=Dimension(weight=60), title="Preview")
665
+ source_frame = Frame(
666
+ source_window, width=Dimension(weight=100), title="Source Code"
667
+ )
668
+
669
+ # Containers
670
+ list_container = VSplit([menu_frame, preview_frame])
671
+ source_container = HSplit([source_frame])
672
+
673
+ # Key bindings for LIST mode
674
+ list_kb = KeyBindings()
675
+
676
+ @list_kb.add("up")
677
+ def _list_up(event):
678
+ if selected_idx[0] > 0:
679
+ selected_idx[0] -= 1
680
+ current_page[0] = ensure_visible_page(
681
+ selected_idx[0],
682
+ current_page[0],
683
+ len(tools),
684
+ PAGE_SIZE,
685
+ )
686
+ update_list_display()
687
+
688
+ @list_kb.add("down")
689
+ def _list_down(event):
690
+ if selected_idx[0] < len(tools) - 1:
691
+ selected_idx[0] += 1
692
+ current_page[0] = ensure_visible_page(
693
+ selected_idx[0],
694
+ current_page[0],
695
+ len(tools),
696
+ PAGE_SIZE,
697
+ )
698
+ update_list_display()
699
+
700
+ @list_kb.add("left")
701
+ def _list_left(event):
702
+ if current_page[0] > 0:
703
+ current_page[0] -= 1
704
+ selected_idx[0] = current_page[0] * PAGE_SIZE
705
+ update_list_display()
706
+
707
+ @list_kb.add("right")
708
+ def _list_right(event):
709
+ if current_page[0] < total_pages[0] - 1:
710
+ current_page[0] += 1
711
+ selected_idx[0] = current_page[0] * PAGE_SIZE
712
+ update_list_display()
713
+
714
+ @list_kb.add("e")
715
+ def _list_toggle(event):
716
+ if get_current_tool():
717
+ pending_action[0] = "toggle"
718
+ event.app.exit()
719
+
720
+ @list_kb.add("d")
721
+ def _list_delete(event):
722
+ if get_current_tool():
723
+ pending_action[0] = "delete"
724
+ event.app.exit()
725
+
726
+ @list_kb.add("escape")
727
+ def _list_escape(event):
728
+ result[0] = None
729
+ pending_action[0] = "exit"
730
+ event.app.exit()
731
+
732
+ @list_kb.add("enter")
733
+ def _list_enter(event):
734
+ tool = get_current_tool()
735
+ if tool:
736
+ # Switch to source view
737
+ view_mode[0] = "source"
738
+ source_scroll[0] = 0
739
+ source_lines[0], source_error[0] = _load_source_code(tool)
740
+ pending_action[0] = "switch_to_source"
741
+ event.app.exit()
742
+
743
+ @list_kb.add("c-c")
744
+ def _list_exit(event):
745
+ result[0] = None
746
+ pending_action[0] = "exit"
747
+ event.app.exit()
748
+
749
+ # Key bindings for SOURCE mode
750
+ source_kb = KeyBindings()
751
+
752
+ @source_kb.add("up")
753
+ def _source_up(event):
754
+ if source_scroll[0] > 0:
755
+ source_scroll[0] -= 1
756
+ update_source_display()
757
+
758
+ @source_kb.add("down")
759
+ def _source_down(event):
760
+ max_scroll = max(0, len(source_lines[0]) - SOURCE_PAGE_SIZE)
761
+ if source_scroll[0] < max_scroll:
762
+ source_scroll[0] += 1
763
+ update_source_display()
764
+
765
+ @source_kb.add("pageup")
766
+ def _source_pageup(event):
767
+ source_scroll[0] = max(0, source_scroll[0] - SOURCE_PAGE_SIZE)
768
+ update_source_display()
769
+
770
+ @source_kb.add("pagedown")
771
+ def _source_pagedown(event):
772
+ max_scroll = max(0, len(source_lines[0]) - SOURCE_PAGE_SIZE)
773
+ source_scroll[0] = min(max_scroll, source_scroll[0] + SOURCE_PAGE_SIZE)
774
+ update_source_display()
775
+
776
+ @source_kb.add("escape")
777
+ def _source_escape(event):
778
+ view_mode[0] = "list"
779
+ pending_action[0] = "switch_to_list"
780
+ event.app.exit()
781
+
782
+ @source_kb.add("q")
783
+ def _source_q(event):
784
+ view_mode[0] = "list"
785
+ pending_action[0] = "switch_to_list"
786
+ event.app.exit()
787
+
788
+ @source_kb.add("c-c")
789
+ def _source_exit(event):
790
+ result[0] = None
791
+ pending_action[0] = "exit"
792
+ event.app.exit()
793
+
794
+ set_awaiting_user_input(True)
795
+
796
+ # Enter alternate screen buffer
797
+ sys.stdout.write("\033[?1049h")
798
+ sys.stdout.write("\033[2J\033[H")
799
+ sys.stdout.flush()
800
+ await asyncio.sleep(0.05)
801
+
802
+ try:
803
+ while True:
804
+ # Clear screen
805
+ sys.stdout.write("\033[2J\033[H")
806
+ sys.stdout.flush()
807
+
808
+ if view_mode[0] == "list":
809
+ # list view
810
+ update_list_display()
811
+ layout = Layout(list_container)
812
+ app = Application(
813
+ layout=layout,
814
+ key_bindings=list_kb,
815
+ full_screen=False,
816
+ mouse_support=False,
817
+ )
818
+ else:
819
+ # Source view
820
+ update_source_display()
821
+ layout = Layout(source_container)
822
+ app = Application(
823
+ layout=layout,
824
+ key_bindings=source_kb,
825
+ full_screen=False,
826
+ mouse_support=False,
827
+ )
828
+
829
+ await app.run_async()
830
+
831
+ # Handle actions
832
+ if pending_action[0] == "toggle":
833
+ tool = get_current_tool()
834
+ if tool:
835
+ selected_name = tool.full_name
836
+ _toggle_tool_enabled(tool)
837
+ refresh_tools(selected_name=selected_name)
838
+ pending_action[0] = None
839
+ continue
840
+
841
+ if pending_action[0] == "delete":
842
+ tool = get_current_tool()
843
+ if tool:
844
+ _delete_tool(tool)
845
+ refresh_tools() # Don't try to keep selection on deleted tool
846
+ pending_action[0] = None
847
+ continue
848
+
849
+ if pending_action[0] == "switch_to_source":
850
+ pending_action[0] = None
851
+ continue
852
+
853
+ if pending_action[0] == "switch_to_list":
854
+ pending_action[0] = None
855
+ continue
856
+
857
+ if pending_action[0] == "exit":
858
+ break
859
+
860
+ # Default: exit
861
+ break
862
+
863
+ finally:
864
+ # Exit alternate screen buffer
865
+ sys.stdout.write("\033[?1049l")
866
+ sys.stdout.flush()
867
+ set_awaiting_user_input(False)
868
+
869
+ emit_info("Exited UC tool browser")
870
+ return result[0]
871
+
872
+
873
+ @register_command(
874
+ name="uc",
875
+ description="Universal Constructor - browse and manage custom tools",
876
+ usage="/uc",
877
+ category="tools",
878
+ )
879
+ def handle_uc_command(command: str) -> bool:
880
+ """Handle the /uc command - opens the interactive TUI.
881
+
882
+ Args:
883
+ command: The full command string.
884
+
885
+ Returns:
886
+ True always (command completed).
887
+ """
888
+ import asyncio
889
+
890
+ try:
891
+ try:
892
+ loop = asyncio.get_running_loop()
893
+ except RuntimeError:
894
+ loop = None
895
+
896
+ if loop is not None and loop.is_running():
897
+ import concurrent.futures
898
+
899
+ # FREE-THREADED: ThreadPoolExecutor is compatible with free-threaded Python 3.14.
900
+ with concurrent.futures.ThreadPoolExecutor() as pool:
901
+ future = pool.submit(asyncio.run, interactive_uc_picker())
902
+ future.result()
903
+ else:
904
+ asyncio.run(interactive_uc_picker())
905
+ except Exception as e:
906
+ emit_error(f"Failed to open UC menu: {e}")
907
+
908
+ return True