cct-cli 0.7.9.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 (325) hide show
  1. calc_terminal/__init__.py +14 -0
  2. calc_terminal/__main__.py +14 -0
  3. calc_terminal/activity.py +1334 -0
  4. calc_terminal/agent.py +3387 -0
  5. calc_terminal/agent_runtime.py +519 -0
  6. calc_terminal/ai_context.py +447 -0
  7. calc_terminal/ai_modes.py +752 -0
  8. calc_terminal/ai_personalization.py +286 -0
  9. calc_terminal/ai_preview_feedback.py +213 -0
  10. calc_terminal/aicore.py +2572 -0
  11. calc_terminal/anim.py +367 -0
  12. calc_terminal/app.py +3685 -0
  13. calc_terminal/art.py +639 -0
  14. calc_terminal/atomsim.py +368 -0
  15. calc_terminal/attachments.py +743 -0
  16. calc_terminal/benchmark_system.py +414 -0
  17. calc_terminal/browser/__init__.py +36 -0
  18. calc_terminal/browser/browser_state.py +346 -0
  19. calc_terminal/browser/devserver.py +176 -0
  20. calc_terminal/browser/engine.py +494 -0
  21. calc_terminal/browser/navigation.py +84 -0
  22. calc_terminal/browser/preview.py +429 -0
  23. calc_terminal/browser/preview_entry.py +95 -0
  24. calc_terminal/browser/project_detector.py +144 -0
  25. calc_terminal/browser/server.py +449 -0
  26. calc_terminal/browser/state.py +75 -0
  27. calc_terminal/browser/watcher.py +99 -0
  28. calc_terminal/browser_gui/__init__.py +1 -0
  29. calc_terminal/browser_gui/__main__.py +3 -0
  30. calc_terminal/browser_gui/launcher.py +173 -0
  31. calc_terminal/browser_gui/playwright_browser.py +117 -0
  32. calc_terminal/browser_gui/qt_browser.py +1501 -0
  33. calc_terminal/browser_gui/webview_browser.py +57 -0
  34. calc_terminal/capabilities/__init__.py +35 -0
  35. calc_terminal/capabilities/adapters/__init__.py +33 -0
  36. calc_terminal/capabilities/adapters/bioinformatics.py +204 -0
  37. calc_terminal/capabilities/adapters/browser_adapter.py +205 -0
  38. calc_terminal/capabilities/adapters/filesystem.py +206 -0
  39. calc_terminal/capabilities/adapters/git_adapter.py +202 -0
  40. calc_terminal/capabilities/adapters/jupyter_adapter.py +138 -0
  41. calc_terminal/capabilities/adapters/ml_frameworks.py +158 -0
  42. calc_terminal/capabilities/adapters/platforms.py +200 -0
  43. calc_terminal/capabilities/adapters/python_exec.py +93 -0
  44. calc_terminal/capabilities/adapters/quantum_adapter.py +150 -0
  45. calc_terminal/capabilities/adapters/scientific_comp.py +123 -0
  46. calc_terminal/capabilities/adapters/structural_bio.py +161 -0
  47. calc_terminal/capabilities/adapters/terminal.py +99 -0
  48. calc_terminal/capabilities/bus.py +178 -0
  49. calc_terminal/capabilities/discovery.py +207 -0
  50. calc_terminal/capabilities/schema.py +221 -0
  51. calc_terminal/cat.ico +0 -0
  52. calc_terminal/cat_browser.py +2018 -0
  53. calc_terminal/chat_store.py +703 -0
  54. calc_terminal/cli.py +1178 -0
  55. calc_terminal/code_editor.py +640 -0
  56. calc_terminal/collaboration.py +723 -0
  57. calc_terminal/commands_data.py +139 -0
  58. calc_terminal/compatibility_engine.py +352 -0
  59. calc_terminal/compute/__init__.py +31 -0
  60. calc_terminal/compute/fabric.py +350 -0
  61. calc_terminal/config.py +227 -0
  62. calc_terminal/core/__init__.py +41 -0
  63. calc_terminal/core/checkpoint.py +156 -0
  64. calc_terminal/core/input/__init__.py +45 -0
  65. calc_terminal/core/mode_registry.py +300 -0
  66. calc_terminal/core/project_graph.py +172 -0
  67. calc_terminal/core/recovery.py +129 -0
  68. calc_terminal/core/security_layer.py +112 -0
  69. calc_terminal/core/task_graph.py +202 -0
  70. calc_terminal/core/unified_runtime.py +184 -0
  71. calc_terminal/core/verification.py +257 -0
  72. calc_terminal/customization.py +1566 -0
  73. calc_terminal/derivations.py +153 -0
  74. calc_terminal/device_control.py +263 -0
  75. calc_terminal/diagnostics/__init__.py +27 -0
  76. calc_terminal/diagnostics/doctor_engine.py +382 -0
  77. calc_terminal/diagnostics/self_test.py +247 -0
  78. calc_terminal/doctor.py +519 -0
  79. calc_terminal/easter_eggs.py +274 -0
  80. calc_terminal/editor/__init__.py +1 -0
  81. calc_terminal/editor/actions.py +263 -0
  82. calc_terminal/editor/commands.py +160 -0
  83. calc_terminal/editor/shortcuts.py +226 -0
  84. calc_terminal/engine.py +259 -0
  85. calc_terminal/errors.py +120 -0
  86. calc_terminal/event_stream.py +146 -0
  87. calc_terminal/eventbus.py +133 -0
  88. calc_terminal/extensions.py +733 -0
  89. calc_terminal/fallback_cli.py +1321 -0
  90. calc_terminal/first_run.py +265 -0
  91. calc_terminal/fomoji_auth.py +1043 -0
  92. calc_terminal/formulas.py +82 -0
  93. calc_terminal/fs_cache.py +121 -0
  94. calc_terminal/fs_watcher.py +277 -0
  95. calc_terminal/game.py +193 -0
  96. calc_terminal/gen1.py +5 -0
  97. calc_terminal/generators.py +245 -0
  98. calc_terminal/gestures/__init__.py +42 -0
  99. calc_terminal/gestures/bindings.py +175 -0
  100. calc_terminal/gestures/manager.py +477 -0
  101. calc_terminal/goodbye.py +363 -0
  102. calc_terminal/gpu3d.py +290 -0
  103. calc_terminal/graphs.py +358 -0
  104. calc_terminal/hardware_analyzer.py +440 -0
  105. calc_terminal/host/__init__.py +30 -0
  106. calc_terminal/host/browser_manager.py +187 -0
  107. calc_terminal/host/desktop.py +1386 -0
  108. calc_terminal/host/launcher.py +395 -0
  109. calc_terminal/host/terminal.py +279 -0
  110. calc_terminal/identity.py +216 -0
  111. calc_terminal/input/__init__.py +54 -0
  112. calc_terminal/input/capabilities.py +258 -0
  113. calc_terminal/input/focus.py +87 -0
  114. calc_terminal/input/gestures.py +64 -0
  115. calc_terminal/input/pointer.py +114 -0
  116. calc_terminal/input/touch.py +345 -0
  117. calc_terminal/keys.py +84 -0
  118. calc_terminal/live_automation.py +165 -0
  119. calc_terminal/mathtext.py +433 -0
  120. calc_terminal/mcp.py +386 -0
  121. calc_terminal/memory.py +337 -0
  122. calc_terminal/memory_v2.py +479 -0
  123. calc_terminal/metrics.py +333 -0
  124. calc_terminal/mode_detection.py +146 -0
  125. calc_terminal/model.py +2431 -0
  126. calc_terminal/model_router.py +665 -0
  127. calc_terminal/models/__init__.py +0 -0
  128. calc_terminal/models/active_state.py +187 -0
  129. calc_terminal/models/dynamic_registry.py +584 -0
  130. calc_terminal/models/manager.py +781 -0
  131. calc_terminal/models/model_metadata.json +3526 -0
  132. calc_terminal/models/profiles.py +194 -0
  133. calc_terminal/models/registry.py +265 -0
  134. calc_terminal/models/schema.py +197 -0
  135. calc_terminal/models/validator.py +287 -0
  136. calc_terminal/models/verification_engine.py +368 -0
  137. calc_terminal/native_picker.py +215 -0
  138. calc_terminal/ollama_catalog.py +279 -0
  139. calc_terminal/ollama_download.py +233 -0
  140. calc_terminal/orchestrator.py +304 -0
  141. calc_terminal/package_research.py +322 -0
  142. calc_terminal/packages.py +1024 -0
  143. calc_terminal/pc_specs.py +116 -0
  144. calc_terminal/permissions.py +334 -0
  145. calc_terminal/pet.py +106 -0
  146. calc_terminal/pipeline.py +505 -0
  147. calc_terminal/platform/__init__.py +491 -0
  148. calc_terminal/platform/desktop.py +491 -0
  149. calc_terminal/platform/web.py +781 -0
  150. calc_terminal/preview/__init__.py +1 -0
  151. calc_terminal/preview/dev_server.py +303 -0
  152. calc_terminal/preview/diagnostics.py +131 -0
  153. calc_terminal/preview/live_reload.py +66 -0
  154. calc_terminal/preview/manager.py +129 -0
  155. calc_terminal/project_stats.py +209 -0
  156. calc_terminal/projects.py +328 -0
  157. calc_terminal/providers/__init__.py +0 -0
  158. calc_terminal/providers/adapters/__init__.py +80 -0
  159. calc_terminal/providers/adapters/anthropic_adapter.py +127 -0
  160. calc_terminal/providers/adapters/base.py +106 -0
  161. calc_terminal/providers/adapters/chinese_adapters.py +420 -0
  162. calc_terminal/providers/adapters/gemini_adapter.py +101 -0
  163. calc_terminal/providers/adapters/ollama_adapter.py +83 -0
  164. calc_terminal/providers/adapters/openai_adapter.py +159 -0
  165. calc_terminal/providers/adapters/other_adapters.py +246 -0
  166. calc_terminal/providers/anthropic_provider.py +172 -0
  167. calc_terminal/providers/auto_update.py +416 -0
  168. calc_terminal/providers/base_provider.py +105 -0
  169. calc_terminal/providers/discovery_manager.py +207 -0
  170. calc_terminal/providers/gemini_provider.py +178 -0
  171. calc_terminal/providers/lifecycle.py +767 -0
  172. calc_terminal/providers/ollama_adapter.py +707 -0
  173. calc_terminal/providers/openai_provider.py +254 -0
  174. calc_terminal/providers/provider_manager.py +1827 -0
  175. calc_terminal/providers/providers.json +4075 -0
  176. calc_terminal/reactionsim.py +279 -0
  177. calc_terminal/registry.py +337 -0
  178. calc_terminal/report.py +162 -0
  179. calc_terminal/research/__init__.py +45 -0
  180. calc_terminal/research/artifact_intel.py +126 -0
  181. calc_terminal/research/data_lineage.py +123 -0
  182. calc_terminal/research/experiment_ledger.py +303 -0
  183. calc_terminal/research/reproducibility.py +131 -0
  184. calc_terminal/resilience/__init__.py +47 -0
  185. calc_terminal/resilience/agent_state.py +121 -0
  186. calc_terminal/resilience/capability_matcher.py +174 -0
  187. calc_terminal/resilience/circuit_breaker.py +158 -0
  188. calc_terminal/resilience/failover_engine.py +230 -0
  189. calc_terminal/resilience/health_monitor.py +192 -0
  190. calc_terminal/resilience/ollama_adapter.py +125 -0
  191. calc_terminal/resilience/orchestrator.py +312 -0
  192. calc_terminal/resilience/types.py +134 -0
  193. calc_terminal/sandbox.py +98 -0
  194. calc_terminal/scires.py +558 -0
  195. calc_terminal/security_scanner.py +126 -0
  196. calc_terminal/session.py +294 -0
  197. calc_terminal/sim3d.py +206 -0
  198. calc_terminal/solver.py +276 -0
  199. calc_terminal/sound.py +127 -0
  200. calc_terminal/task_reports.py +287 -0
  201. calc_terminal/terminal_host.py +201 -0
  202. calc_terminal/terminal_identity.py +411 -0
  203. calc_terminal/test_ai_mode_reliability.py +344 -0
  204. calc_terminal/test_browser.py +368 -0
  205. calc_terminal/test_code_editor_upgrade.py +485 -0
  206. calc_terminal/test_customization.py +1148 -0
  207. calc_terminal/test_customization_ui.py +612 -0
  208. calc_terminal/test_dynamic_registry.py +304 -0
  209. calc_terminal/test_extensions.py +436 -0
  210. calc_terminal/test_overhaul.py +557 -0
  211. calc_terminal/test_project_detect.py +255 -0
  212. calc_terminal/test_root_cause_fix.py +527 -0
  213. calc_terminal/test_stability.py +532 -0
  214. calc_terminal/test_terminal_identity.py +132 -0
  215. calc_terminal/test_v079_speed.py +460 -0
  216. calc_terminal/theme.py +1107 -0
  217. calc_terminal/timeline.py +139 -0
  218. calc_terminal/todos.py +246 -0
  219. calc_terminal/tool_call_normalizer.py +419 -0
  220. calc_terminal/tui.py +104 -0
  221. calc_terminal/ui/__init__.py +8 -0
  222. calc_terminal/ui/activity_panel.py +231 -0
  223. calc_terminal/ui/activity_stream_panel.py +238 -0
  224. calc_terminal/ui/animations.py +122 -0
  225. calc_terminal/ui/app.py +7271 -0
  226. calc_terminal/ui/attach_panel.py +597 -0
  227. calc_terminal/ui/attachments.py +424 -0
  228. calc_terminal/ui/backup_panel.py +810 -0
  229. calc_terminal/ui/browser_shell.py +887 -0
  230. calc_terminal/ui/cat_agent.py +357 -0
  231. calc_terminal/ui/chats_panel.py +899 -0
  232. calc_terminal/ui/command_palette.py +125 -0
  233. calc_terminal/ui/command_palette_modal.py +166 -0
  234. calc_terminal/ui/composer.py +1141 -0
  235. calc_terminal/ui/context_menu.py +197 -0
  236. calc_terminal/ui/conversation.py +1435 -0
  237. calc_terminal/ui/customization_panel.py +1229 -0
  238. calc_terminal/ui/dashboard.py +404 -0
  239. calc_terminal/ui/design_system.py +557 -0
  240. calc_terminal/ui/diff_panel.py +213 -0
  241. calc_terminal/ui/editor.py +2102 -0
  242. calc_terminal/ui/empty_state.py +302 -0
  243. calc_terminal/ui/events.py +487 -0
  244. calc_terminal/ui/extensions_panel.py +815 -0
  245. calc_terminal/ui/footer.py +166 -0
  246. calc_terminal/ui/gestures_panel.py +383 -0
  247. calc_terminal/ui/goodbye_screen.py +100 -0
  248. calc_terminal/ui/header.py +1034 -0
  249. calc_terminal/ui/help_panel.py +254 -0
  250. calc_terminal/ui/live_activities.py +914 -0
  251. calc_terminal/ui/mcp_panel.py +570 -0
  252. calc_terminal/ui/memory_center.py +524 -0
  253. calc_terminal/ui/mode_colors_panel.py +525 -0
  254. calc_terminal/ui/nav_screens.py +747 -0
  255. calc_terminal/ui/ollama_panel.py +536 -0
  256. calc_terminal/ui/palette.py +221 -0
  257. calc_terminal/ui/permission_panel.py +269 -0
  258. calc_terminal/ui/personalization_panel.py +517 -0
  259. calc_terminal/ui/personalize_center.py +1568 -0
  260. calc_terminal/ui/preview_panel.py +441 -0
  261. calc_terminal/ui/resizers.py +402 -0
  262. calc_terminal/ui/sidebar.py +1285 -0
  263. calc_terminal/ui/statusbar.py +168 -0
  264. calc_terminal/ui/theme_css.py +1396 -0
  265. calc_terminal/ui/thinking.py +226 -0
  266. calc_terminal/ui/timeline_panel.py +102 -0
  267. calc_terminal/ui/todo_panel.py +193 -0
  268. calc_terminal/ui/viewport.py +136 -0
  269. calc_terminal/ui/vision_panel.py +489 -0
  270. calc_terminal/ui/welcome_modal.py +343 -0
  271. calc_terminal/ui/widgets.py +160 -0
  272. calc_terminal/ui/workspace.py +831 -0
  273. calc_terminal/viewers/__init__.py +1 -0
  274. calc_terminal/viewers/document_viewer.py +252 -0
  275. calc_terminal/viewers/image_viewer.py +241 -0
  276. calc_terminal/viewers/pdf_viewer.py +203 -0
  277. calc_terminal/viewers/presentation_viewer.py +164 -0
  278. calc_terminal/viewers/registry.py +120 -0
  279. calc_terminal/viewers/spreadsheet_viewer.py +204 -0
  280. calc_terminal/vision/__init__.py +89 -0
  281. calc_terminal/vision/analysis.py +194 -0
  282. calc_terminal/vision/annotations.py +297 -0
  283. calc_terminal/vision/capture.py +171 -0
  284. calc_terminal/vision/context.py +231 -0
  285. calc_terminal/vision/correlation.py +169 -0
  286. calc_terminal/vision/cursor.py +258 -0
  287. calc_terminal/vision/events.py +66 -0
  288. calc_terminal/vision/frame_pipeline.py +259 -0
  289. calc_terminal/vision/priority.py +218 -0
  290. calc_terminal/vision/provider.py +180 -0
  291. calc_terminal/vision/safety.py +149 -0
  292. calc_terminal/vision/session.py +281 -0
  293. calc_terminal/vision/verify.py +162 -0
  294. calc_terminal/vision.py +514 -0
  295. calc_terminal/vscode_integration.py +113 -0
  296. calc_terminal/web/__init__.py +8 -0
  297. calc_terminal/web/cat_runtime.py +710 -0
  298. calc_terminal/web/server.py +2891 -0
  299. calc_terminal/web/static/css/app.css +3152 -0
  300. calc_terminal/web/static/icons/badge-72.png +0 -0
  301. calc_terminal/web/static/icons/cat.ico +0 -0
  302. calc_terminal/web/static/icons/icon-128.png +0 -0
  303. calc_terminal/web/static/icons/icon-144.png +0 -0
  304. calc_terminal/web/static/icons/icon-152.png +0 -0
  305. calc_terminal/web/static/icons/icon-192.png +0 -0
  306. calc_terminal/web/static/icons/icon-384.png +0 -0
  307. calc_terminal/web/static/icons/icon-512.png +0 -0
  308. calc_terminal/web/static/icons/icon-72.png +0 -0
  309. calc_terminal/web/static/icons/icon-96.png +0 -0
  310. calc_terminal/web/static/icons/icon.svg +34 -0
  311. calc_terminal/web/static/icons/new-project.png +0 -0
  312. calc_terminal/web/static/icons/open-project.png +0 -0
  313. calc_terminal/web/static/index.html +734 -0
  314. calc_terminal/web/static/js/app.js +2403 -0
  315. calc_terminal/web/static/manifest.json +88 -0
  316. calc_terminal/web/static/sw.js +230 -0
  317. calc_terminal/workflow_engine.py +769 -0
  318. calc_terminal/workspace.py +593 -0
  319. calc_terminal/workspace_index.py +385 -0
  320. cct_cli-0.7.9.0.dist-info/METADATA +210 -0
  321. cct_cli-0.7.9.0.dist-info/RECORD +325 -0
  322. cct_cli-0.7.9.0.dist-info/WHEEL +5 -0
  323. cct_cli-0.7.9.0.dist-info/entry_points.txt +4 -0
  324. cct_cli-0.7.9.0.dist-info/licenses/LICENSE +21 -0
  325. cct_cli-0.7.9.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,215 @@
