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,674 @@
1
+ """Interactive terminal UI for selecting agents.
2
+
3
+ Provides a split-panel interface for browsing and selecting agents
4
+ with live preview of agent details.
5
+ """
6
+
7
+ import asyncio
8
+ import sys
9
+ import unicodedata
10
+
11
+ from prompt_toolkit.application import Application
12
+ from prompt_toolkit.key_binding import KeyBindings
13
+ from prompt_toolkit.layout import Dimension, Layout, VSplit, Window
14
+ from prompt_toolkit.layout.controls import FormattedTextControl
15
+ from prompt_toolkit.widgets import Frame
16
+
17
+ from code_muse.agents import (
18
+ clone_agent,
19
+ delete_clone_agent,
20
+ get_agent_descriptions,
21
+ get_available_agents,
22
+ get_current_agent,
23
+ is_clone_agent_name,
24
+ )
25
+ from code_muse.command_line.model_picker_completion import load_model_names
26
+ from code_muse.command_line.pagination import (
27
+ ensure_visible_page,
28
+ get_page_bounds,
29
+ get_page_for_index,
30
+ get_total_pages,
31
+ )
32
+ from code_muse.config import (
33
+ clear_agent_pinned_model,
34
+ get_agent_pinned_model,
35
+ set_agent_pinned_model,
36
+ )
37
+ from code_muse.messaging import emit_info, emit_success, emit_warning
38
+ from code_muse.tools.command_runner import set_awaiting_user_input
39
+ from code_muse.tools.common import arrow_select_async
40
+
41
+ PAGE_SIZE = 10 # Agents per page
42
+
43
+
44
+ def _sanitize_display_text(text: str) -> str:
45
+ """Remove or replace characters that cause terminal rendering issues.
46
+
47
+ Args:
48
+ text: Text that may contain emojis or wide characters
49
+
50
+ Returns:
51
+ Sanitized text safe for prompt_toolkit rendering
52
+ """
53
+ # Keep only characters that render cleanly in terminals
54
+ # Be aggressive about stripping anything that could cause width issues
55
+ result = []
56
+ for char in text:
57
+ # Get unicode category
58
+ cat = unicodedata.category(char)
59
+ # Categories to KEEP:
60
+ # - L* (Letters): Lu, Ll, Lt, Lm, Lo
61
+ # - N* (Numbers): Nd, Nl, No
62
+ # - P* (Punctuation): Pc, Pd, Ps, Pe, Pi, Pf, Po
63
+ # - Zs (Space separator)
64
+ # - Sm (Math symbols like +, -, =)
65
+ # - Sc (Currency symbols like $, €)
66
+ # - Sk (Modifier symbols)
67
+ #
68
+ # Categories to SKIP (cause rendering issues):
69
+ # - So (Symbol, other) - emojis
70
+ # - Cf (Format) - ZWJ, etc.
71
+ # - Mn (Mark, nonspacing) - combining characters
72
+ # - Mc (Mark, spacing combining)
73
+ # - Me (Mark, enclosing)
74
+ # - Cn (Not assigned)
75
+ # - Co (Private use)
76
+ # - Cs (Surrogate)
77
+ safe_categories = (
78
+ "Lu",
79
+ "Ll",
80
+ "Lt",
81
+ "Lm",
82
+ "Lo", # Letters
83
+ "Nd",
84
+ "Nl",
85
+ "No", # Numbers
86
+ "Pc",
87
+ "Pd",
88
+ "Ps",
89
+ "Pe",
90
+ "Pi",
91
+ "Pf",
92
+ "Po", # Punctuation
93
+ "Zs", # Space
94
+ "Sm",
95
+ "Sc",
96
+ "Sk", # Safe symbols (math, currency, modifier)
97
+ )
98
+ if cat in safe_categories:
99
+ result.append(char)
100
+
101
+ # Clean up any double spaces left behind and strip
102
+ cleaned = " ".join("".join(result).split())
103
+ return cleaned
104
+
105
+
106
+ def _get_pinned_model(agent_name: str) -> str | None:
107
+ """Return the pinned model for an agent, if any.
108
+
109
+ Checks both built-in agent config and JSON agent files.
110
+ """
111
+ import json
112
+
113
+ # First check built-in agent config
114
+ try:
115
+ pinned = get_agent_pinned_model(agent_name)
116
+ if pinned:
117
+ return pinned
118
+ except Exception:
119
+ pass # Continue to check JSON agents
120
+
121
+ # Check if it's a JSON agent
122
+ try:
123
+ from code_muse.agents.json_agent import discover_json_agents
124
+
125
+ json_agents = discover_json_agents()
126
+ if agent_name in json_agents:
127
+ agent_file_path = json_agents[agent_name]
128
+ with open(agent_file_path, encoding="utf-8") as f:
129
+ agent_config = json.load(f)
130
+ model = agent_config.get("model")
131
+ return model if model else None
132
+ except Exception:
133
+ pass # Return None if we can't read the JSON file
134
+
135
+ return None
136
+
137
+
138
+ def _build_model_picker_choices(
139
+ pinned_model: str | None,
140
+ model_names: list[str],
141
+ ) -> list[str]:
142
+ """Build model picker choices with pinned/unpin indicators."""
143
+ choices = ["✓ (unpin)" if not pinned_model else " (unpin)"]
144
+
145
+ for model_name in model_names:
146
+ if model_name == pinned_model:
147
+ choices.append(f"✓ {model_name} (pinned)")
148
+ else:
149
+ choices.append(f" {model_name}")
150
+
151
+ return choices
152
+
153
+
154
+ def _normalize_model_choice(choice: str) -> str:
155
+ """Normalize a picker choice into a model name or '(unpin)' string."""
156
+ cleaned = choice.strip()
157
+ if cleaned.startswith("✓"):
158
+ cleaned = cleaned.lstrip("✓").strip()
159
+ if cleaned.endswith(" (pinned)"):
160
+ cleaned = cleaned[: -len(" (pinned)")].strip()
161
+ return cleaned
162
+
163
+
164
+ async def _select_pinned_model(agent_name: str) -> str | None:
165
+ """Prompt for a model to pin to the agent."""
166
+ try:
167
+ model_names = load_model_names() or []
168
+ except Exception as exc:
169
+ emit_warning(f"Failed to load models: {exc}")
170
+ return None
171
+
172
+ pinned_model = _get_pinned_model(agent_name)
173
+ choices = _build_model_picker_choices(pinned_model, model_names)
174
+ if not choices:
175
+ emit_warning("No models available to pin.")
176
+ return None
177
+
178
+ try:
179
+ choice = await arrow_select_async(
180
+ f"Select a model to pin for '{agent_name}'",
181
+ choices,
182
+ )
183
+ except KeyboardInterrupt:
184
+ emit_info("Model pinning cancelled")
185
+ return None
186
+
187
+ return _normalize_model_choice(choice)
188
+
189
+
190
+ def _reload_agent_if_current(
191
+ agent_name: str,
192
+ pinned_model: str | None,
193
+ ) -> None:
194
+ """Reload the current agent when its pinned model changes."""
195
+ current_agent = get_current_agent()
196
+ if not current_agent or current_agent.name != agent_name:
197
+ return
198
+
199
+ try:
200
+ if hasattr(current_agent, "refresh_config"):
201
+ current_agent.refresh_config()
202
+ current_agent.reload_code_generation_agent()
203
+ if pinned_model:
204
+ emit_info(f"Active agent reloaded with pinned model '{pinned_model}'")
205
+ else:
206
+ emit_info("Active agent reloaded with default model")
207
+ except Exception as exc:
208
+ emit_warning(f"Pinned model applied but reload failed: {exc}")
209
+
210
+
211
+ def _apply_pinned_model(agent_name: str, model_choice: str) -> None:
212
+ """Persist a pinned model selection for an agent.
213
+
214
+ Handles both built-in agents (via config) and JSON agents (via JSON file).
215
+ """
216
+ import json
217
+
218
+ # Check if this is a JSON agent or a built-in agent
219
+ try:
220
+ from code_muse.agents.json_agent import discover_json_agents
221
+
222
+ json_agents = discover_json_agents()
223
+ is_json_agent = agent_name in json_agents
224
+ except Exception:
225
+ is_json_agent = False
226
+
227
+ try:
228
+ if is_json_agent:
229
+ # Handle JSON agent - modify the JSON file
230
+ agent_file_path = json_agents[agent_name]
231
+
232
+ with open(agent_file_path, encoding="utf-8") as f:
233
+ agent_config = json.load(f)
234
+
235
+ if model_choice == "(unpin)":
236
+ # Remove the model key if it exists
237
+ if "model" in agent_config:
238
+ del agent_config["model"]
239
+ emit_success(f"Model pin cleared for '{agent_name}'")
240
+ pinned_model = None
241
+ else:
242
+ # Set the model
243
+ agent_config["model"] = model_choice
244
+ emit_success(f"Pinned '{model_choice}' to '{agent_name}'")
245
+ pinned_model = model_choice
246
+
247
+ # Save the updated configuration
248
+ with open(agent_file_path, "w", encoding="utf-8") as f:
249
+ json.dump(agent_config, f, indent=2, ensure_ascii=False)
250
+ else:
251
+ # Handle built-in Python agent - use config functions
252
+ if model_choice == "(unpin)":
253
+ clear_agent_pinned_model(agent_name)
254
+ emit_success(f"Model pin cleared for '{agent_name}'")
255
+ pinned_model = None
256
+ else:
257
+ set_agent_pinned_model(agent_name, model_choice)
258
+ emit_success(f"Pinned '{model_choice}' to '{agent_name}'")
259
+ pinned_model = model_choice
260
+
261
+ _reload_agent_if_current(agent_name, pinned_model)
262
+ except Exception as exc:
263
+ emit_warning(f"Failed to apply pinned model: {exc}")
264
+
265
+
266
+ def _get_agent_entries() -> list[tuple[str, str, str]]:
267
+ """Get all agents with their display names and descriptions.
268
+
269
+ Returns:
270
+ list of tuples (agent_name, display_name, description) sorted by name.
271
+ """
272
+ available = get_available_agents()
273
+ descriptions = get_agent_descriptions()
274
+
275
+ entries = []
276
+ for name, display_name in available.items():
277
+ description = descriptions.get(name, "No description available")
278
+ entries.append((name, display_name, description))
279
+
280
+ # Sort alphabetically by agent name
281
+ entries.sort(key=lambda x: x[0].lower())
282
+ return entries
283
+
284
+
285
+ def _render_menu_panel(
286
+ entries: list[tuple[str, str, str]],
287
+ page: int,
288
+ selected_idx: int,
289
+ current_agent_name: str,
290
+ ) -> list:
291
+ """Render the left menu panel with pagination.
292
+
293
+ Args:
294
+ entries: list of (name, display_name, description) tuples
295
+ page: Current page number (0-indexed)
296
+ selected_idx: Currently selected index (global)
297
+ current_agent_name: Name of the current active agent
298
+
299
+ Returns:
300
+ list of (style, text) tuples for FormattedTextControl
301
+ """
302
+ lines = []
303
+ total_pages = get_total_pages(len(entries), PAGE_SIZE)
304
+ start_idx, end_idx = get_page_bounds(page, len(entries), PAGE_SIZE)
305
+
306
+ lines.append(("bold", "Agents"))
307
+ lines.append(("fg:ansibrightblack", f" (Page {page + 1}/{total_pages})"))
308
+ lines.append(("", "\n\n"))
309
+
310
+ if not entries:
311
+ lines.append(("fg:yellow", " No agents found."))
312
+ lines.append(("", "\n\n"))
313
+ else:
314
+ # Show agents for current page
315
+ for i in range(start_idx, end_idx):
316
+ name, display_name, _ = entries[i]
317
+ is_selected = i == selected_idx
318
+ is_current = name == current_agent_name
319
+ pinned_model = _get_pinned_model(name)
320
+
321
+ # Sanitize display name to avoid emoji rendering issues
322
+ safe_display_name = _sanitize_display_text(display_name)
323
+
324
+ # Build the line
325
+ if is_selected:
326
+ lines.append(("fg:ansigreen", "▶ "))
327
+ lines.append(("fg:ansigreen bold", safe_display_name))
328
+ else:
329
+ lines.append(("", " "))
330
+ lines.append(("", safe_display_name))
331
+
332
+ if pinned_model:
333
+ safe_pinned_model = _sanitize_display_text(pinned_model)
334
+ lines.append(("fg:ansiyellow", f" → {safe_pinned_model}"))
335
+
336
+ # Add current marker
337
+ if is_current:
338
+ lines.append(("fg:ansicyan", " ← current"))
339
+
340
+ lines.append(("", "\n"))
341
+
342
+ # Navigation hints
343
+ lines.append(("", "\n"))
344
+ lines.append(("fg:ansibrightblack", " ↑↓ "))
345
+ lines.append(("", "Navigate\n"))
346
+ lines.append(("fg:ansibrightblack", " ←→ "))
347
+ lines.append(("", "Page\n"))
348
+ lines.append(("fg:green", " Enter "))
349
+ lines.append(("", "Select\n"))
350
+ lines.append(("fg:ansibrightblack", " P "))
351
+ lines.append(("", "Pin model\n"))
352
+ lines.append(("fg:ansibrightblack", " C "))
353
+ lines.append(("", "Clone\n"))
354
+ lines.append(("fg:ansibrightblack", " D "))
355
+ lines.append(("", "Delete clone\n"))
356
+ lines.append(("fg:ansibrightred", " Ctrl+C "))
357
+ lines.append(("", "Cancel"))
358
+
359
+ return lines
360
+
361
+
362
+ def _render_preview_panel(
363
+ entry: tuple[str, str, str | None],
364
+ current_agent_name: str,
365
+ ) -> list:
366
+ """Render the right preview panel with agent details.
367
+
368
+ Args:
369
+ entry: Tuple of (name, display_name, description) or None
370
+ current_agent_name: Name of the current active agent
371
+
372
+ Returns:
373
+ list of (style, text) tuples for FormattedTextControl
374
+ """
375
+ lines = []
376
+
377
+ lines.append(("dim cyan", " AGENT DETAILS"))
378
+ lines.append(("", "\n\n"))
379
+
380
+ if not entry:
381
+ lines.append(("fg:yellow", " No agent selected."))
382
+ lines.append(("", "\n"))
383
+ return lines
384
+
385
+ name, display_name, description = entry
386
+ is_current = name == current_agent_name
387
+ pinned_model = _get_pinned_model(name)
388
+
389
+ # Sanitize text to avoid emoji rendering issues
390
+ safe_display_name = _sanitize_display_text(display_name)
391
+ safe_description = _sanitize_display_text(description)
392
+
393
+ # Agent name (identifier)
394
+ lines.append(("bold", "Name: "))
395
+ lines.append(("", name))
396
+ lines.append(("", "\n\n"))
397
+
398
+ # Display name
399
+ lines.append(("bold", "Display Name: "))
400
+ lines.append(("fg:ansicyan", safe_display_name))
401
+ lines.append(("", "\n\n"))
402
+
403
+ # Pinned model
404
+ lines.append(("bold", "Pinned Model: "))
405
+ if pinned_model:
406
+ safe_pinned_model = _sanitize_display_text(pinned_model)
407
+ lines.append(("fg:ansiyellow", safe_pinned_model))
408
+ else:
409
+ lines.append(("fg:ansibrightblack", "default"))
410
+ lines.append(("", "\n\n"))
411
+
412
+ # Description
413
+ lines.append(("bold", "Description:"))
414
+ lines.append(("", "\n"))
415
+
416
+ # Wrap description to fit panel
417
+ desc_lines = safe_description.split("\n")
418
+ for desc_line in desc_lines:
419
+ # Word wrap long lines
420
+ words = desc_line.split()
421
+ current_line = ""
422
+ for word in words:
423
+ if len(current_line) + len(word) + 1 > 55:
424
+ lines.append(("fg:ansibrightblack", current_line))
425
+ lines.append(("", "\n"))
426
+ current_line = word
427
+ else:
428
+ if current_line == "":
429
+ current_line = word
430
+ else:
431
+ current_line += " " + word
432
+ if current_line.strip():
433
+ lines.append(("fg:ansibrightblack", current_line))
434
+ lines.append(("", "\n"))
435
+
436
+ lines.append(("", "\n"))
437
+
438
+ # Current status
439
+ lines.append(("bold", " Status: "))
440
+ if is_current:
441
+ lines.append(("fg:ansigreen bold", "✓ Currently Active"))
442
+ else:
443
+ lines.append(("fg:ansibrightblack", "Not active"))
444
+ lines.append(("", "\n"))
445
+
446
+ return lines
447
+
448
+
449
+ async def interactive_agent_picker() -> str | None:
450
+ """Show interactive terminal UI to select an agent.
451
+
452
+ Returns:
453
+ Agent name to switch to, or None if cancelled.
454
+ """
455
+ entries = _get_agent_entries()
456
+ current_agent = get_current_agent()
457
+ current_agent_name = current_agent.name if current_agent else ""
458
+
459
+ if not entries:
460
+ emit_info("No agents found.")
461
+ return None
462
+
463
+ # State
464
+ selected_idx = [0] # Current selection (global index)
465
+ current_page = [0] # Current page
466
+ result = [None] # Selected agent name
467
+ pending_action = [None] # 'pin', 'clone', 'delete', or None
468
+
469
+ total_pages = [get_total_pages(len(entries), PAGE_SIZE)]
470
+
471
+ def get_current_entry() -> tuple[str, str, str | None]:
472
+ if 0 <= selected_idx[0] < len(entries):
473
+ return entries[selected_idx[0]]
474
+ return None
475
+
476
+ def refresh_entries(selected_name: str | None = None) -> None:
477
+ nonlocal entries
478
+
479
+ entries = _get_agent_entries()
480
+ total_pages[0] = get_total_pages(len(entries), PAGE_SIZE)
481
+
482
+ if not entries:
483
+ selected_idx[0] = 0
484
+ current_page[0] = 0
485
+ return
486
+
487
+ if selected_name:
488
+ for idx, (name, _, _) in enumerate(entries):
489
+ if name == selected_name:
490
+ selected_idx[0] = idx
491
+ break
492
+ else:
493
+ selected_idx[0] = min(selected_idx[0], len(entries) - 1)
494
+ else:
495
+ selected_idx[0] = min(selected_idx[0], len(entries) - 1)
496
+
497
+ current_page[0] = get_page_for_index(selected_idx[0], PAGE_SIZE)
498
+
499
+ # Build UI
500
+ menu_control = FormattedTextControl(text="")
501
+ preview_control = FormattedTextControl(text="")
502
+
503
+ def update_display():
504
+ """Update both panels."""
505
+ menu_control.text = _render_menu_panel(
506
+ entries, current_page[0], selected_idx[0], current_agent_name
507
+ )
508
+ preview_control.text = _render_preview_panel(
509
+ get_current_entry(), current_agent_name
510
+ )
511
+
512
+ menu_window = Window(
513
+ content=menu_control, wrap_lines=False, width=Dimension(weight=35)
514
+ )
515
+ preview_window = Window(
516
+ content=preview_control, wrap_lines=False, width=Dimension(weight=65)
517
+ )
518
+
519
+ menu_frame = Frame(menu_window, width=Dimension(weight=35), title="Agents")
520
+ preview_frame = Frame(preview_window, width=Dimension(weight=65), title="Preview")
521
+
522
+ root_container = VSplit(
523
+ [
524
+ menu_frame,
525
+ preview_frame,
526
+ ]
527
+ )
528
+
529
+ # Key bindings
530
+ kb = KeyBindings()
531
+
532
+ @kb.add("up")
533
+ def _(event):
534
+ if selected_idx[0] > 0:
535
+ selected_idx[0] -= 1
536
+ current_page[0] = ensure_visible_page(
537
+ selected_idx[0],
538
+ current_page[0],
539
+ len(entries),
540
+ PAGE_SIZE,
541
+ )
542
+ update_display()
543
+
544
+ @kb.add("down")
545
+ def _(event):
546
+ if selected_idx[0] < len(entries) - 1:
547
+ selected_idx[0] += 1
548
+ current_page[0] = ensure_visible_page(
549
+ selected_idx[0],
550
+ current_page[0],
551
+ len(entries),
552
+ PAGE_SIZE,
553
+ )
554
+ update_display()
555
+
556
+ @kb.add("left")
557
+ def _(event):
558
+ if current_page[0] > 0:
559
+ current_page[0] -= 1
560
+ selected_idx[0] = current_page[0] * PAGE_SIZE
561
+ update_display()
562
+
563
+ @kb.add("right")
564
+ def _(event):
565
+ if current_page[0] < total_pages[0] - 1:
566
+ current_page[0] += 1
567
+ selected_idx[0] = current_page[0] * PAGE_SIZE
568
+ update_display()
569
+
570
+ @kb.add("p")
571
+ def _(event):
572
+ if get_current_entry():
573
+ pending_action[0] = "pin"
574
+ event.app.exit()
575
+
576
+ @kb.add("c")
577
+ def _(event):
578
+ if get_current_entry():
579
+ pending_action[0] = "clone"
580
+ event.app.exit()
581
+
582
+ @kb.add("d")
583
+ def _(event):
584
+ if get_current_entry():
585
+ pending_action[0] = "delete"
586
+ event.app.exit()
587
+
588
+ @kb.add("enter")
589
+ def _(event):
590
+ entry = get_current_entry()
591
+ if entry:
592
+ result[0] = entry[0] # Store agent name
593
+ event.app.exit()
594
+
595
+ @kb.add("c-c")
596
+ def _(event):
597
+ result[0] = None
598
+ event.app.exit()
599
+
600
+ layout = Layout(root_container)
601
+ app = Application(
602
+ layout=layout,
603
+ key_bindings=kb,
604
+ full_screen=False,
605
+ mouse_support=False,
606
+ )
607
+
608
+ set_awaiting_user_input(True)
609
+
610
+ # Enter alternate screen buffer once for entire session
611
+ sys.stdout.write("\033[?1049h") # Enter alternate buffer
612
+ sys.stdout.write("\033[2J\033[H") # Clear and home
613
+ sys.stdout.flush()
614
+ await asyncio.sleep(0.05)
615
+
616
+ try:
617
+ while True:
618
+ pending_action[0] = None
619
+ result[0] = None
620
+ update_display()
621
+
622
+ # Clear the current buffer
623
+ sys.stdout.write("\033[2J\033[H")
624
+ sys.stdout.flush()
625
+
626
+ # Run application
627
+ await app.run_async()
628
+
629
+ if pending_action[0] == "pin":
630
+ entry = get_current_entry()
631
+ if entry:
632
+ selected_model = await _select_pinned_model(entry[0])
633
+ if selected_model:
634
+ _apply_pinned_model(entry[0], selected_model)
635
+ continue
636
+
637
+ if pending_action[0] == "clone":
638
+ entry = get_current_entry()
639
+ selected_name = None
640
+ if entry:
641
+ cloned_name = clone_agent(entry[0])
642
+ selected_name = cloned_name or entry[0]
643
+ refresh_entries(selected_name=selected_name)
644
+ continue
645
+
646
+ if pending_action[0] == "delete":
647
+ entry = get_current_entry()
648
+ selected_name = None
649
+ if entry:
650
+ agent_name = entry[0]
651
+ selected_name = agent_name
652
+ if not is_clone_agent_name(agent_name):
653
+ emit_warning("Only cloned agents can be deleted.")
654
+ elif agent_name == current_agent_name:
655
+ emit_warning("Cannot delete the active agent. Switch first.")
656
+ else:
657
+ if delete_clone_agent(agent_name):
658
+ selected_name = None
659
+ refresh_entries(selected_name=selected_name)
660
+ continue
661
+
662
+ break
663
+
664
+ finally:
665
+ # Exit alternate screen buffer once at end
666
+ sys.stdout.write("\033[?1049l") # Exit alternate buffer
667
+ sys.stdout.flush()
668
+ # Reset awaiting input flag
669
+ set_awaiting_user_input(False)
670
+
671
+ # Clear exit message
672
+ emit_info("✓ Exited agent picker")
673
+
674
+ return result[0]