Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions data/agent-profiles.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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"
}
]
89 changes: 62 additions & 27 deletions src/app/agents/[handle]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: [],
}
Expand Down Expand Up @@ -109,6 +109,50 @@ const platformColors: Record<string, string> = {
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;

Expand All @@ -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: [],
}
Expand All @@ -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",
Expand Down Expand Up @@ -218,33 +268,18 @@ export default async function AgentProfilePage({ params }: { params: Promise<{ h
{profileForHandle?.bio || profileForHandle?.description || agent.description}
</p>

{/* Trust Score Badge (v2 feature) */}
{agent.trustScore !== undefined && (
<div className="mt-6 pt-6 border-t border-white/10">
<div className="flex items-center justify-between">
<div>
<h3 className="text-sm font-semibold text-white/80 mb-1">Trust Score</h3>
<p className="text-xs text-muted-foreground">
Based on verification, activity, and community feedback
</p>
</div>
<div className="text-right">
<div className={`text-3xl font-bold ${
agent.trustScore >= 95 ? "text-emerald-400" :
agent.trustScore >= 85 ? "text-cyan-400" :
agent.trustScore >= 75 ? "text-amber-400" : "text-orange-400"
}`}>
{agent.trustScore}
</div>
<div className="text-xs text-muted-foreground mt-1">
{agent.trustScore >= 95 ? "Excellent" :
agent.trustScore >= 85 ? "Very Good" :
agent.trustScore >= 75 ? "Good" : "Fair"}
</div>
</div>
{/* Trust Score State */}
<div className="mt-6 pt-6 border-t border-white/10">
<div className="flex items-center justify-between gap-3">
<div>
<h3 className="text-sm font-semibold text-white/80 mb-1">Trust Score</h3>
<p className="text-xs text-muted-foreground">{trust.helper}</p>
</div>
<Badge variant="outline" className={`${trust.className} text-xs`}>
{trust.title}
</Badge>
</div>
)}
</div>
</div>

{/* Installed skills */}
Expand Down
63 changes: 57 additions & 6 deletions src/app/agents/agents-page-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ export type DirectoryAgent = {
capabilities: string[];
hostPlatform: string;
createdAt: string;
trustScore: number;
trustScore?: number;
isVerified?: boolean;
missingVerificationPrereqs?: boolean;
agentJsonUrl?: string;
};

Expand All @@ -28,6 +30,8 @@ type SeedAgentShape = {
platforms?: string[];
joinedAt?: string;
trustScore?: number;
isVerified?: boolean;
missingVerificationPrereqs?: boolean;
links?: { agentJson?: string };
};

Expand All @@ -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 } : {}),
};
}
Expand Down Expand Up @@ -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]
);

Expand Down Expand Up @@ -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 (
<Link href={`/agents/${agent.handle}`}>
<Card className="bg-black/20 border-white/10 hover:border-cyan/30 transition-all cursor-pointer h-full group">
Expand All @@ -224,14 +274,15 @@ function AgentCard({ agent }: { agent: DirectoryAgent }) {

<p className="text-sm text-foreground/80 line-clamp-2 mb-3">{agent.description}</p>

<div className="flex items-center gap-2 mb-3">
<div className="flex items-center gap-2 mb-2">
<Badge variant="outline" className="bg-white/5 text-white/70 border-white/10 text-[10px]">
{agent.hostPlatform}
</Badge>
<Badge variant="outline" className="bg-emerald-500/20 text-emerald-300 border-emerald-500/30 text-[10px]">
trust {agent.trustScore}
<Badge variant="outline" className={`${trust.className} text-[10px]`}>
{trust.label}
</Badge>
</div>
<p className="text-[11px] text-muted-foreground mb-3">{trust.helper}</p>

{agent.capabilities.length > 0 && (
<div className="text-xs text-muted-foreground">
Expand Down
19 changes: 11 additions & 8 deletions src/app/skills/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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],
},
Expand Down
23 changes: 18 additions & 5 deletions src/lib/server/agentProfiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -69,7 +71,13 @@ function normalizeRecord(row: Partial<AgentProfileRecord>): 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"}`),
Expand All @@ -82,7 +90,9 @@ function normalizeRecord(row: Partial<AgentProfileRecord>): 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() } : {}),
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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,
Expand Down
Loading