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,791 @@
1
+ """Interactive TUI for managing agent skills.
2
+
3
+ Launch with /skills to browse, enable, disable, and configure skills.
4
+ Built with prompt_toolkit for proper interactive split-panel interface.
5
+ """
6
+
7
+ import sys
8
+ import time
9
+ from pathlib import Path
10
+
11
+ from prompt_toolkit.application import Application
12
+ from prompt_toolkit.key_binding import KeyBindings
13
+ from prompt_toolkit.layout import Dimension, Layout, VSplit, Window
14
+ from prompt_toolkit.layout.controls import FormattedTextControl
15
+ from prompt_toolkit.widgets import Frame
16
+
17
+ from code_muse.command_line.pagination import (
18
+ ensure_visible_page,
19
+ get_page_bounds,
20
+ get_total_pages,
21
+ )
22
+ from code_muse.command_line.utils import safe_input
23
+ from code_muse.messaging import emit_error, emit_info, emit_success, emit_warning
24
+ from code_muse.plugins.agent_skills.config import (
25
+ add_skill_directory,
26
+ get_disabled_skills,
27
+ get_skill_directories,
28
+ get_skills_enabled,
29
+ remove_skill_directory,
30
+ set_skill_disabled,
31
+ set_skills_enabled,
32
+ )
33
+ from code_muse.plugins.agent_skills.discovery import (
34
+ SkillInfo,
35
+ discover_skills,
36
+ refresh_skill_cache,
37
+ )
38
+ from code_muse.plugins.agent_skills.metadata import (
39
+ SkillMetadata,
40
+ get_skill_resources,
41
+ parse_skill_metadata,
42
+ )
43
+ from code_muse.tools.command_runner import set_awaiting_user_input
44
+
45
+ PAGE_SIZE = 15 # Items per page
46
+
47
+
48
+ class SkillsMenu:
49
+ """Interactive TUI for managing agent skills."""
50
+
51
+ def __init__(self):
52
+ """Initialize the skills menu."""
53
+ self.skills: list[SkillInfo] = []
54
+ self.disabled_skills: list[str] = []
55
+ self.skill_directories: list[Path] = []
56
+ self.skills_enabled = False
57
+
58
+ # State management
59
+ self.selected_idx = 0
60
+ self.current_page = 0
61
+ self.result = None
62
+
63
+ # UI controls (set during run)
64
+ self.menu_control: FormattedTextControl | None = None
65
+ self.preview_control: FormattedTextControl | None = None
66
+
67
+ # Initialize data
68
+ self._refresh_data()
69
+
70
+ def _refresh_data(self) -> None:
71
+ """Refresh skills data from disk."""
72
+ try:
73
+ self.skills = discover_skills()
74
+ self.disabled_skills = get_disabled_skills()
75
+ self.skill_directories = get_skill_directories()
76
+ self.skills_enabled = get_skills_enabled()
77
+ except Exception as e:
78
+ emit_error(f"Failed to refresh skills data: {e}")
79
+
80
+ def _get_current_skill(self) -> SkillInfo | None:
81
+ """Get the currently selected skill."""
82
+ if 0 <= self.selected_idx < len(self.skills):
83
+ return self.skills[self.selected_idx]
84
+ return None
85
+
86
+ def _get_skill_metadata(self, skill: SkillInfo) -> SkillMetadata | None:
87
+ """Get metadata for a skill."""
88
+ try:
89
+ return parse_skill_metadata(skill.path)
90
+ except Exception:
91
+ return None
92
+
93
+ def _is_skill_disabled(self, skill: SkillInfo) -> bool:
94
+ """Check if a skill is disabled."""
95
+ metadata = self._get_skill_metadata(skill)
96
+ if metadata:
97
+ return metadata.name in self.disabled_skills
98
+ return skill.name in self.disabled_skills
99
+
100
+ def _toggle_current_skill(self) -> None:
101
+ """Toggle the enabled/disabled state of the current skill."""
102
+ skill = self._get_current_skill()
103
+ if not skill:
104
+ return
105
+
106
+ metadata = self._get_skill_metadata(skill)
107
+ skill_name = metadata.name if metadata else skill.name
108
+
109
+ is_disabled = skill_name in self.disabled_skills
110
+ set_skill_disabled(skill_name, not is_disabled)
111
+ refresh_skill_cache()
112
+ self._refresh_data()
113
+ self.update_display()
114
+
115
+ def _render_skill_list(self) -> list:
116
+ """Render the skill list panel."""
117
+ lines = []
118
+
119
+ # Header with status
120
+ status_color = "fg:ansigreen" if self.skills_enabled else "fg:ansired"
121
+ status_text = "ENABLED" if self.skills_enabled else "DISABLED"
122
+ lines.append((status_color, f" Skills: {status_text}"))
123
+ lines.append(("", "\n\n"))
124
+
125
+ if not self.skills:
126
+ lines.append(("fg:ansiyellow", " No skills found."))
127
+ lines.append(("", "\n"))
128
+ lines.append(("fg:ansibrightblack", " Create skills in:"))
129
+ lines.append(("", "\n"))
130
+ lines.append(("fg:ansibrightblack", " ~/.muse/skills/"))
131
+ lines.append(("", "\n"))
132
+ lines.append(("fg:ansibrightblack", " ./skills/"))
133
+ lines.append(("", "\n\n"))
134
+ self._render_navigation_hints(lines)
135
+ return lines
136
+
137
+ # Calculate pagination
138
+ total_pages = get_total_pages(len(self.skills), PAGE_SIZE)
139
+ start_idx, end_idx = get_page_bounds(
140
+ self.current_page, len(self.skills), PAGE_SIZE
141
+ )
142
+
143
+ # Render skills
144
+ for i in range(start_idx, end_idx):
145
+ skill = self.skills[i]
146
+ is_selected = i == self.selected_idx
147
+ is_disabled = self._is_skill_disabled(skill)
148
+
149
+ # Status icon
150
+ status_icon = "✗" if is_disabled else "✓"
151
+ status_style = "fg:ansired" if is_disabled else "fg:ansigreen"
152
+
153
+ # Get skill name from metadata if available
154
+ metadata = self._get_skill_metadata(skill)
155
+ display_name = metadata.name if metadata else skill.name
156
+
157
+ # Format line
158
+ prefix = " > " if is_selected else " "
159
+
160
+ if is_selected:
161
+ lines.append(("bold", prefix))
162
+ lines.append((status_style + " bold", status_icon))
163
+ lines.append(("bold", f" {display_name}"))
164
+ else:
165
+ lines.append(("", prefix))
166
+ lines.append((status_style, status_icon))
167
+ lines.append(("fg:ansibrightblack", f" {display_name}"))
168
+
169
+ lines.append(("", "\n"))
170
+
171
+ # Pagination info
172
+ lines.append(("", "\n"))
173
+ lines.append(
174
+ ("fg:ansibrightblack", f" Page {self.current_page + 1}/{total_pages}")
175
+ )
176
+ lines.append(("", "\n"))
177
+
178
+ self._render_navigation_hints(lines)
179
+ return lines
180
+
181
+ def _render_navigation_hints(self, lines: list) -> None:
182
+ """Render navigation hints at the bottom."""
183
+ lines.append(("", "\n"))
184
+ lines.append(("fg:ansibrightblack", " ↑/↓ or j/k "))
185
+ lines.append(("", "Navigate "))
186
+ lines.append(("fg:ansibrightblack", "←/→ "))
187
+ lines.append(("", "Page\n"))
188
+ lines.append(("fg:ansigreen", " Enter "))
189
+ lines.append(("", "Toggle "))
190
+ lines.append(("fg:ansicyan", " t "))
191
+ lines.append(("", "Toggle System\n"))
192
+ lines.append(("fg:ansimagenta", " Ctrl+A "))
193
+ lines.append(("", "Add Dir "))
194
+ lines.append(("fg:ansiyellow", " Ctrl+D "))
195
+ lines.append(("", "Show Dirs\n"))
196
+ lines.append(("fg:ansimagenta", " i "))
197
+ lines.append(("", "Install from catalog\n"))
198
+ lines.append(("fg:ansiyellow", " r "))
199
+ lines.append(("", "Refresh "))
200
+ lines.append(("fg:ansired", " q "))
201
+ lines.append(("", "Exit"))
202
+
203
+ def _render_skill_details(self) -> list:
204
+ """Render the skill details panel."""
205
+ lines = []
206
+
207
+ lines.append(("dim cyan", " SKILL DETAILS"))
208
+ lines.append(("", "\n\n"))
209
+
210
+ skill = self._get_current_skill()
211
+ if not skill:
212
+ lines.append(("fg:ansiyellow", " No skill selected."))
213
+ lines.append(("", "\n\n"))
214
+ lines.append(("fg:ansibrightblack", " Select a skill from the list"))
215
+ lines.append(("", "\n"))
216
+ lines.append(("fg:ansibrightblack", " to view its details."))
217
+ return lines
218
+
219
+ metadata = self._get_skill_metadata(skill)
220
+ is_disabled = self._is_skill_disabled(skill)
221
+
222
+ # Status
223
+ status_text = "Disabled" if is_disabled else "Enabled"
224
+ status_style = "fg:ansired bold" if is_disabled else "fg:ansigreen bold"
225
+ lines.append(("bold", " Status: "))
226
+ lines.append((status_style, status_text))
227
+ lines.append(("", "\n\n"))
228
+
229
+ if metadata:
230
+ # Name
231
+ lines.append(("bold", f" {metadata.name}"))
232
+ lines.append(("", "\n\n"))
233
+
234
+ # Description
235
+ if metadata.description:
236
+ lines.append(("bold", " Description:"))
237
+ lines.append(("", "\n"))
238
+ # Wrap description
239
+ desc = metadata.description
240
+ wrapped = self._wrap_text(desc, 50)
241
+ for line in wrapped:
242
+ lines.append(("fg:ansibrightblack", f" {line}"))
243
+ lines.append(("", "\n"))
244
+ lines.append(("", "\n"))
245
+
246
+ # Tags
247
+ if metadata.tags:
248
+ lines.append(("bold", " Tags:"))
249
+ lines.append(("", "\n"))
250
+ tags_str = ", ".join(metadata.tags)
251
+ lines.append(("fg:ansicyan", f" {tags_str}"))
252
+ lines.append(("", "\n\n"))
253
+
254
+ # Resources
255
+ resources = get_skill_resources(metadata.path)
256
+ if resources:
257
+ lines.append(("bold", " Resources:"))
258
+ lines.append(("", "\n"))
259
+ for resource in resources[:5]: # Show first 5
260
+ resource_name = getattr(resource, "name", str(resource))
261
+ lines.append(("fg:ansiyellow", f" • {resource_name}"))
262
+ lines.append(("", "\n"))
263
+ if len(resources) > 5:
264
+ lines.append(
265
+ ("fg:ansibrightblack", f" ... and {len(resources) - 5} more")
266
+ )
267
+ lines.append(("", "\n"))
268
+ lines.append(("", "\n"))
269
+
270
+ else:
271
+ # No metadata available
272
+ lines.append(("bold", f" {skill.name}"))
273
+ lines.append(("", "\n\n"))
274
+ lines.append(("fg:ansiyellow", " No metadata available"))
275
+ lines.append(("", "\n"))
276
+ lines.append(("fg:ansibrightblack", " Add a SKILL.md with frontmatter to"))
277
+ lines.append(("", "\n"))
278
+ lines.append(
279
+ ("fg:ansibrightblack", " define name, description, and tags.")
280
+ )
281
+ lines.append(("", "\n\n"))
282
+
283
+ # Path
284
+ lines.append(("bold", " Path:"))
285
+ lines.append(("", "\n"))
286
+ path_str = str(skill.path)
287
+ if len(path_str) > 45:
288
+ path_str = "..." + path_str[-42:]
289
+ lines.append(("fg:ansibrightblack", f" {path_str}"))
290
+ lines.append(("", "\n"))
291
+
292
+ return lines
293
+
294
+ def _wrap_text(self, text: str, width: int) -> list[str]:
295
+ """Wrap text to specified width."""
296
+ words = text.split()
297
+ lines = []
298
+ current_line = []
299
+ current_length = 0
300
+
301
+ for word in words:
302
+ if current_length + len(word) + 1 <= width:
303
+ current_line.append(word)
304
+ current_length += len(word) + 1
305
+ else:
306
+ if current_line:
307
+ lines.append(" ".join(current_line))
308
+ current_line = [word]
309
+ current_length = len(word)
310
+
311
+ if current_line:
312
+ lines.append(" ".join(current_line))
313
+
314
+ return lines or [""]
315
+
316
+ def update_display(self) -> None:
317
+ """Update the display based on current state."""
318
+ if self.menu_control:
319
+ self.menu_control.text = self._render_skill_list()
320
+ if self.preview_control:
321
+ self.preview_control.text = self._render_skill_details()
322
+
323
+ def run(self) -> bool:
324
+ """Run the interactive skills browser.
325
+
326
+ Returns:
327
+ True if changes were made, False otherwise.
328
+ """
329
+ # Reset per-run state
330
+ self.result = None
331
+
332
+ # Build UI
333
+ self.menu_control = FormattedTextControl(text="")
334
+ self.preview_control = FormattedTextControl(text="")
335
+
336
+ menu_window = Window(
337
+ content=self.menu_control, wrap_lines=True, width=Dimension(weight=35)
338
+ )
339
+ preview_window = Window(
340
+ content=self.preview_control, wrap_lines=True, width=Dimension(weight=65)
341
+ )
342
+
343
+ menu_frame = Frame(menu_window, width=Dimension(weight=35), title="Skills")
344
+ preview_frame = Frame(
345
+ preview_window, width=Dimension(weight=65), title="Details"
346
+ )
347
+
348
+ root_container = VSplit([menu_frame, preview_frame])
349
+
350
+ # Key bindings
351
+ kb = KeyBindings()
352
+
353
+ @kb.add("up")
354
+ @kb.add("c-p") # Ctrl+P
355
+ @kb.add("k")
356
+ def _(event):
357
+ if self.selected_idx > 0:
358
+ self.selected_idx -= 1
359
+ self.current_page = ensure_visible_page(
360
+ self.selected_idx,
361
+ self.current_page,
362
+ len(self.skills),
363
+ PAGE_SIZE,
364
+ )
365
+ self.update_display()
366
+
367
+ @kb.add("down")
368
+ @kb.add("c-n") # Ctrl+N
369
+ @kb.add("j")
370
+ def _(event):
371
+ if self.selected_idx < len(self.skills) - 1:
372
+ self.selected_idx += 1
373
+ self.current_page = ensure_visible_page(
374
+ self.selected_idx,
375
+ self.current_page,
376
+ len(self.skills),
377
+ PAGE_SIZE,
378
+ )
379
+ self.update_display()
380
+
381
+ @kb.add("left")
382
+ def _(event):
383
+ """Previous page."""
384
+ if self.current_page > 0:
385
+ self.current_page -= 1
386
+ self.selected_idx = self.current_page * PAGE_SIZE
387
+ self.update_display()
388
+
389
+ @kb.add("right")
390
+ def _(event):
391
+ """Next page."""
392
+ total_pages = get_total_pages(len(self.skills), PAGE_SIZE)
393
+ if self.current_page < total_pages - 1:
394
+ self.current_page += 1
395
+ self.selected_idx = self.current_page * PAGE_SIZE
396
+ self.update_display()
397
+
398
+ @kb.add("enter")
399
+ def _(event):
400
+ """Toggle skill enabled/disabled."""
401
+ self._toggle_current_skill()
402
+ self.result = "changed"
403
+
404
+ @kb.add("t")
405
+ def _(event):
406
+ """Toggle skills system on/off."""
407
+ new_state = not self.skills_enabled
408
+ set_skills_enabled(new_state)
409
+ self.skills_enabled = new_state
410
+ self.result = "changed"
411
+ self.update_display()
412
+
413
+ @kb.add("r")
414
+ def _(event):
415
+ """Refresh skills."""
416
+ refresh_skill_cache()
417
+ self._refresh_data()
418
+ self.update_display()
419
+
420
+ @kb.add("c-a")
421
+ def _(event):
422
+ """Add a skill directory."""
423
+ self.result = "add_directory"
424
+ event.app.exit()
425
+
426
+ @kb.add("c-d")
427
+ def _(event):
428
+ """Show/manage directories."""
429
+ self.result = "show_directories"
430
+ event.app.exit()
431
+
432
+ @kb.add("i")
433
+ def _(event):
434
+ """Install skills from catalog."""
435
+ self.result = "install"
436
+ event.app.exit()
437
+
438
+ @kb.add("q")
439
+ @kb.add("escape")
440
+ def _(event):
441
+ self.result = "quit"
442
+ event.app.exit()
443
+
444
+ @kb.add("c-c")
445
+ def _(event):
446
+ self.result = "quit"
447
+ event.app.exit()
448
+
449
+ layout = Layout(root_container)
450
+ app = Application(
451
+ layout=layout,
452
+ key_bindings=kb,
453
+ full_screen=False,
454
+ mouse_support=False,
455
+ )
456
+
457
+ set_awaiting_user_input(True)
458
+
459
+ # Enter alternate screen buffer
460
+ sys.stdout.write("\033[?1049h") # Enter alternate buffer
461
+ sys.stdout.write("\033[2J\033[H") # Clear and home
462
+ sys.stdout.flush()
463
+ time.sleep(0.05)
464
+
465
+ try:
466
+ # Initial display
467
+ self.update_display()
468
+
469
+ # Clear the buffer
470
+ sys.stdout.write("\033[2J\033[H")
471
+ sys.stdout.flush()
472
+
473
+ # Run application in a background thread to avoid event loop conflicts
474
+ app.run(in_thread=True)
475
+
476
+ finally:
477
+ # Exit alternate screen buffer
478
+ sys.stdout.write("\033[?1049l")
479
+ sys.stdout.flush()
480
+
481
+ # Flush any buffered input to prevent stale keypresses
482
+ try:
483
+ import termios
484
+
485
+ termios.tcflush(sys.stdin.fileno(), termios.TCIFLUSH)
486
+ except Exception:
487
+ pass # ImportError on Windows, termios.error, or not a tty
488
+
489
+ # Small delay to let terminal settle before any output
490
+ time.sleep(0.1)
491
+ set_awaiting_user_input(False)
492
+
493
+ return self.result
494
+
495
+
496
+ def _prompt_for_directory() -> Path | None:
497
+ """Prompt user for a directory path to add."""
498
+ try:
499
+ print("\n" + "=" * 60)
500
+ print("ADD SKILL DIRECTORY")
501
+ print("=" * 60)
502
+ print("\nEnter the path to a directory containing skills.")
503
+ print("Examples:")
504
+ print(" ~/.claude/skills")
505
+ print(" /opt/shared-skills")
506
+ print(" ./my-project-skills")
507
+ print("\nPress Ctrl+C to cancel.\n")
508
+
509
+ path = safe_input("Directory path: ").strip()
510
+ if path:
511
+ # Expand ~ to home directory
512
+ return Path(path).expanduser()
513
+ except KeyboardInterrupt, EOFError:
514
+ print("\nCancelled.")
515
+ return None
516
+
517
+
518
+ def _show_directories_menu() -> str | None:
519
+ """Show current directories and allow removal."""
520
+ try:
521
+ dirs = get_skill_directories()
522
+
523
+ print("\n" + "=" * 60)
524
+ print("SKILL DIRECTORIES")
525
+ print("=" * 60)
526
+ print("\nCurrently configured directories:\n")
527
+
528
+ if not dirs:
529
+ print(" (no directories configured)")
530
+ else:
531
+ for i, d in enumerate(dirs, 1):
532
+ exists = Path(d).expanduser().is_dir()
533
+ status = "✓" if exists else "✗ (not found)"
534
+ print(f" {i}. {d} {status}")
535
+
536
+ print("\nOptions:")
537
+ print(" Enter a number to remove that directory")
538
+ print(" Press Enter or Ctrl+C to go back\n")
539
+
540
+ choice = safe_input("Choice: ").strip()
541
+ if choice and choice.isdigit():
542
+ idx = int(choice) - 1
543
+ if 0 <= idx < len(dirs):
544
+ dir_to_remove = dirs[idx]
545
+ confirm = (
546
+ safe_input(f"Remove '{dir_to_remove}'? (y/N): ").strip().lower()
547
+ )
548
+ if confirm in ("y", "yes"):
549
+ remove_skill_directory(dir_to_remove)
550
+ print(f"Removed: {dir_to_remove}")
551
+ return "changed"
552
+ except KeyboardInterrupt, EOFError:
553
+ print("\nCancelled.")
554
+ return None
555
+
556
+
557
+ def show_skills_menu() -> bool:
558
+ """Launch the interactive skills TUI menu.
559
+
560
+ Returns:
561
+ True if changes were made, False otherwise.
562
+ """
563
+
564
+ changes_made = False
565
+
566
+ while True:
567
+ menu = SkillsMenu()
568
+ result = menu.run()
569
+
570
+ if result == "add_directory":
571
+ # Prompt for directory to add
572
+ new_dir = _prompt_for_directory()
573
+ if new_dir:
574
+ if add_skill_directory(new_dir):
575
+ emit_success(f"Added skill directory: {new_dir}")
576
+ changes_made = True
577
+ else:
578
+ emit_warning(f"Directory already configured: {new_dir}")
579
+ # Re-run the menu
580
+ continue
581
+
582
+ elif result == "show_directories":
583
+ # Show directories management
584
+ dir_result = _show_directories_menu()
585
+ if dir_result == "changed":
586
+ changes_made = True
587
+ # Re-run the menu
588
+ continue
589
+
590
+ elif result == "install":
591
+ from code_muse.plugins.agent_skills.skills_install_menu import (
592
+ run_skills_install_menu,
593
+ )
594
+
595
+ install_result = run_skills_install_menu()
596
+ if install_result:
597
+ changes_made = True
598
+ continue # Re-run the skills menu after install
599
+
600
+ elif result == "changed":
601
+ changes_made = True
602
+ break
603
+ elif result == "quit":
604
+ break
605
+ else:
606
+ # User quit or no-op
607
+ break
608
+
609
+ return changes_made
610
+
611
+
612
+ def list_skills() -> bool:
613
+ """list all discovered skills in a simple format.
614
+
615
+ Returns:
616
+ True if successful, False otherwise.
617
+ """
618
+ try:
619
+ skills = discover_skills()
620
+ disabled_skills = get_disabled_skills()
621
+
622
+ if not skills:
623
+ emit_info(
624
+ "No skills found. Create skills in ~/.muse/skills/ or ./skills/"
625
+ )
626
+ return True
627
+
628
+ emit_info(f"\nFound {len(skills)} skill(s):\n")
629
+
630
+ for skill in skills:
631
+ metadata = parse_skill_metadata(skill.path)
632
+ if metadata:
633
+ is_disabled = metadata.name in disabled_skills
634
+ status = "enabled" if not is_disabled else "disabled"
635
+ emit_info(f" [{status}] {metadata.name}")
636
+ if metadata.description:
637
+ emit_info(f" {metadata.description}")
638
+ resources = get_skill_resources(metadata.path)
639
+ if resources:
640
+ emit_info(f" Resources: {len(resources)}")
641
+ else:
642
+ is_disabled = skill.name in disabled_skills
643
+ status = "enabled" if not is_disabled else "disabled"
644
+ emit_info(f" [{status}] {skill.name} (no metadata)")
645
+
646
+ return True
647
+ except Exception as e:
648
+ emit_error(f"Failed to list skills: {e}")
649
+ return False
650
+
651
+
652
+ def handle_skills_command(args: list[str]) -> bool:
653
+ """Handle skills subcommands from the CLI.
654
+
655
+ Args:
656
+ args: list of command arguments (e.g., ['enable', 'my-skill'])
657
+
658
+ Returns:
659
+ True if successful, False otherwise.
660
+ """
661
+ if not args:
662
+ # Show interactive TUI
663
+ return show_skills_menu()
664
+
665
+ command = args[0].lower()
666
+
667
+ if command == "list":
668
+ return list_skills()
669
+ elif command == "enable":
670
+ if len(args) < 2:
671
+ emit_error("Usage: /skills enable <skill-name>")
672
+ return False
673
+ return _enable_skill(args[1])
674
+ elif command == "disable":
675
+ if len(args) < 2:
676
+ emit_error("Usage: /skills disable <skill-name>")
677
+ return False
678
+ return _disable_skill(args[1])
679
+ elif command == "toggle":
680
+ return _toggle_skills_integration()
681
+ elif command == "refresh":
682
+ return _refresh_skills()
683
+ elif command == "help":
684
+ _show_help()
685
+ return True
686
+ else:
687
+ emit_error(f"Unknown command: {command}")
688
+ emit_info("Use '/skills help' to see available commands.")
689
+ return False
690
+
691
+
692
+ def _enable_skill(skill_name: str) -> bool:
693
+ """Enable a specific skill."""
694
+ try:
695
+ skills = discover_skills()
696
+ skill_names = [s.name for s in skills]
697
+
698
+ # Also check metadata names
699
+ for skill in skills:
700
+ metadata = parse_skill_metadata(skill.path)
701
+ if metadata:
702
+ skill_names.append(metadata.name)
703
+
704
+ if skill_name not in skill_names:
705
+ emit_error(f"Skill '{skill_name}' not found.")
706
+ return False
707
+
708
+ disabled = get_disabled_skills()
709
+ if skill_name not in disabled:
710
+ emit_info(f"Skill '{skill_name}' is already enabled.")
711
+ return True
712
+
713
+ set_skill_disabled(skill_name, False)
714
+ refresh_skill_cache()
715
+ emit_success(f"Skill '{skill_name}' has been enabled.")
716
+ return True
717
+ except Exception as e:
718
+ emit_error(f"Failed to enable skill '{skill_name}': {e}")
719
+ return False
720
+
721
+
722
+ def _disable_skill(skill_name: str) -> bool:
723
+ """Disable a specific skill."""
724
+ try:
725
+ skills = discover_skills()
726
+ skill_names = [s.name for s in skills]
727
+
728
+ # Also check metadata names
729
+ for skill in skills:
730
+ metadata = parse_skill_metadata(skill.path)
731
+ if metadata:
732
+ skill_names.append(metadata.name)
733
+
734
+ if skill_name not in skill_names:
735
+ emit_error(f"Skill '{skill_name}' not found.")
736
+ return False
737
+
738
+ disabled = get_disabled_skills()
739
+ if skill_name in disabled:
740
+ emit_info(f"Skill '{skill_name}' is already disabled.")
741
+ return True
742
+
743
+ set_skill_disabled(skill_name, True)
744
+ refresh_skill_cache()
745
+ emit_success(f"Skill '{skill_name}' has been disabled.")
746
+ return True
747
+ except Exception as e:
748
+ emit_error(f"Failed to disable skill '{skill_name}': {e}")
749
+ return False
750
+
751
+
752
+ def _toggle_skills_integration() -> bool:
753
+ """Toggle skills integration on/off."""
754
+ try:
755
+ current = get_skills_enabled()
756
+ new_state = not current
757
+ set_skills_enabled(new_state)
758
+
759
+ if new_state:
760
+ emit_success("Skills integration has been enabled.")
761
+ else:
762
+ emit_warning("Skills integration has been disabled.")
763
+
764
+ return True
765
+ except Exception as e:
766
+ emit_error(f"Failed to toggle skills integration: {e}")
767
+ return False
768
+
769
+
770
+ def _refresh_skills() -> bool:
771
+ """Refresh the skill cache."""
772
+ try:
773
+ emit_info("Refreshing skill cache...")
774
+ refresh_skill_cache()
775
+ emit_success("Skill cache refreshed successfully.")
776
+ return True
777
+ except Exception as e:
778
+ emit_error(f"Failed to refresh skill cache: {e}")
779
+ return False
780
+
781
+
782
+ def _show_help() -> None:
783
+ """Show help information."""
784
+ emit_info("Available commands:")
785
+ emit_info(" /skills - Show interactive TUI")
786
+ emit_info(" /skills list - list all skills")
787
+ emit_info(" /skills enable <name> - Enable a skill")
788
+ emit_info(" /skills disable <name> - Disable a skill")
789
+ emit_info(" /skills toggle - Toggle skills integration")
790
+ emit_info(" /skills refresh - Refresh skill cache")
791
+ emit_info(" /skills help - Show this help")