"use client"; import { useMemo, useState } from "react"; import { Download, ExternalLink, RefreshCcw, Settings2, Shield, Sparkles, Star, Trash2, X, } from "lucide-react"; import type { OfficeSkillsMarketplaceController } from "@/features/office/hooks/useOfficeSkillsMarketplace"; import type { SkillMarketplaceCollectionId, SkillMarketplaceEntry } from "@/lib/skills/marketplace"; import { buildSkillMarketplaceCollections } from "@/lib/skills/marketplace"; import { buildAgentSkillsAllowlistSet, deriveAgentSkillsAccessMode } from "@/lib/skills/presentation"; type MarketplaceFilter = "all" | SkillMarketplaceCollectionId; const FILTER_LABELS: Record = { claw3d: "Claw3D", all: "All", featured: "Featured", installed: "Installed", "setup-required": "Needs setup", "built-in": "Built-in", workspace: "Workspace", extra: "Community", other: "Other", }; const READINESS_LABELS = { ready: "Ready", "needs-setup": "Needs setup", unavailable: "Unavailable", "disabled-globally": "Disabled globally", } as const; const READINESS_CLASSES = { ready: "border-emerald-500/30 bg-emerald-500/10 text-emerald-100", "needs-setup": "border-amber-500/30 bg-amber-500/10 text-amber-100", unavailable: "border-rose-500/30 bg-rose-500/10 text-rose-100", "disabled-globally": "border-cyan-500/30 bg-cyan-500/10 text-cyan-100", } as const; const formatRating = (value: number | undefined) => { if (typeof value !== "number" || !Number.isFinite(value)) { return "4.7"; } return value.toFixed(1); }; const formatInstalls = (value: number | undefined) => { const installs = value ?? 0; if (installs >= 1000) { return `${(installs / 1000).toFixed(1)}k`; } return new Intl.NumberFormat("en-US").format(installs); }; const buildSearchBlob = (entry: SkillMarketplaceEntry): string => { return [ entry.skill.name, entry.skill.description, entry.skill.skillKey, entry.skill.source, entry.metadata.category, entry.metadata.tagline, entry.metadata.capabilities.join(" "), ] .join(" ") .toLowerCase(); }; const getAgentSkillEnabled = ( skillName: string, accessMode: ReturnType, allowlistSet: Set ) => { if (accessMode === "all") { return true; } if (accessMode === "none") { return false; } return allowlistSet.has(skillName.trim()); }; export function SkillsMarketplacePanel({ marketplace, onSelectAgent, onOpenAgentSettings, }: { marketplace: OfficeSkillsMarketplaceController; onSelectAgent: (agentId: string) => void; onOpenAgentSettings: (agentId: string) => void; }) { const [query, setQuery] = useState(""); const [activeFilter, setActiveFilter] = useState("claw3d"); const [detailSkillKey, setDetailSkillKey] = useState(null); const entries = useMemo( () => marketplace.marketplaceSkills ?? marketplace.skillsReport?.skills ?? [], [marketplace.marketplaceSkills, marketplace.skillsReport] ); const collections = useMemo(() => buildSkillMarketplaceCollections(entries), [entries]); const accessMode = useMemo( () => deriveAgentSkillsAccessMode(marketplace.skillsAllowlist), [marketplace.skillsAllowlist] ); const allowlistSet = useMemo( () => buildAgentSkillsAllowlistSet(marketplace.skillsAllowlist), [marketplace.skillsAllowlist] ); const filteredCollections = useMemo(() => { const normalizedQuery = query.trim().toLowerCase(); const visibleCollectionIds: SkillMarketplaceCollectionId[] = activeFilter === "all" ? ["claw3d", "built-in", "installed", "workspace", "extra", "other"] : [activeFilter]; return collections .filter((collection) => visibleCollectionIds.includes(collection.id)) .map((collection) => ({ ...collection, entries: collection.entries.filter((entry) => { if (!normalizedQuery) { return true; } return buildSearchBlob(entry).includes(normalizedQuery); }), })) .filter((collection) => collection.entries.length > 0); }, [activeFilter, collections, query]); const featuredEntries = useMemo(() => { const normalizedQuery = query.trim().toLowerCase(); const featuredCollection = collections.find((collection) => collection.id === "featured"); if (!featuredCollection) { return []; } return featuredCollection.entries .filter((entry) => { if (!normalizedQuery) { return true; } return buildSearchBlob(entry).includes(normalizedQuery); }) .slice(0, 3); }, [collections, query]); const filterCounts = useMemo(() => { const counts: Record = { claw3d: 0, all: entries.length, featured: 0, installed: 0, "setup-required": 0, "built-in": 0, workspace: 0, extra: 0, other: 0, }; for (const collection of collections) { counts[collection.id] = collection.entries.length; } return counts; }, [collections, entries.length]); const detailEntry = collections .flatMap((collection) => collection.entries) .find((entry) => entry.skill.skillKey === detailSkillKey) ?? null; return (
Skills Marketplace
Browse gateway skills like a curated plugin store.
Packaged skill installs target the selected agent workspace. Global setup actions still affect the whole gateway. Agent access controls below apply only to the selected agent.
Agent context
{marketplace.selectedAgent?.name ?? "No agent selected"}
Access mode: {accessMode === "selected" ? "Selected skills" : accessMode}
setQuery(event.target.value)} placeholder="Search skills, categories, or sources" className="w-full rounded border border-white/10 bg-black/40 px-3 py-2 font-mono text-[11px] text-white/85 outline-none transition focus:border-cyan-400/35" aria-label="Search marketplace skills" />
{(Object.keys(FILTER_LABELS) as MarketplaceFilter[]).map((filterId) => ( ))}
{marketplace.message ? (
{marketplace.message.text} {marketplace.message.kind === "success" ? (
Check the `CLAW3D` filter below to find the installed skill quickly.
) : null}
) : null} {marketplace.error && !marketplace.message ? (
{marketplace.error}
) : null} {marketplace.loading ? (
Loading marketplace inventory...
) : null} {!marketplace.loading && activeFilter === "all" && featuredEntries.length > 0 ? (
Featured shelf
{featuredEntries.map((entry) => ( ))}
) : null} {!marketplace.loading && filteredCollections.length === 0 ? (
No matching skills found for this gateway.
) : null} {!marketplace.loading && filteredCollections.map((collection) => (
{collection.label}
{collection.entries.map((entry) => { const packagedSkill = marketplace.packagedSkillsByKey.get(entry.skill.skillKey); const packageOnly = Boolean(packagedSkill && !entry.skill.baseDir.trim()); const isEnabledForAgent = getAgentSkillEnabled(entry.skill.name, accessMode, allowlistSet); const primaryAction = packageOnly ? { label: "Install skill", run: () => void marketplace.handleInstallPackagedSkill(entry.skill.skillKey), icon: Download, } : entry.readiness === "needs-setup" && entry.installable ? { label: "Install deps", run: () => void marketplace.handleInstallSkill(entry.skill), icon: Download, } : entry.readiness === "disabled-globally" ? { label: "Enable gateway", run: () => void marketplace.handleSetSkillGlobalEnabled(entry.skill.skillKey, true), icon: Settings2, } : entry.readiness === "needs-setup" ? { label: "Open settings", run: () => { if (marketplace.selectedAgentId) { onOpenAgentSettings(marketplace.selectedAgentId); } }, icon: Settings2, } : null; const PrimaryIcon = primaryAction?.icon ?? Settings2; return (
{entry.metadata.category} {READINESS_LABELS[entry.readiness]}
{entry.metadata.tagline}
{entry.metadata.trustLabel} {!entry.metadata.hideStats ? ( <> {formatRating(entry.metadata.rating)} {formatInstalls(entry.metadata.installs)} installs ) : null} {entry.skill.source}
{entry.metadata.poweredByName && entry.metadata.poweredByUrl ? ( ) : null} {entry.missingDetails.length > 0 ? (
{entry.missingDetails[0]}
) : null}
{primaryAction ? ( ) : null} {entry.removable ? ( ) : null}
{isEnabledForAgent ? "This skill is currently enabled for the selected agent." : "This skill is currently disabled for the selected agent."}
{entry.removable ? (
Removing from the gateway deletes the installed skill for every agent.
) : null}
); })}
))}
{detailEntry ? (
Skill detail
{detailEntry.skill.name}
{detailEntry.metadata.category} {detailEntry.metadata.trustLabel} {READINESS_LABELS[detailEntry.readiness]}
{detailEntry.metadata.tagline}
{detailEntry.metadata.poweredByName && detailEntry.metadata.poweredByUrl ? ( ) : null}
{!detailEntry.metadata.hideStats ? ( <>
Rating
{formatRating(detailEntry.metadata.rating)}
Installs
{formatInstalls(detailEntry.metadata.installs)}
) : null}
Source
{detailEntry.skill.source}
Capabilities
{detailEntry.metadata.capabilities.map((capability) => (
{capability}
))}
{detailEntry.missingDetails.length > 0 ? (
Setup notes
{detailEntry.missingDetails.map((line) => (
{line}
))}
) : null}
Packaged installs land in the selected workspace. Gateway setup changes still apply to every agent, and agent enablement depends on the selected agent's allowlist.
{marketplace.packagedSkillsByKey.get(detailEntry.skill.skillKey) && !detailEntry.skill.baseDir.trim() ? ( ) : null} {detailEntry.readiness === "needs-setup" && detailEntry.installable ? ( ) : null} {detailEntry.readiness === "disabled-globally" ? ( ) : null} {detailEntry.skill.homepage ? ( Homepage ) : null}
`Enable/Disable for agent` only changes access for the selected agent. `Remove for all agents` deletes the installed skill from the gateway workspace.
) : null}
); }