newcode 0.1.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 (289) 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 +147 -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 +630 -0
  9. code_puppy/agents/agent_golang_reviewer.py +151 -0
  10. code_puppy/agents/agent_helios.py +122 -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 +380 -0
  14. code_puppy/agents/agent_planning.py +165 -0
  15. code_puppy/agents/agent_python_programmer.py +167 -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 +2145 -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 +296 -0
  28. code_puppy/agents/pack/husky.py +307 -0
  29. code_puppy/agents/pack/retriever.py +380 -0
  30. code_puppy/agents/pack/shepherd.py +327 -0
  31. code_puppy/agents/pack/terrier.py +281 -0
  32. code_puppy/agents/pack/watchdog.py +357 -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 +674 -0
  47. code_puppy/chatgpt_codex_client.py +338 -0
  48. code_puppy/claude_cache_client.py +664 -0
  49. code_puppy/cli_runner.py +1038 -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 +526 -0
  57. code_puppy/command_line/command_handler.py +283 -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 +853 -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 +91 -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/skills_completion.py +160 -0
  97. code_puppy/command_line/uc_menu.py +893 -0
  98. code_puppy/command_line/utils.py +93 -0
  99. code_puppy/command_line/wiggum_state.py +78 -0
  100. code_puppy/config.py +1787 -0
  101. code_puppy/error_logging.py +133 -0
  102. code_puppy/gemini_code_assist.py +385 -0
  103. code_puppy/gemini_model.py +754 -0
  104. code_puppy/hook_engine/README.md +105 -0
  105. code_puppy/hook_engine/__init__.py +15 -0
  106. code_puppy/hook_engine/aliases.py +155 -0
  107. code_puppy/hook_engine/engine.py +195 -0
  108. code_puppy/hook_engine/executor.py +293 -0
  109. code_puppy/hook_engine/matcher.py +145 -0
  110. code_puppy/hook_engine/models.py +222 -0
  111. code_puppy/hook_engine/registry.py +106 -0
  112. code_puppy/hook_engine/validator.py +141 -0
  113. code_puppy/http_utils.py +361 -0
  114. code_puppy/keymap.py +128 -0
  115. code_puppy/main.py +10 -0
  116. code_puppy/mcp_/__init__.py +66 -0
  117. code_puppy/mcp_/async_lifecycle.py +286 -0
  118. code_puppy/mcp_/blocking_startup.py +469 -0
  119. code_puppy/mcp_/captured_stdio_server.py +275 -0
  120. code_puppy/mcp_/circuit_breaker.py +290 -0
  121. code_puppy/mcp_/config_wizard.py +507 -0
  122. code_puppy/mcp_/dashboard.py +308 -0
  123. code_puppy/mcp_/error_isolation.py +407 -0
  124. code_puppy/mcp_/examples/retry_example.py +226 -0
  125. code_puppy/mcp_/health_monitor.py +589 -0
  126. code_puppy/mcp_/managed_server.py +428 -0
  127. code_puppy/mcp_/manager.py +807 -0
  128. code_puppy/mcp_/mcp_logs.py +224 -0
  129. code_puppy/mcp_/registry.py +451 -0
  130. code_puppy/mcp_/retry_manager.py +337 -0
  131. code_puppy/mcp_/server_registry_catalog.py +1126 -0
  132. code_puppy/mcp_/status_tracker.py +355 -0
  133. code_puppy/mcp_/system_tools.py +209 -0
  134. code_puppy/mcp_prompts/__init__.py +1 -0
  135. code_puppy/mcp_prompts/hook_creator.py +103 -0
  136. code_puppy/messaging/__init__.py +255 -0
  137. code_puppy/messaging/bus.py +613 -0
  138. code_puppy/messaging/commands.py +167 -0
  139. code_puppy/messaging/markdown_patches.py +57 -0
  140. code_puppy/messaging/message_queue.py +361 -0
  141. code_puppy/messaging/messages.py +569 -0
  142. code_puppy/messaging/queue_console.py +271 -0
  143. code_puppy/messaging/renderers.py +311 -0
  144. code_puppy/messaging/rich_renderer.py +1153 -0
  145. code_puppy/messaging/spinner/__init__.py +83 -0
  146. code_puppy/messaging/spinner/console_spinner.py +240 -0
  147. code_puppy/messaging/spinner/spinner_base.py +96 -0
  148. code_puppy/messaging/subagent_console.py +460 -0
  149. code_puppy/model_factory.py +848 -0
  150. code_puppy/model_switching.py +63 -0
  151. code_puppy/model_utils.py +168 -0
  152. code_puppy/models.json +130 -0
  153. code_puppy/models_dev_api.json +1 -0
  154. code_puppy/models_dev_parser.py +592 -0
  155. code_puppy/plugins/__init__.py +186 -0
  156. code_puppy/plugins/agent_skills/__init__.py +22 -0
  157. code_puppy/plugins/agent_skills/config.py +175 -0
  158. code_puppy/plugins/agent_skills/discovery.py +136 -0
  159. code_puppy/plugins/agent_skills/downloader.py +392 -0
  160. code_puppy/plugins/agent_skills/installer.py +22 -0
  161. code_puppy/plugins/agent_skills/metadata.py +219 -0
  162. code_puppy/plugins/agent_skills/prompt_builder.py +100 -0
  163. code_puppy/plugins/agent_skills/register_callbacks.py +241 -0
  164. code_puppy/plugins/agent_skills/remote_catalog.py +322 -0
  165. code_puppy/plugins/agent_skills/skill_catalog.py +257 -0
  166. code_puppy/plugins/agent_skills/skills_install_menu.py +664 -0
  167. code_puppy/plugins/agent_skills/skills_menu.py +781 -0
  168. code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
  169. code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
  170. code_puppy/plugins/antigravity_oauth/antigravity_model.py +706 -0
  171. code_puppy/plugins/antigravity_oauth/config.py +42 -0
  172. code_puppy/plugins/antigravity_oauth/constants.py +133 -0
  173. code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
  174. code_puppy/plugins/antigravity_oauth/register_callbacks.py +518 -0
  175. code_puppy/plugins/antigravity_oauth/storage.py +288 -0
  176. code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
  177. code_puppy/plugins/antigravity_oauth/token.py +167 -0
  178. code_puppy/plugins/antigravity_oauth/transport.py +863 -0
  179. code_puppy/plugins/antigravity_oauth/utils.py +168 -0
  180. code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
  181. code_puppy/plugins/chatgpt_oauth/config.py +52 -0
  182. code_puppy/plugins/chatgpt_oauth/oauth_flow.py +328 -0
  183. code_puppy/plugins/chatgpt_oauth/register_callbacks.py +176 -0
  184. code_puppy/plugins/chatgpt_oauth/test_plugin.py +295 -0
  185. code_puppy/plugins/chatgpt_oauth/utils.py +499 -0
  186. code_puppy/plugins/claude_code_hooks/__init__.py +1 -0
  187. code_puppy/plugins/claude_code_hooks/config.py +131 -0
  188. code_puppy/plugins/claude_code_hooks/register_callbacks.py +163 -0
  189. code_puppy/plugins/claude_code_oauth/README.md +167 -0
  190. code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
  191. code_puppy/plugins/claude_code_oauth/__init__.py +25 -0
  192. code_puppy/plugins/claude_code_oauth/config.py +52 -0
  193. code_puppy/plugins/claude_code_oauth/register_callbacks.py +453 -0
  194. code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
  195. code_puppy/plugins/claude_code_oauth/token_refresh_heartbeat.py +241 -0
  196. code_puppy/plugins/claude_code_oauth/utils.py +601 -0
  197. code_puppy/plugins/customizable_commands/__init__.py +0 -0
  198. code_puppy/plugins/customizable_commands/register_callbacks.py +152 -0
  199. code_puppy/plugins/example_custom_command/README.md +280 -0
  200. code_puppy/plugins/example_custom_command/register_callbacks.py +48 -0
  201. code_puppy/plugins/file_permission_handler/__init__.py +4 -0
  202. code_puppy/plugins/file_permission_handler/register_callbacks.py +528 -0
  203. code_puppy/plugins/frontend_emitter/__init__.py +25 -0
  204. code_puppy/plugins/frontend_emitter/emitter.py +121 -0
  205. code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
  206. code_puppy/plugins/hook_creator/__init__.py +1 -0
  207. code_puppy/plugins/hook_creator/register_callbacks.py +33 -0
  208. code_puppy/plugins/hook_manager/__init__.py +1 -0
  209. code_puppy/plugins/hook_manager/config.py +277 -0
  210. code_puppy/plugins/hook_manager/hooks_menu.py +551 -0
  211. code_puppy/plugins/hook_manager/register_callbacks.py +205 -0
  212. code_puppy/plugins/oauth_puppy_html.py +224 -0
  213. code_puppy/plugins/scheduler/__init__.py +1 -0
  214. code_puppy/plugins/scheduler/register_callbacks.py +88 -0
  215. code_puppy/plugins/scheduler/scheduler_menu.py +522 -0
  216. code_puppy/plugins/scheduler/scheduler_wizard.py +341 -0
  217. code_puppy/plugins/shell_safety/__init__.py +6 -0
  218. code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
  219. code_puppy/plugins/shell_safety/command_cache.py +156 -0
  220. code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
  221. code_puppy/plugins/synthetic_status/__init__.py +1 -0
  222. code_puppy/plugins/synthetic_status/register_callbacks.py +132 -0
  223. code_puppy/plugins/synthetic_status/status_api.py +147 -0
  224. code_puppy/plugins/universal_constructor/__init__.py +13 -0
  225. code_puppy/plugins/universal_constructor/models.py +138 -0
  226. code_puppy/plugins/universal_constructor/register_callbacks.py +47 -0
  227. code_puppy/plugins/universal_constructor/registry.py +302 -0
  228. code_puppy/plugins/universal_constructor/sandbox.py +584 -0
  229. code_puppy/prompts/antigravity_system_prompt.md +1 -0
  230. code_puppy/pydantic_patches.py +317 -0
  231. code_puppy/reopenable_async_client.py +232 -0
  232. code_puppy/round_robin_model.py +150 -0
  233. code_puppy/scheduler/__init__.py +41 -0
  234. code_puppy/scheduler/__main__.py +9 -0
  235. code_puppy/scheduler/cli.py +118 -0
  236. code_puppy/scheduler/config.py +126 -0
  237. code_puppy/scheduler/daemon.py +280 -0
  238. code_puppy/scheduler/executor.py +155 -0
  239. code_puppy/scheduler/platform.py +19 -0
  240. code_puppy/scheduler/platform_unix.py +22 -0
  241. code_puppy/scheduler/platform_win.py +32 -0
  242. code_puppy/session_storage.py +338 -0
  243. code_puppy/status_display.py +257 -0
  244. code_puppy/summarization_agent.py +176 -0
  245. code_puppy/terminal_utils.py +418 -0
  246. code_puppy/tools/__init__.py +470 -0
  247. code_puppy/tools/agent_tools.py +616 -0
  248. code_puppy/tools/ask_user_question/__init__.py +26 -0
  249. code_puppy/tools/ask_user_question/constants.py +73 -0
  250. code_puppy/tools/ask_user_question/demo_tui.py +55 -0
  251. code_puppy/tools/ask_user_question/handler.py +232 -0
  252. code_puppy/tools/ask_user_question/models.py +304 -0
  253. code_puppy/tools/ask_user_question/registration.py +36 -0
  254. code_puppy/tools/ask_user_question/renderers.py +309 -0
  255. code_puppy/tools/ask_user_question/terminal_ui.py +329 -0
  256. code_puppy/tools/ask_user_question/theme.py +155 -0
  257. code_puppy/tools/ask_user_question/tui_loop.py +423 -0
  258. code_puppy/tools/browser/__init__.py +37 -0
  259. code_puppy/tools/browser/browser_control.py +289 -0
  260. code_puppy/tools/browser/browser_interactions.py +545 -0
  261. code_puppy/tools/browser/browser_locators.py +640 -0
  262. code_puppy/tools/browser/browser_manager.py +378 -0
  263. code_puppy/tools/browser/browser_navigation.py +251 -0
  264. code_puppy/tools/browser/browser_screenshot.py +179 -0
  265. code_puppy/tools/browser/browser_scripts.py +462 -0
  266. code_puppy/tools/browser/browser_workflows.py +221 -0
  267. code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
  268. code_puppy/tools/browser/terminal_command_tools.py +534 -0
  269. code_puppy/tools/browser/terminal_screenshot_tools.py +552 -0
  270. code_puppy/tools/browser/terminal_tools.py +525 -0
  271. code_puppy/tools/command_runner.py +1346 -0
  272. code_puppy/tools/common.py +1409 -0
  273. code_puppy/tools/display.py +84 -0
  274. code_puppy/tools/file_modifications.py +739 -0
  275. code_puppy/tools/file_operations.py +802 -0
  276. code_puppy/tools/scheduler_tools.py +412 -0
  277. code_puppy/tools/skills_tools.py +251 -0
  278. code_puppy/tools/subagent_context.py +158 -0
  279. code_puppy/tools/tools_content.py +51 -0
  280. code_puppy/tools/universal_constructor.py +889 -0
  281. code_puppy/uvx_detection.py +242 -0
  282. code_puppy/version_checker.py +82 -0
  283. newcode-0.1.1.data/data/code_puppy/models.json +130 -0
  284. newcode-0.1.1.data/data/code_puppy/models_dev_api.json +1 -0
  285. newcode-0.1.1.dist-info/METADATA +154 -0
  286. newcode-0.1.1.dist-info/RECORD +289 -0
  287. newcode-0.1.1.dist-info/WHEEL +4 -0
  288. newcode-0.1.1.dist-info/entry_points.txt +3 -0
  289. newcode-0.1.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,242 @@
