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
code_muse/motion.py ADDED
@@ -0,0 +1,126 @@
1
+ """Centralized motion primitives for animated terminal output.
2
+
3
+ All animated effects (shimmer, spinners, indicators) should go through this
4
+ module so callers get a consistent reduced-motion fallback automatically.
5
+
6
+ Usage:
7
+ from code_muse.motion import MotionMode, activity_indicator, shimmer_text
8
+
9
+ mode = MotionMode.from_animations_enabled(True)
10
+
11
+ # A shimmering bullet as an activity indicator
12
+ bullet = activity_indicator(start_time, mode)
13
+
14
+ # Shimmer text like "Thinking..."
15
+ spans = shimmer_text("Thinking...", mode)
16
+ """
17
+
18
+ import time
19
+ from enum import Enum
20
+
21
+ from rich.style import Style
22
+ from rich.text import Span
23
+
24
+ from code_muse.messaging.shimmer import shimmer_spans
25
+
26
+
27
+ class MotionMode(Enum):
28
+ """Animation mode: full animation or reduced-motion fallback."""
29
+
30
+ ANIMATED = "animated"
31
+ REDUCED = "reduced"
32
+
33
+ @classmethod
34
+ def from_animations_enabled(cls, enabled: bool) -> MotionMode:
35
+ """Convert a boolean flag to the corresponding mode."""
36
+ return cls.ANIMATED if enabled else cls.REDUCED
37
+
38
+
39
+ class ReducedMotionIndicator(Enum):
40
+ """Fallback indicator style when animations are disabled."""
41
+
42
+ HIDDEN = "hidden" # nothing shown
43
+ STATIC_BULLET = "static_bullet" # a plain dimmed bullet
44
+
45
+
46
+ def activity_indicator(
47
+ start_time: float | None = None,
48
+ motion_mode: MotionMode = MotionMode.ANIMATED,
49
+ reduced_fallback: ReducedMotionIndicator = ReducedMotionIndicator.STATIC_BULLET,
50
+ ) -> Span | None:
51
+ """Return an activity-indicator ``Span`` appropriate for *motion_mode*.
52
+
53
+ In ``ANIMATED`` mode this is a shimmering bullet ``•`` (truecolor
54
+ terminal) or a blinking alternate-character effect (basic terminal).
55
+
56
+ In ``REDUCED`` mode it follows *reduced_fallback*:
57
+ - ``HIDDEN`` → ``None`` (invisible)
58
+ - ``STATIC_BULLET`` → a dimmed ``•``
59
+
60
+ Args:
61
+ start_time: ``time.monotonic()`` value captured when the activity
62
+ began, or ``None``.
63
+ motion_mode: Current animation mode.
64
+ reduced_fallback: What to show in reduced-motion mode.
65
+
66
+ Returns:
67
+ A ``Span`` or ``None``.
68
+ """
69
+ if motion_mode == MotionMode.REDUCED:
70
+ if reduced_fallback == ReducedMotionIndicator.HIDDEN:
71
+ return None
72
+ # StaticBullet
73
+ return Span(0, 1, Style(dim=True))
74
+
75
+ # Animated mode
76
+ elapsed = time.monotonic() - start_time if start_time else 0.0
77
+
78
+ has_truecolor = _has_truecolor()
79
+ if has_truecolor:
80
+ # Use a shimmer bullet — single character through the shimmer pipeline.
81
+ spans = shimmer_spans("•")
82
+ return spans[0] if spans else Span(0, 1, Style())
83
+ else:
84
+ # 600ms blink period: alternate "•" / "◦".
85
+ blink_on = int(elapsed * 1000 / 600) % 2 == 0
86
+ if blink_on:
87
+ return Span(0, 1, Style())
88
+ else:
89
+ return Span(0, 1, Style(dim=True))
90
+
91
+
92
+ def shimmer_text(
93
+ text: str,
94
+ motion_mode: MotionMode = MotionMode.ANIMATED,
95
+ ) -> list[Span]:
96
+ """Return shimmer-styled spans for *text*, or plain text in reduced mode.
97
+
98
+ Args:
99
+ text: The text to style.
100
+ motion_mode: Current animation mode.
101
+
102
+ Returns:
103
+ List of ``Span`` objects.
104
+ """
105
+ if motion_mode == MotionMode.REDUCED:
106
+ if not text:
107
+ return []
108
+ return [Span(0, len(text), Style())]
109
+ return shimmer_spans(text)
110
+
111
+
112
+ # ---------------------------------------------------------------------------
113
+ # Internal helpers
114
+ # ---------------------------------------------------------------------------
115
+
116
+ _truecolor_cache: bool | None = None
117
+
118
+
119
+ def _has_truecolor() -> bool:
120
+ """Cached truecolor detection."""
121
+ global _truecolor_cache
122
+ if _truecolor_cache is None:
123
+ from code_muse.terminal_utils import detect_truecolor_support
124
+
125
+ _truecolor_cache = detect_truecolor_support()
126
+ return _truecolor_cache
@@ -0,0 +1,471 @@
1
+ """Plugin loading with trust model for user plugins.
2
+
3
+ Built-in plugins (under code_muse/plugins/) load unconditionally.
4
+ User plugins (under ~/.muse/plugins/) require explicit trust
5
+ recorded in a manifest keyed by content hash; fail closed by default.
6
+ No sys.path insertion — user plugins are loaded via importlib with
7
+ unique module names to prevent stdlib/project shadowing.
8
+ """
9
+
10
+ import contextlib
11
+ import hashlib
12
+ import importlib
13
+ import importlib.util
14
+ import json
15
+ import logging
16
+ import os
17
+ import re
18
+ import sys
19
+ from pathlib import Path
20
+
21
+ from code_muse.secret_storage import atomic_write_private_json, ensure_private_dir
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+ # User plugins directory
26
+ USER_PLUGINS_DIR = Path.home() / ".muse" / "plugins"
27
+
28
+ # Track if plugins have already been loaded to prevent duplicate registration
29
+ _PLUGINS_LOADED = False
30
+
31
+ # ---------------------------------------------------------------------------
32
+ # Trust manifest helpers
33
+ # ---------------------------------------------------------------------------
34
+
35
+ # Env var / monkeypatch-friendly override for trust manifest path.
36
+ # Set MUSE_PLUGIN_TRUST_MANIFEST to a file path to redirect the DB.
37
+ _TRUST_MANIFEST_ENV = "MUSE_PLUGIN_TRUST_MANIFEST"
38
+
39
+ # Safe plugin name pattern: only alphanumeric + underscore + hyphen
40
+ _SAFE_NAME_RE = re.compile(r"^[a-zA-Z0-9_-]+$")
41
+
42
+ # Max SKILL.md content size (bytes) before we refuse to read
43
+ _MAX_SKILL_MD_BYTES = 256 * 1024 # 256 KiB
44
+
45
+ # Cap for skill content injected into model context (chars)
46
+ _SKILL_CONTEXT_CAP = 64_000 # ~64k chars
47
+
48
+
49
+ def _default_trust_manifest_path() -> Path:
50
+ """Return default path for the plugin trust manifest."""
51
+ return Path.home() / ".muse" / "plugin_trust.json"
52
+
53
+
54
+ def get_trust_manifest_path() -> Path:
55
+ """Return the trust manifest path (env-override aware)."""
56
+ env_val = os.environ.get(_TRUST_MANIFEST_ENV)
57
+ if env_val:
58
+ return Path(env_val)
59
+ return _default_trust_manifest_path()
60
+
61
+
62
+ def _load_trust_manifest() -> dict:
63
+ """Load the trust manifest from disk. Returns {} on any error."""
64
+ path = get_trust_manifest_path()
65
+ if not path.exists():
66
+ return {}
67
+ try:
68
+ text = path.read_text(encoding="utf-8")
69
+ data = json.loads(text)
70
+ if isinstance(data, dict):
71
+ return data
72
+ except (json.JSONDecodeError, OSError) as exc:
73
+ logger.warning("Failed to read plugin trust manifest at %s: %s", path, exc)
74
+ return {}
75
+
76
+
77
+ def _save_trust_manifest(manifest: dict) -> None:
78
+ """Persist the trust manifest to disk atomically with private perms."""
79
+ path = get_trust_manifest_path()
80
+ try:
81
+ ensure_private_dir(path.parent)
82
+ atomic_write_private_json(path, manifest)
83
+ except OSError as exc:
84
+ logger.warning("Failed to save plugin trust manifest: %s", exc)
85
+
86
+
87
+ def compute_plugin_hash(plugin_dir: Path) -> str:
88
+ """Compute a SHA-256 hash over all .py files in a plugin directory.
89
+
90
+ Hashes register_callbacks.py, __init__.py, and every other .py file
91
+ found recursively under *plugin_dir*. Files are sorted by relative
92
+ path for deterministic ordering. The hash covers file *contents*, not
93
+ just file names.
94
+
95
+ Returns the hex digest string.
96
+ """
97
+ h = hashlib.sha256()
98
+ py_files: list[Path] = []
99
+ try:
100
+ for child in sorted(plugin_dir.rglob("*.py")):
101
+ # Skip hidden files / dirs
102
+ if any(
103
+ part.startswith(".") for part in child.relative_to(plugin_dir).parts
104
+ ):
105
+ continue
106
+ # Skip symlink escapes
107
+ try:
108
+ child.resolve().relative_to(plugin_dir.resolve())
109
+ except ValueError:
110
+ continue
111
+ py_files.append(child)
112
+ except OSError:
113
+ pass
114
+
115
+ for fpath in sorted(py_files):
116
+ rel = fpath.relative_to(plugin_dir)
117
+ h.update(str(rel).encode())
118
+ h.update(b"\0")
119
+ with contextlib.suppress(OSError):
120
+ h.update(fpath.read_bytes())
121
+ h.update(b"\0")
122
+
123
+ return h.hexdigest()
124
+
125
+
126
+ def is_plugin_trusted(plugin_name: str, content_hash: str) -> bool:
127
+ """Check if a user plugin is trusted (manifest contains matching hash)."""
128
+ manifest = _load_trust_manifest()
129
+ entry = manifest.get(plugin_name)
130
+ if not isinstance(entry, dict):
131
+ return False
132
+ return entry.get("hash") == content_hash
133
+
134
+
135
+ def record_plugin_trust(plugin_name: str, content_hash: str, plugin_dir: str) -> None:
136
+ """Record trust for a user plugin in the manifest."""
137
+ manifest = _load_trust_manifest()
138
+ manifest[plugin_name] = {
139
+ "hash": content_hash,
140
+ "path": plugin_dir,
141
+ "trusted_at": _utc_now_iso(),
142
+ }
143
+ _save_trust_manifest(manifest)
144
+
145
+
146
+ def revoke_plugin_trust(plugin_name: str) -> None:
147
+ """Remove a plugin from the trust manifest."""
148
+ manifest = _load_trust_manifest()
149
+ manifest.pop(plugin_name, None)
150
+ _save_trust_manifest(manifest)
151
+
152
+
153
+ def _utc_now_iso() -> str:
154
+ """Return current UTC time as ISO string (no heavy deps)."""
155
+ import datetime
156
+
157
+ return datetime.datetime.now(datetime.UTC).isoformat()
158
+
159
+
160
+ # ---------------------------------------------------------------------------
161
+ # Symlink / hidden-directory safety checks
162
+ # ---------------------------------------------------------------------------
163
+
164
+
165
+ def _is_symlink_escape(child: Path, parent: Path) -> bool:
166
+ """Return True if *child* resolves outside *parent* (symlink escape)."""
167
+ try:
168
+ child.resolve().relative_to(parent.resolve())
169
+ return False
170
+ except ValueError:
171
+ return True
172
+
173
+
174
+ def _should_skip_entry(item: Path, parent: Path) -> bool:
175
+ """Return True if *item* should be skipped during plugin/skill discovery.
176
+
177
+ Skips:
178
+ - hidden dirs (name starts with '.' or '_')
179
+ - symlink escapes outside *parent*
180
+ """
181
+ if item.name.startswith(".") or item.name.startswith("_"):
182
+ return True
183
+ if _is_symlink_escape(item, parent):
184
+ logger.warning(
185
+ "Skipping %s: resolves outside parent directory (symlink escape)", item
186
+ )
187
+ return True
188
+ return False
189
+
190
+
191
+ # ---------------------------------------------------------------------------
192
+ # Built-in plugin loading
193
+ # ---------------------------------------------------------------------------
194
+
195
+
196
+ def _load_builtin_plugins(
197
+ plugins_dir: Path, failed_names: list[str] | None = None
198
+ ) -> list[str]:
199
+ """Load built-in plugins from the package plugins directory.
200
+
201
+ Returns list of successfully loaded plugin names.
202
+ """
203
+ loaded = []
204
+
205
+ for item in plugins_dir.iterdir():
206
+ if item.is_dir() and not item.name.startswith("_"):
207
+ plugin_name = item.name
208
+ callbacks_file = item / "register_callbacks.py"
209
+
210
+ if callbacks_file.exists():
211
+ try:
212
+ module_name = f"code_muse.plugins.{plugin_name}.register_callbacks"
213
+ importlib.import_module(module_name)
214
+ loaded.append(plugin_name)
215
+ except ImportError as e:
216
+ logger.warning(
217
+ "Failed to import callbacks from built-in plugin %s: %s",
218
+ plugin_name,
219
+ e,
220
+ )
221
+ if failed_names is not None:
222
+ failed_names.append(plugin_name)
223
+ except Exception as e:
224
+ logger.error(
225
+ "Unexpected error loading built-in plugin %s: %s",
226
+ plugin_name,
227
+ e,
228
+ )
229
+ if failed_names is not None:
230
+ failed_names.append(plugin_name)
231
+
232
+ return loaded
233
+
234
+
235
+ # ---------------------------------------------------------------------------
236
+ # User plugin loading (trust-gated)
237
+ # ---------------------------------------------------------------------------
238
+
239
+
240
+ def _make_user_module_name(plugin_name: str, content_hash: str) -> str:
241
+ """Build a unique, safe module name for a user plugin.
242
+
243
+ Format: ``code_muse_user_plugin_{safe_name}_{hash_prefix}``
244
+ The hash prefix (first 12 chars) avoids name collisions while keeping
245
+ the module name readable.
246
+ """
247
+ safe = re.sub(r"[^a-zA-Z0-9_]", "_", plugin_name)
248
+ return f"code_muse_user_plugin_{safe}_{content_hash[:12]}"
249
+
250
+
251
+ def _load_single_user_plugin(
252
+ plugin_dir: Path,
253
+ plugin_name: str,
254
+ user_plugins_dir: Path,
255
+ failed_names: list[str] | None = None,
256
+ ) -> str | None:
257
+ """Attempt to load a single user plugin directory.
258
+
259
+ Returns the plugin name on success, None on failure/skip.
260
+ """
261
+ # Safety checks
262
+ if _should_skip_entry(plugin_dir, user_plugins_dir):
263
+ return None
264
+
265
+ # Validate plugin name
266
+ if not _SAFE_NAME_RE.match(plugin_name):
267
+ logger.warning(
268
+ "Skipping user plugin '%s': name contains unsafe characters", plugin_name
269
+ )
270
+ return None
271
+
272
+ callbacks_file = plugin_dir / "register_callbacks.py"
273
+ init_file = plugin_dir / "__init__.py"
274
+
275
+ # Pick the file to load (prefer register_callbacks.py)
276
+ load_file = None
277
+ if callbacks_file.exists():
278
+ if _is_symlink_escape(callbacks_file, plugin_dir):
279
+ logger.warning(
280
+ "Skipping user plugin '%s': register_callbacks.py is a symlink escape",
281
+ plugin_name,
282
+ )
283
+ return None
284
+ load_file = callbacks_file
285
+ elif init_file.exists():
286
+ if _is_symlink_escape(init_file, plugin_dir):
287
+ logger.warning(
288
+ "Skipping user plugin '%s': __init__.py is a symlink escape",
289
+ plugin_name,
290
+ )
291
+ return None
292
+ load_file = init_file
293
+ else:
294
+ # No entry point file
295
+ return None
296
+
297
+ # Compute content hash for trust check
298
+ content_hash = compute_plugin_hash(plugin_dir)
299
+
300
+ # Fail closed: untrusted plugins are NOT imported
301
+ if not is_plugin_trusted(plugin_name, content_hash):
302
+ logger.warning(
303
+ "User plugin '%s' is not trusted (hash: %s…). "
304
+ "To trust it, run: /plugin trust %s "
305
+ "or set MUSE_TRUST_ALL_USER_PLUGINS=1 (dangerous).",
306
+ plugin_name,
307
+ content_hash[:12],
308
+ plugin_name,
309
+ )
310
+ return None
311
+
312
+ # Build unique module name to avoid import shadowing
313
+ module_name = _make_user_module_name(plugin_name, content_hash)
314
+
315
+ try:
316
+ spec = importlib.util.spec_from_file_location(module_name, load_file)
317
+ if spec is None or spec.loader is None:
318
+ logger.warning(
319
+ "Could not create module spec for user plugin: %s", plugin_name
320
+ )
321
+ if failed_names is not None:
322
+ failed_names.append(plugin_name)
323
+ return None
324
+
325
+ module = importlib.util.module_from_spec(spec)
326
+ sys.modules[module_name] = module
327
+ spec.loader.exec_module(module)
328
+ return plugin_name
329
+
330
+ except ImportError as e:
331
+ logger.warning(
332
+ "Failed to import callbacks from user plugin %s: %s", plugin_name, e
333
+ )
334
+ if failed_names is not None:
335
+ failed_names.append(plugin_name)
336
+ except Exception as e:
337
+ logger.error(
338
+ "Unexpected error loading user plugin %s: %s", plugin_name, e, exc_info=True
339
+ )
340
+ if failed_names is not None:
341
+ failed_names.append(plugin_name)
342
+
343
+ return None
344
+
345
+
346
+ def _load_user_plugins(
347
+ user_plugins_dir: Path, failed_names: list[str] | None = None
348
+ ) -> list[str]:
349
+ """Load user plugins from ~/.muse/plugins/.
350
+
351
+ Each plugin should be a directory containing a register_callbacks.py file.
352
+ Plugins are loaded via importlib with unique module names — no sys.path
353
+ insertion. Untrusted plugins are skipped with a clear warning.
354
+
355
+ Returns list of successfully loaded plugin names.
356
+ """
357
+ loaded = []
358
+
359
+ if not user_plugins_dir.exists():
360
+ return loaded
361
+
362
+ if not user_plugins_dir.is_dir():
363
+ logger.warning("User plugins path is not a directory: %s", user_plugins_dir)
364
+ return loaded
365
+
366
+ # Allow trusting all user plugins via env var (for development / CI)
367
+ trust_all = os.environ.get("MUSE_TRUST_ALL_USER_PLUGINS", "") == "1"
368
+
369
+ for item in user_plugins_dir.iterdir():
370
+ if not item.is_dir():
371
+ continue
372
+
373
+ plugin_name = item.name
374
+
375
+ # Safety checks
376
+ if _should_skip_entry(item, user_plugins_dir):
377
+ continue
378
+
379
+ # Validate plugin name
380
+ if not _SAFE_NAME_RE.match(plugin_name):
381
+ logger.warning(
382
+ "Skipping user plugin '%s': name contains unsafe characters",
383
+ plugin_name,
384
+ )
385
+ continue
386
+
387
+ # Dev override: auto-trust everything
388
+ if trust_all:
389
+ content_hash = compute_plugin_hash(item)
390
+ if not is_plugin_trusted(plugin_name, content_hash):
391
+ record_plugin_trust(plugin_name, content_hash, str(item))
392
+
393
+ result = _load_single_user_plugin(
394
+ item, plugin_name, user_plugins_dir, failed_names
395
+ )
396
+ if result is not None:
397
+ loaded.append(result)
398
+
399
+ return loaded
400
+
401
+
402
+ # ---------------------------------------------------------------------------
403
+ # Public API
404
+ # ---------------------------------------------------------------------------
405
+
406
+
407
+ def load_plugin_callbacks() -> dict[str, list[str]]:
408
+ """Dynamically load register_callbacks.py from all plugin sources.
409
+
410
+ Loads plugins from:
411
+ 1. Built-in plugins in the code_muse/plugins/ directory
412
+ 2. User plugins in ~/.muse/plugins/
413
+
414
+ User plugins require trust (content-hash match in manifest).
415
+ No sys.path manipulation is performed.
416
+
417
+ Returns dict with 'builtin' and 'user' keys containing lists of loaded
418
+ plugin names.
419
+
420
+ NOTE: This function is idempotent - calling it multiple times will only
421
+ load plugins once. Subsequent calls return empty lists.
422
+ """
423
+ global _PLUGINS_LOADED
424
+
425
+ # Prevent duplicate loading - plugins register callbacks at import time,
426
+ # so re-importing would cause duplicate registrations
427
+ if _PLUGINS_LOADED:
428
+ logger.debug("Plugins already loaded, skipping duplicate load")
429
+ return {"builtin": [], "user": []}
430
+
431
+ plugins_dir = Path(__file__).parent
432
+ builtin_failed: list[str] = []
433
+ user_failed: list[str] = []
434
+
435
+ result = {
436
+ "builtin": _load_builtin_plugins(plugins_dir, builtin_failed),
437
+ "user": _load_user_plugins(USER_PLUGINS_DIR, user_failed),
438
+ }
439
+
440
+ total_loaded = len(result["builtin"]) + len(result["user"])
441
+ total_failed = len(builtin_failed) + len(user_failed)
442
+ if total_failed:
443
+ all_failed = builtin_failed + user_failed
444
+ names_str = ", ".join(all_failed)
445
+ logger.warning("Plugin load failures: %s", names_str)
446
+ from code_muse.messaging import emit_warning
447
+
448
+ emit_warning(
449
+ f"⚠️ {total_failed}/{total_loaded + total_failed} plugins failed to load"
450
+ )
451
+
452
+ _PLUGINS_LOADED = True
453
+ logger.debug(
454
+ "Loaded plugins: builtin=%s, user=%s", result["builtin"], result["user"]
455
+ )
456
+
457
+ return result
458
+
459
+
460
+ def get_user_plugins_dir() -> Path:
461
+ """Return the path to the user plugins directory."""
462
+ return USER_PLUGINS_DIR
463
+
464
+
465
+ def ensure_user_plugins_dir() -> Path:
466
+ """Create the user plugins directory if it doesn't exist.
467
+
468
+ Returns the path to the directory.
469
+ """
470
+ ensure_private_dir(USER_PLUGINS_DIR)
471
+ return USER_PLUGINS_DIR
@@ -0,0 +1,32 @@
1
+ """Agent Skills plugin - dynamic skill loading and discovery.
2
+
3
+ This plugin enables code_muse to discover, load, and use custom skills
4
+ defined in SKILL.md files. Skills can be placed in user-specific or
5
+ project-specific directories for easy sharing and organization.
6
+ """
7
+
8
+ from .discovery import (
9
+ MAX_SKILL_MD_BYTES,
10
+ SKILL_CONTEXT_CAP,
11
+ SkillInfo,
12
+ cap_skill_content,
13
+ )
14
+ from .metadata import (
15
+ SkillMetadata,
16
+ get_skill_resources,
17
+ load_full_skill_content,
18
+ parse_skill_metadata,
19
+ parse_yaml_frontmatter,
20
+ )
21
+
22
+ __all__ = [
23
+ "SkillInfo",
24
+ "SkillMetadata",
25
+ "MAX_SKILL_MD_BYTES",
26
+ "SKILL_CONTEXT_CAP",
27
+ "cap_skill_content",
28
+ "parse_yaml_frontmatter",
29
+ "parse_skill_metadata",
30
+ "load_full_skill_content",
31
+ "get_skill_resources",
32
+ ]