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,584 @@
1
+ """Utility helpers for the GitHub Copilot auth plugin.
2
+
3
+ Handles browser-based Device Flow authentication, session-token exchange /
4
+ caching, and model registration persistence.
5
+ """
6
+
7
+ import json
8
+ import logging
9
+ import time
10
+ from dataclasses import dataclass
11
+ from typing import Any
12
+
13
+ import httpx
14
+
15
+ from .config import (
16
+ COPILOT_AUTH_CONFIG,
17
+ COPILOT_MODEL_CONTEXT_LENGTHS,
18
+ DEFAULT_COPILOT_MODELS,
19
+ DEVICE_FLOW_CONFIG,
20
+ get_copilot_models_path,
21
+ get_device_token_storage_path,
22
+ get_session_cache_path,
23
+ )
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+ # ---------------------------------------------------------------------------
29
+ # Token storage — persisted tokens obtained via the Device Flow
30
+ # ---------------------------------------------------------------------------
31
+
32
+
33
+ @dataclass
34
+ class CopilotToken:
35
+ """An OAuth token for a GitHub host."""
36
+
37
+ host: str # "github.com" or a GHE hostname
38
+ oauth_token: str
39
+ user: str = ""
40
+
41
+
42
+ def get_token_for_host(host: str) -> CopilotToken | None:
43
+ """Return a stored Device Flow token whose host matches *host* exactly.
44
+
45
+ Returns ``None`` if no token for the given host is found.
46
+ """
47
+ tokens = load_device_tokens()
48
+ for t in tokens:
49
+ if t.host == host:
50
+ return t
51
+ return None
52
+
53
+
54
+ # ---------------------------------------------------------------------------
55
+ # Device Flow — browser-based GitHub OAuth (no IDE required)
56
+ # ---------------------------------------------------------------------------
57
+
58
+
59
+ def start_device_flow(host: str = "github.com") -> dict[str, Any | None]:
60
+ """Initiate the GitHub Device Flow and return the device code response.
61
+
62
+ Returns a dict with ``device_code``, ``user_code``, ``verification_uri``,
63
+ ``expires_in``, and ``interval`` on success, or ``None`` on failure.
64
+ """
65
+ url = DEVICE_FLOW_CONFIG["device_code_url"].format(host=host)
66
+ payload = {
67
+ "client_id": DEVICE_FLOW_CONFIG["client_id"],
68
+ "scope": DEVICE_FLOW_CONFIG["scope"],
69
+ }
70
+ headers = {"Accept": "application/json"}
71
+ try:
72
+ resp = httpx.post(url, data=payload, headers=headers, timeout=30)
73
+ if resp.status_code == 200:
74
+ data = resp.json()
75
+ if data.get("device_code") and data.get("user_code"):
76
+ return data
77
+ logger.warning("Device flow response missing required fields: %s", data)
78
+ else:
79
+ logger.warning(
80
+ "Device flow initiation failed: %s %s",
81
+ resp.status_code,
82
+ resp.text[:300],
83
+ )
84
+ except Exception as exc:
85
+ logger.warning("Device flow error: %s", exc)
86
+ return None
87
+
88
+
89
+ def poll_for_token(
90
+ device_code: str,
91
+ host: str = "github.com",
92
+ interval: int = 5,
93
+ expires_in: int = 900,
94
+ ) -> str | None:
95
+ """Poll GitHub until the user completes the Device Flow authorization.
96
+
97
+ Returns the OAuth ``access_token`` on success, or ``None`` on timeout/denial.
98
+ """
99
+ url = DEVICE_FLOW_CONFIG["access_token_url"].format(host=host)
100
+ payload = {
101
+ "client_id": DEVICE_FLOW_CONFIG["client_id"],
102
+ "device_code": device_code,
103
+ "grant_type": DEVICE_FLOW_CONFIG["grant_type"],
104
+ }
105
+ headers = {"Accept": "application/json"}
106
+
107
+ deadline = time.time() + expires_in
108
+ poll_interval = max(interval, DEVICE_FLOW_CONFIG["default_poll_interval"])
109
+
110
+ while time.time() < deadline:
111
+ time.sleep(poll_interval)
112
+ try:
113
+ resp = httpx.post(url, data=payload, headers=headers, timeout=30)
114
+ data = resp.json() if resp.status_code == 200 else {}
115
+ except Exception as exc:
116
+ logger.warning("Device flow poll error: %s", exc)
117
+ continue
118
+
119
+ token = data.get("access_token")
120
+ if token:
121
+ return token
122
+
123
+ error = data.get("error", "")
124
+ if error == "authorization_pending":
125
+ continue
126
+ if error == "slow_down":
127
+ poll_interval += 5
128
+ continue
129
+ if error in ("expired_token", "access_denied", "unsupported_grant_type"):
130
+ logger.warning("Device flow denied or expired: %s", error)
131
+ return None
132
+ # Unknown error — keep trying until deadline
133
+ logger.debug("Device flow poll returned: %s", data)
134
+
135
+ return None
136
+
137
+
138
+ def save_device_token(host: str, oauth_token: str, user: str = "") -> bool:
139
+ """Persist a token obtained via the Device Flow to disk."""
140
+ try:
141
+ path = get_device_token_storage_path()
142
+ data: dict[str, Any] = {}
143
+ if path.exists():
144
+ with open(path, encoding="utf-8") as fh:
145
+ data = json.load(fh)
146
+ data[host] = {
147
+ "oauth_token": oauth_token,
148
+ "user": user,
149
+ "created_at": time.time(),
150
+ }
151
+ with open(path, "w", encoding="utf-8") as fh:
152
+ json.dump(data, fh, indent=2)
153
+ path.chmod(0o600)
154
+ return True
155
+ except Exception as exc:
156
+ logger.error("Failed to save device token: %s", exc)
157
+ return False
158
+
159
+
160
+ def load_device_tokens() -> list[CopilotToken]:
161
+ """Load tokens previously obtained via the Device Flow."""
162
+ tokens: list[CopilotToken] = []
163
+ try:
164
+ path = get_device_token_storage_path()
165
+ if not path.exists():
166
+ return tokens
167
+ with open(path, encoding="utf-8") as fh:
168
+ data = json.load(fh)
169
+ if isinstance(data, dict):
170
+ for host, entry in data.items():
171
+ if not isinstance(entry, dict):
172
+ continue
173
+ oauth_token = entry.get("oauth_token")
174
+ if oauth_token:
175
+ tokens.append(
176
+ CopilotToken(
177
+ host=host,
178
+ oauth_token=oauth_token,
179
+ user=entry.get("user", ""),
180
+ )
181
+ )
182
+ except Exception as exc:
183
+ logger.warning("Failed to load device tokens: %s", exc)
184
+ return tokens
185
+
186
+
187
+ # ---------------------------------------------------------------------------
188
+ # Session token exchange — converts long-lived OAuth token → short-lived
189
+ # Copilot API bearer token (typically valid ~30 min).
190
+ # ---------------------------------------------------------------------------
191
+
192
+
193
+ @dataclass
194
+ class SessionToken:
195
+ """A short-lived Copilot API session token."""
196
+
197
+ token: str
198
+ expires_at: float # Unix timestamp
199
+ api_endpoint: str = (
200
+ "" # API base URL from the token response (may differ per region/host)
201
+ )
202
+
203
+
204
+ # Module-level cache keyed by (host, oauth_token[:16])
205
+ _session_cache: dict[str, SessionToken] = {}
206
+
207
+ # Stores the API base URL returned by the most recent session-token exchange
208
+ # per host, so that model registration can use it.
209
+ _host_api_endpoints: dict[str, str] = {}
210
+
211
+
212
+ def _cache_key(oauth_token: str, host: str) -> str:
213
+ return f"{host}:{oauth_token[:16]}"
214
+
215
+
216
+ def _token_endpoint(host: str) -> str:
217
+ """Return the Copilot session-token endpoint for the given host."""
218
+ if host == "github.com":
219
+ return COPILOT_AUTH_CONFIG["github_token_url"]
220
+ return COPILOT_AUTH_CONFIG["ghe_token_url_template"].format(host=host)
221
+
222
+
223
+ def exchange_for_session_token(
224
+ oauth_token: str, host: str = "github.com"
225
+ ) -> SessionToken | None:
226
+ """Exchange a GitHub OAuth token for a short-lived Copilot session token.
227
+
228
+ The response ``endpoints.api`` value is the correct API base URL for this
229
+ token and may differ from the default (e.g. regional or GHE deployments).
230
+ """
231
+ url = _token_endpoint(host)
232
+ headers = {
233
+ # TODO: PEP 750 t-string — use templatelib when stable
234
+ "Authorization": f"token {oauth_token}",
235
+ "Accept": "application/json",
236
+ "Editor-Version": COPILOT_AUTH_CONFIG["editor_version"],
237
+ "Editor-Plugin-Version": COPILOT_AUTH_CONFIG["editor_plugin_version"],
238
+ "Copilot-Integration-Id": COPILOT_AUTH_CONFIG["copilot_integration_id"],
239
+ }
240
+ try:
241
+ resp = httpx.get(url, headers=headers, timeout=30)
242
+ if resp.status_code == 200:
243
+ data = resp.json()
244
+ token_str = data.get("token")
245
+ expires_at = data.get("expires_at", 0)
246
+ if token_str:
247
+ # Extract the API endpoint — the token is only valid against
248
+ # this specific URL. Falls back to the default if missing.
249
+ endpoints = data.get("endpoints") or {}
250
+ api_endpoint = (
251
+ endpoints.get("api", "").rstrip("/")
252
+ or COPILOT_AUTH_CONFIG["api_base_url"]
253
+ )
254
+ # Remember this for model registration
255
+ _host_api_endpoints[host] = api_endpoint
256
+ logger.debug(
257
+ "Copilot session for %s: api_endpoint=%s", host, api_endpoint
258
+ )
259
+
260
+ st = SessionToken(
261
+ token=token_str,
262
+ expires_at=float(expires_at),
263
+ api_endpoint=api_endpoint,
264
+ )
265
+ # Persist to disk so we survive process restarts within the window
266
+ _persist_session(st, host, oauth_token)
267
+ return st
268
+ logger.warning("Token endpoint returned 200 but no 'token' field")
269
+ elif resp.status_code == 401:
270
+ logger.warning(
271
+ "Copilot token exchange returned 401 — OAuth token may be revoked."
272
+ )
273
+ else:
274
+ logger.warning(
275
+ "Copilot token exchange failed: %s %s",
276
+ resp.status_code,
277
+ resp.text[:200],
278
+ )
279
+ except httpx.TimeoutException:
280
+ logger.warning("Timeout exchanging Copilot token for host %s", host)
281
+ except Exception as exc:
282
+ logger.warning("Copilot token exchange error: %s", exc)
283
+ return None
284
+
285
+
286
+ def _persist_session(st: SessionToken, host: str, oauth_token: str = "") -> None:
287
+ """Write session token to disk for reuse after restarts.
288
+
289
+ Stores a fingerprint of the OAuth token so that a persisted session is
290
+ only reused when the same OAuth token is still active.
291
+ """
292
+ try:
293
+ path = get_session_cache_path()
294
+ data: dict[str, Any] = {}
295
+ if path.exists():
296
+ with open(path, encoding="utf-8") as fh:
297
+ data = json.load(fh)
298
+ data[host] = {
299
+ "token": st.token,
300
+ "expires_at": st.expires_at,
301
+ "api_endpoint": st.api_endpoint,
302
+ "oauth_fingerprint": oauth_token[:16] if oauth_token else "",
303
+ }
304
+ with open(path, "w", encoding="utf-8") as fh:
305
+ json.dump(data, fh, indent=2)
306
+ path.chmod(0o600)
307
+ except Exception as exc:
308
+ logger.debug("Could not persist session token: %s", exc)
309
+
310
+
311
+ def _load_persisted_session(host: str, oauth_token: str = "") -> SessionToken | None:
312
+ """Load a previously persisted session token from disk.
313
+
314
+ If *oauth_token* is provided, the persisted entry is only returned when
315
+ its stored fingerprint matches ``oauth_token[:16]``, preventing
316
+ cross-account reuse after a re-login.
317
+ """
318
+ try:
319
+ path = get_session_cache_path()
320
+ if not path.exists():
321
+ return None
322
+ with open(path, encoding="utf-8") as fh:
323
+ data = json.load(fh)
324
+ entry = data.get(host)
325
+ if entry:
326
+ # Verify the stored fingerprint matches the current OAuth token
327
+ stored_fp = entry.get("oauth_fingerprint", "")
328
+ if oauth_token and stored_fp and stored_fp != oauth_token[:16]:
329
+ logger.debug(
330
+ "Persisted session fingerprint mismatch for %s — skipping", host
331
+ )
332
+ return None
333
+
334
+ api_endpoint = entry.get(
335
+ "api_endpoint", COPILOT_AUTH_CONFIG["api_base_url"]
336
+ )
337
+ # Restore the host→endpoint mapping
338
+ if api_endpoint:
339
+ _host_api_endpoints[host] = api_endpoint
340
+ return SessionToken(
341
+ token=entry["token"],
342
+ expires_at=float(entry["expires_at"]),
343
+ api_endpoint=api_endpoint,
344
+ )
345
+ except Exception as exc:
346
+ logger.debug("Could not load persisted session: %s", exc)
347
+ return None
348
+
349
+
350
+ def get_valid_session_token(oauth_token: str, host: str = "github.com") -> str | None:
351
+ """Return a valid Copilot session token, refreshing if needed.
352
+
353
+ Checks in-memory cache → on-disk cache → exchanges for a new one.
354
+ Returns the raw bearer token string or ``None``.
355
+ """
356
+ key = _cache_key(oauth_token, host)
357
+
358
+ # 1) In-memory cache
359
+ cached = _session_cache.get(key)
360
+ if cached and cached.expires_at > time.time() + 60:
361
+ # Ensure host→endpoint mapping is populated
362
+ if cached.api_endpoint:
363
+ _host_api_endpoints[host] = cached.api_endpoint
364
+ return cached.token
365
+
366
+ # 2) On-disk cache (with fingerprint verification)
367
+ persisted = _load_persisted_session(host, oauth_token)
368
+ if persisted and persisted.expires_at > time.time() + 60:
369
+ _session_cache[key] = persisted
370
+ return persisted.token
371
+
372
+ # 3) Exchange
373
+ new_token = exchange_for_session_token(oauth_token, host)
374
+ if new_token:
375
+ _session_cache[key] = new_token
376
+ return new_token.token
377
+
378
+ return None
379
+
380
+
381
+ def get_api_endpoint_for_host(host: str) -> str:
382
+ """Return the Copilot API base URL for the given host.
383
+
384
+ The correct endpoint is discovered during session-token exchange
385
+ (the ``endpoints.api`` field in the response). Falls back to the
386
+ default ``api.githubcopilot.com`` if no exchange has happened yet.
387
+ """
388
+ return _host_api_endpoints.get(host, COPILOT_AUTH_CONFIG["api_base_url"])
389
+
390
+
391
+ def clear_caches() -> None:
392
+ """Reset all in-memory session and endpoint caches.
393
+
394
+ Called by ``/copilot-logout`` to ensure no stale Copilot state remains.
395
+ """
396
+ _session_cache.clear()
397
+ _host_api_endpoints.clear()
398
+
399
+
400
+ # ---------------------------------------------------------------------------
401
+ # Model persistence — copilot_models.json
402
+ # ---------------------------------------------------------------------------
403
+
404
+
405
+ def load_copilot_models() -> dict[str, Any]:
406
+ """Load registered Copilot models from copilot_models.json."""
407
+ try:
408
+ path = get_copilot_models_path()
409
+ if path.exists():
410
+ with open(path, encoding="utf-8") as fh:
411
+ data = json.load(fh)
412
+ if isinstance(data, dict):
413
+ return data
414
+ logger.warning("copilot_models.json is not a JSON object — ignoring")
415
+ except Exception as exc:
416
+ logger.error("Failed to load Copilot models: %s", exc)
417
+ return {}
418
+
419
+
420
+ def save_copilot_models(models: dict[str, Any]) -> bool:
421
+ """Persist Copilot models to copilot_models.json."""
422
+ try:
423
+ path = get_copilot_models_path()
424
+ with open(path, "w", encoding="utf-8") as fh:
425
+ json.dump(models, fh, indent=2)
426
+ return True
427
+ except Exception as exc:
428
+ logger.error("Failed to save Copilot models: %s", exc)
429
+ return False
430
+
431
+
432
+ def remove_copilot_models() -> int:
433
+ """Remove all Copilot-sourced models from copilot_models.json."""
434
+ try:
435
+ models = load_copilot_models()
436
+ to_remove = [
437
+ name
438
+ for name, cfg in models.items()
439
+ if cfg.get("oauth_source") == "copilot-auth-plugin"
440
+ ]
441
+ for name in to_remove:
442
+ models.pop(name, None)
443
+ if save_copilot_models(models):
444
+ return len(to_remove)
445
+ except Exception as exc:
446
+ logger.error("Error removing Copilot models: %s", exc)
447
+ return 0
448
+
449
+
450
+ def fetch_copilot_models(session_token: str, host: str = "github.com") -> list[str]:
451
+ """Try to fetch the model catalogue from the Copilot API.
452
+
453
+ Falls back to ``DEFAULT_COPILOT_MODELS`` if the endpoint is unavailable.
454
+ Uses the host-specific API endpoint discovered during token exchange.
455
+ """
456
+ api_base = get_api_endpoint_for_host(host)
457
+ url = f"{api_base}/models"
458
+ headers = {
459
+ "Authorization": f"Bearer {session_token}",
460
+ "Accept": "application/json",
461
+ "Editor-Version": COPILOT_AUTH_CONFIG["editor_version"],
462
+ "Editor-Plugin-Version": COPILOT_AUTH_CONFIG["editor_plugin_version"],
463
+ "Copilot-Integration-Id": COPILOT_AUTH_CONFIG["copilot_integration_id"],
464
+ "Openai-Intent": COPILOT_AUTH_CONFIG["openai_intent"],
465
+ }
466
+ try:
467
+ resp = httpx.get(url, headers=headers, timeout=15)
468
+ if resp.status_code == 200:
469
+ data = resp.json()
470
+ model_list = data.get("data") or data.get("models") or []
471
+ if isinstance(model_list, list):
472
+ ids = []
473
+ for m in model_list:
474
+ if isinstance(m, dict):
475
+ mid = m.get("id") or m.get("name")
476
+ if mid:
477
+ ids.append(mid)
478
+ elif isinstance(m, str):
479
+ ids.append(m)
480
+ if ids:
481
+ logger.info("Fetched %d models from Copilot API", len(ids))
482
+ return ids
483
+ except Exception as exc:
484
+ logger.debug("Could not fetch Copilot model list: %s", exc)
485
+
486
+ logger.info("Using default Copilot model list")
487
+ return DEFAULT_COPILOT_MODELS
488
+
489
+
490
+ def _is_claude_model(model_name: str) -> bool:
491
+ """Check if a model name refers to a Claude/Anthropic model."""
492
+ return model_name.lower().startswith("claude-")
493
+
494
+
495
+ def _is_openai_model(model_name: str) -> bool:
496
+ """Check if a model name refers to an OpenAI/GPT model."""
497
+ lower = model_name.lower()
498
+ return lower.startswith("gpt-") or lower.startswith("o3") or lower.startswith("o4")
499
+
500
+
501
+ def _build_claude_model_settings(model_name: str) -> dict[str, Any]:
502
+ """Build model_settings fields for a Claude model.
503
+
504
+ Mirrors the conventions in the claude_code_oauth plugin's
505
+ ``_build_model_entry``.
506
+ """
507
+ supported_settings = [
508
+ "temperature",
509
+ "extended_thinking",
510
+ "budget_tokens",
511
+ "interleaved_thinking",
512
+ ]
513
+
514
+ # Opus 4-6 (e.g. claude-opus-4.6) supports the effort setting,
515
+ # same as the claude_code_oauth plugin.
516
+ lower = model_name.lower()
517
+ if "opus-4.6" in lower or "opus-4-6" in lower or "4-6-opus" in lower:
518
+ supported_settings.append("effort")
519
+
520
+ return {"supported_settings": supported_settings}
521
+
522
+
523
+ def _build_openai_model_settings(model_name: str) -> dict[str, Any]:
524
+ """Build model_settings fields for an OpenAI/GPT model behind Copilot.
525
+
526
+ The Copilot API only proxies a subset of OpenAI features. Currently
527
+ none of the GPT models exposed through Copilot support
528
+ ``reasoning_effort``, ``summary``, or ``verbosity`` — sending those
529
+ parameters results in a 400 Bad Request. Only basic ``temperature``
530
+ is safe.
531
+ """
532
+ return {"supported_settings": ["temperature"]}
533
+
534
+
535
+ def _model_settings_for(model_name: str) -> dict[str, Any]:
536
+ """Return family-specific config fields for a Copilot model.
537
+
538
+ Claude models get extended_thinking/effort/etc.; OpenAI models get
539
+ reasoning_effort/summary/verbosity; everything else gets temperature.
540
+ """
541
+ if _is_claude_model(model_name):
542
+ return _build_claude_model_settings(model_name)
543
+ if _is_openai_model(model_name):
544
+ return _build_openai_model_settings(model_name)
545
+ # Fallback for other providers (Gemini, etc.)
546
+ return {"supported_settings": ["temperature"]}
547
+
548
+
549
+ def add_models_to_config(
550
+ models: list[str],
551
+ host: str = "github.com",
552
+ ) -> bool:
553
+ """Register Copilot models in copilot_models.json."""
554
+ try:
555
+ copilot_models = load_copilot_models()
556
+ added = 0
557
+ prefix = COPILOT_AUTH_CONFIG["prefix"]
558
+ # Use the API endpoint from session-token exchange (critical for GHE).
559
+ api_url = get_api_endpoint_for_host(host)
560
+ for model_name in models:
561
+ prefixed = f"{prefix}{model_name}"
562
+ entry: dict[str, Any] = {
563
+ "type": "copilot",
564
+ "name": model_name,
565
+ "custom_endpoint": {
566
+ "url": api_url,
567
+ },
568
+ "context_length": COPILOT_MODEL_CONTEXT_LENGTHS.get(
569
+ model_name,
570
+ COPILOT_AUTH_CONFIG["default_context_length"],
571
+ ),
572
+ "copilot_host": host,
573
+ "oauth_source": "copilot-auth-plugin",
574
+ }
575
+ # Merge family-specific settings (supported_settings, etc.)
576
+ entry.update(_model_settings_for(model_name))
577
+ copilot_models[prefixed] = entry
578
+ added += 1
579
+ if save_copilot_models(copilot_models):
580
+ logger.info("Registered %d Copilot models (api: %s)", added, api_url)
581
+ return True
582
+ except Exception as exc:
583
+ logger.error("Error adding Copilot models to config: %s", exc)
584
+ return False
@@ -0,0 +1,14 @@
1
+ """Custom commands plugin — TOML-defined shortcuts with {{args}} injection."""
2
+
3
+ from code_muse.plugins.custom_commands.command_discovery import (
4
+ CommandDef as CommandDef,
5
+ )
6
+ from code_muse.plugins.custom_commands.command_discovery import (
7
+ discover_commands as discover_commands,
8
+ )
9
+ from code_muse.plugins.custom_commands.command_toml_schema import (
10
+ parse_command_toml as parse_command_toml,
11
+ )
12
+ from code_muse.plugins.custom_commands.register_callbacks import (
13
+ CustomCommandResult as CustomCommandResult,
14
+ )
@@ -0,0 +1,82 @@
1
+ import re
2
+
3
+ _ARGS_PLACEHOLDER = "{{args}}"
4
+
5
+ _SHELL_BLOCK_RE = re.compile(r"```(?:bash|shell)", re.IGNORECASE)
6
+ _RUN_SHELL_CMD_RE = re.compile(r"agent_run_shell_command", re.IGNORECASE)
7
+
8
+ # Mapping of command prefix → flag to auto-append
9
+ _AUTO_FLAGS: list[tuple[list[str], str]] = [
10
+ (["npm", "install"], "--silent"),
11
+ (["git"], "--no-pager"),
12
+ (["pnpm"], "--silent"),
13
+ (["cargo"], "--quiet"),
14
+ (["pip", "install"], "--quiet"),
15
+ (["yarn"], "--silent"),
16
+ ]
17
+
18
+ # Flags that indicate a command already has an efficiency flag set
19
+ _EFFICIENCY_FLAGS = {"--silent", "--quiet", "--no-pager"}
20
+
21
+
22
+ def inject_args(prompt: str, args: str) -> str:
23
+ """Replace every occurrence of ``{{args}}`` with *args*.
24
+
25
+ If *args* is empty, ``{{args}}`` is replaced with an empty string.
26
+ """
27
+ return prompt.replace(_ARGS_PLACEHOLDER, args)
28
+
29
+
30
+ def detect_shell_blocks(prompt: str) -> bool:
31
+ """Return ``True`` if *prompt* contains shell-related constructs."""
32
+ return bool(_SHELL_BLOCK_RE.search(prompt) or _RUN_SHELL_CMD_RE.search(prompt))
33
+
34
+
35
+ def auto_flag_shell_command(command: str) -> str:
36
+ """Append efficiency flags to known shell commands when missing.
37
+
38
+ Only appends a flag if the command is recognized and the flag is
39
+ not already present anywhere in the command string.
40
+ """
41
+ stripped = command.strip()
42
+ if not stripped:
43
+ return command
44
+
45
+ # If any efficiency flag is already present, leave the command alone
46
+ if any(flag in stripped for flag in _EFFICIENCY_FLAGS):
47
+ return command
48
+
49
+ parts = stripped.split()
50
+ if not parts:
51
+ return command
52
+
53
+ for prefixes, flag in _AUTO_FLAGS:
54
+ if len(parts) >= len(prefixes) and parts[: len(prefixes)] == prefixes:
55
+ return stripped + " " + flag
56
+
57
+ return command
58
+
59
+
60
+ _FENCED_BLOCK_RE = re.compile(
61
+ r"(```(?:bash|shell)\n)(.*?)(\n```)",
62
+ re.IGNORECASE | re.DOTALL,
63
+ )
64
+
65
+
66
+ def _process_block(match: re.Match[str]) -> str:
67
+ """Process a single fenced shell block: apply flags to each line."""
68
+ prefix = match.group(1)
69
+ body = match.group(2)
70
+ suffix = match.group(3)
71
+
72
+ lines = body.split("\n")
73
+ processed = [auto_flag_shell_command(line) for line in lines]
74
+ return prefix + "\n".join(processed) + suffix
75
+
76
+
77
+ def apply_shell_flags(prompt: str) -> str:
78
+ """Find fenced shell blocks and apply efficiency flags to each line.
79
+
80
+ Returns the prompt with shell commands updated in-place.
81
+ """
82
+ return _FENCED_BLOCK_RE.sub(_process_block, prompt)