@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.
- package/CHANGELOG.md +188 -0
- package/INSTALL-VERIFICATION-INDEX.md +51 -0
- package/LICENSE +21 -0
- package/OMG-setup.sh +2549 -0
- package/QUICK-REFERENCE.md +58 -0
- package/README.md +207 -0
- package/agents/__init__.py +1 -0
- package/agents/__pycache__/model_roles.cpython-313.pyc +0 -0
- package/agents/_model_roles.yaml +26 -0
- package/agents/designer.md +67 -0
- package/agents/explore.md +60 -0
- package/agents/model_roles.py +196 -0
- package/agents/omg-api-builder.md +23 -0
- package/agents/omg-architect-mode.md +41 -0
- package/agents/omg-architect.md +13 -0
- package/agents/omg-backend-engineer.md +41 -0
- package/agents/omg-critic.md +16 -0
- package/agents/omg-database-engineer.md +41 -0
- package/agents/omg-escalation-router.md +17 -0
- package/agents/omg-executor.md +12 -0
- package/agents/omg-frontend-designer.md +41 -0
- package/agents/omg-implement-mode.md +49 -0
- package/agents/omg-infra-engineer.md +41 -0
- package/agents/omg-qa-tester.md +16 -0
- package/agents/omg-research-mode.md +41 -0
- package/agents/omg-security-auditor.md +41 -0
- package/agents/omg-testing-engineer.md +41 -0
- package/agents/plan.md +80 -0
- package/agents/quick_task.md +64 -0
- package/agents/reviewer.md +83 -0
- package/agents/task.md +71 -0
- package/bin/omg +41 -0
- package/commands/OMG:ai-commit.md +113 -0
- package/commands/OMG:api-twin.md +22 -0
- package/commands/OMG:arch.md +313 -0
- package/commands/OMG:browser.md +29 -0
- package/commands/OMG:ccg.md +22 -0
- package/commands/OMG:compat.md +57 -0
- package/commands/OMG:cost.md +181 -0
- package/commands/OMG:crazy.md +125 -0
- package/commands/OMG:create-agent.md +183 -0
- package/commands/OMG:deep-plan.md +18 -0
- package/commands/OMG:deps.md +248 -0
- package/commands/OMG:diagnose-plugins.md +33 -0
- package/commands/OMG:doctor.md +37 -0
- package/commands/OMG:domain-init.md +11 -0
- package/commands/OMG:escalate.md +52 -0
- package/commands/OMG:forge.md +103 -0
- package/commands/OMG:health-check.md +48 -0
- package/commands/OMG:init.md +134 -0
- package/commands/OMG:issue.md +56 -0
- package/commands/OMG:mode.md +44 -0
- package/commands/OMG:playwright.md +17 -0
- package/commands/OMG:preflight.md +26 -0
- package/commands/OMG:preset.md +49 -0
- package/commands/OMG:profile-review.md +58 -0
- package/commands/OMG:project-init.md +11 -0
- package/commands/OMG:ralph-start.md +43 -0
- package/commands/OMG:ralph-stop.md +23 -0
- package/commands/OMG:security-check.md +28 -0
- package/commands/OMG:session-branch.md +101 -0
- package/commands/OMG:session-fork.md +57 -0
- package/commands/OMG:session-merge.md +138 -0
- package/commands/OMG:setup.md +82 -0
- package/commands/OMG:ship.md +18 -0
- package/commands/OMG:stats.md +225 -0
- package/commands/OMG:teams.md +54 -0
- package/commands/OMG:theme.md +44 -0
- package/commands/OMG:validate.md +59 -0
- package/commands/__init__.py +1 -0
- package/docs/command-surface.md +55 -0
- package/docs/install/claude-code.md +53 -0
- package/docs/install/codex.md +45 -0
- package/docs/install/gemini.md +43 -0
- package/docs/install/github-action.md +81 -0
- package/docs/install/github-app-required-checks.md +107 -0
- package/docs/install/github-app.md +161 -0
- package/docs/install/kimi.md +43 -0
- package/docs/install/opencode.md +38 -0
- package/docs/proof.md +182 -0
- package/hooks/__init__.py +0 -0
- package/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_agent_registry.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_analytics.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_budget.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_common.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_compression_optimizer.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_cost_ledger.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_learnings.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_memory.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_post_write.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_protected_context.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_token_counter.cpython-313.pyc +0 -0
- package/hooks/__pycache__/branch_manager.cpython-313.pyc +0 -0
- package/hooks/__pycache__/budget_governor.cpython-313.pyc +0 -0
- package/hooks/__pycache__/circuit-breaker.cpython-313.pyc +0 -0
- package/hooks/__pycache__/compression_feedback.cpython-313.pyc +0 -0
- package/hooks/__pycache__/config-guard.cpython-313.pyc +0 -0
- package/hooks/__pycache__/context_pressure.cpython-313.pyc +0 -0
- package/hooks/__pycache__/credential_store.cpython-313.pyc +0 -0
- package/hooks/__pycache__/fetch-rate-limits.cpython-313.pyc +0 -0
- package/hooks/__pycache__/firewall.cpython-313.pyc +0 -0
- package/hooks/__pycache__/hashline-formatter-bridge.cpython-313.pyc +0 -0
- package/hooks/__pycache__/hashline-injector.cpython-313.pyc +0 -0
- package/hooks/__pycache__/hashline-validator.cpython-313.pyc +0 -0
- package/hooks/__pycache__/idle-detector.cpython-313.pyc +0 -0
- package/hooks/__pycache__/instructions-loaded.cpython-313.pyc +0 -0
- package/hooks/__pycache__/intentgate-keyword-detector.cpython-313.pyc +0 -0
- package/hooks/__pycache__/magic-keyword-router.cpython-313.pyc +0 -0
- package/hooks/__pycache__/policy_engine.cpython-313.pyc +0 -0
- package/hooks/__pycache__/post-tool-failure.cpython-313.pyc +0 -0
- package/hooks/__pycache__/post-write.cpython-313.pyc +0 -0
- package/hooks/__pycache__/post_write.cpython-313.pyc +0 -0
- package/hooks/__pycache__/pre-compact.cpython-313.pyc +0 -0
- package/hooks/__pycache__/pre-tool-inject.cpython-313.pyc +0 -0
- package/hooks/__pycache__/prompt-enhancer.cpython-313.pyc +0 -0
- package/hooks/__pycache__/quality-runner.cpython-313.pyc +0 -0
- package/hooks/__pycache__/query.cpython-313.pyc +0 -0
- package/hooks/__pycache__/secret-guard.cpython-313.pyc +0 -0
- package/hooks/__pycache__/secret_audit.cpython-313.pyc +0 -0
- package/hooks/__pycache__/security_validators.cpython-313.pyc +0 -0
- package/hooks/__pycache__/session-end-capture.cpython-313.pyc +0 -0
- package/hooks/__pycache__/session-start.cpython-313.pyc +0 -0
- package/hooks/__pycache__/setup_wizard.cpython-313.pyc +0 -0
- package/hooks/__pycache__/shadow_manager.cpython-313.pyc +0 -0
- package/hooks/__pycache__/state_migration.cpython-313.pyc +0 -0
- package/hooks/__pycache__/stop-gate.cpython-313.pyc +0 -0
- package/hooks/__pycache__/stop_dispatcher.cpython-313.pyc +0 -0
- package/hooks/__pycache__/tdd-gate.cpython-313.pyc +0 -0
- package/hooks/__pycache__/terms-guard.cpython-313.pyc +0 -0
- package/hooks/__pycache__/test-validator.cpython-313.pyc +0 -0
- package/hooks/__pycache__/test_generator_hook.cpython-313.pyc +0 -0
- package/hooks/__pycache__/todo-state-tracker.cpython-313.pyc +0 -0
- package/hooks/__pycache__/tool-ledger.cpython-313.pyc +0 -0
- package/hooks/__pycache__/trust_review.cpython-313.pyc +0 -0
- package/hooks/__pycache__/user-prompt-submit.cpython-313.pyc +0 -0
- package/hooks/_agent_registry.py +481 -0
- package/hooks/_analytics.py +291 -0
- package/hooks/_budget.py +31 -0
- package/hooks/_common.py +761 -0
- package/hooks/_compression_optimizer.py +119 -0
- package/hooks/_cost_ledger.py +176 -0
- package/hooks/_learnings.py +126 -0
- package/hooks/_memory.py +103 -0
- package/hooks/_post_write.py +46 -0
- package/hooks/_protected_context.py +150 -0
- package/hooks/_token_counter.py +221 -0
- package/hooks/branch_manager.py +255 -0
- package/hooks/budget_governor.py +326 -0
- package/hooks/circuit-breaker.py +270 -0
- package/hooks/compression_feedback.py +254 -0
- package/hooks/config-guard.py +193 -0
- package/hooks/context_pressure.py +119 -0
- package/hooks/credential_store.py +970 -0
- package/hooks/fetch-rate-limits.py +212 -0
- package/hooks/firewall.py +323 -0
- package/hooks/hashline-formatter-bridge.py +224 -0
- package/hooks/hashline-injector.py +273 -0
- package/hooks/hashline-validator.py +216 -0
- package/hooks/idle-detector.py +97 -0
- package/hooks/instructions-loaded.py +26 -0
- package/hooks/intentgate-keyword-detector.py +200 -0
- package/hooks/magic-keyword-router.py +195 -0
- package/hooks/policy_engine.py +767 -0
- package/hooks/post-tool-failure.py +19 -0
- package/hooks/post-write.py +233 -0
- package/hooks/pre-compact.py +470 -0
- package/hooks/pre-tool-inject.py +98 -0
- package/hooks/prompt-enhancer.py +879 -0
- package/hooks/quality-runner.py +191 -0
- package/hooks/query.py +512 -0
- package/hooks/secret-guard.py +120 -0
- package/hooks/secret_audit.py +144 -0
- package/hooks/security_validators.py +93 -0
- package/hooks/session-end-capture.py +505 -0
- package/hooks/session-start.py +261 -0
- package/hooks/setup_wizard.py +1101 -0
- package/hooks/shadow_manager.py +476 -0
- package/hooks/state_migration.py +228 -0
- package/hooks/stop-gate.py +7 -0
- package/hooks/stop_dispatcher.py +1259 -0
- package/hooks/tdd-gate.py +10 -0
- package/hooks/terms-guard.py +98 -0
- package/hooks/test-validator.py +462 -0
- package/hooks/test_generator_hook.py +123 -0
- package/hooks/todo-state-tracker.py +114 -0
- package/hooks/tool-ledger.py +165 -0
- package/hooks/trust_review.py +662 -0
- package/hooks/user-prompt-submit.py +12 -0
- package/hud/omg-hud.mjs +1571 -0
- package/lab/__init__.py +1 -0
- package/lab/__pycache__/__init__.cpython-313.pyc +0 -0
- package/lab/__pycache__/axolotl_adapter.cpython-313.pyc +0 -0
- package/lab/__pycache__/forge_runner.cpython-313.pyc +0 -0
- package/lab/__pycache__/gazebo_adapter.cpython-313.pyc +0 -0
- package/lab/__pycache__/isaac_gym_adapter.cpython-313.pyc +0 -0
- package/lab/__pycache__/mock_isaac_env.cpython-313.pyc +0 -0
- package/lab/__pycache__/pipeline.cpython-313.pyc +0 -0
- package/lab/__pycache__/policies.cpython-313.pyc +0 -0
- package/lab/__pycache__/pybullet_adapter.cpython-313.pyc +0 -0
- package/lab/axolotl_adapter.py +531 -0
- package/lab/forge_runner.py +103 -0
- package/lab/gazebo_adapter.py +168 -0
- package/lab/isaac_gym_adapter.py +190 -0
- package/lab/mock_isaac_env.py +47 -0
- package/lab/pipeline.py +712 -0
- package/lab/policies.py +52 -0
- package/lab/pybullet_adapter.py +192 -0
- package/package.json +61 -0
- package/plugins/README.md +78 -0
- package/plugins/__init__.py +1 -0
- package/plugins/__pycache__/__init__.cpython-313.pyc +0 -0
- package/plugins/advanced/commands/OMG-code-review.md +114 -0
- package/plugins/advanced/commands/OMG-deep-plan.md +266 -0
- package/plugins/advanced/commands/OMG-handoff.md +115 -0
- package/plugins/advanced/commands/OMG-learn.md +110 -0
- package/plugins/advanced/commands/OMG-maintainer.md +31 -0
- package/plugins/advanced/commands/OMG-ralph-start.md +43 -0
- package/plugins/advanced/commands/OMG-ralph-stop.md +23 -0
- package/plugins/advanced/commands/OMG-security-review.md +16 -0
- package/plugins/advanced/commands/OMG-sequential-thinking.md +20 -0
- package/plugins/advanced/commands/OMG-ship.md +46 -0
- package/plugins/advanced/commands/OMG:code-review.md +114 -0
- package/plugins/advanced/commands/OMG:deep-plan.md +266 -0
- package/plugins/advanced/commands/OMG:handoff.md +115 -0
- package/plugins/advanced/commands/OMG:learn.md +110 -0
- package/plugins/advanced/commands/OMG:maintainer.md +31 -0
- package/plugins/advanced/commands/OMG:ralph-start.md +43 -0
- package/plugins/advanced/commands/OMG:ralph-stop.md +23 -0
- package/plugins/advanced/commands/OMG:security-review.md +16 -0
- package/plugins/advanced/commands/OMG:sequential-thinking.md +20 -0
- package/plugins/advanced/commands/OMG:ship.md +46 -0
- package/plugins/advanced/plugin.json +104 -0
- package/plugins/core/plugin.json +204 -0
- package/plugins/dephealth/__init__.py +0 -0
- package/plugins/dephealth/__pycache__/__init__.cpython-313.pyc +0 -0
- package/plugins/dephealth/__pycache__/cve_scanner.cpython-313.pyc +0 -0
- package/plugins/dephealth/__pycache__/license_checker.cpython-313.pyc +0 -0
- package/plugins/dephealth/__pycache__/manifest_detector.cpython-313.pyc +0 -0
- package/plugins/dephealth/__pycache__/vuln_analyzer.cpython-313.pyc +0 -0
- package/plugins/dephealth/cve_scanner.py +279 -0
- package/plugins/dephealth/license_checker.py +135 -0
- package/plugins/dephealth/manifest_detector.py +423 -0
- package/plugins/dephealth/vuln_analyzer.py +176 -0
- package/plugins/testgen/__init__.py +0 -0
- package/plugins/testgen/__pycache__/__init__.cpython-313.pyc +0 -0
- package/plugins/testgen/__pycache__/codamosa_engine.cpython-313.pyc +0 -0
- package/plugins/testgen/__pycache__/edge_case_synthesizer.cpython-313.pyc +0 -0
- package/plugins/testgen/__pycache__/framework_detector.cpython-313.pyc +0 -0
- package/plugins/testgen/__pycache__/skeleton_generator.cpython-313.pyc +0 -0
- package/plugins/testgen/codamosa_engine.py +402 -0
- package/plugins/testgen/edge_case_synthesizer.py +184 -0
- package/plugins/testgen/framework_detector.py +271 -0
- package/plugins/testgen/skeleton_generator.py +219 -0
- package/plugins/viz/__init__.py +0 -0
- package/plugins/viz/__pycache__/__init__.cpython-313.pyc +0 -0
- package/plugins/viz/__pycache__/ast_parser.cpython-313.pyc +0 -0
- package/plugins/viz/__pycache__/diagram_generator.cpython-313.pyc +0 -0
- package/plugins/viz/__pycache__/graph_builder.cpython-313.pyc +0 -0
- package/plugins/viz/__pycache__/native_parsers.cpython-313.pyc +0 -0
- package/plugins/viz/__pycache__/regex_parser.cpython-313.pyc +0 -0
- package/plugins/viz/ast_parser.py +139 -0
- package/plugins/viz/diagram_generator.py +192 -0
- package/plugins/viz/graph_builder.py +444 -0
- package/plugins/viz/native_parsers.py +259 -0
- package/plugins/viz/regex_parser.py +112 -0
- package/pyproject.toml +143 -0
- package/registry/__init__.py +1 -0
- package/registry/__pycache__/__init__.cpython-313.pyc +0 -0
- package/registry/__pycache__/approval_artifact.cpython-313.pyc +0 -0
- package/registry/__pycache__/verify_artifact.cpython-313.pyc +0 -0
- package/registry/approval_artifact.py +236 -0
- package/registry/bundles/algorithms.yaml +45 -0
- package/registry/bundles/api-twin.yaml +48 -0
- package/registry/bundles/ast-pack.yaml +80 -0
- package/registry/bundles/claim-judge.yaml +49 -0
- package/registry/bundles/control-plane.yaml +192 -0
- package/registry/bundles/data-lineage.yaml +47 -0
- package/registry/bundles/delta-classifier.yaml +47 -0
- package/registry/bundles/eval-gate.yaml +47 -0
- package/registry/bundles/hash-edit.yaml +73 -0
- package/registry/bundles/health.yaml +45 -0
- package/registry/bundles/hook-governor.yaml +101 -0
- package/registry/bundles/incident-replay.yaml +47 -0
- package/registry/bundles/lsp-pack.yaml +80 -0
- package/registry/bundles/mcp-fabric.yaml +53 -0
- package/registry/bundles/plan-council.yaml +56 -0
- package/registry/bundles/preflight.yaml +48 -0
- package/registry/bundles/proof-gate.yaml +49 -0
- package/registry/bundles/remote-supervisor.yaml +49 -0
- package/registry/bundles/robotics.yaml +45 -0
- package/registry/bundles/secure-worktree-pipeline.yaml +69 -0
- package/registry/bundles/security-check.yaml +50 -0
- package/registry/bundles/terminal-lane.yaml +61 -0
- package/registry/bundles/test-intent-lock.yaml +49 -0
- package/registry/bundles/tracebank.yaml +47 -0
- package/registry/bundles/vision.yaml +45 -0
- package/registry/omg-capability.schema.json +378 -0
- package/registry/policy-packs/airgapped.lock.json +11 -0
- package/registry/policy-packs/airgapped.signature.json +10 -0
- package/registry/policy-packs/airgapped.yaml +16 -0
- package/registry/policy-packs/fintech.lock.json +11 -0
- package/registry/policy-packs/fintech.signature.json +10 -0
- package/registry/policy-packs/fintech.yaml +15 -0
- package/registry/policy-packs/locked-prod.lock.json +11 -0
- package/registry/policy-packs/locked-prod.signature.json +10 -0
- package/registry/policy-packs/locked-prod.yaml +18 -0
- package/registry/trusted_signers.json +44 -0
- package/registry/verify_artifact.py +493 -0
- package/runtime/__init__.py +36 -0
- package/runtime/__pycache__/__init__.cpython-313.pyc +0 -0
- package/runtime/__pycache__/adoption.cpython-313.pyc +0 -0
- package/runtime/__pycache__/agent_selector.cpython-313.pyc +0 -0
- package/runtime/__pycache__/api_twin.cpython-313.pyc +0 -0
- package/runtime/__pycache__/architecture_signal.cpython-313.pyc +0 -0
- package/runtime/__pycache__/artifact_parsers.cpython-313.pyc +0 -0
- package/runtime/__pycache__/asset_loader.cpython-313.pyc +0 -0
- package/runtime/__pycache__/background_verification.cpython-313.pyc +0 -0
- package/runtime/__pycache__/budget_envelopes.cpython-313.pyc +0 -0
- package/runtime/__pycache__/business_workflow.cpython-313.pyc +0 -0
- package/runtime/__pycache__/canonical_surface.cpython-313.pyc +0 -0
- package/runtime/__pycache__/canonical_taxonomy.cpython-313.pyc +0 -0
- package/runtime/__pycache__/claim_judge.cpython-313.pyc +0 -0
- package/runtime/__pycache__/cli_provider.cpython-313.pyc +0 -0
- package/runtime/__pycache__/compat.cpython-313.pyc +0 -0
- package/runtime/__pycache__/complexity_scorer.cpython-313.pyc +0 -0
- package/runtime/__pycache__/compliance_governor.cpython-313.pyc +0 -0
- package/runtime/__pycache__/config_transaction.cpython-313.pyc +0 -0
- package/runtime/__pycache__/context_compiler.cpython-313.pyc +0 -0
- package/runtime/__pycache__/context_engine.cpython-313.pyc +0 -0
- package/runtime/__pycache__/context_limits.cpython-313.pyc +0 -0
- package/runtime/__pycache__/contract_compiler.cpython-313.pyc +0 -0
- package/runtime/__pycache__/custom_agent_loader.cpython-313.pyc +0 -0
- package/runtime/__pycache__/data_lineage.cpython-313.pyc +0 -0
- package/runtime/__pycache__/defense_state.cpython-313.pyc +0 -0
- package/runtime/__pycache__/delta_classifier.cpython-313.pyc +0 -0
- package/runtime/__pycache__/dispatcher.cpython-313.pyc +0 -0
- package/runtime/__pycache__/doc_generator.cpython-313.pyc +0 -0
- package/runtime/__pycache__/domain_packs.cpython-313.pyc +0 -0
- package/runtime/__pycache__/ecosystem.cpython-313.pyc +0 -0
- package/runtime/__pycache__/equalizer.cpython-313.pyc +0 -0
- package/runtime/__pycache__/eval_gate.cpython-313.pyc +0 -0
- package/runtime/__pycache__/evidence_narrator.cpython-313.pyc +0 -0
- package/runtime/__pycache__/evidence_query.cpython-313.pyc +0 -0
- package/runtime/__pycache__/evidence_registry.cpython-313.pyc +0 -0
- package/runtime/__pycache__/evidence_requirements.cpython-313.pyc +0 -0
- package/runtime/__pycache__/exec_kernel.cpython-313.pyc +0 -0
- package/runtime/__pycache__/explainer_formatter.cpython-313.pyc +0 -0
- package/runtime/__pycache__/feature_registry.cpython-313.pyc +0 -0
- package/runtime/__pycache__/forge_agents.cpython-313.pyc +0 -0
- package/runtime/__pycache__/forge_contracts.cpython-313.pyc +0 -0
- package/runtime/__pycache__/forge_domains.cpython-313.pyc +0 -0
- package/runtime/__pycache__/forge_run_id.cpython-313.pyc +0 -0
- package/runtime/__pycache__/github_integration.cpython-313.pyc +0 -0
- package/runtime/__pycache__/github_review_bot.cpython-313.pyc +0 -0
- package/runtime/__pycache__/github_review_contract.cpython-313.pyc +0 -0
- package/runtime/__pycache__/github_review_formatter.cpython-313.pyc +0 -0
- package/runtime/__pycache__/guide_assert.cpython-313.pyc +0 -0
- package/runtime/__pycache__/hook_governor.cpython-313.pyc +0 -0
- package/runtime/__pycache__/host_parity.cpython-313.pyc +0 -0
- package/runtime/__pycache__/incident_replay.cpython-313.pyc +0 -0
- package/runtime/__pycache__/install_planner.cpython-313.pyc +0 -0
- package/runtime/__pycache__/interaction_journal.cpython-313.pyc +0 -0
- package/runtime/__pycache__/issue_surface.cpython-313.pyc +0 -0
- package/runtime/__pycache__/legacy_compat.cpython-313.pyc +0 -0
- package/runtime/__pycache__/mcp_config_writers.cpython-313.pyc +0 -0
- package/runtime/__pycache__/mcp_lifecycle.cpython-313.pyc +0 -0
- package/runtime/__pycache__/mcp_memory_server.cpython-313.pyc +0 -0
- package/runtime/__pycache__/memory_store.cpython-313.pyc +0 -0
- package/runtime/__pycache__/merge_writer.cpython-313.pyc +0 -0
- package/runtime/__pycache__/music_omr_testbed.cpython-313.pyc +0 -0
- package/runtime/__pycache__/mutation_gate.cpython-313.pyc +0 -0
- package/runtime/__pycache__/omc_compat.cpython-313.pyc +0 -0
- package/runtime/__pycache__/omg_browser_cli.cpython-313.pyc +0 -0
- package/runtime/__pycache__/omg_mcp_server.cpython-313.pyc +0 -0
- package/runtime/__pycache__/opus_plan.cpython-313.pyc +0 -0
- package/runtime/__pycache__/playwright_adapter.cpython-313.pyc +0 -0
- package/runtime/__pycache__/playwright_pack.cpython-313.pyc +0 -0
- package/runtime/__pycache__/plugin_diagnostics.cpython-313.pyc +0 -0
- package/runtime/__pycache__/plugin_interop.cpython-313.pyc +0 -0
- package/runtime/__pycache__/policy_pack_loader.cpython-313.pyc +0 -0
- package/runtime/__pycache__/preflight.cpython-313.pyc +0 -0
- package/runtime/__pycache__/profile_io.cpython-313.pyc +0 -0
- package/runtime/__pycache__/prompt_compiler.cpython-313.pyc +0 -0
- package/runtime/__pycache__/proof_chain.cpython-313.pyc +0 -0
- package/runtime/__pycache__/proof_gate.cpython-313.pyc +0 -0
- package/runtime/__pycache__/provider_parity_eval.cpython-313.pyc +0 -0
- package/runtime/__pycache__/release_artifact_audit.cpython-313.pyc +0 -0
- package/runtime/__pycache__/release_run_coordinator.cpython-313.pyc +0 -0
- package/runtime/__pycache__/release_surface_compiler.cpython-313.pyc +0 -0
- package/runtime/__pycache__/release_surface_registry.cpython-313.pyc +0 -0
- package/runtime/__pycache__/release_surfaces.cpython-313.pyc +0 -0
- package/runtime/__pycache__/remote_supervisor.cpython-313.pyc +0 -0
- package/runtime/__pycache__/repro_pack.cpython-313.pyc +0 -0
- package/runtime/__pycache__/rollback_manifest.cpython-313.pyc +0 -0
- package/runtime/__pycache__/router_critics.cpython-313.pyc +0 -0
- package/runtime/__pycache__/router_executor.cpython-313.pyc +0 -0
- package/runtime/__pycache__/router_selector.cpython-313.pyc +0 -0
- package/runtime/__pycache__/runtime_contracts.cpython-313.pyc +0 -0
- package/runtime/__pycache__/runtime_profile.cpython-313.pyc +0 -0
- package/runtime/__pycache__/security_check.cpython-313.pyc +0 -0
- package/runtime/__pycache__/session_health.cpython-313.pyc +0 -0
- package/runtime/__pycache__/skill_evolution.cpython-313.pyc +0 -0
- package/runtime/__pycache__/skill_registry.cpython-313.pyc +0 -0
- package/runtime/__pycache__/subagent_dispatcher.cpython-313.pyc +0 -0
- package/runtime/__pycache__/subscription_tiers.cpython-313.pyc +0 -0
- package/runtime/__pycache__/team_router.cpython-313.pyc +0 -0
- package/runtime/__pycache__/test_intent_lock.cpython-313-pytest-9.0.2.pyc +0 -0
- package/runtime/__pycache__/test_intent_lock.cpython-313.pyc +0 -0
- package/runtime/__pycache__/tmux_session_manager.cpython-313.pyc +0 -0
- package/runtime/__pycache__/tool_fabric.cpython-313.pyc +0 -0
- package/runtime/__pycache__/tool_plan_gate.cpython-313.pyc +0 -0
- package/runtime/__pycache__/tool_relevance.cpython-313.pyc +0 -0
- package/runtime/__pycache__/tracebank.cpython-313.pyc +0 -0
- package/runtime/__pycache__/untrusted_content.cpython-313.pyc +0 -0
- package/runtime/__pycache__/validate.cpython-313.pyc +0 -0
- package/runtime/__pycache__/verdict_schema.cpython-313.pyc +0 -0
- package/runtime/__pycache__/verification_controller.cpython-313.pyc +0 -0
- package/runtime/__pycache__/verification_loop.cpython-313.pyc +0 -0
- package/runtime/__pycache__/vision_artifacts.cpython-313.pyc +0 -0
- package/runtime/__pycache__/vision_cache.cpython-313.pyc +0 -0
- package/runtime/__pycache__/vision_jobs.cpython-313.pyc +0 -0
- package/runtime/__pycache__/worker_watchdog.cpython-313.pyc +0 -0
- package/runtime/adapters/__init__.py +13 -0
- package/runtime/adapters/__pycache__/__init__.cpython-313.pyc +0 -0
- package/runtime/adapters/__pycache__/claude.cpython-313.pyc +0 -0
- package/runtime/adapters/__pycache__/gpt.cpython-313.pyc +0 -0
- package/runtime/adapters/__pycache__/local.cpython-313.pyc +0 -0
- package/runtime/adapters/claude.py +63 -0
- package/runtime/adapters/gpt.py +56 -0
- package/runtime/adapters/local.py +56 -0
- package/runtime/adoption.py +280 -0
- package/runtime/api_twin.py +450 -0
- package/runtime/architecture_signal.py +226 -0
- package/runtime/artifact_parsers.py +161 -0
- package/runtime/asset_loader.py +62 -0
- package/runtime/background_verification.py +178 -0
- package/runtime/budget_envelopes.py +398 -0
- package/runtime/business_workflow.py +234 -0
- package/runtime/canonical_surface.py +53 -0
- package/runtime/canonical_taxonomy.py +27 -0
- package/runtime/claim_judge.py +648 -0
- package/runtime/cli_provider.py +105 -0
- package/runtime/compat.py +2222 -0
- package/runtime/complexity_scorer.py +148 -0
- package/runtime/compliance_governor.py +505 -0
- package/runtime/config_transaction.py +304 -0
- package/runtime/context_compiler.py +131 -0
- package/runtime/context_engine.py +708 -0
- package/runtime/context_limits.py +363 -0
- package/runtime/contract_compiler.py +3664 -0
- package/runtime/custom_agent_loader.py +366 -0
- package/runtime/data_lineage.py +244 -0
- package/runtime/defense_state.py +261 -0
- package/runtime/delta_classifier.py +231 -0
- package/runtime/dispatcher.py +47 -0
- package/runtime/doc_generator.py +319 -0
- package/runtime/domain_packs.py +75 -0
- package/runtime/ecosystem.py +371 -0
- package/runtime/equalizer.py +268 -0
- package/runtime/eval_gate.py +96 -0
- package/runtime/evidence_narrator.py +147 -0
- package/runtime/evidence_query.py +303 -0
- package/runtime/evidence_registry.py +16 -0
- package/runtime/evidence_requirements.py +157 -0
- package/runtime/exec_kernel.py +267 -0
- package/runtime/explainer_formatter.py +82 -0
- package/runtime/feature_registry.py +109 -0
- package/runtime/forge_agents.py +915 -0
- package/runtime/forge_contracts.py +519 -0
- package/runtime/forge_domains.py +68 -0
- package/runtime/forge_run_id.py +86 -0
- package/runtime/guide_assert.py +135 -0
- package/runtime/hook_governor.py +156 -0
- package/runtime/host_parity.py +373 -0
- package/runtime/incident_replay.py +310 -0
- package/runtime/install_planner.py +617 -0
- package/runtime/interaction_journal.py +566 -0
- package/runtime/issue_surface.py +472 -0
- package/runtime/legacy_compat.py +7 -0
- package/runtime/mcp_config_writers.py +360 -0
- package/runtime/mcp_lifecycle.py +175 -0
- package/runtime/mcp_memory_server.py +220 -0
- package/runtime/memory_parsers/__init__.py +0 -0
- package/runtime/memory_parsers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/runtime/memory_parsers/__pycache__/chatgpt_parser.cpython-313.pyc +0 -0
- package/runtime/memory_parsers/__pycache__/claude_import.cpython-313.pyc +0 -0
- package/runtime/memory_parsers/__pycache__/export.cpython-313.pyc +0 -0
- package/runtime/memory_parsers/__pycache__/gemini_import.cpython-313.pyc +0 -0
- package/runtime/memory_parsers/__pycache__/kimi_import.cpython-313.pyc +0 -0
- package/runtime/memory_parsers/chatgpt_parser.py +257 -0
- package/runtime/memory_parsers/claude_import.py +107 -0
- package/runtime/memory_parsers/export.py +97 -0
- package/runtime/memory_parsers/gemini_import.py +91 -0
- package/runtime/memory_parsers/kimi_import.py +91 -0
- package/runtime/memory_store.py +1182 -0
- package/runtime/merge_writer.py +445 -0
- package/runtime/music_omr_testbed.py +336 -0
- package/runtime/mutation_gate.py +320 -0
- package/runtime/omc_compat.py +7 -0
- package/runtime/omg_browser_cli.py +95 -0
- package/runtime/omg_compat_contract_snapshot.json +936 -0
- package/runtime/omg_contract_snapshot.json +936 -0
- package/runtime/omg_mcp_server.py +306 -0
- package/runtime/playwright_adapter.py +39 -0
- package/runtime/playwright_pack.py +253 -0
- package/runtime/plugin_diagnostics.py +308 -0
- package/runtime/plugin_interop.py +1060 -0
- package/runtime/policy_pack_loader.py +147 -0
- package/runtime/preflight.py +135 -0
- package/runtime/profile_io.py +328 -0
- package/runtime/proof_chain.py +472 -0
- package/runtime/proof_gate.py +442 -0
- package/runtime/provider_parity_eval.py +109 -0
- package/runtime/providers/__init__.py +0 -0
- package/runtime/providers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/runtime/providers/__pycache__/codex_provider.cpython-313.pyc +0 -0
- package/runtime/providers/__pycache__/gemini_provider.cpython-313.pyc +0 -0
- package/runtime/providers/__pycache__/kimi_provider.cpython-313.pyc +0 -0
- package/runtime/providers/__pycache__/opencode_provider.cpython-313.pyc +0 -0
- package/runtime/providers/codex_provider.py +129 -0
- package/runtime/providers/gemini_provider.py +143 -0
- package/runtime/providers/kimi_provider.py +167 -0
- package/runtime/providers/opencode_provider.py +99 -0
- package/runtime/release_artifact_audit.py +556 -0
- package/runtime/release_run_coordinator.py +574 -0
- package/runtime/release_surface_compiler.py +643 -0
- package/runtime/release_surface_registry.py +283 -0
- package/runtime/release_surfaces.py +320 -0
- package/runtime/remote_supervisor.py +79 -0
- package/runtime/repro_pack.py +398 -0
- package/runtime/rollback_manifest.py +143 -0
- package/runtime/router_critics.py +229 -0
- package/runtime/router_executor.py +142 -0
- package/runtime/router_selector.py +99 -0
- package/runtime/runtime_contracts.py +292 -0
- package/runtime/runtime_profile.py +133 -0
- package/runtime/security_check.py +1094 -0
- package/runtime/session_health.py +546 -0
- package/runtime/skill_evolution.py +221 -0
- package/runtime/skill_registry.py +53 -0
- package/runtime/subagent_dispatcher.py +604 -0
- package/runtime/subscription_tiers.py +258 -0
- package/runtime/team_router.py +1399 -0
- package/runtime/test_intent_lock.py +543 -0
- package/runtime/tmux_session_manager.py +172 -0
- package/runtime/tool_fabric.py +570 -0
- package/runtime/tool_plan_gate.py +460 -0
- package/runtime/tracebank.py +125 -0
- package/runtime/untrusted_content.py +360 -0
- package/runtime/validate.py +293 -0
- package/runtime/verdict_schema.py +198 -0
- package/runtime/verification_controller.py +235 -0
- package/runtime/verification_loop.py +73 -0
- package/runtime/vision_artifacts.py +31 -0
- package/runtime/vision_cache.py +38 -0
- package/runtime/vision_jobs.py +92 -0
- package/runtime/worker_watchdog.py +526 -0
- package/scripts/__pycache__/audit-published-artifact.cpython-313.pyc +0 -0
- package/scripts/__pycache__/check-doc-parity.cpython-313.pyc +0 -0
- package/scripts/__pycache__/check-omg-standalone-clean.cpython-313.pyc +0 -0
- package/scripts/__pycache__/github_review_helpers.cpython-313.pyc +0 -0
- package/scripts/__pycache__/omg.cpython-313.pyc +0 -0
- package/scripts/__pycache__/prepare-release-proof-fixtures.cpython-313.pyc +0 -0
- package/scripts/__pycache__/sync-release-identity.cpython-313.pyc +0 -0
- package/scripts/__pycache__/validate-release-identity.cpython-313.pyc +0 -0
- package/scripts/audit-published-artifact.py +59 -0
- package/scripts/check-omg-compat-contract-snapshot.py +137 -0
- package/scripts/check-omg-contract-snapshot.py +12 -0
- package/scripts/check-omg-public-ready.py +273 -0
- package/scripts/check-omg-standalone-clean.py +133 -0
- package/scripts/emit_host_parity.py +72 -0
- package/scripts/legacy_to_omg_migrate.py +29 -0
- package/scripts/migrate-legacy.py +464 -0
- package/scripts/omc_to_omg_migrate.py +12 -0
- package/scripts/omg.py +2962 -0
- package/scripts/pre-release-check.sh +38 -0
- package/scripts/prepare-release-proof-fixtures.py +602 -0
- package/scripts/print-canonical-version.py +80 -0
- package/scripts/settings-merge.py +289 -0
- package/scripts/sync-release-identity.py +481 -0
- package/scripts/validate-release-identity.py +632 -0
- package/scripts/verify-no-omc.sh +5 -0
- package/scripts/verify-standalone.sh +35 -0
- package/settings.json +751 -0
- package/tools/__init__.py +2 -0
- package/tools/__pycache__/__init__.cpython-313.pyc +0 -0
- package/tools/__pycache__/browser_consent.cpython-313.pyc +0 -0
- package/tools/__pycache__/browser_stealth.cpython-313.pyc +0 -0
- package/tools/__pycache__/browser_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/changelog_generator.cpython-313.pyc +0 -0
- package/tools/__pycache__/commit_splitter.cpython-313.pyc +0 -0
- package/tools/__pycache__/config_discovery.cpython-313.pyc +0 -0
- package/tools/__pycache__/config_merger.cpython-313.pyc +0 -0
- package/tools/__pycache__/dashboard_generator.cpython-313.pyc +0 -0
- package/tools/__pycache__/git_inspector.cpython-313.pyc +0 -0
- package/tools/__pycache__/lsp_client.cpython-313.pyc +0 -0
- package/tools/__pycache__/lsp_operations.cpython-313.pyc +0 -0
- package/tools/__pycache__/pr_generator.cpython-313.pyc +0 -0
- package/tools/__pycache__/python_repl.cpython-313.pyc +0 -0
- package/tools/__pycache__/python_sandbox.cpython-313.pyc +0 -0
- package/tools/__pycache__/session_snapshot.cpython-313.pyc +0 -0
- package/tools/__pycache__/ssh_manager.cpython-313.pyc +0 -0
- package/tools/__pycache__/theme_engine.cpython-313.pyc +0 -0
- package/tools/__pycache__/theme_selector.cpython-313.pyc +0 -0
- package/tools/__pycache__/web_search.cpython-313.pyc +0 -0
- package/tools/browser_consent.py +289 -0
- package/tools/browser_stealth.py +481 -0
- package/tools/browser_tool.py +448 -0
- package/tools/changelog_generator.py +347 -0
- package/tools/commit_splitter.py +749 -0
- package/tools/config_discovery.py +151 -0
- package/tools/config_merger.py +449 -0
- package/tools/dashboard_generator.py +300 -0
- package/tools/git_inspector.py +298 -0
- package/tools/lsp_client.py +275 -0
- package/tools/lsp_discovery.py +231 -0
- package/tools/lsp_operations.py +392 -0
- package/tools/pr_generator.py +404 -0
- package/tools/python_repl.py +712 -0
- package/tools/python_sandbox.py +768 -0
- package/tools/search_providers/__init__.py +77 -0
- package/tools/search_providers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/tools/search_providers/__pycache__/brave.cpython-313.pyc +0 -0
- package/tools/search_providers/__pycache__/exa.cpython-313.pyc +0 -0
- package/tools/search_providers/__pycache__/jina.cpython-313.pyc +0 -0
- package/tools/search_providers/__pycache__/perplexity.cpython-313.pyc +0 -0
- package/tools/search_providers/__pycache__/synthetic.cpython-313.pyc +0 -0
- package/tools/search_providers/brave.py +115 -0
- package/tools/search_providers/exa.py +116 -0
- package/tools/search_providers/jina.py +104 -0
- package/tools/search_providers/perplexity.py +139 -0
- package/tools/search_providers/synthetic.py +74 -0
- package/tools/session_snapshot.py +851 -0
- package/tools/ssh_manager.py +912 -0
- package/tools/theme_engine.py +296 -0
- package/tools/theme_selector.py +137 -0
- 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 "$@"
|