oh-my-design-cli 1.3.9 โ 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 +7 -7
- package/README.md +50 -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
package/dist/bin/oh-my-design.js
CHANGED
|
@@ -24,9 +24,9 @@ function readPackageVersion() {
|
|
|
24
24
|
}
|
|
25
25
|
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
|
-
program.command("install-skills").description("Install omd skill files + canonical agents into agent directories (.claude/, .codex/, .opencode/)").option("--dir <path>", "Project root (defaults to cwd)").option("--agent <name...>", "Restrict to specific
|
|
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)).action(
|
|
28
28
|
async (opts) => {
|
|
29
|
-
const { runInstallSkills } = await import("../install-skills-
|
|
29
|
+
const { runInstallSkills } = await import("../install-skills-IETT2TBJ.js");
|
|
30
30
|
const validAgents = ["claude-code", "codex", "opencode"];
|
|
31
31
|
const agents = opts.agent ? opts.agent.filter(
|
|
32
32
|
(a) => validAgents.includes(a)
|
|
@@ -34,7 +34,10 @@ program.command("install-skills").description("Install omd skill files + canonic
|
|
|
34
34
|
const code = await runInstallSkills({
|
|
35
35
|
dir: opts.dir,
|
|
36
36
|
agents,
|
|
37
|
-
force: opts.force
|
|
37
|
+
force: opts.force,
|
|
38
|
+
all: opts.all,
|
|
39
|
+
skillsFilter: opts.skills,
|
|
40
|
+
agentsFilter: opts.agentsOnly
|
|
38
41
|
});
|
|
39
42
|
if (code !== 0) process.exit(code);
|
|
40
43
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../bin/oh-my-design.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { readFileSync, existsSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nfunction readPackageVersion(): string {\n let cur = dirname(fileURLToPath(import.meta.url));\n for (let i = 0; i < 8; i++) {\n const pkg = join(cur, 'package.json');\n if (existsSync(pkg)) {\n try {\n return JSON.parse(readFileSync(pkg, 'utf8')).version ?? '0.0.0';\n } catch {\n return '0.0.0';\n }\n }\n const parent = dirname(cur);\n if (parent === cur) break;\n cur = parent;\n }\n return '0.0.0';\n}\n\nconst program = new Command();\n\nprogram\n .name('oh-my-design')\n .description('Bootstrap oh-my-design skills + agents into your project. After install, talk to your agent in natural language โ no other CLI commands.')\n .version(readPackageVersion())\n .showSuggestionAfterError(true)\n .showHelpAfterError(true);\n\nprogram\n .command('install-skills')\n .description('Install omd skill files + canonical agents into agent directories (.claude/, .codex/, .opencode/)')\n .option('--dir <path>', 'Project root (defaults to cwd)')\n .option('--agent <name...>', 'Restrict to specific
|
|
1
|
+
{"version":3,"sources":["../../bin/oh-my-design.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { readFileSync, existsSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nfunction readPackageVersion(): string {\n let cur = dirname(fileURLToPath(import.meta.url));\n for (let i = 0; i < 8; i++) {\n const pkg = join(cur, 'package.json');\n if (existsSync(pkg)) {\n try {\n return JSON.parse(readFileSync(pkg, 'utf8')).version ?? '0.0.0';\n } catch {\n return '0.0.0';\n }\n }\n const parent = dirname(cur);\n if (parent === cur) break;\n cur = parent;\n }\n return '0.0.0';\n}\n\nconst program = new Command();\n\nprogram\n .name('oh-my-design')\n .description('Bootstrap oh-my-design skills + agents into your project. After install, talk to your agent in natural language โ no other CLI commands.')\n .version(readPackageVersion())\n .showSuggestionAfterError(true)\n .showHelpAfterError(true);\n\nprogram\n .command('install-skills')\n .description('Install omd skill files + canonical agents into agent directories (.claude/, .codex/, .opencode/). Interactive multiselect TUI by default โ picks which skills + sub-agents to install.')\n .option('--dir <path>', 'Project root (defaults to cwd)')\n .option('--agent <name...>', 'Restrict to specific channels (claude-code | codex | opencode)')\n .option('--force', 'Overwrite existing files even without the omd marker')\n .option('--all', 'Skip the interactive TUI and install every shipped skill + agent (use in CI)')\n .option('--skills <names>', 'Comma-separated skill names to install (overrides TUI)', (v) => v.split(',').map((s) => s.trim()).filter(Boolean))\n .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))\n .action(\n async (opts: {\n dir?: string;\n agent?: string[];\n force?: boolean;\n all?: boolean;\n skills?: string[];\n agentsOnly?: string[];\n }) => {\n const { runInstallSkills } = await import('../src/cli/install-skills.js');\n const validAgents = ['claude-code', 'codex', 'opencode'] as const;\n type Agent = (typeof validAgents)[number];\n const agents = opts.agent\n ? (opts.agent.filter((a): a is Agent =>\n (validAgents as readonly string[]).includes(a)\n ) as Agent[])\n : undefined;\n const code = await runInstallSkills({\n dir: opts.dir,\n agents,\n force: opts.force,\n all: opts.all,\n skillsFilter: opts.skills,\n agentsFilter: opts.agentsOnly,\n });\n if (code !== 0) process.exit(code);\n }\n );\n\nprogram.parse();\n"],"mappings":";;;AAAA,SAAS,eAAe;AACxB,SAAS,cAAc,kBAAkB;AACzC,SAAS,SAAS,YAAY;AAC9B,SAAS,qBAAqB;AAE9B,SAAS,qBAA6B;AACpC,MAAI,MAAM,QAAQ,cAAc,YAAY,GAAG,CAAC;AAChD,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,UAAM,MAAM,KAAK,KAAK,cAAc;AACpC,QAAI,WAAW,GAAG,GAAG;AACnB,UAAI;AACF,eAAO,KAAK,MAAM,aAAa,KAAK,MAAM,CAAC,EAAE,WAAW;AAAA,MAC1D,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AACA,UAAM,SAAS,QAAQ,GAAG;AAC1B,QAAI,WAAW,IAAK;AACpB,UAAM;AAAA,EACR;AACA,SAAO;AACT;AAEA,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,cAAc,EACnB,YAAY,+IAA0I,EACtJ,QAAQ,mBAAmB,CAAC,EAC5B,yBAAyB,IAAI,EAC7B,mBAAmB,IAAI;AAE1B,QACG,QAAQ,gBAAgB,EACxB,YAAY,8LAAyL,EACrM,OAAO,gBAAgB,gCAAgC,EACvD,OAAO,qBAAqB,gEAAgE,EAC5F,OAAO,WAAW,sDAAsD,EACxE,OAAO,SAAS,8EAA8E,EAC9F,OAAO,oBAAoB,0DAA0D,CAAC,MAAM,EAAE,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO,CAAC,EAC7I,OAAO,yBAAyB,8HAA8H,CAAC,MAAM,EAAE,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO,CAAC,EACtN;AAAA,EACC,OAAO,SAOD;AACJ,UAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,+BAA8B;AACxE,UAAM,cAAc,CAAC,eAAe,SAAS,UAAU;AAEvD,UAAM,SAAS,KAAK,QACf,KAAK,MAAM;AAAA,MAAO,CAAC,MACjB,YAAkC,SAAS,CAAC;AAAA,IAC/C,IACA;AACJ,UAAM,OAAO,MAAM,iBAAiB;AAAA,MAClC,KAAK,KAAK;AAAA,MACV;AAAA,MACA,OAAO,KAAK;AAAA,MACZ,KAAK,KAAK;AAAA,MACV,cAAc,KAAK;AAAA,MACnB,cAAc,KAAK;AAAA,IACrB,CAAC;AACD,QAAI,SAAS,EAAG,SAAQ,KAAK,IAAI;AAAA,EACnC;AACF;AAEF,QAAQ,MAAM;","names":[]}
|
|
@@ -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.
|