sd-model-hub 0.1.0__tar.gz

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 (131) hide show
  1. sd_model_hub-0.1.0/PKG-INFO +295 -0
  2. sd_model_hub-0.1.0/README.md +256 -0
  3. sd_model_hub-0.1.0/pyproject.toml +85 -0
  4. sd_model_hub-0.1.0/sd_model_hub/__init__.py +33 -0
  5. sd_model_hub-0.1.0/sd_model_hub/__main__.py +6 -0
  6. sd_model_hub-0.1.0/sd_model_hub/api/__init__.py +1 -0
  7. sd_model_hub-0.1.0/sd_model_hub/api/app.py +86 -0
  8. sd_model_hub-0.1.0/sd_model_hub/api/deps.py +14 -0
  9. sd_model_hub-0.1.0/sd_model_hub/api/errors.py +41 -0
  10. sd_model_hub-0.1.0/sd_model_hub/api/openapi.py +29 -0
  11. sd_model_hub-0.1.0/sd_model_hub/api/routers/__init__.py +1 -0
  12. sd_model_hub-0.1.0/sd_model_hub/api/routers/app_info.py +56 -0
  13. sd_model_hub-0.1.0/sd_model_hub/api/routers/auth.py +117 -0
  14. sd_model_hub-0.1.0/sd_model_hub/api/routers/downloads.py +54 -0
  15. sd_model_hub-0.1.0/sd_model_hub/api/routers/hubs.py +37 -0
  16. sd_model_hub-0.1.0/sd_model_hub/api/routers/library.py +143 -0
  17. sd_model_hub-0.1.0/sd_model_hub/api/routers/settings.py +45 -0
  18. sd_model_hub-0.1.0/sd_model_hub/api/routers/sources.py +49 -0
  19. sd_model_hub-0.1.0/sd_model_hub/api/security.py +124 -0
  20. sd_model_hub-0.1.0/sd_model_hub/api/sockets.py +75 -0
  21. sd_model_hub-0.1.0/sd_model_hub/api/static.py +49 -0
  22. sd_model_hub-0.1.0/sd_model_hub/cli/__init__.py +1 -0
  23. sd_model_hub-0.1.0/sd_model_hub/cli/app.py +110 -0
  24. sd_model_hub-0.1.0/sd_model_hub/cli/commands/__init__.py +1 -0
  25. sd_model_hub-0.1.0/sd_model_hub/cli/commands/config.py +47 -0
  26. sd_model_hub-0.1.0/sd_model_hub/cli/commands/download.py +164 -0
  27. sd_model_hub-0.1.0/sd_model_hub/cli/commands/library.py +239 -0
  28. sd_model_hub-0.1.0/sd_model_hub/cli/commands/search.py +142 -0
  29. sd_model_hub-0.1.0/sd_model_hub/cli/commands/system.py +48 -0
  30. sd_model_hub-0.1.0/sd_model_hub/cli/commands/webui.py +75 -0
  31. sd_model_hub-0.1.0/sd_model_hub/cli/factory.py +91 -0
  32. sd_model_hub-0.1.0/sd_model_hub/cli/output.py +67 -0
  33. sd_model_hub-0.1.0/sd_model_hub/core/__init__.py +1 -0
  34. sd_model_hub-0.1.0/sd_model_hub/core/auth/__init__.py +7 -0
  35. sd_model_hub-0.1.0/sd_model_hub/core/auth/models.py +54 -0
  36. sd_model_hub-0.1.0/sd_model_hub/core/auth/oauth_client.py +156 -0
  37. sd_model_hub-0.1.0/sd_model_hub/core/auth/service.py +282 -0
  38. sd_model_hub-0.1.0/sd_model_hub/core/auth/store.py +220 -0
  39. sd_model_hub-0.1.0/sd_model_hub/core/auth/transactions.py +89 -0
  40. sd_model_hub-0.1.0/sd_model_hub/core/context.py +67 -0
  41. sd_model_hub-0.1.0/sd_model_hub/core/db/__init__.py +5 -0
  42. sd_model_hub-0.1.0/sd_model_hub/core/db/database.py +114 -0
  43. sd_model_hub-0.1.0/sd_model_hub/core/detection/__init__.py +6 -0
  44. sd_model_hub-0.1.0/sd_model_hub/core/detection/header.py +150 -0
  45. sd_model_hub-0.1.0/sd_model_hub/core/detection/kinds.py +155 -0
  46. sd_model_hub-0.1.0/sd_model_hub/core/detection/models.py +142 -0
  47. sd_model_hub-0.1.0/sd_model_hub/core/detection/rules.py +121 -0
  48. sd_model_hub-0.1.0/sd_model_hub/core/detection/rules_data/diffusion.json +162 -0
  49. sd_model_hub-0.1.0/sd_model_hub/core/detection/rules_data/lora.json +82 -0
  50. sd_model_hub-0.1.0/sd_model_hub/core/detection/rules_data/other.json +116 -0
  51. sd_model_hub-0.1.0/sd_model_hub/core/detection/service.py +241 -0
  52. sd_model_hub-0.1.0/sd_model_hub/core/downloads/__init__.py +1 -0
  53. sd_model_hub-0.1.0/sd_model_hub/core/downloads/http_downloader.py +184 -0
  54. sd_model_hub-0.1.0/sd_model_hub/core/downloads/hub_runner.py +166 -0
  55. sd_model_hub-0.1.0/sd_model_hub/core/downloads/job.py +44 -0
  56. sd_model_hub-0.1.0/sd_model_hub/core/downloads/manager.py +613 -0
  57. sd_model_hub-0.1.0/sd_model_hub/core/downloads/models.py +84 -0
  58. sd_model_hub-0.1.0/sd_model_hub/core/errors.py +75 -0
  59. sd_model_hub-0.1.0/sd_model_hub/core/events/__init__.py +6 -0
  60. sd_model_hub-0.1.0/sd_model_hub/core/events/bus.py +53 -0
  61. sd_model_hub-0.1.0/sd_model_hub/core/events/models.py +78 -0
  62. sd_model_hub-0.1.0/sd_model_hub/core/hubs/__init__.py +6 -0
  63. sd_model_hub-0.1.0/sd_model_hub/core/hubs/base.py +82 -0
  64. sd_model_hub-0.1.0/sd_model_hub/core/hubs/huggingface.py +89 -0
  65. sd_model_hub-0.1.0/sd_model_hub/core/hubs/models.py +61 -0
  66. sd_model_hub-0.1.0/sd_model_hub/core/hubs/modelscope.py +86 -0
  67. sd_model_hub-0.1.0/sd_model_hub/core/hubs/registry.py +80 -0
  68. sd_model_hub-0.1.0/sd_model_hub/core/hubs/worker.py +94 -0
  69. sd_model_hub-0.1.0/sd_model_hub/core/library/__init__.py +5 -0
  70. sd_model_hub-0.1.0/sd_model_hub/core/library/fsops.py +78 -0
  71. sd_model_hub-0.1.0/sd_model_hub/core/library/layouts.py +129 -0
  72. sd_model_hub-0.1.0/sd_model_hub/core/library/models.py +132 -0
  73. sd_model_hub-0.1.0/sd_model_hub/core/library/previews.py +116 -0
  74. sd_model_hub-0.1.0/sd_model_hub/core/library/safety.py +101 -0
  75. sd_model_hub-0.1.0/sd_model_hub/core/library/service.py +611 -0
  76. sd_model_hub-0.1.0/sd_model_hub/core/library/sidecar.py +111 -0
  77. sd_model_hub-0.1.0/sd_model_hub/core/library/thumbnails.py +36 -0
  78. sd_model_hub-0.1.0/sd_model_hub/core/net/__init__.py +1 -0
  79. sd_model_hub-0.1.0/sd_model_hub/core/net/http.py +41 -0
  80. sd_model_hub-0.1.0/sd_model_hub/core/net/ports.py +66 -0
  81. sd_model_hub-0.1.0/sd_model_hub/core/net/runtime_file.py +51 -0
  82. sd_model_hub-0.1.0/sd_model_hub/core/paths.py +22 -0
  83. sd_model_hub-0.1.0/sd_model_hub/core/record.py +13 -0
  84. sd_model_hub-0.1.0/sd_model_hub/core/settings/__init__.py +6 -0
  85. sd_model_hub-0.1.0/sd_model_hub/core/settings/models.py +150 -0
  86. sd_model_hub-0.1.0/sd_model_hub/core/settings/service.py +233 -0
  87. sd_model_hub-0.1.0/sd_model_hub/core/sources/__init__.py +6 -0
  88. sd_model_hub-0.1.0/sd_model_hub/core/sources/base.py +146 -0
  89. sd_model_hub-0.1.0/sd_model_hub/core/sources/civitai.py +221 -0
  90. sd_model_hub-0.1.0/sd_model_hub/core/sources/github_releases.py +114 -0
  91. sd_model_hub-0.1.0/sd_model_hub/core/sources/models.py +126 -0
  92. sd_model_hub-0.1.0/sd_model_hub/core/sources/openmodeldb.py +158 -0
  93. sd_model_hub-0.1.0/sd_model_hub/core/sources/registry.py +150 -0
  94. sd_model_hub-0.1.0/sd_model_hub/embed.py +292 -0
  95. sd_model_hub-0.1.0/sd_model_hub/logger.py +24 -0
  96. sd_model_hub-0.1.0/sd_model_hub/version.py +3 -0
  97. sd_model_hub-0.1.0/sd_model_hub/webui/__init__.py +1 -0
  98. sd_model_hub-0.1.0/sd_model_hub/webui/dist/assets/BrowseView-BsgxJoVw.js +4 -0
  99. sd_model_hub-0.1.0/sd_model_hub/webui/dist/assets/BrowseView-Cc2rOaSu.css +1 -0
  100. sd_model_hub-0.1.0/sd_model_hub/webui/dist/assets/DestinationPicker-Bhq-e6u1.css +1 -0
  101. sd_model_hub-0.1.0/sd_model_hub/webui/dist/assets/DestinationPicker-CcjOH3hU.js +1 -0
  102. sd_model_hub-0.1.0/sd_model_hub/webui/dist/assets/HubsView-QQtySBy8.css +1 -0
  103. sd_model_hub-0.1.0/sd_model_hub/webui/dist/assets/HubsView-nJ91utuU.js +1 -0
  104. sd_model_hub-0.1.0/sd_model_hub/webui/dist/assets/LibraryView-B5TiV-Ff.css +1 -0
  105. sd_model_hub-0.1.0/sd_model_hub/webui/dist/assets/LibraryView-BqayayWs.js +1 -0
  106. sd_model_hub-0.1.0/sd_model_hub/webui/dist/assets/ModelCard-BkbIdLbR.js +1 -0
  107. sd_model_hub-0.1.0/sd_model_hub/webui/dist/assets/ModelCard-DxBbG1CQ.css +1 -0
  108. sd_model_hub-0.1.0/sd_model_hub/webui/dist/assets/ModelGrid-CxsHdr92.css +1 -0
  109. sd_model_hub-0.1.0/sd_model_hub/webui/dist/assets/ModelGrid-DjjnYTWr.js +1 -0
  110. sd_model_hub-0.1.0/sd_model_hub/webui/dist/assets/SettingsView-BesWiAeg.js +1 -0
  111. sd_model_hub-0.1.0/sd_model_hub/webui/dist/assets/SettingsView-uChTre8h.css +1 -0
  112. sd_model_hub-0.1.0/sd_model_hub/webui/dist/assets/hubs-D20xHqWZ.js +1 -0
  113. sd_model_hub-0.1.0/sd_model_hub/webui/dist/assets/index-CUVnymEA.css +1 -0
  114. sd_model_hub-0.1.0/sd_model_hub/webui/dist/assets/index-IusZG4tg.js +650 -0
  115. sd_model_hub-0.1.0/sd_model_hub/webui/dist/assets/library-euRn7QD7.js +1 -0
  116. sd_model_hub-0.1.0/sd_model_hub/webui/dist/assets/roboto-flex-cyrillic-ext-wght-normal-CVcCQn0y.woff2 +0 -0
  117. sd_model_hub-0.1.0/sd_model_hub/webui/dist/assets/roboto-flex-cyrillic-wght-normal-BJ-nQEdz.woff2 +0 -0
  118. sd_model_hub-0.1.0/sd_model_hub/webui/dist/assets/roboto-flex-greek-wght-normal-DeN4rzfy.woff2 +0 -0
  119. sd_model_hub-0.1.0/sd_model_hub/webui/dist/assets/roboto-flex-latin-ext-wght-normal-gysdOMcS.woff2 +0 -0
  120. sd_model_hub-0.1.0/sd_model_hub/webui/dist/assets/roboto-flex-latin-wght-normal-n-dP9qr2.woff2 +0 -0
  121. sd_model_hub-0.1.0/sd_model_hub/webui/dist/assets/roboto-flex-vietnamese-wght-normal-AVFfiTln.woff2 +0 -0
  122. sd_model_hub-0.1.0/sd_model_hub/webui/dist/assets/sources-N2C54ABB.js +1 -0
  123. sd_model_hub-0.1.0/sd_model_hub/webui/dist/favicon.svg +1 -0
  124. sd_model_hub-0.1.0/sd_model_hub/webui/dist/index.html +28 -0
  125. sd_model_hub-0.1.0/sd_model_hub.egg-info/PKG-INFO +295 -0
  126. sd_model_hub-0.1.0/sd_model_hub.egg-info/SOURCES.txt +129 -0
  127. sd_model_hub-0.1.0/sd_model_hub.egg-info/dependency_links.txt +1 -0
  128. sd_model_hub-0.1.0/sd_model_hub.egg-info/entry_points.txt +2 -0
  129. sd_model_hub-0.1.0/sd_model_hub.egg-info/requires.txt +23 -0
  130. sd_model_hub-0.1.0/sd_model_hub.egg-info/top_level.txt +1 -0
  131. sd_model_hub-0.1.0/setup.cfg +4 -0
