import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; const TEMPLATE_DIR = path.resolve(new URL('../vendor/agent-harness/agent-workspace', import.meta.url).pathname); const SEED_FILES = [ 'SOUL.md', 'IDENTITY.md', 'AGENTS.md', 'USER.md', 'MEMORY.md', 'BOOTSTRAP.md', 'HEARTBEAT.md', 'TOOLS.md', 'PERSONA.md', 'skills/skill-creator/SKILL.md', ]; export function normalizeAssistantName(value) { const text = String(value || '').replace(/[\r\n\t]/g, ' ').trim().replace(/\s+/g, ' ').slice(0, 32); if (!text || /[<>&]/.test(text)) return 'Jarvis'; return text; } export function agentWorkspaceDir() { return path.join(process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local/share'), 'jarvis/workspace'); } export function templateWorkspaceDir() { return TEMPLATE_DIR; } function copyIfMissing(from, to) { if (fs.existsSync(to)) return false; fs.mkdirSync(path.dirname(to), { recursive: true }); fs.copyFileSync(from, to); return true; } function escapeRegExp(value) { return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } function renameAssistant(text, previous, next) { if (!previous || previous === next) return text; return String(text || '').replace(new RegExp(escapeRegExp(previous), 'g'), next); } export function applyAssistantName(dir, name) { const who = normalizeAssistantName(name); const identity = path.join(dir, 'IDENTITY.md'); let text = ''; try { text = fs.readFileSync(identity, 'utf8'); } catch { text = '# IDENTITY.md\n\n'; } const previous = (text.match(/\*\*Name:\*\*\s*(.+)/) || [])[1]?.trim() || 'Jarvis'; text = renameAssistant(text, previous, who); if (/\*\*Name:\*\*/.test(text)) text = text.replace(/\*\*Name:\*\*.*/, `**Name:** ${who}`); else text += `\n- **Name:** ${who}\n`; fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(identity, text); const soul = path.join(dir, 'SOUL.md'); try { let soulText = renameAssistant(fs.readFileSync(soul, 'utf8'), previous, who); soulText = soulText.replace(/^# SOUL\.md:.*/m, `# SOUL.md: ${who}`); fs.writeFileSync(soul, soulText); } catch {} const memory = path.join(dir, 'MEMORY.md'); try { let mem = renameAssistant(fs.readFileSync(memory, 'utf8'), previous, who); if (/Assistant name:/.test(mem)) mem = mem.replace(/Assistant name:.*/, `Assistant name: ${who}`); fs.writeFileSync(memory, mem); } catch {} return who; } export function applyAssistantPrompt(dir, prompt) { const notes = String(prompt || '').replace(/\0/g, '').replace(/\s+$/g, '').slice(0, 4000); const file = path.join(dir, 'PERSONA.md'); const body = notes ? `# PERSONA.md\n\n${notes}\n` : '# PERSONA.md\n\n'; fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(file, body); return notes; } export function ensureAgentWorkspace({ name = 'Jarvis', prompt = '' } = {}) { const dest = agentWorkspaceDir(); fs.mkdirSync(path.join(dest, 'memory'), { recursive: true }); fs.mkdirSync(path.join(dest, 'skills'), { recursive: true }); for (const rel of SEED_FILES) { copyIfMissing(path.join(TEMPLATE_DIR, rel), path.join(dest, rel)); } applyAssistantName(dest, name); applyAssistantPrompt(dest, prompt); return dest; }