@hasna/models 0.0.3 → 0.0.5

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.
package/dist/cli/index.js CHANGED
@@ -2574,34 +2574,56 @@ var source_default = chalk;
2574
2574
 
2575
2575
  // src/cli/index.ts
2576
2576
  import { randomUUID as randomUUID2 } from "crypto";
2577
- import { existsSync, readFileSync as readFileSync2, rmSync, statSync as statSync2 } from "fs";
2578
- import { dirname as dirname4, join as join3 } from "path";
2579
- import { fileURLToPath } from "url";
2577
+ import { existsSync as existsSync2, readFileSync as readFileSync3, rmSync, statSync as statSync2 } from "fs";
2578
+ import { dirname as dirname5, join as join4 } from "path";
2579
+ import { fileURLToPath as fileURLToPath2 } from "url";
2580
2580
 
2581
2581
  // src/version.ts
2582
+ import { existsSync, readFileSync } from "fs";
2583
+ import { dirname, join, parse } from "path";
2584
+ import { fileURLToPath } from "url";
2585
+ var cachedVersion = null;
2582
2586
  function getPackageVersion() {
2583
- return "0.0.2";
2587
+ if (cachedVersion)
2588
+ return cachedVersion;
2589
+ let currentDir = dirname(fileURLToPath(import.meta.url));
2590
+ const root = parse(currentDir).root;
2591
+ while (true) {
2592
+ const packageJsonPath = join(currentDir, "package.json");
2593
+ if (existsSync(packageJsonPath)) {
2594
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
2595
+ if (typeof packageJson.version === "string" && packageJson.version.trim()) {
2596
+ cachedVersion = packageJson.version;
2597
+ return cachedVersion;
2598
+ }
2599
+ throw new Error(`Package metadata at ${packageJsonPath} does not declare a version`);
2600
+ }
2601
+ if (currentDir === root)
2602
+ break;
2603
+ currentDir = dirname(currentDir);
2604
+ }
2605
+ throw new Error("Unable to locate package.json for @hasna/models");
2584
2606
  }
2585
2607
 
2586
2608
  // src/auth.ts
2587
- import { mkdirSync, readFileSync, writeFileSync } from "fs";
2588
- import { dirname } from "path";
2609
+ import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
2610
+ import { dirname as dirname2 } from "path";
2589
2611
  import { spawnSync } from "child_process";
2590
2612
 
2591
2613
  // src/paths.ts
2592
2614
  import { homedir } from "os";
2593
- import { join } from "path";
2615
+ import { join as join2 } from "path";
2594
2616
  function getModelsHome() {
2595
- return process.env["HASNA_MODELS_HOME"] || join(homedir(), ".hasna", "models");
2617
+ return process.env["HASNA_MODELS_HOME"] || join2(homedir(), ".hasna", "models");
2596
2618
  }
2597
2619
  function getDbPath() {
2598
- return process.env["HASNA_MODELS_DB"] || join(getModelsHome(), "models.db");
2620
+ return process.env["HASNA_MODELS_DB"] || join2(getModelsHome(), "models.db");
2599
2621
  }
2600
2622
  function getAuthConfigPath() {
2601
- return join(getModelsHome(), "auth.json");
2623
+ return join2(getModelsHome(), "auth.json");
2602
2624
  }
2603
2625
  function getInstallRoot() {
2604
- return process.env["HASNA_MODELS_INSTALLS"] || join(getModelsHome(), "installs");
2626
+ return process.env["HASNA_MODELS_INSTALLS"] || join2(getModelsHome(), "installs");
2605
2627
  }
2606
2628
 
2607
2629
  // src/auth.ts
