Branch feat/library-v1 off origin/main (8a77678, includes #26 pull/verify, #28 model library, #32 harbor refresh).
No subagents; each unit builds, tests, commits before the next.
- Shelf (
internal/models/shelf.go):type Shelf string,chat|image|audio|""unknown. Resolution order: config root override > file facts > name hints > unknown. Never a guess. File facts: GGUF arch (LLM archs and embeddings and clip/mmproj -> chat; whisper -> audio; SD-family -> image), safetensors header tensor names (lora_,diffusion_model.,first_stage_model./vae-> image;language_model.-> chat),config.jsonmodel_type, Ollama/LM Studio stores -> chat. - Root (
internal/storage):Info(path)->Root{Path, FS, Total, Free, Used, Supports{Hardlink, Symlink, Reflink}}. Linux:unix.Statfsf_type magic table (ext4, btrfs, ntfs3, exfat, xfs, overlay, tmpfs, zfs, f2fs; else "unknown"). Non-linux stub: FS "unknown", zero sizes, no support claims (floor check then warns and is skipped: measured, never assumed). Support table is per-FS, static. - Floor:
Root.UnderFloor(need, frac)=Free < need + Total*frac. Config[library] free_flooris a fraction (0 disables); default 0.10.berth pullrefuses before downloading when the root would drop under it; roots where space cannot be measured warn and proceed (fail open, never a false reservation claim). - Manifest (
internal/manifest, replacesinternal/hf/manifest.go):Manifest{Source, Name, Ref, Files, Pulled}, still written as.berth-manifest.jsonbeside the files. Source "hf" | "civitai"; a manifest with no source normalizes to "hf" (legacy files on disk).Verifyunchanged in shape. - Catalog (
internal/models/catalog.go):<root>/.berth/catalog.json= one root's scan snapshot{root, host, at, models:[{name, path, shelf, bytes}]}. Written best-effort after a scan (never fatal: read-only roots skip it); read back byberth storageso the sibling boot on a shared root sees what this boot resolved. The scan recomputes from files and never depends on the catalog. - CivitAI (
internal/civitai):Endpoint()(env CIVITAI_ENDPOINT),ParseRefforcivitai:<version-id>,civitai:model:<model-id>(latest version), plain version-id, andcivitai.com/model-versions/<id>URLs. Client:Search,ModelVersions,Version(files carry size, sha256 hash and downloadUrl),Downloadthrough the sharedinternal/dltransfer (part-file resume, size + sha256 verification), token fromCIVITAI_API_KEYvia--token-env. - dl (
internal/dl):Fetch(ctx, FetchSpec{URL, Size, SHA256, PartPath, Token, Prog})= one resume attempt (open part, Range from current size, size check, sha check). hf's retry loop keeps owning its backoff; civitai does the same around the same call.
[library]
free_floor = 0.10 # fraction of a root's total space that stays free; 0 disables the guard
[[library.root]]
path = "/mnt/windows/ComfyUI/models"
shelf = "image" # chat | image | audio; declared shelf beats inference for the whole root
shared = "windows" # the other OS this root is shared with; "" = local only[models] dirs stays the root list; [library] adds the knobs. Root overrides match by absolute path.
berth storage(new): roots from--dir, else[models] dirs+[library] rootpaths, else defaults. Per root: filesystem, total/free/used with percent, shared-with, hardlink/symlink/reflink, per-shelf bytes and model counts (scanned), floor verdict, in-root catalog age and host. Exit 1 when any root sits under its floor.berth civitai search QUERY [--type T]... [--limit N](new group) andberth civitai versions MODEL_ID: discovery only; downloading stays inberth pull.berth pull civitai:<ref>: resolves the version, floors the destination root, downloads with resume and sha256, writes the shared manifest intoDIR/civitai/<name-slug>/.berth pull hf:...gains the same floor guard.berth models --shelf chat|image|audio|unknown: filter; the listing gains a SHELF column and the footer totals per shelf; each scanned root gets its.berth/catalog written (best effort).showprints the shelf.
internal/manifest: unified manifest + legacy normalization; hf and cli callers move over.internal/storage: Root/Info/supports/floor, linux + non-linux files, tests.internal/modelsshelf + safetensors header reader;Model.Shelffilled in every scan path; tests.internal/modelscatalog read/write; tests.internal/config[library]section + validation + floor accessor; tests.internal/dlshared transfer; hf refactored onto it (its tests unchanged); resume test.internal/civitaiclient + tests against httptest (search, versions, version, ranged download).berth storagecommand + tests.berth civitai search|versions+ tests.berth pull: civitai dispatch + floor guard on both sources + new manifest; tests.berth models/show: shelf column, filter, totals, catalog write; tests.- README + help text; full proof: build, gofmt, vet, tests, golangci-lint, windows cross-build.
- Floor guard is a refusal, not a
--forceoverride: the no-disk-fills rule is absolute (GUIDED-VISION principle 6). - The catalog is a shared snapshot, not a cache: a stale one is harmless because the scan recomputes.
- CivitAI search needs the API key only for gated material; public search works keyless, same as HF.