1
+ """
2
+ CCT - native Windows file picker for Browse in Attach to Message.
3
+
4
+ Reliability order (v0.8.1 root-cause fix for "Browse does nothing"):
5
+
6
+ 1. tkinter askopenfilenames with root.attributes("-topmost", True).
7
+ The -topmost attribute GUARANTEES the dialog appears above the
8
+ fullscreen terminal even when the terminal owns the foreground
9
+ (Windows Terminal / conhost). This is the primary method because
10
+ the old ctypes-first path could open GetOpenFileNameW BEHIND the
11
+ terminal window (hwndOwner=0 in Windows Terminal returns no usable
12
+ owner), where it silently waited for input the user could not see —
13
+ exactly the reported "button bounces, nothing happens" symptom.
14
+
15
+ 2. ctypes GetOpenFileNameW (comdlg32) as a fallback when tkinter is
16
+ unavailable. hwndOwner=0 works reliably from worker threads;
17
+ lpstrInitialDir sets the start directory (never lpstrFile, which
18
+ causes CDERR_DIALOGFAILURE).
19
+
20
+ Both methods: files only (no folder selection), multi-select enabled,
21
+ "All Files (*.*)" plus common category filters — deliberately broad.
22
+ """
23
+ if __name__ == "__main__":
24
+ print("This is a library file.")
25
+ import sys; sys.exit(1)
26
+
27
+ import os
28
+ import sys
29
+
30
+ try:
31
+ import ctypes
32
+ from ctypes import wintypes
33
+ _CTYPES_OK = True
34
+ except Exception:
35
+ _CTYPES_OK = False
36
+
37
+ _OFN_FILEMUSTEXIST = 0x00001000
38
+ _OFN_PATHMUSTEXIST = 0x00000800
39
+ _OFN_HIDEREADONLY = 0x00000004
40
+ _OFN_EXPLORER = 0x00080000
41
+ _OFN_ALLOWMULTISELECT = 0x00000200
42
+ _FLAGS = (_OFN_FILEMUSTEXIST | _OFN_PATHMUSTEXIST
43
+ | _OFN_HIDEREADONLY | _OFN_EXPLORER | _OFN_ALLOWMULTISELECT)
44
+ _BUFSIZE = 32768
45
+
46
+ FILTER = (
47
+ "All Files (*.*)\0*.*\0"
48
+ "Code Files (*.py;*.js;*.ts;*.tsx;*.jsx;*.java;*.c;*.h;*.cpp;*.rs;*.go)\0"
49
+ "*.py;*.js;*.ts;*.tsx;*.jsx;*.java;*.c;*.h;*.cpp;*.rs;*.go\0"
50
+ "Data Files (*.json;*.yaml;*.yml;*.toml;*.xml;*.csv)\0"
51
+ "*.json;*.yaml;*.yml;*.toml;*.xml;*.csv\0"
52
+ "Documents (*.txt;*.md;*.log;*.html;*.css;*.scss;*.pdf;*.doc;*.docx)\0"
53
+ "*.txt;*.md;*.log;*.html;*.css;*.scss;*.pdf;*.doc;*.docx\0"
54
+ "Images (*.png;*.jpg;*.jpeg;*.webp;*.gif;*.svg;*.bmp;*.ico)\0"
55
+ "*.png;*.jpg;*.jpeg;*.webp;*.gif;*.svg;*.bmp;*.ico\0"
56
+ "Archives (*.zip;*.tar;*.gz;*.bz2;*.rar;*.7z)\0"
57
+ "*.zip;*.tar;*.gz;*.bz2;*.rar;*.7z\0"
58
+ )
59
+
60
+ TK_FILETYPES = [
61
+ ("All Files", "*.*"),
62
+ ("Code Files", "*.py *.js *.ts *.tsx *.jsx *.java *.c *.h *.cpp *.rs *.go"),
63
+ ("Data Files", "*.json *.yaml *.yml *.toml *.xml *.csv"),
64
+ ("Documents", "*.txt *.md *.log *.html *.css *.scss *.pdf *.doc *.docx"),
65
+ ("Images", "*.png *.jpg *.jpeg *.webp *.gif *.svg *.bmp *.ico"),
66
+ ("Archives", "*.zip *.tar *.gz *.bz2 *.rar *.7z"),
67
+ ]
68
+
69
+
70
+ def is_native_supported():
71
+ return sys.platform == "win32"
72
+
73
+
74
+ def parse_multi_buffer(raw):
75
+ if isinstance(raw, bytes):
76
+ raw = raw.decode("utf-16-le", errors="ignore")
77
+ parts = [p for p in raw.split("\x00") if p]
78
+ if not parts:
79
+ return []
80
+ if len(parts) == 1:
81
+ return parts if os.path.isabs(parts[0]) else []
82
+ directory = parts[0]
83
+ out = []
84
+ for name in parts[1:]:
85
+ candidate = os.path.join(directory, name)
86
+ if os.path.isabs(candidate):
87
+ out.append(candidate)
88
+ return out
89
+
90
+
91
+ def _pick_files_tkinter(initial_dir, title):
92
+ """PRIMARY: tkinter dialog forced top-most, so it is always visible.
93
+ Returns list of paths ([] = user cancelled), None = tkinter unusable."""
94
+ try:
95
+ import tkinter as tk
96
+ from tkinter import filedialog
97
+ root = tk.Tk()
98
+ root.withdraw()
99
+ root.attributes("-topmost", True)
100
+ try:
101
+ filepaths = filedialog.askopenfilenames(
102
+ title=title,
103
+ initialdir=initial_dir,
104
+ filetypes=TK_FILETYPES,
105
+ )
106
+ if filepaths:
107
+ return list(filepaths)
108
+ return [] # user cancelled
109
+ finally:
110
+ try:
111
+ root.destroy()
112
+ except Exception:
113
+ pass
114
+ except Exception as e:
115
+ print("[NativePicker] tkinter picker failed: %s" % e, file=sys.stderr)
116
+ return None
117
+
118
+
119
+ class _OPENFILENAMEW(ctypes.Structure):
120
+ _fields_ = [
121
+ ("lStructSize", wintypes.DWORD),
122
+ ("hwndOwner", wintypes.HWND),
123
+ ("hInstance", wintypes.HINSTANCE),
124
+ ("lpstrFilter", wintypes.LPCWSTR),
125
+ ("lpstrCustomFilter", wintypes.LPWSTR),
126
+ ("nMaxCustFilter", wintypes.DWORD),
127
+ ("nFilterIndex", wintypes.DWORD),
128
+ ("lpstrFile", wintypes.LPWSTR),
129
+ ("nMaxFile", wintypes.DWORD),
130
+ ("lpstrFileTitle", wintypes.LPWSTR),
131
+ ("nMaxFileTitle", wintypes.DWORD),
132
+ ("lpstrInitialDir", wintypes.LPCWSTR),
133
+ ("lpstrTitle", wintypes.LPCWSTR),
134
+ ("Flags", wintypes.DWORD),
135
+ ("nFileOffset", wintypes.WORD),
136
+ ("nFileExtension", wintypes.WORD),
137
+ ("lpstrDefExt", wintypes.LPCWSTR),
138
+ ("lCustData", ctypes.c_void_p),
139
+ ("lpfnHook", ctypes.c_void_p),
140
+ ("lpTemplateName", wintypes.LPCWSTR),
141
+ ]
142
+ if ctypes.sizeof(ctypes.c_void_p) == 8:
143
+ _fields_ += [("pvReserved", ctypes.c_void_p),
144
+ ("dwReserved", wintypes.DWORD)]
145
+ _fields_ += [("FlagsEx", wintypes.DWORD)]
146
+
147
+
148
+ def _pick_files_ctypes(initial_dir, title):
149
+ """FALLBACK: ctypes GetOpenFileNameW. Returns list of paths or None."""
150
+ # CRITICAL: lpstrFile buffer must start EMPTY.
151
+ # Setting buf.value to a directory path causes CDERR_DIALOGFAILURE (12290).
152
+ # Initial directory is set via lpstrInitialDir instead.
153
+ buf = ctypes.create_unicode_buffer(_BUFSIZE)
154
+
155
+ filter_str = FILTER + "\x00"
156
+ ofn = _OPENFILENAMEW()
157
+ ofn.lStructSize = ctypes.sizeof(_OPENFILENAMEW)
158
+ # hwndOwner=0: works reliably from worker threads.
159
+ # GetConsoleWindow() returns 0 in Windows Terminal/Textual.
160
+ # GetForegroundWindow() causes error 12290 (CDERR_DIALOGFAILURE).
161
+ ofn.hwndOwner = 0
162
+ ofn.lpstrFilter = filter_str
163
+ ofn.nFilterIndex = 1
164
+ ofn.lpstrFile = ctypes.cast(buf, wintypes.LPWSTR)
165
+ ofn.nMaxFile = _BUFSIZE
166
+ ofn.lpstrTitle = title
167
+ ofn.Flags = _FLAGS
168
+
169
+ # Set initial directory via lpstrInitialDir, NOT via buf.value
170
+ if initial_dir:
171
+ if not os.path.isdir(initial_dir):
172
+ initial_dir = os.path.dirname(initial_dir) or None
173
+ if initial_dir:
174
+ ofn.lpstrInitialDir = initial_dir
175
+
176
+ comdlg32 = ctypes.windll.comdlg32
177
+ fn = comdlg32.GetOpenFileNameW
178
+ fn.argtypes = [ctypes.POINTER(_OPENFILENAMEW)]
179
+ fn.restype = wintypes.BOOL
180
+
181
+ result = fn(ctypes.byref(ofn))
182
+ if result:
183
+ return parse_multi_buffer(buf.raw)
184
+ err = comdlg32.CommDlgExtendedError()
185
+ if err:
186
+ print("[NativePicker] CommDlgExtendedError=%d" % err, file=sys.stderr)
187
+ return None
188
+
189
+
190
+ def pick_files(initial_dir=None, title="Select file(s) to attach"):
191
+ """Show a file picker. Tries the always-visible tkinter dialog first,
192
+ falls back to ctypes GetOpenFileNameW.
193
+ Returns list of paths (empty = cancelled), None = all methods failed."""
194
+ if not is_native_supported():
195
+ return None
196
+
197
+ # Method 1: tkinter (topmost — cannot open behind the terminal)
198
+ result = _pick_files_tkinter(initial_dir, title)
199
+ if result is not None:
200
+ return result
201
+
202
+ # Method 2: ctypes GetOpenFileNameW
203
+ if not _CTYPES_OK:
204
+ return None
205
+ print("[NativePicker] Trying ctypes fallback...", file=sys.stderr)
206
+ try:
207
+ result = _pick_files_ctypes(initial_dir, title)
208
+ if result is not None:
209
+ return result
210
+ except Exception as e:
211
+ print("[NativePicker] ctypes exception: %s" % e, file=sys.stderr)
212
+ import traceback
213
+ traceback.print_exc(file=sys.stderr)
214
+
215
+ return None
@@ -0,0 +1,279 @@
1
+ """Ollama model catalog — Authentic, curated models with dynamic local discovery.
2
+ Creator: Kazi Zillani (CAT Platform).
3
+
4
+ Provides authentic model metadata (params, size, context, capabilities, family)
5
+ without artificial dummy variants. Supports dynamic registration of locally installed
6
+ models and custom user models.
7
+ """
8
+
9
+ from typing import List, Dict, Any, Optional
10
+ import os
11
+
12
+ # Base curated catalog of authentic, modern local models
13
+ CURATED_CATALOG: List[Dict[str, Any]] = []
14
+
15
+ def _add(name: str, params: str, size_gb: float, family: str, desc: str,
16
+ ctx: int = 8192, caps: Optional[List[str]] = None, categories: Optional[List[str]] = None):
17
+ CURATED_CATALOG.append({
18
+ "name": name,
19
+ "params": params,
20
+ "size_gb": float(size_gb),
21
+ "family": family,
22
+ "desc": desc,
23
+ "context": ctx,
24
+ "caps": caps or ["chat"],
25
+ "categories": categories or ["popular"],
26
+ })
27
+
28
+ # =====================================================================
29
+ # 1. CORE LLAMA (Meta)
30
+ # =====================================================================
31
+ _add("llama3.3:70b", "70B", 42.0, "llama", "Meta Llama 3.3 70B — State of the art open weights model", 131072, ["chat", "code", "reasoning"], ["popular", "large"])
32
+ _add("llama3.3:8b", "8B", 4.7, "llama", "Meta Llama 3.3 8B — Fast, capable general assistant", 131072, ["chat", "code"], ["popular", "coding"])
33
+ _add("llama3.2:3b", "3B", 2.0, "llama", "Meta Llama 3.2 3B — Lightweight on-device assistant", 131072, ["chat", "code"], ["popular", "small"])
34
+ _add("llama3.2:1b", "1B", 1.3, "llama", "Meta Llama 3.2 1B — Ultra-lightweight edge model", 131072, ["chat"], ["small"])
35
+ _add("llama3.1:8b", "8B", 4.7, "llama", "Meta Llama 3.1 8B — High context general purpose model", 131072, ["chat", "code"], ["popular", "coding"])
36
+ _add("llama3.1:70b", "70B", 42.0, "llama", "Meta Llama 3.1 70B — Heavyweight reasoning and coding", 131072, ["chat", "code", "reasoning"], ["large"])
37
+ _add("llama3.1:405b", "405B", 243.0, "llama", "Meta Llama 3.1 405B — Frontier flagship open weights", 131072, ["chat", "code", "reasoning"], ["large"])
38
+ _add("llama3:8b", "8B", 4.7, "llama", "Meta Llama 3 8B — Original Llama 3 release", 8192, ["chat", "code"], ["popular"])
39
+ _add("llama2:7b", "7B", 3.8, "llama", "Meta Llama 2 7B — Classic foundation model", 4096, ["chat"], ["small"])
40
+ _add("llama2:13b", "13B", 7.4, "llama", "Meta Llama 2 13B — Classic balanced model", 4096, ["chat"], ["popular"])
41
+
42
+ # =====================================================================
43
+ # 2. QWEN & QWEN CODER (Alibaba)
44
+ # =====================================================================
45
+ _add("qwen2.5-coder:7b", "7B", 4.7, "qwen", "Qwen 2.5 Coder 7B — Premier open-source code generation model", 32768, ["code", "chat"], ["popular", "coding"])
46
+ _add("qwen2.5-coder:14b", "14B", 9.0, "qwen", "Qwen 2.5 Coder 14B — Deep architectural coding & refactoring", 32768, ["code", "chat"], ["coding", "popular"])
47
+ _add("qwen2.5-coder:32b", "32B", 19.0, "qwen", "Qwen 2.5 Coder 32B — Enterprise-grade code intelligence", 32768, ["code", "chat", "reasoning"], ["coding", "large"])
48
+ _add("qwen2.5-coder:3b", "3B", 1.9, "qwen", "Qwen 2.5 Coder 3B — Fast on-device copilot & terminal assistant", 32768, ["code", "chat"], ["coding", "small"])
49
+ _add("qwen2.5-coder:1.5b", "1.5B", 1.0, "qwen", "Qwen 2.5 Coder 1.5B — Ultra-low latency autocomplete", 32768, ["code"], ["coding", "small"])
50
+ _add("qwen2.5:7b", "7B", 4.4, "qwen", "Qwen 2.5 7B — Multilingual general intelligence & math", 32768, ["chat", "code"], ["popular"])
51
+ _add("qwen2.5:14b", "14B", 9.0, "qwen", "Qwen 2.5 14B — Strong balanced reasoning and knowledge", 32768, ["chat", "code", "reasoning"], ["popular"])
52
+ _add("qwen2.5:32b", "32B", 19.0, "qwen", "Qwen 2.5 32B — High-fidelity analytical reasoning", 32768, ["chat", "code", "reasoning"], ["large"])
53
+ _add("qwen2.5:72b", "72B", 41.0, "qwen", "Qwen 2.5 72B — Flagship open weights generalist", 32768, ["chat", "code", "reasoning"], ["large"])
54
+ _add("qwen2.5:3b", "3B", 1.9, "qwen", "Qwen 2.5 3B — Agile compact multilingual model", 32768, ["chat"], ["small"])
55
+ _add("qwen2.5:1.5b", "1.5B", 0.9, "qwen", "Qwen 2.5 1.5B — Lightweight embedded model", 32768, ["chat"], ["small"])
56
+ _add("qwen2.5:0.5b", "0.5B", 0.4, "qwen", "Qwen 2.5 0.5B — Micro model for IoT and quick parsing", 32768, ["chat"], ["small"])
57
+ _add("qwq:32b", "32B", 20.0, "qwen", "QwQ 32B — Thinking & complex mathematical reasoning model", 32768, ["reasoning", "math", "code"], ["reasoning", "popular"])
58
+
59
+ # =====================================================================
60
+ # 3. DEEPSEEK (DeepSeek AI)
61
+ # =====================================================================
62
+ _add("deepseek-r1:1.5b", "1.5B", 1.1, "deepseek", "DeepSeek R1 Distill 1.5B — Micro step-by-step reasoning", 32768, ["reasoning"], ["reasoning", "small"])
63
+ _add("deepseek-r1:7b", "7B", 4.7, "deepseek", "DeepSeek R1 Distill 7B (Qwen) — Fast chain-of-thought", 32768, ["reasoning", "code"], ["reasoning", "popular"])
64
+ _add("deepseek-r1:8b", "8B", 4.9, "deepseek", "DeepSeek R1 Distill 8B (Llama) — Strong CoT reasoning", 32768, ["reasoning", "code"], ["reasoning", "popular"])
65
+ _add("deepseek-r1:14b", "14B", 9.0, "deepseek", "DeepSeek R1 Distill 14B — In-depth logical deduction & math", 32768, ["reasoning", "code", "math"], ["reasoning"])
66
+ _add("deepseek-r1:32b", "32B", 19.0, "deepseek", "DeepSeek R1 Distill 32B — High-end reasoning & complex problems", 32768, ["reasoning", "code", "math"], ["reasoning", "large"])
67
+ _add("deepseek-r1:70b", "70B", 42.0, "deepseek", "DeepSeek R1 Distill 70B — Top-tier open reasoning model", 32768, ["reasoning", "code", "math"], ["reasoning", "large"])
68
+ _add("deepseek-coder:6.7b", "6.7B", 3.8, "deepseek", "DeepSeek Coder 6.7B — Efficient coding specialist", 16384, ["code"], ["coding"])
69
+ _add("deepseek-coder-v2:16b", "16B", 8.9, "deepseek", "DeepSeek Coder V2 16B (MoE) — Multi-language coding engine", 32768, ["code", "chat"], ["coding"])
70
+ _add("deepseek-v3:671b", "671B", 400.0, "deepseek", "DeepSeek V3 671B (MoE) — Frontier flagship architecture", 65536, ["chat", "code", "reasoning"], ["large"])
71
+
72
+ # =====================================================================
73
+ # 4. GOOGLE GEMMA
74
+ # =====================================================================
75
+ _add("gemma3:1b", "1B", 0.8, "gemma", "Google Gemma 3 1B — Next-gen multimodal compact model", 32768, ["chat", "vision"], ["small", "vision"])
76
+ _add("gemma3:4b", "4B", 3.3, "gemma", "Google Gemma 3 4B — State-of-the-art multimodal lightweight model", 32768, ["chat", "vision", "code"], ["popular", "vision", "small"])
77
+ _add("gemma3:12b", "12B", 8.1, "gemma", "Google Gemma 3 12B — Advanced multimodal reasoning", 32768, ["chat", "vision", "code"], ["popular", "vision"])
78
+ _add("gemma3:27b", "27B", 17.0, "gemma", "Google Gemma 3 27B — High-capacity multimodal reasoning", 32768, ["chat", "vision", "reasoning"], ["large", "vision"])
79
+ _add("gemma2:2b", "2B", 1.6, "gemma", "Google Gemma 2 2B — High-efficiency on-device text generator", 8192, ["chat"], ["small"])
80
+ _add("gemma2:9b", "9B", 5.4, "gemma", "Google Gemma 2 9B — Outstanding performance-per-parameter", 8192, ["chat", "code"], ["popular"])
81
+ _add("gemma2:27b", "27B", 16.0, "gemma", "Google Gemma 2 27B — Powerful analytical text model", 8192, ["chat", "reasoning"], ["large"])
82
+ _add("codegemma:7b", "7B", 5.0, "gemma", "Google CodeGemma 7B — Code completion and synthesis", 8192, ["code"], ["coding"])
83
+
84
+ # =====================================================================
85
+ # 5. MISTRAL AI
86
+ # =====================================================================
87
+ _add("mistral:7b", "7B", 4.1, "mistral", "Mistral 7B — The classic high-performance 7B standard", 32768, ["chat", "code"], ["popular"])
88
+ _add("mistral-nemo:12b", "12B", 7.1, "mistral", "Mistral NeMo 12B — 128k context multilingual model with NVIDIA", 128000, ["chat", "code"], ["popular"])
89
+ _add("codestral:22b", "22B", 12.0, "mistral", "Mistral Codestral 22B — Fast 80+ language coding model", 32768, ["code"], ["coding", "popular"])
90
+ _add("mixtral:8x7b", "47B", 26.0, "mistral", "Mixtral 8x7B (MoE) — Fast sparse Mixture-of-Experts", 32768, ["chat", "code"], ["popular", "large"])
91
+ _add("mixtral:8x22b", "141B", 80.0, "mistral", "Mixtral 8x22B (MoE) — High-end reasoning Mixture-of-Experts", 65536, ["chat", "code", "reasoning"], ["large"])
92
+ _add("mathstral:7b", "7B", 4.1, "mistral", "Mistral Mathstral 7B — Mathematical reasoning and science", 32768, ["math", "reasoning"], ["reasoning"])
93
+
94
+ # =====================================================================
95
+ # 6. MICROSOFT PHI
96
+ # =====================================================================
97
+ _add("phi4:14b", "14B", 9.1, "phi", "Microsoft Phi-4 14B — Premier synthetic reasoning & STEM benchmark leader", 16384, ["reasoning", "math", "code"], ["popular", "reasoning"])
98
+ _add("phi3.5:3.8b", "3.8B", 2.2, "phi", "Microsoft Phi-3.5 Mini 3.8B — 128k context high-efficiency model", 128000, ["chat", "code"], ["small", "popular"])
99
+ _add("phi3:14b", "14B", 7.9, "phi", "Microsoft Phi-3 Medium 14B — Analytical reasoning", 16384, ["chat", "reasoning"], ["popular"])
100
+ _add("phi3:3.8b", "3.8B", 2.2, "phi", "Microsoft Phi-3 Mini 3.8B — Compact logic model", 8192, ["chat"], ["small"])
101
+
102
+ # =====================================================================
103
+ # 7. VISION & MULTIMODAL
104
+ # =====================================================================
105
+ _add("llava:7b", "7B", 4.7, "vision", "LLaVA 1.5 7B — Visual question answering and image inspection", 4096, ["vision", "chat"], ["vision", "popular"])
106
+ _add("llava:13b", "13B", 8.0, "vision", "LLaVA 1.5 13B — High-detail image understanding", 4096, ["vision", "chat"], ["vision"])
107
+ _add("llava-llama3:8b", "8B", 5.0, "vision", "LLaVA Llama-3 8B — Multimodal assistant on Llama 3", 8192, ["vision", "chat"], ["vision", "popular"])
108
+ _add("minicpm-v:8b", "8B", 5.5, "vision", "MiniCPM-V 2.6 8B — Outstanding OCR & high-res image analysis", 8192, ["vision", "chat"], ["vision"])
109
+ _add("moondream:1.8b", "1.8B", 1.0, "vision", "Moondream 2 1.8B — Fast edge-ready visual model", 4096, ["vision"], ["vision", "small"])
110
+ _add("bakllava:7b", "7B", 4.7, "vision", "BakLLaVA 7B — Mistral-backed visual assistant", 4096, ["vision"], ["vision"])
111
+
112
+ # =====================================================================
113
+ # 8. SMALL, FAST & ON-DEVICE
114
+ # =====================================================================
115
+ _add("smollm2:1.7b", "1.7B", 1.0, "smollm", "SmolLM2 1.7B — HuggingFace curated small model for edge devices", 8192, ["chat", "code"], ["small"])
116
+ _add("smollm2:360m", "360M", 0.3, "smollm", "SmolLM2 360M — Ultra-compact local language model", 8192, ["chat"], ["small"])
117
+ _add("smollm2:135m", "135M", 0.15, "smollm", "SmolLM2 135M — Micro test model with near-zero memory footprint", 2048, ["chat"], ["small"])
118
+ _add("tinyllama:1.1b", "1.1B", 0.6, "other", "TinyLlama 1.1B — 3T token trained compact architecture", 2048, ["chat"], ["small"])
119
+ _add("orca-mini:3b", "3B", 1.9, "other", "Orca Mini 3B — Microsoft Orca dataset instruction tuned", 4096, ["chat"], ["small"])
120
+ _add("granite3.1:2b", "2B", 1.3, "granite", "IBM Granite 3.1 2B — Enterprise small language model", 8192, ["chat", "code"], ["small"])
121
+ _add("granite3.1:8b", "8B", 4.9, "granite", "IBM Granite 3.1 8B — Enterprise general and tool-use model", 8192, ["chat", "code"], ["popular"])
122
+
123
+ # =====================================================================
124
+ # 9. SPECIALIZED CODING & ENTERPRISE
125
+ # =====================================================================
126
+ _add("codellama:7b", "7B", 3.8, "codellama", "CodeLlama 7B — Meta coding specialist", 16384, ["code"], ["coding"])
127
+ _add("codellama:13b", "13B", 7.4, "codellama", "CodeLlama 13B — Medium-footprint coding model", 16384, ["code"], ["coding"])
128
+ _add("codellama:34b", "34B", 19.0, "codellama", "CodeLlama 34B — High accuracy code completion", 16384, ["code"], ["coding", "large"])
129
+ _add("starcoder2:7b", "7B", 4.0, "starcoder", "StarCoder2 7B — BigCode consortium model for 600+ languages", 16384, ["code"], ["coding"])
130
+ _add("starcoder2:15b", "15B", 9.0, "starcoder", "StarCoder2 15B — High precision code assistant", 16384, ["code"], ["coding"])
131
+ _add("sqlcoder:15b", "15B", 8.5, "other", "Defog SQLCoder 15B — Natural language to SQL query engine", 4096, ["code", "sql"], ["coding"])
132
+ _add("command-r:35b", "35B", 19.0, "cohere", "Cohere Command R 35B — Optimized for RAG and tool execution", 128000, ["chat", "rag"], ["popular", "large"])
133
+ _add("command-r-plus:104b", "104B", 60.0, "cohere", "Cohere Command R+ 104B — Frontier enterprise RAG model", 128000, ["chat", "rag", "reasoning"], ["large"])
134
+
135
+ # =====================================================================
136
+ # 10. EMBEDDINGS & RETRIEVAL (RAG)
137
+ # =====================================================================
138
+ _add("nomic-embed-text:latest", "137M", 0.3, "nomic", "Nomic Embed Text — 8192 context high-quality text embedding", 8192, ["embedding"], ["embedding", "small"])
139
+ _add("bge-m3:latest", "567M", 1.2, "bge", "BAAI BGE-M3 — Multilingual, multi-granularity dense/sparse embedding", 8192, ["embedding"], ["embedding", "small"])
140
+ _add("all-minilm:latest", "33M", 0.1, "minilm", "All-MiniLM L6 v2 — Ultra-fast embedding model for semantic search", 512, ["embedding"], ["embedding", "small"])
141
+ _add("mxbai-embed-large:latest", "335M", 0.7, "mxbai", "MixedBread Embed Large — Top MTEB benchmark embedding model", 512, ["embedding"], ["embedding", "small"])
142
+
143
+
144
+ # Dynamic local model registry (persisted or populated from Ollama API)
145
+ DYNAMIC_REGISTRY: Dict[str, Dict[str, Any]] = {}
146
+
147
+
148
+ def register_custom_model(model_dict: Dict[str, Any]):
149
+ """Register a custom or dynamically discovered model."""
150
+ name = model_dict.get("name")
151
+ if not name:
152
+ return
153
+ DYNAMIC_REGISTRY[name] = {
154
+ "name": name,
155
+ "params": model_dict.get("params", "Unknown"),
156
+ "size_gb": float(model_dict.get("size_gb", 4.0)),
157
+ "family": model_dict.get("family", "custom"),
158
+ "desc": model_dict.get("desc", f"Local model {name}"),
159
+ "context": int(model_dict.get("context", 8192)),
160
+ "caps": model_dict.get("caps", ["chat"]),
161
+ "categories": model_dict.get("categories", ["custom"]),
162
+ "installed": bool(model_dict.get("installed", False)),
163
+ }
164
+
165
+
166
+ def register_installed_models(installed_names: List[str]):
167
+ """Ensure any locally installed model in Ollama is present in the catalog."""
168
+ for raw_name in installed_names:
169
+ name = raw_name.strip()
170
+ if not name:
171
+ continue
172
+ # If already in curated catalog, mark as installed in memory
173
+ existing = get_model(name)
174
+ if not existing:
175
+ # Estimate size / params from tag if possible
176
+ params = "Unknown"
177
+ size_gb = 4.0
178
+ if ":" in name:
179
+ tag = name.split(":")[-1].lower()
180
+ if tag.endswith("b") and tag[:-1].replace(".", "").isdigit():
181
+ params = tag.upper()
182
+ try:
183
+ num = float(tag[:-1])
184
+ size_gb = round(num * 0.65, 1)
185
+ except Exception:
186
+ size_gb = 4.0
187
+
188
+ register_custom_model({
189
+ "name": name,
190
+ "params": params,
191
+ "size_gb": size_gb,
192
+ "family": name.split(":")[0],
193
+ "desc": f"Installed Ollama model: {name}",
194
+ "context": 8192,
195
+ "caps": ["chat"],
196
+ "categories": ["installed"],
197
+ "installed": True,
198
+ })
199
+
200
+
201
+ def all_models(include_dynamic: bool = True) -> List[Dict[str, Any]]:
202
+ """Return all catalog models, combining curated entries and dynamic/installed models."""
203
+ combined: Dict[str, Dict[str, Any]] = {}
204
+ for m in CURATED_CATALOG:
205
+ combined[m["name"]] = dict(m)
206
+
207
+ if include_dynamic:
208
+ for name, m in DYNAMIC_REGISTRY.items():
209
+ if name in combined:
210
+ combined[name].update(m)
211
+ else:
212
+ combined[name] = dict(m)
213
+
214
+ return list(combined.values())
215
+
216
+
217
+ # Compatibility alias for legacy callers
218
+ CATALOG = all_models()
219
+
220
+
221
+ def categories() -> List[str]:
222
+ """Return list of standard navigation categories."""
223
+ return ["all", "popular", "coding", "reasoning", "vision", "small", "large", "embedding"]
224
+
225
+
226
+ def search_models(
227
+ query: str = "",
228
+ category: Optional[str] = None,
229
+ family: Optional[str] = None,
230
+ cap: Optional[str] = None,
231
+ ) -> List[Dict[str, Any]]:
232
+ """Search models with optional category, family, and capability filters."""
233
+ q = (query or "").lower().strip()
234
+ cat = (category or "").lower().strip()
235
+ out = []
236
+
237
+ for m in all_models(include_dynamic=True):
238
+ if family and m.get("family") != family:
239
+ continue
240
+ if cap and cap not in m.get("caps", []):
241
+ continue
242
+ if cat and cat != "all":
243
+ cats = [c.lower() for c in m.get("categories", [])]
244
+ # Match either direct category list or family/caps
245
+ if cat not in cats and cat not in m.get("caps", []) and cat != m.get("family"):
246
+ continue
247
+ if q:
248
+ name_match = q in m.get("name", "").lower()
249
+ fam_match = q in m.get("family", "").lower()
250
+ desc_match = q in m.get("desc", "").lower()
251
+ params_match = q in m.get("params", "").lower()
252
+ if not (name_match or fam_match or desc_match or params_match):
253
+ continue
254
+ out.append(m)
255
+
256
+ return out
257
+
258
+
259
+ def get_model(name: str) -> Optional[Dict[str, Any]]:
260
+ """Look up a model by exact name or base prefix."""
261
+ if not name:
262
+ return None
263
+ name_clean = name.strip()
264
+
265
+ # Check dynamic first
266
+ if name_clean in DYNAMIC_REGISTRY:
267
+ return dict(DYNAMIC_REGISTRY[name_clean])
268
+
269
+ # Check curated exact match
270
+ for m in CURATED_CATALOG:
271
+ if m["name"] == name_clean:
272
+ return dict(m)
273
+
274
+ # Check if tag omitted (e.g. "qwen2.5-coder" matches "qwen2.5-coder:7b" or latest)
275
+ for m in CURATED_CATALOG:
276
+ if m["name"].startswith(name_clean + ":") or m["name"] == f"{name_clean}:latest":
277
+ return dict(m)
278
+
279
+ return None