meltygui 0.1.0__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 (372) hide show
  1. meltygui/__init__.py +107 -0
  2. meltygui/accounts/__init__.py +0 -0
  3. meltygui/accounts/internet_accounts.py +1355 -0
  4. meltygui/chat/__init__.py +91 -0
  5. meltygui/chat/activity.py +75 -0
  6. meltygui/chat/backends.py +36 -0
  7. meltygui/chat/chat_interface.py +732 -0
  8. meltygui/chat/chat_proxy.py +352 -0
  9. meltygui/chat/codex_proxy.py +592 -0
  10. meltygui/chat/codex_settings.py +100 -0
  11. meltygui/chat/codex_transport.py +60 -0
  12. meltygui/chat/command_parser.py +204 -0
  13. meltygui/chat/images.py +227 -0
  14. meltygui/chat/messages.py +417 -0
  15. meltygui/chat/metadata.py +139 -0
  16. meltygui/chat/writer_locks.py +64 -0
  17. meltygui/code/__init__.py +0 -0
  18. meltygui/code/basic_converters.py +533 -0
  19. meltygui/code/chain_converters.py +2111 -0
  20. meltygui/code/code_checks.py +2209 -0
  21. meltygui/code/core_syntax.py +1430 -0
  22. meltygui/code/file_converters.py +1933 -0
  23. meltygui/code/fileref.py +702 -0
  24. meltygui/code/hotswap_guard.py +144 -0
  25. meltygui/code/libcst_conversion.py +9724 -0
  26. meltygui/code/live_instrument.py +392 -0
  27. meltygui/code/live_view.py +2490 -0
  28. meltygui/code/melty_scan.py +2684 -0
  29. meltygui/code/new_codecs.py +1255 -0
  30. meltygui/code/new_converters.py +3017 -0
  31. meltygui/code/project_code.py +278 -0
  32. meltygui/code/source_context.py +63 -0
  33. meltygui/code/symbol_roster.py +1588 -0
  34. meltygui/code/syntax_check.py +34 -0
  35. meltygui/code/syntax_check_worker.py +114 -0
  36. meltygui/completion/__init__.py +0 -0
  37. meltygui/completion/fim.py +1232 -0
  38. meltygui/completion/fim_context.py +481 -0
  39. meltygui/completion/providers/__init__.py +0 -0
  40. meltygui/completion/providers/anthropic_oauth.py +446 -0
  41. meltygui/completion/providers/anthropic_requests.py +69 -0
  42. meltygui/completion/providers/claude.py +203 -0
  43. meltygui/completion/providers/claude_usage.py +499 -0
  44. meltygui/completion/providers/codex_accounts.py +180 -0
  45. meltygui/completion/providers/copilot.py +617 -0
  46. meltygui/completion/providers/oauth_popup.py +220 -0
  47. meltygui/completion/providers/ollama.py +320 -0
  48. meltygui/completion/providers/profiles.py +23 -0
  49. meltygui/core/README.md +88 -0
  50. meltygui/core/__init__.py +1 -0
  51. meltygui/core/automation/__init__.py +1 -0
  52. meltygui/core/automation/action_core.py +153 -0
  53. meltygui/core/automation/collection_action.py +45 -0
  54. meltygui/core/automation/mcp_eval.py +167 -0
  55. meltygui/core/automation/mcp_hotswap.py +107 -0
  56. meltygui/core/automation/mcp_query.py +552 -0
  57. meltygui/core/automation/mcp_server.py +657 -0
  58. meltygui/core/automation/orchestration_core.py +2636 -0
  59. meltygui/core/automation/query_core.py +124 -0
  60. meltygui/core/automation/search_core.py +26 -0
  61. meltygui/core/automation/selector_core.py +340 -0
  62. meltygui/core/automation/value_core.py +1752 -0
  63. meltygui/core/cache/__init__.py +1 -0
  64. meltygui/core/cache/cache_diagnostics.py +0 -0
  65. meltygui/core/cache/invalidation_decoration.py +153 -0
  66. meltygui/core/cache/invalidation_tracker.py +43 -0
  67. meltygui/core/cache/tile_cache.py +5968 -0
  68. meltygui/core/conversion/__init__.py +1 -0
  69. meltygui/core/conversion/bubbling.py +599 -0
  70. meltygui/core/conversion/cache_tree.py +188 -0
  71. meltygui/core/conversion/chain.py +113 -0
  72. meltygui/core/conversion/converter_register.py +145 -0
  73. meltygui/core/conversion/data_decoration.py +67 -0
  74. meltygui/core/conversion/dict_conversion.py +1815 -0
  75. meltygui/core/conversion/dict_conversion_util.py +177 -0
  76. meltygui/core/conversion/dynamic_obj.py +89 -0
  77. meltygui/core/conversion/graph_compare.py +183 -0
  78. meltygui/core/conversion/load_save_v2.py +1032 -0
  79. meltygui/core/conversion/missing_saved_class.py +39 -0
  80. meltygui/core/conversion/path_finder.py +606 -0
  81. meltygui/core/conversion/render_host.py +1083 -0
  82. meltygui/core/core_render.py +6537 -0
  83. meltygui/core/definition_hotswap.py +231 -0
  84. meltygui/core/diagnostics/__init__.py +1 -0
  85. meltygui/core/diagnostics/attribute_churn.py +38 -0
  86. meltygui/core/diagnostics/fps_counter.py +39 -0
  87. meltygui/core/diagnostics/gpu_frame_timer.py +103 -0
  88. meltygui/core/diagnostics/inspection_core.py +169 -0
  89. meltygui/core/diagnostics/monitor_core.py +105 -0
  90. meltygui/core/diagnostics/notifications.py +706 -0
  91. meltygui/core/diagnostics/perf_trace.py +281 -0
  92. meltygui/core/diagnostics/profile_decoration.py +88 -0
  93. meltygui/core/diagnostics/resize_trace.py +62 -0
  94. meltygui/core/diagnostics/screenshot_core.py +247 -0
  95. meltygui/core/diagnostics/session_status.py +98 -0
  96. meltygui/core/diagnostics/trace_core.py +445 -0
  97. meltygui/core/files/__init__.py +1 -0
  98. meltygui/core/files/file_core.py +208 -0
  99. meltygui/core/files/file_explorer_core.py +104 -0
  100. meltygui/core/files/file_tree_core.py +198 -0
  101. meltygui/core/files/file_watch_core.py +43 -0
  102. meltygui/core/files/import_graph_core.py +43 -0
  103. meltygui/core/files/metadata_core.py +51 -0
  104. meltygui/core/graphics/__init__.py +1 -0
  105. meltygui/core/graphics/cuda_context_core.py +166 -0
  106. meltygui/core/graphics/cuda_interop_core.py +136 -0
  107. meltygui/core/graphics/cuda_kernel_core.py +91 -0
  108. meltygui/core/graphics/framebuffer_recorder.py +337 -0
  109. meltygui/core/graphics/gl_state.py +658 -0
  110. meltygui/core/graphics/lut_core.py +52 -0
  111. meltygui/core/graphics/overlay_renderer.py +984 -0
  112. meltygui/core/graphics/scene_target.py +180 -0
  113. meltygui/core/graphics/screenshot.py +439 -0
  114. meltygui/core/graphics/shader_func.py +478 -0
  115. meltygui/core/graphics/tensor_core.py +45 -0
  116. meltygui/core/graphics/text_texture.py +329 -0
  117. meltygui/core/graphics/wayland_color.py +635 -0
  118. meltygui/core/input/__init__.py +1 -0
  119. meltygui/core/input/collision.py +165 -0
  120. meltygui/core/input/drag_drop_core.py +1525 -0
  121. meltygui/core/input/hypr_left_drag.py +323 -0
  122. meltygui/core/input/input_core.py +245 -0
  123. meltygui/core/input/input_handler.py +1101 -0
  124. meltygui/core/input/mouse_cursor.py +355 -0
  125. meltygui/core/input/pynput_backend.py +1054 -0
  126. meltygui/core/input/space_mouse.py +338 -0
  127. meltygui/core/input/touchpad_backend.py +393 -0
  128. meltygui/core/input/view_selection.py +177 -0
  129. meltygui/core/layout/__init__.py +1 -0
  130. meltygui/core/layout/column_core.py +2153 -0
  131. meltygui/core/layout/cursor_core.py +161 -0
  132. meltygui/core/layout/dropdown_core.py +278 -0
  133. meltygui/core/layout/edge_constraints.py +155 -0
  134. meltygui/core/layout/grid_core.py +117 -0
  135. meltygui/core/layout/header_core.py +23 -0
  136. meltygui/core/layout/header_runtime.py +67 -0
  137. meltygui/core/layout/layout_core.py +87 -0
  138. meltygui/core/layout/tile_manager_core.py +591 -0
  139. meltygui/core/melty.py +6726 -0
  140. meltygui/core/module_map.json +896 -0
  141. meltygui/core/module_names.py +19 -0
  142. meltygui/core/rendering/__init__.py +1 -0
  143. meltygui/core/rendering/core_decoration.py +431 -0
  144. meltygui/core/rendering/core_render_helpers.py +328 -0
  145. meltygui/core/rendering/func_metadata.py +398 -0
  146. meltygui/core/rendering/mode.py +818 -0
  147. meltygui/core/rendering/mode_defaults.py +41 -0
  148. meltygui/core/rendering/modes.py +136 -0
  149. meltygui/core/rendering/parameter_core.py +1665 -0
  150. meltygui/core/rendering/render_dispatch.py +1891 -0
  151. meltygui/core/rendering/render_funcs.py +273 -0
  152. meltygui/core/rendering/shaped.py +312 -0
  153. meltygui/core/rendering/window_decoration.py +25 -0
  154. meltygui/core/runtime/__init__.py +1 -0
  155. meltygui/core/runtime/app.py +735 -0
  156. meltygui/core/runtime/app_session.py +140 -0
  157. meltygui/core/runtime/background.py +564 -0
  158. meltygui/core/runtime/extensions.py +53 -0
  159. meltygui/core/runtime/gc_manager.py +1163 -0
  160. meltygui/core/runtime/lifecycle.py +21 -0
  161. meltygui/core/runtime/paths.py +27 -0
  162. meltygui/core/runtime/settings.py +14 -0
  163. meltygui/core/runtime/singleton.py +16 -0
  164. meltygui/core/runtime/thread_safe_bool.py +24 -0
  165. meltygui/core/runtime/thread_signal.py +30 -0
  166. meltygui/core/runtime/toggles.py +3115 -0
  167. meltygui/core/services/__init__.py +1 -0
  168. meltygui/core/services/account_core.py +10 -0
  169. meltygui/core/services/chat_core.py +19 -0
  170. meltygui/core/services/claude_terminal_core.py +346 -0
  171. meltygui/core/services/terminal_core.py +458 -0
  172. meltygui/core/services/terminal_runtime.py +78 -0
  173. meltygui/core/styling/__init__.py +1 -0
  174. meltygui/core/styling/color_core.py +46 -0
  175. meltygui/core/styling/fonts.py +639 -0
  176. meltygui/core/styling/global_style.py +338 -0
  177. meltygui/core/styling/style.py +198 -0
  178. meltygui/core/styling/style_core.py +522 -0
  179. meltygui/core/styling/warm_start.py +149 -0
  180. meltygui/core/windowing/__init__.py +1 -0
  181. meltygui/core/windowing/backends/PYIMGUI_LICENSE +28 -0
  182. meltygui/core/windowing/backends/__init__.py +1 -0
  183. meltygui/core/windowing/backends/imgui_renderer.py +138 -0
  184. meltygui/core/windowing/backends/native_wayland.py +850 -0
  185. meltygui/core/windowing/backends/protocols/xdg-decoration-unstable-v1.xml +156 -0
  186. meltygui/core/windowing/backends/protocols/xdg-shell.xml +1420 -0
  187. meltygui/core/windowing/backends/wayland_protocol.py +101 -0
  188. meltygui/core/windowing/dock_core.py +182 -0
  189. meltygui/core/windowing/frame_geometry.py +42 -0
  190. meltygui/core/windowing/geometry_feed.py +864 -0
  191. meltygui/core/windowing/glfw_utils.py +1343 -0
  192. meltygui/core/windowing/os_frame.py +1552 -0
  193. meltygui/core/windowing/surface.py +643 -0
  194. meltygui/core/windowing/titlebar.py +1560 -0
  195. meltygui/core/windowing/titlebar_buttons.py +281 -0
  196. meltygui/core/windowing/wayland_move.py +932 -0
  197. meltygui/core/windowing/window_api.py +62 -0
  198. meltygui/core/windowing/window_constants.py +339 -0
  199. meltygui/core/windowing/window_visibility.py +162 -0
  200. meltygui/debug/__init__.py +0 -0
  201. meltygui/debug/app_view_utils.py +9 -0
  202. meltygui/editor/__init__.py +0 -0
  203. meltygui/editor/bash_syntax.py +30 -0
  204. meltygui/editor/code_line_fast.py +152 -0
  205. meltygui/editor/diff.py +139 -0
  206. meltygui/editor/external_changes.py +159 -0
  207. meltygui/editor/file_header.py +41 -0
  208. meltygui/editor/live_usage.py +169 -0
  209. meltygui/editor/live_view_views.py +1803 -0
  210. meltygui/editor/pending_save.py +1351 -0
  211. meltygui/editor/roster_tints.py +547 -0
  212. meltygui/editor/source_preview.py +16 -0
  213. meltygui/editor/source_tools.py +10 -0
  214. meltygui/editor/source_ui.py +70 -0
  215. meltygui/editor/spell_check.py +77 -0
  216. meltygui/editor/text_editor.py +9076 -0
  217. meltygui/editor/usage_picker.py +538 -0
  218. meltygui/events/__init__.py +0 -0
  219. meltygui/events/example.py +118 -0
  220. meltygui/examples/__init__.py +0 -0
  221. meltygui/examples/columns_demo.py +33 -0
  222. meltygui/examples/columns_window_demo.py +60 -0
  223. meltygui/examples/context_menu_demo.py +24 -0
  224. meltygui/examples/context_menu_window_demo.py +49 -0
  225. meltygui/examples/gui_playground.py +127 -0
  226. meltygui/examples/live_view_playground.py +256 -0
  227. meltygui/examples/lora.py +43 -0
  228. meltygui/examples/lora_data.py +59 -0
  229. meltygui/examples/lora_policies.py +67 -0
  230. meltygui/examples/mode_demo.py +49 -0
  231. meltygui/examples/modifies_demo.py +119 -0
  232. meltygui/examples/scalar_policies.py +60 -0
  233. meltygui/examples/style_layouts.py +149 -0
  234. meltygui/examples/tile_manager_demo.py +74 -0
  235. meltygui/examples/tint_demo.py +160 -0
  236. meltygui/examples/tint_functions.py +56 -0
  237. meltygui/examples/trace_demo.py +88 -0
  238. meltygui/examples/two_windows.py +36 -0
  239. meltygui/files/__init__.py +0 -0
  240. meltygui/files/fast_file_explorer.py +439 -0
  241. meltygui/gnome_extension/lsd-window-geometry@latent-descent/extension.js +237 -0
  242. meltygui/gnome_extension/lsd-window-geometry@latent-descent/lsd-window-geometry@latent-descent.iml +9 -0
  243. meltygui/gnome_extension/lsd-window-geometry@latent-descent/metadata.json +7 -0
  244. meltygui/graphics/__init__.py +6 -0
  245. meltygui/graphics/base.py +85 -0
  246. meltygui/graphics/examples.py +507 -0
  247. meltygui/graphics/executor.py +520 -0
  248. meltygui/graphics/filter.py +667 -0
  249. meltygui/graphics/filter.pyi +856 -0
  250. meltygui/graphics/generate_stubs.py +22 -0
  251. meltygui/graphics/registry.py +203 -0
  252. meltygui/graphics/shader_compiler.py +155 -0
  253. meltygui/graphics/shaders.py +1302 -0
  254. meltygui/graphics/stub_generator.py +250 -0
  255. meltygui/graphics/texture_manager.py +170 -0
  256. meltygui/graphics/texture_min_max.py +295 -0
  257. meltygui/hdr_color.py +757 -0
  258. meltygui/image_load.py +308 -0
  259. meltygui/model/__init__.py +1 -0
  260. meltygui/model/account_model.py +142 -0
  261. meltygui/model/camera_model.py +159 -0
  262. meltygui/model/chat_model.py +86 -0
  263. meltygui/model/code_model.py +62 -0
  264. meltygui/model/code_proxy_model.py +876 -0
  265. meltygui/model/collection_model.py +41 -0
  266. meltygui/model/color_model.py +127 -0
  267. meltygui/model/cuda_tensor_model.py +36 -0
  268. meltygui/model/cuda_texture_model.py +149 -0
  269. meltygui/model/dropdown_model.py +137 -0
  270. meltygui/model/file_metadata_model.py +73 -0
  271. meltygui/model/file_model.py +289 -0
  272. meltygui/model/format_model.py +390 -0
  273. meltygui/model/graph_model.py +98 -0
  274. meltygui/model/icon_model.py +1024 -0
  275. meltygui/model/import_graph_model.py +468 -0
  276. meltygui/model/layout_model.py +15 -0
  277. meltygui/model/lut_model.py +266 -0
  278. meltygui/model/search_model.py +173 -0
  279. meltygui/model/tensor_model.py +402 -0
  280. meltygui/model/terminal_model.py +329 -0
  281. meltygui/model/texture_model.py +146 -0
  282. meltygui/model/tile_model.py +24 -0
  283. meltygui/model/trace_model.py +198 -0
  284. meltygui/model/trace_report_model.py +146 -0
  285. meltygui/models/__init__.py +0 -0
  286. meltygui/models/file_meta.py +613 -0
  287. meltygui/models/function_console.py +184 -0
  288. meltygui/models/orchestration.py +48 -0
  289. meltygui/pbr.py +1576 -0
  290. meltygui/png_unfilter.c +49 -0
  291. meltygui/resources/JetBrainsMono-Regular.ttf +0 -0
  292. meltygui/resources/THIRD_PARTY_NOTICES.md +21 -0
  293. meltygui/resources/dejavu/DejaVuSans-Bold.ttf +0 -0
  294. meltygui/resources/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
  295. meltygui/resources/dejavu/DejaVuSans.ttf +0 -0
  296. meltygui/resources/dejavu/LICENSE.txt +78 -0
  297. meltygui/resources/fontawesome-LICENSE.txt +121 -0
  298. meltygui/resources/fontawesome-webfont.ttf +0 -0
  299. meltygui/resources/hdri/studio_small_09_1k.hdr +0 -0
  300. meltygui/resources/jetbrains-weights/JetBrainsMono-Bold.ttf +0 -0
  301. meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraBold.ttf +0 -0
  302. meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraLight.ttf +0 -0
  303. meltygui/resources/jetbrains-weights/JetBrainsMono-Light.ttf +0 -0
  304. meltygui/resources/jetbrains-weights/JetBrainsMono-Medium.ttf +0 -0
  305. meltygui/resources/jetbrains-weights/JetBrainsMono-SemiBold.ttf +0 -0
  306. meltygui/resources/jetbrains-weights/JetBrainsMono-Thin.ttf +0 -0
  307. meltygui/resources/jetbrains-weights/OFL.txt +93 -0
  308. meltygui/resources/jetbrains-weights/README.md +2 -0
  309. meltygui/state/__init__.py +0 -0
  310. meltygui/state/account_state.py +22 -0
  311. meltygui/state/animation_state.py +97 -0
  312. meltygui/state/annotation_state.py +22 -0
  313. meltygui/state/chat_state.py +36 -0
  314. meltygui/state/code_state.py +10 -0
  315. meltygui/state/core_enums.py +59 -0
  316. meltygui/state/core_markers.py +115 -0
  317. meltygui/state/core_undo.py +1075 -0
  318. meltygui/state/file_state.py +75 -0
  319. meltygui/state/graph_state.py +26 -0
  320. meltygui/state/inspection_state.py +47 -0
  321. meltygui/state/menu_state.py +10 -0
  322. meltygui/state/model_enums.py +11 -0
  323. meltygui/state/new_core_model.py +2394 -0
  324. meltygui/state/orchestration_state.py +14 -0
  325. meltygui/state/query_state.py +24 -0
  326. meltygui/state/tensor_state.py +10 -0
  327. meltygui/state/terminal_state.py +23 -0
  328. meltygui/state/trace_state.py +32 -0
  329. meltygui/state/voxel_state.py +12 -0
  330. meltygui/text_index.py +816 -0
  331. meltygui/utils/__init__.py +0 -0
  332. meltygui/utils/jump_to_code.py +344 -0
  333. meltygui/utils/pkl_inspect.py +90 -0
  334. meltygui/utils/render_utils.py +1419 -0
  335. meltygui/view/__init__.py +1 -0
  336. meltygui/view/account_view.py +524 -0
  337. meltygui/view/action_view.py +73 -0
  338. meltygui/view/chat_decoration_view.py +112 -0
  339. meltygui/view/chat_view.py +1703 -0
  340. meltygui/view/code_view.py +2677 -0
  341. meltygui/view/collection_view.py +1185 -0
  342. meltygui/view/color_view.py +824 -0
  343. meltygui/view/control_view.py +559 -0
  344. meltygui/view/decoration_view.py +439 -0
  345. meltygui/view/diagnostic_view.py +177 -0
  346. meltygui/view/dropdown_view.py +1082 -0
  347. meltygui/view/file_view.py +1599 -0
  348. meltygui/view/graph_cuda_view.py +105 -0
  349. meltygui/view/graph_view.py +882 -0
  350. meltygui/view/header_view.py +848 -0
  351. meltygui/view/input_view.py +238 -0
  352. meltygui/view/inspection_view.py +1783 -0
  353. meltygui/view/layout_view.py +596 -0
  354. meltygui/view/lut_view.py +45 -0
  355. meltygui/view/menu_view.py +212 -0
  356. meltygui/view/orchestration_view.py +658 -0
  357. meltygui/view/query_view.py +118 -0
  358. meltygui/view/search_view.py +342 -0
  359. meltygui/view/tab_view.py +258 -0
  360. meltygui/view/tensor_view.py +430 -0
  361. meltygui/view/terminal_view.py +355 -0
  362. meltygui/view/text_view.py +8031 -0
  363. meltygui/view/texture_view.py +480 -0
  364. meltygui/view/tile_view.py +38 -0
  365. meltygui/view/trace_view.py +923 -0
  366. meltygui/view/voxel_cuda_view.py +824 -0
  367. meltygui/view/voxel_view.py +1860 -0
  368. meltygui/view/window_view.py +111 -0
  369. meltygui-0.1.0.dist-info/METADATA +143 -0
  370. meltygui-0.1.0.dist-info/RECORD +372 -0
  371. meltygui-0.1.0.dist-info/WHEEL +4 -0
  372. meltygui-0.1.0.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,220 @@
