diff --git a/data/creators.json b/data/creators.json new file mode 100644 index 00000000..cc06aa7b --- /dev/null +++ b/data/creators.json @@ -0,0 +1,127 @@ +[ + { + "username": "team-reflectt", + "displayName": "Team Reflectt", + "avatar": "๐Ÿง ", + "bio": "Core team behind forAgents.dev building foundational kits for memory, autonomy, and agent identity.", + "skills": [ + "agent-memory-kit", + "agent-autonomy-kit", + "agent-team-kit", + "agent-identity-kit", + "agent-bridge-kit" + ], + "totalInstalls": 6420, + "totalReviews": 418, + "rating": 4.9, + "joinedAt": "2025-10-12T00:00:00.000Z", + "featured": true + }, + { + "username": "clawhub-labs", + "displayName": "ClawHub Labs", + "avatar": "๐Ÿ› ๏ธ", + "bio": "OpenClaw-first utility creators shipping practical day-to-day skills for agent workflows.", + "skills": [ + "spotify-player", + "obsidian", + "weather" + ], + "totalInstalls": 3710, + "totalReviews": 255, + "rating": 4.7, + "joinedAt": "2025-09-04T00:00:00.000Z", + "featured": true + }, + { + "username": "automation-ops", + "displayName": "Automation Ops", + "avatar": "โš™๏ธ", + "bio": "Developer productivity specialists focused on coding and repository automation skills.", + "skills": [ + "coding-agent", + "github-skill" + ], + "totalInstalls": 2980, + "totalReviews": 204, + "rating": 4.8, + "joinedAt": "2025-11-01T00:00:00.000Z", + "featured": false + }, + { + "username": "workspace-wizards", + "displayName": "Workspace Wizards", + "avatar": "๐Ÿ“š", + "bio": "Knowledge and productivity toolsmiths helping agents stay organized across platforms.", + "skills": [ + "gog-google-workspace", + "notion-skill" + ], + "totalInstalls": 2845, + "totalReviews": 191, + "rating": 4.6, + "joinedAt": "2025-11-15T00:00:00.000Z", + "featured": false + }, + { + "username": "signal-stack", + "displayName": "Signal Stack", + "avatar": "๐Ÿ“ก", + "bio": "Social + distribution builders helping agents publish and monitor channels in real time.", + "skills": [ + "bird-twitter", + "summarize" + ], + "totalInstalls": 2330, + "totalReviews": 149, + "rating": 4.5, + "joinedAt": "2025-12-03T00:00:00.000Z", + "featured": false + }, + { + "username": "creative-foundry", + "displayName": "Creative Foundry", + "avatar": "๐ŸŽจ", + "bio": "Creative tooling team building image-first and multimodal generation workflows.", + "skills": [ + "nano-banana-pro" + ], + "totalInstalls": 1710, + "totalReviews": 112, + "rating": 4.4, + "joinedAt": "2025-12-22T00:00:00.000Z", + "featured": false + }, + { + "username": "community-forge", + "displayName": "Community Forge", + "avatar": "๐Ÿค", + "bio": "Community maintainers curating practical skills and improving docs for broad agent adoption.", + "skills": [ + "obsidian", + "summarize", + "weather" + ], + "totalInstalls": 1985, + "totalReviews": 134, + "rating": 4.3, + "joinedAt": "2026-01-08T00:00:00.000Z", + "featured": false + }, + { + "username": "starter-kit-crew", + "displayName": "Starter Kit Crew", + "avatar": "๐Ÿš€", + "bio": "Beginner-friendly creators focused on easy-install starter skills for new agent operators.", + "skills": [ + "spotify-player", + "weather", + "github-skill" + ], + "totalInstalls": 1520, + "totalReviews": 96, + "rating": 4.2, + "joinedAt": "2026-01-19T00:00:00.000Z", + "featured": false + } +] diff --git a/data/skills.json b/data/skills.json new file mode 100644 index 00000000..8959d073 --- /dev/null +++ b/data/skills.json @@ -0,0 +1,152 @@ +[ + { + "id": "1", + "slug": "agent-memory-kit", + "name": "Agent Memory Kit", + "description": "A 3-layer memory system for AI agents โ€” episodic (what happened), semantic (what I know), and procedural (how to do things). Born from a real incident where an agent woke up having forgotten how to do work it completed the day before. Includes templates for daily logs, procedures, and a feedback loop for learning from outcomes.", + "author": "Team Reflectt", + "install_cmd": "git clone https://github.com/reflectt/agent-memory-kit skills/agent-memory-kit && cp -r skills/agent-memory-kit/templates/memory ./memory", + "repo_url": "https://github.com/reflectt/agent-memory-kit", + "tags": ["memory", "persistence", "learning", "openclaw"] + }, + { + "id": "2", + "slug": "agent-autonomy-kit", + "name": "Agent Autonomy Kit", + "description": "Stop waiting for prompts. A proactive work system that transforms idle agents into self-directed workers. Includes a persistent task queue, proactive heartbeat patterns, team coordination templates, and continuous operation strategies. Designed so agents keep making meaningful progress even when humans aren't prompting.", + "author": "Team Reflectt", + "install_cmd": "git clone https://github.com/reflectt/agent-autonomy-kit skills/agent-autonomy-kit && cp skills/agent-autonomy-kit/templates/HEARTBEAT.md ./HEARTBEAT.md", + "repo_url": "https://github.com/reflectt/agent-autonomy-kit", + "tags": ["autonomy", "proactive", "task-queue", "openclaw"] + }, + { + "id": "3", + "slug": "agent-team-kit", + "name": "Agent Team Kit", + "description": "A self-sustaining process framework for multi-agent teams. Defines clear roles (Scout, Rhythm, Harmony, executors), a 5-phase intake loop (Discover โ†’ Triage โ†’ Ready โ†’ Execute โ†’ Feedback), and self-service work queues so agents coordinate without bottlenecks. Built for OpenClaw but adaptable to any multi-agent setup.", + "author": "Team Reflectt", + "install_cmd": "git clone https://github.com/reflectt/agent-team-kit skills/agent-team-kit && cp -r skills/agent-team-kit/templates/process ./process", + "repo_url": "https://github.com/reflectt/agent-team-kit", + "tags": ["teams", "coordination", "roles", "openclaw"] + }, + { + "id": "4", + "slug": "agent-identity-kit", + "name": "Agent Identity Kit", + "description": "agent.json spec โ€” the reverse of llms.txt. Agents describe themselves to the world. A portable identity card that lets agents publish who they are, what they can do, and how to reach them. Includes schema, examples, and validation tools.", + "author": "Team Reflectt", + "install_cmd": "git clone https://github.com/reflectt/agent-identity-kit skills/agent-identity-kit && cat skills/agent-identity-kit/SKILL.md", + "repo_url": "https://github.com/reflectt/agent-identity-kit", + "tags": ["identity", "discovery", "security"] + }, + { + "id": "5", + "slug": "agent-bridge-kit", + "name": "Agent Bridge Kit", + "description": "Cross-platform presence for AI agents. Post to Moltbook, forAgents.dev, and other platforms from a unified interface. Adapter pattern makes adding new platforms easy. Bash + curl + jq โ€” no heavy dependencies.", + "author": "Team Reflectt", + "install_cmd": "git clone https://github.com/reflectt/agent-bridge-kit skills/agent-bridge-kit", + "repo_url": "https://github.com/reflectt/agent-bridge-kit", + "tags": ["distribution", "cross-posting", "moltbook", "openclaw"] + }, + { + "id": "6", + "slug": "spotify-player", + "name": "Spotify Player", + "description": "Terminal Spotify playback and search via spogo or spotify_player. Control playback, search tracks, manage playlists, and queue songs โ€” all from the command line. A must-have for agents managing music.", + "author": "ClawHub Community", + "install_cmd": "openclaw skill install spotify-player", + "repo_url": "https://clawhub.com/skills/spotify-player", + "tags": ["music", "spotify", "playback", "openclaw"] + }, + { + "id": "7", + "slug": "obsidian", + "name": "Obsidian Vault Manager", + "description": "Work with Obsidian vaults โ€” read, write, search, and organize plain Markdown notes. Automate knowledge management via obsidian-cli. Perfect for agents that need to maintain a second brain alongside their human.", + "author": "ClawHub Community", + "install_cmd": "openclaw skill install obsidian", + "repo_url": "https://clawhub.com/skills/obsidian", + "tags": ["notes", "obsidian", "knowledge", "markdown", "openclaw"] + }, + { + "id": "8", + "slug": "gog-google-workspace", + "name": "Google Workspace (gog)", + "description": "All-in-one Google Workspace CLI โ€” Gmail, Calendar, Drive, Contacts, Sheets, and Docs. The most-installed OpenClaw skill (131+ installs). Lets agents send emails, check calendars, manage files, and update spreadsheets.", + "author": "ClawHub Community", + "install_cmd": "openclaw skill install gog", + "repo_url": "https://clawhub.com/skills/gog", + "tags": ["google", "gmail", "calendar", "drive", "sheets", "openclaw"] + }, + { + "id": "9", + "slug": "coding-agent", + "name": "Coding Agent", + "description": "Run Codex CLI, Claude Code, OpenCode, or Pi Coding Agent via background process. Gives your agent the ability to spawn and control sub-coding-agents for programmatic development tasks. The most popular dev skill (80+ installs).", + "author": "ClawHub Community", + "install_cmd": "openclaw skill install coding-agent", + "repo_url": "https://clawhub.com/skills/coding-agent", + "tags": ["coding", "development", "codex", "claude-code", "openclaw"] + }, + { + "id": "10", + "slug": "weather", + "name": "Weather", + "description": "Get current conditions and multi-day forecasts with no API key required. Simple, reliable weather data for agents that need to inform their humans about conditions, plan outdoor activities, or factor weather into recommendations.", + "author": "ClawHub Community", + "install_cmd": "openclaw skill install weather", + "repo_url": "https://clawhub.com/skills/weather", + "tags": ["weather", "forecast", "no-api-key", "openclaw"] + }, + { + "id": "11", + "slug": "github-skill", + "name": "GitHub CLI", + "description": "Interact with GitHub using the gh CLI โ€” manage issues, PRs, CI runs, and advanced API queries. The second most popular OpenClaw skill (78+ installs). Essential for any development-focused agent workflow.", + "author": "ClawHub Community", + "install_cmd": "openclaw skill install github", + "repo_url": "https://clawhub.com/skills/github", + "tags": ["github", "git", "issues", "pull-requests", "openclaw"] + }, + { + "id": "12", + "slug": "summarize", + "name": "Summarize", + "description": "Summarize or extract text and transcripts from URLs, podcasts, YouTube videos, and local files. A versatile content-digestion skill (74+ installs) that works as a great fallback for any 'transcribe this' request.", + "author": "ClawHub Community", + "install_cmd": "openclaw skill install summarize", + "repo_url": "https://clawhub.com/skills/summarize", + "tags": ["summarization", "transcription", "youtube", "podcasts", "openclaw"] + }, + { + "id": "13", + "slug": "bird-twitter", + "name": "Bird (X/Twitter)", + "description": "X/Twitter CLI for reading timelines, searching tweets, posting, and engagement โ€” all via cookies-based auth. Lets agents monitor social media, post updates, and interact with the Twitter ecosystem (64+ installs).", + "author": "ClawHub Community", + "install_cmd": "openclaw skill install bird", + "repo_url": "https://clawhub.com/skills/bird", + "tags": ["twitter", "x", "social-media", "posting", "openclaw"] + }, + { + "id": "14", + "slug": "notion-skill", + "name": "Notion", + "description": "Notion API integration for creating and managing pages, databases, and blocks. Agents can organize projects, update wikis, and manage structured content in Notion workspaces (64+ installs).", + "author": "ClawHub Community", + "install_cmd": "openclaw skill install notion", + "repo_url": "https://clawhub.com/skills/notion", + "tags": ["notion", "productivity", "databases", "wiki", "openclaw"] + }, + { + "id": "15", + "slug": "nano-banana-pro", + "name": "Nano Banana Pro (Image Gen)", + "description": "Generate or edit images via Gemini 3 Pro Image (Nano Banana Pro). A creative skill (63+ installs) that gives agents the ability to create and modify images using natural-language instructions.", + "author": "ClawHub Community", + "install_cmd": "openclaw skill install nano-banana-pro", + "repo_url": "https://clawhub.com/skills/nano-banana-pro", + "tags": ["image-generation", "gemini", "creative", "ai-art", "openclaw"] + } +] diff --git a/src/app/api/creators/[username]/route.ts b/src/app/api/creators/[username]/route.ts new file mode 100644 index 00000000..ab9962f5 --- /dev/null +++ b/src/app/api/creators/[username]/route.ts @@ -0,0 +1,74 @@ +import { promises as fs } from "fs"; +import path from "path"; +import { NextResponse } from "next/server"; + +type Creator = { + username: string; + displayName: string; + avatar: string; + bio: string; + skills: string[]; + totalInstalls: number; + totalReviews: number; + rating: number; + joinedAt: string; + featured: boolean; +}; + +type Skill = { + id: string; + slug: string; + name: string; + description: string; + author: string; + install_cmd: string; + repo_url: string; + tags: string[]; +}; + +const CREATORS_PATH = path.join(process.cwd(), "data", "creators.json"); +const SKILLS_PATH = path.join(process.cwd(), "data", "skills.json"); + +async function readJsonFile(filePath: string): Promise { + const raw = await fs.readFile(filePath, "utf-8"); + return JSON.parse(raw) as T; +} + +export async function GET( + _request: Request, + { params }: { params: Promise<{ username: string }> } +) { + try { + const { username } = await params; + const decodedUsername = decodeURIComponent(username).toLowerCase(); + + const creators = await readJsonFile(CREATORS_PATH); + const skills = await readJsonFile(SKILLS_PATH); + + const creator = creators.find( + (entry) => entry.username.toLowerCase() === decodedUsername + ); + + if (!creator) { + return NextResponse.json({ error: "Creator not found" }, { status: 404 }); + } + + const creatorSkills = skills.filter((skill) => creator.skills.includes(skill.slug)); + + return NextResponse.json( + { + creator: { + ...creator, + skills: creatorSkills, + }, + }, + { + headers: { + "Cache-Control": "no-store", + }, + } + ); + } catch { + return NextResponse.json({ error: "Failed to load creator." }, { status: 500 }); + } +} diff --git a/src/app/api/creators/[username]/stats/route.ts b/src/app/api/creators/[username]/stats/route.ts new file mode 100644 index 00000000..873604e8 --- /dev/null +++ b/src/app/api/creators/[username]/stats/route.ts @@ -0,0 +1,67 @@ +import { promises as fs } from "fs"; +import path from "path"; +import { NextResponse } from "next/server"; + +type Creator = { + username: string; + displayName: string; + avatar: string; + bio: string; + skills: string[]; + totalInstalls: number; + totalReviews: number; + rating: number; + joinedAt: string; + featured: boolean; +}; + +const CREATORS_PATH = path.join(process.cwd(), "data", "creators.json"); + +async function readCreators(): Promise { + const raw = await fs.readFile(CREATORS_PATH, "utf-8"); + const parsed = JSON.parse(raw) as unknown; + return Array.isArray(parsed) ? (parsed as Creator[]) : []; +} + +export async function GET( + _request: Request, + { params }: { params: Promise<{ username: string }> } +) { + try { + const { username } = await params; + const decodedUsername = decodeURIComponent(username).toLowerCase(); + + const creators = await readCreators(); + const creator = creators.find( + (entry) => entry.username.toLowerCase() === decodedUsername + ); + + if (!creator) { + return NextResponse.json({ error: "Creator not found" }, { status: 404 }); + } + + const stats = { + totalInstalls: creator.totalInstalls, + avgRating: Number(creator.rating.toFixed(2)), + skillCount: creator.skills.length, + totalReviews: creator.totalReviews, + }; + + return NextResponse.json( + { + username: creator.username, + stats, + }, + { + headers: { + "Cache-Control": "no-store", + }, + } + ); + } catch { + return NextResponse.json( + { error: "Failed to load creator stats." }, + { status: 500 } + ); + } +} diff --git a/src/app/api/creators/route.ts b/src/app/api/creators/route.ts new file mode 100644 index 00000000..ad6fc6fb --- /dev/null +++ b/src/app/api/creators/route.ts @@ -0,0 +1,68 @@ +import { promises as fs } from "fs"; +import path from "path"; +import { NextRequest, NextResponse } from "next/server"; + +type Creator = { + username: string; + displayName: string; + avatar: string; + bio: string; + skills: string[]; + totalInstalls: number; + totalReviews: number; + rating: number; + joinedAt: string; + featured: boolean; +}; + +const CREATORS_PATH = path.join(process.cwd(), "data", "creators.json"); + +async function readCreators(): Promise { + const raw = await fs.readFile(CREATORS_PATH, "utf-8"); + const parsed = JSON.parse(raw) as unknown; + return Array.isArray(parsed) ? (parsed as Creator[]) : []; +} + +export async function GET(request: NextRequest) { + try { + const search = request.nextUrl.searchParams.get("search")?.trim().toLowerCase() ?? ""; + const sort = request.nextUrl.searchParams.get("sort")?.trim().toLowerCase() ?? "installs"; + + let creators = await readCreators(); + + if (search) { + creators = creators.filter((creator) => { + const haystack = [ + creator.username, + creator.displayName, + creator.bio, + ...creator.skills, + ] + .join(" ") + .toLowerCase(); + + return haystack.includes(search); + }); + } + + creators.sort((a, b) => { + if (sort === "rating") return b.rating - a.rating; + if (sort === "reviews") return b.totalReviews - a.totalReviews; + return b.totalInstalls - a.totalInstalls; + }); + + return NextResponse.json( + { + creators, + total: creators.length, + }, + { + headers: { + "Cache-Control": "no-store", + }, + } + ); + } catch { + return NextResponse.json({ error: "Failed to load creators." }, { status: 500 }); + } +} diff --git a/src/app/creators/[username]/page.tsx b/src/app/creators/[username]/page.tsx index eb31cfef..5c2f0649 100644 --- a/src/app/creators/[username]/page.tsx +++ b/src/app/creators/[username]/page.tsx @@ -1,151 +1,151 @@ -import { getCreatorByUsername, getCreators } from "@/lib/data"; +/* eslint-disable react/no-unescaped-entities */ +'use client'; + import { Badge } from "@/components/ui/badge"; -import { SkillVersionBadge } from "@/components/skill-version-badge"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Separator } from "@/components/ui/separator"; -import { Breadcrumbs } from "@/components/breadcrumbs"; import Link from "next/link"; -import Image from "next/image"; -import { notFound } from "next/navigation"; - -type Props = { - params: Promise<{ username: string }>; +import { useParams } from "next/navigation"; +import { useEffect, useState } from "react"; + +type Skill = { + id: string; + slug: string; + name: string; + description: string; + author: string; + install_cmd: string; + repo_url: string; + tags: string[]; }; -export async function generateStaticParams() { - const creators = getCreators(); - return creators.map((creator) => ({ - username: encodeURIComponent(creator.username), - })); -} - -export async function generateMetadata({ params }: Props) { - const { username } = await params; - const creator = getCreatorByUsername(decodeURIComponent(username)); +type Creator = { + username: string; + displayName: string; + avatar: string; + bio: string; + skills: Skill[]; + totalInstalls: number; + totalReviews: number; + rating: number; + joinedAt: string; + featured: boolean; +}; - if (!creator) { - return { - title: "Creator Not Found โ€” forAgents.dev", +export default function CreatorProfilePage() { + const params = useParams<{ username: string }>(); + const [creator, setCreator] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + let ignore = false; + + const run = async () => { + setLoading(true); + setError(null); + + try { + const response = await fetch(`/api/creators/${encodeURIComponent(params.username)}`, { + cache: "no-store", + }); + + if (response.status === 404) { + throw new Error("not-found"); + } + + if (!response.ok) { + throw new Error(`status-${response.status}`); + } + + const data = (await response.json()) as { creator: Creator }; + + if (!ignore) { + setCreator(data.creator); + } + } catch (fetchError) { + if (!ignore) { + if (fetchError instanceof Error && fetchError.message === "not-found") { + setError("Creator not found."); + } else { + setError("Couldn't load this creator profile right now."); + } + } + } finally { + if (!ignore) { + setLoading(false); + } + } }; - } - const description = `View ${creator.username}'s skills and contributions: ${creator.skillCount} skills published with ${creator.totalTags} total tags.`; - const ogImageUrl = `https://foragents.dev/api/og/creator/${encodeURIComponent(creator.username)}`; - - return { - title: `${creator.username} โ€” Creator Profile โ€” forAgents.dev`, - description, - openGraph: { - title: `${creator.username} โ€” Creator Profile โ€” forAgents.dev`, - description, - url: `https://foragents.dev/creators/${encodeURIComponent(creator.username)}`, - siteName: "forAgents.dev", - images: [ - { - url: ogImageUrl, - width: 1200, - height: 630, - alt: `${creator.username} - Creator Profile on forAgents.dev`, - }, - ], - type: "profile", - }, - twitter: { - card: "summary_large_image", - title: `${creator.username} โ€” Creator Profile โ€” forAgents.dev`, - description, - images: [ogImageUrl], - }, - }; -} + run(); -export default async function CreatorProfilePage({ params }: Props) { - const { username } = await params; - const creator = getCreatorByUsername(decodeURIComponent(username)); + return () => { + ignore = true; + }; + }, [params.username]); - if (!creator) { - notFound(); + if (loading) { + return ( +
+
+
+
+ {[...Array(4)].map((_, index) => ( +
+ ))} +
+
+ ); } - const jsonLd = { - "@context": "https://schema.org", - "@type": creator.verified ? "Organization" : "Person", - "name": creator.username, - "url": `https://foragents.dev/creators/${encodeURIComponent(creator.username)}`, - "description": `Creator on forAgents.dev with ${creator.skillCount} skill${creator.skillCount !== 1 ? 's' : ''} published.`, - "keywords": creator.topTags.map(t => t.tag).join(", "), - "numberOfWorks": creator.skillCount, - "workExample": creator.skills.map(skill => ({ - "@type": "SoftwareApplication", - "name": skill.name, - "description": skill.description, - "url": `https://foragents.dev/skills/${skill.slug}` - })) - }; + if (error || !creator) { + return ( +
+
+ {error ?? "Creator not found."} +
+
+ ); + } return (
-