/** * OpenDesign plugin for OpenCode.ai * * Injects the opendesign bootstrap skill via chat transform, * and registers the skills directory via config hook (no symlinks needed). */ import path from 'path'; import fs from 'fs'; import os from 'os'; import { fileURLToPath } from 'url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Minimal frontmatter extractor (avoids external deps). const extractAndStripFrontmatter = (content) => { const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/); if (!match) return { frontmatter: {}, content }; const frontmatterStr = match[1]; const body = match[2]; const frontmatter = {}; for (const line of frontmatterStr.split('\n')) { const colonIdx = line.indexOf(':'); if (colonIdx > 0) { const key = line.slice(0, colonIdx).trim(); const value = line.slice(colonIdx + 1).trim().replace(/^["']|["']$/g, ''); frontmatter[key] = value; } } return { frontmatter, content: body }; }; const normalizePath = (p, homeDir) => { if (!p || typeof p !== 'string') return null; let normalized = p.trim(); if (!normalized) return null; if (normalized.startsWith('~/')) { normalized = path.join(homeDir, normalized.slice(2)); } else if (normalized === '~') { normalized = homeDir; } return path.resolve(normalized); }; export const OpenDesignPlugin = async ({ client, directory }) => { const homeDir = os.homedir(); const opendesignSkillsDir = path.resolve(__dirname, '../../skills'); const envConfigDir = normalizePath(process.env.OPENCODE_CONFIG_DIR, homeDir); const configDir = envConfigDir || path.join(homeDir, '.config/opencode'); const getBootstrapContent = () => { const skillPath = path.join(opendesignSkillsDir, 'opendesign', 'SKILL.md'); if (!fs.existsSync(skillPath)) return null; const fullContent = fs.readFileSync(skillPath, 'utf8'); const { content } = extractAndStripFrontmatter(fullContent); const toolMapping = `**Tool Mapping for OpenCode:** When OpenDesign skills reference tools you don't have, substitute OpenCode equivalents: - \`TodoWrite\` → \`todowrite\` - \`Task\` tool with subagents → OpenCode's subagent system (@mention) - \`Skill\` tool → OpenCode's native \`skill\` tool - \`Read\`, \`Write\`, \`Edit\`, \`Bash\` → your native tools Use OpenCode's native \`skill\` tool to list and load the other OpenDesign skills (wireframe, make-a-deck, interactive-prototype, etc.) on demand.`; return ` You have OpenDesign loaded. **The opendesign entry-point skill is included below. It is ALREADY LOADED — you are currently following it. Do NOT use the skill tool to load "opendesign" again.** ${content} ${toolMapping} `; }; return { // Register the skills directory so OpenCode discovers every SKILL.md // without the user editing opencode.json or symlinking. config: async (config) => { config.skills = config.skills || {}; config.skills.paths = config.skills.paths || []; if (!config.skills.paths.includes(opendesignSkillsDir)) { config.skills.paths.push(opendesignSkillsDir); } }, // Use system prompt transform for compatibility with current OpenCode builds. 'experimental.chat.system.transform': async (_input, output) => { const bootstrap = getBootstrapContent(); if (bootstrap) { (output.system ||= []).push(bootstrap); } } }; };