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,54 @@
1
+ """Register token-caching callbacks and the /cache slash command."""
2
+
3
+ import logging
4
+
5
+ from code_muse.callbacks import register_callback
6
+ from code_muse.messaging import emit_info
7
+ from code_muse.plugins.token_caching.cache_hit_tracking import _session_stats
8
+ from code_muse.plugins.token_caching.stats_display import format_cache_stats
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ # ---------------------------------------------------------------------------
14
+ # Slash-command help
15
+ # ---------------------------------------------------------------------------
16
+
17
+
18
+ def _on_custom_command_help() -> list[tuple[str, str]]:
19
+ """Provide help entry for /help display."""
20
+ return [("cache", "Show token caching statistics")]
21
+
22
+
23
+ # ---------------------------------------------------------------------------
24
+ # Slash-command handler
25
+ # ---------------------------------------------------------------------------
26
+
27
+
28
+ async def _on_custom_command(command: str, name: str) -> bool | None:
29
+ """Handle the /cache slash command.
30
+
31
+ Usage:
32
+ /cache
33
+
34
+ Displays current session cache statistics.
35
+
36
+ Returns:
37
+ ``True`` if handled, ``None`` if the command name doesn't match.
38
+ """
39
+ if name != "cache":
40
+ return None
41
+
42
+ stats_text = format_cache_stats(_session_stats)
43
+ emit_info(stats_text)
44
+ return True
45
+
46
+
47
+ # ---------------------------------------------------------------------------
48
+ # Register callbacks
49
+ # ---------------------------------------------------------------------------
50
+
51
+ register_callback("custom_command_help", _on_custom_command_help)
52
+ register_callback("custom_command", _on_custom_command)
53
+
54
+ logger.debug("Token Caching plugin callbacks registered")
@@ -0,0 +1,35 @@
1
+ """Format cache stats for human and LLM display."""
2
+
3
+ from code_muse.plugins.token_caching.cache_hit_tracking import SessionCacheStats
4
+
5
+
6
+ def format_cache_stats(stats: SessionCacheStats) -> str:
7
+ """Return a compact, human-readable summary of cache stats.
8
+
9
+ Example:
10
+ ``"Cache: 42 hits (12,500 tokens read) · 3 writes (1,200 tokens written) · hit rate 78.3% · est. savings $0.12"``
11
+
12
+ If there has been no cache activity this session, returns:
13
+ ``"Cache: no activity this session"``
14
+ """
15
+ reads = stats.total_read_tokens
16
+ writes = stats.total_write_tokens
17
+ hits = stats.hit_rate
18
+
19
+ if reads == 0 and writes == 0:
20
+ return "Cache: no activity this session"
21
+
22
+ savings = stats.estimated_savings_usd
23
+
24
+ # Format numbers with commas
25
+ reads_str = f"{reads:,}"
26
+ writes_str = f"{writes:,}"
27
+ hit_rate_str = f"{hits * 100:.1f}%"
28
+ savings_str = f"${savings:.2f}"
29
+
30
+ return (
31
+ f"Cache: {reads_str} tokens read"
32
+ f" · {writes_str} tokens written"
33
+ f" · hit rate {hit_rate_str}"
34
+ f" · est. savings {savings_str}"
35
+ )
@@ -0,0 +1,26 @@
1
+ """Token tracking plugin for Muse.
2
+
3
+ Records every command execution with token counts and provides slash-command
4
+ reports for gain, economics, session adoption, and edit efficiency.
5
+ """
6
+
7
+ from code_muse.plugins.token_tracking.database import TrackingDatabase, get_tracking_db
8
+ from code_muse.plugins.token_tracking.edit_analyzer import analyze_replacement
9
+ from code_muse.plugins.token_tracking.record import record_command
10
+ from code_muse.plugins.token_tracking.reports import (
11
+ cc_economics_report,
12
+ edit_efficiency_report,
13
+ gain_report,
14
+ session_report,
15
+ )
16
+
17
+ __all__ = [
18
+ "TrackingDatabase",
19
+ "analyze_replacement",
20
+ "cc_economics_report",
21
+ "edit_efficiency_report",
22
+ "gain_report",
23
+ "get_tracking_db",
24
+ "record_command",
25
+ "session_report",
26
+ ]
@@ -0,0 +1,381 @@
1
+ """SQLite tracking database for token usage history.
2
+
3
+ Stores every command execution with raw vs compressed token counts,
4
+ enabling gain reports and economics analysis.
5
+ """
6
+
7
+ import contextlib
8
+ import logging
9
+ import sqlite3
10
+ import threading
11
+ from datetime import UTC, datetime
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ SCHEMA_V1 = """
18
+ CREATE TABLE IF NOT EXISTS schema_version (
19
+ version INTEGER PRIMARY KEY
20
+ );
21
+ INSERT OR IGNORE INTO schema_version VALUES (1);
22
+
23
+ CREATE TABLE IF NOT EXISTS executions (
24
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
25
+ command TEXT NOT NULL,
26
+ category TEXT NOT NULL,
27
+ strategy TEXT NOT NULL,
28
+ raw_tokens INTEGER NOT NULL,
29
+ compressed_tokens INTEGER NOT NULL,
30
+ savings_pct REAL NOT NULL,
31
+ timestamp TEXT NOT NULL,
32
+ session_id TEXT NOT NULL,
33
+ exit_code INTEGER,
34
+ duration_ms REAL
35
+ );
36
+ CREATE INDEX IF NOT EXISTS idx_executions_timestamp ON executions(timestamp);
37
+ CREATE INDEX IF NOT EXISTS idx_executions_session ON executions(session_id);
38
+ """
39
+
40
+ SCHEMA_MIGRATION_V2 = """
41
+ ALTER TABLE schema_version ADD COLUMN migration_v2 INTEGER DEFAULT 0;
42
+ INSERT OR IGNORE INTO schema_version (version, migration_v2) VALUES (2, 1);
43
+
44
+ CREATE TABLE IF NOT EXISTS edit_analysis (
45
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
46
+ tool_name TEXT NOT NULL,
47
+ file_path TEXT NOT NULL,
48
+ old_bytes INTEGER,
49
+ new_bytes INTEGER,
50
+ total_edit_bytes INTEGER,
51
+ shared_prefix_bytes INTEGER,
52
+ shared_suffix_bytes INTEGER,
53
+ shared_context_bytes INTEGER,
54
+ core_old_bytes INTEGER,
55
+ core_new_bytes INTEGER,
56
+ core_bytes INTEGER,
57
+ wrapper_payload_bytes INTEGER,
58
+ inflation_ratio REAL,
59
+ no_core_change INTEGER DEFAULT 0,
60
+ success INTEGER DEFAULT 1,
61
+ timestamp TEXT NOT NULL,
62
+ session_id TEXT NOT NULL
63
+ );
64
+ CREATE INDEX IF NOT EXISTS idx_edit_analysis_timestamp ON edit_analysis(timestamp);
65
+ CREATE INDEX IF NOT EXISTS idx_edit_analysis_session ON edit_analysis(session_id);
66
+ """
67
+
68
+
69
+ class TrackingDatabase:
70
+ """Thread-safe SQLite database for tracking command executions.
71
+
72
+ Uses WAL mode for concurrency and a single shared connection
73
+ protected by a threading lock.
74
+
75
+ This is a plain class. For the shared application instance use
76
+ :func:`get_tracking_db`.
77
+ """
78
+
79
+ DB_PATH: Path = Path.home() / ".muse" / "tracking.db"
80
+ _CLEANUP_EVERY_N = 100
81
+
82
+ def __init__(self, db_path: Path | str | None = None) -> None:
83
+ """Initialise (or connect to) the tracking database.
84
+
85
+ Args:
86
+ db_path: Override the default ``~/.muse/tracking.db``.
87
+ """
88
+ self._db_path = Path(db_path) if db_path is not None else self.DB_PATH
89
+ self._connection: sqlite3.Connection | None = None
90
+ self._lock = threading.Lock()
91
+ self._insert_count = 0
92
+ self._ensure_dir()
93
+ self._run_migrations()
94
+ self.cleanup()
95
+
96
+ # ------------------------------------------------------------------
97
+ # Connection lifecycle
98
+ # ------------------------------------------------------------------
99
+
100
+ def _ensure_dir(self) -> None:
101
+ """Create the parent directory if it doesn't exist."""
102
+ self._db_path.parent.mkdir(parents=True, exist_ok=True)
103
+
104
+ def get_connection(self) -> sqlite3.Connection:
105
+ """Lazy-initialise and return the shared connection."""
106
+ if self._connection is None:
107
+ with self._lock:
108
+ if self._connection is None:
109
+ self._connection = sqlite3.connect(
110
+ str(self._db_path),
111
+ check_same_thread=False,
112
+ )
113
+ self._connection.execute("PRAGMA journal_mode=WAL")
114
+ self._connection.execute("PRAGMA foreign_keys=ON")
115
+ return self._connection
116
+
117
+ def close(self) -> None:
118
+ """Close the shared connection (idempotent)."""
119
+ with self._lock:
120
+ if self._connection is not None:
121
+ with contextlib.suppress(Exception):
122
+ self._connection.close()
123
+ self._connection = None
124
+
125
+ # ------------------------------------------------------------------
126
+ # Migrations
127
+ # ------------------------------------------------------------------
128
+
129
+ def _run_migrations(self) -> None:
130
+ """Ensure schema is at the latest version."""
131
+ conn = self.get_connection()
132
+ with self._lock:
133
+ # Check if schema_version table exists (v1 baseline)
134
+ try:
135
+ conn.execute("SELECT version FROM schema_version LIMIT 1").fetchone()
136
+ except sqlite3.OperationalError:
137
+ # First install — run full schema
138
+ conn.executescript(SCHEMA_V1)
139
+ conn.commit()
140
+
141
+ # Check v2 migration
142
+ try:
143
+ conn.execute(
144
+ "SELECT migration_v2 FROM schema_version LIMIT 1"
145
+ ).fetchone()
146
+ except sqlite3.OperationalError:
147
+ conn.executescript(SCHEMA_MIGRATION_V2)
148
+ conn.commit()
149
+
150
+ # ------------------------------------------------------------------
151
+ # Public API
152
+ # ------------------------------------------------------------------
153
+
154
+ def insert(
155
+ self,
156
+ command: str,
157
+ category: str,
158
+ strategy: str,
159
+ raw_tokens: int,
160
+ compressed_tokens: int,
161
+ savings_pct: float,
162
+ session_id: str,
163
+ exit_code: int = 0,
164
+ duration_ms: float = 0.0,
165
+ ) -> int:
166
+ """Record a command execution.
167
+
168
+ Returns:
169
+ The new row id, or ``-1`` on error (never raises).
170
+ """
171
+ try:
172
+ timestamp = datetime.now(UTC).isoformat()
173
+ conn = self.get_connection()
174
+ with self._lock:
175
+ cursor = conn.execute(
176
+ """
177
+ INSERT INTO executions
178
+ (command, category, strategy, raw_tokens, compressed_tokens,
179
+ savings_pct, timestamp, session_id, exit_code, duration_ms)
180
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
181
+ """,
182
+ (
183
+ command,
184
+ category,
185
+ strategy,
186
+ raw_tokens,
187
+ compressed_tokens,
188
+ savings_pct,
189
+ timestamp,
190
+ session_id,
191
+ exit_code,
192
+ duration_ms,
193
+ ),
194
+ )
195
+ conn.commit()
196
+ row_id = cursor.lastrowid or -1
197
+
198
+ self._insert_count += 1
199
+ if self._insert_count % self._CLEANUP_EVERY_N == 0:
200
+ self.cleanup()
201
+ return row_id
202
+ except Exception as exc:
203
+ logger.warning("TrackingDatabase insert failed: %s", exc)
204
+ return -1
205
+
206
+ def insert_edit_analysis(
207
+ self,
208
+ tool_name: str,
209
+ file_path: str,
210
+ old_bytes: int,
211
+ new_bytes: int,
212
+ total_edit_bytes: int,
213
+ shared_prefix_bytes: int,
214
+ shared_suffix_bytes: int,
215
+ shared_context_bytes: int,
216
+ core_old_bytes: int,
217
+ core_new_bytes: int,
218
+ core_bytes: int,
219
+ wrapper_payload_bytes: int,
220
+ inflation_ratio: float | None,
221
+ no_core_change: bool,
222
+ success: bool = True,
223
+ session_id: str = "",
224
+ ) -> int:
225
+ """Record an edit operation analysis. Returns row id or -1."""
226
+ try:
227
+ timestamp = datetime.now(UTC).isoformat()
228
+ conn = self.get_connection()
229
+ with self._lock:
230
+ cursor = conn.execute(
231
+ """
232
+ INSERT INTO edit_analysis
233
+ (tool_name, file_path, old_bytes, new_bytes, total_edit_bytes,
234
+ shared_prefix_bytes, shared_suffix_bytes, shared_context_bytes,
235
+ core_old_bytes, core_new_bytes, core_bytes, wrapper_payload_bytes,
236
+ inflation_ratio, no_core_change, success, timestamp, session_id)
237
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
238
+ """,
239
+ (
240
+ tool_name,
241
+ file_path,
242
+ old_bytes,
243
+ new_bytes,
244
+ total_edit_bytes,
245
+ shared_prefix_bytes,
246
+ shared_suffix_bytes,
247
+ shared_context_bytes,
248
+ core_old_bytes,
249
+ core_new_bytes,
250
+ core_bytes,
251
+ wrapper_payload_bytes,
252
+ inflation_ratio,
253
+ 1 if no_core_change else 0,
254
+ 1 if success else 0,
255
+ timestamp,
256
+ session_id,
257
+ ),
258
+ )
259
+ conn.commit()
260
+ return cursor.lastrowid or -1
261
+ except Exception as exc:
262
+ logger.warning("TrackingDatabase insert_edit_analysis failed: %s", exc)
263
+ return -1
264
+
265
+ def query_edit_summary(self, time_range: str = "all") -> list[dict]:
266
+ """Get edit efficiency summary for the given time range.
267
+
268
+ Returns a list of dicts, one per edit_analysis row.
269
+ """
270
+ where = {
271
+ "today": "date(timestamp) = date('now')",
272
+ "week": "timestamp >= datetime('now', '-7 days')",
273
+ "month": "timestamp >= datetime('now', '-30 days')",
274
+ "all": "1=1",
275
+ }.get(time_range, "1=1")
276
+
277
+ conn = self.get_connection()
278
+ with self._lock:
279
+ rows = conn.execute(
280
+ # TODO: PEP 750 t-string — use templatelib when stable
281
+ f"""
282
+ SELECT
283
+ id, tool_name, file_path, old_bytes, new_bytes,
284
+ total_edit_bytes, shared_prefix_bytes, shared_suffix_bytes,
285
+ shared_context_bytes, core_old_bytes, core_new_bytes,
286
+ core_bytes, wrapper_payload_bytes, inflation_ratio,
287
+ no_core_change, success, timestamp, session_id
288
+ FROM edit_analysis
289
+ WHERE {where}
290
+ ORDER BY timestamp DESC
291
+ """,
292
+ ).fetchall()
293
+
294
+ columns = [
295
+ "id",
296
+ "tool_name",
297
+ "file_path",
298
+ "old_bytes",
299
+ "new_bytes",
300
+ "total_edit_bytes",
301
+ "shared_prefix_bytes",
302
+ "shared_suffix_bytes",
303
+ "shared_context_bytes",
304
+ "core_old_bytes",
305
+ "core_new_bytes",
306
+ "core_bytes",
307
+ "wrapper_payload_bytes",
308
+ "inflation_ratio",
309
+ "no_core_change",
310
+ "success",
311
+ "timestamp",
312
+ "session_id",
313
+ ]
314
+ return [dict(zip(columns, row, strict=False)) for row in rows]
315
+
316
+ def cleanup(self, retention_days: int = 90) -> int:
317
+ """Delete records older than *retention_days*.
318
+
319
+ Returns:
320
+ Number of rows deleted.
321
+ """
322
+ try:
323
+ conn = self.get_connection()
324
+ with self._lock:
325
+ cursor = conn.execute(
326
+ """
327
+ DELETE FROM executions
328
+ WHERE timestamp < datetime('now', ?)
329
+ """,
330
+ (f"-{retention_days} days",),
331
+ )
332
+ cursor2 = conn.execute(
333
+ """
334
+ DELETE FROM edit_analysis
335
+ WHERE timestamp < datetime('now', ?)
336
+ """,
337
+ (f"-{retention_days} days",),
338
+ )
339
+ conn.commit()
340
+ return cursor.rowcount + cursor2.rowcount
341
+ except Exception as exc:
342
+ logger.warning("TrackingDatabase cleanup failed: %s", exc)
343
+ return 0
344
+
345
+ # ------------------------------------------------------------------
346
+ # Query helpers
347
+ # ------------------------------------------------------------------
348
+
349
+ def query_one(self, sql: str, parameters: tuple[Any, ...] = ()) -> Any:
350
+ """Execute a query and return the first row (or None)."""
351
+ conn = self.get_connection()
352
+ with self._lock:
353
+ return conn.execute(sql, parameters).fetchone()
354
+
355
+ def query_all(self, sql: str, parameters: tuple[Any, ...] = ()) -> list[Any]:
356
+ """Execute a query and return all rows."""
357
+ conn = self.get_connection()
358
+ with self._lock:
359
+ return conn.execute(sql, parameters).fetchall()
360
+
361
+
362
+ # Module-level singleton cache — simple, testable, explicit.
363
+ _tracking_db_instance: TrackingDatabase | None = None
364
+ _tracking_db_lock = threading.Lock()
365
+
366
+
367
+ def get_tracking_db(db_path: Path | str | None = None) -> TrackingDatabase:
368
+ """Return the shared :class:`TrackingDatabase` instance.
369
+
370
+ The first call creates and caches the instance; subsequent calls
371
+ return the same object. Thread-safe.
372
+
373
+ Args:
374
+ db_path: Override the default ``~/.muse/tracking.db``.
375
+ """
376
+ global _tracking_db_instance
377
+ if _tracking_db_instance is None:
378
+ with _tracking_db_lock:
379
+ if _tracking_db_instance is None:
380
+ _tracking_db_instance = TrackingDatabase(db_path)
381
+ return _tracking_db_instance
@@ -0,0 +1,97 @@
1
+ """Edit context inflation analyzer — ported from Pi project.
2
+
3
+ Measures how much wrapper/boilerplate code surrounds the actual change
4
+ in file edit operations, revealing token waste.
5
+ """
6
+
7
+
8
+ def _utf8_bytes(text: str) -> int:
9
+ """Count UTF-8 bytes in a string."""
10
+ return len(text.encode("utf-8"))
11
+
12
+
13
+ def _longest_common_prefix_len(a: str, b: str) -> int:
14
+ """Length of the longest common prefix between two strings."""
15
+ max_len = min(len(a), len(b))
16
+ i = 0
17
+ while i < max_len and a[i] == b[i]:
18
+ i += 1
19
+ return i
20
+
21
+
22
+ def _longest_common_suffix_len(a: str, b: str) -> int:
23
+ """Length of the longest common suffix between two strings."""
24
+ max_len = min(len(a), len(b))
25
+ i = 0
26
+ while i < max_len and a[-(i + 1)] == b[-(i + 1)]:
27
+ i += 1
28
+ return i
29
+
30
+
31
+ def analyze_replacement(
32
+ old_text: str,
33
+ new_text: str,
34
+ ) -> dict:
35
+ """Analyze an old→new text replacement for context inflation.
36
+
37
+ Returns a dict with byte-level metrics:
38
+ - old_bytes, new_bytes: raw UTF-8 byte counts
39
+ - total_edit_bytes: old + new
40
+ - shared_prefix_bytes, shared_suffix_bytes: identical wrapper context
41
+ - shared_context_bytes: prefix + suffix
42
+ - core_old_bytes, core_new_bytes: the actual changed portions
43
+ - core_bytes: core_old + core_new (the meaningful change)
44
+ - wrapper_payload_bytes: total - core (the overhead)
45
+ - inflation_ratio: total / core (None if no core change)
46
+ - no_core_change: True when old == new (pure wrapper churn)
47
+ """
48
+ old_bytes = _utf8_bytes(old_text)
49
+ new_bytes = _utf8_bytes(new_text)
50
+ total = old_bytes + new_bytes
51
+
52
+ prefix_chars = _longest_common_prefix_len(old_text, new_text)
53
+ old_remainder = old_text[prefix_chars:]
54
+ new_remainder = new_text[prefix_chars:]
55
+
56
+ suffix_chars = _longest_common_suffix_len(old_remainder, new_remainder)
57
+
58
+ old_core = (
59
+ old_remainder[: len(old_remainder) - suffix_chars]
60
+ if suffix_chars > 0
61
+ else old_remainder
62
+ )
63
+ new_core = (
64
+ new_remainder[: len(new_remainder) - suffix_chars]
65
+ if suffix_chars > 0
66
+ else new_remainder
67
+ )
68
+
69
+ prefix = old_text[:prefix_chars]
70
+ suffix = old_remainder[-suffix_chars:] if suffix_chars > 0 else ""
71
+
72
+ shared_prefix_bytes = _utf8_bytes(prefix)
73
+ shared_suffix_bytes = _utf8_bytes(suffix)
74
+ shared_context_bytes = shared_prefix_bytes + shared_suffix_bytes
75
+
76
+ core_old_bytes = _utf8_bytes(old_core)
77
+ core_new_bytes = _utf8_bytes(new_core)
78
+ core_bytes = core_old_bytes + core_new_bytes
79
+ wrapper_payload_bytes = total - core_bytes
80
+
81
+ no_core_change = core_bytes == 0
82
+ inflation_ratio = None if no_core_change else total / core_bytes
83
+
84
+ return {
85
+ "old_bytes": old_bytes,
86
+ "new_bytes": new_bytes,
87
+ "total_edit_bytes": total,
88
+ "shared_prefix_bytes": shared_prefix_bytes,
89
+ "shared_suffix_bytes": shared_suffix_bytes,
90
+ "shared_context_bytes": shared_context_bytes,
91
+ "core_old_bytes": core_old_bytes,
92
+ "core_new_bytes": core_new_bytes,
93
+ "core_bytes": core_bytes,
94
+ "wrapper_payload_bytes": wrapper_payload_bytes,
95
+ "inflation_ratio": inflation_ratio,
96
+ "no_core_change": no_core_change,
97
+ }
@@ -0,0 +1,55 @@
1
+ """Record command execution metrics to the tracking database.
2
+
3
+ Simple, self-contained utility — never raises, never blocks.
4
+ """
5
+
6
+ import logging
7
+
8
+ from code_muse.config import get_current_autosave_id
9
+ from code_muse.plugins.token_tracking.database import get_tracking_db
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ def _count_tokens(text: str) -> int:
15
+ """Count whitespace-delimited tokens in text.
16
+
17
+ Simple heuristic — no tiktoken dependency. For empty text returns 0.
18
+ """
19
+ return len(text.split()) if text else 0
20
+
21
+
22
+ def record_command(
23
+ command: str,
24
+ raw_stdout: str,
25
+ raw_stderr: str,
26
+ compressed_stdout: str,
27
+ compressed_stderr: str,
28
+ category: str,
29
+ strategy: str,
30
+ exit_code: int = 0,
31
+ duration_ms: float = 0.0,
32
+ ) -> None:
33
+ """Insert a tracking record. Never raises — logs and returns on error."""
34
+ try:
35
+ raw_tokens = _count_tokens(f"{raw_stdout}\n{raw_stderr}")
36
+ compressed_tokens = _count_tokens(f"{compressed_stdout}\n{compressed_stderr}")
37
+
38
+ if raw_tokens == 0:
39
+ savings_pct = 0.0
40
+ else:
41
+ savings_pct = (raw_tokens - compressed_tokens) / raw_tokens * 100
42
+
43
+ get_tracking_db().insert(
44
+ command=command,
45
+ category=category,
46
+ strategy=strategy,
47
+ raw_tokens=raw_tokens,
48
+ compressed_tokens=compressed_tokens,
49
+ savings_pct=savings_pct,
50
+ session_id=get_current_autosave_id(),
51
+ exit_code=exit_code,
52
+ duration_ms=duration_ms,
53
+ )
54
+ except Exception:
55
+ logger.debug("record_command failed", exc_info=True)