@@ -2619,14 +2641,14 @@ var DEFAULT_HF_SECRET_KEYS = [
2619
2641
  var cachedResolution = null;
2620
2642
  function readAuthConfig() {
2621
2643
  try {
2622
- return JSON.parse(readFileSync(getAuthConfigPath(), "utf8"));
2644
+ return JSON.parse(readFileSync2(getAuthConfigPath(), "utf8"));
2623
2645
  } catch {
2624
2646
  return {};
2625
2647
  }
2626
2648
  }
2627
2649
  function writeAuthConfig(config) {
2628
2650
  const path = getAuthConfigPath();
2629
- mkdirSync(dirname(path), { recursive: true });
2651
+ mkdirSync(dirname2(path), { recursive: true });
2630
2652
  writeFileSync(path, `${JSON.stringify(config, null, 2)}
2631
2653
  `);
2632
2654
  }
@@ -2779,11 +2801,20 @@ function safePathSegment(value) {
2779
2801
 
2780
2802
  // src/huggingface.ts
2781
2803
  import { createWriteStream, mkdirSync as mkdirSync2, renameSync, statSync, unlinkSync } from "fs";
2782
- import { dirname as dirname2, isAbsolute, join as join2, resolve, sep } from "path";
2804
+ import { dirname as dirname3, isAbsolute, join as join3, resolve, sep } from "path";
2783
2805
  import { pipeline } from "stream/promises";
2784
2806
  import { Readable } from "stream";
2785
2807
  import { randomUUID } from "crypto";
2786
2808
  var HF_ENDPOINT = process.env["HF_ENDPOINT"] || "https://huggingface.co";
2809
+
2810
+ class HuggingFaceApiError extends Error {
2811
+ status;
2812
+ constructor(response, body) {
2813
+ super(`Hugging Face request failed ${response.status} ${response.statusText}: ${body.slice(0, 300)}`);
2814
+ this.name = "HuggingFaceApiError";
2815
+ this.status = response.status;
2816
+ }
2817
+ }
2787
2818
  function apiBase() {
2788
2819
  return HF_ENDPOINT.replace(/\/+$/, "");
2789
2820
  }
@@ -2801,7 +2832,7 @@ async function hfJson(path, init) {
2801
2832
  });
2802
2833
  if (!response.ok) {
2803
2834
  const text = await response.text().catch(() => "");
2804
- throw new Error(`Hugging Face request failed ${response.status} ${response.statusText}: ${text.slice(0, 300)}`);
2835
+ throw new HuggingFaceApiError(response, text);
2805
2836
  }
2806
2837
  return response.json();
2807
2838
  }
@@ -2975,12 +3006,15 @@ async function getHuggingFaceInfo(refOrInput, defaultKind = "model") {
2975
3006
  }
