@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 +62 -28
- package/dist/index.js +16 -4
- package/package.json +1 -1
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
|
|
2578
|
-
import { dirname as
|
|
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
|
-
|
|
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"] ||
|
|
2617
|
+
return process.env["HASNA_MODELS_HOME"] || join2(homedir(), ".hasna", "models");
|
|
2596
2618
|
}
|
|
2597
2619
|
function getDbPath() {
|
|
2598
|
-
return process.env["HASNA_MODELS_DB"] ||
|
|
2620
|
+
return process.env["HASNA_MODELS_DB"] || join2(getModelsHome(), "models.db");
|
|
2599
2621
|
}
|
|
2600
2622
|
function getAuthConfigPath() {
|
|
2601
|
-
return
|
|
2623
|
+
return join2(getModelsHome(), "auth.json");
|
|
2602
2624
|
}
|
|
2603
2625
|
function getInstallRoot() {
|
|
2604
|
-
return process.env["HASNA_MODELS_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(
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 ??
|
|
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(
|
|
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
|
|
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(
|
|
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 &&
|
|
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:
|
|
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 =
|
|
3736
|
-
const path =
|
|
3737
|
-
const text =
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
}
|