llms-py 2.0.20__py3-none-any.whl → 3.0.18__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 (207) hide show
  1. llms/__init__.py +3 -1
  2. llms/db.py +359 -0
  3. llms/{ui/Analytics.mjs → extensions/analytics/ui/index.mjs} +254 -327
  4. llms/extensions/app/README.md +20 -0
  5. llms/extensions/app/__init__.py +588 -0
  6. llms/extensions/app/db.py +540 -0
  7. llms/{ui → extensions/app/ui}/Recents.mjs +99 -73
  8. llms/{ui/Sidebar.mjs → extensions/app/ui/index.mjs} +139 -68
  9. llms/extensions/app/ui/threadStore.mjs +440 -0
  10. llms/extensions/computer/README.md +96 -0
  11. llms/extensions/computer/__init__.py +59 -0
  12. llms/extensions/computer/base.py +80 -0
  13. llms/extensions/computer/bash.py +185 -0
  14. llms/extensions/computer/computer.py +523 -0
  15. llms/extensions/computer/edit.py +299 -0
  16. llms/extensions/computer/filesystem.py +542 -0
  17. llms/extensions/computer/platform.py +461 -0
  18. llms/extensions/computer/run.py +37 -0
  19. llms/extensions/core_tools/CALCULATOR.md +32 -0
  20. llms/extensions/core_tools/__init__.py +599 -0
  21. llms/extensions/core_tools/ui/codemirror/addon/edit/closebrackets.js +201 -0
  22. llms/extensions/core_tools/ui/codemirror/addon/edit/closetag.js +185 -0
  23. llms/extensions/core_tools/ui/codemirror/addon/edit/continuelist.js +101 -0
  24. llms/extensions/core_tools/ui/codemirror/addon/edit/matchbrackets.js +160 -0
  25. llms/extensions/core_tools/ui/codemirror/addon/edit/matchtags.js +66 -0
  26. llms/extensions/core_tools/ui/codemirror/addon/edit/trailingspace.js +27 -0
  27. llms/extensions/core_tools/ui/codemirror/addon/selection/active-line.js +72 -0
  28. llms/extensions/core_tools/ui/codemirror/addon/selection/mark-selection.js +119 -0
  29. llms/extensions/core_tools/ui/codemirror/addon/selection/selection-pointer.js +98 -0
  30. llms/extensions/core_tools/ui/codemirror/codemirror.css +344 -0
  31. llms/extensions/core_tools/ui/codemirror/codemirror.js +9884 -0
  32. llms/extensions/core_tools/ui/codemirror/doc/docs.css +225 -0
  33. llms/extensions/core_tools/ui/codemirror/doc/source_sans.woff +0 -0
  34. llms/extensions/core_tools/ui/codemirror/mode/clike/clike.js +942 -0
  35. llms/extensions/core_tools/ui/codemirror/mode/javascript/index.html +118 -0
  36. llms/extensions/core_tools/ui/codemirror/mode/javascript/javascript.js +962 -0
  37. llms/extensions/core_tools/ui/codemirror/mode/javascript/typescript.html +62 -0
  38. llms/extensions/core_tools/ui/codemirror/mode/python/python.js +402 -0
  39. llms/extensions/core_tools/ui/codemirror/theme/dracula.css +40 -0
  40. llms/extensions/core_tools/ui/codemirror/theme/mocha.css +135 -0
  41. llms/extensions/core_tools/ui/index.mjs +650 -0
  42. llms/extensions/gallery/README.md +61 -0
  43. llms/extensions/gallery/__init__.py +63 -0
  44. llms/extensions/gallery/db.py +243 -0
  45. llms/extensions/gallery/ui/index.mjs +482 -0
  46. llms/extensions/katex/README.md +39 -0
  47. llms/extensions/katex/__init__.py +6 -0
  48. llms/extensions/katex/ui/README.md +125 -0
  49. llms/extensions/katex/ui/contrib/auto-render.js +338 -0
  50. llms/extensions/katex/ui/contrib/auto-render.min.js +1 -0
  51. llms/extensions/katex/ui/contrib/auto-render.mjs +244 -0
  52. llms/extensions/katex/ui/contrib/copy-tex.js +127 -0
  53. llms/extensions/katex/ui/contrib/copy-tex.min.js +1 -0
  54. llms/extensions/katex/ui/contrib/copy-tex.mjs +105 -0
  55. llms/extensions/katex/ui/contrib/mathtex-script-type.js +109 -0
  56. llms/extensions/katex/ui/contrib/mathtex-script-type.min.js +1 -0
  57. llms/extensions/katex/ui/contrib/mathtex-script-type.mjs +24 -0
  58. llms/extensions/katex/ui/contrib/mhchem.js +3213 -0
  59. llms/extensions/katex/ui/contrib/mhchem.min.js +1 -0
  60. llms/extensions/katex/ui/contrib/mhchem.mjs +3109 -0
  61. llms/extensions/katex/ui/contrib/render-a11y-string.js +887 -0
  62. llms/extensions/katex/ui/contrib/render-a11y-string.min.js +1 -0
  63. llms/extensions/katex/ui/contrib/render-a11y-string.mjs +800 -0
  64. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.ttf +0 -0
  65. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff +0 -0
  66. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  67. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  68. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  69. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  70. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  71. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  72. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  73. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  74. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  75. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  76. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  77. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  78. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  79. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.ttf +0 -0
  80. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff +0 -0
  81. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff2 +0 -0
  82. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  83. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  84. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  85. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.ttf +0 -0
  86. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff +0 -0
  87. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff2 +0 -0
  88. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.ttf +0 -0
  89. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff +0 -0
  90. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff2 +0 -0
  91. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  92. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  93. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  94. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.ttf +0 -0
  95. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff +0 -0
  96. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff2 +0 -0
  97. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  98. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  99. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  100. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  101. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  102. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  103. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  104. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  105. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  106. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.ttf +0 -0
  107. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff +0 -0
  108. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff2 +0 -0
  109. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.ttf +0 -0
  110. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff +0 -0
  111. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  112. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.ttf +0 -0
  113. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff +0 -0
  114. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  115. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.ttf +0 -0
  116. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff +0 -0
  117. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  118. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.ttf +0 -0
  119. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff +0 -0
  120. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  121. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  122. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  123. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  124. llms/extensions/katex/ui/index.mjs +92 -0
  125. llms/extensions/katex/ui/katex-swap.css +1230 -0
  126. llms/extensions/katex/ui/katex-swap.min.css +1 -0
  127. llms/extensions/katex/ui/katex.css +1230 -0
  128. llms/extensions/katex/ui/katex.js +19080 -0
  129. llms/extensions/katex/ui/katex.min.css +1 -0
  130. llms/extensions/katex/ui/katex.min.js +1 -0
  131. llms/extensions/katex/ui/katex.min.mjs +1 -0
  132. llms/extensions/katex/ui/katex.mjs +18547 -0
  133. llms/extensions/providers/__init__.py +22 -0
  134. llms/extensions/providers/anthropic.py +260 -0
  135. llms/extensions/providers/cerebras.py +36 -0
  136. llms/extensions/providers/chutes.py +153 -0
  137. llms/extensions/providers/google.py +559 -0
  138. llms/extensions/providers/nvidia.py +103 -0
  139. llms/extensions/providers/openai.py +154 -0
  140. llms/extensions/providers/openrouter.py +74 -0
  141. llms/extensions/providers/zai.py +182 -0
  142. llms/extensions/skills/LICENSE +202 -0
  143. llms/extensions/skills/__init__.py +130 -0
  144. llms/extensions/skills/errors.py +25 -0
  145. llms/extensions/skills/models.py +39 -0
  146. llms/extensions/skills/parser.py +178 -0
  147. llms/extensions/skills/ui/index.mjs +376 -0
  148. llms/extensions/skills/ui/skills/create-plan/SKILL.md +74 -0
  149. llms/extensions/skills/validator.py +177 -0
  150. llms/extensions/system_prompts/README.md +22 -0
  151. llms/extensions/system_prompts/__init__.py +45 -0
  152. llms/extensions/system_prompts/ui/index.mjs +276 -0
  153. llms/extensions/system_prompts/ui/prompts.json +1067 -0
  154. llms/extensions/tools/__init__.py +67 -0
  155. llms/extensions/tools/ui/index.mjs +837 -0
  156. llms/index.html +36 -62
  157. llms/llms.json +180 -879
  158. llms/main.py +4009 -912
  159. llms/providers-extra.json +394 -0
  160. llms/providers.json +1 -0
  161. llms/ui/App.mjs +176 -8
  162. llms/ui/ai.mjs +156 -20
  163. llms/ui/app.css +3768 -321
  164. llms/ui/ctx.mjs +459 -0
  165. llms/ui/index.mjs +131 -0
  166. llms/ui/lib/chart.js +14 -0
  167. llms/ui/lib/charts.mjs +16 -0
  168. llms/ui/lib/color.js +14 -0
  169. llms/ui/lib/highlight.min.mjs +1243 -0
  170. llms/ui/lib/idb.min.mjs +8 -0
  171. llms/ui/lib/marked.min.mjs +8 -0
  172. llms/ui/lib/servicestack-client.mjs +1 -0
  173. llms/ui/lib/servicestack-vue.mjs +37 -0
  174. llms/ui/lib/vue-router.min.mjs +6 -0
  175. llms/ui/lib/vue.min.mjs +13 -0
  176. llms/ui/lib/vue.mjs +18530 -0
  177. llms/ui/markdown.mjs +25 -14
  178. llms/ui/modules/chat/ChatBody.mjs +1156 -0
  179. llms/ui/{SettingsDialog.mjs → modules/chat/SettingsDialog.mjs} +74 -74
  180. llms/ui/modules/chat/index.mjs +995 -0
  181. llms/ui/modules/icons.mjs +46 -0
  182. llms/ui/modules/layout.mjs +271 -0
  183. llms/ui/modules/model-selector.mjs +811 -0
  184. llms/ui/tailwind.input.css +560 -78
  185. llms/ui/typography.css +54 -36
  186. llms/ui/utils.mjs +221 -92
  187. llms_py-3.0.18.dist-info/METADATA +49 -0
  188. llms_py-3.0.18.dist-info/RECORD +194 -0
  189. {llms_py-2.0.20.dist-info → llms_py-3.0.18.dist-info}/WHEEL +1 -1
  190. {llms_py-2.0.20.dist-info → llms_py-3.0.18.dist-info}/licenses/LICENSE +1 -2
  191. llms/ui/Avatar.mjs +0 -28
  192. llms/ui/Brand.mjs +0 -34
  193. llms/ui/ChatPrompt.mjs +0 -443
  194. llms/ui/Main.mjs +0 -740
  195. llms/ui/ModelSelector.mjs +0 -60
  196. llms/ui/ProviderIcon.mjs +0 -29
  197. llms/ui/ProviderStatus.mjs +0 -105
  198. llms/ui/SignIn.mjs +0 -64
  199. llms/ui/SystemPromptEditor.mjs +0 -31
  200. llms/ui/SystemPromptSelector.mjs +0 -36
  201. llms/ui/Welcome.mjs +0 -8
  202. llms/ui/threadStore.mjs +0 -524
  203. llms/ui.json +0 -1069
  204. llms_py-2.0.20.dist-info/METADATA +0 -931
  205. llms_py-2.0.20.dist-info/RECORD +0 -36
  206. {llms_py-2.0.20.dist-info → llms_py-3.0.18.dist-info}/entry_points.txt +0 -0
  207. {llms_py-2.0.20.dist-info → llms_py-3.0.18.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,461 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import os
4
+ import re
5
+ import shutil
6
+ import subprocess
7
+ import sys
8
+ from typing import Optional, Tuple
9
+
10
+
11
+ def get_screen_resolution() -> Tuple[int, int]:
12
+ """
13
+ Get the current screen resolution (width, height).
14
+
15
+ Supports Linux (Wayland/Hyprland, X11), macOS, and Windows.
16
+ Returns the primary monitor's resolution.
17
+
18
+ Returns:
19
+ Tuple[int, int]: (width, height) in pixels
20
+
21
+ Raises:
22
+ RuntimeError: If unable to determine screen resolution
23
+ """
24
+ if sys.platform == "linux":
25
+ return _get_linux_resolution()
26
+ elif sys.platform == "darwin":
27
+ return _get_macos_resolution()
28
+ elif sys.platform == "win32":
29
+ return _get_windows_resolution()
30
+ else:
31
+ raise RuntimeError(f"Unsupported platform: {sys.platform}")
32
+
33
+
34
+ def get_display_num() -> int:
35
+ """
36
+ Get the display number for the current session.
37
+
38
+ On Linux (X11): Returns the X display number from $DISPLAY (e.g., :0 -> 0)
39
+ On Linux (Wayland): Returns the Wayland display number from $WAYLAND_DISPLAY (e.g., wayland-0 -> 0)
40
+ On macOS: Returns the display ID of the main display
41
+ On Windows: Returns the index of the primary monitor (typically 0)
42
+
43
+ Returns:
44
+ int: The display number
45
+
46
+ Raises:
47
+ RuntimeError: If unable to determine display number
48
+ """
49
+ if sys.platform == "linux":
50
+ return _get_linux_display_num()
51
+ elif sys.platform == "darwin":
52
+ return _get_macos_display_num()
53
+ elif sys.platform == "win32":
54
+ return _get_windows_display_num()
55
+ else:
56
+ raise RuntimeError(f"Unsupported platform: {sys.platform}")
57
+
58
+
59
+ def _get_linux_display_num() -> int:
60
+ """Get display number on Linux (X11 or Wayland)."""
61
+
62
+ # Try X11 DISPLAY environment variable first
63
+ display = os.environ.get("DISPLAY")
64
+ if display:
65
+ # DISPLAY format: [hostname]:displaynumber[.screennumber]
66
+ # Examples: :0, :1, localhost:0, :0.0
67
+ match = re.search(r":(\d+)", display)
68
+ if match:
69
+ return int(match.group(1))
70
+
71
+ # Try Wayland display
72
+ wayland_display = os.environ.get("WAYLAND_DISPLAY")
73
+ if wayland_display:
74
+ # WAYLAND_DISPLAY format: wayland-N or just a socket name
75
+ # Examples: wayland-0, wayland-1
76
+ match = re.search(r"wayland-(\d+)", wayland_display)
77
+ if match:
78
+ return int(match.group(1))
79
+ # If it's just "wayland-0" style, try to extract number
80
+ match = re.search(r"(\d+)", wayland_display)
81
+ if match:
82
+ return int(match.group(1))
83
+ # Default to 0 if we have a Wayland display but can't parse number
84
+ return 0
85
+
86
+ # Try Hyprland-specific: get focused monitor ID
87
+ if shutil.which("hyprctl"):
88
+ try:
89
+ result = subprocess.run(["hyprctl", "monitors", "-j"], capture_output=True, text=True, timeout=5)
90
+ if result.returncode == 0:
91
+ import json
92
+
93
+ monitors = json.loads(result.stdout)
94
+ if monitors:
95
+ # Return focused monitor ID or first monitor's ID
96
+ monitor = next((m for m in monitors if m.get("focused")), monitors[0])
97
+ return monitor.get("id", 0)
98
+ except (subprocess.TimeoutExpired, json.JSONDecodeError, KeyError):
99
+ pass
100
+
101
+ raise RuntimeError("Could not determine display number. Neither DISPLAY nor WAYLAND_DISPLAY is set.")
102
+
103
+
104
+ def _get_macos_display_num() -> int:
105
+ """Get display number on macOS."""
106
+
107
+ # Try using CoreGraphics via ctypes
108
+ try:
109
+ import ctypes
110
+ import ctypes.util
111
+
112
+ cg_path = ctypes.util.find_library("CoreGraphics")
113
+ if cg_path:
114
+ cg = ctypes.CDLL(cg_path)
115
+ # CGMainDisplayID returns the display ID of the main display
116
+ cg.CGMainDisplayID.restype = ctypes.c_uint32
117
+ return cg.CGMainDisplayID()
118
+ except (OSError, AttributeError):
119
+ pass
120
+
121
+ # Try using AppKit
122
+ try:
123
+ from AppKit import NSScreen
124
+
125
+ main_screen = NSScreen.mainScreen()
126
+ # Get the display ID from the screen's deviceDescription
127
+ device_desc = main_screen.deviceDescription()
128
+ display_id = device_desc.get("NSScreenNumber", 0)
129
+ return int(display_id)
130
+ except ImportError:
131
+ pass
132
+
133
+ # Try using system_profiler
134
+ try:
135
+ result = subprocess.run(["system_profiler", "SPDisplaysDataType"], capture_output=True, text=True, timeout=10)
136
+ if result.returncode == 0:
137
+ # Look for Display ID or just return 0 for main display
138
+ match = re.search(r"Display ID:\s*(\d+)", result.stdout)
139
+ if match:
140
+ return int(match.group(1))
141
+ # If we found display info but no ID, assume main display is 0
142
+ if "Resolution:" in result.stdout:
143
+ return 0
144
+ except subprocess.TimeoutExpired:
145
+ pass
146
+
147
+ # Default to 0 for main display
148
+ return 0
149
+
150
+
151
+ def _get_windows_display_num() -> int:
152
+ """Get display number on Windows."""
153
+
154
+ # Method 1: Try ctypes to enumerate monitors
155
+ try:
156
+ import ctypes
157
+ from ctypes import wintypes
158
+
159
+ user32 = ctypes.windll.user32
160
+
161
+ # Get the primary monitor handle
162
+ # MonitorFromPoint with MONITOR_DEFAULTTOPRIMARY (0x00000001)
163
+ primary_monitor = user32.MonitorFromPoint(
164
+ ctypes.wintypes.POINT(0, 0),
165
+ 1, # MONITOR_DEFAULTTOPRIMARY
166
+ )
167
+
168
+ # Enumerate all monitors to find the index of the primary
169
+ monitors = []
170
+
171
+ def callback(hMonitor, hdcMonitor, lprcMonitor, dwData):
172
+ monitors.append(hMonitor)
173
+ return True
174
+
175
+ MONITORENUMPROC = ctypes.WINFUNCTYPE(
176
+ ctypes.c_bool,
177
+ ctypes.wintypes.HMONITOR,
178
+ ctypes.wintypes.HDC,
179
+ ctypes.POINTER(ctypes.wintypes.RECT),
180
+ ctypes.wintypes.LPARAM,
181
+ )
182
+
183
+ user32.EnumDisplayMonitors(None, None, MONITORENUMPROC(callback), 0)
184
+
185
+ # Find the index of the primary monitor
186
+ for i, mon in enumerate(monitors):
187
+ if mon == primary_monitor:
188
+ return i
189
+
190
+ # Primary not found in list, return 0
191
+ return 0
192
+
193
+ except (AttributeError, OSError):
194
+ pass
195
+
196
+ # Method 2: Try win32api
197
+ try:
198
+ import win32api
199
+
200
+ # Get all monitors
201
+ monitors = win32api.EnumDisplayMonitors()
202
+ # Find primary (usually the first one, or check flags)
203
+ for i, (handle, dc, rect) in enumerate(monitors):
204
+ info = win32api.GetMonitorInfo(handle)
205
+ if info.get("Flags", 0) & 1: # MONITORINFOF_PRIMARY
206
+ return i
207
+ return 0
208
+ except ImportError:
209
+ pass
210
+
211
+ # Method 3: PowerShell to get monitor index
212
+ try:
213
+ ps_script = """
214
+ Add-Type -AssemblyName System.Windows.Forms
215
+ $screens = [System.Windows.Forms.Screen]::AllScreens
216
+ for ($i = 0; $i -lt $screens.Count; $i++) {
217
+ if ($screens[$i].Primary) {
218
+ Write-Output $i
219
+ break
220
+ }
221
+ }
222
+ """
223
+ result = subprocess.run(
224
+ ["powershell", "-NoProfile", "-Command", ps_script], capture_output=True, text=True, timeout=10
225
+ )
226
+ if result.returncode == 0:
227
+ return int(result.stdout.strip())
228
+ except (subprocess.TimeoutExpired, FileNotFoundError, ValueError):
229
+ pass
230
+
231
+ # Default to 0 (primary monitor)
232
+ return 0
233
+
234
+
235
+ def _get_linux_resolution() -> Tuple[int, int]:
236
+ """Get resolution on Linux, trying Wayland first, then X11."""
237
+
238
+ # Try Hyprland first
239
+ resolution = _try_hyprctl()
240
+ if resolution:
241
+ return resolution
242
+
243
+ # Try wlr-randr (generic Wayland)
244
+ resolution = _try_wlr_randr()
245
+ if resolution:
246
+ return resolution
247
+
248
+ # Try xrandr (X11)
249
+ resolution = _try_xrandr()
250
+ if resolution:
251
+ return resolution
252
+
253
+ # Try xdpyinfo (X11 fallback)
254
+ resolution = _try_xdpyinfo()
255
+ if resolution:
256
+ return resolution
257
+
258
+ raise RuntimeError("Could not determine screen resolution. No supported display server found.")
259
+
260
+
261
+ def _try_hyprctl() -> Optional[Tuple[int, int]]:
262
+ """Try getting resolution via hyprctl (Hyprland)."""
263
+ if not shutil.which("hyprctl"):
264
+ return None
265
+
266
+ try:
267
+ result = subprocess.run(["hyprctl", "monitors", "-j"], capture_output=True, text=True, timeout=5)
268
+ if result.returncode == 0:
269
+ import json
270
+
271
+ monitors = json.loads(result.stdout)
272
+ if monitors:
273
+ # Get the focused monitor or first one
274
+ monitor = next((m for m in monitors if m.get("focused")), monitors[0])
275
+ return (monitor["width"], monitor["height"])
276
+ except (subprocess.TimeoutExpired, json.JSONDecodeError, KeyError):
277
+ pass
278
+ return None
279
+
280
+
281
+ def _try_wlr_randr() -> Optional[Tuple[int, int]]:
282
+ """Try getting resolution via wlr-randr (Wayland)."""
283
+ if not shutil.which("wlr-randr"):
284
+ return None
285
+
286
+ try:
287
+ result = subprocess.run(["wlr-randr"], capture_output=True, text=True, timeout=5)
288
+ if result.returncode == 0:
289
+ # Parse output like: " 3840x2160 px, 60.000000 Hz (current)"
290
+ match = re.search(r"(\d+)x(\d+)\s+px.*current", result.stdout)
291
+ if match:
292
+ return (int(match.group(1)), int(match.group(2)))
293
+ except subprocess.TimeoutExpired:
294
+ pass
295
+ return None
296
+
297
+
298
+ def _try_xrandr() -> Optional[Tuple[int, int]]:
299
+ """Try getting resolution via xrandr (X11)."""
300
+ if not shutil.which("xrandr"):
301
+ return None
302
+
303
+ try:
304
+ result = subprocess.run(["xrandr", "--current"], capture_output=True, text=True, timeout=5)
305
+ if result.returncode == 0:
306
+ # Look for current resolution marked with *
307
+ match = re.search(r"(\d+)x(\d+).*\*", result.stdout)
308
+ if match:
309
+ return (int(match.group(1)), int(match.group(2)))
310
+ except subprocess.TimeoutExpired:
311
+ pass
312
+ return None
313
+
314
+
315
+ def _try_xdpyinfo() -> Optional[Tuple[int, int]]:
316
+ """Try getting resolution via xdpyinfo (X11)."""
317
+ if not shutil.which("xdpyinfo"):
318
+ return None
319
+
320
+ try:
321
+ result = subprocess.run(["xdpyinfo"], capture_output=True, text=True, timeout=5)
322
+ if result.returncode == 0:
323
+ match = re.search(r"dimensions:\s+(\d+)x(\d+)", result.stdout)
324
+ if match:
325
+ return (int(match.group(1)), int(match.group(2)))
326
+ except subprocess.TimeoutExpired:
327
+ pass
328
+ return None
329
+
330
+
331
+ def _get_macos_resolution() -> Tuple[int, int]:
332
+ """Get resolution on macOS using system_profiler."""
333
+ try:
334
+ result = subprocess.run(["system_profiler", "SPDisplaysDataType"], capture_output=True, text=True, timeout=10)
335
+ if result.returncode == 0:
336
+ # Look for "Resolution: 2560 x 1440" or similar
337
+ match = re.search(r"Resolution:\s+(\d+)\s*x\s*(\d+)", result.stdout)
338
+ if match:
339
+ return (int(match.group(1)), int(match.group(2)))
340
+ except subprocess.TimeoutExpired:
341
+ pass
342
+
343
+ # Fallback: try using AppKit via Python (if available)
344
+ try:
345
+ from AppKit import NSScreen
346
+
347
+ frame = NSScreen.mainScreen().frame()
348
+ return (int(frame.size.width), int(frame.size.height))
349
+ except ImportError:
350
+ pass
351
+
352
+ raise RuntimeError("Could not determine screen resolution on macOS")
353
+
354
+
355
+ def _get_windows_resolution() -> Tuple[int, int]:
356
+ """Get resolution on Windows."""
357
+
358
+ # Method 1: Try ctypes (no external dependencies)
359
+ resolution = _try_windows_ctypes()
360
+ if resolution:
361
+ return resolution
362
+
363
+ # Method 2: Try win32api (pywin32)
364
+ resolution = _try_windows_win32api()
365
+ if resolution:
366
+ return resolution
367
+
368
+ # Method 3: Try PowerShell
369
+ resolution = _try_windows_powershell()
370
+ if resolution:
371
+ return resolution
372
+
373
+ # Method 4: Try wmic (deprecated but still works on older systems)
374
+ resolution = _try_windows_wmic()
375
+ if resolution:
376
+ return resolution
377
+
378
+ raise RuntimeError("Could not determine screen resolution on Windows")
379
+
380
+
381
+ def _try_windows_ctypes() -> Optional[Tuple[int, int]]:
382
+ """Try getting resolution via ctypes (built-in)."""
383
+ try:
384
+ import ctypes
385
+
386
+ user32 = ctypes.windll.user32
387
+ user32.SetProcessDPIAware() # Handle DPI scaling
388
+ width = user32.GetSystemMetrics(0) # SM_CXSCREEN
389
+ height = user32.GetSystemMetrics(1) # SM_CYSCREEN
390
+ if width > 0 and height > 0:
391
+ return (width, height)
392
+ except (AttributeError, OSError):
393
+ pass
394
+ return None
395
+
396
+
397
+ def _try_windows_win32api() -> Optional[Tuple[int, int]]:
398
+ """Try getting resolution via win32api (pywin32)."""
399
+ try:
400
+ import win32api
401
+
402
+ width = win32api.GetSystemMetrics(0)
403
+ height = win32api.GetSystemMetrics(1)
404
+ if width > 0 and height > 0:
405
+ return (width, height)
406
+ except ImportError:
407
+ pass
408
+ return None
409
+
410
+
411
+ def _try_windows_powershell() -> Optional[Tuple[int, int]]:
412
+ """Try getting resolution via PowerShell."""
413
+ try:
414
+ # Using Add-Type to access System.Windows.Forms
415
+ ps_script = """
416
+ Add-Type -AssemblyName System.Windows.Forms
417
+ $screen = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds
418
+ "$($screen.Width)x$($screen.Height)"
419
+ """
420
+ result = subprocess.run(
421
+ ["powershell", "-NoProfile", "-Command", ps_script], capture_output=True, text=True, timeout=10
422
+ )
423
+ if result.returncode == 0:
424
+ match = re.match(r"(\d+)x(\d+)", result.stdout.strip())
425
+ if match:
426
+ return (int(match.group(1)), int(match.group(2)))
427
+ except (subprocess.TimeoutExpired, FileNotFoundError):
428
+ pass
429
+ return None
430
+
431
+
432
+ def _try_windows_wmic() -> Optional[Tuple[int, int]]:
433
+ """Try getting resolution via wmic (legacy, but works on older Windows)."""
434
+ try:
435
+ result = subprocess.run(
436
+ ["wmic", "path", "Win32_VideoController", "get", "CurrentHorizontalResolution,CurrentVerticalResolution"],
437
+ capture_output=True,
438
+ text=True,
439
+ timeout=10,
440
+ )
441
+ if result.returncode == 0:
442
+ lines = result.stdout.strip().split("\n")
443
+ for line in lines[1:]: # Skip header
444
+ match = re.match(r"(\d+)\s+(\d+)", line.strip())
445
+ if match:
446
+ return (int(match.group(1)), int(match.group(2)))
447
+ except (subprocess.TimeoutExpired, FileNotFoundError):
448
+ pass
449
+ return None
450
+
451
+
452
+ if __name__ == "__main__":
453
+ try:
454
+ width, height = get_screen_resolution()
455
+ print(f"Screen resolution: {width}x{height}")
456
+
457
+ display_num = get_display_num()
458
+ print(f"Display number: {display_num}")
459
+ except RuntimeError as e:
460
+ print(f"Error: {e}", file=sys.stderr)
461
+ sys.exit(1)
@@ -0,0 +1,37 @@
1
+ """Utility to run shell commands asynchronously with a timeout."""
2
+
3
+ import asyncio
4
+ import contextlib
5
+
6
+ TRUNCATED_MESSAGE: str = "<response clipped><NOTE>To save on context only part of this file has been shown to you. You should retry this tool after you have searched inside the file with `grep -n` in order to find the line numbers of what you are looking for.</NOTE>"
7
+ MAX_RESPONSE_LEN: int = 16000
8
+
9
+
10
+ def maybe_truncate(content: str, truncate_after: int | None = MAX_RESPONSE_LEN):
11
+ """Truncate content and append a notice if content exceeds the specified length."""
12
+ return (
13
+ content
14
+ if not truncate_after or len(content) <= truncate_after
15
+ else content[:truncate_after] + TRUNCATED_MESSAGE
16
+ )
17
+
18
+
19
+ async def run(
20
+ cmd: str,
21
+ timeout: float | None = 120.0, # seconds
22
+ truncate_after: int | None = MAX_RESPONSE_LEN,
23
+ ):
24
+ """Run a shell command asynchronously with a timeout."""
25
+ process = await asyncio.create_subprocess_shell(cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
26
+
27
+ try:
28
+ stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=timeout)
29
+ return (
30
+ process.returncode or 0,
31
+ maybe_truncate(stdout.decode(), truncate_after=truncate_after),
32
+ maybe_truncate(stderr.decode(), truncate_after=truncate_after),
33
+ )
34
+ except asyncio.TimeoutError as exc:
35
+ with contextlib.suppress(ProcessLookupError):
36
+ process.kill()
37
+ raise TimeoutError(f"Command '{cmd}' timed out after {timeout} seconds") from exc
@@ -0,0 +1,32 @@
1
+ # Calculator
2
+
3
+ A powerful and safe mathematical expression evaluator with a rich web interface.
4
+
5
+ ## Features
6
+
7
+ ### 🖥️ UX Friendly Interface
8
+ Experience a clean, modern interface designed for efficiency. The UI is fully responsive and supports dark mode, seamlessly integrating with the rest of the application.
9
+
10
+ ### 💾 Persistent History
11
+ Never lose track of your calculations. The Calculator automatically saves your history to `localStorage`, ensuring your previous expressions and results are preserved between sessions.
12
+
13
+ ### ⚡ 1-Click Interaction
14
+ Streamline your workflow with interactive history items:
15
+ - **Load & Copy**: Click on any past expression or answer to instantly load it into the input field and copy it to your clipboard.
16
+ - **Visual Feedback**: Temporary checkmarks confirm successful copy actions.
17
+
18
+ ### ⌨️ Keyboard-Free Access
19
+ While full keyboard support is available, you can perform complex calculations entirely via the UI:
20
+ - **Numbers & Constants**: Quick access to digits and mathematical constants like `pi`, `e`, `inf`.
21
+ - **Operators**: Comprehensive set of buttons for arithmetic (`+`, `-`, `*`, `/`, `%`, `^`) and boolean logic (`and`, `or`, `not`).
22
+ - **Functions**: One-click insertion or wrapping of selection for all supported math functions.
23
+
24
+ ### 🐍 Python Math Support
25
+ Unlock the power of Python's math library directly in the browser.
26
+ - **Math Functions**: Support for `sin`, `cos`, `tan`, `sqrt`, `log`, `factorial`, and many more.
27
+ - **Statistics**: Built-in functions for `mean`, `median`, `stdev`, and `variance`.
28
+
29
+ ### 🛡️ Safe Evaluation
30
+ Security is a priority. Instead of using Python's unsafe `eval()`, the Calculator uses a robust **AST (Abstract Syntax Tree) evaluator**.
31
+ - **Restricted Environment**: Only allowed mathematical operations and functions are executed.
32
+ - **No Side Effects**: Prevents arbitrary code execution, making it safe to evaluate expressions from untrusted sources.