@trac3er/oh-my-god 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. package/.claude-plugin/marketplace.json +8 -8
  2. package/.claude-plugin/plugin.json +5 -4
  3. package/.claude-plugin/scripts/uninstall.sh +74 -3
  4. package/.claude-plugin/scripts/update.sh +78 -3
  5. package/.coveragerc +26 -0
  6. package/.mcp.json +4 -4
  7. package/CHANGELOG.md +14 -0
  8. package/CODE_OF_CONDUCT.md +27 -0
  9. package/CONTRIBUTING.md +62 -0
  10. package/OMG-setup.sh +1201 -355
  11. package/README.md +77 -56
  12. package/SECURITY.md +25 -0
  13. package/agents/__init__.py +1 -0
  14. package/agents/model_roles.py +196 -0
  15. package/agents/omg-architect-mode.md +3 -5
  16. package/agents/omg-backend-engineer.md +3 -5
  17. package/agents/omg-database-engineer.md +3 -5
  18. package/agents/omg-frontend-designer.md +4 -5
  19. package/agents/omg-implement-mode.md +4 -5
  20. package/agents/omg-infra-engineer.md +3 -5
  21. package/agents/omg-research-mode.md +4 -6
  22. package/agents/omg-security-auditor.md +3 -5
  23. package/agents/omg-testing-engineer.md +3 -5
  24. package/build/lib/yaml.py +321 -0
  25. package/commands/OMG:ai-commit.md +101 -14
  26. package/commands/OMG:arch.md +302 -19
  27. package/commands/OMG:ccg.md +12 -7
  28. package/commands/OMG:compat.md +25 -17
  29. package/commands/OMG:cost.md +173 -13
  30. package/commands/OMG:crazy.md +1 -1
  31. package/commands/OMG:create-agent.md +170 -20
  32. package/commands/OMG:deps.md +235 -17
  33. package/commands/OMG:domain-init.md +1 -1
  34. package/commands/OMG:escalate.md +41 -12
  35. package/commands/OMG:health-check.md +37 -13
  36. package/commands/OMG:init.md +122 -14
  37. package/commands/OMG:project-init.md +1 -1
  38. package/commands/OMG:session-branch.md +76 -9
  39. package/commands/OMG:session-fork.md +42 -5
  40. package/commands/OMG:session-merge.md +124 -8
  41. package/commands/OMG:setup.md +69 -12
  42. package/commands/OMG:stats.md +215 -14
  43. package/commands/OMG:teams.md +19 -10
  44. package/config/lsp_languages.yaml +8 -0
  45. package/hooks/__init__.py +0 -0
  46. package/hooks/_agent_registry.py +423 -0
  47. package/hooks/_analytics.py +291 -0
  48. package/hooks/_budget.py +31 -0
  49. package/hooks/_common.py +569 -0
  50. package/hooks/_compression_optimizer.py +119 -0
  51. package/hooks/_cost_ledger.py +176 -0
  52. package/hooks/_learnings.py +126 -0
  53. package/hooks/_memory.py +103 -0
  54. package/hooks/_protected_context.py +150 -0
  55. package/hooks/_token_counter.py +221 -0
  56. package/hooks/branch_manager.py +236 -0
  57. package/hooks/budget_governor.py +232 -0
  58. package/hooks/circuit-breaker.py +270 -0
  59. package/hooks/compression_feedback.py +254 -0
  60. package/hooks/config-guard.py +216 -0
  61. package/hooks/context_pressure.py +53 -0
  62. package/hooks/credential_store.py +1020 -0
  63. package/hooks/fetch-rate-limits.py +212 -0
  64. package/hooks/firewall.py +48 -0
  65. package/hooks/hashline-formatter-bridge.py +224 -0
  66. package/hooks/hashline-injector.py +273 -0
  67. package/hooks/hashline-validator.py +216 -0
  68. package/hooks/idle-detector.py +95 -0
  69. package/hooks/intentgate-keyword-detector.py +188 -0
  70. package/hooks/magic-keyword-router.py +195 -0
  71. package/hooks/policy_engine.py +505 -0
  72. package/hooks/post-tool-failure.py +19 -0
  73. package/hooks/post-write.py +219 -0
  74. package/hooks/post_write.py +46 -0
  75. package/hooks/pre-compact.py +398 -0
  76. package/hooks/pre-tool-inject.py +98 -0
  77. package/hooks/prompt-enhancer.py +672 -0
  78. package/hooks/quality-runner.py +191 -0
  79. package/hooks/query.py +512 -0
  80. package/hooks/secret-guard.py +61 -0
  81. package/hooks/secret_audit.py +144 -0
  82. package/hooks/session-end-capture.py +137 -0
  83. package/hooks/session-start.py +277 -0
  84. package/hooks/setup_wizard.py +582 -0
  85. package/hooks/shadow_manager.py +297 -0
  86. package/hooks/state_migration.py +225 -0
  87. package/hooks/stop-gate.py +7 -0
  88. package/hooks/stop_dispatcher.py +945 -0
  89. package/hooks/test-validator.py +361 -0
  90. package/hooks/test_generator_hook.py +123 -0
  91. package/hooks/todo-state-tracker.py +114 -0
  92. package/hooks/tool-ledger.py +149 -0
  93. package/hooks/trust_review.py +585 -0
  94. package/hud/omg-hud.mjs +31 -1
  95. package/lab/__init__.py +1 -0
  96. package/lab/pipeline.py +75 -0
  97. package/lab/policies.py +52 -0
  98. package/package.json +7 -18
  99. package/plugins/README.md +33 -61
  100. package/plugins/advanced/commands/OMG:deep-plan.md +3 -3
  101. package/plugins/advanced/commands/OMG:learn.md +1 -1
  102. package/plugins/advanced/commands/OMG:security-review.md +3 -3
  103. package/plugins/advanced/commands/OMG:ship.md +1 -1
  104. package/plugins/advanced/plugin.json +1 -1
  105. package/plugins/core/plugin.json +8 -3
  106. package/plugins/dephealth/__init__.py +0 -0
  107. package/plugins/dephealth/cve_scanner.py +188 -0
  108. package/plugins/dephealth/license_checker.py +135 -0
  109. package/plugins/dephealth/manifest_detector.py +423 -0
  110. package/plugins/dephealth/vuln_analyzer.py +169 -0
  111. package/plugins/testgen/__init__.py +0 -0
  112. package/plugins/testgen/codamosa_engine.py +402 -0
  113. package/plugins/testgen/edge_case_synthesizer.py +184 -0
  114. package/plugins/testgen/framework_detector.py +271 -0
  115. package/plugins/testgen/skeleton_generator.py +219 -0
  116. package/plugins/viz/__init__.py +0 -0
  117. package/plugins/viz/ast_parser.py +139 -0
  118. package/plugins/viz/diagram_generator.py +192 -0
  119. package/plugins/viz/graph_builder.py +444 -0
  120. package/plugins/viz/native_parsers.py +259 -0
  121. package/plugins/viz/regex_parser.py +112 -0
  122. package/pyproject.toml +81 -0
  123. package/rules/contextual/write-verify.md +2 -2
  124. package/rules/core/00-truth.md +1 -1
  125. package/rules/core/01-surgical.md +1 -1
  126. package/rules/core/02-circuit-breaker.md +2 -2
  127. package/rules/core/03-ensemble.md +3 -3
  128. package/rules/core/04-testing.md +3 -3
  129. package/runtime/__init__.py +32 -0
  130. package/runtime/adapters/__init__.py +13 -0
  131. package/runtime/adapters/claude.py +60 -0
  132. package/runtime/adapters/gpt.py +53 -0
  133. package/runtime/adapters/local.py +53 -0
  134. package/runtime/adoption.py +212 -0
  135. package/runtime/business_workflow.py +220 -0
  136. package/runtime/cli_provider.py +85 -0
  137. package/runtime/compat.py +1299 -0
  138. package/runtime/custom_agent_loader.py +366 -0
  139. package/runtime/dispatcher.py +47 -0
  140. package/runtime/ecosystem.py +371 -0
  141. package/runtime/legacy_compat.py +7 -0
  142. package/runtime/mcp_config_writers.py +115 -0
  143. package/runtime/mcp_lifecycle.py +153 -0
  144. package/runtime/mcp_memory_server.py +135 -0
  145. package/runtime/memory_parsers/__init__.py +0 -0
  146. package/runtime/memory_parsers/chatgpt_parser.py +257 -0
  147. package/runtime/memory_parsers/claude_import.py +107 -0
  148. package/runtime/memory_parsers/export.py +97 -0
  149. package/runtime/memory_parsers/gemini_import.py +91 -0
  150. package/runtime/memory_parsers/kimi_import.py +91 -0
  151. package/runtime/memory_store.py +215 -0
  152. package/runtime/omc_compat.py +7 -0
  153. package/runtime/providers/__init__.py +0 -0
  154. package/runtime/providers/codex_provider.py +112 -0
  155. package/runtime/providers/gemini_provider.py +128 -0
  156. package/runtime/providers/kimi_provider.py +151 -0
  157. package/runtime/providers/opencode_provider.py +144 -0
  158. package/runtime/subagent_dispatcher.py +362 -0
  159. package/runtime/team_router.py +1167 -0
  160. package/runtime/tmux_session_manager.py +169 -0
  161. package/scripts/check-omg-compat-contract-snapshot.py +137 -0
  162. package/scripts/check-omg-contract-snapshot.py +12 -0
  163. package/scripts/check-omg-public-ready.py +193 -0
  164. package/scripts/check-omg-standalone-clean.py +103 -0
  165. package/scripts/legacy_to_omg_migrate.py +29 -0
  166. package/scripts/migrate-legacy.py +464 -0
  167. package/scripts/omc_to_omg_migrate.py +12 -0
  168. package/scripts/omg.py +492 -0
  169. package/scripts/settings-merge.py +283 -0
  170. package/scripts/verify-standalone.sh +8 -4
  171. package/settings.json +126 -29
  172. package/templates/profile.yaml +1 -1
  173. package/tools/__init__.py +2 -0
  174. package/tools/browser_consent.py +289 -0
  175. package/tools/browser_stealth.py +481 -0
  176. package/tools/browser_tool.py +448 -0
  177. package/tools/changelog_generator.py +347 -0
  178. package/tools/commit_splitter.py +746 -0
  179. package/tools/config_discovery.py +151 -0
  180. package/tools/config_merger.py +449 -0
  181. package/tools/dashboard_generator.py +300 -0
  182. package/tools/git_inspector.py +298 -0
  183. package/tools/lsp_client.py +275 -0
  184. package/tools/lsp_discovery.py +231 -0
  185. package/tools/lsp_operations.py +392 -0
  186. package/tools/pr_generator.py +404 -0
  187. package/tools/python_repl.py +656 -0
  188. package/tools/python_sandbox.py +609 -0
  189. package/tools/search_providers/__init__.py +77 -0
  190. package/tools/search_providers/brave.py +115 -0
  191. package/tools/search_providers/exa.py +116 -0
  192. package/tools/search_providers/jina.py +104 -0
  193. package/tools/search_providers/perplexity.py +139 -0
  194. package/tools/search_providers/synthetic.py +74 -0
  195. package/tools/session_snapshot.py +736 -0
  196. package/tools/ssh_manager.py +912 -0
  197. package/tools/theme_engine.py +294 -0
  198. package/tools/theme_selector.py +137 -0
  199. package/tools/web_search.py +622 -0
  200. package/yaml.py +321 -0
  201. package/.claude-plugin/scripts/install.sh +0 -9
  202. package/bun.lock +0 -23
  203. package/bunfig.toml +0 -3
  204. package/hooks/_budget.ts +0 -1
  205. package/hooks/_common.ts +0 -63
  206. package/hooks/circuit-breaker.ts +0 -101
  207. package/hooks/config-guard.ts +0 -4
  208. package/hooks/firewall.ts +0 -20
  209. package/hooks/policy_engine.ts +0 -156
  210. package/hooks/post-tool-failure.ts +0 -22
  211. package/hooks/post-write.ts +0 -4
  212. package/hooks/pre-tool-inject.ts +0 -4
  213. package/hooks/prompt-enhancer.ts +0 -46
  214. package/hooks/quality-runner.ts +0 -24
  215. package/hooks/secret-guard.ts +0 -4
  216. package/hooks/session-end-capture.ts +0 -19
  217. package/hooks/session-start.ts +0 -19
  218. package/hooks/shadow_manager.ts +0 -81
  219. package/hooks/stop-gate.ts +0 -22
  220. package/hooks/stop_dispatcher.ts +0 -147
  221. package/hooks/test-generator-hook.ts +0 -4
  222. package/hooks/tool-ledger.ts +0 -27
  223. package/hooks/trust_review.ts +0 -175
  224. package/lab/pipeline.ts +0 -75
  225. package/lab/policies.ts +0 -68
  226. package/runtime/common.ts +0 -111
  227. package/runtime/compat.ts +0 -174
  228. package/runtime/dispatcher.ts +0 -25
  229. package/runtime/ecosystem.ts +0 -186
  230. package/runtime/provider_bootstrap.ts +0 -99
  231. package/runtime/provider_smoke.ts +0 -34
  232. package/runtime/release_readiness.ts +0 -186
  233. package/runtime/team_router.ts +0 -144
  234. package/scripts/check-omg-compat-contract-snapshot.ts +0 -20
  235. package/scripts/check-omg-standalone-clean.ts +0 -12
  236. package/scripts/check-runtime-clean.ts +0 -94
  237. package/scripts/omg.ts +0 -352
  238. package/scripts/settings-merge.ts +0 -93
  239. package/tools/commit_splitter.ts +0 -23
  240. package/tools/git_inspector.ts +0 -18
  241. package/tools/session_snapshot.ts +0 -47
  242. package/trac3er-oh-my-god-2.0.0.tgz +0 -0
  243. package/tsconfig.json +0 -15
