# MacroCLI — Agent Harness SOP ## What Is This? **MacroCLI** is a layered CLI that turns valuable GUI workflows into parameterized, agent-callable macros. The agent sends one command: ```bash cli-anything-macrocli macro run export_png --param output=/tmp/out.png --json ``` The system handles everything else: parameter validation, precondition checks, backend selection, step execution, postcondition verification, and structured result output. The agent never touches the GUI directly. ## Architecture ``` Agent └─▶ cli-anything-macrocli macro run --param k=v --json (L6: CLI) │ MacroRuntime (L5) │ 1. Validate params against MacroDefinition schema │ 2. Check preconditions (file_exists, process_running, …) │ 3. For each step: │ RoutingEngine → select backend by priority (L3) │ Backend.execute(step, resolved_params) (L2) │ 4. Check postconditions │ 5. Collect declared outputs │ 6. Record telemetry in ExecutionSession └─▶ { success, output, error, telemetry } ``` ## Layer Mapping | Layer | Name | Implementation | |-------|------|---------------| | L7 | Agent Task Interface | Caller (any AI agent) | | L6 | Unified CLI Entry | `macrocli_cli.py` — Click CLI | | L5 | Macro Execution Runtime | `core/runtime.py` | | L4 | Parameterized Macro Model | `core/macro_model.py` + `macro_definitions/*.yaml` | | L3 | Backend Routing Engine | `core/routing.py` | | L2 | Execution Backends | `backends/` (7 backends) | | L1 | Target Application | Any GUI-first or closed-source app | ## Execution Backends | Backend | Priority | Trigger | Use case | |---------|----------|---------|----------| | `native_api` | 100 | `backend: native_api` | subprocess / shell commands | | `gui_macro` | 80 | `backend: gui_macro` | precompiled coordinate replay (pyautogui) | | `visual_anchor` | 75 | `backend: visual_anchor` | template-matching click/type (requires `[visual]`) | | `file_transform` | 70 | `backend: file_transform` | XML, JSON, text file editing | | `gui_agent` | 60 | `backend: gui_agent` | vision-model-driven automation (requires `[gui_agent]`) | | `semantic_ui` | 50 | `backend: semantic_ui` | accessibility API + keyboard (xdotool) | | `recovery` | 10 | `backend: recovery` | retry + fallback orchestration | The RoutingEngine respects the step's explicit `backend:` field; if that backend is unavailable it walks down the priority list. ## Macro Definition Format Macros live in `cli_anything/macrocli/macro_definitions/` as YAML files: ```yaml name: export_png version: "1.0" description: Export the active diagram to PNG. parameters: output: type: string required: true example: /tmp/diagram.png preconditions: - process_running: draw.io - file_exists: /path/to/input.drawio steps: - id: export backend: native_api action: run_command params: command: [draw.io, --export, --output, "${output}", input.drawio] timeout_ms: 30000 on_failure: fail # or: skip | continue postconditions: - file_exists: ${output} - file_size_gt: - ${output} - 100 outputs: - name: exported_file path: ${output} agent_hints: danger_level: safe side_effects: [creates_file] reversible: true ``` ### Supported Condition Types | Type | Args | Checks | |------|------|--------| | `file_exists` | path | `os.path.exists(path)` | | `file_size_gt` | [path, min_bytes] | `os.stat(path).st_size > min_bytes` | | `process_running` | name | `pgrep -x name` or psutil | | `env_var` | name | `name in os.environ` | | `always` | true/false | constant pass/fail | ## Package Layout ``` macrocli/ └── agent-harness/ ├── setup.py entry_point: cli-anything-macrocli └── cli_anything/macrocli/ ├── macrocli_cli.py Main Click CLI ├── macro_definitions/ YAML macro registry │ ├── manifest.yaml │ └── examples/ │ ├── export_file.yaml │ ├── transform_json.yaml │ └── undo_last.yaml ├── core/ │ ├── macro_model.py MacroDefinition + YAML loader │ ├── registry.py MacroRegistry │ ├── routing.py RoutingEngine │ ├── runtime.py MacroRuntime (full lifecycle) │ └── session.py ExecutionSession + telemetry ├── backends/ │ ├── base.py Backend ABC + StepResult │ ├── native_api.py subprocess backend │ ├── file_transform.py XML/JSON/text backend │ ├── semantic_ui.py accessibility backend │ ├── visual_anchor.py template-matching backend │ ├── gui_agent.py vision-model automation backend │ ├── gui_macro.py compiled replay backend │ └── recovery.py retry/fallback backend ├── skills/SKILL.md Agent-readable skill definition ├── utils/repl_skin.py Unified REPL skin (cli-anything standard) └── tests/ ├── test_core.py Unit tests (49 tests, no external deps) └── test_full_e2e.py E2E + CLI subprocess tests (15 tests) ``` ## Installation ```bash cd macrocli/agent-harness pip install -e . ``` **Runtime dependencies:** Python 3.10+, PyYAML, click, prompt-toolkit. **Optional extras:** ```bash pip install -e ".[visual]" # visual_anchor backend (mss, Pillow, numpy, pynput) pip install -e ".[gui_agent]" # gui_agent backend (openai, mss, Pillow) pip install -e ".[all]" # everything ``` **gui_agent backend configuration:** The `gui_agent` backend uses the OpenAI SDK and is compatible with any OpenAI-compatible API. Configure via environment variables: | Variable | Description | |--------------------|---------------------------------------------| | `MACROCLI_MODEL` | Model name (required, e.g. `gpt-4o`) | | `MACROCLI_API_KEY` | API key for the provider | | `MACROCLI_BASE_URL`| Base URL (only needed for non-OpenAI hosts) | **Other optional dependencies:** - `xdotool` — semantic_ui backend on Linux - `pyautogui` — gui_macro backend - `psutil` — richer process_running checks ## Running Tests ```bash cd macrocli/agent-harness python3 -m pytest cli_anything/macrocli/tests/ -v -s # 64 passed ``` ## Key Design Decisions **Why YAML macros, not Python?** YAML macros are readable by agents without running code, inspectable via `macro info`, and editable without touching the harness source. **Why 7 backends?** Real GUI applications expose many different control surfaces. The routing engine picks the most reliable one available — the agent doesn't need to know which one ran. The `visual_anchor` backend uses template matching for robust UI element detection, while `gui_agent` uses vision models for dynamic decision-making when the UI state is unpredictable. **Why preconditions and postconditions?** Agents operate in environments where state is uncertain. Failing loudly before execution (preconditions) and verifying after (postconditions) catches problems the agent can act on. **Why `on_failure: skip | continue`?** Some macro steps are best-effort (e.g., confirming a dialog that may or may not appear). Skipping lets the macro continue to the real work.