repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
public Clawd ADK gateway launch mirror
stars
latest
clone command
git clone gitlawb://did:key:z6Mkq5mY...iFZ5/my-project-publ...git clone gitlawb://did:key:z6Mkq5mY.../my-project-publ...2fa351d6docs: add automaton and perps launch sources15d ago| #1 | #!/usr/bin/env node |
| #2 | // Aggregates every agent in src/*.json and template in templates/*.template.json |
| #3 | // into a single agents-catalog.json that the /agents page consumes. |
| #4 | // |
| #5 | // Run: node build-catalog.cjs |
| #6 | // Output: agents-catalog.json (sibling of this script) |
| #7 | |
| #8 | const fs = require("fs"); |
| #9 | const path = require("path"); |
| #10 | |
| #11 | const ROOT = __dirname; |
| #12 | const SRC_DIR = path.join(ROOT, "src"); |
| #13 | const TEMPLATES_DIR = path.join(ROOT, "templates"); |
| #14 | const SKILLS_DIR = path.join(ROOT, "skills"); |
| #15 | const SKILL_SCHEMA_FILE = path.join(SKILLS_DIR, "skill-schema.v1.json"); |
| #16 | const OUTPUT = path.join(ROOT, "agents-catalog.json"); |
| #17 | const PUBLIC_DIR = path.join(ROOT, "public"); |
| #18 | const PUBLIC_API_DIR = path.join(PUBLIC_DIR, "api", "agents"); |
| #19 | const PUBLIC_CATALOG_DIR = path.join(PUBLIC_API_DIR, "catalog"); |
| #20 | const PUBLIC_TEMPLATES_DIR = path.join(PUBLIC_API_DIR, "templates"); |
| #21 | const PUBLIC_REGISTRY_DIR = path.join(PUBLIC_API_DIR, "registry"); |
| #22 | const PUBLIC_SKILLS_DIR = path.join(PUBLIC_DIR, "api", "skills"); |
| #23 | const WELL_KNOWN_DIR = path.join(PUBLIC_DIR, ".well-known"); |
| #24 | const HOST = "https://x402.wtf"; |
| #25 | const CLAWD_MINT = "8cHzQHUS2s2h8TzCmfqPKYiM4dSt4roa3n7MyRLApump"; |
| #26 | |
| #27 | const readJson = (p) => JSON.parse(fs.readFileSync(p, "utf8")); |
| #28 | const writeJson = (p, data) => { |
| #29 | fs.mkdirSync(path.dirname(p), { recursive: true }); |
| #30 | fs.writeFileSync(p, JSON.stringify(data, null, 2) + "\n"); |
| #31 | }; |
| #32 | |
| #33 | function loadAgents() { |
| #34 | const files = fs |
| #35 | .readdirSync(SRC_DIR) |
| #36 | .filter((f) => f.endsWith(".json")) |
| #37 | .sort(); |
| #38 | |
| #39 | return files.map((f) => { |
| #40 | const raw = readJson(path.join(SRC_DIR, f)); |
| #41 | const id = raw.identifier || path.basename(f, ".json"); |
| #42 | const capabilities = raw.solana?.capabilities || []; |
| #43 | const metaplexSkills = raw.solana?.metaplexSkills || deriveMetaplexSkills(capabilities, raw.meta?.tags || []); |
| #44 | const agent = { |
| #45 | identifier: id, |
| #46 | title: raw.meta?.title || id, |
| #47 | description: raw.meta?.description || "", |
| #48 | avatar: raw.meta?.avatar || "🤖", |
| #49 | tags: raw.meta?.tags || [], |
| #50 | category: raw.meta?.category || "defi", |
| #51 | author: raw.author || "solana-clawd", |
| #52 | createdAt: raw.createdAt || null, |
| #53 | oneShot: raw.oneShot === true, |
| #54 | featured: raw.featured === true, |
| #55 | openingMessage: raw.config?.openingMessage || null, |
| #56 | openingQuestions: raw.config?.openingQuestions || [], |
| #57 | tokenUsage: raw.tokenUsage || null, |
| #58 | capabilities, |
| #59 | metaplexSkills, |
| #60 | payment: raw.payment || null, |
| #61 | agentToken: raw.agentToken || null, |
| #62 | // Deploy URLs (consumed by AgentGallery deploy buttons) |
| #63 | deploy: { |
| #64 | json: `/api/agents/catalog/${encodeURIComponent(id)}.json`, |
| #65 | chat: `/agents/chat?agent=${encodeURIComponent(id)}`, |
| #66 | mint: `/agents/mint?template=${encodeURIComponent(id)}`, |
| #67 | mcp: `/api/agents/catalog/${encodeURIComponent(id)}.json`, |
| #68 | registration: `/api/agents/registry/${encodeURIComponent(id)}.json`, |
| #69 | }, |
| #70 | }; |
| #71 | Object.defineProperty(agent, "sourceFile", { value: f, enumerable: false }); |
| #72 | return agent; |
| #73 | }); |
| #74 | } |
| #75 | |
| #76 | // Heuristic: infer Metaplex skill badges from capabilities + tags so older |
| #77 | // agents (without explicit solana.metaplexSkills) still surface correctly. |
| #78 | function deriveMetaplexSkills(capabilities, tags) { |
| #79 | const skills = new Set(); |
| #80 | const has = (x) => capabilities.includes(x) || tags.includes(x); |
| #81 | if (has("metaplex-mint-agent") || has("metaplex-register-identity")) skills.add("agent-registry"); |
| #82 | if (has("metaplex-launch-token-genesis") || has("metaplex-launch-bonding-curve") || tags.includes("genesis")) skills.add("genesis"); |
| #83 | if (has("metaplex-mint-core-nft") || tags.includes("mpl-core")) skills.add("core"); |
| #84 | if (has("metaplex-token-metadata")) skills.add("token-metadata"); |
| #85 | if (has("metaplex-mint-cnft") || tags.includes("bubblegum") || tags.includes("cnft")) skills.add("bubblegum"); |
| #86 | if (has("metaplex-deploy-candy-machine") || tags.includes("candy-machine")) skills.add("candy-machine"); |
| #87 | return Array.from(skills); |
| #88 | } |
| #89 | |
| #90 | function loadTemplates() { |
| #91 | if (!fs.existsSync(TEMPLATES_DIR)) return []; |
| #92 | const files = fs |
| #93 | .readdirSync(TEMPLATES_DIR) |
| #94 | .filter((f) => f.endsWith(".template.json")) |
| #95 | .sort(); |
| #96 | |
| #97 | return files.map((f) => { |
| #98 | const raw = readJson(path.join(TEMPLATES_DIR, f)); |
| #99 | const variables = raw.variables || []; |
| #100 | const tags = raw.agent?.meta?.tags || []; |
| #101 | return { |
| #102 | templateId: raw.templateId, |
| #103 | templateName: raw.templateName, |
| #104 | templateDescription: raw.templateDescription, |
| #105 | templateCategory: raw.templateCategory, |
| #106 | templateAvatar: raw.templateAvatar || "🧩", |
| #107 | variableCount: variables.length, |
| #108 | requiredVariables: variables.filter((v) => v.required).map((v) => v.name), |
| #109 | optionalVariables: variables.filter((v) => !v.required).map((v) => v.name), |
| #110 | variables, |
| #111 | path: `templates/${f}`, |
| #112 | schemaVersion: raw.agent?.schemaVersion || 1, |
| #113 | tags, |
| #114 | verified: raw.registry?.verified === true, |
| #115 | sasProgram: raw.registry?.attestation_service || null, |
| #116 | verifierUI: raw.registry?.attestation_service ? "https://attest.solana.com" : null, |
| #117 | schemas: raw.schemas |
| #118 | ? { |
| #119 | skill: raw.schemas.skill?.name || null, |
| #120 | agentIdentity: raw.schemas.agent_identity?.name || null, |
| #121 | } |
| #122 | : null, |
| #123 | deploy: { |
| #124 | template: `/api/agents/templates/${encodeURIComponent(raw.templateId)}.json`, |
| #125 | create: `/agents/mint?fromTemplate=${encodeURIComponent(raw.templateId)}`, |
| #126 | }, |
| #127 | }; |
| #128 | }); |
| #129 | } |
| #130 | |
| #131 | function parseFrontMatter(raw) { |
| #132 | if (!raw.startsWith("---\n")) return { attributes: {}, body: raw }; |
| #133 | const end = raw.indexOf("\n---\n", 4); |
| #134 | if (end === -1) return { attributes: {}, body: raw }; |
| #135 | const frontMatter = raw.slice(4, end); |
| #136 | const body = raw.slice(end + 5); |
| #137 | return { attributes: parseSimpleYaml(frontMatter), body }; |
| #138 | } |
| #139 | |
| #140 | function parseSimpleYaml(source) { |
| #141 | const root = {}; |
| #142 | const stack = [{ indent: -1, value: root }]; |
| #143 | const lines = source.split("\n"); |
| #144 | |
| #145 | for (const line of lines) { |
| #146 | if (!line.trim() || line.trim().startsWith("#")) continue; |
| #147 | const indent = line.match(/^ */)[0].length; |
| #148 | const trimmed = line.trim(); |
| #149 | |
| #150 | while (stack.length > 1 && indent <= stack[stack.length - 1].indent) { |
| #151 | stack.pop(); |
| #152 | } |
| #153 | |
| #154 | const current = stack[stack.length - 1].value; |
| #155 | |
| #156 | if (trimmed.startsWith("- ")) { |
| #157 | const item = coerceYamlScalar(trimmed.slice(2).trim()); |
| #158 | if (!Array.isArray(current)) continue; |
| #159 | current.push(item); |
| #160 | continue; |
| #161 | } |
| #162 | |
| #163 | const sep = trimmed.indexOf(":"); |
| #164 | if (sep === -1) continue; |
| #165 | const key = trimmed.slice(0, sep).trim(); |
| #166 | const rawValue = trimmed.slice(sep + 1).trim(); |
| #167 | |
| #168 | if (rawValue === "") { |
| #169 | const nextMeaningful = findNextMeaningfulLine(lines, lines.indexOf(line) + 1); |
| #170 | const child = nextMeaningful && nextMeaningful.trim().startsWith("- ") ? [] : {}; |
| #171 | current[key] = child; |
| #172 | stack.push({ indent, value: child }); |
| #173 | continue; |
| #174 | } |
| #175 | |
| #176 | current[key] = coerceYamlScalar(rawValue); |
| #177 | } |
| #178 | |
| #179 | return root; |
| #180 | } |
| #181 | |
| #182 | function findNextMeaningfulLine(lines, startIndex) { |
| #183 | for (let i = startIndex; i < lines.length; i += 1) { |
| #184 | if (lines[i].trim() && !lines[i].trim().startsWith("#")) return lines[i]; |
| #185 | } |
| #186 | return null; |
| #187 | } |
| #188 | |
| #189 | function coerceYamlScalar(value) { |
| #190 | const unquoted = |
| #191 | (value.startsWith('"') && value.endsWith('"')) || |
| #192 | (value.startsWith("'") && value.endsWith("'")) |
| #193 | ? value.slice(1, -1) |
| #194 | : value; |
| #195 | if (unquoted === "true") return true; |
| #196 | if (unquoted === "false") return false; |
| #197 | if (/^-?\d+(\.\d+)?$/.test(unquoted)) return Number(unquoted); |
| #198 | return unquoted; |
| #199 | } |
| #200 | |
| #201 | function inferSkillCategory(skillId) { |
| #202 | if (skillId.startsWith("pump-security")) return "security"; |
| #203 | if (skillId.includes("wallet")) return "wallet"; |
| #204 | if (skillId.includes("ts-") || skillId.includes("typescript") || skillId.includes("sdk")) return "typescript"; |
| #205 | if (skillId.includes("solana")) return "solana-dev"; |
| #206 | return "pump-protocol"; |
| #207 | } |
| #208 | |
| #209 | function loadSkills() { |
| #210 | if (!fs.existsSync(SKILLS_DIR)) return []; |
| #211 | const entries = fs |
| #212 | .readdirSync(SKILLS_DIR, { withFileTypes: true }) |
| #213 | .filter((entry) => entry.isDirectory()) |
| #214 | .map((entry) => entry.name) |
| #215 | .sort(); |
| #216 | |
| #217 | return entries |
| #218 | .filter((skillId) => fs.existsSync(path.join(SKILLS_DIR, skillId, "SKILL.md"))) |
| #219 | .map((skillId) => { |
| #220 | const raw = fs.readFileSync(path.join(SKILLS_DIR, skillId, "SKILL.md"), "utf8"); |
| #221 | const { attributes } = parseFrontMatter(raw); |
| #222 | const description = attributes.description || ""; |
| #223 | const openclaw = attributes.metadata?.openclaw || {}; |
| #224 | return { |
| #225 | skillId, |
| #226 | name: attributes.name || skillId, |
| #227 | description, |
| #228 | category: inferSkillCategory(skillId), |
| #229 | path: `${skillId}/SKILL.md`, |
| #230 | url: `${HOST}/api/skills/${encodeURIComponent(skillId)}`, |
| #231 | tags: Array.from( |
| #232 | new Set( |
| #233 | [skillId.split("-")[0], "solana"] |
| #234 | .concat(skillId.split("-")) |
| #235 | .filter(Boolean) |
| #236 | ) |
| #237 | ), |
| #238 | requiredEnv: Array.isArray(openclaw.requires?.env) ? openclaw.requires.env : [], |
| #239 | homepage: openclaw.homepage || null, |
| #240 | attestation: { |
| #241 | status: "pending", |
| #242 | isFormallyVerified: false, |
| #243 | attestationPda: null, |
| #244 | verificationTimestamp: null, |
| #245 | }, |
| #246 | }; |
| #247 | }); |
| #248 | } |
| #249 | |
| #250 | function countByCategory(agents) { |
| #251 | const map = {}; |
| #252 | for (const a of agents) { |
| #253 | map[a.category] = (map[a.category] || 0) + 1; |
| #254 | } |
| #255 | return map; |
| #256 | } |
| #257 | |
| #258 | function build() { |
| #259 | const agents = loadAgents(); |
| #260 | const templates = loadTemplates(); |
| #261 | const skills = loadSkills(); |
| #262 | |
| #263 | const oneShots = agents.filter((a) => a.oneShot); |
| #264 | const featured = agents.filter((a) => a.featured); |
| #265 | |
| #266 | // Aggregate Metaplex skill coverage across the whole catalog so /agents can |
| #267 | // render a single shared skill rail and surface per-agent badges. |
| #268 | const metaplexSkillCounts = {}; |
| #269 | for (const a of agents) { |
| #270 | for (const skill of a.metaplexSkills) { |
| #271 | metaplexSkillCounts[skill] = (metaplexSkillCounts[skill] || 0) + 1; |
| #272 | } |
| #273 | } |
| #274 | |
| #275 | const metaplexSkill = { |
| #276 | installCommand: "npx skills add metaplex-foundation/skill", |
| #277 | mcpServerHint: { |
| #278 | mcpServers: { |
| #279 | metaplex: { |
| #280 | type: "http", |
| #281 | url: "https://modelcontextprotocol.name/mcp/metaplex", |
| #282 | }, |
| #283 | }, |
| #284 | }, |
| #285 | programs: [ |
| #286 | { id: "agent-registry", label: "Agent Registry", icon: "🪪", description: "On-chain agent identity, delegation, and execution via MPL Core asset-signer PDAs." }, |
| #287 | { id: "genesis", label: "Genesis", icon: "🚀", description: "Token launches — launchpool (48h deposit window) or bonding curve auto-graduating to Raydium CPMM." }, |
| #288 | { id: "core", label: "Core", icon: "🎨", description: "Next-gen NFTs with plugins, royalty enforcement, attributes, and asset-signer execute hooks." }, |
| #289 | { id: "token-metadata", label: "Token Metadata", icon: "🪙", description: "Classic fungibles, NFTs, pNFTs, and editions." }, |
| #290 | { id: "bubblegum", label: "Bubblegum", icon: "🫧", description: "Compressed NFTs via Merkle trees — required for 10k+ mint scale. Needs DAS-enabled RPC." }, |
| #291 | { id: "candy-machine", label: "Candy Machine", icon: "🍬", description: "Core Candy Machine drops with allowlists, start/end dates, mint limits, and payment guards." }, |
| #292 | ], |
| #293 | coverage: metaplexSkillCounts, |
| #294 | ergonomics: [ |
| #295 | { label: "CLI", package: "@metaplex-foundation/cli", entry: "mplx" }, |
| #296 | { label: "Umi SDK", package: "@metaplex-foundation/umi" }, |
| #297 | { label: "Agent Registry SDK", package: "@metaplex-foundation/mpl-agent-registry" }, |
| #298 | { label: "Core SDK", package: "@metaplex-foundation/mpl-core" }, |
| #299 | { label: "Token Metadata SDK", package: "@metaplex-foundation/mpl-token-metadata" }, |
| #300 | { label: "Bubblegum SDK", package: "@metaplex-foundation/mpl-bubblegum" }, |
| #301 | { label: "Candy Machine SDK", package: "@metaplex-foundation/mpl-core-candy-machine" }, |
| #302 | { label: "Genesis SDK", package: "@metaplex-foundation/genesis" }, |
| #303 | ], |
| #304 | }; |
| #305 | |
| #306 | const catalog = { |
| #307 | $schema: `${HOST}/schema/clawdAgentCatalog.v1.json`, |
| #308 | apiVersion: "1.0", |
| #309 | generatedAt: new Date().toISOString(), |
| #310 | hub: { |
| #311 | gallery: `${HOST}/agents`, |
| #312 | mint: `${HOST}/agents/mint`, |
| #313 | registry: `${HOST}/api/agents/registry`, |
| #314 | api: `${HOST}/api/agents`, |
| #315 | }, |
| #316 | stats: { |
| #317 | totalAgents: agents.length, |
| #318 | totalOneShots: oneShots.length, |
| #319 | totalFeatured: featured.length, |
| #320 | totalTemplates: templates.length, |
| #321 | byCategory: countByCategory(agents), |
| #322 | metaplexEnabledAgents: agents.filter((a) => a.metaplexSkills.length > 0).length, |
| #323 | tradingCapableAgents: agents.filter((a) => a.capabilities.includes("swap-execution")).length, |
| #324 | launchCapableAgents: agents.filter( |
| #325 | (a) => |
| #326 | a.capabilities.includes("metaplex-launch-token-genesis") || |
| #327 | a.capabilities.includes("metaplex-launch-bonding-curve") || |
| #328 | a.capabilities.includes("metaplex-create-agent-token") |
| #329 | ).length, |
| #330 | mintCapableAgents: agents.filter( |
| #331 | (a) => |
| #332 | a.capabilities.includes("metaplex-mint-core-nft") || |
| #333 | a.capabilities.includes("metaplex-mint-cnft") || |
| #334 | a.capabilities.includes("metaplex-deploy-candy-machine") |
| #335 | ).length, |
| #336 | }, |
| #337 | metaplexSkill, |
| #338 | categories: [ |
| #339 | { id: "defi", label: "DeFi", icon: "💰" }, |
| #340 | { id: "trading", label: "Trading", icon: "📈" }, |
| #341 | { id: "nft", label: "NFT", icon: "🎨" }, |
| #342 | { id: "analytics", label: "Analytics", icon: "📊" }, |
| #343 | { id: "security", label: "Security", icon: "🛡️" }, |
| #344 | { id: "dev-tools", label: "Dev Tools", icon: "🛠️" }, |
| #345 | { id: "education", label: "Education", icon: "📚" }, |
| #346 | { id: "governance", label: "Governance", icon: "🗳️" }, |
| #347 | { id: "research", label: "Research", icon: "🔎" }, |
| #348 | { id: "infrastructure", label: "Infrastructure", icon: "🏗️" }, |
| #349 | ], |
| #350 | deployPaths: [ |
| #351 | { id: "install", label: "Install", description: "Copy MCP config for Clawd Desktop / Cursor / ClawdOS" }, |
| #352 | { id: "chat", label: "Chat Now", description: "Open instant chat with the agent" }, |
| #353 | { id: "mint", label: "Mint On-chain", description: "Register as an MPL Core asset on Solana" }, |
| #354 | { id: "fork", label: "Fork", description: "Download the JSON, modify, and submit via PR" }, |
| #355 | ], |
| #356 | oneShots, |
| #357 | featured, |
| #358 | agents, |
| #359 | templates, |
| #360 | }; |
| #361 | |
| #362 | const registrationDocs = buildRegistrationDocs(agents, catalog.generatedAt); |
| #363 | const acpRegistry = buildAcpRegistry(agents, templates, catalog, registrationDocs); |
| #364 | const templateIndex = buildTemplateIndex(templates, catalog.generatedAt); |
| #365 | const skillsIndex = buildSkillsIndex(skills, catalog.generatedAt); |
| #366 | |
| #367 | writeJson(OUTPUT, catalog); |
| #368 | writeJson(path.join(TEMPLATES_DIR, "index.json"), templateIndex); |
| #369 | writeJson(path.join(SKILLS_DIR, "index.json"), skillsIndex); |
| #370 | writeStaticApi(catalog, agents, templates, skills, templateIndex, skillsIndex, registrationDocs, acpRegistry); |
| #371 | console.log(`✅ Wrote ${OUTPUT}`); |
| #372 | console.log(` ${agents.length} agents (${oneShots.length} one-shots, ${featured.length} featured)`); |
| #373 | console.log(` ${templates.length} templates`); |
| #374 | console.log(` ${skills.length} skills`); |
| #375 | console.log(` static API: ${path.relative(ROOT, PUBLIC_API_DIR)}`); |
| #376 | } |
| #377 | |
| #378 | function buildTemplateIndex(templates, generatedAt) { |
| #379 | const byCategory = {}; |
| #380 | for (const template of templates) { |
| #381 | byCategory[template.templateCategory] = (byCategory[template.templateCategory] || 0) + 1; |
| #382 | } |
| #383 | |
| #384 | return { |
| #385 | $schema: "https://solanaclawd.com/schemas/agent-template-index.v1.json", |
| #386 | apiVersion: "1.0", |
| #387 | generatedAt, |
| #388 | hub: { |
| #389 | gallery: `${HOST}/agents/templates`, |
| #390 | api: `${HOST}/api/agents/templates`, |
| #391 | schema: "https://solanaclawd.com/schemas/agent-template.v1.json", |
| #392 | }, |
| #393 | stats: { |
| #394 | totalTemplates: templates.length, |
| #395 | byCategory, |
| #396 | }, |
| #397 | templates, |
| #398 | }; |
| #399 | } |
| #400 | |
| #401 | function buildSkillsIndex(skills, generatedAt) { |
| #402 | const byCategory = {}; |
| #403 | for (const skill of skills) { |
| #404 | byCategory[skill.category] = (byCategory[skill.category] || 0) + 1; |
| #405 | } |
| #406 | |
| #407 | return { |
| #408 | $schema: "https://solanaclawd.com/schemas/skill-hub.v1.json", |
| #409 | apiVersion: "1.0", |
| #410 | generatedAt, |
| #411 | hub: { |
| #412 | gallery: `${HOST}/skills`, |
| #413 | api: `${HOST}/api/skills`, |
| #414 | attestation: "https://attest.solana.com", |
| #415 | sasProgram: "22zoJMtdu4tQc2PzL74ZUT7FrwgB1Udec8DdW4yw4BdG", |
| #416 | credentialAuthority: CLAWD_MINT, |
| #417 | }, |
| #418 | formalVerification: { |
| #419 | schema: "skill-schema.v1.json", |
| #420 | sasSchema: "OpenClawdSkillAttestation", |
| #421 | layoutBytes: [12, 32, 12, 8, 1], |
| #422 | fieldNames: ["skill_id", "verifier_pubkey", "proof_hash", "verification_timestamp", "is_formally_verified"], |
| #423 | verifierUI: "https://attest.solana.com", |
| #424 | workflowDoc: `${HOST}/skills/verify`, |
| #425 | }, |
| #426 | stats: { |
| #427 | totalSkills: skills.length, |
| #428 | verifiedSkills: skills.filter((skill) => skill.attestation.isFormallyVerified).length, |
| #429 | pendingVerification: skills.filter((skill) => !skill.attestation.isFormallyVerified).length, |
| #430 | byCategory, |
| #431 | }, |
| #432 | skills, |
| #433 | verificationFlow: [ |
| #434 | "1. build skill implementation and tests", |
| #435 | "2. compute proof_hash from the audited skill artifact bundle", |
| #436 | "3. issue OpenClawdSkillAttestation via the Solana Attestation Agent", |
| #437 | "4. verify attestation on attest.solana.com", |
| #438 | "5. attestation_pda recorded in skills/index.json, isFormallyVerified set to true", |
| #439 | ], |
| #440 | }; |
| #441 | } |
| #442 | |
| #443 | function buildRegistrationDocs(agents, generatedAt) { |
| #444 | return agents.map((agent) => { |
| #445 | const encodedId = encodeURIComponent(agent.identifier); |
| #446 | const registrationId = `openclawd:${agent.identifier}`; |
| #447 | |
| #448 | return { |
| #449 | schemaVersion: "erc-8004-agent-registration-v1", |
| #450 | protocol: "metaplex-agent-registry", |
| #451 | name: agent.title, |
| #452 | description: agent.description, |
| #453 | image: typeof agent.avatar === "string" && agent.avatar.startsWith("http") ? agent.avatar : `${HOST}/nich.jpg`, |
| #454 | external_url: `${HOST}/agents/${encodedId}`, |
| #455 | active: true, |
| #456 | createdAt: agent.createdAt, |
| #457 | updatedAt: generatedAt, |
| #458 | tags: agent.tags, |
| #459 | categories: [agent.category], |
| #460 | owner: { |
| #461 | organization: "OpenClawd", |
| #462 | website: "https://x402.wtf", |
| #463 | token: { |
| #464 | symbol: "CLAWD", |
| #465 | chain: "solana", |
| #466 | mint: CLAWD_MINT, |
| #467 | }, |
| #468 | }, |
| #469 | services: [ |
| #470 | { |
| #471 | name: "web", |
| #472 | endpoint: `${HOST}/agents/${encodedId}`, |
| #473 | }, |
| #474 | { |
| #475 | name: "A2A", |
| #476 | endpoint: `${HOST}/api/agents/a2a`, |
| #477 | version: "0.3.0", |
| #478 | }, |
| #479 | { |
| #480 | name: "catalog", |
| #481 | endpoint: `${HOST}/api/agents/catalog/${encodedId}.json`, |
| #482 | }, |
| #483 | { |
| #484 | name: "registration", |
| #485 | endpoint: `${HOST}/api/agents/registry/${encodedId}.json`, |
| #486 | }, |
| #487 | ], |
| #488 | registrations: [ |
| #489 | { |
| #490 | agentId: registrationId, |
| #491 | agentRegistry: "solana:mainnet:metaplex-agent-registry", |
| #492 | status: "pending-onchain-registration", |
| #493 | registrationUri: `${HOST}/api/agents/registry/${encodedId}.json`, |
| #494 | }, |
| #495 | ], |
| #496 | supportedTrust: ["reputation", "crypto-economic", "token-gated"], |
| #497 | metaplex: { |
| #498 | programs: agent.metaplexSkills, |
| #499 | sdk: "@metaplex-foundation/mpl-agent-registry", |
| #500 | expectedIdentity: { |
| #501 | asset: null, |
| #502 | agentIdentityPda: null, |
| #503 | assetSignerPda: null, |
| #504 | executiveProfilePda: null, |
| #505 | }, |
| #506 | registrationFlow: [ |
| #507 | "mint MPL Core agent asset", |
| #508 | "register AgentIdentity with this registrationUri", |
| #509 | "optionally register an executive profile", |
| #510 | "delegate execution only after operator review", |
| #511 | ], |
| #512 | }, |
| #513 | openclawd: { |
| #514 | identifier: agent.identifier, |
| #515 | capabilities: agent.capabilities, |
| #516 | payment: agent.payment, |
| #517 | agentToken: agent.agentToken, |
| #518 | }, |
| #519 | }; |
| #520 | }); |
| #521 | } |
| #522 | |
| #523 | function buildAcpRegistry(agents, templates, catalog, registrationDocs) { |
| #524 | return { |
| #525 | schemaVersion: "openclawd.acp.registry.v1", |
| #526 | protocol: "Agent Commerce Protocol", |
| #527 | generatedAt: catalog.generatedAt, |
| #528 | host: HOST, |
| #529 | discover: { |
| #530 | catalog: `${HOST}/api/agents/catalog`, |
| #531 | registry: `${HOST}/api/agents/registry`, |
| #532 | templates: `${HOST}/api/agents/templates`, |
| #533 | wellKnown: `${HOST}/.well-known/acp.json`, |
| #534 | }, |
| #535 | chain: { |
| #536 | namespace: "solana", |
| #537 | cluster: "mainnet-beta", |
| #538 | token: { |
| #539 | symbol: "CLAWD", |
| #540 | mint: CLAWD_MINT, |
| #541 | }, |
| #542 | registry: "metaplex-agent-registry", |
| #543 | }, |
| #544 | metaplex: catalog.metaplexSkill, |
| #545 | stats: catalog.stats, |
| #546 | agents: agents.map((agent) => { |
| #547 | const encodedId = encodeURIComponent(agent.identifier); |
| #548 | const registration = registrationDocs.find((doc) => doc.openclawd.identifier === agent.identifier); |
| #549 | return { |
| #550 | id: agent.identifier, |
| #551 | title: agent.title, |
| #552 | category: agent.category, |
| #553 | capabilities: agent.capabilities, |
| #554 | metaplexSkills: agent.metaplexSkills, |
| #555 | oneShot: agent.oneShot, |
| #556 | featured: agent.featured, |
| #557 | endpoints: { |
| #558 | catalog: `${HOST}/api/agents/catalog/${encodedId}.json`, |
| #559 | registration: `${HOST}/api/agents/registry/${encodedId}.json`, |
| #560 | a2a: `${HOST}/api/agents/a2a`, |
| #561 | mint: `${HOST}/agents/mint?template=${encodedId}`, |
| #562 | }, |
| #563 | registrations: registration.registrations, |
| #564 | }; |
| #565 | }), |
| #566 | templates: templates.map((template) => ({ |
| #567 | id: template.templateId, |
| #568 | name: template.name, |
| #569 | endpoint: `${HOST}/api/agents/templates/${encodeURIComponent(template.templateId)}.json`, |
| #570 | })), |
| #571 | }; |
| #572 | } |
| #573 | |
| #574 | function writeStaticApi(catalog, agents, templates, skills, templateIndex, skillsIndex, registrationDocs, acpRegistry) { |
| #575 | fs.rmSync(PUBLIC_API_DIR, { recursive: true, force: true }); |
| #576 | fs.rmSync(PUBLIC_SKILLS_DIR, { recursive: true, force: true }); |
| #577 | |
| #578 | writeJson(path.join(PUBLIC_API_DIR, "index.json"), { |
| #579 | name: "OpenClawd Agents API", |
| #580 | version: catalog.apiVersion, |
| #581 | generatedAt: catalog.generatedAt, |
| #582 | endpoints: { |
| #583 | catalog: "/api/agents/catalog", |
| #584 | registry: "/api/agents/registry", |
| #585 | acp: "/api/agents/acp", |
| #586 | templates: "/api/agents/templates", |
| #587 | }, |
| #588 | }); |
| #589 | |
| #590 | writeJson(path.join(PUBLIC_CATALOG_DIR, "index.json"), catalog); |
| #591 | writeJson(path.join(PUBLIC_API_DIR, "agents-catalog.json"), catalog); |
| #592 | writeJson(path.join(PUBLIC_API_DIR, "acp-registry.json"), acpRegistry); |
| #593 | writeJson(path.join(WELL_KNOWN_DIR, "acp.json"), acpRegistry); |
| #594 | writeJson(path.join(PUBLIC_TEMPLATES_DIR, "index.json"), templateIndex); |
| #595 | writeJson(path.join(PUBLIC_SKILLS_DIR, "index.json"), skillsIndex); |
| #596 | if (fs.existsSync(SKILL_SCHEMA_FILE)) { |
| #597 | writeJson(path.join(PUBLIC_SKILLS_DIR, "skill-schema.v1.json"), readJson(SKILL_SCHEMA_FILE)); |
| #598 | } |
| #599 | copyStaticMetadata(); |
| #600 | |
| #601 | for (const agent of agents) { |
| #602 | const sourcePath = path.join(SRC_DIR, agent.sourceFile); |
| #603 | if (fs.existsSync(sourcePath)) { |
| #604 | const raw = readJson(sourcePath); |
| #605 | writeJson(path.join(PUBLIC_CATALOG_DIR, `${agent.identifier}.json`), raw); |
| #606 | } |
| #607 | } |
| #608 | |
| #609 | writeJson(path.join(PUBLIC_REGISTRY_DIR, "index.json"), { |
| #610 | schemaVersion: "openclawd.metaplex.registry.index.v1", |
| #611 | generatedAt: catalog.generatedAt, |
| #612 | registry: "metaplex-agent-registry", |
| #613 | host: HOST, |
| #614 | count: registrationDocs.length, |
| #615 | agents: registrationDocs.map((doc) => ({ |
| #616 | id: doc.openclawd.identifier, |
| #617 | name: doc.name, |
| #618 | registrationUri: doc.registrations[0].registrationUri, |
| #619 | agentRegistry: doc.registrations[0].agentRegistry, |
| #620 | status: doc.registrations[0].status, |
| #621 | })), |
| #622 | }); |
| #623 | |
| #624 | for (const doc of registrationDocs) { |
| #625 | writeJson(path.join(PUBLIC_REGISTRY_DIR, `${doc.openclawd.identifier}.json`), doc); |
| #626 | } |
| #627 | |
| #628 | for (const template of templates) { |
| #629 | const sourcePath = path.join(TEMPLATES_DIR, `${template.templateId}.template.json`); |
| #630 | if (fs.existsSync(sourcePath)) { |
| #631 | writeJson(path.join(PUBLIC_TEMPLATES_DIR, `${template.templateId}.json`), readJson(sourcePath)); |
| #632 | } |
| #633 | } |
| #634 | |
| #635 | for (const skill of skills) { |
| #636 | const sourcePath = path.join(SKILLS_DIR, skill.skillId, "SKILL.md"); |
| #637 | if (!fs.existsSync(sourcePath)) continue; |
| #638 | writeJson(path.join(PUBLIC_SKILLS_DIR, `${skill.skillId}.json`), { |
| #639 | ...skill, |
| #640 | markdown: fs.readFileSync(sourcePath, "utf8"), |
| #641 | }); |
| #642 | } |
| #643 | } |
| #644 | |
| #645 | function copyStaticMetadata() { |
| #646 | const files = [ |
| #647 | ["server.json", path.join(PUBLIC_DIR, "server.json")], |
| #648 | ["robots.txt", path.join(PUBLIC_DIR, "robots.txt")], |
| #649 | ["humans.txt", path.join(PUBLIC_DIR, "humans.txt")], |
| #650 | [path.join(".well-known", "ai-plugin.json"), path.join(WELL_KNOWN_DIR, "ai-plugin.json")], |
| #651 | ]; |
| #652 | |
| #653 | for (const [from, to] of files) { |
| #654 | const source = path.join(ROOT, from); |
| #655 | if (!fs.existsSync(source)) continue; |
| #656 | fs.mkdirSync(path.dirname(to), { recursive: true }); |
| #657 | fs.copyFileSync(source, to); |
| #658 | } |
| #659 | } |
| #660 | |
| #661 | build(); |
| #662 |