oh-my-design-cli 1.3.8 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +8 -8
- package/README.ko.md +28 -7
- package/README.md +71 -16
- package/README.zh-TW.md +8 -8
- package/agents/omd-asset-curator.md +2 -7
- package/agents/omd-codex-image.md +49 -0
- package/agents/omd-designer-review.md +38 -0
- package/agents/omd-final-qa.md +40 -0
- package/agents/omd-kr-writer.md +35 -0
- package/agents/omd-locale-adapter.md +32 -0
- package/agents/omd-master.md +3 -13
- package/agents/omd-orchestrator.md +34 -0
- package/data/reference-audits/2026-05-14-kr10.md +72 -0
- package/data/reference-audits/2026-05-15-kr10.md +124 -0
- package/data/reference-fingerprints.json +253 -3
- package/data/research/2026-05-18-agent-landscape.md +69 -0
- package/data/research/2026-05-18-kr-style-presets.md +572 -0
- package/dist/bin/oh-my-design.js +6 -3
- package/dist/bin/oh-my-design.js.map +1 -1
- package/dist/{install-skills-MVXVXYAY.js → install-skills-IETT2TBJ.js} +91 -8
- package/dist/install-skills-IETT2TBJ.js.map +1 -0
- package/package.json +9 -3
- package/skills/omd-apply/SKILL.md +0 -1
- package/skills/omd-codex-image/SKILL.md +162 -0
- package/skills/omd-designer-review/SKILL.md +146 -0
- package/skills/omd-final-qa/SKILL.md +153 -0
- package/skills/omd-kr-writer/SKILL.md +229 -0
- package/skills/omd-locale-adapter/SKILL.md +124 -0
- package/skills/omd-orchestrator/SKILL.md +124 -0
- package/web/references/29cm/DESIGN.md +11 -2
- package/web/references/ably/DESIGN.md +12 -2
- package/web/references/airbnb/DESIGN.md +17 -2
- package/web/references/airtable/DESIGN.md +20 -0
- package/web/references/apple/DESIGN.md +17 -2
- package/web/references/baemin/DESIGN.md +11 -2
- package/web/references/banksalad/DESIGN.md +17 -2
- package/web/references/bmw/DESIGN.md +14 -0
- package/web/references/bunjang/DESIGN.md +308 -0
- package/web/references/cal/DESIGN.md +14 -0
- package/web/references/catchtable/DESIGN.md +262 -0
- package/web/references/channeltalk/DESIGN.md +374 -0
- package/web/references/classum/DESIGN.md +217 -0
- package/web/references/claude/DESIGN.md +11 -2
- package/web/references/clay/DESIGN.md +19 -0
- package/web/references/clickhouse/DESIGN.md +19 -0
- package/web/references/cohere/DESIGN.md +20 -0
- package/web/references/coinbase/DESIGN.md +14 -0
- package/web/references/composio/DESIGN.md +14 -0
- package/web/references/coupang/DESIGN.md +17 -2
- package/web/references/cursor/DESIGN.md +20 -0
- package/web/references/dabang/DESIGN.md +210 -0
- package/web/references/dcard/DESIGN.md +11 -2
- package/web/references/elevenlabs/DESIGN.md +20 -0
- package/web/references/expo/DESIGN.md +20 -0
- package/web/references/fastcampus/DESIGN.md +460 -0
- package/web/references/ferrari/DESIGN.md +14 -0
- package/web/references/figma/DESIGN.md +17 -2
- package/web/references/flex/DESIGN.md +309 -0
- package/web/references/framer/DESIGN.md +20 -0
- package/web/references/freee/DESIGN.md +16 -2
- package/web/references/gangnamunni/DESIGN.md +18 -2
- package/web/references/gmarket/DESIGN.md +464 -0
- package/web/references/hashicorp/DESIGN.md +19 -0
- package/web/references/ibm/DESIGN.md +20 -0
- package/web/references/inflearn/DESIGN.md +396 -0
- package/web/references/intercom/DESIGN.md +14 -0
- package/web/references/jumpit/DESIGN.md +366 -0
- package/web/references/kakao/DESIGN.md +14 -0
- package/web/references/kakaobank/DESIGN.md +17 -2
- package/web/references/kakaopay/DESIGN.md +17 -2
- package/web/references/karrot/DESIGN.md +16 -2
- package/web/references/kbank/DESIGN.md +195 -0
- package/web/references/kraken/DESIGN.md +14 -0
- package/web/references/krds/DESIGN.md +17 -2
- package/web/references/kream/DESIGN.md +382 -0
- package/web/references/kurly/DESIGN.md +11 -2
- package/web/references/lamborghini/DESIGN.md +14 -0
- package/web/references/line/DESIGN.md +17 -2
- package/web/references/linear.app/DESIGN.md +17 -2
- package/web/references/lovable/DESIGN.md +14 -0
- package/web/references/lunit/DESIGN.md +249 -0
- package/web/references/mercari/DESIGN.md +11 -2
- package/web/references/minimax/DESIGN.md +14 -0
- package/web/references/mintlify/DESIGN.md +14 -0
- package/web/references/miro/DESIGN.md +20 -0
- package/web/references/mistral.ai/DESIGN.md +20 -0
- package/web/references/mongodb/DESIGN.md +19 -0
- package/web/references/musinsa/DESIGN.md +11 -2
- package/web/references/naver/DESIGN.md +17 -2
- package/web/references/notion/DESIGN.md +11 -2
- package/web/references/nvidia/DESIGN.md +11 -2
- package/web/references/ohouse/DESIGN.md +11 -2
- package/web/references/oliveyoung/DESIGN.md +342 -0
- package/web/references/ollama/DESIGN.md +14 -0
- package/web/references/opencode.ai/DESIGN.md +20 -0
- package/web/references/pinkoi/DESIGN.md +11 -2
- package/web/references/pinterest/DESIGN.md +19 -0
- package/web/references/posthog/DESIGN.md +20 -0
- package/web/references/qanda/DESIGN.md +11 -2
- package/web/references/raycast/DESIGN.md +19 -0
- package/web/references/remember/DESIGN.md +17 -2
- package/web/references/renault/DESIGN.md +14 -0
- package/web/references/replicate/DESIGN.md +14 -0
- package/web/references/resend/DESIGN.md +20 -0
- package/web/references/revolut/DESIGN.md +14 -0
- package/web/references/ridi/DESIGN.md +11 -2
- package/web/references/runwayml/DESIGN.md +14 -0
- package/web/references/sanity/DESIGN.md +20 -0
- package/web/references/sentry/DESIGN.md +14 -0
- package/web/references/socar/DESIGN.md +17 -2
- package/web/references/spacex/DESIGN.md +11 -2
- package/web/references/spotify/DESIGN.md +14 -0
- package/web/references/stripe/DESIGN.md +11 -2
- package/web/references/supabase/DESIGN.md +20 -0
- package/web/references/superhuman/DESIGN.md +20 -0
- package/web/references/tesla/DESIGN.md +11 -2
- package/web/references/together.ai/DESIGN.md +20 -0
- package/web/references/toss/DESIGN.md +16 -2
- package/web/references/toss-securities/DESIGN.md +193 -0
- package/web/references/tving/DESIGN.md +259 -0
- package/web/references/uber/DESIGN.md +19 -0
- package/web/references/upbit/DESIGN.md +276 -0
- package/web/references/upstage/DESIGN.md +214 -0
- package/web/references/vercel/DESIGN.md +17 -2
- package/web/references/voltagent/DESIGN.md +14 -0
- package/web/references/wadiz/DESIGN.md +344 -0
- package/web/references/wanted/DESIGN.md +16 -2
- package/web/references/warp/DESIGN.md +14 -0
- package/web/references/webflow/DESIGN.md +14 -0
- package/web/references/wise/DESIGN.md +19 -0
- package/web/references/x.ai/DESIGN.md +14 -0
- package/web/references/yanolja/DESIGN.md +17 -2
- package/web/references/yeogiotte/DESIGN.md +18 -2
- package/web/references/zapier/DESIGN.md +20 -0
- package/web/references/zigbang/DESIGN.md +12 -2
- package/web/references/zigzag/DESIGN.md +17 -2
- package/agents/omd-3d-blender.md +0 -269
- package/dist/install-skills-MVXVXYAY.js.map +0 -1
|
@@ -310,22 +310,106 @@ async function runInstallSkills(opts = {}) {
|
|
|
310
310
|
console.error(pc.red("omd install-skills: package data not found"));
|
|
311
311
|
return 1;
|
|
312
312
|
}
|
|
313
|
-
const
|
|
314
|
-
if (
|
|
313
|
+
const allSkills = listShippedSkills(packageRoot);
|
|
314
|
+
if (allSkills.length === 0) {
|
|
315
315
|
console.error(pc.red("omd install-skills: no skills found in package"));
|
|
316
316
|
return 1;
|
|
317
317
|
}
|
|
318
|
-
const
|
|
319
|
-
const plans = targets.map((t) => planForTarget(projectRoot, t));
|
|
318
|
+
const allAgents = listCanonicalAgents(packageRoot);
|
|
320
319
|
const force = opts.force ?? false;
|
|
321
320
|
p.intro(
|
|
322
321
|
pc.bold("omd install-skills") + pc.dim(` (${relative(process.cwd(), projectRoot) || "."})`)
|
|
323
322
|
);
|
|
323
|
+
const isTTY = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
324
|
+
const nonInteractive = opts.all || !isTTY || opts.skillsFilter || opts.agentsFilter;
|
|
325
|
+
const detected = autoDetectTargets(projectRoot);
|
|
326
|
+
const presence = detectInstalledAgents(projectRoot);
|
|
327
|
+
const actuallyDetected = [
|
|
328
|
+
presence.claudeCode ? "claude-code" : null,
|
|
329
|
+
presence.codex ? "codex" : null,
|
|
330
|
+
presence.opencode ? "opencode" : null
|
|
331
|
+
].filter((x) => x !== null);
|
|
332
|
+
let skills;
|
|
333
|
+
let canonicalAgents;
|
|
334
|
+
let targets;
|
|
335
|
+
if (nonInteractive) {
|
|
336
|
+
skills = opts.skillsFilter ? allSkills.filter((s) => opts.skillsFilter.includes(s)) : allSkills;
|
|
337
|
+
canonicalAgents = opts.agentsFilter ? allAgents.filter((a) => opts.agentsFilter.includes(a.replace(/\.md$/, ""))) : allAgents;
|
|
338
|
+
targets = opts.agents ? opts.agents : opts.all ? ["claude-code", "codex", "opencode"] : detected;
|
|
339
|
+
} else {
|
|
340
|
+
const skillResult = await p.multiselect({
|
|
341
|
+
message: "Skills \xB7 space = \uD1A0\uAE00 \xB7 a = \uC804\uCCB4 \xB7 enter = \uD655\uC778 (default ALL)",
|
|
342
|
+
options: allSkills.map((s) => ({ value: s, label: s, hint: "omd skill" })),
|
|
343
|
+
initialValues: allSkills,
|
|
344
|
+
required: true
|
|
345
|
+
});
|
|
346
|
+
if (p.isCancel(skillResult)) {
|
|
347
|
+
p.cancel("Install cancelled.");
|
|
348
|
+
return 130;
|
|
349
|
+
}
|
|
350
|
+
skills = skillResult;
|
|
351
|
+
if (allAgents.length > 0) {
|
|
352
|
+
const agentResult = await p.multiselect({
|
|
353
|
+
message: "Sub-agents \xB7 space = \uD1A0\uAE00 \xB7 a = \uC804\uCCB4 \xB7 enter = \uD655\uC778 (default ALL)",
|
|
354
|
+
options: allAgents.map((a) => ({
|
|
355
|
+
value: a,
|
|
356
|
+
label: a.replace(/\.md$/, ""),
|
|
357
|
+
hint: "subagent"
|
|
358
|
+
})),
|
|
359
|
+
initialValues: allAgents,
|
|
360
|
+
required: false
|
|
361
|
+
});
|
|
362
|
+
if (p.isCancel(agentResult)) {
|
|
363
|
+
p.cancel("Install cancelled.");
|
|
364
|
+
return 130;
|
|
365
|
+
}
|
|
366
|
+
canonicalAgents = agentResult;
|
|
367
|
+
} else {
|
|
368
|
+
canonicalAgents = [];
|
|
369
|
+
}
|
|
370
|
+
if (opts.agents) {
|
|
371
|
+
targets = opts.agents;
|
|
372
|
+
} else {
|
|
373
|
+
const targetResult = await p.multiselect({
|
|
374
|
+
message: "Agent channels \xB7 space = \uD1A0\uAE00 \xB7 enter = \uD655\uC778 \xB7 \uCD5C\uC18C 1\uAC1C \uC120\uD0DD",
|
|
375
|
+
options: [
|
|
376
|
+
{
|
|
377
|
+
value: "claude-code",
|
|
378
|
+
label: "Claude Code",
|
|
379
|
+
hint: actuallyDetected.includes("claude-code") ? ".claude/ detected" : ""
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
value: "codex",
|
|
383
|
+
label: "Codex",
|
|
384
|
+
hint: actuallyDetected.includes("codex") ? ".codex/ detected" : ""
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
value: "opencode",
|
|
388
|
+
label: "OpenCode",
|
|
389
|
+
hint: actuallyDetected.includes("opencode") ? ".opencode/ detected" : ""
|
|
390
|
+
}
|
|
391
|
+
],
|
|
392
|
+
initialValues: [],
|
|
393
|
+
required: true
|
|
394
|
+
});
|
|
395
|
+
if (p.isCancel(targetResult)) {
|
|
396
|
+
p.cancel("Install cancelled.");
|
|
397
|
+
return 130;
|
|
398
|
+
}
|
|
399
|
+
targets = targetResult;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
const plans = targets.map((t) => planForTarget(projectRoot, t));
|
|
324
403
|
p.log.message(
|
|
325
|
-
pc.bold(
|
|
404
|
+
pc.bold(`Skills (${skills.length}): `) + skills.map((s) => pc.cyan(s)).join(", ")
|
|
326
405
|
);
|
|
406
|
+
if (canonicalAgents.length > 0) {
|
|
407
|
+
p.log.message(
|
|
408
|
+
pc.bold(`Agents (${canonicalAgents.length}): `) + canonicalAgents.map((a) => pc.cyan(a.replace(/\.md$/, ""))).join(", ")
|
|
409
|
+
);
|
|
410
|
+
}
|
|
327
411
|
p.log.message(
|
|
328
|
-
pc.bold("
|
|
412
|
+
pc.bold("Targets: ") + targets.map((t) => pc.cyan(t)).join(", ")
|
|
329
413
|
);
|
|
330
414
|
const results = [];
|
|
331
415
|
for (const plan of plans) {
|
|
@@ -333,7 +417,6 @@ async function runInstallSkills(opts = {}) {
|
|
|
333
417
|
results.push(installOne(packageRoot, plan, skill, force));
|
|
334
418
|
}
|
|
335
419
|
}
|
|
336
|
-
const canonicalAgents = listCanonicalAgents(packageRoot);
|
|
337
420
|
for (const target of targets) {
|
|
338
421
|
if (target === "claude-code") {
|
|
339
422
|
for (const filename of canonicalAgents) {
|
|
@@ -417,4 +500,4 @@ async function runInstallSkills(opts = {}) {
|
|
|
417
500
|
export {
|
|
418
501
|
runInstallSkills
|
|
419
502
|
};
|
|
420
|
-
//# sourceMappingURL=install-skills-
|
|
503
|
+
//# sourceMappingURL=install-skills-IETT2TBJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/install-skills.ts","../src/core/agent-detect.ts"],"sourcesContent":["import * as p from '@clack/prompts';\nimport pc from 'picocolors';\nimport {\n readFileSync,\n readdirSync,\n writeFileSync,\n existsSync,\n mkdirSync,\n} from 'node:fs';\nimport { join, dirname, relative } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { detectInstalledAgents } from '../core/agent-detect.js';\n\nexport type SkillTarget = 'claude-code' | 'codex' | 'opencode';\n\nexport interface InstallSkillsOptions {\n dir?: string;\n agents?: SkillTarget[];\n force?: boolean;\n /** Non-interactive: install all skills + all agents without TUI prompt.\n * Default false → interactive multiselect when TTY available. */\n all?: boolean;\n /** Pre-select specific skill names from CLI flag (`--skills omd-init,omd-apply`).\n * Overrides interactive prompt when set. */\n skillsFilter?: string[];\n /** Pre-select specific agent names. Overrides interactive prompt when set. */\n agentsFilter?: string[];\n}\n\ninterface InstallPlan {\n target: SkillTarget;\n destDir: string;\n layout: 'folder' | 'flat';\n}\n\nfunction findPackageRoot(): string | null {\n let cur = dirname(fileURLToPath(import.meta.url));\n for (let i = 0; i < 8; i++) {\n if (existsSync(join(cur, 'skills'))) return cur;\n const parent = dirname(cur);\n if (parent === cur) break;\n cur = parent;\n }\n return null;\n}\n\nfunction listShippedSkills(packageRoot: string): string[] {\n const skillsDir = join(packageRoot, 'skills');\n if (!existsSync(skillsDir)) return [];\n return readdirSync(skillsDir)\n .filter((name) => existsSync(join(skillsDir, name, 'SKILL.md')))\n .sort();\n}\n\n/**\n * Canonical agent definitions live at `agents/<name>.md` (markdown with YAML\n * frontmatter). Channel-specific files (.claude/agents/*.md, .codex/agents/*.toml)\n * are generated artifacts — never the source of truth.\n *\n * The package ships only `agents/` and the generator emits per-channel files\n * into the user's project on `omd install-skills`.\n */\nfunction listCanonicalAgents(packageRoot: string): string[] {\n const dir = join(packageRoot, 'agents');\n if (!existsSync(dir)) return [];\n return readdirSync(dir)\n .filter((name) => name.startsWith('omd-') && name.endsWith('.md'))\n .sort();\n}\n\ninterface ParsedAgent {\n name: string;\n description: string;\n tools: string[];\n model: string;\n body: string;\n}\n\n/** Parse `agents/<name>.md` YAML frontmatter + body into structured form. */\nfunction parseCanonicalAgent(packageRoot: string, filename: string): ParsedAgent {\n const src = readFileSync(join(packageRoot, 'agents', filename), 'utf8');\n const match = /^---\\n([\\s\\S]*?)\\n---\\n([\\s\\S]*)$/.exec(src);\n if (!match) {\n throw new Error(`agents/${filename}: missing YAML frontmatter`);\n }\n const fm = match[1];\n const body = match[2];\n const grab = (key: string): string => {\n const re = new RegExp(`^${key}:\\\\s*(.+)$`, 'm');\n const m = re.exec(fm);\n return m ? m[1].trim().replace(/^[\"']|[\"']$/g, '') : '';\n };\n return {\n name: grab('name') || filename.replace(/\\.md$/, ''),\n description: grab('description'),\n tools: grab('tools')\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean),\n model: grab('model') || 'sonnet',\n body,\n };\n}\n\n/** Map Claude tool names to Codex tool names (best-effort). */\nfunction claudeToolsToCodex(tools: string[]): string[] {\n const m: Record<string, string> = {\n Read: 'read_file',\n Write: 'write_file',\n Edit: 'edit_file',\n Bash: 'shell',\n Glob: 'search',\n Grep: 'search',\n WebFetch: 'web_fetch',\n WebSearch: 'search',\n Agent: 'spawn_agent',\n TaskCreate: 'task',\n TaskUpdate: 'task',\n TaskList: 'task',\n };\n const out = new Set<string>();\n for (const t of tools) out.add(m[t] ?? t.toLowerCase());\n return [...out].sort();\n}\n\n/** Map Claude model alias to Codex/OpenAI model id (best-effort). */\nfunction claudeModelToCodex(model: string): string {\n const m: Record<string, string> = {\n haiku: 'gpt-4.1-mini',\n sonnet: 'gpt-4.1',\n opus: 'gpt-4.1',\n };\n return m[model.toLowerCase()] ?? 'gpt-4.1';\n}\n\n/** Render a canonical agent as a Claude Code subagent file.\n * IMPORTANT: Claude Code's subagent parser requires YAML frontmatter (`---`)\n * as the FIRST line of the file. Any preceding content (HTML comments, blank\n * lines) breaks discovery. So we encode the managed-by-omd marker as a\n * custom frontmatter field (`omd_managed: true`) instead of an HTML comment.\n */\nfunction renderClaudeAgent(a: ParsedAgent): string {\n const fm = [\n '---',\n `name: ${a.name}`,\n `description: ${a.description}`,\n `tools: ${a.tools.join(', ')}`,\n `model: ${a.model}`,\n `omd_managed: true`,\n '---',\n '',\n ].join('\\n');\n return fm + a.body;\n}\n\n/** Render a canonical agent as a Codex TOML file (declarative pointer). */\nfunction renderCodexAgent(a: ParsedAgent): string {\n const tools = claudeToolsToCodex(a.tools);\n const model = claudeModelToCodex(a.model);\n const desc = a.description.replace(/\"/g, '\\\\\"');\n return [\n `[agent]`,\n `name = \"${a.name}\"`,\n `description = \"${desc}\"`,\n `model = \"${model}\"`,\n `max_threads = 1`,\n `allowed_tools = [${tools.map((t) => `\"${t}\"`).join(', ')}]`,\n '',\n `instructions = \"\"\"`,\n `Source of truth: agents/${a.name}.md (canonical). The full role spec is`,\n `mirrored to .claude/agents/${a.name}.md when installed for Claude Code.`,\n `Follow that spec verbatim regardless of channel.`,\n '',\n `Codex notes:`,\n `- Spawn sub-agents via spawn_agent with names matching .codex/agents/<name>.toml`,\n `- Use shell to invoke CLI helpers (omd init prepare, omd remember, git apply, npx axe-core, npx lighthouse)`,\n `- All artifacts go inside .omd/runs/run-<latest>/ (or skills/omd-lab-02-design-harness/runs/<lab-version>-...)`,\n `\"\"\"`,\n '',\n ].join('\\n');\n}\n\nfunction planForTarget(projectRoot: string, target: SkillTarget): InstallPlan {\n switch (target) {\n case 'claude-code':\n return {\n target,\n destDir: join(projectRoot, '.claude', 'skills'),\n layout: 'folder',\n };\n case 'codex':\n return {\n target,\n destDir: join(projectRoot, '.codex', 'skills'),\n layout: 'folder',\n };\n case 'opencode':\n return {\n target,\n destDir: join(projectRoot, '.opencode', 'agents'),\n layout: 'flat',\n };\n }\n}\n\nconst MANAGED_HEADER =\n '<!-- omd:installed-skill — managed by `omd install-skills`. Do not edit; rerun the command to refresh. -->';\n\ninterface InstallResult {\n target: SkillTarget;\n skill: string;\n destPath: string;\n status: 'created' | 'updated' | 'unchanged' | 'skipped-drift';\n}\n\nfunction installOne(\n packageRoot: string,\n plan: InstallPlan,\n skill: string,\n force: boolean\n): InstallResult {\n const src = readFileSync(\n join(packageRoot, 'skills', skill, 'SKILL.md'),\n 'utf8'\n );\n const managed = MANAGED_HEADER + '\\n\\n' + src;\n const destPath =\n plan.layout === 'folder'\n ? join(plan.destDir, skill, 'SKILL.md')\n : join(plan.destDir, skill + '.md');\n\n const exists = existsSync(destPath);\n const existing = exists ? readFileSync(destPath, 'utf8') : '';\n\n if (exists && existing === managed) {\n return { target: plan.target, skill, destPath, status: 'unchanged' };\n }\n\n if (exists && !existing.startsWith(MANAGED_HEADER) && !force) {\n return { target: plan.target, skill, destPath, status: 'skipped-drift' };\n }\n\n mkdirSync(dirname(destPath), { recursive: true });\n writeFileSync(destPath, managed, 'utf8');\n return {\n target: plan.target,\n skill,\n destPath,\n status: exists ? 'updated' : 'created',\n };\n}\n\n/** Install a hook script from package's .claude/hooks/ to project. */\nfunction installHookFile(\n packageRoot: string,\n projectRoot: string,\n filename: string,\n force: boolean\n): InstallResult {\n const target: SkillTarget = 'claude-code';\n const skillLabel = `hook:${filename}`;\n const srcPath = join(packageRoot, '.claude', 'hooks', filename);\n const destPath = join(projectRoot, '.claude', 'hooks', filename);\n\n if (!existsSync(srcPath)) {\n return { target, skill: skillLabel, destPath, status: 'skipped-drift' };\n }\n const src = readFileSync(srcPath, 'utf8');\n const exists = existsSync(destPath);\n const existing = exists ? readFileSync(destPath, 'utf8') : '';\n if (exists && existing === src) {\n return { target, skill: skillLabel, destPath, status: 'unchanged' };\n }\n if (exists && !force) {\n return { target, skill: skillLabel, destPath, status: 'skipped-drift' };\n }\n mkdirSync(dirname(destPath), { recursive: true });\n writeFileSync(destPath, src);\n return { target, skill: skillLabel, destPath, status: exists ? 'updated' : 'created' };\n}\n\n/**\n * Install / merge .claude/settings.json. We MERGE hooks (don't clobber user\n * customizations) by checking if the omd-managed `_doc` field is present.\n * Without --force, if a user-edited settings.json exists (no _doc field),\n * we skip with `skipped-drift`.\n */\nfunction installSettingsJson(\n packageRoot: string,\n projectRoot: string,\n force: boolean\n): InstallResult {\n const target: SkillTarget = 'claude-code';\n const skillLabel = 'settings:.claude/settings.json';\n const srcPath = join(packageRoot, '.claude', 'settings.json');\n const destPath = join(projectRoot, '.claude', 'settings.json');\n if (!existsSync(srcPath)) {\n return { target, skill: skillLabel, destPath, status: 'skipped-drift' };\n }\n const src = readFileSync(srcPath, 'utf8');\n const exists = existsSync(destPath);\n const existing = exists ? readFileSync(destPath, 'utf8') : '';\n if (exists && existing === src) {\n return { target, skill: skillLabel, destPath, status: 'unchanged' };\n }\n if (exists && !force) {\n // Check if it's the omd-managed version\n try {\n const parsed = JSON.parse(existing);\n if (typeof parsed._doc === 'string' && parsed._doc.includes('OmD')) {\n // managed — overwrite\n } else {\n return { target, skill: skillLabel, destPath, status: 'skipped-drift' };\n }\n } catch {\n return { target, skill: skillLabel, destPath, status: 'skipped-drift' };\n }\n }\n mkdirSync(dirname(destPath), { recursive: true });\n writeFileSync(destPath, src);\n return { target, skill: skillLabel, destPath, status: exists ? 'updated' : 'created' };\n}\n\n/**\n * Copy a read-only data asset (reference-fingerprints.json, vocabulary.json, …)\n * from the package's `data/` into the project's `.claude/data/` or `.codex/data/`.\n * The skill reads these at runtime — they replace the deprecated `omd init recommend` CLI.\n */\nfunction installDataFile(\n packageRoot: string,\n projectRoot: string,\n channelDir: string,\n dataFilename: string,\n force: boolean\n): InstallResult {\n const target: SkillTarget = channelDir === '.claude' ? 'claude-code' : 'codex';\n const skillLabel = `data:${dataFilename}`;\n\n const srcPath = join(packageRoot, 'data', dataFilename);\n const destPath = join(projectRoot, channelDir, 'data', dataFilename);\n\n if (!existsSync(srcPath)) {\n return { target, skill: skillLabel, destPath, status: 'skipped-drift' };\n }\n\n const src = readFileSync(srcPath, 'utf8');\n const exists = existsSync(destPath);\n const existing = exists ? readFileSync(destPath, 'utf8') : '';\n\n // Data files are pure copies — no managed header (would corrupt JSON).\n if (exists && existing === src) {\n return { target, skill: skillLabel, destPath, status: 'unchanged' };\n }\n if (exists && !force) {\n // Honor user customization unless --force\n return { target, skill: skillLabel, destPath, status: 'skipped-drift' };\n }\n\n mkdirSync(dirname(destPath), { recursive: true });\n writeFileSync(destPath, src, 'utf8');\n return {\n target,\n skill: skillLabel,\n destPath,\n status: exists ? 'updated' : 'created',\n };\n}\n\n/**\n * Generate a per-channel agent file from the canonical `agents/<name>.md`.\n *\n * Channel = 'claude' → emits `.claude/agents/<name>.md` (markdown w/ frontmatter)\n * Channel = 'codex' → emits `.codex/agents/<name>.toml` (TOML pointer)\n */\nfunction installAgentFile(\n packageRoot: string,\n projectRoot: string,\n channel: 'claude' | 'codex',\n filename: string,\n force: boolean\n): InstallResult {\n const target: SkillTarget = channel === 'claude' ? 'claude-code' : 'codex';\n const skillLabel = `agent:${filename}`;\n\n const parsed = parseCanonicalAgent(packageRoot, filename);\n const rendered =\n channel === 'claude' ? renderClaudeAgent(parsed) : renderCodexAgent(parsed);\n\n const destFilename =\n channel === 'claude' ? filename : filename.replace(/\\.md$/, '.toml');\n const destPath = join(\n projectRoot,\n channel === 'claude' ? '.claude' : '.codex',\n 'agents',\n destFilename\n );\n\n // For Claude Code: managed marker is encoded as `omd_managed: true` INSIDE the\n // frontmatter (rendered above) — no HTML comment can precede `---` or the\n // subagent loader rejects the file.\n // For Codex: TOML allows leading comments, so `# omd:installed-agent ...` is fine.\n const managed =\n channel === 'claude'\n ? rendered\n : '# omd:installed-agent — generated from agents/' +\n filename +\n ' by `omd install-skills`. Do not edit; rerun the command to refresh.\\n\\n' +\n rendered;\n\n const exists = existsSync(destPath);\n const existing = exists ? readFileSync(destPath, 'utf8') : '';\n\n if (exists && existing === managed) {\n return { target, skill: skillLabel, destPath, status: 'unchanged' };\n }\n\n // Drift detection sentinels:\n // Claude — look for `omd_managed: true` line inside frontmatter\n // Codex — look for `# omd:installed-agent` comment\n const isManaged =\n channel === 'claude'\n ? /\\nomd_managed:\\s*true\\b/.test(existing)\n : existing.startsWith('# omd:installed-agent');\n\n if (exists && !isManaged && !force) {\n return { target, skill: skillLabel, destPath, status: 'skipped-drift' };\n }\n\n mkdirSync(dirname(destPath), { recursive: true });\n writeFileSync(destPath, managed, 'utf8');\n return {\n target,\n skill: skillLabel,\n destPath,\n status: exists ? 'updated' : 'created',\n };\n}\n\nconst STATUS_LABEL: Record<InstallResult['status'], string> = {\n created: pc.green('created'),\n updated: pc.cyan('updated'),\n unchanged: pc.dim('unchanged'),\n 'skipped-drift': pc.yellow('skipped'),\n};\n\nfunction autoDetectTargets(projectRoot: string): SkillTarget[] {\n const presence = detectInstalledAgents(projectRoot);\n const targets: SkillTarget[] = [];\n if (presence.claudeCode) targets.push('claude-code');\n if (presence.codex) targets.push('codex');\n if (presence.opencode) targets.push('opencode');\n // Cursor uses .mdc rules, not skills — installed via `omd sync`.\n if (targets.length === 0) {\n // Fallback: install for all three so user gets coverage even without\n // explicit signal. Idempotent so cost is low.\n return ['claude-code', 'codex', 'opencode'];\n }\n return targets;\n}\n\nexport async function runInstallSkills(\n opts: InstallSkillsOptions = {}\n): Promise<number> {\n const projectRoot = opts.dir ?? process.cwd();\n const packageRoot = findPackageRoot();\n if (!packageRoot) {\n console.error(pc.red('omd install-skills: package data not found'));\n return 1;\n }\n\n const allSkills = listShippedSkills(packageRoot);\n if (allSkills.length === 0) {\n console.error(pc.red('omd install-skills: no skills found in package'));\n return 1;\n }\n const allAgents = listCanonicalAgents(packageRoot);\n\n const force = opts.force ?? false;\n\n p.intro(\n pc.bold('omd install-skills') +\n pc.dim(` (${relative(process.cwd(), projectRoot) || '.'})`)\n );\n\n // Resolve selection: --all flag, --skills/--agents/--agent filter, or interactive TUI.\n // TUI runs only when stdin is a TTY and the corresponding flag isn't set.\n const isTTY = Boolean(process.stdin.isTTY && process.stdout.isTTY);\n const nonInteractive = opts.all || !isTTY || opts.skillsFilter || opts.agentsFilter;\n\n const detected = autoDetectTargets(projectRoot);\n // Real presence (not the all-3 fallback) — used purely for hint labels.\n const presence = detectInstalledAgents(projectRoot);\n const actuallyDetected: SkillTarget[] = [\n presence.claudeCode ? 'claude-code' : null,\n presence.codex ? 'codex' : null,\n presence.opencode ? 'opencode' : null,\n ].filter((x): x is SkillTarget => x !== null);\n\n let skills: string[];\n let canonicalAgents: string[];\n let targets: SkillTarget[];\n\n if (nonInteractive) {\n // Non-interactive resolution\n skills = opts.skillsFilter\n ? allSkills.filter((s) => opts.skillsFilter!.includes(s))\n : allSkills;\n canonicalAgents = opts.agentsFilter\n ? allAgents.filter((a) => opts.agentsFilter!.includes(a.replace(/\\.md$/, '')))\n : allAgents;\n targets = opts.agents\n ? opts.agents\n : opts.all\n ? (['claude-code', 'codex', 'opencode'] as SkillTarget[])\n : detected;\n } else {\n // === Interactive TUI — skill → subagent → channel order ===\n // 1. Skills (default = ALL selected)\n const skillResult = await p.multiselect({\n message:\n 'Skills · space = 토글 · a = 전체 · enter = 확인 (default ALL)',\n options: allSkills.map((s) => ({ value: s, label: s, hint: 'omd skill' })),\n initialValues: allSkills,\n required: true,\n });\n if (p.isCancel(skillResult)) {\n p.cancel('Install cancelled.');\n return 130;\n }\n skills = skillResult as string[];\n\n // 2. Sub-agents (default = ALL selected)\n if (allAgents.length > 0) {\n const agentResult = await p.multiselect({\n message:\n 'Sub-agents · space = 토글 · a = 전체 · enter = 확인 (default ALL)',\n options: allAgents.map((a) => ({\n value: a,\n label: a.replace(/\\.md$/, ''),\n hint: 'subagent',\n })),\n initialValues: allAgents,\n required: false,\n });\n if (p.isCancel(agentResult)) {\n p.cancel('Install cancelled.');\n return 130;\n }\n canonicalAgents = agentResult as string[];\n } else {\n canonicalAgents = [];\n }\n\n // 3. Agent channels (default = NONE — user explicitly picks)\n if (opts.agents) {\n targets = opts.agents;\n } else {\n const targetResult = await p.multiselect({\n message:\n 'Agent channels · space = 토글 · enter = 확인 · 최소 1개 선택',\n options: [\n {\n value: 'claude-code',\n label: 'Claude Code',\n hint: actuallyDetected.includes('claude-code') ? '.claude/ detected' : '',\n },\n {\n value: 'codex',\n label: 'Codex',\n hint: actuallyDetected.includes('codex') ? '.codex/ detected' : '',\n },\n {\n value: 'opencode',\n label: 'OpenCode',\n hint: actuallyDetected.includes('opencode') ? '.opencode/ detected' : '',\n },\n ] as { value: SkillTarget; label: string; hint?: string }[],\n initialValues: [] as SkillTarget[],\n required: true,\n });\n if (p.isCancel(targetResult)) {\n p.cancel('Install cancelled.');\n return 130;\n }\n targets = targetResult as SkillTarget[];\n }\n }\n\n const plans = targets.map((t) => planForTarget(projectRoot, t));\n\n p.log.message(\n pc.bold(`Skills (${skills.length}): `) +\n skills.map((s) => pc.cyan(s)).join(', ')\n );\n if (canonicalAgents.length > 0) {\n p.log.message(\n pc.bold(`Agents (${canonicalAgents.length}): `) +\n canonicalAgents.map((a) => pc.cyan(a.replace(/\\.md$/, ''))).join(', ')\n );\n }\n p.log.message(\n pc.bold('Targets: ') + targets.map((t) => pc.cyan(t)).join(', ')\n );\n\n const results: InstallResult[] = [];\n for (const plan of plans) {\n for (const skill of skills) {\n results.push(installOne(packageRoot, plan, skill, force));\n }\n }\n\n // Generate per-channel sub-agent definitions from the canonical `agents/`.\n // This is the v2 portable source-of-truth pattern (oh-my-agent style).\n // `canonicalAgents` is already resolved above by the TUI / --agents filter.\n for (const target of targets) {\n if (target === 'claude-code') {\n for (const filename of canonicalAgents) {\n results.push(installAgentFile(packageRoot, projectRoot, 'claude', filename, force));\n }\n } else if (target === 'codex') {\n for (const filename of canonicalAgents) {\n results.push(installAgentFile(packageRoot, projectRoot, 'codex', filename, force));\n }\n }\n // OpenCode currently has no agent-definition channel — skills only.\n }\n\n // Ship the read-only data assets (reference fingerprints, controlled vocab,\n // human-readable tag matrix, opt-out corpus) into the project so skills + hooks\n // can run entirely on the host CLI's own model — no external API keys.\n const dataFiles = [\n 'reference-fingerprints.json',\n 'reference-tags.md',\n 'vocabulary.json',\n 'synonyms.json',\n 'opt-out-corpus.json',\n ];\n for (const target of targets) {\n if (target === 'claude-code') {\n for (const dataFile of dataFiles) {\n results.push(installDataFile(packageRoot, projectRoot, '.claude', dataFile, force));\n }\n } else if (target === 'codex') {\n for (const dataFile of dataFiles) {\n results.push(installDataFile(packageRoot, projectRoot, '.codex', dataFile, force));\n }\n }\n }\n\n // Install hooks (Claude Code only — Codex / OpenCode have separate hook systems)\n if (targets.includes('claude-code')) {\n for (const hookFile of [\n 'skill-activation.cjs',\n 'session-state-loader.cjs',\n 'post-edit-watch.cjs',\n 'session-end-foldin.cjs',\n ]) {\n results.push(installHookFile(packageRoot, projectRoot, hookFile, force));\n }\n // settings.json (with merge, never clobber user)\n results.push(installSettingsJson(packageRoot, projectRoot, force));\n }\n\n p.log.message(pc.bold('\\nResults:'));\n for (const r of results) {\n const rel = relative(projectRoot, r.destPath);\n p.log.message(\n ` ${STATUS_LABEL[r.status]} ${pc.dim(r.target.padEnd(12))} ${rel}`\n );\n }\n\n const driftCount = results.filter((r) => r.status === 'skipped-drift').length;\n const writtenCount = results.filter(\n (r) => r.status === 'created' || r.status === 'updated'\n ).length;\n\n if (driftCount > 0) {\n p.outro(\n pc.yellow(\n `${writtenCount} written, ${driftCount} skipped (existing files lack the omd marker — rerun with --force to overwrite).`\n )\n );\n return 0;\n }\n\n // Friendly next-step nudge after successful install\n const nextSteps = [\n `${pc.bold('Open Claude Code (or Codex). Just say what you want:')}`,\n '',\n ` ${pc.dim('\"토스 스타일 가족 식단 공유 앱 메인 화면 디자인해줘\"')}`,\n ` ${pc.dim('\"Linear-clone B2B SaaS dashboard 만들고 싶어\"')}`,\n ` ${pc.dim('\"이 카드 좀 더 세련되게\"')} ${pc.dim('# 작업 중 자연어 — 자동 라우팅')}`,\n '',\n `${pc.bold('Claude가 description 매칭으로 자동 라우팅')} ${pc.dim('— 슬래시 명령 안 쳐도 됨. Hook은 DESIGN.md 부재 시 omd:init 안내만.')}`,\n '',\n `${pc.dim('Power user shortcut: ')}${pc.cyan('/omd-harness <task>')} ${pc.dim('— 즉시 진입.')}`,\n '',\n `${pc.yellow('⚠ Already-running Claude Code session?')} ${pc.dim('Run `/agents` inside the session to reload — or quit (Cmd+Q on macOS) and relaunch. Without reload, hooks/agents do not load.')}`,\n ].join('\\n');\n p.note(nextSteps, 'Next');\n\n p.outro(\n pc.green(\n `Done. 6 skills · 11 sub-agents · 4 hooks installed (${writtenCount} files).`,\n ),\n );\n return 0;\n}\n\n","import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport type AgentId = 'claude-code' | 'codex' | 'opencode' | 'cursor' | 'unknown';\n\nexport function detectCallingAgent(): AgentId {\n const env = process.env;\n\n if (env.CLAUDECODE === '1' || env.CLAUDE_CODE === '1' || env.CLAUDE_CODE_TASK_ID) {\n return 'claude-code';\n }\n if (env.CODEX_SESSION_ID || env.CODEX || env.OPENAI_CODEX) {\n return 'codex';\n }\n if (env.OPENCODE || env.OPENCODE_SESSION) {\n return 'opencode';\n }\n if (env.CURSOR_SESSION_ID || env.CURSOR_AGENT) {\n return 'cursor';\n }\n\n return 'unknown';\n}\n\nexport interface AgentPresence {\n claudeCode: boolean;\n codex: boolean;\n opencode: boolean;\n cursor: boolean;\n}\n\nexport function detectInstalledAgents(projectRoot: string): AgentPresence {\n return {\n claudeCode:\n existsSync(join(projectRoot, '.claude')) ||\n existsSync(join(projectRoot, 'CLAUDE.md')),\n codex:\n existsSync(join(projectRoot, '.codex')) ||\n existsSync(join(projectRoot, 'AGENTS.md')) ||\n existsSync(join(projectRoot, 'AGENTS.override.md')),\n opencode:\n existsSync(join(projectRoot, '.opencode')) ||\n existsSync(join(projectRoot, 'opencode.json')) ||\n existsSync(join(projectRoot, 'opencode.jsonc')),\n cursor:\n existsSync(join(projectRoot, '.cursor')) ||\n existsSync(join(projectRoot, '.cursorrules')),\n };\n}\n"],"mappings":"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","names":["existsSync","join","existsSync","join"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oh-my-design-cli",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Bootstrap oh-my-design skills + agents into your project. After install, talk to your AI coding agent in natural language
|
|
3
|
+
"version": "1.5.0",
|
|
4
|
+
"description": "Bootstrap oh-my-design skills + agents into your project. After install, talk to your AI coding agent in natural language \u2014 no other CLI commands.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"oh-my-design": "dist/bin/oh-my-design.js",
|
|
@@ -18,6 +18,12 @@
|
|
|
18
18
|
"skills/omd-reference-capture",
|
|
19
19
|
"skills/omd-asset-fetch",
|
|
20
20
|
"skills/omd-experiment-gallery",
|
|
21
|
+
"skills/omd-orchestrator",
|
|
22
|
+
"skills/omd-kr-writer",
|
|
23
|
+
"skills/omd-locale-adapter",
|
|
24
|
+
"skills/omd-designer-review",
|
|
25
|
+
"skills/omd-final-qa",
|
|
26
|
+
"skills/omd-codex-image",
|
|
21
27
|
"agents",
|
|
22
28
|
"data",
|
|
23
29
|
"web/references/*/DESIGN.md",
|
|
@@ -67,4 +73,4 @@
|
|
|
67
73
|
"typescript": "^5.8.2",
|
|
68
74
|
"vitest": "^3.1.1"
|
|
69
75
|
}
|
|
70
|
-
}
|
|
76
|
+
}
|
|
@@ -31,7 +31,6 @@ DESIGN.md를 모든 UI/디자인 작업의 권위 있는 컨텍스트로 사용
|
|
|
31
31
|
| 사용자 요청 패턴 | 처리 경로 | 이유 |
|
|
32
32
|
|---|---|---|
|
|
33
33
|
| "에셋 / 아이콘 / 일러스트 / 차트 / 사진 / 로고 / 그래프 / SVG 만들어" | dispatch `omd-asset-curator` | 매체 선택 + 스택 매칭이 전문 영역 |
|
|
34
|
-
| "3D / 렌더 / 블렌더 / 목업" 명시 | dispatch `omd-3d-blender` | Blender MCP 필요 |
|
|
35
34
|
| "메인 화면 / landing / 전체 디자인 / 처음부터 / 와이어프레임" | 사용자에게 `/omd-harness` 추천 | 10-phase 파이프라인이 적합 |
|
|
36
35
|
| "접근성 / a11y / 색약 / 키보드 네비" 감사 | dispatch `omd-a11y-auditor` | 전문 감사 |
|
|
37
36
|
| "마이크로카피만 다듬어 / 카피 톤 정리 / empty state 문구 전부" 복수 | dispatch `omd-microcopy` | voice 일관성 |
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: omd:codex-image
|
|
3
|
+
description: "이미지 placeholder를 동적으로 materialize. Codex 채널에서는 내장 image-generation primitive 호출, Claude Code 채널에서는 omd-asset-curator로 fall back, OpenCode에서는 spec dump. HTML/MD의 `<!-- omd:gen-image -->` 블록을 단일 source of truth로 사용. '이미지 생성해줘', '플레이스홀더 채워줘', '코덱스로 이미지 만들어' 류 트리거."
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# omd:codex-image
|
|
8
|
+
|
|
9
|
+
채널-aware 이미지 materialization 스킬. **하나의 spec format** + **세 가지 downstream 처리**.
|
|
10
|
+
|
|
11
|
+
문제: oh-my-design이 emit하는 HTML/MD에는 늘 illustration 자리가 비어있다. 채널별 capability가 다르다 — Codex CLI는 내장 image generation을 갖고, Claude Code는 안 가졌고, OpenCode는 user에게 위임한다. 이 차이를 skill 1개로 통합한다.
|
|
12
|
+
|
|
13
|
+
## 0. 단일 spec format
|
|
14
|
+
|
|
15
|
+
artifact (HTML, Markdown, JSX, MDX) 안에 다음 주석 블록을 둔다.
|
|
16
|
+
|
|
17
|
+
```html
|
|
18
|
+
<!-- omd:gen-image
|
|
19
|
+
filename: assets/karrot-hero.png
|
|
20
|
+
prompt: "Karrot mobile home feed screenshot — white canvas, warm near-black headings, single Karrot Orange #FF6600 floating CTA. Mobile portrait, no chrome."
|
|
21
|
+
aspect: "16:9"
|
|
22
|
+
style: "product screenshot, minimal, mobile"
|
|
23
|
+
references:
|
|
24
|
+
- https://www.daangn.com/
|
|
25
|
+
- https://seed-design.io/
|
|
26
|
+
notes: "Single saturated element per viewport. Use brand orange only on the CTA."
|
|
27
|
+
-->
|
|
28
|
+
<img src="assets/karrot-hero.png" alt="당근 홈 피드의 단일 오렌지 CTA" />
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
규칙:
|
|
32
|
+
|
|
33
|
+
- `filename` — 결과물 저장 경로. 같은 디렉토리 기준 상대경로 권장.
|
|
34
|
+
- `prompt` — 한 문단, 구체적, 시각 디테일 + 톤 + 컬러 hex 포함.
|
|
35
|
+
- `aspect` — `16:9` `4:3` `1:1` `9:16` 중 하나.
|
|
36
|
+
- `style` — 1~3 단어 (`product screenshot` / `inline svg` / `editorial photo` / `dieline diagram`).
|
|
37
|
+
- `references` — 실제 브랜드 URL (선택, IP-safe 컨텍스트용).
|
|
38
|
+
- `notes` — generator나 검수자가 알아야 할 제약.
|
|
39
|
+
|
|
40
|
+
이 spec은 channel과 무관하다. 채널별 처리는 §1.
|
|
41
|
+
|
|
42
|
+
## 1. 채널 분기
|
|
43
|
+
|
|
44
|
+
호출 envelope의 `channel` (또는 환경 detect — `process.env.OMD_CHANNEL`이나 host agent 식별)에 따라:
|
|
45
|
+
|
|
46
|
+
### 1.1 channel = `codex`
|
|
47
|
+
|
|
48
|
+
Codex CLI는 native image generation을 갖고 있다. 다음 순서:
|
|
49
|
+
|
|
50
|
+
1. artifact를 Read로 전체 로드
|
|
51
|
+
2. `<!-- omd:gen-image ... -->` 블록을 정규식으로 추출 (multiline)
|
|
52
|
+
3. 각 블록마다 Codex의 image generation primitive 호출 — 도구명은 host에 따라 다르다 (`generate_image`, `image.create`, `dall_e_image_generation`, `gpt_image_1` 등). Codex agent가 자기 환경에서 사용 가능한 것을 선택한다.
|
|
53
|
+
4. 출력 파일을 `filename` 경로에 저장 (mkdir -p)
|
|
54
|
+
5. **주석 블록은 그대로 두고** `<img src>`나 `![]()` 경로가 매칭되는지 확인. 안 맞으면 alt 텍스트 보존하면서 src만 교체.
|
|
55
|
+
6. 각 처리 항목에 `<!-- omd:gen-image:done at=<ISO timestamp> by=codex -->` 주석을 spec 블록 아래에 1줄 추가.
|
|
56
|
+
|
|
57
|
+
generation 실패 시: spec 블록은 그대로, `<!-- omd:gen-image:error reason="..." -->` 추가. 다음 호출이 재시도 가능하도록.
|
|
58
|
+
|
|
59
|
+
prompt 보강 (Codex가 좋은 결과를 내려면):
|
|
60
|
+
|
|
61
|
+
- `prompt` 원문 + 다음 prefix 자동 prepend: `"Render as <aspect> <style>. "`
|
|
62
|
+
- `notes`에 색 hex 있으면 prompt 끝에 `"Use only the following colors: <hex list>."` 추가
|
|
63
|
+
- `references` 있으면 `"Stylistic reference inspiration only — do not copy verbatim: <urls>"` 추가
|
|
64
|
+
|
|
65
|
+
### 1.2 channel = `claude-code`
|
|
66
|
+
|
|
67
|
+
Claude Code는 native image generation이 없다. 대신 `omd-asset-curator` 스킬로 라우팅:
|
|
68
|
+
|
|
69
|
+
1. spec 블록을 추출
|
|
70
|
+
2. 각 블록을 omd-asset-curator의 spec 형식으로 변환:
|
|
71
|
+
- `prompt` → asset-curator의 `description`
|
|
72
|
+
- `style` → asset-curator의 medium 힌트 (`product screenshot` → Picsum/Loremflickr, `inline svg` → 직접 SVG 생성, `icon` → Lucide)
|
|
73
|
+
3. asset-curator가 free-license 자원에서 매칭 (DiceBear avatars, Lucide icons, Picsum CC0 photos, unDraw SVG 등) 또는 인라인 SVG를 생성해 `filename`에 저장
|
|
74
|
+
4. spec 블록 그대로 두고 `<!-- omd:gen-image:done at=<ISO> by=asset-curator source=<url> -->` 추가
|
|
75
|
+
5. **사용자에게 1줄 알림**: "Claude Code에서는 free-license fallback을 적용했어요. 더 정확한 이미지는 Codex 채널에서 generate하면 돼요."
|
|
76
|
+
|
|
77
|
+
### 1.3 channel = `opencode`
|
|
78
|
+
|
|
79
|
+
OpenCode는 image generation도, asset-curator도 자동으로 못 돌린다. user-in-the-loop:
|
|
80
|
+
|
|
81
|
+
1. spec 블록을 추출
|
|
82
|
+
2. terminal에 `## Image generation queue` 섹션을 emit:
|
|
83
|
+
```
|
|
84
|
+
3개 이미지를 수동으로 생성/소싱해서 다음 경로에 두세요:
|
|
85
|
+
|
|
86
|
+
1. assets/karrot-hero.png (16:9)
|
|
87
|
+
prompt: "Karrot mobile home feed — single orange CTA..."
|
|
88
|
+
refs: https://www.daangn.com/
|
|
89
|
+
|
|
90
|
+
2. ...
|
|
91
|
+
```
|
|
92
|
+
3. spec 블록은 그대로 두고 done 주석은 user 확인 후에만 추가
|
|
93
|
+
|
|
94
|
+
## 2. fallback 그래프
|
|
95
|
+
|
|
96
|
+
artifact를 처리할 때 항상 우선순위:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
Codex native gen ──┐
|
|
100
|
+
├─► success → done 주석 추가
|
|
101
|
+
asset-curator ──┘
|
|
102
|
+
│
|
|
103
|
+
└─► fail → user-queue (OpenCode 식 prompt)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
각 단계 실패는 다음 단계로 escalate. 마지막 단계도 실패하면 error 주석 + 사용자에게 명시.
|
|
107
|
+
|
|
108
|
+
## 3. idempotency
|
|
109
|
+
|
|
110
|
+
이 스킬은 같은 artifact에 여러 번 돌려도 안전해야 한다.
|
|
111
|
+
|
|
112
|
+
- 이미 `<!-- omd:gen-image:done -->` 주석이 붙은 spec 블록은 스킵
|
|
113
|
+
- `filename` 경로의 파일이 이미 존재하면 (size > 0) 스킵
|
|
114
|
+
- `--force` 명시되면 둘 다 무시하고 재생성
|
|
115
|
+
|
|
116
|
+
## 4. IP safe rails
|
|
117
|
+
|
|
118
|
+
- `references` URL은 inspiration만. 절대 verbatim 카피 X.
|
|
119
|
+
- 브랜드 로고는 generation이 아니라 reference에서 **다운로드**해서 가져오기. Codex/asset-curator 둘 다 로고는 generate 금지 (왜곡된 가짜 로고는 IP risk).
|
|
120
|
+
- 사람 얼굴이 들어가는 generation은 디폴트 거절 (DiceBear avatars 같은 캐리커처는 OK).
|
|
121
|
+
|
|
122
|
+
## 5. 출력 보고
|
|
123
|
+
|
|
124
|
+
처리 끝에 한 줄 요약:
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
✓ 4 images materialized (codex) · 0 skipped · 0 errors
|
|
128
|
+
→ experiments/2026-05-19/karrot/assets/{hero,one-color,grid,detail}.png
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
또는 fallback이 섞였을 때:
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
✓ 2 codex · 2 asset-curator fallback · 1 user-queue
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## 6. 호출 envelope
|
|
138
|
+
|
|
139
|
+
```yaml
|
|
140
|
+
agent: omd-codex-image
|
|
141
|
+
inputs:
|
|
142
|
+
artifact_path: experiments/2026-05-19/karrot/landing.html
|
|
143
|
+
channel: codex # 또는 claude-code | opencode | auto
|
|
144
|
+
force: false # done 주석 무시하고 재생성
|
|
145
|
+
dry_run: false # spec parse만 하고 generation 안 함
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`channel: auto`면 env 또는 host detection으로 분기.
|
|
149
|
+
|
|
150
|
+
## 7. 금지
|
|
151
|
+
|
|
152
|
+
- spec 블록을 *제거*하지 말 것. done 주석만 *추가*. spec은 영구 기록.
|
|
153
|
+
- Codex가 사용 불가한 환경에서 channel=codex 강제 호출 X — auto 또는 명시적 fallback 사용.
|
|
154
|
+
- 같은 filename에 다른 prompt 두 번 처리 금지 (idempotency violation).
|
|
155
|
+
- generation prompt에 사람 신원이나 실명 절대 넣지 않기.
|
|
156
|
+
|
|
157
|
+
## 8. 다른 스킬과의 관계
|
|
158
|
+
|
|
159
|
+
- **omd-asset-curator**: 무료 라이선스 자산 카탈로그. codex-image의 claude-code fallback path.
|
|
160
|
+
- **omd-reference-capture**: 라이브 브랜드 사이트 캡쳐. `references:` URL 검증용으로 같이 쓰면 정확도 ↑.
|
|
161
|
+
- **omd-orchestrator**: HTML emit 후 codex-image를 마지막 단계에 끼워 넣을 수 있음. orchestrator의 worker 카탈로그에 추가됨.
|
|
162
|
+
- **omd-final-qa**: gen 결과 paste-in 후 자기 rubric에 "image quality" 항목 추가 (선택).
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: omd:designer-review
|
|
3
|
+
description: "시각 + 브랜드 일관성 리뷰. HTML/MD/JSX artifact를 받아 brand DESIGN.md 대비 typo hierarchy, 색 budget, radius scale, 컴포넌트 state, 모바일 반응형 검수. severity BLOCK/WARN/FYI + line ref 출력. 'UI 리뷰', '디자인 검토', 'DESIGN.md 대비 검수' 류 트리거."
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# omd:designer-review
|
|
8
|
+
|
|
9
|
+
artifact를 받아 brand의 DESIGN.md 기준으로 visual / brand consistency를 audit한다. **read-write가 아니라 advisory** — 직접 수정하지 않고 review report만 emit.
|
|
10
|
+
|
|
11
|
+
구조는 [`mastepanoski/claude-skills` ui-design-review](https://agentskills.so/skills/mastepanoski-claude-skills-ui-design-review)에서 차용.
|
|
12
|
+
|
|
13
|
+
## 0. 필수 입력
|
|
14
|
+
|
|
15
|
+
- `artifact_path`: HTML, MD, JSX, TSX 중 하나
|
|
16
|
+
- `design_md_path`: 해당 브랜드의 `references/<id>/DESIGN.md` 또는 프로젝트 루트 `DESIGN.md`
|
|
17
|
+
- (선택) `viewport`: `mobile` | `desktop` | `both` (default: both)
|
|
18
|
+
|
|
19
|
+
이 두 입력이 없으면 BLOCK으로 즉시 종료.
|
|
20
|
+
|
|
21
|
+
## 1. Audit 카테고리 (6)
|
|
22
|
+
|
|
23
|
+
### 1.1 Typography hierarchy
|
|
24
|
+
|
|
25
|
+
- DESIGN.md `§ Typography` 스펙 read
|
|
26
|
+
- artifact 내 h1/h2/h3/p의 size, weight, line-height 추출
|
|
27
|
+
- 일치 여부 검사
|
|
28
|
+
- 빈 h-level skip (h1 → h3) → WARN
|
|
29
|
+
- 본문 weight가 400/500이 아닌 700 fall-through → WARN
|
|
30
|
+
|
|
31
|
+
### 1.2 Color budget (Toss "2 saturated brand elements / viewport" rule)
|
|
32
|
+
|
|
33
|
+
DESIGN.md `§ Color`의 brand saturated tokens 추출.
|
|
34
|
+
|
|
35
|
+
- viewport당 brand saturated 사용 횟수 카운트
|
|
36
|
+
- > 2 → WARN (Toss principle 위반)
|
|
37
|
+
- > 4 → BLOCK
|
|
38
|
+
- DESIGN.md에 없는 hex 직접 사용 → WARN
|
|
39
|
+
- 회색 대신 saturated 사용 (예: warning이 아닌 컨테이너에 red-500) → WARN
|
|
40
|
+
|
|
41
|
+
### 1.3 Radius scale
|
|
42
|
+
|
|
43
|
+
- DESIGN.md `§ Radius` 토큰 read (예: 0, 4, 8, 12, 16, 9999)
|
|
44
|
+
- artifact 내 `border-radius` 추출
|
|
45
|
+
- 토큰에 없는 값 → WARN
|
|
46
|
+
- 한 컴포넌트 안에서 radius 혼용 (8 + 12) → FYI
|
|
47
|
+
|
|
48
|
+
### 1.4 Component states
|
|
49
|
+
|
|
50
|
+
각 interactive 요소가 다음 state를 갖춰야:
|
|
51
|
+
- default ✓
|
|
52
|
+
- hover ✓
|
|
53
|
+
- focus (focus-visible OK) ✓
|
|
54
|
+
- active ✓
|
|
55
|
+
- disabled ✓
|
|
56
|
+
|
|
57
|
+
누락 → BLOCK (focus는 a11y 필수)
|
|
58
|
+
|
|
59
|
+
### 1.5 Mobile responsiveness
|
|
60
|
+
|
|
61
|
+
- viewport=both인 경우 mobile에서 검사
|
|
62
|
+
- 최소 hit area 44x44 (iOS HIG) → BLOCK 미달
|
|
63
|
+
- 가로 스크롤 발생 → BLOCK
|
|
64
|
+
- 텍스트 14px 미만 → WARN
|
|
65
|
+
|
|
66
|
+
### 1.6 Spacing / layout
|
|
67
|
+
|
|
68
|
+
- DESIGN.md `§ Spacing` 토큰
|
|
69
|
+
- 토큰 외 값 (예: `padding: 13px`) → WARN
|
|
70
|
+
- 인접 요소 간 일관성 (한 카드 안에서 padding-x가 16 vs 20 혼재) → WARN
|
|
71
|
+
|
|
72
|
+
## 2. Severity 정의
|
|
73
|
+
|
|
74
|
+
| Severity | 의미 | 후속 조치 |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| **BLOCK** | a11y 또는 hard rule 위반. 출간 불가. | writer로 revision round 1 |
|
|
77
|
+
| **WARN** | best practice 위반. 출간 가능하나 권장 수정. | writer가 판단 후 fix |
|
|
78
|
+
| **FYI** | 정보성. 의도일 수 있음. | 무시 가능 |
|
|
79
|
+
|
|
80
|
+
## 3. 출력 형식
|
|
81
|
+
|
|
82
|
+
`<work_dir>/.reviews/designer-review-round-<N>.md`:
|
|
83
|
+
|
|
84
|
+
```markdown
|
|
85
|
+
# Designer review — round <N>
|
|
86
|
+
|
|
87
|
+
**Date:** <ISO>
|
|
88
|
+
**Artifact:** <path>
|
|
89
|
+
**DESIGN.md:** <path>
|
|
90
|
+
**Viewport:** mobile | desktop | both
|
|
91
|
+
|
|
92
|
+
## Summary
|
|
93
|
+
|
|
94
|
+
- BLOCK: <count>
|
|
95
|
+
- WARN: <count>
|
|
96
|
+
- FYI: <count>
|
|
97
|
+
|
|
98
|
+
## Issues
|
|
99
|
+
|
|
100
|
+
### [BLOCK] Focus state missing on primary CTA
|
|
101
|
+
- **Location:** `components/SignupForm.tsx:42`
|
|
102
|
+
- **Rule:** § Component states — focus is mandatory
|
|
103
|
+
- **Evidence:** `<button className="bg-blue-500 hover:bg-blue-600">` — focus 클래스 없음
|
|
104
|
+
- **Fix suggestion:** add `focus-visible:ring-2 focus-visible:ring-blue-300`
|
|
105
|
+
|
|
106
|
+
### [WARN] Color budget exceeded on mobile hero
|
|
107
|
+
- **Location:** `index.ko.md:34-41`
|
|
108
|
+
- **Rule:** § Color budget — max 2 saturated / viewport
|
|
109
|
+
- **Evidence:** 3 saturated brand 사용 (red-500, blue-500, green-500)
|
|
110
|
+
- **Fix suggestion:** green-500을 gray-700 또는 텍스트로 대체
|
|
111
|
+
...
|
|
112
|
+
|
|
113
|
+
## Verdict
|
|
114
|
+
|
|
115
|
+
- **PASS** (BLOCK=0, WARN≤3) — 출간 OK
|
|
116
|
+
- **REVISION** (BLOCK=0, WARN>3) — 권장 수정 후 재리뷰 옵션
|
|
117
|
+
- **BLOCK** (BLOCK≥1) — 출간 불가, writer revision round 시작
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## 4. 시각 검수 (optional)
|
|
121
|
+
|
|
122
|
+
artifact가 HTML/JSX이고 browser-harness가 가용하면 mobile 320px + desktop 1280px 스크린샷을 캡쳐해 `.reviews/screenshots/`에 저장. 텍스트 audit과 함께 첨부.
|
|
123
|
+
|
|
124
|
+
## 5. DESIGN.md 강제 재독
|
|
125
|
+
|
|
126
|
+
**Anti-pattern**: 이전 review에서 읽은 DESIGN.md를 캐싱해 재사용 → memory hallucination 위험.
|
|
127
|
+
|
|
128
|
+
→ 매 호출마다 DESIGN.md를 **다시 read**. 읽은 timestamp를 report 헤더에 명시.
|
|
129
|
+
|
|
130
|
+
## 6. Anti-patterns
|
|
131
|
+
|
|
132
|
+
- ❌ "looks good" rubber-stamp (final-qa 동일 룰. designer-review도 "전반적으로 괜찮음" 식 응답 금지)
|
|
133
|
+
- ❌ DESIGN.md 안 읽고 일반 best practice로 평가
|
|
134
|
+
- ❌ severity inflation (모든 걸 BLOCK)
|
|
135
|
+
- ❌ severity deflation (BLOCK 사안을 FYI로)
|
|
136
|
+
- ❌ Fix suggestion 없는 issue (반드시 actionable)
|
|
137
|
+
- ❌ Line ref 없는 issue (`somewhere in the file` 금지)
|
|
138
|
+
|
|
139
|
+
## 7. 1회 revision 후 재호출 시
|
|
140
|
+
|
|
141
|
+
input에 `prior_report_path` 포함되면:
|
|
142
|
+
- 이전 BLOCK/WARN 항목을 list로 추출
|
|
143
|
+
- artifact 재read
|
|
144
|
+
- 항목별로 RESOLVED / UNRESOLVED / NEW로 표시
|
|
145
|
+
|
|
146
|
+
Round 2에도 UNRESOLVED BLOCK 있으면 orchestrator로 BLOCK escalation.
|