1
+ """Detect if code-puppy was launched via uvx on Windows.
2
+
3
+ This module provides utilities to detect the launch method of code-puppy,
4
+ specifically to handle signal differences when running via uvx on Windows.
5
+
6
+ On Windows, when launched via `uvx code-puppy`, Ctrl+C (SIGINT) gets captured
7
+ by uvx's process handling before reaching our Python process. To work around
8
+ this, we detect the uvx launch scenario and switch to Ctrl+K for cancellation.
9
+
10
+ Note: This issue is specific to uvx.exe, NOT uv.exe. Running via `uv run`
11
+ handles SIGINT correctly on Windows.
12
+
13
+ On non-Windows platforms, this is not an issue - Ctrl+C works fine with uvx.
14
+ """
15
+
16
+ import os
17
+ import platform
18
+ import sys
19
+ from functools import lru_cache
20
+ from typing import Optional
21
+
22
+ # Cache the detection result - it won't change during runtime
23
+ _uvx_detection_cache: Optional[bool] = None
24
+
25
+
26
+ def _get_parent_process_name_psutil(pid: int) -> Optional[str]:
27
+ """Get parent process name using psutil (if available).
28
+
29
+ Args:
30
+ pid: Process ID to get parent name for
31
+
32
+ Returns:
33
+ Parent process name (lowercase) or None if not found
34
+ """
35
+ try:
36
+ import psutil
37
+
38
+ proc = psutil.Process(pid)
39
+ parent = proc.parent()
40
+ if parent:
41
+ return parent.name().lower()
42
+ except Exception:
43
+ pass
44
+ return None
45
+
46
+
47
+ def _get_parent_process_chain_psutil() -> list[str]:
48
+ """Get the entire parent process chain using psutil.
49
+
50
+ Returns:
51
+ List of process names from current process up to init/System
52
+ """
53
+ chain = []
54
+ try:
55
+ import psutil
56
+
57
+ proc = psutil.Process(os.getpid())
58
+ while proc:
59
+ chain.append(proc.name().lower())
60
+ parent = proc.parent()
61
+ if parent is None or parent.pid in (0, proc.pid):
62
+ break
63
+ proc = parent
64
+ except Exception:
65
+ pass
66
+ return chain
67
+
68
+
69
+ def _get_parent_process_chain_windows_ctypes() -> list[str]:
70
+ """Get parent process chain on Windows using ctypes (no external deps).
71
+
72
+ This is a fallback when psutil is not available.
73
+
74
+ Returns:
75
+ List of process names from current process up to System
76
+ """
77
+ if platform.system() != "Windows":
78
+ return []
79
+
80
+ chain = []
81
+ try:
82
+ import ctypes
83
+ from ctypes import wintypes
84
+
85
+ # Windows API constants
86
+ TH32CS_SNAPPROCESS = 0x00000002
87
+ INVALID_HANDLE_VALUE = -1
88
+
89
+ class PROCESSENTRY32(ctypes.Structure):
90
+ _fields_ = [
91
+ ("dwSize", wintypes.DWORD),
92
+ ("cntUsage", wintypes.DWORD),
93
+ ("th32ProcessID", wintypes.DWORD),
94
+ ("th32DefaultHeapID", ctypes.POINTER(wintypes.ULONG)),
95
+ ("th32ModuleID", wintypes.DWORD),
96
+ ("cntThreads", wintypes.DWORD),
97
+ ("th32ParentProcessID", wintypes.DWORD),
98
+ ("pcPriClassBase", wintypes.LONG),
99
+ ("dwFlags", wintypes.DWORD),
100
+ ("szExeFile", ctypes.c_char * 260),
101
+ ]
102
+
103
+ kernel32 = ctypes.windll.kernel32
104
+
105
+ # Take a snapshot of all processes
106
+ snapshot = kernel32.CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)
107
+ if snapshot == INVALID_HANDLE_VALUE:
108
+ return chain
109
+
110
+ try:
111
+ # Build a map of PID -> (parent_pid, exe_name)
112
+ process_map: dict[int, tuple[int, str]] = {}
113
+ pe = PROCESSENTRY32()
114
+ pe.dwSize = ctypes.sizeof(PROCESSENTRY32)
115
+
116
+ if kernel32.Process32First(snapshot, ctypes.byref(pe)):
117
+ while True:
118
+ pid = pe.th32ProcessID
119
+ parent_pid = pe.th32ParentProcessID
120
+ exe_name = pe.szExeFile.decode("utf-8", errors="ignore").lower()
121
+ process_map[pid] = (parent_pid, exe_name)
122
+
123
+ if not kernel32.Process32Next(snapshot, ctypes.byref(pe)):
124
+ break
125
+
126
+ # Traverse from current PID up the parent chain
127
+ current_pid = os.getpid()
128
+ visited = set() # Prevent infinite loops
129
+
130
+ while current_pid in process_map and current_pid not in visited:
131
+ visited.add(current_pid)
132
+ parent_pid, exe_name = process_map[current_pid]
133
+ chain.append(exe_name)
134
+
135
+ if parent_pid == 0 or parent_pid == current_pid:
136
+ break
137
+ current_pid = parent_pid
138
+
139
+ finally:
140
+ kernel32.CloseHandle(snapshot)
141
+
142
+ except Exception:
143
+ pass
144
+
145
+ return chain
146
+
147
+
148
+ def _get_parent_process_chain() -> list[str]:
149
+ """Get the parent process chain using best available method.
150
+
151
+ Returns:
152
+ List of process names from current process up to init/System
153
+ """
154
+ # Try psutil first (more reliable, cross-platform)
155
+ try:
156
+ import psutil # noqa: F401
157
+
158
+ return _get_parent_process_chain_psutil()
159
+ except ImportError:
160
+ pass
161
+
162
+ # Fall back to ctypes on Windows
163
+ if platform.system() == "Windows":
164
+ return _get_parent_process_chain_windows_ctypes()
165
+
166
+ return []
167
+
168
+
169
+ def _is_uvx_in_chain(chain: list[str]) -> bool:
170
+ """Check if uvx is in the process chain.
171
+
172
+ Note: We only check for uvx.exe, NOT uv.exe. The uv.exe binary
173
+ (used by `uv run`) handles SIGINT correctly on Windows, but
174
+ uvx.exe captures it before it reaches Python.
175
+
176
+ Args:
177
+ chain: List of process names (lowercase)
178
+
179
+ Returns:
180
+ True if uvx.exe is found in the chain
181
+ """
182
+ # Only uvx.exe has the SIGINT issue, not uv.exe
183
+ uvx_names = {"uvx.exe", "uvx"}
184
+ return any(name in uvx_names for name in chain)
185
+
186
+
187
+ @lru_cache(maxsize=1)
188
+ def is_launched_via_uvx() -> bool:
189
+ """Detect if code-puppy was launched via uvx.
190
+
191
+ Traverses the parent process chain to find uvx.exe or uv.exe.
192
+ Result is cached for the lifetime of the process.
193
+
194
+ Returns:
195
+ True if launched via uvx, False otherwise
196
+ """
197
+ chain = _get_parent_process_chain()
198
+ return _is_uvx_in_chain(chain)
199
+
200
+
201
+ def is_windows() -> bool:
202
+ """Check if we're running on Windows.
203
+
204
+ Returns:
205
+ True if running on Windows, False otherwise
206
+ """
207
+ return platform.system() == "Windows"
208
+
209
+
210
+ def should_use_alternate_cancel_key() -> bool:
211
+ """Determine if we should use an alternate cancel key (Ctrl+K) instead of Ctrl+C.
212
+
213
+ This returns True when:
214
+ - Running on Windows AND
215
+ - Launched via uvx
216
+
217
+ In this scenario, Ctrl+C is captured by uvx before reaching Python,
218
+ so we need to use a different key (Ctrl+K) for agent cancellation.
219
+
220
+ Returns:
221
+ True if alternate cancel key should be used, False otherwise
222
+ """
223
+ return is_windows() and is_launched_via_uvx()
224
+
225
+
226
+ def get_uvx_detection_info() -> dict:
227
+ """Get diagnostic information about uvx detection.
228
+
229
+ Useful for debugging and testing.
230
+
231
+ Returns:
232
+ Dictionary with detection details
233
+ """
234
+ chain = _get_parent_process_chain()
235
+ return {
236
+ "is_windows": is_windows(),
237
+ "is_launched_via_uvx": is_launched_via_uvx(),
238
+ "should_use_alternate_cancel_key": should_use_alternate_cancel_key(),
239
+ "parent_process_chain": chain,
240
+ "current_pid": os.getpid(),
241
+ "python_executable": sys.executable,
242
+ }
@@ -0,0 +1,82 @@
1
+ """Version checking utilities."""
2
+
3
+ import httpx
4
+
5
+ from code_puppy.messaging import emit_info, emit_success, emit_warning, get_message_bus
6
+ from code_puppy.messaging.messages import VersionCheckMessage
7
+
8
+
9
+ def normalize_version(version_str):
10
+ if not version_str:
11
+ return version_str
12
+ version_str = version_str.lstrip("v")
13
+ return version_str
14
+
15
+
16
+ def _version_tuple(version_str):
17
+ """Convert version string to tuple of ints for proper comparison."""
18
+ try:
19
+ return tuple(int(x) for x in version_str.split("."))
20
+ except (ValueError, AttributeError):
21
+ return None
22
+
23
+
24
+ def version_is_newer(latest, current):
25
+ """Return True if latest version is strictly newer than current."""
26
+ latest_tuple = _version_tuple(normalize_version(latest))
27
+ current_tuple = _version_tuple(normalize_version(current))
28
+ if latest_tuple is None or current_tuple is None:
29
+ return False
30
+ return latest_tuple > current_tuple
31
+
32
+
33
+ def versions_are_equal(current, latest):
34
+ current_norm = normalize_version(current)
35
+ latest_norm = normalize_version(latest)
36
+ # Try numeric tuple comparison first
37
+ current_tuple = _version_tuple(current_norm)
38
+ latest_tuple = _version_tuple(latest_norm)
39
+ if current_tuple is not None and latest_tuple is not None:
40
+ return current_tuple == latest_tuple
41
+ # Fallback to string comparison
42
+ return current_norm == latest_norm
43
+
44
+
45
+ def fetch_latest_version(package_name):
46
+ try:
47
+ response = httpx.get(f"https://pypi.org/pypi/{package_name}/json", timeout=5.0)
48
+ response.raise_for_status()
49
+ data = response.json()
50
+ return data["info"]["version"]
51
+ except Exception as e:
52
+ emit_warning(f"Error fetching version: {e}")
53
+ return None
54
+
55
+
56
+ def default_version_mismatch_behavior(current_version):
57
+ # Defensive: ensure current_version is never None
58
+ if current_version is None:
59
+ current_version = "0.0.0-unknown"
60
+ emit_warning("Could not detect current version, using fallback")
61
+
62
+ latest_version = fetch_latest_version("newcode")
63
+
64
+ update_available = bool(
65
+ latest_version and version_is_newer(latest_version, current_version)
66
+ )
67
+
68
+ # Emit structured version check message
69
+ version_msg = VersionCheckMessage(
70
+ current_version=current_version,
71
+ latest_version=latest_version or current_version,
72
+ update_available=update_available,
73
+ )
74
+ get_message_bus().emit(version_msg)
75
+
76
+ # Also emit plain text for legacy renderer
77
+ emit_info(f"Current version: {current_version}")
78
+
79
+ if update_available:
80
+ emit_info(f"Latest version: {latest_version}")
81
+ emit_warning(f"A new version is available: {latest_version}")
82
+ emit_success("Please consider updating!")
@@ -0,0 +1,130 @@
1
+ {
2
+ "synthetic-GLM-4.7": {
3
+ "type": "custom_openai",
4
+ "name": "hf:zai-org/GLM-4.7",
5
+ "custom_endpoint": {
6
+ "url": "https://api.synthetic.new/openai/v1/",
7
+ "api_key": "$SYN_API_KEY"
8
+ },
9
+ "context_length": 200000,
10
+ "supported_settings": ["temperature", "seed", "top_p"]
11
+ },
12
+ "synthetic-MiniMax-M2.1": {
13
+ "type": "custom_openai",
14
+ "name": "hf:MiniMaxAI/MiniMax-M2.1",
15
+ "custom_endpoint": {
16
+ "url": "https://api.synthetic.new/openai/v1/",
17
+ "api_key": "$SYN_API_KEY"
18
+ },
19
+ "context_length": 195000,
20
+ "supported_settings": ["temperature", "seed", "top_p"]
21
+ },
22
+ "synthetic-Kimi-K2-Thinking": {
23
+ "type": "custom_openai",
24
+ "name": "hf:moonshotai/Kimi-K2-Thinking",
25
+ "custom_endpoint": {
26
+ "url": "https://api.synthetic.new/openai/v1/",
27
+ "api_key": "$SYN_API_KEY"
28
+ },
29
+ "context_length": 256000,
30
+ "supported_settings": ["temperature", "seed", "top_p"]
31
+ },
32
+ "synthetic-Kimi-K2.5-Thinking": {
33
+ "type": "custom_openai",
34
+ "name": "hf:moonshotai/Kimi-K2.5",
35
+ "custom_endpoint": {
36
+ "url": "https://api.synthetic.new/openai/v1/",
37
+ "api_key": "$SYN_API_KEY"
38
+ },
39
+ "context_length": 256000,
40
+ "supported_settings": ["temperature", "seed", "top_p"]
41
+ },
42
+ "synthetic-Kimi-K2.5-Thinking-NVFP4": {
43
+ "type": "custom_openai",
44
+ "name": "hf:nvidia/Kimi-K2.5-NVFP4",
45
+ "custom_endpoint": {
46
+ "url": "https://api.synthetic.new/openai/v1/",
47
+ "api_key": "$SYN_API_KEY"
48
+ },
49
+ "context_length": 256000,
50
+ "supported_settings": ["temperature", "seed", "top_p"]
51
+ },
52
+ "Gemini-3": {
53
+ "type": "gemini",
54
+ "name": "gemini-3-pro-preview",
55
+ "context_length": 200000,
56
+ "supported_settings": ["temperature", "top_p", "thinking_enabled", "thinking_level"]
57
+ },
58
+ "Gemini-3-Long-Context": {
59
+ "type": "gemini",
60
+ "name": "gemini-3-pro-preview",
61
+ "context_length": 1000000,
62
+ "supported_settings": ["temperature", "top_p", "thinking_enabled", "thinking_level"]
63
+ },
64
+ "gpt-5.2": {
65
+ "type": "openai",
66
+ "name": "gpt-5.1",
67
+ "context_length": 272000,
68
+ "supported_settings": ["temperature", "top_p", "reasoning_effort", "verbosity"],
69
+ "supports_xhigh_reasoning": false
70
+ },
71
+ "gpt-5.2-codex-api": {
72
+ "type": "openai",
73
+ "name": "gpt-5.2-codex",
74
+ "context_length": 272000,
75
+ "supported_settings": ["temperature", "top_p", "reasoning_effort", "verbosity"],
76
+ "supports_xhigh_reasoning": true
77
+ },
78
+ "Cerebras-GLM-4.7": {
79
+ "type": "cerebras",
80
+ "name": "zai-glm-4.7",
81
+ "custom_endpoint": {
82
+ "url": "https://api.cerebras.ai/v1",
83
+ "api_key": "$CEREBRAS_API_KEY"
84
+ },
85
+ "context_length": 131072,
86
+ "supported_settings": ["temperature", "seed", "top_p"]
87
+ },
88
+ "claude-4-5-haiku": {
89
+ "type": "anthropic",
90
+ "name": "claude-haiku-4-5",
91
+ "context_length": 200000,
92
+ "supported_settings": ["temperature", "extended_thinking", "budget_tokens"]
93
+ },
94
+ "claude-4-5-sonnet": {
95
+ "type": "anthropic",
96
+ "name": "claude-sonnet-4-5",
97
+ "context_length": 200000,
98
+ "supported_settings": ["temperature", "extended_thinking", "budget_tokens"]
99
+ },
100
+ "claude-4-5-opus": {
101
+ "type": "anthropic",
102
+ "name": "claude-opus-4-5",
103
+ "context_length": 200000,
104
+ "supported_settings": ["temperature", "extended_thinking", "budget_tokens", "interleaved_thinking"]
105
+ },
106
+ "zai-glm-4.7-coding": {
107
+ "type": "zai_coding",
108
+ "name": "glm-4.7",
109
+ "context_length": 200000,
110
+ "supported_settings": ["temperature", "top_p"]
111
+ },
112
+ "zai-glm-4.7-api": {
113
+ "type": "zai_api",
114
+ "name": "glm-4.7",
115
+ "context_length": 200000,
116
+ "supported_settings": ["temperature", "extended_thinking", "budget_tokens", "interleaved_thinking"]
117
+ },
118
+ "zai-glm-5-coding": {
119
+ "type": "zai_coding",
120
+ "name": "glm-5",
121
+ "context_length": 200000,
122
+ "supported_settings": ["temperature", "top_p"]
123
+ },
124
+ "zai-glm-5-api": {
125
+ "type": "zai_api",
126
+ "name": "glm-5",
127
+ "context_length": 200000,
128
+ "supported_settings": ["temperature", "top_p"]
129
+ }
130
+ }