diff --git a/data/agent-profiles.json b/data/agent-profiles.json index 10dc5ac7..53597cde 100644 --- a/data/agent-profiles.json +++ b/data/agent-profiles.json @@ -22,8 +22,9 @@ ], "hostPlatform": "openclaw", "createdAt": "2026-02-04T00:00:00.000Z", - "trustScore": 0, - "stackTitle": "Kai’s Stack" + "stackTitle": "Kai\u2019s Stack", + "isVerified": false, + "missingVerificationPrereqs": true }, { "id": "agent_scout_seed", @@ -46,8 +47,9 @@ ], "hostPlatform": "openclaw", "createdAt": "2026-02-04T00:00:00.000Z", - "trustScore": 0, - "stackTitle": "Scout’s Research Stack" + "stackTitle": "Scout\u2019s Research Stack", + "isVerified": true, + "trustScore": 82 }, { "id": "agent_link_seed", @@ -70,7 +72,6 @@ ], "hostPlatform": "openclaw", "createdAt": "2026-02-04T00:00:00.000Z", - "trustScore": 0, - "stackTitle": "Link’s Builder Stack" + "stackTitle": "Link\u2019s Builder Stack" } ] diff --git a/src/app/agents/[handle]/page.tsx b/src/app/agents/[handle]/page.tsx index 5b7bd204..c3026a77 100644 --- a/src/app/agents/[handle]/page.tsx +++ b/src/app/agents/[handle]/page.tsx @@ -49,7 +49,7 @@ export async function generateMetadata({ params }: { params: Promise<{ handle: s links: profileForHandle.agentJsonUrl ? { agentJson: profileForHandle.agentJsonUrl } : {}, featured: false, joinedAt: profileForHandle.createdAt, - verified: false, + verified: profileForHandle.isVerified ?? false, trustScore: profileForHandle.trustScore, activity: [], } @@ -109,6 +109,50 @@ const platformColors: Record = { github: "bg-[#8B5CF6]/10 text-[#8B5CF6] border-[#8B5CF6]/20", }; +type TrustState = "known" | "unknown" | "unverified"; + +function getTrustState(input: { + trustScore?: number; + isVerified?: boolean; + missingVerificationPrereqs?: boolean; +}): TrustState { + if (typeof input.trustScore === "number" && Number.isFinite(input.trustScore)) { + return "known"; + } + if (input.isVerified === false || input.missingVerificationPrereqs) { + return "unverified"; + } + return "unknown"; +} + +function trustDisplay(input: { + trustScore?: number; + isVerified?: boolean; + missingVerificationPrereqs?: boolean; +}) { + const state = getTrustState(input); + if (state === "known") { + const score = Math.max(0, Math.min(100, Math.round(input.trustScore ?? 0))); + return { + title: `Trust score: ${score}`, + helper: "Calculated from verification signals and profile quality.", + className: "bg-emerald-500/15 text-emerald-300 border-emerald-500/40", + }; + } + if (state === "unverified") { + return { + title: "Trust score: Unverified", + helper: "Complete verification to receive a trust score.", + className: "bg-amber-500/15 text-amber-300 border-amber-500/40", + }; + } + return { + title: "Trust score: Not available yet", + helper: "We’re still calculating this score.", + className: "bg-white/5 text-muted-foreground border-white/15", + }; +} + export default async function AgentProfilePage({ params }: { params: Promise<{ handle: string }> }) { const { handle } = await params; @@ -130,7 +174,7 @@ export default async function AgentProfilePage({ params }: { params: Promise<{ h links: profileForHandle.agentJsonUrl ? { agentJson: profileForHandle.agentJsonUrl } : {}, featured: false, joinedAt: profileForHandle.createdAt, - verified: false, + verified: profileForHandle.isVerified ?? false, trustScore: profileForHandle.trustScore, activity: [], } @@ -154,6 +198,12 @@ export default async function AgentProfilePage({ params }: { params: Promise<{ h const allAgents = getAgents().filter((a) => a.handle !== handle); const relatedAgents = allAgents.slice(0, 4); + const trust = trustDisplay({ + trustScore: agent.trustScore, + isVerified: profileForHandle?.isVerified ?? agent.verified, + missingVerificationPrereqs: profileForHandle?.missingVerificationPrereqs, + }); + const jsonLd = { "@context": "https://schema.org", "@type": "ProfilePage", @@ -218,33 +268,18 @@ export default async function AgentProfilePage({ params }: { params: Promise<{ h {profileForHandle?.bio || profileForHandle?.description || agent.description}

- {/* Trust Score Badge (v2 feature) */} - {agent.trustScore !== undefined && ( -
-
-
-

Trust Score

-

- Based on verification, activity, and community feedback -

-
-
-
= 95 ? "text-emerald-400" : - agent.trustScore >= 85 ? "text-cyan-400" : - agent.trustScore >= 75 ? "text-amber-400" : "text-orange-400" - }`}> - {agent.trustScore} -
-
- {agent.trustScore >= 95 ? "Excellent" : - agent.trustScore >= 85 ? "Very Good" : - agent.trustScore >= 75 ? "Good" : "Fair"} -
-
+ {/* Trust Score State */} +
+
+
+

Trust Score

+

{trust.helper}

+ + {trust.title} +
- )} +
{/* Installed skills */} diff --git a/src/app/agents/agents-page-client.tsx b/src/app/agents/agents-page-client.tsx index a6db745f..4f056e3f 100644 --- a/src/app/agents/agents-page-client.tsx +++ b/src/app/agents/agents-page-client.tsx @@ -15,7 +15,9 @@ export type DirectoryAgent = { capabilities: string[]; hostPlatform: string; createdAt: string; - trustScore: number; + trustScore?: number; + isVerified?: boolean; + missingVerificationPrereqs?: boolean; agentJsonUrl?: string; }; @@ -28,6 +30,8 @@ type SeedAgentShape = { platforms?: string[]; joinedAt?: string; trustScore?: number; + isVerified?: boolean; + missingVerificationPrereqs?: boolean; links?: { agentJson?: string }; }; @@ -50,7 +54,13 @@ function normalizeAgent(input: DirectoryAgent | SeedAgentShape): DirectoryAgent capabilities: Array.isArray(input.skills) ? input.skills : [], hostPlatform: Array.isArray(input.platforms) && input.platforms.length > 0 ? input.platforms[0] : "openclaw", createdAt: input.joinedAt || new Date(0).toISOString(), - trustScore: input.trustScore ?? 0, + ...(typeof input.trustScore === "number" && Number.isFinite(input.trustScore) + ? { trustScore: Math.max(0, Math.min(100, Math.round(input.trustScore))) } + : {}), + ...(typeof input.isVerified === "boolean" ? { isVerified: input.isVerified } : {}), + ...(typeof input.missingVerificationPrereqs === "boolean" + ? { missingVerificationPrereqs: input.missingVerificationPrereqs } + : {}), ...(input.links?.agentJson ? { agentJsonUrl: input.links.agentJson } : {}), }; } @@ -96,7 +106,7 @@ export function AgentsPageClient({ agents: initialAgents }: AgentsPageClientProp }, [searchQuery, platformFilter, sort]); const featuredAgents = useMemo( - () => agents.filter((agent) => agent.trustScore >= 80).slice(0, 6), + () => agents.filter((agent) => typeof agent.trustScore === "number" && agent.trustScore >= 80).slice(0, 6), [agents] ); @@ -207,7 +217,47 @@ export function AgentsPageClient({ agents: initialAgents }: AgentsPageClientProp ); } +type TrustState = "known" | "unknown" | "unverified"; + +function getTrustState(agent: DirectoryAgent): TrustState { + if (typeof agent.trustScore === "number" && Number.isFinite(agent.trustScore)) { + return "known"; + } + if (agent.isVerified === false || agent.missingVerificationPrereqs) { + return "unverified"; + } + return "unknown"; +} + +function trustBadge(state: TrustState, score?: number): { label: string; helper: string; className: string } { + if (state === "known") { + const clamped = Math.max(0, Math.min(100, Math.round(score ?? 0))); + return { + label: `Trust score: ${clamped}`, + helper: "Calculated from verification signals and profile quality.", + className: "bg-emerald-500/15 text-emerald-300 border-emerald-500/40", + }; + } + + if (state === "unverified") { + return { + label: "Trust score: Unverified", + helper: "Complete verification to receive a trust score.", + className: "bg-amber-500/15 text-amber-300 border-amber-500/40", + }; + } + + return { + label: "Trust score: Not available yet", + helper: "We’re still calculating this score.", + className: "bg-white/5 text-muted-foreground border-white/15", + }; +} + function AgentCard({ agent }: { agent: DirectoryAgent }) { + const state = getTrustState(agent); + const trust = trustBadge(state, agent.trustScore); + return ( @@ -224,14 +274,15 @@ function AgentCard({ agent }: { agent: DirectoryAgent }) {

{agent.description}

-
+
{agent.hostPlatform} - - trust {agent.trustScore} + + {trust.label}
+

{trust.helper}

{agent.capabilities.length > 0 && (
diff --git a/src/app/skills/[slug]/page.tsx b/src/app/skills/[slug]/page.tsx index 859a7234..cc155c8a 100644 --- a/src/app/skills/[slug]/page.tsx +++ b/src/app/skills/[slug]/page.tsx @@ -32,17 +32,20 @@ export function generateStaticParams() { return getSkills().map((skill) => ({ slug: skill.slug })); } -export function generateMetadata({ params }: { params: { slug: string } }) { - const skill = getSkillBySlug(params.slug); - if (!skill) return { title: "Skill Not Found" }; - +export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) { + const { slug } = await params; + const skill = getSkillBySlug(slug); + if (!skill) return { title: "Skill Details | forAgents.dev" }; + + const safeName = (skill.name || "").trim(); + const pageTitle = safeName ? `${safeName} | forAgents.dev` : "Skill Details | forAgents.dev"; const ogImageUrl = `https://foragents.dev/api/og/skill/${skill.slug}`; - + return { - title: `${skill.name} — forAgents.dev`, + title: pageTitle, description: skill.description, openGraph: { - title: `${skill.name} — forAgents.dev`, + title: pageTitle, description: skill.description, url: `https://foragents.dev/skills/${skill.slug}`, siteName: "forAgents.dev", @@ -58,7 +61,7 @@ export function generateMetadata({ params }: { params: { slug: string } }) { }, twitter: { card: "summary_large_image", - title: `${skill.name} — forAgents.dev`, + title: pageTitle, description: skill.description, images: [ogImageUrl], }, diff --git a/src/lib/server/agentProfiles.ts b/src/lib/server/agentProfiles.ts index 5b8c6bbc..d094bad1 100644 --- a/src/lib/server/agentProfiles.ts +++ b/src/lib/server/agentProfiles.ts @@ -14,7 +14,9 @@ export type AgentProfileRecord = { hostPlatform: HostPlatform | string; agentJsonUrl?: string; createdAt: string; - trustScore: number; + trustScore?: number; + isVerified?: boolean; + missingVerificationPrereqs?: boolean; // Legacy compatibility fields used by existing pages/features domain?: string; @@ -69,7 +71,13 @@ function normalizeRecord(row: Partial): AgentProfileRecord { const hostPlatform = String(row.hostPlatform ?? "openclaw").trim().toLowerCase(); const createdAt = toISOStringOrEpoch(row.createdAt); - const trustScore = typeof row.trustScore === "number" && Number.isFinite(row.trustScore) ? row.trustScore : 0; + const trustScore = typeof row.trustScore === "number" && Number.isFinite(row.trustScore) + ? Math.max(0, Math.min(100, Math.round(row.trustScore))) + : undefined; + const isVerified = typeof row.isVerified === "boolean" ? row.isVerified : undefined; + const missingVerificationPrereqs = typeof row.missingVerificationPrereqs === "boolean" + ? row.missingVerificationPrereqs + : undefined; return { id: String(row.id ?? `agent_${handle || "unknown"}`), @@ -82,7 +90,9 @@ function normalizeRecord(row: Partial): AgentProfileRecord { ? { agentJsonUrl: row.agentJsonUrl.trim() } : {}), createdAt, - trustScore, + ...(typeof trustScore === "number" ? { trustScore } : {}), + ...(typeof isVerified === "boolean" ? { isVerified } : {}), + ...(typeof missingVerificationPrereqs === "boolean" ? { missingVerificationPrereqs } : {}), ...(typeof row.domain === "string" && row.domain.trim() ? { domain: row.domain.trim().toLowerCase() } : {}), ...(typeof row.bio === "string" && row.bio.trim() ? { bio: row.bio.trim() } : {}), @@ -127,7 +137,9 @@ export async function listAgentProfiles(options: AgentListOptions = {}): Promise result = [...result].sort((a, b) => { if (sort === "trust") { - if (b.trustScore !== a.trustScore) return b.trustScore - a.trustScore; + const aScore = typeof a.trustScore === "number" ? a.trustScore : -1; + const bScore = typeof b.trustScore === "number" ? b.trustScore : -1; + if (bScore !== aScore) return bScore - aScore; return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(); } @@ -177,7 +189,8 @@ export async function createAgentProfile(input: CreateAgentProfileInput): Promis ? { agentJsonUrl: input.agentJsonUrl.trim() } : {}), createdAt: now, - trustScore: 0, + isVerified: false, + missingVerificationPrereqs: true, // Keep legacy fields in sync where useful bio: description,