oh-my-design-cli 1.8.6 → 1.9.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.
Files changed (275) hide show
  1. package/.claude/hooks/lib/preferences-parser.cjs +1 -0
  2. package/.claude/hooks/lib/preferences-writer.cjs +1 -0
  3. package/.claude/hooks/post-edit-watch.cjs +1 -0
  4. package/.claude/hooks/session-end-foldin.cjs +1 -0
  5. package/.claude/hooks/session-state-loader.cjs +1 -0
  6. package/.claude/hooks/skill-activation.cjs +1 -0
  7. package/.claude/settings.json +10 -1
  8. package/.github/assets/logo-bg.png +0 -0
  9. package/AGENTS.md +20 -1
  10. package/CHANGELOG.md +482 -0
  11. package/MIGRATION.md +103 -0
  12. package/README.ja.md +56 -19
  13. package/README.ko.md +47 -30
  14. package/README.md +48 -30
  15. package/README.zh-TW.md +59 -22
  16. package/agents/AGENT.md +8 -7
  17. package/agents/omd-codex-image.md +1 -1
  18. package/agents/omd-critic.md +6 -7
  19. package/agents/omd-designer-review.md +1 -1
  20. package/agents/omd-final-qa.md +1 -1
  21. package/agents/omd-humanizer.md +38 -0
  22. package/agents/omd-kr-writer.md +3 -4
  23. package/agents/omd-locale-adapter.md +12 -16
  24. package/agents/omd-master.md +24 -19
  25. package/agents/omd-microcopy.md +1 -1
  26. package/agents/omd-orchestrator.md +7 -8
  27. package/agents/omd-slop-auditor.md +31 -0
  28. package/agents/omd-ux-researcher.md +1 -1
  29. package/data/reference-fingerprints.json +2761 -85
  30. package/dist/bin/oh-my-design.js +16 -8
  31. package/dist/bin/oh-my-design.js.map +1 -1
  32. package/dist/chunk-UJIHCEYF.js +99 -0
  33. package/dist/chunk-UJIHCEYF.js.map +1 -0
  34. package/dist/doctor-LDWSFM3I.js +763 -0
  35. package/dist/doctor-LDWSFM3I.js.map +1 -0
  36. package/dist/install-skills-ZZFNJ2II.js +1120 -0
  37. package/dist/install-skills-ZZFNJ2II.js.map +1 -0
  38. package/docs/CLI_QUICKSTART.md +157 -0
  39. package/package.json +25 -4
  40. package/scripts/postinstall.cjs +5 -4
  41. package/skills/claude-design/SKILL.md +38 -14
  42. package/skills/claude-design/references/claude-design-flow.md +11 -11
  43. package/skills/claude-design/references/codebase-analysis.md +5 -4
  44. package/skills/omd-final-qa/SKILL.md +1 -1
  45. package/skills/omd-harness/SKILL.md +51 -50
  46. package/skills/omd-humanize/SKILL.md +116 -0
  47. package/skills/omd-humanize/agents/openai.yaml +4 -0
  48. package/skills/omd-humanize/references/locale-playbooks.md +102 -0
  49. package/skills/omd-humanize/references/sources.md +11 -0
  50. package/skills/omd-init/SKILL.md +18 -12
  51. package/skills/omd-locale-adapter/SKILL.md +58 -91
  52. package/skills/omd-orchestrator/SKILL.md +11 -5
  53. package/skills/omd-reference-capture/SKILL.md +80 -26
  54. package/skills/omd-reference-capture/scripts/PLAYWRIGHT-LICENSE.txt +202 -0
  55. package/skills/omd-reference-capture/scripts/PLAYWRIGHT-NOTICE.txt +5 -0
  56. package/skills/omd-reference-capture/scripts/THIRD_PARTY_NOTICES.md +3 -0
  57. package/skills/omd-reference-capture/scripts/capture-reference-evidence.mjs +159906 -0
  58. package/skills/omd-slop-audit/SKILL.md +110 -0
  59. package/skills/omd-slop-audit/agents/openai.yaml +4 -0
  60. package/skills/omd-slop-audit/references/pattern-catalog.md +96 -0
  61. package/skills/omd-slop-audit/references/sources.md +24 -0
  62. package/web/references/104/DESIGN.md +354 -0
  63. package/web/references/11st/DESIGN.md +197 -316
  64. package/web/references/29cm/DESIGN.md +297 -393
  65. package/web/references/42dot/DESIGN.md +390 -0
  66. package/web/references/8percent/DESIGN.md +438 -0
  67. package/web/references/ably/DESIGN.md +267 -572
  68. package/web/references/acer/DESIGN.md +329 -0
  69. package/web/references/airbnb/DESIGN.md +219 -557
  70. package/web/references/airbridge/DESIGN.md +451 -0
  71. package/web/references/alipay/DESIGN.md +179 -420
  72. package/web/references/apple/DESIGN.md +224 -498
  73. package/web/references/asleep/DESIGN.md +402 -0
  74. package/web/references/au/DESIGN.md +334 -0
  75. package/web/references/autopedia/DESIGN.md +382 -0
  76. package/web/references/aws-cloudscape/DESIGN.md +241 -0
  77. package/web/references/baemin/DESIGN.md +332 -491
  78. package/web/references/banksalad/DESIGN.md +269 -180
  79. package/web/references/barogo/DESIGN.md +416 -0
  80. package/web/references/beusable/DESIGN.md +464 -0
  81. package/web/references/bigin/DESIGN.md +454 -0
  82. package/web/references/bilibili/DESIGN.md +206 -374
  83. package/web/references/bmw/DESIGN.md +162 -276
  84. package/web/references/brandi/DESIGN.md +184 -325
  85. package/web/references/buzzvil/DESIGN.md +457 -0
  86. package/web/references/cafe24/DESIGN.md +472 -0
  87. package/web/references/catchtable/DESIGN.md +219 -264
  88. package/web/references/cgv/DESIGN.md +317 -0
  89. package/web/references/channeltalk/DESIGN.md +210 -336
  90. package/web/references/china-airlines/DESIGN.md +291 -0
  91. package/web/references/cjonstyle/DESIGN.md +453 -0
  92. package/web/references/class101/DESIGN.md +168 -387
  93. package/web/references/classum/DESIGN.md +228 -203
  94. package/web/references/claude/DESIGN.md +242 -479
  95. package/web/references/clay/DESIGN.md +179 -377
  96. package/web/references/clickhouse/DESIGN.md +254 -343
  97. package/web/references/codeit/DESIGN.md +470 -0
  98. package/web/references/cohere/DESIGN.md +214 -324
  99. package/web/references/coinone/DESIGN.md +199 -179
  100. package/web/references/corca/DESIGN.md +414 -0
  101. package/web/references/coupang/DESIGN.md +205 -475
  102. package/web/references/cursor/DESIGN.md +240 -381
  103. package/web/references/dabang/DESIGN.md +248 -213
  104. package/web/references/dable/DESIGN.md +431 -0
  105. package/web/references/datarize/DESIGN.md +338 -0
  106. package/web/references/dealicious/DESIGN.md +402 -0
  107. package/web/references/drdiary/DESIGN.md +404 -0
  108. package/web/references/elastic/DESIGN.md +238 -0
  109. package/web/references/elevenlabs/DESIGN.md +183 -321
  110. package/web/references/elice/DESIGN.md +445 -0
  111. package/web/references/eslite/DESIGN.md +257 -0
  112. package/web/references/evaair/DESIGN.md +338 -0
  113. package/web/references/expo/DESIGN.md +238 -331
  114. package/web/references/familymart-tw/DESIGN.md +254 -0
  115. package/web/references/ferrari/DESIGN.md +160 -377
  116. package/web/references/figma/DESIGN.md +197 -451
  117. package/web/references/fitpet/DESIGN.md +445 -0
  118. package/web/references/flex/DESIGN.md +170 -253
  119. package/web/references/framer/DESIGN.md +197 -340
  120. package/web/references/freee/DESIGN.md +211 -533
  121. package/web/references/friendliai/DESIGN.md +431 -0
  122. package/web/references/frip/DESIGN.md +440 -0
  123. package/web/references/furiosaai/DESIGN.md +450 -0
  124. package/web/references/gangnamunni/DESIGN.md +154 -591
  125. package/web/references/gaudiolab/DESIGN.md +414 -0
  126. package/web/references/google/DESIGN.md +290 -398
  127. package/web/references/goorm/DESIGN.md +470 -0
  128. package/web/references/greencar/DESIGN.md +420 -0
  129. package/web/references/hackle/DESIGN.md +472 -0
  130. package/web/references/hashicorp/DESIGN.md +273 -318
  131. package/web/references/heydealer/DESIGN.md +429 -0
  132. package/web/references/humanscape/DESIGN.md +433 -0
  133. package/web/references/hwahae/DESIGN.md +453 -0
  134. package/web/references/hyundai/DESIGN.md +158 -368
  135. package/web/references/hyundaicard/DESIGN.md +171 -121
  136. package/web/references/ibm/DESIGN.md +227 -378
  137. package/web/references/idus/DESIGN.md +450 -0
  138. package/web/references/igaworks/DESIGN.md +437 -0
  139. package/web/references/inflearn/DESIGN.md +221 -366
  140. package/web/references/intuit/DESIGN.md +244 -0
  141. package/web/references/jandi/DESIGN.md +191 -291
  142. package/web/references/jobplanet/DESIGN.md +415 -0
  143. package/web/references/kakao/DESIGN.md +287 -475
  144. package/web/references/kakaobank/DESIGN.md +220 -521
  145. package/web/references/kakaogames/DESIGN.md +220 -0
  146. package/web/references/kakaopay/DESIGN.md +207 -526
  147. package/web/references/karrot/DESIGN.md +227 -251
  148. package/web/references/kb-kookmin/DESIGN.md +308 -0
  149. package/web/references/kbank/DESIGN.md +220 -175
  150. package/web/references/kcd/DESIGN.md +432 -0
  151. package/web/references/kmong/DESIGN.md +281 -356
  152. package/web/references/krds/DESIGN.md +245 -19
  153. package/web/references/kream/DESIGN.md +224 -359
  154. package/web/references/kurly/DESIGN.md +211 -533
  155. package/web/references/kyobobook/DESIGN.md +445 -0
  156. package/web/references/lablup/DESIGN.md +474 -0
  157. package/web/references/lamborghini/DESIGN.md +198 -360
  158. package/web/references/lemonbase/DESIGN.md +452 -0
  159. package/web/references/lguplus/DESIGN.md +259 -0
  160. package/web/references/likelion/DESIGN.md +283 -0
  161. package/web/references/line/DESIGN.md +396 -355
  162. package/web/references/linear.app/DESIGN.md +209 -562
  163. package/web/references/lotteon/DESIGN.md +386 -0
  164. package/web/references/lovable/DESIGN.md +255 -349
  165. package/web/references/makinarocks/DESIGN.md +242 -0
  166. package/web/references/maum-ai/DESIGN.md +423 -0
  167. package/web/references/medibloc/DESIGN.md +425 -0
  168. package/web/references/megabox/DESIGN.md +232 -0
  169. package/web/references/mercari/DESIGN.md +217 -434
  170. package/web/references/microsoft/DESIGN.md +234 -388
  171. package/web/references/mildang/DESIGN.md +411 -0
  172. package/web/references/millie/DESIGN.md +183 -504
  173. package/web/references/minimax/DESIGN.md +264 -322
  174. package/web/references/miricanvas/DESIGN.md +182 -355
  175. package/web/references/miro/DESIGN.md +234 -232
  176. package/web/references/mistral.ai/DESIGN.md +231 -314
  177. package/web/references/mixi/DESIGN.md +234 -0
  178. package/web/references/moin/DESIGN.md +445 -0
  179. package/web/references/mongodb/DESIGN.md +219 -316
  180. package/web/references/moreh/DESIGN.md +437 -0
  181. package/web/references/musinsa/DESIGN.md +184 -510
  182. package/web/references/mynavi/DESIGN.md +242 -0
  183. package/web/references/myrealtrip/DESIGN.md +169 -406
  184. package/web/references/naver/DESIGN.md +328 -499
  185. package/web/references/naverwebtoon/DESIGN.md +172 -395
  186. package/web/references/neosapience/DESIGN.md +441 -0
  187. package/web/references/nhn/DESIGN.md +169 -346
  188. package/web/references/nhncloud/DESIGN.md +185 -123
  189. package/web/references/nota/DESIGN.md +451 -0
  190. package/web/references/notion/DESIGN.md +271 -475
  191. package/web/references/nrise/DESIGN.md +417 -0
  192. package/web/references/ohouse/DESIGN.md +210 -549
  193. package/web/references/oliveyoung/DESIGN.md +217 -326
  194. package/web/references/ollama/DESIGN.md +241 -308
  195. package/web/references/onestore/DESIGN.md +248 -0
  196. package/web/references/palantir/DESIGN.md +256 -0
  197. package/web/references/panasonic/DESIGN.md +276 -0
  198. package/web/references/patternfly/DESIGN.md +259 -0
  199. package/web/references/payhere/DESIGN.md +438 -0
  200. package/web/references/pega/DESIGN.md +262 -0
  201. package/web/references/peoplefund/DESIGN.md +431 -0
  202. package/web/references/petfriends/DESIGN.md +428 -0
  203. package/web/references/pinkfong/DESIGN.md +199 -367
  204. package/web/references/pinterest/DESIGN.md +258 -308
  205. package/web/references/portone/DESIGN.md +446 -0
  206. package/web/references/poya/DESIGN.md +286 -0
  207. package/web/references/pozalabs/DESIGN.md +438 -0
  208. package/web/references/protopie/DESIGN.md +419 -0
  209. package/web/references/qraft/DESIGN.md +448 -0
  210. package/web/references/queenit/DESIGN.md +432 -0
  211. package/web/references/quotabook/DESIGN.md +449 -0
  212. package/web/references/rebellions/DESIGN.md +449 -0
  213. package/web/references/recruit/DESIGN.md +247 -0
  214. package/web/references/remember/DESIGN.md +202 -396
  215. package/web/references/resend/DESIGN.md +274 -348
  216. package/web/references/returnzero/DESIGN.md +460 -0
  217. package/web/references/sakura-internet/DESIGN.md +269 -0
  218. package/web/references/samsung/DESIGN.md +157 -359
  219. package/web/references/sandoll/DESIGN.md +445 -0
  220. package/web/references/sanity/DESIGN.md +241 -375
  221. package/web/references/saramin/DESIGN.md +465 -0
  222. package/web/references/scatterlab/DESIGN.md +412 -0
  223. package/web/references/selectstar/DESIGN.md +439 -0
  224. package/web/references/servicenow/DESIGN.md +289 -0
  225. package/web/references/shiftee/DESIGN.md +468 -0
  226. package/web/references/shinhanbank/DESIGN.md +161 -341
  227. package/web/references/shinhancard/DESIGN.md +208 -319
  228. package/web/references/sionic/DESIGN.md +454 -0
  229. package/web/references/sktelecom/DESIGN.md +310 -0
  230. package/web/references/socar/DESIGN.md +125 -32
  231. package/web/references/softbank/DESIGN.md +231 -0
  232. package/web/references/solapi/DESIGN.md +483 -0
  233. package/web/references/sony/DESIGN.md +234 -0
  234. package/web/references/soop/DESIGN.md +261 -0
  235. package/web/references/spacex/DESIGN.md +184 -323
  236. package/web/references/spotify/DESIGN.md +253 -394
  237. package/web/references/squeezebits/DESIGN.md +441 -0
  238. package/web/references/stayfolio/DESIGN.md +422 -0
  239. package/web/references/stripe/DESIGN.md +212 -491
  240. package/web/references/supabase/DESIGN.md +244 -316
  241. package/web/references/supertone/DESIGN.md +413 -0
  242. package/web/references/tabling/DESIGN.md +415 -0
  243. package/web/references/taishinbank/DESIGN.md +243 -0
  244. package/web/references/teamlab/DESIGN.md +240 -0
  245. package/web/references/teamsparta/DESIGN.md +472 -0
  246. package/web/references/tellingme/DESIGN.md +392 -0
  247. package/web/references/tesla/DESIGN.md +244 -427
  248. package/web/references/thsr/DESIGN.md +284 -0
  249. package/web/references/thumbtack/DESIGN.md +302 -0
  250. package/web/references/together.ai/DESIGN.md +257 -304
  251. package/web/references/toss/DESIGN.md +251 -593
  252. package/web/references/toss-securities/DESIGN.md +255 -178
  253. package/web/references/tossbank/DESIGN.md +195 -491
  254. package/web/references/toyota/DESIGN.md +238 -0
  255. package/web/references/tving/DESIGN.md +197 -245
  256. package/web/references/typed/DESIGN.md +413 -0
  257. package/web/references/uber/DESIGN.md +256 -333
  258. package/web/references/udn/DESIGN.md +250 -0
  259. package/web/references/upbit/DESIGN.md +145 -256
  260. package/web/references/upstage/DESIGN.md +211 -204
  261. package/web/references/uswds/DESIGN.md +287 -0
  262. package/web/references/vercel/DESIGN.md +236 -440
  263. package/web/references/vuno/DESIGN.md +413 -0
  264. package/web/references/wanted/DESIGN.md +215 -487
  265. package/web/references/weverse/DESIGN.md +437 -0
  266. package/web/references/wise/DESIGN.md +223 -231
  267. package/web/references/wisetracker/DESIGN.md +462 -0
  268. package/web/references/wooribank/DESIGN.md +245 -0
  269. package/web/references/yanolja/DESIGN.md +250 -419
  270. package/web/references/yeogiotte/DESIGN.md +235 -418
  271. package/web/references/zapier/DESIGN.md +256 -377
  272. package/web/references/zendesk/DESIGN.md +288 -0
  273. package/web/references/zigzag/DESIGN.md +200 -621
  274. package/dist/install-skills-7UUDOLG2.js +0 -734
  275. package/dist/install-skills-7UUDOLG2.js.map +0 -1
