@trac3r/oh-my-god 2.2.11

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 (638) hide show
  1. package/CHANGELOG.md +188 -0
  2. package/INSTALL-VERIFICATION-INDEX.md +51 -0
  3. package/LICENSE +21 -0
  4. package/OMG-setup.sh +2549 -0
  5. package/QUICK-REFERENCE.md +58 -0
  6. package/README.md +207 -0
  7. package/agents/__init__.py +1 -0
  8. package/agents/__pycache__/model_roles.cpython-313.pyc +0 -0
  9. package/agents/_model_roles.yaml +26 -0
  10. package/agents/designer.md +67 -0
  11. package/agents/explore.md +60 -0
  12. package/agents/model_roles.py +196 -0
  13. package/agents/omg-api-builder.md +23 -0
  14. package/agents/omg-architect-mode.md +41 -0
  15. package/agents/omg-architect.md +13 -0
  16. package/agents/omg-backend-engineer.md +41 -0
  17. package/agents/omg-critic.md +16 -0
  18. package/agents/omg-database-engineer.md +41 -0
  19. package/agents/omg-escalation-router.md +17 -0
  20. package/agents/omg-executor.md +12 -0
  21. package/agents/omg-frontend-designer.md +41 -0
  22. package/agents/omg-implement-mode.md +49 -0
  23. package/agents/omg-infra-engineer.md +41 -0
  24. package/agents/omg-qa-tester.md +16 -0
  25. package/agents/omg-research-mode.md +41 -0
  26. package/agents/omg-security-auditor.md +41 -0
  27. package/agents/omg-testing-engineer.md +41 -0
  28. package/agents/plan.md +80 -0
  29. package/agents/quick_task.md +64 -0
  30. package/agents/reviewer.md +83 -0
  31. package/agents/task.md +71 -0
  32. package/bin/omg +41 -0
  33. package/commands/OMG:ai-commit.md +113 -0
  34. package/commands/OMG:api-twin.md +22 -0
  35. package/commands/OMG:arch.md +313 -0
  36. package/commands/OMG:browser.md +29 -0
  37. package/commands/OMG:ccg.md +22 -0
  38. package/commands/OMG:compat.md +57 -0
  39. package/commands/OMG:cost.md +181 -0
  40. package/commands/OMG:crazy.md +125 -0
  41. package/commands/OMG:create-agent.md +183 -0
  42. package/commands/OMG:deep-plan.md +18 -0
  43. package/commands/OMG:deps.md +248 -0
  44. package/commands/OMG:diagnose-plugins.md +33 -0
  45. package/commands/OMG:doctor.md +37 -0
  46. package/commands/OMG:domain-init.md +11 -0
  47. package/commands/OMG:escalate.md +52 -0
  48. package/commands/OMG:forge.md +103 -0
  49. package/commands/OMG:health-check.md +48 -0
  50. package/commands/OMG:init.md +134 -0
  51. package/commands/OMG:issue.md +56 -0
  52. package/commands/OMG:mode.md +44 -0
  53. package/commands/OMG:playwright.md +17 -0
  54. package/commands/OMG:preflight.md +26 -0
  55. package/commands/OMG:preset.md +49 -0
  56. package/commands/OMG:profile-review.md +58 -0
  57. package/commands/OMG:project-init.md +11 -0
  58. package/commands/OMG:ralph-start.md +43 -0
  59. package/commands/OMG:ralph-stop.md +23 -0
  60. package/commands/OMG:security-check.md +28 -0
  61. package/commands/OMG:session-branch.md +101 -0
  62. package/commands/OMG:session-fork.md +57 -0
  63. package/commands/OMG:session-merge.md +138 -0
  64. package/commands/OMG:setup.md +82 -0
  65. package/commands/OMG:ship.md +18 -0
  66. package/commands/OMG:stats.md +225 -0
  67. package/commands/OMG:teams.md +54 -0
  68. package/commands/OMG:theme.md +44 -0
  69. package/commands/OMG:validate.md +59 -0
  70. package/commands/__init__.py +1 -0
  71. package/docs/command-surface.md +55 -0
  72. package/docs/install/claude-code.md +53 -0
  73. package/docs/install/codex.md +45 -0
  74. package/docs/install/gemini.md +43 -0
  75. package/docs/install/github-action.md +81 -0
  76. package/docs/install/github-app-required-checks.md +107 -0
  77. package/docs/install/github-app.md +161 -0
  78. package/docs/install/kimi.md +43 -0
  79. package/docs/install/opencode.md +38 -0
  80. package/docs/proof.md +182 -0
  81. package/hooks/__init__.py +0 -0
  82. package/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  83. package/hooks/__pycache__/_agent_registry.cpython-313.pyc +0 -0
  84. package/hooks/__pycache__/_analytics.cpython-313.pyc +0 -0
  85. package/hooks/__pycache__/_budget.cpython-313.pyc +0 -0
  86. package/hooks/__pycache__/_common.cpython-313.pyc +0 -0
  87. package/hooks/__pycache__/_compression_optimizer.cpython-313.pyc +0 -0
  88. package/hooks/__pycache__/_cost_ledger.cpython-313.pyc +0 -0
  89. package/hooks/__pycache__/_learnings.cpython-313.pyc +0 -0
  90. package/hooks/__pycache__/_memory.cpython-313.pyc +0 -0
  91. package/hooks/__pycache__/_post_write.cpython-313.pyc +0 -0
  92. package/hooks/__pycache__/_protected_context.cpython-313.pyc +0 -0
  93. package/hooks/__pycache__/_token_counter.cpython-313.pyc +0 -0
  94. package/hooks/__pycache__/branch_manager.cpython-313.pyc +0 -0
  95. package/hooks/__pycache__/budget_governor.cpython-313.pyc +0 -0
  96. package/hooks/__pycache__/circuit-breaker.cpython-313.pyc +0 -0
  97. package/hooks/__pycache__/compression_feedback.cpython-313.pyc +0 -0
  98. package/hooks/__pycache__/config-guard.cpython-313.pyc +0 -0
  99. package/hooks/__pycache__/context_pressure.cpython-313.pyc +0 -0
  100. package/hooks/__pycache__/credential_store.cpython-313.pyc +0 -0
  101. package/hooks/__pycache__/fetch-rate-limits.cpython-313.pyc +0 -0
  102. package/hooks/__pycache__/firewall.cpython-313.pyc +0 -0
  103. package/hooks/__pycache__/hashline-formatter-bridge.cpython-313.pyc +0 -0
  104. package/hooks/__pycache__/hashline-injector.cpython-313.pyc +0 -0
  105. package/hooks/__pycache__/hashline-validator.cpython-313.pyc +0 -0
  106. package/hooks/__pycache__/idle-detector.cpython-313.pyc +0 -0
  107. package/hooks/__pycache__/instructions-loaded.cpython-313.pyc +0 -0
  108. package/hooks/__pycache__/intentgate-keyword-detector.cpython-313.pyc +0 -0
  109. package/hooks/__pycache__/magic-keyword-router.cpython-313.pyc +0 -0
  110. package/hooks/__pycache__/policy_engine.cpython-313.pyc +0 -0
  111. package/hooks/__pycache__/post-tool-failure.cpython-313.pyc +0 -0
  112. package/hooks/__pycache__/post-write.cpython-313.pyc +0 -0
  113. package/hooks/__pycache__/post_write.cpython-313.pyc +0 -0
  114. package/hooks/__pycache__/pre-compact.cpython-313.pyc +0 -0
  115. package/hooks/__pycache__/pre-tool-inject.cpython-313.pyc +0 -0
  116. package/hooks/__pycache__/prompt-enhancer.cpython-313.pyc +0 -0
  117. package/hooks/__pycache__/quality-runner.cpython-313.pyc +0 -0
  118. package/hooks/__pycache__/query.cpython-313.pyc +0 -0
  119. package/hooks/__pycache__/secret-guard.cpython-313.pyc +0 -0
  120. package/hooks/__pycache__/secret_audit.cpython-313.pyc +0 -0
  121. package/hooks/__pycache__/security_validators.cpython-313.pyc +0 -0
  122. package/hooks/__pycache__/session-end-capture.cpython-313.pyc +0 -0
  123. package/hooks/__pycache__/session-start.cpython-313.pyc +0 -0
  124. package/hooks/__pycache__/setup_wizard.cpython-313.pyc +0 -0
  125. package/hooks/__pycache__/shadow_manager.cpython-313.pyc +0 -0
  126. package/hooks/__pycache__/state_migration.cpython-313.pyc +0 -0
  127. package/hooks/__pycache__/stop-gate.cpython-313.pyc +0 -0
  128. package/hooks/__pycache__/stop_dispatcher.cpython-313.pyc +0 -0
  129. package/hooks/__pycache__/tdd-gate.cpython-313.pyc +0 -0
  130. package/hooks/__pycache__/terms-guard.cpython-313.pyc +0 -0
  131. package/hooks/__pycache__/test-validator.cpython-313.pyc +0 -0
  132. package/hooks/__pycache__/test_generator_hook.cpython-313.pyc +0 -0
  133. package/hooks/__pycache__/todo-state-tracker.cpython-313.pyc +0 -0
  134. package/hooks/__pycache__/tool-ledger.cpython-313.pyc +0 -0
  135. package/hooks/__pycache__/trust_review.cpython-313.pyc +0 -0
  136. package/hooks/__pycache__/user-prompt-submit.cpython-313.pyc +0 -0
  137. package/hooks/_agent_registry.py +481 -0
  138. package/hooks/_analytics.py +291 -0
  139. package/hooks/_budget.py +31 -0
  140. package/hooks/_common.py +761 -0
  141. package/hooks/_compression_optimizer.py +119 -0
  142. package/hooks/_cost_ledger.py +176 -0
  143. package/hooks/_learnings.py +126 -0
  144. package/hooks/_memory.py +103 -0
  145. package/hooks/_post_write.py +46 -0
  146. package/hooks/_protected_context.py +150 -0
  147. package/hooks/_token_counter.py +221 -0
  148. package/hooks/branch_manager.py +255 -0
  149. package/hooks/budget_governor.py +326 -0
  150. package/hooks/circuit-breaker.py +270 -0
  151. package/hooks/compression_feedback.py +254 -0
  152. package/hooks/config-guard.py +193 -0
  153. package/hooks/context_pressure.py +119 -0
  154. package/hooks/credential_store.py +970 -0
  155. package/hooks/fetch-rate-limits.py +212 -0
  156. package/hooks/firewall.py +323 -0
  157. package/hooks/hashline-formatter-bridge.py +224 -0
  158. package/hooks/hashline-injector.py +273 -0
  159. package/hooks/hashline-validator.py +216 -0
  160. package/hooks/idle-detector.py +97 -0
  161. package/hooks/instructions-loaded.py +26 -0
  162. package/hooks/intentgate-keyword-detector.py +200 -0
  163. package/hooks/magic-keyword-router.py +195 -0
  164. package/hooks/policy_engine.py +767 -0
  165. package/hooks/post-tool-failure.py +19 -0
  166. package/hooks/post-write.py +233 -0
  167. package/hooks/pre-compact.py +470 -0
  168. package/hooks/pre-tool-inject.py +98 -0
  169. package/hooks/prompt-enhancer.py +879 -0
  170. package/hooks/quality-runner.py +191 -0
  171. package/hooks/query.py +512 -0
  172. package/hooks/secret-guard.py +120 -0
  173. package/hooks/secret_audit.py +144 -0
  174. package/hooks/security_validators.py +93 -0
  175. package/hooks/session-end-capture.py +505 -0
  176. package/hooks/session-start.py +261 -0
  177. package/hooks/setup_wizard.py +1101 -0
  178. package/hooks/shadow_manager.py +476 -0
  179. package/hooks/state_migration.py +228 -0
  180. package/hooks/stop-gate.py +7 -0
  181. package/hooks/stop_dispatcher.py +1259 -0
  182. package/hooks/tdd-gate.py +10 -0
  183. package/hooks/terms-guard.py +98 -0
  184. package/hooks/test-validator.py +462 -0
  185. package/hooks/test_generator_hook.py +123 -0
  186. package/hooks/todo-state-tracker.py +114 -0
  187. package/hooks/tool-ledger.py +165 -0
  188. package/hooks/trust_review.py +662 -0
  189. package/hooks/user-prompt-submit.py +12 -0
  190. package/hud/omg-hud.mjs +1571 -0
  191. package/lab/__init__.py +1 -0
  192. package/lab/__pycache__/__init__.cpython-313.pyc +0 -0
  193. package/lab/__pycache__/axolotl_adapter.cpython-313.pyc +0 -0
  194. package/lab/__pycache__/forge_runner.cpython-313.pyc +0 -0
  195. package/lab/__pycache__/gazebo_adapter.cpython-313.pyc +0 -0
  196. package/lab/__pycache__/isaac_gym_adapter.cpython-313.pyc +0 -0
  197. package/lab/__pycache__/mock_isaac_env.cpython-313.pyc +0 -0
  198. package/lab/__pycache__/pipeline.cpython-313.pyc +0 -0
  199. package/lab/__pycache__/policies.cpython-313.pyc +0 -0
  200. package/lab/__pycache__/pybullet_adapter.cpython-313.pyc +0 -0
  201. package/lab/axolotl_adapter.py +531 -0
  202. package/lab/forge_runner.py +103 -0
  203. package/lab/gazebo_adapter.py +168 -0
  204. package/lab/isaac_gym_adapter.py +190 -0
  205. package/lab/mock_isaac_env.py +47 -0
  206. package/lab/pipeline.py +712 -0
  207. package/lab/policies.py +52 -0
  208. package/lab/pybullet_adapter.py +192 -0
  209. package/package.json +61 -0
  210. package/plugins/README.md +78 -0
  211. package/plugins/__init__.py +1 -0
  212. package/plugins/__pycache__/__init__.cpython-313.pyc +0 -0
  213. package/plugins/advanced/commands/OMG-code-review.md +114 -0
  214. package/plugins/advanced/commands/OMG-deep-plan.md +266 -0
  215. package/plugins/advanced/commands/OMG-handoff.md +115 -0
  216. package/plugins/advanced/commands/OMG-learn.md +110 -0
  217. package/plugins/advanced/commands/OMG-maintainer.md +31 -0
  218. package/plugins/advanced/commands/OMG-ralph-start.md +43 -0
  219. package/plugins/advanced/commands/OMG-ralph-stop.md +23 -0
  220. package/plugins/advanced/commands/OMG-security-review.md +16 -0
  221. package/plugins/advanced/commands/OMG-sequential-thinking.md +20 -0
  222. package/plugins/advanced/commands/OMG-ship.md +46 -0
  223. package/plugins/advanced/commands/OMG:code-review.md +114 -0
  224. package/plugins/advanced/commands/OMG:deep-plan.md +266 -0
  225. package/plugins/advanced/commands/OMG:handoff.md +115 -0
  226. package/plugins/advanced/commands/OMG:learn.md +110 -0
  227. package/plugins/advanced/commands/OMG:maintainer.md +31 -0
  228. package/plugins/advanced/commands/OMG:ralph-start.md +43 -0
  229. package/plugins/advanced/commands/OMG:ralph-stop.md +23 -0
  230. package/plugins/advanced/commands/OMG:security-review.md +16 -0
  231. package/plugins/advanced/commands/OMG:sequential-thinking.md +20 -0
  232. package/plugins/advanced/commands/OMG:ship.md +46 -0
  233. package/plugins/advanced/plugin.json +104 -0
  234. package/plugins/core/plugin.json +204 -0
  235. package/plugins/dephealth/__init__.py +0 -0
  236. package/plugins/dephealth/__pycache__/__init__.cpython-313.pyc +0 -0
  237. package/plugins/dephealth/__pycache__/cve_scanner.cpython-313.pyc +0 -0
  238. package/plugins/dephealth/__pycache__/license_checker.cpython-313.pyc +0 -0
  239. package/plugins/dephealth/__pycache__/manifest_detector.cpython-313.pyc +0 -0
  240. package/plugins/dephealth/__pycache__/vuln_analyzer.cpython-313.pyc +0 -0
  241. package/plugins/dephealth/cve_scanner.py +279 -0
  242. package/plugins/dephealth/license_checker.py +135 -0
  243. package/plugins/dephealth/manifest_detector.py +423 -0
  244. package/plugins/dephealth/vuln_analyzer.py +176 -0
  245. package/plugins/testgen/__init__.py +0 -0
  246. package/plugins/testgen/__pycache__/__init__.cpython-313.pyc +0 -0
  247. package/plugins/testgen/__pycache__/codamosa_engine.cpython-313.pyc +0 -0
  248. package/plugins/testgen/__pycache__/edge_case_synthesizer.cpython-313.pyc +0 -0
  249. package/plugins/testgen/__pycache__/framework_detector.cpython-313.pyc +0 -0
  250. package/plugins/testgen/__pycache__/skeleton_generator.cpython-313.pyc +0 -0
  251. package/plugins/testgen/codamosa_engine.py +402 -0
  252. package/plugins/testgen/edge_case_synthesizer.py +184 -0
  253. package/plugins/testgen/framework_detector.py +271 -0
  254. package/plugins/testgen/skeleton_generator.py +219 -0
  255. package/plugins/viz/__init__.py +0 -0
  256. package/plugins/viz/__pycache__/__init__.cpython-313.pyc +0 -0
  257. package/plugins/viz/__pycache__/ast_parser.cpython-313.pyc +0 -0
  258. package/plugins/viz/__pycache__/diagram_generator.cpython-313.pyc +0 -0
  259. package/plugins/viz/__pycache__/graph_builder.cpython-313.pyc +0 -0
  260. package/plugins/viz/__pycache__/native_parsers.cpython-313.pyc +0 -0
  261. package/plugins/viz/__pycache__/regex_parser.cpython-313.pyc +0 -0
  262. package/plugins/viz/ast_parser.py +139 -0
  263. package/plugins/viz/diagram_generator.py +192 -0
  264. package/plugins/viz/graph_builder.py +444 -0
  265. package/plugins/viz/native_parsers.py +259 -0
  266. package/plugins/viz/regex_parser.py +112 -0
  267. package/pyproject.toml +143 -0
  268. package/registry/__init__.py +1 -0
  269. package/registry/__pycache__/__init__.cpython-313.pyc +0 -0
  270. package/registry/__pycache__/approval_artifact.cpython-313.pyc +0 -0
  271. package/registry/__pycache__/verify_artifact.cpython-313.pyc +0 -0
  272. package/registry/approval_artifact.py +236 -0
  273. package/registry/bundles/algorithms.yaml +45 -0
  274. package/registry/bundles/api-twin.yaml +48 -0
  275. package/registry/bundles/ast-pack.yaml +80 -0
  276. package/registry/bundles/claim-judge.yaml +49 -0
  277. package/registry/bundles/control-plane.yaml +192 -0
  278. package/registry/bundles/data-lineage.yaml +47 -0
  279. package/registry/bundles/delta-classifier.yaml +47 -0
  280. package/registry/bundles/eval-gate.yaml +47 -0
  281. package/registry/bundles/hash-edit.yaml +73 -0
  282. package/registry/bundles/health.yaml +45 -0
  283. package/registry/bundles/hook-governor.yaml +101 -0
  284. package/registry/bundles/incident-replay.yaml +47 -0
  285. package/registry/bundles/lsp-pack.yaml +80 -0
  286. package/registry/bundles/mcp-fabric.yaml +53 -0
  287. package/registry/bundles/plan-council.yaml +56 -0
  288. package/registry/bundles/preflight.yaml +48 -0
  289. package/registry/bundles/proof-gate.yaml +49 -0
  290. package/registry/bundles/remote-supervisor.yaml +49 -0
  291. package/registry/bundles/robotics.yaml +45 -0
  292. package/registry/bundles/secure-worktree-pipeline.yaml +69 -0
  293. package/registry/bundles/security-check.yaml +50 -0
  294. package/registry/bundles/terminal-lane.yaml +61 -0
  295. package/registry/bundles/test-intent-lock.yaml +49 -0
  296. package/registry/bundles/tracebank.yaml +47 -0
  297. package/registry/bundles/vision.yaml +45 -0
  298. package/registry/omg-capability.schema.json +378 -0
  299. package/registry/policy-packs/airgapped.lock.json +11 -0
  300. package/registry/policy-packs/airgapped.signature.json +10 -0
  301. package/registry/policy-packs/airgapped.yaml +16 -0
  302. package/registry/policy-packs/fintech.lock.json +11 -0
  303. package/registry/policy-packs/fintech.signature.json +10 -0
  304. package/registry/policy-packs/fintech.yaml +15 -0
  305. package/registry/policy-packs/locked-prod.lock.json +11 -0
  306. package/registry/policy-packs/locked-prod.signature.json +10 -0
  307. package/registry/policy-packs/locked-prod.yaml +18 -0
  308. package/registry/trusted_signers.json +44 -0
  309. package/registry/verify_artifact.py +493 -0
  310. package/runtime/__init__.py +36 -0
  311. package/runtime/__pycache__/__init__.cpython-313.pyc +0 -0
  312. package/runtime/__pycache__/adoption.cpython-313.pyc +0 -0
  313. package/runtime/__pycache__/agent_selector.cpython-313.pyc +0 -0
  314. package/runtime/__pycache__/api_twin.cpython-313.pyc +0 -0
  315. package/runtime/__pycache__/architecture_signal.cpython-313.pyc +0 -0
  316. package/runtime/__pycache__/artifact_parsers.cpython-313.pyc +0 -0
  317. package/runtime/__pycache__/asset_loader.cpython-313.pyc +0 -0
  318. package/runtime/__pycache__/background_verification.cpython-313.pyc +0 -0
  319. package/runtime/__pycache__/budget_envelopes.cpython-313.pyc +0 -0
  320. package/runtime/__pycache__/business_workflow.cpython-313.pyc +0 -0
  321. package/runtime/__pycache__/canonical_surface.cpython-313.pyc +0 -0
  322. package/runtime/__pycache__/canonical_taxonomy.cpython-313.pyc +0 -0
  323. package/runtime/__pycache__/claim_judge.cpython-313.pyc +0 -0
  324. package/runtime/__pycache__/cli_provider.cpython-313.pyc +0 -0
  325. package/runtime/__pycache__/compat.cpython-313.pyc +0 -0
  326. package/runtime/__pycache__/complexity_scorer.cpython-313.pyc +0 -0
  327. package/runtime/__pycache__/compliance_governor.cpython-313.pyc +0 -0
  328. package/runtime/__pycache__/config_transaction.cpython-313.pyc +0 -0
  329. package/runtime/__pycache__/context_compiler.cpython-313.pyc +0 -0
  330. package/runtime/__pycache__/context_engine.cpython-313.pyc +0 -0
  331. package/runtime/__pycache__/context_limits.cpython-313.pyc +0 -0
  332. package/runtime/__pycache__/contract_compiler.cpython-313.pyc +0 -0
  333. package/runtime/__pycache__/custom_agent_loader.cpython-313.pyc +0 -0
  334. package/runtime/__pycache__/data_lineage.cpython-313.pyc +0 -0
  335. package/runtime/__pycache__/defense_state.cpython-313.pyc +0 -0
  336. package/runtime/__pycache__/delta_classifier.cpython-313.pyc +0 -0
  337. package/runtime/__pycache__/dispatcher.cpython-313.pyc +0 -0
  338. package/runtime/__pycache__/doc_generator.cpython-313.pyc +0 -0
  339. package/runtime/__pycache__/domain_packs.cpython-313.pyc +0 -0
  340. package/runtime/__pycache__/ecosystem.cpython-313.pyc +0 -0
  341. package/runtime/__pycache__/equalizer.cpython-313.pyc +0 -0
  342. package/runtime/__pycache__/eval_gate.cpython-313.pyc +0 -0
  343. package/runtime/__pycache__/evidence_narrator.cpython-313.pyc +0 -0
  344. package/runtime/__pycache__/evidence_query.cpython-313.pyc +0 -0
  345. package/runtime/__pycache__/evidence_registry.cpython-313.pyc +0 -0
  346. package/runtime/__pycache__/evidence_requirements.cpython-313.pyc +0 -0
  347. package/runtime/__pycache__/exec_kernel.cpython-313.pyc +0 -0
  348. package/runtime/__pycache__/explainer_formatter.cpython-313.pyc +0 -0
  349. package/runtime/__pycache__/feature_registry.cpython-313.pyc +0 -0
  350. package/runtime/__pycache__/forge_agents.cpython-313.pyc +0 -0
  351. package/runtime/__pycache__/forge_contracts.cpython-313.pyc +0 -0
  352. package/runtime/__pycache__/forge_domains.cpython-313.pyc +0 -0
  353. package/runtime/__pycache__/forge_run_id.cpython-313.pyc +0 -0
  354. package/runtime/__pycache__/github_integration.cpython-313.pyc +0 -0
  355. package/runtime/__pycache__/github_review_bot.cpython-313.pyc +0 -0
  356. package/runtime/__pycache__/github_review_contract.cpython-313.pyc +0 -0
  357. package/runtime/__pycache__/github_review_formatter.cpython-313.pyc +0 -0
  358. package/runtime/__pycache__/guide_assert.cpython-313.pyc +0 -0
  359. package/runtime/__pycache__/hook_governor.cpython-313.pyc +0 -0
  360. package/runtime/__pycache__/host_parity.cpython-313.pyc +0 -0
  361. package/runtime/__pycache__/incident_replay.cpython-313.pyc +0 -0
  362. package/runtime/__pycache__/install_planner.cpython-313.pyc +0 -0
  363. package/runtime/__pycache__/interaction_journal.cpython-313.pyc +0 -0
  364. package/runtime/__pycache__/issue_surface.cpython-313.pyc +0 -0
  365. package/runtime/__pycache__/legacy_compat.cpython-313.pyc +0 -0
  366. package/runtime/__pycache__/mcp_config_writers.cpython-313.pyc +0 -0
  367. package/runtime/__pycache__/mcp_lifecycle.cpython-313.pyc +0 -0
  368. package/runtime/__pycache__/mcp_memory_server.cpython-313.pyc +0 -0
  369. package/runtime/__pycache__/memory_store.cpython-313.pyc +0 -0
  370. package/runtime/__pycache__/merge_writer.cpython-313.pyc +0 -0
  371. package/runtime/__pycache__/music_omr_testbed.cpython-313.pyc +0 -0
  372. package/runtime/__pycache__/mutation_gate.cpython-313.pyc +0 -0
  373. package/runtime/__pycache__/omc_compat.cpython-313.pyc +0 -0
  374. package/runtime/__pycache__/omg_browser_cli.cpython-313.pyc +0 -0
  375. package/runtime/__pycache__/omg_mcp_server.cpython-313.pyc +0 -0
  376. package/runtime/__pycache__/opus_plan.cpython-313.pyc +0 -0
  377. package/runtime/__pycache__/playwright_adapter.cpython-313.pyc +0 -0
  378. package/runtime/__pycache__/playwright_pack.cpython-313.pyc +0 -0
  379. package/runtime/__pycache__/plugin_diagnostics.cpython-313.pyc +0 -0
  380. package/runtime/__pycache__/plugin_interop.cpython-313.pyc +0 -0
  381. package/runtime/__pycache__/policy_pack_loader.cpython-313.pyc +0 -0
  382. package/runtime/__pycache__/preflight.cpython-313.pyc +0 -0
  383. package/runtime/__pycache__/profile_io.cpython-313.pyc +0 -0
  384. package/runtime/__pycache__/prompt_compiler.cpython-313.pyc +0 -0
  385. package/runtime/__pycache__/proof_chain.cpython-313.pyc +0 -0
  386. package/runtime/__pycache__/proof_gate.cpython-313.pyc +0 -0
  387. package/runtime/__pycache__/provider_parity_eval.cpython-313.pyc +0 -0
  388. package/runtime/__pycache__/release_artifact_audit.cpython-313.pyc +0 -0
  389. package/runtime/__pycache__/release_run_coordinator.cpython-313.pyc +0 -0
  390. package/runtime/__pycache__/release_surface_compiler.cpython-313.pyc +0 -0
  391. package/runtime/__pycache__/release_surface_registry.cpython-313.pyc +0 -0
  392. package/runtime/__pycache__/release_surfaces.cpython-313.pyc +0 -0
  393. package/runtime/__pycache__/remote_supervisor.cpython-313.pyc +0 -0
  394. package/runtime/__pycache__/repro_pack.cpython-313.pyc +0 -0
  395. package/runtime/__pycache__/rollback_manifest.cpython-313.pyc +0 -0
  396. package/runtime/__pycache__/router_critics.cpython-313.pyc +0 -0
  397. package/runtime/__pycache__/router_executor.cpython-313.pyc +0 -0
  398. package/runtime/__pycache__/router_selector.cpython-313.pyc +0 -0
  399. package/runtime/__pycache__/runtime_contracts.cpython-313.pyc +0 -0
  400. package/runtime/__pycache__/runtime_profile.cpython-313.pyc +0 -0
  401. package/runtime/__pycache__/security_check.cpython-313.pyc +0 -0
  402. package/runtime/__pycache__/session_health.cpython-313.pyc +0 -0
  403. package/runtime/__pycache__/skill_evolution.cpython-313.pyc +0 -0
  404. package/runtime/__pycache__/skill_registry.cpython-313.pyc +0 -0
  405. package/runtime/__pycache__/subagent_dispatcher.cpython-313.pyc +0 -0
  406. package/runtime/__pycache__/subscription_tiers.cpython-313.pyc +0 -0
  407. package/runtime/__pycache__/team_router.cpython-313.pyc +0 -0
  408. package/runtime/__pycache__/test_intent_lock.cpython-313-pytest-9.0.2.pyc +0 -0
  409. package/runtime/__pycache__/test_intent_lock.cpython-313.pyc +0 -0
  410. package/runtime/__pycache__/tmux_session_manager.cpython-313.pyc +0 -0
  411. package/runtime/__pycache__/tool_fabric.cpython-313.pyc +0 -0
  412. package/runtime/__pycache__/tool_plan_gate.cpython-313.pyc +0 -0
  413. package/runtime/__pycache__/tool_relevance.cpython-313.pyc +0 -0
  414. package/runtime/__pycache__/tracebank.cpython-313.pyc +0 -0
  415. package/runtime/__pycache__/untrusted_content.cpython-313.pyc +0 -0
  416. package/runtime/__pycache__/validate.cpython-313.pyc +0 -0
  417. package/runtime/__pycache__/verdict_schema.cpython-313.pyc +0 -0
  418. package/runtime/__pycache__/verification_controller.cpython-313.pyc +0 -0
  419. package/runtime/__pycache__/verification_loop.cpython-313.pyc +0 -0
  420. package/runtime/__pycache__/vision_artifacts.cpython-313.pyc +0 -0
  421. package/runtime/__pycache__/vision_cache.cpython-313.pyc +0 -0
  422. package/runtime/__pycache__/vision_jobs.cpython-313.pyc +0 -0
  423. package/runtime/__pycache__/worker_watchdog.cpython-313.pyc +0 -0
  424. package/runtime/adapters/__init__.py +13 -0
  425. package/runtime/adapters/__pycache__/__init__.cpython-313.pyc +0 -0
  426. package/runtime/adapters/__pycache__/claude.cpython-313.pyc +0 -0
  427. package/runtime/adapters/__pycache__/gpt.cpython-313.pyc +0 -0
  428. package/runtime/adapters/__pycache__/local.cpython-313.pyc +0 -0
  429. package/runtime/adapters/claude.py +63 -0
  430. package/runtime/adapters/gpt.py +56 -0
  431. package/runtime/adapters/local.py +56 -0
  432. package/runtime/adoption.py +280 -0
  433. package/runtime/api_twin.py +450 -0
  434. package/runtime/architecture_signal.py +226 -0
  435. package/runtime/artifact_parsers.py +161 -0
  436. package/runtime/asset_loader.py +62 -0
  437. package/runtime/background_verification.py +178 -0
  438. package/runtime/budget_envelopes.py +398 -0
  439. package/runtime/business_workflow.py +234 -0
  440. package/runtime/canonical_surface.py +53 -0
  441. package/runtime/canonical_taxonomy.py +27 -0
  442. package/runtime/claim_judge.py +648 -0
  443. package/runtime/cli_provider.py +105 -0
  444. package/runtime/compat.py +2222 -0
  445. package/runtime/complexity_scorer.py +148 -0
  446. package/runtime/compliance_governor.py +505 -0
  447. package/runtime/config_transaction.py +304 -0
  448. package/runtime/context_compiler.py +131 -0
  449. package/runtime/context_engine.py +708 -0
  450. package/runtime/context_limits.py +363 -0
  451. package/runtime/contract_compiler.py +3664 -0
  452. package/runtime/custom_agent_loader.py +366 -0
  453. package/runtime/data_lineage.py +244 -0
  454. package/runtime/defense_state.py +261 -0
  455. package/runtime/delta_classifier.py +231 -0
  456. package/runtime/dispatcher.py +47 -0
  457. package/runtime/doc_generator.py +319 -0
  458. package/runtime/domain_packs.py +75 -0
  459. package/runtime/ecosystem.py +371 -0
  460. package/runtime/equalizer.py +268 -0
  461. package/runtime/eval_gate.py +96 -0
  462. package/runtime/evidence_narrator.py +147 -0
  463. package/runtime/evidence_query.py +303 -0
  464. package/runtime/evidence_registry.py +16 -0
  465. package/runtime/evidence_requirements.py +157 -0
  466. package/runtime/exec_kernel.py +267 -0
  467. package/runtime/explainer_formatter.py +82 -0
  468. package/runtime/feature_registry.py +109 -0
  469. package/runtime/forge_agents.py +915 -0
  470. package/runtime/forge_contracts.py +519 -0
  471. package/runtime/forge_domains.py +68 -0
  472. package/runtime/forge_run_id.py +86 -0
  473. package/runtime/guide_assert.py +135 -0
  474. package/runtime/hook_governor.py +156 -0
  475. package/runtime/host_parity.py +373 -0
  476. package/runtime/incident_replay.py +310 -0
  477. package/runtime/install_planner.py +617 -0
  478. package/runtime/interaction_journal.py +566 -0
  479. package/runtime/issue_surface.py +472 -0
  480. package/runtime/legacy_compat.py +7 -0
  481. package/runtime/mcp_config_writers.py +360 -0
  482. package/runtime/mcp_lifecycle.py +175 -0
  483. package/runtime/mcp_memory_server.py +220 -0
  484. package/runtime/memory_parsers/__init__.py +0 -0
  485. package/runtime/memory_parsers/__pycache__/__init__.cpython-313.pyc +0 -0
  486. package/runtime/memory_parsers/__pycache__/chatgpt_parser.cpython-313.pyc +0 -0
  487. package/runtime/memory_parsers/__pycache__/claude_import.cpython-313.pyc +0 -0
  488. package/runtime/memory_parsers/__pycache__/export.cpython-313.pyc +0 -0
  489. package/runtime/memory_parsers/__pycache__/gemini_import.cpython-313.pyc +0 -0
  490. package/runtime/memory_parsers/__pycache__/kimi_import.cpython-313.pyc +0 -0
  491. package/runtime/memory_parsers/chatgpt_parser.py +257 -0
  492. package/runtime/memory_parsers/claude_import.py +107 -0
  493. package/runtime/memory_parsers/export.py +97 -0
  494. package/runtime/memory_parsers/gemini_import.py +91 -0
  495. package/runtime/memory_parsers/kimi_import.py +91 -0
  496. package/runtime/memory_store.py +1182 -0
  497. package/runtime/merge_writer.py +445 -0
  498. package/runtime/music_omr_testbed.py +336 -0
  499. package/runtime/mutation_gate.py +320 -0
  500. package/runtime/omc_compat.py +7 -0
  501. package/runtime/omg_browser_cli.py +95 -0
  502. package/runtime/omg_compat_contract_snapshot.json +936 -0
  503. package/runtime/omg_contract_snapshot.json +936 -0
  504. package/runtime/omg_mcp_server.py +306 -0
  505. package/runtime/playwright_adapter.py +39 -0
  506. package/runtime/playwright_pack.py +253 -0
  507. package/runtime/plugin_diagnostics.py +308 -0
  508. package/runtime/plugin_interop.py +1060 -0
  509. package/runtime/policy_pack_loader.py +147 -0
  510. package/runtime/preflight.py +135 -0
  511. package/runtime/profile_io.py +328 -0
  512. package/runtime/proof_chain.py +472 -0
  513. package/runtime/proof_gate.py +442 -0
  514. package/runtime/provider_parity_eval.py +109 -0
  515. package/runtime/providers/__init__.py +0 -0
  516. package/runtime/providers/__pycache__/__init__.cpython-313.pyc +0 -0
  517. package/runtime/providers/__pycache__/codex_provider.cpython-313.pyc +0 -0
  518. package/runtime/providers/__pycache__/gemini_provider.cpython-313.pyc +0 -0
  519. package/runtime/providers/__pycache__/kimi_provider.cpython-313.pyc +0 -0
  520. package/runtime/providers/__pycache__/opencode_provider.cpython-313.pyc +0 -0
  521. package/runtime/providers/codex_provider.py +129 -0
  522. package/runtime/providers/gemini_provider.py +143 -0
  523. package/runtime/providers/kimi_provider.py +167 -0
  524. package/runtime/providers/opencode_provider.py +99 -0
  525. package/runtime/release_artifact_audit.py +556 -0
  526. package/runtime/release_run_coordinator.py +574 -0
  527. package/runtime/release_surface_compiler.py +643 -0
  528. package/runtime/release_surface_registry.py +283 -0
  529. package/runtime/release_surfaces.py +320 -0
  530. package/runtime/remote_supervisor.py +79 -0
  531. package/runtime/repro_pack.py +398 -0
  532. package/runtime/rollback_manifest.py +143 -0
  533. package/runtime/router_critics.py +229 -0
  534. package/runtime/router_executor.py +142 -0
  535. package/runtime/router_selector.py +99 -0
  536. package/runtime/runtime_contracts.py +292 -0
  537. package/runtime/runtime_profile.py +133 -0
  538. package/runtime/security_check.py +1094 -0
  539. package/runtime/session_health.py +546 -0
  540. package/runtime/skill_evolution.py +221 -0
  541. package/runtime/skill_registry.py +53 -0
  542. package/runtime/subagent_dispatcher.py +604 -0
  543. package/runtime/subscription_tiers.py +258 -0
  544. package/runtime/team_router.py +1399 -0
  545. package/runtime/test_intent_lock.py +543 -0
  546. package/runtime/tmux_session_manager.py +172 -0
  547. package/runtime/tool_fabric.py +570 -0
  548. package/runtime/tool_plan_gate.py +460 -0
  549. package/runtime/tracebank.py +125 -0
  550. package/runtime/untrusted_content.py +360 -0
  551. package/runtime/validate.py +293 -0
  552. package/runtime/verdict_schema.py +198 -0
  553. package/runtime/verification_controller.py +235 -0
  554. package/runtime/verification_loop.py +73 -0
  555. package/runtime/vision_artifacts.py +31 -0
  556. package/runtime/vision_cache.py +38 -0
  557. package/runtime/vision_jobs.py +92 -0
  558. package/runtime/worker_watchdog.py +526 -0
  559. package/scripts/__pycache__/audit-published-artifact.cpython-313.pyc +0 -0
  560. package/scripts/__pycache__/check-doc-parity.cpython-313.pyc +0 -0
  561. package/scripts/__pycache__/check-omg-standalone-clean.cpython-313.pyc +0 -0
  562. package/scripts/__pycache__/github_review_helpers.cpython-313.pyc +0 -0
  563. package/scripts/__pycache__/omg.cpython-313.pyc +0 -0
  564. package/scripts/__pycache__/prepare-release-proof-fixtures.cpython-313.pyc +0 -0
  565. package/scripts/__pycache__/sync-release-identity.cpython-313.pyc +0 -0
  566. package/scripts/__pycache__/validate-release-identity.cpython-313.pyc +0 -0
  567. package/scripts/audit-published-artifact.py +59 -0
  568. package/scripts/check-omg-compat-contract-snapshot.py +137 -0
  569. package/scripts/check-omg-contract-snapshot.py +12 -0
  570. package/scripts/check-omg-public-ready.py +273 -0
  571. package/scripts/check-omg-standalone-clean.py +133 -0
  572. package/scripts/emit_host_parity.py +72 -0
  573. package/scripts/legacy_to_omg_migrate.py +29 -0
  574. package/scripts/migrate-legacy.py +464 -0
  575. package/scripts/omc_to_omg_migrate.py +12 -0
  576. package/scripts/omg.py +2962 -0
  577. package/scripts/pre-release-check.sh +38 -0
  578. package/scripts/prepare-release-proof-fixtures.py +602 -0
  579. package/scripts/print-canonical-version.py +80 -0
  580. package/scripts/settings-merge.py +289 -0
  581. package/scripts/sync-release-identity.py +481 -0
  582. package/scripts/validate-release-identity.py +632 -0
  583. package/scripts/verify-no-omc.sh +5 -0
  584. package/scripts/verify-standalone.sh +35 -0
  585. package/settings.json +751 -0
  586. package/tools/__init__.py +2 -0
  587. package/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  588. package/tools/__pycache__/browser_consent.cpython-313.pyc +0 -0
  589. package/tools/__pycache__/browser_stealth.cpython-313.pyc +0 -0
  590. package/tools/__pycache__/browser_tool.cpython-313.pyc +0 -0
  591. package/tools/__pycache__/changelog_generator.cpython-313.pyc +0 -0
  592. package/tools/__pycache__/commit_splitter.cpython-313.pyc +0 -0
  593. package/tools/__pycache__/config_discovery.cpython-313.pyc +0 -0
  594. package/tools/__pycache__/config_merger.cpython-313.pyc +0 -0
  595. package/tools/__pycache__/dashboard_generator.cpython-313.pyc +0 -0
  596. package/tools/__pycache__/git_inspector.cpython-313.pyc +0 -0
  597. package/tools/__pycache__/lsp_client.cpython-313.pyc +0 -0
  598. package/tools/__pycache__/lsp_operations.cpython-313.pyc +0 -0
  599. package/tools/__pycache__/pr_generator.cpython-313.pyc +0 -0
  600. package/tools/__pycache__/python_repl.cpython-313.pyc +0 -0
  601. package/tools/__pycache__/python_sandbox.cpython-313.pyc +0 -0
  602. package/tools/__pycache__/session_snapshot.cpython-313.pyc +0 -0
  603. package/tools/__pycache__/ssh_manager.cpython-313.pyc +0 -0
  604. package/tools/__pycache__/theme_engine.cpython-313.pyc +0 -0
  605. package/tools/__pycache__/theme_selector.cpython-313.pyc +0 -0
  606. package/tools/__pycache__/web_search.cpython-313.pyc +0 -0
  607. package/tools/browser_consent.py +289 -0
  608. package/tools/browser_stealth.py +481 -0
  609. package/tools/browser_tool.py +448 -0
  610. package/tools/changelog_generator.py +347 -0
  611. package/tools/commit_splitter.py +749 -0
  612. package/tools/config_discovery.py +151 -0
  613. package/tools/config_merger.py +449 -0
  614. package/tools/dashboard_generator.py +300 -0
  615. package/tools/git_inspector.py +298 -0
  616. package/tools/lsp_client.py +275 -0
  617. package/tools/lsp_discovery.py +231 -0
  618. package/tools/lsp_operations.py +392 -0
  619. package/tools/pr_generator.py +404 -0
  620. package/tools/python_repl.py +712 -0
  621. package/tools/python_sandbox.py +768 -0
  622. package/tools/search_providers/__init__.py +77 -0
  623. package/tools/search_providers/__pycache__/__init__.cpython-313.pyc +0 -0
  624. package/tools/search_providers/__pycache__/brave.cpython-313.pyc +0 -0
  625. package/tools/search_providers/__pycache__/exa.cpython-313.pyc +0 -0
  626. package/tools/search_providers/__pycache__/jina.cpython-313.pyc +0 -0
  627. package/tools/search_providers/__pycache__/perplexity.cpython-313.pyc +0 -0
  628. package/tools/search_providers/__pycache__/synthetic.cpython-313.pyc +0 -0
  629. package/tools/search_providers/brave.py +115 -0
  630. package/tools/search_providers/exa.py +116 -0
  631. package/tools/search_providers/jina.py +104 -0
  632. package/tools/search_providers/perplexity.py +139 -0
  633. package/tools/search_providers/synthetic.py +74 -0
  634. package/tools/session_snapshot.py +851 -0
  635. package/tools/ssh_manager.py +912 -0
  636. package/tools/theme_engine.py +296 -0
  637. package/tools/theme_selector.py +137 -0
  638. package/tools/web_search.py +675 -0
