Entropic 2.9.4
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 std::string family; // gh#62 selector
119 std::string size; // gh#62 selector
120 std::string quant; // gh#62 selector
121 bool want_list = false;
122 bool error = false;
123};
124
131static bool match_value_flag(const std::string& arg, const char* flag,
132 int& i, int argc, char* argv[], std::string& dst)
133{
134 if (arg == flag && i + 1 < argc) {
135 dst = argv[++i];
136 return true;
137 }
138 return false;
139}
140
150static bool match_any_value_flag(const std::string& arg, int& i, int argc,
151 char* argv[], DownloadArgs& out)
152{
153 return match_value_flag(arg, "--family", i, argc, argv, out.family)
154 || match_value_flag(arg, "--size", i, argc, argv, out.size)
155 || match_value_flag(arg, "--quant", i, argc, argv, out.quant);
156}
157
164DownloadArgs parse_download_args(int argc, char* argv[])
165{
166 DownloadArgs out;
167 // Early-continue (flat) rather than an else-if chain — keeps the knots
168 // nesting gate happy as selector flags are added.
169 for (int i = 1; i < argc; i++) {
170 std::string arg = argv[i];
171 if (arg == "--list" || arg == "-l") { out.want_list = true; continue; }
172 if (arg == "--dir" && i + 1 < argc) { out.override_dir = argv[++i]; continue; }
173 if (match_any_value_flag(arg, i, argc, argv, out)) { continue; }
174 if (!arg.empty() && arg[0] != '-') { out.key = arg; continue; }
175 std::fprintf(stderr, "entropic download: unknown option '%s'\n",
176 arg.c_str());
177 out.error = true;
178 }
179 return out;
180}
181
192 const DownloadArgs& args,
193 const entropic::config::BundledModels& registry)
194{
195 if (!args.key.empty()) { return args.key; }
196 const bool hf = !args.family.empty();
197 const bool hs = !args.size.empty();
198 const bool hq = !args.quant.empty();
199 if (!hf && !hs && !hq) { return ""; }
200 std::string key;
201 if (!(hf && hs && hq)) {
202 std::fprintf(stderr, "entropic download: --family/--size/--quant "
203 "must be given together (or pass a model key).\n");
204 } else {
205 key = registry.find_by(args.family, args.size, args.quant);
206 if (key.empty()) {
207 std::fprintf(stderr, "entropic download: no bundled model matches "
208 "--family %s --size %s --quant %s. Try --list.\n",
209 args.family.c_str(), args.size.c_str(),
210 args.quant.c_str());
211 }
212 }
213 return key;
214}
215
223 const entropic::config::BundledModels& registry, const std::string& key)
224{
225 const auto* entry = registry.get(key);
226 if (!entry) {
227 std::fprintf(stderr,
228 "entropic download: unknown model key '%s'. "
229 "Run `entropic download --list` for available keys.\n",
230 key.c_str());
231 return nullptr;
232 }
233 if (entry->url.empty()) {
234 std::fprintf(stderr,
235 "entropic download: model '%s' has no URL in the registry.\n",
236 key.c_str());
237 return nullptr;
238 }
239 return entry;
240}
241
249 const std::filesystem::path& target_dir,
250 const std::string& key)
251{
252 int rc = 0;
253 std::error_code ec;
254 std::filesystem::create_directories(target_dir, ec);
255 auto target_file = target_dir / (entry.name + ".gguf");
256 const bool already = std::filesystem::exists(target_file)
257 && std::filesystem::file_size(target_file) > 0;
258
259 if (ec) {
260 std::fprintf(stderr, "entropic download: cannot create %s: %s\n",
261 target_dir.c_str(), ec.message().c_str());
262 rc = 1;
263 } else if (already) {
264 std::printf("Model '%s' already at %s — nothing to do.\n",
265 key.c_str(), target_file.c_str());
266 } else {
267 std::printf("Downloading %s (%.1f GB)\n from: %s\n to: %s\n",
268 entry.name.c_str(), entry.size_gb,
269 entry.url.c_str(), target_file.c_str());
270 rc = curl_download(entry.url, target_file);
271 if (rc != 0) {
272 std::fprintf(stderr,
273 "entropic download: curl exited with status %d\n"
274 "Partial file left at %s — rerun to resume.\n",
275 rc, target_file.c_str());
276 } else {
277 std::printf("\nDone. Point the engine at this dir with:\n"
278 " export ENTROPIC_MODEL_DIR=%s\n",
279 target_dir.c_str());
280 }
281 }
282 return rc;
283}
284
302 const entropic::config::BundledModels& registry,
303 const std::filesystem::path& target_dir)
304{
305 if (entry.mmproj_key.empty()) { return 0; }
306 const auto* mm = resolve_entry(registry, entry.mmproj_key);
307 if (mm == nullptr) { return 0; }
308 std::printf("Fetching paired mmproj '%s' for vision support\n",
309 entry.mmproj_key.c_str());
310 return fetch_to(*mm, target_dir, entry.mmproj_key);
311}
312
329int dispatch(const DownloadArgs& args,
330 const entropic::config::BundledModels& registry)
331{
332 int rc = 0;
333 const bool has_selector = !args.family.empty() || !args.size.empty()
334 || !args.quant.empty();
335 if (args.want_list) {
336 rc = list_models(registry);
337 } else if (const std::string key = resolve_selector_key(args, registry);
338 key.empty()) {
339 if (!has_selector) {
340 std::fprintf(stderr,
341 "Usage: entropic download <model-key>\n"
342 " entropic download --family F --size S --quant Q\n"
343 " entropic download --list\n"
344 " entropic download --dir DIR <model-key>\n");
345 }
346 rc = 1;
347 } else if (const auto* entry = resolve_entry(registry, key); !entry) {
348 rc = 1;
349 } else {
350 auto target_dir = args.override_dir.empty()
351 ? default_model_dir() : args.override_dir;
352 rc = fetch_to(*entry, target_dir, key);
353 if (rc == 0) {
354 rc = fetch_mmproj_if_paired(*entry, registry, target_dir);
355 }
356 }
357 return rc;
358}
359
370int run_download(int argc, char* argv[])
371{
372 auto args = parse_download_args(argc, argv);
373 int rc = 0;
374 if (args.error) {
375 rc = 1;
376 } else {
378 auto err = registry.auto_discover_and_load();
379 if (!err.empty()) {
380 std::fprintf(stderr,
381 "entropic download: cannot find bundled_models.yaml: %s\n"
382 "(Expected under <install-prefix>/share/entropic/ or the source tree's data/.)\n",
383 err.c_str());
384 rc = 1;
385 } else {
386 rc = dispatch(args, registry);
387 }
388 }
389 return rc;
390}
391
392} // 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.
std::string find_by(const std::string &family, const std::string &size_label, const std::string &quant) const
Look up a registry key by (family, size, quant) (gh#62).
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:222
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:300
static bool match_any_value_flag(const std::string &arg, int &i, int argc, char *argv[], DownloadArgs &out)
Try the gh#62 selector value-flags (–family/–size/–quant).
Definition download.cpp:150
DownloadArgs parse_download_args(int argc, char *argv[])
Parse command-line args for entropic download.
Definition download.cpp:164
int run_download(int argc, char *argv[])
Handle entropic download subcommand.
Definition download.cpp:370
std::string resolve_selector_key(const DownloadArgs &args, const entropic::config::BundledModels &registry)
Resolve the target model key from an explicit key or a --family/--size/--quant selector (gh#62).
Definition download.cpp:191
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:248
int dispatch(const DownloadArgs &args, const entropic::config::BundledModels &registry)
Handle entropic download subcommand.
Definition download.cpp:329
static bool match_value_flag(const std::string &arg, const char *flag, int &i, int argc, char *argv[], std::string &dst)
Match a --flag VALUE pair and consume the value (gh#62).
Definition download.cpp:131
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")