1
+ """Placed sign-in popups — Wayland refuses window positioning, so auth pages
2
+ open as a chromeless Chromium `--app` window forced onto XWAYLAND
3
+ (`--ozone-platform=x11`, where placement IS allowed) and a placement thread
4
+ parks it beside the pointer — i.e. over the button that opened it — with
5
+ xdotool, sized for a login form.
6
+
7
+ A dedicated profile (`~/.lsd/oauth-browser`) does two jobs: it guarantees a
8
+ FRESH browser instance (flags would be swallowed by an already-running
9
+ Wayland Chrome otherwise) and it keeps its own Google session between
10
+ sign-ins — the first one asks for credentials, later ones are one click.
11
+
12
+ `shim_env` covers the flow the studio doesn't open itself: `claude auth
13
+ login` calls xdg-open on its own, so its PATH gets a shim dir whose
14
+ xdg-open launches the same placed popup (`place_async` watches for it).
15
+
16
+ Fallback at every step — popups disabled (Toggles.InternetAccounts.
17
+ use_oauth_popup), no Chromium-family browser, no DISPLAY, the browser
18
+ dying before a window appears — is plain copilot.open_url. Every spawn is
19
+ posix_spawn-style (absolute paths, close_fds=False — never fork the
20
+ studio's CUDA/GL address space).
21
+ """
22
+ from __future__ import annotations
23
+
24
+ import os
25
+ import shutil
26
+ import stat
27
+ import subprocess
28
+ import threading
29
+ import time
30
+ from pathlib import Path
31
+
32
+ # Protocol constants (not knobs): the WM_CLASS the popup is found/closed by,
33
+ # and where the shim + browser profile go.
34
+ WINDOW_CLASS = "lsd-oauth"
35
+ PROFILE_DIR = Path.home() / ".lsd" / "oauth-browser"
36
+ SHIM_DIR = Path.home() / ".lsd" / "oauth-shim"
37
+
38
+
39
+ def find_browser(explicit=""):
40
+ """A Chromium-family browser (only they have --app / --ozone-platform)."""
41
+ if explicit:
42
+ found = shutil.which(explicit) or (explicit if Path(explicit).is_file() else None)
43
+ return found
44
+ for name in ("google-chrome", "chromium", "chromium-browser"):
45
+ found = shutil.which(name)
46
+ if found:
47
+ return found
48
+ return None
49
+
50
+
51
+ def popup_available() -> bool:
52
+ from meltygui.core.runtime.toggles import Toggles
53
+ return (bool(Toggles.InternetAccounts.use_oauth_popup)
54
+ and bool(os.environ.get("DISPLAY"))
55
+ and find_browser(Toggles.InternetAccounts.oauth_popup_browser) is not None
56
+ and shutil.which("xdotool") is not None)
57
+
58
+
59
+ class PopupHandle:
60
+ def __init__(self, process):
61
+ self.process = process
62
+ self.placed = False
63
+
64
+ def close(self):
65
+ """End of the flow: the popup's job is done — close it."""
66
+ if self.process is not None and self.process.poll() is None:
67
+ try:
68
+ self.process.terminate()
69
+ except OSError:
70
+ pass
71
+
72
+
73
+ def open_auth_popup(url, browser=None, xdotool=None, size=None, place_timeout_s=15.0):
74
+ """Open `url` as a placed popup; falls back to copilot.open_url and
75
+ returns None when it can't. Returns a PopupHandle (close() on flow end)
76
+ when the popup browser was launched."""
77
+ from meltygui.core.runtime.toggles import Toggles
78
+ browser = browser or (find_browser(Toggles.InternetAccounts.oauth_popup_browser)
79
+ if Toggles.InternetAccounts.use_oauth_popup else None)
80
+ xdotool = xdotool or shutil.which("xdotool")
81
+ if not browser or not xdotool or not os.environ.get("DISPLAY"):
82
+ _fallback(url)
83
+ return None
84
+ width, height = size or Toggles.InternetAccounts.oauth_popup_size
85
+ PROFILE_DIR.mkdir(parents=True, exist_ok=True)
86
+ try:
87
+ process = subprocess.Popen(
88
+ [browser, f"--app={url}", "--ozone-platform=x11", f"--class={WINDOW_CLASS}",
89
+ f"--window-size={width},{height}", f"--user-data-dir={PROFILE_DIR}",
90
+ "--no-first-run", "--no-default-browser-check"],
91
+ close_fds=False, stdin=subprocess.DEVNULL,
92
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
93
+ except OSError:
94
+ _fallback(url)
95
+ return None
96
+ handle = PopupHandle(process)
97
+ threading.Thread(target=_place, args=(handle, url, xdotool, (width, height), place_timeout_s),
98
+ daemon=True, name="oauth-popup-place").start()
99
+ return handle
100
+
101
+
102
+ def place_async(xdotool=None, size=None, place_timeout_s=20.0):
103
+ """Watch for a popup some OTHER process launches (the xdg-open shim under
104
+ `claude auth login`) and park it like open_auth_popup does."""
105
+ from meltygui.core.runtime.toggles import Toggles
106
+ xdotool = xdotool or shutil.which("xdotool")
107
+ if xdotool is None:
108
+ return
109
+ handle = PopupHandle(None)
110
+ threading.Thread(target=_place,
111
+ args=(handle, None, xdotool, size or Toggles.InternetAccounts.oauth_popup_size,
112
+ place_timeout_s),
113
+ daemon=True, name="oauth-popup-place").start()
114
+
115
+
116
+ def close_popups(xdotool=None):
117
+ """Close every lsd-oauth window (flows we don't hold a Popen for)."""
118
+ xdotool = xdotool or shutil.which("xdotool")
119
+ if xdotool is None:
120
+ return
121
+ try:
122
+ found = subprocess.run([xdotool, "search", "--class", WINDOW_CLASS],
123
+ capture_output=True, text=True, timeout=5, close_fds=False)
124
+ for window_id in found.stdout.split():
125
+ subprocess.run([xdotool, "windowclose", window_id],
126
+ timeout=5, close_fds=False,
127
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
128
+ except (OSError, subprocess.TimeoutExpired):
129
+ pass
130
+
131
+
132
+ # ── the xdg-open shim for `claude auth login` ─────────────────────────────
133
+
134
+ def write_shim(browser=None):
135
+ """`~/.lsd/oauth-shim/xdg-open`: launches the placed popup for whatever
136
+ URL Claude Code opens. Regenerated per use so browser/size changes land."""
137
+ from meltygui.core.runtime.toggles import Toggles
138
+ browser = browser or find_browser(Toggles.InternetAccounts.oauth_popup_browser)
139
+ if browser is None:
140
+ return None
141
+ width, height = Toggles.InternetAccounts.oauth_popup_size
142
+ PROFILE_DIR.mkdir(parents=True, exist_ok=True)
143
+ SHIM_DIR.mkdir(parents=True, exist_ok=True)
144
+ shim = SHIM_DIR / "xdg-open"
145
+ shim.write_text(
146
+ "#!/bin/sh\n"
147
+ "# latent-descent oauth shim: Claude Code's browser-open, as a placed popup\n"
148
+ f'exec "{browser}" "--app=$1" --ozone-platform=x11 --class={WINDOW_CLASS} '
149
+ f"--window-size={width},{height} \"--user-data-dir={PROFILE_DIR}\" "
150
+ "--no-first-run --no-default-browser-check "
151
+ ">/dev/null 2>&1 &\n")
152
+ shim.chmod(shim.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
153
+ return shim
154
+
155
+
156
+ def shim_env(base_env):
157
+ """`base_env` with the shim first on PATH (and as $BROWSER) — pass to the
158
+ `claude auth login` subprocess; unchanged when popups can't happen."""
159
+ if not popup_available() or write_shim() is None:
160
+ return dict(base_env)
161
+ env = dict(base_env)
162
+ env["PATH"] = f"{SHIM_DIR}:{env.get('PATH', '')}"
163
+ env["BROWSER"] = str(SHIM_DIR / "xdg-open")
164
+ return env
165
+
166
+
167
+ # ── placement ─────────────────────────────────────────────────────────────
168
+
169
+ def _place(handle, url, xdotool, size, timeout_s):
170
+ """Wait for the popup's X window, then park it beside the pointer
171
+ (clamped to the display span) and raise it. If the browser died before
172
+ a window appeared (bad flags, broken profile) fall back to xdg-open."""
173
+ width, height = size
174
+ # Within this margin of any display edge the popup is pushed inward.
175
+ margin = 16
176
+ # The popup is this far above the pointer so the title area isn't under it.
177
+ pointer_lift = 60
178
+ deadline = time.monotonic() + timeout_s
179
+
180
+ def run(*args):
181
+ return subprocess.run([xdotool, *args], capture_output=True, text=True,
182
+ timeout=10, close_fds=False)
183
+
184
+ window_id = None
185
+ while time.monotonic() < deadline and window_id is None:
186
+ try:
187
+ found = run("search", "--onlyvisible", "--class", WINDOW_CLASS)
188
+ ids = found.stdout.split()
189
+ if ids:
190
+ window_id = ids[-1]
191
+ break
192
+ except (OSError, subprocess.TimeoutExpired):
193
+ return
194
+ if (handle.process is not None and handle.process.poll() not in (None, 0)
195
+ and url is not None):
196
+ _fallback(url) # the popup was but not showing anything
197
+ return
198
+ time.sleep(0.25)
199
+ if window_id is None:
200
+ return
201
+ try:
202
+ mouse = run("getmouselocation", "--shell").stdout
203
+ position = dict(line.split("=", 1) for line in mouse.split() if "=" in line)
204
+ screen = run("getdisplaygeometry").stdout.split()
205
+ screen_width, screen_height = int(screen[0]), int(screen[1])
206
+ x = max(margin, min(int(position.get("X", 0)) - width // 2, screen_width - width - margin))
207
+ y = max(margin, min(int(position.get("Y", 0)) - pointer_lift, screen_height - height - margin))
208
+ run("windowmove", window_id, str(x), str(y))
209
+ run("windowactivate", window_id)
210
+ handle.placed = True
211
+ except (OSError, subprocess.TimeoutExpired, ValueError, IndexError):
212
+ pass
213
+
214
+
215
+ def _fallback(url):
216
+ try:
217
+ from meltygui.completion.providers.copilot import open_url
218
+ open_url(url)
219
+ except Exception:
220
+ pass
@@ -0,0 +1,320 @@
1
+ """Ollama FIM provider — native fill-in-the-middle through a local Ollama
2
+ server (`/api/generate` with `suffix`, streamed). Any FIM-trained model
3
+ works (`qwen2.5-coder:*`, `codellama:*-code`, `deepseek-coder*`,
4
+ `starcoder2`); a chat-only model ignores `suffix` and completes the prefix.
5
+
6
+ Session: one keep-alive HTTP client per host (an Internet Accounts entry of
7
+ kind "ollama"). The account also carries the DEVICE the model should live
8
+ on (`device`: "auto" | "cpu" | "gpu:N" in Ollama's own GPU numbering) —
9
+ every request passes it as llama.cpp options (`main_gpu` / `num_gpu: 0`),
10
+ which is how Ollama decides placement (verified 2026-08-22: main_gpu=2 put
11
+ the model on the H100). Ollama numbers GPUs in CUDA-runtime order —
12
+ `gpu_inventory()` reads that order (names + live memory) from `torch.cuda`,
13
+ which shares the process's already-initialized CUDA runtime and is thread-
14
+ safe, so the probe never spins up a second CUDA context (a bare
15
+ `pycuda.driver.init()` on a probe thread could race the render thread's GL
16
+ context at boot and hang the load) and never shells out to nvidia-smi.
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ import threading
22
+ import time
23
+
24
+ from meltygui.completion.fim import FimRequest
25
+ from meltygui.completion.fim import FimResult
26
+ from meltygui.completion.fim import FimSession
27
+ from meltygui.completion.fim import fim_provider
28
+
29
+
30
+ class OllamaSession(FimSession):
31
+ """One keep-alive client for the Ollama host of Internet Accounts entry
32
+ `account` (kind "ollama": `host`); an explicit `host` overrides it."""
33
+ KIND = "ollama"
34
+
35
+ def __init__(self, account="default", host=None, timeout_s=30.0):
36
+ import httpx
37
+ from meltygui.accounts.internet_accounts import account_field
38
+ self.account = account
39
+ host = host or account_field("ollama", account, "host") or "http://localhost:11434"
40
+ self.host = host.rstrip("/")
41
+ # Short CONNECT timeout so a down/unreachable server fails fast instead
42
+ # of blocking a gui thread for the long read timeout; generation
43
+ # itself keeps the long read timeout.
44
+ self.client = httpx.Client(base_url=self.host,
45
+ timeout=httpx.Timeout(timeout_s, connect=2.0))
46
+ self._status = ("ready",)
47
+
48
+ def status(self):
49
+ return self._status
50
+
51
+ def close(self):
52
+ try:
53
+ self.client.close()
54
+ except Exception:
55
+ pass
56
+
57
+
58
+ # ──────────────────────────────────────────────────────────────────────────
59
+ # Helpers
60
+ # ──────────────────────────────────────────────────────────────────────────
61
+
62
+ def device_options(device) -> dict:
63
+ """llama.cpp options for an account's `device` setting."""
64
+ if not device or device == "auto":
65
+ return {}
66
+ if device == "cpu":
67
+ return {"num_gpu": 0}
68
+ if device.startswith("gpu:"):
69
+ try:
70
+ return {"main_gpu": int(device[4:])}
71
+ except ValueError:
72
+ return {}
73
+ return {}
74
+
75
+
76
+ _inventory_cache = [0.0, None]
77
+
78
+
79
+ def gpu_inventory(max_age=3.0) -> list:
80
+ """[{ollama_index, name, short, used_mib, total_mib}] in OLLAMA (CUDA
81
+ runtime) order — read from torch.cuda, which shares the process's
82
+ already-initialized CUDA runtime (same device order Ollama's llama.cpp
83
+ sees) and is thread-safe. Deliberately NOT pycuda: a bare
84
+ `pycuda.driver.init()` on a probe thread can race the render thread's
85
+ CUDA/GL context creation at boot and hang the load. No subprocess
86
+ either — no nvidia-smi. Best-effort; cached briefly."""
87
+ now = time.monotonic()
88
+ if _inventory_cache[1] is not None and now - _inventory_cache[0] < max_age:
89
+ return _inventory_cache[1]
90
+ gpus = []
91
+ try:
92
+ import torch
93
+ if torch.cuda.is_available():
94
+ for i in range(torch.cuda.device_count()):
95
+ p = torch.cuda.get_device_properties(i)
96
+ try:
97
+ free, total = torch.cuda.mem_get_info(i)
98
+ except Exception:
99
+ free, total = 0, int(getattr(p, "total_memory", 0))
100
+ name = p.name.replace("NVIDIA ", "").replace("GeForce ", "")
101
+ gpus.append({"ollama_index": i, "name": name,
102
+ "short": name.replace(" NVL", "").replace("RTX ", ""),
103
+ "used_mib": int((total - free) / 1048576),
104
+ "total_mib": int(total / 1048576), "order": "cuda"})
105
+ except Exception:
106
+ gpus = []
107
+ _inventory_cache[0] = now
108
+ _inventory_cache[1] = gpus
109
+ return gpus
110
+
111
+
112
+ def _gpu_for_vram(gpus, size_vram) -> dict | None:
113
+ """The GPU a model of `size_vram` bytes most likely sits on — the one
114
+ whose used memory best matches (torch mem_get_info, no per-process
115
+ attribution needed). Good enough for one resident model."""
116
+ want = size_vram / 1048576
117
+ best, best_d = None, None
118
+ for g in gpus:
119
+ if g["used_mib"] >= 0.4 * want:
120
+ d = abs(g["used_mib"] - want)
121
+ if best_d is None or d < best_d:
122
+ best, best_d = g, d
123
+ return best
124
+
125
+
126
+ def device_label(device, gpus=None) -> str:
127
+ """Label for a device setting. `gpus` is the CACHED inventory (or None) —
128
+ this NEVER queries hardware, so it is safe on the render thread. Without
129
+ an inventory a GPU shows as a bare "GPUn" until a probe fills the names."""
130
+ if not device or device == "auto":
131
+ return "auto"
132
+ if device == "cpu":
133
+ return "CPU"
134
+ if device.startswith("gpu:"):
135
+ try:
136
+ i = int(device[4:])
137
+ except ValueError:
138
+ return device
139
+ for g in (gpus or ()):
140
+ if g["ollama_index"] == i:
141
+ return f"GPU{i} {g['short']}"
142
+ return f"GPU{i}"
143
+ return device
144
+
145
+
146
+ def device_choices(gpus=None) -> list:
147
+ """Device options from the CACHED inventory (or None) — no hardware
148
+ query. Falls back to auto/cpu only until a probe supplies the GPUs."""
149
+ return ["auto"] + [f"gpu:{g['ollama_index']}" for g in (gpus or ())] + ["cpu"]
150
+
151
+
152
+ # ──────────────────────────────────────────────────────────────────────────
153
+ # Model management (used by the Internet Accounts window)
154
+ # ──────────────────────────────────────────────────────────────────────────
155
+
156
+ def list_models(client) -> list:
157
+ """[{name, size, loaded, size_vram, expires_at, where}] — /api/tags
158
+ joined with /api/ps and the runners' GPU placement."""
159
+ tags = client.get("/api/tags", timeout=5.0).json().get("models") or []
160
+ try:
161
+ running = {m["name"]: m for m in (client.get("/api/ps", timeout=5.0).json().get("models") or [])}
162
+ except Exception:
163
+ running = {}
164
+ gpus = gpu_inventory() if running else []
165
+ out = []
166
+ for t in tags:
167
+ name = t.get("name", "?")
168
+ r = running.get(name)
169
+ loaded = r is not None
170
+ vram = int(r.get("size_vram") or 0) if r else 0
171
+ w = None
172
+ if loaded:
173
+ if vram == 0:
174
+ w = "CPU"
175
+ else:
176
+ g = _gpu_for_vram(gpus, vram)
177
+ w = f"GPU{g['ollama_index']} {g['short']}" if g else "GPU"
178
+ out.append({"name": name, "size": int(t.get("size") or 0), "loaded": loaded,
179
+ "size_vram": vram, "expires_at": (r or {}).get("expires_at"), "where": w,
180
+ "family": ((t.get("details") or {}).get("family") or "")})
181
+ out.sort(key=lambda m: (not m["loaded"], m["name"]))
182
+ return out
183
+
184
+
185
+ def load_model(client, model, device="auto", keep_alive="30m"):
186
+ """Load `model` onto `device` (an empty generate with keep_alive) — also
187
+ how a loaded model is MOVED: Ollama reloads when the options change."""
188
+ payload = {"model": model, "keep_alive": keep_alive, "options": device_options(device)}
189
+ r = client.post("/api/generate", json=payload, timeout=600.0)
190
+ if r.status_code != 200:
191
+ raise RuntimeError(f"ollama {r.status_code}: {r.text[:200]}")
192
+
193
+
194
+ def unload_model(client, model):
195
+ r = client.post("/api/generate", json={"model": model, "keep_alive": 0}, timeout=60.0)
196
+ if r.status_code != 200:
197
+ raise RuntimeError(f"ollama {r.status_code}: {r.text[:200]}")
198
+
199
+
200
+ # ──────────────────────────────────────────────────────────────────────────
201
+ # Provider
202
+ # ──────────────────────────────────────────────────────────────────────────
203
+
204
+ def _window(req: FimRequest, prefix_chars: int, suffix_chars: int):
205
+ prefix = req.annotated_prefix()
206
+ if len(prefix) > prefix_chars:
207
+ cut = prefix.rfind("\n", 0, len(prefix) - prefix_chars)
208
+ prefix = prefix[cut + 1:] if cut >= 0 else prefix[-prefix_chars:]
209
+ suffix = req.suffix
210
+ if len(suffix) > suffix_chars:
211
+ cut = suffix.find("\n", suffix_chars)
212
+ suffix = suffix[:cut] if cut >= 0 else suffix[:suffix_chars]
213
+ return prefix, suffix
214
+
215
+
216
+ @fim_provider(name="ollama", session=OllamaSession)
217
+ def ollama_fim(req: FimRequest, session: OllamaSession, model="qwen2.5-coder:7b",
218
+ prefix_chars=6000, suffix_chars=1500, context_chars=3000,
219
+ temperature=0.2, device=None, keep_alive=None) -> FimResult:
220
+ """Local FIM. Stable context rides ahead of the prefix as commented
221
+ blocks (FIM models have no side channel for it); the run block is
222
+ inlined through `annotated_prefix`. `device` / `keep_alive` default to
223
+ the account's settings (Internet Accounts → Ollama)."""
224
+ from meltygui.core.runtime.toggles import Toggles
225
+ from meltygui.accounts.internet_accounts import account_field
226
+ if device is None:
227
+ device = account_field("ollama", session.account, "device", "auto")
228
+ if keep_alive is None:
229
+ keep_alive = Toggles.Fim.ollama_keep_alive
230
+ prefix, suffix = _window(req, prefix_chars, suffix_chars)
231
+ ctx_text = req.context.render(("stable",)) if req.context is not None else ""
232
+ if ctx_text:
233
+ ctx_text = ctx_text[-context_chars:]
234
+ commented = "\n".join("# " + ln if ln.strip() else "#" for ln in ctx_text.split("\n"))
235
+ prefix = "# --- context ---\n" + commented + "\n# --- end context ---\n\n" + prefix
236
+ options = {"num_predict": max(16, req.max_tokens), "temperature": temperature}
237
+ options.update(device_options(device))
238
+ payload = {"model": model, "prompt": prefix, "suffix": suffix, "stream": True,
239
+ "keep_alive": keep_alive, "options": options}
240
+ acc = []
241
+ try:
242
+ try:
243
+ reason = _generate(session, payload, req, acc)
244
+ except _Unsupported as e:
245
+ # Not a FIM model - retry prefix-only (and with thinking off for a
246
+ # reasoning model, whose output would otherwise all be thinking).
247
+ # Quality is lower - the model can't see the suffix - but the
248
+ # provider still works; the status says so.
249
+ if "insert" in e.what:
250
+ payload.pop("suffix", None)
251
+ reason = None
252
+ if "insert" in e.what or "think" in e.what:
253
+ payload["think"] = False
254
+ try:
255
+ reason = _generate(session, payload, req, acc)
256
+ except _Unsupported as e2:
257
+ if "think" not in e2.what:
258
+ raise
259
+ payload.pop("think", None)
260
+ reason = _generate(session, payload, req, acc)
261
+ session._status = ("ready", f"{model}: no FIM support, prefix-only")
262
+ return FimResult("".join(acc), provider="ollama", truncated=(reason == "length"))
263
+ except Exception as e:
264
+ session._status = ("error", str(e))
265
+ raise
266
+ session._status = ("ready",)
267
+ # done_reason "length" = hit num_predict (more to come → continue on Tab);
268
+ # "stop" = the model emitted an end token (done, don't auto-continue).
269
+ return FimResult("".join(acc), provider="ollama", truncated=(reason == "length"))
270
+
271
+
272
+ class _Unsupported(RuntimeError):
273
+ def __init__(self, what):
274
+ super().__init__(f"ollama: {what}")
275
+ self.what = what
276
+
277
+
278
+ def _generate(session, payload, req, acc):
279
+ """Stream one /api/generate call into `acc` (list of pieces), emitting
280
+ the running text. Returns the final `done_reason` ("stop" | "length" |
281
+ None). Raises _Unsupported for the model-capability errors ("does not
282
+ support insert/thinking") so the caller can adapt."""
283
+ acc.clear()
284
+ saw_thinking = False
285
+ done_reason = None
286
+ with session.client.stream("POST", "/api/generate", json=payload) as resp:
287
+ if resp.status_code != 200:
288
+ body = resp.read().decode("utf-8", "replace")[:300]
289
+ if "does not support" in body:
290
+ raise _Unsupported(body)
291
+ raise RuntimeError(f"ollama {resp.status_code}: {body}")
292
+ for line in resp.iter_lines():
293
+ if req.cancelled.is_set():
294
+ break
295
+ if not line:
296
+ continue
297
+ try:
298
+ msg = json.loads(line)
299
+ except ValueError:
300
+ continue
301
+ if msg.get("error"):
302
+ err = str(msg["error"])
303
+ if "does not support" in err:
304
+ raise _Unsupported(err)
305
+ raise RuntimeError(f"ollama: {err}")
306
+ if msg.get("thinking"):
307
+ saw_thinking = True
308
+ piece = msg.get("response", "")
309
+ if piece:
310
+ acc.append(piece)
311
+ req.emit("".join(acc))
312
+ if msg.get("done"):
313
+ done_reason = msg.get("done_reason")
314
+ break
315
+ if saw_thinking and not acc and "think" not in payload and not req.cancelled.is_set():
316
+ # A reasoning model spent the entire budget thinking (happens when the
317
+ # suffix is empty, so Ollama didn't reject the insert) - retry with
318
+ # thinking off so the tokens go to the completion.
319
+ raise _Unsupported("thinking consumed the budget")
320
+ return done_reason
@@ -0,0 +1,23 @@
1
+ """Named FIM profiles: a provider plus configuration. Every provider already
2
+ registers a default profile under its own name ("claude", "ollama"); add
3
+ variants here. Kwargs that name a parameter of the provider's session
4
+ class select/construct the SESSION (so two profiles with different session
5
+ kwargs get two live sessions — e.g. two accounts); the rest override the
6
+ provider function's per-request params.
7
+
8
+ fim_profile("claude-fast", claude_fim, model="claude-haiku-4-5")
9
+ fim_profile("copilot-work", copilot_fim, config_dir="~/.config/github-copilot-work")
10
+
11
+ Pick a profile per editor with `draw_text(..., fim="claude-fast")` or
12
+ globally with `Toggles.Fim.profile`.
13
+ """
14
+ from meltygui.completion.fim import fim_profile
15
+ from meltygui.completion.providers.claude import claude_fim
16
+ from meltygui.completion.providers.copilot import copilot_fim
17
+ from meltygui.completion.providers.ollama import ollama_fim
18
+
19
+ fim_profile("claude-fast", claude_fim, model="claude-haiku-4-5", effort=None)
20
+ fim_profile("ollama-qwen", ollama_fim, model="qwen2.5-coder:7b")
21
+ # A second github login: add a "copilot" account in Internet Accounts with
22
+ # its own config dir, then point a profile at it by account id.
23
+ fim_profile("copilot-2", copilot_fim, account="copilot-2")
@@ -0,0 +1,88 @@
1
+ # Core: rendering plumbing and shared runtime
2
+
3
+ Core makes reusable render functions work: it supplies their inputs, tracks state,
4
+ runs converters, dispatches events, caches drawing and connects windows to the OS.
5
+ `Melty` owns the shared runtime. Feature rendering belongs in `view/<feature>_view.py`;
6
+ feature adapters belong in `model/<feature>_model.py`; injected feature state belongs
7
+ in `state/<feature>_state.py`.
8
+
9
+ ## Where to start
10
+
11
+ The root keeps five Python modules: `core_render.py`, `melty.py`,
12
+ `definition_hotswap.py`, `module_names.py`, and the package initializer.
13
+ `module_map.json` translates identifiers in older saved sessions.
14
+ Everything else is grouped by the runtime responsibility it serves:
15
+
16
+ | Folder | Responsibility and main entry points |
17
+ |---|---|
18
+ | `input/` | Event delivery, devices, hit testing, drag/drop and selection: `input_handler.py`, `collision.py`, `drag_drop_core.py` |
19
+ | `rendering/` | Render dispatch, registration, parameter injection support, modes and decorators: `render_dispatch.py`, `parameter_core.py`, `mode.py` |
20
+ | `conversion/` | Dict-like objects, conversion graphs, hosting and persistence: `dict_conversion.py`, `render_host.py`, `load_save_v2.py` |
21
+ | `cache/` | Drawing caches and invalidation: `tile_cache.py`, `invalidation_tracker.py` |
22
+ | `windowing/` | Surface lifecycle, native windows, chrome and platform backends: `surface.py`, `window_api.py`, `backends/` |
23
+ | `graphics/` | Shared GL resources, shaders, overlays, capture and tensor/graph integration: `gl_state.py`, `shader_func.py`, `lut_core.py`, `cuda_context_core.py`, `cuda_interop_core.py`, `cuda_kernel_core.py` |
24
+ | `layout/` | Cursor, grid, column, header and dropdown plumbing |
25
+ | `styling/` | Shared styles, colours, fonts and font warmup |
26
+ | `files/` | Filesystem polling, metadata and file/import-tree integration |
27
+ | `runtime/` | App/session lifecycle, scheduling, settings and shared process helpers |
28
+ | `diagnostics/` | Notifications, profiling, tracing, inspection and diagnostics integration |
29
+ | `automation/` | Orchestration, actions, queries, search and MCP integration |
30
+ | `services/` | Terminal, chat and account runtime integration |
31
+
32
+ These folders organize wiring; they do not turn feature algorithms or local
33
+ presentation into core code. Some inherited integration modules remain mixed;
34
+ see [the outstanding ownership review](../../docs/ARCHITECTURE_DEBT.md).
35
+
36
+ ## Shared presentation inputs
37
+
38
+ Render functions can declare `ui_scale` and `font_manager` in their signatures,
39
+ alongside the existing `style_manager` input. Core supplies the current runtime
40
+ scale and font manager; explicit scale/font-manager overrides are supported for
41
+ previews. These dependencies are excluded from saved view parameters and the
42
+ parameter controls. Views can use their own `draw_state.depth_and_layer` for
43
+ local drawing depth. This keeps feature presentation independent of `Melty`
44
+ lookups without making callers pass the same plumbing repeatedly.
45
+
46
+ `keyboard_available` is true when no text editor owns keyboard focus;
47
+ `pointer_buttons_down` reports whether any primary pointer button is held.
48
+ Core supplies these only to views declaring them. Like scale and font context,
49
+ they allow explicit overrides and are excluded from saved parameter controls.
50
+
51
+ Palette consumers declare `luts`. Core injects the shared `LutPalette` from
52
+ `Melty.luts`, or accepts an explicit override, and subscribes cached consumers
53
+ before the render-cache gate. `luts.texture(name)` is an integer-like texture ID:
54
+ the model handles lazy uploads, updates and per-context storage. There is no
55
+ palette host or separate resource service. `GLState` releases context resources
56
+ when a surface closes. Palette values and proxies belong in `model/lut_model.py`;
57
+ selection and swatches belong in `view/lut_view.py`.
58
+
59
+ ## CUDA interop ownership
60
+
61
+ `cuda_context_core.py` owns primary-context leases and scoped device activation
62
+ for voxel kernels, line kernels and GL interop. Runtime state lives on
63
+ `Melty.cuda_interop`; the existing field also holds the per-device context pool.
64
+ `cuda_interop_core.py` selects the GL-compatible device and manages registered
65
+ buffers, mapping and copies through that shared context manager.
66
+ `model/cuda_texture_model.py` owns versioned tensor uploads as `GLTexture` values.
67
+ Their composite allocations use the caller's `GLState`, including partial-allocation
68
+ cleanup and deferred unregistration retries. Feature renderers do not own CUDA
69
+ context setup. `cuda_kernel_core.py` owns compilation and cached modules. Voxel and line CUDA
70
+ presentation live in `view/voxel_cuda_view.py` and `view/graph_cuda_view.py`.
71
+
72
+ ## Why mode has three files
73
+
74
+ `rendering/mode.py` defines the real enum and its renderer/converter policies. `rendering/modes.py`
75
+ provides lazy `Modes.X` handles so decorators can refer to modes before their
76
+ renderers finish importing. `rendering/mode_defaults.py` holds shared type-to-mode defaults,
77
+ including delayed registration for optional dependencies. Combining these at
78
+ import time would recreate the mode/renderer import cycle.
79
+
80
+ The public package exports remain available from `meltygui`. Internal imports
81
+ use current modules; there are no legacy import aliases, forwarding shims or
82
+ virtual historical namespaces. Definition hotswap preserves live objects and
83
+ state. Saved-name translation belongs to session loading, and source navigation
84
+ resolves actual imports. Update the editor and its Pro dependency with source moves.
85
+
86
+ See [the move inventory and checks](../../docs/CORE_RELOCATION.md). The mixed
87
+ `state/new_core_model.py` and the text-editor implementation await the separate
88
+ editor/state refactor.
@@ -0,0 +1 @@
1
+ """Shared framework wiring."""
@@ -0,0 +1 @@
1
+ """Shared framework automation machinery."""