@@ -0,0 +1,295 @@
1
+ Metadata-Version: 2.4
2
+ Name: sd-model-hub
3
+ Version: 0.1.0
4
+ Summary: Download and manage Stable Diffusion models from the command line and a web UI
5
+ Classifier: Development Status :: 3 - Alpha
6
+ Classifier: Environment :: Console
7
+ Classifier: Environment :: Web Environment
8
+ Classifier: Intended Audience :: End Users/Desktop
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Classifier: Topic :: Multimedia :: Graphics
16
+ Classifier: Topic :: Utilities
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ Requires-Dist: fastapi
20
+ Requires-Dist: uvicorn
21
+ Requires-Dist: websockets>=12
22
+ Requires-Dist: python-socketio
23
+ Requires-Dist: pydantic>=2
24
+ Requires-Dist: httpx
25
+ Requires-Dist: huggingface_hub
26
+ Requires-Dist: modelscope
27
+ Requires-Dist: typer>=0.26.7
28
+ Requires-Dist: rich
29
+ Requires-Dist: Pillow>=10.0
30
+ Requires-Dist: send2trash
31
+ Requires-Dist: tomli-w
32
+ Requires-Dist: tomli; python_version < "3.11"
33
+ Provides-Extra: dev
34
+ Requires-Dist: keyring; extra == "dev"
35
+ Requires-Dist: pytest; extra == "dev"
36
+ Requires-Dist: ruff; extra == "dev"
37
+ Requires-Dist: tomli; extra == "dev"
38
+ Requires-Dist: ty; extra == "dev"
39
+
40
+ # SD Model Hub
41
+
42
+ Download and manage Stable Diffusion models, from the command line or a web UI.
43
+
44
+ - **Browse** Civitai, OpenModelDB and GitHub Releases, and download with resume, SHA256
45
+ checks, preview images and metadata sidecars.
46
+ - **Hubs:** download files or whole repositories from Hugging Face (or a mirror such as
47
+ hf-mirror) and ModelScope.
48
+ - **Library:** point SD Model Hub at your ComfyUI or Stable Diffusion WebUI model folders. It
49
+ identifies each model's type and base model from the safetensors header (never unpickling
50
+ anything), shows previews, and imports, moves, renames and deletes models together with
51
+ their preview images and sidecar files. Drag files into the browser to upload them.
52
+
53
+ The design, the conventions and the known gaps are in [AGENTS.md](AGENTS.md).
54
+
55
+ ## Install
56
+
57
+ ```bash
58
+ pip install . # from a checkout; see "Building" for the web UI
59
+ sd-model-hub --help
60
+ ```
61
+
62
+ Python 3.10 or newer. The web UI is bundled into the package; users do not need Node.
63
+
64
+ ## Command line
65
+
66
+ ```text
67
+ sd-model-hub
68
+ ├── webui start the server and open the web UI
69
+ ├── version | env
70
+ ├── config show | get | set | path
71
+ ├── source list
72
+ ├── auth status | use <manual|oauth> | connect | disconnect
73
+ ├── search <query> --source --kind --base-model --sort --limit
74
+ ├── info <source> <model-id>
75
+ ├── download
76
+ │ ├── model <source> <model-id> --version --file --root --dir --to
77
+ │ ├── url <url> --to --sha256 --name
78
+ │ ├── hf <repo-id> --revision --include --exclude --to
79
+ │ └── modelscope <repo-id> --revision --include --exclude --to
80
+ └── library
81
+ ├── root list | add | remove
82
+ ├── list [path] --root --recursive --kind
83
+ ├── info <path> --hash
84
+ ├── identify <path>
85
+ ├── scan --root
86
+ ├── import <paths...> --root --to --move --rename
87
+ ├── move <src...> <dst>
88
+ ├── rename <path> <new-name>
89
+ └── delete <paths...> --permanent --yes
90
+ ```
91
+
92
+ Every listing command accepts `--json`, which prints the same records the API returns.
93
+ `--debug` works at every level. Errors exit with a code per kind: 2 not found, 3 conflict,
94
+ 4 invalid path or input, 5 needs a token, 6 upstream failure.
95
+
96
+ Examples:
97
+
98
+ ```bash
99
+ sd-model-hub library root add ~/ComfyUI/models --layout comfyui
100
+ sd-model-hub library list ~/ComfyUI/models/loras --recursive --kind lora
101
+ sd-model-hub search "detail tweaker" --kind lora --base-model "SDXL 1.0"
102
+ sd-model-hub download model civitai 122359 # into the layout's LoRA folder
103
+ sd-model-hub download hf stabilityai/sdxl-turbo --include "*.safetensors" --to ./sdxl-turbo
104
+ sd-model-hub config set sources.civitai.token <token>
105
+ sd-model-hub webui --port 7865
106
+ ```
107
+
108
+ Ctrl+C during an HTTP download pauses it and keeps the `.part` file; running the same command
109
+ again resumes. Hub downloads cannot pause (neither library resumes), so Ctrl+C cancels them.
110
+
111
+ ## Embedding in another application
112
+
113
+ ```python
114
+ from sd_model_hub import ModelHubServer, ModelRoot
115
+
116
+ hub = ModelHubServer(
117
+ data_dir="./hub-data", # database and caches
118
+ settings_path="./my-app/model-hub.toml", # the settings file, wherever you want it
119
+ model_roots=[ModelRoot("/srv/models", layout="comfyui", name="Models")],
120
+ lock_model_roots=True, # the user cannot add, change or remove folders
121
+ port=0, # any free port; a number asks for that one
122
+ api_prefix="/tools/model-hub", # keeps our routes clear of yours
123
+ )
124
+
125
+ url = hub.start() # returns as soon as it is listening, e.g. http://127.0.0.1:54123/tools/model-hub
126
+ ...
127
+ hub.stop()
128
+ ```
129
+
130
+ | Option | Effect |
131
+ | --- | --- |
132
+ | `data_dir` | Where the database, caches and (by default) the settings file live |
133
+ | `settings_path` | The settings file on its own, apart from `data_dir` |
134
+ | `model_roots` | Folders of models, each with its own `layout`: `comfyui`, `sd-webui` or `custom` |
135
+ | `lock_model_roots` | Fixes them: the API refuses changes and the interface hides those actions |
136
+ | `port` | `0` any free port, a number for that one (moving up unless `strict_port`), `None` for the configured one |
137
+ | `api_prefix` | Serves the API, the socket and the web UI under one path |
138
+ | `settings` | Pins any other setting, e.g. `{"downloads": {"verify_hash": False}}`; pinned values cannot be changed in the UI |
139
+ | `host`, `access_token`, `open_browser`, `log_level` | As for the command line; a non-loopback host requires a token |
140
+
141
+ `hub.start()` is non-blocking and returns the URL; `hub.run()` serves in the foreground;
142
+ `with ModelHubServer(...) as hub:` does both ends. `hub.services` exposes the library, downloads
143
+ and settings for direct use, and `hub.url`, `hub.port` and `hub.running` describe the server.
144
+
145
+ The command line can also take the prefix: `sd-model-hub webui --api-prefix /tools/model-hub`.
146
+
147
+ ## Settings
148
+
149
+ Settings live in `settings.toml` in the data directory (`sd-model-hub config path`). Any setting
150
+ can be overridden with an environment variable: `SD_MODEL_HUB_<GROUP>__<FIELD>`, for example
151
+ `SD_MODEL_HUB_SERVER__PORT=8000` or `SD_MODEL_HUB_NETWORK__PROXY=http://127.0.0.1:7890`.
152
+ `SD_MODEL_HUB_DATA_DIR` moves the data directory. Tokens are never returned by the API.
153
+
154
+ The server listens on `127.0.0.1` by default. To listen on another address, set
155
+ `server.access_token` first; every request then needs it.
156
+
157
+ ### Civitai authentication
158
+
159
+ Two ways, side by side. Neither replaces the other, and nothing switches between them by itself.
160
+
161
+ **A personal API token** (the default, and all most people need):
162
+
163
+ ```bash
164
+ sd-model-hub config set sources.civitai.token <token> # or SD_MODEL_HUB_SOURCES__CIVITAI__TOKEN
165
+ ```
166
+
167
+ **A connected account** (OAuth, Authorization Code with PKCE). It needs an OAuth application
168
+ registered with Civitai, because the client id identifies *this installation*:
169
+
170
+ ```bash
171
+ sd-model-hub config set auth.civitai.oauth_client_id <client id>
172
+ sd-model-hub webui # then Settings → Civitai authentication → Connect
173
+ sd-model-hub auth status # which credential is in use, and where it is kept
174
+ ```
175
+
176
+ Register the callback with Civitai exactly as the server uses it, by default
177
+ `http://127.0.0.1:7865/api/v1/auth/civitai/callback`. On another port or host, or when working
178
+ on the UI with the Vite dev server, list the exact URLs:
179
+
180
+ ```bash
181
+ sd-model-hub config set auth.civitai.redirect_uris '["http://127.0.0.1:7865/api/v1/auth/civitai/callback", "http://localhost:5173/api/v1/auth/civitai/callback"]'
182
+ ```
183
+
184
+ Tokens stay on the server: in the operating system's credential store when there is one, else a
185
+ file in the data directory readable only by you. They are never in `settings.toml`, never sent
186
+ to the browser, and never written into download records. Access tokens are refreshed about a
187
+ minute before they expire, once even if several downloads ask at the same time, and the rotated
188
+ pair is stored as a whole. `sd-model-hub auth disconnect` revokes the authorization and forgets
189
+ it locally, leaving any API token untouched.
190
+
191
+ An environment variable takes precedence over both, and the settings page says so while it does.
192
+
193
+ ### Symbolic links
194
+
195
+ A model folder whose contents are symbolic links to another disk — as a WebUI install often has
196
+ for its LoRAs — needs one setting, because links that leave the model folder are refused by
197
+ default:
198
+
199
+ ```bash
200
+ sd-model-hub config set library.follow_symlinks true
201
+ ```
202
+
203
+ It is also in Settings, under Content & Library. With it on, linked folders open and keep the
204
+ path you navigate with; deleting, moving and renaming then act on the files where they really
205
+ are. Paths containing `..`, absolute paths and reserved names are still refused in either mode,
206
+ and a folder reached twice through a loop of links is walked once.
207
+
208
+ ## Files SD Model Hub writes next to a model
209
+
210
+ | File | Purpose |
211
+ | --- | --- |
212
+ | `<name>.sdmodelhub.json` | Source metadata: model and version ids, base model, trigger words, SHA256 |
213
+ | `<name>.preview.<ext>` | Preview image, found by the WebUI's own lookup |
214
+ | `<name>.json` | Only when `downloads.write_webui_metadata` is on, and only if absent: the WebUI's user metadata |
215
+
216
+ ## Development
217
+
218
+ ```bash
219
+ pip install -e ".[dev]"
220
+ python scripts/dev.py # list the developer tasks
221
+ python scripts/dev.py dev # API server and web UI together, with hot reload
222
+ python scripts/dev.py check # what CI runs: lint, ty, tests, generated types
223
+ python scripts/dev.py typecheck-py # Python types; targets Python 3.10 by default
224
+ python scripts/dev.py test # pytest, vitest and vue-tsc
225
+ python scripts/dev.py format # ruff fixes and formatting
226
+ python scripts/dev.py typegen # regenerate src/api/schema.d.ts from the OpenAPI schema
227
+ ```
228
+
229
+ `dev` is the one to use while working on the UI: it starts the API server and the Vite dev
230
+ server together, tags each line of output with `api` or `web`, and stops both on Ctrl+C. Open
231
+ the address it prints (http://localhost:5173 by default); Vite proxies `/api` and `/ws` to the
232
+ API server, so one address serves both and the UI hot-reloads.
233
+
234
+ While the API server is restarting, the page's socket reconnects on its own and the proxy says
235
+ so once:
236
+
237
+ ```text
238
+ [api proxy] http://127.0.0.1:7865 is not answering (ECONNREFUSED). Start it with: sd-model-hub webui --no-open
239
+ ```
240
+
241
+ Data reappears by itself once the server is back; there is no need to reload the page.
242
+
243
+ ```bash
244
+ python scripts/dev.py dev --api-port 8000 --web-port 3000 --data-dir ./dev-data
245
+ python scripts/dev.py web-dev # only the UI, against an API server you started yourself
246
+ sd-model-hub webui --no-open # only the API server
247
+ ```
248
+
249
+ The tasks are a small Python script rather than a Makefile, so they work the same on Windows.
250
+ Each one prints the command it runs, so the underlying tools stay easy to call directly.
251
+
252
+ The web UI uses [bun](https://bun.sh). Layers: `sd_model_hub/core` holds all logic and imports
253
+ no web or CLI framework (a test enforces it); `sd_model_hub/api` (FastAPI) and `sd_model_hub/cli`
254
+ (Typer) are thin wrappers over it.
255
+
256
+ Detection rules are JSON files in `sd_model_hub/core/detection/rules_data/`. To add a test
257
+ fixture from a real file: `python scripts/extract_header_fixture.py <file> <name>`.
258
+
259
+ ## Building
260
+
261
+ ```bash
262
+ python scripts/build_wheel.py # web UI with bun, then the wheel and sdist into dist/
263
+ python scripts/build_wheel.py --ci # for CI: skips the type-check, keeps the built web UI
264
+ python scripts/build_wheel.py --outdir out --keep-web-dist
265
+ python scripts/dev.py wheel # the same, through the task runner
266
+ ```
267
+
268
+ A bare `python -m build` produces a wheel without the web UI, because setuptools packages only
269
+ the files that exist when it runs; the server then logs a warning and serves the API only.
270
+
271
+ ## Releasing
272
+
273
+ `.github/workflows/release.yml` publishes to PyPI when a push to `main` changes
274
+ `sd_model_hub/version.py`, when a `v*` tag is pushed, or through **Run workflow** in the Actions tab.
275
+ For a version change on `main`:
276
+
277
+ ```bash
278
+ python scripts/dev.py check # first, locally
279
+ # bump VERSION in sd_model_hub/version.py, commit
280
+ git push origin main
281
+ ```
282
+
283
+ The workflow runs the tests and ty checks on Python 3.10 to 3.14, the web UI tests and type-check, and the
284
+ generated-types check; for tag runs it verifies that the tag matches `sd_model_hub/version.py`, then builds
285
+ the wheel **with the web UI in it**, checks the wheel really contains the UI and the detection
286
+ rules, installs it into a clean environment and runs `sd-model-hub version`. Only then does it
287
+ publish. Tag runs also create the GitHub release with the built files attached.
288
+
289
+ Publishing runs `python -m twine upload` directly on the runner, without a Docker action.
290
+ Existing files are skipped so retrying a release does not upload them again.
291
+
292
+ Set-up, once: create a PyPI API token and save it as the GitHub Actions secret `TWINE_PASSWORD`
293
+ in this repository or its `pypi` environment. Create that environment in the repository settings;
294
+ it can also require an approval before a release goes out. The workflow uses `__token__` as the
295
+ username and does not require a trusted publisher.
@@ -0,0 +1,256 @@
1
+ # SD Model Hub
2
+
3
+ Download and manage Stable Diffusion models, from the command line or a web UI.
4
+
5
+ - **Browse** Civitai, OpenModelDB and GitHub Releases, and download with resume, SHA256
6
+ checks, preview images and metadata sidecars.
7
+ - **Hubs:** download files or whole repositories from Hugging Face (or a mirror such as
8
+ hf-mirror) and ModelScope.
9
+ - **Library:** point SD Model Hub at your ComfyUI or Stable Diffusion WebUI model folders. It
10
+ identifies each model's type and base model from the safetensors header (never unpickling
11
+ anything), shows previews, and imports, moves, renames and deletes models together with
12
+ their preview images and sidecar files. Drag files into the browser to upload them.
13
+
14
+ The design, the conventions and the known gaps are in [AGENTS.md](AGENTS.md).
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ pip install . # from a checkout; see "Building" for the web UI
20
+ sd-model-hub --help
21
+ ```
22
+
23
+ Python 3.10 or newer. The web UI is bundled into the package; users do not need Node.
24
+
25
+ ## Command line
26
+
27
+ ```text
28
+ sd-model-hub
29
+ ├── webui start the server and open the web UI
30
+ ├── version | env
31
+ ├── config show | get | set | path
32
+ ├── source list
33
+ ├── auth status | use <manual|oauth> | connect | disconnect
34
+ ├── search <query> --source --kind --base-model --sort --limit
35
+ ├── info <source> <model-id>
36
+ ├── download
37
+ │ ├── model <source> <model-id> --version --file --root --dir --to
38
+ │ ├── url <url> --to --sha256 --name
39
+ │ ├── hf <repo-id> --revision --include --exclude --to
40
+ │ └── modelscope <repo-id> --revision --include --exclude --to
41
+ └── library
42
+ ├── root list | add | remove
43
+ ├── list [path] --root --recursive --kind
44
+ ├── info <path> --hash
45
+ ├── identify <path>
46
+ ├── scan --root
47
+ ├── import <paths...> --root --to --move --rename
48
+ ├── move <src...> <dst>
49
+ ├── rename <path> <new-name>
50
+ └── delete <paths...> --permanent --yes
51
+ ```
52
+
53
+ Every listing command accepts `--json`, which prints the same records the API returns.
54
+ `--debug` works at every level. Errors exit with a code per kind: 2 not found, 3 conflict,
55
+ 4 invalid path or input, 5 needs a token, 6 upstream failure.
56
+
57
+ Examples:
58
+
59
+ ```bash
60
+ sd-model-hub library root add ~/ComfyUI/models --layout comfyui
61
+ sd-model-hub library list ~/ComfyUI/models/loras --recursive --kind lora
62
+ sd-model-hub search "detail tweaker" --kind lora --base-model "SDXL 1.0"
63
+ sd-model-hub download model civitai 122359 # into the layout's LoRA folder
64
+ sd-model-hub download hf stabilityai/sdxl-turbo --include "*.safetensors" --to ./sdxl-turbo
65
+ sd-model-hub config set sources.civitai.token <token>
66
+ sd-model-hub webui --port 7865
67
+ ```
68
+
69
+ Ctrl+C during an HTTP download pauses it and keeps the `.part` file; running the same command
70
+ again resumes. Hub downloads cannot pause (neither library resumes), so Ctrl+C cancels them.
71
+
72
+ ## Embedding in another application
73
+
74
+ ```python
75
+ from sd_model_hub import ModelHubServer, ModelRoot
76
+
77
+ hub = ModelHubServer(
78
+ data_dir="./hub-data", # database and caches
79
+ settings_path="./my-app/model-hub.toml", # the settings file, wherever you want it
80
+ model_roots=[ModelRoot("/srv/models", layout="comfyui", name="Models")],
81
+ lock_model_roots=True, # the user cannot add, change or remove folders
82
+ port=0, # any free port; a number asks for that one
83
+ api_prefix="/tools/model-hub", # keeps our routes clear of yours
84
+ )
85
+
86
+ url = hub.start() # returns as soon as it is listening, e.g. http://127.0.0.1:54123/tools/model-hub
87
+ ...
88
+ hub.stop()
89
+ ```
90
+
91
+ | Option | Effect |
92
+ | --- | --- |
93
+ | `data_dir` | Where the database, caches and (by default) the settings file live |
94
+ | `settings_path` | The settings file on its own, apart from `data_dir` |
95
+ | `model_roots` | Folders of models, each with its own `layout`: `comfyui`, `sd-webui` or `custom` |
96
+ | `lock_model_roots` | Fixes them: the API refuses changes and the interface hides those actions |
97
+ | `port` | `0` any free port, a number for that one (moving up unless `strict_port`), `None` for the configured one |
98
+ | `api_prefix` | Serves the API, the socket and the web UI under one path |
99
+ | `settings` | Pins any other setting, e.g. `{"downloads": {"verify_hash": False}}`; pinned values cannot be changed in the UI |
100
+ | `host`, `access_token`, `open_browser`, `log_level` | As for the command line; a non-loopback host requires a token |
101
+
102
+ `hub.start()` is non-blocking and returns the URL; `hub.run()` serves in the foreground;
103
+ `with ModelHubServer(...) as hub:` does both ends. `hub.services` exposes the library, downloads
104
+ and settings for direct use, and `hub.url`, `hub.port` and `hub.running` describe the server.
105
+
106
+ The command line can also take the prefix: `sd-model-hub webui --api-prefix /tools/model-hub`.
107
+
108
+ ## Settings
109
+
110
+ Settings live in `settings.toml` in the data directory (`sd-model-hub config path`). Any setting
111
+ can be overridden with an environment variable: `SD_MODEL_HUB_<GROUP>__<FIELD>`, for example
112
+ `SD_MODEL_HUB_SERVER__PORT=8000` or `SD_MODEL_HUB_NETWORK__PROXY=http://127.0.0.1:7890`.
113
+ `SD_MODEL_HUB_DATA_DIR` moves the data directory. Tokens are never returned by the API.
114
+
115
+ The server listens on `127.0.0.1` by default. To listen on another address, set
116
+ `server.access_token` first; every request then needs it.
117
+
118
+ ### Civitai authentication
119
+
120
+ Two ways, side by side. Neither replaces the other, and nothing switches between them by itself.
121
+
122
+ **A personal API token** (the default, and all most people need):
123
+
124
+ ```bash
125
+ sd-model-hub config set sources.civitai.token <token> # or SD_MODEL_HUB_SOURCES__CIVITAI__TOKEN
126
+ ```
127
+
128
+ **A connected account** (OAuth, Authorization Code with PKCE). It needs an OAuth application
129
+ registered with Civitai, because the client id identifies *this installation*:
130
+
131
+ ```bash
132
+ sd-model-hub config set auth.civitai.oauth_client_id <client id>
133
+ sd-model-hub webui # then Settings → Civitai authentication → Connect
134
+ sd-model-hub auth status # which credential is in use, and where it is kept
135
+ ```
136
+
137
+ Register the callback with Civitai exactly as the server uses it, by default
138
+ `http://127.0.0.1:7865/api/v1/auth/civitai/callback`. On another port or host, or when working
139
+ on the UI with the Vite dev server, list the exact URLs:
140
+
141
+ ```bash
142
+ sd-model-hub config set auth.civitai.redirect_uris '["http://127.0.0.1:7865/api/v1/auth/civitai/callback", "http://localhost:5173/api/v1/auth/civitai/callback"]'
143
+ ```
144
+
145
+ Tokens stay on the server: in the operating system's credential store when there is one, else a
146
+ file in the data directory readable only by you. They are never in `settings.toml`, never sent
147
+ to the browser, and never written into download records. Access tokens are refreshed about a
148
+ minute before they expire, once even if several downloads ask at the same time, and the rotated
149
+ pair is stored as a whole. `sd-model-hub auth disconnect` revokes the authorization and forgets
150
+ it locally, leaving any API token untouched.
151
+
152
+ An environment variable takes precedence over both, and the settings page says so while it does.
153
+
154
+ ### Symbolic links
155
+
156
+ A model folder whose contents are symbolic links to another disk — as a WebUI install often has
157
+ for its LoRAs — needs one setting, because links that leave the model folder are refused by
158
+ default:
159
+
160
+ ```bash
161
+ sd-model-hub config set library.follow_symlinks true
162
+ ```
163
+
164
+ It is also in Settings, under Content & Library. With it on, linked folders open and keep the
165
+ path you navigate with; deleting, moving and renaming then act on the files where they really
166
+ are. Paths containing `..`, absolute paths and reserved names are still refused in either mode,
167
+ and a folder reached twice through a loop of links is walked once.
168
+
169
+ ## Files SD Model Hub writes next to a model
170
+
171
+ | File | Purpose |
172
+ | --- | --- |
173
+ | `<name>.sdmodelhub.json` | Source metadata: model and version ids, base model, trigger words, SHA256 |
174
+ | `<name>.preview.<ext>` | Preview image, found by the WebUI's own lookup |
175
+ | `<name>.json` | Only when `downloads.write_webui_metadata` is on, and only if absent: the WebUI's user metadata |
176
+
177
+ ## Development
178
+
179
+ ```bash
180
+ pip install -e ".[dev]"
181
+ python scripts/dev.py # list the developer tasks
182
+ python scripts/dev.py dev # API server and web UI together, with hot reload
183
+ python scripts/dev.py check # what CI runs: lint, ty, tests, generated types
184
+ python scripts/dev.py typecheck-py # Python types; targets Python 3.10 by default
185
+ python scripts/dev.py test # pytest, vitest and vue-tsc
186
+ python scripts/dev.py format # ruff fixes and formatting
187
+ python scripts/dev.py typegen # regenerate src/api/schema.d.ts from the OpenAPI schema
188
+ ```
189
+
190
+ `dev` is the one to use while working on the UI: it starts the API server and the Vite dev
191
+ server together, tags each line of output with `api` or `web`, and stops both on Ctrl+C. Open
192
+ the address it prints (http://localhost:5173 by default); Vite proxies `/api` and `/ws` to the
193
+ API server, so one address serves both and the UI hot-reloads.
194
+
195
+ While the API server is restarting, the page's socket reconnects on its own and the proxy says
196
+ so once:
197
+
198
+ ```text
199
+ [api proxy] http://127.0.0.1:7865 is not answering (ECONNREFUSED). Start it with: sd-model-hub webui --no-open
200
+ ```
201
+
202
+ Data reappears by itself once the server is back; there is no need to reload the page.
203
+
204
+ ```bash
205
+ python scripts/dev.py dev --api-port 8000 --web-port 3000 --data-dir ./dev-data
206
+ python scripts/dev.py web-dev # only the UI, against an API server you started yourself
207
+ sd-model-hub webui --no-open # only the API server
208
+ ```
209
+
210
+ The tasks are a small Python script rather than a Makefile, so they work the same on Windows.
211
+ Each one prints the command it runs, so the underlying tools stay easy to call directly.
212
+
213
+ The web UI uses [bun](https://bun.sh). Layers: `sd_model_hub/core` holds all logic and imports
214
+ no web or CLI framework (a test enforces it); `sd_model_hub/api` (FastAPI) and `sd_model_hub/cli`
215
+ (Typer) are thin wrappers over it.
216
+
217
+ Detection rules are JSON files in `sd_model_hub/core/detection/rules_data/`. To add a test
218
+ fixture from a real file: `python scripts/extract_header_fixture.py <file> <name>`.
219
+
220
+ ## Building
221
+
222
+ ```bash
223
+ python scripts/build_wheel.py # web UI with bun, then the wheel and sdist into dist/
224
+ python scripts/build_wheel.py --ci # for CI: skips the type-check, keeps the built web UI
225
+ python scripts/build_wheel.py --outdir out --keep-web-dist
226
+ python scripts/dev.py wheel # the same, through the task runner
227
+ ```
228
+
229
+ A bare `python -m build` produces a wheel without the web UI, because setuptools packages only
230
+ the files that exist when it runs; the server then logs a warning and serves the API only.
231
+
232
+ ## Releasing
233
+
234
+ `.github/workflows/release.yml` publishes to PyPI when a push to `main` changes
235
+ `sd_model_hub/version.py`, when a `v*` tag is pushed, or through **Run workflow** in the Actions tab.
236
+ For a version change on `main`:
237
+
238
+ ```bash
239
+ python scripts/dev.py check # first, locally
240
+ # bump VERSION in sd_model_hub/version.py, commit
241
+ git push origin main
242
+ ```
243
+
244
+ The workflow runs the tests and ty checks on Python 3.10 to 3.14, the web UI tests and type-check, and the
245
+ generated-types check; for tag runs it verifies that the tag matches `sd_model_hub/version.py`, then builds
246
+ the wheel **with the web UI in it**, checks the wheel really contains the UI and the detection
247
+ rules, installs it into a clean environment and runs `sd-model-hub version`. Only then does it
248
+ publish. Tag runs also create the GitHub release with the built files attached.
249
+
250
+ Publishing runs `python -m twine upload` directly on the runner, without a Docker action.
251
+ Existing files are skipped so retrying a release does not upload them again.
252
+
253
+ Set-up, once: create a PyPI API token and save it as the GitHub Actions secret `TWINE_PASSWORD`
254
+ in this repository or its `pypi` environment. Create that environment in the repository settings;
255
+ it can also require an approval before a release goes out. The workflow uses `__token__` as the
256
+ username and does not require a trusted publisher.
@@ -0,0 +1,85 @@
1
+ [build-system]
2
+ requires = ["setuptools", "pip", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "sd-model-hub"
7
+ description = "Download and manage Stable Diffusion models from the command line and a web UI"
8
+ readme = "README.md"
9
+ requires-python = ">=3.10"
10
+ dynamic = ["version"]
11
+ classifiers = [
12
+ "Development Status :: 3 - Alpha",
13
+ "Environment :: Console",
14
+ "Environment :: Web Environment",
15
+ "Intended Audience :: End Users/Desktop",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.10",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Programming Language :: Python :: 3.13",
21
+ "Programming Language :: Python :: 3.14",
22
+ "Topic :: Multimedia :: Graphics",
23
+ "Topic :: Utilities",
24
+ ]
25
+ dependencies = [
26
+ "fastapi",
27
+ "uvicorn",
28
+ # uvicorn speaks WebSocket only when one of these is installed. Without it the socket falls
29
+ # back to long polling, which works but is slower and noisier.
30
+ "websockets>=12",
31
+ "python-socketio",
32
+ "pydantic>=2",
33
+ "httpx",
34
+ "huggingface_hub",
35
+ "modelscope",
36
+ "typer>=0.26.7",
37
+ "rich",
38
+ "Pillow>=10.0",
39
+ "send2trash",
40
+ "tomli-w",
41
+ "tomli; python_version < '3.11'",
42
+ ]
43
+
44
+ [project.optional-dependencies]
45
+ # ty resolves the optional credential store and the Python 3.10 TOML fallback on every host.
46
+ dev = ["keyring", "pytest", "ruff", "tomli", "ty"]
47
+
48
+ [project.scripts]
49
+ "sd-model-hub" = "sd_model_hub.cli.app:main"
50
+
51
+ [tool.setuptools.packages.find]
52
+ where = ["."]
53
+ include = ["sd_model_hub*"]
54
+
55
+ [tool.setuptools.package-data]
56
+ "sd_model_hub.webui" = ["dist/**/*"]
57
+ "sd_model_hub.core.detection" = ["rules_data/*.json"]
58
+
59
+ [tool.setuptools.dynamic]
60
+ version = { attr = "sd_model_hub.version.VERSION" }
61
+
62
+ [tool.ruff]
63
+ line-length = 180
64
+ indent-width = 4
65
+ extend-exclude = ["sd_model_hub/webui/dist", "sd_model_hub/webui/node_modules"]
66
+ lint.ignore = [
67
+ "E402" # module-import-not-at-top-of-file
68
+ ]
69
+
70
+ [tool.ty.environment]
71
+ python-version = "3.10"
72
+
73
+ [tool.ty.terminal]
74
+ output-format = "concise"
75
+
76
+ [tool.ty.src]
77
+ include = ["sd_model_hub"]
78
+ exclude = ["sd_model_hub/webui/dist", "sd_model_hub/webui/node_modules"]
79
+
80
+ [tool.pytest.ini_options]
81
+ testpaths = ["tests"]
82
+ markers = [
83
+ "live: tests that call real remote services (deselected by default)",
84
+ ]
85
+ addopts = "-m 'not live'"
@@ -0,0 +1,33 @@
1
+ """SD Model Hub: download and manage Stable Diffusion models.
2
+
3
+ To embed the web UI and API in another application:
4
+
5
+ from sd_model_hub import ModelHubServer, ModelRoot
6
+
7
+ hub = ModelHubServer(model_roots=[ModelRoot("/srv/models", layout="comfyui")], port=0)
8
+ print(hub.start()) # http://127.0.0.1:54123
9
+ """
10
+
11
+ from typing import TYPE_CHECKING, Any
12
+
13
+ from sd_model_hub.version import VERSION
14
+
15
+ if TYPE_CHECKING:
16
+ from sd_model_hub.embed import ModelHubServer, ModelRoot, serve
17
+
18
+ __all__ = ["VERSION", "ModelHubServer", "ModelRoot", "serve"]
19
+
20
+ _LAZY = {"ModelHubServer", "ModelRoot", "serve"}
21
+
22
+
23
+ def __getattr__(name: str) -> Any:
24
+ """Import the server lazily, so ``sd-model-hub version`` does not pay for FastAPI."""
25
+ if name in _LAZY:
26
+ from sd_model_hub import embed
27
+
28
+ return getattr(embed, name)
29
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
30
+
31
+
32
+ def __dir__() -> list[str]:
33
+ return sorted(__all__)
@@ -0,0 +1,6 @@
1
+ """Allow ``python -m sd_model_hub``."""
2
+
3
+ from sd_model_hub.cli.app import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1 @@
1
+ """FastAPI wrapper over ``sd_model_hub.core``."""