@@ -0,0 +1,1120 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ CLAUDE_HOOK_PATHS,
4
+ isKnownLegacyHook,
5
+ isSelfConsistentManagedHook,
6
+ renderManagedHook,
7
+ unsafeManagedPath
8
+ } from "./chunk-UJIHCEYF.js";
9
+
10
+ // src/cli/install-skills.ts
11
+ import * as p from "@clack/prompts";
12
+ import pc from "picocolors";
13
+ import {
14
+ readFileSync,
15
+ readdirSync,
16
+ writeFileSync,
17
+ existsSync as existsSync2,
18
+ mkdirSync,
19
+ cpSync,
20
+ rmSync,
21
+ rmdirSync,
22
+ statSync,
23
+ renameSync
24
+ } from "fs";
25
+ import { join as join2, dirname, relative } from "path";
26
+ import { homedir } from "os";
27
+ import { createHash } from "crypto";
28
+ import { fileURLToPath } from "url";
29
+
30
+ // src/core/agent-detect.ts
31
+ import { existsSync } from "fs";
32
+ import { join } from "path";
33
+ function detectInstalledAgents(projectRoot) {
34
+ return {
35
+ claudeCode: existsSync(join(projectRoot, ".claude")) || existsSync(join(projectRoot, "CLAUDE.md")),
36
+ codex: existsSync(join(projectRoot, ".codex")) || existsSync(join(projectRoot, "AGENTS.md")) || existsSync(join(projectRoot, "AGENTS.override.md")),
37
+ opencode: existsSync(join(projectRoot, ".opencode")) || existsSync(join(projectRoot, "opencode.json")) || existsSync(join(projectRoot, "opencode.jsonc")),
38
+ cursor: existsSync(join(projectRoot, ".cursor")) || existsSync(join(projectRoot, ".cursorrules"))
39
+ };
40
+ }
41
+
42
+ // src/cli/install-skills.ts
43
+ var VALID_SKILL_TARGETS = ["claude-code", "codex", "opencode", "cursor"];
44
+ var DEVELOPMENT_ONLY_SKILLS = /* @__PURE__ */ new Set(["omd-lab-02-design-harness"]);
45
+ function targetsAvailableForScope(targets, scope) {
46
+ return scope === "global" ? targets.filter((target) => target !== "cursor") : targets;
47
+ }
48
+ function findPackageRoot() {
49
+ let cur = dirname(fileURLToPath(import.meta.url));
50
+ for (let i = 0; i < 8; i++) {
51
+ if (existsSync2(join2(cur, "skills"))) return cur;
52
+ const parent = dirname(cur);
53
+ if (parent === cur) break;
54
+ cur = parent;
55
+ }
56
+ return null;
57
+ }
58
+ function listShippedSkills(packageRoot) {
59
+ const skillsDir = join2(packageRoot, "skills");
60
+ if (!existsSync2(skillsDir)) return [];
61
+ return readdirSync(skillsDir).filter(
62
+ (name) => !DEVELOPMENT_ONLY_SKILLS.has(name) && existsSync2(join2(skillsDir, name, "SKILL.md"))
63
+ ).sort();
64
+ }
65
+ function listCanonicalAgents(packageRoot) {
66
+ const dir = join2(packageRoot, "agents");
67
+ if (!existsSync2(dir)) return [];
68
+ return readdirSync(dir).filter((name) => name.startsWith("omd-") && name.endsWith(".md")).sort();
69
+ }
70
+ function parseCanonicalAgent(packageRoot, filename) {
71
+ const src = readFileSync(join2(packageRoot, "agents", filename), "utf8");
72
+ const match = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/.exec(src);
73
+ if (!match) {
74
+ throw new Error(`agents/${filename}: missing YAML frontmatter`);
75
+ }
76
+ const fm = match[1];
77
+ const body = match[2];
78
+ const grab = (key) => {
79
+ const re = new RegExp(`^${key}:\\s*(.+)$`, "m");
80
+ const m = re.exec(fm);
81
+ if (!m) return "";
82
+ const value = m[1].trim();
83
+ if (value.startsWith('"') && value.endsWith('"')) {
84
+ try {
85
+ const decoded = JSON.parse(value);
86
+ if (typeof decoded === "string") return decoded;
87
+ } catch {
88
+ }
89
+ }
90
+ if (value.startsWith("'") && value.endsWith("'")) {
91
+ return value.slice(1, -1).replace(/''/g, "'");
92
+ }
93
+ return value;
94
+ };
95
+ return {
96
+ name: grab("name") || filename.replace(/\.md$/, ""),
97
+ description: grab("description"),
98
+ tools: grab("tools").split(",").map((t) => t.trim()).filter(Boolean),
99
+ model: grab("model") || "sonnet",
100
+ body
101
+ };
102
+ }
103
+ function renderClaudeAgent(a) {
104
+ const fm = [
105
+ "---",
106
+ `name: ${JSON.stringify(a.name)}`,
107
+ `description: ${JSON.stringify(a.description)}`,
108
+ `tools: ${JSON.stringify(a.tools)}`,
109
+ `model: ${JSON.stringify(a.model)}`,
110
+ `omd_managed: true`,
111
+ "---",
112
+ ""
113
+ ].join("\n");
114
+ return fm + a.body;
115
+ }
116
+ function nativeAgentBody(body, channel, scope) {
117
+ const skillRoot = channel === "codex" ? scope === "global" ? "~/.agents/skills" : ".agents/skills" : scope === "global" ? "~/.config/opencode/skills" : ".opencode/skills";
118
+ const nativeBody = body.replace(/(?:~\/)?\.claude\/skills\//g, `${skillRoot}/`).replace(/You are spawned as a Claude Code subagent/g, "You run as a host-managed subagent").replace(/Claude Code subagent/g, "host-managed subagent").replace(/the Agent tool/g, "the host's native sub-agent mechanism").replace(/Agent tool/g, "host-native sub-agent mechanism").replace(/AskUserQuestion-compatible/g, "host question-interface compatible").replace(
119
+ /launcher calls AskUserQuestion\(questions_file\)/g,
120
+ "launcher presents questions from `questions_file` to the user"
121
+ ).replace(/launcher calls AskUserQuestion/g, "launcher presents the questions to the user").replace(/no AskUserQuestion/g, "without direct user-question tools");
122
+ return channel === "opencode" ? nativeBody.replace(/\bomd:([a-z0-9-]+)/g, "omd-$1") : nativeBody;
123
+ }
124
+ function nativeSkillRoot(channel, scope) {
125
+ if (channel === "codex") {
126
+ return scope === "global" ? "~/.agents/skills" : ".agents/skills";
127
+ }
128
+ return scope === "global" ? "~/.config/opencode/skills" : ".opencode/skills";
129
+ }
130
+ function renderOpenCodeAgent(a, scope, dataRoot) {
131
+ const skillRoot = nativeSkillRoot("opencode", scope);
132
+ return [
133
+ "---",
134
+ `description: ${JSON.stringify(a.description)}`,
135
+ "mode: subagent",
136
+ "---",
137
+ "",
138
+ "## Installed role runtime contract",
139
+ "",
140
+ `- Resolve referenced OmD skills from \`${skillRoot}/<skill>/SKILL.md\`.`,
141
+ `- Resolve the installed offline catalog from \`${dataRoot}\`.`,
142
+ "- Use OpenCode's native sub-agent mechanism for specialist handoffs.",
143
+ "",
144
+ nativeAgentBody(a.body, "opencode", scope).trimEnd(),
145
+ "",
146
+ "<!-- omd:installed-agent \u2014 generated from the canonical OmD role. -->",
147
+ ""
148
+ ].join("\n");
149
+ }
150
+ function codexSafeAgentBody(body) {
151
+ return body.replace(/`omd init prepare`/g, "`omd:init` skill flow").replace(/^omd remember .*$/gm, "Use the `omd:remember` skill directly with the same finding and context.").replace(/`omd remember [^`]+`/g, "the `omd:remember` skill with the same correction details").replace(/\bomd remember\b/g, "omd:remember skill");
152
+ }
153
+ function renderCodexAgent(a, scope, dataRoot) {
154
+ const name = a.name.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
155
+ const desc = a.description.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
156
+ const skillRoot = nativeSkillRoot("codex", scope);
157
+ const instructions = [
158
+ "## Codex runtime contract",
159
+ "",
160
+ "- Execute the OmD behavior directly from these instructions and the installed skills.",
161
+ `- Resolve referenced OmD skills from \`${skillRoot}/<skill>/SKILL.md\`.`,
162
+ `- Resolve the installed offline catalog from \`${dataRoot}\`.`,
163
+ "- Use Codex's native sub-agent mechanism for specialist handoffs.",
164
+ "- The OmD CLI exposes installation and diagnostics only. Do not invent or invoke removed helper subcommands.",
165
+ "",
166
+ codexSafeAgentBody(nativeAgentBody(a.body, "codex", scope)).trimEnd()
167
+ ].join("\n");
168
+ if (instructions.includes("'''")) {
169
+ throw new Error(`agents/${a.name}.md: cannot encode triple single quote in Codex TOML`);
170
+ }
171
+ return [
172
+ `name = "${name}"`,
173
+ `description = "${desc}"`,
174
+ "",
175
+ `developer_instructions = '''`,
176
+ instructions,
177
+ `'''`,
178
+ ""
179
+ ].join("\n");
180
+ }
181
+ function planForTarget(projectRoot, target, scope) {
182
+ switch (target) {
183
+ case "claude-code":
184
+ return {
185
+ target,
186
+ destDir: join2(projectRoot, ".claude", "skills"),
187
+ layout: "folder"
188
+ };
189
+ case "codex":
190
+ return {
191
+ target,
192
+ destDir: join2(projectRoot, ".agents", "skills"),
193
+ layout: "folder"
194
+ };
195
+ case "opencode":
196
+ return {
197
+ target,
198
+ destDir: scope === "global" ? join2(projectRoot, ".config", "opencode", "skills") : join2(projectRoot, ".opencode", "skills"),
199
+ layout: "folder"
200
+ };
201
+ }
202
+ }
203
+ var CHANNEL_DATA_DIRS = {
204
+ "claude-code": ".claude",
205
+ codex: ".codex",
206
+ opencode: ".opencode",
207
+ cursor: null
208
+ };
209
+ function dataDirFor(target, targets) {
210
+ if (target === "cursor") {
211
+ return targets.includes("claude-code") ? null : ".claude";
212
+ }
213
+ return CHANNEL_DATA_DIRS[target];
214
+ }
215
+ function dataDirForScope(target, targets, scope) {
216
+ if (scope === "global" && target === "opencode") return ".config/opencode";
217
+ return dataDirFor(target, targets);
218
+ }
219
+ var MANAGED_HEADER = "<!-- omd:installed-skill \u2014 managed by `omd install-skills`. Do not edit; rerun the command to refresh. -->";
220
+ var MANAGED_MARKER_SUBSTR = "omd:installed-skill";
221
+ function withManagedMarker(src) {
222
+ const fm = /^(---\r?\n[\s\S]*?\r?\n---\r?\n)([\s\S]*)$/.exec(src);
223
+ if (!fm) {
224
+ return MANAGED_HEADER + "\n\n" + src;
225
+ }
226
+ return fm[1] + MANAGED_HEADER + "\n\n" + fm[2];
227
+ }
228
+ function isManagedSkillFile(content) {
229
+ if (!content) return false;
230
+ const head = content.split("\n", 30).join("\n");
231
+ return head.includes(MANAGED_MARKER_SUBSTR);
232
+ }
233
+ function withGlobalDataHint(src, globalDataRoot) {
234
+ if (!globalDataRoot) return src;
235
+ const hint = [
236
+ `> **Installed global data root (highest priority):** \`${globalDataRoot}\`.`,
237
+ "> Resolve catalog JSON, references, and helper scripts there before project-relative fallbacks.",
238
+ ""
239
+ ].join("\n");
240
+ const fm = /^(---\r?\n[\s\S]*?\r?\n---\r?\n)([\s\S]*)$/.exec(src);
241
+ return fm ? fm[1] + hint + fm[2] : hint + src;
242
+ }
243
+ function renderSkillForChannel(src, folderName, target) {
244
+ const installedName = target === "opencode" ? folderName : folderName.replace(/^omd-/, "omd:");
245
+ const rendered = src.replace(
246
+ /^name:\s*[^\r\n]+$/m,
247
+ `name: ${installedName}`
248
+ );
249
+ return target === "opencode" ? rendered.replace(/\bomd:([a-z0-9][a-z0-9-]*)\b/g, "omd-$1") : rendered;
250
+ }
251
+ function removeManagedLegacyCodexSkills(root) {
252
+ const legacyRoot = join2(root, ".codex", "skills");
253
+ if (!existsSync2(legacyRoot)) return [];
254
+ const results = [];
255
+ for (const entry of readdirSync(legacyRoot, { withFileTypes: true })) {
256
+ if (!entry.isDirectory()) continue;
257
+ const skillPath = join2(legacyRoot, entry.name, "SKILL.md");
258
+ if (!existsSync2(skillPath)) continue;
259
+ const content = readFileSync(skillPath, "utf8");
260
+ if (!isManagedSkillFile(content)) continue;
261
+ rmSync(skillPath, { force: true });
262
+ const dir = dirname(skillPath);
263
+ if (readdirSync(dir).length === 0) rmdirSync(dir);
264
+ results.push({
265
+ target: "codex",
266
+ skill: `legacy-skill:${entry.name}`,
267
+ destPath: skillPath,
268
+ status: "removed"
269
+ });
270
+ }
271
+ return results;
272
+ }
273
+ var IGNORED_SKILL_ENTRIES = /* @__PURE__ */ new Set([".runtime", "__pycache__", ".DS_Store"]);
274
+ function isIgnoredSkillTreeEntry(name) {
275
+ return IGNORED_SKILL_ENTRIES.has(name) || name.endsWith(".pyc");
276
+ }
277
+ function shippedSkillTreeMatches(sourceDir, destinationDir) {
278
+ if (!existsSync2(destinationDir)) return false;
279
+ for (const entry of readdirSync(sourceDir, { withFileTypes: true })) {
280
+ if (entry.name === "SKILL.md" || isIgnoredSkillTreeEntry(entry.name)) continue;
281
+ const source = join2(sourceDir, entry.name);
282
+ const destination = join2(destinationDir, entry.name);
283
+ if (!existsSync2(destination)) return false;
284
+ try {
285
+ const destinationStat = statSync(destination);
286
+ if (entry.isDirectory()) {
287
+ if (!destinationStat.isDirectory() || !shippedSkillTreeMatches(source, destination)) {
288
+ return false;
289
+ }
290
+ } else if (!destinationStat.isFile() || !readFileSync(source).equals(readFileSync(destination))) {
291
+ return false;
292
+ }
293
+ } catch {
294
+ return false;
295
+ }
296
+ }
297
+ return true;
298
+ }
299
+ function parseSkillChannels(skillMd) {
300
+ const fm = /^---\n([\s\S]*?)\n---/.exec(skillMd);
301
+ if (!fm) return null;
302
+ const m = /^x-omd-channels:\s*(.+)$/m.exec(fm[1]);
303
+ if (!m) return null;
304
+ const valid = ["claude-code", "codex", "opencode"];
305
+ const list = m[1].split(/[,\s]+/).map((s) => s.trim()).filter((s) => valid.includes(s));
306
+ return list.length > 0 ? list : null;
307
+ }
308
+ function skillSupportedChannels(packageRoot, skill) {
309
+ return parseSkillChannels(readFileSync(join2(packageRoot, "skills", skill, "SKILL.md"), "utf8")) ?? ["claude-code", "codex", "opencode"];
310
+ }
311
+ function installOne(packageRoot, installRoot, plan, skill, force, globalDataRoot) {
312
+ const skillDir = join2(packageRoot, "skills", skill);
313
+ const src = readFileSync(join2(skillDir, "SKILL.md"), "utf8");
314
+ const channelSrc = withGlobalDataHint(
315
+ renderSkillForChannel(src, skill, plan.target),
316
+ globalDataRoot
317
+ );
318
+ const managed = withManagedMarker(channelSrc);
319
+ const channels = parseSkillChannels(src);
320
+ if (channels && !channels.includes(plan.target)) {
321
+ return {
322
+ target: plan.target,
323
+ skill,
324
+ destPath: join2(plan.destDir, skill + ".md"),
325
+ status: "skipped-incompat"
326
+ };
327
+ }
328
+ const extras = readdirSync(skillDir).filter(
329
+ (n) => n !== "SKILL.md" && !IGNORED_SKILL_ENTRIES.has(n)
330
+ );
331
+ const isMultiFile = extras.length > 0;
332
+ if (plan.layout !== "folder" && isMultiFile) {
333
+ return {
334
+ target: plan.target,
335
+ skill,
336
+ destPath: join2(plan.destDir, skill + ".md"),
337
+ status: "skipped-incompat"
338
+ };
339
+ }
340
+ const destPath = plan.layout === "folder" ? join2(plan.destDir, skill, "SKILL.md") : join2(plan.destDir, skill + ".md");
341
+ if (unsafeManagedPath(installRoot, destPath)) {
342
+ return { target: plan.target, skill, destPath, status: "skipped-drift", reason: "unsafe-path" };
343
+ }
344
+ const exists = existsSync2(destPath);
345
+ const existing = exists ? readFileSync(destPath, "utf8") : "";
346
+ if (exists && existing === managed) {
347
+ const extrasCurrent = !isMultiFile || shippedSkillTreeMatches(
348
+ skillDir,
349
+ join2(plan.destDir, skill)
350
+ );
351
+ if (extrasCurrent) {
352
+ return { target: plan.target, skill, destPath, status: "unchanged" };
353
+ }
354
+ }
355
+ if (exists && !isManagedSkillFile(existing) && !force) {
356
+ return { target: plan.target, skill, destPath, status: "skipped-drift" };
357
+ }
358
+ mkdirSync(dirname(destPath), { recursive: true });
359
+ writeFileSync(destPath, managed, "utf8");
360
+ if (plan.layout === "folder" && isMultiFile) {
361
+ const destSkillDir = join2(plan.destDir, skill);
362
+ for (const entry of extras) {
363
+ cpSync(join2(skillDir, entry), join2(destSkillDir, entry), {
364
+ recursive: true,
365
+ filter: (s) => !/(\/__pycache__|\/\.runtime|\.pyc$|\.DS_Store$)/.test(s)
366
+ });
367
+ }
368
+ }
369
+ return {
370
+ target: plan.target,
371
+ skill,
372
+ destPath,
373
+ status: exists ? "updated" : "created"
374
+ };
375
+ }
376
+ function installHookFile(packageRoot, projectRoot, filename, force) {
377
+ const target = "claude-code";
378
+ const skillLabel = `hook:${filename}`;
379
+ const srcPath = join2(packageRoot, ".claude", "hooks", filename);
380
+ const destPath = join2(projectRoot, ".claude", "hooks", filename);
381
+ if (!existsSync2(srcPath)) {
382
+ return { target, skill: skillLabel, destPath, status: "skipped-drift" };
383
+ }
384
+ if (unsafeManagedPath(projectRoot, destPath)) {
385
+ return { target, skill: skillLabel, destPath, status: "skipped-drift", reason: "unsafe-path" };
386
+ }
387
+ const src = readFileSync(srcPath, "utf8");
388
+ const managed = renderManagedHook(src);
389
+ const exists = existsSync2(destPath);
390
+ const existing = exists ? readFileSync(destPath, "utf8") : "";
391
+ if (exists && existing === managed) {
392
+ return { target, skill: skillLabel, destPath, status: "unchanged" };
393
+ }
394
+ if (exists && !force && !isSelfConsistentManagedHook(existing) && !isKnownLegacyHook(filename, existing)) {
395
+ return { target, skill: skillLabel, destPath, status: "skipped-drift" };
396
+ }
397
+ mkdirSync(dirname(destPath), { recursive: true });
398
+ const tempPath = `${destPath}.omd-${process.pid}-${Date.now()}.tmp`;
399
+ try {
400
+ writeFileSync(tempPath, managed, { encoding: "utf8", flag: "wx" });
401
+ renameSync(tempPath, destPath);
402
+ } catch (error) {
403
+ rmSync(tempPath, { force: true });
404
+ throw error;
405
+ }
406
+ return { target, skill: skillLabel, destPath, status: exists ? "updated" : "created" };
407
+ }
408
+ function isJsonObject(value) {
409
+ return typeof value === "object" && value !== null && !Array.isArray(value);
410
+ }
411
+ function parseJsonObject(value) {
412
+ try {
413
+ const parsed = JSON.parse(value);
414
+ return isJsonObject(parsed) ? parsed : null;
415
+ } catch {
416
+ return null;
417
+ }
418
+ }
419
+ function isOmdHook(value) {
420
+ if (!isJsonObject(value) || typeof value.command !== "string") return false;
421
+ const command = value.command;
422
+ const ownedHookPaths = [
423
+ "${CLAUDE_PROJECT_DIR}/.claude/hooks/skill-activation.cjs",
424
+ "${CLAUDE_PROJECT_DIR}/.claude/hooks/session-state-loader.cjs",
425
+ "${CLAUDE_PROJECT_DIR}/.claude/hooks/post-edit-watch.cjs",
426
+ "${CLAUDE_PROJECT_DIR}/.claude/hooks/session-end-foldin.cjs",
427
+ // Pre-v1.8 installs briefly registered this repository-only helper.
428
+ "${CLAUDE_PROJECT_DIR}/scripts/context_restore.sh"
429
+ ];
430
+ return ownedHookPaths.some((path) => command.includes(path));
431
+ }
432
+ var REPOSITORY_ONLY_CONTEXT_RESTORE_COMMAND = "bash ${CLAUDE_PROJECT_DIR}/scripts/context_restore.sh";
433
+ function isRepositoryOnlySourceHook(value) {
434
+ return isJsonObject(value) && typeof value.command === "string" && value.command === REPOSITORY_ONLY_CONTEXT_RESTORE_COMMAND;
435
+ }
436
+ function stripHookEntries(groups, shouldStrip) {
437
+ const retained = [];
438
+ for (const group of groups) {
439
+ if (!isJsonObject(group) || !Array.isArray(group.hooks)) {
440
+ retained.push(group);
441
+ continue;
442
+ }
443
+ const hooks = group.hooks.filter((hook) => !shouldStrip(hook));
444
+ if (hooks.length > 0) retained.push({ ...group, hooks });
445
+ }
446
+ return retained;
447
+ }
448
+ function stripInstalledOmdHooks(groups) {
449
+ return stripHookEntries(groups, isOmdHook);
450
+ }
451
+ function stripRepositoryOnlySourceHooks(source) {
452
+ if (!isJsonObject(source.hooks)) return source;
453
+ const hooks = {};
454
+ for (const [event, groups] of Object.entries(source.hooks)) {
455
+ hooks[event] = Array.isArray(groups) ? stripHookEntries(groups, isRepositoryOnlySourceHook) : groups;
456
+ }
457
+ return { ...source, hooks };
458
+ }
459
+ function mergeClaudeSettings(existing, source) {
460
+ const installableSource = stripRepositoryOnlySourceHooks(source);
461
+ const existingHooks = isJsonObject(existing.hooks) ? existing.hooks : {};
462
+ const sourceHooks = isJsonObject(installableSource.hooks) ? installableSource.hooks : {};
463
+ const mergedHooks = { ...existingHooks };
464
+ for (const event of /* @__PURE__ */ new Set([...Object.keys(existingHooks), ...Object.keys(sourceHooks)])) {
465
+ const userGroups = Array.isArray(existingHooks[event]) ? existingHooks[event] : [];
466
+ const omdGroups = Array.isArray(sourceHooks[event]) ? sourceHooks[event] : [];
467
+ mergedHooks[event] = [...stripInstalledOmdHooks(userGroups), ...omdGroups];
468
+ }
469
+ return { ...installableSource, ...existing, hooks: mergedHooks };
470
+ }
471
+ function installSettingsJson(packageRoot, projectRoot, force) {
472
+ const target = "claude-code";
473
+ const skillLabel = "settings:.claude/settings.json";
474
+ const srcPath = join2(packageRoot, ".claude", "settings.json");
475
+ const destPath = join2(projectRoot, ".claude", "settings.json");
476
+ if (!existsSync2(srcPath)) {
477
+ return { target, skill: skillLabel, destPath, status: "skipped-drift" };
478
+ }
479
+ if (unsafeManagedPath(projectRoot, destPath)) {
480
+ return { target, skill: skillLabel, destPath, status: "skipped-drift", reason: "unsafe-path" };
481
+ }
482
+ const src = readFileSync(srcPath, "utf8");
483
+ const sourceSettings = parseJsonObject(src);
484
+ if (!sourceSettings) {
485
+ return { target, skill: skillLabel, destPath, status: "skipped-drift" };
486
+ }
487
+ const exists = existsSync2(destPath);
488
+ const existing = exists ? readFileSync(destPath, "utf8") : "";
489
+ const existingSettings = exists ? parseJsonObject(existing) : {};
490
+ if (exists && !existingSettings && !force) {
491
+ return { target, skill: skillLabel, destPath, status: "skipped-drift" };
492
+ }
493
+ const merged = existingSettings ? mergeClaudeSettings(existingSettings, sourceSettings) : stripRepositoryOnlySourceHooks(sourceSettings);
494
+ if (exists && existingSettings && JSON.stringify(existingSettings) === JSON.stringify(merged)) {
495
+ return { target, skill: skillLabel, destPath, status: "unchanged" };
496
+ }
497
+ mkdirSync(dirname(destPath), { recursive: true });
498
+ writeFileSync(destPath, JSON.stringify(merged, null, 2) + "\n", "utf8");
499
+ return { target, skill: skillLabel, destPath, status: exists ? "updated" : "created" };
500
+ }
501
+ function installDataFile(packageRoot, projectRoot, channelDir, dataFilename, _force, target) {
502
+ const skillLabel = `data:${dataFilename}`;
503
+ const srcPath = join2(packageRoot, "data", dataFilename);
504
+ const destPath = join2(projectRoot, channelDir, "data", dataFilename);
505
+ if (!existsSync2(srcPath)) {
506
+ return { target, skill: skillLabel, destPath, status: "skipped-drift" };
507
+ }
508
+ if (unsafeManagedPath(projectRoot, destPath)) {
509
+ return { target, skill: skillLabel, destPath, status: "skipped-drift", reason: "unsafe-path" };
510
+ }
511
+ const src = readFileSync(srcPath, "utf8");
512
+ const exists = existsSync2(destPath);
513
+ const existing = exists ? readFileSync(destPath, "utf8") : "";
514
+ if (exists && existing === src) {
515
+ return { target, skill: skillLabel, destPath, status: "unchanged" };
516
+ }
517
+ mkdirSync(dirname(destPath), { recursive: true });
518
+ writeFileSync(destPath, src, "utf8");
519
+ return {
520
+ target,
521
+ skill: skillLabel,
522
+ destPath,
523
+ status: exists ? "updated" : "created"
524
+ };
525
+ }
526
+ function installAgentFile(packageRoot, projectRoot, channel, filename, force, scope) {
527
+ const target = channel === "claude" ? "claude-code" : channel === "codex" ? "codex" : "opencode";
528
+ const skillLabel = `agent:${filename}`;
529
+ const parsed = parseCanonicalAgent(packageRoot, filename);
530
+ const nativeDataRoot = scope === "global" ? channel === "codex" ? join2(projectRoot, ".codex", "data") : join2(projectRoot, ".config", "opencode", "data") : channel === "codex" ? ".codex/data" : ".opencode/data";
531
+ const rendered = channel === "claude" ? renderClaudeAgent(parsed) : channel === "codex" ? renderCodexAgent(parsed, scope, nativeDataRoot) : renderOpenCodeAgent(parsed, scope, nativeDataRoot);
532
+ const destFilename = channel === "codex" ? filename.replace(/\.md$/, ".toml") : filename;
533
+ const channelRoot = channel === "claude" ? ".claude" : channel === "codex" ? ".codex" : scope === "global" ? join2(".config", "opencode") : ".opencode";
534
+ const destPath = join2(
535
+ projectRoot,
536
+ channelRoot,
537
+ "agents",
538
+ destFilename
539
+ );
540
+ const managed = channel === "claude" || channel === "opencode" ? rendered : "# omd:installed-agent \u2014 generated from agents/" + filename + " by `omd install-skills`. Do not edit; rerun the command to refresh.\n\n" + rendered;
541
+ if (unsafeManagedPath(projectRoot, destPath)) {
542
+ return { target, skill: skillLabel, destPath, status: "skipped-drift", reason: "unsafe-path" };
543
+ }
544
+ const exists = existsSync2(destPath);
545
+ const existing = exists ? readFileSync(destPath, "utf8") : "";
546
+ if (exists && existing === managed) {
547
+ return { target, skill: skillLabel, destPath, status: "unchanged" };
548
+ }
549
+ const isManaged = channel === "claude" ? /\nomd_managed:\s*true\b/.test(existing) : channel === "codex" ? existing.startsWith("# omd:installed-agent") : existing.includes("<!-- omd:installed-agent");
550
+ if (exists && !isManaged && !force) {
551
+ return { target, skill: skillLabel, destPath, status: "skipped-drift" };
552
+ }
553
+ mkdirSync(dirname(destPath), { recursive: true });
554
+ writeFileSync(destPath, managed, "utf8");
555
+ return {
556
+ target,
557
+ skill: skillLabel,
558
+ destPath,
559
+ status: exists ? "updated" : "created"
560
+ };
561
+ }
562
+ function installReferenceCatalog(packageRoot, installRoot, channelDir, force) {
563
+ const srcRoot = join2(packageRoot, "web", "references");
564
+ if (!existsSync2(srcRoot)) return 0;
565
+ const destRoot = join2(installRoot, channelDir, "data", "references");
566
+ if (unsafeManagedPath(installRoot, destRoot)) return 0;
567
+ const manifestPath = join2(destRoot, ".omd-managed.json");
568
+ let previouslyManaged = /* @__PURE__ */ new Set();
569
+ let previousHashes = /* @__PURE__ */ new Map();
570
+ if (existsSync2(manifestPath)) {
571
+ try {
572
+ const parsed = JSON.parse(readFileSync(manifestPath, "utf8"));
573
+ if (isJsonObject(parsed) && Array.isArray(parsed.managedIds) && parsed.managedIds.every((id) => typeof id === "string")) {
574
+ previouslyManaged = new Set(parsed.managedIds);
575
+ if (isJsonObject(parsed.managedDesignHashes)) {
576
+ previousHashes = new Map(
577
+ Object.entries(parsed.managedDesignHashes).filter((entry) => typeof entry[1] === "string")
578
+ );
579
+ }
580
+ }
581
+ } catch {
582
+ }
583
+ }
584
+ let available = 0;
585
+ const sourceIds = /* @__PURE__ */ new Set();
586
+ const managedDesignHashes = {};
587
+ for (const id of readdirSync(srcRoot)) {
588
+ const srcDesign = join2(srcRoot, id, "DESIGN.md");
589
+ if (!existsSync2(srcDesign)) continue;
590
+ sourceIds.add(id);
591
+ available++;
592
+ const destDesign = join2(destRoot, id, "DESIGN.md");
593
+ const src = readFileSync(srcDesign, "utf8");
594
+ const srcHash = createHash("sha256").update(src).digest("hex");
595
+ if (existsSync2(destDesign)) {
596
+ const existing = readFileSync(destDesign, "utf8");
597
+ if (existing === src) {
598
+ managedDesignHashes[id] = srcHash;
599
+ continue;
600
+ }
601
+ const previousHash = previousHashes.get(id);
602
+ const unchangedSinceLastInstall = previousHash !== void 0 && createHash("sha256").update(existing).digest("hex") === previousHash;
603
+ if (!force && !unchangedSinceLastInstall) {
604
+ continue;
605
+ }
606
+ }
607
+ mkdirSync(dirname(destDesign), { recursive: true });
608
+ writeFileSync(destDesign, src, "utf8");
609
+ managedDesignHashes[id] = srcHash;
610
+ }
611
+ if (existsSync2(destRoot)) {
612
+ for (const id of readdirSync(destRoot)) {
613
+ if (previouslyManaged.has(id) && !sourceIds.has(id)) {
614
+ const retiredRoot = join2(destRoot, id);
615
+ const retiredDesign = join2(retiredRoot, "DESIGN.md");
616
+ const previousHash = previousHashes.get(id);
617
+ const safeToRemove = existsSync2(retiredDesign) && (force || previousHash !== void 0 && createHash("sha256").update(readFileSync(retiredDesign, "utf8")).digest("hex") === previousHash);
618
+ if (safeToRemove) rmSync(retiredDesign, { force: true });
619
+ if (existsSync2(retiredRoot) && readdirSync(retiredRoot).length === 0) {
620
+ rmdirSync(retiredRoot);
621
+ }
622
+ }
623
+ }
624
+ }
625
+ mkdirSync(destRoot, { recursive: true });
626
+ writeFileSync(
627
+ manifestPath,
628
+ JSON.stringify({
629
+ schemaVersion: 2,
630
+ managedIds: Object.keys(managedDesignHashes).sort(),
631
+ managedDesignHashes
632
+ }, null, 2) + "\n",
633
+ "utf8"
634
+ );
635
+ return available;
636
+ }
637
+ var CURSOR_RULE_BODY = [
638
+ "The authoritative design spec lives at `@DESIGN.md` (repo root). Open and read before generating/modifying UI.",
639
+ "",
640
+ "Pending preference corrections: `@.omd/preferences.md`.",
641
+ "",
642
+ "Precedence: DESIGN.md > preferences.md > framework defaults.",
643
+ "",
644
+ "If DESIGN.md is missing and the user asks to establish a design system:",
645
+ "1. Inspect the existing product, routes, and constraints.",
646
+ "2. Read `.claude/data/reference-fingerprints.json` and only recommend ids present there.",
647
+ "3. Load the chosen `.claude/data/references/<id>/DESIGN.md`, explain the project-specific delta, and ask before writing root DESIGN.md.",
648
+ "4. Unknown reference fields stay absent; never substitute a system font, generic component, or guessed token as a brand fact.",
649
+ "",
650
+ "When applying DESIGN.md, preserve existing behavior and user copy unless asked, then verify the actual product route and accessibility before reporting completion.",
651
+ "Cursor receives this rule and the catalog, not OmD named skills or sub-agents; execute the contract directly from natural-language requests."
652
+ ].join("\n");
653
+ function renderCursorRule() {
654
+ const hash = createHash("sha256").update(CURSOR_RULE_BODY).digest("hex").slice(0, 12);
655
+ return [
656
+ "---",
657
+ "description: Authoritative brand & UI design system. Read DESIGN.md before UI work.",
658
+ "globs:",
659
+ ' - "**/*.tsx"',
660
+ ' - "**/*.jsx"',
661
+ ' - "**/*.vue"',
662
+ ' - "**/*.svelte"',
663
+ ' - "**/*.css"',
664
+ ' - "**/*.scss"',
665
+ ' - "**/tailwind.config.*"',
666
+ ' - "**/components/**"',
667
+ ' - "**/app/**/page.*"',
668
+ "alwaysApply: false",
669
+ "---",
670
+ "",
671
+ `<!-- omd:start v=1 hash=${hash} -->`,
672
+ CURSOR_RULE_BODY,
673
+ "<!-- omd:end -->",
674
+ ""
675
+ ].join("\n");
676
+ }
677
+ function installCursorRule(installRoot, force) {
678
+ const target = "cursor";
679
+ const skillLabel = "rule:omd-design.mdc";
680
+ const destPath = join2(installRoot, ".cursor", "rules", "omd-design.mdc");
681
+ const rendered = renderCursorRule();
682
+ if (unsafeManagedPath(installRoot, destPath)) {
683
+ return { target, skill: skillLabel, destPath, status: "skipped-drift", reason: "unsafe-path" };
684
+ }
685
+ const exists = existsSync2(destPath);
686
+ const existing = exists ? readFileSync(destPath, "utf8") : "";
687
+ if (exists && existing === rendered) {
688
+ return { target, skill: skillLabel, destPath, status: "unchanged" };
689
+ }
690
+ if (exists && !existing.includes("<!-- omd:start") && !force) {
691
+ return { target, skill: skillLabel, destPath, status: "skipped-drift" };
692
+ }
693
+ mkdirSync(dirname(destPath), { recursive: true });
694
+ writeFileSync(destPath, rendered, "utf8");
695
+ return { target, skill: skillLabel, destPath, status: exists ? "updated" : "created" };
696
+ }
697
+ var STATUS_LABEL = {
698
+ created: pc.green("created"),
699
+ updated: pc.cyan("updated"),
700
+ removed: pc.magenta("removed"),
701
+ unchanged: pc.dim("unchanged"),
702
+ "skipped-drift": pc.yellow("skipped"),
703
+ "skipped-incompat": pc.yellow("skipped (claude-code only)")
704
+ };
705
+ function autoDetectTargets(projectRoot) {
706
+ const presence = detectInstalledAgents(projectRoot);
707
+ const targets = [];
708
+ if (presence.claudeCode) targets.push("claude-code");
709
+ if (presence.codex) targets.push("codex");
710
+ if (presence.opencode) targets.push("opencode");
711
+ if (presence.cursor) targets.push("cursor");
712
+ if (targets.length === 0) {
713
+ return ["claude-code", "codex", "opencode"];
714
+ }
715
+ return targets;
716
+ }
717
+ async function runInstallSkills(opts = {}) {
718
+ const projectRoot = opts.dir ?? process.cwd();
719
+ const packageRoot = findPackageRoot();
720
+ if (!packageRoot) {
721
+ console.error(pc.red("omd install-skills: package data not found"));
722
+ return 1;
723
+ }
724
+ const allSkills = listShippedSkills(packageRoot);
725
+ if (allSkills.length === 0) {
726
+ console.error(pc.red("omd install-skills: no skills found in package"));
727
+ return 1;
728
+ }
729
+ const allAgents = listCanonicalAgents(packageRoot);
730
+ if (opts.agents) {
731
+ const invalidTargets = opts.agents.filter(
732
+ (target) => !VALID_SKILL_TARGETS.includes(target)
733
+ );
734
+ if (invalidTargets.length > 0) {
735
+ console.error(
736
+ pc.red(
737
+ `omd install-skills: invalid agent channel(s): ${invalidTargets.join(", ")}. Choose from ${VALID_SKILL_TARGETS.join(", ")}.`
738
+ )
739
+ );
740
+ return 1;
741
+ }
742
+ if (opts.agents.length === 0) {
743
+ console.error(pc.red("omd install-skills: no agent channel selected"));
744
+ return 1;
745
+ }
746
+ }
747
+ if (opts.skillsFilter) {
748
+ const unknownSkills = opts.skillsFilter.filter((skill) => !allSkills.includes(skill));
749
+ if (unknownSkills.length > 0) {
750
+ console.error(
751
+ pc.red(`omd install-skills: unknown skill(s): ${unknownSkills.join(", ")}`)
752
+ );
753
+ return 1;
754
+ }
755
+ if (opts.skillsFilter.length === 0) {
756
+ console.error(pc.red("omd install-skills: no skill selected"));
757
+ return 1;
758
+ }
759
+ }
760
+ if (opts.agentsFilter) {
761
+ const availableAgentIds = new Set(allAgents.map((name) => name.replace(/\.md$/, "")));
762
+ const unknownAgents = opts.agentsFilter.filter((agent) => !availableAgentIds.has(agent));
763
+ if (unknownAgents.length > 0) {
764
+ console.error(
765
+ pc.red(`omd install-skills: unknown sub-agent(s): ${unknownAgents.join(", ")}`)
766
+ );
767
+ return 1;
768
+ }
769
+ }
770
+ const force = opts.force ?? false;
771
+ const minimal = opts.skillsOnly === true;
772
+ let scope = opts.global ? "global" : "project";
773
+ p.intro(
774
+ pc.bold("omd install-skills") + pc.dim(` (${relative(process.cwd(), projectRoot) || "."})`)
775
+ );
776
+ const isTTY = Boolean(process.stdin.isTTY && process.stdout.isTTY);
777
+ const interactive = isTTY && !opts.all;
778
+ const detected = autoDetectTargets(projectRoot);
779
+ const presence = detectInstalledAgents(projectRoot);
780
+ const actuallyDetected = [
781
+ presence.claudeCode ? "claude-code" : null,
782
+ presence.codex ? "codex" : null,
783
+ presence.opencode ? "opencode" : null,
784
+ presence.cursor ? "cursor" : null
785
+ ].filter((x) => x !== null);
786
+ if (!opts.global && interactive) {
787
+ const scopeResult = await p.select({
788
+ message: "Install scope \xB7 \uC5B4\uB514\uC5D0 \uC124\uCE58\uD560\uAE4C\uC694?",
789
+ options: [
790
+ { value: "project", label: "Project", hint: `${relative(process.cwd(), projectRoot) || "."} \xB7 \uC774 \uD504\uB85C\uC81D\uD2B8\uB9CC` },
791
+ { value: "global", label: "Global", hint: "~/.claude \xB7 ~/.agents \xB7 ~/.config/opencode (hooks/settings \uC81C\uC678)" }
792
+ ],
793
+ initialValue: "project"
794
+ });
795
+ if (p.isCancel(scopeResult)) {
796
+ p.cancel("Install cancelled.");
797
+ return 130;
798
+ }
799
+ scope = scopeResult;
800
+ }
801
+ let skills;
802
+ if (opts.skillsFilter) {
803
+ skills = allSkills.filter((s) => opts.skillsFilter.includes(s));
804
+ } else if (interactive) {
805
+ const skillResult = await p.multiselect({
806
+ message: "Skills \xB7 space = \uD1A0\uAE00 \xB7 a = \uC804\uCCB4 \xB7 enter = \uD655\uC778 (default ALL)",
807
+ options: allSkills.map((s) => ({ value: s, label: s, hint: "omd skill" })),
808
+ initialValues: allSkills,
809
+ required: true
810
+ });
811
+ if (p.isCancel(skillResult)) {
812
+ p.cancel("Install cancelled.");
813
+ return 130;
814
+ }
815
+ skills = skillResult;
816
+ } else {
817
+ skills = allSkills;
818
+ }
819
+ let canonicalAgents;
820
+ if (minimal) {
821
+ canonicalAgents = [];
822
+ } else if (opts.agentsFilter) {
823
+ canonicalAgents = allAgents.filter((a) => opts.agentsFilter.includes(a.replace(/\.md$/, "")));
824
+ } else if (interactive && allAgents.length > 0) {
825
+ const agentResult = await p.multiselect({
826
+ message: "Sub-agents \xB7 space = \uD1A0\uAE00 \xB7 a = \uC804\uCCB4 \xB7 enter = \uD655\uC778 (default ALL)",
827
+ options: allAgents.map((a) => ({ value: a, label: a.replace(/\.md$/, ""), hint: "subagent" })),
828
+ initialValues: allAgents,
829
+ required: false
830
+ });
831
+ if (p.isCancel(agentResult)) {
832
+ p.cancel("Install cancelled.");
833
+ return 130;
834
+ }
835
+ canonicalAgents = agentResult;
836
+ } else {
837
+ canonicalAgents = allAgents;
838
+ }
839
+ const supportedTargets = (() => {
840
+ const set = new Set(skills.flatMap((s) => skillSupportedChannels(packageRoot, s)));
841
+ set.add("cursor");
842
+ return targetsAvailableForScope(
843
+ ["claude-code", "codex", "opencode", "cursor"].filter((t) => set.has(t)),
844
+ scope
845
+ );
846
+ })();
847
+ const channelLabel = {
848
+ "claude-code": "Claude Code",
849
+ codex: "Codex",
850
+ opencode: "OpenCode",
851
+ cursor: "Cursor"
852
+ };
853
+ const channelDir = {
854
+ "claude-code": ".claude",
855
+ codex: ".codex",
856
+ opencode: ".opencode",
857
+ cursor: ".cursor"
858
+ };
859
+ let targets;
860
+ if (opts.agents) {
861
+ targets = opts.agents;
862
+ } else if (interactive) {
863
+ const defaults = actuallyDetected.filter((t) => supportedTargets.includes(t));
864
+ const targetResult = await p.multiselect({
865
+ message: "Agent channels \xB7 \uC5B4\uB514\uC5D0 \uC124\uCE58\uD560\uAE4C\uC694? \xB7 space = \uD1A0\uAE00 \xB7 enter = \uD655\uC778",
866
+ options: supportedTargets.map((t) => ({
867
+ value: t,
868
+ label: channelLabel[t],
869
+ hint: actuallyDetected.includes(t) ? `${channelDir[t]}/ detected` : ""
870
+ })),
871
+ initialValues: defaults.length > 0 ? defaults : supportedTargets,
872
+ required: true
873
+ });
874
+ if (p.isCancel(targetResult)) {
875
+ p.cancel("Install cancelled.");
876
+ return 130;
877
+ }
878
+ targets = targetResult;
879
+ } else {
880
+ targets = opts.all ? ["claude-code", "codex", "opencode"] : minimal ? actuallyDetected.length > 0 ? actuallyDetected : ["claude-code"] : detected;
881
+ const narrowed = targets.filter((t) => supportedTargets.includes(t));
882
+ if (narrowed.length > 0) targets = narrowed;
883
+ }
884
+ if (targets.length === 0) {
885
+ console.error(pc.red("omd install-skills: no compatible agent channel selected"));
886
+ return 1;
887
+ }
888
+ if (scope === "global" && targets.includes("cursor")) {
889
+ console.error(
890
+ pc.red("omd install-skills: Cursor rules are project-scoped. Run the Cursor install from the project root without --global.")
891
+ );
892
+ return 1;
893
+ }
894
+ if (opts.repairHooks && (scope !== "project" || minimal || !targets.includes("claude-code"))) {
895
+ console.error(
896
+ pc.red("omd install-skills: --repair-hooks requires a project-scoped Claude Code target and cannot be combined with --skills-only.")
897
+ );
898
+ return 1;
899
+ }
900
+ const installRoot = scope === "global" ? homedir() : projectRoot;
901
+ const skillChannelTargets = targets.filter(
902
+ (t) => t !== "cursor"
903
+ );
904
+ const plans = skillChannelTargets.map((t) => planForTarget(installRoot, t, scope));
905
+ p.log.message(
906
+ pc.bold("Scope: ") + pc.cyan(scope) + pc.dim(scope === "global" ? " (channel user directories)" : ` (${relative(process.cwd(), projectRoot) || "."})`)
907
+ );
908
+ p.log.message(
909
+ pc.bold(`Skills (${skills.length}): `) + skills.map((s) => pc.cyan(s)).join(", ")
910
+ );
911
+ if (minimal) {
912
+ canonicalAgents = [];
913
+ p.log.message(pc.bold("Agents: ") + pc.dim("skipped (--skills-only)"));
914
+ } else if (canonicalAgents.length > 0) {
915
+ p.log.message(
916
+ pc.bold(`Agents (${canonicalAgents.length}): `) + canonicalAgents.map((a) => pc.cyan(a.replace(/\.md$/, ""))).join(", ")
917
+ );
918
+ }
919
+ p.log.message(
920
+ pc.bold("Targets: ") + targets.map((t) => pc.cyan(t)).join(", ")
921
+ );
922
+ const results = [];
923
+ let catalogCount = 0;
924
+ const catalogDestinations = /* @__PURE__ */ new Set();
925
+ for (const plan of plans) {
926
+ const dataDir = dataDirForScope(plan.target, targets, scope);
927
+ const globalDataRoot = scope === "global" && dataDir ? join2(installRoot, dataDir, "data") : null;
928
+ for (const skill of skills) {
929
+ results.push(installOne(packageRoot, installRoot, plan, skill, force, globalDataRoot));
930
+ }
931
+ }
932
+ if (targets.includes("codex")) {
933
+ results.push(...removeManagedLegacyCodexSkills(installRoot));
934
+ }
935
+ for (const target of targets) {
936
+ if (target === "claude-code") {
937
+ for (const filename of canonicalAgents) {
938
+ results.push(installAgentFile(packageRoot, installRoot, "claude", filename, force, scope));
939
+ }
940
+ } else if (target === "codex") {
941
+ for (const filename of canonicalAgents) {
942
+ results.push(installAgentFile(packageRoot, installRoot, "codex", filename, force, scope));
943
+ }
944
+ } else if (target === "opencode") {
945
+ for (const filename of canonicalAgents) {
946
+ results.push(installAgentFile(packageRoot, installRoot, "opencode", filename, force, scope));
947
+ }
948
+ }
949
+ }
950
+ if (!minimal) {
951
+ if (targets.includes("cursor")) {
952
+ results.push(installCursorRule(installRoot, force));
953
+ }
954
+ const dataFiles = [
955
+ "reference-fingerprints.json",
956
+ "reference-tags.md",
957
+ "vocabulary.json",
958
+ "synonyms.json",
959
+ "opt-out-corpus.json"
960
+ ];
961
+ for (const target of targets) {
962
+ const dataDir = dataDirForScope(target, targets, scope);
963
+ if (!dataDir) continue;
964
+ for (const dataFile of dataFiles) {
965
+ results.push(installDataFile(packageRoot, installRoot, dataDir, dataFile, force, target));
966
+ }
967
+ }
968
+ for (const target of targets) {
969
+ const dataDir = dataDirForScope(target, targets, scope);
970
+ if (dataDir) {
971
+ const catalogRoot = join2(installRoot, dataDir, "data", "references");
972
+ if (unsafeManagedPath(installRoot, catalogRoot)) {
973
+ results.push({
974
+ target,
975
+ skill: "data:references",
976
+ destPath: catalogRoot,
977
+ status: "skipped-drift",
978
+ reason: "unsafe-path"
979
+ });
980
+ continue;
981
+ }
982
+ const count = installReferenceCatalog(packageRoot, installRoot, dataDir, force);
983
+ catalogCount = Math.max(catalogCount, count);
984
+ if (count > 0) {
985
+ catalogDestinations.add(`${dataDir}/data/references/<id>/DESIGN.md`);
986
+ }
987
+ }
988
+ }
989
+ for (const target of targets) {
990
+ if (target === "cursor") continue;
991
+ const cd = dataDirForScope(target, targets, scope);
992
+ if (!cd) continue;
993
+ for (const helper of ["ctx-prime.cjs", "context.cjs"]) {
994
+ const srcHelper = join2(packageRoot, "scripts", helper);
995
+ if (!existsSync2(srcHelper)) continue;
996
+ const destHelper = join2(installRoot, cd, "data", "scripts", helper);
997
+ if (unsafeManagedPath(installRoot, destHelper)) {
998
+ results.push({
999
+ target,
1000
+ skill: `data-script:${helper}`,
1001
+ destPath: destHelper,
1002
+ status: "skipped-drift",
1003
+ reason: "unsafe-path"
1004
+ });
1005
+ continue;
1006
+ }
1007
+ const srcTxt = readFileSync(srcHelper, "utf8");
1008
+ if (existsSync2(destHelper) && readFileSync(destHelper, "utf8") === srcTxt) continue;
1009
+ mkdirSync(dirname(destHelper), { recursive: true });
1010
+ writeFileSync(destHelper, srcTxt, "utf8");
1011
+ }
1012
+ }
1013
+ if (scope === "project" && targets.includes("claude-code")) {
1014
+ for (const hookFile of CLAUDE_HOOK_PATHS) {
1015
+ results.push(
1016
+ installHookFile(
1017
+ packageRoot,
1018
+ installRoot,
1019
+ hookFile,
1020
+ force || opts.repairHooks === true
1021
+ )
1022
+ );
1023
+ }
1024
+ results.push(installSettingsJson(packageRoot, installRoot, force));
1025
+ }
1026
+ }
1027
+ p.log.message(pc.bold("\nResults:"));
1028
+ for (const r of results) {
1029
+ const rel = relative(installRoot, r.destPath);
1030
+ p.log.message(
1031
+ ` ${STATUS_LABEL[r.status]} ${pc.dim(r.target.padEnd(12))} ${rel}`
1032
+ );
1033
+ }
1034
+ const driftCount = results.filter((r) => r.status === "skipped-drift").length;
1035
+ const changedCount = results.filter(
1036
+ (r) => r.status === "created" || r.status === "updated" || r.status === "removed"
1037
+ ).length;
1038
+ const currentCount = results.filter((r) => r.status === "unchanged").length;
1039
+ if (driftCount > 0) {
1040
+ const unsafeCount = results.filter(
1041
+ (result) => result.status === "skipped-drift" && result.reason === "unsafe-path"
1042
+ ).length;
1043
+ const hookDrift = results.some(
1044
+ (result) => result.status === "skipped-drift" && result.skill.startsWith("hook:")
1045
+ );
1046
+ const repairHint = unsafeCount > 0 ? ` ${unsafeCount} unsafe symlinked managed path(s) were refused; replace them with project-local files and run doctor. --force will not bypass this safety check.` : hookDrift ? " Use --repair-hooks for Claude hooks only, or --force to overwrite all unmarked files." : " Rerun with --force to overwrite.";
1047
+ p.outro(
1048
+ pc.yellow(
1049
+ `${changedCount} changed, ${currentCount} already current, ${driftCount} skipped (existing files lack a valid omd ownership marker).${repairHint}`
1050
+ )
1051
+ );
1052
+ return 2;
1053
+ }
1054
+ if (minimal) {
1055
+ for (const r of results.filter((x) => x.status === "skipped-incompat")) {
1056
+ p.log.warn(
1057
+ `${pc.bold(r.skill)} ${pc.dim("skipped for ")}${pc.cyan(r.target)}${pc.dim(" \u2014 declares x-omd-channels (channel not supported).")}`
1058
+ );
1059
+ }
1060
+ const installed = results.filter(
1061
+ (r) => r.status === "created" || r.status === "updated" || r.status === "unchanged"
1062
+ );
1063
+ if (installed.length === 0) {
1064
+ p.outro(pc.yellow("Nothing installed \u2014 no compatible skill/channel match."));
1065
+ return 0;
1066
+ }
1067
+ p.outro(
1068
+ pc.green(
1069
+ `Done. Installed ${skills.map((s) => pc.bold(s)).join(", ")} ${scope === "global" ? `globally for ${targets.join(", ")}` : `for ${targets.join(", ")}`}.`
1070
+ ) + pc.dim(" \u2192 restart your agent, then use the skill (e.g. ") + pc.cyan("/claude-design") + pc.dim(").")
1071
+ );
1072
+ return 0;
1073
+ }
1074
+ const cursorOnly = targets.length === 1 && targets[0] === "cursor";
1075
+ const nextSteps = cursorOnly ? [
1076
+ `${pc.bold("Restart Cursor, then establish or apply DESIGN.md:")}`,
1077
+ "",
1078
+ ` ${pc.cyan("EN")} ${pc.dim("Set up our design system \u2014 Toss-style, for a family meal-tracking app. Ask before writing DESIGN.md.")}`,
1079
+ ` ${pc.cyan("KR")} ${pc.dim("\uD1A0\uC2A4 \uC2A4\uD0C0\uC77C\uB85C \uAC00\uC871 \uC2DD\uB2E8 \uACF5\uC720 \uC571 \uB514\uC790\uC778 \uC2DC\uC2A4\uD15C\uC744 \uC81C\uC548\uD558\uACE0 DESIGN.md \uC791\uC131 \uC804\uC5D0 \uD655\uC778\uD574\uC918")}`,
1080
+ "",
1081
+ `${pc.dim("Cursor uses the installed rule + local catalog directly. It does not receive OmD named skills or sub-agents.")}`,
1082
+ `${pc.dim("You can also choose a reference in the Builder and download DESIGN.md before asking Cursor to build.")}`,
1083
+ "",
1084
+ `${pc.yellow("\u26A0 Already-running session?")} ${pc.dim("Restart Cursor so it reloads the project rule.")}`
1085
+ ].join("\n") : [
1086
+ `${pc.bold("Restart your agent, then type your first prompt:")}`,
1087
+ "",
1088
+ ` ${pc.cyan("EN")} ${pc.dim("Set up our design system \u2014 Toss-style, for a family meal-tracking app.")}`,
1089
+ ` ${pc.cyan("KR")} ${pc.dim("\uD1A0\uC2A4 \uC2A4\uD0C0\uC77C\uB85C \uAC00\uC871 \uC2DD\uB2E8 \uACF5\uC720 \uC571 \uB514\uC790\uC778 \uC2DC\uC2A4\uD15C \uC7A1\uC544\uC918")}`,
1090
+ "",
1091
+ `${pc.dim("Your agent routes through omd:init and writes DESIGN.md after confirmation. Then build against it:")}`,
1092
+ ` ${pc.cyan("EN")} ${pc.dim("Design the home screen.")} ${pc.cyan("KR")} ${pc.dim("\uD648 \uD654\uBA74 \uB514\uC790\uC778\uD574\uC918")}`,
1093
+ "",
1094
+ `${pc.dim('Full walkthrough \u2192 "Your first 60 seconds" in the README. Routing is automatic \u2014 no slash command needed.')}`,
1095
+ `${pc.dim("Power user: ")}${pc.cyan("/omd-harness <task>")}${pc.dim(" \u2014 jump straight into the pipeline.")}`,
1096
+ "",
1097
+ `${pc.yellow("\u26A0 Already-running session?")} ${pc.dim("Restart the coding agent after install. Codex must also trust the project before it loads project-local .codex/agents roles.")}`
1098
+ ].join("\n");
1099
+ p.note(nextSteps, "Next");
1100
+ const hookCount = scope === "project" && targets.includes("claude-code") ? 4 : 0;
1101
+ if (catalogCount > 0) {
1102
+ p.log.message(
1103
+ pc.bold("Reference catalog: ") + pc.cyan(`${catalogCount}`) + pc.dim(` DESIGN.md synced \u2192 ${[...catalogDestinations].join(" + ")}`)
1104
+ );
1105
+ }
1106
+ const reportedSkillCount = skillChannelTargets.length > 0 ? skills.length : 0;
1107
+ const reportedAgentCount = skillChannelTargets.length > 0 ? canonicalAgents.length : 0;
1108
+ p.outro(
1109
+ pc.green(
1110
+ `Done. ${reportedSkillCount} skills \xB7 ${reportedAgentCount} sub-agents \xB7 ${hookCount} hooks \xB7 ${catalogCount} catalog refs ready (${changedCount} changed \xB7 ${currentCount} already current)${scope === "global" ? " globally (channel user directories)" : ""}.`
1111
+ )
1112
+ );
1113
+ return 0;
1114
+ }
1115
+ export {
1116
+ dataDirFor,
1117
+ runInstallSkills,
1118
+ targetsAvailableForScope
1119
+ };
1120
+ //# sourceMappingURL=install-skills-ZZFNJ2II.js.map