noosphere 0.7.0 → 0.8.0

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/README.md CHANGED
@@ -6,11 +6,14 @@ One import. Every model. Every modality.
6
6
 
7
7
  ## Features
8
8
 
9
- - **4 modalities** — LLM chat, image generation, video generation, and text-to-speech
9
+ - **7 modalities** — LLM, image, video, TTS, STT, music, and embeddings
10
10
  - **Always up-to-date models** — Dynamic auto-fetch from ALL provider APIs at runtime (OpenAI, Anthropic, Google, Groq, Mistral, xAI, Cerebras, OpenRouter)
11
+ - **Dynamic descriptions** — Model descriptions fetched from source (Ollama library, HuggingFace READMEs, CivitAI API) — no hardcoded strings
12
+ - **Modality-filtered sync** — `syncModels('llm')` only fetches LLM providers, avoiding unnecessary requests
11
13
  - **867+ media endpoints** — via FAL (Flux, SDXL, Kling, Sora 2, VEO 3, Kokoro, ElevenLabs, and hundreds more)
12
14
  - **30+ HuggingFace tasks** — LLM, image, TTS, translation, summarization, classification, and more
13
- - **Local-first architecture** — Auto-detects ComfyUI, Ollama, Piper, and Kokoro on your machine
15
+ - **Local-first architecture** — Auto-detects Ollama, ComfyUI, Whisper, AudioCraft, Piper, and Kokoro on your machine
16
+ - **Org-aware logos** — HuggingFace models show the real org logo (Meta, Google, NVIDIA) instead of generic HF logo
14
17
  - **Agentic capabilities** — Tool use, function calling, reasoning/thinking, vision, and agent loops via Pi-AI
15
18
  - **Failover & retry** — Automatic retries with exponential backoff and cross-provider failover
16
19
  - **Usage tracking** — Real-time cost, latency, and token tracking across all providers
@@ -366,13 +369,45 @@ await ai.uninstallModel('deepseek-r1:14b');
366
369
  |---|---|---|---|---|
367
370
  | **pi-ai** | LLM | 482 | OpenAI, Anthropic, Google, Groq, Mistral, xAI, OpenRouter, Cerebras | API keys |
368
371
  | **ollama** | LLM, embedding | 70 | 38 installed + 32 from Ollama web catalog | `localhost:11434` |
369
- | **hf-local** | image, video, tts, stt | 220 | HuggingFace catalog (FLUX, SDXL, Wan2.2, Whisper, MusicGen) | Always |
372
+ | **hf-local** | image, video, tts, stt, music | 220 | HuggingFace catalog (FLUX, SDXL, Wan2.2, Whisper, MusicGen) | Always (no API key) |
373
+ | **huggingface** | LLM, image, tts | dynamic | HuggingFace Inference API | `HUGGINGFACE_TOKEN` |
370
374
  | **comfyui** | image, video | dynamic | Installed checkpoints + CivitAI catalog | `localhost:8188` |
371
375
  | **openai-compat** | LLM | dynamic | llama.cpp, LM Studio, vLLM, LocalAI, KoboldCpp, Jan, TabbyAPI | Scans ports |
376
+ | **fal** | image, video, tts | 867+ | FAL.ai (Flux, SDXL, Kling, Sora 2, Kokoro, ElevenLabs) | `FAL_KEY` |
372
377
  | **piper** | TTS | 2+ | Piper voices installed locally | Binary detection |
373
378
  | **whisper-local** | STT | 8 | Whisper/Faster-Whisper (tiny → large-v3) | Python detection |
374
379
  | **audiocraft** | music | 5 | MusicGen (small/medium/large/melody) + AudioGen | Python detection |
375
380
 
