import fs from "node:fs/promises"; import path from "node:path"; const allowedStatuses = new Set(["working", "idle", "warning", "offline"]); const allowedAvatarKinds = new Set([ "female-analyst", "male-ops", "female-researcher", "male-dispatcher", "female-observer", "male-maintainer" ]); function usage() { console.error("Usage: node scripts/sync-openclaw-agent-feed.mjs [target-json]"); process.exit(1); } function assertString(value, field, agentId) { if (typeof value !== "string" || value.trim() === "") { throw new Error(`Invalid ${field} for agent ${agentId}`); } } function assertNumber(value, field, agentId) { if (typeof value !== "number" || Number.isNaN(value)) { throw new Error(`Invalid ${field} for agent ${agentId}`); } } function normalizeAgent(agent) { const agentId = typeof agent.id === "string" ? agent.id : "unknown"; assertString(agent.id, "id", agentId); assertString(agent.name, "name", agentId); assertString(agent.role, "role", agentId); assertString(agent.host, "host", agentId); assertString(agent.owner, "owner", agentId); assertString(agent.currentTask, "currentTask", agentId); assertString(agent.taskId, "taskId", agentId); assertString(agent.taskStage, "taskStage", agentId); assertString(agent.uptime, "uptime", agentId); assertString(agent.lastHeartbeat, "lastHeartbeat", agentId); assertString(agent.updatedAt, "updatedAt", agentId); assertString(agent.lastOutput, "lastOutput", agentId); assertNumber(agent.queueDepth, "queueDepth", agentId); assertNumber(agent.todayCompleted, "todayCompleted", agentId); if (!allowedStatuses.has(agent.status)) { throw new Error(`Invalid status for agent ${agentId}`); } if (agent.avatarKind && !allowedAvatarKinds.has(agent.avatarKind)) { throw new Error(`Invalid avatarKind for agent ${agentId}`); } return { id: agent.id, name: agent.name, role: agent.role, avatarKind: agent.avatarKind ?? "male-dispatcher", status: agent.status, statusLabel: typeof agent.statusLabel === "string" && agent.statusLabel ? agent.statusLabel : undefined, host: agent.host, owner: agent.owner, currentTask: agent.currentTask, taskId: agent.taskId, taskStage: agent.taskStage, queueDepth: agent.queueDepth, todayCompleted: agent.todayCompleted, uptime: agent.uptime, lastHeartbeat: agent.lastHeartbeat, updatedAt: agent.updatedAt, lastOutput: agent.lastOutput, lastError: typeof agent.lastError === "string" ? agent.lastError : undefined, tags: Array.isArray(agent.tags) ? agent.tags.filter((tag) => typeof tag === "string") : [] }; } async function main() { const [, , sourceArg, targetArg] = process.argv; if (!sourceArg) { usage(); } const sourcePath = path.resolve(sourceArg); const targetPath = path.resolve(targetArg ?? path.join(process.cwd(), "storage", "agents", "openclaw-agents.json")); const raw = await fs.readFile(sourcePath, "utf8"); const parsed = JSON.parse(raw); if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.agents)) { throw new Error("Feed JSON must contain an agents array"); } const normalized = { fetchedAt: typeof parsed.fetchedAt === "string" ? parsed.fetchedAt : new Date().toISOString(), agents: parsed.agents.map(normalizeAgent) }; await fs.mkdir(path.dirname(targetPath), { recursive: true }); const tempPath = `${targetPath}.tmp`; await fs.writeFile(tempPath, JSON.stringify(normalized, null, 2), "utf8"); await fs.rename(tempPath, targetPath); console.log(`OpenClaw agent feed synced to ${targetPath}`); console.log(`Agents: ${normalized.agents.length}`); } main().catch((error) => { console.error(error instanceof Error ? error.message : String(error)); process.exit(1); });