code-muse 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (394) hide show
  1. code_muse/__init__.py +26 -0
  2. code_muse/__main__.py +10 -0
  3. code_muse/agents/__init__.py +31 -0
  4. code_muse/agents/_builder.py +214 -0
  5. code_muse/agents/_compaction.py +506 -0
  6. code_muse/agents/_diagnostics.py +171 -0
  7. code_muse/agents/_history.py +382 -0
  8. code_muse/agents/_key_listeners.py +148 -0
  9. code_muse/agents/_non_streaming_render.py +148 -0
  10. code_muse/agents/_runtime.py +596 -0
  11. code_muse/agents/agent_creator_agent.py +603 -0
  12. code_muse/agents/agent_helios.py +47 -0
  13. code_muse/agents/agent_manager.py +740 -0
  14. code_muse/agents/agent_muse.py +78 -0
  15. code_muse/agents/agent_planning.py +44 -0
  16. code_muse/agents/agent_qa_melpomene.py +207 -0
  17. code_muse/agents/base_agent.py +194 -0
  18. code_muse/agents/event_stream_handler.py +361 -0
  19. code_muse/agents/json_agent.py +201 -0
  20. code_muse/agents/prompt_v3.py +521 -0
  21. code_muse/agents/subagent_stream_handler.py +273 -0
  22. code_muse/callbacks.py +941 -0
  23. code_muse/chatgpt_codex_client.py +333 -0
  24. code_muse/claude_cache_client.py +853 -0
  25. code_muse/cli_runner/__init__.py +319 -0
  26. code_muse/cli_runner/args.py +63 -0
  27. code_muse/cli_runner/loop.py +510 -0
  28. code_muse/cli_runner/resume.py +72 -0
  29. code_muse/cli_runner/runner.py +161 -0
  30. code_muse/command_line/__init__.py +1 -0
  31. code_muse/command_line/add_model_menu.py +1331 -0
  32. code_muse/command_line/agent_menu.py +674 -0
  33. code_muse/command_line/attachments.py +397 -0
  34. code_muse/command_line/autosave_menu.py +709 -0
  35. code_muse/command_line/clipboard.py +528 -0
  36. code_muse/command_line/colors_menu.py +530 -0
  37. code_muse/command_line/command_handler.py +262 -0
  38. code_muse/command_line/command_registry.py +150 -0
  39. code_muse/command_line/config_commands.py +711 -0
  40. code_muse/command_line/core_commands.py +740 -0
  41. code_muse/command_line/diff_menu.py +865 -0
  42. code_muse/command_line/file_path_completion.py +73 -0
  43. code_muse/command_line/load_context_completion.py +57 -0
  44. code_muse/command_line/model_picker_completion.py +512 -0
  45. code_muse/command_line/model_settings_menu.py +983 -0
  46. code_muse/command_line/onboarding_slides.py +162 -0
  47. code_muse/command_line/onboarding_wizard.py +337 -0
  48. code_muse/command_line/pagination.py +41 -0
  49. code_muse/command_line/pin_command_completion.py +329 -0
  50. code_muse/command_line/prompt_toolkit_completion.py +886 -0
  51. code_muse/command_line/session_commands.py +304 -0
  52. code_muse/command_line/shell_passthrough.py +145 -0
  53. code_muse/command_line/skills_completion.py +158 -0
  54. code_muse/command_line/types.py +18 -0
  55. code_muse/command_line/uc_menu.py +908 -0
  56. code_muse/command_line/utils.py +105 -0
  57. code_muse/command_line/wiggum_state.py +77 -0
  58. code_muse/config.py +1138 -0
  59. code_muse/config_agent.py +168 -0
  60. code_muse/config_appearance.py +241 -0
  61. code_muse/config_model.py +357 -0
  62. code_muse/config_security.py +73 -0
  63. code_muse/error_logging.py +132 -0
  64. code_muse/evals/__init__.py +35 -0
  65. code_muse/evals/eval_helpers.py +81 -0
  66. code_muse/evals/eval_runner.py +299 -0
  67. code_muse/evals/sample_evals/__init__.py +1 -0
  68. code_muse/evals/sample_evals/eval_frugal_reads.py +59 -0
  69. code_muse/evals/sample_evals/eval_memory_planning.py +31 -0
  70. code_muse/evals/sample_evals/eval_shell_efficiency.py +39 -0
  71. code_muse/evals/sample_evals/eval_tool_masking.py +33 -0
  72. code_muse/fs_scan_cache/__init__.py +31 -0
  73. code_muse/fs_scan_cache/invalidation_hooks.py +89 -0
  74. code_muse/fs_scan_cache/scan_cache_core.cpython-314-darwin.so +0 -0
  75. code_muse/fs_scan_cache/scan_cache_core.pyx +203 -0
  76. code_muse/fs_scan_cache/tool_integration.py +309 -0
  77. code_muse/fs_scan_cache/ttl_policy.py +44 -0
  78. code_muse/gemini_code_assist.py +383 -0
  79. code_muse/gemini_model.py +838 -0
  80. code_muse/hook_engine/README.md +105 -0
  81. code_muse/hook_engine/__init__.py +21 -0
  82. code_muse/hook_engine/aliases.py +153 -0
  83. code_muse/hook_engine/engine.py +221 -0
  84. code_muse/hook_engine/executor.py +347 -0
  85. code_muse/hook_engine/matcher.py +154 -0
  86. code_muse/hook_engine/models.py +245 -0
  87. code_muse/hook_engine/registry.py +114 -0
  88. code_muse/hook_engine/trust.py +268 -0
  89. code_muse/hook_engine/validator.py +144 -0
  90. code_muse/http_utils.py +360 -0
  91. code_muse/keymap.py +128 -0
  92. code_muse/list_filtering.py +26 -0
  93. code_muse/main.py +10 -0
  94. code_muse/messaging/__init__.py +259 -0
  95. code_muse/messaging/bus.py +621 -0
  96. code_muse/messaging/commands.py +166 -0
  97. code_muse/messaging/markdown_patches.py +57 -0
  98. code_muse/messaging/message_queue.py +397 -0
  99. code_muse/messaging/messages.py +591 -0
  100. code_muse/messaging/queue_console.py +269 -0
  101. code_muse/messaging/renderers.py +308 -0
  102. code_muse/messaging/rich_renderer.py +1158 -0
  103. code_muse/messaging/shimmer.py +154 -0
  104. code_muse/messaging/spinner/__init__.py +87 -0
  105. code_muse/messaging/spinner/console_spinner.py +250 -0
  106. code_muse/messaging/spinner/spinner_base.py +82 -0
  107. code_muse/messaging/subagent_console.py +458 -0
  108. code_muse/model_factory.py +1203 -0
  109. code_muse/model_switching.py +59 -0
  110. code_muse/model_utils.py +156 -0
  111. code_muse/models.json +66 -0
  112. code_muse/models_cache/__init__.py +26 -0
  113. code_muse/models_cache/blocking_lru_cache.py +98 -0
  114. code_muse/models_cache/cache_writer.py +86 -0
  115. code_muse/models_cache/sha256_hash.cpython-314-darwin.so +0 -0
  116. code_muse/models_cache/sha256_hash.pyx +34 -0
  117. code_muse/models_cache/startup_integration.py +75 -0
  118. code_muse/models_dev_api.json +1 -0
  119. code_muse/models_dev_parser.py +590 -0
  120. code_muse/motion.py +126 -0
  121. code_muse/plugins/__init__.py +471 -0
  122. code_muse/plugins/agent_skills/__init__.py +32 -0
  123. code_muse/plugins/agent_skills/config.py +176 -0
  124. code_muse/plugins/agent_skills/discovery.py +309 -0
  125. code_muse/plugins/agent_skills/downloader.py +389 -0
  126. code_muse/plugins/agent_skills/installer.py +19 -0
  127. code_muse/plugins/agent_skills/metadata.py +293 -0
  128. code_muse/plugins/agent_skills/prompt_builder.py +66 -0
  129. code_muse/plugins/agent_skills/register_callbacks.py +298 -0
  130. code_muse/plugins/agent_skills/remote_catalog.py +320 -0
  131. code_muse/plugins/agent_skills/skill_catalog.py +254 -0
  132. code_muse/plugins/agent_skills/skills_install_menu.py +690 -0
  133. code_muse/plugins/agent_skills/skills_menu.py +791 -0
  134. code_muse/plugins/autonomous_memory/__init__.py +39 -0
  135. code_muse/plugins/autonomous_memory/bm25_scorer.cpython-314-darwin.so +0 -0
  136. code_muse/plugins/autonomous_memory/bm25_scorer.cpython-314-x86_64-linux-gnu.so +0 -0
  137. code_muse/plugins/autonomous_memory/bm25_scorer.pyx +291 -0
  138. code_muse/plugins/autonomous_memory/consolidation.py +82 -0
  139. code_muse/plugins/autonomous_memory/extraction.py +382 -0
  140. code_muse/plugins/autonomous_memory/lease_lock.py +105 -0
  141. code_muse/plugins/autonomous_memory/memory_injection.py +59 -0
  142. code_muse/plugins/autonomous_memory/register_callbacks.py +268 -0
  143. code_muse/plugins/autonomous_memory/secret_scanner.py +62 -0
  144. code_muse/plugins/autonomous_memory/session_scanner.py +163 -0
  145. code_muse/plugins/aws_bedrock/__init__.py +14 -0
  146. code_muse/plugins/aws_bedrock/config.py +99 -0
  147. code_muse/plugins/aws_bedrock/register_callbacks.py +241 -0
  148. code_muse/plugins/aws_bedrock/utils.py +153 -0
  149. code_muse/plugins/azure_foundry/README.md +238 -0
  150. code_muse/plugins/azure_foundry/__init__.py +15 -0
  151. code_muse/plugins/azure_foundry/config.py +125 -0
  152. code_muse/plugins/azure_foundry/discovery.py +187 -0
  153. code_muse/plugins/azure_foundry/register_callbacks.py +495 -0
  154. code_muse/plugins/azure_foundry/token.py +180 -0
  155. code_muse/plugins/azure_foundry/utils.py +345 -0
  156. code_muse/plugins/build_filter/__init__.py +1 -0
  157. code_muse/plugins/build_filter/register_callbacks.py +201 -0
  158. code_muse/plugins/build_filter/strategies/__init__.py +1 -0
  159. code_muse/plugins/build_filter/strategies/build.py +397 -0
  160. code_muse/plugins/chatgpt_oauth/__init__.py +6 -0
  161. code_muse/plugins/chatgpt_oauth/config.py +52 -0
  162. code_muse/plugins/chatgpt_oauth/oauth_flow.py +338 -0
  163. code_muse/plugins/chatgpt_oauth/register_callbacks.py +172 -0
  164. code_muse/plugins/chatgpt_oauth/test_plugin.py +301 -0
  165. code_muse/plugins/chatgpt_oauth/utils.py +538 -0
  166. code_muse/plugins/checkpointing/__init__.py +29 -0
  167. code_muse/plugins/checkpointing/checkpoint_hook.py +51 -0
  168. code_muse/plugins/checkpointing/conversation_snapshots.py +117 -0
  169. code_muse/plugins/checkpointing/register_callbacks.py +51 -0
  170. code_muse/plugins/checkpointing/restore_command.py +263 -0
  171. code_muse/plugins/checkpointing/rewind_shortcut.py +88 -0
  172. code_muse/plugins/checkpointing/shadow_git.py +90 -0
  173. code_muse/plugins/claude_code_hooks/__init__.py +1 -0
  174. code_muse/plugins/claude_code_hooks/config.py +188 -0
  175. code_muse/plugins/claude_code_hooks/register_callbacks.py +208 -0
  176. code_muse/plugins/claude_code_oauth/README.md +167 -0
  177. code_muse/plugins/claude_code_oauth/SETUP.md +93 -0
  178. code_muse/plugins/claude_code_oauth/__init__.py +25 -0
  179. code_muse/plugins/claude_code_oauth/config.py +52 -0
  180. code_muse/plugins/claude_code_oauth/fast_mode.py +124 -0
  181. code_muse/plugins/claude_code_oauth/prompt_handler.py +63 -0
  182. code_muse/plugins/claude_code_oauth/register_callbacks.py +547 -0
  183. code_muse/plugins/claude_code_oauth/test_fast_mode.py +165 -0
  184. code_muse/plugins/claude_code_oauth/test_plugin.py +283 -0
  185. code_muse/plugins/claude_code_oauth/token_refresh_heartbeat.py +237 -0
  186. code_muse/plugins/claude_code_oauth/utils.py +664 -0
  187. code_muse/plugins/copilot_auth/__init__.py +11 -0
  188. code_muse/plugins/copilot_auth/config.py +91 -0
  189. code_muse/plugins/copilot_auth/reasoning_client.py +409 -0
  190. code_muse/plugins/copilot_auth/register_callbacks.py +461 -0
  191. code_muse/plugins/copilot_auth/utils.py +584 -0
  192. code_muse/plugins/custom_commands/__init__.py +14 -0
  193. code_muse/plugins/custom_commands/args_injection.py +82 -0
  194. code_muse/plugins/custom_commands/command_discovery.py +89 -0
  195. code_muse/plugins/custom_commands/command_toml_schema.py +71 -0
  196. code_muse/plugins/custom_commands/register_callbacks.py +176 -0
  197. code_muse/plugins/customizable_commands/__init__.py +0 -0
  198. code_muse/plugins/customizable_commands/register_callbacks.py +136 -0
  199. code_muse/plugins/destructive_command_guard/__init__.py +14 -0
  200. code_muse/plugins/destructive_command_guard/detector.py +375 -0
  201. code_muse/plugins/destructive_command_guard/register_callbacks.py +148 -0
  202. code_muse/plugins/example_custom_command/README.md +280 -0
  203. code_muse/plugins/example_custom_command/register_callbacks.py +51 -0
  204. code_muse/plugins/file_permission_handler/__init__.py +4 -0
  205. code_muse/plugins/file_permission_handler/register_callbacks.py +441 -0
  206. code_muse/plugins/filter_engine/__init__.py +30 -0
  207. code_muse/plugins/filter_engine/classifier.py +153 -0
  208. code_muse/plugins/filter_engine/content_detector.py +184 -0
  209. code_muse/plugins/filter_engine/dispatcher.py +244 -0
  210. code_muse/plugins/filter_engine/register_callbacks.py +188 -0
  211. code_muse/plugins/filter_engine/registry.py +279 -0
  212. code_muse/plugins/filter_engine/strategies/__init__.py +8 -0
  213. code_muse/plugins/filter_engine/strategies/ast_compressor.cpython-314-darwin.so +0 -0
  214. code_muse/plugins/filter_engine/strategies/ast_compressor.cpython-314-x86_64-linux-gnu.so +0 -0
  215. code_muse/plugins/filter_engine/strategies/ast_compressor.pyx +348 -0
  216. code_muse/plugins/filter_engine/strategies/ast_parser.py +167 -0
  217. code_muse/plugins/filter_engine/strategies/code.cpython-314-darwin.so +0 -0
  218. code_muse/plugins/filter_engine/strategies/code.cpython-314-x86_64-linux-gnu.so +0 -0
  219. code_muse/plugins/filter_engine/strategies/code.pyx +584 -0
  220. code_muse/plugins/filter_engine/strategies/git.cpython-314-darwin.so +0 -0
  221. code_muse/plugins/filter_engine/strategies/git.cpython-314-x86_64-linux-gnu.so +0 -0
  222. code_muse/plugins/filter_engine/strategies/git.pyx +438 -0
  223. code_muse/plugins/filter_engine/strategies/json_compressor.cpython-314-darwin.so +0 -0
  224. code_muse/plugins/filter_engine/strategies/json_compressor.pyx +253 -0
  225. code_muse/plugins/filter_engine/strategies/json_patterns.cpython-314-darwin.so +0 -0
  226. code_muse/plugins/filter_engine/strategies/json_patterns.pyx +178 -0
  227. code_muse/plugins/filter_engine/strategies/lint.cpython-314-darwin.so +0 -0
  228. code_muse/plugins/filter_engine/strategies/lint.cpython-314-x86_64-linux-gnu.so +0 -0
  229. code_muse/plugins/filter_engine/strategies/lint.pyx +626 -0
  230. code_muse/plugins/filter_engine/strategies/test.cpython-314-darwin.so +0 -0
  231. code_muse/plugins/filter_engine/strategies/test.cpython-314-x86_64-linux-gnu.so +0 -0
  232. code_muse/plugins/filter_engine/strategies/test.pyx +431 -0
  233. code_muse/plugins/filter_engine/verbosity.py +63 -0
  234. code_muse/plugins/force_push_guard/__init__.py +5 -0
  235. code_muse/plugins/force_push_guard/detector.py +96 -0
  236. code_muse/plugins/force_push_guard/register_callbacks.py +144 -0
  237. code_muse/plugins/force_push_guard/test_detector.py +143 -0
  238. code_muse/plugins/frontend_emitter/__init__.py +25 -0
  239. code_muse/plugins/frontend_emitter/emitter.py +121 -0
  240. code_muse/plugins/frontend_emitter/register_callbacks.py +259 -0
  241. code_muse/plugins/gac/__init__.py +4 -0
  242. code_muse/plugins/gac/git_ops.py +136 -0
  243. code_muse/plugins/gac/prompt.py +191 -0
  244. code_muse/plugins/gac/register_callbacks.py +82 -0
  245. code_muse/plugins/hook_creator/__init__.py +1 -0
  246. code_muse/plugins/hook_creator/register_callbacks.py +34 -0
  247. code_muse/plugins/hook_manager/__init__.py +1 -0
  248. code_muse/plugins/hook_manager/config.py +289 -0
  249. code_muse/plugins/hook_manager/hooks_menu.py +563 -0
  250. code_muse/plugins/hook_manager/register_callbacks.py +227 -0
  251. code_muse/plugins/hook_monitor/register_callbacks.py +36 -0
  252. code_muse/plugins/mindpack/__init__.py +0 -0
  253. code_muse/plugins/mindpack/factory.py +930 -0
  254. code_muse/plugins/mindpack/judge.py +573 -0
  255. code_muse/plugins/mindpack/memory.py +100 -0
  256. code_muse/plugins/mindpack/mindpack_menu.py +1552 -0
  257. code_muse/plugins/mindpack/orchestration.py +605 -0
  258. code_muse/plugins/mindpack/register_callbacks.py +175 -0
  259. code_muse/plugins/mindpack/schemas.py +358 -0
  260. code_muse/plugins/mindpack/tools.py +387 -0
  261. code_muse/plugins/oauth_muse_html.py +226 -0
  262. code_muse/plugins/ollama_setup/__init__.py +5 -0
  263. code_muse/plugins/ollama_setup/completer.py +36 -0
  264. code_muse/plugins/ollama_setup/register_callbacks.py +410 -0
  265. code_muse/plugins/plan_command/__init__.py +0 -0
  266. code_muse/plugins/plan_command/register_callbacks.py +206 -0
  267. code_muse/plugins/plan_mode/__init__.py +37 -0
  268. code_muse/plugins/plan_mode/mode_cycling.py +40 -0
  269. code_muse/plugins/plan_mode/plan_generation.py +68 -0
  270. code_muse/plugins/plan_mode/plan_hooks.py +74 -0
  271. code_muse/plugins/plan_mode/plan_mode_tools.py +138 -0
  272. code_muse/plugins/plan_mode/register_callbacks.py +121 -0
  273. code_muse/plugins/plugin_trust/register_callbacks.py +140 -0
  274. code_muse/plugins/policy_engine/__init__.py +46 -0
  275. code_muse/plugins/policy_engine/approval_flow_integration.py +59 -0
  276. code_muse/plugins/policy_engine/policy_evaluator.py +75 -0
  277. code_muse/plugins/policy_engine/policy_file_discovery.py +90 -0
  278. code_muse/plugins/policy_engine/policy_toml_schema.py +115 -0
  279. code_muse/plugins/policy_engine/register_callbacks.py +112 -0
  280. code_muse/plugins/pop_command/__init__.py +1 -0
  281. code_muse/plugins/pop_command/register_callbacks.py +189 -0
  282. code_muse/plugins/prompt_newline/__init__.py +13 -0
  283. code_muse/plugins/prompt_newline/config.py +19 -0
  284. code_muse/plugins/prompt_newline/register_callbacks.py +159 -0
  285. code_muse/plugins/safety_status/__init__.py +0 -0
  286. code_muse/plugins/safety_status/register_callbacks.py +113 -0
  287. code_muse/plugins/semantic_compression/__init__.py +6 -0
  288. code_muse/plugins/semantic_compression/compressor.py +295 -0
  289. code_muse/plugins/semantic_compression/config.py +123 -0
  290. code_muse/plugins/semantic_compression/register_callbacks.py +320 -0
  291. code_muse/plugins/shell_minimizer/__init__.py +50 -0
  292. code_muse/plugins/shell_minimizer/builtin_filters.toml +393 -0
  293. code_muse/plugins/shell_minimizer/pipeline.py +556 -0
  294. code_muse/plugins/shell_minimizer/primitives.py +482 -0
  295. code_muse/plugins/shell_minimizer/register_callbacks.py +276 -0
  296. code_muse/plugins/shell_safety/__init__.py +6 -0
  297. code_muse/plugins/shell_safety/agent_shell_safety.py +69 -0
  298. code_muse/plugins/shell_safety/command_cache.py +149 -0
  299. code_muse/plugins/shell_safety/register_callbacks.py +202 -0
  300. code_muse/plugins/synthetic_status/__init__.py +1 -0
  301. code_muse/plugins/synthetic_status/register_callbacks.py +128 -0
  302. code_muse/plugins/synthetic_status/status_api.py +145 -0
  303. code_muse/plugins/token_caching/__init__.py +21 -0
  304. code_muse/plugins/token_caching/cache_hit_tracking.py +128 -0
  305. code_muse/plugins/token_caching/cacheable_prefix_detection.py +28 -0
  306. code_muse/plugins/token_caching/register_callbacks.py +54 -0
  307. code_muse/plugins/token_caching/stats_display.py +35 -0
  308. code_muse/plugins/token_tracking/__init__.py +26 -0
  309. code_muse/plugins/token_tracking/database.py +381 -0
  310. code_muse/plugins/token_tracking/edit_analyzer.py +97 -0
  311. code_muse/plugins/token_tracking/record.py +55 -0
  312. code_muse/plugins/token_tracking/register_callbacks.py +277 -0
  313. code_muse/plugins/token_tracking/reports.py +329 -0
  314. code_muse/plugins/universal_constructor/__init__.py +13 -0
  315. code_muse/plugins/universal_constructor/models.py +136 -0
  316. code_muse/plugins/universal_constructor/register_callbacks.py +47 -0
  317. code_muse/plugins/universal_constructor/registry.py +390 -0
  318. code_muse/plugins/universal_constructor/runner.py +474 -0
  319. code_muse/plugins/universal_constructor/safety.py +440 -0
  320. code_muse/plugins/universal_constructor/sandbox.py +584 -0
  321. code_muse/provider_identity.py +105 -0
  322. code_muse/pydantic_patches.py +410 -0
  323. code_muse/reopenable_async_client.py +233 -0
  324. code_muse/round_robin_model.py +151 -0
  325. code_muse/secret_storage.py +74 -0
  326. code_muse/security/__init__.py +1 -0
  327. code_muse/security/redaction.cpython-314-darwin.so +0 -0
  328. code_muse/security/redaction.cpython-314-x86_64-linux-gnu.so +0 -0
  329. code_muse/security/redaction.pyx +135 -0
  330. code_muse/session_storage.py +565 -0
  331. code_muse/status_display.py +261 -0
  332. code_muse/stream_parser/__init__.py +76 -0
  333. code_muse/stream_parser/assistant_text_parser.py +90 -0
  334. code_muse/stream_parser/citation_parser.py +76 -0
  335. code_muse/stream_parser/inline_hidden_tag_parser.py +236 -0
  336. code_muse/stream_parser/proposed_plan_parser.py +158 -0
  337. code_muse/stream_parser/stream_text_chunk.py +23 -0
  338. code_muse/stream_parser/stream_text_parser.py +27 -0
  339. code_muse/stream_parser/tagged_line_parser.cpython-314-darwin.so +0 -0
  340. code_muse/stream_parser/tagged_line_parser.pyx +251 -0
  341. code_muse/stream_parser/utf8_stream_parser.cpython-314-darwin.so +0 -0
  342. code_muse/stream_parser/utf8_stream_parser.pyx +206 -0
  343. code_muse/summarization_agent.py +308 -0
  344. code_muse/terminal_utils.cpython-314-darwin.so +0 -0
  345. code_muse/terminal_utils.cpython-314-x86_64-linux-gnu.so +0 -0
  346. code_muse/terminal_utils.pyx +483 -0
  347. code_muse/tools/__init__.py +459 -0
  348. code_muse/tools/agent_tools.py +613 -0
  349. code_muse/tools/ask_user_question/__init__.py +26 -0
  350. code_muse/tools/ask_user_question/constants.py +73 -0
  351. code_muse/tools/ask_user_question/demo_tui.py +55 -0
  352. code_muse/tools/ask_user_question/handler.py +232 -0
  353. code_muse/tools/ask_user_question/models.py +302 -0
  354. code_muse/tools/ask_user_question/registration.py +37 -0
  355. code_muse/tools/ask_user_question/renderers.py +336 -0
  356. code_muse/tools/ask_user_question/terminal_ui.py +327 -0
  357. code_muse/tools/ask_user_question/theme.py +156 -0
  358. code_muse/tools/ask_user_question/tui_loop.py +422 -0
  359. code_muse/tools/background_jobs.py +99 -0
  360. code_muse/tools/browser/__init__.py +37 -0
  361. code_muse/tools/browser/browser_control.py +289 -0
  362. code_muse/tools/browser/browser_interactions.py +545 -0
  363. code_muse/tools/browser/browser_locators.py +640 -0
  364. code_muse/tools/browser/browser_manager.py +376 -0
  365. code_muse/tools/browser/browser_navigation.py +251 -0
  366. code_muse/tools/browser/browser_screenshot.py +180 -0
  367. code_muse/tools/browser/browser_scripts.py +462 -0
  368. code_muse/tools/browser/browser_workflows.py +222 -0
  369. code_muse/tools/chrome_cdp/__init__.py +1070 -0
  370. code_muse/tools/chrome_cdp/register_callbacks.py +61 -0
  371. code_muse/tools/command_runner.py +1401 -0
  372. code_muse/tools/common.py +1407 -0
  373. code_muse/tools/display.py +87 -0
  374. code_muse/tools/file_modifications.py +1099 -0
  375. code_muse/tools/file_operations.py +860 -0
  376. code_muse/tools/image_tools.py +185 -0
  377. code_muse/tools/meetin_proxy/__init__.py +243 -0
  378. code_muse/tools/meetin_proxy/capture_addon.py +82 -0
  379. code_muse/tools/meetin_proxy/proxy_manager.py +326 -0
  380. code_muse/tools/meetin_proxy/register_callbacks.py +45 -0
  381. code_muse/tools/path_policy.py +219 -0
  382. code_muse/tools/skills_tools.py +586 -0
  383. code_muse/tools/subagent_context.py +158 -0
  384. code_muse/tools/tools_content.py +50 -0
  385. code_muse/tools/universal_constructor.py +965 -0
  386. code_muse/uvx_detection.py +241 -0
  387. code_muse/version_checker.py +86 -0
  388. code_muse-0.0.1.data/data/code_muse/models.json +66 -0
  389. code_muse-0.0.1.data/data/code_muse/models_dev_api.json +1 -0
  390. code_muse-0.0.1.dist-info/METADATA +845 -0
  391. code_muse-0.0.1.dist-info/RECORD +394 -0
  392. code_muse-0.0.1.dist-info/WHEEL +4 -0
  393. code_muse-0.0.1.dist-info/entry_points.txt +2 -0
  394. code_muse-0.0.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,144 @@
