diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml index 4857a6b..85e6430 100644 --- a/.github/workflows/deploy-pages.yml +++ b/.github/workflows/deploy-pages.yml @@ -6,7 +6,6 @@ on: branches: [main] paths: - 'marketplace/**' - - 'catalog/**' - 'scripts/**' - 'docs/**' - '.github/workflows/deploy-pages.yml' diff --git a/AGENTS.md b/AGENTS.md index 35da3a2..42916ba 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,8 +17,6 @@ This is the **catalog and marketplace** for Red Hat agentic collections. It serv agentic-collections-catalog/ ├── marketplace/ │ └── rh-agentic-collection.yml # Single source of truth for pack discovery -├── catalog/ -│ └── schema.yaml # JSON Schema for .catalog/collection.yaml files in skills repos ├── docs/ # Static site (agentskills.io) │ ├── index.html # SPA entry point │ ├── app.js # Rendering and search logic (XSS-safe, no innerHTML) @@ -80,7 +78,7 @@ Do not add packs to `docs/data.json` or any other file directly. Add them to the - **Marketplace is the single source of truth.** Do not add packs, icons, or titles anywhere else. - **Generated files are read-only.** `docs/data.json`, `docs/mcp.json`, and `docs/collections/*.html` are rebuilt on every run — manual edits will be overwritten. - **No skills development here.** To create or modify skills, work in the appropriate skills source repo. -- **Schema changes need coordination.** Updating `catalog/schema.yaml` requires matching changes in all skills repos' `.catalog/` directories. +- **Schema changes need coordination.** Updating `.catalog/collection.yaml` in skills repos requires consistent field usage across all packs. - **Security.** All DOM manipulation in `app.js` uses `textContent` and `createElement` — never `innerHTML` with external data. ## CI diff --git a/OPEN_ITEMS.md b/OPEN_ITEMS.md index 93aee2b..85e4a20 100644 --- a/OPEN_ITEMS.md +++ b/OPEN_ITEMS.md @@ -5,8 +5,14 @@ * Replace [mcp.json](./docs/mcp.json) ### External contributions -* How to replace collection pack info in absemce of collection.yml? - * README - * License - * List of skills - * Installation \ No newline at end of file +* How to replace collection pack info in absence of collection.yml? + * ~~README~~ + * ~~License~~ + * ~~List of skills~~ + * ~~Installation~~ + +### Evals +* Replace eval with scorecards + +### ??? +* \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 87ea479..9264b23 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ # Documentation Site -This directory contains the generated GitHub Pages site for agentic-collections. +This directory contains the generated GitHub Pages site for agentic-collections-catalog. ## Files @@ -42,7 +42,7 @@ When changing docs presentation metadata (`styles.css`, `app.js`, `mcp.json`): 2. Run validation checks: ```bash -make validate +make test ``` 3. Review validation output before merge. diff --git a/docs/app.js b/docs/app.js index 702fd2a..0bf65af 100644 --- a/docs/app.js +++ b/docs/app.js @@ -1,5 +1,5 @@ /** - * agentic-collections Documentation Site + * agentic-collections-catalog Documentation Site * * SECURITY: All DOM manipulation uses textContent and createElement * to prevent XSS vulnerabilities. No innerHTML with user data. @@ -17,7 +17,7 @@ const JS_STYLE_TOKENS = { }; /** Default LICENSE in upstream repo (collection legal_resources may override). */ -const UPSTREAM_REPO_LICENSE_URL = 'https://github.com/RHEcosystemAppEng/agentic-collections/blob/main/LICENSE'; +const UPSTREAM_REPO_LICENSE_URL = 'https://github.com/RHEcosystemAppEng/agentic-collections-catalog/blob/main/LICENSE'; /** * Update toolbar counter badges @@ -737,475 +737,6 @@ function toggleInstallCallout() { toggle.setAttribute('aria-expanded', callout.classList.contains('collapsed') ? 'false' : 'true'); } -/** - * Show pack details modal (XSS-safe) - */ -function showPackDetails(packName) { - const pack = data.packs.find(p => p.name === packName); - if (!pack) return; - - const modal = document.getElementById('pack-modal'); - const details = document.getElementById('pack-details'); - - // Clear previous content - details.textContent = ''; - - // Create modal header - const header = document.createElement('div'); - header.className = 'modal-header'; - - // Close button - const closeBtn = document.createElement('span'); - closeBtn.className = 'close'; - closeBtn.textContent = '×'; - closeBtn.onclick = () => { - modal.style.display = 'none'; - document.body.style.overflow = ''; // Restore background scrolling - }; - header.appendChild(closeBtn); - - const headerTop = document.createElement('div'); - headerTop.className = 'modal-header-top'; - - const titleGroup = document.createElement('div'); - titleGroup.className = 'modal-title-group'; - - const h2 = document.createElement('h2'); - h2.textContent = pack.plugin?.title || pack.plugin?.name || pack.name; - titleGroup.appendChild(h2); - - // Owner subtitle - const ownerSubtitle = document.createElement('div'); - ownerSubtitle.className = 'pack-owner-subtitle'; - ownerSubtitle.textContent = 'By Red Hat'; - ownerSubtitle.style.color = 'var(--text-muted)'; - ownerSubtitle.style.fontSize = '0.95rem'; - ownerSubtitle.style.marginTop = '0.5rem'; - titleGroup.appendChild(ownerSubtitle); - - // Counts (skills + agents + MCPs) - const counts = document.createElement('div'); - counts.className = 'modal-counts'; - const countParts = []; - if (pack.skills.length > 0) { - countParts.push(`${pack.skills.length} skill${pack.skills.length !== 1 ? 's' : ''}`); - } - if (pack.agents.length > 0) { - countParts.push(`${pack.agents.length} agent${pack.agents.length !== 1 ? 's' : ''}`); - } - // Count both Official and Community MCPs for this pack - const packMCPCount = [...allMCPServers, ...allCommunityMCPServers].filter(server => server.pack === pack.name).length; - if (packMCPCount > 0) { - countParts.push(`${packMCPCount} MCP`); - } - counts.textContent = countParts.join(' '); - titleGroup.appendChild(counts); - - headerTop.appendChild(titleGroup); - - // Meta (version badge + readme button if available) - const meta = document.createElement('div'); - meta.className = 'modal-meta'; - - const versionBadge = document.createElement('span'); - versionBadge.className = 'version-badge'; - versionBadge.textContent = `v${pack.plugin.version || '0.0.0'}`; - meta.appendChild(versionBadge); - - if (pack.has_readme) { - const readmeButton = document.createElement('a'); - readmeButton.className = 'readme-button'; - readmeButton.textContent = 'README'; - readmeButton.href = `https://github.com/RHEcosystemAppEng/agentic-collections/tree/main/${pack.name}`; - readmeButton.target = '_blank'; - meta.appendChild(readmeButton); - } - - headerTop.appendChild(meta); - header.appendChild(headerTop); - - // Description - if (pack.plugin.description) { - const desc = document.createElement('div'); - desc.className = 'modal-description'; - desc.textContent = pack.plugin.description; - header.appendChild(desc); - } - - // Plugin name field (if different from title) - if (pack.plugin.name && pack.plugin.name !== pack.plugin.title) { - const pluginNameDiv = document.createElement('div'); - pluginNameDiv.style.marginTop = '1rem'; - pluginNameDiv.style.fontSize = '0.9rem'; - pluginNameDiv.style.color = 'var(--text-muted)'; - - const label = document.createElement('strong'); - label.textContent = 'Plugin name: '; - label.style.color = 'var(--text-primary)'; - pluginNameDiv.appendChild(label); - - const nameCode = document.createElement('code'); - nameCode.textContent = pack.plugin.name; - nameCode.style.backgroundColor = JS_STYLE_TOKENS.subtleErrorBackground; - nameCode.style.padding = '0.25rem 0.5rem'; - nameCode.style.borderRadius = '4px'; - nameCode.style.fontSize = '0.85rem'; - pluginNameDiv.appendChild(nameCode); - - header.appendChild(pluginNameDiv); - } - - details.appendChild(header); - - // Create modal body - const body = document.createElement('div'); - body.className = 'modal-body'; - - // Installation section (Lola package manager) - const installSection = document.createElement('div'); - installSection.className = 'modal-section'; - - const installHeader = document.createElement('div'); - installHeader.className = 'modal-section-header'; - installHeader.textContent = 'INSTALLATION'; - installSection.appendChild(installHeader); - - const lolaNote = document.createElement('div'); - lolaNote.style.color = 'var(--text-muted)'; - lolaNote.style.fontSize = '0.85rem'; - lolaNote.style.marginBottom = '0.75rem'; - lolaNote.textContent = 'Lola. Applies to current folder only.'; - installSection.appendChild(lolaNote); - - const moduleName = pack.name; - - const codeWrapper = document.createElement('div'); - codeWrapper.className = 'install-code-wrapper'; - - const installPre = document.createElement('pre'); - const installCode = document.createElement('code'); - installCode.textContent = `lola market add rh-agentic-collections https://raw.githubusercontent.com/RHEcosystemAppEng/agentic-collections/main/marketplace/rh-agentic-collection.yml -lola install -f ${moduleName}`; - installPre.appendChild(installCode); - codeWrapper.appendChild(installPre); - - const copyBtn = document.createElement('button'); - copyBtn.className = 'copy-button'; - copyBtn.textContent = 'Copy'; - copyBtn.onclick = () => copyToClipboard(installCode.textContent, copyBtn); - codeWrapper.appendChild(copyBtn); - - installSection.appendChild(codeWrapper); - - const optNote = document.createElement('div'); - optNote.style.color = 'var(--text-muted)'; - optNote.style.fontSize = '0.8rem'; - optNote.style.marginTop = '0.5rem'; - optNote.textContent = 'Add -a claude-code or -a cursor to target one assistant.'; - installSection.appendChild(optNote); - - const lolaLink = document.createElement('div'); - lolaLink.style.marginTop = '0.5rem'; - lolaLink.style.fontSize = '0.85rem'; - const lolaAnchor = document.createElement('a'); - lolaAnchor.href = 'https://github.com/LobsterTrap/lola'; - lolaAnchor.target = '_blank'; - lolaAnchor.textContent = 'Lola →'; - lolaAnchor.style.color = 'var(--primary)'; - lolaAnchor.style.textDecoration = 'none'; - lolaAnchor.onmouseover = () => { lolaAnchor.style.textDecoration = 'underline'; }; - lolaAnchor.onmouseout = () => { lolaAnchor.style.textDecoration = 'none'; }; - lolaLink.appendChild(lolaAnchor); - installSection.appendChild(lolaLink); - - body.appendChild(installSection); - - // Agents section (shown first) - if (pack.agents.length > 0) { - const agentsSection = document.createElement('div'); - agentsSection.className = 'modal-section'; - - const agentsHeader = document.createElement('div'); - agentsHeader.className = 'modal-section-header'; - agentsHeader.textContent = 'AGENTS'; - agentsSection.appendChild(agentsHeader); - - const agentsList = document.createElement('div'); - agentsList.className = 'item-list'; - - pack.agents.forEach(agent => { - const agentDef = document.createElement('div'); - agentDef.className = 'agent-definition'; - - // Agent syntax block - const syntaxBlock = document.createElement('div'); - syntaxBlock.className = 'definition-syntax'; - const syntaxCode = document.createElement('code'); - syntaxCode.textContent = agent.name; - syntaxBlock.appendChild(syntaxCode); - agentDef.appendChild(syntaxBlock); - - // Agent description (with expand/collapse for long text) - const desc = document.createElement('div'); - desc.className = 'definition-description'; - desc.appendChild(createExpandableText(agent.description, 200)); - agentDef.appendChild(desc); - - agentsList.appendChild(agentDef); - }); - - agentsSection.appendChild(agentsList); - body.appendChild(agentsSection); - } - - // Skills section (shown second) - if (pack.skills.length > 0) { - const skillsSection = document.createElement('div'); - skillsSection.className = 'modal-section'; - - const skillsHeader = document.createElement('div'); - skillsHeader.className = 'modal-section-header'; - skillsHeader.textContent = 'SKILLS'; - skillsSection.appendChild(skillsHeader); - - const skillsList = document.createElement('div'); - skillsList.className = 'item-list'; - - pack.skills.forEach(skill => { - const skillDef = document.createElement('div'); - skillDef.className = 'skill-definition'; - - // Skill syntax block - const syntaxBlock = document.createElement('div'); - syntaxBlock.className = 'definition-syntax'; - const syntaxCode = document.createElement('code'); - syntaxCode.textContent = skill.name; - syntaxBlock.appendChild(syntaxCode); - skillDef.appendChild(syntaxBlock); - - // Skill description (with expand/collapse for long text) - const desc = document.createElement('div'); - desc.className = 'definition-description'; - desc.appendChild(createExpandableText(skill.description, 200)); - skillDef.appendChild(desc); - - skillsList.appendChild(skillDef); - }); - - skillsSection.appendChild(skillsList); - body.appendChild(skillsSection); - } - - // Docs section (shown third, if documentation exists) - if (pack.docs && pack.docs.length > 0) { - const docsSection = document.createElement('div'); - docsSection.className = 'modal-section'; - - const docsHeader = document.createElement('div'); - docsHeader.className = 'modal-section-header'; - docsHeader.textContent = 'DOCS'; - docsSection.appendChild(docsHeader); - - // Group docs by category - const docsByCategory = {}; - pack.docs.forEach(doc => { - const category = doc.category || 'general'; - if (!docsByCategory[category]) { - docsByCategory[category] = []; - } - docsByCategory[category].push(doc); - }); - - // Render each category - Object.keys(docsByCategory).sort().forEach(category => { - const categorySection = document.createElement('div'); - categorySection.style.marginBottom = '1.5rem'; - - // Category header - const categoryHeader = document.createElement('div'); - categoryHeader.className = 'modal-section-label'; - categoryHeader.textContent = category.charAt(0).toUpperCase() + category.slice(1); - categorySection.appendChild(categoryHeader); - - // Doc list for this category - const docsList = document.createElement('div'); - docsList.className = 'item-list'; - - docsByCategory[category].forEach(doc => { - const docDef = document.createElement('div'); - docDef.className = 'skill-definition'; - - // Doc title - const titleBlock = document.createElement('div'); - titleBlock.className = 'definition-syntax'; - const titleCode = document.createElement('code'); - titleCode.textContent = doc.title; - titleBlock.appendChild(titleCode); - docDef.appendChild(titleBlock); - - // Sources section - if (doc.sources && doc.sources.length > 0) { - const sourcesDiv = document.createElement('div'); - sourcesDiv.className = 'definition-description'; - - doc.sources.forEach((source, index) => { - // Add separator between sources - if (index > 0) { - sourcesDiv.appendChild(document.createElement('br')); - } - - // Source title label - const sourceLabel = document.createElement('span'); - sourceLabel.textContent = 'Source: '; - sourceLabel.style.color = 'var(--text-muted)'; - sourceLabel.style.fontSize = '0.85rem'; - sourcesDiv.appendChild(sourceLabel); - - // Source link - const sourceLink = document.createElement('a'); - sourceLink.href = source.url; - sourceLink.target = '_blank'; - sourceLink.textContent = source.title; - sourceLink.style.color = 'var(--primary)'; - sourceLink.style.textDecoration = 'none'; - sourceLink.onmouseover = () => { sourceLink.style.textDecoration = 'underline'; }; - sourceLink.onmouseout = () => { sourceLink.style.textDecoration = 'none'; }; - sourcesDiv.appendChild(sourceLink); - }); - - docDef.appendChild(sourcesDiv); - } - - docsList.appendChild(docDef); - }); - - categorySection.appendChild(docsList); - docsSection.appendChild(categorySection); - }); - - // Link to full documentation on GitHub - const docsLink = document.createElement('div'); - docsLink.style.marginTop = '1.5rem'; - docsLink.style.paddingTop = '1rem'; - docsLink.style.borderTop = '1px solid var(--border)'; - const link = document.createElement('a'); - link.href = `https://github.com/RHEcosystemAppEng/agentic-collections/tree/main/${pack.name}/docs`; - link.target = '_blank'; - link.textContent = 'View full documentation on GitHub →'; - link.style.color = 'var(--primary)'; - link.style.textDecoration = 'none'; - link.style.fontWeight = '600'; - link.onmouseover = () => { link.style.textDecoration = 'underline'; }; - link.onmouseout = () => { link.style.textDecoration = 'none'; }; - docsLink.appendChild(link); - docsSection.appendChild(docsLink); - - body.appendChild(docsSection); - } - - // MCP section (shown last, if MCP servers exist for this pack) - // Include both Official and Community MCP servers for this pack - const packMCPServers = [...allMCPServers, ...allCommunityMCPServers].filter(server => server.pack === pack.name); - if (packMCPServers.length > 0) { - const mcpSection = document.createElement('div'); - mcpSection.className = 'modal-section'; - - const mcpHeader = document.createElement('div'); - mcpHeader.className = 'modal-section-header'; - mcpHeader.textContent = 'MCP'; - mcpSection.appendChild(mcpHeader); - - const mcpList = document.createElement('div'); - mcpList.className = 'item-list'; - - packMCPServers.forEach(server => { - const mcpDef = document.createElement('div'); - mcpDef.className = 'skill-definition mcp-link-item'; - mcpDef.style.cursor = 'pointer'; - mcpDef.style.transition = 'all 0.2s ease'; - mcpDef.style.borderLeft = '3px solid transparent'; - mcpDef.style.paddingLeft = '1rem'; - - // MCP server name with icon and arrow (clickable) - const nameBlock = document.createElement('div'); - nameBlock.className = 'definition-syntax'; - nameBlock.style.display = 'flex'; - nameBlock.style.alignItems = 'center'; - nameBlock.style.gap = '0.5rem'; - nameBlock.style.justifyContent = 'space-between'; - - const nameGroup = document.createElement('div'); - nameGroup.style.display = 'flex'; - nameGroup.style.alignItems = 'center'; - nameGroup.style.gap = '0.5rem'; - - // Custom icon (if available) - if (server.icon) { - nameGroup.appendChild(createIconElement(server.icon, 'item-icon')); - } - - const nameCode = document.createElement('code'); - nameCode.textContent = server.title || server.name; - nameCode.style.color = 'var(--primary)'; - nameGroup.appendChild(nameCode); - - // HTTP remote indicator (if applicable) - if (server.type === 'http') { - const icon = document.createElement('span'); - icon.textContent = '🌐'; - icon.title = 'HTTP Remote Server'; - icon.style.fontSize = '0.9rem'; - nameGroup.appendChild(icon); - } - - nameBlock.appendChild(nameGroup); - - // Add arrow indicator - const arrow = document.createElement('span'); - arrow.textContent = '→'; - arrow.style.color = 'var(--text-muted)'; - arrow.style.fontSize = '1.2rem'; - arrow.style.transition = 'transform 0.2s ease'; - nameBlock.appendChild(arrow); - - mcpDef.appendChild(nameBlock); - - // MCP owner subtitle - const ownerText = document.createElement('div'); - ownerText.className = 'definition-description'; - ownerText.textContent = `By ${server.owner || 'Red Hat'}`; - ownerText.style.color = 'var(--text-muted)'; - ownerText.style.fontSize = '0.85rem'; - ownerText.style.marginTop = '0.25rem'; - mcpDef.appendChild(ownerText); - - // Hover effects - mcpDef.onmouseenter = () => { - mcpDef.style.borderLeftColor = 'var(--primary)'; - mcpDef.style.backgroundColor = JS_STYLE_TOKENS.softAccentBackground; - arrow.style.transform = 'translateX(4px)'; - }; - - mcpDef.onmouseleave = () => { - mcpDef.style.borderLeftColor = 'transparent'; - mcpDef.style.backgroundColor = 'transparent'; - arrow.style.transform = 'translateX(0)'; - }; - - // Click to open MCP details - mcpDef.onclick = () => showMCPDetails(server.name, server.pack); - - mcpList.appendChild(mcpDef); - }); - - mcpSection.appendChild(mcpList); - body.appendChild(mcpSection); - } - - details.appendChild(body); - modal.style.display = 'flex'; - document.body.style.overflow = 'hidden'; // Prevent background scrolling -} /** * Show MCP server details modal (XSS-safe) @@ -2268,8 +1799,8 @@ function appendSkillEvalBlock(li, skill) { }; const commitSha = String(ev.commit_sha || '').trim(); const commitShort = commitSha ? commitSha.slice(0, 8) : 'N/A'; - const commitHref = commitSha - ? `https://github.com/RHEcosystemAppEng/agentic-collections/commit/${commitSha}` + const commitHref = commitSha && ev.repository + ? `${String(ev.repository).replace(/\/$/, '')}/commit/${commitSha}` : ''; const prUrl = String(ev.related_pr || '').trim(); const prMatch = prUrl.match(/\/pull\/(\d+)(?:\/|$)/); diff --git a/docs/index.html b/docs/index.html index 7ebec99..60ae320 100644 --- a/docs/index.html +++ b/docs/index.html @@ -3,7 +3,7 @@
-lola market add rh-agentic-collections https://raw.githubusercontent.com/RHEcosystemAppEng/agentic-collections/main/marketplace/rh-agentic-collection.yml
+ lola market add rh-agentic-collections https://raw.githubusercontent.com/RHEcosystemAppEng/agentic-collections-catalog/main/marketplace/rh-agentic-collection.yml
Then lola install -f <module> — applies to current folder only.
Ready-to-use capability packs designed for Claude Code and Cursor to automate complex domain-specific tasks.
@@ -112,14 +112,9 @@/{html.escape(name)} - {desc}"
if list_tag:
heading += f' {html.escape(list_tag)}'
@@ -434,7 +444,7 @@ def _render_skills_list(skills: List[Dict[str, Any]], blob_base: str, list_tag:
block.append(_render_skill_evaluation_block(ev))
block.append(
f""
- "View SKILL.md on GitHub ->"
+ "View SKILL.md →"
)
block.append("")
items.append("\n".join(block))
@@ -546,6 +556,23 @@ def _render_agents_tab(
return "".join(out)
+def _render_lola_install_block(pack_name: str) -> str:
+ """Generate a Lola installation block for packs without catalog deploy_and_use instructions."""
+ name_esc = html.escape(pack_name)
+ code = f"lola install -f {name_esc}"
+ return (
+ "{code}"
+ "No overview available.
") + # Inferred Lola install block for packs with no catalog deploy instructions + if not has_catalog or not collection.get("deploy_and_use"): + overview_parts.append(_render_lola_install_block(pack.get("name", ""))) + # Skills tab contents = collection.get("contents") or {} + pack_skills_raw = pack.get("skills") or [] skills_parts = ["No skills content.
") + if not orchestration and not catalog_skills and not guide and not contents.get("description"): + if pack_skills_raw: + skills_parts.append(_render_skills_list(pack_skills_raw, blob_base)) + else: + skills_parts.append("No skills content.
") # Resources tab resources_html = _render_resources(collection.get("resources") or [], blob_base) diff --git a/scripts/generate_pack_data.py b/scripts/generate_pack_data.py index 793fb51..478c6b7 100644 --- a/scripts/generate_pack_data.py +++ b/scripts/generate_pack_data.py @@ -10,6 +10,7 @@ import pack_registry from catalog_site_bundle import bundle_catalog_for_site +from eval_site_enrichment import apply_eval_enrichment from generate_mcp_data import parse_mcp_file def parse_yaml_frontmatter(file_path: Path) -> Dict[str, Any]: @@ -188,6 +189,8 @@ def load_federated_packs() -> List[Dict[str, Any]]: } if cat_bundle is not None: pack["collection"] = cat_bundle + # Enrich with eval reports from the cloned source repo before cleanup + apply_eval_enrichment([pack], clone_dest) packs.append(pack) catalog_status = "with catalog" if cat_bundle else "README only" mcp_status = f", {len(mcp_servers)} MCP server(s)" if mcp_servers else "" diff --git a/scripts/pack_registry.py b/scripts/pack_registry.py index c0f2bf6..ffd6f4c 100644 --- a/scripts/pack_registry.py +++ b/scripts/pack_registry.py @@ -66,8 +66,6 @@ def load_marketplace_module_by_path( return None -MAIN_REPO_URL = "https://github.com/RHEcosystemAppEng/agentic-collections" - FEDERATION_REF_SHA_RE = re.compile(r"^[0-9a-f]{40}$", re.IGNORECASE) @@ -104,7 +102,7 @@ def validate_federated_module_entry(module: Dict[str, Any]) -> List[str]: def load_federated_modules( marketplace_path: Optional[Path] = None, ) -> List[Dict[str, Any]]: - """Return modules whose repository differs from the main repo (federated packs).""" + """Return all marketplace modules that have an external repository.""" path = marketplace_path or (_repo_root() / DEFAULT_MARKETPLACE) if not path.exists(): return [] @@ -115,8 +113,7 @@ def load_federated_modules( return [] return [ m for m in modules - if isinstance(m, dict) - and m.get("repository", "").rstrip("/") != MAIN_REPO_URL + if isinstance(m, dict) and m.get("repository", "").strip() ]