@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
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Web Search Provider Abstraction for OMG
|
|
4
|
+
|
|
5
|
+
Clean provider interface for web search with credential integration.
|
|
6
|
+
Providers register via the abstract base class; WebSearchManager dispatches
|
|
7
|
+
search/fetch calls to the active provider.
|
|
8
|
+
|
|
9
|
+
Feature flag: OMG_WEB_SEARCH_ENABLED (default: False)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import abc
|
|
13
|
+
import importlib
|
|
14
|
+
import time
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
import urllib.error
|
|
19
|
+
import urllib.request
|
|
20
|
+
from dataclasses import asdict, dataclass, field
|
|
21
|
+
from typing import Any, Dict, List, Optional
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# --- Lazy imports for hooks/_common.py ---
|
|
25
|
+
|
|
26
|
+
_get_feature_flag = None
|
|
27
|
+
_atomic_json_write = None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _ensure_imports():
|
|
31
|
+
"""Lazy import feature flag and atomic write from hooks/_common.py."""
|
|
32
|
+
global _get_feature_flag, _atomic_json_write
|
|
33
|
+
if _get_feature_flag is not None:
|
|
34
|
+
return
|
|
35
|
+
repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
36
|
+
if repo_root not in sys.path:
|
|
37
|
+
sys.path.insert(0, repo_root)
|
|
38
|
+
try:
|
|
39
|
+
from hooks._common import get_feature_flag as _gff
|
|
40
|
+
from hooks._common import atomic_json_write as _ajw
|
|
41
|
+
_get_feature_flag = _gff
|
|
42
|
+
_atomic_json_write = _ajw
|
|
43
|
+
except ImportError:
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# --- Lazy import for credential_store (OPTIONAL) ---
|
|
48
|
+
|
|
49
|
+
_credential_store = None
|
|
50
|
+
_has_credential_store: Optional[bool] = None
|
|
51
|
+
# Backward-compat test seam (patched in existing tests).
|
|
52
|
+
_HAS_CREDENTIAL_STORE = None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _resolve_project_dir() -> str:
|
|
56
|
+
return os.environ.get("CLAUDE_PROJECT_DIR") or os.getcwd()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _mark_external_ingress(*, source_ref: str, content: Any) -> None:
|
|
60
|
+
try:
|
|
61
|
+
from runtime.untrusted_content import TrustTier, mark_untrusted_content
|
|
62
|
+
except Exception:
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
payload = content
|
|
66
|
+
if not isinstance(payload, str):
|
|
67
|
+
payload = json.dumps(payload, sort_keys=True, ensure_ascii=True)
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
mark_untrusted_content(
|
|
71
|
+
_resolve_project_dir(),
|
|
72
|
+
source_type="web",
|
|
73
|
+
source_ref=source_ref,
|
|
74
|
+
content=payload,
|
|
75
|
+
tier=TrustTier.RESEARCH,
|
|
76
|
+
)
|
|
77
|
+
except Exception:
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _check_credential_store() -> bool:
|
|
82
|
+
"""Check if credential_store is available (cached after first check)."""
|
|
83
|
+
global _has_credential_store, _credential_store
|
|
84
|
+
override = globals().get("_HAS_CREDENTIAL_STORE")
|
|
85
|
+
if override is not None:
|
|
86
|
+
return bool(override)
|
|
87
|
+
|
|
88
|
+
if _has_credential_store is None:
|
|
89
|
+
try:
|
|
90
|
+
repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
91
|
+
hooks_dir = os.path.join(repo_root, "hooks")
|
|
92
|
+
if hooks_dir not in sys.path:
|
|
93
|
+
sys.path.insert(0, hooks_dir)
|
|
94
|
+
_cs = importlib.import_module("credential_store")
|
|
95
|
+
_credential_store = _cs
|
|
96
|
+
_has_credential_store = True
|
|
97
|
+
except ImportError:
|
|
98
|
+
_has_credential_store = False
|
|
99
|
+
return bool(_has_credential_store)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# --- Feature flag ---
|
|
103
|
+
|
|
104
|
+
def _is_enabled() -> bool:
|
|
105
|
+
"""Check if Web Search feature is enabled."""
|
|
106
|
+
# Fast path: check env var directly
|
|
107
|
+
env_val = os.environ.get("OMG_WEB_SEARCH_ENABLED", "").lower()
|
|
108
|
+
if env_val in ("0", "false", "no"):
|
|
109
|
+
return False
|
|
110
|
+
if env_val in ("1", "true", "yes"):
|
|
111
|
+
return True
|
|
112
|
+
# Fallback to hooks/_common.get_feature_flag
|
|
113
|
+
_ensure_imports()
|
|
114
|
+
if _get_feature_flag is not None:
|
|
115
|
+
return _get_feature_flag("WEB_SEARCH", default=False)
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# =============================================================================
|
|
120
|
+
# Data Types
|
|
121
|
+
# =============================================================================
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@dataclass
|
|
125
|
+
class SearchResult:
|
|
126
|
+
"""A single search result from a web search provider.
|
|
127
|
+
|
|
128
|
+
Attributes:
|
|
129
|
+
title: The title of the search result.
|
|
130
|
+
url: The URL of the search result.
|
|
131
|
+
snippet: A text snippet or summary from the result.
|
|
132
|
+
source: The provider name that produced this result.
|
|
133
|
+
"""
|
|
134
|
+
title: str
|
|
135
|
+
url: str
|
|
136
|
+
snippet: str
|
|
137
|
+
source: str
|
|
138
|
+
|
|
139
|
+
def to_dict(self) -> Dict[str, str]:
|
|
140
|
+
"""Convert to a plain dictionary."""
|
|
141
|
+
return asdict(self)
|
|
142
|
+
|
|
143
|
+
@classmethod
|
|
144
|
+
def from_dict(cls, data: Dict[str, str]) -> "SearchResult":
|
|
145
|
+
"""Create a SearchResult from a dictionary.
|
|
146
|
+
|
|
147
|
+
Accepts dicts with at least 'title', 'url', 'snippet', 'source' keys.
|
|
148
|
+
Missing keys default to empty string.
|
|
149
|
+
"""
|
|
150
|
+
return cls(
|
|
151
|
+
title=data.get("title", ""),
|
|
152
|
+
url=data.get("url", ""),
|
|
153
|
+
snippet=data.get("snippet", ""),
|
|
154
|
+
source=data.get("source", ""),
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# =============================================================================
|
|
159
|
+
# Abstract Provider Base Class
|
|
160
|
+
# =============================================================================
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class Provider(abc.ABC):
|
|
164
|
+
"""Abstract base class for web search providers.
|
|
165
|
+
|
|
166
|
+
Subclasses must implement `search()` and `fetch()`.
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
@abc.abstractmethod
|
|
170
|
+
def search(self, query: str, **kwargs: Any) -> List[Dict[str, str]]:
|
|
171
|
+
"""Search the web for the given query.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
query: The search query string.
|
|
175
|
+
**kwargs: Provider-specific options (e.g., num_results, language).
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
A list of dicts, each with at least 'title', 'url', 'snippet' keys.
|
|
179
|
+
"""
|
|
180
|
+
...
|
|
181
|
+
|
|
182
|
+
@abc.abstractmethod
|
|
183
|
+
def fetch(self, url: str) -> str:
|
|
184
|
+
"""Fetch the content of a URL and return it as text.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
url: The URL to fetch.
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
The page content as a string.
|
|
191
|
+
"""
|
|
192
|
+
...
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def name(self) -> str:
|
|
196
|
+
"""Provider name (defaults to class name in lowercase)."""
|
|
197
|
+
return self.__class__.__name__.lower()
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
# =============================================================================
|
|
201
|
+
# Credential Lookup
|
|
202
|
+
# =============================================================================
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def get_api_key(provider_name: str) -> Optional[str]:
|
|
206
|
+
"""Get the API key for a provider.
|
|
207
|
+
|
|
208
|
+
Resolution order:
|
|
209
|
+
1. credential_store.get_active_key(provider_name) if available
|
|
210
|
+
2. Environment variable {PROVIDER_NAME}_API_KEY
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
provider_name: The name of the provider (e.g., 'exa', 'serper').
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
The API key string, or None if not found.
|
|
217
|
+
"""
|
|
218
|
+
# Try credential store first
|
|
219
|
+
if _check_credential_store() and _credential_store is not None:
|
|
220
|
+
try:
|
|
221
|
+
key = _credential_store.get_active_key(provider_name)
|
|
222
|
+
if key:
|
|
223
|
+
return key
|
|
224
|
+
except (RuntimeError, ValueError, OSError):
|
|
225
|
+
pass # Fall through to env var
|
|
226
|
+
|
|
227
|
+
# Fallback to environment variable
|
|
228
|
+
env_key = f"{provider_name.upper()}_API_KEY"
|
|
229
|
+
return os.environ.get(env_key)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
# =============================================================================
|
|
233
|
+
# Web Search Manager
|
|
234
|
+
# =============================================================================
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class WebSearchManager:
|
|
238
|
+
"""Central manager for web search operations.
|
|
239
|
+
|
|
240
|
+
Manages registered providers and dispatches search/fetch calls.
|
|
241
|
+
All public methods return early with error/empty if the feature flag
|
|
242
|
+
is disabled.
|
|
243
|
+
"""
|
|
244
|
+
|
|
245
|
+
def __init__(self) -> None:
|
|
246
|
+
self._providers: Dict[str, Provider] = {}
|
|
247
|
+
self._default_provider: Optional[str] = None
|
|
248
|
+
|
|
249
|
+
def register_provider(self, name: str, provider: Provider) -> None:
|
|
250
|
+
"""Register a search provider.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
name: A unique name for the provider (e.g., 'exa', 'serper').
|
|
254
|
+
provider: An instance of a Provider subclass.
|
|
255
|
+
"""
|
|
256
|
+
self._providers[name] = provider
|
|
257
|
+
# First registered provider becomes default
|
|
258
|
+
if self._default_provider is None:
|
|
259
|
+
self._default_provider = name
|
|
260
|
+
|
|
261
|
+
def unregister_provider(self, name: str) -> bool:
|
|
262
|
+
"""Remove a registered provider.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
name: The provider name to remove.
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
True if removed, False if not found.
|
|
269
|
+
"""
|
|
270
|
+
if name in self._providers:
|
|
271
|
+
del self._providers[name]
|
|
272
|
+
if self._default_provider == name:
|
|
273
|
+
self._default_provider = (
|
|
274
|
+
next(iter(self._providers)) if self._providers else None
|
|
275
|
+
)
|
|
276
|
+
return True
|
|
277
|
+
return False
|
|
278
|
+
|
|
279
|
+
def get_providers(self) -> List[str]:
|
|
280
|
+
"""Return a list of registered provider names.
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
List of provider name strings.
|
|
284
|
+
"""
|
|
285
|
+
return list(self._providers.keys())
|
|
286
|
+
|
|
287
|
+
def search(
|
|
288
|
+
self,
|
|
289
|
+
query: str,
|
|
290
|
+
provider: Optional[str] = None,
|
|
291
|
+
use_selector: bool = False,
|
|
292
|
+
dry_run: bool = False,
|
|
293
|
+
**kwargs: Any,
|
|
294
|
+
) -> List[SearchResult]:
|
|
295
|
+
"""Search using a registered provider.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
query: The search query string.
|
|
299
|
+
provider: Provider name to use. If None, uses the default provider
|
|
300
|
+
(or ProviderSelector if use_selector=True).
|
|
301
|
+
use_selector: If True, use ProviderSelector for intelligent
|
|
302
|
+
provider selection with fallback and rate-limit awareness.
|
|
303
|
+
dry_run: If True (with use_selector), prefer synthetic provider.
|
|
304
|
+
**kwargs: Additional arguments passed to the provider's search().
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
A list of SearchResult objects. Empty list if feature disabled,
|
|
308
|
+
no providers registered, or provider not found.
|
|
309
|
+
"""
|
|
310
|
+
if not _is_enabled():
|
|
311
|
+
return []
|
|
312
|
+
|
|
313
|
+
if use_selector:
|
|
314
|
+
provider_name = provider_selector.select_provider(
|
|
315
|
+
query=query,
|
|
316
|
+
preferred=provider,
|
|
317
|
+
manager=self,
|
|
318
|
+
dry_run=dry_run,
|
|
319
|
+
)
|
|
320
|
+
else:
|
|
321
|
+
provider_name = provider or self._default_provider
|
|
322
|
+
|
|
323
|
+
if provider_name is None or provider_name not in self._providers:
|
|
324
|
+
return []
|
|
325
|
+
|
|
326
|
+
prov = self._providers[provider_name]
|
|
327
|
+
try:
|
|
328
|
+
raw_results = prov.search(query, **kwargs)
|
|
329
|
+
except Exception:
|
|
330
|
+
return []
|
|
331
|
+
|
|
332
|
+
results = []
|
|
333
|
+
for item in raw_results:
|
|
334
|
+
item.setdefault("source", provider_name)
|
|
335
|
+
results.append(SearchResult.from_dict(item))
|
|
336
|
+
|
|
337
|
+
_mark_external_ingress(
|
|
338
|
+
source_ref=f"search:{provider_name}",
|
|
339
|
+
content={
|
|
340
|
+
"query": query,
|
|
341
|
+
"provider": provider_name,
|
|
342
|
+
"results": [result.to_dict() for result in results],
|
|
343
|
+
},
|
|
344
|
+
)
|
|
345
|
+
return results
|
|
346
|
+
|
|
347
|
+
def fetch(
|
|
348
|
+
self,
|
|
349
|
+
url: str,
|
|
350
|
+
provider: Optional[str] = None,
|
|
351
|
+
) -> str:
|
|
352
|
+
"""Fetch URL content using a registered provider.
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
url: The URL to fetch.
|
|
356
|
+
provider: Provider name to use. If None, uses default provider.
|
|
357
|
+
If no provider registered, uses stdlib urllib as fallback.
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
The fetched content as a string, or empty string on failure/disabled.
|
|
361
|
+
"""
|
|
362
|
+
if not _is_enabled():
|
|
363
|
+
return ""
|
|
364
|
+
|
|
365
|
+
provider_name = provider or self._default_provider
|
|
366
|
+
if provider_name and provider_name in self._providers:
|
|
367
|
+
try:
|
|
368
|
+
content = self._providers[provider_name].fetch(url)
|
|
369
|
+
except Exception:
|
|
370
|
+
return ""
|
|
371
|
+
_mark_external_ingress(
|
|
372
|
+
source_ref=f"fetch:{provider_name}:{url}",
|
|
373
|
+
content={
|
|
374
|
+
"url": url,
|
|
375
|
+
"provider": provider_name,
|
|
376
|
+
"content": content,
|
|
377
|
+
},
|
|
378
|
+
)
|
|
379
|
+
return content
|
|
380
|
+
|
|
381
|
+
# Fallback: use stdlib urllib if no provider available
|
|
382
|
+
content = _stdlib_fetch(url)
|
|
383
|
+
_mark_external_ingress(
|
|
384
|
+
source_ref=f"fetch:stdlib:{url}",
|
|
385
|
+
content={
|
|
386
|
+
"url": url,
|
|
387
|
+
"provider": "stdlib",
|
|
388
|
+
"content": content,
|
|
389
|
+
},
|
|
390
|
+
)
|
|
391
|
+
return content
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
# =============================================================================
|
|
395
|
+
# Stdlib URL Fetch (fallback)
|
|
396
|
+
# =============================================================================
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def _stdlib_fetch(url: str, timeout: int = 15) -> str:
|
|
400
|
+
"""Fetch URL content using stdlib urllib.request.
|
|
401
|
+
|
|
402
|
+
Args:
|
|
403
|
+
url: The URL to fetch.
|
|
404
|
+
timeout: Request timeout in seconds.
|
|
405
|
+
|
|
406
|
+
Returns:
|
|
407
|
+
The page content as text, or empty string on failure.
|
|
408
|
+
"""
|
|
409
|
+
try:
|
|
410
|
+
req = urllib.request.Request(
|
|
411
|
+
url,
|
|
412
|
+
headers={"User-Agent": "OMG-WebSearch/1.0"},
|
|
413
|
+
)
|
|
414
|
+
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
415
|
+
encoding = resp.headers.get_content_charset() or "utf-8"
|
|
416
|
+
return resp.read().decode(encoding, errors="replace")
|
|
417
|
+
except Exception:
|
|
418
|
+
return ""
|
|
419
|
+
|
|
420
|
+
# =============================================================================
|
|
421
|
+
# Provider Selection Logic
|
|
422
|
+
# =============================================================================
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
# Module-level rate limit state: provider_name -> reset_timestamp
|
|
426
|
+
_rate_limits: Dict[str, float] = {}
|
|
427
|
+
|
|
428
|
+
# Default fallback chain order
|
|
429
|
+
DEFAULT_FALLBACK_CHAIN = ["exa", "brave", "perplexity", "jina", "synthetic"]
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
class ProviderSelector:
|
|
433
|
+
"""Intelligent provider selection with fallback chains and rate-limit tracking.
|
|
434
|
+
|
|
435
|
+
Selection algorithm:
|
|
436
|
+
1. If dry-run mode, prefer 'synthetic'
|
|
437
|
+
2. If `preferred` argument given, use it (if not rate-limited)
|
|
438
|
+
3. If user preference is set, use it (if not rate-limited)
|
|
439
|
+
4. Iterate fallback chain, pick first not rate-limited + registered
|
|
440
|
+
5. Return None if all exhausted
|
|
441
|
+
"""
|
|
442
|
+
|
|
443
|
+
def __init__(self) -> None:
|
|
444
|
+
self._preference_path: Optional[str] = None
|
|
445
|
+
|
|
446
|
+
def _get_preference_path(self) -> str:
|
|
447
|
+
"""Get the path to the preference file."""
|
|
448
|
+
if self._preference_path is None:
|
|
449
|
+
repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
450
|
+
self._preference_path = os.path.join(
|
|
451
|
+
repo_root, ".omg", "state", "search_preference.json"
|
|
452
|
+
)
|
|
453
|
+
return self._preference_path
|
|
454
|
+
|
|
455
|
+
def select_provider(
|
|
456
|
+
self,
|
|
457
|
+
query: str,
|
|
458
|
+
preferred: Optional[str] = None,
|
|
459
|
+
manager: Optional["WebSearchManager"] = None,
|
|
460
|
+
dry_run: bool = False,
|
|
461
|
+
) -> Optional[str]:
|
|
462
|
+
"""Pick the best available provider for a query.
|
|
463
|
+
|
|
464
|
+
Args:
|
|
465
|
+
query: The search query (reserved for future query-type routing).
|
|
466
|
+
preferred: Explicit provider preference for this call.
|
|
467
|
+
manager: WebSearchManager to check registered providers against.
|
|
468
|
+
dry_run: If True, prefer 'synthetic' provider.
|
|
469
|
+
|
|
470
|
+
Returns:
|
|
471
|
+
Provider name string, or None if no provider available.
|
|
472
|
+
"""
|
|
473
|
+
registered = manager.get_providers() if manager else []
|
|
474
|
+
|
|
475
|
+
# Step 0: dry-run mode prefers synthetic
|
|
476
|
+
if dry_run and "synthetic" in registered and not self.is_rate_limited("synthetic"):
|
|
477
|
+
return "synthetic"
|
|
478
|
+
|
|
479
|
+
# Step 1: explicit preferred argument
|
|
480
|
+
if preferred and preferred in registered and not self.is_rate_limited(preferred):
|
|
481
|
+
return preferred
|
|
482
|
+
|
|
483
|
+
# Step 2: user preference from persisted state
|
|
484
|
+
user_pref = self.get_preference()
|
|
485
|
+
if user_pref and user_pref in registered and not self.is_rate_limited(user_pref):
|
|
486
|
+
return user_pref
|
|
487
|
+
|
|
488
|
+
# Step 3: iterate fallback chain
|
|
489
|
+
for provider_name in DEFAULT_FALLBACK_CHAIN:
|
|
490
|
+
if provider_name in registered and not self.is_rate_limited(provider_name):
|
|
491
|
+
return provider_name
|
|
492
|
+
|
|
493
|
+
# Step 4: try any remaining registered provider not in fallback chain
|
|
494
|
+
for provider_name in registered:
|
|
495
|
+
if not self.is_rate_limited(provider_name):
|
|
496
|
+
return provider_name
|
|
497
|
+
|
|
498
|
+
return None
|
|
499
|
+
|
|
500
|
+
def record_rate_limit(
|
|
501
|
+
self, provider_name: str, reset_at: Optional[float] = None
|
|
502
|
+
) -> None:
|
|
503
|
+
"""Mark a provider as rate-limited.
|
|
504
|
+
|
|
505
|
+
Args:
|
|
506
|
+
provider_name: The provider to mark.
|
|
507
|
+
reset_at: Unix timestamp when the rate limit resets.
|
|
508
|
+
Defaults to time.time() + 60 (1 minute).
|
|
509
|
+
"""
|
|
510
|
+
if reset_at is None:
|
|
511
|
+
reset_at = time.time() + 60
|
|
512
|
+
_rate_limits[provider_name] = reset_at
|
|
513
|
+
|
|
514
|
+
def is_rate_limited(self, provider_name: str) -> bool:
|
|
515
|
+
"""Check if a provider is currently rate-limited.
|
|
516
|
+
|
|
517
|
+
Automatically clears expired rate limits.
|
|
518
|
+
|
|
519
|
+
Args:
|
|
520
|
+
provider_name: The provider to check.
|
|
521
|
+
|
|
522
|
+
Returns:
|
|
523
|
+
True if currently rate-limited, False otherwise.
|
|
524
|
+
"""
|
|
525
|
+
if provider_name not in _rate_limits:
|
|
526
|
+
return False
|
|
527
|
+
if time.time() > _rate_limits[provider_name]:
|
|
528
|
+
# Rate limit expired — clean up
|
|
529
|
+
del _rate_limits[provider_name]
|
|
530
|
+
return False
|
|
531
|
+
return True
|
|
532
|
+
|
|
533
|
+
def get_fallback_chain(self, primary_provider: str) -> List[str]:
|
|
534
|
+
"""Return ordered fallback list starting after the primary provider.
|
|
535
|
+
|
|
536
|
+
Args:
|
|
537
|
+
primary_provider: The provider to build fallback chain from.
|
|
538
|
+
|
|
539
|
+
Returns:
|
|
540
|
+
List of provider names in fallback order (excluding primary).
|
|
541
|
+
"""
|
|
542
|
+
chain = list(DEFAULT_FALLBACK_CHAIN)
|
|
543
|
+
if primary_provider in chain:
|
|
544
|
+
idx = chain.index(primary_provider)
|
|
545
|
+
# Everything after primary, then wrap around before it
|
|
546
|
+
chain = chain[idx + 1:] + chain[:idx]
|
|
547
|
+
return chain
|
|
548
|
+
|
|
549
|
+
def set_preference(self, provider_name: str) -> None:
|
|
550
|
+
"""Persist user preferred provider to .omg/state/search_preference.json.
|
|
551
|
+
|
|
552
|
+
Args:
|
|
553
|
+
provider_name: The provider name to set as default.
|
|
554
|
+
"""
|
|
555
|
+
import datetime
|
|
556
|
+
|
|
557
|
+
_ensure_imports()
|
|
558
|
+
data = {
|
|
559
|
+
"provider": provider_name,
|
|
560
|
+
"set_at": datetime.datetime.now().isoformat(),
|
|
561
|
+
}
|
|
562
|
+
path = self._get_preference_path()
|
|
563
|
+
if _atomic_json_write is not None:
|
|
564
|
+
_atomic_json_write(path, data)
|
|
565
|
+
else:
|
|
566
|
+
# Fallback: direct write
|
|
567
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
568
|
+
with open(path, "w") as f:
|
|
569
|
+
json.dump(data, f, indent=2)
|
|
570
|
+
|
|
571
|
+
def get_preference(self) -> Optional[str]:
|
|
572
|
+
"""Read user preferred provider from persisted state.
|
|
573
|
+
|
|
574
|
+
Returns:
|
|
575
|
+
Provider name string, or None if no preference set.
|
|
576
|
+
"""
|
|
577
|
+
path = self._get_preference_path()
|
|
578
|
+
try:
|
|
579
|
+
with open(path) as f:
|
|
580
|
+
data = json.load(f)
|
|
581
|
+
return data.get("provider")
|
|
582
|
+
except (FileNotFoundError, json.JSONDecodeError, KeyError):
|
|
583
|
+
return None
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
# Module-level singleton
|
|
587
|
+
provider_selector = ProviderSelector()
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
# =============================================================================
|
|
591
|
+
# Module-Level Singleton Instance
|
|
592
|
+
# =============================================================================
|
|
593
|
+
|
|
594
|
+
manager = WebSearchManager()
|
|
595
|
+
|
|
596
|
+
# Auto-register bundled providers so CLI and hooks work out of the box.
|
|
597
|
+
# - synthetic always registers (no API key required)
|
|
598
|
+
# - api-key providers register only when credentials are available
|
|
599
|
+
try:
|
|
600
|
+
_providers_mod = importlib.import_module("search_providers")
|
|
601
|
+
_providers_mod.register_all(manager=manager)
|
|
602
|
+
except Exception:
|
|
603
|
+
# Keep module import non-fatal if provider modules are unavailable.
|
|
604
|
+
pass
|
|
605
|
+
|
|
606
|
+
# =============================================================================
|
|
607
|
+
# CLI Interface
|
|
608
|
+
# =============================================================================
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
def _cli_main():
|
|
612
|
+
"""CLI entry point for web_search.py."""
|
|
613
|
+
import argparse
|
|
614
|
+
|
|
615
|
+
parser = argparse.ArgumentParser(
|
|
616
|
+
description="OMG Web Search Tool — provider-based web search abstraction",
|
|
617
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
618
|
+
)
|
|
619
|
+
parser.add_argument("--query", help="Search query string")
|
|
620
|
+
parser.add_argument("--provider", help="Provider name to use")
|
|
621
|
+
parser.add_argument("--fetch", dest="fetch_url", help="Fetch URL content")
|
|
622
|
+
parser.add_argument(
|
|
623
|
+
"--dry-run", action="store_true",
|
|
624
|
+
help="Print what would be searched without making real API calls",
|
|
625
|
+
)
|
|
626
|
+
parser.add_argument(
|
|
627
|
+
"--list-providers", action="store_true",
|
|
628
|
+
help="List registered providers",
|
|
629
|
+
)
|
|
630
|
+
|
|
631
|
+
args = parser.parse_args()
|
|
632
|
+
|
|
633
|
+
# Check feature flag for most operations
|
|
634
|
+
enabled = _is_enabled()
|
|
635
|
+
|
|
636
|
+
if args.list_providers:
|
|
637
|
+
providers = manager.get_providers()
|
|
638
|
+
print(json.dumps({"providers": providers, "enabled": enabled}))
|
|
639
|
+
return
|
|
640
|
+
|
|
641
|
+
if args.dry_run and args.query:
|
|
642
|
+
print(json.dumps({
|
|
643
|
+
"dry_run": True,
|
|
644
|
+
"query": args.query,
|
|
645
|
+
"provider": args.provider or manager._default_provider or "(none)",
|
|
646
|
+
"enabled": enabled,
|
|
647
|
+
"registered_providers": manager.get_providers(),
|
|
648
|
+
"would_search": enabled and len(manager.get_providers()) > 0,
|
|
649
|
+
}, indent=2))
|
|
650
|
+
return
|
|
651
|
+
|
|
652
|
+
if not enabled:
|
|
653
|
+
print(json.dumps({"error": "Web search is disabled (OMG_WEB_SEARCH_ENABLED=false)"}))
|
|
654
|
+
sys.exit(1)
|
|
655
|
+
|
|
656
|
+
if args.fetch_url:
|
|
657
|
+
content = manager.fetch(args.fetch_url, provider=args.provider)
|
|
658
|
+
print(json.dumps({
|
|
659
|
+
"url": args.fetch_url,
|
|
660
|
+
"content_length": len(content),
|
|
661
|
+
"content_preview": content[:500] if content else "",
|
|
662
|
+
}, indent=2))
|
|
663
|
+
return
|
|
664
|
+
|
|
665
|
+
if args.query:
|
|
666
|
+
results = manager.search(args.query, provider=args.provider)
|
|
667
|
+
output = [r.to_dict() for r in results]
|
|
668
|
+
print(json.dumps({"results": output, "count": len(output)}, indent=2))
|
|
669
|
+
return
|
|
670
|
+
|
|
671
|
+
parser.print_help()
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
if __name__ == "__main__":
|
|
675
|
+
_cli_main()
|