381
+ ### Modality-Filtered Sync — Only Fetch What You Need
382
+
383
+ Sync **only the providers relevant to a specific modality** instead of fetching everything. This avoids unnecessary network requests (e.g., fetching 270+ HuggingFace READMEs when you only need LLMs).
384
+
385
+ ```typescript
386
+ // Sync only LLM providers (Ollama, pi-ai, openai-compat, huggingface)
387
+ await ai.syncModels('llm');
388
+
389
+ // Sync only image providers (hf-local, comfyui, fal, huggingface)
390
+ await ai.syncModels('image');
391
+
392
+ // Sync only STT providers (whisper-local, hf-local)
393
+ await ai.syncModels('stt');
394
+
395
+ // Sync everything (backward compatible)
396
+ await ai.syncModels();
397
+ ```
398
+
399
+ **Which providers sync for each modality:**
400
+
401
+ | Modality | Providers Synced |
402
+ |---|---|
403
+ | `llm` | pi-ai, ollama, openai-compat, huggingface (cloud, needs API key) |
404
+ | `image` | hf-local, comfyui, fal, huggingface (cloud) |
405
+ | `video` | hf-local, comfyui, fal |
406
+ | `tts` | hf-local (speech models), fal, piper, kokoro, huggingface (cloud) |
407
+ | `stt` | hf-local, whisper-local |
408
+ | `music` | hf-local (MusicGen, AudioLDM, etc.), audiocraft |
409
+ | `embedding` | ollama |
410
+
376
411
  ### Models by Modality
377
412
 
378
413
  ```typescript
@@ -478,6 +513,38 @@ const comfyModels = models.filter(m => m.provider === 'comfyui');
478
513
  const civitai = comfyModels.filter(m => m.status === 'available');
479
514
  ```
480
515
 
516
+ ### Model Descriptions — Dynamic from Source
517
+
518
+ Every model includes a `description` field fetched dynamically from its source — no hardcoded strings:
519
+
520
+ ```typescript
521
+ const models = await ai.getModels('llm');
522
+
523
+ for (const m of models) {
524
+ console.log(m.name, m.description);
525
+ // "llama3.1" "Llama 3.1 is a new state-of-the-art model from Meta available in 8B, 70B and 405B"
526
+ // "qwen3" "Qwen3 is the latest generation of large language models in Qwen series"
527
+ // "gemma3" "The current, most capable model that runs on a single GPU"
528
+ }
529
+
530
+ const imageModels = await ai.getModels('image');
531
+ for (const m of imageModels) {
532
+ console.log(m.name, m.description);
533
+ // "stable-diffusion-xl-base-1.0" "Stable Diffusion XL (SDXL) is a latent text-to-image..."
534
+ // "FLUX.1-dev" "FLUX.1 [dev] is a 12 billion parameter rectified flow..."
535
+ }
536
+ ```
537
+
538
+ | Provider | Description Source |
539
+ |---|---|
540
+ | **Ollama** | Scraped from `ollama.com/library` page |
541
+ | **HuggingFace Local** | Parsed from each model's `README.md` on HuggingFace Hub |
542
+ | **CivitAI/ComfyUI** | Extracted from CivitAI API response |
543
+ | **Whisper** | Parsed from OpenAI's Whisper README on HuggingFace |
544
+ | **AudioCraft** | Parsed from Meta's AudioCraft README on HuggingFace |
545
+
546
+ All description fetches are **parallel and fail-safe** — if a source is unreachable, models are returned without descriptions. No API keys required.
547
+
481
548
  ### Model Status & Local Info
482
549
 
483
550
  Every local model includes rich metadata:
@@ -486,6 +553,8 @@ Every local model includes rich metadata:
486
553
  interface ModelInfo {
487
554
  id: string;
488
555
  provider: string;
556
+ name: string;
557
+ description?: string; // Dynamic from source (Ollama library, HF README, CivitAI)
489
558
  modality: 'llm' | 'image' | 'video' | 'tts' | 'stt' | 'music' | 'embedding';
490
559
  status?: 'installed' | 'available' | 'downloading' | 'running' | 'error';
491
560
  local: boolean;
package/dist/index.cjs CHANGED
@@ -342,20 +342,22 @@ var Registry = class {
342
342
  const cached = this.modelCache.get(provider);
343
343
  return cached?.models.find((m) => m.id === modelId) ?? null;
344
344
  }
345
- async syncProvider(providerId) {
345
+ async syncProvider(providerId, modality) {
346
346
  const provider = this.providers.get(providerId);
347
347
  if (!provider) return 0;
348
- const models = await provider.listModels();
348
+ if (modality && !provider.modalities.includes(modality)) return 0;
349
+ const models = await provider.listModels(modality);
349
350
  this.modelCache.set(providerId, { models, syncedAt: Date.now() });
350
351
  return models.length;
351
352
  }
352
- async syncAll() {
353
+ async syncAll(modality) {
353
354
  const byProvider = {};
354
355
  const errors = [];
355
356
  let synced = 0;
356
357
  for (const provider of this.providers.values()) {
358
+ if (modality && !provider.modalities.includes(modality)) continue;
357
359
  try {
358
- const count = await this.syncProvider(provider.id);
360
+ const count = await this.syncProvider(provider.id, modality);
359
361
  byProvider[provider.id] = count;
360
362
  synced += count;
361
363
  } catch (err) {
@@ -1140,6 +1142,7 @@ var ComfyUIProvider = class {
1140
1142
  id: `civitai-${item.id}`,
1141
1143
  provider: "comfyui",
1142
1144
  name: item.name ?? `CivitAI Model ${item.id}`,
1145
+ description: item.description ? item.description.replace(/<[^>]+>/g, "").trim().slice(0, 300) || void 0 : void 0,
1143
1146
  modality: "image",
1144
1147
  local: true,
1145
1148
  cost: { price: 0, unit: "free" },
@@ -1578,6 +1581,28 @@ async function fetchJson(url, options) {
1578
1581
  clearTimeout(timer);
1579
1582
  }
1580
1583
  }
1584
+ async function fetchOllamaDescriptions() {
1585
+ const controller = new AbortController();
1586
+ const timer = setTimeout(() => controller.abort(), 5e3);
1587
+ try {
1588
+ const res = await fetch("https://ollama.com/library", { signal: controller.signal });
1589
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
1590
+ const html = await res.text();
1591
+ const descriptions = /* @__PURE__ */ new Map();
1592
+ const cardRegex = /href="\/library\/([^"]+)"[\s\S]*?<p[^>]*>([\s\S]*?)<\/p>/g;
1593
+ let match;
1594
+ while ((match = cardRegex.exec(html)) !== null) {
1595
+ const modelName = match[1].trim();
1596
+ const desc = match[2].replace(/<[^>]*>/g, "").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/\s+/g, " ").trim();
1597
+ if (modelName && desc) {
1598
+ descriptions.set(modelName, desc);
1599
+ }
1600
+ }
1601
+ return descriptions;
1602
+ } finally {
1603
+ clearTimeout(timer);
1604
+ }
1605
+ }
1581
1606
  var OllamaProvider = class {
1582
1607
  id = "ollama";
1583
1608
  name = "Ollama (Local)";
@@ -1601,10 +1626,11 @@ var OllamaProvider = class {
1601
1626
  }
1602
1627
  async listModels(_modality) {
1603
1628
  if (_modality && _modality !== "llm") return [];
1604
- const [localData, catalogData, runningData] = await Promise.all([
1629
+ const [localData, catalogData, runningData, descriptions] = await Promise.all([
1605
1630
  fetchJson(`${this.baseUrl}/api/tags`, { timeoutMs: 5e3 }).catch(() => null),
1606
1631
  fetchJson("https://ollama.com/api/tags", { timeoutMs: 5e3 }).catch(() => null),
1607
- fetchJson(`${this.baseUrl}/api/ps`, { timeoutMs: 5e3 }).catch(() => null)
1632
+ fetchJson(`${this.baseUrl}/api/ps`, { timeoutMs: 5e3 }).catch(() => null),
1633
+ fetchOllamaDescriptions().catch(() => /* @__PURE__ */ new Map())
1608
1634
  ]);
1609
1635
  const runningNames = /* @__PURE__ */ new Set();
1610
1636
  if (runningData?.models) {
@@ -1617,27 +1643,30 @@ var OllamaProvider = class {
1617
1643
  if (localData?.models) {
1618
1644
  for (const m of localData.models) {
1619
1645
  const isRunning = runningNames.has(m.name) || runningNames.has(m.model);
1620
- models.set(m.name, this.toModelInfo(m, isRunning ? "running" : "installed", true));
1646
+ models.set(m.name, this.toModelInfo(m, isRunning ? "running" : "installed", true, descriptions));
1621
1647
  }
1622
1648
  }
1623
1649
  if (catalogData?.models) {
1624
1650
  for (const m of catalogData.models) {
1625
1651
  const name = m.name;
1626
1652
  if (!models.has(name)) {
1627
- models.set(name, this.toModelInfo(m, "available", false));
1653
+ models.set(name, this.toModelInfo(m, "available", false, descriptions));
1628
1654
  }
1629
1655
  }
1630
1656
  }
1631
1657
  return Array.from(models.values());
1632
1658
  }
1633
- toModelInfo(m, status, isLocal) {
1659
+ toModelInfo(m, status, isLocal, descriptions) {
1634
1660
  const name = m.name ?? m.model ?? "unknown";
1635
1661
  const family = m.details?.family;
1636
1662
  const logoProvider = inferLogoProvider(name, family);
1663
+ const baseName = name.split(":")[0];
1664
+ const description = descriptions?.get(baseName);
1637
1665
  return {
1638
1666
  id: name,
1639
1667
  provider: "ollama",
1640
1668
  name,
1669
+ ...description ? { description } : {},
1641
1670
  modality: "llm",
1642
1671
  local: true,
1643
1672
  cost: { price: 0, unit: "free" },
@@ -1846,16 +1875,79 @@ var OllamaProvider = class {
1846
1875
  }
1847
1876
  };
1848
1877
 
1878
+ // src/utils/parse-readme.ts
1879
+ async function fetchReadmeDescription(modelId, timeoutMs = 5e3) {
1880
+ try {
1881
+ const controller = new AbortController();
1882
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
1883
+ try {
1884
+ const res = await fetch(
1885
+ `https://huggingface.co/${modelId}/raw/main/README.md`,
1886
+ { signal: controller.signal }
1887
+ );
1888
+ if (!res.ok) return void 0;
1889
+ const text = await res.text();
1890
+ return parseReadmeDescription(text);
1891
+ } finally {
1892
+ clearTimeout(timer);
1893
+ }
1894
+ } catch {
1895
+ return void 0;
1896
+ }
1897
+ }
1898
+ function parseReadmeDescription(readme) {
1899
+ const withoutFrontmatter = readme.replace(/^---[\s\S]*?---\s*/, "");
1900
+ const lines = withoutFrontmatter.split("\n");
1901
+ let paragraph = "";
1902
+ for (const line of lines) {
1903
+ const trimmed = line.trim();
1904
+ if (!trimmed) {
1905
+ if (paragraph) break;
1906
+ continue;
1907
+ }
1908
+ if (trimmed.startsWith("#")) {
1909
+ if (paragraph) break;
1910
+ continue;
1911
+ }
1912
+ if (/^\[?!\[/.test(trimmed) || /^</.test(trimmed)) continue;
1913
+ if (/^\[.*\]\(.*\)$/.test(trimmed)) continue;
1914
+ paragraph += (paragraph ? " " : "") + trimmed;
1915
+ }
1916
+ if (!paragraph) return void 0;
1917
+ paragraph = paragraph.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/<[^>]+>/g, "").trim();
1918
+ if (!paragraph) return void 0;
1919
+ if (paragraph.length > 300) paragraph = paragraph.slice(0, 297) + "...";
1920
+ return paragraph;
1921
+ }
1922
+
1849
1923
  // src/providers/hf-local.ts
1850
1924
  var import_promises = require("fs/promises");
1851
1925
  var import_node_path = require("path");
1852
1926
  var import_node_os = require("os");
1853
1927
  var FETCH_TIMEOUT_MS3 = 5e3;
1854
1928
  var HF_HUB_API2 = "https://huggingface.co/api/models";
1929
+ var HF_ORG_TO_LOGO_PROVIDER = {
1930
+ "meta-llama": "meta",
1931
+ "facebook": "meta",
1932
+ "google": "google",
1933
+ "microsoft": "microsoft",
1934
+ "nvidia": "nvidia",
1935
+ "mistralai": "mistral",
1936
+ "Qwen": "qwen",
1937
+ "deepseek-ai": "deepseek",
1938
+ "openai": "openai",
1939
+ "CohereForAI": "cohere",
1940
+ "rhasspy": "piper",
1941
+ "stabilityai": "huggingface",
1942
+ "black-forest-labs": "huggingface",
1943
+ "tiiuae": "huggingface",
1944
+ "allenai": "huggingface",
1945
+ "Salesforce": "huggingface"
1946
+ };
1855
1947
  var PIPELINE_TAG_TO_MODALITY = {
1856
1948
  "text-to-image": "image",
1857
1949
  "text-to-video": "video",
1858
- "text-to-audio": "tts",
1950
+ "text-to-audio": "music",
1859
1951
  "text-to-speech": "tts",
1860
1952
  "automatic-speech-recognition": "stt"
1861
1953
  };
@@ -1883,7 +1975,7 @@ async function fetchJsonTimeout(url, timeoutMs = FETCH_TIMEOUT_MS3) {
1883
1975
  var HfLocalProvider = class {
1884
1976
  id = "hf-local";
1885
1977
  name = "HuggingFace Local Models";
1886
- modalities = ["image", "video", "tts", "stt"];
1978
+ modalities = ["image", "video", "tts", "stt", "music"];
1887
1979
  isLocal = true;
1888
1980
  cachedModels = null;
1889
1981
  async ping() {
@@ -1905,8 +1997,7 @@ var HfLocalProvider = class {
1905
1997
  }
1906
1998
  async fetchCatalog() {
1907
1999
  const seen = /* @__PURE__ */ new Set();
1908
- const models = [];
1909
- const logo = getProviderLogo("huggingface");
2000
+ const entries = [];
1910
2001
  const results = await Promise.allSettled(
1911
2002
  CATALOG_QUERIES.map(async (q) => {
1912
2003
  const params = new URLSearchParams({
@@ -1924,32 +2015,56 @@ var HfLocalProvider = class {
1924
2015
  const id = entry.id ?? entry.modelId;
1925
2016
  if (!id || seen.has(id)) continue;
1926
2017
  seen.add(id);
1927
- const pipelineTag = entry.pipeline_tag ?? "";
1928
- const modality = PIPELINE_TAG_TO_MODALITY[pipelineTag] ?? "image";
1929
- models.push({
2018
+ entries.push({
1930
2019
  id,
1931
- provider: "hf-local",
1932
- name: id.split("/").pop() ?? id,
1933
- modality,
1934
- local: true,
1935
- cost: { price: 0, unit: "free" },
1936
- logo,
1937
- status: "available",
1938
- localInfo: {
1939
- sizeBytes: 0,
1940
- runtime: "huggingface",
1941
- family: entry.library_name
1942
- },
1943
- capabilities: {}
2020
+ pipelineTag: entry.pipeline_tag ?? "",
2021
+ libraryName: entry.library_name
1944
2022
  });
1945
2023
  }
1946
2024
  }
2025
+ const descriptionMap = /* @__PURE__ */ new Map();
2026
+ for (let i = 0; i < entries.length; i += 10) {
2027
+ const batch = entries.slice(i, i + 10);
2028
+ const descs = await Promise.allSettled(
2029
+ batch.map(async (e) => {
2030
+ const desc = await fetchReadmeDescription(e.id);
2031
+ return { id: e.id, desc };
2032
+ })
2033
+ );
2034
+ for (const d of descs) {
2035
+ if (d.status === "fulfilled" && d.value.desc) {
2036
+ descriptionMap.set(d.value.id, d.value.desc);
2037
+ }
2038
+ }
2039
+ }
2040
+ const models = [];
2041
+ for (const e of entries) {
2042
+ const modality = PIPELINE_TAG_TO_MODALITY[e.pipelineTag] ?? "image";
2043
+ const org = e.id.includes("/") ? e.id.split("/")[0] : void 0;
2044
+ const logoProvider = org ? HF_ORG_TO_LOGO_PROVIDER[org] ?? "huggingface" : "huggingface";
2045
+ models.push({
2046
+ id: e.id,
2047
+ provider: "hf-local",
2048
+ name: e.id.split("/").pop() ?? e.id,
2049
+ modality,
2050
+ local: true,
2051
+ cost: { price: 0, unit: "free" },
2052
+ logo: getProviderLogo(logoProvider),
2053
+ description: descriptionMap.get(e.id),
2054
+ status: "available",
2055
+ localInfo: {
2056
+ sizeBytes: 0,
2057
+ runtime: "huggingface",
2058
+ family: e.libraryName
2059
+ },
2060
+ capabilities: {}
2061
+ });
2062
+ }
1947
2063
  return models;
1948
2064
  }
1949
2065
  async scanLocalCache() {
1950
2066
  const models = [];
1951
2067
  const cacheDir = (0, import_node_path.join)((0, import_node_os.homedir)(), ".cache", "huggingface", "hub");
1952
- const logo = getProviderLogo("huggingface");
1953
2068
  try {
1954
2069
  const entries = await (0, import_promises.readdir)(cacheDir, { withFileTypes: true });
1955
2070
  for (const entry of entries) {
@@ -1981,6 +2096,8 @@ var HfLocalProvider = class {
1981
2096
  }
1982
2097
  }
1983
2098
  const modality = PIPELINE_TAG_TO_MODALITY[pipelineTag] ?? "image";
2099
+ const org = modelId.includes("/") ? modelId.split("/")[0] : void 0;
2100
+ const logoProvider = org ? HF_ORG_TO_LOGO_PROVIDER[org] ?? "huggingface" : "huggingface";
1984
2101
  models.push({
1985
2102
  id: modelId,
1986
2103
  provider: "hf-local",
@@ -1988,7 +2105,7 @@ var HfLocalProvider = class {
1988
2105
  modality,
1989
2106
  local: true,
1990
2107
  cost: { price: 0, unit: "free" },
1991
- logo,
2108
+ logo: getProviderLogo(logoProvider),
1992
2109
  status: "installed",
1993
2110
  localInfo: {
1994
2111
  sizeBytes: 0,
@@ -2009,6 +2126,25 @@ var import_promises2 = require("fs/promises");
2009
2126
  var import_node_path2 = require("path");
2010
2127
  var import_node_os2 = require("os");
2011
2128
  var WHISPER_MODELS = ["tiny", "base", "small", "medium", "large", "large-v2", "large-v3", "turbo"];
2129
+ var WHISPER_HF_REPOS = {
2130
+ "tiny": "openai/whisper-tiny",
2131
+ "base": "openai/whisper-base",
2132
+ "small": "openai/whisper-small",
2133
+ "medium": "openai/whisper-medium",
2134
+ "large": "openai/whisper-large",
2135
+ "large-v2": "openai/whisper-large-v2",
2136
+ "large-v3": "openai/whisper-large-v3",
2137
+ "turbo": "openai/whisper-large-v3-turbo"
2138
+ };
2139
+ async function fetchWhisperDescriptions() {
2140
+ const descriptions = /* @__PURE__ */ new Map();
2141
+ const fetches = Object.entries(WHISPER_HF_REPOS).map(async ([size, repo]) => {
2142
+ const desc = await fetchReadmeDescription(repo, 8e3);
2143
+ if (desc) descriptions.set(size, desc);
2144
+ });
2145
+ await Promise.allSettled(fetches);
2146
+ return descriptions;
2147
+ }
2012
2148
  function runPython(code, timeoutMs = 5e3) {
2013
2149
  return new Promise((resolve, reject) => {
2014
2150
  const proc = (0, import_node_child_process.execFile)("python3", ["-c", code], { timeout: timeoutMs }, (err, stdout) => {
@@ -2054,7 +2190,8 @@ var WhisperLocalProvider = class {
2054
2190
  if (_modality && _modality !== "stt") return [];
2055
2191
  const runtime = await this.detectRuntime();
2056
2192
  if (!runtime) return [];
2057
- const logo = getProviderLogo("huggingface");
2193
+ const descMap = await fetchWhisperDescriptions().catch(() => /* @__PURE__ */ new Map());
2194
+ const logo = getProviderLogo("openai");
2058
2195
  const models = [];
2059
2196
  for (const name of WHISPER_MODELS) {
2060
2197
  const installed = await this.isModelCached(name, runtime);
@@ -2062,6 +2199,7 @@ var WhisperLocalProvider = class {
2062
2199
  id: `whisper-${name}`,
2063
2200
  provider: "whisper-local",
2064
2201
  name: `Whisper ${name}`,
2202
+ description: descMap.get(name),
2065
2203
  modality: "stt",
2066
2204
  local: true,
2067
2205
  cost: { price: 0, unit: "free" },
@@ -2125,6 +2263,22 @@ var AUDIOCRAFT_MODELS = [
2125
2263
  { id: "musicgen-melody", name: "MusicGen Melody" },
2126
2264
  { id: "audiogen-medium", name: "AudioGen Medium" }
2127
2265
  ];
2266
+ var AUDIOCRAFT_HF_REPOS = {
2267
+ "musicgen-small": "facebook/musicgen-small",
2268
+ "musicgen-medium": "facebook/musicgen-medium",
2269
+ "musicgen-large": "facebook/musicgen-large",
2270
+ "musicgen-melody": "facebook/musicgen-melody",
2271
+ "audiogen-medium": "facebook/audiogen-medium"
2272
+ };
2273
+ async function fetchAudioCraftDescriptions() {
2274
+ const descriptions = /* @__PURE__ */ new Map();
2275
+ const fetches = Object.entries(AUDIOCRAFT_HF_REPOS).map(async ([id, repo]) => {
2276
+ const desc = await fetchReadmeDescription(repo, 8e3);
2277
+ if (desc) descriptions.set(id, desc);
2278
+ });
2279
+ await Promise.allSettled(fetches);
2280
+ return descriptions;
2281
+ }
2128
2282
  function runPython2(code, timeoutMs = 5e3) {
2129
2283
  return new Promise((resolve, reject) => {
2130
2284
  (0, import_node_child_process2.execFile)("python3", ["-c", code], { timeout: timeoutMs }, (err, stdout) => {
@@ -2160,6 +2314,7 @@ var AudioCraftProvider = class {
2160
2314
  async listModels(_modality) {
2161
2315
  if (_modality && _modality !== "music") return [];
2162
2316
  if (!await this.ping()) return [];
2317
+ const descMap = await fetchAudioCraftDescriptions().catch(() => /* @__PURE__ */ new Map());
2163
2318
  const logo = getProviderLogo("meta");
2164
2319
  const models = [];
2165
2320
  for (const m of AUDIOCRAFT_MODELS) {
@@ -2169,6 +2324,7 @@ var AudioCraftProvider = class {
2169
2324
  id: m.id,
2170
2325
  provider: "audiocraft",
2171
2326
  name: m.name,
2327
+ description: descMap.get(m.id),
2172
2328
  modality: "music",
2173
2329
  local: true,
2174
2330
  cost: { price: 0, unit: "free" },
@@ -2564,9 +2720,9 @@ var Noosphere = class {
2564
2720
  if (!this.initialized) await this.init();
2565
2721
  return this.registry.getModel(provider, modelId);
2566
2722
  }
2567
- async syncModels() {
2723
+ async syncModels(modality) {
2568
2724
  if (!this.initialized) await this.init();
2569
- return this.registry.syncAll();
2725
+ return this.registry.syncAll(modality);
2570
2726
  }
2571
2727
  // --- Tracking Methods ---
2572
2728
  getUsage(options) {