package/OMG-setup.sh ADDED
@@ -0,0 +1,2549 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ CLAUDE_DIR="${CLAUDE_CONFIG_DIR:-$HOME/.claude}"
7
+ BACKUP_TS="$(date +%Y%m%d_%H%M%S)"
8
+ BACKUP_DIR="$CLAUDE_DIR/.omg-backup-$BACKUP_TS"
9
+ VERSION="2.2.11"
10
+
11
+ PLUGIN_NAME="omg"
12
+ PLUGIN_MARKETPLACE="omg"
13
+ LEGACY_PLUGIN_MARKETPLACE="oh-advanced-layer"
14
+ PLUGIN_REF="${PLUGIN_NAME}@${PLUGIN_MARKETPLACE}"
15
+ LEGACY_PLUGIN_REF="${PLUGIN_NAME}@${LEGACY_PLUGIN_MARKETPLACE}"
16
+ PLUGIN_CACHE_DIR="$CLAUDE_DIR/plugins/cache/$PLUGIN_MARKETPLACE/$PLUGIN_NAME"
17
+ LEGACY_PLUGIN_CACHE_DIR="$CLAUDE_DIR/plugins/cache/$LEGACY_PLUGIN_MARKETPLACE/$PLUGIN_NAME"
18
+ PLUGIN_BUNDLE_MARKER_FILE=".omg-plugin-bundle"
19
+
20
+ ACTION="install"
21
+ ACTION_EXPLICIT=false
22
+ DRY_RUN=false
23
+ NON_INTERACTIVE=false
24
+ MERGE_POLICY="ask"
25
+ FRESH_INSTALL=false
26
+ INSTALL_AS_PLUGIN=false
27
+ USE_SYMLINK=false
28
+ ENABLE_BROWSER=false
29
+ VERIFY_CLEAN=false
30
+ REPAIR=false
31
+ ADOPTION_MODE="omg-only"
32
+ ADOPT_MODE="auto"
33
+ OMG_PRESET="safe"
34
+ ERRORS=0
35
+ OMG_MANIFEST="$CLAUDE_DIR/.omg-manifest"
36
+ NEW_MANIFEST_ENTRIES=()
37
+
38
+ V3_RULES=(
39
+ "00-truth-evidence.md" "01-enforcement-map.md" "02-doc-check.md"
40
+ "03-working-memory.md" "04-quality-gate.md" "05-structured-reports.md"
41
+ "06-infra-safety.md" "07-cross-model.md" "08-big-picture.md"
42
+ "09-surgical-changes.md" "10-code-simplifier.md" "11-dependency-safety.md"
43
+ "12-circuit-breaker.md" "13-planning-checklist.md" "14-auto-commands.md"
44
+ "15-context-management.md" "16-honest-testing.md" "17-ensemble-collaboration.md"
45
+ "18-collaborative-solving.md" "19-outside-in.md" "20-project-identity.md"
46
+ "21-verified-claims.md" "22-auto-plugin-mcp.md"
47
+ )
48
+ V3_AGENTS_REMOVE=(cross-validator.md dependency-guardian.md infra-guardian.md perf-analyst.md ui-reviewer.md)
49
+ OLD_OMG_AGENTS=(architect.md critic.md executor.md qa-tester.md escalation-router.md)
50
+ V3_COMMANDS_REMOVE=(cross-review.md simplify.md)
51
+ V4_COMMANDS_REMOVE=(
52
+ code-review.md deep-plan.md domain-init.md escalate.md handoff.md
53
+ health-check.md learn.md project-init.md security-review.md
54
+ )
55
+
56
+ # Dynamic hook discovery — no hardcoded list.
57
+ # Used by remove_omg_files() as fallback when manifest is absent.
58
+ build_omg_hooks_list() {
59
+ OMG_HOOKS=()
60
+ for f in "$SCRIPT_DIR"/hooks/*.py; do
61
+ [ -f "$f" ] && OMG_HOOKS+=("$(basename "$f")")
62
+ done
63
+ }
64
+
65
+ usage() {
66
+ cat <<EOF
67
+ OMG Setup Manager
68
+
69
+ Usage:
70
+ ./OMG-setup.sh <action> [OPTIONS]
71
+ ./OMG-setup.sh [OPTIONS] # defaults to install
72
+ ./OMG-setup.sh # interactive menu in terminal mode
73
+
74
+ Actions:
75
+ install Install or upgrade OMG components
76
+ update Alias of install (explicit update mode)
77
+ reinstall Clean reinstall (remove OMG files, then install)
78
+ uninstall Remove OMG-managed files from ~/.claude
79
+
80
+ Options:
81
+ --fresh For install/update: clean reinstall before install
82
+ --symlink Use symlinks instead of copies (dev mode - live updates)
83
+ --install-as-plugin
84
+ Install plugin bundle (plugin.json + MCP + HUD) together
85
+ --dry-run Show what would happen without writing files
86
+ --non-interactive Skip prompts (CI/automation mode)
87
+ --merge-policy=X Settings merge: ask (default), apply, skip
88
+ --mode=omg-only|coexist
89
+ Native OMG adoption mode for overlapping ecosystems
90
+ --adopt=auto Detect OMG-adjacent ecosystems during install/update
91
+ --preset=safe|balanced|interop|labs|buffet|production
92
+ User-facing preset for managed OMG features
93
+ --enable-browser Enable optional OMG browser capability metadata and guidance
94
+ --verify-clean After uninstall, verify no OMG-managed residue remains
95
+ --repair With --verify-clean, back up and remove owned residue
96
+ -h, --help Show this help
97
+
98
+ Examples:
99
+ ./OMG-setup.sh install
100
+ ./OMG-setup.sh install --symlink # Dev mode: live updates from repo
101
+ ./OMG-setup.sh install --install-as-plugin
102
+ ./OMG-setup.sh install --mode=coexist --preset=interop
103
+ ./OMG-setup.sh update --non-interactive --merge-policy=apply
104
+ bunx @trac3r/oh-my-god
105
+ ./OMG-setup.sh reinstall --dry-run
106
+ ./OMG-setup.sh uninstall --dry-run
107
+ EOF
108
+ }
109
+
110
+ is_standalone_installed() {
111
+ [ -f "$CLAUDE_DIR/hooks/.omg-version" ] || [ -d "$CLAUDE_DIR/omg-runtime" ]
112
+ }
113
+
114
+ is_plugin_installed() {
115
+ local marker_new="$PLUGIN_CACHE_DIR/$PLUGIN_BUNDLE_MARKER_FILE"
116
+ local marker_legacy="$LEGACY_PLUGIN_CACHE_DIR/$PLUGIN_BUNDLE_MARKER_FILE"
117
+ if [ -f "$marker_new" ] || [ -f "$marker_legacy" ]; then
118
+ return 0
119
+ fi
120
+ local installed_plugins="$CLAUDE_DIR/plugins/installed_plugins.json"
121
+ if [ -f "$installed_plugins" ] && grep -Eq "\"$PLUGIN_REF\"|\"$LEGACY_PLUGIN_REF\"" "$installed_plugins" 2>/dev/null; then
122
+ return 0
123
+ fi
124
+ return 1
125
+ }
126
+
127
+ prompt_start_action() {
128
+ if $ACTION_EXPLICIT || $NON_INTERACTIVE || $DRY_RUN; then
129
+ return 0
130
+ fi
131
+
132
+ local standalone_installed=false
133
+ local plugin_installed=false
134
+ local anything_installed=false
135
+ is_standalone_installed && standalone_installed=true
136
+ is_plugin_installed && plugin_installed=true
137
+ if $standalone_installed || $plugin_installed; then
138
+ anything_installed=true
139
+ fi
140
+
141
+ echo ""
142
+ echo "Select OMG setup action:"
143
+ echo " 1. Install standalone"
144
+ if $standalone_installed; then
145
+ echo " 2. Update standalone"
146
+ fi
147
+ echo " 3. Install as plugin"
148
+ if $plugin_installed; then
149
+ echo " 4. Update plugin install"
150
+ fi
151
+ if $anything_installed; then
152
+ echo " 5. Uninstall"
153
+ fi
154
+ echo " 0. Cancel"
155
+ echo ""
156
+
157
+ read -p "Choose [1/2/3/4/5/0]: " -r
158
+ case "${REPLY:-}" in
159
+ 1)
160
+ ACTION="install"
161
+ INSTALL_AS_PLUGIN=false
162
+ ;;
163
+ 2)
164
+ if $standalone_installed; then
165
+ ACTION="update"
166
+ INSTALL_AS_PLUGIN=false
167
+ else
168
+ echo "Standalone update unavailable (not installed)."
169
+ exit 1
170
+ fi
171
+ ;;
172
+ 3)
173
+ ACTION="install"
174
+ INSTALL_AS_PLUGIN=true
175
+ ;;
176
+ 4)
177
+ if $plugin_installed; then
178
+ ACTION="update"
179
+ INSTALL_AS_PLUGIN=true
180
+ else
181
+ echo "Plugin update unavailable (plugin install not detected)."
182
+ exit 1
183
+ fi
184
+ ;;
185
+ 5)
186
+ if $anything_installed; then
187
+ ACTION="uninstall"
188
+ else
189
+ echo "Uninstall unavailable (nothing installed)."
190
+ exit 1
191
+ fi
192
+ ;;
193
+ 0)
194
+ echo "Cancelled by user."
195
+ exit 0
196
+ ;;
197
+ *)
198
+ echo "Invalid selection."
199
+ exit 1
200
+ ;;
201
+ esac
202
+ }
203
+
204
+ parse_args() {
205
+ if [ $# -gt 0 ]; then
206
+ case "$1" in
207
+ install|update|reinstall|uninstall)
208
+ ACTION="$1"
209
+ ACTION_EXPLICIT=true
210
+ shift
211
+ ;;
212
+ help|-h|--help)
213
+ usage
214
+ exit 0
215
+ ;;
216
+ esac
217
+ fi
218
+
219
+ for arg in "$@"; do
220
+ case "$arg" in
221
+ --dry-run) DRY_RUN=true ;;
222
+ --symlink) USE_SYMLINK=true ;;
223
+ --non-interactive) NON_INTERACTIVE=true ;;
224
+ --fresh) FRESH_INSTALL=true ;;
225
+ --install-as-plugin) INSTALL_AS_PLUGIN=true ;;
226
+ --enable-browser) ENABLE_BROWSER=true ;;
227
+ --verify-clean) VERIFY_CLEAN=true ;;
228
+ --repair) REPAIR=true ;;
229
+ --merge-policy=*) MERGE_POLICY="${arg#*=}" ;;
230
+ --mode=*) ADOPTION_MODE="${arg#*=}" ;;
231
+ --adopt=*) ADOPT_MODE="${arg#*=}" ;;
232
+ --preset=*) OMG_PRESET="${arg#*=}" ;;
233
+ --help|-h)
234
+ usage
235
+ exit 0
236
+ ;;
237
+ *)
238
+ echo "Unknown option: $arg"
239
+ echo ""
240
+ usage
241
+ exit 1
242
+ ;;
243
+ esac
244
+ done
245
+
246
+ if [ "$ACTION" = "reinstall" ]; then
247
+ FRESH_INSTALL=true
248
+ fi
249
+
250
+ if [ ! -t 0 ]; then
251
+ NON_INTERACTIVE=true
252
+ fi
253
+
254
+ # Auto-enable plugin mode for npm/bunx installs
255
+ if [ -n "${npm_execpath:-}" ] || [ -n "${npm_lifecycle_event:-}" ] || [ -n "${BUN_INSTALL:-}" ]; then
256
+ INSTALL_AS_PLUGIN=true
257
+ fi
258
+
259
+ case "$ADOPTION_MODE" in
260
+ omg-only|coexist) ;;
261
+ *)
262
+ echo "Unknown adoption mode: $ADOPTION_MODE"
263
+ exit 1
264
+ ;;
265
+ esac
266
+
267
+ case "$ADOPT_MODE" in
268
+ auto) ;;
269
+ *)
270
+ echo "Unknown adoption detector mode: $ADOPT_MODE"
271
+ exit 1
272
+ ;;
273
+ esac
274
+
275
+ case "$OMG_PRESET" in
276
+ plugins-first)
277
+ OMG_PRESET="interop"
278
+ ;;
279
+ safe|balanced|interop|labs|buffet|production) ;;
280
+ *)
281
+ echo "Unknown OMG preset: $OMG_PRESET"
282
+ exit 1
283
+ ;;
284
+ esac
285
+ }
286
+
287
+ preflight() {
288
+ echo "Pre-flight checks..."
289
+ if ! command -v python3 &>/dev/null; then
290
+ echo " ❌ python3 not found. Install: https://www.python.org/downloads/"
291
+ exit 1
292
+ fi
293
+ local py_ver py_maj py_min
294
+ py_ver=$(python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')")
295
+ py_maj=$(echo "$py_ver" | cut -d. -f1)
296
+ py_min=$(echo "$py_ver" | cut -d. -f2)
297
+ if [ "$py_maj" -lt 3 ] || { [ "$py_maj" -eq 3 ] && [ "$py_min" -lt 10 ]; }; then
298
+ echo " ❌ Python $py_ver is not supported. OMG requires Python 3.10 or newer."
299
+ echo " Python 3.8 and 3.9 are unsupported because OMG depends on"
300
+ echo " libraries (fastmcp >=2.0) that require Python >= 3.10."
301
+ echo " Upgrade at: https://www.python.org/downloads/"
302
+ exit 1
303
+ fi
304
+ echo " ✓ Python $py_ver"
305
+ }
306
+
307
+
308
+ verify_install_integrity() {
309
+ local manifest="$SCRIPT_DIR/INSTALL_INTEGRITY.sha256"
310
+
311
+ if [ ! -f "$manifest" ]; then
312
+ echo " ~ No integrity manifest found (skipping hash verification)"
313
+ return 0
314
+ fi
315
+
316
+ echo " Verifying install integrity..."
317
+ local failures=0
318
+ local checked=0
319
+
320
+ while IFS= read -r line; do
321
+ # Skip empty lines and comments
322
+ [ -z "$line" ] && continue
323
+ [[ "$line" == "#"* ]] && continue
324
+
325
+ local expected_hash file_path
326
+ expected_hash=$(echo "$line" | awk '{print $1}')
327
+ file_path=$(echo "$line" | awk '{print $2}')
328
+
329
+ [ -z "$expected_hash" ] || [ -z "$file_path" ] && continue
330
+
331
+ local full_path="$SCRIPT_DIR/$file_path"
332
+ if [ ! -f "$full_path" ]; then
333
+ echo " ❌ INTEGRITY FAILURE: $file_path — file not found"
334
+ echo " Expected at: $full_path"
335
+ echo " Action: Re-download or re-clone the OMG source."
336
+ failures=$((failures + 1))
337
+ continue
338
+ fi
339
+
340
+ local actual_hash
341
+ if command -v shasum &>/dev/null; then
342
+ actual_hash=$(shasum -a 256 "$full_path" | awk '{print $1}')
343
+ elif command -v sha256sum &>/dev/null; then
344
+ actual_hash=$(sha256sum "$full_path" | awk '{print $1}')
345
+ else
346
+ echo " ❌ Cannot verify integrity: neither shasum nor sha256sum found"
347
+ echo " Action: Install coreutils or ensure shasum is available."
348
+ exit 1
349
+ fi
350
+
351
+ if [ "$actual_hash" != "$expected_hash" ]; then
352
+ echo " ❌ INTEGRITY FAILURE: $file_path — hash mismatch"
353
+ echo " Expected: $expected_hash"
354
+ echo " Actual: $actual_hash"
355
+ echo " Action: Re-download or re-clone the OMG source."
356
+ failures=$((failures + 1))
357
+ fi
358
+ checked=$((checked + 1))
359
+ done < "$manifest"
360
+
361
+ if [ $failures -gt 0 ]; then
362
+ echo ""
363
+ echo " ❌ Install integrity check FAILED ($failures file(s) corrupted or missing)"
364
+ echo " The installer source does not match the expected integrity manifest."
365
+ echo " This may indicate a corrupted download, incomplete clone, or tampering."
366
+ echo ""
367
+ echo " To fix:"
368
+ echo " 1. Re-clone: git clone https://github.com/anthropics/omg.git"
369
+ echo " 2. Or re-download from the official release page"
370
+ echo " 3. Then re-run: ./OMG-setup.sh install"
371
+ exit 1
372
+ fi
373
+
374
+ echo " ✓ Install integrity verified ($checked file(s) checked)"
375
+ }
376
+
377
+ provision_managed_venv() {
378
+ local venv_dir="$CLAUDE_DIR/omg-runtime/.venv"
379
+ local package_spec="${SCRIPT_DIR}[mcp]"
380
+
381
+ if [ ! -f "$venv_dir/bin/python" ]; then
382
+ if [ -n "${OMG_SETUP_PREWARMED_VENV:-}" ] && [ -d "${OMG_SETUP_PREWARMED_VENV:-}" ]; then
383
+ mkdir -p "$(dirname "$venv_dir")"
384
+ ln -s "$OMG_SETUP_PREWARMED_VENV" "$venv_dir" || {
385
+ echo " ⚠ Could not link prewarmed managed venv (continuing with local venv creation)"
386
+ python3 -m venv "$venv_dir" || {
387
+ echo " ⚠ Could not create managed venv (continuing without it)"
388
+ return 0
389
+ }
390
+ }
391
+ else
392
+ python3 -m venv "$venv_dir" || {
393
+ echo " ⚠ Could not create managed venv (continuing without it)"
394
+ return 0
395
+ }
396
+ fi
397
+ fi
398
+
399
+ # Tests may provide a prebuilt wheel to avoid rebuilding the local package for
400
+ # every managed-runtime install. Symlink mode intentionally keeps editable install semantics.
401
+ if ! $USE_SYMLINK && [ -n "${OMG_SETUP_PACKAGE_SPEC:-}" ]; then
402
+ package_spec="$OMG_SETUP_PACKAGE_SPEC"
403
+ fi
404
+
405
+ if $USE_SYMLINK; then
406
+ "$venv_dir/bin/pip" install --quiet -e "${SCRIPT_DIR}[mcp]" 2>/dev/null || true
407
+ else
408
+ "$venv_dir/bin/pip" install --quiet "$package_spec" 2>/dev/null || true
409
+ fi
410
+
411
+ echo " ✓ Managed venv → $venv_dir"
412
+ }
413
+
414
+ write_managed_mcp_launcher() {
415
+ local launcher_path="$CLAUDE_DIR/omg-runtime/bin/omg-mcp-server.py"
416
+ local runtime_root="$CLAUDE_DIR/omg-runtime"
417
+
418
+ mkdir -p "$(dirname "$launcher_path")"
419
+ python3 - "$launcher_path" "$runtime_root" <<'PY'
420
+ import json
421
+ import sys
422
+ from pathlib import Path
423
+
424
+ launcher_path = Path(sys.argv[1])
425
+ runtime_root = Path(sys.argv[2])
426
+
427
+ content = f"""#!/usr/bin/env python3
428
+ from __future__ import annotations
429
+
430
+ import runpy
431
+ import sys
432
+ from pathlib import Path
433
+
434
+ RUNTIME_ROOT = Path({json.dumps(str(runtime_root))})
435
+ if str(RUNTIME_ROOT) not in sys.path:
436
+ sys.path.insert(0, str(RUNTIME_ROOT))
437
+
438
+ if __name__ == "__main__":
439
+ runpy.run_module("runtime.omg_mcp_server", run_name="__main__")
440
+ """
441
+
442
+ launcher_path.write_text(content, encoding="utf-8")
443
+ launcher_path.chmod(0o755)
444
+ PY
445
+ }
446
+
447
+ patch_omg_control_mcp_python() {
448
+ local venv_python="$CLAUDE_DIR/omg-runtime/.venv/bin/python"
449
+ local launcher_path="$CLAUDE_DIR/omg-runtime/bin/omg-mcp-server.py"
450
+ local mcp_paths=(
451
+ "$CLAUDE_DIR/.mcp.json"
452
+ "$PLUGIN_CACHE_DIR/$VERSION/.claude-plugin/mcp.json"
453
+ "$PLUGIN_CACHE_DIR/$VERSION/.mcp.json"
454
+ )
455
+
456
+ python3 - "$venv_python" "$launcher_path" "${mcp_paths[@]}" <<'PY'
457
+ import json
458
+ import sys
459
+ from pathlib import Path
460
+
461
+ venv_python = sys.argv[1]
462
+ launcher_path = sys.argv[2]
463
+
464
+ for raw_path in sys.argv[3:]:
465
+ mcp_path = Path(raw_path)
466
+ if not mcp_path.exists():
467
+ continue
468
+ try:
469
+ data = json.loads(mcp_path.read_text(encoding="utf-8"))
470
+ except Exception:
471
+ continue
472
+
473
+ servers = data.get("mcpServers")
474
+ if not isinstance(servers, dict):
475
+ continue
476
+
477
+ omg_control = servers.get("omg-control")
478
+ if isinstance(omg_control, dict):
479
+ omg_control["command"] = venv_python
480
+ omg_control["args"] = [launcher_path]
481
+ mcp_path.write_text(json.dumps(data, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
482
+ PY
483
+ }
484
+
485
+ prune_plugin_duplicate_mcp_from_settings() {
486
+ local mcp_path="$CLAUDE_DIR/.mcp.json"
487
+ local plugin_mcp_path="$PLUGIN_CACHE_DIR/$VERSION/.claude-plugin/mcp.json"
488
+
489
+ if [ ! -f "$plugin_mcp_path" ]; then
490
+ plugin_mcp_path="$PLUGIN_CACHE_DIR/$VERSION/.mcp.json"
491
+ fi
492
+
493
+ if [ ! -f "$mcp_path" ] || [ ! -f "$plugin_mcp_path" ]; then
494
+ return 0
495
+ fi
496
+
497
+ python3 - "$mcp_path" "$plugin_mcp_path" <<'PY'
498
+ import json
499
+ import sys
500
+ from pathlib import Path
501
+
502
+ mcp_path = Path(sys.argv[1])
503
+ plugin_mcp_path = Path(sys.argv[2])
504
+
505
+ try:
506
+ data = json.loads(mcp_path.read_text(encoding="utf-8"))
507
+ plugin_data = json.loads(plugin_mcp_path.read_text(encoding="utf-8"))
508
+ except Exception:
509
+ print("0")
510
+ raise SystemExit(0)
511
+
512
+ servers = data.get("mcpServers")
513
+ plugin_servers = plugin_data.get("mcpServers")
514
+ if not isinstance(servers, dict) or not isinstance(plugin_servers, dict):
515
+ print("0")
516
+ raise SystemExit(0)
517
+
518
+ removed = 0
519
+ for key, plugin_value in plugin_servers.items():
520
+ if key in servers and servers.get(key) == plugin_value:
521
+ servers.pop(key, None)
522
+ removed += 1
523
+
524
+ data["mcpServers"] = servers
525
+ mcp_path.write_text(json.dumps(data, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
526
+ print(str(removed))
527
+ PY
528
+ }
529
+
530
+ prune_legacy_plugin_mcp_from_settings() {
531
+ local mcp_path="$CLAUDE_DIR/.mcp.json"
532
+ local venv_python="$CLAUDE_DIR/omg-runtime/.venv/bin/python"
533
+
534
+ if [ ! -f "$mcp_path" ]; then
535
+ return 0
536
+ fi
537
+
538
+ python3 - "$mcp_path" "$venv_python" <<'PY'
539
+ import json
540
+ import sys
541
+ from pathlib import Path
542
+
543
+ mcp_path = Path(sys.argv[1])
544
+ venv_python = sys.argv[2]
545
+
546
+ try:
547
+ data = json.loads(mcp_path.read_text(encoding="utf-8"))
548
+ except Exception:
549
+ print("0")
550
+ raise SystemExit(0)
551
+
552
+ servers = data.get("mcpServers")
553
+ if not isinstance(servers, dict):
554
+ print("0")
555
+ raise SystemExit(0)
556
+
557
+ removed = 0
558
+
559
+ omg_control = servers.get("omg-control")
560
+ if isinstance(omg_control, dict):
561
+ command = omg_control.get("command")
562
+ args = omg_control.get("args")
563
+ if isinstance(args, list) and args == ["-m", "runtime.omg_mcp_server"] and command in {
564
+ "python",
565
+ "python3",
566
+ venv_python,
567
+ }:
568
+ servers.pop("omg-control", None)
569
+ removed += 1
570
+
571
+ data["mcpServers"] = servers
572
+ mcp_path.write_text(json.dumps(data, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
573
+ print(str(removed))
574
+ PY
575
+ }
576
+
577
+ configure_hud_status_line() {
578
+ local settings_path="$CLAUDE_DIR/settings.json"
579
+ local hud_path="$CLAUDE_DIR/hud/omg-hud.mjs"
580
+
581
+ python3 - "$settings_path" "$hud_path" <<'PY'
582
+ import json
583
+ import sys
584
+ from pathlib import Path
585
+
586
+ settings_path = Path(sys.argv[1])
587
+ hud_path = Path(sys.argv[2])
588
+ desired_command = f'node "{hud_path}"'
589
+
590
+ settings = {}
591
+ if settings_path.exists():
592
+ try:
593
+ settings = json.loads(settings_path.read_text(encoding="utf-8"))
594
+ except Exception:
595
+ settings = {}
596
+ if not isinstance(settings, dict):
597
+ settings = {}
598
+
599
+ status_line = settings.get("statusLine")
600
+ if not isinstance(status_line, dict) or not status_line:
601
+ settings["statusLine"] = {
602
+ "type": "command",
603
+ "command": desired_command,
604
+ }
605
+ elif "omg-hud.mjs" in str(status_line.get("command") or ""):
606
+ padding = status_line.get("padding")
607
+ settings["statusLine"] = {
608
+ "type": "command",
609
+ "command": desired_command,
610
+ }
611
+ if isinstance(padding, (int, float)):
612
+ settings["statusLine"]["padding"] = padding
613
+
614
+ settings_path.parent.mkdir(parents=True, exist_ok=True)
615
+ settings_path.write_text(json.dumps(settings, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
616
+ PY
617
+ }
618
+
619
+ remove_hud_status_line() {
620
+ local settings_path="$CLAUDE_DIR/settings.json"
621
+
622
+ if [ ! -f "$settings_path" ]; then
623
+ return 0
624
+ fi
625
+
626
+ python3 - "$settings_path" <<'PY'
627
+ import json
628
+ import sys
629
+ from pathlib import Path
630
+
631
+ settings_path = Path(sys.argv[1])
632
+ try:
633
+ settings = json.loads(settings_path.read_text(encoding="utf-8"))
634
+ except Exception:
635
+ raise SystemExit(0)
636
+ if not isinstance(settings, dict):
637
+ raise SystemExit(0)
638
+
639
+ status_line = settings.get("statusLine")
640
+ if isinstance(status_line, dict) and "omg-hud.mjs" in str(status_line.get("command") or ""):
641
+ settings.pop("statusLine", None)
642
+ settings_path.write_text(json.dumps(settings, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
643
+ PY
644
+ }
645
+
646
+ remove_omg_hooks_from_settings() {
647
+ local settings_path="$CLAUDE_DIR/settings.json"
648
+ local hooks_dir="$CLAUDE_DIR/hooks"
649
+ if [ ! -f "$settings_path" ]; then
650
+ return 0
651
+ fi
652
+ if ! command -v python3 &>/dev/null; then
653
+ return 0
654
+ fi
655
+ python3 - "$settings_path" "$hooks_dir" <<'PY'
656
+ import json, sys
657
+ from pathlib import Path
658
+ settings_path = Path(sys.argv[1])
659
+ hooks_dir = sys.argv[2]
660
+ try:
661
+ settings = json.loads(settings_path.read_text(encoding="utf-8"))
662
+ except Exception:
663
+ raise SystemExit(0)
664
+ if not isinstance(settings, dict):
665
+ raise SystemExit(0)
666
+ hooks_section = settings.get("hooks")
667
+ if not isinstance(hooks_section, dict):
668
+ raise SystemExit(0)
669
+ def is_omg_cmd(cmd):
670
+ if not isinstance(cmd, str):
671
+ return False
672
+ return "omg-runtime" in cmd or (hooks_dir + "/") in cmd
673
+ def entry_is_omg(entry):
674
+ if not isinstance(entry, dict):
675
+ return False
676
+ nested = entry.get("hooks")
677
+ if isinstance(nested, list) and nested:
678
+ return all(isinstance(h, dict) and is_omg_cmd(h.get("command", "")) for h in nested)
679
+ return is_omg_cmd(entry.get("command", ""))
680
+ changed = False
681
+ new_hooks = {}
682
+ for event, entries in hooks_section.items():
683
+ if not isinstance(entries, list):
684
+ new_hooks[event] = entries
685
+ continue
686
+ filtered = [e for e in entries if not entry_is_omg(e)]
687
+ if len(filtered) < len(entries):
688
+ changed = True
689
+ if filtered:
690
+ new_hooks[event] = filtered
691
+ if changed:
692
+ if new_hooks:
693
+ settings["hooks"] = new_hooks
694
+ else:
695
+ settings.pop("hooks", None)
696
+ settings_path.write_text(json.dumps(settings, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
697
+ PY
698
+ }
699
+
700
+ remove_omg_metadata_from_settings() {
701
+ local settings_path="$CLAUDE_DIR/settings.json"
702
+ if [ ! -f "$settings_path" ]; then
703
+ return 0
704
+ fi
705
+ if ! command -v python3 &>/dev/null; then
706
+ return 0
707
+ fi
708
+ python3 - "$settings_path" <<'PY'
709
+ import json
710
+ import sys
711
+ from pathlib import Path
712
+
713
+ settings_path = Path(sys.argv[1])
714
+ try:
715
+ settings = json.loads(settings_path.read_text(encoding="utf-8"))
716
+ except Exception:
717
+ raise SystemExit(0)
718
+ if not isinstance(settings, dict):
719
+ raise SystemExit(0)
720
+
721
+ changed = False
722
+ if "_omg" in settings:
723
+ settings.pop("_omg", None)
724
+ changed = True
725
+
726
+ if changed:
727
+ settings_path.write_text(json.dumps(settings, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
728
+ PY
729
+ }
730
+
731
+ remove_codex_managed_residue() {
732
+ local codex_dir="${HOME:-$HOME}/.codex"
733
+ [ -d "$codex_dir" ] || return 0
734
+
735
+ rm -rf "$codex_dir/.omg"
736
+ rm -f "$codex_dir/bin/omg-codex-hud"
737
+ rm -f "$codex_dir/hud/omg-codex-hud.py"
738
+
739
+ if [ -d "$codex_dir/skills" ]; then
740
+ while IFS= read -r skill_dir; do
741
+ [ -f "$skill_dir/.omg-managed-skill" ] || continue
742
+ rm -rf "$skill_dir"
743
+ done < <(find "$codex_dir/skills" -maxdepth 1 -mindepth 1 -type d -name 'omg-*' 2>/dev/null | sort)
744
+ fi
745
+ }
746
+
747
+ emit_uninstall_receipt() {
748
+ local receipt_path="$CLAUDE_DIR/.omg-uninstall-receipt.json"
749
+ local removed_paths_json="${1:-[]}"
750
+ local preserved_paths_json="${2:-[]}"
751
+ local host_configs_json="${3:-[]}"
752
+ if ! command -v python3 &>/dev/null; then
753
+ return 0
754
+ fi
755
+ python3 - "$receipt_path" "$VERSION" "$removed_paths_json" "$preserved_paths_json" "$host_configs_json" <<'PY'
756
+ import json, sys
757
+ from datetime import datetime, timezone
758
+ from pathlib import Path
759
+ receipt_path = Path(sys.argv[1])
760
+ version = sys.argv[2]
761
+ try:
762
+ removed_paths = json.loads(sys.argv[3])
763
+ except Exception:
764
+ removed_paths = []
765
+ try:
766
+ preserved_paths = json.loads(sys.argv[4])
767
+ except Exception:
768
+ preserved_paths = []
769
+ try:
770
+ host_configs_cleaned = json.loads(sys.argv[5])
771
+ except Exception:
772
+ host_configs_cleaned = []
773
+ receipt = {
774
+ "schema": "UninstallReceipt",
775
+ "timestamp": datetime.now(timezone.utc).isoformat(),
776
+ "version": version,
777
+ "removed_paths": removed_paths,
778
+ "preserved_paths": preserved_paths,
779
+ "host_configs_cleaned": host_configs_cleaned,
780
+ "status": "ok",
781
+ }
782
+ receipt_path.parent.mkdir(parents=True, exist_ok=True)
783
+ receipt_path.write_text(json.dumps(receipt, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
784
+ PY
785
+ }
786
+
787
+ ensure_backup() {
788
+ if ! $DRY_RUN; then
789
+ mkdir -p "$BACKUP_DIR"
790
+ for dir in rules hooks agents commands; do
791
+ [ -d "$CLAUDE_DIR/$dir" ] && cp -r "$CLAUDE_DIR/$dir" "$BACKUP_DIR/$dir" 2>/dev/null || true
792
+ done
793
+ [ -f "$CLAUDE_DIR/settings.json" ] && cp "$CLAUDE_DIR/settings.json" "$BACKUP_DIR/"
794
+ prune_old_backups
795
+ fi
796
+ }
797
+
798
+ prune_old_backups() {
799
+ local backups=()
800
+ while IFS= read -r path; do
801
+ backups+=("$path")
802
+ done < <(find "$CLAUDE_DIR" -maxdepth 1 -type d -name ".omg-backup-*" | sort)
803
+
804
+ local total=${#backups[@]}
805
+ if [ "$total" -le 2 ]; then
806
+ return 0
807
+ fi
808
+
809
+ local remove_count=$((total - 2))
810
+ for old in "${backups[@]:0:$remove_count}"; do
811
+ [[ "$old" == "$CLAUDE_DIR"/* ]] || {
812
+ echo "ERROR: backup prune target outside expected directory: $old" >&2
813
+ exit 1
814
+ }
815
+ rm -rf "$old"
816
+ done
817
+ }
818
+
819
+ is_omg_managed_command_file() {
820
+ local file="$1"
821
+ if [ ! -f "$file" ]; then
822
+ return 1
823
+ fi
824
+
825
+ if grep -q "OMG-AUTO-COMPAT-ALIAS" "$file" 2>/dev/null; then
826
+ return 0
827
+ fi
828
+ if grep -q "OMG-MANAGED-COMMAND" "$file" 2>/dev/null; then
829
+ return 0
830
+ fi
831
+
832
+ local base
833
+ base="$(basename "$file")"
834
+ if [[ "$base" == OMG:* ]] && grep -q "/OMG:" "$file" 2>/dev/null; then
835
+ return 0
836
+ fi
837
+ return 1
838
+ }
839
+
840
+ mark_omg_managed_command_file() {
841
+ local file="$1"
842
+ if [ ! -f "$file" ]; then
843
+ return 0
844
+ fi
845
+ if ! grep -q "OMG-MANAGED-COMMAND" "$file" 2>/dev/null; then
846
+ printf "\n<!-- OMG-MANAGED-COMMAND -->\n" >> "$file"
847
+ fi
848
+ }
849
+
850
+ # Install a file or directory - either copy or symlink based on USE_SYMLINK
851
+ # Usage: install_file <source> <target> [type: file|dir]
852
+ install_file() {
853
+ local src="$1"
854
+ local target="$2"
855
+ local type="${3:-file}"
856
+
857
+ if $USE_SYMLINK; then
858
+ # In symlink mode, create symlink from target -> source
859
+ # First remove existing file/dir if present
860
+ if [ -e "$target" ] || [ -L "$target" ]; then
861
+ rm -rf "$target"
862
+ fi
863
+ ln -s "$src" "$target"
864
+ else
865
+ # In copy mode, do regular copy
866
+ if [ "$type" = "dir" ]; then
867
+ cp -R "$src" "$target"
868
+ else
869
+ cp "$src" "$target"
870
+ fi
871
+ fi
872
+ }
873
+
874
+ track_file() {
875
+ NEW_MANIFEST_ENTRIES+=("$1")
876
+ }
877
+
878
+ reconcile_stale_files() {
879
+ if [ ! -f "$OMG_MANIFEST" ]; then
880
+ echo " (no previous manifest — first install, skipping reconciliation)"
881
+ return 0
882
+ fi
883
+ local stale=0
884
+ while IFS= read -r old_entry; do
885
+ [ -n "$old_entry" ] || continue
886
+ [[ "$old_entry" == "#"* ]] && continue
887
+ local found=false
888
+ for new_entry in "${NEW_MANIFEST_ENTRIES[@]}"; do
889
+ if [ "$old_entry" = "$new_entry" ]; then
890
+ found=true
891
+ break
892
+ fi
893
+ done
894
+ if ! $found; then
895
+ local target="$CLAUDE_DIR/$old_entry"
896
+ if [ -f "$target" ]; then
897
+ if ! $DRY_RUN; then
898
+ rm -f "$target"
899
+ fi
900
+ echo " - $old_entry (removed from source)"
901
+ stale=$((stale + 1))
902
+ fi
903
+ fi
904
+ done < "$OMG_MANIFEST"
905
+ if [ $stale -eq 0 ]; then
906
+ echo " (no stale files)"
907
+ elif $DRY_RUN; then
908
+ echo " (dry-run: would remove $stale stale file(s))"
909
+ else
910
+ echo " ✓ Cleaned $stale stale file(s)"
911
+ fi
912
+ }
913
+
914
+ write_omg_manifest() {
915
+ if ! $DRY_RUN; then
916
+ printf '%s\n' "${NEW_MANIFEST_ENTRIES[@]}" | sort > "$OMG_MANIFEST"
917
+ fi
918
+ }
919
+
920
+ configure_browser_capability() {
921
+ local browser_dir="$CLAUDE_DIR/omg-runtime/browser"
922
+ local browser_state_path="$browser_dir/capability.json"
923
+ local browser_command_json="null"
924
+ local browser_status="missing"
925
+
926
+ if command -v playwright >/dev/null 2>&1; then
927
+ browser_command_json='["playwright"]'
928
+ browser_status="ready"
929
+ elif command -v playwright-cli >/dev/null 2>&1; then
930
+ browser_command_json='["playwright-cli"]'
931
+ browser_status="ready"
932
+ elif command -v npx >/dev/null 2>&1; then
933
+ browser_command_json='["npx","playwright"]'
934
+ browser_status="bootstrap-required"
935
+ fi
936
+
937
+ if $DRY_RUN; then
938
+ echo " (would enable browser capability using command: $browser_command_json)"
939
+ return 0
940
+ fi
941
+
942
+ mkdir -p "$browser_dir"
943
+ python3 - "$browser_state_path" "$browser_status" "$browser_command_json" <<'PY'
944
+ import json
945
+ import sys
946
+ from pathlib import Path
947
+
948
+ path = Path(sys.argv[1])
949
+ status = sys.argv[2]
950
+ command_json = sys.argv[3]
951
+ command = json.loads(command_json)
952
+ payload = {
953
+ "enabled": True,
954
+ "status": status,
955
+ "command": command,
956
+ "remediation": "Use `npx playwright` or install `@playwright/cli`, then install browsers before running /OMG:browser.",
957
+ }
958
+ path.write_text(json.dumps(payload, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
959
+ PY
960
+ track_file "omg-runtime/browser/capability.json"
961
+ echo " ✓ Browser capability enabled"
962
+ }
963
+
964
+ prune_plugin_mcp_from_settings() {
965
+ local mcp_path="$CLAUDE_DIR/.mcp.json"
966
+ if [ ! -f "$mcp_path" ]; then
967
+ return 0
968
+ fi
969
+ python3 - "$mcp_path" <<'PY'
970
+ import json
971
+ import sys
972
+ from pathlib import Path
973
+
974
+ path = Path(sys.argv[1])
975
+ try:
976
+ data = json.loads(path.read_text(encoding="utf-8"))
977
+ except Exception:
978
+ print("0")
979
+ raise SystemExit(0)
980
+
981
+ servers = data.get("mcpServers")
982
+ if not isinstance(servers, dict):
983
+ print("0")
984
+ raise SystemExit(0)
985
+
986
+ removed = 0
987
+ for key in ("context7", "filesystem", "websearch", "chrome-devtools"):
988
+ if key in servers:
989
+ servers.pop(key, None)
990
+ removed += 1
991
+
992
+ if removed:
993
+ path.write_text(json.dumps(data, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
994
+
995
+ print(str(removed))
996
+ PY
997
+ }
998
+
999
+ merge_plugin_mcp_into_settings() {
1000
+ local mcp_path="$CLAUDE_DIR/.mcp.json"
1001
+ local source_mcp_path="$SCRIPT_DIR/.mcp.json"
1002
+ python3 - "$mcp_path" "$source_mcp_path" <<'PY'
1003
+ import json
1004
+ import sys
1005
+ from pathlib import Path
1006
+
1007
+ mcp_path = Path(sys.argv[1])
1008
+ source_mcp_path = Path(sys.argv[2])
1009
+
1010
+ mcp_config = {}
1011
+ if mcp_path.exists():
1012
+ try:
1013
+ mcp_config = json.loads(mcp_path.read_text(encoding="utf-8"))
1014
+ except Exception:
1015
+ mcp_config = {}
1016
+ if not isinstance(mcp_config, dict):
1017
+ mcp_config = {}
1018
+
1019
+ try:
1020
+ source_mcp = json.loads(source_mcp_path.read_text(encoding="utf-8"))
1021
+ except Exception:
1022
+ source_mcp = {}
1023
+
1024
+ incoming = source_mcp.get("mcpServers") if isinstance(source_mcp, dict) else {}
1025
+ if not isinstance(incoming, dict):
1026
+ incoming = {}
1027
+
1028
+ servers = mcp_config.get("mcpServers")
1029
+ if not isinstance(servers, dict):
1030
+ servers = {}
1031
+ for key, value in incoming.items():
1032
+ servers[key] = value
1033
+ mcp_config["mcpServers"] = servers
1034
+
1035
+ mcp_path.parent.mkdir(parents=True, exist_ok=True)
1036
+ mcp_path.write_text(json.dumps(mcp_config, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
1037
+ print(str(len(incoming)))
1038
+ PY
1039
+ }
1040
+
1041
+ write_plugin_mcp_file() {
1042
+ local target_path="$1"
1043
+ local source_mcp_path="$SCRIPT_DIR/.claude-plugin/mcp.json"
1044
+ if [ ! -f "$source_mcp_path" ]; then
1045
+ source_mcp_path="$SCRIPT_DIR/.mcp.json"
1046
+ fi
1047
+ python3 - "$target_path" "$source_mcp_path" <<'PY'
1048
+ import json
1049
+ import sys
1050
+ from pathlib import Path
1051
+
1052
+ target_path = Path(sys.argv[1])
1053
+ source_mcp_path = Path(sys.argv[2])
1054
+
1055
+ try:
1056
+ source_mcp = json.loads(source_mcp_path.read_text(encoding="utf-8"))
1057
+ except Exception:
1058
+ source_mcp = {}
1059
+
1060
+ mcp_servers = source_mcp.get("mcpServers") if isinstance(source_mcp, dict) else {}
1061
+ if not isinstance(mcp_servers, dict):
1062
+ mcp_servers = {}
1063
+
1064
+ # Plugin installs should only publish OMG-specific MCP servers.
1065
+ # Generic servers such as filesystem often already exist at project scope.
1066
+ mcp_servers = {
1067
+ key: value
1068
+ for key, value in mcp_servers.items()
1069
+ if key == "omg-control"
1070
+ }
1071
+
1072
+ payload = {"mcpServers": mcp_servers}
1073
+ target_path.parent.mkdir(parents=True, exist_ok=True)
1074
+ target_path.write_text(json.dumps(payload, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
1075
+ print(str(len(mcp_servers)))
1076
+ PY
1077
+ }
1078
+
1079
+ register_plugin_in_registry() {
1080
+ local plugin_ref="$1"
1081
+ local install_path="$2"
1082
+ local version="$3"
1083
+ local settings_path="$CLAUDE_DIR/settings.json"
1084
+ local installed_plugins_path="$CLAUDE_DIR/plugins/installed_plugins.json"
1085
+
1086
+ python3 - "$settings_path" "$installed_plugins_path" "$plugin_ref" "$install_path" "$version" <<'PY'
1087
+ import json
1088
+ import sys
1089
+ from pathlib import Path
1090
+ from datetime import datetime, timezone
1091
+
1092
+ settings_path = Path(sys.argv[1])
1093
+ installed_plugins_path = Path(sys.argv[2])
1094
+ plugin_ref = sys.argv[3]
1095
+ install_path = sys.argv[4]
1096
+ version = sys.argv[5]
1097
+ now = datetime.now(timezone.utc).isoformat()
1098
+
1099
+ settings = {}
1100
+ if settings_path.exists():
1101
+ try:
1102
+ settings = json.loads(settings_path.read_text(encoding="utf-8"))
1103
+ except Exception:
1104
+ settings = {}
1105
+ if not isinstance(settings, dict):
1106
+ settings = {}
1107
+ enabled = settings.get("enabledPlugins")
1108
+ if not isinstance(enabled, dict):
1109
+ enabled = {}
1110
+ enabled[plugin_ref] = True
1111
+ settings["enabledPlugins"] = enabled
1112
+ settings_path.parent.mkdir(parents=True, exist_ok=True)
1113
+ settings_path.write_text(json.dumps(settings, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
1114
+
1115
+ installed = {}
1116
+ if installed_plugins_path.exists():
1117
+ try:
1118
+ installed = json.loads(installed_plugins_path.read_text(encoding="utf-8"))
1119
+ except Exception:
1120
+ installed = {}
1121
+ if not isinstance(installed, dict):
1122
+ installed = {}
1123
+ installed["version"] = 2
1124
+ plugins = installed.get("plugins")
1125
+ if not isinstance(plugins, dict):
1126
+ plugins = {}
1127
+ plugins[plugin_ref] = [{
1128
+ "scope": "user",
1129
+ "installPath": install_path,
1130
+ "version": version,
1131
+ "installedAt": now,
1132
+ "lastUpdated": now,
1133
+ }]
1134
+ installed["plugins"] = plugins
1135
+ installed_plugins_path.parent.mkdir(parents=True, exist_ok=True)
1136
+ installed_plugins_path.write_text(json.dumps(installed, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
1137
+ PY
1138
+ }
1139
+
1140
+ register_plugin_marketplace() {
1141
+ local marketplace_name="$1"
1142
+ local install_path="$2"
1143
+ local settings_path="$CLAUDE_DIR/settings.json"
1144
+ local known_marketplaces_path="$CLAUDE_DIR/plugins/known_marketplaces.json"
1145
+
1146
+ python3 - "$settings_path" "$known_marketplaces_path" "$marketplace_name" "$install_path" <<'PY'
1147
+ import json
1148
+ import sys
1149
+ from pathlib import Path
1150
+ from datetime import datetime, timezone
1151
+
1152
+ settings_path = Path(sys.argv[1])
1153
+ known_marketplaces_path = Path(sys.argv[2])
1154
+ marketplace_name = sys.argv[3]
1155
+ install_path = sys.argv[4]
1156
+ now = datetime.now(timezone.utc).isoformat()
1157
+ source = {
1158
+ "source": "directory",
1159
+ "path": install_path,
1160
+ }
1161
+
1162
+ settings = {}
1163
+ if settings_path.exists():
1164
+ try:
1165
+ settings = json.loads(settings_path.read_text(encoding="utf-8"))
1166
+ except Exception:
1167
+ settings = {}
1168
+ if not isinstance(settings, dict):
1169
+ settings = {}
1170
+ extra_known = settings.get("extraKnownMarketplaces")
1171
+ if not isinstance(extra_known, dict):
1172
+ extra_known = {}
1173
+ extra_known[marketplace_name] = {"source": source}
1174
+ settings["extraKnownMarketplaces"] = extra_known
1175
+ settings_path.parent.mkdir(parents=True, exist_ok=True)
1176
+ settings_path.write_text(json.dumps(settings, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
1177
+
1178
+ known_marketplaces = {}
1179
+ if known_marketplaces_path.exists():
1180
+ try:
1181
+ known_marketplaces = json.loads(known_marketplaces_path.read_text(encoding="utf-8"))
1182
+ except Exception:
1183
+ known_marketplaces = {}
1184
+ if not isinstance(known_marketplaces, dict):
1185
+ known_marketplaces = {}
1186
+ known_marketplaces[marketplace_name] = {
1187
+ "source": source,
1188
+ "installLocation": install_path,
1189
+ "lastUpdated": now,
1190
+ }
1191
+ known_marketplaces_path.parent.mkdir(parents=True, exist_ok=True)
1192
+ known_marketplaces_path.write_text(
1193
+ json.dumps(known_marketplaces, indent=2, ensure_ascii=True) + "\n",
1194
+ encoding="utf-8",
1195
+ )
1196
+ PY
1197
+ }
1198
+
1199
+ unregister_plugin_from_registry() {
1200
+ local plugin_ref="$1"
1201
+ local settings_path="$CLAUDE_DIR/settings.json"
1202
+ local installed_plugins_path="$CLAUDE_DIR/plugins/installed_plugins.json"
1203
+
1204
+ python3 - "$settings_path" "$installed_plugins_path" "$plugin_ref" <<'PY'
1205
+ import json
1206
+ import sys
1207
+ from pathlib import Path
1208
+
1209
+ settings_path = Path(sys.argv[1])
1210
+ installed_plugins_path = Path(sys.argv[2])
1211
+ plugin_ref = sys.argv[3]
1212
+
1213
+ if settings_path.exists():
1214
+ try:
1215
+ settings = json.loads(settings_path.read_text(encoding="utf-8"))
1216
+ except Exception:
1217
+ settings = {}
1218
+ if isinstance(settings, dict):
1219
+ enabled = settings.get("enabledPlugins")
1220
+ if isinstance(enabled, dict) and plugin_ref in enabled:
1221
+ enabled.pop(plugin_ref, None)
1222
+ settings["enabledPlugins"] = enabled
1223
+ settings_path.write_text(json.dumps(settings, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
1224
+
1225
+ if installed_plugins_path.exists():
1226
+ try:
1227
+ installed = json.loads(installed_plugins_path.read_text(encoding="utf-8"))
1228
+ except Exception:
1229
+ installed = {}
1230
+ if isinstance(installed, dict):
1231
+ plugins = installed.get("plugins")
1232
+ if isinstance(plugins, dict) and plugin_ref in plugins:
1233
+ plugins.pop(plugin_ref, None)
1234
+ installed["plugins"] = plugins
1235
+ installed_plugins_path.write_text(json.dumps(installed, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
1236
+ PY
1237
+ }
1238
+
1239
+ unregister_plugin_marketplace() {
1240
+ local marketplace_name="$1"
1241
+ local settings_path="$CLAUDE_DIR/settings.json"
1242
+ local known_marketplaces_path="$CLAUDE_DIR/plugins/known_marketplaces.json"
1243
+
1244
+ python3 - "$settings_path" "$known_marketplaces_path" "$marketplace_name" <<'PY'
1245
+ import json
1246
+ import sys
1247
+ from pathlib import Path
1248
+
1249
+ settings_path = Path(sys.argv[1])
1250
+ known_marketplaces_path = Path(sys.argv[2])
1251
+ marketplace_name = sys.argv[3]
1252
+
1253
+ if settings_path.exists():
1254
+ try:
1255
+ settings = json.loads(settings_path.read_text(encoding="utf-8"))
1256
+ except Exception:
1257
+ settings = {}
1258
+ if isinstance(settings, dict):
1259
+ extra_known = settings.get("extraKnownMarketplaces")
1260
+ if isinstance(extra_known, dict) and marketplace_name in extra_known:
1261
+ extra_known.pop(marketplace_name, None)
1262
+ settings["extraKnownMarketplaces"] = extra_known
1263
+ settings_path.write_text(json.dumps(settings, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
1264
+
1265
+ if known_marketplaces_path.exists():
1266
+ try:
1267
+ known_marketplaces = json.loads(known_marketplaces_path.read_text(encoding="utf-8"))
1268
+ except Exception:
1269
+ known_marketplaces = {}
1270
+ if isinstance(known_marketplaces, dict) and marketplace_name in known_marketplaces:
1271
+ known_marketplaces.pop(marketplace_name, None)
1272
+ known_marketplaces_path.write_text(
1273
+ json.dumps(known_marketplaces, indent=2, ensure_ascii=True) + "\n",
1274
+ encoding="utf-8",
1275
+ )
1276
+ PY
1277
+ }
1278
+
1279
+ configure_detected_host_mcp_servers() {
1280
+ local managed_python="$CLAUDE_DIR/omg-runtime/.venv/bin/python"
1281
+ local managed_launcher="$CLAUDE_DIR/omg-runtime/bin/omg-mcp-server.py"
1282
+ if [ ! -x "$managed_python" ]; then
1283
+ managed_python="python3"
1284
+ fi
1285
+
1286
+ python3 - "$SCRIPT_DIR" "$managed_python" "$managed_launcher" <<'PY'
1287
+ import shutil
1288
+ import sys
1289
+ from pathlib import Path
1290
+
1291
+ root = Path(sys.argv[1])
1292
+ managed_python = sys.argv[2]
1293
+ managed_launcher = sys.argv[3]
1294
+
1295
+ sys.path.insert(0, str(root))
1296
+
1297
+ from runtime.install_planner import compute_install_plan, execute_plan # noqa: E402
1298
+
1299
+ detected_clis = {
1300
+ host: {"detected": bool(shutil.which(host))}
1301
+ for host in ("codex", "gemini", "kimi", "opencode")
1302
+ }
1303
+
1304
+ plan = compute_install_plan(
1305
+ project_dir=str(root),
1306
+ detected_clis=detected_clis,
1307
+ preset="safe",
1308
+ mode="focused",
1309
+ selected_ids=["omg-control"],
1310
+ control_command=managed_python,
1311
+ control_args=[managed_launcher],
1312
+ selected_servers={
1313
+ "omg-control": {
1314
+ "command": managed_python,
1315
+ "args": [managed_launcher],
1316
+ }
1317
+ },
1318
+ source_root=root,
1319
+ include_claude_action=False,
1320
+ )
1321
+ plan.pre_checks = []
1322
+ result = execute_plan(plan)
1323
+
1324
+ configured = [action.host for action in plan.actions if action.host in {"codex", "gemini", "kimi", "opencode"}]
1325
+ if not result.get("errors") and configured:
1326
+ print(",".join(configured))
1327
+ PY
1328
+ }
1329
+
1330
+ remove_detected_host_mcp_servers() {
1331
+ python3 - <<'PY'
1332
+ import json
1333
+ import os
1334
+ from pathlib import Path
1335
+
1336
+
1337
+ def remove_codex_section(path: Path, server_name: str) -> bool:
1338
+ if not path.exists():
1339
+ return False
1340
+ lines = path.read_text(encoding="utf-8").splitlines(keepends=True)
1341
+ headers = {
1342
+ f"[mcp_servers.{server_name}]",
1343
+ f"[mcp_servers.\"{server_name}\"]",
1344
+ }
1345
+ start_idx = None
1346
+ for idx, line in enumerate(lines):
1347
+ if line.strip() in headers:
1348
+ start_idx = idx
1349
+ break
1350
+ if start_idx is None:
1351
+ return False
1352
+
1353
+ end_idx = len(lines)
1354
+ for idx in range(start_idx + 1, len(lines)):
1355
+ stripped = lines[idx].strip()
1356
+ if stripped.startswith("[") and stripped.endswith("]"):
1357
+ end_idx = idx
1358
+ break
1359
+
1360
+ updated = lines[:start_idx] + lines[end_idx:]
1361
+ content = "".join(updated).lstrip("\n")
1362
+ path.parent.mkdir(parents=True, exist_ok=True)
1363
+ path.write_text(content, encoding="utf-8")
1364
+ return True
1365
+
1366
+
1367
+ def remove_json_server(path: Path, server_name: str) -> bool:
1368
+ if not path.exists():
1369
+ return False
1370
+ try:
1371
+ data = json.loads(path.read_text(encoding="utf-8"))
1372
+ except Exception:
1373
+ return False
1374
+ if not isinstance(data, dict):
1375
+ return False
1376
+ mcp_servers = data.get("mcpServers")
1377
+ if not isinstance(mcp_servers, dict) or server_name not in mcp_servers:
1378
+ return False
1379
+ mcp_servers.pop(server_name, None)
1380
+ data["mcpServers"] = mcp_servers
1381
+ path.parent.mkdir(parents=True, exist_ok=True)
1382
+ path.write_text(json.dumps(data, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
1383
+ return True
1384
+
1385
+
1386
+ def remove_opencode_server(path: Path, server_name: str) -> bool:
1387
+ if not path.exists():
1388
+ return False
1389
+ try:
1390
+ data = json.loads(path.read_text(encoding="utf-8"))
1391
+ except Exception:
1392
+ return False
1393
+ if not isinstance(data, dict):
1394
+ return False
1395
+ mcp_servers = data.get("mcp")
1396
+ if not isinstance(mcp_servers, dict) or server_name not in mcp_servers:
1397
+ return False
1398
+ mcp_servers.pop(server_name, None)
1399
+ data["mcp"] = mcp_servers
1400
+ path.parent.mkdir(parents=True, exist_ok=True)
1401
+ path.write_text(json.dumps(data, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
1402
+ return True
1403
+
1404
+
1405
+ removed: list[str] = []
1406
+ home = Path(os.path.expanduser("~"))
1407
+ if remove_codex_section(home / ".codex" / "config.toml", "omg-control"):
1408
+ removed.append("codex")
1409
+ if remove_json_server(home / ".gemini" / "settings.json", "omg-control"):
1410
+ removed.append("gemini")
1411
+ if remove_json_server(home / ".kimi" / "mcp.json", "omg-control"):
1412
+ removed.append("kimi")
1413
+ if remove_opencode_server(home / ".config" / "opencode" / "opencode.json", "omg-control"):
1414
+ removed.append("opencode")
1415
+
1416
+ if removed:
1417
+ print(",".join(removed))
1418
+ PY
1419
+ }
1420
+
1421
+ apply_omg_preset_to_settings() {
1422
+ local settings_path="$1"
1423
+ local preset="$2"
1424
+
1425
+ if [ ! -f "$settings_path" ]; then
1426
+ return 0
1427
+ fi
1428
+
1429
+ python3 - "$SCRIPT_DIR" "$settings_path" "$preset" <<'PY'
1430
+ import json
1431
+ import sys
1432
+ from pathlib import Path
1433
+
1434
+ root = Path(sys.argv[1])
1435
+ settings_path = Path(sys.argv[2])
1436
+ preset = sys.argv[3]
1437
+
1438
+ sys.path.insert(0, str(root))
1439
+
1440
+ from runtime.adoption import CANONICAL_VERSION, get_preset_features, resolve_preset
1441
+
1442
+ try:
1443
+ settings = json.loads(settings_path.read_text(encoding="utf-8"))
1444
+ except Exception:
1445
+ settings = {}
1446
+
1447
+ if not isinstance(settings, dict):
1448
+ settings = {}
1449
+
1450
+ omg = settings.get("_omg")
1451
+ if not isinstance(omg, dict):
1452
+ omg = {}
1453
+
1454
+ features = omg.get("features")
1455
+ if not isinstance(features, dict):
1456
+ features = {}
1457
+
1458
+ features.update(get_preset_features(resolve_preset(preset)))
1459
+ omg["features"] = features
1460
+ omg["preset"] = resolve_preset(preset)
1461
+ omg["_version"] = CANONICAL_VERSION
1462
+ settings["_omg"] = omg
1463
+
1464
+ settings_path.parent.mkdir(parents=True, exist_ok=True)
1465
+ settings_path.write_text(json.dumps(settings, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
1466
+ PY
1467
+ }
1468
+
1469
+ write_native_adoption_report() {
1470
+ python3 - "$SCRIPT_DIR" "$CLAUDE_DIR" "$ADOPTION_MODE" "$ADOPT_MODE" "$OMG_PRESET" <<'PY'
1471
+ import sys
1472
+ from pathlib import Path
1473
+
1474
+ root = Path(sys.argv[1])
1475
+ project_dir = Path(sys.argv[2])
1476
+ mode = sys.argv[3]
1477
+ adopt = sys.argv[4]
1478
+ preset = sys.argv[5]
1479
+
1480
+ sys.path.insert(0, str(root))
1481
+
1482
+ from runtime.adoption import build_adoption_report, write_adoption_report
1483
+
1484
+ report = build_adoption_report(project_dir, requested_mode=mode, preset=preset, adopt=adopt)
1485
+ path = write_adoption_report(project_dir, report)
1486
+ print(path)
1487
+ PY
1488
+ }
1489
+
1490
+ apply_adoption_mode_marker() {
1491
+ if [ "$ADOPTION_MODE" = "coexist" ]; then
1492
+ if ! $DRY_RUN; then
1493
+ mkdir -p "$CLAUDE_DIR/hooks"
1494
+ printf '%s\n' "coexist" > "$CLAUDE_DIR/hooks/.omg-coexist"
1495
+ fi
1496
+ track_file "hooks/.omg-coexist"
1497
+ else
1498
+ if ! $DRY_RUN; then
1499
+ rm -f "$CLAUDE_DIR/hooks/.omg-coexist"
1500
+ fi
1501
+ fi
1502
+ }
1503
+
1504
+ remove_omg_files() {
1505
+ if ! $DRY_RUN; then
1506
+ local plugin_bundle_marker="$PLUGIN_CACHE_DIR/$PLUGIN_BUNDLE_MARKER_FILE"
1507
+ local plugin_bundle_marker_legacy="$LEGACY_PLUGIN_CACHE_DIR/$PLUGIN_BUNDLE_MARKER_FILE"
1508
+ local remove_plugin_managed_mcp=false
1509
+ if [ -f "$plugin_bundle_marker" ] || [ -f "$plugin_bundle_marker_legacy" ]; then
1510
+ remove_plugin_managed_mcp=true
1511
+ fi
1512
+
1513
+ echo "uninstalling" > "$CLAUDE_DIR/.omg-uninstalling"
1514
+ remove_omg_hooks_from_settings
1515
+ echo " ✓ Removed OMG hook entries from settings.json (if any)"
1516
+ remove_omg_metadata_from_settings
1517
+ echo " ✓ Removed OMG metadata from settings.json (if any)"
1518
+ remove_codex_managed_residue
1519
+ echo " ✓ Removed Codex OMG residue (if any)"
1520
+
1521
+ # Use manifest for precise removal if available.
1522
+ if [ -f "$OMG_MANIFEST" ]; then
1523
+ while IFS= read -r entry; do
1524
+ [ -n "$entry" ] || continue
1525
+ [[ "$entry" == "#"* ]] && continue
1526
+ rm -f "$CLAUDE_DIR/$entry"
1527
+ done < "$OMG_MANIFEST"
1528
+ rm -f "$OMG_MANIFEST"
1529
+ fi
1530
+
1531
+ # Also remove by pattern (covers pre-manifest installs + compat aliases).
1532
+ build_omg_hooks_list
1533
+ for h in "${OMG_HOOKS[@]}"; do
1534
+ rm -f "$CLAUDE_DIR/hooks/$h"
1535
+ done
1536
+ rm -f "$CLAUDE_DIR/hooks/.omg-version" "$CLAUDE_DIR/hooks/.omg-coexist"
1537
+
1538
+ # Remove OMG rules and old v3 rule set.
1539
+ for r in "$CLAUDE_DIR"/rules/0[0-4]-*.md; do
1540
+ [ -f "$r" ] && rm "$r"
1541
+ done
1542
+ for rule in "${V3_RULES[@]}"; do
1543
+ rm -f "$CLAUDE_DIR/rules/$rule"
1544
+ done
1545
+
1546
+ # Remove OMG agents, commands, templates.
1547
+ rm -f "$CLAUDE_DIR"/agents/omg-*.md
1548
+ if [ -d "$CLAUDE_DIR/commands" ]; then
1549
+ while IFS= read -r cmd_path; do
1550
+ if [ -n "$cmd_path" ] && is_omg_managed_command_file "$cmd_path"; then
1551
+ rm -f "$cmd_path"
1552
+ fi
1553
+ done < <(find "$CLAUDE_DIR/commands" -maxdepth 1 -type f -name "*.md" 2>/dev/null | sort)
1554
+ fi
1555
+ [[ "$CLAUDE_DIR/templates/omg" == "$CLAUDE_DIR"* ]] || { echo "ERROR: rm -rf target outside expected directory: $CLAUDE_DIR/templates/omg" >&2; exit 1; }
1556
+ rm -rf "$CLAUDE_DIR/templates/omg"
1557
+ [[ "$CLAUDE_DIR/omg-runtime" == "$CLAUDE_DIR"* ]] || { echo "ERROR: rm -rf target outside expected directory: $CLAUDE_DIR/omg-runtime" >&2; exit 1; }
1558
+ rm -rf "$CLAUDE_DIR/omg-runtime"
1559
+ [[ "$CLAUDE_DIR/.omg" == "$CLAUDE_DIR"* ]] || { echo "ERROR: rm -rf target outside expected directory: $CLAUDE_DIR/.omg" >&2; exit 1; }
1560
+ rm -rf "$CLAUDE_DIR/.omg"
1561
+
1562
+ [[ "$PLUGIN_CACHE_DIR" == "$CLAUDE_DIR"* ]] || { echo "ERROR: rm -rf target outside expected directory: $PLUGIN_CACHE_DIR" >&2; exit 1; }
1563
+ rm -rf "$PLUGIN_CACHE_DIR"
1564
+ [[ "$LEGACY_PLUGIN_CACHE_DIR" == "$CLAUDE_DIR"* ]] || { echo "ERROR: rm -rf target outside expected directory: $LEGACY_PLUGIN_CACHE_DIR" >&2; exit 1; }
1565
+ rm -rf "$LEGACY_PLUGIN_CACHE_DIR"
1566
+ rm -f "$CLAUDE_DIR/hud/omg-hud.mjs"
1567
+ unregister_plugin_from_registry "$PLUGIN_REF"
1568
+ unregister_plugin_from_registry "$LEGACY_PLUGIN_REF"
1569
+ unregister_plugin_marketplace "$PLUGIN_MARKETPLACE"
1570
+ remove_hud_status_line
1571
+
1572
+ if $remove_plugin_managed_mcp; then
1573
+ local pruned_mcp=0
1574
+ pruned_mcp=$(prune_plugin_mcp_from_settings)
1575
+ if [ "${pruned_mcp:-0}" -gt 0 ]; then
1576
+ echo " ✓ Plugin-managed MCP servers removed from .mcp.json ($pruned_mcp)"
1577
+ fi
1578
+ fi
1579
+
1580
+ local removed_host_mcp=""
1581
+ removed_host_mcp=$(remove_detected_host_mcp_servers)
1582
+ if [ -n "$removed_host_mcp" ]; then
1583
+ echo " ✓ Removed OMG MCP config from detected hosts: $removed_host_mcp"
1584
+ fi
1585
+ rm -f "$CLAUDE_DIR/.omg-uninstalling"
1586
+ fi
1587
+ }
1588
+
1589
+ install_plugin_bundle() {
1590
+ local plugin_ref="$PLUGIN_REF"
1591
+ local plugin_root="$PLUGIN_CACHE_DIR/$VERSION"
1592
+ local plugin_manifest_src="$SCRIPT_DIR/.claude-plugin/plugin.json"
1593
+ local plugin_manifest_target="$plugin_root/.claude-plugin/plugin.json"
1594
+ local marketplace_manifest_src="$SCRIPT_DIR/.claude-plugin/marketplace.json"
1595
+ local marketplace_manifest_target="$plugin_root/.claude-plugin/marketplace.json"
1596
+ local plugin_mcp_target="$plugin_root/.claude-plugin/mcp.json"
1597
+ local hud_src="$SCRIPT_DIR/hud/omg-hud.mjs"
1598
+ local hud_target="$CLAUDE_DIR/hud/omg-hud.mjs"
1599
+
1600
+ echo " Plugin bundle mode enabled: install plugin + MCP + HUD together"
1601
+ if $DRY_RUN; then
1602
+ echo " (would install plugin bundle under $plugin_root and deploy HUD to $hud_target)"
1603
+ echo " (would register plugin in ~/.claude/plugins/installed_plugins.json, enable it in settings.json, and add the omg marketplace)"
1604
+ echo " (would merge plugin MCP servers into .mcp.json)"
1605
+ return 0
1606
+ fi
1607
+
1608
+ mkdir -p "$plugin_root/.claude-plugin"
1609
+ mkdir -p "$CLAUDE_DIR/hud"
1610
+ cp "$plugin_manifest_src" "$plugin_manifest_target"
1611
+ if [ -f "$marketplace_manifest_src" ]; then
1612
+ cp "$marketplace_manifest_src" "$marketplace_manifest_target"
1613
+ fi
1614
+
1615
+ # Provide a fallback plugin MCP file if not shipped in npm package.
1616
+ if [ ! -f "$SCRIPT_DIR/.claude-plugin/mcp.json" ] && [ ! -f "$SCRIPT_DIR/.mcp.json" ]; then
1617
+ local _fallback_mcp_dir
1618
+ _fallback_mcp_dir=$(mktemp -d)
1619
+ mkdir -p "$_fallback_mcp_dir/.claude-plugin"
1620
+ cat > "$_fallback_mcp_dir/.claude-plugin/mcp.json" <<'FALLBACK_MCP'
1621
+ {
1622
+ "mcpServers": {
1623
+ "omg-control": {
1624
+ "command": "python3",
1625
+ "args": ["-m", "runtime.omg_mcp_server"]
1626
+ }
1627
+ }
1628
+ }
1629
+ FALLBACK_MCP
1630
+ SCRIPT_DIR="$_fallback_mcp_dir" write_plugin_mcp_file "$plugin_mcp_target" >/dev/null
1631
+ rm -rf "$_fallback_mcp_dir"
1632
+ else
1633
+ write_plugin_mcp_file "$plugin_mcp_target" >/dev/null
1634
+ fi
1635
+
1636
+ cp "$hud_src" "$hud_target"
1637
+ chmod +x "$hud_target" 2>/dev/null || true
1638
+ mkdir -p "$PLUGIN_CACHE_DIR"
1639
+ printf '%s\n' "omg-plugin-bundle-v1" > "$PLUGIN_CACHE_DIR/$PLUGIN_BUNDLE_MARKER_FILE"
1640
+
1641
+ unregister_plugin_from_registry "$LEGACY_PLUGIN_REF"
1642
+ register_plugin_in_registry "$plugin_ref" "$plugin_root" "$VERSION"
1643
+ register_plugin_marketplace "$PLUGIN_MARKETPLACE" "$plugin_root"
1644
+
1645
+ track_file "plugins/cache/$PLUGIN_MARKETPLACE/$PLUGIN_NAME/$VERSION/.claude-plugin/plugin.json"
1646
+ track_file "plugins/cache/$PLUGIN_MARKETPLACE/$PLUGIN_NAME/$VERSION/.claude-plugin/marketplace.json"
1647
+ track_file "plugins/cache/$PLUGIN_MARKETPLACE/$PLUGIN_NAME/$VERSION/.claude-plugin/mcp.json"
1648
+ track_file "plugins/cache/$PLUGIN_MARKETPLACE/$PLUGIN_NAME/$PLUGIN_BUNDLE_MARKER_FILE"
1649
+ track_file "hud/omg-hud.mjs"
1650
+ echo " ✓ Plugin bundle installed and registered in Claude plugin settings"
1651
+ }
1652
+
1653
+ run_uninstall() {
1654
+ echo "═══════════════════════════════════════════════════════════════"
1655
+ echo " OMG Setup Manager — uninstall"
1656
+ echo "═══════════════════════════════════════════════════════════════"
1657
+ echo ""
1658
+
1659
+ if $DRY_RUN; then
1660
+ echo " *** DRY RUN — no files will be changed ***"
1661
+ echo ""
1662
+ fi
1663
+
1664
+ preflight
1665
+
1666
+ local existing_ver=""
1667
+ if [ -f "$CLAUDE_DIR/hooks/.omg-version" ]; then
1668
+ existing_ver=$(cat "$CLAUDE_DIR/hooks/.omg-version" 2>/dev/null || echo "")
1669
+ fi
1670
+ if [ -n "$existing_ver" ]; then
1671
+ echo " ✓ Existing OMG install: $existing_ver"
1672
+ else
1673
+ echo " ~ No .omg-version marker found; uninstall will still remove known OMG files."
1674
+ fi
1675
+
1676
+ if ! $NON_INTERACTIVE && ! $DRY_RUN; then
1677
+ read -p "Proceed with uninstall? [y/N] " -n 1 -r
1678
+ echo ""
1679
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
1680
+ echo "Cancelled."
1681
+ exit 0
1682
+ fi
1683
+ fi
1684
+
1685
+ echo ""
1686
+ echo "Uninstall: removing OMG-managed files from $CLAUDE_DIR"
1687
+ ensure_backup
1688
+ if ! $DRY_RUN; then
1689
+ echo " ✓ Backup: $BACKUP_DIR"
1690
+ else
1691
+ echo " (would backup to $BACKUP_DIR)"
1692
+ fi
1693
+ remove_omg_files
1694
+ if $DRY_RUN; then
1695
+ echo " (would remove OMG hooks/rules/agents/commands/templates)"
1696
+ else
1697
+ echo " ✓ Removed OMG hooks/rules/agents/commands/templates"
1698
+ fi
1699
+
1700
+ if ! $DRY_RUN; then
1701
+ local _removed_json _preserved_json _host_configs_json
1702
+ _removed_json=$(python3 -c "
1703
+ import json, os, sys
1704
+ d = sys.argv[1]
1705
+ paths = [os.path.join(d, p) for p in ['hooks', 'rules', 'agents', 'commands', 'templates/omg', 'omg-runtime', 'hud/omg-hud.mjs', 'plugins/cache'] if not os.path.exists(os.path.join(d, p))]
1706
+ print(json.dumps(paths))
1707
+ " "$CLAUDE_DIR" 2>/dev/null || echo "[]")
1708
+ _preserved_json=$(python3 -c "
1709
+ import json, os, sys
1710
+ d = sys.argv[1]
1711
+ paths = [os.path.join(d, p) for p in ['settings.json'] if os.path.exists(os.path.join(d, p))]
1712
+ print(json.dumps(paths))
1713
+ " "$CLAUDE_DIR" 2>/dev/null || echo "[]")
1714
+ _host_configs_json=$(python3 -c "
1715
+ import json, os
1716
+ home = os.path.expanduser('~')
1717
+ configs = {
1718
+ 'codex': os.path.join(home, '.codex', 'config.toml'),
1719
+ 'gemini': os.path.join(home, '.gemini', 'settings.json'),
1720
+ 'kimi': os.path.join(home, '.kimi', 'mcp.json'),
1721
+ }
1722
+ cleaned = []
1723
+ for host, path in configs.items():
1724
+ if os.path.exists(path):
1725
+ try:
1726
+ content = open(path, encoding='utf-8').read()
1727
+ if 'omg-control' not in content:
1728
+ cleaned.append(path)
1729
+ except Exception:
1730
+ pass
1731
+ else:
1732
+ cleaned.append(path)
1733
+ print(json.dumps(cleaned))
1734
+ " 2>/dev/null || echo "[]")
1735
+ emit_uninstall_receipt "$_removed_json" "$_preserved_json" "$_host_configs_json"
1736
+ echo " ✓ Uninstall receipt written to $CLAUDE_DIR/.omg-uninstall-receipt.json"
1737
+ fi
1738
+
1739
+ if $VERIFY_CLEAN; then
1740
+ python3 - "$CLAUDE_DIR" "$REPAIR" "$DRY_RUN" <<'VERIFY_CLEAN_PY'
1741
+ import glob
1742
+ import json
1743
+ import os
1744
+ import shutil
1745
+ import sys
1746
+ from datetime import datetime, timezone
1747
+ from pathlib import Path
1748
+
1749
+ claude_dir = Path(sys.argv[1])
1750
+ repair = sys.argv[2] == "true"
1751
+ dry_run = sys.argv[3] == "true"
1752
+ home = Path(os.path.expanduser("~"))
1753
+
1754
+
1755
+ def has_codex_section(path: Path, server_name: str) -> bool:
1756
+ if not path.exists():
1757
+ return False
1758
+ lines = path.read_text(encoding="utf-8").splitlines()
1759
+ headers = {f"[mcp_servers.{server_name}]", f'[mcp_servers."{server_name}"]'}
1760
+ return any(line.strip() in headers for line in lines)
1761
+
1762
+
1763
+ def remove_codex_section(path: Path, server_name: str) -> bool:
1764
+ if not path.exists():
1765
+ return False
1766
+ lines = path.read_text(encoding="utf-8").splitlines(keepends=True)
1767
+ headers = {f"[mcp_servers.{server_name}]", f'[mcp_servers."{server_name}"]'}
1768
+ start_idx = None
1769
+ for idx, line in enumerate(lines):
1770
+ if line.strip() in headers:
1771
+ start_idx = idx
1772
+ break
1773
+ if start_idx is None:
1774
+ return False
1775
+ end_idx = len(lines)
1776
+ for idx in range(start_idx + 1, len(lines)):
1777
+ stripped = lines[idx].strip()
1778
+ if stripped.startswith("[") and stripped.endswith("]"):
1779
+ end_idx = idx
1780
+ break
1781
+ updated = lines[:start_idx] + lines[end_idx:]
1782
+ content = "".join(updated).lstrip("\n")
1783
+ path.parent.mkdir(parents=True, exist_ok=True)
1784
+ path.write_text(content, encoding="utf-8")
1785
+ return True
1786
+
1787
+
1788
+ def has_json_server(path: Path, server_name: str, mcp_key: str = "mcpServers") -> bool:
1789
+ if not path.exists():
1790
+ return False
1791
+ try:
1792
+ data = json.loads(path.read_text(encoding="utf-8"))
1793
+ except Exception:
1794
+ return False
1795
+ if not isinstance(data, dict):
1796
+ return False
1797
+ servers = data.get(mcp_key)
1798
+ return isinstance(servers, dict) and server_name in servers
1799
+
1800
+
1801
+ def remove_json_server(path: Path, server_name: str, mcp_key: str = "mcpServers") -> bool:
1802
+ if not path.exists():
1803
+ return False
1804
+ try:
1805
+ data = json.loads(path.read_text(encoding="utf-8"))
1806
+ except Exception:
1807
+ return False
1808
+ if not isinstance(data, dict):
1809
+ return False
1810
+ servers = data.get(mcp_key)
1811
+ if not isinstance(servers, dict) or server_name not in servers:
1812
+ return False
1813
+ servers.pop(server_name, None)
1814
+ data[mcp_key] = servers
1815
+ path.parent.mkdir(parents=True, exist_ok=True)
1816
+ path.write_text(json.dumps(data, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
1817
+ return True
1818
+
1819
+
1820
+ def has_omg_hooks(settings_path: Path) -> bool:
1821
+ if not settings_path.exists():
1822
+ return False
1823
+ try:
1824
+ settings = json.loads(settings_path.read_text(encoding="utf-8"))
1825
+ except Exception:
1826
+ return False
1827
+ hooks = settings.get("hooks")
1828
+ if not isinstance(hooks, dict):
1829
+ return False
1830
+ for _event, entries in hooks.items():
1831
+ if not isinstance(entries, list):
1832
+ continue
1833
+ for entry in entries:
1834
+ if isinstance(entry, dict) and "omg-runtime" in str(entry.get("command", "")):
1835
+ return True
1836
+ return False
1837
+
1838
+
1839
+ def remove_omg_hooks(settings_path: Path) -> bool:
1840
+ if not settings_path.exists():
1841
+ return False
1842
+ try:
1843
+ settings = json.loads(settings_path.read_text(encoding="utf-8"))
1844
+ except Exception:
1845
+ return False
1846
+ hooks = settings.get("hooks")
1847
+ if not isinstance(hooks, dict):
1848
+ return False
1849
+ changed = False
1850
+ new_hooks: dict[str, object] = {}
1851
+ for event, entries in hooks.items():
1852
+ if not isinstance(entries, list):
1853
+ new_hooks[event] = entries
1854
+ continue
1855
+ filtered = [
1856
+ e for e in entries
1857
+ if not (isinstance(e, dict) and "omg-runtime" in str(e.get("command", "")))
1858
+ ]
1859
+ if len(filtered) < len(entries):
1860
+ changed = True
1861
+ if filtered:
1862
+ new_hooks[event] = filtered
1863
+ if not changed:
1864
+ return False
1865
+ if new_hooks:
1866
+ settings["hooks"] = new_hooks
1867
+ else:
1868
+ settings.pop("hooks", None)
1869
+ settings_path.write_text(json.dumps(settings, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
1870
+ return True
1871
+
1872
+
1873
+ def has_omg_status_line(settings_path: Path) -> bool:
1874
+ if not settings_path.exists():
1875
+ return False
1876
+ try:
1877
+ settings = json.loads(settings_path.read_text(encoding="utf-8"))
1878
+ except Exception:
1879
+ return False
1880
+ sl = settings.get("statusLine")
1881
+ return isinstance(sl, dict) and "omg-hud.mjs" in str(sl.get("command", ""))
1882
+
1883
+
1884
+ def remove_omg_status_line(settings_path: Path) -> bool:
1885
+ if not settings_path.exists():
1886
+ return False
1887
+ try:
1888
+ settings = json.loads(settings_path.read_text(encoding="utf-8"))
1889
+ except Exception:
1890
+ return False
1891
+ sl = settings.get("statusLine")
1892
+ if not (isinstance(sl, dict) and "omg-hud.mjs" in str(sl.get("command", ""))):
1893
+ return False
1894
+ settings.pop("statusLine", None)
1895
+ settings_path.write_text(json.dumps(settings, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
1896
+ return True
1897
+
1898
+
1899
+ audited_surfaces: list[str] = []
1900
+ residue_found: list[dict[str, str]] = []
1901
+ repaired_surfaces: list[dict[str, str]] = []
1902
+ backed_up: set[str] = set()
1903
+
1904
+ file_residue_paths = [
1905
+ claude_dir / "hooks" / ".omg-version",
1906
+ claude_dir / "hooks" / ".omg-coexist",
1907
+ claude_dir / "omg-runtime",
1908
+ claude_dir / "templates" / "omg",
1909
+ claude_dir / "hud" / "omg-hud.mjs",
1910
+ claude_dir / ".omg-manifest",
1911
+ ]
1912
+ audited_surfaces.append("claude_file_residue")
1913
+ for p in file_residue_paths:
1914
+ if p.exists():
1915
+ residue_found.append({"surface": "claude_file_residue", "path": str(p)})
1916
+ for pattern in [
1917
+ str(claude_dir / "rules" / "omg-*.md"),
1918
+ str(claude_dir / "rules" / "0[0-4]-*.md"),
1919
+ str(claude_dir / "agents" / "omg-*.md"),
1920
+ ]:
1921
+ for match in glob.glob(pattern):
1922
+ residue_found.append({"surface": "claude_file_residue", "path": match})
1923
+
1924
+ settings_path = claude_dir / "settings.json"
1925
+
1926
+ audited_surfaces.append("claude_hooks")
1927
+ if has_omg_hooks(settings_path):
1928
+ residue_found.append({"surface": "claude_hooks", "path": str(settings_path)})
1929
+
1930
+ audited_surfaces.append("claude_status_line")
1931
+ if has_omg_status_line(settings_path):
1932
+ residue_found.append({"surface": "claude_status_line", "path": str(settings_path)})
1933
+
1934
+ audited_surfaces.append("claude_plugin")
1935
+ try:
1936
+ s = json.loads(settings_path.read_text(encoding="utf-8"))
1937
+ if "omg@omg" in (s.get("enabledPlugins") or {}):
1938
+ residue_found.append({"surface": "claude_plugin", "path": str(settings_path)})
1939
+ except Exception:
1940
+ pass
1941
+
1942
+ codex_path = home / ".codex" / "config.toml"
1943
+ audited_surfaces.append("codex_mcp")
1944
+ if has_codex_section(codex_path, "omg-control"):
1945
+ residue_found.append({"surface": "codex_mcp", "path": str(codex_path)})
1946
+
1947
+ gemini_path = home / ".gemini" / "settings.json"
1948
+ audited_surfaces.append("gemini_mcp")
1949
+ if has_json_server(gemini_path, "omg-control"):
1950
+ residue_found.append({"surface": "gemini_mcp", "path": str(gemini_path)})
1951
+
1952
+ kimi_path = home / ".kimi" / "mcp.json"
1953
+ audited_surfaces.append("kimi_mcp")
1954
+ if has_json_server(kimi_path, "omg-control"):
1955
+ residue_found.append({"surface": "kimi_mcp", "path": str(kimi_path)})
1956
+
1957
+ opencode_path = home / ".config" / "opencode" / "opencode.json"
1958
+ audited_surfaces.append("opencode_mcp")
1959
+ if has_json_server(opencode_path, "omg-control", mcp_key="mcp"):
1960
+ residue_found.append({"surface": "opencode_mcp", "path": str(opencode_path)})
1961
+
1962
+ if repair and not dry_run and residue_found:
1963
+ def backup_once(p: Path) -> None:
1964
+ key = str(p)
1965
+ if key in backed_up or not p.exists():
1966
+ return
1967
+ bak = p.parent / (p.name + ".omg-backup")
1968
+ if p.is_dir():
1969
+ shutil.copytree(p, bak, dirs_exist_ok=True)
1970
+ else:
1971
+ shutil.copy2(p, bak)
1972
+ backed_up.add(key)
1973
+
1974
+ for item in list(residue_found):
1975
+ surface = item["surface"]
1976
+ path = Path(item["path"])
1977
+ if surface == "claude_file_residue":
1978
+ if path.exists():
1979
+ backup_once(path)
1980
+ if path.is_dir():
1981
+ shutil.rmtree(path)
1982
+ else:
1983
+ path.unlink()
1984
+ repaired_surfaces.append(item)
1985
+ elif surface == "codex_mcp":
1986
+ backup_once(path)
1987
+ if remove_codex_section(path, "omg-control"):
1988
+ repaired_surfaces.append(item)
1989
+ elif surface in ("gemini_mcp", "kimi_mcp"):
1990
+ backup_once(path)
1991
+ if remove_json_server(path, "omg-control"):
1992
+ repaired_surfaces.append(item)
1993
+ elif surface == "opencode_mcp":
1994
+ backup_once(path)
1995
+ if remove_json_server(path, "omg-control", mcp_key="mcp"):
1996
+ repaired_surfaces.append(item)
1997
+ elif surface == "claude_hooks":
1998
+ backup_once(path)
1999
+ if remove_omg_hooks(path):
2000
+ repaired_surfaces.append(item)
2001
+ elif surface == "claude_status_line":
2002
+ backup_once(path)
2003
+ if remove_omg_status_line(path):
2004
+ repaired_surfaces.append(item)
2005
+ elif surface == "claude_plugin":
2006
+ backup_once(path)
2007
+ try:
2008
+ s = json.loads(path.read_text(encoding="utf-8"))
2009
+ ep = s.get("enabledPlugins")
2010
+ if isinstance(ep, dict) and "omg@omg" in ep:
2011
+ ep.pop("omg@omg")
2012
+ if not ep:
2013
+ s.pop("enabledPlugins", None)
2014
+ path.write_text(json.dumps(s, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
2015
+ repaired_surfaces.append(item)
2016
+ except Exception:
2017
+ pass
2018
+
2019
+ repaired_set = {(r["surface"], r["path"]) for r in repaired_surfaces}
2020
+ remaining_blockers = [r for r in residue_found if (r["surface"], r["path"]) not in repaired_set]
2021
+ residue_surface_names = {r["surface"] for r in residue_found}
2022
+ preserved_surfaces = [s for s in audited_surfaces if s not in residue_surface_names]
2023
+
2024
+ status = "clean" if not remaining_blockers else "residue_found"
2025
+
2026
+ receipt: dict[str, object] = {
2027
+ "schema": "VerifyCleanReceipt",
2028
+ "timestamp": datetime.now(timezone.utc).isoformat(),
2029
+ "verification_status": status,
2030
+ "audited_surfaces": audited_surfaces,
2031
+ "residue_found": residue_found,
2032
+ "repaired_surfaces": repaired_surfaces,
2033
+ "preserved_surfaces": preserved_surfaces,
2034
+ "remaining_blockers": remaining_blockers,
2035
+ }
2036
+
2037
+ if not dry_run:
2038
+ receipt_path = claude_dir / ".omg-verify-clean-receipt.json"
2039
+ receipt_path.parent.mkdir(parents=True, exist_ok=True)
2040
+ receipt_path.write_text(json.dumps(receipt, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
2041
+
2042
+ print(json.dumps(receipt, indent=2, ensure_ascii=True))
2043
+ if status == "clean":
2044
+ print(" \u2713 Verify-clean: no OMG-owned residue found.", file=sys.stderr)
2045
+ else:
2046
+ count = len(remaining_blockers)
2047
+ print(f" \u2717 Verify-clean: residue found in {count} surface(s)", file=sys.stderr)
2048
+ VERIFY_CLEAN_PY
2049
+ fi
2050
+
2051
+ echo ""
2052
+ echo "Uninstall complete."
2053
+ echo " ✓ If plugin bundle was installed, plugin + MCP + HUD were removed together"
2054
+ echo "Preserved:"
2055
+ echo " - $CLAUDE_DIR/settings.json"
2056
+ echo " - project .omg/ data"
2057
+ echo " - non-OMG custom files"
2058
+ }
2059
+
2060
+ run_install_like() {
2061
+ local existing_ver=""
2062
+ local removed=0
2063
+ local installed_rules=0
2064
+ local installed_hooks=0
2065
+ local hook_errors=0
2066
+ local installed_agents=0
2067
+ local installed_cmds=0
2068
+
2069
+ echo "═══════════════════════════════════════════════════════════════"
2070
+ echo " OMG Setup Manager — $ACTION"
2071
+ echo "═══════════════════════════════════════════════════════════════"
2072
+ echo ""
2073
+
2074
+ if $DRY_RUN; then
2075
+ echo " *** DRY RUN — no files will be changed ***"
2076
+ echo ""
2077
+ fi
2078
+
2079
+ preflight
2080
+ verify_install_integrity
2081
+
2082
+ if [ -f "$CLAUDE_DIR/hooks/.omg-version" ]; then
2083
+ existing_ver=$(cat "$CLAUDE_DIR/hooks/.omg-version" 2>/dev/null || echo "")
2084
+ fi
2085
+
2086
+ if [ "$ACTION" = "update" ] && [ -z "$existing_ver" ]; then
2087
+ echo " ~ No existing OMG install detected. update will proceed as install."
2088
+ fi
2089
+
2090
+ if [ -n "$existing_ver" ]; then
2091
+ echo " ✓ Existing: $existing_ver → target $VERSION"
2092
+ else
2093
+ echo " ✓ Fresh install"
2094
+ fi
2095
+ echo " ✓ Command surface: /OMG:setup and /OMG:crazy are the primary native front door"
2096
+ echo " ✓ Adoption mode: $ADOPTION_MODE"
2097
+ echo " ✓ Preset: $OMG_PRESET"
2098
+
2099
+ if $FRESH_INSTALL; then
2100
+ echo ""
2101
+ echo "Fresh/reinstall mode: remove OMG files before install."
2102
+ if ! $NON_INTERACTIVE && ! $DRY_RUN; then
2103
+ read -p "Proceed with fresh cleanup? [y/N] " -n 1 -r
2104
+ echo ""
2105
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
2106
+ echo "Cancelled."
2107
+ exit 0
2108
+ fi
2109
+ fi
2110
+
2111
+ ensure_backup
2112
+ if ! $DRY_RUN; then
2113
+ echo " ✓ Backup: $BACKUP_DIR"
2114
+ else
2115
+ echo " (would backup to $BACKUP_DIR)"
2116
+ fi
2117
+ remove_omg_files
2118
+ if $DRY_RUN; then
2119
+ echo " (would remove OMG hooks/rules/agents/commands/templates)"
2120
+ else
2121
+ echo " ✓ Clean slate ready"
2122
+ fi
2123
+ existing_ver=""
2124
+ fi
2125
+
2126
+ if [ -n "$existing_ver" ] && ! $FRESH_INSTALL; then
2127
+ echo ""
2128
+ echo "Step 0/7: Backup existing installation..."
2129
+ ensure_backup
2130
+ if ! $DRY_RUN; then
2131
+ echo " ✓ Backup: $BACKUP_DIR"
2132
+ else
2133
+ echo " (would backup to $BACKUP_DIR)"
2134
+ fi
2135
+ fi
2136
+
2137
+ echo ""
2138
+ echo "Step 1/7: Remove deprecated files..."
2139
+
2140
+ for rule in "${V3_RULES[@]}"; do
2141
+ target="$CLAUDE_DIR/rules/$rule"
2142
+ if [ -f "$target" ]; then
2143
+ ! $DRY_RUN && rm "$target"
2144
+ echo " - rules/$rule"
2145
+ removed=$((removed + 1))
2146
+ fi
2147
+ done
2148
+
2149
+ for agent in "${V3_AGENTS_REMOVE[@]}"; do
2150
+ target="$CLAUDE_DIR/agents/$agent"
2151
+ if [ -f "$target" ]; then
2152
+ ! $DRY_RUN && rm "$target"
2153
+ echo " - agents/$agent (v3 deprecated)"
2154
+ removed=$((removed + 1))
2155
+ fi
2156
+ done
2157
+
2158
+ for agent in "${OLD_OMG_AGENTS[@]}"; do
2159
+ target="$CLAUDE_DIR/agents/$agent"
2160
+ if [ -f "$target" ]; then
2161
+ if grep -q "OMG\|omg\|circuit.breaker\|escalat" "$target" 2>/dev/null; then
2162
+ ! $DRY_RUN && rm "$target"
2163
+ echo " - agents/$agent (renamed to omg-$agent)"
2164
+ removed=$((removed + 1))
2165
+ else
2166
+ echo " ~ agents/$agent (kept — appears to be non-OMG/custom)"
2167
+ fi
2168
+ fi
2169
+ done
2170
+
2171
+ for cmd in "${V3_COMMANDS_REMOVE[@]}" "${V4_COMMANDS_REMOVE[@]}"; do
2172
+ target="$CLAUDE_DIR/commands/$cmd"
2173
+ if [ -f "$target" ]; then
2174
+ if is_omg_managed_command_file "$target"; then
2175
+ ! $DRY_RUN && rm "$target"
2176
+ echo " - commands/$cmd (v4 → OMG:$cmd)"
2177
+ removed=$((removed + 1))
2178
+ else
2179
+ echo " ~ commands/$cmd (kept — appears to be non-OMG/custom)"
2180
+ fi
2181
+ fi
2182
+ done
2183
+
2184
+ if compgen -G "$CLAUDE_DIR/commands/*omc*.md" > /dev/null; then
2185
+ for cmd in "$CLAUDE_DIR"/commands/*omc*.md; do
2186
+ [ -f "$cmd" ] || continue
2187
+ if is_omg_managed_command_file "$cmd"; then
2188
+ ! $DRY_RUN && rm "$cmd"
2189
+ echo " - commands/$(basename "$cmd") (removed legacy command)"
2190
+ removed=$((removed + 1))
2191
+ fi
2192
+ done
2193
+ fi
2194
+
2195
+ if [ -d "$CLAUDE_DIR/hooks/__pycache__" ]; then
2196
+ ! $DRY_RUN && rm -rf "$CLAUDE_DIR/hooks/__pycache__"
2197
+ removed=$((removed + 1))
2198
+ fi
2199
+ ! $DRY_RUN && find "$CLAUDE_DIR/hooks/" -name "*.pyc" -delete 2>/dev/null || true
2200
+ [ $removed -eq 0 ] && echo " (nothing to remove)" || echo " ✓ Removed $removed deprecated files"
2201
+
2202
+ echo ""
2203
+ echo "Step 2/7: Core Rules → $CLAUDE_DIR/rules/"
2204
+ ! $DRY_RUN && mkdir -p "$CLAUDE_DIR/rules"
2205
+ for f in "$SCRIPT_DIR"/rules/core/*.md; do
2206
+ name=$(basename "$f")
2207
+ target="$CLAUDE_DIR/rules/$name"
2208
+ ! $DRY_RUN && cp "$f" "$target"
2209
+ installed_rules=$((installed_rules + 1))
2210
+ track_file "rules/$name"
2211
+ done
2212
+ echo " ✓ $installed_rules core rules"
2213
+
2214
+ echo ""
2215
+ echo "Step 3/7: Hooks → $CLAUDE_DIR/hooks/"
2216
+ ! $DRY_RUN && mkdir -p "$CLAUDE_DIR/hooks"
2217
+ for f in "$SCRIPT_DIR"/hooks/*.py; do
2218
+ name=$(basename "$f")
2219
+ target="$CLAUDE_DIR/hooks/$name"
2220
+ if ! $DRY_RUN; then
2221
+ install_file "$f" "$target"
2222
+ if ! $USE_SYMLINK; then
2223
+ chmod +x "$target"
2224
+ fi
2225
+ fi
2226
+ if python3 -c "import py_compile; py_compile.compile('$f', doraise=True)" 2>/dev/null; then
2227
+ echo " ✓ $name"
2228
+ else
2229
+ echo " ❌ $name (SYNTAX ERROR)"
2230
+ hook_errors=$((hook_errors + 1))
2231
+ ERRORS=$((ERRORS + 1))
2232
+ fi
2233
+ installed_hooks=$((installed_hooks + 1))
2234
+ track_file "hooks/$name"
2235
+ done
2236
+ ! $DRY_RUN && echo "$VERSION" > "$CLAUDE_DIR/hooks/.omg-version"
2237
+ apply_adoption_mode_marker
2238
+ echo " ✓ $installed_hooks hooks ($hook_errors errors)"
2239
+
2240
+ echo ""
2241
+ echo "Step 4/7: Agents → $CLAUDE_DIR/agents/"
2242
+ ! $DRY_RUN && mkdir -p "$CLAUDE_DIR/agents"
2243
+ for f in "$SCRIPT_DIR"/agents/*.md; do
2244
+ name=$(basename "$f")
2245
+ target="$CLAUDE_DIR/agents/$name"
2246
+ if ! $DRY_RUN; then
2247
+ [ -f "$target" ] && [ -z "$existing_ver" ] && cp "$target" "$target.bak.$BACKUP_TS"
2248
+ install_file "$f" "$target"
2249
+ fi
2250
+ echo " ✓ $name"
2251
+ installed_agents=$((installed_agents + 1))
2252
+ track_file "agents/$name"
2253
+ done
2254
+ echo " ✓ $installed_agents agents"
2255
+
2256
+ echo ""
2257
+ echo "Step 5/7: Commands → $CLAUDE_DIR/commands/"
2258
+ ! $DRY_RUN && mkdir -p "$CLAUDE_DIR/commands"
2259
+ for f in "$SCRIPT_DIR"/commands/*.md; do
2260
+ name=$(basename "$f")
2261
+ if [[ "$name" == *omc* ]]; then
2262
+ echo " - /$(basename "$name" .md) (skipped: legacy alias commands are unsupported)"
2263
+ continue
2264
+ fi
2265
+ target="$CLAUDE_DIR/commands/$name"
2266
+ if [ -f "$target" ] && ! is_omg_managed_command_file "$target"; then
2267
+ echo " ~ /$(basename "$name" .md) (kept existing custom command)"
2268
+ continue
2269
+ fi
2270
+ if ! $DRY_RUN; then
2271
+ install_file "$f" "$target"
2272
+ if ! $USE_SYMLINK; then
2273
+ mark_omg_managed_command_file "$target"
2274
+ fi
2275
+ fi
2276
+ echo " ✓ /$(basename "$name" .md)"
2277
+ installed_cmds=$((installed_cmds + 1))
2278
+ track_file "commands/$name"
2279
+ done
2280
+ echo ""
2281
+ echo "Step 6/7: Settings + Templates..."
2282
+ MERGE="$SCRIPT_DIR/scripts/settings-merge.py"
2283
+ TARGET="$CLAUDE_DIR/settings.json"
2284
+ SOURCE="$SCRIPT_DIR/settings.json"
2285
+ if ! $DRY_RUN; then
2286
+ if [ ! -f "$TARGET" ]; then
2287
+ cp "$SOURCE" "$TARGET"
2288
+ apply_omg_preset_to_settings "$TARGET" "$OMG_PRESET"
2289
+ echo " ✓ Created settings.json"
2290
+ else
2291
+ if [ "$MERGE_POLICY" = "skip" ]; then
2292
+ apply_omg_preset_to_settings "$TARGET" "$OMG_PRESET"
2293
+ echo " ⊘ Skipped settings merge (--merge-policy=skip)"
2294
+ elif [ "$MERGE_POLICY" = "apply" ] || $NON_INTERACTIVE; then
2295
+ python3 "$MERGE" "$TARGET" "$SOURCE"
2296
+ apply_omg_preset_to_settings "$TARGET" "$OMG_PRESET"
2297
+ echo " ✓ Settings merged (auto)"
2298
+ else
2299
+ echo " Merging settings.json..."
2300
+ dry_run_preview="$(python3 "$MERGE" "$TARGET" "$SOURCE" --dry-run 2>&1)"
2301
+ printf '%s\n' "$dry_run_preview" | sed -n '1,5p' | sed 's/^/ /'
2302
+ echo ""
2303
+ if read -p " Apply merge? [Y/n] " -n 1 -r; then
2304
+ echo ""
2305
+ if [[ ! $REPLY =~ ^[Nn]$ ]]; then
2306
+ python3 "$MERGE" "$TARGET" "$SOURCE"
2307
+ apply_omg_preset_to_settings "$TARGET" "$OMG_PRESET"
2308
+ echo " ✓ Settings merged"
2309
+ else
2310
+ echo " ⊘ Skipped (manual merge needed)"
2311
+ fi
2312
+ else
2313
+ # read failed — only auto-apply if we can confirm non-interactive context
2314
+ # non-interactive fallback: check for clear non-interactive indicators
2315
+ if [ ! -t 0 ] || [ -n "${npm_lifecycle_event:-}" ] || [ -n "${npm_execpath:-}" ]; then
2316
+ python3 "$MERGE" "$TARGET" "$SOURCE"
2317
+ apply_omg_preset_to_settings "$TARGET" "$OMG_PRESET"
2318
+ echo " ✓ Settings merged (auto — non-interactive fallback)"
2319
+ else
2320
+ echo " ⚠ Could not read input. Skipping merge to be safe."
2321
+ echo " Run manually: ./OMG-setup.sh update --merge-policy=apply"
2322
+ fi
2323
+ fi
2324
+ fi
2325
+ fi
2326
+
2327
+ if $USE_SYMLINK; then
2328
+ # In symlink mode, link entire directories for templates
2329
+ if [ -e "$CLAUDE_DIR/templates/omg" ] || [ -L "$CLAUDE_DIR/templates/omg" ]; then
2330
+ rm -rf "$CLAUDE_DIR/templates/omg"
2331
+ fi
2332
+ ln -s "$SCRIPT_DIR/templates" "$CLAUDE_DIR/templates/omg"
2333
+ # Also link contextual rules
2334
+ mkdir -p "$CLAUDE_DIR/templates/omg/contextual-rules"
2335
+ for cr in "$SCRIPT_DIR"/rules/contextual/*.md; do
2336
+ [ -f "$cr" ] && track_file "templates/omg/contextual-rules/$(basename "$cr")"
2337
+ done
2338
+ else
2339
+ mkdir -p "$CLAUDE_DIR/templates/omg"
2340
+ cp "$SCRIPT_DIR"/templates/* "$CLAUDE_DIR/templates/omg/" 2>/dev/null || true
2341
+ for t in "$SCRIPT_DIR"/templates/*; do
2342
+ [ -f "$t" ] && track_file "templates/omg/$(basename "$t")"
2343
+ done
2344
+ mkdir -p "$CLAUDE_DIR/templates/omg/contextual-rules"
2345
+ cp "$SCRIPT_DIR"/rules/contextual/*.md "$CLAUDE_DIR/templates/omg/contextual-rules/" 2>/dev/null || true
2346
+ for cr in "$SCRIPT_DIR"/rules/contextual/*.md; do
2347
+ [ -f "$cr" ] && track_file "templates/omg/contextual-rules/$(basename "$cr")"
2348
+ done
2349
+ fi
2350
+ mkdir -p "$CLAUDE_DIR/templates/omg/state/memory"
2351
+ mkdir -p "$CLAUDE_DIR/templates/omg/state/learnings"
2352
+ mkdir -p "$CLAUDE_DIR/templates/omg/state/ledger"
2353
+ echo " \u2713 State directory templates (memory, learnings, ledger)"
2354
+ echo " \u2713 Templates + contextual rules"
2355
+
2356
+ if $USE_SYMLINK; then
2357
+ # In symlink mode, link runtime directories instead of copying
2358
+ mkdir -p "$CLAUDE_DIR/omg-runtime/scripts"
2359
+ install_file "$SCRIPT_DIR/scripts/omg.py" "$CLAUDE_DIR/omg-runtime/scripts/omg.py"
2360
+
2361
+ [[ "$CLAUDE_DIR/omg-runtime/runtime" == "$CLAUDE_DIR"* ]] || { echo "ERROR: symlink target outside expected directory: $CLAUDE_DIR/omg-runtime/runtime" >&2; exit 1; }
2362
+ [[ "$CLAUDE_DIR/omg-runtime/hooks" == "$CLAUDE_DIR"* ]] || { echo "ERROR: symlink target outside expected directory: $CLAUDE_DIR/omg-runtime/hooks" >&2; exit 1; }
2363
+ [[ "$CLAUDE_DIR/omg-runtime/lab" == "$CLAUDE_DIR"* ]] || { echo "ERROR: symlink target outside expected directory: $CLAUDE_DIR/omg-runtime/lab" >&2; exit 1; }
2364
+ [[ "$CLAUDE_DIR/omg-runtime/plugins" == "$CLAUDE_DIR"* ]] || { echo "ERROR: symlink target outside expected directory: $CLAUDE_DIR/omg-runtime/plugins" >&2; exit 1; }
2365
+ [[ "$CLAUDE_DIR/omg-runtime/registry" == "$CLAUDE_DIR"* ]] || { echo "ERROR: symlink target outside expected directory: $CLAUDE_DIR/omg-runtime/registry" >&2; exit 1; }
2366
+ [[ "$CLAUDE_DIR/omg-runtime/tools" == "$CLAUDE_DIR"* ]] || { echo "ERROR: symlink target outside expected directory: $CLAUDE_DIR/omg-runtime/tools" >&2; exit 1; }
2367
+ [[ "$CLAUDE_DIR/omg-runtime/control_plane" == "$CLAUDE_DIR"* ]] || { echo "ERROR: symlink target outside expected directory: $CLAUDE_DIR/omg-runtime/control_plane" >&2; exit 1; }
2368
+ [[ "$CLAUDE_DIR/omg-runtime/omg_natives" == "$CLAUDE_DIR"* ]] || { echo "ERROR: symlink target outside expected directory: $CLAUDE_DIR/omg-runtime/omg_natives" >&2; exit 1; }
2369
+ [[ "$CLAUDE_DIR/omg-runtime/yaml.py" == "$CLAUDE_DIR"* ]] || { echo "ERROR: symlink target outside expected directory: $CLAUDE_DIR/omg-runtime/yaml.py" >&2; exit 1; }
2370
+
2371
+ rm -rf "$CLAUDE_DIR/omg-runtime/runtime" "$CLAUDE_DIR/omg-runtime/hooks" "$CLAUDE_DIR/omg-runtime/lab" "$CLAUDE_DIR/omg-runtime/plugins" "$CLAUDE_DIR/omg-runtime/registry" "$CLAUDE_DIR/omg-runtime/tools" "$CLAUDE_DIR/omg-runtime/control_plane" "$CLAUDE_DIR/omg-runtime/omg_natives" "$CLAUDE_DIR/omg-runtime/yaml.py"
2372
+ ln -s "$SCRIPT_DIR/runtime" "$CLAUDE_DIR/omg-runtime/runtime"
2373
+ ln -s "$SCRIPT_DIR/hooks" "$CLAUDE_DIR/omg-runtime/hooks"
2374
+ ln -s "$SCRIPT_DIR/lab" "$CLAUDE_DIR/omg-runtime/lab"
2375
+ ln -s "$SCRIPT_DIR/plugins" "$CLAUDE_DIR/omg-runtime/plugins"
2376
+ ln -s "$SCRIPT_DIR/registry" "$CLAUDE_DIR/omg-runtime/registry"
2377
+ ln -s "$SCRIPT_DIR/tools" "$CLAUDE_DIR/omg-runtime/tools"
2378
+ ln -s "$SCRIPT_DIR/control_plane" "$CLAUDE_DIR/omg-runtime/control_plane"
2379
+ if [ -d "$SCRIPT_DIR/omg_natives" ]; then
2380
+ ln -s "$SCRIPT_DIR/omg_natives" "$CLAUDE_DIR/omg-runtime/omg_natives"
2381
+ else
2382
+ echo " ~ omg_natives not found in package — skipping (optional)" >&2
2383
+ fi
2384
+ ln -s "$SCRIPT_DIR/yaml.py" "$CLAUDE_DIR/omg-runtime/yaml.py"
2385
+
2386
+ [[ "$CLAUDE_DIR/omg-runtime" == "$CLAUDE_DIR"* ]] || { echo "ERROR: rm -rf target outside expected directory: $CLAUDE_DIR/omg-runtime" >&2; exit 1; }
2387
+ find "$CLAUDE_DIR/omg-runtime" -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true
2388
+ find "$CLAUDE_DIR/omg-runtime" -name "*.pyc" -delete 2>/dev/null || true
2389
+ echo " ✓ Portable runtime → $CLAUDE_DIR/omg-runtime (symlinked to $SCRIPT_DIR)"
2390
+ else
2391
+ mkdir -p "$CLAUDE_DIR/omg-runtime/scripts"
2392
+ cp "$SCRIPT_DIR/scripts/omg.py" "$CLAUDE_DIR/omg-runtime/scripts/omg.py"
2393
+ [[ "$CLAUDE_DIR/omg-runtime/runtime" == "$CLAUDE_DIR"* ]] || { echo "ERROR: rm -rf target outside expected directory: $CLAUDE_DIR/omg-runtime/runtime" >&2; exit 1; }
2394
+ rm -rf "$CLAUDE_DIR/omg-runtime/runtime" "$CLAUDE_DIR/omg-runtime/hooks" "$CLAUDE_DIR/omg-runtime/lab" "$CLAUDE_DIR/omg-runtime/plugins" "$CLAUDE_DIR/omg-runtime/registry" "$CLAUDE_DIR/omg-runtime/tools" "$CLAUDE_DIR/omg-runtime/control_plane" "$CLAUDE_DIR/omg-runtime/omg_natives" "$CLAUDE_DIR/omg-runtime/yaml.py"
2395
+ cp -R "$SCRIPT_DIR/runtime" "$CLAUDE_DIR/omg-runtime/"
2396
+ cp -R "$SCRIPT_DIR/hooks" "$CLAUDE_DIR/omg-runtime/"
2397
+ cp -R "$SCRIPT_DIR/lab" "$CLAUDE_DIR/omg-runtime/"
2398
+ cp -R "$SCRIPT_DIR/plugins" "$CLAUDE_DIR/omg-runtime/"
2399
+ cp -R "$SCRIPT_DIR/registry" "$CLAUDE_DIR/omg-runtime/"
2400
+ cp -R "$SCRIPT_DIR/tools" "$CLAUDE_DIR/omg-runtime/"
2401
+ cp -R "$SCRIPT_DIR/control_plane" "$CLAUDE_DIR/omg-runtime/"
2402
+ if [ -d "$SCRIPT_DIR/omg_natives" ]; then
2403
+ cp -R "$SCRIPT_DIR/omg_natives" "$CLAUDE_DIR/omg-runtime/"
2404
+ else
2405
+ echo " ~ omg_natives not found in package — skipping (optional)" >&2
2406
+ fi
2407
+ cp "$SCRIPT_DIR/yaml.py" "$CLAUDE_DIR/omg-runtime/yaml.py"
2408
+ [[ "$CLAUDE_DIR/omg-runtime" == "$CLAUDE_DIR"* ]] || { echo "ERROR: rm -rf target outside expected directory: $CLAUDE_DIR/omg-runtime" >&2; exit 1; }
2409
+ find "$CLAUDE_DIR/omg-runtime" -name "__pycache__" -type d -exec rm -rf {} + 2>/dev/null || true
2410
+ find "$CLAUDE_DIR/omg-runtime" -name "*.pyc" -delete 2>/dev/null || true
2411
+ echo " ✓ Portable runtime → $CLAUDE_DIR/omg-runtime"
2412
+ fi
2413
+ if $INSTALL_AS_PLUGIN; then
2414
+ install_plugin_bundle
2415
+ fi
2416
+
2417
+ provision_managed_venv
2418
+ write_managed_mcp_launcher
2419
+ patch_omg_control_mcp_python
2420
+ local pruned_plugin_duplicates=0
2421
+ pruned_plugin_duplicates=$(prune_plugin_duplicate_mcp_from_settings)
2422
+ if [ "${pruned_plugin_duplicates:-0}" -gt 0 ]; then
2423
+ echo " ✓ Removed duplicate plugin-managed MCP servers from .mcp.json ($pruned_plugin_duplicates)"
2424
+ fi
2425
+ local pruned_legacy_plugin_mcp=0
2426
+ pruned_legacy_plugin_mcp=$(prune_legacy_plugin_mcp_from_settings)
2427
+ if [ "${pruned_legacy_plugin_mcp:-0}" -gt 0 ]; then
2428
+ echo " ✓ Removed legacy plugin MCP servers from .mcp.json ($pruned_legacy_plugin_mcp)"
2429
+ fi
2430
+ configure_hud_status_line
2431
+ local configured_hosts=""
2432
+ configured_hosts=$(configure_detected_host_mcp_servers)
2433
+ if [ -n "$configured_hosts" ]; then
2434
+ echo " ✓ Host MCP configured for detected CLIs: $configured_hosts"
2435
+ fi
2436
+ if $ENABLE_BROWSER; then
2437
+ configure_browser_capability
2438
+ fi
2439
+
2440
+ local adoption_report_path=""
2441
+ adoption_report_path=$(write_native_adoption_report)
2442
+ echo " ✓ Adoption report → $adoption_report_path"
2443
+ else
2444
+ echo " (would merge settings.json + copy templates + provision portable runtime)"
2445
+ if $INSTALL_AS_PLUGIN; then
2446
+ install_plugin_bundle
2447
+ fi
2448
+ echo " (would provision managed Python venv at $CLAUDE_DIR/omg-runtime/.venv)"
2449
+ echo " (would wire omg-control into detected Codex/Gemini/Kimi configs when available)"
2450
+ if $ENABLE_BROWSER; then
2451
+ echo " (would enable browser capability metadata and remediation)"
2452
+ fi
2453
+ echo " (would write adoption report and apply preset/mode markers)"
2454
+ fi
2455
+
2456
+
2457
+ echo ""
2458
+ echo "Step 7/7: Reconcile stale files..."
2459
+ reconcile_stale_files
2460
+ write_omg_manifest
2461
+
2462
+ echo ""
2463
+ echo "═══════════════════════════════════════════════════════════════"
2464
+ if [ $ERRORS -eq 0 ]; then
2465
+ echo " ✅ OMG ${VERSION} ${ACTION} completed successfully"
2466
+ else
2467
+ echo " ⚠ OMG ${VERSION} ${ACTION} completed with $ERRORS error(s)"
2468
+ fi
2469
+ echo "═══════════════════════════════════════════════════════════════"
2470
+ echo ""
2471
+ echo " Files: $installed_rules rules, $installed_hooks hooks, $installed_agents agents, $installed_cmds commands"
2472
+ echo " Version: $VERSION"
2473
+ if $USE_SYMLINK; then
2474
+ echo " Mode: Symlink (live updates from $SCRIPT_DIR)"
2475
+ echo " Source: $SCRIPT_DIR"
2476
+ elif $FRESH_INSTALL; then
2477
+ echo " Mode: Fresh reinstall"
2478
+ elif [ -n "$existing_ver" ]; then
2479
+ echo " Upgraded: $existing_ver → $VERSION"
2480
+ echo " Backup: $BACKUP_DIR"
2481
+ fi
2482
+ # --- Post-install validation (runs AFTER all setup writes complete) ---
2483
+ echo ""
2484
+ echo "Post-install validation..."
2485
+ local validate_output=""
2486
+ local validate_rc=0
2487
+ if [ -n "${OMG_TEST_POST_INSTALL_VALIDATE_OUTPUT:-}" ]; then
2488
+ validate_output="${OMG_TEST_POST_INSTALL_VALIDATE_OUTPUT}"
2489
+ validate_rc="${OMG_TEST_POST_INSTALL_VALIDATE_RC:-1}"
2490
+ else
2491
+ validate_output=$(python3 "$SCRIPT_DIR/scripts/omg.py" validate --format json 2>&1) || validate_rc=$?
2492
+ fi
2493
+
2494
+ if ! $DRY_RUN && [ -n "$validate_output" ]; then
2495
+ mkdir -p "$CLAUDE_DIR/.omg/state"
2496
+ printf '%s\n' "$validate_output" > "$CLAUDE_DIR/.omg/state/post-install-validation.json"
2497
+ fi
2498
+
2499
+ if [ $validate_rc -eq 0 ]; then
2500
+ echo " ✅ Post-install validation passed"
2501
+ if ! $DRY_RUN; then
2502
+ echo " ✓ Artifact → $CLAUDE_DIR/.omg/state/post-install-validation.json"
2503
+ fi
2504
+ else
2505
+ echo " ❌ POST-INSTALL VALIDATION FAILED"
2506
+ echo ""
2507
+ printf '%s' "$validate_output" | python3 -c "
2508
+ import sys, json
2509
+ try:
2510
+ data = json.load(sys.stdin)
2511
+ blockers = [c for c in data.get('checks', []) if c.get('status') == 'blocker']
2512
+ if blockers:
2513
+ print(' Blockers:')
2514
+ for b in blockers:
2515
+ print(f' - {b[\"name\"]}: {b[\"message\"]}')
2516
+ else:
2517
+ print(' (no blocker details available)')
2518
+ except Exception:
2519
+ print(' (could not parse validation output)')
2520
+ " 2>/dev/null
2521
+ echo ""
2522
+ if ! $DRY_RUN; then
2523
+ echo " Validation artifact: $CLAUDE_DIR/.omg/state/post-install-validation.json"
2524
+ fi
2525
+ echo " Run: python3 $SCRIPT_DIR/scripts/omg.py validate"
2526
+ exit 1
2527
+ fi
2528
+
2529
+ echo ""
2530
+ if $DRY_RUN; then
2531
+ echo " *** DRY RUN — no files were changed ***"
2532
+ echo " Run without --dry-run to apply changes."
2533
+ fi
2534
+ }
2535
+
2536
+ main() {
2537
+ parse_args "$@"
2538
+ prompt_start_action
2539
+ case "$ACTION" in
2540
+ uninstall) run_uninstall ;;
2541
+ install|update|reinstall) run_install_like ;;
2542
+ *)
2543
+ usage
2544
+ exit 1
2545
+ ;;
2546
+ esac
2547
+ }
2548
+
2549
+ main "$@"