codepp 0.0.437__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 (288) hide show
  1. code_puppy/__init__.py +10 -0
  2. code_puppy/__main__.py +10 -0
  3. code_puppy/agents/__init__.py +31 -0
  4. code_puppy/agents/agent_c_reviewer.py +155 -0
  5. code_puppy/agents/agent_code_puppy.py +117 -0
  6. code_puppy/agents/agent_code_reviewer.py +90 -0
  7. code_puppy/agents/agent_cpp_reviewer.py +132 -0
  8. code_puppy/agents/agent_creator_agent.py +638 -0
  9. code_puppy/agents/agent_golang_reviewer.py +151 -0
  10. code_puppy/agents/agent_helios.py +124 -0
  11. code_puppy/agents/agent_javascript_reviewer.py +160 -0
  12. code_puppy/agents/agent_manager.py +742 -0
  13. code_puppy/agents/agent_pack_leader.py +385 -0
  14. code_puppy/agents/agent_planning.py +165 -0
  15. code_puppy/agents/agent_python_programmer.py +169 -0
  16. code_puppy/agents/agent_python_reviewer.py +90 -0
  17. code_puppy/agents/agent_qa_expert.py +163 -0
  18. code_puppy/agents/agent_qa_kitten.py +208 -0
  19. code_puppy/agents/agent_scheduler.py +121 -0
  20. code_puppy/agents/agent_security_auditor.py +181 -0
  21. code_puppy/agents/agent_terminal_qa.py +323 -0
  22. code_puppy/agents/agent_typescript_reviewer.py +166 -0
  23. code_puppy/agents/base_agent.py +2156 -0
  24. code_puppy/agents/event_stream_handler.py +348 -0
  25. code_puppy/agents/json_agent.py +202 -0
  26. code_puppy/agents/pack/__init__.py +34 -0
  27. code_puppy/agents/pack/bloodhound.py +304 -0
  28. code_puppy/agents/pack/husky.py +327 -0
  29. code_puppy/agents/pack/retriever.py +393 -0
  30. code_puppy/agents/pack/shepherd.py +348 -0
  31. code_puppy/agents/pack/terrier.py +287 -0
  32. code_puppy/agents/pack/watchdog.py +367 -0
  33. code_puppy/agents/prompt_reviewer.py +145 -0
  34. code_puppy/agents/subagent_stream_handler.py +276 -0
  35. code_puppy/api/__init__.py +13 -0
  36. code_puppy/api/app.py +169 -0
  37. code_puppy/api/main.py +21 -0
  38. code_puppy/api/pty_manager.py +453 -0
  39. code_puppy/api/routers/__init__.py +12 -0
  40. code_puppy/api/routers/agents.py +36 -0
  41. code_puppy/api/routers/commands.py +217 -0
  42. code_puppy/api/routers/config.py +75 -0
  43. code_puppy/api/routers/sessions.py +234 -0
  44. code_puppy/api/templates/terminal.html +361 -0
  45. code_puppy/api/websocket.py +154 -0
  46. code_puppy/callbacks.py +692 -0
  47. code_puppy/chatgpt_codex_client.py +338 -0
  48. code_puppy/claude_cache_client.py +672 -0
  49. code_puppy/cli_runner.py +1073 -0
  50. code_puppy/command_line/__init__.py +1 -0
  51. code_puppy/command_line/add_model_menu.py +1092 -0
  52. code_puppy/command_line/agent_menu.py +662 -0
  53. code_puppy/command_line/attachments.py +395 -0
  54. code_puppy/command_line/autosave_menu.py +704 -0
  55. code_puppy/command_line/clipboard.py +527 -0
  56. code_puppy/command_line/colors_menu.py +532 -0
  57. code_puppy/command_line/command_handler.py +293 -0
  58. code_puppy/command_line/command_registry.py +150 -0
  59. code_puppy/command_line/config_commands.py +719 -0
  60. code_puppy/command_line/core_commands.py +867 -0
  61. code_puppy/command_line/diff_menu.py +865 -0
  62. code_puppy/command_line/file_path_completion.py +73 -0
  63. code_puppy/command_line/load_context_completion.py +52 -0
  64. code_puppy/command_line/mcp/__init__.py +10 -0
  65. code_puppy/command_line/mcp/base.py +32 -0
  66. code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
  67. code_puppy/command_line/mcp/custom_server_form.py +688 -0
  68. code_puppy/command_line/mcp/custom_server_installer.py +195 -0
  69. code_puppy/command_line/mcp/edit_command.py +148 -0
  70. code_puppy/command_line/mcp/handler.py +138 -0
  71. code_puppy/command_line/mcp/help_command.py +147 -0
  72. code_puppy/command_line/mcp/install_command.py +214 -0
  73. code_puppy/command_line/mcp/install_menu.py +705 -0
  74. code_puppy/command_line/mcp/list_command.py +94 -0
  75. code_puppy/command_line/mcp/logs_command.py +235 -0
  76. code_puppy/command_line/mcp/remove_command.py +82 -0
  77. code_puppy/command_line/mcp/restart_command.py +100 -0
  78. code_puppy/command_line/mcp/search_command.py +123 -0
  79. code_puppy/command_line/mcp/start_all_command.py +135 -0
  80. code_puppy/command_line/mcp/start_command.py +117 -0
  81. code_puppy/command_line/mcp/status_command.py +184 -0
  82. code_puppy/command_line/mcp/stop_all_command.py +112 -0
  83. code_puppy/command_line/mcp/stop_command.py +80 -0
  84. code_puppy/command_line/mcp/test_command.py +107 -0
  85. code_puppy/command_line/mcp/utils.py +129 -0
  86. code_puppy/command_line/mcp/wizard_utils.py +334 -0
  87. code_puppy/command_line/mcp_completion.py +174 -0
  88. code_puppy/command_line/model_picker_completion.py +197 -0
  89. code_puppy/command_line/model_settings_menu.py +932 -0
  90. code_puppy/command_line/motd.py +96 -0
  91. code_puppy/command_line/onboarding_slides.py +179 -0
  92. code_puppy/command_line/onboarding_wizard.py +342 -0
  93. code_puppy/command_line/pin_command_completion.py +329 -0
  94. code_puppy/command_line/prompt_toolkit_completion.py +846 -0
  95. code_puppy/command_line/session_commands.py +302 -0
  96. code_puppy/command_line/shell_passthrough.py +145 -0
  97. code_puppy/command_line/skills_completion.py +160 -0
  98. code_puppy/command_line/uc_menu.py +893 -0
  99. code_puppy/command_line/utils.py +93 -0
  100. code_puppy/command_line/wiggum_state.py +78 -0
  101. code_puppy/config.py +1770 -0
  102. code_puppy/error_logging.py +134 -0
  103. code_puppy/gemini_code_assist.py +385 -0
  104. code_puppy/gemini_model.py +754 -0
  105. code_puppy/hook_engine/README.md +105 -0
  106. code_puppy/hook_engine/__init__.py +21 -0
  107. code_puppy/hook_engine/aliases.py +155 -0
  108. code_puppy/hook_engine/engine.py +221 -0
  109. code_puppy/hook_engine/executor.py +296 -0
  110. code_puppy/hook_engine/matcher.py +156 -0
  111. code_puppy/hook_engine/models.py +240 -0
  112. code_puppy/hook_engine/registry.py +106 -0
  113. code_puppy/hook_engine/validator.py +144 -0
  114. code_puppy/http_utils.py +361 -0
  115. code_puppy/keymap.py +128 -0
  116. code_puppy/main.py +10 -0
  117. code_puppy/mcp_/__init__.py +66 -0
  118. code_puppy/mcp_/async_lifecycle.py +286 -0
  119. code_puppy/mcp_/blocking_startup.py +469 -0
  120. code_puppy/mcp_/captured_stdio_server.py +275 -0
  121. code_puppy/mcp_/circuit_breaker.py +290 -0
  122. code_puppy/mcp_/config_wizard.py +507 -0
  123. code_puppy/mcp_/dashboard.py +308 -0
  124. code_puppy/mcp_/error_isolation.py +407 -0
  125. code_puppy/mcp_/examples/retry_example.py +226 -0
  126. code_puppy/mcp_/health_monitor.py +589 -0
  127. code_puppy/mcp_/managed_server.py +428 -0
  128. code_puppy/mcp_/manager.py +807 -0
  129. code_puppy/mcp_/mcp_logs.py +224 -0
  130. code_puppy/mcp_/registry.py +451 -0
  131. code_puppy/mcp_/retry_manager.py +337 -0
  132. code_puppy/mcp_/server_registry_catalog.py +1126 -0
  133. code_puppy/mcp_/status_tracker.py +355 -0
  134. code_puppy/mcp_/system_tools.py +209 -0
  135. code_puppy/mcp_prompts/__init__.py +1 -0
  136. code_puppy/mcp_prompts/hook_creator.py +103 -0
  137. code_puppy/messaging/__init__.py +255 -0
  138. code_puppy/messaging/bus.py +613 -0
  139. code_puppy/messaging/commands.py +167 -0
  140. code_puppy/messaging/markdown_patches.py +57 -0
  141. code_puppy/messaging/message_queue.py +361 -0
  142. code_puppy/messaging/messages.py +569 -0
  143. code_puppy/messaging/queue_console.py +271 -0
  144. code_puppy/messaging/renderers.py +311 -0
  145. code_puppy/messaging/rich_renderer.py +1158 -0
  146. code_puppy/messaging/spinner/__init__.py +83 -0
  147. code_puppy/messaging/spinner/console_spinner.py +240 -0
  148. code_puppy/messaging/spinner/spinner_base.py +95 -0
  149. code_puppy/messaging/subagent_console.py +460 -0
  150. code_puppy/model_factory.py +848 -0
  151. code_puppy/model_switching.py +63 -0
  152. code_puppy/model_utils.py +168 -0
  153. code_puppy/models.json +174 -0
  154. code_puppy/models_dev_api.json +1 -0
  155. code_puppy/models_dev_parser.py +592 -0
  156. code_puppy/plugins/__init__.py +186 -0
  157. code_puppy/plugins/agent_skills/__init__.py +22 -0
  158. code_puppy/plugins/agent_skills/config.py +175 -0
  159. code_puppy/plugins/agent_skills/discovery.py +136 -0
  160. code_puppy/plugins/agent_skills/downloader.py +392 -0
  161. code_puppy/plugins/agent_skills/installer.py +22 -0
  162. code_puppy/plugins/agent_skills/metadata.py +219 -0
  163. code_puppy/plugins/agent_skills/prompt_builder.py +60 -0
  164. code_puppy/plugins/agent_skills/register_callbacks.py +241 -0
  165. code_puppy/plugins/agent_skills/remote_catalog.py +322 -0
  166. code_puppy/plugins/agent_skills/skill_catalog.py +257 -0
  167. code_puppy/plugins/agent_skills/skills_install_menu.py +664 -0
  168. code_puppy/plugins/agent_skills/skills_menu.py +781 -0
  169. code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
  170. code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
  171. code_puppy/plugins/antigravity_oauth/antigravity_model.py +706 -0
  172. code_puppy/plugins/antigravity_oauth/config.py +42 -0
  173. code_puppy/plugins/antigravity_oauth/constants.py +133 -0
  174. code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
  175. code_puppy/plugins/antigravity_oauth/register_callbacks.py +518 -0
  176. code_puppy/plugins/antigravity_oauth/storage.py +288 -0
  177. code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
  178. code_puppy/plugins/antigravity_oauth/token.py +167 -0
  179. code_puppy/plugins/antigravity_oauth/transport.py +863 -0
  180. code_puppy/plugins/antigravity_oauth/utils.py +168 -0
  181. code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
  182. code_puppy/plugins/chatgpt_oauth/config.py +52 -0
  183. code_puppy/plugins/chatgpt_oauth/oauth_flow.py +329 -0
  184. code_puppy/plugins/chatgpt_oauth/register_callbacks.py +176 -0
  185. code_puppy/plugins/chatgpt_oauth/test_plugin.py +301 -0
  186. code_puppy/plugins/chatgpt_oauth/utils.py +523 -0
  187. code_puppy/plugins/claude_code_hooks/__init__.py +1 -0
  188. code_puppy/plugins/claude_code_hooks/config.py +137 -0
  189. code_puppy/plugins/claude_code_hooks/register_callbacks.py +175 -0
  190. code_puppy/plugins/claude_code_oauth/README.md +167 -0
  191. code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
  192. code_puppy/plugins/claude_code_oauth/__init__.py +25 -0
  193. code_puppy/plugins/claude_code_oauth/config.py +52 -0
  194. code_puppy/plugins/claude_code_oauth/register_callbacks.py +453 -0
  195. code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
  196. code_puppy/plugins/claude_code_oauth/token_refresh_heartbeat.py +241 -0
  197. code_puppy/plugins/claude_code_oauth/utils.py +640 -0
  198. code_puppy/plugins/customizable_commands/__init__.py +0 -0
  199. code_puppy/plugins/customizable_commands/register_callbacks.py +152 -0
  200. code_puppy/plugins/example_custom_command/README.md +280 -0
  201. code_puppy/plugins/example_custom_command/register_callbacks.py +51 -0
  202. code_puppy/plugins/file_permission_handler/__init__.py +4 -0
  203. code_puppy/plugins/file_permission_handler/register_callbacks.py +470 -0
  204. code_puppy/plugins/frontend_emitter/__init__.py +25 -0
  205. code_puppy/plugins/frontend_emitter/emitter.py +121 -0
  206. code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
  207. code_puppy/plugins/hook_creator/__init__.py +1 -0
  208. code_puppy/plugins/hook_creator/register_callbacks.py +33 -0
  209. code_puppy/plugins/hook_manager/__init__.py +1 -0
  210. code_puppy/plugins/hook_manager/config.py +290 -0
  211. code_puppy/plugins/hook_manager/hooks_menu.py +564 -0
  212. code_puppy/plugins/hook_manager/register_callbacks.py +227 -0
  213. code_puppy/plugins/oauth_puppy_html.py +228 -0
  214. code_puppy/plugins/scheduler/__init__.py +1 -0
  215. code_puppy/plugins/scheduler/register_callbacks.py +88 -0
  216. code_puppy/plugins/scheduler/scheduler_menu.py +522 -0
  217. code_puppy/plugins/scheduler/scheduler_wizard.py +341 -0
  218. code_puppy/plugins/shell_safety/__init__.py +6 -0
  219. code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
  220. code_puppy/plugins/shell_safety/command_cache.py +156 -0
  221. code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
  222. code_puppy/plugins/synthetic_status/__init__.py +1 -0
  223. code_puppy/plugins/synthetic_status/register_callbacks.py +132 -0
  224. code_puppy/plugins/synthetic_status/status_api.py +147 -0
  225. code_puppy/plugins/universal_constructor/__init__.py +13 -0
  226. code_puppy/plugins/universal_constructor/models.py +138 -0
  227. code_puppy/plugins/universal_constructor/register_callbacks.py +47 -0
  228. code_puppy/plugins/universal_constructor/registry.py +302 -0
  229. code_puppy/plugins/universal_constructor/sandbox.py +584 -0
  230. code_puppy/prompts/antigravity_system_prompt.md +1 -0
  231. code_puppy/pydantic_patches.py +356 -0
  232. code_puppy/reopenable_async_client.py +232 -0
  233. code_puppy/round_robin_model.py +150 -0
  234. code_puppy/scheduler/__init__.py +41 -0
  235. code_puppy/scheduler/__main__.py +9 -0
  236. code_puppy/scheduler/cli.py +118 -0
  237. code_puppy/scheduler/config.py +126 -0
  238. code_puppy/scheduler/daemon.py +280 -0
  239. code_puppy/scheduler/executor.py +155 -0
  240. code_puppy/scheduler/platform.py +19 -0
  241. code_puppy/scheduler/platform_unix.py +22 -0
  242. code_puppy/scheduler/platform_win.py +32 -0
  243. code_puppy/session_storage.py +338 -0
  244. code_puppy/status_display.py +257 -0
  245. code_puppy/summarization_agent.py +176 -0
  246. code_puppy/terminal_utils.py +418 -0
  247. code_puppy/tools/__init__.py +501 -0
  248. code_puppy/tools/agent_tools.py +603 -0
  249. code_puppy/tools/ask_user_question/__init__.py +26 -0
  250. code_puppy/tools/ask_user_question/constants.py +73 -0
  251. code_puppy/tools/ask_user_question/demo_tui.py +55 -0
  252. code_puppy/tools/ask_user_question/handler.py +232 -0
  253. code_puppy/tools/ask_user_question/models.py +304 -0
  254. code_puppy/tools/ask_user_question/registration.py +26 -0
  255. code_puppy/tools/ask_user_question/renderers.py +309 -0
  256. code_puppy/tools/ask_user_question/terminal_ui.py +329 -0
  257. code_puppy/tools/ask_user_question/theme.py +155 -0
  258. code_puppy/tools/ask_user_question/tui_loop.py +423 -0
  259. code_puppy/tools/browser/__init__.py +37 -0
  260. code_puppy/tools/browser/browser_control.py +289 -0
  261. code_puppy/tools/browser/browser_interactions.py +545 -0
  262. code_puppy/tools/browser/browser_locators.py +640 -0
  263. code_puppy/tools/browser/browser_manager.py +378 -0
  264. code_puppy/tools/browser/browser_navigation.py +251 -0
  265. code_puppy/tools/browser/browser_screenshot.py +179 -0
  266. code_puppy/tools/browser/browser_scripts.py +462 -0
  267. code_puppy/tools/browser/browser_workflows.py +221 -0
  268. code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
  269. code_puppy/tools/browser/terminal_command_tools.py +534 -0
  270. code_puppy/tools/browser/terminal_screenshot_tools.py +552 -0
  271. code_puppy/tools/browser/terminal_tools.py +525 -0
  272. code_puppy/tools/command_runner.py +1346 -0
  273. code_puppy/tools/common.py +1409 -0
  274. code_puppy/tools/display.py +84 -0
  275. code_puppy/tools/file_modifications.py +886 -0
  276. code_puppy/tools/file_operations.py +802 -0
  277. code_puppy/tools/scheduler_tools.py +412 -0
  278. code_puppy/tools/skills_tools.py +244 -0
  279. code_puppy/tools/subagent_context.py +158 -0
  280. code_puppy/tools/tools_content.py +51 -0
  281. code_puppy/tools/universal_constructor.py +889 -0
  282. code_puppy/uvx_detection.py +242 -0
  283. code_puppy/version_checker.py +82 -0
  284. codepp-0.0.437.dist-info/METADATA +766 -0
  285. codepp-0.0.437.dist-info/RECORD +288 -0
  286. codepp-0.0.437.dist-info/WHEEL +4 -0
  287. codepp-0.0.437.dist-info/entry_points.txt +3 -0
  288. codepp-0.0.437.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,865 @@
