Run one or more prompts in headless mode. Returns the exit code.
(options: HeadlessOptions)
| 106 | |
| 107 | |
| 108 | def run_headless(options: HeadlessOptions) -> int: |
| 109 | """Run one or more prompts in headless mode. Returns the exit code.""" |
| 110 | |
| 111 | if options.output_format not in OUTPUT_FORMATS: |
| 112 | cli_error( |
| 113 | f"error: --output-format must be one of {', '.join(OUTPUT_FORMATS)}", 2 |
| 114 | ) |
| 115 | if options.input_format not in INPUT_FORMATS: |
| 116 | cli_error( |
| 117 | f"error: --input-format must be one of {', '.join(INPUT_FORMATS)}", 2 |
| 118 | ) |
| 119 | if options.input_format == "stream-json" and options.output_format != "stream-json": |
| 120 | cli_error( |
| 121 | "error: --input-format stream-json requires --output-format stream-json", |
| 122 | 2, |
| 123 | ) |
| 124 | |
| 125 | stdout = options.stdout or sys.stdout |
| 126 | stderr = options.stderr or sys.stderr |
| 127 | stdin = options.stdin or sys.stdin |
| 128 | |
| 129 | # ch02 round-3 GAP B: warm the user/system context memos now so the |
| 130 | # CLAUDE.md walk and git probes overlap with provider + registry |
| 131 | # construction below instead of running inside the first turn. |
| 132 | # Mirrors TS main.tsx:1973-1990 (non-interactive early kicks; trust |
| 133 | # is implicit in -p mode and was granted by run_pre_action). |
| 134 | # MUST use the resolved workspace_root, not the process cwd — the |
| 135 | # memos are key-less and first-writer pins the content the query |
| 136 | # path (which passes workspace_root) will read. |
| 137 | workspace_root = options.workspace_root or Path.cwd() |
| 138 | from src.deferred_init import start_deferred_prefetches |
| 139 | |
| 140 | start_deferred_prefetches(cwd=str(workspace_root)) |
| 141 | |
| 142 | provider_name = options.provider_name or get_default_provider() |
| 143 | try: |
| 144 | provider_cfg = get_provider_config(provider_name) |
| 145 | except Exception as exc: |
| 146 | cli_error(f"error: unable to load provider config: {exc}", 2) |
| 147 | # Config api_key wins; fall back to the provider's known env vars (e.g. |
| 148 | # ``DEEPSEEK_API_KEY``) so a freshly-added provider works without ``login``. |
| 149 | # Local providers (Ollama / vLLM / SGLang) need no key. |
| 150 | api_key = resolve_api_key(provider_name, provider_cfg) |
| 151 | if not api_key and provider_requires_api_key(provider_name): |
| 152 | cli_error( |
| 153 | f"error: API key for provider '{provider_name}' is not configured. " |
| 154 | "Run `clawcodex login` to set it up.", |
| 155 | 2, |
| 156 | ) |
| 157 | |
| 158 | provider_cls = get_provider_class(provider_name) |
| 159 | model = options.model or provider_cfg.get("default_model") |
| 160 | provider = provider_cls( |
| 161 | api_key=api_key, |
| 162 | base_url=provider_cfg.get("base_url"), |
| 163 | model=model, |
| 164 | ) |
| 165 |