import fs from "node:fs"; import path from "node:path"; import { DEFAULT_IDENTITY_FILENAME } from "./workspace.js"; export type AgentIdentityFile = { name?: string; emoji?: string; theme?: string; creature?: string; vibe?: string; avatar?: string; }; const IDENTITY_PLACEHOLDER_VALUES = new Set([ "pick something you like", "ai? robot? familiar? ghost in the machine? something weirder?", "how do you come across? sharp? warm? chaotic? calm?", "your signature - pick one that feels right", "workspace-relative path, http(s) url, or data uri", ]); function normalizeIdentityValue(value: string): string { let normalized = value.trim(); normalized = normalized.replace(/^[*_]+|[*_]+$/g, "").trim(); if (normalized.startsWith("(") && normalized.endsWith(")")) { normalized = normalized.slice(1, -1).trim(); } normalized = normalized.replace(/[\u2013\u2014]/g, "-"); normalized = normalized.replace(/\s+/g, " ").toLowerCase(); return normalized; } function isIdentityPlaceholder(value: string): boolean { const normalized = normalizeIdentityValue(value); return IDENTITY_PLACEHOLDER_VALUES.has(normalized); } export function parseIdentityMarkdown(content: string): AgentIdentityFile { const identity: AgentIdentityFile = {}; const lines = content.split(/\r?\n/); for (const line of lines) { const cleaned = line.trim().replace(/^\s*-\s*/, ""); const colonIndex = cleaned.indexOf(":"); if (colonIndex === -1) { continue; } const label = cleaned.slice(0, colonIndex).replace(/[*_]/g, "").trim().toLowerCase(); const value = cleaned .slice(colonIndex + 1) .replace(/^[*_]+|[*_]+$/g, "") .trim(); if (!value) { continue; } if (isIdentityPlaceholder(value)) { continue; } if (label === "name") { identity.name = value; } if (label === "emoji") { identity.emoji = value; } if (label === "creature") { identity.creature = value; } if (label === "vibe") { identity.vibe = value; } if (label === "theme") { identity.theme = value; } if (label === "avatar") { identity.avatar = value; } } return identity; } export function identityHasValues(identity: AgentIdentityFile): boolean { return Boolean( identity.name || identity.emoji || identity.theme || identity.creature || identity.vibe || identity.avatar, ); } export function loadIdentityFromFile(identityPath: string): AgentIdentityFile | null { try { const content = fs.readFileSync(identityPath, "utf-8"); const parsed = parseIdentityMarkdown(content); if (!identityHasValues(parsed)) { return null; } return parsed; } catch { return null; } } export function loadAgentIdentityFromWorkspace(workspace: string): AgentIdentityFile | null { const identityPath = path.join(workspace, DEFAULT_IDENTITY_FILENAME); return loadIdentityFromFile(identityPath); }