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,965 @@
1
+ """Universal Constructor Tool - Dynamic tool creation and management.
2
+
3
+ This module provides the universal_constructor tool that enables users
4
+ to create, manage, and call custom tools dynamically during a session.
5
+ """
6
+
7
+ import subprocess
8
+ from typing import Literal
9
+
10
+ from pydantic import BaseModel, Field
11
+ from pydantic_ai import RunContext
12
+
13
+ from code_muse.messaging import get_message_bus
14
+ from code_muse.messaging.messages import UniversalConstructorMessage
15
+ from code_muse.plugins.universal_constructor.models import (
16
+ UCCallOutput,
17
+ UCCreateOutput,
18
+ UCInfoOutput,
19
+ UCListOutput,
20
+ UCUpdateOutput,
21
+ )
22
+
23
+
24
+ class UniversalConstructorOutput(BaseModel):
25
+ """Unified response model for universal_constructor operations.
26
+
27
+ Wraps all action-specific outputs with a common interface.
28
+ """
29
+
30
+ action: str = Field(..., description="The action that was performed")
31
+ success: bool = Field(..., description="Whether the operation succeeded")
32
+ error: str | None = Field(default=None, description="Error message if failed")
33
+
34
+ # Action-specific results (only one will be populated based on action)
35
+ list_result: UCListOutput | None = Field(
36
+ default=None, description="Result of list action"
37
+ )
38
+ call_result: UCCallOutput | None = Field(
39
+ default=None, description="Result of call action"
40
+ )
41
+ create_result: UCCreateOutput | None = Field(
42
+ default=None, description="Result of create action"
43
+ )
44
+ update_result: UCUpdateOutput | None = Field(
45
+ default=None, description="Result of update action"
46
+ )
47
+ info_result: UCInfoOutput | None = Field(
48
+ default=None, description="Result of info action"
49
+ )
50
+
51
+ model_config = {"arbitrary_types_allowed": True}
52
+
53
+
54
+ def _stub_not_implemented(action: str) -> UniversalConstructorOutput:
55
+ """Return a stub response for unimplemented actions."""
56
+ return UniversalConstructorOutput(
57
+ action=action,
58
+ success=False,
59
+ error="Not implemented yet",
60
+ )
61
+
62
+
63
+ def _run_ruff_format(file_path) -> str | None:
64
+ """Run ruff format on a file.
65
+
66
+ Args:
67
+ file_path: Path to the file to format (str or Path)
68
+
69
+ Returns:
70
+ Warning message if formatting failed, None on success
71
+ """
72
+ try:
73
+ result = subprocess.run(
74
+ ["ruff", "format", str(file_path)],
75
+ capture_output=True,
76
+ text=True,
77
+ timeout=10,
78
+ )
79
+ if result.returncode != 0:
80
+ return f"ruff format failed: {result.stderr.strip()}"
81
+ return None
82
+ except FileNotFoundError:
83
+ return "ruff not found - code not formatted"
84
+ except subprocess.TimeoutExpired:
85
+ return "ruff format timed out"
86
+ except Exception as e:
87
+ return f"ruff format error: {e}"
88
+
89
+
90
+ def _generate_preview(code: str, max_lines: int = 10) -> str:
91
+ """Generate a preview of the first N lines of code.
92
+
93
+ Args:
94
+ code: The source code to preview
95
+ max_lines: Maximum number of lines to include (default 10)
96
+
97
+ Returns:
98
+ A string with the first N lines, with "..." appended if truncated
99
+ """
100
+ lines = code.splitlines()
101
+ if len(lines) <= max_lines:
102
+ return code
103
+ preview_lines = lines[:max_lines]
104
+ return "\n".join(preview_lines) + "\n... (truncated)"
105
+
106
+
107
+ def _emit_uc_message(
108
+ action: str,
109
+ success: bool,
110
+ summary: str,
111
+ tool_name: str | None = None,
112
+ details: str | None = None,
113
+ ) -> None:
114
+ """Emit a UniversalConstructorMessage to the message bus.
115
+
116
+ Args:
117
+ action: The UC action performed (list/call/create/update/info)
118
+ success: Whether the operation succeeded
119
+ summary: Brief summary of the result
120
+ tool_name: Tool name if applicable
121
+ details: Additional details (optional)
122
+ """
123
+ bus = get_message_bus()
124
+ msg = UniversalConstructorMessage(
125
+ action=action,
126
+ tool_name=tool_name,
127
+ success=success,
128
+ summary=summary,
129
+ details=details,
130
+ )
131
+ bus.emit(msg)
132
+
133
+
134
+ async def universal_constructor_impl(
135
+ context: RunContext,
136
+ action: Literal["list", "call", "create", "update", "info"],
137
+ tool_name: str | None = None,
138
+ tool_args: dict | str | None = None,
139
+ python_code: str | None = None,
140
+ description: str | None = None,
141
+ ) -> UniversalConstructorOutput:
142
+ """Implementation of the universal_constructor tool.
143
+
144
+ Routes to appropriate action handler based on the action parameter.
145
+ All actions are currently stubbed out and will return "Not implemented yet".
146
+
147
+ Args:
148
+ context: The run context from pydantic-ai
149
+ action: The operation to perform:
150
+ - "list": List all available UC tools
151
+ - "call": Execute a specific UC tool
152
+ - "create": Create a new UC tool from Python code
153
+ - "update": Modify an existing UC tool
154
+ - "info": Get detailed info about a specific tool
155
+ tool_name: Name of tool (for call/update/info). Supports "namespace.name" format.
156
+ tool_args: Arguments to pass when calling a tool (for call action)
157
+ python_code: Python source code for the tool (for create/update actions)
158
+ description: Human-readable description (for create action)
159
+
160
+ Returns:
161
+ UniversalConstructorOutput with action-specific results
162
+ """
163
+ # Route to appropriate action handler
164
+ if action == "list":
165
+ result = _handle_list_action(context)
166
+ elif action == "call":
167
+ result = _handle_call_action(context, tool_name, tool_args)
168
+ elif action == "create":
169
+ result = _handle_create_action(context, tool_name, python_code, description)
170
+ elif action == "update":
171
+ result = _handle_update_action(context, tool_name, python_code, description)
172
+ elif action == "info":
173
+ result = _handle_info_action(context, tool_name)
174
+ else:
175
+ result = UniversalConstructorOutput(
176
+ action=action,
177
+ success=False,
178
+ error=f"Unknown action: {action}",
179
+ )
180
+
181
+ # Emit the banner message after the action completes
182
+ summary = _build_summary(result)
183
+ _emit_uc_message(
184
+ action=action,
185
+ success=result.success,
186
+ summary=summary,
187
+ tool_name=tool_name,
188
+ details=result.error if not result.success else None,
189
+ )
190
+
191
+ return result
192
+
193
+
194
+ def _build_summary(result: UniversalConstructorOutput) -> str:
195
+ """Build a brief summary string from a UC result.
196
+
197
+ Args:
198
+ result: The UniversalConstructorOutput to summarize
199
+
200
+ Returns:
201
+ A brief human-readable summary string
202
+ """
203
+ if not result.success:
204
+ return result.error or "Operation failed"
205
+
206
+ if result.list_result:
207
+ return f"Found {result.list_result.enabled_count} enabled tools (of {result.list_result.total_count} total)"
208
+ elif result.call_result:
209
+ exec_time = result.call_result.execution_time or 0
210
+ return f"Executed in {exec_time:.2f}s"
211
+ elif result.create_result:
212
+ return f"Created {result.create_result.tool_name}"
213
+ elif result.update_result:
214
+ return f"Updated {result.update_result.tool_name}"
215
+ elif result.info_result and result.info_result.tool:
216
+ return f"Info for {result.info_result.tool.full_name}"
217
+ else:
218
+ return "Operation completed"
219
+
220
+
221
+ def _handle_list_action(context: RunContext) -> UniversalConstructorOutput:
222
+ """Handle the 'list' action - list all available UC tools.
223
+
224
+ Lists all enabled tools from the UC registry, returning their
225
+ metadata, signatures, and source paths.
226
+
227
+ Args:
228
+ context: The run context from pydantic-ai (unused for list action)
229
+
230
+ Returns:
231
+ UniversalConstructorOutput with list_result containing all enabled tools.
232
+ """
233
+ from code_muse.plugins.universal_constructor.registry import get_registry
234
+
235
+ try:
236
+ registry = get_registry()
237
+ # Get all tools (including disabled for count)
238
+ all_tools = registry.list_tools(include_disabled=True)
239
+ enabled_tools = [t for t in all_tools if t.meta.enabled]
240
+
241
+ return UniversalConstructorOutput(
242
+ action="list",
243
+ success=True,
244
+ list_result=UCListOutput(
245
+ tools=enabled_tools,
246
+ total_count=len(all_tools),
247
+ enabled_count=len(enabled_tools),
248
+ ),
249
+ )
250
+ except Exception as e:
251
+ return UniversalConstructorOutput(
252
+ action="list",
253
+ success=False,
254
+ error=f"Failed to list tools: {e}",
255
+ list_result=UCListOutput(
256
+ tools=[],
257
+ total_count=0,
258
+ enabled_count=0,
259
+ error=str(e),
260
+ ),
261
+ )
262
+
263
+
264
+ def _handle_call_action(
265
+ context: RunContext,
266
+ tool_name: str | None,
267
+ tool_args: dict | str | None,
268
+ ) -> UniversalConstructorOutput:
269
+ """Handle the 'call' action - execute a UC tool.
270
+
271
+ Validates the tool exists and is enabled, then executes it with a timeout.
272
+
273
+ Args:
274
+ context: The run context from pydantic-ai
275
+ tool_name: Name of the tool to call (required)
276
+ tool_args: Arguments to pass to the tool function. Accepts either a
277
+ dict, or a JSON-encoded string (some model/transport layers
278
+ stringify nested objects in tool calls).
279
+
280
+ Returns:
281
+ UniversalConstructorOutput with call_result on success or error on failure
282
+ """
283
+ if not tool_name:
284
+ return UniversalConstructorOutput(
285
+ action="call",
286
+ success=False,
287
+ error="tool_name is required for call action",
288
+ )
289
+
290
+ from code_muse.plugins.universal_constructor.registry import get_registry
291
+
292
+ registry = get_registry()
293
+ tool = registry.get_tool(tool_name)
294
+
295
+ if not tool:
296
+ return UniversalConstructorOutput(
297
+ action="call",
298
+ success=False,
299
+ error=f"Tool '{tool_name}' not found",
300
+ )
301
+
302
+ if not tool.meta.enabled:
303
+ return UniversalConstructorOutput(
304
+ action="call",
305
+ success=False,
306
+ error=f"Tool '{tool_name}' is disabled",
307
+ )
308
+
309
+ # Read source for preview
310
+ source_preview = None
311
+ if tool.source_path:
312
+ try:
313
+ from pathlib import Path
314
+
315
+ source_code = Path(tool.source_path).read_text(encoding="utf-8")
316
+ source_preview = _generate_preview(source_code)
317
+ except Exception:
318
+ pass # Preview is optional, don't fail on read errors
319
+
320
+ func = registry.get_tool_function(tool_name)
321
+ if not func:
322
+ return UniversalConstructorOutput(
323
+ action="call",
324
+ success=False,
325
+ error=f"Could not load function for '{tool_name}'",
326
+ )
327
+
328
+ # Handle tool_args being passed as a JSON string (XML marshaling issue)
329
+ args = tool_args or {}
330
+ if isinstance(args, str):
331
+ try:
332
+ import json
333
+
334
+ args = json.loads(args)
335
+ except json.JSONDecodeError:
336
+ return UniversalConstructorOutput(
337
+ action="call",
338
+ success=False,
339
+ error=f"Invalid tool_args: expected dict or JSON string, got: {args[:100]}",
340
+ )
341
+ if not isinstance(args, dict):
342
+ return UniversalConstructorOutput(
343
+ action="call",
344
+ success=False,
345
+ error=f"tool_args must be a dict, got {type(args).__name__}",
346
+ )
347
+
348
+ # Execute with killable subprocess worker instead of thread-only
349
+ from code_muse.plugins.universal_constructor.runner import run_tool_subprocess
350
+
351
+ try:
352
+ run_result = run_tool_subprocess(
353
+ module_path=tool.source_path,
354
+ function_name=tool.function_name,
355
+ args=args,
356
+ timeout=30.0,
357
+ )
358
+
359
+ if not run_result["success"]:
360
+ return UniversalConstructorOutput(
361
+ action="call",
362
+ success=False,
363
+ error=run_result.get("error") or "Tool execution failed",
364
+ call_result=UCCallOutput(
365
+ success=False,
366
+ tool_name=tool_name,
367
+ error=run_result.get("error"),
368
+ execution_time=run_result.get("execution_time"),
369
+ source_preview=source_preview,
370
+ ),
371
+ )
372
+
373
+ return UniversalConstructorOutput(
374
+ action="call",
375
+ success=True,
376
+ call_result=UCCallOutput(
377
+ success=True,
378
+ tool_name=tool_name,
379
+ result=run_result.get("result"),
380
+ execution_time=run_result.get("execution_time"),
381
+ source_preview=source_preview,
382
+ ),
383
+ )
384
+ except Exception as e:
385
+ return UniversalConstructorOutput(
386
+ action="call",
387
+ success=False,
388
+ error=f"Tool execution failed: {e!s}",
389
+ )
390
+
391
+
392
+ def _handle_create_action(
393
+ context: RunContext,
394
+ tool_name: str | None,
395
+ python_code: str | None,
396
+ description: str | None,
397
+ ) -> UniversalConstructorOutput:
398
+ """Handle the 'create' action - create a new UC tool.
399
+
400
+ Creates a new tool from Python source code. The code can either include
401
+ a TOOL_META dictionary, or one will be generated from the provided
402
+ tool_name and description parameters.
403
+
404
+ Supports namespacing via dot notation in tool_name:
405
+ - "weather" → weather.py
406
+ - "api.weather" → api/weather.py
407
+ - "api.finance.stocks" → api/finance/stocks.py
408
+
409
+ Args:
410
+ context: The run context from pydantic-ai
411
+ tool_name: Name of the tool (with optional namespace). Required if
412
+ code doesn't contain TOOL_META.
413
+ python_code: Python source code defining the tool function (required)
414
+ description: Description of what the tool does. Used if no TOOL_META
415
+ in code.
416
+
417
+ Returns:
418
+ UniversalConstructorOutput with create_result on success
419
+ """
420
+ from datetime import datetime
421
+ from pathlib import Path
422
+
423
+ from code_muse.plugins.universal_constructor import USER_UC_DIR
424
+ from code_muse.plugins.universal_constructor.registry import get_registry
425
+ from code_muse.plugins.universal_constructor.safety import (
426
+ UCApprovalStore,
427
+ check_code_safety,
428
+ is_path_within_uc_dir,
429
+ validate_full_tool_name,
430
+ validate_namespace,
431
+ )
432
+ from code_muse.plugins.universal_constructor.sandbox import (
433
+ _extract_tool_meta,
434
+ _validate_tool_meta,
435
+ extract_function_info,
436
+ validate_syntax,
437
+ )
438
+
439
+ # Validate python_code is provided
440
+ if not python_code or not python_code.strip():
441
+ return UniversalConstructorOutput(
442
+ action="create",
443
+ success=False,
444
+ error="python_code is required for create action",
445
+ )
446
+
447
+ # Validate syntax
448
+ syntax_result = validate_syntax(python_code)
449
+ if not syntax_result.valid:
450
+ error_msg = "; ".join(syntax_result.errors)
451
+ return UniversalConstructorOutput(
452
+ action="create",
453
+ success=False,
454
+ error=f"Syntax error in code: {error_msg}",
455
+ )
456
+
457
+ # Extract function info
458
+ func_result = extract_function_info(python_code)
459
+ if not func_result.functions:
460
+ return UniversalConstructorOutput(
461
+ action="create",
462
+ success=False,
463
+ error="No functions found in code - tool must have at least one function",
464
+ )
465
+
466
+ # Get the first function as the main tool function
467
+ main_func = func_result.functions[0]
468
+
469
+ # Try to extract TOOL_META from code
470
+ existing_meta = _extract_tool_meta(python_code)
471
+
472
+ # Determine final tool name and namespace
473
+ if existing_meta and "name" in existing_meta:
474
+ # Use name from TOOL_META
475
+ final_name = existing_meta["name"]
476
+ final_namespace = existing_meta.get("namespace", "")
477
+ elif tool_name:
478
+ # Parse namespace from tool_name (e.g., "api.weather" → namespace="api", name="weather")
479
+ parts = tool_name.rsplit(".", 1)
480
+ if len(parts) == 2:
481
+ final_namespace, final_name = parts[0], parts[1]
482
+ else:
483
+ final_namespace, final_name = "", parts[0]
484
+ else:
485
+ # Use function name as tool name
486
+ final_name = main_func.name
487
+ final_namespace = ""
488
+
489
+ # Validate we have a name
490
+ if not final_name:
491
+ return UniversalConstructorOutput(
492
+ action="create",
493
+ success=False,
494
+ error="Could not determine tool name - provide tool_name or include TOOL_META in code",
495
+ )
496
+
497
+ # Strict name validation
498
+ full_name = f"{final_namespace}.{final_name}" if final_namespace else final_name
499
+ name_error = validate_full_tool_name(full_name)
500
+ if name_error:
501
+ return UniversalConstructorOutput(
502
+ action="create",
503
+ success=False,
504
+ error=name_error,
505
+ )
506
+
507
+ ns_error = validate_namespace(final_namespace)
508
+ if ns_error:
509
+ return UniversalConstructorOutput(
510
+ action="create",
511
+ success=False,
512
+ error=ns_error,
513
+ )
514
+
515
+ # Build file path based on namespace
516
+ if final_namespace:
517
+ # Convert dot notation to path (api.finance → api/finance/)
518
+ namespace_path = Path(*final_namespace.split("."))
519
+ file_dir = USER_UC_DIR / namespace_path
520
+ else:
521
+ file_dir = USER_UC_DIR
522
+
523
+ file_path = file_dir / f"{final_name}.py"
524
+
525
+ # Enforce path containment inside USER_UC_DIR
526
+ if not is_path_within_uc_dir(file_path, USER_UC_DIR):
527
+ return UniversalConstructorOutput(
528
+ action="create",
529
+ success=False,
530
+ error=f"Tool path escapes UC directory: {file_path}",
531
+ )
532
+
533
+ # Build the final code to write
534
+ validation_warnings = []
535
+
536
+ if existing_meta:
537
+ # Validate existing TOOL_META has required fields
538
+ meta_errors = _validate_tool_meta(existing_meta)
539
+ if meta_errors:
540
+ return UniversalConstructorOutput(
541
+ action="create",
542
+ success=False,
543
+ error="Invalid TOOL_META: " + "; ".join(meta_errors),
544
+ )
545
+ # Code already has TOOL_META, use as-is
546
+ final_code = python_code
547
+ # Collect any validation warnings
548
+ validation_warnings.extend(func_result.warnings)
549
+ else:
550
+ # Generate TOOL_META and prepend to code
551
+ final_description = description or main_func.docstring or f"Tool: {final_name}"
552
+
553
+ generated_meta = {
554
+ "name": final_name,
555
+ "namespace": final_namespace,
556
+ "description": final_description,
557
+ "enabled": True,
558
+ "version": "1.0.0",
559
+ "author": "user",
560
+ "created_at": datetime.now().isoformat(),
561
+ }
562
+
563
+ # Format TOOL_META as a dict literal
564
+ meta_str = f"TOOL_META = {repr(generated_meta)}\n\n"
565
+ final_code = meta_str + python_code
566
+ validation_warnings.append("TOOL_META was auto-generated")
567
+ validation_warnings.extend(func_result.warnings)
568
+
569
+ # Hardened safety check: block dangerous code, gate approval-required
570
+ safety = check_code_safety(python_code)
571
+ if safety.blocked:
572
+ return UniversalConstructorOutput(
573
+ action="create",
574
+ success=False,
575
+ error=f"Tool creation blocked: {'; '.join(safety.errors)}",
576
+ )
577
+
578
+ if safety.requires_approval:
579
+ # Store approval so the tool can be enabled after user confirmation
580
+ approval_store = UCApprovalStore()
581
+ approval_store.approve(full_name, safety.code_hash)
582
+ validation_warnings.append(
583
+ "Tool requires explicit approval due to potentially dangerous patterns. "
584
+ f"Auto-approved for this session (hash={safety.code_hash[:16]}...)."
585
+ )
586
+
587
+ # Ensure directory exists and write file
588
+ try:
589
+ file_dir.mkdir(parents=True, exist_ok=True)
590
+ file_path.write_text(final_code, encoding="utf-8")
591
+ except Exception as e:
592
+ return UniversalConstructorOutput(
593
+ action="create",
594
+ success=False,
595
+ error=f"Failed to write tool file: {e}",
596
+ )
597
+
598
+ # Run ruff format on the new file
599
+ format_warning = _run_ruff_format(file_path)
600
+ if format_warning:
601
+ validation_warnings.append(format_warning)
602
+
603
+ # Read formatted code for preview
604
+ formatted_code = file_path.read_text(encoding="utf-8")
605
+
606
+ # Reload registry to pick up the new tool
607
+ try:
608
+ registry = get_registry()
609
+ registry.reload()
610
+ except Exception as e:
611
+ # Tool was written but registry reload failed - still a partial success
612
+ validation_warnings.append(f"Tool created but registry reload failed: {e}")
613
+
614
+ return UniversalConstructorOutput(
615
+ action="create",
616
+ success=True,
617
+ create_result=UCCreateOutput(
618
+ success=True,
619
+ tool_name=full_name,
620
+ source_path=str(file_path),
621
+ preview=_generate_preview(formatted_code),
622
+ validation_warnings=validation_warnings,
623
+ ),
624
+ )
625
+
626
+
627
+ def _handle_update_action(
628
+ context: RunContext,
629
+ tool_name: str | None,
630
+ python_code: str | None,
631
+ description: str | None,
632
+ ) -> UniversalConstructorOutput:
633
+ """Handle the 'update' action - modify an existing UC tool.
634
+
635
+ Replaces an existing tool's code with new Python source code.
636
+ The new code must contain a valid TOOL_META dictionary.
637
+
638
+ Hardened to enforce path containment and block dangerous patterns.
639
+
640
+ Args:
641
+ context: The run context from pydantic-ai
642
+ tool_name: Name of the tool to update (required)
643
+ python_code: New Python source code (required)
644
+ description: Reserved for future use (currently ignored)
645
+
646
+ Returns:
647
+ UniversalConstructorOutput with update_result on success
648
+ """
649
+ from pathlib import Path
650
+
651
+ from code_muse.plugins.universal_constructor import USER_UC_DIR
652
+ from code_muse.plugins.universal_constructor.registry import get_registry
653
+ from code_muse.plugins.universal_constructor.safety import (
654
+ UCApprovalStore,
655
+ check_code_safety,
656
+ is_path_within_uc_dir,
657
+ )
658
+ from code_muse.plugins.universal_constructor.sandbox import (
659
+ _extract_tool_meta,
660
+ _validate_tool_meta,
661
+ validate_syntax,
662
+ )
663
+
664
+ if not tool_name:
665
+ return UniversalConstructorOutput(
666
+ action="update",
667
+ success=False,
668
+ error="tool_name is required for update action",
669
+ )
670
+
671
+ # python_code is required for updates
672
+ if not python_code:
673
+ return UniversalConstructorOutput(
674
+ action="update",
675
+ success=False,
676
+ error="python_code is required for update action",
677
+ )
678
+
679
+ registry = get_registry()
680
+ tool = registry.get_tool(tool_name)
681
+
682
+ if not tool:
683
+ return UniversalConstructorOutput(
684
+ action="update",
685
+ success=False,
686
+ error=f"Tool '{tool_name}' not found",
687
+ )
688
+
689
+ source_path = tool.source_path
690
+ source_path_obj = Path(source_path) if source_path else None
691
+ if not source_path_obj or not source_path_obj.exists():
692
+ return UniversalConstructorOutput(
693
+ action="update",
694
+ success=False,
695
+ error="Tool has no source path or file does not exist",
696
+ )
697
+
698
+ # Enforce path containment inside USER_UC_DIR
699
+ if not is_path_within_uc_dir(source_path_obj, USER_UC_DIR):
700
+ return UniversalConstructorOutput(
701
+ action="update",
702
+ success=False,
703
+ error=f"Tool path escapes UC directory: {source_path_obj}",
704
+ )
705
+
706
+ try:
707
+ # Validate new code syntax
708
+ syntax_result = validate_syntax(python_code)
709
+ if not syntax_result.valid:
710
+ error_msg = "; ".join(syntax_result.errors)
711
+ return UniversalConstructorOutput(
712
+ action="update",
713
+ success=False,
714
+ error=f"Syntax error in new code: {error_msg}",
715
+ )
716
+
717
+ # Validate TOOL_META exists in new code
718
+ new_meta = _extract_tool_meta(python_code)
719
+ if new_meta is None:
720
+ return UniversalConstructorOutput(
721
+ action="update",
722
+ success=False,
723
+ error="New code must contain a valid TOOL_META dictionary",
724
+ )
725
+
726
+ # Validate TOOL_META has required fields
727
+ meta_errors = _validate_tool_meta(new_meta)
728
+ if meta_errors:
729
+ return UniversalConstructorOutput(
730
+ action="update",
731
+ success=False,
732
+ error="Invalid TOOL_META: " + "; ".join(meta_errors),
733
+ )
734
+
735
+ # Hardened safety check: block dangerous code
736
+ safety = check_code_safety(python_code)
737
+ if safety.blocked:
738
+ return UniversalConstructorOutput(
739
+ action="update",
740
+ success=False,
741
+ error=f"Tool update blocked: {'; '.join(safety.errors)}",
742
+ )
743
+
744
+ # Write updated code
745
+ source_path_obj.write_text(python_code, encoding="utf-8")
746
+
747
+ # Run ruff format on the updated file
748
+ format_warning = _run_ruff_format(source_path_obj)
749
+ changes = ["Replaced source code"]
750
+ if format_warning:
751
+ changes.append(f"Format warning: {format_warning}")
752
+ else:
753
+ changes.append("Formatted with ruff")
754
+
755
+ # Read formatted code for preview
756
+ formatted_code = source_path_obj.read_text(encoding="utf-8")
757
+
758
+ # Reload registry to pick up changes
759
+ registry.reload()
760
+
761
+ # Invalidate previous approval if hash changed
762
+ if safety.requires_approval:
763
+ approval_store = UCApprovalStore()
764
+ approval_store.approve(tool_name, safety.code_hash)
765
+ changes.append(
766
+ f"Approval updated for new code hash ({safety.code_hash[:16]}...)"
767
+ )
768
+
769
+ return UniversalConstructorOutput(
770
+ action="update",
771
+ success=True,
772
+ update_result=UCUpdateOutput(
773
+ success=True,
774
+ tool_name=tool_name,
775
+ source_path=source_path,
776
+ preview=_generate_preview(formatted_code),
777
+ changes_applied=changes,
778
+ ),
779
+ )
780
+
781
+ except Exception as e:
782
+ return UniversalConstructorOutput(
783
+ action="update",
784
+ success=False,
785
+ error=f"Failed to update tool: {e}",
786
+ )
787
+
788
+
789
+ def _handle_info_action(
790
+ context: RunContext,
791
+ tool_name: str | None,
792
+ ) -> UniversalConstructorOutput:
793
+ """Handle the 'info' action - get detailed tool information.
794
+
795
+ Retrieves comprehensive information about a UC tool including its
796
+ metadata, source code, and function signature.
797
+
798
+ Args:
799
+ context: The run context from pydantic-ai
800
+ tool_name: Full name of the tool (including namespace)
801
+
802
+ Returns:
803
+ UniversalConstructorOutput with info_result containing tool details
804
+ """
805
+ from pathlib import Path
806
+
807
+ from code_muse.plugins.universal_constructor.registry import get_registry
808
+
809
+ if not tool_name:
810
+ return UniversalConstructorOutput(
811
+ action="info",
812
+ success=False,
813
+ error="tool_name is required for info action",
814
+ )
815
+
816
+ registry = get_registry()
817
+ tool = registry.get_tool(tool_name)
818
+
819
+ if not tool:
820
+ return UniversalConstructorOutput(
821
+ action="info",
822
+ success=False,
823
+ error=f"Tool '{tool_name}' not found",
824
+ )
825
+
826
+ # Read source code from file
827
+ source_code = ""
828
+ source_path = tool.source_path
829
+ source_path_obj = Path(source_path) if source_path else None
830
+ if source_path_obj and source_path_obj.exists():
831
+ try:
832
+ source_code = source_path_obj.read_text(encoding="utf-8")
833
+ except Exception:
834
+ source_code = "[Could not read source]"
835
+ else:
836
+ source_code = "[Source file not found]"
837
+
838
+ return UniversalConstructorOutput(
839
+ action="info",
840
+ success=True,
841
+ info_result=UCInfoOutput(
842
+ success=True,
843
+ tool=tool,
844
+ source_code=source_code,
845
+ ),
846
+ )
847
+
848
+
849
+ def register_universal_constructor(agent):
850
+ """Register the universal_constructor tool with an agent.
851
+
852
+ Args:
853
+ agent: The pydantic-ai agent to register the tool with
854
+ """
855
+
856
+ @agent.tool
857
+ async def universal_constructor(
858
+ context: RunContext,
859
+ action: Literal["list", "call", "create", "update", "info"],
860
+ tool_name: str | None = None,
861
+ tool_args: dict | str | None = None,
862
+ python_code: str | None = None,
863
+ description: str | None = None,
864
+ ) -> UniversalConstructorOutput:
865
+ """Universal Constructor - Your gateway to unlimited capabilities.
866
+
867
+ **YOU CAN BUILD ANYTHING.** The Universal Constructor empowers you to create
868
+ any tool you can imagine using Python. If you can write Python code for it,
869
+ you can make it a permanent, reusable tool. This is your superpower.
870
+
871
+ **CAPABILITIES YOU CAN CREATE:**
872
+ - API integrations (REST, GraphQL, webhooks, any HTTP service)
873
+ - Database connectors (SQL, NoSQL, any data store)
874
+ - File processors (parsers, converters, analyzers for any format)
875
+ - Web scrapers and data extractors
876
+ - Mathematical and scientific computations
877
+ - Text processing and NLP utilities
878
+ - Image and media manipulation (with PIL, etc.)
879
+ - System utilities and automation scripts
880
+ - Network tools (DNS, HTTP, socket operations)
881
+ - Cryptographic functions
882
+ - Data transformation pipelines
883
+ - Custom business logic for any domain
884
+ - Integration with any Python library (requests, pandas, numpy, etc.)
885
+ - Scheduled tasks and background operations
886
+ - Custom validators and formatters
887
+ - Domain-specific calculators
888
+ - ANY Python functionality you need!
889
+
890
+ **WHEN TO USE THIS:**
891
+ - User asks for something your current tools can't do
892
+ - You need to call an external API or service
893
+ - You need custom data processing logic
894
+ - You want to automate a repetitive task
895
+ - You need domain-specific functionality
896
+ - ANY time you think "I wish I had a tool that..."
897
+
898
+ **PHILOSOPHY:** Don't say "I can't do that" - instead, BUILD a tool that can!
899
+ Tools you create persist across sessions, so build once, use forever.
900
+
901
+ Args:
902
+ action: The operation to perform:
903
+ - "list": List all available custom tools with their metadata
904
+ - "call": Execute a specific custom tool with provided arguments
905
+ - "create": Create a new tool from Python code
906
+ - "update": Modify an existing tool's code or metadata
907
+ - "info": Get detailed information about a specific tool
908
+ tool_name: Name of the tool (required for call/update/info actions).
909
+ Supports namespaced format like "namespace.tool_name" for organization.
910
+ tool_args: Dictionary of arguments to pass when calling a tool.
911
+ Only used with action="call". Also accepts a JSON-encoded
912
+ string for compatibility with model/transport layers that
913
+ stringify nested objects in tool calls.
914
+ python_code: Python source code defining the tool function.
915
+ Required for action="create" and action="update".
916
+ You have access to the FULL Python standard library plus any
917
+ installed packages (requests, etc.).
918
+ description: Human-readable description of what the tool does.
919
+ Used with action="create".
920
+
921
+ Returns:
922
+ UniversalConstructorOutput with action-specific results.
923
+
924
+ Examples:
925
+ # Create an API client tool
926
+ code = '''
927
+ import httpx
928
+ TOOL_META = {"name": "weather", "description": "Get weather data"}
929
+ def weather(city: str) -> dict:
930
+ resp = httpx.get(f"https://wttr.in/{city}?format=j1")
931
+ return resp.json()
932
+ '''
933
+ universal_constructor(ctx, action="create", python_code=code)
934
+
935
+ # Create a data processor
936
+ code = '''
937
+ import json
938
+ TOOL_META = {"name": "csv_to_json", "description": "Convert CSV to JSON"}
939
+ def csv_to_json(csv_text: str) -> list:
940
+ lines = csv_text.strip().split("\\n")
941
+ headers = lines[0].split(",")
942
+ return [{h: v for h, v in zip(headers, line.split(","))}
943
+ for line in lines[1:]]
944
+ '''
945
+ universal_constructor(ctx, action="create", python_code=code)
946
+
947
+ # Create a utility tool
948
+ code = '''
949
+ import hashlib
950
+ TOOL_META = {"name": "hasher", "description": "Hash strings"}
951
+ def hasher(text: str, algorithm: str = "sha256") -> str:
952
+ h = hashlib.new(algorithm)
953
+ h.update(text.encode())
954
+ return h.hexdigest()
955
+ '''
956
+ universal_constructor(ctx, action="create", python_code=code)
957
+
958
+ Note:
959
+ Tools are stored in ~/.muse/plugins/universal_constructor/ and
960
+ persist forever. Organize with namespaces: "api.weather", "utils.hasher".
961
+ Code is auto-formatted with ruff. Check existing tools with action="list".
962
+ """
963
+ return await universal_constructor_impl(
964
+ context, action, tool_name, tool_args, python_code, description
965
+ )