diff --git a/README.md b/README.md index adb97c5..c6fba59 100644 --- a/README.md +++ b/README.md @@ -461,6 +461,16 @@ sources: codex: { timeout: 300 } # argv/prompt also overridable, e.g. sources.codex.argv ``` +Agents run **safely by default** (no approval-bypass). For unattended runs on +sources you trust, **`--yolo`** flips on the danger opt-ins for that ingest -- +codex `--dangerously-bypass-approvals-and-sandbox`, hermes `--yolo`, and the +`command` shell (equivalent to setting `sources.codex.unsafe_bypass` / +`sources.hermes.unsafe_yolo` / `sources.command.allow_shell`): + +```bash +doxa ingest https://target.example --via codex --yolo +``` + **Choose how to scrape** with `--mode` / `--prompt` (agent + `command` fetchers). `--prompt` is free-form, so the agent can use whatever it has -- SERP search, browser automation, structured extraction, platform-specific endpoints -- to diff --git a/doxa/cli.py b/doxa/cli.py index 4de8084..1d469f3 100644 --- a/doxa/cli.py +++ b/doxa/cli.py @@ -322,8 +322,20 @@ def cb(index: int, total: int) -> None: return cb +def _apply_yolo(config: dict[str, Any]) -> None: + """doxa's yolo mode: let agent/command fetchers run unattended for this ingest -- + codex bypass-approvals, hermes --yolo, and the command shell. Trusted sources only.""" + sources = config.setdefault("sources", {}) + sources.setdefault("codex", {})["unsafe_bypass"] = True + sources.setdefault("hermes", {})["unsafe_yolo"] = True + sources.setdefault("command", {})["allow_shell"] = True + + def cmd_ingest(args: argparse.Namespace) -> int: config = load_config(args.config, allow_demo_default=False) + if getattr(args, "yolo", False): + _apply_yolo(config) + _hint("yolo: agent fetchers run with bypass/--yolo + shell enabled for this ingest -- trusted sources only.") store = JsonlStore(config) source_args = args.source if isinstance(args.source, list) else [args.source] stdin_text = sys.stdin.read() if "-" in source_args else None @@ -744,6 +756,11 @@ def build_parser() -> argparse.ArgumentParser: "--prompt", help="Fetch/extract instruction for agent (claude/codex/hermes) or command fetchers, e.g. 'extract product name, price, reviews as JSON'.", ) + ingest.add_argument( + "--yolo", + action="store_true", + help="Run agent/command fetchers unattended: codex --dangerously-bypass-approvals, hermes --yolo, command shell. Trusted sources only.", + ) ingest.set_defaults(func=cmd_ingest) index = subparsers.add_parser("index", help="Build optional Postgres/pgvector semantic index.") diff --git a/tests/test_sources_fetchers.py b/tests/test_sources_fetchers.py index 30f460f..2ed17f3 100644 --- a/tests/test_sources_fetchers.py +++ b/tests/test_sources_fetchers.py @@ -149,3 +149,20 @@ def test_command_fetcher_substitutes_prompt() -> None: config = {"sources": {"command": {"argv": ["python3", "-c", "import sys;print('# Cmd\\n\\n'+sys.argv[1])", "{prompt}"]}}} rec = load_url("https://x", fetcher="command", config=config, fetch_prompt="do the thing") assert "do the thing" in rec.text + + +def test_yolo_enables_unattended_agent_fetching() -> None: + from doxa.cli import _apply_yolo, build_parser + from doxa.sources.fetchers import _AGENT_PRESETS, _CODEX_BYPASS_FLAG, _agent_argv + + assert build_parser().parse_args(["ingest", "-", "--yolo"]).yolo is True + + config: dict = {} + _apply_yolo(config) + s = config["sources"] + assert s["codex"]["unsafe_bypass"] is True + assert s["hermes"]["unsafe_yolo"] is True + assert s["command"]["allow_shell"] is True + # the agent argv builder honors the flipped opt-ins + assert _CODEX_BYPASS_FLAG in _agent_argv("codex", _AGENT_PRESETS["codex"]["argv"], s["codex"]) + assert "--yolo" in _agent_argv("hermes", _AGENT_PRESETS["hermes"]["argv"], s["hermes"])