2976
3007
  async function listHuggingFaceFiles(refOrInput, defaultKind = "model") {
2977
3008
  const ref = typeof refOrInput === "string" ? parseProviderRef(refOrInput, defaultKind) : refOrInput;
2978
- const treePath = `/api/${apiKind(ref.entityKind)}/${encodeRepoId(ref.repoId)}/tree/${encodeURIComponent(ref.revision)}?recursive=1&expand=1`;
3009
+ const revision = ref.revision || "main";
3010
+ const treePath = `/api/${apiKind(ref.entityKind)}/${encodeRepoId(ref.repoId)}/tree/${encodeURIComponent(revision)}?recursive=1&expand=1`;
2979
3011
  try {
2980
3012
  const raw = await hfJson(treePath);
2981
3013
  return raw.map((entry) => normalizeTreeFile(entry, ref)).filter((entry) => Boolean(entry));
2982
- } catch {
2983
- const info = await hfJson(`/api/${apiKind(ref.entityKind)}/${encodeRepoId(ref.repoId)}`);
3014
+ } catch (error) {
3015
+ if (!(error instanceof HuggingFaceApiError) || error.status !== 404)
3016
+ throw error;
3017
+ const info = await hfJson(`/api/${apiKind(ref.entityKind)}/${encodeRepoId(ref.repoId)}/revision/${encodeURIComponent(revision)}`);
2984
3018
  const siblings = Array.isArray(info.siblings) ? info.siblings : [];
2985
3019
  return siblings.map((entry) => normalizeSibling(entry, ref)).filter((entry) => Boolean(entry));
2986
3020
  }
@@ -3015,7 +3049,7 @@ async function createDownloadPlan(input) {
3015
3049
  const unknownSizeFiles = files.filter((file) => file.size == null).map((file) => file.path);
3016
3050
  const totalBytes = unknownSizeFiles.length > 0 ? null : knownBytes;
3017
3051
  const maxBytes = input.maxBytes ?? null;
3018
- const destinationRoot = input.destinationRoot ?? join2(getInstallRoot(), input.ref.provider, input.ref.entityKind, safePathSegment(input.ref.repoId), safePathSegment(input.ref.revision));
3052
+ const destinationRoot = input.destinationRoot ?? join3(getInstallRoot(), input.ref.provider, input.ref.entityKind, safePathSegment(input.ref.repoId), safePathSegment(input.ref.revision));
3019
3053
  return {
3020
3054
  ref: input.ref,
3021
3055
  files,
@@ -3037,7 +3071,7 @@ async function downloadPlannedFiles(plan) {
3037
3071
  for (const file of plan.files) {
3038
3072
  const destination = safeDestinationPath(plan.destinationRoot, file.path);
3039
3073
  const tempDestination = `${destination}.partial-${randomUUID()}`;
3040
- mkdirSync2(dirname2(destination), { recursive: true });
3074
+ mkdirSync2(dirname3(destination), { recursive: true });
3041
3075
  const response = await fetch(file.downloadUrl, { headers: headers(), redirect: "follow" });
3042
3076
  if (!response.ok || !response.body) {
3043
3077
  const text = await response.text().catch(() => "");
@@ -3063,14 +3097,14 @@ async function downloadPlannedFiles(plan) {
3063
3097
 
3064
3098
  // src/storage.ts
3065
3099
  import { mkdirSync as mkdirSync3 } from "fs";
3066
- import { dirname as dirname3 } from "path";
3100
+ import { dirname as dirname4 } from "path";
3067
3101
  import { Database } from "bun:sqlite";
3068
3102
  var SCHEMA_VERSION = 1;
3069
3103
 
3070
3104
  class ModelsStore {
3071
3105
  db;
3072
3106
  constructor(path = getDbPath()) {
3073
- mkdirSync3(dirname3(path), { recursive: true });
3107
+ mkdirSync3(dirname4(path), { recursive: true });
3074
3108
  this.db = new Database(path, { create: true });
3075
3109
  this.db.run("PRAGMA busy_timeout = 5000");
3076
3110
  this.db.run("PRAGMA journal_mode = WAL");
@@ -3616,7 +3650,7 @@ program2.command("remove").argument("<id-or-repo>", "install id or repo id").des
3616
3650
  printResult(result, `would remove metadata for ${install.id}${opts.files ? ` and files at ${install.installPath}` : ""}`, opts);
3617
3651
  return;
3618
3652
  }
3619
- if (opts.files && existsSync(install.installPath)) {
3653
+ if (opts.files && existsSync2(install.installPath)) {
3620
3654
  rmSync(install.installPath, { recursive: true, force: true });
3621
3655
  }
3622
3656
  store.deleteInstall(install.id);
@@ -3700,7 +3734,7 @@ program2.command("doctor").description("Check local store, auth, and basic runti
3700
3734
  const auth = redactAuthStatus(getHuggingFaceAuthStatus());
3701
3735
  const checks = [
3702
3736
  { id: "data-dir", status: "ok", detail: dataDir },
3703
- { id: "sqlite", status: existsSync(dbPath) ? "ok" : "warn", detail: dbPath },
3737
+ { id: "sqlite", status: existsSync2(dbPath) ? "ok" : "warn", detail: dbPath },
3704
3738
  { id: "huggingface-auth", status: auth.available ? "ok" : "warn", detail: auth.available ? `token available via ${auth.source}` : "anonymous Hub access only" },
3705
3739
  { id: "catalog", status: "ok", detail: JSON.stringify(store.catalogStats()) }
3706
3740
  ];
@@ -3732,11 +3766,11 @@ program2.command("manual").description("Print the local command manual").option(
3732
3766
  `), opts);
3733
3767
  });
3734
3768
  program2.command("goals").description("Print the implementation goal chain").option("-j, --json", "output JSON").action((opts) => {
3735
- const cliDir = dirname4(fileURLToPath(import.meta.url));
3736
- const path = join3(cliDir, "..", "..", "docs", "GOALS.md");
3737
- const text = existsSync(path) ? readFileSync2(path, "utf8") : "Goal chain not found.";
3769
+ const cliDir = dirname5(fileURLToPath2(import.meta.url));
3770
+ const path = join4(cliDir, "..", "..", "docs", "GOALS.md");
3771
+ const text = existsSync2(path) ? readFileSync3(path, "utf8") : "Goal chain not found.";
3738
3772
  if (isJson(opts)) {
3739
- printJson({ path, bytes: existsSync(path) ? statSync2(path).size : 0, text });
3773
+ printJson({ path, bytes: existsSync2(path) ? statSync2(path).size : 0, text });
3740
3774
  } else {
3741
3775
  console.log(text);
3742
3776
  }
package/dist/index.js CHANGED
@@ -430,6 +430,15 @@ import { pipeline } from "stream/promises";
430
430
  import { Readable } from "stream";
431
431
  import { randomUUID } from "crypto";
432
432
  var HF_ENDPOINT = process.env["HF_ENDPOINT"] || "https://huggingface.co";
433
+
434
+ class HuggingFaceApiError extends Error {
435
+ status;
436
+ constructor(response, body) {
437
+ super(`Hugging Face request failed ${response.status} ${response.statusText}: ${body.slice(0, 300)}`);
438
+ this.name = "HuggingFaceApiError";
439
+ this.status = response.status;
440
+ }
441
+ }
433
442
  function apiBase() {
434
443
  return HF_ENDPOINT.replace(/\/+$/, "");
435
444
  }
@@ -447,7 +456,7 @@ async function hfJson(path, init) {
447
456
  });
448
457
  if (!response.ok) {
449
458
  const text = await response.text().catch(() => "");
450
- throw new Error(`Hugging Face request failed ${response.status} ${response.statusText}: ${text.slice(0, 300)}`);
459
+ throw new HuggingFaceApiError(response, text);
451
460
  }
452
461
  return response.json();
453
462
  }
@@ -621,12 +630,15 @@ async function getHuggingFaceInfo(refOrInput, defaultKind = "model") {
621
630
  }
622
631
  async function listHuggingFaceFiles(refOrInput, defaultKind = "model") {
623
632
  const ref = typeof refOrInput === "string" ? parseProviderRef(refOrInput, defaultKind) : refOrInput;
624
- const treePath = `/api/${apiKind(ref.entityKind)}/${encodeRepoId(ref.repoId)}/tree/${encodeURIComponent(ref.revision)}?recursive=1&expand=1`;
633
+ const revision = ref.revision || "main";
634
+ const treePath = `/api/${apiKind(ref.entityKind)}/${encodeRepoId(ref.repoId)}/tree/${encodeURIComponent(revision)}?recursive=1&expand=1`;
625
635
  try {
626
636
  const raw = await hfJson(treePath);
627
637
  return raw.map((entry) => normalizeTreeFile(entry, ref)).filter((entry) => Boolean(entry));
628
- } catch {
629
- const info = await hfJson(`/api/${apiKind(ref.entityKind)}/${encodeRepoId(ref.repoId)}`);
638
+ } catch (error) {
639
+ if (!(error instanceof HuggingFaceApiError) || error.status !== 404)
640
+ throw error;
641
+ const info = await hfJson(`/api/${apiKind(ref.entityKind)}/${encodeRepoId(ref.repoId)}/revision/${encodeURIComponent(revision)}`);
630
642
  const siblings = Array.isArray(info.siblings) ? info.siblings : [];
631
643
  return siblings.map((entry) => normalizeSibling(entry, ref)).filter((entry) => Boolean(entry));
632
644
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/models",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "CLI-first local model and dataset lifecycle tool for open-source/open-weight catalogs",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",