1
+ """Interactive nested menu for diff configuration.
2
+
3
+ Now using the fixed arrow_select_async with proper HTML escaping.
4
+ Supports cycling through all supported languages with left/right arrows!
5
+ """
6
+
7
+ import asyncio
8
+ import io
9
+ import sys
10
+ from typing import Callable, Optional
11
+
12
+ from prompt_toolkit import Application
13
+ from prompt_toolkit.formatted_text import ANSI, FormattedText
14
+ from prompt_toolkit.key_binding import KeyBindings
15
+ from prompt_toolkit.layout import Layout, VSplit, Window
16
+ from prompt_toolkit.layout.controls import FormattedTextControl
17
+ from prompt_toolkit.widgets import Frame
18
+ from rich.console import Console
19
+
20
+ # Sample code snippets for each language
21
+ LANGUAGE_SAMPLES = {
22
+ "python": (
23
+ "calculator.py",
24
+ """--- a/calculator.py
25
+ +++ b/calculator.py
26
+ @@ -1,12 +1,15 @@
27
+ def calculate_total(items, tax_rate=0.08):
28
+ + # Calculate total price
29
+ + total = 0
30
+ + for item in items:
31
+ + total += item['price']
32
+ - # Calculate subtotal with discount
33
+ - subtotal = sum(item['price'] * item.get('quantity', 1) for item in items)
34
+ - discount = subtotal * 0.1 if subtotal > 100 else 0
35
+
36
+ + # Add tax
37
+ + tax = total * tax_rate
38
+ + final_total = total + tax
39
+ - # Apply tax to discounted amount
40
+ - taxable_amount = subtotal - discount
41
+ - tax = round(taxable_amount * tax_rate, 2)
42
+ - final_total = taxable_amount + tax
43
+
44
+ + return final_total
45
+ - return {
46
+ - 'subtotal': subtotal,
47
+ - 'discount': discount,
48
+ - 'tax': tax,
49
+ - 'total': final_total
50
+ - }""",
51
+ ),
52
+ "javascript": (
53
+ "app.js",
54
+ """--- a/app.js
55
+ +++ b/app.js
56
+ @@ -1,10 +1,12 @@
57
+ -function fetchUserData(userId) {
58
+ - return fetch(`/api/users/${userId}`)
59
+ - .then(response => response.json())
60
+ - .then(data => {
61
+ - return data.user;
62
+ - })
63
+ - .catch(error => console.error(error));
64
+ +async function fetchUserData(userId) {
65
+ + try {
66
+ + const response = await fetch(`/api/users/${userId}`);
67
+ + const data = await response.json();
68
+ + return data.user;
69
+ + } catch (error) {
70
+ + console.error('Failed to fetch user:', error);
71
+ + throw error;
72
+ + }
73
+ }""",
74
+ ),
75
+ "typescript": (
76
+ "service.ts",
77
+ """--- a/service.ts
78
+ +++ b/service.ts
79
+ @@ -1,8 +1,11 @@
80
+ -class UserService {
81
+ - getUser(id: number) {
82
+ - return this.http.get(`/users/${id}`);
83
+ +interface User {
84
+ + id: number;
85
+ + name: string;
86
+ +}
87
+ +
88
+ +class UserService {
89
+ + async getUser(id: number): Promise<User> {
90
+ + const response = await this.http.get<User>(`/users/${id}`);
91
+ + return response.data;
92
+ }
93
+ - deleteUser(id: number) {
94
+ - return this.http.delete(`/users/${id}`);
95
+ - }
96
+ }""",
97
+ ),
98
+ "rust": (
99
+ "main.rs",
100
+ """--- a/main.rs
101
+ +++ b/main.rs
102
+ @@ -1,8 +1,10 @@
103
+ -fn calculate_sum(numbers: Vec<i32>) -> i32 {
104
+ - let mut total = 0;
105
+ - for num in numbers {
106
+ - total = total + num;
107
+ +fn calculate_sum(numbers: &[i32]) -> i32 {
108
+ + numbers.iter().sum()
109
+ +}
110
+ +
111
+ +fn calculate_average(numbers: &[i32]) -> f64 {
112
+ + if numbers.is_empty() {
113
+ + return 0.0;
114
+ }
115
+ - total
116
+ + calculate_sum(numbers) as f64 / numbers.len() as f64
117
+ }""",
118
+ ),
119
+ "go": (
120
+ "handler.go",
121
+ """--- a/handler.go
122
+ +++ b/handler.go
123
+ @@ -1,10 +1,15 @@
124
+ -func HandleRequest(w http.ResponseWriter, r *http.Request) {
125
+ - data := getData()
126
+ - json.NewEncoder(w).Encode(data)
127
+ +func HandleRequest(w http.ResponseWriter, r *http.Request) error {
128
+ + data, err := getData()
129
+ + if err != nil {
130
+ + http.Error(w, err.Error(), http.StatusInternalServerError)
131
+ + return err
132
+ + }
133
+ + w.Header().Set("Content-Type", "application/json")
134
+ + return json.NewEncoder(w).Encode(data)
135
+ }
136
+
137
+ -func getData() map[string]interface{} {
138
+ - return map[string]interface{}{"status": "ok"}
139
+ +func getData() (map[string]interface{}, error) {
140
+ + return map[string]interface{}{"status": "ok"}, nil
141
+ }""",
142
+ ),
143
+ "java": (
144
+ "Calculator.java",
145
+ """--- a/Calculator.java
146
+ +++ b/Calculator.java
147
+ @@ -1,8 +1,12 @@
148
+ public class Calculator {
149
+ - public int add(int a, int b) {
150
+ - return a + b;
151
+ + public double calculateTotal(List<Double> prices) {
152
+ + return prices.stream()
153
+ + .reduce(0.0, Double::sum);
154
+ }
155
+
156
+ - public int multiply(int a, int b) {
157
+ - return a * b;
158
+ + public double calculateAverage(List<Double> prices) {
159
+ + if (prices.isEmpty()) {
160
+ + return 0.0;
161
+ + }
162
+ + return calculateTotal(prices) / prices.size();
163
+ }
164
+ }""",
165
+ ),
166
+ "ruby": (
167
+ "calculator.rb",
168
+ """--- a/calculator.rb
169
+ +++ b/calculator.rb
170
+ @@ -1,8 +1,10 @@
171
+ class Calculator
172
+ - def add(a, b)
173
+ - a + b
174
+ + def calculate_total(items)
175
+ + items.sum { |item| item[:price] }
176
+ end
177
+
178
+ - def multiply(a, b)
179
+ - a * b
180
+ + def calculate_average(items)
181
+ + return 0 if items.empty?
182
+ +
183
+ + calculate_total(items) / items.size.to_f
184
+ end
185
+ end""",
186
+ ),
187
+ "csharp": (
188
+ "Calculator.cs",
189
+ """--- a/Calculator.cs
190
+ +++ b/Calculator.cs
191
+ @@ -1,10 +1,14 @@
192
+ -public class Calculator {
193
+ - public int Add(int a, int b) {
194
+ - return a + b;
195
+ +public class Calculator
196
+ +{
197
+ + public decimal CalculateTotal(IEnumerable<decimal> prices)
198
+ + {
199
+ + return prices.Sum();
200
+ }
201
+
202
+ - public int Multiply(int a, int b) {
203
+ - return a * b;
204
+ + public decimal CalculateAverage(IEnumerable<decimal> prices)
205
+ + {
206
+ + var priceList = prices.ToList();
207
+ + return priceList.Any() ? priceList.Average() : 0m;
208
+ }
209
+ }""",
210
+ ),
211
+ "php": (
212
+ "Calculator.php",
213
+ """--- a/Calculator.php
214
+ +++ b/Calculator.php
215
+ @@ -1,10 +1,14 @@
216
+ <?php
217
+ class Calculator {
218
+ - public function add($a, $b) {
219
+ - return $a + $b;
220
+ + public function calculateTotal(array $items): float {
221
+ + return array_sum(array_column($items, 'price'));
222
+ }
223
+
224
+ - public function multiply($a, $b) {
225
+ - return $a * $b;
226
+ + public function calculateAverage(array $items): float {
227
+ + if (empty($items)) {
228
+ + return 0.0;
229
+ + }
230
+ + return $this->calculateTotal($items) / count($items);
231
+ }
232
+ }""",
233
+ ),
234
+ "html": (
235
+ "index.html",
236
+ """--- a/index.html
237
+ +++ b/index.html
238
+ @@ -1,5 +1,8 @@
239
+ <div class="container">
240
+ - <h1>Welcome</h1>
241
+ - <p>Hello World</p>
242
+ + <header>
243
+ + <h1>Welcome to Our Site</h1>
244
+ + <nav>
245
+ + <a href="#home">Home</a>
246
+ + <a href="#about">About</a>
247
+ + </nav>
248
+ + </header>
249
+ </div>""",
250
+ ),
251
+ "css": (
252
+ "styles.css",
253
+ """--- a/styles.css
254
+ +++ b/styles.css
255
+ @@ -1,5 +1,10 @@
256
+ .container {
257
+ - width: 100%;
258
+ - padding: 20px;
259
+ + max-width: 1200px;
260
+ + margin: 0 auto;
261
+ + padding: 2rem;
262
+ +}
263
+ +
264
+ +.container header {
265
+ + display: flex;
266
+ + justify-content: space-between;
267
+ + align-items: center;
268
+ }""",
269
+ ),
270
+ "json": (
271
+ "config.json",
272
+ """--- a/config.json
273
+ +++ b/config.json
274
+ @@ -1,5 +1,8 @@
275
+ {
276
+ - "name": "app",
277
+ - "version": "1.0.0"
278
+ + "name": "my-awesome-app",
279
+ + "version": "2.0.0",
280
+ + "description": "An awesome application",
281
+ + "author": "Code Puppy",
282
+ + "license": "MIT"
283
+ }""",
284
+ ),
285
+ "yaml": (
286
+ "config.yml",
287
+ """--- a/config.yml
288
+ +++ b/config.yml
289
+ @@ -1,4 +1,8 @@
290
+ app:
291
+ name: myapp
292
+ - version: 1.0
293
+ + version: 2.0
294
+ + environment: production
295
+ +
296
+ +database:
297
+ + host: localhost
298
+ + port: 5432""",
299
+ ),
300
+ "bash": (
301
+ "deploy.sh",
302
+ """--- a/deploy.sh
303
+ +++ b/deploy.sh
304
+ @@ -1,5 +1,9 @@
305
+ #!/bin/bash
306
+ -echo \"Deploying...\"
307
+ -npm run build
308
+ +set -e
309
+ +
310
+ +echo \"Starting deployment...\"
311
+ +npm run build --production
312
+ +npm run test
313
+ +echo \"Deployment complete!\"""",
314
+ ),
315
+ "sql": (
316
+ "schema.sql",
317
+ """--- a/schema.sql
318
+ +++ b/schema.sql
319
+ @@ -1,5 +1,9 @@
320
+ CREATE TABLE users (
321
+ id INTEGER PRIMARY KEY,
322
+ - name TEXT
323
+ + name TEXT NOT NULL,
324
+ + email TEXT UNIQUE NOT NULL,
325
+ + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
326
+ );
327
+ +
328
+ +CREATE INDEX idx_users_email ON users(email);""",
329
+ ),
330
+ }
331
+
332
+ # Get all supported languages in a consistent order
333
+ SUPPORTED_LANGUAGES = [
334
+ "python",
335
+ "javascript",
336
+ "typescript",
337
+ "rust",
338
+ "go",
339
+ "java",
340
+ "ruby",
341
+ "csharp",
342
+ "php",
343
+ "html",
344
+ "css",
345
+ "json",
346
+ "yaml",
347
+ "bash",
348
+ "sql",
349
+ ]
350
+
351
+
352
+ class DiffConfiguration:
353
+ """Holds the current diff configuration state."""
354
+
355
+ def __init__(self):
356
+ """Initialize configuration from current settings."""
357
+ from code_puppy.config import (
358
+ get_diff_addition_color,
359
+ get_diff_deletion_color,
360
+ )
361
+
362
+ self.current_add_color = get_diff_addition_color()
363
+ self.current_del_color = get_diff_deletion_color()
364
+ self.original_add_color = self.current_add_color
365
+ self.original_del_color = self.current_del_color
366
+ self.current_language_index = 0 # Track current language for preview
367
+
368
+ def has_changes(self) -> bool:
369
+ """Check if any changes have been made."""
370
+ return (
371
+ self.current_add_color != self.original_add_color
372
+ or self.current_del_color != self.original_del_color
373
+ )
374
+
375
+ def next_language(self):
376
+ """Cycle to the next language."""
377
+ self.current_language_index = (self.current_language_index + 1) % len(
378
+ SUPPORTED_LANGUAGES
379
+ )
380
+
381
+ def prev_language(self):
382
+ """Cycle to the previous language."""
383
+ self.current_language_index = (self.current_language_index - 1) % len(
384
+ SUPPORTED_LANGUAGES
385
+ )
386
+
387
+ def get_current_language(self) -> str:
388
+ """Get the currently selected language."""
389
+ return SUPPORTED_LANGUAGES[self.current_language_index]
390
+
391
+
392
+ async def interactive_diff_picker() -> Optional[dict]:
393
+ """Show an interactive full-screen terminal UI to configure diff settings.
394
+
395
+ Returns:
396
+ A dict with changes or None if cancelled
397
+ """
398
+ from code_puppy.tools.command_runner import set_awaiting_user_input
399
+
400
+ config = DiffConfiguration()
401
+
402
+ set_awaiting_user_input(True)
403
+
404
+ # Enter alternate screen buffer once for entire session
405
+ sys.stdout.write("\033[?1049h") # Enter alternate buffer
406
+ sys.stdout.write("\033[2J\033[H") # Clear and home
407
+ sys.stdout.flush()
408
+ await asyncio.sleep(0.1) # Minimal delay for state sync
409
+
410
+ try:
411
+ # Main menu loop
412
+ while True:
413
+ choices = [
414
+ "Configure Addition Color",
415
+ "Configure Deletion Color",
416
+ ]
417
+
418
+ if config.has_changes():
419
+ choices.append("Save & Exit")
420
+ else:
421
+ choices.append("Exit")
422
+
423
+ # Dummy update function for main menu (config doesn't change on navigation)
424
+ def dummy_update(choice: str):
425
+ pass
426
+
427
+ def get_main_preview():
428
+ return _get_preview_text_for_prompt_toolkit(config)
429
+
430
+ try:
431
+ selected = await _split_panel_selector(
432
+ "Diff Color Configuration",
433
+ choices,
434
+ dummy_update,
435
+ get_preview=get_main_preview,
436
+ config=config,
437
+ )
438
+ except KeyboardInterrupt:
439
+ break
440
+
441
+ # Handle selection
442
+ if "Addition" in selected:
443
+ await _handle_color_menu(config, "additions")
444
+ elif "Deletion" in selected:
445
+ await _handle_color_menu(config, "deletions")
446
+ else:
447
+ # Exit
448
+ break
449
+
450
+ except Exception:
451
+ # Silent error - just exit cleanly
452
+ return None
453
+ finally:
454
+ set_awaiting_user_input(False)
455
+ # Exit alternate screen buffer once at end
456
+ sys.stdout.write("\033[?1049l") # Exit alternate buffer
457
+ sys.stdout.flush()
458
+
459
+ # Clear exit message
460
+ from code_puppy.messaging import emit_info
461
+
462
+ emit_info("✓ Exited diff color configuration")
463
+
464
+ # Return changes if any
465
+ if config.has_changes():
466
+ return {
467
+ "add_color": config.current_add_color,
468
+ "del_color": config.current_del_color,
469
+ }
470
+
471
+ return None
472
+
473
+
474
+ async def _split_panel_selector(
475
+ title: str,
476
+ choices: list[str],
477
+ on_change: Callable[[str], None],
478
+ get_preview: Callable[[], ANSI],
479
+ config: Optional[DiffConfiguration] = None,
480
+ ) -> Optional[str]:
481
+ """Split-panel selector with menu on left and live preview on right.
482
+
483
+ Supports left/right arrow navigation through languages if config is provided.
484
+ """
485
+ selected_index = [0]
486
+ result = [None]
487
+
488
+ def get_left_panel_text():
489
+ """Generate the selector menu text."""
490
+ try:
491
+ lines = []
492
+ lines.append(("bold cyan", title))
493
+ lines.append(("", "\n\n"))
494
+
495
+ if not choices:
496
+ lines.append(("fg:ansiyellow", "No choices available"))
497
+ lines.append(("", "\n"))
498
+ else:
499
+ for i, choice in enumerate(choices):
500
+ if i == selected_index[0]:
501
+ lines.append(("fg:ansigreen", "▶ "))
502
+ lines.append(("fg:ansigreen bold", choice))
503
+ else:
504
+ lines.append(("", " "))
505
+ lines.append(("", choice))
506
+ lines.append(("", "\n"))
507
+
508
+ lines.append(("", "\n"))
509
+
510
+ # Add language navigation hint if config is available
511
+ if config is not None:
512
+ current_lang = config.get_current_language()
513
+ lang_hint = f"Language: {current_lang.upper()} (←→ to change)"
514
+ lines.append(("fg:ansiyellow", lang_hint))
515
+ lines.append(("", "\n"))
516
+
517
+ lines.append(
518
+ ("fg:ansicyan", "↑↓ Navigate │ Enter Confirm │ Ctrl-C Cancel")
519
+ )
520
+ return FormattedText(lines)
521
+ except Exception as e:
522
+ return FormattedText([("fg:ansired", f"Error: {e}")])
523
+
524
+ def get_right_panel_text():
525
+ """Generate the preview panel text."""
526
+ try:
527
+ preview = get_preview()
528
+ # get_preview() now returns ANSI, which is already FormattedText-compatible
529
+ return preview
530
+ except Exception as e:
531
+ return FormattedText([("fg:ansired", f"Preview error: {e}")])
532
+
533
+ kb = KeyBindings()
534
+
535
+ @kb.add("up")
536
+ @kb.add("c-p") # Ctrl+P = previous (Emacs-style)
537
+ def move_up(event):
538
+ if choices:
539
+ selected_index[0] = (selected_index[0] - 1) % len(choices)
540
+ on_change(choices[selected_index[0]])
541
+ event.app.invalidate()
542
+
543
+ @kb.add("down")
544
+ @kb.add("c-n") # Ctrl+N = next (Emacs-style)
545
+ def move_down(event):
546
+ if choices:
547
+ selected_index[0] = (selected_index[0] + 1) % len(choices)
548
+ on_change(choices[selected_index[0]])
549
+ event.app.invalidate()
550
+
551
+ @kb.add("left")
552
+ def prev_lang(event):
553
+ if config is not None:
554
+ config.prev_language()
555
+ event.app.invalidate()
556
+
557
+ @kb.add("right")
558
+ def next_lang(event):
559
+ if config is not None:
560
+ config.next_language()
561
+ event.app.invalidate()
562
+
563
+ @kb.add("enter")
564
+ def accept(event):
565
+ if choices:
566
+ result[0] = choices[selected_index[0]]
567
+ else:
568
+ result[0] = None
569
+ event.app.exit()
570
+
571
+ @kb.add("c-c")
572
+ def cancel(event):
573
+ result[0] = None
574
+ event.app.exit()
575
+
576
+ # Create split layout with left (selector) and right (preview) panels
577
+ left_panel = Window(
578
+ content=FormattedTextControl(lambda: get_left_panel_text()),
579
+ width=50,
580
+ )
581
+
582
+ right_panel = Window(
583
+ content=FormattedTextControl(lambda: get_right_panel_text()),
584
+ )
585
+
586
+ # Create vertical split (side-by-side panels)
587
+ root_container = VSplit(
588
+ [
589
+ Frame(left_panel, title="Menu"),
590
+ Frame(right_panel, title="Preview"),
591
+ ]
592
+ )
593
+
594
+ layout = Layout(root_container)
595
+ app = Application(
596
+ layout=layout,
597
+ key_bindings=kb,
598
+ full_screen=False, # Don't use full_screen to avoid buffer issues
599
+ mouse_support=False,
600
+ color_depth="DEPTH_24_BIT", # Enable truecolor support
601
+ )
602
+
603
+ sys.stdout.flush()
604
+ sys.stdout.flush()
605
+
606
+ # Trigger initial update only if choices is not empty
607
+ if choices:
608
+ on_change(choices[selected_index[0]])
609
+
610
+ # Just clear the current buffer (don't switch buffers)
611
+ sys.stdout.write("\033[2J\033[H") # Clear screen within current buffer
612
+ sys.stdout.flush()
613
+
614
+ # Run application (stays in same alternate buffer)
615
+ await app.run_async()
616
+
617
+ if result[0] is None:
618
+ raise KeyboardInterrupt()
619
+
620
+ return result[0]
621
+
622
+
623
+ ADDITION_COLORS = {
624
+ # primary first (darkened)
625
+ "dark green": "#0b3e0b",
626
+ "darker green": "#0b1f0b",
627
+ "dark aqua": "#164952",
628
+ "deep teal": "#143f3c",
629
+ # blues (darkened)
630
+ "sky blue": "#406884",
631
+ "soft blue": "#315c78",
632
+ "steel blue": "#20394e",
633
+ "forest teal": "#124831",
634
+ "cool teal": "#1b4b54",
635
+ "marine aqua": "#275860",
636
+ "slate blue": "#304f69",
637
+ "deep steel": "#1e3748",
638
+ "shadow olive": "#2f3a15",
639
+ "deep moss": "#1f3310",
640
+ # G
641
+ "midnight spruce": "#0f3a29",
642
+ "shadow jade": "#0d4a3a",
643
+ # B
644
+ "abyss blue": "#0d2f4d",
645
+ "midnight fjord": "#133552",
646
+ # I
647
+ "dusky indigo": "#1a234d",
648
+ "nocturne indigo": "#161d3f",
649
+ # V
650
+ "midnight violet": "#2a1a3f",
651
+ "deep amethyst": "#3a2860",
652
+ }
653
+
654
+ DELETION_COLORS = {
655
+ # primary first (darkened)
656
+ "dark red": "#4a0f0f",
657
+ # pinks / reds (darkened)
658
+ "pink": "#7f143b",
659
+ "soft red": "#741f3c",
660
+ "salmon": "#842848",
661
+ "rose": "#681c35",
662
+ "deep rose": "#4f1428",
663
+ # oranges (darkened)
664
+ "burnt orange": "#753b10",
665
+ "deep orange": "#5b2b0d",
666
+ # yellows (darkened)
667
+ "amber": "#69551c",
668
+ # reds (darkened)
669
+ "red": "#5d0b0b",
670
+ "ruby": "#5b141f",
671
+ "wine": "#390e1a",
672
+ # purples (darkened)
673
+ "purple": "#5a4284",
674
+ "soft purple": "#503977",
675
+ "violet": "#432758",
676
+ # ROYGBIV deletions (unchanged)
677
+ # R
678
+ "ember crimson": "#5a0e12",
679
+ "smoked ruby": "#4b0b16",
680
+ # O
681
+ "molten orange": "#70340c",
682
+ "baked amber": "#5c2b0a",
683
+ # Y
684
+ "burnt ochre": "#5a4110",
685
+ "tawny umber": "#4c3810",
686
+ # G
687
+ "swamp olive": "#3c3a14",
688
+ "bog green": "#343410",
689
+ # B
690
+ "dusky petrol": "#2a3744",
691
+ "warm slate": "#263038",
692
+ # I
693
+ "wine indigo": "#311b3f",
694
+ "mulberry dusk": "#3f1f52",
695
+ # V
696
+ "garnet plum": "#4a1e3a",
697
+ "dusky magenta": "#5a1f4c",
698
+ }
699
+
700
+
701
+ def _convert_rich_color_to_prompt_toolkit(color: str) -> str:
702
+ """Convert Rich color names to prompt-toolkit compatible names."""
703
+ # Hex colors pass through as-is
704
+ if color.startswith("#"):
705
+ return color
706
+ # Map bright_ colors to ansi equivalents
707
+ if color.startswith("bright_"):
708
+ return "ansi" + color.replace("bright_", "")
709
+ # Basic terminal colors
710
+ if color.lower() in [
711
+ "black",
712
+ "red",
713
+ "green",
714
+ "yellow",
715
+ "blue",
716
+ "magenta",
717
+ "cyan",
718
+ "white",
719
+ "gray",
720
+ "grey",
721
+ ]:
722
+ return color.lower()
723
+ # Default safe fallback for unknown colors
724
+ return "white"
725
+
726
+
727
+ def _get_preview_text_for_prompt_toolkit(config: DiffConfiguration) -> ANSI:
728
+ """Get preview as ANSI for embedding in selector with live colors.
729
+
730
+ Returns ANSI-formatted text that prompt_toolkit can render with full colors.
731
+ """
732
+ from code_puppy.tools.common import format_diff_with_colors
733
+
734
+ # Get the current language and its sample
735
+ current_lang = config.get_current_language()
736
+ filename, sample_diff = LANGUAGE_SAMPLES.get(
737
+ current_lang,
738
+ LANGUAGE_SAMPLES["python"], # Fallback to Python
739
+ )
740
+
741
+ # Build header with current settings info using Rich markup
742
+ header_parts = []
743
+ header_parts.append("[bold]═" * 50 + "[/bold]")
744
+ header_parts.append(
745
+ "[bold cyan] LIVE PREVIEW - Syntax Highlighted Diff[/bold cyan]"
746
+ )
747
+ header_parts.append("[bold]═" * 50 + "[/bold]")
748
+ header_parts.append("")
749
+ header_parts.append(f" Addition Color: [bold]{config.current_add_color}[/bold]")
750
+ header_parts.append(f" Deletion Color: [bold]{config.current_del_color}[/bold]")
751
+ header_parts.append("")
752
+ header_parts.append(
753
+ f" [bold yellow]Language: {current_lang.upper()}[/bold yellow] "
754
+ f"[dim](← → to cycle)[/dim]"
755
+ )
756
+ header_parts.append("")
757
+ header_parts.append(f"[bold] Example ({filename}):[/bold]")
758
+ header_parts.append("")
759
+
760
+ header_text = "\n".join(header_parts)
761
+
762
+ # Temporarily override config to use current preview settings
763
+ from code_puppy.config import (
764
+ get_diff_addition_color,
765
+ get_diff_deletion_color,
766
+ set_diff_addition_color,
767
+ set_diff_deletion_color,
768
+ )
769
+
770
+ # Save original values
771
+ original_add_color = get_diff_addition_color()
772
+ original_del_color = get_diff_deletion_color()
773
+
774
+ try:
775
+ # Temporarily set config to preview values
776
+ set_diff_addition_color(config.current_add_color)
777
+ set_diff_deletion_color(config.current_del_color)
778
+
779
+ # Get the formatted diff (either Rich Text or Rich markup string)
780
+ formatted_diff = format_diff_with_colors(sample_diff)
781
+
782
+ # Render everything with Rich Console to get ANSI output with proper color support
783
+ buffer = io.StringIO()
784
+ console = Console(
785
+ file=buffer,
786
+ force_terminal=True,
787
+ width=90,
788
+ legacy_windows=False,
789
+ color_system="truecolor",
790
+ no_color=False,
791
+ force_interactive=True, # Force interactive mode for better color support
792
+ )
793
+
794
+ # Print header
795
+ console.print(header_text, end="\n")
796
+
797
+ # Print diff (handles both Text objects and markup strings)
798
+ console.print(formatted_diff, end="\n\n")
799
+
800
+ # Print footer
801
+ console.print("[bold]═" * 50 + "[/bold]", end="")
802
+
803
+ ansi_output = buffer.getvalue()
804
+
805
+ finally:
806
+ # Restore original config values
807
+ set_diff_addition_color(original_add_color)
808
+ set_diff_deletion_color(original_del_color)
809
+
810
+ # Wrap in ANSI() so prompt_toolkit can render it
811
+ return ANSI(ansi_output)
812
+
813
+
814
+ async def _handle_color_menu(config: DiffConfiguration, color_type: str) -> None:
815
+ """Handle color selection with live preview updates."""
816
+ # Text mode only (highlighted disabled)
817
+ if color_type == "additions":
818
+ color_dict = ADDITION_COLORS
819
+ current = config.current_add_color
820
+ title = "Select addition color:"
821
+ else:
822
+ color_dict = DELETION_COLORS
823
+ current = config.current_del_color
824
+ title = "Select deletion color:"
825
+
826
+ # Build choices with nice names
827
+ choices = []
828
+ for name, color_value in color_dict.items():
829
+ marker = " ← current" if color_value == current else ""
830
+ choices.append(f"{name}{marker}")
831
+
832
+ # Store original color for potential cancellation
833
+ original_color = current
834
+
835
+ # Callback for live preview updates
836
+ def update_preview(selected_choice: str):
837
+ # Extract color name and look up the actual color value
838
+ color_name = selected_choice.replace(" ← current", "").strip()
839
+ selected_color = color_dict.get(color_name, list(color_dict.values())[0])
840
+ if color_type == "additions":
841
+ config.current_add_color = selected_color
842
+ else:
843
+ config.current_del_color = selected_color
844
+
845
+ # Function to get live preview header with colored diff
846
+ def get_preview_header():
847
+ return _get_preview_text_for_prompt_toolkit(config)
848
+
849
+ try:
850
+ # Use split panel selector with live preview (pass config for language switching)
851
+ await _split_panel_selector(
852
+ title,
853
+ choices,
854
+ update_preview,
855
+ get_preview=get_preview_header,
856
+ config=config,
857
+ )
858
+ except KeyboardInterrupt:
859
+ # Restore original color on cancel
860
+ if color_type == "additions":
861
+ config.current_add_color = original_color
862
+ else:
863
+ config.current_del_color = original_color
864
+ except Exception:
865
+ pass # Silent error handling