@@ -0,0 +1,448 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Puppeteer Browser Integration for OMG
4
+
5
+ Wrapper around Puppeteer MCP tools that generates tool call specifications
6
+ for browser automation. Functions produce spec dicts — actual execution is
7
+ done by Claude Code when it dispatches the MCP call.
8
+
9
+ Feature flag: OMG_BROWSER_ENABLED (default: False)
10
+ """
11
+
12
+ import json
13
+ import os
14
+ import sys
15
+ import uuid
16
+ from dataclasses import asdict, dataclass, field
17
+ from typing import Any, Dict, List, Optional
18
+
19
+
20
+ # --- Lazy imports for hooks/_common.py ---
21
+
22
+ _get_feature_flag = None
23
+
24
+
25
+ def _ensure_imports():
26
+ """Lazy import feature flag from hooks/_common.py."""
27
+ global _get_feature_flag
28
+ if _get_feature_flag is not None:
29
+ return
30
+ repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
31
+ if repo_root not in sys.path:
32
+ sys.path.insert(0, repo_root)
33
+ try:
34
+ from hooks._common import get_feature_flag as _gff
35
+ _get_feature_flag = _gff
36
+ except ImportError:
37
+ pass
38
+
39
+
40
+ # --- Feature flag ---
41
+
42
+ def _is_enabled() -> bool:
43
+ """Check if Browser feature is enabled."""
44
+ # Fast path: check env var directly
45
+ env_val = os.environ.get("OMG_BROWSER_ENABLED", "").lower()
46
+ if env_val in ("0", "false", "no"):
47
+ return False
48
+ if env_val in ("1", "true", "yes"):
49
+ return True
50
+ # Fallback to hooks/_common.get_feature_flag
51
+ _ensure_imports()
52
+ if _get_feature_flag is not None:
53
+ return _get_feature_flag("BROWSER", default=False)
54
+ return False
55
+
56
+
57
+ # --- Response helpers ---
58
+
59
+ def _success_response(result: Any) -> Dict[str, Any]:
60
+ """Create a success response dict."""
61
+ return {"success": True, "result": result, "error": None}
62
+
63
+
64
+ def _error_response(error: str) -> Dict[str, Any]:
65
+ """Create an error response dict."""
66
+ return {"success": False, "result": None, "error": error}
67
+
68
+
69
+ def _disabled_response() -> Dict[str, Any]:
70
+ """Create a response for when the feature flag is disabled."""
71
+ return _error_response("Browser feature is disabled (OMG_BROWSER_ENABLED=false)")
72
+
73
+
74
+ # =============================================================================
75
+ # BrowserSession — session state tracking
76
+ # =============================================================================
77
+
78
+
79
+ @dataclass
80
+ class BrowserSession:
81
+ """Manages browser session state.
82
+
83
+ Tracks the current URL, navigation history, and screenshot names
84
+ for the duration of a browser automation session.
85
+
86
+ Attributes:
87
+ session_id: Unique identifier for this session.
88
+ current_url: The URL the browser is currently on (empty if none).
89
+ history: List of URLs visited during this session.
90
+ screenshots: List of screenshot names taken during this session.
91
+ """
92
+
93
+ session_id: str = field(default_factory=lambda: uuid.uuid4().hex[:12])
94
+ current_url: str = ""
95
+ history: List[str] = field(default_factory=list)
96
+ screenshots: List[str] = field(default_factory=list)
97
+
98
+ def navigate_to(self, url: str) -> None:
99
+ """Record a navigation to a URL."""
100
+ self.current_url = url
101
+ self.history.append(url)
102
+
103
+ def record_screenshot(self, name: str) -> None:
104
+ """Record a screenshot taken."""
105
+ self.screenshots.append(name)
106
+
107
+ def to_dict(self) -> Dict[str, Any]:
108
+ """Convert session state to a plain dictionary."""
109
+ return asdict(self)
110
+
111
+ def reset(self) -> None:
112
+ """Reset session state while keeping the same session_id."""
113
+ self.current_url = ""
114
+ self.history.clear()
115
+ self.screenshots.clear()
116
+
117
+
118
+ # Module-level session instance
119
+ session = BrowserSession()
120
+
121
+
122
+ # =============================================================================
123
+ # Browser Operations — tool call spec generators
124
+ # =============================================================================
125
+
126
+
127
+ def browser_navigate(url: str, timeout: int = 30) -> Dict[str, Any]:
128
+ """Navigate the browser to a URL.
129
+
130
+ Generates a Puppeteer MCP tool call spec for navigation.
131
+
132
+ Args:
133
+ url: The URL to navigate to.
134
+ timeout: Navigation timeout in seconds (default: 30).
135
+
136
+ Returns:
137
+ A dict with ``success``, ``result`` (the tool call spec), and ``error``.
138
+ If the feature flag is disabled, returns an error response.
139
+ """
140
+ if not _is_enabled():
141
+ return _disabled_response()
142
+
143
+ try:
144
+ if not url or not isinstance(url, str):
145
+ return _error_response("URL must be a non-empty string")
146
+
147
+ # Validate URL has a scheme
148
+ if not url.startswith(("http://", "https://")):
149
+ url = f"https://{url}"
150
+
151
+ spec = {
152
+ "tool": "mcp_puppeteer_puppeteer_navigate",
153
+ "parameters": {
154
+ "url": url,
155
+ },
156
+ }
157
+
158
+ # Track session state
159
+ session.navigate_to(url)
160
+
161
+ return _success_response(spec)
162
+
163
+ except Exception as e:
164
+ return _error_response(str(e))
165
+
166
+
167
+ def browser_click(selector: str) -> Dict[str, Any]:
168
+ """Click an element on the page.
169
+
170
+ Generates a Puppeteer MCP tool call spec for clicking.
171
+
172
+ Args:
173
+ selector: CSS selector for the element to click.
174
+
175
+ Returns:
176
+ A dict with ``success``, ``result`` (the tool call spec), and ``error``.
177
+ """
178
+ if not _is_enabled():
179
+ return _disabled_response()
180
+
181
+ try:
182
+ if not selector or not isinstance(selector, str):
183
+ return _error_response("Selector must be a non-empty string")
184
+
185
+ spec = {
186
+ "tool": "mcp_puppeteer_puppeteer_click",
187
+ "parameters": {
188
+ "selector": selector,
189
+ },
190
+ }
191
+
192
+ return _success_response(spec)
193
+
194
+ except Exception as e:
195
+ return _error_response(str(e))
196
+
197
+
198
+ def browser_type(selector: str, text: str) -> Dict[str, Any]:
199
+ """Type text into an input element.
200
+
201
+ Generates a Puppeteer MCP tool call spec for filling an input field.
202
+
203
+ Args:
204
+ selector: CSS selector for the input field.
205
+ text: The text to type into the field.
206
+
207
+ Returns:
208
+ A dict with ``success``, ``result`` (the tool call spec), and ``error``.
209
+ """
210
+ if not _is_enabled():
211
+ return _disabled_response()
212
+
213
+ try:
214
+ if not selector or not isinstance(selector, str):
215
+ return _error_response("Selector must be a non-empty string")
216
+ if not isinstance(text, str):
217
+ return _error_response("Text must be a string")
218
+
219
+ spec = {
220
+ "tool": "mcp_puppeteer_puppeteer_fill",
221
+ "parameters": {
222
+ "selector": selector,
223
+ "value": text,
224
+ },
225
+ }
226
+
227
+ return _success_response(spec)
228
+
229
+ except Exception as e:
230
+ return _error_response(str(e))
231
+
232
+
233
+ def browser_screenshot(name: str, selector: Optional[str] = None) -> Dict[str, Any]:
234
+ """Take a screenshot of the page or a specific element.
235
+
236
+ Generates a Puppeteer MCP tool call spec for taking a screenshot.
237
+
238
+ Args:
239
+ name: Name for the screenshot.
240
+ selector: Optional CSS selector for a specific element to capture.
241
+
242
+ Returns:
243
+ A dict with ``success``, ``result`` (the tool call spec), and ``error``.
244
+ """
245
+ if not _is_enabled():
246
+ return _disabled_response()
247
+
248
+ try:
249
+ if not name or not isinstance(name, str):
250
+ return _error_response("Name must be a non-empty string")
251
+
252
+ params: Dict[str, Any] = {"name": name}
253
+ if selector:
254
+ params["selector"] = selector
255
+
256
+ spec = {
257
+ "tool": "mcp_puppeteer_puppeteer_screenshot",
258
+ "parameters": params,
259
+ }
260
+
261
+ # Track screenshot
262
+ session.record_screenshot(name)
263
+
264
+ return _success_response(spec)
265
+
266
+ except Exception as e:
267
+ return _error_response(str(e))
268
+
269
+
270
+ def browser_evaluate(script: str) -> Dict[str, Any]:
271
+ """Execute JavaScript in the browser console.
272
+
273
+ Generates a Puppeteer MCP tool call spec for evaluating JavaScript.
274
+
275
+ Args:
276
+ script: JavaScript code to execute.
277
+
278
+ Returns:
279
+ A dict with ``success``, ``result`` (the tool call spec), and ``error``.
280
+ """
281
+ if not _is_enabled():
282
+ return _disabled_response()
283
+
284
+ try:
285
+ if not script or not isinstance(script, str):
286
+ return _error_response("Script must be a non-empty string")
287
+
288
+ spec = {
289
+ "tool": "mcp_puppeteer_puppeteer_evaluate",
290
+ "parameters": {
291
+ "script": script,
292
+ },
293
+ }
294
+
295
+ return _success_response(spec)
296
+
297
+ except Exception as e:
298
+ return _error_response(str(e))
299
+
300
+
301
+ # =============================================================================
302
+ # CLI Interface
303
+ # =============================================================================
304
+
305
+
306
+ def _cli_main():
307
+ """CLI entry point for browser_tool.py."""
308
+ import argparse
309
+
310
+ parser = argparse.ArgumentParser(
311
+ description="OMG Browser Tool — Puppeteer MCP wrapper for browser automation",
312
+ formatter_class=argparse.RawDescriptionHelpFormatter,
313
+ )
314
+ parser.add_argument("--navigate", dest="navigate_url", help="Navigate to URL")
315
+ parser.add_argument("--click", dest="click_selector", help="Click CSS selector")
316
+ parser.add_argument(
317
+ "--type", dest="type_args", nargs=2, metavar=("SELECTOR", "TEXT"),
318
+ help="Type text into selector",
319
+ )
320
+ parser.add_argument(
321
+ "--screenshot", dest="screenshot_name", help="Take screenshot with name",
322
+ )
323
+ parser.add_argument(
324
+ "--screenshot-selector", dest="screenshot_selector", default=None,
325
+ help="Optional CSS selector for screenshot (use with --screenshot)",
326
+ )
327
+ parser.add_argument("--evaluate", dest="eval_script", help="Evaluate JavaScript")
328
+ parser.add_argument(
329
+ "--dry-run", action="store_true",
330
+ help="Print the tool call spec without executing",
331
+ )
332
+ parser.add_argument(
333
+ "--session-info", action="store_true",
334
+ help="Show current session state",
335
+ )
336
+
337
+ args = parser.parse_args()
338
+
339
+ enabled = _is_enabled()
340
+
341
+ # Session info
342
+ if args.session_info:
343
+ print(json.dumps({
344
+ "session": session.to_dict(),
345
+ "enabled": enabled,
346
+ }, indent=2))
347
+ return
348
+
349
+ # Dry-run navigate
350
+ if args.dry_run and args.navigate_url:
351
+ result = browser_navigate(args.navigate_url)
352
+ print(json.dumps({
353
+ "dry_run": True,
354
+ "operation": "navigate",
355
+ "url": args.navigate_url,
356
+ "enabled": enabled,
357
+ "spec": result,
358
+ }, indent=2))
359
+ return
360
+
361
+ # Dry-run click
362
+ if args.dry_run and args.click_selector:
363
+ result = browser_click(args.click_selector)
364
+ print(json.dumps({
365
+ "dry_run": True,
366
+ "operation": "click",
367
+ "selector": args.click_selector,
368
+ "enabled": enabled,
369
+ "spec": result,
370
+ }, indent=2))
371
+ return
372
+
373
+ # Dry-run type
374
+ if args.dry_run and args.type_args:
375
+ result = browser_type(args.type_args[0], args.type_args[1])
376
+ print(json.dumps({
377
+ "dry_run": True,
378
+ "operation": "type",
379
+ "selector": args.type_args[0],
380
+ "text": args.type_args[1],
381
+ "enabled": enabled,
382
+ "spec": result,
383
+ }, indent=2))
384
+ return
385
+
386
+ # Dry-run screenshot
387
+ if args.dry_run and args.screenshot_name:
388
+ result = browser_screenshot(args.screenshot_name, args.screenshot_selector)
389
+ print(json.dumps({
390
+ "dry_run": True,
391
+ "operation": "screenshot",
392
+ "name": args.screenshot_name,
393
+ "selector": args.screenshot_selector,
394
+ "enabled": enabled,
395
+ "spec": result,
396
+ }, indent=2))
397
+ return
398
+
399
+ # Dry-run evaluate
400
+ if args.dry_run and args.eval_script:
401
+ result = browser_evaluate(args.eval_script)
402
+ print(json.dumps({
403
+ "dry_run": True,
404
+ "operation": "evaluate",
405
+ "script": args.eval_script,
406
+ "enabled": enabled,
407
+ "spec": result,
408
+ }, indent=2))
409
+ return
410
+
411
+ # Non-dry-run: check feature flag
412
+ if not enabled:
413
+ print(json.dumps({
414
+ "error": "Browser feature is disabled (OMG_BROWSER_ENABLED=false)",
415
+ }))
416
+ sys.exit(1)
417
+
418
+ # Execute operations
419
+ if args.navigate_url:
420
+ result = browser_navigate(args.navigate_url)
421
+ print(json.dumps(result, indent=2))
422
+ return
423
+
424
+ if args.click_selector:
425
+ result = browser_click(args.click_selector)
426
+ print(json.dumps(result, indent=2))
427
+ return
428
+
429
+ if args.type_args:
430
+ result = browser_type(args.type_args[0], args.type_args[1])
431
+ print(json.dumps(result, indent=2))
432
+ return
433
+
434
+ if args.screenshot_name:
435
+ result = browser_screenshot(args.screenshot_name, args.screenshot_selector)
436
+ print(json.dumps(result, indent=2))
437
+ return
438
+
439
+ if args.eval_script:
440
+ result = browser_evaluate(args.eval_script)
441
+ print(json.dumps(result, indent=2))
442
+ return
443
+
444
+ parser.print_help()
445
+
446
+
447
+ if __name__ == "__main__":
448
+ _cli_main()