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,538 @@
1
+ """Utility helpers for the ChatGPT OAuth plugin."""
2
+
3
+ import base64
4
+ import datetime
5
+ import hashlib
6
+ import json
7
+ import logging
8
+ import secrets
9
+ import time
10
+ from dataclasses import dataclass
11
+ from typing import Any
12
+ from urllib.parse import parse_qs as urllib_parse_qs
13
+ from urllib.parse import urlencode, urlparse
14
+
15
+ import httpx
16
+
17
+ from code_muse.secret_storage import (
18
+ atomic_write_private_json,
19
+ warn_or_fix_private_file_mode,
20
+ )
21
+ from code_muse.security.redaction import redact_secrets
22
+
23
+ from .config import (
24
+ CHATGPT_OAUTH_CONFIG,
25
+ get_chatgpt_models_path,
26
+ get_token_storage_path,
27
+ )
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ @dataclass
33
+ class OAuthContext:
34
+ """Runtime state for an in-progress OAuth flow."""
35
+
36
+ state: str
37
+ code_verifier: str
38
+ code_challenge: str
39
+ created_at: float
40
+ redirect_uri: str | None = None
41
+ expires_at: float | None = None # Add expiration time
42
+
43
+ def is_expired(self) -> bool:
44
+ """Check if this OAuth context has expired."""
45
+ if self.expires_at is None:
46
+ # Default 5 minute expiration if not set
47
+ return time.time() - self.created_at > 300
48
+ return time.time() > self.expires_at
49
+
50
+
51
+ def _urlsafe_b64encode(data: bytes) -> str:
52
+ return base64.urlsafe_b64encode(data).decode("utf-8").rstrip("=")
53
+
54
+
55
+ def _generate_code_verifier() -> str:
56
+ return secrets.token_hex(64)
57
+
58
+
59
+ def _compute_code_challenge(code_verifier: str) -> str:
60
+ digest = hashlib.sha256(code_verifier.encode("utf-8")).digest()
61
+ return _urlsafe_b64encode(digest)
62
+
63
+
64
+ def prepare_oauth_context() -> OAuthContext:
65
+ """Create a fresh OAuth PKCE context."""
66
+ state = secrets.token_hex(32)
67
+ code_verifier = _generate_code_verifier()
68
+ code_challenge = _compute_code_challenge(code_verifier)
69
+
70
+ # Set expiration 4 minutes from now (OpenAI sessions are short)
71
+ expires_at = time.time() + 240
72
+
73
+ return OAuthContext(
74
+ state=state,
75
+ code_verifier=code_verifier,
76
+ code_challenge=code_challenge,
77
+ created_at=time.time(),
78
+ expires_at=expires_at,
79
+ )
80
+
81
+
82
+ def assign_redirect_uri(context: OAuthContext, port: int) -> str:
83
+ """Assign redirect URI for the given OAuth context."""
84
+ if context is None:
85
+ raise RuntimeError("OAuth context cannot be None")
86
+ host = CHATGPT_OAUTH_CONFIG["redirect_host"].rstrip("/")
87
+ path = CHATGPT_OAUTH_CONFIG["redirect_path"].lstrip("/")
88
+ required_port = CHATGPT_OAUTH_CONFIG.get("required_port")
89
+ if required_port and port != required_port:
90
+ raise RuntimeError(
91
+ f"OAuth flow must use port {required_port}; attempted to assign port {port}"
92
+ )
93
+ redirect_uri = f"{host}:{port}/{path}"
94
+ context.redirect_uri = redirect_uri
95
+ return redirect_uri
96
+
97
+
98
+ def build_authorization_url(context: OAuthContext) -> str:
99
+ """Return the OpenAI authorization URL with PKCE parameters."""
100
+ if not context.redirect_uri:
101
+ raise RuntimeError("Redirect URI has not been assigned for this OAuth context")
102
+
103
+ params = {
104
+ "response_type": "code",
105
+ "client_id": CHATGPT_OAUTH_CONFIG["client_id"],
106
+ "redirect_uri": context.redirect_uri,
107
+ "scope": CHATGPT_OAUTH_CONFIG["scope"],
108
+ "code_challenge": context.code_challenge,
109
+ "code_challenge_method": "S256",
110
+ "id_token_add_organizations": "true",
111
+ "codex_cli_simplified_flow": "true",
112
+ "state": context.state,
113
+ }
114
+ # TODO: PEP 750 t-string — use templatelib when stable
115
+ return f"{CHATGPT_OAUTH_CONFIG['auth_url']}?{urlencode(params)}"
116
+
117
+
118
+ def parse_authorization_error(url: str) -> str | None:
119
+ """Parse error from OAuth callback URL."""
120
+ try:
121
+ parsed = urlparse(url)
122
+ params = urllib_parse_qs(parsed.query)
123
+ error = params.get("error", [None])[0]
124
+ error_description = params.get("error_description", [None])[0]
125
+ if error:
126
+ return f"{error}: {error_description or 'Unknown error'}"
127
+ except Exception as exc:
128
+ logger.error("Failed to parse OAuth error: %s", exc)
129
+ return None
130
+
131
+
132
+ def parse_jwt_claims(token: str) -> dict[str, Any | None]:
133
+ """Parse JWT token to extract claims."""
134
+ if not token or token.count(".") != 2:
135
+ return None
136
+ try:
137
+ _, payload, _ = token.split(".")
138
+ padded = payload + "=" * (-len(payload) % 4)
139
+ data = base64.urlsafe_b64decode(padded.encode())
140
+ return json.loads(data.decode())
141
+ except Exception as exc:
142
+ logger.error("Failed to parse JWT: %s", exc)
143
+ return None
144
+
145
+
146
+ def load_stored_tokens() -> dict[str, Any | None]:
147
+ try:
148
+ token_path = get_token_storage_path()
149
+ if token_path.exists():
150
+ warn_or_fix_private_file_mode(token_path)
151
+ with open(token_path, encoding="utf-8") as handle:
152
+ return json.load(handle)
153
+ except Exception as exc:
154
+ logger.error("Failed to load tokens: %s", exc)
155
+ return None
156
+
157
+
158
+ def get_valid_access_token() -> str | None:
159
+ """Get a valid access token, refreshing if expired.
160
+
161
+ Returns:
162
+ Valid access token string, or None if not authenticated or refresh failed.
163
+ """
164
+ tokens = load_stored_tokens()
165
+ if not tokens:
166
+ logger.debug("No stored ChatGPT OAuth tokens found")
167
+ return None
168
+
169
+ access_token = tokens.get("access_token")
170
+ if not access_token:
171
+ logger.debug("No access_token in stored tokens")
172
+ return None
173
+
174
+ # Check if token is expired by parsing JWT claims
175
+ claims = parse_jwt_claims(access_token)
176
+ if claims:
177
+ exp = claims.get("exp")
178
+ if exp and isinstance(exp, (int, float)):
179
+ # Add 30 second buffer before expiry
180
+ if time.time() > exp - 30:
181
+ logger.info("ChatGPT OAuth token expired, attempting refresh")
182
+ refreshed = refresh_access_token()
183
+ if refreshed:
184
+ return refreshed
185
+ logger.warning("Token refresh failed")
186
+ return None
187
+
188
+ return access_token
189
+
190
+
191
+ def refresh_access_token() -> str | None:
192
+ """Refresh the access token using the refresh token.
193
+
194
+ Returns:
195
+ New access token if refresh succeeded, None otherwise.
196
+ """
197
+ tokens = load_stored_tokens()
198
+ if not tokens:
199
+ return None
200
+
201
+ refresh_token = tokens.get("refresh_token")
202
+ if not refresh_token:
203
+ logger.debug("No refresh_token available")
204
+ return None
205
+
206
+ payload = {
207
+ "grant_type": "refresh_token",
208
+ "refresh_token": refresh_token,
209
+ "client_id": CHATGPT_OAUTH_CONFIG["client_id"],
210
+ }
211
+
212
+ headers = {
213
+ "Content-Type": "application/x-www-form-urlencoded",
214
+ }
215
+
216
+ try:
217
+ response = httpx.post(
218
+ CHATGPT_OAUTH_CONFIG["token_url"],
219
+ data=payload,
220
+ headers=headers,
221
+ timeout=30,
222
+ )
223
+
224
+ if response.status_code == 200:
225
+ new_tokens = response.json()
226
+ # Merge with existing tokens (preserve account_id, etc.)
227
+ tokens.update(
228
+ {
229
+ "access_token": new_tokens.get("access_token"),
230
+ "refresh_token": new_tokens.get("refresh_token", refresh_token),
231
+ "id_token": new_tokens.get("id_token", tokens.get("id_token")),
232
+ "last_refresh": datetime.datetime.now(datetime.UTC)
233
+ .isoformat()
234
+ .replace("+00:00", "Z"),
235
+ }
236
+ )
237
+ if save_tokens(tokens):
238
+ logger.info("Successfully refreshed ChatGPT OAuth token")
239
+ return tokens["access_token"]
240
+ else:
241
+ safe_text = redact_secrets(response.text)
242
+ logger.error(
243
+ "Token refresh failed: status=%s content_type=%s error=%s",
244
+ response.status_code,
245
+ response.headers.get("content-type", "unknown"),
246
+ safe_text[:200],
247
+ )
248
+ except Exception as exc:
249
+ logger.error("Token refresh error: %s", exc)
250
+
251
+ return None
252
+
253
+
254
+ def save_tokens(tokens: dict[str, Any]) -> bool:
255
+ if tokens is None:
256
+ raise TypeError("tokens cannot be None")
257
+ try:
258
+ token_path = get_token_storage_path()
259
+ atomic_write_private_json(token_path, tokens)
260
+ return True
261
+ except Exception as exc:
262
+ logger.error("Failed to save tokens: %s", exc)
263
+ return False
264
+
265
+
266
+ def load_chatgpt_models() -> dict[str, Any]:
267
+ try:
268
+ models_path = get_chatgpt_models_path()
269
+ if models_path.exists():
270
+ with open(models_path, encoding="utf-8") as handle:
271
+ return json.load(handle)
272
+ except Exception as exc:
273
+ logger.error("Failed to load ChatGPT models: %s", exc)
274
+ return {}
275
+
276
+
277
+ def save_chatgpt_models(models: dict[str, Any]) -> bool:
278
+ try:
279
+ models_path = get_chatgpt_models_path()
280
+ with open(models_path, "w", encoding="utf-8") as handle:
281
+ json.dump(models, handle, indent=2)
282
+ return True
283
+ except Exception as exc:
284
+ logger.error("Failed to save ChatGPT models: %s", exc)
285
+ return False
286
+
287
+
288
+ def exchange_code_for_tokens(
289
+ auth_code: str, context: OAuthContext
290
+ ) -> dict[str, Any | None]:
291
+ """Exchange authorization code for access tokens."""
292
+ if not context.redirect_uri:
293
+ raise RuntimeError("Redirect URI missing from OAuth context")
294
+
295
+ if context.is_expired():
296
+ logger.error("OAuth context expired, cannot exchange code")
297
+ return None
298
+
299
+ payload = {
300
+ "grant_type": "authorization_code",
301
+ "code": auth_code,
302
+ "redirect_uri": context.redirect_uri,
303
+ "client_id": CHATGPT_OAUTH_CONFIG["client_id"],
304
+ "code_verifier": context.code_verifier,
305
+ }
306
+
307
+ headers = {
308
+ "Content-Type": "application/x-www-form-urlencoded",
309
+ }
310
+
311
+ logger.info("Exchanging code for tokens: %s", CHATGPT_OAUTH_CONFIG["token_url"])
312
+ try:
313
+ response = httpx.post(
314
+ CHATGPT_OAUTH_CONFIG["token_url"],
315
+ data=payload,
316
+ headers=headers,
317
+ timeout=30,
318
+ )
319
+ logger.info("Token exchange response: %s", response.status_code)
320
+ if response.status_code == 200:
321
+ token_data = response.json()
322
+ # Add timestamp
323
+ token_data["last_refresh"] = (
324
+ datetime.datetime.now(datetime.UTC).isoformat().replace("+00:00", "Z")
325
+ )
326
+ return token_data
327
+ else:
328
+ safe_text = redact_secrets(response.text)
329
+ logger.error(
330
+ "Token exchange failed: status=%s content_type=%s error=%s",
331
+ response.status_code,
332
+ response.headers.get("content-type", "unknown"),
333
+ safe_text[:200],
334
+ )
335
+ # Try to parse OAuth error
336
+ if response.headers.get("content-type", "").startswith("application/json"):
337
+ try:
338
+ error_data = response.json()
339
+ if "error" in error_data:
340
+ safe_error = redact_secrets(
341
+ error_data.get("error_description", error_data["error"])
342
+ )
343
+ logger.error("OAuth error: %s", safe_error)
344
+ except Exception:
345
+ pass
346
+ except Exception as exc:
347
+ logger.error("Token exchange error: %s", exc)
348
+ return None
349
+
350
+
351
+ # Default models available via ChatGPT Codex API
352
+ # These are the known models that work with ChatGPT OAuth tokens
353
+ # Based on codex-rs CLI and shell-scripts/codex-call.sh
354
+ DEFAULT_CODEX_MODELS = [
355
+ "gpt-5.5",
356
+ "gpt-5.4",
357
+ "gpt-5.3-instant",
358
+ "gpt-5.3-codex-spark",
359
+ "gpt-5.3-codex",
360
+ "gpt-5.2-codex",
361
+ "gpt-5.2",
362
+ ]
363
+
364
+ # Models that MUST always be registered, even if the /models endpoint
365
+ # doesn't return them (e.g. newly launched, not yet in the API catalogue).
366
+ # These are merged into whatever the endpoint returns.
367
+ REQUIRED_CODEX_MODELS = [
368
+ "gpt-5.5",
369
+ "gpt-5.4",
370
+ "gpt-5.3-instant",
371
+ "gpt-5.3-codex",
372
+ ]
373
+
374
+ # Per-model context length overrides (tokens).
375
+ # Models not listed here use CHATGPT_OAUTH_CONFIG["default_context_length"] (272,000).
376
+ CODEX_MODEL_CONTEXT_LENGTHS = {
377
+ "gpt-5.3-codex-spark": 131000,
378
+ "gpt-5.3-instant": 192000,
379
+ }
380
+
381
+
382
+ def _ensure_required_models(models: list[str]) -> list[str]:
383
+ """Merge REQUIRED_CODEX_MODELS into the given list, preserving order.
384
+
385
+ Any required model not already present is prepended so it appears first.
386
+ """
387
+ existing = set(models)
388
+ missing = [m for m in REQUIRED_CODEX_MODELS if m not in existing]
389
+ if missing:
390
+ logger.info("Injecting required models not returned by API: %s", missing)
391
+ return missing + models
392
+
393
+
394
+ def fetch_chatgpt_models(access_token: str, account_id: str) -> list[str | None]:
395
+ """Fetch available models from ChatGPT Codex API.
396
+
397
+ Attempts to fetch models from the API, but falls back to a default list
398
+ of known Codex-compatible models if the API is unavailable.
399
+
400
+ Args:
401
+ access_token: OAuth access token for authentication
402
+ account_id: ChatGPT account ID (required for the API)
403
+
404
+ Returns:
405
+ List of model IDs, or default list if API fails
406
+ """
407
+ import platform
408
+
409
+ # Build the models URL with client version
410
+ client_version = CHATGPT_OAUTH_CONFIG.get("client_version", "0.72.0")
411
+ base_url = CHATGPT_OAUTH_CONFIG["api_base_url"].rstrip("/")
412
+ models_url = f"{base_url}/models"
413
+
414
+ # Build User-Agent to match codex-rs CLI format
415
+ originator = CHATGPT_OAUTH_CONFIG.get("originator", "codex_cli_rs")
416
+ os_name = platform.system()
417
+ if os_name == "Darwin":
418
+ os_name = "Mac OS"
419
+ os_version = platform.release()
420
+ arch = platform.machine()
421
+ user_agent = (
422
+ f"{originator}/{client_version} ({os_name} {os_version}; {arch}) "
423
+ "Terminal_Codex_CLI"
424
+ )
425
+
426
+ headers = {
427
+ "Authorization": f"Bearer {access_token}",
428
+ "ChatGPT-Account-Id": account_id,
429
+ "User-Agent": user_agent,
430
+ "originator": originator,
431
+ "Accept": "application/json",
432
+ }
433
+
434
+ # Query params
435
+ params = {"client_version": client_version}
436
+
437
+ try:
438
+ response = httpx.get(models_url, headers=headers, params=params, timeout=30)
439
+
440
+ if response.status_code == 200:
441
+ # Parse JSON response
442
+ try:
443
+ data = response.json()
444
+ # The response has a "models" key with list of model objects
445
+ if "models" in data and isinstance(data["models"], list):
446
+ models = []
447
+ for model in data["models"]:
448
+ if model is None:
449
+ continue
450
+ model_id = (
451
+ model.get("slug") or model.get("id") or model.get("name")
452
+ )
453
+ if model_id:
454
+ models.append(model_id)
455
+ if models:
456
+ return _ensure_required_models(models)
457
+ except ValueError as exc:
458
+ logger.warning("Failed to parse models response: %s", exc)
459
+
460
+ # API didn't return valid models, use default list
461
+ logger.info(
462
+ "Models endpoint returned %d, using default model list",
463
+ response.status_code,
464
+ )
465
+
466
+ except httpx.TimeoutException:
467
+ logger.warning("Timeout fetching models, using default list")
468
+ except httpx.HTTPError as exc:
469
+ logger.warning("Network error fetching models: %s, using default list", exc)
470
+ except Exception as exc:
471
+ logger.warning("Error fetching models: %s, using default list", exc)
472
+
473
+ # Return default models when API fails
474
+ logger.info("Using default Codex models: %s", DEFAULT_CODEX_MODELS)
475
+ return DEFAULT_CODEX_MODELS
476
+
477
+
478
+ def add_models_to_extra_config(models: list[str]) -> bool:
479
+ """Add ChatGPT models to chatgpt_models.json configuration."""
480
+ try:
481
+ chatgpt_models = load_chatgpt_models()
482
+ added = 0
483
+ for model_name in models:
484
+ prefixed = f"{CHATGPT_OAUTH_CONFIG['prefix']}{model_name}"
485
+
486
+ # Determine supported settings based on model type.
487
+ # ChatGPT OAuth models use the Responses API, so they support
488
+ # reasoning effort, reasoning summaries, and text verbosity.
489
+ supported_settings = ["reasoning_effort", "summary", "verbosity"]
490
+
491
+ # xhigh reasoning is supported by codex models and GPT-5.4+ variants.
492
+ # Older non-codex GPT-5.x models like gpt-5.2 stay capped at "high".
493
+ normalized_model_name = model_name.lower()
494
+ supports_xhigh_reasoning = (
495
+ "codex" in normalized_model_name
496
+ or normalized_model_name.startswith(("gpt-5.4", "gpt-5.5"))
497
+ )
498
+
499
+ chatgpt_models[prefixed] = {
500
+ "type": "chatgpt_oauth",
501
+ "name": model_name,
502
+ "custom_endpoint": {
503
+ # Codex API uses chatgpt.com/backend-api/codex, not api.openai.com
504
+ "url": CHATGPT_OAUTH_CONFIG["api_base_url"],
505
+ },
506
+ "context_length": CODEX_MODEL_CONTEXT_LENGTHS.get(
507
+ model_name, CHATGPT_OAUTH_CONFIG["default_context_length"]
508
+ ),
509
+ "oauth_source": "chatgpt-oauth-plugin",
510
+ "supported_settings": supported_settings,
511
+ "supports_xhigh_reasoning": supports_xhigh_reasoning,
512
+ }
513
+ added += 1
514
+ if save_chatgpt_models(chatgpt_models):
515
+ logger.info("Added %s ChatGPT models", added)
516
+ return True
517
+ except Exception as exc:
518
+ logger.error("Error adding models to config: %s", exc)
519
+ return False
520
+
521
+
522
+ def remove_chatgpt_models() -> int:
523
+ """Remove ChatGPT OAuth models from chatgpt_models.json."""
524
+ try:
525
+ chatgpt_models = load_chatgpt_models()
526
+ to_remove = [
527
+ name
528
+ for name, config in chatgpt_models.items()
529
+ if config.get("oauth_source") == "chatgpt-oauth-plugin"
530
+ ]
531
+ for model_name in to_remove:
532
+ chatgpt_models.pop(model_name, None)
533
+ # Always save, even if no models were removed (to match test expectations)
534
+ if save_chatgpt_models(chatgpt_models):
535
+ return len(to_remove)
536
+ except Exception as exc:
537
+ logger.error("Error removing ChatGPT models: %s", exc)
538
+ return 0
@@ -0,0 +1,29 @@
1
+ """Checkpointing + Rewind plugin for Muse."""
2
+
3
+ from code_muse.plugins.checkpointing.checkpoint_hook import (
4
+ on_pre_tool_call_checkpoint,
5
+ )
6
+ from code_muse.plugins.checkpointing.conversation_snapshots import (
7
+ create_snapshot,
8
+ list_snapshots,
9
+ load_snapshot,
10
+ )
11
+ from code_muse.plugins.checkpointing.restore_command import (
12
+ _handle_restore_command,
13
+ )
14
+ from code_muse.plugins.checkpointing.rewind_shortcut import (
15
+ DoublePressDetector,
16
+ RewindKeyListener,
17
+ )
18
+ from code_muse.plugins.checkpointing.shadow_git import ShadowGit
19
+
20
+ __all__ = [
21
+ "ShadowGit",
22
+ "create_snapshot",
23
+ "load_snapshot",
24
+ "list_snapshots",
25
+ "on_pre_tool_call_checkpoint",
26
+ "_handle_restore_command",
27
+ "DoublePressDetector",
28
+ "RewindKeyListener",
29
+ ]
@@ -0,0 +1,51 @@
1
+ """Pre-tool-call hook for automatic checkpointing."""
2
+
3
+ import asyncio
4
+ import logging
5
+ from typing import Any
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ async def on_pre_tool_call_checkpoint(
11
+ tool_name: str, tool_args: dict[str, Any]
12
+ ) -> None:
13
+ """Callback registered for pre_tool_call — fires-and-forget checkpoint."""
14
+ if tool_name not in ("write_file", "replace_in_file"):
15
+ return None
16
+
17
+ # Fire-and-forget checkpoint (don't await in a way that blocks)
18
+ asyncio.create_task(_create_checkpoint_async(tool_name, tool_args))
19
+ return None
20
+
21
+
22
+ async def _create_checkpoint_async(tool_name: str, tool_args: dict[str, Any]) -> None:
23
+ try:
24
+ import os
25
+
26
+ from code_muse.agents import get_current_agent
27
+ from code_muse.plugins.checkpointing.conversation_snapshots import (
28
+ create_snapshot,
29
+ )
30
+ from code_muse.plugins.checkpointing.shadow_git import ShadowGit
31
+
32
+ project_root = os.getcwd()
33
+
34
+ shadow = ShadowGit(project_root)
35
+ affected_files = _extract_affected_files(tool_name, tool_args)
36
+ commit_hash = shadow.create_checkpoint(tool_name, affected_files)
37
+
38
+ agent = get_current_agent()
39
+ snapshot_path = create_snapshot(
40
+ agent, tool_name, str(tool_args.get("tool_call_id", ""))
41
+ )
42
+
43
+ logger.info(f"Checkpoint created: {commit_hash}, snapshot: {snapshot_path}")
44
+ except Exception as exc:
45
+ logger.error(f"Checkpoint failed: {exc}")
46
+
47
+
48
+ def _extract_affected_files(tool_name: str, tool_args: dict[str, Any]) -> list[str]:
49
+ if tool_name == "write_file" or tool_name == "replace_in_file":
50
+ return [tool_args.get("file_path", "")]
51
+ return []