Entropic 2.3.8
Local-first agentic inference engine
Loading...
Searching...
No Matches
download.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
25
26#include <cstdio>
27#include <cstdlib>
28#include <cstring>
29#include <filesystem>
30#include <string>
31#include <sys/wait.h>
32#include <unistd.h>
33
34namespace entropic::cli {
35
36namespace {
37
44std::filesystem::path default_model_dir()
45{
46 if (const char* env = std::getenv("ENTROPIC_MODEL_DIR"); env && *env) {
47 return std::filesystem::path(env);
48 }
49 const char* home = std::getenv("HOME");
50 if (home && *home) {
51 return std::filesystem::path(home) / ".entropic" / "models";
52 }
53 return std::filesystem::path("/opt/entropic/models");
54}
55
62int list_models(const entropic::config::BundledModels& registry)
63{
64 std::printf("%-12s %-6s %s\n", "KEY", "SIZE", "DESCRIPTION");
65 std::printf("%-12s %-6s %s\n", "---", "----", "-----------");
66 for (const auto& [key, entry] : registry.entries()) {
67 std::printf("%-12s %4.1fGB %s\n",
68 key.c_str(), entry.size_gb, entry.description.c_str());
69 }
70 return 0;
71}
72
86int curl_download(const std::string& url, const std::filesystem::path& dest)
87{
88 pid_t pid = fork();
89 if (pid < 0) {
90 std::fprintf(stderr, "fork failed\n");
91 return 1;
92 }
93 if (pid == 0) {
94 execlp("curl", "curl",
95 "--fail-with-body",
96 "-L",
97 "--progress-bar",
98 "--continue-at", "-",
99 "-o", dest.c_str(),
100 url.c_str(),
101 static_cast<char*>(nullptr));
102 std::fprintf(stderr, "execlp(curl) failed — is curl installed?\n");
103 _exit(127);
104 }
105 int status = 0;
106 waitpid(pid, &status, 0);
107 if (WIFEXITED(status)) {
108 return WEXITSTATUS(status);
109 }
110 return 1;
111}
112
113} // anonymous namespace
114
116 std::string key;
117 std::filesystem::path override_dir;
118 bool want_list = false;
119 bool error = false;
120};
121
128DownloadArgs parse_download_args(int argc, char* argv[])
129{
130 DownloadArgs out;
131 for (int i = 1; i < argc; i++) {
132 std::string arg = argv[i];
133 if (arg == "--list" || arg == "-l") {
134 out.want_list = true;
135 } else if (arg == "--dir" && i + 1 < argc) {
136 out.override_dir = argv[++i];
137 } else if (!arg.empty() && arg[0] != '-') {
138 out.key = arg;
139 } else {
140 std::fprintf(stderr, "entropic download: unknown option '%s'\n",
141 arg.c_str());
142 out.error = true;
143 }
144 }
145 return out;
146}
147
155 const entropic::config::BundledModels& registry, const std::string& key)
156{
157 const auto* entry = registry.get(key);
158 if (!entry) {
159 std::fprintf(stderr,
160 "entropic download: unknown model key '%s'. "
161 "Run `entropic download --list` for available keys.\n",
162 key.c_str());
163 return nullptr;
164 }
165 if (entry->url.empty()) {
166 std::fprintf(stderr,
167 "entropic download: model '%s' has no URL in the registry.\n",
168 key.c_str());
169 return nullptr;
170 }
171 return entry;
172}
173
181 const std::filesystem::path& target_dir,
182 const std::string& key)
183{
184 int rc = 0;
185 std::error_code ec;
186 std::filesystem::create_directories(target_dir, ec);
187 auto target_file = target_dir / (entry.name + ".gguf");
188 const bool already = std::filesystem::exists(target_file)
189 && std::filesystem::file_size(target_file) > 0;
190
191 if (ec) {
192 std::fprintf(stderr, "entropic download: cannot create %s: %s\n",
193 target_dir.c_str(), ec.message().c_str());
194 rc = 1;
195 } else if (already) {
196 std::printf("Model '%s' already at %s — nothing to do.\n",
197 key.c_str(), target_file.c_str());
198 } else {
199 std::printf("Downloading %s (%.1f GB)\n from: %s\n to: %s\n",
200 entry.name.c_str(), entry.size_gb,
201 entry.url.c_str(), target_file.c_str());
202 rc = curl_download(entry.url, target_file);
203 if (rc != 0) {
204 std::fprintf(stderr,
205 "entropic download: curl exited with status %d\n"
206 "Partial file left at %s — rerun to resume.\n",
207 rc, target_file.c_str());
208 } else {
209 std::printf("\nDone. Point the engine at this dir with:\n"
210 " export ENTROPIC_MODEL_DIR=%s\n",
211 target_dir.c_str());
212 }
213 }
214 return rc;
215}
216
234 const entropic::config::BundledModels& registry,
235 const std::filesystem::path& target_dir)
236{
237 if (entry.mmproj_key.empty()) { return 0; }
238 const auto* mm = resolve_entry(registry, entry.mmproj_key);
239 if (mm == nullptr) { return 0; }
240 std::printf("Fetching paired mmproj '%s' for vision support\n",
241 entry.mmproj_key.c_str());
242 return fetch_to(*mm, target_dir, entry.mmproj_key);
243}
244
261int dispatch(const DownloadArgs& args,
262 const entropic::config::BundledModels& registry)
263{
264 int rc = 0;
265 if (args.want_list) {
266 rc = list_models(registry);
267 } else if (args.key.empty()) {
268 std::fprintf(stderr,
269 "Usage: entropic download <model-key>\n"
270 " entropic download --list\n"
271 " entropic download --dir DIR <model-key>\n");
272 rc = 1;
273 } else if (const auto* entry = resolve_entry(registry, args.key); !entry) {
274 rc = 1;
275 } else {
276 auto target_dir = args.override_dir.empty()
277 ? default_model_dir() : args.override_dir;
278 rc = fetch_to(*entry, target_dir, args.key);
279 if (rc == 0) {
280 rc = fetch_mmproj_if_paired(*entry, registry, target_dir);
281 }
282 }
283 return rc;
284}
285
296int run_download(int argc, char* argv[])
297{
298 auto args = parse_download_args(argc, argv);
299 int rc = 0;
300 if (args.error) {
301 rc = 1;
302 } else {
304 auto err = registry.auto_discover_and_load();
305 if (!err.empty()) {
306 std::fprintf(stderr,
307 "entropic download: cannot find bundled_models.yaml: %s\n"
308 "(Expected under <install-prefix>/share/entropic/ or the source tree's data/.)\n",
309 err.c_str());
310 rc = 1;
311 } else {
312 rc = dispatch(args, registry);
313 }
314 }
315 return rc;
316}
317
318} // namespace entropic::cli
Bundled model registry — resolves keys to filesystem paths.
Bundled model registry loaded from bundled_models.yaml.
std::string auto_discover_and_load()
Auto-discover and load bundled_models.yaml.
const BundledModelEntry * get(const std::string &key) const
Get entry by key.
const entropic::config::BundledModelEntry * resolve_entry(const entropic::config::BundledModels &registry, const std::string &key)
Resolve a registry entry by key with user-friendly error output.
Definition download.cpp:154
int fetch_mmproj_if_paired(const entropic::config::BundledModelEntry &entry, const entropic::config::BundledModels &registry, const std::filesystem::path &target_dir)
Paired-mmproj follow-up fetch (gh#42, v2.1.8).
Definition download.cpp:232
DownloadArgs parse_download_args(int argc, char *argv[])
Parse command-line args for entropic download.
Definition download.cpp:128
int run_download(int argc, char *argv[])
Handle entropic download subcommand.
Definition download.cpp:296
int fetch_to(const entropic::config::BundledModelEntry &entry, const std::filesystem::path &target_dir, const std::string &key)
Download one registry entry to a target directory.
Definition download.cpp:180
int dispatch(const DownloadArgs &args, const entropic::config::BundledModels &registry)
Handle entropic download subcommand.
Definition download.cpp:261
Entry in the bundled model registry.
double size_gb
Model size in GB.
std::string mmproj_key
Paired mmproj registry key, or empty (gh#42, v2.1.8).
std::string name
Model filename stem (e.g., "Qwen3.5-35B-A3B-UD-IQ3_XXS")