1
+ """Callback registration for the force push guard plugin.
2
+
3
+ Hooks into the run_shell_command phase to intercept git force push
4
+ commands and prompt the user for approval before allowing them through.
5
+ Returns {"blocked": True} to deny, None to allow.
6
+ """
7
+
8
+ import sys
9
+ from typing import Any
10
+
11
+ from rich.text import Text
12
+
13
+ from code_muse.callbacks import register_callback
14
+ from code_muse.messaging import emit_info, emit_warning
15
+ from code_muse.plugins.force_push_guard.detector import detect_force_push
16
+
17
+
18
+ def _is_interactive() -> bool:
19
+ """Check if we're in an interactive terminal that can show prompts."""
20
+ try:
21
+ return sys.stdin.isatty()
22
+ except AttributeError, OSError:
23
+ return False
24
+
25
+
26
+ async def force_push_guard_callback(
27
+ context: Any, command: str, cwd: str | None = None, timeout: int = 60
28
+ ) -> dict[str, Any | None]:
29
+ """Intercept shell commands containing git force push operations.
30
+
31
+ When a force push is detected:
32
+ - Interactive TTY: prompt the user with approve/reject options.
33
+ - Non-interactive (CI, sub-agent, piped): hard-block with an error.
34
+
35
+ This runs on *every* shell command, but the heavy lifting (regex
36
+ matching) is gated behind a cheap "push" substring check inside
37
+ detect_force_push().
38
+
39
+ Args:
40
+ context: Execution context (unused).
41
+ command: The shell command about to run.
42
+ cwd: Working directory (unused).
43
+ timeout: Command timeout (unused).
44
+
45
+ Returns:
46
+ None if the command is safe to proceed or user approved it.
47
+ Dict with blocked=True if a force push was detected and rejected.
48
+ """
49
+ match = detect_force_push(command)
50
+ if match is None:
51
+ return None
52
+
53
+ # --- Interactive TTY: ask the user ---
54
+ if _is_interactive():
55
+ return await _prompt_user_approval(command, match)
56
+
57
+ # --- Non-interactive: hard-block ---
58
+ return _block_command(command, match)
59
+
60
+
61
+ async def _prompt_user_approval(command: str, match: Any) -> dict[str, Any | None]:
62
+ """Show an interactive approval prompt for the detected force push.
63
+
64
+ Args:
65
+ command: The original shell command.
66
+ match: The ForcePushMatch from the detector.
67
+
68
+ Returns:
69
+ None if user approves, Dict with blocked=True if rejected.
70
+ """
71
+ from code_muse.tools.common import get_user_approval_async
72
+
73
+ panel_content = Text()
74
+ panel_content.append("⚠️ Force push detected: ", style="bold yellow")
75
+ panel_content.append(match.pattern_name, style="bold red")
76
+ panel_content.append("\n", style="")
77
+ panel_content.append(f" {match.description}", style="dim")
78
+ panel_content.append("\n\n", style="")
79
+ panel_content.append("$ ", style="bold green")
80
+ panel_content.append(command, style="bold white")
81
+ panel_content.append(
82
+ "\n\nForce pushing rewrites remote history and can destroy others' work.",
83
+ style="yellow",
84
+ )
85
+
86
+ confirmed, user_feedback = await get_user_approval_async(
87
+ title="Force Push Guard 🛡️",
88
+ content=panel_content,
89
+ border_style="red",
90
+ )
91
+
92
+ if confirmed:
93
+ emit_info("⚠️ Force push approved — proceeding with caution.")
94
+ return None # Allow the command through
95
+
96
+ # Rejected
97
+ reason = user_feedback or "User rejected force push"
98
+ return {
99
+ "blocked": True,
100
+ "reasoning": f"Force push rejected: {match.pattern_name} — {reason}",
101
+ "error_message": (
102
+ f"🛑 Force push rejected. Detected {match.pattern_name} "
103
+ f"in command:\n {command}\n"
104
+ f" {match.description}\n"
105
+ f"Feedback: {reason}"
106
+ ),
107
+ }
108
+
109
+
110
+ def _block_command(command: str, match: Any) -> dict[str, Any]:
111
+ """Hard-block a force push in non-interactive contexts.
112
+
113
+ Args:
114
+ command: The original shell command.
115
+ match: The ForcePushMatch from the detector.
116
+
117
+ Returns:
118
+ Dict with blocked=True and a descriptive error.
119
+ """
120
+ error_message = (
121
+ f"🛑 Force push blocked! Detected {match.pattern_name} "
122
+ f"in command:\n {command}\n"
123
+ f" {match.description}\n\n"
124
+ f"Force pushing rewrites remote history and can destroy others' work.\n"
125
+ f"If you *really* need to force push, use the exact command directly\n"
126
+ f"in your terminal (outside Muse) after double-checking the target branch."
127
+ )
128
+
129
+ emit_warning(error_message)
130
+
131
+ return {
132
+ "blocked": True,
133
+ "reasoning": f"Force push detected: {match.pattern_name} — {match.description}",
134
+ "error_message": error_message,
135
+ }
136
+
137
+
138
+ def register() -> None:
139
+ """Register the force push guard callback."""
140
+ register_callback("run_shell_command", force_push_guard_callback)
141
+
142
+
143
+ # Auto-register when this module is imported
144
+ register()
@@ -0,0 +1,143 @@
1
+ """Tests for the force push guard detector."""
2
+
3
+ from code_muse.plugins.force_push_guard.detector import detect_force_push
4
+
5
+
6
+ class TestDetectForcePush:
7
+ """Test suite for force push pattern detection."""
8
+
9
+ # --- Should BLOCK these commands ---
10
+
11
+ def test_long_force_flag(self):
12
+ result = detect_force_push("git push --force origin main")
13
+ assert result is not None
14
+ assert result.pattern_name == "--force"
15
+
16
+ def test_short_f_flag(self):
17
+ result = detect_force_push("git push -f origin main")
18
+ assert result is not None
19
+ assert result.pattern_name == "-f"
20
+
21
+ def test_capital_f_flag(self):
22
+ result = detect_force_push("git push -F origin main")
23
+ assert result is not None
24
+ assert result.pattern_name == "-F"
25
+
26
+ def test_force_with_lease(self):
27
+ result = detect_force_push("git push --force-with-lease origin feature")
28
+ assert result is not None
29
+ assert result.pattern_name == "--force-with-lease"
30
+
31
+ def test_force_if_includes(self):
32
+ result = detect_force_push("git push --force-if-includes origin feature")
33
+ assert result is not None
34
+ assert result.pattern_name == "--force-if-includes"
35
+
36
+ def test_plus_refspec(self):
37
+ result = detect_force_push("git push origin +main")
38
+ assert result is not None
39
+ assert result.pattern_name == "+refspec"
40
+
41
+ def test_plus_refspec_head(self):
42
+ result = detect_force_push("git push origin +HEAD:refs/heads/main")
43
+ assert result is not None
44
+ assert result.pattern_name == "+refspec"
45
+
46
+ def test_force_before_remote(self):
47
+ result = detect_force_push(
48
+ "git push --force-with-lease --set-upstream origin foo"
49
+ )
50
+ assert result is not None
51
+ assert result.pattern_name == "--force-with-lease"
52
+
53
+ def test_force_after_remote(self):
54
+ result = detect_force_push("git push origin feature --force")
55
+ assert result is not None
56
+ assert result.pattern_name == "--force"
57
+
58
+ def test_force_flag_with_equals(self):
59
+ result = detect_force_push("git push --force=yes origin main")
60
+ assert result is not None
61
+ assert result.pattern_name == "--force"
62
+
63
+ def test_force_with_other_flags(self):
64
+ result = detect_force_push("git push -v -f origin main")
65
+ assert result is not None
66
+ assert result.pattern_name == "-f"
67
+
68
+ # --- Should ALLOW these commands ---
69
+
70
+ def test_normal_push(self):
71
+ assert detect_force_push("git push origin main") is None
72
+
73
+ def test_push_with_set_upstream(self):
74
+ assert detect_force_push("git push --set-upstream origin feature") is None
75
+
76
+ def test_push_with_tags(self):
77
+ assert detect_force_push("git push origin --tags") is None
78
+
79
+ def test_push_u(self):
80
+ assert detect_force_push("git push -u origin main") is None
81
+
82
+ def test_git_pull(self):
83
+ assert detect_force_push("git pull origin main") is None
84
+
85
+ def test_git_status(self):
86
+ assert detect_force_push("git status") is None
87
+
88
+ def test_unrelated_command(self):
89
+ assert detect_force_push("npm install --force") is None
90
+
91
+ def test_empty_string(self):
92
+ assert detect_force_push("") is None
93
+
94
+ def test_echo_push(self):
95
+ assert detect_force_push("echo 'git push --force'") is None
96
+
97
+ def test_push_dry_run(self):
98
+ assert detect_force_push("git push --dry-run origin main") is None
99
+
100
+ def test_git_push_all(self):
101
+ assert detect_force_push("git push --all origin") is None
102
+
103
+ def test_git_push_mirror(self):
104
+ """--mirror IS destructive, but it's not a 'force push' per se.
105
+ We don't block it — different safety concern."""
106
+ assert detect_force_push("git push --mirror") is None
107
+
108
+ def test_push_no_force_file(self):
109
+ """A file named '--force' in a weirdly structured command shouldn't match."""
110
+ # This is a contrived edge case — the regex should not match
111
+ assert detect_force_push("git push origin main") is None
112
+
113
+ def test_grep_push(self):
114
+ """grep containing 'push' should not trigger."""
115
+ assert detect_force_push("grep -r push src/") is None
116
+
117
+ # --- Compound commands (shell operators) ---
118
+
119
+ def test_compound_and_force(self):
120
+ result = detect_force_push("cd foo && git push --force origin main")
121
+ assert result is not None
122
+ assert result.pattern_name == "--force"
123
+
124
+ def test_compound_semicolon_force(self):
125
+ result = detect_force_push("echo hi; git push -f origin main")
126
+ assert result is not None
127
+ assert result.pattern_name == "-f"
128
+
129
+ def test_compound_or_force(self):
130
+ result = detect_force_push("git pull || git push --force origin main")
131
+ assert result is not None
132
+ assert result.pattern_name == "--force"
133
+
134
+ def test_compound_pipe_not_force(self):
135
+ """Piped git push (uncommon) should still be caught if forced."""
136
+ result = detect_force_push("cat file | git push --force")
137
+ # Note: piping to git push makes no sense, but regex should still match
138
+ assert result is not None
139
+ assert result.pattern_name == "--force"
140
+
141
+ def test_compound_and_normal_push(self):
142
+ """Compound with a normal push should be allowed."""
143
+ assert detect_force_push("cd foo && git push origin main") is None
@@ -0,0 +1,25 @@
1
+ """Frontend emitter plugin for Muse.
2
+
3
+ This plugin provides event emission capabilities for frontend integration,
4
+ allowing WebSocket handlers to subscribe to real-time events from the
5
+ agent system including tool calls, streaming events, and agent invocations.
6
+
7
+ Usage:
8
+ from code_muse.plugins.frontend_emitter.emitter import (
9
+ emit_event,
10
+ subscribe,
11
+ unsubscribe,
12
+ get_recent_events,
13
+ )
14
+
15
+ # Subscribe to events
16
+ queue = subscribe()
17
+
18
+ # Process events in your WebSocket handler
19
+ while True:
20
+ event = await queue.get()
21
+ await websocket.send_json(event)
22
+
23
+ # Clean up
24
+ unsubscribe(queue)
25
+ """
@@ -0,0 +1,121 @@
1
+ """Event emitter for frontend integration.
2
+
3
+ Provides a global event queue that WebSocket handlers can subscribe to.
4
+ Events are JSON-serializable dicts with type, timestamp, and data.
5
+ """
6
+
7
+ import asyncio
8
+ import logging
9
+ from datetime import UTC, datetime
10
+ from typing import Any
11
+ from uuid import uuid4
12
+
13
+ from code_muse.config import (
14
+ get_frontend_emitter_enabled,
15
+ get_frontend_emitter_max_recent_events,
16
+ get_frontend_emitter_queue_size,
17
+ )
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+ # Global state for event distribution
22
+ _subscribers: set[asyncio.Queue[dict[str, Any]]] = set()
23
+ _recent_events: list[dict[str, Any]] = [] # Keep last N events for new subscribers
24
+
25
+
26
+ def emit_event(event_type: str, data: Any = None) -> None:
27
+ """Emit an event to all subscribers.
28
+
29
+ Creates a structured event dict with unique ID, type, timestamp, and data,
30
+ then broadcasts it to all active subscriber queues.
31
+
32
+ Args:
33
+ event_type: Type of event (e.g., "tool_call_start", "stream_token")
34
+ data: Event data payload - should be JSON-serializable
35
+ """
36
+ # Early return if emitter is disabled
37
+ if not get_frontend_emitter_enabled():
38
+ return
39
+
40
+ event: dict[str, Any] = {
41
+ "id": str(uuid4()),
42
+ "type": event_type,
43
+ "timestamp": datetime.now(UTC).isoformat(),
44
+ "data": data or {},
45
+ }
46
+
47
+ # Store in recent events for replay to new subscribers
48
+ max_recent = get_frontend_emitter_max_recent_events()
49
+ _recent_events.append(event)
50
+ if len(_recent_events) > max_recent:
51
+ _recent_events.pop(0)
52
+
53
+ # Broadcast to all active subscribers
54
+ for subscriber_queue in _subscribers.copy():
55
+ try:
56
+ subscriber_queue.put_nowait(event)
57
+ except asyncio.QueueFull:
58
+ logger.warning(f"Subscriber queue full, dropping event: {event_type}")
59
+ except Exception as e:
60
+ logger.error(f"Failed to emit event to subscriber: {e}")
61
+
62
+
63
+ def subscribe() -> asyncio.Queue[dict[str, Any]]:
64
+ """Subscribe to events.
65
+
66
+ Creates and returns a new async queue that will receive all future events.
67
+ The queue has a configurable max size (via frontend_emitter_queue_size)
68
+ to prevent unbounded memory growth if the subscriber is slow to process events.
69
+
70
+ Returns:
71
+ An asyncio.Queue that will receive event dictionaries.
72
+ """
73
+ queue_size = get_frontend_emitter_queue_size()
74
+ queue: asyncio.Queue[dict[str, Any]] = asyncio.Queue(maxsize=queue_size)
75
+ _subscribers.add(queue)
76
+ logger.debug(f"New subscriber added, total subscribers: {len(_subscribers)}")
77
+ return queue
78
+
79
+
80
+ def unsubscribe(queue: asyncio.Queue[dict[str, Any]]) -> None:
81
+ """Unsubscribe from events.
82
+
83
+ Removes the queue from the subscriber set. Safe to call even if the queue
84
+ was never subscribed or already unsubscribed.
85
+
86
+ Args:
87
+ queue: The queue returned from subscribe()
88
+ """
89
+ _subscribers.discard(queue)
90
+ logger.debug(f"Subscriber removed, remaining subscribers: {len(_subscribers)}")
91
+
92
+
93
+ def get_recent_events() -> list[dict[str, Any]]:
94
+ """Get recent events for new subscribers.
95
+
96
+ Returns a copy of the most recent events (up to frontend_emitter_max_recent_events).
97
+ Useful for allowing new WebSocket connections to "catch up" on
98
+ recent activity.
99
+
100
+ Returns:
101
+ A list of recent event dictionaries.
102
+ """
103
+ return _recent_events.copy()
104
+
105
+
106
+ def get_subscriber_count() -> int:
107
+ """Get the current number of active subscribers.
108
+
109
+ Returns:
110
+ Number of active subscriber queues.
111
+ """
112
+ return len(_subscribers)
113
+
114
+
115
+ def clear_recent_events() -> None:
116
+ """Clear the recent events buffer.
117
+
118
+ Useful for testing or resetting state.
119
+ """
120
+ _recent_events.clear()
121
+ logger.debug("Recent events cleared")
@@ -0,0 +1,259 @@
1
+ """Callback registration for frontend event emission.
2
+
3
+ This module registers callbacks for various agent events and emits them
4
+ to subscribed WebSocket handlers via the emitter module.
5
+ """
6
+
7
+ import logging
8
+ import time
9
+ from typing import Any
10
+
11
+ from code_muse.callbacks import register_callback
12
+ from code_muse.plugins.frontend_emitter.emitter import emit_event
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ async def on_pre_tool_call(
18
+ tool_name: str, tool_args: dict[str, Any], context: Any = None
19
+ ) -> None:
20
+ """Emit an event when a tool call starts.
21
+
22
+ Args:
23
+ tool_name: Name of the tool being called
24
+ tool_args: Arguments being passed to the tool
25
+ context: Optional context data for the tool call
26
+ """
27
+ try:
28
+ emit_event(
29
+ "tool_call_start",
30
+ {
31
+ "tool_name": tool_name,
32
+ "tool_args": _sanitize_args(tool_args),
33
+ "start_time": time.time(),
34
+ },
35
+ )
36
+ logger.debug(f"Emitted tool_call_start for {tool_name}")
37
+ except Exception as e:
38
+ logger.error(f"Failed to emit pre_tool_call event: {e}")
39
+
40
+
41
+ async def on_post_tool_call(
42
+ tool_name: str,
43
+ tool_args: dict[str, Any],
44
+ result: Any,
45
+ duration_ms: float,
46
+ context: Any = None,
47
+ ) -> None:
48
+ """Emit an event when a tool call completes.
49
+
50
+ Args:
51
+ tool_name: Name of the tool that was called
52
+ tool_args: Arguments that were passed to the tool
53
+ result: The result returned by the tool
54
+ duration_ms: Execution time in milliseconds
55
+ context: Optional context data for the tool call
56
+ """
57
+ try:
58
+ emit_event(
59
+ "tool_call_complete",
60
+ {
61
+ "tool_name": tool_name,
62
+ "tool_args": _sanitize_args(tool_args),
63
+ "duration_ms": duration_ms,
64
+ "success": _is_successful_result(result),
65
+ "result_summary": _summarize_result(result),
66
+ },
67
+ )
68
+ logger.debug(
69
+ f"Emitted tool_call_complete for {tool_name} ({duration_ms:.2f}ms)"
70
+ )
71
+ except Exception as e:
72
+ logger.error(f"Failed to emit post_tool_call event: {e}")
73
+
74
+
75
+ async def on_stream_event(
76
+ event_type: str, event_data: Any, agent_session_id: str | None = None
77
+ ) -> None:
78
+ """Emit streaming events from the agent.
79
+
80
+ Args:
81
+ event_type: Type of the streaming event
82
+ event_data: Data associated with the event
83
+ agent_session_id: Optional session ID of the agent emitting the event
84
+ """
85
+ try:
86
+ emit_event(
87
+ "stream_event",
88
+ {
89
+ "event_type": event_type,
90
+ "event_data": _sanitize_event_data(event_data),
91
+ "agent_session_id": agent_session_id,
92
+ },
93
+ )
94
+ logger.debug(f"Emitted stream_event: {event_type}")
95
+ except Exception as e:
96
+ logger.error(f"Failed to emit stream_event: {e}")
97
+
98
+
99
+ async def on_invoke_agent(*args: Any, **kwargs: Any) -> None:
100
+ """Emit an event when an agent is invoked.
101
+
102
+ Args:
103
+ *args: Positional arguments from the invoke_agent callback
104
+ **kwargs: Keyword arguments from the invoke_agent callback
105
+ """
106
+ try:
107
+ # Extract relevant info from args/kwargs
108
+ agent_info = {
109
+ "agent_name": kwargs.get("agent_name") or (args[0] if args else None),
110
+ "session_id": kwargs.get("session_id"),
111
+ "prompt_preview": _truncate_string(
112
+ kwargs.get("prompt") or (args[1] if len(args) > 1 else None),
113
+ max_length=200,
114
+ ),
115
+ }
116
+ emit_event("agent_invoked", agent_info)
117
+ logger.debug(f"Emitted agent_invoked: {agent_info.get('agent_name')}")
118
+ except Exception as e:
119
+ logger.error(f"Failed to emit invoke_agent event: {e}")
120
+
121
+
122
+ def _sanitize_args(args: dict[str, Any]) -> dict[str, Any]:
123
+ """Sanitize tool arguments for safe emission.
124
+
125
+ Truncates large values and removes potentially sensitive data.
126
+
127
+ Args:
128
+ args: The raw tool arguments
129
+
130
+ Returns:
131
+ Sanitized arguments safe for emission
132
+ """
133
+ if not isinstance(args, dict):
134
+ return {}
135
+
136
+ sanitized: dict[str, Any] = {}
137
+ for key, value in args.items():
138
+ if isinstance(value, str):
139
+ sanitized[key] = _truncate_string(value, max_length=500)
140
+ elif isinstance(value, (int, float, bool, type(None))):
141
+ sanitized[key] = value
142
+ elif isinstance(value, (list, dict)):
143
+ # Just indicate the type and length for complex types
144
+ sanitized[key] = f"<{type(value).__name__}[{len(value)}]>"
145
+ else:
146
+ sanitized[key] = f"<{type(value).__name__}>"
147
+
148
+ return sanitized
149
+
150
+
151
+ def _sanitize_event_data(data: Any) -> Any:
152
+ """Sanitize event data for safe emission.
153
+
154
+ Args:
155
+ data: The raw event data
156
+
157
+ Returns:
158
+ Sanitized data safe for emission
159
+ """
160
+ if data is None:
161
+ return None
162
+
163
+ if isinstance(data, str):
164
+ return _truncate_string(data, max_length=1000)
165
+
166
+ if isinstance(data, (int, float, bool)):
167
+ return data
168
+
169
+ if isinstance(data, dict):
170
+ return {k: _sanitize_event_data(v) for k, v in list(data.items())[:20]}
171
+
172
+ if isinstance(data, (list, tuple)):
173
+ return [_sanitize_event_data(item) for item in data[:20]]
174
+
175
+ return f"<{type(data).__name__}>"
176
+
177
+
178
+ def _is_successful_result(result: Any) -> bool:
179
+ """Determine if a tool result indicates success.
180
+
181
+ Args:
182
+ result: The tool result
183
+
184
+ Returns:
185
+ True if the result appears successful
186
+ """
187
+ if result is None:
188
+ return True # No result often means success
189
+
190
+ if isinstance(result, dict):
191
+ # Check for error indicators
192
+ if result.get("error"):
193
+ return False
194
+ return result.get("success") is not False
195
+
196
+ if isinstance(result, bool):
197
+ return result
198
+
199
+ return True # Default to success
200
+
201
+
202
+ def _summarize_result(result: Any) -> str:
203
+ """Create a brief summary of a tool result.
204
+
205
+ Args:
206
+ result: The tool result
207
+
208
+ Returns:
209
+ A string summary of the result
210
+ """
211
+ if result is None:
212
+ return "<no result>"
213
+
214
+ if isinstance(result, str):
215
+ return _truncate_string(result, max_length=200)
216
+
217
+ if isinstance(result, dict):
218
+ if "error" in result:
219
+ return f"Error: {_truncate_string(str(result['error']), max_length=100)}"
220
+ if "message" in result:
221
+ return _truncate_string(str(result["message"]), max_length=100)
222
+ return f"<dict with {len(result)} keys>"
223
+
224
+ if isinstance(result, (list, tuple)):
225
+ return f"<{type(result).__name__}[{len(result)}]>"
226
+
227
+ return _truncate_string(str(result), max_length=200)
228
+
229
+
230
+ def _truncate_string(value: Any, max_length: int = 100) -> str | None:
231
+ """Truncate a string value if it exceeds max_length.
232
+
233
+ Args:
234
+ value: The value to truncate (will be converted to str)
235
+ max_length: Maximum length before truncation
236
+
237
+ Returns:
238
+ Truncated string or None if value is None
239
+ """
240
+ if value is None:
241
+ return None
242
+
243
+ s = str(value)
244
+ if len(s) > max_length:
245
+ return s[: max_length - 3] + "..."
246
+ return s
247
+
248
+
249
+ def register() -> None:
250
+ """Register all frontend emitter callbacks."""
251
+ register_callback("pre_tool_call", on_pre_tool_call)
252
+ register_callback("post_tool_call", on_post_tool_call)
253
+ register_callback("stream_event", on_stream_event)
254
+ register_callback("invoke_agent", on_invoke_agent)
255
+ logger.debug("Frontend emitter callbacks registered")
256
+
257
+
258
+ # Auto-register callbacks when this module is imported
259
+ register()
@@ -0,0 +1,4 @@
1
+ """GAC plugin — AI-powered git commits via slash commands.
2
+
3
+ Provides /gac, /gac push, and /gac bump commands.
4
+ """