oh-my-design-cli 1.6.6 → 1.7.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/AGENTS.md +1 -1
- package/README.md +8 -8
- package/data/reference-fingerprints.json +1318 -10
- package/dist/bin/oh-my-design.js +1 -1
- package/dist/{install-skills-JNH66GOI.js → install-skills-YYHEC4CS.js} +5 -11
- package/dist/install-skills-YYHEC4CS.js.map +1 -0
- package/package.json +1 -1
- package/skills/claude-design/SKILL.md +7 -2
- package/web/references/11st/DESIGN.md +400 -0
- package/web/references/17live/DESIGN.md +43 -0
- package/web/references/29cm/DESIGN.md +41 -0
- package/web/references/91app/DESIGN.md +31 -0
- package/web/references/ably/DESIGN.md +54 -0
- package/web/references/airbnb/DESIGN.md +58 -0
- package/web/references/airtable/DESIGN.md +39 -0
- package/web/references/alipay/DESIGN.md +50 -0
- package/web/references/amazingtalker/DESIGN.md +434 -0
- package/web/references/appier/DESIGN.md +45 -0
- package/web/references/apple/DESIGN.md +47 -0
- package/web/references/baemin/DESIGN.md +142 -43
- package/web/references/banksalad/DESIGN.md +67 -0
- package/web/references/bilibili/DESIGN.md +45 -0
- package/web/references/bithumb/DESIGN.md +38 -0
- package/web/references/bmw/DESIGN.md +37 -0
- package/web/references/brandi/DESIGN.md +414 -0
- package/web/references/bunjang/DESIGN.md +47 -0
- package/web/references/cakeresume/DESIGN.md +29 -0
- package/web/references/cal/DESIGN.md +52 -0
- package/web/references/catchtable/DESIGN.md +79 -19
- package/web/references/cathay/DESIGN.md +432 -0
- package/web/references/channeltalk/DESIGN.md +48 -0
- package/web/references/class101/DESIGN.md +51 -0
- package/web/references/classting/DESIGN.md +41 -0
- package/web/references/classum/DESIGN.md +43 -0
- package/web/references/claude/DESIGN.md +157 -70
- package/web/references/clay/DESIGN.md +56 -0
- package/web/references/clickhouse/DESIGN.md +50 -0
- package/web/references/cloudflare/DESIGN.md +637 -0
- package/web/references/cohere/DESIGN.md +48 -0
- package/web/references/coinbase/DESIGN.md +139 -5
- package/web/references/coinone/DESIGN.md +39 -0
- package/web/references/composio/DESIGN.md +46 -0
- package/web/references/cookpad/DESIGN.md +37 -0
- package/web/references/coupang/DESIGN.md +57 -2
- package/web/references/cursor/DESIGN.md +44 -0
- package/web/references/dabang/DESIGN.md +57 -19
- package/web/references/dcard/DESIGN.md +57 -0
- package/web/references/dell/DESIGN.md +636 -0
- package/web/references/devsisters/DESIGN.md +29 -0
- package/web/references/discord/DESIGN.md +604 -0
- package/web/references/dji/DESIGN.md +39 -0
- package/web/references/drnow/DESIGN.md +52 -0
- package/web/references/duolingo/DESIGN.md +563 -0
- package/web/references/elevenlabs/DESIGN.md +39 -0
- package/web/references/expo/DESIGN.md +39 -0
- package/web/references/fastcampus/DESIGN.md +50 -0
- package/web/references/ferrari/DESIGN.md +47 -0
- package/web/references/figma/DESIGN.md +44 -0
- package/web/references/finda/DESIGN.md +413 -0
- package/web/references/flex/DESIGN.md +28 -0
- package/web/references/flo/DESIGN.md +43 -0
- package/web/references/framer/DESIGN.md +38 -0
- package/web/references/freee/DESIGN.md +48 -0
- package/web/references/fugle/DESIGN.md +41 -1
- package/web/references/gangnamunni/DESIGN.md +57 -1
- package/web/references/genie/DESIGN.md +415 -0
- package/web/references/github/DESIGN.md +727 -0
- package/web/references/gmarket/DESIGN.md +51 -0
- package/web/references/gogolook/DESIGN.md +25 -1
- package/web/references/gogoro/DESIGN.md +38 -0
- package/web/references/grip/DESIGN.md +39 -0
- package/web/references/hahow/DESIGN.md +26 -0
- package/web/references/hashicorp/DESIGN.md +42 -0
- package/web/references/hogangnono/DESIGN.md +41 -0
- package/web/references/hp/DESIGN.md +563 -0
- package/web/references/hyperconnect/DESIGN.md +393 -0
- package/web/references/hyundaicard/DESIGN.md +24 -0
- package/web/references/ibm/DESIGN.md +44 -0
- package/web/references/ichef/DESIGN.md +44 -0
- package/web/references/ikala/DESIGN.md +400 -0
- package/web/references/inflearn/DESIGN.md +38 -0
- package/web/references/intercom/DESIGN.md +38 -0
- package/web/references/jandi/DESIGN.md +382 -0
- package/web/references/jkopay/DESIGN.md +35 -1
- package/web/references/jobkorea/DESIGN.md +39 -0
- package/web/references/jumpit/DESIGN.md +37 -0
- package/web/references/kakao/DESIGN.md +64 -0
- package/web/references/kakaobank/DESIGN.md +55 -1
- package/web/references/kakaopay/DESIGN.md +59 -0
- package/web/references/kakaot/DESIGN.md +53 -0
- package/web/references/karrot/DESIGN.md +49 -0
- package/web/references/kbank/DESIGN.md +39 -0
- package/web/references/kdan/DESIGN.md +34 -1
- package/web/references/kintone/DESIGN.md +586 -0
- package/web/references/kkbox/DESIGN.md +22 -0
- package/web/references/kkday/DESIGN.md +47 -0
- package/web/references/kmong/DESIGN.md +427 -0
- package/web/references/krafton/DESIGN.md +37 -0
- package/web/references/kraken/DESIGN.md +44 -0
- package/web/references/krds/DESIGN.md +63 -0
- package/web/references/kream/DESIGN.md +32 -0
- package/web/references/kurly/DESIGN.md +38 -1
- package/web/references/laftel/DESIGN.md +40 -0
- package/web/references/lamborghini/DESIGN.md +54 -0
- package/web/references/layerx/DESIGN.md +615 -0
- package/web/references/lezhin/DESIGN.md +47 -0
- package/web/references/line/DESIGN.md +36 -0
- package/web/references/linear.app/DESIGN.md +182 -88
- package/web/references/loom/DESIGN.md +396 -0
- package/web/references/lovable/DESIGN.md +38 -0
- package/web/references/lunit/DESIGN.md +47 -19
- package/web/references/mastercard/DESIGN.md +587 -0
- package/web/references/meituan/DESIGN.md +42 -0
- package/web/references/melon/DESIGN.md +26 -0
- package/web/references/mercari/DESIGN.md +41 -0
- package/web/references/mercury/DESIGN.md +589 -0
- package/web/references/meta/DESIGN.md +615 -0
- package/web/references/millie/DESIGN.md +51 -0
- package/web/references/minimax/DESIGN.md +53 -0
- package/web/references/mintlify/DESIGN.md +45 -0
- package/web/references/miro/DESIGN.md +47 -0
- package/web/references/mistral.ai/DESIGN.md +37 -0
- package/web/references/momoshop/DESIGN.md +43 -0
- package/web/references/money-forward/DESIGN.md +42 -0
- package/web/references/mongodb/DESIGN.md +44 -0
- package/web/references/muji/DESIGN.md +605 -0
- package/web/references/musinsa/DESIGN.md +48 -0
- package/web/references/mustit/DESIGN.md +47 -1
- package/web/references/myrealtrip/DESIGN.md +49 -0
- package/web/references/naver/DESIGN.md +50 -1
- package/web/references/naverwebtoon/DESIGN.md +48 -0
- package/web/references/netflix/DESIGN.md +572 -0
- package/web/references/nexon/DESIGN.md +389 -0
- package/web/references/nhncloud/DESIGN.md +33 -0
- package/web/references/nike/DESIGN.md +588 -0
- package/web/references/note/DESIGN.md +28 -0
- package/web/references/notion/DESIGN.md +48 -0
- package/web/references/nvidia/DESIGN.md +50 -0
- package/web/references/ohouse/DESIGN.md +56 -0
- package/web/references/oliveyoung/DESIGN.md +47 -1
- package/web/references/ollama/DESIGN.md +40 -0
- package/web/references/openai/DESIGN.md +641 -0
- package/web/references/opencode.ai/DESIGN.md +37 -0
- package/web/references/payco/DESIGN.md +40 -0
- package/web/references/paypay/DESIGN.md +656 -0
- package/web/references/pchome/DESIGN.md +439 -0
- package/web/references/perplexity/DESIGN.md +546 -0
- package/web/references/piccollage/DESIGN.md +43 -0
- package/web/references/pinkoi/DESIGN.md +55 -0
- package/web/references/pinterest/DESIGN.md +44 -0
- package/web/references/pixiv/DESIGN.md +613 -0
- package/web/references/pixnet/DESIGN.md +430 -0
- package/web/references/posthog/DESIGN.md +50 -0
- package/web/references/publy/DESIGN.md +52 -0
- package/web/references/qanda/DESIGN.md +49 -1
- package/web/references/ragic/DESIGN.md +444 -0
- package/web/references/ramp/DESIGN.md +634 -0
- package/web/references/rayark/DESIGN.md +22 -0
- package/web/references/raycast/DESIGN.md +45 -0
- package/web/references/remember/DESIGN.md +44 -0
- package/web/references/renault/DESIGN.md +42 -0
- package/web/references/replicate/DESIGN.md +39 -0
- package/web/references/resend/DESIGN.md +44 -0
- package/web/references/retool/DESIGN.md +645 -0
- package/web/references/revolut/DESIGN.md +46 -0
- package/web/references/richart/DESIGN.md +465 -0
- package/web/references/ridi/DESIGN.md +47 -0
- package/web/references/riiid/DESIGN.md +32 -0
- package/web/references/robinhood/DESIGN.md +604 -0
- package/web/references/runwayml/DESIGN.md +45 -0
- package/web/references/sanity/DESIGN.md +50 -0
- package/web/references/sansan/DESIGN.md +631 -0
- package/web/references/sendbird/DESIGN.md +46 -0
- package/web/references/sentry/DESIGN.md +48 -0
- package/web/references/shinhancard/DESIGN.md +421 -0
- package/web/references/shopline/DESIGN.md +431 -0
- package/web/references/slack/DESIGN.md +635 -0
- package/web/references/smarthr/DESIGN.md +48 -0
- package/web/references/smartnews/DESIGN.md +29 -0
- package/web/references/socar/DESIGN.md +35 -0
- package/web/references/soomgo/DESIGN.md +326 -0
- package/web/references/spacex/DESIGN.md +27 -0
- package/web/references/spoon/DESIGN.md +46 -0
- package/web/references/spotify/DESIGN.md +49 -0
- package/web/references/starbucks/DESIGN.md +597 -0
- package/web/references/stripe/DESIGN.md +46 -0
- package/web/references/studio/DESIGN.md +602 -0
- package/web/references/supabase/DESIGN.md +41 -0
- package/web/references/superhuman/DESIGN.md +39 -0
- package/web/references/surveycake/DESIGN.md +442 -0
- package/web/references/tada/DESIGN.md +51 -0
- package/web/references/tesla/DESIGN.md +36 -0
- package/web/references/theverge/DESIGN.md +500 -0
- package/web/references/together.ai/DESIGN.md +33 -0
- package/web/references/toss/DESIGN.md +43 -0
- package/web/references/toss-securities/DESIGN.md +54 -19
- package/web/references/tossbank/DESIGN.md +57 -0
- package/web/references/trenbe/DESIGN.md +41 -0
- package/web/references/triple/DESIGN.md +47 -0
- package/web/references/tumblbug/DESIGN.md +48 -0
- package/web/references/tving/DESIGN.md +40 -0
- package/web/references/uber/DESIGN.md +36 -0
- package/web/references/ubie/DESIGN.md +615 -0
- package/web/references/uniqlo/DESIGN.md +575 -0
- package/web/references/upbit/DESIGN.md +42 -0
- package/web/references/upstage/DESIGN.md +38 -0
- package/web/references/velog/DESIGN.md +28 -0
- package/web/references/vercel/DESIGN.md +44 -0
- package/web/references/voicetube/DESIGN.md +39 -0
- package/web/references/voltagent/DESIGN.md +44 -0
- package/web/references/wadiz/DESIGN.md +71 -19
- package/web/references/wanted/DESIGN.md +46 -0
- package/web/references/warp/DESIGN.md +37 -0
- package/web/references/watcha/DESIGN.md +40 -0
- package/web/references/wavve/DESIGN.md +43 -1
- package/web/references/wconcept/DESIGN.md +45 -0
- package/web/references/webflow/DESIGN.md +49 -0
- package/web/references/wired/DESIGN.md +572 -0
- package/web/references/wise/DESIGN.md +41 -0
- package/web/references/x.ai/DESIGN.md +31 -0
- package/web/references/xiaohongshu/DESIGN.md +39 -0
- package/web/references/yanolja/DESIGN.md +45 -0
- package/web/references/yeogiotte/DESIGN.md +42 -1
- package/web/references/yogiyo/DESIGN.md +50 -0
- package/web/references/yourator/DESIGN.md +453 -0
- package/web/references/zapier/DESIGN.md +41 -0
- package/web/references/zigbang/DESIGN.md +33 -0
- package/web/references/zigzag/DESIGN.md +62 -0
- package/web/references/zozotown/DESIGN.md +578 -0
- package/dist/install-skills-JNH66GOI.js.map +0 -1
package/dist/bin/oh-my-design.js
CHANGED
|
@@ -26,7 +26,7 @@ var program = new Command();
|
|
|
26
26
|
program.name("oh-my-design").description("Bootstrap oh-my-design skills + agents into your project. After install, talk to your agent in natural language \u2014 no other CLI commands.").version(readPackageVersion()).showSuggestionAfterError(true).showHelpAfterError(true);
|
|
27
27
|
program.command("install-skills").description("Install omd skill files + canonical agents into agent directories (.claude/, .codex/, .opencode/). Interactive multiselect TUI by default \u2014 picks which skills + sub-agents to install.").option("--dir <path>", "Project root (defaults to cwd)").option("--agent <name...>", "Restrict to specific channels (claude-code | codex | opencode)").option("--force", "Overwrite existing files even without the omd marker").option("--all", "Skip the interactive TUI and install every shipped skill + agent (use in CI)").option("--skills <names>", "Comma-separated skill names to install (overrides TUI)", (v) => v.split(",").map((s) => s.trim()).filter(Boolean)).option("--agents-only <names>", "Comma-separated agent names to install (overrides TUI). Use --agents-only to disambiguate from --agent (channel selector).", (v) => v.split(",").map((s) => s.trim()).filter(Boolean)).option("--skills-only", "Install only the named skill files \u2014 skip sub-agents, hooks, and settings.json (minimal single-skill install, e.g. --skills claude-design --skills-only)").option("--global", "Install to the user-level dir (~/.claude/skills, available in every project) instead of this project. Writes skills + sub-agents; never touches global hooks/settings.").action(
|
|
28
28
|
async (opts) => {
|
|
29
|
-
const { runInstallSkills } = await import("../install-skills-
|
|
29
|
+
const { runInstallSkills } = await import("../install-skills-YYHEC4CS.js");
|
|
30
30
|
const validAgents = ["claude-code", "codex", "opencode"];
|
|
31
31
|
const agents = opts.agent ? opts.agent.filter(
|
|
32
32
|
(a) => validAgents.includes(a)
|
|
@@ -145,14 +145,14 @@ function planForTarget(projectRoot, target) {
|
|
|
145
145
|
case "codex":
|
|
146
146
|
return {
|
|
147
147
|
target,
|
|
148
|
-
destDir: join2(projectRoot, ".
|
|
148
|
+
destDir: join2(projectRoot, ".agents", "skills"),
|
|
149
149
|
layout: "folder"
|
|
150
150
|
};
|
|
151
151
|
case "opencode":
|
|
152
152
|
return {
|
|
153
153
|
target,
|
|
154
|
-
destDir: join2(projectRoot, ".opencode", "
|
|
155
|
-
layout: "
|
|
154
|
+
destDir: join2(projectRoot, ".opencode", "skills"),
|
|
155
|
+
layout: "folder"
|
|
156
156
|
};
|
|
157
157
|
}
|
|
158
158
|
}
|
|
@@ -168,13 +168,7 @@ function parseSkillChannels(skillMd) {
|
|
|
168
168
|
return list.length > 0 ? list : null;
|
|
169
169
|
}
|
|
170
170
|
function skillSupportedChannels(packageRoot, skill) {
|
|
171
|
-
|
|
172
|
-
let allowed = parseSkillChannels(readFileSync(join2(skillDir, "SKILL.md"), "utf8")) ?? ["claude-code", "codex", "opencode"];
|
|
173
|
-
const extras = readdirSync(skillDir).filter(
|
|
174
|
-
(n) => n !== "SKILL.md" && !IGNORED_SKILL_ENTRIES.has(n)
|
|
175
|
-
);
|
|
176
|
-
if (extras.length > 0) allowed = allowed.filter((c) => c === "claude-code");
|
|
177
|
-
return allowed;
|
|
171
|
+
return parseSkillChannels(readFileSync(join2(packageRoot, "skills", skill, "SKILL.md"), "utf8")) ?? ["claude-code", "codex", "opencode"];
|
|
178
172
|
}
|
|
179
173
|
function installOne(packageRoot, plan, skill, force) {
|
|
180
174
|
const skillDir = join2(packageRoot, "skills", skill);
|
|
@@ -605,4 +599,4 @@ async function runInstallSkills(opts = {}) {
|
|
|
605
599
|
export {
|
|
606
600
|
runInstallSkills
|
|
607
601
|
};
|
|
608
|
-
//# sourceMappingURL=install-skills-
|
|
602
|
+
//# sourceMappingURL=install-skills-YYHEC4CS.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 cpSync,\n} from 'node:fs';\nimport { join, dirname, relative } from 'node:path';\nimport { homedir } from 'node:os';\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 /** Minimal install: only the named skill files — skip sub-agents, data files,\n * hooks, and settings.json. Ideal for shipping a single standalone skill. */\n skillsOnly?: boolean;\n /** Install to the user-level dir (~/.claude/skills) instead of this project.\n * Writes skills + sub-agents (+ data); never touches global hooks/settings.\n * When unset and interactive, the TUI asks project-vs-global. */\n global?: boolean;\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 // Official Codex skill discovery path is `.agents/skills/<name>/SKILL.md`\n // (developers.openai.com/codex/skills) — NOT `.codex/skills`. Folder layout\n // so multi-file skills (scripts/, references/) install + run.\n return {\n target,\n destDir: join(projectRoot, '.agents', 'skills'),\n layout: 'folder',\n };\n case 'opencode':\n // OpenCode loads `.opencode/skills/<name>/SKILL.md` (opencode.ai/docs/skills)\n // as folder skills — the old flat `.opencode/agents/<name>.md` couldn't host\n // a skill's scripts/references.\n return {\n target,\n destDir: join(projectRoot, '.opencode', 'skills'),\n layout: 'folder',\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' | 'skipped-incompat';\n}\n\n// Skill-tree entries that must never be installed (runtime state, caches, OS cruft).\nconst IGNORED_SKILL_ENTRIES = new Set(['.runtime', '__pycache__', '.DS_Store']);\n\n/**\n * A skill may restrict itself to specific agent channels via a frontmatter line\n * `x-omd-channels: claude-code` (comma/space separated). Returns the allowed\n * channels, or null when channel-agnostic (installs anywhere). Used by skills that\n * depend on a particular agent runtime — e.g. claude-design needs Claude Code's\n * claude-in-chrome MCP + Bash/python/node and is therefore claude-code only.\n */\nfunction parseSkillChannels(skillMd: string): SkillTarget[] | null {\n const fm = /^---\\n([\\s\\S]*?)\\n---/.exec(skillMd);\n if (!fm) return null;\n const m = /^x-omd-channels:\\s*(.+)$/m.exec(fm[1]);\n if (!m) return null;\n const valid: SkillTarget[] = ['claude-code', 'codex', 'opencode'];\n const list = m[1]\n .split(/[,\\s]+/)\n .map((s) => s.trim())\n .filter((s): s is SkillTarget => (valid as string[]).includes(s));\n return list.length > 0 ? list : null;\n}\n\n/**\n * The agent channels a skill can install into: its declared `x-omd-channels`\n * (if any), else all channels. All three channels now use folder layout\n * (.claude/skills, .agents/skills, .opencode/skills) so multi-file skills with\n * scripts/references install everywhere — the only restriction is what the skill\n * itself declares (e.g. claude-design needs a browser-driving runtime).\n */\nfunction skillSupportedChannels(packageRoot: string, skill: string): SkillTarget[] {\n return (\n parseSkillChannels(readFileSync(join(packageRoot, 'skills', skill, 'SKILL.md'), 'utf8')) ??\n (['claude-code', 'codex', 'opencode'] as SkillTarget[])\n );\n}\n\nfunction installOne(\n packageRoot: string,\n plan: InstallPlan,\n skill: string,\n force: boolean\n): InstallResult {\n const skillDir = join(packageRoot, 'skills', skill);\n const src = readFileSync(join(skillDir, 'SKILL.md'), 'utf8');\n const managed = MANAGED_HEADER + '\\n\\n' + src;\n\n // Respect a skill's declared channel restriction (frontmatter `x-omd-channels:`).\n const channels = parseSkillChannels(src);\n if (channels && !channels.includes(plan.target)) {\n return {\n target: plan.target,\n skill,\n destPath: join(plan.destDir, skill + '.md'),\n status: 'skipped-incompat',\n };\n }\n\n // A skill is \"multi-file\" when it ships more than SKILL.md (scripts/, references/, …).\n const extras = readdirSync(skillDir).filter(\n (n) => n !== 'SKILL.md' && !IGNORED_SKILL_ENTRIES.has(n)\n );\n const isMultiFile = extras.length > 0;\n\n // Flat channels (codex/opencode) store a skill as a single <skill>.md and cannot\n // host a multi-file skill's scripts/references — such skills are claude-code only.\n if (plan.layout !== 'folder' && isMultiFile) {\n return {\n target: plan.target,\n skill,\n destPath: join(plan.destDir, skill + '.md'),\n status: 'skipped-incompat',\n };\n }\n\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 // Drift protection guards the user-editable SKILL.md. Single-file skills can\n // short-circuit on \"unchanged\"; multi-file skills always re-sync their tree.\n if (exists && existing === managed && !isMultiFile) {\n return { target: plan.target, skill, destPath, status: 'unchanged' };\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\n // Copy the rest of the skill tree (scripts/, references/, …) for folder layout.\n if (plan.layout === 'folder' && isMultiFile) {\n const destSkillDir = join(plan.destDir, skill);\n for (const entry of extras) {\n cpSync(join(skillDir, entry), join(destSkillDir, entry), {\n recursive: true,\n filter: (s) => !/(\\/__pycache__|\\/\\.runtime|\\.pyc$|\\.DS_Store$)/.test(s),\n });\n }\n }\n\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 'skipped-incompat': pc.yellow('skipped (claude-code only)'),\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 const minimal = opts.skillsOnly === true;\n // Install scope: 'project' (<cwd>/.claude/…) or 'global' (~/.claude/…). --global\n // forces it; otherwise the interactive TUI asks. Global writes skills + sub-agents\n // (+ data) to the user-level dir but never touches global hooks/settings.json.\n let scope: 'project' | 'global' = opts.global ? 'global' : 'project';\n\n p.intro(\n pc.bold('omd install-skills') +\n pc.dim(` (${relative(process.cwd(), projectRoot) || '.'})`)\n );\n\n // Each dimension (scope / skills / sub-agents / channels) is resolved\n // independently: a CLI flag pins it; otherwise we prompt — but only when stdin\n // is a TTY and --all wasn't passed. This is the key fix: `--skills X` or\n // `--skills-only` no longer suppress the *channel* (where to install) prompt —\n // they only pin the dimension they name.\n const isTTY = Boolean(process.stdin.isTTY && process.stdout.isTTY);\n const interactive = isTTY && !opts.all;\n\n const detected = autoDetectTargets(projectRoot);\n // Real presence (not the all-3 fallback) — used for hint labels + prompt defaults.\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 // --- Scope (project vs global) — --global pins it, else ask / default project.\n if (!opts.global && interactive) {\n const scopeResult = await p.select({\n message: 'Install scope · 어디에 설치할까요?',\n options: [\n { value: 'project', label: 'Project', hint: `${relative(process.cwd(), projectRoot) || '.'}/.claude/skills · 이 프로젝트만` },\n { value: 'global', label: 'Global', hint: '~/.claude/skills · 모든 프로젝트 (skills + sub-agents, hooks/settings 제외)' },\n ],\n initialValue: 'project',\n });\n if (p.isCancel(scopeResult)) { p.cancel('Install cancelled.'); return 130; }\n scope = scopeResult as 'project' | 'global';\n }\n\n // --- Skills — --skills pins it, else ask / default ALL.\n let skills: string[];\n if (opts.skillsFilter) {\n skills = allSkills.filter((s) => opts.skillsFilter!.includes(s));\n } else if (interactive) {\n const skillResult = await p.multiselect({\n message: '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)) { p.cancel('Install cancelled.'); return 130; }\n skills = skillResult as string[];\n } else {\n skills = allSkills;\n }\n\n // --- Sub-agents — dropped by --skills-only, else --agents pins, else ask / ALL.\n let canonicalAgents: string[];\n if (minimal) {\n canonicalAgents = [];\n } else if (opts.agentsFilter) {\n canonicalAgents = allAgents.filter((a) => opts.agentsFilter!.includes(a.replace(/\\.md$/, '')));\n } else if (interactive && allAgents.length > 0) {\n const agentResult = await p.multiselect({\n message: 'Sub-agents · space = 토글 · a = 전체 · enter = 확인 (default ALL)',\n options: allAgents.map((a) => ({ value: a, label: a.replace(/\\.md$/, ''), hint: 'subagent' })),\n initialValues: allAgents,\n required: false,\n });\n if (p.isCancel(agentResult)) { p.cancel('Install cancelled.'); return 130; }\n canonicalAgents = agentResult as string[];\n } else {\n canonicalAgents = allAgents;\n }\n\n // --- Channels / targets — the \"where do I install\" choice.\n // --agent pins it. Otherwise, in a TTY we ASK — limited to the channels the\n // selected skills actually support (claude-design is claude-code only, so its\n // picker shows just Claude Code). Non-TTY / --all falls back to auto-resolution.\n const supportedTargets = ((): SkillTarget[] => {\n const set = new Set<SkillTarget>(skills.flatMap((s) => skillSupportedChannels(packageRoot, s)));\n return (['claude-code', 'codex', 'opencode'] as SkillTarget[]).filter((t) => set.has(t));\n })();\n const channelLabel: Record<SkillTarget, string> = {\n 'claude-code': 'Claude Code',\n codex: 'Codex',\n opencode: 'OpenCode',\n };\n const channelDir: Record<SkillTarget, string> = {\n 'claude-code': '.claude',\n codex: '.codex',\n opencode: '.opencode',\n };\n let targets: SkillTarget[];\n if (opts.agents) {\n targets = opts.agents;\n } else if (interactive) {\n const defaults = actuallyDetected.filter((t) => supportedTargets.includes(t));\n const targetResult = await p.multiselect({\n message: 'Agent channels · 어디에 설치할까요? · space = 토글 · enter = 확인',\n options: supportedTargets.map((t) => ({\n value: t,\n label: channelLabel[t],\n hint: actuallyDetected.includes(t) ? `${channelDir[t]}/ detected` : '',\n })) as { value: SkillTarget; label: string; hint?: string }[],\n initialValues: defaults.length > 0 ? defaults : supportedTargets,\n required: true,\n });\n if (p.isCancel(targetResult)) { p.cancel('Install cancelled.'); return 130; }\n targets = targetResult as SkillTarget[];\n } else {\n // Non-interactive (CI / piped / --all): resolve from flags + detection,\n // narrowed to channels the selected skills support.\n targets = opts.all\n ? (['claude-code', 'codex', 'opencode'] as SkillTarget[])\n : minimal\n ? (actuallyDetected.length > 0 ? actuallyDetected : (['claude-code'] as SkillTarget[]))\n : detected;\n const narrowed = targets.filter((t) => supportedTargets.includes(t));\n if (narrowed.length > 0) targets = narrowed;\n }\n\n // Global scope roots everything at the home dir, so plan dirs resolve to\n // ~/.claude/skills, ~/.claude/agents, etc. Project scope uses cwd (or --dir).\n const installRoot = scope === 'global' ? homedir() : projectRoot;\n const plans = targets.map((t) => planForTarget(installRoot, t));\n\n p.log.message(\n pc.bold('Scope: ') +\n pc.cyan(scope) +\n pc.dim(scope === 'global' ? ' (~/.claude)' : ` (${relative(process.cwd(), projectRoot) || '.'})`)\n );\n p.log.message(\n pc.bold(`Skills (${skills.length}): `) +\n skills.map((s) => pc.cyan(s)).join(', ')\n );\n if (minimal) {\n // --skills-only: sub-agents are intentionally skipped (minimal single-skill\n // install). Clear BEFORE the summary so we never print agents we won't write.\n canonicalAgents = [];\n p.log.message(pc.bold('Agents: ') + pc.dim('skipped (--skills-only)'));\n } else 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, installRoot, 'claude', filename, force));\n }\n } else if (target === 'codex') {\n for (const filename of canonicalAgents) {\n results.push(installAgentFile(packageRoot, installRoot, 'codex', filename, force));\n }\n }\n // OpenCode currently has no agent-definition channel — skills only.\n }\n\n if (!minimal) {\n // Ship the read-only data assets (reference fingerprints, controlled vocab,\n // human-readable tag matrix, opt-out corpus) so skills + hooks can run entirely\n // 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, installRoot, '.claude', dataFile, force));\n }\n } else if (target === 'codex') {\n for (const dataFile of dataFiles) {\n results.push(installDataFile(packageRoot, installRoot, '.codex', dataFile, force));\n }\n }\n }\n\n // Hooks + settings.json are PROJECT-SCOPED only — a global install must not\n // mutate the user's global Claude config / make hooks fire in every project.\n if (scope === 'project' && 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, installRoot, hookFile, force));\n }\n // settings.json (with merge, never clobber user)\n results.push(installSettingsJson(packageRoot, installRoot, force));\n }\n } // !minimal — skills-only skips data files, hooks, and settings.json\n\n p.log.message(pc.bold('\\nResults:'));\n for (const r of results) {\n const rel = relative(installRoot, 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 // Minimal single-skill install (--skills-only): no omd onboarding, no agents/hooks.\n // Ideal for shipping a standalone skill (e.g. claude-design) to people who don't\n // want the rest of the omd toolchain.\n if (minimal) {\n for (const r of results.filter((x) => x.status === 'skipped-incompat')) {\n p.log.warn(\n `${pc.bold(r.skill)} ${pc.dim('skipped for ')}${pc.cyan(r.target)}${pc.dim(' — declares x-omd-channels (channel not supported).')}`\n );\n }\n const installed = results.filter(\n (r) => r.status === 'created' || r.status === 'updated'\n );\n if (installed.length === 0) {\n p.outro(pc.yellow('Nothing installed — no compatible skill/channel match.'));\n return 0;\n }\n p.outro(\n pc.green(\n `Done. Installed ${skills.map((s) => pc.bold(s)).join(', ')} ${scope === 'global' ? 'globally (~/.claude/skills)' : `for ${targets.join(', ')}`}.`\n ) +\n pc.dim(' → restart your agent, then use the skill (e.g. ') +\n pc.cyan('/claude-design') +\n pc.dim(').')\n );\n return 0;\n }\n\n // Friendly next-step nudge after successful install.\n // The first prompt is kept identical to the README's \"Your first 60 seconds\"\n // block so the README, the terminal, and the postinstall message all teach\n // the same activation moment. Bilingual (EN + KR) so an English reader is not\n // handed a Korean-only outro.\n const nextSteps = [\n `${pc.bold('Restart your agent, then type your first prompt:')}`,\n '',\n ` ${pc.cyan('EN')} ${pc.dim('Set up our design system — Toss-style, for a family meal-tracking app.')}`,\n ` ${pc.cyan('KR')} ${pc.dim('토스 스타일로 가족 식단 공유 앱 디자인 시스템 잡아줘')}`,\n '',\n `${pc.dim('Your agent runs omd:init and writes DESIGN.md. Then build against it:')}`,\n ` ${pc.cyan('EN')} ${pc.dim('Design the home screen.')} ${pc.cyan('KR')} ${pc.dim('홈 화면 디자인해줘')}`,\n '',\n `${pc.dim('Full walkthrough → \"Your first 60 seconds\" in the README. Routing is automatic — no slash command needed.')}`,\n `${pc.dim('Power user: ')}${pc.cyan('/omd-harness <task>')}${pc.dim(' — jump straight into the pipeline.')}`,\n '',\n `${pc.yellow('⚠ Already-running session?')} ${pc.dim('Run `/agents` 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 // Counts derived from what was actually resolved/installed — never hardcoded,\n // so the outro can't drift from the real skill/agent/hook set (or the README).\n const hookCount = scope === 'project' && targets.includes('claude-code') ? 4 : 0;\n p.outro(\n pc.green(\n `Done. ${skills.length} skills · ${canonicalAgents.length} sub-agents · ${hookCount} hooks installed (${writtenCount} files)${scope === 'global' ? ' globally (~/.claude)' : ''}.`,\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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oh-my-design-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Bootstrap oh-my-design skills + agents into your project. After install, talk to your AI coding agent in natural language — no other CLI commands.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,10 +11,10 @@ description: |
|
|
|
11
11
|
트리거: "/claude-design", "이 코드베이스를 클로드 디자인으로 옮겨줘",
|
|
12
12
|
"코드베이스 분석해서 claude design에 전달", "랜딩 디자인 뽑아줘", "claude.ai/design에 보내줘",
|
|
13
13
|
"클로드 디자인에 디자인 요청해줘", "이 레퍼런스로 디자인 만들어줘", "Claude Design에 시안 요청".
|
|
14
|
-
x-omd-channels: claude-code
|
|
14
|
+
x-omd-channels: claude-code codex opencode
|
|
15
15
|
metadata:
|
|
16
16
|
author: 곽성재
|
|
17
|
-
version: "2.
|
|
17
|
+
version: "2.1.0"
|
|
18
18
|
---
|
|
19
19
|
|
|
20
20
|
# Claude Design 컨텍스트 이관 스킬 (v2)
|
|
@@ -157,6 +157,11 @@ python3 ~/.claude/skills/claude-design/scripts/gather_references.py --root "$PWD
|
|
|
157
157
|
|
|
158
158
|
### STEP 5 — DRIVE: Playwright 드라이버 (★ 권장 백엔드)
|
|
159
159
|
|
|
160
|
+
> **채널 호환:** 이 스킬은 claude-code · codex · opencode 모두에서 동작한다. Playwright 백엔드는
|
|
161
|
+
> `node`만 있으면 되는 **에이전트 독립적** 경로이므로 codex/opencode에서도 그대로 쓴다. 단,
|
|
162
|
+
> **STEP 5-FALLBACK(claude-in-chrome)은 Claude Code 전용**이라 codex/opencode에서는 사용 불가 —
|
|
163
|
+
> 이 두 채널에서는 **Playwright가 사실상 필수**이고, 없으면 수동 핸드오프로 전환한다.
|
|
164
|
+
|
|
160
165
|
claude.ai/design 구동의 **1순위 백엔드**. Playwright 가 **자체 Chrome 인스턴스를 CDP 로 직접
|
|
161
166
|
구동**하므로 claude-in-chrome 의 **포그라운드 의존 버그가 없고**(클릭/스크린샷 안정), **전용
|
|
162
167
|
프로필**이라 사용자의 다른 확장과 충돌하지 않는다. 단계별 스크린샷이 떠서 셀렉터 검증·디버깅도 쉽다.
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: 11st
|
|
3
|
+
name: 11st
|
|
4
|
+
country: KR
|
|
5
|
+
category: e-commerce
|
|
6
|
+
homepage: "https://www.11st.co.kr"
|
|
7
|
+
primary_color: "#ff0038"
|
|
8
|
+
logo:
|
|
9
|
+
type: favicon
|
|
10
|
+
slug: "https://www.google.com/s2/favicons?domain=11st.co.kr&sz=128"
|
|
11
|
+
verified: "2026-06-09"
|
|
12
|
+
added: "2026-06-09"
|
|
13
|
+
omd: "0.1"
|
|
14
|
+
tokens:
|
|
15
|
+
source: live-extract
|
|
16
|
+
extracted: "2026-06-09"
|
|
17
|
+
components_harvested: true
|
|
18
|
+
colors:
|
|
19
|
+
primary: "#ff0038"
|
|
20
|
+
accent-discount: "#f43142"
|
|
21
|
+
canvas: "#ffffff"
|
|
22
|
+
heading: "#111111"
|
|
23
|
+
body: "#666666"
|
|
24
|
+
muted: "#a9a9a9"
|
|
25
|
+
on-primary: "#ffffff"
|
|
26
|
+
hairline: "#eeeeee"
|
|
27
|
+
ink: "#000000"
|
|
28
|
+
typography:
|
|
29
|
+
family: { sans: "Noto Sans KR", fallback: "Apple SD Gothic Neo, Malgun Gothic, sans-serif" }
|
|
30
|
+
price-hero: { size: 24, weight: 700, lineHeight: 1.2, tracking: 0, use: "Discount price, the largest emphasis unit on a product card" }
|
|
31
|
+
search-input: { size: 16, weight: 400, lineHeight: 1.3, tracking: 0, use: "Global search field text and active search tab label" }
|
|
32
|
+
price-base: { size: 16, weight: 400, lineHeight: 1.3, tracking: 0, use: "Standard price figure, sub-amounts" }
|
|
33
|
+
body: { size: 14, weight: 400, lineHeight: 1.5, tracking: 0, use: "Default reading text, links, nav, labels" }
|
|
34
|
+
heading: { size: 14, weight: 700, lineHeight: 1.5, tracking: 0, use: "Section heads, h1/h2, strong labels" }
|
|
35
|
+
caption: { size: 13, weight: 400, lineHeight: 1.4, tracking: 0, use: "Strikethrough original price, metadata, fine print" }
|
|
36
|
+
micro: { size: 11, weight: 400, lineHeight: 1.4, tracking: 0, use: "Unit suffix, badge digits, tiny labels" }
|
|
37
|
+
spacing: { xs: 4, sm: 8, md: 12, base: 16, lg: 20, xl: 24, xxl: 40 }
|
|
38
|
+
rounded: { sm: 4, md: 8, lg: 25, full: 9999 }
|
|
39
|
+
shadow:
|
|
40
|
+
none: "none"
|
|
41
|
+
ambient: "rgba(0,0,0,0.06) 0px 2px 8px"
|
|
42
|
+
elevated: "rgba(0,0,0,0.12) 0px 6px 16px"
|
|
43
|
+
components:
|
|
44
|
+
button-primary: { type: button, bg: "#ff0038", fg: "#ffffff", radius: "4px", padding: "12px 24px", font: "16px / 700", use: "Primary commerce CTA (구매하기/장바구니), brand red on white" }
|
|
45
|
+
button-ghost: { type: button, bg: "#ffffff", fg: "#111111", radius: "4px", padding: "10px 20px", font: "14px / 400", use: "Secondary action, 1px #eeeeee border" }
|
|
46
|
+
search-tab: { type: tab, fg: "#111111", radius: "25px", padding: "0px 20px 2px", font: "16px / 400", active: "active label #ff0038, rounded 25px pill seat", use: "Integrated search-scope selector (통합검색/아마존)" }
|
|
47
|
+
input-search: { type: input, bg: "#ffffff", fg: "#666666", radius: "4px", padding: "0px 12px", font: "16px / 400", use: "Global search field, hairline #eeeeee underline/border" }
|
|
48
|
+
card-product: { type: card, bg: "#ffffff", fg: "#111111", radius: "8px", use: "Product tile, 1px #eeeeee border, ambient shadow on hover" }
|
|
49
|
+
badge-discount: { type: badge, bg: "#ffffff", fg: "#f43142", radius: "4px", padding: "1px 6px", font: "11px / 700", use: "Discount-rate flag (13%할인), red figure on white" }
|
|
50
|
+
price-discount: { type: badge, bg: "#ffffff", fg: "#111111", radius: "4px", padding: "0px", font: "24px / 700", use: "Final discount price, the loudest type on a card" }
|
|
51
|
+
price-original: { type: badge, bg: "#ffffff", fg: "#a9a9a9", radius: "4px", padding: "0px", font: "13px / 400", use: "Strikethrough original price (판매가)" }
|
|
52
|
+
listItem-nav: { type: listItem, bg: "#ffffff", fg: "#666666", radius: "0px", padding: "0px 12px", font: "14px / 400", use: "GNB / category nav row, #111111 on hover" }
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
# Design System Inspiration of 11st
|
|
56
|
+
|
|
57
|
+
## 1. Visual Theme & Atmosphere
|
|
58
|
+
|
|
59
|
+
11st (11번가) is one of Korea's largest open-market e-commerce platforms, and its homepage is built for one job: get a shopper from a vast, dense catalog to a purchase decision as fast as possible. The atmosphere is high-density and utilitarian — a white canvas (`#ffffff`) packed edge-to-edge with product tiles, prices, badges, and navigation. This is not the airy, whitespace-luxury aesthetic of a Western SaaS landing page; it is the deliberate, information-rich layout of a Korean mega-mall portal where every pixel of above-the-fold real estate earns its place. The reading temperature is set by a neutral gray body color (`#666666`) on near-black headings (`#111111`), keeping the chrome quiet so that color can do exactly one thing: signal commerce urgency.
|
|
60
|
+
|
|
61
|
+
That single signal is the 11st brand red. The live DOM reveals a sharp, saturated red (`#ff0038`) on active and focused states, paired with a slightly warmer discount-accent red (`#f43142`) for promotional emphasis like discount-rate flags. Against the otherwise grayscale field, red is never decorative — it marks the thing you should act on or the saving you should notice. This restraint is the system's core discipline: a dense gray-and-white grid in which red is the only loud color, so the eye is trained to follow it straight to a CTA or a markdown.
|
|
62
|
+
|
|
63
|
+
Typography is Korean-first. The stack leads with `Noto Sans KR`, falling back through `Apple SD Gothic Neo` and `Malgun Gothic` — the standard high-legibility Hangul stack tuned for small sizes at high density. Most text runs at 14px, the workhorse size of Korean portal UIs, with prices escalating to a bold 24px (`#111111`, weight 700) as the loudest unit on any product card. There is almost no shadow and almost no rounding in the base chrome; depth and softness are reserved, appearing only as gentle ambient elevation on hover and a distinctive 25px-radius pill on the integrated search-scope selector.
|
|
64
|
+
|
|
65
|
+
**Key Characteristics:**
|
|
66
|
+
- White canvas (`#ffffff`) with a high-density grid — information-rich, portal-style, anti-whitespace
|
|
67
|
+
- Brand red `#ff0038` as the single action/urgency color on active and focused states
|
|
68
|
+
- Warmer discount red `#f43142` reserved for promotional markdown emphasis
|
|
69
|
+
- `Noto Sans KR` Hangul-first stack tuned for small-size legibility at high density
|
|
70
|
+
- 14px body workhorse; bold 24px price (`#111111` weight 700) as the dominant card emphasis
|
|
71
|
+
- Strikethrough original price in muted gray (`#a9a9a9`) beside the red-adjacent discount figure
|
|
72
|
+
- Near-flat chrome — minimal shadow, minimal rounding — with a signature 25px search-tab pill
|
|
73
|
+
- Grayscale field (`#111111`/`#666666`/`#a9a9a9`) so red reads as pure intent
|
|
74
|
+
|
|
75
|
+
## 2. Color Palette & Roles
|
|
76
|
+
|
|
77
|
+
### Primary
|
|
78
|
+
- **11st Red** (`#ff0038`): The brand's signature commerce red. Active/focused search-tab state, primary CTA backgrounds, urgency markers. A sharp, fully saturated red that anchors the entire system.
|
|
79
|
+
- **Near-Black Ink** (`#111111`): Primary heading and price color. Not pure black — a dense near-black that reads as authoritative without harshness on white.
|
|
80
|
+
- **Pure White** (`#ffffff`): Page background, card surfaces, search field fill, CTA text on red.
|
|
81
|
+
|
|
82
|
+
### Accent
|
|
83
|
+
- **Discount Red** (`#f43142`): Slightly warmer red used on promotional emphasis — discount-rate flags, "관련 상품" accent emphasis. Distinct from the action-red `#ff0038`; this is the markdown/savings register.
|
|
84
|
+
|
|
85
|
+
### Neutral Scale
|
|
86
|
+
- **Body Gray** (`#666666`): Secondary text, navigation links, labels, the default reading color across chrome.
|
|
87
|
+
- **Muted Gray** (`#a9a9a9`): Strikethrough original price (판매가), de-emphasized metadata, the "before discount" register.
|
|
88
|
+
- **Hairline** (`#eeeeee`): Card borders, dividers, search-field underlines, the quiet structural lines of the dense grid.
|
|
89
|
+
|
|
90
|
+
### Surface & Ink
|
|
91
|
+
- **Ink** (`#000000`): True black reserved for fine iconography and maximal-contrast micro-elements.
|
|
92
|
+
- **On-Primary** (`#ffffff`): Text and icon color on the red CTA surface.
|
|
93
|
+
|
|
94
|
+
## 3. Typography Rules
|
|
95
|
+
|
|
96
|
+
### Font Family
|
|
97
|
+
- **Primary**: `Noto Sans KR`
|
|
98
|
+
- **Fallback**: `Apple SD Gothic Neo`, `Malgun Gothic`, `맑은 고딕`, `돋움`, `sans-serif`
|
|
99
|
+
- The Hangul-first stack is chosen for crisp legibility at the small sizes (11px–14px) that dominate a dense commerce grid.
|
|
100
|
+
|
|
101
|
+
### Hierarchy
|
|
102
|
+
|
|
103
|
+
| Role | Size | Weight | Line Height | Use |
|
|
104
|
+
|------|------|--------|-------------|-----|
|
|
105
|
+
| Price Hero | 24px | 700 | 1.2 | Final discount price — the largest, loudest unit on a product card |
|
|
106
|
+
| Search Input | 16px | 400 | 1.3 | Global search field text and active search-tab label |
|
|
107
|
+
| Price Base | 16px | 400 | 1.3 | Standard price figure, sub-amounts |
|
|
108
|
+
| Heading | 14px | 700 | 1.5 | Section heads, h1/h2, strong labels |
|
|
109
|
+
| Body | 14px | 400 | 1.5 | Default reading text, links, nav rows |
|
|
110
|
+
| Caption | 13px | 400 | 1.4 | Strikethrough original price, metadata, fine print |
|
|
111
|
+
| Micro | 11px | 400 | 1.4 | Unit suffix (원), badge digits, tiny labels |
|
|
112
|
+
|
|
113
|
+
### Principles
|
|
114
|
+
- **14px is the workhorse.** The vast majority of chrome, navigation, and labels sit at 14px — the standard Korean portal reading size that balances density against legibility.
|
|
115
|
+
- **Price is the headline.** There is no 48px hero type here. The biggest, boldest text on the page is a price (24px / 700). In a commerce-first system, the number IS the headline.
|
|
116
|
+
- **Weight binary.** The system runs on 400 (everything) and 700 (headings, prices, emphasis). There is no light-weight display register; this is functional type, not luxury type.
|
|
117
|
+
- **Hangul-first legibility.** `Noto Sans KR` is engineered for the small sizes the dense grid demands; the fallback chain keeps Hangul rendering consistent across OSes.
|
|
118
|
+
|
|
119
|
+
## 4. Component Stylings
|
|
120
|
+
|
|
121
|
+
### Buttons
|
|
122
|
+
**Primary CTA**
|
|
123
|
+
- Background: `#ff0038`
|
|
124
|
+
- Text: `#ffffff`
|
|
125
|
+
- Padding: 12px 24px
|
|
126
|
+
- Radius: 4px
|
|
127
|
+
- Font: 16px Noto Sans KR weight 700
|
|
128
|
+
- Use: Primary commerce action (구매하기 / 장바구니 / 주문하기)
|
|
129
|
+
|
|
130
|
+
**Ghost / Secondary**
|
|
131
|
+
- Background: `#ffffff`
|
|
132
|
+
- Text: `#111111`
|
|
133
|
+
- Padding: 10px 20px
|
|
134
|
+
- Radius: 4px
|
|
135
|
+
- Border: `1px solid #eeeeee`
|
|
136
|
+
- Use: Secondary actions, filters, quiet controls
|
|
137
|
+
|
|
138
|
+
### Search Tab (integrated scope selector)
|
|
139
|
+
- Active label color: `#ff0038`
|
|
140
|
+
- Radius: 25px (the system's signature pill seat)
|
|
141
|
+
- Padding: 0px 20px 2px
|
|
142
|
+
- Font: 16px weight 400
|
|
143
|
+
- Use: Switching search scope (통합검색 / 아마존). The 25px-radius pill is the one place softness appears in the chrome.
|
|
144
|
+
|
|
145
|
+
### Search Input
|
|
146
|
+
- Background: `#ffffff`
|
|
147
|
+
- Text: `#666666`
|
|
148
|
+
- Radius: 4px
|
|
149
|
+
- Padding: 0px 12px
|
|
150
|
+
- Border: hairline `#eeeeee` underline/border
|
|
151
|
+
- Font: 16px weight 400
|
|
152
|
+
|
|
153
|
+
### Cards & Containers (product tile)
|
|
154
|
+
- Background: `#ffffff`
|
|
155
|
+
- Border: `1px solid #eeeeee`
|
|
156
|
+
- Radius: 8px
|
|
157
|
+
- Shadow: none at rest; ambient `rgba(0,0,0,0.06) 0px 2px 8px` on hover
|
|
158
|
+
- Contents: thumbnail, title at 14px, discount-rate badge, 24px/700 discount price, strikethrough original price at 13px `#a9a9a9`
|
|
159
|
+
|
|
160
|
+
### Badges
|
|
161
|
+
**Discount-Rate Flag**
|
|
162
|
+
- Background: `#ffffff`
|
|
163
|
+
- Text: `#f43142`
|
|
164
|
+
- Padding: 1px 6px
|
|
165
|
+
- Radius: 4px
|
|
166
|
+
- Font: 11px weight 700
|
|
167
|
+
- Use: Discount percentage (13%할인) — red figure on white
|
|
168
|
+
|
|
169
|
+
### Navigation (GNB / category)
|
|
170
|
+
- Row text: `#666666` at 14px weight 400
|
|
171
|
+
- Hover: text shifts to `#111111`
|
|
172
|
+
- Radius: 0px (square, dense rows)
|
|
173
|
+
- White background, hairline `#eeeeee` dividers
|
|
174
|
+
|
|
175
|
+
**Tier 1 sources:** https://www.11st.co.kr, https://about.11st.co.kr
|
|
176
|
+
|
|
177
|
+
## 5. Layout Principles
|
|
178
|
+
|
|
179
|
+
### Spacing System
|
|
180
|
+
- Base unit: 4px
|
|
181
|
+
- Scale: 4px, 8px, 12px, 16px, 20px, 24px, 40px
|
|
182
|
+
- The dense small-end steps (4–12px) reflect a layout that packs many tiles and labels into limited vertical space.
|
|
183
|
+
|
|
184
|
+
### Grid & Container
|
|
185
|
+
- Wide centered content column (~1240px) holding multi-column product grids
|
|
186
|
+
- Above-the-fold is information-maximal: GNB, integrated search, category rail, promo banners, and product rails all compete for the first screen
|
|
187
|
+
- Product tiles arranged in dense responsive grids (typically 4–6 across on desktop)
|
|
188
|
+
|
|
189
|
+
### Whitespace Philosophy
|
|
190
|
+
- **Density as a feature.** Unlike whitespace-forward Western design, 11st treats density as a service to the shopper — more products visible means fewer scrolls to a decision. Gaps are tight and functional.
|
|
191
|
+
- **Hairlines over gaps.** Structure is communicated by `#eeeeee` hairlines and borders rather than large empty margins, keeping the grid legible while maximizing content.
|
|
192
|
+
|
|
193
|
+
### Border Radius Scale
|
|
194
|
+
- Square (0px): Nav rows, dividers, many structural blocks
|
|
195
|
+
- Small (4px): Buttons, badges, inputs — the workhorse rounding
|
|
196
|
+
- Medium (8px): Product cards, thumbnails
|
|
197
|
+
- Pill (25px): The integrated search-scope tab — the signature soft accent
|
|
198
|
+
- Full (9999px): Circular icon buttons (scroll-to-top, category toggle)
|
|
199
|
+
|
|
200
|
+
## 6. Depth & Elevation
|
|
201
|
+
|
|
202
|
+
| Level | Treatment | Use |
|
|
203
|
+
|-------|-----------|-----|
|
|
204
|
+
| Flat (Level 0) | No shadow | Page background, nav rows, resting tiles |
|
|
205
|
+
| Ambient (Level 1) | `rgba(0,0,0,0.06) 0px 2px 8px` | Product-card hover lift, quiet panels |
|
|
206
|
+
| Elevated (Level 2) | `rgba(0,0,0,0.12) 0px 6px 16px` | Dropdowns, popovers, floating layers |
|
|
207
|
+
|
|
208
|
+
**Shadow Philosophy**: 11st is a near-flat system. At rest, the chrome carries no shadow at all — structure comes from hairlines and color blocks, not elevation. Shadow appears only as a soft ambient lift on hover and as a slightly deeper layer for floating menus. This restraint keeps the dense grid visually calm; if every tile cast a shadow, the page would become noise. Depth is an interaction affordance here, not a decorative style.
|
|
209
|
+
|
|
210
|
+
## 7. Do's and Don'ts
|
|
211
|
+
|
|
212
|
+
### Do
|
|
213
|
+
- Use `#ff0038` as the single action/urgency color — CTAs, active states, focus
|
|
214
|
+
- Reserve `#f43142` for discount/markdown emphasis (rate flags, savings)
|
|
215
|
+
- Make the price the loudest element: 24px weight 700 in `#111111`
|
|
216
|
+
- Pair the discount price with a muted `#a9a9a9` strikethrough original
|
|
217
|
+
- Use `Noto Sans KR` and keep body text at 14px for portal-grade density
|
|
218
|
+
- Communicate structure with `#eeeeee` hairlines, not large gaps
|
|
219
|
+
- Keep chrome flat; reserve ambient shadow for hover/floating layers only
|
|
220
|
+
- Use the 25px pill only on the integrated search-scope tab — it is a signature, not a default
|
|
221
|
+
|
|
222
|
+
### Don't
|
|
223
|
+
- Don't introduce a second loud color — red is the only urgency signal
|
|
224
|
+
- Don't use pure black (`#000000`) for headings; use near-black `#111111`
|
|
225
|
+
- Don't add airy whitespace at the expense of product density — density is the value
|
|
226
|
+
- Don't pill-round buttons or cards; rounding stays at 4–8px outside the search tab
|
|
227
|
+
- Don't shadow resting tiles — flat-at-rest keeps the dense grid calm
|
|
228
|
+
- Don't shrink the price below its 24px/700 dominance on a card
|
|
229
|
+
- Don't mix `#ff0038` and `#f43142` arbitrarily — action-red vs markdown-red are distinct roles
|
|
230
|
+
- Don't substitute a Latin-first font stack; Hangul legibility leads
|
|
231
|
+
|
|
232
|
+
## 8. Responsive Behavior
|
|
233
|
+
|
|
234
|
+
### Breakpoints
|
|
235
|
+
| Name | Width | Key Changes |
|
|
236
|
+
|------|-------|-------------|
|
|
237
|
+
| Mobile | <768px | Single/2-column tile grid, collapsed GNB, sticky search |
|
|
238
|
+
| Tablet | 768–1024px | 3–4 column grid, condensed category rail |
|
|
239
|
+
| Desktop | 1024–1280px | Full 4–6 column product grids, full GNB |
|
|
240
|
+
| Large | >1280px | Centered ~1240px content with side margins |
|
|
241
|
+
|
|
242
|
+
### Touch Targets
|
|
243
|
+
- Primary CTA at 12px/24px padding gives a comfortable tap surface
|
|
244
|
+
- Nav rows and category items spaced for thumb reach on mobile
|
|
245
|
+
- Circular icon buttons (scroll-to-top, category toggle) sized for tap
|
|
246
|
+
|
|
247
|
+
### Collapsing Strategy
|
|
248
|
+
- Product grid: 6-column → 4 → 3 → 2 → 1 across breakpoints
|
|
249
|
+
- GNB: full horizontal nav → hamburger/category drawer on mobile
|
|
250
|
+
- Integrated search: persistent and often sticky at the top on mobile
|
|
251
|
+
- Price block maintains its 24px/700 dominance; surrounding metadata wraps or truncates first
|
|
252
|
+
|
|
253
|
+
### Image Behavior
|
|
254
|
+
- Square product thumbnails with 8px radius, consistent across breakpoints
|
|
255
|
+
- Promo banners scale full-width and swap to mobile-cropped variants
|
|
256
|
+
- Lazy-loaded tiles below the fold to keep the dense grid performant
|
|
257
|
+
|
|
258
|
+
## 9. Agent Prompt Guide
|
|
259
|
+
|
|
260
|
+
### Quick Color Reference
|
|
261
|
+
- Primary CTA / urgency: 11st Red (`#ff0038`)
|
|
262
|
+
- Discount / markdown accent: Discount Red (`#f43142`)
|
|
263
|
+
- Background: Pure White (`#ffffff`)
|
|
264
|
+
- Heading / price: Near-Black (`#111111`)
|
|
265
|
+
- Body text / nav: Body Gray (`#666666`)
|
|
266
|
+
- Strikethrough / muted: Muted Gray (`#a9a9a9`)
|
|
267
|
+
- Border / hairline: (`#eeeeee`)
|
|
268
|
+
- CTA text: White (`#ffffff`)
|
|
269
|
+
|
|
270
|
+
### Example Component Prompts
|
|
271
|
+
- "Create a product card: white background, 1px solid #eeeeee border, 8px radius, no shadow at rest. Discount-rate badge in #f43142 (11px / 700) top-left. Title at 14px Noto Sans KR #666666. Discount price at 24px weight 700 #111111. Strikethrough original price at 13px #a9a9a9 beside it. On hover, ambient shadow rgba(0,0,0,0.06) 0px 2px 8px."
|
|
272
|
+
- "Build a primary CTA: #ff0038 background, white text, 16px Noto Sans KR weight 700, 12px 24px padding, 4px radius. Label '구매하기'."
|
|
273
|
+
- "Design an integrated search bar: white field, 16px #666666 text, hairline #eeeeee border, 4px radius, with a 25px-radius scope tab whose active label is #ff0038."
|
|
274
|
+
- "Create a dense GNB row: white background, #666666 14px links, hover to #111111, #eeeeee divider hairlines, 0px radius square rows."
|
|
275
|
+
|
|
276
|
+
### Iteration Guide
|
|
277
|
+
1. Keep `#ff0038` as the only loud color — if a second bright hue appears, remove it.
|
|
278
|
+
2. The price is the headline: 24px / 700 / `#111111`, never smaller than its surroundings.
|
|
279
|
+
3. Body stays at 14px `Noto Sans KR`; weight is binary (400 / 700).
|
|
280
|
+
4. Structure with `#eeeeee` hairlines, not whitespace — density is intentional.
|
|
281
|
+
5. Chrome is flat at rest; shadow only on hover (`rgba(0,0,0,0.06) 0px 2px 8px`) or floating layers.
|
|
282
|
+
6. Rounding stays at 4–8px; the 25px pill is exclusive to the search-scope tab.
|
|
283
|
+
7. Distinguish `#ff0038` (action) from `#f43142` (markdown) by role, never interchangeably.
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## 10. Voice & Tone
|
|
288
|
+
|
|
289
|
+
11st's voice is brisk, transactional, and benefit-forward — the register of a Korean open-market that competes on price, speed, and selection. Microcopy is short and action-oriented: 구매하기 (buy), 장바구니 (cart), 할인 (discount), 무료배송 (free shipping). Numbers do the persuading — discount rates, final prices, point rewards — so prose stays out of the way. There is warmth in promotional energy (events, coupons, time-limited deals) but the underlying tone is efficient and shopper-respecting: tell me the price, the saving, and the action, in that order.
|
|
290
|
+
|
|
291
|
+
| Context | Tone |
|
|
292
|
+
|---|---|
|
|
293
|
+
| Product titles | Dense, keyword-rich, scannable. Brand + spec + benefit packed for search legibility. |
|
|
294
|
+
| CTAs | Direct imperatives. "구매하기", "장바구니 담기", "바로구매". |
|
|
295
|
+
| Promotions | Energetic, urgency-flavored. Discount rate and deadline lead. |
|
|
296
|
+
| Price/discount | Numbers first. The saving is the message. |
|
|
297
|
+
| Notices / policy | Plain, procedural Korean. Clear about shipping, returns, points. |
|
|
298
|
+
| Search / empty | Helpful, suggestion-forward. Offers related keywords and categories. |
|
|
299
|
+
|
|
300
|
+
**Forbidden register.** Avoid airy lifestyle-brand prose that buries the price. Avoid burying the discount under adjectives. Avoid a second loud color competing with red for attention. The shopper came to compare and buy — respect that with numbers and clear actions, not mood copy.
|
|
301
|
+
|
|
302
|
+
## 11. Brand Narrative
|
|
303
|
+
|
|
304
|
+
11st (11번가) launched in **2008** as the open-market e-commerce platform of **SK** in South Korea, entering a market already contested by Gmarket and Auction. It grew into one of Korea's largest online marketplaces, and in **2018** the e-commerce business was spun off into a standalone company, **Eleven Street Co., Ltd.**, under the SK Square / SK Telecom orbit. The name "11번가" — literally "11th Street" — frames the service as a bustling commercial street where countless sellers and shoppers meet, an open marketplace rather than a single first-party retailer.
|
|
305
|
+
|
|
306
|
+
The platform's defining strategic move in recent years has been its **Amazon Global Store** partnership, surfacing Amazon's catalog to Korean shoppers directly inside 11st — which is why the integrated search even offers an "아마존" scope alongside 통합검색. This positions 11st not just as a domestic open-market but as a gateway to cross-border selection, competing on breadth of catalog and price.
|
|
307
|
+
|
|
308
|
+
The design system follows directly from this identity. An open-market with millions of SKUs and thousands of sellers cannot afford editorial whitespace; it must surface as many comparable options as possible, as fast as possible. Hence the dense grid, the price-as-headline hierarchy, and the single disciplined use of red to mark action and savings. 11st's brand promise is selection and value, and the UI is engineered to deliver both at a glance.
|
|
309
|
+
|
|
310
|
+
## 12. Principles
|
|
311
|
+
|
|
312
|
+
1. **Density serves the shopper.** More products visible means fewer steps to a decision. Tight grids and hairline structure are a service, not a compromise.
|
|
313
|
+
2. **The price is the headline.** In open-market commerce, the most important information is the number. It gets the largest, boldest type on the card.
|
|
314
|
+
3. **Red means act or save.** A single saturated red (`#ff0038`) marks the action; a markdown red (`#f43142`) marks the saving. Red is never decorative.
|
|
315
|
+
4. **Hangul-first legibility.** `Noto Sans KR` at small sizes keeps a dense Korean grid crisp and scannable.
|
|
316
|
+
5. **Flat at rest, lift on intent.** No resting shadows; elevation is an interaction affordance, keeping the busy grid calm.
|
|
317
|
+
6. **Structure with lines, not gaps.** `#eeeeee` hairlines organize density where whitespace would waste it.
|
|
318
|
+
7. **One signature softness.** The 25px search-scope pill is the single intentional curve in an otherwise square, functional chrome.
|
|
319
|
+
8. **Selection is the brand.** From domestic sellers to the Amazon Global Store, breadth of catalog is the promise the layout must honor.
|
|
320
|
+
|
|
321
|
+
## 13. Personas
|
|
322
|
+
|
|
323
|
+
*Personas below are fictional archetypes informed by publicly observable 11st user segments (deal-seeking shoppers, mobile-first buyers, cross-border bargain hunters, third-party sellers), not individual people.*
|
|
324
|
+
|
|
325
|
+
**Ji-woo Park, 29, Seoul.** Mobile-first deal hunter. Opens 11st during her commute to compare prices on a specific gadget across sellers. Scans discount-rate badges and final prices in seconds; the strikethrough original tells her how good the deal is. Abandons any page where she has to hunt for the price. Trusts the platform because the savings are always legible at a glance.
|
|
326
|
+
|
|
327
|
+
**Min-jun Kim, 42, Daegu.** Household-supplies bulk buyer. Uses 11st on desktop, filling a cart with everyday goods and chasing free-shipping thresholds and point rewards. Values the dense grid — he can see dozens of options without scrolling endlessly. Reads product titles like a spec sheet and ignores anything that looks like marketing fluff.
|
|
328
|
+
|
|
329
|
+
**Seo-yeon Lee, 35, Bundang.** Cross-border bargain shopper. Came to 11st specifically for the Amazon Global Store — she toggles the search scope to 아마존 to compare imported goods against domestic listings. Cares about total landed price and delivery estimate. The integrated search that spans both catalogs is exactly why she stays on 11st instead of using two sites.
|
|
330
|
+
|
|
331
|
+
**Tae-hyun Jung, 38, Incheon.** Third-party seller running a small electronics shop on the open-market. Thinks about how his listings render in the grid — thumbnail, title keywords, discount badge, price. Knows that a clear discount-rate flag and a competitive final price are what win the click in a sea of comparable tiles.
|
|
332
|
+
|
|
333
|
+
## 14. States
|
|
334
|
+
|
|
335
|
+
| State | Treatment |
|
|
336
|
+
|---|---|
|
|
337
|
+
| **Empty (search, no results)** | White canvas. Plain line in `#666666` at 14px: "검색 결과가 없습니다." Suggested related keywords and popular categories offered below. No illustration-heavy dead end — redirect to selection. |
|
|
338
|
+
| **Empty (cart)** | `#666666` single line: "장바구니에 담긴 상품이 없습니다." One `#ff0038` CTA back to shopping. |
|
|
339
|
+
| **Loading (grid first paint)** | Skeleton tiles at final dimensions in `#eeeeee`. Thumbnail, title bar, and price bar placeholders sized to the real tile. Lazy-load below the fold. |
|
|
340
|
+
| **Loading (price/stock refresh)** | Inline spinner or subtle shimmer on the affected tile; previous price stays visible until updated. |
|
|
341
|
+
| **Error (action failed)** | Inline message near the action in plain Korean: what failed + what to do. No generic "오류가 발생했습니다" without a next step. |
|
|
342
|
+
| **Error (sold out)** | Tile marks 품절; CTA switches to disabled/muted; suggests similar in-stock items. |
|
|
343
|
+
| **Success (added to cart)** | Brief confirmation layer or toast: "장바구니에 담았습니다." Quick links to view cart or keep shopping. No emoji. |
|
|
344
|
+
| **Success (order placed)** | Dedicated confirmation page with order number, total, and shipping estimate in `#111111`; next-step links prominent. |
|
|
345
|
+
| **Disabled** | Muted to `#a9a9a9` on text with reduced surface contrast; red CTAs fade rather than recolor, preserving brand read. |
|
|
346
|
+
| **Discount / deal active** | Discount-rate flag in `#f43142`, final price at 24px/700 `#111111`, strikethrough original at 13px `#a9a9a9`. The savings is the state. |
|
|
347
|
+
|
|
348
|
+
## 15. Motion & Easing
|
|
349
|
+
|
|
350
|
+
**Durations**:
|
|
351
|
+
|
|
352
|
+
| Token | Value | Use |
|
|
353
|
+
|---|---|---|
|
|
354
|
+
| `motion-instant` | 0ms | Selection ticks, focus marks |
|
|
355
|
+
| `motion-fast` | 150ms | Hover lift, button press, tab switch |
|
|
356
|
+
| `motion-standard` | 250ms | Dropdowns, cart layer, drawer open |
|
|
357
|
+
| `motion-slow` | 400ms | Banner/carousel transitions, drawer slide |
|
|
358
|
+
|
|
359
|
+
**Easings**:
|
|
360
|
+
|
|
361
|
+
| Token | Curve | Use |
|
|
362
|
+
|---|---|---|
|
|
363
|
+
| `ease-standard` | `cubic-bezier(0.25, 0.1, 0.25, 1)` | Most hover and panel transitions |
|
|
364
|
+
| `ease-enter` | `cubic-bezier(0.2, 0.6, 0.25, 1)` | Drawers, dropdowns arriving |
|
|
365
|
+
| `ease-exit` | `cubic-bezier(0.4, 0.0, 1, 1)` | Dismissals |
|
|
366
|
+
|
|
367
|
+
**Restraint.** Motion is functional, not playful. A dense commerce grid stays calm — hover lifts are quick (150ms) and small, panels slide with standard easing, and there is no bounce or spring that would make a busy page feel chaotic.
|
|
368
|
+
|
|
369
|
+
**Signature motions.**
|
|
370
|
+
|
|
371
|
+
1. **Card hover lift.** Product tiles raise with a fast (150ms) ambient shadow (`rgba(0,0,0,0.06) 0px 2px 8px`) — a small, quick affordance confirming the tile is interactive without disturbing the grid's calm.
|
|
372
|
+
2. **Search-scope tab switch.** Switching between 통합검색 and 아마존 transitions the active label to `#ff0038` within the 25px pill seat using `ease-standard` — instant feedback on scope change.
|
|
373
|
+
3. **Category drawer / cart layer.** Slide-in panels use `motion-standard` with `ease-enter`; dismissals use `ease-exit`. The grid behind stays still.
|
|
374
|
+
4. **Reduce motion.** Under `prefers-reduced-motion: reduce`, hover lifts and panel slides collapse to instant. The dense grid remains fully functional; nothing essential depends on animation.
|
|
375
|
+
|
|
376
|
+
<!--
|
|
377
|
+
OmD v0.1 Sources — 11st
|
|
378
|
+
|
|
379
|
+
Token-level claims (sections 1–9, structured block) are sourced from live DOM
|
|
380
|
+
inspection of https://www.11st.co.kr via Playwright getComputedStyle (2026-06-09):
|
|
381
|
+
- Brand red #ff0038 (active/focused search tab LI), discount red #f43142 (em accent)
|
|
382
|
+
- Heading/price #111111, body #666666, muted/strikethrough #a9a9a9, canvas #ffffff,
|
|
383
|
+
hairline #eeeeee
|
|
384
|
+
- Noto Sans KR / Apple SD Gothic Neo / Malgun Gothic stack
|
|
385
|
+
- 14px body workhorse, 24px/700 discount price, 16px search input, 25px search-tab radius
|
|
386
|
+
See web/references/11st/.verification.md for raw samples.
|
|
387
|
+
|
|
388
|
+
Brand narrative (§11): 11st (11번가) launched 2008 as SK's open-market; the
|
|
389
|
+
e-commerce business was spun off as Eleven Street Co., Ltd. in 2018; the Amazon
|
|
390
|
+
Global Store partnership surfaces Amazon's catalog inside 11st (reflected in the
|
|
391
|
+
아마존 search scope). Widely documented public facts.
|
|
392
|
+
|
|
393
|
+
Personas (§13) are fictional archetypes informed by publicly observable 11st
|
|
394
|
+
user segments (deal-seekers, mobile buyers, cross-border shoppers, sellers).
|
|
395
|
+
Names are illustrative; they do not refer to real people.
|
|
396
|
+
|
|
397
|
+
Interpretive claims (e.g., "density serves the shopper", "the price is the
|
|
398
|
+
headline") are editorial readings connecting 11st's open-market identity to its
|
|
399
|
+
observed design system, not direct 11st statements.
|
|
400
|
+
-->
|