Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions codeframe/cli/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -1998,7 +1998,7 @@ def work_start(
engine: Optional[str] = typer.Option(
None,
"--engine",
help="Agent engine: react (default), plan (legacy), claude-code, opencode, or built-in",
help="Agent engine: react (default), plan (legacy), claude-code, codex, opencode, or built-in",
),
stall_timeout: int = typer.Option(
300,
Expand Down Expand Up @@ -2059,10 +2059,12 @@ def work_start(
task = matching[0]

# Validate API key before creating run record (avoids dangling IN_PROGRESS state)
# External engines (claude-code, opencode) manage their own authentication
if execute:
from codeframe.core.engine_registry import is_external_engine
if not is_external_engine(engine):
if engine == "codex":
from codeframe.cli.validators import require_openai_api_key
require_openai_api_key()
elif not is_external_engine(engine):
from codeframe.cli.validators import require_anthropic_api_key
require_anthropic_api_key()

Expand Down Expand Up @@ -2888,7 +2890,7 @@ def batch_run(
engine: Optional[str] = typer.Option(
None,
"--engine",
help="Agent engine: react (default), plan (legacy), claude-code, opencode, or built-in",
help="Agent engine: react (default), plan (legacy), claude-code, codex, opencode, or built-in",
),
stall_timeout: int = typer.Option(
300,
Expand Down Expand Up @@ -2989,9 +2991,11 @@ def batch_run(
return

# Validate API key before batch execution
# External engines (claude-code, opencode) manage their own authentication
from codeframe.core.engine_registry import is_external_engine
if not is_external_engine(engine):
if engine == "codex":
from codeframe.cli.validators import require_openai_api_key
require_openai_api_key()
elif not is_external_engine(engine):
from codeframe.cli.validators import require_anthropic_api_key
require_anthropic_api_key()

Expand Down
37 changes: 37 additions & 0 deletions codeframe/cli/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,40 @@ def require_anthropic_api_key() -> str:
"Set it in your environment or add it to a .env file."
)
raise typer.Exit(1)


def require_openai_api_key() -> str:
"""Ensure OPENAI_API_KEY is available, loading from .env if needed.

Checks os.environ first. If not found, attempts to load from .env files
(~/.env as base, then cwd/.env with override). If found after loading,
sets in os.environ so subprocesses inherit it.

Returns:
The API key string.

Raises:
typer.Exit: If the key cannot be found anywhere.
"""
key = os.getenv("OPENAI_API_KEY")
if key:
return key

cwd_env = Path.cwd() / ".env"
home_env = Path.home() / ".env"

if home_env.exists():
load_dotenv(home_env)
if cwd_env.exists():
load_dotenv(cwd_env, override=True)

key = os.getenv("OPENAI_API_KEY")
if key:
os.environ["OPENAI_API_KEY"] = key
return key

console.print(
"[red]Error:[/red] OPENAI_API_KEY is not set. "
"Set it in your environment or add it to a .env file."
)
raise typer.Exit(1)
2 changes: 2 additions & 0 deletions codeframe/core/adapters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
BuiltinReactAdapter,
)
from codeframe.core.adapters.claude_code import ClaudeCodeAdapter
from codeframe.core.adapters.codex import CodexAdapter
from codeframe.core.adapters.opencode import OpenCodeAdapter
from codeframe.core.adapters.subprocess_adapter import SubprocessAdapter
from codeframe.core.adapters.verification_wrapper import VerificationWrapper
Expand All @@ -25,6 +26,7 @@
"BuiltinPlanAdapter",
"BuiltinReactAdapter",
"ClaudeCodeAdapter",
"CodexAdapter",
"OpenCodeAdapter",
"SubprocessAdapter",
"VerificationWrapper",
Expand Down
Loading
Loading